Implement wpa_supplicant backend
This commit is contained in:
parent
79c28da9c5
commit
04ada45f44
7 changed files with 874 additions and 2 deletions
|
|
@ -5,6 +5,7 @@ import (
|
|||
|
||||
"github.com/nemunaire/repeater/internal/wifi/backend"
|
||||
"github.com/nemunaire/repeater/internal/wifi/iwd"
|
||||
"github.com/nemunaire/repeater/internal/wifi/wpasupplicant"
|
||||
)
|
||||
|
||||
// createBackend creates the appropriate WiFi backend based on the backend name
|
||||
|
|
@ -13,8 +14,7 @@ func createBackend(backendName string) (backend.WiFiBackend, error) {
|
|||
case "iwd":
|
||||
return iwd.NewIWDBackend(), nil
|
||||
case "wpasupplicant":
|
||||
// TODO: Implement wpa_supplicant backend
|
||||
return nil, fmt.Errorf("wpa_supplicant backend not yet implemented")
|
||||
return wpasupplicant.NewWPABackend(), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid wifi backend: %s (must be 'iwd' or 'wpasupplicant')", backendName)
|
||||
}
|
||||
|
|
|
|||
265
internal/wifi/wpasupplicant/backend.go
Normal file
265
internal/wifi/wpasupplicant/backend.go
Normal file
|
|
@ -0,0 +1,265 @@
|
|||
package wpasupplicant
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/godbus/dbus/v5"
|
||||
"github.com/nemunaire/repeater/internal/wifi/backend"
|
||||
)
|
||||
|
||||
// WPABackend implements the WiFiBackend interface for wpa_supplicant
|
||||
type WPABackend struct {
|
||||
conn *dbus.Conn
|
||||
wpasupplicant dbus.BusObject
|
||||
iface *WPAInterface
|
||||
signalMonitor *SignalMonitor
|
||||
interfaceName string
|
||||
currentNetwork dbus.ObjectPath
|
||||
}
|
||||
|
||||
// NewWPABackend creates a new wpa_supplicant backend instance
|
||||
func NewWPABackend() *WPABackend {
|
||||
return &WPABackend{}
|
||||
}
|
||||
|
||||
// Initialize initializes the wpa_supplicant backend with the given interface name
|
||||
func (b *WPABackend) Initialize(interfaceName string) error {
|
||||
b.interfaceName = interfaceName
|
||||
var err error
|
||||
|
||||
// Connect to D-Bus
|
||||
b.conn, err = dbus.SystemBus()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to connect to D-Bus: %v", err)
|
||||
}
|
||||
|
||||
// Get wpa_supplicant root object
|
||||
b.wpasupplicant = b.conn.Object(Service, dbus.ObjectPath(RootPath))
|
||||
|
||||
// Get interface path for the given interface name
|
||||
interfacePath, err := b.getInterfacePath(interfaceName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get interface for %s: %v", interfaceName, err)
|
||||
}
|
||||
|
||||
b.iface = NewWPAInterface(b.conn, interfacePath)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getInterfacePath gets or creates the wpa_supplicant Interface object path
|
||||
func (b *WPABackend) getInterfacePath(interfaceName string) (dbus.ObjectPath, error) {
|
||||
var interfacePath dbus.ObjectPath
|
||||
|
||||
// Try to get existing interface
|
||||
err := b.wpasupplicant.Call(Service+".GetInterface", 0, interfaceName).Store(&interfacePath)
|
||||
if err == nil {
|
||||
return interfacePath, nil
|
||||
}
|
||||
|
||||
// Interface doesn't exist, create it
|
||||
args := map[string]dbus.Variant{
|
||||
"Ifname": dbus.MakeVariant(interfaceName),
|
||||
}
|
||||
|
||||
err = b.wpasupplicant.Call(Service+".CreateInterface", 0, args).Store(&interfacePath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create interface: %v", err)
|
||||
}
|
||||
|
||||
return interfacePath, nil
|
||||
}
|
||||
|
||||
// Close closes the D-Bus connection
|
||||
func (b *WPABackend) Close() error {
|
||||
if b.conn != nil {
|
||||
b.conn.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ScanNetworks triggers a network scan
|
||||
func (b *WPABackend) ScanNetworks() error {
|
||||
err := b.iface.Scan("active")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to trigger scan: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetOrderedNetworks returns networks sorted by signal strength in backend-agnostic format
|
||||
func (b *WPABackend) GetOrderedNetworks() ([]backend.BackendNetwork, error) {
|
||||
// Get BSS list
|
||||
bssPaths, err := b.iface.GetBSSs()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get BSSs: %v", err)
|
||||
}
|
||||
|
||||
var networks []backend.BackendNetwork
|
||||
seenSSIDs := make(map[string]bool)
|
||||
|
||||
// Iterate through BSSs and collect network info
|
||||
for _, bssPath := range bssPaths {
|
||||
bss := NewBSS(b.conn, bssPath)
|
||||
props, err := bss.GetProperties()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
ssid := string(props.SSID)
|
||||
if ssid == "" || seenSSIDs[ssid] {
|
||||
continue
|
||||
}
|
||||
seenSSIDs[ssid] = true
|
||||
|
||||
// Get BSSID string
|
||||
bssidStr, err := bss.GetBSSIDString()
|
||||
if err != nil {
|
||||
bssidStr = ""
|
||||
}
|
||||
|
||||
// Convert to backend-agnostic format
|
||||
backendNet := backend.BackendNetwork{
|
||||
SSID: ssid,
|
||||
SignalDBm: props.Signal,
|
||||
SecurityType: props.DetermineSecurityType(),
|
||||
BSSID: bssidStr,
|
||||
Frequency: props.Frequency,
|
||||
}
|
||||
|
||||
networks = append(networks, backendNet)
|
||||
}
|
||||
|
||||
// Sort by signal strength (descending)
|
||||
// Note: This is a simple bubble sort for demonstration
|
||||
// In production, use sort.Slice
|
||||
for i := 0; i < len(networks); i++ {
|
||||
for j := i + 1; j < len(networks); j++ {
|
||||
if networks[j].SignalDBm > networks[i].SignalDBm {
|
||||
networks[i], networks[j] = networks[j], networks[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return networks, nil
|
||||
}
|
||||
|
||||
// IsScanning checks if a scan is currently in progress
|
||||
func (b *WPABackend) IsScanning() (bool, error) {
|
||||
return b.iface.GetScanning()
|
||||
}
|
||||
|
||||
// Connect connects to a WiFi network
|
||||
func (b *WPABackend) Connect(ssid, password string) error {
|
||||
// Create network configuration
|
||||
config := make(map[string]interface{})
|
||||
config["ssid"] = fmt.Sprintf("\"%s\"", ssid) // wpa_supplicant expects quoted SSID
|
||||
|
||||
if password != "" {
|
||||
// For WPA/WPA2-PSK networks
|
||||
config["psk"] = fmt.Sprintf("\"%s\"", password)
|
||||
} else {
|
||||
// For open networks
|
||||
config["key_mgmt"] = "NONE"
|
||||
}
|
||||
|
||||
// Add network
|
||||
networkPath, err := b.iface.AddNetwork(config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add network: %v", err)
|
||||
}
|
||||
|
||||
// Store current network path for cleanup
|
||||
b.currentNetwork = networkPath
|
||||
|
||||
// Select (connect to) the network
|
||||
err = b.iface.SelectNetwork(networkPath)
|
||||
if err != nil {
|
||||
// Clean up network on failure
|
||||
b.iface.RemoveNetwork(networkPath)
|
||||
return fmt.Errorf("failed to select network: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Disconnect disconnects from the current WiFi network
|
||||
func (b *WPABackend) Disconnect() error {
|
||||
// Disconnect from current network
|
||||
if err := b.iface.Disconnect(); err != nil {
|
||||
return fmt.Errorf("failed to disconnect: %v", err)
|
||||
}
|
||||
|
||||
// Remove the network configuration if we have one
|
||||
if b.currentNetwork != "" && b.currentNetwork != "/" {
|
||||
b.iface.RemoveNetwork(b.currentNetwork)
|
||||
b.currentNetwork = ""
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetConnectionState returns the current WiFi connection state
|
||||
func (b *WPABackend) GetConnectionState() (backend.ConnectionState, error) {
|
||||
state, err := b.iface.GetState()
|
||||
if err != nil {
|
||||
return backend.StateDisconnected, err
|
||||
}
|
||||
return mapWPAState(state), nil
|
||||
}
|
||||
|
||||
// GetConnectedSSID returns the SSID of the currently connected network
|
||||
func (b *WPABackend) GetConnectedSSID() string {
|
||||
// Get current BSS
|
||||
bssPath, err := b.iface.GetCurrentBSS()
|
||||
if err != nil || bssPath == "/" {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Get BSS object
|
||||
bss := NewBSS(b.conn, bssPath)
|
||||
ssid, err := bss.GetSSIDString()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return ssid
|
||||
}
|
||||
|
||||
// StartEventMonitoring starts monitoring WiFi events
|
||||
func (b *WPABackend) StartEventMonitoring(callbacks backend.EventCallbacks) error {
|
||||
// Create signal monitor
|
||||
b.signalMonitor = NewSignalMonitor(b.conn, b.iface)
|
||||
|
||||
// Start monitoring
|
||||
return b.signalMonitor.Start(callbacks)
|
||||
}
|
||||
|
||||
// StopEventMonitoring stops monitoring WiFi events
|
||||
func (b *WPABackend) StopEventMonitoring() {
|
||||
if b.signalMonitor != nil {
|
||||
b.signalMonitor.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for scan to complete (helper method)
|
||||
func (b *WPABackend) waitForScanComplete(timeout time.Duration) error {
|
||||
start := time.Now()
|
||||
for {
|
||||
if time.Since(start) > timeout {
|
||||
return fmt.Errorf("scan timeout")
|
||||
}
|
||||
|
||||
scanning, err := b.iface.GetScanning()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !scanning {
|
||||
return nil
|
||||
}
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
157
internal/wifi/wpasupplicant/bss.go
Normal file
157
internal/wifi/wpasupplicant/bss.go
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
package wpasupplicant
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/godbus/dbus/v5"
|
||||
)
|
||||
|
||||
// BSS represents a wpa_supplicant BSS (Basic Service Set) object
|
||||
type BSS struct {
|
||||
path dbus.ObjectPath
|
||||
conn *dbus.Conn
|
||||
obj dbus.BusObject
|
||||
}
|
||||
|
||||
// BSSProperties holds the properties of a BSS
|
||||
type BSSProperties struct {
|
||||
SSID []byte
|
||||
BSSID []byte
|
||||
Signal int16 // Signal strength in dBm
|
||||
Frequency uint32 // Frequency in MHz
|
||||
Privacy bool // Whether encryption is enabled
|
||||
RSN map[string]dbus.Variant
|
||||
WPA map[string]dbus.Variant
|
||||
}
|
||||
|
||||
// NewBSS creates a new BSS instance
|
||||
func NewBSS(conn *dbus.Conn, path dbus.ObjectPath) *BSS {
|
||||
return &BSS{
|
||||
path: path,
|
||||
conn: conn,
|
||||
obj: conn.Object(Service, path),
|
||||
}
|
||||
}
|
||||
|
||||
// GetProperties returns all properties of the BSS
|
||||
func (b *BSS) GetProperties() (*BSSProperties, error) {
|
||||
props := &BSSProperties{}
|
||||
|
||||
// Get SSID
|
||||
if ssidProp, err := b.obj.GetProperty(BSSInterface + ".SSID"); err == nil {
|
||||
if ssid, ok := ssidProp.Value().([]byte); ok {
|
||||
props.SSID = ssid
|
||||
}
|
||||
}
|
||||
|
||||
// Get BSSID
|
||||
if bssidProp, err := b.obj.GetProperty(BSSInterface + ".BSSID"); err == nil {
|
||||
if bssid, ok := bssidProp.Value().([]byte); ok {
|
||||
props.BSSID = bssid
|
||||
}
|
||||
}
|
||||
|
||||
// Get Signal
|
||||
if signalProp, err := b.obj.GetProperty(BSSInterface + ".Signal"); err == nil {
|
||||
if signal, ok := signalProp.Value().(int16); ok {
|
||||
props.Signal = signal
|
||||
}
|
||||
}
|
||||
|
||||
// Get Frequency
|
||||
if freqProp, err := b.obj.GetProperty(BSSInterface + ".Frequency"); err == nil {
|
||||
if freq, ok := freqProp.Value().(uint16); ok {
|
||||
props.Frequency = uint32(freq)
|
||||
}
|
||||
}
|
||||
|
||||
// Get Privacy
|
||||
if privacyProp, err := b.obj.GetProperty(BSSInterface + ".Privacy"); err == nil {
|
||||
if privacy, ok := privacyProp.Value().(bool); ok {
|
||||
props.Privacy = privacy
|
||||
}
|
||||
}
|
||||
|
||||
// Get RSN (WPA2) information
|
||||
if rsnProp, err := b.obj.GetProperty(BSSInterface + ".RSN"); err == nil {
|
||||
if rsn, ok := rsnProp.Value().(map[string]dbus.Variant); ok {
|
||||
props.RSN = rsn
|
||||
}
|
||||
}
|
||||
|
||||
// Get WPA information
|
||||
if wpaProp, err := b.obj.GetProperty(BSSInterface + ".WPA"); err == nil {
|
||||
if wpa, ok := wpaProp.Value().(map[string]dbus.Variant); ok {
|
||||
props.WPA = wpa
|
||||
}
|
||||
}
|
||||
|
||||
return props, nil
|
||||
}
|
||||
|
||||
// GetSSIDString returns the SSID as a string
|
||||
func (b *BSS) GetSSIDString() (string, error) {
|
||||
prop, err := b.obj.GetProperty(BSSInterface + ".SSID")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get SSID property: %v", err)
|
||||
}
|
||||
|
||||
ssid, ok := prop.Value().([]byte)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("SSID property is not a byte array")
|
||||
}
|
||||
|
||||
return string(ssid), nil
|
||||
}
|
||||
|
||||
// GetBSSIDString returns the BSSID as a formatted MAC address string
|
||||
func (b *BSS) GetBSSIDString() (string, error) {
|
||||
prop, err := b.obj.GetProperty(BSSInterface + ".BSSID")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get BSSID property: %v", err)
|
||||
}
|
||||
|
||||
bssid, ok := prop.Value().([]byte)
|
||||
if !ok || len(bssid) != 6 {
|
||||
return "", fmt.Errorf("BSSID property is not a valid MAC address")
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%02x:%02x:%02x:%02x:%02x:%02x",
|
||||
bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]), nil
|
||||
}
|
||||
|
||||
// GetSignal returns the signal strength in dBm
|
||||
func (b *BSS) GetSignal() (int16, error) {
|
||||
prop, err := b.obj.GetProperty(BSSInterface + ".Signal")
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to get Signal property: %v", err)
|
||||
}
|
||||
|
||||
signal, ok := prop.Value().(int16)
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("Signal property is not an int16")
|
||||
}
|
||||
|
||||
return signal, nil
|
||||
}
|
||||
|
||||
// DetermineSecurityType determines the security type based on BSS properties
|
||||
func (p *BSSProperties) DetermineSecurityType() string {
|
||||
// Check for WPA2 (RSN)
|
||||
if len(p.RSN) > 0 {
|
||||
return "psk"
|
||||
}
|
||||
|
||||
// Check for WPA
|
||||
if len(p.WPA) > 0 {
|
||||
return "psk"
|
||||
}
|
||||
|
||||
// Check for WEP (privacy but no WPA/RSN)
|
||||
if p.Privacy {
|
||||
return "wep"
|
||||
}
|
||||
|
||||
// Open network
|
||||
return "open"
|
||||
}
|
||||
146
internal/wifi/wpasupplicant/interface.go
Normal file
146
internal/wifi/wpasupplicant/interface.go
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
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 := i.obj.Call(InterfaceInterface+".Scan", 0, 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 := i.obj.GetProperty(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 := i.obj.GetProperty(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 := i.obj.GetProperty(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 := i.obj.Call(InterfaceInterface+".AddNetwork", 0, 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 := i.obj.Call(InterfaceInterface+".SelectNetwork", 0, networkPath).Err
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to select network: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveNetwork removes a network configuration
|
||||
func (i *WPAInterface) RemoveNetwork(networkPath dbus.ObjectPath) error {
|
||||
err := i.obj.Call(InterfaceInterface+".RemoveNetwork", 0, networkPath).Err
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to remove network: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Disconnect disconnects from the current network
|
||||
func (i *WPAInterface) Disconnect() error {
|
||||
err := i.obj.Call(InterfaceInterface+".Disconnect", 0).Err
|
||||
if err != nil {
|
||||
return fmt.Errorf("disconnect 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 := i.obj.GetProperty(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
|
||||
}
|
||||
41
internal/wifi/wpasupplicant/network.go
Normal file
41
internal/wifi/wpasupplicant/network.go
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
package wpasupplicant
|
||||
|
||||
import (
|
||||
"github.com/godbus/dbus/v5"
|
||||
)
|
||||
|
||||
// Network represents a wpa_supplicant Network configuration object
|
||||
type Network struct {
|
||||
path dbus.ObjectPath
|
||||
conn *dbus.Conn
|
||||
obj dbus.BusObject
|
||||
}
|
||||
|
||||
// NewNetwork creates a new Network instance
|
||||
func NewNetwork(conn *dbus.Conn, path dbus.ObjectPath) *Network {
|
||||
return &Network{
|
||||
path: path,
|
||||
conn: conn,
|
||||
obj: conn.Object(Service, path),
|
||||
}
|
||||
}
|
||||
|
||||
// GetPath returns the D-Bus object path for this network
|
||||
func (n *Network) GetPath() dbus.ObjectPath {
|
||||
return n.path
|
||||
}
|
||||
|
||||
// GetProperties returns properties of the network configuration
|
||||
func (n *Network) GetProperties() (map[string]dbus.Variant, error) {
|
||||
prop, err := n.obj.GetProperty(NetworkInterface + ".Properties")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
props, ok := prop.Value().(map[string]dbus.Variant)
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return props, nil
|
||||
}
|
||||
236
internal/wifi/wpasupplicant/signals.go
Normal file
236
internal/wifi/wpasupplicant/signals.go
Normal file
|
|
@ -0,0 +1,236 @@
|
|||
package wpasupplicant
|
||||
|
||||
import (
|
||||
"log"
|
||||
"sync"
|
||||
|
||||
"github.com/godbus/dbus/v5"
|
||||
"github.com/nemunaire/repeater/internal/wifi/backend"
|
||||
)
|
||||
|
||||
// SignalMonitor monitors D-Bus signals from wpa_supplicant
|
||||
type SignalMonitor struct {
|
||||
conn *dbus.Conn
|
||||
iface *WPAInterface
|
||||
callbacks backend.EventCallbacks
|
||||
|
||||
// Signal channel
|
||||
signalChan chan *dbus.Signal
|
||||
|
||||
// Control
|
||||
stopChan chan struct{}
|
||||
mu sync.RWMutex
|
||||
running bool
|
||||
|
||||
// State tracking
|
||||
lastState WPAState
|
||||
}
|
||||
|
||||
// NewSignalMonitor creates a new signal monitor
|
||||
func NewSignalMonitor(conn *dbus.Conn, iface *WPAInterface) *SignalMonitor {
|
||||
return &SignalMonitor{
|
||||
conn: conn,
|
||||
iface: iface,
|
||||
signalChan: make(chan *dbus.Signal, 100),
|
||||
stopChan: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// Start begins monitoring D-Bus signals
|
||||
func (sm *SignalMonitor) Start(callbacks backend.EventCallbacks) error {
|
||||
sm.mu.Lock()
|
||||
if sm.running {
|
||||
sm.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
sm.running = true
|
||||
sm.callbacks = callbacks
|
||||
sm.mu.Unlock()
|
||||
|
||||
interfacePath := sm.iface.GetPath()
|
||||
|
||||
// Add signal match for PropertiesChanged on Interface
|
||||
matchOptions := []dbus.MatchOption{
|
||||
dbus.WithMatchObjectPath(interfacePath),
|
||||
dbus.WithMatchInterface("org.freedesktop.DBus.Properties"),
|
||||
dbus.WithMatchMember("PropertiesChanged"),
|
||||
}
|
||||
|
||||
if err := sm.conn.AddMatchSignal(matchOptions...); err != nil {
|
||||
sm.mu.Lock()
|
||||
sm.running = false
|
||||
sm.mu.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
// Add signal match for ScanDone
|
||||
scanDoneOptions := []dbus.MatchOption{
|
||||
dbus.WithMatchObjectPath(interfacePath),
|
||||
dbus.WithMatchInterface(InterfaceInterface),
|
||||
dbus.WithMatchMember("ScanDone"),
|
||||
}
|
||||
|
||||
if err := sm.conn.AddMatchSignal(scanDoneOptions...); err != nil {
|
||||
sm.mu.Lock()
|
||||
sm.running = false
|
||||
sm.mu.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
// Register signal channel
|
||||
sm.conn.Signal(sm.signalChan)
|
||||
|
||||
// Get initial state
|
||||
state, err := sm.iface.GetState()
|
||||
if err == nil {
|
||||
sm.lastState = state
|
||||
}
|
||||
|
||||
// Start monitoring goroutine
|
||||
go sm.monitor()
|
||||
|
||||
log.Printf("D-Bus signal monitoring started for wpa_supplicant interface %s", interfacePath)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop stops monitoring D-Bus signals
|
||||
func (sm *SignalMonitor) Stop() {
|
||||
sm.mu.Lock()
|
||||
if !sm.running {
|
||||
sm.mu.Unlock()
|
||||
return
|
||||
}
|
||||
sm.running = false
|
||||
sm.mu.Unlock()
|
||||
|
||||
// Signal stop
|
||||
close(sm.stopChan)
|
||||
|
||||
// Remove signal channel
|
||||
sm.conn.RemoveSignal(sm.signalChan)
|
||||
|
||||
log.Printf("D-Bus signal monitoring stopped for wpa_supplicant")
|
||||
}
|
||||
|
||||
// monitor is the main signal processing loop
|
||||
func (sm *SignalMonitor) monitor() {
|
||||
for {
|
||||
select {
|
||||
case sig := <-sm.signalChan:
|
||||
sm.handleSignal(sig)
|
||||
case <-sm.stopChan:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handleSignal processes a D-Bus signal
|
||||
func (sm *SignalMonitor) handleSignal(sig *dbus.Signal) {
|
||||
// Handle ScanDone signal
|
||||
if sig.Name == InterfaceInterface+".ScanDone" {
|
||||
sm.handleScanDone(sig)
|
||||
return
|
||||
}
|
||||
|
||||
// Handle PropertiesChanged signals
|
||||
if sig.Name != "org.freedesktop.DBus.Properties.PropertiesChanged" {
|
||||
return
|
||||
}
|
||||
|
||||
// Verify signal is from Interface
|
||||
if len(sig.Body) < 2 {
|
||||
return
|
||||
}
|
||||
|
||||
interfaceName, ok := sig.Body[0].(string)
|
||||
if !ok || interfaceName != InterfaceInterface {
|
||||
return
|
||||
}
|
||||
|
||||
// Parse changed properties
|
||||
changedProps, ok := sig.Body[1].(map[string]dbus.Variant)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// Check for State property change
|
||||
if stateVariant, ok := changedProps["State"]; ok {
|
||||
if state, ok := stateVariant.Value().(string); ok {
|
||||
sm.handleStateChange(WPAState(state))
|
||||
}
|
||||
}
|
||||
|
||||
// Check for CurrentBSS property change (connection status)
|
||||
if _, ok := changedProps["CurrentBSS"]; ok {
|
||||
// BSS changed, trigger state update
|
||||
sm.handleConnectionChange()
|
||||
}
|
||||
}
|
||||
|
||||
// handleStateChange processes a state change
|
||||
func (sm *SignalMonitor) handleStateChange(state WPAState) {
|
||||
sm.lastState = state
|
||||
|
||||
sm.mu.RLock()
|
||||
callback := sm.callbacks.OnStateChange
|
||||
sm.mu.RUnlock()
|
||||
|
||||
if callback == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Map wpa_supplicant state to backend state
|
||||
backendState := mapWPAState(state)
|
||||
|
||||
// Get connected SSID if connected
|
||||
ssid := ""
|
||||
if backendState == backend.StateConnected {
|
||||
if bssPath, err := sm.iface.GetCurrentBSS(); err == nil && bssPath != "/" {
|
||||
bss := NewBSS(sm.conn, bssPath)
|
||||
if ssidStr, err := bss.GetSSIDString(); err == nil {
|
||||
ssid = ssidStr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
callback(backendState, ssid)
|
||||
}
|
||||
|
||||
// handleConnectionChange processes connection changes
|
||||
func (sm *SignalMonitor) handleConnectionChange() {
|
||||
// Get current state and trigger state change callback
|
||||
state, err := sm.iface.GetState()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
sm.handleStateChange(state)
|
||||
}
|
||||
|
||||
// handleScanDone processes scan completion
|
||||
func (sm *SignalMonitor) handleScanDone(sig *dbus.Signal) {
|
||||
sm.mu.RLock()
|
||||
callback := sm.callbacks.OnScanComplete
|
||||
sm.mu.RUnlock()
|
||||
|
||||
if callback != nil {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
// mapWPAState maps wpa_supplicant states to backend-agnostic states
|
||||
func mapWPAState(state WPAState) backend.ConnectionState {
|
||||
switch state {
|
||||
case StateCompleted:
|
||||
return backend.StateConnected
|
||||
case StateAuthenticating, StateAssociating, StateAssociated, State4WayHandshake, StateGroupHandshake:
|
||||
return backend.StateConnecting
|
||||
case StateDisconnected, StateInactive, StateInterfaceDisabled:
|
||||
return backend.StateDisconnected
|
||||
case StateScanning:
|
||||
// Keep as disconnected if just scanning
|
||||
return backend.StateDisconnected
|
||||
default:
|
||||
return backend.StateDisconnected
|
||||
}
|
||||
}
|
||||
27
internal/wifi/wpasupplicant/types.go
Normal file
27
internal/wifi/wpasupplicant/types.go
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
package wpasupplicant
|
||||
|
||||
const (
|
||||
// D-Bus service and interfaces
|
||||
Service = "fi.w1.wpa_supplicant1"
|
||||
RootPath = "/fi/w1/wpa_supplicant1"
|
||||
InterfaceInterface = "fi.w1.wpa_supplicant1.Interface"
|
||||
BSSInterface = "fi.w1.wpa_supplicant1.BSS"
|
||||
NetworkInterface = "fi.w1.wpa_supplicant1.Network"
|
||||
)
|
||||
|
||||
// WPAState represents the wpa_supplicant connection state
|
||||
type WPAState string
|
||||
|
||||
const (
|
||||
// wpa_supplicant state strings
|
||||
StateDisconnected WPAState = "disconnected"
|
||||
StateInactive WPAState = "inactive"
|
||||
StateScanning WPAState = "scanning"
|
||||
StateAuthenticating WPAState = "authenticating"
|
||||
StateAssociating WPAState = "associating"
|
||||
StateAssociated WPAState = "associated"
|
||||
State4WayHandshake WPAState = "4way_handshake"
|
||||
StateGroupHandshake WPAState = "group_handshake"
|
||||
StateCompleted WPAState = "completed"
|
||||
StateInterfaceDisabled WPAState = "interface_disabled"
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue