学习目标:
熟悉ip层的职责?
熟练数据包如何通过ip层?
熟练ip数据重组的设计思路?
熟悉ip路由的思路?
熟悉netfilter的钩子函数处理?
1数据流路径
2职责
ip层的主要任务有下面5个方面:
1、ip数据包的校验(包括数据包的完整性、格式正确性、校验和等)。
2、防火墙的处理(也就是netfilter子系统)。
3、处理options(这里的options包含了一些可选的信息。比如时间戳或者源路由option)。
4、分片和重组(由于mtu的存在,因此我们需要切包和组包)。
5、接收,输出和转发操作。
3分析各自函数的实现(直接上源码分析)
3.1ip_rcv
该函数主要功能是对ip格式合法性进行检查,然后将报文交给一下流程处理。如ip_rcv_finish。
/*
* Main IP Receive routine.
*/
int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev)
{
struct iphdr *iph;
u32 len;
/* When the interface is in promisc. mode, drop all the crap
* that it receives, do not try to analyse it.
*当网络结构设定为混杂模式时。当网卡处于混杂模式时,收到不是发往该主机的数据包,由net_rx_action()设置。在调用ip_rcv之前,内核会将该数据包交给嗅探器,所以该函数仅丢弃该包。
*/
if (skb->pkt_type == PACKET_OTHERHOST)
goto drop;
/* SNMP所需要的统计数据,暂不进行分析*/
IP_UPD_PO_STATS_BH(dev_net(dev), IPSTATS_MIB_IN, skb->len);
/* skb是否被其他模块共享进行检查。
*ip_rcv是由netif_receive_skb函数调用,如果嗅探器或者其他的用户对数据包需要进
*进行处理,则在调用ip_rcv之前,netif_receive_skb会增加skb的引用计数,既该引
*用计数会大于1。若如此次,则skb_share_check会创建sk_buff的一份拷贝。
*/
if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) {
IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INDISCARDS);
goto out;
}
/*
*pskb_may_pull确保skb->data指向的内存包含的数据至少为IP头部大小,由于每个
*IP数据包包括IP分片必须包含一个完整的IP头部。如果小于IP头部大小,则缺失
*的部分将从数据分片中拷贝。这些分片保存在skb_shinfo(skb)->frags[]中。
*/
if (!pskb_may_pull(skb, sizeof(struct iphdr)))
goto inhdr_error;
/* 返回一个iph结构体,数据已经进行填充 */
iph = ip_hdr(skb);
/* 进行格式的判断
* RFC1122: 3.2.1.2 MUST silently discard any IP frame that fails the checksum.
*
* Is the datagram acceptable?
*
* 1. Length at least the size of an ip header 至少一个20自己的ip头
* 2. Version of 4 ipv4
* 3. Checksums correctly. [Speed optimisation for later, skip loopback checksums]
校验和正确
* 4. Doesn't have a bogus length
*/
if (iph->ihl < 5 || iph->version != 4)
goto inhdr_error;
if (!pskb_may_pull(skb, iph->ihl*4))
goto inhdr_error;
iph = ip_hdr(skb);
if (unlikely(ip_fast_csum((u8 *)iph, iph->ihl)))
goto inhdr_error;
/*确保skb的数据长度大于等于IP头部中指示的IP数据包总长度及数据包总长度必须
*大于等于IP头部长度。
*/
len = ntohs(iph->tot_len);
if (skb->len < len) {
IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INTRUNCATEDPKTS);
goto drop;
} else if (len < (iph->ihl*4))
goto inhdr_error;
/* Our transport medium may have padded the buffer out. Now we know it
* is IP we can trim to the true length of the frame.
* Note this now means skb->len holds ntohs(iph->tot_len).
*/
/*传输媒介可能写数据超过了缓冲区,我们现在已经知道是ip格式的数据包,根据长度提取真实的数据。*/
if (pskb_trim_rcsum(skb, len)) {
IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INDISCARDS);
goto drop;
}
/* Remove any debris in the socket control block */
memset(IPCB(skb), 0, sizeof(struct inet_skb_parm));
/* Must drop socket now because of tproxy. */
skb_orphan(skb);
/* 与netfilter子系统交互,如果防火墙未开,使用默认ip_rcv_finish*/
return NF_HOOK(PF_INET, NF_INET_PRE_ROUTING, skb, dev, NULL,
ip_rcv_finish);
inhdr_error:
IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INHDRERRORS);
drop:
kfree_skb(skb);
out:
return NET_RX_DROP;
}
红色处,参考:http://blog.csdn.net/qy532846454/article/details/6605592
NF_HOOK(PF_INET, NF_INET_PRE_ROUTING, skb, dev, NULL,ip_rcv_finish)
执行顺序:
NF_HOOK()->NF_HOOK_THRESH()->nf_hook_thresh()->nf_hook_slow();
NF_ACCEPT:表示允许报文完成后续操作,执行ip_rcv_finish。
3.2ip_rcv_finish
主要工作是完成路由表的查询,决定报文经过IP层处理后,是继续向上传递,还是进行转发,还是丢弃。
static int ip_rcv_finish(struct sk_buff *skb)
{
const struct iphdr *iph = ip_hdr(skb);
struct rtable *rt;
/*
* Initialise the virtual path cache for the packet. It describes
* how the packet travels inside Linux networking.
*/
/*
通常从外界接收的数据包,skb->dst不会包含路由信息,刚开始没有进行路由表查询,所以还没有相应的路由表项:skb_dst(skb) == NULL。 ip_route_input函数会根据路由表设置路由信息,暂时不分析路由系统。
*/
if (skb->dst == NULL) {
int err = ip_route_input(skb, iph->daddr, iph->saddr, iph->tos,
skb->dev);
if (unlikely(err)) {
if (err == -EHOSTUNREACH)
IP_INC_STATS_BH(IPSTATS_MIB_INADDRERRORS);
else if (err == -ENETUNREACH)
IP_INC_STATS_BH(IPSTATS_MIB_INNOROUTES);
goto drop;
}
}
/* 更新流量控制所需要的统计数据,暂不考虑 */
#ifdef CONFIG_NET_CLS_ROUTE
if (unlikely(skb->dst->tclassid)) {
struct ip_rt_acct *st = ip_rt_acct + 256*smp_processor_id();
u32 idx = skb->dst->tclassid;
st[idx&0xFF].o_packets++;
st[idx&0xFF].o_bytes+=skb->len;
st[(idx>>16)&0xFF].i_packets++;
st[(idx>>16)&0xFF].i_bytes+=skb->len;
}
#endif
/* 如果IP头部大于20字节,则表示IP头部包含IP选项,需要进行选项处理.暂不分析,毕竟很少用 */
if (iph->ihl > 5 && ip_rcv_options(skb))
goto drop;
/* skb->dst上面已经查找了路由表,所以有了路由信息。根据路由类型更新SNMP统计数据。*/
rt = (struct rtable*)skb->dst;
if (rt->rt_type == RTN_MULTICAST)
IP_INC_STATS_BH(IPSTATS_MIB_INMCASTPKTS);
else if (rt->rt_type == RTN_BROADCAST)
IP_INC_STATS_BH(IPSTATS_MIB_INBCASTPKTS);
/*
* dst_input实际上会调用skb->dst->input(skb).input函数会根据路由信息设置为合适的函数指针,如果是递交到本地的则为ip_local_deliver,若是转发则为ip_forward.。暂时仅先考虑ip_local_deliver。
*/
return dst_input(skb);
drop:
kfree_skb(skb);
return NET_RX_DROP;
}
3.3ip_local_deliver发往本机
主要功能:收集IP分片,然后调用ip_local_deliver_finish将一个完整的数据包传送给上层协议。
/*
* Deliver IP Packets to the higher protocol layers.
*/
int ip_local_deliver(struct sk_buff *skb)
{
/*
* 判断该IP数据包是否是一个分片,如果IP_MF置位,则表示该包是分片之一,其
* 后还有更多分片,最后一个IP分片未置位IP_MF但是其offset是非0。
* 如果是一个IP分片,则调用ip_defrag重新组织IP数据包。
*/
if (ip_hdr(skb)->frag_off & htons(IP_MF | IP_OFFSET)) {
if (ip_defrag(skb, IP_DEFRAG_LOCAL_DELIVER))
return 0;
}
/* 调用ip_local_deliver_finish(skb) */
return NF_HOOK(PF_INET, NF_IP_LOCAL_IN, skb, skb->dev, NULL,
ip_local_deliver_finish);
}
3.3.1int ip_defrag(struct sk_buff *skb, u32 user)
Ip分片包进行重组,看看linux是如何对ip分片进行处理的,可以为以后一些开发提供好的思维。如dpdk中样例就是仿照linux中进行编写。
参考:http://blog.csdn.net/qy532846454/article/details/6744252
基本思想:
(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)释放每支散列尾端的重组队列。、
几个问题:
1、hash值是否唯一?
jhash_3word 采用了一种hash算法,可以避免类似发生。
2、分片重叠和分片丢失、重发如何处理?
丢失:如果分片包不会到来,则删除整个队列和清楚hash表。
重叠/重发:重叠可能发生两种情况(与前一片重叠,后一片重叠)
在收到IP分片时,会暂时存储到一个哈希表ip4_frags中,它在IP协议模块加载时初始化,inet_init() -> ipfrag_init()。要留意的是ip4_frag_match用于匹配IP分片是否属于同一个报文;ip_expire用于在IP分片超时时进行处理。
初始化过程:
void __init ipfrag_init(void)
{
ip4_frags_ctl_register();
register_pernet_subsys(&ip4_frags_ops);
ip4_frags.hashfn = ip4_hashfn;
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);
}
/* Process an incoming IP datagram fragment. */
int ip_defrag(struct sk_buff *skb, u32 user)
{
struct ipq *qp;
IP_INC_STATS_BH(IPSTATS_MIB_REASMREQDS);
/* Start by cleaning up the memory. */
/*
* 首先检查所有IP分片所消耗的内存是否大于系统允许的最高阀值,如果是,则调用ip_evictor()丢弃未完全到达的IP分片,从最旧的分片开始释放。此举一来是为了节约内存,二来是未了防止黑客的恶意攻击。使分片在系统中累计,降低系统性能。
*/
if (atomic_read(&ip4_frags.mem) > ip4_frags_ctl.high_thresh)
ip_evictor();
/* Lookup (or create) queue header */
/* 如果该分片是数据报的第一个分片,则ip_find返回一个新的队列来搜集分片,否则返回其所属于的分片队列。*/
if ((qp = ip_find(ip_hdr(skb), user)) != NULL) {
int ret;
spin_lock(&qp->q.lock);
/* 将该分片加入到队列中,重组分片队列,如果所有的包都收到了,则该函数负责重组IP包*/
ret = ip_frag_queue(qp, skb);
spin_unlock(&qp->q.lock);
ipq_put(qp); /* 引用计数减1 */
return ret;
}
IP_INC_STATS_BH(IPSTATS_MIB_REASMFAILS);
kfree_skb(skb);
return -ENOMEM;
}
/* Find the correct entry in the "incomplete datagrams" queue for
* this IP datagram, and create new one, if nothing is found.
*/
/* u32 user这个参数有点迷惑,其表示以何种理由需要对数据包进行重组,在ip_local_deliver的调用序列当中,这个值是IP_DEFRAG_LOCAL_DELIVER。*/
static inline struct ipq *ip_find(struct iphdr *iph, u32 user)
{
struct inet_frag_queue *q;
struct ip4_create_arg arg;
unsigned int hash;
arg.iph = iph;
arg.user = user;
/* hash值为:(识,源IP,目的IP,协议号)
* hash算法,该算法除了使用所给的这四个参数之外,还使用了一个随机值
* ip4_frags.rnd,,其初始化为
* (u32) ((num_physpages ^ (num_physpages>>7)) ^ (jiffies ^ (jiffies >> 6)));
* 这是为了防止黑客根据固定的hash算法,通过设置ip头部的这些字段,生成同样
* HASH值,从而使某一HASH队列长度急剧增大而影响性能。
*/
hash = ipqhashfn(iph->id, iph->saddr, iph->daddr, iph->protocol);
/* 若存在该分片所属的分片队列则返回这个队列,否则创建一个新的队列 */
q = inet_frag_find(&ip4_frags, &arg, 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;
}
inet_frag_find根据hash值取ip4_frag->hash[hash]项 – inet_frag_queue,它是一个队列,然后遍历该队列,当net, id, saddr, daddr, protocol, user相匹配时,就是要找的IP分片。如果没有匹配的,则调用inet_frag_create创建它。
struct inet_frag_queue *inet_frag_find(struct inet_frags *f, void *key,
unsigned int hash)
{
struct inet_frag_queue *q;
struct hlist_node *n;
/* f->lock是读写锁,先搜索是否存在该IP分段所属的队列 */
read_lock(&f->lock);
hlist_for_each_entry(q, n, &f->hash[hash], list) {
/* 扫描该HASH槽中所有节点 f->match中match字段在ipfrag_init中初始化为ip4_frag_match函数。对比分片队列中的散列字段和user是否和key相等,key指向的是struct ip4_create_arg结构,包含IP头部和user字段。 */
if (f->match(q, key)) {
atomic_inc(&q->refcnt); /* 若找到,则增加该队列引用计数。 */
read_unlock(&f->lock);
return q; /* 返回该队列 */
}
}
read_unlock(&f->lock);
/* 该分片是第一个IP分片,创建一个新的分片队列并添加到合适的HASH队列 */
return inet_frag_create(f, key, hash);
}
ip_frag_queue将到来的分片进行重组:
/* Add new segment to existing queue. */
ip_frag_queue()函数将新来的skb包插入队列节点中,这个函数是防御各种碎片攻击的关键,要能处理各种异常的重组过程:
static int ip_frag_queue(struct ipq *qp, struct sk_buff *skb)
{
struct sk_buff *prev, *next;
struct net_device *dev;
int flags, offset;
int ihl, end;
int err = -ENOENT;
/* 对已经有INET_FRAG_COMPLETE标志的的队列节点,后续来的数据包都丢弃。*/
if (qp->q.last_in & INET_FRAG_COMPLETE)
goto err;
if (!(IPCB(skb)->flags & IPSKB_FRAG_COMPLETE) &&
unlikely(ip_frag_too_far(qp)) &&
unlikely(err = ip_frag_reinit(qp))) {
ipq_kill(qp);
goto err;
}
/* 计算当前包的偏移值,IP头中的偏移值只有13位,但表示的是8字节的倍数。*/
offset = ntohs(ip_hdr(skb)->frag_off);
flags = offset & ~IP_OFFSET;
offset &= IP_OFFSET;
offset <<= 3; /* offset is in 8-byte chunks */
ihl = ip_hdrlen(skb);
/* Determine the position of this fragment. --计算片段的位置 */
end = offset + skb->len - ihl;
err = -EINVAL;
/* Is this the final fragment? -- 是最后一个分片吗?*/
if ((flags & IP_MF) == 0) {
/* If we already have some bits beyond end
* or have different end, the segment is corrrupted.
如果我们有一些位已经超过了结束end,或者有不同的结束,这个分片时被破坏。
*/
if (end < qp->q.len ||
((qp->q.last_in & INET_FRAG_LAST_IN) && end != qp->q.len))
goto err;
qp->q.last_in |= INET_FRAG_LAST_IN; //最后一个分包
qp->q.len = end;
} else {
//仍然存在后续的分片包,检查数据长度是否是8字节对齐的
if (end&7) {
end &= ~7;
if (skb->ip_summed != CHECKSUM_UNNECESSARY)
skb->ip_summed = CHECKSUM_NONE;
}
//长度超过当前记录的长度
if (end > qp->q.len) {
/* Some bits beyond end -> corruption. */
if (qp->q.last_in & INET_FRAG_LAST_IN)
goto err;
qp->q.len = end;
}
}
if (end == offset)
goto err;
err = -ENOMEM;
//去掉IP头部分,只保留数据部分
if (pskb_pull(skb, ihl) == NULL)
goto err;
//将skb包长度调整为end-offset, 该值为该skb包中的实际有效数据长度
err = pskb_trim_rcsum(skb, end - offset);
if (err)
goto err;
/* Find out which fragments are in front and at the back of us
* in the chain of fragments so far. We must know where to put
* this fragment, right?
*/
//确定当前包在完整包中的位置,分片包不一定是顺序到达目的端的,有可能是杂乱顺序的,因此需要调整包的顺序.
prev = NULL;
for (next = qp->q.fragments; next != NULL; next = next->next) {
if (FRAG_CB(next)->offset >= offset)
break; /* bingo! */
prev = next;
}
/* We found where to put this one. Check for overlap with
* preceding fragment, and, if needed, align things so that
* any overlaps are eliminated.
*/
//检查偏移是否有重叠,重叠是允许的,只要是正确的
if (prev) {
int i = (FRAG_CB(prev)->offset + prev->len) - offset;
//大于0 说明发生了重叠
if (i > 0) {
offset += i;
err = -EINVAL;
if (end <= offset)
goto err;
err = -ENOMEM;
if (!pskb_pull(skb, i))
goto err;
if (skb->ip_summed != CHECKSUM_UNNECESSARY)
skb->ip_summed = CHECKSUM_NONE;
}
}
err = -ENOMEM;
//如果重叠,则队列后面的所有包的偏移值都要调整,数据包长度的累加值也要相应减小。
while (next && FRAG_CB(next)->offset < end) {
int i = end - FRAG_CB(next)->offset; /* overlap is 'i' bytes */
if (i < next->len) {
/* Eat head of the next overlapped fragment
* and leave the loop. The next ones cannot overlap.
*/
if (!pskb_pull(next, i))
goto err;
FRAG_CB(next)->offset += i;
qp->q.meat -= i;
if (next->ip_summed != CHECKSUM_UNNECESSARY)
next->ip_summed = CHECKSUM_NONE;
break;
} else {
struct sk_buff *free_it = next;
/* Old fragment is completely overridden with
* new one drop it.
*/
next = next->next;
if (prev)
prev->next = next;
else
qp->q.fragments = next;
qp->q.meat -= free_it->len;
frag_kfree_skb(qp->q.net, free_it, NULL);
}
}
// skb记录自己的偏移值
FRAG_CB(skb)->offset = offset;
/* Insert this fragment in the chain of fragments. */
skb->next = next;
if (prev)
prev->next = skb;
else
qp->q.fragments = skb;
dev = skb->dev;
if (dev) {
qp->iif = dev->ifindex;
skb->dev = NULL;
}
qp->q.stamp = skb->tstamp;
qp->q.meat += skb->len;
atomic_add(skb->truesize, &qp->q.net->mem);
if (offset == 0)
qp->q.last_in |= INET_FRAG_FIRST_IN;
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);
write_lock(&ip4_frags.lock);
list_move_tail(&qp->q.lru_list, &qp->q.net->lru_list);
write_unlock(&ip4_frags.lock);
return -EINPROGRESS;
err:
kfree_skb(skb);
return err;
}
3.3.2ip_local_deliver_finish
如果忽略掉原始套接字和IPSec,则该函数仅仅是根据IP头部中的协议字段选择上层L4协议,并交给它来处理。
static int ip_local_deliver_finish(struct sk_buff *skb)
{
/* 跳过IP头部 */
__skb_pull(skb, ip_hdrlen(skb));
/* Point into the IP datagram, just past the header. */
/* 设置传输层头部位置 */
skb_reset_transport_header(skb);
rcu_read_lock();
{
/* Note: See raw.c and net/raw.h, RAWV4_HTABLE_SIZE==MAX_INET_PROTOS */
int protocol = ip_hdr(skb)->protocol;
int hash;
struct sock *raw_sk;
struct net_protocol *ipprot;
resubmit:
/* 这个hash根本不是哈希值,仅仅只是inet_protos数组中的下表而已 */
hash = protocol & (MAX_INET_PROTOS - 1);
raw_sk = sk_head(&raw_v4_htable[hash]);
/* If there maybe a raw socket we must check - if not we
* don't care less
*/
/* 原始套接字?? 忽略... */
if (raw_sk && !raw_v4_input(skb, ip_hdr(skb), hash))
raw_sk = NULL;
/* 查找注册的L4层协议处理结构。 */
if ((ipprot = rcu_dereference(inet_protos[hash])) != NULL) {
int ret;
/* 启用了安全策略,则交给IPSec */
if (!ipprot->no_policy) {
if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
kfree_skb(skb);
goto out;
}
nf_reset(skb);
}
/* 调用L4层协议处理函数 */
/* 通常会是tcp_v4_rcv, udp_rcv, icmp_rcv和igmp_rcv */
/* 如果注册了其他的L4层协议处理,则会进行相应的调用。 系统启动的时候回初始化相应的四层函数*/
ret = ipprot->handler(skb);
if (ret < 0) {
protocol = -ret;
goto resubmit;
}
IP_INC_STATS_BH(IPSTATS_MIB_INDELIVERS);
} else {
if (!raw_sk) { /* 无原始套接字,提交给IPSec */
if (xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
IP_INC_STATS_BH(IPSTATS_MIB_INUNKNOWNPROTOS);
icmp_send(skb, ICMP_DEST_UNREACH,
ICMP_PROT_UNREACH, 0);
}
} else
IP_INC_STATS_BH(IPSTATS_MIB_INDELIVERS);
kfree_skb(skb);
}
}
out:
rcu_read_unlock();
return 0;
}
3.4转发ip_forward
主要做一些路由转发的功能,一般PC上不会使用转发功能(PC如果不是自己处理的包,肯定就不会要),只有那些需要开启转发业务的数据包才有用。
int ip_forward(struct sk_buff *skb)
{
struct iphdr *iph; /* Our header */
struct rtable *rt; /* Route we use */
struct ip_options * opt = &(IPCB(skb)->opt);
/* gso相关设置,对gso不熟悉 */
if (skb_warn_if_lro(skb))
goto drop;
/* 策略检查 */
if (!xfrm4_policy_check(NULL, XFRM_POLICY_FWD, skb))
goto drop;
/*
*如果router alert option被设置了,则立即交由ip_call_ra_chain处理数据包.ip_call_ra_chain函数轮询一个全局链表ip_ra_chain.此全局链表中是一些设置了IP_ROUTER_ALERT的sockets.因为这些sockets对设置了Router Alert option的ip包感兴趣.如果数据包为分片的,则将分片数据包组装好后发给ip_ra_chain链表中的原始套接口中的相关接收函数.(raw sockets)
*/
if (IPCB(skb)->opt.router_alert && ip_call_ra_chain(skb))
return NET_RX_SUCCESS;
if (skb->pkt_type != PACKET_HOST)
goto drop;
skb_forward_csum(skb);
/*
* According to the RFC, we must first decrease the TTL field. If
* that reaches zero, we must reply an ICMP control message telling
* that the packet's lifetime expired.
*/
if (ip_hdr(skb)->ttl <= 1)
goto too_many_hops;
if (!xfrm4_route_forward(skb))
goto drop;
rt = skb_rtable(skb);
/*设置了严格ip源站选路选项(必须按发送者指定的路线走),目的地址不是网关*/
if (opt->is_strictroute && rt->rt_dst != rt->rt_gateway)
goto sr_failed;
if (unlikely(skb->len > dst_mtu(&rt->u.dst) && !skb_is_gso(skb) &&
(ip_hdr(skb)->frag_off & htons(IP_DF))) && !skb->local_df) {
IP_INC_STATS(dev_net(rt->u.dst.dev), IPSTATS_MIB_FRAGFAILS);
icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED,
htonl(dst_mtu(&rt->u.dst)));
goto drop;
}
/* We are about to mangle packet. Copy it! */
if (skb_cow(skb, LL_RESERVED_SPACE(rt->u.dst.dev)+rt->u.dst.header_len))
goto drop;
iph = ip_hdr(skb);
/* Decrease ttl after skb cow done */
ip_decrease_ttl(iph);
/*
* We now generate an ICMP HOST REDIRECT giving the route
* we calculated.
*/
if (rt->rt_flags&RTCF_DOREDIRECT && !opt->srr && !skb_sec_path(skb))
ip_rt_send_redirect(skb);
skb->priority = rt_tos2priority(iph->tos);
return NF_HOOK(PF_INET, NF_INET_FORWARD, skb, skb->dev, rt->u.dst.dev,
ip_forward_finish);
sr_failed:
/*
* Strict routing permits no gatewaying
*/
icmp_send(skb, ICMP_DEST_UNREACH, ICMP_SR_FAILED, 0);
goto drop;
too_many_hops:
/* Tell the sender its packet died... */
IP_INC_STATS_BH(dev_net(skb_dst(skb)->dev), IPSTATS_MIB_INHDRERRORS);
icmp_send(skb, ICMP_TIME_EXCEEDED, ICMP_EXC_TTL, 0);
drop:
kfree_skb(skb);
return NET_RX_DROP;
}
3.4.1转发 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);
if (unlikely(opt->optlen))
ip_forward_options(skb);
return dst_output(skb);
}