github.com/ethereum/go-ethereum@v1.16.1/p2p/nat/natpmp.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 18 19 import ( 20 "errors" 21 "fmt" 22 "net" 23 "strings" 24 "time" 25 26 natpmp "github.com/jackpal/go-nat-pmp" 27 ) 28 29 // pmp adapts the NAT-PMP protocol implementation so it conforms to 30 // the common interface. 31 type pmp struct { 32 gw net.IP 33 c *natpmp.Client 34 } 35 36 func (n *pmp) String() string { 37 return fmt.Sprintf("NAT-PMP(%v)", n.gw) 38 } 39 40 func (n *pmp) ExternalIP() (net.IP, error) { 41 response, err := n.c.GetExternalAddress() 42 if err != nil { 43 return nil, err 44 } 45 return response.ExternalIPAddress[:], nil 46 } 47 48 func (n *pmp) AddMapping(protocol string, extport, intport int, name string, lifetime time.Duration) (uint16, error) { 49 if lifetime <= 0 { 50 return 0, errors.New("lifetime must not be <= 0") 51 } 52 if extport == 0 { 53 extport = intport 54 } 55 // Note order of port arguments is switched between our 56 // AddMapping and the client's AddPortMapping. 57 res, err := n.c.AddPortMapping(strings.ToLower(protocol), intport, extport, int(lifetime/time.Second)) 58 if err != nil { 59 return 0, err 60 } 61 62 // NAT-PMP maps an alternative available port number if the requested port 63 // is already mapped to another address and returns success. Handling of 64 // alternate port numbers is done by the caller. 65 return res.MappedExternalPort, nil 66 } 67 68 func (n *pmp) DeleteMapping(protocol string, extport, intport int) (err error) { 69 // To destroy a mapping, send an add-port with an internalPort of 70 // the internal port to destroy, an external port of zero and a 71 // time of zero. 72 _, err = n.c.AddPortMapping(strings.ToLower(protocol), intport, 0, 0) 73 return err 74 } 75 76 func (n *pmp) MarshalText() ([]byte, error) { 77 return fmt.Appendf(nil, "natpmp:%v", n.gw), nil 78 } 79 80 func discoverPMP() Interface { 81 // run external address lookups on all potential gateways 82 gws := potentialGateways() 83 found := make(chan *pmp, len(gws)) 84 for i := range gws { 85 gw := gws[i] 86 go func() { 87 c := natpmp.NewClient(gw) 88 if _, err := c.GetExternalAddress(); err != nil { 89 found <- nil 90 } else { 91 found <- &pmp{gw, c} 92 } 93 }() 94 } 95 // return the one that responds first. 96 // discovery needs to be quick, so we stop caring about 97 // any responses after a very short timeout. 98 timeout := time.NewTimer(1 * time.Second) 99 defer timeout.Stop() 100 for range gws { 101 select { 102 case c := <-found: 103 if c != nil { 104 return c 105 } 106 case <-timeout.C: 107 return nil 108 } 109 } 110 return nil 111 } 112 113 // TODO: improve this. We currently assume that (on most networks) 114 // the router is X.X.X.1 in a local LAN range. 115 func potentialGateways() (gws []net.IP) { 116 ifaces, err := net.Interfaces() 117 if err != nil { 118 return nil 119 } 120 for _, iface := range ifaces { 121 ifaddrs, err := iface.Addrs() 122 if err != nil { 123 return gws 124 } 125 for _, addr := range ifaddrs { 126 if x, ok := addr.(*net.IPNet); ok { 127 if x.IP.IsPrivate() { 128 ip := x.IP.Mask(x.Mask).To4() 129 if ip != nil { 130 ip[3] = ip[3] | 0x01 131 gws = append(gws, ip) 132 } 133 } 134 } 135 } 136 } 137 return gws 138 }