826 lines
20 KiB
Go
826 lines
20 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"embed"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/fs"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"os/exec"
|
|
"regexp"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/godbus/dbus/v5"
|
|
"github.com/gorilla/mux"
|
|
"github.com/gorilla/websocket"
|
|
)
|
|
|
|
//go:embed all:static
|
|
var _assets embed.FS
|
|
|
|
// Structures de données
|
|
type WiFiNetwork struct {
|
|
SSID string `json:"ssid"`
|
|
Signal int `json:"signal"`
|
|
Security string `json:"security"`
|
|
Channel int `json:"channel"`
|
|
BSSID string `json:"bssid"`
|
|
}
|
|
|
|
type ConnectedDevice struct {
|
|
Name string `json:"name"`
|
|
Type string `json:"type"`
|
|
MAC string `json:"mac"`
|
|
IP string `json:"ip"`
|
|
}
|
|
|
|
type HotspotConfig struct {
|
|
SSID string `json:"ssid"`
|
|
Password string `json:"password"`
|
|
Channel int `json:"channel"`
|
|
}
|
|
|
|
type SystemStatus struct {
|
|
Connected bool `json:"connected"`
|
|
ConnectedSSID string `json:"connectedSSID"`
|
|
HotspotEnabled bool `json:"hotspotEnabled"`
|
|
ConnectedCount int `json:"connectedCount"`
|
|
DataUsage float64 `json:"dataUsage"`
|
|
Uptime int64 `json:"uptime"`
|
|
ConnectedDevices []ConnectedDevice `json:"connectedDevices"`
|
|
}
|
|
|
|
type WiFiConnectRequest struct {
|
|
SSID string `json:"ssid"`
|
|
Password string `json:"password"`
|
|
}
|
|
|
|
type LogEntry struct {
|
|
Timestamp time.Time `json:"timestamp"`
|
|
Source string `json:"source"`
|
|
Message string `json:"message"`
|
|
}
|
|
|
|
// Variables globales
|
|
var (
|
|
currentStatus SystemStatus
|
|
statusMutex sync.RWMutex
|
|
logEntries []LogEntry
|
|
logMutex sync.RWMutex
|
|
websocketClients = make(map[*websocket.Conn]bool)
|
|
clientsMutex sync.RWMutex
|
|
upgrader = websocket.Upgrader{
|
|
CheckOrigin: func(r *http.Request) bool {
|
|
return true
|
|
},
|
|
}
|
|
startTime = time.Now()
|
|
dbusConn *dbus.Conn
|
|
wpaSupplicant dbus.BusObject
|
|
)
|
|
|
|
const (
|
|
WLAN_INTERFACE = "wlan0"
|
|
AP_INTERFACE = "wlan1"
|
|
HOSTAPD_CONF = "/etc/hostapd/hostapd.conf"
|
|
WPA_CONF = "/etc/wpa_supplicant/wpa_supplicant.conf"
|
|
|
|
// D-Bus constantes pour wpa_supplicant
|
|
WPA_SUPPLICANT_SERVICE = "fi.w1.wpa_supplicant1"
|
|
WPA_SUPPLICANT_PATH = "/fi/w1/wpa_supplicant1"
|
|
WPA_SUPPLICANT_IFACE = "fi.w1.wpa_supplicant1"
|
|
WPA_INTERFACE_IFACE = "fi.w1.wpa_supplicant1.Interface"
|
|
WPA_BSS_IFACE = "fi.w1.wpa_supplicant1.BSS"
|
|
WPA_NETWORK_IFACE = "fi.w1.wpa_supplicant1.Network"
|
|
)
|
|
|
|
func main() {
|
|
// Initialiser D-Bus
|
|
var err error
|
|
dbusConn, err = dbus.SystemBus()
|
|
if err != nil {
|
|
log.Fatalf("Erreur de connexion D-Bus: %v", err)
|
|
}
|
|
defer dbusConn.Close()
|
|
|
|
wpaSupplicant = dbusConn.Object(WPA_SUPPLICANT_SERVICE, dbus.ObjectPath(WPA_SUPPLICANT_PATH))
|
|
|
|
// Initialiser le statut système
|
|
initializeStatus()
|
|
|
|
// Démarrer les tâches périodiques
|
|
go periodicStatusUpdate()
|
|
go periodicDeviceUpdate()
|
|
|
|
// Configuration du routeur
|
|
r := mux.NewRouter()
|
|
|
|
// Routes API
|
|
api := r.PathPrefix("/api").Subrouter()
|
|
api.HandleFunc("/wifi/scan", scanWiFiHandler).Methods("GET")
|
|
api.HandleFunc("/wifi/connect", connectWiFiHandler).Methods("POST")
|
|
api.HandleFunc("/wifi/disconnect", disconnectWiFiHandler).Methods("POST")
|
|
api.HandleFunc("/hotspot/config", configureHotspotHandler).Methods("POST")
|
|
api.HandleFunc("/hotspot/toggle", toggleHotspotHandler).Methods("POST")
|
|
api.HandleFunc("/devices", getDevicesHandler).Methods("GET")
|
|
api.HandleFunc("/status", getStatusHandler).Methods("GET")
|
|
api.HandleFunc("/logs", getLogsHandler).Methods("GET")
|
|
api.HandleFunc("/logs", clearLogsHandler).Methods("DELETE")
|
|
|
|
// WebSocket pour les logs en temps réel
|
|
r.HandleFunc("/ws/logs", websocketHandler)
|
|
|
|
// Servir les fichiers statiques
|
|
sub, err := fs.Sub(_assets, "static")
|
|
if err != nil {
|
|
log.Fatal("Unable to cd to static/ directory:", err)
|
|
}
|
|
Assets := http.FS(sub)
|
|
r.PathPrefix("/").Handler(http.FileServer(Assets))
|
|
|
|
addLog("Système", "Serveur API démarré sur le port 8080")
|
|
log.Fatal(http.ListenAndServe(":8080", r))
|
|
}
|
|
|
|
func initializeStatus() {
|
|
statusMutex.Lock()
|
|
defer statusMutex.Unlock()
|
|
|
|
currentStatus = SystemStatus{
|
|
Connected: false,
|
|
ConnectedSSID: "",
|
|
HotspotEnabled: true,
|
|
ConnectedCount: 0,
|
|
DataUsage: 0.0,
|
|
Uptime: 0,
|
|
}
|
|
}
|
|
|
|
// Handlers API
|
|
|
|
func scanWiFiHandler(w http.ResponseWriter, r *http.Request) {
|
|
networks, err := scanWiFiNetworks()
|
|
if err != nil {
|
|
addLog("WiFi", fmt.Sprintf("Erreur lors du scan: %v", err))
|
|
http.Error(w, "Erreur lors du scan WiFi", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
addLog("WiFi", fmt.Sprintf("Scan terminé - %d réseaux trouvés", len(networks)))
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(networks)
|
|
}
|
|
|
|
func connectWiFiHandler(w http.ResponseWriter, r *http.Request) {
|
|
var req WiFiConnectRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
http.Error(w, "Données invalides", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
addLog("WiFi", fmt.Sprintf("Tentative de connexion à %s", req.SSID))
|
|
|
|
err := connectToWiFiDBus(req.SSID, req.Password)
|
|
if err != nil {
|
|
addLog("WiFi", fmt.Sprintf("Échec de connexion: %v", err))
|
|
http.Error(w, fmt.Sprintf("Échec de connexion: %v", err), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
statusMutex.Lock()
|
|
currentStatus.Connected = true
|
|
currentStatus.ConnectedSSID = req.SSID
|
|
statusMutex.Unlock()
|
|
|
|
addLog("WiFi", fmt.Sprintf("Connexion réussie à %s", req.SSID))
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]string{"status": "success"})
|
|
}
|
|
|
|
func disconnectWiFiHandler(w http.ResponseWriter, r *http.Request) {
|
|
addLog("WiFi", "Tentative de déconnexion")
|
|
|
|
err := disconnectWiFiDBus()
|
|
if err != nil {
|
|
addLog("WiFi", fmt.Sprintf("Erreur de déconnexion: %v", err))
|
|
http.Error(w, fmt.Sprintf("Erreur de déconnexion: %v", err), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
statusMutex.Lock()
|
|
currentStatus.Connected = false
|
|
currentStatus.ConnectedSSID = ""
|
|
statusMutex.Unlock()
|
|
|
|
addLog("WiFi", "Déconnexion réussie")
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]string{"status": "success"})
|
|
}
|
|
|
|
func configureHotspotHandler(w http.ResponseWriter, r *http.Request) {
|
|
var config HotspotConfig
|
|
if err := json.NewDecoder(r.Body).Decode(&config); err != nil {
|
|
http.Error(w, "Données invalides", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
err := configureHotspot(config)
|
|
if err != nil {
|
|
addLog("Hotspot", fmt.Sprintf("Erreur de configuration: %v", err))
|
|
http.Error(w, fmt.Sprintf("Erreur de configuration: %v", err), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
addLog("Hotspot", fmt.Sprintf("Configuration mise à jour: %s (Canal %d)", config.SSID, config.Channel))
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]string{"status": "success"})
|
|
}
|
|
|
|
func toggleHotspotHandler(w http.ResponseWriter, r *http.Request) {
|
|
statusMutex.Lock()
|
|
currentStatus.HotspotEnabled = !currentStatus.HotspotEnabled
|
|
enabled := currentStatus.HotspotEnabled
|
|
statusMutex.Unlock()
|
|
|
|
var err error
|
|
if enabled {
|
|
err = startHotspot()
|
|
addLog("Hotspot", "Hotspot activé")
|
|
} else {
|
|
err = stopHotspot()
|
|
addLog("Hotspot", "Hotspot désactivé")
|
|
}
|
|
|
|
if err != nil {
|
|
addLog("Hotspot", fmt.Sprintf("Erreur: %v", err))
|
|
http.Error(w, fmt.Sprintf("Erreur: %v", err), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]bool{"enabled": enabled})
|
|
}
|
|
|
|
func getDevicesHandler(w http.ResponseWriter, r *http.Request) {
|
|
devices, err := getConnectedDevices()
|
|
if err != nil {
|
|
addLog("Système", fmt.Sprintf("Erreur lors de la récupération des appareils: %v", err))
|
|
http.Error(w, "Erreur lors de la récupération des appareils", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(devices)
|
|
}
|
|
|
|
func getStatusHandler(w http.ResponseWriter, r *http.Request) {
|
|
statusMutex.RLock()
|
|
status := currentStatus
|
|
status.Uptime = int64(time.Since(startTime).Seconds())
|
|
statusMutex.RUnlock()
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(status)
|
|
}
|
|
|
|
func getLogsHandler(w http.ResponseWriter, r *http.Request) {
|
|
logMutex.RLock()
|
|
logs := make([]LogEntry, len(logEntries))
|
|
copy(logs, logEntries)
|
|
logMutex.RUnlock()
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(logs)
|
|
}
|
|
|
|
func clearLogsHandler(w http.ResponseWriter, r *http.Request) {
|
|
logMutex.Lock()
|
|
logEntries = []LogEntry{}
|
|
logMutex.Unlock()
|
|
|
|
addLog("Système", "Logs effacés")
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]string{"status": "success"})
|
|
}
|
|
|
|
// Fonctions WiFi avec D-Bus
|
|
|
|
func scanWiFiNetworks() ([]WiFiNetwork, error) {
|
|
interfacePath, err := getWiFiInterfacePath()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("impossible d'obtenir l'interface WiFi: %v", err)
|
|
}
|
|
|
|
// Déclencher un scan
|
|
wifiInterface := dbusConn.Object(WPA_SUPPLICANT_SERVICE, interfacePath)
|
|
call := wifiInterface.Call(WPA_INTERFACE_IFACE+".Scan", 0, map[string]dbus.Variant{"Type": dbus.MakeVariant("active")})
|
|
if call.Err != nil {
|
|
return nil, fmt.Errorf("erreur lors du scan: %v", call.Err)
|
|
}
|
|
|
|
// Attendre un peu pour que le scan se termine
|
|
time.Sleep(2 * time.Second)
|
|
|
|
// Récupérer la liste des BSS
|
|
bssePaths, err := wifiInterface.GetProperty(WPA_INTERFACE_IFACE + ".BSSs")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("erreur lors de la récupération des BSS: %v", err)
|
|
}
|
|
|
|
var networks []WiFiNetwork
|
|
seenSSIDs := make(map[string]bool)
|
|
|
|
for _, bssPath := range bssePaths.Value().([]dbus.ObjectPath) {
|
|
bss := dbusConn.Object(WPA_SUPPLICANT_SERVICE, bssPath)
|
|
|
|
// Récupérer les propriétés du BSS
|
|
var props map[string]dbus.Variant
|
|
err = bss.Call("org.freedesktop.DBus.Properties.GetAll", 0, WPA_BSS_IFACE).Store(&props)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
network := WiFiNetwork{}
|
|
|
|
// Extraire SSID
|
|
if ssidBytes, ok := props["SSID"].Value().([]byte); ok {
|
|
network.SSID = string(ssidBytes)
|
|
}
|
|
|
|
// Éviter les doublons
|
|
if network.SSID == "" || seenSSIDs[network.SSID] {
|
|
continue
|
|
}
|
|
seenSSIDs[network.SSID] = true
|
|
|
|
// Extraire BSSID
|
|
if bssidBytes, ok := props["BSSID"].Value().([]byte); ok {
|
|
network.BSSID = fmt.Sprintf("%02x:%02x:%02x:%02x:%02x:%02x",
|
|
bssidBytes[0], bssidBytes[1], bssidBytes[2], bssidBytes[3], bssidBytes[4], bssidBytes[5])
|
|
}
|
|
|
|
// Extraire la force du signal
|
|
if signal, ok := props["Signal"].Value().(int16); ok {
|
|
network.Signal = signalToStrength(int(signal))
|
|
}
|
|
|
|
// Extraire la fréquence et calculer le canal
|
|
if frequency, ok := props["Frequency"].Value().(uint16); ok {
|
|
network.Channel = frequencyToChannel(int(frequency))
|
|
}
|
|
|
|
// Déterminer la sécurité
|
|
if privacyVal, ok := props["Privacy"].Value().(bool); ok && privacyVal {
|
|
if wpaProps, ok := props["WPA"].Value().(map[string]dbus.Variant); ok && len(wpaProps) > 0 {
|
|
network.Security = "WPA"
|
|
} else if rsnProps, ok := props["RSN"].Value().(map[string]dbus.Variant); ok && len(rsnProps) > 0 {
|
|
network.Security = "WPA2"
|
|
} else {
|
|
network.Security = "WEP"
|
|
}
|
|
} else {
|
|
network.Security = "Open"
|
|
}
|
|
|
|
networks = append(networks, network)
|
|
}
|
|
|
|
// Trier par force du signal
|
|
sort.Slice(networks, func(i, j int) bool {
|
|
return networks[i].Signal > networks[j].Signal
|
|
})
|
|
|
|
return networks, nil
|
|
}
|
|
|
|
func connectToWiFiDBus(ssid, password string) error {
|
|
interfacePath, err := getWiFiInterfacePath()
|
|
if err != nil {
|
|
return fmt.Errorf("impossible d'obtenir l'interface WiFi: %v", err)
|
|
}
|
|
|
|
wifiInterface := dbusConn.Object(WPA_SUPPLICANT_SERVICE, interfacePath)
|
|
|
|
// Créer un nouveau réseau
|
|
networkConfig := map[string]dbus.Variant{
|
|
"ssid": dbus.MakeVariant(ssid),
|
|
}
|
|
|
|
if password != "" {
|
|
networkConfig["psk"] = dbus.MakeVariant(password)
|
|
}
|
|
|
|
var networkPath dbus.ObjectPath
|
|
err = wifiInterface.Call(WPA_INTERFACE_IFACE+".AddNetwork", 0, networkConfig).Store(&networkPath)
|
|
if err != nil {
|
|
return fmt.Errorf("erreur lors de l'ajout du réseau: %v", err)
|
|
}
|
|
|
|
// Sélectionner le réseau
|
|
err = wifiInterface.Call(WPA_INTERFACE_IFACE+".SelectNetwork", 0, networkPath).Err
|
|
if err != nil {
|
|
return fmt.Errorf("erreur lors de la sélection du réseau: %v", err)
|
|
}
|
|
|
|
// Attendre la connexion
|
|
for i := 0; i < 20; i++ {
|
|
time.Sleep(500 * time.Millisecond)
|
|
if isConnectedDBus() {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
return fmt.Errorf("timeout lors de la connexion")
|
|
}
|
|
|
|
func disconnectWiFiDBus() error {
|
|
interfacePath, err := getWiFiInterfacePath()
|
|
if err != nil {
|
|
return fmt.Errorf("impossible d'obtenir l'interface WiFi: %v", err)
|
|
}
|
|
|
|
wifiInterface := dbusConn.Object(WPA_SUPPLICANT_SERVICE, interfacePath)
|
|
|
|
// Déconnecter
|
|
err = wifiInterface.Call(WPA_INTERFACE_IFACE+".Disconnect", 0).Err
|
|
if err != nil {
|
|
return fmt.Errorf("erreur lors de la déconnexion: %v", err)
|
|
}
|
|
|
|
// Supprimer tous les réseaux
|
|
var networks []dbus.ObjectPath
|
|
err = wifiInterface.Call(WPA_INTERFACE_IFACE+".Get", 0, WPA_INTERFACE_IFACE, "Networks").Store(&networks)
|
|
if err == nil {
|
|
for _, networkPath := range networks {
|
|
wifiInterface.Call(WPA_INTERFACE_IFACE+".RemoveNetwork", 0, networkPath)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func getWiFiInterfacePath() (dbus.ObjectPath, error) {
|
|
var interfacePath dbus.ObjectPath
|
|
err := wpaSupplicant.Call(WPA_SUPPLICANT_IFACE+".GetInterface", 0, WLAN_INTERFACE).Store(&interfacePath)
|
|
if err != nil {
|
|
return "", fmt.Errorf("erreur lors de la récupération des interfaces: %v", err)
|
|
}
|
|
|
|
return interfacePath, nil
|
|
}
|
|
|
|
func isConnectedDBus() bool {
|
|
interfacePath, err := getWiFiInterfacePath()
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
wifiInterface := dbusConn.Object(WPA_SUPPLICANT_SERVICE, interfacePath)
|
|
var state string
|
|
err = wifiInterface.Call(WPA_INTERFACE_IFACE+".Get", 0, WPA_INTERFACE_IFACE, "State").Store(&state)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
return state == "completed"
|
|
}
|
|
|
|
func frequencyToChannel(frequency int) int {
|
|
if frequency >= 2412 && frequency <= 2484 {
|
|
if frequency == 2484 {
|
|
return 14
|
|
}
|
|
return (frequency-2412)/5 + 1
|
|
} else if frequency >= 5170 && frequency <= 5825 {
|
|
return (frequency - 5000) / 5
|
|
}
|
|
return 0
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
func connectToWiFi(ssid, password string) error {
|
|
// Créer la configuration wpa_supplicant
|
|
config := fmt.Sprintf(`ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
|
|
update_config=1
|
|
country=FR
|
|
|
|
network={
|
|
ssid="%s"
|
|
psk="%s"
|
|
}
|
|
`, ssid, password)
|
|
|
|
err := os.WriteFile(WPA_CONF, []byte(config), 0600)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Redémarrer wpa_supplicant
|
|
cmd := exec.Command("systemctl", "restart", "wpa_supplicant")
|
|
if err := cmd.Run(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Attendre la connexion
|
|
for i := 0; i < 10; i++ {
|
|
time.Sleep(1 * time.Second)
|
|
if isConnected() {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
return fmt.Errorf("timeout lors de la connexion")
|
|
}
|
|
|
|
func isConnected() bool {
|
|
cmd := exec.Command("iwconfig", WLAN_INTERFACE)
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
return strings.Contains(string(output), "Access Point:")
|
|
}
|
|
|
|
// Fonctions Hotspot
|
|
|
|
func configureHotspot(config HotspotConfig) error {
|
|
hostapdConfig := fmt.Sprintf(`interface=%s
|
|
driver=nl80211
|
|
ssid=%s
|
|
hw_mode=g
|
|
channel=%d
|
|
wmm_enabled=0
|
|
macaddr_acl=0
|
|
auth_algs=1
|
|
ignore_broadcast_ssid=0
|
|
wpa=2
|
|
wpa_passphrase=%s
|
|
wpa_key_mgmt=WPA-PSK
|
|
wpa_pairwise=TKIP
|
|
rsn_pairwise=CCMP
|
|
`, AP_INTERFACE, config.SSID, config.Channel, config.Password)
|
|
|
|
return os.WriteFile(HOSTAPD_CONF, []byte(hostapdConfig), 0644)
|
|
}
|
|
|
|
func startHotspot() error {
|
|
cmd := exec.Command("systemctl", "start", "hostapd")
|
|
return cmd.Run()
|
|
}
|
|
|
|
func stopHotspot() error {
|
|
cmd := exec.Command("systemctl", "stop", "hostapd")
|
|
return cmd.Run()
|
|
}
|
|
|
|
// Fonctions pour les appareils connectés
|
|
|
|
func getConnectedDevices() ([]ConnectedDevice, error) {
|
|
var devices []ConnectedDevice
|
|
|
|
// Lire les baux DHCP
|
|
leases, err := parseDHCPLeases()
|
|
if err != nil {
|
|
return devices, err
|
|
}
|
|
|
|
// Obtenir les informations ARP
|
|
arpInfo, err := getARPInfo()
|
|
if err != nil {
|
|
return devices, err
|
|
}
|
|
|
|
for _, lease := range leases {
|
|
device := ConnectedDevice{
|
|
Name: lease.Hostname,
|
|
MAC: lease.MAC,
|
|
IP: lease.IP,
|
|
Type: guessDeviceType(lease.Hostname, lease.MAC),
|
|
}
|
|
|
|
// Vérifier si l'appareil est toujours connecté via ARP
|
|
if _, exists := arpInfo[lease.IP]; exists {
|
|
devices = append(devices, device)
|
|
}
|
|
}
|
|
|
|
return devices, nil
|
|
}
|
|
|
|
type DHCPLease struct {
|
|
IP string
|
|
MAC string
|
|
Hostname string
|
|
}
|
|
|
|
func parseDHCPLeases() ([]DHCPLease, error) {
|
|
var leases []DHCPLease
|
|
|
|
file, err := os.Open("/var/lib/dhcp/dhcpd.leases")
|
|
if err != nil {
|
|
return leases, err
|
|
}
|
|
defer file.Close()
|
|
|
|
scanner := bufio.NewScanner(file)
|
|
var currentLease DHCPLease
|
|
|
|
for scanner.Scan() {
|
|
line := strings.TrimSpace(scanner.Text())
|
|
|
|
if strings.HasPrefix(line, "lease ") {
|
|
ip := strings.Fields(line)[1]
|
|
currentLease = DHCPLease{IP: ip}
|
|
} else if strings.Contains(line, "hardware ethernet") {
|
|
mac := strings.Fields(line)[2]
|
|
mac = strings.TrimSuffix(mac, ";")
|
|
currentLease.MAC = mac
|
|
} else if strings.Contains(line, "client-hostname") {
|
|
hostname := strings.Fields(line)[1]
|
|
hostname = strings.Trim(hostname, `";`)
|
|
currentLease.Hostname = hostname
|
|
} else if line == "}" {
|
|
if currentLease.IP != "" && currentLease.MAC != "" {
|
|
leases = append(leases, currentLease)
|
|
}
|
|
currentLease = DHCPLease{}
|
|
}
|
|
}
|
|
|
|
return leases, nil
|
|
}
|
|
|
|
func getARPInfo() (map[string]string, error) {
|
|
arpInfo := make(map[string]string)
|
|
|
|
cmd := exec.Command("arp", "-a")
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
return arpInfo, err
|
|
}
|
|
|
|
lines := strings.Split(string(output), "\n")
|
|
for _, line := range lines {
|
|
if matches := regexp.MustCompile(`\(([^)]+)\) at ([0-9a-fA-F:]{17})`).FindStringSubmatch(line); len(matches) > 2 {
|
|
ip := matches[1]
|
|
mac := matches[2]
|
|
arpInfo[ip] = mac
|
|
}
|
|
}
|
|
|
|
return arpInfo, nil
|
|
}
|
|
|
|
func guessDeviceType(hostname, mac string) string {
|
|
hostname = strings.ToLower(hostname)
|
|
|
|
if strings.Contains(hostname, "iphone") || strings.Contains(hostname, "android") {
|
|
return "mobile"
|
|
} else if strings.Contains(hostname, "ipad") || strings.Contains(hostname, "tablet") {
|
|
return "tablet"
|
|
} else if strings.Contains(hostname, "macbook") || strings.Contains(hostname, "laptop") {
|
|
return "laptop"
|
|
}
|
|
|
|
// Deviner par préfixe MAC (OUI)
|
|
macPrefix := strings.ToUpper(mac[:8])
|
|
switch macPrefix {
|
|
case "00:50:56", "00:0C:29", "00:05:69": // VMware
|
|
return "laptop"
|
|
case "08:00:27": // VirtualBox
|
|
return "laptop"
|
|
default:
|
|
return "mobile"
|
|
}
|
|
}
|
|
|
|
// Fonctions de logging
|
|
|
|
func addLog(source, message string) {
|
|
logMutex.Lock()
|
|
entry := LogEntry{
|
|
Timestamp: time.Now(),
|
|
Source: source,
|
|
Message: message,
|
|
}
|
|
logEntries = append(logEntries, entry)
|
|
|
|
// Garder seulement les 100 derniers logs
|
|
if len(logEntries) > 100 {
|
|
logEntries = logEntries[len(logEntries)-100:]
|
|
}
|
|
logMutex.Unlock()
|
|
|
|
// Envoyer aux clients WebSocket
|
|
broadcastToWebSockets(entry)
|
|
|
|
// Log vers la console
|
|
log.Printf("[%s] %s", source, message)
|
|
}
|
|
|
|
// WebSocket pour les logs en temps réel
|
|
|
|
func websocketHandler(w http.ResponseWriter, r *http.Request) {
|
|
conn, err := upgrader.Upgrade(w, r, nil)
|
|
if err != nil {
|
|
log.Printf("Erreur WebSocket: %v", err)
|
|
return
|
|
}
|
|
defer conn.Close()
|
|
|
|
clientsMutex.Lock()
|
|
websocketClients[conn] = true
|
|
clientsMutex.Unlock()
|
|
|
|
defer func() {
|
|
clientsMutex.Lock()
|
|
delete(websocketClients, conn)
|
|
clientsMutex.Unlock()
|
|
}()
|
|
|
|
// Envoyer les logs existants
|
|
logMutex.RLock()
|
|
for _, entry := range logEntries {
|
|
conn.WriteJSON(entry)
|
|
}
|
|
logMutex.RUnlock()
|
|
|
|
// Maintenir la connexion
|
|
for {
|
|
_, _, err := conn.ReadMessage()
|
|
if err != nil {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
func broadcastToWebSockets(entry LogEntry) {
|
|
clientsMutex.RLock()
|
|
defer clientsMutex.RUnlock()
|
|
|
|
for client := range websocketClients {
|
|
err := client.WriteJSON(entry)
|
|
if err != nil {
|
|
client.Close()
|
|
delete(websocketClients, client)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Tâches périodiques
|
|
|
|
func periodicStatusUpdate() {
|
|
ticker := time.NewTicker(5 * time.Second)
|
|
defer ticker.Stop()
|
|
|
|
for range ticker.C {
|
|
statusMutex.Lock()
|
|
currentStatus.Connected = isConnected()
|
|
if !currentStatus.Connected {
|
|
currentStatus.ConnectedSSID = ""
|
|
}
|
|
statusMutex.Unlock()
|
|
}
|
|
}
|
|
|
|
func periodicDeviceUpdate() {
|
|
ticker := time.NewTicker(10 * time.Second)
|
|
defer ticker.Stop()
|
|
|
|
for range ticker.C {
|
|
devices, err := getConnectedDevices()
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
statusMutex.Lock()
|
|
currentStatus.ConnectedDevices = devices
|
|
currentStatus.ConnectedCount = len(devices)
|
|
statusMutex.Unlock()
|
|
}
|
|
}
|