Транспорт TCP
Введение
TCP-транспорт является стандартным и наиболее фундаментальным транспортом в Xray-core. Он обеспечивает сырые TCP-соединения с опциональной безопасностью TLS/REALITY, обфускацией HTTP-заголовков и расширенной настройкой на уровне сокетов. В этом документе рассматриваются процессы dial/listen, поддержка параметров сокетов, механизм Happy Eyeballs для двухстекового соединения и поддержка прозрачного прокси (TProxy).
Регистрация протокола
TCP-транспорт регистрируется под именем "tcp" (transport/internet/tcp/tcp.go:3):
const protocolName = "tcp"Регистрация происходит в трех функциях init():
- Дайлер:
tcp/dialer.go:110-112--RegisterTransportDialer("tcp", Dial) - Слушатель:
tcp/hub.go:138-140--RegisterTransportListener("tcp", ListenTCP) - Создатель конфигурации:
tcp/config.go:8-12--RegisterProtocolConfigCreator("tcp", ...)
Процесс Dial
Точка входа
tcp.Dial (tcp/dialer.go:20-108) вызывается уровнем диспетчеризации транспорта:
func Dial(ctx context.Context, dest net.Destination,
streamSettings *internet.MemoryStreamConfig) (stat.Connection, error) {
conn, err := internet.DialSystem(ctx, dest, streamSettings.SocketSettings)
if err != nil {
return nil, err
}
// Применение TLS или REALITY...
// Применение аутентификатора заголовков...
return stat.Connection(conn), nil
}Применение уровня безопасности
После установления сырого TCP-соединения безопасность применяется в порядке приоритета (tcp/dialer.go:27-93):
- TLS: Если
tls.ConfigFromStreamSettingsвозвращает не nil, выполняется TLS-рукопожатие. Если настроен отпечаток, используется uTLS (tls.UClient), в противном случае -- стандартный Go TLS (tls.Client). - REALITY: Если
reality.ConfigFromStreamSettingsвозвращает не nil (и TLS не настроен), выполняется REALITY-рукопожатие черезreality.UClient.
Обфускация заголовков
После применения безопасности опциональный ConnectionAuthenticator оборачивает соединение (tcp/dialer.go:95-106):
tcpSettings := streamSettings.ProtocolSettings.(*Config)
if tcpSettings.HeaderSettings != nil {
headerConfig, _ := tcpSettings.HeaderSettings.GetInstance()
auth, _ := internet.CreateConnectionAuthenticator(headerConfig)
conn = auth.Client(conn)
}Это позволяет использовать обфускацию HTTP-заголовков, при которой TCP-поток оборачивается так, чтобы выглядеть как обычный HTTP-трафик.
Процесс Listen
Точка входа
tcp.ListenTCP (tcp/hub.go:30-95) создает TCP-слушатель:
func ListenTCP(ctx context.Context, address net.Address, port net.Port,
streamSettings *internet.MemoryStreamConfig, handler internet.ConnHandler) (internet.Listener, error) {
// ...
listener, err = internet.ListenSystem(ctx, &net.TCPAddr{
IP: address.IP(),
Port: int(port),
}, streamSettings.SocketSettings)
// Настройка TLS / REALITY / аутентификации заголовков
go l.keepAccepting()
return l, nil
}Цикл приема соединений
Горутина keepAccepting (tcp/hub.go:97-126) принимает соединения и применяет безопасность:
func (v *Listener) keepAccepting() {
for {
conn, err := v.listener.Accept()
// ...
go func() {
if v.tlsConfig != nil {
conn = tls.Server(conn, v.tlsConfig)
} else if v.realityConfig != nil {
conn, err = reality.Server(conn, v.realityConfig)
}
if v.authConfig != nil {
conn = v.authConfig.Server(conn)
}
v.addConn(stat.Connection(conn))
}()
}
}Поддержка Unix Domain Socket
И dial, и listen поддерживают Unix domain sockets. Слушатель проверяет port == 0 для переключения в режим Unix (tcp/hub.go:44-55).
Протокол PROXY
TCP-слушатель объединяет прием PROXY-протокола из конфигурации TCP и настроек сокета (tcp/hub.go:37-41):
streamSettings.SocketSettings.AcceptProxyProtocol =
l.config.AcceptProxyProtocol || streamSettings.SocketSettings.AcceptProxyProtocolПри включении internet.ListenSystem оборачивает слушатель в proxyproto.Listener (system_listener.go:170-173).
Системный дайлер: параметры сокетов
DefaultSystemDialer.Dial (transport/internet/system_dialer.go:51-146) -- это место, где создаются сырые соединения на уровне ОС. Он настраивает:
- Keep-Alive: Значения по умолчанию, аналогичные Chrome (45 секунд простоя, 45 секунд интервал) через
net.KeepAliveConfigGo 1.24+ (system_dialer.go:91-117) - Multipath TCP:
dialer.SetMultipathTCP(true)при установленномTcpMptcp(system_dialer.go:121-123) - Управление сокетом: Callback
Controlприменяет платформо-зависимые параметры черезapplyOutboundSocketOptions(system_dialer.go:124-143)
Параметры сокетов Linux (sockopt_linux.go)
Реализация для Linux (transport/internet/sockopt_linux.go:43-138) поддерживает:
| Параметр | Системный вызов | Поле конфигурации |
|---|---|---|
| SO_MARK | SOL_SOCKET, SO_MARK | config.Mark |
| SO_BINDTODEVICE | BindToDevice | config.Interface |
| TCP_FASTOPEN_CONNECT | SOL_TCP, TCP_FASTOPEN_CONNECT | config.Tfo (исходящие) |
| TCP_FASTOPEN | SOL_TCP, TCP_FASTOPEN | config.Tfo (входящие) |
| TCP_CONGESTION | SOL_TCP, TCP_CONGESTION | config.TcpCongestion |
| TCP_WINDOW_CLAMP | IPPROTO_TCP, TCP_WINDOW_CLAMP | config.TcpWindowClamp |
| TCP_USER_TIMEOUT | IPPROTO_TCP, TCP_USER_TIMEOUT | config.TcpUserTimeout |
| TCP_MAXSEG | IPPROTO_TCP, TCP_MAXSEG | config.TcpMaxSeg |
| IP_TRANSPARENT | SOL_IP, IP_TRANSPARENT | config.Tproxy |
| IPV6_RECVORIGDSTADDR | Различные | config.ReceiveOriginalDestAddress |
| Custom | Пользовательские level/opt | config.CustomSockopt |
Параметры сокетов macOS (sockopt_darwin.go)
Реализация для Darwin (transport/internet/sockopt_darwin.go:103-192) имеет платформо-зависимые отличия:
- TCP_FASTOPEN использует платформенные константы:
TCP_FASTOPEN_SERVER = 0x01,TCP_FASTOPEN_CLIENT = 0x02(sockopt_darwin.go:19-21) - Привязка к интерфейсу использует
IP_BOUND_IF/IPV6_BOUND_IFвместоSO_BINDTODEVICE(sockopt_darwin.go:136-151) - Прозрачный прокси использует
/dev/pfи ioctlDIOCNATLOOKдля определения исходного адреса назначения (sockopt_darwin.go:40-101)
Разбор значения TFO
Значение конфигурации TFO разбирается особым образом (transport/internet/sockopt.go:21-30):
func (v *SocketConfig) ParseTFOValue() int {
if v.Tfo == 0 { return -1 } // не задано
tfo := int(v.Tfo)
if tfo < 0 { tfo = 0 } // явно отключено
return tfo
}На Linux для исходящих соединений любое положительное значение становится 1 для TCP_FASTOPEN_CONNECT. На Linux для входящих соединений значение передается напрямую в TCP_FASTOPEN как длина очереди.
Пользовательские параметры сокетов
Механизм CustomSockopt (sockopt_linux.go:93-129) позволяет выполнять произвольные вызовы setsockopt:
for _, custom := range config.CustomSockopt {
if custom.System != "" && custom.System != runtime.GOOS { continue }
if !strings.HasPrefix(network, custom.Network) { continue }
// Применение int или string setsockopt
}Поддерживается фильтрация по ОС и типу сети (например, "tcp" соответствует tcp4/tcp6).
Happy Eyeballs (RFC 8305)
Когда разрешено несколько IP-адресов и включен Happy Eyeballs, TcpRaceDial (transport/internet/happy_eyeballs.go:16-97) реализует RFC 8305:
Сортировка IP-адресов
sortIPs (happy_eyeballs.go:100-156) чередует адреса IPv4 и IPv6:
func sortIPs(ips []net.IP, prioritizeIPv6 bool, interleave uint32) []net.IP {
// Разделение на массивы ip4 и ip6
// Чередование: попеременное ip4/ip6 на основе значения interleave
// prioritizeIPv6 определяет, какое семейство идет первым
}При значении interleave=1 по умолчанию результат выглядит так: [v6, v4, v6, v4, ...] (или [v4, v6, ...], если IPv6 не приоритизирован).
Алгоритм Race Dial
sequenceDiagram
participant C as TcpRaceDial
participant T as Timer
participant G1 as Горутина 1
participant G2 as Горутина 2
participant G3 as Горутина 3
C->>T: Старт (задержка 0 мс для первого)
T->>G1: tcpTryDial(IP[0])
T->>T: Reset(tryDelayMs)
T->>G2: tcpTryDial(IP[1])
G1-->>C: result{conn, nil}
C->>C: Отмена контекста (остановка остальных)
C->>C: Ожидание активных горутин
C-->>C: Возврат выигравшего соединенияКлючевые параметры из конфигурации HappyEyeballs:
- TryDelayMs: Задержка между началом каждой новой попытки (по умолчанию 250 мс согласно RFC 8305)
- MaxConcurrentTry: Максимальное количество одновременных попыток соединения
- PrioritizeIpv6: Должен ли IPv6 идти первым в чередующемся списке
- Interleave: Сколько адресов одного семейства перед переключением
Первое успешное соединение побеждает. Все остальные закрываются. Алгоритм корректно обрабатывает отмену и неудачи (happy_eyeballs.go:35-96).
Условия активации
Happy Eyeballs используется только когда выполнены все условия (dialer.go:263):
- Конфигурация HappyEyeballs не nil
- TryDelayMs > 0 и MaxConcurrentTry > 0
- Разрешено минимум 2 IP-адреса
- DialerProxy не настроен
- Назначение -- TCP
Прозрачный прокси (TProxy)
Linux
На Linux TProxy работает путем установки IP_TRANSPARENT на сокетах (sockopt_linux.go:131-135), что позволяет процессу привязываться к нелокальным адресам. Восстановление исходного адреса назначения использует SO_ORIGINAL_DST (tcp/sockopt_linux.go:18-52):
func GetOriginalDestination(conn stat.Connection) (net.Destination, error) {
// Использует getsockopt(SO_ORIGINAL_DST) для восстановления исходного адреса
// назначения из перенаправленного соединения (через iptables REDIRECT/TPROXY)
}macOS
На macOS исходный адрес назначения восстанавливается через PF (sockopt_darwin.go:40-101) с использованием ioctl DIOCNATLOOK на /dev/pf.
Системный слушатель
DefaultListener.Listen (transport/internet/system_listener.go:78-175) создает слушатели на уровне ОС с:
- Управление сокетом: Применяет
applyInboundSocketOptionsчерезgetControlFunc(system_listener.go:24-42) - SO_REUSEPORT: Всегда устанавливается на слушателях (
system_listener.go:39) - Unix domain sockets: Поддерживает абстрактные сокеты (префикс
@в Linux), права доступа к файлам и блокировку файлов (system_listener.go:115-166) - Протокол PROXY: Оборачивается
proxyproto.Listenerпри включении (system_listener.go:170-173) - Multipath TCP:
lc.SetMultipathTCP(true)при настройке (system_listener.go:111-113)
Примечания по реализации
- Протокол по умолчанию: Когда транспорт не указан, используется TCP (
config.go:59-63). - Обертка UDP: Для UDP-назначений
DefaultSystemDialerсоздаетPacketConnWrapper, реализующийnet.Connповерхnet.PacketConn(system_dialer.go:54-89). - Значения keep-alive по умолчанию: Дайлер повторяет поведение keep-alive Chrome: 45 секунд простоя + 45 секунд интервал (
system_dialer.go:91-96). Слушатели по умолчанию имеют keep-alive отключенным (system_listener.go:92). - FakePacketConn: Используется для обертки TCP-соединений как
PacketConnв сценариях QUIC-поверх-TCP (system_dialer.go:258-283). - Таймаут дайлера: Жестко задан как 16 секунд (
system_dialer.go:113). - REALITY на слушателе: Когда настроен REALITY, слушатель запускает горутину для
DetectPostHandshakeRecordsLens(tcp/hub.go:78).