macosudpmdns

mDNS / Bonjour / UDP 5353 Port reusability


I'm wondering if there is a way to allow the device (in this case MBP - MacOS 14.4) other programs to reuse the 5353 port that bonjour uses.

I'd like to also receive and listen to mDNS queries sent by others.

I do not want to disable mDNS responder completely, but would like to reuse the port.

My exact plan / goal:

The problem is, I'm unable to listen to port 5353 because it's already used by _mdns_responder by Apple.

The weirdest part is that JVM's MulticastSocket is able to bind and listen on port 5353 even with the Apple provider mDNSResponder running.

Thank you


Solution

  • So if anyone in the future comes across this issue, you have to know that you need to set both SO_REUSEPORT (socket reuse port) and SO_REUSEADDR (socket reuse address) since the addr:port is shared between all who listen on mDNS.

    Here is the gist of the code (references in code marked as [X]):

    use std::ffi::c_void;
    use std::mem::size_of;
    use std::net::{Ipv6Addr, UdpSocket};
    use std::os::fd::FromRawFd;
    use std::str::FromStr;
    
    use libc::{AF_INET6, bind, c_char, in6_addr, in_addr, perror, setsockopt, SO_REUSEADDR, SO_REUSEPORT, SOCK_DGRAM, sockaddr, sockaddr_in, sockaddr_in6, socket, socklen_t, SOL_SOCKET};
    
    fn main() {
        let error_text = "OPT FAILED" as *const _; // Set error text
        let multicast_ipv6 = Ipv6Addr::from_str("ff02::fb").unwrap(); // IPv6 representation of mDNS addresss.
        let fd = unsafe { socket(AF_INET6, SOCK_DGRAM, 0) }; // Use C bindings to create a socket [0]
        let option_value = 1; // Enable the option
        let cc = &option_value as *const _; // Rust <=> C type games...
    
        unsafe {
            let x = &sockaddr_in6 {
                sin6_family: 30, // AF_INET6
                sin6_port: 5353u16.to_be(), // Networking => Big Endian
                sin6_addr: in6_addr { s6_addr: [0u8; 16] }, // [::] => Any Address
                sin6_len: 0,
                sin6_flowinfo: 0,
                sin6_scope_id: 0,
            } as *const _ as *const sockaddr; // Rust <=> C type games
    
            // Set the SO_REUSEADDR option
            if setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, cc as *const c_void, size_of::<i32>() as socklen_t) < 0 {
                perror(error_text as *const c_char);
            }
    
            // Set the SO_REUSEPORT option
            if setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, cc as *const c_void, size_of::<i32>() as socklen_t) < 0 {
                perror(error_text as *const c_char);
            }
    
            if bind(fd, x, size_of::<sockaddr_in6>() as socklen_t) < 0 {
                perror(error_text as *const c_char);
            }
        }
        let mut socket = unsafe { UdpSocket::from_raw_fd(fd) }; // Retrieve the socket back from C bindings to Rust type.
    
    // This varies per device, in my case I wanted to join en7 network interface.
        let interface = netif::up().unwrap().filter(|x| x.name().contains("en7")).max_by(|x, y| x.scope_id().cmp(&y.scope_id())).unwrap();
    
        socket.join_multicast_v6(&multicast_ipv6, interface.scope_id().unwrap()).expect("Unable to join...");
    
        // Receive packets!
        let mut buffer = [0u8; 1000];
        loop {
            let (size, addr) = socket.recv_from(&mut buffer).unwrap();
            println!("Size: {} => {}", size, String::from_utf8_lossy(&buffer[..size]))
        }
    }