DSCP Classify v2.0 - Service for applying DSCP class to connections [22.03-24.10.x]

This thread is a centralised place for discussing my nftables dscpclassify service for applying DSCP class to connections.

I will update this post with up to date information as the service is enhanced and welcome ideas and people's inputs.

Latest release

OpenWrt 22.03-24.10.x
Release notes

What is DSCP Classify? :star:

DSCP Classify is a service for applying DSCP class to connection packets (supporting OpenWrt 22.03 and above).
It can be used with SQM layer cake QoS to manage priority of client connections (VoIP/gaming/downloads/P2P etc) and reduce Bufferbloat.

The service supports both automatic and user rule classification of connections.

DSCP Classify can mark LAN destined packets with WMM mapped classes to improve transmit prioritisation with 3rd party WiFi access points and switches, see the wmm_mark_lan service configuration option.

Users of layer-cake SQM should install the layer_cake_ct SQM script for setting DSCP marks on inbound packets, see SQM Configuration:red_exclamation_mark:

User Rules :memo:

You can create rules to classify new connections in the service config file.
These use a similar syntax to the OpenWrt firewall config and can match source and destination ports, addresses, sets, firewall zones etc.

More information and examples can be found in the rules section.

Automatic Classification :magic_wand:

Connections that don't match a rule will be automatically classified by the service using one of the below methods.

Client class adoption :sparkles:

The service can automatically adopt the DSCP mark supplied by a non-WAN client.
By default this ignores classes CS6 and CS7 to avoid abuse from clients such as IoT devices.

Bulk client detection for P2P traffic :globe_showing_americas:

These connections are one of the largest causes of Bufferbloat, as a result they are classified as Low Effort (LE) by default and therefore prioritised below Best Effort (BE/DF/CS0) traffic when using the layer-cake qdisc.

High Throughput service detection for Steam downloads, cloud storage etc :articulated_lorry:

Services such as Steam make use of parralel connections to maximise download bandwith, this can also cause bufferbloat and so these connections are classified as High-Throughput (AF13) by default and prioritised as follows by cake:

  • diffserv8: prioritised below Best Effort (BE/DF/CS0) traffic and above Low Effort (LE) traffic
  • diffserv3/4: prioritised equal to Best Effort (BE/DF/CS0) traffic

Service Installation :gear:

To install dscpclassify via command line you can use the following sets of commands.

dscpclassify

repo="https://raw.githubusercontent.com/jeverley/dscpclassify/main"
mkdir -p "/etc/dscpclassify.d"
if [ ! -f "/etc/config/dscpclassify" ]; then
    wget "$repo/etc/config/dscpclassify" -O "/etc/config/dscpclassify"
else
    wget "$repo/etc/config/dscpclassify" -O "/etc/config/dscpclassify_git"
fi
wget "$repo/etc/dscpclassify.d/main.nft" -O "/etc/dscpclassify.d/main.nft"
wget "$repo/etc/dscpclassify.d/maps.nft" -O "/etc/dscpclassify.d/maps.nft"
wget "$repo/etc/dscpclassify.d/verdicts.nft" -O "/etc/dscpclassify.d/verdicts.nft"
wget "$repo/etc/hotplug.d/iface/21-dscpclassify" -O "/etc/hotplug.d/iface/21-dscpclassify"
wget "$repo/etc/init.d/dscpclassify" -O "/etc/init.d/dscpclassify"
chmod +x "/etc/init.d/dscpclassify"
/etc/init.d/dscpclassify enable
/etc/init.d/dscpclassify start

layer_cake_ct.qos

Ingress DSCP marking for SQM cake requires installation and configuration of 'layer_cake_ct.qos' and the package 'kmod-sched-ctinfo':red_exclamation_mark:

repo="https://raw.githubusercontent.com/jeverley/dscpclassify/main"
opkg update
opkg install kmod-sched-ctinfo
wget "$repo/usr/lib/sqm/layer_cake_ct.qos" -O "/usr/lib/sqm/layer_cake_ct.qos"
wget "$repo/usr/lib/sqm/layer_cake_ct.qos.help" -O "/usr/lib/sqm/layer_cake_ct.qos.help"

Configuration :gear:

The service configuration is located in '/etc/config/dscpclassify'.

A working default configuration is provided with the service which should work for most users.

Section "service"

Name Type Required Default Description
class_low_effort string no le 1 The default DSCP class applied to low effort connections
class_high_throughput string no af13 The default DSCP class applied to high-throughput connections
wmm_mark_lan boolean no 0 Mark packets going out of LAN interfaces with DSCP values respective of WMM (RFC-8325)
Advanced The below options are typically only required on non-standard setups
lan_zone list no lan Used to specify LAN firewall zones (lan/guest etc)
wan_zone list no wan Used to specify WAN firewall zones
lan_device list no Used to specify LAN network interfaces (L3 physical interface i.e. br-lan)
wan_device list no Used to specify WAN network interfaces (L3 physical interface)

