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 #include <linux/proc_fs.h> 15 16 #include <linux/netfilter_ipv4/ip_tables.h> 17 #include <linux/netfilter/x_tables.h> 18 #include <linux/netfilter/xt_tcpudp.h> 19 #include <linux/netfilter/xt_SYNPROXY.h> 20 21 #include <net/netfilter/nf_conntrack.h> 22 #include <net/netfilter/nf_conntrack_extend.h> 23 #include <net/netfilter/nf_conntrack_seqadj.h> 24 #include <net/netfilter/nf_conntrack_synproxy.h> 25 #include <net/netfilter/nf_conntrack_zones.h> 26 27 unsigned int synproxy_net_id; 28 EXPORT_SYMBOL_GPL(synproxy_net_id); 29 30 bool 31 synproxy_parse_options(const struct sk_buff *skb, unsigned int doff, 32 const struct tcphdr *th, struct synproxy_options *opts) 33 { 34 int length = (th->doff * 4) - sizeof(*th); 35 u8 buf[40], *ptr; 36 37 ptr = skb_header_pointer(skb, doff + sizeof(*th), length, buf); 38 if (ptr == NULL) 39 return false; 40 41 opts->options = 0; 42 while (length > 0) { 43 int opcode = *ptr++; 44 int opsize; 45 46 switch (opcode) { 47 case TCPOPT_EOL: 48 return true; 49 case TCPOPT_NOP: 50 length--; 51 continue; 52 default: 53 opsize = *ptr++; 54 if (opsize < 2) 55 return true; 56 if (opsize > length) 57 return true; 58 59 switch (opcode) { 60 case TCPOPT_MSS: 61 if (opsize == TCPOLEN_MSS) { 62 opts->mss = get_unaligned_be16(ptr); 63 opts->options |= XT_SYNPROXY_OPT_MSS; 64 } 65 break; 66 case TCPOPT_WINDOW: 67 if (opsize == TCPOLEN_WINDOW) { 68 opts->wscale = *ptr; 69 if (opts->wscale > 14) 70 opts->wscale = 14; 71 opts->options |= XT_SYNPROXY_OPT_WSCALE; 72 } 73 break; 74 case TCPOPT_TIMESTAMP: 75 if (opsize == TCPOLEN_TIMESTAMP) { 76 opts->tsval = get_unaligned_be32(ptr); 77 opts->tsecr = get_unaligned_be32(ptr + 4); 78 opts->options |= XT_SYNPROXY_OPT_TIMESTAMP; 79 } 80 break; 81 case TCPOPT_SACK_PERM: 82 if (opsize == TCPOLEN_SACK_PERM) 83 opts->options |= XT_SYNPROXY_OPT_SACK_PERM; 84 break; 85 } 86 87 ptr += opsize - 2; 88 length -= opsize; 89 } 90 } 91 return true; 92 } 93 EXPORT_SYMBOL_GPL(synproxy_parse_options); 94 95 unsigned int synproxy_options_size(const struct synproxy_options *opts) 96 { 97 unsigned int size = 0; 98 99 if (opts->options & XT_SYNPROXY_OPT_MSS) 100 size += TCPOLEN_MSS_ALIGNED; 101 if (opts->options & XT_SYNPROXY_OPT_TIMESTAMP) 102 size += TCPOLEN_TSTAMP_ALIGNED; 103 else if (opts->options & XT_SYNPROXY_OPT_SACK_PERM) 104 size += TCPOLEN_SACKPERM_ALIGNED; 105 if (opts->options & XT_SYNPROXY_OPT_WSCALE) 106 size += TCPOLEN_WSCALE_ALIGNED; 107 108 return size; 109 } 110 EXPORT_SYMBOL_GPL(synproxy_options_size); 111 112 void 113 synproxy_build_options(struct tcphdr *th, const struct synproxy_options *opts) 114 { 115 __be32 *ptr = (__be32 *)(th + 1); 116 u8 options = opts->options; 117 118 if (options & XT_SYNPROXY_OPT_MSS) 119 *ptr++ = htonl((TCPOPT_MSS << 24) | 120 (TCPOLEN_MSS << 16) | 121 opts->mss); 122 123 if (options & XT_SYNPROXY_OPT_TIMESTAMP) { 124 if (options & XT_SYNPROXY_OPT_SACK_PERM) 125 *ptr++ = htonl((TCPOPT_SACK_PERM << 24) | 126 (TCPOLEN_SACK_PERM << 16) | 127 (TCPOPT_TIMESTAMP << 8) | 128 TCPOLEN_TIMESTAMP); 129 else 130 *ptr++ = htonl((TCPOPT_NOP << 24) | 131 (TCPOPT_NOP << 16) | 132 (TCPOPT_TIMESTAMP << 8) | 133 TCPOLEN_TIMESTAMP); 134 135 *ptr++ = htonl(opts->tsval); 136 *ptr++ = htonl(opts->tsecr); 137 } else if (options & XT_SYNPROXY_OPT_SACK_PERM) 138 *ptr++ = htonl((TCPOPT_NOP << 24) | 139 (TCPOPT_NOP << 16) | 140 (TCPOPT_SACK_PERM << 8) | 141 TCPOLEN_SACK_PERM); 142 143 if (options & XT_SYNPROXY_OPT_WSCALE) 144 *ptr++ = htonl((TCPOPT_NOP << 24) | 145 (TCPOPT_WINDOW << 16) | 146 (TCPOLEN_WINDOW << 8) | 147 opts->wscale); 148 } 149 EXPORT_SYMBOL_GPL(synproxy_build_options); 150 151 void synproxy_init_timestamp_cookie(const struct xt_synproxy_info *info, 152 struct synproxy_options *opts) 153 { 154 opts->tsecr = opts->tsval; 155 opts->tsval = tcp_time_stamp & ~0x3f; 156 157 if (opts->options & XT_SYNPROXY_OPT_WSCALE) { 158 opts->tsval |= opts->wscale; 159 opts->wscale = info->wscale; 160 } else 161 opts->tsval |= 0xf; 162 163 if (opts->options & XT_SYNPROXY_OPT_SACK_PERM) 164 opts->tsval |= 1 << 4; 165 166 if (opts->options & XT_SYNPROXY_OPT_ECN) 167 opts->tsval |= 1 << 5; 168 } 169 EXPORT_SYMBOL_GPL(synproxy_init_timestamp_cookie); 170 171 void synproxy_check_timestamp_cookie(struct synproxy_options *opts) 172 { 173 opts->wscale = opts->tsecr & 0xf; 174 if (opts->wscale != 0xf) 175 opts->options |= XT_SYNPROXY_OPT_WSCALE; 176 177 opts->options |= opts->tsecr & (1 << 4) ? XT_SYNPROXY_OPT_SACK_PERM : 0; 178 179 opts->options |= opts->tsecr & (1 << 5) ? XT_SYNPROXY_OPT_ECN : 0; 180 } 181 EXPORT_SYMBOL_GPL(synproxy_check_timestamp_cookie); 182 183 unsigned int synproxy_tstamp_adjust(struct sk_buff *skb, 184 unsigned int protoff, 185 struct tcphdr *th, 186 struct nf_conn *ct, 187 enum ip_conntrack_info ctinfo, 188 const struct nf_conn_synproxy *synproxy) 189 { 190 unsigned int optoff, optend; 191 __be32 *ptr, old; 192 193 if (synproxy->tsoff == 0) 194 return 1; 195 196 optoff = protoff + sizeof(struct tcphdr); 197 optend = protoff + th->doff * 4; 198 199 if (!skb_make_writable(skb, optend)) 200 return 0; 201 202 while (optoff < optend) { 203 unsigned char *op = skb->data + optoff; 204 205 switch (op[0]) { 206 case TCPOPT_EOL: 207 return 1; 208 case TCPOPT_NOP: 209 optoff++; 210 continue; 211 default: 212 if (optoff + 1 == optend || 213 optoff + op[1] > optend || 214 op[1] < 2) 215 return 0; 216 if (op[0] == TCPOPT_TIMESTAMP && 217 op[1] == TCPOLEN_TIMESTAMP) { 218 if (CTINFO2DIR(ctinfo) == IP_CT_DIR_REPLY) { 219 ptr = (__be32 *)&op[2]; 220 old = *ptr; 221 *ptr = htonl(ntohl(*ptr) - 222 synproxy->tsoff); 223 } else { 224 ptr = (__be32 *)&op[6]; 225 old = *ptr; 226 *ptr = htonl(ntohl(*ptr) + 227 synproxy->tsoff); 228 } 229 inet_proto_csum_replace4(&th->check, skb, 230 old, *ptr, false); 231 return 1; 232 } 233 optoff += op[1]; 234 } 235 } 236 return 1; 237 } 238 EXPORT_SYMBOL_GPL(synproxy_tstamp_adjust); 239 240 static struct nf_ct_ext_type nf_ct_synproxy_extend __read_mostly = { 241 .len = sizeof(struct nf_conn_synproxy), 242 .align = __alignof__(struct nf_conn_synproxy), 243 .id = NF_CT_EXT_SYNPROXY, 244 }; 245 246 #ifdef CONFIG_PROC_FS 247 static void *synproxy_cpu_seq_start(struct seq_file *seq, loff_t *pos) 248 { 249 struct synproxy_net *snet = synproxy_pernet(seq_file_net(seq)); 250 int cpu; 251 252 if (*pos == 0) 253 return SEQ_START_TOKEN; 254 255 for (cpu = *pos - 1; cpu < nr_cpu_ids; cpu++) { 256 if (!cpu_possible(cpu)) 257 continue; 258 *pos = cpu + 1; 259 return per_cpu_ptr(snet->stats, cpu); 260 } 261 262 return NULL; 263 } 264 265 static void *synproxy_cpu_seq_next(struct seq_file *seq, void *v, loff_t *pos) 266 { 267 struct synproxy_net *snet = synproxy_pernet(seq_file_net(seq)); 268 int cpu; 269 270 for (cpu = *pos; cpu < nr_cpu_ids; cpu++) { 271 if (!cpu_possible(cpu)) 272 continue; 273 *pos = cpu + 1; 274 return per_cpu_ptr(snet->stats, cpu); 275 } 276 277 return NULL; 278 } 279 280 static void synproxy_cpu_seq_stop(struct seq_file *seq, void *v) 281 { 282 return; 283 } 284 285 static int synproxy_cpu_seq_show(struct seq_file *seq, void *v) 286 { 287 struct synproxy_stats *stats = v; 288 289 if (v == SEQ_START_TOKEN) { 290 seq_printf(seq, "entries\t\tsyn_received\t" 291 "cookie_invalid\tcookie_valid\t" 292 "cookie_retrans\tconn_reopened\n"); 293 return 0; 294 } 295 296 seq_printf(seq, "%08x\t%08x\t%08x\t%08x\t%08x\t%08x\n", 0, 297 stats->syn_received, 298 stats->cookie_invalid, 299 stats->cookie_valid, 300 stats->cookie_retrans, 301 stats->conn_reopened); 302 303 return 0; 304 } 305 306 static const struct seq_operations synproxy_cpu_seq_ops = { 307 .start = synproxy_cpu_seq_start, 308 .next = synproxy_cpu_seq_next, 309 .stop = synproxy_cpu_seq_stop, 310 .show = synproxy_cpu_seq_show, 311 }; 312 313 static int synproxy_cpu_seq_open(struct inode *inode, struct file *file) 314 { 315 return seq_open_net(inode, file, &synproxy_cpu_seq_ops, 316 sizeof(struct seq_net_private)); 317 } 318 319 static const struct file_operations synproxy_cpu_seq_fops = { 320 .owner = THIS_MODULE, 321 .open = synproxy_cpu_seq_open, 322 .read = seq_read, 323 .llseek = seq_lseek, 324 .release = seq_release_net, 325 }; 326 327 static int __net_init synproxy_proc_init(struct net *net) 328 { 329 if (!proc_create("synproxy", S_IRUGO, net->proc_net_stat, 330 &synproxy_cpu_seq_fops)) 331 return -ENOMEM; 332 return 0; 333 } 334 335 static void __net_exit synproxy_proc_exit(struct net *net) 336 { 337 remove_proc_entry("synproxy", net->proc_net_stat); 338 } 339 #else 340 static int __net_init synproxy_proc_init(struct net *net) 341 { 342 return 0; 343 } 344 345 static void __net_exit synproxy_proc_exit(struct net *net) 346 { 347 return; 348 } 349 #endif /* CONFIG_PROC_FS */ 350 351 static int __net_init synproxy_net_init(struct net *net) 352 { 353 struct synproxy_net *snet = synproxy_pernet(net); 354 struct nf_conn *ct; 355 int err = -ENOMEM; 356 357 ct = nf_ct_tmpl_alloc(net, &nf_ct_zone_dflt, GFP_KERNEL); 358 if (!ct) 359 goto err1; 360 361 if (!nfct_seqadj_ext_add(ct)) 362 goto err2; 363 if (!nfct_synproxy_ext_add(ct)) 364 goto err2; 365 366 __set_bit(IPS_CONFIRMED_BIT, &ct->status); 367 nf_conntrack_get(&ct->ct_general); 368 snet->tmpl = ct; 369 370 snet->stats = alloc_percpu(struct synproxy_stats); 371 if (snet->stats == NULL) 372 goto err2; 373 374 err = synproxy_proc_init(net); 375 if (err < 0) 376 goto err3; 377 378 return 0; 379 380 err3: 381 free_percpu(snet->stats); 382 err2: 383 nf_ct_tmpl_free(ct); 384 err1: 385 return err; 386 } 387 388 static void __net_exit synproxy_net_exit(struct net *net) 389 { 390 struct synproxy_net *snet = synproxy_pernet(net); 391 392 nf_ct_put(snet->tmpl); 393 synproxy_proc_exit(net); 394 free_percpu(snet->stats); 395 } 396 397 static struct pernet_operations synproxy_net_ops = { 398 .init = synproxy_net_init, 399 .exit = synproxy_net_exit, 400 .id = &synproxy_net_id, 401 .size = sizeof(struct synproxy_net), 402 }; 403 404 static int __init synproxy_core_init(void) 405 { 406 int err; 407 408 err = nf_ct_extend_register(&nf_ct_synproxy_extend); 409 if (err < 0) 410 goto err1; 411 412 err = register_pernet_subsys(&synproxy_net_ops); 413 if (err < 0) 414 goto err2; 415 416 return 0; 417 418 err2: 419 nf_ct_extend_unregister(&nf_ct_synproxy_extend); 420 err1: 421 return err; 422 } 423 424 static void __exit synproxy_core_exit(void) 425 { 426 unregister_pernet_subsys(&synproxy_net_ops); 427 nf_ct_extend_unregister(&nf_ct_synproxy_extend); 428 } 429 430 module_init(synproxy_core_init); 431 module_exit(synproxy_core_exit); 432 433 MODULE_LICENSE("GPL"); 434 MODULE_AUTHOR("Patrick McHardy <kaber@trash.net>"); 435