内核UDP隧道框架

目前内核支持的基于UDP隧道的协议有L2TP、IPSec、FOU/GUE、GENEVE和VXLAN等。内核为这些隧道协议提供了一个通用的UDP隧道框架,参见文件net/ipv4/udp_tunnel.c,共性的一些操作统一到了框架中。


隧道创建

对于隧道L2TP、FOU/GUE、GENEVE和VXLAN,隧道创建时,都需要在内核中新建一个UDP套接口,框架中的函数udp_sock_create4提供此功能。不仅是套接口的创建,还有本机接口的绑定bind,以及如果特定隧道提供了对端地址信息,进行连接connect。

int udp_sock_create4(struct net *net, struct udp_port_cfg *cfg, struct socket **sockp)
{   
    err = sock_create_kern(net, AF_INET, SOCK_DGRAM, 0, &sock);
    
    udp_addr.sin_family = AF_INET;
    udp_addr.sin_addr = cfg->local_ip;
    udp_addr.sin_port = cfg->local_udp_port; 
    err = kernel_bind(sock, (struct sockaddr *)&udp_addr, sizeof(udp_addr));
    
    if (cfg->peer_udp_port) { 
        udp_addr.sin_family = AF_INET;
        udp_addr.sin_addr = cfg->peer_ip;
        udp_addr.sin_port = cfg->peer_udp_port;
        err = kernel_connect(sock, (struct sockaddr *)&udp_addr, sizeof(udp_addr), 0);
    }
    sock->sk->sk_no_check_tx = !cfg->use_udp_checksums;
}

函数setup_udp_tunnel_sock建立套接口的隧道绑定。此函数将第二个参数socket套接口关联tunnel隧道属性,内核由此套接口接收到的数据包交由配置的encap_rcv回调函数处理(cfg->encap_rcv)。目前基于UDP的隧道协议主要有L2TP、VxLAN和GENEVE,分别注册了接收处理函数_udp_encap_recv、vxlan_rcv和geneve_udp_encap_recv。通用的UDP隧道协议FOU和GUE,处理函数分别为fou_udp_recv和gue_udp_recv。

void setup_udp_tunnel_sock(struct net *net, struct socket *sock, struct udp_tunnel_sock_cfg *cfg)
{
    struct sock *sk = sock->sk;

    udp_sk(sk)->encap_type = cfg->encap_type;
    udp_sk(sk)->encap_rcv = cfg->encap_rcv;
    udp_sk(sk)->encap_destroy = cfg->encap_destroy;
    udp_sk(sk)->gro_receive = cfg->gro_receive;
    udp_sk(sk)->gro_complete = cfg->gro_complete;

    udp_tunnel_encap_enable(sock);
}

L2TP使用UDP端口1701,VxLAN默认使用端口号8472(IANA规定为4789),GENEVE默认使用6801,默认的端口号都可以通过IP命令修改。而后两种UDP隧道协议fou和gue没有默认端口号,需要用户进行指定。

ip add tunnel tunnel_id 1 peer_tunnel_id 1 udp_sport 5000 udp_dport 5000 encap udp local 192.168.1.1 remote 192.168.1.2
ip link add vxlan10 type vxlan id 10 dstport 6789 group 239.1.1.2 dev eth0
ip link add dev gnv0 type geneve remote 10.10.0.1 vni 1234 dstport 5678

ip fou add port 7777 ipproto 47
ip fou add port 9999 gue


IPSec开启NAT-Traversal穿越时,使用UDP的4500端口封装数据包。通过setsockopt将套接口设置为封装套接口,并且设置封装报文的接收函数xfrm4_udp_encap_rcv。

int udp_lib_setsockopt(struct sock *sk, int level, int optname, ...)
{
    struct udp_sock *up = udp_sk(sk);

    switch (optname) {
    case UDP_ENCAP:
        switch (val) {
        case 0:
        case UDP_ENCAP_ESPINUDP:
        case UDP_ENCAP_ESPINUDP_NON_IKE:
            up->encap_rcv = xfrm4_udp_encap_rcv;
            /* FALLTHROUGH */
        case UDP_ENCAP_L2TPINUDP:
            up->encap_type = val;
            udp_encap_enable();
            break;
        }
	}
}

UDP隧道接收

在UDP数据包处理路径中,函数udp_queue_rcv_skb判断当前套接口的udp_encap_needed是否使能,并且encap_type不为0。随即调用绑定在此套接口上的封装数据包回调处理函数encap_rcv进行处理。

static int udp_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)
{
    struct udp_sock *up = udp_sk(sk);

    if (static_key_false(&udp_encap_needed) && up->encap_type) {
        encap_rcv = READ_ONCE(up->encap_rcv);
        if (encap_rcv) {
            ret = encap_rcv(sk, skb);
    }
}

UDP隧道发送

当数据包到达UDP隧道设备的发送函数(ndo_start_xmit)时,例如GENEVE隧道发送函数(geneve_xmit),进行特定隧道相关处理,之后由通用UDP隧道发送函数udp_tunnel_xmit_skb进行发送。

void udp_tunnel_xmit_skb(struct rtable *rt, struct sock *sk, struct sk_buff *skb, ...)
{
    uh = udp_hdr(skb);
    
    uh->dest = dst_port;
    uh->source = src_port; 
    uh->len = htons(skb->len);
    iptunnel_xmit(sk, rt, skb, src, dst, IPPROTO_UDP, tos, ttl, df, xnet);
}  

UDP隧道Offload

对于支持UDP隧道(VXLAN/GENEVE)Offloading功能的物理网卡,其通过标志位NETDEV_UDP_TUNNEL_PUSH_INFO/NETDEV_UDP_TUNNEL_DROP_INFO进行表示。函数udp_tunnel_push_rx_port与udp_tunnel_drop_rx_port用于设置和取消网卡的Offloading功能。

void udp_tunnel_push_rx_port(struct net_device *dev, struct socket *sock, unsigned short type)
{
    struct sock *sk = sock->sk;
    struct udp_tunnel_info ti;

    if (!dev->netdev_ops->ndo_udp_tunnel_add ||
        !(dev->features & NETIF_F_RX_UDP_TUNNEL_PORT))
        return;

    ti.type = type;
    ti.sa_family = sk->sk_family;
    ti.port = inet_sk(sk)->inet_sport;
    dev->netdev_ops->ndo_udp_tunnel_add(dev, &ti);
}

外部UDP隧道

对于非使用ip link系统生成的UDP隧道,即控制通道在外部系统的隧道,如路由系统,其通过ip route encap指定隧道参数,就需要将这些有路由相关的隧道信息保存在路由缓存中。参见UDP框架函数udp_tun_rx_dst,使用metadata_dst结构体保存通用路由信息和隧道信息。

struct metadata_dst *udp_tun_rx_dst(struct sk_buff *skb,  unsigned short family, __be16 flags, __be64 tunnel_id, int md_size)
{
    struct metadata_dst *tun_dst;
    struct ip_tunnel_info *info;
                         
    tun_dst = ip_tun_rx_dst(skb, flags, tunnel_id, md_size);

    info = &tun_dst->u.tun_info;
    info->key.tp_src = udp_hdr(skb)->source;
    info->key.tp_dst = udp_hdr(skb)->dest;
    if (udp_hdr(skb)->check)  
        info->key.tun_flags |= TUNNEL_CSUM;         
}  

内核版本

Linux-4.15

 

你可能感兴趣的:(隧道XFRM)