frp — Interfaces#

Interface catalog#

Connector#

  • Package: client
  • File: client/connector.go:38
  • Methods:
    Open() error
    Connect() (net.Conn, error)
    Close() error
  • Purpose: Abstracts the physical connection from frpc to frps. Open() establishes the underlying connection or session (TCP, KCP, QUIC, or yamux); Connect() returns a stream from it (or a fresh TCP conn if TCPMux is disabled). This two-phase design allows yamux/QUIC to share one underlying connection across many logical streams.
  • Implementations:
    • defaultConnectorImpl — the real implementation in client/connector.go; handles TCP/TLS/WebSocket/KCP/QUIC and yamux session management.
    • VirtualConnector (in pkg/virtual) — in-process pipe-based connector used by VirtualClient for in-process frp tunnels and the WireGuard vnet overlay.
  • Design quality: Well-segregated. Three methods cleanly express the lifecycle. The separation of Open (session establishment) from Connect (stream acquisition) is a subtle but important design choice that enables mux protocols.

MessageTransporter#

  • Package: pkg/transport
  • File: pkg/transport/message.go:27
  • Methods:
    Send(msg.Message) error
    Do(ctx context.Context, req msg.Message, laneKey, recvMsgType string) (msg.Message, error)
    Dispatch(m msg.Message, laneKey string) bool
    DispatchWithType(m msg.Message, msgType, laneKey string) bool
  • Purpose: Provides HTTP/2-style multiplexed request-response over the single control connection. Do() sends a request and blocks until a matching response arrives on the given laneKey. Dispatch()/DispatchWithType() route incoming responses to the correct waiting goroutine by (msgType, laneKey) pair. Allows many concurrent NewProxy/NewProxyResp exchanges on one stream without ordering constraints.
  • Implementations:
    • transporterImpl — the only implementation; uses a sync.RWMutex-protected map[msgType]map[laneKey]chan msg.Message as the lane registry.
  • Design quality: Well-designed. Do() is the clean entry-point for one-shot request-response. The lane-key mechanism is clever and avoids goroutine-per-request overhead. Slightly overloaded (4 methods, two of which are implementation-detail variants of Dispatch), but this is justified by the need to support both reflection-based and explicit type dispatch.

MessageSender#

  • Package: pkg/transport
  • File: pkg/transport/message.go:38
  • Methods:
    Send(msg.Message) error
  • Purpose: Minimal dependency interface that MessageTransporter consumes. Decouples the transporter from the concrete dispatcher (msg.Dispatcher), enabling substitution (e.g., in tests).
  • Implementations: msg.Dispatcher satisfies this interface (it has Send(msg.Message) error).
  • Design quality: Excellent single-method interface. Follows the Go stdlib idiom of defining narrow dependency interfaces at the point of consumption.

client/proxy.Proxy#

  • Package: client/proxy
  • File: client/proxy/proxy.go:49
  • Methods:
    Run() error
    InWorkConn(net.Conn, *msg.StartWorkConn)
    SetInWorkConnCallback(func(*v1.ProxyBaseConfig, net.Conn, *msg.StartWorkConn) bool)
    Close()
  • Purpose: Client-side data-plane contract. Run() initializes the proxy handler (e.g., opens a local listener for HTTP/HTTPS proxies, sets up plugin). InWorkConn() is called by the manager when the server requests a work connection — the proxy joins the work connection to the local service. SetInWorkConnCallback allows the VirtualClient to intercept connections before forwarding.
  • Implementations: BaseProxy provides default TCP handling; proxy-type structs (TCPProxy, HTTPProxy, UDPProxy, STCPProxy, XTCPProxy, etc.) embed BaseProxy and override InWorkConn for their specific protocol needs. New types are registered via RegisterProxyFactory(reflect.Type, func).
  • Design quality: Clean four-method interface. The callback hook (SetInWorkConnCallback) is an escape hatch for the VirtualClient SDK use-case — slightly awkward as a public interface method, but avoids requiring a separate interface variant.

