255 lines
6.7 KiB
Go
255 lines
6.7 KiB
Go
package iwd
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/godbus/dbus/v5"
|
|
"github.com/nemunaire/repeater/internal/wifi/backend"
|
|
)
|
|
|
|
const (
|
|
AgentPath = "/com/github/nemunaire/repeater/agent"
|
|
)
|
|
|
|
// IWDBackend implements the WiFiBackend interface for iwd (Intel Wireless Daemon)
|
|
type IWDBackend struct {
|
|
conn *dbus.Conn
|
|
manager *Manager
|
|
station *Station
|
|
agent *Agent
|
|
agentManager *AgentManager
|
|
signalMonitor *SignalMonitor
|
|
interfaceName string
|
|
callbacks backend.EventCallbacks
|
|
}
|
|
|
|
// NewIWDBackend creates a new IWD backend instance
|
|
func NewIWDBackend() *IWDBackend {
|
|
return &IWDBackend{}
|
|
}
|
|
|
|
// Initialize initializes the iwd backend with the given interface name
|
|
func (b *IWDBackend) 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("échec de connexion à D-Bus: %v", err)
|
|
}
|
|
|
|
// Find station for interface
|
|
b.manager = NewManager(b.conn)
|
|
b.station, err = b.manager.FindStation(interfaceName)
|
|
if err != nil {
|
|
return fmt.Errorf("impossible de trouver la station pour %s: %v", interfaceName, err)
|
|
}
|
|
|
|
// Create and register agent for credential callbacks
|
|
b.agent = NewAgent(b.conn, dbus.ObjectPath(AgentPath))
|
|
if err := b.agent.Export(); err != nil {
|
|
return fmt.Errorf("échec de l'export de l'agent: %v", err)
|
|
}
|
|
|
|
b.agentManager = NewAgentManager(b.conn)
|
|
if err := b.agentManager.RegisterAgent(dbus.ObjectPath(AgentPath)); err != nil {
|
|
b.agent.Unexport()
|
|
return fmt.Errorf("échec de l'enregistrement de l'agent: %v", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Close closes the D-Bus connection and unregisters the agent
|
|
func (b *IWDBackend) Close() error {
|
|
if b.agentManager != nil && b.agent != nil {
|
|
b.agentManager.UnregisterAgent(dbus.ObjectPath(AgentPath))
|
|
b.agent.Unexport()
|
|
}
|
|
if b.conn != nil {
|
|
b.conn.Close()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ScanNetworks triggers a network scan
|
|
func (b *IWDBackend) ScanNetworks() error {
|
|
err := b.station.Scan()
|
|
if err != nil {
|
|
return fmt.Errorf("erreur lors du scan: %v", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetOrderedNetworks returns networks sorted by signal strength in backend-agnostic format
|
|
func (b *IWDBackend) GetOrderedNetworks() ([]backend.BackendNetwork, error) {
|
|
networkInfos, err := b.station.GetOrderedNetworks()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("erreur lors de la récupération des réseaux: %v", err)
|
|
}
|
|
|
|
var networks []backend.BackendNetwork
|
|
seenSSIDs := make(map[string]bool)
|
|
|
|
for _, netInfo := range networkInfos {
|
|
network := NewNetwork(b.conn, netInfo.Path)
|
|
props, err := network.GetProperties()
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
if props.Name == "" || seenSSIDs[props.Name] {
|
|
continue
|
|
}
|
|
seenSSIDs[props.Name] = true
|
|
|
|
// Convert iwd network to backend-agnostic format
|
|
backendNet := backend.BackendNetwork{
|
|
SSID: props.Name,
|
|
SignalDBm: netInfo.Signal / 100, // iwd provides 100*dBm, convert to dBm
|
|
SecurityType: props.Type,
|
|
BSSID: generateSyntheticBSSID(props.Name), // iwd doesn't expose BSSID
|
|
Frequency: 0, // iwd doesn't expose frequency in GetOrderedNetworks
|
|
}
|
|
|
|
networks = append(networks, backendNet)
|
|
}
|
|
|
|
return networks, nil
|
|
}
|
|
|
|
// IsScanning checks if a scan is currently in progress
|
|
func (b *IWDBackend) IsScanning() (bool, error) {
|
|
return b.station.IsScanning()
|
|
}
|
|
|
|
// Connect connects to a WiFi network
|
|
func (b *IWDBackend) Connect(ssid, password string) error {
|
|
// Store passphrase in agent for callback
|
|
if password != "" {
|
|
b.agent.SetPassphrase(ssid, password)
|
|
}
|
|
|
|
// Ensure passphrase is cleared after connection attempt
|
|
defer func() {
|
|
if password != "" {
|
|
b.agent.ClearPassphrase(ssid)
|
|
}
|
|
}()
|
|
|
|
// Get network object
|
|
network, err := b.station.GetNetwork(ssid)
|
|
if err != nil {
|
|
return fmt.Errorf("réseau '%s' non trouvé: %v", ssid, err)
|
|
}
|
|
|
|
// Connect - iwd will call agent.RequestPassphrase() if needed
|
|
if err := network.Connect(); err != nil {
|
|
return fmt.Errorf("erreur lors de la connexion: %v", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Disconnect disconnects from the current WiFi network
|
|
func (b *IWDBackend) Disconnect() error {
|
|
if err := b.station.Disconnect(); err != nil {
|
|
return fmt.Errorf("erreur lors de la déconnexion: %v", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetConnectionState returns the current WiFi connection state
|
|
func (b *IWDBackend) GetConnectionState() (backend.ConnectionState, error) {
|
|
state, err := b.station.GetState()
|
|
if err != nil {
|
|
return backend.StateDisconnected, err
|
|
}
|
|
return mapIWDState(state), nil
|
|
}
|
|
|
|
// GetConnectedSSID returns the SSID of the currently connected network
|
|
func (b *IWDBackend) GetConnectedSSID() string {
|
|
network, err := b.station.GetConnectedNetwork()
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
|
|
props, err := network.GetProperties()
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
|
|
return props.Name
|
|
}
|
|
|
|
// StartEventMonitoring starts monitoring WiFi events
|
|
func (b *IWDBackend) StartEventMonitoring(callbacks backend.EventCallbacks) error {
|
|
b.callbacks = callbacks
|
|
|
|
// Create signal monitor
|
|
b.signalMonitor = NewSignalMonitor(b.conn, b.station)
|
|
|
|
// Register callbacks - wrap to convert iwd types to backend types
|
|
b.signalMonitor.OnStateChange(func(state StationState, ssid string) {
|
|
if b.callbacks.OnStateChange != nil {
|
|
b.callbacks.OnStateChange(mapIWDState(state), ssid)
|
|
}
|
|
})
|
|
|
|
b.signalMonitor.OnScanComplete(func() {
|
|
if b.callbacks.OnScanComplete != nil {
|
|
b.callbacks.OnScanComplete()
|
|
}
|
|
})
|
|
|
|
// Start monitoring
|
|
return b.signalMonitor.Start()
|
|
}
|
|
|
|
// StopEventMonitoring stops monitoring WiFi events
|
|
func (b *IWDBackend) StopEventMonitoring() {
|
|
if b.signalMonitor != nil {
|
|
b.signalMonitor.Stop()
|
|
}
|
|
}
|
|
|
|
// mapIWDState maps iwd-specific states to backend-agnostic states
|
|
func mapIWDState(state StationState) backend.ConnectionState {
|
|
switch state {
|
|
case StateConnected:
|
|
return backend.StateConnected
|
|
case StateConnecting:
|
|
return backend.StateConnecting
|
|
case StateDisconnecting:
|
|
return backend.StateDisconnecting
|
|
case StateDisconnected:
|
|
return backend.StateDisconnected
|
|
case StateRoaming:
|
|
// Map roaming to connected since we're still connected during roaming
|
|
return backend.StateConnected
|
|
default:
|
|
return backend.StateDisconnected
|
|
}
|
|
}
|
|
|
|
// generateSyntheticBSSID generates a consistent fake BSSID from SSID
|
|
// (iwd doesn't expose real BSSID)
|
|
func generateSyntheticBSSID(ssid string) string {
|
|
// Use a simple hash approach - consistent per SSID
|
|
hash := 0
|
|
for _, c := range ssid {
|
|
hash = ((hash << 5) - hash) + int(c)
|
|
}
|
|
|
|
// Generate 6 bytes for MAC address
|
|
b1 := byte((hash >> 0) & 0xff)
|
|
b2 := byte((hash >> 8) & 0xff)
|
|
b3 := byte((hash >> 16) & 0xff)
|
|
b4 := byte((hash >> 24) & 0xff)
|
|
b5 := byte(len(ssid) & 0xff)
|
|
b6 := byte((len(ssid) >> 8) & 0xff)
|
|
|
|
return fmt.Sprintf("%02x:%02x:%02x:%02x:%02x:%02x", b1, b2, b3, b4, b5, b6)
|
|
}
|