参考:http://blog.sina.com.cn/s/blog_523491650101au7f.html
今天对于tcpdump是如何在收发包流程中抓包流程感兴趣,于是上网查看加上自己看了一下代码,有了一个大概的印象,遂做如下记录。
1. 原理
linux下的抓包是通过注册一种虚拟的底层网络协议来完成对网络报文(准确的说是网络设备)消息的处理权。当网卡接收到一个网络报文之后,它会遍历系统中所有已经注册的网络协议,例如以太网协议、x25协议处理模块来尝试进行报文的解析处理,这一点和一些文件系统的挂载相似,就是让系统中所有的已经注册的文件系统来进行尝试挂载,如果哪一个认为自己可以处理,那么就完成挂载。
当抓包模块把自己伪装成一个网络协议的时候,系统在收到报文的时候就会给这个伪协议一次机会,让它来对网卡收到的报文进行一次处理,此时该模块就会趁机对报文进行窥探,也就是把这个报文完完整整的复制一份,假装是自己接收到的报文,汇报给抓包模块。
2. 协议注册
对于这种协议,也只有在需要的时候才注册,因为它毕竟增加了系统报文的处理速度并且会消耗大量的系统skb。当抓包开始的时候,它会创建一个对应的网络套接口,这种套接口的类型就是af_packet类型。相关实现为net\packet\af_packet.c。
-
static int packet_create(struct socket *sock, int protocol)
-
{
-
struct sock *sk;
-
struct packet_opt *po;
-
int err;
-
-
if (!capable(CAP_NET_RAW))
-
return -EPERM;
-
if (sock->type != SOCK_DGRAM && sock->type != SOCK_RAW
-
#ifdef CONFIG_SOCK_PACKET
-
&& sock->type != SOCK_PACKET
-
#endif
-
)
-
return -ESOCKTNOSUPPORT;
-
-
sock->state = SS_UNCONNECTED;
-
-
err = -ENOBUFS;
-
sk = sk_alloc(PF_PACKET, GFP_KERNEL, 1, NULL);
-
if (sk == NULL)
-
goto out;
-
-
sock->ops = &packet_ops;
-
#ifdef CONFIG_SOCK_PACKET
-
if (sock->type == SOCK_PACKET)
-
sock->ops = &packet_ops_spkt;
-
#endif
-
sock_init_data(sock,sk);
-
sk_set_owner(sk, THIS_MODULE);
-
-
po = sk->sk_protinfo = kmalloc(sizeof(*po), GFP_KERNEL);
-
if (!po)
-
goto out_free;
-
memset(po, 0, sizeof(*po));
-
sk->sk_family = PF_PACKET;
-
po->num = protocol;
-
-
sk->sk_destruct = packet_sock_destruct;
-
atomic_inc(&packet_socks_nr);
-
-
/*
-
* Attach a protocol block
-
*/
-
-
spin_lock_init(&po->bind_lock);
-
po->prot_hook.func = packet_rcv; ----- 这个地方挂接处理函数,注册为packet_rcv。
-
#ifdef CONFIG_SOCK_PACKET
-
if (sock->type == SOCK_PACKET)
-
po->prot_hook.func = packet_rcv_spkt;
-
#endif
-
po->prot_hook.af_packet_priv = sk;
-
-
if (protocol) {
-
po->prot_hook.type = protocol;
-
dev_add_pack(&po->prot_hook); ----- dev_add_pack将协议加入到ptype_all链表中,具体参考下面函数代码
-
sock_hold(sk);
-
po->running = 1;
-
}
-
-
write_lock_bh(&packet_sklist_lock);
-
sk_add_node(sk, &packet_sklist);
-
write_unlock_bh(&packet_sklist_lock);
-
return(0);
-
-
out_free:
-
sk_free(sk);
-
out:
-
return err;
-
}
-
void dev_add_pack(struct packet_type *pt)
-
{
-
int hash;
-
-
spin_lock_bh(&ptype_lock);
-
if (pt->type == htons(ETH_P_ALL)) {
-
netdev_nit++;
-
list_add_rcu(&pt->list, &ptype_all); ----- 加入ptype_all链表
-
} else {
-
hash = ntohs(pt->type) & 15;
-
list_add_rcu(&pt->list, &ptype_base[hash]);
-
}
-
spin_unlock_bh(&ptype_lock);
-
}
3.抓包流程
当一个网卡上真正有报文到来的时候,它就会调用这里注册的packet_rcv函数。
有报文上报,首先调用的是process_backlog函数。位于net\core\dev.c文件中。
-
static int process_backlog(struct net_device *backlog_dev, int *budget)
-
{
-
int work = 0;
-
int quota = min(backlog_dev->quota, *budget);
-
struct softnet_data *queue = &__get_cpu_var(softnet_data);
-
unsigned long start_time = jiffies;
-
-
for (;;) {
-
struct sk_buff *skb;
-
struct net_device *dev;
-
-
local_irq_disable();
-
skb = __skb_dequeue(&queue->input_pkt_queue);
-
if (!skb)
-
goto job_done;
-
local_irq_enable();
-
-
dev = skb->dev;
-
-
netif_receive_skb(skb);
-
-
dev_put(dev);
-
-
work++;
-
-
if (work >= quota || jiffies - start_time > 1)
-
break;
-
-
}
-
-
backlog_dev->quota -= work;
-
*budget -= work;
-
return -1;
-
-
job_done:
-
backlog_dev->quota -= work;
-
*budget -= work;
-
-
list_del(&backlog_dev->poll_list);
-
smp_mb__before_clear_bit();
-
netif_poll_enable(backlog_dev);
-
-
if (queue->throttle)
-
queue->throttle = 0;
-
local_irq_enable();
-
return 0;
-
}
-
int netif_receive_skb(struct sk_buff *skb)
-
{
-
struct packet_type *ptype, *pt_prev;
-
int ret = NET_RX_DROP;
-
unsigned short type;
-
-
#ifdef CONFIG_NETPOLL
-
if (skb->dev->netpoll_rx && skb->dev->poll && netpoll_rx(skb)) {
-
kfree_skb(skb);
-
return NET_RX_DROP;
-
}
-
#endif
-
-
if (!skb->stamp.tv_sec)
-
net_timestamp(&skb->stamp);
-
-
skb_bond(skb);
-
-
__get_cpu_var(netdev_rx_stat).total++;
-
-
skb->h.raw = skb->nh.raw = skb->data;
-
skb->mac_len = skb->nh.raw - skb->mac.raw;
-
-
pt_prev = NULL;
-
-
rcu_read_lock();
-
-
#ifdef CONFIG_NET_CLS_ACT
-
if (skb->tc_verd & TC_NCLS) {
-
skb->tc_verd = CLR_TC_NCLS(skb->tc_verd);
-
goto ncls;
-
}
-
#endif
-
-
list_for_each_entry_rcu(ptype, &ptype_all, list) {
-
if (!ptype->dev || ptype->dev == skb->dev) {
-
if (pt_prev)
-
ret = deliver_skb(skb, pt_prev); ----循环调用deliver_skb函数
-
pt_prev = ptype;
-
}
-
}
-
-
#ifdef CONFIG_NET_CLS_ACT
-
if (pt_prev) {
-
ret = deliver_skb(skb, pt_prev);
-
pt_prev = NULL; /* noone else should process this after*/
-
} else {
-
skb->tc_verd = SET_TC_OK2MUNGE(skb->tc_verd);
-
}
-
-
ret = ing_filter(skb);
-
-
if (ret == TC_ACT_SHOT || (ret == TC_ACT_STOLEN)) {
-
kfree_skb(skb);
-
goto out;
-
}
-
-
skb->tc_verd = 0;
-
ncls:
-
#endif
-
-
handle_diverter(skb);
-
-
if (handle_bridge(&skb, &pt_prev, &ret))
-
goto out;
-
-
type = skb->protocol;
-
list_for_each_entry_rcu(ptype, &ptype_base[ntohs(type)&15], list) {
-
if (ptype->type == type &&
-
(!ptype->dev || ptype->dev == skb->dev)) {
-
if (pt_prev)
-
ret = deliver_skb(skb, pt_prev);
-
pt_prev = ptype;
-
}
-
}
-
-
if (pt_prev) {
-
ret = pt_prev->func(skb, skb->dev, pt_prev);
-
} else {
-
kfree_skb(skb);
-
/* Jamal, now you will not able to escape explaining
-
* me how you were going to use this. :-)
-
*/
-
ret = NET_RX_DROP;
-
}
-
-
out:
-
rcu_read_unlock();
-
return ret;
-
}
-
static __inline__ int deliver_skb(struct sk_buff *skb,
-
struct packet_type *pt_prev)
-
{
-
atomic_inc(&skb->users);
-
return pt_prev->func(skb, skb->dev, pt_prev); ----- 这个地方的func函数就是最开始注册的packet_recv函数。
-
}
自此,怎么调用到packet_rcv流程就已经搞清楚了。
接下来,我们在看看packet_rcv又是怎样将报文进行处理的。在packet_rcv函数中,有如下流程,判断是否有过滤器,有过滤器就
-
if (sk->sk_filter) {
-
unsigned res = run_filter(skb, sk, snaplen); --- 过滤
-
if (res == 0)
-
goto drop_n_restore;
-
if (snaplen > res)
-
snaplen = res;
-
}
-
static inline unsigned run_filter(struct sk_buff *skb, struct sock *sk, unsigned res)
-
{
-
struct sk_filter *filter;
-
-
bh_lock_sock(sk);
-
filter = sk->sk_filter;
-
/*
-
* Our caller already checked that filter != NULL but we need to
-
* verify that under bh_lock_sock() to be safe
-
*/
-
if (likely(filter != NULL))
-
res = sk_run_filter(skb, filter->insns, filter->len); --- 具体的过滤函数,过滤条件是tcpdump经过libpcap编译成的BPF过滤的。
-
bh_unlock_sock(sk);
-
-
return res;
-
}
自此,整个tcpdump的流程完全清楚了。
阅读(2210) | 评论(0) | 转发(0) |