Skip to content
이 페이지는 AI로 번역되어 부정확한 내용이 있을 수 있습니다. GitHub에서 개선에 참여해 주세요.

프로토콜 라우팅 ​

여러 서비스가 같은 공개 포트 하나를 함께 사용합니다(예: 웹사이트, VPN, SSH, 원격 데스크톱이 모두 443을 사용). Gately는 클라이언트가 연결 후 처음 보내는 데이터로 프로토콜을 식별하고, TLS는 핸드셰이크의 도메인(SNI)과 ALPN을, HTTP는 Host를 추가로 확인한 뒤 연결 전체를 해당 백엔드로 그대로 전달합니다. 여기서는 어떤 프로토콜도 복호화하거나 종료하지 않으므로 TLS 인증서는 백엔드에 그대로 둡니다. haproxy의 req_ssl_sni 규칙과 sslh를 합친 역할을 합니다.

도메인별 라우팅(SNI 라우팅)은 그중 한 경우입니다. TLS 라우트를 추가하고 도메인을 입력하면 됩니다.

각 규칙에는 다음 설정이 있습니다.

  • 수신: 프로토콜(TCP 전용, tcp는 듀얼 스택, tcp4 / tcp6는 단일 주소 체계), 수신 주소와 포트(443, 443,8443 또는 포트 범위)
  • 라우트: 각 라우트에는 프로토콜, 이름, 대상 주소(여러 개면 차례로 시도하며 장애 조치), 선택 사항인 대상 포트(비우면 수신 포트와 같음), PROXY 프로토콜 설정(보내지 않음, v1, v2)을 지정합니다. 백엔드에 따라 PROXY가 필수인 곳도 있고 거부하는 곳도 있으므로 PROXY 설정은 라우트별로 둡니다.
  • 기본 대상: 규칙 자체의 대상 주소, 대상 포트, PROXY 프로토콜 설정으로, 일치하는 라우트가 없거나 프로토콜을 식별하지 못할 때 사용합니다.
  • 식별 제한 시간: 클라이언트가 식별 가능한 데이터를 보내기를 기다리는 시간(초)입니다. 0이면 기본값 5초입니다.

식별 가능한 프로토콜 ​

라우트 프로토콜식별 근거추가 매칭
TLSTLS 핸드셰이크 레코드(ClientHello)도메인(SNI), ALPN
HTTP요청 줄의 메서드 이름과 HTTP/ 버전. CONNECT, WebDAV 메서드, HTTP/2 평문 직접 연결(h2c) 포함도메인(Host 헤더)
SSH클라이언트 버전 문자열 SSH-—
RDP 원격 데스크톱TPKT 헤더와 X.224 연결 요청—
SOCKS4 / SOCKS5SOCKS4, SOCKS4a 요청과 SOCKS5 메서드 협상—
OpenVPN (TCP)클라이언트 하드 리셋 패킷. tls-auth, tls-crypt, tls-crypt-v2 포함—
XMPPXML 선언 또는 <stream:stream—
MQTTCONNECT 패킷(3.1, 3.1.1, 5.0)—
PostgreSQL시작 메시지, SSLRequest, GSSENCRequest—
Minecraft(Java 에디션)핸드셰이크 패킷, 1.6 이하 버전의 서버 목록 핑서버 주소
RTSP요청 줄 버전 RTSP/1.0 또는 RTSP/2.0(IP 카메라, NVR)—
SIP요청 줄 버전 SIP/2.0(TCP)—
RedisRESP 배열로 보내는 명령—
AMQP프로토콜 헤더 AMQP(0-9-1 및 1.0, RabbitMQ 등)—
SMBNetBIOS 세션 메시지 안의 SMB1, SMB2, SMB3 협상 요청—
Gitgit:// 프로토콜의 첫 pkt-line(git daemon)—
IRC클라이언트가 처음 보내는 CAP, PASS, NICK 또는 USER 명령—
ADBAndroid 디버그 브리지의 CNXN 메시지(adb connect)—
BitTorrent암호화되지 않은 피어 핸드셰이크—
대기 시간 초과식별 제한 시간 안에 클라이언트가 아무것도 보내지 않음—

매칭 규칙 ​

TLS, HTTP, Minecraft 라우트는 도메인으로 매칭합니다(Minecraft는 클라이언트가 입력한 서버 주소를 핸드셰이크에서 읽고, Forge, BungeeCord, TCPShield가 덧붙인 데이터는 무시합니다). 정확한 도메인(vpn.example.com)이 우선이고, 다음은 가장 긴 와일드카드(*.example.com, 모든 단계와 일치), 그다음 *(도메인을 포함한 모든 연결), 마지막은 도메인을 비운 라우트(SNI나 Host가 없는 연결을 포함해 해당 프로토콜의 모든 연결)입니다. 도메인은 대소문자를 구분하지 않습니다. 저장할 때 도메인은 소문자로 바꾸고 국제화 도메인은 punycode로 변환하며, 포트·경로·@가 들어간 값은 거부합니다.

