From 5cfb14fb2a4b10547c9285e06cd845177063e660 Mon Sep 17 00:00:00 2001 From: Jackson Gothie Date: Thu, 28 May 2026 16:07:06 -0500 Subject: [PATCH] p2p/nat: use default route for nat-pmp gateway --- p2p/nat/natpmp.go | 27 +++++++++-- p2p/nat/natpmp_gateway_linux.go | 72 ++++++++++++++++++++++++++++ p2p/nat/natpmp_gateway_linux_test.go | 56 ++++++++++++++++++++++ p2p/nat/natpmp_gateway_other.go | 25 ++++++++++ 4 files changed, 176 insertions(+), 4 deletions(-) create mode 100644 p2p/nat/natpmp_gateway_linux.go create mode 100644 p2p/nat/natpmp_gateway_linux_test.go create mode 100644 p2p/nat/natpmp_gateway_other.go diff --git a/p2p/nat/natpmp.go b/p2p/nat/natpmp.go index ee07eb4ff68e..5e27e754b04d 100644 --- a/p2p/nat/natpmp.go +++ b/p2p/nat/natpmp.go @@ -110,14 +110,29 @@ func discoverPMP() Interface { return nil } -// TODO: improve this. We currently assume that (on most networks) -// the router is X.X.X.1 in a local LAN range. +// TODO: improve this further. We prefer discovered default gateways when +// available and otherwise fall back to assuming the router is X.X.X.1 in a +// local LAN range. func potentialGateways() (gws []net.IP) { + seen := make(map[string]struct{}) + for _, ip := range defaultGatewayIPs() { + if ip = ip.To4(); ip != nil { + key := ip.String() + if _, ok := seen[key]; !ok { + seen[key] = struct{}{} + gws = append(gws, ip) + } + } + } + ifaces, err := net.Interfaces() if err != nil { - return nil + return gws } for _, iface := range ifaces { + if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagLoopback != 0 { + continue + } ifaddrs, err := iface.Addrs() if err != nil { return gws @@ -128,7 +143,11 @@ func potentialGateways() (gws []net.IP) { ip := x.IP.Mask(x.Mask).To4() if ip != nil { ip[3] = ip[3] | 0x01 - gws = append(gws, ip) + key := ip.String() + if _, ok := seen[key]; !ok { + seen[key] = struct{}{} + gws = append(gws, ip) + } } } } diff --git a/p2p/nat/natpmp_gateway_linux.go b/p2p/nat/natpmp_gateway_linux.go new file mode 100644 index 000000000000..7cce07daf929 --- /dev/null +++ b/p2p/nat/natpmp_gateway_linux.go @@ -0,0 +1,72 @@ +// Copyright 2026 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +//go:build linux + +package nat + +import ( + "bufio" + "bytes" + "encoding/binary" + "net" + "os" + "strconv" + "strings" +) + +func defaultGatewayIPs() []net.IP { + data, err := os.ReadFile("/proc/net/route") + if err != nil { + return nil + } + return parseLinuxRouteTable(data) +} + +func parseLinuxRouteTable(data []byte) []net.IP { + var gws []net.IP + + scanner := bufio.NewScanner(bytes.NewReader(data)) + if !scanner.Scan() { + return nil + } + for scanner.Scan() { + fields := strings.Fields(scanner.Text()) + if len(fields) < 4 || fields[1] != "00000000" { + continue + } + flags, err := strconv.ParseUint(fields[3], 16, 16) + if err != nil || flags&0x2 == 0 { + continue + } + gw, err := parseLinuxRouteHexIPv4(fields[2]) + if err != nil || gw == nil { + continue + } + gws = append(gws, gw) + } + return gws +} + +func parseLinuxRouteHexIPv4(s string) (net.IP, error) { + n, err := strconv.ParseUint(s, 16, 32) + if err != nil { + return nil, err + } + var buf [4]byte + binary.LittleEndian.PutUint32(buf[:], uint32(n)) + return net.IPv4(buf[0], buf[1], buf[2], buf[3]).To4(), nil +} diff --git a/p2p/nat/natpmp_gateway_linux_test.go b/p2p/nat/natpmp_gateway_linux_test.go new file mode 100644 index 000000000000..1fd2bf0c7a1c --- /dev/null +++ b/p2p/nat/natpmp_gateway_linux_test.go @@ -0,0 +1,56 @@ +// Copyright 2026 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +//go:build linux + +package nat + +import ( + "net" + "testing" +) + +func TestParseLinuxRouteTable(t *testing.T) { + data := []byte(`Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT +eth0 00000000 0101A8C0 0003 0 0 100 00000000 0 0 0 +eth0 0001A8C0 00000000 0001 0 0 100 00FFFFFF 0 0 0 +wlan0 00000000 FE01A8C0 0003 0 0 600 00000000 0 0 0 +`) + got := parseLinuxRouteTable(data) + want := []net.IP{ + net.IPv4(192, 168, 1, 1).To4(), + net.IPv4(192, 168, 1, 254).To4(), + } + if len(got) != len(want) { + t.Fatalf("got %d gateways, want %d", len(got), len(want)) + } + for i := range want { + if !got[i].Equal(want[i]) { + t.Fatalf("gateway %d: got %v, want %v", i, got[i], want[i]) + } + } +} + +func TestParseLinuxRouteHexIPv4(t *testing.T) { + got, err := parseLinuxRouteHexIPv4("0101A8C0") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + want := net.IPv4(192, 168, 1, 1).To4() + if !got.Equal(want) { + t.Fatalf("got %v, want %v", got, want) + } +} diff --git a/p2p/nat/natpmp_gateway_other.go b/p2p/nat/natpmp_gateway_other.go new file mode 100644 index 000000000000..5c83a78e8a3a --- /dev/null +++ b/p2p/nat/natpmp_gateway_other.go @@ -0,0 +1,25 @@ +// Copyright 2026 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +//go:build !linux + +package nat + +import "net" + +func defaultGatewayIPs() []net.IP { + return nil +}