오늘은 이전 글에서 사용했던 Code를 약간 수정하여 TCP Termination 과정에서 Passive Close(주로 Server)쪽의 TCP State가 어떻게 CLOSE_WAIT 상태로 계속해서 대기하게 되는지를 살펴보겠습니다.
TCP State Diagram
Established 상태에서 먼저 close() 함수를 호출한 쪽을 Active Close라고 하며, close() 함수를 호출하면 FIN flag packet을 보내고 FIN_WAIT_1로 상태 변경
FIN flag packet을 전달받은 Passive Close 쪽은 상태를 CLOSE_WAIT으로 변경하고 응답으로 ACK를 전달하고 포트에 연결되어 있던 Application에 close()를 요청
ACK를 받은 Active Close 쪽은 상태를 FIN_WAIT_2로 변경
Passive Close 쪽에서 Application이 close() 함수를 호출하면, FIN flag packet을 Active Close 쪽으로 보내고, 상태를 LAST_ACK로 변경
FIN flag packet을 전달받은 Active Close 쪽은 응답으로 ACK를 Passive Close 쪽으로 보내고 상태를 TIME_WAIT으로 변경하고 시간이 지나면 CLOSED
ACK를 전달받은 Passive Close 쪽도 포트를 CLOSED로 처리
정리해보면, 종료처리는 다음과 같은 순서로 진행
Active Close : ESTABLISHED --> close() --> FIN_WAIT_1 --> FIN_WAIT_2 --> (Timeout Parameter) --> TIME_WAIT --> (Timeout Parameter) --> CLOSED
Passive Close : ESTABLISHED --> CLOSE_WAIT --> close() --> LAST_ACK --> CLOSED
CLOSE_WAIT 재현
1. Linux Code 예제
아래 코드에서 Client Socket을 종료하지 않도록 주석 처리하고, Server Socket 또한 종료되지 않도록 while 문으로 loop code를 추가하였습니다.
Server Code
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <x86_64-linux-gnu/sys/socket.h> #include <errno.h> #define BUF_SIZE 1024 void error_handling(char* message); int main(int argc, char* argv[]) { int serv_sock, clnt_sock; char message[BUF_SIZE]; int str_len, i; struct sockaddr_in serv_addr, clnt_addr; socklen_t clnt_addr_size; if (argc != 2) { printf("Usage : %s <port>\n", argv[0]); exit(1); } serv_sock = socket(PF_INET, SOCK_STREAM, 0); if (serv_sock == -1) error_handling("socket() error"); memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); serv_addr.sin_port = htons(atoi(argv[1])); //serv_addr.sin_port = htons(5000); if (bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) { //printf(stderr, "errno: %s\n", strerror(errno)); error_handling("bind() error"); } if (listen(serv_sock, 5) == -1) error_handling("listen() error"); clnt_addr_size = sizeof(clnt_addr); for (i = 0; i < 1; i++) { clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size); if (clnt_sock == -1) error_handling("accept() error"); else printf("Connected client %d \n", i + 1); while ((str_len = read(clnt_sock, message, BUF_SIZE)) != 0) write(clnt_sock, message, str_len); //close(clnt_sock); ### <-- } ### <-- while (1) { sleep(1000); printf("while...\n"); } close(serv_sock); return 0; } void error_handling(char* message) { fputs(message, stderr); fputc('\n', stderr); exit(1); } |
Client Code
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #define BUF_SIZE 1024 void error_handling(char* message); int main(int argc, char* argv[]) { int sock; char message[BUF_SIZE]; int str_len; struct sockaddr_in serv_addr; if (argc != 3) { printf("Usage : %s <IP> <port> \n", argv[0]); exit(1); } sock = socket(PF_INET, SOCK_STREAM, 0); if (sock == -1) error_handling("socket() error"); memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = inet_addr(argv[1]); serv_addr.sin_port = htons(atoi(argv[2])); if (connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) error_handling("connect() error"); else puts("Connected.............."); while (1) { fputs("Input message(Q to quit): ", stdout); fgets(message, BUF_SIZE, stdin); if (!strcmp(message, "q\n") || !strcmp(message, "Q\n")) break; write(sock, message, strlen(message)); str_len = read(sock, message, BUF_SIZE - 1); message[str_len] = 0; printf("Message from server: %s", message); } close(sock); return 0; } void error_handling(char* message) { fputs(message, stderr); fputc('\n', stderr); exit(1); } |
2. Linux에서 재현 테스트
※ 테스트 환경은 Windows 에서 WSL을 이용
Server 프로그램 실행
hwjung@jhaewon-z01:/mnt/c/Users/jhaewon/source/repos/server_linux/server_linux/bin/x64/Debug$ ./server_linux.out 9000 |
Client 프로그램 실행
hwjung@jhaewon-z01:/mnt/c/Users/jhaewon/source/repos/client_linux/client_linux/bin/x64/Debug$ ./client_linux.out 127.0.0.1 9000 Connected.............. Input message(Q to quit): |
TCP 3-way handshake 확인
hwjung@jhaewon-z01:~$ sudo tcpdump -i lo tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on lo, link-type EN10MB (Ethernet), capture size 262144 bytes 11:02:55.146737 IP localhost.46098 > localhost.9000: Flags [S], seq 1471646537, win 65495, options [mss 65495,sackOK,TS val 3964231291 ecr 0,nop,wscale 7], length 0 11:02:55.146747 IP localhost.9000 > localhost.46098: Flags [S.], seq 1893900356, ack 1471646538, win 65483, options [mss 65495,sackOK,TS val 3964231291 ecr 3964231291,nop,wscale 7], length 0 11:02:55.146755 IP localhost.46098 > localhost.9000: Flags [.], ack 1, win 512, options [nop,nop,TS val 3964231291 ecr 3964231291], length 0 |
TCP State 확인
※ TCP 3-way handshake 후, Server와 Client 모두 ESTABLISHED 상태
hwjung@jhaewon-z01:~$ netstat -antp (Not all processes could be identified, non-owned process info will not be shown, you would have to be root to see it all.) Active Internet connections (servers and established) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 0.0.0.0:9000 0.0.0.0:* LISTEN 292/./server_linux. tcp 0 0 127.0.0.1:46098 127.0.0.1:9000 ESTABLISHED 293/./client_linux. tcp 0 0 127.0.0.1:9000 127.0.0.1:46098 ESTABLISHED 292/./server_linux. |
Client에서 Server로 message 전달
hwjung@jhaewon-z01:/mnt/c/Users/jhaewon/source/repos/client_linux/client_linux/bin/x64/Debug$ ./client_linux.out 127.0.0.1 9000 Connected.............. Input message(Q to quit): test Message from server: test Input message(Q to quit): |
TCP Packet 확인
※ test 문자열에 Terminator 포함하여 5 byte 전송되는 것을 확인
hwjung@jhaewon-z01:~$ sudo tcpdump -i lo tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on lo, link-type EN10MB (Ethernet), capture size 262144 bytes 11:02:55.146737 IP localhost.46098 > localhost.9000: Flags [S], seq 1471646537, win 65495, options [mss 65495,sackOK,TS val 3964231291 ecr 0,nop,wscale 7], length 0 11:02:55.146747 IP localhost.9000 > localhost.46098: Flags [S.], seq 1893900356, ack 1471646538, win 65483, options [mss 65495,sackOK,TS val 3964231291 ecr 3964231291,nop,wscale 7], length 0 11:02:55.146755 IP localhost.46098 > localhost.9000: Flags [.], ack 1, win 512, options [nop,nop,TS val 3964231291 ecr 3964231291], length 0 11:24:15.724817 IP localhost.46098 > localhost.9000: Flags [P.], seq 1:6, ack 1, win 512, options [nop,nop,TS val 3965511869 ecr 3964231291], length 5 11:24:15.724865 IP localhost.9000 > localhost.46098: Flags [.], ack 6, win 512, options [nop,nop,TS val 3965511869 ecr 3965511869], length 0 11:24:15.725073 IP localhost.9000 > localhost.46098: Flags [P.], seq 1:6, ack 6, win 512, options [nop,nop,TS val 3965511869 ecr 3965511869], length 5 11:24:15.725106 IP localhost.46098 > localhost.9000: Flags [.], ack 6, win 512, options [nop,nop,TS val 3965511869 ecr 3965511869], length 0 |
Client에서 종료 처리
※ Client가 Active Close가 되고, Server가 Passive Close가 되는 상황
hwjung@jhaewon-z01:/mnt/c/Users/jhaewon/source/repos/client_linux/client_linux/bin/x64/Debug$ ./client_linux.out 127.0.0.1 9000 Connected.............. Input message(Q to quit): test Message from server: test Input message(Q to quit): Q |
TCP Packet 확인
hwjung@jhaewon-z01:~$ sudo tcpdump -i lo tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on lo, link-type EN10MB (Ethernet), capture size 262144 bytes 11:02:55.146737 IP localhost.46098 > localhost.9000: Flags [S], seq 1471646537, win 65495, options [mss 65495,sackOK,TS val 3964231291 ecr 0,nop,wscale 7], length 0 11:02:55.146747 IP localhost.9000 > localhost.46098: Flags [S.], seq 1893900356, ack 1471646538, win 65483, options [mss 65495,sackOK,TS val 3964231291 ecr 3964231291,nop,wscale 7], length 0 11:02:55.146755 IP localhost.46098 > localhost.9000: Flags [.], ack 1, win 512, options [nop,nop,TS val 3964231291 ecr 3964231291], length 0 11:24:15.724817 IP localhost.46098 > localhost.9000: Flags [P.], seq 1:6, ack 1, win 512, options [nop,nop,TS val 3965511869 ecr 3964231291], length 5 11:24:15.724865 IP localhost.9000 > localhost.46098: Flags [.], ack 6, win 512, options [nop,nop,TS val 3965511869 ecr 3965511869], length 0 11:24:15.725073 IP localhost.9000 > localhost.46098: Flags [P.], seq 1:6, ack 6, win 512, options [nop,nop,TS val 3965511869 ecr 3965511869], length 5 11:24:15.725106 IP localhost.46098 > localhost.9000: Flags [.], ack 6, win 512, options [nop,nop,TS val 3965511869 ecr 3965511869], length 0 11:25:25.749303 IP localhost.46098 > localhost.9000: Flags [F.], seq 6, ack 6, win 512, options [nop,nop,TS val 3965581893 ecr 3965511869], length 0 11:25:25.790773 IP localhost.9000 > localhost.46098: Flags [.], ack 7, win 512, options [nop,nop,TS val 3965581935 ecr 3965581893], length 0 |
TCP State 확인
※ Client는 Server로 FIN을 보낸 후 ACK를 받은 상태이기 때문에, FIN_WAIT2로 변경되어 있고 여기서 부터는 TCP Kernel Parameter에 지정된 Timeout 값에 따라 일정 시간 이후에 TIME_WAIT으로 변경되고 TIME_WAIT 상태에서도 일정 시간 이후에 CLOSED로 최종 변경
※ 반면에 Client로 부터 FIN을 받은 Server는 ACK를 보내고 나서 Server Application에서 Close() 함수를 처리 못하는 상황이기 때문에 CLOSE_WAIT 상태로 무한 대기
hwjung@jhaewon-z01:~$ netstat -antp (Not all processes could be identified, non-owned process info will not be shown, you would have to be root to see it all.) Active Internet connections (servers and established) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 0.0.0.0:9000 0.0.0.0:* LISTEN 297/./server_linux. tcp 0 0 127.0.0.1:46098 127.0.0.1:9000 FIN_WAIT2 - tcp 0 0 127.0.0.1:9000 127.0.0.1:46098 CLOSE_WAIT 297/./server_linux. |
이렇게 Passive Close 쪽 프로그램에서 올바르게 Close() 함수를 실행하지 못하게 되는 경우 관련 TCP State는 계속해서 CLOSE_WAIT 상태로 유지됩니다.
CLOSE_WAIT 상태로 계속 유지되는 현상을 발견했을 때는 Passive Close(주로 Server 프로그램)에서 왜 Close() 함수를 실행하지 못하고 있는지를 파악해야 합니다.
이는 프로세스 유형별로 Dump를 뜨면서 분석할 수도 있고, Source Code를 통해서도 Recursive 처리 등 문제가 없는지를 분석해야 할 필요가 있습니다.
'Networking' 카테고리의 다른 글
nping 사용법 (0) | 2023.08.09 |
---|---|
RSS from uplink to virtual nic (2) | 2023.07.26 |
[Socket Programming #1] Server/Client based on TCP (0) | 2023.05.18 |
TCP 3-way handshake fails due to missing routing entry (0) | 2023.05.08 |
Driver/Firmware Check - Network Adapter (0) | 2023.03.18 |