2017-09-23 113 views
1

我在閱讀「Unix網絡編程」第3版。書籍「Unix網絡編程」中的拒絕服務攻擊問題

我遇到6.8節的一個問題「TCP回聲服務器(再訪)」,在座的代碼如下:

#include "unp.h" 

int 
main(int argc, char **argv) 
{ 
    int     i, maxi, maxfd, listenfd, connfd, sockfd; 
    int     nready, client[FD_SETSIZE]; 
    ssize_t    n; 
    fd_set    rset, allset; 
    char    buf[MAXLINE]; 
    socklen_t   clilen; 
    struct sockaddr_in cliaddr, servaddr; 

    listenfd = Socket(AF_INET, SOCK_STREAM, 0); 

    bzero(&servaddr, sizeof(servaddr)); 
    servaddr.sin_family  = AF_INET; 
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY); 
    servaddr.sin_port  = htons(SERV_PORT); 

    Bind(listenfd, (SA *) &servaddr, sizeof(servaddr)); 

    Listen(listenfd, LISTENQ); 

    maxfd = listenfd;   /* initialize */ 
    maxi = -1;     /* index into client[] array */ 
    for (i = 0; i < FD_SETSIZE; i++) 
     client[i] = -1;   /* -1 indicates available entry */ 
    FD_ZERO(&allset); 
    FD_SET(listenfd, &allset); 

    for (; ;) { 
     rset = allset;  /* structure assignment */ 
     nready = Select(maxfd+1, &rset, NULL, NULL, NULL); 

     if (FD_ISSET(listenfd, &rset)) { /* new client connection */ 
      clilen = sizeof(cliaddr); 
      connfd = Accept(listenfd, (SA *) &cliaddr, &clilen); 

      for (i = 0; i < FD_SETSIZE; i++) 
       if (client[i] < 0) { 
        client[i] = connfd; /* save descriptor */ 
        break; 
       } 
      if (i == FD_SETSIZE) 
       err_quit("too many clients"); 

      FD_SET(connfd, &allset); /* add new descriptor to set */ 
      if (connfd > maxfd) 
       maxfd = connfd;   /* for select */ 
      if (i > maxi) 
       maxi = i;    /* max index in client[] array */ 

      if (--nready <= 0) 
       continue;    /* no more readable descriptors */ 
     } 

     for (i = 0; i <= maxi; i++) { /* check all clients for data */ 
      if ((sockfd = client[i]) < 0) 
       continue; 
      **if (FD_ISSET(sockfd, &rset)) { 
       if ((n = Read(sockfd, buf, MAXLINE)) == 0) { 
         /*4connection closed by client */ 
        Close(sockfd); 
        FD_CLR(sockfd, &allset); 
        client[i] = -1; 
       } else 
        Writen(sockfd, buf, n);** 

       if (--nready <= 0) 
        break;    /* no more readable descriptors */ 
      } 
     } 
    } 
} 

關於這個節目,作者說,服務器將從如下DDOS攻擊苦: enter image description here

該點一旦客戶端請求到來,服務器讀取整行然後回顯它。但是這個代碼,我們看到服務器使用Read函數從客戶端讀取數據,而不是ReadLine或Readn,在遇到'\ n'或者獲取指定大小的數據之前,後者不會返回,但是Read函數在這種情況下立即返回。 閱讀功能只是系統調用的包裝「讀」,如下:

ssize_t Read(int fd, void *ptr, size_t nbytes) 
{ 
    ssize_t  n; 

    if ((n = read(fd, ptr, nbytes)) == -1) 
     err_sys("read error"); 
    return(n); 
} 

所以我感到困惑,爲什麼這個服務器將從DDoS攻擊受苦?

任何人都可以解釋它嗎?非常感謝你!

+3

這個例子清楚地表明服務器是單線程的,讀操作是阻塞的。在讀完第一個字節後,服務器保持連接打開等待更多,但客戶端永遠不會發送它,因此服務器不可用於第二個客戶端。 –

+1

此外,這不是DDoS攻擊,這只是一個簡單的DoS攻擊。 –

+0

感謝詹姆斯的糾正。 –

回答

1

我認爲這種混淆是由於本書的第二版和第三版可能存在差異。

我有第二版,其中「Read」實際上是一個「Readline」。然後,解釋是有道理的,因爲Readline堅持閱讀直到換行。

我沒有第三版的副本與之比較。