TLS 라우트에는 ALPN도 지정할 수 있으며, 클라이언트가 제시한 ALPN에 그중 하나가 있을 때만 일치합니다. 같은 도메인에서는 ALPN을 지정한 라우트가 우선하고, ALPN이 맞지 않으면 더 넓은 도메인으로 계속 찾습니다. 예를 들어 acme-tls/1은 TLS-ALPN-01 인증서 검증에 응답하는 프로그램으로, postgresql은 PostgreSQL 17의 직접 TLS 연결로 보낼 수 있습니다. 같은 프로토콜 안에서 도메인 하나(같은 ALPN 포함)는 라우트 하나에만 속할 수 있고, 다른 프로토콜은 종류마다 라우트 하나만 둘 수 있습니다.

「대기 시간 초과」는 VNC, MySQL, FTP, SMTP, POP3, IMAP처럼 서버가 먼저 데이터를 보내는 프로토콜용입니다. 이런 클라이언트는 연결한 뒤 서버를 기다리므로 Gately에는 아무 데이터도 오지 않고, 어떤 프로토콜인지 구분할 수 없습니다. 그래서 포트마다 그중 하나만 「대기 시간 초과」 라우트로 보낼 수 있고, 이런 연결은 식별 제한 시간이 지나야 전달됩니다. 식별 제한 시간은 소수점 한 자리까지 입력할 수 있습니다. TLS, SSH, HTTP 등의 클라이언트는 핸드셰이크 후 몇 밀리초 안에 첫 패킷을 보내므로, 「대기 시간 초과」 라우트가 있으면 0.3~1초면 충분하고 VNC 같은 연결도 그만큼만 기다립니다.

다음 연결은 기본 대상으로 갑니다. 일치하는 라우트가 없는 경우, 데이터가 위 표의 어떤 프로토콜에도 해당하지 않는 경우(예: Shadowsocks, VMess, 암호화를 켠 BitTorrent처럼 특징이 없는 프로토콜, MongoDB, DNS over TCP처럼 첫 패킷이 길이 필드뿐이라 다른 프로토콜과 혼동하기 쉬운 프로토콜), ClientHello를 파싱할 수 없는 경우, 「대기 시간 초과」 라우트가 없는데 클라이언트가 아무것도 보내지 않는 경우입니다. 기본 대상이 없으면 이런 연결은 끊고 「거부」로 집계합니다. 식별이 끝나기 전에 연결을 끊은 클라이언트는 버립니다.

참고 ​

  • TCP 전용입니다. HTTP/3(QUIC), WireGuard 같은 UDP 트래픽은 라우팅되지 않으므로 별도의 규칙을 사용하세요.
  • SIP, Redis, AMQP, IRC 등의 암호화 버전은 TLS 위에서 동작하므로 TLS 라우트에서 도메인이나 ALPN으로 나누세요.
  • ClientHello는 여러 TLS 레코드로 나뉠 수 있으며 최대 64 KB까지 허용합니다. HTTP, RTSP, SIP의 요청 줄은 최대 8 KB이고, HTTP는 Host를 찾기 위해 요청 헤더를 최대 32 KB까지 읽습니다.
  • 연결마다 한 번만 식별하므로, 같은 HTTP keep-alive 연결의 이후 요청은 처음 선택된 백엔드로 갑니다.
  • 규칙 카드에 각 라우트와 기본 대상이 받은 연결 수가 표시되고, 실행 로그의 디버그 항목에는 연결마다 식별한 프로토콜, SNI, ALPN 또는 Host가 기록됩니다.
  • 규칙을 수정해 핫 업데이트할 때(메모, 라우트, 기본 대상, 출발지 IP 목록 등) 프로토콜·도메인·ALPN이 그대로인 라우트는 히트 수를 유지하며, 이동하거나 이름이나 대상을 바꿔도 유지됩니다. 새 라우트와 프로토콜·도메인·ALPN이 바뀐 라우트는 0부터 시작하고, 규칙을 비활성화했다가 다시 활성화하거나 재시작하면 모두 0부터 시작합니다.
  • 라우트, 기본 대상, 식별 제한 시간을 바꾸면 수신을 멈추지 않고 바로 적용되며, 열린 연결은 그대로 유지됩니다.
  • 각 규칙에 출발지 IP 허용 목록 또는 차단 목록(Web 서비스의 구독 IP 목록 참조 가능)을 설정할 수 있으며, 프로토콜을 식별하기 전에 판단합니다. 조건을 만족하지 않는 연결은 데이터를 읽지 않고 닫으며 경로 적중에도 포함하지 않습니다. 침입 방지의 차단이 우선합니다. 목록을 수정해도 수신 대기는 중단되지 않고, 규칙 카드에 거부 횟수를 표시합니다. 사용법은 포트 포워딩과 같습니다.

