Merge branch 'so_txtime-improvements'

Willem de Bruijn says:

====================
SO_TXTIME improvements

FQ targets monotonic timestamps as generated by the TCP stack.

But SO_TXTIME was later added, which can send skbs with timestamps
against other clocks. It is now possible to detect these through skb
tstamp_type.

Make FQ robust by converting these timestamps for use in FQ (patch 2).

This also requires testing against out-of-bounds values. Prefer to do
this at the source, when parsing SCM_TXTIME (patch 1). But, tests in
the hot path are still needed, to handle BPF sources.

Extend the so_txtime selftest to handle this new case (patch 3).

v1: https://lore.kernel.org/20260603190243.2789335-1-willemdebruijn.kernel@gmail.com
====================

Link: https://patch.msgid.link/20260604194221.3319080-1-willemdebruijn.kernel@gmail.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski 2026-06-08 16:00:12 -07:00
commit 1e127c94fa
3 changed files with 83 additions and 10 deletions

View File

@ -3045,12 +3045,42 @@ int __sock_cmsg_send(struct sock *sk, struct cmsghdr *cmsg,
sockc->tsflags |= tsflags;
break;
case SCM_TXTIME:
{
ktime_t tmin;
u64 txtime;
if (!sock_flag(sk, SOCK_TXTIME))
return -EINVAL;
if (cmsg->cmsg_len != CMSG_LEN(sizeof(u64)))
return -EINVAL;
sockc->transmit_time = get_unaligned((u64 *)CMSG_DATA(cmsg));
txtime = get_unaligned((u64 *)CMSG_DATA(cmsg));
/* Allow sending without a delivery time: zero special case */
if (!txtime) {
sockc->transmit_time = 0;
break;
}
switch (sk->sk_clockid) {
case CLOCK_MONOTONIC:
tmin = 1;
break;
case CLOCK_REALTIME:
tmin = max(ktime_mono_to_real(0), 1);
break;
case CLOCK_TAI:
tmin = max(ktime_mono_to_any(0, TK_OFFS_TAI), 1);
break;
default:
tmin = 1;
WARN_ON_ONCE(1);
break;
}
sockc->transmit_time = max_t(ktime_t, txtime, tmin);
break;
}
case SCM_TS_OPT_ID:
if (sk_is_tcp(sk))
return -EINVAL;

View File

@ -537,10 +537,10 @@ static void flow_queue_add(struct fq_flow *flow, struct sk_buff *skb)
rb_insert_color(&skb->rbnode, &flow->t_root);
}
static bool fq_packet_beyond_horizon(const struct sk_buff *skb,
static bool fq_packet_beyond_horizon(ktime_t time_to_send,
const struct fq_sched_data *q, u64 now)
{
return unlikely((s64)skb->tstamp > (s64)(now + q->horizon));
return unlikely((s64)time_to_send > (s64)(now + q->horizon));
}
static void fq_flow_adjust_timer(struct fq_sched_data *q, struct fq_flow *flow,
@ -561,6 +561,36 @@ static void fq_flow_adjust_timer(struct fq_sched_data *q, struct fq_flow *flow,
}
}
static ktime_t fq_skb_tstamp_to_mono(struct sk_buff *skb)
{
const ktime_t mono_max = NSEC_PER_SEC * TIME_UPTIME_SEC_MAX;
if (likely(skb->tstamp_type == SKB_CLOCK_MONOTONIC))
return max(skb->tstamp, 1);
if (skb->tstamp_type == SKB_CLOCK_TAI)
return max(ktime_sub(skb->tstamp, ktime_mono_to_any(0, TK_OFFS_TAI)), 1);
if (likely(skb->tstamp > mono_max))
return max(ktime_sub(skb->tstamp, ktime_mono_to_real(0)), 1);
/* Handle BPF programs setting skb->stamp but not tstamp_type */
net_warn_ratelimited("fq: likely mono tstamp with tstamp_type 0\n");
skb->tstamp_type = SKB_CLOCK_MONOTONIC;
return max(skb->tstamp, 1);
}
static void fq_mono_to_skb_tstamp(struct sk_buff *skb, ktime_t time_to_send)
{
if (skb->tstamp_type == SKB_CLOCK_MONOTONIC)
skb->tstamp = time_to_send;
else if (skb->tstamp_type == SKB_CLOCK_REALTIME)
skb->tstamp = ktime_mono_to_real(time_to_send);
else
skb->tstamp = ktime_mono_to_any(time_to_send, TK_OFFS_TAI);
}
static int fq_enqueue(struct sk_buff *skb, struct Qdisc *sch,
struct sk_buff **to_free)
{
@ -579,17 +609,20 @@ static int fq_enqueue(struct sk_buff *skb, struct Qdisc *sch,
if (!skb->tstamp) {
fq_skb_cb(skb)->time_to_send = now;
} else {
ktime_t time_to_send = fq_skb_tstamp_to_mono(skb);
/* Check if packet timestamp is too far in the future. */
if (fq_packet_beyond_horizon(skb, q, now)) {
if (fq_packet_beyond_horizon(time_to_send, q, now)) {
if (q->horizon_drop) {
q->stat_horizon_drops++;
return qdisc_drop_reason(skb, sch, to_free,
QDISC_DROP_HORIZON_LIMIT);
}
q->stat_horizon_caps++;
skb->tstamp = now + q->horizon;
time_to_send = now + q->horizon;
fq_mono_to_skb_tstamp(skb, time_to_send);
}
fq_skb_cb(skb)->time_to_send = skb->tstamp;
fq_skb_cb(skb)->time_to_send = (u64)time_to_send;
}
f = fq_classify(sch, skb, now);

View File

@ -46,7 +46,7 @@ def _qdisc_setup(ifname, qdisc, optargs=""):
tc(f"qdisc replace dev {ifname} root {qdisc} {optargs}")
def _test_variants_mono():
def _test_variants_fq():
for ipver in ["4", "6"]:
for testcase in [
["no_delay", "a,-1", "a,-1"],
@ -59,13 +59,20 @@ def _test_variants_mono():
yield KsftNamedVariant(name, ipver, testcase[1], testcase[2])
@ksft_variants(_test_variants_mono())
def test_so_txtime_mono(cfg, ipver, args_tx, args_rx):
@ksft_variants(_test_variants_fq())
def test_so_txtime_fq_mono(cfg, ipver, args_tx, args_rx):
"""Run all variants of monotonic (fq) tests."""
_qdisc_setup(cfg.ifname, "fq")
test_so_txtime(cfg, "mono", ipver, args_tx, args_rx, True)
@ksft_variants(_test_variants_fq())
def test_so_txtime_fq_tai(cfg, ipver, args_tx, args_rx):
"""Run all variants of fq tests, but pass CLOCK_TAI to test conversion."""
_qdisc_setup(cfg.ifname, "fq")
test_so_txtime(cfg, "tai", ipver, args_tx, args_rx, True)
def _test_variants_etf():
for ipver in ["4", "6"]:
for testcase in [
@ -95,7 +102,10 @@ def test_so_txtime_etf(cfg, ipver, args_tx, args_rx, expect_fail):
def main() -> None:
"""Boilerplate ksft main."""
with NetDrvEpEnv(__file__) as cfg:
ksft_run([test_so_txtime_mono, test_so_txtime_etf], args=(cfg,))
ksft_run(
[test_so_txtime_fq_mono, test_so_txtime_fq_tai, test_so_txtime_etf],
args=(cfg,),
)
ksft_exit()