Skip to content

quic: support quic/udp address parsing#430

Merged
Officeyutong merged 3 commits into
nervosnetwork:masterfrom
Officeyutong:support-quic-udp-parsing
Apr 16, 2026
Merged

quic: support quic/udp address parsing#430
Officeyutong merged 3 commits into
nervosnetwork:masterfrom
Officeyutong:support-quic-udp-parsing

Conversation

@Officeyutong

@Officeyutong Officeyutong commented Apr 13, 2026

Copy link
Copy Markdown
Collaborator

Summary

This PR is part 1 of 5 in the series to add native QUIC transport support to Tentacle, as outlined in #428.

QUIC Implementation Roadmap

PR Title Description
PR 1 (this) multiaddr: add Udp and QuicV1 protocols Extend the multiaddr crate to parse, serialize, and display /udp/<port> and /quic-v1 protocol components
PR 2 QUIC connectivity spike + self-signed certificate generation Verify quinn + rustls + tokio integration; implement the dual-key model with a self-signed X.509 certificate carrying a private extension with binding_sig
PR 3 Custom rustls certificate verifiers Implement ServerCertVerifier and ClientCertVerifier that enforce Tentacle's identity model instead of CA-based validation
PR 4 End-to-end QuicSession Wire up QuicBiStream, QuicEndpoint, QuicSession, and the three integration points into InnerService; generalize Substream<U> with a stream type parameter
PR 5 ServiceBuilder integration, example, and docs Expose ServiceBuilder::quic_config(), add a runnable example, and write user-facing documentation

What this PR does

Adds Udp(u16) and QuicV1 variants to the Protocol enum in tentacle-multiaddr, enabling addresses of the form /ip4/<addr>/udp/<port>/quic-v1.

Changes in multiaddr/src/protocol.rs:

  • Two new protocol constants: UDP = 0x0111, QUICV1 = 0x01cd
  • Six match branches added for Udp/QuicV1: from_str_peek, from_bytes, write_to_bytes, acquire, Display::fmt, and a new Error::InvalidPort variant for malformed UDP ports

Changes in multiaddr/src/error.rs:

  • New Error::InvalidPort variant for UDP port parsing failures

No changes to any existing tentacle, yamux, or secio code. The quic feature flag and transport-layer address validation (e.g., rejecting DNS-based QUIC addresses) will be introduced in later PRs.

Test plan

Tests are organized into three files:

multiaddr/tests/udp.rs — UDP protocol tests:

  • String parse and Display roundtrip: IPv4, IPv6, port 0, port 65535
  • Binary (to_vec / TryFrom<Vec<u8>>) roundtrip
  • Hex-encoded roundtrip
  • push / pop for Udp protocol component
  • acquire() produces 'static protocols correctly
  • Error cases: missing port, non-numeric port, port overflow (65536), negative port

multiaddr/tests/quic.rs — QuicV1 protocol tests:

  • String parse and Display roundtrip: IPv4, IPv6, port 0, port 65535 (with /quic-v1)
  • QUIC address with /p2p/<peer_id> suffix
  • Binary roundtrip for IPv4 and IPv6 QUIC addresses
  • Hex-encoded roundtrip using the ma_valid helper (same pattern as existing onion tests)
  • push / pop for QuicV1 protocol component
  • acquire() produces 'static protocols correctly
  • DNS-based QUIC addresses (/dns4/.../udp/.../quic-v1) parse successfully at the multiaddr layer (rejection is deferred to the transport layer in PR 4)
  • Error cases: unknown protocol string

multiaddr/tests/quic.rs — Cross-crate compatibility tests (tentacle vs parity-multiaddr):

  • UDP string parse produces identical to_string() in both crates
  • UDP binary encoding is identical between crates
  • UDP binary cross-decode: bytes from one crate decode in the other
  • UDP protocol iteration produces matching components
  • UDP push / pop produces identical results
  • UDP port boundaries (0, 1, 1234, 65535) produce identical encodings
  • IPv6 + UDP binary encoding is identical
  • IPv6 + UDP cross-decode works both ways
  • /quic-v1 shared prefix (/ip4/.../udp/...) is binary-identical with parity
  • /quic-v1 full bytes are not decodable by parity (expected — parity lacks QuicV1)
  • /quic-v1 string is not parseable by parity (expected)
  • parity's legacy /quic is not parseable by tentacle (expected)
  • UDP hex roundtrip cross-crate
  • UDP + P2P suffix with pop comparison

@eval-exec

Copy link
Copy Markdown
Collaborator

Please execute cargo fmt --all

@quake

quake commented Apr 14, 2026

Copy link
Copy Markdown
Member

