From 75526bb8e01b189fc11b12e2bccebc09b2f8f687 Mon Sep 17 00:00:00 2001 From: zhen peng <505380967@qq.com> Date: Fri, 24 Jan 2025 23:16:02 +0800 Subject: [PATCH] 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 --- cmd/utils/flags.go | 2 +- go.mod | 5 ++ go.sum | 34 +++++++++ p2p/nat/nat.go | 7 +- p2p/nat/nat_test.go | 23 ++++++ p2p/nat/stun-list-update.sh | 5 ++ p2p/nat/stun-list.txt | 96 ++++++++++++++++++++++++ p2p/nat/stun.go | 144 ++++++++++++++++++++++++++++++++++++ p2p/nat/stun_test.go | 40 ++++++++++ 9 files changed, 354 insertions(+), 2 deletions(-) create mode 100755 p2p/nat/stun-list-update.sh create mode 100644 p2p/nat/stun-list.txt create mode 100644 p2p/nat/stun.go create mode 100644 p2p/nat/stun_test.go diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 8ef39e88dea3..f079df83b9da 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -760,7 +760,7 @@ var ( } NATFlag = &cli.StringFlag{ Name: "nat", - Usage: "NAT port mapping mechanism (any|none|upnp|pmp|pmp:|extip:)", + Usage: "NAT port mapping mechanism (any|none|upnp|pmp|pmp:|extip:|stun:)", Value: "any", Category: flags.NetworkingCategory, } diff --git a/go.mod b/go.mod index cd1b989e7cf0..fc768460c7c7 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 2e51046d8252..82836a5d8c05 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/p2p/nat/nat.go b/p2p/nat/nat.go index f7fe8196c910..5f7af7424d7c 100644 --- a/p2p/nat/nat.go +++ b/p2p/nat/nat.go @@ -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) } diff --git a/p2p/nat/nat_test.go b/p2p/nat/nat_test.go index 814e6d9e14c8..8dd5644fd60b 100644 --- a/p2p/nat/nat_test.go +++ b/p2p/nat/nat_test.go @@ -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) + } +} diff --git a/p2p/nat/stun-list-update.sh b/p2p/nat/stun-list-update.sh new file mode 100755 index 000000000000..901bfe4ba437 --- /dev/null +++ b/p2p/nat/stun-list-update.sh @@ -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 diff --git a/p2p/nat/stun-list.txt b/p2p/nat/stun-list.txt new file mode 100644 index 000000000000..bc27f7e5a87d --- /dev/null +++ b/p2p/nat/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 diff --git a/p2p/nat/stun.go b/p2p/nat/stun.go new file mode 100644 index 000000000000..36d54398b584 --- /dev/null +++ b/p2p/nat/stun.go @@ -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 . + +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 +} diff --git a/p2p/nat/stun_test.go b/p2p/nat/stun_test.go new file mode 100644 index 000000000000..b5f429986c6d --- /dev/null +++ b/p2p/nat/stun_test.go @@ -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 . + +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) + } +}