I believe what you're seeing is both a flaw (more cosmetic) and an advantage of QoSmate at the same time.
Here's a more technical explanation:
QoSmate creates nftables rules that apply to both directions (ingress and egress), and packets traverse these rules. However, the rules are actually only relevant during egress, because that's when DSCP values are written to conntrack and later restored via tc-ctinfo on ingress. This is necessary because tc processes packets before nftables rules on ingress, so we can't control DSCP markings via nftables rules on ingress.
At the very beginning of these rules (when using HFSC or hybrid), the system checks if ingress washing is enabled. If yes, the following rules are inserted into the .nft include file and set all DSCP values to CS0:
$(if { [ "$ROOT_QDISC" = "hfsc" ] || [ "$ROOT_QDISC" = "hybrid" ]; } && [ "$WASHDSCPDOWN" -eq 1 ]; then
echo "# wash all the DSCP on ingress ... "
echo " counter jump mark_cs0"
fi
)
This has the advantage that if your ISP sends DSCP markings to you, they would be removed before reaching your LAN devices. However, it also means that QoSmate doesn't honor DSCP markings from your LAN devices on egress if they were to make DSCP markings themselves. I find this to be an acceptable tradeoff since you can mark connections with QoSmate anyway.
Further down in the rules, all DSCP values are assigned to the correct classes for tc with this command:
## classify for the HFSC queues:
meta priority set ip dscp map @priomap counter
meta priority set ip6 dscp map @priomap counter
After that, the DSCP values are written to conntrack with this command:
# Store DSCP in conntrack for restoration on ingress
ct mark set ip dscp or 128 counter
ct mark set ip6 dscp or 128 counter
Then the DSCP values are washed again (if egress washing is enabled), which means the DSCP markings are not passed on to your ISP - which in most cases is desired behavior and also best practice, because you usually don't know what the ISP does with DSCP markings.
When the packets return (ingress), the DSCP values are restored from conntrack as mentioned and assigned to the correct classes via tc. The packets have already been correctly classified and everything works properly. However, the packet then goes through the nftables rules again, and since source and destination are swapped in certain constellations, the rules don't match. At the end of the rules, the DSCP values are written to conntrack again. In the connections tab, we read the connections from conntrack every second, and because the last action is writing CS0 to conntrack, it appears as if the connection has CS0, but in reality everything was correctly classified and assigned beforehand. If you configured the connections tab to read every 10ms or so, you would probably see the DSCP values jumping between EF and CS0. But as I said, everything should be correctly assigned beforehand, and this is more of a cosmetic flaw.
You can easily test this by simply disabling ingress washing - then EF should correctly remain in conntrack because it won't be subsequently set to CS0 during actual ingress.
Alternatively, if you want EF to permanently remain in conntrack, you can simply create both rules (one with source port 3074 and one with destination port 3074) and then you shouldn't see this phenomenon anymore.
This way, the traffic gets correctly marked in both directions regardless of how source/destination appear in the different packet flow stages.
I hope this helps you better understand the behavior.