Search code examples
socketswindows-10adamulticastgnat

Receiving multicast traffic using GNAT.Sockets


I am experimenting with IP multicasting in Ada, but doesn't seem to receive any traffic sent to the multicast group. Somehow, it seems like I cannot get the application to fetch the incoming packets.

I can verify (using Wireshark) that a multicast join is sent from my computer, and also I can verify that there is data being sent to the multicast group.

I can verify that OS has the multicast join registered by the netsh command:

netsh interfaces ip show joins

My group is listed with a reference of 1 if I run my program and 0 if it is not.

The following procedure shows my listener, and I invoke it using Mcast_IP => "239.255.128.128" and Mcast_Port => "8807":

with GNAT.Sockets;
with Ada.Streams;
with Ada.Text_IO;

   procedure Receive_Multicast (Mcast_IP   : in String;
                                Mcast_Port : in String)
   is
      package GS renames GNAT.Sockets;
      package AS renames Ada.Streams;
      package Tio renames Ada.Text_IO;

      use GS;

      use type Ada.Streams.Stream_Element_Offset;
      Socket  : GS.Socket_Type;
      Address : GS.Sock_Addr_Type;
      Data    : AS.Stream_Element_Array (1 .. 2**16);
      Offset  : AS.Stream_Element_Offset;
      Sender  : GS.Sock_Addr_Type;
   begin
      Address.Addr := Any_Inet_Addr;
      Address.Port := Port_Type'Value (Mcast_Port);

      Create_Socket (Socket => Socket,
                     Family => Family_Inet,
                     Mode   => Socket_Datagram);

      Bind_Socket (Socket, Address);

      --  Set socket options
      Set_Socket_Option (Socket,
                         Socket_Level,
                         (Reuse_Address, True));

      Set_Socket_Option
        (Socket,
         IP_Protocol_For_IP_Level,
         (Multicast_TTL, 1));

      Set_Socket_Option
        (Socket,
        IP_Protocol_For_IP_Level,
        (Multicast_Loop, True));

      Set_Socket_Option
        (Socket,
         IP_Protocol_For_IP_Level,
         (Add_Membership, Inet_Addr (Mcast_IP), Any_Inet_Addr));

      Tio.Put_Line ("Listening for MULTICASTS on port " & Address.Port'Img);

--  Receive the packet from the socket.
      loop
         Tio.Put_Line ("Waiting for incoming packets...");
         Receive_Socket (Socket => Socket,
                         Item   => Data,
                         Last   => Offset,
                         From   => Sender);
         Tio.Put_Line ("Received " & Offset'Img & " bytes.");
      end loop;
   end Receive_Multicast;

The procedure works its way down to the Receive_Socket call (which is a procedure in GNAT.Sockets package). However, even if I can confirm multicast traffic using Wireshark, the call to Receive_Socket keeps blocking.

UPDATE/SOLUTION:

The code above does actually work, although I had to completely uninstall Kaspersky which apparently did prevent multicasts sent from my own machine to be received (i.e. loopback). The accepted answer does also work flawlessly.


Solution

  • Based on the example in GNAT.Sockets, the code below should work. I've removed some options as they are not relevant for receiving.

    receive_multicast.ads

    procedure Receive_Multicast 
      (IP_Address : String;
       Port       : String);
    

    receive_multicast.adb

    with Ada.Text_IO;
    with Ada.Streams;
    with GNAT.Sockets;
    
    procedure Receive_Multicast 
      (IP_Address : String;
       Port       : String)
    is
    
       use GNAT.Sockets;
    
       Address  : Sock_Addr_Type;
       Socket   : Socket_Type;
    
    begin   
    
       Create_Socket (Socket, Family_Inet, Socket_Datagram);
    
       Set_Socket_Option
         (Socket => Socket,
          Level  => Socket_Level,
          Option => (Reuse_Address, True));
    
       Address.Addr := Any_Inet_Addr;
       Address.Port := Port_Type'Value (Port);
    
       Bind_Socket (Socket, Address);
    
       --  Join a multicast group
    
       --  Portability note: On Windows, this option may be set only
       --  on a bound socket.
    
       Set_Socket_Option
         (Socket => Socket,
          Level  => IP_Protocol_For_IP_Level,
          Option => (Add_Membership, Inet_Addr (IP_Address), Any_Inet_Addr));
    
       --  Receive the packet from the socket.
       declare
    
          use Ada.Text_IO;
          use Ada.Streams;
    
          Data    : Stream_Element_Array (1 .. 2**16);
          Offset  : Stream_Element_Offset;
          Sender  : Sock_Addr_Type;
    
       begin
          Put_Line ("Waiting for incoming packets...");
    
          Receive_Socket
            (Socket => Socket,
             Item   => Data,
             Last   => Offset,
             From   => Sender);
    
          Put_Line ("Received " & Offset'Image & " bytes.");
       end;
    
    end Receive_Multicast;
    

    main.adb

    with Receive_Multicast;
    
    procedure Main is
    begin   
       Receive_Multicast 
         (IP_Address => "239.255.128.128",
          Port       => "8807");   
    end Main;
    

    I couldn't test the code extensively, but when I open Windows PowerShell ISE, load and run the script Send-UdpDatagram.ps1 (see this GitHub Gist) and then execute:

    PS C:\> Send-UdpDatagram -EndPoint "239.255.128.128" -Port 8807 -Message "testing"
    

    Then the Ada program responds with:

    Waiting for incoming packets...
    Received  7 bytes.
    [2019-09-29 10:55:58] process terminated successfully, elapsed time: 07.60s
    

    Update

    I also tested the example code with a Raspberry Pi running Raspbian GNU/Linux 10 (buster):

    • Installed APT packages gnat and gprbuild on the Raspberry Pi.
    • Copied the code to the Raspberry Pi.
    • Compiled it with GNAT FSF (gprbuild -p <proj_name>.gpr).
    • Started four instances of the program, each in a separate terminal.
    • Emitted a packet from a Windows 10 host using the PowerShell function as before.

    The result was the same: the packet was received by all four program instances on the Raspberry Pi. While the programs were waiting for the packet, I could see the memberships (see also this post on SO):

    pi@raspberrypi:~ $ netstat -g
    IPv6/IPv4 Group Memberships
    Interface       RefCnt Group
    --------------- ------ ---------------------
    [...]
    eth0            4      239.255.128.128
    [...]
    
    pi@raspberrypi:~ $ netstat -anu | sort -nk4
    Active Internet connections (servers and established)
    Proto Recv-Q Send-Q Local Address           Foreign Address         State      
    [...]
    udp        0      0 0.0.0.0:8807            0.0.0.0:*                          
    udp        0      0 0.0.0.0:8807            0.0.0.0:*                          
    udp        0      0 0.0.0.0:8807            0.0.0.0:*                          
    udp        0      0 0.0.0.0:8807            0.0.0.0:*                          
    [...]