1. When running on older OpenWrt releases with kernels < 5.13 the service defaults to class CS1 for low effort connections

Section "client_class_adoption"

Name Type Required Default Description
enabled boolean no 1 Adopt the DSCP class supplied by a non-WAN client
exclude_class list no cs6, cs7 Classes to ignore from client class adoption
src_ip list no Include/Exclude source IPs / sets for class adoption, preface excluded IPs with !
src_mac list no Include/Exclude source MACs / sets for class adoption, preface excluded MACs with !

Section "bulk_client_detection"

Name Type Required Default Description
enabled boolean no 1 Detect and classify bulk client connections (i.e. P2P)
class string no Override the service level class_high_throughput setting
Advanced The default configuration for the below should work for most users
min_connections number no 10 Minimum established connections for a client port to be considered as bulk
min_bytes number no 10000 Minimum bytes before a client port is classified as bulk

Section "high_throughput_service_detection"

Name Type Required Default Description
enabled boolean no 1 Detect and classify high throughput service connections (i.e. Windows Update/Steam downloads)
class string no Override the service level class_high_throughput setting
Advanced The default configuration for the below should work for most users
min_connections number no 3 Minimum established connections for a service to be considered as high-throughput
min_bytes number no 1000000 Minimum bytes before the connection is classified as high-throughput

Section "rule"

The rule sections in /etc/config/dscpclassify use the same syntax as OpenWrt's firewal, the class option is used to specified the desired DSCP.
The OpenWrt fw4 rule syntax is outlined in the OpenWrt Wiki, dscpclassify default rules can be viewed here'.

Name Type Required Default Description
enabled boolean no 1 Enable or disable rule.
class string yes The class to apply to connections matching this rule.
name string no Name of the rule.
family string no Specifies the address family (ipv4, ipv6 or any) for which the rules are generated.
proto list no Match traffic using the given protocol. Can be one (or several when using list syntax) of tcp, udp, udplite, icmp, esp, ah, sctp, or all. A protocol name from /etc/protocols is also allowed.
dest list no Specifies the traffic destination firewall zone. Refers to one of the defined zone names.
dest_ip list no Match traffic directed to the specified destination IP address, CIDR notations can be used. Set names can be specified with the @ prefix1.
dest_mac list no Match traffic directed to the specified destination MAC address. Set names can be specified with the @ prefix1.
dest_port list no Match traffic to the specified source port or port range.
src list no Specifies the traffic source firewall zone. Refers to one of the defined zone names.
src_ip list no Match traffic from the specified source IP address, CIDR notations can be used. Set names can be specified with the @ prefix1.
src_mac list no Match traffic from the specified source MAC address. Set names can be specified with the @ prefix1.
src_port list no Match traffic from the specified source port or port range.
device string no Match traffic going in/out of a particular L3 device.
direction string no Must be used in conjunction with device, specifies whether to match traffic travelling in or out.
counter boolean no 0 Enables an nft counter that counts connections matched by the rule.

1. Vervsions ≥ 2.0 allow a mix of sets, MAC, ipv4 and ipv6 addresses.

Example user rule :page_with_curl:

config rule
	option name	'DNS'
	list proto	'tcp'
	list proto	'udp'
	list dest_port	'53'
	list dest_port	'853'
	list dest_port	'5353'
	list dest_ip	'8.8.8.8'
	list dest_ip	'2001:4860:4860::8888'
	list dest_ip	'@DoH'
	list dest_ip	'@DoH6'
	option class	'cs5'
	option counter	'0'

Section "set"

The set sections in /etc/config/dscpclassify use a similar syntax to OpenWrt's firewall, they can be used in conjunction with rules to allow re-use of addresses or dynamic list population from external sources.
DSCP classify's default rules can be viewed here.

Sets must not must not contain multiple address types (i.e. IPv4 and IPv6 addresses in the same set is unsupported).
Sets should be referenced using dest_ip/src_ip/dest_mac/src_mac, with the set name name prefixed with '@'.

Name Type Required Default Description
enabled boolean no 1 Allows to disable the declaration of the set without the need to delete the section.
name string yes Name of the set.
entry list no IP/MAC address or CIDR notation.
loadfile string no A path URL on the openwrt filesystem to a file containing a list of addresses or CIDRs.
Advanced The below config options are not required by most users
comment string no A user defined comment associated with the set.
family string no Specifies the address family (ipv4, ipv6) for the set, if absent the service identifies this from the address entries1.
maxelem uint no Limits the number of entries that can be added to the set.
timeout uint no Specifies the default timeout for entries added to the set. A value of 0 enables the timeout capability flag on the set, but does not put a timeout on entries.

