calico vxlan ipv4 overlay組網(wǎng)跨主機(jī)通信分析

主機(jī)信息

演示環(huán)境.png

由圖可以看出overlay跨主機(jī)通信的細(xì)節(jié)還是很復(fù)雜,經(jīng)過了多個(gè)網(wǎng)卡后才經(jīng)由物理網(wǎng)卡發(fā)出。podEth0 -> cali39d -> vxlan.calico -> ens33。本文詳細(xì)解析了數(shù)據(jù)包是如何在內(nèi)核協(xié)議棧流轉(zhuǎn)的,vxlan設(shè)備封裝報(bào)文的具體過程。
本篇包含以下內(nèi)容 :

  • pod跨命名空間通信
  • calixx網(wǎng)卡到vxlan設(shè)備通信
  • vxlan設(shè)備到物理設(shè)備通信
  • 添加路由內(nèi)核代碼分析
  • 路由條目中的onlink參數(shù)

pod內(nèi)的eth0 -> calixx網(wǎng)卡

參見鏈接 http://www.itdecent.cn/p/75392d686c59

calixxx -> vxlan.calico

calixxx 為容器命名空間內(nèi)網(wǎng)卡veth對(duì)的另一端,存在于主機(jī)的命名空間內(nèi)。
calixx收到二層包之后,向上進(jìn)行傳遞。

netfilter.png
協(xié)議棧處理.png

1、 calixx驅(qū)動(dòng)收到報(bào)文后,經(jīng)過二層處理,經(jīng)過ip_rcv進(jìn)入ip層處理。
2、 通過查找路由,確定了該包需要經(jīng)過vxlan.calico發(fā)出去,下一跳的ip地址是對(duì)端的vtep的地址,對(duì)端vtep的mac地址已經(jīng)由felix寫入到主機(jī)的鄰居表項(xiàng)中。這里要重點(diǎn)理解下一跳的含義。
3、 由于已經(jīng)知道了對(duì)端vtep設(shè)備的mac地址,因?yàn)檫@里不再需要進(jìn)行mac地址查詢,直接由neigh_output交由下層vxlan設(shè)備處理。

路由條目中的onlink

這里先寫個(gè)neilink處理消息的大概邏輯,后續(xù)補(bǔ)充個(gè)流程圖
下一跳是否可達(dá),就是判斷下一跳的地址是否在已有的路由中
執(zhí)行perf record -e probe:fib_check_nh -e probe:netlink_recvmsg -agR ip r add 5.5.6.0/24 via 4.4.10.2 dev ens192 onlink

添加路由.png
// netlink消息的協(xié)議處理函數(shù)
static const struct proto_ops netlink_ops = {
    .family =   PF_NETLINK,
    .owner =    THIS_MODULE,
    .release =  netlink_release,
    .bind =     netlink_bind,
    .connect =  netlink_connect,
    .socketpair =   sock_no_socketpair,
    .accept =   sock_no_accept,
    .getname =  netlink_getname,
    .poll =     datagram_poll,
    .ioctl =    netlink_ioctl,
    .listen =   sock_no_listen,
    .shutdown = sock_no_shutdown,
    .setsockopt =   netlink_setsockopt,
    .getsockopt =   netlink_getsockopt,
    // 內(nèi)核收到用戶態(tài)的消息后的處理函數(shù)
    .sendmsg =  netlink_sendmsg,
    // 用戶讀取netlink消息的處理函數(shù)
    .recvmsg =  netlink_recvmsg,
    .mmap =     sock_no_mmap,
    .sendpage = sock_no_sendpage,
};

// 注冊(cè)增加路由條目的處理函數(shù)
rtnl_register(PF_INET, RTM_NEWROUTE, inet_rtm_newroute, NULL, 0);

struct netlink_kernel_cfg cfg = {
    .groups     = RTNLGRP_MAX,
    .input      = rtnetlink_rcv,
    .cb_mutex   = &rtnl_mutex,
    .flags      = NL_CFG_F_NONROOT_RECV,
    .bind       = rtnetlink_bind,
};
struct sock *
__netlink_kernel_create(struct net *net, int unit, struct module *module,
            struct netlink_kernel_cfg *cfg)
{
    if (cfg && cfg->input)
        // 修改netlink_rcv 為 rtnetlink_rcv函數(shù)
        nlk_sk(sk)->netlink_rcv = cfg->input;
}

// 根據(jù)perf抓取到的調(diào)用棧,繼續(xù)向下分析
static int netlink_unicast_kernel(struct sock *sk, struct sk_buff *skb,
                  struct sock *ssk)
{
    int ret;
    struct netlink_sock *nlk = nlk_sk(sk);

    ret = -ECONNREFUSED;
    if (nlk->netlink_rcv != NULL) {
        ret = skb->len;
        netlink_skb_set_owner_r(skb, sk);
        NETLINK_CB(skb).sk = ssk;
        netlink_deliver_tap_kernel(sk, ssk, skb);
        // 這里就調(diào)用到了上面的cfg的input函數(shù),即rtnetlink_rcv函數(shù)
        nlk->netlink_rcv(skb);
        consume_skb(skb);
    } else {
        kfree_skb(skb);
    }
    sock_put(sk);
    return ret;
}

// 處理子模塊的消息
static int rtnetlink_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh,
                 struct netlink_ext_ack *extack)
{
    link = rtnl_get_link(family, type);
    if (flags & RTNL_FLAG_DOIT_UNLOCKED) {
        doit = link->doit;
        rcu_read_unlock();
        // doit就是注冊(cè)的新增路由表的處理函數(shù)inet_rtm_newroute
        if (doit)
            err = doit(skb, nlh, extack);
        module_put(owner);
        return err;
    }
}

