mirror of https://github.com/ethereum/go-ethereum
p2p/nat: add stun protocol (#31064)
This implements a basic mechanism to query the node's external IP using a STUN server. There is a built-in list of public STUN servers for convenience. The new detection mechanism must be selected explicitly using `--nat=stun` and is not enabled by default in Geth. Fixes #30881 --------- Co-authored-by: Felix Lange <fjl@twurst.com>pull/31075/head
parent
3003a13440
commit
75526bb8e0
@ -0,0 +1,5 @@ |
||||
#!/bin/sh |
||||
|
||||
rm -f stun-list.txt |
||||
curl https://raw.githubusercontent.com/pradt2/always-online-stun/refs/heads/master/valid_ipv4s.txt | sort -n >> stun-list.txt |
||||
curl https://raw.githubusercontent.com/pradt2/always-online-stun/refs/heads/master/valid_ipv6s.txt | sort -n >> stun-list.txt |
@ -0,0 +1,96 @@ |
||||
3.132.228.249:3478 |
||||
3.78.237.53:3478 |
||||
5.161.52.174:3478 |
||||
5.161.57.75:3478 |
||||
18.185.125.152:3478 |
||||
20.93.239.167:3478 |
||||
23.21.199.62:3478 |
||||
23.21.92.55:3478 |
||||
24.204.48.11:3478 |
||||
31.184.236.23:3478 |
||||
34.195.177.19:3478 |
||||
34.197.205.39:3478 |
||||
34.206.168.53:3478 |
||||
34.74.124.204:3478 |
||||
35.158.233.7:3478 |
||||
35.177.202.92:3478 |
||||
35.180.81.93:3478 |
||||
44.230.252.214:3478 |
||||
45.15.102.34:3478 |
||||
49.12.125.53:3478 |
||||
51.255.31.35:3478 |
||||
51.68.112.203:3478 |
||||
51.68.45.75:3478 |
||||
51.83.15.212:3478 |
||||
51.83.201.84:3478 |
||||
52.24.174.49:3478 |
||||
52.26.251.34:3478 |
||||
52.47.70.236:3478 |
||||
52.52.70.85:3478 |
||||
54.197.117.0:3478 |
||||
62.72.83.10:3478 |
||||
66.228.54.23:3478 |
||||
69.20.59.115:3478 |
||||
79.140.42.88:3478 |
||||
80.155.54.123:3478 |
||||
80.156.214.187:3478 |
||||
81.3.27.44:3478 |
||||
81.82.206.117:3478 |
||||
81.83.12.46:3478 |
||||
85.197.87.182:3478 |
||||
87.253.140.133:3478 |
||||
88.198.151.128:3478 |
||||
88.218.220.40:3478 |
||||
88.99.67.241:3478 |
||||
90.145.158.66:3478 |
||||
91.212.41.85:3478 |
||||
92.205.106.161:3478 |
||||
94.140.180.141:3478 |
||||
94.23.17.185:3478 |
||||
95.216.145.84:3478 |
||||
95.216.78.222:3478 |
||||
129.153.212.128:3478 |
||||
136.243.59.79:3478 |
||||
137.74.112.113:3478 |
||||
143.198.60.79:3478 |
||||
147.182.188.245:3478 |
||||
157.161.10.32:3478 |
||||
159.69.191.124:3478 |
||||
159.69.191.124:443 |
||||
172.233.245.118:3478 |
||||
176.9.24.184:3478 |
||||
185.125.180.70:3478 |
||||
185.88.236.76:3478 |
||||
188.138.90.169:3478 |
||||
188.40.18.246:3478 |
||||
188.40.203.74:3478 |
||||
192.172.233.145:3478 |
||||
192.76.120.66:3478 |
||||
193.182.111.151:3478 |
||||
193.22.17.97:3478 |
||||
194.149.74.157:3478 |
||||
195.145.93.141:3478 |
||||
195.201.132.113:3478 |
||||
195.208.107.138:3478 |
||||
198.100.144.121:3478 |
||||
209.251.63.76:3478 |
||||
212.103.68.7:3478 |
||||
212.144.246.197:3478 |
||||
212.18.0.14:3478 |
||||
212.53.40.40:3478 |
||||
212.53.40.43:3478 |
||||
213.239.206.5:3478 |
||||
213.251.48.147:3478 |
||||
217.146.224.74:3478 |
||||
217.91.243.229:3478 |
||||
[2001:1538:1::224:74]:3478 |
||||
[2001:4060:1:1005::10:32]:3478 |
||||
[2001:4060:1:1005::10:32]:3478 |
||||
[2001:41d0:2:12b9::1]:3478 |
||||
[2001:678:b28::118]:3478 |
||||
[2600:1f16:8c5:101:80b:b58b:828:8df4]:3478 |
||||
[2a00:1169:11b:a6b0::]:3478 |
||||
[2a01:4f8:242:56ca::2]:3478 |
||||
[2a01:4f8:c17:8f74::1]:3478 |
||||
[2a01:4f8:c17:8f74::1]:443 |
||||
[2a03:8600::89]:3478 |
@ -0,0 +1,144 @@ |
||||
// Copyright 2025 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package nat |
||||
|
||||
import ( |
||||
_ "embed" |
||||
"errors" |
||||
"fmt" |
||||
"math/rand" |
||||
"net" |
||||
"strings" |
||||
"time" |
||||
|
||||
"github.com/ethereum/go-ethereum/log" |
||||
stunV2 "github.com/pion/stun/v2" |
||||
) |
||||
|
||||
//go:embed stun-list.txt
|
||||
var stunDefaultServers string |
||||
|
||||
const requestLimit = 3 |
||||
|
||||
var errSTUNFailed = errors.New("STUN requests failed") |
||||
|
||||
type stun struct { |
||||
serverList []string |
||||
} |
||||
|
||||
func newSTUN(serverAddr string) (Interface, error) { |
||||
s := new(stun) |
||||
if serverAddr == "" { |
||||
s.serverList = strings.Split(stunDefaultServers, "\n") |
||||
} else { |
||||
_, err := net.ResolveUDPAddr("udp4", serverAddr) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
s.serverList = []string{serverAddr} |
||||
} |
||||
return s, nil |
||||
} |
||||
|
||||
func (s stun) String() string { |
||||
if len(s.serverList) == 1 { |
||||
return fmt.Sprintf("stun:%s", s.serverList[0]) |
||||
} |
||||
return "stun" |
||||
} |
||||
|
||||
func (s stun) MarshalText() ([]byte, error) { |
||||
return []byte(s.String()), nil |
||||
} |
||||
|
||||
func (stun) SupportsMapping() bool { |
||||
return false |
||||
} |
||||
|
||||
func (stun) AddMapping(protocol string, extport, intport int, name string, lifetime time.Duration) (uint16, error) { |
||||
return uint16(extport), nil |
||||
} |
||||
|
||||
func (stun) DeleteMapping(string, int, int) error { |
||||
return nil |
||||
} |
||||
|
||||
func (s *stun) ExternalIP() (net.IP, error) { |
||||
for _, server := range s.randomServers(requestLimit) { |
||||
ip, err := s.externalIP(server) |
||||
if err != nil { |
||||
log.Debug("STUN request failed", "server", server, "err", err) |
||||
continue |
||||
} |
||||
return ip, nil |
||||
} |
||||
return nil, errSTUNFailed |
||||
} |
||||
|
||||
func (s *stun) randomServers(n int) []string { |
||||
n = min(n, len(s.serverList)) |
||||
m := make(map[int]struct{}, n) |
||||
list := make([]string, 0, n) |
||||
for i := 0; i < len(s.serverList)*2 && len(list) < n; i++ { |
||||
index := rand.Intn(len(s.serverList)) |
||||
if _, alreadyHit := m[index]; alreadyHit { |
||||
continue |
||||
} |
||||
list = append(list, s.serverList[index]) |
||||
m[index] = struct{}{} |
||||
} |
||||
return list |
||||
} |
||||
|
||||
func (s *stun) externalIP(server string) (net.IP, error) { |
||||
_, _, err := net.SplitHostPort(server) |
||||
if err != nil { |
||||
server += fmt.Sprintf(":%d", stunV2.DefaultPort) |
||||
} |
||||
|
||||
log.Trace("Attempting STUN binding request", "server", server) |
||||
conn, err := stunV2.Dial("udp4", server) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
defer conn.Close() |
||||
|
||||
message, err := stunV2.Build(stunV2.TransactionID, stunV2.BindingRequest) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
var responseError error |
||||
var mappedAddr stunV2.XORMappedAddress |
||||
err = conn.Do(message, func(event stunV2.Event) { |
||||
if event.Error != nil { |
||||
responseError = event.Error |
||||
return |
||||
} |
||||
if err := mappedAddr.GetFrom(event.Message); err != nil { |
||||
responseError = err |
||||
} |
||||
}) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
if responseError != nil { |
||||
return nil, responseError |
||||
} |
||||
log.Trace("STUN returned IP", "server", server, "ip", mappedAddr.IP) |
||||
return mappedAddr.IP, nil |
||||
} |
@ -0,0 +1,40 @@ |
||||
// Copyright 2025 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package nat |
||||
|
||||
import ( |
||||
"testing" |
||||
|
||||
"github.com/stretchr/testify/assert" |
||||
) |
||||
|
||||
func TestNatStun(t *testing.T) { |
||||
nat, err := newSTUN("") |
||||
assert.NoError(t, err) |
||||
_, err = nat.ExternalIP() |
||||
assert.NoError(t, err) |
||||
} |
||||
|
||||
func TestUnreachedNatServer(t *testing.T) { |
||||
stun := &stun{ |
||||
serverList: []string{"198.51.100.2:1234", "198.51.100.5"}, |
||||
} |
||||
_, err := stun.ExternalIP() |
||||
if err != errSTUNFailed { |
||||
t.Fatal("wrong error:", err) |
||||
} |
||||
} |
Loading…
Reference in new issue