sock结构中的出口路由缓存

基于节省路由查找时间的考虑,内核在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



猜你喜欢

转载自blog.csdn.net/sinat_20184565/article/details/80077252