1/* 2 * (C) 2012 by Jozsef Kadlecsik <kadlec@blackhole.kfki.hu> 3 * (C) 2012 by Pablo Neira Ayuso <pablo@netfilter.org> 4 * 5 * Sponsored by Vyatta Inc. <http://www.vyatta.com> 6 * 7 * This program is free software; you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License version 2 as 9 * published by the Free Software Foundation. 10 */ 11 12#include "conntrackd.h" 13#include "network.h" /* for before and after */ 14#include "helper.h" 15#include "myct.h" 16#include "log.h" 17 18#include <ctype.h> /* for isdigit */ 19#include <errno.h> 20 21#include <netinet/tcp.h> 22 23#include <libmnl/libmnl.h> 24#include <libnetfilter_conntrack/libnetfilter_conntrack.h> 25#include <libnetfilter_queue/libnetfilter_queue.h> 26#include <libnetfilter_queue/libnetfilter_queue_tcp.h> 27#include <libnetfilter_queue/pktbuff.h> 28#include <linux/netfilter.h> 29 30/* TNS SQL*Net Version 2 */ 31enum tns_types { 32 TNS_TYPE_CONNECT = 1, 33 TNS_TYPE_ACCEPT = 2, 34 TNS_TYPE_ACK = 3, 35 TNS_TYPE_REFUSE = 4, 36 TNS_TYPE_REDIRECT = 5, 37 TNS_TYPE_DATA = 6, 38 TNS_TYPE_NULL = 7, 39 TNS_TYPE_ABORT = 9, 40 TNS_TYPE_RESEND = 11, 41 TNS_TYPE_MARKER = 12, 42 TNS_TYPE_ATTENTION = 13, 43 TNS_TYPE_CONTROL = 14, 44 TNS_TYPE_MAX = 19, 45}; 46 47struct tns_header { 48 uint16_t len; 49 uint16_t csum; 50 uint8_t type; 51 uint8_t reserved; 52 uint16_t header_csum; 53}; 54 55struct tns_redirect { 56 uint16_t data_len; 57}; 58 59struct tns_info { 60 /* Scan next DATA|REDIRECT packet */ 61 bool parse; 62}; 63 64static int try_number(const char *data, size_t dlen, uint32_t array[], 65 int array_size, char sep, char term) 66{ 67 uint32_t len; 68 int i; 69 70 memset(array, 0, sizeof(array[0])*array_size); 71 72 /* Keep data pointing at next char. */ 73 for (i = 0, len = 0; len < dlen && i < array_size; len++, data++) { 74 if (*data >= '0' && *data <= '9') { 75 array[i] = array[i]*10 + *data - '0'; 76 } 77 else if (*data == sep) 78 i++; 79 else { 80 /* Skip spaces. */ 81 if (*data == ' ') 82 continue; 83 /* Unexpected character; true if it's the 84 terminator and we're finished. */ 85 if (*data == term && i == array_size - 1) 86 return len; 87 pr_debug("Char %u (got %u nums) `%c' unexpected\n", 88 len, i, *data); 89 return 0; 90 } 91 } 92 pr_debug("Failed to fill %u numbers separated by %c\n", 93 array_size, sep); 94 return 0; 95} 96 97/* Grab port: number up to delimiter */ 98static int get_port(const char *data, size_t dlen, char delim, 99 struct myct_man *cmd) 100{ 101 uint16_t tmp_port = 0; 102 uint32_t i; 103 104 for (i = 0; i < dlen; i++) { 105 /* Finished? */ 106 if (data[i] == delim) { 107 if (tmp_port == 0) 108 break; 109 cmd->u.port = htons(tmp_port); 110 pr_debug("get_port: return %d\n", tmp_port); 111 return i + 1; 112 } 113 else if (data[i] >= '0' && data[i] <= '9') 114 tmp_port = tmp_port*10 + data[i] - '0'; 115 else if (data[i] == ' ') /* Skip spaces */ 116 continue; 117 else { /* Some other crap */ 118 pr_debug("get_port: invalid char `%c'\n", data[i]); 119 break; 120 } 121 } 122 return 0; 123} 124 125/* (ADDRESS=(PROTOCOL=tcp)(DEV=x)(HOST=a.b.c.d)(PORT=a)) */ 126/* FIXME: handle hostnames */ 127 128/* Returns 0, or length of port number */ 129static unsigned int 130find_pattern(struct pkt_buff *pkt, unsigned int dataoff, size_t dlen, 131 struct myct_man *cmd, unsigned int *numoff) 132{ 133 const char *data = (const char *)pktb_network_header(pkt) + dataoff 134 + sizeof(struct tns_header); 135 int length, offset, ret; 136 uint32_t array[4]; 137 const char *p, *start; 138 139 p = strstr(data, "("); 140 if (!p) 141 return 0; 142 143 p = strstr(p+1, "HOST="); 144 if (!p) { 145 pr_debug("HOST= not found\n"); 146 return 0; 147 } 148 149 start = p + strlen("HOST="); 150 offset = (int)(p - data) + strlen("HOST="); 151 *numoff = offset + sizeof(struct tns_header); 152 data += offset; 153 154 length = try_number(data, dlen - offset, array, 4, '.', ')'); 155 if (length == 0) 156 return 0; 157 158 cmd->u3.ip = htonl((array[0] << 24) | (array[1] << 16) | 159 (array[2] << 8) | array[3]); 160 161 p = strstr(data+length, "("); 162 if (!p) 163 return 0; 164 165 p = strstr(p, "PORT="); 166 if (!p) { 167 pr_debug("PORT= not found\n"); 168 return 0; 169 } 170 171 p += strlen("PORT="); 172 ret = get_port(p, dlen - offset - length, ')', cmd); 173 if (ret == 0) 174 return 0; 175 176 p += ret; 177 return (int)(p - start); 178} 179 180static inline uint16_t 181nton(uint16_t len, unsigned int matchoff, unsigned int matchlen) 182{ 183 uint32_t l = (uint32_t)ntohs(len) + matchoff - matchlen; 184 185 return htons(l); 186} 187 188/* So, this packet has hit the connection tracking matching code. 189 Mangle it, and change the expectation to match the new version. */ 190static unsigned int 191nf_nat_tns(struct pkt_buff *pkt, struct tns_header *tns, struct nf_expect *exp, 192 struct nf_conntrack *ct, int dir, 193 unsigned int matchoff, unsigned int matchlen) 194{ 195 union nfct_attr_grp_addr newip; 196 char buffer[sizeof("255.255.255.255)(PORT=65535)")]; 197 unsigned int buflen; 198 const struct nf_conntrack *expected; 199 struct nf_conntrack *nat_tuple; 200 uint16_t initial_port, port; 201 202 /* Connection will come from wherever this packet goes, hence !dir */ 203 cthelper_get_addr_dst(ct, !dir, &newip); 204 205 expected = nfexp_get_attr(exp, ATTR_EXP_EXPECTED); 206 207 nat_tuple = nfct_new(); 208 if (nat_tuple == NULL) 209 return NF_ACCEPT; 210 211 initial_port = nfct_get_attr_u16(expected, ATTR_PORT_DST); 212 213 nfexp_set_attr_u32(exp, ATTR_EXP_NAT_DIR, !dir); 214 215 /* libnetfilter_conntrack needs this */ 216 nfct_set_attr_u8(nat_tuple, ATTR_L3PROTO, AF_INET); 217 nfct_set_attr_u32(nat_tuple, ATTR_IPV4_SRC, 0); 218 nfct_set_attr_u32(nat_tuple, ATTR_IPV4_DST, 0); 219 nfct_set_attr_u8(nat_tuple, ATTR_L4PROTO, IPPROTO_TCP); 220 nfct_set_attr_u16(nat_tuple, ATTR_PORT_DST, 0); 221 222 /* When you see the packet, we need to NAT it the same as the 223 * this one. */ 224 nfexp_set_attr(exp, ATTR_EXP_FN, "nat-follow-master"); 225 226 /* Try to get same port: if not, try to change it. */ 227 for (port = ntohs(initial_port); port != 0; port++) { 228 int ret; 229 230 nfct_set_attr_u16(nat_tuple, ATTR_PORT_SRC, htons(port)); 231 nfexp_set_attr(exp, ATTR_EXP_NAT_TUPLE, nat_tuple); 232 233 ret = cthelper_add_expect(exp); 234 if (ret == 0) 235 break; 236 else if (ret != -EBUSY) { 237 port = 0; 238 break; 239 } 240 } 241 nfct_destroy(nat_tuple); 242 243 if (port == 0) 244 return NF_DROP; 245 246 buflen = snprintf(buffer, sizeof(buffer), 247 "%u.%u.%u.%u)(PORT=%u)", 248 ((unsigned char *)&newip.ip)[0], 249 ((unsigned char *)&newip.ip)[1], 250 ((unsigned char *)&newip.ip)[2], 251 ((unsigned char *)&newip.ip)[3], port); 252 if (!buflen) 253 goto out; 254 255 if (!nfq_tcp_mangle_ipv4(pkt, matchoff, matchlen, buffer, buflen)) 256 goto out; 257 258 if (buflen != matchlen) { 259 /* FIXME: recalculate checksum */ 260 tns->csum = 0; 261 tns->header_csum = 0; 262 263 tns->len = nton(tns->len, matchlen, buflen); 264 if (tns->type == TNS_TYPE_REDIRECT) { 265 struct tns_redirect *r; 266 267 r = (struct tns_redirect *)((char *)tns + sizeof(struct tns_header)); 268 269 r->data_len = nton(r->data_len, matchlen, buflen); 270 } 271 } 272 273 return NF_ACCEPT; 274 275out: 276 cthelper_del_expect(exp); 277 return NF_DROP; 278} 279 280static int 281tns_helper_cb(struct pkt_buff *pkt, uint32_t protoff, 282 struct myct *myct, uint32_t ctinfo) 283{ 284 struct tcphdr *th; 285 struct tns_header *tns; 286 int dir = CTINFO2DIR(ctinfo); 287 unsigned int dataoff, datalen, numoff = 0, numlen; 288 struct tns_info *tns_info = myct->priv_data; 289 union nfct_attr_grp_addr addr; 290 struct nf_expect *exp = NULL; 291 struct myct_man cmd; 292 int ret = NF_ACCEPT; 293 294 memset(&cmd, 0, sizeof(struct myct_man)); 295 memset(&addr, 0, sizeof(union nfct_attr_grp_addr)); 296 297 /* Until there's been traffic both ways, don't look into TCP packets. */ 298 if (ctinfo != IP_CT_ESTABLISHED 299 && ctinfo != IP_CT_ESTABLISHED_REPLY) { 300 pr_debug("TNS: Conntrackinfo = %u\n", ctinfo); 301 goto out; 302 } 303 /* Parse server direction only */ 304 if (dir != MYCT_DIR_REPL) { 305 pr_debug("TNS: skip client direction\n"); 306 goto out; 307 } 308 309 th = (struct tcphdr *) (pktb_network_header(pkt) + protoff); 310 311 dataoff = protoff + th->doff * 4; 312 datalen = pktb_len(pkt); 313 314 if (datalen < sizeof(struct tns_header)) { 315 pr_debug("TNS: skip packet with short header\n"); 316 goto out; 317 } 318 319 tns = (struct tns_header *)(pktb_network_header(pkt) + dataoff); 320 321 if (tns->type == TNS_TYPE_REDIRECT) { 322 struct tns_redirect *redirect; 323 324 dataoff += sizeof(struct tns_header); 325 datalen -= sizeof(struct tns_header); 326 redirect = (struct tns_redirect *)(pktb_network_header(pkt) + dataoff); 327 tns_info->parse = false; 328 329 if (ntohs(redirect->data_len) == 0) { 330 tns_info->parse = true; 331 goto out; 332 } 333 goto parse; 334 } 335 336 /* Parse only the very next DATA packet */ 337 if (!(tns_info->parse && tns->type == TNS_TYPE_DATA)) { 338 tns_info->parse = false; 339 goto out; 340 } 341parse: 342 numlen = find_pattern(pkt, dataoff, datalen, &cmd, &numoff); 343 tns_info->parse = false; 344 if (!numlen) 345 goto out; 346 347 /* We refer to the reverse direction ("!dir") tuples here, 348 * because we're expecting something in the other direction. 349 * Doesn't matter unless NAT is happening. */ 350 cthelper_get_addr_src(myct->ct, !dir, &addr); 351 352 exp = nfexp_new(); 353 if (exp == NULL) 354 goto out; 355 356 if (cthelper_expect_init(exp, myct->ct, 0, 357 &addr, &cmd.u3, 358 IPPROTO_TCP, 359 NULL, &cmd.u.port, 0)) { 360 pr_debug("TNS: failed to init expectation\n"); 361 goto out_exp; 362 } 363 364 /* Now, NAT might want to mangle the packet, and register the 365 * (possibly changed) expectation itself. 366 */ 367 if (nfct_get_attr_u32(myct->ct, ATTR_STATUS) & IPS_NAT_MASK) { 368 ret = nf_nat_tns(pkt, tns, exp, myct->ct, dir, 369 numoff + sizeof(struct tns_header), numlen); 370 goto out_exp; 371 } 372 373 /* Can't expect this? Best to drop packet now. */ 374 if (cthelper_add_expect(exp) < 0) { 375 pr_debug("TNS: cannot add expectation: %s\n", 376 strerror(errno)); 377 ret = NF_DROP; 378 goto out_exp; 379 } 380 goto out; 381 382out_exp: 383 nfexp_destroy(exp); 384out: 385 return ret; 386} 387 388static struct ctd_helper tns_helper = { 389 .name = "tns", 390 .l4proto = IPPROTO_TCP, 391 .cb = tns_helper_cb, 392 .priv_data_len = sizeof(struct tns_info), 393 .policy = { 394 [0] = { 395 .name = "tns", 396 .expect_max = 1, 397 .expect_timeout = 300, 398 }, 399 }, 400}; 401 402void __attribute__ ((constructor)) tns_init(void); 403 404void tns_init(void) 405{ 406 helper_register(&tns_helper); 407} 408