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