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
zhen peng 6 days ago committed by GitHub
parent 3003a13440
commit 75526bb8e0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      cmd/utils/flags.go
  2. 5
      go.mod
  3. 34
      go.sum
  4. 7
      p2p/nat/nat.go
  5. 23
      p2p/nat/nat_test.go
  6. 5
      p2p/nat/stun-list-update.sh
  7. 96
      p2p/nat/stun-list.txt
  8. 144
      p2p/nat/stun.go
  9. 40
      p2p/nat/stun_test.go

@ -760,7 +760,7 @@ var (
}
NATFlag = &cli.StringFlag{
Name: "nat",
Usage: "NAT port mapping mechanism (any|none|upnp|pmp|pmp:<IP>|extip:<IP>)",
Usage: "NAT port mapping mechanism (any|none|upnp|pmp|pmp:<IP>|extip:<IP>|stun:<IP:PORT>)",
Value: "any",
Category: flags.NetworkingCategory,
}

@ -51,6 +51,7 @@ require (
github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416
github.com/olekukonko/tablewriter v0.0.5
github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7
github.com/pion/stun/v2 v2.0.0
github.com/protolambda/bls12-381-util v0.1.0
github.com/protolambda/zrnt v0.32.2
github.com/protolambda/ztyp v0.2.2
@ -126,6 +127,10 @@ require (
github.com/mmcloughlin/addchain v0.4.0 // indirect
github.com/naoina/go-stringutil v0.1.0 // indirect
github.com/opentracing/opentracing-go v1.1.0 // indirect
github.com/pion/dtls/v2 v2.2.7 // indirect
github.com/pion/logging v0.2.2 // indirect
github.com/pion/transport/v2 v2.2.1 // indirect
github.com/pion/transport/v3 v3.0.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.12.0 // indirect

@ -423,6 +423,16 @@ github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 h1:oYW+YCJ1pachXTQm
github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0=
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8=
github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
github.com/pion/stun/v2 v2.0.0 h1:A5+wXKLAypxQri59+tmQKVs7+l6mMM+3d+eER9ifRU0=
github.com/pion/stun/v2 v2.0.0/go.mod h1:22qRSh08fSEttYUmJZGlriq9+03jtVmXNODgLccj8GQ=
github.com/pion/transport/v2 v2.2.1 h1:7qYnCBlpgSJNYMbLCKuSY9KbQdBFoETvPNETv0y4N7c=
github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g=
github.com/pion/transport/v3 v3.0.1 h1:gDTlPJwROfSfz6QfSi0ZmeCSkFcnWWiiR9ES0ouANiM=
github.com/pion/transport/v3 v3.0.1/go.mod h1:UY7kiITrlMv7/IKgd5eTUcaahZx5oUN3l9SzK5f5xE0=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
@ -485,11 +495,17 @@ github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobt
github.com/status-im/keycard-go v0.2.0/go.mod h1:wlp8ZLbsmrF6g6WjugPAx+IzoLrkdf9+mHxBEeo3Hbg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/supranational/blst v0.3.13 h1:AYeSxdOMacwu7FBmpfloBz5pbFXDmJL33RuwnKtmTjk=
@ -528,6 +544,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@ -563,6 +581,7 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -601,6 +620,10 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@ -621,6 +644,7 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -680,7 +704,9 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
@ -689,6 +715,10 @@ golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -698,6 +728,9 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -750,6 +783,7 @@ golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

@ -59,13 +59,16 @@ type Interface interface {
// "upnp" uses the Universal Plug and Play protocol
// "pmp" uses NAT-PMP with an auto-detected gateway address
// "pmp:192.168.0.1" uses NAT-PMP with the given gateway address
// "stun" uses stun protocol with default stun server
// "stun:192.168.0.1:1234" uses stun protocol with stun server address 192.168.0.1:1234
func Parse(spec string) (Interface, error) {
var (
before, after, found = strings.Cut(spec, ":")
mech = strings.ToLower(before)
ip net.IP
)
if found {
// stun is not a valid ip
if found && mech != "stun" {
ip = net.ParseIP(after)
if ip == nil {
return nil, errors.New("invalid IP address")
@ -85,6 +88,8 @@ func Parse(spec string) (Interface, error) {
return UPnP(), nil
case "pmp", "natpmp", "nat-pmp":
return PMP(ip), nil
case "stun":
return newSTUN(after)
default:
return nil, fmt.Errorf("unknown mechanism %q", before)
}

@ -18,8 +18,11 @@ package nat
import (
"net"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
// This test checks that autodisc doesn't hang and returns
@ -61,3 +64,23 @@ func TestAutoDiscRace(t *testing.T) {
}
}
}
// stun should work well
func TestParseStun(t *testing.T) {
testcases := []struct {
natStr string
want *stun
}{
{"stun", &stun{serverList: strings.Split(stunDefaultServers, "\n")}},
{"stun:1.2.3.4:1234", &stun{serverList: []string{"1.2.3.4:1234"}}},
}
for _, tc := range testcases {
nat, err := Parse(tc.natStr)
if err != nil {
t.Errorf("should no err, but get %v", err)
}
stun := nat.(*stun)
assert.Equal(t, stun.serverList, tc.want.serverList)
}
}

@ -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…
Cancel
Save