1. 程式人生 > >Linux MPLS功能詳解

Linux MPLS功能詳解

使用mpls功能,首先需要載入mpls相關的模組:$ sudo modprobe mpls_gso $ sudo modprobe mpls_iptunnel $ sudo modprobe mpls_router  

使能mpls的接收和設定labels表項的數量,預設情況下核心不接收mpls報文,如果不使能此項,在如下使用ip命令配置本機環回lo介面接收mpls資料包時就會失敗。labels轉發表項的數量初始為0,將會導致不能配置label轉發表項。

sysctl -w net.mpls.conf.lo.input=1 sysctl -w net.mpls.platform_labels=1048575

配置1:到網路10.1.1.0/30的資料包增加200和300兩個label

$ sudo ip route add 10.1.1.0/30 encap mpls 200/300 via 192.168.1.1 dev ens33     $  $ ip route  10.1.1.0/30  encap mpls  200/300 via 192.168.1.1 dev ens33  $ 

使用ping測試,tcpdump抓包內容可見,增加了200和300兩個label,標籤300設定了標籤棧底標誌[S]。$ ping 10.1.1.1 PING 10.1.1.1 (10.1.1.1) 56(84) bytes of data. $ $ sudo tcpdump mpls -vve tcpdump: listening on ens33, link-type EN10MB (Ethernet), capture size 262144 bytes 23:36:30.813147 00:0c:29:74:7f:04 (oui Unknown) > 00:90:27:fe:c9:34 (oui Unknown), ethertype MPLS unicast (0x8847), length 106:  MPLS (label 200, exp 0, ttl 64)      (label 300, exp 0, [S], ttl 64)      (tos 0x0, ttl 64, id 36751, offset 0, flags [DF], proto ICMP (1), length 84)     localhost > localhost: ICMP echo request, id 6907, seq 1, length 64

配置2:標籤為500的mpls資料包送到本機的環回介面lo:

$ sudo ip -f mpls route add 500 dev lo $ $ip -f mpls route show 500 dev lo  $

配置3:標籤為100的資料包替換為標籤600,發往192.168.1.2。

$ sudo ip -f mpls route add 100 as to 600 via inet 192.168.1.2 $ $ip -f mpls route show 100 as to 600 via inet 192.168.1.2 dev ens33  500 dev lo  $

配置4:指定多個下一跳地址,為每個下一跳指定不同的(90/91)標籤:

$ sudo ip -f mpls route add 80 nexthop as to 90 via inet 192.168.1.2 nexthop as to 91 via inet 192.168.1.3   $  $ ip -d -f mpls route show unicast 80 proto boot scope global          nexthop as to 90 via inet 192.168.1.2  dev ens33         nexthop as to 91 via inet 192.168.1.3  dev ens33 $

MPLS的路由結構

核心函式mpls_route_add處理MPLS路由的新增工作。所有的MPLS路由全部組織在網路名稱空間的成員platform_label指標陣列(指標的指標)中,通過標籤值可找到對應的MPLS路由。全部路由不超過platform_labels指定的最大數量。

struct netns_mpls {     size_t platform_labels;     struct mpls_route __rcu * __rcu *platform_label; }

一個mpls_route路由表項所佔空間與其所有的下一跳mpls_nh的空間,以及每個下一跳的所有label空間之總和不超過4K位元組大小。mpls_route路由表項結構圖如下:

另外,核心註冊了netdev通知鏈函式mpls_dev_notify處理mpls路由的失效和活動狀態。

static struct notifier_block mpls_dev_notifier = {     .notifier_call = mpls_dev_notify, };

當接收到裝置關閉NETDEV_DOWN事件時,呼叫mpls_ifdown遍歷所有的mpls路由表項,將出口裝置為此裝置的表項標記為不可用(RTNH_F_DEAD)。如果接收到裝置NETDEV_UP的事件,同樣的遍歷所有的表項,經出口裝置為此裝置的表項標記為可用,即去掉RTNH_F_DEAD標誌。

