]>
Commit | Line | Data |
---|---|---|
9fafcd7b PM |
1 | /* SIP extension for UDP NAT alteration. |
2 | * | |
3 | * (C) 2005 by Christian Hentschel <chentschel@arnet.com.ar> | |
4 | * based on RR's ip_nat_ftp.c and other modules. | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License version 2 as | |
8 | * published by the Free Software Foundation. | |
9 | */ | |
10 | ||
11 | #include <linux/module.h> | |
12 | #include <linux/skbuff.h> | |
13 | #include <linux/ip.h> | |
c9bdd4b5 | 14 | #include <net/ip.h> |
9fafcd7b PM |
15 | #include <linux/udp.h> |
16 | ||
17 | #include <net/netfilter/nf_nat.h> | |
18 | #include <net/netfilter/nf_nat_helper.h> | |
19 | #include <net/netfilter/nf_nat_rule.h> | |
20 | #include <net/netfilter/nf_conntrack_helper.h> | |
21 | #include <net/netfilter/nf_conntrack_expect.h> | |
22 | #include <linux/netfilter/nf_conntrack_sip.h> | |
23 | ||
24 | MODULE_LICENSE("GPL"); | |
25 | MODULE_AUTHOR("Christian Hentschel <chentschel@arnet.com.ar>"); | |
26 | MODULE_DESCRIPTION("SIP NAT helper"); | |
27 | MODULE_ALIAS("ip_nat_sip"); | |
28 | ||
9fafcd7b PM |
29 | struct addr_map { |
30 | struct { | |
31 | char src[sizeof("nnn.nnn.nnn.nnn:nnnnn")]; | |
32 | char dst[sizeof("nnn.nnn.nnn.nnn:nnnnn")]; | |
33 | unsigned int srclen, srciplen; | |
34 | unsigned int dstlen, dstiplen; | |
35 | } addr[IP_CT_DIR_MAX]; | |
36 | }; | |
37 | ||
13f7d63c | 38 | static void addr_map_init(const struct nf_conn *ct, struct addr_map *map) |
9fafcd7b | 39 | { |
13f7d63c | 40 | const struct nf_conntrack_tuple *t; |
9fafcd7b PM |
41 | enum ip_conntrack_dir dir; |
42 | unsigned int n; | |
43 | ||
44 | for (dir = 0; dir < IP_CT_DIR_MAX; dir++) { | |
45 | t = &ct->tuplehash[dir].tuple; | |
46 | ||
47 | n = sprintf(map->addr[dir].src, "%u.%u.%u.%u", | |
48 | NIPQUAD(t->src.u3.ip)); | |
49 | map->addr[dir].srciplen = n; | |
50 | n += sprintf(map->addr[dir].src + n, ":%u", | |
51 | ntohs(t->src.u.udp.port)); | |
52 | map->addr[dir].srclen = n; | |
53 | ||
54 | n = sprintf(map->addr[dir].dst, "%u.%u.%u.%u", | |
55 | NIPQUAD(t->dst.u3.ip)); | |
56 | map->addr[dir].dstiplen = n; | |
57 | n += sprintf(map->addr[dir].dst + n, ":%u", | |
58 | ntohs(t->dst.u.udp.port)); | |
59 | map->addr[dir].dstlen = n; | |
60 | } | |
61 | } | |
62 | ||
2a6cfb22 PM |
63 | static unsigned int mangle_packet(struct sk_buff *skb, |
64 | const char **dptr, unsigned int *datalen, | |
65 | unsigned int matchoff, unsigned int matchlen, | |
66 | const char *buffer, unsigned int buflen) | |
67 | { | |
68 | enum ip_conntrack_info ctinfo; | |
69 | struct nf_conn *ct = nf_ct_get(skb, &ctinfo); | |
70 | ||
71 | if (!nf_nat_mangle_udp_packet(skb, ct, ctinfo, matchoff, matchlen, | |
72 | buffer, buflen)) | |
73 | return 0; | |
74 | ||
75 | /* Reload data pointer and adjust datalen value */ | |
76 | *dptr = skb->data + ip_hdrlen(skb) + sizeof(struct udphdr); | |
77 | *datalen += buflen - matchlen; | |
78 | return 1; | |
79 | } | |
80 | ||
212440a7 | 81 | static int map_sip_addr(struct sk_buff *skb, |
2a6cfb22 | 82 | const char **dptr, unsigned int *datalen, |
9fafcd7b PM |
83 | enum sip_header_pos pos, struct addr_map *map) |
84 | { | |
212440a7 PM |
85 | enum ip_conntrack_info ctinfo; |
86 | struct nf_conn *ct = nf_ct_get(skb, &ctinfo); | |
9fafcd7b PM |
87 | enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo); |
88 | unsigned int matchlen, matchoff, addrlen; | |
89 | char *addr; | |
90 | ||
2a6cfb22 PM |
91 | if (ct_sip_get_info(ct, *dptr, *datalen, &matchoff, &matchlen, |
92 | pos) <= 0) | |
9fafcd7b PM |
93 | return 1; |
94 | ||
95 | if ((matchlen == map->addr[dir].srciplen || | |
96 | matchlen == map->addr[dir].srclen) && | |
779382eb | 97 | strncmp(*dptr + matchoff, map->addr[dir].src, matchlen) == 0) { |
9fafcd7b PM |
98 | addr = map->addr[!dir].dst; |
99 | addrlen = map->addr[!dir].dstlen; | |
100 | } else if ((matchlen == map->addr[dir].dstiplen || | |
101 | matchlen == map->addr[dir].dstlen) && | |
779382eb | 102 | strncmp(*dptr + matchoff, map->addr[dir].dst, matchlen) == 0) { |
9fafcd7b PM |
103 | addr = map->addr[!dir].src; |
104 | addrlen = map->addr[!dir].srclen; | |
105 | } else | |
106 | return 1; | |
107 | ||
2a6cfb22 PM |
108 | return mangle_packet(skb, dptr, datalen, matchoff, matchlen, |
109 | addr, addrlen); | |
9fafcd7b PM |
110 | } |
111 | ||
3db05fea | 112 | static unsigned int ip_nat_sip(struct sk_buff *skb, |
2a6cfb22 | 113 | const char **dptr, unsigned int *datalen) |
9fafcd7b | 114 | { |
212440a7 PM |
115 | enum ip_conntrack_info ctinfo; |
116 | struct nf_conn *ct = nf_ct_get(skb, &ctinfo); | |
9fafcd7b PM |
117 | enum sip_header_pos pos; |
118 | struct addr_map map; | |
9fafcd7b | 119 | |
779382eb | 120 | if (*datalen < strlen("SIP/2.0")) |
45241a7a | 121 | return NF_ACCEPT; |
9fafcd7b PM |
122 | |
123 | addr_map_init(ct, &map); | |
124 | ||
125 | /* Basic rules: requests and responses. */ | |
779382eb | 126 | if (strnicmp(*dptr, "SIP/2.0", strlen("SIP/2.0")) != 0) { |
9fafcd7b PM |
127 | /* 10.2: Constructing the REGISTER Request: |
128 | * | |
129 | * The "userinfo" and "@" components of the SIP URI MUST NOT | |
130 | * be present. | |
131 | */ | |
779382eb PM |
132 | if (*datalen >= strlen("REGISTER") && |
133 | strnicmp(*dptr, "REGISTER", strlen("REGISTER")) == 0) | |
9fafcd7b PM |
134 | pos = POS_REG_REQ_URI; |
135 | else | |
136 | pos = POS_REQ_URI; | |
137 | ||
212440a7 | 138 | if (!map_sip_addr(skb, dptr, datalen, pos, &map)) |
9fafcd7b PM |
139 | return NF_DROP; |
140 | } | |
141 | ||
212440a7 PM |
142 | if (!map_sip_addr(skb, dptr, datalen, POS_FROM, &map) || |
143 | !map_sip_addr(skb, dptr, datalen, POS_TO, &map) || | |
144 | !map_sip_addr(skb, dptr, datalen, POS_VIA, &map) || | |
145 | !map_sip_addr(skb, dptr, datalen, POS_CONTACT, &map)) | |
9fafcd7b PM |
146 | return NF_DROP; |
147 | return NF_ACCEPT; | |
148 | } | |
149 | ||
3db05fea | 150 | static unsigned int mangle_sip_packet(struct sk_buff *skb, |
2a6cfb22 | 151 | const char **dptr, unsigned int *datalen, |
9fafcd7b PM |
152 | char *buffer, int bufflen, |
153 | enum sip_header_pos pos) | |
154 | { | |
212440a7 PM |
155 | enum ip_conntrack_info ctinfo; |
156 | struct nf_conn *ct = nf_ct_get(skb, &ctinfo); | |
9fafcd7b PM |
157 | unsigned int matchlen, matchoff; |
158 | ||
2a6cfb22 PM |
159 | if (ct_sip_get_info(ct, *dptr, *datalen, &matchoff, &matchlen, |
160 | pos) <= 0) | |
9fafcd7b PM |
161 | return 0; |
162 | ||
2a6cfb22 PM |
163 | return mangle_packet(skb, dptr, datalen, matchoff, matchlen, |
164 | buffer, bufflen); | |
9fafcd7b PM |
165 | } |
166 | ||
3db05fea | 167 | static int mangle_content_len(struct sk_buff *skb, |
2a6cfb22 | 168 | const char **dptr, unsigned int *datalen) |
9fafcd7b | 169 | { |
212440a7 PM |
170 | enum ip_conntrack_info ctinfo; |
171 | struct nf_conn *ct = nf_ct_get(skb, &ctinfo); | |
2a6cfb22 | 172 | unsigned int matchoff, matchlen; |
9fafcd7b PM |
173 | char buffer[sizeof("65536")]; |
174 | int bufflen; | |
175 | ||
e00ccd4a | 176 | /* Get actual SDP length */ |
2a6cfb22 | 177 | if (ct_sip_get_info(ct, *dptr, *datalen, &matchoff, |
e905a9ed | 178 | &matchlen, POS_SDP_HEADER) > 0) { |
9fafcd7b PM |
179 | |
180 | /* since ct_sip_get_info() give us a pointer passing 'v=' | |
181 | we need to add 2 bytes in this count. */ | |
2a6cfb22 | 182 | int c_len = *datalen - matchoff + 2; |
9fafcd7b PM |
183 | |
184 | /* Now, update SDP length */ | |
2a6cfb22 | 185 | if (ct_sip_get_info(ct, *dptr, *datalen, &matchoff, |
e905a9ed | 186 | &matchlen, POS_CONTENT) > 0) { |
9fafcd7b PM |
187 | |
188 | bufflen = sprintf(buffer, "%u", c_len); | |
2a6cfb22 PM |
189 | return mangle_packet(skb, dptr, datalen, |
190 | matchoff, matchlen, | |
191 | buffer, bufflen); | |
9fafcd7b PM |
192 | } |
193 | } | |
194 | return 0; | |
195 | } | |
196 | ||
3db05fea | 197 | static unsigned int mangle_sdp(struct sk_buff *skb, |
9fafcd7b PM |
198 | enum ip_conntrack_info ctinfo, |
199 | struct nf_conn *ct, | |
200 | __be32 newip, u_int16_t port, | |
2a6cfb22 | 201 | const char **dptr, unsigned int *datalen) |
9fafcd7b PM |
202 | { |
203 | char buffer[sizeof("nnn.nnn.nnn.nnn")]; | |
2a6cfb22 | 204 | unsigned int bufflen; |
9fafcd7b PM |
205 | |
206 | /* Mangle owner and contact info. */ | |
207 | bufflen = sprintf(buffer, "%u.%u.%u.%u", NIPQUAD(newip)); | |
212440a7 PM |
208 | if (!mangle_sip_packet(skb, dptr, datalen, buffer, bufflen, |
209 | POS_OWNER_IP4)) | |
9fafcd7b PM |
210 | return 0; |
211 | ||
212440a7 PM |
212 | if (!mangle_sip_packet(skb, dptr, datalen, buffer, bufflen, |
213 | POS_CONNECTION_IP4)) | |
9fafcd7b PM |
214 | return 0; |
215 | ||
216 | /* Mangle media port. */ | |
217 | bufflen = sprintf(buffer, "%u", port); | |
212440a7 PM |
218 | if (!mangle_sip_packet(skb, dptr, datalen, buffer, bufflen, |
219 | POS_MEDIA)) | |
9fafcd7b PM |
220 | return 0; |
221 | ||
212440a7 | 222 | return mangle_content_len(skb, dptr, datalen); |
9fafcd7b PM |
223 | } |
224 | ||
cfd6c380 HX |
225 | static void ip_nat_sdp_expect(struct nf_conn *ct, |
226 | struct nf_conntrack_expect *exp) | |
227 | { | |
228 | struct nf_nat_range range; | |
229 | ||
230 | /* This must be a fresh one. */ | |
231 | BUG_ON(ct->status & IPS_NAT_DONE_MASK); | |
232 | ||
cfd6c380 HX |
233 | /* For DST manip, map port here to where it's expected. */ |
234 | range.flags = (IP_NAT_RANGE_MAP_IPS | IP_NAT_RANGE_PROTO_SPECIFIED); | |
235 | range.min = range.max = exp->saved_proto; | |
236 | range.min_ip = range.max_ip = exp->saved_ip; | |
cc01dcbd | 237 | nf_nat_setup_info(ct, &range, IP_NAT_MANIP_DST); |
3d244121 PM |
238 | |
239 | /* Change src to where master sends to */ | |
240 | range.flags = IP_NAT_RANGE_MAP_IPS; | |
241 | range.min_ip = range.max_ip | |
242 | = ct->master->tuplehash[!exp->dir].tuple.dst.u3.ip; | |
243 | nf_nat_setup_info(ct, &range, IP_NAT_MANIP_SRC); | |
cfd6c380 HX |
244 | } |
245 | ||
9fafcd7b PM |
246 | /* So, this packet has hit the connection tracking matching code. |
247 | Mangle it, and change the expectation to match the new version. */ | |
3db05fea | 248 | static unsigned int ip_nat_sdp(struct sk_buff *skb, |
212440a7 PM |
249 | const char **dptr, unsigned int *datalen, |
250 | struct nf_conntrack_expect *exp) | |
9fafcd7b | 251 | { |
212440a7 PM |
252 | enum ip_conntrack_info ctinfo; |
253 | struct nf_conn *ct = nf_ct_get(skb, &ctinfo); | |
9fafcd7b PM |
254 | enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo); |
255 | __be32 newip; | |
256 | u_int16_t port; | |
257 | ||
9fafcd7b | 258 | /* Connection will come from reply */ |
f4a607bf JB |
259 | if (ct->tuplehash[dir].tuple.src.u3.ip == |
260 | ct->tuplehash[!dir].tuple.dst.u3.ip) | |
261 | newip = exp->tuple.dst.u3.ip; | |
262 | else | |
263 | newip = ct->tuplehash[!dir].tuple.dst.u3.ip; | |
9fafcd7b | 264 | |
cfd6c380 | 265 | exp->saved_ip = exp->tuple.dst.u3.ip; |
9fafcd7b PM |
266 | exp->tuple.dst.u3.ip = newip; |
267 | exp->saved_proto.udp.port = exp->tuple.dst.u.udp.port; | |
268 | exp->dir = !dir; | |
269 | ||
270 | /* When you see the packet, we need to NAT it the same as the | |
271 | this one. */ | |
cfd6c380 | 272 | exp->expectfn = ip_nat_sdp_expect; |
9fafcd7b PM |
273 | |
274 | /* Try to get same port: if not, try to change it. */ | |
275 | for (port = ntohs(exp->saved_proto.udp.port); port != 0; port++) { | |
276 | exp->tuple.dst.u.udp.port = htons(port); | |
6823645d | 277 | if (nf_ct_expect_related(exp) == 0) |
9fafcd7b PM |
278 | break; |
279 | } | |
280 | ||
281 | if (port == 0) | |
282 | return NF_DROP; | |
283 | ||
2a6cfb22 | 284 | if (!mangle_sdp(skb, ctinfo, ct, newip, port, dptr, datalen)) { |
6823645d | 285 | nf_ct_unexpect_related(exp); |
9fafcd7b PM |
286 | return NF_DROP; |
287 | } | |
288 | return NF_ACCEPT; | |
289 | } | |
290 | ||
291 | static void __exit nf_nat_sip_fini(void) | |
292 | { | |
293 | rcu_assign_pointer(nf_nat_sip_hook, NULL); | |
294 | rcu_assign_pointer(nf_nat_sdp_hook, NULL); | |
295 | synchronize_rcu(); | |
296 | } | |
297 | ||
298 | static int __init nf_nat_sip_init(void) | |
299 | { | |
d1332e0a PM |
300 | BUG_ON(nf_nat_sip_hook != NULL); |
301 | BUG_ON(nf_nat_sdp_hook != NULL); | |
9fafcd7b PM |
302 | rcu_assign_pointer(nf_nat_sip_hook, ip_nat_sip); |
303 | rcu_assign_pointer(nf_nat_sdp_hook, ip_nat_sdp); | |
304 | return 0; | |
305 | } | |
306 | ||
307 | module_init(nf_nat_sip_init); | |
308 | module_exit(nf_nat_sip_fini); |