Commit f3c8dd09 authored by eta's avatar eta
Browse files

onionmasq-apple: cleanup, make it work with latest main

parent 687e1776
Loading
Loading
Loading
Loading
+159 −627

File changed.

Preview size limit exceeded, changes collapsed.

+1 −5
Original line number Diff line number Diff line
@@ -6,7 +6,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[features]
# Enable verbose logcat logging, only useful for debugging purposes.
# Enable verbose logging, only useful for debugging purposes.
# You should NOT enable this in production builds for privacy reasons.
verbose = []

@@ -15,12 +15,8 @@ anyhow = "1.0"
backtrace = "0.3.69"
lazy_static = "1.4.0"
onion-tunnel = { path = "../onion-tunnel", version = "0.1.0" }
# NOTE(eta): the below 2 are only required for working around arti#988; we don't actually use them ;w;
tor-keymgr = { git = "https://gitlab.torproject.org/eta/arti", branch = "exit-selection-draft", features = ["keymgr"] }
tor-config = { git = "https://gitlab.torproject.org/eta/arti", branch = "exit-selection-draft" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
simple_logger = "1"
tokio = { version = "1", features = ["macros", "rt", "rt-multi-thread"] }
tokio-stream = "0.1"
tracing = "0.1.37"
+10 −0
Original line number Diff line number Diff line
@@ -23,6 +23,16 @@ pub extern "C" fn runProxy(
) {
    panic_handling::capture_unwind!({
        let oma = OnionmasqApple::get();
        let cache_dir = unsafe {
            CStr::from_ptr(cache_dir)
                .to_str()
                .expect("invalid UTF-8 in cache_dir")
        };
        let data_dir = unsafe {
            CStr::from_ptr(data_dir)
                .to_str()
                .expect("invalid UTF-8 in data_dir")
        };
        if let Err(e) = oma.run_proxy(cache_dir, data_dir, reader, writer) {
            error!("runProxy() returned error: {e:?}");
        }
+22 −29
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@ use ffi_event::TunnelEvent;
use futures::StreamExt;
use once_cell::sync::OnceCell;
use onion_tunnel::accounting::BandwidthCounter;
use onion_tunnel::config::TunnelConfig;
use onion_tunnel::scaffolding::TunnelCommand;
use onion_tunnel::{CountryCode, OnionTunnel};
use std::ffi::{c_char, CStr, CString};
@@ -25,8 +26,6 @@ use std::str::FromStr;
use std::sync::{Arc, Mutex, RwLock};
use tokio::runtime::Runtime;
use tokio::sync::mpsc;
use tor_config::{BoolOrAuto, CfgPath};
use tor_keymgr::config::arti::ArtiNativeKeystoreConfig;
use tracing::{debug, error, info, warn};
use tracing_subscriber::fmt::{Layer, Subscriber};
use tracing_subscriber::layer::SubscriberExt;
@@ -37,9 +36,9 @@ static ONIONMASQ_APPLE_SINGLETON: OnceCell<OnionmasqApple> = OnceCell::new();
// We want to avoid logging detailed connection-level stuff in production builds to avoid
// embarrassing privacy issues.
#[cfg(feature = "verbose")]
static LOG_FILTER: &str = "info,onionmasq_mobile=trace,onion_tunnel=trace,arti_client=debug,tor_chanmgr=debug,tor_proto=debug,smoltcp=debug";
static LOG_FILTER: &str = "info,onionmasq_apple=trace,onion_tunnel=trace,arti_client=debug,tor_chanmgr=debug,tor_proto=debug,smoltcp=debug";
#[cfg(not(feature = "verbose"))]
static LOG_FILTER: &str = "info,onionmasq_mobile=debug";
static LOG_FILTER: &str = "info,onionmasq_apple=debug";

pub(crate) type BandwidthCounterMap = Arc<DashMap<u64, Arc<BandwidthCounter>>>;

@@ -77,6 +76,7 @@ impl OnionmasqApple {
            panic!("OnionmasqApple::new() called twice!");
        }

        // FIXME(eta): We could just pass the C thing in directly maybe.
        let log_fn = Arc::new(move |log: *const c_char| {
            (log_fn)(log);
        });
@@ -126,18 +126,11 @@ impl OnionmasqApple {
    /// Run the `onion-tunnel` proxy.
    pub fn run_proxy(
        &self,
        cache_dir: *const c_char,
        data_dir: *const c_char,
        cache_dir: &str,
        data_dir: &str,
        reader: ReaderCallback,
        writer: WriterCallback,
    ) -> Result<()> {
        let cache_dir = unsafe { CStr::from_ptr(cache_dir) }
            .to_str()
            .context("failed to get string cache_dir")?;
        let data_dir = unsafe { CStr::from_ptr(data_dir) }
            .to_str()
            .context("failed to get string for data_dir")?;

        let (command_sender, command_receiver) = mpsc::unbounded_channel();
        let (bootstrap_done_sender, mut bootstrap_done_receiver) = mpsc::unbounded_channel();

@@ -145,21 +138,15 @@ impl OnionmasqApple {

        let pcap_path = self.pcap_path.lock().unwrap().clone();

        let mut config = onion_tunnel::TorClientConfigBuilder::from_directories(
            format!("{data_dir}/arti-data"),
            format!("{cache_dir}/arti-cache"),
        );

        // HACK(eta): work around for arti#988
        let aks = ArtiNativeKeystoreConfig::builder()
            .enabled(BoolOrAuto::Explicit(false))
            .path(CfgPath::new("/hopefully-ignored/".into()))
            .build()
            .expect("arti#988 workaround failed");
        let mut config = TunnelConfig::default();

        config.storage().keystore(aks);
        config.state_dir = Some(format!("{data_dir}/arti-data").into());
        config.cache_dir = Some(format!("{cache_dir}/arti-cache").into());
        config.pt_dir = Some(format!("{cache_dir}/arti-pts").into());

        let config = config.build().context("failed to make a TorClientConfig")?;
        if let Some(pp) = pcap_path {
            config.pcap_path = Some(pp.into());
        }

        let counters = self.bandwidth_counters.clone();
        let country_code = self.country_code.clone();
@@ -176,7 +163,6 @@ impl OnionmasqApple {
                },
                NePacketTunnelFlow::new(reader, writer),
                config,
                pcap_path,
            )
            .await
            .context("couldn't start onionmasq proxy")?;
@@ -294,7 +280,7 @@ impl OnionmasqApple {
    fn on_tunnel_event(&self, evt: TunnelEvent) -> Result<()> {
        let serialized = serde_json::to_string(&evt).context("failed to serialize TunnelEvent")?;

        let cstr = CString::new(serialized.to_owned())
        let cstr = CString::new(serialized)
            .context("failed to create CString from serialized TunnelEvent")?;
        (self.event_fn)(cstr.as_ptr());

@@ -335,6 +321,10 @@ impl OnionmasqApple {
    }
}

/// This implements `Write` and forwards all written data to a C callback function.
///
// NOTE(eta): I'm pretty sure I wrote this during the Brussels hacking session over tla's
//            shoulder. Looking back at it now, there's a few issues with it...
#[derive(Clone)]
struct CallbackWriter<F> {
    func: Arc<F>,
@@ -354,12 +344,15 @@ where
    F: Fn(*const c_char) + Send + Sync + 'static,
{
    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
        // FIXME(eta): should we not buffer each line?

        let cstr = CString::new(buf.to_owned())
            .context("failed to create CString from log string")
            .unwrap();

        (self.func)(cstr.as_ptr());

        return Ok(buf.len());
        Ok(buf.len())
    }

    fn flush(&mut self) -> std::io::Result<()> {
+28 −35
Original line number Diff line number Diff line
//! A custom tunnel device type that interfaces with Apple's `NEPacketTunnelFlow` [1].
//!
//! (Really, it just lets you write and read packets with C callbacks, but that's what it's designed
//! to be used for.)
//!
//! [1]: https://developer.apple.com/documentation/networkextension/nepackettunnelflow

use crate::{ReaderCallback, WriterCallback};
use std::collections::VecDeque;
use std::io::{Error, ErrorKind};
use std::pin::Pin;
use std::slice::from_raw_parts;
use std::slice;
use std::sync::Mutex;
use std::task::{Context, Poll, Waker};
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};

static BUFFER: Mutex<VecDeque<Vec<u8>>> = Mutex::new(VecDeque::new());

static WAKER: Mutex<Option<Waker>> = Mutex::new(None);

pub struct NePacketTunnelFlow {
    reader: ReaderCallback,

    writer: WriterCallback,
}

@@ -22,31 +27,23 @@ impl NePacketTunnelFlow {
        NePacketTunnelFlow { reader, writer }
    }

    pub unsafe fn receive(packets: *const *const u8, lens: *const usize, len: usize) {
        match BUFFER.lock() {
            Ok(mut data) => {
                for i in 0..len {
                    let packet = *packets.offset(i as isize);
                    let len = *lens.offset(i as isize);
                    let slice = from_raw_parts(packet, len);
    pub unsafe fn receive(
        packets: *const *const u8,
        packet_lengths: *const usize,
        packet_count: usize,
    ) {
        let mut data = BUFFER.lock().expect("buffer poisoned");

        for i in 0..packet_count {
            let packet = *packets.add(i);
            let len = *packet_lengths.add(i);
            let slice = slice::from_raw_parts(packet, len);

            data.push_back(slice.to_vec());
        }
            }
            Err(_) => {
                // Ignore?
            }
        }

        match WAKER.lock() {
            Ok(mut waker) => {
                if waker.is_some() {
                    waker.take().unwrap().wake();
                }
            }
            Err(_) => {
                // Well. At least, *when* Onionmasq wants to read again, there's going to be data available.
            }
        if let Some(waker) = WAKER.lock().expect("waker poisoned").take() {
            waker.wake();
        }
    }
}
@@ -57,26 +54,22 @@ impl AsyncRead for NePacketTunnelFlow {
        cx: &mut Context<'_>,
        buf: &mut ReadBuf<'_>,
    ) -> Poll<std::io::Result<()>> {
        let packet = match BUFFER.lock() {
            Ok(mut guard) => guard.pop_front(),
            Err(_) => None,
        };
        let mut buffer = BUFFER.lock().expect("buffer poisoned");

        return match packet {
        match buffer.pop_front() {
            Some(packet) => {
                buf.put_slice(packet.as_slice());

                Poll::Ready(Ok(()))
            }
            None => {
                // Force unwrap here. If an error should happen, everything is already FUBAR'd anyway.
                let mut guard = WAKER.lock().unwrap();
                let mut waker = WAKER.lock().expect("waker poisoned");

                // Check, if we're already waiting for a read to complete.
                let already_reading = guard.is_some();
                let already_reading = waker.is_some();

                // Replace the waker with the new one in any case.
                *guard = Some(cx.waker().clone());
                *waker = Some(cx.waker().clone());

                if !already_reading {
                    (self.reader)();
@@ -84,7 +77,7 @@ impl AsyncRead for NePacketTunnelFlow {

                Poll::Pending
            }
        };
        }
    }
}