背景
? ? ? ? 今天遇到一個詭異的現象,當接口附加一個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地址也會接收交給網卡驅動處理。
/*** 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)方法
/*** 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;elseskb->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()
/** 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()接收后直接丟棄,并且沒有在任何地方做丟包統計的動作。
感受
? ? 工作中遇到的每個小問題,背后都蘊藏著大量知識,只有平時多積累總結,才能游刃有余解決所面對的問題。