Receiving multicast traffic using GNAT.Sockets - 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.

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:*
[...]

Related

LuaSocket How to bind to interface?

Is there a SO_BINDTODEVICE equivalent or workaround for LuaSocket?
I've tried:
ifconfig to fetch the inet addr of my interface (e.g. ethA 1.1.1.1) + setpeername("1.1.1.1", 0). When I tcpdump on "ethA", I don't see my packet. Not too sure what the difference between bind versus bindtodevice is - I thought bindtodevice was just a shortcut to fetch the ip address from the interface name but that doesn't seem to be the case.
local udp = socket.udp()
udp:settimeout(1)
udp:setsockname("1.1.1.1", 0)
udp:setpeername("2.2.2.2", 12345)
udp:send(query)
The ip-multicast-if from https://tst2005.github.io/lua-socket/udp.html which was the only thing that mentioned interface in the documentation didn't seem to work.
local udp = socket.udp()
udp:setoption("ip-multicast-if", "1.1.1.1")
udp:settimeout(1)
udp:setsockname("*", 0) -- I've also tried "1.1.1.1" here and it didn't work.
udp:setpeername("2.2.2.2", 12345)
udp:send(query)
I see that it may be an option for luaposix, but I don't have that package and I don't want to bring in an additional dependency just for this.

how to capture packets sent by XDP user space program

Say I have XDP kernel and user programs. Kernel code examines ingress packets and "forwards" them to XDP user space program. XDP user space program then does some processing and sends packets out (could be same NIC or other one).
For controlling reasons we want to examine the outgoing packets sent by XDP user space code. Our first idea was using tc and insert BPF code to egress part, clearly didn't work.
Any idea?
EDIT1: egress program:
SEC("tx")
int track_tx(struct __sk_buff *skb)
{
__u64 t2 = bpf_ktime_get_ns();
void *data_end = (void *)(long)skb->data_end;
void *data = (void *)(long)skb->data;
bpf_custom_printk("\n__________send length %d\n", data_end-data);
return TC_ACT_OK;
}
run:
sudo tc filter add dev ens13 egress bpf da obj layercoop.o sec tx
EDIT2: Code
USer: https://github.com/torvalds/linux/blob/master/samples/bpf/xdpsock_user.c
Kernel (not interesting): https://github.com/torvalds/linux/blob/master/samples/bpf/xdpsock_kern.c
I want to forward incoming packets, so run it with make -j 4; sudo ./xdpsock -i ens13 -l

Why does sending ^D with netcat not trigger an EOF when reading from a Unix socket?

I am trying to write a server that will read from a Unix socket:
use std::io::prelude::*;
use std::os::unix::net::{UnixListener, UnixStream};
fn main() {
let socket_name = "socket";
let listener = match UnixListener::bind(&socket_name) {
Err(err) => panic!("Failed to bind to socket: {}.", err),
Ok(stream) => stream,
};
for mut stream in listener.incoming() {
match stream {
Ok(ref mut stream) => {
let msg = read(stream);
stream.write_all(msg.as_bytes()).expect("Echo");
}
Err(err) => panic!("Error occured when listening from the stream. {}", err),
}
}
fn read(stream: &mut UnixStream) -> String {
let mut s = String::new();
stream.read_to_string(&mut s).unwrap();
s
}
}
(playground)
On the client side I use nc: nc -U socket. I send some data and end it with ^D which should be an EOF. The docs for read_to_string say:
Read all bytes until EOF in this source, appending them to buf
Expected behavior:
After sending ^D on the client side the server responds with echo
Observed behavior:
Server doesn't recognize that EOF was sent and blocks. Only when the client breaks the connection the server prints the message and panics with the broken pipeline.
I send some data and end it with ^D which should be an EOF
It is, to the input of nc. That doesn't mean that the socket itself is closed:
When netcat is faced with an EOF on its standard input, it may or may
not close the sending part of its TCP connection, depending on which
version of netcat it is.
Solved: netcat (nc) doesn’t terminate at end of transmission
ctrl+d which sends an EOF on netcat's stdin: netcat noticed the EOF. It will send no further data through the socket. However, it
continues running and reading from the socket in case the server has
more data to send.
Unix : Epoll, catch ctrl+d and ctrl+c in server
For what it's worth, I cannot reproduce your problem on macOS 10.14.1 with the system-supplied nc.
You might be able to use the -q or -w options for your version of nc:
Assuming that after sending EOF connection will stay idle, you can use -w timeout option, which works for timeout being equal to zero
netcat doesn't terminate when stdin closes
You could also try being non-interactive:
$ echo 'hello' | nc -U socket
hello
See also:
read_to_end method never returns for UnixStream
Reading from a TcpStream with Read::read_to_string hangs until the connection is closed by the remote end
I had a similar issue. Try the nc -N flag.
This flag causes nc to send a shutdown(2) of the TCP socket when the input reaches EOF.
On an older version of Ubuntu (verified on 14.04), it behaved as though -N was always passed (and nc did not support -N).
On newer versions (verified on Ubuntu 20.04), it only exhibits this behavior when the -N flag is set.

