diff --git a/selfdrive/test/test_onroad.py b/selfdrive/test/test_onroad.py index 63456a9e1..2dd4aa072 100644 --- a/selfdrive/test/test_onroad.py +++ b/selfdrive/test/test_onroad.py @@ -68,7 +68,7 @@ PROCS = { "system.loggerd.deleter": 1.0, "./pandad": 19.0, "system.qcomgpsd.qcomgpsd": 1.0, - #"system.hardware.tici.modem": 2.0, + "system.hardware.tici.modem": 2.0, } TIMINGS = { diff --git a/system/hardware/tici/modem.py b/system/hardware/tici/modem.py index f01aa7987..2f994eca5 100755 --- a/system/hardware/tici/modem.py +++ b/system/hardware/tici/modem.py @@ -116,17 +116,17 @@ class PPPSession: def fails(self) -> int: return self._fails - def maybe_install_routes(self, ip: str, peer: str): + def maybe_install_routes(self, ip: str, peer: str) -> bool: """Install routes if peer changed; kill the session on failure so the state machine reconnects.""" if not peer or peer == self._peer: - return + return False try: IPv4Address(ip) IPv4Address(peer) except AddressValueError: logging.warning(f"refusing route install with non-IPv4 ip={ip!r} peer={peer!r}") self.kill() - return + return False self.cleanup_routes() cmds = [ ["sudo", "ip", "route", "add", "default", "via", peer, "dev", "ppp0", "metric", "1000"], @@ -139,9 +139,24 @@ class PPPSession: logging.warning(f"route install failed ({' '.join(cmd[1:])}): {r.stderr.strip()}") self.cleanup_routes() self.kill() - return + return False logging.info(f"route set up for {ip} via {peer}") self._peer = peer + return True + + def maybe_install_dns(self, dns_servers: list[str]) -> bool: + """Register DNS servers with systemd-resolved; kill the session on failure to force a retry.""" + if not dns_servers: + return False + for cmd in (["sudo", "resolvectl", "dns", "ppp0", *dns_servers], + ["sudo", "resolvectl", "default-route", "ppp0", "yes"]): + r = subprocess.run(cmd, capture_output=True, text=True) + if r.returncode != 0: + logging.warning(f"resolvectl failed ({' '.join(cmd[1:])}): {r.stderr.strip()}") + self.kill() + return False + logging.info(f"resolvectl: ppp0 DNS = {dns_servers}") + return True @staticmethod def cleanup_routes(): @@ -150,6 +165,7 @@ class PPPSession: # rules don't have a flush; delete until none remain while subprocess.run(["sudo", "ip", "rule", "del", "table", "1000"], capture_output=True).returncode == 0: pass + subprocess.run(["sudo", "resolvectl", "revert", "ppp0"], capture_output=True) class Modem: @@ -461,7 +477,8 @@ class Modem: peer = parts[parts.index("peer") + 1].split("/")[0] break if ip: - self._ppp.maybe_install_routes(ip, peer) + if self._ppp.maybe_install_routes(ip, peer): + self._ppp.maybe_install_dns(self._read_cellular_dns()) return {"ip_address": ip, "connected": True} if self.S["connected"]: return {"connected": False, "ip_address": ""} @@ -469,6 +486,22 @@ class Modem: pass return {} + def _read_cellular_dns(self) -> list[str]: + v = self._atv(f"AT+CGCONTRDP={DIAL_CID}", "+CGCONTRDP:") + if not v: + return [] + # +CGCONTRDP: ,,,,,,,... + fields = [f.strip().strip('"') for f in v.split(",")] + dns_servers = [] + for d in fields[5:7]: + try: + dns_servers.append(str(IPv4Address(d))) + except (AddressValueError, ValueError): + pass + if not dns_servers: + logging.warning(f"no cellular DNS servers reported by modem: {v!r}") + return dns_servers + def _poll_byte_counters(self) -> dict: try: with open("/sys/class/net/ppp0/statistics/tx_bytes") as f: diff --git a/system/manager/process_config.py b/system/manager/process_config.py index f39af059c..f5a6c5107 100644 --- a/system/manager/process_config.py +++ b/system/manager/process_config.py @@ -106,7 +106,7 @@ procs = [ PythonProcess("lateral_maneuversd", "tools.lateral_maneuvers.lateral_maneuversd", lat_maneuver), PythonProcess("radard", "selfdrive.controls.radard", only_onroad), PythonProcess("hardwared", "system.hardware.hardwared", always_run), - PythonProcess("modem", "system.hardware.tici.modem", always_run, enabled=False), + PythonProcess("modem", "system.hardware.tici.modem", always_run, enabled=TICI), PythonProcess("tombstoned", "system.tombstoned", always_run, enabled=not PC), PythonProcess("updated", "system.updated.updated", only_offroad, enabled=not PC), PythonProcess("uploader", "system.loggerd.uploader", always_run),