CodeVetter · Repo Unpack · Behavior traces
fatedier/frp
Four flows were traced end-to-end. (1) frps startup: cobra -> LoadServerConfig -> validate -> NewService wires all listeners/managers -> Run spawns per-listener goroutines. (2) frpc startup+login: cobra -> LoadClientConfigResult -> aggregator merge -> NewService -> Run -> loopLoginUntilSuccess -> controlSessionDialer.Dial -> NewControl -> ctl.Run. (3) TCP proxy request path: TCPProxy.Run listens -> user conn -> GetWorkConn -> ReqWorkConn -> client NewWorkConn -> StartWorkConn -> InWorkConn -> join to local service. (4) Admin reload: GET /api/reload -> Reload -> UpdateConfigSource -> reloadConfigFromSourcesLocked -> UpdateAllConfigurer.
- frps startup order: rootCmd.RunE -> config.LoadServerConfig (legacy INI detection -> template render -> decode -> Complete) -> security.NewUnsafeFeatures + ValidateServerConfig -> runServer -> log.InitLogger -> server.NewService -> svr.Run.
- server.NewService order: build TLS config -> optional webServer (+metrics) -> BuildServerAuth -> construct managers (ctlManager, pxyManager, pluginManager, ResourceController with port managers) -> register HTTP plugins -> group controllers -> bind muxed TCP listener -> optional KCP/QUIC/SSH/vhost listeners -> NatHoleController.
- svr.Run spawns goroutines: webServer.Run, HandleListener(sshTunnelListener internal=true), HandleListener(kcp), HandleQUICListener, HandleListener(websocket), HandleListener(tls), NatHoleController.CleanWorker, sshTunnelGateway.Run, then blocks in HandleListener(main listener).
- Inbound conn handling: HandleListener -> handleConnection -> acceptConnection (CheckMagic v1/v2, ClientHello negotiation, read first msg) -> switch on msg.Login/msg.NewWorkConn/msg.NewVisitorConn -> RegisterControl / RegisterWorkConn / RegisterVisitorConn.
- Control lifecycle is generation-guarded: ControlManager.Add installs ctl as pending generation keyed by runID (replacing the old control and inheriting its runMu), Activate registers user.clientID in ClientRegistry (conflict -> error), completeLogin writes LoginResp then startLocked flips state to running and launches ctl.worker.
- frpc startup order: runClient -> LoadClientConfigResult -> NewConfigSource.ReplaceAll -> optional NewStoreSource -> NewAggregator.Load -> Filter/Complete configurers -> ValidateAllClientConfig -> startServiceWithAggregator -> client.NewService -> svr.Run.
- client.Service.Run order: optional DNSServer override -> vnetController.Init/Run -> admin webServer -> loopLoginUntilSuccess (first login failure can exit when loginFailExit) -> keepControllerWorking reconnection loop with FastBackoff (3 fast retries/min then exponential to 20s).
- Client login + proxy bring-up: loopLoginUntilSuccess -> controlSessionDialer.Dial(runID) -> NewControl -> ctl.SetInWorkConnCallback -> ctl.Run -> pm.UpdateAll(proxyCfgs) sends NewProxy per proxy and vm.UpdateAll(visitorCfgs); worker starts heartbeatWorker and msgDispatcher.
- Work conn establishment on demand: server GetWorkConn drains pool else sends ReqWorkConn and waits up to UserConnTimeout; client handleReqWorkConn -> connectServer -> authenticated NewWorkConn -> server RegisterWorkConn pushes into workConnCh -> proxy.Start writes StartWorkConn back to client.
- TCP data path completion: client receives StartWorkConn -> pm.HandleWorkConn(proxyName) -> Proxy.InWorkConn -> HandleTCPWorkConnection wraps conn (limit/encrypt/compress) -> writes PROXY protocol header if configured -> dials localIP:localPort -> libio.Join for bidirectional copy.
- Heartbeat: client heartbeatWorker sends authenticated msg.Ping every HeartbeatInterval with backoff and closes session when lastPong exceeds HeartbeatTimeout; server heartbeatWorker closes the Control when lastPing exceeds Transport.HeartbeatTimeout.
- Admin reload trace: GET /api/reload -> apiController.Reload -> Service.UpdateConfigSource (reloadMu serialized) -> cfgSource.ReplaceAll -> reloadConfigFromSourcesLocked -> aggregator.Load -> VirtualNet requirement check -> UpdateAllConfigurer -> ctl.vm.UpdateAll + ctl.pm.UpdateAll.