mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [RFC PATCH v1 01/30] proc: sysctl: address table entries by index
       [not found] <cover.1787771905.git.legion@kernel.org>
@ 2026-08-26 19:42 ` Alexey Gladkov
  2026-08-26 19:42 ` [RFC PATCH v1 02/30] sysctl: add unsigned int limit constants Alexey Gladkov
                   ` (28 subsequent siblings)
  29 siblings, 0 replies; 37+ messages in thread
From: Alexey Gladkov @ 2026-08-26 19:42 UTC (permalink / raw)
  To: Linus Torvalds, Eric W . Biederman, Kees Cook, Joel Granados
  Cc: LKML, linux-fsdevel

proc_sysctl keeps pointers to ctl_table entries in several lookup and
inode paths. That makes it hard to support alternative descriptor
formats because the tree logic assumes that every registered entry
already is a ctl_table object.

Store the entry index in proc inodes and pass header/index pairs through
the lookup, iteration and permission paths instead. The existing
ctl_table entries are still used for all handler, permission and poll
operations, but the core no longer needs to carry a direct ctl_table
pointer through every internal path.

Signed-off-by: Alexey Gladkov <legion@kernel.org>
---
 fs/proc/inode.c       |   2 +-
 fs/proc/internal.h    |   2 +-
 fs/proc/proc_sysctl.c | 330 ++++++++++++++++++++++++------------------
 3 files changed, 194 insertions(+), 140 deletions(-)

diff --git a/fs/proc/inode.c b/fs/proc/inode.c
index b7634f975d98..c735338ecc8d 100644
--- a/fs/proc/inode.c
+++ b/fs/proc/inode.c
@@ -62,7 +62,7 @@ static struct inode *proc_alloc_inode(struct super_block *sb)
 	ei->op.proc_get_link = NULL;
 	ei->pde = NULL;
 	ei->sysctl = NULL;
-	ei->sysctl_entry = NULL;
+	ei->sysctl_entry_idx = 0;
 	INIT_HLIST_NODE(&ei->sibling_inodes);
 	ei->ns_ops = NULL;
 	return &ei->vfs_inode;
diff --git a/fs/proc/internal.h b/fs/proc/internal.h
index 64dc44832808..dd3a102b144f 100644
--- a/fs/proc/internal.h
+++ b/fs/proc/internal.h
@@ -120,7 +120,7 @@ struct proc_inode {
 	union proc_op op;
 	struct proc_dir_entry *pde;
 	struct ctl_table_header *sysctl;
-	const struct ctl_table *sysctl_entry;
+	size_t sysctl_entry_idx;
 	struct hlist_node sibling_inodes;
 	const struct proc_ns_operations *ns_ops;
 	struct inode vfs_inode;
diff --git a/fs/proc/proc_sysctl.c b/fs/proc/proc_sysctl.c
index 49ab74e0bfde..131496490991 100644
--- a/fs/proc/proc_sysctl.c
+++ b/fs/proc/proc_sysctl.c
@@ -20,9 +20,8 @@
 #include <linux/lockdep.h>
 #include "internal.h"
 
-#define list_for_each_table_entry(entry, header)	\
-	entry = header->ctl_table;			\
-	for (size_t i = 0 ; i < header->ctl_table_size; ++i, entry++)
+#define list_for_each_table_entry(index, header)			\
+	for (size_t index = 0; index < (header)->ctl_table_size; index++)
 
 static const struct dentry_operations proc_sys_dentry_operations;
 static const struct file_operations proc_sys_file_operations;
@@ -88,16 +87,39 @@ static struct ctl_table_root sysctl_table_root = {
 static DEFINE_SPINLOCK(sysctl_lock);
 
 static void drop_sysctl_table(struct ctl_table_header *header);
-static int sysctl_follow_link(struct ctl_table_header **phead,
-	const struct ctl_table **pentry);
+static int sysctl_follow_link(struct ctl_table_header **phead, size_t *pindex);
 static int insert_links(struct ctl_table_header *head);
 static void put_links(struct ctl_table_header *header);
 
+static const char *sysctl_entry_procname(struct ctl_table_header *head,
+					 size_t index)
+{
+	return head->ctl_table[index].procname;
+}
+
+static umode_t sysctl_entry_mode(struct ctl_table_header *head, size_t index)
+{
+	return head->ctl_table[index].mode;
+}
+
+static struct ctl_table_poll *sysctl_entry_poll(struct ctl_table_header *head,
+						size_t index)
+{
+	return head->ctl_table[index].poll;
+}
+
+static const struct ctl_table *
+sysctl_entry_table(struct ctl_table_header *head, size_t index,
+		   struct ctl_table *table)
+{
+	return &head->ctl_table[index];
+}
+
 static void sysctl_print_dir(struct ctl_dir *dir)
 {
 	if (dir->header.parent)
 		sysctl_print_dir(dir->header.parent);
-	pr_cont("%s/", dir->header.ctl_table[0].procname);
+	pr_cont("%s/", sysctl_entry_procname(&dir->header, 0));
 }
 
 static int namecmp(const char *name1, int len1, const char *name2, int len2)
@@ -110,11 +132,10 @@ static int namecmp(const char *name1, int len1, const char *name2, int len2)
 	return cmp;
 }
 
-static const struct ctl_table *find_entry(struct ctl_table_header **phead,
-	struct ctl_dir *dir, const char *name, int namelen)
+static bool find_entry(struct ctl_table_header **phead, size_t *pindex,
+		       struct ctl_dir *dir, const char *name, int namelen)
 {
 	struct ctl_table_header *head;
-	const struct ctl_table *entry;
 	struct rb_node *node = dir->root.rb_node;
 
 	lockdep_assert_held(&sysctl_lock);
@@ -127,8 +148,8 @@ static const struct ctl_table *find_entry(struct ctl_table_header **phead,
 
 		ctl_node = rb_entry(node, struct ctl_node, node);
 		head = ctl_node->header;
-		entry = &head->ctl_table[ctl_node - head->node];
-		procname = entry->procname;
+		*pindex = ctl_node - head->node;
+		procname = sysctl_entry_procname(head, *pindex);
 
 		cmp = namecmp(name, namelen, procname, strlen(procname));
 		if (cmp < 0)
@@ -137,32 +158,32 @@ static const struct ctl_table *find_entry(struct ctl_table_header **phead,
 			node = node->rb_right;
 		else {
 			*phead = head;
-			return entry;
+			return true;
 		}
 	}
-	return NULL;
+	return false;
 }
 
-static int insert_entry(struct ctl_table_header *head, const struct ctl_table *entry)
+static int insert_entry(struct ctl_table_header *head, size_t index)
 {
-	struct rb_node *node = &head->node[entry - head->ctl_table].node;
+	struct rb_node *node = &head->node[index].node;
 	struct rb_node **p = &head->parent->root.rb_node;
 	struct rb_node *parent = NULL;
-	const char *name = entry->procname;
+	const char *name = sysctl_entry_procname(head, index);
 	int namelen = strlen(name);
 
 	while (*p) {
 		struct ctl_table_header *parent_head;
-		const struct ctl_table *parent_entry;
 		struct ctl_node *parent_node;
 		const char *parent_name;
+		size_t parent_index;
 		int cmp;
 
 		parent = *p;
 		parent_node = rb_entry(parent, struct ctl_node, node);
 		parent_head = parent_node->header;
-		parent_entry = &parent_head->ctl_table[parent_node - parent_head->node];
-		parent_name = parent_entry->procname;
+		parent_index = parent_node - parent_head->node;
+		parent_name = sysctl_entry_procname(parent_head, parent_index);
 
 		cmp = namecmp(name, namelen, parent_name, strlen(parent_name));
 		if (cmp < 0)
@@ -172,7 +193,7 @@ static int insert_entry(struct ctl_table_header *head, const struct ctl_table *e
 		else {
 			pr_err("sysctl duplicate entry: ");
 			sysctl_print_dir(head->parent);
-			pr_cont("%s\n", entry->procname);
+			pr_cont("%s\n", name);
 			return -EEXIST;
 		}
 	}
@@ -182,9 +203,9 @@ static int insert_entry(struct ctl_table_header *head, const struct ctl_table *e
 	return 0;
 }
 
-static void erase_entry(struct ctl_table_header *head, const struct ctl_table *entry)
+static void erase_entry(struct ctl_table_header *head, size_t index)
 {
-	struct rb_node *node = &head->node[entry - head->ctl_table].node;
+	struct rb_node *node = &head->node[index].node;
 
 	rb_erase(node, &head->parent->root);
 }
@@ -206,9 +227,7 @@ static void init_header(struct ctl_table_header *head,
 	head->node = node;
 	INIT_HLIST_HEAD(&head->inodes);
 	if (node) {
-		const struct ctl_table *entry;
-
-		list_for_each_table_entry(entry, head) {
+		list_for_each_table_entry(index, head) {
 			node->header = head;
 			node++;
 		}
@@ -219,15 +238,12 @@ static void init_header(struct ctl_table_header *head,
 
 static void erase_header(struct ctl_table_header *head)
 {
-	const struct ctl_table *entry;
-
 	list_for_each_table_entry(entry, head)
 		erase_entry(head, entry);
 }
 
 static int insert_header(struct ctl_dir *dir, struct ctl_table_header *header)
 {
-	const struct ctl_table *entry;
 	struct ctl_table_header *dir_h = &dir->header;
 	int err;
 
@@ -248,8 +264,8 @@ static int insert_header(struct ctl_dir *dir, struct ctl_table_header *header)
 	err = insert_links(header);
 	if (err)
 		goto fail_links;
-	list_for_each_table_entry(entry, header) {
-		err = insert_entry(header, entry);
+	list_for_each_table_entry(index, header) {
+		err = insert_entry(header, index);
 		if (err)
 			goto fail;
 	}
@@ -258,7 +274,7 @@ static int insert_header(struct ctl_dir *dir, struct ctl_table_header *header)
 	erase_header(header);
 	put_links(header);
 fail_links:
-	if (header->ctl_table == sysctl_mount_point)
+	if (sysctl_is_perm_empty_ctl_header(header))
 		sysctl_clear_perm_empty_ctl_header(dir_h);
 	header->parent = NULL;
 	drop_sysctl_table(dir_h);
@@ -350,21 +366,23 @@ lookup_header_set(struct ctl_table_root *root)
 	return set;
 }
 
-static const struct ctl_table *lookup_entry(struct ctl_table_header **phead,
-					    struct ctl_dir *dir,
-					    const char *name, int namelen)
+static bool lookup_entry(struct ctl_table_header **phead, size_t *pindex,
+			 struct ctl_dir *dir, const char *name, int namelen)
 {
 	struct ctl_table_header *head;
-	const struct ctl_table *entry;
+	size_t index;
+	bool found;
 
 	spin_lock(&sysctl_lock);
-	entry = find_entry(&head, dir, name, namelen);
-	if (entry && use_table(head))
+	found = find_entry(&head, &index, dir, name, namelen);
+	if (found && use_table(head)) {
 		*phead = head;
-	else
-		entry = NULL;
+		*pindex = index;
+	} else {
+		found = false;
+	}
 	spin_unlock(&sysctl_lock);
-	return entry;
+	return found;
 }
 
 static struct ctl_node *first_usable_entry(struct rb_node *node)
@@ -380,10 +398,10 @@ static struct ctl_node *first_usable_entry(struct rb_node *node)
 }
 
 static void first_entry(struct ctl_dir *dir,
-	struct ctl_table_header **phead, const struct ctl_table **pentry)
+	struct ctl_table_header **phead, size_t *pindex)
 {
 	struct ctl_table_header *head = NULL;
-	const struct ctl_table *entry = NULL;
+	size_t index = 0;
 	struct ctl_node *ctl_node;
 
 	spin_lock(&sysctl_lock);
@@ -391,17 +409,16 @@ static void first_entry(struct ctl_dir *dir,
 	spin_unlock(&sysctl_lock);
 	if (ctl_node) {
 		head = ctl_node->header;
-		entry = &head->ctl_table[ctl_node - head->node];
+		index = ctl_node - head->node;
 	}
 	*phead = head;
-	*pentry = entry;
+	*pindex = index;
 }
 
-static void next_entry(struct ctl_table_header **phead, const struct ctl_table **pentry)
+static void next_entry(struct ctl_table_header **phead, size_t *pindex)
 {
 	struct ctl_table_header *head = *phead;
-	const struct ctl_table *entry = *pentry;
-	struct ctl_node *ctl_node = &head->node[entry - head->ctl_table];
+	struct ctl_node *ctl_node = &head->node[*pindex];
 
 	spin_lock(&sysctl_lock);
 	unuse_table(head);
@@ -411,10 +428,9 @@ static void next_entry(struct ctl_table_header **phead, const struct ctl_table *
 	head = NULL;
 	if (ctl_node) {
 		head = ctl_node->header;
-		entry = &head->ctl_table[ctl_node - head->node];
+		*pindex = ctl_node - head->node;
 	}
 	*phead = head;
-	*pentry = entry;
 }
 
 /*
@@ -433,25 +449,30 @@ static int test_perm(int mode, int op)
 	return -EACCES;
 }
 
-static int sysctl_perm(struct ctl_table_header *head, const struct ctl_table *table, int op)
+static int sysctl_perm(struct ctl_table_header *head, size_t index, int op)
 {
 	struct ctl_table_root *root = head->root;
+	struct ctl_table table;
+	const struct ctl_table *entry;
 	int mode;
 
-	if (root->permissions)
-		mode = root->permissions(head, table);
-	else
-		mode = table->mode;
+	if (root->permissions) {
+		entry = sysctl_entry_table(head, index, &table);
+		mode = root->permissions(head, entry);
+	} else {
+		mode = sysctl_entry_mode(head, index);
+	}
 
 	return test_perm(mode, op);
 }
 
 static struct inode *proc_sys_make_inode(struct super_block *sb,
-		struct ctl_table_header *head, const struct ctl_table *table)
+		struct ctl_table_header *head, size_t index)
 {
 	struct ctl_table_root *root = head->root;
 	struct inode *inode;
 	struct proc_inode *ei;
+	umode_t mode;
 
 	inode = new_inode(sb);
 	if (!inode)
@@ -468,14 +489,15 @@ static struct inode *proc_sys_make_inode(struct super_block *sb,
 		return ERR_PTR(-ENOENT);
 	}
 	ei->sysctl = head;
-	ei->sysctl_entry = table;
+	ei->sysctl_entry_idx = index;
 	hlist_add_head_rcu(&ei->sibling_inodes, &head->inodes);
 	head->count++;
 	spin_unlock(&sysctl_lock);
 
+	mode = sysctl_entry_mode(head, index);
 	simple_inode_init_ts(inode);
-	inode->i_mode = table->mode;
-	if (!S_ISDIR(table->mode)) {
+	inode->i_mode = mode;
+	if (!S_ISDIR(mode)) {
 		inode->i_mode |= S_IFREG;
 		inode->i_op = &proc_sys_inode_operations;
 		inode->i_fop = &proc_sys_file_operations;
@@ -518,7 +540,7 @@ static struct dentry *proc_sys_lookup(struct inode *dir, struct dentry *dentry,
 	struct ctl_table_header *head = grab_header(dir);
 	struct ctl_table_header *h = NULL;
 	const struct qstr *name = &dentry->d_name;
-	const struct ctl_table *p;
+	size_t index;
 	struct inode *inode;
 	struct dentry *err = ERR_PTR(-ENOENT);
 	struct ctl_dir *ctl_dir;
@@ -529,18 +551,17 @@ static struct dentry *proc_sys_lookup(struct inode *dir, struct dentry *dentry,
 
 	ctl_dir = container_of(head, struct ctl_dir, header);
 
-	p = lookup_entry(&h, ctl_dir, name->name, name->len);
-	if (!p)
+	if (!lookup_entry(&h, &index, ctl_dir, name->name, name->len))
 		goto out;
 
-	if (S_ISLNK(p->mode)) {
-		ret = sysctl_follow_link(&h, &p);
+	if (S_ISLNK(sysctl_entry_mode(h, index))) {
+		ret = sysctl_follow_link(&h, &index);
 		err = ERR_PTR(ret);
 		if (ret)
 			goto out;
 	}
 
-	inode = proc_sys_make_inode(dir->i_sb, h ? h : head, p);
+	inode = proc_sys_make_inode(dir->i_sb, h ? h : head, index);
 	err = d_splice_alias_ops(inode, dentry, &proc_sys_dentry_operations);
 
 out:
@@ -555,7 +576,9 @@ static ssize_t proc_sys_call_handler(struct kiocb *iocb, struct iov_iter *iter,
 {
 	struct inode *inode = file_inode(iocb->ki_filp);
 	struct ctl_table_header *head = grab_header(inode);
-	const struct ctl_table *table = PROC_I(inode)->sysctl_entry;
+	size_t index = PROC_I(inode)->sysctl_entry_idx;
+	struct ctl_table table;
+	const struct ctl_table *entry;
 	size_t count = iov_iter_count(iter);
 	char *kbuf;
 	ssize_t error;
@@ -568,12 +591,14 @@ static ssize_t proc_sys_call_handler(struct kiocb *iocb, struct iov_iter *iter,
 	 * and won't be until we finish.
 	 */
 	error = -EPERM;
-	if (sysctl_perm(head, table, write ? MAY_WRITE : MAY_READ))
+	if (sysctl_perm(head, index, write ? MAY_WRITE : MAY_READ))
 		goto out;
 
+	entry = sysctl_entry_table(head, index, &table);
+
 	/* if that can happen at all, it should be -EINVAL, not -EISDIR */
 	error = -EINVAL;
-	if (!table->proc_handler)
+	if (!entry->proc_handler)
 		goto out;
 
 	/* don't even try if the size is too large */
@@ -591,13 +616,13 @@ static ssize_t proc_sys_call_handler(struct kiocb *iocb, struct iov_iter *iter,
 		kbuf[count] = '\0';
 	}
 
-	error = BPF_CGROUP_RUN_PROG_SYSCTL(head, table, write, &kbuf, &count,
+	error = BPF_CGROUP_RUN_PROG_SYSCTL(head, entry, write, &kbuf, &count,
 					   &iocb->ki_pos);
 	if (error)
 		goto out_free_buf;
 
 	/* careful: calling conventions are nasty here */
-	error = table->proc_handler(table, write, kbuf, &count, &iocb->ki_pos);
+	error = entry->proc_handler(entry, write, kbuf, &count, &iocb->ki_pos);
 	if (error)
 		goto out_free_buf;
 
@@ -629,14 +654,16 @@ static ssize_t proc_sys_write(struct kiocb *iocb, struct iov_iter *iter)
 static int proc_sys_open(struct inode *inode, struct file *filp)
 {
 	struct ctl_table_header *head = grab_header(inode);
-	const struct ctl_table *table = PROC_I(inode)->sysctl_entry;
+	size_t index = PROC_I(inode)->sysctl_entry_idx;
+	struct ctl_table_poll *poll;
 
 	/* sysctl was unregistered */
 	if (IS_ERR(head))
 		return PTR_ERR(head);
 
-	if (table->poll)
-		filp->private_data = proc_sys_poll_event(table->poll);
+	poll = sysctl_entry_poll(head, index);
+	if (poll)
+		filp->private_data = proc_sys_poll_event(poll);
 
 	sysctl_head_finish(head);
 
@@ -647,7 +674,10 @@ static __poll_t proc_sys_poll(struct file *filp, poll_table *wait)
 {
 	struct inode *inode = file_inode(filp);
 	struct ctl_table_header *head = grab_header(inode);
-	const struct ctl_table *table = PROC_I(inode)->sysctl_entry;
+	size_t index = PROC_I(inode)->sysctl_entry_idx;
+	struct ctl_table_poll *poll;
+	struct ctl_table table;
+	const struct ctl_table *entry;
 	__poll_t ret = DEFAULT_POLLMASK;
 	unsigned long event;
 
@@ -655,17 +685,19 @@ static __poll_t proc_sys_poll(struct file *filp, poll_table *wait)
 	if (IS_ERR(head))
 		return EPOLLERR | EPOLLHUP;
 
-	if (!table->proc_handler)
+	entry = sysctl_entry_table(head, index, &table);
+	if (!entry->proc_handler)
 		goto out;
 
-	if (!table->poll)
+	poll = sysctl_entry_poll(head, index);
+	if (!poll)
 		goto out;
 
 	event = (unsigned long)filp->private_data;
-	poll_wait(filp, &table->poll->wait, wait);
+	poll_wait(filp, &poll->wait, wait);
 
-	if (event != atomic_read(&table->poll->event)) {
-		filp->private_data = proc_sys_poll_event(table->poll);
+	if (event != atomic_read(&poll->event)) {
+		filp->private_data = proc_sys_poll_event(poll);
 		ret = EPOLLIN | EPOLLRDNORM | EPOLLERR | EPOLLPRI;
 	}
 
@@ -678,7 +710,7 @@ static __poll_t proc_sys_poll(struct file *filp, poll_table *wait)
 static bool proc_sys_fill_cache(struct file *file,
 				struct dir_context *ctx,
 				struct ctl_table_header *head,
-				const struct ctl_table *table)
+				size_t index)
 {
 	struct dentry *child, *dir = file->f_path.dentry;
 	struct inode *inode;
@@ -686,8 +718,8 @@ static bool proc_sys_fill_cache(struct file *file,
 	ino_t ino = 0;
 	unsigned type = DT_UNKNOWN;
 
-	qname.name = table->procname;
-	qname.len  = strlen(table->procname);
+	qname.name = sysctl_entry_procname(head, index);
+	qname.len  = strlen(qname.name);
 	qname.hash = full_name_hash(dir, qname.name, qname.len);
 
 	child = d_lookup(dir, &qname);
@@ -698,7 +730,7 @@ static bool proc_sys_fill_cache(struct file *file,
 			return false;
 		if (d_in_lookup(child)) {
 			struct dentry *res;
-			inode = proc_sys_make_inode(dir->d_sb, head, table);
+			inode = proc_sys_make_inode(dir->d_sb, head, index);
 			res = d_splice_alias_ops(inode, child,
 						 &proc_sys_dentry_operations);
 			d_lookup_done(child);
@@ -722,7 +754,7 @@ static bool proc_sys_fill_cache(struct file *file,
 static bool proc_sys_link_fill_cache(struct file *file,
 				    struct dir_context *ctx,
 				    struct ctl_table_header *head,
-				    const struct ctl_table *table)
+				    size_t index)
 {
 	bool ret = true;
 
@@ -731,16 +763,16 @@ static bool proc_sys_link_fill_cache(struct file *file,
 		return false;
 
 	/* It is not an error if we can not follow the link ignore it */
-	if (sysctl_follow_link(&head, &table))
+	if (sysctl_follow_link(&head, &index))
 		goto out;
 
-	ret = proc_sys_fill_cache(file, ctx, head, table);
+	ret = proc_sys_fill_cache(file, ctx, head, index);
 out:
 	sysctl_head_finish(head);
 	return ret;
 }
 
-static int scan(struct ctl_table_header *head, const struct ctl_table *table,
+static int scan(struct ctl_table_header *head, size_t index,
 		unsigned long *pos, struct file *file,
 		struct dir_context *ctx)
 {
@@ -749,10 +781,10 @@ static int scan(struct ctl_table_header *head, const struct ctl_table *table,
 	if ((*pos)++ < ctx->pos)
 		return true;
 
-	if (unlikely(S_ISLNK(table->mode)))
-		res = proc_sys_link_fill_cache(file, ctx, head, table);
+	if (unlikely(S_ISLNK(sysctl_entry_mode(head, index))))
+		res = proc_sys_link_fill_cache(file, ctx, head, index);
 	else
-		res = proc_sys_fill_cache(file, ctx, head, table);
+		res = proc_sys_fill_cache(file, ctx, head, index);
 
 	if (res)
 		ctx->pos = *pos;
@@ -764,9 +796,9 @@ static int proc_sys_readdir(struct file *file, struct dir_context *ctx)
 {
 	struct ctl_table_header *head = grab_header(file_inode(file));
 	struct ctl_table_header *h = NULL;
-	const struct ctl_table *entry;
 	struct ctl_dir *ctl_dir;
 	unsigned long pos;
+	size_t index;
 
 	if (IS_ERR(head))
 		return PTR_ERR(head);
@@ -778,8 +810,8 @@ static int proc_sys_readdir(struct file *file, struct dir_context *ctx)
 
 	pos = 2;
 
-	for (first_entry(ctl_dir, &h, &entry); h; next_entry(&h, &entry)) {
-		if (!scan(h, entry, &pos, file, ctx)) {
+	for (first_entry(ctl_dir, &h, &index); h; next_entry(&h, &index)) {
+		if (!scan(h, index, &pos, file, ctx)) {
 			sysctl_head_finish(h);
 			break;
 		}
@@ -797,7 +829,7 @@ static int proc_sys_permission(struct mnt_idmap *idmap,
 	 * are _NOT_ writeable, capabilities or not.
 	 */
 	struct ctl_table_header *head;
-	const struct ctl_table *table;
+	size_t index;
 	int error;
 
 	/* Executable files are not allowed under /proc/sys/ */
@@ -808,11 +840,12 @@ static int proc_sys_permission(struct mnt_idmap *idmap,
 	if (IS_ERR(head))
 		return PTR_ERR(head);
 
-	table = PROC_I(inode)->sysctl_entry;
-	if (!table) /* global root - r-xr-xr-x */
+	if (!PROC_I(inode)->sysctl) { /* global root - r-xr-xr-x */
 		error = mask & MAY_WRITE ? -EACCES : 0;
-	else /* Use the permissions on the sysctl table entry */
-		error = sysctl_perm(head, table, mask & ~MAY_NOT_BLOCK);
+	} else {
+		index = PROC_I(inode)->sysctl_entry_idx;
+		error = sysctl_perm(head, index, mask & ~MAY_NOT_BLOCK);
+	}
 
 	sysctl_head_finish(head);
 	return error;
@@ -841,14 +874,14 @@ static int proc_sys_getattr(struct mnt_idmap *idmap,
 {
 	struct inode *inode = d_inode(path->dentry);
 	struct ctl_table_header *head = grab_header(inode);
-	const struct ctl_table *table = PROC_I(inode)->sysctl_entry;
 
 	if (IS_ERR(head))
 		return PTR_ERR(head);
 
 	generic_fillattr(&nop_mnt_idmap, request_mask, inode, stat);
-	if (table)
-		stat->mode = (stat->mode & S_IFMT) | table->mode;
+	if (PROC_I(inode)->sysctl)
+		stat->mode = (stat->mode & S_IFMT) |
+			     sysctl_entry_mode(head, PROC_I(inode)->sysctl_entry_idx);
 
 	sysctl_head_finish(head);
 	return 0;
@@ -945,12 +978,11 @@ static struct ctl_dir *find_subdir(struct ctl_dir *dir,
 				   const char *name, int namelen)
 {
 	struct ctl_table_header *head;
-	const struct ctl_table *entry;
+	size_t index;
 
-	entry = find_entry(&head, dir, name, namelen);
-	if (!entry)
+	if (!find_entry(&head, &index, dir, name, namelen))
 		return ERR_PTR(-ENOENT);
-	if (!S_ISDIR(entry->mode))
+	if (!S_ISDIR(sysctl_entry_mode(head, index)))
 		return ERR_PTR(-ENOTDIR);
 	return container_of(head, struct ctl_dir, header);
 }
@@ -1051,39 +1083,44 @@ static struct ctl_dir *xlate_dir(struct ctl_table_set *set, struct ctl_dir *dir)
 	parent = xlate_dir(set, dir->header.parent);
 	if (IS_ERR(parent))
 		return parent;
-	procname = dir->header.ctl_table[0].procname;
+	procname = sysctl_entry_procname(&dir->header, 0);
 	return find_subdir(parent, procname, strlen(procname));
 }
 
-static int sysctl_follow_link(struct ctl_table_header **phead,
-	const struct ctl_table **pentry)
+static int sysctl_follow_link(struct ctl_table_header **phead, size_t *pindex)
 {
 	struct ctl_table_header *head;
+	struct ctl_table table;
 	const struct ctl_table *entry;
 	struct ctl_table_root *root;
 	struct ctl_table_set *set;
 	struct ctl_dir *dir;
+	size_t index;
 	int ret;
 
+	entry = sysctl_entry_table(*phead, *pindex, &table);
+	root = entry->data;
 	spin_lock(&sysctl_lock);
-	root = (*pentry)->data;
 	set = lookup_header_set(root);
 	dir = xlate_dir(set, (*phead)->parent);
 	if (IS_ERR(dir))
 		ret = PTR_ERR(dir);
 	else {
-		const char *procname = (*pentry)->procname;
+		const char *procname = entry->procname;
 		head = NULL;
-		entry = find_entry(&head, dir, procname, strlen(procname));
 		ret = -ENOENT;
-		if (entry && use_table(head)) {
+		if (!find_entry(&head, &index, dir, procname, strlen(procname)))
+			goto out;
+		ret = -ENOENT;
+		if (use_table(head)) {
 			unuse_table(*phead);
 			*phead = head;
-			*pentry = entry;
+			*pindex = index;
 			ret = 0;
 		}
 	}
 
+out:
 	spin_unlock(&sysctl_lock);
 	return ret;
 }
@@ -1145,7 +1182,10 @@ static int sysctl_check_table(const char *path, struct ctl_table_header *header)
 {
 	const struct ctl_table *entry;
 	int err = 0;
-	list_for_each_table_entry(entry, header) {
+	list_for_each_table_entry(index, header) {
+		struct ctl_table table;
+
+		entry = sysctl_entry_table(header, index, &table);
 		if (!entry->procname)
 			err |= sysctl_err(path, entry, "procname is null");
 		if ((entry->proc_handler == proc_dostring) ||
@@ -1181,14 +1221,13 @@ static struct ctl_table_header *new_links(struct ctl_dir *dir, struct ctl_table_
 {
 	struct ctl_table *link_table, *link;
 	struct ctl_table_header *links;
-	const struct ctl_table *entry;
 	struct ctl_node *node;
 	char *link_name;
 	int name_bytes;
 
 	name_bytes = 0;
-	list_for_each_table_entry(entry, head) {
-		name_bytes += strlen(entry->procname) + 1;
+	list_for_each_table_entry(index, head) {
+		name_bytes += strlen(sysctl_entry_procname(head, index)) + 1;
 	}
 
 	links = kzalloc(sizeof(struct ctl_table_header) +
@@ -1205,9 +1244,11 @@ static struct ctl_table_header *new_links(struct ctl_dir *dir, struct ctl_table_
 	link_name = (char *)(link_table + head->ctl_table_size);
 	link = link_table;
 
-	list_for_each_table_entry(entry, head) {
-		int len = strlen(entry->procname) + 1;
-		memcpy(link_name, entry->procname, len);
+	list_for_each_table_entry(index, head) {
+		const char *procname = sysctl_entry_procname(head, index);
+		int len = strlen(procname) + 1;
+
+		memcpy(link_name, procname, len);
 		link->procname = link_name;
 		link->mode = S_IFLNK|S_IRWXUGO;
 		link->data = head->root;
@@ -1226,29 +1267,33 @@ static bool get_links(struct ctl_dir *dir,
 		      struct ctl_table_root *link_root)
 {
 	struct ctl_table_header *tmp_head;
-	const struct ctl_table *entry, *link;
+	size_t link_index;
 
 	if (header->ctl_table_size == 0 ||
 	    sysctl_is_perm_empty_ctl_header(header))
 		return true;
 
 	/* Are there links available for every entry in table? */
-	list_for_each_table_entry(entry, header) {
-		const char *procname = entry->procname;
-		link = find_entry(&tmp_head, dir, procname, strlen(procname));
-		if (!link)
+	list_for_each_table_entry(index, header) {
+		const char *procname = sysctl_entry_procname(header, index);
+
+		if (!find_entry(&tmp_head, &link_index, dir, procname,
+				strlen(procname)))
 			return false;
-		if (S_ISDIR(link->mode) && S_ISDIR(entry->mode))
+		if (S_ISDIR(sysctl_entry_mode(tmp_head, link_index)) &&
+		    S_ISDIR(sysctl_entry_mode(header, index)))
 			continue;
-		if (S_ISLNK(link->mode) && (link->data == link_root))
+		if (S_ISLNK(sysctl_entry_mode(tmp_head, link_index)) &&
+		    tmp_head->ctl_table[link_index].data == link_root)
 			continue;
 		return false;
 	}
 
 	/* The checks passed.  Increase the registration count on the links */
-	list_for_each_table_entry(entry, header) {
-		const char *procname = entry->procname;
-		link = find_entry(&tmp_head, dir, procname, strlen(procname));
+	list_for_each_table_entry(index, header) {
+		const char *procname = sysctl_entry_procname(header, index);
+
+		find_entry(&tmp_head, &link_index, dir, procname, strlen(procname));
 		tmp_head->nreg++;
 	}
 	return true;
@@ -1375,9 +1420,12 @@ struct ctl_table_header *__register_sysctl_table(
 	struct ctl_table_header *header;
 	struct ctl_dir *dir;
 	struct ctl_node *node;
+	size_t alloc_size;
+
+	alloc_size = sizeof(struct ctl_table_header) +
+		     sizeof(struct ctl_node) * table_size;
 
-	header = kzalloc(sizeof(struct ctl_table_header) +
-			 sizeof(struct ctl_node)*table_size, GFP_KERNEL_ACCOUNT);
+	header = kzalloc(alloc_size, GFP_KERNEL_ACCOUNT);
 	if (!header)
 		return NULL;
 
@@ -1477,7 +1525,7 @@ static void put_links(struct ctl_table_header *header)
 	struct ctl_table_root *root = header->root;
 	struct ctl_dir *parent = header->parent;
 	struct ctl_dir *core_parent;
-	const struct ctl_table *entry;
+	size_t link_index;
 
 	if (header->set == root_set)
 		return;
@@ -1486,18 +1534,24 @@ static void put_links(struct ctl_table_header *header)
 	if (IS_ERR(core_parent))
 		return;
 
-	list_for_each_table_entry(entry, header) {
+	list_for_each_table_entry(index, header) {
 		struct ctl_table_header *link_head;
+		struct ctl_table table;
 		const struct ctl_table *link;
-		const char *name = entry->procname;
+		const char *name = sysctl_entry_procname(header, index);
+
+		if (!find_entry(&link_head, &link_index, core_parent, name,
+				strlen(name)))
+			link = NULL;
+		else
+			link = sysctl_entry_table(link_head, link_index, &table);
 
-		link = find_entry(&link_head, core_parent, name, strlen(name));
 		if (link &&
-		    ((S_ISDIR(link->mode) && S_ISDIR(entry->mode)) ||
+		    ((S_ISDIR(link->mode) &&
+		      S_ISDIR(sysctl_entry_mode(header, index))) ||
 		     (S_ISLNK(link->mode) && (link->data == root)))) {
 			drop_sysctl_table(link_head);
-		}
-		else {
+		} else {
 			pr_err("sysctl link missing during unregister: ");
 			sysctl_print_dir(parent);
 			pr_cont("%s\n", name);
-- 
2.55.0


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [RFC PATCH v1 02/30] sysctl: add unsigned int limit constants
       [not found] <cover.1787771905.git.legion@kernel.org>
  2026-08-26 19:42 ` [RFC PATCH v1 01/30] proc: sysctl: address table entries by index Alexey Gladkov
@ 2026-08-26 19:42 ` Alexey Gladkov
  2026-08-26 19:42 ` [RFC PATCH v1 03/30] sysctl: add typed field descriptors Alexey Gladkov
                   ` (27 subsequent siblings)
  29 siblings, 0 replies; 37+ messages in thread
From: Alexey Gladkov @ 2026-08-26 19:42 UTC (permalink / raw)
  To: Linus Torvalds, Eric W . Biederman, Kees Cook, Joel Granados
  Cc: LKML, linux-fsdevel

Some sysctl handlers use unsigned int storage for their limit arguments.
In particular, proc_dou8vec_minmax() expects extra1 and extra2 to point
to unsigned int values even though the controlled data is an u8.

Provide shared unsigned int constants so typed sysctl descriptors can
pass correctly typed min and max pointers without casting the existing
int constants.

Signed-off-by: Alexey Gladkov <legion@kernel.org>
---
 include/linux/sysctl.h | 7 +++++++
 kernel/sysctl.c        | 3 +++
 2 files changed, 10 insertions(+)

diff --git a/include/linux/sysctl.h b/include/linux/sysctl.h
index 2886fbceb5d6..8d993ba1488b 100644
--- a/include/linux/sysctl.h
+++ b/include/linux/sysctl.h
@@ -59,6 +59,12 @@ extern const int sysctl_vals[];
 #define SYSCTL_LONG_ONE		((void *)&sysctl_long_vals[1])
 #define SYSCTL_LONG_MAX		((void *)&sysctl_long_vals[2])
 
+#define SYSCTL_UINT_ZERO	((unsigned int *)&sysctl_uint_vals[0])
+#define SYSCTL_UINT_ONE		((unsigned int *)&sysctl_uint_vals[1])
+#define SYSCTL_UINT_TWO		((unsigned int *)&sysctl_uint_vals[2])
+#define SYSCTL_UINT_THREE	((unsigned int *)&sysctl_uint_vals[3])
+#define SYSCTL_UINT_FOUR	((unsigned int *)&sysctl_uint_vals[4])
+
 /**
  *
  * "dir" originates from read_iter (dir = 0) or write_iter (dir = 1)
@@ -73,6 +79,7 @@ extern const int sysctl_vals[];
 #define SYSCTL_KERN_TO_USER(dir) (!dir)
 
 extern const unsigned long sysctl_long_vals[];
+extern const unsigned int sysctl_uint_vals[];
 
 typedef int proc_handler(const struct ctl_table *ctl, int write, void *buffer,
 		size_t *lenp, loff_t *ppos);
diff --git a/kernel/sysctl.c b/kernel/sysctl.c
index c9efb17cc255..d1b5284141c4 100644
--- a/kernel/sysctl.c
+++ b/kernel/sysctl.c
@@ -29,6 +29,9 @@ EXPORT_SYMBOL(sysctl_vals);
 const unsigned long sysctl_long_vals[] = { 0, 1, LONG_MAX };
 EXPORT_SYMBOL_GPL(sysctl_long_vals);
 
+const unsigned int sysctl_uint_vals[] = { 0, 1, 2, 3, 4 };
+EXPORT_SYMBOL_GPL(sysctl_uint_vals);
+
 #if defined(CONFIG_SYSCTL)
 
 /* Constants used for minimum and maximum */
-- 
2.55.0


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [RFC PATCH v1 03/30] sysctl: add typed field descriptors
       [not found] <cover.1787771905.git.legion@kernel.org>
  2026-08-26 19:42 ` [RFC PATCH v1 01/30] proc: sysctl: address table entries by index Alexey Gladkov
  2026-08-26 19:42 ` [RFC PATCH v1 02/30] sysctl: add unsigned int limit constants Alexey Gladkov
@ 2026-08-26 19:42 ` Alexey Gladkov
  2026-08-26 19:42 ` [RFC PATCH v1 04/30] sysctl: use sysctl_field in ucounts Alexey Gladkov
                   ` (26 subsequent siblings)
  29 siblings, 0 replies; 37+ messages in thread
From: Alexey Gladkov @ 2026-08-26 19:42 UTC (permalink / raw)
  To: Linus Torvalds, Eric W . Biederman, Kees Cook, Joel Granados
  Cc: LKML, linux-fsdevel

A number of sysctl users need to bind a static table description to data
owned by a namespace or another object at registration time. ctl_table
cannot express this directly because data, extra1 and extra2 contain the
resolved pointers consumed by proc handlers.

Such users currently duplicate ctl_table arrays and replace individual
members, often by table index. Besides allocating one descriptor for
every sysctl instance, this couples the fixup code to the exact ordering
of the source table. The void pointers used for data and limits also
prevent the compiler from detecting mismatches between a proc handler
and its arguments.

Introduce ctl_field as an alternative descriptor format. Each field
selects a supported value type and provides typed accessors which resolve
the backing data and optional limits from a registration context. Both
context-dependent and static limits are supported so common constants do
not require trivial accessor functions. A custom field remains available
for handlers whose arguments cannot be described by the typed variants.

Store either ctl_table or ctl_field descriptors in ctl_table_header and
copy the registration context into the header allocation. This gives the
resolved objects the same lifetime as the registered table without
allocating or modifying a ctl_table array. The regular registration
macro derives the field count and context size at compile time, while
the double-underscore interface remains available for contexts embedded
in larger objects.

Keep the existing proc handler, permission and BPF interfaces unchanged
by materializing a temporary ctl_table only when crossing one of those
legacy boundaries. Existing ctl_table users therefore retain their
current behaviour, while converted users can share a single read-only
descriptor array and gain compile-time checking of the common data and
limit types.

Signed-off-by: Alexey Gladkov <legion@kernel.org>
---
 fs/proc/proc_sysctl.c  | 280 +++++++++++++++++++++++++++++++++++++----
 include/linux/sysctl.h | 254 ++++++++++++++++++++++++++++++++++++-
 2 files changed, 503 insertions(+), 31 deletions(-)

diff --git a/fs/proc/proc_sysctl.c b/fs/proc/proc_sysctl.c
index 131496490991..88d3cc79fc33 100644
--- a/fs/proc/proc_sysctl.c
+++ b/fs/proc/proc_sysctl.c
@@ -18,6 +18,7 @@
 #include <linux/mount.h>
 #include <linux/kmemleak.h>
 #include <linux/lockdep.h>
+#include <linux/overflow.h>
 #include "internal.h"
 
 #define list_for_each_table_entry(index, header)			\
@@ -91,20 +92,62 @@ static int sysctl_follow_link(struct ctl_table_header **phead, size_t *pindex);
 static int insert_links(struct ctl_table_header *head);
 static void put_links(struct ctl_table_header *header);
 
+static inline bool is_field_table(const struct ctl_table_header *head)
+{
+	return head->table_kind == SYSCTL_TABLE_KIND_FIELD;
+}
+
 static const char *sysctl_entry_procname(struct ctl_table_header *head,
 					 size_t index)
 {
+	if (is_field_table(head))
+		return head->ctl_fields[index].procname;
+
 	return head->ctl_table[index].procname;
 }
 
 static umode_t sysctl_entry_mode(struct ctl_table_header *head, size_t index)
 {
-	return head->ctl_table[index].mode;
+	const struct sysctl_field *field;
+
+	if (!is_field_table(head))
+		return head->ctl_table[index].mode;
+
+	field = &head->ctl_fields[index];
+
+	if (field->mode_fn) {
+		lockdep_assert_not_held(&sysctl_lock);
+		return field->mode_fn(head->ctx);
+	}
+
+	return field->mode;
+}
+
+static bool sysctl_entry_is_dir(const struct ctl_table_header *head,
+				size_t index)
+{
+	/* Do not call field mode_fn callback while holding sysctl_lock. */
+	if (is_field_table(head))
+		return false;
+
+	return S_ISDIR(head->ctl_table[index].mode);
+}
+
+static bool sysctl_entry_is_link(const struct ctl_table_header *head,
+				 size_t index)
+{
+	if (is_field_table(head))
+		return false;
+
+	return S_ISLNK(head->ctl_table[index].mode);
 }
 
 static struct ctl_table_poll *sysctl_entry_poll(struct ctl_table_header *head,
 						size_t index)
 {
+	if (is_field_table(head))
+		return NULL;
+
 	return head->ctl_table[index].poll;
 }
 
@@ -112,7 +155,122 @@ static const struct ctl_table *
 sysctl_entry_table(struct ctl_table_header *head, size_t index,
 		   struct ctl_table *table)
 {
-	return &head->ctl_table[index];
+	const struct sysctl_field *field;
+
+	if (!is_field_table(head))
+		return &head->ctl_table[index];
+
+	field = &head->ctl_fields[index];
+
+	memset(table, 0, sizeof(*table));
+	table->procname = field->procname;
+	table->mode = field->mode;
+
+	if (field->mode_fn)
+		table->mode = field->mode_fn(head->ctx);
+
+	switch (field->type) {
+	case SYSCTL_FIELD_CUSTOM:
+		table->proc_handler = field->ctl_custom.proc_handler;
+		table->maxlen = field->ctl_custom.maxlen;
+		if (field->ctl_custom.data)
+			table->data = field->ctl_custom.data(head->ctx);
+		if (field->ctl_custom.extra1)
+			table->extra1 = field->ctl_custom.extra1(head->ctx);
+		if (field->ctl_custom.extra2)
+			table->extra2 = field->ctl_custom.extra2(head->ctx);
+		break;
+	case SYSCTL_FIELD_STRING:
+		table->proc_handler = proc_dostring;
+		table->maxlen = field->ctl_string.maxlen;
+		table->data   = field->ctl_string.data(head->ctx);
+		break;
+	case SYSCTL_FIELD_BOOL:
+		table->proc_handler = proc_dobool;
+		table->maxlen = sizeof(bool);
+		table->data   = field->ctl_bool.data(head->ctx);
+		break;
+	case SYSCTL_FIELD_STATIC_U8_MINMAX:
+		table->proc_handler = proc_dou8vec_minmax;
+		table->maxlen = sizeof(u8);
+		table->data   = field->ctl_static_u8.data(head->ctx);
+		table->extra1 = field->ctl_static_u8.min_value;
+		table->extra2 = field->ctl_static_u8.max_value;
+		break;
+	case SYSCTL_FIELD_STATIC_INT_MINMAX:
+		table->proc_handler = proc_dointvec_minmax;
+		table->maxlen = sizeof(int);
+		table->data   = field->ctl_static_int.data(head->ctx);
+		table->extra1 = field->ctl_static_int.min_value;
+		table->extra2 = field->ctl_static_int.max_value;
+		break;
+	case SYSCTL_FIELD_STATIC_UINT_MINMAX:
+		table->proc_handler = proc_douintvec_minmax;
+		table->maxlen = sizeof(unsigned int);
+		table->data   = field->ctl_static_uint.data(head->ctx);
+		table->extra1 = field->ctl_static_uint.min_value;
+		table->extra2 = field->ctl_static_uint.max_value;
+		break;
+	case SYSCTL_FIELD_STATIC_ULONG_MINMAX:
+		table->proc_handler = proc_doulongvec_minmax;
+		table->maxlen = sizeof(unsigned long);
+		table->data   = field->ctl_static_ulong.data(head->ctx);
+		table->extra1 = field->ctl_static_ulong.min_value;
+		table->extra2 = field->ctl_static_ulong.max_value;
+		break;
+	case SYSCTL_FIELD_U8:
+	case SYSCTL_FIELD_U8_MINMAX:
+		table->proc_handler = proc_dou8vec_minmax;
+		table->maxlen = sizeof(u8);
+		table->data   = field->ctl_u8.data(head->ctx);
+
+		if (field->ctl_u8.min_value)
+			table->extra1 = field->ctl_u8.min_value(head->ctx);
+
+		if (field->ctl_u8.max_value)
+			table->extra2 = field->ctl_u8.max_value(head->ctx);
+		break;
+	case SYSCTL_FIELD_INT:
+	case SYSCTL_FIELD_INT_MINMAX:
+		table->proc_handler = field->type == SYSCTL_FIELD_INT ?
+				      proc_dointvec : proc_dointvec_minmax;
+		table->maxlen = sizeof(int);
+		table->data   = field->ctl_int.data(head->ctx);
+
+		if (field->ctl_int.min_value)
+			table->extra1 = field->ctl_int.min_value(head->ctx);
+
+		if (field->ctl_int.max_value)
+			table->extra2 = field->ctl_int.max_value(head->ctx);
+		break;
+	case SYSCTL_FIELD_UINT:
+	case SYSCTL_FIELD_UINT_MINMAX:
+		table->proc_handler = field->type == SYSCTL_FIELD_UINT ?
+				      proc_douintvec : proc_douintvec_minmax;
+		table->maxlen = sizeof(unsigned int);
+		table->data   = field->ctl_uint.data(head->ctx);
+
+		if (field->ctl_uint.min_value)
+			table->extra1 = field->ctl_uint.min_value(head->ctx);
+
+		if (field->ctl_uint.max_value)
+			table->extra2 = field->ctl_uint.max_value(head->ctx);
+		break;
+	case SYSCTL_FIELD_ULONG:
+	case SYSCTL_FIELD_ULONG_MINMAX:
+		table->proc_handler = proc_doulongvec_minmax;
+		table->maxlen = sizeof(unsigned long);
+		table->data   = field->ctl_ulong.data(head->ctx);
+
+		if (field->ctl_ulong.min_value)
+			table->extra1 = field->ctl_ulong.min_value(head->ctx);
+
+		if (field->ctl_ulong.max_value)
+			table->extra2 = field->ctl_ulong.max_value(head->ctx);
+		break;
+	}
+
+	return table;
 }
 
 static void sysctl_print_dir(struct ctl_dir *dir)
@@ -212,9 +370,17 @@ static void erase_entry(struct ctl_table_header *head, size_t index)
 
 static void init_header(struct ctl_table_header *head,
 	struct ctl_table_root *root, struct ctl_table_set *set,
-	struct ctl_node *node, const struct ctl_table *table, size_t table_size)
+	struct ctl_node *node, const struct ctl_table *table,
+	const struct sysctl_field *fields, size_t table_size,
+	const struct sysctl_context *ctx)
 {
-	head->ctl_table = table;
+	if (fields) {
+		head->ctl_fields = fields;
+		head->table_kind = SYSCTL_TABLE_KIND_FIELD;
+	} else {
+		head->ctl_table = table;
+		head->table_kind = SYSCTL_TABLE_KIND_TABLE;
+	}
 	head->ctl_table_size = table_size;
 	head->ctl_table_arg = table;
 	head->used = 0;
@@ -223,6 +389,7 @@ static void init_header(struct ctl_table_header *head,
 	head->unregistering = NULL;
 	head->root = root;
 	head->set = set;
+	head->ctx = ctx;
 	head->parent = NULL;
 	head->node = node;
 	INIT_HLIST_HEAD(&head->inodes);
@@ -982,7 +1149,7 @@ static struct ctl_dir *find_subdir(struct ctl_dir *dir,
 
 	if (!find_entry(&head, &index, dir, name, namelen))
 		return ERR_PTR(-ENOENT);
-	if (!S_ISDIR(sysctl_entry_mode(head, index)))
+	if (!sysctl_entry_is_dir(head, index))
 		return ERR_PTR(-ENOTDIR);
 	return container_of(head, struct ctl_dir, header);
 }
@@ -1007,7 +1174,8 @@ static struct ctl_dir *new_dir(struct ctl_table_set *set,
 	memcpy(new_name, name, namelen);
 	table[0].procname = new_name;
 	table[0].mode = S_IFDIR|S_IRUGO|S_IXUGO;
-	init_header(&new->header, set->dir.header.root, set, node, table, 1);
+	init_header(&new->header, set->dir.header.root, set, node, table, NULL,
+		    1, NULL);
 
 	return new;
 }
@@ -1256,7 +1424,7 @@ static struct ctl_table_header *new_links(struct ctl_dir *dir, struct ctl_table_
 		link++;
 	}
 	init_header(links, dir->header.root, dir->header.set, node, link_table,
-		    head->ctl_table_size);
+		    NULL, head->ctl_table_size, NULL);
 	links->nreg = head->ctl_table_size;
 
 	return links;
@@ -1280,10 +1448,10 @@ static bool get_links(struct ctl_dir *dir,
 		if (!find_entry(&tmp_head, &link_index, dir, procname,
 				strlen(procname)))
 			return false;
-		if (S_ISDIR(sysctl_entry_mode(tmp_head, link_index)) &&
-		    S_ISDIR(sysctl_entry_mode(header, index)))
+		if (sysctl_entry_is_dir(tmp_head, link_index) &&
+		    sysctl_entry_is_dir(header, index))
 			continue;
-		if (S_ISLNK(sysctl_entry_mode(tmp_head, link_index)) &&
+		if (sysctl_entry_is_link(tmp_head, link_index) &&
 		    tmp_head->ctl_table[link_index].data == link_root)
 			continue;
 		return false;
@@ -1370,18 +1538,22 @@ static struct ctl_dir *sysctl_mkdir_p(struct ctl_dir *dir, const char *path)
 }
 
 /**
- * __register_sysctl_table - register a leaf sysctl table
+ * __register_sysctl_table_internal - register a leaf sysctl table
  * @set: Sysctl tree to register on
  * @path: The path to the directory the sysctl table is in.
+ * @table: The top-level ctl_table array, or %NULL when registering @fields.
+ * @fields: The top-level ctl_field array, or %NULL when registering @table.
+ * @table_size: The number of elements in @table or @fields.
+ * @ctx: Optional context used to resolve @fields entries.
+ * @ctx_size: Size of @ctx, including any wrapper object that embeds it.
  *
- * @table: the top-level table structure. This table should not be free'd
- *         after registration. So it should not be used on stack. It can either
- *         be a global or dynamically allocated by the caller and free'd later
- *         after sysctl unregistration.
- * @table_size : The number of elements in table
+ * Register a sysctl table hierarchy. One of @table or @fields must be
+ * provided. The descriptor array should not be freed after registration, so it
+ * should not be used on stack. It can either be global or dynamically
+ * allocated by the caller and freed later after sysctl unregistration.
  *
- * Register a sysctl table hierarchy. @table should be a filled in ctl_table
- * array.
+ * If @ctx points to a wrapper object, &struct sysctl_context must be the first
+ * member so @ctx can be copied together with the rest of that object.
  *
  * The members of the &struct ctl_table structure are used as follows:
  * procname - the name of the sysctl file under /proc/sys. Set to %NULL to not
@@ -1412,25 +1584,62 @@ static struct ctl_dir *sysctl_mkdir_p(struct ctl_dir *dir, const char *path)
  * This routine returns %NULL on a failure to register, and a pointer
  * to the table header on success.
  */
-struct ctl_table_header *__register_sysctl_table(
-	struct ctl_table_set *set,
-	const char *path, const struct ctl_table *table, size_t table_size)
+static struct ctl_table_header *
+__register_sysctl_table_internal(struct ctl_table_set *set, const char *path,
+				 const struct ctl_table *table,
+				 const struct sysctl_field *fields,
+				 size_t table_size,
+				 const struct sysctl_context *ctx, size_t ctx_size)
 {
 	struct ctl_table_root *root = set->dir.header.root;
 	struct ctl_table_header *header;
 	struct ctl_dir *dir;
 	struct ctl_node *node;
+	const struct sysctl_context *header_ctx = NULL;
+	size_t nodes_size;
 	size_t alloc_size;
+	size_t context_offset;
+
+	if (ctx && ctx_size < sizeof(*ctx))
+		return NULL;
+
+	if (!ctx && ctx_size)
+		return NULL;
 
-	alloc_size = sizeof(struct ctl_table_header) +
-		     sizeof(struct ctl_node) * table_size;
+	if (check_mul_overflow(sizeof(struct ctl_node), table_size, &nodes_size))
+		return NULL;
+
+	if (check_add_overflow(sizeof(*header), nodes_size, &context_offset))
+		return NULL;
+
+	/*
+	 * Store the copied context after the ctl_node array. struct sysctl_context
+	 * is the first member of any caller-defined wrapper, whose alignment
+	 * must not exceed that of struct sysctl_context.
+	 */
+	if (ctx) {
+		if (check_add_overflow(context_offset,
+				       __alignof__(*ctx) - 1, &context_offset))
+			return NULL;
+		context_offset = ALIGN_DOWN(context_offset, __alignof__(*ctx));
+	}
+
+	if (check_add_overflow(context_offset, ctx_size, &alloc_size))
+		return NULL;
 
 	header = kzalloc(alloc_size, GFP_KERNEL_ACCOUNT);
 	if (!header)
 		return NULL;
 
 	node = (struct ctl_node *)(header + 1);
-	init_header(header, root, set, node, table, table_size);
+	if (ctx) {
+		header_ctx = (const struct sysctl_context *)((void *)header +
+							   context_offset);
+		memcpy((void *)header_ctx, ctx, ctx_size);
+	}
+
+	init_header(header, root, set, node, table, fields, table_size,
+		    header_ctx);
 	if (sysctl_check_table(path, header))
 		goto fail;
 
@@ -1460,6 +1669,25 @@ struct ctl_table_header *__register_sysctl_table(
 	return NULL;
 }
 
+struct ctl_table_header *
+__register_sysctl_fields(struct ctl_table_set *set, const char *path,
+			 const struct sysctl_field *fields, size_t field_count,
+			 const struct sysctl_context *ctx, size_t ctx_size)
+{
+	return __register_sysctl_table_internal(set, path, NULL, fields,
+						field_count, ctx, ctx_size);
+}
+EXPORT_SYMBOL(__register_sysctl_fields);
+
+struct ctl_table_header *
+__register_sysctl_table(struct ctl_table_set *set, const char *path,
+			const struct ctl_table *table, size_t table_size)
+{
+	return __register_sysctl_table_internal(set, path, table, NULL,
+						table_size, NULL, 0);
+}
+EXPORT_SYMBOL(__register_sysctl_table);
+
 /**
  * register_sysctl_sz - register a sysctl table
  * @path: The path to the directory the sysctl table is in. If the path
@@ -1548,7 +1776,7 @@ static void put_links(struct ctl_table_header *header)
 
 		if (link &&
 		    ((S_ISDIR(link->mode) &&
-		      S_ISDIR(sysctl_entry_mode(header, index))) ||
+		      sysctl_entry_is_dir(header, index)) ||
 		     (S_ISLNK(link->mode) && (link->data == root)))) {
 			drop_sysctl_table(link_head);
 		} else {
@@ -1604,7 +1832,7 @@ void setup_sysctl_set(struct ctl_table_set *set,
 {
 	memset(set, 0, sizeof(*set));
 	set->is_seen = is_seen;
-	init_header(&set->dir.header, root, set, NULL, root_table, 1);
+	init_header(&set->dir.header, root, set, NULL, root_table, NULL, 1, NULL);
 }
 
 void retire_sysctl_set(struct ctl_table_set *set)
diff --git a/include/linux/sysctl.h b/include/linux/sysctl.h
index 8d993ba1488b..3c7f2ffab334 100644
--- a/include/linux/sysctl.h
+++ b/include/linux/sysctl.h
@@ -26,16 +26,19 @@
 #include <linux/rcupdate.h>
 #include <linux/wait.h>
 #include <linux/rbtree.h>
+#include <linux/stddef.h>
 #include <linux/uidgid.h>
 #include <uapi/linux/sysctl.h>
 
 /* For the /proc/sys support */
 struct completion;
 struct ctl_table;
+struct sysctl_field;
 struct nsproxy;
 struct ctl_table_root;
 struct ctl_table_header;
 struct ctl_dir;
+struct user_namespace;
 
 /* Keep the same order as in fs/proc/proc_sysctl.c */
 #define SYSCTL_ZERO			((void *)&sysctl_vals[0])
@@ -84,6 +87,12 @@ extern const unsigned int sysctl_uint_vals[];
 typedef int proc_handler(const struct ctl_table *ctl, int write, void *buffer,
 		size_t *lenp, loff_t *ppos);
 
+struct sysctl_context {
+	union {
+		struct user_namespace *user_ns;
+	} ns;
+};
+
 int proc_dostring(const struct ctl_table *, int, void *, size_t *, loff_t *);
 int proc_dobool(const struct ctl_table *table, int write, void *buffer,
 		size_t *lenp, loff_t *ppos);
@@ -182,30 +191,244 @@ struct ctl_table {
 	void *extra2;
 } __randomize_layout;
 
+enum sysctl_field_type {
+	SYSCTL_FIELD_CUSTOM,
+	SYSCTL_FIELD_STRING,
+	SYSCTL_FIELD_BOOL,
+	SYSCTL_FIELD_U8,
+	SYSCTL_FIELD_U8_MINMAX,
+	SYSCTL_FIELD_STATIC_U8_MINMAX,
+	SYSCTL_FIELD_INT,
+	SYSCTL_FIELD_INT_MINMAX,
+	SYSCTL_FIELD_UINT,
+	SYSCTL_FIELD_UINT_MINMAX,
+	SYSCTL_FIELD_STATIC_UINT_MINMAX,
+	SYSCTL_FIELD_ULONG,
+	SYSCTL_FIELD_ULONG_MINMAX,
+	SYSCTL_FIELD_STATIC_INT_MINMAX,
+	SYSCTL_FIELD_STATIC_ULONG_MINMAX,
+};
+
+struct sysctl_field_custom {
+	proc_handler *proc_handler;
+	int maxlen;
+	void *(*data)(const struct sysctl_context *ctx);
+	void *(*extra1)(const struct sysctl_context *ctx);
+	void *(*extra2)(const struct sysctl_context *ctx);
+};
+
+struct sysctl_field_string {
+	char *(*data)(const struct sysctl_context *ctx);
+	int maxlen;
+};
+
+struct sysctl_field_bool {
+	bool *(*data)(const struct sysctl_context *ctx);
+};
+
+struct sysctl_field_u8 {
+	u8 *(*data)(const struct sysctl_context *ctx);
+	unsigned int *(*min_value)(const struct sysctl_context *ctx);
+	unsigned int *(*max_value)(const struct sysctl_context *ctx);
+};
+
+struct sysctl_field_static_u8 {
+	u8 *(*data)(const struct sysctl_context *ctx);
+	unsigned int *min_value;
+	unsigned int *max_value;
+};
+
+struct sysctl_field_int {
+	int *(*data)(const struct sysctl_context *ctx);
+	int *(*min_value)(const struct sysctl_context *ctx);
+	int *(*max_value)(const struct sysctl_context *ctx);
+};
+
+struct sysctl_field_static_int {
+	int *(*data)(const struct sysctl_context *ctx);
+	int *min_value;
+	int *max_value;
+};
+
+struct sysctl_field_uint {
+	unsigned int *(*data)(const struct sysctl_context *ctx);
+	unsigned int *(*min_value)(const struct sysctl_context *ctx);
+	unsigned int *(*max_value)(const struct sysctl_context *ctx);
+};
+
+struct sysctl_field_static_uint {
+	unsigned int *(*data)(const struct sysctl_context *ctx);
+	unsigned int *min_value;
+	unsigned int *max_value;
+};
+
+struct sysctl_field_ulong {
+	unsigned long *(*data)(const struct sysctl_context *ctx);
+	unsigned long *(*min_value)(const struct sysctl_context *ctx);
+	unsigned long *(*max_value)(const struct sysctl_context *ctx);
+};
+
+struct sysctl_field_static_ulong {
+	unsigned long *(*data)(const struct sysctl_context *ctx);
+	unsigned long *min_value;
+	unsigned long *max_value;
+};
+
+#define SYSCTL_FIELD_CUSTOM_MODE(_procname, _mode, _mode_fn, _len, _data, _proc_handler)	\
+	{										\
+		.procname	= (_procname),						\
+		.mode		= (_mode),						\
+		.mode_fn	= (_mode_fn),						\
+		.type		= SYSCTL_FIELD_CUSTOM,					\
+		.ctl_custom	= {							\
+			.proc_handler	= (_proc_handler),				\
+			.data		= (_data),					\
+			.maxlen		= (_len),					\
+		}, \
+	}
+
+#define SYSCTL_FIELD_CUSTOM(_procname, _mode, _len, _data, _proc_handler) \
+	SYSCTL_FIELD_CUSTOM_MODE(_procname, _mode, NULL, _len, _data, _proc_handler)
+
+#define SYSCTL_FIELD_STRING(_procname, _mode, _len, _data)		\
+	{							\
+		.procname	= (_procname),			\
+		.mode		= (_mode),			\
+		.type		= SYSCTL_FIELD_STRING,		\
+		.ctl_string	= {				\
+			.data		= (_data),		\
+			.maxlen		= (_len),		\
+		},						\
+	}
+
+#define SYSCTL_FIELD_BOOL(_procname, _mode, _data)			\
+	{							\
+		.procname	= (_procname),			\
+		.mode		= (_mode),			\
+		.type		= SYSCTL_FIELD_BOOL,		\
+		.ctl_bool	= { .data = (_data) },		\
+	}
+
+#define __SYSCTL_FIELD(_type, _name, _procname, _mode, _mode_fn, _data, _min, _max)	\
+	{										\
+		.procname	= (_procname),						\
+		.mode		= (_mode),						\
+		.mode_fn	= (_mode_fn),						\
+		.type		= (_type),						\
+		.ctl_##_name	= {							\
+			.data = (_data),						\
+			.min_value = (_min),						\
+			.max_value = (_max),						\
+		}, \
+	}
+
+#define SYSCTL_FIELD_U8(_procname, _mode, _data)				\
+	__SYSCTL_FIELD(SYSCTL_FIELD_U8, u8,					\
+		       _procname, _mode, NULL, _data, NULL, NULL)
+
+#define SYSCTL_FIELD_U8_MINMAX(_procname, _mode, _data, _min, _max)		\
+	__SYSCTL_FIELD(SYSCTL_FIELD_U8_MINMAX, u8,				\
+		       _procname, _mode, NULL, _data, _min, _max)
+
+#define SYSCTL_FIELD_INT(_procname, _mode, _data)				\
+	__SYSCTL_FIELD(SYSCTL_FIELD_INT, int,					\
+		       _procname, _mode, NULL, _data, NULL, NULL)
+
+#define SYSCTL_FIELD_INT_MINMAX(_procname, _mode, _data, _min, _max)		\
+	__SYSCTL_FIELD(SYSCTL_FIELD_INT_MINMAX, int,				\
+		       _procname, _mode, NULL, _data, _min, _max)
+
+#define SYSCTL_FIELD_UINT(_procname, _mode, _data)				\
+	__SYSCTL_FIELD(SYSCTL_FIELD_UINT, uint,					\
+		       _procname, _mode, NULL, _data, NULL, NULL)
+
+#define SYSCTL_FIELD_UINT_MINMAX(_procname, _mode, _data, _min, _max)		\
+	__SYSCTL_FIELD(SYSCTL_FIELD_UINT_MINMAX, uint,				\
+		       _procname, _mode, NULL, _data, _min, _max)
+
+#define SYSCTL_FIELD_ULONG(_procname, _mode, _data)				\
+	__SYSCTL_FIELD(SYSCTL_FIELD_ULONG, ulong,				\
+		       _procname, _mode, NULL, _data, NULL, NULL)
+
+#define SYSCTL_FIELD_ULONG_MINMAX(_procname, _mode, _data, _min, _max)		\
+	__SYSCTL_FIELD(SYSCTL_FIELD_ULONG_MINMAX, ulong,			\
+		       _procname, _mode, NULL, _data, _min, _max)
+
+#define SYSCTL_FIELD_STATIC_U8_MINMAX(_procname, _mode, _data, _min, _max)	\
+	__SYSCTL_FIELD(SYSCTL_FIELD_STATIC_U8_MINMAX, static_u8,		\
+		       _procname, _mode, NULL, _data, _min, _max)
+
+#define SYSCTL_FIELD_STATIC_UINT_MINMAX(_procname, _mode, _data, _min, _max)	\
+	__SYSCTL_FIELD(SYSCTL_FIELD_STATIC_UINT_MINMAX, static_uint,		\
+		       _procname, _mode, NULL, _data, _min, _max)
+
+#define SYSCTL_FIELD_STATIC_INT_MINMAX(_procname, _mode, _data, _min, _max)	\
+	__SYSCTL_FIELD(SYSCTL_FIELD_STATIC_INT_MINMAX, static_int,		\
+		       _procname, _mode, NULL, _data, _min, _max)
+
+#define SYSCTL_FIELD_STATIC_ULONG_MINMAX(_procname, _mode, _data, _min, _max)	\
+	__SYSCTL_FIELD(SYSCTL_FIELD_STATIC_ULONG_MINMAX, static_ulong,		\
+		       _procname, _mode, NULL, _data, _min, _max)
+
+struct sysctl_field {
+	const char *procname;
+	umode_t mode;
+	enum sysctl_field_type type;
+	umode_t (*mode_fn)(const struct sysctl_context *ctx);
+	union {
+		struct sysctl_field_custom		ctl_custom;
+		struct sysctl_field_string		ctl_string;
+		struct sysctl_field_bool		ctl_bool;
+		struct sysctl_field_u8			ctl_u8;
+		struct sysctl_field_int			ctl_int;
+		struct sysctl_field_uint		ctl_uint;
+		struct sysctl_field_ulong		ctl_ulong;
+		struct sysctl_field_static_u8		ctl_static_u8;
+		struct sysctl_field_static_int		ctl_static_int;
+		struct sysctl_field_static_uint		ctl_static_uint;
+		struct sysctl_field_static_ulong	ctl_static_ulong;
+	};
+} __randomize_layout;
+
 struct ctl_node {
 	struct rb_node node;
 	struct ctl_table_header *header;
 };
 
 /**
- * struct ctl_table_header - maintains dynamic lists of struct ctl_table trees
- * @ctl_table: pointer to the first element in ctl_table array
- * @ctl_table_size: number of elements pointed by @ctl_table
+ * struct ctl_table_header - maintains dynamic lists of sysctl descriptor trees
+ * @ctl_table: pointer to the first element in a legacy ctl_table array
+ * @ctl_fields: pointer to the first element in a ctl_field array
+ * @ctl_table_size: number of elements pointed to by @ctl_table or @ctl_fields
  * @used: The entry will never be touched when equal to 0.
  * @count: Upped every time something is added to @inodes and downed every time
  *         something is removed from inodes
  * @nreg: When nreg drops to 0 the ctl_table_header will be unregistered.
- * @rcu: Delays the freeing of the inode. Introduced with "unfuck proc_sysctl ->d_compare()"
+ * @rcu: delays freeing the header until after an RCU grace period
+ * @unregistering: completion used while unregistering the header
+ * @ctl_table_arg: original legacy ctl_table passed at registration, or NULL
+ * @ctx: copied registration context used to resolve ctl_field entries
+ * @root: sysctl tree containing this header
+ * @set: sysctl set containing this header
+ * @parent: parent directory of this header
+ * @node: array of nodes corresponding to the descriptor entries
+ * @inodes: inodes currently referring to this header
  *
  * @type: Enumeration to differentiate between ctl target types
  * @type.SYSCTL_TABLE_TYPE_DEFAULT: ctl target with no special considerations
  * @type.SYSCTL_TABLE_TYPE_PERMANENTLY_EMPTY: Identifies a permanently empty dir
  *                                            target to serve as a mount point
+ * @table_kind: descriptor format stored in this header
+ * @table_kind.SYSCTL_TABLE_KIND_TABLE: legacy ctl_table descriptors
+ * @table_kind.SYSCTL_TABLE_KIND_FIELD: typed ctl_field descriptors
  */
 struct ctl_table_header {
 	union {
 		struct {
-			const struct ctl_table *ctl_table;
+			union {
+				const struct ctl_table *ctl_table;
+				const struct sysctl_field *ctl_fields;
+			};
 			int ctl_table_size;
 			int used;
 			int count;
@@ -220,10 +443,15 @@ struct ctl_table_header {
 	struct ctl_dir *parent;
 	struct ctl_node *node;
 	struct hlist_head inodes; /* head for proc_inode->sysctl_inodes */
+	const struct sysctl_context *ctx;
 	enum {
 		SYSCTL_TABLE_TYPE_DEFAULT,
 		SYSCTL_TABLE_TYPE_PERMANENTLY_EMPTY,
 	} type;
+	enum {
+		SYSCTL_TABLE_KIND_TABLE,
+		SYSCTL_TABLE_KIND_FIELD,
+	} table_kind;
 };
 
 struct ctl_dir {
@@ -248,6 +476,10 @@ struct ctl_table_root {
 #define register_sysctl(path, table)	\
 	register_sysctl_sz(path, table, ARRAY_SIZE(table))
 
+#define register_sysctl_fields(set, path, fields, ctx)			\
+	__register_sysctl_fields(set, path, fields, ARRAY_SIZE(fields),	\
+				 (ctx), sizeof(*(ctx)))
+
 #ifdef CONFIG_SYSCTL
 
 void proc_sys_poll_notify(struct ctl_table_poll *poll);
@@ -260,6 +492,10 @@ extern void retire_sysctl_set(struct ctl_table_set *set);
 struct ctl_table_header *__register_sysctl_table(
 	struct ctl_table_set *set,
 	const char *path, const struct ctl_table *table, size_t table_size);
+struct ctl_table_header *
+__register_sysctl_fields(struct ctl_table_set *set, const char *path,
+			 const struct sysctl_field *fields, size_t field_count,
+			 const struct sysctl_context *ctx, size_t ctx_size);
 struct ctl_table_header *register_sysctl_sz(const char *path, const struct ctl_table *table,
 					    size_t table_size);
 void unregister_sysctl_table(struct ctl_table_header * table);
@@ -295,6 +531,14 @@ static inline struct ctl_table_header *register_sysctl_sz(const char *path,
 	return NULL;
 }
 
+static inline struct ctl_table_header *
+__register_sysctl_fields(struct ctl_table_set *set, const char *path,
+			 const struct sysctl_field *fields, size_t field_count,
+			 const struct sysctl_context *ctx, size_t ctx_size)
+{
+	return NULL;
+}
+
 static inline void unregister_sysctl_table(struct ctl_table_header * table)
 {
 }
-- 
2.55.0


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [RFC PATCH v1 04/30] sysctl: use sysctl_field in ucounts
       [not found] <cover.1787771905.git.legion@kernel.org>
                   ` (2 preceding siblings ...)
  2026-08-26 19:42 ` [RFC PATCH v1 03/30] sysctl: add typed field descriptors Alexey Gladkov
@ 2026-08-26 19:42 ` Alexey Gladkov
  2026-08-26 19:42 ` [RFC PATCH v1 05/30] sysctl: ipc: use sysctl_field in mq_sysctl Alexey Gladkov
                   ` (25 subsequent siblings)
  29 siblings, 0 replies; 37+ messages in thread
From: Alexey Gladkov @ 2026-08-26 19:42 UTC (permalink / raw)
  To: Linus Torvalds, Eric W . Biederman, Kees Cook, Joel Granados
  Cc: LKML, linux-fsdevel

Convert ucount sysctls. The table can now share one static array across
namespaces instead of allocating and rewriting a ctl_table copy for each
registration.

Signed-off-by: Alexey Gladkov <legion@kernel.org>
---
 kernel/ucount.c | 83 +++++++++++++++++++++++++------------------------
 1 file changed, 43 insertions(+), 40 deletions(-)

diff --git a/kernel/ucount.c b/kernel/ucount.c
index d6dc3e859f12..e16d858b663b 100644
--- a/kernel/ucount.c
+++ b/kernel/ucount.c
@@ -60,34 +60,48 @@ static struct ctl_table_root set_root = {
 	.permissions = set_permissions,
 };
 
-static long ue_zero = 0;
-static long ue_int_max = INT_MAX;
-
-#define UCOUNT_ENTRY(name)					\
-	{							\
-		.procname	= name,				\
-		.maxlen		= sizeof(long),			\
-		.mode		= 0644,				\
-		.proc_handler	= proc_doulongvec_minmax,	\
-		.extra1		= &ue_zero,			\
-		.extra2		= &ue_int_max,			\
-	}
-static const struct ctl_table user_table[] = {
-	UCOUNT_ENTRY("max_user_namespaces"),
-	UCOUNT_ENTRY("max_pid_namespaces"),
-	UCOUNT_ENTRY("max_uts_namespaces"),
-	UCOUNT_ENTRY("max_ipc_namespaces"),
-	UCOUNT_ENTRY("max_net_namespaces"),
-	UCOUNT_ENTRY("max_mnt_namespaces"),
-	UCOUNT_ENTRY("max_cgroup_namespaces"),
-	UCOUNT_ENTRY("max_time_namespaces"),
+static unsigned long ue_zero = 0;
+static unsigned long ue_int_max = INT_MAX;
+
+#define UCOUNT_DATA(name, type)						\
+static unsigned long *name ## _data(const struct sysctl_context *ctx)	\
+{									\
+	return &ctx->ns.user_ns->ucount_max[type];			\
+}
+
+UCOUNT_DATA(user_ns, UCOUNT_USER_NAMESPACES);
+UCOUNT_DATA(pid_ns, UCOUNT_PID_NAMESPACES);
+UCOUNT_DATA(uts_ns, UCOUNT_UTS_NAMESPACES);
+UCOUNT_DATA(ipc_ns, UCOUNT_IPC_NAMESPACES);
+UCOUNT_DATA(net_ns, UCOUNT_NET_NAMESPACES);
+UCOUNT_DATA(mnt_ns, UCOUNT_MNT_NAMESPACES);
+UCOUNT_DATA(cgroup_ns, UCOUNT_CGROUP_NAMESPACES);
+UCOUNT_DATA(time_ns, UCOUNT_TIME_NAMESPACES);
+#ifdef CONFIG_INOTIFY_USER
+UCOUNT_DATA(inotify_instances, UCOUNT_INOTIFY_INSTANCES);
+UCOUNT_DATA(inotify_watches, UCOUNT_INOTIFY_WATCHES);
+#endif
+#ifdef CONFIG_FANOTIFY
+UCOUNT_DATA(fanotify_groups, UCOUNT_FANOTIFY_GROUPS);
+UCOUNT_DATA(fanotify_marks, UCOUNT_FANOTIFY_MARKS);
+#endif
+
+static const struct sysctl_field user_table[] = {
+	SYSCTL_FIELD_STATIC_ULONG_MINMAX("max_user_namespaces", 0644, user_ns_data, &ue_zero, &ue_int_max),
+	SYSCTL_FIELD_STATIC_ULONG_MINMAX("max_pid_namespaces", 0644, pid_ns_data, &ue_zero, &ue_int_max),
+	SYSCTL_FIELD_STATIC_ULONG_MINMAX("max_uts_namespaces", 0644, uts_ns_data, &ue_zero, &ue_int_max),
+	SYSCTL_FIELD_STATIC_ULONG_MINMAX("max_ipc_namespaces", 0644, ipc_ns_data, &ue_zero, &ue_int_max),
+	SYSCTL_FIELD_STATIC_ULONG_MINMAX("max_net_namespaces", 0644, net_ns_data, &ue_zero, &ue_int_max),
+	SYSCTL_FIELD_STATIC_ULONG_MINMAX("max_mnt_namespaces", 0644, mnt_ns_data, &ue_zero, &ue_int_max),
+	SYSCTL_FIELD_STATIC_ULONG_MINMAX("max_cgroup_namespaces", 0644, cgroup_ns_data, &ue_zero, &ue_int_max),
+	SYSCTL_FIELD_STATIC_ULONG_MINMAX("max_time_namespaces", 0644, time_ns_data, &ue_zero, &ue_int_max),
 #ifdef CONFIG_INOTIFY_USER
-	UCOUNT_ENTRY("max_inotify_instances"),
-	UCOUNT_ENTRY("max_inotify_watches"),
+	SYSCTL_FIELD_STATIC_ULONG_MINMAX("max_inotify_instances", 0644, inotify_instances_data, &ue_zero, &ue_int_max),
+	SYSCTL_FIELD_STATIC_ULONG_MINMAX("max_inotify_watches", 0644, inotify_watches_data, &ue_zero, &ue_int_max),
 #endif
 #ifdef CONFIG_FANOTIFY
-	UCOUNT_ENTRY("max_fanotify_groups"),
-	UCOUNT_ENTRY("max_fanotify_marks"),
+	SYSCTL_FIELD_STATIC_ULONG_MINMAX("max_fanotify_groups", 0644, fanotify_groups_data, &ue_zero, &ue_int_max),
+	SYSCTL_FIELD_STATIC_ULONG_MINMAX("max_fanotify_marks", 0644, fanotify_marks_data, &ue_zero, &ue_int_max),
 #endif
 };
 #endif /* CONFIG_SYSCTL */
@@ -95,21 +109,14 @@ static const struct ctl_table user_table[] = {
 bool setup_userns_sysctls(struct user_namespace *ns)
 {
 #ifdef CONFIG_SYSCTL
-	struct ctl_table *tbl;
+	struct sysctl_context ctx = {
+		.ns.user_ns = ns,
+	};
 
 	BUILD_BUG_ON(ARRAY_SIZE(user_table) != UCOUNT_COUNTS);
 	setup_sysctl_set(&ns->set, &set_root, set_is_seen);
-	tbl = kmemdup(user_table, sizeof(user_table), GFP_KERNEL);
-	if (tbl) {
-		int i;
-		for (i = 0; i < UCOUNT_COUNTS; i++) {
-			tbl[i].data = &ns->ucount_max[i];
-		}
-		ns->sysctls = __register_sysctl_table(&ns->set, "user", tbl,
-						      ARRAY_SIZE(user_table));
-	}
+	ns->sysctls = register_sysctl_fields(&ns->set, "user", user_table, &ctx);
 	if (!ns->sysctls) {
-		kfree(tbl);
 		retire_sysctl_set(&ns->set);
 		return false;
 	}
@@ -120,12 +127,8 @@ bool setup_userns_sysctls(struct user_namespace *ns)
 void retire_userns_sysctls(struct user_namespace *ns)
 {
 #ifdef CONFIG_SYSCTL
-	const struct ctl_table *tbl;
-
-	tbl = ns->sysctls->ctl_table_arg;
 	unregister_sysctl_table(ns->sysctls);
 	retire_sysctl_set(&ns->set);
-	kfree(tbl);
 #endif
 }
 
-- 
2.55.0


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [RFC PATCH v1 05/30] sysctl: ipc: use sysctl_field in mq_sysctl
       [not found] <cover.1787771905.git.legion@kernel.org>
                   ` (3 preceding siblings ...)
  2026-08-26 19:42 ` [RFC PATCH v1 04/30] sysctl: use sysctl_field in ucounts Alexey Gladkov
@ 2026-08-26 19:42 ` Alexey Gladkov
  2026-08-26 19:42 ` [RFC PATCH v1 06/30] sysctl: ipc: use sysctl_field in ipc_sysctl Alexey Gladkov
                   ` (24 subsequent siblings)
  29 siblings, 0 replies; 37+ messages in thread
From: Alexey Gladkov @ 2026-08-26 19:42 UTC (permalink / raw)
  To: Linus Torvalds, Eric W . Biederman, Kees Cook, Joel Granados
  Cc: LKML, linux-fsdevel

Convert mq_sysctls. The table can now share one static array across ipc
namespaces instead of allocating and rewriting a ctl_table copy for each
registration.

Signed-off-by: Alexey Gladkov <legion@kernel.org>
---
 include/linux/sysctl.h |   2 +
 ipc/mq_sysctl.c        | 100 +++++++++--------------------------------
 2 files changed, 24 insertions(+), 78 deletions(-)

diff --git a/include/linux/sysctl.h b/include/linux/sysctl.h
index 3c7f2ffab334..e2831190e484 100644
--- a/include/linux/sysctl.h
+++ b/include/linux/sysctl.h
@@ -38,6 +38,7 @@ struct nsproxy;
 struct ctl_table_root;
 struct ctl_table_header;
 struct ctl_dir;
+struct ipc_namespace;
 struct user_namespace;
 
 /* Keep the same order as in fs/proc/proc_sysctl.c */
@@ -90,6 +91,7 @@ typedef int proc_handler(const struct ctl_table *ctl, int write, void *buffer,
 struct sysctl_context {
 	union {
 		struct user_namespace *user_ns;
+		struct ipc_namespace *ipc_ns;
 	} ns;
 };
 
diff --git a/ipc/mq_sysctl.c b/ipc/mq_sysctl.c
index 0dd12e1c9f53..54e59b9901dd 100644
--- a/ipc/mq_sysctl.c
+++ b/ipc/mq_sysctl.c
@@ -11,7 +11,6 @@
 
 #include <linux/stat.h>
 #include <linux/capability.h>
-#include <linux/slab.h>
 #include <linux/cred.h>
 
 static int msg_max_limit_min = MIN_MSGMAX;
@@ -20,50 +19,24 @@ static int msg_max_limit_max = HARD_MSGMAX;
 static int msg_maxsize_limit_min = MIN_MSGSIZEMAX;
 static int msg_maxsize_limit_max = HARD_MSGSIZEMAX;
 
-static const struct ctl_table mq_sysctls[] = {
-	{
-		.procname	= "queues_max",
-		.data		= &init_ipc_ns.mq_queues_max,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
-	{
-		.procname	= "msg_max",
-		.data		= &init_ipc_ns.mq_msg_max,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= &msg_max_limit_min,
-		.extra2		= &msg_max_limit_max,
-	},
-	{
-		.procname	= "msgsize_max",
-		.data		= &init_ipc_ns.mq_msgsize_max,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= &msg_maxsize_limit_min,
-		.extra2		= &msg_maxsize_limit_max,
-	},
-	{
-		.procname	= "msg_default",
-		.data		= &init_ipc_ns.mq_msg_default,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= &msg_max_limit_min,
-		.extra2		= &msg_max_limit_max,
-	},
-	{
-		.procname	= "msgsize_default",
-		.data		= &init_ipc_ns.mq_msgsize_default,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= &msg_maxsize_limit_min,
-		.extra2		= &msg_maxsize_limit_max,
-	},
+#define MQ_SYSCTL_DATA(name, member)				\
+static int *name ## _data(const struct sysctl_context *ctx)	\
+{								\
+	return &ctx->ns.ipc_ns->member;				\
+}
+
+MQ_SYSCTL_DATA(mq_queues_max, mq_queues_max);
+MQ_SYSCTL_DATA(mq_msg_max, mq_msg_max);
+MQ_SYSCTL_DATA(mq_msgsize_max, mq_msgsize_max);
+MQ_SYSCTL_DATA(mq_msg_default, mq_msg_default);
+MQ_SYSCTL_DATA(mq_msgsize_default, mq_msgsize_default);
+
+static const struct sysctl_field mq_sysctls[] = {
+	SYSCTL_FIELD_INT("queues_max", 0644, mq_queues_max_data),
+	SYSCTL_FIELD_STATIC_INT_MINMAX("msg_max", 0644, mq_msg_max_data, &msg_max_limit_min, &msg_max_limit_max),
+	SYSCTL_FIELD_STATIC_INT_MINMAX("msgsize_max", 0644, mq_msgsize_max_data, &msg_maxsize_limit_min, &msg_maxsize_limit_max),
+	SYSCTL_FIELD_STATIC_INT_MINMAX("msg_default", 0644, mq_msg_default_data, &msg_max_limit_min, &msg_max_limit_max),
+	SYSCTL_FIELD_STATIC_INT_MINMAX("msgsize_default", 0644, mq_msgsize_default_data, &msg_maxsize_limit_min, &msg_maxsize_limit_max),
 };
 
 static struct ctl_table_set *set_lookup(struct ctl_table_root *root)
@@ -116,39 +89,14 @@ static struct ctl_table_root set_root = {
 
 bool setup_mq_sysctls(struct ipc_namespace *ns)
 {
-	struct ctl_table *tbl;
+	struct sysctl_context ctx = {
+		.ns.ipc_ns = ns,
+	};
 
 	setup_sysctl_set(&ns->mq_set, &set_root, set_is_seen);
 
-	tbl = kmemdup(mq_sysctls, sizeof(mq_sysctls), GFP_KERNEL);
-	if (tbl) {
-		int i;
-
-		for (i = 0; i < ARRAY_SIZE(mq_sysctls); i++) {
-			if (tbl[i].data == &init_ipc_ns.mq_queues_max)
-				tbl[i].data = &ns->mq_queues_max;
-
-			else if (tbl[i].data == &init_ipc_ns.mq_msg_max)
-				tbl[i].data = &ns->mq_msg_max;
-
-			else if (tbl[i].data == &init_ipc_ns.mq_msgsize_max)
-				tbl[i].data = &ns->mq_msgsize_max;
-
-			else if (tbl[i].data == &init_ipc_ns.mq_msg_default)
-				tbl[i].data = &ns->mq_msg_default;
-
-			else if (tbl[i].data == &init_ipc_ns.mq_msgsize_default)
-				tbl[i].data = &ns->mq_msgsize_default;
-			else
-				tbl[i].data = NULL;
-		}
-
-		ns->mq_sysctls = __register_sysctl_table(&ns->mq_set,
-							 "fs/mqueue", tbl,
-							 ARRAY_SIZE(mq_sysctls));
-	}
+	ns->mq_sysctls = register_sysctl_fields(&ns->mq_set, "fs/mqueue", mq_sysctls, &ctx);
 	if (!ns->mq_sysctls) {
-		kfree(tbl);
 		retire_sysctl_set(&ns->mq_set);
 		return false;
 	}
@@ -158,10 +106,6 @@ bool setup_mq_sysctls(struct ipc_namespace *ns)
 
 void retire_mq_sysctls(struct ipc_namespace *ns)
 {
-	const struct ctl_table *tbl;
-
-	tbl = ns->mq_sysctls->ctl_table_arg;
 	unregister_sysctl_table(ns->mq_sysctls);
 	retire_sysctl_set(&ns->mq_set);
-	kfree(tbl);
 }
-- 
2.55.0


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [RFC PATCH v1 06/30] sysctl: ipc: use sysctl_field in ipc_sysctl
       [not found] <cover.1787771905.git.legion@kernel.org>
                   ` (4 preceding siblings ...)
  2026-08-26 19:42 ` [RFC PATCH v1 05/30] sysctl: ipc: use sysctl_field in mq_sysctl Alexey Gladkov
@ 2026-08-26 19:42 ` Alexey Gladkov
  2026-08-26 19:42 ` [RFC PATCH v1 07/30] sysctl: use sysctl_field in pid sysctls Alexey Gladkov
                   ` (23 subsequent siblings)
  29 siblings, 0 replies; 37+ messages in thread
From: Alexey Gladkov @ 2026-08-26 19:42 UTC (permalink / raw)
  To: Linus Torvalds, Eric W . Biederman, Kees Cook, Joel Granados
  Cc: LKML, linux-fsdevel

IPC clones its sysctl table for each namespace so registration can
rewrite data pointers from init_ipc_ns to the target namespace. The
cloned table then has to stay alive until namespace teardown only so it
can be freed after unregistering the sysctls.

Use sysctl_field to derive IPC namespace data from the registration
context instead. This keeps the sysctl descriptors static and const, and
removes the per-namespace table copy and unregister-time free.

Keep the IPC-specific handlers responsible for their own min/max bounds
so the conversion does not need to extend the generic custom field
interface.

Signed-off-by: Alexey Gladkov <legion@kernel.org>
---
 ipc/ipc_sysctl.c | 256 ++++++++++++++++++-----------------------------
 1 file changed, 98 insertions(+), 158 deletions(-)

diff --git a/ipc/ipc_sysctl.c b/ipc/ipc_sysctl.c
index 9b087ebeb643..7b85b0f5ef82 100644
--- a/ipc/ipc_sysctl.c
+++ b/ipc/ipc_sysctl.c
@@ -13,7 +13,6 @@
 #include <linux/capability.h>
 #include <linux/ipc_namespace.h>
 #include <linux/msg.h>
-#include <linux/slab.h>
 #include <linux/cred.h>
 #include "util.h"
 
@@ -48,6 +47,10 @@ static int proc_ipc_auto_msgmni(const struct ctl_table *table, int write,
 	return proc_dointvec_minmax(&ipc_table, write, buffer, lenp, ppos);
 }
 
+int ipc_mni = IPCMNI;
+int ipc_mni_shift = IPCMNI_SHIFT;
+int ipc_min_cycle = RADIX_TREE_MAP_SIZE;
+
 static int proc_ipc_sem_dointvec(const struct ctl_table *table, int write,
 	void *buffer, size_t *lenp, loff_t *ppos)
 {
@@ -69,117 +72,100 @@ static int proc_ipc_sem_dointvec(const struct ctl_table *table, int write,
 	return ret;
 }
 
-int ipc_mni = IPCMNI;
-int ipc_mni_shift = IPCMNI_SHIFT;
-int ipc_min_cycle = RADIX_TREE_MAP_SIZE;
+#define IPC_DATA(type, field)						\
+static type *ipc_ ## field ## _data(const struct sysctl_context *ctx)	\
+{									\
+	return &ctx->ns.ipc_ns->field;					\
+}
+
+IPC_DATA(void, shm_ctlmax)
+IPC_DATA(void, shm_ctlall)
+IPC_DATA(int, shm_ctlmni)
+IPC_DATA(void, shm_rmid_forced)
+IPC_DATA(void, msg_ctlmax)
+IPC_DATA(void, msg_ctlmni)
+IPC_DATA(void, msg_ctlmnb)
+IPC_DATA(void, sem_ctls)
+
+#define IPC_LIMIT(name, value)						\
+static void *ipc_ ## name ## _limit(const struct sysctl_context *ctx)	\
+{									\
+	return value;							\
+}
+
+IPC_LIMIT(zero, SYSCTL_ZERO)
+IPC_LIMIT(one, SYSCTL_ONE)
+IPC_LIMIT(int_max, SYSCTL_INT_MAX)
+IPC_LIMIT(mni, &ipc_mni)
+
+#define IPC_FIELD_CUSTOM_MINMAX(_procname, _len, _data, _handler, _min, _max) \
+	{								\
+		.procname = (_procname),				\
+		.mode = 0644,						\
+		.type = SYSCTL_FIELD_CUSTOM,				\
+		.ctl_custom = {						\
+			.proc_handler = (_handler),			\
+			.maxlen = (_len),				\
+			.data = (_data),				\
+			.extra1 = (_min),				\
+			.extra2 = (_max),				\
+		},							\
+	}
 
-static const struct ctl_table ipc_sysctls[] = {
-	{
-		.procname	= "shmmax",
-		.data		= &init_ipc_ns.shm_ctlmax,
-		.maxlen		= sizeof(init_ipc_ns.shm_ctlmax),
-		.mode		= 0644,
-		.proc_handler	= proc_doulongvec_minmax,
-	},
-	{
-		.procname	= "shmall",
-		.data		= &init_ipc_ns.shm_ctlall,
-		.maxlen		= sizeof(init_ipc_ns.shm_ctlall),
-		.mode		= 0644,
-		.proc_handler	= proc_doulongvec_minmax,
-	},
-	{
-		.procname	= "shmmni",
-		.data		= &init_ipc_ns.shm_ctlmni,
-		.maxlen		= sizeof(init_ipc_ns.shm_ctlmni),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= &ipc_mni,
-	},
-	{
-		.procname	= "shm_rmid_forced",
-		.data		= &init_ipc_ns.shm_rmid_forced,
-		.maxlen		= sizeof(init_ipc_ns.shm_rmid_forced),
-		.mode		= 0644,
-		.proc_handler	= proc_ipc_dointvec_minmax_orphans,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_ONE,
-	},
-	{
-		.procname	= "msgmax",
-		.data		= &init_ipc_ns.msg_ctlmax,
-		.maxlen		= sizeof(init_ipc_ns.msg_ctlmax),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_INT_MAX,
-	},
-	{
-		.procname	= "msgmni",
-		.data		= &init_ipc_ns.msg_ctlmni,
-		.maxlen		= sizeof(init_ipc_ns.msg_ctlmni),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= &ipc_mni,
-	},
-	{
-		.procname	= "auto_msgmni",
-		.data		= NULL,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_ipc_auto_msgmni,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_ONE,
-	},
-	{
-		.procname	=  "msgmnb",
-		.data		= &init_ipc_ns.msg_ctlmnb,
-		.maxlen		= sizeof(init_ipc_ns.msg_ctlmnb),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_INT_MAX,
-	},
-	{
-		.procname	= "sem",
-		.data		= &init_ipc_ns.sem_ctls,
-		.maxlen		= 4*sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_ipc_sem_dointvec,
-	},
 #ifdef CONFIG_CHECKPOINT_RESTORE
-	{
-		.procname	= "sem_next_id",
-		.data		= &init_ipc_ns.ids[IPC_SEM_IDS].next_id,
-		.maxlen		= sizeof(init_ipc_ns.ids[IPC_SEM_IDS].next_id),
-		.mode		= 0444,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_INT_MAX,
-	},
-	{
-		.procname	= "msg_next_id",
-		.data		= &init_ipc_ns.ids[IPC_MSG_IDS].next_id,
-		.maxlen		= sizeof(init_ipc_ns.ids[IPC_MSG_IDS].next_id),
-		.mode		= 0444,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_INT_MAX,
-	},
-	{
-		.procname	= "shm_next_id",
-		.data		= &init_ipc_ns.ids[IPC_SHM_IDS].next_id,
-		.maxlen		= sizeof(init_ipc_ns.ids[IPC_SHM_IDS].next_id),
-		.mode		= 0444,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_INT_MAX,
-	},
+#define IPC_ID_NEXT_DATA(name, id)					\
+static int *ipc_ ## name ## _data(const struct sysctl_context *ctx)	\
+{									\
+	return &ctx->ns.ipc_ns->ids[id].next_id;			\
+}
+
+IPC_ID_NEXT_DATA(sem_next_id, IPC_SEM_IDS)
+IPC_ID_NEXT_DATA(msg_next_id, IPC_MSG_IDS)
+IPC_ID_NEXT_DATA(shm_next_id, IPC_SHM_IDS)
+#endif
+
+static const struct sysctl_field ipc_sysctls[] = {
+	SYSCTL_FIELD_CUSTOM("shmmax", 0644, sizeof(init_ipc_ns.shm_ctlmax),
+			 ipc_shm_ctlmax_data, proc_doulongvec_minmax),
+	SYSCTL_FIELD_CUSTOM("shmall", 0644, sizeof(init_ipc_ns.shm_ctlall),
+			 ipc_shm_ctlall_data, proc_doulongvec_minmax),
+	SYSCTL_FIELD_STATIC_INT_MINMAX("shmmni", 0644, ipc_shm_ctlmni_data,
+				    SYSCTL_ZERO, &ipc_mni),
+	IPC_FIELD_CUSTOM_MINMAX("shm_rmid_forced",
+				sizeof(init_ipc_ns.shm_rmid_forced),
+				ipc_shm_rmid_forced_data,
+				proc_ipc_dointvec_minmax_orphans,
+				ipc_zero_limit, ipc_one_limit),
+	IPC_FIELD_CUSTOM_MINMAX("msgmax", sizeof(init_ipc_ns.msg_ctlmax),
+				ipc_msg_ctlmax_data, proc_dointvec_minmax,
+				ipc_zero_limit, ipc_int_max_limit),
+	IPC_FIELD_CUSTOM_MINMAX("msgmni", sizeof(init_ipc_ns.msg_ctlmni),
+				ipc_msg_ctlmni_data, proc_dointvec_minmax,
+				ipc_zero_limit, ipc_mni_limit),
+	IPC_FIELD_CUSTOM_MINMAX("auto_msgmni", sizeof(int), NULL,
+				proc_ipc_auto_msgmni,
+				ipc_zero_limit, ipc_one_limit),
+	IPC_FIELD_CUSTOM_MINMAX("msgmnb", sizeof(init_ipc_ns.msg_ctlmnb),
+				ipc_msg_ctlmnb_data, proc_dointvec_minmax,
+				ipc_zero_limit, ipc_int_max_limit),
+	SYSCTL_FIELD_CUSTOM("sem", 0644, 4 * sizeof(int), ipc_sem_ctls_data,
+			 proc_ipc_sem_dointvec),
+#ifdef CONFIG_CHECKPOINT_RESTORE
+	SYSCTL_FIELD_STATIC_INT_MINMAX("sem_next_id", 0444,
+				    ipc_sem_next_id_data, SYSCTL_ZERO,
+				    SYSCTL_INT_MAX),
+	SYSCTL_FIELD_STATIC_INT_MINMAX("msg_next_id", 0444,
+				    ipc_msg_next_id_data, SYSCTL_ZERO,
+				    SYSCTL_INT_MAX),
+	SYSCTL_FIELD_STATIC_INT_MINMAX("shm_next_id", 0444,
+				    ipc_shm_next_id_data, SYSCTL_ZERO,
+				    SYSCTL_INT_MAX),
 #endif
 };
 
+#undef IPC_FIELD_CUSTOM_MINMAX
+#undef IPC_LIMIT
+
 static struct ctl_table_set *set_lookup(struct ctl_table_root *root)
 {
 	return &current->nsproxy->ipc_ns->ipc_set;
@@ -244,57 +230,15 @@ static struct ctl_table_root set_root = {
 
 bool setup_ipc_sysctls(struct ipc_namespace *ns)
 {
-	struct ctl_table *tbl;
+	struct sysctl_context ctx = {
+		.ns.ipc_ns = ns,
+	};
 
 	setup_sysctl_set(&ns->ipc_set, &set_root, set_is_seen);
 
-	tbl = kmemdup(ipc_sysctls, sizeof(ipc_sysctls), GFP_KERNEL);
-	if (tbl) {
-		int i;
-
-		for (i = 0; i < ARRAY_SIZE(ipc_sysctls); i++) {
-			if (tbl[i].data == &init_ipc_ns.shm_ctlmax)
-				tbl[i].data = &ns->shm_ctlmax;
-
-			else if (tbl[i].data == &init_ipc_ns.shm_ctlall)
-				tbl[i].data = &ns->shm_ctlall;
-
-			else if (tbl[i].data == &init_ipc_ns.shm_ctlmni)
-				tbl[i].data = &ns->shm_ctlmni;
-
-			else if (tbl[i].data == &init_ipc_ns.shm_rmid_forced)
-				tbl[i].data = &ns->shm_rmid_forced;
-
-			else if (tbl[i].data == &init_ipc_ns.msg_ctlmax)
-				tbl[i].data = &ns->msg_ctlmax;
-
-			else if (tbl[i].data == &init_ipc_ns.msg_ctlmni)
-				tbl[i].data = &ns->msg_ctlmni;
-
-			else if (tbl[i].data == &init_ipc_ns.msg_ctlmnb)
-				tbl[i].data = &ns->msg_ctlmnb;
-
-			else if (tbl[i].data == &init_ipc_ns.sem_ctls)
-				tbl[i].data = &ns->sem_ctls;
-#ifdef CONFIG_CHECKPOINT_RESTORE
-			else if (tbl[i].data == &init_ipc_ns.ids[IPC_SEM_IDS].next_id)
-				tbl[i].data = &ns->ids[IPC_SEM_IDS].next_id;
-
-			else if (tbl[i].data == &init_ipc_ns.ids[IPC_MSG_IDS].next_id)
-				tbl[i].data = &ns->ids[IPC_MSG_IDS].next_id;
-
-			else if (tbl[i].data == &init_ipc_ns.ids[IPC_SHM_IDS].next_id)
-				tbl[i].data = &ns->ids[IPC_SHM_IDS].next_id;
-#endif
-			else
-				tbl[i].data = NULL;
-		}
-
-		ns->ipc_sysctls = __register_sysctl_table(&ns->ipc_set, "kernel", tbl,
-							  ARRAY_SIZE(ipc_sysctls));
-	}
+	ns->ipc_sysctls = register_sysctl_fields(&ns->ipc_set, "kernel",
+						 ipc_sysctls, &ctx);
 	if (!ns->ipc_sysctls) {
-		kfree(tbl);
 		retire_sysctl_set(&ns->ipc_set);
 		return false;
 	}
@@ -304,12 +248,8 @@ bool setup_ipc_sysctls(struct ipc_namespace *ns)
 
 void retire_ipc_sysctls(struct ipc_namespace *ns)
 {
-	const struct ctl_table *tbl;
-
-	tbl = ns->ipc_sysctls->ctl_table_arg;
 	unregister_sysctl_table(ns->ipc_sysctls);
 	retire_sysctl_set(&ns->ipc_set);
-	kfree(tbl);
 }
 
 static int __init ipc_sysctl_init(void)
-- 
2.55.0


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [RFC PATCH v1 07/30] sysctl: use sysctl_field in pid sysctls
       [not found] <cover.1787771905.git.legion@kernel.org>
                   ` (5 preceding siblings ...)
  2026-08-26 19:42 ` [RFC PATCH v1 06/30] sysctl: ipc: use sysctl_field in ipc_sysctl Alexey Gladkov
@ 2026-08-26 19:42 ` Alexey Gladkov
  2026-08-26 19:42 ` [RFC PATCH v1 08/30] sysctl: net: use sysctl_field in unix sysctl Alexey Gladkov
                   ` (22 subsequent siblings)
  29 siblings, 0 replies; 37+ messages in thread
From: Alexey Gladkov @ 2026-08-26 19:42 UTC (permalink / raw)
  To: Linus Torvalds, Eric W . Biederman, Kees Cook, Joel Granados
  Cc: LKML, linux-fsdevel

The pid namespace sysctl registration allocates a private ctl_table copy
only to replace the static pid_max data pointer with the value from the
registered namespace. This keeps otherwise shared sysctl metadata
writable at registration time and requires per-namespace table lifetime
management.

Use a sysctl_field accessor to derive pid_max from the registration
context instead. The pid sysctl table can remain static and const, while
registration no longer needs to clone, patch and free a ctl_table array.

Signed-off-by: Alexey Gladkov <legion@kernel.org>
---
 include/linux/sysctl.h |  2 ++
 kernel/pid.c           | 41 +++++++++++++----------------------------
 2 files changed, 15 insertions(+), 28 deletions(-)

diff --git a/include/linux/sysctl.h b/include/linux/sysctl.h
index e2831190e484..056d10f4ab3d 100644
--- a/include/linux/sysctl.h
+++ b/include/linux/sysctl.h
@@ -39,6 +39,7 @@ struct ctl_table_root;
 struct ctl_table_header;
 struct ctl_dir;
 struct ipc_namespace;
+struct pid_namespace;
 struct user_namespace;
 
 /* Keep the same order as in fs/proc/proc_sysctl.c */
@@ -92,6 +93,7 @@ struct sysctl_context {
 	union {
 		struct user_namespace *user_ns;
 		struct ipc_namespace *ipc_ns;
+		struct pid_namespace *pid_ns;
 	} ns;
 };
 
diff --git a/kernel/pid.c b/kernel/pid.c
index fd5c2d4aa349..66991ee435fb 100644
--- a/kernel/pid.c
+++ b/kernel/pid.c
@@ -787,23 +787,15 @@ static int proc_do_cad_pid(const struct ctl_table *table, int write, void *buffe
 	return 0;
 }
 
-static const struct ctl_table pid_table[] = {
-	{
-		.procname	= "pid_max",
-		.data		= &init_pid_ns.pid_max,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= &pid_max_min,
-		.extra2		= &pid_max_max,
-	},
+static int *pid_max_data(const struct sysctl_context *ctx)
+{
+	return &ctx->ns.pid_ns->pid_max;
+}
+
+static const struct sysctl_field pid_table[] = {
+	SYSCTL_FIELD_STATIC_INT_MINMAX("pid_max", 0644, pid_max_data, &pid_max_min, &pid_max_max),
 #ifdef CONFIG_PROC_SYSCTL
-	{
-		.procname	= "cad_pid",
-		.maxlen		= sizeof(int),
-		.mode		= 0600,
-		.proc_handler	= proc_do_cad_pid,
-	},
+	SYSCTL_FIELD_CUSTOM("cad_pid", 0600, sizeof(int), NULL, proc_do_cad_pid),
 #endif
 };
 #endif
@@ -811,21 +803,18 @@ static const struct ctl_table pid_table[] = {
 int register_pidns_sysctls(struct pid_namespace *pidns)
 {
 #ifdef CONFIG_SYSCTL
-	struct ctl_table *tbl;
+	struct sysctl_context ctx = {
+		.ns.pid_ns = pidns,
+	};
 
 	setup_sysctl_set(&pidns->set, &pid_table_root, set_is_seen);
 
-	tbl = kmemdup(pid_table, sizeof(pid_table), GFP_KERNEL);
-	if (!tbl)
-		return -ENOMEM;
-	tbl->data = &pidns->pid_max;
 	pidns->pid_max = min(pid_max_max, max_t(int, pidns->pid_max,
 			     PIDS_PER_CPU_DEFAULT * num_possible_cpus()));
 
-	pidns->sysctls = __register_sysctl_table(&pidns->set, "kernel", tbl,
-						 ARRAY_SIZE(pid_table));
+	pidns->sysctls = register_sysctl_fields(&pidns->set, "kernel",
+						pid_table, &ctx);
 	if (!pidns->sysctls) {
-		kfree(tbl);
 		retire_sysctl_set(&pidns->set);
 		return -ENOMEM;
 	}
@@ -836,12 +825,8 @@ int register_pidns_sysctls(struct pid_namespace *pidns)
 void unregister_pidns_sysctls(struct pid_namespace *pidns)
 {
 #ifdef CONFIG_SYSCTL
-	const struct ctl_table *tbl;
-
-	tbl = pidns->sysctls->ctl_table_arg;
 	unregister_sysctl_table(pidns->sysctls);
 	retire_sysctl_set(&pidns->set);
-	kfree(tbl);
 #endif
 }
 
-- 
2.55.0


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [RFC PATCH v1 08/30] sysctl: net: use sysctl_field in unix sysctl
       [not found] <cover.1787771905.git.legion@kernel.org>
                   ` (6 preceding siblings ...)
  2026-08-26 19:42 ` [RFC PATCH v1 07/30] sysctl: use sysctl_field in pid sysctls Alexey Gladkov
@ 2026-08-26 19:42 ` Alexey Gladkov
  2026-08-26 19:42 ` [RFC PATCH v1 09/30] sysctl: net: use sysctl_field in xfrm sysctls Alexey Gladkov
                   ` (21 subsequent siblings)
  29 siblings, 0 replies; 37+ messages in thread
From: Alexey Gladkov @ 2026-08-26 19:42 UTC (permalink / raw)
  To: Linus Torvalds, Eric W . Biederman, Kees Cook, Joel Granados
  Cc: LKML, linux-fsdevel

The unix sysctl table is cloned for non-init network namespaces only to
replace the data pointer with per-net storage. That keeps a mutable
ctl_table copy alive for each namespace even though the descriptor
itself is otherwise static.

Add a netns-aware sysctl_field registration helper and use it for the
unix sysctl table. The data pointer is derived from the registration
context, so the table can stay const and the per-net clone and free path
are no longer needed.

Signed-off-by: Alexey Gladkov <legion@kernel.org>
---
 include/linux/sysctl.h     |  2 ++
 net/unix/sysctl_net_unix.c | 48 +++++++++++---------------------------
 2 files changed, 15 insertions(+), 35 deletions(-)

diff --git a/include/linux/sysctl.h b/include/linux/sysctl.h
index 056d10f4ab3d..bbdb0fcfbcd4 100644
--- a/include/linux/sysctl.h
+++ b/include/linux/sysctl.h
@@ -38,6 +38,7 @@ struct nsproxy;
 struct ctl_table_root;
 struct ctl_table_header;
 struct ctl_dir;
+struct net;
 struct ipc_namespace;
 struct pid_namespace;
 struct user_namespace;
@@ -94,6 +95,7 @@ struct sysctl_context {
 		struct user_namespace *user_ns;
 		struct ipc_namespace *ipc_ns;
 		struct pid_namespace *pid_ns;
+		struct net *net_ns;
 	} ns;
 };
 
diff --git a/net/unix/sysctl_net_unix.c b/net/unix/sysctl_net_unix.c
index e02ed6e3955c..fc6687f9eaf4 100644
--- a/net/unix/sysctl_net_unix.c
+++ b/net/unix/sysctl_net_unix.c
@@ -5,58 +5,36 @@
  * Authors:	Mike Shaver.
  */
 
-#include <linux/slab.h>
-#include <linux/string.h>
 #include <linux/sysctl.h>
 #include <net/af_unix.h>
 #include <net/net_namespace.h>
 
 #include "af_unix.h"
 
-static struct ctl_table unix_table[] = {
-	{
-		.procname	= "max_dgram_qlen",
-		.data		= &init_net.unx.sysctl_max_dgram_qlen,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec
-	},
+static int *unix_max_dgram_qlen_data(const struct sysctl_context *ctx)
+{
+	return &ctx->ns.net_ns->unx.sysctl_max_dgram_qlen;
+}
+
+static const struct sysctl_field unix_table[] = {
+	SYSCTL_FIELD_INT("max_dgram_qlen", 0644, unix_max_dgram_qlen_data),
 };
 
 int __net_init unix_sysctl_register(struct net *net)
 {
-	struct ctl_table *table;
-
-	if (net_eq(net, &init_net)) {
-		table = unix_table;
-	} else {
-		table = kmemdup(unix_table, sizeof(unix_table), GFP_KERNEL);
-		if (!table)
-			goto err_alloc;
+	struct sysctl_context ctx = {
+		.ns.net_ns = net,
+	};
 
-		table[0].data = &net->unx.sysctl_max_dgram_qlen;
-	}
-
-	net->unx.ctl = register_net_sysctl_sz(net, "net/unix", table,
-					      ARRAY_SIZE(unix_table));
+	net->unx.ctl = register_sysctl_fields(&net->sysctls, "net/unix",
+					      unix_table, &ctx);
 	if (net->unx.ctl == NULL)
-		goto err_reg;
+		return -ENOMEM;
 
 	return 0;
-
-err_reg:
-	if (!net_eq(net, &init_net))
-		kfree(table);
-err_alloc:
-	return -ENOMEM;
 }
 
 void unix_sysctl_unregister(struct net *net)
 {
-	const struct ctl_table *table;
-
-	table = net->unx.ctl->ctl_table_arg;
 	unregister_net_sysctl_table(net->unx.ctl);
-	if (!net_eq(net, &init_net))
-		kfree(table);
 }
-- 
2.55.0


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [RFC PATCH v1 09/30] sysctl: net: use sysctl_field in xfrm sysctls
       [not found] <cover.1787771905.git.legion@kernel.org>
                   ` (7 preceding siblings ...)
  2026-08-26 19:42 ` [RFC PATCH v1 08/30] sysctl: net: use sysctl_field in unix sysctl Alexey Gladkov
@ 2026-08-26 19:42 ` Alexey Gladkov
  2026-08-26 19:42 ` [RFC PATCH v1 10/30] sysctl: net: use sysctl_field for simple IPv4 per-net sysctls Alexey Gladkov
                   ` (20 subsequent siblings)
  29 siblings, 0 replies; 37+ messages in thread
From: Alexey Gladkov @ 2026-08-26 19:42 UTC (permalink / raw)
  To: Linus Torvalds, Eric W . Biederman, Kees Cook, Joel Granados
  Cc: LKML, linux-fsdevel

The xfrm sysctl table is cloned for each network namespace only to
replace data pointers with per-net storage. The descriptors are
otherwise static, while unprivileged network namespaces still register
an empty table to preserve the existing visibility rule.

Use sysctl_field accessors to derive the per-net storage from the
registration context instead. This keeps the xfrm descriptors const and
removes the per-net ctl_table allocation and free path without changing
which sysctls are exposed.

Signed-off-by: Alexey Gladkov <legion@kernel.org>
---
 net/xfrm/xfrm_sysctl.c | 77 ++++++++++++++++--------------------------
 1 file changed, 30 insertions(+), 47 deletions(-)

diff --git a/net/xfrm/xfrm_sysctl.c b/net/xfrm/xfrm_sysctl.c
index ca003e8a0376..1097dd4dc0dc 100644
--- a/net/xfrm/xfrm_sysctl.c
+++ b/net/xfrm/xfrm_sysctl.c
@@ -1,6 +1,5 @@
 // SPDX-License-Identifier: GPL-2.0
 #include <linux/sysctl.h>
-#include <linux/slab.h>
 #include <net/net_namespace.h>
 #include <net/xfrm.h>
 
@@ -13,71 +12,55 @@ static void __net_init __xfrm_sysctl_init(struct net *net)
 }
 
 #ifdef CONFIG_SYSCTL
-static struct ctl_table xfrm_table[] = {
-	{
-		.procname	= "xfrm_aevent_etime",
-		.maxlen		= sizeof(u32),
-		.mode		= 0644,
-		.proc_handler	= proc_douintvec
-	},
-	{
-		.procname	= "xfrm_aevent_rseqth",
-		.maxlen		= sizeof(u32),
-		.mode		= 0644,
-		.proc_handler	= proc_douintvec
-	},
-	{
-		.procname	= "xfrm_larval_drop",
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec
-	},
-	{
-		.procname	= "xfrm_acq_expires",
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec
-	},
+#define XFRM_UINT_DATA(name)						\
+static unsigned int *xfrm_ ## name ## _data(const struct sysctl_context *ctx) \
+{									\
+	return &ctx->ns.net_ns->xfrm.name;				\
+}
+
+#define XFRM_INT_DATA(name)						\
+static int *xfrm_ ## name ## _data(const struct sysctl_context *ctx)	\
+{									\
+	return &ctx->ns.net_ns->xfrm.name;				\
+}
+
+XFRM_UINT_DATA(sysctl_aevent_etime)
+XFRM_UINT_DATA(sysctl_aevent_rseqth)
+XFRM_INT_DATA(sysctl_larval_drop)
+XFRM_INT_DATA(sysctl_acq_expires)
+
+static const struct sysctl_field xfrm_table[] = {
+	SYSCTL_FIELD_UINT("xfrm_aevent_etime", 0644, xfrm_sysctl_aevent_etime_data),
+	SYSCTL_FIELD_UINT("xfrm_aevent_rseqth", 0644, xfrm_sysctl_aevent_rseqth_data),
+	SYSCTL_FIELD_INT("xfrm_larval_drop", 0644, xfrm_sysctl_larval_drop_data),
+	SYSCTL_FIELD_INT("xfrm_acq_expires", 0644, xfrm_sysctl_acq_expires_data),
 };
 
 int __net_init xfrm_sysctl_init(struct net *net)
 {
-	struct ctl_table *table;
+	struct sysctl_context ctx = {
+		.ns.net_ns = net,
+	};
 	size_t table_size = ARRAY_SIZE(xfrm_table);
 
 	__xfrm_sysctl_init(net);
 
-	table = kmemdup(xfrm_table, sizeof(xfrm_table), GFP_KERNEL);
-	if (!table)
-		goto out_kmemdup;
-	table[0].data = &net->xfrm.sysctl_aevent_etime;
-	table[1].data = &net->xfrm.sysctl_aevent_rseqth;
-	table[2].data = &net->xfrm.sysctl_larval_drop;
-	table[3].data = &net->xfrm.sysctl_acq_expires;
-
 	/* Don't export sysctls to unprivileged users */
 	if (net->user_ns != &init_user_ns)
 		table_size = 0;
 
-	net->xfrm.sysctl_hdr = register_net_sysctl_sz(net, "net/core", table,
-						      table_size);
+	net->xfrm.sysctl_hdr = __register_sysctl_fields(&net->sysctls, "net/core",
+							xfrm_table, table_size,
+							&ctx, sizeof(ctx));
 	if (!net->xfrm.sysctl_hdr)
-		goto out_register;
-	return 0;
+		return -ENOMEM;
 
-out_register:
-	kfree(table);
-out_kmemdup:
-	return -ENOMEM;
+	return 0;
 }
 
 void __net_exit xfrm_sysctl_fini(struct net *net)
 {
-	const struct ctl_table *table;
-
-	table = net->xfrm.sysctl_hdr->ctl_table_arg;
 	unregister_net_sysctl_table(net->xfrm.sysctl_hdr);
-	kfree(table);
 }
 #else
 int __net_init xfrm_sysctl_init(struct net *net)
-- 
2.55.0


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [RFC PATCH v1 10/30] sysctl: net: use sysctl_field for simple IPv4 per-net sysctls
       [not found] <cover.1787771905.git.legion@kernel.org>
                   ` (8 preceding siblings ...)
  2026-08-26 19:42 ` [RFC PATCH v1 09/30] sysctl: net: use sysctl_field in xfrm sysctls Alexey Gladkov
@ 2026-08-26 19:42 ` Alexey Gladkov
  2026-08-26 19:42 ` [RFC PATCH v1 11/30] sysctl: net: use sysctl_field in IPv4 sysctls Alexey Gladkov
                   ` (19 subsequent siblings)
  29 siblings, 0 replies; 37+ messages in thread
From: Alexey Gladkov @ 2026-08-26 19:42 UTC (permalink / raw)
  To: Linus Torvalds, Eric W . Biederman, Kees Cook, Joel Granados
  Cc: LKML, linux-fsdevel

Several IPv4 sysctl tables are cloned per network namespace only to bind
entries to namespace-local storage. The tables themselves are otherwise
static, and some entries also use extra fields only to recover the
owning network namespace.

Use sysctl_field for the simple per-net IPv4 sysctls in xfrm4, IP
fragment handling, and route configuration. The data pointers are
derived from the registration context, so the descriptors can stay const
and the per-net ctl_table allocation and free paths are no longer
needed.

Signed-off-by: Alexey Gladkov <legion@kernel.org>
---
 net/ipv4/ip_fragment.c  |  96 +++++++++++++++----------------------
 net/ipv4/route.c        | 102 ++++++++++++++++------------------------
 net/ipv4/xfrm4_policy.c |  52 ++++++--------------
 3 files changed, 92 insertions(+), 158 deletions(-)

diff --git a/net/ipv4/ip_fragment.c b/net/ipv4/ip_fragment.c
index 56b0f738d2f2..9d808ecf7b71 100644
--- a/net/ipv4/ip_fragment.c
+++ b/net/ipv4/ip_fragment.c
@@ -548,32 +548,37 @@ EXPORT_SYMBOL(ip_check_defrag);
 #ifdef CONFIG_SYSCTL
 static int dist_min;
 
-static struct ctl_table ip4_frags_ns_ctl_table[] = {
-	{
-		.procname	= "ipfrag_high_thresh",
-		.maxlen		= sizeof(unsigned long),
-		.mode		= 0644,
-		.proc_handler	= proc_doulongvec_minmax,
-	},
-	{
-		.procname	= "ipfrag_low_thresh",
-		.maxlen		= sizeof(unsigned long),
-		.mode		= 0644,
-		.proc_handler	= proc_doulongvec_minmax,
-	},
-	{
-		.procname	= "ipfrag_time",
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_jiffies,
-	},
-	{
-		.procname	= "ipfrag_max_dist",
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= &dist_min,
-	},
+static unsigned long *ip4_frags_high_thresh_data(const struct sysctl_context *ctx)
+{
+	return &ctx->ns.net_ns->ipv4.fqdir->high_thresh;
+}
+
+static unsigned long *ip4_frags_low_thresh_data(const struct sysctl_context *ctx)
+{
+	return &ctx->ns.net_ns->ipv4.fqdir->low_thresh;
+}
+
+static int *ip4_frags_max_dist_data(const struct sysctl_context *ctx)
+{
+	return &ctx->ns.net_ns->ipv4.fqdir->max_dist;
+}
+
+static void *ip4_frags_timeout_data(const struct sysctl_context *ctx)
+{
+	return &ctx->ns.net_ns->ipv4.fqdir->timeout;
+}
+
+static const struct sysctl_field ip4_frags_ns_ctl_table[] = {
+	SYSCTL_FIELD_ULONG_MINMAX("ipfrag_high_thresh", 0644,
+			       ip4_frags_high_thresh_data,
+			       ip4_frags_low_thresh_data, NULL),
+	SYSCTL_FIELD_ULONG_MINMAX("ipfrag_low_thresh", 0644,
+			       ip4_frags_low_thresh_data,
+			       NULL, ip4_frags_high_thresh_data),
+	SYSCTL_FIELD_CUSTOM("ipfrag_time", 0644, sizeof(int),
+			 ip4_frags_timeout_data, proc_dointvec_jiffies),
+	SYSCTL_FIELD_STATIC_INT_MINMAX("ipfrag_max_dist", 0644,
+				    ip4_frags_max_dist_data, &dist_min, NULL),
 };
 
 /* secret interval has been deprecated */
@@ -590,45 +595,20 @@ static struct ctl_table ip4_frags_ctl_table[] = {
 
 static int __net_init ip4_frags_ns_ctl_register(struct net *net)
 {
-	struct ctl_table *table;
-	struct ctl_table_header *hdr;
-
-	table = ip4_frags_ns_ctl_table;
-	if (!net_eq(net, &init_net)) {
-		table = kmemdup(table, sizeof(ip4_frags_ns_ctl_table), GFP_KERNEL);
-		if (!table)
-			goto err_alloc;
+	struct sysctl_context ctx = {
+		.ns.net_ns = net,
+	};
 
-	}
-	table[0].data	= &net->ipv4.fqdir->high_thresh;
-	table[0].extra1	= &net->ipv4.fqdir->low_thresh;
-	table[1].data	= &net->ipv4.fqdir->low_thresh;
-	table[1].extra2	= &net->ipv4.fqdir->high_thresh;
-	table[2].data	= &net->ipv4.fqdir->timeout;
-	table[3].data	= &net->ipv4.fqdir->max_dist;
-
-	hdr = register_net_sysctl_sz(net, "net/ipv4", table,
-				     ARRAY_SIZE(ip4_frags_ns_ctl_table));
-	if (!hdr)
-		goto err_reg;
-
-	net->ipv4.frags_hdr = hdr;
+	net->ipv4.frags_hdr = register_sysctl_fields(&net->sysctls, "net/ipv4",
+						     ip4_frags_ns_ctl_table, &ctx);
+	if (!net->ipv4.frags_hdr)
+		return -ENOMEM;
 	return 0;
-
-err_reg:
-	if (!net_eq(net, &init_net))
-		kfree(table);
-err_alloc:
-	return -ENOMEM;
 }
 
 static void __net_exit ip4_frags_ns_ctl_unregister(struct net *net)
 {
-	const struct ctl_table *table;
-
-	table = net->ipv4.frags_hdr->ctl_table_arg;
 	unregister_net_sysctl_table(net->ipv4.frags_hdr);
-	kfree(table);
 }
 
 static void __init ip4_frags_ctl_register(void)
diff --git a/net/ipv4/route.c b/net/ipv4/route.c
index 3d62d45d84bd..7b64756e0ccf 100644
--- a/net/ipv4/route.c
+++ b/net/ipv4/route.c
@@ -3471,7 +3471,7 @@ static int ip_min_valid_pmtu __read_mostly	= IPV4_MIN_MTU;
 static int ipv4_sysctl_rtcache_flush(const struct ctl_table *__ctl, int write,
 		void *buffer, size_t *lenp, loff_t *ppos)
 {
-	struct net *net = (struct net *)__ctl->extra1;
+	struct net *net = __ctl->data;
 
 	if (write) {
 		rt_cache_flush(net);
@@ -3573,85 +3573,63 @@ static struct ctl_table ipv4_route_table[] = {
 
 static const char ipv4_route_flush_procname[] = "flush";
 
-static struct ctl_table ipv4_route_netns_table[] = {
-	{
-		.procname	= ipv4_route_flush_procname,
-		.maxlen		= sizeof(int),
-		.mode		= 0200,
-		.proc_handler	= ipv4_sysctl_rtcache_flush,
-	},
-	{
-		.procname       = "min_pmtu",
-		.data           = &init_net.ipv4.ip_rt_min_pmtu,
-		.maxlen         = sizeof(int),
-		.mode           = 0644,
-		.proc_handler   = proc_dointvec_minmax,
-		.extra1         = &ip_min_valid_pmtu,
-	},
-	{
-		.procname       = "mtu_expires",
-		.data           = &init_net.ipv4.ip_rt_mtu_expires,
-		.maxlen         = sizeof(int),
-		.mode           = 0644,
-		.proc_handler   = proc_dointvec_jiffies,
-	},
-	{
-		.procname   = "min_adv_mss",
-		.data       = &init_net.ipv4.ip_rt_min_advmss,
-		.maxlen     = sizeof(int),
-		.mode       = 0644,
-		.proc_handler   = proc_dointvec,
-	},
+#define IPV4_ROUTE_DATA(name)						\
+static int *ipv4_route_ ## name ## _data(const struct sysctl_context *ctx)	\
+{									\
+	return &ctx->ns.net_ns->ipv4.name;				\
+}
+
+static void *ipv4_route_net_data(const struct sysctl_context *ctx)
+{
+	return ctx->ns.net_ns;
+}
+
+IPV4_ROUTE_DATA(ip_rt_min_pmtu)
+IPV4_ROUTE_DATA(ip_rt_min_advmss)
+
+static void *ipv4_route_ip_rt_mtu_expires_data(const struct sysctl_context *ctx)
+{
+	return &ctx->ns.net_ns->ipv4.ip_rt_mtu_expires;
+}
+
+static const struct sysctl_field ipv4_route_netns_table[] = {
+	SYSCTL_FIELD_CUSTOM(ipv4_route_flush_procname, 0200, sizeof(int),
+			 ipv4_route_net_data, ipv4_sysctl_rtcache_flush),
+	SYSCTL_FIELD_STATIC_INT_MINMAX("min_pmtu", 0644,
+				    ipv4_route_ip_rt_min_pmtu_data,
+				    &ip_min_valid_pmtu, NULL),
+	SYSCTL_FIELD_CUSTOM("mtu_expires", 0644, sizeof(int),
+			 ipv4_route_ip_rt_mtu_expires_data,
+			 proc_dointvec_jiffies),
+	SYSCTL_FIELD_INT("min_adv_mss", 0644, ipv4_route_ip_rt_min_advmss_data),
 };
 
 static __net_init int sysctl_route_net_init(struct net *net)
 {
-	struct ctl_table *tbl;
-	size_t table_size = ARRAY_SIZE(ipv4_route_netns_table);
+	struct sysctl_context ctx = {
+		.ns.net_ns = net,
+	};
 
-	tbl = ipv4_route_netns_table;
 	if (!net_eq(net, &init_net)) {
-		int i;
-
-		tbl = kmemdup(tbl, sizeof(ipv4_route_netns_table), GFP_KERNEL);
-		if (!tbl)
-			goto err_dup;
-
 		/* Don't export non-whitelisted sysctls to unprivileged users */
 		if (net->user_ns != &init_user_ns) {
-			if (tbl[0].procname != ipv4_route_flush_procname)
-				table_size = 0;
+			if (ipv4_route_netns_table[0].procname !=
+			    ipv4_route_flush_procname)
+				return 0;
 		}
-
-		/* Update the variables to point into the current struct net
-		 * except for the first element flush
-		 */
-		for (i = 1; i < table_size; i++)
-			tbl[i].data += (void *)net - (void *)&init_net;
 	}
-	tbl[0].extra1 = net;
 
-	net->ipv4.route_hdr = register_net_sysctl_sz(net, "net/ipv4/route",
-						     tbl, table_size);
+	net->ipv4.route_hdr = register_sysctl_fields(&net->sysctls,
+						     "net/ipv4/route",
+						     ipv4_route_netns_table, &ctx);
 	if (!net->ipv4.route_hdr)
-		goto err_reg;
+		return -ENOMEM;
 	return 0;
-
-err_reg:
-	if (tbl != ipv4_route_netns_table)
-		kfree(tbl);
-err_dup:
-	return -ENOMEM;
 }
 
 static __net_exit void sysctl_route_net_exit(struct net *net)
 {
-	const struct ctl_table *tbl;
-
-	tbl = net->ipv4.route_hdr->ctl_table_arg;
 	unregister_net_sysctl_table(net->ipv4.route_hdr);
-	BUG_ON(tbl == ipv4_route_netns_table);
-	kfree(tbl);
 }
 
 static __net_initdata struct pernet_operations sysctl_route_ops = {
diff --git a/net/ipv4/xfrm4_policy.c b/net/ipv4/xfrm4_policy.c
index 58faf1ddd2b1..be871b3ef3bd 100644
--- a/net/ipv4/xfrm4_policy.c
+++ b/net/ipv4/xfrm4_policy.c
@@ -141,56 +141,32 @@ static const struct xfrm_policy_afinfo xfrm4_policy_afinfo = {
 };
 
 #ifdef CONFIG_SYSCTL
-static struct ctl_table xfrm4_policy_table[] = {
-	{
-		.procname       = "xfrm4_gc_thresh",
-		.data           = &init_net.xfrm.xfrm4_dst_ops.gc_thresh,
-		.maxlen         = sizeof(int),
-		.mode           = 0644,
-		.proc_handler   = proc_dointvec,
-	},
+static int *xfrm4_gc_thresh_data(const struct sysctl_context *ctx)
+{
+	return &ctx->ns.net_ns->xfrm.xfrm4_dst_ops.gc_thresh;
+}
+
+static const struct sysctl_field xfrm4_policy_table[] = {
+	SYSCTL_FIELD_INT("xfrm4_gc_thresh", 0644, xfrm4_gc_thresh_data),
 };
 
 static __net_init int xfrm4_net_sysctl_init(struct net *net)
 {
-	struct ctl_table *table;
-	struct ctl_table_header *hdr;
-
-	table = xfrm4_policy_table;
-	if (!net_eq(net, &init_net)) {
-		table = kmemdup(table, sizeof(xfrm4_policy_table), GFP_KERNEL);
-		if (!table)
-			goto err_alloc;
-
-		table[0].data = &net->xfrm.xfrm4_dst_ops.gc_thresh;
-	}
+	struct sysctl_context ctx = {
+		.ns.net_ns = net,
+	};
 
-	hdr = register_net_sysctl_sz(net, "net/ipv4", table,
-				     ARRAY_SIZE(xfrm4_policy_table));
-	if (!hdr)
-		goto err_reg;
+	net->ipv4.xfrm4_hdr = register_sysctl_fields(&net->sysctls, "net/ipv4",
+						     xfrm4_policy_table, &ctx);
+	if (!net->ipv4.xfrm4_hdr)
+		return -ENOMEM;
 
-	net->ipv4.xfrm4_hdr = hdr;
 	return 0;
-
-err_reg:
-	if (!net_eq(net, &init_net))
-		kfree(table);
-err_alloc:
-	return -ENOMEM;
 }
 
 static __net_exit void xfrm4_net_sysctl_exit(struct net *net)
 {
-	const struct ctl_table *table;
-
-	if (!net->ipv4.xfrm4_hdr)
-		return;
-
-	table = net->ipv4.xfrm4_hdr->ctl_table_arg;
 	unregister_net_sysctl_table(net->ipv4.xfrm4_hdr);
-	if (!net_eq(net, &init_net))
-		kfree(table);
 }
 #else /* CONFIG_SYSCTL */
 static inline int xfrm4_net_sysctl_init(struct net *net)
-- 
2.55.0


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [RFC PATCH v1 11/30] sysctl: net: use sysctl_field in IPv4 sysctls
       [not found] <cover.1787771905.git.legion@kernel.org>
                   ` (9 preceding siblings ...)
  2026-08-26 19:42 ` [RFC PATCH v1 10/30] sysctl: net: use sysctl_field for simple IPv4 per-net sysctls Alexey Gladkov
@ 2026-08-26 19:42 ` Alexey Gladkov
  2026-08-26 19:42 ` [RFC PATCH v1 12/30] sysctl: net: use sysctl_field in IPv6 xfrm sysctls Alexey Gladkov
                   ` (18 subsequent siblings)
  29 siblings, 0 replies; 37+ messages in thread
From: Alexey Gladkov @ 2026-08-26 19:42 UTC (permalink / raw)
  To: Linus Torvalds, Eric W . Biederman, Kees Cook, Joel Granados
  Cc: LKML, linux-fsdevel

The per-net IPv4 sysctl table is cloned for every network namespace so
that data pointers can be rebound from init_net to the namespace being
registered. The table is otherwise static, and the clone also relies on
mutating selected ctl_table entries before registration.

Use sysctl_field descriptors for the per-net IPv4 sysctls instead. The
data pointers are derived from the registration context, so the table
can stay const and the per-net ctl_table allocation, offset patching,
and free path are no longer needed.

Keep the global IPv4 sysctl table on ctl_table, since it is registered
only for init_net and is not part of the per-net clone path.

Signed-off-by: Alexey Gladkov <legion@kernel.org>
---
 net/ipv4/sysctl_net_ipv4.c | 1622 +++++++++++++-----------------------
 1 file changed, 559 insertions(+), 1063 deletions(-)

diff --git a/net/ipv4/sysctl_net_ipv4.c b/net/ipv4/sysctl_net_ipv4.c
index d8bdb1bdbff1..75860e7f272e 100644
--- a/net/ipv4/sysctl_net_ipv4.c
+++ b/net/ipv4/sysctl_net_ipv4.c
@@ -20,22 +20,22 @@
 #include <net/protocol.h>
 #include <net/netevent.h>
 
-static int tcp_retr1_max = 255;
+static unsigned int tcp_retr1_max = 255;
 static int ip_local_port_range_min[] = { 1, 1 };
 static int ip_local_port_range_max[] = { 65535, 65535 };
 static int tcp_adv_win_scale_min = -31;
 static int tcp_adv_win_scale_max = 31;
-static int tcp_app_win_max = 31;
+static unsigned int tcp_app_win_max = 31;
 static int tcp_min_snd_mss_min = TCP_MIN_SND_MSS;
 static int tcp_min_snd_mss_max = 65535;
 static int tcp_rto_max_max = TCP_RTO_MAX_SEC * MSEC_PER_SEC;
 static int ip_privileged_port_min;
 static int ip_privileged_port_max = 65535;
-static int ip_ttl_min = 1;
-static int ip_ttl_max = 255;
-static int tcp_syn_retries_min = 1;
-static int tcp_syn_retries_max = MAX_TCP_SYNCNT;
-static int tcp_syn_linear_timeouts_max = MAX_TCP_SYNCNT;
+static unsigned int ip_ttl_min = 1;
+static unsigned int ip_ttl_max = 255;
+static unsigned int tcp_syn_retries_min = 1;
+static unsigned int tcp_syn_retries_max = MAX_TCP_SYNCNT;
+static unsigned int tcp_syn_linear_timeouts_max = MAX_TCP_SYNCNT;
 static unsigned long ip_ping_group_range_min[] = { 0, 0 };
 static unsigned long ip_ping_group_range_max[] = { GID_T_MAX, GID_T_MAX };
 static u32 u32_max_div_HZ = UINT_MAX / HZ;
@@ -44,11 +44,11 @@ static u32 fib_multipath_hash_fields_all_mask __maybe_unused =
 	FIB_MULTIPATH_HASH_FIELD_ALL_MASK;
 static unsigned int tcp_child_ehash_entries_max = 16 * 1024 * 1024;
 static unsigned int udp_child_hash_entries_max = UDP_HTABLE_SIZE_MAX;
-static int tcp_plb_max_rounds = 31;
+static unsigned int tcp_plb_max_rounds = 31;
 static int tcp_plb_max_cong_thresh = 256;
 static unsigned int tcp_tw_reuse_delay_max = TCP_PAWS_MSL * MSEC_PER_SEC;
-static int tcp_ecn_mode_max = 5;
-static u32 icmp_errors_extension_mask_all =
+static unsigned int tcp_ecn_mode_max = 5;
+static unsigned int icmp_errors_extension_mask_all =
 	GENMASK_U8(ICMP_ERR_EXT_COUNT - 1, 0);
 
 /* obsolete */
@@ -203,11 +203,16 @@ static int ipv4_fwd_update_priority(const struct ctl_table *table, int write,
 				    void *buffer, size_t *lenp, loff_t *ppos)
 {
 	struct net *net;
+	struct ctl_table tmp;
 	int ret;
 
 	net = container_of(table->data, struct net,
 			   ipv4.sysctl_ip_fwd_update_priority);
-	ret = proc_dou8vec_minmax(table, write, buffer, lenp, ppos);
+	tmp = *table;
+	tmp.extra1 = SYSCTL_UINT_ZERO;
+	tmp.extra2 = SYSCTL_UINT_ONE;
+
+	ret = proc_dou8vec_minmax(&tmp, write, buffer, lenp, ppos);
 	if (write && ret == 0)
 		call_netevent_notifiers(NETEVENT_IPV4_FWD_UPDATE_PRIORITY_UPDATE,
 					net);
@@ -365,15 +370,28 @@ static int proc_tfo_blackhole_detect_timeout(const struct ctl_table *table,
 {
 	struct net *net = container_of(table->data, struct net,
 	    ipv4.sysctl_tcp_fastopen_blackhole_timeout);
+	struct ctl_table tmp = *table;
 	int ret;
 
-	ret = proc_dointvec_minmax(table, write, buffer, lenp, ppos);
+	tmp.extra1 = SYSCTL_ZERO;
+
+	ret = proc_dointvec_minmax(&tmp, write, buffer, lenp, ppos);
 	if (write && ret == 0)
 		atomic_set(&net->ipv4.tfo_active_disable_times, 0);
 
 	return ret;
 }
 
+static int proc_dointvec_minmax_one(const struct ctl_table *table, int write,
+				    void *buffer, size_t *lenp, loff_t *ppos)
+{
+	struct ctl_table tmp = *table;
+
+	tmp.extra1 = SYSCTL_ONE;
+
+	return proc_dointvec_minmax(&tmp, write, buffer, lenp, ppos);
+}
+
 static int proc_tcp_available_ulp(const struct ctl_table *ctl,
 				  int write, void *buffer, size_t *lenp,
 				  loff_t *ppos)
@@ -445,9 +463,13 @@ static int proc_fib_multipath_hash_policy(const struct ctl_table *table, int wri
 {
 	struct net *net = container_of(table->data, struct net,
 	    ipv4.sysctl_fib_multipath_hash_policy);
+	struct ctl_table tmp = *table;
 	int ret;
 
-	ret = proc_dou8vec_minmax(table, write, buffer, lenp, ppos);
+	tmp.extra1 = SYSCTL_UINT_ZERO;
+	tmp.extra2 = SYSCTL_UINT_THREE;
+
+	ret = proc_dou8vec_minmax(&tmp, write, buffer, lenp, ppos);
 	if (write && ret == 0)
 		call_netevent_notifiers(NETEVENT_IPV4_MPATH_HASH_UPDATE, net);
 
@@ -459,11 +481,15 @@ static int proc_fib_multipath_hash_fields(const struct ctl_table *table, int wri
 					  loff_t *ppos)
 {
 	struct net *net;
+	struct ctl_table tmp = *table;
 	int ret;
 
 	net = container_of(table->data, struct net,
 			   ipv4.sysctl_fib_multipath_hash_fields);
-	ret = proc_douintvec_minmax(table, write, buffer, lenp, ppos);
+	tmp.extra1 = SYSCTL_ONE;
+	tmp.extra2 = &fib_multipath_hash_fields_all_mask;
+
+	ret = proc_douintvec_minmax(&tmp, write, buffer, lenp, ppos);
 	if (write && ret == 0)
 		call_netevent_notifiers(NETEVENT_IPV4_MPATH_HASH_UPDATE, net);
 
@@ -624,1065 +650,543 @@ static struct ctl_table ipv4_table[] = {
 	},
 };
 
-static struct ctl_table ipv4_net_table[] = {
-	{
-		.procname	= "tcp_max_tw_buckets",
-		.data		= &init_net.ipv4.tcp_death_row.sysctl_max_tw_buckets,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec
-	},
-	{
-		.procname	= "icmp_echo_ignore_all",
-		.data		= &init_net.ipv4.sysctl_icmp_echo_ignore_all,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_ONE
-	},
-	{
-		.procname	= "icmp_echo_enable_probe",
-		.data		= &init_net.ipv4.sysctl_icmp_echo_enable_probe,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_ONE
-	},
-	{
-		.procname	= "icmp_echo_ignore_broadcasts",
-		.data		= &init_net.ipv4.sysctl_icmp_echo_ignore_broadcasts,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_ONE
-	},
-	{
-		.procname	= "icmp_ignore_bogus_error_responses",
-		.data		= &init_net.ipv4.sysctl_icmp_ignore_bogus_error_responses,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_ONE
-	},
-	{
-		.procname	= "icmp_errors_use_inbound_ifaddr",
-		.data		= &init_net.ipv4.sysctl_icmp_errors_use_inbound_ifaddr,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_ONE
-	},
-	{
-		.procname	= "icmp_errors_extension_mask",
-		.data		= &init_net.ipv4.sysctl_icmp_errors_extension_mask,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= &icmp_errors_extension_mask_all,
-	},
-	{
-		.procname	= "icmp_ratelimit",
-		.data		= &init_net.ipv4.sysctl_icmp_ratelimit,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_ms_jiffies,
-	},
-	{
-		.procname	= "icmp_ratemask",
-		.data		= &init_net.ipv4.sysctl_icmp_ratemask,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec
-	},
-	{
-		.procname	= "icmp_msgs_per_sec",
-		.data		= &init_net.ipv4.sysctl_icmp_msgs_per_sec,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ZERO,
-	},
-	{
-		.procname	= "icmp_msgs_burst",
-		.data		= &init_net.ipv4.sysctl_icmp_msgs_burst,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ZERO,
-	},
-	{
-		.procname	= "ping_group_range",
-		.data		= &init_net.ipv4.ping_group_range.range,
-		.maxlen		= sizeof(gid_t)*2,
-		.mode		= 0644,
-		.proc_handler	= ipv4_ping_group_range,
-	},
+static umode_t ipv4_init_net_writable_mode(const struct sysctl_context *ctx)
+{
+	return net_eq(ctx->ns.net_ns, &init_net) ? 0644 : 0444;
+}
+
+static void *ipv4_netns_data(const struct sysctl_context *ctx)
+{
+	return ctx->ns.net_ns;
+}
+
+#define IPV4_DATA(type, name, field)					\
+static type *ipv4_ ## name ## _data(const struct sysctl_context *ctx)	\
+{									\
+	return &ctx->ns.net_ns->ipv4.field;				\
+}
+
+#define IPV4_SYSCTL_DATA(type, name)	IPV4_DATA(type, name, sysctl_ ## name)
+
+IPV4_DATA(int, tcp_max_tw_buckets, tcp_death_row.sysctl_max_tw_buckets)
+IPV4_DATA(void, ping_group_range, ping_group_range.range)
+IPV4_DATA(void, ip_local_reserved_ports, sysctl_local_reserved_ports)
+IPV4_DATA(u8, ip_forward_use_pmtu, sysctl_ip_fwd_use_pmtu)
+IPV4_DATA(void, ip_forward_update_priority, sysctl_ip_fwd_update_priority)
+IPV4_DATA(u8, igmp_link_local_mcast_reports, sysctl_igmp_llm_reports)
+IPV4_DATA(void, tcp_congestion_control, tcp_congestion_control)
+IPV4_DATA(int, tcp_max_syn_backlog, sysctl_max_syn_backlog)
+IPV4_DATA(void, tcp_fastopen_key, sysctl_tcp_fastopen)
+IPV4_DATA(void, tcp_fastopen_blackhole_timeout_sec, sysctl_tcp_fastopen_blackhole_timeout)
+IPV4_DATA(void, ip_unprivileged_port_start, sysctl_ip_prot_sock)
+IPV4_DATA(u8, tcp_no_metrics_save, sysctl_tcp_nometrics_save)
+IPV4_DATA(void, tcp_ehash_entries, sysctl_tcp_child_ehash_entries)
+IPV4_DATA(void, udp_hash_entries, sysctl_udp_child_hash_entries)
+
+IPV4_SYSCTL_DATA(u8, icmp_echo_ignore_all)
+IPV4_SYSCTL_DATA(u8, icmp_echo_enable_probe)
+IPV4_SYSCTL_DATA(u8, icmp_echo_ignore_broadcasts)
+IPV4_SYSCTL_DATA(u8, icmp_ignore_bogus_error_responses)
+IPV4_SYSCTL_DATA(u8, icmp_errors_use_inbound_ifaddr)
+IPV4_SYSCTL_DATA(u8, icmp_errors_extension_mask)
+IPV4_SYSCTL_DATA(void, icmp_ratelimit)
+IPV4_SYSCTL_DATA(int, icmp_ratemask)
+IPV4_SYSCTL_DATA(int, icmp_msgs_per_sec)
+IPV4_SYSCTL_DATA(int, icmp_msgs_burst)
 #ifdef CONFIG_NET_L3_MASTER_DEV
-	{
-		.procname	= "raw_l3mdev_accept",
-		.data		= &init_net.ipv4.sysctl_raw_l3mdev_accept,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_ONE,
-	},
+IPV4_SYSCTL_DATA(u8, raw_l3mdev_accept)
 #endif
-	{
-		.procname	= "tcp_ecn",
-		.data		= &init_net.ipv4.sysctl_tcp_ecn,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= &tcp_ecn_mode_max,
-	},
-	{
-		.procname	= "tcp_ecn_option",
-		.data		= &init_net.ipv4.sysctl_tcp_ecn_option,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_THREE,
-	},
-	{
-		.procname	= "tcp_ecn_option_beacon",
-		.data		= &init_net.ipv4.sysctl_tcp_ecn_option_beacon,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_THREE,
-	},
-	{
-		.procname	= "tcp_ecn_fallback",
-		.data		= &init_net.ipv4.sysctl_tcp_ecn_fallback,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_ONE,
-	},
-	{
-		.procname	= "ip_dynaddr",
-		.data		= &init_net.ipv4.sysctl_ip_dynaddr,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-	},
-	{
-		.procname	= "ip_early_demux",
-		.data		= &init_net.ipv4.sysctl_ip_early_demux,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-	},
-	{
-		.procname       = "udp_early_demux",
-		.data           = &init_net.ipv4.sysctl_udp_early_demux,
-		.maxlen         = sizeof(u8),
-		.mode           = 0644,
-		.proc_handler   = proc_dou8vec_minmax,
-	},
-	{
-		.procname       = "tcp_early_demux",
-		.data           = &init_net.ipv4.sysctl_tcp_early_demux,
-		.maxlen         = sizeof(u8),
-		.mode           = 0644,
-		.proc_handler   = proc_dou8vec_minmax,
-	},
-	{
-		.procname       = "nexthop_compat_mode",
-		.data           = &init_net.ipv4.sysctl_nexthop_compat_mode,
-		.maxlen         = sizeof(u8),
-		.mode           = 0644,
-		.proc_handler   = proc_dou8vec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_ONE,
-	},
-	{
-		.procname	= "ip_default_ttl",
-		.data		= &init_net.ipv4.sysctl_ip_default_ttl,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra1		= &ip_ttl_min,
-		.extra2		= &ip_ttl_max,
-	},
-	{
-		.procname	= "ip_local_port_range",
-		.maxlen		= 0,
-		.data		= &init_net,
-		.mode		= 0644,
-		.proc_handler	= ipv4_local_port_range,
-	},
-	{
-		.procname	= "ip_local_port_step_width",
-		.maxlen		= sizeof(u32),
-		.data		= &init_net.ipv4.sysctl_ip_local_port_step_width,
-		.mode		= 0644,
-		.proc_handler	= proc_douintvec,
-	},
-	{
-		.procname	= "ip_local_reserved_ports",
-		.data		= &init_net.ipv4.sysctl_local_reserved_ports,
-		.maxlen		= 65536,
-		.mode		= 0644,
-		.proc_handler	= proc_do_large_bitmap,
-	},
-	{
-		.procname	= "ip_no_pmtu_disc",
-		.data		= &init_net.ipv4.sysctl_ip_no_pmtu_disc,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-	},
-	{
-		.procname	= "ip_forward_use_pmtu",
-		.data		= &init_net.ipv4.sysctl_ip_fwd_use_pmtu,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-	},
-	{
-		.procname	= "ip_forward_update_priority",
-		.data		= &init_net.ipv4.sysctl_ip_fwd_update_priority,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler   = ipv4_fwd_update_priority,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_ONE,
-	},
-	{
-		.procname	= "ip_nonlocal_bind",
-		.data		= &init_net.ipv4.sysctl_ip_nonlocal_bind,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-	},
-	{
-		.procname	= "ip_autobind_reuse",
-		.data		= &init_net.ipv4.sysctl_ip_autobind_reuse,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra1         = SYSCTL_ZERO,
-		.extra2         = SYSCTL_ONE,
-	},
-	{
-		.procname	= "fwmark_reflect",
-		.data		= &init_net.ipv4.sysctl_fwmark_reflect,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-	},
-	{
-		.procname	= "tcp_fwmark_accept",
-		.data		= &init_net.ipv4.sysctl_tcp_fwmark_accept,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-	},
+IPV4_SYSCTL_DATA(u8, tcp_ecn)
+IPV4_SYSCTL_DATA(u8, tcp_ecn_option)
+IPV4_SYSCTL_DATA(u8, tcp_ecn_option_beacon)
+IPV4_SYSCTL_DATA(u8, tcp_ecn_fallback)
+IPV4_SYSCTL_DATA(u8, ip_dynaddr)
+IPV4_SYSCTL_DATA(u8, ip_early_demux)
+IPV4_SYSCTL_DATA(u8, udp_early_demux)
+IPV4_SYSCTL_DATA(u8, tcp_early_demux)
+IPV4_SYSCTL_DATA(u8, nexthop_compat_mode)
+IPV4_SYSCTL_DATA(u8, ip_default_ttl)
+IPV4_SYSCTL_DATA(unsigned int, ip_local_port_step_width)
+IPV4_SYSCTL_DATA(u8, ip_no_pmtu_disc)
+IPV4_SYSCTL_DATA(u8, ip_nonlocal_bind)
+IPV4_SYSCTL_DATA(u8, ip_autobind_reuse)
+IPV4_SYSCTL_DATA(u8, fwmark_reflect)
+IPV4_SYSCTL_DATA(u8, tcp_fwmark_accept)
+IPV4_SYSCTL_DATA(u8, tcp_mtu_probing)
 #ifdef CONFIG_NET_L3_MASTER_DEV
-	{
-		.procname	= "tcp_l3mdev_accept",
-		.data		= &init_net.ipv4.sysctl_tcp_l3mdev_accept,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_ONE,
-	},
+IPV4_SYSCTL_DATA(u8, tcp_l3mdev_accept)
 #endif
-	{
-		.procname	= "tcp_mtu_probing",
-		.data		= &init_net.ipv4.sysctl_tcp_mtu_probing,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-	},
-	{
-		.procname	= "tcp_base_mss",
-		.data		= &init_net.ipv4.sysctl_tcp_base_mss,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
-	{
-		.procname	= "tcp_min_snd_mss",
-		.data		= &init_net.ipv4.sysctl_tcp_min_snd_mss,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= &tcp_min_snd_mss_min,
-		.extra2		= &tcp_min_snd_mss_max,
-	},
-	{
-		.procname	= "tcp_mtu_probe_floor",
-		.data		= &init_net.ipv4.sysctl_tcp_mtu_probe_floor,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= &tcp_min_snd_mss_min,
-		.extra2		= &tcp_min_snd_mss_max,
-	},
-	{
-		.procname	= "tcp_probe_threshold",
-		.data		= &init_net.ipv4.sysctl_tcp_probe_threshold,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
-	{
-		.procname	= "tcp_probe_interval",
-		.data		= &init_net.ipv4.sysctl_tcp_probe_interval,
-		.maxlen		= sizeof(u32),
-		.mode		= 0644,
-		.proc_handler	= proc_douintvec_minmax,
-		.extra2		= &u32_max_div_HZ,
-	},
-	{
-		.procname	= "igmp_link_local_mcast_reports",
-		.data		= &init_net.ipv4.sysctl_igmp_llm_reports,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-	},
-	{
-		.procname	= "igmp_max_memberships",
-		.data		= &init_net.ipv4.sysctl_igmp_max_memberships,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec
-	},
-	{
-		.procname	= "igmp_max_msf",
-		.data		= &init_net.ipv4.sysctl_igmp_max_msf,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec
-	},
+IPV4_SYSCTL_DATA(int, tcp_base_mss)
+IPV4_SYSCTL_DATA(int, tcp_min_snd_mss)
+IPV4_SYSCTL_DATA(int, tcp_mtu_probe_floor)
+IPV4_SYSCTL_DATA(int, tcp_probe_threshold)
+IPV4_SYSCTL_DATA(unsigned int, tcp_probe_interval)
+IPV4_SYSCTL_DATA(int, igmp_max_memberships)
+IPV4_SYSCTL_DATA(int, igmp_max_msf)
 #ifdef CONFIG_IP_MULTICAST
-	{
-		.procname	= "igmp_qrv",
-		.data		= &init_net.ipv4.sysctl_igmp_qrv,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ONE
-	},
+IPV4_SYSCTL_DATA(int, igmp_qrv)
 #endif
-	{
-		.procname	= "tcp_congestion_control",
-		.data		= &init_net.ipv4.tcp_congestion_control,
-		.mode		= 0644,
-		.maxlen		= TCP_CA_NAME_MAX,
-		.proc_handler	= proc_tcp_congestion_control,
-	},
-	{
-		.procname	= "tcp_available_congestion_control",
-		.maxlen		= TCP_CA_BUF_MAX,
-		.mode		= 0444,
-		.proc_handler   = proc_tcp_available_congestion_control,
-	},
-	{
-		.procname	= "tcp_allowed_congestion_control",
-		.maxlen		= TCP_CA_BUF_MAX,
-		.mode		= 0644,
-		.proc_handler   = proc_allowed_congestion_control,
-	},
-	{
-		.procname	= "tcp_keepalive_time",
-		.data		= &init_net.ipv4.sysctl_tcp_keepalive_time,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_jiffies,
-	},
-	{
-		.procname	= "tcp_keepalive_probes",
-		.data		= &init_net.ipv4.sysctl_tcp_keepalive_probes,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-	},
-	{
-		.procname	= "tcp_keepalive_intvl",
-		.data		= &init_net.ipv4.sysctl_tcp_keepalive_intvl,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_jiffies,
-	},
-	{
-		.procname	= "tcp_syn_retries",
-		.data		= &init_net.ipv4.sysctl_tcp_syn_retries,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra1		= &tcp_syn_retries_min,
-		.extra2		= &tcp_syn_retries_max
-	},
-	{
-		.procname	= "tcp_synack_retries",
-		.data		= &init_net.ipv4.sysctl_tcp_synack_retries,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-	},
+IPV4_SYSCTL_DATA(void, tcp_keepalive_time)
+IPV4_SYSCTL_DATA(u8, tcp_keepalive_probes)
+IPV4_SYSCTL_DATA(void, tcp_keepalive_intvl)
+IPV4_SYSCTL_DATA(u8, tcp_syn_retries)
+IPV4_SYSCTL_DATA(u8, tcp_synack_retries)
 #ifdef CONFIG_SYN_COOKIES
-	{
-		.procname	= "tcp_syncookies",
-		.data		= &init_net.ipv4.sysctl_tcp_syncookies,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-	},
+IPV4_SYSCTL_DATA(u8, tcp_syncookies)
 #endif
-	{
-		.procname	= "tcp_migrate_req",
-		.data		= &init_net.ipv4.sysctl_tcp_migrate_req,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_ONE
-	},
-	{
-		.procname	= "tcp_reordering",
-		.data		= &init_net.ipv4.sysctl_tcp_reordering,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec
-	},
-	{
-		.procname	= "tcp_retries1",
-		.data		= &init_net.ipv4.sysctl_tcp_retries1,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra2		= &tcp_retr1_max
-	},
-	{
-		.procname	= "tcp_retries2",
-		.data		= &init_net.ipv4.sysctl_tcp_retries2,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-	},
-	{
-		.procname	= "tcp_orphan_retries",
-		.data		= &init_net.ipv4.sysctl_tcp_orphan_retries,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-	},
-	{
-		.procname	= "tcp_fin_timeout",
-		.data		= &init_net.ipv4.sysctl_tcp_fin_timeout,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_jiffies,
-	},
-	{
-		.procname	= "tcp_notsent_lowat",
-		.data		= &init_net.ipv4.sysctl_tcp_notsent_lowat,
-		.maxlen		= sizeof(unsigned int),
-		.mode		= 0644,
-		.proc_handler	= proc_douintvec,
-	},
-	{
-		.procname	= "tcp_tw_reuse",
-		.data		= &init_net.ipv4.sysctl_tcp_tw_reuse,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_TWO,
-	},
-	{
-		.procname	= "tcp_tw_reuse_delay",
-		.data		= &init_net.ipv4.sysctl_tcp_tw_reuse_delay,
-		.maxlen		= sizeof(unsigned int),
-		.mode		= 0644,
-		.proc_handler	= proc_douintvec_minmax,
-		.extra1		= SYSCTL_ONE,
-		.extra2		= &tcp_tw_reuse_delay_max,
-	},
-	{
-		.procname	= "tcp_max_syn_backlog",
-		.data		= &init_net.ipv4.sysctl_max_syn_backlog,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec
-	},
-	{
-		.procname	= "tcp_fastopen",
-		.data		= &init_net.ipv4.sysctl_tcp_fastopen,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
-	{
-		.procname	= "tcp_fastopen_key",
-		.mode		= 0600,
-		.data		= &init_net.ipv4.sysctl_tcp_fastopen,
-		/* maxlen to print the list of keys in hex (*2), with dashes
-		 * separating doublewords and a comma in between keys.
-		 */
-		.maxlen		= ((TCP_FASTOPEN_KEY_LENGTH *
-				   2 * TCP_FASTOPEN_KEY_MAX) +
-				   (TCP_FASTOPEN_KEY_MAX * 5)),
-		.proc_handler	= proc_tcp_fastopen_key,
-	},
-	{
-		.procname	= "tcp_fastopen_blackhole_timeout_sec",
-		.data		= &init_net.ipv4.sysctl_tcp_fastopen_blackhole_timeout,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_tfo_blackhole_detect_timeout,
-		.extra1		= SYSCTL_ZERO,
-	},
+IPV4_SYSCTL_DATA(u8, tcp_migrate_req)
+IPV4_SYSCTL_DATA(int, tcp_reordering)
+IPV4_SYSCTL_DATA(u8, tcp_retries1)
+IPV4_SYSCTL_DATA(u8, tcp_retries2)
+IPV4_SYSCTL_DATA(u8, tcp_orphan_retries)
+IPV4_SYSCTL_DATA(void, tcp_fin_timeout)
+IPV4_SYSCTL_DATA(unsigned int, tcp_notsent_lowat)
+IPV4_SYSCTL_DATA(u8, tcp_tw_reuse)
+IPV4_SYSCTL_DATA(unsigned int, tcp_tw_reuse_delay)
+IPV4_SYSCTL_DATA(int, tcp_fastopen)
 #ifdef CONFIG_IP_ROUTE_MULTIPATH
-	{
-		.procname	= "fib_multipath_use_neigh",
-		.data		= &init_net.ipv4.sysctl_fib_multipath_use_neigh,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_ONE,
-	},
-	{
-		.procname	= "fib_multipath_hash_policy",
-		.data		= &init_net.ipv4.sysctl_fib_multipath_hash_policy,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_fib_multipath_hash_policy,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_THREE,
-	},
-	{
-		.procname	= "fib_multipath_hash_fields",
-		.data		= &init_net.ipv4.sysctl_fib_multipath_hash_fields,
-		.maxlen		= sizeof(u32),
-		.mode		= 0644,
-		.proc_handler	= proc_fib_multipath_hash_fields,
-		.extra1		= SYSCTL_ONE,
-		.extra2		= &fib_multipath_hash_fields_all_mask,
-	},
-	{
-		.procname	= "fib_multipath_hash_seed",
-		.data		= &init_net,
-		.maxlen		= sizeof(u32),
-		.mode		= 0644,
-		.proc_handler	= proc_fib_multipath_hash_seed,
-	},
+IPV4_SYSCTL_DATA(u8, fib_multipath_use_neigh)
+IPV4_SYSCTL_DATA(void, fib_multipath_hash_policy)
+IPV4_SYSCTL_DATA(void, fib_multipath_hash_fields)
 #endif
-	{
-		.procname	= "ip_unprivileged_port_start",
-		.maxlen		= sizeof(int),
-		.data		= &init_net.ipv4.sysctl_ip_prot_sock,
-		.mode		= 0644,
-		.proc_handler	= ipv4_privileged_ports,
-	},
 #ifdef CONFIG_NET_L3_MASTER_DEV
-	{
-		.procname	= "udp_l3mdev_accept",
-		.data		= &init_net.ipv4.sysctl_udp_l3mdev_accept,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_ONE,
-	},
+IPV4_SYSCTL_DATA(u8, udp_l3mdev_accept)
 #endif
-	{
-		.procname	= "tcp_sack",
-		.data		= &init_net.ipv4.sysctl_tcp_sack,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-	},
-	{
-		.procname	= "tcp_window_scaling",
-		.data		= &init_net.ipv4.sysctl_tcp_window_scaling,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-	},
-	{
-		.procname	= "tcp_timestamps",
-		.data		= &init_net.ipv4.sysctl_tcp_timestamps,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-	},
-	{
-		.procname	= "tcp_early_retrans",
-		.data		= &init_net.ipv4.sysctl_tcp_early_retrans,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_FOUR,
-	},
-	{
-		.procname	= "tcp_recovery",
-		.data		= &init_net.ipv4.sysctl_tcp_recovery,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-	},
-	{
-		.procname       = "tcp_thin_linear_timeouts",
-		.data           = &init_net.ipv4.sysctl_tcp_thin_linear_timeouts,
-		.maxlen         = sizeof(u8),
-		.mode           = 0644,
-		.proc_handler   = proc_dou8vec_minmax,
-	},
-	{
-		.procname	= "tcp_slow_start_after_idle",
-		.data		= &init_net.ipv4.sysctl_tcp_slow_start_after_idle,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-	},
-	{
-		.procname	= "tcp_retrans_collapse",
-		.data		= &init_net.ipv4.sysctl_tcp_retrans_collapse,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-	},
-	{
-		.procname	= "tcp_stdurg",
-		.data		= &init_net.ipv4.sysctl_tcp_stdurg,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-	},
-	{
-		.procname	= "tcp_rfc1337",
-		.data		= &init_net.ipv4.sysctl_tcp_rfc1337,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-	},
-	{
-		.procname	= "tcp_abort_on_overflow",
-		.data		= &init_net.ipv4.sysctl_tcp_abort_on_overflow,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-	},
-	{
-		.procname	= "tcp_fack",
-		.data		= &init_net.ipv4.sysctl_tcp_fack,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-	},
-	{
-		.procname	= "tcp_max_reordering",
-		.data		= &init_net.ipv4.sysctl_tcp_max_reordering,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec
-	},
-	{
-		.procname	= "tcp_dsack",
-		.data		= &init_net.ipv4.sysctl_tcp_dsack,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-	},
-	{
-		.procname	= "tcp_app_win",
-		.data		= &init_net.ipv4.sysctl_tcp_app_win,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= &tcp_app_win_max,
-	},
-	{
-		.procname	= "tcp_adv_win_scale",
-		.data		= &init_net.ipv4.sysctl_tcp_adv_win_scale,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= &tcp_adv_win_scale_min,
-		.extra2		= &tcp_adv_win_scale_max,
-	},
-	{
-		.procname	= "tcp_frto",
-		.data		= &init_net.ipv4.sysctl_tcp_frto,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-	},
-	{
-		.procname	= "tcp_no_metrics_save",
-		.data		= &init_net.ipv4.sysctl_tcp_nometrics_save,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-	},
-	{
-		.procname	= "tcp_no_ssthresh_metrics_save",
-		.data		= &init_net.ipv4.sysctl_tcp_no_ssthresh_metrics_save,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_ONE,
-	},
-	{
-		.procname	= "tcp_moderate_rcvbuf",
-		.data		= &init_net.ipv4.sysctl_tcp_moderate_rcvbuf,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-	},
-	{
-		.procname	= "tcp_rcvbuf_low_rtt",
-		.data		= &init_net.ipv4.sysctl_tcp_rcvbuf_low_rtt,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_INT_MAX,
-	},
-	{
-		.procname	= "tcp_tso_win_divisor",
-		.data		= &init_net.ipv4.sysctl_tcp_tso_win_divisor,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-	},
-	{
-		.procname	= "tcp_workaround_signed_windows",
-		.data		= &init_net.ipv4.sysctl_tcp_workaround_signed_windows,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-	},
-	{
-		.procname	= "tcp_limit_output_bytes",
-		.data		= &init_net.ipv4.sysctl_tcp_limit_output_bytes,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec
-	},
-	{
-		.procname	= "tcp_challenge_ack_limit",
-		.data		= &init_net.ipv4.sysctl_tcp_challenge_ack_limit,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec
-	},
-	{
-		.procname	= "tcp_min_tso_segs",
-		.data		= &init_net.ipv4.sysctl_tcp_min_tso_segs,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra1		= SYSCTL_ONE,
-	},
-	{
-		.procname	= "tcp_tso_rtt_log",
-		.data		= &init_net.ipv4.sysctl_tcp_tso_rtt_log,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-	},
-	{
-		.procname	= "tcp_min_rtt_wlen",
-		.data		= &init_net.ipv4.sysctl_tcp_min_rtt_wlen,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= &one_day_secs
-	},
-	{
-		.procname	= "tcp_autocorking",
-		.data		= &init_net.ipv4.sysctl_tcp_autocorking,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_ONE,
-	},
-	{
-		.procname	= "tcp_invalid_ratelimit",
-		.data		= &init_net.ipv4.sysctl_tcp_invalid_ratelimit,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_ms_jiffies,
-	},
-	{
-		.procname	= "tcp_pacing_ss_ratio",
-		.data		= &init_net.ipv4.sysctl_tcp_pacing_ss_ratio,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_ONE_THOUSAND,
-	},
-	{
-		.procname	= "tcp_pacing_ca_ratio",
-		.data		= &init_net.ipv4.sysctl_tcp_pacing_ca_ratio,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_ONE_THOUSAND,
-	},
-	{
-		.procname	= "tcp_wmem",
-		.data		= &init_net.ipv4.sysctl_tcp_wmem,
-		.maxlen		= sizeof(init_net.ipv4.sysctl_tcp_wmem),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ONE,
-	},
-	{
-		.procname	= "tcp_rmem",
-		.data		= &init_net.ipv4.sysctl_tcp_rmem,
-		.maxlen		= sizeof(init_net.ipv4.sysctl_tcp_rmem),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ONE,
-	},
-	{
-		.procname	= "tcp_comp_sack_delay_ns",
-		.data		= &init_net.ipv4.sysctl_tcp_comp_sack_delay_ns,
-		.maxlen		= sizeof(unsigned long),
-		.mode		= 0644,
-		.proc_handler	= proc_doulongvec_minmax,
-	},
-	{
-		.procname	= "tcp_comp_sack_rtt_percent",
-		.data		= &init_net.ipv4.sysctl_tcp_comp_sack_rtt_percent,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ONE,
-		.extra2		= SYSCTL_ONE_THOUSAND,
-	},
-	{
-		.procname	= "tcp_comp_sack_slack_ns",
-		.data		= &init_net.ipv4.sysctl_tcp_comp_sack_slack_ns,
-		.maxlen		= sizeof(unsigned long),
-		.mode		= 0644,
-		.proc_handler	= proc_doulongvec_minmax,
-	},
-	{
-		.procname	= "tcp_comp_sack_nr",
-		.data		= &init_net.ipv4.sysctl_tcp_comp_sack_nr,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra1		= SYSCTL_ZERO,
-	},
-	{
-		.procname	= "tcp_backlog_ack_defer",
-		.data		= &init_net.ipv4.sysctl_tcp_backlog_ack_defer,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_ONE,
-	},
-	{
-		.procname       = "tcp_reflect_tos",
-		.data           = &init_net.ipv4.sysctl_tcp_reflect_tos,
-		.maxlen         = sizeof(u8),
-		.mode           = 0644,
-		.proc_handler   = proc_dou8vec_minmax,
-		.extra1         = SYSCTL_ZERO,
-		.extra2         = SYSCTL_ONE,
-	},
-	{
-		.procname	= "tcp_ehash_entries",
-		.data		= &init_net.ipv4.sysctl_tcp_child_ehash_entries,
-		.mode		= 0444,
-		.proc_handler	= proc_tcp_ehash_entries,
-	},
-	{
-		.procname	= "tcp_child_ehash_entries",
-		.data		= &init_net.ipv4.sysctl_tcp_child_ehash_entries,
-		.maxlen		= sizeof(unsigned int),
-		.mode		= 0644,
-		.proc_handler	= proc_douintvec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= &tcp_child_ehash_entries_max,
-	},
-	{
-		.procname	= "udp_hash_entries",
-		.data		= &init_net.ipv4.sysctl_udp_child_hash_entries,
-		.mode		= 0444,
-		.proc_handler	= proc_udp_hash_entries,
-	},
-	{
-		.procname	= "udp_child_hash_entries",
-		.data		= &init_net.ipv4.sysctl_udp_child_hash_entries,
-		.maxlen		= sizeof(unsigned int),
-		.mode		= 0644,
-		.proc_handler	= proc_douintvec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= &udp_child_hash_entries_max,
-	},
-	{
-		.procname	= "udp_rmem_min",
-		.data		= &init_net.ipv4.sysctl_udp_rmem_min,
-		.maxlen		= sizeof(init_net.ipv4.sysctl_udp_rmem_min),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ONE
-	},
-	{
-		.procname	= "udp_wmem_min",
-		.data		= &init_net.ipv4.sysctl_udp_wmem_min,
-		.maxlen		= sizeof(init_net.ipv4.sysctl_udp_wmem_min),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ONE
-	},
-	{
-		.procname	= "fib_notify_on_flag_change",
-		.data		= &init_net.ipv4.sysctl_fib_notify_on_flag_change,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_TWO,
-	},
-	{
-		.procname       = "tcp_plb_enabled",
-		.data           = &init_net.ipv4.sysctl_tcp_plb_enabled,
-		.maxlen         = sizeof(u8),
-		.mode           = 0644,
-		.proc_handler   = proc_dou8vec_minmax,
-		.extra1         = SYSCTL_ZERO,
-		.extra2         = SYSCTL_ONE,
-	},
-	{
-		.procname       = "tcp_plb_idle_rehash_rounds",
-		.data           = &init_net.ipv4.sysctl_tcp_plb_idle_rehash_rounds,
-		.maxlen         = sizeof(u8),
-		.mode           = 0644,
-		.proc_handler   = proc_dou8vec_minmax,
-		.extra2		= &tcp_plb_max_rounds,
-	},
-	{
-		.procname       = "tcp_plb_rehash_rounds",
-		.data           = &init_net.ipv4.sysctl_tcp_plb_rehash_rounds,
-		.maxlen         = sizeof(u8),
-		.mode           = 0644,
-		.proc_handler   = proc_dou8vec_minmax,
-		.extra2         = &tcp_plb_max_rounds,
-	},
-	{
-		.procname       = "tcp_plb_suspend_rto_sec",
-		.data           = &init_net.ipv4.sysctl_tcp_plb_suspend_rto_sec,
-		.maxlen         = sizeof(u8),
-		.mode           = 0644,
-		.proc_handler   = proc_dou8vec_minmax,
-	},
-	{
-		.procname       = "tcp_plb_cong_thresh",
-		.data           = &init_net.ipv4.sysctl_tcp_plb_cong_thresh,
-		.maxlen         = sizeof(int),
-		.mode           = 0644,
-		.proc_handler   = proc_dointvec_minmax,
-		.extra1         = SYSCTL_ZERO,
-		.extra2         = &tcp_plb_max_cong_thresh,
-	},
-	{
-		.procname	= "tcp_syn_linear_timeouts",
-		.data		= &init_net.ipv4.sysctl_tcp_syn_linear_timeouts,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= &tcp_syn_linear_timeouts_max,
-	},
-	{
-		.procname	= "tcp_shrink_window",
-		.data		= &init_net.ipv4.sysctl_tcp_shrink_window,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_ONE,
-	},
-	{
-		.procname	= "tcp_pingpong_thresh",
-		.data		= &init_net.ipv4.sysctl_tcp_pingpong_thresh,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra1		= SYSCTL_ONE,
-	},
-	{
-		.procname	= "tcp_rto_min_us",
-		.data		= &init_net.ipv4.sysctl_tcp_rto_min_us,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ONE,
-	},
-	{
-		.procname	= "tcp_rto_max_ms",
-		.data		= &init_net.ipv4.sysctl_tcp_rto_max_ms,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ONE_THOUSAND,
-		.extra2		= &tcp_rto_max_max,
-	},
+IPV4_SYSCTL_DATA(u8, tcp_sack)
+IPV4_SYSCTL_DATA(u8, tcp_window_scaling)
+IPV4_SYSCTL_DATA(u8, tcp_timestamps)
+IPV4_SYSCTL_DATA(u8, tcp_early_retrans)
+IPV4_SYSCTL_DATA(u8, tcp_recovery)
+IPV4_SYSCTL_DATA(u8, tcp_thin_linear_timeouts)
+IPV4_SYSCTL_DATA(u8, tcp_slow_start_after_idle)
+IPV4_SYSCTL_DATA(u8, tcp_retrans_collapse)
+IPV4_SYSCTL_DATA(u8, tcp_stdurg)
+IPV4_SYSCTL_DATA(u8, tcp_rfc1337)
+IPV4_SYSCTL_DATA(u8, tcp_abort_on_overflow)
+IPV4_SYSCTL_DATA(u8, tcp_fack)
+IPV4_SYSCTL_DATA(int, tcp_max_reordering)
+IPV4_SYSCTL_DATA(u8, tcp_dsack)
+IPV4_SYSCTL_DATA(u8, tcp_app_win)
+IPV4_SYSCTL_DATA(int, tcp_adv_win_scale)
+IPV4_SYSCTL_DATA(u8, tcp_frto)
+IPV4_SYSCTL_DATA(u8, tcp_no_ssthresh_metrics_save)
+IPV4_SYSCTL_DATA(u8, tcp_moderate_rcvbuf)
+IPV4_SYSCTL_DATA(int, tcp_rcvbuf_low_rtt)
+IPV4_SYSCTL_DATA(u8, tcp_tso_win_divisor)
+IPV4_SYSCTL_DATA(u8, tcp_workaround_signed_windows)
+IPV4_SYSCTL_DATA(int, tcp_limit_output_bytes)
+IPV4_SYSCTL_DATA(int, tcp_challenge_ack_limit)
+IPV4_SYSCTL_DATA(u8, tcp_min_tso_segs)
+IPV4_SYSCTL_DATA(u8, tcp_tso_rtt_log)
+IPV4_SYSCTL_DATA(int, tcp_min_rtt_wlen)
+IPV4_SYSCTL_DATA(u8, tcp_autocorking)
+IPV4_SYSCTL_DATA(void, tcp_invalid_ratelimit)
+IPV4_SYSCTL_DATA(int, tcp_pacing_ss_ratio)
+IPV4_SYSCTL_DATA(int, tcp_pacing_ca_ratio)
+IPV4_SYSCTL_DATA(void, tcp_wmem)
+IPV4_SYSCTL_DATA(void, tcp_rmem)
+IPV4_SYSCTL_DATA(unsigned long, tcp_comp_sack_delay_ns)
+IPV4_SYSCTL_DATA(int, tcp_comp_sack_rtt_percent)
+IPV4_SYSCTL_DATA(unsigned long, tcp_comp_sack_slack_ns)
+IPV4_SYSCTL_DATA(u8, tcp_comp_sack_nr)
+IPV4_SYSCTL_DATA(u8, tcp_backlog_ack_defer)
+IPV4_SYSCTL_DATA(u8, tcp_reflect_tos)
+IPV4_SYSCTL_DATA(unsigned int, tcp_child_ehash_entries)
+IPV4_SYSCTL_DATA(unsigned int, udp_child_hash_entries)
+IPV4_SYSCTL_DATA(int, udp_rmem_min)
+IPV4_SYSCTL_DATA(int, udp_wmem_min)
+IPV4_SYSCTL_DATA(u8, fib_notify_on_flag_change)
+IPV4_SYSCTL_DATA(u8, tcp_plb_enabled)
+IPV4_SYSCTL_DATA(u8, tcp_plb_idle_rehash_rounds)
+IPV4_SYSCTL_DATA(u8, tcp_plb_rehash_rounds)
+IPV4_SYSCTL_DATA(u8, tcp_plb_suspend_rto_sec)
+IPV4_SYSCTL_DATA(int, tcp_plb_cong_thresh)
+IPV4_SYSCTL_DATA(u8, tcp_syn_linear_timeouts)
+IPV4_SYSCTL_DATA(u8, tcp_shrink_window)
+IPV4_SYSCTL_DATA(u8, tcp_pingpong_thresh)
+IPV4_SYSCTL_DATA(int, tcp_rto_min_us)
+IPV4_SYSCTL_DATA(int, tcp_rto_max_ms)
+
+static const struct sysctl_field ipv4_net_table[] = {
+	SYSCTL_FIELD_INT("tcp_max_tw_buckets", 0644,
+			ipv4_tcp_max_tw_buckets_data),
+	SYSCTL_FIELD_STATIC_U8_MINMAX("icmp_echo_ignore_all", 0644,
+			ipv4_icmp_echo_ignore_all_data,
+			SYSCTL_UINT_ZERO, SYSCTL_UINT_ONE),
+	SYSCTL_FIELD_STATIC_U8_MINMAX("icmp_echo_enable_probe", 0644,
+			ipv4_icmp_echo_enable_probe_data,
+			SYSCTL_UINT_ZERO, SYSCTL_UINT_ONE),
+	SYSCTL_FIELD_STATIC_U8_MINMAX("icmp_echo_ignore_broadcasts", 0644,
+			ipv4_icmp_echo_ignore_broadcasts_data,
+			SYSCTL_UINT_ZERO, SYSCTL_UINT_ONE),
+	SYSCTL_FIELD_STATIC_U8_MINMAX("icmp_ignore_bogus_error_responses", 0644,
+			ipv4_icmp_ignore_bogus_error_responses_data,
+			SYSCTL_UINT_ZERO, SYSCTL_UINT_ONE),
+	SYSCTL_FIELD_STATIC_U8_MINMAX("icmp_errors_use_inbound_ifaddr", 0644,
+			ipv4_icmp_errors_use_inbound_ifaddr_data,
+			SYSCTL_UINT_ZERO, SYSCTL_UINT_ONE),
+	SYSCTL_FIELD_STATIC_U8_MINMAX("icmp_errors_extension_mask", 0644,
+			ipv4_icmp_errors_extension_mask_data,
+			SYSCTL_UINT_ZERO, &icmp_errors_extension_mask_all),
+	SYSCTL_FIELD_CUSTOM("icmp_ratelimit", 0644,
+			sizeof(int),
+			ipv4_icmp_ratelimit_data,
+			proc_dointvec_ms_jiffies),
+	SYSCTL_FIELD_INT("icmp_ratemask", 0644,
+			ipv4_icmp_ratemask_data),
+	SYSCTL_FIELD_STATIC_INT_MINMAX("icmp_msgs_per_sec", 0644,
+			ipv4_icmp_msgs_per_sec_data,
+			SYSCTL_ZERO, NULL),
+	SYSCTL_FIELD_STATIC_INT_MINMAX("icmp_msgs_burst", 0644,
+			ipv4_icmp_msgs_burst_data,
+			SYSCTL_ZERO, NULL),
+	SYSCTL_FIELD_CUSTOM("ping_group_range", 0644,
+			sizeof(gid_t) * 2,
+			ipv4_ping_group_range_data,
+			ipv4_ping_group_range),
+#ifdef CONFIG_NET_L3_MASTER_DEV
+	SYSCTL_FIELD_STATIC_U8_MINMAX("raw_l3mdev_accept", 0644,
+			ipv4_raw_l3mdev_accept_data,
+			SYSCTL_UINT_ZERO, SYSCTL_UINT_ONE),
+#endif
+	SYSCTL_FIELD_STATIC_U8_MINMAX("tcp_ecn", 0644,
+			ipv4_tcp_ecn_data,
+			SYSCTL_UINT_ZERO, &tcp_ecn_mode_max),
+	SYSCTL_FIELD_STATIC_U8_MINMAX("tcp_ecn_option", 0644,
+			ipv4_tcp_ecn_option_data,
+			SYSCTL_UINT_ZERO, SYSCTL_UINT_THREE),
+	SYSCTL_FIELD_STATIC_U8_MINMAX("tcp_ecn_option_beacon", 0644,
+			ipv4_tcp_ecn_option_beacon_data,
+			SYSCTL_UINT_ZERO, SYSCTL_UINT_THREE),
+	SYSCTL_FIELD_STATIC_U8_MINMAX("tcp_ecn_fallback", 0644,
+			ipv4_tcp_ecn_fallback_data,
+			SYSCTL_UINT_ZERO, SYSCTL_UINT_ONE),
+	SYSCTL_FIELD_U8("ip_dynaddr", 0644,
+			ipv4_ip_dynaddr_data),
+	SYSCTL_FIELD_U8("ip_early_demux", 0644,
+			ipv4_ip_early_demux_data),
+	SYSCTL_FIELD_U8("udp_early_demux", 0644,
+			ipv4_udp_early_demux_data),
+	SYSCTL_FIELD_U8("tcp_early_demux", 0644,
+			ipv4_tcp_early_demux_data),
+	SYSCTL_FIELD_STATIC_U8_MINMAX("nexthop_compat_mode", 0644,
+			ipv4_nexthop_compat_mode_data,
+			SYSCTL_UINT_ZERO, SYSCTL_UINT_ONE),
+	SYSCTL_FIELD_STATIC_U8_MINMAX("ip_default_ttl", 0644,
+			ipv4_ip_default_ttl_data,
+			&ip_ttl_min, &ip_ttl_max),
+	SYSCTL_FIELD_CUSTOM("ip_local_port_range", 0644,
+			0,
+			ipv4_netns_data,
+			ipv4_local_port_range),
+	SYSCTL_FIELD_UINT("ip_local_port_step_width", 0644,
+			ipv4_ip_local_port_step_width_data),
+	SYSCTL_FIELD_CUSTOM("ip_local_reserved_ports", 0644,
+			65536,
+			ipv4_ip_local_reserved_ports_data,
+			proc_do_large_bitmap),
+	SYSCTL_FIELD_U8("ip_no_pmtu_disc", 0644,
+			ipv4_ip_no_pmtu_disc_data),
+	SYSCTL_FIELD_U8("ip_forward_use_pmtu", 0644,
+			ipv4_ip_forward_use_pmtu_data),
+	SYSCTL_FIELD_CUSTOM("ip_forward_update_priority", 0644,
+			sizeof(u8),
+			ipv4_ip_forward_update_priority_data,
+			ipv4_fwd_update_priority),
+	SYSCTL_FIELD_U8("ip_nonlocal_bind", 0644,
+			ipv4_ip_nonlocal_bind_data),
+	SYSCTL_FIELD_STATIC_U8_MINMAX("ip_autobind_reuse", 0644,
+			ipv4_ip_autobind_reuse_data,
+			SYSCTL_UINT_ZERO, SYSCTL_UINT_ONE),
+	SYSCTL_FIELD_U8("fwmark_reflect", 0644,
+			ipv4_fwmark_reflect_data),
+	SYSCTL_FIELD_U8("tcp_fwmark_accept", 0644,
+			ipv4_tcp_fwmark_accept_data),
+#ifdef CONFIG_NET_L3_MASTER_DEV
+	SYSCTL_FIELD_STATIC_U8_MINMAX("tcp_l3mdev_accept", 0644,
+			ipv4_tcp_l3mdev_accept_data,
+			SYSCTL_UINT_ZERO, SYSCTL_UINT_ONE),
+#endif
+	SYSCTL_FIELD_U8("tcp_mtu_probing", 0644,
+			ipv4_tcp_mtu_probing_data),
+	SYSCTL_FIELD_INT("tcp_base_mss", 0644,
+			ipv4_tcp_base_mss_data),
+	SYSCTL_FIELD_STATIC_INT_MINMAX("tcp_min_snd_mss", 0644,
+			ipv4_tcp_min_snd_mss_data,
+			&tcp_min_snd_mss_min, &tcp_min_snd_mss_max),
+	SYSCTL_FIELD_STATIC_INT_MINMAX("tcp_mtu_probe_floor", 0644,
+			ipv4_tcp_mtu_probe_floor_data,
+			&tcp_min_snd_mss_min, &tcp_min_snd_mss_max),
+	SYSCTL_FIELD_INT("tcp_probe_threshold", 0644,
+			ipv4_tcp_probe_threshold_data),
+	SYSCTL_FIELD_STATIC_UINT_MINMAX("tcp_probe_interval", 0644,
+			ipv4_tcp_probe_interval_data,
+			NULL, &u32_max_div_HZ),
+	SYSCTL_FIELD_U8("igmp_link_local_mcast_reports", 0644,
+			ipv4_igmp_link_local_mcast_reports_data),
+	SYSCTL_FIELD_INT("igmp_max_memberships", 0644,
+			ipv4_igmp_max_memberships_data),
+	SYSCTL_FIELD_INT("igmp_max_msf", 0644,
+			ipv4_igmp_max_msf_data),
+#ifdef CONFIG_IP_MULTICAST
+	SYSCTL_FIELD_STATIC_INT_MINMAX("igmp_qrv", 0644,
+			ipv4_igmp_qrv_data,
+			SYSCTL_ONE, NULL),
+#endif
+	SYSCTL_FIELD_CUSTOM("tcp_congestion_control", 0644,
+			TCP_CA_NAME_MAX,
+			ipv4_tcp_congestion_control_data,
+			proc_tcp_congestion_control),
+	SYSCTL_FIELD_CUSTOM("tcp_available_congestion_control", 0444,
+			TCP_CA_BUF_MAX,
+			NULL,
+			proc_tcp_available_congestion_control),
+	SYSCTL_FIELD_CUSTOM_MODE("tcp_allowed_congestion_control", 0644,
+			ipv4_init_net_writable_mode, TCP_CA_BUF_MAX, NULL,
+			proc_allowed_congestion_control),
+	SYSCTL_FIELD_CUSTOM("tcp_keepalive_time", 0644,
+			sizeof(int),
+			ipv4_tcp_keepalive_time_data,
+			proc_dointvec_jiffies),
+	SYSCTL_FIELD_U8("tcp_keepalive_probes", 0644,
+			ipv4_tcp_keepalive_probes_data),
+	SYSCTL_FIELD_CUSTOM("tcp_keepalive_intvl", 0644,
+			sizeof(int),
+			ipv4_tcp_keepalive_intvl_data,
+			proc_dointvec_jiffies),
+	SYSCTL_FIELD_STATIC_U8_MINMAX("tcp_syn_retries", 0644,
+			ipv4_tcp_syn_retries_data,
+			&tcp_syn_retries_min, &tcp_syn_retries_max),
+	SYSCTL_FIELD_U8("tcp_synack_retries", 0644,
+			ipv4_tcp_synack_retries_data),
+#ifdef CONFIG_SYN_COOKIES
+	SYSCTL_FIELD_U8("tcp_syncookies", 0644,
+			ipv4_tcp_syncookies_data),
+#endif
+	SYSCTL_FIELD_STATIC_U8_MINMAX("tcp_migrate_req", 0644,
+			ipv4_tcp_migrate_req_data,
+			SYSCTL_UINT_ZERO, SYSCTL_UINT_ONE),
+	SYSCTL_FIELD_INT("tcp_reordering", 0644,
+			ipv4_tcp_reordering_data),
+	SYSCTL_FIELD_STATIC_U8_MINMAX("tcp_retries1", 0644,
+			ipv4_tcp_retries1_data,
+			NULL, &tcp_retr1_max),
+	SYSCTL_FIELD_U8("tcp_retries2", 0644,
+			ipv4_tcp_retries2_data),
+	SYSCTL_FIELD_U8("tcp_orphan_retries", 0644,
+			ipv4_tcp_orphan_retries_data),
+	SYSCTL_FIELD_CUSTOM("tcp_fin_timeout", 0644,
+			sizeof(int),
+			ipv4_tcp_fin_timeout_data,
+			proc_dointvec_jiffies),
+	SYSCTL_FIELD_UINT("tcp_notsent_lowat", 0644,
+			ipv4_tcp_notsent_lowat_data),
+	SYSCTL_FIELD_STATIC_U8_MINMAX("tcp_tw_reuse", 0644,
+			ipv4_tcp_tw_reuse_data,
+			SYSCTL_UINT_ZERO, SYSCTL_UINT_TWO),
+	SYSCTL_FIELD_STATIC_UINT_MINMAX("tcp_tw_reuse_delay", 0644,
+			ipv4_tcp_tw_reuse_delay_data,
+			SYSCTL_UINT_ONE, &tcp_tw_reuse_delay_max),
+	SYSCTL_FIELD_INT("tcp_max_syn_backlog", 0644,
+			ipv4_tcp_max_syn_backlog_data),
+	SYSCTL_FIELD_INT("tcp_fastopen", 0644,
+			ipv4_tcp_fastopen_data),
+	SYSCTL_FIELD_CUSTOM("tcp_fastopen_key", 0600,
+			((TCP_FASTOPEN_KEY_LENGTH * 2 * TCP_FASTOPEN_KEY_MAX) + (TCP_FASTOPEN_KEY_MAX * 5)),
+			ipv4_tcp_fastopen_key_data,
+			proc_tcp_fastopen_key),
+	SYSCTL_FIELD_CUSTOM("tcp_fastopen_blackhole_timeout_sec", 0644,
+			sizeof(int),
+			ipv4_tcp_fastopen_blackhole_timeout_sec_data,
+			proc_tfo_blackhole_detect_timeout),
+#ifdef CONFIG_IP_ROUTE_MULTIPATH
+	SYSCTL_FIELD_STATIC_U8_MINMAX("fib_multipath_use_neigh", 0644,
+			ipv4_fib_multipath_use_neigh_data,
+			SYSCTL_UINT_ZERO, SYSCTL_UINT_ONE),
+	SYSCTL_FIELD_CUSTOM("fib_multipath_hash_policy", 0644,
+			sizeof(u8),
+			ipv4_fib_multipath_hash_policy_data,
+			proc_fib_multipath_hash_policy),
+	SYSCTL_FIELD_CUSTOM("fib_multipath_hash_fields", 0644,
+			sizeof(u32),
+			ipv4_fib_multipath_hash_fields_data,
+			proc_fib_multipath_hash_fields),
+	SYSCTL_FIELD_CUSTOM("fib_multipath_hash_seed", 0644,
+			sizeof(u32),
+			ipv4_netns_data,
+			proc_fib_multipath_hash_seed),
+#endif
+	SYSCTL_FIELD_CUSTOM("ip_unprivileged_port_start", 0644,
+			sizeof(int),
+			ipv4_ip_unprivileged_port_start_data,
+			ipv4_privileged_ports),
+#ifdef CONFIG_NET_L3_MASTER_DEV
+	SYSCTL_FIELD_STATIC_U8_MINMAX("udp_l3mdev_accept", 0644,
+			ipv4_udp_l3mdev_accept_data,
+			SYSCTL_UINT_ZERO, SYSCTL_UINT_ONE),
+#endif
+	SYSCTL_FIELD_U8("tcp_sack", 0644,
+			ipv4_tcp_sack_data),
+	SYSCTL_FIELD_U8("tcp_window_scaling", 0644,
+			ipv4_tcp_window_scaling_data),
+	SYSCTL_FIELD_U8("tcp_timestamps", 0644,
+			ipv4_tcp_timestamps_data),
+	SYSCTL_FIELD_STATIC_U8_MINMAX("tcp_early_retrans", 0644,
+			ipv4_tcp_early_retrans_data,
+			SYSCTL_UINT_ZERO, SYSCTL_UINT_FOUR),
+	SYSCTL_FIELD_U8("tcp_recovery", 0644,
+			ipv4_tcp_recovery_data),
+	SYSCTL_FIELD_U8("tcp_thin_linear_timeouts", 0644,
+			ipv4_tcp_thin_linear_timeouts_data),
+	SYSCTL_FIELD_U8("tcp_slow_start_after_idle", 0644,
+			ipv4_tcp_slow_start_after_idle_data),
+	SYSCTL_FIELD_U8("tcp_retrans_collapse", 0644,
+			ipv4_tcp_retrans_collapse_data),
+	SYSCTL_FIELD_U8("tcp_stdurg", 0644,
+			ipv4_tcp_stdurg_data),
+	SYSCTL_FIELD_U8("tcp_rfc1337", 0644,
+			ipv4_tcp_rfc1337_data),
+	SYSCTL_FIELD_U8("tcp_abort_on_overflow", 0644,
+			ipv4_tcp_abort_on_overflow_data),
+	SYSCTL_FIELD_U8("tcp_fack", 0644,
+			ipv4_tcp_fack_data),
+	SYSCTL_FIELD_INT("tcp_max_reordering", 0644,
+			ipv4_tcp_max_reordering_data),
+	SYSCTL_FIELD_U8("tcp_dsack", 0644,
+			ipv4_tcp_dsack_data),
+	SYSCTL_FIELD_STATIC_U8_MINMAX("tcp_app_win", 0644,
+			ipv4_tcp_app_win_data,
+			SYSCTL_UINT_ZERO, &tcp_app_win_max),
+	SYSCTL_FIELD_STATIC_INT_MINMAX("tcp_adv_win_scale", 0644,
+			ipv4_tcp_adv_win_scale_data,
+			&tcp_adv_win_scale_min, &tcp_adv_win_scale_max),
+	SYSCTL_FIELD_U8("tcp_frto", 0644,
+			ipv4_tcp_frto_data),
+	SYSCTL_FIELD_U8("tcp_no_metrics_save", 0644,
+			ipv4_tcp_no_metrics_save_data),
+	SYSCTL_FIELD_STATIC_U8_MINMAX("tcp_no_ssthresh_metrics_save", 0644,
+			ipv4_tcp_no_ssthresh_metrics_save_data,
+			SYSCTL_UINT_ZERO, SYSCTL_UINT_ONE),
+	SYSCTL_FIELD_U8("tcp_moderate_rcvbuf", 0644,
+			ipv4_tcp_moderate_rcvbuf_data),
+	SYSCTL_FIELD_STATIC_INT_MINMAX("tcp_rcvbuf_low_rtt", 0644,
+			ipv4_tcp_rcvbuf_low_rtt_data,
+			SYSCTL_ZERO, SYSCTL_INT_MAX),
+	SYSCTL_FIELD_U8("tcp_tso_win_divisor", 0644,
+			ipv4_tcp_tso_win_divisor_data),
+	SYSCTL_FIELD_U8("tcp_workaround_signed_windows", 0644,
+			ipv4_tcp_workaround_signed_windows_data),
+	SYSCTL_FIELD_INT("tcp_limit_output_bytes", 0644,
+			ipv4_tcp_limit_output_bytes_data),
+	SYSCTL_FIELD_INT("tcp_challenge_ack_limit", 0644,
+			ipv4_tcp_challenge_ack_limit_data),
+	SYSCTL_FIELD_STATIC_U8_MINMAX("tcp_min_tso_segs", 0644,
+			ipv4_tcp_min_tso_segs_data,
+			SYSCTL_UINT_ONE, NULL),
+	SYSCTL_FIELD_U8("tcp_tso_rtt_log", 0644,
+			ipv4_tcp_tso_rtt_log_data),
+	SYSCTL_FIELD_STATIC_INT_MINMAX("tcp_min_rtt_wlen", 0644,
+			ipv4_tcp_min_rtt_wlen_data,
+			SYSCTL_ZERO, &one_day_secs),
+	SYSCTL_FIELD_STATIC_U8_MINMAX("tcp_autocorking", 0644,
+			ipv4_tcp_autocorking_data,
+			SYSCTL_UINT_ZERO, SYSCTL_UINT_ONE),
+	SYSCTL_FIELD_CUSTOM("tcp_invalid_ratelimit", 0644,
+			sizeof(int),
+			ipv4_tcp_invalid_ratelimit_data,
+			proc_dointvec_ms_jiffies),
+	SYSCTL_FIELD_STATIC_INT_MINMAX("tcp_pacing_ss_ratio", 0644,
+			ipv4_tcp_pacing_ss_ratio_data,
+			SYSCTL_ZERO, SYSCTL_ONE_THOUSAND),
+	SYSCTL_FIELD_STATIC_INT_MINMAX("tcp_pacing_ca_ratio", 0644,
+			ipv4_tcp_pacing_ca_ratio_data,
+			SYSCTL_ZERO, SYSCTL_ONE_THOUSAND),
+	SYSCTL_FIELD_CUSTOM("tcp_wmem", 0644,
+			sizeof(init_net.ipv4.sysctl_tcp_wmem),
+			ipv4_tcp_wmem_data,
+			proc_dointvec_minmax_one),
+	SYSCTL_FIELD_CUSTOM("tcp_rmem", 0644,
+			sizeof(init_net.ipv4.sysctl_tcp_rmem),
+			ipv4_tcp_rmem_data,
+			proc_dointvec_minmax_one),
+	SYSCTL_FIELD_ULONG("tcp_comp_sack_delay_ns", 0644,
+			ipv4_tcp_comp_sack_delay_ns_data),
+	SYSCTL_FIELD_STATIC_INT_MINMAX("tcp_comp_sack_rtt_percent", 0644,
+			ipv4_tcp_comp_sack_rtt_percent_data,
+			SYSCTL_ONE, SYSCTL_ONE_THOUSAND),
+	SYSCTL_FIELD_ULONG("tcp_comp_sack_slack_ns", 0644,
+			ipv4_tcp_comp_sack_slack_ns_data),
+	SYSCTL_FIELD_STATIC_U8_MINMAX("tcp_comp_sack_nr", 0644,
+			ipv4_tcp_comp_sack_nr_data,
+			SYSCTL_UINT_ZERO, NULL),
+	SYSCTL_FIELD_STATIC_U8_MINMAX("tcp_backlog_ack_defer", 0644,
+			ipv4_tcp_backlog_ack_defer_data,
+			SYSCTL_UINT_ZERO, SYSCTL_UINT_ONE),
+	SYSCTL_FIELD_STATIC_U8_MINMAX("tcp_reflect_tos", 0644,
+			ipv4_tcp_reflect_tos_data,
+			SYSCTL_UINT_ZERO, SYSCTL_UINT_ONE),
+	SYSCTL_FIELD_CUSTOM("tcp_ehash_entries", 0444,
+			0,
+			ipv4_tcp_ehash_entries_data,
+			proc_tcp_ehash_entries),
+	SYSCTL_FIELD_STATIC_UINT_MINMAX("tcp_child_ehash_entries", 0644,
+			ipv4_tcp_child_ehash_entries_data,
+			SYSCTL_UINT_ZERO, &tcp_child_ehash_entries_max),
+	SYSCTL_FIELD_CUSTOM("udp_hash_entries", 0444,
+			0,
+			ipv4_udp_hash_entries_data,
+			proc_udp_hash_entries),
+	SYSCTL_FIELD_STATIC_UINT_MINMAX("udp_child_hash_entries", 0644,
+			ipv4_udp_child_hash_entries_data,
+			SYSCTL_UINT_ZERO, &udp_child_hash_entries_max),
+	SYSCTL_FIELD_STATIC_INT_MINMAX("udp_rmem_min", 0644,
+			ipv4_udp_rmem_min_data, SYSCTL_ONE, NULL),
+	SYSCTL_FIELD_STATIC_INT_MINMAX("udp_wmem_min", 0644,
+			ipv4_udp_wmem_min_data, SYSCTL_ONE, NULL),
+	SYSCTL_FIELD_STATIC_U8_MINMAX("fib_notify_on_flag_change", 0644,
+			ipv4_fib_notify_on_flag_change_data,
+			SYSCTL_UINT_ZERO, SYSCTL_UINT_TWO),
+	SYSCTL_FIELD_STATIC_U8_MINMAX("tcp_plb_enabled", 0644,
+			ipv4_tcp_plb_enabled_data,
+			SYSCTL_UINT_ZERO, SYSCTL_UINT_ONE),
+	SYSCTL_FIELD_STATIC_U8_MINMAX("tcp_plb_idle_rehash_rounds", 0644,
+			ipv4_tcp_plb_idle_rehash_rounds_data,
+			NULL, &tcp_plb_max_rounds),
+	SYSCTL_FIELD_STATIC_U8_MINMAX("tcp_plb_rehash_rounds", 0644,
+			ipv4_tcp_plb_rehash_rounds_data,
+			NULL, &tcp_plb_max_rounds),
+	SYSCTL_FIELD_U8("tcp_plb_suspend_rto_sec", 0644,
+			ipv4_tcp_plb_suspend_rto_sec_data),
+	SYSCTL_FIELD_STATIC_INT_MINMAX("tcp_plb_cong_thresh", 0644,
+			ipv4_tcp_plb_cong_thresh_data,
+			SYSCTL_ZERO, &tcp_plb_max_cong_thresh),
+	SYSCTL_FIELD_STATIC_U8_MINMAX("tcp_syn_linear_timeouts", 0644,
+			ipv4_tcp_syn_linear_timeouts_data,
+			SYSCTL_UINT_ZERO, &tcp_syn_linear_timeouts_max),
+	SYSCTL_FIELD_STATIC_U8_MINMAX("tcp_shrink_window", 0644,
+			ipv4_tcp_shrink_window_data,
+			SYSCTL_UINT_ZERO, SYSCTL_UINT_ONE),
+	SYSCTL_FIELD_STATIC_U8_MINMAX("tcp_pingpong_thresh", 0644,
+			ipv4_tcp_pingpong_thresh_data,
+			SYSCTL_UINT_ONE, NULL),
+	SYSCTL_FIELD_STATIC_INT_MINMAX("tcp_rto_min_us", 0644,
+			ipv4_tcp_rto_min_us_data,
+			SYSCTL_ONE, NULL),
+	SYSCTL_FIELD_STATIC_INT_MINMAX("tcp_rto_max_ms", 0644,
+			ipv4_tcp_rto_max_ms_data,
+			SYSCTL_ONE_THOUSAND, &tcp_rto_max_max),
 };
 
 static __net_init int ipv4_sysctl_init_net(struct net *net)
 {
-	size_t table_size = ARRAY_SIZE(ipv4_net_table);
-	struct ctl_table *table;
-
-	table = ipv4_net_table;
-	if (!net_eq(net, &init_net)) {
-		int i;
-
-		table = kmemdup(table, sizeof(ipv4_net_table), GFP_KERNEL);
-		if (!table)
-			goto err_alloc;
-
-		for (i = 0; i < table_size; i++) {
-			if (table[i].data) {
-				/* Update the variables to point into
-				 * the current struct net
-				 */
-				table[i].data += (void *)net - (void *)&init_net;
-			} else {
-				/* Entries without data pointer are global;
-				 * Make them read-only in non-init_net ns
-				 */
-				table[i].mode &= ~0222;
-			}
-		}
-	}
+	struct sysctl_context ctx = {
+		.ns.net_ns = net,
+	};
 
-	net->ipv4.ipv4_hdr = register_net_sysctl_sz(net, "net/ipv4", table,
-						    table_size);
+	net->ipv4.ipv4_hdr = register_sysctl_fields(&net->sysctls, "net/ipv4",
+						    ipv4_net_table, &ctx);
 	if (!net->ipv4.ipv4_hdr)
-		goto err_reg;
+		return -ENOMEM;
 
 	net->ipv4.sysctl_local_reserved_ports = kzalloc(65536 / 8, GFP_KERNEL);
 	if (!net->ipv4.sysctl_local_reserved_ports)
@@ -1694,21 +1198,13 @@ static __net_init int ipv4_sysctl_init_net(struct net *net)
 
 err_ports:
 	unregister_net_sysctl_table(net->ipv4.ipv4_hdr);
-err_reg:
-	if (!net_eq(net, &init_net))
-		kfree(table);
-err_alloc:
 	return -ENOMEM;
 }
 
 static __net_exit void ipv4_sysctl_exit_net(struct net *net)
 {
-	const struct ctl_table *table;
-
 	kfree(net->ipv4.sysctl_local_reserved_ports);
-	table = net->ipv4.ipv4_hdr->ctl_table_arg;
 	unregister_net_sysctl_table(net->ipv4.ipv4_hdr);
-	kfree(table);
 }
 
 static __net_initdata struct pernet_operations ipv4_sysctl_ops = {
-- 
2.55.0


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [RFC PATCH v1 12/30] sysctl: net: use sysctl_field in IPv6 xfrm sysctls
       [not found] <cover.1787771905.git.legion@kernel.org>
                   ` (10 preceding siblings ...)
  2026-08-26 19:42 ` [RFC PATCH v1 11/30] sysctl: net: use sysctl_field in IPv4 sysctls Alexey Gladkov
@ 2026-08-26 19:42 ` Alexey Gladkov
  2026-08-26 19:42 ` [RFC PATCH v1 13/30] sysctl: net: use sysctl_field in IPv6 fragment sysctls Alexey Gladkov
                   ` (17 subsequent siblings)
  29 siblings, 0 replies; 37+ messages in thread
From: Alexey Gladkov @ 2026-08-26 19:42 UTC (permalink / raw)
  To: Linus Torvalds, Eric W . Biederman, Kees Cook, Joel Granados
  Cc: LKML, linux-fsdevel

The IPv6 xfrm sysctl table is cloned for every non-init network
namespace only to rebind the xfrm6_gc_thresh data pointer. The
descriptor itself is static and does not need per-net storage.

Use a sysctl_field descriptor so the data pointer is derived from the
network namespace registration context. This keeps the table const
and removes the per-net allocation and free path.

Signed-off-by: Alexey Gladkov <legion@kernel.org>
---
 net/ipv6/xfrm6_policy.c | 48 +++++++++++------------------------------
 1 file changed, 13 insertions(+), 35 deletions(-)

diff --git a/net/ipv6/xfrm6_policy.c b/net/ipv6/xfrm6_policy.c
index 125ea9a5b8a0..3df9872100c8 100644
--- a/net/ipv6/xfrm6_policy.c
+++ b/net/ipv6/xfrm6_policy.c
@@ -186,56 +186,34 @@ static void xfrm6_policy_fini(void)
 }
 
 #ifdef CONFIG_SYSCTL
-static struct ctl_table xfrm6_policy_table[] = {
-	{
-		.procname       = "xfrm6_gc_thresh",
-		.data		= &init_net.xfrm.xfrm6_dst_ops.gc_thresh,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler   = proc_dointvec,
-	},
+static int *xfrm6_gc_thresh_data(const struct sysctl_context *ctx)
+{
+	return &ctx->ns.net_ns->xfrm.xfrm6_dst_ops.gc_thresh;
+}
+
+static const struct sysctl_field xfrm6_policy_table[] = {
+	SYSCTL_FIELD_INT("xfrm6_gc_thresh", 0644, xfrm6_gc_thresh_data),
 };
 
 static int __net_init xfrm6_net_sysctl_init(struct net *net)
 {
-	struct ctl_table *table;
+	struct sysctl_context ctx = {
+		.ns.net_ns = net,
+	};
 	struct ctl_table_header *hdr;
 
-	table = xfrm6_policy_table;
-	if (!net_eq(net, &init_net)) {
-		table = kmemdup(table, sizeof(xfrm6_policy_table), GFP_KERNEL);
-		if (!table)
-			goto err_alloc;
-
-		table[0].data = &net->xfrm.xfrm6_dst_ops.gc_thresh;
-	}
-
-	hdr = register_net_sysctl_sz(net, "net/ipv6", table,
-				     ARRAY_SIZE(xfrm6_policy_table));
+	hdr = register_sysctl_fields(&net->sysctls, "net/ipv6",
+				     xfrm6_policy_table, &ctx);
 	if (!hdr)
-		goto err_reg;
+		return -ENOMEM;
 
 	net->ipv6.sysctl.xfrm6_hdr = hdr;
 	return 0;
-
-err_reg:
-	if (!net_eq(net, &init_net))
-		kfree(table);
-err_alloc:
-	return -ENOMEM;
 }
 
 static void __net_exit xfrm6_net_sysctl_exit(struct net *net)
 {
-	const struct ctl_table *table;
-
-	if (!net->ipv6.sysctl.xfrm6_hdr)
-		return;
-
-	table = net->ipv6.sysctl.xfrm6_hdr->ctl_table_arg;
 	unregister_net_sysctl_table(net->ipv6.sysctl.xfrm6_hdr);
-	if (!net_eq(net, &init_net))
-		kfree(table);
 }
 #else /* CONFIG_SYSCTL */
 static inline int xfrm6_net_sysctl_init(struct net *net)
-- 
2.55.0


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [RFC PATCH v1 13/30] sysctl: net: use sysctl_field in IPv6 fragment sysctls
       [not found] <cover.1787771905.git.legion@kernel.org>
                   ` (11 preceding siblings ...)
  2026-08-26 19:42 ` [RFC PATCH v1 12/30] sysctl: net: use sysctl_field in IPv6 xfrm sysctls Alexey Gladkov
@ 2026-08-26 19:42 ` Alexey Gladkov
  2026-08-26 19:42 ` [RFC PATCH v1 14/30] sysctl: net: use sysctl_field in 6lowpan " Alexey Gladkov
                   ` (16 subsequent siblings)
  29 siblings, 0 replies; 37+ messages in thread
From: Alexey Gladkov @ 2026-08-26 19:42 UTC (permalink / raw)
  To: Linus Torvalds, Eric W . Biederman, Kees Cook, Joel Granados
  Cc: LKML, linux-fsdevel

The IPv6 fragment sysctl tables are cloned for every non-init network
namespace only to bind the entries to that namespace's fqdir. The clone
also has to patch the low/high threshold limits through extra1 and
extra2 before registration.

Use sysctl_field descriptors for the IPv6 fragment and conntrack
fragment sysctls. The data and limit pointers are derived from the
registration context, so the tables can stay const and the per-net
allocation, pointer patching, and free paths are no longer needed.

Signed-off-by: Alexey Gladkov <legion@kernel.org>
---
 net/ipv6/netfilter/nf_conntrack_reasm.c | 77 ++++++++++---------------
 net/ipv6/reassembly.c                   | 77 ++++++++++---------------
 2 files changed, 60 insertions(+), 94 deletions(-)

diff --git a/net/ipv6/netfilter/nf_conntrack_reasm.c b/net/ipv6/netfilter/nf_conntrack_reasm.c
index 64ab23ff559b..f9785d9bd8f3 100644
--- a/net/ipv6/netfilter/nf_conntrack_reasm.c
+++ b/net/ipv6/netfilter/nf_conntrack_reasm.c
@@ -43,73 +43,56 @@ static struct nft_ct_frag6_pernet *nf_frag_pernet(struct net *net)
 
 #ifdef CONFIG_SYSCTL
 
-static struct ctl_table nf_ct_frag6_sysctl_table[] = {
-	{
-		.procname	= "nf_conntrack_frag6_timeout",
-		.maxlen		= sizeof(unsigned int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_jiffies,
-	},
-	{
-		.procname	= "nf_conntrack_frag6_low_thresh",
-		.maxlen		= sizeof(unsigned long),
-		.mode		= 0644,
-		.proc_handler	= proc_doulongvec_minmax,
-	},
-	{
-		.procname	= "nf_conntrack_frag6_high_thresh",
-		.maxlen		= sizeof(unsigned long),
-		.mode		= 0644,
-		.proc_handler	= proc_doulongvec_minmax,
-	},
+static void *nf_ct_frag6_timeout_data(const struct sysctl_context *ctx)
+{
+	return &nf_frag_pernet(ctx->ns.net_ns)->fqdir->timeout;
+}
+
+static unsigned long *nf_ct_frag6_low_thresh_data(const struct sysctl_context *ctx)
+{
+	return &nf_frag_pernet(ctx->ns.net_ns)->fqdir->low_thresh;
+}
+
+static unsigned long *nf_ct_frag6_high_thresh_data(const struct sysctl_context *ctx)
+{
+	return &nf_frag_pernet(ctx->ns.net_ns)->fqdir->high_thresh;
+}
+
+static const struct sysctl_field nf_ct_frag6_sysctl_table[] = {
+	SYSCTL_FIELD_CUSTOM("nf_conntrack_frag6_timeout", 0644, sizeof(unsigned int),
+			 nf_ct_frag6_timeout_data, proc_dointvec_jiffies),
+	SYSCTL_FIELD_ULONG_MINMAX("nf_conntrack_frag6_low_thresh", 0644,
+			       nf_ct_frag6_low_thresh_data, NULL,
+			       nf_ct_frag6_high_thresh_data),
+	SYSCTL_FIELD_ULONG_MINMAX("nf_conntrack_frag6_high_thresh", 0644,
+			       nf_ct_frag6_high_thresh_data,
+			       nf_ct_frag6_low_thresh_data, NULL),
 };
 
 static int nf_ct_frag6_sysctl_register(struct net *net)
 {
+	struct sysctl_context ctx = {
+		.ns.net_ns = net,
+	};
 	struct nft_ct_frag6_pernet *nf_frag;
-	struct ctl_table *table;
 	struct ctl_table_header *hdr;
 
-	table = nf_ct_frag6_sysctl_table;
-	if (!net_eq(net, &init_net)) {
-		table = kmemdup(table, sizeof(nf_ct_frag6_sysctl_table),
-				GFP_KERNEL);
-		if (table == NULL)
-			goto err_alloc;
-	}
-
 	nf_frag = nf_frag_pernet(net);
 
-	table[0].data	= &nf_frag->fqdir->timeout;
-	table[1].data	= &nf_frag->fqdir->low_thresh;
-	table[1].extra2	= &nf_frag->fqdir->high_thresh;
-	table[2].data	= &nf_frag->fqdir->high_thresh;
-	table[2].extra1	= &nf_frag->fqdir->low_thresh;
-
-	hdr = register_net_sysctl_sz(net, "net/netfilter", table,
-				     ARRAY_SIZE(nf_ct_frag6_sysctl_table));
+	hdr = register_sysctl_fields(&net->sysctls, "net/netfilter",
+				     nf_ct_frag6_sysctl_table, &ctx);
 	if (hdr == NULL)
-		goto err_reg;
+		return -ENOMEM;
 
 	nf_frag->nf_frag_frags_hdr = hdr;
 	return 0;
-
-err_reg:
-	if (!net_eq(net, &init_net))
-		kfree(table);
-err_alloc:
-	return -ENOMEM;
 }
 
 static void __net_exit nf_ct_frags6_sysctl_unregister(struct net *net)
 {
 	struct nft_ct_frag6_pernet *nf_frag = nf_frag_pernet(net);
-	const struct ctl_table *table;
 
-	table = nf_frag->nf_frag_frags_hdr->ctl_table_arg;
 	unregister_net_sysctl_table(nf_frag->nf_frag_frags_hdr);
-	if (!net_eq(net, &init_net))
-		kfree(table);
 }
 
 #else
diff --git a/net/ipv6/reassembly.c b/net/ipv6/reassembly.c
index 11f9144bebbe..87bd9e1efbb3 100644
--- a/net/ipv6/reassembly.c
+++ b/net/ipv6/reassembly.c
@@ -424,25 +424,30 @@ static const struct inet6_protocol frag_protocol = {
 
 #ifdef CONFIG_SYSCTL
 
-static struct ctl_table ip6_frags_ns_ctl_table[] = {
-	{
-		.procname	= "ip6frag_high_thresh",
-		.maxlen		= sizeof(unsigned long),
-		.mode		= 0644,
-		.proc_handler	= proc_doulongvec_minmax,
-	},
-	{
-		.procname	= "ip6frag_low_thresh",
-		.maxlen		= sizeof(unsigned long),
-		.mode		= 0644,
-		.proc_handler	= proc_doulongvec_minmax,
-	},
-	{
-		.procname	= "ip6frag_time",
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_jiffies,
-	},
+static unsigned long *ip6_frags_high_thresh_data(const struct sysctl_context *ctx)
+{
+	return &ctx->ns.net_ns->ipv6.fqdir->high_thresh;
+}
+
+static unsigned long *ip6_frags_low_thresh_data(const struct sysctl_context *ctx)
+{
+	return &ctx->ns.net_ns->ipv6.fqdir->low_thresh;
+}
+
+static void *ip6_frags_timeout_data(const struct sysctl_context *ctx)
+{
+	return &ctx->ns.net_ns->ipv6.fqdir->timeout;
+}
+
+static const struct sysctl_field ip6_frags_ns_ctl_table[] = {
+	SYSCTL_FIELD_ULONG_MINMAX("ip6frag_high_thresh", 0644,
+			       ip6_frags_high_thresh_data,
+			       ip6_frags_low_thresh_data, NULL),
+	SYSCTL_FIELD_ULONG_MINMAX("ip6frag_low_thresh", 0644,
+			       ip6_frags_low_thresh_data,
+			       NULL, ip6_frags_high_thresh_data),
+	SYSCTL_FIELD_CUSTOM("ip6frag_time", 0644, sizeof(int),
+			 ip6_frags_timeout_data, proc_dointvec_jiffies),
 };
 
 /* secret interval has been deprecated */
@@ -459,45 +464,23 @@ static struct ctl_table ip6_frags_ctl_table[] = {
 
 static int __net_init ip6_frags_ns_sysctl_register(struct net *net)
 {
-	struct ctl_table *table;
+	struct sysctl_context ctx = {
+		.ns.net_ns = net,
+	};
 	struct ctl_table_header *hdr;
 
-	table = ip6_frags_ns_ctl_table;
-	if (!net_eq(net, &init_net)) {
-		table = kmemdup(table, sizeof(ip6_frags_ns_ctl_table), GFP_KERNEL);
-		if (!table)
-			goto err_alloc;
-
-	}
-	table[0].data	= &net->ipv6.fqdir->high_thresh;
-	table[0].extra1	= &net->ipv6.fqdir->low_thresh;
-	table[1].data	= &net->ipv6.fqdir->low_thresh;
-	table[1].extra2	= &net->ipv6.fqdir->high_thresh;
-	table[2].data	= &net->ipv6.fqdir->timeout;
-
-	hdr = register_net_sysctl_sz(net, "net/ipv6", table,
-				     ARRAY_SIZE(ip6_frags_ns_ctl_table));
+	hdr = register_sysctl_fields(&net->sysctls, "net/ipv6",
+				     ip6_frags_ns_ctl_table, &ctx);
 	if (!hdr)
-		goto err_reg;
+		return -ENOMEM;
 
 	net->ipv6.sysctl.frags_hdr = hdr;
 	return 0;
-
-err_reg:
-	if (!net_eq(net, &init_net))
-		kfree(table);
-err_alloc:
-	return -ENOMEM;
 }
 
 static void __net_exit ip6_frags_ns_sysctl_unregister(struct net *net)
 {
-	const struct ctl_table *table;
-
-	table = net->ipv6.sysctl.frags_hdr->ctl_table_arg;
 	unregister_net_sysctl_table(net->ipv6.sysctl.frags_hdr);
-	if (!net_eq(net, &init_net))
-		kfree(table);
 }
 
 static struct ctl_table_header *ip6_ctl_header;
-- 
2.55.0


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [RFC PATCH v1 14/30] sysctl: net: use sysctl_field in 6lowpan fragment sysctls
       [not found] <cover.1787771905.git.legion@kernel.org>
                   ` (12 preceding siblings ...)
  2026-08-26 19:42 ` [RFC PATCH v1 13/30] sysctl: net: use sysctl_field in IPv6 fragment sysctls Alexey Gladkov
@ 2026-08-26 19:42 ` Alexey Gladkov
  2026-08-26 19:42 ` [RFC PATCH v1 15/30] sysctl: net: use sysctl_field in vsock sysctls Alexey Gladkov
                   ` (15 subsequent siblings)
  29 siblings, 0 replies; 37+ messages in thread
From: Alexey Gladkov @ 2026-08-26 19:42 UTC (permalink / raw)
  To: Linus Torvalds, Eric W . Biederman, Kees Cook, Joel Granados
  Cc: LKML, linux-fsdevel

The 6lowpan fragment sysctl table is cloned for every non-init network
namespace only to bind the entries to that namespace's fqdir. The clone
also has to patch the low/high threshold limits through extra1 and
extra2 before registration.

Use sysctl_field descriptors so the data and limit pointers are derived
from the registration context. This keeps the table const and removes
the per-net allocation, pointer patching, and free path while preserving
the existing unprivileged net namespace visibility rule.

Signed-off-by: Alexey Gladkov <legion@kernel.org>
---
 net/ieee802154/6lowpan/reassembly.c | 84 ++++++++++++-----------------
 1 file changed, 33 insertions(+), 51 deletions(-)

diff --git a/net/ieee802154/6lowpan/reassembly.c b/net/ieee802154/6lowpan/reassembly.c
index ddb6a5817d09..c54e50a213f4 100644
--- a/net/ieee802154/6lowpan/reassembly.c
+++ b/net/ieee802154/6lowpan/reassembly.c
@@ -326,25 +326,30 @@ int lowpan_frag_rcv(struct sk_buff *skb, u8 frag_type)
 
 #ifdef CONFIG_SYSCTL
 
-static struct ctl_table lowpan_frags_ns_ctl_table[] = {
-	{
-		.procname	= "6lowpanfrag_high_thresh",
-		.maxlen		= sizeof(unsigned long),
-		.mode		= 0644,
-		.proc_handler	= proc_doulongvec_minmax,
-	},
-	{
-		.procname	= "6lowpanfrag_low_thresh",
-		.maxlen		= sizeof(unsigned long),
-		.mode		= 0644,
-		.proc_handler	= proc_doulongvec_minmax,
-	},
-	{
-		.procname	= "6lowpanfrag_time",
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_jiffies,
-	},
+static unsigned long *lowpan_frags_high_thresh_data(const struct sysctl_context *ctx)
+{
+	return &net_ieee802154_lowpan(ctx->ns.net_ns)->fqdir->high_thresh;
+}
+
+static unsigned long *lowpan_frags_low_thresh_data(const struct sysctl_context *ctx)
+{
+	return &net_ieee802154_lowpan(ctx->ns.net_ns)->fqdir->low_thresh;
+}
+
+static void *lowpan_frags_timeout_data(const struct sysctl_context *ctx)
+{
+	return &net_ieee802154_lowpan(ctx->ns.net_ns)->fqdir->timeout;
+}
+
+static const struct sysctl_field lowpan_frags_ns_ctl_table[] = {
+	SYSCTL_FIELD_ULONG_MINMAX("6lowpanfrag_high_thresh", 0644,
+			       lowpan_frags_high_thresh_data,
+			       lowpan_frags_low_thresh_data, NULL),
+	SYSCTL_FIELD_ULONG_MINMAX("6lowpanfrag_low_thresh", 0644,
+			       lowpan_frags_low_thresh_data,
+			       NULL, lowpan_frags_high_thresh_data),
+	SYSCTL_FIELD_CUSTOM("6lowpanfrag_time", 0644, sizeof(int),
+			 lowpan_frags_timeout_data, proc_dointvec_jiffies),
 };
 
 /* secret interval has been deprecated */
@@ -361,55 +366,32 @@ static struct ctl_table lowpan_frags_ctl_table[] = {
 
 static int __net_init lowpan_frags_ns_sysctl_register(struct net *net)
 {
-	struct ctl_table *table;
+	struct sysctl_context ctx = {
+		.ns.net_ns = net,
+	};
 	struct ctl_table_header *hdr;
 	struct netns_ieee802154_lowpan *ieee802154_lowpan =
 		net_ieee802154_lowpan(net);
-	size_t table_size = ARRAY_SIZE(lowpan_frags_ns_ctl_table);
-
-	table = lowpan_frags_ns_ctl_table;
-	if (!net_eq(net, &init_net)) {
-		table = kmemdup(table, sizeof(lowpan_frags_ns_ctl_table),
-				GFP_KERNEL);
-		if (table == NULL)
-			goto err_alloc;
-
-		/* Don't export sysctls to unprivileged users */
-		if (net->user_ns != &init_user_ns)
-			table_size = 0;
-	}
 
-	table[0].data	= &ieee802154_lowpan->fqdir->high_thresh;
-	table[0].extra1	= &ieee802154_lowpan->fqdir->low_thresh;
-	table[1].data	= &ieee802154_lowpan->fqdir->low_thresh;
-	table[1].extra2	= &ieee802154_lowpan->fqdir->high_thresh;
-	table[2].data	= &ieee802154_lowpan->fqdir->timeout;
+	/* Don't export sysctls to unprivileged users */
+	if (!net_eq(net, &init_net) && net->user_ns != &init_user_ns)
+		return 0;
 
-	hdr = register_net_sysctl_sz(net, "net/ieee802154/6lowpan", table,
-				     table_size);
+	hdr = register_sysctl_fields(&net->sysctls, "net/ieee802154/6lowpan",
+				     lowpan_frags_ns_ctl_table, &ctx);
 	if (hdr == NULL)
-		goto err_reg;
+		return -ENOMEM;
 
 	ieee802154_lowpan->sysctl.frags_hdr = hdr;
 	return 0;
-
-err_reg:
-	if (!net_eq(net, &init_net))
-		kfree(table);
-err_alloc:
-	return -ENOMEM;
 }
 
 static void __net_exit lowpan_frags_ns_sysctl_unregister(struct net *net)
 {
-	const struct ctl_table *table;
 	struct netns_ieee802154_lowpan *ieee802154_lowpan =
 		net_ieee802154_lowpan(net);
 
-	table = ieee802154_lowpan->sysctl.frags_hdr->ctl_table_arg;
 	unregister_net_sysctl_table(ieee802154_lowpan->sysctl.frags_hdr);
-	if (!net_eq(net, &init_net))
-		kfree(table);
 }
 
 static struct ctl_table_header *lowpan_ctl_header;
-- 
2.55.0


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [RFC PATCH v1 15/30] sysctl: net: use sysctl_field in vsock sysctls
       [not found] <cover.1787771905.git.legion@kernel.org>
                   ` (13 preceding siblings ...)
  2026-08-26 19:42 ` [RFC PATCH v1 14/30] sysctl: net: use sysctl_field in 6lowpan " Alexey Gladkov
@ 2026-08-26 19:42 ` Alexey Gladkov
  2026-08-26 19:42 ` [RFC PATCH v1 16/30] sysctl: net: use sysctl_field in MPTCP sysctls Alexey Gladkov
                   ` (14 subsequent siblings)
  29 siblings, 0 replies; 37+ messages in thread
From: Alexey Gladkov @ 2026-08-26 19:42 UTC (permalink / raw)
  To: Linus Torvalds, Eric W . Biederman, Kees Cook, Joel Granados
  Cc: LKML, linux-fsdevel

The vsock sysctl table is cloned for every non-init network namespace
only to bind the entries to that namespace's vsock state. The custom
mode handlers already derive the namespace from the data pointer, so the
table clone is just per-net storage for otherwise static descriptors.

Use sysctl_field descriptors so the data pointers are derived from the
registration context. This keeps the table const and removes the per-net
allocation, pointer patching, and free path.

Signed-off-by: Alexey Gladkov <legion@kernel.org>
---
 net/vmw_vsock/af_vsock.c | 91 ++++++++++++++++------------------------
 1 file changed, 36 insertions(+), 55 deletions(-)

diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c
index 44037b066a5f..74af67e01430 100644
--- a/net/vmw_vsock/af_vsock.c
+++ b/net/vmw_vsock/af_vsock.c
@@ -2875,71 +2875,52 @@ static int vsock_net_child_mode_string(const struct ctl_table *table, int write,
 	return 0;
 }
 
-static struct ctl_table vsock_table[] = {
-	{
-		.procname	= "ns_mode",
-		.data		= &init_net.vsock.mode,
-		.maxlen		= VSOCK_NET_MODE_STR_MAX,
-		.mode		= 0444,
-		.proc_handler	= vsock_net_mode_string
-	},
-	{
-		.procname	= "child_ns_mode",
-		.data		= &init_net.vsock.child_ns_mode,
-		.maxlen		= VSOCK_NET_MODE_STR_MAX,
-		.mode		= 0644,
-		.proc_handler	= vsock_net_child_mode_string
-	},
-	{
-		.procname	= "g2h_fallback",
-		.data		= &init_net.vsock.g2h_fallback,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_ONE,
-	},
-};
+#ifdef CONFIG_SYSCTL
+static void *vsock_net_mode_data(const struct sysctl_context *ctx)
+{
+	return &ctx->ns.net_ns->vsock.mode;
+}
 
-static int __net_init vsock_sysctl_register(struct net *net)
+static void *vsock_net_child_mode_data(const struct sysctl_context *ctx)
 {
-	struct ctl_table *table;
+	return &ctx->ns.net_ns->vsock.child_ns_mode;
+}
 
-	if (net_eq(net, &init_net)) {
-		table = vsock_table;
-	} else {
-		table = kmemdup(vsock_table, sizeof(vsock_table), GFP_KERNEL);
-		if (!table)
-			goto err_alloc;
+static int *vsock_g2h_fallback_data(const struct sysctl_context *ctx)
+{
+	return &ctx->ns.net_ns->vsock.g2h_fallback;
+}
 
-		table[0].data = &net->vsock.mode;
-		table[1].data = &net->vsock.child_ns_mode;
-		table[2].data = &net->vsock.g2h_fallback;
-	}
+static const struct sysctl_field vsock_table[] = {
+	SYSCTL_FIELD_CUSTOM("ns_mode", 0444, VSOCK_NET_MODE_STR_MAX,
+			    vsock_net_mode_data, vsock_net_mode_string),
+	SYSCTL_FIELD_CUSTOM("child_ns_mode", 0644, VSOCK_NET_MODE_STR_MAX,
+			    vsock_net_child_mode_data,
+			    vsock_net_child_mode_string),
+	SYSCTL_FIELD_STATIC_INT_MINMAX("g2h_fallback", 0644,
+				       vsock_g2h_fallback_data,
+				       SYSCTL_ZERO, SYSCTL_ONE),
+};
+
+static int __net_init vsock_sysctl_register(struct net *net)
+{
+	struct sysctl_context ctx = {
+		.ns.net_ns = net,
+	};
 
-	net->vsock.sysctl_hdr = register_net_sysctl_sz(net, "net/vsock", table,
-						       ARRAY_SIZE(vsock_table));
+	net->vsock.sysctl_hdr = register_sysctl_fields(&net->sysctls, "net/vsock",
+						       vsock_table, &ctx);
 	if (!net->vsock.sysctl_hdr)
-		goto err_reg;
+		return -ENOMEM;
 
 	return 0;
-
-err_reg:
-	if (!net_eq(net, &init_net))
-		kfree(table);
-err_alloc:
-	return -ENOMEM;
 }
-
-static void vsock_sysctl_unregister(struct net *net)
+#else
+static int __net_init vsock_sysctl_register(struct net *net)
 {
-	const struct ctl_table *table;
-
-	table = net->vsock.sysctl_hdr->ctl_table_arg;
-	unregister_net_sysctl_table(net->vsock.sysctl_hdr);
-	if (!net_eq(net, &init_net))
-		kfree(table);
+	return 0;
 }
+#endif
 
 static void vsock_net_init(struct net *net)
 {
@@ -2965,7 +2946,7 @@ static __net_init int vsock_sysctl_init_net(struct net *net)
 
 static __net_exit void vsock_sysctl_exit_net(struct net *net)
 {
-	vsock_sysctl_unregister(net);
+	unregister_net_sysctl_table(net->vsock.sysctl_hdr);
 }
 
 static struct pernet_operations vsock_sysctl_ops = {
-- 
2.55.0


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [RFC PATCH v1 16/30] sysctl: net: use sysctl_field in MPTCP sysctls
       [not found] <cover.1787771905.git.legion@kernel.org>
                   ` (14 preceding siblings ...)
  2026-08-26 19:42 ` [RFC PATCH v1 15/30] sysctl: net: use sysctl_field in vsock sysctls Alexey Gladkov
@ 2026-08-26 19:42 ` Alexey Gladkov
  2026-08-26 19:42 ` [RFC PATCH v1 17/30] sysctl: net: use sysctl_field in SCTP sysctls Alexey Gladkov
                   ` (13 subsequent siblings)
  29 siblings, 0 replies; 37+ messages in thread
From: Alexey Gladkov @ 2026-08-26 19:42 UTC (permalink / raw)
  To: Linus Torvalds, Eric W . Biederman, Kees Cook, Joel Granados
  Cc: LKML, linux-fsdevel

The MPTCP sysctl table is cloned for every non-init network namespace
only to bind the entries to that namespace's pernet state. The cloned
table is then patched by index, which makes the registration path depend
on the table layout staying in sync with the data pointer assignments.

Use sysctl_field descriptors so the data pointers are derived from the
registration context. Keep the special min/max handling inside the
custom handlers that need it, and leave read-only informational entries
without data pointers.

This keeps the table const and removes the per-net allocation, pointer
patching, and free path.

Signed-off-by: Alexey Gladkov <legion@kernel.org>
---
 net/mptcp/ctrl.c | 205 ++++++++++++++++++-----------------------------
 1 file changed, 76 insertions(+), 129 deletions(-)

diff --git a/net/mptcp/ctrl.c b/net/mptcp/ctrl.c
index d96130e49942..31bde3e2eb28 100644
--- a/net/mptcp/ctrl.c
+++ b/net/mptcp/ctrl.c
@@ -19,7 +19,7 @@
 static int mptcp_pernet_id;
 
 #ifdef CONFIG_SYSCTL
-static int mptcp_pm_type_max = __MPTCP_PM_TYPE_MAX;
+static unsigned int mptcp_pm_type_max = __MPTCP_PM_TYPE_MAX;
 #endif
 
 struct mptcp_pernet {
@@ -172,9 +172,12 @@ static int proc_blackhole_detect_timeout(const struct ctl_table *table,
 	struct mptcp_pernet *pernet = container_of(table->data,
 						   struct mptcp_pernet,
 						   blackhole_timeout);
+	struct ctl_table tmp = *table;
 	int ret;
 
-	ret = proc_dointvec_minmax(table, write, buffer, lenp, ppos);
+	tmp.extra1 = SYSCTL_ZERO;
+
+	ret = proc_dointvec_minmax(&tmp, write, buffer, lenp, ppos);
 	if (write && ret == 0)
 		atomic_set(&pernet->active_disable_times, 0);
 
@@ -236,9 +239,13 @@ static int proc_pm_type(const struct ctl_table *ctl, int write,
 	struct mptcp_pernet *pernet = container_of(ctl->data,
 						   struct mptcp_pernet,
 						   pm_type);
+	struct ctl_table tmp = *ctl;
 	int ret;
 
-	ret = proc_dou8vec_minmax(ctl, write, buffer, lenp, ppos);
+	tmp.extra1 = SYSCTL_UINT_ZERO;
+	tmp.extra2 = &mptcp_pm_type_max;
+
+	ret = proc_dou8vec_minmax(&tmp, write, buffer, lenp, ppos);
 	if (write && ret == 0) {
 		u8 pm_type = READ_ONCE(*(u8 *)ctl->data);
 		char *pm_name = "";
@@ -271,148 +278,88 @@ static int proc_available_path_managers(const struct ctl_table *ctl,
 	return ret;
 }
 
-static struct ctl_table mptcp_sysctl_table[] = {
-	{
-		.procname = "enabled",
-		.maxlen = sizeof(u8),
-		.mode = 0644,
-		/* users with CAP_NET_ADMIN or root (not and) can change this
-		 * value, same as other sysctl or the 'net' tree.
-		 */
-		.proc_handler = proc_dou8vec_minmax,
-		.extra1       = SYSCTL_ZERO,
-		.extra2       = SYSCTL_ONE
-	},
-	{
-		.procname = "add_addr_timeout",
-		.maxlen = sizeof(unsigned int),
-		.mode = 0644,
-		.proc_handler = proc_dointvec_jiffies,
-	},
-	{
-		.procname = "checksum_enabled",
-		.maxlen = sizeof(u8),
-		.mode = 0644,
-		.proc_handler = proc_dou8vec_minmax,
-		.extra1       = SYSCTL_ZERO,
-		.extra2       = SYSCTL_ONE
-	},
-	{
-		.procname = "allow_join_initial_addr_port",
-		.maxlen = sizeof(u8),
-		.mode = 0644,
-		.proc_handler = proc_dou8vec_minmax,
-		.extra1       = SYSCTL_ZERO,
-		.extra2       = SYSCTL_ONE
-	},
-	{
-		.procname = "stale_loss_cnt",
-		.maxlen = sizeof(unsigned int),
-		.mode = 0644,
-		.proc_handler = proc_douintvec_minmax,
-	},
-	{
-		.procname = "pm_type",
-		.maxlen = sizeof(u8),
-		.mode = 0644,
-		.proc_handler = proc_pm_type,
-		.extra1       = SYSCTL_ZERO,
-		.extra2       = &mptcp_pm_type_max
-	},
-	{
-		.procname = "scheduler",
-		.maxlen	= MPTCP_SCHED_NAME_MAX,
-		.mode = 0644,
-		.proc_handler = proc_scheduler,
-	},
-	{
-		.procname = "available_schedulers",
-		.maxlen	= MPTCP_SCHED_BUF_MAX,
-		.mode = 0444,
-		.proc_handler = proc_available_schedulers,
-	},
-	{
-		.procname = "close_timeout",
-		.maxlen = sizeof(unsigned int),
-		.mode = 0644,
-		.proc_handler = proc_dointvec_jiffies,
-	},
-	{
-		.procname = "blackhole_timeout",
-		.maxlen = sizeof(unsigned int),
-		.mode = 0644,
-		.proc_handler = proc_blackhole_detect_timeout,
-		.extra1 = SYSCTL_ZERO,
-	},
-	{
-		.procname = "syn_retrans_before_tcp_fallback",
-		.maxlen = sizeof(u8),
-		.mode = 0644,
-		.proc_handler = proc_dou8vec_minmax,
-	},
-	{
-		.procname = "path_manager",
-		.maxlen	= MPTCP_PM_NAME_MAX,
-		.mode = 0644,
-		.proc_handler = proc_path_manager,
-	},
-	{
-		.procname = "available_path_managers",
-		.maxlen	= MPTCP_PM_BUF_MAX,
-		.mode = 0444,
-		.proc_handler = proc_available_path_managers,
-	},
+#define MPTCP_DATA(type, field)						\
+static type *mptcp_ ## field ## _data(const struct sysctl_context *ctx)	\
+{									\
+	return &mptcp_get_pernet(ctx->ns.net_ns)->field;		\
+}
+
+#define MPTCP_CUSTOM_DATA(field)					\
+static void *mptcp_ ## field ## _data(const struct sysctl_context *ctx)	\
+{									\
+	return &mptcp_get_pernet(ctx->ns.net_ns)->field;		\
+}
+
+static u8 *mptcp_enabled_data(const struct sysctl_context *ctx)
+{
+	return &mptcp_get_pernet(ctx->ns.net_ns)->mptcp_enabled;
+}
+
+MPTCP_CUSTOM_DATA(add_addr_timeout)
+MPTCP_DATA(u8, checksum_enabled)
+MPTCP_DATA(u8, allow_join_initial_addr_port)
+MPTCP_DATA(unsigned int, stale_loss_cnt)
+MPTCP_CUSTOM_DATA(pm_type)
+MPTCP_CUSTOM_DATA(scheduler)
+MPTCP_CUSTOM_DATA(close_timeout)
+MPTCP_CUSTOM_DATA(blackhole_timeout)
+MPTCP_DATA(u8, syn_retrans_before_tcp_fallback)
+MPTCP_CUSTOM_DATA(path_manager)
+
+static const struct sysctl_field mptcp_sysctl_table[] = {
+	/* users with CAP_NET_ADMIN or root (not and) can change this
+	 * value, same as other sysctl or the 'net' tree.
+	 */
+	SYSCTL_FIELD_STATIC_U8_MINMAX("enabled", 0644, mptcp_enabled_data,
+				   SYSCTL_UINT_ZERO, SYSCTL_UINT_ONE),
+	SYSCTL_FIELD_CUSTOM("add_addr_timeout", 0644, sizeof(unsigned int),
+			 mptcp_add_addr_timeout_data, proc_dointvec_jiffies),
+	SYSCTL_FIELD_STATIC_U8_MINMAX("checksum_enabled", 0644,
+				   mptcp_checksum_enabled_data,
+				   SYSCTL_UINT_ZERO, SYSCTL_UINT_ONE),
+	SYSCTL_FIELD_STATIC_U8_MINMAX("allow_join_initial_addr_port", 0644,
+				   mptcp_allow_join_initial_addr_port_data,
+				   SYSCTL_UINT_ZERO, SYSCTL_UINT_ONE),
+	SYSCTL_FIELD_UINT("stale_loss_cnt", 0644, mptcp_stale_loss_cnt_data),
+	SYSCTL_FIELD_CUSTOM("pm_type", 0644, sizeof(u8),
+			 mptcp_pm_type_data, proc_pm_type),
+	SYSCTL_FIELD_CUSTOM("scheduler", 0644, MPTCP_SCHED_NAME_MAX,
+			 mptcp_scheduler_data, proc_scheduler),
+	SYSCTL_FIELD_CUSTOM("available_schedulers", 0444, MPTCP_SCHED_BUF_MAX,
+			 NULL, proc_available_schedulers),
+	SYSCTL_FIELD_CUSTOM("close_timeout", 0644, sizeof(unsigned int),
+			 mptcp_close_timeout_data, proc_dointvec_jiffies),
+	SYSCTL_FIELD_CUSTOM("blackhole_timeout", 0644, sizeof(unsigned int),
+			 mptcp_blackhole_timeout_data,
+			 proc_blackhole_detect_timeout),
+	SYSCTL_FIELD_U8("syn_retrans_before_tcp_fallback", 0644,
+		     mptcp_syn_retrans_before_tcp_fallback_data),
+	SYSCTL_FIELD_CUSTOM("path_manager", 0644, MPTCP_PM_NAME_MAX,
+			 mptcp_path_manager_data, proc_path_manager),
+	SYSCTL_FIELD_CUSTOM("available_path_managers", 0444, MPTCP_PM_BUF_MAX,
+			 NULL, proc_available_path_managers),
 };
 
 static int mptcp_pernet_new_table(struct net *net, struct mptcp_pernet *pernet)
 {
+	struct sysctl_context ctx = {
+		.ns.net_ns = net,
+	};
 	struct ctl_table_header *hdr;
-	struct ctl_table *table;
-
-	table = mptcp_sysctl_table;
-	if (!net_eq(net, &init_net)) {
-		table = kmemdup(table, sizeof(mptcp_sysctl_table), GFP_KERNEL);
-		if (!table)
-			goto err_alloc;
-	}
 
-	table[0].data = &pernet->mptcp_enabled;
-	table[1].data = &pernet->add_addr_timeout;
-	table[2].data = &pernet->checksum_enabled;
-	table[3].data = &pernet->allow_join_initial_addr_port;
-	table[4].data = &pernet->stale_loss_cnt;
-	table[5].data = &pernet->pm_type;
-	table[6].data = &pernet->scheduler;
-	/* table[7] is for available_schedulers which is read-only info */
-	table[8].data = &pernet->close_timeout;
-	table[9].data = &pernet->blackhole_timeout;
-	table[10].data = &pernet->syn_retrans_before_tcp_fallback;
-	table[11].data = &pernet->path_manager;
-	/* table[12] is for available_path_managers which is read-only info */
-
-	hdr = register_net_sysctl_sz(net, MPTCP_SYSCTL_PATH, table,
-				     ARRAY_SIZE(mptcp_sysctl_table));
+	hdr = register_sysctl_fields(&net->sysctls, MPTCP_SYSCTL_PATH,
+				     mptcp_sysctl_table, &ctx);
 	if (!hdr)
-		goto err_reg;
+		return -ENOMEM;
 
 	pernet->ctl_table_hdr = hdr;
 
 	return 0;
-
-err_reg:
-	if (!net_eq(net, &init_net))
-		kfree(table);
-err_alloc:
-	return -ENOMEM;
 }
 
 static void mptcp_pernet_del_table(struct mptcp_pernet *pernet)
 {
-	const struct ctl_table *table = pernet->ctl_table_hdr->ctl_table_arg;
-
 	unregister_net_sysctl_table(pernet->ctl_table_hdr);
-
-	kfree(table);
 }
 
 #else
-- 
2.55.0


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [RFC PATCH v1 17/30] sysctl: net: use sysctl_field in SCTP sysctls
       [not found] <cover.1787771905.git.legion@kernel.org>
                   ` (15 preceding siblings ...)
  2026-08-26 19:42 ` [RFC PATCH v1 16/30] sysctl: net: use sysctl_field in MPTCP sysctls Alexey Gladkov
@ 2026-08-26 19:42 ` Alexey Gladkov
  2026-08-26 20:29   ` Linus Torvalds
  2026-08-26 19:42 ` [RFC PATCH v1 18/30] sysctl: net: use sysctl_field in core IPv6 sysctls Alexey Gladkov
                   ` (12 subsequent siblings)
  29 siblings, 1 reply; 37+ messages in thread
From: Alexey Gladkov @ 2026-08-26 19:42 UTC (permalink / raw)
  To: Linus Torvalds, Eric W . Biederman, Kees Cook, Joel Granados
  Cc: LKML, linux-fsdevel

The SCTP per-net sysctl table is cloned for every network namespace so
that all data pointers can be shifted from init_net to the namespace
being registered. A few entries then rely on table indexes to patch
cross-field limits such as rto_min/rto_max and pf_retrans/ps_retrans.

Use sysctl_field descriptors for the per-net SCTP sysctls instead. The
data and limit pointers are derived from the registration context, so
the table can stay const and the per-net allocation, offset arithmetic,
index-based patching, and free path are no longer needed.

Keep the special validation inside the custom handlers that need it.

Signed-off-by: Alexey Gladkov <legion@kernel.org>
---
 net/sctp/sysctl.c | 505 ++++++++++++++++------------------------------
 1 file changed, 169 insertions(+), 336 deletions(-)

diff --git a/net/sctp/sysctl.c b/net/sctp/sysctl.c
index 15e7db9a3ab2..a3577683b9b9 100644
--- a/net/sctp/sysctl.c
+++ b/net/sctp/sysctl.c
@@ -25,9 +25,9 @@
 #include <net/sctp/sctp.h>
 #include <linux/sysctl.h>
 
-static int timer_max = 86400000; /* ms in one day */
-static int sack_timer_min = 1;
-static int sack_timer_max = 500;
+static unsigned int timer_max = 86400000; /* ms in one day */
+static unsigned int sack_timer_min = 1;
+static unsigned int sack_timer_max = 500;
 static int addr_scope_max = SCTP_SCOPE_POLICY_MAX;
 static int rwnd_scale_max = 16;
 static int rto_alpha_min = 0;
@@ -51,8 +51,10 @@ static int proc_sctp_do_rto_max(const struct ctl_table *ctl, int write, void *bu
 				size_t *lenp, loff_t *ppos);
 static int proc_sctp_do_udp_port(const struct ctl_table *ctl, int write, void *buffer,
 				 size_t *lenp, loff_t *ppos);
-static int proc_sctp_do_alpha_beta(const struct ctl_table *ctl, int write,
-				   void *buffer, size_t *lenp, loff_t *ppos);
+static int proc_sctp_do_alpha(const struct ctl_table *ctl, int write,
+			      void *buffer, size_t *lenp, loff_t *ppos);
+static int proc_sctp_do_beta(const struct ctl_table *ctl, int write,
+			     void *buffer, size_t *lenp, loff_t *ppos);
 static int proc_sctp_do_auth(const struct ctl_table *ctl, int write,
 			     void *buffer, size_t *lenp, loff_t *ppos);
 static int proc_sctp_do_probe_interval(const struct ctl_table *ctl, int write,
@@ -82,306 +84,138 @@ static struct ctl_table sctp_table[] = {
 	},
 };
 
-/* The following index defines are used in sctp_sysctl_net_register().
- * If you add new items to the sctp_net_table, please ensure that
- * the index values of these defines hold the same meaning indicated by
- * their macro names when they appear in sctp_net_table.
- */
-#define SCTP_RTO_MIN_IDX       0
-#define SCTP_RTO_MAX_IDX       1
-#define SCTP_PF_RETRANS_IDX    2
-#define SCTP_PS_RETRANS_IDX    3
-
-static struct ctl_table sctp_net_table[] = {
-	[SCTP_RTO_MIN_IDX] = {
-		.procname	= "rto_min",
-		.data		= &init_net.sctp.rto_min,
-		.maxlen		= sizeof(unsigned int),
-		.mode		= 0644,
-		.proc_handler	= proc_sctp_do_rto_min,
-		.extra1         = SYSCTL_ONE,
-		.extra2         = &init_net.sctp.rto_max
-	},
-	[SCTP_RTO_MAX_IDX] =  {
-		.procname	= "rto_max",
-		.data		= &init_net.sctp.rto_max,
-		.maxlen		= sizeof(unsigned int),
-		.mode		= 0644,
-		.proc_handler	= proc_sctp_do_rto_max,
-		.extra1         = &init_net.sctp.rto_min,
-		.extra2         = &timer_max
-	},
-	[SCTP_PF_RETRANS_IDX] = {
-		.procname	= "pf_retrans",
-		.data		= &init_net.sctp.pf_retrans,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= &init_net.sctp.ps_retrans,
-	},
-	[SCTP_PS_RETRANS_IDX] = {
-		.procname	= "ps_retrans",
-		.data		= &init_net.sctp.ps_retrans,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= &init_net.sctp.pf_retrans,
-		.extra2		= &ps_retrans_max,
-	},
-	{
-		.procname	= "rto_initial",
-		.data		= &init_net.sctp.rto_initial,
-		.maxlen		= sizeof(unsigned int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1         = SYSCTL_ONE,
-		.extra2         = &timer_max
-	},
-	{
-		.procname	= "rto_alpha_exp_divisor",
-		.data		= &init_net.sctp.rto_alpha,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_sctp_do_alpha_beta,
-		.extra1		= &rto_alpha_min,
-		.extra2		= &rto_alpha_max,
-	},
-	{
-		.procname	= "rto_beta_exp_divisor",
-		.data		= &init_net.sctp.rto_beta,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_sctp_do_alpha_beta,
-		.extra1		= &rto_beta_min,
-		.extra2		= &rto_beta_max,
-	},
-	{
-		.procname	= "max_burst",
-		.data		= &init_net.sctp.max_burst,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_INT_MAX,
-	},
-	{
-		.procname	= "cookie_preserve_enable",
-		.data		= &init_net.sctp.cookie_preserve_enable,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
-	{
-		.procname	= "cookie_hmac_alg",
-		.data		= &init_net.sctp.cookie_auth_enable,
-		.maxlen		= 8,
-		.mode		= 0644,
-		.proc_handler	= proc_sctp_do_hmac_alg,
-	},
-	{
-		.procname	= "valid_cookie_life",
-		.data		= &init_net.sctp.valid_cookie_life,
-		.maxlen		= sizeof(unsigned int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1         = SYSCTL_ONE,
-		.extra2         = &timer_max
-	},
-	{
-		.procname	= "sack_timeout",
-		.data		= &init_net.sctp.sack_timeout,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1         = &sack_timer_min,
-		.extra2         = &sack_timer_max,
-	},
-	{
-		.procname	= "hb_interval",
-		.data		= &init_net.sctp.hb_interval,
-		.maxlen		= sizeof(unsigned int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1         = SYSCTL_ONE,
-		.extra2         = &timer_max
-	},
-	{
-		.procname	= "association_max_retrans",
-		.data		= &init_net.sctp.max_retrans_association,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ONE,
-		.extra2		= SYSCTL_INT_MAX,
-	},
-	{
-		.procname	= "path_max_retrans",
-		.data		= &init_net.sctp.max_retrans_path,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ONE,
-		.extra2		= SYSCTL_INT_MAX,
-	},
-	{
-		.procname	= "max_init_retransmits",
-		.data		= &init_net.sctp.max_retrans_init,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ONE,
-		.extra2		= SYSCTL_INT_MAX,
-	},
-	{
-		.procname	= "sndbuf_policy",
-		.data		= &init_net.sctp.sndbuf_policy,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
-	{
-		.procname	= "rcvbuf_policy",
-		.data		= &init_net.sctp.rcvbuf_policy,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
-	{
-		.procname	= "default_auto_asconf",
-		.data		= &init_net.sctp.default_auto_asconf,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
-	{
-		.procname	= "addip_enable",
-		.data		= &init_net.sctp.addip_enable,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
-	{
-		.procname	= "addip_noauth_enable",
-		.data		= &init_net.sctp.addip_noauth,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
-	{
-		.procname	= "prsctp_enable",
-		.data		= &init_net.sctp.prsctp_enable,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
-	{
-		.procname	= "reconf_enable",
-		.data		= &init_net.sctp.reconf_enable,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
-	{
-		.procname	= "auth_enable",
-		.data		= &init_net.sctp.auth_enable,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_sctp_do_auth,
-	},
-	{
-		.procname	= "intl_enable",
-		.data		= &init_net.sctp.intl_enable,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
-	{
-		.procname	= "ecn_enable",
-		.data		= &init_net.sctp.ecn_enable,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
-	{
-		.procname	= "plpmtud_probe_interval",
-		.data		= &init_net.sctp.probe_interval,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_sctp_do_probe_interval,
-	},
-	{
-		.procname	= "udp_port",
-		.data		= &init_net.sctp.udp_port,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_sctp_do_udp_port,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= &udp_port_max,
-	},
-	{
-		.procname	= "encap_port",
-		.data		= &init_net.sctp.encap_port,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= &udp_port_max,
-	},
-	{
-		.procname	= "addr_scope_policy",
-		.data		= &init_net.sctp.scope_policy,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= &addr_scope_max,
-	},
-	{
-		.procname	= "rwnd_update_shift",
-		.data		= &init_net.sctp.rwnd_upd_shift,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= &proc_dointvec_minmax,
-		.extra1		= SYSCTL_ONE,
-		.extra2		= &rwnd_scale_max,
-	},
-	{
-		.procname	= "max_autoclose",
-		.data		= &init_net.sctp.max_autoclose,
-		.maxlen		= sizeof(unsigned long),
-		.mode		= 0644,
-		.proc_handler	= &proc_doulongvec_minmax,
-		.extra1		= &max_autoclose_min,
-		.extra2		= &max_autoclose_max,
-	},
+static int *sctp_ps_retrans_max_data(const struct sysctl_context *ctx)
+{
+	return &ps_retrans_max;
+}
+
+#define SCTP_DATA(type, field)						\
+static type *sctp_ ## field ## _data(const struct sysctl_context *ctx)	\
+{									\
+	return &ctx->ns.net_ns->sctp.field;				\
+}
+
+#define SCTP_CUSTOM_DATA(field)						\
+static void *sctp_ ## field ## _data(const struct sysctl_context *ctx)	\
+{									\
+	return &ctx->ns.net_ns->sctp.field;				\
+}
+
+SCTP_CUSTOM_DATA(rto_min)
+SCTP_CUSTOM_DATA(rto_max)
+SCTP_DATA(int, pf_retrans)
+SCTP_DATA(int, ps_retrans)
+SCTP_DATA(unsigned int, rto_initial)
+SCTP_CUSTOM_DATA(rto_alpha)
+SCTP_CUSTOM_DATA(rto_beta)
+SCTP_DATA(int, max_burst)
+SCTP_DATA(int, cookie_preserve_enable)
+SCTP_CUSTOM_DATA(cookie_auth_enable)
+SCTP_DATA(unsigned int, valid_cookie_life)
+SCTP_DATA(unsigned int, sack_timeout)
+SCTP_DATA(unsigned int, hb_interval)
+SCTP_DATA(int, max_retrans_association)
+SCTP_DATA(int, max_retrans_path)
+SCTP_DATA(int, max_retrans_init)
+SCTP_DATA(int, sndbuf_policy)
+SCTP_DATA(int, rcvbuf_policy)
+SCTP_DATA(int, default_auto_asconf)
+SCTP_DATA(int, addip_enable)
+SCTP_DATA(int, addip_noauth)
+SCTP_DATA(int, prsctp_enable)
+SCTP_DATA(int, reconf_enable)
+SCTP_CUSTOM_DATA(auth_enable)
+SCTP_DATA(int, intl_enable)
+SCTP_DATA(int, ecn_enable)
+SCTP_CUSTOM_DATA(probe_interval)
+SCTP_CUSTOM_DATA(udp_port)
+SCTP_DATA(int, encap_port)
+SCTP_DATA(int, scope_policy)
+SCTP_DATA(int, rwnd_upd_shift)
+SCTP_DATA(unsigned long, max_autoclose)
+SCTP_DATA(int, pf_enable)
+SCTP_DATA(int, pf_expose)
 #ifdef CONFIG_NET_L3_MASTER_DEV
-	{
-		.procname	= "l3mdev_accept",
-		.data		= &init_net.sctp.l3mdev_accept,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_ONE,
-	},
+SCTP_DATA(int, l3mdev_accept)
 #endif
-	{
-		.procname	= "pf_enable",
-		.data		= &init_net.sctp.pf_enable,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
-	{
-		.procname	= "pf_expose",
-		.data		= &init_net.sctp.pf_expose,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= &pf_expose_max,
-	},
+
+static const struct sysctl_field sctp_net_table[] = {
+	SYSCTL_FIELD_CUSTOM("rto_min", 0644, sizeof(unsigned int),
+			 sctp_rto_min_data, proc_sctp_do_rto_min),
+	SYSCTL_FIELD_CUSTOM("rto_max", 0644, sizeof(unsigned int),
+			 sctp_rto_max_data, proc_sctp_do_rto_max),
+	SYSCTL_FIELD_INT_MINMAX("pf_retrans", 0644, sctp_pf_retrans_data,
+			     SYSCTL_ZERO, sctp_ps_retrans_data),
+	SYSCTL_FIELD_INT_MINMAX("ps_retrans", 0644, sctp_ps_retrans_data,
+			     sctp_pf_retrans_data, sctp_ps_retrans_max_data),
+	SYSCTL_FIELD_STATIC_UINT_MINMAX("rto_initial", 0644,
+				     sctp_rto_initial_data,
+				     SYSCTL_UINT_ONE, &timer_max),
+	SYSCTL_FIELD_CUSTOM("rto_alpha_exp_divisor", 0644, sizeof(int),
+			 sctp_rto_alpha_data, proc_sctp_do_alpha),
+	SYSCTL_FIELD_CUSTOM("rto_beta_exp_divisor", 0644, sizeof(int),
+			 sctp_rto_beta_data, proc_sctp_do_beta),
+	SYSCTL_FIELD_STATIC_INT_MINMAX("max_burst", 0644, sctp_max_burst_data,
+				    SYSCTL_ZERO, SYSCTL_INT_MAX),
+	SYSCTL_FIELD_INT("cookie_preserve_enable", 0644,
+		      sctp_cookie_preserve_enable_data),
+	SYSCTL_FIELD_CUSTOM("cookie_hmac_alg", 0644, 8,
+			 sctp_cookie_auth_enable_data, proc_sctp_do_hmac_alg),
+	SYSCTL_FIELD_STATIC_UINT_MINMAX("valid_cookie_life", 0644,
+				     sctp_valid_cookie_life_data,
+				     SYSCTL_UINT_ONE, &timer_max),
+	SYSCTL_FIELD_STATIC_UINT_MINMAX("sack_timeout", 0644,
+				     sctp_sack_timeout_data,
+				     &sack_timer_min, &sack_timer_max),
+	SYSCTL_FIELD_STATIC_UINT_MINMAX("hb_interval", 0644,
+				     sctp_hb_interval_data,
+				     SYSCTL_UINT_ONE, &timer_max),
+	SYSCTL_FIELD_STATIC_INT_MINMAX("association_max_retrans", 0644,
+				    sctp_max_retrans_association_data,
+				    SYSCTL_ONE, SYSCTL_INT_MAX),
+	SYSCTL_FIELD_STATIC_INT_MINMAX("path_max_retrans", 0644,
+				    sctp_max_retrans_path_data,
+				    SYSCTL_ONE, SYSCTL_INT_MAX),
+	SYSCTL_FIELD_STATIC_INT_MINMAX("max_init_retransmits", 0644,
+				    sctp_max_retrans_init_data,
+				    SYSCTL_ONE, SYSCTL_INT_MAX),
+	SYSCTL_FIELD_INT("sndbuf_policy", 0644, sctp_sndbuf_policy_data),
+	SYSCTL_FIELD_INT("rcvbuf_policy", 0644, sctp_rcvbuf_policy_data),
+	SYSCTL_FIELD_INT("default_auto_asconf", 0644,
+		      sctp_default_auto_asconf_data),
+	SYSCTL_FIELD_INT("addip_enable", 0644, sctp_addip_enable_data),
+	SYSCTL_FIELD_INT("addip_noauth_enable", 0644, sctp_addip_noauth_data),
+	SYSCTL_FIELD_INT("prsctp_enable", 0644, sctp_prsctp_enable_data),
+	SYSCTL_FIELD_INT("reconf_enable", 0644, sctp_reconf_enable_data),
+	SYSCTL_FIELD_CUSTOM("auth_enable", 0644, sizeof(int),
+			 sctp_auth_enable_data, proc_sctp_do_auth),
+	SYSCTL_FIELD_INT("intl_enable", 0644, sctp_intl_enable_data),
+	SYSCTL_FIELD_INT("ecn_enable", 0644, sctp_ecn_enable_data),
+	SYSCTL_FIELD_CUSTOM("plpmtud_probe_interval", 0644, sizeof(int),
+			 sctp_probe_interval_data,
+			 proc_sctp_do_probe_interval),
+	SYSCTL_FIELD_CUSTOM("udp_port", 0644, sizeof(int),
+			 sctp_udp_port_data, proc_sctp_do_udp_port),
+	SYSCTL_FIELD_STATIC_INT_MINMAX("encap_port", 0644,
+				    sctp_encap_port_data,
+				    SYSCTL_ZERO, &udp_port_max),
+	SYSCTL_FIELD_STATIC_INT_MINMAX("addr_scope_policy", 0644,
+				    sctp_scope_policy_data,
+				    SYSCTL_ZERO, &addr_scope_max),
+	SYSCTL_FIELD_STATIC_INT_MINMAX("rwnd_update_shift", 0644,
+				    sctp_rwnd_upd_shift_data,
+				    SYSCTL_ONE, &rwnd_scale_max),
+	SYSCTL_FIELD_STATIC_ULONG_MINMAX("max_autoclose", 0644,
+				      sctp_max_autoclose_data,
+				      &max_autoclose_min, &max_autoclose_max),
+#ifdef CONFIG_NET_L3_MASTER_DEV
+	SYSCTL_FIELD_STATIC_INT_MINMAX("l3mdev_accept", 0644,
+				    sctp_l3mdev_accept_data,
+				    SYSCTL_ZERO, SYSCTL_ONE),
+#endif
+	SYSCTL_FIELD_INT("pf_enable", 0644, sctp_pf_enable_data),
+	SYSCTL_FIELD_STATIC_INT_MINMAX("pf_expose", 0644, sctp_pf_expose_data,
+				    SYSCTL_ZERO, &pf_expose_max),
 };
 
 static int proc_sctp_do_hmac_alg(const struct ctl_table *ctl, int write,
@@ -423,8 +257,6 @@ static int proc_sctp_do_rto_min(const struct ctl_table *ctl, int write,
 				void *buffer, size_t *lenp, loff_t *ppos)
 {
 	struct net *net = container_of(ctl->data, struct net, sctp.rto_min);
-	unsigned int min = *(unsigned int *) ctl->extra1;
-	unsigned int max = *(unsigned int *) ctl->extra2;
 	struct ctl_table tbl;
 	int ret, new_value;
 
@@ -438,7 +270,7 @@ static int proc_sctp_do_rto_min(const struct ctl_table *ctl, int write,
 
 	ret = proc_dointvec(&tbl, write, buffer, lenp, ppos);
 	if (write && ret == 0) {
-		if (new_value > max || new_value < min)
+		if (new_value > (int) net->sctp.rto_max || new_value < 1)
 			return -EINVAL;
 
 		net->sctp.rto_min = new_value;
@@ -451,8 +283,6 @@ static int proc_sctp_do_rto_max(const struct ctl_table *ctl, int write,
 				void *buffer, size_t *lenp, loff_t *ppos)
 {
 	struct net *net = container_of(ctl->data, struct net, sctp.rto_max);
-	unsigned int min = *(unsigned int *) ctl->extra1;
-	unsigned int max = *(unsigned int *) ctl->extra2;
 	struct ctl_table tbl;
 	int ret, new_value;
 
@@ -466,7 +296,8 @@ static int proc_sctp_do_rto_max(const struct ctl_table *ctl, int write,
 
 	ret = proc_dointvec(&tbl, write, buffer, lenp, ppos);
 	if (write && ret == 0) {
-		if (new_value > max || new_value < min)
+		if (new_value > (int) timer_max ||
+		    new_value < (int) net->sctp.rto_min)
 			return -EINVAL;
 
 		net->sctp.rto_max = new_value;
@@ -485,6 +316,28 @@ static int proc_sctp_do_alpha_beta(const struct ctl_table *ctl, int write,
 	return proc_dointvec_minmax(ctl, write, buffer, lenp, ppos);
 }
 
+static int proc_sctp_do_alpha(const struct ctl_table *ctl, int write,
+			      void *buffer, size_t *lenp, loff_t *ppos)
+{
+	struct ctl_table tmp = *ctl;
+
+	tmp.extra1 = &rto_alpha_min;
+	tmp.extra2 = &rto_alpha_max;
+
+	return proc_sctp_do_alpha_beta(&tmp, write, buffer, lenp, ppos);
+}
+
+static int proc_sctp_do_beta(const struct ctl_table *ctl, int write,
+			     void *buffer, size_t *lenp, loff_t *ppos)
+{
+	struct ctl_table tmp = *ctl;
+
+	tmp.extra1 = &rto_beta_min;
+	tmp.extra2 = &rto_beta_max;
+
+	return proc_sctp_do_alpha_beta(&tmp, write, buffer, lenp, ppos);
+}
+
 static int proc_sctp_do_auth(const struct ctl_table *ctl, int write,
 			     void *buffer, size_t *lenp, loff_t *ppos)
 {
@@ -520,8 +373,6 @@ static int proc_sctp_do_udp_port(const struct ctl_table *ctl, int write,
 				 void *buffer, size_t *lenp, loff_t *ppos)
 {
 	struct net *net = container_of(ctl->data, struct net, sctp.udp_port);
-	unsigned int min = *(unsigned int *)ctl->extra1;
-	unsigned int max = *(unsigned int *)ctl->extra2;
 	struct ctl_table tbl;
 	int ret, new_value;
 
@@ -537,7 +388,7 @@ static int proc_sctp_do_udp_port(const struct ctl_table *ctl, int write,
 	if (write && ret == 0) {
 		struct sock *sk = net->sctp.ctl_sock;
 
-		if (new_value > max || new_value < min)
+		if (new_value > udp_port_max || new_value < 0)
 			return -EINVAL;
 
 		mutex_lock(&sctp_sysctl_mutex);
@@ -588,38 +439,20 @@ static int proc_sctp_do_probe_interval(const struct ctl_table *ctl, int write,
 
 int sctp_sysctl_net_register(struct net *net)
 {
-	size_t table_size = ARRAY_SIZE(sctp_net_table);
-	struct ctl_table *table;
-	int i;
-
-	table = kmemdup(sctp_net_table, sizeof(sctp_net_table), GFP_KERNEL);
-	if (!table)
+	struct sysctl_context ctx = {
+		.ns.net_ns = net,
+	};
+	net->sctp.sysctl_header = register_sysctl_fields(&net->sysctls, "net/sctp",
+							 sctp_net_table, &ctx);
+	if (!net->sctp.sysctl_header)
 		return -ENOMEM;
 
-	for (i = 0; i < table_size; i++)
-		table[i].data += (char *)(&net->sctp) - (char *)&init_net.sctp;
-
-	table[SCTP_RTO_MIN_IDX].extra2 = &net->sctp.rto_max;
-	table[SCTP_RTO_MAX_IDX].extra1 = &net->sctp.rto_min;
-	table[SCTP_PF_RETRANS_IDX].extra2 = &net->sctp.ps_retrans;
-	table[SCTP_PS_RETRANS_IDX].extra1 = &net->sctp.pf_retrans;
-
-	net->sctp.sysctl_header = register_net_sysctl_sz(net, "net/sctp",
-							 table, table_size);
-	if (net->sctp.sysctl_header == NULL) {
-		kfree(table);
-		return -ENOMEM;
-	}
 	return 0;
 }
 
 void sctp_sysctl_net_unregister(struct net *net)
 {
-	const struct ctl_table *table;
-
-	table = net->sctp.sysctl_header->ctl_table_arg;
 	unregister_net_sysctl_table(net->sctp.sysctl_header);
-	kfree(table);
 }
 
 static struct ctl_table_header *sctp_sysctl_header;
-- 
2.55.0


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [RFC PATCH v1 18/30] sysctl: net: use sysctl_field in core IPv6 sysctls
       [not found] <cover.1787771905.git.legion@kernel.org>
                   ` (16 preceding siblings ...)
  2026-08-26 19:42 ` [RFC PATCH v1 17/30] sysctl: net: use sysctl_field in SCTP sysctls Alexey Gladkov
@ 2026-08-26 19:42 ` Alexey Gladkov
  2026-08-26 19:42 ` [RFC PATCH v1 19/30] sysctl: net: use sysctl_field in net core per-net sysctls Alexey Gladkov
                   ` (11 subsequent siblings)
  29 siblings, 0 replies; 37+ messages in thread
From: Alexey Gladkov @ 2026-08-26 19:42 UTC (permalink / raw)
  To: Linus Torvalds, Eric W . Biederman, Kees Cook, Joel Granados
  Cc: LKML, linux-fsdevel

The core IPv6, route and ICMP sysctl tables are per-netns, but they
currently get there by cloning ctl_table arrays and patching each data
pointer after allocation. That keeps writable table copies around for
every net namespace and makes the code depend on table indexes staying
in sync with the patch-up code.

Describe these tables with sysctl_field instead. The data pointers are
now derived from the registration context, so the tables can stay static
and const while still resolving to the right net namespace.

Keep the few non-standard handlers as custom fields. The route flush
handler now derives the net namespace from its data pointer instead of
using extra1, and the multipath and wide IOAM handlers keep their limits
inside the handler-local ctl_table copy.

Signed-off-by: Alexey Gladkov <legion@kernel.org>
---
 include/net/ipv6.h         |   6 +-
 net/ipv6/icmp.c            | 127 ++++++---------
 net/ipv6/route.c           | 173 ++++++++------------
 net/ipv6/sysctl_net_ipv6.c | 322 ++++++++++++-------------------------
 4 files changed, 224 insertions(+), 404 deletions(-)

diff --git a/include/net/ipv6.h b/include/net/ipv6.h
index 1dec81faff28..08ede58aabfb 100644
--- a/include/net/ipv6.h
+++ b/include/net/ipv6.h
@@ -1207,10 +1207,8 @@ static inline int snmp6_unregister_dev(struct inet6_dev *idev) { return 0; }
 #endif
 
 #ifdef CONFIG_SYSCTL
-struct ctl_table *ipv6_icmp_sysctl_init(struct net *net);
-size_t ipv6_icmp_sysctl_table_size(void);
-struct ctl_table *ipv6_route_sysctl_init(struct net *net);
-size_t ipv6_route_sysctl_table_size(struct net *net);
+int ipv6_icmp_sysctl_register(struct sysctl_context *ctx);
+int ipv6_route_sysctl_register(struct sysctl_context *ctx);
 int ipv6_sysctl_register(void);
 void ipv6_sysctl_unregister(void);
 #endif
diff --git a/net/ipv6/icmp.c b/net/ipv6/icmp.c
index efb23807a026..8fd0dc035ccd 100644
--- a/net/ipv6/icmp.c
+++ b/net/ipv6/icmp.c
@@ -1371,87 +1371,62 @@ EXPORT_SYMBOL(icmpv6_err_convert);
 
 #ifdef CONFIG_SYSCTL
 
-static u32 icmpv6_errors_extension_mask_all =
+static unsigned int icmpv6_errors_extension_mask_all =
 	GENMASK_U8(ICMP_ERR_EXT_COUNT - 1, 0);
 
-static struct ctl_table ipv6_icmp_table_template[] = {
-	{
-		.procname	= "ratelimit",
-		.data		= &init_net.ipv6.sysctl.icmpv6_time,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_ms_jiffies,
-	},
-	{
-		.procname	= "echo_ignore_all",
-		.data		= &init_net.ipv6.sysctl.icmpv6_echo_ignore_all,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler = proc_dou8vec_minmax,
-	},
-	{
-		.procname	= "echo_ignore_multicast",
-		.data		= &init_net.ipv6.sysctl.icmpv6_echo_ignore_multicast,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler = proc_dou8vec_minmax,
-	},
-	{
-		.procname	= "echo_ignore_anycast",
-		.data		= &init_net.ipv6.sysctl.icmpv6_echo_ignore_anycast,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler = proc_dou8vec_minmax,
-	},
-	{
-		.procname	= "ratemask",
-		.data		= &init_net.ipv6.sysctl.icmpv6_ratemask_ptr,
-		.maxlen		= ICMPV6_MSG_MAX + 1,
-		.mode		= 0644,
-		.proc_handler = proc_do_large_bitmap,
-	},
-	{
-		.procname	= "error_anycast_as_unicast",
-		.data		= &init_net.ipv6.sysctl.icmpv6_error_anycast_as_unicast,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_ONE,
-	},
-	{
-		.procname	= "errors_extension_mask",
-		.data		= &init_net.ipv6.sysctl.icmpv6_errors_extension_mask,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= &icmpv6_errors_extension_mask_all,
-	},
-};
+#define IPV6_ICMP_DATA(type, name, field)				\
+static type *ipv6_icmp_##name##_data(const struct sysctl_context *ctx)	\
+{									\
+	return &ctx->ns.net_ns->ipv6.sysctl.field;			\
+}
 
-struct ctl_table * __net_init ipv6_icmp_sysctl_init(struct net *net)
-{
-	struct ctl_table *table;
-
-	table = kmemdup(ipv6_icmp_table_template,
-			sizeof(ipv6_icmp_table_template),
-			GFP_KERNEL);
-
-	if (table) {
-		table[0].data = &net->ipv6.sysctl.icmpv6_time;
-		table[1].data = &net->ipv6.sysctl.icmpv6_echo_ignore_all;
-		table[2].data = &net->ipv6.sysctl.icmpv6_echo_ignore_multicast;
-		table[3].data = &net->ipv6.sysctl.icmpv6_echo_ignore_anycast;
-		table[4].data = &net->ipv6.sysctl.icmpv6_ratemask_ptr;
-		table[5].data = &net->ipv6.sysctl.icmpv6_error_anycast_as_unicast;
-		table[6].data = &net->ipv6.sysctl.icmpv6_errors_extension_mask;
-	}
-	return table;
+#define IPV6_ICMP_CUSTOM_DATA(name, field)				\
+static void *ipv6_icmp_##name##_data(const struct sysctl_context *ctx)	\
+{									\
+	return &ctx->ns.net_ns->ipv6.sysctl.field;			\
 }
 
-size_t ipv6_icmp_sysctl_table_size(void)
+IPV6_ICMP_CUSTOM_DATA(ratelimit, icmpv6_time)
+IPV6_ICMP_DATA(u8, echo_ignore_all, icmpv6_echo_ignore_all)
+IPV6_ICMP_DATA(u8, echo_ignore_multicast, icmpv6_echo_ignore_multicast)
+IPV6_ICMP_DATA(u8, echo_ignore_anycast, icmpv6_echo_ignore_anycast)
+IPV6_ICMP_CUSTOM_DATA(ratemask, icmpv6_ratemask_ptr)
+IPV6_ICMP_DATA(u8, error_anycast_as_unicast,
+	       icmpv6_error_anycast_as_unicast)
+IPV6_ICMP_DATA(u8, errors_extension_mask,
+	       icmpv6_errors_extension_mask)
+
+static const struct sysctl_field ipv6_icmp_table[] = {
+	SYSCTL_FIELD_CUSTOM("ratelimit", 0644, sizeof(int),
+			 ipv6_icmp_ratelimit_data, proc_dointvec_ms_jiffies),
+	SYSCTL_FIELD_U8("echo_ignore_all", 0644,
+		     ipv6_icmp_echo_ignore_all_data),
+	SYSCTL_FIELD_U8("echo_ignore_multicast", 0644,
+		     ipv6_icmp_echo_ignore_multicast_data),
+	SYSCTL_FIELD_U8("echo_ignore_anycast", 0644,
+		     ipv6_icmp_echo_ignore_anycast_data),
+	SYSCTL_FIELD_CUSTOM("ratemask", 0644, ICMPV6_MSG_MAX + 1,
+			 ipv6_icmp_ratemask_data, proc_do_large_bitmap),
+	SYSCTL_FIELD_STATIC_U8_MINMAX("error_anycast_as_unicast", 0644,
+				   ipv6_icmp_error_anycast_as_unicast_data,
+				   SYSCTL_UINT_ZERO, SYSCTL_UINT_ONE),
+	SYSCTL_FIELD_STATIC_U8_MINMAX("errors_extension_mask", 0644,
+				   ipv6_icmp_errors_extension_mask_data,
+				   SYSCTL_UINT_ZERO,
+				   &icmpv6_errors_extension_mask_all),
+};
+
+int ipv6_icmp_sysctl_register(struct sysctl_context *ctx)
 {
-	return ARRAY_SIZE(ipv6_icmp_table_template);
+	struct ctl_table_header *hdr;
+
+	hdr = register_sysctl_fields(&ctx->ns.net_ns->sysctls, "net/ipv6/icmp",
+				     ipv6_icmp_table, ctx);
+	if (!hdr)
+		return -ENOMEM;
+
+	ctx->ns.net_ns->ipv6.sysctl.icmp_hdr = hdr;
+
+	return 0;
 }
 #endif
diff --git a/net/ipv6/route.c b/net/ipv6/route.c
index b106e5fef9cb..4861972d6bd3 100644
--- a/net/ipv6/route.c
+++ b/net/ipv6/route.c
@@ -6539,127 +6539,82 @@ static int ipv6_sysctl_rtcache_flush(const struct ctl_table *ctl, int write,
 	if (ret)
 		return ret;
 
-	net = (struct net *)ctl->extra1;
+	net = container_of(ctl->data, struct net, ipv6.sysctl.flush_delay);
 	delay = READ_ONCE(net->ipv6.sysctl.flush_delay);
 	fib6_run_gc(delay <= 0 ? 0 : (unsigned long)delay, net, delay > 0);
 	return 0;
 }
 
-static struct ctl_table ipv6_route_table_template[] = {
-	{
-		.procname	=	"max_size",
-		.data		=	&init_net.ipv6.sysctl.ip6_rt_max_size,
-		.maxlen		=	sizeof(int),
-		.mode		=	0644,
-		.proc_handler	=	proc_dointvec,
-	},
-	{
-		.procname	=	"gc_thresh",
-		.data		=	&ip6_dst_ops_template.gc_thresh,
-		.maxlen		=	sizeof(int),
-		.mode		=	0644,
-		.proc_handler	=	proc_dointvec,
-	},
-	{
-		.procname	=	"flush",
-		.data		=	&init_net.ipv6.sysctl.flush_delay,
-		.maxlen		=	sizeof(int),
-		.mode		=	0200,
-		.proc_handler	=	ipv6_sysctl_rtcache_flush
-	},
-	{
-		.procname	=	"gc_min_interval",
-		.data		=	&init_net.ipv6.sysctl.ip6_rt_gc_min_interval,
-		.maxlen		=	sizeof(int),
-		.mode		=	0644,
-		.proc_handler	=	proc_dointvec_jiffies,
-	},
-	{
-		.procname	=	"gc_timeout",
-		.data		=	&init_net.ipv6.sysctl.ip6_rt_gc_timeout,
-		.maxlen		=	sizeof(int),
-		.mode		=	0644,
-		.proc_handler	=	proc_dointvec_jiffies,
-	},
-	{
-		.procname	=	"gc_interval",
-		.data		=	&init_net.ipv6.sysctl.ip6_rt_gc_interval,
-		.maxlen		=	sizeof(int),
-		.mode		=	0644,
-		.proc_handler	=	proc_dointvec_jiffies,
-	},
-	{
-		.procname	=	"gc_elasticity",
-		.data		=	&init_net.ipv6.sysctl.ip6_rt_gc_elasticity,
-		.maxlen		=	sizeof(int),
-		.mode		=	0644,
-		.proc_handler	=	proc_dointvec,
-	},
-	{
-		.procname	=	"mtu_expires",
-		.data		=	&init_net.ipv6.sysctl.ip6_rt_mtu_expires,
-		.maxlen		=	sizeof(int),
-		.mode		=	0644,
-		.proc_handler	=	proc_dointvec_jiffies,
-	},
-	{
-		.procname	=	"min_adv_mss",
-		.data		=	&init_net.ipv6.sysctl.ip6_rt_min_advmss,
-		.maxlen		=	sizeof(int),
-		.mode		=	0644,
-		.proc_handler	=	proc_dointvec,
-	},
-	{
-		.procname	=	"gc_min_interval_ms",
-		.data		=	&init_net.ipv6.sysctl.ip6_rt_gc_min_interval,
-		.maxlen		=	sizeof(int),
-		.mode		=	0644,
-		.proc_handler	=	proc_dointvec_ms_jiffies,
-	},
-	{
-		.procname	=	"skip_notify_on_dev_down",
-		.data		=	&init_net.ipv6.sysctl.skip_notify_on_dev_down,
-		.maxlen		=	sizeof(u8),
-		.mode		=	0644,
-		.proc_handler	=	proc_dou8vec_minmax,
-		.extra1		=	SYSCTL_ZERO,
-		.extra2		=	SYSCTL_ONE,
-	},
+#define IPV6_ROUTE_DATA(type, name, field)				\
+static type *ipv6_route_##name##_data(const struct sysctl_context *ctx)	\
+{									\
+	return &ctx->ns.net_ns->ipv6.sysctl.field;			\
+}
+
+#define IPV6_ROUTE_CUSTOM_DATA(name, field)				\
+static void *ipv6_route_##name##_data(const struct sysctl_context *ctx)	\
+{									\
+	return &ctx->ns.net_ns->ipv6.sysctl.field;			\
+}
+
+IPV6_ROUTE_DATA(int, max_size, ip6_rt_max_size)
+IPV6_ROUTE_DATA(int, gc_elasticity, ip6_rt_gc_elasticity)
+IPV6_ROUTE_DATA(int, min_adv_mss, ip6_rt_min_advmss)
+IPV6_ROUTE_DATA(u8, skip_notify_on_dev_down, skip_notify_on_dev_down)
+IPV6_ROUTE_CUSTOM_DATA(flush, flush_delay)
+IPV6_ROUTE_CUSTOM_DATA(gc_min_interval, ip6_rt_gc_min_interval)
+IPV6_ROUTE_CUSTOM_DATA(gc_timeout, ip6_rt_gc_timeout)
+IPV6_ROUTE_CUSTOM_DATA(gc_interval, ip6_rt_gc_interval)
+IPV6_ROUTE_CUSTOM_DATA(mtu_expires, ip6_rt_mtu_expires)
+
+static int *ipv6_route_gc_thresh_data(const struct sysctl_context *ctx)
+{
+	return &ctx->ns.net_ns->ipv6.ip6_dst_ops.gc_thresh;
+}
+
+static const struct sysctl_field ipv6_route_table[] = {
+	SYSCTL_FIELD_INT("max_size", 0644, ipv6_route_max_size_data),
+	SYSCTL_FIELD_INT("gc_thresh", 0644, ipv6_route_gc_thresh_data),
+	SYSCTL_FIELD_CUSTOM("flush", 0200, sizeof(int), ipv6_route_flush_data,
+			 ipv6_sysctl_rtcache_flush),
+	SYSCTL_FIELD_CUSTOM("gc_min_interval", 0644, sizeof(int),
+			 ipv6_route_gc_min_interval_data,
+			 proc_dointvec_jiffies),
+	SYSCTL_FIELD_CUSTOM("gc_timeout", 0644, sizeof(int),
+			 ipv6_route_gc_timeout_data, proc_dointvec_jiffies),
+	SYSCTL_FIELD_CUSTOM("gc_interval", 0644, sizeof(int),
+			 ipv6_route_gc_interval_data, proc_dointvec_jiffies),
+	SYSCTL_FIELD_INT("gc_elasticity", 0644,
+		      ipv6_route_gc_elasticity_data),
+	SYSCTL_FIELD_CUSTOM("mtu_expires", 0644, sizeof(int),
+			 ipv6_route_mtu_expires_data, proc_dointvec_jiffies),
+	SYSCTL_FIELD_INT("min_adv_mss", 0644, ipv6_route_min_adv_mss_data),
+	SYSCTL_FIELD_CUSTOM("gc_min_interval_ms", 0644, sizeof(int),
+			 ipv6_route_gc_min_interval_data,
+			 proc_dointvec_ms_jiffies),
+	SYSCTL_FIELD_STATIC_U8_MINMAX("skip_notify_on_dev_down", 0644,
+				   ipv6_route_skip_notify_on_dev_down_data,
+				   SYSCTL_UINT_ZERO, SYSCTL_UINT_ONE),
 };
 
-struct ctl_table * __net_init ipv6_route_sysctl_init(struct net *net)
+int ipv6_route_sysctl_register(struct sysctl_context *ctx)
 {
-	struct ctl_table *table;
-
-	table = kmemdup(ipv6_route_table_template,
-			sizeof(ipv6_route_table_template),
-			GFP_KERNEL);
+	struct ctl_table_header *hdr;
+	size_t table_size = ARRAY_SIZE(ipv6_route_table);
 
-	if (table) {
-		table[0].data = &net->ipv6.sysctl.ip6_rt_max_size;
-		table[1].data = &net->ipv6.ip6_dst_ops.gc_thresh;
-		table[2].data = &net->ipv6.sysctl.flush_delay;
-		table[2].extra1 = net;
-		table[3].data = &net->ipv6.sysctl.ip6_rt_gc_min_interval;
-		table[4].data = &net->ipv6.sysctl.ip6_rt_gc_timeout;
-		table[5].data = &net->ipv6.sysctl.ip6_rt_gc_interval;
-		table[6].data = &net->ipv6.sysctl.ip6_rt_gc_elasticity;
-		table[7].data = &net->ipv6.sysctl.ip6_rt_mtu_expires;
-		table[8].data = &net->ipv6.sysctl.ip6_rt_min_advmss;
-		table[9].data = &net->ipv6.sysctl.ip6_rt_gc_min_interval;
-		table[10].data = &net->ipv6.sysctl.skip_notify_on_dev_down;
-	}
+	/* Don't export sysctls to unprivileged users */
+	if (ctx->ns.net_ns->user_ns != &init_user_ns)
+		table_size = 1;
 
-	return table;
-}
+	hdr = __register_sysctl_fields(&ctx->ns.net_ns->sysctls, "net/ipv6/route",
+				       ipv6_route_table, table_size,
+				       ctx, sizeof(*ctx));
+	if (!hdr)
+		return -ENOMEM;
 
-size_t ipv6_route_sysctl_table_size(struct net *net)
-{
-	/* Don't export sysctls to unprivileged users */
-	if (net->user_ns != &init_user_ns)
-		return 1;
+	ctx->ns.net_ns->ipv6.sysctl.route_hdr = hdr;
 
-	return ARRAY_SIZE(ipv6_route_table_template);
+	return 0;
 }
 #endif
 
diff --git a/net/ipv6/sysctl_net_ipv6.c b/net/ipv6/sysctl_net_ipv6.c
index d2cd33e2698d..45485ead8048 100644
--- a/net/ipv6/sysctl_net_ipv6.c
+++ b/net/ipv6/sysctl_net_ipv6.c
@@ -10,7 +10,6 @@
 #include <linux/sysctl.h>
 #include <linux/in6.h>
 #include <linux/ipv6.h>
-#include <linux/slab.h>
 #include <linux/export.h>
 #include <net/ndisc.h>
 #include <net/ipv6.h>
@@ -24,21 +23,24 @@
 #include <linux/ioam6.h>
 
 static int flowlabel_reflect_max = 0x7;
-static int auto_flowlabels_max = IP6_AUTO_FLOW_LABEL_MAX;
-static u32 rt6_multipath_hash_fields_all_mask =
+static unsigned int auto_flowlabels_max = IP6_AUTO_FLOW_LABEL_MAX;
+static unsigned int rt6_multipath_hash_fields_all_mask =
 	FIB_MULTIPATH_HASH_FIELD_ALL_MASK;
-static u32 ioam6_id_max = IOAM6_DEFAULT_ID;
+static unsigned int ioam6_id_max = IOAM6_DEFAULT_ID;
 static u64 ioam6_id_wide_max = IOAM6_DEFAULT_ID_WIDE;
 
 static int proc_rt6_multipath_hash_policy(const struct ctl_table *table, int write,
 					  void *buffer, size_t *lenp, loff_t *ppos)
 {
+	struct ctl_table tmp = *table;
 	struct net *net;
 	int ret;
 
 	net = container_of(table->data, struct net,
 			   ipv6.sysctl.multipath_hash_policy);
-	ret = proc_dou8vec_minmax(table, write, buffer, lenp, ppos);
+	tmp.extra1 = SYSCTL_UINT_ZERO;
+	tmp.extra2 = SYSCTL_UINT_THREE;
+	ret = proc_dou8vec_minmax(&tmp, write, buffer, lenp, ppos);
 	if (write && ret == 0)
 		call_netevent_notifiers(NETEVENT_IPV6_MPATH_HASH_UPDATE, net);
 
@@ -49,170 +51,103 @@ static int
 proc_rt6_multipath_hash_fields(const struct ctl_table *table, int write, void *buffer,
 			       size_t *lenp, loff_t *ppos)
 {
+	struct ctl_table tmp = *table;
 	struct net *net;
 	int ret;
 
 	net = container_of(table->data, struct net,
 			   ipv6.sysctl.multipath_hash_fields);
-	ret = proc_douintvec_minmax(table, write, buffer, lenp, ppos);
+	tmp.extra1 = SYSCTL_UINT_ONE;
+	tmp.extra2 = &rt6_multipath_hash_fields_all_mask;
+	ret = proc_douintvec_minmax(&tmp, write, buffer, lenp, ppos);
 	if (write && ret == 0)
 		call_netevent_notifiers(NETEVENT_IPV6_MPATH_HASH_UPDATE, net);
 
 	return ret;
 }
 
-static struct ctl_table ipv6_table_template[] = {
-	{
-		.procname	= "bindv6only",
-		.data		= &init_net.ipv6.sysctl.bindv6only,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-	},
-	{
-		.procname	= "anycast_src_echo_reply",
-		.data		= &init_net.ipv6.sysctl.anycast_src_echo_reply,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-	},
-	{
-		.procname	= "flowlabel_consistency",
-		.data		= &init_net.ipv6.sysctl.flowlabel_consistency,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-	},
-	{
-		.procname	= "auto_flowlabels",
-		.data		= &init_net.ipv6.sysctl.auto_flowlabels,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra2		= &auto_flowlabels_max
-	},
-	{
-		.procname	= "fwmark_reflect",
-		.data		= &init_net.ipv6.sysctl.fwmark_reflect,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-	},
-	{
-		.procname	= "idgen_retries",
-		.data		= &init_net.ipv6.sysctl.idgen_retries,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
-	{
-		.procname	= "idgen_delay",
-		.data		= &init_net.ipv6.sysctl.idgen_delay,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_jiffies,
-	},
-	{
-		.procname	= "flowlabel_state_ranges",
-		.data		= &init_net.ipv6.sysctl.flowlabel_state_ranges,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-	},
-	{
-		.procname	= "ip_nonlocal_bind",
-		.data		= &init_net.ipv6.sysctl.ip_nonlocal_bind,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-	},
-	{
-		.procname	= "flowlabel_reflect",
-		.data		= &init_net.ipv6.sysctl.flowlabel_reflect,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= &flowlabel_reflect_max,
-	},
-	{
-		.procname	= "max_dst_opts_number",
-		.data		= &init_net.ipv6.sysctl.max_dst_opts_cnt,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec
-	},
-	{
-		.procname	= "max_hbh_opts_number",
-		.data		= &init_net.ipv6.sysctl.max_hbh_opts_cnt,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec
-	},
-	{
-		.procname	= "max_dst_opts_length",
-		.data		= &init_net.ipv6.sysctl.max_dst_opts_len,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec
-	},
-	{
-		.procname	= "max_hbh_length",
-		.data		= &init_net.ipv6.sysctl.max_hbh_opts_len,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec
-	},
-	{
-		.procname	= "fib_multipath_hash_policy",
-		.data		= &init_net.ipv6.sysctl.multipath_hash_policy,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler   = proc_rt6_multipath_hash_policy,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_THREE,
-	},
-	{
-		.procname	= "fib_multipath_hash_fields",
-		.data		= &init_net.ipv6.sysctl.multipath_hash_fields,
-		.maxlen		= sizeof(u32),
-		.mode		= 0644,
-		.proc_handler	= proc_rt6_multipath_hash_fields,
-		.extra1		= SYSCTL_ONE,
-		.extra2		= &rt6_multipath_hash_fields_all_mask,
-	},
-	{
-		.procname	= "seg6_flowlabel",
-		.data		= &init_net.ipv6.sysctl.seg6_flowlabel,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec
-	},
-	{
-		.procname	= "fib_notify_on_flag_change",
-		.data		= &init_net.ipv6.sysctl.fib_notify_on_flag_change,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra1         = SYSCTL_ZERO,
-		.extra2         = SYSCTL_TWO,
-	},
-	{
-		.procname	= "ioam6_id",
-		.data		= &init_net.ipv6.sysctl.ioam6_id,
-		.maxlen		= sizeof(u32),
-		.mode		= 0644,
-		.proc_handler	= proc_douintvec_minmax,
-		.extra2		= &ioam6_id_max,
-	},
-	{
-		.procname	= "ioam6_id_wide",
-		.data		= &init_net.ipv6.sysctl.ioam6_id_wide,
-		.maxlen		= sizeof(u64),
-		.mode		= 0644,
-		.proc_handler	= proc_doulongvec_minmax,
-		.extra2		= &ioam6_id_wide_max,
-	},
+#define IPV6_SYSCTL_DATA(type, name, field)				\
+static type *ipv6_##name##_data(const struct sysctl_context *ctx)		\
+{									\
+	return &ctx->ns.net_ns->ipv6.sysctl.field;			\
+}
+
+#define IPV6_SYSCTL_CUSTOM_DATA(name, field)				\
+static void *ipv6_##name##_data(const struct sysctl_context *ctx)		\
+{									\
+	return &ctx->ns.net_ns->ipv6.sysctl.field;			\
+}
+
+IPV6_SYSCTL_DATA(u8, bindv6only, bindv6only)
+IPV6_SYSCTL_DATA(u8, anycast_src_echo_reply, anycast_src_echo_reply)
+IPV6_SYSCTL_DATA(u8, flowlabel_consistency, flowlabel_consistency)
+IPV6_SYSCTL_DATA(u8, auto_flowlabels, auto_flowlabels)
+IPV6_SYSCTL_DATA(u8, fwmark_reflect, fwmark_reflect)
+IPV6_SYSCTL_DATA(int, idgen_retries, idgen_retries)
+IPV6_SYSCTL_CUSTOM_DATA(idgen_delay, idgen_delay)
+IPV6_SYSCTL_DATA(u8, flowlabel_state_ranges, flowlabel_state_ranges)
+IPV6_SYSCTL_DATA(u8, ip_nonlocal_bind, ip_nonlocal_bind)
+IPV6_SYSCTL_DATA(int, flowlabel_reflect, flowlabel_reflect)
+IPV6_SYSCTL_DATA(int, max_dst_opts_number, max_dst_opts_cnt)
+IPV6_SYSCTL_DATA(int, max_hbh_opts_number, max_hbh_opts_cnt)
+IPV6_SYSCTL_DATA(int, max_dst_opts_length, max_dst_opts_len)
+IPV6_SYSCTL_DATA(int, max_hbh_length, max_hbh_opts_len)
+IPV6_SYSCTL_CUSTOM_DATA(fib_multipath_hash_policy, multipath_hash_policy)
+IPV6_SYSCTL_CUSTOM_DATA(fib_multipath_hash_fields, multipath_hash_fields)
+IPV6_SYSCTL_DATA(int, seg6_flowlabel, seg6_flowlabel)
+IPV6_SYSCTL_DATA(u8, fib_notify_on_flag_change, fib_notify_on_flag_change)
+IPV6_SYSCTL_DATA(unsigned int, ioam6_id, ioam6_id)
+IPV6_SYSCTL_CUSTOM_DATA(ioam6_id_wide, ioam6_id_wide)
+
+static int proc_ioam6_id_wide(const struct ctl_table *table, int write,
+			      void *buffer, size_t *lenp, loff_t *ppos)
+{
+	struct ctl_table tmp = *table;
+
+	tmp.extra2 = &ioam6_id_wide_max;
+	return proc_doulongvec_minmax(&tmp, write, buffer, lenp, ppos);
+}
+
+static const struct sysctl_field ipv6_table[] = {
+	SYSCTL_FIELD_U8("bindv6only", 0644, ipv6_bindv6only_data),
+	SYSCTL_FIELD_U8("anycast_src_echo_reply", 0644,
+		     ipv6_anycast_src_echo_reply_data),
+	SYSCTL_FIELD_U8("flowlabel_consistency", 0644,
+		     ipv6_flowlabel_consistency_data),
+	SYSCTL_FIELD_STATIC_U8_MINMAX("auto_flowlabels", 0644,
+				   ipv6_auto_flowlabels_data, NULL,
+				   &auto_flowlabels_max),
+	SYSCTL_FIELD_U8("fwmark_reflect", 0644, ipv6_fwmark_reflect_data),
+	SYSCTL_FIELD_INT("idgen_retries", 0644, ipv6_idgen_retries_data),
+	SYSCTL_FIELD_CUSTOM("idgen_delay", 0644, sizeof(int),
+			 ipv6_idgen_delay_data, proc_dointvec_jiffies),
+	SYSCTL_FIELD_U8("flowlabel_state_ranges", 0644,
+		     ipv6_flowlabel_state_ranges_data),
+	SYSCTL_FIELD_U8("ip_nonlocal_bind", 0644, ipv6_ip_nonlocal_bind_data),
+	SYSCTL_FIELD_STATIC_INT_MINMAX("flowlabel_reflect", 0644,
+				    ipv6_flowlabel_reflect_data,
+				    SYSCTL_ZERO, &flowlabel_reflect_max),
+	SYSCTL_FIELD_INT("max_dst_opts_number", 0644,
+		      ipv6_max_dst_opts_number_data),
+	SYSCTL_FIELD_INT("max_hbh_opts_number", 0644,
+		      ipv6_max_hbh_opts_number_data),
+	SYSCTL_FIELD_INT("max_dst_opts_length", 0644,
+		      ipv6_max_dst_opts_length_data),
+	SYSCTL_FIELD_INT("max_hbh_length", 0644, ipv6_max_hbh_length_data),
+	SYSCTL_FIELD_CUSTOM("fib_multipath_hash_policy", 0644, sizeof(u8),
+			 ipv6_fib_multipath_hash_policy_data,
+			 proc_rt6_multipath_hash_policy),
+	SYSCTL_FIELD_CUSTOM("fib_multipath_hash_fields", 0644, sizeof(u32),
+			 ipv6_fib_multipath_hash_fields_data,
+			 proc_rt6_multipath_hash_fields),
+	SYSCTL_FIELD_INT("seg6_flowlabel", 0644, ipv6_seg6_flowlabel_data),
+	SYSCTL_FIELD_STATIC_U8_MINMAX("fib_notify_on_flag_change", 0644,
+				   ipv6_fib_notify_on_flag_change_data,
+				   SYSCTL_UINT_ZERO, SYSCTL_UINT_TWO),
+	SYSCTL_FIELD_STATIC_UINT_MINMAX("ioam6_id", 0644, ipv6_ioam6_id_data,
+				     NULL, &ioam6_id_max),
+	SYSCTL_FIELD_CUSTOM("ioam6_id_wide", 0644, sizeof(u64),
+			 ipv6_ioam6_id_wide_data, proc_ioam6_id_wide),
 };
 
 static struct ctl_table ipv6_rotable[] = {
@@ -251,81 +186,38 @@ static struct ctl_table ipv6_rotable[] = {
 
 static int __net_init ipv6_sysctl_net_init(struct net *net)
 {
-	size_t table_size = ARRAY_SIZE(ipv6_table_template);
-	struct ctl_table *ipv6_table;
-	struct ctl_table *ipv6_route_table;
-	struct ctl_table *ipv6_icmp_table;
-	int err, i;
-
-	err = -ENOMEM;
-	ipv6_table = kmemdup(ipv6_table_template, sizeof(ipv6_table_template),
-			     GFP_KERNEL);
-	if (!ipv6_table)
-		goto out;
-	/* Update the variables to point into the current struct net */
-	for (i = 0; i < table_size; i++)
-		ipv6_table[i].data += (void *)net - (void *)&init_net;
-
-	ipv6_route_table = ipv6_route_sysctl_init(net);
-	if (!ipv6_route_table)
-		goto out_ipv6_table;
-
-	ipv6_icmp_table = ipv6_icmp_sysctl_init(net);
-	if (!ipv6_icmp_table)
-		goto out_ipv6_route_table;
+	struct sysctl_context ctx = {
+		.ns.net_ns = net,
+	};
+	int err = -ENOMEM;
 
-	net->ipv6.sysctl.hdr = register_net_sysctl_sz(net, "net/ipv6",
-						      ipv6_table, table_size);
+	net->ipv6.sysctl.hdr = register_sysctl_fields(&net->sysctls, "net/ipv6",
+						      ipv6_table, &ctx);
 	if (!net->ipv6.sysctl.hdr)
-		goto out_ipv6_icmp_table;
+		goto out;
 
-	net->ipv6.sysctl.route_hdr = register_net_sysctl_sz(net,
-							    "net/ipv6/route",
-							    ipv6_route_table,
-							    ipv6_route_sysctl_table_size(net));
-	if (!net->ipv6.sysctl.route_hdr)
-		goto out_unregister_ipv6_table;
+	err = ipv6_route_sysctl_register(&ctx);
+	if (err)
+		goto out_err;
 
-	net->ipv6.sysctl.icmp_hdr = register_net_sysctl_sz(net,
-							   "net/ipv6/icmp",
-							   ipv6_icmp_table,
-							   ipv6_icmp_sysctl_table_size());
-	if (!net->ipv6.sysctl.icmp_hdr)
-		goto out_unregister_route_table;
+	err = ipv6_icmp_sysctl_register(&ctx);
+	if (err)
+		goto out_err;
 
 	err = 0;
 out:
 	return err;
-out_unregister_route_table:
+out_err:
 	unregister_net_sysctl_table(net->ipv6.sysctl.route_hdr);
-out_unregister_ipv6_table:
 	unregister_net_sysctl_table(net->ipv6.sysctl.hdr);
-out_ipv6_icmp_table:
-	kfree(ipv6_icmp_table);
-out_ipv6_route_table:
-	kfree(ipv6_route_table);
-out_ipv6_table:
-	kfree(ipv6_table);
 	goto out;
 }
 
 static void __net_exit ipv6_sysctl_net_exit(struct net *net)
 {
-	const struct ctl_table *ipv6_table;
-	const struct ctl_table *ipv6_route_table;
-	const struct ctl_table *ipv6_icmp_table;
-
-	ipv6_table = net->ipv6.sysctl.hdr->ctl_table_arg;
-	ipv6_route_table = net->ipv6.sysctl.route_hdr->ctl_table_arg;
-	ipv6_icmp_table = net->ipv6.sysctl.icmp_hdr->ctl_table_arg;
-
 	unregister_net_sysctl_table(net->ipv6.sysctl.icmp_hdr);
 	unregister_net_sysctl_table(net->ipv6.sysctl.route_hdr);
 	unregister_net_sysctl_table(net->ipv6.sysctl.hdr);
-
-	kfree(ipv6_table);
-	kfree(ipv6_route_table);
-	kfree(ipv6_icmp_table);
 }
 
 static struct pernet_operations ipv6_sysctl_net_ops = {
-- 
2.55.0


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [RFC PATCH v1 19/30] sysctl: net: use sysctl_field in net core per-net sysctls
       [not found] <cover.1787771905.git.legion@kernel.org>
                   ` (17 preceding siblings ...)
  2026-08-26 19:42 ` [RFC PATCH v1 18/30] sysctl: net: use sysctl_field in core IPv6 sysctls Alexey Gladkov
@ 2026-08-26 19:42 ` Alexey Gladkov
  2026-08-26 19:42 ` [RFC PATCH v1 20/30] sysctl: net: use sysctl_field in SMC sysctls Alexey Gladkov
                   ` (10 subsequent siblings)
  29 siblings, 0 replies; 37+ messages in thread
From: Alexey Gladkov @ 2026-08-26 19:42 UTC (permalink / raw)
  To: Linus Torvalds, Eric W . Biederman, Kees Cook, Joel Granados
  Cc: LKML, linux-fsdevel

The net core per-net sysctl table is cloned for every non-init network
namespace so that the per-net entries can be patched to point at the
current struct net and the global buffer limit entries can be made
read-only.

Describe the table with sysctl_field instead. The per-net entries now
derive their storage from the registration context, and the global
buffer limit entries keep their init-net-only write permission through
a mode callback.

This keeps the table static and const while preserving the existing
permission model.

Signed-off-by: Alexey Gladkov <legion@kernel.org>
---
 net/core/sysctl_net_core.c | 241 ++++++++++++++++++-------------------
 1 file changed, 116 insertions(+), 125 deletions(-)

diff --git a/net/core/sysctl_net_core.c b/net/core/sysctl_net_core.c
index b508618bfc12..39b112a302c2 100644
--- a/net/core/sysctl_net_core.c
+++ b/net/core/sysctl_net_core.c
@@ -678,100 +678,118 @@ static struct ctl_table net_core_table[] = {
 	},
 };
 
-static struct ctl_table netns_core_table[] = {
+static umode_t netns_core_init_net_writable_mode(const struct sysctl_context *ctx)
+{
+	return net_eq(ctx->ns.net_ns, &init_net) ? 0644 : 0444;
+}
+
+#define NETNS_CORE_SYSCTL_DATA(type, name)				\
+static type *netns_core_##name##_data(const struct sysctl_context *ctx)	\
+{									\
+	return &ctx->ns.net_ns->core.sysctl_##name;			\
+}
+
+#define NETNS_CORE_SYSCTL_CUSTOM_DATA(name)				\
+static void *netns_core_##name##_data(const struct sysctl_context *ctx)	\
+{									\
+	return &ctx->ns.net_ns->core.sysctl_##name;			\
+}
+
+NETNS_CORE_SYSCTL_DATA(int, somaxconn)
+NETNS_CORE_SYSCTL_DATA(int, optmem_max)
+NETNS_CORE_SYSCTL_DATA(u8, txrehash)
+NETNS_CORE_SYSCTL_CUSTOM_DATA(txq_reselection)
+NETNS_CORE_SYSCTL_DATA(u8, bypass_prot_mem)
+
+static u8 *netns_core_tstamp_allow_data_value(const struct sysctl_context *ctx)
+{
+	return &ctx->ns.net_ns->core.sysctl_tstamp_allow_data;
+}
+
 #if IS_ENABLED(CONFIG_RPS)
-	{
-		.procname	= "rps_default_mask",
-		.data		= &init_net,
-		.mode		= 0644,
-		.proc_handler	= rps_default_mask_sysctl
-	},
+static void *netns_core_rps_default_mask_data(const struct sysctl_context *ctx)
+{
+	return ctx->ns.net_ns;
+}
 #endif
-	{
-		.procname	= "somaxconn",
-		.data		= &init_net.core.sysctl_somaxconn,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.extra1		= SYSCTL_ZERO,
-		.proc_handler	= proc_dointvec_minmax
-	},
-	{
-		.procname	= "optmem_max",
-		.data		= &init_net.core.sysctl_optmem_max,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.extra1		= SYSCTL_ZERO,
-		.proc_handler	= proc_dointvec_minmax
-	},
-	{
-		.procname	= "txrehash",
-		.data		= &init_net.core.sysctl_txrehash,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_ONE,
-		.proc_handler	= proc_dou8vec_minmax,
-	},
-	{
-		.procname	= "txq_reselection_ms",
-		.data		= &init_net.core.sysctl_txq_reselection,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_ms_jiffies,
-	},
-	{
-		.procname	= "tstamp_allow_data",
-		.data		= &init_net.core.sysctl_tstamp_allow_data,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_ONE
-	},
-	{
-		.procname	= "bypass_prot_mem",
-		.data		= &init_net.core.sysctl_bypass_prot_mem,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_ONE
-	},
-	/* sysctl_core_net_init() will set the values after this
-	 * to readonly in network namespaces
-	 */
-	{
-		.procname	= "wmem_max",
-		.data		= &sysctl_wmem_max,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= &min_sndbuf,
-	},
-	{
-		.procname	= "rmem_max",
-		.data		= &sysctl_rmem_max,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= &min_rcvbuf,
-	},
-	{
-		.procname	= "wmem_default",
-		.data		= &sysctl_wmem_default,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= &min_sndbuf,
-	},
-	{
-		.procname	= "rmem_default",
-		.data		= &sysctl_rmem_default,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= &min_rcvbuf,
-	},
+
+static void *netns_core_sysctl_wmem_max_data(const struct sysctl_context *ctx)
+{
+	return &sysctl_wmem_max;
+}
+
+static void *netns_core_sysctl_rmem_max_data(const struct sysctl_context *ctx)
+{
+	return &sysctl_rmem_max;
+}
+
+static void *netns_core_sysctl_wmem_default_data(const struct sysctl_context *ctx)
+{
+	return &sysctl_wmem_default;
+}
+
+static void *netns_core_sysctl_rmem_default_data(const struct sysctl_context *ctx)
+{
+	return &sysctl_rmem_default;
+}
+
+static int proc_dointvec_minmax_sndbuf(const struct ctl_table *table, int write,
+				       void *buffer, size_t *lenp, loff_t *ppos)
+{
+	struct ctl_table tmp = *table;
+
+	tmp.extra1 = &min_sndbuf;
+	return proc_dointvec_minmax(&tmp, write, buffer, lenp, ppos);
+}
+
+static int proc_dointvec_minmax_rcvbuf(const struct ctl_table *table, int write,
+				       void *buffer, size_t *lenp, loff_t *ppos)
+{
+	struct ctl_table tmp = *table;
+
+	tmp.extra1 = &min_rcvbuf;
+	return proc_dointvec_minmax(&tmp, write, buffer, lenp, ppos);
+}
+
+static const struct sysctl_field netns_core_table[] = {
+#if IS_ENABLED(CONFIG_RPS)
+	SYSCTL_FIELD_CUSTOM("rps_default_mask", 0644, 0,
+			 netns_core_rps_default_mask_data,
+			 rps_default_mask_sysctl),
+#endif
+	SYSCTL_FIELD_STATIC_INT_MINMAX("somaxconn", 0644,
+				    netns_core_somaxconn_data,
+				    SYSCTL_ZERO, NULL),
+	SYSCTL_FIELD_STATIC_INT_MINMAX("optmem_max", 0644,
+				    netns_core_optmem_max_data,
+				    SYSCTL_ZERO, NULL),
+	SYSCTL_FIELD_STATIC_U8_MINMAX("txrehash", 0644,
+				   netns_core_txrehash_data,
+				   SYSCTL_UINT_ZERO, SYSCTL_UINT_ONE),
+	SYSCTL_FIELD_CUSTOM("txq_reselection_ms", 0644, sizeof(int),
+			 netns_core_txq_reselection_data, proc_dointvec_ms_jiffies),
+	SYSCTL_FIELD_STATIC_U8_MINMAX("tstamp_allow_data", 0644,
+				   netns_core_tstamp_allow_data_value,
+				   SYSCTL_UINT_ZERO, SYSCTL_UINT_ONE),
+	SYSCTL_FIELD_STATIC_U8_MINMAX("bypass_prot_mem", 0644,
+				   netns_core_bypass_prot_mem_data,
+				   SYSCTL_UINT_ZERO, SYSCTL_UINT_ONE),
+	SYSCTL_FIELD_CUSTOM_MODE("wmem_max", 0644,
+			      netns_core_init_net_writable_mode, sizeof(int),
+			      netns_core_sysctl_wmem_max_data,
+			      proc_dointvec_minmax_sndbuf),
+	SYSCTL_FIELD_CUSTOM_MODE("rmem_max", 0644,
+			      netns_core_init_net_writable_mode, sizeof(int),
+			      netns_core_sysctl_rmem_max_data,
+			      proc_dointvec_minmax_rcvbuf),
+	SYSCTL_FIELD_CUSTOM_MODE("wmem_default", 0644,
+			      netns_core_init_net_writable_mode, sizeof(int),
+			      netns_core_sysctl_wmem_default_data,
+			      proc_dointvec_minmax_sndbuf),
+	SYSCTL_FIELD_CUSTOM_MODE("rmem_default", 0644,
+			      netns_core_init_net_writable_mode, sizeof(int),
+			      netns_core_sysctl_rmem_default_data,
+			      proc_dointvec_minmax_rcvbuf),
 };
 
 static int __init fb_tunnels_only_for_init_net_sysctl_setup(char *str)
@@ -789,50 +807,23 @@ __setup("fb_tunnels=", fb_tunnels_only_for_init_net_sysctl_setup);
 
 static __net_init int sysctl_core_net_init(struct net *net)
 {
-	size_t table_size = ARRAY_SIZE(netns_core_table);
-	struct ctl_table *tbl;
-
-	tbl = netns_core_table;
-	if (!net_eq(net, &init_net)) {
-		int i;
-		tbl = kmemdup(tbl, sizeof(netns_core_table), GFP_KERNEL);
-		if (tbl == NULL)
-			goto err_dup;
-
-		for (i = 0; i < table_size; ++i) {
-			if (tbl[i].data == &sysctl_wmem_max)
-				break;
-
-			tbl[i].data += (char *)net - (char *)&init_net;
-		}
-		for (; i < table_size; ++i)
-			tbl[i].mode &= ~0222;
-	}
-
-	net->core.sysctl_hdr = register_net_sysctl_sz(net, "net/core", tbl, table_size);
+	struct sysctl_context ctx = {
+		.ns.net_ns = net,
+	};
+	net->core.sysctl_hdr = register_sysctl_fields(&net->sysctls, "net/core",
+						      netns_core_table, &ctx);
 	if (net->core.sysctl_hdr == NULL)
-		goto err_reg;
+		return -ENOMEM;
 
 	return 0;
-
-err_reg:
-	if (tbl != netns_core_table)
-		kfree(tbl);
-err_dup:
-	return -ENOMEM;
 }
 
 static __net_exit void sysctl_core_net_exit(struct net *net)
 {
-	const struct ctl_table *tbl;
-
-	tbl = net->core.sysctl_hdr->ctl_table_arg;
 	unregister_net_sysctl_table(net->core.sysctl_hdr);
-	BUG_ON(tbl == netns_core_table);
 #if IS_ENABLED(CONFIG_RPS)
 	kfree(net->core.rps_default_mask);
 #endif
-	kfree(tbl);
 }
 
 static __net_initdata struct pernet_operations sysctl_core_ops = {
-- 
2.55.0


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [RFC PATCH v1 20/30] sysctl: net: use sysctl_field in SMC sysctls
       [not found] <cover.1787771905.git.legion@kernel.org>
                   ` (18 preceding siblings ...)
  2026-08-26 19:42 ` [RFC PATCH v1 19/30] sysctl: net: use sysctl_field in net core per-net sysctls Alexey Gladkov
@ 2026-08-26 19:42 ` Alexey Gladkov
  2026-08-26 19:42 ` [RFC PATCH v1 21/30] sysctl: net: use sysctl_field in VRF sysctls Alexey Gladkov
                   ` (9 subsequent siblings)
  29 siblings, 0 replies; 37+ messages in thread
From: Alexey Gladkov @ 2026-08-26 19:42 UTC (permalink / raw)
  To: Linus Torvalds, Eric W . Biederman, Kees Cook, Joel Granados
  Cc: LKML, linux-fsdevel

SMC sysctls are per-net, but non-init network namespaces currently get
a private ctl_table copy only so each data pointer can be adjusted from
init_net to the target net namespace. The copied table is then kept
around solely for unregister time.

Use sysctl_field so the data pointers are derived from the registration
context instead. This keeps the SMC sysctl description static and const
while preserving the existing per-net behaviour and the optional
handshake-control inheritance.

While converting the table, handle limit_smc_hs through a bool-aware
custom handler. The old ctl_table entry described the bool storage as
an int and relied on the untyped sysctl interface to hide that mismatch.

Signed-off-by: Alexey Gladkov <legion@kernel.org>
---
 net/smc/smc_sysctl.c | 208 ++++++++++++++++++-------------------------
 1 file changed, 89 insertions(+), 119 deletions(-)

diff --git a/net/smc/smc_sysctl.c b/net/smc/smc_sysctl.c
index b1efed546243..801bbd8773d3 100644
--- a/net/smc/smc_sysctl.c
+++ b/net/smc/smc_sysctl.c
@@ -97,112 +97,98 @@ static int proc_smc_hs_ctrl(const struct ctl_table *ctl, int write,
 }
 #endif /* CONFIG_SMC_HS_CTRL_BPF */
 
-static struct ctl_table smc_table[] = {
-	{
-		.procname       = "autocorking_size",
-		.data           = &init_net.smc.sysctl_autocorking_size,
-		.maxlen         = sizeof(unsigned int),
-		.mode           = 0644,
-		.proc_handler	= proc_douintvec,
-	},
-	{
-		.procname	= "smcr_buf_type",
-		.data		= &init_net.smc.sysctl_smcr_buf_type,
-		.maxlen		= sizeof(unsigned int),
-		.mode		= 0644,
-		.proc_handler	= proc_douintvec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_TWO,
-	},
-	{
-		.procname	= "smcr_testlink_time",
-		.data		= &init_net.smc.sysctl_smcr_testlink_time,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_jiffies,
-	},
-	{
-		.procname	= "wmem",
-		.data		= &init_net.smc.sysctl_wmem,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= &min_sndbuf,
-		.extra2		= &max_sndbuf,
-	},
-	{
-		.procname	= "rmem",
-		.data		= &init_net.smc.sysctl_rmem,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= &min_rcvbuf,
-		.extra2		= &max_rcvbuf,
-	},
-	{
-		.procname	= "smcr_max_links_per_lgr",
-		.data		= &init_net.smc.sysctl_max_links_per_lgr,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= &links_per_lgr_min,
-		.extra2		= &links_per_lgr_max,
-	},
-	{
-		.procname	= "smcr_max_conns_per_lgr",
-		.data		= &init_net.smc.sysctl_max_conns_per_lgr,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= &conns_per_lgr_min,
-		.extra2		= &conns_per_lgr_max,
-	},
-	{
-		.procname	= "limit_smc_hs",
-		.data		= &init_net.smc.limit_smc_hs,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_ONE,
-	},
-	{
-		.procname	= "smcr_max_send_wr",
-		.data		= &init_net.smc.sysctl_smcr_max_send_wr,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= &smcr_max_wr_min,
-		.extra2		= &smcr_max_wr_max,
-	},
-	{
-		.procname	= "smcr_max_recv_wr",
-		.data		= &init_net.smc.sysctl_smcr_max_recv_wr,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= &smcr_max_wr_min,
-		.extra2		= &smcr_max_wr_max,
-	},
+#define SMC_SYSCTL_DATA(type, name)					\
+static type *smc_##name##_data(const struct sysctl_context *ctx)		\
+{									\
+	return &ctx->ns.net_ns->smc.sysctl_##name;			\
+}
+
+#define SMC_SYSCTL_CUSTOM_DATA(name)					\
+static void *smc_##name##_data(const struct sysctl_context *ctx)		\
+{									\
+	return &ctx->ns.net_ns->smc.sysctl_##name;			\
+}
+
+SMC_SYSCTL_DATA(unsigned int, autocorking_size)
+SMC_SYSCTL_DATA(unsigned int, smcr_buf_type)
+SMC_SYSCTL_CUSTOM_DATA(smcr_testlink_time)
+SMC_SYSCTL_DATA(int, wmem)
+SMC_SYSCTL_DATA(int, rmem)
+SMC_SYSCTL_DATA(int, max_links_per_lgr)
+SMC_SYSCTL_DATA(int, max_conns_per_lgr)
+SMC_SYSCTL_DATA(unsigned int, smcr_max_send_wr)
+SMC_SYSCTL_DATA(unsigned int, smcr_max_recv_wr)
+
+static void *smc_limit_smc_hs_data(const struct sysctl_context *ctx)
+{
+	return &ctx->ns.net_ns->smc.limit_smc_hs;
+}
+
+static int proc_smc_limit_smc_hs(const struct ctl_table *ctl, int write,
+				 void *buffer, size_t *lenp, loff_t *ppos)
+{
+	bool *limit_smc_hs = ctl->data;
+	struct ctl_table tmp = *ctl;
+	int val = READ_ONCE(*limit_smc_hs);
+	int ret;
+
+	tmp.data = &val;
+	tmp.maxlen = sizeof(val);
+	tmp.extra1 = SYSCTL_ZERO;
+	tmp.extra2 = SYSCTL_ONE;
+
+	ret = proc_dointvec_minmax(&tmp, write, buffer, lenp, ppos);
+	if (write && !ret)
+		WRITE_ONCE(*limit_smc_hs, val);
+
+	return ret;
+}
+
 #if IS_ENABLED(CONFIG_SMC_HS_CTRL_BPF)
-	{
-		.procname	= "hs_ctrl",
-		.data		= &init_net.smc.hs_ctrl,
-		.mode		= 0644,
-		.maxlen		= SMC_HS_CTRL_NAME_MAX,
-		.proc_handler	= proc_smc_hs_ctrl,
-	},
-#endif /* CONFIG_SMC_HS_CTRL_BPF */
+static void *smc_hs_ctrl_data(const struct sysctl_context *ctx)
+{
+	return &ctx->ns.net_ns->smc.hs_ctrl;
+}
+#endif
+
+static const struct sysctl_field smc_table[] = {
+	SYSCTL_FIELD_UINT("autocorking_size", 0644, smc_autocorking_size_data),
+	SYSCTL_FIELD_STATIC_UINT_MINMAX("smcr_buf_type", 0644,
+				     smc_smcr_buf_type_data,
+				     SYSCTL_UINT_ZERO, SYSCTL_UINT_TWO),
+	SYSCTL_FIELD_CUSTOM("smcr_testlink_time", 0644, sizeof(int),
+			 smc_smcr_testlink_time_data, proc_dointvec_jiffies),
+	SYSCTL_FIELD_STATIC_INT_MINMAX("wmem", 0644, smc_wmem_data,
+				    &min_sndbuf, &max_sndbuf),
+	SYSCTL_FIELD_STATIC_INT_MINMAX("rmem", 0644, smc_rmem_data,
+				    &min_rcvbuf, &max_rcvbuf),
+	SYSCTL_FIELD_STATIC_INT_MINMAX("smcr_max_links_per_lgr", 0644,
+				    smc_max_links_per_lgr_data,
+				    &links_per_lgr_min, &links_per_lgr_max),
+	SYSCTL_FIELD_STATIC_INT_MINMAX("smcr_max_conns_per_lgr", 0644,
+				    smc_max_conns_per_lgr_data,
+				    &conns_per_lgr_min, &conns_per_lgr_max),
+	SYSCTL_FIELD_CUSTOM("limit_smc_hs", 0644, sizeof(bool),
+			 smc_limit_smc_hs_data, proc_smc_limit_smc_hs),
+	SYSCTL_FIELD_STATIC_UINT_MINMAX("smcr_max_send_wr", 0644,
+				     smc_smcr_max_send_wr_data,
+				     &smcr_max_wr_min, &smcr_max_wr_max),
+	SYSCTL_FIELD_STATIC_UINT_MINMAX("smcr_max_recv_wr", 0644,
+				     smc_smcr_max_recv_wr_data,
+				     &smcr_max_wr_min, &smcr_max_wr_max),
+#if IS_ENABLED(CONFIG_SMC_HS_CTRL_BPF)
+	SYSCTL_FIELD_CUSTOM("hs_ctrl", 0644, SMC_HS_CTRL_NAME_MAX,
+			 smc_hs_ctrl_data, proc_smc_hs_ctrl),
+#endif
 };
 
 int __net_init smc_sysctl_net_init(struct net *net)
 {
-	size_t table_size = ARRAY_SIZE(smc_table);
-	struct ctl_table *table;
+	struct sysctl_context ctx = {
+		.ns.net_ns = net,
+	};
 
-	table = smc_table;
 	if (!net_eq(net, &init_net)) {
-		int i;
 #if IS_ENABLED(CONFIG_SMC_HS_CTRL_BPF)
 		struct smc_hs_ctrl *ctrl;
 
@@ -213,19 +199,12 @@ int __net_init smc_sysctl_net_init(struct net *net)
 			rcu_assign_pointer(net->smc.hs_ctrl, ctrl);
 		rcu_read_unlock();
 #endif /* CONFIG_SMC_HS_CTRL_BPF */
-
-		table = kmemdup(table, sizeof(smc_table), GFP_KERNEL);
-		if (!table)
-			goto err_alloc;
-
-		for (i = 0; i < table_size; i++)
-			table[i].data += (void *)net - (void *)&init_net;
 	}
 
-	net->smc.smc_hdr = register_net_sysctl_sz(net, "net/smc", table,
-						  table_size);
+	net->smc.smc_hdr = register_sysctl_fields(&net->sysctls, "net/smc",
+						  smc_table, &ctx);
 	if (!net->smc.smc_hdr)
-		goto err_reg;
+		goto err_alloc;
 
 	net->smc.sysctl_autocorking_size = SMC_AUTOCORKING_DEFAULT_SIZE;
 	net->smc.sysctl_smcr_buf_type = SMCR_PHYS_CONT_BUFS;
@@ -237,13 +216,10 @@ int __net_init smc_sysctl_net_init(struct net *net)
 	net->smc.sysctl_smcr_max_send_wr = SMCR_MAX_SEND_WR_DEF;
 	net->smc.sysctl_smcr_max_recv_wr = SMCR_MAX_RECV_WR_DEF;
 	/* disable handshake limitation by default */
-	net->smc.limit_smc_hs = 0;
+	net->smc.limit_smc_hs = false;
 
 	return 0;
 
-err_reg:
-	if (!net_eq(net, &init_net))
-		kfree(table);
 err_alloc:
 #if IS_ENABLED(CONFIG_SMC_HS_CTRL_BPF)
 	smc_net_replace_smc_hs_ctrl(net, NULL);
@@ -253,14 +229,8 @@ int __net_init smc_sysctl_net_init(struct net *net)
 
 void __net_exit smc_sysctl_net_exit(struct net *net)
 {
-	const struct ctl_table *table;
-
-	table = net->smc.smc_hdr->ctl_table_arg;
 	unregister_net_sysctl_table(net->smc.smc_hdr);
 #if IS_ENABLED(CONFIG_SMC_HS_CTRL_BPF)
 	smc_net_replace_smc_hs_ctrl(net, NULL);
 #endif /* CONFIG_SMC_HS_CTRL_BPF */
-
-	if (!net_eq(net, &init_net))
-		kfree(table);
 }
-- 
2.55.0


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [RFC PATCH v1 21/30] sysctl: net: use sysctl_field in VRF sysctls
       [not found] <cover.1787771905.git.legion@kernel.org>
                   ` (19 preceding siblings ...)
  2026-08-26 19:42 ` [RFC PATCH v1 20/30] sysctl: net: use sysctl_field in SMC sysctls Alexey Gladkov
@ 2026-08-26 19:42 ` Alexey Gladkov
  2026-08-26 19:42 ` [RFC PATCH v1 22/30] sysctl: net: use sysctl_field in RDS sysctls Alexey Gladkov
                   ` (8 subsequent siblings)
  29 siblings, 0 replies; 37+ messages in thread
From: Alexey Gladkov @ 2026-08-26 19:42 UTC (permalink / raw)
  To: Linus Torvalds, Eric W . Biederman, Kees Cook, Joel Granados
  Cc: LKML, linux-fsdevel

VRF has a single per-net sysctl, but still clones its ctl_table so the
handler can receive the current net namespace through extra1. The copied
table then has to be kept until namespace teardown only to free it after
unregistering the sysctl.

Signed-off-by: Alexey Gladkov <legion@kernel.org>
---
 drivers/net/vrf.c | 43 +++++++++++++++----------------------------
 1 file changed, 15 insertions(+), 28 deletions(-)

diff --git a/drivers/net/vrf.c b/drivers/net/vrf.c
index 46209917ae4d..2155297f2f73 100644
--- a/drivers/net/vrf.c
+++ b/drivers/net/vrf.c
@@ -1828,7 +1828,7 @@ static int vrf_strict_mode_change(struct vrf_map *vmap, bool new_mode)
 static int vrf_shared_table_handler(const struct ctl_table *table, int write,
 				    void *buffer, size_t *lenp, loff_t *ppos)
 {
-	struct net *net = (struct net *)table->extra1;
+	struct net *net = table->data;
 	struct vrf_map *vmap = netns_vrf_map(net);
 	int proc_strict_mode = 0;
 	struct ctl_table tmp = {
@@ -1852,35 +1852,25 @@ static int vrf_shared_table_handler(const struct ctl_table *table, int write,
 	return ret;
 }
 
-static const struct ctl_table vrf_table[] = {
-	{
-		.procname	= "strict_mode",
-		.data		= NULL,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= vrf_shared_table_handler,
-		/* set by the vrf_netns_init */
-		.extra1		= NULL,
-	},
+static void *vrf_strict_mode_data(const struct sysctl_context *ctx)
+{
+	return ctx->ns.net_ns;
+}
+
+static const struct sysctl_field vrf_table[] = {
+	SYSCTL_FIELD_CUSTOM("strict_mode", 0644, 0, vrf_strict_mode_data,
+			 vrf_shared_table_handler),
 };
 
 static int vrf_netns_init_sysctl(struct net *net, struct netns_vrf *nn_vrf)
 {
-	struct ctl_table *table;
-
-	table = kmemdup(vrf_table, sizeof(vrf_table), GFP_KERNEL);
-	if (!table)
-		return -ENOMEM;
-
-	/* init the extra1 parameter with the reference to current netns */
-	table[0].extra1 = net;
-
-	nn_vrf->ctl_hdr = register_net_sysctl_sz(net, "net/vrf", table,
-						 ARRAY_SIZE(vrf_table));
-	if (!nn_vrf->ctl_hdr) {
-		kfree(table);
+	struct sysctl_context ctx = {
+		.ns.net_ns = net,
+	};
+	nn_vrf->ctl_hdr = register_sysctl_fields(&net->sysctls, "net/vrf",
+						 vrf_table, &ctx);
+	if (!nn_vrf->ctl_hdr)
 		return -ENOMEM;
-	}
 
 	return 0;
 }
@@ -1888,11 +1878,8 @@ static int vrf_netns_init_sysctl(struct net *net, struct netns_vrf *nn_vrf)
 static void vrf_netns_exit_sysctl(struct net *net)
 {
 	struct netns_vrf *nn_vrf = net_generic(net, vrf_net_id);
-	const struct ctl_table *table;
 
-	table = nn_vrf->ctl_hdr->ctl_table_arg;
 	unregister_net_sysctl_table(nn_vrf->ctl_hdr);
-	kfree(table);
 }
 #else
 static int vrf_netns_init_sysctl(struct net *net, struct netns_vrf *nn_vrf)
-- 
2.55.0


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [RFC PATCH v1 22/30] sysctl: net: use sysctl_field in RDS sysctls
       [not found] <cover.1787771905.git.legion@kernel.org>
                   ` (20 preceding siblings ...)
  2026-08-26 19:42 ` [RFC PATCH v1 21/30] sysctl: net: use sysctl_field in VRF sysctls Alexey Gladkov
@ 2026-08-26 19:42 ` Alexey Gladkov
  2026-08-26 19:42 ` [RFC PATCH v1 23/30] sysctl: netfilter: use sysctl_field for per-net sysctls Alexey Gladkov
                   ` (7 subsequent siblings)
  29 siblings, 0 replies; 37+ messages in thread
From: Alexey Gladkov @ 2026-08-26 19:42 UTC (permalink / raw)
  To: Linus Torvalds, Eric W . Biederman, Kees Cook, Joel Granados
  Cc: LKML, linux-fsdevel

RDS TCP clones its sysctl table for every non-init network namespace so
that the per-net buffer limits can be patched into the ctl_table
entries. The handlers also rely on the ctl_table data pointer to
recover the owning RDS per-net state.

Use sysctl_field descriptors instead and resolve the per-net data from
the sysctl context at registration time. This keeps the RDS TCP sysctl
layout static, removes the per-net table allocation, and avoids binding
the code to fixed ctl_table indexes.

Pass the per-entry minimum and the RDS per-net state through the custom
field callbacks so both buffer sysctls can share one handler.

Signed-off-by: Alexey Gladkov <legion@kernel.org>
---
 net/rds/tcp.c | 129 ++++++++++++++++++++++++--------------------------
 net/rds/tcp.h |   1 -
 2 files changed, 63 insertions(+), 67 deletions(-)

diff --git a/net/rds/tcp.c b/net/rds/tcp.c
index 5830b31a1f37..2504221ec592 100644
--- a/net/rds/tcp.c
+++ b/net/rds/tcp.c
@@ -34,6 +34,7 @@
 #include <linux/slab.h>
 #include <linux/in.h>
 #include <linux/module.h>
+#include <linux/sysctl.h>
 #include <net/tcp.h>
 #include <net/net_namespace.h>
 #include <net/netns/generic.h>
@@ -61,33 +62,60 @@ static atomic_t rds_tcp_unloading = ATOMIC_INIT(0);
 
 static struct kmem_cache *rds_tcp_conn_slab;
 
-static int rds_tcp_sndbuf_handler(const struct ctl_table *ctl, int write,
-				  void *buffer, size_t *lenp, loff_t *fpos);
-static int rds_tcp_rcvbuf_handler(const struct ctl_table *ctl, int write,
-				  void *buffer, size_t *lenp, loff_t *fpos);
+static int rds_tcp_skbuf_handler(const struct ctl_table *ctl, int write,
+				 void *buffer, size_t *lenp, loff_t *fpos);
 
 static int rds_tcp_min_sndbuf = SOCK_MIN_SNDBUF;
 static int rds_tcp_min_rcvbuf = SOCK_MIN_RCVBUF;
 
-static struct ctl_table rds_tcp_sysctl_table[] = {
-#define	RDS_TCP_SNDBUF	0
-	{
-		.procname       = "rds_tcp_sndbuf",
-		/* data is per-net pointer */
-		.maxlen         = sizeof(int),
-		.mode           = 0644,
-		.proc_handler   = rds_tcp_sndbuf_handler,
-		.extra1		= &rds_tcp_min_sndbuf,
-	},
-#define	RDS_TCP_RCVBUF	1
-	{
-		.procname       = "rds_tcp_rcvbuf",
-		/* data is per-net pointer */
-		.maxlen         = sizeof(int),
-		.mode           = 0644,
-		.proc_handler   = rds_tcp_rcvbuf_handler,
-		.extra1		= &rds_tcp_min_rcvbuf,
-	},
+static void *rds_tcp_sndbuf_data(const struct sysctl_context *ctx)
+{
+	struct rds_tcp_net *rtn = net_generic(ctx->ns.net_ns, rds_tcp_netid);
+
+	return &rtn->sndbuf_size;
+}
+
+static void *rds_tcp_rcvbuf_data(const struct sysctl_context *ctx)
+{
+	struct rds_tcp_net *rtn = net_generic(ctx->ns.net_ns, rds_tcp_netid);
+
+	return &rtn->rcvbuf_size;
+}
+
+static void *rds_tcp_net_data(const struct sysctl_context *ctx)
+{
+	return net_generic(ctx->ns.net_ns, rds_tcp_netid);
+}
+
+static void *rds_tcp_min_sndbuf_data(const struct sysctl_context *ctx)
+{
+	return &rds_tcp_min_sndbuf;
+}
+
+static void *rds_tcp_min_rcvbuf_data(const struct sysctl_context *ctx)
+{
+	return &rds_tcp_min_rcvbuf;
+}
+
+#define RDS_TCP_SKBUF_ENTRY(_name, _data, _min)				\
+	{								\
+		.procname	= (_name),				\
+		.mode		= 0644,					\
+		.type		= SYSCTL_FIELD_CUSTOM,			\
+		.ctl_custom	= {					\
+			.proc_handler	= rds_tcp_skbuf_handler,	\
+			.data		= (_data),			\
+			.extra1		= (_min),			\
+			.extra2		= rds_tcp_net_data,		\
+			.maxlen		= sizeof(int),			\
+		},							\
+	}
+
+static const struct sysctl_field rds_tcp_sysctl_table[] = {
+	RDS_TCP_SKBUF_ENTRY("rds_tcp_sndbuf", rds_tcp_sndbuf_data,
+			    rds_tcp_min_sndbuf_data),
+	RDS_TCP_SKBUF_ENTRY("rds_tcp_rcvbuf", rds_tcp_rcvbuf_data,
+			    rds_tcp_min_rcvbuf_data),
 };
 
 u32 rds_tcp_write_seq(struct rds_tcp_connection *tc)
@@ -542,36 +570,28 @@ void rds_tcp_accept_work(struct rds_tcp_net *rtn)
 static __net_init int rds_tcp_init_net(struct net *net)
 {
 	struct rds_tcp_net *rtn = net_generic(net, rds_tcp_netid);
-	struct ctl_table *tbl;
 	int err = 0;
 
 	memset(rtn, 0, sizeof(*rtn));
 
 	mutex_init(&rtn->rds_tcp_accept_lock);
 
+#if IS_ENABLED(CONFIG_SYSCTL)
+	struct sysctl_context ctx = {
+		.ns.net_ns = net,
+	};
+
 	/* {snd, rcv}buf_size default to 0, which implies we let the
 	 * stack pick the value, and permit auto-tuning of buffer size.
 	 */
-	if (net == &init_net) {
-		tbl = rds_tcp_sysctl_table;
-	} else {
-		tbl = kmemdup(rds_tcp_sysctl_table,
-			      sizeof(rds_tcp_sysctl_table), GFP_KERNEL);
-		if (!tbl) {
-			pr_warn("could not set allocate sysctl table\n");
-			return -ENOMEM;
-		}
-		rtn->ctl_table = tbl;
-	}
-	tbl[RDS_TCP_SNDBUF].data = &rtn->sndbuf_size;
-	tbl[RDS_TCP_RCVBUF].data = &rtn->rcvbuf_size;
-	rtn->rds_tcp_sysctl = register_net_sysctl_sz(net, "net/rds/tcp", tbl,
-						     ARRAY_SIZE(rds_tcp_sysctl_table));
+	rtn->rds_tcp_sysctl = register_sysctl_fields(&net->sysctls, "net/rds/tcp",
+						     rds_tcp_sysctl_table, &ctx);
 	if (!rtn->rds_tcp_sysctl) {
 		pr_warn("could not register sysctl\n");
 		err = -ENOMEM;
 		goto fail;
 	}
+#endif
 
 #if IS_ENABLED(CONFIG_IPV6)
 	rtn->rds_tcp_listen_sock = rds_tcp_listen_init(net, true);
@@ -598,8 +618,6 @@ static __net_init int rds_tcp_init_net(struct net *net)
 	return 0;
 
 fail:
-	if (net != &init_net)
-		kfree(tbl);
 	return err;
 }
 
@@ -640,9 +658,6 @@ static void __net_exit rds_tcp_exit_net(struct net *net)
 
 	if (rtn->rds_tcp_sysctl)
 		unregister_net_sysctl_table(rtn->rds_tcp_sysctl);
-
-	if (net != &init_net)
-		kfree(rtn->ctl_table);
 }
 
 static struct pernet_operations rds_tcp_net_ops = {
@@ -685,16 +700,16 @@ static void rds_tcp_sysctl_reset(struct net *net)
 	spin_unlock_irq(&rds_tcp_conn_lock);
 }
 
-static int rds_tcp_skbuf_handler(struct rds_tcp_net *rtn,
-				 const struct ctl_table *ctl, int write,
+static int rds_tcp_skbuf_handler(const struct ctl_table *ctl, int write,
 				 void *buffer, size_t *lenp, loff_t *fpos)
 {
+	struct rds_tcp_net *rtn = ctl->extra2;
+	int *min = ctl->extra1;
 	int err;
 
 	err = proc_dointvec_minmax(ctl, write, buffer, lenp, fpos);
 	if (err < 0) {
-		pr_warn("Invalid input. Must be >= %d\n",
-			*(int *)(ctl->extra1));
+		pr_warn("Invalid input. Must be >= %d\n", *min);
 		return err;
 	}
 
@@ -707,24 +722,6 @@ static int rds_tcp_skbuf_handler(struct rds_tcp_net *rtn,
 	return 0;
 }
 
-static int rds_tcp_sndbuf_handler(const struct ctl_table *ctl, int write,
-				  void *buffer, size_t *lenp, loff_t *fpos)
-{
-	struct rds_tcp_net *rtn = container_of(ctl->data, struct rds_tcp_net,
-					       sndbuf_size);
-
-	return rds_tcp_skbuf_handler(rtn, ctl, write, buffer, lenp, fpos);
-}
-
-static int rds_tcp_rcvbuf_handler(const struct ctl_table *ctl, int write,
-				  void *buffer, size_t *lenp, loff_t *fpos)
-{
-	struct rds_tcp_net *rtn = container_of(ctl->data, struct rds_tcp_net,
-					       rcvbuf_size);
-
-	return rds_tcp_skbuf_handler(rtn, ctl, write, buffer, lenp, fpos);
-}
-
 static void rds_tcp_exit(void)
 {
 	rds_tcp_set_unloading();
diff --git a/net/rds/tcp.h b/net/rds/tcp.h
index 39c86347188c..266e1d765fd5 100644
--- a/net/rds/tcp.h
+++ b/net/rds/tcp.h
@@ -14,7 +14,6 @@ struct rds_tcp_net {
 	struct socket		*rds_tcp_accepted_sock;
 	struct work_struct	rds_tcp_accept_w;
 	struct ctl_table_header	*rds_tcp_sysctl;
-	const struct ctl_table	*ctl_table;
 	int			sndbuf_size;
 	int			rcvbuf_size;
 };
-- 
2.55.0


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [RFC PATCH v1 23/30] sysctl: netfilter: use sysctl_field for per-net sysctls
       [not found] <cover.1787771905.git.legion@kernel.org>
                   ` (21 preceding siblings ...)
  2026-08-26 19:42 ` [RFC PATCH v1 22/30] sysctl: net: use sysctl_field in RDS sysctls Alexey Gladkov
@ 2026-08-26 19:42 ` Alexey Gladkov
  2026-08-26 19:42 ` [RFC PATCH v1 24/30] sysctl: ipvs: " Alexey Gladkov
                   ` (6 subsequent siblings)
  29 siblings, 0 replies; 37+ messages in thread
From: Alexey Gladkov @ 2026-08-26 19:42 UTC (permalink / raw)
  To: Linus Torvalds, Eric W . Biederman, Kees Cook, Joel Granados
  Cc: LKML, linux-fsdevel

Netfilter still clones several sysctl tables for each network namespace
so the registration code can patch data pointers, modes, and namespace
context into the copied ctl_table entries. This keeps the tables
writable and ties the setup code to the exact table layout.

Use sysctl_field for the per-net conntrack, nf_log, and lwtunnel sysctls
instead. The table descriptions can stay static and const while the
effective ctl_table entries are derived from the registration context.

This removes the per-net table copies and the unregister-time frees, and
avoids the conntrack index patch-up helpers that had to stay in sync
with the ctl_table layout.

Signed-off-by: Alexey Gladkov <legion@kernel.org>
---
 net/netfilter/nf_conntrack_standalone.c | 743 ++++++++++--------------
 net/netfilter/nf_hooks_lwtunnel.c       |  40 +-
 net/netfilter/nf_log.c                  |  90 +--
 3 files changed, 369 insertions(+), 504 deletions(-)

diff --git a/net/netfilter/nf_conntrack_standalone.c b/net/netfilter/nf_conntrack_standalone.c
index be2953c7d702..94a5353fcb93 100644
--- a/net/netfilter/nf_conntrack_standalone.c
+++ b/net/netfilter/nf_conntrack_standalone.c
@@ -581,345 +581,344 @@ nf_conntrack_log_invalid_sysctl(const struct ctl_table *table, int write,
 }
 
 static struct ctl_table_header *nf_ct_netfilter_header;
+static unsigned int nf_ct_max_limit = INT_MAX;
+
+static umode_t nf_ct_global_sysctl_mode(const struct sysctl_context *ctx)
+{
+	return net_eq(ctx->ns.net_ns, &init_net) ? 0644 : 0444;
+}
+
+static unsigned int *nf_ct_max_data(const struct sysctl_context *ctx)
+{
+	return &nf_conntrack_max;
+}
+
+static void *nf_ct_count_data(const struct sysctl_context *ctx)
+{
+	struct nf_conntrack_net *cnet = nf_ct_pernet(ctx->ns.net_ns);
+
+	return &cnet->count;
+}
+
+static void *nf_ct_buckets_data(const struct sysctl_context *ctx)
+{
+	return &nf_conntrack_htable_size_user;
+}
+
+static u8 *nf_ct_checksum_data(const struct sysctl_context *ctx)
+{
+	return &ctx->ns.net_ns->ct.sysctl_checksum;
+}
+
+static void *nf_ct_log_invalid_data(const struct sysctl_context *ctx)
+{
+	return &ctx->ns.net_ns->ct.sysctl_log_invalid;
+}
+
+static unsigned int *nf_ct_expect_max_data(const struct sysctl_context *ctx)
+{
+	return &nf_ct_expect_max;
+}
+
+static u8 *nf_ct_acct_data(const struct sysctl_context *ctx)
+{
+	return &ctx->ns.net_ns->ct.sysctl_acct;
+}
 
-enum nf_ct_sysctl_index {
-	NF_SYSCTL_CT_MAX,
-	NF_SYSCTL_CT_COUNT,
-	NF_SYSCTL_CT_BUCKETS,
-	NF_SYSCTL_CT_CHECKSUM,
-	NF_SYSCTL_CT_LOG_INVALID,
-	NF_SYSCTL_CT_EXPECT_MAX,
-	NF_SYSCTL_CT_ACCT,
 #ifdef CONFIG_NF_CONNTRACK_EVENTS
-	NF_SYSCTL_CT_EVENTS,
+static u8 *nf_ct_events_data(const struct sysctl_context *ctx)
+{
+	return &ctx->ns.net_ns->ct.sysctl_events;
+}
 #endif
+
 #ifdef CONFIG_NF_CONNTRACK_TIMESTAMP
-	NF_SYSCTL_CT_TIMESTAMP,
+static u8 *nf_ct_timestamp_data(const struct sysctl_context *ctx)
+{
+	return &ctx->ns.net_ns->ct.sysctl_tstamp;
+}
 #endif
-	NF_SYSCTL_CT_PROTO_TIMEOUT_GENERIC,
-	NF_SYSCTL_CT_PROTO_TIMEOUT_TCP_SYN_SENT,
-	NF_SYSCTL_CT_PROTO_TIMEOUT_TCP_SYN_RECV,
-	NF_SYSCTL_CT_PROTO_TIMEOUT_TCP_ESTABLISHED,
-	NF_SYSCTL_CT_PROTO_TIMEOUT_TCP_FIN_WAIT,
-	NF_SYSCTL_CT_PROTO_TIMEOUT_TCP_CLOSE_WAIT,
-	NF_SYSCTL_CT_PROTO_TIMEOUT_TCP_LAST_ACK,
-	NF_SYSCTL_CT_PROTO_TIMEOUT_TCP_TIME_WAIT,
-	NF_SYSCTL_CT_PROTO_TIMEOUT_TCP_CLOSE,
-	NF_SYSCTL_CT_PROTO_TIMEOUT_TCP_RETRANS,
-	NF_SYSCTL_CT_PROTO_TIMEOUT_TCP_UNACK,
+
+static void *nf_ct_generic_timeout_data(const struct sysctl_context *ctx)
+{
+	return &nf_generic_pernet(ctx->ns.net_ns)->timeout;
+}
+
+#define NF_CT_TCP_TIMEOUT_DATA(name, state)				\
+static void *nf_ct_tcp_timeout_ ## name ## _data(const struct sysctl_context *ctx)	\
+{									\
+	struct nf_tcp_net *tn = nf_tcp_pernet(ctx->ns.net_ns);		\
+	return &tn->timeouts[TCP_CONNTRACK_ ## state];			\
+}
+
+NF_CT_TCP_TIMEOUT_DATA(syn_sent, SYN_SENT)
+NF_CT_TCP_TIMEOUT_DATA(syn_recv, SYN_RECV)
+NF_CT_TCP_TIMEOUT_DATA(established, ESTABLISHED)
+NF_CT_TCP_TIMEOUT_DATA(fin_wait, FIN_WAIT)
+NF_CT_TCP_TIMEOUT_DATA(close_wait, CLOSE_WAIT)
+NF_CT_TCP_TIMEOUT_DATA(last_ack, LAST_ACK)
+NF_CT_TCP_TIMEOUT_DATA(time_wait, TIME_WAIT)
+NF_CT_TCP_TIMEOUT_DATA(close, CLOSE)
+NF_CT_TCP_TIMEOUT_DATA(retrans, RETRANS)
+NF_CT_TCP_TIMEOUT_DATA(unack, UNACK)
+#undef NF_CT_TCP_TIMEOUT_DATA
+
 #if IS_ENABLED(CONFIG_NF_FLOW_TABLE)
-	NF_SYSCTL_CT_PROTO_TIMEOUT_TCP_OFFLOAD,
+static void *nf_ct_tcp_offload_timeout_data(const struct sysctl_context *ctx)
+{
+	return &nf_tcp_pernet(ctx->ns.net_ns)->offload_timeout;
+}
 #endif
-	NF_SYSCTL_CT_PROTO_TCP_LOOSE,
-	NF_SYSCTL_CT_PROTO_TCP_LIBERAL,
-	NF_SYSCTL_CT_PROTO_TCP_IGNORE_INVALID_RST,
-	NF_SYSCTL_CT_PROTO_TCP_MAX_RETRANS,
-	NF_SYSCTL_CT_PROTO_TIMEOUT_UDP,
-	NF_SYSCTL_CT_PROTO_TIMEOUT_UDP_STREAM,
+
+#define NF_CT_TCP_U8_DATA(name, field)					\
+static u8 *nf_ct_tcp_ ## name ## _data(const struct sysctl_context *ctx)	\
+{									\
+	return &nf_tcp_pernet(ctx->ns.net_ns)->field;			\
+}
+
+NF_CT_TCP_U8_DATA(loose, tcp_loose)
+NF_CT_TCP_U8_DATA(liberal, tcp_be_liberal)
+NF_CT_TCP_U8_DATA(ignore_invalid_rst, tcp_ignore_invalid_rst)
+NF_CT_TCP_U8_DATA(max_retrans, tcp_max_retrans)
+#undef NF_CT_TCP_U8_DATA
+
+static void *nf_ct_udp_timeout_data(const struct sysctl_context *ctx)
+{
+	return &nf_udp_pernet(ctx->ns.net_ns)->timeouts[UDP_CT_UNREPLIED];
+}
+
+static void *nf_ct_udp_stream_timeout_data(const struct sysctl_context *ctx)
+{
+	return &nf_udp_pernet(ctx->ns.net_ns)->timeouts[UDP_CT_REPLIED];
+}
+
 #if IS_ENABLED(CONFIG_NF_FLOW_TABLE)
-	NF_SYSCTL_CT_PROTO_TIMEOUT_UDP_OFFLOAD,
+static void *nf_ct_udp_offload_timeout_data(const struct sysctl_context *ctx)
+{
+	return &nf_udp_pernet(ctx->ns.net_ns)->offload_timeout;
+}
 #endif
-	NF_SYSCTL_CT_PROTO_TIMEOUT_ICMP,
-	NF_SYSCTL_CT_PROTO_TIMEOUT_ICMPV6,
+
+static void *nf_ct_icmp_timeout_data(const struct sysctl_context *ctx)
+{
+	return &nf_icmp_pernet(ctx->ns.net_ns)->timeout;
+}
+
+static void *nf_ct_icmpv6_timeout_data(const struct sysctl_context *ctx)
+{
+	return &nf_icmpv6_pernet(ctx->ns.net_ns)->timeout;
+}
+
 #ifdef CONFIG_NF_CT_PROTO_SCTP
-	NF_SYSCTL_CT_PROTO_TIMEOUT_SCTP_CLOSED,
-	NF_SYSCTL_CT_PROTO_TIMEOUT_SCTP_COOKIE_WAIT,
-	NF_SYSCTL_CT_PROTO_TIMEOUT_SCTP_COOKIE_ECHOED,
-	NF_SYSCTL_CT_PROTO_TIMEOUT_SCTP_ESTABLISHED,
-	NF_SYSCTL_CT_PROTO_TIMEOUT_SCTP_SHUTDOWN_SENT,
-	NF_SYSCTL_CT_PROTO_TIMEOUT_SCTP_SHUTDOWN_RECD,
-	NF_SYSCTL_CT_PROTO_TIMEOUT_SCTP_SHUTDOWN_ACK_SENT,
-	NF_SYSCTL_CT_PROTO_TIMEOUT_SCTP_HEARTBEAT_SENT,
+#define NF_CT_SCTP_TIMEOUT_DATA(name, state)				\
+static void *nf_ct_sctp_timeout_ ## name ## _data(const struct sysctl_context *ctx) \
+{									\
+	struct nf_sctp_net *sn = nf_sctp_pernet(ctx->ns.net_ns);	\
+	return &sn->timeouts[SCTP_CONNTRACK_ ## state];			\
+}
+
+NF_CT_SCTP_TIMEOUT_DATA(closed, CLOSED)
+NF_CT_SCTP_TIMEOUT_DATA(cookie_wait, COOKIE_WAIT)
+NF_CT_SCTP_TIMEOUT_DATA(cookie_echoed, COOKIE_ECHOED)
+NF_CT_SCTP_TIMEOUT_DATA(established, ESTABLISHED)
+NF_CT_SCTP_TIMEOUT_DATA(shutdown_sent, SHUTDOWN_SENT)
+NF_CT_SCTP_TIMEOUT_DATA(shutdown_recd, SHUTDOWN_RECD)
+NF_CT_SCTP_TIMEOUT_DATA(shutdown_ack_sent, SHUTDOWN_ACK_SENT)
+NF_CT_SCTP_TIMEOUT_DATA(heartbeat_sent, HEARTBEAT_SENT)
+#undef NF_CT_SCTP_TIMEOUT_DATA
 #endif
+
 #ifdef CONFIG_NF_CT_PROTO_GRE
-	NF_SYSCTL_CT_PROTO_TIMEOUT_GRE,
-	NF_SYSCTL_CT_PROTO_TIMEOUT_GRE_STREAM,
+static void *nf_ct_gre_timeout_data(const struct sysctl_context *ctx)
+{
+	return &nf_gre_pernet(ctx->ns.net_ns)->timeouts[GRE_CT_UNREPLIED];
+}
+
+static void *nf_ct_gre_stream_timeout_data(const struct sysctl_context *ctx)
+{
+	return &nf_gre_pernet(ctx->ns.net_ns)->timeouts[GRE_CT_REPLIED];
+}
 #endif
 
-	NF_SYSCTL_CT_LAST_SYSCTL,
-};
+#define NF_CT_GLOBAL_UINT_MINMAX(_procname, _data)			\
+	{								\
+		.procname	= (_procname),				\
+		.mode		= 0644,					\
+		.mode_fn	= nf_ct_global_sysctl_mode,		\
+		.type		= SYSCTL_FIELD_STATIC_UINT_MINMAX,		\
+		.ctl_static_uint = {					\
+			.data		= (_data),			\
+			.min_value	= SYSCTL_UINT_ONE,		\
+			.max_value	= &nf_ct_max_limit,		\
+		},							\
+	}
 
-static struct ctl_table nf_ct_sysctl_table[] = {
-	[NF_SYSCTL_CT_MAX] = {
-		.procname	= "nf_conntrack_max",
-		.data		= &nf_conntrack_max,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ONE,
-		.extra2		= SYSCTL_INT_MAX,
-	},
-	[NF_SYSCTL_CT_COUNT] = {
-		.procname	= "nf_conntrack_count",
-		.maxlen		= sizeof(int),
-		.mode		= 0444,
-		.proc_handler	= proc_dointvec,
-	},
-	[NF_SYSCTL_CT_BUCKETS] = {
-		.procname       = "nf_conntrack_buckets",
-		.data           = &nf_conntrack_htable_size_user,
-		.maxlen         = sizeof(unsigned int),
-		.mode           = 0644,
-		.proc_handler   = nf_conntrack_hash_sysctl,
-	},
-	[NF_SYSCTL_CT_CHECKSUM] = {
-		.procname	= "nf_conntrack_checksum",
-		.data		= &init_net.ct.sysctl_checksum,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra1 	= SYSCTL_ZERO,
-		.extra2 	= SYSCTL_ONE,
-	},
-	[NF_SYSCTL_CT_LOG_INVALID] = {
-		.procname	= "nf_conntrack_log_invalid",
-		.data		= &init_net.ct.sysctl_log_invalid,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= nf_conntrack_log_invalid_sysctl,
-	},
-	[NF_SYSCTL_CT_EXPECT_MAX] = {
-		.procname	= "nf_conntrack_expect_max",
-		.data		= &nf_ct_expect_max,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ONE,
-		.extra2		= SYSCTL_INT_MAX,
-	},
-	[NF_SYSCTL_CT_ACCT] = {
-		.procname	= "nf_conntrack_acct",
-		.data		= &init_net.ct.sysctl_acct,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra1 	= SYSCTL_ZERO,
-		.extra2 	= SYSCTL_ONE,
-	},
+static const struct sysctl_field nf_ct_sysctl_table[] = {
+	NF_CT_GLOBAL_UINT_MINMAX("nf_conntrack_max", nf_ct_max_data),
+	SYSCTL_FIELD_CUSTOM("nf_conntrack_count", 0444, sizeof(int),
+			 nf_ct_count_data, proc_dointvec),
+	SYSCTL_FIELD_CUSTOM_MODE("nf_conntrack_buckets", 0644,
+			      nf_ct_global_sysctl_mode,
+			      sizeof(unsigned int),
+			      nf_ct_buckets_data,
+			      nf_conntrack_hash_sysctl),
+	SYSCTL_FIELD_STATIC_U8_MINMAX("nf_conntrack_checksum", 0644,
+				   nf_ct_checksum_data, SYSCTL_UINT_ZERO,
+				   SYSCTL_UINT_ONE),
+	SYSCTL_FIELD_CUSTOM("nf_conntrack_log_invalid", 0644, sizeof(u8),
+			 nf_ct_log_invalid_data,
+			 nf_conntrack_log_invalid_sysctl),
+	NF_CT_GLOBAL_UINT_MINMAX("nf_conntrack_expect_max",
+				 nf_ct_expect_max_data),
+	SYSCTL_FIELD_STATIC_U8_MINMAX("nf_conntrack_acct", 0644,
+				   nf_ct_acct_data, SYSCTL_UINT_ZERO,
+				   SYSCTL_UINT_ONE),
 #ifdef CONFIG_NF_CONNTRACK_EVENTS
-	[NF_SYSCTL_CT_EVENTS] = {
-		.procname	= "nf_conntrack_events",
-		.data		= &init_net.ct.sysctl_events,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra1 	= SYSCTL_ZERO,
-		.extra2		= SYSCTL_TWO,
-	},
+	SYSCTL_FIELD_STATIC_U8_MINMAX("nf_conntrack_events", 0644,
+				   nf_ct_events_data, SYSCTL_UINT_ZERO,
+				   SYSCTL_UINT_TWO),
 #endif
 #ifdef CONFIG_NF_CONNTRACK_TIMESTAMP
-	[NF_SYSCTL_CT_TIMESTAMP] = {
-		.procname	= "nf_conntrack_timestamp",
-		.data		= &init_net.ct.sysctl_tstamp,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra1 	= SYSCTL_ZERO,
-		.extra2 	= SYSCTL_ONE,
-	},
+	SYSCTL_FIELD_STATIC_U8_MINMAX("nf_conntrack_timestamp", 0644,
+				   nf_ct_timestamp_data,
+				   SYSCTL_UINT_ZERO,
+				   SYSCTL_UINT_ONE),
 #endif
-	[NF_SYSCTL_CT_PROTO_TIMEOUT_GENERIC] = {
-		.procname	= "nf_conntrack_generic_timeout",
-		.maxlen		= sizeof(unsigned int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_jiffies,
-	},
-	[NF_SYSCTL_CT_PROTO_TIMEOUT_TCP_SYN_SENT] = {
-		.procname	= "nf_conntrack_tcp_timeout_syn_sent",
-		.maxlen		= sizeof(unsigned int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_jiffies,
-	},
-	[NF_SYSCTL_CT_PROTO_TIMEOUT_TCP_SYN_RECV] = {
-		.procname	= "nf_conntrack_tcp_timeout_syn_recv",
-		.maxlen		= sizeof(unsigned int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_jiffies,
-	},
-	[NF_SYSCTL_CT_PROTO_TIMEOUT_TCP_ESTABLISHED] = {
-		.procname	= "nf_conntrack_tcp_timeout_established",
-		.maxlen		= sizeof(unsigned int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_jiffies,
-	},
-	[NF_SYSCTL_CT_PROTO_TIMEOUT_TCP_FIN_WAIT] = {
-		.procname	= "nf_conntrack_tcp_timeout_fin_wait",
-		.maxlen		= sizeof(unsigned int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_jiffies,
-	},
-	[NF_SYSCTL_CT_PROTO_TIMEOUT_TCP_CLOSE_WAIT] = {
-		.procname	= "nf_conntrack_tcp_timeout_close_wait",
-		.maxlen		= sizeof(unsigned int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_jiffies,
-	},
-	[NF_SYSCTL_CT_PROTO_TIMEOUT_TCP_LAST_ACK] = {
-		.procname	= "nf_conntrack_tcp_timeout_last_ack",
-		.maxlen		= sizeof(unsigned int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_jiffies,
-	},
-	[NF_SYSCTL_CT_PROTO_TIMEOUT_TCP_TIME_WAIT] = {
-		.procname	= "nf_conntrack_tcp_timeout_time_wait",
-		.maxlen		= sizeof(unsigned int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_jiffies,
-	},
-	[NF_SYSCTL_CT_PROTO_TIMEOUT_TCP_CLOSE] = {
-		.procname	= "nf_conntrack_tcp_timeout_close",
-		.maxlen		= sizeof(unsigned int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_jiffies,
-	},
-	[NF_SYSCTL_CT_PROTO_TIMEOUT_TCP_RETRANS] = {
-		.procname	= "nf_conntrack_tcp_timeout_max_retrans",
-		.maxlen		= sizeof(unsigned int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_jiffies,
-	},
-	[NF_SYSCTL_CT_PROTO_TIMEOUT_TCP_UNACK] = {
-		.procname	= "nf_conntrack_tcp_timeout_unacknowledged",
-		.maxlen		= sizeof(unsigned int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_jiffies,
-	},
+	SYSCTL_FIELD_CUSTOM("nf_conntrack_generic_timeout", 0644,
+			 sizeof(unsigned int),
+			 nf_ct_generic_timeout_data,
+			 proc_dointvec_jiffies),
+	SYSCTL_FIELD_CUSTOM("nf_conntrack_tcp_timeout_syn_sent", 0644,
+			 sizeof(unsigned int),
+			 nf_ct_tcp_timeout_syn_sent_data,
+			 proc_dointvec_jiffies),
+	SYSCTL_FIELD_CUSTOM("nf_conntrack_tcp_timeout_syn_recv", 0644,
+			 sizeof(unsigned int),
+			 nf_ct_tcp_timeout_syn_recv_data,
+			 proc_dointvec_jiffies),
+	SYSCTL_FIELD_CUSTOM("nf_conntrack_tcp_timeout_established", 0644,
+			 sizeof(unsigned int),
+			 nf_ct_tcp_timeout_established_data,
+			 proc_dointvec_jiffies),
+	SYSCTL_FIELD_CUSTOM("nf_conntrack_tcp_timeout_fin_wait", 0644,
+			 sizeof(unsigned int),
+			 nf_ct_tcp_timeout_fin_wait_data,
+			 proc_dointvec_jiffies),
+	SYSCTL_FIELD_CUSTOM("nf_conntrack_tcp_timeout_close_wait", 0644,
+			 sizeof(unsigned int),
+			 nf_ct_tcp_timeout_close_wait_data,
+			 proc_dointvec_jiffies),
+	SYSCTL_FIELD_CUSTOM("nf_conntrack_tcp_timeout_last_ack", 0644,
+			 sizeof(unsigned int),
+			 nf_ct_tcp_timeout_last_ack_data,
+			 proc_dointvec_jiffies),
+	SYSCTL_FIELD_CUSTOM("nf_conntrack_tcp_timeout_time_wait", 0644,
+			 sizeof(unsigned int),
+			 nf_ct_tcp_timeout_time_wait_data,
+			 proc_dointvec_jiffies),
+	SYSCTL_FIELD_CUSTOM("nf_conntrack_tcp_timeout_close", 0644,
+			 sizeof(unsigned int),
+			 nf_ct_tcp_timeout_close_data,
+			 proc_dointvec_jiffies),
+	SYSCTL_FIELD_CUSTOM("nf_conntrack_tcp_timeout_max_retrans", 0644,
+			 sizeof(unsigned int),
+			 nf_ct_tcp_timeout_retrans_data,
+			 proc_dointvec_jiffies),
+	SYSCTL_FIELD_CUSTOM("nf_conntrack_tcp_timeout_unacknowledged", 0644,
+			 sizeof(unsigned int),
+			 nf_ct_tcp_timeout_unack_data,
+			 proc_dointvec_jiffies),
 #if IS_ENABLED(CONFIG_NF_FLOW_TABLE)
-	[NF_SYSCTL_CT_PROTO_TIMEOUT_TCP_OFFLOAD] = {
-		.procname	= "nf_flowtable_tcp_timeout",
-		.maxlen		= sizeof(unsigned int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_jiffies,
-	},
+	SYSCTL_FIELD_CUSTOM("nf_flowtable_tcp_timeout", 0644,
+			 sizeof(unsigned int),
+			 nf_ct_tcp_offload_timeout_data,
+			 proc_dointvec_jiffies),
 #endif
-	[NF_SYSCTL_CT_PROTO_TCP_LOOSE] = {
-		.procname	= "nf_conntrack_tcp_loose",
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra1 	= SYSCTL_ZERO,
-		.extra2 	= SYSCTL_ONE,
-	},
-	[NF_SYSCTL_CT_PROTO_TCP_LIBERAL] = {
-		.procname       = "nf_conntrack_tcp_be_liberal",
-		.maxlen		= sizeof(u8),
-		.mode           = 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra1 	= SYSCTL_ZERO,
-		.extra2 	= SYSCTL_ONE,
-	},
-	[NF_SYSCTL_CT_PROTO_TCP_IGNORE_INVALID_RST] = {
-		.procname	= "nf_conntrack_tcp_ignore_invalid_rst",
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_ONE,
-	},
-	[NF_SYSCTL_CT_PROTO_TCP_MAX_RETRANS] = {
-		.procname	= "nf_conntrack_tcp_max_retrans",
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-	},
-	[NF_SYSCTL_CT_PROTO_TIMEOUT_UDP] = {
-		.procname	= "nf_conntrack_udp_timeout",
-		.maxlen		= sizeof(unsigned int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_jiffies,
-	},
-	[NF_SYSCTL_CT_PROTO_TIMEOUT_UDP_STREAM] = {
-		.procname	= "nf_conntrack_udp_timeout_stream",
-		.maxlen		= sizeof(unsigned int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_jiffies,
-	},
+	SYSCTL_FIELD_STATIC_U8_MINMAX("nf_conntrack_tcp_loose", 0644,
+				   nf_ct_tcp_loose_data,
+				   SYSCTL_UINT_ZERO,
+				   SYSCTL_UINT_ONE),
+	SYSCTL_FIELD_STATIC_U8_MINMAX("nf_conntrack_tcp_be_liberal",
+				   0644, nf_ct_tcp_liberal_data,
+				   SYSCTL_UINT_ZERO,
+				   SYSCTL_UINT_ONE),
+	SYSCTL_FIELD_STATIC_U8_MINMAX("nf_conntrack_tcp_ignore_invalid_rst",
+				   0644,
+				   nf_ct_tcp_ignore_invalid_rst_data,
+				   SYSCTL_UINT_ZERO,
+				   SYSCTL_UINT_ONE),
+	SYSCTL_FIELD_U8("nf_conntrack_tcp_max_retrans", 0644,
+			     nf_ct_tcp_max_retrans_data),
+	SYSCTL_FIELD_CUSTOM("nf_conntrack_udp_timeout", 0644,
+			 sizeof(unsigned int),
+			 nf_ct_udp_timeout_data,
+			 proc_dointvec_jiffies),
+	SYSCTL_FIELD_CUSTOM("nf_conntrack_udp_timeout_stream", 0644,
+			 sizeof(unsigned int),
+			 nf_ct_udp_stream_timeout_data,
+			 proc_dointvec_jiffies),
 #if IS_ENABLED(CONFIG_NF_FLOW_TABLE)
-	[NF_SYSCTL_CT_PROTO_TIMEOUT_UDP_OFFLOAD] = {
-		.procname	= "nf_flowtable_udp_timeout",
-		.maxlen		= sizeof(unsigned int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_jiffies,
-	},
+	SYSCTL_FIELD_CUSTOM("nf_flowtable_udp_timeout", 0644,
+			 sizeof(unsigned int),
+			 nf_ct_udp_offload_timeout_data,
+			 proc_dointvec_jiffies),
 #endif
-	[NF_SYSCTL_CT_PROTO_TIMEOUT_ICMP] = {
-		.procname	= "nf_conntrack_icmp_timeout",
-		.maxlen		= sizeof(unsigned int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_jiffies,
-	},
-	[NF_SYSCTL_CT_PROTO_TIMEOUT_ICMPV6] = {
-		.procname	= "nf_conntrack_icmpv6_timeout",
-		.maxlen		= sizeof(unsigned int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_jiffies,
-	},
+	SYSCTL_FIELD_CUSTOM("nf_conntrack_icmp_timeout", 0644,
+			 sizeof(unsigned int),
+			 nf_ct_icmp_timeout_data,
+			 proc_dointvec_jiffies),
+	SYSCTL_FIELD_CUSTOM("nf_conntrack_icmpv6_timeout", 0644,
+			 sizeof(unsigned int),
+			 nf_ct_icmpv6_timeout_data,
+			 proc_dointvec_jiffies),
 #ifdef CONFIG_NF_CT_PROTO_SCTP
-	[NF_SYSCTL_CT_PROTO_TIMEOUT_SCTP_CLOSED] = {
-		.procname	= "nf_conntrack_sctp_timeout_closed",
-		.maxlen		= sizeof(unsigned int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_jiffies,
-	},
-	[NF_SYSCTL_CT_PROTO_TIMEOUT_SCTP_COOKIE_WAIT] = {
-		.procname	= "nf_conntrack_sctp_timeout_cookie_wait",
-		.maxlen		= sizeof(unsigned int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_jiffies,
-	},
-	[NF_SYSCTL_CT_PROTO_TIMEOUT_SCTP_COOKIE_ECHOED] = {
-		.procname	= "nf_conntrack_sctp_timeout_cookie_echoed",
-		.maxlen		= sizeof(unsigned int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_jiffies,
-	},
-	[NF_SYSCTL_CT_PROTO_TIMEOUT_SCTP_ESTABLISHED] = {
-		.procname	= "nf_conntrack_sctp_timeout_established",
-		.maxlen		= sizeof(unsigned int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_jiffies,
-	},
-	[NF_SYSCTL_CT_PROTO_TIMEOUT_SCTP_SHUTDOWN_SENT] = {
-		.procname	= "nf_conntrack_sctp_timeout_shutdown_sent",
-		.maxlen		= sizeof(unsigned int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_jiffies,
-	},
-	[NF_SYSCTL_CT_PROTO_TIMEOUT_SCTP_SHUTDOWN_RECD] = {
-		.procname	= "nf_conntrack_sctp_timeout_shutdown_recd",
-		.maxlen		= sizeof(unsigned int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_jiffies,
-	},
-	[NF_SYSCTL_CT_PROTO_TIMEOUT_SCTP_SHUTDOWN_ACK_SENT] = {
-		.procname	= "nf_conntrack_sctp_timeout_shutdown_ack_sent",
-		.maxlen		= sizeof(unsigned int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_jiffies,
-	},
-	[NF_SYSCTL_CT_PROTO_TIMEOUT_SCTP_HEARTBEAT_SENT] = {
-		.procname	= "nf_conntrack_sctp_timeout_heartbeat_sent",
-		.maxlen		= sizeof(unsigned int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_jiffies,
-	},
+	SYSCTL_FIELD_CUSTOM("nf_conntrack_sctp_timeout_closed", 0644,
+			 sizeof(unsigned int),
+			 nf_ct_sctp_timeout_closed_data,
+			 proc_dointvec_jiffies),
+	SYSCTL_FIELD_CUSTOM("nf_conntrack_sctp_timeout_cookie_wait", 0644,
+			 sizeof(unsigned int),
+			 nf_ct_sctp_timeout_cookie_wait_data,
+			 proc_dointvec_jiffies),
+	SYSCTL_FIELD_CUSTOM("nf_conntrack_sctp_timeout_cookie_echoed",
+			 0644, sizeof(unsigned int),
+			 nf_ct_sctp_timeout_cookie_echoed_data,
+			 proc_dointvec_jiffies),
+	SYSCTL_FIELD_CUSTOM("nf_conntrack_sctp_timeout_established",
+			 0644, sizeof(unsigned int),
+			 nf_ct_sctp_timeout_established_data,
+			 proc_dointvec_jiffies),
+	SYSCTL_FIELD_CUSTOM("nf_conntrack_sctp_timeout_shutdown_sent",
+			 0644, sizeof(unsigned int),
+			 nf_ct_sctp_timeout_shutdown_sent_data,
+			 proc_dointvec_jiffies),
+	SYSCTL_FIELD_CUSTOM("nf_conntrack_sctp_timeout_shutdown_recd",
+			 0644, sizeof(unsigned int),
+			 nf_ct_sctp_timeout_shutdown_recd_data,
+			 proc_dointvec_jiffies),
+	SYSCTL_FIELD_CUSTOM("nf_conntrack_sctp_timeout_shutdown_ack_sent",
+			 0644, sizeof(unsigned int),
+			 nf_ct_sctp_timeout_shutdown_ack_sent_data,
+			 proc_dointvec_jiffies),
+	SYSCTL_FIELD_CUSTOM("nf_conntrack_sctp_timeout_heartbeat_sent",
+			 0644, sizeof(unsigned int),
+			 nf_ct_sctp_timeout_heartbeat_sent_data,
+			 proc_dointvec_jiffies),
 #endif
 #ifdef CONFIG_NF_CT_PROTO_GRE
-	[NF_SYSCTL_CT_PROTO_TIMEOUT_GRE] = {
-		.procname       = "nf_conntrack_gre_timeout",
-		.maxlen         = sizeof(unsigned int),
-		.mode           = 0644,
-		.proc_handler   = proc_dointvec_jiffies,
-	},
-	[NF_SYSCTL_CT_PROTO_TIMEOUT_GRE_STREAM] = {
-		.procname       = "nf_conntrack_gre_timeout_stream",
-		.maxlen         = sizeof(unsigned int),
-		.mode           = 0644,
-		.proc_handler   = proc_dointvec_jiffies,
-	},
+	SYSCTL_FIELD_CUSTOM("nf_conntrack_gre_timeout", 0644,
+			 sizeof(unsigned int),
+			 nf_ct_gre_timeout_data,
+			 proc_dointvec_jiffies),
+	SYSCTL_FIELD_CUSTOM("nf_conntrack_gre_timeout_stream", 0644,
+			 sizeof(unsigned int),
+			 nf_ct_gre_stream_timeout_data,
+			 proc_dointvec_jiffies),
 #endif
 };
 
+#undef NF_CT_GLOBAL_UINT_MINMAX
+
 static struct ctl_table nf_ct_netfilter_table[] = {
 	{
 		.procname	= "nf_conntrack_max",
@@ -932,138 +931,26 @@ static struct ctl_table nf_ct_netfilter_table[] = {
 	},
 };
 
-static void nf_conntrack_standalone_init_tcp_sysctl(struct net *net,
-						    struct ctl_table *table)
-{
-	struct nf_tcp_net *tn = nf_tcp_pernet(net);
-
-#define XASSIGN(XNAME, tn) \
-	table[NF_SYSCTL_CT_PROTO_TIMEOUT_TCP_ ## XNAME].data = \
-			&(tn)->timeouts[TCP_CONNTRACK_ ## XNAME]
-
-	XASSIGN(SYN_SENT, tn);
-	XASSIGN(SYN_RECV, tn);
-	XASSIGN(ESTABLISHED, tn);
-	XASSIGN(FIN_WAIT, tn);
-	XASSIGN(CLOSE_WAIT, tn);
-	XASSIGN(LAST_ACK, tn);
-	XASSIGN(TIME_WAIT, tn);
-	XASSIGN(CLOSE, tn);
-	XASSIGN(RETRANS, tn);
-	XASSIGN(UNACK, tn);
-#undef XASSIGN
-#define XASSIGN(XNAME, rval) \
-	table[NF_SYSCTL_CT_PROTO_TCP_ ## XNAME].data = (rval)
-
-	XASSIGN(LOOSE, &tn->tcp_loose);
-	XASSIGN(LIBERAL, &tn->tcp_be_liberal);
-	XASSIGN(MAX_RETRANS, &tn->tcp_max_retrans);
-	XASSIGN(IGNORE_INVALID_RST, &tn->tcp_ignore_invalid_rst);
-#undef XASSIGN
-
-#if IS_ENABLED(CONFIG_NF_FLOW_TABLE)
-	table[NF_SYSCTL_CT_PROTO_TIMEOUT_TCP_OFFLOAD].data = &tn->offload_timeout;
-#endif
-
-}
-
-static void nf_conntrack_standalone_init_sctp_sysctl(struct net *net,
-						     struct ctl_table *table)
-{
-#ifdef CONFIG_NF_CT_PROTO_SCTP
-	struct nf_sctp_net *sn = nf_sctp_pernet(net);
-
-#define XASSIGN(XNAME, sn) \
-	table[NF_SYSCTL_CT_PROTO_TIMEOUT_SCTP_ ## XNAME].data = \
-			&(sn)->timeouts[SCTP_CONNTRACK_ ## XNAME]
-
-	XASSIGN(CLOSED, sn);
-	XASSIGN(COOKIE_WAIT, sn);
-	XASSIGN(COOKIE_ECHOED, sn);
-	XASSIGN(ESTABLISHED, sn);
-	XASSIGN(SHUTDOWN_SENT, sn);
-	XASSIGN(SHUTDOWN_RECD, sn);
-	XASSIGN(SHUTDOWN_ACK_SENT, sn);
-	XASSIGN(HEARTBEAT_SENT, sn);
-#undef XASSIGN
-#endif
-}
-
-static void nf_conntrack_standalone_init_gre_sysctl(struct net *net,
-						    struct ctl_table *table)
-{
-#ifdef CONFIG_NF_CT_PROTO_GRE
-	struct nf_gre_net *gn = nf_gre_pernet(net);
-
-	table[NF_SYSCTL_CT_PROTO_TIMEOUT_GRE].data = &gn->timeouts[GRE_CT_UNREPLIED];
-	table[NF_SYSCTL_CT_PROTO_TIMEOUT_GRE_STREAM].data = &gn->timeouts[GRE_CT_REPLIED];
-#endif
-}
-
 static int nf_conntrack_standalone_init_sysctl(struct net *net)
 {
+	struct sysctl_context ctx = {
+		.ns.net_ns = net,
+	};
 	struct nf_conntrack_net *cnet = nf_ct_pernet(net);
-	struct nf_udp_net *un = nf_udp_pernet(net);
-	struct ctl_table *table;
-
-	BUILD_BUG_ON(ARRAY_SIZE(nf_ct_sysctl_table) != NF_SYSCTL_CT_LAST_SYSCTL);
-
-	table = kmemdup(nf_ct_sysctl_table, sizeof(nf_ct_sysctl_table),
-			GFP_KERNEL);
-	if (!table)
-		return -ENOMEM;
 
-	table[NF_SYSCTL_CT_COUNT].data = &cnet->count;
-	table[NF_SYSCTL_CT_CHECKSUM].data = &net->ct.sysctl_checksum;
-	table[NF_SYSCTL_CT_LOG_INVALID].data = &net->ct.sysctl_log_invalid;
-	table[NF_SYSCTL_CT_ACCT].data = &net->ct.sysctl_acct;
-#ifdef CONFIG_NF_CONNTRACK_EVENTS
-	table[NF_SYSCTL_CT_EVENTS].data = &net->ct.sysctl_events;
-#endif
-#ifdef CONFIG_NF_CONNTRACK_TIMESTAMP
-	table[NF_SYSCTL_CT_TIMESTAMP].data = &net->ct.sysctl_tstamp;
-#endif
-	table[NF_SYSCTL_CT_PROTO_TIMEOUT_GENERIC].data = &nf_generic_pernet(net)->timeout;
-	table[NF_SYSCTL_CT_PROTO_TIMEOUT_ICMP].data = &nf_icmp_pernet(net)->timeout;
-	table[NF_SYSCTL_CT_PROTO_TIMEOUT_ICMPV6].data = &nf_icmpv6_pernet(net)->timeout;
-	table[NF_SYSCTL_CT_PROTO_TIMEOUT_UDP].data = &un->timeouts[UDP_CT_UNREPLIED];
-	table[NF_SYSCTL_CT_PROTO_TIMEOUT_UDP_STREAM].data = &un->timeouts[UDP_CT_REPLIED];
-#if IS_ENABLED(CONFIG_NF_FLOW_TABLE)
-	table[NF_SYSCTL_CT_PROTO_TIMEOUT_UDP_OFFLOAD].data = &un->offload_timeout;
-#endif
-
-	nf_conntrack_standalone_init_tcp_sysctl(net, table);
-	nf_conntrack_standalone_init_sctp_sysctl(net, table);
-	nf_conntrack_standalone_init_gre_sysctl(net, table);
-
-	/* Don't allow non-init_net ns to alter global sysctls */
-	if (!net_eq(&init_net, net)) {
-		table[NF_SYSCTL_CT_MAX].mode = 0444;
-		table[NF_SYSCTL_CT_EXPECT_MAX].mode = 0444;
-		table[NF_SYSCTL_CT_BUCKETS].mode = 0444;
-	}
-
-	cnet->sysctl_header = register_net_sysctl_sz(net, "net/netfilter",
-						     table,
-						     ARRAY_SIZE(nf_ct_sysctl_table));
+	cnet->sysctl_header = register_sysctl_fields(&net->sysctls, "net/netfilter",
+						     nf_ct_sysctl_table, &ctx);
 	if (!cnet->sysctl_header)
-		goto out_unregister_netfilter;
+		return -ENOMEM;
 
 	return 0;
-
-out_unregister_netfilter:
-	kfree(table);
-	return -ENOMEM;
 }
 
 static void nf_conntrack_standalone_fini_sysctl(struct net *net)
 {
 	struct nf_conntrack_net *cnet = nf_ct_pernet(net);
-	const struct ctl_table *table;
 
-	table = cnet->sysctl_header->ctl_table_arg;
 	unregister_net_sysctl_table(cnet->sysctl_header);
-	kfree(table);
 }
 #else
 static int nf_conntrack_standalone_init_sysctl(struct net *net)
diff --git a/net/netfilter/nf_hooks_lwtunnel.c b/net/netfilter/nf_hooks_lwtunnel.c
index 2d890dd04ff8..7c7730dd107c 100644
--- a/net/netfilter/nf_hooks_lwtunnel.c
+++ b/net/netfilter/nf_hooks_lwtunnel.c
@@ -54,53 +54,31 @@ int nf_hooks_lwtunnel_sysctl_handler(const struct ctl_table *table, int write,
 }
 EXPORT_SYMBOL_GPL(nf_hooks_lwtunnel_sysctl_handler);
 
-static struct ctl_table nf_lwtunnel_sysctl_table[] = {
-	{
-		.procname	= "nf_hooks_lwtunnel",
-		.data		= NULL,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= nf_hooks_lwtunnel_sysctl_handler,
-	},
+static const struct sysctl_field nf_lwtunnel_sysctl_table[] = {
+	SYSCTL_FIELD_CUSTOM("nf_hooks_lwtunnel", 0644, sizeof(int), NULL,
+			 nf_hooks_lwtunnel_sysctl_handler),
 };
 
 static int __net_init nf_lwtunnel_net_init(struct net *net)
 {
+	struct sysctl_context ctx = {
+		.ns.net_ns = net,
+	};
 	struct ctl_table_header *hdr;
-	struct ctl_table *table;
-
-	table = nf_lwtunnel_sysctl_table;
-	if (!net_eq(net, &init_net)) {
-		table = kmemdup(nf_lwtunnel_sysctl_table,
-				sizeof(nf_lwtunnel_sysctl_table),
-				GFP_KERNEL);
-		if (!table)
-			goto err_alloc;
-	}
 
-	hdr = register_net_sysctl_sz(net, "net/netfilter", table,
-				     ARRAY_SIZE(nf_lwtunnel_sysctl_table));
+	hdr = register_sysctl_fields(&net->sysctls, "net/netfilter",
+				     nf_lwtunnel_sysctl_table, &ctx);
 	if (!hdr)
-		goto err_reg;
+		return -ENOMEM;
 
 	net->nf.nf_lwtnl_dir_header = hdr;
 
 	return 0;
-err_reg:
-	if (!net_eq(net, &init_net))
-		kfree(table);
-err_alloc:
-	return -ENOMEM;
 }
 
 static void __net_exit nf_lwtunnel_net_exit(struct net *net)
 {
-	const struct ctl_table *table;
-
-	table = net->nf.nf_lwtnl_dir_header->ctl_table_arg;
 	unregister_net_sysctl_table(net->nf.nf_lwtnl_dir_header);
-	if (!net_eq(net, &init_net))
-		kfree(table);
 }
 
 static struct pernet_operations nf_lwtunnel_net_ops = {
diff --git a/net/netfilter/nf_log.c b/net/netfilter/nf_log.c
index f4d80654dfe6..4dc29d355152 100644
--- a/net/netfilter/nf_log.c
+++ b/net/netfilter/nf_log.c
@@ -420,8 +420,6 @@ static const struct seq_operations nflog_seq_ops = {
 #endif /* PROC_FS */
 
 #ifdef CONFIG_SYSCTL
-static char nf_log_sysctl_fnames[NFPROTO_NUMPROTO-NFPROTO_UNSPEC][3];
-static struct ctl_table nf_log_sysctl_table[NFPROTO_NUMPROTO];
 static struct ctl_table_header *nf_log_sysctl_fhdr;
 
 static struct ctl_table nf_log_sysctl_ftable[] = {
@@ -438,10 +436,14 @@ static int nf_log_proc_dostring(const struct ctl_table *table, int write,
 			 void *buffer, size_t *lenp, loff_t *ppos)
 {
 	const struct nf_logger *logger;
+	unsigned int tindex;
 	char buf[NFLOGGER_NAME_LEN];
 	int r = 0;
-	int tindex = (unsigned long)table->extra1;
-	struct net *net = table->extra2;
+	struct net *net = table->data;
+
+	r = kstrtouint(table->procname, 10, &tindex);
+	if (WARN_ON_ONCE(r || tindex >= NFPROTO_NUMPROTO))
+		return -EINVAL;
 
 	if (write) {
 		struct ctl_table tmp = *table;
@@ -484,68 +486,66 @@ static int nf_log_proc_dostring(const struct ctl_table *table, int write,
 	return r;
 }
 
+static void *nf_log_net_data(const struct sysctl_context *ctx)
+{
+	return ctx->ns.net_ns;
+}
+
+#define NF_LOG_SYSCTL_FIELD(id)						\
+	[id] = SYSCTL_FIELD_CUSTOM(#id, 0644, NFLOGGER_NAME_LEN,	\
+				nf_log_net_data, nf_log_proc_dostring)
+
+static const struct sysctl_field nf_log_sysctl_table[] = {
+	NF_LOG_SYSCTL_FIELD(0),
+	NF_LOG_SYSCTL_FIELD(1),
+	NF_LOG_SYSCTL_FIELD(2),
+	NF_LOG_SYSCTL_FIELD(3),
+	NF_LOG_SYSCTL_FIELD(4),
+	NF_LOG_SYSCTL_FIELD(5),
+	NF_LOG_SYSCTL_FIELD(6),
+	NF_LOG_SYSCTL_FIELD(7),
+	NF_LOG_SYSCTL_FIELD(8),
+	NF_LOG_SYSCTL_FIELD(9),
+	NF_LOG_SYSCTL_FIELD(10),
+};
+#undef NF_LOG_SYSCTL_FIELD
+
 static int netfilter_log_sysctl_init(struct net *net)
 {
-	int i;
-	struct ctl_table *table;
-
-	table = nf_log_sysctl_table;
-	if (!net_eq(net, &init_net)) {
-		table = kmemdup(nf_log_sysctl_table,
-				 sizeof(nf_log_sysctl_table),
-				 GFP_KERNEL);
-		if (!table)
-			goto err_alloc;
-	} else {
-		for (i = NFPROTO_UNSPEC; i < NFPROTO_NUMPROTO; i++) {
-			snprintf(nf_log_sysctl_fnames[i],
-				 3, "%d", i);
-			nf_log_sysctl_table[i].procname	=
-				nf_log_sysctl_fnames[i];
-			nf_log_sysctl_table[i].maxlen = NFLOGGER_NAME_LEN;
-			nf_log_sysctl_table[i].mode = 0644;
-			nf_log_sysctl_table[i].proc_handler =
-				nf_log_proc_dostring;
-			nf_log_sysctl_table[i].extra1 =
-				(void *)(unsigned long) i;
-		}
+	struct sysctl_context ctx = {
+		.ns.net_ns = net,
+	};
+	struct ctl_table_header *hdr;
+
+	BUILD_BUG_ON(ARRAY_SIZE(nf_log_sysctl_table) != NFPROTO_NUMPROTO);
+
+	if (net_eq(net, &init_net)) {
 		nf_log_sysctl_fhdr = register_net_sysctl(net, "net/netfilter",
 							 nf_log_sysctl_ftable);
 		if (!nf_log_sysctl_fhdr)
 			goto err_freg;
 	}
 
-	for (i = NFPROTO_UNSPEC; i < NFPROTO_NUMPROTO; i++)
-		table[i].extra2 = net;
-
-	net->nf.nf_log_dir_header = register_net_sysctl_sz(net,
-							   "net/netfilter/nf_log",
-							   table,
-							   ARRAY_SIZE(nf_log_sysctl_table));
-	if (!net->nf.nf_log_dir_header)
+	hdr = register_sysctl_fields(&net->sysctls, "net/netfilter/nf_log",
+				     nf_log_sysctl_table, &ctx);
+	if (!hdr)
 		goto err_reg;
 
+	net->nf.nf_log_dir_header = hdr;
+
 	return 0;
 
 err_reg:
-	if (!net_eq(net, &init_net))
-		kfree(table);
-	else
+	if (net_eq(net, &init_net))
 		unregister_net_sysctl_table(nf_log_sysctl_fhdr);
 err_freg:
-err_alloc:
 	return -ENOMEM;
 }
 
 static void netfilter_log_sysctl_exit(struct net *net)
 {
-	const struct ctl_table *table;
-
-	table = net->nf.nf_log_dir_header->ctl_table_arg;
 	unregister_net_sysctl_table(net->nf.nf_log_dir_header);
-	if (!net_eq(net, &init_net))
-		kfree(table);
-	else
+	if (net_eq(net, &init_net))
 		unregister_net_sysctl_table(nf_log_sysctl_fhdr);
 }
 #else
-- 
2.55.0


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [RFC PATCH v1 24/30] sysctl: ipvs: use sysctl_field for per-net sysctls
       [not found] <cover.1787771905.git.legion@kernel.org>
                   ` (22 preceding siblings ...)
  2026-08-26 19:42 ` [RFC PATCH v1 23/30] sysctl: netfilter: use sysctl_field for per-net sysctls Alexey Gladkov
@ 2026-08-26 19:42 ` Alexey Gladkov
  2026-08-26 19:42 ` [RFC PATCH v1 25/30] sysctl: bridge: use sysctl_field for br_netfilter sysctls Alexey Gladkov
                   ` (5 subsequent siblings)
  29 siblings, 0 replies; 37+ messages in thread
From: Alexey Gladkov @ 2026-08-26 19:42 UTC (permalink / raw)
  To: Linus Torvalds, Eric W . Biederman, Kees Cook, Joel Granados
  Cc: LKML, linux-fsdevel

IPVS still builds per-net sysctl tables by cloning ctl_table arrays and
then patching the copied entries with namespace-local data pointers,
modes, and handler context. The main table also depends on the entry
order matching the initialization code.

Use sysctl_field for the IPVS per-net sysctls instead. The sysctl
descriptors can stay static and const, while data pointers and
per-namespace modes are derived from the registration context.

This removes the per-net table copies from the IPVS control, LBLC, and
LBLCR sysctls, and drops the table pointers that only existed so the
cloned tables could be freed at namespace teardown.

Signed-off-by: Alexey Gladkov <legion@kernel.org>
---
 include/net/ip_vs.h              |   3 -
 net/netfilter/ipvs/ip_vs_ctl.c   | 533 +++++++++++++------------------
 net/netfilter/ipvs/ip_vs_lblc.c  |  49 ++-
 net/netfilter/ipvs/ip_vs_lblcr.c |  50 ++-
 4 files changed, 263 insertions(+), 372 deletions(-)

diff --git a/include/net/ip_vs.h b/include/net/ip_vs.h
index a02e569813d2..e0833ab99f30 100644
--- a/include/net/ip_vs.h
+++ b/include/net/ip_vs.h
@@ -1214,7 +1214,6 @@ struct netns_ipvs {
 
 	/* sys-ctl struct */
 	struct ctl_table_header	*sysctl_hdr;
-	struct ctl_table	*sysctl_tbl;
 #endif
 
 	/* sysctl variables */
@@ -1259,11 +1258,9 @@ struct netns_ipvs {
 	/* ip_vs_lblc */
 	int			sysctl_lblc_expiration;
 	struct ctl_table_header	*lblc_ctl_header;
-	struct ctl_table	*lblc_ctl_table;
 	/* ip_vs_lblcr */
 	int			sysctl_lblcr_expiration;
 	struct ctl_table_header	*lblcr_ctl_header;
-	struct ctl_table	*lblcr_ctl_table;
 	unsigned long		work_flags;	/* IP_VS_WORK_* flags */
 	/* ip_vs_est */
 	struct delayed_work	est_reload_work;/* Reload kthread tasks */
diff --git a/net/netfilter/ipvs/ip_vs_ctl.c b/net/netfilter/ipvs/ip_vs_ctl.c
index bd9cae44d214..e1db1146f343 100644
--- a/net/netfilter/ipvs/ip_vs_ctl.c
+++ b/net/netfilter/ipvs/ip_vs_ctl.c
@@ -2320,10 +2320,9 @@ static int ip_vs_zero_all(struct netns_ipvs *ipvs)
 #ifdef CONFIG_SYSCTL
 
 static int
-proc_do_defense_mode(const struct ctl_table *table, int write,
-		     void *buffer, size_t *lenp, loff_t *ppos)
+__proc_do_defense_mode(struct netns_ipvs *ipvs, const struct ctl_table *table,
+		       int write, void *buffer, size_t *lenp, loff_t *ppos)
 {
-	struct netns_ipvs *ipvs = table->extra2;
 	int *valp = table->data;
 	int val = *valp;
 	int rc;
@@ -2346,11 +2345,45 @@ proc_do_defense_mode(const struct ctl_table *table, int write,
 	return rc;
 }
 
+static int
+proc_do_drop_entry(const struct ctl_table *table, int write,
+		   void *buffer, size_t *lenp, loff_t *ppos)
+{
+	struct netns_ipvs *ipvs;
+
+	ipvs = container_of(table->data, struct netns_ipvs,
+			    sysctl_drop_entry);
+	return __proc_do_defense_mode(ipvs, table, write, buffer, lenp, ppos);
+}
+
+static int
+proc_do_drop_packet(const struct ctl_table *table, int write,
+		    void *buffer, size_t *lenp, loff_t *ppos)
+{
+	struct netns_ipvs *ipvs;
+
+	ipvs = container_of(table->data, struct netns_ipvs,
+			    sysctl_drop_packet);
+	return __proc_do_defense_mode(ipvs, table, write, buffer, lenp, ppos);
+}
+
+static int
+proc_do_secure_tcp(const struct ctl_table *table, int write,
+		   void *buffer, size_t *lenp, loff_t *ppos)
+{
+	struct netns_ipvs *ipvs;
+
+	ipvs = container_of(table->data, struct netns_ipvs,
+			    sysctl_secure_tcp);
+	return __proc_do_defense_mode(ipvs, table, write, buffer, lenp, ppos);
+}
+
 static int
 proc_do_sync_threshold(const struct ctl_table *table, int write,
 		       void *buffer, size_t *lenp, loff_t *ppos)
 {
-	struct netns_ipvs *ipvs = table->extra2;
+	struct netns_ipvs *ipvs = container_of(table->data, struct netns_ipvs,
+					       sysctl_sync_threshold);
 	int *valp = table->data;
 	int val[2];
 	int rc;
@@ -2401,7 +2434,8 @@ proc_do_sync_ports(const struct ctl_table *table, int write,
 static int ipvs_proc_est_cpumask_set(const struct ctl_table *table,
 				     void *buffer)
 {
-	struct netns_ipvs *ipvs = table->extra2;
+	struct netns_ipvs *ipvs = container_of(table->data, struct netns_ipvs,
+					       sysctl_est_cpulist);
 	cpumask_var_t *valp = table->data;
 	cpumask_var_t newmask;
 	int ret;
@@ -2440,7 +2474,8 @@ static int ipvs_proc_est_cpumask_set(const struct ctl_table *table,
 static int ipvs_proc_est_cpumask_get(const struct ctl_table *table,
 				     void *buffer, size_t size)
 {
-	struct netns_ipvs *ipvs = table->extra2;
+	struct netns_ipvs *ipvs = container_of(table->data, struct netns_ipvs,
+					       sysctl_est_cpulist);
 	cpumask_var_t *valp = table->data;
 	struct cpumask *mask;
 	int ret;
@@ -2491,8 +2526,8 @@ static int ipvs_proc_est_cpulist(const struct ctl_table *table, int write,
 static int ipvs_proc_est_nice(const struct ctl_table *table, int write,
 			      void *buffer, size_t *lenp, loff_t *ppos)
 {
-	struct netns_ipvs *ipvs = table->extra2;
 	int *valp = table->data;
+	struct netns_ipvs *ipvs;
 	int val = *valp;
 	int ret;
 
@@ -2502,6 +2537,7 @@ static int ipvs_proc_est_nice(const struct ctl_table *table, int write,
 		.mode = table->mode,
 	};
 
+	ipvs = container_of(table->data, struct netns_ipvs, sysctl_est_nice);
 	ret = proc_dointvec(&tmp_table, write, buffer, lenp, ppos);
 	if (write && ret >= 0) {
 		if (val < MIN_NICE || val > MAX_NICE) {
@@ -2521,8 +2557,8 @@ static int ipvs_proc_est_nice(const struct ctl_table *table, int write,
 static int ipvs_proc_run_estimation(const struct ctl_table *table, int write,
 				    void *buffer, size_t *lenp, loff_t *ppos)
 {
-	struct netns_ipvs *ipvs = table->extra2;
 	int *valp = table->data;
+	struct netns_ipvs *ipvs;
 	int val = *valp;
 	int ret;
 
@@ -2532,6 +2568,8 @@ static int ipvs_proc_run_estimation(const struct ctl_table *table, int write,
 		.mode = table->mode,
 	};
 
+	ipvs = container_of(table->data, struct netns_ipvs,
+			    sysctl_run_estimation);
 	ret = proc_dointvec(&tmp_table, write, buffer, lenp, ppos);
 	if (write && ret >= 0) {
 		mutex_lock(&ipvs->est_mutex);
@@ -2547,8 +2585,8 @@ static int ipvs_proc_run_estimation(const struct ctl_table *table, int write,
 static int ipvs_proc_conn_lfactor(const struct ctl_table *table, int write,
 				  void *buffer, size_t *lenp, loff_t *ppos)
 {
-	struct netns_ipvs *ipvs = table->extra2;
 	int *valp = table->data;
+	struct netns_ipvs *ipvs;
 	int val = *valp;
 	int ret;
 
@@ -2557,6 +2595,8 @@ static int ipvs_proc_conn_lfactor(const struct ctl_table *table, int write,
 		.maxlen = sizeof(int),
 	};
 
+	ipvs = container_of(table->data, struct netns_ipvs,
+			    sysctl_conn_lfactor);
 	ret = proc_dointvec(&tmp_table, write, buffer, lenp, ppos);
 	if (write && ret >= 0) {
 		if (val < -8 || val > 8) {
@@ -2574,8 +2614,8 @@ static int ipvs_proc_conn_lfactor(const struct ctl_table *table, int write,
 static int ipvs_proc_svc_lfactor(const struct ctl_table *table, int write,
 				 void *buffer, size_t *lenp, loff_t *ppos)
 {
-	struct netns_ipvs *ipvs = table->extra2;
 	int *valp = table->data;
+	struct netns_ipvs *ipvs;
 	int val = *valp;
 	int ret;
 
@@ -2584,6 +2624,8 @@ static int ipvs_proc_svc_lfactor(const struct ctl_table *table, int write,
 		.maxlen = sizeof(int),
 	};
 
+	ipvs = container_of(table->data, struct netns_ipvs,
+			    sysctl_svc_lfactor);
 	ret = proc_dointvec(&tmp_table, write, buffer, lenp, ppos);
 	if (write && ret >= 0) {
 		if (val < -8 || val > 8) {
@@ -2604,216 +2646,181 @@ static int ipvs_proc_svc_lfactor(const struct ctl_table *table, int write,
 	return ret;
 }
 
+#define IPVS_DATA(type, member)						\
+static type *ip_vs_ ## member ## _data(const struct sysctl_context *ctx)	\
+{									\
+	return &net_ipvs(ctx->ns.net_ns)->member;			\
+}
+
+#define IPVS_CUSTOM_DATA(member)					\
+static void *ip_vs_ ## member ## _data(const struct sysctl_context *ctx)	\
+{									\
+	return &net_ipvs(ctx->ns.net_ns)->member;			\
+}
+
+IPVS_DATA(int, sysctl_amemthresh)
+IPVS_DATA(int, sysctl_am_droprate)
+IPVS_CUSTOM_DATA(sysctl_drop_entry)
+IPVS_CUSTOM_DATA(sysctl_drop_packet)
+#ifdef CONFIG_IP_VS_NFCT
+IPVS_DATA(int, sysctl_conntrack)
+#endif
+IPVS_CUSTOM_DATA(sysctl_secure_tcp)
+IPVS_DATA(int, sysctl_snat_reroute)
+IPVS_DATA(int, sysctl_sync_ver)
+IPVS_CUSTOM_DATA(sysctl_sync_ports)
+IPVS_DATA(int, sysctl_sync_persist_mode)
+IPVS_DATA(unsigned long, sysctl_sync_qlen_max)
+IPVS_DATA(int, sysctl_sync_sock_size)
+IPVS_DATA(int, sysctl_cache_bypass)
+IPVS_DATA(int, sysctl_expire_nodest_conn)
+IPVS_DATA(int, sysctl_sloppy_tcp)
+IPVS_DATA(int, sysctl_sloppy_sctp)
+IPVS_DATA(int, sysctl_expire_quiescent_template)
+IPVS_CUSTOM_DATA(sysctl_sync_threshold)
+IPVS_CUSTOM_DATA(sysctl_sync_refresh_period)
+IPVS_DATA(int, sysctl_sync_retries)
+IPVS_DATA(int, sysctl_nat_icmp_send)
+IPVS_DATA(int, sysctl_pmtu_disc)
+IPVS_DATA(int, sysctl_backup_only)
+IPVS_DATA(int, sysctl_conn_reuse_mode)
+IPVS_DATA(int, sysctl_schedule_icmp)
+IPVS_DATA(int, sysctl_ignore_tunneled)
+IPVS_CUSTOM_DATA(sysctl_run_estimation)
+IPVS_CUSTOM_DATA(sysctl_est_cpulist)
+IPVS_CUSTOM_DATA(sysctl_est_nice)
+IPVS_CUSTOM_DATA(sysctl_conn_lfactor)
+IPVS_CUSTOM_DATA(sysctl_svc_lfactor)
+
+#undef IPVS_DATA
+#undef IPVS_CUSTOM_DATA
+
+#ifdef CONFIG_IP_VS_DEBUG
+static int *ip_vs_debug_level_data(const struct sysctl_context *ctx)
+{
+	return &sysctl_ip_vs_debug_level;
+}
+#endif
+
+static umode_t ip_vs_unpriv_sysctl_mode(const struct sysctl_context *ctx)
+{
+	return ctx->ns.net_ns->user_ns != &init_user_ns ? 0444 : 0644;
+}
+
+#ifdef CONFIG_IP_VS_DEBUG
+static umode_t ip_vs_debug_level_mode(const struct sysctl_context *ctx)
+{
+	return net_eq(ctx->ns.net_ns, &init_net) ? 0644 : 0444;
+}
+#endif
+
+#define IPVS_FIELD_INT_MODE(_procname, _data, _mode_fn)		\
+	{								\
+		.procname	= (_procname),				\
+		.mode		= 0644,					\
+		.mode_fn	= (_mode_fn),				\
+		.type		= SYSCTL_FIELD_INT,			\
+		.ctl_int	= { .data = (_data) },			\
+	}
+
+#define IPVS_FIELD_ULONG_MODE(_procname, _data, _mode_fn)	\
+	{								\
+		.procname	= (_procname),				\
+		.mode		= 0644,					\
+		.mode_fn	= (_mode_fn),				\
+		.type		= SYSCTL_FIELD_ULONG,			\
+		.ctl_ulong	= { .data = (_data) },			\
+	}
+
 /*
  *	IPVS sysctl table (under the /proc/sys/net/ipv4/vs/)
- *	Do not change order or insert new entries without
- *	align with netns init in ip_vs_control_net_init()
  */
-
-static struct ctl_table vs_vars[] = {
-	{
-		.procname	= "amemthresh",
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
-	{
-		.procname	= "am_droprate",
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
-	{
-		.procname	= "drop_entry",
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_do_defense_mode,
-	},
-	{
-		.procname	= "drop_packet",
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_do_defense_mode,
-	},
+static const struct sysctl_field vs_vars[] = {
+	SYSCTL_FIELD_INT("amemthresh", 0644, ip_vs_sysctl_amemthresh_data),
+	SYSCTL_FIELD_INT("am_droprate", 0644, ip_vs_sysctl_am_droprate_data),
+	SYSCTL_FIELD_CUSTOM("drop_entry", 0644, sizeof(int),
+			 ip_vs_sysctl_drop_entry_data, proc_do_drop_entry),
+	SYSCTL_FIELD_CUSTOM("drop_packet", 0644, sizeof(int),
+			 ip_vs_sysctl_drop_packet_data, proc_do_drop_packet),
 #ifdef CONFIG_IP_VS_NFCT
-	{
-		.procname	= "conntrack",
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= &proc_dointvec,
-	},
+	SYSCTL_FIELD_INT("conntrack", 0644, ip_vs_sysctl_conntrack_data),
 #endif
-	{
-		.procname	= "secure_tcp",
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_do_defense_mode,
-	},
-	{
-		.procname	= "snat_reroute",
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= &proc_dointvec,
-	},
-	{
-		.procname	= "sync_version",
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_ONE,
-	},
-	{
-		.procname	= "sync_ports",
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_do_sync_ports,
-	},
-	{
-		.procname	= "sync_persist_mode",
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
-	{
-		.procname	= "sync_qlen_max",
-		.maxlen		= sizeof(unsigned long),
-		.mode		= 0644,
-		.proc_handler	= proc_doulongvec_minmax,
-	},
-	{
-		.procname	= "sync_sock_size",
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
-	{
-		.procname	= "cache_bypass",
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
-	{
-		.procname	= "expire_nodest_conn",
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
-	{
-		.procname	= "sloppy_tcp",
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
-	{
-		.procname	= "sloppy_sctp",
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
-	{
-		.procname	= "expire_quiescent_template",
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
-	{
-		.procname	= "sync_threshold",
-		.maxlen		=
-			sizeof(((struct netns_ipvs *)0)->sysctl_sync_threshold),
-		.mode		= 0644,
-		.proc_handler	= proc_do_sync_threshold,
-	},
-	{
-		.procname	= "sync_refresh_period",
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_jiffies,
-	},
-	{
-		.procname	= "sync_retries",
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_THREE,
-	},
-	{
-		.procname	= "nat_icmp_send",
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
-	{
-		.procname	= "pmtu_disc",
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
-	{
-		.procname	= "backup_only",
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
-	{
-		.procname	= "conn_reuse_mode",
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
-	{
-		.procname	= "schedule_icmp",
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
-	{
-		.procname	= "ignore_tunneled",
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
-	{
-		.procname	= "run_estimation",
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= ipvs_proc_run_estimation,
-	},
-	{
-		.procname	= "est_cpulist",
-		.maxlen		= NR_CPUS,	/* unused */
-		.mode		= 0644,
-		.proc_handler	= ipvs_proc_est_cpulist,
-	},
-	{
-		.procname	= "est_nice",
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= ipvs_proc_est_nice,
-	},
-	{
-		.procname	= "conn_lfactor",
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= ipvs_proc_conn_lfactor,
-	},
-	{
-		.procname	= "svc_lfactor",
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= ipvs_proc_svc_lfactor,
-	},
+	SYSCTL_FIELD_CUSTOM("secure_tcp", 0644, sizeof(int),
+			 ip_vs_sysctl_secure_tcp_data, proc_do_secure_tcp),
+	SYSCTL_FIELD_INT("snat_reroute", 0644,
+		      ip_vs_sysctl_snat_reroute_data),
+	SYSCTL_FIELD_STATIC_INT_MINMAX("sync_version", 0644,
+				    ip_vs_sysctl_sync_ver_data,
+				    SYSCTL_ZERO, SYSCTL_ONE),
+	SYSCTL_FIELD_CUSTOM("sync_ports", 0644, sizeof(int),
+			 ip_vs_sysctl_sync_ports_data, proc_do_sync_ports),
+	SYSCTL_FIELD_INT("sync_persist_mode", 0644,
+		      ip_vs_sysctl_sync_persist_mode_data),
+	IPVS_FIELD_ULONG_MODE("sync_qlen_max",
+			      ip_vs_sysctl_sync_qlen_max_data,
+			      ip_vs_unpriv_sysctl_mode),
+	IPVS_FIELD_INT_MODE("sync_sock_size",
+			    ip_vs_sysctl_sync_sock_size_data,
+			    ip_vs_unpriv_sysctl_mode),
+	SYSCTL_FIELD_INT("cache_bypass", 0644, ip_vs_sysctl_cache_bypass_data),
+	SYSCTL_FIELD_INT("expire_nodest_conn", 0644,
+		      ip_vs_sysctl_expire_nodest_conn_data),
+	SYSCTL_FIELD_INT("sloppy_tcp", 0644, ip_vs_sysctl_sloppy_tcp_data),
+	SYSCTL_FIELD_INT("sloppy_sctp", 0644, ip_vs_sysctl_sloppy_sctp_data),
+	SYSCTL_FIELD_INT("expire_quiescent_template", 0644,
+		      ip_vs_sysctl_expire_quiescent_template_data),
+	SYSCTL_FIELD_CUSTOM("sync_threshold", 0644,
+			 sizeof(((struct netns_ipvs *)0)->sysctl_sync_threshold),
+			 ip_vs_sysctl_sync_threshold_data,
+			 proc_do_sync_threshold),
+	SYSCTL_FIELD_CUSTOM("sync_refresh_period", 0644, sizeof(unsigned int),
+			 ip_vs_sysctl_sync_refresh_period_data,
+			 proc_dointvec_jiffies),
+	SYSCTL_FIELD_STATIC_INT_MINMAX("sync_retries", 0644,
+				    ip_vs_sysctl_sync_retries_data,
+				    SYSCTL_ZERO, SYSCTL_THREE),
+	SYSCTL_FIELD_INT("nat_icmp_send", 0644,
+		      ip_vs_sysctl_nat_icmp_send_data),
+	SYSCTL_FIELD_INT("pmtu_disc", 0644, ip_vs_sysctl_pmtu_disc_data),
+	SYSCTL_FIELD_INT("backup_only", 0644, ip_vs_sysctl_backup_only_data),
+	SYSCTL_FIELD_INT("conn_reuse_mode", 0644,
+		      ip_vs_sysctl_conn_reuse_mode_data),
+	SYSCTL_FIELD_INT("schedule_icmp", 0644,
+		      ip_vs_sysctl_schedule_icmp_data),
+	SYSCTL_FIELD_INT("ignore_tunneled", 0644,
+		      ip_vs_sysctl_ignore_tunneled_data),
+	SYSCTL_FIELD_CUSTOM_MODE("run_estimation", 0644,
+			      ip_vs_unpriv_sysctl_mode,
+			      sizeof(int),
+			      ip_vs_sysctl_run_estimation_data,
+			      ipvs_proc_run_estimation),
+	SYSCTL_FIELD_CUSTOM_MODE("est_cpulist", 0644,
+			      ip_vs_unpriv_sysctl_mode,
+			      NR_CPUS,
+			      ip_vs_sysctl_est_cpulist_data,
+			      ipvs_proc_est_cpulist),
+	SYSCTL_FIELD_CUSTOM_MODE("est_nice", 0644,
+			      ip_vs_unpriv_sysctl_mode,
+			      sizeof(int),
+			      ip_vs_sysctl_est_nice_data,
+			      ipvs_proc_est_nice),
+	SYSCTL_FIELD_CUSTOM_MODE("conn_lfactor", 0644,
+			      ip_vs_unpriv_sysctl_mode,
+			      sizeof(int),
+			      ip_vs_sysctl_conn_lfactor_data,
+			      ipvs_proc_conn_lfactor),
+	SYSCTL_FIELD_CUSTOM_MODE("svc_lfactor", 0644,
+			      ip_vs_unpriv_sysctl_mode,
+			      sizeof(int),
+			      ip_vs_sysctl_svc_lfactor_data,
+			      ipvs_proc_svc_lfactor),
 #ifdef CONFIG_IP_VS_DEBUG
-	{
-		.procname	= "debug_level",
-		.data		= &sysctl_ip_vs_debug_level,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
+	IPVS_FIELD_INT_MODE("debug_level", ip_vs_debug_level_data,
+			    ip_vs_debug_level_mode),
 #endif
 };
+#undef IPVS_FIELD_INT_MODE
+#undef IPVS_FIELD_ULONG_MODE
 
 #endif
 
@@ -4946,11 +4953,10 @@ static void ip_vs_genl_unregister(void)
 #ifdef CONFIG_SYSCTL
 static int __net_init ip_vs_control_net_init_sysctl(struct netns_ipvs *ipvs)
 {
-	struct net *net = ipvs->net;
-	struct ctl_table *tbl;
-	int idx, ret;
-	size_t ctl_table_size = ARRAY_SIZE(vs_vars);
-	bool unpriv = net->user_ns != &init_user_ns;
+	struct sysctl_context ctx = {
+		.ns.net_ns = ipvs->net,
+	};
+	int ret;
 
 	atomic_set(&ipvs->dropentry, 0);
 	spin_lock_init(&ipvs->dropentry_lock);
@@ -4961,109 +4967,28 @@ static int __net_init ip_vs_control_net_init_sysctl(struct netns_ipvs *ipvs)
 			  expire_nodest_conn_handler);
 	ipvs->est_stopped = 0;
 
-	if (!net_eq(net, &init_net)) {
-		tbl = kmemdup(vs_vars, sizeof(vs_vars), GFP_KERNEL);
-		if (tbl == NULL)
-			return -ENOMEM;
-	} else
-		tbl = vs_vars;
 	/* Initialize sysctl defaults */
-	for (idx = 0; idx < ARRAY_SIZE(vs_vars); idx++) {
-		if (tbl[idx].proc_handler == proc_do_defense_mode)
-			tbl[idx].extra2 = ipvs;
-	}
-	idx = 0;
 	ipvs->sysctl_amemthresh = 1024;
-	tbl[idx++].data = &ipvs->sysctl_amemthresh;
 	ipvs->sysctl_am_droprate = 10;
-	tbl[idx++].data = &ipvs->sysctl_am_droprate;
-	tbl[idx++].data = &ipvs->sysctl_drop_entry;
-	tbl[idx++].data = &ipvs->sysctl_drop_packet;
-#ifdef CONFIG_IP_VS_NFCT
-	tbl[idx++].data = &ipvs->sysctl_conntrack;
-#endif
-	tbl[idx++].data = &ipvs->sysctl_secure_tcp;
 	ipvs->sysctl_snat_reroute = 1;
-	tbl[idx++].data = &ipvs->sysctl_snat_reroute;
 	ipvs->sysctl_sync_ver = 1;
-	tbl[idx++].data = &ipvs->sysctl_sync_ver;
 	ipvs->sysctl_sync_ports = 1;
-	tbl[idx++].data = &ipvs->sysctl_sync_ports;
-	tbl[idx++].data = &ipvs->sysctl_sync_persist_mode;
-
 	ipvs->sysctl_sync_qlen_max = nr_free_buffer_pages() / 32;
-	if (unpriv)
-		tbl[idx].mode = 0444;
-	tbl[idx++].data = &ipvs->sysctl_sync_qlen_max;
-
 	ipvs->sysctl_sync_sock_size = 0;
-	if (unpriv)
-		tbl[idx].mode = 0444;
-	tbl[idx++].data = &ipvs->sysctl_sync_sock_size;
-
-	tbl[idx++].data = &ipvs->sysctl_cache_bypass;
-	tbl[idx++].data = &ipvs->sysctl_expire_nodest_conn;
-	tbl[idx++].data = &ipvs->sysctl_sloppy_tcp;
-	tbl[idx++].data = &ipvs->sysctl_sloppy_sctp;
-	tbl[idx++].data = &ipvs->sysctl_expire_quiescent_template;
 	ipvs->sysctl_sync_threshold[0] = DEFAULT_SYNC_THRESHOLD;
 	ipvs->sysctl_sync_threshold[1] = DEFAULT_SYNC_PERIOD;
-	tbl[idx].data = &ipvs->sysctl_sync_threshold;
-	tbl[idx].extra2 = ipvs;
-	tbl[idx++].maxlen = sizeof(ipvs->sysctl_sync_threshold);
 	ipvs->sysctl_sync_refresh_period = DEFAULT_SYNC_REFRESH_PERIOD;
-	tbl[idx++].data = &ipvs->sysctl_sync_refresh_period;
 	ipvs->sysctl_sync_retries = clamp_t(int, DEFAULT_SYNC_RETRIES, 0, 3);
-	tbl[idx++].data = &ipvs->sysctl_sync_retries;
-	tbl[idx++].data = &ipvs->sysctl_nat_icmp_send;
 	ipvs->sysctl_pmtu_disc = 1;
-	tbl[idx++].data = &ipvs->sysctl_pmtu_disc;
-	tbl[idx++].data = &ipvs->sysctl_backup_only;
 	ipvs->sysctl_conn_reuse_mode = 1;
-	tbl[idx++].data = &ipvs->sysctl_conn_reuse_mode;
-	tbl[idx++].data = &ipvs->sysctl_schedule_icmp;
-	tbl[idx++].data = &ipvs->sysctl_ignore_tunneled;
-
 	ipvs->sysctl_run_estimation = 1;
-	if (unpriv)
-		tbl[idx].mode = 0444;
-	tbl[idx].extra2 = ipvs;
-	tbl[idx++].data = &ipvs->sysctl_run_estimation;
-
 	ipvs->est_cpulist_valid = 0;
-	if (unpriv)
-		tbl[idx].mode = 0444;
-	tbl[idx].extra2 = ipvs;
-	tbl[idx++].data = &ipvs->sysctl_est_cpulist;
-
 	ipvs->sysctl_est_nice = IPVS_EST_NICE;
-	if (unpriv)
-		tbl[idx].mode = 0444;
-	tbl[idx].extra2 = ipvs;
-	tbl[idx++].data = &ipvs->sysctl_est_nice;
 
-	if (unpriv)
-		tbl[idx].mode = 0444;
-	tbl[idx].extra2 = ipvs;
-	tbl[idx++].data = &ipvs->sysctl_conn_lfactor;
-
-	if (unpriv)
-		tbl[idx].mode = 0444;
-	tbl[idx].extra2 = ipvs;
-	tbl[idx++].data = &ipvs->sysctl_svc_lfactor;
-
-#ifdef CONFIG_IP_VS_DEBUG
-	/* Global sysctls must be ro in non-init netns */
-	if (!net_eq(net, &init_net))
-		tbl[idx++].mode = 0444;
-#endif
-
-	ret = -ENOMEM;
-	ipvs->sysctl_hdr = register_net_sysctl_sz(net, "net/ipv4/vs", tbl,
-						  ctl_table_size);
+	ipvs->sysctl_hdr = register_sysctl_fields(&ipvs->net->sysctls, "net/ipv4/vs",
+						  vs_vars, &ctx);
 	if (!ipvs->sysctl_hdr)
-		goto err;
-	ipvs->sysctl_tbl = tbl;
+		return -ENOMEM;
 
 	ret = ip_vs_start_estimator(ipvs, &ipvs->tot_stats->s);
 	if (ret < 0)
@@ -5077,15 +5002,11 @@ static int __net_init ip_vs_control_net_init_sysctl(struct netns_ipvs *ipvs)
 
 err:
 	unregister_net_sysctl_table(ipvs->sysctl_hdr);
-	if (!net_eq(net, &init_net))
-		kfree(tbl);
 	return ret;
 }
 
 static void __net_exit ip_vs_control_net_cleanup_sysctl(struct netns_ipvs *ipvs)
 {
-	struct net *net = ipvs->net;
-
 	cancel_delayed_work_sync(&ipvs->expire_nodest_conn_work);
 	cancel_delayed_work_sync(&ipvs->defense_work);
 	cancel_work_sync(&ipvs->defense_work.work);
@@ -5102,8 +5023,6 @@ static void __net_exit ip_vs_control_net_cleanup_sysctl(struct netns_ipvs *ipvs)
 	if (ipvs->est_cpulist_valid)
 		free_cpumask_var(ipvs->sysctl_est_cpulist);
 
-	if (!net_eq(net, &init_net))
-		kfree(ipvs->sysctl_tbl);
 }
 
 #else
diff --git a/net/netfilter/ipvs/ip_vs_lblc.c b/net/netfilter/ipvs/ip_vs_lblc.c
index 15ccb2b2fa1f..55a50f6511fe 100644
--- a/net/netfilter/ipvs/ip_vs_lblc.c
+++ b/net/netfilter/ipvs/ip_vs_lblc.c
@@ -114,14 +114,14 @@ struct ip_vs_lblc_table {
  *      IPVS LBLC sysctl table
  */
 #ifdef CONFIG_SYSCTL
-static struct ctl_table vs_vars_table[] = {
-	{
-		.procname	= "lblc_expiration",
-		.data		= NULL,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_jiffies,
-	},
+static void *ip_vs_lblc_expiration_data(const struct sysctl_context *ctx)
+{
+	return &net_ipvs(ctx->ns.net_ns)->sysctl_lblc_expiration;
+}
+
+static const struct sysctl_field vs_vars_table[] = {
+	SYSCTL_FIELD_CUSTOM("lblc_expiration", 0644, sizeof(int),
+			 ip_vs_lblc_expiration_data, proc_dointvec_jiffies),
 };
 #endif
 
@@ -548,36 +548,26 @@ static struct ip_vs_scheduler ip_vs_lblc_scheduler = {
 #ifdef CONFIG_SYSCTL
 static int __net_init __ip_vs_lblc_init(struct net *net)
 {
+	struct sysctl_context ctx = {
+		.ns.net_ns = net,
+	};
 	struct netns_ipvs *ipvs = net_ipvs(net);
-	size_t vars_table_size = ARRAY_SIZE(vs_vars_table);
 
 	if (!ipvs)
 		return -ENOENT;
 
-	if (!net_eq(net, &init_net)) {
-		ipvs->lblc_ctl_table = kmemdup(vs_vars_table,
-						sizeof(vs_vars_table),
-						GFP_KERNEL);
-		if (ipvs->lblc_ctl_table == NULL)
-			return -ENOMEM;
+	ipvs->sysctl_lblc_expiration = DEFAULT_EXPIRATION;
 
+	if (!net_eq(net, &init_net)) {
 		/* Don't export sysctls to unprivileged users */
 		if (net->user_ns != &init_user_ns)
-			vars_table_size = 0;
+			return 0;
+	}
 
-	} else
-		ipvs->lblc_ctl_table = vs_vars_table;
-	ipvs->sysctl_lblc_expiration = DEFAULT_EXPIRATION;
-	ipvs->lblc_ctl_table[0].data = &ipvs->sysctl_lblc_expiration;
-
-	ipvs->lblc_ctl_header = register_net_sysctl_sz(net, "net/ipv4/vs",
-						       ipvs->lblc_ctl_table,
-						       vars_table_size);
-	if (!ipvs->lblc_ctl_header) {
-		if (!net_eq(net, &init_net))
-			kfree(ipvs->lblc_ctl_table);
+	ipvs->lblc_ctl_header = register_sysctl_fields(&net->sysctls, "net/ipv4/vs",
+						       vs_vars_table, &ctx);
+	if (!ipvs->lblc_ctl_header)
 		return -ENOMEM;
-	}
 
 	return 0;
 }
@@ -587,9 +577,6 @@ static void __net_exit __ip_vs_lblc_exit(struct net *net)
 	struct netns_ipvs *ipvs = net_ipvs(net);
 
 	unregister_net_sysctl_table(ipvs->lblc_ctl_header);
-
-	if (!net_eq(net, &init_net))
-		kfree(ipvs->lblc_ctl_table);
 }
 
 #else
diff --git a/net/netfilter/ipvs/ip_vs_lblcr.c b/net/netfilter/ipvs/ip_vs_lblcr.c
index c90ea897c3f7..285e720244a0 100644
--- a/net/netfilter/ipvs/ip_vs_lblcr.c
+++ b/net/netfilter/ipvs/ip_vs_lblcr.c
@@ -285,14 +285,14 @@ struct ip_vs_lblcr_table {
  *      IPVS LBLCR sysctl table
  */
 
-static struct ctl_table vs_vars_table[] = {
-	{
-		.procname	= "lblcr_expiration",
-		.data		= NULL,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_jiffies,
-	},
+static void *ip_vs_lblcr_expiration_data(const struct sysctl_context *ctx)
+{
+	return &net_ipvs(ctx->ns.net_ns)->sysctl_lblcr_expiration;
+}
+
+static const struct sysctl_field vs_vars_table[] = {
+	SYSCTL_FIELD_CUSTOM("lblcr_expiration", 0644, sizeof(int),
+			 ip_vs_lblcr_expiration_data, proc_dointvec_jiffies),
 };
 #endif
 
@@ -734,36 +734,27 @@ static struct ip_vs_scheduler ip_vs_lblcr_scheduler =
 #ifdef CONFIG_SYSCTL
 static int __net_init __ip_vs_lblcr_init(struct net *net)
 {
+	struct sysctl_context ctx = {
+		.ns.net_ns = net,
+	};
 	struct netns_ipvs *ipvs = net_ipvs(net);
-	size_t vars_table_size = ARRAY_SIZE(vs_vars_table);
 
 	if (!ipvs)
 		return -ENOENT;
 
-	if (!net_eq(net, &init_net)) {
-		ipvs->lblcr_ctl_table = kmemdup(vs_vars_table,
-						sizeof(vs_vars_table),
-						GFP_KERNEL);
-		if (ipvs->lblcr_ctl_table == NULL)
-			return -ENOMEM;
+	ipvs->sysctl_lblcr_expiration = DEFAULT_EXPIRATION;
 
+	if (!net_eq(net, &init_net)) {
 		/* Don't export sysctls to unprivileged users */
 		if (net->user_ns != &init_user_ns)
-			vars_table_size = 0;
-	} else
-		ipvs->lblcr_ctl_table = vs_vars_table;
-	ipvs->sysctl_lblcr_expiration = DEFAULT_EXPIRATION;
-	ipvs->lblcr_ctl_table[0].data = &ipvs->sysctl_lblcr_expiration;
-
-	ipvs->lblcr_ctl_header = register_net_sysctl_sz(net, "net/ipv4/vs",
-							ipvs->lblcr_ctl_table,
-							vars_table_size);
-	if (!ipvs->lblcr_ctl_header) {
-		if (!net_eq(net, &init_net))
-			kfree(ipvs->lblcr_ctl_table);
-		return -ENOMEM;
+			return 0;
 	}
 
+	ipvs->lblcr_ctl_header = register_sysctl_fields(&net->sysctls, "net/ipv4/vs",
+							vs_vars_table, &ctx);
+	if (!ipvs->lblcr_ctl_header)
+		return -ENOMEM;
+
 	return 0;
 }
 
@@ -772,9 +763,6 @@ static void __net_exit __ip_vs_lblcr_exit(struct net *net)
 	struct netns_ipvs *ipvs = net_ipvs(net);
 
 	unregister_net_sysctl_table(ipvs->lblcr_ctl_header);
-
-	if (!net_eq(net, &init_net))
-		kfree(ipvs->lblcr_ctl_table);
 }
 
 #else
-- 
2.55.0


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [RFC PATCH v1 25/30] sysctl: bridge: use sysctl_field for br_netfilter sysctls
       [not found] <cover.1787771905.git.legion@kernel.org>
                   ` (23 preceding siblings ...)
  2026-08-26 19:42 ` [RFC PATCH v1 24/30] sysctl: ipvs: " Alexey Gladkov
@ 2026-08-26 19:42 ` Alexey Gladkov
  2026-08-26 19:42 ` [RFC PATCH v1 26/30] sysctl: net: use sysctl_field for MPLS sysctls Alexey Gladkov
                   ` (4 subsequent siblings)
  29 siblings, 0 replies; 37+ messages in thread
From: Alexey Gladkov @ 2026-08-26 19:42 UTC (permalink / raw)
  To: Linus Torvalds, Eric W . Biederman, Kees Cook, Joel Granados
  Cc: LKML, linux-fsdevel

br_netfilter clones its sysctl table for each non-init network namespace
so the registration path can patch in namespace-local data pointers. The
copied table then has to be kept alive until namespace teardown only so
it can be freed after unregistering the sysctls.

Use sysctl_field to derive the br_netfilter per-net data from the
registration context instead. This keeps the sysctl descriptors static
and const, and removes the per-net table copy and unregister-time free.

Signed-off-by: Alexey Gladkov <legion@kernel.org>
---
 net/bridge/br_netfilter_hooks.c | 99 ++++++++++++---------------------
 1 file changed, 37 insertions(+), 62 deletions(-)

diff --git a/net/bridge/br_netfilter_hooks.c b/net/bridge/br_netfilter_hooks.c
index 0a394e5f4391..18eccde39885 100644
--- a/net/bridge/br_netfilter_hooks.c
+++ b/net/bridge/br_netfilter_hooks.c
@@ -1181,43 +1181,37 @@ int brnf_sysctl_call_tables(const struct ctl_table *ctl, int write,
 	return ret;
 }
 
-static struct ctl_table brnf_table[] = {
-	{
-		.procname	= "bridge-nf-call-arptables",
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= brnf_sysctl_call_tables,
-	},
-	{
-		.procname	= "bridge-nf-call-iptables",
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= brnf_sysctl_call_tables,
-	},
-	{
-		.procname	= "bridge-nf-call-ip6tables",
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= brnf_sysctl_call_tables,
-	},
-	{
-		.procname	= "bridge-nf-filter-vlan-tagged",
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= brnf_sysctl_call_tables,
-	},
-	{
-		.procname	= "bridge-nf-filter-pppoe-tagged",
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= brnf_sysctl_call_tables,
-	},
-	{
-		.procname	= "bridge-nf-pass-vlan-input-dev",
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= brnf_sysctl_call_tables,
-	},
+#define BRNF_DATA(field)						\
+static void *brnf_ ## field ## _data(const struct sysctl_context *ctx)	\
+{									\
+	struct brnf_net *brnet = net_generic(ctx->ns.net_ns, brnf_net_id); \
+									\
+	return &brnet->field;						\
+}
+
+BRNF_DATA(call_arptables)
+BRNF_DATA(call_iptables)
+BRNF_DATA(call_ip6tables)
+BRNF_DATA(filter_vlan_tagged)
+BRNF_DATA(filter_pppoe_tagged)
+BRNF_DATA(pass_vlan_indev)
+#undef BRNF_DATA
+
+static const struct sysctl_field brnf_table[] = {
+	SYSCTL_FIELD_CUSTOM("bridge-nf-call-arptables", 0644, sizeof(int),
+			 brnf_call_arptables_data, brnf_sysctl_call_tables),
+	SYSCTL_FIELD_CUSTOM("bridge-nf-call-iptables", 0644, sizeof(int),
+			 brnf_call_iptables_data, brnf_sysctl_call_tables),
+	SYSCTL_FIELD_CUSTOM("bridge-nf-call-ip6tables", 0644, sizeof(int),
+			 brnf_call_ip6tables_data, brnf_sysctl_call_tables),
+	SYSCTL_FIELD_CUSTOM("bridge-nf-filter-vlan-tagged", 0644, sizeof(int),
+			 brnf_filter_vlan_tagged_data,
+			 brnf_sysctl_call_tables),
+	SYSCTL_FIELD_CUSTOM("bridge-nf-filter-pppoe-tagged", 0644, sizeof(int),
+			 brnf_filter_pppoe_tagged_data,
+			 brnf_sysctl_call_tables),
+	SYSCTL_FIELD_CUSTOM("bridge-nf-pass-vlan-input-dev", 0644, sizeof(int),
+			 brnf_pass_vlan_indev_data, brnf_sysctl_call_tables),
 };
 
 static inline void br_netfilter_sysctl_default(struct brnf_net *brnf)
@@ -1232,33 +1226,18 @@ static inline void br_netfilter_sysctl_default(struct brnf_net *brnf)
 
 static int br_netfilter_sysctl_init_net(struct net *net)
 {
-	struct ctl_table *table = brnf_table;
+	struct sysctl_context ctx = {
+		.ns.net_ns = net,
+	};
 	struct brnf_net *brnet;
 
-	if (!net_eq(net, &init_net)) {
-		table = kmemdup(table, sizeof(brnf_table), GFP_KERNEL);
-		if (!table)
-			return -ENOMEM;
-	}
-
 	brnet = net_generic(net, brnf_net_id);
-	table[0].data = &brnet->call_arptables;
-	table[1].data = &brnet->call_iptables;
-	table[2].data = &brnet->call_ip6tables;
-	table[3].data = &brnet->filter_vlan_tagged;
-	table[4].data = &brnet->filter_pppoe_tagged;
-	table[5].data = &brnet->pass_vlan_indev;
-
 	br_netfilter_sysctl_default(brnet);
 
-	brnet->ctl_hdr = register_net_sysctl_sz(net, "net/bridge", table,
-						ARRAY_SIZE(brnf_table));
-	if (!brnet->ctl_hdr) {
-		if (!net_eq(net, &init_net))
-			kfree(table);
-
+	brnet->ctl_hdr = register_sysctl_fields(&net->sysctls, "net/bridge",
+						brnf_table, &ctx);
+	if (!brnet->ctl_hdr)
 		return -ENOMEM;
-	}
 
 	return 0;
 }
@@ -1266,11 +1245,7 @@ static int br_netfilter_sysctl_init_net(struct net *net)
 static void br_netfilter_sysctl_exit_net(struct net *net,
 					 struct brnf_net *brnet)
 {
-	const struct ctl_table *table = brnet->ctl_hdr->ctl_table_arg;
-
 	unregister_net_sysctl_table(brnet->ctl_hdr);
-	if (!net_eq(net, &init_net))
-		kfree(table);
 }
 
 static int __net_init brnf_init_net(struct net *net)
-- 
2.55.0


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [RFC PATCH v1 26/30] sysctl: net: use sysctl_field for MPLS sysctls
       [not found] <cover.1787771905.git.legion@kernel.org>
                   ` (24 preceding siblings ...)
  2026-08-26 19:42 ` [RFC PATCH v1 25/30] sysctl: bridge: use sysctl_field for br_netfilter sysctls Alexey Gladkov
@ 2026-08-26 19:42 ` Alexey Gladkov
  2026-08-26 19:42 ` [RFC PATCH v1 27/30] sysctl: net: use sysctl_field in IPv4 devconf sysctls Alexey Gladkov
                   ` (3 subsequent siblings)
  29 siblings, 0 replies; 37+ messages in thread
From: Alexey Gladkov @ 2026-08-26 19:42 UTC (permalink / raw)
  To: Linus Torvalds, Eric W . Biederman, Kees Cook, Joel Granados
  Cc: LKML, linux-fsdevel

MPLS clones both its per-net and per-device sysctl tables so
registration can turn offset-based entries into pointers to the
corresponding network namespace or MPLS device. These mutable copies
then have to remain allocated until the sysctls are unregistered.

Use sysctl_field accessors to resolve per-net data from struct sysctl_context,
and embed that context in an MPLS device-specific wrapper for the
per-device table. This allows both descriptor arrays to remain static
and const while preserving access to the network namespace and mpls_dev
state.

Remove the per-instance table clones, offset arithmetic, and matching
unregister-time frees.

Signed-off-by: Alexey Gladkov <legion@kernel.org>
---
 include/linux/sysctl.h |   4 ++
 net/mpls/af_mpls.c     | 154 ++++++++++++++++-------------------------
 2 files changed, 62 insertions(+), 96 deletions(-)

diff --git a/include/linux/sysctl.h b/include/linux/sysctl.h
index bbdb0fcfbcd4..6cf2ef4f13e8 100644
--- a/include/linux/sysctl.h
+++ b/include/linux/sysctl.h
@@ -486,6 +486,10 @@ struct ctl_table_root {
 	__register_sysctl_fields(set, path, fields, ARRAY_SIZE(fields),	\
 				 (ctx), sizeof(*(ctx)))
 
+#define register_sysctl_fields_ctx(set, path, fields, ctx)		\
+	__register_sysctl_fields(set, path, fields, ARRAY_SIZE(fields),	\
+				 (&(ctx)->context), sizeof(*(ctx)))
+
 #ifdef CONFIG_SYSCTL
 
 void proc_sys_poll_notify(struct ctl_table_poll *poll);
diff --git a/net/mpls/af_mpls.c b/net/mpls/af_mpls.c
index 26340a7306b5..5b58fea375e7 100644
--- a/net/mpls/af_mpls.c
+++ b/net/mpls/af_mpls.c
@@ -1387,74 +1387,62 @@ static int mpls_netconf_dump_devconf(struct sk_buff *skb,
 	return err;
 }
 
-#define MPLS_PERDEV_SYSCTL_OFFSET(field)	\
-	(&((struct mpls_dev *)0)->field)
+struct mpls_dev_ctl_context {
+	struct sysctl_context context;
+	struct mpls_dev *mdev;
+};
+
+static void *mpls_dev_data(const struct sysctl_context *ctx)
+{
+	const struct mpls_dev_ctl_context *mpls_ctx =
+		container_of(ctx, struct mpls_dev_ctl_context, context);
+
+	return mpls_ctx->mdev;
+}
 
 static int mpls_conf_proc(const struct ctl_table *ctl, int write,
 			  void *buffer, size_t *lenp, loff_t *ppos)
 {
-	int oval = *(int *)ctl->data;
-	int ret = proc_dointvec(ctl, write, buffer, lenp, ppos);
+	struct mpls_dev *mdev = ctl->data;
+	int oval = mdev->input_enabled;
+	struct ctl_table tmp = *ctl;
+	int ret;
 
-	if (write) {
-		struct mpls_dev *mdev = ctl->extra1;
-		int i = (int *)ctl->data - (int *)mdev;
-		struct net *net = ctl->extra2;
-		int val = *(int *)ctl->data;
+	tmp.data = &mdev->input_enabled;
+	ret = proc_dointvec(&tmp, write, buffer, lenp, ppos);
 
-		if (i == offsetof(struct mpls_dev, input_enabled) &&
-		    val != oval) {
-			mpls_netconf_notify_devconf(net, RTM_NEWNETCONF,
-						    NETCONFA_INPUT, mdev);
-		}
-	}
+	if (write && ret == 0 && mdev->input_enabled != oval)
+		mpls_netconf_notify_devconf(dev_net(mdev->dev), RTM_NEWNETCONF,
+					    NETCONFA_INPUT, mdev);
 
 	return ret;
 }
 
-static const struct ctl_table mpls_dev_table[] = {
-	{
-		.procname	= "input",
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= mpls_conf_proc,
-		.data		= MPLS_PERDEV_SYSCTL_OFFSET(input_enabled),
-	},
+static const struct sysctl_field mpls_dev_table[] = {
+	SYSCTL_FIELD_CUSTOM("input", 0644, sizeof(int), mpls_dev_data,
+			 mpls_conf_proc),
 };
 
 static int mpls_dev_sysctl_register(struct net_device *dev,
 				    struct mpls_dev *mdev)
 {
 	char path[sizeof("net/mpls/conf/") + IFNAMSIZ];
-	size_t table_size = ARRAY_SIZE(mpls_dev_table);
 	struct net *net = dev_net(dev);
-	struct ctl_table *table;
-	int i;
-
-	table = kmemdup(&mpls_dev_table, sizeof(mpls_dev_table), GFP_KERNEL);
-	if (!table)
-		goto out;
-
-	/* Table data contains only offsets relative to the base of
-	 * the mdev at this point, so make them absolute.
-	 */
-	for (i = 0; i < table_size; i++) {
-		table[i].data = (char *)mdev + (uintptr_t)table[i].data;
-		table[i].extra1 = mdev;
-		table[i].extra2 = net;
-	}
+	struct mpls_dev_ctl_context ctx = {
+		.context.ns.net_ns = net,
+		.mdev = mdev,
+	};
 
 	snprintf(path, sizeof(path), "net/mpls/conf/%s", dev->name);
 
-	mdev->sysctl = register_net_sysctl_sz(net, path, table, table_size);
+	mdev->sysctl = register_sysctl_fields_ctx(&net->sysctls, path,
+						  mpls_dev_table, &ctx);
 	if (!mdev->sysctl)
-		goto free;
+		goto out;
 
 	mpls_netconf_notify_devconf(net, RTM_NEWNETCONF, NETCONFA_ALL, mdev);
 	return 0;
 
-free:
-	kfree(table);
 out:
 	mdev->sysctl = NULL;
 	return -ENOBUFS;
@@ -1464,14 +1452,11 @@ static void mpls_dev_sysctl_unregister(struct net_device *dev,
 				       struct mpls_dev *mdev)
 {
 	struct net *net = dev_net(dev);
-	const struct ctl_table *table;
 
 	if (!mdev->sysctl)
 		return;
 
-	table = mdev->sysctl->ctl_table_arg;
 	unregister_net_sysctl_table(mdev->sysctl);
-	kfree(table);
 
 	mpls_netconf_notify_devconf(net, RTM_DELNETCONF, 0, mdev);
 }
@@ -2705,43 +2690,35 @@ static int mpls_platform_labels(const struct ctl_table *table, int write,
 	return ret;
 }
 
-#define MPLS_NS_SYSCTL_OFFSET(field)		\
-	(&((struct net *)0)->field)
+#define MPLS_DATA(type, field)						\
+static type *mpls_ ## field ## _data(const struct sysctl_context *ctx)	\
+{									\
+	return &ctx->ns.net_ns->mpls.field;				\
+}
 
-static const struct ctl_table mpls_table[] = {
-	{
-		.procname	= "platform_labels",
-		.data		= NULL,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= mpls_platform_labels,
-	},
-	{
-		.procname	= "ip_ttl_propagate",
-		.data		= MPLS_NS_SYSCTL_OFFSET(mpls.ip_ttl_propagate),
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_ONE,
-	},
-	{
-		.procname	= "default_ttl",
-		.data		= MPLS_NS_SYSCTL_OFFSET(mpls.default_ttl),
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ONE,
-		.extra2		= &ttl_max,
-	},
+static void *mpls_net_data(const struct sysctl_context *ctx)
+{
+	return ctx->ns.net_ns;
+}
+
+MPLS_DATA(int, ip_ttl_propagate)
+MPLS_DATA(int, default_ttl)
+
+static const struct sysctl_field mpls_table[] = {
+	SYSCTL_FIELD_CUSTOM("platform_labels", 0644, sizeof(int),
+			 mpls_net_data, mpls_platform_labels),
+	SYSCTL_FIELD_STATIC_INT_MINMAX("ip_ttl_propagate", 0644,
+				    mpls_ip_ttl_propagate_data, SYSCTL_ZERO,
+				    SYSCTL_ONE),
+	SYSCTL_FIELD_STATIC_INT_MINMAX("default_ttl", 0644, mpls_default_ttl_data,
+				    SYSCTL_ONE, &ttl_max),
 };
 
 static __net_init int mpls_net_init(struct net *net)
 {
-	size_t table_size = ARRAY_SIZE(mpls_table);
-	struct ctl_table *table;
-	int i;
-
+	struct sysctl_context ctx = {
+		.ns.net_ns = net,
+	};
 	mutex_init(&net->mpls.platform_mutex);
 	seqcount_mutex_init(&net->mpls.platform_label_seq, &net->mpls.platform_mutex);
 
@@ -2750,23 +2727,11 @@ static __net_init int mpls_net_init(struct net *net)
 	net->mpls.ip_ttl_propagate = 1;
 	net->mpls.default_ttl = 255;
 
-	table = kmemdup(mpls_table, sizeof(mpls_table), GFP_KERNEL);
-	if (table == NULL)
+	net->mpls.ctl = register_sysctl_fields(&net->sysctls, "net/mpls",
+					       mpls_table, &ctx);
+	if (!net->mpls.ctl)
 		return -ENOMEM;
 
-	/* Table data contains only offsets relative to the base of
-	 * the mdev at this point, so make them absolute.
-	 */
-	for (i = 0; i < table_size; i++)
-		table[i].data = (char *)net + (uintptr_t)table[i].data;
-
-	net->mpls.ctl = register_net_sysctl_sz(net, "net/mpls", table,
-					       table_size);
-	if (net->mpls.ctl == NULL) {
-		kfree(table);
-		return -ENOMEM;
-	}
-
 	return 0;
 }
 
@@ -2774,12 +2739,9 @@ static __net_exit void mpls_net_exit(struct net *net)
 {
 	struct mpls_route __rcu **platform_label;
 	size_t platform_labels;
-	const struct ctl_table *table;
 	unsigned int index;
 
-	table = net->mpls.ctl->ctl_table_arg;
 	unregister_net_sysctl_table(net->mpls.ctl);
-	kfree(table);
 
 	/* An rcu grace period has passed since there was a device in
 	 * the network namespace (and thus the last in flight packet)
-- 
2.55.0


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [RFC PATCH v1 27/30] sysctl: net: use sysctl_field in IPv4 devconf sysctls
       [not found] <cover.1787771905.git.legion@kernel.org>
                   ` (25 preceding siblings ...)
  2026-08-26 19:42 ` [RFC PATCH v1 26/30] sysctl: net: use sysctl_field for MPLS sysctls Alexey Gladkov
@ 2026-08-26 19:42 ` Alexey Gladkov
  2026-08-26 19:42 ` [RFC PATCH v1 28/30] sysctl: net: use sysctl_field in IPv6 " Alexey Gladkov
                   ` (2 subsequent siblings)
  29 siblings, 0 replies; 37+ messages in thread
From: Alexey Gladkov @ 2026-08-26 19:42 UTC (permalink / raw)
  To: Linus Torvalds, Eric W . Biederman, Kees Cook, Joel Granados
  Cc: LKML, linux-fsdevel

IPv4 devconf clones its sysctl tables for each registered devconf so the
registration path can rewrite data pointers and attach the matching
network namespace through extra2. The copied tables then have to stay
alive until unregistration only so they can be freed.

Allow sysctl_field custom entries to derive extra1 and extra2 from the
registration context, and use that in devinet. This keeps the devconf
sysctl descriptors static and const while preserving the existing
handlers' view of data, extra1 and extra2.

The per-net ip_forward entry uses the same context path, so it no longer
needs a one-entry table copy either.

Signed-off-by: Alexey Gladkov <legion@kernel.org>
---
 net/ipv4/devinet.c | 258 ++++++++++++++++++++++++---------------------
 1 file changed, 140 insertions(+), 118 deletions(-)

diff --git a/net/ipv4/devinet.c b/net/ipv4/devinet.c
index 58fe7cb69545..69b4b720a0e2 100644
--- a/net/ipv4/devinet.c
+++ b/net/ipv4/devinet.c
@@ -2613,15 +2613,44 @@ static int ipv4_doint_and_flush(const struct ctl_table *ctl, int write,
 	return ret;
 }
 
-#define DEVINET_SYSCTL_ENTRY(attr, name, mval, proc) \
-	{ \
-		.procname	= name, \
-		.data		= ipv4_devconf.data + \
-				  IPV4_DEVCONF_ ## attr - 1, \
-		.maxlen		= sizeof(int), \
-		.mode		= mval, \
-		.proc_handler	= proc, \
-		.extra1		= &ipv4_devconf, \
+struct devinet_ctl_context {
+	struct sysctl_context context;
+	struct ipv4_devconf *devconf;
+};
+
+#define DEVINET_DATA(attr)						\
+static void *devinet_ ## attr ## _data(const struct sysctl_context *ctx)	\
+{									\
+	const struct devinet_ctl_context *devinet_ctx =                 \
+		container_of(ctx, struct devinet_ctl_context, context); \
+	return &devinet_ctx->devconf->data[IPV4_DEVCONF_ ## attr - 1];  \
+}
+
+static void *devinet_conf_data(const struct sysctl_context *ctx)
+{
+	const struct devinet_ctl_context *devinet_ctx =
+		container_of(ctx, struct devinet_ctl_context, context);
+
+	return devinet_ctx->devconf;
+}
+
+static void *devinet_net_data(const struct sysctl_context *ctx)
+{
+	return ctx->ns.net_ns;
+}
+
+#define DEVINET_SYSCTL_ENTRY(attr, name, mval, proc)			\
+	{								\
+		.procname	= name,					\
+		.mode		= mval,					\
+		.type		= SYSCTL_FIELD_CUSTOM,			\
+		.ctl_custom	= {					\
+			.proc_handler	= proc,				\
+			.data		= devinet_ ## attr ## _data,	\
+			.extra1		= devinet_conf_data,		\
+			.extra2		= devinet_net_data,		\
+			.maxlen		= sizeof(int),			\
+		},							\
 	}
 
 #define DEVINET_SYSCTL_RW_ENTRY(attr, name) \
@@ -2636,104 +2665,118 @@ static int ipv4_doint_and_flush(const struct ctl_table *ctl, int write,
 #define DEVINET_SYSCTL_FLUSHING_ENTRY(attr, name) \
 	DEVINET_SYSCTL_COMPLEX_ENTRY(attr, name, ipv4_doint_and_flush)
 
-static struct devinet_sysctl_table {
-	struct ctl_table_header *sysctl_header;
-	struct ctl_table devinet_vars[IPV4_DEVCONF_MAX];
-} devinet_sysctl = {
-	.devinet_vars = {
-		DEVINET_SYSCTL_COMPLEX_ENTRY(FORWARDING, "forwarding",
-					     devinet_sysctl_forward),
-		DEVINET_SYSCTL_RO_ENTRY(MC_FORWARDING, "mc_forwarding"),
-		DEVINET_SYSCTL_RW_ENTRY(BC_FORWARDING, "bc_forwarding"),
-
-		DEVINET_SYSCTL_RW_ENTRY(ACCEPT_REDIRECTS, "accept_redirects"),
-		DEVINET_SYSCTL_RW_ENTRY(SECURE_REDIRECTS, "secure_redirects"),
-		DEVINET_SYSCTL_RW_ENTRY(SHARED_MEDIA, "shared_media"),
-		DEVINET_SYSCTL_RW_ENTRY(RP_FILTER, "rp_filter"),
-		DEVINET_SYSCTL_RW_ENTRY(SEND_REDIRECTS, "send_redirects"),
-		DEVINET_SYSCTL_RW_ENTRY(ACCEPT_SOURCE_ROUTE,
-					"accept_source_route"),
-		DEVINET_SYSCTL_RW_ENTRY(ACCEPT_LOCAL, "accept_local"),
-		DEVINET_SYSCTL_RW_ENTRY(SRC_VMARK, "src_valid_mark"),
-		DEVINET_SYSCTL_RW_ENTRY(PROXY_ARP, "proxy_arp"),
-		DEVINET_SYSCTL_RW_ENTRY(MEDIUM_ID, "medium_id"),
-		DEVINET_SYSCTL_RW_ENTRY(BOOTP_RELAY, "bootp_relay"),
-		DEVINET_SYSCTL_RW_ENTRY(LOG_MARTIANS, "log_martians"),
-		DEVINET_SYSCTL_RW_ENTRY(TAG, "tag"),
-		DEVINET_SYSCTL_RW_ENTRY(ARPFILTER, "arp_filter"),
-		DEVINET_SYSCTL_RW_ENTRY(ARP_ANNOUNCE, "arp_announce"),
-		DEVINET_SYSCTL_RW_ENTRY(ARP_IGNORE, "arp_ignore"),
-		DEVINET_SYSCTL_RW_ENTRY(ARP_ACCEPT, "arp_accept"),
-		DEVINET_SYSCTL_RW_ENTRY(ARP_NOTIFY, "arp_notify"),
-		DEVINET_SYSCTL_RW_ENTRY(ARP_EVICT_NOCARRIER,
-					"arp_evict_nocarrier"),
-		DEVINET_SYSCTL_RW_ENTRY(PROXY_ARP_PVLAN, "proxy_arp_pvlan"),
-		DEVINET_SYSCTL_RW_ENTRY(FORCE_IGMP_VERSION,
-					"force_igmp_version"),
-		DEVINET_SYSCTL_RW_ENTRY(IGMPV2_UNSOLICITED_REPORT_INTERVAL,
-					"igmpv2_unsolicited_report_interval"),
-		DEVINET_SYSCTL_RW_ENTRY(IGMPV3_UNSOLICITED_REPORT_INTERVAL,
-					"igmpv3_unsolicited_report_interval"),
-		DEVINET_SYSCTL_RW_ENTRY(IGNORE_ROUTES_WITH_LINKDOWN,
-					"ignore_routes_with_linkdown"),
-		DEVINET_SYSCTL_RW_ENTRY(DROP_GRATUITOUS_ARP,
-					"drop_gratuitous_arp"),
-
-		DEVINET_SYSCTL_FLUSHING_ENTRY(NOXFRM, "disable_xfrm"),
-		DEVINET_SYSCTL_FLUSHING_ENTRY(NOPOLICY, "disable_policy"),
-		DEVINET_SYSCTL_FLUSHING_ENTRY(PROMOTE_SECONDARIES,
-					      "promote_secondaries"),
-		DEVINET_SYSCTL_FLUSHING_ENTRY(ROUTE_LOCALNET,
-					      "route_localnet"),
-		DEVINET_SYSCTL_FLUSHING_ENTRY(DROP_UNICAST_IN_L2_MULTICAST,
-					      "drop_unicast_in_l2_multicast"),
-	},
+DEVINET_DATA(FORWARDING)
+DEVINET_DATA(MC_FORWARDING)
+DEVINET_DATA(BC_FORWARDING)
+DEVINET_DATA(ACCEPT_REDIRECTS)
+DEVINET_DATA(SECURE_REDIRECTS)
+DEVINET_DATA(SHARED_MEDIA)
+DEVINET_DATA(RP_FILTER)
+DEVINET_DATA(SEND_REDIRECTS)
+DEVINET_DATA(ACCEPT_SOURCE_ROUTE)
+DEVINET_DATA(ACCEPT_LOCAL)
+DEVINET_DATA(SRC_VMARK)
+DEVINET_DATA(PROXY_ARP)
+DEVINET_DATA(MEDIUM_ID)
+DEVINET_DATA(BOOTP_RELAY)
+DEVINET_DATA(LOG_MARTIANS)
+DEVINET_DATA(TAG)
+DEVINET_DATA(ARPFILTER)
+DEVINET_DATA(ARP_ANNOUNCE)
+DEVINET_DATA(ARP_IGNORE)
+DEVINET_DATA(ARP_ACCEPT)
+DEVINET_DATA(ARP_NOTIFY)
+DEVINET_DATA(ARP_EVICT_NOCARRIER)
+DEVINET_DATA(PROXY_ARP_PVLAN)
+DEVINET_DATA(FORCE_IGMP_VERSION)
+DEVINET_DATA(IGMPV2_UNSOLICITED_REPORT_INTERVAL)
+DEVINET_DATA(IGMPV3_UNSOLICITED_REPORT_INTERVAL)
+DEVINET_DATA(IGNORE_ROUTES_WITH_LINKDOWN)
+DEVINET_DATA(DROP_GRATUITOUS_ARP)
+DEVINET_DATA(NOXFRM)
+DEVINET_DATA(NOPOLICY)
+DEVINET_DATA(PROMOTE_SECONDARIES)
+DEVINET_DATA(ROUTE_LOCALNET)
+DEVINET_DATA(DROP_UNICAST_IN_L2_MULTICAST)
+
+static const struct sysctl_field devinet_sysctls[] = {
+	DEVINET_SYSCTL_COMPLEX_ENTRY(FORWARDING, "forwarding",
+				     devinet_sysctl_forward),
+	DEVINET_SYSCTL_RO_ENTRY(MC_FORWARDING, "mc_forwarding"),
+	DEVINET_SYSCTL_RW_ENTRY(BC_FORWARDING, "bc_forwarding"),
+	DEVINET_SYSCTL_RW_ENTRY(ACCEPT_REDIRECTS, "accept_redirects"),
+	DEVINET_SYSCTL_RW_ENTRY(SECURE_REDIRECTS, "secure_redirects"),
+	DEVINET_SYSCTL_RW_ENTRY(SHARED_MEDIA, "shared_media"),
+	DEVINET_SYSCTL_RW_ENTRY(RP_FILTER, "rp_filter"),
+	DEVINET_SYSCTL_RW_ENTRY(SEND_REDIRECTS, "send_redirects"),
+	DEVINET_SYSCTL_RW_ENTRY(ACCEPT_SOURCE_ROUTE,
+				"accept_source_route"),
+	DEVINET_SYSCTL_RW_ENTRY(ACCEPT_LOCAL, "accept_local"),
+	DEVINET_SYSCTL_RW_ENTRY(SRC_VMARK, "src_valid_mark"),
+	DEVINET_SYSCTL_RW_ENTRY(PROXY_ARP, "proxy_arp"),
+	DEVINET_SYSCTL_RW_ENTRY(MEDIUM_ID, "medium_id"),
+	DEVINET_SYSCTL_RW_ENTRY(BOOTP_RELAY, "bootp_relay"),
+	DEVINET_SYSCTL_RW_ENTRY(LOG_MARTIANS, "log_martians"),
+	DEVINET_SYSCTL_RW_ENTRY(TAG, "tag"),
+	DEVINET_SYSCTL_RW_ENTRY(ARPFILTER, "arp_filter"),
+	DEVINET_SYSCTL_RW_ENTRY(ARP_ANNOUNCE, "arp_announce"),
+	DEVINET_SYSCTL_RW_ENTRY(ARP_IGNORE, "arp_ignore"),
+	DEVINET_SYSCTL_RW_ENTRY(ARP_ACCEPT, "arp_accept"),
+	DEVINET_SYSCTL_RW_ENTRY(ARP_NOTIFY, "arp_notify"),
+	DEVINET_SYSCTL_RW_ENTRY(ARP_EVICT_NOCARRIER,
+				"arp_evict_nocarrier"),
+	DEVINET_SYSCTL_RW_ENTRY(PROXY_ARP_PVLAN, "proxy_arp_pvlan"),
+	DEVINET_SYSCTL_RW_ENTRY(FORCE_IGMP_VERSION,
+				"force_igmp_version"),
+	DEVINET_SYSCTL_RW_ENTRY(IGMPV2_UNSOLICITED_REPORT_INTERVAL,
+				"igmpv2_unsolicited_report_interval"),
+	DEVINET_SYSCTL_RW_ENTRY(IGMPV3_UNSOLICITED_REPORT_INTERVAL,
+				"igmpv3_unsolicited_report_interval"),
+	DEVINET_SYSCTL_RW_ENTRY(IGNORE_ROUTES_WITH_LINKDOWN,
+				"ignore_routes_with_linkdown"),
+	DEVINET_SYSCTL_RW_ENTRY(DROP_GRATUITOUS_ARP,
+				"drop_gratuitous_arp"),
+	DEVINET_SYSCTL_FLUSHING_ENTRY(NOXFRM, "disable_xfrm"),
+	DEVINET_SYSCTL_FLUSHING_ENTRY(NOPOLICY, "disable_policy"),
+	DEVINET_SYSCTL_FLUSHING_ENTRY(PROMOTE_SECONDARIES,
+				      "promote_secondaries"),
+	DEVINET_SYSCTL_FLUSHING_ENTRY(ROUTE_LOCALNET,
+				      "route_localnet"),
+	DEVINET_SYSCTL_FLUSHING_ENTRY(DROP_UNICAST_IN_L2_MULTICAST,
+				      "drop_unicast_in_l2_multicast"),
 };
 
 static int __devinet_sysctl_register(struct net *net, char *dev_name,
 				     int ifindex, struct ipv4_devconf *p)
 {
-	int i;
-	struct devinet_sysctl_table *t;
+	struct ctl_table_header *hdr;
+	struct devinet_ctl_context ctx = {
+		.context.ns.net_ns = net,
+		.devconf = p,
+	};
 	char path[sizeof("net/ipv4/conf/") + IFNAMSIZ];
 
-	t = kmemdup(&devinet_sysctl, sizeof(*t), GFP_KERNEL_ACCOUNT);
-	if (!t)
-		goto out;
-
-	for (i = 0; i < ARRAY_SIZE(t->devinet_vars); i++) {
-		t->devinet_vars[i].data += (char *)p - (char *)&ipv4_devconf;
-		t->devinet_vars[i].extra1 = p;
-		t->devinet_vars[i].extra2 = net;
-	}
-
 	snprintf(path, sizeof(path), "net/ipv4/conf/%s", dev_name);
 
-	t->sysctl_header = register_net_sysctl(net, path, t->devinet_vars);
-	if (!t->sysctl_header)
-		goto free;
+	hdr = register_sysctl_fields_ctx(&net->sysctls, path, devinet_sysctls, &ctx);
+	if (!hdr)
+		return -ENOMEM;
 
-	p->sysctl = t;
+	p->sysctl = hdr;
 
 	inet_netconf_notify_devconf(net, RTM_NEWNETCONF, NETCONFA_ALL,
 				    ifindex, p);
 	return 0;
-
-free:
-	kfree(t);
-out:
-	return -ENOMEM;
 }
 
 static void __devinet_sysctl_unregister(struct net *net,
 					struct ipv4_devconf *cnf, int ifindex)
 {
-	struct devinet_sysctl_table *t = cnf->sysctl;
+	struct ctl_table_header *hdr = cnf->sysctl;
 
-	if (t) {
+	if (hdr) {
 		cnf->sysctl = NULL;
-		unregister_net_sysctl_table(t->sysctl_header);
-		kfree(t);
+		unregister_net_sysctl_table(hdr);
 	}
 
 	inet_netconf_notify_devconf(net, RTM_DELNETCONF, 0, ifindex, NULL);
@@ -2764,17 +2807,9 @@ static void devinet_sysctl_unregister(struct in_device *idev)
 	neigh_sysctl_unregister(idev->arp_parms);
 }
 
-static struct ctl_table ctl_forward_entry[] = {
-	{
-		.procname	= "ip_forward",
-		.data		= &ipv4_devconf.data[
-					IPV4_DEVCONF_FORWARDING - 1],
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= devinet_sysctl_forward,
-		.extra1		= &ipv4_devconf,
-		.extra2		= &init_net,
-	},
+static const struct sysctl_field ctl_forward_entry[] = {
+	DEVINET_SYSCTL_COMPLEX_ENTRY(FORWARDING, "ip_forward",
+				     devinet_sysctl_forward),
 };
 #endif
 
@@ -2782,7 +2817,7 @@ static __net_init int devinet_init_net(struct net *net)
 {
 #ifdef CONFIG_SYSCTL
 	struct ctl_table_header *forw_hdr;
-	struct ctl_table *tbl;
+	struct devinet_ctl_context forw_ctx;
 #endif
 	struct ipv4_devconf *all, *dflt;
 	int err;
@@ -2802,16 +2837,6 @@ static __net_init int devinet_init_net(struct net *net)
 	if (!dflt)
 		goto err_alloc_dflt;
 
-#ifdef CONFIG_SYSCTL
-	tbl = kmemdup(ctl_forward_entry, sizeof(ctl_forward_entry), GFP_KERNEL);
-	if (!tbl)
-		goto err_alloc_ctl;
-
-	tbl[0].data = &all->data[IPV4_DEVCONF_FORWARDING - 1];
-	tbl[0].extra1 = all;
-	tbl[0].extra2 = net;
-#endif
-
 	if (!net_eq(net, &init_net)) {
 		switch (net_inherit_devconf()) {
 		case 3:
@@ -2847,10 +2872,15 @@ static __net_init int devinet_init_net(struct net *net)
 		goto err_reg_dflt;
 
 	err = -ENOMEM;
-	forw_hdr = register_net_sysctl_sz(net, "net/ipv4", tbl,
-					  ARRAY_SIZE(ctl_forward_entry));
+	forw_ctx = (struct devinet_ctl_context) {
+		.context.ns.net_ns = net,
+		.devconf = all,
+	};
+	forw_hdr = register_sysctl_fields_ctx(&net->sysctls, "net/ipv4",
+					      ctl_forward_entry, &forw_ctx);
 	if (!forw_hdr)
 		goto err_reg_ctl;
+
 	net->ipv4.forw_hdr = forw_hdr;
 #endif
 
@@ -2869,8 +2899,6 @@ static __net_init int devinet_init_net(struct net *net)
 err_reg_dflt:
 	__devinet_sysctl_unregister(net, all, NETCONFA_IFINDEX_ALL);
 err_reg_all:
-	kfree(tbl);
-err_alloc_ctl:
 #endif
 	kfree(dflt);
 err_alloc_dflt:
@@ -2883,20 +2911,14 @@ static __net_init int devinet_init_net(struct net *net)
 
 static __net_exit void devinet_exit_net(struct net *net)
 {
-#ifdef CONFIG_SYSCTL
-	const struct ctl_table *tbl;
-#endif
-
 	cancel_delayed_work_sync(&net->ipv4.addr_chk_work);
 
 #ifdef CONFIG_SYSCTL
-	tbl = net->ipv4.forw_hdr->ctl_table_arg;
 	unregister_net_sysctl_table(net->ipv4.forw_hdr);
 	__devinet_sysctl_unregister(net, net->ipv4.devconf_dflt,
 				    NETCONFA_IFINDEX_DEFAULT);
 	__devinet_sysctl_unregister(net, net->ipv4.devconf_all,
 				    NETCONFA_IFINDEX_ALL);
-	kfree(tbl);
 #endif
 	kfree(net->ipv4.devconf_dflt);
 	kfree(net->ipv4.devconf_all);
-- 
2.55.0


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [RFC PATCH v1 28/30] sysctl: net: use sysctl_field in IPv6 devconf sysctls
       [not found] <cover.1787771905.git.legion@kernel.org>
                   ` (26 preceding siblings ...)
  2026-08-26 19:42 ` [RFC PATCH v1 27/30] sysctl: net: use sysctl_field in IPv4 devconf sysctls Alexey Gladkov
@ 2026-08-26 19:42 ` Alexey Gladkov
  2026-08-26 19:42 ` [RFC PATCH v1 29/30] sysctl: net: use sysctl_field in neighbour sysctls Alexey Gladkov
  2026-08-26 19:42 ` [RFC PATCH v1 30/30] sysctl: parport: use sysctl_field for dynamic sysctls Alexey Gladkov
  29 siblings, 0 replies; 37+ messages in thread
From: Alexey Gladkov @ 2026-08-26 19:42 UTC (permalink / raw)
  To: Linus Torvalds, Eric W . Biederman, Kees Cook, Joel Granados
  Cc: LKML, linux-fsdevel

IPv6 devconf allocates a private sysctl table for each registered
devconf only so registration can rewrite data pointers and attach the
matching inet6_dev and net namespace through extra1 and extra2.

Use sysctl_field with an IPv6 devconf registration context instead. The
table descriptors can stay static and const, while custom handlers still
see the same data, extra1 and extra2 values they used before.

This removes the per-devconf sysctl table clone and the matching
unregister side free.

Signed-off-by: Alexey Gladkov <legion@kernel.org>
---
 net/ipv6/addrconf.c | 746 +++++++++++++++-----------------------------
 1 file changed, 252 insertions(+), 494 deletions(-)

diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c
index 5476b6536eb7..a3bac460e7bd 100644
--- a/net/ipv6/addrconf.c
+++ b/net/ipv6/addrconf.c
@@ -6826,516 +6826,280 @@ static int addrconf_sysctl_force_forwarding(const struct ctl_table *ctl, int wri
 }
 
 static int minus_one = -1;
-static const int two_five_five = 255;
+static int two_five_five = 255;
 static u32 ioam6_if_id_max = U16_MAX;
 
-static const struct ctl_table addrconf_sysctl[] = {
-	{
-		.procname	= "forwarding",
-		.data		= &ipv6_devconf.forwarding,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= addrconf_sysctl_forward,
-	},
-	{
-		.procname	= "hop_limit",
-		.data		= &ipv6_devconf.hop_limit,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= (void *)SYSCTL_ONE,
-		.extra2		= (void *)&two_five_five,
-	},
-	{
-		.procname	= "mtu",
-		.data		= &ipv6_devconf.mtu6,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= addrconf_sysctl_mtu,
-	},
-	{
-		.procname	= "accept_ra",
-		.data		= &ipv6_devconf.accept_ra,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
-	{
-		.procname	= "accept_redirects",
-		.data		= &ipv6_devconf.accept_redirects,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
-	{
-		.procname	= "autoconf",
-		.data		= &ipv6_devconf.autoconf,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
-	{
-		.procname	= "dad_transmits",
-		.data		= &ipv6_devconf.dad_transmits,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
-	{
-		.procname	= "router_solicitations",
-		.data		= &ipv6_devconf.rtr_solicits,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= &minus_one,
-	},
-	{
-		.procname	= "router_solicitation_interval",
-		.data		= &ipv6_devconf.rtr_solicit_interval,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_jiffies,
-	},
-	{
-		.procname	= "router_solicitation_max_interval",
-		.data		= &ipv6_devconf.rtr_solicit_max_interval,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_jiffies,
-	},
-	{
-		.procname	= "router_solicitation_delay",
-		.data		= &ipv6_devconf.rtr_solicit_delay,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_jiffies,
-	},
-	{
-		.procname	= "force_mld_version",
-		.data		= &ipv6_devconf.force_mld_version,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
-	{
-		.procname	= "mldv1_unsolicited_report_interval",
-		.data		=
-			&ipv6_devconf.mldv1_unsolicited_report_interval,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_ms_jiffies,
-	},
-	{
-		.procname	= "mldv2_unsolicited_report_interval",
-		.data		=
-			&ipv6_devconf.mldv2_unsolicited_report_interval,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_ms_jiffies,
-	},
-	{
-		.procname	= "use_tempaddr",
-		.data		= &ipv6_devconf.use_tempaddr,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
-	{
-		.procname	= "temp_valid_lft",
-		.data		= &ipv6_devconf.temp_valid_lft,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
-	{
-		.procname	= "temp_prefered_lft",
-		.data		= &ipv6_devconf.temp_prefered_lft,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
-	{
-		.procname       = "regen_min_advance",
-		.data           = &ipv6_devconf.regen_min_advance,
-		.maxlen         = sizeof(int),
-		.mode           = 0644,
-		.proc_handler   = proc_dointvec,
-	},
-	{
-		.procname	= "regen_max_retry",
-		.data		= &ipv6_devconf.regen_max_retry,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
-	{
-		.procname	= "max_desync_factor",
-		.data		= &ipv6_devconf.max_desync_factor,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
-	{
-		.procname	= "max_addresses",
-		.data		= &ipv6_devconf.max_addresses,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
-	{
-		.procname	= "accept_ra_defrtr",
-		.data		= &ipv6_devconf.accept_ra_defrtr,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
-	{
-		.procname	= "ra_defrtr_metric",
-		.data		= &ipv6_devconf.ra_defrtr_metric,
-		.maxlen		= sizeof(u32),
-		.mode		= 0644,
-		.proc_handler	= proc_douintvec_minmax,
-		.extra1		= (void *)SYSCTL_ONE,
-	},
-	{
-		.procname	= "accept_ra_min_hop_limit",
-		.data		= &ipv6_devconf.accept_ra_min_hop_limit,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
-	{
-		.procname	= "accept_ra_min_lft",
-		.data		= &ipv6_devconf.accept_ra_min_lft,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
-	{
-		.procname	= "accept_ra_pinfo",
-		.data		= &ipv6_devconf.accept_ra_pinfo,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
-	{
-		.procname	= "ra_honor_pio_life",
-		.data		= &ipv6_devconf.ra_honor_pio_life,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_ONE,
-	},
-	{
-		.procname	= "ra_honor_pio_pflag",
-		.data		= &ipv6_devconf.ra_honor_pio_pflag,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_ONE,
-	},
+struct addrconf_ctl_context {
+	struct sysctl_context context;
+	struct ipv6_devconf *devconf;
+};
+
+#define ADDRCONF_DATA(type, field)						\
+static type *addrconf_ ## field ## _data(const struct sysctl_context *ctx)	\
+{										\
+	const struct addrconf_ctl_context *addrconf_ctx =			\
+		container_of(ctx, struct addrconf_ctl_context, context);	\
+	return &addrconf_ctx->devconf->field;					\
+}
+
+static void *addrconf_idev_data(const struct sysctl_context *ctx)
+{
+	const struct addrconf_ctl_context *addrconf_ctx =
+		container_of(ctx, struct addrconf_ctl_context, context);
+	struct ipv6_devconf *cnf = addrconf_ctx->devconf;
+	struct net *net = ctx->ns.net_ns;
+
+	if (cnf == net->ipv6.devconf_all || cnf == net->ipv6.devconf_dflt)
+		return NULL;
+
+	return container_of(cnf, struct inet6_dev, cnf);
+}
+
+static void *addrconf_net_data(const struct sysctl_context *ctx)
+{
+	return ctx->ns.net_ns;
+}
+
+#define ADDRCONF_CUSTOM_ENTRY(field, name, mval, len, proc)		\
+	{								\
+		.procname	= name,					\
+		.mode		= mval,					\
+		.type		= SYSCTL_FIELD_CUSTOM,			\
+		.ctl_custom	= {					\
+			.proc_handler	= proc,				\
+			.data		= addrconf_ ## field ## _data,	\
+			.extra1		= addrconf_idev_data,		\
+			.extra2		= addrconf_net_data,		\
+			.maxlen		= len,				\
+		},							\
+	}
+
+#define ADDRCONF_INT_ENTRY(field, name) \
+	SYSCTL_FIELD_INT(name, 0644, addrconf_ ## field ## _data)
+
+#define ADDRCONF_INT_RO_ENTRY(field, name) \
+	SYSCTL_FIELD_INT(name, 0444, addrconf_ ## field ## _data)
+
+#define ADDRCONF_INT_MINMAX_ENTRY(field, name, min, max) \
+	SYSCTL_FIELD_STATIC_INT_MINMAX(name, 0644, \
+				       addrconf_ ## field ## _data, min, max)
+
+#define ADDRCONF_UINT_ENTRY(field, name) \
+	SYSCTL_FIELD_UINT(name, 0644, addrconf_ ## field ## _data)
+
+#define ADDRCONF_UINT_MINMAX_ENTRY(field, name, min, max) \
+	SYSCTL_FIELD_STATIC_UINT_MINMAX(name, 0644, \
+				        addrconf_ ## field ## _data, min, max)
+
+#define ADDRCONF_U8_MINMAX_ENTRY(field, name, min, max) \
+	SYSCTL_FIELD_STATIC_U8_MINMAX(name, 0644, \
+				      addrconf_ ## field ## _data, min, max)
+
+ADDRCONF_DATA(void, forwarding)
+ADDRCONF_DATA(int, hop_limit)
+ADDRCONF_DATA(void, mtu6)
+ADDRCONF_DATA(int, accept_ra)
+ADDRCONF_DATA(int, accept_redirects)
+ADDRCONF_DATA(int, autoconf)
+ADDRCONF_DATA(int, dad_transmits)
+ADDRCONF_DATA(int, rtr_solicits)
+ADDRCONF_DATA(void, rtr_solicit_interval)
+ADDRCONF_DATA(void, rtr_solicit_max_interval)
+ADDRCONF_DATA(void, rtr_solicit_delay)
+ADDRCONF_DATA(int, force_mld_version)
+ADDRCONF_DATA(void, mldv1_unsolicited_report_interval)
+ADDRCONF_DATA(void, mldv2_unsolicited_report_interval)
+ADDRCONF_DATA(int, use_tempaddr)
+ADDRCONF_DATA(int, temp_valid_lft)
+ADDRCONF_DATA(int, temp_prefered_lft)
+ADDRCONF_DATA(int, regen_min_advance)
+ADDRCONF_DATA(int, regen_max_retry)
+ADDRCONF_DATA(int, max_desync_factor)
+ADDRCONF_DATA(int, max_addresses)
+ADDRCONF_DATA(int, accept_ra_defrtr)
+ADDRCONF_DATA(unsigned int, ra_defrtr_metric)
+ADDRCONF_DATA(int, accept_ra_min_hop_limit)
+ADDRCONF_DATA(int, accept_ra_min_lft)
+ADDRCONF_DATA(int, accept_ra_pinfo)
+ADDRCONF_DATA(u8, ra_honor_pio_life)
+ADDRCONF_DATA(u8, ra_honor_pio_pflag)
 #ifdef CONFIG_IPV6_ROUTER_PREF
-	{
-		.procname	= "accept_ra_rtr_pref",
-		.data		= &ipv6_devconf.accept_ra_rtr_pref,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
-	{
-		.procname	= "router_probe_interval",
-		.data		= &ipv6_devconf.rtr_probe_interval,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_jiffies,
-	},
+ADDRCONF_DATA(int, accept_ra_rtr_pref)
+ADDRCONF_DATA(void, rtr_probe_interval)
 #ifdef CONFIG_IPV6_ROUTE_INFO
-	{
-		.procname	= "accept_ra_rt_info_min_plen",
-		.data		= &ipv6_devconf.accept_ra_rt_info_min_plen,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
-	{
-		.procname	= "accept_ra_rt_info_max_plen",
-		.data		= &ipv6_devconf.accept_ra_rt_info_max_plen,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
+ADDRCONF_DATA(int, accept_ra_rt_info_min_plen)
+ADDRCONF_DATA(int, accept_ra_rt_info_max_plen)
 #endif
 #endif
-	{
-		.procname	= "proxy_ndp",
-		.data		= &ipv6_devconf.proxy_ndp,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= addrconf_sysctl_proxy_ndp,
-	},
-	{
-		.procname	= "accept_source_route",
-		.data		= &ipv6_devconf.accept_source_route,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
+ADDRCONF_DATA(void, proxy_ndp)
+ADDRCONF_DATA(int, accept_source_route)
 #ifdef CONFIG_IPV6_OPTIMISTIC_DAD
-	{
-		.procname	= "optimistic_dad",
-		.data		= &ipv6_devconf.optimistic_dad,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler   = proc_dointvec,
-	},
-	{
-		.procname	= "use_optimistic",
-		.data		= &ipv6_devconf.use_optimistic,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
+ADDRCONF_DATA(int, optimistic_dad)
+ADDRCONF_DATA(int, use_optimistic)
 #endif
 #ifdef CONFIG_IPV6_MROUTE
-	{
-		.procname	= "mc_forwarding",
-		.data		= &ipv6_devconf.mc_forwarding,
-		.maxlen		= sizeof(int),
-		.mode		= 0444,
-		.proc_handler	= proc_dointvec,
-	},
+ADDRCONF_DATA(void, mc_forwarding)
 #endif
-	{
-		.procname	= "disable_ipv6",
-		.data		= &ipv6_devconf.disable_ipv6,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= addrconf_sysctl_disable,
-	},
-	{
-		.procname	= "accept_dad",
-		.data		= &ipv6_devconf.accept_dad,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
-	{
-		.procname	= "force_tllao",
-		.data		= &ipv6_devconf.force_tllao,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec
-	},
-	{
-		.procname	= "ndisc_notify",
-		.data		= &ipv6_devconf.ndisc_notify,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec
-	},
-	{
-		.procname	= "suppress_frag_ndisc",
-		.data		= &ipv6_devconf.suppress_frag_ndisc,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec
-	},
-	{
-		.procname	= "accept_ra_from_local",
-		.data		= &ipv6_devconf.accept_ra_from_local,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
-	{
-		.procname	= "accept_ra_mtu",
-		.data		= &ipv6_devconf.accept_ra_mtu,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
-	{
-		.procname	= "stable_secret",
-		.data		= &ipv6_devconf.stable_secret,
-		.maxlen		= IPV6_MAX_STRLEN,
-		.mode		= 0600,
-		.proc_handler	= addrconf_sysctl_stable_secret,
-	},
-	{
-		.procname	= "use_oif_addrs_only",
-		.data		= &ipv6_devconf.use_oif_addrs_only,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
-	{
-		.procname	= "ignore_routes_with_linkdown",
-		.data		= &ipv6_devconf.ignore_routes_with_linkdown,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= addrconf_sysctl_ignore_routes_with_linkdown,
-	},
-	{
-		.procname	= "drop_unicast_in_l2_multicast",
-		.data		= &ipv6_devconf.drop_unicast_in_l2_multicast,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
-	{
-		.procname	= "drop_unsolicited_na",
-		.data		= &ipv6_devconf.drop_unsolicited_na,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
-	{
-		.procname	= "keep_addr_on_down",
-		.data		= &ipv6_devconf.keep_addr_on_down,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-
-	},
-	{
-		.procname	= "seg6_enabled",
-		.data		= &ipv6_devconf.seg6_enabled,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
+ADDRCONF_DATA(void, disable_ipv6)
+ADDRCONF_DATA(int, accept_dad)
+ADDRCONF_DATA(int, force_tllao)
+ADDRCONF_DATA(int, ndisc_notify)
+ADDRCONF_DATA(int, suppress_frag_ndisc)
+ADDRCONF_DATA(int, accept_ra_from_local)
+ADDRCONF_DATA(int, accept_ra_mtu)
+ADDRCONF_DATA(void, stable_secret)
+ADDRCONF_DATA(int, use_oif_addrs_only)
+ADDRCONF_DATA(void, ignore_routes_with_linkdown)
+ADDRCONF_DATA(int, drop_unicast_in_l2_multicast)
+ADDRCONF_DATA(int, drop_unsolicited_na)
+ADDRCONF_DATA(int, keep_addr_on_down)
+ADDRCONF_DATA(int, seg6_enabled)
 #ifdef CONFIG_IPV6_SEG6_HMAC
-	{
-		.procname	= "seg6_require_hmac",
-		.data		= &ipv6_devconf.seg6_require_hmac,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
-	},
+ADDRCONF_DATA(int, seg6_require_hmac)
 #endif
-	{
-		.procname       = "enhanced_dad",
-		.data           = &ipv6_devconf.enhanced_dad,
-		.maxlen         = sizeof(int),
-		.mode           = 0644,
-		.proc_handler   = proc_dointvec,
-	},
-	{
-		.procname	= "addr_gen_mode",
-		.data		= &ipv6_devconf.addr_gen_mode,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= addrconf_sysctl_addr_gen_mode,
-	},
-	{
-		.procname       = "disable_policy",
-		.data           = &ipv6_devconf.disable_policy,
-		.maxlen         = sizeof(int),
-		.mode           = 0644,
-		.proc_handler   = addrconf_sysctl_disable_policy,
-	},
-	{
-		.procname	= "ndisc_tclass",
-		.data		= &ipv6_devconf.ndisc_tclass,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= (void *)SYSCTL_ZERO,
-		.extra2		= (void *)&two_five_five,
-	},
-	{
-		.procname	= "rpl_seg_enabled",
-		.data		= &ipv6_devconf.rpl_seg_enabled,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler   = proc_dointvec_minmax,
-		.extra1         = SYSCTL_ZERO,
-		.extra2         = SYSCTL_ONE,
-	},
-	{
-		.procname	= "ioam6_enabled",
-		.data		= &ipv6_devconf.ioam6_enabled,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra1		= (void *)SYSCTL_ZERO,
-		.extra2		= (void *)SYSCTL_ONE,
-	},
-	{
-		.procname	= "ioam6_id",
-		.data		= &ipv6_devconf.ioam6_id,
-		.maxlen		= sizeof(u32),
-		.mode		= 0644,
-		.proc_handler	= proc_douintvec_minmax,
-		.extra1		= (void *)SYSCTL_ZERO,
-		.extra2		= (void *)&ioam6_if_id_max,
-	},
-	{
-		.procname	= "ioam6_id_wide",
-		.data		= &ipv6_devconf.ioam6_id_wide,
-		.maxlen		= sizeof(u32),
-		.mode		= 0644,
-		.proc_handler	= proc_douintvec,
-	},
-	{
-		.procname	= "ndisc_evict_nocarrier",
-		.data		= &ipv6_devconf.ndisc_evict_nocarrier,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra1		= (void *)SYSCTL_ZERO,
-		.extra2		= (void *)SYSCTL_ONE,
-	},
-	{
-		.procname	= "accept_untracked_na",
-		.data		= &ipv6_devconf.accept_untracked_na,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_TWO,
-	},
-	{
-		.procname	= "force_forwarding",
-		.data		= &ipv6_devconf.force_forwarding,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= addrconf_sysctl_force_forwarding,
-	},
+ADDRCONF_DATA(void, enhanced_dad)
+ADDRCONF_DATA(void, addr_gen_mode)
+ADDRCONF_DATA(void, disable_policy)
+ADDRCONF_DATA(int, ndisc_tclass)
+ADDRCONF_DATA(int, rpl_seg_enabled)
+ADDRCONF_DATA(u8, ioam6_enabled)
+ADDRCONF_DATA(unsigned int, ioam6_id)
+ADDRCONF_DATA(unsigned int, ioam6_id_wide)
+ADDRCONF_DATA(u8, ndisc_evict_nocarrier)
+ADDRCONF_DATA(int, accept_untracked_na)
+ADDRCONF_DATA(void, force_forwarding)
+
+static const struct sysctl_field addrconf_sysctl[] = {
+	ADDRCONF_CUSTOM_ENTRY(forwarding, "forwarding", 0644, sizeof(int),
+			      addrconf_sysctl_forward),
+	ADDRCONF_INT_MINMAX_ENTRY(hop_limit, "hop_limit", SYSCTL_ONE,
+				  &two_five_five),
+	ADDRCONF_CUSTOM_ENTRY(mtu6, "mtu", 0644, sizeof(int),
+			      addrconf_sysctl_mtu),
+	ADDRCONF_INT_ENTRY(accept_ra, "accept_ra"),
+	ADDRCONF_INT_ENTRY(accept_redirects, "accept_redirects"),
+	ADDRCONF_INT_ENTRY(autoconf, "autoconf"),
+	ADDRCONF_INT_ENTRY(dad_transmits, "dad_transmits"),
+	ADDRCONF_INT_MINMAX_ENTRY(rtr_solicits, "router_solicitations",
+				  &minus_one, NULL),
+	ADDRCONF_CUSTOM_ENTRY(rtr_solicit_interval,
+			      "router_solicitation_interval", 0644,
+			      sizeof(int), proc_dointvec_jiffies),
+	ADDRCONF_CUSTOM_ENTRY(rtr_solicit_max_interval,
+			      "router_solicitation_max_interval", 0644,
+			      sizeof(int), proc_dointvec_jiffies),
+	ADDRCONF_CUSTOM_ENTRY(rtr_solicit_delay,
+			      "router_solicitation_delay", 0644,
+			      sizeof(int), proc_dointvec_jiffies),
+	ADDRCONF_INT_ENTRY(force_mld_version, "force_mld_version"),
+	ADDRCONF_CUSTOM_ENTRY(mldv1_unsolicited_report_interval,
+			      "mldv1_unsolicited_report_interval", 0644,
+			      sizeof(int), proc_dointvec_ms_jiffies),
+	ADDRCONF_CUSTOM_ENTRY(mldv2_unsolicited_report_interval,
+			      "mldv2_unsolicited_report_interval", 0644,
+			      sizeof(int), proc_dointvec_ms_jiffies),
+	ADDRCONF_INT_ENTRY(use_tempaddr, "use_tempaddr"),
+	ADDRCONF_INT_ENTRY(temp_valid_lft, "temp_valid_lft"),
+	ADDRCONF_INT_ENTRY(temp_prefered_lft, "temp_prefered_lft"),
+	ADDRCONF_INT_ENTRY(regen_min_advance, "regen_min_advance"),
+	ADDRCONF_INT_ENTRY(regen_max_retry, "regen_max_retry"),
+	ADDRCONF_INT_ENTRY(max_desync_factor, "max_desync_factor"),
+	ADDRCONF_INT_ENTRY(max_addresses, "max_addresses"),
+	ADDRCONF_INT_ENTRY(accept_ra_defrtr, "accept_ra_defrtr"),
+	ADDRCONF_UINT_MINMAX_ENTRY(ra_defrtr_metric, "ra_defrtr_metric",
+				   SYSCTL_UINT_ONE, NULL),
+	ADDRCONF_INT_ENTRY(accept_ra_min_hop_limit,
+			 "accept_ra_min_hop_limit"),
+	ADDRCONF_INT_ENTRY(accept_ra_min_lft, "accept_ra_min_lft"),
+	ADDRCONF_INT_ENTRY(accept_ra_pinfo, "accept_ra_pinfo"),
+	ADDRCONF_U8_MINMAX_ENTRY(ra_honor_pio_life, "ra_honor_pio_life",
+				 SYSCTL_UINT_ZERO, SYSCTL_UINT_ONE),
+	ADDRCONF_U8_MINMAX_ENTRY(ra_honor_pio_pflag, "ra_honor_pio_pflag",
+				 SYSCTL_UINT_ZERO, SYSCTL_UINT_ONE),
+#ifdef CONFIG_IPV6_ROUTER_PREF
+	ADDRCONF_INT_ENTRY(accept_ra_rtr_pref, "accept_ra_rtr_pref"),
+	ADDRCONF_CUSTOM_ENTRY(rtr_probe_interval, "router_probe_interval",
+			      0644, sizeof(int), proc_dointvec_jiffies),
+#ifdef CONFIG_IPV6_ROUTE_INFO
+	ADDRCONF_INT_ENTRY(accept_ra_rt_info_min_plen,
+			 "accept_ra_rt_info_min_plen"),
+	ADDRCONF_INT_ENTRY(accept_ra_rt_info_max_plen,
+			 "accept_ra_rt_info_max_plen"),
+#endif
+#endif
+	ADDRCONF_CUSTOM_ENTRY(proxy_ndp, "proxy_ndp", 0644, sizeof(int),
+			      addrconf_sysctl_proxy_ndp),
+	ADDRCONF_INT_ENTRY(accept_source_route, "accept_source_route"),
+#ifdef CONFIG_IPV6_OPTIMISTIC_DAD
+	ADDRCONF_INT_ENTRY(optimistic_dad, "optimistic_dad"),
+	ADDRCONF_INT_ENTRY(use_optimistic, "use_optimistic"),
+#endif
+#ifdef CONFIG_IPV6_MROUTE
+	ADDRCONF_CUSTOM_ENTRY(mc_forwarding, "mc_forwarding", 0444,
+			      sizeof(int), proc_dointvec),
+#endif
+	ADDRCONF_CUSTOM_ENTRY(disable_ipv6, "disable_ipv6", 0644,
+			      sizeof(int), addrconf_sysctl_disable),
+	ADDRCONF_INT_ENTRY(accept_dad, "accept_dad"),
+	ADDRCONF_INT_ENTRY(force_tllao, "force_tllao"),
+	ADDRCONF_INT_ENTRY(ndisc_notify, "ndisc_notify"),
+	ADDRCONF_INT_ENTRY(suppress_frag_ndisc, "suppress_frag_ndisc"),
+	ADDRCONF_INT_ENTRY(accept_ra_from_local, "accept_ra_from_local"),
+	ADDRCONF_INT_ENTRY(accept_ra_mtu, "accept_ra_mtu"),
+	ADDRCONF_CUSTOM_ENTRY(stable_secret, "stable_secret", 0600,
+			      IPV6_MAX_STRLEN, addrconf_sysctl_stable_secret),
+	ADDRCONF_INT_ENTRY(use_oif_addrs_only, "use_oif_addrs_only"),
+	ADDRCONF_CUSTOM_ENTRY(ignore_routes_with_linkdown,
+			      "ignore_routes_with_linkdown", 0644, sizeof(int),
+			      addrconf_sysctl_ignore_routes_with_linkdown),
+	ADDRCONF_INT_ENTRY(drop_unicast_in_l2_multicast,
+			 "drop_unicast_in_l2_multicast"),
+	ADDRCONF_INT_ENTRY(drop_unsolicited_na, "drop_unsolicited_na"),
+	ADDRCONF_INT_ENTRY(keep_addr_on_down, "keep_addr_on_down"),
+	ADDRCONF_INT_ENTRY(seg6_enabled, "seg6_enabled"),
+#ifdef CONFIG_IPV6_SEG6_HMAC
+	ADDRCONF_INT_ENTRY(seg6_require_hmac, "seg6_require_hmac"),
+#endif
+	ADDRCONF_CUSTOM_ENTRY(enhanced_dad, "enhanced_dad", 0644,
+			      sizeof(int), proc_dointvec),
+	ADDRCONF_CUSTOM_ENTRY(addr_gen_mode, "addr_gen_mode", 0644,
+			      sizeof(int), addrconf_sysctl_addr_gen_mode),
+	ADDRCONF_CUSTOM_ENTRY(disable_policy, "disable_policy", 0644,
+			      sizeof(int), addrconf_sysctl_disable_policy),
+	ADDRCONF_INT_MINMAX_ENTRY(ndisc_tclass, "ndisc_tclass",
+				  SYSCTL_ZERO, &two_five_five),
+	ADDRCONF_INT_MINMAX_ENTRY(rpl_seg_enabled, "rpl_seg_enabled",
+				  SYSCTL_ZERO, SYSCTL_ONE),
+	ADDRCONF_U8_MINMAX_ENTRY(ioam6_enabled, "ioam6_enabled",
+				 SYSCTL_UINT_ZERO, SYSCTL_UINT_ONE),
+	ADDRCONF_UINT_MINMAX_ENTRY(ioam6_id, "ioam6_id",
+				   SYSCTL_UINT_ZERO, &ioam6_if_id_max),
+	ADDRCONF_UINT_ENTRY(ioam6_id_wide, "ioam6_id_wide"),
+	ADDRCONF_U8_MINMAX_ENTRY(ndisc_evict_nocarrier,
+				 "ndisc_evict_nocarrier", SYSCTL_UINT_ZERO,
+				 SYSCTL_UINT_ONE),
+	ADDRCONF_INT_MINMAX_ENTRY(accept_untracked_na, "accept_untracked_na",
+				  SYSCTL_ZERO, SYSCTL_TWO),
+	ADDRCONF_CUSTOM_ENTRY(force_forwarding, "force_forwarding", 0644,
+			      sizeof(int), addrconf_sysctl_force_forwarding),
 };
 
 static int __addrconf_sysctl_register(struct net *net, char *dev_name,
 		struct inet6_dev *idev, struct ipv6_devconf *p)
 {
-	size_t table_size = ARRAY_SIZE(addrconf_sysctl);
-	int i, ifindex;
-	struct ctl_table *table;
+	struct addrconf_ctl_context ctx = {
+		.context.ns.net_ns = net,
+		.devconf = p,
+	};
+	int ifindex;
 	char path[sizeof("net/ipv6/conf/") + IFNAMSIZ];
 
-	table = kmemdup(addrconf_sysctl, sizeof(addrconf_sysctl), GFP_KERNEL_ACCOUNT);
-	if (!table)
-		goto out;
-
-	for (i = 0; i < table_size; i++) {
-		table[i].data += (char *)p - (char *)&ipv6_devconf;
-		/* If one of these is already set, then it is not safe to
-		 * overwrite either of them: this makes proc_dointvec_minmax
-		 * usable.
-		 */
-		if (!table[i].extra1 && !table[i].extra2) {
-			table[i].extra1 = idev; /* embedded; no ref */
-			table[i].extra2 = net;
-		}
-	}
-
 	snprintf(path, sizeof(path), "net/ipv6/conf/%s", dev_name);
 
-	p->sysctl_header = register_net_sysctl_sz(net, path, table,
-						  table_size);
+	p->sysctl_header = register_sysctl_fields_ctx(&net->sysctls, path,
+						      addrconf_sysctl, &ctx);
 	if (!p->sysctl_header)
-		goto free;
+		goto out;
 
 	if (!strcmp(dev_name, "all"))
 		ifindex = NETCONFA_IFINDEX_ALL;
@@ -7347,8 +7111,6 @@ static int __addrconf_sysctl_register(struct net *net, char *dev_name,
 				     ifindex, p);
 	return 0;
 
-free:
-	kfree(table);
 out:
 	return -ENOBUFS;
 }
@@ -7356,15 +7118,11 @@ static int __addrconf_sysctl_register(struct net *net, char *dev_name,
 static void __addrconf_sysctl_unregister(struct net *net,
 					 struct ipv6_devconf *p, int ifindex)
 {
-	const struct ctl_table *table;
-
 	if (!p->sysctl_header)
 		return;
 
-	table = p->sysctl_header->ctl_table_arg;
 	unregister_net_sysctl_table(p->sysctl_header);
 	p->sysctl_header = NULL;
-	kfree(table);
 
 	inet6_netconf_notify_devconf(net, RTM_DELNETCONF, 0, ifindex, NULL);
 }
-- 
2.55.0


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [RFC PATCH v1 29/30] sysctl: net: use sysctl_field in neighbour sysctls
       [not found] <cover.1787771905.git.legion@kernel.org>
                   ` (27 preceding siblings ...)
  2026-08-26 19:42 ` [RFC PATCH v1 28/30] sysctl: net: use sysctl_field in IPv6 " Alexey Gladkov
@ 2026-08-26 19:42 ` Alexey Gladkov
  2026-08-26 19:42 ` [RFC PATCH v1 30/30] sysctl: parport: use sysctl_field for dynamic sysctls Alexey Gladkov
  29 siblings, 0 replies; 37+ messages in thread
From: Alexey Gladkov @ 2026-08-26 19:42 UTC (permalink / raw)
  To: Linus Torvalds, Eric W . Biederman, Kees Cook, Joel Granados
  Cc: LKML, linux-fsdevel

Neighbour sysctl registration clones the template table so it can
rewrite data pointers, attach the net device and neigh_parms through
extra1 and extra2, and override a few IPv6 NDISC handlers.

Use sysctl_field with a neigh_parms registration context instead. The
sysctl descriptors can stay static and const, while accessors provide
the same data, extra1 and extra2 values to the existing handlers.

Keep the NDISC handler selection local to neighbour sysctls by storing
the optional handler in neigh_parms and using fixed wrapper handlers for
the affected entries. This avoids cloning the sysctl table while
preserving the old IPv6 notification behavior.

Signed-off-by: Alexey Gladkov <legion@kernel.org>
---
 include/net/neighbour.h |   1 +
 net/core/neighbour.c    | 329 ++++++++++++++++++++++++----------------
 2 files changed, 202 insertions(+), 128 deletions(-)

diff --git a/include/net/neighbour.h b/include/net/neighbour.h
index 8860cc2175fc..73b6c7017264 100644
--- a/include/net/neighbour.h
+++ b/include/net/neighbour.h
@@ -78,6 +78,7 @@ struct neigh_parms {
 	struct neigh_table *tbl;
 
 	void	*sysctl_table;
+	proc_handler *sysctl_handler;
 
 	int dead;
 	refcount_t refcnt;
diff --git a/net/core/neighbour.c b/net/core/neighbour.c
index 5d9216016507..6eb74b0d59b2 100644
--- a/net/core/neighbour.c
+++ b/net/core/neighbour.c
@@ -1779,6 +1779,7 @@ struct neigh_parms *neigh_parms_alloc(struct net_device *dev,
 		p->dev = dev;
 		write_pnet(&p->net, net);
 		p->sysctl_table = NULL;
+		p->sysctl_handler = NULL;
 
 		if (ops->ndo_neigh_setup && ops->ndo_neigh_setup(dev, p)) {
 			netdev_put(dev, &p->dev_tracker);
@@ -3762,148 +3763,220 @@ static int neigh_proc_base_reachable_time(const struct ctl_table *ctl, int write
 	return ret;
 }
 
-#define NEIGH_PARMS_DATA_OFFSET(index)	\
-	(&((struct neigh_parms *) 0)->data[index])
+struct neigh_ctl_context {
+	struct sysctl_context context;
+	struct neigh_parms *parms;
+};
+
+#define NEIGH_PARMS_DATA(attr, data_attr)				\
+static void *neigh_ ## attr ## _data(const struct sysctl_context *ctx)	\
+{									\
+	const struct neigh_ctl_context *neigh_ctx =			\
+		container_of(ctx, struct neigh_ctl_context, context);	\
+	return &neigh_ctx->parms->data[NEIGH_VAR_ ## data_attr];	\
+}
+
+#define NEIGH_TABLE_DATA(type, attr, field)				\
+static type *neigh_ ## attr ## _data(const struct sysctl_context *ctx)	\
+{									\
+	const struct neigh_ctl_context *neigh_ctx =			\
+		container_of(ctx, struct neigh_ctl_context, context);	\
+	return &neigh_ctx->parms->tbl->field;				\
+}
+
+static void *neigh_dev_data(const struct sysctl_context *ctx)
+{
+	const struct neigh_ctl_context *neigh_ctx =
+		container_of(ctx, struct neigh_ctl_context, context);
+
+	return neigh_ctx->parms->dev;
+}
+
+static void *neigh_parms_data(const struct sysctl_context *ctx)
+{
+	const struct neigh_ctl_context *neigh_ctx =
+		container_of(ctx, struct neigh_ctl_context, context);
+
+	return neigh_ctx->parms;
+}
+
+static void *neigh_handler_data(const struct sysctl_context *ctx)
+{
+	const struct neigh_ctl_context *neigh_ctx =
+		container_of(ctx, struct neigh_ctl_context, context);
+
+	return neigh_ctx->parms->sysctl_handler;
+}
+
+static int neigh_proc_call_handler(const struct ctl_table *ctl, int write,
+				   void *buffer, size_t *lenp, loff_t *ppos,
+				   proc_handler *fallback)
+{
+	proc_handler *handler = ctl->extra1;
+
+	if (handler) {
+		struct neigh_parms *p = ctl->extra2;
+		struct ctl_table tmp = *ctl;
+
+		tmp.extra1 = p->dev;
+		tmp.extra2 = p;
+		return handler(&tmp, write, buffer, lenp, ppos);
+	}
+
+	return fallback(ctl, write, buffer, lenp, ppos);
+}
 
-#define NEIGH_SYSCTL_ENTRY(attr, data_attr, name, mval, proc) \
-	[NEIGH_VAR_ ## attr] = { \
-		.procname	= name, \
-		.data		= NEIGH_PARMS_DATA_OFFSET(NEIGH_VAR_ ## data_attr), \
-		.maxlen		= sizeof(int), \
-		.mode		= mval, \
-		.proc_handler	= proc, \
+static int neigh_proc_retrans_time(const struct ctl_table *ctl, int write,
+				   void *buffer, size_t *lenp, loff_t *ppos)
+{
+	return neigh_proc_call_handler(ctl, write, buffer, lenp, ppos,
+				       neigh_proc_dointvec_userhz_jiffies);
+}
+
+static int neigh_proc_retrans_time_ms(const struct ctl_table *ctl, int write,
+				      void *buffer, size_t *lenp, loff_t *ppos)
+{
+	return neigh_proc_call_handler(ctl, write, buffer, lenp, ppos,
+				       neigh_proc_dointvec_ms_jiffies);
+}
+
+static int neigh_proc_base_reachable_time_handler(const struct ctl_table *ctl,
+						  int write, void *buffer,
+						  size_t *lenp, loff_t *ppos)
+{
+	return neigh_proc_call_handler(ctl, write, buffer, lenp, ppos,
+				       neigh_proc_base_reachable_time);
+}
+
+#define NEIGH_SYSCTL_ENTRY(attr, name, proc)				\
+	[NEIGH_VAR_ ## attr] = {					\
+		.procname	= name,					\
+		.mode		= 0644,					\
+		.type		= SYSCTL_FIELD_CUSTOM,			\
+		.ctl_custom	= {					\
+			.proc_handler	= proc,				\
+			.data		= neigh_ ## attr ## _data,	\
+			.extra1		= neigh_dev_data,		\
+			.extra2		= neigh_parms_data,		\
+			.maxlen		= sizeof(int),			\
+		},							\
+	}
+
+#define NEIGH_SYSCTL_HANDLER_ENTRY(attr, name, proc)			\
+	[NEIGH_VAR_ ## attr] = {					\
+		.procname	= name,					\
+		.mode		= 0644,					\
+		.type		= SYSCTL_FIELD_CUSTOM,			\
+		.ctl_custom	= {					\
+			.proc_handler	= proc,				\
+			.data		= neigh_ ## attr ## _data,	\
+			.extra1		= neigh_handler_data,		\
+			.extra2		= neigh_parms_data,		\
+			.maxlen		= sizeof(int),			\
+		},							\
 	}
 
 #define NEIGH_SYSCTL_ZERO_INTMAX_ENTRY(attr, name) \
-	NEIGH_SYSCTL_ENTRY(attr, attr, name, 0644, neigh_proc_dointvec_zero_intmax)
+	NEIGH_SYSCTL_ENTRY(attr, name, neigh_proc_dointvec_zero_intmax)
 
 #define NEIGH_SYSCTL_JIFFIES_ENTRY(attr, name) \
-	NEIGH_SYSCTL_ENTRY(attr, attr, name, 0644, neigh_proc_dointvec_jiffies)
+	NEIGH_SYSCTL_ENTRY(attr, name, neigh_proc_dointvec_jiffies)
 
 #define NEIGH_SYSCTL_USERHZ_JIFFIES_ENTRY(attr, name) \
-	NEIGH_SYSCTL_ENTRY(attr, attr, name, 0644, neigh_proc_dointvec_userhz_jiffies)
+	NEIGH_SYSCTL_ENTRY(attr, name, neigh_proc_dointvec_userhz_jiffies)
 
 #define NEIGH_SYSCTL_MS_JIFFIES_POSITIVE_ENTRY(attr, name) \
-	NEIGH_SYSCTL_ENTRY(attr, attr, name, 0644, neigh_proc_dointvec_ms_jiffies_positive)
-
-#define NEIGH_SYSCTL_MS_JIFFIES_REUSED_ENTRY(attr, data_attr, name) \
-	NEIGH_SYSCTL_ENTRY(attr, data_attr, name, 0644, neigh_proc_dointvec_ms_jiffies)
-
-#define NEIGH_SYSCTL_UNRES_QLEN_REUSED_ENTRY(attr, data_attr, name) \
-	NEIGH_SYSCTL_ENTRY(attr, data_attr, name, 0644, neigh_proc_dointvec_unres_qlen)
-
-static struct neigh_sysctl_table {
-	struct ctl_table_header *sysctl_header;
-	struct ctl_table neigh_vars[NEIGH_VAR_MAX];
-} neigh_sysctl_template __read_mostly = {
-	.neigh_vars = {
-		NEIGH_SYSCTL_ZERO_INTMAX_ENTRY(MCAST_PROBES, "mcast_solicit"),
-		NEIGH_SYSCTL_ZERO_INTMAX_ENTRY(UCAST_PROBES, "ucast_solicit"),
-		NEIGH_SYSCTL_ZERO_INTMAX_ENTRY(APP_PROBES, "app_solicit"),
-		NEIGH_SYSCTL_ZERO_INTMAX_ENTRY(MCAST_REPROBES, "mcast_resolicit"),
-		NEIGH_SYSCTL_USERHZ_JIFFIES_ENTRY(RETRANS_TIME, "retrans_time"),
-		NEIGH_SYSCTL_JIFFIES_ENTRY(BASE_REACHABLE_TIME, "base_reachable_time"),
-		NEIGH_SYSCTL_JIFFIES_ENTRY(DELAY_PROBE_TIME, "delay_first_probe_time"),
-		NEIGH_SYSCTL_MS_JIFFIES_POSITIVE_ENTRY(INTERVAL_PROBE_TIME_MS,
-						       "interval_probe_time_ms"),
-		NEIGH_SYSCTL_JIFFIES_ENTRY(GC_STALETIME, "gc_stale_time"),
-		NEIGH_SYSCTL_ZERO_INTMAX_ENTRY(QUEUE_LEN_BYTES, "unres_qlen_bytes"),
-		NEIGH_SYSCTL_ZERO_INTMAX_ENTRY(PROXY_QLEN, "proxy_qlen"),
-		NEIGH_SYSCTL_USERHZ_JIFFIES_ENTRY(ANYCAST_DELAY, "anycast_delay"),
-		NEIGH_SYSCTL_USERHZ_JIFFIES_ENTRY(PROXY_DELAY, "proxy_delay"),
-		NEIGH_SYSCTL_USERHZ_JIFFIES_ENTRY(LOCKTIME, "locktime"),
-		NEIGH_SYSCTL_UNRES_QLEN_REUSED_ENTRY(QUEUE_LEN, QUEUE_LEN_BYTES, "unres_qlen"),
-		NEIGH_SYSCTL_MS_JIFFIES_REUSED_ENTRY(RETRANS_TIME_MS, RETRANS_TIME, "retrans_time_ms"),
-		NEIGH_SYSCTL_MS_JIFFIES_REUSED_ENTRY(BASE_REACHABLE_TIME_MS, BASE_REACHABLE_TIME, "base_reachable_time_ms"),
-		[NEIGH_VAR_GC_INTERVAL] = {
-			.procname	= "gc_interval",
-			.maxlen		= sizeof(int),
-			.mode		= 0644,
-			.proc_handler	= proc_dointvec_jiffies,
-		},
-		[NEIGH_VAR_GC_THRESH1] = {
-			.procname	= "gc_thresh1",
-			.maxlen		= sizeof(int),
-			.mode		= 0644,
-			.extra1		= SYSCTL_ZERO,
-			.extra2		= SYSCTL_INT_MAX,
-			.proc_handler	= proc_dointvec_minmax,
-		},
-		[NEIGH_VAR_GC_THRESH2] = {
-			.procname	= "gc_thresh2",
-			.maxlen		= sizeof(int),
-			.mode		= 0644,
-			.extra1		= SYSCTL_ZERO,
-			.extra2		= SYSCTL_INT_MAX,
-			.proc_handler	= proc_dointvec_minmax,
-		},
-		[NEIGH_VAR_GC_THRESH3] = {
-			.procname	= "gc_thresh3",
-			.maxlen		= sizeof(int),
-			.mode		= 0644,
-			.extra1		= SYSCTL_ZERO,
-			.extra2		= SYSCTL_INT_MAX,
-			.proc_handler	= proc_dointvec_minmax,
-		},
-	},
+	NEIGH_SYSCTL_ENTRY(attr, name, neigh_proc_dointvec_ms_jiffies_positive)
+
+#define NEIGH_SYSCTL_MS_JIFFIES_ENTRY(attr, name) \
+	NEIGH_SYSCTL_ENTRY(attr, name, neigh_proc_dointvec_ms_jiffies)
+
+#define NEIGH_SYSCTL_UNRES_QLEN_ENTRY(attr, name) \
+	NEIGH_SYSCTL_ENTRY(attr, name, neigh_proc_dointvec_unres_qlen)
+
+NEIGH_PARMS_DATA(MCAST_PROBES, MCAST_PROBES)
+NEIGH_PARMS_DATA(UCAST_PROBES, UCAST_PROBES)
+NEIGH_PARMS_DATA(APP_PROBES, APP_PROBES)
+NEIGH_PARMS_DATA(MCAST_REPROBES, MCAST_REPROBES)
+NEIGH_PARMS_DATA(RETRANS_TIME, RETRANS_TIME)
+NEIGH_PARMS_DATA(BASE_REACHABLE_TIME, BASE_REACHABLE_TIME)
+NEIGH_PARMS_DATA(DELAY_PROBE_TIME, DELAY_PROBE_TIME)
+NEIGH_PARMS_DATA(INTERVAL_PROBE_TIME_MS, INTERVAL_PROBE_TIME_MS)
+NEIGH_PARMS_DATA(GC_STALETIME, GC_STALETIME)
+NEIGH_PARMS_DATA(QUEUE_LEN_BYTES, QUEUE_LEN_BYTES)
+NEIGH_PARMS_DATA(PROXY_QLEN, PROXY_QLEN)
+NEIGH_PARMS_DATA(ANYCAST_DELAY, ANYCAST_DELAY)
+NEIGH_PARMS_DATA(PROXY_DELAY, PROXY_DELAY)
+NEIGH_PARMS_DATA(LOCKTIME, LOCKTIME)
+NEIGH_PARMS_DATA(QUEUE_LEN, QUEUE_LEN_BYTES)
+NEIGH_PARMS_DATA(RETRANS_TIME_MS, RETRANS_TIME)
+NEIGH_PARMS_DATA(BASE_REACHABLE_TIME_MS, BASE_REACHABLE_TIME)
+NEIGH_TABLE_DATA(void, GC_INTERVAL, gc_interval)
+NEIGH_TABLE_DATA(int, GC_THRESH1, gc_thresh1)
+NEIGH_TABLE_DATA(int, GC_THRESH2, gc_thresh2)
+NEIGH_TABLE_DATA(int, GC_THRESH3, gc_thresh3)
+
+static const struct sysctl_field neigh_sysctl_table[] = {
+	NEIGH_SYSCTL_ZERO_INTMAX_ENTRY(MCAST_PROBES, "mcast_solicit"),
+	NEIGH_SYSCTL_ZERO_INTMAX_ENTRY(UCAST_PROBES, "ucast_solicit"),
+	NEIGH_SYSCTL_ZERO_INTMAX_ENTRY(APP_PROBES, "app_solicit"),
+	NEIGH_SYSCTL_ZERO_INTMAX_ENTRY(MCAST_REPROBES, "mcast_resolicit"),
+	NEIGH_SYSCTL_HANDLER_ENTRY(RETRANS_TIME, "retrans_time",
+				   neigh_proc_retrans_time),
+	NEIGH_SYSCTL_HANDLER_ENTRY(BASE_REACHABLE_TIME, "base_reachable_time",
+				   neigh_proc_base_reachable_time_handler),
+	NEIGH_SYSCTL_JIFFIES_ENTRY(DELAY_PROBE_TIME, "delay_first_probe_time"),
+	NEIGH_SYSCTL_MS_JIFFIES_POSITIVE_ENTRY(INTERVAL_PROBE_TIME_MS,
+					       "interval_probe_time_ms"),
+	NEIGH_SYSCTL_JIFFIES_ENTRY(GC_STALETIME, "gc_stale_time"),
+	NEIGH_SYSCTL_ZERO_INTMAX_ENTRY(QUEUE_LEN_BYTES, "unres_qlen_bytes"),
+	NEIGH_SYSCTL_ZERO_INTMAX_ENTRY(PROXY_QLEN, "proxy_qlen"),
+	NEIGH_SYSCTL_USERHZ_JIFFIES_ENTRY(ANYCAST_DELAY, "anycast_delay"),
+	NEIGH_SYSCTL_USERHZ_JIFFIES_ENTRY(PROXY_DELAY, "proxy_delay"),
+	NEIGH_SYSCTL_USERHZ_JIFFIES_ENTRY(LOCKTIME, "locktime"),
+	NEIGH_SYSCTL_UNRES_QLEN_ENTRY(QUEUE_LEN, "unres_qlen"),
+	NEIGH_SYSCTL_HANDLER_ENTRY(RETRANS_TIME_MS, "retrans_time_ms",
+				   neigh_proc_retrans_time_ms),
+	NEIGH_SYSCTL_HANDLER_ENTRY(BASE_REACHABLE_TIME_MS,
+				   "base_reachable_time_ms",
+				   neigh_proc_base_reachable_time_handler),
+	SYSCTL_FIELD_CUSTOM("gc_interval", 0644, sizeof(int),
+			 neigh_GC_INTERVAL_data, proc_dointvec_jiffies),
+	SYSCTL_FIELD_STATIC_INT_MINMAX("gc_thresh1", 0644,
+				    neigh_GC_THRESH1_data,
+				    SYSCTL_ZERO, SYSCTL_INT_MAX),
+	SYSCTL_FIELD_STATIC_INT_MINMAX("gc_thresh2", 0644,
+				    neigh_GC_THRESH2_data,
+				    SYSCTL_ZERO, SYSCTL_INT_MAX),
+	SYSCTL_FIELD_STATIC_INT_MINMAX("gc_thresh3", 0644,
+				    neigh_GC_THRESH3_data,
+				    SYSCTL_ZERO, SYSCTL_INT_MAX),
 };
 
+#define NEIGH_SYSCTL_DEFAULT_COUNT	4
+
 int neigh_sysctl_register(struct net_device *dev, struct neigh_parms *p,
 			  proc_handler *handler)
 {
-	int i;
-	struct neigh_sysctl_table *t;
+	struct ctl_table_header *hdr;
+	struct net *net = neigh_parms_net(p);
+	struct neigh_ctl_context ctx = {
+		.context.ns.net_ns = net,
+		.parms = p,
+	};
 	const char *dev_name_source;
 	char neigh_path[ sizeof("net//neigh/") + IFNAMSIZ + IFNAMSIZ ];
 	char *p_name;
 	size_t neigh_vars_size;
 
-	t = kmemdup(&neigh_sysctl_template, sizeof(*t), GFP_KERNEL_ACCOUNT);
-	if (!t)
-		goto err;
-
-	for (i = 0; i < NEIGH_VAR_GC_INTERVAL; i++) {
-		t->neigh_vars[i].data += (long) p;
-		t->neigh_vars[i].extra1 = dev;
-		t->neigh_vars[i].extra2 = p;
-	}
+	p->sysctl_handler = handler;
 
-	neigh_vars_size = ARRAY_SIZE(t->neigh_vars);
+	neigh_vars_size = ARRAY_SIZE(neigh_sysctl_table);
 	if (dev) {
 		dev_name_source = dev->name;
-		/* Terminate the table early */
-		neigh_vars_size = NEIGH_VAR_BASE_REACHABLE_TIME_MS + 1;
+		neigh_vars_size -= NEIGH_SYSCTL_DEFAULT_COUNT;
 	} else {
-		struct neigh_table *tbl = p->tbl;
 		dev_name_source = "default";
-		t->neigh_vars[NEIGH_VAR_GC_INTERVAL].data = &tbl->gc_interval;
-		t->neigh_vars[NEIGH_VAR_GC_THRESH1].data = &tbl->gc_thresh1;
-		t->neigh_vars[NEIGH_VAR_GC_THRESH2].data = &tbl->gc_thresh2;
-		t->neigh_vars[NEIGH_VAR_GC_THRESH3].data = &tbl->gc_thresh3;
-	}
-
-	if (handler) {
-		/* RetransTime */
-		t->neigh_vars[NEIGH_VAR_RETRANS_TIME].proc_handler = handler;
-		/* ReachableTime */
-		t->neigh_vars[NEIGH_VAR_BASE_REACHABLE_TIME].proc_handler = handler;
-		/* RetransTime (in milliseconds)*/
-		t->neigh_vars[NEIGH_VAR_RETRANS_TIME_MS].proc_handler = handler;
-		/* ReachableTime (in milliseconds) */
-		t->neigh_vars[NEIGH_VAR_BASE_REACHABLE_TIME_MS].proc_handler = handler;
-	} else {
-		/* Those handlers will update p->reachable_time after
-		 * base_reachable_time(_ms) is set to ensure the new timer starts being
-		 * applied after the next neighbour update instead of waiting for
-		 * neigh_periodic_work to update its value (can be multiple minutes)
-		 * So any handler that replaces them should do this as well
-		 */
-		/* ReachableTime */
-		t->neigh_vars[NEIGH_VAR_BASE_REACHABLE_TIME].proc_handler =
-			neigh_proc_base_reachable_time;
-		/* ReachableTime (in milliseconds) */
-		t->neigh_vars[NEIGH_VAR_BASE_REACHABLE_TIME_MS].proc_handler =
-			neigh_proc_base_reachable_time;
 	}
 
 	switch (neigh_parms_family(p)) {
@@ -3919,18 +3992,17 @@ int neigh_sysctl_register(struct net_device *dev, struct neigh_parms *p,
 
 	snprintf(neigh_path, sizeof(neigh_path), "net/%s/neigh/%s",
 		p_name, dev_name_source);
-	t->sysctl_header = register_net_sysctl_sz(neigh_parms_net(p),
-						  neigh_path, t->neigh_vars,
-						  neigh_vars_size);
-	if (!t->sysctl_header)
-		goto free;
+	hdr = __register_sysctl_fields(&net->sysctls, neigh_path,
+				       neigh_sysctl_table, neigh_vars_size,
+				       &ctx.context, sizeof(ctx));
+	if (!hdr)
+		goto err;
 
-	p->sysctl_table = t;
+	p->sysctl_table = hdr;
 	return 0;
 
-free:
-	kfree(t);
 err:
+	p->sysctl_handler = NULL;
 	return -ENOBUFS;
 }
 EXPORT_SYMBOL(neigh_sysctl_register);
@@ -3938,10 +4010,11 @@ EXPORT_SYMBOL(neigh_sysctl_register);
 void neigh_sysctl_unregister(struct neigh_parms *p)
 {
 	if (p->sysctl_table) {
-		struct neigh_sysctl_table *t = p->sysctl_table;
+		struct ctl_table_header *hdr = p->sysctl_table;
+
 		p->sysctl_table = NULL;
-		unregister_net_sysctl_table(t->sysctl_header);
-		kfree(t);
+		p->sysctl_handler = NULL;
+		unregister_net_sysctl_table(hdr);
 	}
 }
 EXPORT_SYMBOL(neigh_sysctl_unregister);
-- 
2.55.0


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [RFC PATCH v1 30/30] sysctl: parport: use sysctl_field for dynamic sysctls
       [not found] <cover.1787771905.git.legion@kernel.org>
                   ` (28 preceding siblings ...)
  2026-08-26 19:42 ` [RFC PATCH v1 29/30] sysctl: net: use sysctl_field in neighbour sysctls Alexey Gladkov
@ 2026-08-26 19:42 ` Alexey Gladkov
  29 siblings, 0 replies; 37+ messages in thread
From: Alexey Gladkov @ 2026-08-26 19:42 UTC (permalink / raw)
  To: Linus Torvalds, Eric W . Biederman, Kees Cook, Joel Granados
  Cc: LKML, linux-fsdevel

parport builds per-port and per-device sysctl tables by cloning template
ctl_table arrays and then patching the data and extra pointers at fixed
indexes. That keeps the static table definitions writable in practice
and makes the registration path depend on the exact order of the table
entries.

Use sysctl_field for those tables instead. The per-object state is
supplied through the registration context, so the table layout can stay
static and the data pointers are resolved when proc_sysctl builds the
effective ctl_table. This removes the need to duplicate the tables for
every parport object and keeps the per-entry state binding next to the
entry definition.

Signed-off-by: Alexey Gladkov <legion@kernel.org>
---
 drivers/parport/procfs.c | 317 ++++++++++++++++++---------------------
 fs/proc/proc_sysctl.c    |  11 ++
 include/linux/parport.h  |   6 +-
 include/linux/sysctl.h   |  12 ++
 4 files changed, 171 insertions(+), 175 deletions(-)

diff --git a/drivers/parport/procfs.c b/drivers/parport/procfs.c
index 3880460e67f2..6348d1342069 100644
--- a/drivers/parport/procfs.c
+++ b/drivers/parport/procfs.c
@@ -236,147 +236,134 @@ do {									\
 	return 0;
 }
 
-static const unsigned long parport_min_timeslice_value =
+static unsigned long parport_min_timeslice_value =
 PARPORT_MIN_TIMESLICE_VALUE;
 
-static const unsigned long parport_max_timeslice_value =
+static unsigned long parport_max_timeslice_value =
 PARPORT_MAX_TIMESLICE_VALUE;
 
-static const  int parport_min_spintime_value =
+static int parport_min_spintime_value =
 PARPORT_MIN_SPINTIME_VALUE;
 
-static const int parport_max_spintime_value =
+static int parport_max_spintime_value =
 PARPORT_MAX_SPINTIME_VALUE;
 
+struct parport_ctl_context {
+	struct sysctl_context context;
+	struct parport *port;
+};
 
-struct parport_sysctl_table {
-	struct ctl_table_header *port_header;
-	struct ctl_table_header *devices_header;
-#ifdef CONFIG_PARPORT_1284
-	struct ctl_table vars[10];
-#else
-	struct ctl_table vars[5];
-#endif /* IEEE 1284 support */
-	struct ctl_table device_dir[1];
+struct pardevice_ctl_context {
+	struct sysctl_context context;
+	struct pardevice *device;
 };
 
-static const struct parport_sysctl_table parport_sysctl_template = {
-	.port_header = NULL,
-	.devices_header = NULL,
-	{
-		{
-			.procname	= "spintime",
-			.data		= NULL,
-			.maxlen		= sizeof(int),
-			.mode		= 0644,
-			.proc_handler	= proc_dointvec_minmax,
-			.extra1		= (void*) &parport_min_spintime_value,
-			.extra2		= (void*) &parport_max_spintime_value
-		},
-		{
-			.procname	= "base-addr",
-			.data		= NULL,
-			.maxlen		= 0,
-			.mode		= 0444,
-			.proc_handler	= do_hardware_base_addr
-		},
-		{
-			.procname	= "irq",
-			.data		= NULL,
-			.maxlen		= 0,
-			.mode		= 0444,
-			.proc_handler	= do_hardware_irq
-		},
-		{
-			.procname	= "dma",
-			.data		= NULL,
-			.maxlen		= 0,
-			.mode		= 0444,
-			.proc_handler	= do_hardware_dma
-		},
-		{
-			.procname	= "modes",
-			.data		= NULL,
-			.maxlen		= 0,
-			.mode		= 0444,
-			.proc_handler	= do_hardware_modes
-		},
+static int *parport_spintime_data(const struct sysctl_context *ctx)
+{
+	const struct parport_ctl_context *parport_ctx =
+		container_of(ctx, struct parport_ctl_context, context);
+
+	return &parport_ctx->port->spintime;
+}
+
+static void *pardevice_timeslice_data(const struct sysctl_context *ctx)
+{
+	const struct pardevice_ctl_context *pardevice_ctx =
+		container_of(ctx, struct pardevice_ctl_context, context);
+
+	return &pardevice_ctx->device->timeslice;
+}
+
+static void *parport_data(const struct sysctl_context *ctx)
+{
+	const struct parport_ctl_context *parport_ctx =
+		container_of(ctx, struct parport_ctl_context, context);
+
+	return parport_ctx->port;
+}
+
+static void *parport_min_timeslice_data(const struct sysctl_context *ctx)
+{
+	return &parport_min_timeslice_value;
+}
+
+static void *parport_max_timeslice_data(const struct sysctl_context *ctx)
+{
+	return &parport_max_timeslice_value;
+}
+
 #ifdef CONFIG_PARPORT_1284
-		{
-			.procname	= "autoprobe",
-			.data		= NULL,
-			.maxlen		= 0,
-			.mode		= 0444,
-			.proc_handler	= do_autoprobe
-		},
-		{
-			.procname	= "autoprobe0",
-			.data		= NULL,
-			.maxlen		= 0,
-			.mode		= 0444,
-			.proc_handler	= do_autoprobe
-		},
-		{
-			.procname	= "autoprobe1",
-			.data		= NULL,
-			.maxlen		= 0,
-			.mode		= 0444,
-			.proc_handler	= do_autoprobe
-		},
-		{
-			.procname	= "autoprobe2",
-			.data		= NULL,
-			.maxlen		= 0,
-			.mode		= 0444,
-			.proc_handler	= do_autoprobe
-		},
-		{
-			.procname	= "autoprobe3",
-			.data		= NULL,
-			.maxlen		= 0,
-			.mode		= 0444,
-			.proc_handler	= do_autoprobe
-		},
-#endif /* IEEE 1284 support */
-	},
-	{
-		{
-			.procname	= "active",
-			.data		= NULL,
-			.maxlen		= 0,
-			.mode		= 0444,
-			.proc_handler	= do_active_device
-		},
-	},
+#define PARPORT_PROBE_DATA(index)							\
+static void *parport_probe_info_ ## index ## _data(const struct sysctl_context *ctx)	\
+{											\
+	const struct parport_ctl_context *parport_ctx =					\
+		container_of(ctx, struct parport_ctl_context, context);			\
+	return &parport_ctx->port->probe_info[index];					\
+}
+
+PARPORT_PROBE_DATA(0)
+PARPORT_PROBE_DATA(1)
+PARPORT_PROBE_DATA(2)
+PARPORT_PROBE_DATA(3)
+PARPORT_PROBE_DATA(4)
+#endif
+
+#define PARPORT_PORT_ENTRY(name, proc)					\
+	{								\
+		.procname	= name,					\
+		.mode		= 0444,					\
+		.type		= SYSCTL_FIELD_CUSTOM,			\
+		.ctl_custom	= {					\
+			.proc_handler	= proc,				\
+			.extra1		= parport_data,			\
+			.maxlen		= 0,				\
+		},							\
+	}
+
+#define PARPORT_PROBE_ENTRY(name, index)				\
+	{								\
+		.procname	= name,					\
+		.mode		= 0444,					\
+		.type		= SYSCTL_FIELD_CUSTOM,			\
+		.ctl_custom	= {					\
+			.proc_handler	= do_autoprobe,			\
+			.extra2		= parport_probe_info_ ## index ## _data, \
+			.maxlen		= 0,				\
+		},							\
+	}
+
+static const struct sysctl_field parport_sysctl_table[] = {
+	SYSCTL_FIELD_STATIC_INT_MINMAX("spintime", 0644, parport_spintime_data,
+				       &parport_min_spintime_value,
+				       &parport_max_spintime_value),
+	PARPORT_PORT_ENTRY("base-addr", do_hardware_base_addr),
+	PARPORT_PORT_ENTRY("irq", do_hardware_irq),
+	PARPORT_PORT_ENTRY("dma", do_hardware_dma),
+	PARPORT_PORT_ENTRY("modes", do_hardware_modes),
+#ifdef CONFIG_PARPORT_1284
+	PARPORT_PROBE_ENTRY("autoprobe", 0),
+	PARPORT_PROBE_ENTRY("autoprobe0", 1),
+	PARPORT_PROBE_ENTRY("autoprobe1", 2),
+	PARPORT_PROBE_ENTRY("autoprobe2", 3),
+	PARPORT_PROBE_ENTRY("autoprobe3", 4),
+#endif
 };
 
-struct parport_device_sysctl_table
-{
-	struct ctl_table_header *sysctl_header;
-	struct ctl_table vars[1];
-	struct ctl_table device_dir[1];
+static const struct sysctl_field parport_device_dir_table[] = {
+	PARPORT_PORT_ENTRY("active", do_active_device),
 };
 
-static const struct parport_device_sysctl_table
-parport_device_sysctl_template = {
-	.sysctl_header = NULL,
+static const struct sysctl_field parport_device_sysctl_table[] = {
 	{
-		{
-			.procname 	= "timeslice",
-			.data		= NULL,
-			.maxlen		= sizeof(unsigned long),
-			.mode		= 0644,
+		.procname	= "timeslice",
+		.mode		= 0644,
+		.type		= SYSCTL_FIELD_CUSTOM,
+		.ctl_custom	= {
 			.proc_handler	= proc_doulongvec_ms_jiffies_minmax,
-			.extra1		= (void*) &parport_min_timeslice_value,
-			.extra2		= (void*) &parport_max_timeslice_value
-		},
-	},
-	{
-		{
-			.procname	= NULL,
-			.data		= NULL,
-			.maxlen		= 0,
-			.mode		= 0555,
+			.data		= pardevice_timeslice_data,
+			.extra1		= parport_min_timeslice_data,
+			.extra2		= parport_max_timeslice_data,
+			.maxlen		= sizeof(unsigned long),
 		},
 	}
 };
@@ -414,35 +401,27 @@ parport_default_sysctl_table = {
 
 int parport_proc_register(struct parport *port)
 {
-	struct parport_sysctl_table *t;
+	struct parport_ctl_context ctx = {
+		.port = port,
+	};
+	struct ctl_table_header *hdr;
 	char *tmp_dir_path;
-	int i, err = 0;
-
-	t = kmemdup(&parport_sysctl_template, sizeof(*t), GFP_KERNEL);
-	if (t == NULL)
-		return -ENOMEM;
-
-	t->device_dir[0].extra1 = port;
-
-	t->vars[0].data = &port->spintime;
-	for (i = 0; i < 5; i++) {
-		t->vars[i].extra1 = port;
-#ifdef CONFIG_PARPORT_1284
-		t->vars[5 + i].extra2 = &port->probe_info[i];
-#endif /* IEEE 1284 support */
-	}
+	int err = 0;
 
 	tmp_dir_path = kasprintf(GFP_KERNEL, "dev/parport/%s/devices", port->name);
 	if (!tmp_dir_path) {
 		err = -ENOMEM;
-		goto exit_free_t;
+		goto out;
 	}
 
-	t->devices_header = register_sysctl(tmp_dir_path, t->device_dir);
-	if (t->devices_header == NULL) {
+	hdr = register_sysctl_fields_sz(tmp_dir_path, parport_device_dir_table,
+					ARRAY_SIZE(parport_device_dir_table),
+					&ctx.context, sizeof(ctx));
+	if (hdr == NULL) {
 		err = -ENOENT;
 		goto  exit_free_tmp_dir_path;
 	}
+	port->sysctl_devices_header = hdr;
 
 	kfree(tmp_dir_path);
 
@@ -452,83 +431,75 @@ int parport_proc_register(struct parport *port)
 		goto unregister_devices_h;
 	}
 
-	t->port_header = register_sysctl(tmp_dir_path, t->vars);
-	if (t->port_header == NULL) {
+	hdr = register_sysctl_fields_sz(tmp_dir_path, parport_sysctl_table,
+					ARRAY_SIZE(parport_sysctl_table),
+					&ctx.context, sizeof(ctx));
+	if (hdr == NULL) {
 		err = -ENOENT;
 		goto unregister_devices_h;
 	}
-
-	port->sysctl_table = t;
+	port->sysctl_table = hdr;
 
 	kfree(tmp_dir_path);
 	return 0;
 
 unregister_devices_h:
-	unregister_sysctl_table(t->devices_header);
+	unregister_sysctl_table(port->sysctl_devices_header);
+	port->sysctl_devices_header = NULL;
 
 exit_free_tmp_dir_path:
 	kfree(tmp_dir_path);
 
-exit_free_t:
-	kfree(t);
+out:
 	return err;
 }
 
 int parport_proc_unregister(struct parport *port)
 {
+	if (port->sysctl_devices_header) {
+		unregister_sysctl_table(port->sysctl_devices_header);
+		port->sysctl_devices_header = NULL;
+	}
 	if (port->sysctl_table) {
-		struct parport_sysctl_table *t = port->sysctl_table;
+		unregister_sysctl_table(port->sysctl_table);
 		port->sysctl_table = NULL;
-		unregister_sysctl_table(t->devices_header);
-		unregister_sysctl_table(t->port_header);
-		kfree(t);
 	}
 	return 0;
 }
 
 int parport_device_proc_register(struct pardevice *device)
 {
-	struct parport_device_sysctl_table *t;
+	struct pardevice_ctl_context ctx = {
+		.device = device,
+	};
 	struct parport * port = device->port;
 	char *tmp_dir_path;
 	int err = 0;
-	
-	t = kmemdup(&parport_device_sysctl_template, sizeof(*t), GFP_KERNEL);
-	if (t == NULL)
-		return -ENOMEM;
 
 	/* Allocate a buffer for two paths: dev/parport/PORT/devices/DEVICE. */
 	tmp_dir_path = kasprintf(GFP_KERNEL, "dev/parport/%s/devices/%s", port->name, device->name);
 	if (!tmp_dir_path) {
 		err = -ENOMEM;
-		goto exit_free_t;
+		goto out;
 	}
 
-	t->vars[0].data = &device->timeslice;
-
-	t->sysctl_header = register_sysctl(tmp_dir_path, t->vars);
-	if (t->sysctl_header == NULL) {
-		kfree(t);
-		t = NULL;
-	}
-	device->sysctl_table = t;
+	device->sysctl_table =
+		register_sysctl_fields_sz(tmp_dir_path, parport_device_sysctl_table,
+				       ARRAY_SIZE(parport_device_sysctl_table),
+				       &ctx.context, sizeof(ctx));
 
 	kfree(tmp_dir_path);
 	return 0;
 
-exit_free_t:
-	kfree(t);
-
+out:
 	return err;
 }
 
 int parport_device_proc_unregister(struct pardevice *device)
 {
 	if (device->sysctl_table) {
-		struct parport_device_sysctl_table *t = device->sysctl_table;
+		unregister_sysctl_table(device->sysctl_table);
 		device->sysctl_table = NULL;
-		unregister_sysctl_table(t->sysctl_header);
-		kfree(t);
 	}
 	return 0;
 }
diff --git a/fs/proc/proc_sysctl.c b/fs/proc/proc_sysctl.c
index 88d3cc79fc33..c715f625a410 100644
--- a/fs/proc/proc_sysctl.c
+++ b/fs/proc/proc_sysctl.c
@@ -1714,6 +1714,17 @@ struct ctl_table_header *register_sysctl_sz(const char *path, const struct ctl_t
 }
 EXPORT_SYMBOL(register_sysctl_sz);
 
+struct ctl_table_header *register_sysctl_fields_sz(const char *path,
+						   const struct sysctl_field *fields,
+						   size_t field_count,
+						   const struct sysctl_context *ctx,
+						   size_t ctx_size)
+{
+	return __register_sysctl_fields(&sysctl_table_root.default_set, path,
+					fields, field_count, ctx, ctx_size);
+}
+EXPORT_SYMBOL(register_sysctl_fields_sz);
+
 /**
  * __register_sysctl_init() - register sysctl table to path
  * @path: path name for sysctl base. If that path doesn't exist we will create
diff --git a/include/linux/parport.h b/include/linux/parport.h
index 464c2ad28039..ea3dd405ea3c 100644
--- a/include/linux/parport.h
+++ b/include/linux/parport.h
@@ -20,6 +20,7 @@
 /* Define this later. */
 struct parport;
 struct pardevice;
+struct ctl_table_header;
 
 struct pc_parport_state {
 	unsigned int ctr;
@@ -151,7 +152,7 @@ struct pardevice {
 	unsigned long waiting;		 /* long req'd for set_bit --RR */
 	struct pardevice *waitprev;
 	struct pardevice *waitnext;
-	void * sysctl_table;
+	struct ctl_table_header *sysctl_table;
 };
 
 #define to_pardevice(n) container_of(n, struct pardevice, dev)
@@ -223,7 +224,8 @@ struct parport {
 	struct timer_list timer;
 	unsigned int flags;
 
-	void *sysctl_table;
+	struct ctl_table_header *sysctl_table;
+	struct ctl_table_header *sysctl_devices_header;
 	struct parport_device_info probe_info[5]; /* 0-3 + non-IEEE1284.3 */
 	struct ieee1284_info ieee1284;
 
diff --git a/include/linux/sysctl.h b/include/linux/sysctl.h
index 6cf2ef4f13e8..b98f1ee94df2 100644
--- a/include/linux/sysctl.h
+++ b/include/linux/sysctl.h
@@ -506,6 +506,10 @@ struct ctl_table_header *
 __register_sysctl_fields(struct ctl_table_set *set, const char *path,
 			 const struct sysctl_field *fields, size_t field_count,
 			 const struct sysctl_context *ctx, size_t ctx_size);
+struct ctl_table_header *
+register_sysctl_fields_sz(const char *path,
+			  const struct sysctl_field *fields, size_t field_count,
+			  const struct sysctl_context *ctx, size_t ctx_size);
 struct ctl_table_header *register_sysctl_sz(const char *path, const struct ctl_table *table,
 					    size_t table_size);
 void unregister_sysctl_table(struct ctl_table_header * table);
@@ -541,6 +545,14 @@ static inline struct ctl_table_header *register_sysctl_sz(const char *path,
 	return NULL;
 }
 
+static inline struct ctl_table_header *
+register_sysctl_fields_sz(const char *path,
+			  const struct ctl_field *fields, size_t field_count,
+			  const struct ctl_context *ctx, size_t ctx_size)
+{
+	return NULL;
+}
+
 static inline struct ctl_table_header *
 __register_sysctl_fields(struct ctl_table_set *set, const char *path,
 			 const struct sysctl_field *fields, size_t field_count,
-- 
2.55.0


^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [RFC PATCH v1 17/30] sysctl: net: use sysctl_field in SCTP sysctls
  2026-08-26 19:42 ` [RFC PATCH v1 17/30] sysctl: net: use sysctl_field in SCTP sysctls Alexey Gladkov
@ 2026-08-26 20:29   ` Linus Torvalds
  2026-08-29 16:14     ` Alexey Gladkov
  0 siblings, 1 reply; 37+ messages in thread
From: Linus Torvalds @ 2026-08-26 20:29 UTC (permalink / raw)
  To: Alexey Gladkov
  Cc: Eric W . Biederman, Kees Cook, Joel Granados, LKML, linux-fsdevel

I detest this series.

Maybe it makes technical sense, but as long as it makes no human
visual sense, I'm NAK'ing it as being garbage.

On Wed, 26 Aug 2026 at 12:43, Alexey Gladkov <legion@kernel.org> wrote:
>
 [..]

The whole series is full of complete illegible noise like this:

> +static const struct sysctl_field sctp_net_table[] = {
> +       SYSCTL_FIELD_CUSTOM("rto_min", 0644, sizeof(unsigned int),
> +                        sctp_rto_min_data, proc_sctp_do_rto_min),
> +       SYSCTL_FIELD_CUSTOM("rto_max", 0644, sizeof(unsigned int),
> +                        sctp_rto_max_data, proc_sctp_do_rto_max),
> +       SYSCTL_FIELD_INT_MINMAX("pf_retrans", 0644, sctp_pf_retrans_data,
> +                            SYSCTL_ZERO, sctp_ps_retrans_data),
> +       SYSCTL_FIELD_INT_MINMAX("ps_retrans", 0644, sctp_ps_retrans_data,
> +                            sctp_pf_retrans_data, sctp_ps_retrans_max_data),
> +       SYSCTL_FIELD_STATIC_UINT_MINMAX("rto_initial", 0644,
> +                                    sctp_rto_initial_data,
> +                                    SYSCTL_UINT_ONE, &timer_max),
[...]

where apparently the indentation has been decided by a rodent on crack
who was given an Ouija board and instructed to ask his dead ancestors
what indentation to use.

So no.

That kind of complete random code is simply not acceptable.

I don't know what the correct answer is, but it is *not* this series.
It needs to be consistent and visually parseable by humans *without*
asking your dead ancestors for help.

And it's not just the indentation. That SYSCTL_FIELD_CUSTOM() thing
needs to be usable and able to do some minimal type checking - not
just passed a random sizeof() in a random argumentt. I'd suggest
passing the actual type, and then checking that the type *matches* the
data pointer it is passed too.

The old code may be ugly too, and have various other warts, but at
least it had somewhat legible and understandable initializers:

        {
                .procname       = "prsctp_enable",
                .data           = &init_net.sctp.prsctp_enable,
                .maxlen         = sizeof(int),
                .mode           = 0644,
                .proc_handler   = proc_dointvec,
        },

is at least something that can be read by a human and those things had
consistent whitespace rather than some quantum randomness.

And this is also just complete line noise that only makes the code worse:

    +#define SCTP_DATA(type, field)                                         \
    +static type *sctp_ ## field ## _data(const struct sysctl_context *ctx) \
    +{                                                                      \
    +       return &ctx->ns.net_ns->sctp.field;                             \
    +}
    +
    +#define SCTP_CUSTOM_DATA(field)
             \
    +static void *sctp_ ## field ## _data(const struct sysctl_context *ctx) \
    +{                                                                      \
    +       return &ctx->ns.net_ns->sctp.field;                             \
    +}
    +
    +SCTP_CUSTOM_DATA(rto_min)
    +SCTP_CUSTOM_DATA(rto_max)
    +SCTP_DATA(int, pf_retrans)
    +SCTP_DATA(int, ps_retrans)
    +SCTP_DATA(unsigned int, rto_initial)
    [...]

If we're doing these kinds of changes, the end result has to look
*BETTER* than the thing it replaces, not worse.

Yes, a few of the patches did look better. But the majority of them
only looked worse. Some of it should be easy to fix: use consistent
whitespace, and use sane argument ordering.

But honestly, the old setup didn't *rely* on argument ordering, and
used named initializers to make things more legible and robust.

So I suspect that should be what you should aim for in the new setup
too, and that probably means "completely different approach".

               Linus

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [RFC PATCH v1 17/30] sysctl: net: use sysctl_field in SCTP sysctls
  2026-08-26 20:29   ` Linus Torvalds
@ 2026-08-29 16:14     ` Alexey Gladkov
  2026-08-29 16:14       ` [RFC PATCH 1/4] sysctl: add typed field descriptors Alexey Gladkov
                         ` (4 more replies)
  0 siblings, 5 replies; 37+ messages in thread
From: Alexey Gladkov @ 2026-08-29 16:14 UTC (permalink / raw)
  To: Linus Torvalds
  Cc: Eric W . Biederman, Kees Cook, Joel Granados, LKML, linux-fsdevel

On Wed, Aug 26, 2026 at 01:29:00PM -0700, Linus Torvalds wrote:
> I detest this series.
> 
> Maybe it makes technical sense, but as long as it makes no human
> visual sense, I'm NAK'ing it as being garbage.

You are right that my approach added too many macros and made the tables
harder to read. Sorry.

> On Wed, 26 Aug 2026 at 12:43, Alexey Gladkov <legion@kernel.org> wrote:
> >
>  [..]
> 
> The whole series is full of complete illegible noise like this:
> 
> > +static const struct sysctl_field sctp_net_table[] = {
> > +       SYSCTL_FIELD_CUSTOM("rto_min", 0644, sizeof(unsigned int),
> > +                        sctp_rto_min_data, proc_sctp_do_rto_min),
> > +       SYSCTL_FIELD_CUSTOM("rto_max", 0644, sizeof(unsigned int),
> > +                        sctp_rto_max_data, proc_sctp_do_rto_max),
> > +       SYSCTL_FIELD_INT_MINMAX("pf_retrans", 0644, sctp_pf_retrans_data,
> > +                            SYSCTL_ZERO, sctp_ps_retrans_data),
> > +       SYSCTL_FIELD_INT_MINMAX("ps_retrans", 0644, sctp_ps_retrans_data,
> > +                            sctp_pf_retrans_data, sctp_ps_retrans_max_data),
> > +       SYSCTL_FIELD_STATIC_UINT_MINMAX("rto_initial", 0644,
> > +                                    sctp_rto_initial_data,
> > +                                    SYSCTL_UINT_ONE, &timer_max),
> [...]
> 
> where apparently the indentation has been decided by a rodent on crack
> who was given an Ouija board and instructed to ask his dead ancestors
> what indentation to use.
> 
> So no.
> 
> That kind of complete random code is simply not acceptable.
> 
> I don't know what the correct answer is, but it is *not* this series.
> It needs to be consistent and visually parseable by humans *without*
> asking your dead ancestors for help.
> 
> And it's not just the indentation. That SYSCTL_FIELD_CUSTOM() thing
> needs to be usable and able to do some minimal type checking - not
> just passed a random sizeof() in a random argumentt. I'd suggest
> passing the actual type, and then checking that the type *matches* the
> data pointer it is passed too.
> 
> The old code may be ugly too, and have various other warts, but at
> least it had somewhat legible and understandable initializers:
> 
>         {
>                 .procname       = "prsctp_enable",
>                 .data           = &init_net.sctp.prsctp_enable,
>                 .maxlen         = sizeof(int),
>                 .mode           = 0644,
>                 .proc_handler   = proc_dointvec,
>         },
> 
> is at least something that can be read by a human and those things had
> consistent whitespace rather than some quantum randomness.
> 
> And this is also just complete line noise that only makes the code worse:
> 
>     +#define SCTP_DATA(type, field)                                         \
>     +static type *sctp_ ## field ## _data(const struct sysctl_context *ctx) \
>     +{                                                                      \
>     +       return &ctx->ns.net_ns->sctp.field;                             \
>     +}
>     +
>     +#define SCTP_CUSTOM_DATA(field)
>              \
>     +static void *sctp_ ## field ## _data(const struct sysctl_context *ctx) \
>     +{                                                                      \
>     +       return &ctx->ns.net_ns->sctp.field;                             \
>     +}
>     +
>     +SCTP_CUSTOM_DATA(rto_min)
>     +SCTP_CUSTOM_DATA(rto_max)
>     +SCTP_DATA(int, pf_retrans)
>     +SCTP_DATA(int, ps_retrans)
>     +SCTP_DATA(unsigned int, rto_initial)
>     [...]
> 
> If we're doing these kinds of changes, the end result has to look
> *BETTER* than the thing it replaces, not worse.
> 
> Yes, a few of the patches did look better. But the majority of them
> only looked worse. Some of it should be easy to fix: use consistent
> whitespace, and use sane argument ordering.
> 
> But honestly, the old setup didn't *rely* on argument ordering, and
> used named initializers to make things more legible and robust.
> 
> So I suspect that should be what you should aim for in the new setup
> too, and that probably means "completely different approach".
> 
>                Linus
> 

I tried an alternative based on offsets. The following four patches add the
infrastructure and convert IPC, SCTP, and MPLS as examples.

The remaining macros are short wrappers around offsetof() and are only used for
compile-time type checking. If they still hurt readability, I can drop that
checking and use plain offsetof() instead.

Does this look better for you, or should I just stop?


Alexey Gladkov (4):
  sysctl: add typed field descriptors
  sysctl: ipc: use typed fields for IPC namespace sysctls
  sctp: use typed fields for per-net sysctls
  mpls: use typed fields for per-device sysctls

 fs/proc/proc_sysctl.c  | 282 ++++++++++++++++++++++++++---
 include/linux/sysctl.h | 134 +++++++++++++-
 ipc/ipc_sysctl.c       | 188 +++++++++----------
 net/mpls/af_mpls.c     |  66 ++++---
 net/sctp/sysctl.c      | 398 +++++++++++++++++++++--------------------
 5 files changed, 703 insertions(+), 365 deletions(-)

-- 
Rgrds, legion


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [RFC PATCH 1/4] sysctl: add typed field descriptors
  2026-08-29 16:14     ` Alexey Gladkov
@ 2026-08-29 16:14       ` Alexey Gladkov
  2026-08-29 16:14       ` [RFC PATCH 2/4] sysctl: ipc: use typed fields for IPC namespace sysctls Alexey Gladkov
                         ` (3 subsequent siblings)
  4 siblings, 0 replies; 37+ messages in thread
From: Alexey Gladkov @ 2026-08-29 16:14 UTC (permalink / raw)
  To: Linus Torvalds
  Cc: Eric W . Biederman, Kees Cook, Joel Granados, LKML, linux-fsdevel

Several sysctl users duplicate ctl_table arrays at registration time so
data and limit pointers can be redirected to namespace or device state.
The copies consume memory for every instance and their index-based
fixups silently depend on the source table order.

Add ctl_field as an alternative static descriptor. A field records
the value kind and a checked offset into an object selected by a
registration context. Type-specific offset helpers verify the backing
member type at build time, while the core derives the legacy proc
handler, size and limit pointers from the field kind.

Keep ctl_table as the interface used by proc handlers, permissions and
BPF by materializing one entry on the stack when those paths need it.
Existing ctl_table registrations are unchanged, and converted users can
share one read-only descriptor array without allocating a table copy.

Signed-off-by: Alexey Gladkov <legion@kernel.org>
---
 fs/proc/proc_sysctl.c  | 282 +++++++++++++++++++++++++++++++++++++----
 include/linux/sysctl.h | 134 +++++++++++++++++++-
 2 files changed, 384 insertions(+), 32 deletions(-)

diff --git a/fs/proc/proc_sysctl.c b/fs/proc/proc_sysctl.c
index 131496490991..b52ce725c212 100644
--- a/fs/proc/proc_sysctl.c
+++ b/fs/proc/proc_sysctl.c
@@ -18,6 +18,7 @@
 #include <linux/mount.h>
 #include <linux/kmemleak.h>
 #include <linux/lockdep.h>
+#include <linux/overflow.h>
 #include "internal.h"
 
 #define list_for_each_table_entry(index, header)			\
@@ -91,28 +92,171 @@ static int sysctl_follow_link(struct ctl_table_header **phead, size_t *pindex);
 static int insert_links(struct ctl_table_header *head);
 static void put_links(struct ctl_table_header *header);
 
+static inline bool is_field_table(const struct ctl_table_header *head)
+{
+	return head->table_kind == SYSCTL_TABLE_KIND_FIELD;
+}
+
 static const char *sysctl_entry_procname(struct ctl_table_header *head,
 					 size_t index)
 {
+	if (is_field_table(head))
+		return head->ctl_fields[index].procname;
+
 	return head->ctl_table[index].procname;
 }
 
 static umode_t sysctl_entry_mode(struct ctl_table_header *head, size_t index)
 {
-	return head->ctl_table[index].mode;
+	const struct sysctl_field *field;
+
+	if (!is_field_table(head))
+		return head->ctl_table[index].mode;
+
+	field = &head->ctl_fields[index];
+
+	if (field->mode_fn) {
+		lockdep_assert_not_held(&sysctl_lock);
+		return field->mode_fn(head->ctx);
+	}
+
+	return field->mode;
+}
+
+static bool sysctl_entry_is_dir(const struct ctl_table_header *head,
+				size_t index)
+{
+	/* Do not call field mode_fn callback while holding sysctl_lock. */
+	if (is_field_table(head))
+		return false;
+
+	return S_ISDIR(head->ctl_table[index].mode);
+}
+
+static bool sysctl_entry_is_link(const struct ctl_table_header *head,
+				 size_t index)
+{
+	if (is_field_table(head))
+		return false;
+
+	return S_ISLNK(head->ctl_table[index].mode);
 }
 
 static struct ctl_table_poll *sysctl_entry_poll(struct ctl_table_header *head,
 						size_t index)
 {
+	if (is_field_table(head))
+		return NULL;
+
 	return head->ctl_table[index].poll;
 }
 
+static void *sysctl_context_object(const struct sysctl_context *ctx)
+{
+	if (!ctx)
+		return NULL;
+	if (ctx->object)
+		return ctx->object(ctx);
+
+	switch (ctx->type) {
+	case SYSCTL_CONTEXT_USER_NS:
+		return ctx->ns.user_ns;
+	case SYSCTL_CONTEXT_IPC_NS:
+		return ctx->ns.ipc_ns;
+	case SYSCTL_CONTEXT_PID_NS:
+		return ctx->ns.pid_ns;
+	case SYSCTL_CONTEXT_NET_NS:
+		return ctx->ns.net_ns;
+	}
+
+	return NULL;
+}
+
+static void *sysctl_context_data(const struct sysctl_context *ctx,
+				 size_t offset, size_t size)
+{
+	void *object = sysctl_context_object(ctx);
+
+	if (!object || offset > ctx->object_size ||
+	    size > ctx->object_size - offset)
+		return NULL;
+
+	return (char *)object + offset;
+}
+
 static const struct ctl_table *
 sysctl_entry_table(struct ctl_table_header *head, size_t index,
 		   struct ctl_table *table)
 {
-	return &head->ctl_table[index];
+	const struct sysctl_field *field;
+
+	if (!is_field_table(head))
+		return &head->ctl_table[index];
+
+	field = &head->ctl_fields[index];
+
+	memset(table, 0, sizeof(*table));
+	table->procname = field->procname;
+	table->mode = field->mode;
+
+	if (field->mode_fn)
+		table->mode = field->mode_fn(head->ctx);
+
+	switch (field->type) {
+	case SYSCTL_FIELD_NO_DATA:
+		break;
+	case SYSCTL_FIELD_STRING:
+		table->proc_handler = proc_dostring;
+		table->maxlen = field->maxlen;
+		break;
+	case SYSCTL_FIELD_BOOL:
+		table->proc_handler = proc_dobool;
+		table->maxlen = sizeof(bool);
+		break;
+	case SYSCTL_FIELD_U8:
+	case SYSCTL_FIELD_U8_MINMAX:
+		table->proc_handler = proc_dou8vec_minmax;
+		table->maxlen = sizeof(u8);
+		table->extra1 = field->u8_limits.min_value;
+		table->extra2 = field->u8_limits.max_value;
+		break;
+	case SYSCTL_FIELD_INT:
+	case SYSCTL_FIELD_INT_MINMAX:
+		table->proc_handler = field->type == SYSCTL_FIELD_INT ?
+				      proc_dointvec : proc_dointvec_minmax;
+		table->maxlen = sizeof(int);
+		table->extra1 = field->int_limits.min_value;
+		table->extra2 = field->int_limits.max_value;
+		break;
+	case SYSCTL_FIELD_UINT:
+	case SYSCTL_FIELD_UINT_MINMAX:
+		table->proc_handler = field->type == SYSCTL_FIELD_UINT ?
+				      proc_douintvec : proc_douintvec_minmax;
+		table->maxlen = sizeof(unsigned int);
+		table->extra1 = field->uint_limits.min_value;
+		table->extra2 = field->uint_limits.max_value;
+		break;
+	case SYSCTL_FIELD_ULONG:
+	case SYSCTL_FIELD_ULONG_MINMAX:
+		table->proc_handler = proc_doulongvec_minmax;
+		table->maxlen = sizeof(unsigned long);
+		table->extra1 = field->ulong_limits.min_value;
+		table->extra2 = field->ulong_limits.max_value;
+		break;
+	case SYSCTL_FIELD_SIZE_T:
+		table->proc_handler = proc_doulongvec_minmax;
+		table->maxlen = sizeof(size_t);
+		break;
+	}
+
+	if (field->type != SYSCTL_FIELD_NO_DATA)
+		table->data = sysctl_context_data(head->ctx, field->data_offset, table->maxlen);
+	if (field->proc_handler)
+		table->proc_handler = field->proc_handler;
+	if (field->maxlen)
+		table->maxlen = field->maxlen;
+
+	return table;
 }
 
 static void sysctl_print_dir(struct ctl_dir *dir)
@@ -212,9 +356,17 @@ static void erase_entry(struct ctl_table_header *head, size_t index)
 
 static void init_header(struct ctl_table_header *head,
 	struct ctl_table_root *root, struct ctl_table_set *set,
-	struct ctl_node *node, const struct ctl_table *table, size_t table_size)
+	struct ctl_node *node, const struct ctl_table *table,
+	const struct sysctl_field *fields, size_t table_size,
+	const struct sysctl_context *ctx)
 {
-	head->ctl_table = table;
+	if (fields) {
+		head->ctl_fields = fields;
+		head->table_kind = SYSCTL_TABLE_KIND_FIELD;
+	} else {
+		head->ctl_table = table;
+		head->table_kind = SYSCTL_TABLE_KIND_TABLE;
+	}
 	head->ctl_table_size = table_size;
 	head->ctl_table_arg = table;
 	head->used = 0;
@@ -223,6 +375,7 @@ static void init_header(struct ctl_table_header *head,
 	head->unregistering = NULL;
 	head->root = root;
 	head->set = set;
+	head->ctx = ctx;
 	head->parent = NULL;
 	head->node = node;
 	INIT_HLIST_HEAD(&head->inodes);
@@ -982,7 +1135,7 @@ static struct ctl_dir *find_subdir(struct ctl_dir *dir,
 
 	if (!find_entry(&head, &index, dir, name, namelen))
 		return ERR_PTR(-ENOENT);
-	if (!S_ISDIR(sysctl_entry_mode(head, index)))
+	if (!sysctl_entry_is_dir(head, index))
 		return ERR_PTR(-ENOTDIR);
 	return container_of(head, struct ctl_dir, header);
 }
@@ -1007,7 +1160,8 @@ static struct ctl_dir *new_dir(struct ctl_table_set *set,
 	memcpy(new_name, name, namelen);
 	table[0].procname = new_name;
 	table[0].mode = S_IFDIR|S_IRUGO|S_IXUGO;
-	init_header(&new->header, set->dir.header.root, set, node, table, 1);
+	init_header(&new->header, set->dir.header.root, set, node, table, NULL,
+		    1, NULL);
 
 	return new;
 }
@@ -1188,6 +1342,10 @@ static int sysctl_check_table(const char *path, struct ctl_table_header *header)
 		entry = sysctl_entry_table(header, index, &table);
 		if (!entry->procname)
 			err |= sysctl_err(path, entry, "procname is null");
+		if (is_field_table(header) &&
+		    header->ctl_fields[index].type != SYSCTL_FIELD_NO_DATA &&
+		    !entry->data)
+			err |= sysctl_err(path, entry, "No data");
 		if ((entry->proc_handler == proc_dostring) ||
 		    (entry->proc_handler == proc_dobool) ||
 		    (entry->proc_handler == proc_dointvec) ||
@@ -1200,7 +1358,7 @@ static int sysctl_check_table(const char *path, struct ctl_table_header *header)
 		    (entry->proc_handler == proc_dointvec_ms_jiffies) ||
 		    (entry->proc_handler == proc_doulongvec_minmax) ||
 		    (entry->proc_handler == proc_doulongvec_ms_jiffies_minmax)) {
-			if (!entry->data)
+			if (!is_field_table(header) && !entry->data)
 				err |= sysctl_err(path, entry, "No data");
 			if (!entry->maxlen)
 				err |= sysctl_err(path, entry, "No maxlen");
@@ -1256,7 +1414,7 @@ static struct ctl_table_header *new_links(struct ctl_dir *dir, struct ctl_table_
 		link++;
 	}
 	init_header(links, dir->header.root, dir->header.set, node, link_table,
-		    head->ctl_table_size);
+		    NULL, head->ctl_table_size, NULL);
 	links->nreg = head->ctl_table_size;
 
 	return links;
@@ -1280,10 +1438,10 @@ static bool get_links(struct ctl_dir *dir,
 		if (!find_entry(&tmp_head, &link_index, dir, procname,
 				strlen(procname)))
 			return false;
-		if (S_ISDIR(sysctl_entry_mode(tmp_head, link_index)) &&
-		    S_ISDIR(sysctl_entry_mode(header, index)))
+		if (sysctl_entry_is_dir(tmp_head, link_index) &&
+		    sysctl_entry_is_dir(header, index))
 			continue;
-		if (S_ISLNK(sysctl_entry_mode(tmp_head, link_index)) &&
+		if (sysctl_entry_is_link(tmp_head, link_index) &&
 		    tmp_head->ctl_table[link_index].data == link_root)
 			continue;
 		return false;
@@ -1370,18 +1528,25 @@ static struct ctl_dir *sysctl_mkdir_p(struct ctl_dir *dir, const char *path)
 }
 
 /**
- * __register_sysctl_table - register a leaf sysctl table
+ * __register_sysctl_table_internal - register a leaf sysctl table
  * @set: Sysctl tree to register on
  * @path: The path to the directory the sysctl table is in.
+ * @table: The top-level ctl_table array, or %NULL when registering @fields.
+ * @fields: The top-level sysctl_field array, or %NULL when registering @table.
+ * @table_size: The number of elements in @table or @fields.
+ * @ctx: Optional context used to resolve @fields entries.
+ * @ctx_size: Size of @ctx, including any wrapper object that embeds it.
  *
- * @table: the top-level table structure. This table should not be free'd
- *         after registration. So it should not be used on stack. It can either
- *         be a global or dynamically allocated by the caller and free'd later
- *         after sysctl unregistration.
- * @table_size : The number of elements in table
+ * Register a sysctl table hierarchy. One of @table or @fields must be
+ * provided. The descriptor array should not be freed after registration, so it
+ * should not be used on stack. It can either be global or dynamically
+ * allocated by the caller and freed later after sysctl unregistration.
  *
- * Register a sysctl table hierarchy. @table should be a filled in ctl_table
- * array.
+ * If @ctx points to a wrapper object, &struct sysctl_context must be the first
+ * member so @ctx can be copied together with the rest of that object.
+ * Data offsets stored in @fields are checked against @ctx->object_size and
+ * applied to the namespace selected by @ctx->ns. A context wrapper may provide
+ * @ctx->object for tables whose data belongs to another object.
  *
  * The members of the &struct ctl_table structure are used as follows:
  * procname - the name of the sysctl file under /proc/sys. Set to %NULL to not
@@ -1412,25 +1577,69 @@ static struct ctl_dir *sysctl_mkdir_p(struct ctl_dir *dir, const char *path)
  * This routine returns %NULL on a failure to register, and a pointer
  * to the table header on success.
  */
-struct ctl_table_header *__register_sysctl_table(
-	struct ctl_table_set *set,
-	const char *path, const struct ctl_table *table, size_t table_size)
+static struct ctl_table_header *
+__register_sysctl_table_internal(struct ctl_table_set *set, const char *path,
+				 const struct ctl_table *table,
+				 const struct sysctl_field *fields,
+				 size_t table_size,
+				 const struct sysctl_context *ctx, size_t ctx_size)
 {
 	struct ctl_table_root *root = set->dir.header.root;
 	struct ctl_table_header *header;
 	struct ctl_dir *dir;
 	struct ctl_node *node;
+	const struct sysctl_context *header_ctx = NULL;
+	size_t nodes_size;
 	size_t alloc_size;
+	size_t context_offset;
+
+	if (!!table == !!fields)
+		return NULL;
+
+	if (ctx && ctx_size < sizeof(*ctx))
+		return NULL;
+
+	if (!ctx && ctx_size)
+		return NULL;
+
+	if (fields && (!ctx || !ctx->object_size ||
+		       !sysctl_context_object(ctx)))
+		return NULL;
+
+	if (check_mul_overflow(sizeof(struct ctl_node), table_size, &nodes_size))
+		return NULL;
+
+	if (check_add_overflow(sizeof(*header), nodes_size, &context_offset))
+		return NULL;
+
+	/*
+	 * Store the copied context after the ctl_node array. struct sysctl_context
+	 * is the first member of any caller-defined wrapper, whose alignment
+	 * must not exceed that of struct sysctl_context.
+	 */
+	if (ctx) {
+		if (check_add_overflow(context_offset,
+				       __alignof__(*ctx) - 1, &context_offset))
+			return NULL;
+		context_offset = ALIGN_DOWN(context_offset, __alignof__(*ctx));
+	}
 
-	alloc_size = sizeof(struct ctl_table_header) +
-		     sizeof(struct ctl_node) * table_size;
+	if (check_add_overflow(context_offset, ctx_size, &alloc_size))
+		return NULL;
 
 	header = kzalloc(alloc_size, GFP_KERNEL_ACCOUNT);
 	if (!header)
 		return NULL;
 
 	node = (struct ctl_node *)(header + 1);
-	init_header(header, root, set, node, table, table_size);
+	if (ctx) {
+		header_ctx = (const struct sysctl_context *)((void *)header +
+							   context_offset);
+		memcpy((void *)header_ctx, ctx, ctx_size);
+	}
+
+	init_header(header, root, set, node, table, fields, table_size,
+		    header_ctx);
 	if (sysctl_check_table(path, header))
 		goto fail;
 
@@ -1460,6 +1669,25 @@ struct ctl_table_header *__register_sysctl_table(
 	return NULL;
 }
 
+struct ctl_table_header *
+__register_sysctl_fields(struct ctl_table_set *set, const char *path,
+			 const struct sysctl_field *fields, size_t field_count,
+			 const struct sysctl_context *ctx, size_t ctx_size)
+{
+	return __register_sysctl_table_internal(set, path, NULL, fields,
+						field_count, ctx, ctx_size);
+}
+EXPORT_SYMBOL(__register_sysctl_fields);
+
+struct ctl_table_header *
+__register_sysctl_table(struct ctl_table_set *set, const char *path,
+			const struct ctl_table *table, size_t table_size)
+{
+	return __register_sysctl_table_internal(set, path, table, NULL,
+						table_size, NULL, 0);
+}
+EXPORT_SYMBOL(__register_sysctl_table);
+
 /**
  * register_sysctl_sz - register a sysctl table
  * @path: The path to the directory the sysctl table is in. If the path
@@ -1548,7 +1776,7 @@ static void put_links(struct ctl_table_header *header)
 
 		if (link &&
 		    ((S_ISDIR(link->mode) &&
-		      S_ISDIR(sysctl_entry_mode(header, index))) ||
+		      sysctl_entry_is_dir(header, index)) ||
 		     (S_ISLNK(link->mode) && (link->data == root)))) {
 			drop_sysctl_table(link_head);
 		} else {
@@ -1604,7 +1832,7 @@ void setup_sysctl_set(struct ctl_table_set *set,
 {
 	memset(set, 0, sizeof(*set));
 	set->is_seen = is_seen;
-	init_header(&set->dir.header, root, set, NULL, root_table, 1);
+	init_header(&set->dir.header, root, set, NULL, root_table, NULL, 1, NULL);
 }
 
 void retire_sysctl_set(struct ctl_table_set *set)
diff --git a/include/linux/sysctl.h b/include/linux/sysctl.h
index 8d993ba1488b..72e6222a97d8 100644
--- a/include/linux/sysctl.h
+++ b/include/linux/sysctl.h
@@ -22,20 +22,27 @@
 #ifndef _LINUX_SYSCTL_H
 #define _LINUX_SYSCTL_H
 
+#include <linux/build_bug.h>
 #include <linux/list.h>
 #include <linux/rcupdate.h>
 #include <linux/wait.h>
 #include <linux/rbtree.h>
+#include <linux/stddef.h>
 #include <linux/uidgid.h>
 #include <uapi/linux/sysctl.h>
 
 /* For the /proc/sys support */
 struct completion;
 struct ctl_table;
+struct sysctl_field;
 struct nsproxy;
 struct ctl_table_root;
 struct ctl_table_header;
 struct ctl_dir;
+struct ipc_namespace;
+struct net;
+struct pid_namespace;
+struct user_namespace;
 
 /* Keep the same order as in fs/proc/proc_sysctl.c */
 #define SYSCTL_ZERO			((void *)&sysctl_vals[0])
@@ -84,6 +91,27 @@ extern const unsigned int sysctl_uint_vals[];
 typedef int proc_handler(const struct ctl_table *ctl, int write, void *buffer,
 		size_t *lenp, loff_t *ppos);
 
+enum sysctl_context_type {
+	SYSCTL_CONTEXT_USER_NS,
+	SYSCTL_CONTEXT_IPC_NS,
+	SYSCTL_CONTEXT_PID_NS,
+	SYSCTL_CONTEXT_NET_NS,
+};
+
+union sysctl_namespace {
+	struct user_namespace *user_ns;
+	struct ipc_namespace *ipc_ns;
+	struct pid_namespace *pid_ns;
+	struct net *net_ns;
+};
+
+struct sysctl_context {
+	enum sysctl_context_type type;
+	size_t object_size;
+	union sysctl_namespace ns;
+	void *(*object)(const struct sysctl_context *ctx);
+};
+
 int proc_dostring(const struct ctl_table *, int, void *, size_t *, loff_t *);
 int proc_dobool(const struct ctl_table *table, int write, void *buffer,
 		size_t *lenp, loff_t *ppos);
@@ -182,30 +210,105 @@ struct ctl_table {
 	void *extra2;
 } __randomize_layout;
 
+enum sysctl_field_type {
+	SYSCTL_FIELD_NO_DATA,
+	SYSCTL_FIELD_STRING,
+	SYSCTL_FIELD_BOOL,
+	SYSCTL_FIELD_U8,
+	SYSCTL_FIELD_U8_MINMAX,
+	SYSCTL_FIELD_INT,
+	SYSCTL_FIELD_INT_MINMAX,
+	SYSCTL_FIELD_UINT,
+	SYSCTL_FIELD_UINT_MINMAX,
+	SYSCTL_FIELD_ULONG,
+	SYSCTL_FIELD_ULONG_MINMAX,
+	SYSCTL_FIELD_SIZE_T,
+};
+
+#define __SYSCTL_FIELD_OFFSET(_struct, _field, _type)				\
+	(offsetof(_struct, _field) +						\
+	 BUILD_BUG_ON_ZERO(!__same_type(((_struct *)0)->_field, *(_type *)0)))
+
+#define SYSCTL_FIELD_INT_OFFSET(_struct, _field)	__SYSCTL_FIELD_OFFSET(_struct, _field, int)
+#define SYSCTL_FIELD_UINT_OFFSET(_struct, _field)	__SYSCTL_FIELD_OFFSET(_struct, _field, unsigned int)
+#define SYSCTL_FIELD_ULONG_OFFSET(_struct, _field)	__SYSCTL_FIELD_OFFSET(_struct, _field, unsigned long)
+#define SYSCTL_FIELD_SIZE_T_OFFSET(_struct, _field)	__SYSCTL_FIELD_OFFSET(_struct, _field, size_t)
+
+struct sysctl_field_u8_limits {
+	unsigned int *min_value;
+	unsigned int *max_value;
+};
+
+struct sysctl_field_int_limits {
+	int *min_value;
+	int *max_value;
+};
+
+struct sysctl_field_uint_limits {
+	unsigned int *min_value;
+	unsigned int *max_value;
+};
+
+struct sysctl_field_ulong_limits {
+	unsigned long *min_value;
+	unsigned long *max_value;
+};
+
+struct sysctl_field {
+	const char *procname;
+	umode_t mode;
+	enum sysctl_field_type type;
+	umode_t (*mode_fn)(const struct sysctl_context *ctx);
+	proc_handler *proc_handler;
+	int maxlen;
+	size_t data_offset;
+	union {
+		struct sysctl_field_u8_limits		u8_limits;
+		struct sysctl_field_int_limits		int_limits;
+		struct sysctl_field_uint_limits		uint_limits;
+		struct sysctl_field_ulong_limits	ulong_limits;
+	};
+} __randomize_layout;
+
 struct ctl_node {
 	struct rb_node node;
 	struct ctl_table_header *header;
 };
 
 /**
- * struct ctl_table_header - maintains dynamic lists of struct ctl_table trees
- * @ctl_table: pointer to the first element in ctl_table array
- * @ctl_table_size: number of elements pointed by @ctl_table
+ * struct ctl_table_header - maintains dynamic lists of sysctl descriptor trees
+ * @ctl_table: pointer to the first element in a legacy ctl_table array
+ * @ctl_fields: pointer to the first element in a ctl_field array
+ * @ctl_table_size: number of elements pointed to by @ctl_table or @ctl_fields
  * @used: The entry will never be touched when equal to 0.
  * @count: Upped every time something is added to @inodes and downed every time
  *         something is removed from inodes
  * @nreg: When nreg drops to 0 the ctl_table_header will be unregistered.
- * @rcu: Delays the freeing of the inode. Introduced with "unfuck proc_sysctl ->d_compare()"
+ * @rcu: delays freeing the header until after an RCU grace period
+ * @unregistering: completion used while unregistering the header
+ * @ctl_table_arg: original legacy ctl_table passed at registration, or NULL
+ * @ctx: copied registration context used to resolve sysctl_field entries
+ * @root: sysctl tree containing this header
+ * @set: sysctl set containing this header
+ * @parent: parent directory of this header
+ * @node: array of nodes corresponding to the descriptor entries
+ * @inodes: inodes currently referring to this header
  *
  * @type: Enumeration to differentiate between ctl target types
  * @type.SYSCTL_TABLE_TYPE_DEFAULT: ctl target with no special considerations
  * @type.SYSCTL_TABLE_TYPE_PERMANENTLY_EMPTY: Identifies a permanently empty dir
  *                                            target to serve as a mount point
+ * @table_kind: descriptor format stored in this header
+ * @table_kind.SYSCTL_TABLE_KIND_TABLE: legacy ctl_table descriptors
+ * @table_kind.SYSCTL_TABLE_KIND_FIELD: typed ctl_field descriptors
  */
 struct ctl_table_header {
 	union {
 		struct {
-			const struct ctl_table *ctl_table;
+			union {
+				const struct ctl_table *ctl_table;
+				const struct sysctl_field *ctl_fields;
+			};
 			int ctl_table_size;
 			int used;
 			int count;
@@ -220,10 +323,15 @@ struct ctl_table_header {
 	struct ctl_dir *parent;
 	struct ctl_node *node;
 	struct hlist_head inodes; /* head for proc_inode->sysctl_inodes */
+	const struct sysctl_context *ctx;
 	enum {
 		SYSCTL_TABLE_TYPE_DEFAULT,
 		SYSCTL_TABLE_TYPE_PERMANENTLY_EMPTY,
 	} type;
+	enum {
+		SYSCTL_TABLE_KIND_TABLE,
+		SYSCTL_TABLE_KIND_FIELD,
+	} table_kind;
 };
 
 struct ctl_dir {
@@ -248,6 +356,10 @@ struct ctl_table_root {
 #define register_sysctl(path, table)	\
 	register_sysctl_sz(path, table, ARRAY_SIZE(table))
 
+#define register_sysctl_fields(set, path, fields, ctx)			\
+	__register_sysctl_fields(set, path, fields, ARRAY_SIZE(fields),	\
+				 (ctx), sizeof(*(ctx)))
+
 #ifdef CONFIG_SYSCTL
 
 void proc_sys_poll_notify(struct ctl_table_poll *poll);
@@ -260,6 +372,10 @@ extern void retire_sysctl_set(struct ctl_table_set *set);
 struct ctl_table_header *__register_sysctl_table(
 	struct ctl_table_set *set,
 	const char *path, const struct ctl_table *table, size_t table_size);
+struct ctl_table_header *
+__register_sysctl_fields(struct ctl_table_set *set, const char *path,
+			 const struct sysctl_field *fields, size_t field_count,
+			 const struct sysctl_context *ctx, size_t ctx_size);
 struct ctl_table_header *register_sysctl_sz(const char *path, const struct ctl_table *table,
 					    size_t table_size);
 void unregister_sysctl_table(struct ctl_table_header * table);
@@ -295,6 +411,14 @@ static inline struct ctl_table_header *register_sysctl_sz(const char *path,
 	return NULL;
 }
 
+static inline struct ctl_table_header *
+__register_sysctl_fields(struct ctl_table_set *set, const char *path,
+			 const struct sysctl_field *fields, size_t field_count,
+			 const struct sysctl_context *ctx, size_t ctx_size)
+{
+	return NULL;
+}
+
 static inline void unregister_sysctl_table(struct ctl_table_header * table)
 {
 }
-- 
2.55.0


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [RFC PATCH 2/4] sysctl: ipc: use typed fields for IPC namespace sysctls
  2026-08-29 16:14     ` Alexey Gladkov
  2026-08-29 16:14       ` [RFC PATCH 1/4] sysctl: add typed field descriptors Alexey Gladkov
@ 2026-08-29 16:14       ` Alexey Gladkov
  2026-08-29 16:14       ` [RFC PATCH 3/4] sctp: use typed fields for per-net sysctls Alexey Gladkov
                         ` (2 subsequent siblings)
  4 siblings, 0 replies; 37+ messages in thread
From: Alexey Gladkov @ 2026-08-29 16:14 UTC (permalink / raw)
  To: Linus Torvalds
  Cc: Eric W . Biederman, Kees Cook, Joel Granados, LKML, linux-fsdevel

IPC sysctl registration clones the table and rewrites data and limit
pointers for each namespace. This allocates a full ctl_table array per
namespace and ties the fixup loop to the table layout.

Describe the table with ctl_field offsets into ipc_namespace instead.
Keep the auto_msgmni entry data-less because its handler selects the
effective value itself. This removes the per-namespace table allocation
while preserving the existing handlers and limits.

Signed-off-by: Alexey Gladkov <legion@kernel.org>
---
 ipc/ipc_sysctl.c | 188 +++++++++++++++++++++--------------------------
 1 file changed, 82 insertions(+), 106 deletions(-)

diff --git a/ipc/ipc_sysctl.c b/ipc/ipc_sysctl.c
index 9b087ebeb643..f017b4109cc8 100644
--- a/ipc/ipc_sysctl.c
+++ b/ipc/ipc_sysctl.c
@@ -13,7 +13,6 @@
 #include <linux/capability.h>
 #include <linux/ipc_namespace.h>
 #include <linux/msg.h>
-#include <linux/slab.h>
 #include <linux/cred.h>
 #include "util.h"
 
@@ -41,6 +40,8 @@ static int proc_ipc_auto_msgmni(const struct ctl_table *table, int write,
 
 	memcpy(&ipc_table, table, sizeof(ipc_table));
 	ipc_table.data = &dummy;
+	ipc_table.extra1 = SYSCTL_ZERO;
+	ipc_table.extra2 = SYSCTL_ONE;
 
 	if (write)
 		pr_info_once("writing to auto_msgmni has no effect");
@@ -72,110 +73,129 @@ static int proc_ipc_sem_dointvec(const struct ctl_table *table, int write,
 int ipc_mni = IPCMNI;
 int ipc_mni_shift = IPCMNI_SHIFT;
 int ipc_min_cycle = RADIX_TREE_MAP_SIZE;
+static unsigned int ipc_mni_max = IPCMNI;
+static unsigned int ipc_uint_max = INT_MAX;
 
-static const struct ctl_table ipc_sysctls[] = {
+static const struct sysctl_field ipc_sysctls[] = {
 	{
 		.procname	= "shmmax",
-		.data		= &init_ipc_ns.shm_ctlmax,
-		.maxlen		= sizeof(init_ipc_ns.shm_ctlmax),
 		.mode		= 0644,
-		.proc_handler	= proc_doulongvec_minmax,
+		.type		= SYSCTL_FIELD_SIZE_T,
+		.data_offset	= SYSCTL_FIELD_SIZE_T_OFFSET(struct ipc_namespace,
+							     shm_ctlmax),
 	},
 	{
 		.procname	= "shmall",
-		.data		= &init_ipc_ns.shm_ctlall,
-		.maxlen		= sizeof(init_ipc_ns.shm_ctlall),
 		.mode		= 0644,
-		.proc_handler	= proc_doulongvec_minmax,
+		.type		= SYSCTL_FIELD_SIZE_T,
+		.data_offset	= SYSCTL_FIELD_SIZE_T_OFFSET(struct ipc_namespace,
+							     shm_ctlall),
 	},
 	{
 		.procname	= "shmmni",
-		.data		= &init_ipc_ns.shm_ctlmni,
-		.maxlen		= sizeof(init_ipc_ns.shm_ctlmni),
 		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= &ipc_mni,
+		.type		= SYSCTL_FIELD_INT_MINMAX,
+		.data_offset	= SYSCTL_FIELD_INT_OFFSET(struct ipc_namespace,
+							  shm_ctlmni),
+		.int_limits = {
+			.min_value	= SYSCTL_ZERO,
+			.max_value	= &ipc_mni,
+		},
 	},
 	{
 		.procname	= "shm_rmid_forced",
-		.data		= &init_ipc_ns.shm_rmid_forced,
-		.maxlen		= sizeof(init_ipc_ns.shm_rmid_forced),
 		.mode		= 0644,
+		.type		= SYSCTL_FIELD_INT_MINMAX,
 		.proc_handler	= proc_ipc_dointvec_minmax_orphans,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_ONE,
+		.data_offset	= SYSCTL_FIELD_INT_OFFSET(struct ipc_namespace,
+							  shm_rmid_forced),
+		.int_limits = {
+			.min_value	= SYSCTL_ZERO,
+			.max_value	= SYSCTL_ONE,
+		},
 	},
 	{
 		.procname	= "msgmax",
-		.data		= &init_ipc_ns.msg_ctlmax,
-		.maxlen		= sizeof(init_ipc_ns.msg_ctlmax),
 		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_INT_MAX,
+		.type		= SYSCTL_FIELD_UINT_MINMAX,
+		.data_offset	= SYSCTL_FIELD_UINT_OFFSET(struct ipc_namespace,
+							   msg_ctlmax),
+		.uint_limits = {
+			.min_value	= SYSCTL_UINT_ZERO,
+			.max_value	= &ipc_uint_max,
+		},
 	},
 	{
 		.procname	= "msgmni",
-		.data		= &init_ipc_ns.msg_ctlmni,
-		.maxlen		= sizeof(init_ipc_ns.msg_ctlmni),
 		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= &ipc_mni,
+		.type		= SYSCTL_FIELD_UINT_MINMAX,
+		.data_offset	= SYSCTL_FIELD_UINT_OFFSET(struct ipc_namespace,
+							   msg_ctlmni),
+		.uint_limits = {
+			.min_value	= SYSCTL_UINT_ZERO,
+			.max_value	= &ipc_mni_max,
+		},
 	},
 	{
 		.procname	= "auto_msgmni",
-		.data		= NULL,
-		.maxlen		= sizeof(int),
 		.mode		= 0644,
+		.type		= SYSCTL_FIELD_NO_DATA,
 		.proc_handler	= proc_ipc_auto_msgmni,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_ONE,
+		.maxlen		= sizeof(int),
 	},
 	{
-		.procname	=  "msgmnb",
-		.data		= &init_ipc_ns.msg_ctlmnb,
-		.maxlen		= sizeof(init_ipc_ns.msg_ctlmnb),
+		.procname	= "msgmnb",
 		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_INT_MAX,
+		.type		= SYSCTL_FIELD_UINT_MINMAX,
+		.data_offset	= SYSCTL_FIELD_UINT_OFFSET(struct ipc_namespace,
+							   msg_ctlmnb),
+		.uint_limits = {
+			.min_value	= SYSCTL_UINT_ZERO,
+			.max_value	= &ipc_uint_max,
+		},
 	},
 	{
 		.procname	= "sem",
-		.data		= &init_ipc_ns.sem_ctls,
-		.maxlen		= 4*sizeof(int),
 		.mode		= 0644,
+		.type		= SYSCTL_FIELD_INT,
 		.proc_handler	= proc_ipc_sem_dointvec,
+		.maxlen		= 4 * sizeof(int),
+		.data_offset	= SYSCTL_FIELD_INT_OFFSET(struct ipc_namespace,
+							  sem_ctls[0]),
 	},
 #ifdef CONFIG_CHECKPOINT_RESTORE
 	{
 		.procname	= "sem_next_id",
-		.data		= &init_ipc_ns.ids[IPC_SEM_IDS].next_id,
-		.maxlen		= sizeof(init_ipc_ns.ids[IPC_SEM_IDS].next_id),
 		.mode		= 0444,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_INT_MAX,
+		.type		= SYSCTL_FIELD_INT_MINMAX,
+		.data_offset	= SYSCTL_FIELD_INT_OFFSET(struct ipc_namespace,
+							  ids[IPC_SEM_IDS].next_id),
+		.int_limits = {
+			.min_value	= SYSCTL_ZERO,
+			.max_value	= SYSCTL_INT_MAX,
+		},
 	},
 	{
 		.procname	= "msg_next_id",
-		.data		= &init_ipc_ns.ids[IPC_MSG_IDS].next_id,
-		.maxlen		= sizeof(init_ipc_ns.ids[IPC_MSG_IDS].next_id),
 		.mode		= 0444,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_INT_MAX,
+		.type		= SYSCTL_FIELD_INT_MINMAX,
+		.data_offset	= SYSCTL_FIELD_INT_OFFSET(struct ipc_namespace,
+							  ids[IPC_MSG_IDS].next_id),
+		.int_limits = {
+			.min_value	= SYSCTL_ZERO,
+			.max_value	= SYSCTL_INT_MAX,
+		},
 	},
 	{
 		.procname	= "shm_next_id",
-		.data		= &init_ipc_ns.ids[IPC_SHM_IDS].next_id,
-		.maxlen		= sizeof(init_ipc_ns.ids[IPC_SHM_IDS].next_id),
 		.mode		= 0444,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_INT_MAX,
+		.type		= SYSCTL_FIELD_INT_MINMAX,
+		.data_offset	= SYSCTL_FIELD_INT_OFFSET(struct ipc_namespace,
+							  ids[IPC_SHM_IDS].next_id),
+		.int_limits = {
+			.min_value	= SYSCTL_ZERO,
+			.max_value	= SYSCTL_INT_MAX,
+		},
 	},
 #endif
 };
@@ -244,57 +264,16 @@ static struct ctl_table_root set_root = {
 
 bool setup_ipc_sysctls(struct ipc_namespace *ns)
 {
-	struct ctl_table *tbl;
+	struct sysctl_context ctx = {
+		.type = SYSCTL_CONTEXT_IPC_NS,
+		.object_size = sizeof(*ns),
+		.ns.ipc_ns = ns,
+	};
 
 	setup_sysctl_set(&ns->ipc_set, &set_root, set_is_seen);
-
-	tbl = kmemdup(ipc_sysctls, sizeof(ipc_sysctls), GFP_KERNEL);
-	if (tbl) {
-		int i;
-
-		for (i = 0; i < ARRAY_SIZE(ipc_sysctls); i++) {
-			if (tbl[i].data == &init_ipc_ns.shm_ctlmax)
-				tbl[i].data = &ns->shm_ctlmax;
-
-			else if (tbl[i].data == &init_ipc_ns.shm_ctlall)
-				tbl[i].data = &ns->shm_ctlall;
-
-			else if (tbl[i].data == &init_ipc_ns.shm_ctlmni)
-				tbl[i].data = &ns->shm_ctlmni;
-
-			else if (tbl[i].data == &init_ipc_ns.shm_rmid_forced)
-				tbl[i].data = &ns->shm_rmid_forced;
-
-			else if (tbl[i].data == &init_ipc_ns.msg_ctlmax)
-				tbl[i].data = &ns->msg_ctlmax;
-
-			else if (tbl[i].data == &init_ipc_ns.msg_ctlmni)
-				tbl[i].data = &ns->msg_ctlmni;
-
-			else if (tbl[i].data == &init_ipc_ns.msg_ctlmnb)
-				tbl[i].data = &ns->msg_ctlmnb;
-
-			else if (tbl[i].data == &init_ipc_ns.sem_ctls)
-				tbl[i].data = &ns->sem_ctls;
-#ifdef CONFIG_CHECKPOINT_RESTORE
-			else if (tbl[i].data == &init_ipc_ns.ids[IPC_SEM_IDS].next_id)
-				tbl[i].data = &ns->ids[IPC_SEM_IDS].next_id;
-
-			else if (tbl[i].data == &init_ipc_ns.ids[IPC_MSG_IDS].next_id)
-				tbl[i].data = &ns->ids[IPC_MSG_IDS].next_id;
-
-			else if (tbl[i].data == &init_ipc_ns.ids[IPC_SHM_IDS].next_id)
-				tbl[i].data = &ns->ids[IPC_SHM_IDS].next_id;
-#endif
-			else
-				tbl[i].data = NULL;
-		}
-
-		ns->ipc_sysctls = __register_sysctl_table(&ns->ipc_set, "kernel", tbl,
-							  ARRAY_SIZE(ipc_sysctls));
-	}
+	ns->ipc_sysctls =
+		register_sysctl_fields(&ns->ipc_set, "kernel", ipc_sysctls, &ctx);
 	if (!ns->ipc_sysctls) {
-		kfree(tbl);
 		retire_sysctl_set(&ns->ipc_set);
 		return false;
 	}
@@ -304,12 +283,8 @@ bool setup_ipc_sysctls(struct ipc_namespace *ns)
 
 void retire_ipc_sysctls(struct ipc_namespace *ns)
 {
-	const struct ctl_table *tbl;
-
-	tbl = ns->ipc_sysctls->ctl_table_arg;
 	unregister_sysctl_table(ns->ipc_sysctls);
 	retire_sysctl_set(&ns->ipc_set);
-	kfree(tbl);
 }
 
 static int __init ipc_sysctl_init(void)
@@ -326,6 +301,7 @@ device_initcall(ipc_sysctl_init);
 static int __init ipc_mni_extend(char *str)
 {
 	ipc_mni = IPCMNI_EXTEND;
+	ipc_mni_max = IPCMNI_EXTEND;
 	ipc_mni_shift = IPCMNI_EXTEND_SHIFT;
 	ipc_min_cycle = IPCMNI_EXTEND_MIN_CYCLE;
 	pr_info("IPCMNI extended to %d.\n", ipc_mni);
-- 
2.55.0


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [RFC PATCH 3/4] sctp: use typed fields for per-net sysctls
  2026-08-29 16:14     ` Alexey Gladkov
  2026-08-29 16:14       ` [RFC PATCH 1/4] sysctl: add typed field descriptors Alexey Gladkov
  2026-08-29 16:14       ` [RFC PATCH 2/4] sysctl: ipc: use typed fields for IPC namespace sysctls Alexey Gladkov
@ 2026-08-29 16:14       ` Alexey Gladkov
  2026-08-29 16:14       ` [RFC PATCH 4/4] mpls: use typed fields for per-device sysctls Alexey Gladkov
  2026-08-30 15:38       ` [RFC PATCH v1 17/30] sysctl: net: use sysctl_field in SCTP sysctls Linus Torvalds
  4 siblings, 0 replies; 37+ messages in thread
From: Alexey Gladkov @ 2026-08-29 16:14 UTC (permalink / raw)
  To: Linus Torvalds
  Cc: Eric W . Biederman, Kees Cook, Joel Granados, LKML, linux-fsdevel

SCTP clones its sysctl table for every network namespace and rewrites
entries by index to install namespace data and dynamic limits. The index
fixups depend on the table order and retain a full ctl_table allocation
per namespace.

Replace the cloned table with static ctl_field descriptors using
checked offsets into struct net. Keep the specialized handlers for
values whose bounds depend on other SCTP settings, and let them
construct the temporary ctl_table arguments required by the existing
proc helpers.

Signed-off-by: Alexey Gladkov <legion@kernel.org>
---
 net/sctp/sysctl.c | 398 ++++++++++++++++++++++++----------------------
 1 file changed, 206 insertions(+), 192 deletions(-)

diff --git a/net/sctp/sysctl.c b/net/sctp/sysctl.c
index 15e7db9a3ab2..030c7924be05 100644
--- a/net/sctp/sysctl.c
+++ b/net/sctp/sysctl.c
@@ -25,9 +25,9 @@
 #include <net/sctp/sctp.h>
 #include <linux/sysctl.h>
 
-static int timer_max = 86400000; /* ms in one day */
-static int sack_timer_min = 1;
-static int sack_timer_max = 500;
+static unsigned int timer_max = 86400000; /* ms in one day */
+static unsigned int sack_timer_min = 1;
+static unsigned int sack_timer_max = 500;
 static int addr_scope_max = SCTP_SCOPE_POLICY_MAX;
 static int rwnd_scale_max = 16;
 static int rto_alpha_min = 0;
@@ -49,10 +49,18 @@ static int proc_sctp_do_rto_min(const struct ctl_table *ctl, int write,
 				void *buffer, size_t *lenp, loff_t *ppos);
 static int proc_sctp_do_rto_max(const struct ctl_table *ctl, int write, void *buffer,
 				size_t *lenp, loff_t *ppos);
+static int proc_sctp_do_pf_retrans(const struct ctl_table *ctl, int write,
+				   void *buffer, size_t *lenp, loff_t *ppos);
+static int proc_sctp_do_ps_retrans(const struct ctl_table *ctl, int write,
+				   void *buffer, size_t *lenp, loff_t *ppos);
 static int proc_sctp_do_udp_port(const struct ctl_table *ctl, int write, void *buffer,
 				 size_t *lenp, loff_t *ppos);
 static int proc_sctp_do_alpha_beta(const struct ctl_table *ctl, int write,
 				   void *buffer, size_t *lenp, loff_t *ppos);
+static int proc_sctp_do_alpha(const struct ctl_table *ctl, int write,
+			      void *buffer, size_t *lenp, loff_t *ppos);
+static int proc_sctp_do_beta(const struct ctl_table *ctl, int write,
+			     void *buffer, size_t *lenp, loff_t *ppos);
 static int proc_sctp_do_auth(const struct ctl_table *ctl, int write,
 			     void *buffer, size_t *lenp, loff_t *ppos);
 static int proc_sctp_do_probe_interval(const struct ctl_table *ctl, int write,
@@ -82,305 +90,285 @@ static struct ctl_table sctp_table[] = {
 	},
 };
 
-/* The following index defines are used in sctp_sysctl_net_register().
- * If you add new items to the sctp_net_table, please ensure that
- * the index values of these defines hold the same meaning indicated by
- * their macro names when they appear in sctp_net_table.
- */
-#define SCTP_RTO_MIN_IDX       0
-#define SCTP_RTO_MAX_IDX       1
-#define SCTP_PF_RETRANS_IDX    2
-#define SCTP_PS_RETRANS_IDX    3
-
-static struct ctl_table sctp_net_table[] = {
-	[SCTP_RTO_MIN_IDX] = {
+static const struct sysctl_field sctp_net_table[] = {
+	{
 		.procname	= "rto_min",
-		.data		= &init_net.sctp.rto_min,
-		.maxlen		= sizeof(unsigned int),
 		.mode		= 0644,
+		.type		= SYSCTL_FIELD_UINT,
 		.proc_handler	= proc_sctp_do_rto_min,
-		.extra1         = SYSCTL_ONE,
-		.extra2         = &init_net.sctp.rto_max
+		.data_offset	= SYSCTL_FIELD_UINT_OFFSET(struct net, sctp.rto_min),
 	},
-	[SCTP_RTO_MAX_IDX] =  {
+	{
 		.procname	= "rto_max",
-		.data		= &init_net.sctp.rto_max,
-		.maxlen		= sizeof(unsigned int),
 		.mode		= 0644,
+		.type		= SYSCTL_FIELD_UINT,
 		.proc_handler	= proc_sctp_do_rto_max,
-		.extra1         = &init_net.sctp.rto_min,
-		.extra2         = &timer_max
+		.data_offset	= SYSCTL_FIELD_UINT_OFFSET(struct net, sctp.rto_max),
 	},
-	[SCTP_PF_RETRANS_IDX] = {
+	{
 		.procname	= "pf_retrans",
-		.data		= &init_net.sctp.pf_retrans,
-		.maxlen		= sizeof(int),
 		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= &init_net.sctp.ps_retrans,
+		.type		= SYSCTL_FIELD_INT,
+		.proc_handler	= proc_sctp_do_pf_retrans,
+		.data_offset	= SYSCTL_FIELD_INT_OFFSET(struct net, sctp.pf_retrans),
 	},
-	[SCTP_PS_RETRANS_IDX] = {
+	{
 		.procname	= "ps_retrans",
-		.data		= &init_net.sctp.ps_retrans,
-		.maxlen		= sizeof(int),
 		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= &init_net.sctp.pf_retrans,
-		.extra2		= &ps_retrans_max,
+		.type		= SYSCTL_FIELD_INT,
+		.proc_handler	= proc_sctp_do_ps_retrans,
+		.data_offset	= SYSCTL_FIELD_INT_OFFSET(struct net, sctp.ps_retrans),
 	},
 	{
 		.procname	= "rto_initial",
-		.data		= &init_net.sctp.rto_initial,
-		.maxlen		= sizeof(unsigned int),
 		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1         = SYSCTL_ONE,
-		.extra2         = &timer_max
+		.type		= SYSCTL_FIELD_UINT_MINMAX,
+		.data_offset	= SYSCTL_FIELD_UINT_OFFSET(struct net, sctp.rto_initial),
+		.uint_limits = {
+			.min_value	= SYSCTL_UINT_ONE,
+			.max_value	= &timer_max,
+		},
 	},
 	{
 		.procname	= "rto_alpha_exp_divisor",
-		.data		= &init_net.sctp.rto_alpha,
-		.maxlen		= sizeof(int),
 		.mode		= 0644,
-		.proc_handler	= proc_sctp_do_alpha_beta,
-		.extra1		= &rto_alpha_min,
-		.extra2		= &rto_alpha_max,
+		.type		= SYSCTL_FIELD_INT,
+		.proc_handler	= proc_sctp_do_alpha,
+		.data_offset	= SYSCTL_FIELD_INT_OFFSET(struct net, sctp.rto_alpha),
 	},
 	{
 		.procname	= "rto_beta_exp_divisor",
-		.data		= &init_net.sctp.rto_beta,
-		.maxlen		= sizeof(int),
 		.mode		= 0644,
-		.proc_handler	= proc_sctp_do_alpha_beta,
-		.extra1		= &rto_beta_min,
-		.extra2		= &rto_beta_max,
+		.type		= SYSCTL_FIELD_INT,
+		.proc_handler	= proc_sctp_do_beta,
+		.data_offset	= SYSCTL_FIELD_INT_OFFSET(struct net, sctp.rto_beta),
 	},
 	{
 		.procname	= "max_burst",
-		.data		= &init_net.sctp.max_burst,
-		.maxlen		= sizeof(int),
 		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_INT_MAX,
+		.type		= SYSCTL_FIELD_INT_MINMAX,
+		.data_offset	= SYSCTL_FIELD_INT_OFFSET(struct net, sctp.max_burst),
+		.int_limits = {
+			.min_value	= SYSCTL_ZERO,
+			.max_value	= SYSCTL_INT_MAX,
+		},
 	},
 	{
 		.procname	= "cookie_preserve_enable",
-		.data		= &init_net.sctp.cookie_preserve_enable,
-		.maxlen		= sizeof(int),
 		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
+		.type		= SYSCTL_FIELD_INT,
+		.data_offset	= SYSCTL_FIELD_INT_OFFSET(struct net, sctp.cookie_preserve_enable),
 	},
 	{
 		.procname	= "cookie_hmac_alg",
-		.data		= &init_net.sctp.cookie_auth_enable,
-		.maxlen		= 8,
 		.mode		= 0644,
+		.type		= SYSCTL_FIELD_INT,
 		.proc_handler	= proc_sctp_do_hmac_alg,
+		.maxlen		= 8,
+		.data_offset	= SYSCTL_FIELD_INT_OFFSET(struct net, sctp.cookie_auth_enable),
 	},
 	{
 		.procname	= "valid_cookie_life",
-		.data		= &init_net.sctp.valid_cookie_life,
-		.maxlen		= sizeof(unsigned int),
 		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1         = SYSCTL_ONE,
-		.extra2         = &timer_max
+		.type		= SYSCTL_FIELD_UINT_MINMAX,
+		.data_offset	= SYSCTL_FIELD_UINT_OFFSET(struct net, sctp.valid_cookie_life),
+		.uint_limits = {
+			.min_value	= SYSCTL_UINT_ONE,
+			.max_value	= &timer_max,
+		},
 	},
 	{
 		.procname	= "sack_timeout",
-		.data		= &init_net.sctp.sack_timeout,
-		.maxlen		= sizeof(int),
 		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1         = &sack_timer_min,
-		.extra2         = &sack_timer_max,
+		.type		= SYSCTL_FIELD_UINT_MINMAX,
+		.data_offset	= SYSCTL_FIELD_UINT_OFFSET(struct net, sctp.sack_timeout),
+		.uint_limits = {
+			.min_value	= &sack_timer_min,
+			.max_value	= &sack_timer_max,
+		},
 	},
 	{
 		.procname	= "hb_interval",
-		.data		= &init_net.sctp.hb_interval,
-		.maxlen		= sizeof(unsigned int),
 		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1         = SYSCTL_ONE,
-		.extra2         = &timer_max
+		.type		= SYSCTL_FIELD_UINT_MINMAX,
+		.data_offset	= SYSCTL_FIELD_UINT_OFFSET(struct net, sctp.hb_interval),
+		.uint_limits = {
+			.min_value	= SYSCTL_UINT_ONE,
+			.max_value	= &timer_max,
+		},
 	},
 	{
 		.procname	= "association_max_retrans",
-		.data		= &init_net.sctp.max_retrans_association,
-		.maxlen		= sizeof(int),
 		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ONE,
-		.extra2		= SYSCTL_INT_MAX,
+		.type		= SYSCTL_FIELD_INT_MINMAX,
+		.data_offset	= SYSCTL_FIELD_INT_OFFSET(struct net, sctp.max_retrans_association),
+		.int_limits = {
+			.min_value	= SYSCTL_ONE,
+			.max_value	= SYSCTL_INT_MAX,
+		},
 	},
 	{
 		.procname	= "path_max_retrans",
-		.data		= &init_net.sctp.max_retrans_path,
-		.maxlen		= sizeof(int),
 		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ONE,
-		.extra2		= SYSCTL_INT_MAX,
+		.type		= SYSCTL_FIELD_INT_MINMAX,
+		.data_offset	= SYSCTL_FIELD_INT_OFFSET(struct net, sctp.max_retrans_path),
+		.int_limits = {
+			.min_value	= SYSCTL_ONE,
+			.max_value	= SYSCTL_INT_MAX,
+		},
 	},
 	{
 		.procname	= "max_init_retransmits",
-		.data		= &init_net.sctp.max_retrans_init,
-		.maxlen		= sizeof(int),
 		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ONE,
-		.extra2		= SYSCTL_INT_MAX,
+		.type		= SYSCTL_FIELD_INT_MINMAX,
+		.data_offset	= SYSCTL_FIELD_INT_OFFSET(struct net, sctp.max_retrans_init),
+		.int_limits = {
+			.min_value	= SYSCTL_ONE,
+			.max_value	= SYSCTL_INT_MAX,
+		},
 	},
 	{
 		.procname	= "sndbuf_policy",
-		.data		= &init_net.sctp.sndbuf_policy,
-		.maxlen		= sizeof(int),
 		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
+		.type		= SYSCTL_FIELD_INT,
+		.data_offset	= SYSCTL_FIELD_INT_OFFSET(struct net, sctp.sndbuf_policy),
 	},
 	{
 		.procname	= "rcvbuf_policy",
-		.data		= &init_net.sctp.rcvbuf_policy,
-		.maxlen		= sizeof(int),
 		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
+		.type		= SYSCTL_FIELD_INT,
+		.data_offset	= SYSCTL_FIELD_INT_OFFSET(struct net, sctp.rcvbuf_policy),
 	},
 	{
 		.procname	= "default_auto_asconf",
-		.data		= &init_net.sctp.default_auto_asconf,
-		.maxlen		= sizeof(int),
 		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
+		.type		= SYSCTL_FIELD_INT,
+		.data_offset	= SYSCTL_FIELD_INT_OFFSET(struct net, sctp.default_auto_asconf),
 	},
 	{
 		.procname	= "addip_enable",
-		.data		= &init_net.sctp.addip_enable,
-		.maxlen		= sizeof(int),
 		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
+		.type		= SYSCTL_FIELD_INT,
+		.data_offset	= SYSCTL_FIELD_INT_OFFSET(struct net, sctp.addip_enable),
 	},
 	{
 		.procname	= "addip_noauth_enable",
-		.data		= &init_net.sctp.addip_noauth,
-		.maxlen		= sizeof(int),
 		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
+		.type		= SYSCTL_FIELD_INT,
+		.data_offset	= SYSCTL_FIELD_INT_OFFSET(struct net, sctp.addip_noauth),
 	},
 	{
 		.procname	= "prsctp_enable",
-		.data		= &init_net.sctp.prsctp_enable,
-		.maxlen		= sizeof(int),
 		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
+		.type		= SYSCTL_FIELD_INT,
+		.data_offset	= SYSCTL_FIELD_INT_OFFSET(struct net, sctp.prsctp_enable),
 	},
 	{
 		.procname	= "reconf_enable",
-		.data		= &init_net.sctp.reconf_enable,
-		.maxlen		= sizeof(int),
 		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
+		.type		= SYSCTL_FIELD_INT,
+		.data_offset	= SYSCTL_FIELD_INT_OFFSET(struct net, sctp.reconf_enable),
 	},
 	{
 		.procname	= "auth_enable",
-		.data		= &init_net.sctp.auth_enable,
-		.maxlen		= sizeof(int),
 		.mode		= 0644,
+		.type		= SYSCTL_FIELD_INT,
 		.proc_handler	= proc_sctp_do_auth,
+		.data_offset	= SYSCTL_FIELD_INT_OFFSET(struct net, sctp.auth_enable),
 	},
 	{
 		.procname	= "intl_enable",
-		.data		= &init_net.sctp.intl_enable,
-		.maxlen		= sizeof(int),
 		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
+		.type		= SYSCTL_FIELD_INT,
+		.data_offset	= SYSCTL_FIELD_INT_OFFSET(struct net, sctp.intl_enable),
 	},
 	{
 		.procname	= "ecn_enable",
-		.data		= &init_net.sctp.ecn_enable,
-		.maxlen		= sizeof(int),
 		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
+		.type		= SYSCTL_FIELD_INT,
+		.data_offset	= SYSCTL_FIELD_INT_OFFSET(struct net, sctp.ecn_enable),
 	},
 	{
 		.procname	= "plpmtud_probe_interval",
-		.data		= &init_net.sctp.probe_interval,
-		.maxlen		= sizeof(int),
 		.mode		= 0644,
+		.type		= SYSCTL_FIELD_UINT,
 		.proc_handler	= proc_sctp_do_probe_interval,
+		.data_offset	= SYSCTL_FIELD_UINT_OFFSET(struct net, sctp.probe_interval),
 	},
 	{
 		.procname	= "udp_port",
-		.data		= &init_net.sctp.udp_port,
-		.maxlen		= sizeof(int),
 		.mode		= 0644,
+		.type		= SYSCTL_FIELD_INT,
 		.proc_handler	= proc_sctp_do_udp_port,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= &udp_port_max,
+		.data_offset	= SYSCTL_FIELD_INT_OFFSET(struct net, sctp.udp_port),
 	},
 	{
 		.procname	= "encap_port",
-		.data		= &init_net.sctp.encap_port,
-		.maxlen		= sizeof(int),
 		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= &udp_port_max,
+		.type		= SYSCTL_FIELD_INT_MINMAX,
+		.data_offset	= SYSCTL_FIELD_INT_OFFSET(struct net, sctp.encap_port),
+		.int_limits = {
+			.min_value	= SYSCTL_ZERO,
+			.max_value	= &udp_port_max,
+		},
 	},
 	{
 		.procname	= "addr_scope_policy",
-		.data		= &init_net.sctp.scope_policy,
-		.maxlen		= sizeof(int),
 		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= &addr_scope_max,
+		.type		= SYSCTL_FIELD_INT_MINMAX,
+		.data_offset	= SYSCTL_FIELD_INT_OFFSET(struct net, sctp.scope_policy),
+		.int_limits = {
+			.min_value	= SYSCTL_ZERO,
+			.max_value	= &addr_scope_max,
+		},
 	},
 	{
 		.procname	= "rwnd_update_shift",
-		.data		= &init_net.sctp.rwnd_upd_shift,
-		.maxlen		= sizeof(int),
 		.mode		= 0644,
-		.proc_handler	= &proc_dointvec_minmax,
-		.extra1		= SYSCTL_ONE,
-		.extra2		= &rwnd_scale_max,
+		.type		= SYSCTL_FIELD_INT_MINMAX,
+		.data_offset	= SYSCTL_FIELD_INT_OFFSET(struct net, sctp.rwnd_upd_shift),
+		.int_limits = {
+			.min_value	= SYSCTL_ONE,
+			.max_value	= &rwnd_scale_max,
+		},
 	},
 	{
 		.procname	= "max_autoclose",
-		.data		= &init_net.sctp.max_autoclose,
-		.maxlen		= sizeof(unsigned long),
 		.mode		= 0644,
-		.proc_handler	= &proc_doulongvec_minmax,
-		.extra1		= &max_autoclose_min,
-		.extra2		= &max_autoclose_max,
+		.type		= SYSCTL_FIELD_ULONG_MINMAX,
+		.data_offset	= SYSCTL_FIELD_ULONG_OFFSET(struct net, sctp.max_autoclose),
+		.ulong_limits = {
+			.min_value	= &max_autoclose_min,
+			.max_value	= &max_autoclose_max,
+		},
 	},
 #ifdef CONFIG_NET_L3_MASTER_DEV
 	{
 		.procname	= "l3mdev_accept",
-		.data		= &init_net.sctp.l3mdev_accept,
-		.maxlen		= sizeof(int),
 		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_ONE,
+		.type		= SYSCTL_FIELD_INT_MINMAX,
+		.data_offset	= SYSCTL_FIELD_INT_OFFSET(struct net, sctp.l3mdev_accept),
+		.int_limits = {
+			.min_value	= SYSCTL_ZERO,
+			.max_value	= SYSCTL_ONE,
+		},
 	},
 #endif
 	{
 		.procname	= "pf_enable",
-		.data		= &init_net.sctp.pf_enable,
-		.maxlen		= sizeof(int),
 		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
+		.type		= SYSCTL_FIELD_INT,
+		.data_offset	= SYSCTL_FIELD_INT_OFFSET(struct net, sctp.pf_enable),
 	},
 	{
 		.procname	= "pf_expose",
-		.data		= &init_net.sctp.pf_expose,
-		.maxlen		= sizeof(int),
 		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= &pf_expose_max,
+		.type		= SYSCTL_FIELD_INT_MINMAX,
+		.data_offset	= SYSCTL_FIELD_INT_OFFSET(struct net, sctp.pf_expose),
+		.int_limits = {
+			.min_value	= SYSCTL_ZERO,
+			.max_value	= &pf_expose_max,
+		},
 	},
 };
 
@@ -423,8 +411,6 @@ static int proc_sctp_do_rto_min(const struct ctl_table *ctl, int write,
 				void *buffer, size_t *lenp, loff_t *ppos)
 {
 	struct net *net = container_of(ctl->data, struct net, sctp.rto_min);
-	unsigned int min = *(unsigned int *) ctl->extra1;
-	unsigned int max = *(unsigned int *) ctl->extra2;
 	struct ctl_table tbl;
 	int ret, new_value;
 
@@ -438,7 +424,7 @@ static int proc_sctp_do_rto_min(const struct ctl_table *ctl, int write,
 
 	ret = proc_dointvec(&tbl, write, buffer, lenp, ppos);
 	if (write && ret == 0) {
-		if (new_value > max || new_value < min)
+		if (new_value > net->sctp.rto_max || new_value < 1)
 			return -EINVAL;
 
 		net->sctp.rto_min = new_value;
@@ -451,8 +437,6 @@ static int proc_sctp_do_rto_max(const struct ctl_table *ctl, int write,
 				void *buffer, size_t *lenp, loff_t *ppos)
 {
 	struct net *net = container_of(ctl->data, struct net, sctp.rto_max);
-	unsigned int min = *(unsigned int *) ctl->extra1;
-	unsigned int max = *(unsigned int *) ctl->extra2;
 	struct ctl_table tbl;
 	int ret, new_value;
 
@@ -466,7 +450,7 @@ static int proc_sctp_do_rto_max(const struct ctl_table *ctl, int write,
 
 	ret = proc_dointvec(&tbl, write, buffer, lenp, ppos);
 	if (write && ret == 0) {
-		if (new_value > max || new_value < min)
+		if (new_value > timer_max || new_value < net->sctp.rto_min)
 			return -EINVAL;
 
 		net->sctp.rto_max = new_value;
@@ -475,6 +459,30 @@ static int proc_sctp_do_rto_max(const struct ctl_table *ctl, int write,
 	return ret;
 }
 
+static int proc_sctp_do_pf_retrans(const struct ctl_table *ctl, int write,
+				   void *buffer, size_t *lenp, loff_t *ppos)
+{
+	struct net *net = container_of(ctl->data, struct net, sctp.pf_retrans);
+	struct ctl_table table = *ctl;
+
+	table.extra1 = SYSCTL_ZERO;
+	table.extra2 = &net->sctp.ps_retrans;
+
+	return proc_dointvec_minmax(&table, write, buffer, lenp, ppos);
+}
+
+static int proc_sctp_do_ps_retrans(const struct ctl_table *ctl, int write,
+				   void *buffer, size_t *lenp, loff_t *ppos)
+{
+	struct net *net = container_of(ctl->data, struct net, sctp.ps_retrans);
+	struct ctl_table table = *ctl;
+
+	table.extra1 = &net->sctp.pf_retrans;
+	table.extra2 = &ps_retrans_max;
+
+	return proc_dointvec_minmax(&table, write, buffer, lenp, ppos);
+}
+
 static int proc_sctp_do_alpha_beta(const struct ctl_table *ctl, int write,
 				   void *buffer, size_t *lenp, loff_t *ppos)
 {
@@ -485,6 +493,28 @@ static int proc_sctp_do_alpha_beta(const struct ctl_table *ctl, int write,
 	return proc_dointvec_minmax(ctl, write, buffer, lenp, ppos);
 }
 
+static int proc_sctp_do_alpha(const struct ctl_table *ctl, int write,
+			      void *buffer, size_t *lenp, loff_t *ppos)
+{
+	struct ctl_table table = *ctl;
+
+	table.extra1 = &rto_alpha_min;
+	table.extra2 = &rto_alpha_max;
+
+	return proc_sctp_do_alpha_beta(&table, write, buffer, lenp, ppos);
+}
+
+static int proc_sctp_do_beta(const struct ctl_table *ctl, int write,
+			     void *buffer, size_t *lenp, loff_t *ppos)
+{
+	struct ctl_table table = *ctl;
+
+	table.extra1 = &rto_beta_min;
+	table.extra2 = &rto_beta_max;
+
+	return proc_sctp_do_alpha_beta(&table, write, buffer, lenp, ppos);
+}
+
 static int proc_sctp_do_auth(const struct ctl_table *ctl, int write,
 			     void *buffer, size_t *lenp, loff_t *ppos)
 {
@@ -520,8 +550,6 @@ static int proc_sctp_do_udp_port(const struct ctl_table *ctl, int write,
 				 void *buffer, size_t *lenp, loff_t *ppos)
 {
 	struct net *net = container_of(ctl->data, struct net, sctp.udp_port);
-	unsigned int min = *(unsigned int *)ctl->extra1;
-	unsigned int max = *(unsigned int *)ctl->extra2;
 	struct ctl_table tbl;
 	int ret, new_value;
 
@@ -537,7 +565,7 @@ static int proc_sctp_do_udp_port(const struct ctl_table *ctl, int write,
 	if (write && ret == 0) {
 		struct sock *sk = net->sctp.ctl_sock;
 
-		if (new_value > max || new_value < min)
+		if (new_value > udp_port_max || new_value < 0)
 			return -EINVAL;
 
 		mutex_lock(&sctp_sysctl_mutex);
@@ -588,38 +616,24 @@ static int proc_sctp_do_probe_interval(const struct ctl_table *ctl, int write,
 
 int sctp_sysctl_net_register(struct net *net)
 {
-	size_t table_size = ARRAY_SIZE(sctp_net_table);
-	struct ctl_table *table;
-	int i;
-
-	table = kmemdup(sctp_net_table, sizeof(sctp_net_table), GFP_KERNEL);
-	if (!table)
+	struct sysctl_context ctx = {
+		.type = SYSCTL_CONTEXT_NET_NS,
+		.object_size = sizeof(init_net),
+		.ns.net_ns = net,
+	};
+
+	net->sctp.sysctl_header =
+		register_sysctl_fields(&net->sysctls, "net/sctp", sctp_net_table,
+				       &ctx);
+	if (!net->sctp.sysctl_header)
 		return -ENOMEM;
 
-	for (i = 0; i < table_size; i++)
-		table[i].data += (char *)(&net->sctp) - (char *)&init_net.sctp;
-
-	table[SCTP_RTO_MIN_IDX].extra2 = &net->sctp.rto_max;
-	table[SCTP_RTO_MAX_IDX].extra1 = &net->sctp.rto_min;
-	table[SCTP_PF_RETRANS_IDX].extra2 = &net->sctp.ps_retrans;
-	table[SCTP_PS_RETRANS_IDX].extra1 = &net->sctp.pf_retrans;
-
-	net->sctp.sysctl_header = register_net_sysctl_sz(net, "net/sctp",
-							 table, table_size);
-	if (net->sctp.sysctl_header == NULL) {
-		kfree(table);
-		return -ENOMEM;
-	}
 	return 0;
 }
 
 void sctp_sysctl_net_unregister(struct net *net)
 {
-	const struct ctl_table *table;
-
-	table = net->sctp.sysctl_header->ctl_table_arg;
 	unregister_net_sysctl_table(net->sctp.sysctl_header);
-	kfree(table);
 }
 
 static struct ctl_table_header *sctp_sysctl_header;
-- 
2.55.0


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [RFC PATCH 4/4] mpls: use typed fields for per-device sysctls
  2026-08-29 16:14     ` Alexey Gladkov
                         ` (2 preceding siblings ...)
  2026-08-29 16:14       ` [RFC PATCH 3/4] sctp: use typed fields for per-net sysctls Alexey Gladkov
@ 2026-08-29 16:14       ` Alexey Gladkov
  2026-08-30 15:38       ` [RFC PATCH v1 17/30] sysctl: net: use sysctl_field in SCTP sysctls Linus Torvalds
  4 siblings, 0 replies; 37+ messages in thread
From: Alexey Gladkov @ 2026-08-29 16:14 UTC (permalink / raw)
  To: Linus Torvalds
  Cc: Eric W . Biederman, Kees Cook, Joel Granados, LKML, linux-fsdevel

MPLS clones its device sysctl table and converts relative data pointers
into mpls_dev addresses at registration time. It also stores the device
and network namespace in extra1 and extra2 for the handler.

Use a context wrapper to retain both objects and select mpls_dev as the
backing object for typed field offsets. The static field table can then
be shared by all devices without allocating and rewriting a ctl_table
array.

Signed-off-by: Alexey Gladkov <legion@kernel.org>
---
 net/mpls/af_mpls.c | 66 ++++++++++++++++++++++------------------------
 1 file changed, 31 insertions(+), 35 deletions(-)

diff --git a/net/mpls/af_mpls.c b/net/mpls/af_mpls.c
index 26340a7306b5..725df199cf1d 100644
--- a/net/mpls/af_mpls.c
+++ b/net/mpls/af_mpls.c
@@ -1387,8 +1387,18 @@ static int mpls_netconf_dump_devconf(struct sk_buff *skb,
 	return err;
 }
 
-#define MPLS_PERDEV_SYSCTL_OFFSET(field)	\
-	(&((struct mpls_dev *)0)->field)
+struct mpls_dev_sysctl_context {
+	struct sysctl_context context;
+	struct mpls_dev *mdev;
+};
+
+static void *mpls_dev_sysctl_object(const struct sysctl_context *ctx)
+{
+	const struct mpls_dev_sysctl_context *mpls_ctx;
+
+	mpls_ctx = container_of(ctx, struct mpls_dev_sysctl_context, context);
+	return mpls_ctx->mdev;
+}
 
 static int mpls_conf_proc(const struct ctl_table *ctl, int write,
 			  void *buffer, size_t *lenp, loff_t *ppos)
@@ -1397,14 +1407,12 @@ static int mpls_conf_proc(const struct ctl_table *ctl, int write,
 	int ret = proc_dointvec(ctl, write, buffer, lenp, ppos);
 
 	if (write) {
-		struct mpls_dev *mdev = ctl->extra1;
-		int i = (int *)ctl->data - (int *)mdev;
-		struct net *net = ctl->extra2;
+		struct mpls_dev *mdev;
 		int val = *(int *)ctl->data;
 
-		if (i == offsetof(struct mpls_dev, input_enabled) &&
-		    val != oval) {
-			mpls_netconf_notify_devconf(net, RTM_NEWNETCONF,
+		mdev = container_of(ctl->data, struct mpls_dev, input_enabled);
+		if (val != oval) {
+			mpls_netconf_notify_devconf(dev_net(mdev->dev), RTM_NEWNETCONF,
 						    NETCONFA_INPUT, mdev);
 		}
 	}
@@ -1412,13 +1420,13 @@ static int mpls_conf_proc(const struct ctl_table *ctl, int write,
 	return ret;
 }
 
-static const struct ctl_table mpls_dev_table[] = {
+static const struct sysctl_field mpls_dev_table[] = {
 	{
 		.procname	= "input",
-		.maxlen		= sizeof(int),
 		.mode		= 0644,
+		.type		= SYSCTL_FIELD_INT,
 		.proc_handler	= mpls_conf_proc,
-		.data		= MPLS_PERDEV_SYSCTL_OFFSET(input_enabled),
+		.data_offset	= SYSCTL_FIELD_INT_OFFSET(struct mpls_dev, input_enabled),
 	},
 };
 
@@ -1426,35 +1434,27 @@ static int mpls_dev_sysctl_register(struct net_device *dev,
 				    struct mpls_dev *mdev)
 {
 	char path[sizeof("net/mpls/conf/") + IFNAMSIZ];
-	size_t table_size = ARRAY_SIZE(mpls_dev_table);
 	struct net *net = dev_net(dev);
-	struct ctl_table *table;
-	int i;
-
-	table = kmemdup(&mpls_dev_table, sizeof(mpls_dev_table), GFP_KERNEL);
-	if (!table)
-		goto out;
-
-	/* Table data contains only offsets relative to the base of
-	 * the mdev at this point, so make them absolute.
-	 */
-	for (i = 0; i < table_size; i++) {
-		table[i].data = (char *)mdev + (uintptr_t)table[i].data;
-		table[i].extra1 = mdev;
-		table[i].extra2 = net;
-	}
+	struct mpls_dev_sysctl_context ctx = {
+		.context = {
+			.object_size = sizeof(*mdev),
+			.object = mpls_dev_sysctl_object,
+		},
+		.mdev = mdev,
+	};
 
 	snprintf(path, sizeof(path), "net/mpls/conf/%s", dev->name);
 
-	mdev->sysctl = register_net_sysctl_sz(net, path, table, table_size);
+	mdev->sysctl =
+		__register_sysctl_fields(&net->sysctls, path, mpls_dev_table,
+					 ARRAY_SIZE(mpls_dev_table), &ctx.context,
+					 sizeof(ctx));
 	if (!mdev->sysctl)
-		goto free;
+		goto out;
 
 	mpls_netconf_notify_devconf(net, RTM_NEWNETCONF, NETCONFA_ALL, mdev);
 	return 0;
 
-free:
-	kfree(table);
 out:
 	mdev->sysctl = NULL;
 	return -ENOBUFS;
@@ -1464,14 +1464,10 @@ static void mpls_dev_sysctl_unregister(struct net_device *dev,
 				       struct mpls_dev *mdev)
 {
 	struct net *net = dev_net(dev);
-	const struct ctl_table *table;
-
 	if (!mdev->sysctl)
 		return;
 
-	table = mdev->sysctl->ctl_table_arg;
 	unregister_net_sysctl_table(mdev->sysctl);
-	kfree(table);
 
 	mpls_netconf_notify_devconf(net, RTM_DELNETCONF, 0, mdev);
 }
-- 
2.55.0


^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [RFC PATCH v1 17/30] sysctl: net: use sysctl_field in SCTP sysctls
  2026-08-29 16:14     ` Alexey Gladkov
                         ` (3 preceding siblings ...)
  2026-08-29 16:14       ` [RFC PATCH 4/4] mpls: use typed fields for per-device sysctls Alexey Gladkov
@ 2026-08-30 15:38       ` Linus Torvalds
  4 siblings, 0 replies; 37+ messages in thread
From: Linus Torvalds @ 2026-08-30 15:38 UTC (permalink / raw)
  To: Alexey Gladkov
  Cc: Eric W . Biederman, Kees Cook, Joel Granados, LKML, linux-fsdevel

On Sat, 29 Aug 2026 at 09:15, Alexey Gladkov <legion@kernel.org> wrote:
>
> Does this look better for you, or should I just stop?

This certainly looks a lot less disruptive, and seems to allow
subsystems to migrate to the new model incrementally.

The conversion patches for ipc and mpls looked sane to me. I imagine
the other various cases would be similar. So I don't see any huge
issues here - obviously the affected subsystem maintainers may have
some (I suspect mainly networking), but the whole "avoid having to
copying the tables" is certainly a worthy goal.

            Linus

^ permalink raw reply	[flat|nested] 37+ messages in thread

end of thread, other threads:[~2026-08-30 15:38 UTC | newest]

Thread overview: 37+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
     [not found] <cover.1787771905.git.legion@kernel.org>
2026-08-26 19:42 ` [RFC PATCH v1 01/30] proc: sysctl: address table entries by index Alexey Gladkov
2026-08-26 19:42 ` [RFC PATCH v1 02/30] sysctl: add unsigned int limit constants Alexey Gladkov
2026-08-26 19:42 ` [RFC PATCH v1 03/30] sysctl: add typed field descriptors Alexey Gladkov
2026-08-26 19:42 ` [RFC PATCH v1 04/30] sysctl: use sysctl_field in ucounts Alexey Gladkov
2026-08-26 19:42 ` [RFC PATCH v1 05/30] sysctl: ipc: use sysctl_field in mq_sysctl Alexey Gladkov
2026-08-26 19:42 ` [RFC PATCH v1 06/30] sysctl: ipc: use sysctl_field in ipc_sysctl Alexey Gladkov
2026-08-26 19:42 ` [RFC PATCH v1 07/30] sysctl: use sysctl_field in pid sysctls Alexey Gladkov
2026-08-26 19:42 ` [RFC PATCH v1 08/30] sysctl: net: use sysctl_field in unix sysctl Alexey Gladkov
2026-08-26 19:42 ` [RFC PATCH v1 09/30] sysctl: net: use sysctl_field in xfrm sysctls Alexey Gladkov
2026-08-26 19:42 ` [RFC PATCH v1 10/30] sysctl: net: use sysctl_field for simple IPv4 per-net sysctls Alexey Gladkov
2026-08-26 19:42 ` [RFC PATCH v1 11/30] sysctl: net: use sysctl_field in IPv4 sysctls Alexey Gladkov
2026-08-26 19:42 ` [RFC PATCH v1 12/30] sysctl: net: use sysctl_field in IPv6 xfrm sysctls Alexey Gladkov
2026-08-26 19:42 ` [RFC PATCH v1 13/30] sysctl: net: use sysctl_field in IPv6 fragment sysctls Alexey Gladkov
2026-08-26 19:42 ` [RFC PATCH v1 14/30] sysctl: net: use sysctl_field in 6lowpan " Alexey Gladkov
2026-08-26 19:42 ` [RFC PATCH v1 15/30] sysctl: net: use sysctl_field in vsock sysctls Alexey Gladkov
2026-08-26 19:42 ` [RFC PATCH v1 16/30] sysctl: net: use sysctl_field in MPTCP sysctls Alexey Gladkov
2026-08-26 19:42 ` [RFC PATCH v1 17/30] sysctl: net: use sysctl_field in SCTP sysctls Alexey Gladkov
2026-08-26 20:29   ` Linus Torvalds
2026-08-29 16:14     ` Alexey Gladkov
2026-08-29 16:14       ` [RFC PATCH 1/4] sysctl: add typed field descriptors Alexey Gladkov
2026-08-29 16:14       ` [RFC PATCH 2/4] sysctl: ipc: use typed fields for IPC namespace sysctls Alexey Gladkov
2026-08-29 16:14       ` [RFC PATCH 3/4] sctp: use typed fields for per-net sysctls Alexey Gladkov
2026-08-29 16:14       ` [RFC PATCH 4/4] mpls: use typed fields for per-device sysctls Alexey Gladkov
2026-08-30 15:38       ` [RFC PATCH v1 17/30] sysctl: net: use sysctl_field in SCTP sysctls Linus Torvalds
2026-08-26 19:42 ` [RFC PATCH v1 18/30] sysctl: net: use sysctl_field in core IPv6 sysctls Alexey Gladkov
2026-08-26 19:42 ` [RFC PATCH v1 19/30] sysctl: net: use sysctl_field in net core per-net sysctls Alexey Gladkov
2026-08-26 19:42 ` [RFC PATCH v1 20/30] sysctl: net: use sysctl_field in SMC sysctls Alexey Gladkov
2026-08-26 19:42 ` [RFC PATCH v1 21/30] sysctl: net: use sysctl_field in VRF sysctls Alexey Gladkov
2026-08-26 19:42 ` [RFC PATCH v1 22/30] sysctl: net: use sysctl_field in RDS sysctls Alexey Gladkov
2026-08-26 19:42 ` [RFC PATCH v1 23/30] sysctl: netfilter: use sysctl_field for per-net sysctls Alexey Gladkov
2026-08-26 19:42 ` [RFC PATCH v1 24/30] sysctl: ipvs: " Alexey Gladkov
2026-08-26 19:42 ` [RFC PATCH v1 25/30] sysctl: bridge: use sysctl_field for br_netfilter sysctls Alexey Gladkov
2026-08-26 19:42 ` [RFC PATCH v1 26/30] sysctl: net: use sysctl_field for MPLS sysctls Alexey Gladkov
2026-08-26 19:42 ` [RFC PATCH v1 27/30] sysctl: net: use sysctl_field in IPv4 devconf sysctls Alexey Gladkov
2026-08-26 19:42 ` [RFC PATCH v1 28/30] sysctl: net: use sysctl_field in IPv6 " Alexey Gladkov
2026-08-26 19:42 ` [RFC PATCH v1 29/30] sysctl: net: use sysctl_field in neighbour sysctls Alexey Gladkov
2026-08-26 19:42 ` [RFC PATCH v1 30/30] sysctl: parport: use sysctl_field for dynamic sysctls Alexey Gladkov

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®