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 inclient/connector.go; handles TCP/TLS/WebSocket/KCP/QUIC and yamux session management.VirtualConnector(inpkg/virtual) — in-process pipe-based connector used byVirtualClientfor in-process frp tunnels and the WireGuardvnetoverlay.
- Design quality: Well-segregated. Three methods cleanly express the lifecycle. The separation of
Open(session establishment) fromConnect(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 givenlaneKey.Dispatch()/DispatchWithType()route incoming responses to the correct waiting goroutine by (msgType, laneKey) pair. Allows many concurrentNewProxy/NewProxyRespexchanges on one stream without ordering constraints. - Implementations:
transporterImpl— the only implementation; uses async.RWMutex-protectedmap[msgType]map[laneKey]chan msg.Messageas 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
MessageTransporterconsumes. Decouples the transporter from the concrete dispatcher (msg.Dispatcher), enabling substitution (e.g., in tests). - Implementations:
msg.Dispatchersatisfies this interface (it hasSend(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.SetInWorkConnCallbackallows theVirtualClientto intercept connections before forwarding. - Implementations:
BaseProxyprovides default TCP handling; proxy-type structs (TCPProxy,HTTPProxy,UDPProxy,STCPProxy,XTCPProxy, etc.) embedBaseProxyand overrideInWorkConnfor their specific protocol needs. New types are registered viaRegisterProxyFactory(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 manyGet*accessors expose shared state to concrete sub-types without inheritance. - Implementations:
BaseProxyprovides all accessors andGetWorkConnFromPool/Close; concrete types (TCPProxy,HTTPProxy,UDPProxy, etc.) embedBaseProxyand implementRun(). Registered viaRegisterProxyFactory. - Design quality: Somewhat broad (11 methods). The
Get*methods exist primarily to exposeBaseProxyfields 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).
AcceptConnallows the visitor plugin system to inject connections programmatically. - Implementations:
STCPVisitor,XTCPVisitor,SUDPVisitor, all embeddingBaseVisitor. - 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
Visitorimplementations access to the client control session without creating a circular import. Defined by the visitor package (consumer), satisfied byclient.Control. This is the classic Go “define interfaces at the point of use” pattern. - Implementations:
client.Controlsatisfies this interface. - Design quality: Good. Avoids the visitor package importing the
clientpackage 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 viaRegister(name, CreatorFn). - Design quality: Clean three-method plugin interface. The
ConnectionInfostruct (carries the wrapped conn, underlying conn, and PROXY protocol header) is a well-chosen value type rather than interface, keepingHandlesimple.
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).IsSupportallows 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.gothat POSTs JSON to a configured URL. - Design quality: The
anyparameter and return types forcontent/retContentsacrifice 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
Proxyinterface 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.
Helperis defined in thevisitorpackage (consumer);MessageSenderis defined intransport(consumer of a dispatcher). None of the concrete types implement an interface explicitly — all satisfaction is implicit. - stdlib interfaces used:
net.Connis the fundamental abstraction throughout the data plane (Connect(),InWorkConn(),AcceptConn(),GetWorkConnFromPool()).io.ReadWriteCloseris used for wrapped work connections (encryption + compression layers).context.Contextappears in all async-capable interfaces.
Key abstractions#
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-processVirtualClientSDK use-case without any changes to control or proxy logic.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.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 theRegisterProxyFactoryregistry, it makes adding a new proxy type a matter of implementing one interface and oneinit()call.client/proxy.
Proxy— The symmetric client-side data-plane contract. TheInWorkConnmethod is the key dispatch point: when the server requests a new work connection, the proxy manager finds the rightProxyby name and callsInWorkConn, which joins the network streams. TheSetInWorkConnCallbackhook is the SDK extension point.Visitor+Helper— Together these define the P2P tunnel subsystem.Helperis the dependency-inversion boundary that decouples visitor implementations from the client control session.Visitoris the uniform runtime handle for STCP/XTCP/SUDP — theManagertreats 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.