@libp2p/upnp-nat 2.0.12-a82b07d8c → 2.0.12-b02ea9b6e

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,38 @@
1
+ import type { Gateway } from '@achingbrain/nat-port-mapper';
2
+ import type { ComponentLogger } from '@libp2p/interface';
3
+ import type { AddressManager } from '@libp2p/interface-internal';
4
+ export interface UPnPPortMapperInit {
5
+ gateway: Gateway;
6
+ externalAddressCheckInterval?: number;
7
+ externalAddressCheckTimeout?: number;
8
+ }
9
+ export interface UPnPPortMapperComponents {
10
+ logger: ComponentLogger;
11
+ addressManager: AddressManager;
12
+ }
13
+ export declare class UPnPPortMapper {
14
+ private readonly gateway;
15
+ private readonly externalAddress;
16
+ private readonly addressManager;
17
+ private readonly log;
18
+ private readonly mappedPorts;
19
+ private started;
20
+ constructor(components: UPnPPortMapperComponents, init: UPnPPortMapperInit);
21
+ start(): Promise<void>;
22
+ stop(): Promise<void>;
23
+ /**
24
+ * Update the local address mappings when the gateway's external interface
25
+ * address changes
26
+ */
27
+ private remapPorts;
28
+ /**
29
+ * Return any eligible multiaddrs that are not mapped on the detected gateway
30
+ */
31
+ private getUnmappedAddresses;
32
+ mapIpAddresses(): Promise<void>;
33
+ /**
34
+ * Some ISPs have double-NATs, there's not much we can do with them
35
+ */
36
+ private assertNotBehindDoubleNAT;
37
+ }
38
+ //# sourceMappingURL=upnp-port-mapper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upnp-port-mapper.d.ts","sourceRoot":"","sources":["../../src/upnp-port-mapper.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,8BAA8B,CAAA;AAC3D,OAAO,KAAK,EAAE,eAAe,EAAU,MAAM,mBAAmB,CAAA;AAChE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAA;AAGhE,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAA;IAChB,4BAA4B,CAAC,EAAE,MAAM,CAAA;IACrC,2BAA2B,CAAC,EAAE,MAAM,CAAA;CACrC;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,eAAe,CAAA;IACvB,cAAc,EAAE,cAAc,CAAA;CAC/B;AAOD,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAiB;IACjD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAgB;IAC/C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAQ;IAC5B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA0B;IACtD,OAAO,CAAC,OAAO,CAAS;gBAEX,UAAU,EAAE,wBAAwB,EAAE,IAAI,EAAE,kBAAkB;IAkBrE,KAAK,IAAK,OAAO,CAAC,IAAI,CAAC;IASvB,IAAI,IAAK,OAAO,CAAC,IAAI,CAAC;IAe5B;;;OAGG;IACH,OAAO,CAAC,UAAU;IAalB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAyCtB,cAAc,IAAK,OAAO,CAAC,IAAI,CAAC;IAmEtC;;OAEG;IACH,OAAO,CAAC,wBAAwB;CAWjC"}
@@ -0,0 +1,166 @@
1
+ import { isIPv4, isIPv6 } from '@chainsafe/is-ip';
2
+ import { InvalidParametersError, start, stop } from '@libp2p/interface';
3
+ import { isLoopback } from '@libp2p/utils/multiaddr/is-loopback';
4
+ import { isPrivate } from '@libp2p/utils/multiaddr/is-private';
5
+ import { isPrivateIp } from '@libp2p/utils/private-ip';
6
+ import { QUICV1, TCP, WebSockets, WebSocketsSecure, WebTransport } from '@multiformats/multiaddr-matcher';
7
+ import { dynamicExternalAddress } from './check-external-address.js';
8
+ import { DoubleNATError, InvalidIPAddressError } from './errors.js';
9
+ export class UPnPPortMapper {
10
+ gateway;
11
+ externalAddress;
12
+ addressManager;
13
+ log;
14
+ mappedPorts;
15
+ started;
16
+ constructor(components, init) {
17
+ this.log = components.logger.forComponent(`libp2p:upnp-nat:gateway:${init.gateway.id}`);
18
+ this.addressManager = components.addressManager;
19
+ this.gateway = init.gateway;
20
+ this.externalAddress = dynamicExternalAddress({
21
+ gateway: this.gateway,
22
+ addressManager: this.addressManager,
23
+ logger: components.logger
24
+ }, {
25
+ interval: init.externalAddressCheckInterval,
26
+ timeout: init.externalAddressCheckTimeout,
27
+ onExternalAddressChange: this.remapPorts.bind(this)
28
+ });
29
+ this.gateway = init.gateway;
30
+ this.mappedPorts = new Map();
31
+ this.started = false;
32
+ }
33
+ async start() {
34
+ if (this.started) {
35
+ return;
36
+ }
37
+ await start(this.externalAddress);
38
+ this.started = true;
39
+ }
40
+ async stop() {
41
+ try {
42
+ const shutdownTimeout = AbortSignal.timeout(1000);
43
+ await this.gateway.stop({
44
+ signal: shutdownTimeout
45
+ });
46
+ }
47
+ catch (err) {
48
+ this.log.error('error closing gateway - %e', err);
49
+ }
50
+ await stop(this.externalAddress);
51
+ this.started = false;
52
+ }
53
+ /**
54
+ * Update the local address mappings when the gateway's external interface
55
+ * address changes
56
+ */
57
+ remapPorts(newExternalHost) {
58
+ for (const [key, { externalHost, externalPort }] of this.mappedPorts.entries()) {
59
+ const [host, port, transport] = key.split('-');
60
+ this.addressManager.removePublicAddressMapping(host, parseInt(port), externalHost, externalPort, transport === 'tcp' ? 'tcp' : 'udp');
61
+ this.addressManager.addPublicAddressMapping(host, parseInt(port), newExternalHost, externalPort, transport === 'tcp' ? 'tcp' : 'udp');
62
+ }
63
+ }
64
+ /**
65
+ * Return any eligible multiaddrs that are not mapped on the detected gateway
66
+ */
67
+ getUnmappedAddresses(multiaddrs, ipType) {
68
+ const output = [];
69
+ for (const ma of multiaddrs) {
70
+ // ignore public addresses
71
+ if (!isPrivate(ma)) {
72
+ continue;
73
+ }
74
+ // ignore loopback
75
+ if (isLoopback(ma)) {
76
+ continue;
77
+ }
78
+ // only IP based addresses
79
+ if (!(TCP.exactMatch(ma) ||
80
+ WebSockets.exactMatch(ma) ||
81
+ WebSocketsSecure.exactMatch(ma) ||
82
+ QUICV1.exactMatch(ma) ||
83
+ WebTransport.exactMatch(ma))) {
84
+ continue;
85
+ }
86
+ const { port, host, family, transport } = ma.toOptions();
87
+ if (family !== ipType) {
88
+ continue;
89
+ }
90
+ if (this.mappedPorts.has(`${host}-${port}-${transport}`)) {
91
+ continue;
92
+ }
93
+ output.push(ma);
94
+ }
95
+ return output;
96
+ }
97
+ async mapIpAddresses() {
98
+ try {
99
+ const externalHost = await this.externalAddress.getPublicIp();
100
+ let ipType = 4;
101
+ if (isIPv4(externalHost)) {
102
+ ipType = 4;
103
+ }
104
+ else if (isIPv6(externalHost)) {
105
+ ipType = 6;
106
+ }
107
+ else {
108
+ throw new InvalidIPAddressError(`Public address ${externalHost} was not an IPv4 address`);
109
+ }
110
+ // filter addresses to get private, non-relay, IP based addresses that we
111
+ // haven't mapped yet
112
+ const addresses = this.getUnmappedAddresses(this.addressManager.getAddresses(), ipType);
113
+ if (addresses.length === 0) {
114
+ this.log('no private, non-relay, unmapped, IP based addresses found');
115
+ return;
116
+ }
117
+ this.log('%s public IP %s', this.externalAddress != null ? 'using configured' : 'discovered', externalHost);
118
+ this.assertNotBehindDoubleNAT(externalHost);
119
+ for (const addr of addresses) {
120
+ // try to open uPnP ports for each thin waist address
121
+ const { family, host, port, transport } = addr.toOptions();
122
+ if (family === 6) {
123
+ // only support IPv4 addresses
124
+ continue;
125
+ }
126
+ if (this.mappedPorts.has(`${host}-${port}-${transport}`)) {
127
+ // already mapped this port
128
+ continue;
129
+ }
130
+ try {
131
+ const key = `${host}-${port}-${transport}`;
132
+ this.log('creating mapping of key %s', key);
133
+ const externalPort = await this.gateway.map(port, {
134
+ localAddress: host,
135
+ protocol: transport === 'tcp' ? 'tcp' : 'udp'
136
+ });
137
+ this.mappedPorts.set(key, {
138
+ externalHost,
139
+ externalPort
140
+ });
141
+ this.log('created mapping of %s:%s to %s:%s', externalHost, externalPort, host, port);
142
+ this.addressManager.addPublicAddressMapping(host, port, externalHost, externalPort, transport === 'tcp' ? 'tcp' : 'udp');
143
+ }
144
+ catch (err) {
145
+ this.log.error('failed to create mapping of %s:%s - %e', host, port, err);
146
+ }
147
+ }
148
+ }
149
+ catch (err) {
150
+ this.log.error('error finding gateways - %e', err);
151
+ }
152
+ }
153
+ /**
154
+ * Some ISPs have double-NATs, there's not much we can do with them
155
+ */
156
+ assertNotBehindDoubleNAT(publicIp) {
157
+ const isPrivate = isPrivateIp(publicIp);
158
+ if (isPrivate === true) {
159
+ throw new DoubleNATError(`${publicIp} is private - please init uPnPNAT with 'externalAddress' set to an externally routable IP or ensure you are not behind a double NAT`);
160
+ }
161
+ if (isPrivate == null) {
162
+ throw new InvalidParametersError(`${publicIp} is not an IP address`);
163
+ }
164
+ }
165
+ }
166
+ //# sourceMappingURL=upnp-port-mapper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upnp-port-mapper.js","sourceRoot":"","sources":["../../src/upnp-port-mapper.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.12-a82b07d8c",
3
+ "version": "2.0.12-b02ea9b6e",
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,20 +50,20 @@
50
50
  "test:electron-main": "aegir test -t electron-main"
