mirror of
https://github.com/outbackdingo/firezone.git
synced 2026-01-27 10:18:54 +00:00
Our logging library, `tracing` supports structured logging. This is useful because it preserves the more than just the string representation of a value and thus allows the active logging backend(s) to capture more information for a particular value. In the case of errors, this is especially useful because it allows us to capture the sources of a particular error. Unfortunately, recording an error as a tracing value is a bit cumbersome because `tracing::Value` is only implemented for `&dyn std::error::Error`. Casting an error to this is quite verbose. To make it easier, we introduce two utility functions in `firezone-logging`: - `std_dyn_err` - `anyhow_dyn_err` Tracking errors as correct `tracing::Value`s will be especially helpful once we enable Sentry's `tracing` integration: https://docs.rs/sentry-tracing/latest/sentry_tracing/#tracking-errors
150 lines
4.2 KiB
Rust
150 lines
4.2 KiB
Rust
use firezone_logging::std_dyn_err;
|
|
use socket_factory::{DatagramIn, DatagramOut, SocketFactory, UdpSocket};
|
|
use std::{
|
|
io,
|
|
net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6},
|
|
task::{ready, Context, Poll, Waker},
|
|
};
|
|
|
|
const UNSPECIFIED_V4_SOCKET: SocketAddrV4 = SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0);
|
|
const UNSPECIFIED_V6_SOCKET: SocketAddrV6 = SocketAddrV6::new(Ipv6Addr::UNSPECIFIED, 0, 0, 0);
|
|
|
|
#[derive(Default)]
|
|
pub(crate) struct Sockets {
|
|
waker: Option<Waker>,
|
|
|
|
socket_v4: Option<UdpSocket>,
|
|
socket_v6: Option<UdpSocket>,
|
|
}
|
|
|
|
impl Sockets {
|
|
pub fn rebind(&mut self, socket_factory: &dyn SocketFactory<UdpSocket>) {
|
|
self.socket_v4 = socket_factory(&SocketAddr::V4(UNSPECIFIED_V4_SOCKET))
|
|
.inspect_err(|e| tracing::warn!(error = std_dyn_err(e), "Failed to bind IPv4 socket"))
|
|
.ok();
|
|
self.socket_v6 = socket_factory(&SocketAddr::V6(UNSPECIFIED_V6_SOCKET))
|
|
.inspect_err(|e| tracing::warn!(error = std_dyn_err(e), "Failed to bind IPv6 socket"))
|
|
.ok();
|
|
|
|
if let Some(waker) = self.waker.take() {
|
|
waker.wake();
|
|
}
|
|
}
|
|
|
|
pub fn poll_has_sockets(&mut self, cx: &mut Context<'_>) -> Poll<()> {
|
|
if self.socket_v4.is_none() && self.socket_v6.is_none() {
|
|
let previous = self.waker.replace(cx.waker().clone());
|
|
|
|
if previous.is_none() {
|
|
// If we didn't have a waker yet, it means we just lost our sockets. Let the user know everything will be suspended.
|
|
tracing::error!("No available UDP sockets")
|
|
}
|
|
|
|
return Poll::Pending;
|
|
}
|
|
|
|
Poll::Ready(())
|
|
}
|
|
|
|
pub fn poll_send_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
|
if let Some(socket) = self.socket_v4.as_ref() {
|
|
ready!(socket.poll_send_ready(cx))?;
|
|
}
|
|
|
|
if let Some(socket) = self.socket_v6.as_ref() {
|
|
ready!(socket.poll_send_ready(cx))?;
|
|
}
|
|
|
|
Poll::Ready(Ok(()))
|
|
}
|
|
|
|
pub fn send(&mut self, datagram: DatagramOut) -> io::Result<()> {
|
|
let socket = match datagram.dst {
|
|
SocketAddr::V4(dst) => self.socket_v4.as_mut().ok_or_else(|| {
|
|
io::Error::new(
|
|
io::ErrorKind::NotConnected,
|
|
format!("failed send packet to {dst}: no IPv4 socket"),
|
|
)
|
|
})?,
|
|
SocketAddr::V6(dst) => self.socket_v6.as_mut().ok_or_else(|| {
|
|
io::Error::new(
|
|
io::ErrorKind::NotConnected,
|
|
format!("failed send packet to {dst}: no IPv6 socket"),
|
|
)
|
|
})?,
|
|
};
|
|
socket.send(datagram)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn poll_recv_from<'b>(
|
|
&mut self,
|
|
ip4_buffer: &'b mut [u8],
|
|
ip6_buffer: &'b mut [u8],
|
|
cx: &mut Context<'_>,
|
|
) -> Poll<io::Result<impl Iterator<Item = DatagramIn<'b>>>> {
|
|
let mut iter = PacketIter::new();
|
|
|
|
if let Some(Poll::Ready(packets)) = self
|
|
.socket_v4
|
|
.as_ref()
|
|
.map(|s| s.poll_recv_from(ip4_buffer, cx))
|
|
{
|
|
iter.ip4 = Some(packets?);
|
|
}
|
|
|
|
if let Some(Poll::Ready(packets)) = self
|
|
.socket_v6
|
|
.as_ref()
|
|
.map(|s| s.poll_recv_from(ip6_buffer, cx))
|
|
{
|
|
iter.ip6 = Some(packets?);
|
|
}
|
|
|
|
if iter.is_empty() {
|
|
return Poll::Pending;
|
|
}
|
|
|
|
Poll::Ready(Ok(iter))
|
|
}
|
|
}
|
|
|
|
struct PacketIter<T4, T6> {
|
|
ip4: Option<T4>,
|
|
ip6: Option<T6>,
|
|
}
|
|
|
|
impl<T4, T6> PacketIter<T4, T6> {
|
|
fn new() -> Self {
|
|
Self {
|
|
ip4: None,
|
|
ip6: None,
|
|
}
|
|
}
|
|
|
|
fn is_empty(&self) -> bool {
|
|
self.ip4.is_none() && self.ip6.is_none()
|
|
}
|
|
}
|
|
|
|
impl<'a, T4, T6> Iterator for PacketIter<T4, T6>
|
|
where
|
|
T4: Iterator<Item = DatagramIn<'a>>,
|
|
T6: Iterator<Item = DatagramIn<'a>>,
|
|
{
|
|
type Item = DatagramIn<'a>;
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
if let Some(packet) = self.ip4.as_mut().and_then(|i| i.next()) {
|
|
return Some(packet);
|
|
}
|
|
|
|
if let Some(packet) = self.ip6.as_mut().and_then(|i| i.next()) {
|
|
return Some(packet);
|
|
}
|
|
|
|
None
|
|
}
|
|
}
|