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 } }