DNSSEC resolver, record encoding/decoding

Signed-off-by: Luca Fulchir <luca.fulchir@runesauth.com>
This commit is contained in:
Luca Fulchir 2023-02-08 19:03:31 +01:00
parent 3e4ef61edb
commit 3797ca869d
Signed by: luca.fulchir
GPG Key ID: 8F6440603D13A78E
4 changed files with 602 additions and 113 deletions

View File

@ -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]

136
src/dnssec/mod.rs Normal file
View File

@ -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<SocketAddr>) -> Result<Self, Error> {
// 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<SocketAddr>;
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<Record> {
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",
)
});
}
}

417
src/dnssec/record.rs Normal file
View File

@ -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<u8>,
/// 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<u8>) {
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<NonZeroU16>,
/// 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<u8>) {
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<PublicKey>,
/// List of all authentication servers' addresses.
/// Multiple ones can point to the same authentication server
addresses: Vec<Address>,
}
impl Record {
/// Simply encode all the record in base85
pub fn encode(&self) -> Result<String, Error> {
// 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::<usize>()
+ self.public_keys.iter().map(|a| a.raw_len()).sum::<usize>();
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<Self, Error> {
// 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),
}

View File

@ -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<UdpSocket>, JoinHandle<Result<()>>)>,
/// DNSSEC resolvers. multiple for failover
resolvers: Vec<(
DnssecClient,
JoinHandle<result::Result<(), ::trust_dns_proto::error::ProtoError>>,
)>,
sockets: Vec<(Arc<UdpSocket>, JoinHandle<::std::io::Result<()>>)>,
/// DNSSEC resolver, with failovers
dnssec: Option<dnssec::Dnssec>,
/// Broadcast channel to tell workers to stop working
stop_working: ::tokio::sync::broadcast::Sender<bool>,
}
@ -50,86 +57,56 @@ impl Drop for Fenrir {
impl Fenrir {
/// Create a new Fenrir endpoint
pub fn new(config: &Config) -> Result<Self> {
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<Self, Error> {
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<UdpSocket>, JoinHandle<Result<()>>)>,
resolvers: Vec<(
DnssecClient,
JoinHandle<
result::Result<(), ::trust_dns_proto::error::ProtoError>,
>,
)>,
sockets: Vec<(Arc<UdpSocket>, 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<TcpStream>,
>::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<dnssec::Record, Error> {
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<String> {
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<UdpSocket> {
async fn bind_udp(sock: SocketAddr) -> ::std::io::Result<UdpSocket> {
let socket = UdpSocket::bind(sock).await?;
// PERF: SO_REUSEADDR/REUSEPORT
Ok(socket)
@ -216,7 +145,7 @@ async fn bind_udp(sock: SocketAddr) -> Result<UdpSocket> {
async fn listen_udp(
mut stop_working: ::tokio::sync::broadcast::Receiver<bool>,
socket: Arc<UdpSocket>,
) -> Result<()> {
) -> ::std::io::Result<()> {
// jumbo frames are 9K max
let mut buffer: [u8; 9000] = [0; 9000];
loop {