IPv6 UPnP multicast for Rust DLNA Server (MacOS)

Hello all,

I'm a hobbyist programmer and new to rust.

I'm trying to contribute to an issue on a rust DLNA server, hoping to get it working on MacOS.

Currently, I've boiled it down to the code below, which runs on Linux but panics on MacOS.

use std::net::{Ipv6Addr, UdpSocket};

fn ipv6() {
    let addr = Ipv6Addr::new(0xff02, 0, 0, 0, 0, 0, 0, 0xc);
    let socket = UdpSocket::bind("[::]:0").expect("couldn't bind socket");
    // socket.connect((addr, 1900)).expect("unable to connect");
    socket
        .join_multicast_v6(&addr, 0)
        .expect("couldn't join multicast");
    socket
        .send_to("foo ipv6".as_bytes(), (addr, 1900))
        .expect("couldn't send");
}

fn ipv4() {
    let socket = UdpSocket::bind("[::]:0").expect("couldn't bind socket");
    socket
        .connect("239.255.255.250:1900")
        .expect("couldn't connect");
    socket.send("foo ipv4".as_bytes()).expect("couldn't send");
}

fn main() {
    ipv4();
    ipv6();
}

Backtrace on MacOS:

$ RUST_BACKTRACE=1 cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.43s
     Running `target/debug/udp`
thread 'main' panicked at 'couldn't connect: Os { code: 22, kind: InvalidInput, message: "Invalid argument" }', src/libcore/result.rs:1009:5
stack backtrace:
   0: std::sys::unix::backtrace::tracing::imp::unwind_backtrace
             at src/libstd/sys/unix/backtrace/tracing/gcc_s.rs:49
   1: std::sys_common::backtrace::_print
             at src/libstd/sys_common/backtrace.rs:71
   2: std::panicking::default_hook::{{closure}}
             at src/libstd/sys_common/backtrace.rs:59
             at src/libstd/panicking.rs:211
   3: std::panicking::default_hook
             at src/libstd/panicking.rs:227
   4: <std::panicking::begin_panic::PanicPayload<A> as core::panic::BoxMeUp>::get
             at src/libstd/panicking.rs:491
   5: std::panicking::continue_panic_fmt
             at src/libstd/panicking.rs:398
   6: std::panicking::try::do_call
             at src/libstd/panicking.rs:325
   7: core::char::methods::<impl char>::escape_debug
             at src/libcore/panicking.rs:95
   8: core::result::unwrap_failed
             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libcore/macros.rs:26
   9: <core::result::Result<T, E>>::expect
             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libcore/result.rs:835
  10: udp::ipv4
             at src/main.rs:17
  11: udp::main
             at src/main.rs:24
  12: std::rt::lang_start::{{closure}}
             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libstd/rt.rs:74
  13: std::panicking::try::do_call
             at src/libstd/rt.rs:59
             at src/libstd/panicking.rs:310
  14: panic_unwind::dwarf::eh::read_encoded_pointer
             at src/libpanic_unwind/lib.rs:102
  15: <std::panicking::begin_panic::PanicPayload<A> as core::panic::BoxMeUp>::get
             at src/libstd/panicking.rs:289
             at src/libstd/panic.rs:398
             at src/libstd/rt.rs:58
  16: std::rt::lang_start
             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libstd/rt.rs:74
  17: udp::main

If I instead run only the ipv6 function on MacOS, I get:

$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.76s
     Running `target/debug/udp`
thread 'main' panicked at 'couldn't join multicast: Os { code: 49, kind: AddrNotAvailable, message: "Can\'t assign requested address" }', src/libcore/result.rs:1009:5
note: Run with `RUST_BACKTRACE=1` for a backtrace.

following advice from here about specifying the interface index on MacOS, if I change it to .join_multicast_v6(&addr, 5) (based on ifconfig -v), or if I comment out the part about joining multicast entirely, either way I get:

$ cargo run
   Compiling udp v0.1.0 (/Users/n8henrie/git/rustymedia/tmp/udp)
...
    Finished dev [unoptimized + debuginfo] target(s) in 2.32s
     Running `target/debug/udp`
thread 'main' panicked at 'couldn't send: Os { code: 65, kind: Other, message: "No route to host" }', src/libcore/result.rs:1009:5
note: Run with `RUST_BACKTRACE=1` for a backtrace.

So far, I've tried numerous combinations of binding to or connecting to [::]:0 vs [ffo2::c], but clearly I don't know what I'm doing or why this UPnP over ipv6 multicast stuff isn't working.

Any pointers on what I'm missing here? Thanks in advance.

EDIT: I also posted to r/rust's "Easy Question" thread recently and got a likely relevant response about IPV6_V6ONLY on MacOS.

One thing that bugs me is, that in ipv4 you bind on a IPv6 addr ([::], but try to connect to a IPv4 addr. Maybe that's the issue?
Afaik Linux does bind to IPv4 and IPv6 if you open a port on IPv6 (see man (7) ipv6, search for IPV6_V6ONLY).

Maybe you should take a look at that?

Thanks for your response.

Sorry I didn't include it initially, I started a thread on Reddit where a helpful individual made note of this and recommended I investigate the IPV6_V6ONLY setting. I had a hard time finding the thread last night when posting, but you motivated me to dig through and find it again, so I've added to the original post.

I used that simply because it's how the library I'm trying to update was written. I think it and the IPV6_V6ONLY flag is likely relevant to the crash on MacOS, which is why I'm trying to figure out how to do this in ipv6 multicast -- once I have that working, I hope to bind to [::] and use a match block to use an appropriate method for either ipv4 or ipv6 depending on what type of address was bound.