repeater/internal/wifi/wifi.go

242 lines
5.6 KiB
Go

package wifi
import (
"fmt"
"sort"
"strings"
"time"
"github.com/godbus/dbus/v5"
"github.com/nemunaire/repeater/internal/models"
"github.com/nemunaire/repeater/internal/wifi/iwd"
)
const (
AGENT_PATH = "/com/github/nemunaire/repeater/agent"
)
var (
wlanInterface string
dbusConn *dbus.Conn
iwdManager *iwd.Manager
station *iwd.Station
agent *iwd.Agent
agentManager *iwd.AgentManager
)
// Initialize initializes the WiFi service with iwd D-Bus connection
func Initialize(interfaceName string) error {
wlanInterface = interfaceName
var err error
// Connect to D-Bus
dbusConn, err = dbus.SystemBus()
if err != nil {
return fmt.Errorf("échec de connexion à D-Bus: %v", err)
}
// Find station for interface
iwdManager = iwd.NewManager(dbusConn)
station, err = iwdManager.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
agent = iwd.NewAgent(dbusConn, dbus.ObjectPath(AGENT_PATH))
if err := agent.Export(); err != nil {
return fmt.Errorf("échec de l'export de l'agent: %v", err)
}
agentManager = iwd.NewAgentManager(dbusConn)
if err := agentManager.RegisterAgent(dbus.ObjectPath(AGENT_PATH)); err != nil {
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 Close() {
if agentManager != nil && agent != nil {
agentManager.UnregisterAgent(dbus.ObjectPath(AGENT_PATH))
agent.Unexport()
}
if dbusConn != nil {
dbusConn.Close()
}
}
// ScanNetworks scans for available WiFi networks
func ScanNetworks() ([]models.WiFiNetwork, error) {
// Check if already scanning
scanning, err := station.IsScanning()
if err == nil && scanning {
time.Sleep(3 * time.Second)
} else {
// Trigger scan
err := station.Scan()
if err != nil && !strings.Contains(err.Error(), "rejected") {
return nil, fmt.Errorf("erreur lors du scan: %v", err)
}
time.Sleep(2 * time.Second)
}
// Get ordered networks
networkInfos, err := station.GetOrderedNetworks()
if err != nil {
return nil, fmt.Errorf("erreur lors de la récupération des réseaux: %v", err)
}
var networks []models.WiFiNetwork
seenSSIDs := make(map[string]bool)
for _, netInfo := range networkInfos {
network := iwd.NewNetwork(dbusConn, netInfo.Path)
props, err := network.GetProperties()
if err != nil {
continue
}
if props.Name == "" || seenSSIDs[props.Name] {
continue
}
seenSSIDs[props.Name] = true
wifiNet := models.WiFiNetwork{
SSID: props.Name,
Signal: signalToStrength(int(netInfo.Signal) / 100),
Security: mapSecurityType(props.Type),
BSSID: generateSyntheticBSSID(props.Name),
Channel: 0,
}
networks = append(networks, wifiNet)
}
// Sort by signal strength (descending)
sort.Slice(networks, func(i, j int) bool {
return networks[i].Signal > networks[j].Signal
})
return networks, nil
}
// Connect connects to a WiFi network using iwd agent callback
func Connect(ssid, password string) error {
// Store passphrase in agent for callback
if password != "" {
agent.SetPassphrase(ssid, password)
}
// Ensure passphrase is cleared after connection attempt
defer func() {
if password != "" {
agent.ClearPassphrase(ssid)
}
}()
// Get network object
network, err := 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)
}
// Poll for connection
for i := 0; i < 20; i++ {
time.Sleep(500 * time.Millisecond)
if IsConnected() {
return nil
}
}
return fmt.Errorf("timeout lors de la connexion")
}
// Disconnect disconnects from the current WiFi network
func Disconnect() error {
if err := station.Disconnect(); err != nil {
return fmt.Errorf("erreur lors de la déconnexion: %v", err)
}
return nil
}
// IsConnected checks if WiFi is connected using iwd
func IsConnected() bool {
state, err := station.GetState()
if err != nil {
return false
}
return state == iwd.StateConnected
}
// GetConnectedSSID returns the SSID of the currently connected network
func GetConnectedSSID() string {
network, err := station.GetConnectedNetwork()
if err != nil {
return ""
}
props, err := network.GetProperties()
if err != nil {
return ""
}
return props.Name
}
// mapSecurityType maps iwd security types to display format
func mapSecurityType(iwdType string) string {
switch iwdType {
case "open":
return "Open"
case "wep":
return "WEP"
case "psk":
return "WPA2"
case "8021x":
return "WPA2"
default:
return "WPA2"
}
}
// generateSyntheticBSSID generates a consistent fake BSSID from SSID
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)
}
// signalToStrength converts signal level (dBm) to strength (1-5)
func signalToStrength(level int) int {
if level >= -30 {
return 5
} else if level >= -50 {
return 4
} else if level >= -60 {
return 3
} else if level >= -70 {
return 2
} else {
return 1
}
}