Skip to content

Commit

Permalink
Merge f9fdbb0 into 31903de
Browse files Browse the repository at this point in the history
  • Loading branch information
aBozowski authored Feb 9, 2024
2 parents 31903de + f9fdbb0 commit 2411593
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 101 deletions.
1 change: 1 addition & 0 deletions src/tools/interop/idt/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ pycache/
venv/
.zip
BUILD
prober_urls.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,8 @@
# limitations under the License.
#

enable_foyer_probers = True
foyer_prober_traceroute_limit = 32
import os

enable_probers = True
prober_traceroute_limit = 32
prober_urls_file_name = os.path.join(os.path.dirname(__file__), "prober_urls.txt")
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ async def stop_capture(self) -> None:
self.analysis.show_analysis()

async def probe_capture(self) -> None:
if config.enable_foyer_probers:
if config.enable_probers:
await PlayServicesProber(self.platform, self.artifact_dir).probe_services()
else:
logger.critical("Foyer probers disabled in config!")
logger.critical("Probers disabled in config!")
32 changes: 19 additions & 13 deletions src/tools/interop/idt/capture/ecosystem/play_services/prober.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

from . import config

logger = log.get_logger(__file__)
_LOGGER = log.get_logger(__file__)


class PlayServicesProber:
Expand All @@ -30,42 +30,48 @@ def __init__(self, platform, artifact_dir):
# TODO: Handle all resolved addresses
self.platform = platform
self.artifact_dir = artifact_dir
self.logger = logger
self.logger = _LOGGER
self.probe_artifact = os.path.join(self.artifact_dir, "net_probes.txt")
self.command_suffix = f" 2>&1 | tee -a {self.probe_artifact}"
self.target = "googlehomefoyer-pa.googleapis.com"
self.tracert_limit = config.foyer_prober_traceroute_limit
self.target = ""
self.tracert_limit = config.prober_traceroute_limit

def run_command(self, command):
Bash(f"{command} {self.command_suffix}", sync=True).start_command()

async def _probe_tracert_icmp_foyer(self) -> None:
async def _probe_tracert_icmp(self) -> None:
self.logger.info(f"icmp traceroute to {self.target}")
self.run_command(f"traceroute -m {self.tracert_limit} {self.target}")

async def _probe_tracert_udp_foyer(self) -> None:
async def _probe_tracert_udp(self) -> None:
# TODO: Per-host-platform impl
self.logger.info(f"udp traceroute to {self.target}")
self.run_command(f"traceroute -m {self.tracert_limit} -U -p 443 {self.target}")

async def _probe_tracert_tcp_foyer(self) -> None:
async def _probe_tracert_tcp(self) -> None:
# TODO: Per-host-platform impl
self.logger.info(f"tcp traceroute to {self.target}")
self.run_command(f"traceroute -m {self.tracert_limit} -T -p 443 {self.target}")

async def _probe_ping_foyer(self) -> None:
async def _probe_ping(self) -> None:
self.logger.info(f"ping {self.target}")
self.run_command(f"ping -c 4 {self.target}")

async def _probe_dns_foyer(self) -> None:
async def _probe_dns(self) -> None:
self.logger.info(f"dig {self.target}")
self.run_command(f"dig {self.target}")

async def _probe_from_phone_ping_foyer(self) -> None:
async def _probe_from_phone_ping(self) -> None:
self.logger.info(f"ping {self.target} from phone")
self.platform.run_adb_command(f"shell ping -c 4 {self.target} {self.command_suffix}")

async def probe_services(self) -> None:
self.logger.info(f"Probing {self.target}")
for probe_func in [s for s in dir(self) if s.startswith('_probe')]:
await getattr(self, probe_func)()
if not os.path.exists(config.prober_urls_file_name):
self.logger.info("No probe targets configured")
return
with open(config.prober_urls_file_name) as urls_file:
for line in urls_file:
self.target = line
self.logger.info(f"Probing {self.target}")
for probe_func in [s for s in dir(self) if s.startswith('_probe')]:
await getattr(self, probe_func)()
181 changes: 97 additions & 84 deletions src/tools/interop/idt/discovery/dnssd.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import asyncio
import os
import re
import traceback
from dataclasses import dataclass
from textwrap import dedent
Expand All @@ -31,104 +32,112 @@


