package app import ( "strings" "time" ) const ( transportPolicyTargetAdGuardID = "adguardvpn" transportPolicyTargetAdGuardIfaceID = transportDefaultIfaceID transportPolicyTargetAdGuardTable = "agvpn" ) var transportPolicyTargetAdGuardAllowedActions = []string{"start", "stop", "restart"} func isTransportPolicyVirtualClientID(id string) bool { return sanitizeID(id) == transportPolicyTargetAdGuardID } func isTransportPolicyVirtualClient(client TransportClient) bool { if isTransportPolicyVirtualClientID(client.ID) { return true } return strings.EqualFold(strings.TrimSpace(string(client.Kind)), transportPolicyTargetAdGuardID) } func resolveTransportPolicyVirtualClient(id string) (TransportClient, bool) { if !isTransportPolicyVirtualClientID(id) { return TransportClient{}, false } return buildTransportPolicyAdGuardTarget() } func transportPolicyPersistableClients(items []TransportClient) []TransportClient { if len(items) == 0 { return nil } out := make([]TransportClient, 0, len(items)) for _, it := range items { if isTransportPolicyVirtualClient(it) { continue } out = append(out, it) } return out } func transportPolicyClientsWithVirtualTargets(base []TransportClient) []TransportClient { out := append([]TransportClient(nil), base...) for i := range out { out[i].ID = sanitizeID(out[i].ID) } if adg, ok := buildTransportPolicyAdGuardTarget(); ok { if idx := findTransportClientIndex(out, adg.ID); idx >= 0 { out[idx] = adg } else { out = append(out, adg) } } return out } func buildTransportPolicyAdGuardTarget() (TransportClient, bool) { now := time.Now().UTC() stdout, _, _, err := runCommandTimeout(1500*time.Millisecond, "systemctl", "is-active", adgvpnUnit) unitState := strings.TrimSpace(stdout) if err != nil && unitState == "" { return TransportClient{}, false } word, raw := parseAutoloopStatus(tailFile(autoloopLogPath, 120)) return buildTransportPolicyAdGuardTargetFromObservation(unitState, word, raw, now), true } func buildTransportPolicyAdGuardTargetFromObservation(unitState, statusWord, raw string, observedAt time.Time) TransportClient { ts := observedAt.UTC().Format(time.RFC3339) status := adGuardPolicyTargetStatus(unitState, statusWord) iface := adGuardPolicyTargetIface(raw, status) lastError := "" if status == TransportClientDown || status == TransportClientDegraded { msg := strings.TrimSpace(unitState) if msg == "" { msg = strings.TrimSpace(statusWord) } lastError = msg } return TransportClient{ ID: transportPolicyTargetAdGuardID, Name: "AdGuard VPN", Kind: TransportClientKind(transportPolicyTargetAdGuardID), Enabled: true, Status: status, IfaceID: transportPolicyTargetAdGuardIfaceID, Iface: iface, RoutingTable: transportPolicyTargetAdGuardTable, Capabilities: []string{"vpn", "systemd", "autoloop"}, Health: TransportClientHealth{ LastCheck: ts, LastError: lastError, }, Runtime: TransportClientRuntime{ Backend: "adguard", AllowedActions: append([]string(nil), transportPolicyTargetAdGuardAllowedActions...), LastAction: "observe", LastActionAt: ts, }, UpdatedAt: ts, } } func adGuardPolicyTargetStatus(unitState, statusWord string) TransportClientStatus { word := strings.ToUpper(strings.TrimSpace(statusWord)) switch word { case "CONNECTED": return TransportClientUp case "RECONNECTING": return TransportClientStarting case "DISCONNECTED": return TransportClientDown case "ERROR": return TransportClientDegraded } state := strings.ToLower(strings.TrimSpace(unitState)) switch state { case "active": return TransportClientUp case "activating", "reloading": return TransportClientStarting case "failed", "inactive", "deactivating": return TransportClientDown default: return TransportClientDown } } func adGuardPolicyTargetIface(raw string, status TransportClientStatus) string { v := strings.TrimSpace(raw) if v != "" { low := strings.ToLower(v) needle := "running on " if idx := strings.LastIndex(low, needle); idx >= 0 { tail := strings.TrimSpace(v[idx+len(needle):]) if tail != "" { iface := strings.Trim(tail, ".,;:()[]{}") if parts := strings.Fields(iface); len(parts) > 0 { if strings.TrimSpace(parts[0]) != "" { return strings.TrimSpace(parts[0]) } } } } } if iface, _ := resolveTrafficIface(loadTrafficModeState().PreferredIface); strings.TrimSpace(iface) != "" { return strings.TrimSpace(iface) } if status == TransportClientUp || status == TransportClientStarting { return "tun0" } return "" }