@libp2p/upnp-nat 2.0.11 → 2.0.12-3650283f7
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 +8 -8
- 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/dist/typedoc-urls.json +0 -12
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
import { NotStartedError, start, stop } from '@libp2p/interface'
|
|
2
2
|
import { repeatingTask } from '@libp2p/utils/repeating-task'
|
|
3
|
-
import { multiaddr } from '@multiformats/multiaddr'
|
|
4
3
|
import pDefer from 'p-defer'
|
|
5
4
|
import { raceSignal } from 'race-signal'
|
|
6
|
-
import type {
|
|
5
|
+
import type { Gateway } from '@achingbrain/nat-port-mapper'
|
|
7
6
|
import type { AbortOptions, ComponentLogger, Logger, Startable } from '@libp2p/interface'
|
|
8
7
|
import type { AddressManager } from '@libp2p/interface-internal'
|
|
9
8
|
import type { RepeatingTask } from '@libp2p/utils/repeating-task'
|
|
10
9
|
import type { DeferredPromise } from 'p-defer'
|
|
11
10
|
|
|
12
11
|
export interface ExternalAddressCheckerComponents {
|
|
13
|
-
|
|
12
|
+
gateway: Gateway
|
|
14
13
|
addressManager: AddressManager
|
|
15
14
|
logger: ComponentLogger
|
|
16
15
|
}
|
|
@@ -18,7 +17,7 @@ export interface ExternalAddressCheckerComponents {
|
|
|
18
17
|
export interface ExternalAddressCheckerInit {
|
|
19
18
|
interval?: number
|
|
20
19
|
timeout?: number
|
|
21
|
-
|
|
20
|
+
onExternalAddressChange?(newExternalAddress: string): void
|
|
22
21
|
}
|
|
23
22
|
|
|
24
23
|
export interface ExternalAddress {
|
|
@@ -30,19 +29,19 @@ export interface ExternalAddress {
|
|
|
30
29
|
*/
|
|
31
30
|
class ExternalAddressChecker implements ExternalAddress, Startable {
|
|
32
31
|
private readonly log: Logger
|
|
33
|
-
private readonly
|
|
32
|
+
private readonly gateway: Gateway
|
|
34
33
|
private readonly addressManager: AddressManager
|
|
35
34
|
private started: boolean
|
|
36
35
|
private lastPublicIp?: string
|
|
37
36
|
private readonly lastPublicIpPromise: DeferredPromise<string>
|
|
38
37
|
private readonly check: RepeatingTask
|
|
39
|
-
private readonly
|
|
38
|
+
private readonly onExternalAddressChange?: (newExternalAddress: string) => void
|
|
40
39
|
|
|
41
|
-
constructor (components: ExternalAddressCheckerComponents, init: ExternalAddressCheckerInit
|
|
40
|
+
constructor (components: ExternalAddressCheckerComponents, init: ExternalAddressCheckerInit) {
|
|
42
41
|
this.log = components.logger.forComponent('libp2p:upnp-nat:external-address-check')
|
|
43
|
-
this.
|
|
42
|
+
this.gateway = components.gateway
|
|
44
43
|
this.addressManager = components.addressManager
|
|
45
|
-
this.
|
|
44
|
+
this.onExternalAddressChange = init.onExternalAddressChange
|
|
46
45
|
this.started = false
|
|
47
46
|
|
|
48
47
|
this.checkExternalAddress = this.checkExternalAddress.bind(this)
|
|
@@ -61,14 +60,11 @@ class ExternalAddressChecker implements ExternalAddress, Startable {
|
|
|
61
60
|
}
|
|
62
61
|
|
|
63
62
|
await start(this.check)
|
|
64
|
-
|
|
65
|
-
this.check.start()
|
|
66
63
|
this.started = true
|
|
67
64
|
}
|
|
68
65
|
|
|
69
66
|
async stop (): Promise<void> {
|
|
70
67
|
await stop(this.check)
|
|
71
|
-
|
|
72
68
|
this.started = false
|
|
73
69
|
}
|
|
74
70
|
|
|
@@ -87,37 +83,21 @@ class ExternalAddressChecker implements ExternalAddress, Startable {
|
|
|
87
83
|
|
|
88
84
|
private async checkExternalAddress (options?: AbortOptions): Promise<void> {
|
|
89
85
|
try {
|
|
90
|
-
const externalAddress = await this.
|
|
86
|
+
const externalAddress = await this.gateway.externalIp(options)
|
|
91
87
|
|
|
92
88
|
// check if our public address has changed
|
|
93
89
|
if (this.lastPublicIp != null && externalAddress !== this.lastPublicIp) {
|
|
94
90
|
this.log('external address changed from %s to %s', this.lastPublicIp, externalAddress)
|
|
95
91
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
if (!addrString.includes(this.lastPublicIp)) {
|
|
100
|
-
continue
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// create a new version of the multiaddr with the new public IP
|
|
104
|
-
const newAddress = multiaddr(addrString.replace(this.lastPublicIp, externalAddress))
|
|
105
|
-
|
|
106
|
-
// remove the old address and add the new one
|
|
107
|
-
this.addressManager.removeObservedAddr(ma)
|
|
108
|
-
this.addressManager.confirmObservedAddr(newAddress)
|
|
109
|
-
|
|
110
|
-
if (this.autoConfirmAddress) {
|
|
111
|
-
this.addressManager.confirmObservedAddr(newAddress)
|
|
112
|
-
} else {
|
|
113
|
-
this.addressManager.addObservedAddr(newAddress)
|
|
114
|
-
}
|
|
115
|
-
}
|
|
92
|
+
// notify listeners that the address has changed
|
|
93
|
+
this.onExternalAddressChange?.(externalAddress)
|
|
116
94
|
}
|
|
117
95
|
|
|
118
96
|
this.lastPublicIp = externalAddress
|
|
119
97
|
this.lastPublicIpPromise.resolve(externalAddress)
|
|
120
98
|
} catch (err: any) {
|
|
99
|
+
this.log.error('could not resolve external address - %e', err)
|
|
100
|
+
|
|
121
101
|
if (this.lastPublicIp != null) {
|
|
122
102
|
// ignore the error if we've previously run successfully
|
|
123
103
|
return
|
|
@@ -128,7 +108,7 @@ class ExternalAddressChecker implements ExternalAddress, Startable {
|
|
|
128
108
|
}
|
|
129
109
|
}
|
|
130
110
|
|
|
131
|
-
export function dynamicExternalAddress (components: ExternalAddressCheckerComponents, init: ExternalAddressCheckerInit
|
|
111
|
+
export function dynamicExternalAddress (components: ExternalAddressCheckerComponents, init: ExternalAddressCheckerInit): ExternalAddress {
|
|
132
112
|
return new ExternalAddressChecker(components, init)
|
|
133
113
|
}
|
|
134
114
|
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { TypedEventEmitter, start, stop } from '@libp2p/interface'
|
|
2
|
+
import { repeatingTask } from '@libp2p/utils/repeating-task'
|
|
3
|
+
import { DEFAULT_GATEWAY_SEARCH_INTERVAL, DEFAULT_GATEWAY_SEARCH_TIMEOUT } from './constants.js'
|
|
4
|
+
import type { Gateway, UPnPNAT } from '@achingbrain/nat-port-mapper'
|
|
5
|
+
import type { ComponentLogger, Logger } from '@libp2p/interface'
|
|
6
|
+
import type { RepeatingTask } from '@libp2p/utils/repeating-task'
|
|
7
|
+
|
|
8
|
+
export interface GatewayFinderComponents {
|
|
9
|
+
logger: ComponentLogger
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface GatewayFinderInit {
|
|
13
|
+
portMappingClient: UPnPNAT
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface GatewayFinderEvents {
|
|
17
|
+
'gateway': CustomEvent<Gateway>
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class GatewayFinder extends TypedEventEmitter<GatewayFinderEvents> {
|
|
21
|
+
private readonly log: Logger
|
|
22
|
+
private readonly gateways: Gateway[]
|
|
23
|
+
private readonly findGateways: RepeatingTask
|
|
24
|
+
private readonly portMappingClient: UPnPNAT
|
|
25
|
+
private started: boolean
|
|
26
|
+
|
|
27
|
+
constructor (components: GatewayFinderComponents, init: GatewayFinderInit) {
|
|
28
|
+
super()
|
|
29
|
+
|
|
30
|
+
this.log = components.logger.forComponent('libp2p:upnp-nat')
|
|
31
|
+
this.portMappingClient = init.portMappingClient
|
|
32
|
+
this.started = false
|
|
33
|
+
this.gateways = []
|
|
34
|
+
|
|
35
|
+
// every five minutes, search for network gateways for one minute
|
|
36
|
+
this.findGateways = repeatingTask(async (options) => {
|
|
37
|
+
for await (const gateway of this.portMappingClient.findGateways(options)) {
|
|
38
|
+
if (this.gateways.some(g => g.id === gateway.id)) {
|
|
39
|
+
// already seen this gateway
|
|
40
|
+
continue
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
this.gateways.push(gateway)
|
|
44
|
+
this.safeDispatchEvent('gateway', {
|
|
45
|
+
detail: gateway
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
}, DEFAULT_GATEWAY_SEARCH_INTERVAL, {
|
|
49
|
+
runImmediately: true,
|
|
50
|
+
timeout: DEFAULT_GATEWAY_SEARCH_TIMEOUT
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async start (): Promise<void> {
|
|
55
|
+
if (this.started) {
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
this.started = true
|
|
60
|
+
await start(this.findGateways)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Stops the NAT manager
|
|
65
|
+
*/
|
|
66
|
+
async stop (): Promise<void> {
|
|
67
|
+
await stop(this.findGateways)
|
|
68
|
+
this.started = false
|
|
69
|
+
}
|
|
70
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -35,11 +35,12 @@
|
|
|
35
35
|
* ```
|
|
36
36
|
*/
|
|
37
37
|
|
|
38
|
-
import { UPnPNAT as UPnPNATClass
|
|
38
|
+
import { UPnPNAT as UPnPNATClass } from './upnp-nat.js'
|
|
39
|
+
import type { UPnPNAT as UPnPNATClient, MapPortOptions } from '@achingbrain/nat-port-mapper'
|
|
39
40
|
import type { ComponentLogger, Libp2pEvents, NodeInfo, PeerId, TypedEventTarget } from '@libp2p/interface'
|
|
40
41
|
import type { AddressManager } from '@libp2p/interface-internal'
|
|
41
42
|
|
|
42
|
-
export type {
|
|
43
|
+
export type { UPnPNATClient, MapPortOptions }
|
|
43
44
|
|
|
44
45
|
export interface PMPOptions {
|
|
45
46
|
/**
|
|
@@ -49,12 +50,6 @@ export interface PMPOptions {
|
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
export interface UPnPNATInit {
|
|
52
|
-
/**
|
|
53
|
-
* Pass a string to hard code the external address, otherwise it will be
|
|
54
|
-
* auto-detected
|
|
55
|
-
*/
|
|
56
|
-
externalAddress?: string
|
|
57
|
-
|
|
58
53
|
/**
|
|
59
54
|
* Check if the external address has changed this often in ms. Ignored if an
|
|
60
55
|
* external address is specified.
|
|
@@ -71,66 +66,31 @@ export interface UPnPNATInit {
|
|
|
71
66
|
*/
|
|
72
67
|
externalAddressCheckTimeout?: number
|
|
73
68
|
|
|
74
|
-
/**
|
|
75
|
-
* Pass a value to use instead of auto-detection
|
|
76
|
-
*/
|
|
77
|
-
localAddress?: string
|
|
78
|
-
|
|
79
69
|
/**
|
|
80
70
|
* A string value to use for the port mapping description on the gateway
|
|
81
71
|
*/
|
|
82
|
-
|
|
72
|
+
portMappingDescription?: string
|
|
83
73
|
|
|
84
74
|
/**
|
|
85
|
-
* How long UPnP port mappings should last for in
|
|
86
|
-
*/
|
|
87
|
-
ttl?: number
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Whether to automatically refresh UPnP port mappings when their TTL is reached
|
|
91
|
-
*/
|
|
92
|
-
keepAlive?: boolean
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Pass a value to use instead of auto-detection
|
|
96
|
-
*/
|
|
97
|
-
gateway?: string
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* How long in ms to wait before giving up trying to auto-detect a
|
|
101
|
-
* `urn:schemas-upnp-org:device:InternetGatewayDevice:1` device on the local
|
|
102
|
-
* network
|
|
75
|
+
* How long UPnP port mappings should last for in ms
|
|
103
76
|
*
|
|
104
|
-
* @default
|
|
77
|
+
* @default 720_000
|
|
105
78
|
*/
|
|
106
|
-
|
|
79
|
+
portMappingTTL?: number
|
|
107
80
|
|
|
108
81
|
/**
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
* multiple addresses are being added, the mapping function is debounced by
|
|
112
|
-
* this number of ms
|
|
82
|
+
* Whether to automatically refresh UPnP port mappings when their TTL is
|
|
83
|
+
* reached
|
|
113
84
|
*
|
|
114
|
-
* @default
|
|
85
|
+
* @default true
|
|
115
86
|
*/
|
|
116
|
-
|
|
87
|
+
portMappingAutoRefresh?: boolean
|
|
117
88
|
|
|
118
89
|
/**
|
|
119
90
|
* A preconfigured instance of a NatAPI client can be passed as an option,
|
|
120
91
|
* otherwise one will be created
|
|
121
92
|
*/
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Any mapped addresses are added to the observed address list. These
|
|
126
|
-
* addresses require additional verification by the `@libp2p/autonat` protocol
|
|
127
|
-
* or similar before they are trusted.
|
|
128
|
-
*
|
|
129
|
-
* To skip this verification and trust them immediately pass `true` here
|
|
130
|
-
*
|
|
131
|
-
* @default false
|
|
132
|
-
*/
|
|
133
|
-
autoConfirmAddress?: boolean
|
|
93
|
+
portMappingClient?: UPnPNATClient
|
|
134
94
|
}
|
|
135
95
|
|
|
136
96
|
export interface UPnPNATComponents {
|
|
@@ -142,7 +102,7 @@ export interface UPnPNATComponents {
|
|
|
142
102
|
}
|
|
143
103
|
|
|
144
104
|
export interface UPnPNAT {
|
|
145
|
-
|
|
105
|
+
portMappingClient: UPnPNATClient
|
|
146
106
|
}
|
|
147
107
|
|
|
148
108
|
export function uPnPNAT (init: UPnPNATInit = {}): (components: UPnPNATComponents) => UPnPNAT {
|
package/src/upnp-nat.ts
CHANGED
|
@@ -1,84 +1,53 @@
|
|
|
1
1
|
import { upnpNat } from '@achingbrain/nat-port-mapper'
|
|
2
|
-
import {
|
|
3
|
-
import { InvalidParametersError, serviceCapabilities, serviceDependencies, start, stop } from '@libp2p/interface'
|
|
2
|
+
import { serviceCapabilities, setMaxListeners, start, stop } from '@libp2p/interface'
|
|
4
3
|
import { debounce } from '@libp2p/utils/debounce'
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { multiaddr } from '@multiformats/multiaddr'
|
|
9
|
-
import { QUICV1, TCP, WebSockets, WebSocketsSecure, WebTransport } from '@multiformats/multiaddr-matcher'
|
|
10
|
-
import { raceSignal } from 'race-signal'
|
|
11
|
-
import { dynamicExternalAddress, staticExternalAddress } from './check-external-address.js'
|
|
12
|
-
import { DoubleNATError, InvalidIPAddressError } from './errors.js'
|
|
13
|
-
import type { ExternalAddress } from './check-external-address.js'
|
|
4
|
+
import { DEFAULT_PORT_MAPPING_TTL } from './constants.js'
|
|
5
|
+
import { GatewayFinder } from './gateway-finder.js'
|
|
6
|
+
import { UPnPPortMapper } from './upnp-port-mapper.js'
|
|
14
7
|
import type { UPnPNATComponents, UPnPNATInit, UPnPNAT as UPnPNATInterface } from './index.js'
|
|
15
|
-
import type {
|
|
16
|
-
import type {
|
|
17
|
-
import type { AddressManager } from '@libp2p/interface-internal'
|
|
8
|
+
import type { Gateway, UPnPNAT as UPnPNATClient } from '@achingbrain/nat-port-mapper'
|
|
9
|
+
import type { Logger, Startable } from '@libp2p/interface'
|
|
18
10
|
import type { DebouncedFunction } from '@libp2p/utils/debounce'
|
|
19
|
-
import type { Multiaddr } from '@multiformats/multiaddr'
|
|
20
|
-
|
|
21
|
-
const DEFAULT_TTL = 7200
|
|
22
|
-
|
|
23
|
-
export type { NatAPI, MapPortOptions }
|
|
24
11
|
|
|
25
12
|
export class UPnPNAT implements Startable, UPnPNATInterface {
|
|
26
|
-
public client: NatAPI
|
|
27
|
-
private readonly addressManager: AddressManager
|
|
28
|
-
private readonly events: TypedEventTarget<Libp2pEvents>
|
|
29
|
-
private readonly externalAddress: ExternalAddress
|
|
30
|
-
private readonly localAddress?: string
|
|
31
|
-
private readonly description: string
|
|
32
|
-
private readonly ttl: number
|
|
33
|
-
private readonly keepAlive: boolean
|
|
34
|
-
private readonly gateway?: string
|
|
35
|
-
private started: boolean
|
|
36
13
|
private readonly log: Logger
|
|
37
|
-
private readonly
|
|
38
|
-
private readonly
|
|
39
|
-
private
|
|
40
|
-
|
|
14
|
+
private readonly components: UPnPNATComponents
|
|
15
|
+
private readonly init: UPnPNATInit
|
|
16
|
+
private started: boolean
|
|
17
|
+
public portMappingClient: UPnPNATClient
|
|
18
|
+
private shutdownController?: AbortController
|
|
19
|
+
private readonly mapIpAddressesDebounced: DebouncedFunction
|
|
20
|
+
private readonly gatewayFinder: GatewayFinder
|
|
21
|
+
private readonly portMappers: UPnPPortMapper[]
|
|
41
22
|
|
|
42
23
|
constructor (components: UPnPNATComponents, init: UPnPNATInit) {
|
|
43
24
|
this.log = components.logger.forComponent('libp2p:upnp-nat')
|
|
44
|
-
this.
|
|
45
|
-
this.
|
|
25
|
+
this.components = components
|
|
26
|
+
this.init = init
|
|
46
27
|
this.started = false
|
|
47
|
-
this.
|
|
48
|
-
this.description = init.description ?? `${components.nodeInfo.name}@${components.nodeInfo.version} ${components.peerId.toString()}`
|
|
49
|
-
this.ttl = init.ttl ?? DEFAULT_TTL
|
|
50
|
-
this.keepAlive = init.keepAlive ?? true
|
|
51
|
-
this.gateway = init.gateway
|
|
52
|
-
this.gatewayDetectionTimeout = init.gatewayDetectionTimeout ?? 10000
|
|
53
|
-
this.autoConfirmAddress = init.autoConfirmAddress ?? false
|
|
54
|
-
this.mappedPorts = new Map()
|
|
55
|
-
|
|
56
|
-
if (this.ttl < DEFAULT_TTL) {
|
|
57
|
-
throw new InvalidParametersError(`NatManager ttl should be at least ${DEFAULT_TTL} seconds`)
|
|
58
|
-
}
|
|
28
|
+
this.portMappers = []
|
|
59
29
|
|
|
60
|
-
this.
|
|
61
|
-
description:
|
|
62
|
-
ttl:
|
|
63
|
-
|
|
64
|
-
gateway: this.gateway
|
|
30
|
+
this.portMappingClient = init.portMappingClient ?? upnpNat({
|
|
31
|
+
description: init.portMappingDescription ?? `${components.nodeInfo.name}@${components.nodeInfo.version} ${components.peerId.toString()}`,
|
|
32
|
+
ttl: init.portMappingTTL ?? DEFAULT_PORT_MAPPING_TTL,
|
|
33
|
+
autoRefresh: init.portMappingAutoRefresh ?? true
|
|
65
34
|
})
|
|
66
35
|
|
|
67
|
-
|
|
36
|
+
// trigger update when our addresses change
|
|
37
|
+
this.mapIpAddressesDebounced = debounce(async () => {
|
|
38
|
+
try {
|
|
39
|
+
await this.mapIpAddresses()
|
|
40
|
+
} catch (err: any) {
|
|
41
|
+
this.log.error('error mapping IP addresses - %e', err)
|
|
42
|
+
}
|
|
43
|
+
}, 5_000)
|
|
68
44
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
logger: components.logger
|
|
76
|
-
}, {
|
|
77
|
-
autoConfirmAddress: init.autoConfirmAddress,
|
|
78
|
-
interval: init.externalAddressCheckInterval,
|
|
79
|
-
timeout: init.externalAddressCheckTimeout
|
|
80
|
-
})
|
|
81
|
-
}
|
|
45
|
+
// trigger update when we discovery gateways on the network
|
|
46
|
+
this.gatewayFinder = new GatewayFinder(components, {
|
|
47
|
+
portMappingClient: this.portMappingClient
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
this.onGatewayDiscovered = this.onGatewayDiscovered.bind(this)
|
|
82
51
|
}
|
|
83
52
|
|
|
84
53
|
readonly [Symbol.toStringTag] = '@libp2p/upnp-nat'
|
|
@@ -87,16 +56,6 @@ export class UPnPNAT implements Startable, UPnPNATInterface {
|
|
|
87
56
|
'@libp2p/nat-traversal'
|
|
88
57
|
]
|
|
89
58
|
|
|
90
|
-
get [serviceDependencies] (): string[] {
|
|
91
|
-
if (!this.autoConfirmAddress) {
|
|
92
|
-
return [
|
|
93
|
-
'@libp2p/autonat'
|
|
94
|
-
]
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return []
|
|
98
|
-
}
|
|
99
|
-
|
|
100
59
|
isStarted (): boolean {
|
|
101
60
|
return this.started
|
|
102
61
|
}
|
|
@@ -107,158 +66,46 @@ export class UPnPNAT implements Startable, UPnPNATInterface {
|
|
|
107
66
|
}
|
|
108
67
|
|
|
109
68
|
this.started = true
|
|
110
|
-
this.
|
|
111
|
-
|
|
69
|
+
this.shutdownController = new AbortController()
|
|
70
|
+
setMaxListeners(Infinity, this.shutdownController.signal)
|
|
71
|
+
this.components.events.addEventListener('self:peer:update', this.mapIpAddressesDebounced)
|
|
72
|
+
this.gatewayFinder.addEventListener('gateway', this.onGatewayDiscovered)
|
|
73
|
+
await start(this.mapIpAddressesDebounced, this.gatewayFinder, ...this.portMappers)
|
|
112
74
|
}
|
|
113
75
|
|
|
114
76
|
/**
|
|
115
77
|
* Stops the NAT manager
|
|
116
78
|
*/
|
|
117
79
|
async stop (): Promise<void> {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
this.events.removeEventListener('self:peer:update', this.onSelfPeerUpdate)
|
|
125
|
-
await stop(this.externalAddress, this.onSelfPeerUpdate)
|
|
80
|
+
this.shutdownController?.abort()
|
|
81
|
+
this.components.events.removeEventListener('self:peer:update', this.mapIpAddressesDebounced)
|
|
82
|
+
this.gatewayFinder.removeEventListener('gateway', this.onGatewayDiscovered)
|
|
83
|
+
await stop(this.mapIpAddressesDebounced, this.gatewayFinder, ...this.portMappers)
|
|
126
84
|
this.started = false
|
|
127
85
|
}
|
|
128
86
|
|
|
129
|
-
|
|
130
|
-
this.
|
|
131
|
-
.
|
|
132
|
-
|
|
133
|
-
})
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
private getUnmappedAddresses (multiaddrs: Multiaddr[], ipType: 4 | 6): Multiaddr[] {
|
|
137
|
-
const output: Multiaddr[] = []
|
|
138
|
-
|
|
139
|
-
for (const ma of multiaddrs) {
|
|
140
|
-
// ignore public addresses
|
|
141
|
-
if (!isPrivate(ma)) {
|
|
142
|
-
continue
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// ignore loopback
|
|
146
|
-
if (isLoopback(ma)) {
|
|
147
|
-
continue
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// only IP based addresses
|
|
151
|
-
if (!(
|
|
152
|
-
TCP.exactMatch(ma) ||
|
|
153
|
-
WebSockets.exactMatch(ma) ||
|
|
154
|
-
WebSocketsSecure.exactMatch(ma) ||
|
|
155
|
-
QUICV1.exactMatch(ma) ||
|
|
156
|
-
WebTransport.exactMatch(ma)
|
|
157
|
-
)) {
|
|
158
|
-
continue
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const { port, family } = ma.toOptions()
|
|
162
|
-
|
|
163
|
-
if (family !== ipType) {
|
|
164
|
-
continue
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
if (this.mappedPorts.has(port)) {
|
|
168
|
-
continue
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
output.push(ma)
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
return output
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
async mapIpAddresses (): Promise<void> {
|
|
178
|
-
if (this.externalAddress == null) {
|
|
179
|
-
this.log('discovering public address')
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
const publicIp = await this.externalAddress.getPublicIp({
|
|
183
|
-
signal: AbortSignal.timeout(this.gatewayDetectionTimeout)
|
|
184
|
-
})
|
|
185
|
-
|
|
186
|
-
this.externalAddress ?? await raceSignal(this.client.externalIp(), AbortSignal.timeout(this.gatewayDetectionTimeout), {
|
|
187
|
-
errorMessage: `Did not discover a "urn:schemas-upnp-org:device:InternetGatewayDevice:1" device on the local network after ${this.gatewayDetectionTimeout}ms - UPnP may not be configured on your router correctly`
|
|
87
|
+
onGatewayDiscovered (event: CustomEvent<Gateway>): void {
|
|
88
|
+
const mapper = new UPnPPortMapper(this.components, {
|
|
89
|
+
...this.init,
|
|
90
|
+
gateway: event.detail
|
|
188
91
|
})
|
|
189
92
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
if (isIPv4(publicIp)) {
|
|
193
|
-
ipType = 4
|
|
194
|
-
} else if (isIPv6(publicIp)) {
|
|
195
|
-
ipType = 6
|
|
196
|
-
} else {
|
|
197
|
-
throw new InvalidIPAddressError(`Public address ${publicIp} was not an IPv4 address`)
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// filter addresses to get private, non-relay, IP based addresses that we
|
|
201
|
-
// haven't mapped yet
|
|
202
|
-
const addresses = this.getUnmappedAddresses(this.addressManager.getAddresses(), ipType)
|
|
203
|
-
|
|
204
|
-
if (addresses.length === 0) {
|
|
205
|
-
this.log('no private, non-relay, unmapped, IP based addresses found')
|
|
206
|
-
return
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
this.log('%s public IP %s', this.externalAddress != null ? 'using configured' : 'discovered', publicIp)
|
|
210
|
-
|
|
211
|
-
this.assertNotBehindDoubleNAT(publicIp)
|
|
212
|
-
|
|
213
|
-
for (const addr of addresses) {
|
|
214
|
-
// try to open uPnP ports for each thin waist address
|
|
215
|
-
const { family, host, port, transport } = addr.toOptions()
|
|
216
|
-
|
|
217
|
-
if (family === 6) {
|
|
218
|
-
// only support IPv4 addresses
|
|
219
|
-
continue
|
|
220
|
-
}
|
|
93
|
+
this.portMappers.push(mapper)
|
|
221
94
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
const protocol = transport.toUpperCase()
|
|
228
|
-
|
|
229
|
-
this.log(`creating mapping of ${host}:${port}`)
|
|
230
|
-
|
|
231
|
-
const mappedPort = await this.client.map(port, {
|
|
232
|
-
localAddress: host,
|
|
233
|
-
protocol: protocol === 'TCP' ? 'TCP' : 'UDP'
|
|
95
|
+
start(mapper)
|
|
96
|
+
.then(() => {
|
|
97
|
+
this.mapIpAddressesDebounced()
|
|
234
98
|
})
|
|
235
|
-
|
|
236
|
-
this.mappedPorts.set(port, mappedPort)
|
|
237
|
-
|
|
238
|
-
const ma = multiaddr(addr.toString().replace(`/ip${family}/${host}/${transport}/${port}`, `/ip${family}/${publicIp}/${transport}/${mappedPort}`))
|
|
239
|
-
|
|
240
|
-
this.log(`created mapping of ${publicIp}:${mappedPort} to ${host}:${port} as %a`, ma)
|
|
241
|
-
|
|
242
|
-
if (this.autoConfirmAddress) {
|
|
243
|
-
this.addressManager.confirmObservedAddr(ma)
|
|
244
|
-
} else {
|
|
245
|
-
this.addressManager.addObservedAddr(ma)
|
|
246
|
-
}
|
|
247
|
-
}
|
|
99
|
+
.catch(() => {})
|
|
248
100
|
}
|
|
249
101
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
throw new DoubleNATError(`${publicIp} is private - please set config.nat.externalIp to an externally routable IP or ensure you are not behind a double NAT`)
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
if (isPrivate == null) {
|
|
261
|
-
throw new InvalidParametersError(`${publicIp} is not an IP address`)
|
|
102
|
+
async mapIpAddresses (): Promise<void> {
|
|
103
|
+
try {
|
|
104
|
+
await Promise.all(
|
|
105
|
+
this.portMappers.map(async mapper => mapper.mapIpAddresses())
|
|
106
|
+
)
|
|
107
|
+
} catch (err: any) {
|
|
108
|
+
this.log.error('error mapping IP addresses - %e', err)
|
|
262
109
|
}
|
|
263
110
|
}
|
|
264
111
|
}
|