net/netns: use AF_ROUTE socket to determine what to bind to

Updates #5719

Signed-off-by: Andrew Dunham <andrew@du.nham.ca>
This commit is contained in:
Andrew Dunham 2022-09-21 22:09:29 -04:00
parent 0607832397
commit eb6343cdaf
2 changed files with 84 additions and 4 deletions

View File

@ -10,6 +10,7 @@
"log" "log"
"net" "net"
"net/netip" "net/netip"
"os"
"syscall" "syscall"
"golang.org/x/net/route" "golang.org/x/net/route"
@ -86,6 +87,76 @@ func DefaultRouteInterfaceIndex() (int, error) {
return 0, fmt.Errorf("ambiguous gateway interfaces found: %v", indexSeen) return 0, fmt.Errorf("ambiguous gateway interfaces found: %v", indexSeen)
} }
// InterfaceIndexFor returns the interface index that we should bind to in
// order to send traffic to the provided address.
func InterfaceIndexFor(addr netip.Addr) (int, error) {
fd, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, unix.AF_UNSPEC)
if err != nil {
return 0, fmt.Errorf("creating AF_ROUTE socket: %w", err)
}
defer unix.Close(fd)
var routeAddr route.Addr
if addr.Is4() {
routeAddr = &route.Inet4Addr{IP: addr.As4()}
} else {
routeAddr = &route.Inet6Addr{IP: addr.As16()}
}
rm := route.RouteMessage{
Version: unix.RTM_VERSION,
Type: unix.RTM_GET,
Flags: unix.RTF_UP,
ID: uintptr(os.Getpid()),
Seq: 1,
Addrs: []route.Addr{
unix.RTAX_DST: routeAddr,
},
}
b, err := rm.Marshal()
if err != nil {
return 0, fmt.Errorf("marshaling RouteMessage: %w", err)
}
_, err = unix.Write(fd, b)
if err != nil {
return 0, fmt.Errorf("writing message: %w")
}
var buf [2048]byte
n, err := unix.Read(fd, buf[:])
if err != nil {
return 0, fmt.Errorf("reading message: %w", err)
}
msgs, err := route.ParseRIB(route.RIBTypeRoute, buf[:n])
if err != nil {
return 0, fmt.Errorf("route.ParseRIB: %w", err)
}
if len(msgs) == 0 {
return 0, fmt.Errorf("no messages")
}
for _, msg := range msgs {
rm, ok := msg.(*route.RouteMessage)
if !ok {
continue
}
if rm.Version < 3 || rm.Version > 5 || rm.Type != unix.RTM_GET {
continue
}
if len(rm.Addrs) < unix.RTAX_GATEWAY {
continue
}
laddr, ok := rm.Addrs[unix.RTAX_GATEWAY].(*route.LinkAddr)
if !ok {
continue
}
return laddr.Index, nil
}
return 0, fmt.Errorf("no valid address found")
}
func init() { func init() {
likelyHomeRouterIP = likelyHomeRouterIPDarwinFetchRIB likelyHomeRouterIP = likelyHomeRouterIPDarwinFetchRIB
} }

View File

@ -9,6 +9,7 @@
import ( import (
"fmt" "fmt"
"net/netip"
"strings" "strings"
"syscall" "syscall"
@ -32,12 +33,20 @@ func controlLogf(logf logger.Logf, network, address string, c syscall.RawConn) e
// Don't bind to an interface for localhost connections. // Don't bind to an interface for localhost connections.
return nil return nil
} }
idx, err := interfaces.DefaultRouteInterfaceIndex() addr, err := netip.ParseAddr(address)
if err != nil {
return fmt.Errorf("netip.ParseAddr(%s): %w", address, err)
}
idx, err := interfaces.InterfaceIndexFor(addr)
if err != nil {
logf("[unexpected] netns: InterfaceIndexFor failed; falling back to default: %v", err)
idx, err = interfaces.DefaultRouteInterfaceIndex()
if err != nil { if err != nil {
logf("[unexpected] netns: DefaultRouteInterfaceIndex: %v", err) logf("[unexpected] netns: DefaultRouteInterfaceIndex: %v", err)
return nil return nil
} }
v6 := strings.Contains(address, "]:") || strings.HasSuffix(network, "6") // hacky test for v6 }
v6 := addr.Is6()
proto := unix.IPPROTO_IP proto := unix.IPPROTO_IP
opt := unix.IP_BOUND_IF opt := unix.IP_BOUND_IF
if v6 { if v6 {