[Network] TFO란?

윤동환·2023년 4월 24일
1

Network

목록 보기
8/9
post-thumbnail

TFO란?

TCP Fast Open의 약자로 TCP의 3-way handshake를 통한 연결 및 처리를 빠르게 하기위해(RTT[Round Trip Time]를 줄이기 위해) 구글에서 2011년도에 제시한 프로토콜 확장입니다.

즉, 두 끝점 간의 연속적인 TCP(Transmission Control Protocol) 연결
속도를 높이
는 확장입니다.

어떻게 속도를 높이나요?
TFO방식은 3-way handshake를 위한 SYN 패킷에 data를 먼저 실어보내고 SYN-ACK패킷에 그 응답을 받도록 하여 첫 연결시 발생하는 RTT를 줄일 수 있습니다.

1. TCP, TFO 연결 방식 차이

TCP
TCP의 경우 클라이언트에서 SYN를 보내면 서버로 부터 ACK가 올 때까지
wait을 하게 됩니다.

TFO
TFO또한 TCP 기반으로 통신하는 방식이기 때문에
TFO로 통신하기 위해선 쿠키가 필요합니다. 해서 첫 연결시에는 쿠키를 요청하고 받습니다.

wireshake로 TFO통신시 주고받은 패킷의 option을 확인한 값입니다.

이렇게 쿠키를 클라이언트가 받게되면 해당 쿠키를 기반으로 SYN 패킷에 넣어 통신 할 수 있습니다.

위에 ACK 패킷으로 받은 쿠키값을 기반으로 데이터를 실어 보내는 패킷의 option값입니다.

2. TFO 초기 설정 조건

커널의 버전이 TFO를 지원하는지 확인해야합니다.

uname -r 
  • 커널의 릴리즈 정보를 확인을 합니다.
    3.12부터 지원하기 시작했지만 클라이언트는 3.16, 서버는 3.17부터 지원됩니다.
    때문에 커널 3.17 버전 부터 TFO방식으로 클라이언트와 서버 통신하는 것을 추천드립니다.

tcp_fastopen을 클라이언트와 서버를 지원하도록 수정하기

커널 설정 값의 덤프 파일인 "/proc/sys/net/ipv4/tcp_fastopen"를

cat /proc/sys/net/ipv4/tcp_fastopen

명령어로 확인합니다.
출력 값에 대한 설정은 아래와 같이 의미합니다.
0 : 모두 비활성화
1 : 클라이언트 만 활성화
2 : 서버 만 활성화
3 : 클라이언트, 서버 활성화

sysctl -w net.ipv4.tcp_fastopen=3 

위의 명령어로 원하는 설정으로 제어할 수 있습니다.

커널 설정 값에 따른 통신 결과

TFO 연결 실패시 TCP로 통신하도록 한 결과입니다.

0. Sysctl –w net.ipv4.tcp_fastopen=0 으로 클라이언트, 서버 둘 다 허용하지 않을 때
일반 TCP방식으로 통신하는 것을 확인 할 수 있습니다.

1. sysctl -w net.ipv4.tcp_fastopen=1 으로 클라이언트만 허용 시

SYN 패킷에 아래처럼 쿠키 요청이 들어갔으나, server는 TFO를 지원하지 않기때문에
TCP통신처럼 PSH, ACK 패킷이 client -> server, server -> client 총 2번 생겼습니다.
하지만 일반 TCP와는 다르게 연결됨의 SYN-ACK패킷을 받으며 data를 3 handshake의 ack에 실어 보내는 것을 확인할 수 있습니다.

2. sysctl -w net.ipv4.tcp_fastopen=2 으로 서버만 허용 시

일반 TCP방식으로 통신하는 것을 확인 할 수 있습니다.

3. sysctl -w net.ipv4.tcp_fastopen=3 으로 정상 처리 시
첫번째와 두번째 행의 패킷 옵션을 보면 쿠키를 요청하고 받습니다. 때문에 syn 패킷에는 데이터가 들어가있지 않습니다.
때문에 첫 연결시 SYN 패킷에 data가 있지 않고, ack시 data를 전송합니다.

적용을 위한 코드

setsockopt() 으로 Socket fd에 TCP_FASTOPEN 옵션 설정을 해주어
서버와 클라이언트가 TFO 방식으로 통신할 수 있습니다.

getsockopt()을 사용하여 socket fd에 적용된 옵션 값을 확인할 수 있습니다.


TFO 클라이언트는 연결 개시와 데이터 전송을 한 단계로 결합하기 때문에
단일 작업에서 서버 주소와 데이터를 모두 지정할 수 있는 API를 사용해야 합니다.
이를 위해 클라이언트는 두 가지 시스템 호출( sendto() 및 sendmsg() ) 중 하나를 사용할 수 있습니다.

3. TCP, TFO 성능 비교

10개의 패킷의 연결부터 데이터 전송 까지 시간 비교
TCP
0.000779초
TFO
0.000677초

4. TCP, TFO 클라이언트, 서버 코드 비교

common

header file

#ifndef TFO_H
# define TFO_H

# include <cstdio>
# include <cstdlib>
# include <cstring>
# include <sys/socket.h>
# include <netinet/in.h>
# include <arpa/inet.h>
# include <unistd.h>
# include <errno.h>
# include <linux/tcp.h>

# define PORT 8080
# define BUFFER_SIZE 1024
# define CLIENT_CONNECT 10 

#endif

TCP

TCP client

#include "tfo.h"

int main(int argc, char const *argv[]) {
	char buffer[BUFFER_SIZE] = {0};

	for (int count = 0; count < CLIENT_CONNECT; ++count) {
		int sock = 0;
		if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
			printf("Socket creation error\n");
			return -1;
		}

		int port = PORT;
		if (argc >= 2)
			port = atoi(argv[1]);
		
		struct sockaddr_in serv_addr;
	
		memset(&serv_addr, 0, sizeof(serv_addr));
		serv_addr.sin_family = AF_INET;
		serv_addr.sin_port = htons(port);

		if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
			printf("Invalid address / Address not supported\n");
			return -1;
		}
		if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
			printf("Connection Failed\n");
			return -1;
		}

		//	printf("Enter message: ");
		//	fgets(buffer, BU(const int)BUFFER_SIZE, stdin);
		snprintf(buffer, BUFFER_SIZE, "---[%d] client message---", count); 
		send(sock, buffer, strlen(buffer), 0);
		memset(buffer, 0, BUFFER_SIZE);
		printf("Message sent\n");
		int valread = read(sock, buffer, BUFFER_SIZE);
		printf("Server reply: %s\n", buffer);
		close(sock);
	}
	return 0;
}

TCP server

#include "tfo.h"

int main(int argc, char const *argv[]) {
	
	char buffer[BUFFER_SIZE] = {0};


	int server_fd;
	if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
		printf("Socket creation error\n");
		return -1;
	}

	int opt = 1;
	if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
		printf("Setsockopt error\n");
		return -1;
	}

	int port = PORT;
	
	if (argc >= 2)
		port = atoi(argv[1]);
	
	struct sockaddr_in address;
	
	address.sin_family = AF_INET;
	address.sin_addr.s_addr = inet_addr("127.0.0.1");
	address.sin_port = htons(port);

	if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
		printf("Bind failed %s\n", strerror(errno));
		return -1;
	}

	if (listen(server_fd, 3) < 0) {
		printf("Listen error %s\n", strerror(errno));
		return -1;
	}

	int new_socket = 0;
	int addrlen = sizeof(address);

	char serv_msg[BUFFER_SIZE] = {0};


	for (int count = 0; count < CLIENT_CONNECT; ++count) {
		if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
			printf("Accept error\n");
			return -1;
		}

		int valread = read(new_socket, buffer, BUFFER_SIZE);
		printf("Client message: %sClient message len : %d\n", buffer, valread);
		
		snprintf(serv_msg, (const int)BUFFER_SIZE, "[%d] server send", count);

		int valsend = send(new_socket, serv_msg, strlen(serv_msg), 0);
//		printf("Hello message sent valsend : %d\n", valsend);
		close(new_socket);
	}
	close(server_fd);
	return 0;
}

TFO

TFO client

#include "tfo.h"

int main(int argc, char const *argv[]) {
	char buffer[BUFFER_SIZE] = {0};

	int client_fd = 0;

	for (int count = 0; count < CLIENT_CONNECT; ++count) {
		if ((client_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
			printf("Socket creation error [%d] %s\n", errno, strerror(errno));
			return -1;
		}
		int opt = 5;
		int opt_len = sizeof(opt);

		if (setsockopt(client_fd,  IPPROTO_TCP, TCP_FASTOPEN, &opt, opt_len) < 0) {
			printf("TFO setsockopt %s\n", strerror(errno));
			return -1;
		}

		int port = PORT;

		if (argc >= 2)
			port = atoi(argv[1]);

		struct sockaddr_in client_addr;

		memset(&client_addr, 0, sizeof(client_addr));
		client_addr.sin_family = AF_INET;
		client_addr.sin_port = htons(port);

		if (inet_pton(AF_INET, "112.172.129.232", &client_addr.sin_addr) <= 0) {
			printf("Invalid address / Address not supported\n");
			return -1;
		}
		
		snprintf(buffer, BUFFER_SIZE, "[%d] client message", count);
		
		if (getsockopt(client_fd,  IPPROTO_TCP, TCP_FASTOPEN, &opt, (socklen_t *)&opt_len) < 0) {
			printf("TFO getsockopt %s\n", strerror(errno));
			return -1;
		}
		printf("opt : %d\n", opt);
		int ret = sendto(client_fd, buffer, strlen(buffer), MSG_FASTOPEN, (struct sockaddr *)&client_addr, sizeof(client_addr));
		if (ret < 0) {
			if (errno == EINPROGRESS) {
				printf("TCP_FASTOPEN in progress\n");
			} else {
				printf("sendmsg %s\n", strerror(errno));
				ret = connect(client_fd, (struct sockaddr *)&client_addr, sizeof(client_addr));
				printf("ret : %d\n", ret);
				send(client_fd, buffer, strlen(buffer), 0);
			}
		} else {
			printf("Message sent\n");
		}

		memset(&buffer, 0, BUFFER_SIZE);
		if (recv(client_fd, buffer, BUFFER_SIZE, 0) < 0) {
			printf("recv errro [%d] %s\n", errno, strerror(errno));
		}
		printf("Server reply: %s\n", buffer);
		close(client_fd);
	}

	return 0;
}

TFO server

#include "tfo.h"

int main(int argc, char const *argv[]) {
	char buffer[BUFFER_SIZE] = {0};

	int server_fd = 0;
	if ((server_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == 0) {
		printf("Socket creation error\n");
		return -1;
	}

	int opt = 1;
	int opt_len = sizeof(opt);
	if (setsockopt(server_fd, IPPROTO_TCP, TCP_FASTOPEN, &opt, opt_len) < 0) {
		printf("TCP_FASTOPEN option set error\n");
		return -1;
	}


	int port = PORT;
	if (argc >= 2)
		port = atoi(argv[1]);

	struct sockaddr_in serv_addr;

	memset(&serv_addr, 0, sizeof(serv_addr));
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_port = htons(port);

	if (inet_pton(AF_INET, "112.172.129.232", &serv_addr.sin_addr) <= 0) {
		printf("Error [%d] %s\n", errno, strerror(errno));
		return -1;
	}
	if (bind(server_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
		printf("Bind failed\n");
		return -1;
	}

	if (listen(server_fd, 3) < 0) {
		printf("Listen failed\n");
		return -1;
	}

	int addrlen = sizeof(serv_addr);
	char serv_msg[BUFFER_SIZE] = {0};
	int client_fd = 0;

	for (int count = 0; count < CLIENT_CONNECT; ++count) {
		if ((client_fd = accept(server_fd, (struct sockaddr *)&serv_addr, (socklen_t *)&addrlen)) < 0) {
			printf("Accept failed\n");
			return -1;
		}

		if (recv(client_fd, &buffer, BUFFER_SIZE, 0) < 0) {
			printf("recv error [%d] %s\n", errno, strerror(errno));
		}

		printf("Client message: %s\n", buffer);

		memset(&serv_msg, 0, BUFFER_SIZE);
		snprintf(serv_msg, BUFFER_SIZE, "[%d] serv message", count);
		if (send(client_fd, &serv_msg, strlen(serv_msg), 0) < 0) {// MSG_FASTOPEN, (struct sockaddr *)&serv_addr, sizeof(serv_addr))) {
			printf("send error [%d] %s\n", errno, strerror(errno));
		}
		printf("server message sent\n");
		close(client_fd);
	}
	close(server_fd);
	return 0;
}

Reference

profile
모르면 공부하고 알게되면 공유하는 개발자

0개의 댓글