@libp2p/upnp-nat 2.0.11 → 2.0.12-339b7df88

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,223 @@
1
+ import { isIPv4, isIPv6 } from '@chainsafe/is-ip'
2
+ import { InvalidParametersError, start, stop } from '@libp2p/interface'
3
+ import { isLoopback } from '@libp2p/utils/multiaddr/is-loopback'
4
+ import { isPrivate } from '@libp2p/utils/multiaddr/is-private'
5
+ import { isPrivateIp } from '@libp2p/utils/private-ip'
6
+ import { QUICV1, TCP, WebSockets, WebSocketsSecure, WebTransport } from '@multiformats/multiaddr-matcher'
7
+ import { dynamicExternalAddress } from './check-external-address.js'
8
+ import { DoubleNATError, InvalidIPAddressError } from './errors.js'
9
+ import type { ExternalAddress } from './check-external-address.js'
10
+ import type { Gateway } from '@achingbrain/nat-port-mapper'
11
+ import type { ComponentLogger, Logger } from '@libp2p/interface'
12
+ import type { AddressManager } from '@libp2p/interface-internal'
13
+ import type { Multiaddr } from '@multiformats/multiaddr'
14
+
15
+ export interface UPnPPortMapperInit {
16
+ gateway: Gateway
17
+ externalAddressCheckInterval?: number
18
+ externalAddressCheckTimeout?: number
19
+ }
20
+
21
+ export interface UPnPPortMapperComponents {
22
+ logger: ComponentLogger
23
+ addressManager: AddressManager
24
+ }
25
+
26
+ interface PortMapping {
27
+ externalHost: string
28
+ externalPort: number
29
+ }
30
+
31
+ export class UPnPPortMapper {
32
+ private readonly gateway: Gateway
33
+ private readonly externalAddress: ExternalAddress
34
+ private readonly addressManager: AddressManager
35
+ private readonly log: Logger
36
+ private readonly mappedPorts: Map<string, PortMapping>
37
+ private started: boolean
38
+
39
+ constructor (components: UPnPPortMapperComponents, init: UPnPPortMapperInit) {
40
+ this.log = components.logger.forComponent(`libp2p:upnp-nat:gateway:${init.gateway.id}`)
41
+ this.addressManager = components.addressManager
42
+ this.gateway = init.gateway
43
+ this.externalAddress = dynamicExternalAddress({
44
+ gateway: this.gateway,
45
+ addressManager: this.addressManager,
46
+ logger: components.logger
47
+ }, {
48
+ interval: init.externalAddressCheckInterval,
49
+ timeout: init.externalAddressCheckTimeout,
50
+ onExternalAddressChange: this.remapPorts.bind(this)
51
+ })
52
+ this.gateway = init.gateway
53
+ this.mappedPorts = new Map()
54
+ this.started = false
55
+ }
56
+
57
+ async start (): Promise<void> {
58
+ if (this.started) {
59
+ return
60
+ }
61
+
62
+ await start(this.externalAddress)
63
+ this.started = true
64
+ }
65
+
66
+ async stop (): Promise<void> {
67
+ try {
68
+ const shutdownTimeout = AbortSignal.timeout(1000)
69
+
70
+ await this.gateway.stop({
71
+ signal: shutdownTimeout
72
+ })
73
+ } catch (err: any) {
74
+ this.log.error('error closing gateway - %e', err)
75
+ }
76
+
77
+ await stop(this.externalAddress)
78
+ this.started = false
79
+ }
80
+
81
+ /**
82
+ * Update the local address mappings when the gateway's external interface
83
+ * address changes
84
+ */
85
+ private remapPorts (newExternalHost: string): void {
86
+ for (const [key, { externalHost, externalPort }] of this.mappedPorts.entries()) {
87
+ const [
88
+ host,
89
+ port,
90
+ transport
91
+ ] = key.split('-')
92
+
93
+ this.addressManager.removePublicAddressMapping(host, parseInt(port), externalHost, externalPort, transport === 'tcp' ? 'tcp' : 'udp')
94
+ this.addressManager.addPublicAddressMapping(host, parseInt(port), newExternalHost, externalPort, transport === 'tcp' ? 'tcp' : 'udp')
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Return any eligible multiaddrs that are not mapped on the detected gateway
100
+ */
101
+ private getUnmappedAddresses (multiaddrs: Multiaddr[], ipType: 4 | 6): Multiaddr[] {
102
+ const output: Multiaddr[] = []
103
+
104
+ for (const ma of multiaddrs) {
105
+ // ignore public addresses
106
+ if (!isPrivate(ma)) {
107
+ continue
108
+ }
109
+
110
+ // ignore loopback
111
+ if (isLoopback(ma)) {
112
+ continue
113
+ }
114
+
115
+ // only IP based addresses
116
+ if (!(
117
+ TCP.exactMatch(ma) ||
118
+ WebSockets.exactMatch(ma) ||
119
+ WebSocketsSecure.exactMatch(ma) ||
120
+ QUICV1.exactMatch(ma) ||
121
+ WebTransport.exactMatch(ma)
122
+ )) {
123
+ continue
124
+ }
125
+
126
+ const { port, host, family, transport } = ma.toOptions()
127
+
128
+ if (family !== ipType) {
129
+ continue
130
+ }
131
+
132
+ if (this.mappedPorts.has(`${host}-${port}-${transport}`)) {
133
+ continue
134
+ }
135
+
136
+ output.push(ma)
137
+ }
138
+
139
+ return output
140
+ }
141
+
142
+ async mapIpAddresses (): Promise<void> {
143
+ try {
144
+ const externalHost = await this.externalAddress.getPublicIp()
145
+
146
+ let ipType: 4 | 6 = 4
147
+
148
+ if (isIPv4(externalHost)) {
149
+ ipType = 4
150
+ } else if (isIPv6(externalHost)) {
151
+ ipType = 6
152
+ } else {
153
+ throw new InvalidIPAddressError(`Public address ${externalHost} was not an IPv4 address`)
154
+ }
155
+
156
+ // filter addresses to get private, non-relay, IP based addresses that we
157
+ // haven't mapped yet
158
+ const addresses = this.getUnmappedAddresses(this.addressManager.getAddresses(), ipType)
159
+
160
+ if (addresses.length === 0) {
161
+ this.log('no private, non-relay, unmapped, IP based addresses found')
162
+ return
163
+ }
164
+
165
+ this.log('%s public IP %s', this.externalAddress != null ? 'using configured' : 'discovered', externalHost)
166
+
167
+ this.assertNotBehindDoubleNAT(externalHost)
168
+
169
+ for (const addr of addresses) {
170
+ // try to open uPnP ports for each thin waist address
171
+ const { family, host, port, transport } = addr.toOptions()
172
+
173
+ if (family === 6) {
174
+ // only support IPv4 addresses
175
+ continue
176
+ }
177
+
178
+ if (this.mappedPorts.has(`${host}-${port}-${transport}`)) {
179
+ // already mapped this port
180
+ continue
181
+ }
182
+
183
+ try {
184
+ const key = `${host}-${port}-${transport}`
185
+ this.log('creating mapping of key %s', key)
186
+
187
+ const externalPort = await this.gateway.map(port, {
188
+ localAddress: host,
189
+ protocol: transport === 'tcp' ? 'tcp' : 'udp'
190
+ })
191
+
192
+ this.mappedPorts.set(key, {
193
+ externalHost,
194
+ externalPort
195
+ })
196
+
197
+ this.log('created mapping of %s:%s to %s:%s', externalHost, externalPort, host, port)
198
+
199
+ this.addressManager.addPublicAddressMapping(host, port, externalHost, externalPort, transport === 'tcp' ? 'tcp' : 'udp')
200
+ } catch (err) {
201
+ this.log.error('failed to create mapping of %s:%s - %e', host, port, err)
202
+ }
203
+ }
204
+ } catch (err: any) {
205
+ this.log.error('error finding gateways - %e', err)
206
+ }
207
+ }
208
+
209
+ /**
210
+ * Some ISPs have double-NATs, there's not much we can do with them
211
+ */
212
+ private assertNotBehindDoubleNAT (publicIp: string): void {
213
+ const isPrivate = isPrivateIp(publicIp)
214
+
215
+ if (isPrivate === true) {
216
+ throw new DoubleNATError(`${publicIp} is private - please init uPnPNAT with 'externalAddress' set to an externally routable IP or ensure you are not behind a double NAT`)
217
+ }
218
+
219
+ if (isPrivate == null) {
220
+ throw new InvalidParametersError(`${publicIp} is not an IP address`)
221
+ }
222
+ }
223
+ }
package/LICENSE DELETED
@@ -1,4 +0,0 @@
1
- This project is dual licensed under MIT and Apache-2.0.
2
-
3
- MIT: https://www.opensource.org/licenses/mit
4
- Apache-2.0: https://www.apache.org/licenses/license-2.0
@@ -1,12 +0,0 @@
1
- {
2
- "PMPOptions": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_upnp_nat.PMPOptions.html",
3
- ".:PMPOptions": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_upnp_nat.PMPOptions.html",
4
- "UPnPNAT": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_upnp_nat.UPnPNAT.html",
5
- ".:UPnPNAT": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_upnp_nat.UPnPNAT.html",
6
- "UPnPNATComponents": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_upnp_nat.UPnPNATComponents.html",
7
- ".:UPnPNATComponents": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_upnp_nat.UPnPNATComponents.html",
8
- "UPnPNATInit": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_upnp_nat.UPnPNATInit.html",
9
- ".:UPnPNATInit": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_upnp_nat.UPnPNATInit.html",
10
- "uPnPNAT": "https://libp2p.github.io/js-libp2p/functions/_libp2p_upnp_nat.uPnPNAT-1.html",
11
- ".:uPnPNAT": "https://libp2p.github.io/js-libp2p/functions/_libp2p_upnp_nat.uPnPNAT-1.html"
12
- }