<feed xmlns='http://www.w3.org/2005/Atom'>
<title>qosify, branch master</title>
<subtitle>OpenWrt QoS implementation based one eBPF + CAKE</subtitle>
<id>https://git.openwrt.org/project/qosify/atom?h=master</id>
<link rel='self' href='https://git.openwrt.org/project/qosify/atom?h=master'/>
<link rel='alternate' type='text/html' href='https://git.openwrt.org/project/qosify/'/>
<updated>2026-10-15T17:43:35Z</updated>
<entry>
<title>interface: accept only plain words in the tc arguments</title>
<updated>2026-10-15T17:43:35Z</updated>
<author>
<name>John Crispin</name>
</author>
<published>2026-10-15T16:00:00Z</published>
<link rel='alternate' type='text/html' href='https://git.openwrt.org/project/qosify/commit/?id=143d71d2479176ad5fdb0f9ce191affa2dc9640d'/>
<id>urn:sha1:143d71d2479176ad5fdb0f9ce191affa2dc9640d</id>
<content type='text'>
cmd_add_qdisc() appends bandwidth_up, bandwidth_down, mode and the
options to a tc command line that qosify_run_cmd() runs through sh -c.
check_str() refused only a single quote, so a value with a shell
separator ran a command as root. It now refuses every byte outside
letters, digits, '.', '_', '-' and the space that separates option
words. A refused mode keeps the default diffserv4, so the command
never carries "(null)".

Signed-off-by: John Crispin &lt;john@phrozen.org&gt;
</content>
</entry>
<entry>
<title>loader: remove the pinned programs and maps on exit</title>
<updated>2026-10-15T17:43:35Z</updated>
<author>
<name>John Crispin</name>
</author>
<published>2026-10-13T16:15:26Z</published>
<link rel='alternate' type='text/html' href='https://git.openwrt.org/project/qosify/commit/?id=d2892f00f5938c7802ac64a47afff7db5d20d93b'/>
<id>urn:sha1:d2892f00f5938c7802ac64a47afff7db5d20d93b</id>
<content type='text'>
The daemon pins its classifier programs as /sys/fs/bpf/qosify_* and its
maps below /sys/fs/bpf/qosify_data, and it removes stale pins only at the
next start. After a stop, the pinned programs keep the maps allocated:
5.9 MiB of kernel memory on a BPI-R4 with qosify disabled.

Remove the pins when the daemon leaves its main loop, after the
interfaces are torn down. The one-shot mode keeps its pins.

Signed-off-by: John Crispin &lt;john@phrozen.org&gt;
</content>
</entry>
<entry>
<title>map: accept port 65535 in port rules</title>
<updated>2026-10-14T09:57:39Z</updated>
<author>
<name>Felix Fietkau</name>
</author>
<published>2026-10-14T09:42:51Z</published>
<link rel='alternate' type='text/html' href='https://git.openwrt.org/project/qosify/commit/?id=b3ee50eff9d924a6c7a0c02b9ab1afc6056b7a00'/>
<id>urn:sha1:b3ee50eff9d924a6c7a0c02b9ab1afc6056b7a00</id>
<content type='text'>
qosify_map_set_port() rejects an end port of 65535, so rules like
tcp:65535 or udp:60000-65535 are refused. The port maps have an entry
for each of the 65536 ports, and the default value already covers port
65535.

Only reject end ports above 65535.

Signed-off-by: Felix Fietkau &lt;nbd@nbd.name&gt;
</content>
</entry>
<entry>
<title>qosify-bpf: add a macro for timeouts in seconds</title>
<updated>2026-10-14T09:42:35Z</updated>
<author>
<name>Felix Fietkau</name>
</author>
<published>2026-10-14T09:42:35Z</published>
<link rel='alternate' type='text/html' href='https://git.openwrt.org/project/qosify/commit/?id=ddd72827f252436de6e0be80867ec52b9a3ad298'/>
<id>urn:sha1:ddd72827f252436de6e0be80867ec52b9a3ad298</id>
<content type='text'>
Three timeouts convert seconds to the unit of cur_time() with the same
expression. Add a macro for the conversion. IPV4_FRAG_TIMEOUT keeps its
own value instead of FLOW_TIMEOUT, since it mirrors ipfrag_time and
only matches the flow timeout by chance.

