Multipath TCP(MPTCP)의 보안/프라이버시, 성능 영향, 소켓 옵션 지원, 지원 운영체제, QUIC와의 비교, 미들박스 처리, 미지원 환경에서의 애플리케이션 처리, 빌드 타임 체크, 동작 확인 방법, 커널 개발 환경 부트스트랩, NIC 수신 큐 이슈, GRO 지원, OpenSSH 연동 방법 등 자주 묻는 질문과 답변을 정리합니다.
MPTCP는 기존 TCP와 동일한 수준의 보안을 유지하는 것을 목표로 하며, 일반적인 네트워크 공격에 대응하기 위한 특정 메커니즘을 포함합니다. 자세한 내용은 RFC 8684를 참고하세요. TCP보다 더 높은 보안을 달성하려면, 예를 들어 MPTCPsec처럼 프로토콜을 일부 수정해야 합니다.
서버와 클라이언트를 분리해 생각해야 합니다. 서버에서는 기본값으로 MPTCP를 활성화해 두고, 요청이 있을 때만 사용하도록 하여 영향도를 최소화할 수 있습니다. 클라이언트 측에서는 기본적으로 MPTCP가 사용되고 있음을 사용자에게 알리는 것이 유용할 수 있습니다.
클라이언트는 일반적으로 MPTCP 사용으로 얻는 이점이 가장 큽니다. 다만 MPTCP의 이점은 사용자가 시스템을 멀티패스 기능을 활용하도록 구성했을 때 주로 실현됩니다. 그럼에도 불구하고 네트워크 인터페이스가 하나뿐인 경우에도, 연결을 끊지 않고 한 네트워크에서 다른 네트워크로 자주 전환하는 이동성(mobility) 시나리오에서는 MPTCP가 도움이 됩니다. 서버가 MPTCP를 지원하지 않으면 연결은 “일반” TCP로 계속됩니다.
서버는 보통 안정적이고 빠르며 신뢰할 수 있는 네트워크 연결을 갖고 있어 MPTCP로 직접적인 이점을 얻기 어렵습니다. 전형적인 MPTCP 개선은 클라이언트/서버 시스템 전체에서 체감됩니다. 다만 서버 특화 사용 사례도 있습니다:
서버에서는 기본값으로 MPTCP를 활성화해 사용자에게 MPTCP 사용 여부를 선택할 수 있게 하길 권장합니다. 클라이언트가 MPTCP 사용을 요청하지 않으면, 연결이 accept될 때 서버 애플리케이션은 커널 내에서 “일반” TCP 소켓을 생성하므로 성능 영향은 최소화됩니다.
MPTCP는 TCP 애플리케이션의 성능을 해치지 않으면서 네트워크 회복력과 활용도를 개선하도록 설계되었습니다. 각 TCP 패킷에 몇 바이트를 추가하여 관리 가능한 오버헤드(약 1%)가 발생하지만, 여러 네트워크 경로를 활용할 때 처리량과 신뢰성이 향상되어 이점이 됩니다.
또한 클라이언트가 MPTCP 사용을 요청하지 않으면, 연결이 accept될 때 서버 애플리케이션은 커널 내에서 “일반” TCP 소켓을 생성하므로 성능 영향은 최소화됩니다.
Linux에서 MPTCP는 TCP가 처리하는 대부분의 소켓 옵션을 지원합니다. 일부 덜 일반적인 옵션은 지원되지 않을 수 있습니다. 그런 경우, 새 이슈에 사용 사례를 문서화하고 패치를 공유해 주세요.
예를 들어, Linux 커널의 MPTCP는 현재 KTLS와 호환되지 않습니다.
일반적으로 동일한 값이 모든 서브플로우에 전파되며, 이는 setsockopt() 호출 이후에 생성된 서브플로우에도 적용됩니다. 서브플로우별로 다른 값을 설정하려면 eBPF를 사용할 수 있습니다.
MPTCP는 Linux 커널 5.6부터 공식 커널에서 지원됩니다. 어떤 애플리케이션이든 손쉽게 사용할 수 있습니다. 채택은 macOS, 로드 밸런서, 나아가 FreeBSD 및 Windows 등 다양한 플랫폼으로 확장되고 있습니다. 다만... macOS에서의 MPTCP 사용은 Linux와 다릅니다:
AF_MULTIPATH(39) 도메인으로 생성한 경우 멀티패스 지원을 언급하는 connectx 시스템 콜에 대한 문서가 일부 있습니다. 하지만 이러한 인터페이스를 사용하기 위해 무엇이 필요한지는 명확하지 않습니다. MPTCP 연결 자체는 생성할 수 있는 것으로 보이지만, 추가 서브플로우는 생성할 수 없는 듯합니다. Apple에서 공개한 예제 코드가 없어 이 방법은 권장되지 않는 것으로 보입니다.FreeBSD에는 구현이 진행된 적이 있으나 수년 전의 일이며, 여기에 따르면 현재는 동작하지 않습니다.
기타 특정 시스템(Citrix 로드 밸런서, 사용자 공간 등)용 구현도 있습니다: 자세한 내용은 여기를 참고하세요.
WSL2를 사용해 Windows에서 MPTCP를 사용할 수도 있습니다.
MPTCP는 전송 계층에서 멀티패스 기능을 제공함으로써 TCP의 기능을 확장합니다. 반면 UDP 위에 구축된 QUIC는 지연 시간 감소와 연결 마이그레이션 개선에 중점을 둡니다. 둘 다 멀티패스 기능을 제안하지만, 개발 및 표준화 단계는 서로 다릅니다.
QUIC의 멀티패스 기능은 아직 표준화되지 않았습니다. 초안은 여기에서 볼 수 있습니다. 애플리케이션은 경로 선택과 경로 간 데이터 흐름 제어를 위해 더 많은 구성 옵션을 가질 수도 있습니다.
프로토콜에는 두 가지 버전이 있습니다: RFC 6824 (MPTCPv0, 폐기)와 RFC 8684 (MPTCPv1, 현재). 업스트림 Linux 커널은 v1만 지원합니다. 이전의 트리 외 커널은 v0와 v1을 모두 지원했지만, 이제는 업스트림 릴리스를 사용하는 것이 권장됩니다.
MPTCP는 필요할 때 표준 TCP로 폴백하도록 세심하게 설계되었습니다. 이는 MPTCP와 같은 “알 수 없는” TCP 옵션을 제거할 수 있는 NAT와 방화벽(미들박스)이 존재하는 환경에서도 연결이 중단되지 않도록 보장합니다.
만약 네트워크에서 MPTCP가 허용되지 않아 “일반” TCP가 대신 사용되는 것을 발견했다면, 네트워크 담당자에게 이 문제를 보고해 주세요. MPTCP가 허용되지 않는 것은 실수일 가능성이 매우 큽니다.
MPTCP를 네이티브로 지원하는 애플리케이션은 먼저 MPTCP 소켓 생성을 시도하고, 오류가 발생하면 “일반” TCP로 폴백해야 합니다. Linux 커널이 MPTCP를 지원하지 않으면 소켓 생성 시 적절한 오류가 반환됩니다. 커널 버전과 구성은 빌드 타임과 런타임이 서로 다를 수 있습니다(예: 일부 빌더는 최신 사용자 공간 프로그램을 갖춘 chroot/컨테이너를 사용하지만, 커널은 오래된 안정 버전일 수 있음). 따라서 빌드 타임에 최종 사용자의 런타임 환경을 추측하기보다, 런타임에 커널이 미지원 시 오류를 반환하도록 두고 그에 따라 처리하는 방식의 체크를 권장합니다.
소스 코드를 다른 머신에서 컴파일하는 관행이 일반적이며, 빌드 머신에서 더 오래된 커널을 실행하고 있을 수도 있으므로, 타깃이 Linux인지 확인하는 것 외에는 빌드 타임에 MPTCP 사용을 제한하지 않는 것을 권장합니다.
오래된 libC 버전에는 IPPROTO_MPTCP가 없을 수 있으므로 이를 수동으로 정의해야 할 수도 있습니다. 이에 대한 자세한 내용은 구현 가이드를 참고하세요.
IPPROTO_MPTCP가 정의되어 있지 않나요?프로그램이 항상 실행되는 시스템에서 컴파일되는 것은 아니므로, 해당 심볼이 이미 정의되어 있지 않다면 IPPROTO_MPTCP를 수동으로 정의하는 것을 권장합니다:
#ifndef IPPROTO_MPTCP
#define IPPROTO_MPTCP 262
#endif
가장 쉬운 방법은 MPTCP 사용 여부를 확인해 클라이언트에 알려주는 전용 테스트 서버를 이용하는 것입니다. 예를 들어:
$ mptcpize run curl https://check.mptcp.dev
You are using MPTCP.
또한 ss -Mia, tcpdump, wireshark 같은 도구를 사용하거나, nstat 또는 /proc/net/netstat에서 카운터를 확인할 수도 있습니다.
Linux 커널의 MPTCP에 기여하고 싶다면, Docker 이미지를 사용해 기본 커널 개발 환경을 만들 수 있습니다. 커널 소스 코드를 다운로드한 뒤 컨테이너를 시작하세요:
$ cd [kernel source code]
$ docker run -v "${PWD}:${PWD}:rw" -w "${PWD}" --privileged --rm -it \
--pull always mptcp/mptcp-upstream-virtme-docker:latest \
manual-normal
자세한 내용은 MPTCP Upstream Virtme Docker 페이지를 참고하세요.
MPTCP에서도 서브플로우 처리는 TCP 스택이 수행합니다. “일반” TCP와의 주요 차이는 이 처리가 소켓 백로그를 사용하지 않으며, Linux에서 bottom half (BH)라고 불리는 2차 인터럽트 핸들러에서 항상 이루어진다는 점입니다. 호스트에 부하가 높을 때 BH 처리는 ksoftirqd 컨텍스트에서 수행되며, ksoftirqd가 스케줄링된 시점과 실제로 핸들러를 실행하는 시점 사이에 지연이 발생할 수 있습니다. 이는 프로세스 스케줄러의 결정(및 설정)에 따라 달라집니다.
이러한 재전송을 줄이고 NIC 수준에서의 패킷 드롭을 피하는 한 가지 방법은 NIC RX 큐를 늘리는 것입니다. 자세한 내용은 이슈 #253을 참고하세요.
소프트웨어 Generic Receive Offload(GRO)는 MAC 헤더가 동일하고 특정 TCP 및 IP 헤더만 달라질 수 있을 때 패킷을 병합합니다. 즉, 연속하는 패킷들의 TCP 옵션이 동일하다면 병합할 수 있습니다.
MPTCP는 각 패킷에 TCP 옵션을 추가하지만, TCP Segmentation Offload(TSO)가 사용될 때는 동일한 MPTCP 옵션이 각 패킷에 복제됩니다. 이러한 패킷이 수신되고 다른 조건들이 충족되면, 수신 측 소프트웨어 GRO는 분할된 패킷들의 TCP 옵션이 모두 동일하므로 송신 측의 TSO 이전 원래 패킷을 재구성할 수 있습니다.
하드웨어 GRO의 경우, 이론적으로 NIC도 같은 기법을 사용할 수 있습니다. 그러나 많은 Intel 카드처럼 일부 NIC는 더 “보수적”이어서 TCP 타임스탬프 이외의 비지원 TCP 옵션이 포함된 TCP 패킷을 병합하지 않습니다. 이러한 NIC에서는 하드웨어 GRO가 MPTCP와 함께 동작할 수 없으며 소프트웨어 GRO로 폴백됩니다.
MPTCP 성능을 다른 프로토콜과 비교할 때, 두 경우 모두에서 하드웨어 GRO가 사용되는지 확인하는 것이 중요합니다.
현재까지 OpenSSH 관리자는 이 기능이 모든 플랫폼에서 제공되는 것이 아니라 Linux 배포판과 macOS에서만 제공된다는 이유로 “네이티브” MPTCP 지원을 추가하기를 원치 않습니다: [1][2]. 향후 네이티브 지원이 추가될 수도 있습니다. 그때까지는 다음과 같은 우회 방법이 있습니다:
systemctl edit ssh.socket으로 설정을 편집해 아래 줄을 추가하고, systemctl restart ssh.socket으로 소켓을 재시작하세요:[Socket]
SocketProtocol=mptcp
* 더 오래된 systemd 버전을 사용하거나, systemd가 SSH 소켓을 관리하지 않는 경우에는 먼저 SSH 소켓을 사용 중이라면 비활성화하세요:
systemctl disable --now ssh.socket
rm -f /etc/systemd/system/ssh.service.d/00-socket.conf
rm -f /etc/systemd/system/ssh.socket.d/addresses.conf
systemctl daemon-reload
systemctl enable --now ssh.service
그런 다음 SSH 서비스가 TCP 대신 MPTCP 소켓을 생성하도록 강제합니다:
mptcpize enable ssh.service
* 다른 시스템 매니저를 사용하는 경우 `sshd` 실행 앞에 `mptcpize run`을 접두로 붙이거나, `LD_PRELOAD`를 `libmptcpwrap.so.0.0.1`의 전체 경로로 설정하세요.
scp, rsync, git 등) 앞에 mptcpize run을 붙이세요. 예:mptcpize run ssh example.org
* 대안으로 `ProxyCommand` 옵션에 `mptcpize run`을 사용하도록 설정할 수 있습니다. 예를 들어 `~/.ssh/config` 파일에 다음 줄을 추가합니다:
Host (...)
ProxyCommand mptcpize run ssh -W %h:%p -l %r -p %p %h
이 프록시 커맨드는 ssh를 프록시로 한 번 더 실행하게 되어 효율이 떨어집니다. 하지만 모든 ssh 명령에 접두를 붙이지 않아도 되고, git, Nautilus 같은 파일 관리자, Filezilla 등 다른 도구에서 SSH를 사용할 때 유용합니다.