]>
Commit | Line | Data |
---|---|---|
48f8ac26 | 1 | /* SIP extension for NAT alteration. |
9fafcd7b PM |
2 | * |
3 | * (C) 2005 by Christian Hentschel <chentschel@arnet.com.ar> | |
4 | * based on RR's ip_nat_ftp.c and other modules. | |
f49e1aa1 | 5 | * (C) 2007 United Security Providers |
9a664821 | 6 | * (C) 2007, 2008, 2011, 2012 Patrick McHardy <kaber@trash.net> |
9fafcd7b PM |
7 | * |
8 | * This program is free software; you can redistribute it and/or modify | |
9 | * it under the terms of the GNU General Public License version 2 as | |
10 | * published by the Free Software Foundation. | |
11 | */ | |
12 | ||
13 | #include <linux/module.h> | |
14 | #include <linux/skbuff.h> | |
9a664821 | 15 | #include <linux/inet.h> |
9fafcd7b | 16 | #include <linux/udp.h> |
48f8ac26 | 17 | #include <linux/tcp.h> |
9fafcd7b PM |
18 | |
19 | #include <net/netfilter/nf_nat.h> | |
20 | #include <net/netfilter/nf_nat_helper.h> | |
5ddded90 | 21 | #include <net/netfilter/nf_conntrack_core.h> |
9fafcd7b PM |
22 | #include <net/netfilter/nf_conntrack_helper.h> |
23 | #include <net/netfilter/nf_conntrack_expect.h> | |
41d73ec0 | 24 | #include <net/netfilter/nf_conntrack_seqadj.h> |
9fafcd7b PM |
25 | #include <linux/netfilter/nf_conntrack_sip.h> |
26 | ||
27 | MODULE_LICENSE("GPL"); | |
28 | MODULE_AUTHOR("Christian Hentschel <chentschel@arnet.com.ar>"); | |
29 | MODULE_DESCRIPTION("SIP NAT helper"); | |
30 | MODULE_ALIAS("ip_nat_sip"); | |
31 | ||
9fafcd7b | 32 | |
051966c0 PM |
33 | static unsigned int mangle_packet(struct sk_buff *skb, unsigned int protoff, |
34 | unsigned int dataoff, | |
2a6cfb22 PM |
35 | const char **dptr, unsigned int *datalen, |
36 | unsigned int matchoff, unsigned int matchlen, | |
37 | const char *buffer, unsigned int buflen) | |
38 | { | |
39 | enum ip_conntrack_info ctinfo; | |
40 | struct nf_conn *ct = nf_ct_get(skb, &ctinfo); | |
48f8ac26 PM |
41 | struct tcphdr *th; |
42 | unsigned int baseoff; | |
43 | ||
44 | if (nf_ct_protonum(ct) == IPPROTO_TCP) { | |
9a664821 PM |
45 | th = (struct tcphdr *)(skb->data + protoff); |
46 | baseoff = protoff + th->doff * 4; | |
48f8ac26 PM |
47 | matchoff += dataoff - baseoff; |
48 | ||
49 | if (!__nf_nat_mangle_tcp_packet(skb, ct, ctinfo, | |
051966c0 | 50 | protoff, matchoff, matchlen, |
48f8ac26 PM |
51 | buffer, buflen, false)) |
52 | return 0; | |
53 | } else { | |
9a664821 | 54 | baseoff = protoff + sizeof(struct udphdr); |
48f8ac26 PM |
55 | matchoff += dataoff - baseoff; |
56 | ||
57 | if (!nf_nat_mangle_udp_packet(skb, ct, ctinfo, | |
051966c0 | 58 | protoff, matchoff, matchlen, |
48f8ac26 PM |
59 | buffer, buflen)) |
60 | return 0; | |
61 | } | |
2a6cfb22 PM |
62 | |
63 | /* Reload data pointer and adjust datalen value */ | |
3b6b9fab | 64 | *dptr = skb->data + dataoff; |
2a6cfb22 PM |
65 | *datalen += buflen - matchlen; |
66 | return 1; | |
67 | } | |
68 | ||
9a664821 PM |
69 | static int sip_sprintf_addr(const struct nf_conn *ct, char *buffer, |
70 | const union nf_inet_addr *addr, bool delim) | |
71 | { | |
72 | if (nf_ct_l3num(ct) == NFPROTO_IPV4) | |
73 | return sprintf(buffer, "%pI4", &addr->ip); | |
74 | else { | |
75 | if (delim) | |
76 | return sprintf(buffer, "[%pI6c]", &addr->ip6); | |
77 | else | |
78 | return sprintf(buffer, "%pI6c", &addr->ip6); | |
79 | } | |
80 | } | |
81 | ||
82 | static int sip_sprintf_addr_port(const struct nf_conn *ct, char *buffer, | |
83 | const union nf_inet_addr *addr, u16 port) | |
84 | { | |
85 | if (nf_ct_l3num(ct) == NFPROTO_IPV4) | |
86 | return sprintf(buffer, "%pI4:%u", &addr->ip, port); | |
87 | else | |
88 | return sprintf(buffer, "[%pI6c]:%u", &addr->ip6, port); | |
89 | } | |
90 | ||
051966c0 PM |
91 | static int map_addr(struct sk_buff *skb, unsigned int protoff, |
92 | unsigned int dataoff, | |
ac367740 PM |
93 | const char **dptr, unsigned int *datalen, |
94 | unsigned int matchoff, unsigned int matchlen, | |
624f8b7b | 95 | union nf_inet_addr *addr, __be16 port) |
9fafcd7b | 96 | { |
212440a7 | 97 | enum ip_conntrack_info ctinfo; |
624f8b7b | 98 | struct nf_conn *ct = nf_ct_get(skb, &ctinfo); |
9fafcd7b | 99 | enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo); |
7266507d | 100 | struct nf_ct_sip_master *ct_sip_info = nfct_help_data(ct); |
9a664821 | 101 | char buffer[INET6_ADDRSTRLEN + sizeof("[]:nnnnn")]; |
624f8b7b | 102 | unsigned int buflen; |
9a664821 | 103 | union nf_inet_addr newaddr; |
624f8b7b PM |
104 | __be16 newport; |
105 | ||
9a664821 | 106 | if (nf_inet_addr_cmp(&ct->tuplehash[dir].tuple.src.u3, addr) && |
624f8b7b | 107 | ct->tuplehash[dir].tuple.src.u.udp.port == port) { |
9a664821 | 108 | newaddr = ct->tuplehash[!dir].tuple.dst.u3; |
624f8b7b | 109 | newport = ct->tuplehash[!dir].tuple.dst.u.udp.port; |
9a664821 | 110 | } else if (nf_inet_addr_cmp(&ct->tuplehash[dir].tuple.dst.u3, addr) && |
624f8b7b | 111 | ct->tuplehash[dir].tuple.dst.u.udp.port == port) { |
9a664821 | 112 | newaddr = ct->tuplehash[!dir].tuple.src.u3; |
7266507d KC |
113 | newport = ct_sip_info->forced_dport ? : |
114 | ct->tuplehash[!dir].tuple.src.u.udp.port; | |
9fafcd7b PM |
115 | } else |
116 | return 1; | |
117 | ||
9a664821 | 118 | if (nf_inet_addr_cmp(&newaddr, addr) && newport == port) |
624f8b7b PM |
119 | return 1; |
120 | ||
9a664821 | 121 | buflen = sip_sprintf_addr_port(ct, buffer, &newaddr, ntohs(newport)); |
051966c0 PM |
122 | return mangle_packet(skb, protoff, dataoff, dptr, datalen, |
123 | matchoff, matchlen, buffer, buflen); | |
9fafcd7b PM |
124 | } |
125 | ||
051966c0 PM |
126 | static int map_sip_addr(struct sk_buff *skb, unsigned int protoff, |
127 | unsigned int dataoff, | |
ac367740 | 128 | const char **dptr, unsigned int *datalen, |
624f8b7b | 129 | enum sip_header_types type) |
ac367740 PM |
130 | { |
131 | enum ip_conntrack_info ctinfo; | |
132 | struct nf_conn *ct = nf_ct_get(skb, &ctinfo); | |
133 | unsigned int matchlen, matchoff; | |
624f8b7b PM |
134 | union nf_inet_addr addr; |
135 | __be16 port; | |
ac367740 | 136 | |
624f8b7b PM |
137 | if (ct_sip_parse_header_uri(ct, *dptr, NULL, *datalen, type, NULL, |
138 | &matchoff, &matchlen, &addr, &port) <= 0) | |
ac367740 | 139 | return 1; |
051966c0 PM |
140 | return map_addr(skb, protoff, dataoff, dptr, datalen, |
141 | matchoff, matchlen, &addr, port); | |
ac367740 PM |
142 | } |
143 | ||
9a664821 | 144 | static unsigned int nf_nat_sip(struct sk_buff *skb, unsigned int protoff, |
051966c0 | 145 | unsigned int dataoff, |
2a6cfb22 | 146 | const char **dptr, unsigned int *datalen) |
9fafcd7b | 147 | { |
212440a7 PM |
148 | enum ip_conntrack_info ctinfo; |
149 | struct nf_conn *ct = nf_ct_get(skb, &ctinfo); | |
720ac708 | 150 | enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo); |
7266507d | 151 | struct nf_ct_sip_master *ct_sip_info = nfct_help_data(ct); |
3b6b9fab | 152 | unsigned int coff, matchoff, matchlen; |
48f8ac26 | 153 | enum sip_header_types hdr; |
624f8b7b PM |
154 | union nf_inet_addr addr; |
155 | __be16 port; | |
c978cd3a | 156 | int request, in_header; |
9fafcd7b | 157 | |
9fafcd7b | 158 | /* Basic rules: requests and responses. */ |
18082746 | 159 | if (strncasecmp(*dptr, "SIP/2.0", strlen("SIP/2.0")) != 0) { |
ac367740 | 160 | if (ct_sip_parse_request(ct, *dptr, *datalen, |
624f8b7b PM |
161 | &matchoff, &matchlen, |
162 | &addr, &port) > 0 && | |
051966c0 | 163 | !map_addr(skb, protoff, dataoff, dptr, datalen, |
b20ab9cc PNA |
164 | matchoff, matchlen, &addr, port)) { |
165 | nf_ct_helper_log(skb, ct, "cannot mangle SIP message"); | |
9fafcd7b | 166 | return NF_DROP; |
b20ab9cc | 167 | } |
720ac708 PM |
168 | request = 1; |
169 | } else | |
170 | request = 0; | |
171 | ||
48f8ac26 PM |
172 | if (nf_ct_protonum(ct) == IPPROTO_TCP) |
173 | hdr = SIP_HDR_VIA_TCP; | |
174 | else | |
175 | hdr = SIP_HDR_VIA_UDP; | |
176 | ||
720ac708 PM |
177 | /* Translate topmost Via header and parameters */ |
178 | if (ct_sip_parse_header_uri(ct, *dptr, NULL, *datalen, | |
48f8ac26 | 179 | hdr, NULL, &matchoff, &matchlen, |
720ac708 | 180 | &addr, &port) > 0) { |
f22eb25c | 181 | unsigned int olen, matchend, poff, plen, buflen, n; |
9a664821 | 182 | char buffer[INET6_ADDRSTRLEN + sizeof("[]:nnnnn")]; |
720ac708 PM |
183 | |
184 | /* We're only interested in headers related to this | |
185 | * connection */ | |
186 | if (request) { | |
9a664821 PM |
187 | if (!nf_inet_addr_cmp(&addr, |
188 | &ct->tuplehash[dir].tuple.src.u3) || | |
720ac708 PM |
189 | port != ct->tuplehash[dir].tuple.src.u.udp.port) |
190 | goto next; | |
191 | } else { | |
9a664821 PM |
192 | if (!nf_inet_addr_cmp(&addr, |
193 | &ct->tuplehash[dir].tuple.dst.u3) || | |
720ac708 PM |
194 | port != ct->tuplehash[dir].tuple.dst.u.udp.port) |
195 | goto next; | |
196 | } | |
197 | ||
f22eb25c | 198 | olen = *datalen; |
051966c0 | 199 | if (!map_addr(skb, protoff, dataoff, dptr, datalen, |
b20ab9cc PNA |
200 | matchoff, matchlen, &addr, port)) { |
201 | nf_ct_helper_log(skb, ct, "cannot mangle Via header"); | |
720ac708 | 202 | return NF_DROP; |
b20ab9cc | 203 | } |
720ac708 | 204 | |
f22eb25c | 205 | matchend = matchoff + matchlen + *datalen - olen; |
720ac708 PM |
206 | |
207 | /* The maddr= parameter (RFC 2361) specifies where to send | |
208 | * the reply. */ | |
209 | if (ct_sip_parse_address_param(ct, *dptr, matchend, *datalen, | |
210 | "maddr=", &poff, &plen, | |
02b69cbd | 211 | &addr, true) > 0 && |
9a664821 PM |
212 | nf_inet_addr_cmp(&addr, &ct->tuplehash[dir].tuple.src.u3) && |
213 | !nf_inet_addr_cmp(&addr, &ct->tuplehash[!dir].tuple.dst.u3)) { | |
214 | buflen = sip_sprintf_addr(ct, buffer, | |
215 | &ct->tuplehash[!dir].tuple.dst.u3, | |
216 | true); | |
051966c0 | 217 | if (!mangle_packet(skb, protoff, dataoff, dptr, datalen, |
b20ab9cc PNA |
218 | poff, plen, buffer, buflen)) { |
219 | nf_ct_helper_log(skb, ct, "cannot mangle maddr"); | |
720ac708 | 220 | return NF_DROP; |
b20ab9cc | 221 | } |
720ac708 PM |
222 | } |
223 | ||
224 | /* The received= parameter (RFC 2361) contains the address | |
225 | * from which the server received the request. */ | |
226 | if (ct_sip_parse_address_param(ct, *dptr, matchend, *datalen, | |
227 | "received=", &poff, &plen, | |
02b69cbd | 228 | &addr, false) > 0 && |
9a664821 PM |
229 | nf_inet_addr_cmp(&addr, &ct->tuplehash[dir].tuple.dst.u3) && |
230 | !nf_inet_addr_cmp(&addr, &ct->tuplehash[!dir].tuple.src.u3)) { | |
231 | buflen = sip_sprintf_addr(ct, buffer, | |
232 | &ct->tuplehash[!dir].tuple.src.u3, | |
233 | false); | |
051966c0 | 234 | if (!mangle_packet(skb, protoff, dataoff, dptr, datalen, |
5aed9387 | 235 | poff, plen, buffer, buflen)) { |
b20ab9cc | 236 | nf_ct_helper_log(skb, ct, "cannot mangle received"); |
720ac708 | 237 | return NF_DROP; |
5aed9387 | 238 | } |
720ac708 PM |
239 | } |
240 | ||
241 | /* The rport= parameter (RFC 3581) contains the port number | |
242 | * from which the server received the request. */ | |
243 | if (ct_sip_parse_numerical_param(ct, *dptr, matchend, *datalen, | |
244 | "rport=", &poff, &plen, | |
245 | &n) > 0 && | |
246 | htons(n) == ct->tuplehash[dir].tuple.dst.u.udp.port && | |
247 | htons(n) != ct->tuplehash[!dir].tuple.src.u.udp.port) { | |
248 | __be16 p = ct->tuplehash[!dir].tuple.src.u.udp.port; | |
249 | buflen = sprintf(buffer, "%u", ntohs(p)); | |
051966c0 | 250 | if (!mangle_packet(skb, protoff, dataoff, dptr, datalen, |
b20ab9cc PNA |
251 | poff, plen, buffer, buflen)) { |
252 | nf_ct_helper_log(skb, ct, "cannot mangle rport"); | |
720ac708 | 253 | return NF_DROP; |
b20ab9cc | 254 | } |
720ac708 | 255 | } |
9fafcd7b PM |
256 | } |
257 | ||
720ac708 | 258 | next: |
c978cd3a | 259 | /* Translate Contact headers */ |
3b6b9fab | 260 | coff = 0; |
c978cd3a | 261 | in_header = 0; |
3b6b9fab | 262 | while (ct_sip_parse_header_uri(ct, *dptr, &coff, *datalen, |
c978cd3a PM |
263 | SIP_HDR_CONTACT, &in_header, |
264 | &matchoff, &matchlen, | |
265 | &addr, &port) > 0) { | |
051966c0 PM |
266 | if (!map_addr(skb, protoff, dataoff, dptr, datalen, |
267 | matchoff, matchlen, | |
b20ab9cc PNA |
268 | &addr, port)) { |
269 | nf_ct_helper_log(skb, ct, "cannot mangle contact"); | |
c978cd3a | 270 | return NF_DROP; |
b20ab9cc | 271 | } |
c978cd3a PM |
272 | } |
273 | ||
051966c0 | 274 | if (!map_sip_addr(skb, protoff, dataoff, dptr, datalen, SIP_HDR_FROM) || |
b20ab9cc PNA |
275 | !map_sip_addr(skb, protoff, dataoff, dptr, datalen, SIP_HDR_TO)) { |
276 | nf_ct_helper_log(skb, ct, "cannot mangle SIP from/to"); | |
9fafcd7b | 277 | return NF_DROP; |
b20ab9cc | 278 | } |
48f8ac26 | 279 | |
7266507d KC |
280 | /* Mangle destination port for Cisco phones, then fix up checksums */ |
281 | if (dir == IP_CT_DIR_REPLY && ct_sip_info->forced_dport) { | |
282 | struct udphdr *uh; | |
283 | ||
b20ab9cc PNA |
284 | if (!skb_make_writable(skb, skb->len)) { |
285 | nf_ct_helper_log(skb, ct, "cannot mangle packet"); | |
7266507d | 286 | return NF_DROP; |
b20ab9cc | 287 | } |
7266507d KC |
288 | |
289 | uh = (void *)skb->data + protoff; | |
290 | uh->dest = ct_sip_info->forced_dport; | |
291 | ||
292 | if (!nf_nat_mangle_udp_packet(skb, ct, ctinfo, protoff, | |
b20ab9cc PNA |
293 | 0, 0, NULL, 0)) { |
294 | nf_ct_helper_log(skb, ct, "cannot mangle packet"); | |
7266507d | 295 | return NF_DROP; |
b20ab9cc | 296 | } |
7266507d KC |
297 | } |
298 | ||
9fafcd7b PM |
299 | return NF_ACCEPT; |
300 | } | |
301 | ||
9a664821 PM |
302 | static void nf_nat_sip_seq_adjust(struct sk_buff *skb, unsigned int protoff, |
303 | s16 off) | |
48f8ac26 PM |
304 | { |
305 | enum ip_conntrack_info ctinfo; | |
306 | struct nf_conn *ct = nf_ct_get(skb, &ctinfo); | |
307 | const struct tcphdr *th; | |
308 | ||
309 | if (nf_ct_protonum(ct) != IPPROTO_TCP || off == 0) | |
310 | return; | |
311 | ||
9a664821 | 312 | th = (struct tcphdr *)(skb->data + protoff); |
41d73ec0 | 313 | nf_ct_seqadj_set(ct, ctinfo, th->seq, off); |
48f8ac26 PM |
314 | } |
315 | ||
0f32a40f | 316 | /* Handles expected signalling connections and media streams */ |
9a664821 | 317 | static void nf_nat_sip_expected(struct nf_conn *ct, |
0f32a40f PM |
318 | struct nf_conntrack_expect *exp) |
319 | { | |
5ddded90 AN |
320 | struct nf_conn_help *help = nfct_help(ct->master); |
321 | struct nf_conntrack_expect *pair_exp; | |
322 | int range_set_for_snat = 0; | |
c7232c99 | 323 | struct nf_nat_range range; |
0f32a40f PM |
324 | |
325 | /* This must be a fresh one. */ | |
326 | BUG_ON(ct->status & IPS_NAT_DONE_MASK); | |
327 | ||
328 | /* For DST manip, map port here to where it's expected. */ | |
cbc9f2f4 | 329 | range.flags = (NF_NAT_RANGE_MAP_IPS | NF_NAT_RANGE_PROTO_SPECIFIED); |
c7232c99 PM |
330 | range.min_proto = range.max_proto = exp->saved_proto; |
331 | range.min_addr = range.max_addr = exp->saved_addr; | |
cbc9f2f4 | 332 | nf_nat_setup_info(ct, &range, NF_NAT_MANIP_DST); |
0f32a40f | 333 | |
5ddded90 AN |
334 | /* Do media streams SRC manip according with the parameters |
335 | * found in the paired expectation. | |
336 | */ | |
337 | if (exp->class != SIP_EXPECT_SIGNALLING) { | |
338 | spin_lock_bh(&nf_conntrack_expect_lock); | |
339 | hlist_for_each_entry(pair_exp, &help->expectations, lnode) { | |
340 | if (pair_exp->tuple.src.l3num == nf_ct_l3num(ct) && | |
341 | pair_exp->tuple.dst.protonum == ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.protonum && | |
342 | nf_inet_addr_cmp(&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.u3, &pair_exp->saved_addr) && | |
343 | ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.u.all == pair_exp->saved_proto.all) { | |
344 | range.flags = (NF_NAT_RANGE_MAP_IPS | NF_NAT_RANGE_PROTO_SPECIFIED); | |
345 | range.min_proto.all = range.max_proto.all = pair_exp->tuple.dst.u.all; | |
346 | range.min_addr = range.max_addr = pair_exp->tuple.dst.u3; | |
347 | range_set_for_snat = 1; | |
348 | break; | |
349 | } | |
350 | } | |
351 | spin_unlock_bh(&nf_conntrack_expect_lock); | |
352 | } | |
353 | ||
354 | /* When no paired expectation has been found, change src to | |
355 | * where master sends to, but only if the connection actually came | |
356 | * from the same source. | |
357 | */ | |
358 | if (!range_set_for_snat && | |
359 | nf_inet_addr_cmp(&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.u3, | |
9a664821 | 360 | &ct->master->tuplehash[exp->dir].tuple.src.u3)) { |
cbc9f2f4 | 361 | range.flags = NF_NAT_RANGE_MAP_IPS; |
c7232c99 PM |
362 | range.min_addr = range.max_addr |
363 | = ct->master->tuplehash[!exp->dir].tuple.dst.u3; | |
5ddded90 | 364 | range_set_for_snat = 1; |
0f32a40f | 365 | } |
5ddded90 AN |
366 | |
367 | /* Perform SRC manip. */ | |
368 | if (range_set_for_snat) | |
369 | nf_nat_setup_info(ct, &range, NF_NAT_MANIP_SRC); | |
0f32a40f PM |
370 | } |
371 | ||
9a664821 | 372 | static unsigned int nf_nat_sip_expect(struct sk_buff *skb, unsigned int protoff, |
051966c0 | 373 | unsigned int dataoff, |
0f32a40f PM |
374 | const char **dptr, unsigned int *datalen, |
375 | struct nf_conntrack_expect *exp, | |
376 | unsigned int matchoff, | |
377 | unsigned int matchlen) | |
378 | { | |
379 | enum ip_conntrack_info ctinfo; | |
380 | struct nf_conn *ct = nf_ct_get(skb, &ctinfo); | |
381 | enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo); | |
7266507d | 382 | struct nf_ct_sip_master *ct_sip_info = nfct_help_data(ct); |
9a664821 | 383 | union nf_inet_addr newaddr; |
0f32a40f | 384 | u_int16_t port; |
7266507d | 385 | __be16 srcport; |
9a664821 | 386 | char buffer[INET6_ADDRSTRLEN + sizeof("[]:nnnnn")]; |
95c96174 | 387 | unsigned int buflen; |
0f32a40f PM |
388 | |
389 | /* Connection will come from reply */ | |
9a664821 PM |
390 | if (nf_inet_addr_cmp(&ct->tuplehash[dir].tuple.src.u3, |
391 | &ct->tuplehash[!dir].tuple.dst.u3)) | |
392 | newaddr = exp->tuple.dst.u3; | |
0f32a40f | 393 | else |
9a664821 | 394 | newaddr = ct->tuplehash[!dir].tuple.dst.u3; |
0f32a40f PM |
395 | |
396 | /* If the signalling port matches the connection's source port in the | |
397 | * original direction, try to use the destination port in the opposite | |
398 | * direction. */ | |
7266507d KC |
399 | srcport = ct_sip_info->forced_dport ? : |
400 | ct->tuplehash[dir].tuple.src.u.udp.port; | |
401 | if (exp->tuple.dst.u.udp.port == srcport) | |
0f32a40f PM |
402 | port = ntohs(ct->tuplehash[!dir].tuple.dst.u.udp.port); |
403 | else | |
404 | port = ntohs(exp->tuple.dst.u.udp.port); | |
405 | ||
c7232c99 | 406 | exp->saved_addr = exp->tuple.dst.u3; |
9a664821 | 407 | exp->tuple.dst.u3 = newaddr; |
0f32a40f PM |
408 | exp->saved_proto.udp.port = exp->tuple.dst.u.udp.port; |
409 | exp->dir = !dir; | |
9a664821 | 410 | exp->expectfn = nf_nat_sip_expected; |
0f32a40f PM |
411 | |
412 | for (; port != 0; port++) { | |
5b92b61f PNA |
413 | int ret; |
414 | ||
0f32a40f | 415 | exp->tuple.dst.u.udp.port = htons(port); |
5b92b61f PNA |
416 | ret = nf_ct_expect_related(exp); |
417 | if (ret == 0) | |
418 | break; | |
419 | else if (ret != -EBUSY) { | |
420 | port = 0; | |
0f32a40f | 421 | break; |
5b92b61f | 422 | } |
0f32a40f PM |
423 | } |
424 | ||
b20ab9cc PNA |
425 | if (port == 0) { |
426 | nf_ct_helper_log(skb, ct, "all ports in use for SIP"); | |
0f32a40f | 427 | return NF_DROP; |
b20ab9cc | 428 | } |
0f32a40f | 429 | |
9a664821 | 430 | if (!nf_inet_addr_cmp(&exp->tuple.dst.u3, &exp->saved_addr) || |
0f32a40f | 431 | exp->tuple.dst.u.udp.port != exp->saved_proto.udp.port) { |
9a664821 | 432 | buflen = sip_sprintf_addr_port(ct, buffer, &newaddr, port); |
051966c0 | 433 | if (!mangle_packet(skb, protoff, dataoff, dptr, datalen, |
b20ab9cc PNA |
434 | matchoff, matchlen, buffer, buflen)) { |
435 | nf_ct_helper_log(skb, ct, "cannot mangle packet"); | |
0f32a40f | 436 | goto err; |
b20ab9cc | 437 | } |
0f32a40f PM |
438 | } |
439 | return NF_ACCEPT; | |
440 | ||
441 | err: | |
442 | nf_ct_unexpect_related(exp); | |
443 | return NF_DROP; | |
444 | } | |
445 | ||
051966c0 PM |
446 | static int mangle_content_len(struct sk_buff *skb, unsigned int protoff, |
447 | unsigned int dataoff, | |
3e9b4600 | 448 | const char **dptr, unsigned int *datalen) |
9fafcd7b | 449 | { |
212440a7 PM |
450 | enum ip_conntrack_info ctinfo; |
451 | struct nf_conn *ct = nf_ct_get(skb, &ctinfo); | |
3e9b4600 PM |
452 | unsigned int matchoff, matchlen; |
453 | char buffer[sizeof("65536")]; | |
454 | int buflen, c_len; | |
9fafcd7b | 455 | |
3e9b4600 PM |
456 | /* Get actual SDP length */ |
457 | if (ct_sip_get_sdp_header(ct, *dptr, 0, *datalen, | |
458 | SDP_HDR_VERSION, SDP_HDR_UNSPEC, | |
459 | &matchoff, &matchlen) <= 0) | |
460 | return 0; | |
461 | c_len = *datalen - matchoff + strlen("v="); | |
462 | ||
463 | /* Now, update SDP length */ | |
ea45f12a PM |
464 | if (ct_sip_get_header(ct, *dptr, 0, *datalen, SIP_HDR_CONTENT_LENGTH, |
465 | &matchoff, &matchlen) <= 0) | |
9fafcd7b PM |
466 | return 0; |
467 | ||
3e9b4600 | 468 | buflen = sprintf(buffer, "%u", c_len); |
051966c0 PM |
469 | return mangle_packet(skb, protoff, dataoff, dptr, datalen, |
470 | matchoff, matchlen, buffer, buflen); | |
9fafcd7b PM |
471 | } |
472 | ||
051966c0 PM |
473 | static int mangle_sdp_packet(struct sk_buff *skb, unsigned int protoff, |
474 | unsigned int dataoff, | |
3b6b9fab PM |
475 | const char **dptr, unsigned int *datalen, |
476 | unsigned int sdpoff, | |
c71529e4 HX |
477 | enum sdp_header_types type, |
478 | enum sdp_header_types term, | |
479 | char *buffer, int buflen) | |
9fafcd7b | 480 | { |
212440a7 PM |
481 | enum ip_conntrack_info ctinfo; |
482 | struct nf_conn *ct = nf_ct_get(skb, &ctinfo); | |
3e9b4600 | 483 | unsigned int matchlen, matchoff; |
9fafcd7b | 484 | |
3b6b9fab | 485 | if (ct_sip_get_sdp_header(ct, *dptr, sdpoff, *datalen, type, term, |
3e9b4600 | 486 | &matchoff, &matchlen) <= 0) |
c71529e4 | 487 | return -ENOENT; |
051966c0 PM |
488 | return mangle_packet(skb, protoff, dataoff, dptr, datalen, |
489 | matchoff, matchlen, buffer, buflen) ? 0 : -EINVAL; | |
9fafcd7b PM |
490 | } |
491 | ||
9a664821 | 492 | static unsigned int nf_nat_sdp_addr(struct sk_buff *skb, unsigned int protoff, |
051966c0 | 493 | unsigned int dataoff, |
3b6b9fab PM |
494 | const char **dptr, unsigned int *datalen, |
495 | unsigned int sdpoff, | |
4ab9e64e PM |
496 | enum sdp_header_types type, |
497 | enum sdp_header_types term, | |
498 | const union nf_inet_addr *addr) | |
9fafcd7b | 499 | { |
9a664821 PM |
500 | enum ip_conntrack_info ctinfo; |
501 | struct nf_conn *ct = nf_ct_get(skb, &ctinfo); | |
502 | char buffer[INET6_ADDRSTRLEN]; | |
4ab9e64e | 503 | unsigned int buflen; |
9fafcd7b | 504 | |
9a664821 | 505 | buflen = sip_sprintf_addr(ct, buffer, addr, false); |
051966c0 PM |
506 | if (mangle_sdp_packet(skb, protoff, dataoff, dptr, datalen, |
507 | sdpoff, type, term, buffer, buflen)) | |
9fafcd7b PM |
508 | return 0; |
509 | ||
051966c0 | 510 | return mangle_content_len(skb, protoff, dataoff, dptr, datalen); |
4ab9e64e PM |
511 | } |
512 | ||
9a664821 | 513 | static unsigned int nf_nat_sdp_port(struct sk_buff *skb, unsigned int protoff, |
051966c0 | 514 | unsigned int dataoff, |
3b6b9fab | 515 | const char **dptr, unsigned int *datalen, |
4ab9e64e PM |
516 | unsigned int matchoff, |
517 | unsigned int matchlen, | |
518 | u_int16_t port) | |
519 | { | |
520 | char buffer[sizeof("nnnnn")]; | |
521 | unsigned int buflen; | |
522 | ||
523 | buflen = sprintf(buffer, "%u", port); | |
051966c0 PM |
524 | if (!mangle_packet(skb, protoff, dataoff, dptr, datalen, |
525 | matchoff, matchlen, buffer, buflen)) | |
9fafcd7b PM |
526 | return 0; |
527 | ||
051966c0 | 528 | return mangle_content_len(skb, protoff, dataoff, dptr, datalen); |
4ab9e64e PM |
529 | } |
530 | ||
9a664821 | 531 | static unsigned int nf_nat_sdp_session(struct sk_buff *skb, unsigned int protoff, |
051966c0 | 532 | unsigned int dataoff, |
3b6b9fab PM |
533 | const char **dptr, unsigned int *datalen, |
534 | unsigned int sdpoff, | |
4ab9e64e PM |
535 | const union nf_inet_addr *addr) |
536 | { | |
9a664821 PM |
537 | enum ip_conntrack_info ctinfo; |
538 | struct nf_conn *ct = nf_ct_get(skb, &ctinfo); | |
539 | char buffer[INET6_ADDRSTRLEN]; | |
4ab9e64e PM |
540 | unsigned int buflen; |
541 | ||
542 | /* Mangle session description owner and contact addresses */ | |
9a664821 | 543 | buflen = sip_sprintf_addr(ct, buffer, addr, false); |
051966c0 | 544 | if (mangle_sdp_packet(skb, protoff, dataoff, dptr, datalen, sdpoff, |
9a664821 | 545 | SDP_HDR_OWNER, SDP_HDR_MEDIA, buffer, buflen)) |
4ab9e64e PM |
546 | return 0; |
547 | ||
051966c0 | 548 | switch (mangle_sdp_packet(skb, protoff, dataoff, dptr, datalen, sdpoff, |
9a664821 | 549 | SDP_HDR_CONNECTION, SDP_HDR_MEDIA, |
c71529e4 HX |
550 | buffer, buflen)) { |
551 | case 0: | |
552 | /* | |
553 | * RFC 2327: | |
554 | * | |
555 | * Session description | |
556 | * | |
557 | * c=* (connection information - not required if included in all media) | |
558 | */ | |
559 | case -ENOENT: | |
560 | break; | |
561 | default: | |
9fafcd7b | 562 | return 0; |
c71529e4 | 563 | } |
9fafcd7b | 564 | |
051966c0 | 565 | return mangle_content_len(skb, protoff, dataoff, dptr, datalen); |
9fafcd7b PM |
566 | } |
567 | ||
568 | /* So, this packet has hit the connection tracking matching code. | |
569 | Mangle it, and change the expectation to match the new version. */ | |
9a664821 | 570 | static unsigned int nf_nat_sdp_media(struct sk_buff *skb, unsigned int protoff, |
051966c0 | 571 | unsigned int dataoff, |
3b6b9fab | 572 | const char **dptr, unsigned int *datalen, |
4ab9e64e PM |
573 | struct nf_conntrack_expect *rtp_exp, |
574 | struct nf_conntrack_expect *rtcp_exp, | |
575 | unsigned int mediaoff, | |
576 | unsigned int medialen, | |
577 | union nf_inet_addr *rtp_addr) | |
9fafcd7b | 578 | { |
212440a7 PM |
579 | enum ip_conntrack_info ctinfo; |
580 | struct nf_conn *ct = nf_ct_get(skb, &ctinfo); | |
9fafcd7b | 581 | enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo); |
9fafcd7b PM |
582 | u_int16_t port; |
583 | ||
9fafcd7b | 584 | /* Connection will come from reply */ |
9a664821 PM |
585 | if (nf_inet_addr_cmp(&ct->tuplehash[dir].tuple.src.u3, |
586 | &ct->tuplehash[!dir].tuple.dst.u3)) | |
587 | *rtp_addr = rtp_exp->tuple.dst.u3; | |
f4a607bf | 588 | else |
9a664821 | 589 | *rtp_addr = ct->tuplehash[!dir].tuple.dst.u3; |
9fafcd7b | 590 | |
c7232c99 | 591 | rtp_exp->saved_addr = rtp_exp->tuple.dst.u3; |
9a664821 | 592 | rtp_exp->tuple.dst.u3 = *rtp_addr; |
a9c1d359 PM |
593 | rtp_exp->saved_proto.udp.port = rtp_exp->tuple.dst.u.udp.port; |
594 | rtp_exp->dir = !dir; | |
9a664821 | 595 | rtp_exp->expectfn = nf_nat_sip_expected; |
a9c1d359 | 596 | |
c7232c99 | 597 | rtcp_exp->saved_addr = rtcp_exp->tuple.dst.u3; |
9a664821 | 598 | rtcp_exp->tuple.dst.u3 = *rtp_addr; |
a9c1d359 PM |
599 | rtcp_exp->saved_proto.udp.port = rtcp_exp->tuple.dst.u.udp.port; |
600 | rtcp_exp->dir = !dir; | |
9a664821 | 601 | rtcp_exp->expectfn = nf_nat_sip_expected; |
a9c1d359 PM |
602 | |
603 | /* Try to get same pair of ports: if not, try to change them. */ | |
604 | for (port = ntohs(rtp_exp->tuple.dst.u.udp.port); | |
605 | port != 0; port += 2) { | |
5b92b61f PNA |
606 | int ret; |
607 | ||
a9c1d359 | 608 | rtp_exp->tuple.dst.u.udp.port = htons(port); |
5b92b61f PNA |
609 | ret = nf_ct_expect_related(rtp_exp); |
610 | if (ret == -EBUSY) | |
a9c1d359 | 611 | continue; |
5b92b61f PNA |
612 | else if (ret < 0) { |
613 | port = 0; | |
614 | break; | |
615 | } | |
a9c1d359 | 616 | rtcp_exp->tuple.dst.u.udp.port = htons(port + 1); |
5b92b61f PNA |
617 | ret = nf_ct_expect_related(rtcp_exp); |
618 | if (ret == 0) | |
9fafcd7b | 619 | break; |
3f509c68 PNA |
620 | else if (ret == -EBUSY) { |
621 | nf_ct_unexpect_related(rtp_exp); | |
622 | continue; | |
623 | } else if (ret < 0) { | |
5b92b61f PNA |
624 | nf_ct_unexpect_related(rtp_exp); |
625 | port = 0; | |
626 | break; | |
627 | } | |
9fafcd7b PM |
628 | } |
629 | ||
b20ab9cc PNA |
630 | if (port == 0) { |
631 | nf_ct_helper_log(skb, ct, "all ports in use for SDP media"); | |
4ab9e64e | 632 | goto err1; |
b20ab9cc | 633 | } |
4ab9e64e PM |
634 | |
635 | /* Update media port. */ | |
636 | if (rtp_exp->tuple.dst.u.udp.port != rtp_exp->saved_proto.udp.port && | |
9a664821 | 637 | !nf_nat_sdp_port(skb, protoff, dataoff, dptr, datalen, |
b20ab9cc PNA |
638 | mediaoff, medialen, port)) { |
639 | nf_ct_helper_log(skb, ct, "cannot mangle SDP message"); | |
4ab9e64e | 640 | goto err2; |
b20ab9cc | 641 | } |
9fafcd7b | 642 | |
9fafcd7b | 643 | return NF_ACCEPT; |
4ab9e64e PM |
644 | |
645 | err2: | |
646 | nf_ct_unexpect_related(rtp_exp); | |
647 | nf_ct_unexpect_related(rtcp_exp); | |
648 | err1: | |
649 | return NF_DROP; | |
9fafcd7b PM |
650 | } |
651 | ||
544d5c7d | 652 | static struct nf_ct_helper_expectfn sip_nat = { |
9a664821 PM |
653 | .name = "sip", |
654 | .expectfn = nf_nat_sip_expected, | |
544d5c7d PNA |
655 | }; |
656 | ||
9fafcd7b PM |
657 | static void __exit nf_nat_sip_fini(void) |
658 | { | |
180cf72f | 659 | RCU_INIT_POINTER(nf_nat_sip_hooks, NULL); |
660 | ||
544d5c7d | 661 | nf_ct_helper_expectfn_unregister(&sip_nat); |
9fafcd7b PM |
662 | synchronize_rcu(); |
663 | } | |
664 | ||
180cf72f | 665 | static const struct nf_nat_sip_hooks sip_hooks = { |
666 | .msg = nf_nat_sip, | |
667 | .seq_adjust = nf_nat_sip_seq_adjust, | |
668 | .expect = nf_nat_sip_expect, | |
669 | .sdp_addr = nf_nat_sdp_addr, | |
670 | .sdp_port = nf_nat_sdp_port, | |
671 | .sdp_session = nf_nat_sdp_session, | |
672 | .sdp_media = nf_nat_sdp_media, | |
673 | }; | |
674 | ||
9fafcd7b PM |
675 | static int __init nf_nat_sip_init(void) |
676 | { | |
180cf72f | 677 | BUG_ON(nf_nat_sip_hooks != NULL); |
678 | RCU_INIT_POINTER(nf_nat_sip_hooks, &sip_hooks); | |
544d5c7d | 679 | nf_ct_helper_expectfn_register(&sip_nat); |
9fafcd7b PM |
680 | return 0; |
681 | } | |
682 | ||
683 | module_init(nf_nat_sip_init); | |
684 | module_exit(nf_nat_sip_fini); |