diff -urN linux-2.4.32/net/ipv4/netfilter/Config.in linux-2.4.32.new/net/ipv4/netfilter/Config.in --- linux-2.4.32/net/ipv4/netfilter/Config.in 2006-02-04 19:16:25.000000000 +0100 +++ linux-2.4.32.new/net/ipv4/netfilter/Config.in 2006-02-04 19:00:13.000000000 +0100 @@ -15,6 +15,7 @@ dep_tristate ' Connection byte counter support' CONFIG_IP_NF_MATCH_CONNBYTES $CONFIG_IP_NF_CT_ACCT $CONFIG_IP_NF_CONNTRACK $CONFIG_IP_NF_IPTABLES dep_tristate ' GRE protocol support' CONFIG_IP_NF_CT_PROTO_GRE $CONFIG_IP_NF_CONNTRACK dep_tristate ' PPTP protocol support' CONFIG_IP_NF_PPTP $CONFIG_IP_NF_CT_PROTO_GRE + dep_tristate ' SIP protocol support' CONFIG_IP_NF_SIP $CONFIG_IP_NF_CONNTRACK fi if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then @@ -103,6 +104,13 @@ define_tristate CONFIG_IP_NF_NAT_PROTO_GRE $CONFIG_IP_NF_NAT fi fi + if [ "$CONFIG_IP_NF_SIP" = "m" ]; then + define_tristate CONFIG_IP_NF_NAT_SIP m + else + if [ "$CONFIG_IP_NF_SIP" = "y" ]; then + define_tristate CONFIG_IP_NF_NAT_SIP $CONFIG_IP_NF_NAT + fi + fi if [ "$CONFIG_IP_NF_AMANDA" = "m" ]; then define_tristate CONFIG_IP_NF_NAT_AMANDA m else diff -urN linux-2.4.32/net/ipv4/netfilter/ip_conntrack_sip.c linux-2.4.32.new/net/ipv4/netfilter/ip_conntrack_sip.c --- linux-2.4.32/net/ipv4/netfilter/ip_conntrack_sip.c 1970-01-01 01:00:00.000000000 +0100 +++ linux-2.4.32.new/net/ipv4/netfilter/ip_conntrack_sip.c 2006-02-04 19:06:01.000000000 +0100 @@ -0,0 +1,312 @@ +/* + * SIP extension for IP connection tracking. + * + * Copyright (C) 2004, CyberTAN Corporation + * All Rights Reserved. + * + * THIS SOFTWARE IS OFFERED "AS IS", AND CYBERTAN GRANTS NO WARRANTIES OF ANY + * KIND, EXPRESS OR IMPLIED, BY STATUTE, COMMUNICATION OR OTHERWISE. CYBERTAN + * SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A SPECIFIC PURPOSE OR NONINFRINGEMENT CONCERNING THIS SOFTWARE. + * + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +DECLARE_LOCK(ip_sip_lock); + +struct module *ip_conntrack_sip = THIS_MODULE; + +#define MAX_PORTS 8 +static int ports[MAX_PORTS]; +static int ports_c; +#ifdef MODULE_PARM +MODULE_PARM(ports, "1-" __MODULE_STRING(MAX_PORTS) "i"); +#endif + +#if 0 + #define DEBUGP printk +#else + #define DEBUGP(format, args...) +#endif + +#define RTCP_SUPPORT + +static int set_expected_rtp(struct ip_conntrack *ct, u_int32_t dstip, + u_int16_t dstport, unsigned int conntype) +{ + struct ip_conntrack_expect expect, *exp = &expect; + struct ip_ct_sip_expect *exp_sip_info = &exp->help.exp_sip_info; + int ret = 0; + + memset(&expect, 0, sizeof(expect)); + LOCK_BH(&ip_sip_lock); + + DEBUGP("conntrack_sip: %s: [%s]: DST=%u.%u.%u.%u:%u\n", __FUNCTION__, + conntype == CONN_RTP ? "RTP" : "RTCP", NIPQUAD(dstip), dstport); + + /* exp_sip_info */ + exp_sip_info->port = dstport; + exp_sip_info->type = conntype; + exp_sip_info->nated = 0; + + /* new expectation */ + exp->tuple = ((struct ip_conntrack_tuple) + { { 0, { 0 } }, + { dstip, { htons(dstport) }, IPPROTO_UDP }}); + exp->mask = ((struct ip_conntrack_tuple) + { { 0, { 0 } }, + { 0xFFFFFFFF, { 0xFFFF }, 0xFFFF }}); + exp->expectfn = NULL; + + if ((ret=ip_conntrack_expect_related(ct, exp)) != 0) { + DEBUGP("Can't add new expectation. \n"); + } + + UNLOCK_BH(&ip_sip_lock); + + return ret; +} + +/* +static int unset_expected_rtp(struct ip_conntrack *ct, u_int32_t dstip, u_int16_t dstport) +{ + struct ip_conntrack_expect *exp; + const struct ip_conntrack_tuple tuple = { { 0, { 0 } }, + { dstip, { htons(dstport) }, IPPROTO_UDP }}; + + LOCK_BH(&ip_sip_lock); + + exp = ip_conntrack_expect_find_get(&tuple); + if (exp) { + DEBUGP("Find the expectation %p, then delete it.\n", exp); + ip_conntrack_unexpect_related(exp); + } + + UNLOCK_BH(&ip_sip_lock); + + return 0; +}*/ + +/* return: + * 0 : Not found + * 1 : Found domain name + * 2 : Found dotted quads + */ +int find_sdp_rtp_addr(const char *data, size_t dlen, + unsigned int *numoff, unsigned int *numlen, u_int32_t *addr) +{ + char *st, *p = (char *)data; + const char *limit = data + dlen; + unsigned char p1, p2, p3, p4; + + while (p < limit) { + /* find 'c=' line */ + if (strncmp(p, "\nc=",3) && strncmp(p, "\rc=",3)) { + p++; + continue; + } + p += 3; + + if (strncmp(p, "IN IP4 ",7)) + continue; + p += 7; + + /* IP address */ + st = p; + + /* FQDNs or dotted quads */ + while (isalpha(*p) || isdigit(*p) || (*p=='.') || (*p=='-')) { + p++; + if (p == limit) + return 0; + } + + *numoff = st - data; + *numlen = p - st; + + /* Convert the IP address */ + p1 = simple_strtoul(st, &st ,10); + if (*st != '.') + return 1; + p2 = simple_strtoul(st+1, &st, 10); + if (*st != '.') + return 1; + p3 = simple_strtoul(st+1, &st, 10); + if (*st != '.') + return 1; + p4 = simple_strtoul(st+1, &st, 10); + + *addr = (p1<<24) | (p2<<16) | (p3<<8) | p4; + + return 2; + } + + return 0; +} + +u_int16_t find_sdp_audio_port(const char *data, size_t dlen, + unsigned int *numoff, unsigned int *numlen) +{ + char *st, *p = (char *)data; + const char *limit = data + dlen; + u_int16_t port = 0; + + while (p < limit) { + /* find 'm=' */ + if (strncmp(p, "\nm=", 3) && strncmp(p, "\rm=", 3)) { + p++; + continue; + } + p += 3; + + /* audio stream */ + if (strncmp(p ,"audio ",6)) + continue; + p += 6; + + st = p; + port = simple_strtoul(p, &p, 10); + + *numoff = st - data; + *numlen = p - st; + + return port; + } + + return 0; +} + +static int help(const struct iphdr *iph, size_t len, + struct ip_conntrack *ct, + enum ip_conntrack_info ctinfo) +{ + int dir = CTINFO2DIR(ctinfo); + unsigned int matchlen, matchoff; + + u_int32_t ipaddr=0; + u_int16_t port = 0; + + int found = 0; + struct udphdr *udph = (void *)iph + iph->ihl * 4; + const char *data = (const char *)udph + 8; + unsigned int udplen = len - iph->ihl * 4; + unsigned int datalen = udplen - 8; + struct ip_ct_sip_master *ct_sip_info = &ct->help.ct_sip_info; + + DEBUGP("\nconntrack_sip: help(): DIR=%d, conntrackinfo=%u\n", dir, ctinfo); + DEBUGP("conntrack_sip: %u.%u.%u.%u:%u -> %u.%u.%u.%u:%u\n", + NIPQUAD(ct->tuplehash[dir].tuple.src.ip), + ntohs(ct->tuplehash[dir].tuple.src.u.udp.port), + NIPQUAD(ct->tuplehash[dir].tuple.dst.ip), + ntohs(ct->tuplehash[dir].tuple.dst.u.udp.port) ); + + /* Reset for a new incoming packet */ + ct_sip_info->mangled = 0; + + /* keep the connection alive */ + ip_ct_refresh_acct(ct, ctinfo, iph, (SIP_EXPIRES * HZ)); + + /* Don't need to set the expectation for upstream direction */ + if (dir == IP_CT_DIR_REPLY) + return NF_ACCEPT; + + /* Need to set the expected connection for further incoming RTP stream */ + if (strncmp(data, "INVITE", 6) != 0 && strncmp(data, "SIP/2.0 200", 11) != 0) { + DEBUGP("conntrack_sip: Not interesting packet.\n"); + return NF_ACCEPT; + } + + /* Find RTP address */ + found = find_sdp_rtp_addr(data, datalen, &matchoff, &matchlen, &ipaddr); + if (!found) + return NF_ACCEPT; + + DEBUGP("conntrack_sip: 'IN IP4' is %s.\n", (found == 1) ? "FQDNs" : "dotted quads"); + + /* If it's a null address, then the call is on hold */ + if (found == 2 && ipaddr == 0) { + DEBUGP("conntrack_sip: Null address is found.\n"); + return NF_ACCEPT; + } + + /* Find audio port, and we don't like the well-known ports, + * which is less than 1024 */ + port = find_sdp_audio_port(data, datalen, &matchoff, &matchlen); + if (port < 1024) + return NF_ACCEPT; + + DEBUGP("conntrack_sip: audio port=%d.\n", port); + + ipaddr = ct->tuplehash[dir].tuple.src.ip; + ct_sip_info->rtpport = port; + + /* RFC1889 - RTP uses an even port number and the corresponding RTCP + * stream uses the next higher (odd) port number. */ + if (set_expected_rtp(ct, ipaddr, port, CONN_RTP) == 0) { +#ifdef RTCP_SUPPORT + set_expected_rtp(ct, ipaddr, port + 1, CONN_RTCP); +#endif + } + + return NF_ACCEPT; +} + +static struct ip_conntrack_helper sip[MAX_PORTS]; + + +/* Not __exit: called from init() */ +static void fini(void) +{ + int i; + for (i = 0; (i < MAX_PORTS) && ports[i]; i++) { + DEBUGP("ip_ct_sip: unregistering helper for port %d\n", + ports[i]); + ip_conntrack_helper_unregister(&sip[i]); + } +} + +static int __init init(void) +{ + int i, ret; + + if (ports[0] == 0) + ports[0] = SIP_PORT; + + for (i = 0; (i < MAX_PORTS) && ports[i]; i++) { + memset(&sip[i], 0, sizeof(struct ip_conntrack_helper)); + sip[i].tuple.dst.u.udp.port = htons(ports[i]); + sip[i].tuple.dst.protonum = IPPROTO_UDP; + sip[i].mask.dst.u.udp.port = 0xF0FF; + sip[i].mask.dst.protonum = 0xFFFF; + sip[i].help = help; + sip[i].timeout = RTP_TIMEOUT; + DEBUGP("ip_ct_sip: registering helper for port %d\n", + ports[i]); + ret = ip_conntrack_helper_register(&sip[i]); + + if (ret) { + fini(); + return ret; + } + ports_c++; + } + return 0; +} + + +EXPORT_SYMBOL(ip_sip_lock); +EXPORT_SYMBOL(ip_conntrack_sip); + +module_init(init); +module_exit(fini); diff -urN linux-2.4.32/net/ipv4/netfilter/ip_nat_sip.c linux-2.4.32.new/net/ipv4/netfilter/ip_nat_sip.c --- linux-2.4.32/net/ipv4/netfilter/ip_nat_sip.c 1970-01-01 01:00:00.000000000 +0100 +++ linux-2.4.32.new/net/ipv4/netfilter/ip_nat_sip.c 2006-02-04 19:00:13.000000000 +0100 @@ -0,0 +1,800 @@ +/* + * SIP extension for TCP NAT alteration. + * + * Copyright (C) 2004, CyberTAN Corporation + * All Rights Reserved. + * + * THIS SOFTWARE IS OFFERED "AS IS", AND CYBERTAN GRANTS NO WARRANTIES OF ANY + * KIND, EXPRESS OR IMPLIED, BY STATUTE, COMMUNICATION OR OTHERWISE. CYBERTAN + * SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A SPECIFIC PURPOSE OR NONINFRINGEMENT CONCERNING THIS SOFTWARE. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if 0 + #define DEBUGP printk +#else + #define DEBUGP(format, args...) +#endif + +#define MAX_PORTS 8 +static int ports[MAX_PORTS]; +static int ports_c = 0; + +#ifdef MODULE_PARM +MODULE_PARM(ports, "1-" __MODULE_STRING(MAX_PORTS) "i"); +#endif + +DECLARE_LOCK_EXTERN(ip_sip_lock); + +#define RTCP_SUPPORT + +/* down(stream): caller -> callee + up(stream): caller <- callee */ + + +/* Return 1 for match, 0 for accept, -1 for partial. */ +static int find_pattern(const char *data, size_t dlen, + const char *pattern, size_t plen, + char skip, char term, + unsigned int *numoff, + unsigned int *numlen + ) +{ + size_t i, j, k; + +// DEBUGP("find_pattern `%s': dlen = %u\n", pattern, dlen); + if (dlen == 0) + return 0; + + if (dlen <= plen) { + /* Short packet: try for partial? */ + if (strnicmp(data, pattern, dlen) == 0) + return -1; + else return 0; + } + + for(i=0; i<= (dlen - plen); i++){ + if( memcmp(data + i, pattern, plen ) != 0 ) continue; + + /* patten match !! */ + *numoff=i + plen; + for (j=*numoff, k=0; data[j] != term; j++, k++) + if( j > dlen ) return -1 ; /* no terminal char */ + + *numlen = k; + return 1; + } + + return 0; +} + +static unsigned int +sip_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; + struct ip_ct_sip_expect *exp_sip_info; + struct ip_conntrack *master = master_ct(ct); + + IP_NF_ASSERT(info); + IP_NF_ASSERT(master); + IP_NF_ASSERT(!(info->initialized & (1<master->help.exp_sip_info; + + LOCK_BH(&ip_sip_lock); + + /* Outer machine IP */ + newsrcip = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.ip; + /* Client (virtual) IP under NAT */ + newdstip = master->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.ip; + + UNLOCK_BH(&ip_sip_lock); + + if (HOOK2MANIP(hooknum) == IP_NAT_MANIP_SRC) + newip = newsrcip; + else + newip = newdstip; + + DEBUGP("sip_nat_expected: IP to %u.%u.%u.%u:%u\n", NIPQUAD(newip), + htons(exp_sip_info->port)); + + 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) + { htons(exp_sip_info->port) }); + } + + return ip_nat_setup_info(ct, &mr, hooknum); +} + +static int _find_sip_via_addrport(const char *data, size_t dlen, + unsigned int *numoff, unsigned int *numlen) +{ + const char *addr, *p = data; + const char *limit = data + dlen; + + while (p < limit) { + /* Find the topmost via tag */ + if (strnicmp(p, "\nvia:",5) && strnicmp(p, "\nv:",3) && + strnicmp(p, "\rvia:",5) && strnicmp(p, "\rv:",3)) { + p++; + continue; + } + + /* Look for UDP */ + while (*p!='U' && *p!='u') { + if (p == limit) + return 0; + p++; + } + p+= 3; + + if (p >= limit) + return 0; + + /* Skip a space */ + while (*p == ' '){ + if (p == limit) + return 0; + p++; + } + + /* IP address */ + addr = p; + + /* FQDNs or dotted quads */ + while (isalpha(*p) || isdigit(*p) || (*p=='.') || (*p=='-')) { + p++; + if (p == limit) + return 0; + } + + /* If there is a port number, skip it */ + if (*p == ':') { + p++; + if (p == limit) + return 0; + + while (isdigit(*p)) { + p++; + if (p == limit) + return 0; + } + } + + *numoff = addr - data; + *numlen = p - addr; + + return 1; + } + + return 0; /* Not found */ +} + +static int _mangle_sip_via(struct ip_conntrack *ct, enum ip_conntrack_info ctinfo, + struct sk_buff **pskb, u_int32_t newip, u_int16_t newport, + unsigned int numoff, unsigned int numlen) +{ + int buflen; + char buffer[sizeof("nnn.nnn.nnn.nnn:nnnnn")]; + + sprintf(buffer, "%u.%u.%u.%u:%u", NIPQUAD(newip), newport); + buflen = strlen(buffer); + + MUST_BE_LOCKED(&ip_sip_lock); + + if (!ip_nat_mangle_udp_packet(pskb, ct, ctinfo, numoff, + numlen, buffer, buflen) ) + return 0; + + DEBUGP("(SIP) Via is changed to %s.\n", buffer); + + return buflen - numlen; +} + +static int mangle_sip_via(struct ip_conntrack *ct, enum ip_conntrack_info ctinfo, + struct sk_buff **pskb, u_int32_t newip, u_int16_t newport) +{ + struct iphdr *iph = (*pskb)->nh.iph; + struct udphdr *udph = (void *)iph + iph->ihl * 4; + const char *data = (const char *)udph + 8; + unsigned int udplen = ntohs(iph->tot_len) - (iph->ihl * 4); + unsigned int datalen = udplen - 8; + unsigned int matchoff, matchlen; + + /* Find the topmost via tag */ + _find_sip_via_addrport(data , datalen, &matchoff, &matchlen); + return _mangle_sip_via(ct, ctinfo, pskb, newip, newport, + matchoff, matchlen); +} + +static int mangle_sip_contact(struct ip_conntrack *ct, enum ip_conntrack_info ctinfo, + struct sk_buff **pskb, u_int32_t newip, u_int16_t newport) +{ + struct iphdr *iph = (*pskb)->nh.iph; + struct udphdr *udph = (void *)iph + iph->ihl * 4; + const char *data = (const char *)udph + 8; + unsigned int udplen = ntohs(iph->tot_len) - (iph->ihl * 4); + unsigned int datalen = udplen - 8; + + int buflen, diff_len = 0; + char buffer[sizeof("nnn.nnn.nnn.nnn:nnnnn")]; + const char *uri, *addr, *p = data; + const char *limit = data + datalen; + + + while (p < limit) { + if (strnicmp(p, "\ncontact:", 9) && strnicmp(p, "\nm:", 3) && + strnicmp(p, "\rcontact:", 9) && strnicmp(p, "\rm:", 3)) { + p++; + continue; + } + + while (strnicmp(p, "sip:", 4)) { + if (p == limit) + return 0; + p++; + } + p += 4; + + /* If there is user info in the contact */ + uri = p; + + while (*p!='@' && *p!='>' && *p!=';' && *p!='\n' && *p!='\r' && *p!='?' && *p!=',') { + if (p == limit) + return 0; + p++; + } + + if (*p=='@') + p++; + else + p = uri; /* back to previous URI pointer */ + + /* IP address */ + addr = p; + + /* FQDNs or dotted quads */ + while (isalpha(*p) || isdigit(*p) || (*p=='.') || (*p=='-')) { + p++; + if (p == limit) + return 0; + } + + /* If there is a port number, skip it */ + if (*p==':') { + p++; + while (isdigit(*p)) + p++; + } + + sprintf(buffer, "%u.%u.%u.%u:%u", NIPQUAD(newip), newport); + buflen = strlen(buffer); + + MUST_BE_LOCKED(&ip_sip_lock); + + if (!ip_nat_mangle_udp_packet(pskb, ct, ctinfo, addr - data, + p - addr, buffer, buflen) ) + return 0; + + diff_len = buflen - (p - addr); + DEBUGP("(SIP) Contact is changed to %s.\n", buffer); + break; + } + + return diff_len; +} + +static int _find_sip_content_length_size(const char *data, size_t dlen, + unsigned int *numoff, unsigned int *numlen) +{ + char *st, *p = (char *)data; + const char *limit = data + dlen; + int size = 0; + + while (p < limit) { + if (strnicmp(p, "\nContent-Length:", 16) && strnicmp(p, "\nl:", 3) && + strnicmp(p, "\rContent-Length:", 16) && strnicmp(p, "\rm:", 3)) { + p++; + continue; + } + + /* Go through the string above */ + while (*p != ':') { + if (p == limit) + return 0; + p++; + } + p++; + + while (*p == ' ') { + if (p == limit) + return 0; + p++; + } + + st = p; + size = simple_strtoul(p, &p, 10); + + *numoff = st - data; + *numlen = p - st; + + return size; + } + + return 0; +} + +static int mangle_sip_content_length(struct ip_conntrack *ct, enum ip_conntrack_info ctinfo, + struct sk_buff **pskb, int diff_len) +{ + struct iphdr *iph = (*pskb)->nh.iph; + struct udphdr *udph = (void *)iph + iph->ihl * 4; + const char *data = (const char *)udph + 8; + unsigned int udplen = ntohs(iph->tot_len) - (iph->ihl * 4); + unsigned int datalen = udplen - 8; + + unsigned int matchlen, matchoff; + int size, buflen; + char buffer[sizeof("nnnnn")]; + + /* original legth */ + size = _find_sip_content_length_size(data, datalen, &matchoff, &matchlen); + + /* new legth */ + sprintf(buffer, "%u", size + diff_len); + buflen = strlen(buffer); + + if (!ip_nat_mangle_udp_packet(pskb, ct, ctinfo, matchoff, + matchlen, buffer, buflen) ) + return 0; + + DEBUGP("(SDP) Content-Length is changed %d->%s.\n", size, buffer); + return buflen - matchlen; + +} + +static int mangle_sip_sdp_content(struct ip_conntrack *ct, enum ip_conntrack_info ctinfo, + struct sk_buff **pskb, u_int32_t newip, u_int16_t newport) +{ + struct iphdr *iph = (*pskb)->nh.iph; + struct udphdr *udph = (void *)iph + iph->ihl * 4; + const char *data = (const char *)udph + 8; + unsigned int udplen = ntohs(iph->tot_len) - (iph->ihl * 4); + unsigned int datalen = udplen - 8; + + unsigned int matchlen, matchoff; + int found, buflen, diff_len = 0; + char buffer[sizeof("nnn.nnn.nnn.nnn")]; + char *addr, *p; + char *limit = (char *)data + datalen; + u_int32_t ipaddr = 0; + u_int16_t getport; + int dir; + + /* Find RTP address */ + found = find_sdp_rtp_addr(data, datalen, &matchoff, &matchlen, &ipaddr); + if (found) { + /* If it's a null address, then the call is on hold */ + if (found == 2 && ipaddr == 0) + return 0; + + sprintf(buffer, "%u.%u.%u.%u", NIPQUAD(newip)); + buflen = strlen(buffer); + + MUST_BE_LOCKED(&ip_sip_lock); + + if (!ip_nat_mangle_udp_packet(pskb, ct, ctinfo, matchoff, + matchlen, buffer, buflen) ) + return 0; + + diff_len += (buflen - matchlen); + DEBUGP("(SDP) RTP address is changed to %s.\n", buffer); + } + + /* Find audio port */ + getport = find_sdp_audio_port(data, datalen, &matchoff, &matchlen); + if (getport != newport) { + sprintf(buffer, "%d", newport); + buflen = strlen(buffer); + + MUST_BE_LOCKED(&ip_sip_lock); + + if (!ip_nat_mangle_udp_packet(pskb, ct, ctinfo, matchoff, + matchlen, buffer, buflen) ) + return 0; + + diff_len += (buflen - matchlen); + DEBUGP("(SDP) audio port is changed to %d.\n", newport); + } + + dir = CTINFO2DIR(ctinfo); + if (dir == IP_CT_DIR_ORIGINAL) + return diff_len; + + /* Find Session ID address */ + found = find_pattern(data, datalen, " IN IP4 ", 8, ' ', '\r', + &matchoff, &matchlen); + if (found) { + p = addr = (char *)data + matchoff; + + /* FQDNs or dotted quads */ + while (isalpha(*p) || isdigit(*p) || (*p=='.') || (*p=='-')) { + p++; + if (p == limit) + return 0; + } + + sprintf(buffer, "%u.%u.%u.%u", NIPQUAD(newip)); + buflen = strlen(buffer); + + MUST_BE_LOCKED(&ip_sip_lock); + + if (!ip_nat_mangle_udp_packet(pskb, ct, ctinfo, addr - data, + p - addr, buffer, buflen) ) + return 0; + + diff_len += (buflen - (p - addr)); + DEBUGP("(SDP) Session ID is changed to %s.\n", buffer); + } + + return diff_len; +} + +static int mangle_sip_packet(struct ip_conntrack *ct, enum ip_conntrack_info ctinfo, + struct sk_buff **pskb, u_int32_t newip) +{ + struct iphdr *iph = (*pskb)->nh.iph; + struct udphdr *udph = (void *)iph + iph->ihl * 4; + const char *data = (const char *)udph + 8; + int diff_len = 0; + struct ip_ct_sip_master *ct_sip_info = &ct->help.ct_sip_info; + u_int16_t natport = ntohs(ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.u.udp.port); + + DEBUGP("nat_sip: %s:(%d)\n", __FUNCTION__, __LINE__); + + ct_sip_info->mangled = 1; + + /* Changes the via, if this is a request */ + if (strnicmp(data,"SIP/2.0",7) != 0) { + mangle_sip_via(ct, ctinfo, pskb, newip, natport); + } + + mangle_sip_contact(ct, ctinfo, pskb, newip, natport); + + if ((diff_len = mangle_sip_sdp_content(ct, ctinfo, pskb, newip, ct_sip_info->rtpport)) != 0) + mangle_sip_content_length(ct, ctinfo, pskb, diff_len); + + return 1; +} + +static struct ip_conntrack_expect * +expect_find(struct ip_conntrack *ct, u_int32_t dstip, u_int16_t dstport) +{ + const struct ip_conntrack_tuple tuple = { { 0, { 0 } }, + { dstip, { htons(dstport) }, IPPROTO_UDP }}; + + return ip_conntrack_expect_find_get(&tuple); + +} + +static int sip_out_data_fixup(struct ip_conntrack *ct, + struct sk_buff **pskb, + enum ip_conntrack_info ctinfo, + struct ip_conntrack_expect *expect) +{ + struct ip_conntrack_tuple newtuple; + struct ip_ct_sip_master *ct_sip_info = &ct->help.ct_sip_info; + struct ip_ct_sip_expect *exp_sip_info; + u_int32_t wanip = ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.ip; // NAT wan ip + u_int16_t port = 0; +#ifdef RTCP_SUPPORT + struct ip_conntrack_expect *rtcp_exp; +#endif + + MUST_BE_LOCKED(&ip_sip_lock); + + if (expect) { + DEBUGP("nat_sip: %s: There is a exp %p.\n", __FUNCTION__, expect); + + exp_sip_info = &expect->help.exp_sip_info; + if (exp_sip_info->nated) { + DEBUGP("nat_sip: %s: The exp %p had been changed.\n", __FUNCTION__, expect); + goto mangle; + } + + /* RTP expect */ + if (exp_sip_info->type == CONN_RTP) { + port = ntohs(expect->tuple.dst.u.udp.port); +#ifdef RTCP_SUPPORT + rtcp_exp = expect_find(ct, expect->tuple.dst.ip, port + 1); +#endif + /* RFC1889 - If an application is supplied with an odd number + * for use as the RTP port, it should replace this number with + * the next lower (even) number. */ + if (port % 2) + port++; + + /* fullfill newtuple */ + newtuple.dst.ip = wanip; + newtuple.src.ip = expect->tuple.src.ip; + newtuple.dst.protonum = expect->tuple.dst.protonum; + + /* Try to get same port: if not, try to change it. */ + for (; port != 0; port += 2) { + newtuple.dst.u.udp.port = htons(port); + if (ip_conntrack_change_expect(expect, &newtuple) == 0) { +#ifdef RTCP_SUPPORT + /* Change RTCP */ + if (rtcp_exp) { + DEBUGP("nat_sip: %s: RTCP exp %p found.\n", + __FUNCTION__, rtcp_exp); + newtuple.dst.u.udp.port = htons(port + 1); + if (ip_conntrack_change_expect(rtcp_exp, &newtuple) != 0) { + DEBUGP("nat_sip: %s: Can't change RTCP exp %p.\n", + __FUNCTION__, rtcp_exp); + continue; + } + rtcp_exp->help.exp_sip_info.nated = 1; + } +#endif + break; + } + } + if (port == 0) + return 0; + + exp_sip_info->nated = 1; + ct_sip_info->rtpport = port; + DEBUGP("nat_sip: %s: RTP exp %p, masq port=%d\n", __FUNCTION__, expect, port); + } +#ifdef RTCP_SUPPORT + else { + /* We ignore the RTCP expect, and will adjust it later + * during RTP expect */ + DEBUGP("nat_sip: %s: RTCP exp %p, by-pass.\n", __FUNCTION__, expect); + return 1; + } +#endif + } + +mangle: + /* Change address inside packet to match way we're mapping + this connection. */ + if (!ct_sip_info->mangled) + if (!mangle_sip_packet(ct, ctinfo, pskb, wanip)) + return 0; + + return 1; +} + +static int sip_in_data_fixup(struct ip_conntrack *ct, + struct sk_buff **pskb, + enum ip_conntrack_info ctinfo, + struct ip_conntrack_expect *expect) +{ + struct iphdr *iph = (*pskb)->nh.iph; + struct udphdr *udph = (void *)iph + iph->ihl * 4; + const char *data = (const char *)udph + 8; + unsigned int udplen = ntohs(iph->tot_len) - (iph->ihl * 4); + unsigned int datalen = udplen - 8; + struct ip_ct_sip_master *ct_sip_info = &ct->help.ct_sip_info; + u_int32_t wanip = ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.ip; // NAT wan ip + unsigned int matchlen, matchoff; + int found, diff_len = 0; + u_int32_t ipaddr = 0, vip = 0; + u_int16_t port = 0, vport = 0, aport = 0; + struct ip_conntrack_expect *exp; +#ifdef RTCP_SUPPORT + struct ip_conntrack_expect *rtcpexp; +#endif + + if (expect) + DEBUGP("nat_sip: %s: There is a exp %p.\n", __FUNCTION__, expect); + + /* Prevent from mangling the packet or expect twice */ + if (ct_sip_info->mangled) + return 1; + + ct_sip_info->mangled = 1; + + if (strnicmp(data, "SIP/2.0 200", 11) == 0) { + /* Find CSeq field */ + found = find_pattern(data, datalen, "CSeq: ", 6, ' ', '\r', + &matchoff, &matchlen); + if (found) { + char *p = (char *)data + matchoff; + + simple_strtoul(p, &p, 10); + if (strnicmp(p, " REGISTER", 9) == 0) { + DEBUGP("nat_sip: 200 OK - REGISTER\n"); + vip = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.ip; + vport = ntohs(ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.u.udp.port); + mangle_sip_via(ct, ctinfo, pskb, vip, vport); + mangle_sip_contact(ct, ctinfo, pskb, vip, vport); + return 1; + } + } + } + + /* Only interesting the SDP content. */ + if (strnicmp(data, "INVITE", 6) != 0 && strnicmp(data, "SIP/2.0 200", 11) != 0) + return 1; + + /* Find RTP address */ + found = find_sdp_rtp_addr(data, datalen, &matchoff, &matchlen, &ipaddr); + + DEBUGP("nat_sip: sdp address found = %d, ipaddr = %u.\n", found, ipaddr); + if (found < 2) + return 1; + + /* Is it a null address or our WAN address? */ + if (ipaddr == 0 || (htonl(ipaddr) != wanip)) + return 1; + + DEBUGP("nat_sip: %s: This is a lookback RTP connection.\n", __FUNCTION__); + + /* Find audio port, and we don't like the well-known ports, + * which is less than 1024 */ + port = find_sdp_audio_port(data, datalen, &matchoff, &matchlen); + if (port < 1024) + return 0; + + exp = expect_find(ct, wanip, port); + if (exp) { + DEBUGP("nat_sip: %s: Found exp %p, tuple.dst=%u.%u.%u.%u:%u.\n", + __FUNCTION__, exp, NIPQUAD(ipaddr), port); + + /* Restore masq-ed SDP */ + vip = exp->expectant->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.ip; + aport = exp->help.exp_sip_info.port; + if ((diff_len = mangle_sip_sdp_content(ct, ctinfo, pskb, vip, aport)) != 0) + mangle_sip_content_length(ct, ctinfo, pskb, diff_len); + + /* Unset RTP, RTCP expect, respectively */ + ip_conntrack_unexpect_related(exp); +#ifdef RTCP_SUPPORT + rtcpexp = expect_find(ct, wanip, port + 1); + if (rtcpexp) + ip_conntrack_unexpect_related(rtcpexp); +#endif + } + + return 1; +} + +static unsigned int 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 iphdr *iph = (*pskb)->nh.iph; + struct udphdr *udph = (void *)iph + iph->ihl * 4; + const char *data = (const char *)udph + 8; + + /* Only mangle things once: original direction in POST_ROUTING + and reply direction on PRE_ROUTING. */ + dir = CTINFO2DIR(ctinfo); + if (!((hooknum == NF_IP_POST_ROUTING && dir == IP_CT_DIR_ORIGINAL) + || (hooknum == NF_IP_PRE_ROUTING && dir == IP_CT_DIR_REPLY))) { + DEBUGP("nat_sip: 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 (strnicmp(data, "REGISTER" , 8) == 0) + DEBUGP("nat_sip: REGISTER\n"); + else if (strnicmp(data, "INVITE" , 6) == 0) + DEBUGP("nat_sip: INVITE\n"); + else if (strnicmp(data, "ACK" , 3) == 0) + DEBUGP("nat_sip: ACK\n"); + else if (strnicmp(data, "BYE", 3) == 0) + DEBUGP("nat_sip: BYE\n"); + else if (strnicmp(data, "SIP/2.0 200", 11) == 0) + DEBUGP("nat_sip: 200 OK\n"); + else if (strnicmp(data, "SIP/2.0 100", 11) == 0) + DEBUGP("nat_sip: 100 Trying\n"); + else if (strnicmp(data, "SIP/2.0 180", 11) == 0) + DEBUGP("nat_sip: 180 Ringing\n"); + + LOCK_BH(&ip_sip_lock); + + if (dir == IP_CT_DIR_ORIGINAL) + sip_out_data_fixup(ct, pskb, ctinfo, exp); + else + sip_in_data_fixup(ct, pskb, ctinfo, exp); + + UNLOCK_BH(&ip_sip_lock); + + return NF_ACCEPT; +} + +static struct ip_nat_helper sip[MAX_PORTS]; +static char sip_names[MAX_PORTS][6]; + +/* Not __exit: called from init() */ +static void fini(void) +{ + int i; + + for (i = 0; (i < MAX_PORTS) && ports[i]; i++) { + DEBUGP("ip_nat_sip: unregistering port %d\n", ports[i]); + ip_nat_helper_unregister(&sip[i]); + } +} + +static int __init init(void) +{ + int i, ret=0; + char *tmpname; + + if (ports[0] == 0) + ports[0] = SIP_PORT; + + for (i = 0; (i < MAX_PORTS) && ports[i]; i++) { + + memset(&sip[i], 0, sizeof(struct ip_nat_helper)); + + sip[i].tuple.dst.u.udp.port = htons(ports[i]); + sip[i].tuple.dst.protonum = IPPROTO_UDP; + sip[i].mask.dst.u.udp.port = 0xF0FF; + sip[i].mask.dst.protonum = 0xFFFF; + sip[i].help = nat_help; + sip[i].expect = sip_nat_expected; + sip[i].flags = IP_NAT_HELPER_F_ALWAYS; + + tmpname = &sip_names[i][0]; + sprintf(tmpname, "sip%2.2d", i); + sip[i].name = tmpname; + + DEBUGP("ip_nat_sip: Trying to register for port %d\n", + ports[i]); + ret = ip_nat_helper_register(&sip[i]); + + if (ret) { + printk("ip_nat_sip: error registering " + "helper for port %d\n", ports[i]); + fini(); + return ret; + } + ports_c++; + } + + return ret; +} + +module_init(init); +module_exit(fini); diff -urN linux-2.4.32/net/ipv4/netfilter/Makefile linux-2.4.32.new/net/ipv4/netfilter/Makefile --- linux-2.4.32/net/ipv4/netfilter/Makefile 2006-02-04 19:16:25.000000000 +0100 +++ linux-2.4.32.new/net/ipv4/netfilter/Makefile 2006-02-04 19:00:13.000000000 +0100 @@ -53,6 +53,10 @@ ifdef CONFIG_IP_NF_NAT_PPTP export-objs += ip_conntrack_pptp.o endif +obj-$(CONFIG_IP_NF_SIP) += ip_conntrack_sip.o +ifdef CONFIG_IP_NF_NAT_SIP + export-objs += ip_conntrack_sip.o +endif # NAT helpers @@ -62,6 +66,7 @@ obj-$(CONFIG_IP_NF_NAT_IRC) += ip_nat_irc.o obj-$(CONFIG_IP_NF_NAT_PROTO_GRE) += ip_nat_proto_gre.o obj-$(CONFIG_IP_NF_NAT_PPTP) += ip_nat_pptp.o +obj-$(CONFIG_IP_NF_NAT_SIP) += ip_nat_sip.o # generic IP tables obj-$(CONFIG_IP_NF_IPTABLES) += ip_tables.o diff -urN linux-2.4.32/include/linux/netfilter_ipv4/ip_conntrack.h linux-2.4.32.new/include/linux/netfilter_ipv4/ip_conntrack.h --- linux-2.4.32/include/linux/netfilter_ipv4/ip_conntrack.h 2006-02-04 19:16:25.000000000 +0100 +++ linux-2.4.32.new/include/linux/netfilter_ipv4/ip_conntrack.h 2006-02-04 19:00:13.000000000 +0100 @@ -71,6 +71,7 @@ #include #include #include +#include /* per expectation: application helper private data */ union ip_conntrack_expect_help { @@ -79,6 +80,7 @@ struct ip_ct_ftp_expect exp_ftp_info; struct ip_ct_irc_expect exp_irc_info; struct ip_ct_pptp_expect exp_pptp_info; + struct ip_ct_sip_expect exp_sip_info; #ifdef CONFIG_IP_NF_NAT_NEEDED union { @@ -93,6 +95,7 @@ struct ip_ct_ftp_master ct_ftp_info; struct ip_ct_irc_master ct_irc_info; struct ip_ct_pptp_master ct_pptp_info; + struct ip_ct_sip_master ct_sip_info; }; #ifdef CONFIG_IP_NF_NAT_NEEDED diff -urN linux-2.4.32/include/linux/netfilter_ipv4/ip_conntrack_sip.h linux-2.4.32.new/include/linux/netfilter_ipv4/ip_conntrack_sip.h --- linux-2.4.32/include/linux/netfilter_ipv4/ip_conntrack_sip.h 1970-01-01 01:00:00.000000000 +0100 +++ linux-2.4.32.new/include/linux/netfilter_ipv4/ip_conntrack_sip.h 2006-02-04 19:00:13.000000000 +0100 @@ -0,0 +1,56 @@ +#ifndef _IP_CONNTRACK_SIP_H +#define _IP_CONNTRACK_SIP_H +/* SIP tracking. */ + +#ifdef __KERNEL__ + +#include + +/* Protects sip part of conntracks */ +DECLARE_LOCK_EXTERN(ip_sip_lock); + +#define SIP_PORT 5060 /* UDP */ +#define SIP_EXPIRES 3600 /* seconds */ +#define RTP_TIMEOUT 180 /* seconds */ + +#endif /* __KERNEL__ */ + +/* SIP Request */ +#define SIP_INVITE 0x01 +#define SIP_ACK 0x02 +#define SIP_BYE 0x04 +/* SIP Response */ +#define SIP_100 0x10 +#define SIP_200 0x20 +#define SIP_200_BYE 0x40 +/* SIP session direction */ +#define SIP_OUTGOING 0 +#define SIP_INCOMING 1 + +enum ip_ct_conntype +{ + CONN_SIP, + CONN_RTP, + CONN_RTCP, +}; + +/* This structure is per expected connection */ +struct ip_ct_sip_expect +{ + u_int16_t port; /* TCP port that was to be used */ + + enum ip_ct_conntype type; + int nated; +}; + +/* This structure exists only once per master */ +struct ip_ct_sip_master { + int mangled; + u_int16_t rtpport; +}; + +extern u_int16_t find_sdp_audio_port(const char *data, size_t dlen, + unsigned int *numoff, unsigned int *numlen); +extern int find_sdp_rtp_addr(const char *data, size_t dlen, + unsigned int *numoff, unsigned int *numlen, u_int32_t *addr); +#endif /* _IP_CONNTRACK_SIP_H */