package divert import ( "fmt" "net" "strings" ) // FilterParams collects the inputs needed to build a WinDivert filter // expression for Drover's outbound capture. type FilterParams struct { // TargetPIDs is the set of PIDs whose outbound traffic should be // captured (e.g. Discord variants). When empty, the resulting // filter is "false" — captures nothing — which is the right // behaviour while procscan reports zero Discord processes. TargetPIDs []uint32 // OwnPID is drover.exe's own PID. Excluded from capture so our // SOCKS5 traffic to the upstream proxy doesn't get re-captured. OwnPID uint32 // UpstreamIP is the resolved IPv4 of the upstream SOCKS5 proxy. // Excluded from capture as a second line of defence against // self-loops. If unparseable, "0.0.0.0" is substituted (caller // should validate before calling). UpstreamIP string } // BuildFilter returns a WinDivert filter expression string suitable // for WinDivertOpen. The expression captures only outbound IPv4 TCP/UDP // from the listed PIDs, excluding our own process and the upstream // proxy's IP. func BuildFilter(p FilterParams) string { if len(p.TargetPIDs) == 0 { return "false" } upstream := p.UpstreamIP if net.ParseIP(upstream).To4() == nil { upstream = "0.0.0.0" } pidClauses := make([]string, len(p.TargetPIDs)) for i, pid := range p.TargetPIDs { pidClauses[i] = fmt.Sprintf("processId == %d", pid) } pidClause := "(" + strings.Join(pidClauses, " or ") + ")" parts := []string{ "outbound", "(tcp or udp)", "ip", pidClause, fmt.Sprintf("processId != %d", p.OwnPID), fmt.Sprintf("ip.DstAddr != %s", upstream), "not (ip.DstAddr >= 224.0.0.0 and ip.DstAddr <= 239.255.255.255)", "not (ip.DstAddr >= 127.0.0.0 and ip.DstAddr <= 127.255.255.255)", "not (ip.DstAddr >= 169.254.0.0 and ip.DstAddr <= 169.254.255.255)", } return strings.Join(parts, " and ") }