natmgr.go 6.54 KB
Newer Older
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
1
2
3
package basichost

import (
4
	"context"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
5
6
	"sync"

7
	inat "github.com/libp2p/go-libp2p-nat"
Jeromy's avatar
Jeromy committed
8

Jeromy's avatar
Jeromy committed
9
	goprocess "github.com/jbenet/goprocess"
Jeromy's avatar
Jeromy committed
10
	lgbl "github.com/libp2p/go-libp2p-loggables"
Jeromy's avatar
Jeromy committed
11
	inet "github.com/libp2p/go-libp2p-net"
Jeromy's avatar
Jeromy committed
12
	ma "github.com/multiformats/go-multiaddr"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
13
14
)

Enzo Haussecker's avatar
Enzo Haussecker committed
15
// NATManager takes care of adding + removing port mappings to the nat.
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
16
// Initialized with the host if it has a NATPortMap option enabled.
Enzo Haussecker's avatar
Enzo Haussecker committed
17
18
// NATManager receives signals from the network, and check on nat mappings:
//  * NATManager listens to the network and adds or closes port mappings
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
19
//    as the network signals Listen() or ListenClose().
Enzo Haussecker's avatar
Enzo Haussecker committed
20
21
//  * closing the NATManager closes the nat and its mappings.
type NATManager struct {
22
	net   inet.Network
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
23
24
25
26
	natmu sync.RWMutex // guards nat (ready could obviate this mutex, but safety first.)
	nat   *inat.NAT

	ready chan struct{}     // closed once the nat is ready to process port mappings
Enzo Haussecker's avatar
Enzo Haussecker committed
27
	proc  goprocess.Process // NATManager has a process + children. can be closed.
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
28
29
}

Enzo Haussecker's avatar
Enzo Haussecker committed
30
31
func NewNATManager(net inet.Network) *NATManager {
	nmgr := &NATManager{
32
		net:   net,
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
33
34
35
36
37
		ready: make(chan struct{}),
	}

	nmgr.proc = goprocess.WithTeardown(func() error {
		// on closing, unregister from network notifications.
38
		net.StopNotify((*nmgrNetNotifiee)(nmgr))
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
39
40
41
42
43
44
45
46
		return nil
	})

	// discover the nat.
	nmgr.discoverNAT()
	return nmgr
}

Enzo Haussecker's avatar
Enzo Haussecker committed
47
// Close closes the NATManager, closing the underlying nat
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
48
// and unregistering from network events.
Enzo Haussecker's avatar
Enzo Haussecker committed
49
func (nmgr *NATManager) Close() error {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
50
51
52
53
54
	return nmgr.proc.Close()
}

// Ready returns a channel which will be closed when the NAT has been found
// and is ready to be used, or the search process is done.
Enzo Haussecker's avatar
Enzo Haussecker committed
55
func (nmgr *NATManager) Ready() <-chan struct{} {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
56
57
58
	return nmgr.ready
}

Enzo Haussecker's avatar
Enzo Haussecker committed
59
func (nmgr *NATManager) discoverNAT() {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
60
61
62
63
64
65
66
67
68
69
70
71
72

	nmgr.proc.Go(func(worker goprocess.Process) {
		// inat.DiscoverNAT blocks until the nat is found or a timeout
		// is reached. we unfortunately cannot specify timeouts-- the
		// library we're using just blocks.
		//
		// Note: on early shutdown, there may be a case where we're trying
		// to close before DiscoverNAT() returns. Since we cant cancel it
		// (library) we can choose to (1) drop the result and return early,
		// or (2) wait until it times out to exit. For now we choose (2),
		// to avoid leaking resources in a non-obvious way. the only case
		// this affects is when the daemon is being started up and _immediately_
		// asked to close. other services are also starting up, so ok to wait.
Jeromy's avatar
Jeromy committed
73
74
75
76
77
78
		discoverdone := make(chan struct{})
		var nat *inat.NAT
		go func() {
			defer close(discoverdone)
			nat = inat.DiscoverNAT()
		}()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
79
80
81
82
83
84

		// by this point -- after finding the NAT -- we may have already
		// be closing. if so, just exit.
		select {
		case <-worker.Closing():
			return
Jeromy's avatar
Jeromy committed
85
86
87
88
		case <-discoverdone:
			if nat == nil { // no nat, or failed to get it.
				return
			}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
89
90
91
92
93
94
95
96
97
98
99
100
101
102
		}

		// wire up the nat to close when nmgr closes.
		// nmgr.proc is our parent, and waiting for us.
		nmgr.proc.AddChild(nat.Process())

		// set the nat.
		nmgr.natmu.Lock()
		nmgr.nat = nat
		nmgr.natmu.Unlock()

		// signal that we're ready to process nat mappings:
		close(nmgr.ready)

Enzo Haussecker's avatar
Enzo Haussecker committed
103
		// sign NATManager up for network notifications
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
104
105
		// we need to sign up here to avoid missing some notifs
		// before the NAT has been found.
106
		nmgr.net.Notify((*nmgrNetNotifiee)(nmgr))
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
107
108
109
110
111

		// if any interfaces were brought up while we were setting up
		// the nat, now is the time to setup port mappings for them.
		// we release ready, then grab them to avoid losing any. adding
		// a port mapping is idempotent, so its ok to add the same twice.
112
		addrs := nmgr.net.ListenAddresses()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
113
114
115
116
117
118
119
		for _, addr := range addrs {
			// we do it async because it's slow and we may want to close beforehand
			go addPortMapping(nmgr, addr)
		}
	})
}

