阿里云-云小站(无限量代金券发放中)
【腾讯云】云服务器、云数据库、COS、CDN、短信等热卖云产品特惠抢购

Linux流负载均衡中Layer7的数据流(连接跟踪)识别问题

180次阅读
没有评论

共计 2885 个字符,预计需要花费 8 分钟才能阅读完成。

1. 支持 Layer7 的 nf_conntrack 真的没有必要做

走火入魔之后,你会觉得需要赶紧将“基于五元组的数据流”改成“基于应用层协议固定偏移的数据流”,赶紧动手,越快越好!于是此人在支持 zone conntrack 的 Linux 3.17 内核上为 nf_conn 增加了几个字段:

bool l7; // 布尔型,表示是否要进行 layer7 的匹配。

u32 offset; // 应用层流标识的偏移

u32 offlen; // 应用层流标识的长度

以上的三个字段在 CT target 中被设置,同时被设置的还有 zone,它表明:

凡是属于 zone $id 的数据包都用应用层固定偏移定义的固定长度的流标识来识别一个流,而不再使用传统的五元组来识别一个流。重新定义 tuple,同样增加一个 bool 型 l7,表示它是否是应用层的流标识,同时增加一个 MAX_IDLEN 长度的数组 sid,这意味着流标识别最长是 MAX_IDLEN 字节。

话说以上就是基本的数据定义,那么在代码逻辑上,修改也不难,主要是修改 resolve_normal_ct 函数,取出 tmpl 模板中的 l7,如果它非 0,那就表明需要“应用层流标识”来识别流,此时根据 offset,offlen 字段,定位到 [iphdr+iphdrlen+transphdrlen] 这个位置,取出 offlen 字节的数据,作为 hash 计算的 key 计算 hash 值,在__nf_conntrack_find_get 之前,tuple 被填充成了应用层的 sid,同时置位 tuple 的 l7,这意味着在 find conntrack 的时候,比较的是 tuple 的 sid 值而不是五元组。最后,在 conn confirm 的时候,将 conntrack 按照其 offset,offlen 定位的 payload 信息表示的 sid(它已经被放进了 tuple 结构中,由其 char sid[MAX_IDLEN]; 字段来标识)来进行插入。

修改,编译,测试总共用了不到两个小时(买的 iMac 太 TMD 给力了!!)。随性,随玩,吃点东西,喝口茶,开始得瑟。这个人就是我啊!

开始思考所作所为的意义后,也是一个反省的过程!我发现,突然发现,所作的一切都没有意义。conntrack 结构体并没有保存什么用于应用层的信息,虽然我自己扩展了它,能让它保存很多东西,比如路由,socket,等,但是事实上还没有什么地方真的用到了这些,即这些都是自己没事玩玩的东西。conntrack 中保存的最重要的信息就是 NAT 信息,即 tuple 信息,这个 tuple 是基于传统 5 元组的,你想啊,如果我用基于 sessionID 的应用层信息来标识一个 tuple,那么 NAT 怎么办?如果客户端的 IP 地址发生变化,即使 sessionID 不变,NAT 还是要重新做,还是得不到任何益处。我的本意就是能省去由于 IP 地址,端口发生变化后的那一系列重新操作,但是最终还是没有省,因为改变的是 IP 和端口,需要重新修改或者修饰的依然是 IP 和端口这些信息。

如果上面的代码是写在了纸上,很显然,我会将其撕碎,然后扔进垃圾桶 …

2. 支持 Layer7 任意 payload 哈希计算的 reuseport 是强大的

Linux 最新的内核已经支持了 UDP 的 reuseport 选项,这个机制可以很好地为 UDP 的负载均衡服务,如果不了解可以 bing 一下。它之所以可以做负载均衡,就是它通过一个固定的 5 元组来计算一个固定 hash,然后基于这个固定 hash 将一个数据包分发到固定的 socket,如果 IP 地址不发生变化,一切都会很好,但是 IP 地址在移动环境下会发生变化,这就意味着 5 元组信息发生了变化,那么重新计算的 hash 将也会发生变化(不发生变化那是碰撞了!),这就意味着这个变化了 IP 的客户端发出的下一个 UDP 数据包将可能被分发给别的 socket,这在基于 UDP 的长连接服务中是不希望发生的。以下是__udp4_lib_lookup 核心代码:

begin: 
    result = NULL; 
    badness = -1; 
    sk_nulls_for_each_rcu(sk, node, &hslot->head) {
    // sessionID 版本的 hash 计算,服务端不要鉴别 sport/saddr 为妙!
        score = compute_score(sk, net, saddr, hnum, sport, 
                      daddr, dport, dif); 
        if (score > badness) {
            result = sk; 
            badness = score; 
            reuseport = sk->sk_reuseport; 
            if (reuseport) {
                // 5 元组流版本,根据 4 元组计算一个 hash 值 
                //hash = inet_ehashfn(net, daddr, hnum, saddr, htons(sport)); 
        // sid 流版本,基于 sessionID 计算 hash。
        // 问题是这个 sid 怎么传到这里 … 大修吧 
        hash = sid_based_hash(sid,); 
                matches = 1; 
            }   
        } else if (score == badness && reuseport) {
            matches++; 
            // 是否由该 sk 替换上次匹配到的 sk,就看 hash 值的影响了 
            if (((u64)hash * matches) >> 32 == 0) {
                result = sk; 
            }   
 
            hash = hash * 1664525 + 1013904223; 
        }   
    }   
    /*   
    * if the nulls value we got at the end of this lookup is 
    * not the expected one, we must restart lookup. 
    * We probably met an item that was moved to another chain. 
    */ 
    if (get_nulls_value(node) != slot) 
        goto begin; 

注释中提到了大修,意思是,我必须将一个 skb 传到这里,才能根据 setsockopt 的参数 reuseport 标志,sid 的 offset,sid 的 offlen 来获取 sid,然后计算 hash,但这个修理很容易,重新编译一下内核即可。

在 UDP 的 reuseport 中采用 sessionID 识别一个流是很爽的一件事,因为此时数据已经到传输层了,除却重新封装的数据包,基本都是达到本机某个 UDP 服务的,数据包已经到达此地,说明 5 元组相关的鉴别比如 NAT 之类的已经完全通过,下一步就是往应用层送数据了,此时根据应用层的 sid 来识别一个流,就能确保即便是客户端 IP 改变了,它发出的请求也能到达同一个 UDP 服务线程 … 这也为移动时代提供了一个好的实景,在五元组频繁更换的年代,如何保持应用层不断开 …

正文完
星哥玩云-微信公众号
post-qrcode
 0
星锅
版权声明:本站原创文章,由 星锅 于2022-01-20发表,共计2885字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
【腾讯云】推广者专属福利,新客户无门槛领取总价值高达2860元代金券,每种代金券限量500张,先到先得。
阿里云-最新活动爆款每日限量供应
评论(没有评论)
验证码
【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中