注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

gmd20的个人空间

// 编程和生活

 
 
 

日志

 
 

linux网络协议栈:cisco vpn client实现代码和Linux抓包接口的学习(一)  

2010-12-01 21:40:10|  分类: linux相关 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

这几天在做一个网络包相关的bug,都是通过 Linux上的思科vpn客户端连到客户的服务器上面去测试的,抓包测试的时候,才发现wireshark没有办法把cisco vpn client接口的返回包抓下来,出去的包是可以抓的到的。bug终于有点头绪了,回来没事做到csdn逛了逛,想起这个问题来,就找了cisco的vpn client的代码,看看能不能发现是什么问题,

说Linux的网络的,发现两本书好像讲的不错

“Understanding_Linux_Network_Internals.chm” “?Prentice.Hall.The.Linux.Networking.Architecture.Design.and.Implementation.of.Network.Protocols.in.the.Linux.Kernel.chm”

反正我这种非专业人士读了,感觉对些网络概念的理解有很大帮助。

cisco的vpn client代码,网上很容易找到,我找到是这个 vpnclient-linux-x86_64-4.8.02.0030-k9-AMD64_ONLY_by_t3x.tar.gz,这个应该是没有打了新的2.6.35那些新版内核的补丁的,新版的内核net_device 结构变量,把那些hard_start_xmit函数整理到一个net_device_ops结构里面去了,所以在新的内核里面还要改一下。

先大概浏览vpn核模块的代码,关键的代码都是在?interceptor.c文件里面,之前也稍微看过一点点,有点了解。

像这种vpn的实现,就理解来说无非就是在网络包出口和入口的两个地方做点重新打包修改的什么,封装成IPSEC等协议吧,Cisco的这个代码还是很简单清晰的。

--------==出口的====-------

他替换了网络设备net_device 结构的hard_start_xmit (新版内核是net_device_ops->?ndo_start_xmit ) 函数,然后网络设备出去的包都会先调到他自己的挂载函数了,就可以在这里做包的修改和封装了。 因为自己也用了一些vpn头部,所以需要设置一下网络设备的什么mtu啦等。还有他替换了所有系统的以太网类型的设备,反正就是认为以太网类型的都是他可以用来做vpn的??? 不知道还有没有什么比直接替换挂钩更好的办法不? 新版的ndo_start_xmit都是常量指针来的了,用这种办法还要强制转换替换。不过这种办法很简单容易理解就是了。不过我们之前发现这种实现办法和vmware的虚拟机虚拟网卡有冲突,当时我们是在vpn client里面直接跳过虚拟网卡来处理的。

    ndo_start_xmit 是一个很有意思的函数,比如可以自己搞个假的网络设备,然后调用真的网络设备的这个函数发送出去。中间可以自己做些修改什么的。就像cisco 这个vpn 客户端的内核模块做的一样。当然我也知道还有其他这样用的驱动^_^

----------------入口点---------------------

他这个实现很有意思,他代码是这样写的

?    /* find the handler for inbound IP packets by adding a dummy handler
     * for that packet type into the kernel. Because the packet handlers
     * are stored in a hash table, we'll be able to pull the original
     * ip packet handler out of the list that dummy_pt was inserted into.*/
    kernel_memset(&dummy_pt, 0, sizeof(dummy_pt));
    dummy_pt.type = htons(ETH_P_IP);
    dummy_pt.func = recv_ip_packet_handler;

    dev_add_pack(&dummy_pt);
    /* this should be the original IP packet handler */
    default_pt = PACKET_TYPE_NEXT(&dummy_pt);
    /* there may be more than one other packet handler in our bucket,
     * so look through all the buckets */
    while (default_pt != NULL && default_pt->type != htons(ETH_P_IP))
    {
        default_pt = PACKET_TYPE_NEXT(default_pt);
    }
    if (!default_pt)
    {
        printk(KERN_DEBUG "No default handler found for %x protocol!!\n",
               dummy_pt.type);
        dev_remove_pack(&dummy_pt);
        error = VPNIFUP_FAILURE;
        goto error_exit;
    }
    /*remove the dummy handler handler */
    original_ip_handler.pt = default_pt;
    dev_remove_pack(&dummy_pt);

    /*and replace the original handler function with our function */
    original_ip_handler.orig_handler_func = original_ip_handler.pt->func;
    original_ip_handler.pt->func = recv_ip_packet_handler;

