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 CSS

Entry points#

BinaryPathPurpose
frpccmd/frpc/main.goClient binary — runs on the LAN-side machine; connects to frps, registers proxies and visitors, forwards local ports to the tunnel
frpscmd/frps/main.goServer 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 service
  • proxy — manage individual proxy
  • nathole — trigger/coordinate NAT hole-punching
  • admin — interact with frpc admin API
  • verify — 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/)#

  • clientService (lifecycle), Control (persistent control channel), Connector (transport), config hot-reload
  • client/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 events
  • client/health — health checker for TCP/HTTP local services (affects proxy state)
  • client/configmgmt — config manager helpers for hot-reload

Server packages (server/)#

  • serverService (lifecycle), Control (per-client session management)
  • server/proxy — server-side proxy handlers (per proxy type)
  • server/visitor — server-side visitor listener management
  • server/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 ports
  • server/registry — in-memory registries for proxies, users, and listeners
  • server/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 validation
  • pkg/auth — authentication: token, OIDC, mTLS client/server
  • pkg/transport — message-level read/write abstraction over net.Conn
  • pkg/nathole — NAT hole-punching coordination (STUN-based)
  • pkg/plugin/{client,server,visitor} — plugin interfaces and built-in implementations
  • pkg/ssh — SSH tunnel gateway
  • pkg/vnet / pkg/virtual — WireGuard L3 virtual network (VirtualNet feature)
  • pkg/util/vhost — virtual-host HTTP/HTTPS reverse proxy router
  • pkg/util/net — connection wrappers, multiplexing, pipe utilities
  • pkg/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 → compiles bin/frps and bin/frpc (CGO_ENABLED=0, trimpath, -s -w)
    • make web → builds Vue dashboards in web/frps/dist and web/frpc/dist via sub-makes
    • make all → fmt + web + build
    • make test → unit tests across all packages
    • make e2e → runs Ginkgo e2e suite via hack/run-e2e.sh
    • Build tags frps / frpc + noweb control 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-compiles for multi-arch releases; package.sh bundles release archives

Notable structural decisions#

  1. Parallel top-level package trees for each binary role. Rather than using internal/ scoping, frp places client/ and server/ 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.

  2. Build-tag-based web embedding. The web dashboards (Vue apps) are compiled into Go embed blobs under web/frpc/ and web/frps/. Build tags noweb and frps/frpc allow building slim binaries without the dashboard, or including it. assets/assets.go bridges the embed into a single access point. This avoids separate static file serving infrastructure.

  3. pkg/msg as the shared wire protocol layer. All control-plane messages (both frpc→frps and frps→frpc direction) are defined in pkg/msg as 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.

  4. Legacy config support via a parallel legacy/ subtree. The migration from ini-format to YAML/TOML/JSON config is handled by keeping a pkg/config/legacy/ subtree alongside pkg/config/v1/. Both cmd/frpc/sub/ and cmd/frps/root.go detect legacy format and warn; no symlinks or shims — the old parser simply lives in a separate package until it is removed.

  5. test/e2e/ as a separate, self-contained test world. The end-to-end suite under test/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.