Enzo Haussecker's avatar
Enzo Haussecker committed
120
// NAT returns the NATManager's nat object. this may be nil, if
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
121
122
// (a) the search process is still ongoing, or (b) the search process
// found no nat. Clients must check whether the return value is nil.
Enzo Haussecker's avatar
Enzo Haussecker committed
123
func (nmgr *NATManager) NAT() *inat.NAT {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
124
125
126
127
128
	nmgr.natmu.Lock()
	defer nmgr.natmu.Unlock()
	return nmgr.nat
}

Enzo Haussecker's avatar
Enzo Haussecker committed
129
func addPortMapping(nmgr *NATManager, intaddr ma.Multiaddr) {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
130
131
	nat := nmgr.NAT()
	if nat == nil {
Enzo Haussecker's avatar
Enzo Haussecker committed
132
		panic("NATManager addPortMapping called without a nat.")
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
	}

	// first, check if the port mapping already exists.
	for _, mapping := range nat.Mappings() {
		if mapping.InternalAddr().Equal(intaddr) {
			return // it exists! return.
		}
	}

	ctx := context.TODO()
	lm := make(lgbl.DeferredMap)
	lm["internalAddr"] = func() interface{} { return intaddr.String() }

	defer log.EventBegin(ctx, "natMgrAddPortMappingWait", lm).Done()

	select {
	case <-nmgr.proc.Closing():
		lm["outcome"] = "cancelled"
		return // no use.
	case <-nmgr.ready: // wait until it's ready.
	}

	// actually start the port map (sub-event because waiting may take a while)
	defer log.EventBegin(ctx, "natMgrAddPortMapping", lm).Done()

	// get the nat
	m, err := nat.NewMapping(intaddr)
	if err != nil {
		lm["outcome"] = "failure"
		lm["error"] = err
		return
	}

	extaddr, err := m.ExternalAddr()
	if err != nil {
		lm["outcome"] = "failure"
		lm["error"] = err
		return
	}

	lm["outcome"] = "success"
	lm["externalAddr"] = func() interface{} { return extaddr.String() }
	log.Infof("established nat port mapping: %s <--> %s", intaddr, extaddr)
}

Enzo Haussecker's avatar
Enzo Haussecker committed
178
func rmPortMapping(nmgr *NATManager, intaddr ma.Multiaddr) {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
179
180
	nat := nmgr.NAT()
	if nat == nil {
Enzo Haussecker's avatar
Enzo Haussecker committed
181
		panic("NATManager rmPortMapping called without a nat.")
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
182
183
184
185
186
187
188
189
190
191
192
193
194
195
	}

	// list the port mappings (it may be gone on it's own, so we need to
	// check this list, and not store it ourselves behind the scenes)

	// close mappings for this internal address.
	for _, mapping := range nat.Mappings() {
		if mapping.InternalAddr().Equal(intaddr) {
			mapping.Close()
		}
	}
}

// nmgrNetNotifiee implements the network notification listening part
Enzo Haussecker's avatar
Enzo Haussecker committed
196
// of the NATManager. this is merely listening to Listen() and ListenClose()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
197
// events.
Enzo Haussecker's avatar
Enzo Haussecker committed
198
type nmgrNetNotifiee NATManager
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
199

Enzo Haussecker's avatar
Enzo Haussecker committed
200
201
func (nn *nmgrNetNotifiee) natManager() *NATManager {
	return (*NATManager)(nn)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
}

func (nn *nmgrNetNotifiee) Listen(n inet.Network, addr ma.Multiaddr) {
	if nn.natManager().NAT() == nil {
		return // not ready or doesnt exist.
	}

	addPortMapping(nn.natManager(), addr)
}

func (nn *nmgrNetNotifiee) ListenClose(n inet.Network, addr ma.Multiaddr) {
	if nn.natManager().NAT() == nil {
		return // not ready or doesnt exist.
	}

	rmPortMapping(nn.natManager(), addr)
}

func (nn *nmgrNetNotifiee) Connected(inet.Network, inet.Conn)      {}
func (nn *nmgrNetNotifiee) Disconnected(inet.Network, inet.Conn)   {}
func (nn *nmgrNetNotifiee) OpenedStream(inet.Network, inet.Stream) {}
func (nn *nmgrNetNotifiee) ClosedStream(inet.Network, inet.Stream) {}