Skip to main content

FreeBSD용 RACK 및 대체 TCP 스택

by Randall Stewart and Michael TÜxen

 

2017년에 FreeBSD의 TCP 스택이 변경되어 여러 TCP 스택이 공존할 수 있게 되었습니다. 이렇게 하면 기존 TCP 스택을 그대로 유지하면서 제한된 수의 함수 호출을 통해 혁신을 이룰 수 있습니다. 일부 기능은 여전히 모든 TCP 스택 간에 공유됩니다. SYN-Cookies 처리, 체크섬 검증, 포트 번호와 IP 주소를 기반으로 한 TCP 엔드포인트 조회와 같은 수신 TCP 세그먼트 처리의 초기 단계를 포함한 SYN-Cache 구현이 그 예입니다. 주어진 시간에 TCP 연결은 정확히 하나의 TCP 스택에 의해 처리되지만, 이 TCP 스택은 TCP 연결의 수명 기간 동안 변경될 수 있습니다.

TCP RACK 스택은 tcp_do_segment() 함수와 다른 많은 모듈화된 하위 함수에 대한 호출에서 원래의 TCP 스택을 완전히 재작성하는 것으로 시작되었습니다. 초기 목표는 최근 확인(RACK)이라는 손실 감지 메서드에 대한 지원을 추가하는 것이었습니다. RACK은 인터넷 초안에 설명되어 있었고, 2021년에 RFC 8985가 되었습니다. 이 TCP 스택의 이름인 RACK은 여기에서 유래했습니다. 그러나 TCP RACK 스택은 단순히 RFC 8985에 대한 지원을 추가하는 것 이상으로 성장했습니다. 재작성의 일부에는 선택적 승인(SACK) 정보를 처리하는 완전히 다른 방식이 포함됩니다. TCP RACK 스택에서는 전송된 모든 사용자 데이터의 전체 맵이 유지되어 사용자 데이터의 재전송 처리를 개선하고 RFC 8985에 설명된 RACK 손실 감지를 추가할 수 있습니다. 이 재작성에서 많은 추가 기능이 추가되었으며 이 문서에 설명되어 있습니다.

TCP RACK 스택 사용 방법

RACK 스택은 FreeBSD CURRENT와 FreeBSD 14.0 모두에서 사용할 수 있습니다. 이를 사용할 수 있도록 설정하는 방법은 FreeBSD 버전에 따라 다릅니다.

FreeBSD 14.0의 경우 커널 구성 파일에 다음 두 줄을 추가해야 합니다.

option TCPHPTS
makeoptions WITH_EXTRA_TCP_STACKS=1

를 실행하고 커널을 다시 빌드합니다. 첫 번째 줄은 TCP 고정밀 타이머 시스템(HPTS)을 커널에 컴파일하는 결과입니다. 두 번째 줄은 TCP RACK 스택용 커널 로드 가능 모듈(tcp_rack.ko)을 생성하는 결과입니다. TCP RACK 스택을 사용하려면 커널 모듈을 로드해야 합니다.

tcp_rack_load=”YES”

이 작업은 재부팅할 때마다 /boot/loader.conf 파일에 해당 줄을 추가하여 수행할 수 있습니다.

FreeBSD CURRENT에서는 TCP RACK과 HPTS가 모두 기본적으로 커널 모듈로 빌드됩니다. tcphpts.ko는 tcp_rack.ko의 종속성으로 자동으로 로드되므로, 후자만 kldload를 사용하여 로드해야 합니다. 재부팅할 때마다 TCP RACK 스택을 로드하려면 /boot/loader.conf 파일에 다음 두 줄을 추가해야 합니다:

tcphpts_load=”YES”
tcp_rack_load=”YES”

커널 구성 파일에 다음 두 줄을 추가하고 커널을 다시 빌드하면 FreeBSD CURRENT의 커널에 TCP RACK 스택을 정적으로 컴파일할 수도 있습니다.

option TCPHPTS
option TCP_RACK

TCP 전송 개발자가 다양한 TCP 스택을 계측하고 디버깅하는 표준 방법이기 때문에, 이제 FreeBSD 14.0 이상과 모든 64비트 플랫폼의 FreeBSD CURRENT에도 TCP 블랙박스 로깅(옵션 TCP_BLACKBOX)이 기본으로 빌드되어 있습니다.

