| /* SIP extension for UDP NAT alteration. |
| * |
| * (C) 2005 by Christian Hentschel <chentschel@arnet.com.ar> |
| * based on RR's ip_nat_ftp.c and other modules. |
| * |
| * 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 <linux/module.h> |
| #include <linux/skbuff.h> |
| #include <linux/ip.h> |
| #include <linux/udp.h> |
| |
| #include <linux/netfilter_ipv4.h> |
| #include <linux/netfilter_ipv4/ip_nat.h> |
| #include <linux/netfilter_ipv4/ip_nat_helper.h> |
| #include <linux/netfilter_ipv4/ip_conntrack_helper.h> |
| #include <linux/netfilter_ipv4/ip_conntrack_sip.h> |
| |
| MODULE_LICENSE("GPL"); |
| MODULE_AUTHOR("Christian Hentschel <chentschel@arnet.com.ar>"); |
| MODULE_DESCRIPTION("SIP NAT helper"); |
| |
| #if 0 |
| #define DEBUGP printk |
| #else |
| #define DEBUGP(format, args...) |
| #endif |
| |
| extern struct sip_header_nfo ct_sip_hdrs[]; |
| |
| static unsigned int mangle_sip_packet(struct sk_buff **pskb, |
| enum ip_conntrack_info ctinfo, |
| struct ip_conntrack *ct, |
| const char **dptr, size_t dlen, |
| char *buffer, int bufflen, |
| struct sip_header_nfo *hnfo) |
| { |
| unsigned int matchlen, matchoff; |
| |
| if (ct_sip_get_info(*dptr, dlen, &matchoff, &matchlen, hnfo) <= 0) |
| return 0; |
| |
| if (!ip_nat_mangle_udp_packet(pskb, ct, ctinfo, |
| matchoff, matchlen, buffer, bufflen)) |
| return 0; |
| |
| /* We need to reload this. Thanks Patrick. */ |
| *dptr = (*pskb)->data + (*pskb)->nh.iph->ihl*4 + sizeof(struct udphdr); |
| return 1; |
| } |
| |
| static unsigned int ip_nat_sip(struct sk_buff **pskb, |
| enum ip_conntrack_info ctinfo, |
| struct ip_conntrack *ct, |
| const char **dptr) |
| { |
| enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo); |
| char buffer[sizeof("nnn.nnn.nnn.nnn:nnnnn")]; |
| unsigned int bufflen, dataoff; |
| u_int32_t ip; |
| u_int16_t port; |
| |
| dataoff = (*pskb)->nh.iph->ihl*4 + sizeof(struct udphdr); |
| |
| ip = ct->tuplehash[!dir].tuple.dst.ip; |
| port = ct->tuplehash[!dir].tuple.dst.u.udp.port; |
| bufflen = sprintf(buffer, "%u.%u.%u.%u:%u", NIPQUAD(ip), ntohs(port)); |
| |
| /* short packet ? */ |
| if (((*pskb)->len - dataoff) < (sizeof("SIP/2.0") - 1)) |
| return 0; |
| |
| /* Basic rules: requests and responses. */ |
| if (memcmp(*dptr, "SIP/2.0", sizeof("SIP/2.0") - 1) == 0) { |
| const char *aux; |
| |
| if ((ctinfo) < IP_CT_IS_REPLY) { |
| mangle_sip_packet(pskb, ctinfo, ct, dptr, |
| (*pskb)->len - dataoff, |
| buffer, bufflen, |
| &ct_sip_hdrs[POS_CONTACT]); |
| return 1; |
| } |
| |
| if (!mangle_sip_packet(pskb, ctinfo, ct, dptr, |
| (*pskb)->len - dataoff, |
| buffer, bufflen, &ct_sip_hdrs[POS_VIA])) |
| return 0; |
| |
| /* This search should ignore case, but later.. */ |
| aux = ct_sip_search("CSeq:", *dptr, sizeof("CSeq:") - 1, |
| (*pskb)->len - dataoff); |
| if (!aux) |
| return 0; |
| |
| if (!ct_sip_search("REGISTER", aux, sizeof("REGISTER"), |
| ct_sip_lnlen(aux, *dptr + (*pskb)->len - dataoff))) |
| return 1; |
| |
| return mangle_sip_packet(pskb, ctinfo, ct, dptr, |
| (*pskb)->len - dataoff, |
| buffer, bufflen, |
| &ct_sip_hdrs[POS_CONTACT]); |
| } |
| if ((ctinfo) < IP_CT_IS_REPLY) { |
| if (!mangle_sip_packet(pskb, ctinfo, ct, dptr, |
| (*pskb)->len - dataoff, |
| buffer, bufflen, &ct_sip_hdrs[POS_VIA])) |
| return 0; |
| |
| /* Mangle Contact if exists only. - watch udp_nat_mangle()! */ |
| mangle_sip_packet(pskb, ctinfo, ct, dptr, (*pskb)->len - dataoff, |
| buffer, bufflen, &ct_sip_hdrs[POS_CONTACT]); |
| return 1; |
| } |
| /* This mangle requests headers. */ |
| return mangle_sip_packet(pskb, ctinfo, ct, dptr, |
| ct_sip_lnlen(*dptr, |
| *dptr + (*pskb)->len - dataoff), |
| buffer, bufflen, &ct_sip_hdrs[POS_REQ_HEADER]); |
| } |
| |
| static int mangle_content_len(struct sk_buff **pskb, |
| enum ip_conntrack_info ctinfo, |
| struct ip_conntrack *ct, |
| const char *dptr) |
| { |
| unsigned int dataoff, matchoff, matchlen; |
| char buffer[sizeof("65536")]; |
| int bufflen; |
| |
| dataoff = (*pskb)->nh.iph->ihl*4 + sizeof(struct udphdr); |
| |
| /* Get actual SDP lenght */ |
| if (ct_sip_get_info(dptr, (*pskb)->len - dataoff, &matchoff, |
| &matchlen, &ct_sip_hdrs[POS_SDP_HEADER]) > 0) { |
| |
| /* since ct_sip_get_info() give us a pointer passing 'v=' |
| we need to add 2 bytes in this count. */ |
| int c_len = (*pskb)->len - dataoff - matchoff + 2; |
| |
| /* Now, update SDP lenght */ |
| if (ct_sip_get_info(dptr, (*pskb)->len - dataoff, &matchoff, |
| &matchlen, &ct_sip_hdrs[POS_CONTENT]) > 0) { |
| |
| bufflen = sprintf(buffer, "%u", c_len); |
| |
| return ip_nat_mangle_udp_packet(pskb, ct, ctinfo, |
| matchoff, matchlen, |
| buffer, bufflen); |
| } |
| } |
| return 0; |
| } |
| |
| static unsigned int mangle_sdp(struct sk_buff **pskb, |
| enum ip_conntrack_info ctinfo, |
| struct ip_conntrack *ct, |
| u_int32_t newip, u_int16_t port, |
| const char *dptr) |
| { |
| char buffer[sizeof("nnn.nnn.nnn.nnn")]; |
| unsigned int dataoff, bufflen; |
| |
| dataoff = (*pskb)->nh.iph->ihl*4 + sizeof(struct udphdr); |
| |
| /* Mangle owner and contact info. */ |
| bufflen = sprintf(buffer, "%u.%u.%u.%u", NIPQUAD(newip)); |
| if (!mangle_sip_packet(pskb, ctinfo, ct, &dptr, (*pskb)->len - dataoff, |
| buffer, bufflen, &ct_sip_hdrs[POS_OWNER])) |
| return 0; |
| |
| if (!mangle_sip_packet(pskb, ctinfo, ct, &dptr, (*pskb)->len - dataoff, |
| buffer, bufflen, &ct_sip_hdrs[POS_CONNECTION])) |
| return 0; |
| |
| /* Mangle media port. */ |
| bufflen = sprintf(buffer, "%u", port); |
| if (!mangle_sip_packet(pskb, ctinfo, ct, &dptr, (*pskb)->len - dataoff, |
| buffer, bufflen, &ct_sip_hdrs[POS_MEDIA])) |
| return 0; |
| |
| return mangle_content_len(pskb, ctinfo, ct, dptr); |
| } |
| |
| /* So, this packet has hit the connection tracking matching code. |
| Mangle it, and change the expectation to match the new version. */ |
| static unsigned int ip_nat_sdp(struct sk_buff **pskb, |
| enum ip_conntrack_info ctinfo, |
| struct ip_conntrack_expect *exp, |
| const char *dptr) |
| { |
| struct ip_conntrack *ct = exp->master; |
| enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo); |
| u_int32_t newip; |
| u_int16_t port; |
| |
| DEBUGP("ip_nat_sdp():\n"); |
| |
| /* Connection will come from reply */ |
| newip = ct->tuplehash[!dir].tuple.dst.ip; |
| |
| exp->tuple.dst.ip = newip; |
| exp->saved_proto.udp.port = exp->tuple.dst.u.udp.port; |
| exp->dir = !dir; |
| |
| /* When you see the packet, we need to NAT it the same as the |
| this one. */ |
| exp->expectfn = ip_nat_follow_master; |
| |
| /* Try to get same port: if not, try to change it. */ |
| for (port = ntohs(exp->saved_proto.udp.port); port != 0; port++) { |
| exp->tuple.dst.u.udp.port = htons(port); |
| if (ip_conntrack_expect_related(exp) == 0) |
| break; |
| } |
| |
| if (port == 0) |
| return NF_DROP; |
| |
| if (!mangle_sdp(pskb, ctinfo, ct, newip, port, dptr)) { |
| ip_conntrack_unexpect_related(exp); |
| return NF_DROP; |
| } |
| return NF_ACCEPT; |
| } |
| |
| static void __exit fini(void) |
| { |
| ip_nat_sip_hook = NULL; |
| ip_nat_sdp_hook = NULL; |
| /* Make sure noone calls it, meanwhile. */ |
| synchronize_net(); |
| } |
| |
| static int __init init(void) |
| { |
| BUG_ON(ip_nat_sip_hook); |
| BUG_ON(ip_nat_sdp_hook); |
| ip_nat_sip_hook = ip_nat_sip; |
| ip_nat_sdp_hook = ip_nat_sdp; |
| return 0; |
| } |
| |
| module_init(init); |
| module_exit(fini); |