| /* |
| * (C) 2012 by Jozsef Kadlecsik <kadlec@blackhole.kfki.hu> |
| * (C) 2012 by Pablo Neira Ayuso <pablo@netfilter.org> |
| * |
| * Sponsored by Vyatta Inc. <http://www.vyatta.com> |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation. |
| */ |
| |
| #include "conntrackd.h" |
| #include "network.h" /* for before and after */ |
| #include "helper.h" |
| #include "myct.h" |
| #include "log.h" |
| |
| #include <ctype.h> /* for isdigit */ |
| #include <errno.h> |
| |
| #define _GNU_SOURCE |
| #include <netinet/tcp.h> |
| |
| #include <libmnl/libmnl.h> |
| #include <libnetfilter_conntrack/libnetfilter_conntrack.h> |
| #include <libnetfilter_queue/libnetfilter_queue.h> |
| #include <libnetfilter_queue/libnetfilter_queue_tcp.h> |
| #include <libnetfilter_queue/pktbuff.h> |
| #include <linux/netfilter.h> |
| |
| /* TNS SQL*Net Version 2 */ |
| enum tns_types { |
| TNS_TYPE_CONNECT = 1, |
| TNS_TYPE_ACCEPT = 2, |
| TNS_TYPE_ACK = 3, |
| TNS_TYPE_REFUSE = 4, |
| TNS_TYPE_REDIRECT = 5, |
| TNS_TYPE_DATA = 6, |
| TNS_TYPE_NULL = 7, |
| TNS_TYPE_ABORT = 9, |
| TNS_TYPE_RESEND = 11, |
| TNS_TYPE_MARKER = 12, |
| TNS_TYPE_ATTENTION = 13, |
| TNS_TYPE_CONTROL = 14, |
| TNS_TYPE_MAX = 19, |
| }; |
| |
| struct tns_header { |
| uint16_t len; |
| uint16_t csum; |
| uint8_t type; |
| uint8_t reserved; |
| uint16_t header_csum; |
| }; |
| |
| struct tns_redirect { |
| uint16_t data_len; |
| }; |
| |
| struct tns_info { |
| /* Scan next DATA|REDIRECT packet */ |
| bool parse; |
| }; |
| |
| static int try_number(const char *data, size_t dlen, uint32_t array[], |
| int array_size, char sep, char term) |
| { |
| uint32_t len; |
| int i; |
| |
| memset(array, 0, sizeof(array[0])*array_size); |
| |
| /* Keep data pointing at next char. */ |
| for (i = 0, len = 0; len < dlen && i < array_size; len++, data++) { |
| if (*data >= '0' && *data <= '9') { |
| array[i] = array[i]*10 + *data - '0'; |
| } |
| else if (*data == sep) |
| i++; |
| else { |
| /* Skip spaces. */ |
| if (*data == ' ') |
| continue; |
| /* Unexpected character; true if it's the |
| terminator and we're finished. */ |
| if (*data == term && i == array_size - 1) |
| return len; |
| pr_debug("Char %u (got %u nums) `%c' unexpected\n", |
| len, i, *data); |
| return 0; |
| } |
| } |
| pr_debug("Failed to fill %u numbers separated by %c\n", |
| array_size, sep); |
| return 0; |
| } |
| |
| /* Grab port: number up to delimiter */ |
| static int get_port(const char *data, size_t dlen, char delim, |
| struct myct_man *cmd) |
| { |
| uint16_t tmp_port = 0; |
| uint32_t i; |
| |
| for (i = 0; i < dlen; i++) { |
| /* Finished? */ |
| if (data[i] == delim) { |
| if (tmp_port == 0) |
| break; |
| cmd->u.port = htons(tmp_port); |
| pr_debug("get_port: return %d\n", tmp_port); |
| return i + 1; |
| } |
| else if (data[i] >= '0' && data[i] <= '9') |
| tmp_port = tmp_port*10 + data[i] - '0'; |
| else if (data[i] == ' ') /* Skip spaces */ |
| continue; |
| else { /* Some other crap */ |
| pr_debug("get_port: invalid char `%c'\n", data[i]); |
| break; |
| } |
| } |
| return 0; |
| } |
| |
| /* (ADDRESS=(PROTOCOL=tcp)(DEV=x)(HOST=a.b.c.d)(PORT=a)) */ |
| /* FIXME: handle hostnames */ |
| |
| /* Returns 0, or length of port number */ |
| static unsigned int |
| find_pattern(struct pkt_buff *pkt, unsigned int dataoff, size_t dlen, |
| struct myct_man *cmd, unsigned int *numoff) |
| { |
| const char *data = (const char *)pktb_network_header(pkt) + dataoff |
| + sizeof(struct tns_header); |
| int length, offset, ret; |
| uint32_t array[4]; |
| const char *p, *start; |
| |
| p = strstr(data, "("); |
| if (!p) |
| return 0; |
| |
| p = strstr(p+1, "HOST="); |
| if (!p) { |
| pr_debug("HOST= not found\n"); |
| return 0; |
| } |
| |
| start = p + strlen("HOST="); |
| offset = (int)(p - data) + strlen("HOST="); |
| *numoff = offset + sizeof(struct tns_header); |
| data += offset; |
| |
| length = try_number(data, dlen - offset, array, 4, '.', ')'); |
| if (length == 0) |
| return 0; |
| |
| cmd->u3.ip = htonl((array[0] << 24) | (array[1] << 16) | |
| (array[2] << 8) | array[3]); |
| |
| p = strstr(data+length, "("); |
| if (!p) |
| return 0; |
| |
| p = strstr(p, "PORT="); |
| if (!p) { |
| pr_debug("PORT= not found\n"); |
| return 0; |
| } |
| |
| p += strlen("PORT="); |
| ret = get_port(p, dlen - offset - length, ')', cmd); |
| if (ret == 0) |
| return 0; |
| |
| p += ret; |
| return (int)(p - start); |
| } |
| |
| static inline uint16_t |
| nton(uint16_t len, unsigned int matchoff, unsigned int matchlen) |
| { |
| uint32_t l = (uint32_t)ntohs(len) + matchoff - matchlen; |
| |
| return htons(l); |
| } |
| |
| /* So, this packet has hit the connection tracking matching code. |
| Mangle it, and change the expectation to match the new version. */ |
| static unsigned int |
| nf_nat_tns(struct pkt_buff *pkt, struct tns_header *tns, struct nf_expect *exp, |
| struct nf_conntrack *ct, int dir, |
| unsigned int matchoff, unsigned int matchlen) |
| { |
| union nfct_attr_grp_addr newip; |
| char buffer[sizeof("255.255.255.255)(PORT=65535)")]; |
| unsigned int buflen; |
| const struct nf_conntrack *expected; |
| struct nf_conntrack *nat_tuple; |
| uint16_t initial_port, port; |
| |
| /* Connection will come from wherever this packet goes, hence !dir */ |
| cthelper_get_addr_dst(ct, !dir, &newip); |
| |
| expected = nfexp_get_attr(exp, ATTR_EXP_EXPECTED); |
| |
| nat_tuple = nfct_new(); |
| if (nat_tuple == NULL) |
| return NF_ACCEPT; |
| |
| initial_port = nfct_get_attr_u16(expected, ATTR_PORT_DST); |
| |
| nfexp_set_attr_u32(exp, ATTR_EXP_NAT_DIR, !dir); |
| |
| /* libnetfilter_conntrack needs this */ |
| nfct_set_attr_u8(nat_tuple, ATTR_L3PROTO, AF_INET); |
| nfct_set_attr_u32(nat_tuple, ATTR_IPV4_SRC, 0); |
| nfct_set_attr_u32(nat_tuple, ATTR_IPV4_DST, 0); |
| nfct_set_attr_u8(nat_tuple, ATTR_L4PROTO, IPPROTO_TCP); |
| nfct_set_attr_u16(nat_tuple, ATTR_PORT_DST, 0); |
| |
| /* When you see the packet, we need to NAT it the same as the |
| * this one. */ |
| nfexp_set_attr(exp, ATTR_EXP_FN, "nat-follow-master"); |
| |
| /* Try to get same port: if not, try to change it. */ |
| for (port = ntohs(initial_port); port != 0; port++) { |
| int ret; |
| |
| nfct_set_attr_u16(nat_tuple, ATTR_PORT_SRC, htons(port)); |
| nfexp_set_attr(exp, ATTR_EXP_NAT_TUPLE, nat_tuple); |
| |
| ret = cthelper_add_expect(exp); |
| if (ret == 0) |
| break; |
| else if (ret != -EBUSY) { |
| port = 0; |
| break; |
| } |
| } |
| nfct_destroy(nat_tuple); |
| |
| if (port == 0) |
| return NF_DROP; |
| |
| buflen = snprintf(buffer, sizeof(buffer), |
| "%u.%u.%u.%u)(PORT=%u)", |
| ((unsigned char *)&newip.ip)[0], |
| ((unsigned char *)&newip.ip)[1], |
| ((unsigned char *)&newip.ip)[2], |
| ((unsigned char *)&newip.ip)[3], port); |
| if (!buflen) |
| goto out; |
| |
| if (!nfq_tcp_mangle_ipv4(pkt, matchoff, matchlen, buffer, buflen)) |
| goto out; |
| |
| if (buflen != matchlen) { |
| /* FIXME: recalculate checksum */ |
| tns->csum = 0; |
| tns->header_csum = 0; |
| |
| tns->len = nton(tns->len, matchlen, buflen); |
| if (tns->type == TNS_TYPE_REDIRECT) { |
| struct tns_redirect *r; |
| |
| r = (struct tns_redirect *)((char *)tns + sizeof(struct tns_header)); |
| |
| r->data_len = nton(r->data_len, matchlen, buflen); |
| } |
| } |
| |
| return NF_ACCEPT; |
| |
| out: |
| cthelper_del_expect(exp); |
| return NF_DROP; |
| } |
| |
| static int |
| tns_helper_cb(struct pkt_buff *pkt, uint32_t protoff, |
| struct myct *myct, uint32_t ctinfo) |
| { |
| struct tcphdr *th; |
| struct tns_header *tns; |
| int dir = CTINFO2DIR(ctinfo); |
| unsigned int dataoff, datalen, numoff = 0, numlen; |
| struct tns_info *tns_info = myct->priv_data; |
| union nfct_attr_grp_addr addr; |
| struct nf_expect *exp = NULL; |
| struct myct_man cmd; |
| int ret = NF_ACCEPT; |
| |
| memset(&cmd, 0, sizeof(struct myct_man)); |
| memset(&addr, 0, sizeof(union nfct_attr_grp_addr)); |
| |
| /* Until there's been traffic both ways, don't look into TCP packets. */ |
| if (ctinfo != IP_CT_ESTABLISHED |
| && ctinfo != IP_CT_ESTABLISHED_REPLY) { |
| pr_debug("TNS: Conntrackinfo = %u\n", ctinfo); |
| goto out; |
| } |
| /* Parse server direction only */ |
| if (dir != MYCT_DIR_REPL) { |
| pr_debug("TNS: skip client direction\n"); |
| goto out; |
| } |
| |
| th = (struct tcphdr *) (pktb_network_header(pkt) + protoff); |
| |
| dataoff = protoff + th->doff * 4; |
| datalen = pktb_len(pkt); |
| |
| if (datalen < sizeof(struct tns_header)) { |
| pr_debug("TNS: skip packet with short header\n"); |
| goto out; |
| } |
| |
| tns = (struct tns_header *)(pktb_network_header(pkt) + dataoff); |
| |
| if (tns->type == TNS_TYPE_REDIRECT) { |
| struct tns_redirect *redirect; |
| |
| dataoff += sizeof(struct tns_header); |
| datalen -= sizeof(struct tns_header); |
| redirect = (struct tns_redirect *)(pktb_network_header(pkt) + dataoff); |
| tns_info->parse = false; |
| |
| if (ntohs(redirect->data_len) == 0) { |
| tns_info->parse = true; |
| goto out; |
| } |
| goto parse; |
| } |
| |
| /* Parse only the very next DATA packet */ |
| if (!(tns_info->parse && tns->type == TNS_TYPE_DATA)) { |
| tns_info->parse = false; |
| goto out; |
| } |
| parse: |
| numlen = find_pattern(pkt, dataoff, datalen, &cmd, &numoff); |
| tns_info->parse = false; |
| if (!numlen) |
| goto out; |
| |
| /* We refer to the reverse direction ("!dir") tuples here, |
| * because we're expecting something in the other direction. |
| * Doesn't matter unless NAT is happening. */ |
| cthelper_get_addr_src(myct->ct, !dir, &addr); |
| |
| exp = nfexp_new(); |
| if (exp == NULL) |
| goto out; |
| |
| if (cthelper_expect_init(exp, myct->ct, 0, |
| &addr, &cmd.u3, |
| IPPROTO_TCP, |
| NULL, &cmd.u.port, 0)) { |
| pr_debug("TNS: failed to init expectation\n"); |
| goto out_exp; |
| } |
| |
| /* Now, NAT might want to mangle the packet, and register the |
| * (possibly changed) expectation itself. |
| */ |
| if (nfct_get_attr_u32(myct->ct, ATTR_STATUS) & IPS_NAT_MASK) { |
| ret = nf_nat_tns(pkt, tns, exp, myct->ct, dir, |
| numoff + sizeof(struct tns_header), numlen); |
| goto out_exp; |
| } |
| |
| /* Can't expect this? Best to drop packet now. */ |
| if (cthelper_add_expect(exp) < 0) { |
| pr_debug("TNS: cannot add expectation: %s\n", |
| strerror(errno)); |
| ret = NF_DROP; |
| goto out_exp; |
| } |
| goto out; |
| |
| out_exp: |
| nfexp_destroy(exp); |
| out: |
| return ret; |
| } |
| |
| static struct ctd_helper tns_helper = { |
| .name = "tns", |
| .l4proto = IPPROTO_TCP, |
| .cb = tns_helper_cb, |
| .priv_data_len = sizeof(struct tns_info), |
| .policy = { |
| [0] = { |
| .name = "tns", |
| .expect_max = 1, |
| .expect_timeout = 300, |
| }, |
| }, |
| }; |
| |
| void __attribute__ ((constructor)) tns_init(void); |
| |
| void tns_init(void) |
| { |
| helper_register(&tns_helper); |
| } |