SQM with two VPN

Excellent! Will I even be able to use 3 or 4 VPNs? I just thought that for some ip subnets I want to use a different VPN.

OK it's not so well tested, but I have generalised out now. You can pull the latest files from:

For nftables I now have variables at the beginning in which you can define the interface names and IP addresses, and also MAC addresses for IoT devices to set to bulk. If you don't want to use the MAC addresses bit then just comment out the line lower down that uses this variable.

table inet cake-dual-ifb-inet
flush table inet cake-dual-ifb-inet

table bridge cake-dual-ifb-bridge
flush table bridge cake-dual-ifb-bridge

# local interfaces to collect traffic from
# ingress traffic through forward hook is sent to ifb-ul
# egress traffic through forward hook is sent to ifb-dl
define IFACE_NAMES = {
	br-lan,
	br-guest
}

# local interface IP addresses
# traffic with these destination addresses is skipped
define IFACE_IPS = {
	192.168.1.1,
	192.168.2.1
}

# local MAC addresses to set to bulk (e.g. IoT devices)
define BULK_MACS = {
	XX,
	YY
}

table inet cake-dual-ifb-inet {

        chain hook-output {

                type filter hook output priority filter

                # OpenWrt->wan
                oifname wan mark !=3 mark set 2 goto process-openwrt-to-wan

        }

        chain hook-forward {

                type filter hook forward priority filter

                # lan->wan
                iifname $IFACE_NAMES goto process-lan-to-wan

                # wan->lan
                oifname $IFACE_NAMES goto process-wan-to-lan

        }

	chain hook-postrouting {

		type filter hook postrouting priority filter

		# fix ttl to help disguise use of router over mobile network
		# for bridge mode set ttl to 64
		# for USB tethering set ttl to 65
		
		oifname wan ip ttl set 64
	}

	chain process-openwrt-to-wan {

		jump classify-dscp
		jump store-dscp-in-conntrack
		# mark special bit '256' in conntrack for tc filtering on wan ingress
		ct mark set ct mark or 256
	}

	chain process-lan-to-wan {

		# Handle DNS hijacking (effectively resulting in openwrt-to-wan)
		tcp dport 53 mark set 2 goto process-openwrt-to-wan
		udp dport 53 mark set 2 goto process-openwrt-to-wan

	}

	chain process-wan-to-lan {

		oifname $IFACE_NAMES mark set 1
		
		# Handle DNS hijacking
		tcp sport 53 mark set 0
		udp sport 53 mark set 0

	}

	# OpenWrt->wan dscp classication by router
        chain classify-dscp {

		meta l4proto . th dport vmap @rules_proto_dport
        
	}

        map rules_proto_dport {
                type inet_proto . inet_service : verdict
                elements = {
			tcp . 53 : goto dscp_set_voice,  # DNS
                        udp . 53 : goto dscp_set_voice,  # DNS
                        tcp . 853 : goto dscp_set_voice, # DNS-over-TLS
                        udp . 853 : goto dscp_set_voice, # DNS-over-TLS
                        udp . 123 : goto dscp_set_voice  # NTP
                }
        }

        # designate packet for cake tin: bulk
        chain dscp_set_bulk {
                ip dscp set cs1
        }

        # designate packet for cake tin: besteffort
        chain dscp_set_besteffort {
                ip dscp set cs0
        }

        # designate packet for cake tin: video
        chain dscp_set_video {
                ip dscp set cs2
        }

        # designate packet for cake tin: voice
        chain dscp_set_voice {
                ip dscp set cs4
        }	

	chain store-dscp-in-conntrack {

                # store DSCPs in conntracks
                ip version 4 ct mark set (@nh,8,8 & 252) >> 2
                ip6 version 6 ct mark set (@nh,0,16 & 4032) >> 6
                ct mark set ct mark or 128
        }
}

table bridge cake-dual-ifb-bridge {

	 chain hook-input {

                type filter hook input priority filter

                meta pkttype unicast ip daddr != $IFACE_IPS goto process-lan-to-wan
        }

        chain process-lan-to-wan {

                ibrname $IFACE_NAMES mark set 1

                # Handle DNS hijacking
                tcp dport 53 mark set 0
                udp dport 53 mark set 0

                jump classify-dscp
                goto store-dscp-in-conntrack

        }

        # lan->wan dscp classication by router
	# set/override  DHCP classifications on lan-originating traffic
	chain classify-dscp {

                meta l4proto . th dport vmap @rules_proto_dport

                # IoT devices (put mac addresses here)
                ibrname $IFACE_NAMES ether saddr $BULK_MACS goto dscp_set_bulk
	}

        map rules_proto_dport {
                type inet_proto . inet_service : verdict
                elements = {
                        udp . 123 : goto dscp_set_voice # NTP
                }
        }

        # designate packet for cake tin: bulk
        chain dscp_set_bulk {
                ip dscp set cs1
        }

	# designate packet for cake tin: besteffort
	chain dscp_set_besteffort {
		ip dscp set cs0
	}

	# designate packet for cake tin: video
	chain dscp_set_video {
		ip dscp set cs2
	}
        
	# designate packet for cake tin: voice
        chain dscp_set_voice {
                ip dscp set cs4
        }
 
	chain store-dscp-in-conntrack {

                # store DSCPs in conntracks
                ip version 4 ct mark set (@nh,8,8 & 252) >> 2
                ip6 version 6 ct mark set (@nh,0,16 & 4032) >> 6
                ct mark set ct mark or 128
        }

}

