Create network_utils crate (#7761)

Anchor currently depends on `lighthouse_network` for a few types and utilities that live within. As we use our own libp2p behaviours, we actually do not use the core logic in that crate. This makes us transitively depend on a bunch of unneeded crates (even a whole separate libp2p if the versions mismatch!)


  Move things we require into it's own lightweight crate.


Co-Authored-By: Daniel Knopik <daniel@dknopik.de>
This commit is contained in:
Daniel Knopik
2025-09-10 14:59:24 +02:00
committed by GitHub
parent caa1df6fc3
commit ee1b6bc81b
42 changed files with 198 additions and 169 deletions

View File

@@ -0,0 +1,17 @@
[package]
name = "network_utils"
version = "0.1.0"
edition = { workspace = true }
[dependencies]
discv5 = { workspace = true }
libp2p-identity = "0.2"
lru_cache = { workspace = true }
metrics = { workspace = true }
multiaddr = "0.18.2"
parking_lot = { workspace = true }
serde = { workspace = true }
tiny-keccak = { version = "2", features = ["keccak"] }
[dev-dependencies]
hex = { workspace = true }

View File

@@ -0,0 +1,46 @@
use metrics::*;
use std::sync::LazyLock;
pub static NAT_OPEN: LazyLock<Result<IntGaugeVec>> = LazyLock::new(|| {
try_create_int_gauge_vec(
"nat_open",
"An estimate indicating if the local node is reachable from external nodes",
&["protocol"],
)
});
pub static DISCOVERY_BYTES: LazyLock<Result<IntGaugeVec>> = LazyLock::new(|| {
try_create_int_gauge_vec(
"discovery_bytes",
"The number of bytes sent and received in discovery",
&["direction"],
)
});
pub static DISCOVERY_QUEUE: LazyLock<Result<IntGauge>> = LazyLock::new(|| {
try_create_int_gauge(
"discovery_queue_size",
"The number of discovery queries awaiting execution",
)
});
pub static DISCOVERY_REQS: LazyLock<Result<Gauge>> = LazyLock::new(|| {
try_create_float_gauge(
"discovery_requests",
"The number of unsolicited discovery requests per second",
)
});
pub static DISCOVERY_SESSIONS: LazyLock<Result<IntGauge>> = LazyLock::new(|| {
try_create_int_gauge(
"discovery_sessions",
"The number of active discovery sessions with peers",
)
});
pub fn scrape_discovery_metrics() {
let metrics =
discv5::metrics::Metrics::from(discv5::Discv5::<discv5::DefaultProtocolId>::raw_metrics());
set_float_gauge(&DISCOVERY_REQS, metrics.unsolicited_requests_per_second);
set_gauge(&DISCOVERY_SESSIONS, metrics.active_sessions as i64);
set_gauge_vec(&DISCOVERY_BYTES, &["inbound"], metrics.bytes_recv as i64);
set_gauge_vec(&DISCOVERY_BYTES, &["outbound"], metrics.bytes_sent as i64);
set_gauge_vec(&NAT_OPEN, &["discv5_ipv4"], metrics.ipv4_contactable as i64);
set_gauge_vec(&NAT_OPEN, &["discv5_ipv6"], metrics.ipv6_contactable as i64);
}

View File

@@ -0,0 +1,394 @@
//! ENR extension trait to support libp2p integration.
use discv5::enr::{CombinedKey, CombinedPublicKey};
use libp2p_identity::{KeyType, Keypair, PublicKey, ed25519, secp256k1};
use multiaddr::{Multiaddr, PeerId, Protocol};
use tiny_keccak::{Hasher, Keccak};
type Enr = discv5::enr::Enr<CombinedKey>;
pub const QUIC_ENR_KEY: &str = "quic";
pub const QUIC6_ENR_KEY: &str = "quic6";
/// Extend ENR for libp2p types.
pub trait EnrExt {
/// The libp2p `PeerId` for the record.
fn peer_id(&self) -> PeerId;
/// Returns a list of multiaddrs if the ENR has an `ip` and one of [`tcp`,`udp`,`quic`] key **or** an `ip6` and one of [`tcp6`,`udp6`,`quic6`].
/// The vector remains empty if these fields are not defined.
fn multiaddr(&self) -> Vec<Multiaddr>;
/// Returns a list of multiaddrs with the `PeerId` prepended.
fn multiaddr_p2p(&self) -> Vec<Multiaddr>;
/// Returns any multiaddrs that contain the TCP protocol with the `PeerId` prepended.
fn multiaddr_p2p_tcp(&self) -> Vec<Multiaddr>;
/// Returns any multiaddrs that contain the UDP protocol with the `PeerId` prepended.
fn multiaddr_p2p_udp(&self) -> Vec<Multiaddr>;
/// Returns any multiaddrs that contain the TCP protocol.
fn multiaddr_tcp(&self) -> Vec<Multiaddr>;
/// Returns any QUIC multiaddrs that are registered in this ENR.
fn multiaddr_quic(&self) -> Vec<Multiaddr>;
/// Returns the quic port if one is set.
fn quic4(&self) -> Option<u16>;
/// Returns the quic6 port if one is set.
fn quic6(&self) -> Option<u16>;
}
/// Extend ENR CombinedPublicKey for libp2p types.
pub trait CombinedKeyPublicExt {
/// Converts the publickey into a peer id, without consuming the key.
fn as_peer_id(&self) -> PeerId;
}
/// Extend ENR CombinedKey for conversion to libp2p keys.
pub trait CombinedKeyExt {
/// Converts a libp2p key into an ENR combined key.
fn from_libp2p(key: Keypair) -> Result<CombinedKey, &'static str>;
/// Converts a [`secp256k1::Keypair`] into and Enr [`CombinedKey`].
fn from_secp256k1(key: &secp256k1::Keypair) -> CombinedKey;
}
impl EnrExt for Enr {
/// The libp2p `PeerId` for the record.
fn peer_id(&self) -> PeerId {
self.public_key().as_peer_id()
}
/// Returns the quic port if one is set.
fn quic4(&self) -> Option<u16> {
self.get_decodable(QUIC_ENR_KEY).and_then(Result::ok)
}
/// Returns the quic6 port if one is set.
fn quic6(&self) -> Option<u16> {
self.get_decodable(QUIC6_ENR_KEY).and_then(Result::ok)
}
/// Returns a list of multiaddrs if the ENR has an `ip` and either a `tcp`, `quic` or `udp` key **or** an `ip6` and either a `tcp6` `quic6` or `udp6`.
/// The vector remains empty if these fields are not defined.
fn multiaddr(&self) -> Vec<Multiaddr> {
let mut multiaddrs: Vec<Multiaddr> = Vec::new();
if let Some(ip) = self.ip4() {
if let Some(udp) = self.udp4() {
let mut multiaddr: Multiaddr = ip.into();
multiaddr.push(Protocol::Udp(udp));
multiaddrs.push(multiaddr);
}
if let Some(quic) = self.quic4() {
let mut multiaddr: Multiaddr = ip.into();
multiaddr.push(Protocol::Udp(quic));
multiaddr.push(Protocol::QuicV1);
multiaddrs.push(multiaddr);
}
if let Some(tcp) = self.tcp4() {
let mut multiaddr: Multiaddr = ip.into();
multiaddr.push(Protocol::Tcp(tcp));
multiaddrs.push(multiaddr);
}
}
if let Some(ip6) = self.ip6() {
if let Some(udp6) = self.udp6() {
let mut multiaddr: Multiaddr = ip6.into();
multiaddr.push(Protocol::Udp(udp6));
multiaddrs.push(multiaddr);
}
if let Some(quic6) = self.quic6() {
let mut multiaddr: Multiaddr = ip6.into();
multiaddr.push(Protocol::Udp(quic6));
multiaddr.push(Protocol::QuicV1);
multiaddrs.push(multiaddr);
}
if let Some(tcp6) = self.tcp6() {
let mut multiaddr: Multiaddr = ip6.into();
multiaddr.push(Protocol::Tcp(tcp6));
multiaddrs.push(multiaddr);
}
}
multiaddrs
}
/// Returns a list of multiaddrs if the ENR has an `ip` and either a `tcp` or `udp` key **or** an `ip6` and either a `tcp6` or `udp6`.
/// The vector remains empty if these fields are not defined.
///
/// This also prepends the `PeerId` into each multiaddr with the `P2p` protocol.
fn multiaddr_p2p(&self) -> Vec<Multiaddr> {
let peer_id = self.peer_id();
let mut multiaddrs: Vec<Multiaddr> = Vec::new();
if let Some(ip) = self.ip4() {
if let Some(udp) = self.udp4() {
let mut multiaddr: Multiaddr = ip.into();
multiaddr.push(Protocol::Udp(udp));
multiaddr.push(Protocol::P2p(peer_id));
multiaddrs.push(multiaddr);
}
if let Some(tcp) = self.tcp4() {
let mut multiaddr: Multiaddr = ip.into();
multiaddr.push(Protocol::Tcp(tcp));
multiaddr.push(Protocol::P2p(peer_id));
multiaddrs.push(multiaddr);
}
}
if let Some(ip6) = self.ip6() {
if let Some(udp6) = self.udp6() {
let mut multiaddr: Multiaddr = ip6.into();
multiaddr.push(Protocol::Udp(udp6));
multiaddr.push(Protocol::P2p(peer_id));
multiaddrs.push(multiaddr);
}
if let Some(tcp6) = self.tcp6() {
let mut multiaddr: Multiaddr = ip6.into();
multiaddr.push(Protocol::Tcp(tcp6));
multiaddr.push(Protocol::P2p(peer_id));
multiaddrs.push(multiaddr);
}
}
multiaddrs
}
/// Returns a list of multiaddrs if the ENR has an `ip` and a `tcp` key **or** an `ip6` and a `tcp6`.
/// The vector remains empty if these fields are not defined.
///
/// This also prepends the `PeerId` into each multiaddr with the `P2p` protocol.
fn multiaddr_p2p_tcp(&self) -> Vec<Multiaddr> {
let peer_id = self.peer_id();
let mut multiaddrs: Vec<Multiaddr> = Vec::new();
if let Some(ip) = self.ip4()
&& let Some(tcp) = self.tcp4()
{
let mut multiaddr: Multiaddr = ip.into();
multiaddr.push(Protocol::Tcp(tcp));
multiaddr.push(Protocol::P2p(peer_id));
multiaddrs.push(multiaddr);
}
if let Some(ip6) = self.ip6()
&& let Some(tcp6) = self.tcp6()
{
let mut multiaddr: Multiaddr = ip6.into();
multiaddr.push(Protocol::Tcp(tcp6));
multiaddr.push(Protocol::P2p(peer_id));
multiaddrs.push(multiaddr);
}
multiaddrs
}
/// Returns a list of multiaddrs if the ENR has an `ip` and a `udp` key **or** an `ip6` and a `udp6`.
/// The vector remains empty if these fields are not defined.
///
/// This also prepends the `PeerId` into each multiaddr with the `P2p` protocol.
fn multiaddr_p2p_udp(&self) -> Vec<Multiaddr> {
let peer_id = self.peer_id();
let mut multiaddrs: Vec<Multiaddr> = Vec::new();
if let Some(ip) = self.ip4()
&& let Some(udp) = self.udp4()
{
let mut multiaddr: Multiaddr = ip.into();
multiaddr.push(Protocol::Udp(udp));
multiaddr.push(Protocol::P2p(peer_id));
multiaddrs.push(multiaddr);
}
if let Some(ip6) = self.ip6()
&& let Some(udp6) = self.udp6()
{
let mut multiaddr: Multiaddr = ip6.into();
multiaddr.push(Protocol::Udp(udp6));
multiaddr.push(Protocol::P2p(peer_id));
multiaddrs.push(multiaddr);
}
multiaddrs
}
/// Returns a list of multiaddrs if the ENR has an `ip` and a `quic` key **or** an `ip6` and a `quic6`.
fn multiaddr_quic(&self) -> Vec<Multiaddr> {
let mut multiaddrs: Vec<Multiaddr> = Vec::new();
if let Some(quic_port) = self.quic4()
&& let Some(ip) = self.ip4()
{
let mut multiaddr: Multiaddr = ip.into();
multiaddr.push(Protocol::Udp(quic_port));
multiaddr.push(Protocol::QuicV1);
multiaddrs.push(multiaddr);
}
if let Some(quic6_port) = self.quic6()
&& let Some(ip6) = self.ip6()
{
let mut multiaddr: Multiaddr = ip6.into();
multiaddr.push(Protocol::Udp(quic6_port));
multiaddr.push(Protocol::QuicV1);
multiaddrs.push(multiaddr);
}
multiaddrs
}
/// Returns a list of multiaddrs if the ENR has an `ip` and either a `tcp` or `udp` key **or** an `ip6` and either a `tcp6` or `udp6`.
fn multiaddr_tcp(&self) -> Vec<Multiaddr> {
let mut multiaddrs: Vec<Multiaddr> = Vec::new();
if let Some(ip) = self.ip4()
&& let Some(tcp) = self.tcp4()
{
let mut multiaddr: Multiaddr = ip.into();
multiaddr.push(Protocol::Tcp(tcp));
multiaddrs.push(multiaddr);
}
if let Some(ip6) = self.ip6()
&& let Some(tcp6) = self.tcp6()
{
let mut multiaddr: Multiaddr = ip6.into();
multiaddr.push(Protocol::Tcp(tcp6));
multiaddrs.push(multiaddr);
}
multiaddrs
}
}
impl CombinedKeyPublicExt for CombinedPublicKey {
/// Converts the publickey into a peer id, without consuming the key.
///
/// This is only available with the `libp2p` feature flag.
fn as_peer_id(&self) -> PeerId {
match self {
Self::Secp256k1(pk) => {
let pk_bytes = pk.to_sec1_bytes();
let libp2p_pk: PublicKey = secp256k1::PublicKey::try_from_bytes(&pk_bytes)
.expect("valid public key")
.into();
PeerId::from_public_key(&libp2p_pk)
}
Self::Ed25519(pk) => {
let pk_bytes = pk.to_bytes();
let libp2p_pk: PublicKey = ed25519::PublicKey::try_from_bytes(&pk_bytes)
.expect("valid public key")
.into();
PeerId::from_public_key(&libp2p_pk)
}
}
}
}
impl CombinedKeyExt for CombinedKey {
fn from_libp2p(key: Keypair) -> Result<CombinedKey, &'static str> {
match key.key_type() {
KeyType::Secp256k1 => {
let key = key.try_into_secp256k1().expect("right key type");
let secret =
discv5::enr::k256::ecdsa::SigningKey::from_slice(&key.secret().to_bytes())
.expect("libp2p key must be valid");
Ok(CombinedKey::Secp256k1(secret))
}
KeyType::Ed25519 => {
let key = key.try_into_ed25519().expect("right key type");
let ed_keypair = discv5::enr::ed25519_dalek::SigningKey::from_bytes(
&(key.to_bytes()[..32])
.try_into()
.expect("libp2p key must be valid"),
);
Ok(CombinedKey::from(ed_keypair))
}
_ => Err("Unsupported keypair kind"),
}
}
fn from_secp256k1(key: &secp256k1::Keypair) -> Self {
let secret = discv5::enr::k256::ecdsa::SigningKey::from_slice(&key.secret().to_bytes())
.expect("libp2p key must be valid");
CombinedKey::Secp256k1(secret)
}
}
// helper function to convert a peer_id to a node_id. This is only possible for secp256k1/ed25519 libp2p
// peer_ids
pub fn peer_id_to_node_id(peer_id: &PeerId) -> Result<discv5::enr::NodeId, String> {
// A libp2p peer id byte representation should be 2 length bytes + 4 protobuf bytes + compressed pk bytes
// if generated from a PublicKey with Identity multihash.
let pk_bytes = &peer_id.to_bytes()[2..];
let public_key = PublicKey::try_decode_protobuf(pk_bytes).map_err(|e| {
format!(
" Cannot parse libp2p public key public key from peer id: {}",
e
)
})?;
match public_key.key_type() {
KeyType::Secp256k1 => {
let pk = public_key
.clone()
.try_into_secp256k1()
.expect("right key type");
let uncompressed_key_bytes = &pk.to_bytes_uncompressed()[1..];
let mut output = [0_u8; 32];
let mut hasher = Keccak::v256();
hasher.update(uncompressed_key_bytes);
hasher.finalize(&mut output);
Ok(discv5::enr::NodeId::parse(&output).expect("Must be correct length"))
}
KeyType::Ed25519 => {
let pk = public_key
.clone()
.try_into_ed25519()
.expect("right key type");
let uncompressed_key_bytes = pk.to_bytes();
let mut output = [0_u8; 32];
let mut hasher = Keccak::v256();
hasher.update(&uncompressed_key_bytes);
hasher.finalize(&mut output);
Ok(discv5::enr::NodeId::parse(&output).expect("Must be correct length"))
}
_ => Err(format!("Unsupported public key from peer {}", peer_id)),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_secp256k1_peer_id_conversion() {
let sk_hex = "df94a73d528434ce2309abb19c16aedb535322797dbd59c157b1e04095900f48";
let sk_bytes = hex::decode(sk_hex).unwrap();
let secret_key = discv5::enr::k256::ecdsa::SigningKey::from_slice(&sk_bytes).unwrap();
let libp2p_sk = secp256k1::SecretKey::try_from_bytes(sk_bytes).unwrap();
let secp256k1_kp: secp256k1::Keypair = libp2p_sk.into();
let libp2p_kp: Keypair = secp256k1_kp.into();
let peer_id = libp2p_kp.public().to_peer_id();
let enr = discv5::enr::Enr::builder().build(&secret_key).unwrap();
let node_id = peer_id_to_node_id(&peer_id).unwrap();
assert_eq!(enr.node_id(), node_id);
}
#[test]
fn test_ed25519_peer_conversion() {
let sk_hex = "4dea8a5072119927e9d243a7d953f2f4bc95b70f110978e2f9bc7a9000e4b261";
let sk_bytes = hex::decode(sk_hex).unwrap();
let secret_key = discv5::enr::ed25519_dalek::SigningKey::from_bytes(
&sk_bytes.clone().try_into().unwrap(),
);
let libp2p_sk = ed25519::SecretKey::try_from_bytes(sk_bytes).unwrap();
let secp256k1_kp: ed25519::Keypair = libp2p_sk.into();
let libp2p_kp: Keypair = secp256k1_kp.into();
let peer_id = libp2p_kp.public().to_peer_id();
let enr = discv5::enr::Enr::builder().build(&secret_key).unwrap();
let node_id = peer_id_to_node_id(&peer_id).unwrap();
assert_eq!(enr.node_id(), node_id);
}
}

View File

@@ -0,0 +1,4 @@
pub mod discovery_metrics;
pub mod enr_ext;
pub mod listen_addr;
pub mod unused_port;

View File

@@ -0,0 +1,104 @@
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
use multiaddr::{Multiaddr, Protocol};
use serde::{Deserialize, Serialize};
/// A listening address composed by an Ip, an UDP port and a TCP port.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ListenAddr<Ip> {
/// The IP address we will listen on.
pub addr: Ip,
/// The UDP port that discovery will listen on.
pub disc_port: u16,
/// The UDP port that QUIC will listen on.
pub quic_port: u16,
/// The TCP port that libp2p will listen on.
pub tcp_port: u16,
}
impl<Ip: Into<IpAddr> + Clone> ListenAddr<Ip> {
pub fn discovery_socket_addr(&self) -> SocketAddr {
(self.addr.clone().into(), self.disc_port).into()
}
pub fn quic_socket_addr(&self) -> SocketAddr {
(self.addr.clone().into(), self.quic_port).into()
}
pub fn tcp_socket_addr(&self) -> SocketAddr {
(self.addr.clone().into(), self.tcp_port).into()
}
}
/// Types of listening addresses Lighthouse can accept.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum ListenAddress {
V4(ListenAddr<Ipv4Addr>),
V6(ListenAddr<Ipv6Addr>),
DualStack(ListenAddr<Ipv4Addr>, ListenAddr<Ipv6Addr>),
}
impl ListenAddress {
/// Return the listening address over IpV4 if any.
pub fn v4(&self) -> Option<&ListenAddr<Ipv4Addr>> {
match self {
ListenAddress::V4(v4_addr) | ListenAddress::DualStack(v4_addr, _) => Some(v4_addr),
ListenAddress::V6(_) => None,
}
}
/// Return the listening address over IpV6 if any.
pub fn v6(&self) -> Option<&ListenAddr<Ipv6Addr>> {
match self {
ListenAddress::V6(v6_addr) | ListenAddress::DualStack(_, v6_addr) => Some(v6_addr),
ListenAddress::V4(_) => None,
}
}
/// Returns the addresses the Swarm will listen on, given the setup.
pub fn libp2p_addresses(&self) -> impl Iterator<Item = Multiaddr> {
let v4_tcp_multiaddr = self
.v4()
.map(|v4_addr| Multiaddr::from(v4_addr.addr).with(Protocol::Tcp(v4_addr.tcp_port)));
let v4_quic_multiaddr = self.v4().map(|v4_addr| {
Multiaddr::from(v4_addr.addr)
.with(Protocol::Udp(v4_addr.quic_port))
.with(Protocol::QuicV1)
});
let v6_quic_multiaddr = self.v6().map(|v6_addr| {
Multiaddr::from(v6_addr.addr)
.with(Protocol::Udp(v6_addr.quic_port))
.with(Protocol::QuicV1)
});
let v6_tcp_multiaddr = self
.v6()
.map(|v6_addr| Multiaddr::from(v6_addr.addr).with(Protocol::Tcp(v6_addr.tcp_port)));
v4_tcp_multiaddr
.into_iter()
.chain(v4_quic_multiaddr)
.chain(v6_quic_multiaddr)
.chain(v6_tcp_multiaddr)
}
pub fn unused_v4_ports() -> Self {
ListenAddress::V4(ListenAddr {
addr: Ipv4Addr::UNSPECIFIED,
disc_port: crate::unused_port::unused_udp4_port().unwrap(),
quic_port: crate::unused_port::unused_udp4_port().unwrap(),
tcp_port: crate::unused_port::unused_tcp4_port().unwrap(),
})
}
pub fn unused_v6_ports() -> Self {
ListenAddress::V6(ListenAddr {
addr: Ipv6Addr::UNSPECIFIED,
disc_port: crate::unused_port::unused_udp6_port().unwrap(),
quic_port: crate::unused_port::unused_udp6_port().unwrap(),
tcp_port: crate::unused_port::unused_tcp6_port().unwrap(),
})
}
}