The code of the program does not change.

Signed-off-by: Felix Fietkau &lt;nbd@nbd.name&gt;
</content>
</entry>
<entry>
<title>interface: redirect DNS replies with one flower filter</title>
<updated>2026-10-14T09:42:00Z</updated>
<author>
<name>Felix Fietkau</name>
</author>
<published>2026-10-14T09:29:32Z</published>
<link rel='alternate' type='text/html' href='https://git.openwrt.org/project/qosify/commit/?id=aa3aa6b52c63766bf4ef483286776b1e265ca5e0'/>
<id>urn:sha1:aa3aa6b52c63766bf4ef483286776b1e265ca5e0</id>
<content type='text'>
The u32 filters which redirect DNS replies to ifb-dns read the source
port at a fixed offset from the network header. They do not check the IP
protocol or the fragment offset. With a VLAN tag, tc matches the
protocol of the removed tag, and "offset plus 4" only applies to linked
hash tables. DNS replies over IPv6 with a VLAN tag, with an 802.1ad tag,
with two tags or with IPv4 options are thus not redirected. TCP packets
and fragments with 00 35 at that offset are redirected, and ifb-dns
sends them back past the ingress shaping.

Use one flower instance with protocol all, with a filter for UDP source
port 53 over IPv4 and one over IPv6. The flow dissector finds the
protocol behind all VLAN tags and the end of the IP header, and does not
read the ports of fragments. Both filters use the same mask, so each
packet needs one flow dissection. tc takes the ethertype of a flower
filter from its protocol, so add the filters over netlink.

Fragmented DNS replies are no longer redirected, since flower also does
not read the ports of a first fragment. The DNS parser saw only the
first fragment of such replies.

Fixes: 6ff06d66c36c ("dns: add code for snooping dns packets")
Fixes: 6aeeddbc91ad ("interface: extend dns filters to cover vlan tagged traffic as well")
Signed-off-by: Felix Fietkau &lt;nbd@nbd.name&gt;
</content>
</entry>
<entry>
<title>interface: factor out the setup of filter netlink messages</title>
<updated>2026-10-14T09:42:00Z</updated>
<author>
<name>Felix Fietkau</name>
</author>
<published>2026-10-14T09:28:00Z</published>
<link rel='alternate' type='text/html' href='https://git.openwrt.org/project/qosify/commit/?id=90c756581a62bd09c5c95084554d300b555bd45f'/>
<id>urn:sha1:90c756581a62bd09c5c95084554d300b555bd45f</id>
<content type='text'>
Move the setup of the tcmsg header and the filter kind out of
cmd_add_bpf_filter(), so that the DNS redirect filters can also be added
over netlink. Return an error if the message cannot be allocated.

Signed-off-by: Felix Fietkau &lt;nbd@nbd.name&gt;
</content>
</entry>
<entry>
<title>qosify-bpf: classify IPv4 fragments like their first fragment</title>
<updated>2026-10-14T09:42:00Z</updated>
<author>
<name>Nick Hainke</name>
</author>
<published>2026-10-07T14:00:00Z</published>
<link rel='alternate' type='text/html' href='https://git.openwrt.org/project/qosify/commit/?id=f840a74077f3bd351ad9a4a4f4bf4f271586fd86'/>
<id>urn:sha1:f840a74077f3bd351ad9a4a4f4bf4f271586fd86</id>
<content type='text'>
Only the first fragment of an IPv4 datagram carries the TCP or UDP
header. The payload of the other fragments was read as ports, which put
the fragments of one datagram into random classes.

Store the final DSCP value of the first fragment in an LRU map, keyed by
the addresses, ID and protocol of the datagram and the direction, and
give it to the other fragments. The map is shared by the ingress and
egress programs. A datagram forwarded without reassembly passes both,
fragment by fragment, so without the direction the egress value of a
fragment would replace the ingress value before the next fragment. In
a test through the ingress and egress hooks of one device, 20 of 30
ingress fragments got the egress value without it.

