1#!/usr/bin/env python3 2# 3# Copyright (c) 2021, 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 json 32import os 33import socket 34import struct 35import logging 36 37PORT = 12345 38IFNAME = 'eth0' 39GROUP = 'ff02::114' 40 41libc = ctypes.CDLL(ctypes.util.find_library('c')) 42 43logging.basicConfig(level=logging.INFO) 44 45 46def if_nametoindex(name): 47 if not isinstance(name, str): 48 raise TypeError('name must be a string.') 49 ret = libc.if_nametoindex(name.encode('ascii')) 50 if not ret: 51 raise RuntimeError("Invalid Name") 52 return ret 53 54 55def get_ipaddr(): 56 for line in os.popen(f'ip addr list dev {IFNAME} | grep inet6 | grep \'scope link\' '): 57 addr = line.strip().split()[1] 58 return addr.split('/')[0] 59 60 61def advertise_bbr(s: socket.socket, src): 62 bbr_info = { 63 'ven': 'OpenThread_BR', 64 'mod': 'OpenThread_BR', 65 'ver': '1.0', 66 'add': get_ipaddr(), 67 'por': 22, 68 } 69 70 logging.info("Advertise: %r", bbr_info) 71 s.sendto(json.dumps(bbr_info).encode('utf8'), src) 72 73 74def main(): 75 # Look up multicast group address in name server and find out IP version 76 addrinfo = socket.getaddrinfo(GROUP, None)[0] 77 assert addrinfo[0] == socket.AF_INET6 78 79 # Create a socket 80 s = socket.socket(addrinfo[0], socket.SOCK_DGRAM) 81 s.setsockopt(socket.SOL_SOCKET, socket.SO_BINDTODEVICE, (IFNAME + '\0').encode('ascii')) 82 83 # Bind it to the port 84 s.bind(('', PORT)) 85 86 group_bin = socket.inet_pton(addrinfo[0], addrinfo[4][0]) 87 # Join group 88 interface_index = if_nametoindex(IFNAME) 89 mreq = group_bin + struct.pack('@I', interface_index) 90 s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq) 91 92 logging.info("Advertising BBR on interface %s group %s ...", IFNAME, GROUP) 93 94 # Loop, printing any data we receive 95 while True: 96 data, src = s.recvfrom(100) 97 if data == b'BBR': 98 logging.info('Received BBR query, advertising') 99 advertise_bbr(s, src) 100 else: 101 logging.warn('Received %r, but ignored', data) 102 103 104if __name__ == '__main__': 105 main() 106