Chinaunix首页 | 论坛 | 博客
  • 博客访问: 187880
  • 博文数量: 40
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 418
  • 用 户 组: 普通用户
  • 注册时间: 2013-06-26 22:37
文章存档

2015年(4)

2014年(27)

2013年(9)

我的朋友

分类: LINUX

2015-02-10 22:44:59

参考:http://blog.sina.com.cn/s/blog_523491650101au7f.html

      今天对于tcpdump是如何在收发包流程中抓包流程感兴趣,于是上网查看加上自己看了一下代码,有了一个大概的印象,遂做如下记录。

1. 原理
     linux下的抓包是通过注册一种虚拟的底层网络协议来完成对网络报文(准确的说是网络设备)消息的处理权。当网卡接收到一个网络报文之后,它会遍历系统中所有已经注册的网络协议,例如以太网协议、x25协议处理模块来尝试进行报文的解析处理,这一点和一些文件系统的挂载相似,就是让系统中所有的已经注册的文件系统来进行尝试挂载,如果哪一个认为自己可以处理,那么就完成挂载。
当抓包模块把自己伪装成一个网络协议的时候,系统在收到报文的时候就会给这个伪协议一次机会,让它来对网卡收到的报文进行一次处理,此时该模块就会趁机对报文进行窥探,也就是把这个报文完完整整的复制一份,假装是自己接收到的报文,汇报给抓包模块。

2. 协议注册
对于这种协议,也只有在需要的时候才注册,因为它毕竟增加了系统报文的处理速度并且会消耗大量的系统skb。当抓包开始的时候,它会创建一个对应的网络套接口,这种套接口的类型就是af_packet类型。相关实现为net\packet\af_packet.c。

点击(此处)折叠或打开

  1. static int packet_create(struct socket *sock, int protocol)
  2. {
  3.     struct sock *sk;
  4.     struct packet_opt *po;
  5.     int err;

  6.     if (!capable(CAP_NET_RAW))
  7.         return -EPERM;
  8.     if (sock->type != SOCK_DGRAM && sock->type != SOCK_RAW
  9. #ifdef CONFIG_SOCK_PACKET
  10.      && sock->type != SOCK_PACKET
  11. #endif
  12.      )
  13.         return -ESOCKTNOSUPPORT;

  14.     sock->state = SS_UNCONNECTED;

  15.     err = -ENOBUFS;
  16.     sk = sk_alloc(PF_PACKET, GFP_KERNEL, 1, NULL);
  17.     if (sk == NULL)
  18.         goto out;

  19.     sock->ops = &packet_ops;
  20. #ifdef CONFIG_SOCK_PACKET
  21.     if (sock->type == SOCK_PACKET)
  22.         sock->ops = &packet_ops_spkt;
  23. #endif
  24.     sock_init_data(sock,sk);
  25.     sk_set_owner(sk, THIS_MODULE);

  26.     po = sk->sk_protinfo = kmalloc(sizeof(*po), GFP_KERNEL);
  27.     if (!po)
  28.         goto out_free;
  29.     memset(po, 0, sizeof(*po));
  30.     sk->sk_family = PF_PACKET;
  31.     po->num = protocol;

  32.     sk->sk_destruct = packet_sock_destruct;
  33.     atomic_inc(&packet_socks_nr);

  34.     /*
  35.      *    Attach a protocol block
  36.      */

  37.     spin_lock_init(&po->bind_lock);
  38.     po->prot_hook.func = packet_rcv;       ----- 这个地方挂接处理函数,注册为packet_rcv。
  39. #ifdef CONFIG_SOCK_PACKET
  40.     if (sock->type == SOCK_PACKET)
  41.         po->prot_hook.func = packet_rcv_spkt;
  42. #endif
  43.     po->prot_hook.af_packet_priv = sk;

  44.     if (protocol) {
  45.         po->prot_hook.type = protocol;
  46.         dev_add_pack(&po->prot_hook);      ----- dev_add_pack将协议加入到ptype_all链表中,具体参考下面函数代码
  47.         sock_hold(sk);
  48.         po->running = 1;
  49.     }

  50.     write_lock_bh(&packet_sklist_lock);
  51.     sk_add_node(sk, &packet_sklist);
  52.     write_unlock_bh(&packet_sklist_lock);
  53.     return(0);

  54. out_free:
  55.     sk_free(sk);
  56. out:
  57.     return err;
  58. }

点击(此处)折叠或打开

  1. void dev_add_pack(struct packet_type *pt)
  2. {
  3.     int hash;

  4.     spin_lock_bh(&ptype_lock);
  5.     if (pt->type == htons(ETH_P_ALL)) {
  6.         netdev_nit++;
  7.         list_add_rcu(&pt->list, &ptype_all);         ----- 加入ptype_all链表
  8.     } else {
  9.         hash = ntohs(pt->type) & 15;
  10.         list_add_rcu(&pt->list, &ptype_base[hash]);
  11.     }
  12.     spin_unlock_bh(&ptype_lock);
  13. }
3.抓包流程
        当一个网卡上真正有报文到来的时候,它就会调用这里注册的packet_rcv函数。
        有报文上报,首先调用的是process_backlog函数。位于net\core\dev.c文件中。

