Rework into using nftables, along with proper Xray DNS configuration

This commit is contained in:
Levent Duivel 2025-04-13 13:41:16 +05:00
parent a5c8961d78
commit f9645b0ef6
17 changed files with 249 additions and 195 deletions

View File

@ -1,17 +1,21 @@
openwrt-xray
------------
Requirements
------------
- OpenWRT 22.03 and higher
- Router should be in 192.168.0.0/16 subnet (default rules operate on that)
Install
-------
1. Drop the files onto OpenWRT (22.03 and higher) router
2. Run `install_xray.sh`: `chmod +x /root/install_xray.sh && /root/install_xray.sh`
3. Configure this installation:
- Edit this rule in `/etc/xray/startup.sh`: `iptables -t mangle -A XRAY -d 1.1.1.1 -j RETURN` to match your public static IP address
- In `/root/xray_config/04_outbounds.json` add your connection details
- You can optionally add excluding/blocking rules to `startup.sh`, see possible additions in `fwd_functions.sh` beside it.
- In `/etc/xray/config/outbounds.jsonc` add your connection details (but be carefult with specified streamSettings->sockOpt, these are required)
- You can optionally add excluding/blocking rules to `/etc/xray/custom_rules.sh`, see possible additions in `fwd_functions.sh` beside it.
4. Enable the `xray` service in LuCI (System -> Startup, it should be at the end of the list) and reboot your router.
(In case it fails to work, you may disable the service and reboot the router again to revert the effects)
(In case it fails to work, you may disable the service and reboot the router again to revert the effects, or use `/etc/xray/revert.sh`)
crontab
-------

View File

