@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.
@@ -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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"}
@@ -1,4 +1,8 @@
1
1
  export declare class DoubleNATError extends Error {
2
2
  constructor(message?: string);
3
3
  }
4
+ export declare class InvalidIPAddressError extends Error {
5
+ static name: string;
6
+ name: string;
7
+ }
4
8
  //# sourceMappingURL=errors.d.ts.map
@@ -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"}
@@ -4,4 +4,8 @@ export class DoubleNATError extends Error {
4
4
  this.name = 'DoubleNATError';
5
5
  }
6
6
  }
7
+ export class InvalidIPAddressError extends Error {
8
+ static name = 'InvalidIPAddressError';
9
+ name = 'InvalidIPAddressError';
10
+ }
7
11
  //# sourceMappingURL=errors.js.map
@@ -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"}
@@ -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, TransportManager } from '@libp2p/interface-internal';
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 value to use instead of auto-detection
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;
@@ -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;AAC1E,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAA;AAElF,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;;OAEG;IACH,eAAe,CAAC,EAAE,MAAM,CAAA;IAExB;;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;CACjB;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,QAAQ,CAAA;IAClB,MAAM,EAAE,eAAe,CAAA;IACvB,gBAAgB,EAAE,gBAAgB,CAAA;IAClC,cAAc,EAAE,cAAc,CAAA;CAC/B;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"}
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"}
@@ -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;AAyDzF,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"}
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"}
@@ -1,12 +1,13 @@
1
- import { type NatAPI, type MapPortOptions } from '@achingbrain/nat-port-mapper';
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 components;
9
- private readonly externalAddress?;
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
- * Attempt to use uPnP to configure port mapping using the current gateway.
24
- *
25
- * Run after start to ensure the transport manager has all addresses configured.
29
+ * Stops the NAT manager
26
30
  */
27
- afterStart(): void;
31
+ stop(): Promise<void>;
32
+ _onSelfPeerUpdate(): void;
33
+ private getUnmappedAddresses;
28
34
  mapIpAddresses(): Promise<void>;
29
35
  /**
30
- * Stops the NAT manager
36
+ * Some ISPs have double-NATs, there's not much we can do with them
31
37
  */
32
- stop(): Promise<void>;
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":"AAAA,OAAO,EAAW,KAAK,MAAM,EAAE,KAAK,cAAc,EAAE,MAAM,8BAA8B,CAAA;AACxF,OAAO,EAA0B,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AAM/E,OAAO,KAAK,EAAE,iBAAiB,EAAE,WAAW,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAC7F,OAAO,KAAK,EAAU,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAI1D,YAAY,EAAE,MAAM,EAAE,cAAc,EAAE,CAAA;AAMtC,qBAAa,OAAQ,YAAW,SAAS,EAAE,gBAAgB;IAClD,MAAM,EAAE,MAAM,CAAA;IACrB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAmB;IAC9C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAQ;IACzC,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;gBAEf,UAAU,EAAE,iBAAiB,EAAE,IAAI,EAAE,WAAW;IAwB7D,QAAQ,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,sBAAqB;IAElD,QAAQ,CAAC,CAAC,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAEvC;IAED,SAAS,IAAK,OAAO;IAIrB,KAAK,IAAK,IAAI;IAId;;;;OAIG;IACH,UAAU,IAAK,IAAI;IAcb,cAAc,IAAK,OAAO,CAAC,IAAI,CAAC;IAsDtC;;OAEG;IACG,IAAI,IAAK,OAAO,CAAC,IAAI,CAAC;CAW7B"}
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"}
@@ -1,17 +1,20 @@
1
1
  import { upnpNat } from '@achingbrain/nat-port-mapper';
