7

I'm using multicast UDP over localhost to implement a loose collection of cooperative programs running on a single machine. The following code works well on Mac OSX, Windows and linux. The flaw is that the code will receive UDP packets outside of the localhost network as well. For example, sendSock.sendto(pkt, ('192.168.0.25', 1600)) is received by my test machine when sent from another box on my network.

import platform, time, socket, select

addr = ("239.255.2.9", 1600)

sendSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sendSock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 24)
sendSock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_IF, 
    socket.inet_aton("127.0.0.1"))

recvSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
recvSock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
if hasattr(socket, 'SO_REUSEPORT'):
    recvSock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, True)

recvSock.bind(("0.0.0.0", addr[1]))
status = recvSock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, 
    socket.inet_aton(addr[0]) + socket.inet_aton("127.0.0.1"));

while 1:
    pkt = "Hello host: {1} time: {0}".format(time.ctime(), platform.node())
    print "SEND to: {0} data: {1}".format(addr, pkt)
    r = sendSock.sendto(pkt, addr)

    while select.select([recvSock], [], [], 0)[0]:
        data, fromAddr = recvSock.recvfrom(1024)
        print "RECV from: {0} data: {1}".format(fromAddr, data)

    time.sleep(2)

I've attempted to recvSock.bind(("127.0.0.1", addr[1])), but that prevents the socket from receiving any multicast traffic. Is there a proper way to configure recvSock to only accept multicast packets from the 127/24 network, or do I need to test the address of each received packet?

Shane Holloway
  • 7,550
  • 4
  • 29
  • 37

4 Answers4

6

Contrary to what has been stated in other answers here, IPv4 supports TTL-based multicast scoping, as follows:

0: node-local (not forwarded outside the current host)
1: link-local (not forwarded outside the current subnet)
< 32: site-local
< 64: region-local
< 128: continent-local
< 255: global

(It also supports Administratively Scoped Multicast.)

Source: W.R. Stevens, Unix Network Programming, 2nd edition, Vol I, section 19.2, with corrections to match RFC 2365.

user207421
  • 305,947
  • 44
  • 307
  • 483
  • I thought TTL-based scoping is only limiting for the sending side. How could it be used to filter incoming packets on an untrusted network? – Shane Holloway Feb 22 '13 at 18:02
2

Unfortunately, multicast IP doesn't have any such "filtering by subnetwork" feature -- so, unless you want to muck with IPTables (on Linux) or equivalent "firewall" SW/HW of your system/network to try and "drop on the floor" every multicast packet you don't like, I think you'll have to do it at application level (with a test on fromAddr in your inner loop, for example). Is the IP traffic from other hosts so much it degrades your performance...?

Alex Martelli
  • 854,459
  • 170
  • 1,222
  • 1,395
  • Not much traffic, more of an opportunity for a packet injection security breach. I'd rather have it dealt with structurally than at each site's usage. But then there is wishing and there is work. – Shane Holloway May 21 '10 at 16:04
  • 1
    @Shane, if Mordred fakes 127.0.0.1 packets to you, you can _definitely_ keep those out with IPTables and the like -- multicast or not, anything purporting to be "from 127.0.0.1" but actually coming in through any actual interface is clearly bogus -- so it's definitely easy to deal with "structurally" (but it's totally a serverfault problem, **not** a stackoverflow one, as the code you write has nothing to do with the issue, it's all about the proper administration of systems and networks). – Alex Martelli May 21 '10 at 21:50
1

