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

Multicast bikeshedding #457

Open
alabamenhu opened this issue Dec 19, 2024 · 8 comments
Open

Multicast bikeshedding #457

alabamenhu opened this issue Dec 19, 2024 · 8 comments
Labels
language Changes to the Raku Programming Language

Comments

@alabamenhu
Copy link

When IO::Socket::Async was designed, its bind-udp was developed just with unicast and broadcast modes in mind, hence it has the option :broadcast, defaulting to :unicast. MoarVM similarly was designed only to handle this option, though I've since added support for multicast.

Adding a :multicast option would, given the current interface, feel nice, but provide two incompatible named arguments. That is

IO::Socket::Async.bind-udp($host, :broadcast, :multicast);

The above makes no sense and will/should ultimately create an error somewhere further down the line at the VM level, which will provide a LTA error.

@alabamenhu alabamenhu added the language Changes to the Raku Programming Language label Dec 19, 2024
@alabamenhu
Copy link
Author

In a perfect world, we'd have:

IO::Socket::Async.bind-udp: $host, :type<unicast>;
IO::Socket::Async.bind-udp: $host, :type<multicast>;
IO::Socket::Async.bind-udp: $host, :type<broadcast>;

This would require a language revision, plus gating. But then we could throw a better error of having bad arguments / unsupport values (+ extra bikeshedding, as @timo suggested equally cromulent :cast<uni>, :cast<multi> and :cast<broad>

@alabamenhu
Copy link
Author

We could just throw an error when detecting :broadcast and :multicast. It's a simple check, and we could use the incompatible adverbs error. The docs describe them as adverb, although they could just as easy be envisioned as named args:

This returns an initialized IO::Socket::Async server object that is configured to receive UDP messages sent to the specified $host and $port and is equivalent to listen for a TCP socket. The :broadcast adverb can be specified to allow the receipt of messages sent to the broadcast address.

This method would be backwards compatible with current code.

@alabamenhu
Copy link
Author

We could do nothing. Any of the VMs should instead catch it or error internally, and pass it back up.

@niner
Copy link

niner commented Dec 19, 2024

The type argument should not be a string. That's what Enums are for. Otherwise we'd again have to validate that argument against an allowlist manually and there would not be any tool support for allowed values.

@lizmat
Copy link
Collaborator

lizmat commented Dec 19, 2024

Perhaps better an nqp::const value?

@alabamenhu
Copy link
Author

The type argument should not be a string. That's what Enums are for. Otherwise we'd again have to validate that argument against an allowlist manually and there would not be any tool support for allowed values.

Could do it like with the Endianness then:

IO::Socket::Async.bind-udp: $host, UDP-Unicast;
IO::Socket::Async.bind-udp: $host, UDP-Multicast;
IO::Socket::Async.bind-udp: $host, UDP-Broadcast;

@alabamenhu
Copy link
Author

alabamenhu commented Dec 26, 2024

As I've continued to refine the implementation, a few other wrinkles to consider:

  • Broadcast does not exist in IPv6
  • Multicast in IPv6 needs to specify an interface
  • Multicast in IPv6 also has a source-specific mode of operation

With regards to the interface, NodeJS says the OS will choose a default interface if not specified for IPv6, but can be specified (and virtually all IPv6 examples do include this, with some users reporting issues unless they do). NodeJS's manner is passing NULL to libuv's add_membership, which fails, at least, on macOS. Julia makes no mention of the group/interfaces being optional in their docs, but does the same. They explicitly disable testing on some systems, though, implying there's still some trouble to be figured out here, as BSD/Mac, Windows, and other *nix all handle the interfaces naming/indexing a teeny bit differently.

In other words, it seems like it's a really good idea for us to expose the ability to specify an interface, and probably also leaving it to module land to decide optimal defaults (and/or even saying attempts will be made without it specified, but no guarantees).

Regardless, we need to have a way to specify one or more interfaces.

Both NodeJS and Julia specify the broadcast/multicast status after creating the socket, akin to the following in Raku:

my $socket = IO::Socket::Async.bind-udp: '::', $port; 
$socket.join-multicast-group: $multicast-addr, $interface;

I don't necessarily think we need to go that route, since to me it's not immediately intuitive that you should bind to :: (or 0.0.0.0 for IPv4). In that sense

my $socket = IO::Socket::Async.bind-udp: $multicast-addr, $port, :multicast($interface [, ...]);

Seems much cleaner and intuitive. However, there are even more potential complexities that NodeJS and Julia support. Potential rendering in a single call as Raku currently does, versus the multicall way they do:

# Single call
my $socket = IO::Socket::Async.bind-udp: 
        $multicast-addr, 
        $port, 
        :multicast(
            $interface1,
            $interface2,
            $interface3,
            :filter(<source1 source2>)
         ) 


# Multi call (a la NodeJS / Julia) 
my $socket = IO::Socket::Async.bind-udp: '::', $port;
$socket.join-source-specific-multicast: $source1, $multicast-addr, $interface1;
$socket.join-source-specific-multicast: $source2, $multicast-addr, $interface1;
$socket.join-source-specific-multicast: $source1, $multicast-addr, $interface2;
$socket.join-source-specific-multicast: $source2, $multicast-addr, $interface2;
$socket.join-source-specific-multicast: $source1, $multicast-addr, $interface3;
$socket.join-source-specific-multicast: $source2, $multicast-addr, $interface3;

@alabamenhu
Copy link
Author

Actually, @timo had an even better idea. Multidispatch, such that the signatures would be:

multi method bind-udp(IO::Socket::Async:U: Str() $host, Int() $port)
multi method bind-udp(IO::Socket::Async:U: Str() $host, Int() $port, :$broadcast!)
multi method bind-udp(IO::Socket::Async:U: Str() $host, Int() $port, :$multicast!, 
    Int()  :$interface = 0,     # the interface ID (generally 0, but on Mac, `en0` might be >10)
    Str()  :$only-from,         # source specific host, if specified, only get packets from here
    Bool() :$loop-back = False, # receive our own messages?
)

The broadcast option doesn't really need a host, it's only available on IPv4 sockets and will always be 255.255.255.255, but it feels weird to have just the port with :broadcast. Instead, we could provide better error message if there's a problem binding to the host, suggesting 255.255.255.255 (on a given network this might reroute to 192.168.1.255, so that would also be valid, but the 4x255 functions like 127.0.0.1 as an alias).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
language Changes to the Raku Programming Language
Projects
None yet
Development

No branches or pull requests

3 participants