1 /* SIP extension for NAT alteration.
3 * (C) 2005 by Christian Hentschel <chentschel@arnet.com.ar>
4 * based on RR's ip_nat_ftp.c and other modules.
5 * (C) 2007 United Security Providers
6 * (C) 2007, 2008 Patrick McHardy <kaber@trash.net>
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.
13 #include <linux/module.h>
14 #include <linux/skbuff.h>
17 #include <linux/udp.h>
18 #include <linux/tcp.h>
20 #include <net/netfilter/nf_nat.h>
21 #include <net/netfilter/nf_nat_helper.h>
22 #include <net/netfilter/nf_nat_rule.h>
23 #include <net/netfilter/nf_conntrack_helper.h>
24 #include <net/netfilter/nf_conntrack_expect.h>
25 #include <linux/netfilter/nf_conntrack_sip.h>
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");
33 static unsigned int mangle_packet(struct sk_buff
*skb
, unsigned int dataoff
,
34 const char **dptr
, unsigned int *datalen
,
35 unsigned int matchoff
, unsigned int matchlen
,
36 const char *buffer
, unsigned int buflen
)
38 enum ip_conntrack_info ctinfo
;
39 struct nf_conn
*ct
= nf_ct_get(skb
, &ctinfo
);
43 if (nf_ct_protonum(ct
) == IPPROTO_TCP
) {
44 th
= (struct tcphdr
*)(skb
->data
+ ip_hdrlen(skb
));
45 baseoff
= ip_hdrlen(skb
) + th
->doff
* 4;
46 matchoff
+= dataoff
- baseoff
;
48 if (!__nf_nat_mangle_tcp_packet(skb
, ct
, ctinfo
,
50 buffer
, buflen
, false))
53 baseoff
= ip_hdrlen(skb
) + sizeof(struct udphdr
);
54 matchoff
+= dataoff
- baseoff
;
56 if (!nf_nat_mangle_udp_packet(skb
, ct
, ctinfo
,
62 /* Reload data pointer and adjust datalen value */
63 *dptr
= skb
->data
+ dataoff
;
64 *datalen
+= buflen
- matchlen
;
68 static int map_addr(struct sk_buff
*skb
, unsigned int dataoff
,
69 const char **dptr
, unsigned int *datalen
,
70 unsigned int matchoff
, unsigned int matchlen
,
71 union nf_inet_addr
*addr
, __be16 port
)
73 enum ip_conntrack_info ctinfo
;
74 struct nf_conn
*ct
= nf_ct_get(skb
, &ctinfo
);
75 enum ip_conntrack_dir dir
= CTINFO2DIR(ctinfo
);
76 char buffer
[sizeof("nnn.nnn.nnn.nnn:nnnnn")];
81 if (ct
->tuplehash
[dir
].tuple
.src
.u3
.ip
== addr
->ip
&&
82 ct
->tuplehash
[dir
].tuple
.src
.u
.udp
.port
== port
) {
83 newaddr
= ct
->tuplehash
[!dir
].tuple
.dst
.u3
.ip
;
84 newport
= ct
->tuplehash
[!dir
].tuple
.dst
.u
.udp
.port
;
85 } else if (ct
->tuplehash
[dir
].tuple
.dst
.u3
.ip
== addr
->ip
&&
86 ct
->tuplehash
[dir
].tuple
.dst
.u
.udp
.port
== port
) {
87 newaddr
= ct
->tuplehash
[!dir
].tuple
.src
.u3
.ip
;
88 newport
= ct
->tuplehash
[!dir
].tuple
.src
.u
.udp
.port
;
92 if (newaddr
== addr
->ip
&& newport
== port
)
95 buflen
= sprintf(buffer
, "%pI4:%u", &newaddr
, ntohs(newport
));
97 return mangle_packet(skb
, dataoff
, dptr
, datalen
, matchoff
, matchlen
,
101 static int map_sip_addr(struct sk_buff
*skb
, unsigned int dataoff
,
102 const char **dptr
, unsigned int *datalen
,
103 enum sip_header_types type
)
105 enum ip_conntrack_info ctinfo
;
106 struct nf_conn
*ct
= nf_ct_get(skb
, &ctinfo
);
107 unsigned int matchlen
, matchoff
;
108 union nf_inet_addr addr
;
111 if (ct_sip_parse_header_uri(ct
, *dptr
, NULL
, *datalen
, type
, NULL
,
112 &matchoff
, &matchlen
, &addr
, &port
) <= 0)
114 return map_addr(skb
, dataoff
, dptr
, datalen
, matchoff
, matchlen
,
118 static unsigned int ip_nat_sip(struct sk_buff
*skb
, unsigned int dataoff
,
119 const char **dptr
, unsigned int *datalen
)
121 enum ip_conntrack_info ctinfo
;
122 struct nf_conn
*ct
= nf_ct_get(skb
, &ctinfo
);
123 enum ip_conntrack_dir dir
= CTINFO2DIR(ctinfo
);
124 unsigned int coff
, matchoff
, matchlen
;
125 enum sip_header_types hdr
;
126 union nf_inet_addr addr
;
128 int request
, in_header
;
130 /* Basic rules: requests and responses. */
131 if (strnicmp(*dptr
, "SIP/2.0", strlen("SIP/2.0")) != 0) {
132 if (ct_sip_parse_request(ct
, *dptr
, *datalen
,
133 &matchoff
, &matchlen
,
135 !map_addr(skb
, dataoff
, dptr
, datalen
, matchoff
, matchlen
,
142 if (nf_ct_protonum(ct
) == IPPROTO_TCP
)
143 hdr
= SIP_HDR_VIA_TCP
;
145 hdr
= SIP_HDR_VIA_UDP
;
147 /* Translate topmost Via header and parameters */
148 if (ct_sip_parse_header_uri(ct
, *dptr
, NULL
, *datalen
,
149 hdr
, NULL
, &matchoff
, &matchlen
,
151 unsigned int olen
, matchend
, poff
, plen
, buflen
, n
;
152 char buffer
[sizeof("nnn.nnn.nnn.nnn:nnnnn")];
154 /* We're only interested in headers related to this
157 if (addr
.ip
!= ct
->tuplehash
[dir
].tuple
.src
.u3
.ip
||
158 port
!= ct
->tuplehash
[dir
].tuple
.src
.u
.udp
.port
)
161 if (addr
.ip
!= ct
->tuplehash
[dir
].tuple
.dst
.u3
.ip
||
162 port
!= ct
->tuplehash
[dir
].tuple
.dst
.u
.udp
.port
)
167 if (!map_addr(skb
, dataoff
, dptr
, datalen
, matchoff
, matchlen
,
171 matchend
= matchoff
+ matchlen
+ *datalen
- olen
;
173 /* The maddr= parameter (RFC 2361) specifies where to send
175 if (ct_sip_parse_address_param(ct
, *dptr
, matchend
, *datalen
,
176 "maddr=", &poff
, &plen
,
178 addr
.ip
== ct
->tuplehash
[dir
].tuple
.src
.u3
.ip
&&
179 addr
.ip
!= ct
->tuplehash
[!dir
].tuple
.dst
.u3
.ip
) {
180 buflen
= sprintf(buffer
, "%pI4",
181 &ct
->tuplehash
[!dir
].tuple
.dst
.u3
.ip
);
182 if (!mangle_packet(skb
, dataoff
, dptr
, datalen
,
183 poff
, plen
, buffer
, buflen
))
187 /* The received= parameter (RFC 2361) contains the address
188 * from which the server received the request. */
189 if (ct_sip_parse_address_param(ct
, *dptr
, matchend
, *datalen
,
190 "received=", &poff
, &plen
,
192 addr
.ip
== ct
->tuplehash
[dir
].tuple
.dst
.u3
.ip
&&
193 addr
.ip
!= ct
->tuplehash
[!dir
].tuple
.src
.u3
.ip
) {
194 buflen
= sprintf(buffer
, "%pI4",
195 &ct
->tuplehash
[!dir
].tuple
.src
.u3
.ip
);
196 if (!mangle_packet(skb
, dataoff
, dptr
, datalen
,
197 poff
, plen
, buffer
, buflen
))
201 /* The rport= parameter (RFC 3581) contains the port number
202 * from which the server received the request. */
203 if (ct_sip_parse_numerical_param(ct
, *dptr
, matchend
, *datalen
,
204 "rport=", &poff
, &plen
,
206 htons(n
) == ct
->tuplehash
[dir
].tuple
.dst
.u
.udp
.port
&&
207 htons(n
) != ct
->tuplehash
[!dir
].tuple
.src
.u
.udp
.port
) {
208 __be16 p
= ct
->tuplehash
[!dir
].tuple
.src
.u
.udp
.port
;
209 buflen
= sprintf(buffer
, "%u", ntohs(p
));
210 if (!mangle_packet(skb
, dataoff
, dptr
, datalen
,
211 poff
, plen
, buffer
, buflen
))
217 /* Translate Contact headers */
220 while (ct_sip_parse_header_uri(ct
, *dptr
, &coff
, *datalen
,
221 SIP_HDR_CONTACT
, &in_header
,
222 &matchoff
, &matchlen
,
224 if (!map_addr(skb
, dataoff
, dptr
, datalen
, matchoff
, matchlen
,
229 if (!map_sip_addr(skb
, dataoff
, dptr
, datalen
, SIP_HDR_FROM
) ||
230 !map_sip_addr(skb
, dataoff
, dptr
, datalen
, SIP_HDR_TO
))
236 static void ip_nat_sip_seq_adjust(struct sk_buff
*skb
, s16 off
)
238 enum ip_conntrack_info ctinfo
;
239 struct nf_conn
*ct
= nf_ct_get(skb
, &ctinfo
);
240 const struct tcphdr
*th
;
242 if (nf_ct_protonum(ct
) != IPPROTO_TCP
|| off
== 0)
245 th
= (struct tcphdr
*)(skb
->data
+ ip_hdrlen(skb
));
246 nf_nat_set_seq_adjust(ct
, ctinfo
, th
->seq
, off
);
249 /* Handles expected signalling connections and media streams */
250 static void ip_nat_sip_expected(struct nf_conn
*ct
,
251 struct nf_conntrack_expect
*exp
)
253 struct nf_nat_ipv4_range range
;
255 /* This must be a fresh one. */
256 BUG_ON(ct
->status
& IPS_NAT_DONE_MASK
);
258 /* For DST manip, map port here to where it's expected. */
259 range
.flags
= (NF_NAT_RANGE_MAP_IPS
| NF_NAT_RANGE_PROTO_SPECIFIED
);
260 range
.min
= range
.max
= exp
->saved_proto
;
261 range
.min_ip
= range
.max_ip
= exp
->saved_ip
;
262 nf_nat_setup_info(ct
, &range
, NF_NAT_MANIP_DST
);
264 /* Change src to where master sends to, but only if the connection
265 * actually came from the same source. */
266 if (ct
->tuplehash
[IP_CT_DIR_ORIGINAL
].tuple
.src
.u3
.ip
==
267 ct
->master
->tuplehash
[exp
->dir
].tuple
.src
.u3
.ip
) {
268 range
.flags
= NF_NAT_RANGE_MAP_IPS
;
269 range
.min_ip
= range
.max_ip
270 = ct
->master
->tuplehash
[!exp
->dir
].tuple
.dst
.u3
.ip
;
271 nf_nat_setup_info(ct
, &range
, NF_NAT_MANIP_SRC
);
275 static unsigned int ip_nat_sip_expect(struct sk_buff
*skb
, unsigned int dataoff
,
276 const char **dptr
, unsigned int *datalen
,
277 struct nf_conntrack_expect
*exp
,
278 unsigned int matchoff
,
279 unsigned int matchlen
)
281 enum ip_conntrack_info ctinfo
;
282 struct nf_conn
*ct
= nf_ct_get(skb
, &ctinfo
);
283 enum ip_conntrack_dir dir
= CTINFO2DIR(ctinfo
);
286 char buffer
[sizeof("nnn.nnn.nnn.nnn:nnnnn")];
289 /* Connection will come from reply */
290 if (ct
->tuplehash
[dir
].tuple
.src
.u3
.ip
== ct
->tuplehash
[!dir
].tuple
.dst
.u3
.ip
)
291 newip
= exp
->tuple
.dst
.u3
.ip
;
293 newip
= ct
->tuplehash
[!dir
].tuple
.dst
.u3
.ip
;
295 /* If the signalling port matches the connection's source port in the
296 * original direction, try to use the destination port in the opposite
298 if (exp
->tuple
.dst
.u
.udp
.port
==
299 ct
->tuplehash
[dir
].tuple
.src
.u
.udp
.port
)
300 port
= ntohs(ct
->tuplehash
[!dir
].tuple
.dst
.u
.udp
.port
);
302 port
= ntohs(exp
->tuple
.dst
.u
.udp
.port
);
304 exp
->saved_ip
= exp
->tuple
.dst
.u3
.ip
;
305 exp
->tuple
.dst
.u3
.ip
= newip
;
306 exp
->saved_proto
.udp
.port
= exp
->tuple
.dst
.u
.udp
.port
;
308 exp
->expectfn
= ip_nat_sip_expected
;
310 for (; port
!= 0; port
++) {
313 exp
->tuple
.dst
.u
.udp
.port
= htons(port
);
314 ret
= nf_ct_expect_related(exp
);
317 else if (ret
!= -EBUSY
) {
326 if (exp
->tuple
.dst
.u3
.ip
!= exp
->saved_ip
||
327 exp
->tuple
.dst
.u
.udp
.port
!= exp
->saved_proto
.udp
.port
) {
328 buflen
= sprintf(buffer
, "%pI4:%u", &newip
, port
);
329 if (!mangle_packet(skb
, dataoff
, dptr
, datalen
,
330 matchoff
, matchlen
, buffer
, buflen
))
336 nf_ct_unexpect_related(exp
);
340 static int mangle_content_len(struct sk_buff
*skb
, unsigned int dataoff
,
341 const char **dptr
, unsigned int *datalen
)
343 enum ip_conntrack_info ctinfo
;
344 struct nf_conn
*ct
= nf_ct_get(skb
, &ctinfo
);
345 unsigned int matchoff
, matchlen
;
346 char buffer
[sizeof("65536")];
349 /* Get actual SDP length */
350 if (ct_sip_get_sdp_header(ct
, *dptr
, 0, *datalen
,
351 SDP_HDR_VERSION
, SDP_HDR_UNSPEC
,
352 &matchoff
, &matchlen
) <= 0)
354 c_len
= *datalen
- matchoff
+ strlen("v=");
356 /* Now, update SDP length */
357 if (ct_sip_get_header(ct
, *dptr
, 0, *datalen
, SIP_HDR_CONTENT_LENGTH
,
358 &matchoff
, &matchlen
) <= 0)
361 buflen
= sprintf(buffer
, "%u", c_len
);
362 return mangle_packet(skb
, dataoff
, dptr
, datalen
, matchoff
, matchlen
,
366 static int mangle_sdp_packet(struct sk_buff
*skb
, unsigned int dataoff
,
367 const char **dptr
, unsigned int *datalen
,
369 enum sdp_header_types type
,
370 enum sdp_header_types term
,
371 char *buffer
, int buflen
)
373 enum ip_conntrack_info ctinfo
;
374 struct nf_conn
*ct
= nf_ct_get(skb
, &ctinfo
);
375 unsigned int matchlen
, matchoff
;
377 if (ct_sip_get_sdp_header(ct
, *dptr
, sdpoff
, *datalen
, type
, term
,
378 &matchoff
, &matchlen
) <= 0)
380 return mangle_packet(skb
, dataoff
, dptr
, datalen
, matchoff
, matchlen
,
381 buffer
, buflen
) ? 0 : -EINVAL
;
384 static unsigned int ip_nat_sdp_addr(struct sk_buff
*skb
, unsigned int dataoff
,
385 const char **dptr
, unsigned int *datalen
,
387 enum sdp_header_types type
,
388 enum sdp_header_types term
,
389 const union nf_inet_addr
*addr
)
391 char buffer
[sizeof("nnn.nnn.nnn.nnn")];
394 buflen
= sprintf(buffer
, "%pI4", &addr
->ip
);
395 if (mangle_sdp_packet(skb
, dataoff
, dptr
, datalen
, sdpoff
, type
, term
,
399 return mangle_content_len(skb
, dataoff
, dptr
, datalen
);
402 static unsigned int ip_nat_sdp_port(struct sk_buff
*skb
, unsigned int dataoff
,
403 const char **dptr
, unsigned int *datalen
,
404 unsigned int matchoff
,
405 unsigned int matchlen
,
408 char buffer
[sizeof("nnnnn")];
411 buflen
= sprintf(buffer
, "%u", port
);
412 if (!mangle_packet(skb
, dataoff
, dptr
, datalen
, matchoff
, matchlen
,
416 return mangle_content_len(skb
, dataoff
, dptr
, datalen
);
419 static unsigned int ip_nat_sdp_session(struct sk_buff
*skb
, unsigned int dataoff
,
420 const char **dptr
, unsigned int *datalen
,
422 const union nf_inet_addr
*addr
)
424 char buffer
[sizeof("nnn.nnn.nnn.nnn")];
427 /* Mangle session description owner and contact addresses */
428 buflen
= sprintf(buffer
, "%pI4", &addr
->ip
);
429 if (mangle_sdp_packet(skb
, dataoff
, dptr
, datalen
, sdpoff
,
430 SDP_HDR_OWNER_IP4
, SDP_HDR_MEDIA
,
434 switch (mangle_sdp_packet(skb
, dataoff
, dptr
, datalen
, sdpoff
,
435 SDP_HDR_CONNECTION_IP4
, SDP_HDR_MEDIA
,
441 * Session description
443 * c=* (connection information - not required if included in all media)
451 return mangle_content_len(skb
, dataoff
, dptr
, datalen
);
454 /* So, this packet has hit the connection tracking matching code.
455 Mangle it, and change the expectation to match the new version. */
456 static unsigned int ip_nat_sdp_media(struct sk_buff
*skb
, unsigned int dataoff
,
457 const char **dptr
, unsigned int *datalen
,
458 struct nf_conntrack_expect
*rtp_exp
,
459 struct nf_conntrack_expect
*rtcp_exp
,
460 unsigned int mediaoff
,
461 unsigned int medialen
,
462 union nf_inet_addr
*rtp_addr
)
464 enum ip_conntrack_info ctinfo
;
465 struct nf_conn
*ct
= nf_ct_get(skb
, &ctinfo
);
466 enum ip_conntrack_dir dir
= CTINFO2DIR(ctinfo
);
469 /* Connection will come from reply */
470 if (ct
->tuplehash
[dir
].tuple
.src
.u3
.ip
==
471 ct
->tuplehash
[!dir
].tuple
.dst
.u3
.ip
)
472 rtp_addr
->ip
= rtp_exp
->tuple
.dst
.u3
.ip
;
474 rtp_addr
->ip
= ct
->tuplehash
[!dir
].tuple
.dst
.u3
.ip
;
476 rtp_exp
->saved_ip
= rtp_exp
->tuple
.dst
.u3
.ip
;
477 rtp_exp
->tuple
.dst
.u3
.ip
= rtp_addr
->ip
;
478 rtp_exp
->saved_proto
.udp
.port
= rtp_exp
->tuple
.dst
.u
.udp
.port
;
480 rtp_exp
->expectfn
= ip_nat_sip_expected
;
482 rtcp_exp
->saved_ip
= rtcp_exp
->tuple
.dst
.u3
.ip
;
483 rtcp_exp
->tuple
.dst
.u3
.ip
= rtp_addr
->ip
;
484 rtcp_exp
->saved_proto
.udp
.port
= rtcp_exp
->tuple
.dst
.u
.udp
.port
;
485 rtcp_exp
->dir
= !dir
;
486 rtcp_exp
->expectfn
= ip_nat_sip_expected
;
488 /* Try to get same pair of ports: if not, try to change them. */
489 for (port
= ntohs(rtp_exp
->tuple
.dst
.u
.udp
.port
);
490 port
!= 0; port
+= 2) {
493 rtp_exp
->tuple
.dst
.u
.udp
.port
= htons(port
);
494 ret
= nf_ct_expect_related(rtp_exp
);
501 rtcp_exp
->tuple
.dst
.u
.udp
.port
= htons(port
+ 1);
502 ret
= nf_ct_expect_related(rtcp_exp
);
505 else if (ret
!= -EBUSY
) {
506 nf_ct_unexpect_related(rtp_exp
);
515 /* Update media port. */
516 if (rtp_exp
->tuple
.dst
.u
.udp
.port
!= rtp_exp
->saved_proto
.udp
.port
&&
517 !ip_nat_sdp_port(skb
, dataoff
, dptr
, datalen
,
518 mediaoff
, medialen
, port
))
524 nf_ct_unexpect_related(rtp_exp
);
525 nf_ct_unexpect_related(rtcp_exp
);
530 static struct nf_ct_helper_expectfn sip_nat
= {
532 .expectfn
= ip_nat_sip_expected
,
535 static void __exit
nf_nat_sip_fini(void)
537 RCU_INIT_POINTER(nf_nat_sip_hook
, NULL
);
538 RCU_INIT_POINTER(nf_nat_sip_seq_adjust_hook
, NULL
);
539 RCU_INIT_POINTER(nf_nat_sip_expect_hook
, NULL
);
540 RCU_INIT_POINTER(nf_nat_sdp_addr_hook
, NULL
);
541 RCU_INIT_POINTER(nf_nat_sdp_port_hook
, NULL
);
542 RCU_INIT_POINTER(nf_nat_sdp_session_hook
, NULL
);
543 RCU_INIT_POINTER(nf_nat_sdp_media_hook
, NULL
);
544 nf_ct_helper_expectfn_unregister(&sip_nat
);
548 static int __init
nf_nat_sip_init(void)
550 BUG_ON(nf_nat_sip_hook
!= NULL
);
551 BUG_ON(nf_nat_sip_seq_adjust_hook
!= NULL
);
552 BUG_ON(nf_nat_sip_expect_hook
!= NULL
);
553 BUG_ON(nf_nat_sdp_addr_hook
!= NULL
);
554 BUG_ON(nf_nat_sdp_port_hook
!= NULL
);
555 BUG_ON(nf_nat_sdp_session_hook
!= NULL
);
556 BUG_ON(nf_nat_sdp_media_hook
!= NULL
);
557 RCU_INIT_POINTER(nf_nat_sip_hook
, ip_nat_sip
);
558 RCU_INIT_POINTER(nf_nat_sip_seq_adjust_hook
, ip_nat_sip_seq_adjust
);
559 RCU_INIT_POINTER(nf_nat_sip_expect_hook
, ip_nat_sip_expect
);
560 RCU_INIT_POINTER(nf_nat_sdp_addr_hook
, ip_nat_sdp_addr
);
561 RCU_INIT_POINTER(nf_nat_sdp_port_hook
, ip_nat_sdp_port
);
562 RCU_INIT_POINTER(nf_nat_sdp_session_hook
, ip_nat_sdp_session
);
563 RCU_INIT_POINTER(nf_nat_sdp_media_hook
, ip_nat_sdp_media
);
564 nf_ct_helper_expectfn_register(&sip_nat
);
568 module_init(nf_nat_sip_init
);
569 module_exit(nf_nat_sip_fini
);