The problem
The man page tells:
owner
This module attempts to match various characteristics of the
packet creator, for locally generated packets. This match is only
valid in the OUTPUT and POSTROUTING chains. Forwarded packets do
not have any socket associated with them. Packets from kernel
threads do have a socket, but usually no owner.
So there's no direct equivalent for INPUT. The reason is simple: an incoming packet doesn't come from a process, so it's not possible to check the socket owner of the process that will (very soon but has not yet) receive(d) this packet (despite the existence of a socket
match which can do such advance lookup but doesn't have any owner-related feature).
What can be done instead, is to associate a conntrack mark to the (first) time an outgoing packet of this flow is associated with an user, and mark subsequent packets, including incoming packets, from the conntrack mark. It's then possible to use other features like quota.
Blindly restores a mark from the conntrack flow to the packet: the mark if any (0 means there was none) is now available on the incoming packet.
This could have been done in mangle/INPUT instead, since the overall feature makes sense only to local traffic (so not to traffic passing through FORWARD chains), but this is easier to not care about rule ordering later and do it in mangle/PREROUTING (one can choose to optimize and move it to mangle/INPUT).
iptables -t mangle -A PREROUTING -j CONNMARK --restore-mark
(add more related rules here or in mangle/INPUT or filter/INPUT as needed that should start with -m mark --mark 1
).
Also restore the mark from the conntrack flow to the outgoing packet. Then if there's no mark, do the owner test to add a firewall mark to the packet
Chronologically that's the place where the mark will be initially set, since testing the owner of a packet can be done only in the output path.
The mark test before setting the mark is there to possibly allow with later rules to change again this mark (and ultimately the conntrack mark) without then getting it overwritten again on next output packet.
iptables -t mangle -A OUTPUT -j CONNMARK --restore-mark
iptables -t mangle -A OUTPUT -m mark --mark 0 -m owner --uid-owner root -j MARK --set-mark 1
(add more related rules here or in filter/OUTPUT as needed that should start with -m mark --mark 1
).
Save back the firewall mark on the packet to the conntrack flow mark (it will also save a mark that was changed)
This is done in POSTROUTING so there's no rule ordering to care about (it could also have been done in mangle/OUTPUT).
iptables -t mangle -A POSTROUTING -j CONNMARK --save-mark
Depending on the problem some optimization is probably possible, but this gives the idea.
With this setup, as soon as a packet in a flow gets for the first time a mark (either the first packet of an originally outgoing flow from a root process, or the first reply packet of an originally incoming flow to a root process), the flow will inherit the mark and any further packets of this flow will also inherit this mark, both ways.
Caveat: this method won't allow to account the the first packet in an incoming flow. For quota accounting that means the first payload size won't be accounted (eg: received UDP query, or of the first TCP incoming (SYN) packet (not that important, unless it's a TCP Fast Open with initial payload)).
Addressing OP's specific question
Once above framework is in place one can now check the mark in INPUT or OUTPUT to know it's about a packet to or from the root user.
INPUT
The quota2
match in (default) count down mode returns true as long as there is quota remaining: the test must be inverted with a !
, as documented:
When counting down from the initial quota, the counter will stop at 0
and the match will return false [...]
[!
] --quota
iq
Specify the initial quota for this counter. If the counter al‐ ready
exists, it is not reset. An "!" may be used to invert the result of
the match. The negation has no effect when --grow is used.
to return false and not execute the target DROP until there is no quota left anymore (reaches 0). An other way (not done here) would be to invert the logic of the actions (-j ACCEPT
followed by a single -j DROP
rule).
For OP's quota2
match to then apply in INPUT:
iptables -A INPUT -m mark --mark 1 -m quota2 --name 10mb_quota ! --quota 10000000 -j DROP
Note that it didn't make sense to reapply the same mark as in OP's example: this was already done with the previous command in mangle/PREROUTING (just as it was already done with OP's initial command iptables -t mangle -A OUTPUT -m owner --uid-owner root -j MARK --set-mark 1
, thus for no additional effect). Instead of -j DROP
one could jump to a new user chain that will handle basic traffic policing with rate limit. I think applying quota to root wouldn't be a good idea, but could make sense for an other user. Anyway that's what OP asked.
revised OUTPUT
Revised OP's quota2 to apply in OUTPUT like was done in INPUT.
iptables -A OUTPUT -m mark --mark 1 -m quota2 --name 10mb_quota ! --quota 10000000 -j DROP
As before one could instead choose rate limit once the global quota has been consumed.