Welcome, guest | Sign In | My Account | Store | Cart

IPv6 can be tricky in many ways, and multicast makes it even more fun. This recipe is intended to give you a working implementation to start from.

Python, 101 lines
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
#!/usr/bin/env python
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#~ Imports 
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

import sys
import time
import socket
import select
import struct

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#~ LibC definitions of if_nametoindex
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

if_nametoindex = None

if if_nametoindex is None:
    try: import ctypes
    except ImportError: pass
    else:
        _libc = ctypes.cdll.libc
        def if_nametoindex(interfaceName):
            # if you have a better way to get the interface index, I'd love to hear
            # it...  You are supposed to be able to leave the interface number as 0,
            # but I get an exception when I try this.  (MacOS 10.4)  It may be because
            # it is a multihomed device...
            return _libc.if_nametoindex(interfaceName)

if if_nametoindex is None:
    try: import dl
    except ImportError: pass
    else:
        _libc = dl.open('libc.so')
        def if_nametoindex(interfaceName):
            # if you have a better way to get the interface index, I'd love to hear
            # it...  You are supposed to be able to leave the interface number as 0,
            # but I get an exception when I try this.  (MacOS 10.4)  It may be because
            # it is a multihomed device...
            return _libc.call('if_nametoindex', interfaceName)

if if_nametoindex is None:
    raise RuntimeError("No implementation allowing access to if_nametoindex available")

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#~ Definitions 
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

maddr = ('ff12::4242', 4242)

def ipv6Multicast(host='::1', maddr=maddr):
    haddr = socket.getaddrinfo(host, maddr[1], socket.AF_INET6, socket.SOCK_DGRAM)[0][-1]
    maddr = socket.getaddrinfo(maddr[0], maddr[1], socket.AF_INET6, socket.SOCK_DGRAM)[0][-1]

    sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)

    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    if hasattr(socket, "SO_REUSEPORT"):
        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)

    sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_LOOP, 1)
    sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_HOPS, 5)

    ifn = haddr[3] or if_nametoindex('lo0')
    ifn = struct.pack("I", ifn)
    sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_IF, ifn)

    group = socket.inet_pton(socket.AF_INET6, maddr[0]) + ifn
    sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, group)

    sock.bind(haddr)
    sock.setblocking(False)

    return sock, maddr

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#~ Main 
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

if __name__=='__main__':
    # change host to use your IPv6 address...
    sock, maddr = ipv6Multicast(host='::1')

    # send initial message
    msg = ' '.join(sys.argv[1:])
    msg = msg or 'IPv6 multicast recipie'
    sock.sendto(msg, maddr)

    try:
        t0 = 0
        while 1:
            tn = time.time()
            if tn-t0 > 1:
                sock.sendto('blink: '+msg, maddr)
                t0 = tn

            if select.select([sock],[],[],1)[0]:
                # we have a message... print what was sent!
                print sock.recvfrom(1024)
    except KeyboardInterrupt:
        pass

The thing I like least is the dependence on ctypes (or dl) to provide if_nametoindex. It would be nice if this method was provided by Python's socket module instead. If you have another approach, I'd love to hear about it!

Windows IPv6 presents the other challenge, and I've not yet had the opportunity to test and tweak this pattern to run on that platform. I also miss the inet_ntop and inet_pton methods on this platform.

2 comments

Shane Holloway (author) 16 years, 1 month ago  # | flag

Platforms. Oh, forgot to mention this implementation will only work on libc platforms having either ctypes or dl modules. I've tested it on FreeBSD and MacOSX (Darwin 10.4).

Multicast between different users on the same machine. After a little debugging, I discovered that multiple users on the same machine could not connect to the same multicast channel. This may or may not be important to you, but it was for me. And challenging to track down. In order to achieve this, you must bind to the ANY address '::' (or ''), which made sense. But to complicate matters, a udp46 protocol on MacOSX does not seem to support it. I'm not sure if it is a bug or not, but to allow binding, simply switch to udp6 protocol by setting IPV6_V6ONLY.

sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)