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