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