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.
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);
}
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.
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.
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.