tcpdump抓包实现过程

目录

应用层

socket内核实现

内核根据ptype_all找抓包点(重要)

过滤包抓包点netfilter(重要)

总结

应用层实现抓包关键程序


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 挂在上面的虚拟协议。

总结

1) tcpdump 是通过 socket 系统调用;
2) 注册packet_rcv回调函数到ptype_all队列中;
3) 网络收发包,会在网络设备层遍历 ptype_all 中的协议,并执行其中的回调;
4) 数据包是先经过网络设备层然后才到协议层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);
                }
        }

}


  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

为了维护世界和平_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值