Skip to content

Commit

Permalink
Merge branch 'main' into feat/websockets-auto-tls
Browse files Browse the repository at this point in the history
  • Loading branch information
achingbrain authored Nov 26, 2024
2 parents 6ae1f97 + b02ea9b commit 53e18d7
Show file tree
Hide file tree
Showing 8 changed files with 408 additions and 286 deletions.
2 changes: 1 addition & 1 deletion packages/upnp-nat/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
"test:electron-main": "aegir test -t electron-main"
},
"dependencies": {
"@achingbrain/nat-port-mapper": "^2.0.1",
"@achingbrain/nat-port-mapper": "^3.0.1",
"@chainsafe/is-ip": "^2.0.2",
"@libp2p/interface": "^2.2.1",
"@libp2p/interface-internal": "^2.1.1",
Expand Down
15 changes: 7 additions & 8 deletions packages/upnp-nat/src/check-external-address.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ import { NotStartedError, start, stop } from '@libp2p/interface'
import { repeatingTask } from '@libp2p/utils/repeating-task'
import pDefer from 'p-defer'
import { raceSignal } from 'race-signal'
import type { NatAPI } from '@achingbrain/nat-port-mapper'
import type { Gateway } from '@achingbrain/nat-port-mapper'
import type { AbortOptions, ComponentLogger, Logger, Startable } from '@libp2p/interface'
import type { AddressManager } from '@libp2p/interface-internal'
import type { RepeatingTask } from '@libp2p/utils/repeating-task'
import type { DeferredPromise } from 'p-defer'

export interface ExternalAddressCheckerComponents {
client: NatAPI
gateway: Gateway
addressManager: AddressManager
logger: ComponentLogger
}
Expand All @@ -29,7 +29,7 @@ export interface ExternalAddress {
*/
class ExternalAddressChecker implements ExternalAddress, Startable {
private readonly log: Logger
private readonly client: NatAPI
private readonly gateway: Gateway
private readonly addressManager: AddressManager
private started: boolean
private lastPublicIp?: string
Expand All @@ -39,7 +39,7 @@ class ExternalAddressChecker implements ExternalAddress, Startable {

constructor (components: ExternalAddressCheckerComponents, init: ExternalAddressCheckerInit) {
this.log = components.logger.forComponent('libp2p:upnp-nat:external-address-check')
this.client = components.client
this.gateway = components.gateway
this.addressManager = components.addressManager
this.onExternalAddressChange = init.onExternalAddressChange
this.started = false
Expand All @@ -60,14 +60,11 @@ class ExternalAddressChecker implements ExternalAddress, Startable {
}

await start(this.check)

this.check.start()
this.started = true
}

async stop (): Promise<void> {
await stop(this.check)

this.started = false
}

Expand All @@ -86,7 +83,7 @@ class ExternalAddressChecker implements ExternalAddress, Startable {

private async checkExternalAddress (options?: AbortOptions): Promise<void> {
try {
const externalAddress = await this.client.externalIp(options)
const externalAddress = await this.gateway.externalIp(options)

// check if our public address has changed
if (this.lastPublicIp != null && externalAddress !== this.lastPublicIp) {
Expand All @@ -99,6 +96,8 @@ class ExternalAddressChecker implements ExternalAddress, Startable {
this.lastPublicIp = externalAddress
this.lastPublicIpPromise.resolve(externalAddress)
} catch (err: any) {
this.log.error('could not resolve external address - %e', err)

if (this.lastPublicIp != null) {
// ignore the error if we've previously run successfully
return
Expand Down
3 changes: 3 additions & 0 deletions packages/upnp-nat/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const DEFAULT_PORT_MAPPING_TTL = 720_000
export const DEFAULT_GATEWAY_SEARCH_TIMEOUT = 60_000
export const DEFAULT_GATEWAY_SEARCH_INTERVAL = 300_000
70 changes: 70 additions & 0 deletions packages/upnp-nat/src/gateway-finder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { TypedEventEmitter, start, stop } from '@libp2p/interface'
import { repeatingTask } from '@libp2p/utils/repeating-task'
import { DEFAULT_GATEWAY_SEARCH_INTERVAL, DEFAULT_GATEWAY_SEARCH_TIMEOUT } from './constants.js'
import type { Gateway, UPnPNAT } from '@achingbrain/nat-port-mapper'
import type { ComponentLogger, Logger } from '@libp2p/interface'
import type { RepeatingTask } from '@libp2p/utils/repeating-task'

export interface GatewayFinderComponents {
logger: ComponentLogger
}

export interface GatewayFinderInit {
portMappingClient: UPnPNAT
}

export interface GatewayFinderEvents {
'gateway': CustomEvent<Gateway>
}

export class GatewayFinder extends TypedEventEmitter<GatewayFinderEvents> {
private readonly log: Logger
private readonly gateways: Gateway[]
private readonly findGateways: RepeatingTask
private readonly portMappingClient: UPnPNAT
private started: boolean

constructor (components: GatewayFinderComponents, init: GatewayFinderInit) {
super()

this.log = components.logger.forComponent('libp2p:upnp-nat')
this.portMappingClient = init.portMappingClient
this.started = false
this.gateways = []

// every five minutes, search for network gateways for one minute
this.findGateways = repeatingTask(async (options) => {
for await (const gateway of this.portMappingClient.findGateways(options)) {
if (this.gateways.some(g => g.id === gateway.id)) {
// already seen this gateway
continue
}

this.gateways.push(gateway)
this.safeDispatchEvent('gateway', {
detail: gateway
})
}
}, DEFAULT_GATEWAY_SEARCH_INTERVAL, {
runImmediately: true,
timeout: DEFAULT_GATEWAY_SEARCH_TIMEOUT
})
}

async start (): Promise<void> {
if (this.started) {
return
}

this.started = true
await start(this.findGateways)
}

/**
* Stops the NAT manager
*/
async stop (): Promise<void> {
await stop(this.findGateways)
this.started = false
}
}
48 changes: 14 additions & 34 deletions packages/upnp-nat/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,12 @@
* ```
*/

import { UPnPNAT as UPnPNATClass, type NatAPI, type MapPortOptions } from './upnp-nat.js'
import { UPnPNAT as UPnPNATClass } from './upnp-nat.js'
import type { UPnPNAT as UPnPNATClient, MapPortOptions } from '@achingbrain/nat-port-mapper'
import type { ComponentLogger, Libp2pEvents, NodeInfo, PeerId, TypedEventTarget } from '@libp2p/interface'
import type { AddressManager } from '@libp2p/interface-internal'

export type { NatAPI, MapPortOptions }
export type { UPnPNATClient, MapPortOptions }

export interface PMPOptions {
/**
Expand All @@ -49,12 +50,6 @@ export interface PMPOptions {
}

export interface UPnPNATInit {
/**
* Pass a string to hard code the external address, otherwise it will be
* auto-detected
*/
externalAddress?: string

/**
* Check if the external address has changed this often in ms. Ignored if an
* external address is specified.
Expand All @@ -71,46 +66,31 @@ export interface UPnPNATInit {
*/
externalAddressCheckTimeout?: number

/**
* Pass a value to use instead of auto-detection
*/
localAddress?: string

/**
* A string value to use for the port mapping description on the gateway
*/
description?: string
portMappingDescription?: string

/**
* How long UPnP port mappings should last for in seconds (minimum 1200)
*/
ttl?: number

/**
* Whether to automatically refresh UPnP port mappings when their TTL is reached
*/
keepAlive?: boolean

/**
* Pass a value to use instead of auto-detection
* How long UPnP port mappings should last for in ms
*
* @default 720_000
*/
gateway?: string
portMappingTTL?: number

/**
* Ports are mapped when the `self:peer:update` event fires, which happens
* when the node's addresses change. To avoid starting to map ports while
* multiple addresses are being added, the mapping function is debounced by
* this number of ms
* Whether to automatically refresh UPnP port mappings when their TTL is
* reached
*
* @default 5000
* @default true
*/
delay?: number
portMappingAutoRefresh?: boolean

/**
* A preconfigured instance of a NatAPI client can be passed as an option,
* otherwise one will be created
*/
client?: NatAPI
portMappingClient?: UPnPNATClient
}

export interface UPnPNATComponents {
Expand All @@ -122,7 +102,7 @@ export interface UPnPNATComponents {
}

export interface UPnPNAT {
client: NatAPI
portMappingClient: UPnPNATClient
}

export function uPnPNAT (init: UPnPNATInit = {}): (components: UPnPNATComponents) => UPnPNAT {
Expand Down
Loading

0 comments on commit 53e18d7

Please sign in to comment.