Linux內核中的udp隧道框架

起源

TCP雖然能保證傳輸的可靠性,但其繁瑣的狀態機以及複雜的擁塞控制機制讓它難以作爲隧道報文的外層封裝,詳見TCP-in-TCP

相對而言,UDP就沒這個困擾了,丟包的事情交給應用層處理就行。因而,不少隧道協議都是將UDP作爲外層報文的方案。自然而然,與網絡發展聯繫緊密的Linux內核也開始支持這些隧道協議,較新的內核已經支持fou、l2tp、vxlan、tipc、geneve等UDP隧道協議。

最開始,各個隧道協議都是獨立實現的,但隨着數量的增多,在patch之後,內核將這些UDP隧道公共的部分抽離出來,也就形成了UDP隧道框架,其涉及的API在include/net/udp_tunnel.h中定義

內核UDP隧道的原理

下圖以vxlan爲例,展示了內核UDP隧道的工作過程:

upkkuT.png

其中,左邊是發送端,右邊是接收端,綠色陰影的部分是內核協議棧。可以看出,無論是發送端還是接收端,都涉及函數重入:發送端兩次進入ip_local_out(), 接收端兩次進入ip_local_deliver()

隧道socket

對發送端來說,第一次進入ip_local_out()傳入的sk是與原始報文關聯的套接字,也就是原始協議的套接字,它可能是個TCP套接字,也可能是UDP套接字或者RAWIP套接字,隧道並不care這件事。但是第二次進入ip_local_out()時,它需要一個隧道的UDP套接字。UDP隧道框架提供了一個創建隧道套接字的API。

static inline int udp_sock_create(struct net *net,
                  struct udp_port_cfg *cfg,
                  struct socket **sockp)
{
    if (cfg->family == AF_INET)
        return udp_sock_create4(net, cfg, sockp);

    ......
    return -EPFNOSUPPORT;
}

cfg參數指定了UDP隧道本端和對端和IP地址和使用的端口號。這裏創建的套接字都是內核套接字(區別於用戶態使用socket()創建的)

接收端也是同樣的道理,從真實網卡收到的一定是一個UDP報文,因此接收端也需要一個UDP套接字。這個套接字中記錄的地址和端口信息與接收端正好相反。

upkivV.png

Encap rcv回調函數

對接收端來說,收到UDP報文後,它還需要將報文找到分流給正確的隧道協議,比如這個UDP隧道報文是交給vxlan,還是交給geneve?又或者這根本就只是一個普通的UDP報文,不是一個UDP隧道報文?

因此,內核需要將如何分流記錄在UDP套接字上。

struct udp_sock {
    ......
    /*
     * For encapsulation sockets.
     */
    int (*encap_rcv)(struct sock *sk, struct sk_buff *skb);
    ......
};

這裏的encap_rcv回調函數便是起到隧道報文分流的作用

在UDP接收時,內核會首先查看套接字上是否設置了該回調函數,如果設置了,表示這是一個隧道套接字。調用對應的處理函數,比如vxlan隧道會將其設置爲vxlan_rcv,genven隧道會將其設置爲geneve_udp_encap_recv

設置的過程是通過下面這個API完成的

void setup_udp_tunnel_sock(struct net *net, struct socket *sock,
               struct udp_tunnel_sock_cfg *cfg)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章