diff --git a/Cargo.toml b/Cargo.toml index 90279d1..7c194df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,13 +25,20 @@ crate_type = [ "lib", "cdylib", "staticlib" ] [dependencies] # please keep these in alphabetical order +# base85 repo has no tags, fix on a commit. v1.1.1 points to older, wrong version +base85 = { git = "https://gitlab.com/darkwyrm/base85", rev = "d98efbfd171dd9ba48e30a5c88f94db92fc7b3c6" } futures = { version = "^0.3" } +num-traits = { version = "^0.2" } +num-derive = { version = "^0.3" } +strum = { version = "^0.24" } +strum_macros = { version = "^0.24" } thiserror = { version = "^1.0" } tokio = { version = "^1", features = ["full"] } # PERF: todo linux-only, behind "iouring" feature #tokio-uring = { version = "^0.4" } tracing = { version = "^0.1" } -trust-dns-client = { version = "^0.22" } +trust-dns-resolver = { version = "^0.22", features = [ "dnssec-ring" ] } +trust-dns-client = { version = "^0.22", features = [ "dnssec" ] } trust-dns-proto = { version = "^0.22" } [profile.dev] diff --git a/src/dnssec/mod.rs b/src/dnssec/mod.rs new file mode 100644 index 0000000..0d28d8b --- /dev/null +++ b/src/dnssec/mod.rs @@ -0,0 +1,136 @@ +//! Common dnssec utils + +use ::std::{net::SocketAddr, vec::Vec}; +use ::tracing::error; +use ::trust_dns_resolver::TokioAsyncResolver; + +pub mod record; +pub use record::Record; + +/// Common errors for Dnssec setup and usage +#[derive(::thiserror::Error, Debug)] +pub enum Error { + /// Error reading or parsing /etc/resolv.conf + #[error("resolv.conf: {0:?}")] + ResolvConf(String), + /// Error in the resolver setup + #[error("resolver setup: {0:?}")] + Setup(String), + /// Errors in establishing connection or connection drops + #[error("nameserver connection: {0:?}")] + NameserverConnection(String), +} + +#[cfg(any( + target_os = "linux", + target_os = "macos", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "openbsd", + target_os = "netbsd", +))] + +/// Easy Dnssec handling for Fenrir +#[allow(missing_debug_implementations)] +pub struct Dnssec { + /// real resolver + resolver: TokioAsyncResolver, +} + +impl Dnssec { + /// Spawn connections to DNS via TCP + pub async fn new(resolvers: &Vec) -> Result { + // use a TCP connection to the DNS. + // the records we need are big, will not fit in a UDP packet + let resolv_conf_resolvers: Vec; + let resolvers = if resolvers.len() > 0 { + resolvers + } else { + // load from system's /etc/resolv.conf + let resolv_conf = + match ::trust_dns_resolver::system_conf::read_system_conf() { + Ok((resolv_conf, _)) => resolv_conf, + Err(e) => return Err(Error::ResolvConf(e.to_string())), + }; + resolv_conf_resolvers = resolv_conf + .name_servers() + .iter() + .map(|r| r.socket_addr) + .collect(); + + &resolv_conf_resolvers + }; + use ::trust_dns_resolver::config::{ResolverConfig, ResolverOpts}; + let mut opts = ResolverOpts::default(); + opts.edns0 = true; + opts.validate = true; + opts.use_hosts_file = false; + opts.try_tcp_on_error = true; + let opts = opts; // no more mut + + // default() uses google's dns: don't use + let mut config = ResolverConfig::new(); + + for resolver in resolvers.iter() { + use ::trust_dns_resolver::config::{NameServerConfig, Protocol}; + config.add_name_server(NameServerConfig::new( + resolver.clone(), + Protocol::Tcp, + )); + } + + let resolver = match TokioAsyncResolver::tokio(config, opts) { + Ok(resolver) => resolver, + Err(e) => return Err(Error::Setup(e.to_string())), + }; + + Ok(Self { resolver }) + } + /// Get the fenrir data for a domain + pub async fn resolv(&self, domain: &str) -> ::std::io::Result { + use ::trust_dns_client::rr::Name; + + let fqdn_str = "_fenrir.".to_owned() + domain; + ::tracing::debug!("Resolving: {}", fqdn_str); + let fqdn = Name::from_utf8(&fqdn_str)?; + let answers = self.resolver.txt_lookup(fqdn).await?; + const TXT_RECORD_START: &str = "v=Fenrir1 "; + let mut found_but_wrong: bool = false; + for txt_raw in answers.into_iter() { + let txt = ::std::format!("{}", txt_raw); + if !txt.starts_with(TXT_RECORD_START) { + ::tracing::trace!("Found NON-fenrir record: {}", txt); + continue; + } + ::tracing::trace!("Found fenrir record: {}", txt); + let base85 = txt[TXT_RECORD_START.len()..].trim(); + let bytes = match ::base85::decode(base85) { + Ok(bytes) => bytes, + Err(e) => { + ::tracing::error!("Invalid DNSSEC base85: {}", e); + continue; + } + }; + let record = match Record::decode(&bytes) { + Ok(record) => record, + Err(e) => { + found_but_wrong = true; + ::tracing::error!("{}: {}", fqdn_str, e); + continue; + } + }; + return Ok(record); + } + return Err(if found_but_wrong { + ::std::io::Error::new( + ::std::io::ErrorKind::InvalidData, + "record found but not parsable", + ) + } else { + ::std::io::Error::new( + ::std::io::ErrorKind::NotFound, + "record not found", + ) + }); + } +} diff --git a/src/dnssec/record.rs b/src/dnssec/record.rs new file mode 100644 index 0000000..5669876 --- /dev/null +++ b/src/dnssec/record.rs @@ -0,0 +1,417 @@ +//! +//! Structs and information to create/parse the _fenrir DNSSEC record +//! +//! Encoding and decoding in base85, RFC1924 +//! +//! +//! Basic encoding idea: +//! * 1 byte: half-bytes +//! * half: num of addresses +//! * half: num of pubkeys +//! [ # list of addresses +//! * 1 byte: bitfield +//! * 0..1 ipv4/ipv6 +//! * 2..4 priority (for failover) +//! * 5..7 weight between priority +//! * 1 byte: public key id +//! * 2 bytes: UDP port +//! * X bytes: IP +//! ] +//! [ # list of pubkeys +//! * 1 byte: pubkey type +//! * 1 byte: pubkey id +//! * Y bytes: pubkey +//! ] + +use ::core::num::NonZeroU16; +use ::num_traits::FromPrimitive; +use ::std::{net::IpAddr, vec::Vec}; + +/* + * Public key data + */ + +/// Public Key ID +#[derive(Debug, Copy, Clone)] +pub struct PublicKeyId(u8); + +/// Public Key Type +#[derive(::num_derive::FromPrimitive, Debug, Copy, Clone)] +// public enum: use non_exhaustive to force users to add a default case +// so in the future we can expand this easily +#[non_exhaustive] +#[repr(u8)] +pub enum PublicKeyType { + /// ed25519 asymmetric key + Ed25519 = 0, +} + +impl PublicKeyType { + /// Get the size of a public key of this kind + pub fn key_len(&self) -> usize { + match &self { + PublicKeyType::Ed25519 => 42, + } + } +} + +/// Public Key, with its type and id +#[derive(Debug, Clone)] +pub struct PublicKey { + /// public key raw data + raw: Vec, + /// type of public key + kind: PublicKeyType, + /// id of public key + id: PublicKeyId, +} + +impl PublicKey { + fn raw_len(&self) -> usize { + let size = 2; // Public Key Type + ID + size + self.raw.len() + } + fn encode_into(&self, raw: &mut Vec) { + raw.push(self.kind as u8); + raw.push(self.id.0); + raw.extend_from_slice(&self.raw); + } + fn decode_raw(raw: &[u8]) -> Result<(Self, usize), Error> { + if raw.len() < 4 { + return Err(Error::NotEnoughData(0)); + } + + let kind = PublicKeyType::from_u8(raw[0]).unwrap(); + let id = PublicKeyId(raw[1]); + if raw.len() < 2 + kind.key_len() { + return Err(Error::NotEnoughData(2)); + } + + let mut raw_key = Vec::with_capacity(kind.key_len()); + raw_key.extend_from_slice(&raw[2..(2 + kind.key_len())]); + + let total_length = 2 + kind.key_len(); + + Ok(( + Self { + raw: raw_key, + kind, + id, + }, + total_length, + )) + } +} + +/* + * Address data + */ + +/// Priority of each group of addresses +#[derive(::num_derive::FromPrimitive, Debug, Copy, Clone)] +// public enum: use non_exhaustive to force users to add a default case +// so in the future we can expand this easily +#[non_exhaustive] +#[repr(u8)] +pub enum AddressPriority { + /// Initially contact addresses in this priority + P0 = 0, + /// First failover + P1, + /// Second failover + P2, + /// Third failover + P3, + /// Fourth failover + P4, + /// Fifth failover + P5, + /// Sisth failover + P6, + /// Seventh failover + P7, +} + +/// Inside of each group, weight of the address +/// This helps in distributing the traffic to multiple authentication servers: +/// * client sums all weights in a group +/// * generate a random number [0..sum_of_weights] +/// * the number indicates which server will take the connection +/// So to equally distribute all connections you just have to use the same +/// weight in the same group +#[derive(::num_derive::FromPrimitive, Debug, Copy, Clone)] +// public enum: use non_exhaustive to force users to add a default case +// so in the future we can expand this easily +#[non_exhaustive] +#[repr(u8)] +pub enum AddressWeight { + /// Minimum weigth: 1 + W1 = 0, + /// little weigth: 2 + W2, + /// little weigth: 3 + W3, + /// medium weigth: 4 + W4, + /// medium weigth: 5 + W5, + /// heavy weigth: 6 + W6, + /// heavy weigth: 7 + W7, + /// Maximum weigth: 8 + W8, +} + +/// Authentication server address information: +/// * ip +/// * udp port +/// * priority +/// * weight within priority +#[derive(Debug, Clone, Copy)] +pub struct Address { + /// Ip address of server, v4 or v6 + pub ip: IpAddr, + /// udp port. None means that this address is reachable only + /// with Fenrir over IP + pub port: Option, + /// Priority group of this address + pub priority: AddressPriority, + /// Weight of this address in the priority group + pub weight: AddressWeight, + /// Public key ID used by this address + pub public_key_id: PublicKeyId, +} + +impl Address { + fn raw_len(&self) -> usize { + let size = 4; // UDP port + Priority + Weight + match self.ip { + IpAddr::V4(_) => size + 4, + IpAddr::V6(_) => size + 16, + } + } + fn encode_into(&self, raw: &mut Vec) { + let mut bitfield: u8 = match self.ip { + IpAddr::V4(_) => 0, + IpAddr::V6(_) => 1, + }; + bitfield <<= 3; + bitfield |= self.priority as u8; + bitfield <<= 3; + bitfield |= self.weight as u8; + + raw.push(bitfield); + + raw.push(self.public_key_id.0); + raw.extend_from_slice( + &(match self.port { + Some(port) => port.get().to_le_bytes(), + None => [0, 0], // oh noez, which zero goes first? + }), + ); + + match self.ip { + IpAddr::V4(ip) => { + let raw_ip = ip.octets(); + raw.extend_from_slice(&raw_ip); + } + IpAddr::V6(ip) => { + let raw_ip = ip.octets(); + raw.extend_from_slice(&raw_ip); + } + }; + } + fn decode_raw(raw: &[u8]) -> Result<(Self, usize), Error> { + if raw.len() < 8 { + return Err(Error::NotEnoughData(0)); + } + let ip_type = raw[0] >> 6; + let is_ipv6: bool; + let total_length: usize; + match ip_type { + 0 => { + is_ipv6 = false; + total_length = 8; + } + 1 => { + total_length = 20; + if raw.len() < total_length { + return Err(Error::NotEnoughData(1)); + } + is_ipv6 = true + } + _ => return Err(Error::UnsupportedData(0)), + } + let raw_priority = (raw[0] << 2) >> 5; + let raw_weight = (raw[0] << 5) >> 5; + let priority = AddressPriority::from_u8(raw_priority).unwrap(); + let weight = AddressWeight::from_u8(raw_weight).unwrap(); + + let public_key_id = PublicKeyId(raw[1]); + + let raw_port = u16::from_le_bytes(raw[2..3].try_into().unwrap()); + let port = if raw_port == 0 { + None + } else { + Some(NonZeroU16::new(raw_port).unwrap()) + }; + let ip = if is_ipv6 { + let raw_ip: [u8; 16] = raw[4..20].try_into().unwrap(); + IpAddr::from(raw_ip) + } else { + let raw_ip: [u8; 4] = raw[4..8].try_into().unwrap(); + IpAddr::from(raw_ip) + }; + + Ok(( + Self { + ip, + port, + priority, + weight, + public_key_id, + }, + total_length, + )) + } +} + +/* + * Actual record puuting it all toghether + */ + +/// All informations found in the DNSSEC record +#[derive(Debug, Clone)] +pub struct Record { + /// Public keys used by any authentication server + public_keys: Vec, + /// List of all authentication servers' addresses. + /// Multiple ones can point to the same authentication server + addresses: Vec
, +} + +impl Record { + /// Simply encode all the record in base85 + pub fn encode(&self) -> Result { + // check possible failure scenarios + if self.public_keys.len() == 0 { + return Err(Error::NoPublicKeyFound); + } + if self.public_keys.len() > 16 { + return Err(Error::Max16PublicKeys); + } + if self.addresses.len() == 0 { + return Err(Error::NoAddressFound); + } + if self.addresses.len() > 16 { + return Err(Error::Max16Addresses); + } + // everything else is all good + + let total_size: usize = 1 + + self.addresses.iter().map(|a| a.raw_len()).sum::() + + self.public_keys.iter().map(|a| a.raw_len()).sum::(); + + let mut raw = Vec::with_capacity(total_size); + + // amount of data. addresses, then pubkeys. 4 bits each + let len_combined: u8 = self.addresses.len() as u8; + let len_combined = len_combined << 4; + let len_combined = len_combined | self.public_keys.len() as u8; + + raw.push(len_combined); + + for address in self.addresses.iter() { + address.encode_into(&mut raw); + } + for public_key in self.public_keys.iter() { + public_key.encode_into(&mut raw); + } + + Ok(::base85::encode(&raw)) + } + /// Decode from base85 to the actual object + pub fn decode(raw: &[u8]) -> Result { + // bare minimum for 1 address and key + const MIN_RAW_LENGTH: usize = 1 + 8 + 8; + if raw.len() <= MIN_RAW_LENGTH { + return Err(Error::NotEnoughData(0)); + } + let mut num_addresses = (raw[0] >> 4) as usize; + let mut num_public_keys = (raw[0] & 0x0F) as usize; + let mut bytes_parsed = 1; + + let mut result = Self { + addresses: Vec::with_capacity(num_addresses), + public_keys: Vec::with_capacity(num_public_keys), + }; + + while num_addresses > 0 { + let (address, bytes) = + match Address::decode_raw(&raw[bytes_parsed..]) { + Ok(address) => address, + Err(Error::UnsupportedData(b)) => { + return Err(Error::UnsupportedData(bytes_parsed + b)) + } + Err(Error::NotEnoughData(b)) => { + return Err(Error::NotEnoughData(bytes_parsed + b)) + } + Err(e) => return Err(e), + }; + bytes_parsed = bytes_parsed + bytes; + result.addresses.push(address); + num_addresses = num_addresses - 1; + } + while num_public_keys > 0 { + let (public_key, bytes) = + match PublicKey::decode_raw(&raw[bytes_parsed..]) { + Ok(public_key) => public_key, + Err(Error::UnsupportedData(b)) => { + return Err(Error::UnsupportedData(bytes_parsed + b)) + } + Err(Error::NotEnoughData(b)) => { + return Err(Error::NotEnoughData(bytes_parsed + b)) + } + Err(e) => return Err(e), + }; + bytes_parsed = bytes_parsed + bytes; + result.public_keys.push(public_key); + num_public_keys = num_public_keys - 1; + } + if bytes_parsed != raw.len() { + Err(Error::UnknownData(bytes_parsed)) + } else { + Ok(result) + } + } +} + +/// Possible errors in encoding or decoding the DNSSEC record +#[derive(::thiserror::Error, Debug)] +pub enum Error { + /// General IO error + #[error("IO error: {0:?}")] + IO(#[from] ::std::io::Error), + /// We need at least one address + #[error("no addresses found")] + NoAddressFound, + /// Too many addresses (max 16) + #[error("can't encode more than 16 addresses")] + Max16Addresses, + /// We need at least one public key + #[error("no public keys found")] + NoPublicKeyFound, + /// Maximum 16 public keys supported + #[error("can't encode more than 16 public keys")] + Max16PublicKeys, + /// Not enough data to decode something meaningful + #[error("not enough data. Parsed {0} bytes")] + NotEnoughData(usize), + /// Unsupported Data: can't parse + #[error("Unsupported data. Parsed {0} bytes")] + UnsupportedData(usize), + /// Unknown data at the end + #[error("Unknown data after {0} bytes")] + UnknownData(usize), +} diff --git a/src/lib.rs b/src/lib.rs index ae8eca5..ea22a8b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,29 +13,36 @@ //! //! libFenrir is the official rust library implementing the Fenrir protocol -use ::std::{io::Result, net::SocketAddr, result, sync::Arc}; -use ::tokio::{net::UdpSocket, runtime, task::JoinHandle}; -use trust_dns_client::client::{ - AsyncClient as DnssecClient, ClientHandle as DnssecHandle, -}; +use ::std::{net::SocketAddr, sync::Arc}; +use ::tokio::{net::UdpSocket, task::JoinHandle}; mod config; pub use config::Config; +pub mod dnssec; + +/// Main fenrir library errors +#[derive(::thiserror::Error, Debug)] +pub enum Error { + /// General I/O error + #[error("IO: {0:?}")] + IO(#[from] ::std::io::Error), + /// Dnssec errors + #[error("Dnssec: {0:?}")] + Dnssec(#[from] dnssec::Error), + /// The library was not initialized (run .start()) + #[error("not initialized")] + NotInitialized, +} /// Instance of a fenrir endpoint #[allow(missing_copy_implementations, missing_debug_implementations)] pub struct Fenrir { /// library Configuration cfg: Config, - /// internal runtime - rt: ::tokio::runtime::Runtime, /// listening udp sockets - sockets: Vec<(Arc, JoinHandle>)>, - /// DNSSEC resolvers. multiple for failover - resolvers: Vec<( - DnssecClient, - JoinHandle>, - )>, + sockets: Vec<(Arc, JoinHandle<::std::io::Result<()>>)>, + /// DNSSEC resolver, with failovers + dnssec: Option, /// Broadcast channel to tell workers to stop working stop_working: ::tokio::sync::broadcast::Sender, } @@ -50,86 +57,56 @@ impl Drop for Fenrir { impl Fenrir { /// Create a new Fenrir endpoint - pub fn new(config: &Config) -> Result { - let mut builder = runtime::Builder::new_multi_thread(); - if let Some(threads) = config.threads { - builder.worker_threads(threads.get()); - } - let rt = match builder.thread_name("libFenrir").build() { - Ok(rt) => rt, - Err(e) => { - ::tracing::error!("Can't initialize: {}", e); - return Err(e); - } - }; - // start listening + pub fn new(config: &Config) -> Result { let listen_num = config.listen.len(); - let resolvers_num = config.resolvers.len(); let (sender, _) = ::tokio::sync::broadcast::channel(1); let endpoint = Fenrir { cfg: config.clone(), - rt: rt, sockets: Vec::with_capacity(listen_num), - resolvers: Vec::with_capacity(resolvers_num), + dnssec: None, stop_working: sender, }; Ok(endpoint) } /// Start all workers, listeners - pub async fn start(&mut self) -> Result<()> { + pub async fn start(&mut self) -> Result<(), Error> { if let Err(e) = self.add_sockets().await { self.stop().await; - return Err(e); - } - if let Err(e) = self.setup_dnssec().await { - self.stop().await; - return Err(e); + return Err(e.into()); } + self.dnssec = Some(dnssec::Dnssec::new(&self.cfg.resolvers).await?); Ok(()) } /// Stop all workers, listeners /// asyncronous version for Drop fn stop_sync(&mut self) { - let mut toempty_resolv = Vec::new(); let mut toempty_socket = Vec::new(); - ::std::mem::swap(&mut self.resolvers, &mut toempty_resolv); ::std::mem::swap(&mut self.sockets, &mut toempty_socket); - self.rt - .block_on(Self::real_stop(toempty_socket, toempty_resolv)); + let task = ::tokio::task::spawn(Self::real_stop(toempty_socket)); + let _ = futures::executor::block_on(task); + self.dnssec = None; } /// Stop all workers, listeners pub async fn stop(&mut self) { - // TODO let _ = self.stop_working.send(true); - let mut toempty_resolv = Vec::new(); let mut toempty_socket = Vec::new(); - ::std::mem::swap(&mut self.resolvers, &mut toempty_resolv); ::std::mem::swap(&mut self.sockets, &mut toempty_socket); - Self::real_stop(toempty_socket, toempty_resolv).await; + Self::real_stop(toempty_socket).await; + self.dnssec = None; } /// actually do the work of stopping resolvers and listeners async fn real_stop( - sockets: Vec<(Arc, JoinHandle>)>, - resolvers: Vec<( - DnssecClient, - JoinHandle< - result::Result<(), ::trust_dns_proto::error::ProtoError>, - >, - )>, + sockets: Vec<(Arc, JoinHandle<::std::io::Result<()>>)>, ) { - for r in resolvers.into_iter() { - let _ = r.1.await; - } for s in sockets.into_iter() { let _ = s.1.await; } } /// Add all UDP sockets found in config /// and start listening for packets - async fn add_sockets(&mut self) -> Result<()> { + async fn add_sockets(&mut self) -> ::std::io::Result<()> { let sockets = self.cfg.listen.iter().map(|s_addr| async { - //let socket = self.rt.block_on(bind_udp(s_addr.clone()))?; - let socket = self.rt.spawn(bind_udp(s_addr.clone())).await??; + let socket = ::tokio::spawn(bind_udp(s_addr.clone())).await??; Ok(Arc::new(socket)) }); let sockets = ::futures::future::join_all(sockets).await; @@ -138,7 +115,7 @@ impl Fenrir { Ok(s) => { let stop_working = self.stop_working.subscribe(); let join = - self.rt.spawn(listen_udp(stop_working, s.clone())); + ::tokio::spawn(listen_udp(stop_working, s.clone())); self.sockets.push((s, join)); } Err(e) => { @@ -148,65 +125,17 @@ impl Fenrir { } Ok(()) } - /// Add and initialize the DNSSEC resolvers to the endpoint - async fn setup_dnssec(&mut self) -> Result<()> { - // use a TCP connection to the DNS. - // the records we need are big, will not fit in a UDP packet - use tokio::net::TcpStream; - use trust_dns_client::{ - client::AsyncClient, proto::iocompat::AsyncIoTokioAsStd, - tcp::TcpClientStream, - }; - - for resolver in self.cfg.resolvers.iter() { - let (stream, sender) = TcpClientStream::< - AsyncIoTokioAsStd, - >::new(resolver.clone()); - let client_tostart = AsyncClient::new(stream, sender, None); - // await the connection to be established - let (client, bg) = client_tostart.await?; - - let handle = self.rt.spawn(bg); - self.resolvers.push((client, handle)); + /// Get the raw TXT record of a Fenrir domain + pub async fn resolv(&self, domain: &str) -> Result { + match &self.dnssec { + Some(dnssec) => Ok(dnssec.resolv(domain).await?), + None => Err(Error::NotInitialized), } - Ok(()) - } - /// get the fenrir data for a domain - pub async fn resolv(&mut self, domain: &str) -> Result { - use std::str::FromStr; - use trust_dns_client::rr::{DNSClass, Name, RData, RecordType}; - - let fullname = "_fenrir".to_owned() + domain; - for client in self.resolvers.iter_mut() { - let query = client.0.query( - Name::from_str(&fullname).unwrap(), - DNSClass::IN, - RecordType::TXT, - ); - - // wait for its response - let response = query.await?; - - // validate it's what we expected - match response.answers()[0].data() { - Some(RData::TXT(text)) => return Ok(text.to_string()), - _ => { - return Err(::std::io::Error::new( - ::std::io::ErrorKind::NotFound, - "No TXT record found", - )) - } - } - } - Err(::std::io::Error::new( - ::std::io::ErrorKind::NotConnected, - "No DNS server usable", - )) } } /// Add an async udp listener -async fn bind_udp(sock: SocketAddr) -> Result { +async fn bind_udp(sock: SocketAddr) -> ::std::io::Result { let socket = UdpSocket::bind(sock).await?; // PERF: SO_REUSEADDR/REUSEPORT Ok(socket) @@ -216,7 +145,7 @@ async fn bind_udp(sock: SocketAddr) -> Result { async fn listen_udp( mut stop_working: ::tokio::sync::broadcast::Receiver, socket: Arc, -) -> Result<()> { +) -> ::std::io::Result<()> { // jumbo frames are 9K max let mut buffer: [u8; 9000] = [0; 9000]; loop {