点击(此处)折叠或打开

  1. static int process_backlog(struct net_device *backlog_dev, int *budget)
  2. {
  3.     int work = 0;
  4.     int quota = min(backlog_dev->quota, *budget);
  5.     struct softnet_data *queue = &__get_cpu_var(softnet_data);
  6.     unsigned long start_time = jiffies;

  7.     for (;;) {
  8.         struct sk_buff *skb;
  9.         struct net_device *dev;

  10.         local_irq_disable();
  11.         skb = __skb_dequeue(&queue->input_pkt_queue);
  12.         if (!skb)
  13.             goto job_done;
  14.         local_irq_enable();

  15.         dev = skb->dev;

  16.         netif_receive_skb(skb);

  17.         dev_put(dev);

  18.         work++;

  19.         if (work >= quota || jiffies - start_time > 1)
  20.             break;

  21.     }

  22.     backlog_dev->quota -= work;
  23.     *budget -= work;
  24.     return -1;

  25. job_done:
  26.     backlog_dev->quota -= work;
  27.     *budget -= work;

  28.     list_del(&backlog_dev->poll_list);
  29.     smp_mb__before_clear_bit();
  30.     netif_poll_enable(backlog_dev);

  31.     if (queue->throttle)
  32.         queue->throttle = 0;
  33.     local_irq_enable();
  34.     return 0;
  35. }

点击(此处)折叠或打开

  1. int netif_receive_skb(struct sk_buff *skb)
  2. {
  3.     struct packet_type *ptype, *pt_prev;
  4.     int ret = NET_RX_DROP;
  5.     unsigned short type;

  6. #ifdef CONFIG_NETPOLL
  7.     if (skb->dev->netpoll_rx && skb->dev->poll && netpoll_rx(skb)) {
  8.         kfree_skb(skb);
  9.         return NET_RX_DROP;
  10.     }
  11. #endif

  12.     if (!skb->stamp.tv_sec)
  13.         net_timestamp(&skb->stamp);

  14.     skb_bond(skb);

  15.     __get_cpu_var(netdev_rx_stat).total++;

  16.     skb->h.raw = skb->nh.raw = skb->data;
  17.     skb->mac_len = skb->nh.raw - skb->mac.raw;

  18.     pt_prev = NULL;

  19.     rcu_read_lock();

  20. #ifdef CONFIG_NET_CLS_ACT
  21.     if (skb->tc_verd & TC_NCLS) {
  22.         skb->tc_verd = CLR_TC_NCLS(skb->tc_verd);
  23.         goto ncls;
  24.     }
  25. #endif

  26.     list_for_each_entry_rcu(ptype, &ptype_all, list) {
  27.         if (!ptype->dev || ptype->dev == skb->dev) {
  28.             if (pt_prev)
  29.                 ret = deliver_skb(skb, pt_prev);      ----循环调用deliver_skb函数
  30.             pt_prev = ptype;
  31.         }
  32.     }

  33. #ifdef CONFIG_NET_CLS_ACT
  34.     if (pt_prev) {
  35.         ret = deliver_skb(skb, pt_prev);
  36.         pt_prev = NULL; /* noone else should process this after*/
  37.     } else {
  38.         skb->tc_verd = SET_TC_OK2MUNGE(skb->tc_verd);
  39.     }

  40.     ret = ing_filter(skb);

  41.     if (ret == TC_ACT_SHOT || (ret == TC_ACT_STOLEN)) {
  42.         kfree_skb(skb);
  43.         goto out;
  44.     }

  45.     skb->tc_verd = 0;
  46. ncls:
  47. #endif

  48.     handle_diverter(skb);

  49.     if (handle_bridge(&skb, &pt_prev, &ret))
  50.         goto out;

  51.     type = skb->protocol;
  52.     list_for_each_entry_rcu(ptype, &ptype_base[ntohs(type)&15], list) {
  53.         if (ptype->type == type &&
  54.          (!ptype->dev || ptype->dev == skb->dev)) {
  55.             if (pt_prev)
  56.                 ret = deliver_skb(skb, pt_prev);
  57.             pt_prev = ptype;
  58.         }
  59.     }

  60.     if (pt_prev) {
  61.         ret = pt_prev->func(skb, skb->dev, pt_prev);
  62.     } else {
  63.         kfree_skb(skb);
  64.         /* Jamal, now you will not able to escape explaining
  65.          * me how you were going to use this. :-)
  66.          */
  67.         ret = NET_RX_DROP;
  68.     }

  69. out:
  70.     rcu_read_unlock();
  71.     return ret;
  72. }

点击(此处)折叠或打开

  1. static __inline__ int deliver_skb(struct sk_buff *skb,
  2.                  struct packet_type *pt_prev)
  3. {
  4.     atomic_inc(&skb->users);
  5.     return pt_prev->func(skb, skb->dev, pt_prev);    ----- 这个地方的func函数就是最开始注册的packet_recv函数。
  6. }
    自此,怎么调用到packet_rcv流程就已经搞清楚了。
    接下来,我们在看看packet_rcv又是怎样将报文进行处理的。在packet_rcv函数中,有如下流程,判断是否有过滤器,有过滤器就
    

点击(此处)折叠或打开

  1. if (sk->sk_filter) {
  2.         unsigned res = run_filter(skb, sk, snaplen);       --- 过滤
  3.         if (res == 0)
  4.             goto drop_n_restore;
  5.         if (snaplen > res)
  6.             snaplen = res;
  7.     }

点击(此处)折叠或打开

  1. static inline unsigned run_filter(struct sk_buff *skb, struct sock *sk, unsigned res)
  2. {
  3.     struct sk_filter *filter;

  4.     bh_lock_sock(sk);
  5.     filter = sk->sk_filter;
  6.     /*
  7.      * Our caller already checked that filter != NULL but we need to
  8.      * verify that under bh_lock_sock() to be safe
  9.      */
  10.     if (likely(filter != NULL))
  11.         res = sk_run_filter(skb, filter->insns, filter->len);    --- 具体的过滤函数,过滤条件是tcpdump经过libpcap编译成的BPF过滤的。
  12.     bh_unlock_sock(sk);

  13.     return res;
  14. }
自此,整个tcpdump的流程完全清楚了。







阅读(4195) | 评论(0) | 转发(2) |
给主人留下些什么吧!~~