github.com/arieschain/arieschain@v0.0.0-20191023063405-37c074544356/p2p/nat/nat.go (about) 1 // Package nat provides access to common network 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/quickchainproject/quickchain/log" 13 "github.com/jackpal/go-nat-pmp" 14 ) 15 16 // An implementation of nat.Interface can map local ports to ports 17 // accessible from the Internet. 18 type Interface interface { 19 // These methods manage a mapping between a port on the local 20 // machine to a port that can be connected to from the internet. 21 // 22 // protocol is "UDP" or "TCP". Some implementations allow setting 23 // a display name for the mapping. The mapping may be removed by 24 // the gateway when its lifetime ends. 25 AddMapping(protocol string, extport, intport int, name string, lifetime time.Duration) error 26 DeleteMapping(protocol string, extport, intport int) error 27 28 // This method should return the external (Internet-facing) 29 // address of the gateway device. 30 ExternalIP() (net.IP, error) 31 32 // Should return name of the method. This is used for logging. 33 String() string 34 } 35 36 // Parse parses a NAT interface description. 37 // The following formats are currently accepted. 38 // Note that mechanism names are not case-sensitive. 39 // 40 // "" or "none" return nil 41 // "extip:77.12.33.4" will assume the local machine is reachable on the given IP 42 // "any" uses the first auto-detected mechanism 43 // "upnp" uses the Universal Plug and Play protocol 44 // "pmp" uses NAT-PMP with an auto-detected gateway address 45 // "pmp:192.168.0.1" uses NAT-PMP with the given gateway address 46 func Parse(spec string) (Interface, error) { 47 var ( 48 parts = strings.SplitN(spec, ":", 2) 49 mech = strings.ToLower(parts[0]) 50 ip net.IP 51 ) 52 if len(parts) > 1 { 53 ip = net.ParseIP(parts[1]) 54 if ip == nil { 55 return nil, errors.New("invalid IP address") 56 } 57 } 58 switch mech { 59 case "", "none", "off": 60 return nil, nil 61 case "any", "auto", "on": 62 return Any(), nil 63 case "extip", "ip": 64 if ip == nil { 65 return nil, errors.New("missing IP address") 66 } 67 return ExtIP(ip), nil 68 case "upnp": 69 return UPnP(), nil 70 case "pmp", "natpmp", "nat-pmp": 71 return PMP(ip), nil 72 default: 73 return nil, fmt.Errorf("unknown mechanism %q", parts[0]) 74 } 75 } 76 77 const ( 78 mapTimeout = 20 * time.Minute 79 mapUpdateInterval = 15 * time.Minute 80 ) 81 82 // Map adds a port mapping on m and keeps it alive until c is closed. 83 // This function is typically invoked in its own goroutine. 84 func Map(m Interface, c chan struct{}, protocol string, extport, intport int, name string) { 85 log := log.New("proto", protocol, "extport", extport, "intport", intport, "interface", m) 86 refresh := time.NewTimer(mapUpdateInterval) 87 defer func() { 88 refresh.Stop() 89 log.Debug("Deleting port mapping") 90 m.DeleteMapping(protocol, extport, intport) 91 }() 92 if err := m.AddMapping(protocol, extport, intport, name, mapTimeout); err != nil { 93 log.Debug("Couldn't add port mapping", "err", err) 94 } else { 95 log.Info("Mapped network port") 96 } 97 for { 98 select { 99 case _, ok := <-c: 100 if !ok { 101 return 102 } 103 case <-refresh.C: 104 log.Trace("Refreshing port mapping") 105 if err := m.AddMapping(protocol, extport, intport, name, mapTimeout); err != nil { 106 log.Debug("Couldn't add port mapping", "err", 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 // type of interface being autodiscovered 175 once sync.Once 176 doit func() Interface 177 178 mu sync.Mutex 179 found Interface 180 } 181 182 func startautodisc(what string, doit func() Interface) Interface { 183 // TODO: monitor network configuration and rerun doit when it changes. 184 return &autodisc{what: what, doit: doit} 185 } 186 187 func (n *autodisc) AddMapping(protocol string, extport, intport int, name string, lifetime time.Duration) error { 188 if err := n.wait(); err != nil { 189 return err 190 } 191 return n.found.AddMapping(protocol, extport, intport, name, lifetime) 192 } 193 194 func (n *autodisc) DeleteMapping(protocol string, extport, intport int) error { 195 if err := n.wait(); err != nil { 196 return err 197 } 198 return n.found.DeleteMapping(protocol, extport, intport) 199 } 200 201 func (n *autodisc) ExternalIP() (net.IP, error) { 202 if err := n.wait(); err != nil { 203 return nil, err 204 } 205 return n.found.ExternalIP() 206 } 207 208 func (n *autodisc) String() string { 209 n.mu.Lock() 210 defer n.mu.Unlock() 211 if n.found == nil { 212 return n.what 213 } else { 214 return n.found.String() 215 } 216 } 217 218 // wait blocks until auto-discovery has been performed. 219 func (n *autodisc) wait() error { 220 n.once.Do(func() { 221 n.mu.Lock() 222 n.found = n.doit() 223 n.mu.Unlock() 224 }) 225 if n.found == nil { 226 return fmt.Errorf("no %s router discovered", n.what) 227 } 228 return nil 229 }