aboutsummaryrefslogtreecommitdiffstats
path: root/net/netfilter
diff options
context:
space:
mode:
authorPatrick McHardy <kaber@trash.net>2013-08-27 02:50:14 -0400
committerPablo Neira Ayuso <pablo@netfilter.org>2013-08-27 18:27:54 -0400
commit48b1de4c110a7afa4b85862f6c75af817db26fad (patch)
tree9d5af0462fdfda02e8eba53018f3e9e577c657f2 /net/netfilter
parent0198230b7705eb2386e53778d944e307eef0cc71 (diff)
netfilter: add SYNPROXY core/target
Add a SYNPROXY for netfilter. The code is split into two parts, the synproxy core with common functions and an address family specific target. The SYNPROXY receives the connection request from the client, responds with a SYN/ACK containing a SYN cookie and announcing a zero window and checks whether the final ACK from the client contains a valid cookie. It then establishes a connection to the original destination and, if successful, sends a window update to the client with the window size announced by the server. Support for timestamps, SACK, window scaling and MSS options can be statically configured as target parameters if the features of the server are known. If timestamps are used, the timestamp value sent back to the client in the SYN/ACK will be different from the real timestamp of the server. In order to now break PAWS, the timestamps are translated in the direction server->client. Signed-off-by: Patrick McHardy <kaber@trash.net> Tested-by: Martin Topholm <mph@one.com> Signed-off-by: Jesper Dangaard Brouer <brouer@redhat.com> Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
Diffstat (limited to 'net/netfilter')
-rw-r--r--net/netfilter/Kconfig3
-rw-r--r--net/netfilter/Makefile3
-rw-r--r--net/netfilter/nf_conntrack_core.c6
-rw-r--r--net/netfilter/nf_conntrack_proto_tcp.c16
-rw-r--r--net/netfilter/nf_conntrack_seqadj.c20
-rw-r--r--net/netfilter/nf_synproxy_core.c432
6 files changed, 480 insertions, 0 deletions
diff --git a/net/netfilter/Kconfig b/net/netfilter/Kconfig
index c45fc1a60e0d..62a171ab204f 100644
--- a/net/netfilter/Kconfig
+++ b/net/netfilter/Kconfig
@@ -408,6 +408,9 @@ config NF_NAT_TFTP
408 depends on NF_CONNTRACK && NF_NAT 408 depends on NF_CONNTRACK && NF_NAT
409 default NF_NAT && NF_CONNTRACK_TFTP 409 default NF_NAT && NF_CONNTRACK_TFTP
410 410
411config NETFILTER_SYNPROXY
412 tristate
413
411endif # NF_CONNTRACK 414endif # NF_CONNTRACK
412 415
413config NETFILTER_XTABLES 416config NETFILTER_XTABLES
diff --git a/net/netfilter/Makefile b/net/netfilter/Makefile
index 89a9c1658f5e..c3a0a12907f6 100644
--- a/net/netfilter/Makefile
+++ b/net/netfilter/Makefile
@@ -61,6 +61,9 @@ obj-$(CONFIG_NF_NAT_IRC) += nf_nat_irc.o
61obj-$(CONFIG_NF_NAT_SIP) += nf_nat_sip.o 61obj-$(CONFIG_NF_NAT_SIP) += nf_nat_sip.o
62obj-$(CONFIG_NF_NAT_TFTP) += nf_nat_tftp.o 62obj-$(CONFIG_NF_NAT_TFTP) += nf_nat_tftp.o
63 63
64# SYNPROXY
65obj-$(CONFIG_NETFILTER_SYNPROXY) += nf_synproxy_core.o
66
64# generic X tables 67# generic X tables
65obj-$(CONFIG_NETFILTER_XTABLES) += x_tables.o xt_tcpudp.o 68obj-$(CONFIG_NETFILTER_XTABLES) += x_tables.o xt_tcpudp.o
66 69
diff --git a/net/netfilter/nf_conntrack_core.c b/net/netfilter/nf_conntrack_core.c
index 00a7a94d4132..5d892febd64c 100644
--- a/net/netfilter/nf_conntrack_core.c
+++ b/net/netfilter/nf_conntrack_core.c
@@ -48,6 +48,7 @@
48#include <net/netfilter/nf_conntrack_timestamp.h> 48#include <net/netfilter/nf_conntrack_timestamp.h>
49#include <net/netfilter/nf_conntrack_timeout.h> 49#include <net/netfilter/nf_conntrack_timeout.h>
50#include <net/netfilter/nf_conntrack_labels.h> 50#include <net/netfilter/nf_conntrack_labels.h>
51#include <net/netfilter/nf_conntrack_synproxy.h>
51#include <net/netfilter/nf_nat.h> 52#include <net/netfilter/nf_nat.h>
52#include <net/netfilter/nf_nat_core.h> 53#include <net/netfilter/nf_nat_core.h>
53#include <net/netfilter/nf_nat_helper.h> 54#include <net/netfilter/nf_nat_helper.h>
@@ -799,6 +800,11 @@ init_conntrack(struct net *net, struct nf_conn *tmpl,
799 if (IS_ERR(ct)) 800 if (IS_ERR(ct))
800 return (struct nf_conntrack_tuple_hash *)ct; 801 return (struct nf_conntrack_tuple_hash *)ct;
801 802
803 if (tmpl && nfct_synproxy(tmpl)) {
804 nfct_seqadj_ext_add(ct);
805 nfct_synproxy_ext_add(ct);
806 }
807
802 timeout_ext = tmpl ? nf_ct_timeout_find(tmpl) : NULL; 808 timeout_ext = tmpl ? nf_ct_timeout_find(tmpl) : NULL;
803 if (timeout_ext) 809 if (timeout_ext)
804 timeouts = NF_CT_TIMEOUT_EXT_DATA(timeout_ext); 810 timeouts = NF_CT_TIMEOUT_EXT_DATA(timeout_ext);
diff --git a/net/netfilter/nf_conntrack_proto_tcp.c b/net/netfilter/nf_conntrack_proto_tcp.c
index 984a8d1a3359..44d1ea32570a 100644
--- a/net/netfilter/nf_conntrack_proto_tcp.c
+++ b/net/netfilter/nf_conntrack_proto_tcp.c
@@ -28,6 +28,7 @@
28#include <net/netfilter/nf_conntrack_l4proto.h> 28#include <net/netfilter/nf_conntrack_l4proto.h>
29#include <net/netfilter/nf_conntrack_ecache.h> 29#include <net/netfilter/nf_conntrack_ecache.h>
30#include <net/netfilter/nf_conntrack_seqadj.h> 30#include <net/netfilter/nf_conntrack_seqadj.h>
31#include <net/netfilter/nf_conntrack_synproxy.h>
31#include <net/netfilter/nf_log.h> 32#include <net/netfilter/nf_log.h>
32#include <net/netfilter/ipv4/nf_conntrack_ipv4.h> 33#include <net/netfilter/ipv4/nf_conntrack_ipv4.h>
33#include <net/netfilter/ipv6/nf_conntrack_ipv6.h> 34#include <net/netfilter/ipv6/nf_conntrack_ipv6.h>
@@ -946,6 +947,21 @@ static int tcp_packet(struct nf_conn *ct,
946 "state %s ", tcp_conntrack_names[old_state]); 947 "state %s ", tcp_conntrack_names[old_state]);
947 return NF_ACCEPT; 948 return NF_ACCEPT;
948 case TCP_CONNTRACK_MAX: 949 case TCP_CONNTRACK_MAX:
950 /* Special case for SYN proxy: when the SYN to the server or
951 * the SYN/ACK from the server is lost, the client may transmit
952 * a keep-alive packet while in SYN_SENT state. This needs to
953 * be associated with the original conntrack entry in order to
954 * generate a new SYN with the correct sequence number.
955 */
956 if (nfct_synproxy(ct) && old_state == TCP_CONNTRACK_SYN_SENT &&
957 index == TCP_ACK_SET && dir == IP_CT_DIR_ORIGINAL &&
958 ct->proto.tcp.last_dir == IP_CT_DIR_ORIGINAL &&
959 ct->proto.tcp.seen[dir].td_end - 1 == ntohl(th->seq)) {
960 pr_debug("nf_ct_tcp: SYN proxy client keep alive\n");
961 spin_unlock_bh(&ct->lock);
962 return NF_ACCEPT;
963 }
964
949 /* Invalid packet */ 965 /* Invalid packet */
950 pr_debug("nf_ct_tcp: Invalid dir=%i index=%u ostate=%u\n", 966 pr_debug("nf_ct_tcp: Invalid dir=%i index=%u ostate=%u\n",
951 dir, get_conntrack_index(th), old_state); 967 dir, get_conntrack_index(th), old_state);
diff --git a/net/netfilter/nf_conntrack_seqadj.c b/net/netfilter/nf_conntrack_seqadj.c
index 483eb9ce3216..5f9bfd060dea 100644
--- a/net/netfilter/nf_conntrack_seqadj.c
+++ b/net/netfilter/nf_conntrack_seqadj.c
@@ -6,6 +6,26 @@
6#include <net/netfilter/nf_conntrack_extend.h> 6#include <net/netfilter/nf_conntrack_extend.h>
7#include <net/netfilter/nf_conntrack_seqadj.h> 7#include <net/netfilter/nf_conntrack_seqadj.h>
8 8
9int nf_ct_seqadj_init(struct nf_conn *ct, enum ip_conntrack_info ctinfo,
10 s32 off)
11{
12 enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
13 struct nf_conn_seqadj *seqadj;
14 struct nf_ct_seqadj *this_way;
15
16 if (off == 0)
17 return 0;
18
19 set_bit(IPS_SEQ_ADJUST_BIT, &ct->status);
20
21 seqadj = nfct_seqadj(ct);
22 this_way = &seqadj->seq[dir];
23 this_way->offset_before = off;
24 this_way->offset_after = off;
25 return 0;
26}
27EXPORT_SYMBOL_GPL(nf_ct_seqadj_init);
28
9int nf_ct_seqadj_set(struct nf_conn *ct, enum ip_conntrack_info ctinfo, 29int nf_ct_seqadj_set(struct nf_conn *ct, enum ip_conntrack_info ctinfo,
10 __be32 seq, s32 off) 30 __be32 seq, s32 off)
11{ 31{
diff --git a/net/netfilter/nf_synproxy_core.c b/net/netfilter/nf_synproxy_core.c
new file mode 100644
index 000000000000..d23dc791aca7
--- /dev/null
+++ b/net/netfilter/nf_synproxy_core.c
@@ -0,0 +1,432 @@
1/*
2 * Copyright (c) 2013 Patrick McHardy <kaber@trash.net>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 as
6 * published by the Free Software Foundation.
7 */
8
9#include <linux/module.h>
10#include <linux/skbuff.h>
11#include <asm/unaligned.h>
12#include <net/tcp.h>
13#include <net/netns/generic.h>
14
15#include <linux/netfilter_ipv4/ip_tables.h>
16#include <linux/netfilter/x_tables.h>
17#include <linux/netfilter/xt_tcpudp.h>
18#include <linux/netfilter/xt_SYNPROXY.h>
19#include <net/netfilter/nf_conntrack.h>
20#include <net/netfilter/nf_conntrack_extend.h>
21#include <net/netfilter/nf_conntrack_seqadj.h>
22#include <net/netfilter/nf_conntrack_synproxy.h>
23
24int synproxy_net_id;
25EXPORT_SYMBOL_GPL(synproxy_net_id);
26
27void
28synproxy_parse_options(const struct sk_buff *skb, unsigned int doff,
29 const struct tcphdr *th, struct synproxy_options *opts)
30{
31 int length = (th->doff * 4) - sizeof(*th);
32 u8 buf[40], *ptr;
33
34 ptr = skb_header_pointer(skb, doff + sizeof(*th), length, buf);
35 BUG_ON(ptr == NULL);
36
37 opts->options = 0;
38 while (length > 0) {
39 int opcode = *ptr++;
40 int opsize;
41
42 switch (opcode) {
43 case TCPOPT_EOL:
44 return;
45 case TCPOPT_NOP:
46 length--;
47 continue;
48 default:
49 opsize = *ptr++;
50 if (opsize < 2)
51 return;
52 if (opsize > length)
53 return;
54
55 switch (opcode) {
56 case TCPOPT_MSS:
57 if (opsize == TCPOLEN_MSS) {
58 opts->mss = get_unaligned_be16(ptr);
59 opts->options |= XT_SYNPROXY_OPT_MSS;
60 }
61 break;
62 case TCPOPT_WINDOW:
63 if (opsize == TCPOLEN_WINDOW) {
64 opts->wscale = *ptr;
65 if (opts->wscale > 14)
66 opts->wscale = 14;
67 opts->options |= XT_SYNPROXY_OPT_WSCALE;
68 }
69 break;
70 case TCPOPT_TIMESTAMP:
71 if (opsize == TCPOLEN_TIMESTAMP) {
72 opts->tsval = get_unaligned_be32(ptr);
73 opts->tsecr = get_unaligned_be32(ptr + 4);
74 opts->options |= XT_SYNPROXY_OPT_TIMESTAMP;
75 }
76 break;
77 case TCPOPT_SACK_PERM:
78 if (opsize == TCPOLEN_SACK_PERM)
79 opts->options |= XT_SYNPROXY_OPT_SACK_PERM;
80 break;
81 }
82
83 ptr += opsize - 2;
84 length -= opsize;
85 }
86 }
87}
88EXPORT_SYMBOL_GPL(synproxy_parse_options);
89
90unsigned int synproxy_options_size(const struct synproxy_options *opts)
91{
92 unsigned int size = 0;
93
94 if (opts->options & XT_SYNPROXY_OPT_MSS)
95 size += TCPOLEN_MSS_ALIGNED;
96 if (opts->options & XT_SYNPROXY_OPT_TIMESTAMP)
97 size += TCPOLEN_TSTAMP_ALIGNED;
98 else if (opts->options & XT_SYNPROXY_OPT_SACK_PERM)
99 size += TCPOLEN_SACKPERM_ALIGNED;
100 if (opts->options & XT_SYNPROXY_OPT_WSCALE)
101 size += TCPOLEN_WSCALE_ALIGNED;
102
103 return size;
104}
105EXPORT_SYMBOL_GPL(synproxy_options_size);
106
107void
108synproxy_build_options(struct tcphdr *th, const struct synproxy_options *opts)
109{
110 __be32 *ptr = (__be32 *)(th + 1);
111 u8 options = opts->options;
112
113 if (options & XT_SYNPROXY_OPT_MSS)
114 *ptr++ = htonl((TCPOPT_MSS << 24) |
115 (TCPOLEN_MSS << 16) |
116 opts->mss);
117
118 if (options & XT_SYNPROXY_OPT_TIMESTAMP) {
119 if (options & XT_SYNPROXY_OPT_SACK_PERM)
120 *ptr++ = htonl((TCPOPT_SACK_PERM << 24) |
121 (TCPOLEN_SACK_PERM << 16) |
122 (TCPOPT_TIMESTAMP << 8) |
123 TCPOLEN_TIMESTAMP);
124 else
125 *ptr++ = htonl((TCPOPT_NOP << 24) |
126 (TCPOPT_NOP << 16) |
127 (TCPOPT_TIMESTAMP << 8) |
128 TCPOLEN_TIMESTAMP);
129
130 *ptr++ = htonl(opts->tsval);
131 *ptr++ = htonl(opts->tsecr);
132 } else if (options & XT_SYNPROXY_OPT_SACK_PERM)
133 *ptr++ = htonl((TCPOPT_NOP << 24) |
134 (TCPOPT_NOP << 16) |
135 (TCPOPT_SACK_PERM << 8) |
136 TCPOLEN_SACK_PERM);
137
138 if (options & XT_SYNPROXY_OPT_WSCALE)
139 *ptr++ = htonl((TCPOPT_NOP << 24) |
140 (TCPOPT_WINDOW << 16) |
141 (TCPOLEN_WINDOW << 8) |
142 opts->wscale);
143}
144EXPORT_SYMBOL_GPL(synproxy_build_options);
145
146void synproxy_init_timestamp_cookie(const struct xt_synproxy_info *info,
147 struct synproxy_options *opts)
148{
149 opts->tsecr = opts->tsval;
150 opts->tsval = tcp_time_stamp & ~0x3f;
151
152 if (opts->options & XT_SYNPROXY_OPT_WSCALE)
153 opts->tsval |= info->wscale;
154 else
155 opts->tsval |= 0xf;
156
157 if (opts->options & XT_SYNPROXY_OPT_SACK_PERM)
158 opts->tsval |= 1 << 4;
159
160 if (opts->options & XT_SYNPROXY_OPT_ECN)
161 opts->tsval |= 1 << 5;
162}
163EXPORT_SYMBOL_GPL(synproxy_init_timestamp_cookie);
164
165void synproxy_check_timestamp_cookie(struct synproxy_options *opts)
166{
167 opts->wscale = opts->tsecr & 0xf;
168 if (opts->wscale != 0xf)
169 opts->options |= XT_SYNPROXY_OPT_WSCALE;
170
171 opts->options |= opts->tsecr & (1 << 4) ? XT_SYNPROXY_OPT_SACK_PERM : 0;
172
173 opts->options |= opts->tsecr & (1 << 5) ? XT_SYNPROXY_OPT_ECN : 0;
174}
175EXPORT_SYMBOL_GPL(synproxy_check_timestamp_cookie);
176
177unsigned int synproxy_tstamp_adjust(struct sk_buff *skb,
178 unsigned int protoff,
179 struct tcphdr *th,
180 struct nf_conn *ct,
181 enum ip_conntrack_info ctinfo,
182 const struct nf_conn_synproxy *synproxy)
183{
184 unsigned int optoff, optend;
185 u32 *ptr, old;
186
187 if (synproxy->tsoff == 0)
188 return 1;
189
190 optoff = protoff + sizeof(struct tcphdr);
191 optend = protoff + th->doff * 4;
192
193 if (!skb_make_writable(skb, optend))
194 return 0;
195
196 while (optoff < optend) {
197 unsigned char *op = skb->data + optoff;
198
199 switch (op[0]) {
200 case TCPOPT_EOL:
201 return 1;
202 case TCPOPT_NOP:
203 optoff++;
204 continue;
205 default:
206 if (optoff + 1 == optend ||
207 optoff + op[1] > optend ||
208 op[1] < 2)
209 return 0;
210 if (op[0] == TCPOPT_TIMESTAMP &&
211 op[1] == TCPOLEN_TIMESTAMP) {
212 if (CTINFO2DIR(ctinfo) == IP_CT_DIR_REPLY) {
213 ptr = (u32 *)&op[2];
214 old = *ptr;
215 *ptr = htonl(ntohl(*ptr) -
216 synproxy->tsoff);
217 } else {
218 ptr = (u32 *)&op[6];
219 old = *ptr;
220 *ptr = htonl(ntohl(*ptr) +
221 synproxy->tsoff);
222 }
223 inet_proto_csum_replace4(&th->check, skb,
224 old, *ptr, 0);
225 return 1;
226 }
227 optoff += op[1];
228 }
229 }
230 return 1;
231}
232EXPORT_SYMBOL_GPL(synproxy_tstamp_adjust);
233
234static struct nf_ct_ext_type nf_ct_synproxy_extend __read_mostly = {
235 .len = sizeof(struct nf_conn_synproxy),
236 .align = __alignof__(struct nf_conn_synproxy),
237 .id = NF_CT_EXT_SYNPROXY,
238};
239
240#ifdef CONFIG_PROC_FS
241static void *synproxy_cpu_seq_start(struct seq_file *seq, loff_t *pos)
242{
243 struct synproxy_net *snet = synproxy_pernet(seq_file_net(seq));
244 int cpu;
245
246 if (*pos == 0)
247 return SEQ_START_TOKEN;
248
249 for (cpu = *pos - 1; cpu < nr_cpu_ids; cpu++) {
250 if (!cpu_possible(cpu))
251 continue;
252 *pos = cpu + 1;
253 return per_cpu_ptr(snet->stats, cpu);
254 }
255
256 return NULL;
257}
258
259static void *synproxy_cpu_seq_next(struct seq_file *seq, void *v, loff_t *pos)
260{
261 struct synproxy_net *snet = synproxy_pernet(seq_file_net(seq));
262 int cpu;
263
264 for (cpu = *pos; cpu < nr_cpu_ids; cpu++) {
265 if (!cpu_possible(cpu))
266 continue;
267 *pos = cpu + 1;
268 return per_cpu_ptr(snet->stats, cpu);
269 }
270
271 return NULL;
272}
273
274static void synproxy_cpu_seq_stop(struct seq_file *seq, void *v)
275{
276 return;
277}
278
279static int synproxy_cpu_seq_show(struct seq_file *seq, void *v)
280{
281 struct synproxy_stats *stats = v;
282
283 if (v == SEQ_START_TOKEN) {
284 seq_printf(seq, "entries\t\tsyn_received\t"
285 "cookie_invalid\tcookie_valid\t"
286 "cookie_retrans\tconn_reopened\n");
287 return 0;
288 }
289
290 seq_printf(seq, "%08x\t%08x\t%08x\t%08x\t%08x\t%08x\n", 0,
291 stats->syn_received,
292 stats->cookie_invalid,
293 stats->cookie_valid,
294 stats->cookie_retrans,
295 stats->conn_reopened);
296
297 return 0;
298}
299
300static const struct seq_operations synproxy_cpu_seq_ops = {
301 .start = synproxy_cpu_seq_start,
302 .next = synproxy_cpu_seq_next,
303 .stop = synproxy_cpu_seq_stop,
304 .show = synproxy_cpu_seq_show,
305};
306
307static int synproxy_cpu_seq_open(struct inode *inode, struct file *file)
308{
309 return seq_open_net(inode, file, &synproxy_cpu_seq_ops,
310 sizeof(struct seq_net_private));
311}
312
313static const struct file_operations synproxy_cpu_seq_fops = {
314 .owner = THIS_MODULE,
315 .open = synproxy_cpu_seq_open,
316 .read = seq_read,
317 .llseek = seq_lseek,
318 .release = seq_release_net,
319};
320
321static int __net_init synproxy_proc_init(struct net *net)
322{
323 if (!proc_create("synproxy", S_IRUGO, net->proc_net_stat,
324 &synproxy_cpu_seq_fops))
325 return -ENOMEM;
326 return 0;
327}
328
329static void __net_exit synproxy_proc_exit(struct net *net)
330{
331 remove_proc_entry("synproxy", net->proc_net_stat);
332}
333#else
334static int __net_init synproxy_proc_init(struct net *net)
335{
336 return 0;
337}
338
339static void __net_exit synproxy_proc_exit(struct net *net)
340{
341 return;
342}
343#endif /* CONFIG_PROC_FS */
344
345static int __net_init synproxy_net_init(struct net *net)
346{
347 struct synproxy_net *snet = synproxy_pernet(net);
348 struct nf_conntrack_tuple t;
349 struct nf_conn *ct;
350 int err = -ENOMEM;
351
352 memset(&t, 0, sizeof(t));
353 ct = nf_conntrack_alloc(net, 0, &t, &t, GFP_KERNEL);
354 if (IS_ERR(ct)) {
355 err = PTR_ERR(ct);
356 goto err1;
357 }
358
359 __set_bit(IPS_TEMPLATE_BIT, &ct->status);
360 __set_bit(IPS_CONFIRMED_BIT, &ct->status);
361 if (!nfct_seqadj_ext_add(ct))
362 goto err2;
363 if (!nfct_synproxy_ext_add(ct))
364 goto err2;
365
366 snet->tmpl = ct;
367
368 snet->stats = alloc_percpu(struct synproxy_stats);
369 if (snet->stats == NULL)
370 goto err2;
371
372 err = synproxy_proc_init(net);
373 if (err < 0)
374 goto err3;
375
376 return 0;
377
378err3:
379 free_percpu(snet->stats);
380err2:
381 nf_conntrack_free(ct);
382err1:
383 return err;
384}
385
386static void __net_exit synproxy_net_exit(struct net *net)
387{
388 struct synproxy_net *snet = synproxy_pernet(net);
389
390 nf_conntrack_free(snet->tmpl);
391 synproxy_proc_exit(net);
392 free_percpu(snet->stats);
393}
394
395static struct pernet_operations synproxy_net_ops = {
396 .init = synproxy_net_init,
397 .exit = synproxy_net_exit,
398 .id = &synproxy_net_id,
399 .size = sizeof(struct synproxy_net),
400};
401
402static int __init synproxy_core_init(void)
403{
404 int err;
405
406 err = nf_ct_extend_register(&nf_ct_synproxy_extend);
407 if (err < 0)
408 goto err1;
409
410 err = register_pernet_subsys(&synproxy_net_ops);
411 if (err < 0)
412 goto err2;
413
414 return 0;
415
416err2:
417 nf_ct_extend_unregister(&nf_ct_synproxy_extend);
418err1:
419 return err;
420}
421
422static void __exit synproxy_core_exit(void)
423{
424 unregister_pernet_subsys(&synproxy_net_ops);
425 nf_ct_extend_unregister(&nf_ct_synproxy_extend);
426}
427
428module_init(synproxy_core_init);
429module_exit(synproxy_core_exit);
430
431MODULE_LICENSE("GPL");
432MODULE_AUTHOR("Patrick McHardy <kaber@trash.net>");