From 025c94e58627800fb056ddf48cf450938fbffbd1 Mon Sep 17 00:00:00 2001 From: Owen Date: Tue, 18 Nov 2025 14:53:12 -0500 Subject: [PATCH] Export wireguard logger --- clients/clients.go | 220 ++++++++++++++++++++++++----------------- holepunch/holepunch.go | 8 +- logger/logger.go | 19 ++++ main.go | 2 +- netstack2/proxy.go | 141 ++++++++++++++++---------- netstack2/tun.go | 8 +- 6 files changed, 243 insertions(+), 155 deletions(-) diff --git a/clients/clients.go b/clients/clients.go index 1e47606..bc7140c 100644 --- a/clients/clients.go +++ b/clients/clients.go @@ -35,8 +35,9 @@ type WgConfig struct { } type Target struct { - CIDR string `json:"cidr"` - PortRange []PortRange `json:"portRange,omitempty"` + SourcePrefix string `json:"sourcePrefix"` + DestPrefix string `json:"destPrefix"` + PortRange []PortRange `json:"portRange,omitempty"` } type PortRange struct { @@ -332,9 +333,9 @@ func (s *WireGuardService) handleConfig(msg websocket.WSMessage) { logger.Error("Failed to ensure WireGuard peers: %v", err) } - // if err := s.ensureTargets(config.Targets); err != nil { - // logger.Error("Failed to ensure WireGuard targets: %v", err) - // } + if err := s.ensureTargets(config.Targets); err != nil { + logger.Error("Failed to ensure WireGuard targets: %v", err) + } } func (s *WireGuardService) ensureWireguardInterface(wgconfig WgConfig) error { @@ -460,15 +461,15 @@ func (s *WireGuardService) ensureTargets(targets []Target) error { return fmt.Errorf("netstack not initialized") } - // handler.AddSubnetRule(subnet2, []PortRange{ - // {Min: 12000, Max: 12001}, - // {Min: 8000, Max: 8000}, - // }) - for _, target := range targets { - prefix, err := netip.ParsePrefix(target.CIDR) + sourcePrefix, err := netip.ParsePrefix(target.SourcePrefix) if err != nil { - return fmt.Errorf("invalid CIDR %s: %v", target.CIDR, err) + return fmt.Errorf("invalid CIDR %s: %v", target.SourcePrefix, err) + } + + destPrefix, err := netip.ParsePrefix(target.DestPrefix) + if err != nil { + return fmt.Errorf("invalid CIDR %s: %v", target.DestPrefix, err) } var portRanges []netstack2.PortRange @@ -479,9 +480,9 @@ func (s *WireGuardService) ensureTargets(targets []Target) error { }) } - s.tnet.AddProxySubnetRule(prefix, portRanges) + s.tnet.AddProxySubnetRule(sourcePrefix, destPrefix, portRanges) - logger.Info("Added target subnet %s with port ranges: %v", target.CIDR, target.PortRange) + logger.Info("Added target subnet %s with port ranges: %v", target.SourcePrefix, target.PortRange) } return nil @@ -830,7 +831,6 @@ func (s *WireGuardService) reportPeerBandwidth() error { // filterReadOnlyFields removes read-only fields from WireGuard IPC configuration func (s *WireGuardService) handleAddTarget(msg websocket.WSMessage) { logger.Debug("Received message: %v", msg.Data) - var target Target jsonData, err := json.Marshal(msg.Data) if err != nil { @@ -838,33 +838,86 @@ func (s *WireGuardService) handleAddTarget(msg websocket.WSMessage) { return } - if err := json.Unmarshal(jsonData, &target); err != nil { - logger.Info("Error unmarshaling target data: %v", err) - return - } - if s.tnet == nil { logger.Info("Netstack not initialized") return } - prefix, err := netip.ParsePrefix(target.CIDR) - if err != nil { - logger.Info("Invalid CIDR %s: %v", target.CIDR, err) + // Try to unmarshal as array first + var targets []Target + if err := json.Unmarshal(jsonData, &targets); err != nil { + logger.Warn("Error unmarshaling target data: %v", err) return } - var portRanges []netstack2.PortRange - for _, pr := range target.PortRange { - portRanges = append(portRanges, netstack2.PortRange{ - Min: pr.Min, - Max: pr.Max, - }) + // Process all targets + for _, target := range targets { + sourcePrefix, err := netip.ParsePrefix(target.SourcePrefix) + if err != nil { + logger.Info("Invalid CIDR %s: %v", target.SourcePrefix, err) + continue + } + + destPrefix, err := netip.ParsePrefix(target.DestPrefix) + if err != nil { + logger.Info("Invalid CIDR %s: %v", target.DestPrefix, err) + continue + } + + var portRanges []netstack2.PortRange + for _, pr := range target.PortRange { + portRanges = append(portRanges, netstack2.PortRange{ + Min: pr.Min, + Max: pr.Max, + }) + } + + s.tnet.AddProxySubnetRule(sourcePrefix, destPrefix, portRanges) + + logger.Info("Added target subnet %s with port ranges: %v", target.SourcePrefix, target.PortRange) + } +} + +// filterReadOnlyFields removes read-only fields from WireGuard IPC configuration +func (s *WireGuardService) handleRemoveTarget(msg websocket.WSMessage) { + logger.Debug("Received message: %v", msg.Data) + + jsonData, err := json.Marshal(msg.Data) + if err != nil { + logger.Info("Error marshaling data: %v", err) + return } - s.tnet.AddProxySubnetRule(prefix, portRanges) + if s.tnet == nil { + logger.Info("Netstack not initialized") + return + } - logger.Info("Added target subnet %s with port ranges: %v", target.CIDR, target.PortRange) + // Try to unmarshal as array first + var targets []Target + if err := json.Unmarshal(jsonData, &targets); err != nil { + logger.Warn("Error unmarshaling target data: %v", err) + return + } + + // Process all targets + for _, target := range targets { + sourcePrefix, err := netip.ParsePrefix(target.SourcePrefix) + if err != nil { + logger.Info("Invalid CIDR %s: %v", target.SourcePrefix, err) + continue + } + + destPrefix, err := netip.ParsePrefix(target.DestPrefix) + if err != nil { + logger.Info("Invalid CIDR %s: %v", target.DestPrefix, err) + continue + } + + s.tnet.RemoveProxySubnetRule(sourcePrefix, destPrefix) + + logger.Info("Removed target subnet %s", target.SourcePrefix) + } } func (s *WireGuardService) handleUpdateTarget(msg websocket.WSMessage) { @@ -872,8 +925,8 @@ func (s *WireGuardService) handleUpdateTarget(msg websocket.WSMessage) { // you are going to get a oldTarget and a newTarget in the message type UpdateTargetRequest struct { - OldTarget Target `json:"oldTarget"` - NewTarget Target `json:"newTarget"` + OldTargets []Target `json:"oldTargets"` + NewTargets []Target `json:"newTargets"` } jsonData, err := json.Marshal(msg.Data) @@ -882,78 +935,59 @@ func (s *WireGuardService) handleUpdateTarget(msg websocket.WSMessage) { return } - var request UpdateTargetRequest - if err := json.Unmarshal(jsonData, &request); err != nil { - logger.Info("Error unmarshaling data: %v", err) - return - } - if s.tnet == nil { logger.Info("Netstack not initialized") return } - prefix, err := netip.ParsePrefix(request.OldTarget.CIDR) - if err != nil { - logger.Info("Invalid CIDR %s: %v", request.OldTarget.CIDR, err) + // Try to unmarshal as array first + var requests UpdateTargetRequest + if err := json.Unmarshal(jsonData, &requests); err != nil { + logger.Warn("Error unmarshaling target data: %v", err) return } - s.tnet.RemoveProxySubnetRule(prefix) + // Process all update requests + for _, target := range requests.OldTargets { + sourcePrefix, err := netip.ParsePrefix(target.SourcePrefix) + if err != nil { + logger.Info("Invalid CIDR %s: %v", target.SourcePrefix, err) + continue + } - // Now add the new target - newPrefix, err := netip.ParsePrefix(request.NewTarget.CIDR) - if err != nil { - logger.Info("Invalid CIDR %s: %v", request.NewTarget.CIDR, err) - return + destPrefix, err := netip.ParsePrefix(target.DestPrefix) + if err != nil { + logger.Info("Invalid CIDR %s: %v", target.DestPrefix, err) + continue + } + + s.tnet.RemoveProxySubnetRule(sourcePrefix, destPrefix) } - var portRanges []netstack2.PortRange - for _, pr := range request.NewTarget.PortRange { - portRanges = append(portRanges, netstack2.PortRange{ - Min: pr.Min, - Max: pr.Max, - }) + for _, target := range requests.NewTargets { + // Now add the new target + sourcePrefix, err := netip.ParsePrefix(target.SourcePrefix) + if err != nil { + logger.Info("Invalid CIDR %s: %v", target.SourcePrefix, err) + continue + } + + destPrefix, err := netip.ParsePrefix(target.DestPrefix) + if err != nil { + logger.Info("Invalid CIDR %s: %v", target.DestPrefix, err) + continue + } + + var portRanges []netstack2.PortRange + for _, pr := range target.PortRange { + portRanges = append(portRanges, netstack2.PortRange{ + Min: pr.Min, + Max: pr.Max, + }) + } + + s.tnet.AddProxySubnetRule(sourcePrefix, destPrefix, portRanges) } - - s.tnet.AddProxySubnetRule(newPrefix, portRanges) - - logger.Info("Updated target subnet from %s to %s", request.OldTarget.CIDR, request.NewTarget.CIDR) -} - -func (s *WireGuardService) handleRemoveTarget(msg websocket.WSMessage) { - logger.Debug("Received message: %v", msg.Data) - - type RemoveTargetRequest struct { - CIDR string `json:"cidr"` - } - - jsonData, err := json.Marshal(msg.Data) - if err != nil { - logger.Info("Error marshaling data: %v", err) - return - } - - var request RemoveTargetRequest - if err := json.Unmarshal(jsonData, &request); err != nil { - logger.Info("Error unmarshaling data: %v", err) - return - } - - if s.tnet == nil { - logger.Info("Netstack not initialized") - return - } - - prefix, err := netip.ParsePrefix(request.CIDR) - if err != nil { - logger.Info("Invalid CIDR %s: %v", request.CIDR, err) - return - } - - s.tnet.RemoveProxySubnetRule(prefix) - - logger.Info("Removed target subnet %s", request.CIDR) } // filterReadOnlyFields removes read-only fields from WireGuard IPC configuration diff --git a/holepunch/holepunch.go b/holepunch/holepunch.go index c9c31c6..df88530 100644 --- a/holepunch/holepunch.go +++ b/holepunch/holepunch.go @@ -173,10 +173,10 @@ func (m *Manager) runMultipleExitNodes(exitNodes []ExitNode) { } } - ticker := time.NewTicker(250 * time.Millisecond) + ticker := time.NewTicker(1 * time.Second) defer ticker.Stop() - timeout := time.NewTimer(15 * time.Second) + timeout := time.NewTimer(5 * time.Second) defer timeout.Stop() for { @@ -226,10 +226,10 @@ func (m *Manager) runSingleEndpoint(endpoint, serverPubKey string) { logger.Warn("Failed to send initial hole punch: %v", err) } - ticker := time.NewTicker(250 * time.Millisecond) + ticker := time.NewTicker(1 * time.Second) defer ticker.Stop() - timeout := time.NewTimer(15 * time.Second) + timeout := time.NewTimer(5 * time.Second) defer timeout.Stop() for { diff --git a/logger/logger.go b/logger/logger.go index c647443..d9927d4 100644 --- a/logger/logger.go +++ b/logger/logger.go @@ -127,3 +127,22 @@ func Fatal(format string, args ...interface{}) { func SetOutput(output *os.File) { GetLogger().SetOutput(output) } + +// WireGuardLogger is a wrapper type that matches WireGuard's Logger interface +type WireGuardLogger struct { + Verbosef func(format string, args ...any) + Errorf func(format string, args ...any) +} + +// GetWireGuardLogger returns a WireGuard-compatible logger that writes to the newt logger +// The prepend string is added as a prefix to all log messages +func (l *Logger) GetWireGuardLogger(prepend string) *WireGuardLogger { + return &WireGuardLogger{ + Verbosef: func(format string, args ...any) { + l.Debug(prepend+format, args...) + }, + Errorf: func(format string, args ...any) { + l.Error(prepend+format, args...) + }, + } +} diff --git a/main.go b/main.go index 7ccc0d2..329fda7 100644 --- a/main.go +++ b/main.go @@ -368,7 +368,7 @@ func main() { tlsClientCAs = append(tlsClientCAs, tlsClientCAsFlag...) } - logger.Init() + logger.Init(nil) loggerLevel := util.ParseLogLevel(logLevel) logger.GetLogger().SetLevel(loggerLevel) diff --git a/netstack2/proxy.go b/netstack2/proxy.go index 569f93e..8e37f12 100644 --- a/netstack2/proxy.go +++ b/netstack2/proxy.go @@ -23,68 +23,95 @@ type PortRange struct { Max uint16 } -// SubnetRule represents a subnet with optional port restrictions +// SubnetRule represents a subnet with optional port restrictions and source address type SubnetRule struct { - Prefix netip.Prefix - PortRanges []PortRange // empty slice means all ports allowed + SourcePrefix netip.Prefix // Source IP prefix (who is sending) + DestPrefix netip.Prefix // Destination IP prefix (where it's going) + PortRanges []PortRange // empty slice means all ports allowed } -// SubnetLookup provides fast IP subnet and port matching +// ruleKey is used as a map key for fast O(1) lookups +type ruleKey struct { + sourcePrefix string + destPrefix string +} + +// SubnetLookup provides fast IP subnet and port matching with O(1) lookup performance type SubnetLookup struct { mu sync.RWMutex - rules []SubnetRule + rules map[ruleKey]*SubnetRule // Map for O(1) lookups by prefix combination } // NewSubnetLookup creates a new subnet lookup table func NewSubnetLookup() *SubnetLookup { return &SubnetLookup{ - rules: make([]SubnetRule, 0), + rules: make(map[ruleKey]*SubnetRule), } } -// AddSubnet adds a subnet to the lookup table with optional port restrictions +// AddSubnet adds a subnet rule with source and destination prefixes and optional port restrictions // If portRanges is nil or empty, all ports are allowed for this subnet -func (sl *SubnetLookup) AddSubnet(prefix netip.Prefix, portRanges []PortRange) { +func (sl *SubnetLookup) AddSubnet(sourcePrefix, destPrefix netip.Prefix, portRanges []PortRange) { sl.mu.Lock() defer sl.mu.Unlock() - sl.rules = append(sl.rules, SubnetRule{ - Prefix: prefix, - PortRanges: portRanges, - }) -} + key := ruleKey{ + sourcePrefix: sourcePrefix.String(), + destPrefix: destPrefix.String(), + } -// RemoveSubnet removes a subnet from the lookup table -func (sl *SubnetLookup) RemoveSubnet(prefix netip.Prefix) { - sl.mu.Lock() - defer sl.mu.Unlock() - - for i, rule := range sl.rules { - if rule.Prefix == prefix { - sl.rules = append(sl.rules[:i], sl.rules[i+1:]...) - return - } + sl.rules[key] = &SubnetRule{ + SourcePrefix: sourcePrefix, + DestPrefix: destPrefix, + PortRanges: portRanges, } } -// Match checks if an IP and port match any subnet rule -// Returns true if the IP is in a matching subnet AND the port is in an allowed range -func (sl *SubnetLookup) Match(ip netip.Addr, port uint16) bool { +// RemoveSubnet removes a subnet rule from the lookup table +func (sl *SubnetLookup) RemoveSubnet(sourcePrefix, destPrefix netip.Prefix) { + sl.mu.Lock() + defer sl.mu.Unlock() + + key := ruleKey{ + sourcePrefix: sourcePrefix.String(), + destPrefix: destPrefix.String(), + } + + delete(sl.rules, key) +} + +// Match checks if a source IP, destination IP, and port match any subnet rule +// Returns true if BOTH: +// - The source IP is in the rule's source prefix +// - The destination IP is in the rule's destination prefix +// - The port is in an allowed range (or no port restrictions exist) +// +// This implementation uses O(n) iteration but checks exact prefix matches first for common cases +func (sl *SubnetLookup) Match(srcIP, dstIP netip.Addr, port uint16) bool { sl.mu.RLock() defer sl.mu.RUnlock() + // Iterate through all rules to find matching source and destination prefixes + // This is O(n) but necessary since we need to check prefix containment, not exact match for _, rule := range sl.rules { - if rule.Prefix.Contains(ip) { - // If no port ranges specified, all ports are allowed - if len(rule.PortRanges) == 0 { - return true - } + // Check if source and destination IPs match their respective prefixes + if !rule.SourcePrefix.Contains(srcIP) { + continue + } + if !rule.DestPrefix.Contains(dstIP) { + continue + } - // Check if port is in any of the allowed ranges - for _, pr := range rule.PortRanges { - if port >= pr.Min && port <= pr.Max { - return true - } + // Both IPs match - now check port restrictions + // If no port ranges specified, all ports are allowed + if len(rule.PortRanges) == 0 { + return true + } + + // Check if port is in any of the allowed ranges + for _, pr := range rule.PortRanges { + if port >= pr.Min && port <= pr.Max { + return true } } } @@ -150,37 +177,42 @@ func NewProxyHandler(options ProxyHandlerOptions) (*ProxyHandler, error) { } } - // // Example 1: Add a subnet with no port restrictions (all ports allowed) - // // This accepts all traffic to 10.20.20.0/24 - // subnet1 := netip.MustParsePrefix("10.20.20.0/24") - // handler.AddSubnetRule(subnet1, nil) + // // Example 1: Add a rule with no port restrictions (all ports allowed) + // // This accepts all traffic FROM 10.0.0.0/24 TO 10.20.20.0/24 + // sourceSubnet := netip.MustParsePrefix("10.0.0.0/24") + // destSubnet := netip.MustParsePrefix("10.20.20.0/24") + // handler.AddSubnetRule(sourceSubnet, destSubnet, nil) - // // Example 2: Add a subnet with specific port ranges - // // This accepts traffic to 192.168.1.0/24 only on ports 80, 443, and 8000-9000 - // subnet2 := netip.MustParsePrefix("10.20.21.21/32") - // handler.AddSubnetRule(subnet2, []PortRange{ - // {Min: 12000, Max: 12001}, - // {Min: 8000, Max: 8000}, + // // Example 2: Add a rule with specific port ranges + // // This accepts traffic FROM 10.0.0.5/32 TO 10.20.21.21/32 only on ports 80, 443, and 8000-9000 + // sourceIP := netip.MustParsePrefix("10.0.0.5/32") + // destIP := netip.MustParsePrefix("10.20.21.21/32") + // handler.AddSubnetRule(sourceIP, destIP, []PortRange{ + // {Min: 80, Max: 80}, + // {Min: 443, Max: 443}, + // {Min: 8000, Max: 9000}, // }) return handler, nil } // AddSubnetRule adds a subnet with optional port restrictions to the proxy handler +// sourcePrefix: The IP prefix of the peer sending the data +// destPrefix: The IP prefix of the destination // If portRanges is nil or empty, all ports are allowed for this subnet -func (p *ProxyHandler) AddSubnetRule(prefix netip.Prefix, portRanges []PortRange) { +func (p *ProxyHandler) AddSubnetRule(sourcePrefix, destPrefix netip.Prefix, portRanges []PortRange) { if p == nil || !p.enabled { return } - p.subnetLookup.AddSubnet(prefix, portRanges) + p.subnetLookup.AddSubnet(sourcePrefix, destPrefix, portRanges) } // RemoveSubnetRule removes a subnet from the proxy handler -func (p *ProxyHandler) RemoveSubnetRule(prefix netip.Prefix) { +func (p *ProxyHandler) RemoveSubnetRule(sourcePrefix, destPrefix netip.Prefix) { if p == nil || !p.enabled { return } - p.subnetLookup.RemoveSubnet(prefix) + p.subnetLookup.RemoveSubnet(sourcePrefix, destPrefix) } // Initialize sets up the promiscuous NIC with the netTun's notification system @@ -239,11 +271,14 @@ func (p *ProxyHandler) HandleIncomingPacket(packet []byte) bool { // Parse IPv4 header ipv4Header := header.IPv4(packet) + srcIP := ipv4Header.SourceAddress() dstIP := ipv4Header.DestinationAddress() // Convert gvisor tcpip.Address to netip.Addr + srcBytes := srcIP.As4() + srcAddr := netip.AddrFrom4(srcBytes) dstBytes := dstIP.As4() - addr := netip.AddrFrom4(dstBytes) + dstAddr := netip.AddrFrom4(dstBytes) // Parse transport layer to get destination port var dstPort uint16 @@ -269,8 +304,8 @@ func (p *ProxyHandler) HandleIncomingPacket(packet []byte) bool { dstPort = 0 } - // Check if the destination IP and port match any subnet rule - if p.subnetLookup.Match(addr, dstPort) { + // Check if the source IP, destination IP, and port match any subnet rule + if p.subnetLookup.Match(srcAddr, dstAddr, dstPort) { // Inject into proxy stack pkb := stack.NewPacketBuffer(stack.PacketBufferOptions{ Payload: buffer.MakeWithData(packet), diff --git a/netstack2/tun.go b/netstack2/tun.go index 20db481..2cd00ab 100644 --- a/netstack2/tun.go +++ b/netstack2/tun.go @@ -350,18 +350,18 @@ func (net *Net) ListenUDP(laddr *net.UDPAddr) (*gonet.UDPConn, error) { // AddProxySubnetRule adds a subnet rule to the proxy handler // If portRanges is nil or empty, all ports are allowed for this subnet -func (net *Net) AddProxySubnetRule(prefix netip.Prefix, portRanges []PortRange) { +func (net *Net) AddProxySubnetRule(sourcePrefix, destPrefix netip.Prefix, portRanges []PortRange) { tun := (*netTun)(net) if tun.proxyHandler != nil { - tun.proxyHandler.AddSubnetRule(prefix, portRanges) + tun.proxyHandler.AddSubnetRule(sourcePrefix, destPrefix, portRanges) } } // RemoveProxySubnetRule removes a subnet rule from the proxy handler -func (net *Net) RemoveProxySubnetRule(prefix netip.Prefix) { +func (net *Net) RemoveProxySubnetRule(sourcePrefix, destPrefix netip.Prefix) { tun := (*netTun)(net) if tun.proxyHandler != nil { - tun.proxyHandler.RemoveSubnetRule(prefix) + tun.proxyHandler.RemoveSubnetRule(sourcePrefix, destPrefix) } }