virt-manager Windows 11 VM traffic cannot be NAT'd due to Xray transparent proxy (TPROXY/nftables) on Debian host
05:47 04 Nov 2025

I am running a Windows 11 virtual machine on my Debian host system using virt-manager (QEMU/KVM).

My Debian host uses a transparent proxy configured with Xray and nftables for all outgoing traffic. The VM is connected using the default NAT network provided by libvirt.

The issue I'm facing is that the VM's network traffic seems unable to be properly NAT'd and/or routed through the host's transparent proxy setup. Based on a preliminary assessment (from an AI), the transparent proxy rules might be interfering with the default NAT mechanism for the VM's packets.

I need a way to ensure the VM's traffic:

  1. Is properly NAT'd by libvirt (or a similar mechanism).
  2. Is then successfully diverted and processed by the Xray transparent proxy before leaving the host.

Is my assumption correct that the transparent proxy is the cause, and if so, how can I modify my nftables or routing to accommodate the VM's traffic? Or is the problem elsewhere (e.g., related to libvirt NAT and its interaction with prerouting hooks)?

Transparent Proxy Configuration Files

Here are the relevant files for my transparent proxy setup:

transparent_start.sh

#!/usr/bin/bash

# Stop previous instance first
/usr/local/etc/xray/transparent_stop.sh

# Set policy route v4
/usr/sbin/ip rule add fwmark 1 table 100
/usr/sbin/ip route add local 0.0.0.0/0 dev lo table 100

# Set policy route v6
/usr/sbin/ip -6 rule add fwmark 1 table 106
/usr/sbin/ip -6 route add local ::/0 dev lo table 106

# Set nftables rules
/usr/sbin/nft -f /usr/local/etc/xray/transparent_rules.nft

transparent_stop.sh

#!/usr/bin/bash

# Remove policy route v4
/usr/sbin/ip rule del fwmark 1 table 100
/usr/sbin/ip route del local 0.0.0.0/0 dev lo table 100

# Remove policy route v6
/usr/sbin/ip -6 rule del fwmark 1 table 106
/usr/sbin/ip -6 route del local ::/0 dev lo table 106

# Destroy nftables table
/usr/sbin/nft destroy table inet xray

transparent_rules.nft

#!/usr/sbin/nft -f

add table inet xray {
  chain prerouting {
    type filter hook prerouting priority filter; policy accept;
    # Exclusions
    ip daddr { 127.0.0.0/8, 224.0.0.0/4, 255.255.255.255 } return
    meta l4proto tcp ip daddr 192.168.0.0/16 return
    ip daddr 192.168.0.0/16 udp dport != 53 return
    ip6 daddr { ::1, fe80::/64 } return
    meta l4proto tcp ip6 daddr 2409:8a55:612:cb0::/64 return
    ip6 daddr 2409:8a55:612:cb0::/64 udp dport != 53 return
    
    # Skip marked packets
    meta mark 0x000000ff return
    
    # TPROXY redirection to Xray on port 23939 (IPv4 and IPv6)
    meta l4proto { tcp, udp } meta mark set 0x00000001 tproxy ip to 127.0.0.1:23939 accept
    meta l4proto { tcp, udp } meta mark set 0x00000001 tproxy ip6 to [::1]:23939 accept
  }

  chain output {
    type route hook output priority filter; policy accept;
    # Exclusions
    ip daddr { 127.0.0.0/8, 224.0.0.0/4, 255.255.255.255 } return
    meta l4proto tcp ip daddr 192.168.0.0/16 return
    ip daddr 192.168.0.0/16 udp dport != 53 return
    ip6 daddr { ::1, fe80::/64 } return
    meta l4proto tcp ip6 daddr 2409:8a55:612:cb0::/64 return
    ip6 daddr 2409:8a55:612:cb0::/64 udp dport != 53 return
    
    # Skip marked packets
    meta mark 0x000000ff return
    
    # Mark local traffic that should be proxied
    meta l4proto { tcp, udp } meta mark set 0x00000001 accept
  }

  chain divert {
    type filter hook prerouting priority mangle; policy accept;
    # Mark packets for TPROXY based on socket transparent option
    meta l4proto tcp socket transparent 1 meta mark set 0x00000001 accept
  }
}

The issue is likely where libvirt's NAT (which typically uses the POSTROUTING hook for SNAT) interacts with the prerouting TPROXY/redirection rules.

Any guidance on how to properly integrate libvirt NAT with a TPROXY transparent proxy setup would be greatly appreciated.

linux virtualization kvm nftables transparentproxy