1#!/usr/bin/env python3
2#
3#  Copyright (c) 2020, The OpenThread Authors.
4#  All rights reserved.
5#
6#  Redistribution and use in source and binary forms, with or without
7#  modification, are permitted provided that the following conditions are met:
8#  1. Redistributions of source code must retain the above copyright
9#     notice, this list of conditions and the following disclaimer.
10#  2. Redistributions in binary form must reproduce the above copyright
11#     notice, this list of conditions and the following disclaimer in the
12#     documentation and/or other materials provided with the distribution.
13#  3. Neither the name of the copyright holder nor the
14#     names of its contributors may be used to endorse or promote products
15#     derived from this software without specific prior written permission.
16#
17#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18#  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19#  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20#  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
21#  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22#  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23#  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24#  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25#  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26#  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27#  POSSIBILITY OF SUCH DAMAGE.
28#
29import ctypes
30import ctypes.util
31import socket
32import struct
33import sys
34import time
35
36MYPORT = 8123
37MYTTL = 1  # Increase to reach other networks
38
39libc = ctypes.CDLL(ctypes.util.find_library('c'))
40
41
42def if_nametoindex(name):
43    if not isinstance(name, str):
44        raise TypeError('name must be a string.')
45    ret = libc.if_nametoindex(name.encode('ascii'))
46    if not ret:
47        raise RuntimeError("Invalid Name")
48    return ret
49
50
51def if_indextoname(index):
52    if not isinstance(index, int):
53        raise TypeError('index must be an int.')
54    libc.if_indextoname.argtypes = [ctypes.c_uint32, ctypes.c_char_p]
55    libc.if_indextoname.restype = ctypes.c_char_p
56
57    ifname = ctypes.create_string_buffer(32)
58    ifname = libc.if_indextoname(index, ifname)
59    if not ifname:
60        raise RuntimeError("Invalid Index")
61    return ifname
62
63
64def main():
65    args = sys.argv[1:]
66    is_sender = False
67
68    if args[0] == '-s':
69        is_sender = True
70        args.pop(0)
71    elif args[0] == '-u':
72        is_multicast_receiver = False
73        args.pop(0)
74    else:
75        is_multicast_receiver = True
76
77    ifname, group = args
78
79    if is_sender:
80        sender(ifname, group)
81    else:
82        receiver(ifname, group, is_multicast_receiver=is_multicast_receiver)
83
84
85def sender(ifname, group):
86    addrinfo = socket.getaddrinfo(group, None)[0]
87
88    s = socket.socket(addrinfo[0], socket.SOCK_DGRAM)
89    s.setsockopt(socket.SOL_SOCKET, socket.SO_BINDTODEVICE, (ifname + '\0').encode('ascii'))
90
91    # Set Time-to-live (optional)
92    ttl_bin = struct.pack('@i', MYTTL)
93    assert addrinfo[0] == socket.AF_INET6
94    s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_HOPS, ttl_bin)
95
96    while True:
97        data = repr(time.time())
98        s.sendto((data + '\0').encode('ascii'), (addrinfo[4][0], MYPORT))
99        time.sleep(1)
100
101
102def receiver(ifname, group, is_multicast_receiver=True):
103    # Look up multicast group address in name server and find out IP version
104    addrinfo = socket.getaddrinfo(group, None)[0]
105    assert addrinfo[0] == socket.AF_INET6
106
107    # Create a socket
108    s = socket.socket(addrinfo[0], socket.SOCK_DGRAM)
109    s.setsockopt(socket.SOL_SOCKET, socket.SO_BINDTODEVICE, (ifname + '\0').encode('ascii'))
110
111    # Allow multiple copies of this program on one machine
112    # (not strictly needed)
113    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
114
115    # Bind it to the port
116    s.bind(('', MYPORT))
117
118    if is_multicast_receiver:
119        group_bin = socket.inet_pton(addrinfo[0], addrinfo[4][0])
120        # Join group
121        interface_index = if_nametoindex(ifname)
122        mreq = group_bin + struct.pack('@I', interface_index)
123        s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq)
124
125    # Loop, printing any data we receive
126    while True:
127        data, sender = s.recvfrom(1500)
128        while data[-1:] == '\0':
129            data = data[:-1]  # Strip trailing \0's
130        print(str(sender) + '  ' + repr(data))
131
132
133if __name__ == '__main__':
134    main()
135