diff --git a/api/basic/helper/multiaddr.ts b/api/basic/helper/multiaddr.ts index 5100304..20568d4 100644 --- a/api/basic/helper/multiaddr.ts +++ b/api/basic/helper/multiaddr.ts @@ -5,16 +5,43 @@ import { Multiaddr } from 'multiaddr' * Convert a comma-separated list of origins into an array of * multiaddr strings that we could possibly connect to. * - * Filters out: - * - malformed multiaddrs and ones with transports we don't recognise - * - private or reserved ip address that we wouldn't be able to connect to. - */ + * Picks the first 10 non-ip based multiaddr, + * or ip based multiaddrs with a public (non-bogon) IP address + * or a /p2p/:peerId addr from a bogon ip based multiaddr. + **/ export function findUsableMultiaddrs (input = ''): string[] { if (input === '' || input === null) return [] - return input - .split(',') - .filter(isMultiaddr) - .filter(hasPublicIpAddress) + const specificAddrs: Set = new Set() + const p2pAddrs: Set = new Set() + for (const str of input.split(',')) { + const input = str.trim() + const ma = asMultiaddr(input) + if (ma === undefined) continue + // where we've got a ma with an ip we can't connect to, + // try to extract the /p2p/:peerId addr where available + if (hasBogonIpAddress(input)) { + const addr = getP2pAddr(input) + if (addr !== undefined) { + p2pAddrs.add(addr) + } + } else { + // either an ip based multiaddr with public ip, or a non-ip based multiaddr + specificAddrs.add(input) + } + } + return Array + .from(specificAddrs) + .concat(Array.from(p2pAddrs)) + .slice(1, 10) // don't return an unbounded number of multiaddrs. +} + +export function asMultiaddr (input = ''): Multiaddr | undefined { + if (input === '' || input === null) return undefined + try { + return new Multiaddr(input) // eslint-disable-line no-new + } catch (e) { + return undefined + } } export function isMultiaddr (input = ''): boolean { @@ -37,3 +64,21 @@ export function hasPublicIpAddress (input = ''): boolean { // not a IP based multiaddr, so we allow it. return true } + +export function hasBogonIpAddress (input = ''): boolean { + if (input === '' || input === null) return false + if (input.startsWith('/ip6/') || input.startsWith('/ip4/')) { + const ip = input.split('/').at(2) + if (ip === undefined) return false + return bogon(ip) + } + return false +} + +export function getP2pAddr (input = ''): string | undefined { + if (input === '' || input === null) return undefined + const match = input.match(/\/p2p\/\w+$/) + if (match != null) { + return match.at(0) + } +} diff --git a/api/test/multiaddrs.test.js b/api/test/multiaddrs.test.js new file mode 100644 index 0000000..8795c3e --- /dev/null +++ b/api/test/multiaddrs.test.js @@ -0,0 +1,42 @@ +import test from 'ava' +import { findUsableMultiaddrs } from '../basic/helper/multiaddr.js' + +const fixture = [ + '/ip4/127.0.0.1/tcp/4001/p2p/12D3KooWADjHf2kyANQodg9z5sSdX4bGEMbWg7ojwu6SCyDAMtzN', + '/ip4/127.0.0.1/udp/4001/quic-v1/p2p/12D3KooWADjHf2kyANQodg9z5sSdX4bGEMbWg7ojwu6SCyDAMtzN', + '/ip4/127.0.0.1/udp/4001/quic-v1/webtransport/certhash/uEiAcyORkzbKPHpd2Rq8px1APBfdnTJ1jzH10u92mYJAOMA/certhash/uEiBFkYB7Q0cp49VFSMY9ae8ffHaRJf7N0WXCGBkGp4KCIQ/p2p/12D3KooWADjHf2kyANQodg9z5sSdX4bGEMbWg7ojwu6SCyDAMtzN', + '/ip4/127.0.0.1/udp/4001/quic/p2p/12D3KooWADjHf2kyANQodg9z5sSdX4bGEMbWg7ojwu6SCyDAMtzN', + '/ip4/192.168.1.113/tcp/4001/p2p/12D3KooWADjHf2kyANQodg9z5sSdX4bGEMbWg7ojwu6SCyDAMtzN', + '/ip4/192.168.1.113/udp/4001/quic-v1/p2p/12D3KooWADjHf2kyANQodg9z5sSdX4bGEMbWg7ojwu6SCyDAMtzN', + '/ip4/192.168.1.113/udp/4001/quic-v1/webtransport/certhash/uEiAcyORkzbKPHpd2Rq8px1APBfdnTJ1jzH10u92mYJAOMA/certhash/uEiBFkYB7Q0cp49VFSMY9ae8ffHaRJf7N0WXCGBkGp4KCIQ/p2p/12D3KooWADjHf2kyANQodg9z5sSdX4bGEMbWg7ojwu6SCyDAMtzN', + '/ip4/192.168.1.113/udp/4001/quic/p2p/12D3KooWADjHf2kyANQodg9z5sSdX4bGEMbWg7ojwu6SCyDAMtzN', + '/ip4/45.76.79.40/udp/4001/quic-v1/p2p/12D3KooWGFLFZ9uYAqD8WschDcPDT4PgmsGzgvwrTdDmV4LD5kSe/p2p-circuit/p2p/12D3KooWADjHf2kyANQodg9z5sSdX4bGEMbWg7ojwu6SCyDAMtzN', + '/ip4/45.76.79.40/udp/4001/quic/p2p/12D3KooWGFLFZ9uYAqD8WschDcPDT4PgmsGzgvwrTdDmV4LD5kSe/p2p-circuit/p2p/12D3KooWADjHf2kyANQodg9z5sSdX4bGEMbWg7ojwu6SCyDAMtzN', + '/ip4/5.161.44.89/tcp/4001/p2p/12D3KooWSvYbdaYZmZucbkEHKDDoHNqCtkEMWdSm1z4udww6fyUM/p2p-circuit/p2p/12D3KooWADjHf2kyANQodg9z5sSdX4bGEMbWg7ojwu6SCyDAMtzN', + '/ip4/5.161.44.89/udp/4001/quic/p2p/12D3KooWSvYbdaYZmZucbkEHKDDoHNqCtkEMWdSm1z4udww6fyUM/p2p-circuit/p2p/12D3KooWADjHf2kyANQodg9z5sSdX4bGEMbWg7ojwu6SCyDAMtzN', + '/ip6/2a01:4ff:f0:ab6::1/tcp/4001/p2p/12D3KooWSvYbdaYZmZucbkEHKDDoHNqCtkEMWdSm1z4udww6fyUM/p2p-circuit/p2p/12D3KooWADjHf2kyANQodg9z5sSdX4bGEMbWg7ojwu6SCyDAMtzN', + '/ip6/2a01:4ff:f0:ab6::1/udp/4001/quic/p2p/12D3KooWSvYbdaYZmZucbkEHKDDoHNqCtkEMWdSm1z4udww6fyUM/p2p-circuit/p2p/12D3KooWADjHf2kyANQodg9z5sSdX4bGEMbWg7ojwu6SCyDAMtzN', + '/ip6/::1/tcp/4001/p2p/12D3KooWADjHf2kyANQodg9z5sSdX4bGEMbWg7ojwu6SCyDAMtzN', + '/ip6/::1/udp/4001/quic-v1/p2p/12D3KooWADjHf2kyANQodg9z5sSdX4bGEMbWg7ojwu6SCyDAMtzN', + '/ip6/::1/udp/4001/quic-v1/webtransport/certhash/uEiAcyORkzbKPHpd2Rq8px1APBfdnTJ1jzH10u92mYJAOMA/certhash/uEiBFkYB7Q0cp49VFSMY9ae8ffHaRJf7N0WXCGBkGp4KCIQ/p2p/12D3KooWADjHf2kyANQodg9z5sSdX4bGEMbWg7ojwu6SCyDAMtzN', + '/ip6/::1/udp/4001/quic/p2p/12D3KooWADjHf2kyANQodg9z5sSdX4bGEMbWg7ojwu6SCyDAMtzN' +] + +const expected = [ + '/ip4/5.161.44.89/tcp/4001/p2p/12D3KooWSvYbdaYZmZucbkEHKDDoHNqCtkEMWdSm1z4udww6fyUM/p2p-circuit/p2p/12D3KooWADjHf2kyANQodg9z5sSdX4bGEMbWg7ojwu6SCyDAMtzN', + '/ip4/5.161.44.89/udp/4001/quic/p2p/12D3KooWSvYbdaYZmZucbkEHKDDoHNqCtkEMWdSm1z4udww6fyUM/p2p-circuit/p2p/12D3KooWADjHf2kyANQodg9z5sSdX4bGEMbWg7ojwu6SCyDAMtzN', + '/ip6/2a01:4ff:f0:ab6::1/tcp/4001/p2p/12D3KooWSvYbdaYZmZucbkEHKDDoHNqCtkEMWdSm1z4udww6fyUM/p2p-circuit/p2p/12D3KooWADjHf2kyANQodg9z5sSdX4bGEMbWg7ojwu6SCyDAMtzN', + '/ip6/2a01:4ff:f0:ab6::1/udp/4001/quic/p2p/12D3KooWSvYbdaYZmZucbkEHKDDoHNqCtkEMWdSm1z4udww6fyUM/p2p-circuit/p2p/12D3KooWADjHf2kyANQodg9z5sSdX4bGEMbWg7ojwu6SCyDAMtzN', + '/p2p/12D3KooWADjHf2kyANQodg9z5sSdX4bGEMbWg7ojwu6SCyDAMtzN' +] + +test('findUsableMultiaddrs', t => { + const input = fixture.join(',') + const res = findUsableMultiaddrs(input) + t.is(res.length, 5) + let i = 0 + for (const ma of expected) { + t.is(res[i], ma) + i++ + } +})