diff options
-rw-r--r-- | include/net/netfilter/nf_conntrack_extend.h | 6 | ||||
-rw-r--r-- | include/net/netfilter/nf_conntrack_seqadj.h | 2 | ||||
-rw-r--r-- | include/net/netfilter/nf_conntrack_synproxy.h | 77 | ||||
-rw-r--r-- | include/uapi/linux/netfilter/xt_SYNPROXY.h | 16 | ||||
-rw-r--r-- | net/ipv4/netfilter/Kconfig | 13 | ||||
-rw-r--r-- | net/ipv4/netfilter/Makefile | 1 | ||||
-rw-r--r-- | net/ipv4/netfilter/ipt_SYNPROXY.c | 472 | ||||
-rw-r--r-- | net/netfilter/Kconfig | 3 | ||||
-rw-r--r-- | net/netfilter/Makefile | 3 | ||||
-rw-r--r-- | net/netfilter/nf_conntrack_core.c | 6 | ||||
-rw-r--r-- | net/netfilter/nf_conntrack_proto_tcp.c | 16 | ||||
-rw-r--r-- | net/netfilter/nf_conntrack_seqadj.c | 20 | ||||
-rw-r--r-- | net/netfilter/nf_synproxy_core.c | 432 |
13 files changed, 1066 insertions, 1 deletions
diff --git a/include/net/netfilter/nf_conntrack_extend.h b/include/net/netfilter/nf_conntrack_extend.h index 2a22bcbfe6e4..ff95434e50ca 100644 --- a/include/net/netfilter/nf_conntrack_extend.h +++ b/include/net/netfilter/nf_conntrack_extend.h | |||
@@ -9,8 +9,8 @@ enum nf_ct_ext_id { | |||
9 | NF_CT_EXT_HELPER, | 9 | NF_CT_EXT_HELPER, |
10 | #if defined(CONFIG_NF_NAT) || defined(CONFIG_NF_NAT_MODULE) | 10 | #if defined(CONFIG_NF_NAT) || defined(CONFIG_NF_NAT_MODULE) |
11 | NF_CT_EXT_NAT, | 11 | NF_CT_EXT_NAT, |
12 | NF_CT_EXT_SEQADJ, | ||
13 | #endif | 12 | #endif |
13 | NF_CT_EXT_SEQADJ, | ||
14 | NF_CT_EXT_ACCT, | 14 | NF_CT_EXT_ACCT, |
15 | #ifdef CONFIG_NF_CONNTRACK_EVENTS | 15 | #ifdef CONFIG_NF_CONNTRACK_EVENTS |
16 | NF_CT_EXT_ECACHE, | 16 | NF_CT_EXT_ECACHE, |
@@ -27,6 +27,9 @@ enum nf_ct_ext_id { | |||
27 | #ifdef CONFIG_NF_CONNTRACK_LABELS | 27 | #ifdef CONFIG_NF_CONNTRACK_LABELS |
28 | NF_CT_EXT_LABELS, | 28 | NF_CT_EXT_LABELS, |
29 | #endif | 29 | #endif |
30 | #if IS_ENABLED(CONFIG_NETFILTER_SYNPROXY) | ||
31 | NF_CT_EXT_SYNPROXY, | ||
32 | #endif | ||
30 | NF_CT_EXT_NUM, | 33 | NF_CT_EXT_NUM, |
31 | }; | 34 | }; |
32 | 35 | ||
@@ -39,6 +42,7 @@ enum nf_ct_ext_id { | |||
39 | #define NF_CT_EXT_TSTAMP_TYPE struct nf_conn_tstamp | 42 | #define NF_CT_EXT_TSTAMP_TYPE struct nf_conn_tstamp |
40 | #define NF_CT_EXT_TIMEOUT_TYPE struct nf_conn_timeout | 43 | #define NF_CT_EXT_TIMEOUT_TYPE struct nf_conn_timeout |
41 | #define NF_CT_EXT_LABELS_TYPE struct nf_conn_labels | 44 | #define NF_CT_EXT_LABELS_TYPE struct nf_conn_labels |
45 | #define NF_CT_EXT_SYNPROXY_TYPE struct nf_conn_synproxy | ||
42 | 46 | ||
43 | /* Extensions: optional stuff which isn't permanently in struct. */ | 47 | /* Extensions: optional stuff which isn't permanently in struct. */ |
44 | struct nf_ct_ext { | 48 | struct nf_ct_ext { |
diff --git a/include/net/netfilter/nf_conntrack_seqadj.h b/include/net/netfilter/nf_conntrack_seqadj.h index 30bfbbed9f47..f6177a5fe0ca 100644 --- a/include/net/netfilter/nf_conntrack_seqadj.h +++ b/include/net/netfilter/nf_conntrack_seqadj.h | |||
@@ -30,6 +30,8 @@ static inline struct nf_conn_seqadj *nfct_seqadj_ext_add(struct nf_conn *ct) | |||
30 | return nf_ct_ext_add(ct, NF_CT_EXT_SEQADJ, GFP_ATOMIC); | 30 | return nf_ct_ext_add(ct, NF_CT_EXT_SEQADJ, GFP_ATOMIC); |
31 | } | 31 | } |
32 | 32 | ||
33 | extern int nf_ct_seqadj_init(struct nf_conn *ct, enum ip_conntrack_info ctinfo, | ||
34 | s32 off); | ||
33 | extern int nf_ct_seqadj_set(struct nf_conn *ct, enum ip_conntrack_info ctinfo, | 35 | extern int nf_ct_seqadj_set(struct nf_conn *ct, enum ip_conntrack_info ctinfo, |
34 | __be32 seq, s32 off); | 36 | __be32 seq, s32 off); |
35 | extern void nf_ct_tcp_seqadj_set(struct sk_buff *skb, | 37 | extern void nf_ct_tcp_seqadj_set(struct sk_buff *skb, |
diff --git a/include/net/netfilter/nf_conntrack_synproxy.h b/include/net/netfilter/nf_conntrack_synproxy.h new file mode 100644 index 000000000000..806f54a290d6 --- /dev/null +++ b/include/net/netfilter/nf_conntrack_synproxy.h | |||
@@ -0,0 +1,77 @@ | |||
1 | #ifndef _NF_CONNTRACK_SYNPROXY_H | ||
2 | #define _NF_CONNTRACK_SYNPROXY_H | ||
3 | |||
4 | #include <net/netns/generic.h> | ||
5 | |||
6 | struct nf_conn_synproxy { | ||
7 | u32 isn; | ||
8 | u32 its; | ||
9 | u32 tsoff; | ||
10 | }; | ||
11 | |||
12 | static inline struct nf_conn_synproxy *nfct_synproxy(const struct nf_conn *ct) | ||
13 | { | ||
14 | #if IS_ENABLED(CONFIG_NETFILTER_SYNPROXY) | ||
15 | return nf_ct_ext_find(ct, NF_CT_EXT_SYNPROXY); | ||
16 | #else | ||
17 | return NULL; | ||
18 | #endif | ||
19 | } | ||
20 | |||
21 | static inline struct nf_conn_synproxy *nfct_synproxy_ext_add(struct nf_conn *ct) | ||
22 | { | ||
23 | #if IS_ENABLED(CONFIG_NETFILTER_SYNPROXY) | ||
24 | return nf_ct_ext_add(ct, NF_CT_EXT_SYNPROXY, GFP_ATOMIC); | ||
25 | #else | ||
26 | return NULL; | ||
27 | #endif | ||
28 | } | ||
29 | |||
30 | struct synproxy_stats { | ||
31 | unsigned int syn_received; | ||
32 | unsigned int cookie_invalid; | ||
33 | unsigned int cookie_valid; | ||
34 | unsigned int cookie_retrans; | ||
35 | unsigned int conn_reopened; | ||
36 | }; | ||
37 | |||
38 | struct synproxy_net { | ||
39 | struct nf_conn *tmpl; | ||
40 | struct synproxy_stats __percpu *stats; | ||
41 | }; | ||
42 | |||
43 | extern int synproxy_net_id; | ||
44 | static inline struct synproxy_net *synproxy_pernet(struct net *net) | ||
45 | { | ||
46 | return net_generic(net, synproxy_net_id); | ||
47 | } | ||
48 | |||
49 | struct synproxy_options { | ||
50 | u8 options; | ||
51 | u8 wscale; | ||
52 | u16 mss; | ||
53 | u32 tsval; | ||
54 | u32 tsecr; | ||
55 | }; | ||
56 | |||
57 | struct tcphdr; | ||
58 | struct xt_synproxy_info; | ||
59 | extern void synproxy_parse_options(const struct sk_buff *skb, unsigned int doff, | ||
60 | const struct tcphdr *th, | ||
61 | struct synproxy_options *opts); | ||
62 | extern unsigned int synproxy_options_size(const struct synproxy_options *opts); | ||
63 | extern void synproxy_build_options(struct tcphdr *th, | ||
64 | const struct synproxy_options *opts); | ||
65 | |||
66 | extern void synproxy_init_timestamp_cookie(const struct xt_synproxy_info *info, | ||
67 | struct synproxy_options *opts); | ||
68 | extern void synproxy_check_timestamp_cookie(struct synproxy_options *opts); | ||
69 | |||
70 | extern unsigned int synproxy_tstamp_adjust(struct sk_buff *skb, | ||
71 | unsigned int protoff, | ||
72 | struct tcphdr *th, | ||
73 | struct nf_conn *ct, | ||
74 | enum ip_conntrack_info ctinfo, | ||
75 | const struct nf_conn_synproxy *synproxy); | ||
76 | |||
77 | #endif /* _NF_CONNTRACK_SYNPROXY_H */ | ||
diff --git a/include/uapi/linux/netfilter/xt_SYNPROXY.h b/include/uapi/linux/netfilter/xt_SYNPROXY.h new file mode 100644 index 000000000000..2d59fbaa93c6 --- /dev/null +++ b/include/uapi/linux/netfilter/xt_SYNPROXY.h | |||
@@ -0,0 +1,16 @@ | |||
1 | #ifndef _XT_SYNPROXY_H | ||
2 | #define _XT_SYNPROXY_H | ||
3 | |||
4 | #define XT_SYNPROXY_OPT_MSS 0x01 | ||
5 | #define XT_SYNPROXY_OPT_WSCALE 0x02 | ||
6 | #define XT_SYNPROXY_OPT_SACK_PERM 0x04 | ||
7 | #define XT_SYNPROXY_OPT_TIMESTAMP 0x08 | ||
8 | #define XT_SYNPROXY_OPT_ECN 0x10 | ||
9 | |||
10 | struct xt_synproxy_info { | ||
11 | __u8 options; | ||
12 | __u8 wscale; | ||
13 | __u16 mss; | ||
14 | }; | ||
15 | |||
16 | #endif /* _XT_SYNPROXY_H */ | ||
diff --git a/net/ipv4/netfilter/Kconfig b/net/ipv4/netfilter/Kconfig index 4e9028017428..1657e39b291f 100644 --- a/net/ipv4/netfilter/Kconfig +++ b/net/ipv4/netfilter/Kconfig | |||
@@ -110,6 +110,19 @@ config IP_NF_TARGET_REJECT | |||
110 | 110 | ||
111 | To compile it as a module, choose M here. If unsure, say N. | 111 | To compile it as a module, choose M here. If unsure, say N. |
112 | 112 | ||
113 | config IP_NF_TARGET_SYNPROXY | ||
114 | tristate "SYNPROXY target support" | ||
115 | depends on NF_CONNTRACK && NETFILTER_ADVANCED | ||
116 | select NETFILTER_SYNPROXY | ||
117 | select SYN_COOKIES | ||
118 | help | ||
119 | The SYNPROXY target allows you to intercept TCP connections and | ||
120 | establish them using syncookies before they are passed on to the | ||
121 | server. This allows to avoid conntrack and server resource usage | ||
122 | during SYN-flood attacks. | ||
123 | |||
124 | To compile it as a module, choose M here. If unsure, say N. | ||
125 | |||
113 | config IP_NF_TARGET_ULOG | 126 | config IP_NF_TARGET_ULOG |
114 | tristate "ULOG target support (obsolete)" | 127 | tristate "ULOG target support (obsolete)" |
115 | default m if NETFILTER_ADVANCED=n | 128 | default m if NETFILTER_ADVANCED=n |
diff --git a/net/ipv4/netfilter/Makefile b/net/ipv4/netfilter/Makefile index 007b128eecc9..3622b248b6dd 100644 --- a/net/ipv4/netfilter/Makefile +++ b/net/ipv4/netfilter/Makefile | |||
@@ -46,6 +46,7 @@ obj-$(CONFIG_IP_NF_TARGET_CLUSTERIP) += ipt_CLUSTERIP.o | |||
46 | obj-$(CONFIG_IP_NF_TARGET_ECN) += ipt_ECN.o | 46 | obj-$(CONFIG_IP_NF_TARGET_ECN) += ipt_ECN.o |
47 | obj-$(CONFIG_IP_NF_TARGET_MASQUERADE) += ipt_MASQUERADE.o | 47 | obj-$(CONFIG_IP_NF_TARGET_MASQUERADE) += ipt_MASQUERADE.o |
48 | obj-$(CONFIG_IP_NF_TARGET_REJECT) += ipt_REJECT.o | 48 | obj-$(CONFIG_IP_NF_TARGET_REJECT) += ipt_REJECT.o |
49 | obj-$(CONFIG_IP_NF_TARGET_SYNPROXY) += ipt_SYNPROXY.o | ||
49 | obj-$(CONFIG_IP_NF_TARGET_ULOG) += ipt_ULOG.o | 50 | obj-$(CONFIG_IP_NF_TARGET_ULOG) += ipt_ULOG.o |
50 | 51 | ||
51 | # generic ARP tables | 52 | # generic ARP tables |
diff --git a/net/ipv4/netfilter/ipt_SYNPROXY.c b/net/ipv4/netfilter/ipt_SYNPROXY.c new file mode 100644 index 000000000000..94371db6aecc --- /dev/null +++ b/net/ipv4/netfilter/ipt_SYNPROXY.c | |||
@@ -0,0 +1,472 @@ | |||
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 <net/tcp.h> | ||
12 | |||
13 | #include <linux/netfilter_ipv4/ip_tables.h> | ||
14 | #include <linux/netfilter/x_tables.h> | ||
15 | #include <linux/netfilter/xt_SYNPROXY.h> | ||
16 | #include <net/netfilter/nf_conntrack.h> | ||
17 | #include <net/netfilter/nf_conntrack_seqadj.h> | ||
18 | #include <net/netfilter/nf_conntrack_synproxy.h> | ||
19 | |||
20 | static struct iphdr * | ||
21 | synproxy_build_ip(struct sk_buff *skb, u32 saddr, u32 daddr) | ||
22 | { | ||
23 | struct iphdr *iph; | ||
24 | |||
25 | skb_reset_network_header(skb); | ||
26 | iph = (struct iphdr *)skb_put(skb, sizeof(*iph)); | ||
27 | iph->version = 4; | ||
28 | iph->ihl = sizeof(*iph) / 4; | ||
29 | iph->tos = 0; | ||
30 | iph->id = 0; | ||
31 | iph->frag_off = htons(IP_DF); | ||
32 | iph->ttl = sysctl_ip_default_ttl; | ||
33 | iph->protocol = IPPROTO_TCP; | ||
34 | iph->check = 0; | ||
35 | iph->saddr = saddr; | ||
36 | iph->daddr = daddr; | ||
37 | |||
38 | return iph; | ||
39 | } | ||
40 | |||
41 | static void | ||
42 | synproxy_send_tcp(const struct sk_buff *skb, struct sk_buff *nskb, | ||
43 | struct nf_conntrack *nfct, enum ip_conntrack_info ctinfo, | ||
44 | struct iphdr *niph, struct tcphdr *nth, | ||
45 | unsigned int tcp_hdr_size) | ||
46 | { | ||
47 | nth->check = ~tcp_v4_check(tcp_hdr_size, niph->saddr, niph->daddr, 0); | ||
48 | nskb->ip_summed = CHECKSUM_PARTIAL; | ||
49 | nskb->csum_start = (unsigned char *)nth - nskb->head; | ||
50 | nskb->csum_offset = offsetof(struct tcphdr, check); | ||
51 | |||
52 | skb_dst_set_noref(nskb, skb_dst(skb)); | ||
53 | nskb->protocol = htons(ETH_P_IP); | ||
54 | if (ip_route_me_harder(nskb, RTN_UNSPEC)) | ||
55 | goto free_nskb; | ||
56 | |||
57 | if (nfct) { | ||
58 | nskb->nfct = nfct; | ||
59 | nskb->nfctinfo = ctinfo; | ||
60 | nf_conntrack_get(nfct); | ||
61 | } | ||
62 | |||
63 | ip_local_out(nskb); | ||
64 | return; | ||
65 | |||
66 | free_nskb: | ||
67 | kfree_skb(nskb); | ||
68 | } | ||
69 | |||
70 | static void | ||
71 | synproxy_send_client_synack(const struct sk_buff *skb, const struct tcphdr *th, | ||
72 | const struct synproxy_options *opts) | ||
73 | { | ||
74 | struct sk_buff *nskb; | ||
75 | struct iphdr *iph, *niph; | ||
76 | struct tcphdr *nth; | ||
77 | unsigned int tcp_hdr_size; | ||
78 | u16 mss = opts->mss; | ||
79 | |||
80 | iph = ip_hdr(skb); | ||
81 | |||
82 | tcp_hdr_size = sizeof(*nth) + synproxy_options_size(opts); | ||
83 | nskb = alloc_skb(sizeof(*niph) + tcp_hdr_size + MAX_TCP_HEADER, | ||
84 | GFP_ATOMIC); | ||
85 | if (nskb == NULL) | ||
86 | return; | ||
87 | skb_reserve(nskb, MAX_TCP_HEADER); | ||
88 | |||
89 | niph = synproxy_build_ip(nskb, iph->daddr, iph->saddr); | ||
90 | |||
91 | skb_reset_transport_header(nskb); | ||
92 | nth = (struct tcphdr *)skb_put(nskb, tcp_hdr_size); | ||
93 | nth->source = th->dest; | ||
94 | nth->dest = th->source; | ||
95 | nth->seq = htonl(__cookie_v4_init_sequence(iph, th, &mss)); | ||
96 | nth->ack_seq = htonl(ntohl(th->seq) + 1); | ||
97 | tcp_flag_word(nth) = TCP_FLAG_SYN | TCP_FLAG_ACK; | ||
98 | if (opts->options & XT_SYNPROXY_OPT_ECN) | ||
99 | tcp_flag_word(nth) |= TCP_FLAG_ECE; | ||
100 | nth->doff = tcp_hdr_size / 4; | ||
101 | nth->window = 0; | ||
102 | nth->check = 0; | ||
103 | nth->urg_ptr = 0; | ||
104 | |||
105 | synproxy_build_options(nth, opts); | ||
106 | |||
107 | synproxy_send_tcp(skb, nskb, skb->nfct, IP_CT_ESTABLISHED_REPLY, | ||
108 | niph, nth, tcp_hdr_size); | ||
109 | } | ||
110 | |||
111 | static void | ||
112 | synproxy_send_server_syn(const struct synproxy_net *snet, | ||
113 | const struct sk_buff *skb, const struct tcphdr *th, | ||
114 | const struct synproxy_options *opts, u32 recv_seq) | ||
115 | { | ||
116 | struct sk_buff *nskb; | ||
117 | struct iphdr *iph, *niph; | ||
118 | struct tcphdr *nth; | ||
119 | unsigned int tcp_hdr_size; | ||
120 | |||
121 | iph = ip_hdr(skb); | ||
122 | |||
123 | tcp_hdr_size = sizeof(*nth) + synproxy_options_size(opts); | ||
124 | nskb = alloc_skb(sizeof(*niph) + tcp_hdr_size + MAX_TCP_HEADER, | ||
125 | GFP_ATOMIC); | ||
126 | if (nskb == NULL) | ||
127 | return; | ||
128 | skb_reserve(nskb, MAX_TCP_HEADER); | ||
129 | |||
130 | niph = synproxy_build_ip(nskb, iph->saddr, iph->daddr); | ||
131 | |||
132 | skb_reset_transport_header(nskb); | ||
133 | nth = (struct tcphdr *)skb_put(nskb, tcp_hdr_size); | ||
134 | nth->source = th->source; | ||
135 | nth->dest = th->dest; | ||
136 | nth->seq = htonl(recv_seq - 1); | ||
137 | /* ack_seq is used to relay our ISN to the synproxy hook to initialize | ||
138 | * sequence number translation once a connection tracking entry exists. | ||
139 | */ | ||
140 | nth->ack_seq = htonl(ntohl(th->ack_seq) - 1); | ||
141 | tcp_flag_word(nth) = TCP_FLAG_SYN; | ||
142 | if (opts->options & XT_SYNPROXY_OPT_ECN) | ||
143 | tcp_flag_word(nth) |= TCP_FLAG_ECE | TCP_FLAG_CWR; | ||
144 | nth->doff = tcp_hdr_size / 4; | ||
145 | nth->window = th->window; | ||
146 | nth->check = 0; | ||
147 | nth->urg_ptr = 0; | ||
148 | |||
149 | synproxy_build_options(nth, opts); | ||
150 | |||
151 | synproxy_send_tcp(skb, nskb, &snet->tmpl->ct_general, IP_CT_NEW, | ||
152 | niph, nth, tcp_hdr_size); | ||
153 | } | ||
154 | |||
155 | static void | ||
156 | synproxy_send_server_ack(const struct synproxy_net *snet, | ||
157 | const struct ip_ct_tcp *state, | ||
158 | const struct sk_buff *skb, const struct tcphdr *th, | ||
159 | const struct synproxy_options *opts) | ||
160 | { | ||
161 | struct sk_buff *nskb; | ||
162 | struct iphdr *iph, *niph; | ||
163 | struct tcphdr *nth; | ||
164 | unsigned int tcp_hdr_size; | ||
165 | |||
166 | iph = ip_hdr(skb); | ||
167 | |||
168 | tcp_hdr_size = sizeof(*nth) + synproxy_options_size(opts); | ||
169 | nskb = alloc_skb(sizeof(*niph) + tcp_hdr_size + MAX_TCP_HEADER, | ||
170 | GFP_ATOMIC); | ||
171 | if (nskb == NULL) | ||
172 | return; | ||
173 | skb_reserve(nskb, MAX_TCP_HEADER); | ||
174 | |||
175 | niph = synproxy_build_ip(nskb, iph->daddr, iph->saddr); | ||
176 | |||
177 | skb_reset_transport_header(nskb); | ||
178 | nth = (struct tcphdr *)skb_put(nskb, tcp_hdr_size); | ||
179 | nth->source = th->dest; | ||
180 | nth->dest = th->source; | ||
181 | nth->seq = htonl(ntohl(th->ack_seq)); | ||
182 | nth->ack_seq = htonl(ntohl(th->seq) + 1); | ||
183 | tcp_flag_word(nth) = TCP_FLAG_ACK; | ||
184 | nth->doff = tcp_hdr_size / 4; | ||
185 | nth->window = htons(state->seen[IP_CT_DIR_ORIGINAL].td_maxwin); | ||
186 | nth->check = 0; | ||
187 | nth->urg_ptr = 0; | ||
188 | |||
189 | synproxy_build_options(nth, opts); | ||
190 | |||
191 | synproxy_send_tcp(skb, nskb, NULL, 0, niph, nth, tcp_hdr_size); | ||
192 | } | ||
193 | |||
194 | static void | ||
195 | synproxy_send_client_ack(const struct synproxy_net *snet, | ||
196 | const struct sk_buff *skb, const struct tcphdr *th, | ||
197 | const struct synproxy_options *opts) | ||
198 | { | ||
199 | struct sk_buff *nskb; | ||
200 | struct iphdr *iph, *niph; | ||
201 | struct tcphdr *nth; | ||
202 | unsigned int tcp_hdr_size; | ||
203 | |||
204 | iph = ip_hdr(skb); | ||
205 | |||
206 | tcp_hdr_size = sizeof(*nth) + synproxy_options_size(opts); | ||
207 | nskb = alloc_skb(sizeof(*niph) + tcp_hdr_size + MAX_TCP_HEADER, | ||
208 | GFP_ATOMIC); | ||
209 | if (nskb == NULL) | ||
210 | return; | ||
211 | skb_reserve(nskb, MAX_TCP_HEADER); | ||
212 | |||
213 | niph = synproxy_build_ip(nskb, iph->saddr, iph->daddr); | ||
214 | |||
215 | skb_reset_transport_header(nskb); | ||
216 | nth = (struct tcphdr *)skb_put(nskb, tcp_hdr_size); | ||
217 | nth->source = th->source; | ||
218 | nth->dest = th->dest; | ||
219 | nth->seq = htonl(ntohl(th->seq) + 1); | ||
220 | nth->ack_seq = th->ack_seq; | ||
221 | tcp_flag_word(nth) = TCP_FLAG_ACK; | ||
222 | nth->doff = tcp_hdr_size / 4; | ||
223 | nth->window = ntohs(htons(th->window) >> opts->wscale); | ||
224 | nth->check = 0; | ||
225 | nth->urg_ptr = 0; | ||
226 | |||
227 | synproxy_build_options(nth, opts); | ||
228 | |||
229 | synproxy_send_tcp(skb, nskb, NULL, 0, niph, nth, tcp_hdr_size); | ||
230 | } | ||
231 | |||
232 | static bool | ||
233 | synproxy_recv_client_ack(const struct synproxy_net *snet, | ||
234 | const struct sk_buff *skb, const struct tcphdr *th, | ||
235 | struct synproxy_options *opts, u32 recv_seq) | ||
236 | { | ||
237 | int mss; | ||
238 | |||
239 | mss = __cookie_v4_check(ip_hdr(skb), th, ntohl(th->ack_seq) - 1); | ||
240 | if (mss == 0) { | ||
241 | this_cpu_inc(snet->stats->cookie_invalid); | ||
242 | return false; | ||
243 | } | ||
244 | |||
245 | this_cpu_inc(snet->stats->cookie_valid); | ||
246 | opts->mss = mss; | ||
247 | |||
248 | if (opts->options & XT_SYNPROXY_OPT_TIMESTAMP) | ||
249 | synproxy_check_timestamp_cookie(opts); | ||
250 | |||
251 | synproxy_send_server_syn(snet, skb, th, opts, recv_seq); | ||
252 | return true; | ||
253 | } | ||
254 | |||
255 | static unsigned int | ||
256 | synproxy_tg4(struct sk_buff *skb, const struct xt_action_param *par) | ||
257 | { | ||
258 | const struct xt_synproxy_info *info = par->targinfo; | ||
259 | struct synproxy_net *snet = synproxy_pernet(dev_net(par->in)); | ||
260 | struct synproxy_options opts = {}; | ||
261 | struct tcphdr *th, _th; | ||
262 | |||
263 | if (nf_ip_checksum(skb, par->hooknum, par->thoff, IPPROTO_TCP)) | ||
264 | return NF_DROP; | ||
265 | |||
266 | th = skb_header_pointer(skb, par->thoff, sizeof(_th), &_th); | ||
267 | if (th == NULL) | ||
268 | return NF_DROP; | ||
269 | |||
270 | synproxy_parse_options(skb, par->thoff, th, &opts); | ||
271 | |||
272 | if (th->syn && !th->ack) { | ||
273 | /* Initial SYN from client */ | ||
274 | this_cpu_inc(snet->stats->syn_received); | ||
275 | |||
276 | if (th->ece && th->cwr) | ||
277 | opts.options |= XT_SYNPROXY_OPT_ECN; | ||
278 | |||
279 | opts.options &= info->options; | ||
280 | if (opts.options & XT_SYNPROXY_OPT_TIMESTAMP) | ||
281 | synproxy_init_timestamp_cookie(info, &opts); | ||
282 | else | ||
283 | opts.options &= ~(XT_SYNPROXY_OPT_WSCALE | | ||
284 | XT_SYNPROXY_OPT_SACK_PERM | | ||
285 | XT_SYNPROXY_OPT_ECN); | ||
286 | |||
287 | synproxy_send_client_synack(skb, th, &opts); | ||
288 | } else if (th->ack && !(th->fin || th->rst)) | ||
289 | /* ACK from client */ | ||
290 | synproxy_recv_client_ack(snet, skb, th, &opts, ntohl(th->seq)); | ||
291 | |||
292 | return NF_DROP; | ||
293 | } | ||
294 | |||
295 | static unsigned int ipv4_synproxy_hook(unsigned int hooknum, | ||
296 | struct sk_buff *skb, | ||
297 | const struct net_device *in, | ||
298 | const struct net_device *out, | ||
299 | int (*okfn)(struct sk_buff *)) | ||
300 | { | ||
301 | struct synproxy_net *snet = synproxy_pernet(dev_net(in ? : out)); | ||
302 | enum ip_conntrack_info ctinfo; | ||
303 | struct nf_conn *ct; | ||
304 | struct nf_conn_synproxy *synproxy; | ||
305 | struct synproxy_options opts = {}; | ||
306 | const struct ip_ct_tcp *state; | ||
307 | struct tcphdr *th, _th; | ||
308 | unsigned int thoff; | ||
309 | |||
310 | ct = nf_ct_get(skb, &ctinfo); | ||
311 | if (ct == NULL) | ||
312 | return NF_ACCEPT; | ||
313 | |||
314 | synproxy = nfct_synproxy(ct); | ||
315 | if (synproxy == NULL) | ||
316 | return NF_ACCEPT; | ||
317 | |||
318 | if (nf_is_loopback_packet(skb)) | ||
319 | return NF_ACCEPT; | ||
320 | |||
321 | thoff = ip_hdrlen(skb); | ||
322 | th = skb_header_pointer(skb, thoff, sizeof(_th), &_th); | ||
323 | if (th == NULL) | ||
324 | return NF_DROP; | ||
325 | |||
326 | state = &ct->proto.tcp; | ||
327 | switch (state->state) { | ||
328 | case TCP_CONNTRACK_CLOSE: | ||
329 | if (th->rst && !test_bit(IPS_SEEN_REPLY_BIT, &ct->status)) { | ||
330 | nf_ct_seqadj_init(ct, ctinfo, synproxy->isn - | ||
331 | ntohl(th->seq) + 1); | ||
332 | break; | ||
333 | } | ||
334 | |||
335 | if (!th->syn || th->ack || | ||
336 | CTINFO2DIR(ctinfo) != IP_CT_DIR_ORIGINAL) | ||
337 | break; | ||
338 | |||
339 | /* Reopened connection - reset the sequence number and timestamp | ||
340 | * adjustments, they will get initialized once the connection is | ||
341 | * reestablished. | ||
342 | */ | ||
343 | nf_ct_seqadj_init(ct, ctinfo, 0); | ||
344 | synproxy->tsoff = 0; | ||
345 | this_cpu_inc(snet->stats->conn_reopened); | ||
346 | |||
347 | /* fall through */ | ||
348 | case TCP_CONNTRACK_SYN_SENT: | ||
349 | synproxy_parse_options(skb, thoff, th, &opts); | ||
350 | |||
351 | if (!th->syn && th->ack && | ||
352 | CTINFO2DIR(ctinfo) == IP_CT_DIR_ORIGINAL) { | ||
353 | /* Keep-Alives are sent with SEG.SEQ = SND.NXT-1, | ||
354 | * therefore we need to add 1 to make the SYN sequence | ||
355 | * number match the one of first SYN. | ||
356 | */ | ||
357 | if (synproxy_recv_client_ack(snet, skb, th, &opts, | ||
358 | ntohl(th->seq) + 1)) | ||
359 | this_cpu_inc(snet->stats->cookie_retrans); | ||
360 | |||
361 | return NF_DROP; | ||
362 | } | ||
363 | |||
364 | synproxy->isn = ntohl(th->ack_seq); | ||
365 | if (opts.options & XT_SYNPROXY_OPT_TIMESTAMP) | ||
366 | synproxy->its = opts.tsecr; | ||
367 | break; | ||
368 | case TCP_CONNTRACK_SYN_RECV: | ||
369 | if (!th->syn || !th->ack) | ||
370 | break; | ||
371 | |||
372 | synproxy_parse_options(skb, thoff, th, &opts); | ||
373 | if (opts.options & XT_SYNPROXY_OPT_TIMESTAMP) | ||
374 | synproxy->tsoff = opts.tsval - synproxy->its; | ||
375 | |||
376 | opts.options &= ~(XT_SYNPROXY_OPT_MSS | | ||
377 | XT_SYNPROXY_OPT_WSCALE | | ||
378 | XT_SYNPROXY_OPT_SACK_PERM); | ||
379 | |||
380 | swap(opts.tsval, opts.tsecr); | ||
381 | synproxy_send_server_ack(snet, state, skb, th, &opts); | ||
382 | |||
383 | nf_ct_seqadj_init(ct, ctinfo, synproxy->isn - ntohl(th->seq)); | ||
384 | |||
385 | swap(opts.tsval, opts.tsecr); | ||
386 | synproxy_send_client_ack(snet, skb, th, &opts); | ||
387 | |||
388 | consume_skb(skb); | ||
389 | return NF_STOLEN; | ||
390 | default: | ||
391 | break; | ||
392 | } | ||
393 | |||
394 | synproxy_tstamp_adjust(skb, thoff, th, ct, ctinfo, synproxy); | ||
395 | return NF_ACCEPT; | ||
396 | } | ||
397 | |||
398 | static int synproxy_tg4_check(const struct xt_tgchk_param *par) | ||
399 | { | ||
400 | const struct ipt_entry *e = par->entryinfo; | ||
401 | |||
402 | if (e->ip.proto != IPPROTO_TCP || | ||
403 | e->ip.invflags & XT_INV_PROTO) | ||
404 | return -EINVAL; | ||
405 | |||
406 | return nf_ct_l3proto_try_module_get(par->family); | ||
407 | } | ||
408 | |||
409 | static void synproxy_tg4_destroy(const struct xt_tgdtor_param *par) | ||
410 | { | ||
411 | nf_ct_l3proto_module_put(par->family); | ||
412 | } | ||
413 | |||
414 | static struct xt_target synproxy_tg4_reg __read_mostly = { | ||
415 | .name = "SYNPROXY", | ||
416 | .family = NFPROTO_IPV4, | ||
417 | .target = synproxy_tg4, | ||
418 | .targetsize = sizeof(struct xt_synproxy_info), | ||
419 | .checkentry = synproxy_tg4_check, | ||
420 | .destroy = synproxy_tg4_destroy, | ||
421 | .me = THIS_MODULE, | ||
422 | }; | ||
423 | |||
424 | static struct nf_hook_ops ipv4_synproxy_ops[] __read_mostly = { | ||
425 | { | ||
426 | .hook = ipv4_synproxy_hook, | ||
427 | .owner = THIS_MODULE, | ||
428 | .pf = NFPROTO_IPV4, | ||
429 | .hooknum = NF_INET_LOCAL_IN, | ||
430 | .priority = NF_IP_PRI_CONNTRACK_CONFIRM - 1, | ||
431 | }, | ||
432 | { | ||
433 | .hook = ipv4_synproxy_hook, | ||
434 | .owner = THIS_MODULE, | ||
435 | .pf = NFPROTO_IPV4, | ||
436 | .hooknum = NF_INET_POST_ROUTING, | ||
437 | .priority = NF_IP_PRI_CONNTRACK_CONFIRM - 1, | ||
438 | }, | ||
439 | }; | ||
440 | |||
441 | static int __init synproxy_tg4_init(void) | ||
442 | { | ||
443 | int err; | ||
444 | |||
445 | err = nf_register_hooks(ipv4_synproxy_ops, | ||
446 | ARRAY_SIZE(ipv4_synproxy_ops)); | ||
447 | if (err < 0) | ||
448 | goto err1; | ||
449 | |||
450 | err = xt_register_target(&synproxy_tg4_reg); | ||
451 | if (err < 0) | ||
452 | goto err2; | ||
453 | |||
454 | return 0; | ||
455 | |||
456 | err2: | ||
457 | nf_unregister_hooks(ipv4_synproxy_ops, ARRAY_SIZE(ipv4_synproxy_ops)); | ||
458 | err1: | ||
459 | return err; | ||
460 | } | ||
461 | |||
462 | static void __exit synproxy_tg4_exit(void) | ||
463 | { | ||
464 | xt_unregister_target(&synproxy_tg4_reg); | ||
465 | nf_unregister_hooks(ipv4_synproxy_ops, ARRAY_SIZE(ipv4_synproxy_ops)); | ||
466 | } | ||
467 | |||
468 | module_init(synproxy_tg4_init); | ||
469 | module_exit(synproxy_tg4_exit); | ||
470 | |||
471 | MODULE_LICENSE("GPL"); | ||
472 | MODULE_AUTHOR("Patrick McHardy <kaber@trash.net>"); | ||
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 | ||
411 | config NETFILTER_SYNPROXY | ||
412 | tristate | ||
413 | |||
411 | endif # NF_CONNTRACK | 414 | endif # NF_CONNTRACK |
412 | 415 | ||
413 | config NETFILTER_XTABLES | 416 | config 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 | |||
61 | obj-$(CONFIG_NF_NAT_SIP) += nf_nat_sip.o | 61 | obj-$(CONFIG_NF_NAT_SIP) += nf_nat_sip.o |
62 | obj-$(CONFIG_NF_NAT_TFTP) += nf_nat_tftp.o | 62 | obj-$(CONFIG_NF_NAT_TFTP) += nf_nat_tftp.o |
63 | 63 | ||
64 | # SYNPROXY | ||
65 | obj-$(CONFIG_NETFILTER_SYNPROXY) += nf_synproxy_core.o | ||
66 | |||
64 | # generic X tables | 67 | # generic X tables |
65 | obj-$(CONFIG_NETFILTER_XTABLES) += x_tables.o xt_tcpudp.o | 68 | obj-$(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 | ||
9 | int 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 | } | ||
27 | EXPORT_SYMBOL_GPL(nf_ct_seqadj_init); | ||
28 | |||
9 | int nf_ct_seqadj_set(struct nf_conn *ct, enum ip_conntrack_info ctinfo, | 29 | int 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 | |||
24 | int synproxy_net_id; | ||
25 | EXPORT_SYMBOL_GPL(synproxy_net_id); | ||
26 | |||
27 | void | ||
28 | synproxy_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 | } | ||
88 | EXPORT_SYMBOL_GPL(synproxy_parse_options); | ||
89 | |||
90 | unsigned 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 | } | ||
105 | EXPORT_SYMBOL_GPL(synproxy_options_size); | ||
106 | |||
107 | void | ||
108 | synproxy_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 | } | ||
144 | EXPORT_SYMBOL_GPL(synproxy_build_options); | ||
145 | |||
146 | void 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 | } | ||
163 | EXPORT_SYMBOL_GPL(synproxy_init_timestamp_cookie); | ||
164 | |||
165 | void 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 | } | ||
175 | EXPORT_SYMBOL_GPL(synproxy_check_timestamp_cookie); | ||
176 | |||
177 | unsigned 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 | } | ||
232 | EXPORT_SYMBOL_GPL(synproxy_tstamp_adjust); | ||
233 | |||
234 | static 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 | ||
241 | static 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 | |||
259 | static 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 | |||
274 | static void synproxy_cpu_seq_stop(struct seq_file *seq, void *v) | ||
275 | { | ||
276 | return; | ||
277 | } | ||
278 | |||
279 | static 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 | |||
300 | static 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 | |||
307 | static 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 | |||
313 | static 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 | |||
321 | static 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 | |||
329 | static void __net_exit synproxy_proc_exit(struct net *net) | ||
330 | { | ||
331 | remove_proc_entry("synproxy", net->proc_net_stat); | ||
332 | } | ||
333 | #else | ||
334 | static int __net_init synproxy_proc_init(struct net *net) | ||
335 | { | ||
336 | return 0; | ||
337 | } | ||
338 | |||
339 | static void __net_exit synproxy_proc_exit(struct net *net) | ||
340 | { | ||
341 | return; | ||
342 | } | ||
343 | #endif /* CONFIG_PROC_FS */ | ||
344 | |||
345 | static 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 | |||
378 | err3: | ||
379 | free_percpu(snet->stats); | ||
380 | err2: | ||
381 | nf_conntrack_free(ct); | ||
382 | err1: | ||
383 | return err; | ||
384 | } | ||
385 | |||
386 | static 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 | |||
395 | static 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 | |||
402 | static 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 | |||
416 | err2: | ||
417 | nf_ct_extend_unregister(&nf_ct_synproxy_extend); | ||
418 | err1: | ||
419 | return err; | ||
420 | } | ||
421 | |||
422 | static 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 | |||
428 | module_init(synproxy_core_init); | ||
429 | module_exit(synproxy_core_exit); | ||
430 | |||
431 | MODULE_LICENSE("GPL"); | ||
432 | MODULE_AUTHOR("Patrick McHardy <kaber@trash.net>"); | ||