github.com/ethereum/go-ethereum@v1.16.1/p2p/nat/nat.go (about) 1 // Copyright 2015 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 provides access to common network port mapping protocols. 18 package nat 19 20 import ( 21 "errors" 22 "fmt" 23 "net" 24 "strings" 25 "sync" 26 "time" 27 28 "github.com/ethereum/go-ethereum/log" 29 natpmp "github.com/jackpal/go-nat-pmp" 30 ) 31 32 // Interface An implementation of nat.Interface can map local ports to ports 33 // accessible from the Internet. 34 type Interface interface { 35 // These methods manage a mapping between a port on the local 36 // machine to a port that can be connected to from the internet. 37 // 38 // protocol is "UDP" or "TCP". Some implementations allow setting 39 // a display name for the mapping. The mapping may be removed by 40 // the gateway when its lifetime ends. 41 AddMapping(protocol string, extport, intport int, name string, lifetime time.Duration) (uint16, error) 42 DeleteMapping(protocol string, extport, intport int) error 43 44 // ExternalIP should return the external (Internet-facing) 45 // address of the gateway device. 46 ExternalIP() (net.IP, error) 47 48 // String should return name of the method. This is used for logging. 49 String() string 50 } 51 52 // Parse parses a NAT interface description. 53 // The following formats are currently accepted. 54 // Note that mechanism names are not case-sensitive. 55 // 56 // "" or "none" return nil 57 // "extip:77.12.33.4" will assume the local machine is reachable on the given IP 58 // "any" uses the first auto-detected mechanism 59 // "upnp" uses the Universal Plug and Play protocol 60 // "pmp" uses NAT-PMP with an auto-detected gateway address 61 // "pmp:192.168.0.1" uses NAT-PMP with the given gateway address 62 // "stun" uses stun protocol with default stun server 63 // "stun:192.168.0.1:1234" uses stun protocol with stun server address 192.168.0.1:1234 64 func Parse(spec string) (Interface, error) { 65 var ( 66 before, after, found = strings.Cut(spec, ":") 67 mech = strings.ToLower(before) 68 ip net.IP 69 ) 70 // stun is not a valid ip 71 if found && mech != "stun" { 72 ip = net.ParseIP(after) 73 if ip == nil { 74 return nil, errors.New("invalid IP address") 75 } 76 } 77 switch mech { 78 case "", "none", "off": 79 return nil, nil 80 case "any", "auto", "on": 81 return Any(), nil 82 case "extip", "ip": 83 if ip == nil { 84 return nil, errors.New("missing IP address") 85 } 86 return ExtIP(ip), nil 87 case "upnp": 88 return UPnP(), nil 89 case "pmp", "natpmp", "nat-pmp": 90 return PMP(ip), nil 91 case "stun": 92 return newSTUN(after) 93 default: 94 return nil, fmt.Errorf("unknown mechanism %q", before) 95 } 96 } 97 98 const ( 99 DefaultMapTimeout = 10 * time.Minute 100 ) 101 102 // Map adds a port mapping on m and keeps it alive until c is closed. 103 // This function is typically invoked in its own goroutine. 104 // 105 // Note that Map does not handle the situation where the NAT interface assigns a different 106 // external port than the requested one. 107 func Map(m Interface, c <-chan struct{}, protocol string, extport, intport int, name string) { 108 log := log.New("proto", protocol, "extport", extport, "intport", intport, "interface", m) 109 refresh := time.NewTimer(DefaultMapTimeout) 110 defer func() { 111 refresh.Stop() 112 log.Debug("Deleting port mapping") 113 m.DeleteMapping(protocol, extport, intport) 114 }() 115 if _, err := m.AddMapping(protocol, extport, intport, name, DefaultMapTimeout); err != nil { 116 log.Debug("Couldn't add port mapping", "err", err) 117 } else { 118 log.Info("Mapped network port") 119 } 120 for { 121 select { 122 case _, ok := <-c: 123 if !ok { 124 return 125 } 126 case <-refresh.C: 127 log.Trace("Refreshing port mapping") 128 if _, err := m.AddMapping(protocol, extport, intport, name, DefaultMapTimeout); err != nil { 129 log.Debug("Couldn't add port mapping", "err", err) 130 } 131 refresh.Reset(DefaultMapTimeout) 132 } 133 } 134 } 135 136 // ExtIP assumes that the local machine is reachable on the given 137 // external IP address, and that any required ports were mapped manually. 138 // Mapping operations will not return an error but won't actually do anything. 139 type ExtIP net.IP 140 141 func (n ExtIP) ExternalIP() (net.IP, error) { return net.IP(n), nil } 142 func (n ExtIP) String() string { return fmt.Sprintf("ExtIP(%v)", net.IP(n)) } 143 func (n ExtIP) MarshalText() ([]byte, error) { return fmt.Appendf(nil, "extip:%v", net.IP(n)), nil } 144 145 // These do nothing. 146 147 func (ExtIP) AddMapping(protocol string, extport, intport int, name string, lifetime time.Duration) (uint16, error) { 148 return uint16(extport), nil 149 } 150 func (ExtIP) DeleteMapping(string, int, int) error { return nil } 151 152 // Any returns a port mapper that tries to discover any supported 153 // mechanism on the local network. 154 func Any() Interface { 155 // TODO: attempt to discover whether the local machine has an 156 // Internet-class address. Return ExtIP in this case. 157 return startautodisc("any", func() Interface { 158 found := make(chan Interface, 2) 159 go func() { found <- discoverUPnP() }() 160 go func() { found <- discoverPMP() }() 161 for i := 0; i < cap(found); i++ { 162 if c := <-found; c != nil { 163 return c 164 } 165 } 166 return nil 167 }) 168 } 169 170 // UPnP returns a port mapper that uses UPnP. It will attempt to 171 // discover the address of your router using UDP broadcasts. 172 func UPnP() Interface { 173 return startautodisc("upnp", discoverUPnP) 174 } 175 176 // PMP returns a port mapper that uses NAT-PMP. The provided gateway 177 // address should be the IP of your router. If the given gateway 178 // address is nil, PMP will attempt to auto-discover the router. 179 func PMP(gateway net.IP) Interface { 180 if gateway != nil { 181 return &pmp{gw: gateway, c: natpmp.NewClient(gateway)} 182 } 183 return startautodisc("natpmp", discoverPMP) 184 } 185 186 // autodisc represents a port mapping mechanism that is still being 187 // auto-discovered. Calls to the Interface methods on this type will 188 // wait until the discovery is done and then call the method on the 189 // discovered mechanism. 190 // 191 // This type is useful because discovery can take a while but we 192 // want return an Interface value from UPnP, PMP and Auto immediately. 193 type autodisc struct { 194 what string // type of interface being autodiscovered 195 once sync.Once 196 doit func() Interface 197 198 mu sync.Mutex 199 found Interface 200 } 201 202 func startautodisc(what string, doit func() Interface) Interface { 203 // TODO: monitor network configuration and rerun doit when it changes. 204 return &autodisc{what: what, doit: doit} 205 } 206 207 func (n *autodisc) AddMapping(protocol string, extport, intport int, name string, lifetime time.Duration) (uint16, error) { 208 if err := n.wait(); err != nil { 209 return 0, err 210 } 211 return n.found.AddMapping(protocol, extport, intport, name, lifetime) 212 } 213 214 func (n *autodisc) DeleteMapping(protocol string, extport, intport int) error { 215 if err := n.wait(); err != nil { 216 return err 217 } 218 return n.found.DeleteMapping(protocol, extport, intport) 219 } 220 221 func (n *autodisc) ExternalIP() (net.IP, error) { 222 if err := n.wait(); err != nil { 223 return nil, err 224 } 225 return n.found.ExternalIP() 226 } 227 228 func (n *autodisc) String() string { 229 n.mu.Lock() 230 defer n.mu.Unlock() 231 if n.found == nil { 232 return n.what 233 } 234 return n.found.String() 235 } 236 237 func (n *autodisc) MarshalText() ([]byte, error) { 238 return []byte(n.what), nil 239 } 240 241 // wait blocks until auto-discovery has been performed. 242 func (n *autodisc) wait() error { 243 n.once.Do(func() { 244 n.mu.Lock() 245 n.found = n.doit() 246 n.mu.Unlock() 247 }) 248 if n.found == nil { 249 return fmt.Errorf("no %s router discovered", n.what) 250 } 251 return nil 252 }