• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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