Skip to content

Commit

Permalink
feat: add multiaddr filter function (#305)
Browse files Browse the repository at this point in the history
related to libp2p/js-libp2p#1510

---------

Co-authored-by: Alex Potsides <alex@achingbrain.net>
  • Loading branch information
mpetrunic and achingbrain authored Mar 21, 2023
1 parent b083ac9 commit bcd3cb5
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 2 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@
},
"dependencies": {
"@chainsafe/is-ip": "^2.0.1",
"@chainsafe/netmask": "^2.0.0",
"dns-over-http-resolver": "^2.1.0",
"err-code": "^3.0.1",
"multiformats": "^11.0.0",
Expand All @@ -175,8 +176,7 @@
"devDependencies": {
"@types/varint": "^6.0.0",
"aegir": "^38.1.0",
"sinon": "^15.0.0",
"util": "^0.12.3"
"sinon": "^15.0.0"
},
"browser": {
"./dist/src/resolvers/dns.js": "./dist/src/resolvers/dns.browser.js"
Expand Down
23 changes: 23 additions & 0 deletions src/convert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ import varint from 'varint'
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
import { concat as uint8ArrayConcat } from 'uint8arrays/concat'
import type { Multiaddr } from './index.js'
import { IpNet } from '@chainsafe/netmask'

const ip4Protocol = getProtocol('ip4')
const ip6Protocol = getProtocol('ip6')
const ipcidrProtocol = getProtocol('ipcidr')

/**
* converts (serializes) addresses
Expand Down Expand Up @@ -107,6 +113,23 @@ export function convertToBytes (proto: string | number, str: string): Uint8Array
}
}

export function convertToIpNet (multiaddr: Multiaddr): IpNet {
let mask: string | undefined
let addr: string | undefined
multiaddr.stringTuples().forEach(([code, value]) => {
if (code === ip4Protocol.code || code === ip6Protocol.code) {
addr = value
}
if (code === ipcidrProtocol.code) {
mask = value
}
})
if (mask == null || addr == null) {
throw new Error('Invalid multiaddr')
}
return new IpNet(addr, mask)
}

const decoders = Object.values(bases).map((c) => c.decoder)
const anybaseDecoder = (function () {
let acc = decoders[0].or(decoders[1])
Expand Down
46 changes: 46 additions & 0 deletions src/filter/multiaddr-filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import type { IpNet } from '@chainsafe/netmask'
import { convertToIpNet } from '../convert.js'
import { multiaddr, Multiaddr, MultiaddrInput } from '../index.js'

/**
* A utility class to determine if a Multiaddr contains another
* multiaddr.
*
* This can be used with ipcidr ranges to determine if a given
* multiaddr is in a ipcidr range.
*
* @example
*
* ```js
* import { multiaddr, MultiaddrFilter } from '@multiformats/multiaddr'
*
* const range = multiaddr('/ip4/192.168.10.10/ipcidr/24')
* const filter = new MultiaddrFilter(range)
*
* const input = multiaddr('/ip4/192.168.10.2/udp/60')
* console.info(filter.contains(input)) // true
* ```
*/
export class MultiaddrFilter {
private readonly multiaddr: Multiaddr
private readonly netmask: IpNet

public constructor (input: MultiaddrInput) {
this.multiaddr = multiaddr(input)
this.netmask = convertToIpNet(this.multiaddr)
}

public contains (input: MultiaddrInput): boolean {
if (input == null) return false
const m = multiaddr(input)
let ip
for (const [code, value] of m.stringTuples()) {
if (code === 4 || code === 41) {
ip = value
break
}
}
if (ip === undefined) return false
return this.netmask.contains(ip)
}
}
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ export interface AbortOptions {
export const resolvers = new Map<string, Resolver>()
const symbol = Symbol.for('@multiformats/js-multiaddr/multiaddr')

export { MultiaddrFilter } from './filter/multiaddr-filter.js'

export interface Multiaddr {
bytes: Uint8Array

Expand Down
19 changes: 19 additions & 0 deletions test/convert.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import * as convert from '../src/convert.js'
import { expect } from 'aegir/chai'
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
import { multiaddr } from '../src/index.js'

describe('convert', () => {
it('handles ip4 buffers', () => {
Expand Down Expand Up @@ -100,4 +101,22 @@ describe('convert', () => {
const bytesOut = convert.convertToBytes(466, outcome)
expect(bytesOut.toString()).to.equal(bytes.toString())
})

it('convertToIpNet ip4', function () {
const ipnet = convert.convertToIpNet(multiaddr('/ip4/192.0.2.0/ipcidr/24'))
expect(ipnet.toString()).equal('192.0.2.0/24')
})

it('convertToIpNet ip6', function () {
const ipnet = convert.convertToIpNet(multiaddr('/ip6/2001:0db8:85a3:0000:0000:8a2e:0370:7334/ipcidr/64'))
expect(ipnet.toString()).equal('2001:0db8:85a3:0000:0000:0000:0000:0000/64')
})

it('convertToIpNet not ipcidr', function () {
expect(() => convert.convertToIpNet(multiaddr('/ip6/2001:0db8:85a3:0000:0000:8a2e:0370:7334/tcp/64'))).to.throw()
})

it('convertToIpNet not ipv6', function () {
expect(() => convert.convertToIpNet(multiaddr('/dns6/foo.com/ipcidr/64'))).to.throw()
})
})
25 changes: 25 additions & 0 deletions test/filter/multiaddr-filter.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/* eslint-env mocha */
import { expect } from 'aegir/chai'
import { MultiaddrFilter, multiaddr, MultiaddrInput } from '../../src/index.js'

describe('MultiaddrFilter', () => {
const cases: Array<[MultiaddrInput, MultiaddrInput, boolean]> = [
['/ip4/192.168.10.10/ipcidr/24', '/ip4/192.168.10.2/tcp/60', true],
[multiaddr('/ip4/192.168.10.10/ipcidr/24'), '/ip4/192.168.10.2/tcp/60', true],
[multiaddr('/ip4/192.168.10.10/ipcidr/24').bytes, '/ip4/192.168.10.2/tcp/60', true],
['/ip4/192.168.10.10/ipcidr/24', '/ip4/192.168.10.2/udp/60', true],
['/ip4/192.168.10.10/ipcidr/24', multiaddr('/ip4/192.168.11.2/tcp/60'), false],
['/ip4/192.168.10.10/ipcidr/24', null, false],
['/ip4/192.168.10.10/ipcidr/24', multiaddr('/ip4/192.168.11.2/udp/60').bytes, false],
['/ip4/192.168.10.10/ipcidr/24', '/ip4/192.168.11.2/udp/60', false],
['/ip4/192.168.10.10/ipcidr/24', '/ip6/2001:db8:3333:4444:5555:6666:7777:8888/tcp/60', false],
['/ip6/2001:db8:3333:4444:5555:6666:7777:8888/ipcidr/60', '/ip6/2001:0db8:3333:4440:0000:0000:0000:0000/tcp/60', true],
['/ip6/2001:db8:3333:4444:5555:6666:7777:8888/ipcidr/60', '/ip6/2001:0db8:3333:4450:0000:0000:0000:0000/tcp/60', false]
]

cases.forEach(([cidr, ip, result]) => {
it(`multiaddr filter cidr=${cidr} ip=${ip} result=${String(result)}`, function () {
expect(new MultiaddrFilter(cidr).contains(ip)).to.be.equal(result)
})
})
})

0 comments on commit bcd3cb5

Please sign in to comment.