]>
Commit | Line | Data |
---|---|---|
9fafcd7b PM |
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> | |
c9bdd4b5 | 14 | #include <net/ip.h> |
9fafcd7b PM |
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 | ||
9fafcd7b | 29 | |
2a6cfb22 PM |
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 | ||
ac367740 PM |
48 | static int map_addr(struct sk_buff *skb, |
49 | const char **dptr, unsigned int *datalen, | |
50 | unsigned int matchoff, unsigned int matchlen, | |
624f8b7b | 51 | union nf_inet_addr *addr, __be16 port) |
9fafcd7b | 52 | { |
212440a7 | 53 | enum ip_conntrack_info ctinfo; |
624f8b7b | 54 | struct nf_conn *ct = nf_ct_get(skb, &ctinfo); |
9fafcd7b | 55 | enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo); |
624f8b7b PM |
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; | |
9fafcd7b PM |
69 | } else |
70 | return 1; | |
71 | ||
624f8b7b PM |
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 | ||
2a6cfb22 | 78 | return mangle_packet(skb, dptr, datalen, matchoff, matchlen, |
624f8b7b | 79 | buffer, buflen); |
9fafcd7b PM |
80 | } |
81 | ||
ac367740 PM |
82 | static int map_sip_addr(struct sk_buff *skb, |
83 | const char **dptr, unsigned int *datalen, | |
624f8b7b | 84 | enum sip_header_types type) |
ac367740 PM |
85 | { |
86 | enum ip_conntrack_info ctinfo; | |
87 | struct nf_conn *ct = nf_ct_get(skb, &ctinfo); | |
88 | unsigned int matchlen, matchoff; | |
624f8b7b PM |
89 | union nf_inet_addr addr; |
90 | __be16 port; | |
ac367740 | 91 | |
624f8b7b PM |
92 | if (ct_sip_parse_header_uri(ct, *dptr, NULL, *datalen, type, NULL, |
93 | &matchoff, &matchlen, &addr, &port) <= 0) | |
ac367740 | 94 | return 1; |
624f8b7b | 95 | return map_addr(skb, dptr, datalen, matchoff, matchlen, &addr, port); |
ac367740 PM |
96 | } |
97 | ||
3db05fea | 98 | static unsigned int ip_nat_sip(struct sk_buff *skb, |
2a6cfb22 | 99 | const char **dptr, unsigned int *datalen) |
9fafcd7b | 100 | { |
212440a7 PM |
101 | enum ip_conntrack_info ctinfo; |
102 | struct nf_conn *ct = nf_ct_get(skb, &ctinfo); | |
720ac708 | 103 | enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo); |
ac367740 | 104 | unsigned int matchoff, matchlen; |
624f8b7b PM |
105 | union nf_inet_addr addr; |
106 | __be16 port; | |
720ac708 | 107 | int request; |
9fafcd7b | 108 | |
9fafcd7b | 109 | /* Basic rules: requests and responses. */ |
779382eb | 110 | if (strnicmp(*dptr, "SIP/2.0", strlen("SIP/2.0")) != 0) { |
ac367740 | 111 | if (ct_sip_parse_request(ct, *dptr, *datalen, |
624f8b7b PM |
112 | &matchoff, &matchlen, |
113 | &addr, &port) > 0 && | |
114 | !map_addr(skb, dptr, datalen, matchoff, matchlen, | |
115 | &addr, port)) | |
9fafcd7b | 116 | return NF_DROP; |
720ac708 PM |
117 | request = 1; |
118 | } else | |
119 | request = 0; | |
120 | ||
121 | /* Translate topmost Via header and parameters */ | |
122 | if (ct_sip_parse_header_uri(ct, *dptr, NULL, *datalen, | |
123 | SIP_HDR_VIA, NULL, &matchoff, &matchlen, | |
124 | &addr, &port) > 0) { | |
125 | unsigned int matchend, poff, plen, buflen, n; | |
126 | char buffer[sizeof("nnn.nnn.nnn.nnn:nnnnn")]; | |
127 | ||
128 | /* We're only interested in headers related to this | |
129 | * connection */ | |
130 | if (request) { | |
131 | if (addr.ip != ct->tuplehash[dir].tuple.src.u3.ip || | |
132 | port != ct->tuplehash[dir].tuple.src.u.udp.port) | |
133 | goto next; | |
134 | } else { | |
135 | if (addr.ip != ct->tuplehash[dir].tuple.dst.u3.ip || | |
136 | port != ct->tuplehash[dir].tuple.dst.u.udp.port) | |
137 | goto next; | |
138 | } | |
139 | ||
140 | if (!map_addr(skb, dptr, datalen, matchoff, matchlen, | |
141 | &addr, port)) | |
142 | return NF_DROP; | |
143 | ||
144 | matchend = matchoff + matchlen; | |
145 | ||
146 | /* The maddr= parameter (RFC 2361) specifies where to send | |
147 | * the reply. */ | |
148 | if (ct_sip_parse_address_param(ct, *dptr, matchend, *datalen, | |
149 | "maddr=", &poff, &plen, | |
150 | &addr) > 0 && | |
151 | addr.ip == ct->tuplehash[dir].tuple.src.u3.ip && | |
152 | addr.ip != ct->tuplehash[!dir].tuple.dst.u3.ip) { | |
153 | __be32 ip = ct->tuplehash[!dir].tuple.dst.u3.ip; | |
154 | buflen = sprintf(buffer, "%u.%u.%u.%u", NIPQUAD(ip)); | |
155 | if (!mangle_packet(skb, dptr, datalen, poff, plen, | |
156 | buffer, buflen)) | |
157 | return NF_DROP; | |
158 | } | |
159 | ||
160 | /* The received= parameter (RFC 2361) contains the address | |
161 | * from which the server received the request. */ | |
162 | if (ct_sip_parse_address_param(ct, *dptr, matchend, *datalen, | |
163 | "received=", &poff, &plen, | |
164 | &addr) > 0 && | |
165 | addr.ip == ct->tuplehash[dir].tuple.dst.u3.ip && | |
166 | addr.ip != ct->tuplehash[!dir].tuple.src.u3.ip) { | |
167 | __be32 ip = ct->tuplehash[!dir].tuple.src.u3.ip; | |
168 | buflen = sprintf(buffer, "%u.%u.%u.%u", NIPQUAD(ip)); | |
169 | if (!mangle_packet(skb, dptr, datalen, poff, plen, | |
170 | buffer, buflen)) | |
171 | return NF_DROP; | |
172 | } | |
173 | ||
174 | /* The rport= parameter (RFC 3581) contains the port number | |
175 | * from which the server received the request. */ | |
176 | if (ct_sip_parse_numerical_param(ct, *dptr, matchend, *datalen, | |
177 | "rport=", &poff, &plen, | |
178 | &n) > 0 && | |
179 | htons(n) == ct->tuplehash[dir].tuple.dst.u.udp.port && | |
180 | htons(n) != ct->tuplehash[!dir].tuple.src.u.udp.port) { | |
181 | __be16 p = ct->tuplehash[!dir].tuple.src.u.udp.port; | |
182 | buflen = sprintf(buffer, "%u", ntohs(p)); | |
183 | if (!mangle_packet(skb, dptr, datalen, poff, plen, | |
184 | buffer, buflen)) | |
185 | return NF_DROP; | |
186 | } | |
9fafcd7b PM |
187 | } |
188 | ||
720ac708 | 189 | next: |
624f8b7b PM |
190 | if (!map_sip_addr(skb, dptr, datalen, SIP_HDR_FROM) || |
191 | !map_sip_addr(skb, dptr, datalen, SIP_HDR_TO) || | |
624f8b7b | 192 | !map_sip_addr(skb, dptr, datalen, SIP_HDR_CONTACT)) |
9fafcd7b PM |
193 | return NF_DROP; |
194 | return NF_ACCEPT; | |
195 | } | |
196 | ||
3e9b4600 PM |
197 | static int mangle_content_len(struct sk_buff *skb, |
198 | const char **dptr, unsigned int *datalen) | |
9fafcd7b | 199 | { |
212440a7 PM |
200 | enum ip_conntrack_info ctinfo; |
201 | struct nf_conn *ct = nf_ct_get(skb, &ctinfo); | |
3e9b4600 PM |
202 | unsigned int matchoff, matchlen; |
203 | char buffer[sizeof("65536")]; | |
204 | int buflen, c_len; | |
9fafcd7b | 205 | |
3e9b4600 PM |
206 | /* Get actual SDP length */ |
207 | if (ct_sip_get_sdp_header(ct, *dptr, 0, *datalen, | |
208 | SDP_HDR_VERSION, SDP_HDR_UNSPEC, | |
209 | &matchoff, &matchlen) <= 0) | |
210 | return 0; | |
211 | c_len = *datalen - matchoff + strlen("v="); | |
212 | ||
213 | /* Now, update SDP length */ | |
ea45f12a PM |
214 | if (ct_sip_get_header(ct, *dptr, 0, *datalen, SIP_HDR_CONTENT_LENGTH, |
215 | &matchoff, &matchlen) <= 0) | |
9fafcd7b PM |
216 | return 0; |
217 | ||
3e9b4600 | 218 | buflen = sprintf(buffer, "%u", c_len); |
2a6cfb22 | 219 | return mangle_packet(skb, dptr, datalen, matchoff, matchlen, |
3e9b4600 | 220 | buffer, buflen); |
9fafcd7b PM |
221 | } |
222 | ||
3e9b4600 PM |
223 | static unsigned mangle_sdp_packet(struct sk_buff *skb, |
224 | const char **dptr, unsigned int *datalen, | |
225 | enum sdp_header_types type, | |
226 | char *buffer, int buflen) | |
9fafcd7b | 227 | { |
212440a7 PM |
228 | enum ip_conntrack_info ctinfo; |
229 | struct nf_conn *ct = nf_ct_get(skb, &ctinfo); | |
3e9b4600 | 230 | unsigned int matchlen, matchoff; |
9fafcd7b | 231 | |
3e9b4600 PM |
232 | if (ct_sip_get_sdp_header(ct, *dptr, 0, *datalen, type, SDP_HDR_UNSPEC, |
233 | &matchoff, &matchlen) <= 0) | |
234 | return 0; | |
235 | return mangle_packet(skb, dptr, datalen, matchoff, matchlen, | |
236 | buffer, buflen); | |
9fafcd7b PM |
237 | } |
238 | ||
3db05fea | 239 | static unsigned int mangle_sdp(struct sk_buff *skb, |
9fafcd7b PM |
240 | enum ip_conntrack_info ctinfo, |
241 | struct nf_conn *ct, | |
242 | __be32 newip, u_int16_t port, | |
2a6cfb22 | 243 | const char **dptr, unsigned int *datalen) |
9fafcd7b PM |
244 | { |
245 | char buffer[sizeof("nnn.nnn.nnn.nnn")]; | |
2a6cfb22 | 246 | unsigned int bufflen; |
9fafcd7b PM |
247 | |
248 | /* Mangle owner and contact info. */ | |
249 | bufflen = sprintf(buffer, "%u.%u.%u.%u", NIPQUAD(newip)); | |
3e9b4600 PM |
250 | if (!mangle_sdp_packet(skb, dptr, datalen, SDP_HDR_OWNER_IP4, |
251 | buffer, bufflen)) | |
9fafcd7b PM |
252 | return 0; |
253 | ||
3e9b4600 PM |
254 | if (!mangle_sdp_packet(skb, dptr, datalen, SDP_HDR_CONNECTION_IP4, |
255 | buffer, bufflen)) | |
9fafcd7b PM |
256 | return 0; |
257 | ||
258 | /* Mangle media port. */ | |
259 | bufflen = sprintf(buffer, "%u", port); | |
3e9b4600 PM |
260 | if (!mangle_sdp_packet(skb, dptr, datalen, SDP_HDR_MEDIA, |
261 | buffer, bufflen)) | |
9fafcd7b PM |
262 | return 0; |
263 | ||
212440a7 | 264 | return mangle_content_len(skb, dptr, datalen); |
9fafcd7b PM |
265 | } |
266 | ||
cfd6c380 HX |
267 | static void ip_nat_sdp_expect(struct nf_conn *ct, |
268 | struct nf_conntrack_expect *exp) | |
269 | { | |
270 | struct nf_nat_range range; | |
271 | ||
272 | /* This must be a fresh one. */ | |
273 | BUG_ON(ct->status & IPS_NAT_DONE_MASK); | |
274 | ||
cfd6c380 HX |
275 | /* For DST manip, map port here to where it's expected. */ |
276 | range.flags = (IP_NAT_RANGE_MAP_IPS | IP_NAT_RANGE_PROTO_SPECIFIED); | |
277 | range.min = range.max = exp->saved_proto; | |
278 | range.min_ip = range.max_ip = exp->saved_ip; | |
cc01dcbd | 279 | nf_nat_setup_info(ct, &range, IP_NAT_MANIP_DST); |
3d244121 PM |
280 | |
281 | /* Change src to where master sends to */ | |
282 | range.flags = IP_NAT_RANGE_MAP_IPS; | |
283 | range.min_ip = range.max_ip | |
284 | = ct->master->tuplehash[!exp->dir].tuple.dst.u3.ip; | |
285 | nf_nat_setup_info(ct, &range, IP_NAT_MANIP_SRC); | |
cfd6c380 HX |
286 | } |
287 | ||
9fafcd7b PM |
288 | /* So, this packet has hit the connection tracking matching code. |
289 | Mangle it, and change the expectation to match the new version. */ | |
3db05fea | 290 | static unsigned int ip_nat_sdp(struct sk_buff *skb, |
212440a7 PM |
291 | const char **dptr, unsigned int *datalen, |
292 | struct nf_conntrack_expect *exp) | |
9fafcd7b | 293 | { |
212440a7 PM |
294 | enum ip_conntrack_info ctinfo; |
295 | struct nf_conn *ct = nf_ct_get(skb, &ctinfo); | |
9fafcd7b PM |
296 | enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo); |
297 | __be32 newip; | |
298 | u_int16_t port; | |
299 | ||
9fafcd7b | 300 | /* Connection will come from reply */ |
f4a607bf JB |
301 | if (ct->tuplehash[dir].tuple.src.u3.ip == |
302 | ct->tuplehash[!dir].tuple.dst.u3.ip) | |
303 | newip = exp->tuple.dst.u3.ip; | |
304 | else | |
305 | newip = ct->tuplehash[!dir].tuple.dst.u3.ip; | |
9fafcd7b | 306 | |
cfd6c380 | 307 | exp->saved_ip = exp->tuple.dst.u3.ip; |
9fafcd7b PM |
308 | exp->tuple.dst.u3.ip = newip; |
309 | exp->saved_proto.udp.port = exp->tuple.dst.u.udp.port; | |
310 | exp->dir = !dir; | |
311 | ||
312 | /* When you see the packet, we need to NAT it the same as the | |
313 | this one. */ | |
cfd6c380 | 314 | exp->expectfn = ip_nat_sdp_expect; |
9fafcd7b PM |
315 | |
316 | /* Try to get same port: if not, try to change it. */ | |
317 | for (port = ntohs(exp->saved_proto.udp.port); port != 0; port++) { | |
318 | exp->tuple.dst.u.udp.port = htons(port); | |
6823645d | 319 | if (nf_ct_expect_related(exp) == 0) |
9fafcd7b PM |
320 | break; |
321 | } | |
322 | ||
323 | if (port == 0) | |
324 | return NF_DROP; | |
325 | ||
2a6cfb22 | 326 | if (!mangle_sdp(skb, ctinfo, ct, newip, port, dptr, datalen)) { |
6823645d | 327 | nf_ct_unexpect_related(exp); |
9fafcd7b PM |
328 | return NF_DROP; |
329 | } | |
330 | return NF_ACCEPT; | |
331 | } | |
332 | ||
333 | static void __exit nf_nat_sip_fini(void) | |
334 | { | |
335 | rcu_assign_pointer(nf_nat_sip_hook, NULL); | |
336 | rcu_assign_pointer(nf_nat_sdp_hook, NULL); | |
337 | synchronize_rcu(); | |
338 | } | |
339 | ||
340 | static int __init nf_nat_sip_init(void) | |
341 | { | |
d1332e0a PM |
342 | BUG_ON(nf_nat_sip_hook != NULL); |
343 | BUG_ON(nf_nat_sdp_hook != NULL); | |
9fafcd7b PM |
344 | rcu_assign_pointer(nf_nat_sip_hook, ip_nat_sip); |
345 | rcu_assign_pointer(nf_nat_sdp_hook, ip_nat_sdp); | |
346 | return 0; | |
347 | } | |
348 | ||
349 | module_init(nf_nat_sip_init); | |
350 | module_exit(nf_nat_sip_fini); |