본문 바로가기

Networking

[Socket Programming #2] CLOSE_WAIT

오늘은 이전 글에서 사용했던 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 처리 등 문제가 없는지를 분석해야 할 필요가 있습니다.