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