sysctl net.inet.tcp.functions_available

위는 FreeBSD 시스템에서 TCP RACK 스택을 사용할 수 있게 하는 방법을 설명합니다. 셸에서 실행하면 사용 가능한 모든 TCP 스택 목록이 표시됩니다.

곧 출시될 버전인 FreeBSD 14.1 이상에서는 TCP RACK 스택의 사용법이 위에서 설명한 FreeBSD CURRENT와 동일합니다.

TCP RACK 스택을 실제로 사용하는 방법은 여러 가지가 있는데, 일부는 애플리케이션의 소스 코드 변경을 포함하며 일부는 구성 변경만 포함합니다.

sysctl-variable net.inet.tcp.functions_default는 socket(2) 시스템 호출을 사용하여 생성된 새 TCP 엔드포인트에 사용되는 기본 TCP 스택을 지정하는 데 사용됩니다. 다음을 실행하면 기본 스택이 TCP RACK 스택으로 설정됩니다. 

sysctl net.inet.tcp.functions_default=rack

/etc/sysctl.conf 파일에 이 줄을 추가하면 시스템 재부팅 후 TCP RACK 스택이 기본 TCP 스택이 됩니다.

net.inet.tcp.functions_default=rack

listener를 통해 TCP 엔드포인트가 생성되면 listener에서 TCP 스택이 상속되거나 sysctl-variable net.inet.tcp.functions_inherit_listen_socket_stack이 0이 아닌지 또는 0인지에 따라 기본 TCP 스택을 기반으로 합니다. 이 변수의 기본값은 1입니다.

이 도구의 매뉴얼 페이지에 설명된 대로 tcpsso(8) 명령줄 도구를 사용하여 개별 TCP 연결의 TCP 스택을 변경할 수도 있습니다.

소스 코드를 변경할 수 있는 경우, TCP_FUNCTION_BLK라는 이름의 IPPROTO_TCP 수준 소켓 옵션을 사용하여 소켓에 사용 중인 TCP 스택을 TCP RACK 스택으로 전환할 수 있습니다. 옵션 값의 타입은 구조체 tcp_function_set입니다. 예를 들어, 다음 코드는 이를 수행합니다:

struct tcp_function_set tfs;

strncpy(tfs.function_set_name, “rack”, TCP_FUNCTION_NAME_LEN_MAX);
tfs.pcbcnt = 0;
setsockopt(fd, IPPROTO_TCP, TCP_FUNCTION_BLK, &tfs, sizeof(tfs));

TCP RACK 스택을 사용하면 기본 TCP 스택이 현재 지원하지 않는 여러 기능을 사용할 수 있습니다. 이러한 기능 중 상당수는 net.inet.tcp.rack의 IPPROTO_TCP 수준 소켓 옵션 또는 sysctl-variables를 통해 제어할 수 있습니다.

TCP RACK 스택의 특징

다음 섹션에서는 TCP RACK 스택이 제공하는 가장 중요한 기능에 대해 설명합니다.

RACK/TLP

Recent Acknowledgement (RACK)과 Tail Loss Probe (TLP)는 TCP RACK 스택에 통합된 두 가지 기능입니다. RACK은 패킷 손실이 감지되고 재전송이 트리거되는 방식을 변경합니다. FreeBSD 기본 스택에서 구현되고 RFC 5681에 명시된 손실 감지는 TCP 스택이 재전송을 보내도록 하기 위해 세 번의 중복 승인 또는 SACK을 통한 승인 도착을 필요로 합니다. 예를 들어 패킷이 4개 미만으로 전송된 경우, 이로 인해 재전송 시간 초과가 발생한 후에야 TCP 스택이 재전송을 전송하게 됩니다. RACK은 이를 변경하여 SACK이 도착했을 때 손실된 패킷을 전송한 후 충분한 시간이 경과한 경우 즉시 재전송이 이루어지도록 합니다. 충분한 시간이 경과하지 않은 경우(일반적으로 현재 RTT보다 약간 큰 시간), 작은 RACK 타이머가 시작되고 이 타이머가 만료되면 재전송이 전송됩니다. 이렇게 하면 재전송 시간 초과로 인해 데이터를 강제로 전송해야 하는 많은 경우(전부는 아니지만)가 해결됩니다. 마지막 경우는 TLP로 해결됩니다. 이 경우 TCP RACK 스택이 데이터를 전송할 때마다 재전송 타이머 대신 TLP 타이머를 시작합니다. TLP 타이머가 만료되면 TCP RACK 스택은 새 세그먼트 또는 마지막으로 전송된 세그먼트를 보냅니다. 이 TLP 전송 세그먼트의 희망은 발신자가 모든 데이터가 수신되었음을 나타내는 확인을 다시 받거나(마지막 확인이 손실된 경우) TLP가 SACK을 유도하여 재전송 시간 초과에 도달하지 않고 정상적인 빠른 복구 메커니즘이 이어받아 혼잡 창이 1 MSS로 축소되는 것입니다.

