背景
今天遇到一个诡异的现象,当接口附加一个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()接收后直接丢弃,并且没有在任何地方做丢包统计的动作。
感受
工作中遇到的每个小问题,背后都蕴藏着大量知识,只有平时多积累总结,才能游刃有余解决所面对的问题。