repeater/internal/wifi/wpasupplicant/dbusutil.go
Pierre-Olivier Mercier 11ae69de56 wpasupplicant: Disable D-Bus auto-activation on all calls
Every method call and property access to fi.w1.wpa_supplicant1 used flag
0, leaving D-Bus service activation enabled. Any stray request — a startup
race before `service wpa_supplicant start` takes effect, or a leaked call
while the Ethernet uplink is the chosen path — would make the bus daemon
spawn `wpa_supplicant -u` from its .service file, bypassing repeater's own
launch of the daemon.

Route every interaction through callNoAutoStart/getPropNoAutoStart/
setPropNoAutoStart, which set dbus.FlagNoAutoStart. A leaked call now fails
cleanly with ServiceUnknown instead of resurrecting the daemon, leaving
repeater as the sole launcher.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 11:44:59 +08:00

60 lines
2.4 KiB
Go

package wpasupplicant
import (
"fmt"
"strings"
"github.com/godbus/dbus/v5"
)
// D-Bus interactions with the fi.w1.wpa_supplicant1 name must never trigger
// service activation. repeater is the sole launcher of the daemon (via
// `service wpa_supplicant start` in the app layer); if a call reached the bus
// name while the daemon was down — a startup race, or a stray call in
// Ethernet-uplink mode — the bus would auto-activate it from its .service
// file, spawning `wpa_supplicant -u` and bypassing repeater's own launch.
//
// godbus only suppresses activation when FlagNoAutoStart is set on the message
// (msg.Flags = flags & (FlagNoAutoStart | FlagNoReplyExpected)); the default
// flag value of 0 leaves activation enabled. The helpers below set the flag on
// every method call and property access. A leaked call then fails cleanly with
// a ServiceUnknown error instead of resurrecting the daemon.
// callNoAutoStart performs a method call with D-Bus auto-activation disabled.
func callNoAutoStart(obj dbus.BusObject, method string, args ...interface{}) *dbus.Call {
return obj.Call(method, dbus.FlagNoAutoStart, args...)
}
// getPropNoAutoStart reads a property (interface.member notation) with
// auto-activation disabled. It mirrors dbus.Object.GetProperty, which
// otherwise issues Properties.Get with the activation-enabling flag 0.
func getPropNoAutoStart(obj dbus.BusObject, p string) (dbus.Variant, error) {
var result dbus.Variant
iface, prop, err := splitProperty(p)
if err != nil {
return result, err
}
err = obj.Call("org.freedesktop.DBus.Properties.Get", dbus.FlagNoAutoStart, iface, prop).Store(&result)
return result, err
}
// setPropNoAutoStart writes a property with auto-activation disabled. The value
// is passed through as-is, so callers wrap it in a dbus.Variant just as they
// would for dbus.Object.SetProperty.
func setPropNoAutoStart(obj dbus.BusObject, p string, v interface{}) error {
iface, prop, err := splitProperty(p)
if err != nil {
return err
}
return obj.Call("org.freedesktop.DBus.Properties.Set", dbus.FlagNoAutoStart, iface, prop, v).Err
}
// splitProperty splits an "interface.member" property name into its interface
// and member parts, matching godbus's own parsing.
func splitProperty(p string) (iface, prop string, err error) {
idx := strings.LastIndex(p, ".")
if idx == -1 || idx+1 == len(p) {
return "", "", fmt.Errorf("dbus: invalid property %s", p)
}
return p[:idx], p[idx+1:], nil
}