TCP RACK 스택 사용자는 스택을 활성화하기만 하면 자동으로 RACK과 TLP의 이점을 모두 누릴 수 있습니다. 상위 계층에서는 소켓 옵션이나 구성이 필요하지 않습니다.

Proportional Rate Reduction (PRR)

비례 전송률 감소(PRR)는 TCP RACK 스택의 또 다른 자동 내장 기능으로, RFC 6937에 명시되어 있으며 현재 IETF에서 업데이트 중입니다. PRR은 빠른 복구 중에 데이터가 전송되는 방식을 개선합니다. RFC 5681에 명시된 대로 TCP 혼잡 제어를 사용하면 빠른 복구에 들어갈 때 혼잡 윈도우가 절반으로 줄어듭니다. 그러면 빠른 복구 중에 새 데이터 전송이 지연됩니다. 기본적으로 발신자는 미처리 데이터의 절반이 승인될 때까지 기다려야만 재전송과 함께 새 데이터 전송을 시작할 수 있습니다. 이로 인해 발신자와 수신자 간의 데이터 흐름에 '스톨'이 발생합니다. PRR은 이를 개선하기 위해 설계되어 빠른 복구 중에 거의 매번 승인될 때마다 새로운 데이터 세그먼트를 전송할 수 있습니다. 이렇게 하면 데이터 '멈춤'을 방지하고 데이터를 계속 이동시켜 RTT 및 기타 전송 메트릭을 활성 상태로 유지하고 업데이트할 수 있습니다.

RACK Rapid Recovery (RRR)

RACK Rapid Recovery(RRR)는 버그로 시작된 흥미로운 기능입니다. 초기 개발 과정에서 TCP RACK 스택은 실수로 하나 이상의 세그먼트가 누락된 SACK이 도착하고 모든 데이터에 대한 RACK 타이머가 만료되면 하나의 세그먼트를 전송하고 RACK 타이머를 시작하는 경우를 허용했습니다. RACK 타이머가 만료되면(RACK 최소 타임아웃 값인 1ms로 설정됨), TCP RACK 스택은 누락된 세그먼트 중 하나를 다시 전송합니다. 누락된 세그먼트가 모두 전송될 때까지 이 과정이 반복됩니다. 이렇게 하면 초기 복구 중에 PRR을 효과적으로 무시하고 훨씬 나중에 추가 PRR 세그먼트를 전송하는 비용이 발생합니다. 예를 들어 RRR이 첫 번째 재전송과 두 개의 추가 세그먼트 등 3개의 세그먼트를 전송한 경우, PRR이 새 세그먼트를 전송하기까지 약 6개의 확인이 더 도착해야 합니다.

이 버그가 발견되어 '수정'되었을 때 사용자 경험 품질(QoE)이 저하되었습니다. 이러한 초기 세그먼트 손실로 인해 상당수의 데이터 세그먼트 전송이 지연되는 경우가 많았기 때문입니다. 따라서 이 기능을 끌 수 있는 기능으로 추가했으며, 기본 설정에서 12Mbps로 RRR 속도를 효과적으로 설정할 수 있는 시간으로 프로그래밍할 수도 있습니다. 기본적으로 이 기능은 밀리초마다 한 세그먼트에 대해 RRR 복구 속도가 설정된 상태로 켜져 있습니다. 따라서 최대 전송 단위(MTU)가 1500바이트라고 가정하면 12Mbps의 속도가 됩니다.

SACK Attack Detection