static int mpls_dev_notify(struct notifier_block *this, unsigned long event, void *ptr)
{
    struct net_device *dev = netdev_notifier_info_to_dev(ptr);

    switch (event) {
    case NETDEV_DOWN:
        mpls_ifdown(dev, event);
        break;
    case NETDEV_UP:
        mpls_ifup(dev, RTNH_F_DEAD | RTNH_F_LINKDOWN);
        break;
	}
}

MPLS資料幀處理

Linux核心協議棧註冊了一個ETH_P_MPLS_UC(0x8847)型別的資料包處理函式(mpls_packet_type)處理MPLS報文。            static struct packet_type mpls_packet_type __read_mostly = {     .type = cpu_to_be16(ETH_P_MPLS_UC),     .func = mpls_forward, };

首先通過MPLS標籤解析mpls_entry_decode函式,得到資料包中MPLS標籤的4個欄位:標籤值、TTL、流量類別和標籤棧底標誌位。標籤值欄位佔用20個bit位,其最大值為2**20 -1,由於mpls協議存在預留值,合法的標籤值從16開始(MPLS_LABEL_FIRST_UNRESERVED)。

static inline struct mpls_entry_decoded mpls_entry_decode(struct mpls_shim_hdr *hdr)
{    
    struct mpls_entry_decoded result;
    unsigned entry = be32_to_cpu(hdr->label_stack_entry);

    result.label = (entry & MPLS_LS_LABEL_MASK) >> MPLS_LS_LABEL_SHIFT;
    result.ttl = (entry & MPLS_LS_TTL_MASK) >> MPLS_LS_TTL_SHIFT;
    result.tc =  (entry & MPLS_LS_TC_MASK) >> MPLS_LS_TC_SHIFT;
    result.bos = (entry & MPLS_LS_S_MASK) >> MPLS_LS_S_SHIFT;

    return result;
}

其次通過得到的MPLS標籤值,查詢mpls路由表。有之前介紹的mpls路由表結構可知,以標籤值做索引即可找到對應的路由項:

static struct mpls_route *mpls_route_input_rcu(struct net *net, unsigned index)
{
    struct mpls_route __rcu **platform_label = rcu_dereference(net->mpls.platform_label);
    rt = rcu_dereference(platform_label[index]);
    return rt;
}

對於僅有一個下一跳的路由表項,直接使用。對於多個下一跳地址,需要在找到的路由項中,選擇一個下一跳地址,函式(mpls_select_multipath)完成此功能。檢查如果活動的下一跳的個數為0,返回空。否則根據資料包中的以下欄位計算hash值:最多4個標籤值、IP頭的源IP地址、目的IP地址和協議號。得到的hash值與當前活動的下一跳數量(alive)取餘得到下一跳地址的索引值。mpls_get_nexthop函式取出mpls路由的下一跳。

如果活動的下一跳數量與下一跳總數相等,說明根據下一跳索引nh_index可以直接取到下一跳結構。否則,需要遍歷所有活動的下一跳列表,找到此下一跳。

    alive = READ_ONCE(rt->rt_nhn_alive);
    if (alive == 0) return NULL;

    hash = mpls_multipath_hash(rt, skb);
    nh_index = hash % alive;
    if (alive == rt->rt_nhn) goto out;
    for_nexthops(rt) {
        unsigned int nh_flags = READ_ONCE(nh->nh_flags);
        if (nh_flags & (RTNH_F_DEAD | RTNH_F_LINKDOWN))  continue;
        if (n == nh_index) return nh;
        n++;
    } endfor_nexthops(rt);
out:
    return mpls_get_nexthop(rt, nh_index);

如果配置了標籤替換,僅替換資料包中的頂層標籤。以下程式碼將mpls路由表項中下一跳對應的標籤棧寫入到資料包頭中。

    hdr = mpls_hdr(skb);
    bos = dec.bos;
    for (i = nh->nh_labels - 1; i >= 0; i--) {
        hdr[i] = mpls_entry_encode(nh->nh_label[i], dec.ttl, 0, bos);
        bos = false;
    }

環境

iproute2版本:4.17.0 核心版本:  Linux-4.15