fix(dialer): skip SO_BINDTODEVICE when interface is specified as IP address

When the user specifies an IP address as the interface (e.g.
interface=10.1.1.1), GOST resolved it to its hosting interface and
called SO_BINDTODEVICE on it — breaking outbound connectivity for
IP aliases on loopback or dummy interfaces (go-gost/gost#785).

The initial fix (caa82f2) only skipped SO_BINDTODEVICE for loopback
interfaces, missing non-loopback dummy interfaces like ip-aliases.

This change adds an isIP return value to ParseInterfaceAddr so callers
can distinguish 'user specified an IP' (intent: source IP binding for
policy routing — skip SO_BINDTODEVICE) from 'user specified an
interface name' (intent: force traffic through that NIC — keep
SO_BINDTODEVICE). The dialOnce method now only calls bindDevice when
bindToDevice is true AND the input was an interface name (!isIP).

The loopback-specific guard in bindDevice is reverted — superseded by
the broader isIP-based fix that covers all interface types.
This commit is contained in:
ginuerzh
2026-06-23 21:43:54 +08:00
parent a32ffd5608
commit c06ba17916
4 changed files with 31 additions and 20 deletions
+19 -7
View File
@@ -202,39 +202,48 @@ func Test_findInterfaceByIP(t *testing.T) {
func TestParseInterfaceAddr(t *testing.T) {
// Empty interface name
ifce, addrs, err := ParseInterfaceAddr("", "tcp")
ifce, addrs, isIP, err := ParseInterfaceAddr("", "tcp")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if ifce != "" {
t.Errorf("expected empty ifce, got %s", ifce)
}
if isIP {
t.Errorf("expected isIP=false for empty string, got true")
}
if len(addrs) != 1 || addrs[0] != nil {
t.Errorf("expected [nil], got %v", addrs)
}
// Non-existent interface
_, _, err = ParseInterfaceAddr("nonexistent_interface_xyz", "tcp")
_, _, _, err = ParseInterfaceAddr("nonexistent_interface_xyz", "tcp")
if err == nil {
t.Error("expected error for non-existent interface")
}
// IP string as interface name
ifce, addrs, err = ParseInterfaceAddr("127.0.0.1", "tcp")
// IP string as interface name — isIP should be true
ifce, addrs, isIP, err = ParseInterfaceAddr("127.0.0.1", "tcp")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if !isIP {
t.Errorf("expected isIP=true for IP input, got false")
}
if ifce == "" {
t.Log("127.0.0.1 not found on any interface (may happen in containers)")
} else if len(addrs) != 1 {
t.Errorf("expected 1 addr, got %d", len(addrs))
}
// IP that doesn't exist on any interface
ifce, addrs, err = ParseInterfaceAddr("203.0.113.1", "udp")
// IP that doesn't exist on any interface — should still have isIP=true
ifce, addrs, isIP, err = ParseInterfaceAddr("203.0.113.1", "udp")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if !isIP {
t.Errorf("expected isIP=true for IP input, got false")
}
if ifce != "" {
t.Errorf("expected empty ifce for unassigned IP, got %s", ifce)
}
@@ -243,10 +252,13 @@ func TestParseInterfaceAddr(t *testing.T) {
}
// Test UDP network specific returns
ifce, addrs, err = ParseInterfaceAddr("127.0.0.1", "udp")
ifce, addrs, isIP, err = ParseInterfaceAddr("127.0.0.1", "udp")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if !isIP {
t.Errorf("expected isIP=true for IP input, got false")
}
if ifce != "" {
if len(addrs) != 1 {
t.Errorf("expected 1 addr, got %d", len(addrs))