전송되는 데이터의 전체 맵을 유지하는 것의 단점 중 하나는 경우에 따라 이 맵이 상당히 커질 수 있다는 것입니다. TCP RACK 그러나 악의적인 피어가 TCP RACK 스택이 많은 양의 메모리를 사용하고 해당 메모리를 검색하는 데 과도한 시간을 소비하도록 센트맵을 지속적으로 더 작은 조각으로 분할하여 TCP 연결에 사용되는 메모리와 CPU 리소스를 공격하도록 설계할 수 있는 가능성이 존재합니다. 공격자가 매 바이트마다 SACK을 보내는 경우를 예로 들 수 있습니다. 이는 심각한 위협이 될 수 있으며 원치 않는 방식으로 시스템에 영향을 미칠 수 있습니다.

TCP RACK 스택에는 TCP_SAD_DETECTION이라는 선택적 컴파일 기능이 포함되어 있습니다. SAD는 SACK 공격 탐지(SAD)의 약자입니다. 커널 구성 파일에 해당 줄을 추가하고 커널을 다시 빌드하여 TCP RACK 스택에 이 기능을 활성화할 수 있습니다.

option TCP_SAD_DETECTION

일단 추가하면 기본적으로 켜져 있습니다. 악의적인 피어를 감시하고, 탐지되면 해당 피어에서 SACK 처리를 비활성화합니다. 이렇게 하면 해당 피어의 성능이 저하되지만 연결이 진행되는 것을 막지는 못합니다. 사실상 SACK이 활성화되지 않은 것처럼 응답하는 연결이 됩니다. 이렇게 하면 손실 복구에 불이익이 발생하지만 연결은 계속할 수 있습니다.

Burst Mitigation

버스트 완화 기능은 사용자 개입 없이 TCP RACK 스택에 내장되어 있습니다. 버스트를 완화하기 위해 스택은 전송 기회에 설정된 크기(최대 버스트 크기)만 전송하고 작은 타이머를 시작하거나(더 많이 전송하기 위해) 반환되는 확인 스트림에 따라 더 많은 데이터를 전송하도록 유도합니다. 이렇게 하면 과도한 손실을 유발할 수 있는 대규모 버스트를 완화하는 데 도움이 됩니다.

Support for TCP Blackbox Logging (BBLog)

TCP RACK 스택의 흥미로운 측면 중 하나는 디버깅과 일반적인 통계 분석 및 계측을 위한 TCP 블랙박스 로깅을 광범위하게 지원한다는 점입니다. 이를 통해 문제를 추적하고 연결 동작에 대한 분석을 훨씬 쉽게 얻을 수 있습니다.

Large Receive Offload (LRO) Integration for Burst Mitigation

TCP 대용량 수신 오프로드(LRO)는 수신된 여러 개의 TCP 세그먼트를 하나의 세그먼트로 합쳐서 TCP 스택으로 전달하기 전에 수신기에 필요한 CPU 리소스를 줄이는 기능입니다. 이로 인해 개별 수신 세그먼트에 대한 정보가 손실되는 경우가 많지만, TCP 스택에서 처리해야 하는 TCP 세그먼트 수가 줄어들기 때문에 필요한 CPU 리소스를 줄일 수 있습니다.

흥미로운 기능 상호 작용은 TCP RACK 스택에서 페이싱을 더 잘 지원하기 위해 LRO 코드에 적용된 일련의 변경 사항입니다. TCP 연결이 버스트 완화를 수행하는 경우, 전송 경로를 더 자주 통과하여 더 작은 버스트를 보내는 경향이 있습니다. 이 때문에 패킷 도착 정보에 대한 모든 타이밍 데이터가 손실 없이 TCP RACK 스택으로 전달될 수 있도록 LRO 코드가 변경되었습니다. 기본적으로 패킷을 처리하는 동안 LRO 코드는 패킷이 TCP RACK 스택에 직접 패킷을 큐에 넣을 수 있는 연결과 연결되어 있는지 조회합니다. 그렇다면 패킷은 연결에 직접 큐에 대기하고 연결 상태에 따라 연결이 깨어날 수 있습니다. TCP RACK 스택이 버스트 완화 또는 페이싱을 수행하는 경우, 타이머가 만료되고 인바운드 승인을 통해 무언가를 수행할 수 있을 때까지 웨이크업이 연기됩니다. 이러한 단계는 또한 IP 스택 처리를 우회하므로 필요한 CPU 리소스를 추가로 약간 줄일 수 있습니다.

A Host of Alternate Features