1. Vervsions ≥ 2.0 will attempt to autodetect a set's family if the option is not specified.

Example set and rule :page_with_curl:

config set
	option name 'ms_teams'
	list entry '13.107.64.0/18'
	list entry '52.112.0.0/14'
	list entry '52.122.0.0/15'

config set
	option name 'ms_teams6'
	option family 'ipv6'
	list entry '2603:1063::/39'

config rule
	option name 'Microsoft Teams Voice'
	option proto 'udp'
	option src_port '50000-50019'
	option dest_port '3478-3481'
	list dest_ip '@ms_teams'
	list dest_ip '@ms_teams6'
	option class 'ef'

SQM configuration :rocket:

The 'layer_cake_ct.qos' queue setup script must be selected for your wan device in SQM setup,

It is important that Ignore DSCP on ingress is Allow in SQM setup otherwise cake will ignore the service's DSCP classes.

Below is validated working SQM config for use with the service

Config parameter Value
qdisc_advanced 1
squash_dscp 0, to ensure cake does not remove ingress packet DSCP values
squash_ingress 0, to ensure cake looks at packet marks on ingress
qdisc_really_really_advanced 1
iqdisc_opts nat dual-dsthost ingress diffserv4
eqdisc_opts nat dual-srchost ack-filter diffserv4
script layer_cake_ct.qos


Service Uninstall :gear:

To remove dscpclassify via command line you can use the following commands.

/etc/init.d/dscpclassify stop
/etc/init.d/dscpclassify disable
rm -rf "/etc/dscpclassify.d"
rm -f "/etc/hotplug.d/iface/21-dscpclassify"
rm -f "/etc/init.d/dscpclassify"

Associated discussions

Acknowledgements

I'd like to thank @ldir, @amteza and @_FailSafe for their great suggestions and contributions!

Issues and Suggestions

Please raise any issues, suggestions for improvement and rule set updates in GitHub.
Github issues

If available please include the debug file /tmp/dscpclassify.debug as this will help with troubleshooting.

16 Likes

Great work!

Question: you have a WMM node in which you seem to over-write DSCPs so that intent maps to the the right (we can argue about that :wink: *) AC for each DSCP. I would argue that instead of re-writing DSCPs over and over again, we might be better of using qos_map_set to configure the desired DSCP to AC mapping for the AP and all stations instead?

*) Neither AC_VI and especially not AC_VO are without considerable side-effects on "normal" traffic in AC_BE (both by taking weighted priority in air access as well as reducing the total WiFi segment capacity by limiting aggregation duration for AC_VO), so it is arguable whether one should actually move all that much traffic to the higher classes. I know that https://www.rfc-editor.org/rfc/rfc8325 seems to endorse such usage of ACs, but I note that rfc8325 is pretty thin on studies actually showing the consequences of doing so... so IMHO rfc8325 is decent information how to map different PHBs to WiFi but one first should figure out whether one wants to give a specific PHB special treatment over WiFi in the first place.

1 Like

Fair points, I know we've discussed before too :slight_smile: I make use of it in my setup as my mesh APs are running stock firmware.
What I can do is make this off by default and then it's down to the user to decide whether they want to use it with their setup (which feels like a good compromise given the RFC's draft status).

3 Likes

Good argument! I had only considered the full OpenWrt APs case here and forgot about non-qos-map configurable APs at all.

That would be a safer default IMHO that still allows users to opt in (I am a big fan of opt-in, but also of sane defaults and here the best path is unclear to me).

great script thanks again,

I don't know yet if there is counter addition in your repository ?

to be sure and help people in sqm we have to put like your image

DO not Squash

allow

ECN (by default )

ECN

I also noticed a problem with your repository

if I take from github on your repository my rules

in the chain static class don't work all

if I take from Ldir in these cases it works all I put you in illustration the error :wink:

type or paste code here

Yes I've added the counter option now :slightly_smiling_face:

You can enable counters for a particular rule by adding the option

option counter '1'

That's correct yes: do not squash, allow.

You can leave the ECN settings at their default values.

