linux查看子系统链路,链路层和网络层的接口 (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;

}

}

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->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);

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值