基本TCPソケット2−1

listen関数

  • 1.クライアントからSYNが到着
  • 2.TCPは確立待ちコネクションキュー上に新しいエントリを作成
  • 3.3WHSが成功すると、対応するエントリが確立待ちコネクションキューから確立済みコネクションキューの末尾に移動する。
  • 4.accept関数を呼び出すと、確立済みコネクションキューの最初のエントリがプロセスに返されるか、キューが空であった場合は、確立済みコネクションキューに加えられるまでプロセスはスリープ状態におかれる。



    環境変数でbacklogの値を指定できるようにしたlisten関数ラッパ

    void Listen(int fd, int backlog)
    {
      char *ptr;
      
      if ( (ptr = getenv("LISTENQ") ) != NULL )
        backlog = atoi(ptr);
      if (listen(fd, backlog) < 0)
        err_sys("listen error");
    }

    クライントからのSYNが到着した際にキューが満杯だった場合、TCPは、単にSYNを無視する。クライアントはSYNを再送することにより、運がよければキューの空きを見つけることが可能である。もし、TCPがRSTを返すと、クライアントのconnectは即座にエラーを返す。TCPサーバーがRSTを返してしまうと、"このポートにサーバーが存在しない"と"サーバーのキューが満杯である"を区別できない。

    3WHS完了後、サーバーがacceptを呼び出すまでの間に到着したデータは、ソケットの受信バッファが許す限り、サーバーのTCPによってバッファリングされるべき。

    バックログの値は、確立済みコネクション数を指定すべき。

    SYNフラッディング
    TCPサーバーに連続でSYNを送出し、TCPの確立待ちコネクションキューを溢れさせる攻撃。各SYNの始点IPアドレスは乱数で設定されているため、サーバーのSYN/ACKはどこにも届かない(IPスプーフィング)。





    参考文献

  • UNIXネットワークプログラミング スティーブンズ
  • 基本TCPソケット2−2

    accept関数

    TCPサーバーはacceptを用いて確立済みコネクションキューの先頭から、接続完了している次のコネクションを取り出す。確立済みコネクションキューが空の場合、プロセスはスリープ状態におかれる(ブロッキングソケットの場合)。

    #include <sys/socket.h>
    
    int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
    
    戻り値:成功なら非負のディスクリプタ、エラーなら-1

    acceptが成功した場合、その戻り値はカーネルが新規に作成した、クライアントとのTCPコネクションを参照しているディスクリプタ



    クライアントから接続を受け付けた時刻をコンソールに表示するプログラム

    以下のコマンドでサーバーに接続する
    telnet

    #include <time.h>
    #include <sys/socket.h>
    #include <sys/types.h>
    #include <arpa/inet.h>
    #include <stdio.h>
    #include <string.h>
    
    #define MAXLINE 64
    
    int
    main(int argc, char **argv)
    {
      int listenfd, connfd;
      socklen_t len;
      struct sockaddr_in servaddr, cliaddr;
      char buff[MAXLINE];
      time_t ticks;
    
      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(8001);
    
      bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
    
      listen(listenfd, 64);
     
      for (;;) {
        len = sizeof(cliaddr);    connfd = accept(listenfd, (struct sockaddr*)&cliaddr, &len);
        printf("connection from %s port %d\n",      inet_ntop(AF_INET, &cliaddr.sin_addr, buff, sizeof(buff)), ntohs(cliaddr.sin_port));
        ticks = time(NULL);
        snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));
        write(connfd, buff, strlen(buff));
        close(connfd);
      }
      return 0;
    }

    コンパイル: gcc -o daytime daytime.c

    実行: ./daytime



    参考文献

  • UNIXネットワークプログラミング スティーブンズ
  • 基本TCPソケット2−3

    並行サーバー

    並行サーバーの骨格

    pid_t pid;
    int listenfd, connfd;
    
    listenfd = socket(...);
    
    bind(listenfd, ...);
    listen(listenfd, backlog);
    
    for (;;) {
      connfd = accept(listenfd, ...);
      if ( (pid = fork()) == 0 ) {//Child
        //子プロセスは、リスニングソケットをクローズ
        close(listenfd);
    
        //要求の処理
        doit(connfd);
    
        //このクライアントの処理は終了
        close(connfd);
    
       //子プロセスの終了
        exit(0);
      }
      //Parent
      close(connfd);//親プロセスは接続済みソケットをクローズ
    }

    参考文献

  • UNIXネットワークプログラミング スティーブンズ
  • 基本TCPソケット2−4

    getsockname関数とgetpeername関数

    #include <sys/socket.h>
    
    int getsockname(int sockfd, struct sockaddr *localaddr, socklen_t *addrlen);
    
    int getpeername(int sockfd, struct sockaddr *peeraddr, socklen_t *addrlen);
    
    戻り値:成功 → 0,  エラー → -1

    getsocknameサンプル

    #include <sys/socket.h>
    #include <sys/types.h>
    #include <stdio.h>#include <arpa/inet.h>
    
    int
    main()
    {
      int sockfd;
      struct sockaddr_in addr;
      struct sockaddr_in sockname;
      socklen_t addr_len = sizeof(sockname);
      int sockname_len = sizeof(sockname);
      sockfd = socket(AF_INET, SOCK_STREAM, 0);
    
      addr.sin_family = AF_INET;
      addr.sin_port = htons(80);
      addr.sin_addr.s_addr = inet_addr("192.168.10.254");//接続先IPアドレスを指定する
      connect(sockfd, (struct sockaddr*)&addr, addr_len);
    
      getsockname(sockfd, (struct sockaddr*)&sockname, &sockname_len);
    
      printf("IP Address = %s, Port Number = %d\n",
        inet_ntoa(sockname.sin_addr), ntohs(sockname.sin_port));
    
      return 0;
    }

    getpeername サンプル

    #include <stdio.h>
    #include <sys/socket.h>
    #include <sys/types.h>
    #include <arpa/inet.h>
    
    int
    main()
    {
      int listenfd, connfd;
      struct sockaddr_in addr;
      struct sockaddr_in client_addr;
      struct sockaddr_in peer;
      int client_addr_len = sizeof(client_addr);
      socklen_t peer_len = sizeof(peer);
    
      listenfd = socket(AF_INET, SOCK_STREAM, 0);
      addr.sin_family = AF_INET;
      addr.sin_port = htons(8000);
      addr.sin_addr.s_addr = htonl(INADDR_ANY);
      bind(listenfd, (struct sockaddr*)&addr, sizeof(addr));
      listen(listenfd, 64);
      connfd = accept(listenfd, (struct sockaddr*)&client_addr, &client_addr_len);
    
      getpeername(connfd, (struct sockaddr*)&peer, &peer_len);
    
      printf("IP Address=%s, Port=%d\n",
        inet_ntoa(peer.sin_addr), ntohs(peer.sin_port));
      return 0;
    }

    参考文献

  • UNIXネットワークプログラミング スティーブンズ