Implement wpa_supplicant backend
This commit is contained in:
parent
79c28da9c5
commit
04ada45f44
7 changed files with 874 additions and 2 deletions
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)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue