github.com/ethereumproject/go-ethereum@v5.5.2+incompatible/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 "strconv" 25 "strings" 26 "sync" 27 "time" 28 29 "github.com/ethereumproject/go-ethereum/logger" 30 "github.com/ethereumproject/go-ethereum/logger/glog" 31 "github.com/jackpal/go-nat-pmp" 32 ) 33 34 // An implementation of nat.Interface can map local ports to ports 35 // accessible from the Internet. 36 type Interface interface { 37 // These methods manage a mapping between a port on the local 38 // machine to a port that can be connected to from the internet. 39 // 40 // protocol is "UDP" or "TCP". Some implementations allow setting 41 // a display name for the mapping. The mapping may be removed by 42 // the gateway when its lifetime ends. 43 AddMapping(protocol string, extport, intport int, name string, lifetime time.Duration) error 44 DeleteMapping(protocol string, extport, intport int) error 45 46 // This method should return the external (Internet-facing) 47 // address of the gateway device. 48 ExternalIP() (net.IP, error) 49 50 // Should return name of the method. This is used for logging. 51 String() string 52 } 53 54 type autodiscoverInterfaceType string 55 56 const ( 57 autoDiscUPnPOrNatPMP = "UPnP or NAT-PMP" 58 autoDiscUPnP = "UPnP" 59 autoDiscNatPMP = "NAT-PMP" 60 ) 61 62 // Parse parses a NAT interface description. 63 // The following formats are currently accepted. 64 // Note that mechanism names are not case-sensitive. 65 // 66 // "" or "none" return nil 67 // "extip:77.12.33.4" will assume the local machine is reachable on the given IP 68 // "any" uses the first auto-detected mechanism 69 // "upnp" uses the Universal Plug and Play protocol 70 // "pmp" uses NAT-PMP with an auto-detected gateway address 71 // "pmp:192.168.0.1" uses NAT-PMP with the given gateway address 72 func Parse(spec string) (Interface, error) { 73 var ( 74 parts = strings.SplitN(spec, ":", 2) 75 mech = strings.ToLower(parts[0]) 76 ip net.IP 77 ) 78 if len(parts) > 1 { 79 ip = net.ParseIP(parts[1]) 80 if ip == nil { 81 return nil, errors.New("invalid IP address") 82 } 83 } 84 switch mech { 85 case "", "none", "off": 86 return nil, nil 87 case "any", "auto", "on": 88 return Any(), nil 89 case "extip", "ip": 90 if ip == nil { 91 return nil, errors.New("missing IP address") 92 } 93 return ExtIP(ip), nil 94 case "upnp": 95 return UPnP(), nil 96 case "pmp", "natpmp", "nat-pmp": 97 return PMP(ip), nil 98 default: 99 return nil, fmt.Errorf("unknown mechanism %q", parts[0]) 100 } 101 } 102 103 const ( 104 mapTimeout = 20 * time.Minute 105 mapUpdateInterval = 15 * time.Minute 106 ) 107 108 // Map adds a port mapping on m and keeps it alive until c is closed. 109 // This function is typically invoked in its own goroutine. 110 func Map(m Interface, c chan struct{}, protocol string, extport, intport int, name string) { 111 refresh := time.NewTimer(mapUpdateInterval) 112 113 defer func() { 114 refresh.Stop() 115 glog.V(logger.Debug).Infof("Deleting port mapping: %s %d -> %d (%s) using %s\n", protocol, extport, intport, name, m) 116 m.DeleteMapping(protocol, extport, intport) 117 }() 118 119 refreshPortMappingLabel := "Refresh port mapping" 120 121 handleIfAddMappingErr := func(label string, err error) { 122 if err == nil { 123 glog.V(logger.Debug).Infof("%s %s:%d -> %d (%s) using %s\n", label, protocol, extport, intport, name, m) 124 // eg on start up 125 if label != refreshPortMappingLabel { 126 glog.D(logger.Info).Infof("%s %s:%s -> %s (%s) using %s\n", label, logger.ColorGreen(protocol), logger.ColorGreen(strconv.Itoa(extport)), logger.ColorGreen(strconv.Itoa(intport)), name, m) 127 } 128 } else { 129 switch m.String() { 130 // if upnp error, not critical 131 case autoDiscUPnPOrNatPMP: 132 glog.V(logger.Debug).Infof("%s: Network port %s:%d could not be mapped: %v\n", label, protocol, intport, err) 133 return 134 default: 135 glog.V(logger.Error).Errorf("%s: Network port %s:%d could not be mapped: %v\n", label, protocol, intport, err) 136 } 137 } 138 } 139 140 err := m.AddMapping(protocol, intport, extport, name, mapTimeout) 141 handleIfAddMappingErr("Mapping network port", err) 142 143 for { 144 select { 145 case _, ok := <-c: 146 if !ok { 147 return 148 } 149 case <-refresh.C: 150 glog.V(logger.Debug).Infof("Refresh port mapping %s:%d -> %d (%s) using %s\n", protocol, extport, intport, name, m) 151 err := m.AddMapping(protocol, intport, extport, name, mapTimeout) 152 handleIfAddMappingErr(refreshPortMappingLabel, err) 153 refresh.Reset(mapUpdateInterval) 154 } 155 } 156 } 157 158 // ExtIP assumes that the local machine is reachable on the given 159 // external IP address, and that any required ports were mapped manually. 160 // Mapping operations will not return an error but won't actually do anything. 161 func ExtIP(ip net.IP) Interface { 162 if ip == nil { 163 panic("IP must not be nil") 164 } 165 return extIP(ip) 166 } 167 168 type extIP net.IP 169 170 func (n extIP) ExternalIP() (net.IP, error) { return net.IP(n), nil } 171 func (n extIP) String() string { return fmt.Sprintf("ExtIP(%v)", net.IP(n)) } 172 173 // These do nothing. 174 func (extIP) AddMapping(string, int, int, string, time.Duration) error { return nil } 175 func (extIP) DeleteMapping(string, int, int) error { return nil } 176 177 // Any returns a port mapper that tries to discover any supported 178 // mechanism on the local network. 179 func Any() Interface { 180 // TODO: attempt to discover whether the local machine has an 181 // Internet-class address. Return ExtIP in this case. 182 return startautodisc(autoDiscUPnPOrNatPMP, func() Interface { 183 found := make(chan Interface, 2) 184 go func() { found <- discoverUPnP() }() 185 go func() { found <- discoverPMP() }() 186 for i := 0; i < cap(found); i++ { 187 if c := <-found; c != nil { 188 return c 189 } 190 } 191 return nil 192 }) 193 } 194 195 // UPnP returns a port mapper that uses UPnP. It will attempt to 196 // discover the address of your router using UDP broadcasts. 197 func UPnP() Interface { 198 return startautodisc(autoDiscUPnP, discoverUPnP) 199 } 200 201 // PMP returns a port mapper that uses NAT-PMP. The provided gateway 202 // address should be the IP of your router. If the given gateway 203 // address is nil, PMP will attempt to auto-discover the router. 204 func PMP(gateway net.IP) Interface { 205 if gateway != nil { 206 return &pmp{gw: gateway, c: natpmp.NewClient(gateway)} 207 } 208 return startautodisc(autoDiscNatPMP, discoverPMP) 209 } 210 211 // autodisc represents a port mapping mechanism that is still being 212 // auto-discovered. Calls to the Interface methods on this type will 213 // wait until the discovery is done and then call the method on the 214 // discovered mechanism. 215 // 216 // This type is useful because discovery can take a while but we 217 // want return an Interface value from UPnP, PMP and Auto immediately. 218 type autodisc struct { 219 what string // type of interface being autodiscovered 220 once sync.Once 221 doit func() Interface 222 223 mu sync.Mutex 224 found Interface 225 } 226 227 func startautodisc(what autodiscoverInterfaceType, doit func() Interface) Interface { 228 // TODO: monitor network configuration and rerun doit when it changes. 229 // TODO: dont cast the string, use actual types 230 ad := &autodisc{what: string(what), doit: doit} 231 // Start the auto discovery as early as possible so it is already 232 // in progress when the rest of the stack calls the methods. 233 go ad.wait() 234 return ad 235 } 236 237 func (n *autodisc) AddMapping(protocol string, extport, intport int, name string, lifetime time.Duration) error { 238 if err := n.wait(); err != nil { 239 return err 240 } 241 return n.found.AddMapping(protocol, extport, intport, name, lifetime) 242 } 243 244 func (n *autodisc) DeleteMapping(protocol string, extport, intport int) error { 245 if err := n.wait(); err != nil { 246 return err 247 } 248 return n.found.DeleteMapping(protocol, extport, intport) 249 } 250 251 func (n *autodisc) ExternalIP() (net.IP, error) { 252 if err := n.wait(); err != nil { 253 return nil, err 254 } 255 return n.found.ExternalIP() 256 } 257 258 func (n *autodisc) String() string { 259 n.mu.Lock() 260 defer n.mu.Unlock() 261 if n.found == nil { 262 return n.what 263 } else { 264 return n.found.String() 265 } 266 } 267 268 // wait blocks until auto-discovery has been performed. 269 func (n *autodisc) wait() error { 270 n.once.Do(func() { 271 n.mu.Lock() 272 n.found = n.doit() 273 n.mu.Unlock() 274 }) 275 if n.found == nil { 276 return fmt.Errorf("no %s router discovered", n.what) 277 } 278 return nil 279 }