先通过?dev_add_pack 函数注册一个?ETH_P_IP协议的处理函数,得到一个链表指针,然后通过这个链表指针找到系统原有ip处理函数(这个应该是ip_rcv 函数?http://lxr.linux.no/linux+v2.6.36/net/ipv4/ip_input.c#L375)。为的就是找到原有函数,进行挂钩,这样就能拦截所有系统ip包入口点。这个也是很有趣的东东,之前我没有见过,刚刚发现?Understanding Linux Network Internals 一书的 “13.4. Protocol Handler Registration” 小节有说到?用?dev_add_pack 和 ?dev_remove_pack 函数来系统里面注册自己网络协议的办法,感兴趣的可以去看一下介绍,还有参考一下上面的例子。

我不知道他们这里怎么选择了这种办法,要是我的话,我可能考虑使用netfilter应该也是可以实现的,毕竟我感觉这种hook的办法可能不是很正式。

---------------------------------------------------------------------------------------

来到这里,问题应该比较清晰了,wireshark不能抓到回来的网络包,应该就是这个模块里面上传个上一级的地方少做了哪些步骤了。

我记得第一本书里面说了抓包是怎么实现的了,还有一个图,再自己看了一下,原文里面有一段是这么写的:


13.1. Overview of Network Stack

Network sniffers such as tcpdump and Ethereal are common users of AF_SOCKET sockets. You can see from the figure that

AF_PACKET sockets hand frames directly to dev_queue_xmit, and receive ingress frames directly from the network protocol

dispatcher routine (this latter point is addressed in Chapter 10).

结合书上所讲和内核源代码,Linux 的raw socket抓包实现应该是往系统注册一个PF_PACKET网络协议,然后直接把原始网络包上传给网络抓包程序。raw socket的实现是在http://lxr.linux.no/linux+v2.6.36/net/packet/af_packet.c 里面做的吧。

系统调用各个协议来处理网络包应该是这个地方

http://lxr.linux.no/linux+v2.6.36/net/core/dev.c#L2817
static int __netif_receive_skb(struct sk_buff *skb)

2878        list_for_each_entry_rcu(ptype, &ptype_all, list) {
2879                if (ptype->dev == null_or_orig || ptype->dev == skb->dev ||
2880                    ptype->dev == orig_dev) {
2881                        if (pt_prev)
2882                                ret = deliver_skb(skb, pt_prev, orig_dev); //调用各个协议的处理函数
2883                        pt_prev = ptype;
2884                }
2885        }

2614static inline int deliver_skb(struct sk_buff *skb,
2615                              struct packet_type *pt_prev,
2616                              struct net_device *orig_dev)
2617{
2618        atomic_inc(&skb->users);
2619        return pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
2620}
2621

而注册的抓包协议PF_PACKET的处理函数应该是这个
static int packet_rcv_spkt(struct sk_buff *skb, struct net_device *dev,
                           struct packet_type *pt, struct net_device *orig_dev)

是在这个地方注册进去的
http://lxr.linux.no/linux+v2.6.36/net/packet/af_packet.c#L1493
/*
1447 *      Create a packet of type SOCK_PACKET.
1448 */
1449
1450static int packet_create(struct net *net, struct socket *sock, int protocol,
1451                         int kern)


1477        po = pkt_sk(sk);
1478        sk->sk_family = PF_PACKET;
1479        po->num = proto;


   if (sock->type == SOCK_PACKET)
1493                po->prot_hook.func = packet_rcv_spkt;
1494
1495        po->prot_hook.af_packet_priv = sk;
1496
1497        if (proto) {
1498                po->prot_hook.type = proto;
1499                dev_add_pack(&po->prot_hook);   注册协议
1500                sock_hold(sk);
1501                po->running = 1;
1502        }

由此就可以大概猜测得出,cisco vpn抓包没能捕获回来的包,应该就是解码的时候?packet_rcv_spkt 函数能能认出解码之前的包,直接丢弃了??应该是解码后,在传个这个函数处理的吧。我们再来看一下cisco自己搞的ip 协议处理函数。

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,14)
static int
recv_ip_packet_handler(struct sk_buff *skb,
                       struct net_device *dev,
                       struct packet_type *type,
                       struct net_device *orig_dev)
#else
static int
recv_ip_packet_handler(struct sk_buff *skb,
                       struct net_device *dev,
                       struct packet_type *type)
#endif
{
    int rc2 = 0;
    int tmp_rc = 0;
    CNISTATUS rc = 0;
    CNIPACKET NewPacket = NULL;
    CNIFRAGMENT Fragment = NULL;
    CNIFRAGMENT MacHdr = NULL;
    PVOID lpReceiveContext = NULL;
    ULONG ulFinalPacketSize;
    BINDING *pBinding = NULL;
    struct ethhdr ppp_dummy_buf;
    int hard_header_len;

#ifdef MOD_INC_AND_DEC
    MOD_INC_USE_COUNT;
#endif
    if (dev->type == ARPHRD_LOOPBACK)
    {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,14)
        rc2 = original_ip_handler.orig_handler_func(skb, dev, type, dev);
#else
        rc2 = original_ip_handler.orig_handler_func(skb, dev, type);
#endif
        goto exit_gracefully;
    }

    /* Don't handle non-eth non-ppp packets */
    if (!supported_device(dev))
    {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,14)
        rc2 = original_ip_handler.orig_handler_func(skb, dev, type, dev);
#else
        rc2 = original_ip_handler.orig_handler_func(skb, dev, type);
#endif
        goto exit_gracefully;
    }

    pBinding = getbindingbydev(dev);

    /* if we don't have a binding, this is a new device that
     * has been brought up while the tunnel is up. For now,
     * just pass the packet
     */
    if (!pBinding)
    {
        static int firsttime = 1;
        if (firsttime)
        {
            printk(KERN_DEBUG "RECV: new dev %s detected\n", dev->name);
            firsttime = 0;
        }

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,14)
        rc2 = original_ip_handler.orig_handler_func(skb, dev, type, dev);
