当IP数据包上传给IP层时,有IP层的ip_rcv()函数进行接收,这个函数的主要作用就是检查这个数据包。这个函数不会处理不属于这个主机的数据包。也就是这个包的目标MAC地址不是本机的话,该函数就不会处理,而不是L3层的IP地址。 这里我们可以看到pkt_type的类型是PACKET_OTHERHOST,说明这里是其它主机的数据包,直接进行丢弃处理。在网卡获取数据包的中断处理过程(ei_interrupt)中调用了ei_receive,而ei_receive又调用eth_type_trans,在这个过程中将包的类型设置为PACKET_OTHEHOST。
然后在ip_rcv中判断包类型是不是PACKET_OTHERHOST,如果是则丢弃之。
if (skb->pkt_type == PACKET_OTHERHOST) goto drop;
|
接下来是一个共享的检查,如果是共享的数据包,需要修改skb中的信息,所以需要复制一个副本,再作进一步的处理。
if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) {
IP_INC_STATS_BH(IPSTATS_MIB_INDISCARDS);
goto out;
}
|
再下面的处理就是检查首部的长度是否够长,校检和,版本号等信息。 if (iph->ihl < 5 || iph->version != 4) goto inhdr_error;
if (!pskb_may_pull(skb, iph->ihl*4)) goto inhdr_error; 如果没有错误的话就会调ip_rcv_finiish()函数进行进一步的处理。 return NF_HOOK(PF_INET, NF_IP_PRE_ROUTING, skb, dev, NULL, ip_rcv_finish); 在这个函数中首先查找路由信息, if (likely(skb->dst == NULL)) { int err = ip_route_input(skb, iph->daddr, iph->saddr, iph->tos, skb->dev); 然后,对IP头部中的选项信息进行了进行处理。 if (iph->ihl > 5 && ip_rcv_options(skb)) goto drop;
return dst_input(skb);
|
最后,返回调用dst_input()函数,这个函数主机是根据路由信息调用相应的input函数,
err = skb->dst->input(skb);
但是这个input函数可能是ip_local_deliver或者是ip_forward();
Ip_forward()函数主机是对数据包进行转发发送,ip_local_deliver()函数主要是在本机进行处理的过程。
首先,确定接收到的包是不是分片,如果是,则要将分片重新装成一个完整的IP数据包再上传给L4层。最后调用ip_local_deliver_finish()函数。
int ip_local_deliver(struct sk_buff *skb) if (skb->nh.iph->frag_off & htons(IP_MF|IP_OFFSET)) { skb = ip_defrag(skb, IP_DEFRAG_LOCAL_DELIVER); if (!skb) return 0; } return NF_HOOK(PF_INET, NF_IP_LOCAL_IN, skb, skb->dev, NULL, ip_local_deliver_finish); ip_local_deliver_finish
|
确定将这个数据包传送给L4层,L3层的头已经没有作用,先去掉L3的头部。
int ihl = skb->nh.iph->ihl*4;
__skb_pull(skb, ihl);//将skb->data指向L4的头部。
/* Point into the IP datagram, just past the header. */ skb->h.raw = skb->data;// 重新设置skb->h.raw
接下来就是要处理与L4层相关的协议了,这里首先处理的是Raw IP,先查看raw_v4_htable有没有注册这个L4协议的Raw IP。 int protocol = skb->nh.iph->protocol; int hash; struct sock *raw_sk; struct net_protocol *ipprot;
resubmit: hash = protocol & (MAX_INET_PROTOS - 1); raw_sk = sk_head(&raw_v4_htable[hash]); 如果有,则要执行raw_v4_input()对其进行处理。提交一个副本。 if (raw_sk && !raw_v4_input(skb, skb->nh.iph, hash)) raw_sk = NULL; 然后,对L4层的各层协议进行相应的处理,调用不同的处理函数。 if ((ipprot = rcu_dereference(inet_protos[hash])) != NULL) { int ret;
if (!ipprot->no_policy) { if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) { kfree_skb(skb); goto out; } nf_reset(skb); } ret = ipprot->handler(skb);//这里是调用相应的处理函数。
if (ret < 0) { protocol = -ret; goto resubmit; }
|
调用相应的处理函数时要进行相就的注册,这里主要依据6to4隧道为例。我们可以在源码文件中sit.c,中的sit_init()函数中看到下面的注册添加函数。
if (inet_add_protocol(&sit_protocol, IPPROTO_IPV6) < 0) { printk(KERN_INFO "sit init: Can't add protocol\n"); return -EAGAIN; }
|
也就是把数组中的inet_protos[]的相应位添加相应的处理函数。
int inet_add_protocol(struct net_protocol *prot, unsigned char protocol) { int hash, ret;
hash = protocol & (MAX_INET_PROTOS - 1);
spin_lock_bh(&inet_proto_lock); if (inet_protos[hash]) { ret = -1; } else { inet_protos[hash] = prot; ret = 0; } spin_unlock_bh(&inet_proto_lock);
return ret; }
|
我们可以看到这里数组的类型仍然是inet_protos,也就说仍然是IPv4中的版本号。我们这里使用的协议号为IPPROTO_IPV6也就是41。下面对收到协议号为41的协议进行处理的函数ipip6_rcv。也就是对数组sit_protocol函数指针进行赋值。当判断到的IPv4中8位协议字段为41时,就会调用这里的处理函数也就是ipip6_rcv(),对收到的协议数据包进行处理。这里对6to4类型的遂道而言。Linux内核为2.6.16,在Linux2.6.25以后的内核有较大的变化。其增加了ISATAP类型的遂道接口。
static struct net_protocol sit_protocol = { .handler = ipip6_rcv, .err_handler = ipip6_err, }; 其中协议的结构体如下所示: struct net_protocol { int (*handler)(struct sk_buff *skb); void (*err_handler)(struct sk_buff *skb, u32 info); int no_policy; };
|
在ipip6_rcv()函数中,首先,利用ipip6_tunnel_lookup()函数根据源目的地址查找出所属的隧道,如果查找不到会出现协议不可达和目的地址不可达的错误报文。
static int ipip6_rcv(struct sk_buff *skb) { struct iphdr *iph; struct ip_tunnel *tunnel;
if (!pskb_may_pull(skb, sizeof(struct ipv6hdr))) goto out;
iph = skb->nh.iph;
read_lock(&ipip6_lock); if ((tunnel = ipip6_tunnel_lookup(iph->saddr, iph->daddr)) != NULL) { secpath_reset(skb); skb->mac.raw = skb->nh.raw; skb->nh.raw = skb->data; memset(&(IPCB(skb)->opt), 0, sizeof(struct ip_options)); IPCB(skb)->flags = 0; skb->protocol = htons(ETH_P_IPV6); skb->pkt_type = PACKET_HOST; tunnel->stat.rx_packets++; tunnel->stat.rx_bytes += skb->len; skb->dev = tunnel->dev; dst_release(skb->dst); skb->dst = NULL; nf_reset(skb); ipip6_ecn_decapsulate(iph, skb); netif_rx(skb); read_unlock(&ipip6_lock); return 0; }
icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PROT_UNREACH, 0); kfree_skb(skb); read_unlock(&ipip6_lock); out: return 0; }
|
在ipip6_tunnel_lookup()函数中,对建立隧道时使用的地址,和传递的地址进行比较,并且对接口进行判断是否打开,只有这两个全部成立时,查找隧道接口才算成功。
static struct ip_tunnel * ipip6_tunnel_lookup(u32 remote, u32 local) { unsigned h0 = HASH(remote); unsigned h1 = HASH(local); struct ip_tunnel *t;
for (t = tunnels_r_l[h0^h1]; t; t = t->next) { if (local == t->parms.iph.saddr && remote == t->parms.iph.daddr && (t->dev->flags&IFF_UP)) return t; } for (t = tunnels_r[h0]; t; t = t->next) { if (remote == t->parms.iph.daddr && (t->dev->flags&IFF_UP)) return t; } for (t = tunnels_l[h1]; t; t = t->next) { if (local == t->parms.iph.saddr && (t->dev->flags&IFF_UP)) return t; } if ((t = tunnels_wc[0]) != NULL && (t->dev->flags&IFF_UP)) return t; return NULL; }
|
|