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


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服务线程...这也为移动时代提供了一个好的实景,在五元组频繁更换的年代,如何保持应用层不断开...

相关内容