2 Likes
  chain static_classify {
                meta l4proto { tcp, udp } th dport { 53, 853, 5353 } goto ct_set_cs2 comment "DNS"
                ip saddr 192.168.2.160 tcp dport 1935 counter packets 0 bytes 0 goto ct_set_cs3 comment "twitch1"
                ip dscp != { cs0, cs6, cs7 } iifname != { "wan" } (@nh,8,8 & 0xfc) >> 2 vmap @dscp_ct
                ip6 dscp != { cs0, cs6, cs7 } iifname != { "wan" } (@nh,0,16 & 0xfc0) >> 6 vmap @dscp_ct
                meta l4proto != { tcp, udp } goto ct_set_cs0
                ct mark set ct mark & 0xffffff80 | 0x00000080

my rules for consoles game ps5 doesn't appair so if i take to Ldir he appair i don't know if you undesrtand ?

config rule
        option name 'PS5udp'
        list proto 'udp'
        list src_ip '192.168.2.160'
	list dest_port '!80'
	list dest_port '!443'
	option class 'cs4'
        option family 'ipv4'
	option counter '1'

i has just dns and twitch1

1 Like

Ahh yes I see you're making use of the negative matching present in the latest commit from @amteza in @ldir's fork.
I'm looking to merge that functionality into the main branch in a couple of hours, will update back here once it's ready. :slightly_smiling_face:

4 Likes

ok great so I could take from your repository

on the hand or developped ??

the most recent I could see in terms of time

and thanks for the ECN

so
ECN (by default) the first one
and NOECN (by default) the second one :slight_smile:

and not "ECN" the second

cake will ignore the ECN setting in the GUI and always use ECN if the packets have either ECT(0) or ECT(1) set. (Or cake is already in emergency drop mode or the respective flow triggered cake's BLUE mode which IIRC drops hard).

2 Likes

It's not committed to develop yet.

2 Likes

That’s what I also suggested in my thread as new users could get confused if their static marks are overwritten when wmm is enabled.

One thing that always comes to my mind when i think about dscpclassify is: do you inted to make a luci app for dscpclassify? Or could it maybe be integrated into the luci firewall?

Setting dscp through luci firewall would probably already work with your script. I have to try that…

2 Likes

DSCPCLASSIFY PARAMETER

yes it would be a great idea if by luci we could put it as hudra suggests but I think the work would be long?
if we could integrate our dscpclassify parameters like in my thread

it would be christmas for the first time in such a short time :sweat_smile:

yes exactly :wink:

I will wait for your last merge to update the script,

@Hudra yes I also have the same little problem as you the counter seems to be blocked especially for twitch1

 ip saddr 192.168.2.160 tcp dport 1935 counter packets 1 bytes 60 goto ct_set_cs3 comment "twitch1"

Actually, I don’t have any problem, maybe you misunderstood something:

In this post I just wanted to make clear that I think it would be better to disable the option wmm by default. But I was too slow as @yelreve already fixed it in his repository.

If you are referring to my thread where I asked why my counters are not increasing by much when the option “counter” is set here is your answer:

So, if you have 1 packet in your counter it probably works and you won’t ever see counters increasing for every packet. Only for every flow… at least unless there is an option to use counters from conntrack but I don’t know if this is possible.

3 Likes

I've updated the main branch now with the negative matching ability, I took it one step further to allow mixing both negative and positive matches on a single rule (though probably a fairly niche use case).

@anon78773196 I think your config in thread Sharing dscpclassify configuration on OpenWrt should now work as expected with the main code branch. :slight_smile:

It also includes the auto-merge tweak for sets with intervals.

@Hudra you're correct, the rule counters only count matched connections (not every packet in the connection).

7 Likes

may i ask what negative matching mean ??

It allows you to write a rule which would match everything except the entries you've specified with an '!'.
For example the below would match all tcp/udp connections that did not have a destination port of 443 or 80.

config rule
	option name 'Not HTTP'
	list proto		'tcp'
	list proto		'udp'
	list dest_port	'!443'
	list dest_port	'!80'
	option class 'cs4'
3 Likes

is it the same as writing nowash in iqdisc_opts and eqdisc_opts right ??
what if i only wash on egress ?

In SQM setup those two parameters correspond to the following options

Squash DSCP on inbound packets (ingress) = squash_dscp
Ignore DSCP on ingress = squash_ingress

You're correct, setting squash_dscp to 0 (allow) is equivalent to nowash in iqdisc_opts.

Enabling squash/wash has the effect of making cake overwrite the DSCP to 0 after passing through the tc qdisc, however DSCP classify would re-apply it from the conntrack for lan bound packets.

squash_ingress doesn't seem to actually do anything in my testing this morning. *Edit: per @moeller0's note below this is because I'm specifying a diffserv in my qdisc_opts.

Washing on WAN egress via eqdisc_opts would make sure your DSCPs are all set to CS0 when sent to your ISP after passing through the qdisc - that could be beneficial depending on how your ISP handles different DSCP marks.

4 Likes