@libp2p/upnp-nat 2.0.10 → 2.0.11
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 +19 -0
- package/dist/src/check-external-address.d.ts.map +1 -0
- package/dist/src/check-external-address.js +98 -0
- package/dist/src/check-external-address.js.map +1 -0
- package/dist/src/errors.d.ts +4 -0
- package/dist/src/errors.d.ts.map +1 -1
- package/dist/src/errors.js +4 -0
- package/dist/src/errors.js.map +1 -1
- package/dist/src/index.d.ts +51 -4
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/upnp-nat.d.ts +17 -11
- package/dist/src/upnp-nat.d.ts.map +1 -1
- package/dist/src/upnp-nat.js +149 -64
- package/dist/src/upnp-nat.js.map +1 -1
- package/package.json +8 -5
- package/src/check-external-address.ts +139 -0
- package/src/errors.ts +5 -0
- package/src/index.ts +57 -4
- package/src/upnp-nat.ts +178 -70
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { NatAPI } from '@achingbrain/nat-port-mapper';
|
|
2
|
+
import type { AbortOptions, ComponentLogger } from '@libp2p/interface';
|
|
3
|
+
import type { AddressManager } from '@libp2p/interface-internal';
|
|
4
|
+
export interface ExternalAddressCheckerComponents {
|
|
5
|
+
client: NatAPI;
|
|
6
|
+
addressManager: AddressManager;
|
|
7
|
+
logger: ComponentLogger;
|
|
8
|
+
}
|
|
9
|
+
export interface ExternalAddressCheckerInit {
|
|
10
|
+
interval?: number;
|
|
11
|
+
timeout?: number;
|
|
12
|
+
autoConfirmAddress?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export interface ExternalAddress {
|
|
15
|
+
getPublicIp(options?: AbortOptions): Promise<string> | string;
|
|
16
|
+
}
|
|
17
|
+
export declare function dynamicExternalAddress(components: ExternalAddressCheckerComponents, init?: ExternalAddressCheckerInit): ExternalAddress;
|
|
18
|
+
export declare function staticExternalAddress(address: string): ExternalAddress;
|
|
19
|
+
//# sourceMappingURL=check-external-address.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check-external-address.d.ts","sourceRoot":"","sources":["../../src/check-external-address.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,8BAA8B,CAAA;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAqB,MAAM,mBAAmB,CAAA;AACzF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAA;AAIhE,MAAM,WAAW,gCAAgC;IAC/C,MAAM,EAAE,MAAM,CAAA;IACd,cAAc,EAAE,cAAc,CAAA;IAC9B,MAAM,EAAE,eAAe,CAAA;CACxB;AAED,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,kBAAkB,CAAC,EAAE,OAAO,CAAA;CAC7B;AAED,MAAM,WAAW,eAAe;IAC9B,WAAW,CAAE,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAA;CAC/D;AAyGD,wBAAgB,sBAAsB,CAAE,UAAU,EAAE,gCAAgC,EAAE,IAAI,GAAE,0BAA+B,GAAG,eAAe,CAE5I;AAED,wBAAgB,qBAAqB,CAAE,OAAO,EAAE,MAAM,GAAG,eAAe,CAIvE"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { NotStartedError, start, stop } from '@libp2p/interface';
|
|
2
|
+
import { repeatingTask } from '@libp2p/utils/repeating-task';
|
|
3
|
+
import { multiaddr } from '@multiformats/multiaddr';
|
|
4
|
+
import pDefer from 'p-defer';
|
|
5
|
+
import { raceSignal } from 'race-signal';
|
|
6
|
+
/**
|
|
7
|
+
* Monitors the external network address and notifies when/if it changes
|
|
8
|
+
*/
|
|
9
|
+
class ExternalAddressChecker {
|
|
10
|
+
log;
|
|
11
|
+
client;
|
|
12
|
+
addressManager;
|
|
13
|
+
started;
|
|
14
|
+
lastPublicIp;
|
|
15
|
+
lastPublicIpPromise;
|
|
16
|
+
check;
|
|
17
|
+
autoConfirmAddress;
|
|
18
|
+
constructor(components, init = {}) {
|
|
19
|
+
this.log = components.logger.forComponent('libp2p:upnp-nat:external-address-check');
|
|
20
|
+
this.client = components.client;
|
|
21
|
+
this.addressManager = components.addressManager;
|
|
22
|
+
this.autoConfirmAddress = init.autoConfirmAddress ?? false;
|
|
23
|
+
this.started = false;
|
|
24
|
+
this.checkExternalAddress = this.checkExternalAddress.bind(this);
|
|
25
|
+
this.lastPublicIpPromise = pDefer();
|
|
26
|
+
this.check = repeatingTask(this.checkExternalAddress, init.interval ?? 30000, {
|
|
27
|
+
timeout: init.timeout ?? 10000,
|
|
28
|
+
runImmediately: true
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
async start() {
|
|
32
|
+
if (this.started) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
await start(this.check);
|
|
36
|
+
this.check.start();
|
|
37
|
+
this.started = true;
|
|
38
|
+
}
|
|
39
|
+
async stop() {
|
|
40
|
+
await stop(this.check);
|
|
41
|
+
this.started = false;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Return the last public IP address we found, or wait for it to be found
|
|
45
|
+
*/
|
|
46
|
+
async getPublicIp(options) {
|
|
47
|
+
if (!this.started) {
|
|
48
|
+
throw new NotStartedError('Not started yet');
|
|
49
|
+
}
|
|
50
|
+
return this.lastPublicIp ?? raceSignal(this.lastPublicIpPromise.promise, options?.signal, {
|
|
51
|
+
errorMessage: 'Requesting the public IP from the network gateway timed out - UPnP may not be enabled'
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
async checkExternalAddress(options) {
|
|
55
|
+
try {
|
|
56
|
+
const externalAddress = await this.client.externalIp(options);
|
|
57
|
+
// check if our public address has changed
|
|
58
|
+
if (this.lastPublicIp != null && externalAddress !== this.lastPublicIp) {
|
|
59
|
+
this.log('external address changed from %s to %s', this.lastPublicIp, externalAddress);
|
|
60
|
+
for (const ma of this.addressManager.getAddresses()) {
|
|
61
|
+
const addrString = ma.toString();
|
|
62
|
+
if (!addrString.includes(this.lastPublicIp)) {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
// create a new version of the multiaddr with the new public IP
|
|
66
|
+
const newAddress = multiaddr(addrString.replace(this.lastPublicIp, externalAddress));
|
|
67
|
+
// remove the old address and add the new one
|
|
68
|
+
this.addressManager.removeObservedAddr(ma);
|
|
69
|
+
this.addressManager.confirmObservedAddr(newAddress);
|
|
70
|
+
if (this.autoConfirmAddress) {
|
|
71
|
+
this.addressManager.confirmObservedAddr(newAddress);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
this.addressManager.addObservedAddr(newAddress);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
this.lastPublicIp = externalAddress;
|
|
79
|
+
this.lastPublicIpPromise.resolve(externalAddress);
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
if (this.lastPublicIp != null) {
|
|
83
|
+
// ignore the error if we've previously run successfully
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
this.lastPublicIpPromise.reject(err);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
export function dynamicExternalAddress(components, init = {}) {
|
|
91
|
+
return new ExternalAddressChecker(components, init);
|
|
92
|
+
}
|
|
93
|
+
export function staticExternalAddress(address) {
|
|
94
|
+
return {
|
|
95
|
+
getPublicIp: () => address
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=check-external-address.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check-external-address.js","sourceRoot":"","sources":["../../src/check-external-address.ts"],"names":[],"mappings":"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"}
|
package/dist/src/errors.d.ts
CHANGED
package/dist/src/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/errors.ts"],"names":[],"mappings":"AAAA,qBAAa,cAAe,SAAQ,KAAK;gBAC1B,OAAO,SAAwB;CAI7C"}
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/errors.ts"],"names":[],"mappings":"AAAA,qBAAa,cAAe,SAAQ,KAAK;gBAC1B,OAAO,SAAwB;CAI7C;AAED,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,MAAM,CAAC,IAAI,SAA0B;IACrC,IAAI,SAA0B;CAC/B"}
|
package/dist/src/errors.js
CHANGED
package/dist/src/errors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,cAAe,SAAQ,KAAK;IACvC,YAAa,OAAO,GAAG,qBAAqB;QAC1C,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAA;IAC9B,CAAC;CACF"}
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,cAAe,SAAQ,KAAK;IACvC,YAAa,OAAO,GAAG,qBAAqB;QAC1C,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAA;IAC9B,CAAC;CACF;AAED,MAAM,OAAO,qBAAsB,SAAQ,KAAK;IAC9C,MAAM,CAAC,IAAI,GAAG,uBAAuB,CAAA;IACrC,IAAI,GAAG,uBAAuB,CAAA"}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
* ```
|
|
36
36
|
*/
|
|
37
37
|
import { type NatAPI, type MapPortOptions } from './upnp-nat.js';
|
|
38
|
-
import type { ComponentLogger, NodeInfo, PeerId } from '@libp2p/interface';
|
|
39
|
-
import type { AddressManager
|
|
38
|
+
import type { ComponentLogger, Libp2pEvents, NodeInfo, PeerId, TypedEventTarget } from '@libp2p/interface';
|
|
39
|
+
import type { AddressManager } from '@libp2p/interface-internal';
|
|
40
40
|
export type { NatAPI, MapPortOptions };
|
|
41
41
|
export interface PMPOptions {
|
|
42
42
|
/**
|
|
@@ -46,9 +46,24 @@ export interface PMPOptions {
|
|
|
46
46
|
}
|
|
47
47
|
export interface UPnPNATInit {
|
|
48
48
|
/**
|
|
49
|
-
* Pass a
|
|
49
|
+
* Pass a string to hard code the external address, otherwise it will be
|
|
50
|
+
* auto-detected
|
|
50
51
|
*/
|
|
51
52
|
externalAddress?: string;
|
|
53
|
+
/**
|
|
54
|
+
* Check if the external address has changed this often in ms. Ignored if an
|
|
55
|
+
* external address is specified.
|
|
56
|
+
*
|
|
57
|
+
* @default 30000
|
|
58
|
+
*/
|
|
59
|
+
externalAddressCheckInterval?: number;
|
|
60
|
+
/**
|
|
61
|
+
* Do not take longer than this to check if the external address has changed
|
|
62
|
+
* in ms. Ignored if an external address is specified.
|
|
63
|
+
*
|
|
64
|
+
* @default 10000
|
|
65
|
+
*/
|
|
66
|
+
externalAddressCheckTimeout?: number;
|
|
52
67
|
/**
|
|
53
68
|
* Pass a value to use instead of auto-detection
|
|
54
69
|
*/
|
|
@@ -69,13 +84,45 @@ export interface UPnPNATInit {
|
|
|
69
84
|
* Pass a value to use instead of auto-detection
|
|
70
85
|
*/
|
|
71
86
|
gateway?: string;
|
|
87
|
+
/**
|
|
88
|
+
* How long in ms to wait before giving up trying to auto-detect a
|
|
89
|
+
* `urn:schemas-upnp-org:device:InternetGatewayDevice:1` device on the local
|
|
90
|
+
* network
|
|
91
|
+
*
|
|
92
|
+
* @default 10000
|
|
93
|
+
*/
|
|
94
|
+
gatewayDetectionTimeout?: number;
|
|
95
|
+
/**
|
|
96
|
+
* Ports are mapped when the `self:peer:update` event fires, which happens
|
|
97
|
+
* when the node's addresses change. To avoid starting to map ports while
|
|
98
|
+
* multiple addresses are being added, the mapping function is debounced by
|
|
99
|
+
* this number of ms
|
|
100
|
+
*
|
|
101
|
+
* @default 5000
|
|
102
|
+
*/
|
|
103
|
+
delay?: number;
|
|
104
|
+
/**
|
|
105
|
+
* A preconfigured instance of a NatAPI client can be passed as an option,
|
|
106
|
+
* otherwise one will be created
|
|
107
|
+
*/
|
|
108
|
+
client?: NatAPI;
|
|
109
|
+
/**
|
|
110
|
+
* Any mapped addresses are added to the observed address list. These
|
|
111
|
+
* addresses require additional verification by the `@libp2p/autonat` protocol
|
|
112
|
+
* or similar before they are trusted.
|
|
113
|
+
*
|
|
114
|
+
* To skip this verification and trust them immediately pass `true` here
|
|
115
|
+
*
|
|
116
|
+
* @default false
|
|
117
|
+
*/
|
|
118
|
+
autoConfirmAddress?: boolean;
|
|
72
119
|
}
|
|
73
120
|
export interface UPnPNATComponents {
|
|
74
121
|
peerId: PeerId;
|
|
75
122
|
nodeInfo: NodeInfo;
|
|
76
123
|
logger: ComponentLogger;
|
|
77
|
-
transportManager: TransportManager;
|
|
78
124
|
addressManager: AddressManager;
|
|
125
|
+
events: TypedEventTarget<Libp2pEvents>;
|
|
79
126
|
}
|
|
80
127
|
export interface UPnPNAT {
|
|
81
128
|
client: NatAPI;
|
package/dist/src/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,OAAO,EAA2B,KAAK,MAAM,EAAE,KAAK,cAAc,EAAE,MAAM,eAAe,CAAA;AACzF,OAAO,KAAK,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,OAAO,EAA2B,KAAK,MAAM,EAAE,KAAK,cAAc,EAAE,MAAM,eAAe,CAAA;AACzF,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AAC1G,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAA;AAEhE,YAAY,EAAE,MAAM,EAAE,cAAc,EAAE,CAAA;AAEtC,MAAM,WAAW,UAAU;IACzB;;OAEG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAA;IAExB;;;;;OAKG;IACH,4BAA4B,CAAC,EAAE,MAAM,CAAA;IAErC;;;;;OAKG;IACH,2BAA2B,CAAC,EAAE,MAAM,CAAA;IAEpC;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IAErB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IAEpB;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAA;IAEZ;;OAEG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;IAEnB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAEhB;;;;;;OAMG;IACH,uBAAuB,CAAC,EAAE,MAAM,CAAA;IAEhC;;;;;;;OAOG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IAEd;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IAEf;;;;;;;;OAQG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAA;CAC7B;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,QAAQ,CAAA;IAClB,MAAM,EAAE,eAAe,CAAA;IACvB,cAAc,EAAE,cAAc,CAAA;IAC9B,MAAM,EAAE,gBAAgB,CAAC,YAAY,CAAC,CAAA;CACvC;AAED,MAAM,WAAW,OAAO;IACtB,MAAM,EAAE,MAAM,CAAA;CACf;AAED,wBAAgB,OAAO,CAAE,IAAI,GAAE,WAAgB,GAAG,CAAC,UAAU,EAAE,iBAAiB,KAAK,OAAO,CAI3F"}
|
package/dist/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,OAAO,EAAE,OAAO,IAAI,YAAY,EAAoC,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,OAAO,EAAE,OAAO,IAAI,YAAY,EAAoC,MAAM,eAAe,CAAA;AA8GzF,MAAM,UAAU,OAAO,CAAE,OAAoB,EAAE;IAC7C,OAAO,CAAC,UAA6B,EAAE,EAAE;QACvC,OAAO,IAAI,YAAY,CAAC,UAAU,EAAE,IAAI,CAAC,CAAA;IAC3C,CAAC,CAAA;AACH,CAAC"}
|
package/dist/src/upnp-nat.d.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { serviceCapabilities } from '@libp2p/interface';
|
|
1
|
+
import { serviceCapabilities, serviceDependencies } from '@libp2p/interface';
|
|
3
2
|
import type { UPnPNATComponents, UPnPNATInit, UPnPNAT as UPnPNATInterface } from './index.js';
|
|
3
|
+
import type { NatAPI, MapPortOptions } from '@achingbrain/nat-port-mapper';
|
|
4
4
|
import type { Startable } from '@libp2p/interface';
|
|
5
5
|
export type { NatAPI, MapPortOptions };
|
|
6
6
|
export declare class UPnPNAT implements Startable, UPnPNATInterface {
|
|
7
7
|
client: NatAPI;
|
|
8
|
-
private readonly
|
|
9
|
-
private readonly
|
|
8
|
+
private readonly addressManager;
|
|
9
|
+
private readonly events;
|
|
10
|
+
private readonly externalAddress;
|
|
10
11
|
private readonly localAddress?;
|
|
11
12
|
private readonly description;
|
|
12
13
|
private readonly ttl;
|
|
@@ -14,21 +15,26 @@ export declare class UPnPNAT implements Startable, UPnPNATInterface {
|
|
|
14
15
|
private readonly gateway?;
|
|
15
16
|
private started;
|
|
16
17
|
private readonly log;
|
|
18
|
+
private readonly gatewayDetectionTimeout;
|
|
19
|
+
private readonly mappedPorts;
|
|
20
|
+
private readonly onSelfPeerUpdate;
|
|
21
|
+
private readonly autoConfirmAddress;
|
|
17
22
|
constructor(components: UPnPNATComponents, init: UPnPNATInit);
|
|
18
23
|
readonly [Symbol.toStringTag] = "@libp2p/upnp-nat";
|
|
19
24
|
readonly [serviceCapabilities]: string[];
|
|
25
|
+
get [serviceDependencies](): string[];
|
|
20
26
|
isStarted(): boolean;
|
|
21
|
-
start(): void
|
|
27
|
+
start(): Promise<void>;
|
|
22
28
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* Run after start to ensure the transport manager has all addresses configured.
|
|
29
|
+
* Stops the NAT manager
|
|
26
30
|
*/
|
|
27
|
-
|
|
31
|
+
stop(): Promise<void>;
|
|
32
|
+
_onSelfPeerUpdate(): void;
|
|
33
|
+
private getUnmappedAddresses;
|
|
28
34
|
mapIpAddresses(): Promise<void>;
|
|
29
35
|
/**
|
|
30
|
-
*
|
|
36
|
+
* Some ISPs have double-NATs, there's not much we can do with them
|
|
31
37
|
*/
|
|
32
|
-
|
|
38
|
+
private assertNotBehindDoubleNAT;
|
|
33
39
|
}
|
|
34
40
|
//# sourceMappingURL=upnp-nat.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"upnp-nat.d.ts","sourceRoot":"","sources":["../../src/upnp-nat.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"upnp-nat.d.ts","sourceRoot":"","sources":["../../src/upnp-nat.ts"],"names":[],"mappings":"AAEA,OAAO,EAA0B,mBAAmB,EAAE,mBAAmB,EAAe,MAAM,mBAAmB,CAAA;AAWjH,OAAO,KAAK,EAAE,iBAAiB,EAAE,WAAW,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAC7F,OAAO,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAA;AAC1E,OAAO,KAAK,EAAwB,SAAS,EAAoB,MAAM,mBAAmB,CAAA;AAO1F,YAAY,EAAE,MAAM,EAAE,cAAc,EAAE,CAAA;AAEtC,qBAAa,OAAQ,YAAW,SAAS,EAAE,gBAAgB;IAClD,MAAM,EAAE,MAAM,CAAA;IACrB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAgB;IAC/C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgC;IACvD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAiB;IACjD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAQ;IACtC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAQ;IACpC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAQ;IAC5B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAQ;IACjC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAQ;IAC5B,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAAQ;IAChD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAqB;IACjD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAmB;IACpD,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAS;gBAE/B,UAAU,EAAE,iBAAiB,EAAE,IAAI,EAAE,WAAW;IA0C7D,QAAQ,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,sBAAqB;IAElD,QAAQ,CAAC,CAAC,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAEvC;IAED,IAAI,CAAC,mBAAmB,CAAC,IAAK,MAAM,EAAE,CAQrC;IAED,SAAS,IAAK,OAAO;IAIf,KAAK,IAAK,OAAO,CAAC,IAAI,CAAC;IAU7B;;OAEG;IACG,IAAI,IAAK,OAAO,CAAC,IAAI,CAAC;IAY5B,iBAAiB,IAAK,IAAI;IAO1B,OAAO,CAAC,oBAAoB;IAyCtB,cAAc,IAAK,OAAO,CAAC,IAAI,CAAC;IAyEtC;;OAEG;IACH,OAAO,CAAC,wBAAwB;CAWjC"}
|
package/dist/src/upnp-nat.js
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import { upnpNat } from '@achingbrain/nat-port-mapper';
|
|
2
|
-
import {
|
|
2
|
+
import { isIPv4, isIPv6 } from '@chainsafe/is-ip';
|
|
3
|
+
import { InvalidParametersError, serviceCapabilities, serviceDependencies, start, stop } from '@libp2p/interface';
|
|
4
|
+
import { debounce } from '@libp2p/utils/debounce';
|
|
3
5
|
import { isLoopback } from '@libp2p/utils/multiaddr/is-loopback';
|
|
6
|
+
import { isPrivate } from '@libp2p/utils/multiaddr/is-private';
|
|
4
7
|
import { isPrivateIp } from '@libp2p/utils/private-ip';
|
|
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';
|
|
8
13
|
const DEFAULT_TTL = 7200;
|
|
9
|
-
function highPort(min = 1024, max = 65535) {
|
|
10
|
-
return Math.floor(Math.random() * (max - min + 1) + min);
|
|
11
|
-
}
|
|
12
14
|
export class UPnPNAT {
|
|
13
15
|
client;
|
|
14
|
-
|
|
16
|
+
addressManager;
|
|
17
|
+
events;
|
|
15
18
|
externalAddress;
|
|
16
19
|
localAddress;
|
|
17
20
|
description;
|
|
@@ -20,106 +23,188 @@ export class UPnPNAT {
|
|
|
20
23
|
gateway;
|
|
21
24
|
started;
|
|
22
25
|
log;
|
|
26
|
+
gatewayDetectionTimeout;
|
|
27
|
+
mappedPorts;
|
|
28
|
+
onSelfPeerUpdate;
|
|
29
|
+
autoConfirmAddress;
|
|
23
30
|
constructor(components, init) {
|
|
24
|
-
this.components = components;
|
|
25
31
|
this.log = components.logger.forComponent('libp2p:upnp-nat');
|
|
32
|
+
this.addressManager = components.addressManager;
|
|
33
|
+
this.events = components.events;
|
|
26
34
|
this.started = false;
|
|
27
|
-
this.externalAddress = init.externalAddress;
|
|
28
35
|
this.localAddress = init.localAddress;
|
|
29
|
-
this.description = init.description ?? `${components.nodeInfo.name}@${components.nodeInfo.version} ${
|
|
36
|
+
this.description = init.description ?? `${components.nodeInfo.name}@${components.nodeInfo.version} ${components.peerId.toString()}`;
|
|
30
37
|
this.ttl = init.ttl ?? DEFAULT_TTL;
|
|
31
38
|
this.keepAlive = init.keepAlive ?? true;
|
|
32
39
|
this.gateway = init.gateway;
|
|
40
|
+
this.gatewayDetectionTimeout = init.gatewayDetectionTimeout ?? 10000;
|
|
41
|
+
this.autoConfirmAddress = init.autoConfirmAddress ?? false;
|
|
42
|
+
this.mappedPorts = new Map();
|
|
33
43
|
if (this.ttl < DEFAULT_TTL) {
|
|
34
44
|
throw new InvalidParametersError(`NatManager ttl should be at least ${DEFAULT_TTL} seconds`);
|
|
35
45
|
}
|
|
36
|
-
this.client = upnpNat({
|
|
46
|
+
this.client = init.client ?? upnpNat({
|
|
37
47
|
description: this.description,
|
|
38
48
|
ttl: this.ttl,
|
|
39
49
|
keepAlive: this.keepAlive,
|
|
40
50
|
gateway: this.gateway
|
|
41
51
|
});
|
|
52
|
+
this.onSelfPeerUpdate = debounce(this._onSelfPeerUpdate.bind(this), init.delay ?? 5000);
|
|
53
|
+
if (typeof init.externalAddress === 'string') {
|
|
54
|
+
this.externalAddress = staticExternalAddress(init.externalAddress);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
this.externalAddress = dynamicExternalAddress({
|
|
58
|
+
client: this.client,
|
|
59
|
+
addressManager: this.addressManager,
|
|
60
|
+
logger: components.logger
|
|
61
|
+
}, {
|
|
62
|
+
autoConfirmAddress: init.autoConfirmAddress,
|
|
63
|
+
interval: init.externalAddressCheckInterval,
|
|
64
|
+
timeout: init.externalAddressCheckTimeout
|
|
65
|
+
});
|
|
66
|
+
}
|
|
42
67
|
}
|
|
43
68
|
[Symbol.toStringTag] = '@libp2p/upnp-nat';
|
|
44
69
|
[serviceCapabilities] = [
|
|
45
70
|
'@libp2p/nat-traversal'
|
|
46
71
|
];
|
|
72
|
+
get [serviceDependencies]() {
|
|
73
|
+
if (!this.autoConfirmAddress) {
|
|
74
|
+
return [
|
|
75
|
+
'@libp2p/autonat'
|
|
76
|
+
];
|
|
77
|
+
}
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
47
80
|
isStarted() {
|
|
48
81
|
return this.started;
|
|
49
82
|
}
|
|
50
|
-
start() {
|
|
51
|
-
|
|
83
|
+
async start() {
|
|
84
|
+
if (this.started) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
this.started = true;
|
|
88
|
+
this.events.addEventListener('self:peer:update', this.onSelfPeerUpdate);
|
|
89
|
+
await start(this.externalAddress, this.onSelfPeerUpdate);
|
|
52
90
|
}
|
|
53
91
|
/**
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* Run after start to ensure the transport manager has all addresses configured.
|
|
92
|
+
* Stops the NAT manager
|
|
57
93
|
*/
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
94
|
+
async stop() {
|
|
95
|
+
try {
|
|
96
|
+
await this.client?.close();
|
|
61
97
|
}
|
|
62
|
-
|
|
63
|
-
// done async to not slow down startup
|
|
64
|
-
void this.mapIpAddresses().catch((err) => {
|
|
65
|
-
// hole punching errors are non-fatal
|
|
98
|
+
catch (err) {
|
|
66
99
|
this.log.error(err);
|
|
100
|
+
}
|
|
101
|
+
this.events.removeEventListener('self:peer:update', this.onSelfPeerUpdate);
|
|
102
|
+
await stop(this.externalAddress, this.onSelfPeerUpdate);
|
|
103
|
+
this.started = false;
|
|
104
|
+
}
|
|
105
|
+
_onSelfPeerUpdate() {
|
|
106
|
+
this.mapIpAddresses()
|
|
107
|
+
.catch(err => {
|
|
108
|
+
this.log.error('error mapping IP addresses - %e', err);
|
|
67
109
|
});
|
|
68
110
|
}
|
|
69
|
-
|
|
70
|
-
const
|
|
71
|
-
for (const
|
|
72
|
-
//
|
|
73
|
-
|
|
74
|
-
if (!addr.isThinWaistAddress() || transport !== 'tcp') {
|
|
75
|
-
// only bare tcp addresses
|
|
76
|
-
// eslint-disable-next-line no-continue
|
|
111
|
+
getUnmappedAddresses(multiaddrs, ipType) {
|
|
112
|
+
const output = [];
|
|
113
|
+
for (const ma of multiaddrs) {
|
|
114
|
+
// ignore public addresses
|
|
115
|
+
if (!isPrivate(ma)) {
|
|
77
116
|
continue;
|
|
78
117
|
}
|
|
79
|
-
|
|
80
|
-
|
|
118
|
+
// ignore loopback
|
|
119
|
+
if (isLoopback(ma)) {
|
|
81
120
|
continue;
|
|
82
121
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
122
|
+
// only IP based addresses
|
|
123
|
+
if (!(TCP.exactMatch(ma) ||
|
|
124
|
+
WebSockets.exactMatch(ma) ||
|
|
125
|
+
WebSocketsSecure.exactMatch(ma) ||
|
|
126
|
+
QUICV1.exactMatch(ma) ||
|
|
127
|
+
WebTransport.exactMatch(ma))) {
|
|
86
128
|
continue;
|
|
87
129
|
}
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
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`);
|
|
130
|
+
const { port, family } = ma.toOptions();
|
|
131
|
+
if (family !== ipType) {
|
|
132
|
+
continue;
|
|
92
133
|
}
|
|
93
|
-
if (
|
|
94
|
-
|
|
134
|
+
if (this.mappedPorts.has(port)) {
|
|
135
|
+
continue;
|
|
95
136
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
137
|
+
output.push(ma);
|
|
138
|
+
}
|
|
139
|
+
return output;
|
|
140
|
+
}
|
|
141
|
+
async mapIpAddresses() {
|
|
142
|
+
if (this.externalAddress == null) {
|
|
143
|
+
this.log('discovering public address');
|
|
144
|
+
}
|
|
145
|
+
const publicIp = await this.externalAddress.getPublicIp({
|
|
146
|
+
signal: AbortSignal.timeout(this.gatewayDetectionTimeout)
|
|
147
|
+
});
|
|
148
|
+
this.externalAddress ?? await raceSignal(this.client.externalIp(), AbortSignal.timeout(this.gatewayDetectionTimeout), {
|
|
149
|
+
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`
|
|
150
|
+
});
|
|
151
|
+
let ipType = 4;
|
|
152
|
+
if (isIPv4(publicIp)) {
|
|
153
|
+
ipType = 4;
|
|
154
|
+
}
|
|
155
|
+
else if (isIPv6(publicIp)) {
|
|
156
|
+
ipType = 6;
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
throw new InvalidIPAddressError(`Public address ${publicIp} was not an IPv4 address`);
|
|
160
|
+
}
|
|
161
|
+
// filter addresses to get private, non-relay, IP based addresses that we
|
|
162
|
+
// haven't mapped yet
|
|
163
|
+
const addresses = this.getUnmappedAddresses(this.addressManager.getAddresses(), ipType);
|
|
164
|
+
if (addresses.length === 0) {
|
|
165
|
+
this.log('no private, non-relay, unmapped, IP based addresses found');
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
this.log('%s public IP %s', this.externalAddress != null ? 'using configured' : 'discovered', publicIp);
|
|
169
|
+
this.assertNotBehindDoubleNAT(publicIp);
|
|
170
|
+
for (const addr of addresses) {
|
|
171
|
+
// try to open uPnP ports for each thin waist address
|
|
172
|
+
const { family, host, port, transport } = addr.toOptions();
|
|
173
|
+
if (family === 6) {
|
|
174
|
+
// only support IPv4 addresses
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
if (this.mappedPorts.has(port)) {
|
|
178
|
+
// already mapped this port
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
const protocol = transport.toUpperCase();
|
|
182
|
+
this.log(`creating mapping of ${host}:${port}`);
|
|
183
|
+
const mappedPort = await this.client.map(port, {
|
|
184
|
+
localAddress: host,
|
|
185
|
+
protocol: protocol === 'TCP' ? 'TCP' : 'UDP'
|
|
103
186
|
});
|
|
104
|
-
this.
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
187
|
+
this.mappedPorts.set(port, mappedPort);
|
|
188
|
+
const ma = multiaddr(addr.toString().replace(`/ip${family}/${host}/${transport}/${port}`, `/ip${family}/${publicIp}/${transport}/${mappedPort}`));
|
|
189
|
+
this.log(`created mapping of ${publicIp}:${mappedPort} to ${host}:${port} as %a`, ma);
|
|
190
|
+
if (this.autoConfirmAddress) {
|
|
191
|
+
this.addressManager.confirmObservedAddr(ma);
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
this.addressManager.addObservedAddr(ma);
|
|
195
|
+
}
|
|
109
196
|
}
|
|
110
197
|
}
|
|
111
198
|
/**
|
|
112
|
-
*
|
|
199
|
+
* Some ISPs have double-NATs, there's not much we can do with them
|
|
113
200
|
*/
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
try {
|
|
119
|
-
await this.client.close();
|
|
201
|
+
assertNotBehindDoubleNAT(publicIp) {
|
|
202
|
+
const isPrivate = isPrivateIp(publicIp);
|
|
203
|
+
if (isPrivate === true) {
|
|
204
|
+
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`);
|
|
120
205
|
}
|
|
121
|
-
|
|
122
|
-
|
|
206
|
+
if (isPrivate == null) {
|
|
207
|
+
throw new InvalidParametersError(`${publicIp} is not an IP address`);
|
|
123
208
|
}
|
|
124
209
|
}
|
|
125
210
|
}
|
package/dist/src/upnp-nat.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"upnp-nat.js","sourceRoot":"","sources":["../../src/upnp-nat.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,
|
|
1
|
+
{"version":3,"file":"upnp-nat.js","sourceRoot":"","sources":["../../src/upnp-nat.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@libp2p/upnp-nat",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.11",
|
|
4
4
|
"description": "UPnP NAT hole punching",
|
|
5
5
|
"license": "Apache-2.0 OR MIT",
|
|
6
6
|
"homepage": "https://github.com/libp2p/js-libp2p/tree/main/packages/upnp-nat#readme",
|
|
@@ -50,12 +50,15 @@
|
|
|
50
50
|
"test:electron-main": "aegir test -t electron-main"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@achingbrain/nat-port-mapper": "^
|
|
53
|
+
"@achingbrain/nat-port-mapper": "^2.0.1",
|
|
54
|
+
"@chainsafe/is-ip": "^2.0.2",
|
|
54
55
|
"@libp2p/interface": "^2.2.0",
|
|
55
|
-
"@libp2p/interface-internal": "^2.0
|
|
56
|
-
"@libp2p/utils": "^6.
|
|
56
|
+
"@libp2p/interface-internal": "^2.1.0",
|
|
57
|
+
"@libp2p/utils": "^6.2.0",
|
|
57
58
|
"@multiformats/multiaddr": "^12.2.3",
|
|
58
|
-
"
|
|
59
|
+
"@multiformats/multiaddr-matcher": "^1.4.0",
|
|
60
|
+
"p-defer": "^4.0.1",
|
|
61
|
+
"race-signal": "^1.1.0"
|
|
59
62
|
},
|
|
60
63
|
"devDependencies": {
|
|
61
64
|
"@libp2p/crypto": "^5.0.6",
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { NotStartedError, start, stop } from '@libp2p/interface'
|
|
2
|
+
import { repeatingTask } from '@libp2p/utils/repeating-task'
|
|
3
|
+
import { multiaddr } from '@multiformats/multiaddr'
|
|
4
|
+
import pDefer from 'p-defer'
|
|
5
|
+
import { raceSignal } from 'race-signal'
|
|
6
|
+
import type { NatAPI } from '@achingbrain/nat-port-mapper'
|
|
7
|
+
import type { AbortOptions, ComponentLogger, Logger, Startable } from '@libp2p/interface'
|
|
8
|
+
import type { AddressManager } from '@libp2p/interface-internal'
|
|
9
|
+
import type { RepeatingTask } from '@libp2p/utils/repeating-task'
|
|
10
|
+
import type { DeferredPromise } from 'p-defer'
|
|
11
|
+
|
|
12
|
+
export interface ExternalAddressCheckerComponents {
|
|
13
|
+
client: NatAPI
|
|
14
|
+
addressManager: AddressManager
|
|
15
|
+
logger: ComponentLogger
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ExternalAddressCheckerInit {
|
|
19
|
+
interval?: number
|
|
20
|
+
timeout?: number
|
|
21
|
+
autoConfirmAddress?: boolean
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ExternalAddress {
|
|
25
|
+
getPublicIp (options?: AbortOptions): Promise<string> | string
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Monitors the external network address and notifies when/if it changes
|
|
30
|
+
*/
|
|
31
|
+
class ExternalAddressChecker implements ExternalAddress, Startable {
|
|
32
|
+
private readonly log: Logger
|
|
33
|
+
private readonly client: NatAPI
|
|
34
|
+
private readonly addressManager: AddressManager
|
|
35
|
+
private started: boolean
|
|
36
|
+
private lastPublicIp?: string
|
|
37
|
+
private readonly lastPublicIpPromise: DeferredPromise<string>
|
|
38
|
+
private readonly check: RepeatingTask
|
|
39
|
+
private readonly autoConfirmAddress: boolean
|
|
40
|
+
|
|
41
|
+
constructor (components: ExternalAddressCheckerComponents, init: ExternalAddressCheckerInit = {}) {
|
|
42
|
+
this.log = components.logger.forComponent('libp2p:upnp-nat:external-address-check')
|
|
43
|
+
this.client = components.client
|
|
44
|
+
this.addressManager = components.addressManager
|
|
45
|
+
this.autoConfirmAddress = init.autoConfirmAddress ?? false
|
|
46
|
+
this.started = false
|
|
47
|
+
|
|
48
|
+
this.checkExternalAddress = this.checkExternalAddress.bind(this)
|
|
49
|
+
|
|
50
|
+
this.lastPublicIpPromise = pDefer()
|
|
51
|
+
|
|
52
|
+
this.check = repeatingTask(this.checkExternalAddress, init.interval ?? 30000, {
|
|
53
|
+
timeout: init.timeout ?? 10000,
|
|
54
|
+
runImmediately: true
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async start (): Promise<void> {
|
|
59
|
+
if (this.started) {
|
|
60
|
+
return
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
await start(this.check)
|
|
64
|
+
|
|
65
|
+
this.check.start()
|
|
66
|
+
this.started = true
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async stop (): Promise<void> {
|
|
70
|
+
await stop(this.check)
|
|
71
|
+
|
|
72
|
+
this.started = false
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Return the last public IP address we found, or wait for it to be found
|
|
77
|
+
*/
|
|
78
|
+
async getPublicIp (options?: AbortOptions): Promise<string> {
|
|
79
|
+
if (!this.started) {
|
|
80
|
+
throw new NotStartedError('Not started yet')
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return this.lastPublicIp ?? raceSignal(this.lastPublicIpPromise.promise, options?.signal, {
|
|
84
|
+
errorMessage: 'Requesting the public IP from the network gateway timed out - UPnP may not be enabled'
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private async checkExternalAddress (options?: AbortOptions): Promise<void> {
|
|
89
|
+
try {
|
|
90
|
+
const externalAddress = await this.client.externalIp(options)
|
|
91
|
+
|
|
92
|
+
// check if our public address has changed
|
|
93
|
+
if (this.lastPublicIp != null && externalAddress !== this.lastPublicIp) {
|
|
94
|
+
this.log('external address changed from %s to %s', this.lastPublicIp, externalAddress)
|
|
95
|
+
|
|
96
|
+
for (const ma of this.addressManager.getAddresses()) {
|
|
97
|
+
const addrString = ma.toString()
|
|
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
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
this.lastPublicIp = externalAddress
|
|
119
|
+
this.lastPublicIpPromise.resolve(externalAddress)
|
|
120
|
+
} catch (err: any) {
|
|
121
|
+
if (this.lastPublicIp != null) {
|
|
122
|
+
// ignore the error if we've previously run successfully
|
|
123
|
+
return
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
this.lastPublicIpPromise.reject(err)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function dynamicExternalAddress (components: ExternalAddressCheckerComponents, init: ExternalAddressCheckerInit = {}): ExternalAddress {
|
|
132
|
+
return new ExternalAddressChecker(components, init)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function staticExternalAddress (address: string): ExternalAddress {
|
|
136
|
+
return {
|
|
137
|
+
getPublicIp: () => address
|
|
138
|
+
}
|
|
139
|
+
}
|
package/src/errors.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -36,8 +36,8 @@
|
|
|
36
36
|
*/
|
|
37
37
|
|
|
38
38
|
import { UPnPNAT as UPnPNATClass, type NatAPI, type MapPortOptions } from './upnp-nat.js'
|
|
39
|
-
import type { ComponentLogger, NodeInfo, PeerId } from '@libp2p/interface'
|
|
40
|
-
import type { AddressManager
|
|
39
|
+
import type { ComponentLogger, Libp2pEvents, NodeInfo, PeerId, TypedEventTarget } from '@libp2p/interface'
|
|
40
|
+
import type { AddressManager } from '@libp2p/interface-internal'
|
|
41
41
|
|
|
42
42
|
export type { NatAPI, MapPortOptions }
|
|
43
43
|
|
|
@@ -50,10 +50,27 @@ export interface PMPOptions {
|
|
|
50
50
|
|
|
51
51
|
export interface UPnPNATInit {
|
|
52
52
|
/**
|
|
53
|
-
* Pass a
|
|
53
|
+
* Pass a string to hard code the external address, otherwise it will be
|
|
54
|
+
* auto-detected
|
|
54
55
|
*/
|
|
55
56
|
externalAddress?: string
|
|
56
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Check if the external address has changed this often in ms. Ignored if an
|
|
60
|
+
* external address is specified.
|
|
61
|
+
*
|
|
62
|
+
* @default 30000
|
|
63
|
+
*/
|
|
64
|
+
externalAddressCheckInterval?: number
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Do not take longer than this to check if the external address has changed
|
|
68
|
+
* in ms. Ignored if an external address is specified.
|
|
69
|
+
*
|
|
70
|
+
* @default 10000
|
|
71
|
+
*/
|
|
72
|
+
externalAddressCheckTimeout?: number
|
|
73
|
+
|
|
57
74
|
/**
|
|
58
75
|
* Pass a value to use instead of auto-detection
|
|
59
76
|
*/
|
|
@@ -78,14 +95,50 @@ export interface UPnPNATInit {
|
|
|
78
95
|
* Pass a value to use instead of auto-detection
|
|
79
96
|
*/
|
|
80
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
|
|
103
|
+
*
|
|
104
|
+
* @default 10000
|
|
105
|
+
*/
|
|
106
|
+
gatewayDetectionTimeout?: number
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Ports are mapped when the `self:peer:update` event fires, which happens
|
|
110
|
+
* when the node's addresses change. To avoid starting to map ports while
|
|
111
|
+
* multiple addresses are being added, the mapping function is debounced by
|
|
112
|
+
* this number of ms
|
|
113
|
+
*
|
|
114
|
+
* @default 5000
|
|
115
|
+
*/
|
|
116
|
+
delay?: number
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* A preconfigured instance of a NatAPI client can be passed as an option,
|
|
120
|
+
* otherwise one will be created
|
|
121
|
+
*/
|
|
122
|
+
client?: NatAPI
|
|
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
|
|
81
134
|
}
|
|
82
135
|
|
|
83
136
|
export interface UPnPNATComponents {
|
|
84
137
|
peerId: PeerId
|
|
85
138
|
nodeInfo: NodeInfo
|
|
86
139
|
logger: ComponentLogger
|
|
87
|
-
transportManager: TransportManager
|
|
88
140
|
addressManager: AddressManager
|
|
141
|
+
events: TypedEventTarget<Libp2pEvents>
|
|
89
142
|
}
|
|
90
143
|
|
|
91
144
|
export interface UPnPNAT {
|
package/src/upnp-nat.ts
CHANGED
|
@@ -1,25 +1,32 @@
|
|
|
1
|
-
import { upnpNat
|
|
2
|
-
import {
|
|
1
|
+
import { upnpNat } from '@achingbrain/nat-port-mapper'
|
|
2
|
+
import { isIPv4, isIPv6 } from '@chainsafe/is-ip'
|
|
3
|
+
import { InvalidParametersError, serviceCapabilities, serviceDependencies, start, stop } from '@libp2p/interface'
|
|
4
|
+
import { debounce } from '@libp2p/utils/debounce'
|
|
3
5
|
import { isLoopback } from '@libp2p/utils/multiaddr/is-loopback'
|
|
6
|
+
import { isPrivate } from '@libp2p/utils/multiaddr/is-private'
|
|
4
7
|
import { isPrivateIp } from '@libp2p/utils/private-ip'
|
|
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'
|
|
8
14
|
import type { UPnPNATComponents, UPnPNATInit, UPnPNAT as UPnPNATInterface } from './index.js'
|
|
9
|
-
import type {
|
|
15
|
+
import type { NatAPI, MapPortOptions } from '@achingbrain/nat-port-mapper'
|
|
16
|
+
import type { Libp2pEvents, Logger, Startable, TypedEventTarget } from '@libp2p/interface'
|
|
17
|
+
import type { AddressManager } from '@libp2p/interface-internal'
|
|
18
|
+
import type { DebouncedFunction } from '@libp2p/utils/debounce'
|
|
19
|
+
import type { Multiaddr } from '@multiformats/multiaddr'
|
|
10
20
|
|
|
11
21
|
const DEFAULT_TTL = 7200
|
|
12
22
|
|
|
13
23
|
export type { NatAPI, MapPortOptions }
|
|
14
24
|
|
|
15
|
-
function highPort (min = 1024, max = 65535): number {
|
|
16
|
-
return Math.floor(Math.random() * (max - min + 1) + min)
|
|
17
|
-
}
|
|
18
|
-
|
|
19
25
|
export class UPnPNAT implements Startable, UPnPNATInterface {
|
|
20
26
|
public client: NatAPI
|
|
21
|
-
private readonly
|
|
22
|
-
private readonly
|
|
27
|
+
private readonly addressManager: AddressManager
|
|
28
|
+
private readonly events: TypedEventTarget<Libp2pEvents>
|
|
29
|
+
private readonly externalAddress: ExternalAddress
|
|
23
30
|
private readonly localAddress?: string
|
|
24
31
|
private readonly description: string
|
|
25
32
|
private readonly ttl: number
|
|
@@ -27,29 +34,51 @@ export class UPnPNAT implements Startable, UPnPNATInterface {
|
|
|
27
34
|
private readonly gateway?: string
|
|
28
35
|
private started: boolean
|
|
29
36
|
private readonly log: Logger
|
|
37
|
+
private readonly gatewayDetectionTimeout: number
|
|
38
|
+
private readonly mappedPorts: Map<number, number>
|
|
39
|
+
private readonly onSelfPeerUpdate: DebouncedFunction
|
|
40
|
+
private readonly autoConfirmAddress: boolean
|
|
30
41
|
|
|
31
42
|
constructor (components: UPnPNATComponents, init: UPnPNATInit) {
|
|
32
|
-
this.components = components
|
|
33
|
-
|
|
34
43
|
this.log = components.logger.forComponent('libp2p:upnp-nat')
|
|
44
|
+
this.addressManager = components.addressManager
|
|
45
|
+
this.events = components.events
|
|
35
46
|
this.started = false
|
|
36
|
-
this.externalAddress = init.externalAddress
|
|
37
47
|
this.localAddress = init.localAddress
|
|
38
|
-
this.description = init.description ?? `${components.nodeInfo.name}@${components.nodeInfo.version} ${
|
|
48
|
+
this.description = init.description ?? `${components.nodeInfo.name}@${components.nodeInfo.version} ${components.peerId.toString()}`
|
|
39
49
|
this.ttl = init.ttl ?? DEFAULT_TTL
|
|
40
50
|
this.keepAlive = init.keepAlive ?? true
|
|
41
51
|
this.gateway = init.gateway
|
|
52
|
+
this.gatewayDetectionTimeout = init.gatewayDetectionTimeout ?? 10000
|
|
53
|
+
this.autoConfirmAddress = init.autoConfirmAddress ?? false
|
|
54
|
+
this.mappedPorts = new Map()
|
|
42
55
|
|
|
43
56
|
if (this.ttl < DEFAULT_TTL) {
|
|
44
57
|
throw new InvalidParametersError(`NatManager ttl should be at least ${DEFAULT_TTL} seconds`)
|
|
45
58
|
}
|
|
46
59
|
|
|
47
|
-
this.client = upnpNat({
|
|
60
|
+
this.client = init.client ?? upnpNat({
|
|
48
61
|
description: this.description,
|
|
49
62
|
ttl: this.ttl,
|
|
50
63
|
keepAlive: this.keepAlive,
|
|
51
64
|
gateway: this.gateway
|
|
52
65
|
})
|
|
66
|
+
|
|
67
|
+
this.onSelfPeerUpdate = debounce(this._onSelfPeerUpdate.bind(this), init.delay ?? 5000)
|
|
68
|
+
|
|
69
|
+
if (typeof init.externalAddress === 'string') {
|
|
70
|
+
this.externalAddress = staticExternalAddress(init.externalAddress)
|
|
71
|
+
} else {
|
|
72
|
+
this.externalAddress = dynamicExternalAddress({
|
|
73
|
+
client: this.client,
|
|
74
|
+
addressManager: this.addressManager,
|
|
75
|
+
logger: components.logger
|
|
76
|
+
}, {
|
|
77
|
+
autoConfirmAddress: init.autoConfirmAddress,
|
|
78
|
+
interval: init.externalAddressCheckInterval,
|
|
79
|
+
timeout: init.externalAddressCheckTimeout
|
|
80
|
+
})
|
|
81
|
+
}
|
|
53
82
|
}
|
|
54
83
|
|
|
55
84
|
readonly [Symbol.toStringTag] = '@libp2p/upnp-nat'
|
|
@@ -58,99 +87,178 @@ export class UPnPNAT implements Startable, UPnPNATInterface {
|
|
|
58
87
|
'@libp2p/nat-traversal'
|
|
59
88
|
]
|
|
60
89
|
|
|
90
|
+
get [serviceDependencies] (): string[] {
|
|
91
|
+
if (!this.autoConfirmAddress) {
|
|
92
|
+
return [
|
|
93
|
+
'@libp2p/autonat'
|
|
94
|
+
]
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return []
|
|
98
|
+
}
|
|
99
|
+
|
|
61
100
|
isStarted (): boolean {
|
|
62
101
|
return this.started
|
|
63
102
|
}
|
|
64
103
|
|
|
65
|
-
start (): void {
|
|
66
|
-
|
|
104
|
+
async start (): Promise<void> {
|
|
105
|
+
if (this.started) {
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
this.started = true
|
|
110
|
+
this.events.addEventListener('self:peer:update', this.onSelfPeerUpdate)
|
|
111
|
+
await start(this.externalAddress, this.onSelfPeerUpdate)
|
|
67
112
|
}
|
|
68
113
|
|
|
69
114
|
/**
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
* Run after start to ensure the transport manager has all addresses configured.
|
|
115
|
+
* Stops the NAT manager
|
|
73
116
|
*/
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
117
|
+
async stop (): Promise<void> {
|
|
118
|
+
try {
|
|
119
|
+
await this.client?.close()
|
|
120
|
+
} catch (err: any) {
|
|
121
|
+
this.log.error(err)
|
|
77
122
|
}
|
|
78
123
|
|
|
79
|
-
this.
|
|
124
|
+
this.events.removeEventListener('self:peer:update', this.onSelfPeerUpdate)
|
|
125
|
+
await stop(this.externalAddress, this.onSelfPeerUpdate)
|
|
126
|
+
this.started = false
|
|
127
|
+
}
|
|
80
128
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
129
|
+
_onSelfPeerUpdate (): void {
|
|
130
|
+
this.mapIpAddresses()
|
|
131
|
+
.catch(err => {
|
|
132
|
+
this.log.error('error mapping IP addresses - %e', err)
|
|
133
|
+
})
|
|
86
134
|
}
|
|
87
135
|
|
|
88
|
-
|
|
89
|
-
const
|
|
136
|
+
private getUnmappedAddresses (multiaddrs: Multiaddr[], ipType: 4 | 6): Multiaddr[] {
|
|
137
|
+
const output: Multiaddr[] = []
|
|
90
138
|
|
|
91
|
-
for (const
|
|
92
|
-
//
|
|
93
|
-
|
|
139
|
+
for (const ma of multiaddrs) {
|
|
140
|
+
// ignore public addresses
|
|
141
|
+
if (!isPrivate(ma)) {
|
|
142
|
+
continue
|
|
143
|
+
}
|
|
94
144
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
// eslint-disable-next-line no-continue
|
|
145
|
+
// ignore loopback
|
|
146
|
+
if (isLoopback(ma)) {
|
|
98
147
|
continue
|
|
99
148
|
}
|
|
100
149
|
|
|
101
|
-
|
|
102
|
-
|
|
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
|
+
)) {
|
|
103
158
|
continue
|
|
104
159
|
}
|
|
105
160
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
161
|
+
const { port, family } = ma.toOptions()
|
|
162
|
+
|
|
163
|
+
if (family !== ipType) {
|
|
109
164
|
continue
|
|
110
165
|
}
|
|
111
166
|
|
|
112
|
-
|
|
113
|
-
|
|
167
|
+
if (this.mappedPorts.has(port)) {
|
|
168
|
+
continue
|
|
169
|
+
}
|
|
114
170
|
|
|
115
|
-
|
|
116
|
-
|
|
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`
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
let ipType: 4 | 6 = 4
|
|
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
|
|
117
220
|
}
|
|
118
221
|
|
|
119
|
-
if (
|
|
120
|
-
|
|
222
|
+
if (this.mappedPorts.has(port)) {
|
|
223
|
+
// already mapped this port
|
|
224
|
+
continue
|
|
121
225
|
}
|
|
122
226
|
|
|
123
|
-
const
|
|
227
|
+
const protocol = transport.toUpperCase()
|
|
124
228
|
|
|
125
|
-
this.log(`
|
|
229
|
+
this.log(`creating mapping of ${host}:${port}`)
|
|
126
230
|
|
|
127
|
-
await this.client.map({
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
localAddress: this.localAddress,
|
|
131
|
-
protocol: transport.toUpperCase() === 'TCP' ? 'TCP' : 'UDP'
|
|
231
|
+
const mappedPort = await this.client.map(port, {
|
|
232
|
+
localAddress: host,
|
|
233
|
+
protocol: protocol === 'TCP' ? 'TCP' : 'UDP'
|
|
132
234
|
})
|
|
133
235
|
|
|
134
|
-
this.
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
}
|
|
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
|
+
}
|
|
139
247
|
}
|
|
140
248
|
}
|
|
141
249
|
|
|
142
250
|
/**
|
|
143
|
-
*
|
|
251
|
+
* Some ISPs have double-NATs, there's not much we can do with them
|
|
144
252
|
*/
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
253
|
+
private assertNotBehindDoubleNAT (publicIp: string): void {
|
|
254
|
+
const isPrivate = isPrivateIp(publicIp)
|
|
255
|
+
|
|
256
|
+
if (isPrivate === true) {
|
|
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`)
|
|
148
258
|
}
|
|
149
259
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
} catch (err: any) {
|
|
153
|
-
this.log.error(err)
|
|
260
|
+
if (isPrivate == null) {
|
|
261
|
+
throw new InvalidParametersError(`${publicIp} is not an IP address`)
|
|
154
262
|
}
|
|
155
263
|
}
|
|
156
264
|
}
|