熟悉ip层的职责?
熟练数据包如何通过ip层?
熟练ip数据重组的设计思路?
熟悉ip路由的思路?
熟悉netfilter的钩子函数处理?
c="https://img-blog.csdn.net/20150910171011871?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" align="middle" alt="" />
ip层的主要任务有下面5个方面:
1、ip数据包的校验(包括数据包的完整性、格式正确性、校验和等)。
2、防火墙的处理(也就是netfilter子系统)。
3、处理options(这里的options包含了一些可选的信息。比如时间戳或者源路由option)。
4、分片和重组(由于mtu的存在c;因此我们需要切包和组包)。
5、接收c;输出和转发操作。
该函数主要功能是对ip格式合法性进行检查c;然后将报文交给一下流程处理。如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;
color:#00ff">/* When the interface is in promisc. mode, drop all the crap
color:#00ff"> * that it receives, do not try to analyse it.
color:#00ff"> *当网络结构设定为混杂模式时。color:#00ff">当网卡处于混杂模式时c;收到不是发往该主机的数据包c;由net_rx_action()设置。在调用ip_rcv之前c;内核会将该数据包交给嗅探器c;所以该函数仅丢弃该包。
color:#00ff"> */
if (skb->pkt_type == PACKET_OTHERHOST)
goto drop;
color:#00ff">/* color:#00ff">SNMP所需要的统计数据c;color:#00ff">暂不进行分析color:#00ff">*/
IP_UPD_PO_STATS_BH(dev_net(dev), IPSTATS_MIB_IN, skb->len);
color:#00ff">/* skb是否被其他模块共享进行检查。
color:#00ff"> *ip_rcv是由netif_receive_skb函数调用c;如果嗅探器或者其他的用户对数据包需要进
color:#00ff"> *进行处理c;则在调用ip_rcv之前c;netif_receive_skb会增加skb的引用计数c;既该引
color:#00ff"> *用计数会大于1。若如此次c;则skb_share_check会创建sk_buff的一份拷贝。
color:#00ff"> */
if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) {
IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INDISCARDS);
goto out;
}
color:#00ff">/*
color:#00ff"> *pskb_may_pull确保skb->data指向的内存包含的数据至少为IP头部大小c;由于每个
color:#00ff"> *IP数据包包括IP分片必须包含一个完整的IP头部。如果小于IP头部大小c;则缺失
color:#00ff"> *的部分将从数据分片中拷贝。这些分片保存在skb_shinfo(skb)->frags[]中。
color:#00ff"> */
if (!pskb_may_pull(skb, sizeof(struct iphdr)))
goto inhdr_error;
color:#00ff">/* 返回一个iph结构体c;数据已经进行填充 */
iph = ip_hdr(skb);
/* color:#00ff">进行格式的判断
* RFC1122: 3.2.1.2 MUST silently discard any IP frame that fails the checksum.
*
*
* 1. Length at least the size of an ip header color:#00ff">至少一个20自己的ip头
* 2. Version of 4 color:#00ff">ipv4
* 3. Checksums correctly. [Speed optimisation for later, skip loopback checksums]
color:#00ff">校验和正确
* 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;
color:#00ff">/*确保skb的数据长度大于等于IP头部中指示的IP数据包总长度及数据包总长度必须
color:#00ff"> *大于等于IP头部长度。
color:#00ff"> */
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).
*/
color:#00ff">/*传输媒介可能写数据超过了缓冲区c;我们现在已经知道是ip格式的数据包c;根据长度提取真实的数据。*/
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);
color:#00ff">/* 与color:#00ff">netfiltercolor:#00ff">子系统交互c;如果防火墙未开c;使用默认color:#00ff">ip_rcv_finish*/
ckground:rgb(255,0,0)">return NF_HOOK(PF_INET, NF_INET_PRE_ROUTING, skb, dev, NULL,
ckground:rgb(255,0,0)"> ip_rcv_finish);
inhdr_error:
IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INHDRERRORS);
drop:
kfree_skb(skb);
out:
return NET_RX_DROP;
}
红色处c;参考: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:表示允许报文完成后续操作c;执行ip_rcv_finish。
主要工作是完成路由表的查询c;决定报文经过IP层处理后c;是继续向上传递c;还是进行转发c;还是丢弃。
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.
*/
/*
color:#00ff">通常从外界接收的数据包,skb->dst不会包含路由信息c;刚开始没有进行路由表查询c;所以还没有相应的路由表项:skb_dst(skb) == NULLcolor:#00ff">。color:#00ff"> ip_route_input函数会根据路由表设置路由信息c;暂时不color:#00ff">分析color:#00ff">路由系统。
*/
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;
}
}
/* color:#00ff">更新流量控制所需要的统计数据c;color:#00ff">暂不考虑 */
#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
/* color:#00ff">如果IP头部大于20字节c;则表示IP头部包含IP选项c;需要进行选项处理.暂color:#00ff">不分析color:#00ff">c;毕竟很少用 */
if (iph->ihl > 5 && ip_rcv_options(skb))
goto drop;
/* color:#00ff">skb->dstcolor:#00ff">上面已经查找了路由表c;所以有了color:#00ff">路由信息。根据路由类型更新SNMP统计数据color:#00ff">。*/
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);
/*
* color:#00ff">dst_input实际上会调用skb->dst->input(skb).input函数会根据路由信息设置为合适的函数指针c;如果是递交到本地的则为ip_local_deliverc;若是转发则为ip_forward.color:#00ff">。color:#00ff">暂时仅先考虑ip_local_deliver。
*/
return dst_input(skb);
drop:
kfree_skb(skb);
return NET_RX_DROP;
}
主要功能:收集IP分片c;然后调用ip_local_deliver_finish将一个完整的数据包传送给上层协议。
/*
* Deliver IP Packets to the higher protocol layers.
*/
int ip_local_deliver(struct sk_buff *skb)
{
/*
* color:#00ff">判断该IP数据包是否是一个分片c;如果IP_MF置位c;则表示该包是分片之一c;其
color:#00ff"> * 后还有更多分片c;最后一个IP分片未置位IP_MF但是其offset是非0。
color:#00ff"> * 如果是一个IP分片c;则调用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) */
ckground:rgb(255,0,0)"> return NF_HOOK(PF_INET, NF_IP_LOCAL_IN, skb, skb->dev, NULL,
ckground:rgb(255,0,0)"> ip_local_deliver_finish);
}
Ip分片包进行重组c;看看linux是如何对ip分片进行处理的c;可以为以后一些开发提供好的思维。如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算法c;可以避免类似发生。
2、分片重叠和分片丢失、重发如何处理?
丢失:如果分片包不会到来c;则删除整个队列和清楚hash表。
重叠/重发:重叠可能发生两种情况(与前一片重叠c;后一片重叠)
在收到IP分片时c;会暂时存储到一个哈希表ip4_frags中c;它在IP协议模块加载时初始化c;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 = color:#00ff">ip4_frag_match;
ip4_frags.frag_expire = color:#00ff">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. */
/*
* color:#00ff">首先检查所有IP分片所消耗的内存是否大于系统允许的最高阀值c;如果是c;则调用ip_evictor()丢弃未完全到达的IP分片c;从最旧的分片开始释放。此举一来是为了节约内存c;二来是未了防止黑客的恶意攻击。使分片在系统中累计c;降低系统性能。
*/
if (atomic_read(&ip4_frags.mem) > ip4_frags_ctl.high_thresh)
ip_evictor();
/* Lookup (or create) queue header */
/* color:#00ff">如果该分片是数据报的第一个分片c;则ip_find返回一个新的队列来搜集分片c;否则返回其所属于的分片队列。*/
if ((qp = ip_find(ip_hdr(skb), user)) != NULL) {
int ret;
/* color:#00ff">将该分片加入到队列中c;重组分片队列c;如果所有的包都收到了c;则该函数负责重组IP包*/
ret = ip_frag_queue(qp, skb);
ipq_put(qp); /* color:#00ff">引用计数减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这个参数有点迷惑c;其表示以何种理由需要对数据包进行重组c;在ip_local_deliver的调用序列当中c;这个值是IP_DEFRAG_LOCAL_DELIVER。*/
static inline struct ipq *ip_find(struct iphdr *iph, u32 user)
{
struct inet_frag_queue *q;
unsigned int hash;
arg.iph = iph;
arg.user = user;
color:#00ff"> /*color:#00ff"> hash值为:(识c;源IPc;目的IPc;协议号)
color:#00ff"> * hash算法c;该算法除了使用所给的这四个参数之外c;还使用了一个随机值
color:#00ff"> * ip4_frags.rnd,c;其初始化为
color:#00ff"> * (u32) ((num_physpages ^ (num_physpages>>7)) ^ (jiffies ^ (jiffies >> 6)));
color:#00ff"> * 这是为了防止黑客根据固定的hash算法c;通过设置ip头部的这些字段c;生成同样
color:#00ff"> * HASH值c;从而使某一HASH队列长度急剧增大而影响性能。
color:#00ff"> */
hash = ipqhashfn(iph->id, iph->saddr, iph->daddr, iph->protocol);
color:#00ff">/* 若存在该分片所属的分片队列则返回这个队列c;否则创建一个新的队列 */
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_queuec;它是一个队列c;然后遍历该队列c;当color:#00ff">net, id, saddr, daddr, protocol, user相匹配时c;就是要找的IP分片。如果没有匹配的c;则调用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;
/* color:#00ff">f->lock是读写锁c;先搜索是否存在该IP分段所属的队列 */
hlist_for_each_entry(q, n, &f->hash[hash], list) {
/* 扫描该HASH槽中所有节点 f->match中match字段在ipfrag_init中初始化为ip4_frag_match函数。对比分片队列中的散列字段和user是否和key相等c;key指向的是struct ip4_create_arg结构c;包含IP头部和user字段。 */
if (f->match(q, key)) {
atomic_inc(&q->refcnt); /* 若找到c;则增加该队列引用计数。 */
return q; /* 返回该队列 */
}
}
/* 该分片是第一个IP分片c;创建一个新的分片队列并添加到合适的HASH队列 */
return inet_frag_create(f, key, hash);
}
ip_frag_queue将到来的分片进行重组:
/* Add new segment to existing queue. */
color:#00ff">ip_frag_queue()函数将新来的skb包插入队列节点中c;这个函数是防御各种碎片攻击的关键c;要能处理各种异常的重组过程:
static int ip_frag_queue(struct ipq *qp, struct sk_buff *skb)
{
struct sk_buff *prev, *next;
int flags, offset;
int ihl, end;
int err = -ENOENT;
/* color:#00ff">对已经有color:#00ff">INET_FRAG_COMPLETEcolor:#00ff">标志的的队列节点c;后续来的数据包都丢弃。*/
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;
}
/* color:#00ff">计算当前包的偏移值c;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);
/*color:#00ff"> Determine the position of this fragment. color:#00ff">--计算片段的位置 */
end = offset + skb->len - ihl;
err = -EINVAL;
/*color:#00ff"> Is this the final fragment? color:#00ff"> -- 是最后一个分片吗?*/
if ((flags & IP_MF) == 0) {
/* If we already have some bits beyond end
* or have different end, the segment is corrrupted.
color:#00ff">如果我们有一些位已经超过了结束endc;或者有不同的结束c;这个分片时被破坏。
*/
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; color:#00ff">//最后一个分包
qp->q.len = end;
} else {
color:#00ff">//color:#00ff">仍然存在后续的分片包,检查数据长度是否是8字节对齐的
if (end&7) {
end &= ~7;
if (skb->ip_summed != CHECKSUM_UNNECESSARY)
skb->ip_summed = CHECKSUM_NONE;
}
color:#00ff">//长color:#00ff">度超过当前记录的长度
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;
color:#00ff">//去掉IP头部分c;只保留数据部分
if (pskb_pull(skb, ihl) == NULL)
goto err;
color:#00ff">//将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?
*/
color:#00ff">//确定当前包在完整包中的位置,分片包不一定是顺序到达目的端的,有可能是杂乱顺序的,因此需要调整包的顺序.
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.
*/
color:#00ff">//color:#00ff">检查偏移是否有重叠,重叠是允许的,只要是正确的
if (prev) {
int i = (FRAG_CB(prev)->offset + prev->len) - offset;
color:#00ff">//大于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;
color:#00ff">//如果重叠,则队列后面的所有包的偏移值都要调整,数据包长度的累加值也要相应减小color:#00ff">。
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);
}
}
color:#00ff">// 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);
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;
}
如果忽略掉原始套接字和IPSecc;则该函数仅仅是根据IP头部中的协议字段选择上层L4协议c;并交给它来处理。
static int ip_local_deliver_finish(struct sk_buff *skb)
{
/*color:#00ff"> 跳过IP头部 */
__skb_pull(skb, ip_hdrlen(skb));
/* Point into the IP datagram, just past the header. */
/* color:#00ff">设置传输层头部位置 */
skb_reset_transport_header(skb);
{
/* Note: See raw.c and net/raw.h, RAWV4_HTABLE_SIZE==MAX_INET_PROTOS */
int protocol = ip_hdr(skb)->protocol;
int hash;
resubmit:
/* 这个hash根本不是哈希值c;仅仅只是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;
/* color:#00ff">查找注册的L4层协议处理结构。 */
if ((ipprot = rcu_dereference(inet_protos[hash])) != NULL) {
int ret;
/* color:#00ff">启用了安全策略c;则交给IPSec */
if (!ipprot->no_policy) {
if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
kfree_skb(skb);
goto out;
}
nf_reset(skb);
}
/*color:#00ff"> 调用L4层协议处理函数 */
color:#00ff"> /* 通常会是tcp_v4_rcv, udp_rcv, icmp_rcv和igmp_rcv */
color:#00ff"> /* 如果注册了其他的L4层协议处理c;则会进行相应的调用。 color:#00ff">系统启动的时候回初始化相应的四层函数color:#00ff">*/
ret = ipprot->handler(skb);
if (ret < 0) {
protocol = -ret;
goto resubmit;
}
IP_INC_STATS_BH(IPSTATS_MIB_INDELIVERS);
} else {
if (!raw_sk) { /* 无原始套接字c;提交给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:
return 0;
}
主要做一些路由转发的功能c;一般PC上不会使用转发功能(PC如果不是自己处理的包c;肯定就不会要)c;只有那些需要开启转发业务的数据包才有用。
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);
color:#00ff">/* color:#00ff">gso相关设置c;对gso不熟悉color:#00ff"> */
if (skb_warn_if_lro(skb))
goto drop;
color:#00ff">/* 策略检查 */
if (!xfrm4_policy_check(NULL, XFRM_POLICY_FWD, skb))
goto drop;
color:#00ff">/*
color:#00ff"> *如果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)
color:#00ff"> */
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);
color:#00ff">/*color:#00ff">设置了严格ip源站选路选项(必须按发送者指定的路线走)color:#00ff">,目的地址不是网关*/
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
*/
if (rt->rt_flags&RTCF_DOREDIRECT && !opt->srr && !skb_sec_path(skb))
ip_rt_send_redirect(skb);
skb->priority = rt_tos2priority(iph->tos);
ckground:rgb(255,0,0)">return NF_HOOK(PF_INET, NF_INET_FORWARD, skb, skb->dev, rt->u.dst.dev,
ckground:rgb(255,0,0)"> 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;
}
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);
}