51
51
  },
52
52
  "dependencies": {
53
- "@achingbrain/nat-port-mapper": "^2.0.1",
53
+ "@achingbrain/nat-port-mapper": "^3.0.1",
54
54
  "@chainsafe/is-ip": "^2.0.2",
55
- "@libp2p/interface": "2.2.1-a82b07d8c",
56
- "@libp2p/interface-internal": "2.1.1-a82b07d8c",
57
- "@libp2p/utils": "6.2.1-a82b07d8c",
55
+ "@libp2p/interface": "2.2.1-b02ea9b6e",
56
+ "@libp2p/interface-internal": "2.1.1-b02ea9b6e",
57
+ "@libp2p/utils": "6.2.1-b02ea9b6e",
58
58
  "@multiformats/multiaddr": "^12.2.3",
59
59
  "@multiformats/multiaddr-matcher": "^1.4.0",
60
60
  "p-defer": "^4.0.1",
61
61
  "race-signal": "^1.1.0"
62
62
  },
63
63
  "devDependencies": {
64
- "@libp2p/crypto": "5.0.7-a82b07d8c",
65
- "@libp2p/logger": "5.1.4-a82b07d8c",
66
- "@libp2p/peer-id": "5.0.8-a82b07d8c",
64
+ "@libp2p/crypto": "5.0.7-b02ea9b6e",
65
+ "@libp2p/logger": "5.1.4-b02ea9b6e",
66
+ "@libp2p/peer-id": "5.0.8-b02ea9b6e",
67
67
  "aegir": "^44.0.1",
68
68
  "sinon-ts": "^2.0.0"
69
69
  },
