| #!/usr/bin/env python3 |
| # |
| # Copyright (c) 2022, The OpenThread Authors. |
| # All rights reserved. |
| # |
| # Redistribution and use in source and binary forms, with or without |
| # modification, are permitted provided that the following conditions are met: |
| # 1. Redistributions of source code must retain the above copyright |
| # notice, this list of conditions and the following disclaimer. |
| # 2. Redistributions in binary form must reproduce the above copyright |
| # notice, this list of conditions and the following disclaimer in the |
| # documentation and/or other materials provided with the distribution. |
| # 3. Neither the name of the copyright holder nor the |
| # names of its contributors may be used to endorse or promote products |
| # derived from this software without specific prior written permission. |
| # |
| # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE |
| # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| # POSSIBILITY OF SUCH DAMAGE. |
| |
| import ipaddress |
| import sys |
| import time |
| from zeroconf import IPVersion, ServiceBrowser, ServiceStateChange, Zeroconf, DNSAddress, DNSService, DNSText |
| |
| |
| def on_service_state_change(zeroconf, service_type, name, state_change): |
| if state_change is ServiceStateChange.Added: |
| zeroconf.get_service_info(service_type, name) |
| |
| |
| class BorderAgent(object): |
| alias = None |
| server_name = None |
| link_local_addr = None |
| port = None |
| thread_status = None |
| |
| def __init__(self, alias): |
| self.alias = alias |
| |
| def __repr__(self): |
| return str([self.alias, self.link_local_addr, self.port, self.thread_status]) |
| |
| |
| def parse_cache(cache): |
| border_agents = [] |
| |
| # Find all border routers |
| for ptr in cache['_meshcop._udp.local.']: |
| border_agents.append(BorderAgent(ptr.alias)) |
| |
| # Find server name, port and Thread Interface status for each border router |
| for ba in border_agents: |
| for record in cache[ba.alias.lower()]: |
| if isinstance(record, DNSService): |
| ba.server_name = record.server |
| ba.port = record.port |
| elif isinstance(record, DNSText): |
| text = bytearray(record.text) |
| sb = text.split(b'sb=')[1][0:4] |
| ba.thread_status = (sb[3] & 0x18) >> 3 |
| |
| # Find link local address for each border router |
| for ba in border_agents: |
| for record in cache[ba.server_name.lower()]: |
| if isinstance(record, DNSAddress): |
| addr = ipaddress.ip_address(record.address) |
| if isinstance(addr, ipaddress.IPv6Address) and addr.is_link_local: |
| ba.link_local_addr = str(addr) |
| break |
| |
| return border_agents |
| |
| |
| def main(): |
| # Browse border agents |
| zeroconf = Zeroconf(ip_version=IPVersion.V6Only) |
| ServiceBrowser(zeroconf, "_meshcop._udp.local.", handlers=[on_service_state_change]) |
| time.sleep(2) |
| cache = zeroconf.cache.cache |
| zeroconf.close() |
| |
| border_agents = parse_cache(cache) |
| for ba in border_agents: |
| print(ba) |
| |
| |
| if __name__ == '__main__': |
| main() |