/quic refers to the old draft-29 protocol, while /quic-v1 refers to the standardized RFC 9000 QUIC v1, so they are not interchangeable, we should use quic-v1 in tentacle

Comment thread multiaddr/src/protocol.rs Outdated
const WSS: u32 = 0x01de;
const MEMORY: u32 = 0x0309;
const ONION3: u32 = 0x01bd;
const QUIC: u32 = 0x1111;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@eval-exec eval-exec Apr 14, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread multiaddr/src/protocol.rs Outdated
"udp" => {
let s = iter.next().ok_or(Error::InvalidProtocolString)?;
Ok(Protocol::Udp(
u16::from_str_radix(s, 10).map_err(|_| Error::InvalidPort)?,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we use s.parse()? here?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaced with s.parse()? now

@eval-exec

eval-exec commented Apr 14, 2026

Copy link
Copy Markdown
Collaborator

We have

mod test {
use super::{Multiaddr, Protocol};
use parity_multiaddr::{Multiaddr as OtherMultiaddr, Protocol as OtherProtocol};
#[test]
fn compatibility_test() {
let mut address: Multiaddr = "/ip4/127.0.0.1".parse().unwrap();
address.push(Protocol::Tcp(10000));
assert_eq!(address, "/ip4/127.0.0.1/tcp/10000".parse().unwrap());
let _address: Multiaddr = "/ip4/127.0.0.1/tcp/20/tls/main".parse().unwrap();
let mut address_1: Multiaddr =
"/ip4/47.111.169.36/tcp/8111/p2p/QmNQ4jky6uVqLDrPU7snqxARuNGWNLgSrTnssbRuy3ij2W"
.parse()
.unwrap();
let mut address_2: OtherMultiaddr =
"/ip4/47.111.169.36/tcp/8111/p2p/QmNQ4jky6uVqLDrPU7snqxARuNGWNLgSrTnssbRuy3ij2W"
.parse()
.unwrap();
let p_1 = address_1.pop().unwrap();
let p_2 = address_2.pop().unwrap();
match (p_1, p_2) {
(Protocol::P2P(s_1), OtherProtocol::P2p(s_2)) => assert_eq!(s_1, s_2.to_bytes()),
e => panic!("not expect protocol: {:?}", e),
}
}
}

Perhaps we should move fn compatibility_test into ./multiaddr/tests/ and also add parity tests for quic, quic-v1, and udp.

Comment thread multiaddr/src/protocol.rs Outdated
"udp" => {
let s = iter.next().ok_or(Error::InvalidProtocolString)?;
Ok(Protocol::Udp(
u16::from_str_radix(s, 10).map_err(|_| Error::InvalidPort)?,

@eval-exec eval-exec Apr 14, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
u16::from_str_radix(s, 10).map_err(|_| Error::InvalidPort)?,
s.parse()?

If we use s.parse(), we can avoid introducing a new error type like Error::InvalidPort.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@Officeyutong

Copy link
Copy Markdown
Collaborator Author

We have

mod test {
use super::{Multiaddr, Protocol};
use parity_multiaddr::{Multiaddr as OtherMultiaddr, Protocol as OtherProtocol};
#[test]
fn compatibility_test() {
let mut address: Multiaddr = "/ip4/127.0.0.1".parse().unwrap();
address.push(Protocol::Tcp(10000));
assert_eq!(address, "/ip4/127.0.0.1/tcp/10000".parse().unwrap());
let _address: Multiaddr = "/ip4/127.0.0.1/tcp/20/tls/main".parse().unwrap();
let mut address_1: Multiaddr =
"/ip4/47.111.169.36/tcp/8111/p2p/QmNQ4jky6uVqLDrPU7snqxARuNGWNLgSrTnssbRuy3ij2W"
.parse()
.unwrap();
let mut address_2: OtherMultiaddr =
"/ip4/47.111.169.36/tcp/8111/p2p/QmNQ4jky6uVqLDrPU7snqxARuNGWNLgSrTnssbRuy3ij2W"
.parse()
.unwrap();
let p_1 = address_1.pop().unwrap();
let p_2 = address_2.pop().unwrap();
match (p_1, p_2) {
(Protocol::P2P(s_1), OtherProtocol::P2p(s_2)) => assert_eq!(s_1, s_2.to_bytes()),
e => panic!("not expect protocol: {:?}", e),
}
}
}

Perhaps we should move fn compatibility_test into ./multiaddr/tests/ and also add parity tests for quic, quic-v1, and udp.

compatibility_test was moved to tests folder now. Also added tests for quic-v1 and udp. quic was not added since we are not using it.

@Officeyutong Officeyutong merged commit adb579b into nervosnetwork:master Apr 16, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants