• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            隨筆-159  評論-223  文章-30  trackbacks-0
             
            描述
               TCP連接跟蹤是網絡流控和防火墻中的一項重要的基礎技術,當運用于主機時,連接必與進程相關聯,要么是主動發出的,要么是被動接受的,當后代進程被動態創建時,由于文件描述符的繼承,一個連接就會被這個進程樹中的所有進程共享;當一個進程發出或接受多個連接時,就擁有了多個連接。本方法可用于網絡安全產品中,監控TCP連接及所屬進程,能準確并動態地知道一個連接被哪些進程共享,一個進程擁有哪些連接。

            特點

               操作系統自帶的netstat工具只是關聯到了一個根進程,無法看到擁有該連接的所有進程,查看進程擁有的全部連接也不方便。該方法的特點是實時跟蹤、查看連接與進程相關信息方便、支持連接的管控。


            實現

               本方法通過內核安全的十字鏈表實現了連接與進程的相關性,連接信息結構體含有一個所屬進程鏈表頭,進程信息結構體含有一個擁有連接鏈表頭,通過十字鏈表結點鏈接,x方向鏈接到進程的連接鏈表,y方向鏈接到連接的進程鏈表,如下圖所示
               進程1為根進程,進程2,...,進程n為進程1的后代進程;連接1,連接2,...,連接n為進程1產生的連接。node(x,y)為十字鏈表結點,用于關聯連接與進程,x對應進程編號,y對應連接編號,每個node包含了所屬的連接和進程指針,每行和每列都是一個雙向循環鏈表(循環未畫出),每個鏈表用一個自旋鎖同步操作。
               動態跟蹤的過程包括4個方面:進程創建、進程退出、連接產生、連接銷毀。在Linux下,可通過攔截內核函數do_fork掛鉤進程創建,攔截do_exit掛鉤進程退出;可通過攔截inet_stream_ops的成員函數connect和accept掛鉤連接產生,攔截成員函數release掛鉤連接銷毀。下面為4個方面對應的流程圖,由于所有外層加鎖前已禁止本地中斷和內核搶占,因此內層加鎖前就不必再禁止本地中斷和內核搶占了。

               進程創建
               將copy_node插入到c的進程鏈表末尾,即為y方向增加(下同);插入到p的連接鏈表末尾,為x方向增加(下同)。

               進程退出
               從c的進程鏈表中移除node,即為y方向移除(下同);再從p的連接鏈表中移除node,即為x方向移除(下同)。


               連接產生
                  當進程發出連接或接受連接時,調用此流程。

               連接銷毀
                  當某個進程銷毀連接時,調用此流程。
            posted @ 2016-07-13 11:24 春秋十二月 閱讀(1530) | 評論 (0)編輯 收藏
            描述
               在P2P應用系統中,當需要與處于不同私網的對方可靠通信時,先嘗試TCP打洞穿透,若穿透失敗,再通過處于公網上的代理服務器(下文簡稱proxy)轉發與對方通信,如下圖所示  
               終端A先連接并發消息msg1到proxy(連接為c1);proxy再發消息msg2給P2P服務器,P2P服務器收到消息后通過已有的連接發消息msg3給終端B,B收到msg3后,連接并發消息msg4到proxy(連接為c2)。這4個消息格式由應用層協議決定,但必須遵守的規范如下:
                msg1至少包含B的設備ID。
                msg2至少包含B的設備ID和c1的連接ID。
                msg3至少包含c1的連接ID和連接代理指示。
                msg4至少包含B的設備ID和c1的連接ID。
               proxy為多線程架構,當接受到若干連接時,如果數據相互轉發的兩個連接(比如上圖中的c1和c2)不在同一線程,由于一個連接寫數據到另一連接的發送隊列,而另一連接從發送隊列讀數據以發送,那么就要對另一連接的發送隊列(或緩沖區)加鎖,這樣一來由于頻繁的網絡IO而頻繁地加解鎖,降低了轉發效率,因此為了解決這一問題,就需要調度TCP連接到同一線程。


            特點
               本方法能將數據轉發的兩邊連接放在同一線程,從而避免了數據轉發時的加鎖,由于是一對一的連接匹配,因此也做到了每個線程中連接數的均衡。


            實現
               工作原理
                  proxy的主線程負責綁定知名端口并監聽連接,工作線程負責數據轉發。當接受到一個連接時,按輪轉法調度它到某個工作線程,在那個線程內接收分析應用層協議數據,以識別連接類型,即判斷連接是來自數據請求方還是數據響應方,為統一描述,這里把前者特稱為客戶連接,后者為服務連接,連接所在的線程為宿主線程。如果是客戶連接,那么先通知P2P服務器請求對應的客戶端來連接代理,并等待匹配;如果是服務連接,那么就去匹配客戶連接,在匹配過程中,如果服務連接和客戶連接不在同一線程內,那么就會調度到客戶連接的宿主線程,匹配成功后,就開始轉發數據。
                  為了加快查找,分2個hash表存放連接,客戶連接放在客戶連接hash表中,以連接ID為鍵值;服務連接放在服務連接hash表中,以設備ID為鍵值。

               TCP連接調度
                  包括新連接的輪轉、識別連接類型、匹配客戶連接1、匹配客戶連接2和關閉連接共5個流程,如下一一所述。
                  新連接的輪轉
                     該流程工作在主線程,如下圖所示
                  索引i初始為0,對于新來的連接,由于還不明確連接類型,所以先放入客戶連接表中。

                  識別連接類型
                     該流程工作在工作線程,當接受到一個連接時開始執行,如下圖所示
                  當連接類型為服務連接時,從客戶連接表轉移到服務連接表。

                  匹配客戶連接1
                     該流程工作在工作線程,當接受到一個服務連接時開始執行,如下圖所示

                  匹配客戶連接2
                     該流程工作在工作線程,當服務連接移到客戶連接的宿主線程時開始執行,如下圖所示
                  這里的流程和匹配客戶連接流程1有些類似,看起來好像做了重復的判斷操作,但這是必要的,因為在服務連接轉移到另一線程這個瞬間內,客戶連接有可能斷開了,也有可能斷開后又來了一個相同連接ID的其它客戶連接,所以要重新去客戶連接表查找一次,然后進行4個分支判斷。 
               
                  關閉連接
                     該流程工作在工作線程內,當連接斷開或空閑時執行。當一邊讀數據出錯時,不能馬上關閉另一邊連接,得在另一邊緩沖區數據發送完后才能關閉;當一邊連接寫數據出錯時,可以馬上關閉另一邊連接,如下圖所示
            posted @ 2016-07-12 16:59 春秋十二月 閱讀(1807) | 評論 (0)編輯 收藏
                 摘要:    為了使nginx支持windows服務,本文闡述以下主要的改進實現。ngx_main函數   為了在SCM服務中復用main函數的邏輯,將其重命名為ngx_main,并添加第3個參數is_scm以兼容控制臺運行方式,聲明在core/nginx.h中。    Code highligh...  閱讀全文
            posted @ 2016-07-12 15:31 春秋十二月 閱讀(4507) | 評論 (0)編輯 收藏
            結構定義
             1 struct state_machine {
             2     int state;
             3     
             4 };
             5 
             6 enum { 
             7     s1,
             8     s2,
             9     
            10     sn
            11 };
               假設s1為初始狀態,狀態變遷為s1->s2->...->sn。

            常規實現 
               狀態機處理函數state_machine_handle通常在一個循環內或被事件驅動框架調用,輸入data會隨時序變化,從而引起狀態的變遷,偽代碼框架如下。
             1void handle_s1(struct state_machine *sm, void *data)
             2{
             3    //do something about state 1
             4    if(is_satisfy_s2(data))
             5        sm->state = s2;
             6}

             7
             8void handle_s2(struct state_machine *sm, void *data)
             9{
            10    //do something about state 2
            11    if(is_satisfy_s3(data))
            12        sm->state = s3;
            13}

            14
            15void handle_sn_1(struct state_machine *sm, void *data)
            16{
            17    //do something about state n-1
            18    if(is_satisfy_sn(data))
            19        sm->state = sn;
            20}

            21
            22void state_machine_handle(struct state_machine *sm, void *data)
            23{
            24    switch(sm->state){
            25        case s1:
            26            handle_s1(sm,data);
            27            break;
            28            
            29        case s2:
            30            handle_s2(sm,data);
            31            break;            
            32            
            33        case sn:
            34            handle_sn(sm,data);
            35            break;
            36    }

            37}
               sm->state初始化為s1。

            改進實現
               為了免去丑陋的switch case分支結構,在state_machine內用成員函數指針handler替代了state,改進后的框架如下。
             1struct state_machine;
             2typedef void (*state_handler)(struct state_machine*void*);
             3
             4struct state_machine {
             5    state_handler handler;
             6    
             7}
            ;
             8
             9void handle_s1(struct state_machine *sm, void *data)
            10{
            11    //do something about state 1
            12    if(is_satisfy_s2(data))
            13        sm->handler = handle_s2;
            14}

            15
            16void handle_s2(struct state_machine *sm, void *data)
            17{
            18    //do something about state 2
            19    if(is_satisfy_s3(data))
            20        sm->handler = handle_s3;
            21}

            22
            23void handle_sn_1(struct state_machine *sm, void *data)
            24{
            25    //do something about state n-1
            26    if(is_satisfy_sn(data))
            27        sm->handler = handle_sn;
            28}

            29
            30void state_machine_handle(struct state_machine *sm, void *data)
            31{
            32    sm->handler(sm, data);
            33}
               sm->handler初始化為handle_s1,該方法在性能上應略優于常規方法,而且邏輯更清晰自然,非常適合于網絡流的處理,在nginx中分析http和email協議時,得到了廣泛應用。
            posted @ 2016-05-05 09:46 春秋十二月 閱讀(4059) | 評論 (1)編輯 收藏
            腳本概述
               由于使用objdump反匯編linux內核的輸出太多(2.6.32-220.el6.x86_64統計結果為1457706行),而很多時候只是想查看特定部分的機器碼與匯編指令,例如函數的入口、堆棧、調用了哪個函數等,為了高效和通用,因此編寫了一個簡單的awk腳本,其命令行參數說明如下:
               ● SLINE表示匹配的起始行號(不小于1),SPAT表示匹配的起始行模式,這兩個只能有一個生效,當都有效時,以SLINE為準。
               ● NUM表示從起始行開始的連續輸出行數(不小于1,含起始行),EPAT表示匹配的結束行模式,這兩個只能有一個生效,當都有效時,以NUM為準。

            腳本實現
               檢查傳值
               由于向腳本傳入的值在BEGIN塊內沒生效,在動作塊{}和END塊內有效,但若在{}內進行檢查則太低效,因為處理每條記錄都要判斷,所以為了避免在{}內進行多余的判斷,就在BEGIN塊內解析命令行參數來間接獲得傳值,當傳值無效時,給出提示并退出。
             1for(k=1;k<ARGC;++k){
             2        str=ARGV[k]
             3        if(1==match(str,"SLINE=")){
             4            SLINE = substr(str,7)
             5        }else if(1==match(str,"SPAT=")){
             6            SPAT = substr(str,6)
             7        }else if(1==match(str,"NUM=")){
             8            NUM = substr(str,5)
             9        }else if(1==match(str,"EPAT=")){
            10            EPAT = substr(str,6)
            11        }
            12    }
            13
            14  if(SLINE<=0 && SPAT==""){
            15      print "Usage: rangeshow must specifies valid SLINE which must be greater than 0, or SPAT which can't be empty"
            16      exit 1    
            17  }
            18
            19  if(NUM<=0 && EPAT==""){
            20      print "Usage: rangeshow must specifies valid NUM which must be greater than 0, or EPAT which can't be empty"
            21    exit 1
            22}

               結束處理
               當處理了NUM條記錄或匹配了結束行模式時,應退出動作塊{}。   
             1if(0==start_nr){ 
             2      
             3}else{
             4    if(NUM>0) {
             5        if(NR<start_nr+NUM) {
             6            ++matched_nr
             7            print $0            
             8        }else
             9            exit 0
            10
            11    }else{
            12        ++matched_nr
            13        print $0        
            14        if(0!=match($0,EPAT))
            15            exit 0
            16    }
            17}

               完整腳本下載:rangeshow

            腳本示例
               查看linux內核第10000行開始的10條指令,如下圖
               
               
               查看linux內核函數do_fork入口開始的10條指令,如下圖    
               

               查看linux內核第10000行開始到callq的一段指令,如下圖
               

               查看linux內核函數do_exit入口到調用profile_task_exit的一段指令,如下圖   
               
            posted @ 2015-10-27 15:36 春秋十二月 閱讀(1784) | 評論 (1)編輯 收藏
               本文根據RFC793協議規范和BSD 4.4的實現,總結了TCP分組丟失時的狀態變遷,如下圖所示:實線箭頭表示客戶端的狀態變遷,線段虛線箭頭表示服務端的狀態變遷,圓點虛線箭頭表示客戶端或服務端的狀態變遷;黑色文字表示正常時的行為,紅色文字表示分組丟失時的行為。

               這里假設重傳時分組依然會丟失,當在不同狀態(CLOSED除外)分組丟失后,最終會關閉套接字而回到CLOSED狀態。下面逐個分析各狀態時的情景。

            SYN_SENT
               連接階段第1次握手,客戶端發送的SYN分組丟失,因此超時收不到服務端的SYN+ACK而重傳SYN,嘗試幾次后放棄,關閉套接字。

            SYN_RCVD
               1)連接階段第2次握手,服務端響應的SYN+ACK分組丟失,因此超時收不到客戶端的ACK而重傳SYN+ACK,嘗試幾次后放棄,發送RST并關閉套接字。
               2)連接階段第3次握手,客戶端發送的ACK分組丟失,因此服務端超時收不到ACK而重傳SYN+ACK,嘗試幾次后放棄,發送RST并關閉套接字。
               3)同時打開第2次握手,本端響應的SYN+ACK分組丟失,因此對端超時收不到SYN+ACK而重傳SYN、嘗試幾次后放棄、發送RST并關閉套接字,而此時本端收到RST。

            ESTABLISHED
               1)連接階段第3次握手,客戶端發送ACK分組后,雖然丟失但會進入該狀態(因為ACK不需要確認),但此時服務端還處于SYN_RCVD狀態,因為超時收不到客戶端的ACK而重傳SYN+ACK、嘗試幾次后放棄、發送RST并關閉套接字,而此時客戶端收到RST。
               2)數據傳輸階段,本端發送的Data分組丟失,因此超時收不到對數據的確認而重傳、嘗試幾次后放棄、發送RST并關閉套接字,而此時對端收到RST。

            FIN_WAIT_1
               1)關閉階段第1次握手,客戶端發送的FIN分組丟失,因此超時收不到服務端的ACK而重傳FIN,嘗試幾次后放棄,發送RST并關閉套接字。
               2)關閉階段第2次握手,服務端響應的ACK分組丟失,因此客戶端超時收不到ACK而重傳FIN,嘗試幾次后放棄,發送RST并關閉套接字。
             
            FIN_WAIT_2
               關閉階段第3次握手,服務端發送的FIN分組丟失,因此超時收不到客戶端的ACK而重傳FIN、嘗試幾次后放棄、發送RST并關閉套接字,而此時客戶端收到RST。
             
            CLOSING
               同時關閉第2次握手,本端發送的ACK分組丟失,導致對端超時收不到ACK而重傳FIN、嘗試幾次后放棄、發送RST并關閉套接字,而此時本端收到RST。

            TIME_WAIT
               關閉階段第4次握手,客戶端響應的ACK分組丟失,導致服務端超時收不到ACK而重傳FIN、嘗試幾次后放棄、發送RST并關閉套接字,而此時客戶端收到RST。

            CLOSE_WAIT
               關閉階段第2次握手,服務端響應的ACK分組丟失,導致客戶端超時收不到ACK而重傳FIN、嘗試幾次后放棄、發送RST并關閉套接字,而此時服務端收到RST。

            LAST_ACK
               關閉階段第3次握手,服務端發送的FIN分組丟失,導致超時收不到客戶端的ACK而重傳FIN、嘗試幾次后放棄、發送RST并關閉套接字。
            posted @ 2015-10-05 00:44 春秋十二月 閱讀(3302) | 評論 (1)編輯 收藏
                 摘要:    由于linux內核中的struct list_head已經定義了指向前驅的prev指針和指向后繼的next指針,并且提供了相關的鏈表操作方法,因此為方便復用,本文在它的基礎上封裝實現了一種使用開鏈法解決沖突的通用內核Hash表glib_htable,提供了初始化、增加、查找、刪除、清空和銷毀6種操作,除初始化和銷毀外,其它操作都做了同步,適用于中斷和進程上下文。...  閱讀全文
            posted @ 2015-09-15 17:18 春秋十二月 閱讀(2217) | 評論 (0)編輯 收藏
               nginx的域名解析器使用已連接udp(收發前先調用ngx_udp_connect)發送dns查詢、接收dns響應,如上篇tcp異步連接所講,iocp需要先投遞udp的接收操作,才能引發接收完成的事件,因此要對域名解析器和udp異步接收作些改進。

            發送后投遞
                dns查詢由ngx_resolver_send_query函數實現,定義在core/ngx_resolver.c中。
             1static ngx_int_t ngx_resolver_send_query(ngx_resolver_t *r, ngx_resolver_node_t *rn)
             2{
             3    
             4     if (rn->naddrs == (u_short) -1{
             5        n = ngx_send(uc->connection, rn->query, rn->qlen);
             6    
             7  }

             8
             9#if (NGX_HAVE_INET6)
            10  if (rn->query6 && rn->naddrs6 == (u_short) -1{
            11      n = ngx_send(uc->connection, rn->query6, rn->qlen);
            12    
            13  }

            14#endif
            15
            16#if (NGX_WIN32) 
            17    if (ngx_event_flags & NGX_USE_IOCP_EVENT){
            18        uc->connection->read->ready = 1;
            19        ngx_resolver_read_response(uc->connection->read);
            20}

            21#endif
            22
            23  return NGX_OK;
            24}
                當nginx用于代理連接上游服務器前,要先解析域名,首次調用鏈為:ngx_http_upstream_init_request->ngx_resolver_name->ngx_resolver_name_locked->ngx_resolver_send_query;若5s(單次超時)后還沒收到dns響應,則再發送1次查詢,調用鏈為:ngx_resolver_resend_handler->ngx_resolver_resend->ngx_resolver_send_query,如此反復,直到收到響應或30s(默認總超時)后不再發送查詢。它調用ngx_send發送dns查詢,16行~21行代碼為筆者添加,ngx_resolver_read_response函數用于接收并分析dns響應報文,它會調用到下面的ngx_udp_overlapped_wsarecv函數。

            異步接收
               由ngx_udp_overlapped_wsarecv函數實現,定義在os/win32/ngx_udp_wsarecv.c中。
             1ssize_t ngx_udp_overlapped_wsarecv(ngx_connection_t *c, u_char *buf, size_t size)
             2{
             3    int             flags, rc;
             4    WSABUF          wsabuf;
             5    ngx_err_t       err;
             6    ngx_event_t    *rev;
             7    WSAOVERLAPPED  *ovlp;
             8    u_long             bytes;
             9    
            10    rev = c->read;
            11    
            12    if (!rev->ready) {
            13      ngx_log_error(NGX_LOG_ALERT, c->log, 0"ngx_udp_overlapped_wsarecv second wsa post");
            14      return NGX_AGAIN;
            15  }

            16    
            17  if (rev->complete) {
            18   if (ngx_event_flags & NGX_USE_IOCP_EVENT) {
            19       if (rev->ovlp.error && rev->ovlp.error != ERROR_MORE_DATA{
            20           ngx_connection_error(c, rev->ovlp.error, "ngx_udp_overlapped_wsarecv() failed");
            21           return NGX_ERROR;
            22       }

            23   }

            24   
            25   rev->complete = 0;
            26  }

            27     
            28    ovlp = NULL;
            29    wsabuf.buf = (CHAR *) buf;
            30    wsabuf.len = (ULONG) size;
            31    flags = 0;
            32    
            33retry:    
            34    rc = WSARecv(c->fd, &wsabuf, 1, (DWORD*)&bytes, (LPDWORD)&flags, ovlp, NULL);
            35        
            36    if (rc == -1{
            37         rev->ready = 0;
            38         err = ngx_socket_errno;
            39    
            40       if (err == WSA_IO_PENDING) {
            41             return NGX_AGAIN;
            42         }

            43    
            44       if (err == WSAEWOULDBLOCK) {
            45           if (ngx_event_flags & NGX_USE_IOCP_EVENT) {                
            46                 rev->ovlp.type = NGX_IOCP_IO;
            47                 ovlp = (WSAOVERLAPPED *)&rev->ovlp;
            48                 ngx_memzero(ovlp, sizeof(WSAOVERLAPPED));
            49                
            50                 wsabuf.buf = NULL;
            51                 wsabuf.len = 0;
            52                 flags = MSG_PEEK;
            53                
            54                goto retry;            
            55            }

            56            
            57            return NGX_AGAIN;            
            58        }

            59            
            60        ngx_connection_error(c, err, "ngx_udp_overlapped_wsarecv() failed");    
            61        rev->error = 1;
            62    
            63        return NGX_ERROR;
            64    }

            65    
            66    if ((ngx_event_flags & NGX_USE_IOCP_EVENT) && ovlp) {
            67        rev->ready = 0;
            68        return NGX_AGAIN;
            69    }

            70    
            71    return bytes;
            72}
               先以非阻塞方式接收,若發生WSAWOULDBLOCK錯誤,則使用MSG_PEEK標志投遞一個0字節的重疊接收操作,當dns響應返回時發生完成事件,會再次進入ngx_resolver_read_response而調用到該函數,此時rev->complete為1,rev->ovlp.error為ERROR_MORE_DATA(GetQueuedCompletionStatus返回的錯誤),由于使用了MSG_PEEK,因此數據還在接收緩沖區中,要忽略ERROR_MORE_DATA而繼續接收,這時就能成功了。不管WSARecv返回WSA_IO_PENDING錯誤還是成功,iocp都會得到完成通知,所以這里當重疊操作投遞成功時,返回NGX_AGAIN,便于在回調內統一處理。
            posted @ 2015-06-25 17:01 春秋十二月 閱讀(6241) | 評論 (0)編輯 收藏
               iocp是Windows NT操作系統的一種高效IO模型,對應于Linux中的epoll和FreeBSD中的kqueue,nginx對ske(select、kqueue和epoll的首寫字母組合)的支持很好,但截止到1.6.2版本,還不支持iocp。由于ske都是反應器模式,即先注冊IO事件,當IO事件發生(讀寫通知)時,在其回調內主動調用API來讀或寫數據;而iocp是前攝器模式,要先投遞IO操作,才能引發IO事件(完成通知)的發生,在其回調內數據已被動由操作系統讀或寫完成。因此,iocp的特點決定了nginx對它的支持與ske有所不同。通過hg clone http://hg.nginx.org/nginx下載的nginx源代碼,雖然實現了iocp事件模塊、異步接受連接、部分異步讀寫,但根本不能正常工作,而且不支持異步連接和SCM服務控制,筆者在參考ske模塊的實現基礎上,改進支持了如下特性:
                  1. 異步接受連接時的負載均衡
                  2. 正反向代理的異步連接
                  3. 異步聚合讀寫
                  4. 域名解析時的UDP異步接收
                  5. 異步文件傳輸
                  6. SCM服務控制
               由于2、4、6均為原創,其它幾點的思路皆源于ske模塊的實現(只是平臺API不同),因此本文先闡述異步連接的實現。為了兼容select事件模塊,所有iocp相關的代碼使用NGX_HAVE_IOCP宏和(或)NGX_USE_IOCP_EVENT標志包圍,其中NGX_HAVE_IOCP宏用于條件編譯,在WIN32平臺下,定義為1;當選擇的事件模塊為iocp時,全局變量ngx_event_flags才包含NGX_USE_IOCP_EVENT標志。

            異步連接對端
               由ngx_event_connect_peer函數(這里省去了與異步連接無關的代碼)實現,定義在event/ngx_event_connect.c中,因為connect不支持異步連接事件的完成通知,所以要使用擴展API ConnectEx。 
             1ngx_int_t ngx_event_connect_peer(ngx_peer_connection_t *pc)
             2{
             3    int                rc;
             4    ngx_int_t          event;
             5    ngx_err_t          err;
             6    ngx_uint_t         level,family;
             7    ngx_socket_t       s;
             8    ngx_event_t       *rev, *wev;
             9    
            10    s = ngx_socket(family = pc->sockaddr->sa_family, SOCK_STREAM, 0);
            11              
            12    #if (NGX_HAVE_IOCP)
            13    if((pc->local==NULL||pc->local->sockaddr->sa_family != family) 
            14        && (ngx_event_flags & NGX_USE_IOCP_EVENT)){
            15        if(ngx_iocp_set_localaddr(pc->log,family,&pc->local) != NGX_OK)
            16            goto failed;    
            17    }

            18    #endif
            19       
            20    
            21    #if (NGX_HAVE_IOCP)
            22    if(ngx_event_flags&NGX_USE_IOCP_EVENT){        
            23        LPWSAOVERLAPPED   ovlp;
            24        ovlp = (LPWSAOVERLAPPED)&wev->ovlp;
            25        ngx_memzero(ovlp,sizeof(WSAOVERLAPPED));
            26        wev->ovlp.type = NGX_IOCP_CONNECT;
            27        rc = ngx_connectex(s,pc->sockaddr,pc->socklen,NULL,0,NULL,ovlp) ? 0 : -1;
            28    
            29    }
            else
            30        rc = connect(s, pc->sockaddr, pc->socklen);
            31    #else
            32      rc = connect(s, pc->sockaddr, pc->socklen);
            33    #endif
            34    
            35    if (rc == -1{
            36        err = ngx_socket_errno;
            37        if (err != NGX_EINPROGRESS
            38    #if (NGX_WIN32)
            39        /* Winsock returns WSAEWOULDBLOCK (NGX_EAGAIN) */
            40        && err != NGX_EAGAIN
            41    #if (NGX_HAVE_IOCP)
            42        && err != WSA_IO_PENDING
            43    #endif
            44    #endif
            45            ){
            46            
            47            ngx_log_error(level, c->log, err, "connect() to %V failed", pc->name);
            48            ngx_close_connection(c);
            49            pc->connection = NULL;
            50          
            51            return NGX_DECLINED;
            52        }

            53    }

            54    
            55}
               調用ConnectEx前要先bind本地地址,不然發生WSAEINVAL錯誤;由于域名解析可能返回IPv6記錄,導致創建本地套接字的地址族為AF_INET6,因此bind時需要匹配IPv6地址,不然發生WSAEFAULT錯誤,導致nginx返回Internal Server Error錯誤給前端,因此綁定前要調用ngx_iocp_set_localaddr設定正確的本地地址,當且僅當pc->local為空或地址族不匹配時。

            本地初始化與設定
               支持IPv6,實現在event/modules/ngx_iocp_module.c。
               地址變量定義如下。
            1static struct sockaddr_in  sin;
            2#if (NGX_HAVE_INET6)
            3static struct sockaddr_in6  sin6;
            4#endif
            5static ngx_addr_t           local_addr;
               sin對應IPv4,sin6對應IPv6,作為bind的套接字本地地址。

               sin和sin6在啟動iocp事件模塊時調用ngx_iocp_init初始化。   
             1static ngx_int_t ngx_iocp_init(ngx_cycle_t *cycle, ngx_msec_t timer)
             2{
             3    
             4  sin.sin_family = AF_INET;
             5  sin.sin_port = 0;
             6  sin.sin_addr.s_addr = INADDR_ANY;
             7    
             8#if (NGX_HAVE_INET6)
             9    sin6.sin6_family = AF_INET6;
            10    sin6.sin6_port = 0;
            11    sin6.sin6_addr = in6addr_any;
            12#endif
            13    
            14  local_addr.name.len = sizeof("INADDR_ANY"- 1;
            15  local_addr.name.data = (u_char *)"INADDR_ANY";    
            16    
            17}
               不論IP地址或端口,都指定為0,表示由系統自動分配出口IP地址和未占用的端口。

               本地設定由ngx_iocp_set_localaddr實現。
             1ngx_int_t ngx_iocp_set_localaddr(ngx_log_t *log, in_port_t family, ngx_addr_t **local)
             2{
             3    struct sockaddr *sa;
             4    socklen_t len;
             5    
             6    if(AF_INET == family){            
             7        sa = &sin;
             8        len = sizeof(struct sockaddr_in);        
             9    }

            10#if (NGX_HAVE_INET6)
            11    else if(AF_INET6 == family){
            12        sa = &sin6;
            13        len = sizeof(struct sockaddr_in6);
            14    }

            15#endif
            16    else{
            17        ngx_log_error(NGX_LOG_ALERT, log, 0"not supported address family");
            18        return NGX_ERROR;        
            19    }

            20
            21    local_addr.sockaddr = sa;
            22    local_addr.socklen = len;
            23    *local = &local_addr;
            24
            25    return NGX_OK;
            26}
               對于除IPv4和IPv6外的協議族,則記錄一個錯誤日志。必要時也可擴展支持其它的協議族,例如NetBIOS(對應地址族為AF_NETBIOS),但要看ConnectEx是否支持。
            posted @ 2015-06-24 17:02 春秋十二月 閱讀(7626) | 評論 (1)編輯 收藏
               ICMP在IP系統間傳遞差錯和管理報文,是任何IP系統必須實現的組成部分。Linux 2.6.34中ICMP模塊的實現在linux/icmp.h,net/icmp.h和ipv4/icmp.c中,導出了icmp_err_convert數組和icmp_send函數,供其它網絡子系統使用。在其它網絡子系統中,當檢測到錯誤時,調用icmp_send產生并發送相應的ICMP差錯消息到源主機;當源主機收到ICMP不可達差錯消息,傳遞到原始套接字和傳輸層,而它們使用icmp_err_convert把對應的消息代碼轉換成套接字層比較容易理解的錯誤代碼。在內核空間中可發送的ICMP消息包括查詢應答和差錯報文,下面總結了產生這兩類消息的網絡子系統(及函數)與錯誤轉換。


            應答消息
               應答消息由ICMP模塊的內部函數icmp_reply而非icmp_send發送。根據RFC1122 3.2.2.9規范, 除非一個主機作為地址掩碼代理,否則不能發送回復,這對應ICMP的icmp_address實現為空,因此上表沒有列出地址掩碼應答項(內核符號為ICMP_ADDRESSREPLY)。

            差錯消息
               差錯消息由中間路由器或目的主機產生,當數據報不能成功提交給目的主機時。從上表可見,在IP層的接收、本地處理、轉發和輸出各過程中,都可能產生差錯消息;在傳輸層如果對應的端口沒有打開,那么UDP會產生ICMP端口不可達差錯,而TCP則會使用自己的差錯處理機制發送一個RST復位包,這也是上表沒有列出TCP子系統的原因。對于重定向差錯,由ICMP模塊的icmp_redirect調用ip_rt_redirect更新路由;其它差錯則由icmp_unreach處理。


            錯誤轉換
               第2列為icmp_err_convert數組索引,第4列也就是調用socket API出錯時返回的errno,最后1列為icmp_err_convert中的fatal成員取值,0表示非致命錯誤,1表示致命錯誤,需要報告給用戶進程。錯誤轉換會被RAW的raw_err、TCP的tcp_v4_err和UDP的udp_err用到,對于ICMP_DEST_UNREACH類型的差錯,使用上表轉換;ICMP_SOURCE_QUENCH類型的忽略不處理;ICMP_PARAMETERPROB類型的轉換成EPROTO(協議錯誤);ICMP_TIME_EXCEEDED類型的轉換成EHOSTUNREACH
               在這要注意,從ICMP_PORT_UNREACH到ECONNREFUSED的轉換,不適用于TCP,原因已在上節說明;而對于UDP的未連接套接字,如果主機在線而端口沒打開,調用sendto得不到ECONNREFUSED錯誤,但recvfrom會阻塞,這是因為雖然內核收到了ICMP差錯,但沒上報給應用進程。盡管如此,如果想得到ECONNREFUSED錯誤,那么可以寫個ICMP守護進程,應用進程先把它的套接字描述符通過unix域套接口傳遞到ICMP守護進程,而守護進程使用raw socket來接收ICMP差錯,再發給應用進程。


            發送限速
               不論一般差錯消息還是重定向差錯消息,發送限速針對的都是特定目標主機。
               一般限速
               在使用icmp_send發送差錯消息(PMTU消息除外)時,為減少網絡擁塞而限制了發送的速率,限速由xrlim_allow函數實現,定義在ipv4/icmp.c中。
             1#define XRLIM_BURST_FACTOR 6
             2int xrlim_allow(struct dst_entry *dst, int timeout)
             3{
             4    unsigned long now, token = dst->rate_tokens;
             5    int rc = 0;
             6
             7    now = jiffies;
             8    token += now - dst->rate_last;
             9    dst->rate_last = now;
            10    if (token > XRLIM_BURST_FACTOR * timeout)
            11        token = XRLIM_BURST_FACTOR * timeout;
            12    if (token >= timeout) {
            13        token -= timeout;
            14        rc = 1;
            15    }

            16    dst->rate_tokens = token;
            17    return rc;
            18}
               dst為目標路由緩存,timeout為允許發送的超時(單位為jiffies),dst->rate_tokens記錄令牌的個數,當令牌個數不小于timeout時,則減少timeout并允許發送一個消息;反之則不能發送,需等到令牌個數累積到大于timeout時才能發送,但是不能無限大,否則就會導致在一個可能很短的timeout內,發送遠多于6個的消息,引起ICMP風暴,所以這里限制了令牌的最大值為XRLIM_BURST_FACTOR*timeout即6倍的超時,也就是說在一個timeout內,最多能發送6個差錯消息。 
              
               重定向限速
               路由子系統使用ip_rt_send_redirect來發送重定向消息,定義在ipv4/route.c中,該函數內部調用icmp_send實現,在它的限速基礎上,使用指數回退算法控制發送速率。
             1void ip_rt_send_redirect(struct sk_buff *skb)
             2{
             3    struct rtable *rt = skb_rtable(skb);
             4    
               
             5
             6    /* No redirected packets during ip_rt_redirect_silence;
             7     * reset the algorithm.
             8     */

             9    if (time_after(jiffies, rt->u.dst.rate_last + ip_rt_redirect_silence))
            10        rt->u.dst.rate_tokens = 0;
            11
            12    /* Too many ignored redirects; do not send anything
            13     * set u.dst.rate_last to the last seen redirected packet.
            14     */

            15    if (rt->u.dst.rate_tokens >= ip_rt_redirect_number{
            16        rt->u.dst.rate_last = jiffies;
            17        return;
            18    }

            19
            20    /* Check for load limit; set rate_last to the latest sent
            21     * redirect.
            22     */

            23    if (rt->u.dst.rate_tokens == 0 || time_after(jiffies, (rt->u.dst.rate_last (ip_rt_redirect_load << rt->u.dst.rate_tokens)))) {
            24        icmp_send(skb, ICMP_REDIRECT, ICMP_REDIR_HOST, rt->rt_gateway);
            25        rt->u.dst.rate_last = jiffies;
            26        ++rt->u.dst.rate_tokens;
            27        
            28    }

            29}
               重定向差錯使用ip_rt_redirect_silence(默認為(HZ/50)<<10)、ip_rt_redirect_number(默認為9)和ip_rt_redirect_load(默認為HZ/50)3個量來控制發送的速率;rt->u.dst.rate_last記錄上次發送的時間,rt->u.dst.rate_tokens累計發送總數,最大值為ip_rt_redirect_number;當兩次發送的時間間隔超過ip_rt_redirect_silence或ip_rt_redirect_load<<rt->u.dst.rate_tokens,并且發送總數不超過ip_rt_redirect_number時,才允許發送一個,這樣一來,在ip_rt_redirect_silence間隔內,每次發送的超時呈2的指數增長,達到了變減速發送的效果,直到總數達到ip_rt_redirect_number時停止發送,這是因為源主機可能忽略了重定向消息所以停止發送;當ip_rt_redirect_silence時間過后,又允許發送了,這是因為認為源主機沒有更新路由所以又需要發送。
            posted @ 2015-05-18 19:52 春秋十二月 閱讀(2796) | 評論 (0)編輯 收藏
            僅列出標題
            共16頁: First 5 6 7 8 9 10 11 12 13 Last 
            亚洲人成精品久久久久 | 亚洲国产二区三区久久| 久久天天躁狠狠躁夜夜avapp| 精品99久久aaa一级毛片| 国产亚洲欧美精品久久久 | 一本色综合网久久| 天天做夜夜做久久做狠狠| 国产精品狼人久久久久影院| 久久亚洲AV成人无码电影| 性欧美丰满熟妇XXXX性久久久| 中文字幕无码久久人妻| 伊人久久大香线蕉AV一区二区| 香蕉久久AⅤ一区二区三区| 久久笫一福利免费导航| 波多野结衣久久| 伊人久久大香线蕉av不卡| 麻豆AV一区二区三区久久| 久久久久亚洲av无码专区| 久久久久久久久无码精品亚洲日韩| 久久久久久午夜成人影院| 久久精品人人槡人妻人人玩AV | 久久久久亚洲精品日久生情| 国内精品人妻无码久久久影院导航| 欧美日韩精品久久久久| 亚洲国产精品18久久久久久| 久久国产免费观看精品3| 久久综合九色综合欧美狠狠| 久久93精品国产91久久综合| 一级做a爰片久久毛片看看| 中文字幕人妻色偷偷久久| 狠色狠色狠狠色综合久久| 久久国产精品视频| 久久久www免费人成精品| 久久99热只有频精品8| 国产无套内射久久久国产| 国内精品伊人久久久久妇| 久久综合狠狠综合久久综合88 | 性做久久久久久久久| 久久久久久国产精品免费无码| 国产福利电影一区二区三区,免费久久久久久久精 | 精品久久久无码中文字幕|