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