395
技術社區[雲棲]
網絡子係統46_ip協議數據幀的轉發
// ip協議數據轉發
// ip_forward以回調函數的形式,保存在skb->dst->input,skb->dst在ip_route_input路由封包時被設置
// 調用路徑:ip_rcv->ip_rcv_finish->dst_input->(skb->dst->input)
// 函數的主要任務:
// 1.遞減ttl
// 2.如果路由被重定向,則向發送方發送icmp重定向報文
// 2.如果有選項,通過ip_forward_options處理在轉發時需要更新的選項
// 3.通過ip_output,將報文傳遞到ip發送路徑上
1.1 int ip_forward(struct sk_buff *skb)
{
struct iphdr *iph;
struct rtable *rt;
struct ip_options * opt = &(IPCB(skb)->opt);//ip協議控製塊,由ip_options_compile初始化
//XFRM是 Linux 2.6 內核為安全處理引入的一個可擴展功能框架
if (!xfrm4_policy_check(NULL, XFRM_POLICY_FWD, skb))
goto drop;
//
if (IPCB(skb)->opt.router_alert && ip_call_ra_chain(skb))//對router alert選項的處理
return NET_RX_SUCCESS;
if (skb->pkt_type != PACKET_HOST)//重複檢查,l2地址非本機,直接丟棄
goto drop;
skb->ip_summed = CHECKSUM_NONE;//需要軟件重新計算校驗和
iph = skb->nh.iph;
if (iph->ttl <= 1)//數據幀到期
goto too_many_hops;
//XFRM路由
if (!xfrm4_route_forward(skb))
goto drop;
iph = skb->nh.iph;
rt = (struct rtable*)skb->dst;
//處理嚴源路由選項
// rt->rt_dst 目的ip地址
// rt->rt_gateway 當目的主機直連時,rt_gateway匹配目的地址,當需要通過網關到達目的地址時,rt_gateway設置為下一條網關
// skb->dst在ip_options_rcv_srr中,使用選項中指定的ip地址作為目標地址,通過路由查找,設置為第一個非本機ip的單播路由項
if (opt->is_strictroute && rt->rt_dst != rt->rt_gateway)//由於是嚴源路由選項,因此下一跳地址,必須等於源路由選項中列出的地址
goto sr_failed;
if (skb_cow(skb, LL_RESERVED_SPACE(rt->u.dst.dev)+rt->u.dst.header_len))//檢查skb是否被共享,如果被共享的話,拷貝一份,並在頭部預留l3+l2
goto drop;
iph = skb->nh.iph;
//遞減跳數
ip_decrease_ttl(iph);
//RTCF_DOREDIRECT在ip_route_input_slow中被設置,表示ICMP_REDIRECT必須被送回源地址
if (rt->rt_flags&RTCF_DOREDIRECT && !opt->srr)
ip_rt_send_redirect(skb);
skb->priority = rt_tos2priority(iph->tos);//通過tos字段計算skb在規則隊列中的優先級
return NF_HOOK(PF_INET, NF_IP_FORWARD, skb, skb->dev, rt->u.dst.dev,
ip_forward_finish);
sr_failed:
icmp_send(skb, ICMP_DEST_UNREACH, ICMP_SR_FAILED, 0);
goto drop;
too_many_hops:
icmp_send(skb, ICMP_TIME_EXCEEDED, ICMP_EXC_TTL, 0);
drop:
kfree_skb(skb);
return NET_RX_DROP;
}
// 將轉發數據傳遞到發送路徑上
// 調用路徑:ip_forward->netfilter hooks->ip_forward_finish
1.2 static inline int ip_forward_finish(struct sk_buff *skb)
{
struct ip_options * opt = &(IPCB(skb)->opt);
IP_INC_STATS_BH(IPSTATS_MIB_OUTFORWDATAGRAMS);
if (unlikely(opt->optlen))
ip_forward_options(skb);//處理寬鬆路由選項和time stamp選項
return dst_output(skb);//調用ip_output
}
// 數據轉發時更新的選項
// 1.record route選項
// 2.源路由選項
// 3.time stamp選項
1.3 void ip_forward_options(struct sk_buff *skb)
{
struct ip_options * opt = &(IPCB(skb)->opt);
unsigned char * optptr;
struct rtable *rt = (struct rtable*)skb->dst;
unsigned char *raw = skb->nh.raw;
//record route選項
if (opt->rr_needaddr) {//說明為出口skb,因為入口skb在ip_options_compile已經被使用首選源地址填寫
optptr = (unsigned char *)raw + opt->rr;
ip_rt_get_source(&optptr[optptr[2]-5], rt);
opt->is_changed = 1;
}
if (opt->srr_is_hit) {//表示在源路由選項列表中,有可用的下一跳ip地址
int srrptr, srrspace;
optptr = raw + opt->srr;
for ( srrptr=optptr[2], srrspace = optptr[1];
srrptr <= srrspace;
srrptr += 4
) {
if (srrptr + 3 > srrspace)
break;
if (memcmp(&rt->rt_dst, &optptr[srrptr-1], 4) == 0)//下一跳地址在源路由地址列表中
break;
}
if (srrptr + 3 <= srrspace) {//表示下一跳在源路由地址列表中
opt->is_changed = 1;
ip_rt_get_source(&optptr[srrptr-1], rt);
skb->nh.iph->daddr = rt->rt_dst;//更新目的地址為下一跳的地址
optptr[2] = srrptr+4;//更新選項的ptr[2]指針到下一跳
} else if (net_ratelimit())
printk(KERN_CRIT "ip_forward(): Argh! Destination lost!\n");
if (opt->ts_needaddr) {
optptr = raw + opt->ts;
ip_rt_get_source(&optptr[optptr[2]-9], rt);
opt->is_changed = 1;
}
}
if (opt->is_changed) {
opt->is_changed = 0;
ip_send_check(skb->nh.iph);//計算ip校驗和
}
}
最後更新:2017-04-03 14:53:38