2
- import { InvalidParametersError, serviceCapabilities } from '@libp2p/interface';
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 { fromNodeAddress } from '@multiformats/multiaddr';
6
- import { isBrowser } from 'wherearewe';
7
- import { DoubleNATError } from './errors.js';
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
- components;
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} ${this.components.peerId.toString()}`;
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
- // #TODO: is there a way to remove this? Seems like a hack
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
- * Attempt to use uPnP to configure port mapping using the current gateway.
55
- *
56
- * Run after start to ensure the transport manager has all addresses configured.
92
+ * Stops the NAT manager
57
93
  */
58
- afterStart() {
59
- if (isBrowser || this.started) {
60
- return;
94
+ async stop() {
95
+ try {
96
+ await this.client?.close();
61
97
  }
62
- this.started = true;
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
- async mapIpAddresses() {
70
- const addrs = this.components.transportManager.getAddrs();
71
- for (const addr of addrs) {
72
- // try to open uPnP ports for each thin waist address
73
- const { family, host, port, transport } = addr.toOptions();
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
- if (isLoopback(addr)) {
80
- // eslint-disable-next-line no-continue
118
+ // ignore loopback
119
+ if (isLoopback(ma)) {
81
120
  continue;
82
121
  }
83
- if (family !== 4) {
84
- // ignore ipv6
85
- // eslint-disable-next-line no-continue
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 publicIp = this.externalAddress ?? await this.client.externalIp();
89
- const isPrivate = isPrivateIp(publicIp);
90
- if (isPrivate === true) {
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 (isPrivate == null) {
94
- throw new InvalidParametersError(`${publicIp} is not an IP address`);
134
+ if (this.mappedPorts.has(port)) {
135
+ continue;
95
136
  }
96
- const publicPort = highPort();
97
- this.log(`opening uPnP connection from ${publicIp}:${publicPort} to ${host}:${port}`);
98
- await this.client.map({
99
- publicPort,
100
- localPort: port,
101
- localAddress: this.localAddress,
102
- protocol: transport.toUpperCase() === 'TCP' ? 'TCP' : 'UDP'
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.components.addressManager.addObservedAddr(fromNodeAddress({
105
- family: 4,
106
- address: publicIp,
107
- port: publicPort
108
- }, transport));
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
- * Stops the NAT manager
199
+ * Some ISPs have double-NATs, there's not much we can do with them
113
200
  */
114
- async stop() {
115
- if (isBrowser || this.client == null) {
116
- return;
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
- catch (err) {
122
- this.log.error(err);
206
+ if (isPrivate == null) {
207
+ throw new InvalidParametersError(`${publicIp} is not an IP address`);
123
208
  }
124
209
  }
125
210
  }
@@ -1 +1 @@
1
- {"version":3,"file":"upnp-nat.js","sourceRoot":"","sources":["../../src/upnp-nat.ts"],"names":[],"mappings":"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"}
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.10",
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": "^1.0.13",
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.10",
56
- "@libp2p/utils": "^6.1.3",
56
+ "@libp2p/interface-internal": "^2.1.0",
57
+ "@libp2p/utils": "^6.2.0",
57
58
  "@multiformats/multiaddr": "^12.2.3",
58
- "wherearewe": "^2.0.1"
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
@@ -4,3 +4,8 @@ export class DoubleNATError extends Error {
4
4
  this.name = 'DoubleNATError'
5
5
  }
6
6
  }
7
+
8
+ export class InvalidIPAddressError extends Error {
9
+ static name = 'InvalidIPAddressError'
10
+ name = 'InvalidIPAddressError'
11
+ }
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, TransportManager } from '@libp2p/interface-internal'
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 value to use instead of auto-detection
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, type NatAPI, type MapPortOptions } from '@achingbrain/nat-port-mapper'
2
- import { InvalidParametersError, serviceCapabilities } from '@libp2p/interface'
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 { fromNodeAddress } from '@multiformats/multiaddr'
6
- import { isBrowser } from 'wherearewe'
7
- import { DoubleNATError } from './errors.js'
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 { Logger, Startable } from '@libp2p/interface'
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 components: UPnPNATComponents
22
- private readonly externalAddress?: string
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} ${this.components.peerId.toString()}`
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
- // #TODO: is there a way to remove this? Seems like a hack
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
- * Attempt to use uPnP to configure port mapping using the current gateway.
71
- *
72
- * Run after start to ensure the transport manager has all addresses configured.
115
+ * Stops the NAT manager
73
116
  */
74
- afterStart (): void {
75
- if (isBrowser || this.started) {
76
- return
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.started = true
124
+ this.events.removeEventListener('self:peer:update', this.onSelfPeerUpdate)
125
+ await stop(this.externalAddress, this.onSelfPeerUpdate)
126
+ this.started = false
127
+ }
80
128
 
81
- // done async to not slow down startup
82
- void this.mapIpAddresses().catch((err) => {
83
- // hole punching errors are non-fatal
84
- this.log.error(err)
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
- async mapIpAddresses (): Promise<void> {
89
- const addrs = this.components.transportManager.getAddrs()
136
+ private getUnmappedAddresses (multiaddrs: Multiaddr[], ipType: 4 | 6): Multiaddr[] {
137
+ const output: Multiaddr[] = []
90
138
 
91
- for (const addr of addrs) {
92
- // try to open uPnP ports for each thin waist address
93
- const { family, host, port, transport } = addr.toOptions()
139
+ for (const ma of multiaddrs) {
140
+ // ignore public addresses
141
+ if (!isPrivate(ma)) {
142
+ continue
143
+ }
94
144
 
95
- if (!addr.isThinWaistAddress() || transport !== 'tcp') {
96
- // only bare tcp addresses
97
- // eslint-disable-next-line no-continue
145
+ // ignore loopback
146
+ if (isLoopback(ma)) {
98
147
  continue
99
148
  }
100
149
 
101
- if (isLoopback(addr)) {
102
- // eslint-disable-next-line no-continue
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
- if (family !== 4) {
107
- // ignore ipv6
108
- // eslint-disable-next-line no-continue
161
+ const { port, family } = ma.toOptions()
162
+
163
+ if (family !== ipType) {
109
164
  continue
110
165
  }
111
166
 
112
- const publicIp = this.externalAddress ?? await this.client.externalIp()
113
- const isPrivate = isPrivateIp(publicIp)
167
+ if (this.mappedPorts.has(port)) {
168
+ continue
169
+ }
114
170
 
115
- if (isPrivate === true) {
116
- 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`)
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 (isPrivate == null) {
120
- throw new InvalidParametersError(`${publicIp} is not an IP address`)
222
+ if (this.mappedPorts.has(port)) {
223
+ // already mapped this port
224
+ continue
121
225
  }
122
226
 
123
- const publicPort = highPort()
227
+ const protocol = transport.toUpperCase()
124
228
 
125
- this.log(`opening uPnP connection from ${publicIp}:${publicPort} to ${host}:${port}`)
229
+ this.log(`creating mapping of ${host}:${port}`)
126
230
 
127
- await this.client.map({
128
- publicPort,
129
- localPort: port,
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.components.addressManager.addObservedAddr(fromNodeAddress({
135
- family: 4,
136
- address: publicIp,
137
- port: publicPort
138
- }, transport))
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
- * Stops the NAT manager
251
+ * Some ISPs have double-NATs, there's not much we can do with them
144
252
  */
145
- async stop (): Promise<void> {
146
- if (isBrowser || this.client == null) {
147
- return
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
- try {
151
- await this.client.close()
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
  }