@dataclass()
class MdnsTypeInfo:
class ServiceTypeInfo:
type: str
description: str


commissioner = MdnsTypeInfo(
_COMMISSIONER_SERVICE_INFO = ServiceTypeInfo(
"COMMISSIONER",
"This is a service for a Matter commissioner aka. controller"
"A service for a Matter commissioner aka. controller"
)
commissionable = MdnsTypeInfo(
_COMMISSIONABLE_SERVICE_INFO = ServiceTypeInfo(
"COMMISSIONABLE / EXTENDED DISCOVERY",
"This is a service to be used in the commissioning process and provides more info about the device."
"A service to be used in the commissioning process and provides more info about the device."
)
operational = MdnsTypeInfo(
_OPERATIONAL_SERVICE_INFO = ServiceTypeInfo(
"OPERATIONAL",
"This is a service for a commissioned Matter device. It exposes limited info about the device."
"A service for a commissioned Matter device. It exposes limited info about the device."
)
border_router = MdnsTypeInfo(
_TBR_SERVICE_INFO = ServiceTypeInfo(
"THREAD BORDER ROUTER",
"This is a service for a thread border router; may be used for thread+Matter devices."
"A service for a thread border router; may be used for thread+Matter devices."
)

_MDNS_TYPES = {
"_matterd._udp.local.": commissioner,
"_matterc._udp.local.": commissionable,
"_matter._tcp.local.": operational,
"_meshcop._udp.local.": border_router,
_TREL_SERVICE_INFO = ServiceTypeInfo(
"THREAD RADIO ENCAPSULATION LINK",
"A service for Thread Radio Encapsulation Link which is a method for thread BRs to exchange data on IP links."
)

_SERVICE_TYPE_INFO = {
"_matterd._udp.local.": _COMMISSIONER_SERVICE_INFO,
"_matterc._udp.local.": _COMMISSIONABLE_SERVICE_INFO,
"_matter._tcp.local.": _OPERATIONAL_SERVICE_INFO,
"_meshcop._udp.local.": _TBR_SERVICE_INFO,
"_trel._udp.local.": _TREL_SERVICE_INFO,
}


@dataclass()
class RecordParser:
class TxtRecordParser:
readable_name: str
explanation: str
parse: Callable[[str], str]


# TODO: Meshcop parser
def unwrap_str(to_unwrap: str) -> str:
return re.sub(r"\s+", ' ', to_unwrap)


# TODO: Thread parser

class MatterTxtRecordParser:

def __init__(self):
self.parsers = {
"D": RecordParser("Discriminator",
dedent("\
Differentiates this instance of the device from others w/ same VID/PID that might be \n\
in the environment."),
MatterTxtRecordParser.parse_d), # To hex
"VP": RecordParser("VID/PID",
"The Vendor ID and Product ID (each are two bytes of hex) that identify this product.",
MatterTxtRecordParser.parse_vp), # Split + to hex
"CM": RecordParser("Commissioning mode",
"Whether the device is in commissioning mode or not.",
MatterTxtRecordParser.parse_cm), # Decode
"DT": RecordParser("Device type",
"Application type for this end device.",
MatterTxtRecordParser.parse_dt), # Decode
"DN": RecordParser("Device name",
"Manufacturer provided device name. MAY match NodeLabel in Basic info cluster.",
MatterTxtRecordParser.parse_pass_through), # None
"RI": RecordParser("Rotating identifier",
"Vendor specific, non-trackable per-device ID.",
MatterTxtRecordParser.parse_pass_through), # None
"PH": RecordParser("Pairing hint",
dedent("\
Given the current device state, follow these instructions to make the device \n\
commissionable."),
MatterTxtRecordParser.parse_ph), # Decode
"PI": RecordParser("Pairing instructions",
dedent("\
Used with the Pairing hint. If the Pairing hint mentions N, this is the \n\
value of N."),
MatterTxtRecordParser.parse_pass_through), # None
# General records
"SII": RecordParser("Session idle interval",
"Message Reliability Protocol retry interval while the device is idle in milliseconds.",
MatterTxtRecordParser.parse_pass_through), # None
"SAI": RecordParser("Session active interval",
dedent("\
Message Reliability Protocol retry interval while the device is active \n\
in milliseconds."),
MatterTxtRecordParser.parse_pass_through), # None
"SAT": RecordParser("Session active threshold",
"Duration of time this device stays active after last activity in milliseconds.",
MatterTxtRecordParser.parse_pass_through), # None
"T": RecordParser("Supports TCP",
"Whether this device supports TCP client and or Server.",
MatterTxtRecordParser.parse_t), # Decode
# Commissioning
"D": TxtRecordParser("Discriminator",
unwrap_str("Differentiates advertisements from this instance of the device from \
advertisement from others devices w/ the same VID/PID."),
MatterTxtRecordParser.parse_d), # To hex
"VP": TxtRecordParser("VID/PID",
"The Vendor ID and Product ID (each two bytes of hex) that identify this product.",
MatterTxtRecordParser.parse_vp), # Split + to hex
"CM": TxtRecordParser("Commissioning mode",
"Whether the device is in commissioning mode or not.",
MatterTxtRecordParser.parse_cm), # Decode
"DT": TxtRecordParser("Device type",
"Application type for this end device.",
MatterTxtRecordParser.parse_dt), # Decode map
"DN": TxtRecordParser("Device name",
"Manufacturer provided device name. MAY match NodeLabel in Basic info cluster.",
MatterTxtRecordParser.parse_pass_through), # None
"RI": TxtRecordParser("Rotating identifier",
"Vendor specific, non-trackable per-device ID.",
MatterTxtRecordParser.parse_pass_through), # None
"PH": TxtRecordParser("Pairing hint",
unwrap_str("Given the current device state, follow these instructions to make the \
device commissionable."),
MatterTxtRecordParser.parse_ph), # Decode bitmap
"PI": TxtRecordParser("Pairing instructions",
unwrap_str("Used with the Pairing hint. If the Pairing hint mentions N, this \
is the value of N."),
MatterTxtRecordParser.parse_pass_through), # None
# General
"SII": TxtRecordParser("Session idle interval",
unwrap_str("Message Reliability Protocol retry interval while the device is idle in \
milliseconds."),
MatterTxtRecordParser.parse_pass_through), # None
"SAI": TxtRecordParser("Session active interval",
unwrap_str("Message Reliability Protocol retry interval while the device is \
active in milliseconds."),
MatterTxtRecordParser.parse_pass_through), # None
"SAT": TxtRecordParser("Session active threshold",
"Duration of time this device stays active after last activity in milliseconds.",
MatterTxtRecordParser.parse_pass_through), # None
"T": TxtRecordParser("Supports TCP",
"Whether this device supports TCP.",
MatterTxtRecordParser.parse_t), # Decode
}
self.unparsed_records = ""
self.parsed_records = ""

def parse_single_record(self, key: str, value: str):
parser: RecordParser = self.parsers[key]
def parse_single_txt_record(self, key: str, value: str):
parser: TxtRecordParser = self.parsers[key]
self.parsed_records += add_border(parser.readable_name + "\n")
self.parsed_records += parser.explanation + "\n\n"
try:
self.parsed_records += "PARSED VALUE: " + parser.parse(value) + "\n"
self.parsed_records += "PARSED VALUE:\n" + parser.parse(value) + "\n"
except Exception:
logger.error("Exception parsing TXT record, appending raw value")
logger.error(traceback.format_exc())
Expand All @@ -144,7 +153,7 @@ def get_output(self) -> str:
ret += parsed_exp + self.parsed_records
return ret

def parse_records(self, info: ServiceInfo) -> str:
def parse_txt_records(self, info: ServiceInfo) -> str:
if info.properties is not None:
for name, value in info.properties.items():
try:
Expand All @@ -158,7 +167,7 @@ def parse_records(self, info: ServiceInfo) -> str:
if name not in self.parsers:
self.unparsed_records += f"KEY: {name} VALUE: {value}\n"
else:
self.parse_single_record(name, value)
self.parse_single_txt_record(name, value)
return self.get_output()

@staticmethod
Expand Down Expand Up @@ -301,20 +310,23 @@ def handle_service_info(
type_: str,
name: str,
delta_type: str) -> None:
to_log = "SERVICE EVENT\n"
to_log += f"{name}\n"
to_log += f"SERVICE {delta_type}\n"
to_log += _SERVICE_TYPE_INFO[type_].type + "\n"
to_log += _SERVICE_TYPE_INFO[type_].description + "\n"
info = zc.get_service_info(type_, name)
self.discovered_matter_devices[name] = info
to_log = f"{name}\n"
update_str = f"\nSERVICE {delta_type}\n"
to_log += ("*" * (len(update_str) - 2)) + update_str
to_log += _MDNS_TYPES[type_].type + "\n"
to_log += _MDNS_TYPES[type_].description + "\n"
to_log += f"A/SRV TTL: {str(info.host_ttl)}\n"
to_log += f"PTR/TXT TTL: {str(info.other_ttl)}\n"
txt_parser = MatterTxtRecordParser()
to_log += txt_parser.parse_records(info)
to_log += self.log_addr(info)
self.logger.info(to_log)
self.write_log(to_log, name)
if info is not None:
self.discovered_matter_devices[name] = info
to_log += f"A/SRV TTL: {str(info.host_ttl)}\n"
to_log += f"PTR/TXT TTL: {str(info.other_ttl)}\n"
txt_parser = MatterTxtRecordParser()
to_log += txt_parser.parse_txt_records(info)
to_log += self.log_addr(info)
self.logger.info(to_log)
self.write_log(to_log, name)
else:
self.logger.warning(f"No info found for {to_log}")

def add_service(self, zc: Zeroconf, type_: str, name: str) -> None:
self.handle_service_info(zc, type_, name, "ADDED")
Expand All @@ -323,17 +335,18 @@ def update_service(self, zc: Zeroconf, type_: str, name: str) -> None:
self.handle_service_info(zc, type_, name, "UPDATED")

def remove_service(self, zc: Zeroconf, type_: str, name: str) -> None:
to_log = f"Service {name} removed\n"
to_log += _MDNS_TYPES[type_].type + "\n"
to_log += _MDNS_TYPES[type_].description
to_log = "SERVICE_EVENT\n"
to_log += f"Service {name} removed\n"
to_log += _SERVICE_TYPE_INFO[type_].type + "\n"
to_log += _SERVICE_TYPE_INFO[type_].description
if name in self.discovered_matter_devices:
del self.discovered_matter_devices[name]
self.logger.warning(to_log)
self.write_log(to_log, name)

def browse_interactive(self) -> None:
zc = Zeroconf()
ServiceBrowser(zc, list(_MDNS_TYPES.keys()), self)
ServiceBrowser(zc, list(_SERVICE_TYPE_INFO.keys()), self)
try:
self.logger.warning(
dedent("\
Expand All @@ -348,7 +361,7 @@ def browse_interactive(self) -> None:

async def browse_once(self, browse_time_seconds: int) -> Zeroconf:
zc = Zeroconf()
ServiceBrowser(zc, list(_MDNS_TYPES.keys()), self)
ServiceBrowser(zc, list(_SERVICE_TYPE_INFO.keys()), self)
await asyncio.sleep(browse_time_seconds)
zc.close()
return zc

0 comments on commit 2411593

Please sign in to comment.