1 // SPDX-License-Identifier: GPL-2.0
2 
3 #include <linux/sysctl.h>
4 #include <net/lwtunnel.h>
5 #include <net/netfilter/nf_hooks_lwtunnel.h>
6 #include <linux/netfilter.h>
7 
8 #include "nf_internals.h"
9 
nf_hooks_lwtunnel_get(void)10 static inline int nf_hooks_lwtunnel_get(void)
11 {
12 	if (static_branch_unlikely(&nf_hooks_lwtunnel_enabled))
13 		return 1;
14 	else
15 		return 0;
16 }
17 
nf_hooks_lwtunnel_set(int enable)18 static inline int nf_hooks_lwtunnel_set(int enable)
19 {
20 	if (static_branch_unlikely(&nf_hooks_lwtunnel_enabled)) {
21 		if (!enable)
22 			return -EBUSY;
23 	} else if (enable) {
24 		static_branch_enable(&nf_hooks_lwtunnel_enabled);
25 	}
26 
27 	return 0;
28 }
29 
30 #ifdef CONFIG_SYSCTL
nf_hooks_lwtunnel_sysctl_handler(const struct ctl_table * table,int write,void * buffer,size_t * lenp,loff_t * ppos)31 int nf_hooks_lwtunnel_sysctl_handler(const struct ctl_table *table, int write,
32 				     void *buffer, size_t *lenp, loff_t *ppos)
33 {
34 	int proc_nf_hooks_lwtunnel_enabled = 0;
35 	struct ctl_table tmp = {
36 		.procname = table->procname,
37 		.data = &proc_nf_hooks_lwtunnel_enabled,
38 		.maxlen = sizeof(int),
39 		.mode = table->mode,
40 		.extra1 = SYSCTL_ZERO,
41 		.extra2 = SYSCTL_ONE,
42 	};
43 	int ret;
44 
45 	if (!write)
46 		proc_nf_hooks_lwtunnel_enabled = nf_hooks_lwtunnel_get();
47 
48 	ret = proc_dointvec_minmax(&tmp, write, buffer, lenp, ppos);
49 
50 	if (write && ret == 0)
51 		ret = nf_hooks_lwtunnel_set(proc_nf_hooks_lwtunnel_enabled);
52 
53 	return ret;
54 }
55 EXPORT_SYMBOL_GPL(nf_hooks_lwtunnel_sysctl_handler);
56 
57 static struct ctl_table nf_lwtunnel_sysctl_table[] = {
58 	{
59 		.procname	= "nf_hooks_lwtunnel",
60 		.data		= NULL,
61 		.maxlen		= sizeof(int),
62 		.mode		= 0644,
63 		.proc_handler	= nf_hooks_lwtunnel_sysctl_handler,
64 	},
65 };
66 
nf_lwtunnel_net_init(struct net * net)67 static int __net_init nf_lwtunnel_net_init(struct net *net)
68 {
69 	struct ctl_table_header *hdr;
70 	struct ctl_table *table;
71 
72 	table = nf_lwtunnel_sysctl_table;
73 	if (!net_eq(net, &init_net)) {
74 		table = kmemdup(nf_lwtunnel_sysctl_table,
75 				sizeof(nf_lwtunnel_sysctl_table),
76 				GFP_KERNEL);
77 		if (!table)
78 			goto err_alloc;
79 	}
80 
81 	hdr = register_net_sysctl_sz(net, "net/netfilter", table,
82 				     ARRAY_SIZE(nf_lwtunnel_sysctl_table));
83 	if (!hdr)
84 		goto err_reg;
85 
86 	net->nf.nf_lwtnl_dir_header = hdr;
87 
88 	return 0;
89 err_reg:
90 	if (!net_eq(net, &init_net))
91 		kfree(table);
92 err_alloc:
93 	return -ENOMEM;
94 }
95 
nf_lwtunnel_net_exit(struct net * net)96 static void __net_exit nf_lwtunnel_net_exit(struct net *net)
97 {
98 	const struct ctl_table *table;
99 
100 	table = net->nf.nf_lwtnl_dir_header->ctl_table_arg;
101 	unregister_net_sysctl_table(net->nf.nf_lwtnl_dir_header);
102 	if (!net_eq(net, &init_net))
103 		kfree(table);
104 }
105 
106 static struct pernet_operations nf_lwtunnel_net_ops = {
107 	.init = nf_lwtunnel_net_init,
108 	.exit = nf_lwtunnel_net_exit,
109 };
110 
netfilter_lwtunnel_init(void)111 int __init netfilter_lwtunnel_init(void)
112 {
113 	return register_pernet_subsys(&nf_lwtunnel_net_ops);
114 }
115 
netfilter_lwtunnel_fini(void)116 void netfilter_lwtunnel_fini(void)
117 {
118 	unregister_pernet_subsys(&nf_lwtunnel_net_ops);
119 }
120 #else
netfilter_lwtunnel_init(void)121 int __init netfilter_lwtunnel_init(void) { return 0; }
netfilter_lwtunnel_fini(void)122 void netfilter_lwtunnel_fini(void) {}
123 #endif /* CONFIG_SYSCTL */
124