package vpn import ( "bufio" "encoding/json" "fmt" "net" "os" "os/exec" "path/filepath" "sort" "strconv" "strings" "sync" "syscall" "time" ) // ======================== PPP Profiles (/ppp profile) ======================== type PPPProfile struct { ID string `json:"id"` Name string `json:"name"` // e.g. "default", "default-encryption", "pool-lan" LocalAddress string `json:"local_address"` // e.g. "10.10.10.1" (server IP on tunnel) RemoteAddress string `json:"remote_address"` // e.g. "10.10.10.10-10.10.10.50" or "10.10.10.10" DNSServers string `json:"dns_servers"` // e.g. "1.1.1.1,8.8.8.8" UseEncryption string `json:"use_encryption"` // "no", "yes", "required" (MPPE-128) OnlyOne bool `json:"only_one"` // One session per user Comment string `json:"comment,omitempty"` } // ======================== PPP Secrets (/ppp secret) ======================== type PPPSecret struct { ID string `json:"id"` Name string `json:"name"` // Username Password string `json:"password"` // Password Service string `json:"service"` // "any", "pppoe", "pptp", "l2tp" Profile string `json:"profile"` // Profile name LocalAddress string `json:"local_address,omitempty"` RemoteAddress string `json:"remote_address,omitempty"` CallerID string `json:"caller_id,omitempty"` Comment string `json:"comment,omitempty"` Disabled bool `json:"disabled"` } // ======================== PPPoE Server (/interface pppoe-server server) ======================== type PPPoEServerConfig struct { Enabled bool `json:"enabled"` ServiceName string `json:"service_name"` // e.g. "isp-service" Interface string `json:"interface"` // e.g. "eth0", "eth0.10", "br0" DefaultProfile string `json:"default_profile"` OneSessionPerHost bool `json:"one_session_per_host"` MaxMTU int `json:"max_mtu"` // default 1480 / 1492 MaxMRU int `json:"max_mru"` // default 1480 / 1492 Running bool `json:"running"` ActiveSessions int `json:"active_sessions"` LastError string `json:"last_error,omitempty"` } // ======================== PPTP Server (/interface pptp-server server) ======================== type PPTPServerConfig struct { Enabled bool `json:"enabled"` Port int `json:"port"` // default 1723 DefaultProfile string `json:"default_profile"` RequireMPPE bool `json:"require_mppe"` // 128-bit MPPE Running bool `json:"running"` ActiveSessions int `json:"active_sessions"` LastError string `json:"last_error,omitempty"` } // ======================== L2TP Server (/interface l2tp-server server) ======================== type L2TPServerConfig struct { Enabled bool `json:"enabled"` Port int `json:"port"` // default 1701 DefaultProfile string `json:"default_profile"` UseIPSec string `json:"use_ipsec"` // "no", "yes", "required" IPSecSecret string `json:"ipsec_secret,omitempty"` Running bool `json:"running"` ActiveSessions int `json:"active_sessions"` LastError string `json:"last_error,omitempty"` } // ======================== IPSec Roadwarrior Server ======================== type IPSecServerConfig struct { Enabled bool `json:"enabled"` PoolAddress string `json:"pool_address"` // e.g. "10.10.30.0/24" PresharedKey string `json:"preshared_key"` LocalSubnet string `json:"local_subnet"` // e.g. "0.0.0.0/0" DNSServers string `json:"dns_servers"` // e.g. "1.1.1.1,8.8.8.8" Running bool `json:"running"` ActiveSessions int `json:"active_sessions"` LastError string `json:"last_error,omitempty"` } // ======================== Active PPP Session (/ppp active) ======================== type ActivePPPSession struct { ID string `json:"id"` Interface string `json:"interface"` // e.g. "ppp0" Username string `json:"username"` Service string `json:"service"` // "pppoe", "pptp", "l2tp" CallerID string `json:"caller_id,omitempty"` Address string `json:"address"` // client assigned IP Uptime string `json:"uptime"` RxBytes uint64 `json:"rx_bytes"` TxBytes uint64 `json:"tx_bytes"` RxHuman string `json:"rx_human,omitempty"` TxHuman string `json:"tx_human,omitempty"` } // ======================== Central PPP & Server Manager ======================== type PPPServerManager struct { mu sync.RWMutex baseDir string profiles map[string]*PPPProfile secrets map[string]*PPPSecret pppoeServer PPPoEServerConfig pptpServer PPTPServerConfig l2tpServer L2TPServerConfig ipsecServer IPSecServerConfig pppoePIDFile string } func NewPPPServerManager() *PPPServerManager { mgr := &PPPServerManager{ baseDir: "/etc/debtik/vpn", profiles: make(map[string]*PPPProfile), secrets: make(map[string]*PPPSecret), pppoePIDFile: "/var/run/debtik-pppoe-server.pid", pppoeServer: PPPoEServerConfig{ Enabled: false, ServiceName: "debtik-pppoe", Interface: "eth0", DefaultProfile: "default", MaxMTU: 1480, MaxMRU: 1480, }, pptpServer: PPTPServerConfig{ Enabled: false, Port: 1723, DefaultProfile: "default-encryption", RequireMPPE: true, }, l2tpServer: L2TPServerConfig{ Enabled: false, Port: 1701, DefaultProfile: "default", UseIPSec: "no", }, ipsecServer: IPSecServerConfig{ Enabled: false, PoolAddress: "10.10.30.0/24", PresharedKey: "DebTikSecret123", LocalSubnet: "0.0.0.0/0", DNSServers: "1.1.1.1,8.8.8.8", }, } mgr.initDefaults() mgr.loadAll() mgr.SyncSecretsToSystem() mgr.applyAll() return mgr } func (m *PPPServerManager) initDefaults() { m.profiles["default"] = &PPPProfile{ ID: "default", Name: "default", LocalAddress: "10.10.10.1", RemoteAddress: "10.10.10.10-10.10.10.50", DNSServers: "1.1.1.1,8.8.8.8", UseEncryption: "no", OnlyOne: true, Comment: "Default PPP profile for unencrypted/standard tunnels", } m.profiles["default-encryption"] = &PPPProfile{ ID: "default-encryption", Name: "default-encryption", LocalAddress: "10.10.20.1", RemoteAddress: "10.10.20.10-10.10.20.50", DNSServers: "1.1.1.1,8.8.8.8", UseEncryption: "required", OnlyOne: true, Comment: "Default PPP profile with required MPPE-128 encryption", } } func (m *PPPServerManager) loadAll() { _ = os.MkdirAll(m.baseDir, 0755) // Profiles if data, err := os.ReadFile(filepath.Join(m.baseDir, "ppp_profiles.json")); err == nil { var list []*PPPProfile if err := json.Unmarshal(data, &list); err == nil { for _, p := range list { m.profiles[p.Name] = p } } } // Secrets if data, err := os.ReadFile(filepath.Join(m.baseDir, "ppp_secrets.json")); err == nil { var list []*PPPSecret if err := json.Unmarshal(data, &list); err == nil { for _, s := range list { m.secrets[s.ID] = s } } } // PPPoE Server if data, err := os.ReadFile(filepath.Join(m.baseDir, "pppoe_server.json")); err == nil { _ = json.Unmarshal(data, &m.pppoeServer) } // PPTP Server if data, err := os.ReadFile(filepath.Join(m.baseDir, "pptp_server.json")); err == nil { _ = json.Unmarshal(data, &m.pptpServer) } // L2TP Server if data, err := os.ReadFile(filepath.Join(m.baseDir, "l2tp_server.json")); err == nil { _ = json.Unmarshal(data, &m.l2tpServer) } // IPSec Server if data, err := os.ReadFile(filepath.Join(m.baseDir, "ipsec_server.json")); err == nil { _ = json.Unmarshal(data, &m.ipsecServer) } } func (m *PPPServerManager) save(filename string, v any) error { _ = os.MkdirAll(m.baseDir, 0755) data, err := json.MarshalIndent(v, "", " ") if err != nil { return err } return os.WriteFile(filepath.Join(m.baseDir, filename), data, 0644) } // ======================== PPP Profiles CRUD ======================== func (m *PPPServerManager) ListProfiles() []*PPPProfile { m.mu.RLock() defer m.mu.RUnlock() list := make([]*PPPProfile, 0) for _, p := range m.profiles { list = append(list, p) } sort.Slice(list, func(i, j int) bool { return list[i].Name < list[j].Name }) return list } func (m *PPPServerManager) GetProfile(name string) *PPPProfile { m.mu.RLock() defer m.mu.RUnlock() return m.profiles[name] } func (m *PPPServerManager) SaveProfile(p *PPPProfile) error { m.mu.Lock() defer m.mu.Unlock() if p.Name == "" { return fmt.Errorf("profile name is required") } if p.LocalAddress == "" { p.LocalAddress = "10.10.10.1" } if p.RemoteAddress == "" { p.RemoteAddress = "10.10.10.10-10.10.10.50" } if p.ID == "" { p.ID = p.Name } m.profiles[p.Name] = p var list []*PPPProfile for _, pr := range m.profiles { list = append(list, pr) } return m.save("ppp_profiles.json", list) } func (m *PPPServerManager) DeleteProfile(name string) error { m.mu.Lock() defer m.mu.Unlock() if name == "default" || name == "default-encryption" { return fmt.Errorf("cannot delete default profile") } delete(m.profiles, name) var list []*PPPProfile for _, pr := range m.profiles { list = append(list, pr) } return m.save("ppp_profiles.json", list) } // ======================== PPP Secrets CRUD ======================== func (m *PPPServerManager) ListSecrets() []*PPPSecret { m.mu.RLock() defer m.mu.RUnlock() list := make([]*PPPSecret, 0) for _, s := range m.secrets { list = append(list, s) } sort.Slice(list, func(i, j int) bool { return list[i].Name < list[j].Name }) return list } func (m *PPPServerManager) SaveSecret(s *PPPSecret) error { m.mu.Lock() defer m.mu.Unlock() if s.Name == "" || s.Password == "" { return fmt.Errorf("username and password are required") } if s.Service == "" { s.Service = "any" } if s.Profile == "" { s.Profile = "default" } if s.ID == "" { s.ID = fmt.Sprintf("secret-%d", time.Now().UnixNano()) } m.secrets[s.ID] = s var list []*PPPSecret for _, sc := range m.secrets { list = append(list, sc) } if err := m.save("ppp_secrets.json", list); err != nil { return err } return m.SyncSecretsToSystem() } func (m *PPPServerManager) SetSecretState(id string, disabled bool) error { m.mu.Lock() defer m.mu.Unlock() s, ok := m.secrets[id] if !ok { return fmt.Errorf("secret not found") } s.Disabled = disabled var list []*PPPSecret for _, sc := range m.secrets { list = append(list, sc) } _ = m.save("ppp_secrets.json", list) return m.SyncSecretsToSystem() } func (m *PPPServerManager) DeleteSecret(id string) error { m.mu.Lock() defer m.mu.Unlock() delete(m.secrets, id) var list []*PPPSecret for _, sc := range m.secrets { list = append(list, sc) } _ = m.save("ppp_secrets.json", list) return m.SyncSecretsToSystem() } // SyncSecretsToSystem synchronizes all enabled secrets into /etc/ppp/chap-secrets and /etc/ppp/pap-secrets func (m *PPPServerManager) SyncSecretsToSystem() error { var lines []string lines = append(lines, "# Autogenerated by DebTik - DO NOT EDIT MANUALLY") lines = append(lines, "# client\tserver\tsecret\tIP addresses") for _, s := range m.secrets { if s.Disabled { continue } serviceName := "*" if s.Service != "any" && s.Service != "" { serviceName = s.Service } ipAddr := "*" if s.RemoteAddress != "" { ipAddr = s.RemoteAddress } lines = append(lines, fmt.Sprintf("\"%s\"\t%s\t\"%s\"\t%s", s.Name, serviceName, s.Password, ipAddr)) } content := strings.Join(lines, "\n") + "\n" _ = os.WriteFile("/etc/ppp/chap-secrets", []byte(content), 0600) _ = os.WriteFile("/etc/ppp/pap-secrets", []byte(content), 0600) return nil } // ======================== PPPoE Server Management ======================== func (m *PPPServerManager) GetPPPoEServer() PPPoEServerConfig { m.mu.Lock() defer m.mu.Unlock() // Check if running m.pppoeServer.Running = m.isProcessRunning(m.pppoePIDFile) m.pppoeServer.ActiveSessions = m.countPPPSessions("pppoe") return m.pppoeServer } func (m *PPPServerManager) SetPPPoEServer(cfg PPPoEServerConfig) error { m.mu.Lock() defer m.mu.Unlock() m.pppoeServer.Enabled = cfg.Enabled if cfg.ServiceName != "" { m.pppoeServer.ServiceName = cfg.ServiceName } if cfg.Interface != "" { m.pppoeServer.Interface = cfg.Interface } if cfg.DefaultProfile != "" { m.pppoeServer.DefaultProfile = cfg.DefaultProfile } m.pppoeServer.OneSessionPerHost = cfg.OneSessionPerHost if cfg.MaxMTU > 0 { m.pppoeServer.MaxMTU = cfg.MaxMTU } if cfg.MaxMRU > 0 { m.pppoeServer.MaxMRU = cfg.MaxMRU } _ = m.save("pppoe_server.json", m.pppoeServer) return m.applyPPPoEServer() } func (m *PPPServerManager) applyPPPoEServer() error { m.stopPPPoEServer() if !m.pppoeServer.Enabled { m.pppoeServer.Running = false m.pppoeServer.LastError = "" return nil } // Prepare pppoe-server-options profile := m.profiles[m.pppoeServer.DefaultProfile] if profile == nil { profile = m.profiles["default"] } localIP := "10.10.10.1" if profile != nil && profile.LocalAddress != "" { localIP = profile.LocalAddress } startIP := "10.10.10.10" poolCount := 50 if profile != nil && profile.RemoteAddress != "" { if strings.Contains(profile.RemoteAddress, "-") { parts := strings.Split(profile.RemoteAddress, "-") startIP = strings.TrimSpace(parts[0]) } else { startIP = profile.RemoteAddress } } // Write /etc/ppp/pppoe-server-options dns1 := "1.1.1.1" dns2 := "8.8.8.8" if profile != nil && profile.DNSServers != "" { parts := strings.Split(profile.DNSServers, ",") if len(parts) > 0 { dns1 = strings.TrimSpace(parts[0]) } if len(parts) > 1 { dns2 = strings.TrimSpace(parts[1]) } } opts := fmt.Sprintf(`auth require-chap login lcp-echo-interval 10 lcp-echo-failure 2 ms-dns %s ms-dns %s netmask 255.255.255.0 default-mru default-asyncmap proxyarp `, dns1, dns2) _ = os.WriteFile("/etc/ppp/pppoe-server-options", []byte(opts), 0644) // Launch pppoe-server // pppoe-server -I -L -R -N -S -O /etc/ppp/pppoe-server-options -X args := []string{ "-I", m.pppoeServer.Interface, "-L", localIP, "-R", startIP, "-N", strconv.Itoa(poolCount), "-S", m.pppoeServer.ServiceName, "-O", "/etc/ppp/pppoe-server-options", "-X", m.pppoePIDFile, } if m.pppoeServer.OneSessionPerHost { args = append(args, "-x", "1") } cmd := exec.Command("/usr/sbin/pppoe-server", args...) if out, err := cmd.CombinedOutput(); err != nil { m.pppoeServer.LastError = fmt.Sprintf("start failed: %s - %v", strings.TrimSpace(string(out)), err) m.pppoeServer.Running = false return fmt.Errorf("%s", m.pppoeServer.LastError) } time.Sleep(300 * time.Millisecond) m.pppoeServer.Running = m.isProcessRunning(m.pppoePIDFile) m.pppoeServer.LastError = "" return nil } func (m *PPPServerManager) stopPPPoEServer() { if data, err := os.ReadFile(m.pppoePIDFile); err == nil { pid, _ := strconv.Atoi(strings.TrimSpace(string(data))) if pid > 0 { _ = syscall.Kill(pid, syscall.SIGTERM) time.Sleep(200 * time.Millisecond) _ = syscall.Kill(pid, syscall.SIGKILL) } _ = os.Remove(m.pppoePIDFile) } _ = exec.Command("pkill", "-f", "/usr/sbin/pppoe-server").Run() } // ======================== PPTP Server Management ======================== func (m *PPPServerManager) GetPPTPServer() PPTPServerConfig { m.mu.Lock() defer m.mu.Unlock() out, _ := exec.Command("systemctl", "is-active", "pptpd").Output() m.pptpServer.Running = strings.TrimSpace(string(out)) == "active" m.pptpServer.ActiveSessions = m.countPPPSessions("pptp") return m.pptpServer } func (m *PPPServerManager) SetPPTPServer(cfg PPTPServerConfig) error { m.mu.Lock() defer m.mu.Unlock() m.pptpServer.Enabled = cfg.Enabled if cfg.Port > 0 { m.pptpServer.Port = cfg.Port } if cfg.DefaultProfile != "" { m.pptpServer.DefaultProfile = cfg.DefaultProfile } m.pptpServer.RequireMPPE = cfg.RequireMPPE _ = m.save("pptp_server.json", m.pptpServer) return m.applyPPTPServer() } func (m *PPPServerManager) applyPPTPServer() error { if !m.pptpServer.Enabled { _ = exec.Command("systemctl", "stop", "pptpd").Run() m.pptpServer.Running = false m.pptpServer.LastError = "" return nil } profile := m.profiles[m.pptpServer.DefaultProfile] if profile == nil { profile = m.profiles["default-encryption"] } localIP := "10.10.20.1" if profile != nil && profile.LocalAddress != "" { localIP = profile.LocalAddress } remotePool := "10.10.20.10-50" if profile != nil && profile.RemoteAddress != "" { remotePool = profile.RemoteAddress } // Write /etc/pptpd.conf pptpdConf := fmt.Sprintf(`option /etc/ppp/pptpd-options logwtmp localip %s remoteip %s `, localIP, remotePool) _ = os.WriteFile("/etc/pptpd.conf", []byte(pptpdConf), 0644) // Write /etc/ppp/pptpd-options dns1 := "1.1.1.1" dns2 := "8.8.8.8" if profile != nil && profile.DNSServers != "" { parts := strings.Split(profile.DNSServers, ",") if len(parts) > 0 { dns1 = strings.TrimSpace(parts[0]) } if len(parts) > 1 { dns2 = strings.TrimSpace(parts[1]) } } mppeRule := "require-mppe-128" if !m.pptpServer.RequireMPPE { mppeRule = "# require-mppe-128" } pptpdOpts := fmt.Sprintf(`name pptpd refuse-pap refuse-chap refuse-mschap require-mschap-v2 %s ms-dns %s ms-dns %s proxyarp nodefaultroute lock nobsdcomp novj novjccomp nologfd `, mppeRule, dns1, dns2) _ = os.WriteFile("/etc/ppp/pptpd-options", []byte(pptpdOpts), 0644) _ = exec.Command("systemctl", "restart", "pptpd").Run() time.Sleep(300 * time.Millisecond) out, _ := exec.Command("systemctl", "is-active", "pptpd").Output() m.pptpServer.Running = strings.TrimSpace(string(out)) == "active" m.pptpServer.LastError = "" return nil } // ======================== L2TP Server Management ======================== func (m *PPPServerManager) GetL2TPServer() L2TPServerConfig { m.mu.Lock() defer m.mu.Unlock() out, _ := exec.Command("systemctl", "is-active", "xl2tpd").Output() m.l2tpServer.Running = strings.TrimSpace(string(out)) == "active" m.l2tpServer.ActiveSessions = m.countPPPSessions("l2tp") return m.l2tpServer } func (m *PPPServerManager) SetL2TPServer(cfg L2TPServerConfig) error { m.mu.Lock() defer m.mu.Unlock() m.l2tpServer.Enabled = cfg.Enabled if cfg.Port > 0 { m.l2tpServer.Port = cfg.Port } if cfg.DefaultProfile != "" { m.l2tpServer.DefaultProfile = cfg.DefaultProfile } if cfg.UseIPSec != "" { m.l2tpServer.UseIPSec = cfg.UseIPSec } m.l2tpServer.IPSecSecret = cfg.IPSecSecret _ = m.save("l2tp_server.json", m.l2tpServer) return m.applyL2TPServer() } func (m *PPPServerManager) applyL2TPServer() error { if !m.l2tpServer.Enabled { // When disabled, don't stop xl2tpd completely as client may need it, just reload without LNS _ = os.WriteFile("/etc/ppp/options.xl2tpd.server", []byte("# disabled\n"), 0644) _ = exec.Command("systemctl", "reload-or-restart", "xl2tpd").Run() m.l2tpServer.Running = false m.l2tpServer.LastError = "" return nil } profile := m.profiles[m.l2tpServer.DefaultProfile] if profile == nil { profile = m.profiles["default"] } localIP := "10.10.40.1" if profile != nil && profile.LocalAddress != "" { localIP = profile.LocalAddress } remotePool := "10.10.40.10-10.10.40.50" if profile != nil && profile.RemoteAddress != "" { remotePool = profile.RemoteAddress } // Write /etc/xl2tpd/xl2tpd.conf with [lns default] _ = os.MkdirAll("/etc/xl2tpd", 0755) xl2tpdConf := fmt.Sprintf(`[global] port = %d [lns default] ip range = %s local ip = %s require chap = yes refuse pap = yes require authentication = yes name = l2tpd pppoptfile = /etc/ppp/options.xl2tpd.server length bit = yes `, m.l2tpServer.Port, remotePool, localIP) _ = os.WriteFile("/etc/xl2tpd/xl2tpd.conf", []byte(xl2tpdConf), 0644) // Write /etc/ppp/options.xl2tpd.server dns1 := "1.1.1.1" dns2 := "8.8.8.8" if profile != nil && profile.DNSServers != "" { parts := strings.Split(profile.DNSServers, ",") if len(parts) > 0 { dns1 = strings.TrimSpace(parts[0]) } if len(parts) > 1 { dns2 = strings.TrimSpace(parts[1]) } } opts := fmt.Sprintf(`require-mschap-v2 ms-dns %s ms-dns %s auth mtu 1400 mru 1400 proxyarp nodefaultroute `, dns1, dns2) _ = os.WriteFile("/etc/ppp/options.xl2tpd.server", []byte(opts), 0644) _ = exec.Command("systemctl", "restart", "xl2tpd").Run() time.Sleep(300 * time.Millisecond) out, _ := exec.Command("systemctl", "is-active", "xl2tpd").Output() m.l2tpServer.Running = strings.TrimSpace(string(out)) == "active" m.l2tpServer.LastError = "" return nil } // ======================== IPSec Server Management ======================== func (m *PPPServerManager) GetIPSecServer() IPSecServerConfig { m.mu.Lock() defer m.mu.Unlock() out, _ := exec.Command("swanctl", "--list-conns").Output() m.ipsecServer.Running = strings.Contains(string(out), "rw-server") m.ipsecServer.ActiveSessions = m.countIPSecSessions() return m.ipsecServer } func (m *PPPServerManager) SetIPSecServer(cfg IPSecServerConfig) error { m.mu.Lock() defer m.mu.Unlock() m.ipsecServer.Enabled = cfg.Enabled if cfg.PoolAddress != "" { m.ipsecServer.PoolAddress = cfg.PoolAddress } if cfg.PresharedKey != "" { m.ipsecServer.PresharedKey = cfg.PresharedKey } if cfg.LocalSubnet != "" { m.ipsecServer.LocalSubnet = cfg.LocalSubnet } if cfg.DNSServers != "" { m.ipsecServer.DNSServers = cfg.DNSServers } _ = m.save("ipsec_server.json", m.ipsecServer) return m.applyIPSecServer() } func (m *PPPServerManager) applyIPSecServer() error { confFile := "/etc/swanctl/conf.d/rw-server.conf" if !m.ipsecServer.Enabled { _ = os.Remove(confFile) _ = exec.Command("swanctl", "--load-all").Run() m.ipsecServer.Running = false m.ipsecServer.LastError = "" return nil } _ = os.MkdirAll("/etc/swanctl/conf.d", 0755) swanConf := fmt.Sprintf(`connections { rw-server { local_addrs = %%any pools = rw-pool local { auth = psk id = %%any } remote { auth = psk } children { rw-net { local_ts = %s esp_proposals = aes256gcm128-sha256-modp2048,aes256-sha256-modp2048 } } } } pools { rw-pool { addrs = %s dns = %s } } secrets { ike-rw { secret = "%s" } } `, m.ipsecServer.LocalSubnet, m.ipsecServer.PoolAddress, m.ipsecServer.DNSServers, m.ipsecServer.PresharedKey) if err := os.WriteFile(confFile, []byte(swanConf), 0600); err != nil { m.ipsecServer.LastError = err.Error() return err } if out, err := exec.Command("swanctl", "--load-all").CombinedOutput(); err != nil { m.ipsecServer.LastError = strings.TrimSpace(string(out)) return err } m.ipsecServer.Running = true m.ipsecServer.LastError = "" return nil } // ======================== Active Connections & Kill ======================== func (m *PPPServerManager) ListActiveSessions() []ActivePPPSession { list := make([]ActivePPPSession, 0) // Read network interfaces matching ppp* ifaces, err := net.Interfaces() if err != nil { return list } for _, iface := range ifaces { if !strings.HasPrefix(iface.Name, "ppp") { continue } addrs, _ := iface.Addrs() var assignedIP string for _, a := range addrs { if ipNet, ok := a.(*net.IPNet); ok && !ipNet.IP.IsLoopback() && ipNet.IP.To4() != nil { assignedIP = ipNet.IP.String() break } } sess := ActivePPPSession{ ID: iface.Name, Interface: iface.Name, Address: assignedIP, Uptime: "active", } // Try to detect service from process or default sess.Service = "ppp" sess.Username = "client-" + iface.Name m.populateIfaceTraffic(iface.Name, &sess) list = append(list, sess) } sort.Slice(list, func(i, j int) bool { return list[i].Interface < list[j].Interface }) return list } func (m *PPPServerManager) DisconnectSession(ifaceName string) error { if !strings.HasPrefix(ifaceName, "ppp") { return fmt.Errorf("invalid ppp interface") } _ = exec.Command("ip", "link", "set", ifaceName, "down").Run() _ = exec.Command("pkill", "-f", ifaceName).Run() return nil } func (m *PPPServerManager) populateIfaceTraffic(ifaceName string, sess *ActivePPPSession) { file, err := os.Open("/proc/net/dev") if err != nil { return } defer file.Close() scanner := bufio.NewScanner(file) for scanner.Scan() { line := scanner.Text() if strings.Contains(line, ifaceName+":") { parts := strings.Fields(line) if len(parts) >= 10 { rxBytes, _ := strconv.ParseUint(parts[1], 10, 64) txBytes, _ := strconv.ParseUint(parts[9], 10, 64) sess.RxBytes = rxBytes sess.TxBytes = txBytes sess.RxHuman = formatBytes(rxBytes) sess.TxHuman = formatBytes(txBytes) } break } } } func (m *PPPServerManager) countPPPSessions(service string) int { ifaces, err := net.Interfaces() if err != nil { return 0 } count := 0 for _, i := range ifaces { if strings.HasPrefix(i.Name, "ppp") { count++ } } return count } func (m *PPPServerManager) countIPSecSessions() int { out, err := exec.Command("swanctl", "--list-sas").Output() if err != nil { return 0 } return strings.Count(string(out), "ESTABLISHED") } func (m *PPPServerManager) isProcessRunning(pidFile string) bool { data, err := os.ReadFile(pidFile) if err != nil { return false } pid, err := strconv.Atoi(strings.TrimSpace(string(data))) if err != nil || pid <= 0 { return false } return syscall.Kill(pid, 0) == nil } func (m *PPPServerManager) applyAll() { if m.pppoeServer.Enabled { _ = m.applyPPPoEServer() } if m.pptpServer.Enabled { _ = m.applyPPTPServer() } if m.l2tpServer.Enabled { _ = m.applyL2TPServer() } if m.ipsecServer.Enabled { _ = m.applyIPSecServer() } }