0

Basing on MC receiver from stack: How do you UDP multicast in Python?

I would like to completely understand what is going on. Here is what I understand and what not:

As far as I understand: socket_name = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) => creating socket with IP proto ver 4, that will receive MC datagrams using UDP.

socket_name.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) this is responsible for setting parameter that socket could use the same address

(and in this case, port, because SO_REUSEADDR = SO_REUSEPORT for Multicasts). (Paragraph multicast ~ How do SO_REUSEADDR and SO_REUSEPORT differ?)

if IS_ALL_GROUPS: socket_name.bind(('', MCAST_PORT)) means that if IS_ALL_GROUPS is true, bind socket to any addres, and else: socket_name.bind((MCAST_GRP, MCAST_PORT)) means bind socket to specific given IP address.

mreq = struct.pack("4sl", socket.inet_aton(MCAST_GRP), socket.INADDR_ANY) means convert IP address to binary form and socket_name.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq) means that we add membership of group and after this line packets starting to arrive.

However I don't understand some things.

Why when I used IS_ALL_GROUPS = true where MCAST_GRP = '239.0.1.104' program could start and when the parameter was false, it didn't bound to specific multicast address? My logic is that when the parameter is true it binds to any MCast addres he gets from IGMP join messages and when parameter is false, it binds to specific given address. Am I correct?

I have multithread program that analyses bitrate to which i provide more than one address in form of list. When I set IS_ALL_GROUPS to false, program works correctly printing out e.g. 10.5, 4.5, 5.0 where each result is bitrate of one stream from unique address, however all of addresses share the same port 12345. When I set IS_ALL_GROUPS to true, program sums up results giving 20.0, 20.0, 20.0, do you know what may be the cause?

import socket
import struct

MCAST_GRP = '239.0.1.104'
MCAST_PORT = 12345
IS_ALL_GROUPS = True

socket_name = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
if IS_ALL_GROUPS:
    # on this port, receives ALL multicast groups
    socket_name.bind(('', MCAST_PORT))
else:
    # on this port, listen ONLY to MCAST_GRP
    socket_name.bind((MCAST_GRP, MCAST_PORT))
mreq = struct.pack("4sl", socket.inet_aton(MCAST_GRP), socket.INADDR_ANY)

sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)

while True:
  print socket_name.recv(10240)
Yenjay
  • 55
  • 8
  • what program is this that "sums up results giving 20.0, 20.0, 20.0"? 20 is indeed the sum of 10.5, 4.5, 5.0, so if you're asking for everything maybe you're getting it.... – Sam Mason Sep 03 '19 at 12:33
  • I'm actually asking, if you know what causes that parameter `is_all_groups` to decide to merge 3 streams into one. For me, this parameter is just setting whether we scan all groups or any group. – Yenjay Sep 04 '19 at 06:20

1 Answers1

1

I think there are a few things going on:

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)

should probably be:

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0)

the only "datagram" based protocol for IPv4 is UDP, the protocol field is just for "raw sockets" which are used by programs like tcpdump. this shouldn't matter though.

next:

sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

just allows multiple sockets to be bound to this same (addr, port) combination at once, i.e. you can run multiple copies of your program at once, or in quick succession. which I think is what you're saying

next:

sock.bind((host, port))

says you want to receive packets destined for the a given address and port. the special address '' (i.e. INADDR_ANY) means that the kernel shouldn't do any filtering on address. you probably don't want to bind to any specific address at this stage, see What does it mean to bind a multicast (UDP) socket?

next:

mreq = struct.pack("4sl", socket.inet_aton(MCAST_GRP), socket.INADDR_ANY)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)

I'd suggest using "=4sl" for the format string as this gives me 8 bytes, rather than 16, and is consistent with a C struct ip_mreq on my (Linux) system. this difference in padding might just be a 32/64bit issue, and doesn't seem to hurt

to summarise: 1. the setsockopt(IP_ADD_MEMBERSHIP) call has asked your kernel's network stack to arrange (via IGMP) for multicast packets sent to MCAST_GRP to be delivered to a network interface attached to your host. 2. the bind() call has arranged for packets received via the network interface to make it to the socket in your process. it might be important that the bind() specifies INADDR_ANY so that all multicast packets that arrive are delivered to your process rather than being filtered

a couple of command lines that might be useful are:

tcpdump -i eth0 -vv 'ip proto 2'

which dumps IGMP packets, and:

tcpdump -i eth0 -vv 'net 224.0.0.0/4'

which dumps multicast packets

I'm not sure what your "multithread analysis program" is doing, as you've not given any relevant info. your posted code is also wrong, presumably socket_name and sock are the same? I'd also suggest using Python 3 as Python 2 is dying

Sam Mason
  • 15,216
  • 1
  • 41
  • 60