mirror of
https://github.com/jmcorgan/fips.git
synced 2026-07-22 07:48:26 +00:00
feat: Android-ready core: target_os gating and app-owned TUN seam
Make the FIPS core build and run as an embedded Android library. The host app owns the TUN (e.g. an Android VpnService) and FIPS performs no system-TUN or CAP_NET_ADMIN operations. Squashed from the following changes: - gate desktop transports/TUN by target_os, not features: a plain `cargo build` now compiles for every target with no flags. Ethernet (raw AF_PACKET / BPF) is gated to linux/macos, so Android (target_os = "android", not "linux") self-excludes it as Windows already did; real system-TUN ops are gated per linux/macos and Android gets a no-op stub; the ipi6_ifindex cast handles it being i32 on Android vs u32 on macOS. No Cargo features are introduced; desktop builds are unchanged. - app-owned TUN seam: Node::enable_app_owned_tun() lets an embedder that owns the TUN fd exchange IPv6 packet bytes with FIPS over channels instead of FIPS creating a system TUN device. It returns (app_outbound_tx, app_inbound_rx): the embedder pushes packets read from its fd into the outbound sender (app -> mesh) and pulls packets destined for its fd from the inbound receiver (mesh -> app). start() gates system-TUN creation on tun_tx being unset, so with the channels pre-installed it skips device creation and does no system-TUN ops; both directions reuse the existing inbound-shim and run_rx_loop wiring. Packets entering via app_outbound_tx bypass handle_tun_packet, so the embedder must push only fd00::/8-destined packets and clamp TCP MSS on outbound SYNs; the rustdoc and the IPv6-adapter design doc spell this out. - keep the android target warning-clean so the cross-compile check passes clippy -D warnings. - add an Android cross-compile CI check: cross-compile the library for aarch64-linux-android via cargo-ndk and run clippy -D warnings. Android ships as an embedded library (the host app owns the TUN), so there is no daemon binary to package; this is a check job, not a packaging one. - docs: list Android as a supported platform. Tests: app_owned_tun_seam_wires_channels covers the channel round-trip and the Active state; start_skips_system_tun_when_app_owned runs start() and asserts no named system device is created.
This commit is contained in:
42
.github/workflows/ci.yml
vendored
42
.github/workflows/ci.yml
vendored
@@ -88,6 +88,48 @@ jobs:
|
|||||||
${{ runner.os }}-cargo-
|
${{ runner.os }}-cargo-
|
||||||
- run: cargo clippy --all-targets --all-features -- -D warnings
|
- run: cargo clippy --all-targets --all-features -- -D warnings
|
||||||
|
|
||||||
|
# ───────────────────────────────────────────────────────────────────────────
|
||||||
|
# Android cross-check
|
||||||
|
#
|
||||||
|
# FIPS runs on Android as an embedded library — the host app owns the TUN
|
||||||
|
# (an Android VpnService), so there are no daemon binaries to package, unlike
|
||||||
|
# the desktop targets. This job only cross-compiles the library for the
|
||||||
|
# android target to guard the android-only cfg paths (and the `not(android)`
|
||||||
|
# exclusions) from silently bit-rotting; nothing else in CI compiles them.
|
||||||
|
# cargo-ndk wires the NDK toolchain, which is required even for a check
|
||||||
|
# because `ring` compiles C at build time.
|
||||||
|
# ───────────────────────────────────────────────────────────────────────────
|
||||||
|
android-check:
|
||||||
|
name: Android cross-check (aarch64)
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
- name: Install Rust toolchain (+ Android target)
|
||||||
|
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
|
with:
|
||||||
|
target: aarch64-linux-android
|
||||||
|
components: clippy
|
||||||
|
cache: false
|
||||||
|
rustflags: ''
|
||||||
|
- name: Cache Cargo registry + build
|
||||||
|
uses: actions/cache@v5
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cargo/registry
|
||||||
|
~/.cargo/git
|
||||||
|
target
|
||||||
|
key: ${{ runner.os }}-cargo-android-${{ hashFiles('**/Cargo.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-cargo-
|
||||||
|
- name: Install cargo-ndk
|
||||||
|
uses: taiki-e/install-action@v2
|
||||||
|
with:
|
||||||
|
tool: cargo-ndk
|
||||||
|
- name: Clippy the library for Android
|
||||||
|
run: |
|
||||||
|
export ANDROID_NDK_HOME="${ANDROID_NDK_HOME:-$ANDROID_NDK_LATEST_HOME}"
|
||||||
|
cargo ndk -t arm64-v8a clippy --lib -- -D warnings
|
||||||
|
|
||||||
build:
|
build:
|
||||||
name: Build (${{ matrix.os }})
|
name: Build (${{ matrix.os }})
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
|||||||
22
README.md
22
README.md
@@ -112,17 +112,19 @@ tutorial progression starting at
|
|||||||
cargo build --release
|
cargo build --release
|
||||||
```
|
```
|
||||||
|
|
||||||
Requires Rust 1.94.1+ (edition 2024). Linux, macOS, and Windows are
|
Requires Rust 1.94.1+ (edition 2024). Linux, macOS, and Windows run as
|
||||||
supported; transport availability varies by platform.
|
standalone daemons; Android is supported as an embedded library (the host
|
||||||
|
app owns the TUN, e.g. a `VpnService`). Transport availability varies by
|
||||||
|
platform.
|
||||||
|
|
||||||
| Transport | Linux | macOS | Windows | OpenWrt |
|
| Transport | Linux | macOS | Windows | Android | OpenWrt |
|
||||||
|-----------|:-----:|:-----:|:-------:|:-------:|
|
|-----------|:-----:|:-----:|:-------:|:-------:|:-------:|
|
||||||
| UDP | ✅ | ✅ | ✅ | ✅ |
|
| UDP | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||||
| TCP | ✅ | ✅ | ✅ | ✅ |
|
| TCP | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||||
| Ethernet | ✅ | ✅ | ❌ | ✅ |
|
| Ethernet | ✅ | ✅ | ❌ | ❌ | ✅ |
|
||||||
| Tor | ✅ | ✅ | ✅ | ✅ |
|
| Tor | ✅ | ✅ | ✅ | ❌ | ✅ |
|
||||||
| Nym | ✅ | ✅ | ✅ | ❌ |
|
| Nym | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||||
| BLE | ✅ | ❌ | ❌ | ❌ |
|
| BLE | ✅ | ❌ | ❌ | ❌ | ❌ |
|
||||||
|
|
||||||
On Linux, a source build requires `libclang` — the LAN gateway's
|
On Linux, a source build requires `libclang` — the LAN gateway's
|
||||||
nftables bindings are generated by `bindgen` at build time, which
|
nftables bindings are generated by `bindgen` at build time, which
|
||||||
|
|||||||
@@ -302,6 +302,34 @@ alternative — running under a dedicated unprivileged service
|
|||||||
account with the capability granted on the binary — see
|
account with the capability granted on the binary — see
|
||||||
[../how-to/run-as-unprivileged-user.md](../how-to/run-as-unprivileged-user.md).
|
[../how-to/run-as-unprivileged-user.md](../how-to/run-as-unprivileged-user.md).
|
||||||
|
|
||||||
|
### App-Owned TUN (embedded hosts)
|
||||||
|
|
||||||
|
On platforms where FIPS is embedded rather than run as a daemon — notably
|
||||||
|
Android, where the `VpnService` owns the TUN fd and the app has no
|
||||||
|
`CAP_NET_ADMIN` — FIPS does not create `fips0` itself. Instead the embedder owns
|
||||||
|
the fd and exchanges IPv6 packet bytes with FIPS over channels.
|
||||||
|
|
||||||
|
`Node::enable_app_owned_tun()` sets this up. It is called after `Node::new` and
|
||||||
|
before `start()` (and before the node is moved into a background task), mirroring
|
||||||
|
`control_read_handle()`, and returns two app-side channel ends:
|
||||||
|
|
||||||
|
- **app → mesh** — the embedder pushes IPv6 packets read from its fd into
|
||||||
|
`app_outbound_tx`. These are drained by `run_rx_loop` into `handle_tun_outbound`
|
||||||
|
and routed exactly as the Reader Thread's output would be.
|
||||||
|
- **mesh → app** — inbound mesh traffic on port 256 is reconstructed and written
|
||||||
|
to the node's `tun_tx` (the same sink the Writer Thread reads); the embedder
|
||||||
|
pulls from `app_inbound_rx` and writes to its fd.
|
||||||
|
|
||||||
|
With the channels installed, `start()` skips system-TUN creation (it gates on
|
||||||
|
`tun_tx` being unset), so FIPS does no `CAP_NET_ADMIN` operations.
|
||||||
|
|
||||||
|
Because packets enter via `app_outbound_tx` rather than the Reader Thread, they
|
||||||
|
**bypass `handle_tun_packet`** — the `fd00::/8` destination filter, the ICMPv6
|
||||||
|
Destination Unreachable for off-mesh dests (see [Reader Thread](#reader-thread)),
|
||||||
|
and the [TUN-Side TCP MSS Clamping](#tun-side-tcp-mss-clamping). The embedder is
|
||||||
|
therefore responsible for routing only `fd00::/8` to its TUN (so only mesh-bound
|
||||||
|
packets arrive) and for clamping TCP MSS on outbound SYNs.
|
||||||
|
|
||||||
## Implementation Status
|
## Implementation Status
|
||||||
|
|
||||||
| Feature | Status |
|
| Feature | Status |
|
||||||
|
|||||||
@@ -45,12 +45,10 @@ impl Node {
|
|||||||
/// (e.g. only non-UDP transports). Enabled on Linux and macOS:
|
/// (e.g. only non-UDP transports). Enabled on Linux and macOS:
|
||||||
/// both kernels route a matching peer 5-tuple to the connected
|
/// both kernels route a matching peer 5-tuple to the connected
|
||||||
/// socket when it shares the wildcard listen port via SO_REUSEPORT.
|
/// socket when it shares the wildcard listen port via SO_REUSEPORT.
|
||||||
|
/// Only compiled on Linux/macOS — the sole caller (the rx_loop tick) is
|
||||||
|
/// gated the same way, so on other targets (android) there is nothing to do.
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||||
pub(in crate::node) async fn activate_connected_udp_sessions(&mut self) {
|
pub(in crate::node) async fn activate_connected_udp_sessions(&mut self) {
|
||||||
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
|
|
||||||
{
|
|
||||||
// No-op on platforms without the connected-UDP fast path.
|
|
||||||
}
|
|
||||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
|
||||||
{
|
{
|
||||||
if !connected_udp_enabled() {
|
if !connected_udp_enabled() {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -1312,7 +1312,10 @@ impl Node {
|
|||||||
// Singleton child booleans, with today's exact enable conditions.
|
// Singleton child booleans, with today's exact enable conditions.
|
||||||
let nostr = self.config().node.rendezvous.nostr.enabled;
|
let nostr = self.config().node.rendezvous.nostr.enabled;
|
||||||
let mdns = self.config().node.rendezvous.lan.enabled;
|
let mdns = self.config().node.rendezvous.lan.enabled;
|
||||||
let tun = self.config().tun.enabled;
|
// No Tun child when the TUN is app-owned (the embedder pre-set
|
||||||
|
// `tun_tx` via `enable_app_owned_tun`) — FIPS does no system-TUN ops;
|
||||||
|
// the channels installed before `start` carry both directions.
|
||||||
|
let tun = self.config().tun.enabled && self.supervisor.tun_tx.is_none();
|
||||||
let dns = self.config().dns.enabled;
|
let dns = self.config().dns.enabled;
|
||||||
|
|
||||||
// Worker-pool booleans + counts. Unix only — the workers issue
|
// Worker-pool booleans + counts. Unix only — the workers issue
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ use crate::proto::lookup::{Lookup, LookupBackoff, LookupForwardRateLimiter};
|
|||||||
use crate::proto::mmp::Mmp;
|
use crate::proto::mmp::Mmp;
|
||||||
use crate::proto::routing::{self, Router, RoutingErrorRateLimiter};
|
use crate::proto::routing::{self, Router, RoutingErrorRateLimiter};
|
||||||
use crate::proto::stp::TreeState;
|
use crate::proto::stp::TreeState;
|
||||||
#[cfg(unix)]
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||||
use crate::transport::ethernet::EthernetTransport;
|
use crate::transport::ethernet::EthernetTransport;
|
||||||
use crate::transport::nym::NymTransport;
|
use crate::transport::nym::NymTransport;
|
||||||
use crate::transport::tcp::TcpTransport;
|
use crate::transport::tcp::TcpTransport;
|
||||||
@@ -62,7 +62,7 @@ use crate::transport::{
|
|||||||
};
|
};
|
||||||
use crate::upper::hosts::HostMap;
|
use crate::upper::hosts::HostMap;
|
||||||
use crate::upper::icmp_rate_limit::IcmpRateLimiter;
|
use crate::upper::icmp_rate_limit::IcmpRateLimiter;
|
||||||
use crate::upper::tun::{TunError, TunState, TunTx};
|
use crate::upper::tun::{TunError, TunOutboundTx, TunState, TunTx};
|
||||||
use crate::utils::index::IndexAllocator;
|
use crate::utils::index::IndexAllocator;
|
||||||
use crate::{Config, ConfigError, Identity, IdentityError, NodeAddr, PeerIdentity, TreeCoordinate};
|
use crate::{Config, ConfigError, Identity, IdentityError, NodeAddr, PeerIdentity, TreeCoordinate};
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
@@ -873,7 +873,7 @@ impl Node {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create Ethernet transport instances (Unix only — requires raw sockets)
|
// Create Ethernet transport instances (Unix only — requires raw sockets)
|
||||||
#[cfg(unix)]
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||||
{
|
{
|
||||||
let eth_instances: Vec<_> = self
|
let eth_instances: Vec<_> = self
|
||||||
.config()
|
.config()
|
||||||
@@ -1007,7 +1007,7 @@ impl Node {
|
|||||||
&self,
|
&self,
|
||||||
addr_str: &str,
|
addr_str: &str,
|
||||||
) -> Result<(TransportId, TransportAddr), NodeError> {
|
) -> Result<(TransportId, TransportAddr), NodeError> {
|
||||||
#[cfg(unix)]
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||||
{
|
{
|
||||||
let (iface, mac_str) = addr_str.split_once('/').ok_or_else(|| {
|
let (iface, mac_str) = addr_str.split_once('/').ok_or_else(|| {
|
||||||
NodeError::NoTransportForType(format!(
|
NodeError::NoTransportForType(format!(
|
||||||
@@ -1039,7 +1039,7 @@ impl Node {
|
|||||||
|
|
||||||
Ok((transport_id, TransportAddr::from_bytes(&mac)))
|
Ok((transport_id, TransportAddr::from_bytes(&mac)))
|
||||||
}
|
}
|
||||||
#[cfg(not(unix))]
|
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
|
||||||
{
|
{
|
||||||
Err(NodeError::NoTransportForType(
|
Err(NodeError::NoTransportForType(
|
||||||
"Ethernet transport is not supported on this platform".to_string(),
|
"Ethernet transport is not supported on this platform".to_string(),
|
||||||
@@ -2691,6 +2691,36 @@ impl Node {
|
|||||||
self.supervisor.tun_tx.as_ref()
|
self.supervisor.tun_tx.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set up an **app-owned TUN**: rather than FIPS creating a system TUN
|
||||||
|
/// device, the embedder (e.g. an Android `VpnService`) owns the fd and
|
||||||
|
/// exchanges IPv6 packet bytes with FIPS over the returned channels. Call
|
||||||
|
/// this after [`Node::new`] and **before** [`Self::start`] — and before
|
||||||
|
/// moving the node into a background task.
|
||||||
|
///
|
||||||
|
/// Returns `(app_outbound_tx, app_inbound_rx)`:
|
||||||
|
/// - push IPv6 packets read from the app's TUN fd into `app_outbound_tx`
|
||||||
|
/// (app → mesh); FIPS routes them to the destination node.
|
||||||
|
/// - pull IPv6 packets destined for the app's TUN fd from `app_inbound_rx`
|
||||||
|
/// (mesh → app) and write them to the fd (`recv_timeout` for clean stop).
|
||||||
|
///
|
||||||
|
/// With this set, [`Self::start`] skips system-TUN creation (it gates on
|
||||||
|
/// `tun_tx` being unset). Packets pushed into `app_outbound_tx` bypass the
|
||||||
|
/// system-TUN reader's `handle_tun_packet`, so the embedder must do what that
|
||||||
|
/// path otherwise would: push only `fd::/8`-destined IPv6 packets — FIPS no
|
||||||
|
/// longer filters the destination or emits ICMPv6 unreachable for off-mesh
|
||||||
|
/// dests — and clamp TCP MSS on outbound SYNs.
|
||||||
|
pub fn enable_app_owned_tun(&mut self) -> (TunOutboundTx, std::sync::mpsc::Receiver<Vec<u8>>) {
|
||||||
|
let tun_channel_size = self.config().node.buffers.tun_channel;
|
||||||
|
// app → mesh: the app pushes; `run_rx_loop` drains `tun_outbound_rx`.
|
||||||
|
let (outbound_tx, outbound_rx) = tokio::sync::mpsc::channel(tun_channel_size);
|
||||||
|
// mesh → app: the node writes inbound packets to `tun_tx`; the app pulls.
|
||||||
|
let (tun_tx, tun_rx) = std::sync::mpsc::channel();
|
||||||
|
self.supervisor.tun_tx = Some(tun_tx);
|
||||||
|
self.supervisor.tun_outbound_rx = Some(outbound_rx);
|
||||||
|
self.tun_state = TunState::Active;
|
||||||
|
(outbound_tx, tun_rx)
|
||||||
|
}
|
||||||
|
|
||||||
// === Sending ===
|
// === Sending ===
|
||||||
|
|
||||||
/// Encrypt and send a link-layer message to an authenticated peer.
|
/// Encrypt and send a link-layer message to an authenticated peer.
|
||||||
|
|||||||
@@ -2137,3 +2137,66 @@ async fn handle_msg1_admits_existing_peer_at_cap() {
|
|||||||
"rate limiter must rebalance after the (bypass-admitted) handler returns"
|
"rate limiter must rebalance after the (bypass-admitted) handler returns"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// App-owned TUN seam: `enable_app_owned_tun` wires the embedder's packet
|
||||||
|
/// channels (an Android `VpnService` owns the fd) and marks the TUN active so
|
||||||
|
/// `start()` skips system-TUN creation.
|
||||||
|
#[test]
|
||||||
|
fn app_owned_tun_seam_wires_channels() {
|
||||||
|
let mut config = crate::Config::new();
|
||||||
|
config.tun.enabled = true;
|
||||||
|
let mut node = make_node_with(config);
|
||||||
|
|
||||||
|
let (outbound_tx, tun_rx) = node.enable_app_owned_tun();
|
||||||
|
|
||||||
|
// TUN is active and the inbound (mesh→app) sender is installed, so `start()`
|
||||||
|
// will skip `TunDevice::create` (it gates on `tun_tx.is_none()`).
|
||||||
|
assert_eq!(node.tun_state(), crate::upper::tun::TunState::Active);
|
||||||
|
assert!(node.tun_tx().is_some(), "inbound sender installed");
|
||||||
|
|
||||||
|
// mesh → app: a packet the node delivers to its `tun_tx` reaches the app's rx.
|
||||||
|
let pkt = vec![0x60u8, 0, 0, 0, 0, 0];
|
||||||
|
node.tun_tx().unwrap().send(pkt.clone()).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
tun_rx
|
||||||
|
.recv_timeout(std::time::Duration::from_millis(200))
|
||||||
|
.unwrap(),
|
||||||
|
pkt,
|
||||||
|
"the app pulls the same bytes the node wrote",
|
||||||
|
);
|
||||||
|
|
||||||
|
// app → mesh: the returned sender is live (its matching rx is held by the node
|
||||||
|
// and drained by `run_rx_loop` → `handle_tun_outbound`).
|
||||||
|
assert!(outbound_tx.try_send(vec![0x60]).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// With an app-owned TUN configured, `start()` must NOT create a system TUN
|
||||||
|
/// device: it leaves `tun_name` unset (a real device records its interface name)
|
||||||
|
/// and keeps the TUN `Active` with the app-owned channels.
|
||||||
|
#[tokio::test]
|
||||||
|
async fn start_skips_system_tun_when_app_owned() {
|
||||||
|
// Mirror `make_healthy_node` (one loopback UDP transport so bring-up
|
||||||
|
// reaches `Full`), plus tun.enabled so the Tun child WOULD spawn if the
|
||||||
|
// app-owned gate failed.
|
||||||
|
let mut config = crate::Config::new();
|
||||||
|
config.transports.udp = crate::config::TransportInstances::Single(crate::config::UdpConfig {
|
||||||
|
bind_addr: Some("127.0.0.1:0".to_string()),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
config.dns.enabled = false;
|
||||||
|
config.tun.enabled = true;
|
||||||
|
let mut node = make_node_with(config);
|
||||||
|
|
||||||
|
let (_outbound_tx, _tun_rx) = node.enable_app_owned_tun();
|
||||||
|
node.start().await.unwrap();
|
||||||
|
|
||||||
|
// No system device was created (that path records the interface name); the
|
||||||
|
// app-owned TUN stayed active.
|
||||||
|
assert!(
|
||||||
|
node.tun_name().is_none(),
|
||||||
|
"app-owned TUN must not create a named system device",
|
||||||
|
);
|
||||||
|
assert_eq!(node.tun_state(), crate::upper::tun::TunState::Active);
|
||||||
|
|
||||||
|
node.stop().await.unwrap();
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ pub mod tcp;
|
|||||||
pub mod tor;
|
pub mod tor;
|
||||||
pub mod udp;
|
pub mod udp;
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||||
pub mod ethernet;
|
pub mod ethernet;
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
@@ -20,7 +20,7 @@ pub mod ble;
|
|||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
use ble::DefaultBleTransport;
|
use ble::DefaultBleTransport;
|
||||||
#[cfg(unix)]
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||||
use ethernet::EthernetTransport;
|
use ethernet::EthernetTransport;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use loopback::LoopbackTransport;
|
use loopback::LoopbackTransport;
|
||||||
@@ -664,7 +664,7 @@ pub enum TransportHandle {
|
|||||||
/// UDP/IP transport.
|
/// UDP/IP transport.
|
||||||
Udp(UdpTransport),
|
Udp(UdpTransport),
|
||||||
/// Raw Ethernet transport.
|
/// Raw Ethernet transport.
|
||||||
#[cfg(unix)]
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||||
Ethernet(EthernetTransport),
|
Ethernet(EthernetTransport),
|
||||||
/// TCP/IP transport.
|
/// TCP/IP transport.
|
||||||
Tcp(TcpTransport),
|
Tcp(TcpTransport),
|
||||||
@@ -685,7 +685,7 @@ impl TransportHandle {
|
|||||||
pub async fn start(&mut self) -> Result<(), TransportError> {
|
pub async fn start(&mut self) -> Result<(), TransportError> {
|
||||||
match self {
|
match self {
|
||||||
TransportHandle::Udp(t) => t.start_async().await,
|
TransportHandle::Udp(t) => t.start_async().await,
|
||||||
#[cfg(unix)]
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||||
TransportHandle::Ethernet(t) => t.start_async().await,
|
TransportHandle::Ethernet(t) => t.start_async().await,
|
||||||
TransportHandle::Tcp(t) => t.start_async().await,
|
TransportHandle::Tcp(t) => t.start_async().await,
|
||||||
TransportHandle::Tor(t) => t.start_async().await,
|
TransportHandle::Tor(t) => t.start_async().await,
|
||||||
@@ -701,7 +701,7 @@ impl TransportHandle {
|
|||||||
pub async fn stop(&mut self) -> Result<(), TransportError> {
|
pub async fn stop(&mut self) -> Result<(), TransportError> {
|
||||||
match self {
|
match self {
|
||||||
TransportHandle::Udp(t) => t.stop_async().await,
|
TransportHandle::Udp(t) => t.stop_async().await,
|
||||||
#[cfg(unix)]
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||||
TransportHandle::Ethernet(t) => t.stop_async().await,
|
TransportHandle::Ethernet(t) => t.stop_async().await,
|
||||||
TransportHandle::Tcp(t) => t.stop_async().await,
|
TransportHandle::Tcp(t) => t.stop_async().await,
|
||||||
TransportHandle::Tor(t) => t.stop_async().await,
|
TransportHandle::Tor(t) => t.stop_async().await,
|
||||||
@@ -717,7 +717,7 @@ impl TransportHandle {
|
|||||||
pub async fn send(&self, addr: &TransportAddr, data: &[u8]) -> Result<usize, TransportError> {
|
pub async fn send(&self, addr: &TransportAddr, data: &[u8]) -> Result<usize, TransportError> {
|
||||||
match self {
|
match self {
|
||||||
TransportHandle::Udp(t) => t.send_async(addr, data).await,
|
TransportHandle::Udp(t) => t.send_async(addr, data).await,
|
||||||
#[cfg(unix)]
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||||
TransportHandle::Ethernet(t) => t.send_async(addr, data).await,
|
TransportHandle::Ethernet(t) => t.send_async(addr, data).await,
|
||||||
TransportHandle::Tcp(t) => t.send_async(addr, data).await,
|
TransportHandle::Tcp(t) => t.send_async(addr, data).await,
|
||||||
TransportHandle::Tor(t) => t.send_async(addr, data).await,
|
TransportHandle::Tor(t) => t.send_async(addr, data).await,
|
||||||
@@ -733,7 +733,7 @@ impl TransportHandle {
|
|||||||
pub fn transport_id(&self) -> TransportId {
|
pub fn transport_id(&self) -> TransportId {
|
||||||
match self {
|
match self {
|
||||||
TransportHandle::Udp(t) => t.transport_id(),
|
TransportHandle::Udp(t) => t.transport_id(),
|
||||||
#[cfg(unix)]
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||||
TransportHandle::Ethernet(t) => t.transport_id(),
|
TransportHandle::Ethernet(t) => t.transport_id(),
|
||||||
TransportHandle::Tcp(t) => t.transport_id(),
|
TransportHandle::Tcp(t) => t.transport_id(),
|
||||||
TransportHandle::Tor(t) => t.transport_id(),
|
TransportHandle::Tor(t) => t.transport_id(),
|
||||||
@@ -749,7 +749,7 @@ impl TransportHandle {
|
|||||||
pub fn name(&self) -> Option<&str> {
|
pub fn name(&self) -> Option<&str> {
|
||||||
match self {
|
match self {
|
||||||
TransportHandle::Udp(t) => t.name(),
|
TransportHandle::Udp(t) => t.name(),
|
||||||
#[cfg(unix)]
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||||
TransportHandle::Ethernet(t) => t.name(),
|
TransportHandle::Ethernet(t) => t.name(),
|
||||||
TransportHandle::Tcp(t) => t.name(),
|
TransportHandle::Tcp(t) => t.name(),
|
||||||
TransportHandle::Tor(t) => t.name(),
|
TransportHandle::Tor(t) => t.name(),
|
||||||
@@ -765,7 +765,7 @@ impl TransportHandle {
|
|||||||
pub fn transport_type(&self) -> &TransportType {
|
pub fn transport_type(&self) -> &TransportType {
|
||||||
match self {
|
match self {
|
||||||
TransportHandle::Udp(t) => t.transport_type(),
|
TransportHandle::Udp(t) => t.transport_type(),
|
||||||
#[cfg(unix)]
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||||
TransportHandle::Ethernet(t) => t.transport_type(),
|
TransportHandle::Ethernet(t) => t.transport_type(),
|
||||||
TransportHandle::Tcp(t) => t.transport_type(),
|
TransportHandle::Tcp(t) => t.transport_type(),
|
||||||
TransportHandle::Tor(t) => t.transport_type(),
|
TransportHandle::Tor(t) => t.transport_type(),
|
||||||
@@ -781,7 +781,7 @@ impl TransportHandle {
|
|||||||
pub fn state(&self) -> TransportState {
|
pub fn state(&self) -> TransportState {
|
||||||
match self {
|
match self {
|
||||||
TransportHandle::Udp(t) => t.state(),
|
TransportHandle::Udp(t) => t.state(),
|
||||||
#[cfg(unix)]
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||||
TransportHandle::Ethernet(t) => t.state(),
|
TransportHandle::Ethernet(t) => t.state(),
|
||||||
TransportHandle::Tcp(t) => t.state(),
|
TransportHandle::Tcp(t) => t.state(),
|
||||||
TransportHandle::Tor(t) => t.state(),
|
TransportHandle::Tor(t) => t.state(),
|
||||||
@@ -797,7 +797,7 @@ impl TransportHandle {
|
|||||||
pub fn mtu(&self) -> u16 {
|
pub fn mtu(&self) -> u16 {
|
||||||
match self {
|
match self {
|
||||||
TransportHandle::Udp(t) => t.mtu(),
|
TransportHandle::Udp(t) => t.mtu(),
|
||||||
#[cfg(unix)]
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||||
TransportHandle::Ethernet(t) => t.mtu(),
|
TransportHandle::Ethernet(t) => t.mtu(),
|
||||||
TransportHandle::Tcp(t) => t.mtu(),
|
TransportHandle::Tcp(t) => t.mtu(),
|
||||||
TransportHandle::Tor(t) => t.mtu(),
|
TransportHandle::Tor(t) => t.mtu(),
|
||||||
@@ -816,7 +816,7 @@ impl TransportHandle {
|
|||||||
pub fn link_mtu(&self, addr: &TransportAddr) -> u16 {
|
pub fn link_mtu(&self, addr: &TransportAddr) -> u16 {
|
||||||
match self {
|
match self {
|
||||||
TransportHandle::Udp(t) => t.link_mtu(addr),
|
TransportHandle::Udp(t) => t.link_mtu(addr),
|
||||||
#[cfg(unix)]
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||||
TransportHandle::Ethernet(t) => t.link_mtu(addr),
|
TransportHandle::Ethernet(t) => t.link_mtu(addr),
|
||||||
TransportHandle::Tcp(t) => t.link_mtu(addr),
|
TransportHandle::Tcp(t) => t.link_mtu(addr),
|
||||||
TransportHandle::Tor(t) => t.link_mtu(addr),
|
TransportHandle::Tor(t) => t.link_mtu(addr),
|
||||||
@@ -832,7 +832,7 @@ impl TransportHandle {
|
|||||||
pub fn local_addr(&self) -> Option<std::net::SocketAddr> {
|
pub fn local_addr(&self) -> Option<std::net::SocketAddr> {
|
||||||
match self {
|
match self {
|
||||||
TransportHandle::Udp(t) => t.local_addr(),
|
TransportHandle::Udp(t) => t.local_addr(),
|
||||||
#[cfg(unix)]
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||||
TransportHandle::Ethernet(_) => None,
|
TransportHandle::Ethernet(_) => None,
|
||||||
TransportHandle::Tcp(t) => t.local_addr(),
|
TransportHandle::Tcp(t) => t.local_addr(),
|
||||||
TransportHandle::Tor(_) => None,
|
TransportHandle::Tor(_) => None,
|
||||||
@@ -848,7 +848,7 @@ impl TransportHandle {
|
|||||||
pub fn interface_name(&self) -> Option<&str> {
|
pub fn interface_name(&self) -> Option<&str> {
|
||||||
match self {
|
match self {
|
||||||
TransportHandle::Udp(_) => None,
|
TransportHandle::Udp(_) => None,
|
||||||
#[cfg(unix)]
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||||
TransportHandle::Ethernet(t) => Some(t.interface_name()),
|
TransportHandle::Ethernet(t) => Some(t.interface_name()),
|
||||||
TransportHandle::Tcp(_) => None,
|
TransportHandle::Tcp(_) => None,
|
||||||
TransportHandle::Tor(_) => None,
|
TransportHandle::Tor(_) => None,
|
||||||
@@ -888,7 +888,7 @@ impl TransportHandle {
|
|||||||
pub fn discover(&self) -> Result<Vec<DiscoveredPeer>, TransportError> {
|
pub fn discover(&self) -> Result<Vec<DiscoveredPeer>, TransportError> {
|
||||||
match self {
|
match self {
|
||||||
TransportHandle::Udp(t) => t.discover(),
|
TransportHandle::Udp(t) => t.discover(),
|
||||||
#[cfg(unix)]
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||||
TransportHandle::Ethernet(t) => t.discover(),
|
TransportHandle::Ethernet(t) => t.discover(),
|
||||||
TransportHandle::Tcp(t) => t.discover(),
|
TransportHandle::Tcp(t) => t.discover(),
|
||||||
TransportHandle::Tor(t) => t.discover(),
|
TransportHandle::Tor(t) => t.discover(),
|
||||||
@@ -904,7 +904,7 @@ impl TransportHandle {
|
|||||||
pub fn auto_connect(&self) -> bool {
|
pub fn auto_connect(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
TransportHandle::Udp(t) => t.auto_connect(),
|
TransportHandle::Udp(t) => t.auto_connect(),
|
||||||
#[cfg(unix)]
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||||
TransportHandle::Ethernet(t) => t.auto_connect(),
|
TransportHandle::Ethernet(t) => t.auto_connect(),
|
||||||
TransportHandle::Tcp(t) => t.auto_connect(),
|
TransportHandle::Tcp(t) => t.auto_connect(),
|
||||||
TransportHandle::Tor(t) => t.auto_connect(),
|
TransportHandle::Tor(t) => t.auto_connect(),
|
||||||
@@ -920,7 +920,7 @@ impl TransportHandle {
|
|||||||
pub fn accept_connections(&self) -> bool {
|
pub fn accept_connections(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
TransportHandle::Udp(t) => t.accept_connections(),
|
TransportHandle::Udp(t) => t.accept_connections(),
|
||||||
#[cfg(unix)]
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||||
TransportHandle::Ethernet(t) => t.accept_connections(),
|
TransportHandle::Ethernet(t) => t.accept_connections(),
|
||||||
TransportHandle::Tcp(t) => t.accept_connections(),
|
TransportHandle::Tcp(t) => t.accept_connections(),
|
||||||
TransportHandle::Tor(t) => t.accept_connections(),
|
TransportHandle::Tor(t) => t.accept_connections(),
|
||||||
@@ -942,7 +942,7 @@ impl TransportHandle {
|
|||||||
pub async fn connect(&self, addr: &TransportAddr) -> Result<(), TransportError> {
|
pub async fn connect(&self, addr: &TransportAddr) -> Result<(), TransportError> {
|
||||||
match self {
|
match self {
|
||||||
TransportHandle::Udp(_) => Ok(()), // connectionless
|
TransportHandle::Udp(_) => Ok(()), // connectionless
|
||||||
#[cfg(unix)]
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||||
TransportHandle::Ethernet(_) => Ok(()), // connectionless
|
TransportHandle::Ethernet(_) => Ok(()), // connectionless
|
||||||
TransportHandle::Tcp(t) => t.connect_async(addr).await,
|
TransportHandle::Tcp(t) => t.connect_async(addr).await,
|
||||||
TransportHandle::Tor(t) => t.connect_async(addr).await,
|
TransportHandle::Tor(t) => t.connect_async(addr).await,
|
||||||
@@ -962,7 +962,7 @@ impl TransportHandle {
|
|||||||
pub fn connection_state(&self, addr: &TransportAddr) -> ConnectionState {
|
pub fn connection_state(&self, addr: &TransportAddr) -> ConnectionState {
|
||||||
match self {
|
match self {
|
||||||
TransportHandle::Udp(_) => ConnectionState::Connected,
|
TransportHandle::Udp(_) => ConnectionState::Connected,
|
||||||
#[cfg(unix)]
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||||
TransportHandle::Ethernet(_) => ConnectionState::Connected,
|
TransportHandle::Ethernet(_) => ConnectionState::Connected,
|
||||||
TransportHandle::Tcp(t) => t.connection_state_sync(addr),
|
TransportHandle::Tcp(t) => t.connection_state_sync(addr),
|
||||||
TransportHandle::Tor(t) => t.connection_state_sync(addr),
|
TransportHandle::Tor(t) => t.connection_state_sync(addr),
|
||||||
@@ -981,7 +981,7 @@ impl TransportHandle {
|
|||||||
pub async fn close_connection(&self, addr: &TransportAddr) {
|
pub async fn close_connection(&self, addr: &TransportAddr) {
|
||||||
match self {
|
match self {
|
||||||
TransportHandle::Udp(t) => t.close_connection(addr),
|
TransportHandle::Udp(t) => t.close_connection(addr),
|
||||||
#[cfg(unix)]
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||||
TransportHandle::Ethernet(t) => t.close_connection(addr),
|
TransportHandle::Ethernet(t) => t.close_connection(addr),
|
||||||
TransportHandle::Tcp(t) => t.close_connection_async(addr).await,
|
TransportHandle::Tcp(t) => t.close_connection_async(addr).await,
|
||||||
TransportHandle::Tor(t) => t.close_connection_async(addr).await,
|
TransportHandle::Tor(t) => t.close_connection_async(addr).await,
|
||||||
@@ -1006,7 +1006,7 @@ impl TransportHandle {
|
|||||||
pub fn congestion(&self) -> TransportCongestion {
|
pub fn congestion(&self) -> TransportCongestion {
|
||||||
match self {
|
match self {
|
||||||
TransportHandle::Udp(t) => t.congestion(),
|
TransportHandle::Udp(t) => t.congestion(),
|
||||||
#[cfg(unix)]
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||||
TransportHandle::Ethernet(_) => TransportCongestion::default(),
|
TransportHandle::Ethernet(_) => TransportCongestion::default(),
|
||||||
TransportHandle::Tcp(_) => TransportCongestion::default(),
|
TransportHandle::Tcp(_) => TransportCongestion::default(),
|
||||||
TransportHandle::Tor(_) => TransportCongestion::default(),
|
TransportHandle::Tor(_) => TransportCongestion::default(),
|
||||||
@@ -1026,7 +1026,7 @@ impl TransportHandle {
|
|||||||
TransportHandle::Udp(t) => {
|
TransportHandle::Udp(t) => {
|
||||||
serde_json::to_value(t.stats().snapshot()).unwrap_or_default()
|
serde_json::to_value(t.stats().snapshot()).unwrap_or_default()
|
||||||
}
|
}
|
||||||
#[cfg(unix)]
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||||
TransportHandle::Ethernet(t) => {
|
TransportHandle::Ethernet(t) => {
|
||||||
serde_json::to_value(t.stats().snapshot()).unwrap_or_default()
|
serde_json::to_value(t.stats().snapshot()).unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -301,7 +301,7 @@ fn extract_pktinfo_ifindex(msg: &libc::msghdr) -> Option<u32> {
|
|||||||
if cmsg.cmsg_level == libc::IPPROTO_IPV6 && cmsg.cmsg_type == libc::IPV6_PKTINFO {
|
if cmsg.cmsg_level == libc::IPPROTO_IPV6 && cmsg.cmsg_type == libc::IPV6_PKTINFO {
|
||||||
let data_ptr = unsafe { libc::CMSG_DATA(cmsg_ptr) } as *const libc::in6_pktinfo;
|
let data_ptr = unsafe { libc::CMSG_DATA(cmsg_ptr) } as *const libc::in6_pktinfo;
|
||||||
let pktinfo: libc::in6_pktinfo = unsafe { std::ptr::read_unaligned(data_ptr) };
|
let pktinfo: libc::in6_pktinfo = unsafe { std::ptr::read_unaligned(data_ptr) };
|
||||||
return Some(pktinfo.ipi6_ifindex);
|
return Some(pktinfo.ipi6_ifindex as u32);
|
||||||
}
|
}
|
||||||
cmsg_ptr = unsafe { libc::CMSG_NXTHDR(msg, cmsg_ptr) };
|
cmsg_ptr = unsafe { libc::CMSG_NXTHDR(msg, cmsg_ptr) };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ use tracing::error;
|
|||||||
use tracing::{debug, trace};
|
use tracing::{debug, trace};
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use tracing::{error, warn};
|
use tracing::{error, warn};
|
||||||
#[cfg(unix)]
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||||
use tun::Layer;
|
use tun::Layer;
|
||||||
|
|
||||||
/// Read-only handle to the per-destination path MTU map. Populated by
|
/// Read-only handle to the per-destination path MTU map. Populated by
|
||||||
@@ -234,7 +234,10 @@ impl TunDevice {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the TUN device
|
// Create the TUN device. `mut` is only exercised on linux/macos, where
|
||||||
|
// the name/layer/mtu are set below; other unix targets (android) pass
|
||||||
|
// the default config through unchanged.
|
||||||
|
#[cfg_attr(not(any(target_os = "linux", target_os = "macos")), allow(unused_mut))]
|
||||||
let mut tun_config = tun::Configuration::default();
|
let mut tun_config = tun::Configuration::default();
|
||||||
|
|
||||||
// On macOS, utun devices get kernel-assigned names (utun0, utun1, ...),
|
// On macOS, utun devices get kernel-assigned names (utun0, utun1, ...),
|
||||||
@@ -1224,6 +1227,32 @@ mod windows_tun {
|
|||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
pub use windows_tun::{TunDevice, TunWriter, run_tun_reader, shutdown_tun_interface};
|
pub use windows_tun::{TunDevice, TunWriter, run_tun_reader, shutdown_tun_interface};
|
||||||
|
|
||||||
|
// Android uses an app-owned TUN (the embedder owns the fd, e.g. an Android
|
||||||
|
// VpnService); FIPS never creates or configures a system TUN here. These no-op
|
||||||
|
// stubs stand in for the platform ops so the shared TunDevice code compiles.
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
|
mod platform {
|
||||||
|
use super::TunError;
|
||||||
|
use std::net::Ipv6Addr;
|
||||||
|
|
||||||
|
pub fn is_ipv6_disabled() -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
pub async fn interface_exists(_name: &str) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
pub async fn delete_interface(_name: &str) -> Result<(), TunError> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
pub async fn configure_interface(
|
||||||
|
_name: &str,
|
||||||
|
_addr: Ipv6Addr,
|
||||||
|
_mtu: u16,
|
||||||
|
) -> Result<(), TunError> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
mod platform {
|
mod platform {
|
||||||
use super::TunError;
|
use super::TunError;
|
||||||
|
|||||||
Reference in New Issue
Block a user