数据报文去哪儿了

背景

今天遇到一个诡异的现象,当接口附加一个IP时,主IP业务正常,附加IP死活不行,tcpdump抓包确可以正常抓到到业务的报文,但是在PREROUTING raw添加规则确没有命中,说明报文没有到netfilter框架内,Scapy打流测试,通过bpftrace 跟踪kfree_skb,没有捕获大量的kfree_skb 调用,才引发今天的问题,数据报文去哪儿了?

结论

由于上层设备发送报文时,将附加IP的MAC地址填错导致,但是tcpdump可以明确报文已经到了主机,但是为什么却没有往netfilter框架递送,bpftrace 为什么没有捕获大量的kfree_skb? 简单的讲错误的MAC地址,正确的IP 是否可以正常通信?为什么?

内核源码分析

本文使用内核版本 5.10, 网卡驱动 e1000e

我们知道当网卡工作在直接模式(Direct Model)时,网卡只接收自己MAC地址的帧,此模式下通过scapy打流不匹配的目的mac地址时,数据帧直接被网卡层面丢弃,bpftrace 此时无法捕获kfree_skb事件。

当使用tcpdump工具时会将网卡设置为混杂模式(Promiscuous Model),不匹配自己的MAC地址也会接收交给网卡驱动处理。

cpp 复制代码
/**
 * e1000_receive_skb - helper function to handle Rx indications
 * @adapter: board private structure
 * @netdev: pointer to netdev struct
 * @staterr: descriptor extended error and status field as written by hardware
 * @vlan: descriptor vlan field as written by hardware (no le/be conversion)
 * @skb: pointer to sk_buff to be indicated to stack
 **/
static void e1000_receive_skb(struct e1000_adapter *adapter,
			      struct net_device *netdev, struct sk_buff *skb,
			      u32 staterr, __le16 vlan)
{
	u16 tag = le16_to_cpu(vlan);

	e1000e_rx_hwtstamp(adapter, staterr, skb);

	skb->protocol = eth_type_trans(skb, netdev);

	if (staterr & E1000_RXD_STAT_VP)
		__vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q), tag);

	napi_gro_receive(&adapter->napi, skb);
}

这里e1000e网卡驱动接收报文,此处注意eth_type_trans(skb, netdev)方法

cpp 复制代码
/**
 * eth_type_trans - determine the packet's protocol ID.
 * @skb: received socket data
 * @dev: receiving network device
 *
 * The rule here is that we
 * assume 802.3 if the type field is short enough to be a length.
 * This is normal practice and works for any 'now in use' protocol.
 */
__be16 eth_type_trans(struct sk_buff *skb, struct net_device *dev)
{
	unsigned short _service_access_point;
	const unsigned short *sap;
	const struct ethhdr *eth;

	skb->dev = dev;
	skb_reset_mac_header(skb);

	eth = (struct ethhdr *)skb->data;
	skb_pull_inline(skb, ETH_HLEN);

    /* 此处目的MAC不是设备DEV地址时,命中条件 */
	if (unlikely(!ether_addr_equal_64bits(eth->h_dest,
					      dev->dev_addr))) {
        /* 检测目的MAC是否为多播或者组播 */
		if (unlikely(is_multicast_ether_addr_64bits(eth->h_dest))) {
			if (ether_addr_equal_64bits(eth->h_dest, dev->broadcast))
				skb->pkt_type = PACKET_BROADCAST;
			else
				skb->pkt_type = PACKET_MULTICAST;
		} else {
			skb->pkt_type = PACKET_OTHERHOST;
		}
	}

    if (likely(eth_proto_is_802_3(eth->h_proto)))
		return eth->h_proto;
    .....
}

当我们的目的MAC地址与自身dev地址不匹配时,会将pkt_type = PACKET_OTHERHOST 然后返回eth->proto, 这里我们使用的IP报文,也就是将来会使用ip_rcv()处理。

然后通过NAPI接口,__netif_receive_skb_core,等一系列调用最终调用到ip_rcv_core()

cpp 复制代码
/*
 * 	Main IP Receive routine.
 */
static struct sk_buff *ip_rcv_core(struct sk_buff *skb, struct net *net)
{
	const struct iphdr *iph;
	u32 len;

	/* When the interface is in promisc. mode, drop all the crap
	 * that it receives, do not try to analyse it.
	 */
	if (skb->pkt_type == PACKET_OTHERHOST)
		goto drop;

    ......

drop:
	kfree_skb(skb);
out:
	return NULL;
}

这里有明确的注释说明,混杂模式的报文即pkt_type为PACKET_OTHERHOST值,直接丢弃,并且此处的drop,内核协议栈层面并没有做任何的丢包统计。

总结

经过分析我们可以总结我们遇到的问题,

1,通过scapy打流测试,为什么bpftrace没有捕获到大量的kfree_skb事件?

这是因为网卡工作在直接模式(Direct Model)网卡将目的MAC不是自己的直接丢弃,验证这个想象,可以直接使用tcpdump 工具抓包,此时bpftrace 可以捕获大量的kfree_skb事件。

2,tcpdump 捕获到去往自己IP的报文,为什么没有到netfilter框架?

这是因为tcpdump将网卡设置为混杂模式(Promiscuous Model)网卡驱动接收报文并将报文类型置为PACKET_OTHERHOST,当ip_rcv_core()接收后直接丢弃,并且没有在任何地方做丢包统计的动作。

感受

工作中遇到的每个小问题,背后都蕴藏着大量知识,只有平时多积累总结,才能游刃有余解决所面对的问题。

相关推荐
Xの哲學3 小时前
Linux流量控制: 内核队列的深度剖析
linux·服务器·算法·架构·边缘计算
tuokuac4 小时前
docker中nginx配置报错解决
linux·运维·服务器
掘根4 小时前
【消息队列项目】虚拟机管理实现
网络
Joren的学习记录5 小时前
【Linux运维大神系列】docker详解(四)
linux·运维·docker
老王熬夜敲代码5 小时前
网络中数据传输的具体过程
linux·网络·笔记
Elastic 中国社区官方博客6 小时前
让我们把这个 expense 工具从 n8n 迁移到 Elastic One Workflow
大数据·运维·elasticsearch·搜索引擎·ai·信息可视化·全文检索
汤愈韬6 小时前
TK_网络基础和常见攻击(笔记)
网络·笔记
程序员佳佳6 小时前
2025年大模型终极横评:GPT-5.2、Banana Pro与DeepSeek V3.2实战硬核比拼(附统一接入方案)
服务器·数据库·人工智能·python·gpt·api
( •̀∀•́ )9206 小时前
GitHub Actions SSH 部署密钥
运维·ssh·github
louqle7 小时前
docker基本知识及常用命令汇总
运维·docker·容器