The other fragments are counted in the class and pattern statistics of
the first fragment, so the statistics count the whole datagram. They
skip the bulk and priority detection, which so works per datagram and
doesn't see a higher packet rate for fragmented traffic. After a config
reload they keep the value of their first fragment, only their class
statistics go to the class which now has its index.

The other fragments are never parsed for ports. Without an entry, for
example if they arrive before the first fragment, they get the default
value of the protocol, and IP rules still apply. Entries are used for
30 seconds, like the default IPv4 reassembly timeout of Linux, and are
replaced by the first fragment of a datagram which reuses the ID.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Nick Hainke &lt;vincent@systemli.org&gt;
Signed-off-by: Felix Fietkau &lt;nbd@nbd.name&gt;
</content>
</entry>
<entry>
<title>dns: check the length of address records</title>
<updated>2026-10-14T09:42:00Z</updated>
<author>
<name>Nick Hainke</name>
</author>
<published>2026-10-07T14:00:00Z</published>
<link rel='alternate' type='text/html' href='https://git.openwrt.org/project/qosify/commit/?id=551491cead1caff96e95e16634352597f7e48c3d'/>
<id>urn:sha1:551491cead1caff96e95e16634352597f7e48c3d</id>
<content type='text'>
The address of an A or AAAA record was copied without checking the
record length. A record shorter than the address made qosify read
beyond it and add an address made of the following bytes. Skip such
records.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Nick Hainke &lt;vincent@systemli.org&gt;
Signed-off-by: Felix Fietkau &lt;nbd@nbd.name&gt;
</content>
</entry>
<entry>
<title>qosify-bpf: only override unmarked packets with a fallback value</title>
<updated>2026-10-14T09:42:00Z</updated>
<author>
<name>Nick Hainke</name>
</author>
<published>2026-10-07T14:00:00Z</published>
<link rel='alternate' type='text/html' href='https://git.openwrt.org/project/qosify/commit/?id=c6983494fe42a156c6da85b9f73138c6bb430bd3'/>
<id>urn:sha1:c6983494fe42a156c6da85b9f73138c6bb430bd3</id>
<content type='text'>
A value prefixed with '+' should only be set if the packet has no DSCP
value yet. This did not work as documented:

- Since commit 44ce40f8e083 ("qosify: count all traffic in per-DSCP
  statistics") the fallback flag is checked after the value was masked
  and shifted, so a bit of the DSCP value decides instead of the flag.
  Values like CS6 were handled as fallback values, and values prefixed
  with '+' always overrode the packet.
- The check for an unmarked packet tested the ECN bits instead of the
  DSCP bits. For IPv6 it read the header without converting it to host
  byte order, so on little endian systems it tested the IP version and
  never marked the packet.

With the default config, DNS and NTP over IPv6 never got the voice
class.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Nick Hainke &lt;vincent@systemli.org&gt;
Signed-off-by: Felix Fietkau &lt;nbd@nbd.name&gt;
</content>
</entry>
<entry>
<title>interface: create the ifb device with one queue per receive queue</title>
<updated>2026-10-14T09:42:00Z</updated>
<author>
<name>Nick Hainke</name>
</author>
<published>2026-10-07T14:00:00Z</published>
<link rel='alternate' type='text/html' href='https://git.openwrt.org/project/qosify/commit/?id=b2f9e2818d1c1ff0623d9aa7689af9a8c3c49797'/>
<id>urn:sha1:b2f9e2818d1c1ff0623d9aa7689af9a8c3c49797</id>
<content type='text'>
With the multiqueue option, cake_mq is only used on devices with more
than one transmit queue. The ifb device for ingress was created with
'ip link add', and the ip of BusyBox can't set the number of queues, so
ingress always used cake.

Create the ifb device over netlink instead, with one queue per receive
queue of the device if multiqueue is set. Packets keep the number of
their receive queue on the ifb device, so each receive queue gets its
own cake instance.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Nick Hainke &lt;vincent@systemli.org&gt;
Signed-off-by: Felix Fietkau &lt;nbd@nbd.name&gt;
</content>
</entry>
</feed>
