Инициализация проекта
Всем привет :)
This commit is contained in:
163
internal/wireguard/client.go
Normal file
163
internal/wireguard/client.go
Normal file
@@ -0,0 +1,163 @@
|
||||
package wireguard
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
"wg-panel/internal/database"
|
||||
|
||||
"github.com/skip2/go-qrcode"
|
||||
)
|
||||
|
||||
// CreateClient создает нового клиента
|
||||
func CreateClient(db *database.Database, serverID, name, comment string) (*database.Client, error) {
|
||||
// Находим сервер
|
||||
var server *database.Server
|
||||
for i := range db.Servers {
|
||||
if db.Servers[i].ID == serverID {
|
||||
server = &db.Servers[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if server == nil {
|
||||
return nil, fmt.Errorf("server not found")
|
||||
}
|
||||
|
||||
// Генерируем ключи
|
||||
privateKey, publicKey, err := database.GenerateKeys()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Создаем клиента
|
||||
client := database.Client{
|
||||
ID: fmt.Sprintf("%d", time.Now().UnixNano()),
|
||||
ServerID: serverID,
|
||||
Name: name,
|
||||
PublicKey: publicKey,
|
||||
PrivateKey: privateKey,
|
||||
Address: database.GetNextClientIP(server),
|
||||
Enabled: true,
|
||||
Comment: comment,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
// Добавляем peer в WireGuard если сервер запущен
|
||||
if server.Enabled {
|
||||
log.Printf("➕ Добавляю peer %s в WireGuard...", client.Name)
|
||||
if err := addPeerToWireGuard(server, client); err != nil {
|
||||
log.Printf("⚠️ Ошибка добавления peer: %v", err)
|
||||
} else {
|
||||
log.Printf("✅ Peer добавлен")
|
||||
}
|
||||
}
|
||||
|
||||
// Обновляем конфиг файл
|
||||
UpdateServerConfig(server, db)
|
||||
|
||||
return &client, nil
|
||||
}
|
||||
|
||||
// DeleteClient удаляет клиента
|
||||
func DeleteClient(db *database.Database, client *database.Client) error {
|
||||
// Находим сервер
|
||||
var server *database.Server
|
||||
for j := range db.Servers {
|
||||
if db.Servers[j].ID == client.ServerID {
|
||||
server = &db.Servers[j]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Удаляем peer из WireGuard
|
||||
if server != nil && server.Enabled {
|
||||
removePeerFromWireGuard(server, *client)
|
||||
}
|
||||
|
||||
// Обновляем конфиг файл
|
||||
if server != nil {
|
||||
UpdateServerConfig(server, db)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToggleClient включает/выключает клиента
|
||||
func ToggleClient(db *database.Database, client *database.Client) error {
|
||||
client.Enabled = !client.Enabled
|
||||
|
||||
// Находим сервер
|
||||
var server *database.Server
|
||||
for j := range db.Servers {
|
||||
if db.Servers[j].ID == client.ServerID {
|
||||
server = &db.Servers[j]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if server != nil && server.Enabled {
|
||||
if client.Enabled {
|
||||
addPeerToWireGuard(server, *client)
|
||||
} else {
|
||||
removePeerFromWireGuard(server, *client)
|
||||
}
|
||||
}
|
||||
|
||||
// Обновляем конфиг файл
|
||||
if server != nil {
|
||||
UpdateServerConfig(server, db)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GenerateClientConfig генерирует конфиг для клиента
|
||||
func GenerateClientConfig(client database.Client, server *database.Server) string {
|
||||
// Получаем endpoint сервера
|
||||
endpoint := database.GetServerEndpoint()
|
||||
|
||||
config := fmt.Sprintf(`[Interface]
|
||||
PrivateKey = %s
|
||||
Address = %s/32
|
||||
DNS = %s
|
||||
|
||||
[Peer]
|
||||
PublicKey = %s
|
||||
Endpoint = %s:%d
|
||||
AllowedIPs = 0.0.0.0/0
|
||||
PersistentKeepalive = 10
|
||||
`, client.PrivateKey, client.Address, server.DNS, server.PublicKey, endpoint, server.ListenPort)
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
// GenerateQRCode генерирует QR код для конфига
|
||||
func GenerateQRCode(config string) ([]byte, error) {
|
||||
return qrcode.Encode(config, qrcode.Medium, 256)
|
||||
}
|
||||
|
||||
// addPeerToWireGuard добавляет peer в WireGuard
|
||||
func addPeerToWireGuard(server *database.Server, client database.Client) error {
|
||||
cmd := exec.Command("wg", "set", server.Interface, "peer", client.PublicKey,
|
||||
"allowed-ips", client.Address+"/32")
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
log.Printf("Ошибка добавления peer: %s, %v", string(output), err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// removePeerFromWireGuard удаляет peer из WireGuard
|
||||
func removePeerFromWireGuard(server *database.Server, client database.Client) error {
|
||||
cmd := exec.Command("wg", "set", server.Interface, "peer", client.PublicKey, "remove")
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
log.Printf("Ошибка удаления peer: %s, %v", string(output), err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
72
internal/wireguard/iptables.go
Normal file
72
internal/wireguard/iptables.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package wireguard
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// CleanIPTables очищает все правила iptables
|
||||
func CleanIPTables() error {
|
||||
log.Println("🧹 Очистка iptables...")
|
||||
|
||||
commands := [][]string{
|
||||
// Устанавливаем политики по умолчанию в ACCEPT
|
||||
{"iptables", "-P", "INPUT", "ACCEPT"},
|
||||
{"iptables", "-P", "FORWARD", "ACCEPT"},
|
||||
{"iptables", "-P", "OUTPUT", "ACCEPT"},
|
||||
|
||||
// Очищаем все цепочки
|
||||
{"iptables", "-t", "nat", "-F"},
|
||||
{"iptables", "-t", "mangle", "-F"},
|
||||
{"iptables", "-t", "filter", "-F"},
|
||||
{"iptables", "-t", "raw", "-F"},
|
||||
|
||||
// Удаляем пользовательские цепочки
|
||||
{"iptables", "-t", "nat", "-X"},
|
||||
{"iptables", "-t", "mangle", "-X"},
|
||||
{"iptables", "-t", "filter", "-X"},
|
||||
{"iptables", "-t", "raw", "-X"},
|
||||
}
|
||||
|
||||
for _, cmdArgs := range commands {
|
||||
cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
log.Printf(" ⚠️ Команда %v: %v (output: %s)", cmdArgs, err, string(output))
|
||||
// Продолжаем даже при ошибках
|
||||
}
|
||||
}
|
||||
|
||||
log.Println(" ✅ iptables очищен")
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetupBasicIPTables настраивает базовые правила iptables
|
||||
func SetupBasicIPTables() error {
|
||||
log.Println("🔧 Настройка базовых правил iptables...")
|
||||
|
||||
commands := [][]string{
|
||||
// Разрешаем loopback
|
||||
{"iptables", "-A", "INPUT", "-i", "lo", "-j", "ACCEPT"},
|
||||
{"iptables", "-A", "OUTPUT", "-o", "lo", "-j", "ACCEPT"},
|
||||
|
||||
// Разрешаем established и related соединения
|
||||
{"iptables", "-A", "INPUT", "-m", "state", "--state", "ESTABLISHED,RELATED", "-j", "ACCEPT"},
|
||||
{"iptables", "-A", "OUTPUT", "-m", "state", "--state", "ESTABLISHED,RELATED", "-j", "ACCEPT"},
|
||||
{"iptables", "-A", "FORWARD", "-m", "state", "--state", "ESTABLISHED,RELATED", "-j", "ACCEPT"},
|
||||
|
||||
// Разрешаем SSH (чтобы не потерять доступ)
|
||||
{"iptables", "-A", "INPUT", "-p", "tcp", "--dport", "22", "-j", "ACCEPT"},
|
||||
}
|
||||
|
||||
for _, cmdArgs := range commands {
|
||||
cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
log.Printf(" ⚠️ Команда %v: %v (output: %s)", cmdArgs, err, string(output))
|
||||
}
|
||||
}
|
||||
|
||||
log.Println(" ✅ Базовые правила настроены")
|
||||
return nil
|
||||
}
|
177
internal/wireguard/portforward.go
Normal file
177
internal/wireguard/portforward.go
Normal file
@@ -0,0 +1,177 @@
|
||||
package wireguard
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os/exec"
|
||||
|
||||
"wg-panel/internal/database"
|
||||
)
|
||||
|
||||
// isPortAvailable проверяет доступен ли порт
|
||||
func isPortAvailable(db *database.Database, port int, protocol string) bool {
|
||||
protocols := []string{protocol}
|
||||
if protocol == "both" {
|
||||
protocols = []string{"tcp", "udp", "both"}
|
||||
}
|
||||
|
||||
for _, client := range db.Clients {
|
||||
for _, pf := range client.PortForwards {
|
||||
if pf.Port == port {
|
||||
// Проверяем конфликт протоколов
|
||||
if pf.Protocol == "both" || protocol == "both" {
|
||||
return false
|
||||
}
|
||||
for _, p := range protocols {
|
||||
if pf.Protocol == p {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// AddPortForward добавляет проброс порта для клиента
|
||||
func AddPortForward(db *database.Database, client *database.Client, port int, protocol, description string) error {
|
||||
// Проверяем что порт свободен
|
||||
if !isPortAvailable(db, port, protocol) {
|
||||
return fmt.Errorf("порт %d/%s уже используется", port, protocol)
|
||||
}
|
||||
|
||||
// Добавляем в список
|
||||
portForward := database.PortForward{
|
||||
Port: port,
|
||||
Protocol: protocol,
|
||||
Description: description,
|
||||
}
|
||||
client.PortForwards = append(client.PortForwards, portForward)
|
||||
|
||||
// Применяем правила iptables если клиент активен
|
||||
if client.Enabled {
|
||||
if err := applyPortForwardRules(client, portForward); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// updatePortForward обновляет проброс порта
|
||||
func updatePortForward(client *database.Client, port int, protocol, newDescription string) error {
|
||||
for i, pf := range client.PortForwards {
|
||||
if pf.Port == port && pf.Protocol == protocol {
|
||||
client.PortForwards[i].Description = newDescription
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("проброс порта не найден")
|
||||
}
|
||||
|
||||
// RemovePortForward удаляет проброс порта
|
||||
func RemovePortForward(client *database.Client, port int, protocol string) error {
|
||||
// Находим и удаляем проброс
|
||||
for i, pf := range client.PortForwards {
|
||||
if pf.Port == port && pf.Protocol == protocol {
|
||||
// Удаляем правила iptables
|
||||
if client.Enabled {
|
||||
removePortForwardRules(client, pf)
|
||||
}
|
||||
|
||||
// Удаляем из списка
|
||||
client.PortForwards = append(client.PortForwards[:i], client.PortForwards[i+1:]...)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("проброс порта не найден")
|
||||
}
|
||||
|
||||
// applyPortForwardRules применяет правила iptables для проброса порта
|
||||
func applyPortForwardRules(client *database.Client, pf database.PortForward) error {
|
||||
log.Printf(" 🔀 Применяю проброс порта %d (%s)", pf.Port, pf.Protocol)
|
||||
|
||||
netInterface := database.GetDefaultInterface()
|
||||
|
||||
protocols := []string{pf.Protocol}
|
||||
if pf.Protocol == "both" {
|
||||
protocols = []string{"tcp", "udp"}
|
||||
}
|
||||
|
||||
for _, proto := range protocols {
|
||||
commands := [][]string{
|
||||
// DNAT только для пакетов приходящих с внешнего интерфейса
|
||||
{"iptables", "-t", "nat", "-A", "PREROUTING", "-i", netInterface, "-p", proto,
|
||||
"--dport", fmt.Sprintf("%d", pf.Port), "-j", "DNAT",
|
||||
"--to-destination", fmt.Sprintf("%s:%d", client.Address, pf.Port)},
|
||||
|
||||
// Разрешаем FORWARD для этого порта
|
||||
{"iptables", "-I", "FORWARD", "1", "-p", proto, "-d", client.Address,
|
||||
"--dport", fmt.Sprintf("%d", pf.Port), "-j", "ACCEPT"},
|
||||
}
|
||||
|
||||
for _, cmdArgs := range commands {
|
||||
cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
log.Printf(" ⚠️ Ошибка: %v (output: %s)", err, string(output))
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf(" ✅ Проброс порта настроен")
|
||||
return nil
|
||||
}
|
||||
|
||||
// removePortForwardRules удаляет правила iptables для проброса порта
|
||||
func removePortForwardRules(client *database.Client, pf database.PortForward) error {
|
||||
netInterface := database.GetDefaultInterface()
|
||||
|
||||
protocols := []string{pf.Protocol}
|
||||
if pf.Protocol == "both" {
|
||||
protocols = []string{"tcp", "udp"}
|
||||
}
|
||||
|
||||
for _, proto := range protocols {
|
||||
commands := [][]string{
|
||||
{"iptables", "-t", "nat", "-D", "PREROUTING", "-i", netInterface, "-p", proto,
|
||||
"--dport", fmt.Sprintf("%d", pf.Port), "-j", "DNAT",
|
||||
"--to-destination", fmt.Sprintf("%s:%d", client.Address, pf.Port)},
|
||||
|
||||
{"iptables", "-D", "FORWARD", "-p", proto, "-d", client.Address,
|
||||
"--dport", fmt.Sprintf("%d", pf.Port), "-j", "ACCEPT"},
|
||||
}
|
||||
|
||||
for _, cmdArgs := range commands {
|
||||
cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)
|
||||
cmd.Run() // Игнорируем ошибки при удалении
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ApplyAllPortForwards применяет все пробросы портов для клиента
|
||||
func ApplyAllPortForwards(client *database.Client) error {
|
||||
if !client.Enabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, pf := range client.PortForwards {
|
||||
if err := applyPortForwardRules(client, pf); err != nil {
|
||||
log.Printf(" ⚠️ Ошибка проброса порта %d: %v", pf.Port, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// removeAllPortForwards удаляет все пробросы портов для клиента
|
||||
func removeAllPortForwards(client *database.Client) error {
|
||||
for _, pf := range client.PortForwards {
|
||||
removePortForwardRules(client, pf)
|
||||
}
|
||||
return nil
|
||||
}
|
196
internal/wireguard/server.go
Normal file
196
internal/wireguard/server.go
Normal file
@@ -0,0 +1,196 @@
|
||||
package wireguard
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"wg-panel/internal/database"
|
||||
)
|
||||
|
||||
// UpdateServerConfig обновляет конфиг файл сервера
|
||||
func UpdateServerConfig(server *database.Server, db *database.Database) error {
|
||||
configContent := fmt.Sprintf(`[Interface]
|
||||
PrivateKey = %s
|
||||
Address = %s
|
||||
ListenPort = %d
|
||||
PostUp = %s
|
||||
PostDown = %s
|
||||
`, server.PrivateKey, server.Address, server.ListenPort, server.PostUp, server.PostDown)
|
||||
|
||||
// Добавляем всех клиентов
|
||||
for _, client := range db.Clients {
|
||||
if client.ServerID == server.ID && client.Enabled {
|
||||
configContent += fmt.Sprintf("\n[Peer]\nPublicKey = %s\nAllowedIPs = %s/32\n",
|
||||
client.PublicKey, client.Address)
|
||||
}
|
||||
}
|
||||
|
||||
configPath := fmt.Sprintf("/etc/wireguard/%s.conf", server.Interface)
|
||||
return os.WriteFile(configPath, []byte(configContent), 0600)
|
||||
}
|
||||
|
||||
// CreateServer создает новый WireGuard сервер
|
||||
func CreateServer(db *database.Database, name, address string, port int, dns string) (*database.Server, error) {
|
||||
// Генерируем ключи
|
||||
privateKey, publicKey, err := database.GenerateKeys()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Определяем имя интерфейса
|
||||
interfaceName := fmt.Sprintf("wg%d", len(db.Servers))
|
||||
|
||||
// Получаем основной сетевой интерфейс
|
||||
netInterface := database.GetDefaultInterface()
|
||||
log.Printf("📡 Определен сетевой интерфейс для NAT: %s", netInterface)
|
||||
|
||||
// Создаем сервер
|
||||
server := database.Server{
|
||||
ID: fmt.Sprintf("%d", time.Now().UnixNano()),
|
||||
Name: name,
|
||||
Interface: interfaceName,
|
||||
PrivateKey: privateKey,
|
||||
PublicKey: publicKey,
|
||||
Address: address,
|
||||
ListenPort: port,
|
||||
DNS: dns,
|
||||
Enabled: true, // Запускаем сразу
|
||||
CreatedAt: time.Now(),
|
||||
PostUp: fmt.Sprintf("iptables -I FORWARD 1 -i %%i -j ACCEPT; iptables -I FORWARD 1 -o %%i -j ACCEPT; iptables -t nat -A POSTROUTING -o %s -j MASQUERADE", netInterface),
|
||||
PostDown: fmt.Sprintf("iptables -D FORWARD -i %%i -j ACCEPT; iptables -D FORWARD -o %%i -j ACCEPT; iptables -t nat -D POSTROUTING -o %s -j MASQUERADE", netInterface),
|
||||
NextClientIP: 2,
|
||||
}
|
||||
|
||||
// Включаем IP forwarding
|
||||
log.Println("🔧 Включаю IP forwarding...")
|
||||
if err := database.EnableIPForwarding(); err != nil {
|
||||
log.Println("⚠️ Предупреждение: не удалось включить IP forwarding:", err)
|
||||
} else {
|
||||
log.Println("✅ IP forwarding включен")
|
||||
}
|
||||
|
||||
// Создаем конфиг файл
|
||||
log.Printf("📝 Создаю конфиг %s...", interfaceName)
|
||||
configContent := fmt.Sprintf(`[Interface]
|
||||
PrivateKey = %s
|
||||
Address = %s
|
||||
ListenPort = %d
|
||||
PostUp = %s
|
||||
PostDown = %s
|
||||
`, server.PrivateKey, server.Address, server.ListenPort, server.PostUp, server.PostDown)
|
||||
|
||||
configPath := fmt.Sprintf("/etc/wireguard/%s.conf", interfaceName)
|
||||
if err := os.WriteFile(configPath, []byte(configContent), 0600); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Запускаем интерфейс сразу (так как Enabled = true)
|
||||
log.Printf("🚀 Запускаю интерфейс %s...", interfaceName)
|
||||
cmd := exec.Command("wg-quick", "up", interfaceName)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
log.Printf("❌ Ошибка запуска: %s", string(output))
|
||||
return nil, fmt.Errorf("failed to start interface: %v, output: %s", err, string(output))
|
||||
}
|
||||
log.Printf("✅ Интерфейс %s запущен", interfaceName)
|
||||
|
||||
return &server, nil
|
||||
}
|
||||
|
||||
// ToggleServer включает/выключает сервер
|
||||
func ToggleServer(server *database.Server) error {
|
||||
if server.Enabled {
|
||||
// Выключаем
|
||||
cmd := exec.Command("wg-quick", "down", server.Interface)
|
||||
if err := cmd.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
server.Enabled = false
|
||||
} else {
|
||||
// Включаем IP forwarding перед запуском
|
||||
database.EnableIPForwarding()
|
||||
|
||||
// Включаем
|
||||
cmd := exec.Command("wg-quick", "up", server.Interface)
|
||||
if err := cmd.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
server.Enabled = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteServer удаляет сервер
|
||||
func DeleteServer(server *database.Server) error {
|
||||
// Останавливаем интерфейс
|
||||
if server.Enabled {
|
||||
exec.Command("wg-quick", "down", server.Interface).Run()
|
||||
}
|
||||
|
||||
// Удаляем конфиг файл
|
||||
configPath := fmt.Sprintf("/etc/wireguard/%s.conf", server.Interface)
|
||||
return os.Remove(configPath)
|
||||
}
|
||||
|
||||
// UpdateStats обновляет статистику из WireGuard
|
||||
func UpdateStats(db *database.Database) {
|
||||
for _, server := range db.Servers {
|
||||
if !server.Enabled {
|
||||
continue
|
||||
}
|
||||
|
||||
cmd := exec.Command("wg", "show", server.Interface, "dump")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
lines := strings.Split(string(output), "\n")
|
||||
for _, line := range lines[1:] { // Пропускаем первую строку (заголовок)
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
fields := strings.Split(line, "\t")
|
||||
if len(fields) < 8 {
|
||||
continue
|
||||
}
|
||||
|
||||
pubKey := fields[0]
|
||||
endpoint := fields[2] // IP:Port клиента
|
||||
lastHandshake, _ := strconv.ParseInt(fields[4], 10, 64)
|
||||
rxBytes, _ := strconv.ParseInt(fields[5], 10, 64) // received
|
||||
txBytes, _ := strconv.ParseInt(fields[6], 10, 64) // sent
|
||||
|
||||
// Обновляем статистику клиента
|
||||
for i := range db.Clients {
|
||||
if db.Clients[i].PublicKey == pubKey && db.Clients[i].ServerID == server.ID {
|
||||
db.Clients[i].RxBytes = rxBytes
|
||||
db.Clients[i].TxBytes = txBytes
|
||||
db.Clients[i].Endpoint = endpoint
|
||||
if lastHandshake > 0 {
|
||||
db.Clients[i].LastHandshake = time.Unix(lastHandshake, 0)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
database.SaveDatabase(db)
|
||||
}
|
||||
|
||||
// UpdateStatsLoop обновляет статистику каждые 5 секунд
|
||||
func UpdateStatsLoop(db *database.Database) {
|
||||
ticker := time.NewTicker(5 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for range ticker.C {
|
||||
UpdateStats(db)
|
||||
}
|
||||
}
|
254
internal/wireguard/sync.go
Normal file
254
internal/wireguard/sync.go
Normal file
@@ -0,0 +1,254 @@
|
||||
package wireguard
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"wg-panel/internal/database"
|
||||
)
|
||||
|
||||
// SyncWireGuardWithDatabase синхронизирует WireGuard с базой данных
|
||||
// База данных - единственный источник истины
|
||||
func SyncWireGuardWithDatabase(db *database.Database) error {
|
||||
log.Println("🔄 Синхронизация WireGuard с базой данных...")
|
||||
log.Println("📋 База данных - единственный источник истины")
|
||||
|
||||
// Получаем список всех интерфейсов WireGuard
|
||||
cmd := exec.Command("wg", "show", "interfaces")
|
||||
output, err := cmd.Output()
|
||||
|
||||
var activeInterfaces []string
|
||||
if err == nil && len(output) > 0 {
|
||||
activeInterfaces = strings.Fields(strings.TrimSpace(string(output)))
|
||||
}
|
||||
|
||||
// Создаем карту интерфейсов из БД
|
||||
dbInterfaces := make(map[string]*database.Server)
|
||||
for i := range db.Servers {
|
||||
dbInterfaces[db.Servers[i].Interface] = &db.Servers[i]
|
||||
}
|
||||
|
||||
// Удаляем все интерфейсы которых НЕТ в БД
|
||||
for _, iface := range activeInterfaces {
|
||||
if _, exists := dbInterfaces[iface]; !exists {
|
||||
log.Printf(" ❌ Интерфейс %s не найден в БД - удаление...", iface)
|
||||
if err := removeInterface(iface); err != nil {
|
||||
log.Printf(" ⚠️ Ошибка удаления интерфейса: %v", err)
|
||||
} else {
|
||||
log.Printf(" ✅ Интерфейс %s удален", iface)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Также удаляем конфиг файлы которых нет в БД (но интерфейс не запущен)
|
||||
cmd = exec.Command("sh", "-c", "ls /etc/wireguard/*.conf 2>/dev/null | xargs -n1 basename | sed 's/.conf$//'")
|
||||
output, err = cmd.Output()
|
||||
if err == nil && len(output) > 0 {
|
||||
configInterfaces := strings.Fields(strings.TrimSpace(string(output)))
|
||||
for _, iface := range configInterfaces {
|
||||
if _, exists := dbInterfaces[iface]; !exists {
|
||||
configPath := fmt.Sprintf("/etc/wireguard/%s.conf", iface)
|
||||
log.Printf(" 🗑️ Удаление конфига %s (не в БД)...", configPath)
|
||||
if err := os.Remove(configPath); err != nil {
|
||||
log.Printf(" ⚠️ Ошибка удаления конфига: %v", err)
|
||||
} else {
|
||||
log.Printf(" ✅ Конфиг удален")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Синхронизируем каждый сервер из БД
|
||||
for i := range db.Servers {
|
||||
server := &db.Servers[i]
|
||||
log.Printf(" 🔧 Синхронизация сервера %s (%s)...", server.Name, server.Interface)
|
||||
|
||||
// Проверяем запущен ли интерфейс
|
||||
isRunning := false
|
||||
for _, iface := range activeInterfaces {
|
||||
if iface == server.Interface {
|
||||
isRunning = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if server.Enabled {
|
||||
// Сервер должен быть запущен
|
||||
if isRunning {
|
||||
// Если уже запущен - перезапускаем (чтобы применить правила iptables после очистки)
|
||||
log.Printf(" 🔄 Интерфейс уже запущен, перезапускаю для применения правил...")
|
||||
if err := stopInterface(server.Interface); err != nil {
|
||||
log.Printf(" ⚠️ Ошибка остановки: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf(" 🚀 Запуск интерфейса %s...", server.Interface)
|
||||
if err := startInterface(server, db); err != nil {
|
||||
log.Printf(" ❌ Ошибка запуска: %v", err)
|
||||
server.Enabled = false
|
||||
continue
|
||||
}
|
||||
|
||||
// Синхронизируем peers
|
||||
log.Printf(" 🧹 Очистка всех peers...")
|
||||
if err := clearAllPeers(server.Interface); err != nil {
|
||||
log.Printf(" ⚠️ Ошибка очистки: %v", err)
|
||||
}
|
||||
|
||||
log.Printf(" 📤 Загрузка peers из БД...")
|
||||
loadedCount := 0
|
||||
for j := range db.Clients {
|
||||
client := &db.Clients[j]
|
||||
if client.ServerID == server.ID && client.Enabled {
|
||||
if err := addPeerToWireGuard(server, *client); err != nil {
|
||||
log.Printf(" ⚠️ Ошибка добавления %s: %v", client.Name, err)
|
||||
} else {
|
||||
loadedCount++
|
||||
// Применяем пробросы портов
|
||||
if len(client.PortForwards) > 0 {
|
||||
log.Printf(" 🔀 Применяю %d пробросов портов для %s...", len(client.PortForwards), client.Name)
|
||||
ApplyAllPortForwards(client)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Printf(" ✅ Загружено %d peers", loadedCount)
|
||||
|
||||
} else {
|
||||
// Сервер должен быть остановлен
|
||||
if isRunning {
|
||||
log.Printf(" 🛑 Остановка интерфейса %s...", server.Interface)
|
||||
if err := stopInterface(server.Interface); err != nil {
|
||||
log.Printf(" ⚠️ Ошибка остановки: %v", err)
|
||||
} else {
|
||||
log.Printf(" ✅ Интерфейс остановлен")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
database.SaveDatabase(db)
|
||||
log.Println("✅ Синхронизация завершена")
|
||||
return nil
|
||||
}
|
||||
|
||||
// removeInterface удаляет интерфейс WireGuard
|
||||
func removeInterface(iface string) error {
|
||||
// Останавливаем интерфейс
|
||||
cmd := exec.Command("wg-quick", "down", iface)
|
||||
cmd.Run() // Игнорируем ошибку если уже остановлен
|
||||
|
||||
// Удаляем конфиг файл
|
||||
configPath := fmt.Sprintf("/etc/wireguard/%s.conf", iface)
|
||||
return os.Remove(configPath)
|
||||
}
|
||||
|
||||
// stopInterface останавливает интерфейс WireGuard
|
||||
func stopInterface(iface string) error {
|
||||
cmd := exec.Command("wg-quick", "down", iface)
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
// startInterface запускает интерфейс WireGuard
|
||||
func startInterface(server *database.Server, db *database.Database) error {
|
||||
log.Printf(" 📝 Обновляю конфиг файл...")
|
||||
// Убеждаемся что конфиг файл существует
|
||||
if err := UpdateServerConfig(server, db); err != nil {
|
||||
return fmt.Errorf("ошибка создания конфига: %v", err)
|
||||
}
|
||||
|
||||
log.Printf(" 🔧 Включаю IP forwarding...")
|
||||
// Включаем IP forwarding
|
||||
if err := database.EnableIPForwarding(); err != nil {
|
||||
log.Printf(" ⚠️ Ошибка IP forwarding: %v", err)
|
||||
}
|
||||
|
||||
log.Printf(" 🚀 Запускаю wg-quick up %s...", server.Interface)
|
||||
// Запускаем интерфейс
|
||||
cmd := exec.Command("wg-quick", "up", server.Interface)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
log.Printf(" ❌ Вывод wg-quick: %s", string(output))
|
||||
return fmt.Errorf("wg-quick up failed: %v, output: %s", err, string(output))
|
||||
}
|
||||
log.Printf(" ✅ Интерфейс запущен успешно")
|
||||
|
||||
// Применяем правила iptables вручную (не полагаемся на PostUp)
|
||||
if err := ApplyWireGuardIPTablesRules(server); err != nil {
|
||||
log.Printf(" ⚠️ Ошибка применения правил: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// clearAllPeers очищает все peers из интерфейса WireGuard
|
||||
func clearAllPeers(iface string) error {
|
||||
// Получаем список всех peers
|
||||
cmd := exec.Command("wg", "show", iface, "peers")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
peers := strings.Split(strings.TrimSpace(string(output)), "\n")
|
||||
removedCount := 0
|
||||
|
||||
for _, peer := range peers {
|
||||
peer = strings.TrimSpace(peer)
|
||||
if peer == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Удаляем peer
|
||||
cmd := exec.Command("wg", "set", iface, "peer", peer, "remove")
|
||||
if err := cmd.Run(); err != nil {
|
||||
log.Printf(" ⚠️ Не удалось удалить peer %s: %v", peer[:16]+"...", err)
|
||||
} else {
|
||||
removedCount++
|
||||
}
|
||||
}
|
||||
|
||||
if removedCount > 0 {
|
||||
log.Printf(" 🗑️ Удалено %d peers", removedCount)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateNextClientIP обновляет счетчик следующего IP для клиентов
|
||||
func updateNextClientIP(server *database.Server, db *database.Database) {
|
||||
// Парсим адрес сервера
|
||||
parts := strings.Split(server.Address, "/")
|
||||
if len(parts) != 2 {
|
||||
server.NextClientIP = 2
|
||||
return
|
||||
}
|
||||
|
||||
ipParts := strings.Split(parts[0], ".")
|
||||
if len(ipParts) != 4 {
|
||||
server.NextClientIP = 2
|
||||
return
|
||||
}
|
||||
|
||||
// Находим максимальный IP среди клиентов этого сервера
|
||||
maxIP := 1
|
||||
for _, client := range db.Clients {
|
||||
if client.ServerID != server.ID {
|
||||
continue
|
||||
}
|
||||
|
||||
clientIPParts := strings.Split(client.Address, ".")
|
||||
if len(clientIPParts) == 4 {
|
||||
lastOctet, err := strconv.Atoi(clientIPParts[3])
|
||||
if err == nil && lastOctet > maxIP {
|
||||
maxIP = lastOctet
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
server.NextClientIP = maxIP + 1
|
||||
}
|
76
internal/wireguard/wg_iptables.go
Normal file
76
internal/wireguard/wg_iptables.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package wireguard
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os/exec"
|
||||
|
||||
"wg-panel/internal/database"
|
||||
)
|
||||
|
||||
// ApplyWireGuardIPTablesRules применяет правила iptables для WireGuard сервера
|
||||
func ApplyWireGuardIPTablesRules(server *database.Server) error {
|
||||
netInterface := database.GetDefaultInterface()
|
||||
iface := server.Interface
|
||||
network := getNetworkFromAddress(server.Address)
|
||||
|
||||
log.Printf(" 🔧 Применяю правила iptables для %s (сеть: %s, интерфейс: %s)...", iface, network, netInterface)
|
||||
|
||||
// Правила FORWARD
|
||||
commands := [][]string{
|
||||
{"iptables", "-I", "FORWARD", "1", "-i", iface, "-j", "ACCEPT"},
|
||||
{"iptables", "-I", "FORWARD", "1", "-o", iface, "-j", "ACCEPT"},
|
||||
{"iptables", "-t", "nat", "-A", "POSTROUTING", "-s", network, "-o", netInterface, "-j", "MASQUERADE"},
|
||||
}
|
||||
|
||||
for i, cmdArgs := range commands {
|
||||
cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
log.Printf(" ⚠️ Команда #%d %v: %v (output: %s)", i+1, cmdArgs, err, string(output))
|
||||
} else {
|
||||
log.Printf(" ✅ Команда #%d выполнена: %v", i+1, cmdArgs)
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf(" ✅ Правила iptables применены")
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveWireGuardIPTablesRules удаляет правила iptables для WireGuard сервера
|
||||
func RemoveWireGuardIPTablesRules(server *database.Server) error {
|
||||
netInterface := database.GetDefaultInterface()
|
||||
iface := server.Interface
|
||||
|
||||
log.Printf(" 🗑️ Удаляю правила iptables для %s...", iface)
|
||||
|
||||
commands := [][]string{
|
||||
{"iptables", "-D", "FORWARD", "-i", iface, "-j", "ACCEPT"},
|
||||
{"iptables", "-D", "FORWARD", "-o", iface, "-j", "ACCEPT"},
|
||||
{"iptables", "-t", "nat", "-D", "POSTROUTING", "-s", getNetworkFromAddress(server.Address), "-o", netInterface, "-j", "MASQUERADE"},
|
||||
}
|
||||
|
||||
for _, cmdArgs := range commands {
|
||||
cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)
|
||||
cmd.Run() // Игнорируем ошибки
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getNetworkFromAddress извлекает подсеть из адреса типа "10.0.0.1/24" -> "10.0.0.0/24"
|
||||
func getNetworkFromAddress(address string) string {
|
||||
// Простая реализация - заменяем последний октет на 0
|
||||
parts := address[:len(address)-1] // убираем последнюю цифру
|
||||
// Находим последнюю точку
|
||||
lastDot := -1
|
||||
for i := len(parts) - 1; i >= 0; i-- {
|
||||
if parts[i] == '.' {
|
||||
lastDot = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if lastDot > 0 {
|
||||
return parts[:lastDot+1] + "0" + address[len(address)-3:] // +0 и маска
|
||||
}
|
||||
return address
|
||||
}
|
Reference in New Issue
Block a user