diff options
-rw-r--r-- | include/linux/netfilter/x_tables.h | 6 | ||||
-rw-r--r-- | include/net/net_namespace.h | 4 | ||||
-rw-r--r-- | include/net/netns/x_tables.h | 10 | ||||
-rw-r--r-- | net/ipv4/netfilter/arp_tables.c | 12 | ||||
-rw-r--r-- | net/ipv4/netfilter/ip_tables.c | 12 | ||||
-rw-r--r-- | net/ipv6/netfilter/ip6_tables.c | 12 | ||||
-rw-r--r-- | net/netfilter/x_tables.c | 34 |
7 files changed, 60 insertions, 30 deletions
diff --git a/include/linux/netfilter/x_tables.h b/include/linux/netfilter/x_tables.h index 937cebb3ab54..91a1dd5b9c66 100644 --- a/include/linux/netfilter/x_tables.h +++ b/include/linux/netfilter/x_tables.h | |||
@@ -335,7 +335,8 @@ extern int xt_check_target(const struct xt_target *target, unsigned short family | |||
335 | unsigned int size, const char *table, unsigned int hook, | 335 | unsigned int size, const char *table, unsigned int hook, |
336 | unsigned short proto, int inv_proto); | 336 | unsigned short proto, int inv_proto); |
337 | 337 | ||
338 | extern struct xt_table *xt_register_table(struct xt_table *table, | 338 | extern struct xt_table *xt_register_table(struct net *net, |
339 | struct xt_table *table, | ||
339 | struct xt_table_info *bootstrap, | 340 | struct xt_table_info *bootstrap, |
340 | struct xt_table_info *newinfo); | 341 | struct xt_table_info *newinfo); |
341 | extern void *xt_unregister_table(struct xt_table *table); | 342 | extern void *xt_unregister_table(struct xt_table *table); |
@@ -352,7 +353,8 @@ extern struct xt_target *xt_request_find_target(int af, const char *name, | |||
352 | extern int xt_find_revision(int af, const char *name, u8 revision, int target, | 353 | extern int xt_find_revision(int af, const char *name, u8 revision, int target, |
353 | int *err); | 354 | int *err); |
354 | 355 | ||
355 | extern struct xt_table *xt_find_table_lock(int af, const char *name); | 356 | extern struct xt_table *xt_find_table_lock(struct net *net, int af, |
357 | const char *name); | ||
356 | extern void xt_table_unlock(struct xt_table *t); | 358 | extern void xt_table_unlock(struct xt_table *t); |
357 | 359 | ||
358 | extern int xt_proto_init(int af); | 360 | extern int xt_proto_init(int af); |
diff --git a/include/net/net_namespace.h b/include/net/net_namespace.h index b8c1d60ba9e4..28738b7d53eb 100644 --- a/include/net/net_namespace.h +++ b/include/net/net_namespace.h | |||
@@ -12,6 +12,7 @@ | |||
12 | #include <net/netns/packet.h> | 12 | #include <net/netns/packet.h> |
13 | #include <net/netns/ipv4.h> | 13 | #include <net/netns/ipv4.h> |
14 | #include <net/netns/ipv6.h> | 14 | #include <net/netns/ipv6.h> |
15 | #include <net/netns/x_tables.h> | ||
15 | 16 | ||
16 | struct proc_dir_entry; | 17 | struct proc_dir_entry; |
17 | struct net_device; | 18 | struct net_device; |
@@ -56,6 +57,9 @@ struct net { | |||
56 | #if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) | 57 | #if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) |
57 | struct netns_ipv6 ipv6; | 58 | struct netns_ipv6 ipv6; |
58 | #endif | 59 | #endif |
60 | #ifdef CONFIG_NETFILTER | ||
61 | struct netns_xt xt; | ||
62 | #endif | ||
59 | }; | 63 | }; |
60 | 64 | ||
61 | #ifdef CONFIG_NET | 65 | #ifdef CONFIG_NET |
diff --git a/include/net/netns/x_tables.h b/include/net/netns/x_tables.h new file mode 100644 index 000000000000..0cb63ed2c1fc --- /dev/null +++ b/include/net/netns/x_tables.h | |||
@@ -0,0 +1,10 @@ | |||
1 | #ifndef __NETNS_X_TABLES_H | ||
2 | #define __NETNS_X_TABLES_H | ||
3 | |||
4 | #include <linux/list.h> | ||
5 | #include <linux/net.h> | ||
6 | |||
7 | struct netns_xt { | ||
8 | struct list_head tables[NPROTO]; | ||
9 | }; | ||
10 | #endif | ||
diff --git a/net/ipv4/netfilter/arp_tables.c b/net/ipv4/netfilter/arp_tables.c index 060de950e6ac..0da50a4a6578 100644 --- a/net/ipv4/netfilter/arp_tables.c +++ b/net/ipv4/netfilter/arp_tables.c | |||
@@ -870,7 +870,7 @@ static int get_info(void __user *user, int *len, int compat) | |||
870 | if (compat) | 870 | if (compat) |
871 | xt_compat_lock(NF_ARP); | 871 | xt_compat_lock(NF_ARP); |
872 | #endif | 872 | #endif |
873 | t = try_then_request_module(xt_find_table_lock(NF_ARP, name), | 873 | t = try_then_request_module(xt_find_table_lock(&init_net, NF_ARP, name), |
874 | "arptable_%s", name); | 874 | "arptable_%s", name); |
875 | if (t && !IS_ERR(t)) { | 875 | if (t && !IS_ERR(t)) { |
876 | struct arpt_getinfo info; | 876 | struct arpt_getinfo info; |
@@ -926,7 +926,7 @@ static int get_entries(struct arpt_get_entries __user *uptr, int *len) | |||
926 | return -EINVAL; | 926 | return -EINVAL; |
927 | } | 927 | } |
928 | 928 | ||
929 | t = xt_find_table_lock(NF_ARP, get.name); | 929 | t = xt_find_table_lock(&init_net, NF_ARP, get.name); |
930 | if (t && !IS_ERR(t)) { | 930 | if (t && !IS_ERR(t)) { |
931 | struct xt_table_info *private = t->private; | 931 | struct xt_table_info *private = t->private; |
932 | duprintf("t->private->number = %u\n", | 932 | duprintf("t->private->number = %u\n", |
@@ -966,7 +966,7 @@ static int __do_replace(const char *name, unsigned int valid_hooks, | |||
966 | goto out; | 966 | goto out; |
967 | } | 967 | } |
968 | 968 | ||
969 | t = try_then_request_module(xt_find_table_lock(NF_ARP, name), | 969 | t = try_then_request_module(xt_find_table_lock(&init_net, NF_ARP, name), |
970 | "arptable_%s", name); | 970 | "arptable_%s", name); |
971 | if (!t || IS_ERR(t)) { | 971 | if (!t || IS_ERR(t)) { |
972 | ret = t ? PTR_ERR(t) : -ENOENT; | 972 | ret = t ? PTR_ERR(t) : -ENOENT; |
@@ -1132,7 +1132,7 @@ static int do_add_counters(void __user *user, unsigned int len, int compat) | |||
1132 | goto free; | 1132 | goto free; |
1133 | } | 1133 | } |
1134 | 1134 | ||
1135 | t = xt_find_table_lock(NF_ARP, name); | 1135 | t = xt_find_table_lock(&init_net, NF_ARP, name); |
1136 | if (!t || IS_ERR(t)) { | 1136 | if (!t || IS_ERR(t)) { |
1137 | ret = t ? PTR_ERR(t) : -ENOENT; | 1137 | ret = t ? PTR_ERR(t) : -ENOENT; |
1138 | goto free; | 1138 | goto free; |
@@ -1604,7 +1604,7 @@ static int compat_get_entries(struct compat_arpt_get_entries __user *uptr, | |||
1604 | } | 1604 | } |
1605 | 1605 | ||
1606 | xt_compat_lock(NF_ARP); | 1606 | xt_compat_lock(NF_ARP); |
1607 | t = xt_find_table_lock(NF_ARP, get.name); | 1607 | t = xt_find_table_lock(&init_net, NF_ARP, get.name); |
1608 | if (t && !IS_ERR(t)) { | 1608 | if (t && !IS_ERR(t)) { |
1609 | struct xt_table_info *private = t->private; | 1609 | struct xt_table_info *private = t->private; |
1610 | struct xt_table_info info; | 1610 | struct xt_table_info info; |
@@ -1751,7 +1751,7 @@ int arpt_register_table(struct arpt_table *table, | |||
1751 | return ret; | 1751 | return ret; |
1752 | } | 1752 | } |
1753 | 1753 | ||
1754 | new_table = xt_register_table(table, &bootstrap, newinfo); | 1754 | new_table = xt_register_table(&init_net, table, &bootstrap, newinfo); |
1755 | if (IS_ERR(new_table)) { | 1755 | if (IS_ERR(new_table)) { |
1756 | xt_free_table_info(newinfo); | 1756 | xt_free_table_info(newinfo); |
1757 | return PTR_ERR(new_table); | 1757 | return PTR_ERR(new_table); |
diff --git a/net/ipv4/netfilter/ip_tables.c b/net/ipv4/netfilter/ip_tables.c index 1b7c09e4a007..bc22ea421a94 100644 --- a/net/ipv4/netfilter/ip_tables.c +++ b/net/ipv4/netfilter/ip_tables.c | |||
@@ -1112,7 +1112,7 @@ static int get_info(void __user *user, int *len, int compat) | |||
1112 | if (compat) | 1112 | if (compat) |
1113 | xt_compat_lock(AF_INET); | 1113 | xt_compat_lock(AF_INET); |
1114 | #endif | 1114 | #endif |
1115 | t = try_then_request_module(xt_find_table_lock(AF_INET, name), | 1115 | t = try_then_request_module(xt_find_table_lock(&init_net, AF_INET, name), |
1116 | "iptable_%s", name); | 1116 | "iptable_%s", name); |
1117 | if (t && !IS_ERR(t)) { | 1117 | if (t && !IS_ERR(t)) { |
1118 | struct ipt_getinfo info; | 1118 | struct ipt_getinfo info; |
@@ -1170,7 +1170,7 @@ get_entries(struct ipt_get_entries __user *uptr, int *len) | |||
1170 | return -EINVAL; | 1170 | return -EINVAL; |
1171 | } | 1171 | } |
1172 | 1172 | ||
1173 | t = xt_find_table_lock(AF_INET, get.name); | 1173 | t = xt_find_table_lock(&init_net, AF_INET, get.name); |
1174 | if (t && !IS_ERR(t)) { | 1174 | if (t && !IS_ERR(t)) { |
1175 | struct xt_table_info *private = t->private; | 1175 | struct xt_table_info *private = t->private; |
1176 | duprintf("t->private->number = %u\n", private->number); | 1176 | duprintf("t->private->number = %u\n", private->number); |
@@ -1208,7 +1208,7 @@ __do_replace(const char *name, unsigned int valid_hooks, | |||
1208 | goto out; | 1208 | goto out; |
1209 | } | 1209 | } |
1210 | 1210 | ||
1211 | t = try_then_request_module(xt_find_table_lock(AF_INET, name), | 1211 | t = try_then_request_module(xt_find_table_lock(&init_net, AF_INET, name), |
1212 | "iptable_%s", name); | 1212 | "iptable_%s", name); |
1213 | if (!t || IS_ERR(t)) { | 1213 | if (!t || IS_ERR(t)) { |
1214 | ret = t ? PTR_ERR(t) : -ENOENT; | 1214 | ret = t ? PTR_ERR(t) : -ENOENT; |
@@ -1383,7 +1383,7 @@ do_add_counters(void __user *user, unsigned int len, int compat) | |||
1383 | goto free; | 1383 | goto free; |
1384 | } | 1384 | } |
1385 | 1385 | ||
1386 | t = xt_find_table_lock(AF_INET, name); | 1386 | t = xt_find_table_lock(&init_net, AF_INET, name); |
1387 | if (!t || IS_ERR(t)) { | 1387 | if (!t || IS_ERR(t)) { |
1388 | ret = t ? PTR_ERR(t) : -ENOENT; | 1388 | ret = t ? PTR_ERR(t) : -ENOENT; |
1389 | goto free; | 1389 | goto free; |
@@ -1924,7 +1924,7 @@ compat_get_entries(struct compat_ipt_get_entries __user *uptr, int *len) | |||
1924 | } | 1924 | } |
1925 | 1925 | ||
1926 | xt_compat_lock(AF_INET); | 1926 | xt_compat_lock(AF_INET); |
1927 | t = xt_find_table_lock(AF_INET, get.name); | 1927 | t = xt_find_table_lock(&init_net, AF_INET, get.name); |
1928 | if (t && !IS_ERR(t)) { | 1928 | if (t && !IS_ERR(t)) { |
1929 | struct xt_table_info *private = t->private; | 1929 | struct xt_table_info *private = t->private; |
1930 | struct xt_table_info info; | 1930 | struct xt_table_info info; |
@@ -2075,7 +2075,7 @@ int ipt_register_table(struct xt_table *table, const struct ipt_replace *repl) | |||
2075 | return ret; | 2075 | return ret; |
2076 | } | 2076 | } |
2077 | 2077 | ||
2078 | new_table = xt_register_table(table, &bootstrap, newinfo); | 2078 | new_table = xt_register_table(&init_net, table, &bootstrap, newinfo); |
2079 | if (IS_ERR(new_table)) { | 2079 | if (IS_ERR(new_table)) { |
2080 | xt_free_table_info(newinfo); | 2080 | xt_free_table_info(newinfo); |
2081 | return PTR_ERR(new_table); | 2081 | return PTR_ERR(new_table); |
diff --git a/net/ipv6/netfilter/ip6_tables.c b/net/ipv6/netfilter/ip6_tables.c index f7a62398d7b7..1aac3ef39414 100644 --- a/net/ipv6/netfilter/ip6_tables.c +++ b/net/ipv6/netfilter/ip6_tables.c | |||
@@ -1138,7 +1138,7 @@ static int get_info(void __user *user, int *len, int compat) | |||
1138 | if (compat) | 1138 | if (compat) |
1139 | xt_compat_lock(AF_INET6); | 1139 | xt_compat_lock(AF_INET6); |
1140 | #endif | 1140 | #endif |
1141 | t = try_then_request_module(xt_find_table_lock(AF_INET6, name), | 1141 | t = try_then_request_module(xt_find_table_lock(&init_net, AF_INET6, name), |
1142 | "ip6table_%s", name); | 1142 | "ip6table_%s", name); |
1143 | if (t && !IS_ERR(t)) { | 1143 | if (t && !IS_ERR(t)) { |
1144 | struct ip6t_getinfo info; | 1144 | struct ip6t_getinfo info; |
@@ -1196,7 +1196,7 @@ get_entries(struct ip6t_get_entries __user *uptr, int *len) | |||
1196 | return -EINVAL; | 1196 | return -EINVAL; |
1197 | } | 1197 | } |
1198 | 1198 | ||
1199 | t = xt_find_table_lock(AF_INET6, get.name); | 1199 | t = xt_find_table_lock(&init_net, AF_INET6, get.name); |
1200 | if (t && !IS_ERR(t)) { | 1200 | if (t && !IS_ERR(t)) { |
1201 | struct xt_table_info *private = t->private; | 1201 | struct xt_table_info *private = t->private; |
1202 | duprintf("t->private->number = %u\n", private->number); | 1202 | duprintf("t->private->number = %u\n", private->number); |
@@ -1235,7 +1235,7 @@ __do_replace(const char *name, unsigned int valid_hooks, | |||
1235 | goto out; | 1235 | goto out; |
1236 | } | 1236 | } |
1237 | 1237 | ||
1238 | t = try_then_request_module(xt_find_table_lock(AF_INET6, name), | 1238 | t = try_then_request_module(xt_find_table_lock(&init_net, AF_INET6, name), |
1239 | "ip6table_%s", name); | 1239 | "ip6table_%s", name); |
1240 | if (!t || IS_ERR(t)) { | 1240 | if (!t || IS_ERR(t)) { |
1241 | ret = t ? PTR_ERR(t) : -ENOENT; | 1241 | ret = t ? PTR_ERR(t) : -ENOENT; |
@@ -1410,7 +1410,7 @@ do_add_counters(void __user *user, unsigned int len, int compat) | |||
1410 | goto free; | 1410 | goto free; |
1411 | } | 1411 | } |
1412 | 1412 | ||
1413 | t = xt_find_table_lock(AF_INET6, name); | 1413 | t = xt_find_table_lock(&init_net, AF_INET6, name); |
1414 | if (!t || IS_ERR(t)) { | 1414 | if (!t || IS_ERR(t)) { |
1415 | ret = t ? PTR_ERR(t) : -ENOENT; | 1415 | ret = t ? PTR_ERR(t) : -ENOENT; |
1416 | goto free; | 1416 | goto free; |
@@ -1950,7 +1950,7 @@ compat_get_entries(struct compat_ip6t_get_entries __user *uptr, int *len) | |||
1950 | } | 1950 | } |
1951 | 1951 | ||
1952 | xt_compat_lock(AF_INET6); | 1952 | xt_compat_lock(AF_INET6); |
1953 | t = xt_find_table_lock(AF_INET6, get.name); | 1953 | t = xt_find_table_lock(&init_net, AF_INET6, get.name); |
1954 | if (t && !IS_ERR(t)) { | 1954 | if (t && !IS_ERR(t)) { |
1955 | struct xt_table_info *private = t->private; | 1955 | struct xt_table_info *private = t->private; |
1956 | struct xt_table_info info; | 1956 | struct xt_table_info info; |
@@ -2101,7 +2101,7 @@ int ip6t_register_table(struct xt_table *table, const struct ip6t_replace *repl) | |||
2101 | return ret; | 2101 | return ret; |
2102 | } | 2102 | } |
2103 | 2103 | ||
2104 | new_table = xt_register_table(table, &bootstrap, newinfo); | 2104 | new_table = xt_register_table(&init_net, table, &bootstrap, newinfo); |
2105 | if (IS_ERR(new_table)) { | 2105 | if (IS_ERR(new_table)) { |
2106 | xt_free_table_info(newinfo); | 2106 | xt_free_table_info(newinfo); |
2107 | return PTR_ERR(new_table); | 2107 | return PTR_ERR(new_table); |
diff --git a/net/netfilter/x_tables.c b/net/netfilter/x_tables.c index d8d8637739ba..d62f722ccccb 100644 --- a/net/netfilter/x_tables.c +++ b/net/netfilter/x_tables.c | |||
@@ -44,7 +44,6 @@ struct xt_af { | |||
44 | struct mutex mutex; | 44 | struct mutex mutex; |
45 | struct list_head match; | 45 | struct list_head match; |
46 | struct list_head target; | 46 | struct list_head target; |
47 | struct list_head tables; | ||
48 | #ifdef CONFIG_COMPAT | 47 | #ifdef CONFIG_COMPAT |
49 | struct mutex compat_mutex; | 48 | struct mutex compat_mutex; |
50 | struct compat_delta *compat_offsets; | 49 | struct compat_delta *compat_offsets; |
@@ -597,14 +596,14 @@ void xt_free_table_info(struct xt_table_info *info) | |||
597 | EXPORT_SYMBOL(xt_free_table_info); | 596 | EXPORT_SYMBOL(xt_free_table_info); |
598 | 597 | ||
599 | /* Find table by name, grabs mutex & ref. Returns ERR_PTR() on error. */ | 598 | /* Find table by name, grabs mutex & ref. Returns ERR_PTR() on error. */ |
600 | struct xt_table *xt_find_table_lock(int af, const char *name) | 599 | struct xt_table *xt_find_table_lock(struct net *net, int af, const char *name) |
601 | { | 600 | { |
602 | struct xt_table *t; | 601 | struct xt_table *t; |
603 | 602 | ||
604 | if (mutex_lock_interruptible(&xt[af].mutex) != 0) | 603 | if (mutex_lock_interruptible(&xt[af].mutex) != 0) |
605 | return ERR_PTR(-EINTR); | 604 | return ERR_PTR(-EINTR); |
606 | 605 | ||
607 | list_for_each_entry(t, &xt[af].tables, list) | 606 | list_for_each_entry(t, &net->xt.tables[af], list) |
608 | if (strcmp(t->name, name) == 0 && try_module_get(t->me)) | 607 | if (strcmp(t->name, name) == 0 && try_module_get(t->me)) |
609 | return t; | 608 | return t; |
610 | mutex_unlock(&xt[af].mutex); | 609 | mutex_unlock(&xt[af].mutex); |
@@ -660,7 +659,7 @@ xt_replace_table(struct xt_table *table, | |||
660 | } | 659 | } |
661 | EXPORT_SYMBOL_GPL(xt_replace_table); | 660 | EXPORT_SYMBOL_GPL(xt_replace_table); |
662 | 661 | ||
663 | struct xt_table *xt_register_table(struct xt_table *table, | 662 | struct xt_table *xt_register_table(struct net *net, struct xt_table *table, |
664 | struct xt_table_info *bootstrap, | 663 | struct xt_table_info *bootstrap, |
665 | struct xt_table_info *newinfo) | 664 | struct xt_table_info *newinfo) |
666 | { | 665 | { |
@@ -673,7 +672,7 @@ struct xt_table *xt_register_table(struct xt_table *table, | |||
673 | goto out; | 672 | goto out; |
674 | 673 | ||
675 | /* Don't autoload: we'd eat our tail... */ | 674 | /* Don't autoload: we'd eat our tail... */ |
676 | list_for_each_entry(t, &xt[table->af].tables, list) { | 675 | list_for_each_entry(t, &net->xt.tables[table->af], list) { |
677 | if (strcmp(t->name, table->name) == 0) { | 676 | if (strcmp(t->name, table->name) == 0) { |
678 | ret = -EEXIST; | 677 | ret = -EEXIST; |
679 | goto unlock; | 678 | goto unlock; |
@@ -692,7 +691,7 @@ struct xt_table *xt_register_table(struct xt_table *table, | |||
692 | /* save number of initial entries */ | 691 | /* save number of initial entries */ |
693 | private->initial_entries = private->number; | 692 | private->initial_entries = private->number; |
694 | 693 | ||
695 | list_add(&table->list, &xt[table->af].tables); | 694 | list_add(&table->list, &net->xt.tables[table->af]); |
696 | mutex_unlock(&xt[table->af].mutex); | 695 | mutex_unlock(&xt[table->af].mutex); |
697 | return table; | 696 | return table; |
698 | 697 | ||
@@ -744,7 +743,7 @@ static struct list_head *type2list(u_int16_t af, u_int16_t type) | |||
744 | list = &xt[af].match; | 743 | list = &xt[af].match; |
745 | break; | 744 | break; |
746 | case TABLE: | 745 | case TABLE: |
747 | list = &xt[af].tables; | 746 | list = &init_net.xt.tables[af]; |
748 | break; | 747 | break; |
749 | default: | 748 | default: |
750 | list = NULL; | 749 | list = NULL; |
@@ -919,10 +918,22 @@ void xt_proto_fini(int af) | |||
919 | } | 918 | } |
920 | EXPORT_SYMBOL_GPL(xt_proto_fini); | 919 | EXPORT_SYMBOL_GPL(xt_proto_fini); |
921 | 920 | ||
921 | static int __net_init xt_net_init(struct net *net) | ||
922 | { | ||
923 | int i; | ||
924 | |||
925 | for (i = 0; i < NPROTO; i++) | ||
926 | INIT_LIST_HEAD(&net->xt.tables[i]); | ||
927 | return 0; | ||
928 | } | ||
929 | |||
930 | static struct pernet_operations xt_net_ops = { | ||
931 | .init = xt_net_init, | ||
932 | }; | ||
922 | 933 | ||
923 | static int __init xt_init(void) | 934 | static int __init xt_init(void) |
924 | { | 935 | { |
925 | int i; | 936 | int i, rv; |
926 | 937 | ||
927 | xt = kmalloc(sizeof(struct xt_af) * NPROTO, GFP_KERNEL); | 938 | xt = kmalloc(sizeof(struct xt_af) * NPROTO, GFP_KERNEL); |
928 | if (!xt) | 939 | if (!xt) |
@@ -936,13 +947,16 @@ static int __init xt_init(void) | |||
936 | #endif | 947 | #endif |
937 | INIT_LIST_HEAD(&xt[i].target); | 948 | INIT_LIST_HEAD(&xt[i].target); |
938 | INIT_LIST_HEAD(&xt[i].match); | 949 | INIT_LIST_HEAD(&xt[i].match); |
939 | INIT_LIST_HEAD(&xt[i].tables); | ||
940 | } | 950 | } |
941 | return 0; | 951 | rv = register_pernet_subsys(&xt_net_ops); |
952 | if (rv < 0) | ||
953 | kfree(xt); | ||
954 | return rv; | ||
942 | } | 955 | } |
943 | 956 | ||
944 | static void __exit xt_fini(void) | 957 | static void __exit xt_fini(void) |
945 | { | 958 | { |
959 | unregister_pernet_subsys(&xt_net_ops); | ||
946 | kfree(xt); | 960 | kfree(xt); |
947 | } | 961 | } |
948 | 962 | ||