rust-libp2p icon indicating copy to clipboard operation
rust-libp2p copied to clipboard

build(ios): error on iOS with libp2p 0.56 using quic feature and if-watch 3.2.1

Open sando38 opened this issue 3 months ago • 3 comments

Hello,

I tried to build an application with the latest libp2p version 0.56 including quic features on iOS, however, I have the below compiler errors, which seem to be related to the issue #5628 and #5679. I see that if-watch v3.2.1 was used in the compilation.

Undefined symbols for architecture arm64:
  "_SCDynamicStoreCreateRunLoopSource", referenced from:
      system_configuration::dynamic_store::SCDynamicStore::create_run_loop_source::hc606d8191d1eee96 in libapp.a[1330](system_configuration-ef8f09e6c16c7e34.system_configuration.a183a244f0552131-cgu.0.rcgu.o)
  "_SCDynamicStoreCreateWithOptions", referenced from:
      system_configuration::dynamic_store::SCDynamicStore::create::h7b8d644247fa5cbe in libapp.a[1330](system_configuration-ef8f09e6c16c7e34.system_configuration.a183a244f0552131-cgu.0.rcgu.o)
  "_SCDynamicStoreSetNotificationKeys", referenced from:
      std::sys::backtrace::__rust_begin_short_backtrace::h50d2f1a25ee701a8 in libapp.a[1329](if_watch-7a230dd48f870c3b.if_watch.56977769d1be1092-cgu.0.rcgu.o)
  "_kSCDynamicStoreUseSessionKeys", referenced from:
      system_configuration::dynamic_store::SCDynamicStoreBuilder$LT$T$GT$::create_store_options::h88a4075b4fa8049f in libapp.a[1329](if_watch-7a230dd48f870c3b.if_watch.56977769d1be1092-cgu.0.rcgu.o)

I am not experienced with iOS internals, but from the errors above I suspect that i.e. SCDynamicStore does not exist in the iOS world.

I checked with Claude LLM the apple.rs module in if-watch and it suggested to patch it this way (if it makes sense), practically using the fallback polling on iOS which is also described in the if-watch README.md file.

--- a/src/apple.rs
+++ b/src/apple.rs
@@ -1,11 +1,17 @@
 use crate::{IfEvent, IpNet, Ipv4Net, Ipv6Net};
+#[cfg(target_os = "macos")]
 use core_foundation::array::CFArray;
+#[cfg(target_os = "macos")]
 use core_foundation::runloop::{kCFRunLoopCommonModes, CFRunLoop};
+#[cfg(target_os = "macos")]
 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};
+#[cfg(target_os = "macos")]
 use system_configuration::dynamic_store::{
     SCDynamicStore, SCDynamicStoreBuilder, SCDynamicStoreCallBackContext,
 };
@@ -40,7 +46,12 @@ pub struct IfWatcher {
 impl IfWatcher {
     pub fn new() -> Result<Self> {
         let (tx, rx) = mpsc::channel(1);
-        std::thread::spawn(|| background_task(tx));
+        
+        #[cfg(target_os = "macos")]
+        std::thread::spawn(move || background_task_macos(tx));
+        
+        #[cfg(not(target_os = "macos"))]
+        std::thread::spawn(move || background_task_ios(tx));
+        
         let mut watcher = Self {
             addrs: Default::default(),
             queue: Default::default(),
@@ -119,6 +130,7 @@ fn ifaddr_to_ipnet(addr: IfAddr) -> IpNet {
     }
 }
 
+#[cfg(target_os = "macos")]
 fn callback(_store: SCDynamicStore, _changed_keys: CFArray<CFString>, info: &mut mpsc::Sender<()>) {
     match info.try_send(()) {
         Err(err) if err.is_disconnected() => CFRunLoop::get_current().stop(),
@@ -126,7 +138,8 @@ fn callback(_store: SCDynamicStore, _changed_keys: CFArray<CFString>, info: &mu
     }
 }
 
-fn background_task(tx: mpsc::Sender<()>) {
+#[cfg(target_os = "macos")]
+fn background_task_macos(tx: mpsc::Sender<()>) {
     let store = SCDynamicStoreBuilder::new("global-network-watcher")
         .callback_context(SCDynamicStoreCallBackContext {
             callout: callback,
@@ -140,4 +153,24 @@ fn background_task(tx: mpsc::Sender<()>) {
     let source = store.create_run_loop_source();
     let run_loop = CFRunLoop::get_current();
     run_loop.add_source(&source, unsafe { kCFRunLoopCommonModes });
     CFRunLoop::run_current();
-}
\ No newline at end of file
+}
+
+#[cfg(not(target_os = "macos"))]
+fn background_task_ios(mut tx: mpsc::Sender<()>) {
+    // On iOS, tvOS, and watchOS, we use a polling approach
+    // Poll every 2 seconds for network interface changes
+    loop {
+        std::thread::sleep(std::time::Duration::from_secs(2));
+        
+        // Send a notification to trigger a resync
+        match tx.try_send(()) {
+            Ok(_) => {},
+            Err(err) => {
+                if err.is_disconnected() {
+                    // Receiver has been dropped, exit the thread
+                    break;
+                }
+                // If the channel is full, skip this notification
+            }
+        }
+    }
+}

Any feedback appreciated! Thanks

sando38 avatar Sep 16 '25 07:09 sando38

Hey @sando38. Thanks for reporting this. Does it happen only with quic or does it also happen with tcp?

dariusc93 avatar Sep 16 '25 13:09 dariusc93

Thanks for your quick answer. In my cargo.toml, I had features set to full. Practically I should only have features required ["quic", "kad", "mdns", "noise"]. Haven't narrowed it down yet. Anything particular I should try?

P.S. I checked the workspace crate libp2p-tcp, it also uses the if-watch dependency, meaning, the same error will occur as well.

sando38 avatar Sep 16 '25 14:09 sando38

Hi, and thanks for the interest, this should be submitted at the if-watch repo, can you get an isolated MRE? Thanks

jxs avatar Sep 19 '25 14:09 jxs