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