您的位置:首页 > 理论基础 > 计算机网络

linux网络协议栈分析笔记6-IP层的处理2

2013-04-09 21:07 447 查看
上章说到ip_rcv_finish最后会有两个选择:
1)ip_local_deliver
2)ip_forward

现在我们看下ip_forward()
->ip_forward()
->struct ip_options * opt = &(IPCB(skb)->opt); option中保存的是skb的一些ip头中的options信息
->if (IPCB(skb)->opt.router_alert && ip_call_ra_chain(skb)) 如果router alert option被设置了,则立即交由ip_call_ra_chain处理数据包
return NET_RX_SUCCESS;
->if (skb->pkt_type != PACKET_HOST)
goto drop;
->if (ip_hdr(skb)->ttl <= 1) 检查TTL
goto too_many_hops;
->rt = skb_rtable(skb); 获得路由信息
->if (opt->is_strictroute && rt->rt_dst != rt->rt_gateway) 设置了严格ip源站选路选项(必须按发送者指定的路线走)
goto sr_failed;
->if (skb_cow(skb, LL_RESERVED_SPACE(rt->u.dst.dev)+rt->u.dst.header_len))因为此时可能对skb做处理,所以要copy一个数据包
goto drop;
->ip_decrease_ttl(iph); TTL-1
->if (rt->rt_flags&RTCF_DOREDIRECT && !opt->srr && !skb_sec_path(skb))
ip_rt_send_redirect(skb); 如果没设置源站选路选项,则如果有更好的路线,通知源端,即ip报文的重定向
->NF_HOOK(PF_INET, NF_INET_FORWARD, skb, skb->dev, rt->u.dst.dev,
ip_forward_finish);
以上主要处理路由和报文ip头相关字段信息,最终进ip_forward_finish

static int ip_forward_finish(struct sk_buff *skb)

{

struct ip_options * opt = &(IPCB(skb)->opt);

IP_INC_STATS_BH(dev_net(skb_dst(skb)->dev), IPSTATS_MIB_OUTFORWDATAGRAMS); 记录MIB

if (unlikely(opt->optlen))

ip_forward_options(skb); 继续处理ip头的option选项

return dst_output(skb); 进入发送流程

}

看来IP层主要都是围绕路由在选择后续处理动作,我们后续对路由进行主要分析,接着看前一章提到过的一个重要处理:
ip_defrag(skb, IP_DEFRAG_LOCAL_DELIVER) IP分片报文的重组
(1)当内核接收到本地的IP包,在传递给上层协议处理之前,先进行碎片重组。IP包片段之间的标识号(id)是相同的。当IP包片偏量(frag_off)第 14位(IP_MF)为1时,表示该IP包有后继片段。片偏量的低13位则为该片段在完整数据包中的偏移量,以8字节为单位。当IP_MF位为0时,表示IP包是最后一块碎片。
(2)碎片重组由重组队列完成,每一重组队列对应于(daddr, saddr, protocol, id)构成的键值,它们存在于ipq结构构成的散列链之中。重组队列将IP包按照将片段偏量的顺序进行排列,当所有的片段都到齐后,就可以将队列中的包碎片按顺序拼合成一个完整的IP包
(3)如果30秒后重组队列内包未到齐,则重组过程失败,重组队列被释放,同时向发送方以ICMP协议通知失败信息。重组队列的内存消耗不得大于256k (sysctl_ipfrag_high_thresh),否则将会调用(ip_evictor)释放每支散列尾端的重组队列



->ip_defrag()
->if (atomic_read(&net->ipv4.frags.mem) > net->ipv4.frags.high_thresh) 不得超过规定内存空间大小
ip_evictor(net);
->qp = ip_find(net, ip_hdr(skb), user)) 寻找这片报文的iqp头
->ip_frag_queue(qp, skb); 将这片SKB插入hash队列中
->if (qp->q.last_in == (INET_FRAG_FIRST_IN | INET_FRAG_LAST_IN) &&

qp->q.meat == qp->q.len) 如果是第一个碎片包或者最后一个碎片包,并且偏移长度总和等于碎片长度总和

return ip_frag_reasm(qp, prev, dev); 所有分片已到 进行重组

上图主要用到的数据结构:
分片缓存结构
struct ipq {

struct inet_frag_queue q; 分片队列结构

u32 user;

__be32 saddr; hash因子 : 源ip,目的ip 协议号 报文id

__be32 daddr;

__be16 id;

u8 protocol;

int iif;

unsigned int rid;

struct inet_peer *peer;

};
分片队列结构
struct inet_frag_queue {

struct hlist_node list; 哈希结点结构

struct netns_frags *net;

struct list_head lru_list; /* lru list member */

spinlock_t lock;

atomic_t refcnt;

struct timer_list timer; /* when will this queue expire? */ 分片缓存的老化定时器

struct sk_buff *fragments; /* list of received fragments */ 收到分片的链

ktime_t stamp;

int len; /* total length of orig datagram */

int meat;

__u8 last_in; /* first/last segment arrived? */

#define INET_FRAG_COMPLETE 4 目前分片缓存的状态

#define INET_FRAG_FIRST_IN 2

#define INET_FRAG_LAST_IN 1

};

