Programmer
Programmer

Reputation: 150

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

Upvotes: 0

Views: 458

Answers (1)

Programmer
Programmer

Reputation: 150

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]):

  • [0] - We need to create a socket using C bindings because in order to set socket options, we have to do so before BINDING the socket.

  • Deps used: rust STD, netif (for network interface fetching) and libc for C bindings.

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]))
    }
}

Upvotes: 1

Related Questions