server/proxy.Proxy#

  • Package: server/proxy
  • File: server/proxy/proxy.go:49
  • Methods:
    Context() context.Context
    Run() (remoteAddr string, err error)
    GetName() string
    GetConfigurer() v1.ProxyConfigurer
    GetWorkConnFromPool(src, dst net.Addr) (workConn net.Conn, err error)
    GetUsedPortsNum() int
    GetResourceController() *controller.ResourceController
    GetUserInfo() plugin.UserInfo
    GetLimiter() *rate.Limiter
    GetLoginMsg() *msg.Login
    Close()
  • Purpose: Server-side data-plane contract. Run() binds the remote port (or registers a vhost route) and returns the allocated address. GetWorkConnFromPool() is called by the type-specific handler to acquire a work connection from frpc when a user connection arrives. The many Get* accessors expose shared state to concrete sub-types without inheritance.
  • Implementations: BaseProxy provides all accessors and GetWorkConnFromPool/Close; concrete types (TCPProxy, HTTPProxy, UDPProxy, etc.) embed BaseProxy and implement Run(). Registered via RegisterProxyFactory.
  • Design quality: Somewhat broad (11 methods). The Get* methods exist primarily to expose BaseProxy fields to concrete sub-types — this is a common Go pattern for “inheritance via embedding” but it leaks implementation structure into the interface. An interface this size is harder to satisfy in tests; a narrower accessor interface per concern would be more ISP-compliant.

Visitor#

  • Package: client/visitor
  • File: client/visitor/visitor.go:54
  • Methods:
    Run() error
    AcceptConn(conn net.Conn) error
    Close()
  • Purpose: Client-side P2P tunnel endpoint. Visitors expose a local listener that other processes connect to; the visitor forwards traffic to the remote private service via a secret-key-authenticated connection to frps (STCP) or a direct NAT hole-punch (XTCP). AcceptConn allows the visitor plugin system to inject connections programmatically.
  • Implementations: STCPVisitor, XTCPVisitor, SUDPVisitor, all embedding BaseVisitor.
  • Design quality: Well-segregated. Three methods cleanly model the lifecycle plus connection injection.

visitor.Helper#

  • Package: client/visitor
  • File: client/visitor/visitor.go:39
  • Methods:
    ConnectServer() (net.Conn, error)
    TransferConn(string, net.Conn) error
    MsgTransporter() transport.MessageTransporter
    VNetController() *vnet.Controller
    RunID() string
  • Purpose: Dependency interface that gives Visitor implementations access to the client control session without creating a circular import. Defined by the visitor package (consumer), satisfied by client.Control. This is the classic Go “define interfaces at the point of use” pattern.
  • Implementations: client.Control satisfies this interface.
  • Design quality: Good. Avoids the visitor package importing the client package directly. Five methods span three distinct concerns (connectivity, messaging, identity) — could be split further, but the cohesion is reasonable given the limited number of consumers.

client/plugin.Plugin#

  • Package: pkg/plugin/client
  • File: pkg/plugin/client/plugin.go:66
  • Methods:
    Name() string
    Handle(ctx context.Context, connInfo *ConnectionInfo)
    Close() error
  • Purpose: Client-side connection interceptor. When a proxy has a plugin configured, the plugin’s Handle() receives the work connection (wrapped with optional encryption/compression) instead of the default dial-to-local-service behavior. Used for built-in plugins: http_proxy, socks5, static_file, unix_domain_socket, tls2raw, http2https, https2http, and the vnet WireGuard tun adapter.
  • Implementations: All built-in plugins in pkg/plugin/client/ register themselves via Register(name, CreatorFn).
  • Design quality: Clean three-method plugin interface. The ConnectionInfo struct (carries the wrapped conn, underlying conn, and PROXY protocol header) is a well-chosen value type rather than interface, keeping Handle simple.

