上章说到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);
}