diff --git a/.gitignore b/.gitignore index 2992fd60..72220b26 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ # Project specific -3rd local_conf # Rust diff --git a/3rd/if-watch/.github/workflows/ci.yml b/3rd/if-watch/.github/workflows/ci.yml new file mode 100644 index 00000000..d8d5f742 --- /dev/null +++ b/3rd/if-watch/.github/workflows/ci.yml @@ -0,0 +1,109 @@ +on: [pull_request] + +name: if-watch + +jobs: + ci: + strategy: + fail-fast: false + matrix: + toolchain: + - rust: stable + #- rust: nightly + platform: + - target: x86_64-unknown-linux-gnu + host: ubuntu-latest + cross: false + + - target: x86_64-apple-darwin + host: macos-latest + cross: false + + - target: x86_64-pc-windows-msvc + host: windows-latest + cross: false + + - target: armv7-linux-androideabi + host: ubuntu-latest + cross: true + - target: aarch64-linux-android + host: ubuntu-latest + cross: true + + - target: aarch64-apple-ios + host: macos-latest + cross: true + env: + RUST_BACKTRACE: 1 + CARGO_INCREMENTAL: 0 + LLVM_CONFIG_PATH: /usr/local/opt/llvm/bin/llvm-config + NDK_HOME: /usr/local/lib/android/sdk/ndk-bundle + + runs-on: ${{ matrix.platform.host }} + steps: + - name: Checkout sources + uses: actions/checkout@v2 + + - name: Cache cargo folder + uses: actions/cache@v1 + with: + path: ~/.cargo + key: ${{ matrix.platform.target }}-cargo-${{ matrix.toolchain.rust }} + + - name: Install dependencies ubuntu + if: matrix.platform.host == 'ubuntu-latest' + run: sudo apt-get install llvm-dev + + - name: Install dependencies macos + if: matrix.platform.host == 'macos-latest' + run: brew install llvm + + - name: Install dependencies windows + if: matrix.platform.host == 'windows-latest' + run: choco install llvm + + - name: Install rust toolchain + uses: hecrj/setup-rust-action@v1 + with: + rust-version: ${{ matrix.toolchain.rust }} + targets: ${{ matrix.platform.target }} + + - name: Install cargo-apk + if: contains(matrix.platform.target, 'android') + run: cargo install cargo-apk + + - name: Build + if: contains(matrix.platform.target, 'android') == false + run: cargo build --workspace --all-features --target ${{ matrix.platform.target }} + + - name: Build android + if: contains(matrix.platform.target, 'android') + run: cargo apk build --target ${{ matrix.platform.target }} --all-features + + - name: Rust tests + if: matrix.platform.cross == false + run: cargo test --workspace --all-features + + lint-rust: + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v2 + + - name: Cache cargo folder + uses: actions/cache@v1 + with: + path: ~/.cargo + key: lint-cargo + + - name: Install rust toolchain + uses: hecrj/setup-rust-action@v1 + with: + rust-version: stable + components: clippy, rustfmt + + - name: cargo fmt + run: cargo fmt --all -- --check + + - name: cargo clippy + run: cargo clippy --workspace --all-features --examples --tests -- -D warnings diff --git a/3rd/if-watch/.gitignore b/3rd/if-watch/.gitignore new file mode 100644 index 00000000..96ef6c0b --- /dev/null +++ b/3rd/if-watch/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/3rd/if-watch/CHANGELOG.md b/3rd/if-watch/CHANGELOG.md new file mode 100644 index 00000000..223afafe --- /dev/null +++ b/3rd/if-watch/CHANGELOG.md @@ -0,0 +1,71 @@ +# Changelog +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.2.0] + +### Fixed + +- Update `async-io`, `if-addrs` crates. + See [PR 33](https://github.com/mxinden/if-watch/pull/33). + +## [3.1.0] + +### Fixed + +- Update `windows` crate. + See [PR 32](https://github.com/mxinden/if-watch/pull/32). + +## [3.0.1] + +### Fixed + +- For all architectures running the fallback option (e.g. Android) reverse the logic when checking if a recorded interface still exists in the new list to avoid reporting all interfaces as down and then up in the same resync(). + See [PR 31]. + +[PR 31]: https://github.com/mxinden/if-watch/pull/31 + +## [3.0.0] + +### 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 +- Add `IfWatcher::poll_if_event`. Implement `Stream` instead of `Future` for `IfWatcher`. + See [PR 23] and [PR 25]. +- Make `IfWatcher::new` synchronous. See [PR 24]. + +[PR 23]: https://github.com/mxinden/if-watch/pull/23 +[PR 24]: https://github.com/mxinden/if-watch/pull/24 +[PR 25]: https://github.com/mxinden/if-watch/pull/25 + +## [1.1.1] + +### Fixed +- Update to `rtnetlink` `v0.10`. See [PR 19]. + +[PR 19]: https://github.com/mxinden/if-watch/pull/19 + +## [1.1.0] +### Added +- Return socket closure as error. See [PR 15]. + +### Fixed +- Update to `windows` `v0.34`. See [PR 16]. + +[PR 15]: https://github.com/mxinden/if-watch/pull/15 +[PR 16]: https://github.com/mxinden/if-watch/pull/16 + +## [1.0.0] - 2022-01-12 +### Added +- macos/ios backend + +### Changed +- linux backend rewritten to use rtnetlink +- windows backend rewritten to use windows crate instead of winapi diff --git a/3rd/if-watch/Cargo.toml b/3rd/if-watch/Cargo.toml new file mode 100644 index 00000000..a07d7fdd --- /dev/null +++ b/3rd/if-watch/Cargo.toml @@ -0,0 +1,50 @@ +[package] +name = "if-watch" +version = "3.2.0" +authors = ["David Craven ", "Parity Technologies Limited "] +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] +default = ["tokio"] +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 } + +[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies] +core-foundation = "0.9.2" +if-addrs = "0.10.0" +system-configuration = "0.6.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.10.0" +windows = { version = "0.51.0", features = ["Win32_NetworkManagement_IpHelper", "Win32_Foundation", "Win32_NetworkManagement_Ndis", "Win32_Networking_WinSock"] } + +[target.'cfg(not(any(target_os = "ios", target_os = "linux", target_os = "macos", target_os = "windows")))'.dependencies] +async-io = "2.0.0" +if-addrs = "0.10.0" + +[dev-dependencies] +env_logger = "0.10.0" +smol = "1.2.5" +tokio = { version = "1.21.2", features = ["rt", "macros"] } + +[[example]] +name = "if_watch" +required-features = ["smol"] diff --git a/3rd/if-watch/README.md b/3rd/if-watch/README.md new file mode 100644 index 00000000..cae001ba --- /dev/null +++ b/3rd/if-watch/README.md @@ -0,0 +1,16 @@ +# Cross platform asynchronous network watcher + +```sh +cargo run --example if_watch +Got event Ok(Up(127.0.0.1/8)) +Got event Ok(Up(192.168.6.65/24)) +Got event Ok(Up(::1/128)) +Got event Ok(Up(2a01:8b81:7000:9700:cef9:e4ff:fe9e:b23b/64)) +Got event Ok(Up(fe80::cef9:e4ff:fe9e:b23b/64)) +``` + +Supported platforms at the moment are: +Linux, Windows and Android with a fallback for Macos and ios that polls for changes every 10s. + +## License +MIT OR Apache-2.0 diff --git a/3rd/if-watch/examples/if_watch.rs b/3rd/if-watch/examples/if_watch.rs new file mode 100644 index 00000000..012eed17 --- /dev/null +++ b/3rd/if-watch/examples/if_watch.rs @@ -0,0 +1,13 @@ +use futures::StreamExt; +use if_watch::smol::IfWatcher; + +fn main() { + env_logger::init(); + smol::block_on(async { + let mut set = IfWatcher::new().unwrap(); + loop { + let event = set.select_next_some().await; + println!("Got event {:?}", event); + } + }); +} diff --git a/3rd/if-watch/src/apple.rs b/3rd/if-watch/src/apple.rs new file mode 100644 index 00000000..c3099a42 --- /dev/null +++ b/3rd/if-watch/src/apple.rs @@ -0,0 +1,151 @@ +use crate::{IfEvent, IpNet, Ipv4Net, Ipv6Net}; +use core_foundation::array::CFArray; +use core_foundation::runloop::{kCFRunLoopCommonModes, CFRunLoop}; +use core_foundation::string::CFString; +use fnv::FnvHashSet; +use futures::channel::mpsc; +use futures::stream::{FusedStream, Stream}; +use if_addrs::IfAddr; +use std::collections::VecDeque; +use std::io::Result; +use std::pin::Pin; +use std::task::{Context, Poll}; +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, + queue: VecDeque, + rx: mpsc::Receiver<()>, +} + +impl IfWatcher { + pub fn new() -> Result { + let (tx, rx) = mpsc::channel(1); + std::thread::spawn(|| background_task(tx)); + let mut watcher = Self { + addrs: Default::default(), + queue: Default::default(), + rx, + }; + watcher.resync()?; + Ok(watcher) + } + + fn resync(&mut self) -> Result<()> { + let addrs = if_addrs::get_if_addrs()?; + for old_addr in self.addrs.clone() { + if addrs + .iter() + .find(|addr| addr.ip() == old_addr.addr()) + .is_none() + { + self.addrs.remove(&old_addr); + self.queue.push_back(IfEvent::Down(old_addr)); + } + } + for new_addr in addrs { + let ipnet = ifaddr_to_ipnet(new_addr.addr); + if self.addrs.insert(ipnet) { + self.queue.push_back(IfEvent::Up(ipnet)); + } + } + Ok(()) + } + + /// Iterate over current networks. + pub fn iter(&self) -> impl Iterator { + self.addrs.iter() + } + + /// Poll for an address change event. + pub fn poll_if_event(&mut self, cx: &mut Context) -> Poll> { + loop { + if let Some(event) = self.queue.pop_front() { + return Poll::Ready(Ok(event)); + } + if Pin::new(&mut self.rx).poll_next(cx).is_pending() { + return Poll::Pending; + } + if let Err(error) = self.resync() { + return Poll::Ready(Err(error)); + } + } + } +} + +impl Stream for IfWatcher { + type Item = Result; + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + 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) => { + let prefix_len = (!u32::from_be_bytes(ip.netmask.octets())).leading_zeros(); + IpNet::V4( + Ipv4Net::new(ip.ip, prefix_len as u8).expect("if_addrs returned a valid prefix"), + ) + } + IfAddr::V6(ip) => { + let prefix_len = (!u128::from_be_bytes(ip.netmask.octets())).leading_zeros(); + IpNet::V6( + Ipv6Net::new(ip.ip, prefix_len as u8).expect("if_addrs returned a valid prefix"), + ) + } + } +} + +fn callback(_store: SCDynamicStore, _changed_keys: CFArray, info: &mut mpsc::Sender<()>) { + match info.try_send(()) { + Err(err) if err.is_disconnected() => CFRunLoop::get_current().stop(), + _ => {} + } +} + +fn background_task(tx: mpsc::Sender<()>) { + let store = SCDynamicStoreBuilder::new("global-network-watcher") + .callback_context(SCDynamicStoreCallBackContext { + callout: callback, + info: tx, + }) + .build(); + store.set_notification_keys( + &CFArray::::from_CFTypes(&[]), + &CFArray::from_CFTypes(&[CFString::new("State:/Network/Interface/.*/IPv.")]), + ); + let source = store.create_run_loop_source(); + let run_loop = CFRunLoop::get_current(); + run_loop.add_source(&source, unsafe { kCFRunLoopCommonModes }); + CFRunLoop::run_current(); +} diff --git a/3rd/if-watch/src/fallback.rs b/3rd/if-watch/src/fallback.rs new file mode 100644 index 00000000..5c6fca6f --- /dev/null +++ b/3rd/if-watch/src/fallback.rs @@ -0,0 +1,116 @@ +use crate::IfEvent; +use async_io::Timer; +use futures::stream::{FusedStream, Stream}; +use if_addrs::IfAddr; +use ipnet::{IpNet, Ipv4Net, Ipv6Net}; +use std::collections::{HashSet, VecDeque}; +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 { + addrs: HashSet, + queue: VecDeque, + ticker: Timer, +} + +impl IfWatcher { + /// Create a watcher. + pub fn new() -> Result { + Ok(Self { + addrs: Default::default(), + queue: Default::default(), + ticker: Timer::interval_at(Instant::now(), Duration::from_secs(10)), + }) + } + + fn resync(&mut self) -> Result<()> { + let addrs = if_addrs::get_if_addrs()?; + for old_addr in self.addrs.clone() { + if !addrs.iter().any(|addr| addr.ip() == old_addr.addr()) { + self.addrs.remove(&old_addr); + self.queue.push_back(IfEvent::Down(old_addr)); + } + } + for new_addr in addrs { + let ipnet = ifaddr_to_ipnet(new_addr.addr); + if self.addrs.insert(ipnet) { + self.queue.push_back(IfEvent::Up(ipnet)); + } + } + Ok(()) + } + + /// Iterate over current networks. + pub fn iter(&self) -> impl Iterator { + self.addrs.iter() + } + + /// Poll for an address change event. + pub fn poll_if_event(&mut self, cx: &mut Context) -> Poll> { + loop { + if let Some(event) = self.queue.pop_front() { + return Poll::Ready(Ok(event)); + } + if Pin::new(&mut self.ticker).poll_next(cx).is_pending() { + return Poll::Pending; + } + if let Err(err) = self.resync() { + return Poll::Ready(Err(err)); + } + } + } +} + +impl Stream for IfWatcher { + type Item = Result; + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + 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) => { + let prefix_len = (!u32::from_be_bytes(ip.netmask.octets())).leading_zeros(); + IpNet::V4( + Ipv4Net::new(ip.ip, prefix_len as u8).expect("if_addrs returned a valid prefix"), + ) + } + IfAddr::V6(ip) => { + let prefix_len = (!u128::from_be_bytes(ip.netmask.octets())).leading_zeros(); + IpNet::V6( + Ipv6Net::new(ip.ip, prefix_len as u8).expect("if_addrs returned a valid prefix"), + ) + } + } +} diff --git a/3rd/if-watch/src/lib.rs b/3rd/if-watch/src/lib.rs new file mode 100644 index 00000000..4a352303 --- /dev/null +++ b/3rd/if-watch/src/lib.rs @@ -0,0 +1,120 @@ +//! IP address watching. +#![deny(missing_docs)] +#![deny(warnings)] + +pub use ipnet::{IpNet, Ipv4Net, Ipv6Net}; + +#[cfg(target_os = "macos")] +mod apple; +#[cfg(target_os = "ios")] +mod apple; +#[cfg(not(any( + target_os = "ios", + target_os = "linux", + target_os = "macos", + target_os = "windows", +)))] +mod fallback; +#[cfg(target_os = "linux")] +mod linux; +#[cfg(target_os = "windows")] +mod win; + +#[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", +)))] +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")] +#[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)] +pub enum IfEvent { + /// A new local address has been added. + Up(IpNet), + /// A local address has been deleted. + Down(IpNet), +} + +// #[cfg(test)] +// mod tests { +// use futures::StreamExt; +// use std::pin::Pin; + +// #[test] +// 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_smol_is_send() { +// use super::smol::IfWatcher; + +// smol::block_on(async { +// fn is_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) {} +// is_send(IfWatcher::new()); +// is_send(IfWatcher::new().unwrap()); +// is_send(Pin::new(&mut IfWatcher::new().unwrap())); +// } +// } diff --git a/3rd/if-watch/src/linux.rs b/3rd/if-watch/src/linux.rs new file mode 100644 index 00000000..6fa3ab11 --- /dev/null +++ b/3rd/if-watch/src/linux.rs @@ -0,0 +1,174 @@ +use crate::{IfEvent, IpNet, Ipv4Net, Ipv6Net}; +use fnv::FnvHashSet; +use futures::ready; +use futures::stream::{FusedStream, Stream, TryStreamExt}; +use futures::StreamExt; +use rtnetlink::constants::{RTMGRP_IPV4_IFADDR, RTMGRP_IPV6_IFADDR}; +use rtnetlink::packet::address::nlas::Nla; +use rtnetlink::packet::{AddressMessage, RtnlMessage}; +use rtnetlink::proto::{Connection, NetlinkPayload}; +use rtnetlink::sys::{AsyncSocket, SocketAddr}; +use std::collections::VecDeque; +use std::future::Future; +use std::io::{Error, ErrorKind, Result}; +use std::net::{Ipv4Addr, Ipv6Addr}; +use std::pin::Pin; +use std::task::{Context, Poll}; + +#[cfg(feature = "tokio")] +pub mod tokio { + //! An interface watcher that uses `rtnetlink`'s [`TokioSocket`](rtnetlink::sys::TokioSocket) + use rtnetlink::sys::TokioSocket; + + /// Watches for interface changes. + pub type IfWatcher = super::IfWatcher; +} + +#[cfg(feature = "smol")] +pub mod smol { + //! An interface watcher that uses `rtnetlink`'s [`SmolSocket`](rtnetlink::sys::SmolSocket) + use rtnetlink::sys::SmolSocket; + + /// Watches for interface changes. + pub type IfWatcher = super::IfWatcher; +} + +pub struct IfWatcher { + conn: Connection, + messages: Pin> + Send>>, + addrs: FnvHashSet, + queue: VecDeque, +} + +impl std::fmt::Debug for IfWatcher { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + f.debug_struct("IfWatcher") + .field("addrs", &self.addrs) + .finish_non_exhaustive() + } +} + +impl IfWatcher +where + T: AsyncSocket + Unpin, +{ + /// Create a watcher. + pub fn new() -> Result { + let (mut conn, handle, messages) = rtnetlink::new_connection_with_socket::()?; + let groups = RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR; + let addr = SocketAddr::new(0, groups); + conn.socket_mut().socket_mut().bind(&addr)?; + let get_addrs_stream = handle + .address() + .get() + .execute() + .map_ok(RtnlMessage::NewAddress) + .map_err(|err| Error::new(ErrorKind::Other, err)); + let msg_stream = messages.filter_map(|(msg, _)| async { + match msg.payload { + NetlinkPayload::Error(err) => Some(Err(err.to_io())), + NetlinkPayload::InnerMessage(msg) => Some(Ok(msg)), + _ => None, + } + }); + let messages = get_addrs_stream.chain(msg_stream).boxed(); + let addrs = FnvHashSet::default(); + let queue = VecDeque::default(); + Ok(Self { + conn, + messages, + addrs, + queue, + }) + } + + /// Iterate over current networks. + pub fn iter(&self) -> impl Iterator { + self.addrs.iter() + } + + fn add_address(&mut self, msg: AddressMessage) { + for net in iter_nets(msg) { + if self.addrs.insert(net) { + self.queue.push_back(IfEvent::Up(net)); + } + } + } + + fn rem_address(&mut self, msg: AddressMessage) { + for net in iter_nets(msg) { + if self.addrs.remove(&net) { + self.queue.push_back(IfEvent::Down(net)); + } + } + } + + /// Poll for an address change event. + pub fn poll_if_event(&mut self, cx: &mut Context) -> Poll> { + loop { + if let Some(event) = self.queue.pop_front() { + return Poll::Ready(Ok(event)); + } + if Pin::new(&mut self.conn).poll(cx).is_ready() { + return Poll::Ready(Err(socket_err())); + } + let message = ready!(self.messages.poll_next_unpin(cx)).ok_or_else(socket_err)??; + match message { + RtnlMessage::NewAddress(msg) => self.add_address(msg), + RtnlMessage::DelAddress(msg) => self.rem_address(msg), + _ => {} + } + } + } +} + +fn socket_err() -> std::io::Error { + std::io::Error::new(ErrorKind::BrokenPipe, "rtnetlink socket closed") +} + +fn iter_nets(msg: AddressMessage) -> impl Iterator { + let prefix = msg.header.prefix_len; + let family = msg.header.family; + msg.nlas.into_iter().filter_map(move |nla| { + if let Nla::Address(octets) = nla { + match family { + 2 => { + let mut addr = [0; 4]; + addr.copy_from_slice(&octets); + Some(IpNet::V4( + Ipv4Net::new(Ipv4Addr::from(addr), prefix).unwrap(), + )) + } + 10 => { + let mut addr = [0; 16]; + addr.copy_from_slice(&octets); + Some(IpNet::V6( + Ipv6Net::new(Ipv6Addr::from(addr), prefix).unwrap(), + )) + } + _ => None, + } + } else { + None + } + }) +} + +impl Stream for IfWatcher +where + T: AsyncSocket + Unpin, +{ + type Item = Result; + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::into_inner(self).poll_if_event(cx).map(Some) + } +} + +impl FusedStream for IfWatcher +where + T: AsyncSocket + AsyncSocket + Unpin, +{ + fn is_terminated(&self) -> bool { + false + } +} diff --git a/3rd/if-watch/src/win.rs b/3rd/if-watch/src/win.rs new file mode 100644 index 00000000..b0a2bce5 --- /dev/null +++ b/3rd/if-watch/src/win.rs @@ -0,0 +1,191 @@ +use crate::{IfEvent, IpNet, Ipv4Net, Ipv6Net}; +use fnv::FnvHashSet; +use futures::stream::{FusedStream, Stream}; +use futures::task::AtomicWaker; +use if_addrs::IfAddr; +use std::collections::VecDeque; +use std::ffi::c_void; +use std::io::{Error, ErrorKind, Result}; +use std::pin::Pin; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; +use std::task::{Context, Poll}; +use windows::Win32::Foundation::{BOOLEAN, HANDLE}; +use windows::Win32::NetworkManagement::IpHelper::{ + CancelMibChangeNotify2, NotifyIpInterfaceChange, MIB_IPINTERFACE_ROW, MIB_NOTIFICATION_TYPE, +}; +use windows::Win32::Networking::WinSock::AF_UNSPEC; + +#[cfg(feature = "tokio")] +pub mod tokio { + //! An interface watcher. + //! **On Windows 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 Windows 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 { + addrs: FnvHashSet, + queue: VecDeque, + #[allow(unused)] + notif: IpChangeNotification, + waker: Arc, + resync: Arc, +} + +impl IfWatcher { + /// Create a watcher. + pub fn new() -> Result { + let resync = Arc::new(AtomicBool::new(true)); + let waker = Arc::new(AtomicWaker::new()); + Ok(Self { + addrs: Default::default(), + queue: Default::default(), + waker: waker.clone(), + resync: resync.clone(), + notif: IpChangeNotification::new(Box::new(move |_, _| { + resync.store(true, Ordering::Relaxed); + waker.wake(); + }))?, + }) + } + + fn resync(&mut self) -> Result<()> { + let addrs = if_addrs::get_if_addrs()?; + for old_addr in self.addrs.clone() { + if addrs + .iter() + .find(|addr| addr.ip() == old_addr.addr()) + .is_none() + { + self.addrs.remove(&old_addr); + self.queue.push_back(IfEvent::Down(old_addr)); + } + } + for new_addr in addrs { + let ipnet = ifaddr_to_ipnet(new_addr.addr); + if self.addrs.insert(ipnet) { + self.queue.push_back(IfEvent::Up(ipnet)); + } + } + Ok(()) + } + + /// Iterate over current networks. + pub fn iter(&self) -> impl Iterator { + self.addrs.iter() + } + + /// Poll for an address change event. + pub fn poll_if_event(&mut self, cx: &mut Context) -> Poll> { + loop { + if let Some(event) = self.queue.pop_front() { + return Poll::Ready(Ok(event)); + } + if !self.resync.swap(false, Ordering::Relaxed) { + self.waker.register(cx.waker()); + return Poll::Pending; + } + if let Err(error) = self.resync() { + return Poll::Ready(Err(error)); + } + } + } +} + +impl Stream for IfWatcher { + type Item = Result; + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + 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) => { + let prefix_len = (!u32::from_be_bytes(ip.netmask.octets())).leading_zeros(); + IpNet::V4( + Ipv4Net::new(ip.ip, prefix_len as u8).expect("if_addrs returned a valid prefix"), + ) + } + IfAddr::V6(ip) => { + let prefix_len = (!u128::from_be_bytes(ip.netmask.octets())).leading_zeros(); + IpNet::V6( + Ipv6Net::new(ip.ip, prefix_len as u8).expect("if_addrs returned a valid prefix"), + ) + } + } +} + +/// IP change notifications +struct IpChangeNotification { + handle: HANDLE, + callback: *mut IpChangeCallback, +} + +impl std::fmt::Debug for IpChangeNotification { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "IpChangeNotification") + } +} + +type IpChangeCallback = Box; + +impl IpChangeNotification { + /// Register for route change notifications + fn new(cb: IpChangeCallback) -> Result { + unsafe extern "system" fn global_callback( + caller_context: *const c_void, + row: *const MIB_IPINTERFACE_ROW, + notification_type: MIB_NOTIFICATION_TYPE, + ) { + (**(caller_context as *const IpChangeCallback))(&*row, notification_type) + } + let mut handle = HANDLE::default(); + let callback = Box::into_raw(Box::new(cb)); + unsafe { + NotifyIpInterfaceChange( + AF_UNSPEC, + Some(global_callback), + Some(callback as _), + BOOLEAN(0), + &mut handle as _, + ) + .map_err(|err| Error::new(ErrorKind::Other, err.to_string()))?; + } + Ok(Self { callback, handle }) + } +} + +impl Drop for IpChangeNotification { + fn drop(&mut self) { + unsafe { + if let Err(err) = CancelMibChangeNotify2(self.handle) { + log::error!("error deregistering notification: {}", err); + } + drop(Box::from_raw(self.callback)); + } + } +} + +unsafe impl Send for IpChangeNotification {} diff --git a/Cargo.lock b/Cargo.lock index ef39caff..f9971771 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -262,36 +262,180 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "155a5a185e42c6b77ac7b88a15143d930a9e9727a5b7b77eed417404ab15c247" +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener 2.5.3", + "futures-core", +] + +[[package]] +name = "async-channel" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7ebdfa2ebdab6b1760375fa7d6f382b9f486eac35fc994625a00e89280bdbb7" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand 2.1.1", + "futures-lite 2.3.0", + "slab", +] + +[[package]] +name = "async-fs" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06" +dependencies = [ + "async-lock 2.8.0", + "autocfg", + "blocking", + "futures-lite 1.13.0", +] + +[[package]] +name = "async-global-executor" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" +dependencies = [ + "async-channel 2.3.1", + "async-executor", + "async-io 2.3.4", + "async-lock 3.4.0", + "blocking", + "futures-lite 2.3.0", + "once_cell", +] + +[[package]] +name = "async-io" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" +dependencies = [ + "async-lock 2.8.0", + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-lite 1.13.0", + "log", + "parking", + "polling 2.8.0", + "rustix 0.37.27", + "slab", + "socket2 0.4.10", + "waker-fn", +] + [[package]] name = "async-io" version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "444b0228950ee6501b3568d3c93bf1176a1fdbc3b758dcd9475046d30f4dc7e8" dependencies = [ - "async-lock", + "async-lock 3.4.0", "cfg-if", "concurrent-queue", "futures-io", - "futures-lite", + "futures-lite 2.3.0", "parking", - "polling", + "polling 3.7.3", "rustix 0.38.35", "slab", "tracing", "windows-sys 0.59.0", ] +[[package]] +name = "async-lock" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" +dependencies = [ + "event-listener 2.5.3", +] + [[package]] name = "async-lock" version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" dependencies = [ - "event-listener", + "event-listener 5.3.1", "event-listener-strategy", "pin-project-lite", ] +[[package]] +name = "async-net" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0434b1ed18ce1cf5769b8ac540e33f01fa9471058b5e89da9e06f3c882a8c12f" +dependencies = [ + "async-io 1.13.0", + "blocking", + "futures-lite 1.13.0", +] + +[[package]] +name = "async-process" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6438ba0a08d81529c69b36700fa2f95837bfe3e776ab39cde9c14d9149da88" +dependencies = [ + "async-io 1.13.0", + "async-lock 2.8.0", + "async-signal", + "blocking", + "cfg-if", + "event-listener 3.1.0", + "futures-lite 1.13.0", + "rustix 0.38.35", + "windows-sys 0.48.0", +] + +[[package]] +name = "async-signal" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" +dependencies = [ + "async-io 2.3.4", + "async-lock 3.4.0", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix 0.38.35", + "signal-hook-registry", + "slab", + "windows-sys 0.59.0", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + [[package]] name = "async-trait" version = "0.1.81" @@ -316,6 +460,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "attohttpc" version = "0.24.1" @@ -494,6 +644,19 @@ dependencies = [ "generic-array", ] +[[package]] +name = "blocking" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" +dependencies = [ + "async-channel 2.3.1", + "async-task", + "futures-io", + "futures-lite 2.3.0", + "piper", +] + [[package]] name = "bs58" version = "0.5.1" @@ -1500,6 +1663,23 @@ dependencies = [ "version_check", ] +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "event-listener" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d93877bcde0eb80ca09131a08d23f0a5c18a620b01db137dba666d18cd9b30c2" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + [[package]] name = "event-listener" version = "5.3.1" @@ -1517,7 +1697,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" dependencies = [ - "event-listener", + "event-listener 5.3.1", "pin-project-lite", ] @@ -1896,6 +2076,15 @@ dependencies = [ "tantivy-common", ] +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + [[package]] name = "fastrand" version = "2.1.1" @@ -2051,13 +2240,31 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand 1.9.0", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + [[package]] name = "futures-lite" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" dependencies = [ + "fastrand 2.1.1", "futures-core", + "futures-io", + "parking", "pin-project-lite", ] @@ -2328,7 +2535,7 @@ dependencies = [ "ipnet", "once_cell", "rand 0.8.5", - "socket2", + "socket2 0.5.7", "thiserror", "tinyvec", "tokio", @@ -2504,7 +2711,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2", + "socket2 0.5.7", "tokio", "tower-service", "tracing", @@ -2561,7 +2768,7 @@ dependencies = [ "http-body 1.0.1", "hyper 1.4.1", "pin-project-lite", - "socket2", + "socket2 0.5.7", "tokio", "tower", "tower-service", @@ -2624,17 +2831,17 @@ dependencies = [ [[package]] name = "if-watch" version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6b0422c86d7ce0e97169cc42e04ae643caf278874a7a3c87b8150a220dc7e1e" dependencies = [ - "async-io", + "async-io 2.3.4", "core-foundation", + "env_logger", "fnv", "futures", "if-addrs", "ipnet", "log", "rtnetlink", + "smol", "system-configuration", "tokio", "windows", @@ -2747,7 +2954,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" dependencies = [ - "socket2", + "socket2 0.5.7", "widestring", "windows-sys 0.48.0", "winreg", @@ -3078,7 +3285,7 @@ dependencies = [ "libp2p-swarm", "rand 0.8.5", "smallvec", - "socket2", + "socket2 0.5.7", "tokio", "tracing", "void", @@ -3182,7 +3389,7 @@ dependencies = [ "rand 0.8.5", "ring 0.17.8", "rustls", - "socket2", + "socket2 0.5.7", "thiserror", "tokio", "tracing", @@ -3238,7 +3445,7 @@ dependencies = [ "libc", "libp2p-core", "libp2p-identity", - "socket2", + "socket2 0.5.7", "tokio", "tracing", ] @@ -3787,6 +3994,7 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "416060d346fbaf1f23f9512963e3e878f1a78e707cb699ba9215761754244307" dependencies = [ + "async-io 1.13.0", "bytes", "futures", "libc", @@ -4087,6 +4295,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand 2.1.1", + "futures-io", +] + [[package]] name = "pkcs8" version = "0.10.2" @@ -4103,6 +4322,22 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +[[package]] +name = "polling" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if", + "concurrent-queue", + "libc", + "log", + "pin-project-lite", + "windows-sys 0.48.0", +] + [[package]] name = "polling" version = "3.7.3" @@ -4372,7 +4607,7 @@ dependencies = [ "quinn-udp", "rustc-hash 2.0.0", "rustls", - "socket2", + "socket2 0.5.7", "thiserror", "tokio", "tracing", @@ -4403,7 +4638,7 @@ checksum = "8bffec3605b73c6f1754535084a85229fa8a30f86014e6c81aeec4abb68b0285" dependencies = [ "libc", "once_cell", - "socket2", + "socket2 0.5.7", "tracing", "windows-sys 0.52.0", ] @@ -4693,6 +4928,7 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "322c53fd76a18698f1c27381d58091de3a043d356aa5bd0d510608b565f469a0" dependencies = [ + "async-global-executor", "futures", "log", "netlink-packet-route", @@ -5039,6 +5275,15 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + [[package]] name = "signature" version = "2.2.0" @@ -5084,6 +5329,23 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +[[package]] +name = "smol" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13f2b548cd8447f8de0fdf1c592929f70f4fc7039a05e47404b0d096ec6987a1" +dependencies = [ + "async-channel 1.9.0", + "async-executor", + "async-fs", + "async-io 1.13.0", + "async-lock 2.8.0", + "async-net", + "async-process", + "blocking", + "futures-lite 1.13.0", +] + [[package]] name = "snow" version = "0.9.6" @@ -5101,6 +5363,16 @@ dependencies = [ "subtle", ] +[[package]] +name = "socket2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "socket2" version = "0.5.7" @@ -5235,18 +5507,20 @@ dependencies = [ [[package]] name = "system-configuration" -version = "0.5.1" -source = "git+https://github.com/tmpfs/system-configuration-rs?rev=572913bf0c74ed996b2beeac3d61ed681075cbd4#572913bf0c74ed996b2beeac3d61ed681075cbd4" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.6.0", "core-foundation", "system-configuration-sys", ] [[package]] name = "system-configuration-sys" -version = "0.5.0" -source = "git+https://github.com/tmpfs/system-configuration-rs?rev=572913bf0c74ed996b2beeac3d61ed681075cbd4#572913bf0c74ed996b2beeac3d61ed681075cbd4" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" dependencies = [ "core-foundation-sys", "libc", @@ -5371,7 +5645,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" dependencies = [ "cfg-if", - "fastrand", + "fastrand 2.1.1", "once_cell", "rustix 0.38.35", "windows-sys 0.59.0", @@ -5491,7 +5765,7 @@ dependencies = [ "libc", "mio", "pin-project-lite", - "socket2", + "socket2 0.5.7", "tokio-macros", "windows-sys 0.52.0", ] @@ -5784,6 +6058,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +[[package]] +name = "waker-fn" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" + [[package]] name = "walkdir" version = "2.5.0" diff --git a/Cargo.toml b/Cargo.toml index c6e0ac03..c9eabb5b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,11 @@ [package] authors = ["Andre-Philippe Paquet "] -categories = ["database-implementations", "command-line-interface", "wasm", "web-programming"] +categories = [ + "database-implementations", + "command-line-interface", + "wasm", + "web-programming", +] description = "Distributed applications framework" edition = "2021" exclude = ["examples", "tools"] @@ -11,18 +16,17 @@ repository = "https://github.com/appaquet/exocore" version = "0.1.25" [features] -default = [ - "apps-sdk", - "client", - "logger", - "web", -] +default = ["apps-sdk", "client", "logger", "web"] # Top level features apps-sdk = ["exocore-apps-sdk", "exocore-store", "protos"] client = ["core-runtime", "transport-p2p", "store-remote", "protos", "anyhow"] logger = ["core-logger"] -tests-utils = ["exocore-core/tests-utils", "exocore-transport/tests-utils", "exocore-store/tests-utils"] +tests-utils = [ + "exocore-core/tests-utils", + "exocore-transport/tests-utils", + "exocore-store/tests-utils", +] web = ["transport-p2p-web", "protos", "exocore-core/web"] # Underlying crates features @@ -41,22 +45,22 @@ transport-p2p-web = ["exocore-transport/p2p-web"] [dependencies] anyhow = { version = "1.0.86", optional = true } -exocore-apps-sdk = {version = "0.1.25", path = "./apps/sdk", default-features = false, optional = true} -exocore-chain = {version = "0.1.25", path = "./chain", default-features = false, optional = true} -exocore-core = {version = "0.1.25", path = "./core", default-features = false, optional = true} -exocore-discovery = {version = "0.1.25", path = "./discovery", default-features = false, optional = true} -exocore-protos = {version = "0.1.25", path = "./protos", default-features = false, optional = true} -exocore-store = {version = "0.1.25", path = "./store", default-features = false, optional = true} -exocore-transport = {version = "0.1.25", path = "./transport", default-features = false, optional = true} +exocore-apps-sdk = { version = "0.1.25", path = "./apps/sdk", default-features = false, optional = true } +exocore-chain = { version = "0.1.25", path = "./chain", default-features = false, optional = true } +exocore-core = { version = "0.1.25", path = "./core", default-features = false, optional = true } +exocore-discovery = { version = "0.1.25", path = "./discovery", default-features = false, optional = true } +exocore-protos = { version = "0.1.25", path = "./protos", default-features = false, optional = true } +exocore-store = { version = "0.1.25", path = "./store", default-features = false, optional = true } +exocore-transport = { version = "0.1.25", path = "./transport", default-features = false, optional = true } log = "0.4.22" [dev-dependencies] -exocore-core = {version = "0.1.25", path = "./core", features = ["tests-utils"]} +exocore-core = { version = "0.1.25", path = "./core", features = [ + "tests-utils", +] } [workspace] -exclude = [ - "3rd", -] +exclude = ["3rd"] members = [ "core", "protos", @@ -72,6 +76,7 @@ members = [ "clients/web", "clients/android", "clients/c", + "3rd/if-watch", ] [profile.production] @@ -84,4 +89,5 @@ debug = true [patch.crates-io] # See https://github.com/mullvad/system-configuration-rs/issues/41 -system-configuration = { git = "https://github.com/tmpfs/system-configuration-rs", rev = "572913bf0c74ed996b2beeac3d61ed681075cbd4"} +# system-configuration = { git = "https://github.com/tmpfs/system-configuration-rs", rev = "572913bf0c74ed996b2beeac3d61ed681075cbd4"} +if-watch = { path = "./3rd/if-watch" }