rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
97.93k stars 12.68k forks source link

[MacOS] UDP Multicast: Can't assign requested address on interface 0 (as suggested in the documentation) #123715

Open MihaelBercic opened 6 months ago

MihaelBercic commented 6 months ago

So far tested on:

The following code

use std::net::{Ipv6Addr, SocketAddrV6, UdpSocket};
use std::str::FromStr;

fn main() {
    let local = SocketAddrV6::new(Ipv6Addr::UNSPECIFIED, 61666, 0, 0);
    let multicast = Ipv6Addr::from_str("ff02::fb").unwrap();
    let udp = UdpSocket::bind(&local).expect("Unable to bind!");
    udp.join_multicast_v6(&multicast, 0).expect("Unable to join to multicast...");

    match udp.send_to(b"Hello", "[::1]:61666") { // Trying to send a packet to multicast address...
        Ok(bytes_sent) => println!("Sent {} bytes", bytes_sent),
        Err(err) => eprintln!("Error sending data: {}", err),
    }
}

produces the following error:

Unable to join to multicast...: Os { code: 49, kind: AddrNotAvailable, message: "Can't assign requested address" }

It is expected for the socket to join the group (in documentation it also says setting interface to 0 allows for the OS to decide on the interface).

However instead of that, on macOS the above error happens. On windows however, it does not and works as expected.

Meta

rustc --version --verbose:

rustc 1.77.2 (25ef9e3d8 2024-04-09)
binary: rustc
commit-hash: 25ef9e3d85d934b27d9dada2f9dd52b1dc63bb04
commit-date: 2024-04-09
host: aarch64-apple-darwin
release: 1.77.2
LLVM version: 17.0.6
bjorn3 commented 6 months ago

https://stackoverflow.com/questions/65534342/receiving-udp-multicast-on-macos-big-sur suggests that the firewall blocks receiving multicast packets unless "Block all incoming connections" and "stealth mode" are disabled. From a quick search more people seem to be having problems with multicast support on macOS in general. Not just with rust.

MihaelBercic commented 6 months ago

https://stackoverflow.com/questions/65534342/receiving-udp-multicast-on-macos-big-sur suggests that the firewall blocks receiving multicast packets unless "Block all incoming connections" and "stealth mode" are disabled. From a quick search more people seem to be having problems with multicast support on macOS in general. Not just with rust.

Hello, thank you for the reply. I have both disabled but still unable to receive any multicast packets.

In JVM (Kotlin and Java), I was able to receive multicast packets just fine.

image
bjorn3 commented 6 months ago

Is the java executable signed? If so "Automatically allow downloaded signed software to receive incoming connections" would apply to it, unlike rustc compiled executables, which you probably need to allowlist manually.

MihaelBercic commented 6 months ago

Is the java executable signed? If so "Automatically allow downloaded signed software to receive incoming connections" would apply to it, unlike rustc compiled executables, which you probably need to allowlist manually.

But if that'd be the case, after disabling the firewall I would be able to receive the packets right? However even if disabling the firewall, nothing changes.

bjorn3 commented 6 months ago

Yeah, it should. Can you check if a C program is able to receive UDP multicast packets as sanity check?

MihaelBercic commented 6 months ago

Yeah, it should. Can you check if a C program is able to receive UDP multicast packets as sanity check?

I will give it my best. I'm not that familiar with C but once i have it written I'll reply to this issue.

MihaelBercic commented 6 months ago

Yeah, it should. Can you check if a C program is able to receive UDP multicast packets as sanity check?

https://gist.github.com/MihaelBercic/557a0386b45e5e912ff493725dfff1f2

This code doesn't work either, can you check if it's the code issue or is it actually not working.

bjorn3 commented 6 months ago

I don't see anything obviously bad with it, but I'm not all that familiar with the socket api either. In any case for me on linux running that program seems to work as expected when I use iperf to send it multicast packets.

MihaelBercic commented 6 months ago

