Replace the single use of k8s.io/apimachinery/pkg/util/validation
.IsQualifiedName (annotation key validation in pkg/config/v1/validation)
with a local implementation in pkg/util/validation. Newer apimachinery
versions link klog only for IP/CIDR warning helpers, which grows frpc and
frps, and this was the only frp import of that package.
The local IsQualifiedName keeps the exact behavior and error messages of
apimachinery v0.28.8, including the qualified-name (label key) format and
the DNS-1123 subdomain prefix rules. Annotation keys are still lowercased
before validation, so the accepted set is unchanged.
Verified against apimachinery v0.28.8 on 228,220 inputs (exhaustive short
strings, boundary lengths and seeded random data, including lowercased
variants): every error message matches.
No dependency versions change; go.mod and go.sum are untouched.
Upgrade github.com/quic-go/quic-go from v0.60.0 to v0.65.0. Only go.mod
and go.sum change; the go and toolchain lines are unchanged and no other
dependencies move.
This brings the QUIC transport fixes from v0.61-v0.65: connection
migration and NAT rebinding handling, congestion control and PMTU
recovery, and stream error handling. frp uses the core QUIC API only:
Dial/Listen, config timeouts and stream limits, Conn and Stream.
Move client_golang to v1.25.0 with its required module updates
(client_model v0.6.3, common v0.72.0, procfs v0.22.0, xxhash v2.3.0,
protobuf v1.36.12, munnerz/goautoneg). go.mod keeps go 1.26.0 and has
no toolchain line.
Sync the frpc text metrics clone with the new default Go collector:
add go_gc_gogc_percent, go_gc_gomemlimit_bytes and
go_sched_gomaxprocs_threads, update the go_memstats help texts, and
remove go_memstats_lookups_total. frpc still exposes go_* metrics only.
Upgrade golang.org/x/net v0.57.0 -> v0.62.0 and github.com/gorilla/websocket
v1.5.0 -> v1.5.3 to clear the reported HTTP/2 server and websocket
advisories, along with patch/minor updates to the other direct dependencies:
x/crypto, x/sys, x/sync, x/oauth2, x/time, go-oidc, go-toml/v2, samber/lo,
cobra, pflag, rodaine/table, netlink, ini, and the test-only testify,
ginkgo, gomega and gjson modules.
go.mod keeps `go 1.26.0` and has no toolchain line. Only go.mod and go.sum
change.
When a client logs in again with the same run ID, frps replaces the old
control and closes its control stream, but the yamux session that carried it
stays open until keepalive reclaims it. The replaced control's work
connections and streams share that session, so they are left behind for up to
tcpMuxKeepaliveInterval plus the ping budget.
A mux session carries exactly one control connection, so the control now
records its session and closes it from the single teardown point shared by
replacement, fatal exit and shutdown. QUIC and non-multiplexed transports keep
the previous path.
Closing the session ends every stream that still belongs to the replaced
control. Those proxies are already being torn down, so the cost is that
in-flight connections of the old control are interrupted immediately instead
of at the keepalive deadline. A replacement control is not affected, and the
ownership rules for a still-online control do not change.
Tests: replacement and fatal exit close the session while the replacement
control keeps working, and concurrent logins with the same run ID leave
exactly one live session.
Update github.com/fatedier/yamux from v0.2.0 to v0.3.0. go.mod and go.sum are
the only dependency changes.
v0.3.0 adds a public Stream.Reset() and fixes several stream and session
lifecycle problems: a read or write blocked on a shutting-down session returns
instead of spinning, the stream close lifecycle is safe to retry, failed stream
opens release their tracking state, and ConnectionWriteTimeout is now enforced
as a write deadline on the underlying transport instead of only bounding how
long a caller waits for its own frame.
The last change is observable in frp. When a tcpMux connection stalls, the
write now fails at the transport and the session is torn down, so a new stream
returns ErrSessionShutdown; before, the caller timed out while the session
stayed blocked but alive until a keepalive ping also timed out. All frp
transports implement SetWriteDeadline (TCP, TLS, WebSocket, WSS and KCP); KCP
reports its own timeout error instead of the mapped one. Reads on an
established stream now return ErrSessionShutdown instead of io.EOF when the
session dies; a graceful stream close still returns io.EOF, and frp data paths
already treat any read error as the end of the stream.
Adapt TestDispatcherYamuxWriteTimeoutInterruptsRead to the new behaviour: the
delayed test connection now honours SetWriteDeadline, and the test asserts that
both yamux sessions close after the timed-out write instead of expecting the
session to stay usable.
When an HTTPS or tcpmux route listener is closed after a connection has been
resolved to it but before the handoff, Muxer.handle clears the deadline and
then sends on the closed accept channel. The recovered panic returns without
closing the connection, so the peer waits until its own read timeout.
Add a done channel and sync.Once to Listener. Close closes done and is
idempotent; handle and Accept select on accept/done, and a pending connection
is closed when done is closed. Connections already handed to Accept keep their
current ownership. Remove the PanicToError-based control flow.
Cover the pending handoff with net.Pipe and real TCP tests, plus an idempotent
Close test.
Fixes#5573
Logger kept its rendered prefix in a plain string field that was written by
AddPrefix, AppendPrefix and ResetPrefixes and read without synchronization by
Tracef, Spawn and the other logging methods. In frpc the race is reachable on
the service-level logger, which every component of a session shares through
its context: adding the run ID after a login writes the prefix while control,
proxy manager, connector and visitor goroutines keep logging through the same
logger, and a reload that creates proxies calls Spawn on it. A torn string
header can pair a nil data pointer with a non-zero length, and formatting such
a prefix can crash the process when the log call's level is enabled.
Serialize the writers with a per-Logger mutex and publish the rendered prefix
as an immutable string through an atomic pointer, so the log path takes no
lock. Spawn copies the parent prefix list under the same mutex, keeping
children independent snapshots. Logging output is unchanged.
InWorkConn published readCh and sendCh into pxy fields while holding
pxy.mu, then read those same fields again after unlocking to start the
sender, reader and heartbeat goroutines and to call udp.Forwarder. Two
work connections installed concurrently made that unlocked read race
with the next generation's write to the same fields.
Create the channels as locals, publish them under the existing lock,
and use only those locals for the goroutines and the forwarder. The
fields stay guarded by pxy.mu, so Close still closes the current
generation. No behavior change for a single generation.
Add TestUDPProxyInWorkConnGenerationChannels, which drives two
InWorkConn calls concurrently so the race detector covers the field
accesses.
Record the deprecation of the observability endpoints on webServer in
doc/deprecations.md, so the removal target is in one place next to the
other active deprecations.
The webServer /healthz, the frps webServer /metrics with its top-level
enablePrometheus switch, and the pprof handlers with webServer.pprofEnable
are deprecated since v0.72.0, when metricsServer was added. They are
replaced by metricsServer, whose /healthz, /metrics and pprofEnable are
independent of the dashboard listener.
Add pkg/metrics/text, a small Prometheus text-format writer, and use it
for frpc's /metrics. The endpoint lives only on metricsServer and is
covered by its Basic Auth, like the other endpoints there.
The package emits the Go runtime families and, on Linux, the process
families read from /proc. Their names, types and help strings match
client_golang's default collectors, so existing Go runtime dashboards
keep working. It deliberately emits no frp_ business metrics and none of
client_golang's promhttp_ handler metrics.
Writing the subset ourselves keeps client_golang and protobuf out of the
frpc binary; both are still used by frps and by the tests here.
Add a separate HTTP listener for observability, configured through
metricsServer in both frps and frpc. It serves /healthz and, when
pprofEnable is set, the pprof handlers except /debug/pprof/cmdline. It
never serves the dashboard, static assets or the admin API, and it does
not touch the global assets filesystem.
frps also serves /metrics on this listener with both runtime and business
metrics, enabled by metricsServer.port alone. The in-memory collector
still starts only with the dashboard, and the Prometheus collector starts
either with metricsServer or with enablePrometheus plus webServer, so a
configuration that collected nothing before still collects nothing.
The /healthz, /metrics and pprof endpoints on webServer, and
enablePrometheus, keep working but are deprecated and log a hint at
startup. frpc does not serve /metrics yet.
The https2http and https2https plugins read their certificate and key
once when the proxy starts, so a certificate that an external tool renews
on disk is only picked up after the proxy itself is recreated. frpc
reload rebuilt proxies only when their configuration changed, and a
rebuild closes the work connections that are already in use.
Let the plugin own the refresh. The HTTPS plugin server keeps the active
keypair and the last refresh error in one immutable snapshot published
through an atomic pointer, so a TLS handshake only loads that pointer and
never touches the disk. Reload rereads both files, and when their bytes
match the published pair it returns without parsing or replacing
anything; otherwise it parses the pair and swaps the snapshot in. A read
or parse failure keeps the previous certificate serving and records the
error next to it.
frpc reload collects the reloadable plugins of the live proxies and
applies the new configuration inside the same reload transaction that
also serializes control handoff, so a reload that races a reconnect
still refreshes the plugins of the published control. The certificate
files are read only after that transaction has been released. The config
store API keeps applying configuration without refreshing certificates,
because only an explicit reload may reread files. Proxy status reports a
refresh failure next to the still running proxy instead of dropping its
remote address.
No configuration field is added, and frps and the wire protocol are
unchanged. Without a reload nothing changes: certificates are still read
once at startup, generated self-signed certificates still never reload,
and a proxy whose configuration changed still goes through the existing
rebuild path.
KCPListener hands accepted sessions to a consumer through an unbuffered
channel, but its accept goroutine wrote to that channel without watching for
shutdown. A Close racing with a handoff left the goroutine blocked forever,
holding the session it was about to deliver, because closing the underlying
listener does not unblock a send. The close flag it read was also written by
Close without synchronisation.
Watch a done channel in the handoff and close the connection that was never
delivered, so the goroutine can exit and the session is released. Accept now
returns ErrKCPListenerClosed as soon as the listener is closed instead of
blocking on the channel, and the close state is a done channel guarded by
sync.Once.
A ready handoff can still win the race with Close; the session then belongs to
the caller of Accept, matching how WebsocketListener behaves.
Tests cover a pending handoff that is cancelled by Close and a blocked Accept
that is woken by it.
A constructor that binds a port before its fallible preparation leaks the
listener when a later step returns an error, and no one can close it because
the returned service is nil.
pkg/util/http: load the TLS keypair and build the auth middleware before
net.Listen, so binding is the last step that can run and a failure leaves no
bound port behind.
server: create the web server after auth, the tcpmux, main, kcp, quic, ssh
gateway and vhost listeners, and the nat hole controller, following what frpc
already does. If the web server itself fails, close the resources created so
far before returning. The port managers start their cleanup workers on
creation, so they are created after every fallible step too, and the metrics
collectors keep being enabled only when the web server is configured.
Add regression tests for all three failure paths.
Move the frpc-only TLS option into the shared TLSConfig as
transport.tls.useSystemCa, replacing the unpublished useSystemRoots.
When set, frpc verifies the server with the system CAs; when
trustedCaFile is also set, its CAs are appended to the system pool so
both are trusted. Failures to load either source abort the connection
instead of falling back. Enabling the option without effective TLS is a
configuration error, and the field is rejected in frps transport.tls
and in both webServer.tls sections.
Fixes#5545
Use net/http Server.Protocols and the merged golib listener path instead of the deprecated h2c handler. Keep HTTP/1.1 and cleartext HTTP/2 prior-knowledge support, while intentionally dropping HTTP/1.1 Upgrade: h2c.
Move the frps dashboard Clients and Proxies views to the paginated
/api/v2/clients and /api/v2/proxies endpoints instead of fetching all
data at once, and extend server-side proxy search so the search box
keeps working under pagination.
Frontend:
- Add V2Envelope/V2Page types and getV2 HTTP helper to api/http.ts
- Add v2 paginated fetch functions to api/client.ts and api/proxy.ts
- Add ClientV2Info and ProxyV2Info types for v2 API responses
- Rewrite Clients.vue with server-side pagination, status/user search
filtering, and ElPagination component
- Rewrite Proxies.vue with server-side pagination, type tabs, client
dropdown filter, and a search box that passes q to the API
- Default page size 10, selectable sizes [10, 20, 50, 100]
Backend:
- Extend /api/v2/proxies q matching to also cover online proxy spec
fields: TCP/UDP remotePort and HTTP/HTTPS/TCPMux customDomains and
subdomain, so dashboard search no longer needs to scan every page
- Add controller_v2 tests for the new spec-field matching
SUDP payload codec follows transport wireProtocol; same-protocol v1/v1 and v2/v2 keep raw join; only mixed proxy/visitor protocols use message-aware bridge; no new capability/selection field.