2

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.

chrillof
  • 325
  • 2
  • 15
  • It's totally pointless to enable reuse-address*after* you've already bound the socket. – user207421 Sep 29 '19 at 03:52
  • Well, it doesn't complain and it doesn't seem to matter for the outcome whether I put it just before I join the multicast group. – chrillof Sep 29 '19 at 07:27
  • As @user207421 already stated, please take care of when you set a socket option (before or after binding). See also the multicast example given in the spec of `GNAT.Sockets` (see [here](https://gcc.gnu.org/viewcvs/gcc/trunk/gcc/ada/libgnat/g-socket.ads?view=markup#l172) and [here](https://gcc.gnu.org/viewcvs/gcc/trunk/gcc/ada/libgnat/g-socket.ads?view=markup#l214)). – DeeDee Sep 29 '19 at 07:53
  • It doesn't complain, but it doesn't do anything either. How could it possibly? – user207421 Sep 29 '19 at 08:57
  • As I said, it did not seem to matter wherever I put the binding of the socket until after all socket set options. I did try it out before I gave my reply. – chrillof Sep 29 '19 at 15:15
  • @DeeDee, I am aware that setting socket operations must be delicately planned, and I've tried various locations with the bind-operation, avail with no luck. My implementation is somewhat based on the specification you refer to, and strange as it may be, it doesn't work even if I use that literal example. – chrillof Sep 29 '19 at 16:09

1 Answers1

5

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:*                          
[...]
DeeDee
  • 5,654
  • 7
  • 14
  • Thanks for the reply. I used your literal example, but the Ada program still blocks at the receive operation. The packet being sent from the PowerShell script does show up in Wireshark and also if I try a Java implementation of what I'm trying to accomplish in Ada it works flawlessly. This is buggering me as it shouldn't be a hard thing to do... – chrillof Sep 29 '19 at 16:06
  • Ok, there's not much I can think of except for the obvious like a Firewall somehow interfering. Just for info: what version of GNAT are you using? I tested my example with GNAT CE 2019. – DeeDee Sep 29 '19 at 18:50
  • I'm using the latest GNAT CE 2019, and I have tried turning off all firewalls and anti-virus. Can it be some network adapter settings in Windows? I see the IGMP join message when I start the Ada program, so it seems like it's using the correct interface... – chrillof Sep 29 '19 at 20:23
  • A follow-up question. Are you running both the client and server on the same host, and both using Windows 10? – chrillof Oct 03 '19 at 20:58
  • Yes, both on the same host, using Windows 10. – DeeDee Oct 04 '19 at 06:06
  • I also tested the example code with a Raspberry Pi. Result was the same: packets were received (see updated answer). – DeeDee Oct 06 '19 at 08:37
  • I really do appreciate your effort and from what I can tell, there seems to be a problem related to my computer. Both my initial question as well as your example works flawlessly on my laptop, hence the accepted answer. – chrillof Oct 08 '19 at 20:14
  • On a side note, my laptop AND my desktop computer receives the multicasts when sent from my laptop. However, sending the other way around, packages are only seen on my laptop. Running both yours and my implementation simultaneously on my desktop occationally worked as intended, although it looks like the UDP buffer is read twice (i.e. it looks like it received several identical packets). However, I cannot get it working locally on my desktop computer again, but I figure that is another matter which I'll try to solve. Thanks again for your help. – chrillof Oct 08 '19 at 20:25
  • 1
    I've updated the original question with a paragraph explaining that Kaspersky seemed to trick me. When I had it completely removed/uninstalled (and not only disabled) everything works as expected. – chrillof Oct 21 '19 at 20:13
  • Good to hear you found the issue and thanks for updating the question! This may help other people too :-). – DeeDee Oct 21 '19 at 20:48