Skip to content

Commit bcd3cb5

Browse files
feat: add multiaddr filter function (#305)
related to libp2p/js-libp2p#1510 --------- Co-authored-by: Alex Potsides <[email protected]>
1 parent b083ac9 commit bcd3cb5

File tree

6 files changed

+117
-2
lines changed

6 files changed

+117
-2
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@
166166
},
167167
"dependencies": {
168168
"@chainsafe/is-ip": "^2.0.1",
169+
"@chainsafe/netmask": "^2.0.0",
169170
"dns-over-http-resolver": "^2.1.0",
170171
"err-code": "^3.0.1",
171172
"multiformats": "^11.0.0",
@@ -175,8 +176,7 @@
175176
"devDependencies": {
176177
"@types/varint": "^6.0.0",
177178
"aegir": "^38.1.0",
178-
"sinon": "^15.0.0",
179-
"util": "^0.12.3"
179+
"sinon": "^15.0.0"
180180
},
181181
"browser": {
182182
"./dist/src/resolvers/dns.js": "./dist/src/resolvers/dns.browser.js"

src/convert.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ import varint from 'varint'
1515
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
1616
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
1717
import { concat as uint8ArrayConcat } from 'uint8arrays/concat'
18+
import type { Multiaddr } from './index.js'
19+
import { IpNet } from '@chainsafe/netmask'
20+
21+
const ip4Protocol = getProtocol('ip4')
22+
const ip6Protocol = getProtocol('ip6')
23+
const ipcidrProtocol = getProtocol('ipcidr')
1824

1925
/**
2026
* converts (serializes) addresses
@@ -107,6 +113,23 @@ export function convertToBytes (proto: string | number, str: string): Uint8Array
107113
}
108114
}
109115

116+
export function convertToIpNet (multiaddr: Multiaddr): IpNet {
117+
let mask: string | undefined
118+
let addr: string | undefined
119+
multiaddr.stringTuples().forEach(([code, value]) => {
120+
if (code === ip4Protocol.code || code === ip6Protocol.code) {
121+
addr = value
122+
}
123+
if (code === ipcidrProtocol.code) {
124+
mask = value
125+
}
126+
})
127+
if (mask == null || addr == null) {
128+
throw new Error('Invalid multiaddr')
129+
}
130+
return new IpNet(addr, mask)
131+
}
132+
110133
const decoders = Object.values(bases).map((c) => c.decoder)
111134
const anybaseDecoder = (function () {
112135
let acc = decoders[0].or(decoders[1])

src/filter/multiaddr-filter.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import type { IpNet } from '@chainsafe/netmask'
2+
import { convertToIpNet } from '../convert.js'
3+
import { multiaddr, Multiaddr, MultiaddrInput } from '../index.js'
4+
5+
/**
6+
* A utility class to determine if a Multiaddr contains another
7+
* multiaddr.
8+
*
9+
* This can be used with ipcidr ranges to determine if a given
10+
* multiaddr is in a ipcidr range.
11+
*
12+
* @example
13+
*
14+
* ```js
15+
* import { multiaddr, MultiaddrFilter } from '@multiformats/multiaddr'
16+
*
17+
* const range = multiaddr('/ip4/192.168.10.10/ipcidr/24')
18+
* const filter = new MultiaddrFilter(range)
19+
*
20+
* const input = multiaddr('/ip4/192.168.10.2/udp/60')
21+
* console.info(filter.contains(input)) // true
22+
* ```
23+
*/
24+
export class MultiaddrFilter {
25+
private readonly multiaddr: Multiaddr
26+
private readonly netmask: IpNet
27+
28+
public constructor (input: MultiaddrInput) {
29+
this.multiaddr = multiaddr(input)
30+
this.netmask = convertToIpNet(this.multiaddr)
31+
}
32+
33+
public contains (input: MultiaddrInput): boolean {
34+
if (input == null) return false
35+
const m = multiaddr(input)
36+
let ip
37+
for (const [code, value] of m.stringTuples()) {
38+
if (code === 4 || code === 41) {
39+
ip = value
40+
break
41+
}
42+
}
43+
if (ip === undefined) return false
44+
return this.netmask.contains(ip)
45+
}
46+
}

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ export interface AbortOptions {
9494
export const resolvers = new Map<string, Resolver>()
9595
const symbol = Symbol.for('@multiformats/js-multiaddr/multiaddr')
9696

97+
export { MultiaddrFilter } from './filter/multiaddr-filter.js'
98+
9799
export interface Multiaddr {
98100
bytes: Uint8Array
99101

test/convert.spec.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import * as convert from '../src/convert.js'
33
import { expect } from 'aegir/chai'
44
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
5+
import { multiaddr } from '../src/index.js'
56

67
describe('convert', () => {
78
it('handles ip4 buffers', () => {
@@ -100,4 +101,22 @@ describe('convert', () => {
100101
const bytesOut = convert.convertToBytes(466, outcome)
101102
expect(bytesOut.toString()).to.equal(bytes.toString())
102103
})
104+
105+
it('convertToIpNet ip4', function () {
106+
const ipnet = convert.convertToIpNet(multiaddr('/ip4/192.0.2.0/ipcidr/24'))
107+
expect(ipnet.toString()).equal('192.0.2.0/24')
108+
})
109+
110+
it('convertToIpNet ip6', function () {
111+
const ipnet = convert.convertToIpNet(multiaddr('/ip6/2001:0db8:85a3:0000:0000:8a2e:0370:7334/ipcidr/64'))
112+
expect(ipnet.toString()).equal('2001:0db8:85a3:0000:0000:0000:0000:0000/64')
113+
})
114+
115+
it('convertToIpNet not ipcidr', function () {
116+
expect(() => convert.convertToIpNet(multiaddr('/ip6/2001:0db8:85a3:0000:0000:8a2e:0370:7334/tcp/64'))).to.throw()
117+
})
118+
119+
it('convertToIpNet not ipv6', function () {
120+
expect(() => convert.convertToIpNet(multiaddr('/dns6/foo.com/ipcidr/64'))).to.throw()
121+
})
103122
})

test/filter/multiaddr-filter.spec.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/* eslint-env mocha */
2+
import { expect } from 'aegir/chai'
3+
import { MultiaddrFilter, multiaddr, MultiaddrInput } from '../../src/index.js'
4+
5+
describe('MultiaddrFilter', () => {
6+
const cases: Array<[MultiaddrInput, MultiaddrInput, boolean]> = [
7+
['/ip4/192.168.10.10/ipcidr/24', '/ip4/192.168.10.2/tcp/60', true],
8+
[multiaddr('/ip4/192.168.10.10/ipcidr/24'), '/ip4/192.168.10.2/tcp/60', true],
9+
[multiaddr('/ip4/192.168.10.10/ipcidr/24').bytes, '/ip4/192.168.10.2/tcp/60', true],
10+
['/ip4/192.168.10.10/ipcidr/24', '/ip4/192.168.10.2/udp/60', true],
11+
['/ip4/192.168.10.10/ipcidr/24', multiaddr('/ip4/192.168.11.2/tcp/60'), false],
12+
['/ip4/192.168.10.10/ipcidr/24', null, false],
13+
['/ip4/192.168.10.10/ipcidr/24', multiaddr('/ip4/192.168.11.2/udp/60').bytes, false],
14+
['/ip4/192.168.10.10/ipcidr/24', '/ip4/192.168.11.2/udp/60', false],
15+
['/ip4/192.168.10.10/ipcidr/24', '/ip6/2001:db8:3333:4444:5555:6666:7777:8888/tcp/60', false],
16+
['/ip6/2001:db8:3333:4444:5555:6666:7777:8888/ipcidr/60', '/ip6/2001:0db8:3333:4440:0000:0000:0000:0000/tcp/60', true],
17+
['/ip6/2001:db8:3333:4444:5555:6666:7777:8888/ipcidr/60', '/ip6/2001:0db8:3333:4450:0000:0000:0000:0000/tcp/60', false]
18+
]
19+
20+
cases.forEach(([cidr, ip, result]) => {
21+
it(`multiaddr filter cidr=${cidr} ip=${ip} result=${String(result)}`, function () {
22+
expect(new MultiaddrFilter(cidr).contains(ip)).to.be.equal(result)
23+
})
24+
})
25+
})

0 commit comments

Comments
 (0)