Skip to content

Multicast bikeshedding #457

@alabamenhu

Description

@alabamenhu

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.

Activity

alabamenhu

alabamenhu commented on Dec 19, 2024

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

alabamenhu commented on Dec 19, 2024

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

alabamenhu commented on Dec 19, 2024

@alabamenhu
Author

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

niner

niner commented on Dec 19, 2024

@niner

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

lizmat commented on Dec 19, 2024

@lizmat
Collaborator

Perhaps better an nqp::const value?

alabamenhu

alabamenhu commented on Dec 19, 2024

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

alabamenhu commented on Dec 26, 2024

@alabamenhu
Author

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

alabamenhu commented on Dec 31, 2024

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    languageChanges to the Raku Programming Language

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @niner@lizmat@alabamenhu

        Issue actions

          Multicast bikeshedding · Issue #457 · Raku/problem-solving