@@ -2,14 +2,14 @@ import { NotStartedError, start, stop } from '@libp2p/interface'
2
2
  import { repeatingTask } from '@libp2p/utils/repeating-task'
3
3
  import pDefer from 'p-defer'
4
4
  import { raceSignal } from 'race-signal'
5
- import type { NatAPI } from '@achingbrain/nat-port-mapper'
5
+ import type { Gateway } from '@achingbrain/nat-port-mapper'
6
6
  import type { AbortOptions, ComponentLogger, Logger, Startable } from '@libp2p/interface'
7
7
  import type { AddressManager } from '@libp2p/interface-internal'
8
8
  import type { RepeatingTask } from '@libp2p/utils/repeating-task'
9
9
  import type { DeferredPromise } from 'p-defer'
10
10
 
11
11
  export interface ExternalAddressCheckerComponents {
12
- client: NatAPI
12
+ gateway: Gateway
13
13
  addressManager: AddressManager
14
14
  logger: ComponentLogger
15
15
  }
@@ -29,7 +29,7 @@ export interface ExternalAddress {
29
29
  */
30
30
  class ExternalAddressChecker implements ExternalAddress, Startable {
31
31
  private readonly log: Logger
32
- private readonly client: NatAPI
32
+ private readonly gateway: Gateway
33
33
  private readonly addressManager: AddressManager
34
34
  private started: boolean
35
35
  private lastPublicIp?: string
@@ -39,7 +39,7 @@ class ExternalAddressChecker implements ExternalAddress, Startable {
39
39
 
40
40
  constructor (components: ExternalAddressCheckerComponents, init: ExternalAddressCheckerInit) {
41
41
  this.log = components.logger.forComponent('libp2p:upnp-nat:external-address-check')
42
- this.client = components.client
42
+ this.gateway = components.gateway
43
43
  this.addressManager = components.addressManager
44
44
  this.onExternalAddressChange = init.onExternalAddressChange
45
45
  this.started = false
@@ -60,14 +60,11 @@ class ExternalAddressChecker implements ExternalAddress, Startable {
60
60
  }
61
61
 
62
62
  await start(this.check)
63
-
64
- this.check.start()
65
63
  this.started = true
66
64
  }
67
65
 
68
66
  async stop (): Promise<void> {
69
67
  await stop(this.check)
70
-
71
68
  this.started = false
72
69
  }
73
70
 
@@ -86,7 +83,7 @@ class ExternalAddressChecker implements ExternalAddress, Startable {
86
83
 
87
84
  private async checkExternalAddress (options?: AbortOptions): Promise<void> {
88
85
  try {
89
- const externalAddress = await this.client.externalIp(options)
86
+ const externalAddress = await this.gateway.externalIp(options)
90
87
 
91
88
  // check if our public address has changed
92
89
  if (this.lastPublicIp != null && externalAddress !== this.lastPublicIp) {
@@ -99,6 +96,8 @@ class ExternalAddressChecker implements ExternalAddress, Startable {
99
96
  this.lastPublicIp = externalAddress
100
97
  this.lastPublicIpPromise.resolve(externalAddress)
101
98
  } catch (err: any) {
99
+ this.log.error('could not resolve external address - %e', err)
100
+
102
101
  if (this.lastPublicIp != null) {
103
102
  // ignore the error if we've previously run successfully
104
103
  return
@@ -0,0 +1,3 @@
1
+ export const DEFAULT_PORT_MAPPING_TTL = 720_000
2
+ export const DEFAULT_GATEWAY_SEARCH_TIMEOUT = 60_000
3
+ export const DEFAULT_GATEWAY_SEARCH_INTERVAL = 300_000
@@ -0,0 +1,70 @@
1
+ import { TypedEventEmitter, start, stop } from '@libp2p/interface'
2
+ import { repeatingTask } from '@libp2p/utils/repeating-task'
3
+ import { DEFAULT_GATEWAY_SEARCH_INTERVAL, DEFAULT_GATEWAY_SEARCH_TIMEOUT } from './constants.js'
4
+ import type { Gateway, UPnPNAT } from '@achingbrain/nat-port-mapper'
5
+ import type { ComponentLogger, Logger } from '@libp2p/interface'
6
+ import type { RepeatingTask } from '@libp2p/utils/repeating-task'
7
+
8
+ export interface GatewayFinderComponents {
9
+ logger: ComponentLogger
10
+ }
11
+
12
+ export interface GatewayFinderInit {
13
+ portMappingClient: UPnPNAT
14
+ }
15
+
16
+ export interface GatewayFinderEvents {
17
+ 'gateway': CustomEvent<Gateway>
18
+ }
19
+
20
+ export class GatewayFinder extends TypedEventEmitter<GatewayFinderEvents> {
21
+ private readonly log: Logger
22
+ private readonly gateways: Gateway[]
23
+ private readonly findGateways: RepeatingTask
24
+ private readonly portMappingClient: UPnPNAT
25
+ private started: boolean
26
+
27
+ constructor (components: GatewayFinderComponents, init: GatewayFinderInit) {
28
+ super()
29
+
30
+ this.log = components.logger.forComponent('libp2p:upnp-nat')
31
+ this.portMappingClient = init.portMappingClient
32
+ this.started = false
33
+ this.gateways = []
34
+
35
+ // every five minutes, search for network gateways for one minute
36
+ this.findGateways = repeatingTask(async (options) => {
37
+ for await (const gateway of this.portMappingClient.findGateways(options)) {
38
+ if (this.gateways.some(g => g.id === gateway.id)) {
39
+ // already seen this gateway
40
+ continue
41
+ }
42
+
43
+ this.gateways.push(gateway)
44
+ this.safeDispatchEvent('gateway', {
45
+ detail: gateway
46
+ })
47
+ }
48
+ }, DEFAULT_GATEWAY_SEARCH_INTERVAL, {
49
+ runImmediately: true,
50
+ timeout: DEFAULT_GATEWAY_SEARCH_TIMEOUT
51
+ })
52
+ }
53
+
54
+ async start (): Promise<void> {
55
+ if (this.started) {
56
+ return
57
+ }
58
+
59
+ this.started = true
60
+ await start(this.findGateways)
61
+ }
62
+
63
+ /**
64
+ * Stops the NAT manager
65
+ */
66
+ async stop (): Promise<void> {
67
+ await stop(this.findGateways)
68
+ this.started = false
69
+ }
70
+ }
package/src/index.ts CHANGED
@@ -35,11 +35,12 @@
35
35
  * ```
36
36
  */
37
37
 
38
- import { UPnPNAT as UPnPNATClass, type NatAPI, type MapPortOptions } from './upnp-nat.js'
38
+ import { UPnPNAT as UPnPNATClass } from './upnp-nat.js'
39
+ import type { UPnPNAT as UPnPNATClient, MapPortOptions } from '@achingbrain/nat-port-mapper'
39
40
  import type { ComponentLogger, Libp2pEvents, NodeInfo, PeerId, TypedEventTarget } from '@libp2p/interface'
40
41
  import type { AddressManager } from '@libp2p/interface-internal'
41
42
 
42
- export type { NatAPI, MapPortOptions }
43
+ export type { UPnPNATClient, MapPortOptions }
43
44
 
44
45
  export interface PMPOptions {
45
46
  /**
@@ -49,12 +50,6 @@ export interface PMPOptions {
49
50
  }
50
51
 
51
52
  export interface UPnPNATInit {
52
- /**
53
- * Pass a string to hard code the external address, otherwise it will be
54
- * auto-detected
55
- */
56
- externalAddress?: string
57
-
58
53
  /**
59
54
  * Check if the external address has changed this often in ms. Ignored if an
60
55
  * external address is specified.
@@ -71,46 +66,31 @@ export interface UPnPNATInit {
71
66
  */
72
67
  externalAddressCheckTimeout?: number
73
68
 
74
- /**
75
- * Pass a value to use instead of auto-detection
76
- */
77
- localAddress?: string
78
-
79
69
  /**
80
70
  * A string value to use for the port mapping description on the gateway
81
71
  */
82
- description?: string
72
+ portMappingDescription?: string
83
73
 
84
74
  /**
85
- * How long UPnP port mappings should last for in seconds (minimum 1200)
86
- */
87
- ttl?: number
88
-
89
- /**
90
- * Whether to automatically refresh UPnP port mappings when their TTL is reached
91
- */
92
- keepAlive?: boolean
93
-
94
- /**
95
- * Pass a value to use instead of auto-detection
75
+ * How long UPnP port mappings should last for in ms
76
+ *
77
+ * @default 720_000
96
78
  */
97
- gateway?: string
79
+ portMappingTTL?: number
98
80
 
99
81
  /**
100
- * Ports are mapped when the `self:peer:update` event fires, which happens
101
- * when the node's addresses change. To avoid starting to map ports while
102
- * multiple addresses are being added, the mapping function is debounced by
103
- * this number of ms
82
+ * Whether to automatically refresh UPnP port mappings when their TTL is
83
+ * reached
104
84
  *
105
- * @default 5000
85
+ * @default true
106
86
  */
107
- delay?: number
87
+ portMappingAutoRefresh?: boolean
108
88
 
109
89
  /**
110
90
  * A preconfigured instance of a NatAPI client can be passed as an option,
111
91
  * otherwise one will be created
112
92
  */
113
- client?: NatAPI
93
+ portMappingClient?: UPnPNATClient
114
94
  }
115
95
 
116
96
  export interface UPnPNATComponents {
@@ -122,7 +102,7 @@ export interface UPnPNATComponents {
122
102
  }
123
103
 
124
104
  export interface UPnPNAT {
125
- client: NatAPI
105
+ portMappingClient: UPnPNATClient
126
106
  }
127
107
 
128
108
  export function uPnPNAT (init: UPnPNATInit = {}): (components: UPnPNATComponents) => UPnPNAT {