View File

@@ -0,0 +1,99 @@
use lru_cache::LRUTimeCache;
use parking_lot::Mutex;
use std::net::{SocketAddr, TcpListener, UdpSocket};
use std::sync::LazyLock;
use std::time::Duration;
#[derive(Copy, Clone)]
pub enum Transport {
Tcp,
Udp,
}
#[derive(Copy, Clone)]
pub enum IpVersion {
Ipv4,
Ipv6,
}
pub const CACHED_PORTS_TTL: Duration = Duration::from_secs(300);
static FOUND_PORTS_CACHE: LazyLock<Mutex<LRUTimeCache<u16>>> =
LazyLock::new(|| Mutex::new(LRUTimeCache::new(CACHED_PORTS_TTL)));
/// A convenience wrapper over [`zero_port`].
pub fn unused_tcp4_port() -> Result<u16, String> {
zero_port(Transport::Tcp, IpVersion::Ipv4)
}
/// A convenience wrapper over [`zero_port`].
pub fn unused_udp4_port() -> Result<u16, String> {
zero_port(Transport::Udp, IpVersion::Ipv4)
}
/// A convenience wrapper over [`zero_port`].
pub fn unused_tcp6_port() -> Result<u16, String> {
zero_port(Transport::Tcp, IpVersion::Ipv6)
}
/// A convenience wrapper over [`zero_port`].
pub fn unused_udp6_port() -> Result<u16, String> {
zero_port(Transport::Udp, IpVersion::Ipv6)
}
/// A bit of hack to find an unused port.
///
/// Does not guarantee that the given port is unused after the function exits, just that it was
/// unused before the function started (i.e., it does not reserve a port).
///
/// ## Notes
///
/// It is possible that users are unable to bind to the ports returned by this function as the OS
/// has a buffer period where it doesn't allow binding to the same port even after the socket is
/// closed. We might have to use SO_REUSEADDR socket option from `std::net2` crate in that case.
pub fn zero_port(transport: Transport, ipv: IpVersion) -> Result<u16, String> {
let localhost = match ipv {
IpVersion::Ipv4 => std::net::Ipv4Addr::LOCALHOST.into(),
IpVersion::Ipv6 => std::net::Ipv6Addr::LOCALHOST.into(),
};
let socket_addr = std::net::SocketAddr::new(localhost, 0);
let mut unused_port: u16;
loop {
unused_port = find_unused_port(transport, socket_addr)?;
let mut cache_lock = FOUND_PORTS_CACHE.lock();
if !cache_lock.contains(&unused_port) {
cache_lock.insert(unused_port);
break;
}
}
Ok(unused_port)
}
fn find_unused_port(transport: Transport, socket_addr: SocketAddr) -> Result<u16, String> {
let local_addr = match transport {
Transport::Tcp => {
let listener = TcpListener::bind(socket_addr).map_err(|e| {
format!("Failed to create TCP listener to find unused port: {:?}", e)
})?;
listener.local_addr().map_err(|e| {
format!(
"Failed to read TCP listener local_addr to find unused port: {:?}",
e
)
})?
}
Transport::Udp => {
let socket = UdpSocket::bind(socket_addr)
.map_err(|e| format!("Failed to create UDP socket to find unused port: {:?}", e))?;
socket.local_addr().map_err(|e| {
format!(
"Failed to read UDP socket local_addr to find unused port: {:?}",
e
)
})?
}
};
Ok(local_addr.port())
}