diff --git a/p2p/nat/natpmp.go b/p2p/nat/natpmp.go
index ee07eb4ff6..5e27e754b0 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 0000000000..7cce07daf9
--- /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 0000000000..1fd2bf0c7a
--- /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 0000000000..5c83a78e8a
--- /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
+}