IP_ADD_MEMBERSHIP fails when set both on interface and its subinterface; is that expected?

I'm debugging a 3rd-party network application and trying to figure out why it reports errors when calling setsockopt with IP_ADD_MEMBERSHIP to set up a multicast group. The application is in C++, but I've written an MWE in python that replicates the same syscalls:
import socket
import struct
ETH0_IP = "192.168.88.85"
ETH0_1_IP = "192.168.88.254"
MULTICAST_IP = "224.0.0.7"
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
ip = socket.inet_aton(ETH0_IP)
s.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_IF, ip)
group = struct.pack("4s4s", socket.inet_aton(MULTICAST_IP), ip)
s.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, group)
# s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
s2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
ip2 = socket.inet_aton(ETH0_1_IP)
s2.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_IF, ip2)
group2 = struct.pack("4s4s", socket.inet_aton(MULTICAST_IP), ip2)
# the second group is added to the first socket so that we can only bind to one socket and read data from it
s.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, group2)
At the second IP_ADD_MEMBERSHIP call I get error OSError: [Errno 98] Address already in use.
I found out this only happens when ETH0_1_IP is a subinterface of ETH_0_IP. And I'm not sure if this is expected. If it is, is there a way to actually detect this situation and discard subinterfaces of already bound interfaces? Further, would my multicast socket receive data sent to the subinterface if registration for it fails with the above error?
For the sake of completeness:
$ cat /etc/network/interfaces
auto lo
iface lo inet loopback
iface lo inet6 loopback
auto eth0:1
iface eth0:1 inet static
address 192.168.88.254
netmask 255.255.240.0
Linux is tracking your alias interface as the same interface and so rejecting the attempt to re-use the interface.
In a bit more detail, I have run your code successfully on CentOS 7 using two separate physical interfaces with no changes. If I then change the code to use an alias on the same physical address, it fails with the same error that you see.
Digging a little further, I see that if I dump the interface indeces (using SIOCGIFINDEX) for the physical adaptor and the alias, they do indeed have the same index.
If you want to use Python to check this for yourself, have a quick look at https://gist.github.com/firaxis/0e538c8e5f81eaa55748acc5e679a36e for some code (missing imports of ctypes and socket) and then try something like this:
print(Interface(name="eth0").index)
print(Interface(name="eth0:1").index)

Lua Socket cannot be properly stopped by Ctrl+C

I have a standalone lua script that uses lua sockets to connect to a server via TCP IP. It uses receive call to receive data from that server. It works, however, when I try to stop it with Ctrl+C, one of the two scenarios is happening:
-If there is currently no traffic and receive is waiting, Ctrl+C will have no effect. The program will continue to run, and will have to be terminated by kill.
-If there is traffic, the program will exit with the below printout and with the socket still open and with the server not accepting another connection:
lua: luaSocketTest.lua:15: interrupted!
stack traceback:
[C]: in function 'receive'
luaSocketTest.lua:15: in function 'doWork'
luaSocketTest.lua:22: in main chunk
[C]: ?
I tried using pcall to solve the second scenario, without success. pcall doesn't return, the process still throws the error.
Sample of my program is below:
local socket = require ("socket")
local ip = "localhost"
local port = 5003
function doWork ()
print ("Starting socket: "..ip..":"..port)
client = assert(socket.connect(ip, port))
print ("Socket Accepted")
client:send("TEST TEST")
while 1 do
local byte, err = client:receive (1)
if not err then
print (byte)
end
end
end
while 1 do
local status = pcall(doWork())
print ("EXITED PCALL WITH STATUS: "..tostring(status))
if not status then client:close() end
end
This would be quite a change, but you could employ lua-ev. It allows to add Signal handlers, which is exactly what is required to react to ctrl-c.
local socket = require'socket'
-- make connect and send in blocking mode
local client = socket.connect(ip,port)
client:send('TEST TEST')
-- make client non-blocking
client:settimeout(0)
ev.IO.new(function()
repeat
local data,err,part = client:receive(10000)
print('received',data or part)
until err
end,client:getfd(),ev.READ):start(ev.Loop.default)
local ev = require'ev'
local SIGINT = 2
ev.Signal.new(function()
print('SIGINT received')
end,SIGINT):start(ev.Loop.default)
ev.Loop.default:loop()