Initial import of qos-profiles
Role subpackages set the system-wide default qdisc and TCP congestion control via /usr/lib/sysctl.d, without pulling in the base package: server fq + bbr bare metal, VM guests vmhost fq_codel + bbr hypervisors, container hosts router fq_codel + cubic desktop fq_codel + cubic The base package applies cake per interface from /etc/qos-profiles/<iface>.conf, layered over role defaults and /etc/qos-profiles/defaults.conf: - LINK_RATE with per-direction overrides, shaped to SHAPE_PERCENT (default 95, per direction overridable) - outbound fairness per source host and diffserv4; inbound via ifb, fairness per destination host, besteffort with DSCP wash - NAT awareness, extra cake args (shared and per direction), raw qdisc overrides - config parsed without executing it, validated before any change, previous state undone on each run Applied by the qos-profiles@<iface> unit (bound to the device) or the qos-profiles-networkmanager dispatcher subpackage, installed automatically on hosts with NetworkManager. Includes ready-made examples for VM hosts, OVH dual-NIC servers, DC and home routers.
This commit is contained in:
commit
86a0d2cdd1
26 files changed
+672
No files matched your search
Executable
+16
@@ -0,0 +1,16 @@
|
|||||||
|
#!/usr/bin/bash
|
||||||
|
# NetworkManager dispatcher: apply qos-profiles when an interface with
|
||||||
|
# /etc/qos-profiles/<iface>.conf comes up, clean up when it is removed.
|
||||||
|
# Both the device and its IP interface are checked (eth0 and ppp0 for PPPoE).
|
||||||
|
|
||||||
|
action=$2
|
||||||
|
ifaces=("$1")
|
||||||
|
[[ -n ${DEVICE_IP_IFACE:-} && $DEVICE_IP_IFACE != "$1" ]] && ifaces+=("$DEVICE_IP_IFACE")
|
||||||
|
|
||||||
|
for iface in "${ifaces[@]}"; do
|
||||||
|
[[ -n $iface && -f /etc/qos-profiles/$iface.conf ]] || continue
|
||||||
|
case $action in
|
||||||
|
up | reapply) /usr/libexec/qos-profiles/qos-apply start "$iface" ;;
|
||||||
|
down) [[ -e /sys/class/net/$iface ]] || /usr/libexec/qos-profiles/qos-apply stop "$iface" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# qos-profiles-desktop: workstation. Same as the Fedora defaults, pinned explicitly.
|
||||||
|
-net.core.default_qdisc = fq_codel
|
||||||
|
-net.ipv4.tcp_congestion_control = cubic
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# qos-profiles-router: forwarding router.
|
||||||
|
# fq_codel on all interfaces; shape the WAN with cake in /etc/qos-profiles/<iface>.conf.
|
||||||
|
-net.core.default_qdisc = fq_codel
|
||||||
|
-net.ipv4.tcp_congestion_control = cubic
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# qos-profiles-server: bare-metal server or VM guest.
|
||||||
|
# Traffic is mostly locally generated TCP: fq paces per socket, BBR uses the pacing.
|
||||||
|
-net.core.default_qdisc = fq
|
||||||
|
-net.ipv4.tcp_congestion_control = bbr
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# qos-profiles-vmhost: hypervisor / container host.
|
||||||
|
# Traffic is mostly forwarded from guests: fq_codel keeps queue delay low for it.
|
||||||
|
# BBR still serves the host's own TCP and rootful containers.
|
||||||
|
# Per-guest fairness on the uplink: /etc/qos-profiles/<iface>.conf with cake.
|
||||||
|
-net.core.default_qdisc = fq_codel
|
||||||
|
-net.ipv4.tcp_congestion_control = bbr
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
# qos-profiles
|
||||||
|
|
||||||
|
Default qdisc and TCP congestion control per machine role, plus optional
|
||||||
|
per-interface qdiscs (cake, with ifb for inbound traffic).
|
||||||
|
|
||||||
|
## Roles
|
||||||
|
|
||||||
|
Install exactly one role subpackage:
|
||||||
|
|
||||||
|
| Subpackage | default_qdisc | congestion control | cake NAT | Use for |
|
||||||
|
| ---------------------- | ------------- | ------------------ | -------- | -------------------------------------------- |
|
||||||
|
| `qos-profiles-server` | fq | bbr | no | bare-metal servers without guests, VM guests |
|
||||||
|
| `qos-profiles-vmhost` | fq_codel | bbr | no | hypervisors, container hosts |
|
||||||
|
| `qos-profiles-router` | fq_codel | cubic | yes | routers |
|
||||||
|
| `qos-profiles-desktop` | fq_codel | cubic | no | workstations |
|
||||||
|
|
||||||
|
The sysctl file is `/usr/lib/sysctl.d/60-qos-profiles-<role>.conf`; override it
|
||||||
|
in `/etc/sysctl.d/`. `default_qdisc` only applies to interfaces created after
|
||||||
|
it is set, so reboot after installing or switching roles. Congestion control
|
||||||
|
is system-wide (or per route), not per interface.
|
||||||
|
|
||||||
|
## Per-interface qdiscs
|
||||||
|
|
||||||
|
Needs the `qos-profiles` package; the role packages don't pull it in.
|
||||||
|
Create `/etc/qos-profiles/<iface>.conf`, see
|
||||||
|
`/usr/share/qos-profiles/example.conf` for all variables and
|
||||||
|
`/usr/share/qos-profiles/examples/` for ready-made setups:
|
||||||
|
|
||||||
|
| Example | Setup |
|
||||||
|
| --------------------------- | -------------------------------------------------- |
|
||||||
|
| `defaults.conf` | site-wide `/etc/qos-profiles/defaults.conf` |
|
||||||
|
| `vmhost-routed-uplink.conf` | VM host, guests routed via the host uplink (EVPN) |
|
||||||
|
| `vmhost-egress-only.conf` | VM host, outbound fairness only at NIC line rate |
|
||||||
|
| `vmhost-nat-guests.conf` | VM/container host with masqueraded guests |
|
||||||
|
| `ovh-public.conf` | dual-NIC dedicated server, public NIC |
|
||||||
|
| `ovh-vrack-private.conf` | private NIC capped by the provider, outbound only |
|
||||||
|
| `dc-router-transit.conf` | datacenter router, transit uplink, no NAT |
|
||||||
|
| `router-ftth.conf` | fibre with Ethernet handoff, NAT |
|
||||||
|
| `router-vdsl-pppoe.conf` | VDSL2 with PPPoE on `ppp0`, NAT, ACK filtering |
|
||||||
|
| `router-docsis.conf` | cable, NAT, ACK filtering |
|
||||||
|
| `backup-nic-raw.conf` | raw `fq` qdisc with a per-flow cap instead of cake |
|
||||||
|
|
||||||
|
Minimal example:
|
||||||
|
|
||||||
|
```
|
||||||
|
LINK_RATE=1gbit
|
||||||
|
```
|
||||||
|
|
||||||
|
This shapes both directions to 95% of the link rate with cake. Outbound,
|
||||||
|
traffic is shared per source host and DiffServ classes are applied
|
||||||
|
(`diffserv4`). Inbound, traffic is shared per destination host, DSCP marks
|
||||||
|
are ignored (`besteffort`) and cleared (`wash`), since they come from the
|
||||||
|
internet. `EGRESS_SHAPE_PERCENT` and `INGRESS_SHAPE_PERCENT` set the
|
||||||
|
percentage per direction.
|
||||||
|
|
||||||
|
Configuration is read in order, later overriding earlier:
|
||||||
|
|
||||||
|
1. `/usr/lib/qos-profiles/role-<role>.conf` — installed role package
|
||||||
|
2. `/etc/qos-profiles/defaults.conf` — site-wide
|
||||||
|
3. `/etc/qos-profiles/<iface>.conf` — the interface
|
||||||
|
|
||||||
|
It is applied by either:
|
||||||
|
|
||||||
|
- `qos-profiles-networkmanager` (installed automatically with NetworkManager)
|
||||||
|
when NetworkManager brings the interface up, or
|
||||||
|
- `systemctl enable --now qos-profiles@<iface>` for interfaces NetworkManager
|
||||||
|
doesn't manage. It runs whenever the device appears.
|
||||||
|
|
||||||
|
Both may be active; they're serialised and idempotent. To apply changes run
|
||||||
|
`systemctl restart qos-profiles@<iface>` or
|
||||||
|
`/usr/libexec/qos-profiles/qos-apply start <iface>`. Stopping restores the
|
||||||
|
default qdisc and removes the ifb device.
|
||||||
|
|
||||||
|
Inbound shaping replaces any existing `clsact` qdisc on the interface (and its
|
||||||
|
tc/BPF filters).
|
||||||
|
|
||||||
|
## DiffServ
|
||||||
|
|
||||||
|
`diffserv4` sorts outbound packets by DSCP into bulk (CS1), best effort, video
|
||||||
|
(AF4x, CS3, ...) and voice (EF, CS6, ...). Higher classes get priority only up
|
||||||
|
to a share of the rate, so they can't starve the others. Marking backup or
|
||||||
|
replication traffic CS1 (e.g. nftables `ip dscp set cs1`) makes it yield to
|
||||||
|
everything else.
|
||||||
|
|
||||||
|
## Topologies
|
||||||
|
|
||||||
|
- **VM host, VMs routed via the host uplink (EVPN anycast gateway):** cake on
|
||||||
|
the uplink NIC gives each guest an equal share. VXLAN-encapsulated traffic on
|
||||||
|
the same NIC is seen by its outer header (the VTEP address), so it shares as
|
||||||
|
a single host.
|
||||||
|
- **VM host, VMs bridged to a switch, routing elsewhere:** the WAN bottleneck
|
||||||
|
is at the router; put cake on the router WAN (`qos-profiles-router`). The
|
||||||
|
host only needs the role package.
|
||||||
|
- **Host with separate public and private NICs:** configure the public NIC only.
|
||||||
|
- **Bond or VLAN uplink:** these devices have no queue of their own; put cake
|
||||||
|
on the physical NIC, or set a rate explicitly.
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Dedicated backup/storage NIC with a raw qdisc instead of cake.
|
||||||
|
# Install as: /etc/qos-profiles/<backup nic>.conf
|
||||||
|
#
|
||||||
|
# fq with a per-flow cap: a single large transfer can't fill the link, while
|
||||||
|
# many parallel flows still can. EGRESS replaces everything cake-related for
|
||||||
|
# that direction; inbound is left alone.
|
||||||
|
|
||||||
|
EGRESS="fq maxrate 500mbit"
|
||||||
|
INGRESS_RATE=none
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# Datacenter router, transit uplink. Hosts behind it have public addresses
|
||||||
|
# (no NAT); VM hosts are on L2 behind this router and need no shaping.
|
||||||
|
# Install as: /etc/qos-profiles/<transit>.conf (role: qos-profiles-router)
|
||||||
|
#
|
||||||
|
# Fair share per server in both directions. Cake uses a single queue: fine at
|
||||||
|
# 1 Gbit/s, check CPU load before using it above 10 Gbit/s.
|
||||||
|
|
||||||
|
LINK_RATE=1gbit
|
||||||
|
NAT=no
|
||||||
|
CAKE_ARGS="ethernet"
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Site-wide defaults, for all configured interfaces.
|
||||||
|
# Install as: /etc/qos-profiles/defaults.conf
|
||||||
|
#
|
||||||
|
# Only overrides what differs from the role package defaults; each
|
||||||
|
# /etc/qos-profiles/<iface>.conf can still override these.
|
||||||
|
|
||||||
|
# Leave more headroom inbound, where shaping is less precise.
|
||||||
|
INGRESS_SHAPE_PERCENT=90
|
||||||
|
|
||||||
|
# All our uplinks are plain Ethernet.
|
||||||
|
CAKE_ARGS="ethernet"
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# Dedicated server with separate public and private NICs (e.g. OVH), public
|
||||||
|
# side. Configure only the public NIC; the private one keeps the default qdisc.
|
||||||
|
# Install as: /etc/qos-profiles/<public nic>.conf
|
||||||
|
#
|
||||||
|
# Use the bandwidth the provider guarantees, which can be lower than the NIC
|
||||||
|
# speed (e.g. 1 Gbit/s NIC with 500 Mbit/s public bandwidth).
|
||||||
|
|
||||||
|
LINK_RATE=500mbit
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Private network NIC capped by the provider below the NIC speed (e.g. OVH
|
||||||
|
# vRack limited to 2 Gbit/s on a 10 Gbit/s NIC), used for replication and
|
||||||
|
# backups between our own hosts.
|
||||||
|
# Install as: /etc/qos-profiles/<private nic>.conf
|
||||||
|
#
|
||||||
|
# Shape outbound only, so the queue stays here instead of the provider
|
||||||
|
# dropping at its policer. Traffic is our own, so DiffServ marks are trusted:
|
||||||
|
# mark bulk transfers CS1 to let them yield.
|
||||||
|
|
||||||
|
EGRESS_RATE=2gbit
|
||||||
|
INGRESS_RATE=none
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Router on cable (DOCSIS), asymmetric, NAT.
|
||||||
|
# Install as: /etc/qos-profiles/<wan>.conf (role: qos-profiles-router)
|
||||||
|
#
|
||||||
|
# Cable ISPs usually provision a bit above the advertised rate; start with the
|
||||||
|
# advertised rates and lower SHAPE_PERCENT if latency still rises under load.
|
||||||
|
|
||||||
|
EGRESS_RATE=50mbit
|
||||||
|
INGRESS_RATE=500mbit
|
||||||
|
INGRESS_SHAPE_PERCENT=90
|
||||||
|
CAKE_ARGS="docsis"
|
||||||
|
EGRESS_CAKE_ARGS="ack-filter"
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Office/home router on fibre (FTTH/GPON) with an Ethernet handoff and NAT.
|
||||||
|
# Install as: /etc/qos-profiles/<wan>.conf (role: qos-profiles-router)
|
||||||
|
#
|
||||||
|
# Use the subscribed rates. If the ISP tags the WAN with a VLAN, put this on
|
||||||
|
# the physical NIC and add "ether-vlan".
|
||||||
|
|
||||||
|
EGRESS_RATE=250mbit
|
||||||
|
INGRESS_RATE=500mbit
|
||||||
|
CAKE_ARGS="ethernet"
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# Router on VDSL2 with PPPoE, asymmetric, NAT.
|
||||||
|
# Install as: /etc/qos-profiles/ppp0.conf (role: qos-profiles-router)
|
||||||
|
#
|
||||||
|
# Configure the PPPoE interface (ppp0), not the Ethernet below it. Use the
|
||||||
|
# DSL sync rates from the modem, they're higher than the usable rate; the
|
||||||
|
# pppoe-ptm overhead accounts for the rest.
|
||||||
|
# ack-filter drops redundant TCP ACKs on the slow upstream.
|
||||||
|
|
||||||
|
EGRESS_RATE=20mbit
|
||||||
|
INGRESS_RATE=100mbit
|
||||||
|
INGRESS_SHAPE_PERCENT=90
|
||||||
|
CAKE_ARGS="pppoe-ptm"
|
||||||
|
EGRESS_CAKE_ARGS="ack-filter"
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# VM host where only outbound fairness matters and the NIC line rate is the
|
||||||
|
# real limit (provider delivers the full NIC speed).
|
||||||
|
# Install as: /etc/qos-profiles/<uplink>.conf (role: qos-profiles-vmhost)
|
||||||
|
#
|
||||||
|
# Cake runs unlimited: no shaping cost, the NIC's byte queue limits keep the
|
||||||
|
# queue in cake. Inbound is left alone.
|
||||||
|
|
||||||
|
EGRESS_RATE=unlimited
|
||||||
|
INGRESS_RATE=none
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# VM or container host with masqueraded guests (libvirt NAT network, rootful
|
||||||
|
# containers on a NAT bridge).
|
||||||
|
# Install as: /etc/qos-profiles/<uplink>.conf (role: qos-profiles-vmhost)
|
||||||
|
#
|
||||||
|
# With NAT=yes cake looks up the conntrack entry and shares per internal
|
||||||
|
# guest address instead of the single public one.
|
||||||
|
|
||||||
|
LINK_RATE=1gbit
|
||||||
|
NAT=yes
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# VM host, guests routed through the host uplink (EVPN, anycast gateway on
|
||||||
|
# the host). Fair share per guest in both directions.
|
||||||
|
# Install as: /etc/qos-profiles/<uplink>.conf (role: qos-profiles-vmhost)
|
||||||
|
#
|
||||||
|
# Outbound is shared per guest source IP, inbound per guest destination IP.
|
||||||
|
# VXLAN traffic between hosts on the same NIC shares as a single host (cake
|
||||||
|
# only sees the outer VTEP addresses).
|
||||||
|
|
||||||
|
LINK_RATE=1gbit
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
# qos-profiles interface configuration, all variables. Ready-made setups are
|
||||||
|
# in /usr/share/qos-profiles/examples/.
|
||||||
|
# Copy to /etc/qos-profiles/<iface>.conf. Applied by the NetworkManager
|
||||||
|
# dispatcher when the interface comes up, or without NetworkManager by:
|
||||||
|
# systemctl enable --now qos-profiles@<iface>
|
||||||
|
#
|
||||||
|
# Defaults come from the installed role package (/usr/lib/qos-profiles/) and
|
||||||
|
# /etc/qos-profiles/defaults.conf. Use a physical NIC: bond and VLAN devices
|
||||||
|
# have no queue of their own.
|
||||||
|
|
||||||
|
# Full link rate in both directions (bit, kbit, mbit, gbit, tbit).
|
||||||
|
# Cake shapes to SHAPE_PERCENT of it.
|
||||||
|
#LINK_RATE=1gbit
|
||||||
|
|
||||||
|
# Per-direction link rates, override LINK_RATE.
|
||||||
|
# EGRESS_RATE: "unlimited" = cake without shaping (default without LINK_RATE,
|
||||||
|
# fine while the NIC line rate is the bottleneck), "none" = off
|
||||||
|
# INGRESS_RATE: "none" = no inbound shaping (default without LINK_RATE)
|
||||||
|
#EGRESS_RATE=100mbit
|
||||||
|
#INGRESS_RATE=none
|
||||||
|
|
||||||
|
# Percentage of the link rate to shape to, so the queue builds here and not
|
||||||
|
# in the upstream equipment. Per-direction values override SHAPE_PERCENT.
|
||||||
|
#SHAPE_PERCENT=95
|
||||||
|
#EGRESS_SHAPE_PERCENT=95
|
||||||
|
#INGRESS_SHAPE_PERCENT=90
|
||||||
|
|
||||||
|
# DiffServ classes: besteffort, diffserv3, diffserv4, diffserv8, precedence.
|
||||||
|
# Inbound marks come from the internet, so they're ignored by default and
|
||||||
|
# cleared before forwarding (INGRESS_WASH=yes).
|
||||||
|
#DIFFSERV=diffserv4
|
||||||
|
#INGRESS_DIFFSERV=besteffort
|
||||||
|
#INGRESS_WASH=yes
|
||||||
|
|
||||||
|
# Fairness per host behind NAT on this machine (yes/no).
|
||||||
|
#NAT=no
|
||||||
|
|
||||||
|
# Extra cake options for both directions: link overhead (ethernet, pppoe-ptm,
|
||||||
|
# docsis, ...), rtt, or a different fairness mode (flows, triple-isolate, ...).
|
||||||
|
# Per-direction options are added after CAKE_ARGS.
|
||||||
|
#CAKE_ARGS="ethernet"
|
||||||
|
#EGRESS_CAKE_ARGS="ack-filter"
|
||||||
|
#INGRESS_CAKE_ARGS=""
|
||||||
|
|
||||||
|
# ifb device for inbound shaping, default ifb-<iface> (max 15 characters).
|
||||||
|
#IFB=ifb-wan
|
||||||
|
|
||||||
|
# Raw qdisc specs; when set, replace everything above for that direction.
|
||||||
|
#EGRESS="fq_codel target 3ms"
|
||||||
|
#INGRESS="cake besteffort dual-dsthost ingress bandwidth 450mbit"
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
#!/usr/bin/bash
|
||||||
|
# Apply or remove per-interface qdiscs (cake, with ifb for inbound shaping).
|
||||||
|
#
|
||||||
|
# Configuration is read, later files overriding earlier ones, from:
|
||||||
|
# /usr/lib/qos-profiles/role-*.conf defaults of the installed role package
|
||||||
|
# /etc/qos-profiles/defaults.conf site-wide defaults
|
||||||
|
# /etc/qos-profiles/<iface>.conf the interface
|
||||||
|
# See /usr/share/doc/qos-profiles/README.md for the variables.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
shopt -s nullglob
|
||||||
|
|
||||||
|
KEYS=(LINK_RATE EGRESS_RATE INGRESS_RATE
|
||||||
|
SHAPE_PERCENT EGRESS_SHAPE_PERCENT INGRESS_SHAPE_PERCENT
|
||||||
|
DIFFSERV INGRESS_DIFFSERV INGRESS_WASH NAT
|
||||||
|
CAKE_ARGS EGRESS_CAKE_ARGS INGRESS_CAKE_ARGS IFB EGRESS INGRESS)
|
||||||
|
CONF_DIR=/etc/qos-profiles
|
||||||
|
ROLE_DIR=/usr/lib/qos-profiles
|
||||||
|
STATE_DIR=/run/qos-profiles
|
||||||
|
|
||||||
|
die() {
|
||||||
|
echo "${0##*/}: $*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Parse KEY=value lines without executing them. Values may be "quoted",
|
||||||
|
# 'quoted' or bare; a trailing # comment is allowed.
|
||||||
|
load() {
|
||||||
|
local file=$1 line key val n=0
|
||||||
|
|
||||||
|
while IFS= read -r line || [[ -n $line ]]; do
|
||||||
|
n=$((n + 1))
|
||||||
|
[[ $line =~ ^[[:space:]]*(#|$) ]] && continue
|
||||||
|
[[ $line =~ ^[[:space:]]*([A-Z_]+)=(.*)$ ]] || die "$file:$n: invalid line"
|
||||||
|
key=${BASH_REMATCH[1]}
|
||||||
|
val=${BASH_REMATCH[2]}
|
||||||
|
[[ " ${KEYS[*]} " == *" $key "* ]] || die "$file:$n: unknown variable $key"
|
||||||
|
if [[ $val =~ ^\"([^\"]*)\"[[:space:]]*(#.*)?$ || $val =~ ^\'([^\']*)\'[[:space:]]*(#.*)?$ ]]; then
|
||||||
|
val=${BASH_REMATCH[1]}
|
||||||
|
elif [[ $val =~ ^([^[:space:]#\"\']*)[[:space:]]*(#.*)?$ ]]; then
|
||||||
|
val=${BASH_REMATCH[1]}
|
||||||
|
else
|
||||||
|
die "$file:$n: invalid value for $key"
|
||||||
|
fi
|
||||||
|
printf -v "$key" '%s' "$val"
|
||||||
|
done <"$file"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Usage: yesno <variable> <keyword if yes> <keyword if no>
|
||||||
|
yesno() {
|
||||||
|
case ${!1,,} in
|
||||||
|
yes) echo "$2" ;;
|
||||||
|
no) echo "$3" ;;
|
||||||
|
*) die "$1 must be yes or no" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# Usage: percent <variable>
|
||||||
|
percent() {
|
||||||
|
[[ ${!1} =~ ^[0-9]+$ ]] && ((${!1} >= 1 && ${!1} <= 100)) || die "$1 must be 1-100"
|
||||||
|
echo "${!1}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Usage: shaped_rate <link rate> <percent>
|
||||||
|
# Link rate (e.g. 1gbit, 2.5Gbit, 500mbit) -> percent of it in kbit.
|
||||||
|
shaped_rate() {
|
||||||
|
awk -v r="${1,,}" -v p="$2" 'BEGIN {
|
||||||
|
n = r; sub(/[a-z]+$/, "", n); u = substr(r, length(n) + 1)
|
||||||
|
m["bit"] = 1; m[""] = 1; m["kbit"] = 1e3; m["mbit"] = 1e6; m["gbit"] = 1e9; m["tbit"] = 1e12
|
||||||
|
if (n !~ /^[0-9]+(\.[0-9]+)?$/ || !(u in m) || n == 0) exit 1
|
||||||
|
printf "%dkbit\n", n * m[u] * p / 100 / 1000
|
||||||
|
}' || die "invalid rate: $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Build the qdisc for one direction into the named array.
|
||||||
|
# Usage: build <array> <raw spec> <link rate> <percent> <cake args> <diffserv> <isolation> [extra...]
|
||||||
|
build() {
|
||||||
|
local -n spec=$1
|
||||||
|
local raw=$2 rate=$3 pct=$4 args=$5 diffserv=$6 isolation=$7 bw extra
|
||||||
|
shift 7
|
||||||
|
|
||||||
|
spec=()
|
||||||
|
if [[ -n $raw ]]; then
|
||||||
|
read -ra spec <<<"$raw"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
[[ -n $rate && $rate != none ]] || return 0
|
||||||
|
spec=(cake "$diffserv" "$isolation" "$@")
|
||||||
|
if [[ $rate == unlimited ]]; then
|
||||||
|
spec+=(unlimited)
|
||||||
|
else
|
||||||
|
bw=$(shaped_rate "$rate" "$pct")
|
||||||
|
spec+=(bandwidth "$bw")
|
||||||
|
fi
|
||||||
|
read -ra extra <<<"$CAKE_ARGS $args"
|
||||||
|
spec+=("${extra[@]}")
|
||||||
|
}
|
||||||
|
|
||||||
|
# Undo whatever the last start applied, as recorded in the state file.
|
||||||
|
cleanup() {
|
||||||
|
local state=$STATE_DIR/$dev key val
|
||||||
|
|
||||||
|
[[ -f $state ]] || return 0
|
||||||
|
while IFS='=' read -r key val; do
|
||||||
|
case $key in
|
||||||
|
egress) tc qdisc del dev "$dev" root 2>/dev/null || true ;;
|
||||||
|
ifb)
|
||||||
|
tc qdisc del dev "$dev" clsact 2>/dev/null || true
|
||||||
|
ip link del dev "$val" 2>/dev/null || true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done <"$state"
|
||||||
|
rm -f "$state"
|
||||||
|
}
|
||||||
|
|
||||||
|
start() {
|
||||||
|
local -a egress ingress
|
||||||
|
local state=$STATE_DIR/$dev ifb egress_pct ingress_pct wash nat
|
||||||
|
|
||||||
|
[[ -d /sys/class/net/$dev ]] || die "no such interface: $dev"
|
||||||
|
: "${EGRESS_SHAPE_PERCENT:=$SHAPE_PERCENT}" "${INGRESS_SHAPE_PERCENT:=$SHAPE_PERCENT}"
|
||||||
|
egress_pct=$(percent EGRESS_SHAPE_PERCENT)
|
||||||
|
ingress_pct=$(percent INGRESS_SHAPE_PERCENT)
|
||||||
|
wash=$(yesno INGRESS_WASH wash nowash)
|
||||||
|
nat=$(yesno NAT nat nonat)
|
||||||
|
|
||||||
|
# Egress without any rate runs cake unlimited (the NIC is the bottleneck);
|
||||||
|
# ingress without a rate is not shaped, cake needs one there.
|
||||||
|
build egress "$EGRESS" "${EGRESS_RATE:-${LINK_RATE:-unlimited}}" \
|
||||||
|
"$egress_pct" "$EGRESS_CAKE_ARGS" "$DIFFSERV" dual-srchost "$nat"
|
||||||
|
build ingress "$INGRESS" "${INGRESS_RATE:-$LINK_RATE}" \
|
||||||
|
"$ingress_pct" "$INGRESS_CAKE_ARGS" "$INGRESS_DIFFSERV" dual-dsthost "$nat" \
|
||||||
|
ingress "$wash"
|
||||||
|
ifb=${IFB:-ifb-$dev}
|
||||||
|
((${#ingress[@]} == 0 || ${#ifb} <= 15)) ||
|
||||||
|
die "ifb name '$ifb' too long, set IFB= in $CONF_DIR/$dev.conf"
|
||||||
|
|
||||||
|
cleanup
|
||||||
|
if ((${#egress[@]})); then
|
||||||
|
tc qdisc replace dev "$dev" root "${egress[@]}"
|
||||||
|
echo egress=1 >>"$state"
|
||||||
|
fi
|
||||||
|
if ((${#ingress[@]})); then
|
||||||
|
ip link add name "$ifb" type ifb
|
||||||
|
echo "ifb=$ifb" >>"$state"
|
||||||
|
ip link set dev "$ifb" up
|
||||||
|
tc qdisc replace dev "$ifb" root "${ingress[@]}"
|
||||||
|
tc qdisc replace dev "$dev" clsact
|
||||||
|
tc filter add dev "$dev" ingress protocol all matchall \
|
||||||
|
action mirred egress redirect dev "$ifb"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
[[ $# -eq 2 ]] || die "usage: ${0##*/} start|stop <interface>"
|
||||||
|
action=$1
|
||||||
|
dev=$2
|
||||||
|
[[ $dev =~ ^[^/[:space:]]{1,15}$ && $dev != . && $dev != .. ]] || die "invalid interface name: $dev"
|
||||||
|
|
||||||
|
LINK_RATE= EGRESS_RATE= INGRESS_RATE=
|
||||||
|
SHAPE_PERCENT=95 EGRESS_SHAPE_PERCENT= INGRESS_SHAPE_PERCENT=
|
||||||
|
DIFFSERV=diffserv4 INGRESS_DIFFSERV=besteffort INGRESS_WASH=yes NAT=no
|
||||||
|
CAKE_ARGS= EGRESS_CAKE_ARGS= INGRESS_CAKE_ARGS= IFB= EGRESS= INGRESS=
|
||||||
|
for f in "$ROLE_DIR"/role-*.conf "$CONF_DIR/defaults.conf" "$CONF_DIR/$dev.conf"; do
|
||||||
|
[[ -f $f ]] && load "$f"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Serialise the systemd unit and the NetworkManager dispatcher.
|
||||||
|
mkdir -p "$STATE_DIR"
|
||||||
|
exec 9>"$STATE_DIR/$dev.lock"
|
||||||
|
flock 9
|
||||||
|
|
||||||
|
case $action in
|
||||||
|
start) start ;;
|
||||||
|
stop) cleanup ;;
|
||||||
|
*) die "unknown action: $action" ;;
|
||||||
|
esac
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
# qos-profiles creates ifb devices on demand; don't create unused ifb0/ifb1.
|
||||||
|
options ifb numifbs=0
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
Summary: Queueing discipline and TCP congestion control profiles
|
||||||
|
Name: qos-profiles
|
||||||
|
Version: 1
|
||||||
|
Release: 1%{?dist}
|
||||||
|
License: GPL-2.0-or-later
|
||||||
|
URL: http://gitea.infomaas.com/rpms-fedora-ims/%{name}
|
||||||
|
BuildArch: noarch
|
||||||
|
Source0: README.md
|
||||||
|
Source1: qos-apply
|
||||||
|
Source2: qos-profiles@.service
|
||||||
|
Source3: qos-profiles-modprobe.conf
|
||||||
|
Source4: example.conf
|
||||||
|
Source5: 50-qos-profiles
|
||||||
|
Source10: 60-qos-profiles-server.conf
|
||||||
|
Source11: 60-qos-profiles-vmhost.conf
|
||||||
|
Source12: 60-qos-profiles-router.conf
|
||||||
|
Source13: 60-qos-profiles-desktop.conf
|
||||||
|
Source20: role-server.conf
|
||||||
|
Source21: role-vmhost.conf
|
||||||
|
Source22: role-router.conf
|
||||||
|
Source23: role-desktop.conf
|
||||||
|
Source30: example-backup-nic-raw.conf
|
||||||
|
Source31: example-dc-router-transit.conf
|
||||||
|
Source32: example-defaults.conf
|
||||||
|
Source33: example-ovh-public.conf
|
||||||
|
Source34: example-ovh-vrack-private.conf
|
||||||
|
Source35: example-router-docsis.conf
|
||||||
|
Source36: example-router-ftth.conf
|
||||||
|
Source37: example-router-vdsl-pppoe.conf
|
||||||
|
Source38: example-vmhost-egress-only.conf
|
||||||
|
Source39: example-vmhost-nat-guests.conf
|
||||||
|
Source40: example-vmhost-routed-uplink.conf
|
||||||
|
|
||||||
|
BuildRequires: systemd-rpm-macros
|
||||||
|
Requires: gawk
|
||||||
|
Requires: iproute
|
||||||
|
Requires: iproute-tc
|
||||||
|
Requires: util-linux-core
|
||||||
|
Recommends: (%{name}-networkmanager if NetworkManager)
|
||||||
|
%{?systemd_requires}
|
||||||
|
|
||||||
|
%description
|
||||||
|
Per-interface queueing disciplines (cake, with ifb for inbound shaping)
|
||||||
|
configured in /etc/qos-profiles/<interface>.conf and applied by the
|
||||||
|
qos-profiles@<interface> systemd unit or the NetworkManager dispatcher.
|
||||||
|
|
||||||
|
The role subpackages set the default qdisc, TCP congestion control and
|
||||||
|
per-interface defaults for servers, VM hosts, routers and desktops.
|
||||||
|
|
||||||
|
%package networkmanager
|
||||||
|
Summary: NetworkManager dispatcher for qos-profiles
|
||||||
|
Requires: %{name} = %{version}-%{release}
|
||||||
|
Requires: NetworkManager
|
||||||
|
|
||||||
|
%description networkmanager
|
||||||
|
Applies /etc/qos-profiles/<interface>.conf when NetworkManager brings the
|
||||||
|
interface up, without enabling the qos-profiles@<interface> unit.
|
||||||
|
|
||||||
|
%package server
|
||||||
|
Summary: Queueing profile for servers and VM guests
|
||||||
|
Provides: %{name}-role
|
||||||
|
Conflicts: %{name}-role
|
||||||
|
|
||||||
|
%description server
|
||||||
|
Sets fq as the default qdisc and BBR as TCP congestion control, for machines
|
||||||
|
that mostly send their own TCP traffic: bare-metal servers and VM guests.
|
||||||
|
|
||||||
|
%package vmhost
|
||||||
|
Summary: Queueing profile for VM and container hosts
|
||||||
|
Provides: %{name}-role
|
||||||
|
Conflicts: %{name}-role
|
||||||
|
|
||||||
|
%description vmhost
|
||||||
|
Sets fq_codel as the default qdisc for traffic forwarded from guests and BBR as
|
||||||
|
TCP congestion control for the host's own traffic. Configure cake on the
|
||||||
|
uplink for fairness per guest.
|
||||||
|
|
||||||
|
%package router
|
||||||
|
Summary: Queueing profile for routers
|
||||||
|
Provides: %{name}-role
|
||||||
|
Conflicts: %{name}-role
|
||||||
|
|
||||||
|
%description router
|
||||||
|
Sets fq_codel as the default qdisc and CUBIC as TCP congestion control, and
|
||||||
|
enables cake NAT awareness by default. Configure cake on the WAN with the
|
||||||
|
link rate.
|
||||||
|
|
||||||
|
%package desktop
|
||||||
|
Summary: Queueing profile for desktops
|
||||||
|
Provides: %{name}-role
|
||||||
|
Conflicts: %{name}-role
|
||||||
|
|
||||||
|
%description desktop
|
||||||
|
Sets fq_codel as the default qdisc and CUBIC as TCP congestion control.
|
||||||
|
|
||||||
|
%prep
|
||||||
|
cp -p %{SOURCE0} .
|
||||||
|
|
||||||
|
%install
|
||||||
|
install -Dpm 755 %{SOURCE1} %{buildroot}%{_libexecdir}/%{name}/qos-apply
|
||||||
|
install -Dpm 644 %{SOURCE2} %{buildroot}%{_unitdir}/qos-profiles@.service
|
||||||
|
install -Dpm 644 %{SOURCE3} %{buildroot}%{_modprobedir}/%{name}.conf
|
||||||
|
install -Dpm 644 %{SOURCE4} %{buildroot}%{_datadir}/%{name}/example.conf
|
||||||
|
install -Dpm 755 %{SOURCE5} %{buildroot}%{_prefix}/lib/NetworkManager/dispatcher.d/50-qos-profiles
|
||||||
|
install -dm 755 %{buildroot}%{_sysconfdir}/%{name}
|
||||||
|
for f in %{SOURCE30} %{SOURCE31} %{SOURCE32} %{SOURCE33} %{SOURCE34} %{SOURCE35} \
|
||||||
|
%{SOURCE36} %{SOURCE37} %{SOURCE38} %{SOURCE39} %{SOURCE40}; do
|
||||||
|
n=$(basename "$f")
|
||||||
|
install -Dpm 644 "$f" %{buildroot}%{_datadir}/%{name}/examples/"${n#example-}"
|
||||||
|
done
|
||||||
|
for f in %{SOURCE10} %{SOURCE11} %{SOURCE12} %{SOURCE13}; do
|
||||||
|
install -Dpm 644 "$f" %{buildroot}%{_sysctldir}/"$(basename "$f")"
|
||||||
|
done
|
||||||
|
for f in %{SOURCE20} %{SOURCE21} %{SOURCE22} %{SOURCE23}; do
|
||||||
|
install -Dpm 644 "$f" %{buildroot}%{_prefix}/lib/%{name}/"$(basename "$f")"
|
||||||
|
done
|
||||||
|
|
||||||
|
%post
|
||||||
|
%systemd_post qos-profiles@.service
|
||||||
|
|
||||||
|
%preun
|
||||||
|
%systemd_preun qos-profiles@.service
|
||||||
|
|
||||||
|
%post server
|
||||||
|
%sysctl_apply 60-qos-profiles-server.conf
|
||||||
|
|
||||||
|
%post vmhost
|
||||||
|
%sysctl_apply 60-qos-profiles-vmhost.conf
|
||||||
|
|
||||||
|
%post router
|
||||||
|
%sysctl_apply 60-qos-profiles-router.conf
|
||||||
|
|
||||||
|
%post desktop
|
||||||
|
%sysctl_apply 60-qos-profiles-desktop.conf
|
||||||
|
|
||||||
|
%files
|
||||||
|
%doc README.md
|
||||||
|
%{_libexecdir}/%{name}/
|
||||||
|
%{_unitdir}/qos-profiles@.service
|
||||||
|
%{_modprobedir}/%{name}.conf
|
||||||
|
%{_datadir}/%{name}/
|
||||||
|
%dir %{_prefix}/lib/%{name}
|
||||||
|
%dir %{_sysconfdir}/%{name}
|
||||||
|
|
||||||
|
%files networkmanager
|
||||||
|
%{_prefix}/lib/NetworkManager/dispatcher.d/50-qos-profiles
|
||||||
|
|
||||||
|
%files server
|
||||||
|
%{_sysctldir}/60-qos-profiles-server.conf
|
||||||
|
%dir %{_prefix}/lib/%{name}
|
||||||
|
%{_prefix}/lib/%{name}/role-server.conf
|
||||||
|
|
||||||
|
%files vmhost
|
||||||
|
%{_sysctldir}/60-qos-profiles-vmhost.conf
|
||||||
|
%dir %{_prefix}/lib/%{name}
|
||||||
|
%{_prefix}/lib/%{name}/role-vmhost.conf
|
||||||
|
|
||||||
|
%files router
|
||||||
|
%{_sysctldir}/60-qos-profiles-router.conf
|
||||||
|
%dir %{_prefix}/lib/%{name}
|
||||||
|
%{_prefix}/lib/%{name}/role-router.conf
|
||||||
|
|
||||||
|
%files desktop
|
||||||
|
%{_sysctldir}/60-qos-profiles-desktop.conf
|
||||||
|
%dir %{_prefix}/lib/%{name}
|
||||||
|
%{_prefix}/lib/%{name}/role-desktop.conf
|
||||||
|
|
||||||
|
%changelog
|
||||||
|
* Sat Oct 10 2026 Zoran Pericic <zpericic@netst.org> - 1-1
|
||||||
|
- Initial release
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Queueing disciplines for %I
|
||||||
|
Documentation=file:///usr/share/doc/qos-profiles/README.md
|
||||||
|
BindsTo=sys-subsystem-net-devices-%i.device
|
||||||
|
After=sys-subsystem-net-devices-%i.device
|
||||||
|
ConditionPathExists=/etc/qos-profiles/%I.conf
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
RemainAfterExit=yes
|
||||||
|
ExecStart=/usr/libexec/qos-profiles/qos-apply start %I
|
||||||
|
ExecStop=/usr/libexec/qos-profiles/qos-apply stop %I
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=sys-subsystem-net-devices-%i.device
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Interface defaults of qos-profiles-desktop, override in /etc/qos-profiles/.
|
||||||
|
DIFFSERV=diffserv4
|
||||||
|
INGRESS_DIFFSERV=besteffort
|
||||||
|
INGRESS_WASH=yes
|
||||||
|
NAT=no
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# Interface defaults of qos-profiles-router, override in /etc/qos-profiles/.
|
||||||
|
# nat: cake sees LAN addresses behind masquerade, for per-host fairness.
|
||||||
|
DIFFSERV=diffserv4
|
||||||
|
INGRESS_DIFFSERV=besteffort
|
||||||
|
INGRESS_WASH=yes
|
||||||
|
NAT=yes
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Interface defaults of qos-profiles-server, override in /etc/qos-profiles/.
|
||||||
|
DIFFSERV=diffserv4
|
||||||
|
INGRESS_DIFFSERV=besteffort
|
||||||
|
INGRESS_WASH=yes
|
||||||
|
NAT=no
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Interface defaults of qos-profiles-vmhost, override in /etc/qos-profiles/.
|
||||||
|
DIFFSERV=diffserv4
|
||||||
|
INGRESS_DIFFSERV=besteffort
|
||||||
|
INGRESS_WASH=yes
|
||||||
|
NAT=no
|
||||||
Reference in new issue
Block a user