// 對(duì)于下一跳的檢查函數(shù),內(nèi)核注釋中寫明了下一跳很復(fù)雜,是由于歷史原因,,,
static int fib_check_nh(struct fib_config *cfg, struct fib_nh *nh,
            struct netlink_ext_ack *extack)
{
    if (nh->nh_gw) {
        struct fib_result res;
        // 有onlink參數(shù),單獨(dú)處理
        if (nh->nh_flags & RTNH_F_ONLINK) {
            dev = __dev_get_by_index(net, nh->nh_oif);
            addr_type = inet_addr_type_dev_table(net, dev, nh->nh_gw);
            nh->nh_dev = dev;
            dev_hold(dev);
            nh->nh_scope = RT_SCOPE_LINK;
            return 0;
        }
        // 沒有onlink,需要查找下一跳nh是否可達(dá)。
        {
            struct fib_table *tbl = NULL;
            struct flowi4 fl4 = {
                .daddr = nh->nh_gw,
                .flowi4_scope = cfg->fc_scope + 1,
                .flowi4_oif = nh->nh_oif,
                .flowi4_iif = LOOPBACK_IFINDEX,
            };
            if (cfg->fc_table)
                tbl = fib_get_table(net, cfg->fc_table);

            if (tbl)
                err = fib_table_lookup(tbl, &fl4, &res,
                               FIB_LOOKUP_IGNORE_LINKSTATE |
                               FIB_LOOKUP_NOREF);

            if (!tbl || err) {
                err = fib_lookup(net, &fl4, &res,
                         FIB_LOOKUP_IGNORE_LINKSTATE);
            }

            if (err) {
                NL_SET_ERR_MSG(extack,
                           "Nexthop has invalid gateway");
                rcu_read_unlock();
                return err;
            }
        }
}

vxlan設(shè)備到物理網(wǎng)卡

static const struct net_device_ops vxlan_netdev_ether_ops = {
    .ndo_init       = vxlan_init,
    .ndo_uninit     = vxlan_uninit,
    .ndo_open       = vxlan_open,
    .ndo_stop       = vxlan_stop,
    .ndo_start_xmit     = vxlan_xmit,
    .ndo_get_stats64    = ip_tunnel_get_stats64,
};
vxlan.png

這里這個(gè)箭頭從下面指到上面并不是畫圖空間不足,而是為了表現(xiàn)數(shù)據(jù)包是從二層又回到了三層的處理中。
1、vxlan設(shè)備的轉(zhuǎn)發(fā)表中記錄了對(duì)端vtep的mac地址和remoteip的對(duì)應(yīng)關(guān)系。
2、 封裝udp,和ip頭后,經(jīng)由ip_local_out重新進(jìn)入ip層處理,經(jīng)過output和postrouting后,由ip_finish_output2出ip層。這里要重點(diǎn)理解,vxlan驅(qū)動(dòng)將上層包偽裝成一個(gè)要出本機(jī)的正常的數(shù)據(jù)包,由ip_local_out進(jìn)入ip層處理,因?yàn)閕p_local_out是一個(gè)正常的出本機(jī)udp,tcp包在ip層處理的入口函數(shù)。要正確理解vxlan設(shè)備的封裝過程以及vxlan設(shè)備在這個(gè)過程中起到的作用。
3、 經(jīng)過查詢路由,與本機(jī)處于同網(wǎng)段,通過mac地址查詢獲取到對(duì)端物理網(wǎng)卡的mac地址,經(jīng)由物理網(wǎng)卡發(fā)送。如果與本機(jī)不在同一網(wǎng)段,則將包交由網(wǎng)關(guān)處理。

物理網(wǎng)卡

static const struct net_device_ops ixgb_netdev_ops = {
    .ndo_start_xmit     = ixgb_xmit_frame,
};

wireshark分析

抓包.png

perf抓取內(nèi)核調(diào)用棧

在pod內(nèi)ping另一臺(tái)主機(jī)上的pod的ip。使用perf抓取指定函數(shù)的調(diào)用堆棧。

perf probe --add iptunnel_xmit
perf probe --add ip_output
perf record  -e probe:iptunnel_xmit -e probe:ip_output -ag -F max sleep 10
perf使用.png

使用ftrace抓取內(nèi)核調(diào)用棧

在pod內(nèi)ping另一臺(tái)主機(jī)上的pod的ip。使用ftrace抓取指定函數(shù)的調(diào)用堆棧。

echo iptunnel_xmit > /sys/kernel/debug/tracing/set_ftrace_filter
echo function  > /sys/kernel/debug/tracing/current_tracer
echo 1 > /sys/kernel/debug/tracing/options/func_stack_trace
cat /sys/kernel/debug/tracing/trace
           <...>-527743 [001] ..s1 98518.998187: iptunnel_xmit <-vxlan_xmit_one
           <...>-527743 [001] ..s1 98518.998199: <stack trace>
 => iptunnel_xmit
 => vxlan_xmit_one
 => vxlan_xmit
 => dev_hard_start_xmit
 => __dev_queue_xmit
 => ip_finish_output2
 => ip_output
 => ip_forward
 => ip_rcv
 => __netif_receive_skb_one_core
 => process_backlog
 => net_rx_action
 => __do_softirq
 => do_softirq_own_stack
 => do_softirq
 => __local_bh_enable_ip
 => ip_finish_output2
 => ip_output
 => ip_send_skb
 => raw_sendmsg
 => sock_sendmsg
 => __sys_sendto
 => __x64_sys_sendto
 => do_syscall_64
 => entry_SYSCALL_64_after_hwframe

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容