242 lines
5.6 KiB
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
|
|
}
|
|
}
|