This document explains how the frp client (frpc) establishes and maintains network connections to the frp server (frps). It covers the Connector abstraction, transport protocol selection (TCP, KCP, QUIC, WebSocket), connection multiplexing strategies, and the complete connection lifecycle from establishment to termination.
For information about how connections are used after establishment, see Control and Work Connections. For authentication and security aspects of connections, see Authentication and Security. For transport-specific configuration options, see Transport Protocol Options.
The Connector interface provides a unified abstraction for establishing connections to the frp server, regardless of the underlying transport protocol. This design allows the client to support multiple transport mechanisms through a single, consistent API.
Connector Interface Methods
| Method | Purpose | Behavior |
|---|---|---|
Open() | Establishes the underlying connection | Creates TCP connection, QUIC connection, or yamux session depending on configuration |
Connect() | Returns a connection stream | Returns a stream from the underlying connection, or creates a new TCP connection if multiplexing is disabled |
Close() | Closes the underlying connection | Safely closes QUIC connection or yamux session |
The interface is defined at client/connector.go40-44:
Sources: client/connector.go40-44 client/connector.go80-87
The frp client supports several transport protocols, each with different characteristics and use cases:
Transport Protocol Characteristics
| Protocol | Multiplexing | TLS | Firewall Traversal | Latency | Use Case |
|---|---|---|---|---|---|
tcp | Optional (yamux) | Optional | Standard | Low | Default, most compatible |
kcp | Optional (yamux) | Optional | Good (UDP) | Low (Lossy) | High-latency/lossy networks |
websocket | Optional (yamux) | Optional | Good (HTTP) | Low | Firewalls blocking custom ports |
wss | Optional (yamux) | Required | Excellent (HTTPS) | Low | Strict firewall environments |
quic | Native | Required | Good (UDP) | Very Low | High-performance, mobile networks |
The protocol is determined by the Transport.Protocol configuration field and processed in the realConnect() and Open() methods.
Sources: client/connector.go104-139 client/connector.go183-214
The connection establishment follows a two-phase process: Open() creates the underlying transport, and Connect() returns usable streams.
Connection Modes
The Connector operates in one of three modes depending on configuration:
QUIC Mode (c.quicConn != nil):
Open() establishes a QUIC connection at client/connector.go104-139Connect() opens a new QUIC stream at client/connector.go166-171TCPMux Mode (c.muxSession != nil):
Open() establishes a physical connection and creates a yamux session at client/connector.go145-161Connect() opens a new yamux stream at client/connector.go172-177Direct Mode (both nil):
Open() does nothing if TCPMux is disabled client/connector.go141-143Connect() creates a new connection each time at client/connector.go180Sources: client/connector.go100-162 client/connector.go165-181
When Transport.TCPMux is enabled and the protocol is not QUIC, the client uses the yamux library to multiplex multiple logical streams over a single physical connection. This reduces connection overhead and improves performance.
yamux Configuration
The yamux session is configured with optimized parameters at client/connector.go150-154:
| Parameter | Value | Purpose |
|---|---|---|
KeepAliveInterval | Configurable | Sends keep-alive pings to detect connection failures |
MaxStreamWindowSize | 6 MB | Flow control window size per stream for high throughput |
LogOutput | Trace writer | Forwards yamux internal logs to frp's logging system at Trace level pkg/util/xlog/log_writer.go31-35 |
Sources: client/connector.go150-161 pkg/util/xlog/log_writer.go31-35
QUIC provides native multiplexing and built-in TLS, making it ideal for high-performance scenarios. When QUIC is enabled, the client bypasses yamux entirely and uses QUIC's native stream multiplexing.
QUIC Configuration Parameters
The QUIC connection is configured with parameters from Transport.QUIC at client/connector.go129-133:
| Parameter | Config Field | Purpose |
|---|---|---|
MaxIdleTimeout | Transport.QUIC.MaxIdleTimeout | Time before idle connection closes |
MaxIncomingStreams | Transport.QUIC.MaxIncomingStreams | Maximum concurrent streams server can initiate |
KeepAlivePeriod | Transport.QUIC.KeepalivePeriod | Interval for keep-alive packets |
TLS Requirements
QUIC mandates TLS. The TLS configuration is built at client/connector.go111-124:
Transport.TLS.Enable is true, uses custom certificates.NextProtos = []string{"frp"} to identify the protocol client/connector.go124Stream to net.Conn Conversion
QUIC streams are converted to net.Conn interface at client/connector.go171 using netpkg.QuicStreamToNetConn(), which wraps the QUIC stream to implement the full net.Conn interface.
Sources: client/connector.go104-139 client/connector.go166-171
WebSocket transport allows frp to work through HTTP-aware firewalls and proxies. The client uses WebSocket upgrade hooks to transform TCP connections into WebSocket connections.
WebSocket Implementation
The client uses DialHookWebsocket at pkg/util/net/dial.go24-55 to perform the upgrade. The tunnel payload is sent as websocket.BinaryFrame to avoid UTF-8 validation by intermediaries pkg/util/net/dial.go52
Sources: client/connector.go216-227 pkg/util/net/dial.go24-55
TLS can be enabled for all transport protocols. The frp protocol uses a custom first byte to distinguish TLS connections from non-TLS connections, which is applied via DialHookCustomTLSHeadByte at pkg/util/net/dial.go12-22
The FRPTLSHeadByte is written to the connection if TLS is enabled and not explicitly disabled via DisableCustomTLSFirstByte pkg/util/net/dial.go14-19
Sources: client/connector.go187-206 pkg/util/net/dial.go12-22
For higher-level components like the Control manager, frp provides a MessageConnector which wraps the raw Connector to provide structured message communication.
The messageConnector handles the initial handshake (writing the magic byte for V2 protocols) and wraps the resulting connection in a msg.Conn for structured message passing client/connector.go63-73
Sources: client/connector.go51-73
The Connector manages the complete lifecycle of the underlying connection, from establishment through cleanup.
Thread Safety
The Close() method uses sync.Once at client/connector.go230-244 to guarantee idempotency and thread-safe cleanup of the quicConn or muxSession.
Sources: client/connector.go217-245 client/connector.go80-94