[NETFILTER]: nf_conntrack_sip: perform NAT after parsing
[linux-2.6] / net / ipv4 / netfilter / nf_nat_sip.c
1 /* SIP extension for UDP NAT alteration.
2  *
3  * (C) 2005 by Christian Hentschel <chentschel@arnet.com.ar>
4  * based on RR's ip_nat_ftp.c and other modules.
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License version 2 as
8  * published by the Free Software Foundation.
9  */
10
11 #include <linux/module.h>
12 #include <linux/skbuff.h>
13 #include <linux/ip.h>
14 #include <net/ip.h>
15 #include <linux/udp.h>
16
17 #include <net/netfilter/nf_nat.h>
18 #include <net/netfilter/nf_nat_helper.h>
19 #include <net/netfilter/nf_nat_rule.h>
20 #include <net/netfilter/nf_conntrack_helper.h>
21 #include <net/netfilter/nf_conntrack_expect.h>
22 #include <linux/netfilter/nf_conntrack_sip.h>
23
24 MODULE_LICENSE("GPL");
25 MODULE_AUTHOR("Christian Hentschel <chentschel@arnet.com.ar>");
26 MODULE_DESCRIPTION("SIP NAT helper");
27 MODULE_ALIAS("ip_nat_sip");
28
29
30 static unsigned int mangle_packet(struct sk_buff *skb,
31                                   const char **dptr, unsigned int *datalen,
32                                   unsigned int matchoff, unsigned int matchlen,
33                                   const char *buffer, unsigned int buflen)
34 {
35         enum ip_conntrack_info ctinfo;
36         struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
37
38         if (!nf_nat_mangle_udp_packet(skb, ct, ctinfo, matchoff, matchlen,
39                                       buffer, buflen))
40                 return 0;
41
42         /* Reload data pointer and adjust datalen value */
43         *dptr = skb->data + ip_hdrlen(skb) + sizeof(struct udphdr);
44         *datalen += buflen - matchlen;
45         return 1;
46 }
47
48 static int map_addr(struct sk_buff *skb,
49                     const char **dptr, unsigned int *datalen,
50                     unsigned int matchoff, unsigned int matchlen,
51                     union nf_inet_addr *addr, __be16 port)
52 {
53         enum ip_conntrack_info ctinfo;
54         struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
55         enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
56         char buffer[sizeof("nnn.nnn.nnn.nnn:nnnnn")];
57         unsigned int buflen;
58         __be32 newaddr;
59         __be16 newport;
60
61         if (ct->tuplehash[dir].tuple.src.u3.ip == addr->ip &&
62             ct->tuplehash[dir].tuple.src.u.udp.port == port) {
63                 newaddr = ct->tuplehash[!dir].tuple.dst.u3.ip;
64                 newport = ct->tuplehash[!dir].tuple.dst.u.udp.port;
65         } else if (ct->tuplehash[dir].tuple.dst.u3.ip == addr->ip &&
66                    ct->tuplehash[dir].tuple.dst.u.udp.port == port) {
67                 newaddr = ct->tuplehash[!dir].tuple.src.u3.ip;
68                 newport = ct->tuplehash[!dir].tuple.src.u.udp.port;
69         } else
70                 return 1;
71
72         if (newaddr == addr->ip && newport == port)
73                 return 1;
74
75         buflen = sprintf(buffer, "%u.%u.%u.%u:%u",
76                          NIPQUAD(newaddr), ntohs(newport));
77
78         return mangle_packet(skb, dptr, datalen, matchoff, matchlen,
79                              buffer, buflen);
80 }
81
82 static int map_sip_addr(struct sk_buff *skb,
83                         const char **dptr, unsigned int *datalen,
84                         enum sip_header_types type)
85 {
86         enum ip_conntrack_info ctinfo;
87         struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
88         unsigned int matchlen, matchoff;
89         union nf_inet_addr addr;
90         __be16 port;
91
92         if (ct_sip_parse_header_uri(ct, *dptr, NULL, *datalen, type, NULL,
93                                     &matchoff, &matchlen, &addr, &port) <= 0)
94                 return 1;
95         return map_addr(skb, dptr, datalen, matchoff, matchlen, &addr, port);
96 }
97
98 static unsigned int ip_nat_sip(struct sk_buff *skb,
99                                const char **dptr, unsigned int *datalen)
100 {
101         enum ip_conntrack_info ctinfo;
102         struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
103         unsigned int matchoff, matchlen;
104         union nf_inet_addr addr;
105         __be16 port;
106
107         /* Basic rules: requests and responses. */
108         if (strnicmp(*dptr, "SIP/2.0", strlen("SIP/2.0")) != 0) {
109                 if (ct_sip_parse_request(ct, *dptr, *datalen,
110                                          &matchoff, &matchlen,
111                                          &addr, &port) > 0 &&
112                     !map_addr(skb, dptr, datalen, matchoff, matchlen,
113                               &addr, port))
114                         return NF_DROP;
115         }
116
117         if (!map_sip_addr(skb, dptr, datalen, SIP_HDR_FROM) ||
118             !map_sip_addr(skb, dptr, datalen, SIP_HDR_TO) ||
119             !map_sip_addr(skb, dptr, datalen, SIP_HDR_VIA) ||
120             !map_sip_addr(skb, dptr, datalen, SIP_HDR_CONTACT))
121                 return NF_DROP;
122         return NF_ACCEPT;
123 }
124
125 static int mangle_content_len(struct sk_buff *skb,
126                               const char **dptr, unsigned int *datalen)
127 {
128         enum ip_conntrack_info ctinfo;
129         struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
130         unsigned int matchoff, matchlen;
131         char buffer[sizeof("65536")];
132         int buflen, c_len;
133
134         /* Get actual SDP length */
135         if (ct_sip_get_sdp_header(ct, *dptr, 0, *datalen,
136                                   SDP_HDR_VERSION, SDP_HDR_UNSPEC,
137                                   &matchoff, &matchlen) <= 0)
138                 return 0;
139         c_len = *datalen - matchoff + strlen("v=");
140
141         /* Now, update SDP length */
142         if (ct_sip_get_header(ct, *dptr, 0, *datalen, SIP_HDR_CONTENT_LENGTH,
143                               &matchoff, &matchlen) <= 0)
144                 return 0;
145
146         buflen = sprintf(buffer, "%u", c_len);
147         return mangle_packet(skb, dptr, datalen, matchoff, matchlen,
148                              buffer, buflen);
149 }
150
151 static unsigned mangle_sdp_packet(struct sk_buff *skb,
152                                   const char **dptr, unsigned int *datalen,
153                                   enum sdp_header_types type,
154                                   char *buffer, int buflen)
155 {
156         enum ip_conntrack_info ctinfo;
157         struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
158         unsigned int matchlen, matchoff;
159
160         if (ct_sip_get_sdp_header(ct, *dptr, 0, *datalen, type, SDP_HDR_UNSPEC,
161                                   &matchoff, &matchlen) <= 0)
162                 return 0;
163         return mangle_packet(skb, dptr, datalen, matchoff, matchlen,
164                              buffer, buflen);
165 }
166
167 static unsigned int mangle_sdp(struct sk_buff *skb,
168                                enum ip_conntrack_info ctinfo,
169                                struct nf_conn *ct,
170                                __be32 newip, u_int16_t port,
171                                const char **dptr, unsigned int *datalen)
172 {
173         char buffer[sizeof("nnn.nnn.nnn.nnn")];
174         unsigned int bufflen;
175
176         /* Mangle owner and contact info. */
177         bufflen = sprintf(buffer, "%u.%u.%u.%u", NIPQUAD(newip));
178         if (!mangle_sdp_packet(skb, dptr, datalen, SDP_HDR_OWNER_IP4,
179                                buffer, bufflen))
180                 return 0;
181
182         if (!mangle_sdp_packet(skb, dptr, datalen, SDP_HDR_CONNECTION_IP4,
183                                buffer, bufflen))
184                 return 0;
185
186         /* Mangle media port. */
187         bufflen = sprintf(buffer, "%u", port);
188         if (!mangle_sdp_packet(skb, dptr, datalen, SDP_HDR_MEDIA,
189                                buffer, bufflen))
190                 return 0;
191
192         return mangle_content_len(skb, dptr, datalen);
193 }
194
195 static void ip_nat_sdp_expect(struct nf_conn *ct,
196                               struct nf_conntrack_expect *exp)
197 {
198         struct nf_nat_range range;
199
200         /* This must be a fresh one. */
201         BUG_ON(ct->status & IPS_NAT_DONE_MASK);
202
203         /* For DST manip, map port here to where it's expected. */
204         range.flags = (IP_NAT_RANGE_MAP_IPS | IP_NAT_RANGE_PROTO_SPECIFIED);
205         range.min = range.max = exp->saved_proto;
206         range.min_ip = range.max_ip = exp->saved_ip;
207         nf_nat_setup_info(ct, &range, IP_NAT_MANIP_DST);
208
209         /* Change src to where master sends to */
210         range.flags = IP_NAT_RANGE_MAP_IPS;
211         range.min_ip = range.max_ip
212                 = ct->master->tuplehash[!exp->dir].tuple.dst.u3.ip;
213         nf_nat_setup_info(ct, &range, IP_NAT_MANIP_SRC);
214 }
215
216 /* So, this packet has hit the connection tracking matching code.
217    Mangle it, and change the expectation to match the new version. */
218 static unsigned int ip_nat_sdp(struct sk_buff *skb,
219                                const char **dptr, unsigned int *datalen,
220                                struct nf_conntrack_expect *exp)
221 {
222         enum ip_conntrack_info ctinfo;
223         struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
224         enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
225         __be32 newip;
226         u_int16_t port;
227
228         /* Connection will come from reply */
229         if (ct->tuplehash[dir].tuple.src.u3.ip ==
230             ct->tuplehash[!dir].tuple.dst.u3.ip)
231                 newip = exp->tuple.dst.u3.ip;
232         else
233                 newip = ct->tuplehash[!dir].tuple.dst.u3.ip;
234
235         exp->saved_ip = exp->tuple.dst.u3.ip;
236         exp->tuple.dst.u3.ip = newip;
237         exp->saved_proto.udp.port = exp->tuple.dst.u.udp.port;
238         exp->dir = !dir;
239
240         /* When you see the packet, we need to NAT it the same as the
241            this one. */
242         exp->expectfn = ip_nat_sdp_expect;
243
244         /* Try to get same port: if not, try to change it. */
245         for (port = ntohs(exp->saved_proto.udp.port); port != 0; port++) {
246                 exp->tuple.dst.u.udp.port = htons(port);
247                 if (nf_ct_expect_related(exp) == 0)
248                         break;
249         }
250
251         if (port == 0)
252                 return NF_DROP;
253
254         if (!mangle_sdp(skb, ctinfo, ct, newip, port, dptr, datalen)) {
255                 nf_ct_unexpect_related(exp);
256                 return NF_DROP;
257         }
258         return NF_ACCEPT;
259 }
260
261 static void __exit nf_nat_sip_fini(void)
262 {
263         rcu_assign_pointer(nf_nat_sip_hook, NULL);
264         rcu_assign_pointer(nf_nat_sdp_hook, NULL);
265         synchronize_rcu();
266 }
267
268 static int __init nf_nat_sip_init(void)
269 {
270         BUG_ON(nf_nat_sip_hook != NULL);
271         BUG_ON(nf_nat_sdp_hook != NULL);
272         rcu_assign_pointer(nf_nat_sip_hook, ip_nat_sip);
273         rcu_assign_pointer(nf_nat_sdp_hook, ip_nat_sdp);
274         return 0;
275 }
276
277 module_init(nf_nat_sip_init);
278 module_exit(nf_nat_sip_fini);