-
Notifications
You must be signed in to change notification settings - Fork 446
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Use CIDR format for connection-manager allow/deny lists #2783
base: main
Are you sure you want to change the base?
Conversation
…r handling for invalid multiaddrs
…Net with encapsulation
…g behavior for allowlist connections
Commit History and ChangesThis document summarizes the commits made to the repository, detailing the changes and the rationale behind each commit. 1. Commit: Add
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good, really close - comments inline.
const ma = '/ip4/127.0.0.1/ipcidr/32' | ||
const ipNet = multiaddrToIpNet(ma) | ||
expect(ipNet.toString()).to.equal('127.0.0.1/32') | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please can you add some tests for IPv6 addresses
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
But
it('should convert a simple IPv6 multiaddr to an IpNet', () => {
const ma = '/ip6/::1/ipcidr/128'
const ipNet = multiaddrToIpNet(ma)
expect(ipNet.toString()).to.equal('0000:0000:0000:0000:0000:0000:0000:0001/128')
})
Don't work it gives error so I removed it, but it should be a basic test it shouldn't fail
I'm not expert in ip6 but look at this examples: is there a bug in @multiformats/multiaddr ?
import { multiaddr} from '@multiformats/multiaddr'
function test (a) {
const ma = multiaddr(a)
console.log(a, ma.toString())
}
test('/ip6/0:0:0:0:0:0:0:1') // /ip6/::1
test('/ip6/::1') // /ip6/::1
test('/ip6/2001:db8:3333:4444:5555:6666:7777:8888/ipcidr/99') // /ip6/2001:db8:3333:4444:5555:6666:7777:8888/ipcidr/99
test('/ip6/2001:db8::/ipcidr/99') // /ip6/2001:db8::/ipcidr/99
test('/ip6/2001:db8::/ipcidr/100')// throw new SyntaxError('Unexpected end of data')
test('/ip6/::1/ipcidr/128') // throw new SyntaxError('Unexpected end of data')
test('/ip6/0000:0000:0000:0000:0000:0000:0000:0001/ipcidr/128') // throw new SyntaxError('Unexpected end of data')
test('/ip6/0:0:0:0:0:0:0:1/ipcidr/128') // throw new SyntaxError('Unexpected end of data')
node test.mjs
/ip6/0:0:0:0:0:0:0:1 /ip6/::1
/ip6/::1 /ip6/::1
/ip6/2001:db8:3333:4444:5555:6666:7777:8888/ipcidr/99 /ip6/2001:db8:3333:4444:5555:6666:7777:8888/ipcidr/99
/ip6/2001:db8::/ipcidr/99 /ip6/2001:db8::/ipcidr/99
file:///home/luca/Informatica/Learning/PNL_Launchpad_Curriculum/Libp2p/tmp/test-multiformats/node_modules/multiformats/dist/src/bases/base.js:156
throw new SyntaxError('Unexpected end of data');
^
SyntaxError: Unexpected end of data
at decode (file:///home/luca/Informatica/Learning/PNL_Launchpad_Curriculum/Libp2p/tmp/test-multiformats/node_modules/multiformats/dist/src/bases/base.js:156:15)
at Decoder.decode [as baseDecode] (file:///home/luca/Informatica/Learning/PNL_Launchpad_Curriculum/Libp2p/tmp/test-multiformats/node_modules/multiformats/dist/src/bases/base.js:199:20)
at Decoder.decode (file:///home/luca/Informatica/Learning/PNL_Launchpad_Curriculum/Libp2p/tmp/test-multiformats/node_modules/multiformats/dist/src/bases/base.js:52:25)
at fromString (file:///home/luca/Informatica/Learning/PNL_Launchpad_Curriculum/Libp2p/tmp/test-multiformats/node_modules/uint8arrays/dist/src/from-string.node.js:20:25)
at convertToBytes (file:///home/luca/Informatica/Learning/PNL_Launchpad_Curriculum/Libp2p/tmp/test-multiformats/node_modules/@multiformats/multiaddr/dist/src/convert.js:100:20)
at stringToMultiaddrParts (file:///home/luca/Informatica/Learning/PNL_Launchpad_Curriculum/Libp2p/tmp/test-multiformats/node_modules/@multiformats/multiaddr/dist/src/codec.js:44:23)
at new Multiaddr (file:///home/luca/Informatica/Learning/PNL_Launchpad_Curriculum/Libp2p/tmp/test-multiformats/node_modules/@multiformats/multiaddr/dist/src/multiaddr.js:58:21)
at multiaddr (file:///home/luca/Informatica/Learning/PNL_Launchpad_Curriculum/Libp2p/tmp/test-multiformats/node_modules/@multiformats/multiaddr/dist/src/index.js:195:12)
at test (file:///home/luca/Informatica/Learning/PNL_Launchpad_Curriculum/Libp2p/tmp/test-multiformats/test.mjs:4:14)
at file:///home/luca/Informatica/Learning/PNL_Launchpad_Curriculum/Libp2p/tmp/test-multiformats/test.mjs:12:1
Node.js v22.10.0
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In @multifomats/multiaddr/src/convert.ts
export function convertToBytes (proto: string | number, str: string): Uint8Array {
const protocol = getProtocol(proto)
switch (protocol.code) {
case 4: // ipv4
return ip2bytes(str)
case 41: // ipv6
return ip2bytes(str)
case 42: // ipv6zone
return str2bytes(str)
case 6: // tcp
case 273: // udp
case 33: // dccp
case 132: // sctp
return port2bytes(parseInt(str, 10))
case 53: // dns
case 54: // dns4
case 55: // dns6
case 56: // dnsaddr
case 400: // unix
case 449: // sni
case 777: // memory
return str2bytes(str)
case 421: // ipfs
return mh2bytes(str)
case 444: // onion
return onion2bytes(str)
case 445: // onion3
return onion32bytes(str)
case 466: // certhash
return mb2bytes(str)
case 481: // http-path
return str2bytes(globalThis.decodeURIComponent(str))
default:
return uint8ArrayFromString(str, 'base16') // no clue. convert from hex
}
}
There is no case 43 (ipcidr) so in the default case
The issue arises because the string 100 (or >99) is being interpreted as hexadecimal when passed to uint8ArrayFromString with the base16 encoding. Hexadecimal (base16) encoding expects strings consisting of characters from 0-9 and a-f. In base16, each character represents 4 bits, so the total number of characters must be even to decode into full bytes.
If I add
case 43: // ipcidr
console.log('convertToBytes case 43:', str)
str = str.length % 2 !== 0 ? '0' + str : str
console.log('convertToBytes uint8ArrayFromString', uint8ArrayFromString(str, 'base16'))
return uint8ArrayFromString(str, 'base16')
It pass but then in ipnet.ts
export class IpNet {
public readonly network: Uint8Array;
public readonly mask: Uint8Array;
/**
*
* @param ipOrCidr either network ip or full cidr address
* @param mask in case ipOrCidr is network this can be either mask in decimal format or as ip address
*/
constructor(ipOrCidr: string, mask?: string | number) {
if (mask == null) {
({ network: this.network, mask: this.mask } = parseCidr(ipOrCidr));
} else {
const ipResult = parseIP(ipOrCidr);
if (ipResult == null) {
throw new Error("Failed to parse network");
}
mask = String(mask);
const m = parseInt(mask, 10);
if (
Number.isNaN(m) ||
String(m).length !== mask.length ||
m < 0 ||
m > ipResult.length * 8
) {
const maskResult = parseIP(mask);
if (maskResult == null) {
throw new Error("Failed to parse mask");
}
this.mask = maskResult;
} else {
this.mask = cidrMask(m, 8 * ipResult.length);
}
this.network = maskIp(ipResult, this.mask);
}
}
IpNet('2001:db8:3333:4444:5555:6666:7777:8888','0128')
throw Error: Failed to parse mask
0128 failes because of the leading '0'
The mask Parameter and Issue
The constructor is designed to handle two types of input for mask:
Numeric CIDR masks, like 128 (as an integer or string without leading zeros).
IP masks, like 255.255.255.0 or ffff:ffff:ffff:ffff::.
In your case, the input '0128' is problematic because of the leading zero:
parseInt('0128', 10) correctly parses this as 128 (a valid CIDR mask).
However, the conditional
String(m).length !== mask.length
fails:
String(m).length evaluates to 3 ("128".length).
mask.length evaluates to 4 ("0128".length).
This mismatch triggers the constructor to treat '0128' as an IP mask instead of a CIDR mask.
SUGGESTIONS?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit confused by this thread, but this may help:
The CIDR mask is limited to 0-32 for ipv4 and 0-128 for ipv6. When you convert the string form of the CIDR mast to bytes, you only need a single byte to represent all options. So the string to byte logic should convert the given decimal string number to a byte. And vice versa for the bytes to string part. See the go-multiaddr implementation for an example:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@MarcoPolo
Make sense. But the existing code is expecting a uint8Array so I was trying to change the less code possibile. Maybe I have to go take a deeper look...
@@ -571,7 +583,7 @@ export class DefaultConnectionManager implements ConnectionManager, Startable { | |||
async acceptIncomingConnection (maConn: MultiaddrConnection): Promise<boolean> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please can you add some tests that show that incoming connections are denied for multiaddrs in the deny list and allowed for ones in the allow list.
They should show that individual addresses and ranges are handled correctly in both IPv4 and IPv6 formats.
It should be a case of adding more tests like this one and this one.
@SgtPooki @wemeetagain @dhuseby
Description
This PR updates the connection manager to treat multiaddrs in the allow/deny lists using the standard IP CIDR format (e.g.
/ip4/52.55.0.0/ipcidr/16
) rather than string prefixes (e.g./ip4/52.55
). This allows us to validate multiaddrs accurately and ensures better control over IP address matching.Changes:
.allow
and.deny
properties in the connection manager to useIpNet[]
for IP range validation.IpNet
format for both allow and deny lists.Relevant issues:
Notes & open questions
IpNet
format to ensure the connection manager handles IPs in CIDR notation.Change checklist