/* * H.323 'brute force' extension for NAT alteration. * Jozsef Kadlecsik * * Based on ip_masq_h323.c for 2.2 kernels from CoRiTel, Sofia project. * (http://www.coritel.it/projects/sofia/nat.html) * Uses Sampsa Ranta's excellent idea on using expectfn to 'bind' * the unregistered helpers to the conntrack entries. */ #include #include #include #include #include #include #include #include #include #include #include #include MODULE_AUTHOR("Jozsef Kadlecsik "); MODULE_DESCRIPTION("H.323 'brute force' connection tracking module"); MODULE_LICENSE("GPL"); DECLARE_LOCK_EXTERN(ip_h323_lock); struct module *ip_nat_h323 = THIS_MODULE; #if 0 #define DEBUGP printk #else #define DEBUGP(format, args...) #endif /* FIXME: Time out? --RR */ static unsigned int h225_nat_expected(struct sk_buff **pskb, unsigned int hooknum, struct ip_conntrack *ct, struct ip_nat_info *info); static unsigned int h225_nat_help(struct ip_conntrack *ct, struct ip_conntrack_expect *exp, struct ip_nat_info *info, enum ip_conntrack_info ctinfo, unsigned int hooknum, struct sk_buff **pskb); static struct ip_nat_helper h245 = { { NULL, NULL }, "H.245", /* name */ 0, /* flags */ NULL, /* module */ { { 0, { 0 } }, /* tuple */ { 0, { 0 }, IPPROTO_TCP } }, { { 0, { 0xFFFF } }, /* mask */ { 0, { 0 }, 0xFFFF } }, h225_nat_help, /* helper */ h225_nat_expected /* expectfn */ }; static unsigned int h225_nat_expected(struct sk_buff **pskb, unsigned int hooknum, struct ip_conntrack *ct, struct ip_nat_info *info) { struct ip_nat_multi_range mr; u_int32_t newdstip, newsrcip, newip; u_int16_t port; struct ip_ct_h225_expect *exp_info; struct ip_ct_h225_master *master_info; struct ip_conntrack *master = master_ct(ct); unsigned int is_h225, ret; IP_NF_ASSERT(info); IP_NF_ASSERT(master); IP_NF_ASSERT(!(info->initialized & (1<master->expectant->help.ct_h225_info; exp_info = &ct->master->help.exp_h225_info; LOCK_BH(&ip_h323_lock); DEBUGP("master: "); DUMP_TUPLE(&master->tuplehash[IP_CT_DIR_ORIGINAL].tuple); DUMP_TUPLE(&master->tuplehash[IP_CT_DIR_REPLY].tuple); DEBUGP("conntrack: "); DUMP_TUPLE(&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple); if (exp_info->dir == IP_CT_DIR_ORIGINAL) { /* Make connection go to the client. */ newdstip = master->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.ip; newsrcip = master->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.ip; DEBUGP("h225_nat_expected: %u.%u.%u.%u->%u.%u.%u.%u (to client)\n", NIPQUAD(newsrcip), NIPQUAD(newdstip)); } else { /* Make the connection go to the server */ newdstip = master->tuplehash[IP_CT_DIR_REPLY].tuple.src.ip; newsrcip = master->tuplehash[IP_CT_DIR_REPLY].tuple.dst.ip; DEBUGP("h225_nat_expected: %u.%u.%u.%u->%u.%u.%u.%u (to server)\n", NIPQUAD(newsrcip), NIPQUAD(newdstip)); } port = exp_info->port; is_h225 = master_info->is_h225 == H225_PORT; UNLOCK_BH(&ip_h323_lock); if (HOOK2MANIP(hooknum) == IP_NAT_MANIP_SRC) newip = newsrcip; else newip = newdstip; DEBUGP("h225_nat_expected: IP to %u.%u.%u.%u\n", NIPQUAD(newip)); mr.rangesize = 1; /* We don't want to manip the per-protocol, just the IPs... */ mr.range[0].flags = IP_NAT_RANGE_MAP_IPS; mr.range[0].min_ip = mr.range[0].max_ip = newip; /* ... unless we're doing a MANIP_DST, in which case, make sure we map to the correct port */ if (HOOK2MANIP(hooknum) == IP_NAT_MANIP_DST) { mr.range[0].flags |= IP_NAT_RANGE_PROTO_SPECIFIED; mr.range[0].min = mr.range[0].max = ((union ip_conntrack_manip_proto) { port }); } ret = ip_nat_setup_info(ct, &mr, hooknum); if (is_h225) { DEBUGP("h225_nat_expected: H.225, setting NAT helper for %p\n", ct); /* NAT expectfn called with ip_nat_lock write-locked */ info->helper = &h245; } return ret; } static int h323_signal_address_fixup(struct ip_conntrack *ct, struct sk_buff **pskb, enum ip_conntrack_info ctinfo) { struct iphdr *iph = (*pskb)->nh.iph; struct tcphdr *tcph = (void *)iph + iph->ihl*4; char *data = (char *) tcph + tcph->doff * 4; u_int32_t tcplen = (*pskb)->len - iph->ihl*4; u_int32_t datalen = tcplen - tcph->doff*4; struct ip_ct_h225_master *info = &ct->help.ct_h225_info; u_int32_t newip; u_int16_t port; int i; MUST_BE_LOCKED(&ip_h323_lock); DEBUGP("h323_signal_address_fixup: %s %s\n", between(info->seq[IP_CT_DIR_ORIGINAL], ntohl(tcph->seq), ntohl(tcph->seq) + datalen) ? "yes" : "no", between(info->seq[IP_CT_DIR_REPLY], ntohl(tcph->seq), ntohl(tcph->seq) + datalen) ? "yes" : "no"); if (!(between(info->seq[IP_CT_DIR_ORIGINAL], ntohl(tcph->seq), ntohl(tcph->seq) + datalen) || between(info->seq[IP_CT_DIR_REPLY], ntohl(tcph->seq), ntohl(tcph->seq) + datalen))) return 1; DEBUGP("h323_signal_address_fixup: offsets %u + 6 and %u + 6 in %u\n", info->offset[IP_CT_DIR_ORIGINAL], info->offset[IP_CT_DIR_REPLY], tcplen); DUMP_TUPLE(&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple); DUMP_TUPLE(&ct->tuplehash[IP_CT_DIR_REPLY].tuple); for (i = 0; i < IP_CT_DIR_MAX; i++) { DEBUGP("h323_signal_address_fixup: %s %s\n", info->dir == IP_CT_DIR_ORIGINAL ? "original" : "reply", i == IP_CT_DIR_ORIGINAL ? "caller" : "callee"); if (!between(info->seq[i], ntohl(tcph->seq), ntohl(tcph->seq) + datalen)) continue; if (!between(info->seq[i] + 6, ntohl(tcph->seq), ntohl(tcph->seq) + datalen)) { /* Partial retransmisison. It's a cracker being funky. */ if (net_ratelimit()) { printk("H.323_NAT: partial packet %u/6 in %u/%u\n", info->seq[i], ntohl(tcph->seq), ntohl(tcph->seq) + datalen); } return 0; } /* Change address inside packet to match way we're mapping this connection. */ if (i == IP_CT_DIR_ORIGINAL) { newip = ct->tuplehash[!info->dir].tuple.dst.ip; port = ct->tuplehash[!info->dir].tuple.dst.u.tcp.port; } else { newip = ct->tuplehash[!info->dir].tuple.src.ip; port = ct->tuplehash[!info->dir].tuple.src.u.tcp.port; } DEBUGP("h323_signal_address_fixup: orig %s IP:port %u.%u.%u.%u:%u\n", i == IP_CT_DIR_ORIGINAL ? "source" : "dest ", NIPQUAD(*((u_int32_t *)(data + info->offset[i]))), ntohs(*((u_int16_t *)(data + info->offset[i] + 4)))); /* Modify the packet */ *(u_int32_t *)(data + info->offset[i]) = newip; *(u_int16_t *)(data + info->offset[i] + 4) = port; DEBUGP("h323_signal_address_fixup: new %s IP:port %u.%u.%u.%u:%u\n", i == IP_CT_DIR_ORIGINAL ? "source" : "dest ", NIPQUAD(*((u_int32_t *)(data + info->offset[i]))), ntohs(*((u_int16_t *)(data + info->offset[i] + 4)))); } /* fix checksum information */ (*pskb)->csum = csum_partial((char *)tcph + tcph->doff*4, datalen, 0); tcph->check = 0; tcph->check = tcp_v4_check(tcph, tcplen, iph->saddr, iph->daddr, csum_partial((char *)tcph, tcph->doff*4, (*pskb)->csum)); ip_send_check(iph); return 1; } static int h323_data_fixup(struct ip_ct_h225_expect *info, struct ip_conntrack *ct, struct sk_buff **pskb, enum ip_conntrack_info ctinfo, struct ip_conntrack_expect *expect) { u_int32_t newip; u_int16_t port; struct ip_conntrack_tuple newtuple; struct iphdr *iph = (*pskb)->nh.iph; struct tcphdr *tcph = (void *)iph + iph->ihl*4; char *data = (char *) tcph + tcph->doff * 4; u_int32_t tcplen = (*pskb)->len - iph->ihl*4; struct ip_ct_h225_master *master_info = &ct->help.ct_h225_info; int is_h225; MUST_BE_LOCKED(&ip_h323_lock); DEBUGP("h323_data_fixup: offset %u + 6 in %u\n", info->offset, tcplen); DUMP_TUPLE(&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple); DUMP_TUPLE(&ct->tuplehash[IP_CT_DIR_REPLY].tuple); memset(&newtuple, 0, sizeof(newtuple)); if (!between(expect->seq + 6, ntohl(tcph->seq), ntohl(tcph->seq) + tcplen - tcph->doff * 4)) { /* Partial retransmisison. It's a cracker being funky. */ if (net_ratelimit()) { printk("H.323_NAT: partial packet %u/6 in %u/%u\n", expect->seq, ntohl(tcph->seq), ntohl(tcph->seq) + tcplen - tcph->doff * 4); } return 0; } /* Change address inside packet to match way we're mapping this connection. */ if (info->dir == IP_CT_DIR_REPLY) { /* Must be where client thinks server is */ newip = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.ip; /* Expect something from client->server */ newtuple.src.ip = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.ip; newtuple.dst.ip = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.ip; } else { /* Must be where server thinks client is */ newip = ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.ip; /* Expect something from server->client */ newtuple.src.ip = ct->tuplehash[IP_CT_DIR_REPLY].tuple.src.ip; newtuple.dst.ip = ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.ip; } is_h225 = (master_info->is_h225 == H225_PORT); if (is_h225) { newtuple.dst.protonum = IPPROTO_TCP; newtuple.src.u.tcp.port = expect->tuple.src.u.tcp.port; } else { newtuple.dst.protonum = IPPROTO_UDP; newtuple.src.u.udp.port = expect->tuple.src.u.udp.port; } /* Try to get same port: if not, try to change it. */ for (port = ntohs(info->port); port != 0; port++) { if (is_h225) newtuple.dst.u.tcp.port = htons(port); else newtuple.dst.u.udp.port = htons(port); if (ip_conntrack_change_expect(expect, &newtuple) == 0) break; } if (port == 0) { DEBUGP("h323_data_fixup: no free port found!\n"); return 0; } port = htons(port); DEBUGP("h323_data_fixup: orig IP:port %u.%u.%u.%u:%u\n", NIPQUAD(*((u_int32_t *)(data + info->offset))), ntohs(*((u_int16_t *)(data + info->offset + 4)))); /* Modify the packet */ *(u_int32_t *)(data + info->offset) = newip; *(u_int16_t *)(data + info->offset + 4) = port; DEBUGP("h323_data_fixup: new IP:port %u.%u.%u.%u:%u\n", NIPQUAD(*((u_int32_t *)(data + info->offset))), ntohs(*((u_int16_t *)(data + info->offset + 4)))); /* fix checksum information */ /* FIXME: usually repeated multiple times in the case of H.245! */ (*pskb)->csum = csum_partial((char *)tcph + tcph->doff*4, tcplen - tcph->doff*4, 0); tcph->check = 0; tcph->check = tcp_v4_check(tcph, tcplen, iph->saddr, iph->daddr, csum_partial((char *)tcph, tcph->doff*4, (*pskb)->csum)); ip_send_check(iph); return 1; } static unsigned int h225_nat_help(struct ip_conntrack *ct, struct ip_conntrack_expect *exp, struct ip_nat_info *info, enum ip_conntrack_info ctinfo, unsigned int hooknum, struct sk_buff **pskb) { int dir; struct ip_ct_h225_expect *exp_info; /* Only mangle things once: original direction in POST_ROUTING and reply direction on PRE_ROUTING. */ dir = CTINFO2DIR(ctinfo); DEBUGP("nat_h323: dir %s at hook %s\n", dir == IP_CT_DIR_ORIGINAL ? "ORIG" : "REPLY", hooknum == NF_IP_POST_ROUTING ? "POSTROUTING" : hooknum == NF_IP_PRE_ROUTING ? "PREROUTING" : hooknum == NF_IP_LOCAL_OUT ? "OUTPUT" : "???"); if (!((hooknum == NF_IP_POST_ROUTING && dir == IP_CT_DIR_ORIGINAL) || (hooknum == NF_IP_PRE_ROUTING && dir == IP_CT_DIR_REPLY))) { DEBUGP("nat_h323: Not touching dir %s at hook %s\n", dir == IP_CT_DIR_ORIGINAL ? "ORIG" : "REPLY", hooknum == NF_IP_POST_ROUTING ? "POSTROUTING" : hooknum == NF_IP_PRE_ROUTING ? "PREROUTING" : hooknum == NF_IP_LOCAL_OUT ? "OUTPUT" : "???"); return NF_ACCEPT; } if (!exp) { LOCK_BH(&ip_h323_lock); if (!h323_signal_address_fixup(ct, pskb, ctinfo)) { UNLOCK_BH(&ip_h323_lock); return NF_DROP; } UNLOCK_BH(&ip_h323_lock); return NF_ACCEPT; } exp_info = &exp->help.exp_h225_info; LOCK_BH(&ip_h323_lock); if (!h323_data_fixup(exp_info, ct, pskb, ctinfo, exp)) { UNLOCK_BH(&ip_h323_lock); return NF_DROP; } UNLOCK_BH(&ip_h323_lock); return NF_ACCEPT; } static struct ip_nat_helper h225 = { { NULL, NULL }, "H.225", /* name */ IP_NAT_HELPER_F_ALWAYS, /* flags */ THIS_MODULE, /* module */ { { 0, { __constant_htons(H225_PORT) } }, /* tuple */ { 0, { 0 }, IPPROTO_TCP } }, { { 0, { 0xFFFF } }, /* mask */ { 0, { 0 }, 0xFFFF } }, h225_nat_help, /* helper */ h225_nat_expected /* expectfn */ }; static int __init init(void) { int ret; ret = ip_nat_helper_register(&h225); if (ret != 0) printk("ip_nat_h323: cannot initialize the module!\n"); return ret; } static void __exit fini(void) { ip_nat_helper_unregister(&h225); } module_init(init); module_exit(fini);