]>
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); |
c978cd3a | 104 | unsigned int dataoff, matchoff, matchlen; |
624f8b7b PM |
105 | union nf_inet_addr addr; |
106 | __be16 port; | |
c978cd3a | 107 | int request, in_header; |
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: |
c978cd3a PM |
190 | /* Translate Contact headers */ |
191 | dataoff = 0; | |
192 | in_header = 0; | |
193 | while (ct_sip_parse_header_uri(ct, *dptr, &dataoff, *datalen, | |
194 | SIP_HDR_CONTACT, &in_header, | |
195 | &matchoff, &matchlen, | |
196 | &addr, &port) > 0) { | |
197 | if (!map_addr(skb, dptr, datalen, matchoff, matchlen, | |
198 | &addr, port)) | |
199 | return NF_DROP; | |
200 | } | |
201 | ||
624f8b7b | 202 | if (!map_sip_addr(skb, dptr, datalen, SIP_HDR_FROM) || |
c978cd3a | 203 | !map_sip_addr(skb, dptr, datalen, SIP_HDR_TO)) |
9fafcd7b PM |
204 | return NF_DROP; |
205 | return NF_ACCEPT; | |
206 | } | |
207 | ||
3e9b4600 PM |
208 | static int mangle_content_len(struct sk_buff *skb, |
209 | const char **dptr, unsigned int *datalen) | |
9fafcd7b | 210 | { |
212440a7 PM |
211 | enum ip_conntrack_info ctinfo; |
212 | struct nf_conn *ct = nf_ct_get(skb, &ctinfo); | |
3e9b4600 PM |
213 | unsigned int matchoff, matchlen; |
214 | char buffer[sizeof("65536")]; | |
215 | int buflen, c_len; | |
9fafcd7b | 216 | |
3e9b4600 PM |
217 | /* Get actual SDP length */ |
218 | if (ct_sip_get_sdp_header(ct, *dptr, 0, *datalen, | |
219 | SDP_HDR_VERSION, SDP_HDR_UNSPEC, | |
220 | &matchoff, &matchlen) <= 0) | |
221 | return 0; | |
222 | c_len = *datalen - matchoff + strlen("v="); | |
223 | ||
224 | /* Now, update SDP length */ | |
ea45f12a PM |
225 | if (ct_sip_get_header(ct, *dptr, 0, *datalen, SIP_HDR_CONTENT_LENGTH, |
226 | &matchoff, &matchlen) <= 0) | |
9fafcd7b PM |
227 | return 0; |
228 | ||
3e9b4600 | 229 | buflen = sprintf(buffer, "%u", c_len); |
2a6cfb22 | 230 | return mangle_packet(skb, dptr, datalen, matchoff, matchlen, |
3e9b4600 | 231 | buffer, buflen); |
9fafcd7b PM |
232 | } |
233 | ||
3e9b4600 PM |
234 | static unsigned mangle_sdp_packet(struct sk_buff *skb, |
235 | const char **dptr, unsigned int *datalen, | |
236 | enum sdp_header_types type, | |
237 | char *buffer, int buflen) | |
9fafcd7b | 238 | { |
212440a7 PM |
239 | enum ip_conntrack_info ctinfo; |
240 | struct nf_conn *ct = nf_ct_get(skb, &ctinfo); | |
3e9b4600 | 241 | unsigned int matchlen, matchoff; |
9fafcd7b | 242 | |
3e9b4600 PM |
243 | if (ct_sip_get_sdp_header(ct, *dptr, 0, *datalen, type, SDP_HDR_UNSPEC, |
244 | &matchoff, &matchlen) <= 0) | |
245 | return 0; | |
246 | return mangle_packet(skb, dptr, datalen, matchoff, matchlen, | |
247 | buffer, buflen); | |
9fafcd7b PM |
248 | } |
249 | ||
3db05fea | 250 | static unsigned int mangle_sdp(struct sk_buff *skb, |
9fafcd7b PM |
251 | enum ip_conntrack_info ctinfo, |
252 | struct nf_conn *ct, | |
253 | __be32 newip, u_int16_t port, | |
2a6cfb22 | 254 | const char **dptr, unsigned int *datalen) |
9fafcd7b PM |
255 | { |
256 | char buffer[sizeof("nnn.nnn.nnn.nnn")]; | |
2a6cfb22 | 257 | unsigned int bufflen; |
9fafcd7b PM |
258 | |
259 | /* Mangle owner and contact info. */ | |
260 | bufflen = sprintf(buffer, "%u.%u.%u.%u", NIPQUAD(newip)); | |
3e9b4600 PM |
261 | if (!mangle_sdp_packet(skb, dptr, datalen, SDP_HDR_OWNER_IP4, |
262 | buffer, bufflen)) | |
9fafcd7b PM |
263 | return 0; |
264 | ||
3e9b4600 PM |
265 | if (!mangle_sdp_packet(skb, dptr, datalen, SDP_HDR_CONNECTION_IP4, |
266 | buffer, bufflen)) | |
9fafcd7b PM |
267 | return 0; |
268 | ||
269 | /* Mangle media port. */ | |
270 | bufflen = sprintf(buffer, "%u", port); | |
3e9b4600 PM |
271 | if (!mangle_sdp_packet(skb, dptr, datalen, SDP_HDR_MEDIA, |
272 | buffer, bufflen)) | |
9fafcd7b PM |
273 | return 0; |
274 | ||
212440a7 | 275 | return mangle_content_len(skb, dptr, datalen); |
9fafcd7b PM |
276 | } |
277 | ||
cfd6c380 HX |
278 | static void ip_nat_sdp_expect(struct nf_conn *ct, |
279 | struct nf_conntrack_expect *exp) | |
280 | { | |
281 | struct nf_nat_range range; | |
282 | ||
283 | /* This must be a fresh one. */ | |
284 | BUG_ON(ct->status & IPS_NAT_DONE_MASK); | |
285 | ||
cfd6c380 HX |
286 | /* For DST manip, map port here to where it's expected. */ |
287 | range.flags = (IP_NAT_RANGE_MAP_IPS | IP_NAT_RANGE_PROTO_SPECIFIED); | |
288 | range.min = range.max = exp->saved_proto; | |
289 | range.min_ip = range.max_ip = exp->saved_ip; | |
cc01dcbd | 290 | nf_nat_setup_info(ct, &range, IP_NAT_MANIP_DST); |
3d244121 PM |
291 | |
292 | /* Change src to where master sends to */ | |
293 | range.flags = IP_NAT_RANGE_MAP_IPS; | |
294 | range.min_ip = range.max_ip | |
295 | = ct->master->tuplehash[!exp->dir].tuple.dst.u3.ip; | |
296 | nf_nat_setup_info(ct, &range, IP_NAT_MANIP_SRC); | |
cfd6c380 HX |
297 | } |
298 | ||
9fafcd7b PM |
299 | /* So, this packet has hit the connection tracking matching code. |
300 | Mangle it, and change the expectation to match the new version. */ | |
3db05fea | 301 | static unsigned int ip_nat_sdp(struct sk_buff *skb, |
212440a7 PM |
302 | const char **dptr, unsigned int *datalen, |
303 | struct nf_conntrack_expect *exp) | |
9fafcd7b | 304 | { |
212440a7 PM |
305 | enum ip_conntrack_info ctinfo; |
306 | struct nf_conn *ct = nf_ct_get(skb, &ctinfo); | |
9fafcd7b PM |
307 | enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo); |
308 | __be32 newip; | |
309 | u_int16_t port; | |
310 | ||
9fafcd7b | 311 | /* Connection will come from reply */ |
f4a607bf JB |
312 | if (ct->tuplehash[dir].tuple.src.u3.ip == |
313 | ct->tuplehash[!dir].tuple.dst.u3.ip) | |
314 | newip = exp->tuple.dst.u3.ip; | |
315 | else | |
316 | newip = ct->tuplehash[!dir].tuple.dst.u3.ip; | |
9fafcd7b | 317 | |
cfd6c380 | 318 | exp->saved_ip = exp->tuple.dst.u3.ip; |
9fafcd7b PM |
319 | exp->tuple.dst.u3.ip = newip; |
320 | exp->saved_proto.udp.port = exp->tuple.dst.u.udp.port; | |
321 | exp->dir = !dir; | |
322 | ||
323 | /* When you see the packet, we need to NAT it the same as the | |
324 | this one. */ | |
cfd6c380 | 325 | exp->expectfn = ip_nat_sdp_expect; |
9fafcd7b PM |
326 | |
327 | /* Try to get same port: if not, try to change it. */ | |
328 | for (port = ntohs(exp->saved_proto.udp.port); port != 0; port++) { | |
329 | exp->tuple.dst.u.udp.port = htons(port); | |
6823645d | 330 | if (nf_ct_expect_related(exp) == 0) |
9fafcd7b PM |
331 | break; |
332 | } | |
333 | ||
334 | if (port == 0) | |
335 | return NF_DROP; | |
336 | ||
2a6cfb22 | 337 | if (!mangle_sdp(skb, ctinfo, ct, newip, port, dptr, datalen)) { |
6823645d | 338 | nf_ct_unexpect_related(exp); |
9fafcd7b PM |
339 | return NF_DROP; |
340 | } | |
341 | return NF_ACCEPT; | |
342 | } | |
343 | ||
344 | static void __exit nf_nat_sip_fini(void) | |
345 | { | |
346 | rcu_assign_pointer(nf_nat_sip_hook, NULL); | |
347 | rcu_assign_pointer(nf_nat_sdp_hook, NULL); | |
348 | synchronize_rcu(); | |
349 | } | |
350 | ||
351 | static int __init nf_nat_sip_init(void) | |
352 | { | |
d1332e0a PM |
353 | BUG_ON(nf_nat_sip_hook != NULL); |
354 | BUG_ON(nf_nat_sdp_hook != NULL); | |
9fafcd7b PM |
355 | rcu_assign_pointer(nf_nat_sip_hook, ip_nat_sip); |
356 | rcu_assign_pointer(nf_nat_sdp_hook, ip_nat_sdp); | |
357 | return 0; | |
358 | } | |
359 | ||
360 | module_init(nf_nat_sip_init); | |
361 | module_exit(nf_nat_sip_fini); |