1#!/usr/bin/env python3 2# 3# Copyright (c) 2024, 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# 29 30import logging 31import dbus 32import gi.repository.GLib as GLib 33import subprocess 34import threading 35import os 36 37from dbus.mainloop.glib import DBusGMainLoop 38 39DBusGMainLoop(set_as_default=True) 40 41logging.basicConfig(level=logging.INFO, 42 format='%(asctime)s - %(levelname)s - %(message)s') 43 44bus = dbus.SystemBus() 45intended_dhcp6pd_state = None 46 47DHCP_CONFIG_PATH = "/etc/dhcpcd.conf" 48DHCP_CONFIG_PD_PATH = "/etc/dhcpcd.conf.pd" 49DHCP_CONFIG_NO_PD_PATH = "/etc/dhcpcd.conf.no-pd" 50 51 52def restart_dhcpcd_service(config_path): 53 if not os.path.isfile(config_path): 54 logging.error(f"{config_path} not found. Cannot apply configuration.") 55 return 56 try: 57 subprocess.run(["cp", config_path, DHCP_CONFIG_PATH], check=True) 58 subprocess.run(["systemctl", "daemon-reload"], check=True) 59 subprocess.run(["service", "dhcpcd", "restart"], check=True) 60 logging.info( 61 f"Successfully restarted dhcpcd service with {config_path}.") 62 except subprocess.CalledProcessError as e: 63 logging.error(f"Error restarting dhcpcd service: {e}") 64 65 66def restart_dhcpcd_with_pd_config(): 67 global intended_dhcp6pd_state 68 restart_dhcpcd_service(DHCP_CONFIG_PD_PATH) 69 intended_dhcp6pd_state = None 70 71 72def restart_dhcpcd_with_no_pd_config(): 73 restart_dhcpcd_service(DHCP_CONFIG_NO_PD_PATH) 74 75 76def properties_changed_handler(interface_name, changed_properties, 77 invalidated_properties): 78 global intended_dhcp6pd_state 79 if "Dhcp6PdState" not in changed_properties: 80 return 81 new_state = changed_properties["Dhcp6PdState"] 82 logging.info(f"Dhcp6PdState changed to: {new_state}") 83 if new_state == "running" and intended_dhcp6pd_state != "running": 84 intended_dhcp6pd_state = "running" 85 thread = threading.Thread(target=restart_dhcpcd_with_pd_config) 86 thread.start() 87 elif new_state in ("stopped", "idle", 88 "disabled") and intended_dhcp6pd_state is None: 89 restart_dhcpcd_with_no_pd_config() 90 91 92def connect_to_signal(): 93 try: 94 dbus_obj = bus.get_object('io.openthread.BorderRouter.wpan0', 95 '/io/openthread/BorderRouter/wpan0') 96 properties_dbus_iface = dbus.Interface( 97 dbus_obj, 'org.freedesktop.DBus.Properties') 98 dbus_obj.connect_to_signal( 99 "PropertiesChanged", 100 properties_changed_handler, 101 dbus_interface=properties_dbus_iface.dbus_interface) 102 logging.info("Connected to D-Bus signal.") 103 except dbus.DBusException as e: 104 logging.error(f"Error connecting to D-Bus: {e}") 105 106 107def handle_name_owner_changed(new_owner): 108 if new_owner: 109 logging.info(f"New D-Bus owner({new_owner}) assigned, connecting...") 110 connect_to_signal() 111 112 113def main(): 114 # Ensure dhcpcd is running in its last known state. This addresses a potential race condition 115 # during system startup due to the loop dependency in dhcpcd-radvd-network.target. 116 # 117 # - network.target activation relies on the completion of dhcpcd start 118 # - during bootup, dhcpcd tries to start radvd with PD enabled before network.target is 119 # active, which leads to a timeout failure 120 # - so we will prevent radvd from starting before target.network is active 121 # 122 # By restarting dhcpcd here, we ensure it runs after network.target is active, allowing 123 # radvd to start correctly and dhcpcd to configure the interface. 124 try: 125 subprocess.run(["systemctl", "reload-or-restart", "dhcpcd"], check=True) 126 logging.info("Successfully restarting dhcpcd service.") 127 except subprocess.CalledProcessError as e: 128 logging.error(f"Error restarting dhcpcd service: {e}") 129 130 loop = GLib.MainLoop() 131 132 bus.watch_name_owner(bus_name='io.openthread.BorderRouter.wpan0', 133 callback=handle_name_owner_changed) 134 135 loop.run() 136 137 138if __name__ == '__main__': 139 main() 140