至於Drunken Code Monkey的解釋,true表示read是阻塞的,但是它受select的保護,這將保證只有在套接字上有活動(斷開連接或在至少1個字節讀取)。所以保證讀取不會被阻塞。但是看到我,如果讀取被替換的Readline有關解釋(在第2版)

又見一前一後上堆棧溢出Unix Network Programming Clarification

+0

「阻止」閱讀並不意味着永遠阻止。讀取等待某個事情本身就是一個問題。這個例子是關於一個客戶端的,但假設你在讀取時間超過500毫秒。現在讓我們假設一個攻擊者利用這個延遲啓動一連串的連接。這是一個分佈式拒絕服務攻擊,它會**使服務器陷入困境。正確的處理方式是在即使讀取之前立即爲每個傳入的套接字連接產生一個單獨的線程。 –

+0

你是對的,謝謝你!我已經與作者安迪魯道夫證實,他的回答是:「是的,你是對的,這是書中的一個錯誤。之前的版本使用了readline,因此受到了這個問題的困擾,但我們忽略了在更新該段落時更改了段落。如果我們甚至做這本書的另一版本,它是在我的清單上的事情要修復!「 –

+0

我不知道其他兩位作者重新拍攝已故W.理查德史蒂文斯的作品恭喜約瑟夫發現這一點 –

0

按斯特凡的迴應,在這裏是爲了說明正確的連接在一個處理的例子多線程TCP服務器。請注意,我對Linux開發很不舒服,很容易編寫它,所以這是C#,但程序流程應該是相同的。如果您必須將其視爲僞代碼。

// We use a wait handle here to synchronize the client threads with the main thread. 
    private static AutoResetEvent _waitHandle = new AutoResetEvent(false); 

    static void Main(string[] args) 
    { 
     // Start the server on port 1337 
     StartServer(1337); 
    } 

    private static void StartServer(int port) 
    { 
     // Create a connection listener 
     var listener = new TcpListener(IPAddress.Any, port); 

     try 
     { 
      // Start the listener 
      listener.Start(); 
      while (true) 
      { 
       // Wait for a connection, and defer connection handling asynchronously. 
       listener.BeginAcceptTcpClient(new AsyncCallback(HandleAsyncConnection), listener); 
       _waitHandle.WaitOne(); 
       _waitHandle.Reset(); 
      } 
     } 
     catch (SocketException ex) 
     { 
      // Handle socket errors or any other exception you deem necessary here 
     } 
     finally 
     { 
      // Stop the server. 
      listener.Stop(); 
     } 
    } 

    private static void HandleAsyncConnection(IAsyncResult state) 
    { 
     // Get the listener and the client references 
     var listener = (TcpListener)state.AsyncState; 
     using (var tcpClient = listener.EndAcceptTcpClient(state)) 
     { 
      // Signal the main thread that we have started handling this request. 
      // At this point the server is ready to handle another connection, and no amount 
      // of tomfoolery on the client's side will prevent this. 
      _waitHandle.Set(); 

      // Declare buffers 
      var inBuff = new byte[tcpClient.ReceiveBufferSize]; 
      var outBuff = new byte[tcpClient.SendBufferSize]; 

      // Get the connection stream 
      using (var stream = tcpClient.GetStream()) 
      { 
       try 
       { 
        // Read some data into inBuff 
        stream.Read(inBuff, 0, tcpClient.ReceiveBufferSize); 

        // Do something with the data here, put response in outBuff... 

        // Send response to client 
        stream.Write(outBuff, 0, outBuff.Length); 
       } 
       catch (SocketException ex) 
       { 
        // Handle socket errors or any other exception you deem necessary here 
       } 
      } 
     } 
    } 
+0

我假設「\\將一些數據讀入inBuff //將響應發送給客戶端部分」中存在代碼缺失 - 如果讀取的字節數少於tcpClient.ReceiveBufferSize或tcpClient.ReceiveBufferSize!= outBuff.Length? –

+1

如果存在比ReceiveBufferSize更少的數據,Read調用將在TcpClient.ReceiveTimeout之後超時。如果你期望傳入的數據比你真實緩衝的大,你必須調用Read多次,直到沒有更多的數據爲止。大小適當的消息,但是,您可能想要適當地發送和接收代碼,重要的是此時無關緊要,因爲請求正在被服務獨一無二的線程。服務器偵聽器在我們_waitHandle.Set()時刻再次可用。 –