Compare commits

...

2 Commits

Author SHA1 Message Date
fatedier
610e5ed479 improve yamux logging (#4952)
Some checks failed
golangci-lint / lint (push) Has been cancelled
Close stale issues and PRs / stale (push) Has been cancelled
2025-08-25 17:52:58 +08:00
fatedier
80d3f332e1 xtcp: add configuration to disable assisted addresses in NAT traversal (#4951) 2025-08-25 15:52:52 +08:00
14 changed files with 137 additions and 18 deletions

View File

@@ -1,7 +1,3 @@
## Features
* Support tokenSource for loading authentication tokens from files.
## Fixes
* Fix SSH tunnel gateway incorrectly binding to proxyBindAddr instead of bindAddr, which caused external connections to fail when proxyBindAddr was set to 127.0.0.1.
* Add NAT traversal configuration options for XTCP proxies and visitors. Support disabling assisted addresses to avoid using slow VPN connections during NAT hole punching.

View File

@@ -17,7 +17,6 @@ package client
import (
"context"
"crypto/tls"
"io"
"net"
"strconv"
"strings"
@@ -115,7 +114,8 @@ func (c *defaultConnectorImpl) Open() error {
fmuxCfg := fmux.DefaultConfig()
fmuxCfg.KeepAliveInterval = time.Duration(c.cfg.Transport.TCPMuxKeepaliveInterval) * time.Second
fmuxCfg.LogOutput = io.Discard
// Use trace level for yamux logs
fmuxCfg.LogOutput = xlog.NewTraceWriter(xl)
fmuxCfg.MaxStreamWindowSize = 6 * 1024 * 1024
session, err := fmux.Client(conn, fmuxCfg)
if err != nil {

View File

@@ -276,10 +276,12 @@ func (ctl *Control) heartbeatWorker() {
}
func (ctl *Control) worker() {
xl := ctl.xl
go ctl.heartbeatWorker()
go ctl.msgDispatcher.Run()
<-ctl.msgDispatcher.Done()
xl.Debugf("control message dispatcher exited")
ctl.closeSession()
ctl.pm.Close()

View File

@@ -64,11 +64,19 @@ func (pxy *XTCPProxy) InWorkConn(conn net.Conn, startWorkConnMsg *msg.StartWorkC
}
xl.Tracef("nathole prepare start")
prepareResult, err := nathole.Prepare([]string{pxy.clientCfg.NatHoleSTUNServer})
// Prepare NAT traversal options
var opts nathole.PrepareOptions
if pxy.cfg.NatTraversal != nil && pxy.cfg.NatTraversal.DisableAssistedAddrs {
opts.DisableAssistedAddrs = true
}
prepareResult, err := nathole.Prepare([]string{pxy.clientCfg.NatHoleSTUNServer}, opts)
if err != nil {
xl.Warnf("nathole prepare error: %v", err)
return
}
xl.Infof("nathole prepare success, nat type: %s, behavior: %s, addresses: %v, assistedAddresses: %v",
prepareResult.NatType, prepareResult.Behavior, prepareResult.Addrs, prepareResult.AssistedAddrs)
defer prepareResult.ListenConn.Close()

View File

@@ -276,11 +276,19 @@ func (sv *XTCPVisitor) makeNatHole() {
}
xl.Tracef("nathole prepare start")
prepareResult, err := nathole.Prepare([]string{sv.clientCfg.NatHoleSTUNServer})
// Prepare NAT traversal options
var opts nathole.PrepareOptions
if sv.cfg.NatTraversal != nil && sv.cfg.NatTraversal.DisableAssistedAddrs {
opts.DisableAssistedAddrs = true
}
prepareResult, err := nathole.Prepare([]string{sv.clientCfg.NatHoleSTUNServer}, opts)
if err != nil {
xl.Warnf("nathole prepare error: %v", err)
return
}
xl.Infof("nathole prepare success, nat type: %s, behavior: %s, addresses: %v, assistedAddresses: %v",
prepareResult.NatType, prepareResult.Behavior, prepareResult.Addrs, prepareResult.AssistedAddrs)

View File

@@ -372,6 +372,14 @@ localPort = 22
# Otherwise, visitors from same user can connect. '*' means allow all users.
allowUsers = ["user1", "user2"]
# NAT traversal configuration (optional)
[proxies.natTraversal]
# Disable the use of local network interfaces (assisted addresses) for NAT traversal.
# When enabled, only STUN-discovered public addresses will be used.
# This can improve performance when you have slow VPN connections.
# Default: false
disableAssistedAddrs = false
[[proxies]]
name = "vnet-server"
type = "stcp"
@@ -411,6 +419,13 @@ minRetryInterval = 90
# fallbackTo = "stcp_visitor"
# fallbackTimeoutMs = 500
# NAT traversal configuration (optional)
[visitors.natTraversal]
# Disable the use of local network interfaces (assisted addresses) for NAT traversal.
# When enabled, only STUN-discovered public addresses will be used.
# Default: false
disableAssistedAddrs = false
[[visitors]]
name = "vnet-visitor"
type = "stcp"

2
go.mod
View File

@@ -82,4 +82,4 @@ require (
)
// TODO(fatedier): Temporary use the modified version, update to the official version after merging into the official repository.
replace github.com/hashicorp/yamux => github.com/fatedier/yamux v0.0.0-20230628132301-7aca4898904d
replace github.com/hashicorp/yamux => github.com/fatedier/yamux v0.0.0-20250825093530-d0154be01cd6

4
go.sum
View File

@@ -22,8 +22,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatedier/golib v0.5.1 h1:hcKAnaw5mdI/1KWRGejxR+i1Hn/NvbY5UsMKDr7o13M=
github.com/fatedier/golib v0.5.1/go.mod h1:W6kIYkIFxHsTzbgqg5piCxIiDo4LzwgTY6R5W8l9NFQ=
github.com/fatedier/yamux v0.0.0-20230628132301-7aca4898904d h1:ynk1ra0RUqDWQfvFi5KtMiSobkVQ3cNc0ODb8CfIETo=
github.com/fatedier/yamux v0.0.0-20230628132301-7aca4898904d/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
github.com/fatedier/yamux v0.0.0-20250825093530-d0154be01cd6 h1:u92UUy6FURPmNsMBUuongRWC0rBqN6gd01Dzu+D21NE=
github.com/fatedier/yamux v0.0.0-20250825093530-d0154be01cd6/go.mod h1:c5/tk6G0dSpXGzJN7Wk1OEie8grdSJAmeawId9Zvd34=
github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=

View File

@@ -96,6 +96,14 @@ type TLSConfig struct {
ServerName string `json:"serverName,omitempty"`
}
// NatTraversalConfig defines configuration options for NAT traversal
type NatTraversalConfig struct {
// DisableAssistedAddrs disables the use of local network interfaces
// for assisted connections during NAT traversal. When enabled,
// only STUN-discovered public addresses will be used.
DisableAssistedAddrs bool `json:"disableAssistedAddrs,omitempty"`
}
type LogConfig struct {
// This is destination where frp should write the logs.
// If "console" is used, logs will be printed to stdout, otherwise,

View File

@@ -422,6 +422,9 @@ type XTCPProxyConfig struct {
Secretkey string `json:"secretKey,omitempty"`
AllowUsers []string `json:"allowUsers,omitempty"`
// NatTraversal configuration for NAT traversal
NatTraversal *NatTraversalConfig `json:"natTraversal,omitempty"`
}
func (c *XTCPProxyConfig) MarshalToMsg(m *msg.NewProxy) {

View File

@@ -160,6 +160,9 @@ type XTCPVisitorConfig struct {
MinRetryInterval int `json:"minRetryInterval,omitempty"`
FallbackTo string `json:"fallbackTo,omitempty"`
FallbackTimeoutMs int `json:"fallbackTimeoutMs,omitempty"`
// NatTraversal configuration for NAT traversal
NatTraversal *NatTraversalConfig `json:"natTraversal,omitempty"`
}
func (c *XTCPVisitorConfig) Complete(g *ClientCommonConfig) {

View File

@@ -68,6 +68,13 @@ var (
DetectRoleReceiver = "receiver"
)
// PrepareOptions defines options for NAT traversal preparation
type PrepareOptions struct {
// DisableAssistedAddrs disables the use of local network interfaces
// for assisted connections during NAT traversal
DisableAssistedAddrs bool
}
type PrepareResult struct {
Addrs []string
AssistedAddrs []string
@@ -108,7 +115,7 @@ func PreCheck(
}
// Prepare is used to do some preparation work before penetration.
func Prepare(stunServers []string) (*PrepareResult, error) {
func Prepare(stunServers []string, opts PrepareOptions) (*PrepareResult, error) {
// discover for Nat type
addrs, localAddr, err := Discover(stunServers, "")
if err != nil {
@@ -133,9 +140,13 @@ func Prepare(stunServers []string) (*PrepareResult, error) {
return nil, fmt.Errorf("listen local udp addr error: %v", err)
}
assistedAddrs := make([]string, 0, len(localIPs))
for _, ip := range localIPs {
assistedAddrs = append(assistedAddrs, net.JoinHostPort(ip, strconv.Itoa(laddr.Port)))
// Apply NAT traversal options
var assistedAddrs []string
if !opts.DisableAssistedAddrs {
assistedAddrs = make([]string, 0, len(localIPs))
for _, ip := range localIPs {
assistedAddrs = append(assistedAddrs, net.JoinHostPort(ip, strconv.Itoa(laddr.Port)))
}
}
return &PrepareResult{
Addrs: addrs,

View File

@@ -0,0 +1,65 @@
// Copyright 2025 The frp Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package xlog
import "strings"
// LogWriter forwards writes to frp's logger at configurable level.
// It is safe for concurrent use as long as the underlying Logger is thread-safe.
type LogWriter struct {
xl *Logger
logFunc func(string)
}
func (w LogWriter) Write(p []byte) (n int, err error) {
msg := strings.TrimSpace(string(p))
w.logFunc(msg)
return len(p), nil
}
func NewTraceWriter(xl *Logger) LogWriter {
return LogWriter{
xl: xl,
logFunc: func(msg string) { xl.Tracef("%s", msg) },
}
}
func NewDebugWriter(xl *Logger) LogWriter {
return LogWriter{
xl: xl,
logFunc: func(msg string) { xl.Debugf("%s", msg) },
}
}
func NewInfoWriter(xl *Logger) LogWriter {
return LogWriter{
xl: xl,
logFunc: func(msg string) { xl.Infof("%s", msg) },
}
}
func NewWarnWriter(xl *Logger) LogWriter {
return LogWriter{
xl: xl,
logFunc: func(msg string) { xl.Warnf("%s", msg) },
}
}
func NewErrorWriter(xl *Logger) LogWriter {
return LogWriter{
xl: xl,
logFunc: func(msg string) { xl.Errorf("%s", msg) },
}
}

View File

@@ -19,7 +19,6 @@ import (
"context"
"crypto/tls"
"fmt"
"io"
"net"
"net/http"
"os"
@@ -516,7 +515,8 @@ func (svr *Service) HandleListener(l net.Listener, internal bool) {
if lo.FromPtr(svr.cfg.Transport.TCPMux) && !internal {
fmuxCfg := fmux.DefaultConfig()
fmuxCfg.KeepAliveInterval = time.Duration(svr.cfg.Transport.TCPMuxKeepaliveInterval) * time.Second
fmuxCfg.LogOutput = io.Discard
// Use trace level for yamux logs
fmuxCfg.LogOutput = xlog.NewTraceWriter(xlog.FromContextSafe(ctx))
fmuxCfg.MaxStreamWindowSize = 6 * 1024 * 1024
session, err := fmux.Server(frpConn, fmuxCfg)
if err != nil {