skb结构体中的成员_skb_refdst用于暂时缓存路由,避免在skb生存期内的重复路由查找。不同于sock结构体中有两个成员缓存路由:sk_rx_dst缓存入口路由,sk_dst_cache缓存出口路由。skb结构体中的_skb_refdst在特定时刻仅缓存一种路由。
路由缓存引用计数
如下设置缓存的两个函数,skb_dst_set需要在调用前增加引用计数(dst_clone);而skb_dst_set_noref不需要,其通过标志SKB_DST_NOREF用来标识此缓存没有引用计数,并且在skb_dst_drop函数释放路由缓存时,不进行释放操作。
static inline void skb_dst_set(struct sk_buff *skb, struct dst_entry *dst) { skb->_skb_refdst = (unsigned long)dst; } static inline void skb_dst_set_noref(struct sk_buff *skb, struct dst_entry *dst) { skb->_skb_refdst = (unsigned long)dst | SKB_DST_NOREF; }
出口路由缓存
对于本地发出的数据包(本地创建分配的skb),其缓存的为出口路由。例如,作为TCP服务端,在回复客户端SYN+ACK时,新建一个skb结构体,根据路由查询结果(inet_csk_route_req查询出口路由),设置skb路由缓存,此时缓存的为出口路由,之后在发送过程中就不需要再次查找路由了。
struct sk_buff *tcp_make_synack(...) { skb = sock_wmalloc(sk, MAX_TCP_HEADER, 1, GFP_ATOMIC); skb_dst_set(skb, dst); } static int tcp_v4_send_synack(...) { if (!dst && (dst = inet_csk_route_req(sk, &fl4, req)) == NULL) return -1; skb = tcp_make_synack(sk, dst, req, foc); }
入口路由缓存
static int ip_rcv_finish(struct sock *sk, struct sk_buff *skb) { if (sysctl_ip_early_demux && !skb_dst(skb) && skb->sk == NULL) { ipprot->early_demux(skb); } if (!skb_valid_dst(skb)) { int err = ip_route_input_noref(skb, iph->daddr, iph->saddr, iph->tos, skb->dev); } }
转发路由缓存
转发路由缓存
与入口路由缓存查找方法相同,同是ip_rcv_finish函数中获得转发路由缓存。此时,FIB(fib_lookup)查询出的路由类型不是之前的RTN_LOCAL,路由dst的input函数指针设置为ip_forward; output函数指针设置为ip_output。在转发过程中避免重复查找路由。
static int __mkroute_input(...) { rth = rt_dst_alloc(out_dev->dev, IN_DEV_CONF_GET(in_dev, NOPOLICY), IN_DEV_CONF_GET(out_dev, NOXFRM), do_cache); rth->dst.input = ip_forward; rth->dst.output = ip_output; } static int ip_route_input_slow(...) { if (!IN_DEV_FORWARD(in_dev)) goto no_route; // 最终调用__mkroute_input。 err = ip_mkroute_input(skb, &res, &fl4, in_dev, daddr, saddr, tos); out: return err; }
内核版本
linux-3.10.0