]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
net_sched: sch_fq: convert skb->tstamp if not monotonic
authorWillem de Bruijn <willemb@google.com>
Thu, 4 Jun 2026 19:41:04 +0000 (15:41 -0400)
committerJakub Kicinski <kuba@kernel.org>
Mon, 8 Jun 2026 23:00:06 +0000 (16:00 -0700)
FQ currently assumes skb->tstamp holds monotonic time, as used by TCP.

Users with ns_capable CAP_NET_ADMIN can transmit skbs using SO_TXTIME
with CLOCK_MONOTONIC, CLOCK_REALTIME or CLOCK_TAI clockids as of
commit 80b14dee2bea ("net: Add a new socket option for a future
transmit time.")

More recently, skbs also gained tstamp_type to explicitly communicate
the clockid of skb->tstamp, with commit 4d25ca2d6801 ("net: Rename
mono_delivery_time to tstamp_type for scalabilty"), commit
1693c5db6ab8 ("net: Add additional bit to support clockid_t timestamp
type") and a few others.

Detect other clocks and convert to monotonic for use in FQ. That is,
convert fq_skb_cb(skb)->time_to_send. Do not convert skb->tstamp
itself. Network device clocks are more commonly synchronized to TAI.

Conversion may be imprecise due to clock adjustment (e.g., adjfreq)
between when SCM_TSTAMP is set and when it is converted in fq_enqueue.
The common codepath is short, so skew will be well below common pacing
operation. Even in edge cases, bursts (too soon) or beyond horizon
(too late) are indistinguishable from network conditions. To which
senders must be robust, as long as infrequent.

Avoid overflow due to negative offsets becoming huge when converting
from signed ktime_t to u64 time_to_send. Bound lower to mono 1 and
upper to now + q->horizon. This protects against bad input, e.g.,
from BPF programs.

Detect legacy BPF programs that program skb->tstamp without setting
skb->tstamp_type. Here tstamp_type is zero (SKB_CLOCK_REALTIME), but
the value will be unrealistic for realtime in the 21st century. Follow
existing TIME_UPTIME_SEC_MAX as bound between mono and realtime.

Signed-off-by: Willem de Bruijn <willemb@google.com>
----

Changes
  v1 -> v2
    - replace Fixes tag with references inside the commit message

Link: https://patch.msgid.link/20260604194221.3319080-3-willemdebruijn.kernel@gmail.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
net/sched/sch_fq.c

index 33783c9f8e1665e096cf22adf61db3c199b87afb..7cae082a984721215b17fea2d094095155580dcb 100644 (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);