基于节省路由查找时间的考虑,内核在sock结构体sk_dst_cache中缓存了出口路由表项。
DATAGRAM出口路由
在UDP发包函数中,如果路由缓存rt为空,并且此sock状态为TCP_ESTABLISHED,更新出口路由缓存(sk_dst_set)。TCP_ESTABLISHED为借用了TCP的状态,表明此UDP套接口使用了connect系统调用,建立了一个类似TCP的端到端连接,此时,缓存出口路由才有意义。另外,如果用户层在发送函数send中给出了MSG_DONTROUTE参数(用于直连路由),不缓存出口路由。
int ip4_datagram_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len) { sk_dst_set(sk, &rt->dst); } int udp_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg, size_t len) { if (rt == NULL) { if (connected) sk_dst_set(sk, dst_clone(&rt->dst)); } } void ip4_datagram_release_cb(struct sock *sk) { sk_dst_set(sk, dst); }
TCP出口路由缓存
TCP通过函数sk_setup_caps设置出口路由缓存。
void sk_setup_caps(struct sock *sk, struct dst_entry *dst) { sk_dst_set(sk, dst); }
1)TCP客户端在connect调用中缓存出口路由:
int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len) { sk_setup_caps(sk, &rt->dst); }
2)TCP服务端在三次握手完成之后,在新建的子sock结构体中缓存出口路由:
struct sock *tcp_v4_syn_recv_sock(struct sock *sk, struct sk_buff *skb, struct request_sock *req, struct dst_entry *dst) { sk_setup_caps(newsk, dst); }
IP层路由缓存
IP层在发送数据包时进行路由缓存检查与设置。
int ip_queue_xmit(struct sk_buff *skb, struct flowi *fl) { rt = (struct rtable *)__sk_dst_check(sk, 0); if (rt == NULL) { __be32 daddr; sk_setup_caps(sk, &rt->dst); } }
路径MTU变化导致缓存更新
当接收到ICMP协议报文类型为ICMP_FRAG_NEEDED时,TCP需要更新fib表中的路径MTU,将导致路由项的更新,需要之后更新sock中的路由缓存。另外,如果该路由项设置了mtu lock标志,或者ICMP报文中给出的MTU值本就小于出接口的MTU值,不需更新路由项,也就不用更新sock中的路由缓存。
static struct dst_entry *inet_csk_rebuild_route(struct sock *sk, struct flowi *fl) { if (rt) sk_setup_caps(sk, &rt->dst); } struct dst_entry *inet_csk_update_pmtu(struct sock *sk, u32 mtu) { struct dst_entry *dst = __sk_dst_check(sk, 0); dst->ops->update_pmtu(dst, sk, NULL, mtu); dst = __sk_dst_check(sk, 0); if (!dst) dst = inet_csk_rebuild_route(sk, &inet->cork.fl); }
对于UDP,RAW以及ICMP协议自身来说,接收到ICMP协议报文类型为ICMP_FRAG_NEEDED时,调用ipv4_sk_update_pmtu函数改变pmtu,更新skb中缓存的路由。
void ipv4_sk_update_pmtu(struct sk_buff *skb, struct sock *sk, u32 mtu) { if (new) sk_dst_set(sk, &rt->dst); } void __udp4_lib_err(struct sk_buff *skb, u32 info, struct udp_table *udptable) { case ICMP_DEST_UNREACH: if (code == ICMP_FRAG_NEEDED) { /* Path MTU discovery */ ipv4_sk_update_pmtu(skb, sk, info); } } static void raw_err(struct sock *sk, struct sk_buff *skb, u32 info) { if (type == ICMP_DEST_UNREACH && code == ICMP_FRAG_NEEDED) ipv4_sk_update_pmtu(skb, sk, info); } void icmp_err(struct sk_buff *skb, u32 info) { if (type == ICMP_DEST_UNREACH && code == ICMP_FRAG_NEEDED) ipv4_update_pmtu(skb, net, info, 0, 0, IPPROTO_ICMP, 0); }
出口路由缓存失效
TCP重传超时之后,尝试清空路由缓存(dst_negative_advice)。
static inline void dst_negative_advice(struct sock *sk) { struct dst_entry *ndst, *dst = __sk_dst_get(sk); //回调函数ipv4_negative_advice、ip6_negative_advice //或者VPN的xfrm_negative_advice函数 if (dst && dst->ops->negative_advice) { ndst = dst->ops->negative_advice(dst); if (ndst != dst) { rcu_assign_pointer(sk->sk_dst_cache, ndst); sk_reset_txq(sk); } } } static int tcp_write_timeout(struct sock *sk) { if ((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV)) { if (icsk->icsk_retransmits) { dst_negative_advice(sk); } } }
内核版本
linux-3.10.0