github.com/neatlab/neatio@v1.7.3-0.20220425043230-d903e92fcc75/network/p2p/nat/nat.go (about) 1 package nat 2 3 import ( 4 "errors" 5 "fmt" 6 "net" 7 "strings" 8 "sync" 9 "time" 10 11 natpmp "github.com/jackpal/go-nat-pmp" 12 "github.com/neatlab/neatio/chain/log" 13 ) 14 15 type Interface interface { 16 AddMapping(protocol string, extport, intport int, name string, lifetime time.Duration) error 17 DeleteMapping(protocol string, extport, intport int) error 18 19 ExternalIP() (net.IP, error) 20 21 String() string 22 } 23 24 func Parse(spec string) (Interface, error) { 25 var ( 26 parts = strings.SplitN(spec, ":", 2) 27 mech = strings.ToLower(parts[0]) 28 ip net.IP 29 ) 30 if len(parts) > 1 { 31 ip = net.ParseIP(parts[1]) 32 if ip == nil { 33 return nil, errors.New("invalid IP address") 34 } 35 } 36 switch mech { 37 case "", "none", "off": 38 return nil, nil 39 case "any", "auto", "on": 40 return Any(), nil 41 case "extip", "ip": 42 if ip == nil { 43 return nil, errors.New("missing IP address") 44 } 45 return ExtIP(ip), nil 46 case "upnp": 47 return UPnP(), nil 48 case "pmp", "natpmp", "nat-pmp": 49 return PMP(ip), nil 50 default: 51 return nil, fmt.Errorf("unknown mechanism %q", parts[0]) 52 } 53 } 54 55 const ( 56 mapTimeout = 20 * time.Minute 57 mapUpdateInterval = 15 * time.Minute 58 ) 59 60 func Map(m Interface, c chan struct{}, protocol string, extport, intport int, name string) { 61 log := log.New("proto", protocol, "extport", extport, "intport", intport, "interface", m) 62 refresh := time.NewTimer(mapUpdateInterval) 63 defer func() { 64 refresh.Stop() 65 log.Debug("Deleting port mapping") 66 m.DeleteMapping(protocol, extport, intport) 67 }() 68 if err := m.AddMapping(protocol, extport, intport, name, mapTimeout); err != nil { 69 log.Debug("Couldn't add port mapping", "err", err) 70 } 71 72 for { 73 select { 74 case _, ok := <-c: 75 if !ok { 76 return 77 } 78 case <-refresh.C: 79 log.Trace("Refreshing port mapping") 80 if err := m.AddMapping(protocol, extport, intport, name, mapTimeout); err != nil { 81 log.Debug("Couldn't add port mapping", "err", err) 82 } 83 refresh.Reset(mapUpdateInterval) 84 } 85 } 86 } 87 88 func ExtIP(ip net.IP) Interface { 89 if ip == nil { 90 panic("IP must not be nil") 91 } 92 return extIP(ip) 93 } 94 95 type extIP net.IP 96 97 func (n extIP) ExternalIP() (net.IP, error) { return net.IP(n), nil } 98 func (n extIP) String() string { return fmt.Sprintf("ExtIP(%v)", net.IP(n)) } 99 100 func (extIP) AddMapping(string, int, int, string, time.Duration) error { return nil } 101 func (extIP) DeleteMapping(string, int, int) error { return nil } 102 103 func Any() Interface { 104 105 return startautodisc("UPnP or NAT-PMP", func() Interface { 106 found := make(chan Interface, 2) 107 go func() { found <- discoverUPnP() }() 108 go func() { found <- discoverPMP() }() 109 for i := 0; i < cap(found); i++ { 110 if c := <-found; c != nil { 111 return c 112 } 113 } 114 return nil 115 }) 116 } 117 118 func UPnP() Interface { 119 return startautodisc("UPnP", discoverUPnP) 120 } 121 122 func PMP(gateway net.IP) Interface { 123 if gateway != nil { 124 return &pmp{gw: gateway, c: natpmp.NewClient(gateway)} 125 } 126 return startautodisc("NAT-PMP", discoverPMP) 127 } 128 129 type autodisc struct { 130 what string 131 once sync.Once 132 doit func() Interface 133 134 mu sync.Mutex 135 found Interface 136 } 137 138 func startautodisc(what string, doit func() Interface) Interface { 139 140 return &autodisc{what: what, doit: doit} 141 } 142 143 func (n *autodisc) AddMapping(protocol string, extport, intport int, name string, lifetime time.Duration) error { 144 if err := n.wait(); err != nil { 145 return err 146 } 147 return n.found.AddMapping(protocol, extport, intport, name, lifetime) 148 } 149 150 func (n *autodisc) DeleteMapping(protocol string, extport, intport int) error { 151 if err := n.wait(); err != nil { 152 return err 153 } 154 return n.found.DeleteMapping(protocol, extport, intport) 155 } 156 157 func (n *autodisc) ExternalIP() (net.IP, error) { 158 if err := n.wait(); err != nil { 159 return nil, err 160 } 161 return n.found.ExternalIP() 162 } 163 164 func (n *autodisc) String() string { 165 n.mu.Lock() 166 defer n.mu.Unlock() 167 if n.found == nil { 168 return n.what 169 } else { 170 return n.found.String() 171 } 172 } 173 174 func (n *autodisc) wait() error { 175 n.once.Do(func() { 176 n.mu.Lock() 177 n.found = n.doit() 178 n.mu.Unlock() 179 }) 180 if n.found == nil { 181 return fmt.Errorf("no %s router discovered", n.what) 182 } 183 return nil 184 }