1, ip包的转发
* ip包转发流程函数调用关系
ip_rcv_finish()
->skb->dst->input()
->ip_forward() //若是非本地包
-> ip_forward_finish()
-> ip_forward_options()
-> dst_output() // 发送ip数据包
-> ip_output()
-> ip_output_finish()
->ip_local_deliver() // 本地数据包
->ip_defrag() // 碎片重组
->ip_local_deliver_finish() //调用第传输层协议处理数据包:tcp_v4_rcv等。
* ip包转发的实现
/*
* ip_forward函数的主要流程如下:
* (1) 检查LRO是否被设置,若设置不能被转发,直接丢弃该数据包
* (2) 检查ipsec策略,若没有通过丢弃该数据包
* (3) 检查是否设置了Router Alert选项,若设置了调用ip_call_ra_chain函数进行处理,若处理成功直接返回NET_RX_SUCCESS,该函数的流程结束。
* (4) 检查ttl的值,若!=1 丢弃该数据包
* (5) 检查下一跳的值,若有更优的下一跳,则调用ip_rt_send_redirect发送一个ICMP的REDIRECT消息
* (6) 进入netfilter的链,进行规则检测
* (7) 最后,调用ip_forward_finish进行后半部分的处理
*
*/
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);
// 接收到的包不能被转发,因为LRO(Large Receive Offload)被设置。直接丢弃该包。
if (skb_warn_if_lro(skb))
goto drop;
// 检查ipsec策略
if (!xfrm4_policy_check(NULL, XFRM_POLICY_FWD, skb))
goto drop;
// 若设置了Router Alert选项,则调用ip_call_ra_chain函数进行处理
// 参见《ip选项处理》一节
if (IPCB(skb)->opt.router_alert && ip_call_ra_chain(skb))
return NET_RX_SUCCESS;
// 若该数据包不是自己的包,则丢弃该包
if (skb->pkt_type != PACKET_HOST)
goto drop;
// 我们处理的是ip层的包,而校验和是tcp层的事,所以这里我们只需要把校验和设置成空
skb_forward_csum(skb);
// 若ip包的ttl<=1,必须要丢弃该数据包,并且发送一个icmp信息给源地址。
// icmp消息类型是:ICMP_TIME_EXCEEDED,编号是:ICMP_EXC_TTL
// 注意此时ttl还没有被减1,因为此时可能有其他的子系统还在共享该数据包,例如抓包器等。
/*
* 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;
// 检查是否有ipsec策略要转发该包
if (!xfrm4_route_forward(skb))
goto drop;
// 获取该数据包的rtable结构指针,该结构中包含所有的转发包需要的信息,包括下一跳的主机rt_gateway
rt = skb->rtable;
// 若ip头包含一个Strict Source Route选项,且包的下一跳和路由表中的不匹配,则丢弃该数据包
if (opt->is_strictroute && rt->rt_dst != rt->rt_gateway)
goto sr_failed;
// 若数据包大于目的ip的mtu大小,且设置了IP_DF标志位,则丢弃该数据包
// 并发送一个icmp错误信息,类型是:ICMP_DEST_UNREACH,编码是:ICMP_FRAG_NEEDED
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;
}
// 因为我们要修改数据包的内容了,此时需要检查数据包是否被共享
// 若是共享数据包,就调用skb_cow复制一个副本。
/* 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;
// 获取skb中的ip头指针
iph = ip_hdr(skb);
// 现在把skb中的ttl字段减1,此时也要共享ip的校验和
/* Decrease ttl after skb cow done */
ip_decrease_ttl(iph);
/*
* We now generate an ICMP HOST REDIRECT giving the route
* we calculated.
*/
// 若存在比原来包中更优的下一跳,则给源发送一个ICMP的REDIRECT消息
if (rt->rt_flags&RTCF_DOREDIRECT && !opt->srr && !skb->sp)
ip_rt_send_redirect(skb);
// 用ip头的tos字段设置skb->priority优先级,用于以后的流量控制(Qos)
skb->priority = rt_tos2priority(iph->tos);
// 处理完所有的事情后,进入netfilter的链,若没有netfilter规则,直接进入ip_forward_finish函数
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->dev), IPSTATS_MIB_INHDRERRORS);
icmp_send(skb, ICMP_TIME_EXCEEDED, ICMP_EXC_TTL, 0);
drop:
kfree_skb(skb);
return NET_RX_DROP;
}
* 包转发下半部分
// 到这里说明ip包的所有检查已经完成,可以转发该数据包了
static int ip_forward_finish(struct sk_buff *skb)
{
// 获取ip的选项
struct ip_options * opt = &(IPCB(skb)->opt);
// 添加ip的统计数据
IP_INC_STATS_BH(dev_net(skb->dst->dev), IPSTATS_MIB_OUTFORWDATAGRAMS);
// 处理forward相关的选项,见ip选项处理一节
if (unlikely(opt->optlen))
ip_forward_options(skb);
// 调用dst_output
return dst_output(skb);
}
// 就是调用skb中的dst指针的output函数进行发送
// 若是单播地址,则会初始化为ip_output
// 若是多播地址,则会初始化为ip_mc_output
/* Output packet to network from transport. */
static inline int dst_output(struct sk_buff *skb)
{
return skb->dst->output(skb);
}
2, ip包本地传送
/*
* Deliver IP Packets to the higher protocol layers.
*/
int ip_local_deliver(struct sk_buff *skb)
{
/*
* Reassemble IP fragments.
*/
// 若包是分片包,需要重组分片包,分片包的重组,有另外的章节描述
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函数进行上层处理
return NF_HOOK(PF_INET, NF_INET_LOCAL_IN, skb, skb->dev, NULL,
ip_local_deliver_finish);
}
* 本地包处理下半部分
本地Ip包处理的下半部分,主要调用第4层协议的相应处理函数,处理数据包。
static int ip_local_deliver_finish(struct sk_buff *skb)
{
struct net *net = dev_net(skb->dev);
// 调整skb数据包的结构和长度
__skb_pull(skb, ip_hdrlen(skb));
// 让传输层的头指针,指向skb->data的指针
/* Point into the IP datagram, just past the header. */
skb_reset_transport_header(skb);
// 加读锁
rcu_read_lock();
{
// 获取传输层协议
int protocol = ip_hdr(skb)->protocol;
int hash, raw;
struct net_protocol *ipprot;
resubmit:
// 若是raw socket发送的,需要做相应的处理,clone数据包
raw = raw_local_deliver(skb, protocol);
// 计算传输层协议处理结构在inet_protos数组hash表中的位置
hash = protocol & (MAX_INET_PROTOS - 1);
// 获取传输层协议处理指针
ipprot = rcu_dereference(inet_protos[hash]);
// 若获取到了对应传输层的处理结构
if (ipprot != NULL && (net == &init_net || ipprot->netns_ok)) {
int ret;
// 检查ipsec策略
if (!ipprot->no_policy) {
if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
kfree_skb(skb);
goto out;
}
nf_reset(skb);
}
// 调用传输层处理函数进行处理。
// tcp的是tcp_v4_rcv,udp是udp_rcv...
ret = ipprot->handler(skb);
// 处理数据包失败,再次尝试
if (ret < 0) {
protocol = -ret;
goto resubmit;
}
// 添加数据包处理统计信息
IP_INC_STATS_BH(net, IPSTATS_MIB_INDELIVERS);
} else { // 若没有找到相应传输层的处理函数
if (!raw) { // 不是raw socket的数据包,检查ipsec安全策略
if (xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
IP_INC_STATS_BH(net, IPSTATS_MIB_INUNKNOWNPROTOS);
icmp_send(skb, ICMP_DEST_UNREACH,
ICMP_PROT_UNREACH, 0);
}
} else
IP_INC_STATS_BH(net, IPSTATS_MIB_INDELIVERS);
kfree_skb(skb);
}
}
out:
rcu_read_unlock();
return 0;
}
说明:基于 linux kernel 版本: 2.6.27