diff --git a/clients/clients.go b/clients/clients.go index a945985..7ef953f 100644 --- a/clients/clients.go +++ b/clients/clients.go @@ -40,12 +40,13 @@ type Target struct { SourcePrefix string `json:"sourcePrefix"` DestPrefix string `json:"destPrefix"` RewriteTo string `json:"rewriteTo,omitempty"` + DisableIcmp bool `json:"disableIcmp,omitempty"` PortRange []PortRange `json:"portRange,omitempty"` } type PortRange struct { - Min uint16 `json:"min"` - Max uint16 `json:"max"` + Min uint16 `json:"min"` + Max uint16 `json:"max"` Protocol string `json:"protocol"` // "tcp" or "udp" } @@ -593,8 +594,8 @@ func (s *WireGuardService) ensureWireguardInterface(wgconfig WgConfig) error { s.dns, s.mtu, netstack2.NetTunOptions{ - EnableTCPProxy: true, - EnableUDPProxy: true, + EnableTCPProxy: true, + EnableUDPProxy: true, EnableICMPProxy: true, }, ) @@ -701,13 +702,13 @@ func (s *WireGuardService) ensureTargets(targets []Target) error { var portRanges []netstack2.PortRange for _, pr := range target.PortRange { portRanges = append(portRanges, netstack2.PortRange{ - Min: pr.Min, - Max: pr.Max, + Min: pr.Min, + Max: pr.Max, Protocol: pr.Protocol, }) } - s.tnet.AddProxySubnetRule(sourcePrefix, destPrefix, target.RewriteTo, portRanges) + s.tnet.AddProxySubnetRule(sourcePrefix, destPrefix, target.RewriteTo, portRanges, target.DisableIcmp) logger.Info("Added target subnet from %s to %s rewrite to %s with port ranges: %v", target.SourcePrefix, target.DestPrefix, target.RewriteTo, target.PortRange) } @@ -1095,10 +1096,11 @@ func (s *WireGuardService) handleAddTarget(msg websocket.WSMessage) { portRanges = append(portRanges, netstack2.PortRange{ Min: pr.Min, Max: pr.Max, + Protocol: pr.Protocol, }) } - s.tnet.AddProxySubnetRule(sourcePrefix, destPrefix, target.RewriteTo, portRanges) + s.tnet.AddProxySubnetRule(sourcePrefix, destPrefix, target.RewriteTo, portRanges, target.DisableIcmp) logger.Info("Added target subnet from %s to %s rewrite to %s with port ranges: %v", target.SourcePrefix, target.DestPrefix, target.RewriteTo, target.PortRange) } @@ -1210,12 +1212,13 @@ func (s *WireGuardService) handleUpdateTarget(msg websocket.WSMessage) { var portRanges []netstack2.PortRange for _, pr := range target.PortRange { portRanges = append(portRanges, netstack2.PortRange{ - Min: pr.Min, - Max: pr.Max, + Min: pr.Min, + Max: pr.Max, + Protocol: pr.Protocol, }) } - s.tnet.AddProxySubnetRule(sourcePrefix, destPrefix, target.RewriteTo, portRanges) + s.tnet.AddProxySubnetRule(sourcePrefix, destPrefix, target.RewriteTo, portRanges, target.DisableIcmp) logger.Info("Added target subnet from %s to %s rewrite to %s with port ranges: %v", target.SourcePrefix, target.DestPrefix, target.RewriteTo, target.PortRange) } } diff --git a/netstack2/handlers.go b/netstack2/handlers.go index d6a861c..722a33d 100644 --- a/netstack2/handlers.go +++ b/netstack2/handlers.go @@ -424,7 +424,7 @@ func (h *ICMPHandler) handleICMPPacket(id stack.TransportEndpointID, pkt *stack. return false } - matchedRule := h.proxyHandler.subnetLookup.Match(srcAddr, dstAddr, 0) + matchedRule := h.proxyHandler.subnetLookup.Match(srcAddr, dstAddr, 0, header.ICMPv4ProtocolNumber) if matchedRule == nil { logger.Debug("ICMP Handler: No matching subnet rule for %s -> %s", srcIP, dstIP) return false @@ -446,7 +446,7 @@ func (h *ICMPHandler) handleICMPPacket(id stack.TransportEndpointID, pkt *stack. // Get the full ICMP payload (including the data after the header) icmpPayload := pkt.Data().AsRange().ToSlice() - + // Handle the ping in a goroutine to avoid blocking go h.proxyPing(srcIP, dstIP, actualDstIP, icmpHdr.Ident(), icmpHdr.Sequence(), icmpPayload) @@ -513,7 +513,7 @@ func (h *ICMPHandler) proxyPing(srcIP, originalDstIP, actualDstIP string, ident, // Wait for reply - loop to filter out non-matching packets (like our own echo request) replyBuf := make([]byte, 1500) var echoReply *icmp.Echo - + for { n, peer, err := conn.ReadFrom(replyBuf) if err != nil { diff --git a/netstack2/proxy.go b/netstack2/proxy.go index 90ec15e..fefb18d 100644 --- a/netstack2/proxy.go +++ b/netstack2/proxy.go @@ -70,7 +70,7 @@ func NewSubnetLookup() *SubnetLookup { // 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 // rewriteTo can be either an IP/CIDR (e.g., "192.168.1.1/32") or a domain name (e.g., "example.com") -func (sl *SubnetLookup) AddSubnet(sourcePrefix, destPrefix netip.Prefix, rewriteTo string, portRanges []PortRange) { +func (sl *SubnetLookup) AddSubnet(sourcePrefix, destPrefix netip.Prefix, rewriteTo string, portRanges []PortRange, disableIcmp bool) { sl.mu.Lock() defer sl.mu.Unlock() @@ -82,6 +82,7 @@ func (sl *SubnetLookup) AddSubnet(sourcePrefix, destPrefix netip.Prefix, rewrite sl.rules[key] = &SubnetRule{ SourcePrefix: sourcePrefix, DestPrefix: destPrefix, + DisableIcmp: disableIcmp, RewriteTo: rewriteTo, PortRanges: portRanges, } @@ -124,6 +125,11 @@ func (sl *SubnetLookup) Match(srcIP, dstIP netip.Addr, port uint16, proto tcpip. continue } + if rule.DisableIcmp && (proto == header.ICMPv4ProtocolNumber || proto == header.ICMPv6ProtocolNumber) { + // ICMP is disabled for this subnet + return nil + } + // Both IPs match - now check port restrictions // If no port ranges specified, all ports are allowed if len(rule.PortRanges) == 0 { @@ -187,8 +193,8 @@ type ProxyHandler struct { destRewriteTable map[destKey]netip.Addr // Maps original dest to rewritten dest for handler lookups natMu sync.RWMutex enabled bool - icmpReplies chan []byte // Channel for ICMP reply packets to be sent back through the tunnel - notifiable channel.Notification // Notification handler for triggering reads + icmpReplies chan []byte // Channel for ICMP reply packets to be sent back through the tunnel + notifiable channel.Notification // Notification handler for triggering reads } // ProxyHandlerOptions configures the proxy handler @@ -275,11 +281,11 @@ func NewProxyHandler(options ProxyHandlerOptions) (*ProxyHandler, error) { // destPrefix: The IP prefix of the destination // rewriteTo: Optional address to rewrite destination to - can be IP/CIDR or domain name // If portRanges is nil or empty, all ports are allowed for this subnet -func (p *ProxyHandler) AddSubnetRule(sourcePrefix, destPrefix netip.Prefix, rewriteTo string, portRanges []PortRange) { +func (p *ProxyHandler) AddSubnetRule(sourcePrefix, destPrefix netip.Prefix, rewriteTo string, portRanges []PortRange, disableIcmp bool) { if p == nil || !p.enabled { return } - p.subnetLookup.AddSubnet(sourcePrefix, destPrefix, rewriteTo, portRanges) + p.subnetLookup.AddSubnet(sourcePrefix, destPrefix, rewriteTo, portRanges, disableIcmp) } // RemoveSubnetRule removes a subnet from the proxy handler diff --git a/netstack2/tun.go b/netstack2/tun.go index 71eeff2..e743f1e 100644 --- a/netstack2/tun.go +++ b/netstack2/tun.go @@ -354,10 +354,10 @@ 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 // rewriteTo can be either an IP/CIDR (e.g., "192.168.1.1/32") or a domain name (e.g., "example.com") -func (net *Net) AddProxySubnetRule(sourcePrefix, destPrefix netip.Prefix, rewriteTo string, portRanges []PortRange) { +func (net *Net) AddProxySubnetRule(sourcePrefix, destPrefix netip.Prefix, rewriteTo string, portRanges []PortRange, disableIcmp bool) { tun := (*netTun)(net) if tun.proxyHandler != nil { - tun.proxyHandler.AddSubnetRule(sourcePrefix, destPrefix, rewriteTo, portRanges) + tun.proxyHandler.AddSubnetRule(sourcePrefix, destPrefix, rewriteTo, portRanges, disableIcmp) } }