이 외에도 다양한 소켓 옵션과 sysctl-variables를 통해 TCP RACK 스택에서 많은 기능을 사용할 수 있습니다. 현재 TCP RACK 스택은 페이싱, 버스트 완화 옵션, 복구 응답 수정 등 다양한 기능을 가능하게 하는 58개의 소켓 옵션을 지원합니다. 소켓 옵션 외에도, 소켓 옵션을 모든 연결에 적용하거나 다양한 TCP RACK 스택 기본 구성을 수정하기 위해 약 150개의 sysctl-variables가 존재합니다. 이러한 모든 기능과 구성을 통해 네트워크 조건과 요구사항에 맞게 TCP RACK 스택을 조정할 수 있습니다.

넷플릭스가 TCP RACK 스택을 발전시키는 방법

넷플릭스는 현재 TCP RACK 스택만 사용하고 있으며, FreeBSD 기본 스택은 존재하지만 사용되지 않습니다. 넷플릭스가 TCP RACK 스택을 사용하는 방식은 약간 새롭고 주목할 가치가 있습니다. 넷플릭스는 실제로 릴리스 번호로 명명된 여러 세대의 TCP RACK 스택을 보관하고 있습니다. 항상 전송 그룹에서 개발 중인 모든 최첨단 기능이 포함된 "최신" TCP RACK 스택을 유지합니다.

주기적으로 릴리스가 중단되면 릴리스 번호에 따라 개발 중인 최신 TCP RACK 스택이 복사되어 지원됩니다. 그런 다음 이 TCP 스택은 사용 중인 기본 TCP 스택인 이전 릴리스와 비교하여 QoE 및 CPU 성능을 기준으로 평가됩니다. 최신 TCP RACK 스택이 이전 TCP RACK 스택보다 최소한 동등하거나 더 나은 경우, 기본값은 다음 릴리스에서 최신 TCP RACK 스택으로 전환됩니다. 이전 TCP RACK 스택은 여러 릴리스 동안 유지되다가 결국 제거됩니다.

TCP RACK 스택의 새로운 기능도 이러한 방식으로 테스트하여 해당 기능이 가치를 더하는지 여부를 결정할 수 있습니다. 넷플릭스 사용자의 체감 품질 저하 없이 네트워크에 미치는 영향을 줄이는 것이 전송 팀의 주요 목표 중 하나이며, 이를 통해 넷플릭스는 더 나은 네트워크 시민이 되는 동시에 전반적으로 우수한 QoE를 제공할 수 있습니다.

결론 및 전망

TCP RACK 스택은 FreeBSD 기본 스택에 대한 강력한 대안을 제공합니다. 더 많은 기능과 옵션을 추가하여 애플리케이션 개발자가 사용자에게 더 나은 TCP 경험을 제공할 수 있는 다양한 대안을 제공합니다.

TCP RACK 스택은 넷플릭스 설정과 워크로드를 사용하여 광범위하게 테스트되었습니다. 하지만 다른 설정과 워크로드에서도 테스트하는 것이 중요합니다. 따라서 사용자가 자신의 하드웨어에서, 자신의 설정과 워크로드에서 TCP RACK 스택을 테스트해 주시면 감사하겠습니다. 테스트 중에 발견되는 문제가 있으면 net@freebsd.org 또는 이 문서의 작성자에게 보고해 주세요. 피드백 및 추가 테스트에 따라, TCP RACK 스택은 향후 FreeBSD의 기본 스택이 될 수도 있습니다.


랜달 스튜어트(rrs@freebsd.org)는 40년 이상 운영체제 개발자로, 10년 이상 FreeBSD 개발자로 활동해 왔습니다. 그는 TCP와 SCTP를 포함한 전송을 전문으로 하지만 운영체제의 다른 영역에도 관심을 갖고 있습니다. 현재 넷플릭스에서 전송 팀에서 근무하며 TCP 스택을 지원하는 동시에 사용자 QoE를 지속적으로 개선하기 위해 혁신하고 있습니다.


마이클 투센(tuexen@freebsd.org)은 뮌스터 응용과학대학의 교수이자 넷플릭스의 파트타임 계약자이며 2009년부터 FreeBSD 소스 커미터로 활동하고 있습니다. 그의 관심 분야는 SCTP와 TCP와 같은 전송 프로토콜과 IETF에서의 표준화, 그리고 FreeBSD에서의 구현입니다.