And then for the init.d script:

#!/bin/sh /etc/rc.common

exec &> /var/log/cake-dual-ifb.log

START=50
STOP=4

# local interfaces to combine flows from
ifaces="br-lan br-guest"

start() 
{
	# Setup dual IFBs
	ip link add name ifb-ul type ifb
	ip link add name ifb-dl type ifb
	ip link set ifb-ul up
	ip link set ifb-dl up

	for iface in $ifaces; do 

		tc qdisc add dev $iface handle ffff: ingress
		tc qdisc add dev $iface handle 1: root prio

		# capture $if (ingress) -> wan 
		# filter on fwmark 1
		tc filter add dev $iface parent ffff: protocol ip handle 1 fw flowid 1:1 action mirred egress redirect dev ifb-ul

		# capture wan -> $iface (egress) and restore DSCPs from conntracks
		# filter on fwmark 1
		tc filter add dev $iface parent 1: protocol ip handle 1 fw flowid 1:1 action ctinfo dscp 63 128 action mirred egress redirect dev ifb-dl
	done 

	tc qdisc add dev wan handle ffff: ingress
	tc qdisc add dev wan handle 1: root prio

	# capture OpenWrt -> wan (egress)
	# filter by fwmark 2 (use nftables to skip over WireGuard traffic with fwmark 3 and apply fwmark 2 to the remainder)
	tc filter add dev wan parent 1: protocol ip handle 2 fw flowid 1:1 action mirred egress redirect dev ifb-ul

	# capture wan (ingress) -> OpenWrt and restore DSCPs from conntracks
	# filter on conntrack bit 128 set
 	tc filter add dev wan parent ffff: prio 1 protocol ip matchall action ctinfo cpmark continue
	tc filter add dev wan parent ffff: prio 2 protocol ip handle 256/256 fw flowid 1:1 action ctinfo dscp 63 128 action mirred egress redirect dev ifb-dl

	# apply CAKE on the IFBs
	tc qdisc add dev ifb-ul root cake bandwidth 30Mbit diffserv4 dual-srchost nonat wash no-ack-filter noatm overhead 92
	tc qdisc add dev ifb-dl root cake bandwidth 25Mbit diffserv4 dual-dsthost nonat nowash ingress no-ack-filter noatm overhead 92
}

stop() 
{
	for iface in $ifaces; do
	
		tc qdisc del dev $iface ingress
		tc qdisc del dev $iface root

	done

	tc qdisc del dev wan ingress
	tc qdisc del dev wan root

        tc qdisc del dev ifb-ul root
        tc qdisc del dev ifb-dl root
        ip link set ifb-ul down
        ip link del ifb-ul
        ip link set ifb-dl down
        ip link del ifb-dl
} 

@Openwrtfunboy I changed the fwmarks as follows:

  • fwmark 1 is for all local interface ingress/egress
  • fwmark 2 is for all wan ingress/egress associated with OpenWrt traffic
  • fwmark 3 is for all VPNs <-- you must set this in LuCi

Really you should take the time to understand what these do so you can test it and adapt for your particular needs.

@moeller0 you may be not so familiar with nftables but does at least the init.d script make sense to you? Now the user can set interface names in nftables variables and init.d script variables and it should generate the appropriate tc calls.

1 Like

I did not quite understand about the MAC for IoT. I have an IOT network, but it does not have access to the Internet and other VLANs, it is completely isolated, and can only communicate with the Home Assistant controller in another VLAN (made with traffic rules).

Yes then just ignore that / comment those lines out. I used that to set all my IoT devices to bulk because they're just stupid stuff anyway like sending out data relating to my home power usage.

1 Like

So im right that i should set fwmark 0x1 for all my br-lan.xx and 0x2 for wan and 0x3 for all my vpns?

No just fwmark 0x3 for VPNs in LuCi.

Hm, script will automatically add all corresponding fwmarks?
(i mean 0x1 and 0x2)

Yes - have you read it?

Consider e.g.:

        chain hook-output {

                type filter hook output priority filter

                # OpenWrt->wan
                oifname wan mark !=3 mark set 2 goto process-openwrt-to-wan

        }

That is, for all packets arriving in the output hook of nftables that does not have a mark of 3 then set the mark to 2 because it is OpenWrt traffic.

1 Like

I am very happy! Thank you! Very smart! I recently woke up, so I will try everything after the routine. Thanks again, you make my life better :slight_smile:

You should test, test and test using:

tcpdump -i ifb-dl -v

And

tcpdump -i ifb-ul -v

You shouldn't see any duplicated packets or encrypted VPN traffic and the DSCPs should get properly handled. A simple test is to ping 9.9.9.9 from a lan client and then run e.g.:

tcpdump -i ifb-ul -v host 9.9.9.9

And also you should see the CAKE diffserv4 counters incrementing properly with:

tc -s qdisc
1 Like

Any update?

Hello. I'm a little sick and not feeling well, I will definitely do everything in the coming days.

Sorry to hear that. Gesundheit!