<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Linux 內核中 reuseport 的演進

          共 8568字,需瀏覽 18分鐘

           ·

          2021-01-19 07:38


          轉自:187J3X1

          鏈接:https://reurl.cc/4y5LRY

          SO_REUSEPORT?選項在Linux 3.9被引入內核,在這之前也有一個很像的選項SO_REUSEADDR

          如果你不太清楚這兩者的區(qū)別和聯系,建議搜索 How do SO_REUSEADDR and SO_REUSEPORT differ?。

          如果不想讀,那么下面這一節(jié)算是為懶人準備的。

          SO_REUSEADDR 與 SO_REUSEPORT 是什么?

          TCP/UDP用五元組唯一標識一個連接。

          任何時候,兩條連接的五元組都不能完全相同,否則當收到一個報文時,協議棧沒辦法判斷它是屬于哪個連接的。

          五元組{, , , , }

          五元組里,protocol在創(chuàng)建socket時確定,bind()時確定,connect()時確定。

          當然,bind()connect()在一些時候并不需要顯式使用,不過這不在本文的討論范圍里。

          那么,如果對socket設置了SO_REUSEADDRSO_REUSEPORT選項,它們什么時候起作用呢?

          答案是bind(),也就在確定時。

          不同操作系統(tǒng)內核對待SO_REUSEADDRSO_REUSEPORT的行為有少許差異,但它們都源自BSD

          因此,接下來就以BSD的實現為標準進行說明。

          SO_REUSEADDR

          假設我現在需要bind()socketA綁定到A:X,將socketB綁定到B:Y(不考慮X=0或者Y=0,因為0表示讓內核自動分配端口,一定不會沖突)。

          如果X!=Y,那么無論AB的關系如何,兩個bind()都會成功。但如果X==Y,那么結果會是下面這樣:

          SO_REUSEADDR       socketA        socketB       Result
          ---------------------------------------------------------------------
          ON/OFF 192.168.0.1:21 192.168.0.1:21 Error (EADDRINUSE)
          ON/OFF 192.168.0.1:21 10.0.0.1:21 OK
          ON/OFF 10.0.0.1:21 192.168.0.1:21 OK
          OFF 0.0.0.0:21 192.168.1.0:21 Error (EADDRINUSE)
          OFF 192.168.1.0:21 0.0.0.0:21 Error (EADDRINUSE)
          ON 0.0.0.0:21 192.168.1.0:21 OK
          ON 192.168.1.0:21 0.0.0.0:21 OK
          ON/OFF 0.0.0.0:21 0.0.0.0:21 Error (EADDRINUSE)

          第一列表示是否設置SO_REUSEADDR,最后一列表示綁定的socket是否能綁定成功。

          :這里設置的對象是指綁定的socket(也就是說不關心前一個是否設置)

          可以看出,BSD的實現中SO_REUSEADDR可以讓一個使用通配地址(0.0.0.0),一個使用指定地址(192.168.1.0)的socket同時綁定成功

          SO_REUSEADDR還有一種應用情景:在TCP中存在一個TIME_WAIT狀態(tài),它是指主動關閉的一端最后停留的階段。

          假設socketA綁定到A:X,在完成TCP通信后主動使用close(),進入TIME_WAIT,此時,如果socketB也去綁定A:X,那么同樣會得到?EADDRINUSE?錯誤,但如果socketB設置了SO_REUSEADDR,那么就可以綁定成功。

          SO_REUSEPORT

          如果理解了SO_REUSEADDR,那么SO_REUSEPORT就很好理解了,它讓兩個socket可以綁定完全相同的

          SO_REUSEPORT       socketA        socketB       Result
          ---------------------------------------------------------------------
          ON 192.168.0.1:21 192.168.0.1:21 OK

          提醒一下,以上的結果都是BSD的結果,Linux內核有一些不一樣的地方,具體表現為

          • 3.9版本支持SO_REUSEPORT,作為Server的TCP Socket一旦綁定到了具體的端口,啟動了LISTEN,即使它之前設置過SO_REUSEADDR, 也不會生效。這一點Linux比BSD更加嚴格

          SO_REUSEADDR       socketA        socketB       Result
          ---------------------------------------------------------------------
          ON/OFF 192.168.0.1:21 0.0.0.0:21 Error (EADDRINUSE)
          • 3.9版本之前,作為Client的Socket,SO_REUSEADDR選項具有BSD中的SO_REUSEPORT的效果。這一點Linux又比BSD更加寬松。

          SO_REUSEADDR      socketA            socketB           Result
          ---------------------------------------------------------------------
          ON 192.168.0.2:55555 192.168.0.2:55555 OK


          Linux中reuseport的演進


          Linux < 3.9


          下面看看具體是怎么做的:? ?內核socket使用skc_reuse字段表示是否設置了SO_REUSEADDR


           struct sock_common {
          /* omitted */
          unsigned char skc_reuse;
          /* omitted */
          }

          int sock_setsockopt(struct socket *sock, int level, int optname,...
          {
          ......
          case SO_REUSEADDR:
          sk->sk_reuse = (valbool ? SK_CAN_REUSE : SK_NO_REUSE);
          break;
          }

          inet_bind_bucket表示一個綁定的端口。

          struct inet_bind_bucket {
          /* omitted */
          unsigned short port;
          signed short fastreuse;
          int num_owners;
          struct hlist_node node;
          struct hlist_head owners;
          };

          上面結構中的fastreuse表示該端口是否支持共享,所有共享該端口的socket掛到owner成員上。在用戶使用bind()時,內核使用TCP:inet_csk_get_port(),UDP:udp_v4_get_port()來綁定端口。

          /* inet_connection_Sock.c: inet_csk_get_port() */
          tb_found:
          if (!hlist_empty(&tb->owners)) {
          ......
          if (tb->fastreuse > 0 &&
          sk->sk_reuse && sk->sk_state != TCP_LISTEN &&
          smallest_size == -1) {
          goto success;

          所以,當該端口支持共享,且socket也設置了SO_REUSEADDR并且不為LISTEN狀態(tài)時,此次bind()可以成功。

          3.9 =< Linux < 4.5

          3.9版本內核增加了對SO_REUSEPORT的支持,listener可以綁定到相同的了。

          這個時候,當Server收到Client發(fā)送的SYN報文時,會選擇其中一個socket進行響應。

          具體到實現,3.9版本擴展了sock_common,將原來記錄skc_reuse進行了拆分.

          struct sock_common {
          unsigned short skc_family;
          volatile unsigned char skc_state;
          - unsigned char skc_reuse;
          + unsigned char skc_reuse:4;
          + unsigned char skc_reuseport:4;


          @@ int sock_setsockopt(struct socket *sock, int level, int optname,
          case SO_REUSEADDR:
          sk->sk_reuse = (valbool ? SK_CAN_REUSE : SK_NO_REUSE);
          break;
          + case SO_REUSEPORT:
          + sk->sk_reuseport = valbool;
          + break;

          然后對inet_bind_bucket也相應進行了擴展

          struct inet_bind_bucket {
          /* omitted */
          unsigned short port;
          - signed short fastreuse;
          + signed char fastreuse;
          + signed char fastreuseport;
          + kuid_t fastuid;

          而在綁定端口時,增加了一個隊reuseport的通過條件

          /* inet_connection_sock.c: inet_csk_get_port() */
          tb_found:
          if (sk->sk_reuse == SK_FORCE_REUSE)
          goto success;
          - if (tb->fastreuse > 0 &&
          - sk->sk_reuse && sk->sk_state != TCP_LISTEN &&
          + if (((tb->fastreuse > 0 &&
          + sk->sk_reuse && sk->sk_state != TCP_LISTEN) ||
          + (tb->fastreuseport > 0 &&
          + sk->sk_reuseport && uid_eq(tb->fastuid, uid)))
          && smallest_size == -1) {
          goto success;

          而當Client的SYN報文到達時,Server會首先根據本地端口(SYN報文的)計算出一條hash沖突鏈,然后遍歷該鏈表上的所有Socket,根據四元組匹配程度進行打分;

          如果使能了reuseport,那么可能有多個Socket都將拿到最高分,此時內核將隨機選擇一個進行后續(xù)處理。

          /* inet_hashtables.c  */
          struct sock *__inet_lookup_listener(struct......)
          {
          struct sock *sk, *result;
          unsigned int hash = inet_lhashfn(net, hnum);
          struct inet_listen_hashbucket *ilb = &hashinfo->listening_hash[hash]; // 根據本地端口找到hash沖突鏈
          /* code omitted */
          result = NULL;
          hiscore = 0;
          sk_nulls_for_each_rcu(sk, node, &ilb->head) {
          score = compute_score(sk, net, hnum, daddr, dif); // 根據匹配程度進行打分
          if (score > hiscore) {
          result = sk;
          hiscore = score;
          reuseport = sk->sk_reuseport;
          if (reuseport) {
          phash = inet_ehashfn(net, daddr, hnum,
          saddr, sport);
          matches = 1; // 如果是reuseport 則累計多少個socket滿足
          }
          } else if (score == hiscore && reuseport) {
          matches++;
          if (reciprocal_scale(phash, matches) == 0)
          result = sk;
          phash = next_pseudo_random32(phash);
          }
          }
          /*
          * 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.
          */

          return result;
          }

          舉個栗子,假設內核有4條listening socket的hash沖突鏈,然后用戶建立了4個Server:A、B、C、D,監(jiān)聽的地址和端口如下圖所示,A和B使能了SO_REUSEPORT

          沖突鏈是以端口為Key的,因此A、B、D會掛到同一條沖突鏈上。

          如果此時收到對端一個SYN報文<192.168.10.1, 21>,那么內核會遍歷listening_hash[0],為上面的7個socket進行打分,而由于B監(jiān)聽的是精確的地址,所以B的得分會比A高,內核最終選擇出一個SocketB進行后續(xù)處理。

          4.5 < Linux

          從上面的例子可以看出,當收到SYN報文時,內核一定會遍歷一條完整hash沖突鏈,為每一個socket進行打分,這稍微有些多余。

          因此,在4.5版本中,內核引入了reuseport groups,它將綁定到同一個IP和Port,并且設置了SO_REUSEPORT選項的socket組織到一個group內部。

          --- a/include/net/sock.h
          +++ b/include/net/sock.h
          @@ -318,6 +318,7 @@ struct cg_proto;
          * @sk_error_report: callback to indicate errors (e.g. %MSG_ERRQUEUE)
          * @sk_backlog_rcv: callback to process the backlog
          * @sk_destruct: called at sock freeing time, i.e. when all refcnt == 0
          + * @sk_reuseport_cb: reuseport group container
          */
          struct sock {
          /*
          @@ -453,6 +454,7 @@ struct sock {
          int (*sk_backlog_rcv)(struct sock *sk,
          struct sk_buff *skb);
          void (*sk_destruct)(struct sock *sk);
          + struct sock_reuseport __rcu *sk_reuseport_cb;
          };

          這個特性在4.5版本只支持UDP,而在4.6版本開始支持TCP(patch)。

          這樣在查找listen socket時,內核將不用再遍歷整個沖突鏈,而是在找到一個合格的socket時,如果它設置了SO_REUSEPORT,就直接找到它所屬的reuseport group,從中選擇一個進行后續(xù)處理。

          @@ -215,6 +217,7 @@ struct sock *__inet_lookup_listener(struct net *net,
          unsigned int hash = inet_lhashfn(net, hnum);
          struct inet_listen_hashbucket *ilb = &hashinfo->listening_hash[hash];
          int score, hiscore, matches = 0, reuseport = 0;
          + bool select_ok = true;
          u32 phash = 0;

          rcu_read_lock();
          @@ -230,6 +233,15 @@ begin:
          if (reuseport) {
          phash = inet_ehashfn(net, daddr, hnum,
          saddr, sport);
          + if (select_ok) {
          + struct sock *sk2;
          + sk2 = reuseport_select_sock(sk, phash,
          + skb, doff);
          + if (sk2) {
          + result = sk2;
          + goto found;
          + }
          + }
          matches = 1;
          }
          }


          良許個人微信


          添加良許個人微信即送3套程序員必讀資料


          → 精選技術資料共享

          → 高手如云交流社群





          本公眾號全部博文已整理成一個目錄,請在公眾號里回復「m」獲取!

          推薦閱讀:

          紅旗 Linux 桌面操作系統(tǒng)11來了:支持國產自主CPU,全新UI風格設計,兼容面廣

          2020互聯網大廠職級對應薪資一覽表。

          一份兩百億閱讀的 Git 教程!


          5T技術資源大放送!包括但不限于:C/C++,Linux,Python,Java,PHP,人工智能,單片機,樹莓派,等等。在公眾號內回復「1024」,即可免費獲取!!


          瀏覽 53
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  香蕉伊人在线观看 | 免费黄色在线看 | 国产婷婷色一区二区在线观看 | 国产日韩视频在线 | 狠狠撸狠狠操 |