ipv6:linux上發生路由查詢的兩個位置
一什麼時候發生路由查詢?
首先認識路由樹:
具體說來:
根據ip地址來區分參考“IPv6地址型別:單播/組播/任播地址”:
(1)If the destination address matchesFE80::<EUI64>//資料包是發給本機,執行一般的收發,對於PC機而言就是走這條。通過dst_entry到ND協議,發給最近的路由主機。
skb->dst->input=ip6_input
skb->dst->output=ip6_output---
(2)Else if the destination address’s first 10bits matches FE80::
skb->dst->input=ip6_forward
skb->dst->output=ip6_output
(3)Else if the destination address’s first 8bits matches FF00:://資料包廣播
skb->dst->input=ip6_mc_input
skb->dst->output=ip6_output
(4)Else ( no match)
skb->dst->input=ip6_pkt_discard
skb->dst->output=ip6_pkt_discard
“ip字首”對應的”路由節點“樹:
其次:關鍵資料結構dst_entry:
最終生成的IP資料報的路由稱為目的入口(dst_entry),目的入口反映了相鄰的外部主機在主機內部的一種“映象”,
struct dst_entry {
…..
int (*input)(structsk_buff *);//決定該路由樹節點是應該轉發還是接收
int (*output)(structsk_buff *);//傳送介面
…...
union{
structdst_entry *next;
structrtable __rcu *rt_next;
structrt6_info *rt6_next;
structdn_route __rcu *dn_next;
};
};
兩種情況會發生路由查詢:
1)接收資料的時候:ip6_route_input()
就是在主機接收到資料包的時候發生路由查詢,為什麼在接收資料的時候呢?因為一個數據包,需要路由判斷其目的地(
如果目的地是本機,則進入接收的流程;
如果不是本機,則通過forward處理,然後進入傳送流程;
如果都不是,則丟棄.
大概路由查詢的原理是:主機需要通過路由表來判斷資料包的目的地在ip6_rcv_finish()裡面,會調用ip6_route_input(skb),這個函式返回的是路由表中對應的fib6_node,這個節點的input函式,就會根據不同的目的地呼叫不同的函式來處理。
2)傳送時候發生的路由:ip6_route_output()
二路由查詢的呼叫流程和查詢演算法
1)接收資料的時候:ip6_route_input()
ipv6_rcv()---->ip6_rcv_finish()----》ip6_route_input{
voidip6_route_input(struct sk_buff *skb)
{
conststruct ipv6hdr *iph = ipv6_hdr(skb);
structnet *net = dev_net(skb->dev);
intflags = RT6_LOOKUP_F_HAS_SADDR;
structflowi6 fl6 = {
.flowi6_iif= skb->dev->ifindex,
.daddr= iph->daddr,
.saddr= iph->saddr,
.flowlabel= ip6_flowinfo(iph),
.flowi6_mark= skb->mark,
.flowi6_proto= iph->nexthdr,
};
skb_dst_set(skb, ip6_route_input_lookup(net, skb->dev,&fl6, flags));
}
static inline voidskb_dst_set(struct sk_buff *skb, struct dst_entry *dst)
{
skb->_skb_refdst = (unsigned long)dst;---執行ip6_route_input_lookup()
}
static structdst_entry *ip6_route_input_lookup(structnet *net,
structnet_device *dev,
structflowi6 *fl6, int flags)
{
if(rt6_need_strict(&fl6->daddr) && dev->type != ARPHRD_PIMREG)
flags|= RT6_LOOKUP_F_IFACE;
returnfib6_rule_lookup(net, fl6,flags, ip6_pol_route_input);
}
最終跑到:ip6_pol_route_input()--->ip6_pol_route()--->fib6_lookup()----->---》fib6_lookup_1()//查詢路由表的Radix樹。
2)傳送時候發生的路由:ip6_route_output()
所有在ip6_xmit之前都會先進行路由查詢:例如:
Tcp_ipv6.c(trunk\kernel-3.10\net\ipv6): err= ip6_xmit(sk, skb, fl6, np->opt, np->tclass);
Tcp_ipv6.c(trunk\kernel-3.10\net\ipv6): ip6_xmit(ctl_sk,buff, &fl6, NULL, tclass);
Ipv6.c(trunk\kernel-3.10\net\dccp): err= ip6_xmit(sk, skb, &fl6, np->opt, np->tclass);
dccp_v6_send_response()---->ip6_dst_lookup_flow()進行路由查詢
Ipv6.c(trunk\kernel-3.10\net\dccp): ip6_xmit(ctl_sk,skb, &fl6, NULL, 0);
tcp_v6_send_synack()----》inet6_csk_route_req()----ip6_dst_lookup_flow()//進行路由查詢
Ipv6.c(trunk\kernel-3.10\net\sctp): returnip6_xmit(sk, skb, fl6, np->opt, np->tclass);
Inet6_connection_sock.c(trunk\kernel-3.10\net\ipv6): res= ip6_xmit(sk, skb, &fl6, np->opt, np->tclass);
例如一般的tcp傳送資料,tcp層到--->網路層:
Inet6_connection_sock.c@inet6_csk_xmit(struct sk_buff *skb, struct flowi*fl_unused){
完成的工作:路由查詢(就是找到一個合適的正確的dst)
int inet6_csk_xmit(struct sk_buff *skb, struct flowi*fl_unused)
{
structsock *sk = skb->sk;
structipv6_pinfo *np = inet6_sk(sk);
structflowi6 fl6;
struct dst_entry *dst;
intres;
//開始路由查詢:
dst =inet6_csk_route_socket(sk, &fl6);//查詢路由表,目的是根據路由表資訊,找到該資料包的下一條地址。
...
skb_dst_set_noref(skb, dst);//把查詢到的dst 賦值給,上層下來的skb包中的成員:skb->_skb_refdst
/* Restore final destination back after routing done */
fl6.daddr= np->daddr;
//路由查詢結束;
res= ip6_xmit(sk, skb, &fl6, np->opt,np->tclass);
rcu_read_unlock();
returnres;
}
static structdst_entry *inet6_csk_route_socket(struct sock*sk,
structflowi6 *fl6)
{
structinet_sock *inet = inet_sk(sk);
structipv6_pinfo *np = inet6_sk(sk);
structin6_addr *final_p, final;
structdst_entry *dst;
//更新一條dst 到flowi資訊
memset(fl6,0, sizeof(*fl6));
fl6->flowi6_proto= sk->sk_protocol;
fl6->daddr= np->daddr;
fl6->saddr= np->saddr;
fl6->flowlabel= np->flow_label;
IP6_ECN_flow_xmit(sk,fl6->flowlabel);
fl6->flowi6_oif= sk->sk_bound_dev_if;
fl6->flowi6_mark= sk->sk_mark;
fl6->fl6_sport= inet->inet_sport;
fl6->fl6_dport= inet->inet_dport;
fl6->flowi6_uid= sock_i_uid(sk);
security_sk_classify_flow(sk,flowi6_to_flowi(fl6));
final_p= fl6_update_dst(fl6, np->opt, &final);
//首先查詢路由快取中是否有匹配的dst
dst= __inet6_csk_dst_check(sk, np->dst_cookie);
if(!dst) {
//其次到路由表裡面去查詢 dst
dst= ip6_dst_lookup_flow(sk, fl6, final_p, false);
if(!IS_ERR(dst))
__inet6_csk_dst_store(sk,dst, NULL, NULL);
}
returndst;
}
//到路由表裡面去查詢 dst的流程是:
ip6_dst_lookup_flow()-----》ip6_dst_lookup_tail()----》ip6_route_output()----》fib6_rule_lookup()----》ip6_pol_route_output()
----ip6_pol_route()---》fib6_lookup()---》fib6_lookup_1()//查詢路由表的Radix樹。
3)查詢演算法:
fib6_lookup_1()