您的位置:首页 > 其它

ip层和4层的接口实现分析

2016-05-15 22:14 393 查看
ip层和4层的接口实现分析

Socket.netCiscoLinux数据结构

首先来看一下基于3层的ipv4以及ipv6实现的一些4层的协议:



这里要注意并没有IGMPV6,这是因为在ipv6中,它是作为iCMPv6的一部分实现的.

首先我们要知道输入数据包的ip头中的protocol域标识了,将要传递的4层协议.

我们这里主要介绍的是ip数据包从3层传递到4层的接口(也就是输入帧接口).而输出帧的处理,我前面的blog都已经有介绍,想了解的话,可以去看前面的blog.

先来看主要的数据结构,然后我们会分析ip_local_deliver_finish函数(也就是3层处理的出口函数).

在内核中,每一个4层协议都是一个net_protocol结构体,而内核会在启动的时候将所有的4层协议都注册到一个数组inet_protos中,然后根据数据包的ip头来得到相应的handle函数:

Java代码


struct net_protocol {

///协议的处理函数,也就是将要处理输入数据报的4层协议的处理函数.

int (*handler)(struct sk_buff *skb);

///协议的错误处理函数.

void (*err_handler)(struct sk_buff *skb, u32 info);

///gso相关的两个函数.

int (*gso_send_check)(struct sk_buff *skb);

struct sk_buff *(*gso_segment)(struct sk_buff *skb,

int features);

///主要是被ipsec所使用的两个域

unsigned int no_policy:1,

netns_ok:1;

};

L4的协议都是在linux/in.h这个文件中,都是以IPPROTO开头的一些宏.由于ip头中的4层协议域是8位,因此4层协议的最大数值也就是255.而在内核中,255是raw
ip, IPPPROTO_RAW:

Java代码


enum {

IPPROTO_IP = 0,

IPPROTO_ICMP = 1,

IPPROTO_IGMP = 2,

IPPROTO_IPIP = 4,

IPPROTO_TCP = 6,

IPPROTO_EGP = 8,

IPPROTO_PUP = 12,

IPPROTO_UDP = 17,

IPPROTO_IDP = 22,

IPPROTO_DCCP = 33,

IPPROTO_RSVP = 46,

IPPROTO_GRE = 47,

IPPROTO_IPV6 = 41,

IPPROTO_ESP = 50,

IPPROTO_AH = 51,

IPPROTO_BEETPH = 94,

IPPROTO_PIM = 103,

IPPROTO_COMP = 108,

IPPROTO_SCTP = 132,

IPPROTO_UDPLITE = 136,

IPPROTO_RAW = 255,

IPPROTO_MAX

};

这里要上面列出的协议,并不是所有的都在内核态handle的,其中一些经常在用户态handle的例如(IPPROTO_RSVP).

内核是通过inet_add_protocol来添加协议到inet_protos数组中的,相应的还有一个删除方法,我们先来看inet_protos的结构:



这里要注意的就是读写inet_protos时,使用的是自旋锁,而只读时,使用的是RCU(Read-Copy
Update).

然后来看inet_add_protocol的源码:

Java代码


struct net_protocol *inet_protos[MAX_INET_PROTOS] ____cacheline_aligned_in_smp;

///这里只是举两个例子,tcp和udp的协议注册函数.我们这次暂时就不分析tcp和udp的处理函数了(我会在3层结束后,分析4层源码)

static struct net_protocol tcp_protocol = {

.handler = tcp_v4_rcv,

.err_handler = tcp_v4_err,

.gso_send_check = tcp_v4_gso_send_check,

.gso_segment = tcp_tso_segment,

.no_policy = 1,

.netns_ok = 1,

};

static struct net_protocol udp_protocol = {

.handler = udp_rcv,

.err_handler = udp_err,

.no_policy = 1,

.netns_ok = 1,

};

int inet_add_protocol(struct net_protocol *prot, unsigned char protocol)

{

int hash, ret;

///计算当前协议在数组中的slot.

hash = protocol & (MAX_INET_PROTOS - 1);

///使用自旋锁.

spin_lock_bh(&inet_proto_lock);

if (inet_protos[hash]) {

ret = -1;

} else {

///将相应的prot添加到数组

inet_protos[hash] = prot;

ret = 0;

}

spin_unlock_bh(&inet_proto_lock);

return ret;

}

