TC网络带宽控制(包含与ebpf结合方案)

2023年 7月 19日 63.7k 0

 1.背景

实验环境为centos8。

tc是一个很强大的网络管理工具,比如增加网络延迟、带宽流量管理等,本篇将基于流量管理来详细讲解下相关功能。

tc主要有三种类型:

  • qdisc:队列,用来存放需要处理的数据包。基于队列类型有相应的规则,可以通过man tc来查看。
  • class: 类别,比如针对流量的htb qdisc,也会有很多种类别。比如限制 100m带宽的,1000m带宽的,他们属于不同的类别。类别必需要挂靠在qdisc 上。
  • filter:过滤器,filter同样需要挂靠在qdisc上,由filter定义数据包对应哪个class,并应用相应的规则。
  • 每个网卡,默认会有一个根的qdisc,类型为:fq_codel,当我们添加其它qdisc时,这个默认的会自动被替换。由于ebpf的引入,qdisc还有一个特殊的根,可通过tc add qdisc dev eth0 clsact添加,这个是针对ebpf的da功能而添加的,从linux内核4.4开始,详细的可参考:arthurchiao.art/blog/unders…

    这里只讲解root的qdisc的使用。

    tc的规则,以名为root的qdisc为根,基于filter进行分类,并应用对应class下的规则。而class又可以再继续挂载qdisc,而这个子qdisc又可以挂载class与filter。就这样形成了一棵树形结构,而这个树形结构上的每个元素都有一个唯一的id标识。

    根root的id为1:,对应的16进制为0x10000,而后面新添加的元素,则由用户指定id

    2.基于tc实现流量管理

    1.给根的qdisc规则设置为htb(如果需要修改,需要先将原有的删除,再添加;如果是同规则不同参数,则可以直接用replace)。

    tc qdisc add dev enp1s0 root handle 1: htb
    

    2.基于根,添加两个class,分别对应两种不同的限速方案。

    tc class add dev enp1s0 parent 1: classid 1:1 htb rate 1mbit prio 0
    tc class add dev enp1s0 parent 1: classid 1:2 htb rate 2mbit prio 0
    

    3.基于class 1:1添加子qdisc,并添加限速方案。​​​​​​​

    tc qdisc add dev enp1s0 parent 1:1 handle 2: htb 
    tc class add dev enp1s0 parent 2: classid 2:1 htb rate 3mbit prio 0
    # 同样的格式
    tc qdisc add dev enp1s0 parent 2:1 handle 3: htb
    tc class add dev enp1s0 parent 3: classid 3:1 htb rate 4mbit prio 0
    

    4.添加filter规则,用于指定流量分类规则。

    tc filter add dev enp1s0 protocol ip parent 1: prio 1 u32 match ip dst 10.10.40.25/32 flowid 1:2
    

    这里是基于目的ip进行的分类。最后的flowid,指定的是它将采用哪个class进行策略管理。

    需要注意的是,虽然这里创建的三个qdisc存在父子关系,但不是说一定要从上往下应用下来。比如下面这种分类也是可行的。

    tc filter add dev enp1s0 protocol ip parent 1: prio 1 u32 match ip dst 10.10.40.25/32 flowid 3:1
    

    这种直接从最上层跳到下层,也是可行的。只要规则到达了叶子节点,则这个分类结束。

    5.结果验证

    可通过scp复制文件,检查流量是否被限制。​​​​​​​

    scp ../kernel_4.18_el8.tgz root@10.10.40.25:/tmp/
    kernel_4.18_el8.tgz                                           2% 5808KB   233KB/s   01:30
    

    由于这里限制的是2m,对应KB需要除以8,就在256KB的左右。

    3. 与ebpf功能结合

    tc的功能很强大,同时也提供了很多种filter功能,可通过man tc-ematch或者man tc-u32来查看各种匹配规则。

    使用ebpf的好处:struct __sk_buff *skb ebpf的入参为这个结构,可以通过这个结构,直接获取信息。

    这个结构体的定义可以参考:elixir.bootlin.com/linux/v4.18…

    下面展示基于ebpf程序来设置tc_classid,实现流量控制功能。

    bandwidth_limit.c 里面有些未使用的变量与注释,是为了方便调试。printk的输出,可以通过cat /sys/kernel/debug/tracing/trace_pipe查看。​​​​​​​

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #define bpf_ntohs(x)            __builtin_bswap16(x)
    #define bpf_htons(x)            __builtin_bswap16(x)
    #define bpf_ntohl(x)            __builtin_bswap32(x)
    #define bpf_htonl(x)            __builtin_bswap32(x)
    
    #ifndef __section
    #define __section(NAME)                  \
       __attribute__((section(NAME), used))
    #endif
    
    #ifndef __inline
    #define __inline                         \
       inline __attribute__((always_inline))
    #endif
    
    #ifndef offsetof
    #define offsetof(TYPE, MEMBER) ((size_t) & ((TYPE *)0)->MEMBER)
    #endif
    
    #ifndef BPF_FUNC
    # define BPF_FUNC(NAME, ...)              \
       (*NAME)(__VA_ARGS__) = (void *)BPF_FUNC_##NAME
    #endif
    
    static void *BPF_FUNC(map_lookup_elem, void *map, const void *key);
    static void BPF_FUNC(trace_printk, const char *fmt, int fmt_size, ...);
    static long (*bpf_skb_load_bytes)(const struct __sk_buff *, __u32,
                                       void *, __u32) =
            (void *) BPF_FUNC_skb_load_bytes;
    static long (*bpf_skb_store_bytes)(void *ctx, int off, void *from, int len, int flags) =
            (void *) BPF_FUNC_skb_store_bytes;
    
    #ifndef printk
    # define printk(fmt, ...)                                      \
        ({                                                         \
            char ____fmt[] = fmt;                                  \
            trace_printk(____fmt, sizeof(____fmt), ##__VA_ARGS__); \
        })
    #endif
    
    unsigned long long load_word(void *skb,
                                 unsigned long long off) asm("llvm.bpf.load.word");
    
    static __u64 BPF_FUNC(ktime_get_ns);
    
    #ifndef __READ_ONCE
    # define __READ_ONCE(X)         (*(volatile typeof(X) *)&X)
    #endif
    
    #ifndef __WRITE_ONCE
    # define __WRITE_ONCE(X, V)     (*(volatile typeof(X) *)&X) = (V)
    #endif
    
    static __inline unsigned int set_bandwidth(struct __sk_buff *skb)
    {
      __u32 proto;
      __u64 delay, now, t, t_next;
      __u64 ret;
    
      proto = skb->protocol;
      if (proto != bpf_htons(ETH_P_IP) &&
          proto != bpf_htons(ETH_P_IPV6))
        return 0;
    
      void *data = (void *)(long)skb->data;
      void *data_end = (void *)(long)skb->data_end;
    
      if (data + sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct tcphdr) > data_end) {
        return 0;
      }
    
      struct tcphdr *tcph = data + sizeof(struct ethhdr) + sizeof(struct iphdr);
      unsigned long long daddr = load_word(skb, ETH_HLEN + offsetof(struct iphdr, daddr));
      //unsigned long long saddr = load_word(skb, ETH_HLEN + offsetof(struct iphdr, saddr));
      uint16_t dstPortNumber = ntohs(tcph->dest);
    
      //if (dstPortNumber != 60443)
      //  return 0;
        
      if (daddr != 0x0a0a2819)  // 10.10.40.25
        return 0;
        
      //printk("get classid ok %x", skb->tc_classid);
      //skb->tc_classid=0x10001;
    
      //printk("set classid ok");
      return  0x10002;
    }
    
    __section("bandwidth")
    unsigned int tc_bandwidth(struct __sk_buff *skb)
    {
      return set_bandwidth(skb);
    }
    
    char __license[] __section("license") = "GPL";
    

    编译c : 编译环境所需要的依赖为:yum install -y gcc ncurses-devel elfutils-libelf-devel bc openssl-devel libcap-devel clang llvm graphviz bison flex glibc-devel make。

    clang -O2 -Wall -target bpf  -c bandwidth_limit.c -o bandwidth_limit.o
    

    加载filter。

    tc filter replace dev enp1s0 parent 1: prio 1 handle 1 bpf obj bandwidth_limit.o sec bandwidth
    

    通过ebpf程序返回的值,可以实现filter中设置classid的目的,这样就可以与tc的功能相结合起来。

    当然,从内核5.1开始,可以通过edt的方式实现源生的ebpf流量控制,而老版本还仍然需要依赖tc。由于ebpf程序的引入,可以通过ebpf map实现用户态与内核态的数据交互,而map数据结构则相比tc的规则更加直观,也更加好管理,cilium已经基于edt实现了,可参考:docs.cilium.io/en/v1.13/ne…

    4. 环境清理

    tc qdisc del dev enp1s0 root
    

    本文来自沃趣科技产品研发部专家:李龙辉

    更多技术干货请关注公号【云原生数据库】

    Squids.cn,基于公有云基础资源,提供云上RDS、云备份、云迁移、SQL窗口等门户企业功能,帮助企业快速构建云上数据库融合生态。

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论