Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
MihaelBercic opened this issue Apr 10, 2024 · 16 comments
Labels
C-bug Category: This is a bug. O-macos Operating system: macOS T-libs Relevant to the library team, which will review and decide on the PR/issue.

Comments

@MihaelBercic
Copy link

So far tested on:

  • windows 11: works
  • macos 14.4: doesn't work

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
@MihaelBercic MihaelBercic added the C-bug Category: This is a bug. label Apr 10, 2024
@rustbot rustbot added the needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. label Apr 10, 2024
@bjorn3
Copy link
Member

bjorn3 commented Apr 11, 2024

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.

@bjorn3 bjorn3 added the O-macos Operating system: macOS label Apr 11, 2024
@MihaelBercic
Copy link
Author

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
Copy link
Member

bjorn3 commented Apr 11, 2024

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
Copy link
Author

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
Copy link
Member

bjorn3 commented Apr 11, 2024

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

@MihaelBercic
Copy link
Author

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
Copy link
Author

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
Copy link
Member

bjorn3 commented Apr 11, 2024

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
Copy link
Author

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
Copy link
Member

bjorn3 commented Apr 11, 2024

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.

@jieyouxu jieyouxu added the T-libs Relevant to the library team, which will review and decide on the PR/issue. label Apr 15, 2024
@saethlin saethlin removed the needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. label Apr 28, 2024
@darioalessandro
Copy link

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
Copy link

abhillman commented May 12, 2024

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
Copy link

abhillman commented May 12, 2024

EDIT: disregard, please see #123715 (comment)


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
Copy link
Member

@abhillman

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

@abhillman
Copy link

abhillman commented May 12, 2024

EDIT: likely unrelated, see #123715 (comment)


@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.

@RaubCamaioni
Copy link

RaubCamaioni commented Feb 13, 2025

@abhillman

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

Same issue on macOS sequoia 15.3 arm64
Firewall off, network sharing off, for all testing.
Python example code provided, using "INADDR_ANY" or address bind.

If binding to 0.0.0.0 or valid adapter, multicast messages are send out.
Oddly, when binding to 0.0.0.0, the source ip becomes 192.168.58.169.
This is not an address distributed by my DHCP server. Not sure where the last two octets are coming from.

Binding to 0.0.0.0 I can see multicast from the host but not remote.
Binding to adapter I receive no multicast messages.

Wireshark is also unable to capture multicast packets.

Is Mac always a pain when dealing with the network?
Is this something we can expect will be fixed?
Anyone have a working example/program capturing multicast packets on the network?
Maybe related to code signing and multicast entitlement?

import socket
import time
from functools import partial
from contextlib import suppress
from threading import Thread

MCAST_GRP = "239.2.3.1"
MCAST_PORT = 6969
ADAPTER = "192.168.1.195"

# create socket
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
# reuse addr/port required if other applications binding to same addr/port
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)

# binding to "all" interfaces
# most systems (windows, linux) "all" binds to default route interfaces
# binding to adapter does not receive packets
# bind to multicast address on linux
# bind = "0.0.0.0"
bind = ADAPTER
sock.bind((bind, MCAST_PORT))

# required: specify multicast output will throw following error otherwise
# OSError: [Errno 49] Can't assign requested address
# The error occurs on the "sendto" function
# Sendto must try binding to an invalid address if the IP_MULTICAST_IF is not given
sock.setsockopt(
    socket.IPPROTO_IP,
    socket.IP_MULTICAST_IF,
    socket.inet_aton(ADAPTER),
)

# required: send join requst to multicast group
sock.setsockopt(
    socket.IPPROTO_IP,
    socket.IP_ADD_MEMBERSHIP,
    socket.inet_aton(MCAST_GRP) + socket.inet_aton(ADAPTER),
)


# helper sending function
def sender(s_sock, interval=1):
    while True:
        time.sleep(interval)
        data = b"Hello World"
        print(f"send: {data}")
        s_sock.sendto(data, (MCAST_GRP, MCAST_PORT))


# send mutlicast message
Thread(target=partial(sender, sock), daemon=True).start()

# recv multicast
with suppress(KeyboardInterrupt):
    while True:
        data, addr = sock.recvfrom(1024)
        print(f"recv: {addr} - {data}")

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-bug Category: This is a bug. O-macos Operating system: macOS T-libs Relevant to the library team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

9 participants