@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.
- package/dist/src/check-external-address.d.ts +4 -4
- package/dist/src/check-external-address.d.ts.map +1 -1
- package/dist/src/check-external-address.js +10 -26
- package/dist/src/check-external-address.js.map +1 -1
- package/dist/src/constants.d.ts +4 -0
- package/dist/src/constants.d.ts.map +1 -0
- package/dist/src/constants.js +4 -0
- package/dist/src/constants.js.map +1 -0
- package/dist/src/gateway-finder.d.ts +26 -0
- package/dist/src/gateway-finder.d.ts.map +1 -0
- package/dist/src/gateway-finder.js +48 -0
- package/dist/src/gateway-finder.js.map +1 -0
- package/dist/src/index.d.ts +12 -47
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/upnp-nat.d.ts +11 -24
- package/dist/src/upnp-nat.d.ts.map +1 -1
- package/dist/src/upnp-nat.js +56 -175
- package/dist/src/upnp-nat.js.map +1 -1
- package/dist/src/upnp-port-mapper.d.ts +38 -0
- package/dist/src/upnp-port-mapper.d.ts.map +1 -0
- package/dist/src/upnp-port-mapper.js +166 -0
- package/dist/src/upnp-port-mapper.js.map +1 -0
- package/package.json +11 -11
- package/src/check-external-address.ts +14 -34
- package/src/constants.ts +3 -0
- package/src/gateway-finder.ts +70 -0
- package/src/index.ts +13 -53
- package/src/upnp-nat.ts +60 -213
- package/src/upnp-port-mapper.ts +223 -0
- package/LICENSE +0 -4
- package/dist/typedoc-urls.json +0 -12
|
@@ -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
package/dist/typedoc-urls.json
DELETED
|
@@ -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
|
-
}
|