Files
firezone/rust/connlib/tunnel/src/sockets.rs
Thomas Eizinger 73eebd2c4d refactor(rust): consistently record errors as tracing::Value (#7104)
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
2024-10-22 04:46:26 +00:00

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
}
}