分片总队列:
struct inet_frags {

struct hlist_head hash[INETFRAGS_HASHSZ]; #define INETFRAGS_HASHSZ 64 哈希桶

rwlock_t lock;

u32 rnd; 哈希随机因子, 定时变更,用来抗攻击

int qsize;

int secret_interval;

struct timer_list secret_timer;

操作函数:

unsigned int (*hashfn)(struct inet_frag_queue *);

void (*constructor)(struct inet_frag_queue *q,

void *arg);

void (*destructor)(struct inet_frag_queue *);

void (*skb_free)(struct sk_buff *);

int (*match)(struct inet_frag_queue *q,

void *arg);

void (*frag_expire)(unsigned long data);

};

三个重量级函数:
ip_find:
ip_frag_queue
ip_frag_reasm
static inline struct ipq *ip_find(struct net *net, struct iphdr *iph, u32 user)

{

struct inet_frag_queue *q;

struct ip4_create_arg arg;

unsigned int hash;

arg.iph = iph;

arg.user = user;

read_lock(&ip4_frags.lock);

hash = ipqhashfn(iph->id, iph->saddr, iph->daddr, iph->protocol); 根据四因子,得到hash值

q = inet_frag_find(&net->ipv4.frags, &ip4_frags, &arg, hash); 进入hash桶查找

if (q == NULL)

goto out_nomem;

return container_of(q, struct ipq, q);

out_nomem:

LIMIT_NETDEBUG(KERN_ERR "ip_frag_create: no memory left !\n");

return NULL;

}
ip4_frags结构的初始化
void __init ipfrag_init(void)

{

ip4_frags_ctl_register();

register_pernet_subsys(&ip4_frags_ops);

ip4_frags.hashfn = ip4_hashfn; hash函数

ip4_frags.constructor = ip4_frag_init;

ip4_frags.destructor = ip4_frag_free;

ip4_frags.skb_free = NULL;

ip4_frags.qsize = sizeof(struct ipq);

ip4_frags.match = ip4_frag_match; 匹配函数

ip4_frags.frag_expire = ip_expire; 老化函数

ip4_frags.secret_interval = 10 * 60 * HZ;

inet_frags_init(&ip4_frags);

}

->inet_frag_find()
-> hlist_for_each_entry(q, n, &f->hash[hash], list) {
if (q->net == nf && f->match(q, key)) {

atomic_inc(&q->refcnt);

read_unlock(&f->lock);

return q; 找到了则返回头节点

}

}
->inet_frag_create() 否则创建分片缓存的一个空间

ip_frag_queue() 进行分片入队列
主要进行合法性校验,报文重叠处理,排序插入等操作,此处不再详细分析

ip_frag_reasm()
取得ipq中fragments头节点的ip头长度

ihlen = ip_hdrlen(head);

如果把头节点的IP首部长度加上ipq结构中的碎片总长度相加,就得到了重组之

后报文的长度

len = ihlen + qp->len;

if (len > 65535)

goto out_oversize;

头节点必须没有被克隆过

if (skb_cloned(head) && pskb_expand_head(head, 0, 0, GFP_ATOMIC))

goto out_nomem;

碎片中第一个节点如果是分片的,需要特殊处理,这里的”分片”并不是指IP包的

碎片,而是指skb存储结构离散分布,并不在一个连续的内存空间内

if (skb_shinfo(head)->frag_list) {

struct sk_buff *clone;

int i, plen = 0;

如果头节点是分片的,那么需要重新申请一个skb,并且把这个新的skb放到

第一个skb end指针之后skb_shared_info结构的frag_list链表上

if ((clone = alloc_skb(0, GFP_ATOMIC)) == NULL)

goto out_nomem;

clone->next = head->next;

head->next = clone;

把head原来的分片放在新申请的skb的frag_list里面

skb_shinfo(clone)->frag_list = skb_shinfo(head)->frag_list;

skb_shinfo(head)->frag_list = NULL;

计算head中总的分片长度

for (i=0; i<skb_shinfo(head)->nr_frags; i++)

plen += skb_shinfo(head)->frags[i].size;

实际上最后生成了一个自身数据为0.不包含任何数据,但是这个新的的frag_list中却包含了所有的分片

clone->len = clone->data_len = head->data_len - plen;

head->data_len -= clone->len;

head->len -= clone->len;

clone->csum = 0;

clone->ip_summed = head->ip_summed;

atomic_add(clone->truesize, &ip_frag_mem);

}

把head以后所有的碎片都当作是head frag_list里面的分片来处理

skb_shinfo(head)->frag_list = head->next;

skb_push(head, head->data - skb_network_header(head));

atomic_sub(head->truesize, &ip_frag_mem);

协议栈的处理会通过skb_linearize()函数将head报文的frag_list链表里面的数据包都合并成一个报文,所以

将链表里面所有skb的len和data_len,以及true_size都和head中相应的值相加,最后得到了合并后数据包的长度

for (fp=head->next; fp; fp = fp->next) {

head->data_len += fp->len;

head->len += fp->len;

if (head->ip_summed != fp->ip_summed)

head->ip_summed = CHECKSUM_NONE;

else if (head->ip_summed == CHECKSUM_COMPLETE)

head->csum = csum_add(head->csum, fp->csum);

head->truesize += fp->truesize;

atomic_sub(fp->truesize, &ip_frag_mem);

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