334 lines
12 KiB
Go
334 lines
12 KiB
Go
|
|
package network
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"bufio"
|
|||
|
|
"encoding/json"
|
|||
|
|
"fmt"
|
|||
|
|
"net"
|
|||
|
|
"net/http"
|
|||
|
|
"os"
|
|||
|
|
"os/exec"
|
|||
|
|
"regexp"
|
|||
|
|
"strconv"
|
|||
|
|
"strings"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// NetworkInterface представляет информацию о сетевом интерфейсе
|
|||
|
|
type NetworkInterface struct {
|
|||
|
|
Name string `json:"name"`
|
|||
|
|
IP string `json:"ip"`
|
|||
|
|
Netmask string `json:"netmask"`
|
|||
|
|
Gateway string `json:"gateway"`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// WiFiNetwork представляет информацию о WiFi сети
|
|||
|
|
type WiFiNetwork struct {
|
|||
|
|
SSID string `json:"ssid"`
|
|||
|
|
Signal int `json:"signal"` // Пример, можно добавить больше информации
|
|||
|
|
Security string `json:"security"` // Пример, можно добавить больше информации
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ErrorResponse для JSON ответов с ошибками
|
|||
|
|
type ErrorResponse struct {
|
|||
|
|
Error string `json:"error"`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ScanWiFiNetworks сканирует доступные WiFi сети
|
|||
|
|
func ScanWiFiNetworks() ([]WiFiNetwork, error) {
|
|||
|
|
networks, err := ScanWiFiNetworksImpl() // Реализация зависит от ОС
|
|||
|
|
if err != nil {
|
|||
|
|
return nil, err
|
|||
|
|
}
|
|||
|
|
return networks, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ConnectToWiFi подключается к WiFi сети
|
|||
|
|
func ConnectToWiFi(ssid, password string) error {
|
|||
|
|
return ConnectToWiFiImpl(ssid, password) // Реализация зависит от ОС
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// GetNetworkStatus получает статус сетевых интерфейсов (wlan0, eth0)
|
|||
|
|
func GetNetworkStatus() ([]NetworkInterface, error) {
|
|||
|
|
interfaces, err := getNetworkStatusImpl() // Реализация зависит от ОС
|
|||
|
|
if err != nil {
|
|||
|
|
return nil, err
|
|||
|
|
}
|
|||
|
|
return interfaces, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ConfigureNetworkInterface настраивает статический IP для интерфейса
|
|||
|
|
func ConfigureNetworkInterface(iface, ip, netmask, gateway string) error {
|
|||
|
|
return configureNetworkInterfaceImpl(iface, ip, netmask, gateway) // Реализация зависит от ОС
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Функция для выполнения команд shell
|
|||
|
|
func executeCommand(command string, args ...string) (string, error) {
|
|||
|
|
cmd := exec.Command(command, args...)
|
|||
|
|
output, err := cmd.CombinedOutput()
|
|||
|
|
if err != nil {
|
|||
|
|
return "", fmt.Errorf("ошибка выполнения команды: %s %s, вывод: %s, ошибка: %v", command, strings.Join(args, " "), string(output), err)
|
|||
|
|
}
|
|||
|
|
return string(output), nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Функция для отправки JSON ответа
|
|||
|
|
func SendJSONResponse(w http.ResponseWriter, data interface{}, statusCode int) {
|
|||
|
|
w.Header().Set("Content-Type", "application/json")
|
|||
|
|
w.WriteHeader(statusCode)
|
|||
|
|
json.NewEncoder(w).Encode(data)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Функция для отправки JSON ошибки
|
|||
|
|
func SendJSONError(w http.ResponseWriter, message string, statusCode int) {
|
|||
|
|
SendJSONResponse(w, ErrorResponse{Error: message}, statusCode)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// GetConnectedWiFiSSID получает SSID текущей подключенной WiFi сети
|
|||
|
|
func GetConnectedWiFiSSID() (string, error) {
|
|||
|
|
ssid, err := getConnectedWiFiSSIDImpl() // Реализация зависит от ОС
|
|||
|
|
if err != nil {
|
|||
|
|
return "", err
|
|||
|
|
}
|
|||
|
|
return ssid, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// DisconnectWiFi отключается от текущей WiFi сети
|
|||
|
|
func DisconnectWiFi() error {
|
|||
|
|
return disconnectWiFiImpl() // Реализация зависит от ОС
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ScanWiFiNetworksImpl реализация для Linux
|
|||
|
|
func ScanWiFiNetworksImpl() ([]WiFiNetwork, error) {
|
|||
|
|
output, err := executeCommand("sudo", "iwlist", "wlan0", "scan")
|
|||
|
|
if err != nil {
|
|||
|
|
return nil, fmt.Errorf("ошибка сканирования WiFi: %v", err)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
networks := []WiFiNetwork{}
|
|||
|
|
scanner := bufio.NewScanner(strings.NewReader(output))
|
|||
|
|
currentNetwork := WiFiNetwork{}
|
|||
|
|
reSSID := regexp.MustCompile(`ESSID:"([^"]*)"`)
|
|||
|
|
reSignal := regexp.MustCompile(`level=(-?\d+)`)
|
|||
|
|
reEncryption := regexp.MustCompile(`IE: .*?(WPA|WPA2|WEP).*`)
|
|||
|
|
|
|||
|
|
for scanner.Scan() {
|
|||
|
|
line := scanner.Text()
|
|||
|
|
if strings.Contains(line, "ESSID:") {
|
|||
|
|
matches := reSSID.FindStringSubmatch(line)
|
|||
|
|
if len(matches) > 1 {
|
|||
|
|
currentNetwork.SSID = matches[1]
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if strings.Contains(line, "Signal level=") {
|
|||
|
|
matches := reSignal.FindStringSubmatch(line)
|
|||
|
|
if len(matches) > 1 {
|
|||
|
|
signalLevel, _ := strconv.Atoi(matches[1])
|
|||
|
|
currentNetwork.Signal = signalLevel
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if strings.Contains(line, "IE:") && reEncryption.MatchString(line) {
|
|||
|
|
matches := reEncryption.FindStringSubmatch(line)
|
|||
|
|
if len(matches) > 1 {
|
|||
|
|
currentNetwork.Security = matches[1]
|
|||
|
|
} else {
|
|||
|
|
currentNetwork.Security = "Open" // Если не найдено, считаем открытой
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if currentNetwork.SSID != "" {
|
|||
|
|
networks = append(networks, currentNetwork)
|
|||
|
|
currentNetwork = WiFiNetwork{} // Сброс для следующей сети
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return networks, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ConnectToWiFiImpl реализация для Linux
|
|||
|
|
func ConnectToWiFiImpl(ssid, password string) error {
|
|||
|
|
// Создаем файл конфигурации wpa_supplicant.conf
|
|||
|
|
conf := fmt.Sprintf(`ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
|
|||
|
|
update_config=1
|
|||
|
|
country=RU
|
|||
|
|
|
|||
|
|
network={
|
|||
|
|
ssid="%s"
|
|||
|
|
psk="%s"
|
|||
|
|
}
|
|||
|
|
`, ssid, password)
|
|||
|
|
|
|||
|
|
err := os.WriteFile("/etc/wpa_supplicant/wpa_supplicant.conf", []byte(conf), 0600)
|
|||
|
|
if err != nil {
|
|||
|
|
return fmt.Errorf("ошибка записи в wpa_supplicant.conf: %v", err)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Перезапускаем wpa_supplicant
|
|||
|
|
_, err = executeCommand("sudo", "wpa_cli", "-i", "wlan0", "reconfigure")
|
|||
|
|
if err != nil {
|
|||
|
|
// Попробуем перезапустить службу wpa_supplicant, если wpa_cli не сработал
|
|||
|
|
_, errRestartService := executeCommand("sudo", "systemctl", "restart", "wpa_supplicant")
|
|||
|
|
if errRestartService != nil {
|
|||
|
|
return fmt.Errorf("ошибка перезапуска wpa_supplicant: %v, также ошибка wpa_cli: %v", errRestartService, err)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Переподключаем интерфейс wlan0
|
|||
|
|
_, err = executeCommand("sudo", "ifdown", "wlan0")
|
|||
|
|
if err != nil {
|
|||
|
|
fmt.Printf("Предупреждение: ошибка ifdown wlan0: %v\n", err) // Не критическая ошибка
|
|||
|
|
}
|
|||
|
|
_, err = executeCommand("sudo", "ifup", "wlan0")
|
|||
|
|
if err != nil {
|
|||
|
|
return fmt.Errorf("ошибка ifup wlan0: %v", err)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// getNetworkStatusImpl реализация для Linux
|
|||
|
|
func getNetworkStatusImpl() ([]NetworkInterface, error) {
|
|||
|
|
interfaces := []NetworkInterface{}
|
|||
|
|
|
|||
|
|
// Получаем статус для eth0
|
|||
|
|
eth0Status, err := getInterfaceStatus("eth0")
|
|||
|
|
if err == nil {
|
|||
|
|
interfaces = append(interfaces, eth0Status)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Получаем статус для wlan0
|
|||
|
|
wlan0Status, err := getInterfaceStatus("wlan0")
|
|||
|
|
if err == nil {
|
|||
|
|
interfaces = append(interfaces, wlan0Status)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return interfaces, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func getInterfaceStatus(ifaceName string) (NetworkInterface, error) {
|
|||
|
|
ifconfigOutput, err := executeCommand("ifconfig", ifaceName)
|
|||
|
|
if err != nil {
|
|||
|
|
return NetworkInterface{}, fmt.Errorf("ошибка ifconfig %s: %v", ifaceName, err)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
ip, netmask, err := parseIfconfigOutput(ifconfigOutput)
|
|||
|
|
if err != nil && !strings.Contains(err.Error(), "interface is down") { // Игнорируем ошибку, если интерфейс выключен
|
|||
|
|
fmt.Printf("Предупреждение: ошибка парсинга ifconfig %s: %v\n", ifaceName, err)
|
|||
|
|
return NetworkInterface{Name: ifaceName}, nil // Возвращаем интерфейс без IP и маски, но без критической ошибки
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
gateway, err := getDefaultGateway(ifaceName)
|
|||
|
|
if err != nil {
|
|||
|
|
fmt.Printf("Предупреждение: ошибка получения шлюза для %s: %v\n", ifaceName, err)
|
|||
|
|
gateway = "" // Шлюз может быть не задан
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return NetworkInterface{
|
|||
|
|
Name: ifaceName,
|
|||
|
|
IP: ip,
|
|||
|
|
Netmask: netmask,
|
|||
|
|
Gateway: gateway,
|
|||
|
|
}, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func parseIfconfigOutput(output string) (ip, netmask string, err error) {
|
|||
|
|
reIP := regexp.MustCompile(`inet (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})`)
|
|||
|
|
reNetmask := regexp.MustCompile(`netmask (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})`)
|
|||
|
|
|
|||
|
|
ipMatches := reIP.FindStringSubmatch(output)
|
|||
|
|
if len(ipMatches) > 1 {
|
|||
|
|
ip = ipMatches[1]
|
|||
|
|
} else {
|
|||
|
|
return "", "", fmt.Errorf("IP адрес не найден или интерфейс выключен") // Изменено сообщение об ошибке
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
netmaskMatches := reNetmask.FindStringSubmatch(output)
|
|||
|
|
if len(netmaskMatches) > 1 {
|
|||
|
|
netmask = netmaskMatches[1]
|
|||
|
|
} else {
|
|||
|
|
netmask = "" // Маска может быть не найдена, не критично
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return ip, netmask, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func getDefaultGateway(ifaceName string) (string, error) {
|
|||
|
|
routeOutput, err := executeCommand("route", "-n")
|
|||
|
|
if err != nil {
|
|||
|
|
return "", fmt.Errorf("ошибка route -n: %v", err)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
scanner := bufio.NewScanner(strings.NewReader(routeOutput))
|
|||
|
|
for scanner.Scan() {
|
|||
|
|
line := scanner.Text()
|
|||
|
|
fields := strings.Fields(line)
|
|||
|
|
if len(fields) >= 4 && fields[0] == "0.0.0.0" && fields[5] == ifaceName {
|
|||
|
|
return fields[1], nil // Шлюз находится во втором поле
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return "", fmt.Errorf("шлюз по умолчанию для интерфейса %s не найден", ifaceName)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// configureNetworkInterfaceImpl реализация для Linux
|
|||
|
|
func configureNetworkInterfaceImpl(iface, ip, netmask, gateway string) error {
|
|||
|
|
// Проверяем валидность IP и маски
|
|||
|
|
if net.ParseIP(ip) == nil {
|
|||
|
|
return fmt.Errorf("неверный формат IP адреса: %s", ip)
|
|||
|
|
}
|
|||
|
|
if net.ParseIP(netmask) == nil {
|
|||
|
|
return fmt.Errorf("неверный формат маски подсети: %s", netmask)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Настраиваем IP и маску
|
|||
|
|
_, err := executeCommand("sudo", "ifconfig", iface, ip, "netmask", netmask)
|
|||
|
|
if err != nil {
|
|||
|
|
return fmt.Errorf("ошибка настройки IP и маски: %v", err)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Настраиваем шлюз, только если он указан
|
|||
|
|
if gateway != "" {
|
|||
|
|
if net.ParseIP(gateway) == nil {
|
|||
|
|
return fmt.Errorf("неверный формат шлюза: %s", gateway)
|
|||
|
|
}
|
|||
|
|
_, err = executeCommand("sudo", "route", "del", "default", "gw", "0.0.0.0", "dev", iface) // Удаляем старый шлюз, если есть
|
|||
|
|
if err != nil && !strings.Contains(err.Error(), "No such process") && !strings.Contains(err.Error(), "not in table") { // Игнорируем ошибки, если шлюз не был установлен
|
|||
|
|
fmt.Printf("Предупреждение: ошибка удаления старого шлюза: %v\n", err)
|
|||
|
|
}
|
|||
|
|
_, err = executeCommand("sudo", "route", "add", "default", "gw", gateway, "dev", iface)
|
|||
|
|
if err != nil {
|
|||
|
|
return fmt.Errorf("ошибка настройки шлюза: %v", err)
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
// Если шлюз не указан, удаляем шлюз по умолчанию для этого интерфейса
|
|||
|
|
_, err = executeCommand("sudo", "route", "del", "default", "gw", "0.0.0.0", "dev", iface)
|
|||
|
|
if err != nil && !strings.Contains(err.Error(), "No such process") && !strings.Contains(err.Error(), "not in table") {
|
|||
|
|
fmt.Printf("Предупреждение: ошибка удаления шлюза по умолчанию: %v\n", err)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// getConnectedWiFiSSIDImpl реализация для Linux
|
|||
|
|
func getConnectedWiFiSSIDImpl() (string, error) {
|
|||
|
|
output, err := executeCommand("iwconfig", "wlan0")
|
|||
|
|
if err != nil {
|
|||
|
|
return "", fmt.Errorf("ошибка выполнения iwconfig wlan0: %v", err)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
reSSID := regexp.MustCompile(`ESSID:"([^"]*)"`)
|
|||
|
|
matches := reSSID.FindStringSubmatch(output)
|
|||
|
|
if len(matches) > 1 {
|
|||
|
|
return matches[1], nil
|
|||
|
|
}
|
|||
|
|
return "", fmt.Errorf("не удалось получить SSID из вывода iwconfig, возможно, не подключено к WiFi")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// disconnectWiFiImpl реализация для Linux
|
|||
|
|
func disconnectWiFiImpl() error {
|
|||
|
|
// Отключаем интерфейс wlan0
|
|||
|
|
_, err := executeCommand("sudo", "ifdown", "wlan0")
|
|||
|
|
if err != nil {
|
|||
|
|
return fmt.Errorf("ошибка ifdown wlan0: %v", err)
|
|||
|
|
}
|
|||
|
|
return nil
|
|||
|
|
}
|