server/plugin.Plugin#

  • Package: pkg/plugin/server
  • File: pkg/plugin/server/plugin.go:32
  • Methods:
    Name() string
    IsSupport(op string) bool
    Handle(ctx context.Context, op string, content any) (res *Response, retContent any, err error)
  • Purpose: Server-side lifecycle webhook. The server calls registered plugins at six lifecycle points (Login, NewProxy, CloseProxy, Ping, NewWorkConn, NewUserConn). IsSupport allows a plugin to declare which operations it handles. The current implementation sends HTTP webhook requests to external URLs — the interface is the internal abstraction for that HTTP client.
  • Implementations: One implementation in pkg/plugin/server/http.go that POSTs JSON to a configured URL.
  • Design quality: The any parameter and return types for content/retContent sacrifice static typing for generality across the six different event shapes. Reasonable trade-off given the small number of concrete callers, but hurts readability.

Interface patterns#

  • Size distribution: Mostly small (1–5 methods). The server Proxy interface at 11 methods is the outlier and shows signs of “fat interface” syndrome driven by embedding-based pseudo-inheritance. All plugin interfaces are lean (3 methods).
  • Embedding: No interface-embedding (composition of smaller interfaces into larger ones) is used. Each interface is defined standalone.
  • Implicit satisfaction: frp follows the Go idiom of defining interfaces at the consumer, not the provider. Helper is defined in the visitor package (consumer); MessageSender is defined in transport (consumer of a dispatcher). None of the concrete types implement an interface explicitly — all satisfaction is implicit.
  • stdlib interfaces used: net.Conn is the fundamental abstraction throughout the data plane (Connect(), InWorkConn(), AcceptConn(), GetWorkConnFromPool()). io.ReadWriteCloser is used for wrapped work connections (encryption + compression layers). context.Context appears in all async-capable interfaces.

Key abstractions#

  1. Connector — The hinge point between transport protocols and the rest of the system. Its three-method API hides a complex decision tree (QUIC vs yamux vs raw TCP vs WebSocket) and enables the in-process VirtualClient SDK use-case without any changes to control or proxy logic.

  2. MessageTransporter — The control-plane multiplexer. Without this abstraction, concurrent proxy registrations on the same connection would require serialization or connection-per-proxy. The lane-key design allows HTTP/2-style parallel request-response which is essential for frp’s scale.

  3. server/proxy.Proxy — The server-side data-plane extension point. Every proxy type (TCP, HTTP, HTTPS, UDP, STCP, SUDP, XTCP) implements this interface. Combined with the RegisterProxyFactory registry, it makes adding a new proxy type a matter of implementing one interface and one init() call.

  4. client/proxy.Proxy — The symmetric client-side data-plane contract. The InWorkConn method is the key dispatch point: when the server requests a new work connection, the proxy manager finds the right Proxy by name and calls InWorkConn, which joins the network streams. The SetInWorkConnCallback hook is the SDK extension point.

  5. Visitor + Helper — Together these define the P2P tunnel subsystem. Helper is the dependency-inversion boundary that decouples visitor implementations from the client control session. Visitor is the uniform runtime handle for STCP/XTCP/SUDP — the Manager treats all three identically.

Interface-driven extensibility#

frp achieves extensibility at three levels:

Transport: Connector is injected via ServiceOptions.ConnectorCreator (a func(context.Context, *v1.ClientCommonConfig) Connector). The VirtualClient in pkg/virtual provides a pipe-based Connector that runs an entire frpc inside a process, enabling SDK usage and the WireGuard vnet overlay.

Proxy types: Both client/proxy and server/proxy use a global factory registry (map[reflect.Type]func(...) Proxy) populated by init() calls in each proxy-type file. This is a registry pattern that decouples the proxy manager from concrete proxy types, allowing new types to be added without modifying the manager.

Plugins: The client plugin system (pkg/plugin/client) and server plugin system (pkg/plugin/server) each have their own Plugin interface and Register/Create factory functions. Client plugins intercept connections before local forwarding; server plugins receive HTTP webhook calls at six lifecycle events. The visitor plugin system (pkg/plugin/visitor) adds a third layer for the P2P visitor subsystem.