I don't see anything obviously bad with it, but I'm not all that familiar with the socket api either. In any case for me on linux running that program seems to work as expected when I use iperf to send it multicast packets.

Does the code also catch packets that are not sent by yourself? Such as tvs, spotify mdns queries and such?

bjorn3 commented 6 months ago

I looked in wireshark for any multicast packets that are sent my direction by other devices at home, but the only ones I could find were not udp multicast packets and thus wouldn't get caught by this test program.

darioalessandro commented 5 months ago

Just to add some color to this ticket, here's my program:

https://github.com/security-union/rust-projects-to-inspire-you/blob/master/control-accelerometer/src/bin/server.rs

the python counterpart works just fine: https://github.com/security-union/rust-projects-to-inspire-you/blob/master/control-accelerometer/server.py

My firewall is off.

same code works flawlessly on Debian

abhillman commented 5 months ago

EDIT: this is almost certainly unrelated as it was due to lo0 not having 127.0.0.1 as an alias for that interface on my machine. Although adding that alias via sudo ifconfig lo0 alias 127.0.0.1 "fixes" the below code, it does not fix the code posted by @MihaelBercic above.


I am running into this as well on macOS. Here is a simple repro:

$ cat > example.rs
use std::net::TcpListener;

fn main() {
  let _ = TcpListener::bind("127.0.0.1:0").unwrap();
}
$ rustc example.rs && ./example
thread 'main' panicked at example.rs:4:44:
called `Result::unwrap()` on an `Err` value: Os { code: 49, kind: AddrNotAvailable, message: "Can't assign requested address" }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Nightly has the same bug:

$ rustc --version 
rustc 1.80.0-nightly (78a775127 2024-05-11)
$ rustc example.rs && ./example 
thread 'main' panicked at example.rs:4:44:
called `Result::unwrap()` on an `Err` value: Os { code: 49, kind: AddrNotAvailable, message: "Can't assign requested address" }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

% uname -a
Darwin ok.localdomain 23.4.0 Darwin Kernel Version 23.4.0: Wed Feb 21 21:44:54 PST 2024; root:xnu-10063.101.15~2/RELEASE_ARM64_T6031 arm64
% rustc --version
rustc 1.78.0 (9b00956e5 2024-04-29)
abhillman commented 5 months ago

EDIT: disregard, please see https://github.com/rust-lang/rust/issues/123715#issuecomment-2106082837


Ugh. This is probably a macOS thing:

$ cat ok.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>

int main() {
    int sockfd;
    struct sockaddr_in server_addr;
    socklen_t addr_len = sizeof(server_addr);

    // Create socket
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        perror("Socket creation failed");
        exit(EXIT_FAILURE);
    }

    // Bind to 127.0.0.1:0
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
    server_addr.sin_port = 0;  // Let the OS choose the port

    if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        perror("Bind failed");
        exit(EXIT_FAILURE);
    }

    // Get the chosen port
    if (getsockname(sockfd, (struct sockaddr *)&server_addr, &addr_len) < 0) {
        perror("Getsockname failed");
        exit(EXIT_FAILURE);
    }

    // Print the chosen port
    printf("Chosen port: %d\n", ntohs(server_addr.sin_port));

    return 0;
}
$ clang ok.c -o ok && ./ok
Bind failed: Can't assign requested address

That said, this code works on macOS, so there is something interesting that Python is doing that seems to allow for things to "just work": python3 -c 'import socket; s=socket.socket(); s.bind(("", 0)); print(s.getsockname()[1]); s.close()'

weihanglo commented 5 months ago

@abhillman

The python example is not equivalent to others; an empty string implies binding to any interface (INADDR_ANY).

abhillman commented 5 months ago

EDIT: likely unrelated, see https://github.com/rust-lang/rust/issues/123715#issuecomment-2106082837


@abhillman

The python example is not equivalent to others; an empty string implies binding to any interface (INADDR_ANY).

Thanks for the reminder. Indeed. And the C code above does work if we change to INADDR_ANY.