예: haproxy 대체 ​

다음과 같은 haproxy 설정은

text
frontend https
    bind 192.0.2.10:443
    mode tcp
    tcp-request inspect-delay 5s
    tcp-request content accept if { req_ssl_hello_type 1 }
    use_backend ocserv if { req_ssl_sni -i tz.example.com }
    default_backend web
backend ocserv
    server ocserv 127.0.0.1:443 send-proxy-v2
backend web
    server web 127.0.0.1:8443 send-proxy-v2

Gately에서는 규칙 하나가 됩니다.

설정값
프로토콜TCP
수신 주소 / 포트192.0.2.10 / 443
라우트 ocserv프로토콜 TLS, 도메인 tz.example.com, 대상 127.0.0.1, 포트 443, PROXY v2
기본 대상127.0.0.1, 대상 포트 8443, PROXY v2

127.0.0.1:443에서 수신하는 백엔드와 충돌하지 않도록 수신 주소를 서버의 공인 주소로 지정하세요. 웹사이트를 Gately가 직접 제공하려면 127.0.0.1:8443에 웹 서비스 리스너를 추가하고 「PROXY 프로토콜 수신」을 켠 뒤 신뢰할 PROXY 출처에 127.0.0.0/8을 추가합니다. 포트를 수신할 수 있는 프로그램은 하나뿐이므로 규칙을 켜기 전에 haproxy를 중지하세요.

예: sslh 대체 ​

다음과 같은 sslh 실행 인수는

text
sslh --listen 0.0.0.0:443 --timeout 2 \
     --ssh 127.0.0.1:22 --openvpn 127.0.0.1:1194 --tls 127.0.0.1:8443

Gately에서는 규칙 하나가 됩니다.

설정값
프로토콜TCP
수신 주소 / 포트비움 / 443
식별 제한 시간2
라우트 ssh프로토콜 SSH, 대상 127.0.0.1, 포트 22
라우트 vpn프로토콜 OpenVPN, 대상 127.0.0.1, 포트 1194
라우트 web프로토콜 TLS, 도메인 비움, 대상 127.0.0.1, 포트 8443
라우트 vnc(선택)프로토콜 「대기 시간 초과」, 대상 127.0.0.1, 포트 5900

sslh는 시간 초과 후 기본적으로 연결을 SSH로 넘깁니다. Gately는 SSH- 버전 문자열로 SSH를 바로 식별하므로, 시간 초과된 연결은 VNC처럼 서버가 먼저 보내는 서비스로 보낼 수 있습니다. RDP, SOCKS, HTTP 라우트를 더 추가하거나 도메인별로 TLS를 여러 백엔드에 나눌 수도 있습니다.

포트 포워딩에서 업그레이드 ​

프로토콜 라우팅은 원래 포트 포워딩 규칙의 「SNI 라우팅」 모드였습니다. 업그레이드 후 처음 시작할 때 포트 포워딩의 SNI 라우팅 규칙은 프로토콜 라우팅 모듈로 자동으로 옮겨집니다. 각 규칙의 key, 이름, 활성화 상태, 그룹, 모든 라우트는 그대로 유지되고, 기존 라우트는 모두 TLS 라우트가 됩니다. 포트 포워딩 모듈이 꺼져 있었다면 프로토콜 라우팅 모듈도 함께 꺼지며, 포트 포워딩 권한이 있는 API 토큰은 프로토콜 라우팅에도 같은 수준의 권한을 받습니다.

옮겨진 뒤에는 /api/protoroute/rules로 이 규칙을 관리하세요. 라우트 필드 이름은 sniRoutes에서 routes로 바뀌었고 mode 필드는 더 이상 쓰지 않습니다. /api/portforward/rules에 라우팅 규칙을 제출하면 거부됩니다. 모니터링 지표도 gately_protoroute_*로 바뀌었습니다. 모니터링 지표를 참고하세요.

배포 팁 ​

프로토콜 라우팅은 호스트 포트를 직접 수신하므로 Docker 배포에서는 --network host를 권장합니다.

그룹과 정렬 ​

규칙에 "그룹"을 지정할 수 있습니다. 그룹, 정렬, 검색은 포트 포워딩과 같은 방식으로 동작하며, 검색 상자로 이름, 그룹, 수신 포트, 대상, 도메인 또는 라우트 프로토콜을 기준으로 거를 수 있습니다.

Apache-2.0 라이선스로 공개되어 있습니다.