1package nat 2 3import ( 4 "context" 5 "errors" 6 "fmt" 7 "sync" 8 "time" 9 10 logging "github.com/ipfs/go-log" 11 goprocess "github.com/jbenet/goprocess" 12 periodic "github.com/jbenet/goprocess/periodic" 13 nat "github.com/libp2p/go-nat" 14) 15 16var ( 17 // ErrNoMapping signals no mapping exists for an address 18 ErrNoMapping = errors.New("mapping not established") 19) 20 21var log = logging.Logger("nat") 22 23// MappingDuration is a default port mapping duration. 24// Port mappings are renewed every (MappingDuration / 3) 25const MappingDuration = time.Second * 60 26 27// CacheTime is the time a mapping will cache an external address for 28const CacheTime = time.Second * 15 29 30// DiscoverNAT looks for a NAT device in the network and 31// returns an object that can manage port mappings. 32func DiscoverNAT(ctx context.Context) (*NAT, error) { 33 var ( 34 natInstance nat.NAT 35 err error 36 ) 37 38 done := make(chan struct{}) 39 go func() { 40 defer close(done) 41 // This will abort in 10 seconds anyways. 42 natInstance, err = nat.DiscoverGateway() 43 }() 44 45 select { 46 case <-done: 47 case <-ctx.Done(): 48 return nil, ctx.Err() 49 } 50 51 if err != nil { 52 return nil, err 53 } 54 55 // Log the device addr. 56 addr, err := natInstance.GetDeviceAddress() 57 if err != nil { 58 log.Debug("DiscoverGateway address error:", err) 59 } else { 60 log.Debug("DiscoverGateway address:", addr) 61 } 62 63 return newNAT(natInstance), nil 64} 65 66// NAT is an object that manages address port mappings in 67// NATs (Network Address Translators). It is a long-running 68// service that will periodically renew port mappings, 69// and keep an up-to-date list of all the external addresses. 70type NAT struct { 71 natmu sync.Mutex 72 nat nat.NAT 73 proc goprocess.Process 74 75 mappingmu sync.RWMutex // guards mappings 76 mappings map[*mapping]struct{} 77} 78 79func newNAT(realNAT nat.NAT) *NAT { 80 return &NAT{ 81 nat: realNAT, 82 proc: goprocess.WithParent(goprocess.Background()), 83 mappings: make(map[*mapping]struct{}), 84 } 85} 86 87// Close shuts down all port mappings. NAT can no longer be used. 88func (nat *NAT) Close() error { 89 return nat.proc.Close() 90} 91 92// Process returns the nat's life-cycle manager, for making it listen 93// to close signals. 94func (nat *NAT) Process() goprocess.Process { 95 return nat.proc 96} 97 98// Mappings returns a slice of all NAT mappings 99func (nat *NAT) Mappings() []Mapping { 100 nat.mappingmu.Lock() 101 maps2 := make([]Mapping, 0, len(nat.mappings)) 102 for m := range nat.mappings { 103 maps2 = append(maps2, m) 104 } 105 nat.mappingmu.Unlock() 106 return maps2 107} 108 109func (nat *NAT) addMapping(m *mapping) { 110 // make mapping automatically close when nat is closed. 111 nat.proc.AddChild(m.proc) 112 113 nat.mappingmu.Lock() 114 nat.mappings[m] = struct{}{} 115 nat.mappingmu.Unlock() 116} 117 118func (nat *NAT) rmMapping(m *mapping) { 119 nat.mappingmu.Lock() 120 delete(nat.mappings, m) 121 nat.mappingmu.Unlock() 122} 123 124// NewMapping attempts to construct a mapping on protocol and internal port 125// It will also periodically renew the mapping until the returned Mapping 126// -- or its parent NAT -- is Closed. 127// 128// May not succeed, and mappings may change over time; 129// NAT devices may not respect our port requests, and even lie. 130// Clients should not store the mapped results, but rather always 131// poll our object for the latest mappings. 132func (nat *NAT) NewMapping(protocol string, port int) (Mapping, error) { 133 if nat == nil { 134 return nil, fmt.Errorf("no nat available") 135 } 136 137 switch protocol { 138 case "tcp", "udp": 139 default: 140 return nil, fmt.Errorf("invalid protocol: %s", protocol) 141 } 142 143 m := &mapping{ 144 intport: port, 145 nat: nat, 146 proto: protocol, 147 } 148 149 m.proc = goprocess.WithTeardown(func() error { 150 nat.rmMapping(m) 151 nat.natmu.Lock() 152 defer nat.natmu.Unlock() 153 nat.nat.DeletePortMapping(m.Protocol(), m.InternalPort()) 154 return nil 155 }) 156 157 nat.addMapping(m) 158 159 m.proc.AddChild(periodic.Every(MappingDuration/3, func(worker goprocess.Process) { 160 nat.establishMapping(m) 161 })) 162 163 // do it once synchronously, so first mapping is done right away, and before exiting, 164 // allowing users -- in the optimistic case -- to use results right after. 165 nat.establishMapping(m) 166 return m, nil 167} 168 169func (nat *NAT) establishMapping(m *mapping) { 170 oldport := m.ExternalPort() 171 172 log.Debugf("Attempting port map: %s/%d", m.Protocol(), m.InternalPort()) 173 comment := "libp2p" 174 175 nat.natmu.Lock() 176 newport, err := nat.nat.AddPortMapping(m.Protocol(), m.InternalPort(), comment, MappingDuration) 177 if err != nil { 178 // Some hardware does not support mappings with timeout, so try that 179 newport, err = nat.nat.AddPortMapping(m.Protocol(), m.InternalPort(), comment, 0) 180 } 181 nat.natmu.Unlock() 182 183 if err != nil || newport == 0 { 184 m.setExternalPort(0) // clear mapping 185 // TODO: log.Event 186 log.Warnf("failed to establish port mapping: %s", err) 187 // we do not close if the mapping failed, 188 // because it may work again next time. 189 return 190 } 191 192 m.setExternalPort(newport) 193 log.Debugf("NAT Mapping: %d --> %d (%s)", m.ExternalPort(), m.InternalPort(), m.Protocol()) 194 if oldport != 0 && newport != oldport { 195 log.Debugf("failed to renew same port mapping: ch %d -> %d", oldport, newport) 196 } 197} 198