@libp2p/upnp-nat 2.0.11 → 2.0.12-06f79b646

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,220 @@
1
+ import { isIPv4 } from '@chainsafe/is-ip'
2
+ import { InvalidParametersError, start, stop } from '@libp2p/interface'
3
+ import { isLinkLocal } from '@libp2p/utils/multiaddr/is-link-local'
4
+ import { isLoopback } from '@libp2p/utils/multiaddr/is-loopback'
5
+ import { isPrivate } from '@libp2p/utils/multiaddr/is-private'
6
+ import { isPrivateIp } from '@libp2p/utils/private-ip'
7
+ import { QUICV1, TCP, WebSockets, WebSocketsSecure, WebTransport } from '@multiformats/multiaddr-matcher'
8
+ import { dynamicExternalAddress } from './check-external-address.js'
9
+ import { DoubleNATError } from './errors.js'
10
+ import type { ExternalAddress } from './check-external-address.js'
11
+ import type { Gateway } from '@achingbrain/nat-port-mapper'
12
+ import type { ComponentLogger, Logger } from '@libp2p/interface'
13
+ import type { AddressManager } from '@libp2p/interface-internal'
14
+ import type { Multiaddr } from '@multiformats/multiaddr'
15
+
16
+ export interface UPnPPortMapperInit {
17
+ gateway: Gateway
18
+ externalAddressCheckInterval?: number
19
+ externalAddressCheckTimeout?: number
20
+ }
21
+
22
+ export interface UPnPPortMapperComponents {
23
+ logger: ComponentLogger
24
+ addressManager: AddressManager
25
+ }
26
+
27
+ interface PortMapping {
28
+ externalHost: string
29
+ externalPort: number
30
+ }
31
+
32
+ export class UPnPPortMapper {
33
+ private readonly gateway: Gateway
34
+ private readonly externalAddress: ExternalAddress
35
+ private readonly addressManager: AddressManager
36
+ private readonly log: Logger
37
+ private readonly mappedPorts: Map<string, PortMapping>
38
+ private started: boolean
39
+
40
+ constructor (components: UPnPPortMapperComponents, init: UPnPPortMapperInit) {
41
+ this.log = components.logger.forComponent(`libp2p:upnp-nat:gateway:${init.gateway.id}`)
42
+ this.addressManager = components.addressManager
43
+ this.gateway = init.gateway
44
+ this.externalAddress = dynamicExternalAddress({
45
+ gateway: this.gateway,
46
+ addressManager: this.addressManager,
47
+ logger: components.logger
48
+ }, {
49
+ interval: init.externalAddressCheckInterval,
50
+ timeout: init.externalAddressCheckTimeout,
51
+ onExternalAddressChange: this.remapPorts.bind(this)
52
+ })
53
+ this.gateway = init.gateway
54
+ this.mappedPorts = new Map()
55
+ this.started = false
56
+ }
57
+
58
+ async start (): Promise<void> {
59
+ if (this.started) {
60
+ return
61
+ }
62
+
63
+ await start(this.externalAddress)
64
+ this.started = true
65
+ }
66
+
67
+ async stop (): Promise<void> {
68
+ try {
69
+ const shutdownTimeout = AbortSignal.timeout(1000)
70
+
71
+ await this.gateway.stop({
72
+ signal: shutdownTimeout
73
+ })
74
+ } catch (err: any) {
75
+ this.log.error('error closing gateway - %e', err)
76
+ }
77
+
78
+ await stop(this.externalAddress)
79
+ this.started = false
80
+ }
81
+
82
+ /**
83
+ * Update the local address mappings when the gateway's external interface
84
+ * address changes
85
+ */
86
+ private remapPorts (newExternalHost: string): void {
87
+ for (const [key, { externalHost, externalPort }] of this.mappedPorts.entries()) {
88
+ const [
89
+ host,
90
+ port,
91
+ transport
92
+ ] = key.split('-')
93
+
94
+ this.addressManager.removePublicAddressMapping(host, parseInt(port), externalHost, externalPort, transport === 'tcp' ? 'tcp' : 'udp')
95
+ this.addressManager.addPublicAddressMapping(host, parseInt(port), newExternalHost, externalPort, transport === 'tcp' ? 'tcp' : 'udp')
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Return any eligible multiaddrs that are not mapped on the detected gateway
101
+ */
102
+ private getUnmappedAddresses (multiaddrs: Multiaddr[], publicAddresses: string[]): Multiaddr[] {
103
+ const output: Multiaddr[] = []
104
+
105
+ for (const ma of multiaddrs) {
106
+ const stringTuples = ma.stringTuples()
107
+ const address = `${stringTuples[0][1]}`
108
+
109
+ // ignore public IPv4 addresses
110
+ if (isIPv4(address) && !isPrivate(ma)) {
111
+ continue
112
+ }
113
+
114
+ // ignore any addresses that match the interface on the network gateway
115
+ if (publicAddresses.includes(address)) {
116
+ continue
117
+ }
118
+
119
+ // ignore loopback
120
+ if (isLoopback(ma)) {
121
+ continue
122
+ }
123
+
124
+ // ignore link-local addresses
125
+ if (isLinkLocal(ma)) {
126
+ continue
127
+ }
128
+
129
+ // only IP based addresses
130
+ if (!(
131
+ TCP.exactMatch(ma) ||
132
+ WebSockets.exactMatch(ma) ||
133
+ WebSocketsSecure.exactMatch(ma) ||
134
+ QUICV1.exactMatch(ma) ||
135
+ WebTransport.exactMatch(ma)
136
+ )) {
137
+ continue
138
+ }
139
+
140
+ const { port, transport } = ma.toOptions()
141
+
142
+ if (this.mappedPorts.has(`${port}-${transport}`)) {
143
+ continue
144
+ }
145
+
146
+ output.push(ma)
147
+ }
148
+
149
+ return output
150
+ }
151
+
152
+ async mapIpAddresses (): Promise<void> {
153
+ try {
154
+ const externalHost = await this.externalAddress.getPublicIp()
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(), [externalHost])
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('discovered public IP %s', 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 { port, host, transport, family } = addr.toOptions()
172
+
173
+ // don't try to open port on IPv6 host via IPv4 gateway
174
+ if (family === 4 && this.gateway.family !== 'IPv4') {
175
+ continue
176
+ }
177
+
178
+ // don't try to open port on IPv4 host via IPv6 gateway
179
+ if (family === 6 && this.gateway.family !== 'IPv6') {
180
+ continue
181
+ }
182
+
183
+ const key = `${host}-${port}-${transport}`
184
+
185
+ if (this.mappedPorts.has(key)) {
186
+ // already mapped this port
187
+ continue
188
+ }
189
+
190
+ try {
191
+ const mapping = await this.gateway.map(port, host, {
192
+ protocol: transport === 'tcp' ? 'TCP' : 'UDP'
193
+ })
194
+ this.mappedPorts.set(key, mapping)
195
+ this.addressManager.addPublicAddressMapping(mapping.internalHost, mapping.internalPort, mapping.externalHost, mapping.externalPort, transport === 'tcp' ? 'tcp' : 'udp')
196
+ this.log('created mapping of %s:%s to %s:%s for protocol %s', mapping.internalHost, mapping.internalPort, mapping.externalHost, mapping.externalPort, transport)
197
+ } catch (err) {
198
+ this.log.error('failed to create mapping for %s:%d for protocol - %e', host, port, transport, err)
199
+ }
200
+ }
201
+ } catch (err: any) {
202
+ this.log.error('error finding gateways - %e', err)
203
+ }
204
+ }
205
+
206
+ /**
207
+ * Some ISPs have double-NATs, there's not much we can do with them
208
+ */
209
+ private assertNotBehindDoubleNAT (publicIp: string): void {
210
+ const isPrivate = isPrivateIp(publicIp)
211
+
212
+ if (isPrivate === true) {
213
+ 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`)
214
+ }
215
+
216
+ if (isPrivate == null) {
217
+ throw new InvalidParametersError(`${publicIp} is not an IP address`)
218
+ }
219
+ }
220
+ }
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
- }