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