链路层和网络层的接口 (linux网络子系统学习 第五节 ) 推荐

网络驱动接收到报文后,会初始化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)*/ __be16 type; /* This is really htons(ether_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 链表上。

移除协议:

void __dev_remove_pack(struct packet_type *pt) struct list_head *head; struct packet_type *pt1; spin_lock_bh( ptype_lock); if (pt- type == htons(ETH_P_ALL)) head = ptype_all; else head = ptype_base[ntohs(pt- type) PTYPE_HASH_MASK]; list_for_each_entry(pt1, head, list) { if (pt == pt1) { list_del_rcu( pt- list); goto out; spin_unlock_bh( ptype_lock);void dev_remove_pack(struct packet_type *pt) __dev_remove_pack(pt); /*移除ptype后要等所有cpu核上的进程都切换一遍后再返回, 这时说明被移除的ptype 已经没有进程在使用了,可以 放心的去做下一步动作,比如释放该结构体所占的内存*/ synchronize_net();}

每个协议要自己定义自己的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 三层口的报文, 如果是,把接收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-  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 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; 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);}

停止每日在车水马龙的市井里忙碌的穿梭,

链路层和网络层的接口 (linux网络子系统学习  第五节 ) 推荐

相关文章:

你感兴趣的文章:

标签云: