Skip to content

Commit c82d4e4

Browse files
feat!: use concrete errors (#13)
Replaces `anyhow` errors with concrete error types. One more step towards n0-computer/iroh#2741
1 parent 54eaefe commit c82d4e4

17 files changed

+607
-323
lines changed

Cargo.lock

+286-166
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

netwatch/Cargo.toml

+3-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ rust-version = "1.81"
1616
workspace = true
1717

1818
[dependencies]
19-
anyhow = { version = "1" }
2019
atomic-waker = "1.1.2"
2120
bytes = "1.7"
2221
futures-lite = "2.5"
@@ -58,11 +57,13 @@ rtnetlink = "=0.13.1" # pinned because of https://github.com/rust-netlink/rtnetl
5857

5958
[target.'cfg(target_os = "windows")'.dependencies]
6059
wmi = "0.14"
61-
windows = { version = "0.58", features = ["Win32_NetworkManagement_IpHelper", "Win32_Foundation", "Win32_NetworkManagement_Ndis", "Win32_Networking_WinSock"] }
60+
windows = { version = "0.59", features = ["Win32_NetworkManagement_IpHelper", "Win32_Foundation", "Win32_NetworkManagement_Ndis", "Win32_Networking_WinSock"] }
61+
windows-result = "0.3"
6262
serde = { version = "1", features = ["derive"] }
6363
derive_more = { version = "1.0.0", features = ["debug"] }
6464

6565
[dev-dependencies]
66+
testresult = "0.4.1"
6667
tokio = { version = "1", features = [
6768
"io-util",
6869
"sync",

netwatch/src/interfaces/linux.rs

+31-15
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
//! Linux-specific network interfaces implementations.
22
3-
use anyhow::{anyhow, Result};
43
#[cfg(not(target_os = "android"))]
54
use futures_util::TryStreamExt;
65
use tokio::{
@@ -10,6 +9,26 @@ use tokio::{
109

1110
use super::DefaultRouteDetails;
1211

12+
#[derive(Debug, thiserror::Error)]
13+
pub enum Error {
14+
#[error("IO {0}")]
15+
Io(#[from] std::io::Error),
16+
#[cfg(not(target_os = "android"))]
17+
#[error("no netlink response")]
18+
NoResponse,
19+
#[cfg(not(target_os = "android"))]
20+
#[error("interface not found")]
21+
InterfaceNotFound,
22+
#[error("iface field is missing")]
23+
MissingIfaceField,
24+
#[error("destination field is missing")]
25+
MissingDestinationField,
26+
#[error("mask field is missing")]
27+
MissingMaskField,
28+
#[error("netlink")]
29+
Netlink(#[from] rtnetlink::Error),
30+
}
31+
1332
pub async fn default_route() -> Option<DefaultRouteDetails> {
1433
let route = default_route_proc().await;
1534
if let Ok(route) = route {
@@ -27,7 +46,7 @@ pub async fn default_route() -> Option<DefaultRouteDetails> {
2746

2847
const PROC_NET_ROUTE_PATH: &str = "/proc/net/route";
2948

30-
async fn default_route_proc() -> Result<Option<DefaultRouteDetails>> {
49+
async fn default_route_proc() -> Result<Option<DefaultRouteDetails>, Error> {
3150
const ZERO_ADDR: &str = "00000000";
3251
let file = File::open(PROC_NET_ROUTE_PATH).await?;
3352

@@ -50,9 +69,9 @@ async fn default_route_proc() -> Result<Option<DefaultRouteDetails>> {
5069
continue;
5170
}
5271
let mut fields = line.split_ascii_whitespace();
53-
let iface = fields.next().ok_or(anyhow!("iface field missing"))?;
54-
let destination = fields.next().ok_or(anyhow!("destination field missing"))?;
55-
let mask = fields.nth(5).ok_or(anyhow!("mask field missing"))?;
72+
let iface = fields.next().ok_or(Error::MissingIfaceField)?;
73+
let destination = fields.next().ok_or(Error::MissingDestinationField)?;
74+
let mask = fields.nth(5).ok_or(Error::MissingMaskField)?;
5675
// if iface.starts_with("tailscale") || iface.starts_with("wg") {
5776
// continue;
5877
// }
@@ -70,15 +89,15 @@ async fn default_route_proc() -> Result<Option<DefaultRouteDetails>> {
7089
/// We use this on Android where /proc/net/route can be missing entries or have locked-down
7190
/// permissions. See also comments in <https://github.com/tailscale/tailscale/pull/666>.
7291
#[cfg(target_os = "android")]
73-
pub async fn default_route_android_ip_route() -> Result<Option<DefaultRouteDetails>> {
92+
pub async fn default_route_android_ip_route() -> Result<Option<DefaultRouteDetails>, Error> {
7493
use tokio::process::Command;
7594

7695
let output = Command::new("/system/bin/ip")
7796
.args(["route", "show", "table", "0"])
7897
.kill_on_drop(true)
7998
.output()
8099
.await?;
81-
let stdout = std::str::from_utf8(&output.stdout)?;
100+
let stdout = std::string::String::from_utf8_lossy(&output.stdout);
82101
let details = parse_android_ip_route(&stdout).map(|iface| DefaultRouteDetails {
83102
interface_name: iface.to_string(),
84103
});
@@ -104,7 +123,7 @@ fn parse_android_ip_route(stdout: &str) -> Option<&str> {
104123
}
105124

106125
#[cfg(not(target_os = "android"))]
107-
async fn default_route_netlink() -> Result<Option<DefaultRouteDetails>> {
126+
async fn default_route_netlink() -> Result<Option<DefaultRouteDetails>, Error> {
108127
use tracing::{info_span, Instrument};
109128

110129
let (connection, handle, _receiver) = rtnetlink::new_connection()?;
@@ -127,7 +146,7 @@ async fn default_route_netlink() -> Result<Option<DefaultRouteDetails>> {
127146
async fn default_route_netlink_family(
128147
handle: &rtnetlink::Handle,
129148
family: rtnetlink::IpVersion,
130-
) -> Result<Option<(String, u32)>> {
149+
) -> Result<Option<(String, u32)>, Error> {
131150
use netlink_packet_route::route::RouteAttribute;
132151

133152
let mut routes = handle.route().get(family).execute();
@@ -165,21 +184,18 @@ async fn default_route_netlink_family(
165184
}
166185

167186
#[cfg(not(target_os = "android"))]
168-
async fn iface_by_index(handle: &rtnetlink::Handle, index: u32) -> Result<String> {
187+
async fn iface_by_index(handle: &rtnetlink::Handle, index: u32) -> Result<String, Error> {
169188
use netlink_packet_route::link::LinkAttribute;
170189

171190
let mut links = handle.link().get().match_index(index).execute();
172-
let msg = links
173-
.try_next()
174-
.await?
175-
.ok_or_else(|| anyhow!("No netlink response"))?;
191+
let msg = links.try_next().await?.ok_or(Error::NoResponse)?;
176192

177193
for nla in msg.attributes {
178194
if let LinkAttribute::IfName(name) = nla {
179195
return Ok(name);
180196
}
181197
}
182-
Err(anyhow!("Interface name not found"))
198+
Err(Error::InterfaceNotFound)
183199
}
184200

185201
#[cfg(test)]

netwatch/src/interfaces/windows.rs

+12-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,17 @@ struct Win32_IP4RouteTable {
1313
Name: String,
1414
}
1515

16-
fn get_default_route() -> anyhow::Result<DefaultRouteDetails> {
16+
#[derive(Debug, thiserror::Error)]
17+
pub enum Error {
18+
#[error("IO {0}")]
19+
Io(#[from] std::io::Error),
20+
#[error("not route found")]
21+
NoRoute,
22+
#[error("WMI {0}")]
23+
Wmi(#[from] wmi::WMIError),
24+
}
25+
26+
fn get_default_route() -> Result<DefaultRouteDetails, Error> {
1727
let com_con = COMLibrary::new()?;
1828
let wmi_con = WMIConnection::new(com_con)?;
1929

@@ -22,7 +32,7 @@ fn get_default_route() -> anyhow::Result<DefaultRouteDetails> {
2232
.filtered_query(&query)?
2333
.drain(..)
2434
.next()
25-
.ok_or_else(|| anyhow::anyhow!("no route found"))?;
35+
.ok_or(Error::NoRoute)?;
2636

2737
Ok(DefaultRouteDetails {
2838
interface_name: route.Name,

netwatch/src/netmon.rs

+24-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
//! Monitoring of networking interfaces and route changes.
22
3-
use anyhow::Result;
43
use futures_lite::future::Boxed as BoxFuture;
54
use tokio::sync::{mpsc, oneshot};
65
use tokio_util::task::AbortOnDropHandle;
@@ -32,9 +31,29 @@ pub struct Monitor {
3231
actor_tx: mpsc::Sender<ActorMessage>,
3332
}
3433

34+
#[derive(Debug, thiserror::Error)]
35+
pub enum Error {
36+
#[error("channel closed")]
37+
ChannelClosed,
38+
#[error("actor {0}")]
39+
Actor(#[from] actor::Error),
40+
}
41+
42+
impl<T> From<mpsc::error::SendError<T>> for Error {
43+
fn from(_value: mpsc::error::SendError<T>) -> Self {
44+
Self::ChannelClosed
45+
}
46+
}
47+
48+
impl From<oneshot::error::RecvError> for Error {
49+
fn from(_value: oneshot::error::RecvError) -> Self {
50+
Self::ChannelClosed
51+
}
52+
}
53+
3554
impl Monitor {
3655
/// Create a new monitor.
37-
pub async fn new() -> Result<Self> {
56+
pub async fn new() -> Result<Self, Error> {
3857
let actor = Actor::new().await?;
3958
let actor_tx = actor.subscribe();
4059

@@ -49,7 +68,7 @@ impl Monitor {
4968
}
5069

5170
/// Subscribe to network changes.
52-
pub async fn subscribe<F>(&self, callback: F) -> Result<CallbackToken>
71+
pub async fn subscribe<F>(&self, callback: F) -> Result<CallbackToken, Error>
5372
where
5473
F: Fn(bool) -> BoxFuture<()> + 'static + Sync + Send,
5574
{
@@ -62,7 +81,7 @@ impl Monitor {
6281
}
6382

6483
/// Unsubscribe a callback from network changes, using the provided token.
65-
pub async fn unsubscribe(&self, token: CallbackToken) -> Result<()> {
84+
pub async fn unsubscribe(&self, token: CallbackToken) -> Result<(), Error> {
6685
let (s, r) = oneshot::channel();
6786
self.actor_tx
6887
.send(ActorMessage::Unsubscribe(token, s))
@@ -72,7 +91,7 @@ impl Monitor {
7291
}
7392

7493
/// Potential change detected outside
75-
pub async fn network_change(&self) -> Result<()> {
94+
pub async fn network_change(&self) -> Result<(), Error> {
7695
self.actor_tx.send(ActorMessage::NetworkChange).await?;
7796
Ok(())
7897
}

netwatch/src/netmon/actor.rs

+6-10
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ use std::{
44
time::{Duration, Instant},
55
};
66

7-
use anyhow::Result;
87
use futures_lite::future::Boxed as BoxFuture;
8+
pub(super) use os::Error;
99
use os::{is_interesting_interface, RouteMonitor};
1010
use tokio::sync::{mpsc, oneshot};
11-
use tracing::{debug, trace, warn};
11+
use tracing::{debug, trace};
1212

1313
#[cfg(target_os = "android")]
1414
use super::android as os;
@@ -77,7 +77,7 @@ pub(super) enum ActorMessage {
7777
}
7878

7979
impl Actor {
80-
pub(super) async fn new() -> Result<Self> {
80+
pub(super) async fn new() -> Result<Self, os::Error> {
8181
let interface_state = State::new().await;
8282
let wall_time = Instant::now();
8383

@@ -114,9 +114,7 @@ impl Actor {
114114

115115
_ = debounce_interval.tick() => {
116116
if let Some(time_jumped) = last_event.take() {
117-
if let Err(err) = self.handle_potential_change(time_jumped).await {
118-
warn!("failed to handle network changes: {:?}", err);
119-
};
117+
self.handle_potential_change(time_jumped).await;
120118
}
121119
}
122120
_ = wall_time_interval.tick() => {
@@ -172,7 +170,7 @@ impl Actor {
172170
token
173171
}
174172

175-
async fn handle_potential_change(&mut self, time_jumped: bool) -> Result<()> {
173+
async fn handle_potential_change(&mut self, time_jumped: bool) {
176174
trace!("potential change");
177175

178176
let new_state = State::new().await;
@@ -181,7 +179,7 @@ impl Actor {
181179
// No major changes, continue on
182180
if !time_jumped && old_state == &new_state {
183181
debug!("no changes detected");
184-
return Ok(());
182+
return;
185183
}
186184

187185
let is_major = is_major_change(old_state, &new_state) || time_jumped;
@@ -197,8 +195,6 @@ impl Actor {
197195
cb(is_major).await;
198196
});
199197
}
200-
201-
Ok(())
202198
}
203199

204200
/// Reports whether wall time jumped more than 150%

netwatch/src/netmon/android.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
1-
use anyhow::Result;
21
use tokio::sync::mpsc;
32

43
use super::actor::NetworkMessage;
54

5+
#[derive(Debug, thiserror::Error)]
6+
#[error("error")]
7+
pub struct Error;
8+
69
#[derive(Debug)]
710
pub(super) struct RouteMonitor {
811
_sender: mpsc::Sender<NetworkMessage>,
912
}
1013

1114
impl RouteMonitor {
12-
pub(super) fn new(_sender: mpsc::Sender<NetworkMessage>) -> Result<Self> {
15+
pub(super) fn new(_sender: mpsc::Sender<NetworkMessage>) -> Result<Self, Error> {
1316
// Very sad monitor. Android doesn't allow us to do this
1417

1518
Ok(RouteMonitor { _sender })

netwatch/src/netmon/bsd.rs

+8-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
use anyhow::Result;
21
#[cfg(any(target_os = "macos", target_os = "ios"))]
32
use libc::{RTAX_DST, RTAX_IFP};
43
use tokio::{io::AsyncReadExt, sync::mpsc};
@@ -15,7 +14,13 @@ pub(super) struct RouteMonitor {
1514
_handle: AbortOnDropHandle<()>,
1615
}
1716

18-
fn create_socket() -> Result<tokio::net::UnixStream> {
17+
#[derive(Debug, thiserror::Error)]
18+
pub enum Error {
19+
#[error("IO {0}")]
20+
Io(#[from] std::io::Error),
21+
}
22+
23+
fn create_socket() -> std::io::Result<tokio::net::UnixStream> {
1924
let socket = socket2::Socket::new(libc::AF_ROUTE.into(), socket2::Type::RAW, None)?;
2025
socket.set_nonblocking(true)?;
2126
let socket_std: std::os::unix::net::UnixStream = socket.into();
@@ -27,7 +32,7 @@ fn create_socket() -> Result<tokio::net::UnixStream> {
2732
}
2833

2934
impl RouteMonitor {
30-
pub(super) fn new(sender: mpsc::Sender<NetworkMessage>) -> Result<Self> {
35+
pub(super) fn new(sender: mpsc::Sender<NetworkMessage>) -> Result<Self, Error> {
3136
let mut socket = create_socket()?;
3237
let handle = tokio::task::spawn(async move {
3338
trace!("AF_ROUTE monitor started");

netwatch/src/netmon/linux.rs

+7-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ use std::{
33
net::IpAddr,
44
};
55

6-
use anyhow::Result;
76
use futures_lite::StreamExt;
87
use libc::{
98
RTNLGRP_IPV4_IFADDR, RTNLGRP_IPV4_ROUTE, RTNLGRP_IPV4_RULE, RTNLGRP_IPV6_IFADDR,
@@ -32,6 +31,12 @@ impl Drop for RouteMonitor {
3231
}
3332
}
3433

34+
#[derive(Debug, thiserror::Error)]
35+
pub enum Error {
36+
#[error("IO {0}")]
37+
Io(#[from] std::io::Error),
38+
}
39+
3540
const fn nl_mgrp(group: u32) -> u32 {
3641
if group > 31 {
3742
panic!("use netlink_sys::Socket::add_membership() for this group");
@@ -52,7 +57,7 @@ macro_rules! get_nla {
5257
}
5358

5459
impl RouteMonitor {
55-
pub(super) fn new(sender: mpsc::Sender<NetworkMessage>) -> Result<Self> {
60+
pub(super) fn new(sender: mpsc::Sender<NetworkMessage>) -> Result<Self, Error> {
5661
let (mut conn, mut _handle, mut messages) = new_connection()?;
5762

5863
// Specify flags to listen on.

0 commit comments

Comments
 (0)