frp — Structure#
Layout pattern#
Standard Go Layout (cmd/internal-like/pkg) — Dual-binary variant
frp follows a variant of the standard Go layout: cmd/ holds the two entry-point binaries (frpc and frps), pkg/ holds shared libraries, and the domain-specific business logic lives in top-level package trees (client/ and server/) rather than in internal/. There is no internal/ directory; instead, the separation between client-domain and server-domain code is enforced by convention and clear naming. The build uses build tags (frpc / frps / noweb) to conditionally include the web dashboard assets in each binary.
Directory map#
frp/
├── cmd/
│ ├── frpc/ # frpc binary entry point (Cobra-based)
│ │ ├── main.go # wires system compat + sub.Execute()
│ │ └── sub/ # Cobra sub-commands: root, admin, proxy, nathole, verify
│ └── frps/ # frps binary entry point (Cobra-based)
│ ├── main.go # wires system compat + Execute()
│ ├── root.go # cobra root command, config loading, server.NewService()
│ └── verify.go # config verify sub-command
│
├── client/ # Client-side domain logic
│ ├── service.go # Service: top-level client lifecycle
│ ├── control.go # Control: persistent frpc↔frps control channel
│ ├── connector.go # Connector: manages underlying transport connections
│ ├── config_manager.go # Hot-reload config management
│ ├── api_router.go # Admin HTTP API routing
│ ├── configmgmt/ # Config manager helpers
│ ├── event/ # Internal event bus (proxy lifecycle events)
│ ├── health/ # Health check for TCP/HTTP proxied services
│ ├── http/ # Client-side HTTP model types
│ ├── proxy/ # Proxy type implementations (TCP, UDP, HTTP, STCP, …)
│ └── visitor/ # Visitor implementations (STCP, XTCP, SUDP visitors)
│
├── server/ # Server-side domain logic
│ ├── service.go # Service: top-level server lifecycle
│ ├── control.go # Control: per-client control session on server side
│ ├── api_router.go # Dashboard/admin HTTP API routing
│ ├── controller/ # Resource manager (proxy/visitor resource allocation)
│ ├── group/ # Proxy group management (TCP mux, HTTP mux groups)
│ ├── http/ # Server-side HTTP model types
│ ├── metrics/ # Server metrics collection
│ ├── ports/ # Port manager (allocates/tracks bound ports)
│ ├── proxy/ # Server-side proxy handler implementations
│ ├── registry/ # In-memory registries (proxy, user, listener)
│ └── visitor/ # Server-side visitor listener management
│
├── pkg/ # Shared/reusable packages
│ ├── auth/ # Auth methods (token, OIDC, mTLS); legacy subdir
│ ├── config/ # Config loading/parsing; v1/ (current), legacy/ (ini)
│ │ ├── v1/ # Current YAML/TOML/JSON config schema + validation
│ │ ├── legacy/ # Legacy ini-format config parsing
│ │ ├── source/ # Config source abstraction (file, env, flags)
│ │ └── types/ # Shared config value types (Duration, BandwidthQuantity)
│ ├── errors/ # Sentinel error values
│ ├── metrics/ # Metrics facade (aggregate, mem, prometheus backends)
│ ├── msg/ # Wire message types (frpc↔frps control protocol structs)
│ ├── naming/ # Naming utilities (proxy/tunnel names)
│ ├── nathole/ # NAT hole-punching (STUN-based, coordination protocol)
│ ├── plugin/ # Plugin definitions: client/, server/, visitor/
│ ├── policy/ # Security policy: featuregate/ and security/ (unsafe features)
│ ├── proto/ # Protocol helpers: udp/ (UDP datagram framing)
│ ├── sdk/ # Go client SDK for frps admin API: sdk/client/
│ ├── ssh/ # SSH gateway tunnel implementation
│ ├── transport/ # Transport abstraction (message read/write over connections)
│ ├── util/ # Assorted utilities:
│ │ ├── http/ # HTTP utilities (basic auth, CONNECT proxy, etc.)
│ │ ├── jsonx/ # JSON extras
│ │ ├── limit/ # Rate-limiting (io.Reader/Writer wrappers)
│ │ ├── log/ # Logging facade (wraps go.uber.org/zap)
│ │ ├── metric/ # Metric primitives (counters, date counters)
│ │ ├── net/ # Network utilities (connection wrappers, mux, pipe)
│ │ ├── system/ # OS compatibility (ulimits, signal handling)
│ │ ├── tcpmux/ # HTTP/1.1 CONNECT-based TCP multiplexer
│ │ ├── util/ # Misc helpers (string, rand, env)
│ │ ├── version/ # Version string management
│ │ ├── vhost/ # Virtual-host HTTP/HTTPS router (reverse proxy logic)
│ │ ├── wait/ # Retry/backoff/polling helpers
│ │ └── xlog/ # Context-aware structured logging adapter
│ ├── virtual/ # Virtual client/server (in-process frp tunnel for VirtualNet)
│ └── vnet/ # VirtualNet: WireGuard-based L3 network overlay
│
├── assets/ # Embedded static asset helpers (assets.go)
├── conf/ # Example config files (frpc.toml, frps.toml, full examples)
├── doc/ # Documentation; doc/agents/ for agent runbooks
├── dockerfiles/ # Dockerfile-for-frpc, Dockerfile-for-frps
├── hack/ # Dev scripts: run-e2e.sh, download.sh
├── test/ # End-to-end test suite
│ └── e2e/
│ ├── framework/ # Ginkgo test helpers, process management
│ ├── mock/ # Mock HTTP/OIDC/stream servers for e2e
│ ├── pkg/ # e2e utilities: port, request, process, cert, rpc, ssh
│ ├── v1/ # Current-format e2e test cases: basic, features, plugin
│ └── legacy/ # Legacy-format e2e test cases
└── web/ # Vue/Vite web dashboards (compiled separately)
├── frpc/ # frpc admin dashboard (Vue 3)
├── frps/ # frps dashboard (Vue 3)
└── shared/ # Shared Vue components and CSSEntry points#
| Binary | Path | Purpose |
|---|---|---|
frpc | cmd/frpc/main.go | Client binary — runs on the LAN-side machine; connects to frps, registers proxies and visitors, forwards local ports to the tunnel |
frps | cmd/frps/main.go | Server binary — runs on the public-IP machine; accepts frpc connections, manages the control channel, allocates ports, proxies incoming traffic to tunneled clients |
frpc sub-commands (via Cobra cmd/frpc/sub/):
root— start the main client serviceproxy— manage individual proxynathole— trigger/coordinate NAT hole-punchingadmin— interact with frpc admin APIverify— validate config file without running
frps sub-commands (via Cobra cmd/frps/):
- root (default) — start the server
verify— validate config file without running
Package organization#
Client packages (client/)#
client—Service(lifecycle),Control(persistent control channel),Connector(transport), config hot-reloadclient/proxy— proxy type handlers: TCPProxy, HTTPProxy, STCPProxy, XTCPProxy, UDPProxy, etc.client/visitor— visitor implementations: STCPVisitor, XTCPVisitor (NAT traversal receivers)client/event— lightweight internal event bus for proxy lifecycle eventsclient/health— health checker for TCP/HTTP local services (affects proxy state)client/configmgmt— config manager helpers for hot-reload
Server packages (server/)#
server—Service(lifecycle),Control(per-client session management)server/proxy— server-side proxy handlers (per proxy type)server/visitor— server-side visitor listener managementserver/controller— resource manager (allocates ports, limits bandwidth, manages credentials)server/group— proxy group load balancing (TCP mux group, HTTP group)server/ports— port pool: allocates and tracks bound TCP/UDP portsserver/registry— in-memory registries for proxies, users, and listenersserver/metrics— server-side metrics collection and exposure
Public packages (pkg/)#
pkg/msg— wire protocol message types (the frpc↔frps control protocol structs)pkg/config/v1— the current configuration schema (YAML/TOML/JSON) with validationpkg/auth— authentication: token, OIDC, mTLS client/serverpkg/transport— message-level read/write abstraction over net.Connpkg/nathole— NAT hole-punching coordination (STUN-based)pkg/plugin/{client,server,visitor}— plugin interfaces and built-in implementationspkg/ssh— SSH tunnel gatewaypkg/vnet/pkg/virtual— WireGuard L3 virtual network (VirtualNet feature)pkg/util/vhost— virtual-host HTTP/HTTPS reverse proxy routerpkg/util/net— connection wrappers, multiplexing, pipe utilitiespkg/sdk/client— Go client SDK for frps admin REST API
Layering#
The package graph follows a clear dependency direction:
cmd/* → client/ | server/ → pkg/*
client/ ↔ (no import of server/)
server/ ↔ (no import of client/)
pkg/* → stdlib + third-party only (mostly)pkg/msg is the shared boundary: both client/ and server/ import it for the wire protocol. pkg/config/v1, pkg/auth, pkg/transport, pkg/util/* are pure shared libraries. There is no clean-architecture layering of domain/application/infrastructure — it is functional decomposition by binary role rather than by DDD layers.
Build system#
- Build tool: GNU Make (
Makefile,Makefile.cross-compiles) - Key targets:
make build→ compilesbin/frpsandbin/frpc(CGO_ENABLED=0, trimpath, -s -w)make web→ builds Vue dashboards inweb/frps/distandweb/frpc/distvia sub-makesmake all→ fmt + web + buildmake test→ unit tests across all packagesmake e2e→ runs Ginkgo e2e suite viahack/run-e2e.sh- Build tags
frps/frpc+nowebcontrol which web assets and platform-specific code are compiled in
- Docker: Yes — two separate Dockerfiles (
dockerfiles/Dockerfile-for-frpc,Dockerfile-for-frps); single-stage (no multi-stage), scratch or alpine base - Cross-compilation:
Makefile.cross-compilesfor multi-arch releases;package.shbundles release archives
Notable structural decisions#
Parallel top-level package trees for each binary role. Rather than using
internal/scoping, frp placesclient/andserver/as sibling top-level packages. This is semantically clear but relies on convention rather than the compiler to prevent cross-imports. It keeps the two binaries’ domain logic fully separated and independently testable.Build-tag-based web embedding. The web dashboards (Vue apps) are compiled into Go embed blobs under
web/frpc/andweb/frps/. Build tagsnowebandfrps/frpcallow building slim binaries without the dashboard, or including it.assets/assets.gobridges the embed into a single access point. This avoids separate static file serving infrastructure.pkg/msgas the shared wire protocol layer. All control-plane messages (both frpc→frps and frps→frpc direction) are defined inpkg/msgas plain Go structs with JSON tags. This single package is the explicit seam between client and server domains — a clean separation of the protocol definition from its producers and consumers.Legacy config support via a parallel
legacy/subtree. The migration from ini-format to YAML/TOML/JSON config is handled by keeping apkg/config/legacy/subtree alongsidepkg/config/v1/. Bothcmd/frpc/sub/andcmd/frps/root.godetect legacy format and warn; no symlinks or shims — the old parser simply lives in a separate package until it is removed.test/e2e/as a separate, self-contained test world. The end-to-end suite undertest/e2e/uses Ginkgo/Gomega and has its own framework, mock servers, utility packages, and legacy vs. v1 sub-directories. This level of investment in e2e testing is unusual for a proxy tool of this size, and reflects confidence in integration-level validation over unit-level mocking.