feat(router): add 'auto' interface for source-in source-out on multi-homed hosts

When a router is configured with interface: "auto", the outbound socket
is bound to the same IP that received the incoming connection (read from
DstAddr in context, which is conn.LocalAddr() from the service accept
loop). This enables the 源进源出 (source-in source-out) pattern without
requiring a hardcoded interface name.

Supports comma-separated interface lists (e.g. "auto,eth1") where only
the "auto" token is replaced with the detected ingress IP. Falls back
to the original string when the ingress address is INADDR_ANY, loopback,
or unavailable, so the remaining entries still take effect.

Closes go-gost/gost#341
This commit is contained in:
ginuerzh
2026-06-20 21:30:28 +08:00
parent ae92f5aee6
commit 504c56f3c3
+27 -1
View File
@@ -5,6 +5,7 @@ import (
"context" "context"
"fmt" "fmt"
"net" "net"
"strings"
"time" "time"
"github.com/go-gost/core/chain" "github.com/go-gost/core/chain"
@@ -141,9 +142,34 @@ func (r *Router) dial(ctx context.Context, network, address string, log logger.L
if route == nil { if route == nil {
route = DefaultRoute route = DefaultRoute
} }
ifceName := r.options.IfceName
if strings.Contains(ifceName, "auto") {
// "auto" triggers per-connection interface detection: the
// ingress IP (conn.LocalAddr()) is used as the bind address,
// ensuring egress traffic leaves via the same interface that
// received the connection — the "source-in source-out"
// pattern for multi-homed hosts.
if dstAddr := xctx.DstAddrFromContext(ctx); dstAddr != nil {
if host, _, _ := net.SplitHostPort(dstAddr.String()); host != "" &&
host != "0.0.0.0" && host != "::" {
// Replace only exact "auto" tokens in the
// comma-separated interface list.
parts := strings.Split(ifceName, ",")
for i, p := range parts {
if p == "auto" {
parts[i] = host
}
}
ifceName = strings.Join(parts, ",")
}
}
if strings.Contains(ifceName, "auto") {
log.Debugf("auto interface: no suitable ingress address, keeping literal %q", ifceName)
}
}
conn, err = route.Dial(ctx, network, ipAddr, conn, err = route.Dial(ctx, network, ipAddr,
append([]chain.DialOption{ append([]chain.DialOption{
chain.InterfaceDialOption(r.options.IfceName), chain.InterfaceDialOption(ifceName),
chain.NetnsDialOption(r.options.Netns), chain.NetnsDialOption(r.options.Netns),
chain.SockOptsDialOption(r.options.SockOpts), chain.SockOptsDialOption(r.options.SockOpts),
chain.LoggerDialOption(log), chain.LoggerDialOption(log),