Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

linux: Feature gate async runtime, allowing opting between Tokio or smol #27

Merged
merged 10 commits into from
Nov 4, 2022
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ jobs:

- name: Build android
if: contains(matrix.platform.target, 'android')
run: cargo apk --target ${{ matrix.platform.target }} build --workspace --all-features
run: cargo apk build --target ${{ matrix.platform.target }} --all-features

- name: Rust tests
if: matrix.platform.cross == false
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [3.0.0] - [unreleased]
mxinden marked this conversation as resolved.
Show resolved Hide resolved

### Changed
- Feature gate async runtime, allowing opting between Tokio or smol. For every OS each `IfWatcher` is
under the `tokio` or `smol` module. This makes it a breaking change as there
is no more a default implementation. See [PR 27](https://github.com/mxinden/if-watch/pull/27).

## [2.0.0]

### Changed
Expand Down
20 changes: 18 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,26 +1,35 @@
[package]
name = "if-watch"
version = "2.0.0"
version = "3.0.0"
authors = ["David Craven <david@craven.ch>", "Parity Technologies Limited <admin@parity.io>"]
edition = "2021"
keywords = ["asynchronous", "routing"]
license = "MIT OR Apache-2.0"
description = "crossplatform asynchronous network watcher"
repository = "https://github.com/mxinden/if-watch"

[lib]
crate-type = ["cdylib", "lib"]

[features]
tokio = ["dep:tokio", "rtnetlink/tokio_socket"]
smol = ["dep:smol", "rtnetlink/smol_socket"]

[dependencies]
fnv = "1.0.7"
futures = "0.3.19"
ipnet = "2.3.1"
log = "0.4.14"

[target.'cfg(target_os = "linux")'.dependencies]
rtnetlink = { version = "0.10.0", default-features = false, features = ["smol_socket"] }
rtnetlink = { version = "0.10.0", default-features = false }

[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies]
core-foundation = "0.9.2"
if-addrs = "0.7.0"
system-configuration = "0.5.0"
tokio = { version = "1.21.2", features = ["rt"], optional = true }
smol = { version = "1.2.5", optional = true }

[target.'cfg(target_os = "windows")'.dependencies]
if-addrs = "0.7.0"
Expand All @@ -32,3 +41,10 @@ if-addrs = "0.7.0"

[dev-dependencies]
env_logger = "0.9.0"
smol = "1.2.5"
tokio = { version = "1.21.2", features = ["rt", "macros"] }

[[example]]
name = "if_watch"
required-features = ["smol"]

4 changes: 2 additions & 2 deletions examples/if_watch.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use futures::StreamExt;
use if_watch::IfWatcher;
use if_watch::smol::IfWatcher;
mxinden marked this conversation as resolved.
Show resolved Hide resolved

fn main() {
env_logger::init();
futures::executor::block_on(async {
smol::block_on(async {
let mut set = IfWatcher::new().unwrap();
loop {
let event = set.select_next_some().await;
Expand Down
37 changes: 36 additions & 1 deletion src/apple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use core_foundation::runloop::{kCFRunLoopCommonModes, CFRunLoop};
use core_foundation::string::CFString;
use fnv::FnvHashSet;
use futures::channel::mpsc;
use futures::stream::Stream;
use futures::stream::{FusedStream, Stream};
use if_addrs::IfAddr;
use std::collections::VecDeque;
use std::io::Result;
Expand All @@ -14,6 +14,26 @@ use system_configuration::dynamic_store::{
SCDynamicStore, SCDynamicStoreBuilder, SCDynamicStoreCallBackContext,
};

#[cfg(feature = "tokio")]
pub mod tokio {
//! An interface watcher.
//! **On Apple Platforms there is no difference between `tokio` and `smol` features,**
//! **this was done to maintain the api compatible with other platforms**.

/// Watches for interface changes.
pub type IfWatcher = super::IfWatcher;
}

#[cfg(feature = "smol")]
pub mod smol {
//! An interface watcher.
//! **On Apple platforms there is no difference between `tokio` and `smol` features,**
//! **this was done to maintain the api compatible with other platforms**.

/// Watches for interface changes.
pub type IfWatcher = super::IfWatcher;
}

#[derive(Debug)]
pub struct IfWatcher {
addrs: FnvHashSet<IpNet>,
Expand Down Expand Up @@ -55,10 +75,12 @@ impl IfWatcher {
Ok(())
}

/// Iterate over current networks.
pub fn iter(&self) -> impl Iterator<Item = &IpNet> {
self.addrs.iter()
}

/// Poll for an address change event.
pub fn poll_if_event(&mut self, cx: &mut Context) -> Poll<Result<IfEvent>> {
loop {
if let Some(event) = self.queue.pop_front() {
Expand All @@ -74,6 +96,19 @@ impl IfWatcher {
}
}

impl Stream for IfWatcher {
type Item = Result<IfEvent>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
Pin::into_inner(self).poll_if_event(cx).map(Some)
}
}

impl FusedStream for IfWatcher {
fn is_terminated(&self) -> bool {
false
}
}

fn ifaddr_to_ipnet(addr: IfAddr) -> IpNet {
match addr {
IfAddr::V4(ip) => {
Expand Down
40 changes: 37 additions & 3 deletions src/fallback.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,34 @@
use crate::IfEvent;
use async_io::Timer;
use futures::stream::Stream;
use futures::stream::{FusedStream, Stream};
use if_addrs::IfAddr;
use ipnet::{IpNet, Ipv4Net, Ipv6Net};
use std::collections::{HashSet, VecDeque};
use std::future::Future;
use std::io::Result;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::{Duration, Instant};

#[cfg(feature = "tokio")]
pub mod tokio {
//! An interface watcher.
//! **On this platform there is no difference between `tokio` and `smol` features,**
//! **this was done to maintain the api compatible with other platforms**.

/// Watches for interface changes.
pub type IfWatcher = super::IfWatcher;
}

#[cfg(feature = "smol")]
pub mod smol {
//! An interface watcher.
//! **On this platform there is no difference between `tokio` and `smol` features,**
//! **this was done to maintain the api compatible with other platforms**.

/// Watches for interface changes.
pub type IfWatcher = super::IfWatcher;
}

/// An address set/watcher
#[derive(Debug)]
pub struct IfWatcher {
Expand All @@ -19,7 +38,7 @@ pub struct IfWatcher {
}

impl IfWatcher {
/// Create a watcher
/// Create a watcher.
pub fn new() -> Result<Self> {
Ok(Self {
addrs: Default::default(),
Expand All @@ -45,10 +64,12 @@ impl IfWatcher {
Ok(())
}

/// Iterate over current networks.
pub fn iter(&self) -> impl Iterator<Item = &IpNet> {
self.addrs.iter()
}

/// Poll for an address change event.
pub fn poll_if_event(&mut self, cx: &mut Context) -> Poll<Result<IfEvent>> {
loop {
if let Some(event) = self.queue.pop_front() {
Expand All @@ -64,6 +85,19 @@ impl IfWatcher {
}
}

impl Stream for IfWatcher {
type Item = Result<IfEvent>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
Pin::into_inner(self).poll_if_event(cx).map(Some)
}
}

impl FusedStream for IfWatcher {
fn is_terminated(&self) -> bool {
false
}
}

fn ifaddr_to_ipnet(addr: IfAddr) -> IpNet {
match addr {
IfAddr::V4(ip) => {
Expand Down
114 changes: 62 additions & 52 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,7 @@
#![deny(missing_docs)]
#![deny(warnings)]

use futures::stream::FusedStream;
use futures::Stream;
pub use ipnet::{IpNet, Ipv4Net, Ipv6Net};
use std::io::Result;
use std::pin::Pin;
use std::task::{Context, Poll};

#[cfg(target_os = "macos")]
mod apple;
Expand All @@ -25,21 +20,47 @@ mod linux;
#[cfg(target_os = "windows")]
mod win;

#[cfg(target_os = "macos")]
use apple as platform_impl;
#[cfg(target_os = "ios")]
use apple as platform_impl;
#[cfg(any(target_os = "macos", target_os = "ios"))]
#[cfg(feature = "tokio")]
pub use apple::tokio;

#[cfg(any(target_os = "macos", target_os = "ios"))]
#[cfg(feature = "smol")]
pub use apple::smol;

#[cfg(feature = "smol")]
#[cfg(not(any(
target_os = "ios",
target_os = "linux",
target_os = "macos",
target_os = "windows",
)))]
use fallback as platform_impl;
#[cfg(target_os = "linux")]
use linux as platform_impl;
pub use fallback::smol;

#[cfg(feature = "tokio")]
#[cfg(not(any(
target_os = "ios",
target_os = "linux",
target_os = "macos",
target_os = "windows",
)))]
pub use fallback::tokio;

#[cfg(target_os = "windows")]
use win as platform_impl;
#[cfg(feature = "tokio")]
pub use win::tokio;

#[cfg(target_os = "windows")]
#[cfg(feature = "smol")]
pub use win::smol;

#[cfg(target_os = "linux")]
#[cfg(feature = "tokio")]
pub use linux::tokio;

#[cfg(target_os = "linux")]
#[cfg(feature = "smol")]
pub use linux::smol;

/// An address change event.
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
Expand All @@ -50,61 +71,50 @@ pub enum IfEvent {
Down(IpNet),
}

/// Watches for interface changes.
#[derive(Debug)]
pub struct IfWatcher(platform_impl::IfWatcher);

impl IfWatcher {
/// Create a watcher
pub fn new() -> Result<Self> {
platform_impl::IfWatcher::new().map(Self)
}

/// Iterate over current networks.
pub fn iter(&self) -> impl Iterator<Item = &IpNet> {
self.0.iter()
}

/// Poll for an address change event.
pub fn poll_if_event(&mut self, cx: &mut Context) -> Poll<Result<IfEvent>> {
self.0.poll_if_event(cx)
}
}

impl Stream for IfWatcher {
type Item = Result<IfEvent>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
Pin::into_inner(self).poll_if_event(cx).map(Some)
}
}

impl FusedStream for IfWatcher {
fn is_terminated(&self) -> bool {
false
}
}

#[cfg(test)]
mod tests {
use super::*;
use futures::StreamExt;
use std::pin::Pin;

#[test]
fn test_ip_watch() {
futures::executor::block_on(async {
fn test_smol_ip_watch() {
use super::smol::IfWatcher;

smol::block_on(async {
let mut set = IfWatcher::new().unwrap();
let event = set.select_next_some().await.unwrap();
println!("Got event {:?}", event);
});
}

#[tokio::test]
async fn test_tokio_ip_watch() {
use super::tokio::IfWatcher;

let mut set = IfWatcher::new().unwrap();
let event = set.select_next_some().await.unwrap();
println!("Got event {:?}", event);
}

#[test]
fn test_is_send() {
futures::executor::block_on(async {
fn test_smol_is_send() {
use super::smol::IfWatcher;

smol::block_on(async {
fn is_send<T: Send>(_: T) {}
is_send(IfWatcher::new());
is_send(IfWatcher::new().unwrap());
is_send(Pin::new(&mut IfWatcher::new().unwrap()));
});
}

#[tokio::test]
async fn test_tokio_is_send() {
use super::tokio::IfWatcher;

fn is_send<T: Send>(_: T) {}
is_send(IfWatcher::new());
is_send(IfWatcher::new().unwrap());
is_send(Pin::new(&mut IfWatcher::new().unwrap()));
}
}
Loading