github.com/MetalBlockchain/metalgo@v1.11.9/nat/nat.go (about) 1 // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package nat 5 6 import ( 7 "net/netip" 8 "sync" 9 "time" 10 11 "go.uber.org/zap" 12 13 "github.com/MetalBlockchain/metalgo/utils" 14 "github.com/MetalBlockchain/metalgo/utils/logging" 15 ) 16 17 const ( 18 mapTimeout = 30 * time.Minute 19 maxRefreshRetries = 3 20 ) 21 22 // Router describes the functionality that a network device must support to be 23 // able to open ports to an external IP. 24 type Router interface { 25 // True iff this router supports NAT 26 SupportsNAT() bool 27 // Map external port [extPort] to internal port [intPort] for [duration] 28 MapPort(intPort, extPort uint16, desc string, duration time.Duration) error 29 // Undo a port mapping 30 UnmapPort(intPort, extPort uint16) error 31 // Return our external IP 32 ExternalIP() (netip.Addr, error) 33 } 34 35 // GetRouter returns a router on the current network. 36 func GetRouter() Router { 37 if r := getUPnPRouter(); r != nil { 38 return r 39 } 40 if r := getPMPRouter(); r != nil { 41 return r 42 } 43 44 return NewNoRouter() 45 } 46 47 // Mapper attempts to open a set of ports on a router 48 type Mapper struct { 49 log logging.Logger 50 r Router 51 closer chan struct{} 52 wg sync.WaitGroup 53 } 54 55 // NewPortMapper returns an initialized mapper 56 func NewPortMapper(log logging.Logger, r Router) *Mapper { 57 return &Mapper{ 58 log: log, 59 r: r, 60 closer: make(chan struct{}), 61 } 62 } 63 64 // Map external port [extPort] (exposed to the internet) to internal port [intPort] (where our process is listening) 65 // and set [ip]. Does this every [updateTime]. [ip] may be nil. 66 func (m *Mapper) Map( 67 intPort uint16, 68 extPort uint16, 69 desc string, 70 ip *utils.Atomic[netip.AddrPort], 71 updateTime time.Duration, 72 ) { 73 if !m.r.SupportsNAT() { 74 return 75 } 76 77 // we attempt a port map, and log an Error if it fails. 78 err := m.retryMapPort(intPort, extPort, desc, mapTimeout) 79 if err != nil { 80 m.log.Error("NAT traversal failed", 81 zap.Uint16("externalPort", extPort), 82 zap.Uint16("internalPort", intPort), 83 zap.Error(err), 84 ) 85 } else { 86 m.log.Info("NAT traversal successful", 87 zap.Uint16("externalPort", extPort), 88 zap.Uint16("internalPort", intPort), 89 ) 90 } 91 92 m.wg.Add(1) 93 go m.keepPortMapping(intPort, extPort, desc, ip, updateTime) 94 } 95 96 // Retry port map up to maxRefreshRetries with a 1 second delay 97 func (m *Mapper) retryMapPort(intPort, extPort uint16, desc string, timeout time.Duration) error { 98 var err error 99 for retryCnt := 0; retryCnt < maxRefreshRetries; retryCnt++ { 100 err = m.r.MapPort(intPort, extPort, desc, timeout) 101 if err == nil { 102 return nil 103 } 104 105 // log a message, sleep a second and retry. 106 m.log.Warn("renewing port mapping failed", 107 zap.Int("attempt", retryCnt+1), 108 zap.Uint16("externalPort", extPort), 109 zap.Uint16("internalPort", intPort), 110 zap.Error(err), 111 ) 112 time.Sleep(1 * time.Second) 113 } 114 return err 115 } 116 117 // keepPortMapping runs in the background to keep a port mapped. It renews the mapping from [extPort] 118 // to [intPort]] every [updateTime]. Updates [ip] every [updateTime]. 119 func (m *Mapper) keepPortMapping( 120 intPort uint16, 121 extPort uint16, 122 desc string, 123 ip *utils.Atomic[netip.AddrPort], 124 updateTime time.Duration, 125 ) { 126 updateTimer := time.NewTimer(updateTime) 127 128 defer func(extPort uint16) { 129 updateTimer.Stop() 130 131 m.log.Debug("unmapping port", 132 zap.Uint16("externalPort", extPort), 133 ) 134 135 if err := m.r.UnmapPort(intPort, extPort); err != nil { 136 m.log.Debug("error unmapping port", 137 zap.Uint16("externalPort", extPort), 138 zap.Uint16("internalPort", intPort), 139 zap.Error(err), 140 ) 141 } 142 143 m.wg.Done() 144 }(extPort) 145 146 for { 147 select { 148 case <-updateTimer.C: 149 err := m.retryMapPort(intPort, extPort, desc, mapTimeout) 150 if err != nil { 151 m.log.Warn("renew NAT traversal failed", 152 zap.Uint16("externalPort", extPort), 153 zap.Uint16("internalPort", intPort), 154 zap.Error(err), 155 ) 156 } 157 m.updateIP(ip) 158 updateTimer.Reset(updateTime) 159 case <-m.closer: 160 return 161 } 162 } 163 } 164 165 func (m *Mapper) updateIP(ip *utils.Atomic[netip.AddrPort]) { 166 if ip == nil { 167 return 168 } 169 newAddr, err := m.r.ExternalIP() 170 if err != nil { 171 m.log.Error("failed to get external IP", 172 zap.Error(err), 173 ) 174 return 175 } 176 oldAddrPort := ip.Get() 177 oldAddr := oldAddrPort.Addr() 178 if newAddr != oldAddr { 179 port := oldAddrPort.Port() 180 ip.Set(netip.AddrPortFrom(newAddr, port)) 181 m.log.Info("external IP updated", 182 zap.Stringer("oldIP", oldAddr), 183 zap.Stringer("newIP", newAddr), 184 ) 185 } 186 } 187 188 // UnmapAllPorts stops mapping all ports from this mapper and attempts to unmap 189 // them. 190 func (m *Mapper) UnmapAllPorts() { 191 close(m.closer) 192 m.wg.Wait() 193 m.log.Info("Unmapped all ports") 194 }