Linux流負載均衡中Layer7的數據流(鏈接跟蹤)識別問題

1.支持Layer7的nf_conntrack真的沒有必要作

走火入魔以後,你會以爲須要趕忙將「基於五元組的數據流」改爲「基於應用層協議固定偏移的數據流」,趕忙動手,越快越好!因而此人在支持zone conntrack的Linux 3.17內核上爲nf_conn增長了幾個字段:html

bool l7; //布爾型,表示是否要進行layer7的匹配。node

u32 offset; //應用層流標識的偏移linux

以上的三個字段在CT target中被設置,同時被設置的還有zone,它代表:數組

凡是屬於zone $id的數據包都用應用層固定偏移定義的固定長度的流標識來識別一個流,而再也不使用傳統的五元組來識別一個流。從新定義tuple,一樣增長一個bool 型l7,表示它是不是應用層的流標識,同時增長一個MAX_IDLEN長度的數組sid,這意味着流標識別最長是MAX_IDLEN字節。session

話說以上就是基本的數據定義,那麼在代碼邏輯上,修改也不難,主要是修改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給力了!!)。隨性,隨玩,吃點東西,喝口茶,開始得瑟。這我的就是我啊!socket

開始思考所做所爲的意義後,也是一個檢討的過程!我發現,忽然發現,所做的一切都沒有意義。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核心代碼:this

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服務線程...這也爲移動時代提供了一個好的實景,在五元組頻繁 更換的年代,如何保持應用層不斷開...

相關文章
相關標籤/搜索