If your host supports IPv6, you can use the scope component of the multicast address (this is the 'x' in the multicast prefix FF0x:) to implicitly restrict both incoming and outbound packets to the local host, by specifying a scope of 1 (e.g. use the IPv6 multicast address FF01::107 for the "name service server" on the local host only). Unfortunately the IPv4 multicast mechanism does not have an explicit scope, and RFC 2365 (http://tools.ietf.org/html/rfc2365) which defines administratively scoped IPv4 multicast ranges, does not define a node-local scope address, only a link-local scope range.

Alex Dupuy
  • 5,984
  • 3
  • 39
  • 35
0

You can use connect() to 127.0.0.1 on the multicast socket, then the IP stack can filter for you.

Updated with source code to demonstrate

You can run this script multiple times on one host and see the multicast packets distributed:

#!/usr/bin/python

import platform, time, socket, select

addr = ("239.255.2.9", 1600)

sendSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sendSock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 24)
sendSock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_IF,
    socket.inet_aton("127.0.0.1"))

recvSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
recvSock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
if hasattr(socket, 'SO_REUSEPORT'):
    recvSock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, True)

recvSock.bind(("0.0.0.0", addr[1]))
status = recvSock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP,
    socket.inet_aton(addr[0]) + socket.inet_aton("127.0.0.1"));

sendSock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
if hasattr(socket, 'SO_REUSEPORT'):
    sendSock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, True)
sendSock.bind(("127.0.0.1", addr[1]));
recvSock.connect(("127.0.0.1", addr[1]));

while 1:
    pkt = "Hello host: {1} time: {0}".format(time.ctime(), platform.node())
    print "SEND to: {0} data: {1}".format(addr, pkt)
    r = sendSock.sendto(pkt, addr)

    while select.select([recvSock], [], [], 0)[0]:
        data, fromAddr = recvSock.recvfrom(1024)
        print "RECV from: {0} data: {1}".format(fromAddr, data)

    time.sleep(2)

To initiate packets from a different interface:

#!/usr/bin/python

import platform, time, socket, select

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

pkt = "Hello from network.";
sendSock.sendto(pkt, ('10.65.42.129', 1600))

I ran all three on Cygwin on Windows XP and verified the result to be as required.

Example output

SEND to: ('239.255.2.9', 1600) data: Hello host: NYCMDV8X5R1 time: Thu Feb 21 13:15:15 2013
RECV from: ('127.0.0.1', 1600) data: Hello host: NYCMDV8X5R1 time: Thu Feb 21 13:15:14 2013
RECV from: ('127.0.0.1', 1600) data: Hello host: NYCMDV8X5R1 time: Thu Feb 21 13:15:15 2013
SEND to: ('239.255.2.9', 1600) data: Hello host: NYCMDV8X5R1 time: Thu Feb 21 13:15:17 2013
RECV from: ('127.0.0.1', 1600) data: Hello host: NYCMDV8X5R1 time: Thu Feb 21 13:15:16 2013
RECV from: ('127.0.0.1', 1600) data: Hello host: NYCMDV8X5R1 time: Thu Feb 21 13:15:17 2013

Previously the output would show external packets, for example:

SEND to: ('239.255.2.9', 1600) data: Hello host: NYCMDV8X5R1 time: Thu Feb 21 13:07:05 2013
RECV from: ('10.65.42.129', 4711) data: Hello from network.
RECV from: ('127.0.0.1', 4710) data: Hello host: NYCMDV8X5R1 time: Thu Feb 21 13:07:05 2013
SEND to: ('239.255.2.9', 1600) data: Hello host: NYCMDV8X5R1 time: Thu Feb 21 13:07:11 2013
RECV from: ('10.65.42.129', 4712) data: Hello from network.
Steve-o
  • 12,678
  • 2
  • 41
  • 60
  • 1
    A good thought, but it doesn't work with the multicast packets. And you get a socket error on the second connect: `socket.error: [Errno 48] Address already in use` – Shane Holloway May 22 '10 at 13:44
  • thanks for the expanded answer! I still get the address in use error on Mac OSX, but your answer did trigger another thought. I only get spoofed packets on the last socket that bound to that multicast address. So I thought a simple solution would be to just bind another receiving socket on the address. However, it turned out not to work either. – Shane Holloway Feb 22 '13 at 18:07