Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions crates/p2p/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use crate::{

/// Returns the external IP and Hostname fields as multiaddrs using the listen
/// TCP addresses ports.
pub(crate) fn external_tcp_multiaddrs(cfg: &P2PConfig) -> crate::p2p::Result<Vec<Multiaddr>> {
pub fn external_tcp_multiaddrs(cfg: &P2PConfig) -> crate::p2p::Result<Vec<Multiaddr>> {
let addrs = cfg.parse_tcp_addrs()?;

let mut ports = vec![];
Expand Down Expand Up @@ -60,7 +60,7 @@ pub(crate) fn external_tcp_multiaddrs(cfg: &P2PConfig) -> crate::p2p::Result<Vec

/// Returns the external IP and Hostname fields as multiaddrs using the listen
/// UDP addresses ports.
pub(crate) fn external_udp_multiaddrs(cfg: &P2PConfig) -> crate::p2p::Result<Vec<Multiaddr>> {
pub fn external_udp_multiaddrs(cfg: &P2PConfig) -> crate::p2p::Result<Vec<Multiaddr>> {
let addrs = cfg.parse_udp_addrs()?;

let mut ports = vec![];
Expand Down
2 changes: 2 additions & 0 deletions crates/relay-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ pluto-p2p.workspace = true
pluto-core.workspace = true

[dev-dependencies]
reqwest = { workspace = true }
serde_json = { workspace = true }

[lints]
workspace = true
3 changes: 3 additions & 0 deletions crates/relay-server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,6 @@ pub mod utils;
pub use error::RelayP2PError;

pub(crate) use error::Result;

#[doc(hidden)]
pub use web::enr_server;
9 changes: 9 additions & 0 deletions crates/relay-server/src/p2p.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use pluto_p2p::{
manet::Manet,
p2p::{Node, NodeType},
p2p_context::P2PContext,
utils::{external_tcp_multiaddrs, external_udp_multiaddrs},
};

/// Runs a relay P2P node.
Expand Down Expand Up @@ -69,12 +70,20 @@ pub async fn run_relay_p2p_node(

let listeners = Arc::new(RwLock::new(Vec::new()));

// Compute external multiaddrs from external_ip / external_host config so
// they're advertised on `/` and folded into ENR responses on `/enr` even
// when libp2p only sees private listen addresses (e.g., K8s pods behind
// NodePort).
let mut external_addrs = external_tcp_multiaddrs(&config.p2p_config)?;
external_addrs.extend(external_udp_multiaddrs(&config.p2p_config)?);

let enr_server_handle = tokio::spawn(enr_server(
server_errors.clone(),
config.clone(),
key.clone(),
*node.local_peer_id(),
listeners.clone(),
external_addrs,
ct.child_token(),
));

Expand Down
169 changes: 169 additions & 0 deletions crates/relay-server/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,172 @@ pub(crate) fn extract_ip_and_udp_port(addr: &Multiaddr) -> Option<(Ipv4Addr, u16
_ => None,
}
}

/// Extracts DNS hostname and TCP port from a `/dns(4|6)/<host>/tcp/<port>`
/// multiaddr.
pub(crate) fn extract_dns_and_tcp_port(addr: &Multiaddr) -> Option<(String, u16)> {
let mut host: Option<String> = None;
let mut port: Option<u16> = None;

for protocol in addr.iter() {
match protocol {
Protocol::Dns(h) | Protocol::Dns4(h) | Protocol::Dns6(h) => {
host = Some(h.into_owned());
}
Protocol::Tcp(p) => port = Some(p),
_ => {}
}
}

match (host, port) {
(Some(h), Some(p)) => Some((h, p)),
_ => None,
}
}

/// Extracts DNS hostname and UDP port from a
/// `/dns(4|6)/<host>/udp/<port>/quic-v1` multiaddr.
pub(crate) fn extract_dns_and_udp_port(addr: &Multiaddr) -> Option<(String, u16)> {
let mut host: Option<String> = None;
let mut port: Option<u16> = None;

for protocol in addr.iter() {
match protocol {
Protocol::Dns(h) | Protocol::Dns4(h) | Protocol::Dns6(h) => {
host = Some(h.into_owned());
}
Protocol::Udp(p) => port = Some(p),
_ => {}
}
}

match (host, port) {
(Some(h), Some(p)) => Some((h, p)),
_ => None,
}
}

#[cfg(test)]
mod tests {
use super::*;
use std::net::Ipv6Addr;

fn ma(s: &str) -> Multiaddr {
s.parse().expect("valid multiaddr")
}

#[test]
fn is_public_addr_public_ipv4() {
assert!(is_public_addr(&ma("/ip4/1.2.3.4/tcp/8000")));
}

#[test]
fn is_public_addr_private_ipv4() {
assert!(!is_public_addr(&ma("/ip4/10.0.0.1/tcp/8000")));
assert!(!is_public_addr(&ma("/ip4/192.168.1.1/tcp/8000")));
assert!(!is_public_addr(&ma("/ip4/172.16.0.1/tcp/8000")));
}

#[test]
fn is_public_addr_loopback_unspecified_linklocal() {
assert!(!is_public_addr(&ma("/ip4/127.0.0.1/tcp/8000")));
assert!(!is_public_addr(&ma("/ip4/0.0.0.0/tcp/8000")));
assert!(!is_public_addr(&ma("/ip4/169.254.1.1/tcp/8000")));
}

#[test]
fn is_public_addr_dns_is_not_public() {
// No IP component: function falls through to `false`.
assert!(!is_public_addr(&ma("/dns/example.com/tcp/8000")));
}

#[test]
fn extract_ip_and_tcp_port_happy() {
let ip = Ipv4Addr::new(1, 2, 3, 4);
let got = extract_ip_and_tcp_port(&ma("/ip4/1.2.3.4/tcp/8000")).unwrap();
assert_eq!(got, (ip, 8000));
}

#[test]
fn extract_ip_and_tcp_port_missing_ip() {
assert!(extract_ip_and_tcp_port(&ma("/dns/example.com/tcp/8000")).is_none());
}

#[test]
fn extract_ip_and_tcp_port_missing_tcp() {
assert!(extract_ip_and_tcp_port(&ma("/ip4/1.2.3.4/udp/8000/quic-v1")).is_none());
}

#[test]
fn extract_ip_and_udp_port_quic_v1() {
let ip = Ipv4Addr::new(5, 6, 7, 8);
let got = extract_ip_and_udp_port(&ma("/ip4/5.6.7.8/udp/9000/quic-v1")).unwrap();
assert_eq!(got, (ip, 9000));
}

#[test]
fn extract_ip_and_udp_port_ignores_tcp() {
assert!(extract_ip_and_udp_port(&ma("/ip4/1.2.3.4/tcp/8000")).is_none());
}

#[test]
fn extract_dns_and_tcp_port_dns() {
let got = extract_dns_and_tcp_port(&ma("/dns/relay.example.com/tcp/3610")).unwrap();
assert_eq!(got, ("relay.example.com".to_string(), 3610));
}

#[test]
fn extract_dns_and_tcp_port_dns4() {
let got = extract_dns_and_tcp_port(&ma("/dns4/relay.example.com/tcp/3610")).unwrap();
assert_eq!(got, ("relay.example.com".to_string(), 3610));
}

#[test]
fn extract_dns_and_tcp_port_dns6() {
let got = extract_dns_and_tcp_port(&ma("/dns6/relay.example.com/tcp/3610")).unwrap();
assert_eq!(got, ("relay.example.com".to_string(), 3610));
}

#[test]
fn extract_dns_and_tcp_port_skips_ip4() {
assert!(extract_dns_and_tcp_port(&ma("/ip4/1.2.3.4/tcp/3610")).is_none());
}

#[test]
fn extract_dns_and_tcp_port_missing_tcp() {
assert!(extract_dns_and_tcp_port(&ma("/dns/relay.example.com/udp/3610/quic-v1")).is_none());
}

#[test]
fn extract_dns_and_udp_port_quic_v1() {
let got = extract_dns_and_udp_port(&ma("/dns/relay.example.com/udp/3610/quic-v1")).unwrap();
assert_eq!(got, ("relay.example.com".to_string(), 3610));
}

#[test]
fn extract_dns_and_udp_port_skips_tcp() {
assert!(extract_dns_and_udp_port(&ma("/dns/relay.example.com/tcp/3610")).is_none());
}

#[test]
fn extract_dns_and_udp_port_dns4_dns6() {
let got4 =
extract_dns_and_udp_port(&ma("/dns4/relay.example.com/udp/3610/quic-v1")).unwrap();
assert_eq!(got4, ("relay.example.com".to_string(), 3610));

let got6 =
extract_dns_and_udp_port(&ma("/dns6/relay.example.com/udp/3610/quic-v1")).unwrap();
assert_eq!(got6, ("relay.example.com".to_string(), 3610));
}

#[test]
fn ipv6_helpers_do_not_crash() {
// Sanity: IPv6-shaped multiaddrs don't match the IPv4 extractors but
// also don't panic.
let addr: Multiaddr = format!("/ip6/{}/tcp/8000", Ipv6Addr::LOCALHOST)
.parse()
.unwrap();
assert!(extract_ip_and_tcp_port(&addr).is_none());
assert!(extract_ip_and_udp_port(&addr).is_none());
}
}
Loading
Loading