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를 줄일 수 있습니다.
TCP
TCP의 경우 클라이언트에서 SYN를 보내면 서버로 부터 ACK가 올 때까지
wait을 하게 됩니다.
TFO
TFO또한 TCP 기반으로 통신하는 방식이기 때문에
TFO로 통신하기 위해선 쿠키가 필요합니다. 해서 첫 연결시에는 쿠키를 요청하고 받습니다.
wireshake로 TFO통신시 주고받은 패킷의 option을 확인한 값입니다.
이렇게 쿠키를 클라이언트가 받게되면 해당 쿠키를 기반으로 SYN 패킷에 넣어 통신 할 수 있습니다.
위에 ACK 패킷으로 받은 쿠키값을 기반으로 데이터를 실어 보내는 패킷의 option값입니다.
uname -r
커널 설정 값의 덤프 파일인 "/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() ) 중 하나를 사용할 수 있습니다.
10개의 패킷의 연결부터 데이터 전송 까지 시간 비교
TCP
0.000779초
TFO
0.000677초
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 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 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;
}