github.com/ethereum/go-ethereum@v1.16.1/p2p/nat/stun.go (about) 1 // Copyright 2025 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package nat 18 19 import ( 20 _ "embed" 21 "errors" 22 "fmt" 23 "math/rand" 24 "net" 25 "strings" 26 "time" 27 28 "github.com/ethereum/go-ethereum/log" 29 stunV2 "github.com/pion/stun/v2" 30 ) 31 32 //go:embed stun-list.txt 33 var stunDefaultServers string 34 35 const requestLimit = 3 36 37 var errSTUNFailed = errors.New("STUN requests failed") 38 39 type stun struct { 40 serverList []string 41 } 42 43 func newSTUN(serverAddr string) (Interface, error) { 44 s := new(stun) 45 if serverAddr == "" { 46 s.serverList = strings.Split(stunDefaultServers, "\n") 47 } else { 48 _, err := net.ResolveUDPAddr("udp4", serverAddr) 49 if err != nil { 50 return nil, err 51 } 52 s.serverList = []string{serverAddr} 53 } 54 return s, nil 55 } 56 57 func (s stun) String() string { 58 if len(s.serverList) == 1 { 59 return fmt.Sprintf("stun:%s", s.serverList[0]) 60 } 61 return "stun" 62 } 63 64 func (s stun) MarshalText() ([]byte, error) { 65 return []byte(s.String()), nil 66 } 67 68 func (stun) SupportsMapping() bool { 69 return false 70 } 71 72 func (stun) AddMapping(protocol string, extport, intport int, name string, lifetime time.Duration) (uint16, error) { 73 return uint16(extport), nil 74 } 75 76 func (stun) DeleteMapping(string, int, int) error { 77 return nil 78 } 79 80 func (s *stun) ExternalIP() (net.IP, error) { 81 for _, server := range s.randomServers(requestLimit) { 82 ip, err := s.externalIP(server) 83 if err != nil { 84 log.Debug("STUN request failed", "server", server, "err", err) 85 continue 86 } 87 return ip, nil 88 } 89 return nil, errSTUNFailed 90 } 91 92 func (s *stun) randomServers(n int) []string { 93 n = min(n, len(s.serverList)) 94 m := make(map[int]struct{}, n) 95 list := make([]string, 0, n) 96 for i := 0; i < len(s.serverList)*2 && len(list) < n; i++ { 97 index := rand.Intn(len(s.serverList)) 98 if _, alreadyHit := m[index]; alreadyHit { 99 continue 100 } 101 list = append(list, s.serverList[index]) 102 m[index] = struct{}{} 103 } 104 return list 105 } 106 107 func (s *stun) externalIP(server string) (net.IP, error) { 108 _, _, err := net.SplitHostPort(server) 109 if err != nil { 110 server += fmt.Sprintf(":%d", stunV2.DefaultPort) 111 } 112 113 log.Trace("Attempting STUN binding request", "server", server) 114 conn, err := stunV2.Dial("udp4", server) 115 if err != nil { 116 return nil, err 117 } 118 defer conn.Close() 119 120 message, err := stunV2.Build(stunV2.TransactionID, stunV2.BindingRequest) 121 if err != nil { 122 return nil, err 123 } 124 125 var responseError error 126 var mappedAddr stunV2.XORMappedAddress 127 err = conn.Do(message, func(event stunV2.Event) { 128 if event.Error != nil { 129 responseError = event.Error 130 return 131 } 132 if err := mappedAddr.GetFrom(event.Message); err != nil { 133 responseError = err 134 } 135 }) 136 if err != nil { 137 return nil, err 138 } 139 if responseError != nil { 140 return nil, responseError 141 } 142 log.Trace("STUN returned IP", "server", server, "ip", mappedAddr.IP) 143 return mappedAddr.IP, nil 144 }