From 504c56f3c31c10bb84be569fefdcd4c75bc919ad Mon Sep 17 00:00:00 2001 From: ginuerzh Date: Sat, 20 Jun 2026 21:30:28 +0800 Subject: [PATCH] feat(router): add 'auto' interface for source-in source-out on multi-homed hosts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- chain/router.go | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/chain/router.go b/chain/router.go index 1a10c76e..5edfe561 100644 --- a/chain/router.go +++ b/chain/router.go @@ -5,6 +5,7 @@ import ( "context" "fmt" "net" + "strings" "time" "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 { 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, append([]chain.DialOption{ - chain.InterfaceDialOption(r.options.IfceName), + chain.InterfaceDialOption(ifceName), chain.NetnsDialOption(r.options.Netns), chain.SockOptsDialOption(r.options.SockOpts), chain.LoggerDialOption(log),