Linux 内核抓包功能实现基础(四) 手动查找邻居缓存填充MAC地址

          之前写了三篇关于内核抓包功能的实现,包括抓包原理、实现以及抓包服务器的实现。基本的功能都已经有了,但是还有些小问题有待解决。今天有空就解决一下。

            开发版本基于内核3.4.39。

           先介绍一下问题背景,抓包框架是基于netfilter的,通过在Pre-Routing和Post-Routing链上分别挂一个钩子函数来对报文处理。抓包基本上需要报文所有的原始数据,包括mac地址、ip地址以及数据层。对于后两者数据本来都在,但是对于mac地址,就不一定啦。所有进来的报文都是带有mac地址的,但是对于本机出去的报文来说,mac地址也是有的,只不过在IP层还看不到,因为我们挂载post-routing链上的钩子仍属于IP层,系统报文在IP层只负责IP层头域的修改,至于mac层,还需要继续往下走。直到查找邻居缓存,找到后就填写mac地址发送出去,找不到的话就发送arp广播同时缓存该报文,一定时间内没收到arp响应或者缓存的报文数量过多,报文就会被丢弃。如果我们要在IP层去填充mac地址的话,就必须手动去查找系统邻居缓存了。至于怎么查,可以直接参考系统TCP/IP协议栈的处理方式。

          我们首先看一下系统是怎么处理mac地址信息的:

       首先,ip_output接收skb后,会先让挂在netfilter post-routing链上的钩子函数处理,我们的抓包钩子也在这里,处理完成后,如果报文还在的话,就传给ip_finish_output处理,这个函数主要就是根据 MTU 判断报文长度是否太长,太长的话就调用ip_fragment函数处理,不管报文是否分片,最终都会调用ip_finish_output2函数。
        而这个ip_finish_output2函数就去获取邻居信息,通过调用dst_get_neighbour_noref得到邻居信息,之后调用neigh_output函数。neigh_output函数就去判断是否存在缓存项,存在的话就调用neigh_hh_output填充mac地址,填完之后就调用发送函数,至此报文发送完成,但是如果不存在缓存项的话,就会先缓存报文并发送arp请求,直到有响应或者超时或者缓存报文过多就把它丢弃。我们来看一下这几个函数:

static inline int ip_finish_output2(struct sk_buff *skb)
{
	struct dst_entry *dst = skb_dst(skb); /* 协议无关的目的缓存相关的数据结构 */
	struct rtable *rt = (struct rtable *)dst; /* 路由缓存相关结构体 */
	struct net_device *dev = dst->dev;
	unsigned int hh_len = LL_RESERVED_SPACE(dev);
	struct neighbour *neigh;

    // 
	if (rt->rt_type == RTN_MULTICAST) {
		IP_UPD_PO_STATS(dev_net(dev), IPSTATS_MIB_OUTMCAST, skb->len);
	} else if (rt->rt_type == RTN_BROADCAST)
		IP_UPD_PO_STATS(dev_net(dev), IPSTATS_MIB_OUTBCAST, skb->len);

	/* Be paranoid, rather than too clever. */
	// 如果首部空间不够大的话,则重新分配
	if (unlikely(skb_headroom(skb) < hh_len && dev->header_ops)) {
		struct sk_buff *skb2;

		skb2 = skb_realloc_headroom(skb, LL_RESERVED_SPACE(dev));
		if (skb2 == NULL) {
			kfree_skb(skb);
			return -ENOMEM;
		}
		if (skb->sk)
			skb_set_owner_w(skb2, skb->sk);
		kfree_skb(skb);
		skb = skb2;
	}

	rcu_read_lock();
        
        //获取邻居信息,获取失败就丢弃报文
	neigh = dst_get_neighbour_noref(dst);
	if (neigh) {	
                //调用邻居层发送接口
		int res = neigh_output(neigh, skb);

		rcu_read_unlock();
		return res;
	}
	rcu_read_unlock();

	if (net_ratelimit())
		printk(KERN_DEBUG "ip_finish_output2: No header cache and no neighbour!\n");
	kfree_skb(skb);
	return -EINVAL;
}
static inline int neigh_output(struct neighbour *n, struct sk_buff *skb)
{
	struct hh_cache *hh = &n->hh;
	if ((n->nud_state & NUD_CONNECTED) && hh->hh_len)
		return neigh_hh_output(hh, skb); //存在缓存则填充并发送
	else
		return n->output(n, skb); //不存在则送至缓存队列
}

     

static inline int neigh_hh_output(struct hh_cache *hh, struct sk_buff *skb)
{
	unsigned seq;
	int hh_len;
        
        //填充mac地址
	do {                
		int hh_alen;

		seq = read_seqbegin(&hh->hh_lock);
		hh_len = hh->hh_len;
		hh_alen = HH_DATA_ALIGN(hh_len);
		memcpy(skb->data - hh_alen, hh->hh_data, hh_alen);
	} while (read_seqretry(&hh->hh_lock, seq));

        //将mac头域压入数据栈中
	skb_push(skb, hh_len);

        //发送函数,报文处理完成
	return dev_queue_xmit(skb);
}

  以上就是内核对网络报文mac地址的处理,可以看到在Netfilter 的Post-Routing链上报文是还没有mac地址的,因此我们这里可以手动去设置,方式就是参考内核的实现,只不过提前处理了。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值