How do I interact with a IPv6 link-local address

In IPv6 there's this link-local address (that's dependent on an interface, from my part it's en0) ff02::1 (so the full address is actually ff02::1%en0). This is a multicast address.

I want to send a "Hello world!" udp datagram to it and check that multiple hosts on the local-link receive it:

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

fn main() {
    let multicast_address =
        Ipv6Addr::from_str("ff02::1%en0").expect("Can't create multicast address"); // FAILS HERE

    let multicast_socket = SocketAddrV6::new(multicast_address, 1234, 0, 0);

    let sender_socket = UdpSocket::bind("[::]:0").expect("Can't bind to local address");

    sender_socket
        .connect(multicast_socket)
        .expect("Can't connect to multicast socket");

    let message = b"hello world";

    sender_socket.send(message).expect("Can't send message");
}

The program fails because ff02::1%en0 can't be parsed. Is that expected? It works without the %en0 however, then the address becomes unreachable.

Pls help! :sob:

Ipv6Addr doesn't support specifying an interface. SocketAddrV6 supports this, but you must specify the interface index (it doesn't work when specifying the name).

You can do the following, assuming en0's index is 0:

    let multicast_socket = SocketAddrV6::from_str("[ff02::1%0]:1234").expect("Can't create multicast address");
    // ...

This doesn’t seem to be true. When called as ("address", port).to_socket_addrs(), the address string is passed straight to getaddrinfo(), which will resolve the interface name to an ID if it feels like it. For example, with an interface name of lo resolving to 1:

use std::net::ToSocketAddrs;

pub fn main() {
    let mut addrs = ("ff02::1%lo", 80).to_socket_addrs().unwrap();
    let addr = addrs.next().unwrap();
    println!("{:?}", addr);
}

(Playground)

Output:

[ff02::1%1]:80

I only tried with from_str, nice to see that to_socket_addrs works.

2 Likes

Agree both should work alike

ok, well thank you both! This versions seems to work now. However, I've tried to test it with a terminal prompt running:
nc -u -6 ff02::1%en0 1234
And I ran the new code, but no "Hello World!" showed up on the screen. Why?
It did however showed up if I ran it in "server mode" with -l:
nc -u -6 -l ff02::1%en0 1234

The code:

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

fn main() {
    let multicast_socket = ("ff02::1%en0", 1234)
        .to_socket_addrs()
        .expect("Can't parse multicast socket address")
        .next()
        .expect("Can't get multicast socket address");

    let sender_socket = UdpSocket::bind("[::]:0").expect("Can't bind to local address");

    sender_socket
        .connect(multicast_socket)
        .expect("Can't connect to multicast socket");

    let message = b"hello world";

    sender_socket.send(message).expect("Can't send message");
}

When you run netcat without the -l option, it tries to read from its input and write to the specified address; so

echo hello world | nc -u -6 ff02::1%en0 1234 

does something very similar to your Rust program. With the -l option, it copies from the network to its standard output, printing what your Rust program sends.

By the way, the connect() method takes anything implementing ToSocketAddrs as its parameter, so you can write .connect(("ff02::1%en0", 1234)) and make your program a bit shorter. This is how bind("[::]:0") works. It would have made it harder to see which step was failing while you were debugging the program, though.

1 Like

ff02::1 being "all hosts", why do you still have to join? In Rust, indeed I don't get the packet without joining.

With nc it gets it:
nc -u -6 -l fe80::1814:6285:1fe9:94b7%en0 1234

Why is that?

As far as I know, yes, at least for sending messages. In this case ff02::1 is the “all hosts” multicast address, so it’s basically a broadcast. I don’t understand how to correctly construct an IPv6 address for more specific multicast groups.

If you want to receive multicast traffic, you also need to use UdpSocket::join_multicast_v6() to start with, and possibly some of the other options from socket2.

2 Likes

I've tried:

use std::net::{ToSocketAddrs, UdpSocket};
use std::thread;

fn main() {
    let multicast_socket = ("ff02::1%en0", 1234)
        .to_socket_addrs()
        .expect("Can't parse multicast socket address")
        .next()
        .expect("Can't get multicast socket address");

    let sender_socket = UdpSocket::bind("[::]:0").expect("Can't bind to local address");

    sender_socket
        .connect(multicast_socket)
        .expect("Can't connect to multicast socket");

    let task = thread::spawn(move || {
        let receiver_socket = UdpSocket::bind("fe80::1814:6285:1fe9:94b7%en0:1234")
            .expect("Can't bind receiver socket");

        let mut buffer = [0; 1024];
        match receiver_socket.recv(&mut buffer) {
            Ok(received) => {
                println!("Received {} bytes", received);
                println!("Message: {:?}", &buffer[..received]);
            }
            Err(e) => eprintln!("Error receiving message: {}", e),
        }
    });

    let message = b"hello world";

    sender_socket.send(message).expect("Can't send message");

    task.join().expect("Can't join task");
}

It just hangs, indicating it's still waiting to receive a packtet

Ok, update, if I send a nc packet it does indeed receive it, even so, without using the join_multicast_v6 method:
echo "hello" | nc -u -6 ff02::1%en0 1234

Last update, it does work with Rust as well! I can confirm it's just a dead-lock error. As sometimes it seems the main thread gets to send the packet before the second one can get to listen for it.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.