repeater/internal/wifi/wpasupplicant/interface.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

180 lines
5 KiB
Go

package wpasupplicant
import (
"fmt"
"github.com/godbus/dbus/v5"
)
// WPAInterface represents a wpa_supplicant Interface object
type WPAInterface struct {
path dbus.ObjectPath
conn *dbus.Conn
obj dbus.BusObject
}
// NewWPAInterface creates a new WPAInterface instance
func NewWPAInterface(conn *dbus.Conn, path dbus.ObjectPath) *WPAInterface {
return &WPAInterface{
path: path,
conn: conn,
obj: conn.Object(Service, path),
}
}
// Scan triggers a network scan
func (i *WPAInterface) Scan(scanType string) error {
args := map[string]interface{}{
"Type": scanType, // "active" or "passive"
}
err := callNoAutoStart(i.obj, InterfaceInterface+".Scan", args).Err
if err != nil {
return fmt.Errorf("scan failed: %v", err)
}
return nil
}
// GetBSSs returns a list of BSS (Basic Service Set) object paths
func (i *WPAInterface) GetBSSs() ([]dbus.ObjectPath, error) {
prop, err := getPropNoAutoStart(i.obj, InterfaceInterface+".BSSs")
if err != nil {
return nil, fmt.Errorf("failed to get BSSs property: %v", err)
}
bsss, ok := prop.Value().([]dbus.ObjectPath)
if !ok {
return nil, fmt.Errorf("BSSs property is not an array of ObjectPath")
}
return bsss, nil
}
// GetState returns the current connection state
func (i *WPAInterface) GetState() (WPAState, error) {
prop, err := getPropNoAutoStart(i.obj, InterfaceInterface+".State")
if err != nil {
return "", fmt.Errorf("failed to get State property: %v", err)
}
state, ok := prop.Value().(string)
if !ok {
return "", fmt.Errorf("State property is not a string")
}
return WPAState(state), nil
}
// GetCurrentBSS returns the currently connected BSS object path
func (i *WPAInterface) GetCurrentBSS() (dbus.ObjectPath, error) {
prop, err := getPropNoAutoStart(i.obj, InterfaceInterface+".CurrentBSS")
if err != nil {
return "", fmt.Errorf("failed to get CurrentBSS property: %v", err)
}
bss, ok := prop.Value().(dbus.ObjectPath)
if !ok {
return "", fmt.Errorf("CurrentBSS property is not an ObjectPath")
}
return bss, nil
}
// AddNetwork creates a new network configuration
func (i *WPAInterface) AddNetwork(config map[string]interface{}) (dbus.ObjectPath, error) {
var networkPath dbus.ObjectPath
// Convert config to proper DBus variant format
dbusConfig := make(map[string]dbus.Variant)
for key, value := range config {
dbusConfig[key] = dbus.MakeVariant(value)
}
err := callNoAutoStart(i.obj, InterfaceInterface+".AddNetwork", dbusConfig).Store(&networkPath)
if err != nil {
return "", fmt.Errorf("failed to add network: %v", err)
}
return networkPath, nil
}
// SelectNetwork connects to a network
func (i *WPAInterface) SelectNetwork(networkPath dbus.ObjectPath) error {
err := callNoAutoStart(i.obj, InterfaceInterface+".SelectNetwork", networkPath).Err
if err != nil {
return fmt.Errorf("failed to select network: %v", err)
}
return nil
}
// EnableNetwork marks a network configuration as enabled (eligible for auto-connect)
func (i *WPAInterface) EnableNetwork(networkPath dbus.ObjectPath) error {
netObj := i.conn.Object(Service, networkPath)
err := setPropNoAutoStart(netObj, NetworkInterface+".Enabled", dbus.MakeVariant(true))
if err != nil {
return fmt.Errorf("failed to enable network: %v", err)
}
return nil
}
// RemoveNetwork removes a network configuration
func (i *WPAInterface) RemoveNetwork(networkPath dbus.ObjectPath) error {
err := callNoAutoStart(i.obj, InterfaceInterface+".RemoveNetwork", networkPath).Err
if err != nil {
return fmt.Errorf("failed to remove network: %v", err)
}
return nil
}
// GetNetworks returns the object paths of all configured networks
func (i *WPAInterface) GetNetworks() ([]dbus.ObjectPath, error) {
prop, err := getPropNoAutoStart(i.obj, InterfaceInterface+".Networks")
if err != nil {
return nil, fmt.Errorf("failed to get Networks property: %v", err)
}
networks, ok := prop.Value().([]dbus.ObjectPath)
if !ok {
return nil, fmt.Errorf("Networks property is not an array of ObjectPath")
}
return networks, nil
}
// Disconnect disconnects from the current network
func (i *WPAInterface) Disconnect() error {
err := callNoAutoStart(i.obj, InterfaceInterface+".Disconnect").Err
if err != nil {
return fmt.Errorf("disconnect failed: %v", err)
}
return nil
}
// SaveConfig saves the current configuration to the wpa_supplicant config file
func (i *WPAInterface) SaveConfig() error {
err := callNoAutoStart(i.obj, InterfaceInterface+".SaveConfig").Err
if err != nil {
return fmt.Errorf("save config failed: %v", err)
}
return nil
}
// GetPath returns the D-Bus object path for this interface
func (i *WPAInterface) GetPath() dbus.ObjectPath {
return i.path
}
// GetScanning returns whether a scan is currently in progress
func (i *WPAInterface) GetScanning() (bool, error) {
prop, err := getPropNoAutoStart(i.obj, InterfaceInterface+".Scanning")
if err != nil {
return false, fmt.Errorf("failed to get Scanning property: %v", err)
}
scanning, ok := prop.Value().(bool)
if !ok {
return false, fmt.Errorf("Scanning property is not a boolean")
}
return scanning, nil
}