@ -25,7 +25,7 @@ start_service() {
config_get dialer "config" "dialer"
config_get format "config" "format" "json"
# runs iptables setup
# runs nftables setup
/etc/xray/startup.sh
procd_open_instance "$CONF"

View File

@ -1,9 +0,0 @@
{
"log":
{
"access": "/etc/xray/log/access.log",
"dnsLog": false,
"error": "/etc/xray/log/error.log",
"loglevel": "none"
}
}

View File

@ -1,24 +0,0 @@
{
"transport":
{
"domainStrategy": "IPIfNonMatch",
"grpcSettings":
{
"health_check_timeout": 20,
"idle_timeout": 60,
"initial_windows_size": 35536,
"permit_without_stream": true
},
"httpSettings":
{
"health_check_timeout": 15,
"read_idle_timeout": 10
},
"sockopt":
{
"tcpFastOpen": true,
"tcpMptcp": true,
"tcpNoDelay": true
}
}
}

View File

@ -1,13 +0,0 @@
{
"policy":
{
"levels":
{
"0":
{
// If you have issues with SSH connections, it's recommended to increase this value. See the docs
"connIdle": 30
}
}
}
}

19
etc/xray/config/dns.jsonc Normal file
View File

@ -0,0 +1,19 @@
{
"dns": {
"tag": "dns-in",
"hosts": {
"dns.google": "8.8.8.8"
},
"servers": [
"https://dns.google/dns-query",
{
"address": "localhost",
"disableFallback": true,
"domains": [
"regexp:.*\\.lan"
]
}
],
"queryStrategy": "UseIPv4"
}
}

View File

@ -1,18 +1,14 @@
{
"inbounds":
[
"inbounds": [
{
"port": 61219,
"protocol": "dokodemo-door",
"settings":
{
"settings": {
"followRedirect": true,
"network": "tcp,udp"
},
"sniffing":
{
"destOverride":
[
"sniffing": {
"destOverride": [
"http",
"tls",
"quic"
@ -20,10 +16,8 @@
"enabled": true,
"routeOnly": true
},
"streamSettings":
{
"sockopt":
{
"streamSettings": {
"sockopt": {
"tproxy": "tproxy"
}
},

View File

@ -0,0 +1,8 @@
{
"log": {
// "access": "/etc/xray/log/access.log",
// "error": "/etc/xray/log/error.log",
"loglevel": "none",
"dnsLog": true
}
}

View File

@ -1,17 +1,14 @@
{
"outbounds":
[
"outbounds": [
{
"tag": "vless-reality",
"protocol": "vless",
"settings":
{
"vnext":
[
"settings": {
"vnext": [
{
"address": "1.1.1.1",
"port": 443,
"users":
[
"users": [
{
"encryption": "none",
"flow": "xtls-rprx-vision",
@ -22,39 +19,52 @@
}
]
},
"streamSettings":
{
"streamSettings": {
"network": "tcp",
"realitySettings":
{
"realitySettings": {
"fingerprint": "chrome",
"publicKey": "",
"serverName": "",
"shortId": "",
"spiderX": "/"
},
"security": "reality"
},
"tag": "vless-reality"
"security": "reality",
// Important: This is required for rules to work correctly!
"sockopt": {
"domainStrategy": "UseIP",
"mark": 2
}
}
},
{
"tag": "direct",
"protocol": "freedom",
"tag": "direct"
},
{
"protocol": "blackhole",
"settings":
{
"response":
{
"type": "http"
"streamSettings": {
"sockopt": {
"mark": 2
}
},
"tag": "block"
"settings": {
"domainStrategy": "UseIP"
}
},
{
"tag": "block",
"protocol": "blackhole",
"settings": {
"response": {
"type": "http"
}
}
},
{
"tag": "dns-out",
"protocol": "dns",
"tag": "dns"
"streamSettings": {
"sockopt": {
"mark": 2
}
}
}
]
}

View File

@ -0,0 +1,10 @@
{
"policy": {
"levels": {
"0": {
// If you have issues with SSH connections, it's recommended to increase this value. See the docs
"connIdle": 30
}
}
}
}

View File

@ -1,25 +1,31 @@
{
"routing": {
"domainStrategy": "IPIfNonMatch",
"rules": [
// Capture DNS
{
"inboundTag": ["redirect", "tproxy"],
"outboundTag": "dns",
"type": "field",
"inboundTag": "tproxy",
"outboundTag": "dns-out",
"port": 53
},
// Block QUIC
{
"inboundTag": ["redirect", "tproxy"],
"inboundTag": "tproxy",
"outboundTag": "block",
"type": "field",
"protocol": ["quic"]
"protocol": [
"quic"
]
},
// Force DNS to go through direct
// If needed, you can force DNS to go through other outbound using tags for specific servers in dns.jsonc
{
"inboundTag": "dns-in",
"outboundTag": "direct"
},
// Force specific source IPs to go direct
{
"inboundTag": ["redirect", "tproxy"],
"inboundTag": "tproxy",
"outboundTag": "direct",
"type": "field",
"source": [
"192.168.2.255",
"192.168.2.254"
@ -27,9 +33,8 @@
},
// Block common ads and other stuff
{
"inboundTag": ["redirect", "tproxy"],
"inboundTag": "tproxy",
"outboundTag": "block",
"type": "field",
"domain": [
"geosite:category-ads-all",
"google-analytics",
@ -42,39 +47,36 @@
},
// Force BitTorrent to go through direct
{
"inboundTag": ["redirect", "tproxy"],
"inboundTag": "tproxy",
"outboundTag": "direct",
"type": "field",
"protocol": ["bittorrent"]
},
// Explicitly force direct
"protocol": "bittorrent"
},
// Explicitly force direct (domains)
{
"inboundTag": ["redirect", "tproxy"],
"inboundTag": "tproxy",
"outboundTag": "direct",
"type": "field",
"domain": [
"regexp:^([\\w\\-\\.]+\\.)ru$", // .ru
"regexp:^([\\w\\-\\.]+\\.)su$", // .su
"regexp:^([\\w\\-\\.]+\\.)xn--p1ai$", // .рф
"regexp:^([\\w\\-\\.]+\\.)xn--p1acf$", // .рус
"regexp:^([\\w\\-\\.]+\\.)xn--80asehdb$", // .онлайн
"regexp:^([\\w\\-\\.]+\\.)ru$", // .ru
// "regexp:^([\\w\\-\\.]+\\.)su$", // .su
"regexp:^([\\w\\-\\.]+\\.)xn--p1ai$", // .рф
"regexp:^([\\w\\-\\.]+\\.)xn--p1acf$", // .рус
"regexp:^([\\w\\-\\.]+\\.)xn--80asehdb$", // .онлайн
"regexp:^([\\w\\-\\.]+\\.)xn--c1avg$", // .орг
"regexp:^([\\w\\-\\.]+\\.)xn--80aswg$", // .сайт
"regexp:^([\\w\\-\\.]+\\.)xn--80adxhks$", // .москва
"regexp:^([\\w\\-\\.]+\\.)moscow$", // .moscow
"regexp:^([\\w\\-\\.]+\\.)xn--d1acj3b$", // .дети
"regexp:^([\\w\\-\\.]+\\.)yandex$", // .yandex
"regexp:^([\\w\\-\\.]+\\.)xn--80aswg$", // .сайт
"regexp:^([\\w\\-\\.]+\\.)xn--80adxhks$", // .москва
"regexp:^([\\w\\-\\.]+\\.)moscow$", // .moscow
"regexp:^([\\w\\-\\.]+\\.)xn--d1acj3b$", // .дети
"regexp:^([\\w\\-\\.]+\\.)yandex$", // .yandex
"geosite:category-ru",
"geosite:category-gov-ru",
"geosite:yandex",
"geosite:steam",
"geosite:vk",
"geosite:category-gov-ru",
"regexp:^assets(\\d*?)\\.xboxlive\\.com$",
// "regexp:^assets(\\d*?)\\.xboxlive\\.com$",
"domain:rt.ru",
"domain:ngenix.net",
"domain:plex.tv",
"geoip:ru",
"domain:kaspersky.com",
"domain:koronapay.com",
"domain:binance.com",
@ -87,13 +89,23 @@
"domain:veesp.com"
]
},
// Explicitly force direct (IPs)
{
"inboundTag": "tproxy",
"outboundTag": "direct",
"ip": [
"geoip:ru",
"geoip:am"
]
},
// No rules found? Go vless-reality
{
"inboundTag": ["redirect", "tproxy"],
"outboundTag": "vless-reality",
"type": "field"
"inboundTag": [
"tproxy",
"dns-in"
],
"outboundTag": "vless-reality"
}
]
}
}
}

9
etc/xray/custom_rules.sh Normal file
View File

@ -0,0 +1,9 @@
#!/bin/sh
# Source the function definitions
. /etc/xray/fwd_functions.sh
# Add your custom rules here
# See the fwd_functions.sh for the available functions
# Example: Exclude traefik HTTP+HTTPS
# direct_port_range_for_ip "192.168.1.165" 80 443

View File

@ -1,63 +1,69 @@
#!/bin/sh
# Function to add iptables rules for a specific IP and port
# Function to add nftables rules for a specific IP and port
direct_port_for_ip() {
ip=$1
port=$2
iptables -t mangle -A XRAY -d "$ip"/32 -p tcp --dport "$port" -j RETURN
iptables -t mangle -A XRAY -d "$ip"/32 -p udp --dport "$port" -j RETURN
iptables -t mangle -A XRAY -s "$ip"/32 -p tcp --sport "$port" -j RETURN
iptables -t mangle -A XRAY -s "$ip"/32 -p udp --sport "$port" -j RETURN
nft insert rule ip xray prerouting ip daddr "$ip" tcp dport "$port" counter return
nft insert rule ip xray prerouting ip daddr "$ip" udp dport "$port" counter return
nft insert rule ip xray output ip daddr "$ip" tcp dport "$port" counter return
nft insert rule ip xray output ip daddr "$ip" udp dport "$port" counter return
}
# Function to add iptables rules for a single port without specifying IP
# Function to add nftables rules for a single port without specifying IP
direct_port() {
port=$1
iptables -t mangle -A XRAY -p tcp --dport "$port" -j RETURN
iptables -t mangle -A XRAY -p udp --dport "$port" -j RETURN
iptables -t mangle -A XRAY -p tcp --sport "$port" -j RETURN
iptables -t mangle -A XRAY -p udp --sport "$port" -j RETURN
nft insert rule ip xray prerouting tcp dport "$port" counter return
nft insert rule ip xray prerouting udp dport "$port" counter return
nft insert rule ip xray output tcp dport "$port" counter return
nft insert rule ip xray output udp dport "$port" counter return
}
# Function to add iptables rules for a range of ports for a specific IP
# Function to add nftables rules for a range of ports for a specific IP
direct_port_range_for_ip() {
ip=$1
start_port=$2
end_port=$3
port=$start_port
while [ "$port" -le "$end_port" ]; do
direct_port_for_ip "$ip" "$port"
port=$((port + 1))
done
nft insert rule ip xray prerouting ip daddr "$ip" tcp dport { "$start_port"-"$end_port" } counter return
nft insert rule ip xray prerouting ip daddr "$ip" udp dport { "$start_port"-"$end_port" } counter return
nft insert rule ip xray output ip daddr "$ip" tcp dport { "$start_port"-"$end_port" } counter return
nft insert rule ip xray output ip daddr "$ip" udp dport { "$start_port"-"$end_port" } counter return
}
# Function to add iptables rules for a range of ports without specifying IP
# Function to add nftables rules for a range of ports without specifying IP
direct_port_range() {
start_port=$1
end_port=$2
port=$start_port
while [ "$port" -le "$end_port" ]; do
direct_port "$port"
port=$((port + 1))
done
nft insert rule ip xray prerouting tcp dport { "$start_port"-"$end_port" } counter return
nft insert rule ip xray prerouting udp dport { "$start_port"-"$end_port" } counter return
nft insert rule ip xray output tcp dport { "$start_port"-"$end_port" } counter return
nft insert rule ip xray output udp dport { "$start_port"-"$end_port" } counter return
}
# Function to add iptables rules for an IP without specifying ports
# Function to add nftables rules for an IP without specifying ports
direct_ip() {
ip=$1
iptables -t mangle -A XRAY -d "$ip"/32 -j RETURN
iptables -t mangle -A XRAY -s "$ip"/32 -j RETURN
nft insert rule ip xray prerouting ip saddr "$ip" counter return
nft insert rule ip xray output ip saddr "$ip" counter return
nft insert rule ip xray prerouting ip daddr "$ip" counter return
nft insert rule ip xray output ip daddr "$ip" counter return
}
# Function to add iptables rules for blocking IP
# Function to add nftables rules for blocking IP
block_ip() {
ip=$1
iptables -I FORWARD 1 -d "$ip"/32 -j DROP
iptables -I FORWARD 1 -s "$ip"/32 -j DROP
# Block in prerouting chain
nft insert rule ip xray prerouting ip daddr "$ip" counter drop
nft insert rule ip xray prerouting ip saddr "$ip" counter drop
# Block in output chain
nft insert rule ip xray output ip daddr "$ip" counter drop
nft insert rule ip xray output ip saddr "$ip" counter drop
}

33
etc/xray/nft.conf Normal file
View File

@ -0,0 +1,33 @@
#!/usr/sbin/nft -f
define RESERVED_IP = {
10.0.0.0/8,
100.64.0.0/10,
127.0.0.0/8,
169.254.0.0/16,
172.16.0.0/12,
192.0.0.0/24,
224.0.0.0/4,
240.0.0.0/4,
255.255.255.255/32
}
table ip xray {
chain prerouting {
type filter hook prerouting priority mangle; policy accept;
ip daddr $RESERVED_IP return
ip daddr 192.168.0.0/16 tcp dport != 53 return
ip daddr 192.168.0.0/16 udp dport != 53 return
ip protocol tcp tproxy to 127.0.0.1:61219 meta mark set 1
ip protocol udp tproxy to 127.0.0.1:61219 meta mark set 1
}
chain output {
type route hook output priority mangle; policy accept;
ip daddr $RESERVED_IP return
ip daddr 192.168.0.0/16 tcp dport != 53 return
ip daddr 192.168.0.0/16 udp dport != 53 return
meta mark 2 return
ip protocol tcp meta mark set 1
ip protocol udp meta mark set 1
}
}

6
etc/xray/revert.sh Normal file
View File

@ -0,0 +1,6 @@
#!/bin/sh
nft delete table ip xray
ip route del local default dev lo table 100
ip rule del table 100
rm -f /tmp/xray_startup_executed

View File

@ -1,54 +1,43 @@
#!/bin/sh
# Ensure this script runs only once per boot
if [ -f /tmp/xray_startup_executed ]; then
# The file exists, so do not run the script
echo "This script was executed already. To revert the results, reboot the device"
exit 0
fi
# Source the function definitions
. /etc/xray/fwd_functions.sh
# create chain
# Get WAN device name first
WAN_DEVICE=$(uci get network.wan.device)
if [ -z "$WAN_DEVICE" ]; then
echo "Error: Could not determine WAN device"
exit 1
fi
# Get WAN interface IP address using the device name, excluding localhost and private IPs
# Comment this out, if it doesn't work for you
WAN_IP=$(ip addr show $WAN_DEVICE | grep 'inet ' | awk '{print $2}' | cut -d/ -f1 | grep -v '^127\.' | grep -v '^192\.168\.')
# WAN_IP="1.1.1.1"
if [ -z "$WAN_IP" ]; then
echo "Error: Could not determine WAN IP address for device $WAN_DEVICE"
exit 1
fi
if [ -f /tmp/xray_startup_executed ]; then
sh /etc/xray/revert.sh
fi
# Create routing table and rules
ip route add local default dev lo table 100
ip rule add fwmark 1 table 100
ip route add local 0.0.0.0/0 dev lo table 100
iptables -t mangle -N XRAY
# exclude private ipv4
iptables -t mangle -A XRAY -d 255.255.255.255/32 -j RETURN
iptables -t mangle -A XRAY -d 0.0.0.0/8 -j RETURN
iptables -t mangle -A XRAY -d 10.0.0.0/8 -j RETURN
iptables -t mangle -A XRAY -d 100.64.0.0/10 -j RETURN
iptables -t mangle -A XRAY -d 127.0.0.0/8 -j RETURN
iptables -t mangle -A XRAY -d 169.254.0.0/16 -j RETURN
iptables -t mangle -A XRAY -d 172.16.0.0/12 -j RETURN
iptables -t mangle -A XRAY -d 192.0.0.0/24 -j RETURN
iptables -t mangle -A XRAY -d 192.0.2.0/24 -j RETURN
iptables -t mangle -A XRAY -d 192.168.0.0/16 -j RETURN
iptables -t mangle -A XRAY -d 198.18.0.0/15 -j RETURN
iptables -t mangle -A XRAY -d 198.51.100.0/24 -j RETURN
iptables -t mangle -A XRAY -d 203.0.113.0/24 -j RETURN
iptables -t mangle -A XRAY -d 224.0.0.0/4 -j RETURN
iptables -t mangle -A XRAY -d 240.0.0.0/4 -j RETURN
# Load nftables rules from nft.conf
nft -f /etc/xray/nft.conf
# Execute custom rules if they exist
if [ -f /etc/xray/custom_rules.sh ]; then
sh /etc/xray/custom_rules.sh
fi
# !!! PROVIDE YOUR OWN IP HERE !!!
iptables -t mangle -A XRAY -d 1.1.1.1 -j RETURN
# exclude from Xray the following:
# SAMPLE - you can test the rules using /root/fwd_manual.sh script
# traefik HTTP+HTTPS
#direct_port_range_for_ip "10.241.1.165" 80 443
# add forwarding rule
iptables -t mangle -A XRAY -p tcp -j TPROXY --on-port 61219 --tproxy-mark 1
iptables -t mangle -A XRAY -p udp -j TPROXY --on-port 61219 --tproxy-mark 1
iptables -t mangle -A PREROUTING -j XRAY
# Add rules to bypass the firewall for the WAN IP
direct_ip "$WAN_IP"
# required for check above
touch /tmp/xray_startup_executed

View File

@ -3,4 +3,4 @@
# Source the function definitions
. /etc/xray/fwd_functions.sh
direct_ip "10.241.1.3"
direct_ip "192.168.1.3"