基本TCPソケット2−1
listen関数
環境変数で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スプーフィング)。
参考文献
基本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
参考文献
基本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);//親プロセスは接続済みソケットをクローズ }
参考文献
基本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; }
参考文献