#else
        rc2 = original_ip_handler.orig_handler_func(skb, dev, type);
#endif
        goto exit_gracefully;
    }

    //only need to handle IP packets.
    if (skb->protocol != htons(ETH_P_IP))
    {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,14)
        rc2 = original_ip_handler.orig_handler_func(skb, dev, type, dev);
#else
        rc2 = original_ip_handler.orig_handler_func(skb, dev, type);
#endif
        goto exit_gracefully;
    }

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,19)
    if (skb->ip_summed == CHECKSUM_PARTIAL)
#else
    if (skb->ip_summed == CHECKSUM_HW)
#endif
    {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,7)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,10)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,19)
       if (skb_checksum_help(skb))
#else
       if (skb_checksum_help(skb,1))
#endif
#else
       if (skb_checksum_help(&skb,1))
#endif // LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,10)
       {
           dev_kfree_skb(skb);
           skb = NULL;
           goto exit_gracefully;
       }
#else
       skb->ip_summed = CHECKSUM_NONE;
#endif
    }

    reset_inject_status(&pBinding->recv_stat);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,22)
    if (skb->mac_header)
#else
    if (skb->mac.raw)
#endif
    {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,22)
        hard_header_len = skb->data - skb->mac_header;
#else
        hard_header_len = skb->data - skb->mac.raw;
#endif
        if ((hard_header_len < 0) || (hard_header_len > skb_headroom(skb)))
        {
            printk(KERN_DEBUG "bad hh len %d\n", hard_header_len);
            hard_header_len = 0;
        }
    }
    else
    {
        hard_header_len = 0;
    }

    pBinding->recv_real_hh_len = hard_header_len;

    switch (hard_header_len)
    {
    case ETH_HLEN:
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,22)
        CniNewFragment(ETH_HLEN, skb->mac_header, &MacHdr, CNI_USE_BUFFER);
#else
        CniNewFragment(ETH_HLEN, skb->mac.raw, &MacHdr, CNI_USE_BUFFER);
#endif
        break;
    case IPPP_MAX_HEADER:
    case 0:
        MacHdr = build_ppp_fake_mac_frag(&ppp_dummy_buf);
        break;
    default:
        printk(KERN_DEBUG "unknown mac header length (%d)\n", hard_header_len);
        dev_kfree_skb(skb);
        skb = NULL;
        goto exit_gracefully;
    }

    CniNewFragment(skb->len, skb->data, &Fragment, CNI_USE_BUFFER);
    ulFinalPacketSize = skb->len;

    rc = CNICallbackTable.Receive((void *) &pBinding,
                                  &lpReceiveContext,
                                  &MacHdr,
                                  &Fragment, &NewPacket, &ulFinalPacketSize);

    switch (rc)
    {
    case CNI_CONSUME:
        tmp_rc = CNICallbackTable.ReceiveComplete(pBinding,
                                                  lpReceiveContext, NewPacket);

        dev_kfree_skb(skb);
        rx_packets++;
        if (pBinding->recv_stat.called)
        {
            rc2 = pBinding->recv_stat.rc;
        }
        break;
    case CNI_CHAIN:
        tmp_rc = CNICallbackTable.ReceiveComplete(pBinding,
                                                  lpReceiveContext, NewPacket);

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,14)
        rc2 = original_ip_handler.orig_handler_func(skb, dev, type, dev);   //调用ip协议处理函数
#else
        rc2 = original_ip_handler.orig_handler_func(skb, dev, type);
#endif

        if (pBinding->recv_stat.called)
        {
            rc2 = pBinding->recv_stat.rc;
        }

        break;
    case CNI_DISCARD:
        dev_kfree_skb(skb);
        rx_dropped++;
        break;
    default:
        printk(KERN_DEBUG "RECV: Unhandled case in %s rc was %x\n",
               __FUNCTION__, (uint) rc);

        dev_kfree_skb(skb);
        rx_dropped++;
    }
exit_gracefully:
    if (MacHdr)
    {
        CniReleaseFragment(MacHdr, CNI_KEEP_BUFFERS);
    }
    if (Fragment)
    {
        CniReleaseFragment(Fragment, CNI_KEEP_BUFFERS);
    }
#ifdef MOD_INC_AND_DEC
    MOD_DEC_USE_COUNT;
#endif

    return rc2;
}

可以看到他解码之后,是调用了?系统原有的IP处理函数的(ip_rcv)我看到,这个ip_rcv 也是调用了NF_HOOK函数的,所有那些netfilter的iptables那些过滤设施还能正常工作。而我们要解决抓包不成功的问题,只要模拟他的办法,用dev_add_pack dev_remove_pack 得到PF_PACKET的原有函数地址,然后最后再调用多一次处理这个解码以后的包应该就可以了。至此问题解决,明天去到公司再测试一下。

  评论这张
 
阅读(1041)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017