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.
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.
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.