Skip to content

Commit

Permalink
fix: use addresses with metadata to map ports (#2878)
Browse files Browse the repository at this point in the history
To map ports for later verification by autonat, use addresses with
metadata to get unverified addresses.
  • Loading branch information
achingbrain authored Dec 6, 2024
1 parent 1729fca commit d51c21f
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 36 deletions.
35 changes: 23 additions & 12 deletions packages/upnp-nat/src/upnp-port-mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ import { DoubleNATError } from './errors.js'
import type { ExternalAddress } from './check-external-address.js'
import type { Gateway } from '@achingbrain/nat-port-mapper'
import type { ComponentLogger, Logger } from '@libp2p/interface'
import type { AddressManager } from '@libp2p/interface-internal'
import type { AddressManager, NodeAddress } from '@libp2p/interface-internal'
import type { Multiaddr } from '@multiformats/multiaddr'

const MAX_DATE = 8_640_000_000_000_000

export interface UPnPPortMapperInit {
gateway: Gateway
externalAddressCheckInterval?: number
Expand Down Expand Up @@ -104,10 +106,15 @@ export class UPnPPortMapper {
/**
* Return any eligible multiaddrs that are not mapped on the detected gateway
*/
private getUnmappedAddresses (multiaddrs: Multiaddr[], publicAddresses: string[]): Multiaddr[] {
private getUnmappedAddresses (multiaddrs: NodeAddress[], publicAddresses: string[]): Multiaddr[] {
const output: Multiaddr[] = []

for (const ma of multiaddrs) {
for (const { multiaddr: ma, type } of multiaddrs) {
// only consider transport addresses, ignore mapped/observed addrs
if (type !== 'transport') {
continue
}

const stringTuples = ma.stringTuples()
const address = `${stringTuples[0][1]}`

Expand All @@ -132,13 +139,7 @@ export class UPnPPortMapper {
}

// only IP based addresses
if (!(
TCP.exactMatch(ma) ||
WebSockets.exactMatch(ma) ||
WebSocketsSecure.exactMatch(ma) ||
QUICV1.exactMatch(ma) ||
WebTransport.exactMatch(ma)
)) {
if (!this.isIPAddress(ma)) {
continue
}

Expand All @@ -160,7 +161,7 @@ export class UPnPPortMapper {

// filter addresses to get private, non-relay, IP based addresses that we
// haven't mapped yet
const addresses = this.getUnmappedAddresses(this.addressManager.getAddresses(), [externalHost])
const addresses = this.getUnmappedAddresses(this.addressManager.getAddressesWithMetadata(), [externalHost])

if (addresses.length === 0) {
this.log('no private, non-relay, unmapped, IP based addresses found')
Expand Down Expand Up @@ -203,7 +204,9 @@ export class UPnPPortMapper {
if (options?.autoConfirmAddress === true) {
const ma = multiaddr(`/ip${family}/${host}/${transport}/${port}`)
this.log('auto-confirming IP address %a', ma)
this.addressManager.confirmObservedAddr(ma)
this.addressManager.confirmObservedAddr(ma, {
ttl: MAX_DATE - Date.now()
})
}
} catch (err) {
this.log.error('failed to create mapping for %s:%d for protocol - %e', host, port, transport, err)
Expand All @@ -228,4 +231,12 @@ export class UPnPPortMapper {
throw new InvalidParametersError(`${publicIp} is not an IP address`)
}
}

private isIPAddress (ma: Multiaddr): boolean {
return TCP.exactMatch(ma) ||
WebSockets.exactMatch(ma) ||
WebSocketsSecure.exactMatch(ma) ||
QUICV1.exactMatch(ma) ||
WebTransport.exactMatch(ma)
}
}
88 changes: 64 additions & 24 deletions packages/upnp-nat/test/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,17 @@ describe('UPnP NAT (TCP)', () => {

gateway.externalIp.resolves(externalHost)

components.addressManager.getAddresses.returns([
multiaddr('/ip4/127.0.0.1/tcp/4002'),
multiaddr(`/ip4/${internalHost}/tcp/${internalPort}`)
])
components.addressManager.getAddressesWithMetadata.returns([{
multiaddr: multiaddr('/ip4/127.0.0.1/tcp/4002'),
verified: true,
type: 'transport',
expires: Date.now() + 10_000
}, {
multiaddr: multiaddr(`/ip4/${internalHost}/tcp/${internalPort}`),
verified: true,
type: 'transport',
expires: Date.now() + 10_000
}])

gateway.map.withArgs(internalPort, internalHost).resolves({
internalHost,
Expand Down Expand Up @@ -120,10 +127,17 @@ describe('UPnP NAT (TCP)', () => {

gateway.externalIp.resolves(externalHost)

components.addressManager.getAddresses.returns([
multiaddr('/ip4/127.0.0.1/tcp/4002'),
multiaddr(`/ip4/${internalHost}/tcp/${internalPort}`)
])
components.addressManager.getAddressesWithMetadata.returns([{
multiaddr: multiaddr('/ip4/127.0.0.1/tcp/4002'),
verified: true,
type: 'transport',
expires: Date.now() + 10_000
}, {
multiaddr: multiaddr(`/ip4/${internalHost}/tcp/${internalPort}`),
verified: true,
type: 'transport',
expires: Date.now() + 10_000
}])

gateway.map.withArgs(internalPort, internalHost).resolves({
internalHost,
Expand Down Expand Up @@ -153,10 +167,17 @@ describe('UPnP NAT (TCP)', () => {

gateway.externalIp.resolves('192.168.1.1')

components.addressManager.getAddresses.returns([
multiaddr('/ip4/127.0.0.1/tcp/4002'),
multiaddr('/ip4/192.168.1.12/tcp/4002')
])
components.addressManager.getAddressesWithMetadata.returns([{
multiaddr: multiaddr('/ip4/127.0.0.1/tcp/4002'),
verified: true,
type: 'transport',
expires: Date.now() + 10_000
}, {
multiaddr: multiaddr('/ip4/192.168.1.12/tcp/4002'),
verified: true,
type: 'transport',
expires: Date.now() + 10_000
}])

await start(natManager)
await natManager.mapIpAddresses()
Expand All @@ -177,6 +198,13 @@ describe('UPnP NAT (TCP)', () => {
multiaddr('/ip6/fe80::9400:67ff:fe19:2a0f/tcp/0')
])

components.addressManager.getAddressesWithMetadata.returns([{
multiaddr: multiaddr('/ip6/fe80::9400:67ff:fe19:2a0f/tcp/0'),
verified: true,
type: 'transport',
expires: Date.now() + 10_000
}])

await start(natManager)
await natManager.mapIpAddresses()

Expand All @@ -192,9 +220,12 @@ describe('UPnP NAT (TCP)', () => {

gateway.externalIp.resolves('82.3.1.5')

components.addressManager.getAddresses.returns([
multiaddr('/ip6/::1/tcp/0')
])
components.addressManager.getAddressesWithMetadata.returns([{
multiaddr: multiaddr('/ip6/::1/tcp/0'),
verified: true,
type: 'transport',
expires: Date.now() + 10_000
}])

await start(natManager)
await natManager.mapIpAddresses()
Expand All @@ -211,9 +242,12 @@ describe('UPnP NAT (TCP)', () => {

gateway.externalIp.resolves('82.3.1.5')

components.addressManager.getAddresses.returns([
multiaddr('/ip4/192.168.1.12/udp/4001')
])
components.addressManager.getAddressesWithMetadata.returns([{
multiaddr: multiaddr('/ip4/192.168.1.12/udp/4001'),
verified: true,
type: 'transport',
expires: Date.now() + 10_000
}])

await start(natManager)
await natManager.mapIpAddresses()
Expand All @@ -230,9 +264,12 @@ describe('UPnP NAT (TCP)', () => {

gateway.externalIp.resolves('82.3.1.5')

components.addressManager.getAddresses.returns([
multiaddr('/ip4/127.0.0.1/tcp/4001')
])
components.addressManager.getAddressesWithMetadata.returns([{
multiaddr: multiaddr('/ip4/127.0.0.1/tcp/4001'),
verified: true,
type: 'transport',
expires: Date.now() + 10_000
}])

await start(natManager)
await natManager.mapIpAddresses()
Expand All @@ -249,9 +286,12 @@ describe('UPnP NAT (TCP)', () => {

gateway.externalIp.resolves('82.3.1.5')

components.addressManager.getAddresses.returns([
multiaddr('/ip4/127.0.0.1/tcp/4001/sctp/0')
])
components.addressManager.getAddressesWithMetadata.returns([{
multiaddr: multiaddr('/ip4/127.0.0.1/tcp/4001/sctp/0'),
verified: true,
type: 'transport',
expires: Date.now() + 10_000
}])

await start(natManager)
await natManager.mapIpAddresses()
Expand Down

0 comments on commit d51c21f

Please sign in to comment.