然后这些协议的注册都是在内核boot的时候在inet_init中初始化的,下面就是inet_init的代码片段.:

Java代码


static int __init inet_init(void)

{

...........................................

if (inet_add_protocol(&icmp_protocol, IPPROTO_ICMP) < 0)

printk(KERN_CRIT "inet_init: Cannot add ICMP protocol\n");

if (inet_add_protocol(&udp_protocol, IPPROTO_UDP) < 0)

printk(KERN_CRIT "inet_init: Cannot add UDP protocol\n");

if (inet_add_protocol(&tcp_protocol, IPPROTO_TCP) < 0)

printk(KERN_CRIT "inet_init: Cannot add TCP protocol\n");

#ifdef CONFIG_IP_MULTICAST

if (inet_add_protocol(&igmp_protocol, IPPROTO_IGMP) < 0)

printk(KERN_CRIT "inet_init: Cannot add IGMP protocol\n");

#endif

..................................

}

知道协议如何注册之后,我们来分析ip_local_deliver_finish函数,来看3层是如何将数据包发送到4层的.

1 我们知道linux支持raw数据包的发送,因此在这里会对raw
socket进行了特殊处理,它会clone一份数据包然后传递给相应的raw处理函数,然后再继续后面的处理.

2 ipsec.这时还需要加上相应的ipsec头,然后再传给4层处理.看下面的图:



Java代码


static int ip_local_deliver_finish(struct sk_buff *skb)

{

///取出相应的net信息.

struct net *net = dev_net(skb->dev);

///下面两个主要是调整data指针,使data指针指向4层的数据开始处.

__skb_pull(skb, ip_hdrlen(skb));

skb_reset_transport_header(skb);

///加rcu锁.

rcu_read_lock();

{

///取出ip头中的协议.

int protocol = ip_hdr(skb)->protocol;

int hash, raw;

struct net_protocol *ipprot;

resubmit:

///得到raw socket, 如果不是raw socket,则返回0.

raw = raw_local_deliver(skb, protocol);

///计算4层协议的slot.

hash = protocol & (MAX_INET_PROTOS - 1);

///rcu读取相应的协议处理结构.

ipprot = rcu_dereference(inet_protos[hash]);

///主要是ipprot是否有被当前主机注册.

if (ipprot != NULL && (net == &init_net || ipprot->netns_ok)) {

int ret;

///判断ipsec,并进行相关处理.

if (!ipprot->no_policy) {

if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {

kfree_skb(skb);

goto out;

}

nf_reset(skb);

}

///调用handler,进入相应的4层协议的处理.

ret = ipprot->handler(skb);

if (ret < 0) {

protocol = -ret;

goto resubmit;

}

IP_INC_STATS_BH(net, IPSTATS_MIB_INDELIVERS);

}

................................................

out:

rcu_read_unlock();

return 0;

}

最后来看一下raw socket的处理,通过上面我们知道,会调用raw_local_deliver来进行raw
socket的相关处理(如果没有raw socket,则会直接返回):

当应用程序使用raw ip
socket,他只需要攒递给内核协议id(4层的协议),以及目的地址.因此这里存取sock的hash表使用的key就是4层协议id.

Java代码


///相应的hash表,保存raw socket.

struct raw_hashinfo {

rwlock_t lock;

struct hlist_head ht[RAW_HTABLE_SIZE];

};

static struct raw_hashinfo raw_v4_hashinfo = {

.lock = __RW_LOCK_UNLOCKED(raw_v4_hashinfo.lock),

};

int raw_local_deliver(struct sk_buff *skb, int protocol)

{

int hash;

struct sock *raw_sk;

///通过协议计算hash值(使用4层协议id).

hash = protocol & (RAW_HTABLE_SIZE - 1);

///得到相应的raw_sk.

raw_sk = sk_head(&raw_v4_hashinfo.ht[hash]);

///交给raw socket的处理函数,raw_v4_input中会clone一个skb,然后交给最后的raw_rev函数去处理最终的数据包.

if (raw_sk && !raw_v4_input(skb, ip_hdr(skb), hash))

raw_sk = NULL;

return raw_sk != NULL;

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: