Linux Bridge 网卡转发数据时的优先级问题

背景

Linux Bridge 是内核中通过软件模拟交换机的技术,在实际应用中,我们可以通过分析和修改 Bridge 的源代码,来对这台“交换机”做自定义的配置,以满足特殊的场景需求。
三台服务器 host1、host2、host3 依次相连。网络拓扑结构如下所示,host1 有两块网卡 eth0、eth1,分别与 host2、host3 相连。host1 上将两块网卡聚合成一个网桥 br1,host2 和 host3 之间可以借由 br1 进行通信。
在这里插入图片描述
实际运行时,host2 与 host1 均有大量数据发送至 host3。

问题描述

因项目需求,需要对 host2、host1 到 host3 的数据流进行优先级划分,保障部分数据流优先占用网络带宽。
初步方案:
根据网络拓扑可知,发送至 host3 的数据都需要经过 host1 的 eth0 网卡,因此对 eth0 配置 qdisc 规则 prio,prio 会根据 IP 首部 TOS 字段,来将数据包放入不同的队列(band)中,在发送时则会优先发送高优 band 的数据。因此,理论上通过设置不同数据流的 TOS 字段,可以实现数据流的优先级划分。
通过 man tc-prio 查看 TOS 与 band 的对应关系,共有3个 band,band0-band2,发送优先级依次降低。

              TOS     Bits  Means                    Linux Priority    Band
              ------------------------------------------------------------
              0x0     0     Normal Service           0 Best Effort     1
              0x2     1     Minimize Monetary Cost   0 Best Effort     1
              0x4     2     Maximize Reliability     0 Best Effort     1
              0x6     3     mmc+mr                   0 Best Effort     1
              0x8     4     Maximize Throughput      2 Bulk            2
              0xa     5     mmc+mt                   2 Bulk            2
              0xc     6     mr+mt                    2 Bulk            2
              0xe     7     mmc+mr+mt                2 Bulk            2
              0x10    8     Minimize Delay           6 Interactive     0
              0x12    9     mmc+md                   6 Interactive     0
              0x14    10    mr+md                    6 Interactive     0
              0x16    11    mmc+mr+md                6 Interactive     0
              0x18    12    mt+md                    4 Int. Bulk       1
              0x1a    13    mmc+mt+md                4 Int. Bulk       1
              0x1c    14    mr+mt+md                 4 Int. Bulk       1
              0x1e    15    mmc+mr+mt+md             4 Int. Bulk       1

方案测试:
host3 上启动 iperf3 server,host1 和 host2 分别启动 iperf3 client 向 host3 发送数据,将 TOS 字段设置为0x10,通过 tc 打印各个 band 的统计,看数据是否从 band0 被发出。
打印各个 band 统计的命令

tc -s qdisc ls dev eth0

问题现象:
从 host1 上向 host3 发送 TOS 为0x10的数据,能够看到数据均从 band0 发出。但从 host2 向 host3 发送相同的数据,却看到数据是从 band1 发出的,与预期不符。

原因分析:

思路如下:

  1. 分析 prio 源码,看代码具体是根据什么信息,如何将数据包加入不同队列的。
  2. 分析 bridge 中数据转发的代码,看是否会对数据包信息做改动。

基于内核版本:6.0

prio 入队

prio 数据包入队代码

static struct Qdisc *
prio_classify(struct sk_buff *skb, struct Qdisc *sch, int *qerr)
{
        struct prio_sched_data *q = qdisc_priv(sch);
        u32 band = skb->priority; /* 后续根据band来确定返回的queue */
        struct tcf_result res;
        struct tcf_proto *fl;
        int err;

        *qerr = NET_XMIT_SUCCESS | __NET_XMIT_BYPASS;
        if (TC_H_MAJ(skb->priority) != sch->handle) {
                fl = rcu_dereference_bh(q->filter_list);
                err = tcf_classify(skb, NULL, fl, &res, false);
......
                if (!fl || err < 0) {
                        if (TC_H_MAJ(band))
                                band = 0;
                        return q->queues[q->prio2band[band & TC_PRIO_MAX]];
                }
                band = res.classid;
        }
        band = TC_H_MIN(band) - 1;
        if (band >= q->bands)
                return q->queues[q->prio2band[0]];

        return q->queues[band];
}

/* 数据包入队 */
static int
prio_enqueue(struct sk_buff *skb, struct Qdisc *sch, struct sk_buff **to_free)
{
        unsigned int len = qdisc_pkt_len(skb);
        struct Qdisc *qdisc;
        int ret;
		/* prio_classify选择要入的队列 */
        qdisc = prio_classify(skb, sch, &ret);
......
}

根据 prio 源码可知,prio 是根据 skb->priority 来确定要进入的队列的,因此我们需要查看 skb->priority 是在哪里被设置,以及在 bridge 转发过程中是否设置了该值。

skb->priority 设置

梳理 ip 数据发送流程,在 ip 层发送函数 ip_queue_xmit 中,skb->priority 被赋值为 sk->priority。

/* Note: skb->sk can be different from sk, in case of tunnels */
int __ip_queue_xmit(struct sock *sk, struct sk_buff *skb, struct flowi *fl,
                    __u8 tos)
{
......
        /* TODO : should we use skb->sk here instead of sk ? */
        skb->priority = sk->sk_priority;

接下来再看 sk->priority 是在哪里被赋值的。
用户态通过 setsockopt 设置发送数据的 TOS 字段,在内核中的实现如下:

        case IP_TOS:    /* This sets both TOS and Precedence */
                if (sk->sk_type == SOCK_STREAM) {
                        val &= ~INET_ECN_MASK;
                        val |= inet->tos & INET_ECN_MASK;
                }
                if (inet->tos != val) {
                        inet->tos = val;
                        sk->sk_priority = rt_tos2priority(val);
                        sk_dst_reset(sk);
                }
                break;

可以看到,用户态设置 TOS 字段,内核中实际转化为了
sk->priority,也就是说,用户态通过设置 socket 选项来确定属于当前 socket 的数据包的优先级。

网桥转发过程

梳理内核代码,网桥数据转发流程如下:
__netif_receive_skb_core->rx_handler(br_handle_frame)->br_handle_frame_finish->br_pass_frame_up->br_forward->dev_queue_xmit(发送数据)
转发函数中并没有针对 skb->priority 做赋值操作,因此 skb->priority 为0,根据 prio 的 priomap,被分入 band1中。

解决方案:

梳理网桥转发代码,看到转发函数 __br_forward 的结尾处通过 NF_HOOK 插入了一个 netfilter 钩子点,意味着我们可以通过 netfiler 钩子函数来动态改变转发数据的 skb->priority。

static void __br_forward(const struct net_bridge_port *to,
                         struct sk_buff *skb, bool local_orig)
{
......
        NF_HOOK(NFPROTO_BRIDGE, br_hook,
                net, NULL, skb, indev, skb->dev,
                br_forward_finish);
}

netfiler 示例代码,我们需要将 TOS 字段0x10的数据包的优先级改为6(根据 man tc-prio,优先级为6的数据包会被放入 band0)。

#include <linux/netfilter.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/netfilter_bridge.h>
#include <linux/ip.h>
#include <linux/inet.h>
#include <linux/if_ether.h>
#include <linux/in.h>
#include <linux/tcp.h>
#include <linux/byteorder/generic.h>

/**
 * __br_forward处被调用的hook函数.
 * 根据TOS修改数据包的优先级.
 */
unsigned int modify_tos_hookfn(unsigned int hooknum,
        struct sk_buff *skb,
        const struct net_device *in,
        const struct net_device *out,
        int (*okfn)(struct sk_buff *))
{
        struct ethhdr *ethh;
        struct iphdr *iph;

        ethh = eth_hdr(skb);
        /* exclude packets of whose net protocol is not ip */
        if (ethh->h_proto != ntohs(ETH_P_IP))
                goto out;

        iph = ip_hdr(skb);
        if (iph->tos == 0x10) {
                skb->priority = 6;
                trace_printk("Change tos, dstip: 0x%x, tos: 0x%x, priority: %d\n",
                                iph->daddr, iph->tos, skb->priority);
        }

out:
        return NF_ACCEPT;
}

/* A netfilter instance to use */
static struct nf_hook_ops br_hook = {
        .hook = (nf_hookfn *)modify_tos_hookfn,
        .pf = NFPROTO_BRIDGE,
        .hooknum = NF_BR_FORWARD,
        .priority = NF_BR_PRI_FIRST,
};

static int __init nf_init(void)
{
        if (nf_register_net_hook(&init_net, &br_hook)) {
                printk(KERN_ERR"nf_register_hook() failed\n");
                return -1;
        }
        return 0;
}

static void __exit nf_exit(void)
{
        nf_unregister_net_hook(&init_net, &br_hook);
}

module_init(nf_init);
module_exit(nf_exit);
MODULE_LICENSE("GPL");

编译该模块,插入 host1 的内核中,再次通过 iperf3 进行相同的测试。通过 tc 打印各个 band 的统计,就可以看到从 host2 向 host3 发送的 TOS = 0x10 的数据,是从 band0 发出的了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux bridge是一个在Linux操作系统下使用的虚拟网络设备,类似于一个简单的二层虚拟交换机。它集成在Linux中,允许不同的终端虚拟设备通过连接到bridge来实现彼此之间的通信和与外部设备的通信。Linux bridge主要由四个部分组成,这些部分是构成二层物理交换机的关键组件,可以说是虚拟化了物理交换机的基本功能。 与传统的物理交换机类似,Linux bridge具有多个端口,数据可以从任何端口进入,并通过查找目的MAC地址来决定从哪个端口出去。创建Linux bridge,可以将多个物理或虚拟网络接口连接到它上面,从而实现不同设备之间的数据转发和互联。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [云计算网络之--linux bridge 详解](https://blog.csdn.net/wuheshi/article/details/108133097)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [Linux虚拟网络设备之bridge(桥)](https://blog.csdn.net/weixin_34366546/article/details/89059434)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值