diff options
author | Patrick McHardy <kaber@trash.net> | 2006-12-03 01:09:57 -0500 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2006-12-03 01:09:57 -0500 |
commit | 9fafcd7b203229c3f3893a475741afc27e276306 (patch) | |
tree | cc1c5051142c35dee2c584c97892624cb701bf20 | |
parent | f09943fefe6b702e40893d35b4f10fd1064037fe (diff) |
[NETFILTER]: nf_conntrack/nf_nat: add SIP helper port
Add IPv4 and IPv6 capable nf_conntrack port of the SIP conntrack/NAT helper.
Signed-off-by: Patrick McHardy <kaber@trash.net>
Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r-- | include/linux/netfilter/nf_conntrack_sip.h | 41 | ||||
-rw-r--r-- | net/ipv4/netfilter/Kconfig | 5 | ||||
-rw-r--r-- | net/ipv4/netfilter/Makefile | 1 | ||||
-rw-r--r-- | net/ipv4/netfilter/nf_nat_sip.c | 283 | ||||
-rw-r--r-- | net/netfilter/Kconfig | 12 | ||||
-rw-r--r-- | net/netfilter/Makefile | 1 | ||||
-rw-r--r-- | net/netfilter/nf_conntrack_sip.c | 530 |
7 files changed, 873 insertions, 0 deletions
diff --git a/include/linux/netfilter/nf_conntrack_sip.h b/include/linux/netfilter/nf_conntrack_sip.h new file mode 100644 index 000000000000..bb7f2041db74 --- /dev/null +++ b/include/linux/netfilter/nf_conntrack_sip.h | |||
@@ -0,0 +1,41 @@ | |||
1 | #ifndef __NF_CONNTRACK_SIP_H__ | ||
2 | #define __NF_CONNTRACK_SIP_H__ | ||
3 | #ifdef __KERNEL__ | ||
4 | |||
5 | #define SIP_PORT 5060 | ||
6 | #define SIP_TIMEOUT 3600 | ||
7 | |||
8 | enum sip_header_pos { | ||
9 | POS_REG_REQ_URI, | ||
10 | POS_REQ_URI, | ||
11 | POS_FROM, | ||
12 | POS_TO, | ||
13 | POS_VIA, | ||
14 | POS_CONTACT, | ||
15 | POS_CONTENT, | ||
16 | POS_MEDIA, | ||
17 | POS_OWNER_IP4, | ||
18 | POS_CONNECTION_IP4, | ||
19 | POS_OWNER_IP6, | ||
20 | POS_CONNECTION_IP6, | ||
21 | POS_SDP_HEADER, | ||
22 | }; | ||
23 | |||
24 | extern unsigned int (*nf_nat_sip_hook)(struct sk_buff **pskb, | ||
25 | enum ip_conntrack_info ctinfo, | ||
26 | struct nf_conn *ct, | ||
27 | const char **dptr); | ||
28 | extern unsigned int (*nf_nat_sdp_hook)(struct sk_buff **pskb, | ||
29 | enum ip_conntrack_info ctinfo, | ||
30 | struct nf_conntrack_expect *exp, | ||
31 | const char *dptr); | ||
32 | |||
33 | extern int ct_sip_get_info(struct nf_conn *ct, const char *dptr, size_t dlen, | ||
34 | unsigned int *matchoff, unsigned int *matchlen, | ||
35 | enum sip_header_pos pos); | ||
36 | extern int ct_sip_lnlen(const char *line, const char *limit); | ||
37 | extern const char *ct_sip_search(const char *needle, const char *haystack, | ||
38 | size_t needle_len, size_t haystack_len, | ||
39 | int case_sensitive); | ||
40 | #endif /* __KERNEL__ */ | ||
41 | #endif /* __NF_CONNTRACK_SIP_H__ */ | ||
diff --git a/net/ipv4/netfilter/Kconfig b/net/ipv4/netfilter/Kconfig index c3327ac024de..83e83f553ccb 100644 --- a/net/ipv4/netfilter/Kconfig +++ b/net/ipv4/netfilter/Kconfig | |||
@@ -555,6 +555,11 @@ config IP_NF_NAT_SIP | |||
555 | default IP_NF_NAT if IP_NF_SIP=y | 555 | default IP_NF_NAT if IP_NF_SIP=y |
556 | default m if IP_NF_SIP=m | 556 | default m if IP_NF_SIP=m |
557 | 557 | ||
558 | config NF_NAT_SIP | ||
559 | tristate | ||
560 | depends on IP_NF_IPTABLES && NF_CONNTRACK && NF_NAT | ||
561 | default NF_NAT && NF_CONNTRACK_SIP | ||
562 | |||
558 | # mangle + specific targets | 563 | # mangle + specific targets |
559 | config IP_NF_MANGLE | 564 | config IP_NF_MANGLE |
560 | tristate "Packet mangling" | 565 | tristate "Packet mangling" |
diff --git a/net/ipv4/netfilter/Makefile b/net/ipv4/netfilter/Makefile index ef33ff2cdda9..167b65e89aca 100644 --- a/net/ipv4/netfilter/Makefile +++ b/net/ipv4/netfilter/Makefile | |||
@@ -55,6 +55,7 @@ obj-$(CONFIG_NF_NAT_FTP) += nf_nat_ftp.o | |||
55 | obj-$(CONFIG_NF_NAT_H323) += nf_nat_h323.o | 55 | obj-$(CONFIG_NF_NAT_H323) += nf_nat_h323.o |
56 | obj-$(CONFIG_NF_NAT_IRC) += nf_nat_irc.o | 56 | obj-$(CONFIG_NF_NAT_IRC) += nf_nat_irc.o |
57 | obj-$(CONFIG_NF_NAT_PPTP) += nf_nat_pptp.o | 57 | obj-$(CONFIG_NF_NAT_PPTP) += nf_nat_pptp.o |
58 | obj-$(CONFIG_NF_NAT_SIP) += nf_nat_sip.o | ||
58 | 59 | ||
59 | # NAT protocols (nf_nat) | 60 | # NAT protocols (nf_nat) |
60 | obj-$(CONFIG_NF_NAT_PROTO_GRE) += nf_nat_proto_gre.o | 61 | obj-$(CONFIG_NF_NAT_PROTO_GRE) += nf_nat_proto_gre.o |
diff --git a/net/ipv4/netfilter/nf_nat_sip.c b/net/ipv4/netfilter/nf_nat_sip.c new file mode 100644 index 000000000000..3d524b957310 --- /dev/null +++ b/net/ipv4/netfilter/nf_nat_sip.c | |||
@@ -0,0 +1,283 @@ | |||
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> | ||
14 | #include <linux/udp.h> | ||
15 | |||
16 | #include <net/netfilter/nf_nat.h> | ||
17 | #include <net/netfilter/nf_nat_helper.h> | ||
18 | #include <net/netfilter/nf_nat_rule.h> | ||
19 | #include <net/netfilter/nf_conntrack_helper.h> | ||
20 | #include <net/netfilter/nf_conntrack_expect.h> | ||
21 | #include <linux/netfilter/nf_conntrack_sip.h> | ||
22 | |||
23 | MODULE_LICENSE("GPL"); | ||
24 | MODULE_AUTHOR("Christian Hentschel <chentschel@arnet.com.ar>"); | ||
25 | MODULE_DESCRIPTION("SIP NAT helper"); | ||
26 | MODULE_ALIAS("ip_nat_sip"); | ||
27 | |||
28 | #if 0 | ||
29 | #define DEBUGP printk | ||
30 | #else | ||
31 | #define DEBUGP(format, args...) | ||
32 | #endif | ||
33 | |||
34 | struct addr_map { | ||
35 | struct { | ||
36 | char src[sizeof("nnn.nnn.nnn.nnn:nnnnn")]; | ||
37 | char dst[sizeof("nnn.nnn.nnn.nnn:nnnnn")]; | ||
38 | unsigned int srclen, srciplen; | ||
39 | unsigned int dstlen, dstiplen; | ||
40 | } addr[IP_CT_DIR_MAX]; | ||
41 | }; | ||
42 | |||
43 | static void addr_map_init(struct nf_conn *ct, struct addr_map *map) | ||
44 | { | ||
45 | struct nf_conntrack_tuple *t; | ||
46 | enum ip_conntrack_dir dir; | ||
47 | unsigned int n; | ||
48 | |||
49 | for (dir = 0; dir < IP_CT_DIR_MAX; dir++) { | ||
50 | t = &ct->tuplehash[dir].tuple; | ||
51 | |||
52 | n = sprintf(map->addr[dir].src, "%u.%u.%u.%u", | ||
53 | NIPQUAD(t->src.u3.ip)); | ||
54 | map->addr[dir].srciplen = n; | ||
55 | n += sprintf(map->addr[dir].src + n, ":%u", | ||
56 | ntohs(t->src.u.udp.port)); | ||
57 | map->addr[dir].srclen = n; | ||
58 | |||
59 | n = sprintf(map->addr[dir].dst, "%u.%u.%u.%u", | ||
60 | NIPQUAD(t->dst.u3.ip)); | ||
61 | map->addr[dir].dstiplen = n; | ||
62 | n += sprintf(map->addr[dir].dst + n, ":%u", | ||
63 | ntohs(t->dst.u.udp.port)); | ||
64 | map->addr[dir].dstlen = n; | ||
65 | } | ||
66 | } | ||
67 | |||
68 | static int map_sip_addr(struct sk_buff **pskb, enum ip_conntrack_info ctinfo, | ||
69 | struct nf_conn *ct, const char **dptr, size_t dlen, | ||
70 | enum sip_header_pos pos, struct addr_map *map) | ||
71 | { | ||
72 | enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo); | ||
73 | unsigned int matchlen, matchoff, addrlen; | ||
74 | char *addr; | ||
75 | |||
76 | if (ct_sip_get_info(ct, *dptr, dlen, &matchoff, &matchlen, pos) <= 0) | ||
77 | return 1; | ||
78 | |||
79 | if ((matchlen == map->addr[dir].srciplen || | ||
80 | matchlen == map->addr[dir].srclen) && | ||
81 | memcmp(*dptr + matchoff, map->addr[dir].src, matchlen) == 0) { | ||
82 | addr = map->addr[!dir].dst; | ||
83 | addrlen = map->addr[!dir].dstlen; | ||
84 | } else if ((matchlen == map->addr[dir].dstiplen || | ||
85 | matchlen == map->addr[dir].dstlen) && | ||
86 | memcmp(*dptr + matchoff, map->addr[dir].dst, matchlen) == 0) { | ||
87 | addr = map->addr[!dir].src; | ||
88 | addrlen = map->addr[!dir].srclen; | ||
89 | } else | ||
90 | return 1; | ||
91 | |||
92 | if (!nf_nat_mangle_udp_packet(pskb, ct, ctinfo, | ||
93 | matchoff, matchlen, addr, addrlen)) | ||
94 | return 0; | ||
95 | *dptr = (*pskb)->data + (*pskb)->nh.iph->ihl*4 + sizeof(struct udphdr); | ||
96 | return 1; | ||
97 | |||
98 | } | ||
99 | |||
100 | static unsigned int ip_nat_sip(struct sk_buff **pskb, | ||
101 | enum ip_conntrack_info ctinfo, | ||
102 | struct nf_conn *ct, | ||
103 | const char **dptr) | ||
104 | { | ||
105 | enum sip_header_pos pos; | ||
106 | struct addr_map map; | ||
107 | int dataoff, datalen; | ||
108 | |||
109 | dataoff = (*pskb)->nh.iph->ihl*4 + sizeof(struct udphdr); | ||
110 | datalen = (*pskb)->len - dataoff; | ||
111 | if (datalen < sizeof("SIP/2.0") - 1) | ||
112 | return NF_DROP; | ||
113 | |||
114 | addr_map_init(ct, &map); | ||
115 | |||
116 | /* Basic rules: requests and responses. */ | ||
117 | if (strncmp(*dptr, "SIP/2.0", sizeof("SIP/2.0") - 1) != 0) { | ||
118 | /* 10.2: Constructing the REGISTER Request: | ||
119 | * | ||
120 | * The "userinfo" and "@" components of the SIP URI MUST NOT | ||
121 | * be present. | ||
122 | */ | ||
123 | if (datalen >= sizeof("REGISTER") - 1 && | ||
124 | strncmp(*dptr, "REGISTER", sizeof("REGISTER") - 1) == 0) | ||
125 | pos = POS_REG_REQ_URI; | ||
126 | else | ||
127 | pos = POS_REQ_URI; | ||
128 | |||
129 | if (!map_sip_addr(pskb, ctinfo, ct, dptr, datalen, pos, &map)) | ||
130 | return NF_DROP; | ||
131 | } | ||
132 | |||
133 | if (!map_sip_addr(pskb, ctinfo, ct, dptr, datalen, POS_FROM, &map) || | ||
134 | !map_sip_addr(pskb, ctinfo, ct, dptr, datalen, POS_TO, &map) || | ||
135 | !map_sip_addr(pskb, ctinfo, ct, dptr, datalen, POS_VIA, &map) || | ||
136 | !map_sip_addr(pskb, ctinfo, ct, dptr, datalen, POS_CONTACT, &map)) | ||
137 | return NF_DROP; | ||
138 | return NF_ACCEPT; | ||
139 | } | ||
140 | |||
141 | static unsigned int mangle_sip_packet(struct sk_buff **pskb, | ||
142 | enum ip_conntrack_info ctinfo, | ||
143 | struct nf_conn *ct, | ||
144 | const char **dptr, size_t dlen, | ||
145 | char *buffer, int bufflen, | ||
146 | enum sip_header_pos pos) | ||
147 | { | ||
148 | unsigned int matchlen, matchoff; | ||
149 | |||
150 | if (ct_sip_get_info(ct, *dptr, dlen, &matchoff, &matchlen, pos) <= 0) | ||
151 | return 0; | ||
152 | |||
153 | if (!nf_nat_mangle_udp_packet(pskb, ct, ctinfo, | ||
154 | matchoff, matchlen, buffer, bufflen)) | ||
155 | return 0; | ||
156 | |||
157 | /* We need to reload this. Thanks Patrick. */ | ||
158 | *dptr = (*pskb)->data + (*pskb)->nh.iph->ihl*4 + sizeof(struct udphdr); | ||
159 | return 1; | ||
160 | } | ||
161 | |||
162 | static int mangle_content_len(struct sk_buff **pskb, | ||
163 | enum ip_conntrack_info ctinfo, | ||
164 | struct nf_conn *ct, | ||
165 | const char *dptr) | ||
166 | { | ||
167 | unsigned int dataoff, matchoff, matchlen; | ||
168 | char buffer[sizeof("65536")]; | ||
169 | int bufflen; | ||
170 | |||
171 | dataoff = (*pskb)->nh.iph->ihl*4 + sizeof(struct udphdr); | ||
172 | |||
173 | /* Get actual SDP lenght */ | ||
174 | if (ct_sip_get_info(ct, dptr, (*pskb)->len - dataoff, &matchoff, | ||
175 | &matchlen, POS_SDP_HEADER) > 0) { | ||
176 | |||
177 | /* since ct_sip_get_info() give us a pointer passing 'v=' | ||
178 | we need to add 2 bytes in this count. */ | ||
179 | int c_len = (*pskb)->len - dataoff - matchoff + 2; | ||
180 | |||
181 | /* Now, update SDP length */ | ||
182 | if (ct_sip_get_info(ct, dptr, (*pskb)->len - dataoff, &matchoff, | ||
183 | &matchlen, POS_CONTENT) > 0) { | ||
184 | |||
185 | bufflen = sprintf(buffer, "%u", c_len); | ||
186 | return nf_nat_mangle_udp_packet(pskb, ct, ctinfo, | ||
187 | matchoff, matchlen, | ||
188 | buffer, bufflen); | ||
189 | } | ||
190 | } | ||
191 | return 0; | ||
192 | } | ||
193 | |||
194 | static unsigned int mangle_sdp(struct sk_buff **pskb, | ||
195 | enum ip_conntrack_info ctinfo, | ||
196 | struct nf_conn *ct, | ||
197 | __be32 newip, u_int16_t port, | ||
198 | const char *dptr) | ||
199 | { | ||
200 | char buffer[sizeof("nnn.nnn.nnn.nnn")]; | ||
201 | unsigned int dataoff, bufflen; | ||
202 | |||
203 | dataoff = (*pskb)->nh.iph->ihl*4 + sizeof(struct udphdr); | ||
204 | |||
205 | /* Mangle owner and contact info. */ | ||
206 | bufflen = sprintf(buffer, "%u.%u.%u.%u", NIPQUAD(newip)); | ||
207 | if (!mangle_sip_packet(pskb, ctinfo, ct, &dptr, (*pskb)->len - dataoff, | ||
208 | buffer, bufflen, POS_OWNER_IP4)) | ||
209 | return 0; | ||
210 | |||
211 | if (!mangle_sip_packet(pskb, ctinfo, ct, &dptr, (*pskb)->len - dataoff, | ||
212 | buffer, bufflen, POS_CONNECTION_IP4)) | ||
213 | return 0; | ||
214 | |||
215 | /* Mangle media port. */ | ||
216 | bufflen = sprintf(buffer, "%u", port); | ||
217 | if (!mangle_sip_packet(pskb, ctinfo, ct, &dptr, (*pskb)->len - dataoff, | ||
218 | buffer, bufflen, POS_MEDIA)) | ||
219 | return 0; | ||
220 | |||
221 | return mangle_content_len(pskb, ctinfo, ct, dptr); | ||
222 | } | ||
223 | |||
224 | /* So, this packet has hit the connection tracking matching code. | ||
225 | Mangle it, and change the expectation to match the new version. */ | ||
226 | static unsigned int ip_nat_sdp(struct sk_buff **pskb, | ||
227 | enum ip_conntrack_info ctinfo, | ||
228 | struct nf_conntrack_expect *exp, | ||
229 | const char *dptr) | ||
230 | { | ||
231 | struct nf_conn *ct = exp->master; | ||
232 | enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo); | ||
233 | __be32 newip; | ||
234 | u_int16_t port; | ||
235 | |||
236 | DEBUGP("ip_nat_sdp():\n"); | ||
237 | |||
238 | /* Connection will come from reply */ | ||
239 | newip = ct->tuplehash[!dir].tuple.dst.u3.ip; | ||
240 | |||
241 | exp->tuple.dst.u3.ip = newip; | ||
242 | exp->saved_proto.udp.port = exp->tuple.dst.u.udp.port; | ||
243 | exp->dir = !dir; | ||
244 | |||
245 | /* When you see the packet, we need to NAT it the same as the | ||
246 | this one. */ | ||
247 | exp->expectfn = nf_nat_follow_master; | ||
248 | |||
249 | /* Try to get same port: if not, try to change it. */ | ||
250 | for (port = ntohs(exp->saved_proto.udp.port); port != 0; port++) { | ||
251 | exp->tuple.dst.u.udp.port = htons(port); | ||
252 | if (nf_conntrack_expect_related(exp) == 0) | ||
253 | break; | ||
254 | } | ||
255 | |||
256 | if (port == 0) | ||
257 | return NF_DROP; | ||
258 | |||
259 | if (!mangle_sdp(pskb, ctinfo, ct, newip, port, dptr)) { | ||
260 | nf_conntrack_unexpect_related(exp); | ||
261 | return NF_DROP; | ||
262 | } | ||
263 | return NF_ACCEPT; | ||
264 | } | ||
265 | |||
266 | static void __exit nf_nat_sip_fini(void) | ||
267 | { | ||
268 | rcu_assign_pointer(nf_nat_sip_hook, NULL); | ||
269 | rcu_assign_pointer(nf_nat_sdp_hook, NULL); | ||
270 | synchronize_rcu(); | ||
271 | } | ||
272 | |||
273 | static int __init nf_nat_sip_init(void) | ||
274 | { | ||
275 | BUG_ON(rcu_dereference(nf_nat_sip_hook)); | ||
276 | BUG_ON(rcu_dereference(nf_nat_sdp_hook)); | ||
277 | rcu_assign_pointer(nf_nat_sip_hook, ip_nat_sip); | ||
278 | rcu_assign_pointer(nf_nat_sdp_hook, ip_nat_sdp); | ||
279 | return 0; | ||
280 | } | ||
281 | |||
282 | module_init(nf_nat_sip_init); | ||
283 | module_exit(nf_nat_sip_fini); | ||
diff --git a/net/netfilter/Kconfig b/net/netfilter/Kconfig index 6b2eb26ae03f..e73891194b31 100644 --- a/net/netfilter/Kconfig +++ b/net/netfilter/Kconfig | |||
@@ -236,6 +236,18 @@ config NF_CONNTRACK_PPTP | |||
236 | 236 | ||
237 | To compile it as a module, choose M here. If unsure, say N. | 237 | To compile it as a module, choose M here. If unsure, say N. |
238 | 238 | ||
239 | config NF_CONNTRACK_SIP | ||
240 | tristate "SIP protocol support (EXPERIMENTAL)" | ||
241 | depends on EXPERIMENTAL && NF_CONNTRACK | ||
242 | help | ||
243 | SIP is an application-layer control protocol that can establish, | ||
244 | modify, and terminate multimedia sessions (conferences) such as | ||
245 | Internet telephony calls. With the ip_conntrack_sip and | ||
246 | the nf_nat_sip modules you can support the protocol on a connection | ||
247 | tracking/NATing firewall. | ||
248 | |||
249 | To compile it as a module, choose M here. If unsure, say N. | ||
250 | |||
239 | config NF_CT_NETLINK | 251 | config NF_CT_NETLINK |
240 | tristate 'Connection tracking netlink interface (EXPERIMENTAL)' | 252 | tristate 'Connection tracking netlink interface (EXPERIMENTAL)' |
241 | depends on EXPERIMENTAL && NF_CONNTRACK && NETFILTER_NETLINK | 253 | depends on EXPERIMENTAL && NF_CONNTRACK && NETFILTER_NETLINK |
diff --git a/net/netfilter/Makefile b/net/netfilter/Makefile index 897bed4cbd79..86da21700aa3 100644 --- a/net/netfilter/Makefile +++ b/net/netfilter/Makefile | |||
@@ -29,6 +29,7 @@ obj-$(CONFIG_NF_CONNTRACK_H323) += nf_conntrack_h323.o | |||
29 | obj-$(CONFIG_NF_CONNTRACK_IRC) += nf_conntrack_irc.o | 29 | obj-$(CONFIG_NF_CONNTRACK_IRC) += nf_conntrack_irc.o |
30 | obj-$(CONFIG_NF_CONNTRACK_NETBIOS_NS) += nf_conntrack_netbios_ns.o | 30 | obj-$(CONFIG_NF_CONNTRACK_NETBIOS_NS) += nf_conntrack_netbios_ns.o |
31 | obj-$(CONFIG_NF_CONNTRACK_PPTP) += nf_conntrack_pptp.o | 31 | obj-$(CONFIG_NF_CONNTRACK_PPTP) += nf_conntrack_pptp.o |
32 | obj-$(CONFIG_NF_CONNTRACK_SIP) += nf_conntrack_sip.o | ||
32 | 33 | ||
33 | # generic X tables | 34 | # generic X tables |
34 | obj-$(CONFIG_NETFILTER_XTABLES) += x_tables.o xt_tcpudp.o | 35 | obj-$(CONFIG_NETFILTER_XTABLES) += x_tables.o xt_tcpudp.o |
diff --git a/net/netfilter/nf_conntrack_sip.c b/net/netfilter/nf_conntrack_sip.c new file mode 100644 index 000000000000..e0c06795a943 --- /dev/null +++ b/net/netfilter/nf_conntrack_sip.c | |||
@@ -0,0 +1,530 @@ | |||
1 | /* SIP extension for IP connection tracking. | ||
2 | * | ||
3 | * (C) 2005 by Christian Hentschel <chentschel@arnet.com.ar> | ||
4 | * based on RR's ip_conntrack_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/ctype.h> | ||
13 | #include <linux/skbuff.h> | ||
14 | #include <linux/inet.h> | ||
15 | #include <linux/in.h> | ||
16 | #include <linux/udp.h> | ||
17 | |||
18 | #include <net/netfilter/nf_conntrack.h> | ||
19 | #include <net/netfilter/nf_conntrack_expect.h> | ||
20 | #include <net/netfilter/nf_conntrack_helper.h> | ||
21 | #include <linux/netfilter/nf_conntrack_sip.h> | ||
22 | |||
23 | #if 0 | ||
24 | #define DEBUGP printk | ||
25 | #else | ||
26 | #define DEBUGP(format, args...) | ||
27 | #endif | ||
28 | |||
29 | MODULE_LICENSE("GPL"); | ||
30 | MODULE_AUTHOR("Christian Hentschel <chentschel@arnet.com.ar>"); | ||
31 | MODULE_DESCRIPTION("SIP connection tracking helper"); | ||
32 | MODULE_ALIAS("ip_conntrack_sip"); | ||
33 | |||
34 | #define MAX_PORTS 8 | ||
35 | static unsigned short ports[MAX_PORTS]; | ||
36 | static int ports_c; | ||
37 | module_param_array(ports, ushort, &ports_c, 0400); | ||
38 | MODULE_PARM_DESC(ports, "port numbers of SIP servers"); | ||
39 | |||
40 | static unsigned int sip_timeout __read_mostly = SIP_TIMEOUT; | ||
41 | module_param(sip_timeout, uint, 0600); | ||
42 | MODULE_PARM_DESC(sip_timeout, "timeout for the master SIP session"); | ||
43 | |||
44 | unsigned int (*nf_nat_sip_hook)(struct sk_buff **pskb, | ||
45 | enum ip_conntrack_info ctinfo, | ||
46 | struct nf_conn *ct, | ||
47 | const char **dptr) __read_mostly; | ||
48 | EXPORT_SYMBOL_GPL(nf_nat_sip_hook); | ||
49 | |||
50 | unsigned int (*nf_nat_sdp_hook)(struct sk_buff **pskb, | ||
51 | enum ip_conntrack_info ctinfo, | ||
52 | struct nf_conntrack_expect *exp, | ||
53 | const char *dptr) __read_mostly; | ||
54 | EXPORT_SYMBOL_GPL(nf_nat_sdp_hook); | ||
55 | |||
56 | static int digits_len(struct nf_conn *, const char *, const char *, int *); | ||
57 | static int epaddr_len(struct nf_conn *, const char *, const char *, int *); | ||
58 | static int skp_digits_len(struct nf_conn *, const char *, const char *, int *); | ||
59 | static int skp_epaddr_len(struct nf_conn *, const char *, const char *, int *); | ||
60 | |||
61 | struct sip_header_nfo { | ||
62 | const char *lname; | ||
63 | const char *sname; | ||
64 | const char *ln_str; | ||
65 | size_t lnlen; | ||
66 | size_t snlen; | ||
67 | size_t ln_strlen; | ||
68 | int case_sensitive; | ||
69 | int (*match_len)(struct nf_conn *, const char *, | ||
70 | const char *, int *); | ||
71 | }; | ||
72 | |||
73 | static const struct sip_header_nfo ct_sip_hdrs[] = { | ||
74 | [POS_REG_REQ_URI] = { /* SIP REGISTER request URI */ | ||
75 | .lname = "sip:", | ||
76 | .lnlen = sizeof("sip:") - 1, | ||
77 | .ln_str = ":", | ||
78 | .ln_strlen = sizeof(":") - 1, | ||
79 | .match_len = epaddr_len, | ||
80 | }, | ||
81 | [POS_REQ_URI] = { /* SIP request URI */ | ||
82 | .lname = "sip:", | ||
83 | .lnlen = sizeof("sip:") - 1, | ||
84 | .ln_str = "@", | ||
85 | .ln_strlen = sizeof("@") - 1, | ||
86 | .match_len = epaddr_len, | ||
87 | }, | ||
88 | [POS_FROM] = { /* SIP From header */ | ||
89 | .lname = "From:", | ||
90 | .lnlen = sizeof("From:") - 1, | ||
91 | .sname = "\r\nf:", | ||
92 | .snlen = sizeof("\r\nf:") - 1, | ||
93 | .ln_str = "sip:", | ||
94 | .ln_strlen = sizeof("sip:") - 1, | ||
95 | .match_len = skp_epaddr_len, | ||
96 | }, | ||
97 | [POS_TO] = { /* SIP To header */ | ||
98 | .lname = "To:", | ||
99 | .lnlen = sizeof("To:") - 1, | ||
100 | .sname = "\r\nt:", | ||
101 | .snlen = sizeof("\r\nt:") - 1, | ||
102 | .ln_str = "sip:", | ||
103 | .ln_strlen = sizeof("sip:") - 1, | ||
104 | .match_len = skp_epaddr_len | ||
105 | }, | ||
106 | [POS_VIA] = { /* SIP Via header */ | ||
107 | .lname = "Via:", | ||
108 | .lnlen = sizeof("Via:") - 1, | ||
109 | .sname = "\r\nv:", | ||
110 | .snlen = sizeof("\r\nv:") - 1, /* rfc3261 "\r\n" */ | ||
111 | .ln_str = "UDP ", | ||
112 | .ln_strlen = sizeof("UDP ") - 1, | ||
113 | .match_len = epaddr_len, | ||
114 | }, | ||
115 | [POS_CONTACT] = { /* SIP Contact header */ | ||
116 | .lname = "Contact:", | ||
117 | .lnlen = sizeof("Contact:") - 1, | ||
118 | .sname = "\r\nm:", | ||
119 | .snlen = sizeof("\r\nm:") - 1, | ||
120 | .ln_str = "sip:", | ||
121 | .ln_strlen = sizeof("sip:") - 1, | ||
122 | .match_len = skp_epaddr_len | ||
123 | }, | ||
124 | [POS_CONTENT] = { /* SIP Content length header */ | ||
125 | .lname = "Content-Length:", | ||
126 | .lnlen = sizeof("Content-Length:") - 1, | ||
127 | .sname = "\r\nl:", | ||
128 | .snlen = sizeof("\r\nl:") - 1, | ||
129 | .ln_str = ":", | ||
130 | .ln_strlen = sizeof(":") - 1, | ||
131 | .match_len = skp_digits_len | ||
132 | }, | ||
133 | [POS_MEDIA] = { /* SDP media info */ | ||
134 | .case_sensitive = 1, | ||
135 | .lname = "\nm=", | ||
136 | .lnlen = sizeof("\nm=") - 1, | ||
137 | .sname = "\rm=", | ||
138 | .snlen = sizeof("\rm=") - 1, | ||
139 | .ln_str = "audio ", | ||
140 | .ln_strlen = sizeof("audio ") - 1, | ||
141 | .match_len = digits_len | ||
142 | }, | ||
143 | [POS_OWNER_IP4] = { /* SDP owner address*/ | ||
144 | .case_sensitive = 1, | ||
145 | .lname = "\no=", | ||
146 | .lnlen = sizeof("\no=") - 1, | ||
147 | .sname = "\ro=", | ||
148 | .snlen = sizeof("\ro=") - 1, | ||
149 | .ln_str = "IN IP4 ", | ||
150 | .ln_strlen = sizeof("IN IP4 ") - 1, | ||
151 | .match_len = epaddr_len | ||
152 | }, | ||
153 | [POS_CONNECTION_IP4] = {/* SDP connection info */ | ||
154 | .case_sensitive = 1, | ||
155 | .lname = "\nc=", | ||
156 | .lnlen = sizeof("\nc=") - 1, | ||
157 | .sname = "\rc=", | ||
158 | .snlen = sizeof("\rc=") - 1, | ||
159 | .ln_str = "IN IP4 ", | ||
160 | .ln_strlen = sizeof("IN IP4 ") - 1, | ||
161 | .match_len = epaddr_len | ||
162 | }, | ||
163 | [POS_OWNER_IP6] = { /* SDP owner address*/ | ||
164 | .case_sensitive = 1, | ||
165 | .lname = "\no=", | ||
166 | .lnlen = sizeof("\no=") - 1, | ||
167 | .sname = "\ro=", | ||
168 | .snlen = sizeof("\ro=") - 1, | ||
169 | .ln_str = "IN IP6 ", | ||
170 | .ln_strlen = sizeof("IN IP6 ") - 1, | ||
171 | .match_len = epaddr_len | ||
172 | }, | ||
173 | [POS_CONNECTION_IP6] = {/* SDP connection info */ | ||
174 | .case_sensitive = 1, | ||
175 | .lname = "\nc=", | ||
176 | .lnlen = sizeof("\nc=") - 1, | ||
177 | .sname = "\rc=", | ||
178 | .snlen = sizeof("\rc=") - 1, | ||
179 | .ln_str = "IN IP6 ", | ||
180 | .ln_strlen = sizeof("IN IP6 ") - 1, | ||
181 | .match_len = epaddr_len | ||
182 | }, | ||
183 | [POS_SDP_HEADER] = { /* SDP version header */ | ||
184 | .case_sensitive = 1, | ||
185 | .lname = "\nv=", | ||
186 | .lnlen = sizeof("\nv=") - 1, | ||
187 | .sname = "\rv=", | ||
188 | .snlen = sizeof("\rv=") - 1, | ||
189 | .ln_str = "=", | ||
190 | .ln_strlen = sizeof("=") - 1, | ||
191 | .match_len = digits_len | ||
192 | } | ||
193 | }; | ||
194 | |||
195 | /* get line lenght until first CR or LF seen. */ | ||
196 | int ct_sip_lnlen(const char *line, const char *limit) | ||
197 | { | ||
198 | const char *k = line; | ||
199 | |||
200 | while ((line <= limit) && (*line == '\r' || *line == '\n')) | ||
201 | line++; | ||
202 | |||
203 | while (line <= limit) { | ||
204 | if (*line == '\r' || *line == '\n') | ||
205 | break; | ||
206 | line++; | ||
207 | } | ||
208 | return line - k; | ||
209 | } | ||
210 | EXPORT_SYMBOL_GPL(ct_sip_lnlen); | ||
211 | |||
212 | /* Linear string search, case sensitive. */ | ||
213 | const char *ct_sip_search(const char *needle, const char *haystack, | ||
214 | size_t needle_len, size_t haystack_len, | ||
215 | int case_sensitive) | ||
216 | { | ||
217 | const char *limit = haystack + (haystack_len - needle_len); | ||
218 | |||
219 | while (haystack <= limit) { | ||
220 | if (case_sensitive) { | ||
221 | if (strncmp(haystack, needle, needle_len) == 0) | ||
222 | return haystack; | ||
223 | } else { | ||
224 | if (strnicmp(haystack, needle, needle_len) == 0) | ||
225 | return haystack; | ||
226 | } | ||
227 | haystack++; | ||
228 | } | ||
229 | return NULL; | ||
230 | } | ||
231 | EXPORT_SYMBOL_GPL(ct_sip_search); | ||
232 | |||
233 | static int digits_len(struct nf_conn *ct, const char *dptr, | ||
234 | const char *limit, int *shift) | ||
235 | { | ||
236 | int len = 0; | ||
237 | while (dptr <= limit && isdigit(*dptr)) { | ||
238 | dptr++; | ||
239 | len++; | ||
240 | } | ||
241 | return len; | ||
242 | } | ||
243 | |||
244 | /* get digits lenght, skiping blank spaces. */ | ||
245 | static int skp_digits_len(struct nf_conn *ct, const char *dptr, | ||
246 | const char *limit, int *shift) | ||
247 | { | ||
248 | for (; dptr <= limit && *dptr == ' '; dptr++) | ||
249 | (*shift)++; | ||
250 | |||
251 | return digits_len(ct, dptr, limit, shift); | ||
252 | } | ||
253 | |||
254 | static int parse_addr(struct nf_conn *ct, const char *cp, const char **endp, | ||
255 | union nf_conntrack_address *addr, const char *limit) | ||
256 | { | ||
257 | const char *end; | ||
258 | int family = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.l3num; | ||
259 | int ret = 0; | ||
260 | |||
261 | switch (family) { | ||
262 | case AF_INET: | ||
263 | ret = in4_pton(cp, limit - cp, (u8 *)&addr->ip, -1, &end); | ||
264 | break; | ||
265 | case AF_INET6: | ||
266 | ret = in6_pton(cp, limit - cp, (u8 *)&addr->ip6, -1, &end); | ||
267 | break; | ||
268 | default: | ||
269 | BUG(); | ||
270 | } | ||
271 | |||
272 | if (ret == 0 || end == cp) | ||
273 | return 0; | ||
274 | if (endp) | ||
275 | *endp = end; | ||
276 | return 1; | ||
277 | } | ||
278 | |||
279 | /* skip ip address. returns its length. */ | ||
280 | static int epaddr_len(struct nf_conn *ct, const char *dptr, | ||
281 | const char *limit, int *shift) | ||
282 | { | ||
283 | union nf_conntrack_address addr; | ||
284 | const char *aux = dptr; | ||
285 | |||
286 | if (!parse_addr(ct, dptr, &dptr, &addr, limit)) { | ||
287 | DEBUGP("ip: %s parse failed.!\n", dptr); | ||
288 | return 0; | ||
289 | } | ||
290 | |||
291 | /* Port number */ | ||
292 | if (*dptr == ':') { | ||
293 | dptr++; | ||
294 | dptr += digits_len(ct, dptr, limit, shift); | ||
295 | } | ||
296 | return dptr - aux; | ||
297 | } | ||
298 | |||
299 | /* get address length, skiping user info. */ | ||
300 | static int skp_epaddr_len(struct nf_conn *ct, const char *dptr, | ||
301 | const char *limit, int *shift) | ||
302 | { | ||
303 | int s = *shift; | ||
304 | |||
305 | for (; dptr <= limit && *dptr != '@'; dptr++) | ||
306 | (*shift)++; | ||
307 | |||
308 | if (*dptr == '@') { | ||
309 | dptr++; | ||
310 | (*shift)++; | ||
311 | } else | ||
312 | *shift = s; | ||
313 | |||
314 | return epaddr_len(ct, dptr, limit, shift); | ||
315 | } | ||
316 | |||
317 | /* Returns 0 if not found, -1 error parsing. */ | ||
318 | int ct_sip_get_info(struct nf_conn *ct, | ||
319 | const char *dptr, size_t dlen, | ||
320 | unsigned int *matchoff, | ||
321 | unsigned int *matchlen, | ||
322 | enum sip_header_pos pos) | ||
323 | { | ||
324 | const struct sip_header_nfo *hnfo = &ct_sip_hdrs[pos]; | ||
325 | const char *limit, *aux, *k = dptr; | ||
326 | int shift = 0; | ||
327 | |||
328 | limit = dptr + (dlen - hnfo->lnlen); | ||
329 | |||
330 | while (dptr <= limit) { | ||
331 | if ((strncmp(dptr, hnfo->lname, hnfo->lnlen) != 0) && | ||
332 | (strncmp(dptr, hnfo->sname, hnfo->snlen) != 0)) { | ||
333 | dptr++; | ||
334 | continue; | ||
335 | } | ||
336 | aux = ct_sip_search(hnfo->ln_str, dptr, hnfo->ln_strlen, | ||
337 | ct_sip_lnlen(dptr, limit), | ||
338 | hnfo->case_sensitive); | ||
339 | if (!aux) { | ||
340 | DEBUGP("'%s' not found in '%s'.\n", hnfo->ln_str, | ||
341 | hnfo->lname); | ||
342 | return -1; | ||
343 | } | ||
344 | aux += hnfo->ln_strlen; | ||
345 | |||
346 | *matchlen = hnfo->match_len(ct, aux, limit, &shift); | ||
347 | if (!*matchlen) | ||
348 | return -1; | ||
349 | |||
350 | *matchoff = (aux - k) + shift; | ||
351 | |||
352 | DEBUGP("%s match succeeded! - len: %u\n", hnfo->lname, | ||
353 | *matchlen); | ||
354 | return 1; | ||
355 | } | ||
356 | DEBUGP("%s header not found.\n", hnfo->lname); | ||
357 | return 0; | ||
358 | } | ||
359 | EXPORT_SYMBOL_GPL(ct_sip_get_info); | ||
360 | |||
361 | static int set_expected_rtp(struct sk_buff **pskb, | ||
362 | struct nf_conn *ct, | ||
363 | enum ip_conntrack_info ctinfo, | ||
364 | union nf_conntrack_address *addr, | ||
365 | __be16 port, | ||
366 | const char *dptr) | ||
367 | { | ||
368 | struct nf_conntrack_expect *exp; | ||
369 | enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo); | ||
370 | int family = ct->tuplehash[!dir].tuple.src.l3num; | ||
371 | int ret; | ||
372 | typeof(nf_nat_sdp_hook) nf_nat_sdp; | ||
373 | |||
374 | exp = nf_conntrack_expect_alloc(ct); | ||
375 | if (exp == NULL) | ||
376 | return NF_DROP; | ||
377 | nf_conntrack_expect_init(exp, family, | ||
378 | &ct->tuplehash[!dir].tuple.src.u3, addr, | ||
379 | IPPROTO_UDP, NULL, &port); | ||
380 | |||
381 | nf_nat_sdp = rcu_dereference(nf_nat_sdp_hook); | ||
382 | if (nf_nat_sdp && ct->status & IPS_NAT_MASK) | ||
383 | ret = nf_nat_sdp(pskb, ctinfo, exp, dptr); | ||
384 | else { | ||
385 | if (nf_conntrack_expect_related(exp) != 0) | ||
386 | ret = NF_DROP; | ||
387 | else | ||
388 | ret = NF_ACCEPT; | ||
389 | } | ||
390 | nf_conntrack_expect_put(exp); | ||
391 | |||
392 | return ret; | ||
393 | } | ||
394 | |||
395 | static int sip_help(struct sk_buff **pskb, | ||
396 | unsigned int protoff, | ||
397 | struct nf_conn *ct, | ||
398 | enum ip_conntrack_info ctinfo) | ||
399 | { | ||
400 | int family = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.l3num; | ||
401 | union nf_conntrack_address addr; | ||
402 | unsigned int dataoff, datalen; | ||
403 | const char *dptr; | ||
404 | int ret = NF_ACCEPT; | ||
405 | int matchoff, matchlen; | ||
406 | u_int16_t port; | ||
407 | enum sip_header_pos pos; | ||
408 | typeof(nf_nat_sip_hook) nf_nat_sip; | ||
409 | |||
410 | /* No Data ? */ | ||
411 | dataoff = protoff + sizeof(struct udphdr); | ||
412 | if (dataoff >= (*pskb)->len) | ||
413 | return NF_ACCEPT; | ||
414 | |||
415 | nf_ct_refresh(ct, *pskb, sip_timeout * HZ); | ||
416 | |||
417 | if (!skb_is_nonlinear(*pskb)) | ||
418 | dptr = (*pskb)->data + dataoff; | ||
419 | else { | ||
420 | DEBUGP("Copy of skbuff not supported yet.\n"); | ||
421 | goto out; | ||
422 | } | ||
423 | |||
424 | nf_nat_sip = rcu_dereference(nf_nat_sip_hook); | ||
425 | if (nf_nat_sip && ct->status & IPS_NAT_MASK) { | ||
426 | if (!nf_nat_sip(pskb, ctinfo, ct, &dptr)) { | ||
427 | ret = NF_DROP; | ||
428 | goto out; | ||
429 | } | ||
430 | } | ||
431 | |||
432 | datalen = (*pskb)->len - dataoff; | ||
433 | if (datalen < sizeof("SIP/2.0 200") - 1) | ||
434 | goto out; | ||
435 | |||
436 | /* RTP info only in some SDP pkts */ | ||
437 | if (memcmp(dptr, "INVITE", sizeof("INVITE") - 1) != 0 && | ||
438 | memcmp(dptr, "SIP/2.0 200", sizeof("SIP/2.0 200") - 1) != 0) { | ||
439 | goto out; | ||
440 | } | ||
441 | /* Get address and port from SDP packet. */ | ||
442 | pos = family == AF_INET ? POS_CONNECTION_IP4 : POS_CONNECTION_IP6; | ||
443 | if (ct_sip_get_info(ct, dptr, datalen, &matchoff, &matchlen, pos) > 0) { | ||
444 | |||
445 | /* We'll drop only if there are parse problems. */ | ||
446 | if (!parse_addr(ct, dptr + matchoff, NULL, &addr, | ||
447 | dptr + datalen)) { | ||
448 | ret = NF_DROP; | ||
449 | goto out; | ||
450 | } | ||
451 | if (ct_sip_get_info(ct, dptr, datalen, &matchoff, &matchlen, | ||
452 | POS_MEDIA) > 0) { | ||
453 | |||
454 | port = simple_strtoul(dptr + matchoff, NULL, 10); | ||
455 | if (port < 1024) { | ||
456 | ret = NF_DROP; | ||
457 | goto out; | ||
458 | } | ||
459 | ret = set_expected_rtp(pskb, ct, ctinfo, &addr, | ||
460 | htons(port), dptr); | ||
461 | } | ||
462 | } | ||
463 | out: | ||
464 | return ret; | ||
465 | } | ||
466 | |||
467 | static struct nf_conntrack_helper sip[MAX_PORTS][2] __read_mostly; | ||
468 | static char sip_names[MAX_PORTS][2][sizeof("sip-65535")] __read_mostly; | ||
469 | |||
470 | static void nf_conntrack_sip_fini(void) | ||
471 | { | ||
472 | int i, j; | ||
473 | |||
474 | for (i = 0; i < ports_c; i++) { | ||
475 | for (j = 0; j < 2; j++) { | ||
476 | if (sip[i][j].me == NULL) | ||
477 | continue; | ||
478 | nf_conntrack_helper_unregister(&sip[i][j]); | ||
479 | } | ||
480 | } | ||
481 | } | ||
482 | |||
483 | static int __init nf_conntrack_sip_init(void) | ||
484 | { | ||
485 | int i, j, ret; | ||
486 | char *tmpname; | ||
487 | |||
488 | if (ports_c == 0) | ||
489 | ports[ports_c++] = SIP_PORT; | ||
490 | |||
491 | for (i = 0; i < ports_c; i++) { | ||
492 | memset(&sip[i], 0, sizeof(sip[i])); | ||
493 | |||
494 | sip[i][0].tuple.src.l3num = AF_INET; | ||
495 | sip[i][1].tuple.src.l3num = AF_INET6; | ||
496 | for (j = 0; j < 2; j++) { | ||
497 | sip[i][j].tuple.dst.protonum = IPPROTO_UDP; | ||
498 | sip[i][j].tuple.src.u.udp.port = htons(ports[i]); | ||
499 | sip[i][j].mask.src.l3num = 0xFFFF; | ||
500 | sip[i][j].mask.src.u.udp.port = htons(0xFFFF); | ||
501 | sip[i][j].mask.dst.protonum = 0xFF; | ||
502 | sip[i][j].max_expected = 2; | ||
503 | sip[i][j].timeout = 3 * 60; /* 3 minutes */ | ||
504 | sip[i][j].me = THIS_MODULE; | ||
505 | sip[i][j].help = sip_help; | ||
506 | |||
507 | tmpname = &sip_names[i][j][0]; | ||
508 | if (ports[i] == SIP_PORT) | ||
509 | sprintf(tmpname, "sip"); | ||
510 | else | ||
511 | sprintf(tmpname, "sip-%u", i); | ||
512 | sip[i][j].name = tmpname; | ||
513 | |||
514 | DEBUGP("port #%u: %u\n", i, ports[i]); | ||
515 | |||
516 | ret = nf_conntrack_helper_register(&sip[i][j]); | ||
517 | if (ret) { | ||
518 | printk("nf_ct_sip: failed to register helper " | ||
519 | "for pf: %u port: %u\n", | ||
520 | sip[i][j].tuple.src.l3num, ports[i]); | ||
521 | nf_conntrack_sip_fini(); | ||
522 | return ret; | ||
523 | } | ||
524 | } | ||
525 | } | ||
526 | return 0; | ||
527 | } | ||
528 | |||
529 | module_init(nf_conntrack_sip_init); | ||
530 | module_exit(nf_conntrack_sip_fini); | ||