网络驱动接收到报文后,会初始化skb->protocol 字段。链路层的接收函数netif_receive_skb会根据该字段来确定把报文送给那个协议模块进一步处理。
以太网的设备调用 eth_type_trans()来给skb->protocol赋值。
__be16 eth_type_trans(struct sk_buff *skb,struct net_device *dev)
{
struct ethhdr *eth;
unsigned char *rawp;net
/*把接收net_device 赋给skb*/
skb->dev = dev;
/*给skb 的 mac 头指针赋值*/
skb_reset_mac_header(skb);
/*把 skb->data 下移,跳过以太头*/
skb_pull(skb, ETH_HLEN);
eth = eth_hdr(skb);
if (unlikely(is_multicast_ether_addr(eth->h_dest)))
{
/*判断是否是广播和组播报文,是就给skb->pkt_type赋值*/
if (!compare_ether_addr_64bits(eth->h_dest,dev->broadcast))
skb->pkt_type = PACKET_BROADCAST;
else
skb->pkt_type = PACKET_MULTICAST;
}
/*如果报文的目的MAC不是到接收设备的MAC,设置skb->pkt_type*/
else if (1 /*dev->flags&IFF_PROMISC */ )
{
if (unlikely(compare_ether_addr_64bits(eth->h_dest,
dev->dev_addr)))
skb->pkt_type = PACKET_OTHERHOST;
}
/*Marvell 交换芯片dsa 头*/
if (netdev_uses_dsa_tags(dev))
return htons(ETH_P_DSA);
if (netdev_uses_trailer_tags(dev))
return htons(ETH_P_TRAILER);
/*以太网头在此返回*/
if (ntohs(eth->h_proto) >= 1536)
return eth->h_proto;
/*以下处理非以太网的报文,不讨论*/
rawp = skb->data;
if (*(unsigned short *)rawp == 0xFFFF)
return htons(ETH_P_802_3);
/* Real 802.2 LLC*/
return htons(ETH_P_802_2);
}
网络设备驱动在调用netif_receive_skb()或netif_rx()前调用 eth_type_trans():
skb->protocol = eth_type_trans(skb,dev);
每个网络层协议都会初始化一个接收报文的函数。Linux内核中使用数据结构 struct packet_type 来描述单个网络层协议。
struct packet_type
{
/*协议类型,比如ip(0x0800),vlan(0x8100)*/
/* This is really htons(ether_type). */
__be16 type;
/*指定接收的网络设备,如果为空,就从所有网络设备中接收,
如果指定,就只接收指定设备上的数据*/
struct net_device *dev; /* NULL is wildcarded here */
/*协议接收函数*/
int (*func) (struct sk_buff *,
struct net_device *,
struct packet_type *,
struct net_device *);
/*下面是 gso 功能使用,每个协议要用gso功能就自己实现自己的函数*/
struct sk_buff *(*gso_segment)(struct sk_buff *skb,int features);
int (*gso_send_check)(struct sk_buff *skb);
/*下面是 gro 功能使用,每个协议要用gro功能就自己实现自己的函数*/
struct sk_buff **(*gro_receive)(struct sk_buff **head,struct sk_buff *skb);
int (*gro_complete)(struct sk_buff *skb);
/*指向协议使用的私有数据指针,一般没有使用*/
void *af_packet_priv;
/*把该结构体连接到相应的hash 链表上*/
struct list_head list;
};
linux 内核中定义了一张hash 表 ptype_base,hash key 是struct packet_type 中的type字段。表中的每个元素都指向一个struct packet_type 的链表。
同时还定义了一个struct packet_type 的链表ptype_all。这个链表上的协议处理程序接收所以协议的报文,主要用于网络工具和网络嗅探器接收报文。比如tcpdump 抓包程序和原始套接字使用这种类型的packet_type结构。
/* 0800 IP
* 8100 802.1Q VLAN
* 0001 802.3
* 0002 AX.25
* 0004 802.2
* 8035 RARP
* 0005 SNAP
* 0805 X.25
* 0806 ARP
* 8137 IPX
* 0009 Localtalk
* 86DD IPv6
*/
#define PTYPE_HASH_SIZE (16)
#define PTYPE_HASH_MASK (PTYPE_HASH_SIZE - 1)
static DEFINE_SPINLOCK(ptype_lock);
static struct list_head ptype_base[PTYPE_HASH_SIZE] __read_mostly;
static struct list_head ptype_all __read_mostly; /* Taps */
这一张hash 表,协议栈根据报文类型找到相应的处理函数。
linux 内核中使用如下函数进行协议的添加和删除:
添加协议:
void dev_add_pack(struct packet_type *pt)
{
int hash;
/*ptype_all 链表使用ptype_lock 自旋锁来保护。
ptype_bash hash 表中写的时候用该自旋锁来保护,
读的时候用rcu 锁来保护,实质就是读的时候禁止抢占*/
spin_lock_bh(&ptype_lock);
if (pt->type == htons(ETH_P_ALL))
list_add_rcu(&pt->list, &ptype_all);
else {
hash = ntohs(pt->type) & PTYPE_HASH_MASK;
list_add_rcu(&pt->list, &ptype_base[hash]);
}
spin_unlock_bh(&ptype_lock);
}
如果协议类型定义为 ETH_P_ALL,就是接收所有类型的报文。就把该packet_type加入到ptyte_all 链表上。
每个协议要自己定义自己的paket_type变量,初始化自己的数据。然后自协议模块初始化时调用
dev_add_packet把自己的packet_type 加入到 packet_base hash表中。
协议栈的真正入口函数 netif_receive_skb()
int netif_receive_skb(struct sk_buff *skb)
{
struct packet_type *ptype, *pt_prev;
struct net_device *orig_dev;
struct net_device *null_or_orig;
int ret = NET_RX_DROP;
__be16 type;
/*给skb赋值接收时间戳*/
if (!skb->tstamp.tv64)
net_timestamp(skb);
/*如果带vlan tag,报文走到这里一般是网卡硬件支持解析vlan,驱动已经把解析出来的
vlan tag 赋值给了sbk,判断是否是到本机vlan 三层口的报文,
如果是,把接收dev 赋成vlan dev*/
if (skb->vlan_tci && vlan_hwaccel_do_receive(skb))
return NET_RX_SUCCESS;
/*netpoll是用于在网络设备及I/O还没初始化好时
也能进行报文的收发处理的虚拟网卡的一种实现,
主要用于远程调试及网络控制终端*/
/* if we've gotten here through NAPI, check netpoll */
if (netpoll_receive_skb(skb))
return NET_RX_DROP;
if (!skb->iif)
skb->iif = skb->dev->ifindex;
/*处理链路聚合,如果接收dev 加入了聚合组,把接收dev 替换成聚合口*/
null_or_orig = NULL;
orig_dev = skb->dev;
if (orig_dev->master)
{
if (skb_bond_should_drop(skb))
null_or_orig = orig_dev; /* deliver only exact match */
else
skb->dev = orig_dev->master;
}
/*增加收包统计计数*/
__get_cpu_var(netdev_rx_stat).total++;
/*初始化skb 中以下指针*/
skb_reset_network_header(skb);
skb_reset_transport_header(skb);
skb->mac_len = skb->network_header - skb->mac_header;
pt_prev = NULL;
/*在ptype_base 和 ptype_base中查找,要使用rcu 读锁保护临界区*/
rcu_read_lock();
/*如果内核注册了协议嗅探器,把skb 拷贝一份传给它进行处理。
注意:
经过这轮循环,最后一个协议嗅探器的执行函数是没有被调用的,
放在下面进行调用,因为报文处理的最后一个处理函数被调用时
不需要进行skb user 引用计数的加 1,所以,下一个处理函数会把
最后一个ptype 传递进去,如果该函数要处理掉该skb时,应该先执行
该ptype 处理函数后再执行自己的处理程序*/
list_for_each_entry_rcu(ptype, &ptype_all, list)
{
if (ptype->dev == null_or_orig ||
ptype->dev == skb->dev ||
ptype->dev == orig_dev)
{
if (pt_prev)
ret = deliver_skb(skb, pt_prev, orig_dev);
pt_prev = ptype;
}
}
/*进入桥进行二层处理,如果返回skb == NULL,说明skb 被直接二层
转发走了,不用再送网络层了,函数直接返回*/
skb = handle_bridge(skb, &pt_prev, &ret, orig_dev);
if (!skb)
goto out;
/*处理vlan*/
skb = handle_macvlan(skb, &pt_prev, &ret, orig_dev);
if (!skb)
goto out;
/*经过以上处理,报文没被消化掉,就在 ptype_base hash 表中
找到该报文的协议接收函数,送给相应协议处理*/
type = skb->protocol;
list_for_each_entry_rcu(ptype,
&ptype_base[ntohs(type) & PTYPE_HASH_MASK], list)
{
if (ptype->type == type &&
(ptype->dev == null_or_orig
|| ptype->dev == skb->dev
|| ptype->dev == orig_dev))
{
if (pt_prev)
ret = deliver_skb(skb, pt_prev, orig_dev);
pt_prev = ptype;
}
}
if (pt_prev)
{
ret = pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
}
else
{
kfree_skb(skb);
/* Jamal, now you will not able to escape explaining
* me how you were going to use this. :-)
*/
ret = NET_RX_DROP;
}
out:
rcu_read_unlock();
return ret;
}
EXPORT_SYMBOL(netif_receive_skb);
static inline int deliver_skb(struct sk_buff *skb,
struct packet_type *pt_prev,
struct net_device *orig_dev)
{
/*送入相应协议接收函数时要对skb 的引用计数加一,
防止正在使用时被释放。因为一个报文可以被多个协议
的接收函数处理。但最后一个协议接收函数不需要对skb
的引用计数加一,因为最后一个协议接收函数负责释放该
报文所占的内存*/
atomic_inc(&skb->users);
return pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
}