tcpdump抓包实现过程
Posted 为了维护世界和平_
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了tcpdump抓包实现过程相关的知识,希望对你有一定的参考价值。
目录
tcpdump抓包源码分析
应用层
协议族AF_PACKET
socket(AF_PACKET, SOCK_RAW, ETH_P_ALL)
协议族和地址族关系:每一种协议族都有对应的地址族。IPV4的协议族PF_INET,地址族为AF_INET,一一对应,值完全一样,经常混用。
socket内核实现
SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)
return __sys_socket(family, type, protocol);
int __sys_socket(int family, int type, int protocol)
retval = sock_create(family, type, protocol, &sock);
int __sock_create(struct net *net, int family, int type, int protocol,
struct socket **res, int kern)
pf = rcu_dereference(net_families[family]);
err = pf->create(net, sock, protocol, kern);
从net_families中获取指定协议,并调用create方法创建
static const struct net_proto_family packet_family_ops =
.family = PF_PACKET,
.create = packet_create,
.owner = THIS_MODULE,
;
static int __init packet_init(void)
rc = sock_register(&packet_family_ops);
//注册packet_family_ops 到net_families中
int sock_register(const struct net_proto_family *ops)
rcu_assign_pointer(net_families[ops->family], ops);
//pf->create就是packet_create
static int packet_create(struct net *net, struct socket *sock, int protocol,
int kern)
//创建时的状态
sock->state = SS_UNCONNECTED;
sk = sk_alloc(net, PF_PACKET, GFP_KERNEL, &packet_proto, kern);
sock->ops = &packet_ops;
po = pkt_sk(sk);
//拥塞控制
init_completion(&po->skb_completion);
sk->sk_family = PF_PACKET;
po->xmit = dev_queue_xmit;
//fun上注册回调函数为 packet_rcv
po->prot_hook.func = packet_rcv;
if (proto)
po->prot_hook.type = proto;
__register_prot_hook(sk);
static void __register_prot_hook(struct sock *sk)
struct packet_sock *po = pkt_sk(sk);
dev_add_pack(&po->prot_hook);//注册
void dev_add_pack(struct packet_type *pt)
struct list_head *head = ptype_head(pt);
list_add_rcu(&pt->list, head);
//ptype_head
static inline struct list_head *ptype_head(const struct packet_type *pt)
if (pt->type == htons(ETH_P_ALL))
return pt->dev ? &pt->dev->ptype_all : &ptype_all;
else
return pt->dev ? &pt->dev->ptype_specific :
&ptype_base[ntohs(pt->type) & PTYPE_HASH_MASK];
dev_add_pack 其实最后是把 hook 函数添加到了 ptype_all 里了,代码如下。
内核根据ptype_all找抓包点(重要)
static int __netif_receive_skb_core(struct sk_buff *skb, bool pfmemalloc)
......
//遍历 ptype_all (tcpdump 在这里挂了虚拟协议)
list_for_each_entry_rcu(ptype, &ptype_all, list)
if (pt_prev)
ret = deliver_skb(skb, pt_prev, orig_dev);
pt_prev = ptype;
static inline int deliver_skb(struct sk_buff *skb,
struct packet_type *pt_prev,
struct net_device *orig_dev)
//这个回调函数就是注册的packet_rcv
return pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
//将skb添加到sk_receive_queue中
static int packet_rcv(struct sk_buff *skb, struct net_device *dev,
struct packet_type *pt, struct net_device *orig_dev)
__skb_queue_tail(&sk->sk_receive_queue, skb);
可见 packet_rcv 把收到的 skb 放到了当前 packet socket 的接收队列里了。调用 recvfrom 的时候就可以获取到所抓到的包
过滤包抓包点netfilter(重要)
网络接收不经过netfilter
网络发包经过netfilter
发包 IP层各种 netfilter 规则的过滤
int __ip_local_out(struct sk_buff *skb)
......
return nf_hook(NFPROTO_IPV4, NF_INET_LOCAL_OUT, skb, NULL,
skb_dst(skb)->dev, dst_output);
struct sk_buff *dev_hard_start_xmit(struct sk_buff *first, struct net_device *dev,
struct netdev_queue *txq, int *ret)
while (skb)
rc = xmit_one(skb, dev, txq, next != NULL);
//xmit_one->dev_queue_xmit_nit
void dev_queue_xmit_nit(struct sk_buff *skb, struct net_device *dev)
list_for_each_entry_rcu(ptype, ptype_list, list)
if (ptype->ignore_outgoing)
continue;
if (pt_prev)
deliver_skb(skb2, pt_prev, skb->dev);
pt_prev = ptype;
continue;
static inline int deliver_skb(struct sk_buff *skb,
struct packet_type *pt_prev,
struct net_device *orig_dev)
return pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
在 dev_queue_xmit_nit 中遍历 ptype_all 中的协议,并依次调用 deliver_skb。这就会执行到 tcpdump 挂在上面的虚拟协议。
总结
- tcpdump 是通过 socket 系统调用,
- 注册packet_rcv回调函数到ptype_all队列中
- 网络收发包,会在网络设备层遍历 ptype_all 中的协议,并执行其中的回调。
- 数据包是先经过网络设备层然后才到协议层n,etfilter在tcpdump收包过程中起不到过滤;在发包过程 IP 层进入各种 netfilter 规则的过滤起作用。
-
应用层实现抓包关键程序
在应用层实现抓包 类型设置为PF_PACKET
//PF_PACKET
int main(int argc, char *argv[])
if( (sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL))) < 0 )
printf("Create socket error.\\n");
exit(0);
while(1)
len = recvfrom(sock, buffer, BUFFER_MAX, 0, NULL, NULL);
if (len < 46)
printf("Catch packet length error.\\n" );
close(sock);
exit(0);
参考
https://course.0voice.com/v1/course/intro?courseId=2&agentId=0
以上是关于tcpdump抓包实现过程的主要内容,如果未能解决你的问题,请参考以下文章