]>
Commit | Line | Data |
---|---|---|
91447636 A |
1 | /* |
2 | * Copyright (c) 2003 Apple Computer, Inc. All rights reserved. | |
3 | * | |
4 | * @APPLE_LICENSE_HEADER_START@ | |
5 | * | |
37839358 A |
6 | * The contents of this file constitute Original Code as defined in and |
7 | * are subject to the Apple Public Source License Version 1.1 (the | |
8 | * "License"). You may not use this file except in compliance with the | |
9 | * License. Please obtain a copy of the License at | |
10 | * http://www.apple.com/publicsource and read it before using this file. | |
91447636 | 11 | * |
37839358 A |
12 | * This Original Code and all software distributed under the License are |
13 | * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER | |
91447636 A |
14 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, |
15 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, | |
37839358 A |
16 | * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the |
17 | * License for the specific language governing rights and limitations | |
18 | * under the License. | |
91447636 A |
19 | * |
20 | * @APPLE_LICENSE_HEADER_END@ | |
21 | */ | |
22 | ||
23 | /* $FreeBSD: src/sys/netinet6/ip6_fw.c,v 1.2.2.9 2002/04/28 05:40:27 suz Exp $ */ | |
24 | /* $KAME: ip6_fw.c,v 1.21 2001/01/24 01:25:32 itojun Exp $ */ | |
25 | ||
26 | /* | |
27 | * Copyright (C) 1998, 1999, 2000 and 2001 WIDE Project. | |
28 | * All rights reserved. | |
29 | * | |
30 | * Redistribution and use in source and binary forms, with or without | |
31 | * modification, are permitted provided that the following conditions | |
32 | * are met: | |
33 | * 1. Redistributions of source code must retain the above copyright | |
34 | * notice, this list of conditions and the following disclaimer. | |
35 | * 2. Redistributions in binary form must reproduce the above copyright | |
36 | * notice, this list of conditions and the following disclaimer in the | |
37 | * documentation and/or other materials provided with the distribution. | |
38 | * 3. Neither the name of the project nor the names of its contributors | |
39 | * may be used to endorse or promote products derived from this software | |
40 | * without specific prior written permission. | |
41 | * | |
42 | * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND | |
43 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
44 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
45 | * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE | |
46 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
47 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |
48 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |
49 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
50 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |
51 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |
52 | * SUCH DAMAGE. | |
53 | */ | |
54 | ||
55 | /* | |
56 | * Copyright (c) 1993 Daniel Boulet | |
57 | * Copyright (c) 1994 Ugen J.S.Antsilevich | |
58 | * Copyright (c) 1996 Alex Nash | |
59 | * | |
60 | * Redistribution and use in source forms, with and without modification, | |
61 | * are permitted provided that this entire comment appears intact. | |
62 | * | |
63 | * Redistribution in binary form may occur without any restrictions. | |
64 | * Obviously, it would be nice if you gave credit where credit is due | |
65 | * but requiring it would be too onerous. | |
66 | * | |
67 | * This software is provided ``AS IS'' without any warranties of any kind. | |
68 | */ | |
69 | ||
70 | /* | |
71 | * Implement IPv6 packet firewall | |
72 | */ | |
73 | ||
74 | ||
75 | #ifdef IP6DIVERT | |
76 | #error "NOT SUPPORTED IPV6 DIVERT" | |
77 | #endif | |
78 | #ifdef IP6FW_DIVERT_RESTART | |
79 | #error "NOT SUPPORTED IPV6 DIVERT" | |
80 | #endif | |
81 | ||
82 | #include <string.h> | |
83 | #include <machine/spl.h> | |
84 | ||
85 | #include <sys/param.h> | |
86 | #include <sys/systm.h> | |
87 | #include <sys/malloc.h> | |
88 | #include <sys/mbuf.h> | |
89 | #include <sys/queue.h> | |
90 | #include <sys/kernel.h> | |
91 | #include <sys/socket.h> | |
92 | #include <sys/socketvar.h> | |
93 | #include <sys/syslog.h> | |
94 | #include <sys/lock.h> | |
95 | #include <sys/time.h> | |
96 | #include <net/if.h> | |
97 | #include <net/route.h> | |
98 | #include <netinet/in_systm.h> | |
99 | #include <netinet/in.h> | |
100 | #include <netinet/ip.h> | |
101 | ||
102 | #include <netinet/ip6.h> | |
103 | #include <netinet6/ip6_var.h> | |
104 | #include <netinet6/in6_var.h> | |
105 | #include <netinet/icmp6.h> | |
106 | ||
107 | #include <netinet/in_pcb.h> | |
108 | ||
109 | #include <netinet6/ip6_fw.h> | |
110 | #include <netinet/ip_var.h> | |
111 | #include <netinet/tcp.h> | |
112 | #include <netinet/tcp_seq.h> | |
113 | #include <netinet/tcp_timer.h> | |
114 | #include <netinet/tcp_var.h> | |
115 | #include <netinet/udp.h> | |
116 | ||
117 | #include <sys/sysctl.h> | |
118 | ||
119 | #include <net/net_osdep.h> | |
120 | ||
121 | MALLOC_DEFINE(M_IP6FW, "Ip6Fw/Ip6Acct", "Ip6Fw/Ip6Acct chain's"); | |
122 | ||
123 | static int fw6_debug = 1; | |
124 | #ifdef IPV6FIREWALL_VERBOSE | |
125 | static int fw6_verbose = 1; | |
126 | #else | |
127 | static int fw6_verbose = 0; | |
128 | #endif | |
129 | #ifdef IPV6FIREWALL_VERBOSE_LIMIT | |
130 | static int fw6_verbose_limit = IPV6FIREWALL_VERBOSE_LIMIT; | |
131 | #else | |
132 | static int fw6_verbose_limit = 0; | |
133 | #endif | |
134 | ||
135 | LIST_HEAD (ip6_fw_head, ip6_fw_chain) ip6_fw_chain; | |
136 | ||
137 | #ifdef SYSCTL_NODE | |
138 | SYSCTL_DECL(_net_inet6_ip6); | |
139 | SYSCTL_NODE(_net_inet6_ip6, OID_AUTO, fw, CTLFLAG_RW, 0, "Firewall"); | |
140 | SYSCTL_INT(_net_inet6_ip6_fw, OID_AUTO, enable, CTLFLAG_RW, | |
141 | &ip6_fw_enable, 0, "Enable ip6fw"); | |
142 | SYSCTL_INT(_net_inet6_ip6_fw, OID_AUTO, debug, CTLFLAG_RW, &fw6_debug, 0, ""); | |
143 | SYSCTL_INT(_net_inet6_ip6_fw, OID_AUTO, verbose, CTLFLAG_RW, &fw6_verbose, 0, ""); | |
144 | SYSCTL_INT(_net_inet6_ip6_fw, OID_AUTO, verbose_limit, CTLFLAG_RW, &fw6_verbose_limit, 0, ""); | |
145 | #endif | |
146 | ||
147 | #define dprintf(a) do { \ | |
148 | if (fw6_debug) \ | |
149 | printf a; \ | |
150 | } while (0) | |
151 | #define SNPARGS(buf, len) buf + len, sizeof(buf) > len ? sizeof(buf) - len : 0 | |
152 | ||
153 | static int add_entry6 __P((struct ip6_fw_head *chainptr, struct ip6_fw *frwl)); | |
154 | static int del_entry6 __P((struct ip6_fw_head *chainptr, u_short number)); | |
155 | static int zero_entry6 __P((struct ip6_fw *frwl)); | |
156 | static struct ip6_fw *check_ip6fw_struct __P((struct ip6_fw *m)); | |
157 | static int ip6opts_match __P((struct ip6_hdr **ip6, struct ip6_fw *f, | |
158 | struct mbuf **m, | |
159 | int *off, int *nxt, u_short *offset)); | |
160 | static int port_match6 __P((u_short *portptr, int nports, u_short port, | |
161 | int range_flag)); | |
162 | static int tcp6flg_match __P((struct tcphdr *tcp6, struct ip6_fw *f)); | |
163 | static int icmp6type_match __P((struct icmp6_hdr * icmp, struct ip6_fw * f)); | |
164 | static void ip6fw_report __P((struct ip6_fw *f, struct ip6_hdr *ip6, | |
165 | struct ifnet *rif, struct ifnet *oif, int off, int nxt)); | |
166 | ||
167 | static int ip6_fw_chk __P((struct ip6_hdr **pip6, | |
168 | struct ifnet *oif, u_int16_t *cookie, struct mbuf **m)); | |
169 | static int ip6_fw_ctl __P((struct sockopt *)); | |
170 | ||
171 | static char err_prefix[] = "ip6_fw_ctl:"; | |
172 | extern lck_mtx_t *ip6_mutex; | |
173 | ||
174 | /* | |
175 | * Returns 1 if the port is matched by the vector, 0 otherwise | |
176 | */ | |
177 | static | |
178 | __inline int | |
179 | port_match6(u_short *portptr, int nports, u_short port, int range_flag) | |
180 | { | |
181 | if (!nports) | |
182 | return 1; | |
183 | if (range_flag) { | |
184 | if (portptr[0] <= port && port <= portptr[1]) { | |
185 | return 1; | |
186 | } | |
187 | nports -= 2; | |
188 | portptr += 2; | |
189 | } | |
190 | while (nports-- > 0) { | |
191 | if (*portptr++ == port) { | |
192 | return 1; | |
193 | } | |
194 | } | |
195 | return 0; | |
196 | } | |
197 | ||
198 | static int | |
199 | tcp6flg_match(struct tcphdr *tcp6, struct ip6_fw *f) | |
200 | { | |
201 | u_char flg_set, flg_clr; | |
202 | ||
203 | /* | |
204 | * If an established connection is required, reject packets that | |
205 | * have only SYN of RST|ACK|SYN set. Otherwise, fall through to | |
206 | * other flag requirements. | |
207 | */ | |
208 | if ((f->fw_ipflg & IPV6_FW_IF_TCPEST) && | |
209 | ((tcp6->th_flags & (IPV6_FW_TCPF_RST | IPV6_FW_TCPF_ACK | | |
210 | IPV6_FW_TCPF_SYN)) == IPV6_FW_TCPF_SYN)) | |
211 | return 0; | |
212 | ||
213 | flg_set = tcp6->th_flags & f->fw_tcpf; | |
214 | flg_clr = tcp6->th_flags & f->fw_tcpnf; | |
215 | ||
216 | if (flg_set != f->fw_tcpf) | |
217 | return 0; | |
218 | if (flg_clr) | |
219 | return 0; | |
220 | ||
221 | return 1; | |
222 | } | |
223 | ||
224 | static int | |
225 | icmp6type_match(struct icmp6_hdr *icmp6, struct ip6_fw *f) | |
226 | { | |
227 | int type; | |
228 | ||
229 | if (!(f->fw_flg & IPV6_FW_F_ICMPBIT)) | |
230 | return(1); | |
231 | ||
232 | type = icmp6->icmp6_type; | |
233 | ||
234 | /* check for matching type in the bitmap */ | |
235 | if (type < IPV6_FW_ICMPTYPES_DIM * sizeof(unsigned) * 8 && | |
236 | (f->fw_icmp6types[type / (sizeof(unsigned) * 8)] & | |
237 | (1U << (type % (8 * sizeof(unsigned)))))) | |
238 | return(1); | |
239 | ||
240 | return(0); /* no match */ | |
241 | } | |
242 | ||
243 | static int | |
244 | is_icmp6_query(struct ip6_hdr *ip6, int off) | |
245 | { | |
246 | const struct icmp6_hdr *icmp6; | |
247 | int icmp6_type; | |
248 | ||
249 | icmp6 = (struct icmp6_hdr *)((caddr_t)ip6 + off); | |
250 | icmp6_type = icmp6->icmp6_type; | |
251 | ||
252 | if (icmp6_type == ICMP6_ECHO_REQUEST || | |
253 | icmp6_type == ICMP6_MEMBERSHIP_QUERY || | |
254 | icmp6_type == ICMP6_WRUREQUEST || | |
255 | icmp6_type == ICMP6_FQDN_QUERY || | |
256 | icmp6_type == ICMP6_NI_QUERY) | |
257 | return(1); | |
258 | ||
259 | return(0); | |
260 | } | |
261 | ||
262 | static int | |
263 | ip6opts_match(struct ip6_hdr **pip6, struct ip6_fw *f, struct mbuf **m, | |
264 | int *off, int *nxt, u_short *offset) | |
265 | { | |
266 | int len; | |
267 | struct ip6_hdr *ip6 = *pip6; | |
268 | struct ip6_ext *ip6e; | |
269 | u_char opts, nopts, nopts_sve; | |
270 | ||
271 | opts = f->fw_ip6opt; | |
272 | nopts = nopts_sve = f->fw_ip6nopt; | |
273 | ||
274 | *nxt = ip6->ip6_nxt; | |
275 | *off = sizeof(struct ip6_hdr); | |
276 | len = ntohs(ip6->ip6_plen) + sizeof(struct ip6_hdr); | |
277 | while (*off < len) { | |
278 | ip6e = (struct ip6_ext *)((caddr_t) ip6 + *off); | |
279 | if ((*m)->m_len < *off + sizeof(*ip6e)) | |
280 | goto opts_check; /* XXX */ | |
281 | ||
282 | switch(*nxt) { | |
283 | case IPPROTO_FRAGMENT: | |
284 | if ((*m)->m_len >= *off + sizeof(struct ip6_frag)) { | |
285 | struct ip6_frag *ip6f; | |
286 | ||
287 | ip6f = (struct ip6_frag *) ((caddr_t)ip6 + *off); | |
288 | *offset = ip6f->ip6f_offlg & IP6F_OFF_MASK; | |
289 | } | |
290 | opts &= ~IPV6_FW_IP6OPT_FRAG; | |
291 | nopts &= ~IPV6_FW_IP6OPT_FRAG; | |
292 | *off += sizeof(struct ip6_frag); | |
293 | break; | |
294 | case IPPROTO_AH: | |
295 | opts &= ~IPV6_FW_IP6OPT_AH; | |
296 | nopts &= ~IPV6_FW_IP6OPT_AH; | |
297 | *off += (ip6e->ip6e_len + 2) << 2; | |
298 | break; | |
299 | default: | |
300 | switch (*nxt) { | |
301 | case IPPROTO_HOPOPTS: | |
302 | opts &= ~IPV6_FW_IP6OPT_HOPOPT; | |
303 | nopts &= ~IPV6_FW_IP6OPT_HOPOPT; | |
304 | break; | |
305 | case IPPROTO_ROUTING: | |
306 | opts &= ~IPV6_FW_IP6OPT_ROUTE; | |
307 | nopts &= ~IPV6_FW_IP6OPT_ROUTE; | |
308 | break; | |
309 | case IPPROTO_ESP: | |
310 | opts &= ~IPV6_FW_IP6OPT_ESP; | |
311 | nopts &= ~IPV6_FW_IP6OPT_ESP; | |
312 | break; | |
313 | case IPPROTO_NONE: | |
314 | opts &= ~IPV6_FW_IP6OPT_NONXT; | |
315 | nopts &= ~IPV6_FW_IP6OPT_NONXT; | |
316 | goto opts_check; | |
317 | break; | |
318 | case IPPROTO_DSTOPTS: | |
319 | opts &= ~IPV6_FW_IP6OPT_OPTS; | |
320 | nopts &= ~IPV6_FW_IP6OPT_OPTS; | |
321 | break; | |
322 | default: | |
323 | goto opts_check; | |
324 | break; | |
325 | } | |
326 | *off += (ip6e->ip6e_len + 1) << 3; | |
327 | break; | |
328 | } | |
329 | *nxt = ip6e->ip6e_nxt; | |
330 | ||
331 | } | |
332 | opts_check: | |
333 | if (f->fw_ip6opt == f->fw_ip6nopt) /* XXX */ | |
334 | return 1; | |
335 | ||
336 | if (opts == 0 && nopts == nopts_sve) | |
337 | return 1; | |
338 | else | |
339 | return 0; | |
340 | } | |
341 | ||
342 | static | |
343 | __inline int | |
344 | iface_match(struct ifnet *ifp, union ip6_fw_if *ifu, int byname) | |
345 | { | |
346 | /* Check by name or by IP address */ | |
347 | if (byname) { | |
348 | /* Check unit number (-1 is wildcard) */ | |
349 | if (ifu->fu_via_if.unit != -1 | |
350 | && ifp->if_unit != ifu->fu_via_if.unit) | |
351 | return(0); | |
352 | /* Check name */ | |
353 | if (strncmp(ifp->if_name, ifu->fu_via_if.name, IP6FW_IFNLEN)) | |
354 | return(0); | |
355 | return(1); | |
356 | } else if (!IN6_IS_ADDR_UNSPECIFIED(&ifu->fu_via_ip6)) { /* Zero == wildcard */ | |
357 | struct ifaddr *ia; | |
358 | ||
359 | ifnet_lock_shared(ifp); | |
360 | for (ia = ifp->if_addrlist.tqh_first; ia; ia = ia->ifa_list.tqe_next) | |
361 | { | |
362 | ||
363 | if (ia->ifa_addr == NULL) | |
364 | continue; | |
365 | if (ia->ifa_addr->sa_family != AF_INET6) | |
366 | continue; | |
367 | if (!IN6_ARE_ADDR_EQUAL(&ifu->fu_via_ip6, | |
368 | &(((struct sockaddr_in6 *) | |
369 | (ia->ifa_addr))->sin6_addr))) | |
370 | continue; | |
371 | ifnet_lock_done(ifp); | |
372 | return(1); | |
373 | } | |
374 | ifnet_lock_done(ifp); | |
375 | return(0); | |
376 | } | |
377 | return(1); | |
378 | } | |
379 | ||
380 | static void | |
381 | ip6fw_report(struct ip6_fw *f, struct ip6_hdr *ip6, | |
382 | struct ifnet *rif, struct ifnet *oif, int off, int nxt) | |
383 | { | |
384 | static int counter; | |
385 | struct tcphdr *const tcp6 = (struct tcphdr *) ((caddr_t) ip6+ off); | |
386 | struct udphdr *const udp = (struct udphdr *) ((caddr_t) ip6+ off); | |
387 | struct icmp6_hdr *const icmp6 = (struct icmp6_hdr *) ((caddr_t) ip6+ off); | |
388 | int count; | |
389 | char *action; | |
390 | char action2[32], proto[102], name[18]; | |
391 | int len; | |
392 | ||
393 | count = f ? f->fw_pcnt : ++counter; | |
394 | if (fw6_verbose_limit != 0 && count > fw6_verbose_limit) | |
395 | return; | |
396 | ||
397 | /* Print command name */ | |
398 | snprintf(SNPARGS(name, 0), "ip6fw: %d", f ? f->fw_number : -1); | |
399 | ||
400 | action = action2; | |
401 | if (!f) | |
402 | action = "Refuse"; | |
403 | else { | |
404 | switch (f->fw_flg & IPV6_FW_F_COMMAND) { | |
405 | case IPV6_FW_F_DENY: | |
406 | action = "Deny"; | |
407 | break; | |
408 | case IPV6_FW_F_REJECT: | |
409 | if (f->fw_reject_code == IPV6_FW_REJECT_RST) | |
410 | action = "Reset"; | |
411 | else | |
412 | action = "Unreach"; | |
413 | break; | |
414 | case IPV6_FW_F_ACCEPT: | |
415 | action = "Accept"; | |
416 | break; | |
417 | case IPV6_FW_F_COUNT: | |
418 | action = "Count"; | |
419 | break; | |
420 | case IPV6_FW_F_DIVERT: | |
421 | snprintf(SNPARGS(action2, 0), "Divert %d", | |
422 | f->fw_divert_port); | |
423 | break; | |
424 | case IPV6_FW_F_TEE: | |
425 | snprintf(SNPARGS(action2, 0), "Tee %d", | |
426 | f->fw_divert_port); | |
427 | break; | |
428 | case IPV6_FW_F_SKIPTO: | |
429 | snprintf(SNPARGS(action2, 0), "SkipTo %d", | |
430 | f->fw_skipto_rule); | |
431 | break; | |
432 | default: | |
433 | action = "UNKNOWN"; | |
434 | break; | |
435 | } | |
436 | } | |
437 | ||
438 | switch (nxt) { | |
439 | case IPPROTO_TCP: | |
440 | len = snprintf(SNPARGS(proto, 0), "TCP [%s]", | |
441 | ip6_sprintf(&ip6->ip6_src)); | |
442 | if (off > 0) | |
443 | len += snprintf(SNPARGS(proto, len), ":%d ", | |
444 | ntohs(tcp6->th_sport)); | |
445 | else | |
446 | len += snprintf(SNPARGS(proto, len), " "); | |
447 | len += snprintf(SNPARGS(proto, len), "[%s]", | |
448 | ip6_sprintf(&ip6->ip6_dst)); | |
449 | if (off > 0) | |
450 | snprintf(SNPARGS(proto, len), ":%d", | |
451 | ntohs(tcp6->th_dport)); | |
452 | break; | |
453 | case IPPROTO_UDP: | |
454 | len = snprintf(SNPARGS(proto, 0), "UDP [%s]", | |
455 | ip6_sprintf(&ip6->ip6_src)); | |
456 | if (off > 0) | |
457 | len += snprintf(SNPARGS(proto, len), ":%d ", | |
458 | ntohs(udp->uh_sport)); | |
459 | else | |
460 | len += snprintf(SNPARGS(proto, len), " "); | |
461 | len += snprintf(SNPARGS(proto, len), "[%s]", | |
462 | ip6_sprintf(&ip6->ip6_dst)); | |
463 | if (off > 0) | |
464 | snprintf(SNPARGS(proto, len), ":%d", | |
465 | ntohs(udp->uh_dport)); | |
466 | break; | |
467 | case IPPROTO_ICMPV6: | |
468 | if (off > 0) | |
469 | len = snprintf(SNPARGS(proto, 0), "IPV6-ICMP:%u.%u ", | |
470 | icmp6->icmp6_type, icmp6->icmp6_code); | |
471 | else | |
472 | len = snprintf(SNPARGS(proto, 0), "IPV6-ICMP "); | |
473 | len += snprintf(SNPARGS(proto, len), "[%s]", | |
474 | ip6_sprintf(&ip6->ip6_src)); | |
475 | snprintf(SNPARGS(proto, len), " [%s]", | |
476 | ip6_sprintf(&ip6->ip6_dst)); | |
477 | break; | |
478 | default: | |
479 | len = snprintf(SNPARGS(proto, 0), "P:%d [%s]", nxt, | |
480 | ip6_sprintf(&ip6->ip6_src)); | |
481 | snprintf(SNPARGS(proto, len), " [%s]", | |
482 | ip6_sprintf(&ip6->ip6_dst)); | |
483 | break; | |
484 | } | |
485 | ||
486 | if (oif) | |
487 | log(LOG_AUTHPRIV | LOG_INFO, "%s %s %s out via %s\n", | |
488 | name, action, proto, if_name(oif)); | |
489 | else if (rif) | |
490 | log(LOG_AUTHPRIV | LOG_INFO, "%s %s %s in via %s\n", | |
491 | name, action, proto, if_name(rif)); | |
492 | else | |
493 | log(LOG_AUTHPRIV | LOG_INFO, "%s %s %s", | |
494 | name, action, proto); | |
495 | if (fw6_verbose_limit != 0 && count == fw6_verbose_limit) | |
496 | log(LOG_AUTHPRIV | LOG_INFO, "ip6fw: limit reached on entry %d\n", | |
497 | f ? f->fw_number : -1); | |
498 | } | |
499 | ||
500 | /* | |
501 | * Parameters: | |
502 | * | |
503 | * ip Pointer to packet header (struct ip6_hdr *) | |
504 | * hlen Packet header length | |
505 | * oif Outgoing interface, or NULL if packet is incoming | |
506 | * #ifndef IP6FW_DIVERT_RESTART | |
507 | * *cookie Ignore all divert/tee rules to this port (if non-zero) | |
508 | * #else | |
509 | * *cookie Skip up to the first rule past this rule number; | |
510 | * #endif | |
511 | * *m The packet; we set to NULL when/if we nuke it. | |
512 | * | |
513 | * Return value: | |
514 | * | |
515 | * 0 The packet is to be accepted and routed normally OR | |
516 | * the packet was denied/rejected and has been dropped; | |
517 | * in the latter case, *m is equal to NULL upon return. | |
518 | * port Divert the packet to port. | |
519 | */ | |
520 | ||
521 | static int | |
522 | ip6_fw_chk(struct ip6_hdr **pip6, | |
523 | struct ifnet *oif, u_int16_t *cookie, struct mbuf **m) | |
524 | { | |
525 | struct ip6_fw_chain *chain; | |
526 | struct ip6_fw *rule = NULL; | |
527 | struct ip6_hdr *ip6 = *pip6; | |
528 | struct ifnet *const rif = (*m)->m_pkthdr.rcvif; | |
529 | u_short offset = 0; | |
530 | int off = sizeof(struct ip6_hdr), nxt = ip6->ip6_nxt; | |
531 | u_short src_port, dst_port; | |
532 | #ifdef IP6FW_DIVERT_RESTART | |
533 | u_int16_t skipto = *cookie; | |
534 | #else | |
535 | u_int16_t ignport = ntohs(*cookie); | |
536 | #endif | |
537 | struct timeval timenow; | |
538 | ||
539 | getmicrotime(&timenow); | |
540 | ||
541 | *cookie = 0; | |
542 | /* | |
543 | * Go down the chain, looking for enlightment | |
544 | * #ifdef IP6FW_DIVERT_RESTART | |
545 | * If we've been asked to start at a given rule immediatly, do so. | |
546 | * #endif | |
547 | */ | |
548 | chain = LIST_FIRST(&ip6_fw_chain); | |
549 | #ifdef IP6FW_DIVERT_RESTART | |
550 | if (skipto) { | |
551 | if (skipto >= 65535) | |
552 | goto dropit; | |
553 | while (chain && (chain->rule->fw_number <= skipto)) { | |
554 | chain = LIST_NEXT(chain, chain); | |
555 | } | |
556 | if (! chain) goto dropit; | |
557 | } | |
558 | #endif /* IP6FW_DIVERT_RESTART */ | |
559 | for (; chain; chain = LIST_NEXT(chain, chain)) { | |
560 | struct ip6_fw *const f = chain->rule; | |
561 | ||
562 | if (oif) { | |
563 | /* Check direction outbound */ | |
564 | if (!(f->fw_flg & IPV6_FW_F_OUT)) | |
565 | continue; | |
566 | } else { | |
567 | /* Check direction inbound */ | |
568 | if (!(f->fw_flg & IPV6_FW_F_IN)) | |
569 | continue; | |
570 | } | |
571 | ||
572 | #define IN6_ARE_ADDR_MASKEQUAL(x,y,z) (\ | |
573 | (((x)->s6_addr32[0] & (y)->s6_addr32[0]) == (z)->s6_addr32[0]) && \ | |
574 | (((x)->s6_addr32[1] & (y)->s6_addr32[1]) == (z)->s6_addr32[1]) && \ | |
575 | (((x)->s6_addr32[2] & (y)->s6_addr32[2]) == (z)->s6_addr32[2]) && \ | |
576 | (((x)->s6_addr32[3] & (y)->s6_addr32[3]) == (z)->s6_addr32[3])) | |
577 | ||
578 | /* If src-addr doesn't match, not this rule. */ | |
579 | if (((f->fw_flg & IPV6_FW_F_INVSRC) != 0) ^ | |
580 | (!IN6_ARE_ADDR_MASKEQUAL(&ip6->ip6_src,&f->fw_smsk,&f->fw_src))) | |
581 | continue; | |
582 | ||
583 | /* If dest-addr doesn't match, not this rule. */ | |
584 | if (((f->fw_flg & IPV6_FW_F_INVDST) != 0) ^ | |
585 | (!IN6_ARE_ADDR_MASKEQUAL(&ip6->ip6_dst,&f->fw_dmsk,&f->fw_dst))) | |
586 | continue; | |
587 | ||
588 | #undef IN6_ARE_ADDR_MASKEQUAL | |
589 | /* Interface check */ | |
590 | if ((f->fw_flg & IF6_FW_F_VIAHACK) == IF6_FW_F_VIAHACK) { | |
591 | struct ifnet *const iface = oif ? oif : rif; | |
592 | ||
593 | /* Backwards compatibility hack for "via" */ | |
594 | if (!iface || !iface_match(iface, | |
595 | &f->fw_in_if, f->fw_flg & IPV6_FW_F_OIFNAME)) | |
596 | continue; | |
597 | } else { | |
598 | /* Check receive interface */ | |
599 | if ((f->fw_flg & IPV6_FW_F_IIFACE) | |
600 | && (!rif || !iface_match(rif, | |
601 | &f->fw_in_if, f->fw_flg & IPV6_FW_F_IIFNAME))) | |
602 | continue; | |
603 | /* Check outgoing interface */ | |
604 | if ((f->fw_flg & IPV6_FW_F_OIFACE) | |
605 | && (!oif || !iface_match(oif, | |
606 | &f->fw_out_if, f->fw_flg & IPV6_FW_F_OIFNAME))) | |
607 | continue; | |
608 | } | |
609 | ||
610 | /* Check IP options */ | |
611 | if (!ip6opts_match(&ip6, f, m, &off, &nxt, &offset)) | |
612 | continue; | |
613 | ||
614 | /* Fragments */ | |
615 | if ((f->fw_flg & IPV6_FW_F_FRAG) && !offset) | |
616 | continue; | |
617 | ||
618 | /* Check protocol; if wildcard, match */ | |
619 | if (f->fw_prot == IPPROTO_IPV6) | |
620 | goto got_match; | |
621 | ||
622 | /* If different, don't match */ | |
623 | if (nxt != f->fw_prot) | |
624 | continue; | |
625 | ||
626 | #define PULLUP_TO(len) do { \ | |
627 | if ((*m)->m_len < (len) \ | |
628 | && (*m = m_pullup(*m, (len))) == 0) { \ | |
629 | goto dropit; \ | |
630 | } \ | |
631 | *pip6 = ip6 = mtod(*m, struct ip6_hdr *); \ | |
632 | } while (0) | |
633 | ||
634 | /* Protocol specific checks */ | |
635 | switch (nxt) { | |
636 | case IPPROTO_TCP: | |
637 | { | |
638 | struct tcphdr *tcp6; | |
639 | ||
640 | if (offset == 1) { /* cf. RFC 1858 */ | |
641 | PULLUP_TO(off + 4); /* XXX ? */ | |
642 | goto bogusfrag; | |
643 | } | |
644 | if (offset != 0) { | |
645 | /* | |
646 | * TCP flags and ports aren't available in this | |
647 | * packet -- if this rule specified either one, | |
648 | * we consider the rule a non-match. | |
649 | */ | |
650 | if (f->fw_nports != 0 || | |
651 | f->fw_tcpf != f->fw_tcpnf) | |
652 | continue; | |
653 | ||
654 | break; | |
655 | } | |
656 | PULLUP_TO(off + 14); | |
657 | tcp6 = (struct tcphdr *) ((caddr_t)ip6 + off); | |
658 | if (((f->fw_tcpf != f->fw_tcpnf) || | |
659 | (f->fw_ipflg & IPV6_FW_IF_TCPEST)) && | |
660 | !tcp6flg_match(tcp6, f)) | |
661 | continue; | |
662 | src_port = ntohs(tcp6->th_sport); | |
663 | dst_port = ntohs(tcp6->th_dport); | |
664 | goto check_ports; | |
665 | } | |
666 | ||
667 | case IPPROTO_UDP: | |
668 | { | |
669 | struct udphdr *udp; | |
670 | ||
671 | if (offset != 0) { | |
672 | /* | |
673 | * Port specification is unavailable -- if this | |
674 | * rule specifies a port, we consider the rule | |
675 | * a non-match. | |
676 | */ | |
677 | if (f->fw_nports != 0) | |
678 | continue; | |
679 | ||
680 | break; | |
681 | } | |
682 | PULLUP_TO(off + 4); | |
683 | udp = (struct udphdr *) ((caddr_t)ip6 + off); | |
684 | src_port = ntohs(udp->uh_sport); | |
685 | dst_port = ntohs(udp->uh_dport); | |
686 | check_ports: | |
687 | if (!port_match6(&f->fw_pts[0], | |
688 | IPV6_FW_GETNSRCP(f), src_port, | |
689 | f->fw_flg & IPV6_FW_F_SRNG)) | |
690 | continue; | |
691 | if (!port_match6(&f->fw_pts[IPV6_FW_GETNSRCP(f)], | |
692 | IPV6_FW_GETNDSTP(f), dst_port, | |
693 | f->fw_flg & IPV6_FW_F_DRNG)) | |
694 | continue; | |
695 | break; | |
696 | } | |
697 | ||
698 | case IPPROTO_ICMPV6: | |
699 | { | |
700 | struct icmp6_hdr *icmp; | |
701 | ||
702 | if (offset != 0) /* Type isn't valid */ | |
703 | break; | |
704 | PULLUP_TO(off + 2); | |
705 | icmp = (struct icmp6_hdr *) ((caddr_t)ip6 + off); | |
706 | if (!icmp6type_match(icmp, f)) | |
707 | continue; | |
708 | break; | |
709 | } | |
710 | #undef PULLUP_TO | |
711 | ||
712 | bogusfrag: | |
713 | if (fw6_verbose) | |
714 | ip6fw_report(NULL, ip6, rif, oif, off, nxt); | |
715 | goto dropit; | |
716 | } | |
717 | ||
718 | got_match: | |
719 | #ifndef IP6FW_DIVERT_RESTART | |
720 | /* Ignore divert/tee rule if socket port is "ignport" */ | |
721 | switch (f->fw_flg & IPV6_FW_F_COMMAND) { | |
722 | case IPV6_FW_F_DIVERT: | |
723 | case IPV6_FW_F_TEE: | |
724 | if (f->fw_divert_port == ignport) | |
725 | continue; /* ignore this rule */ | |
726 | break; | |
727 | } | |
728 | ||
729 | #endif /* IP6FW_DIVERT_RESTART */ | |
730 | /* Update statistics */ | |
731 | f->fw_pcnt += 1; | |
732 | f->fw_bcnt += ntohs(ip6->ip6_plen); | |
733 | f->timestamp = timenow.tv_sec; | |
734 | ||
735 | /* Log to console if desired */ | |
736 | if ((f->fw_flg & IPV6_FW_F_PRN) && fw6_verbose) | |
737 | ip6fw_report(f, ip6, rif, oif, off, nxt); | |
738 | ||
739 | /* Take appropriate action */ | |
740 | switch (f->fw_flg & IPV6_FW_F_COMMAND) { | |
741 | case IPV6_FW_F_ACCEPT: | |
742 | return(0); | |
743 | case IPV6_FW_F_COUNT: | |
744 | continue; | |
745 | case IPV6_FW_F_DIVERT: | |
746 | #ifdef IP6FW_DIVERT_RESTART | |
747 | *cookie = f->fw_number; | |
748 | #else | |
749 | *cookie = htons(f->fw_divert_port); | |
750 | #endif /* IP6FW_DIVERT_RESTART */ | |
751 | return(f->fw_divert_port); | |
752 | case IPV6_FW_F_TEE: | |
753 | /* | |
754 | * XXX someday tee packet here, but beware that you | |
755 | * can't use m_copym() or m_copypacket() because | |
756 | * the divert input routine modifies the mbuf | |
757 | * (and these routines only increment reference | |
758 | * counts in the case of mbuf clusters), so need | |
759 | * to write custom routine. | |
760 | */ | |
761 | continue; | |
762 | case IPV6_FW_F_SKIPTO: | |
763 | #ifdef DIAGNOSTIC | |
764 | while (chain->chain.le_next | |
765 | && chain->chain.le_next->rule->fw_number | |
766 | < f->fw_skipto_rule) | |
767 | #else | |
768 | while (chain->chain.le_next->rule->fw_number | |
769 | < f->fw_skipto_rule) | |
770 | #endif | |
771 | chain = chain->chain.le_next; | |
772 | continue; | |
773 | } | |
774 | ||
775 | /* Deny/reject this packet using this rule */ | |
776 | rule = f; | |
777 | break; | |
778 | } | |
779 | ||
780 | #ifdef DIAGNOSTIC | |
781 | /* Rule 65535 should always be there and should always match */ | |
782 | if (!chain) | |
783 | panic("ip6_fw: chain"); | |
784 | #endif | |
785 | ||
786 | /* | |
787 | * At this point, we're going to drop the packet. | |
788 | * Send a reject notice if all of the following are true: | |
789 | * | |
790 | * - The packet matched a reject rule | |
791 | * - The packet is not an ICMP packet, or is an ICMP query packet | |
792 | * - The packet is not a multicast or broadcast packet | |
793 | */ | |
794 | if ((rule->fw_flg & IPV6_FW_F_COMMAND) == IPV6_FW_F_REJECT | |
795 | && (nxt != IPPROTO_ICMPV6 || is_icmp6_query(ip6, off)) | |
796 | && !((*m)->m_flags & (M_BCAST|M_MCAST)) | |
797 | && !IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst)) { | |
798 | switch (rule->fw_reject_code) { | |
799 | case IPV6_FW_REJECT_RST: | |
800 | { | |
801 | struct tcphdr *const tcp = | |
802 | (struct tcphdr *) ((caddr_t)ip6 + off); | |
803 | struct { | |
804 | struct ip6_hdr ip6; | |
805 | struct tcphdr th; | |
806 | } ti; | |
807 | tcp_seq ack, seq; | |
808 | int flags; | |
809 | ||
810 | if (offset != 0 || (tcp->th_flags & TH_RST)) | |
811 | break; | |
812 | ||
813 | ti.ip6 = *ip6; | |
814 | ti.th = *tcp; | |
815 | ti.th.th_seq = ntohl(ti.th.th_seq); | |
816 | ti.th.th_ack = ntohl(ti.th.th_ack); | |
817 | ti.ip6.ip6_nxt = IPPROTO_TCP; | |
818 | if (ti.th.th_flags & TH_ACK) { | |
819 | ack = 0; | |
820 | seq = ti.th.th_ack; | |
821 | flags = TH_RST; | |
822 | } else { | |
823 | ack = ti.th.th_seq; | |
824 | if (((*m)->m_flags & M_PKTHDR) != 0) { | |
825 | ack += (*m)->m_pkthdr.len - off | |
826 | - (ti.th.th_off << 2); | |
827 | } else if (ip6->ip6_plen) { | |
828 | ack += ntohs(ip6->ip6_plen) + sizeof(*ip6) | |
829 | - off - (ti.th.th_off << 2); | |
830 | } else { | |
831 | m_freem(*m); | |
832 | *m = 0; | |
833 | break; | |
834 | } | |
835 | seq = 0; | |
836 | flags = TH_RST|TH_ACK; | |
837 | } | |
838 | bcopy(&ti, ip6, sizeof(ti)); | |
839 | tcp_respond(NULL, ip6, (struct tcphdr *)(ip6 + 1), | |
840 | *m, ack, seq, flags); | |
841 | *m = NULL; | |
842 | break; | |
843 | } | |
844 | default: /* Send an ICMP unreachable using code */ | |
845 | if (oif) | |
846 | (*m)->m_pkthdr.rcvif = oif; | |
847 | lck_mtx_assert(ip6_mutex, LCK_MTX_ASSERT_OWNED); | |
848 | lck_mtx_unlock(ip6_mutex); | |
849 | icmp6_error(*m, ICMP6_DST_UNREACH, | |
850 | rule->fw_reject_code, 0); | |
851 | lck_mtx_lock(ip6_mutex); | |
852 | *m = NULL; | |
853 | break; | |
854 | } | |
855 | } | |
856 | ||
857 | dropit: | |
858 | /* | |
859 | * Finally, drop the packet. | |
860 | */ | |
861 | if (*m) { | |
862 | m_freem(*m); | |
863 | *m = NULL; | |
864 | } | |
865 | return(0); | |
866 | } | |
867 | ||
868 | static int | |
869 | add_entry6(struct ip6_fw_head *chainptr, struct ip6_fw *frwl) | |
870 | { | |
871 | struct ip6_fw *ftmp = 0; | |
872 | struct ip6_fw_chain *fwc = 0, *fcp, *fcpl = 0; | |
873 | u_short nbr = 0; | |
874 | int s; | |
875 | ||
876 | fwc = _MALLOC(sizeof *fwc, M_IP6FW, M_WAITOK); | |
877 | ftmp = _MALLOC(sizeof *ftmp, M_IP6FW, M_WAITOK); | |
878 | if (!fwc || !ftmp) { | |
879 | dprintf(("%s malloc said no\n", err_prefix)); | |
880 | if (fwc) FREE(fwc, M_IP6FW); | |
881 | if (ftmp) FREE(ftmp, M_IP6FW); | |
882 | return (ENOSPC); | |
883 | } | |
884 | ||
885 | bcopy(frwl, ftmp, sizeof(struct ip6_fw)); | |
886 | ftmp->fw_in_if.fu_via_if.name[IP6FW_IFNLEN - 1] = '\0'; | |
887 | ftmp->fw_pcnt = 0L; | |
888 | ftmp->fw_bcnt = 0L; | |
889 | fwc->rule = ftmp; | |
890 | ||
891 | s = splnet(); | |
892 | ||
893 | if (!chainptr->lh_first) { | |
894 | LIST_INSERT_HEAD(chainptr, fwc, chain); | |
895 | splx(s); | |
896 | return(0); | |
897 | } else if (ftmp->fw_number == (u_short)-1) { | |
898 | if (fwc) FREE(fwc, M_IP6FW); | |
899 | if (ftmp) FREE(ftmp, M_IP6FW); | |
900 | splx(s); | |
901 | dprintf(("%s bad rule number\n", err_prefix)); | |
902 | return (EINVAL); | |
903 | } | |
904 | ||
905 | /* If entry number is 0, find highest numbered rule and add 100 */ | |
906 | if (ftmp->fw_number == 0) { | |
907 | for (fcp = chainptr->lh_first; fcp; fcp = fcp->chain.le_next) { | |
908 | if (fcp->rule->fw_number != (u_short)-1) | |
909 | nbr = fcp->rule->fw_number; | |
910 | else | |
911 | break; | |
912 | } | |
913 | if (nbr < (u_short)-1 - 100) | |
914 | nbr += 100; | |
915 | ftmp->fw_number = nbr; | |
916 | } | |
917 | ||
918 | /* Got a valid number; now insert it, keeping the list ordered */ | |
919 | for (fcp = chainptr->lh_first; fcp; fcp = fcp->chain.le_next) { | |
920 | if (fcp->rule->fw_number > ftmp->fw_number) { | |
921 | if (fcpl) { | |
922 | LIST_INSERT_AFTER(fcpl, fwc, chain); | |
923 | } else { | |
924 | LIST_INSERT_HEAD(chainptr, fwc, chain); | |
925 | } | |
926 | break; | |
927 | } else { | |
928 | fcpl = fcp; | |
929 | } | |
930 | } | |
931 | ||
932 | splx(s); | |
933 | return (0); | |
934 | } | |
935 | ||
936 | static int | |
937 | del_entry6(struct ip6_fw_head *chainptr, u_short number) | |
938 | { | |
939 | struct ip6_fw_chain *fcp; | |
940 | int s; | |
941 | ||
942 | s = splnet(); | |
943 | ||
944 | fcp = chainptr->lh_first; | |
945 | if (number != (u_short)-1) { | |
946 | for (; fcp; fcp = fcp->chain.le_next) { | |
947 | if (fcp->rule->fw_number == number) { | |
948 | LIST_REMOVE(fcp, chain); | |
949 | splx(s); | |
950 | FREE(fcp->rule, M_IP6FW); | |
951 | FREE(fcp, M_IP6FW); | |
952 | return 0; | |
953 | } | |
954 | } | |
955 | } | |
956 | ||
957 | splx(s); | |
958 | return (EINVAL); | |
959 | } | |
960 | ||
961 | static int | |
962 | zero_entry6(struct ip6_fw *frwl) | |
963 | { | |
964 | struct ip6_fw_chain *fcp; | |
965 | int s; | |
966 | ||
967 | /* | |
968 | * It's possible to insert multiple chain entries with the | |
969 | * same number, so we don't stop after finding the first | |
970 | * match if zeroing a specific entry. | |
971 | */ | |
972 | s = splnet(); | |
973 | for (fcp = ip6_fw_chain.lh_first; fcp; fcp = fcp->chain.le_next) | |
974 | if (!frwl || frwl->fw_number == 0 || frwl->fw_number == fcp->rule->fw_number) { | |
975 | fcp->rule->fw_bcnt = fcp->rule->fw_pcnt = 0; | |
976 | fcp->rule->timestamp = 0; | |
977 | } | |
978 | splx(s); | |
979 | ||
980 | if (fw6_verbose) { | |
981 | if (frwl) | |
982 | log(LOG_AUTHPRIV | LOG_NOTICE, | |
983 | "ip6fw: Entry %d cleared.\n", frwl->fw_number); | |
984 | else | |
985 | log(LOG_AUTHPRIV | LOG_NOTICE, | |
986 | "ip6fw: Accounting cleared.\n"); | |
987 | } | |
988 | ||
989 | return(0); | |
990 | } | |
991 | ||
992 | static struct ip6_fw * | |
993 | check_ip6fw_struct(struct ip6_fw *frwl) | |
994 | { | |
995 | /* Check for invalid flag bits */ | |
996 | if ((frwl->fw_flg & ~IPV6_FW_F_MASK) != 0) { | |
997 | dprintf(("%s undefined flag bits set (flags=%x)\n", | |
998 | err_prefix, frwl->fw_flg)); | |
999 | return (NULL); | |
1000 | } | |
1001 | /* Must apply to incoming or outgoing (or both) */ | |
1002 | if (!(frwl->fw_flg & (IPV6_FW_F_IN | IPV6_FW_F_OUT))) { | |
1003 | dprintf(("%s neither in nor out\n", err_prefix)); | |
1004 | return (NULL); | |
1005 | } | |
1006 | /* Empty interface name is no good */ | |
1007 | if (((frwl->fw_flg & IPV6_FW_F_IIFNAME) | |
1008 | && !*frwl->fw_in_if.fu_via_if.name) | |
1009 | || ((frwl->fw_flg & IPV6_FW_F_OIFNAME) | |
1010 | && !*frwl->fw_out_if.fu_via_if.name)) { | |
1011 | dprintf(("%s empty interface name\n", err_prefix)); | |
1012 | return (NULL); | |
1013 | } | |
1014 | /* Sanity check interface matching */ | |
1015 | if ((frwl->fw_flg & IF6_FW_F_VIAHACK) == IF6_FW_F_VIAHACK) { | |
1016 | ; /* allow "via" backwards compatibility */ | |
1017 | } else if ((frwl->fw_flg & IPV6_FW_F_IN) | |
1018 | && (frwl->fw_flg & IPV6_FW_F_OIFACE)) { | |
1019 | dprintf(("%s outgoing interface check on incoming\n", | |
1020 | err_prefix)); | |
1021 | return (NULL); | |
1022 | } | |
1023 | /* Sanity check port ranges */ | |
1024 | if ((frwl->fw_flg & IPV6_FW_F_SRNG) && IPV6_FW_GETNSRCP(frwl) < 2) { | |
1025 | dprintf(("%s src range set but n_src_p=%d\n", | |
1026 | err_prefix, IPV6_FW_GETNSRCP(frwl))); | |
1027 | return (NULL); | |
1028 | } | |
1029 | if ((frwl->fw_flg & IPV6_FW_F_DRNG) && IPV6_FW_GETNDSTP(frwl) < 2) { | |
1030 | dprintf(("%s dst range set but n_dst_p=%d\n", | |
1031 | err_prefix, IPV6_FW_GETNDSTP(frwl))); | |
1032 | return (NULL); | |
1033 | } | |
1034 | if (IPV6_FW_GETNSRCP(frwl) + IPV6_FW_GETNDSTP(frwl) > IPV6_FW_MAX_PORTS) { | |
1035 | dprintf(("%s too many ports (%d+%d)\n", | |
1036 | err_prefix, IPV6_FW_GETNSRCP(frwl), IPV6_FW_GETNDSTP(frwl))); | |
1037 | return (NULL); | |
1038 | } | |
1039 | /* | |
1040 | * Protocols other than TCP/UDP don't use port range | |
1041 | */ | |
1042 | if ((frwl->fw_prot != IPPROTO_TCP) && | |
1043 | (frwl->fw_prot != IPPROTO_UDP) && | |
1044 | (IPV6_FW_GETNSRCP(frwl) || IPV6_FW_GETNDSTP(frwl))) { | |
1045 | dprintf(("%s port(s) specified for non TCP/UDP rule\n", | |
1046 | err_prefix)); | |
1047 | return(NULL); | |
1048 | } | |
1049 | ||
1050 | /* | |
1051 | * Rather than modify the entry to make such entries work, | |
1052 | * we reject this rule and require user level utilities | |
1053 | * to enforce whatever policy they deem appropriate. | |
1054 | */ | |
1055 | if ((frwl->fw_src.s6_addr32[0] & (~frwl->fw_smsk.s6_addr32[0])) || | |
1056 | (frwl->fw_src.s6_addr32[1] & (~frwl->fw_smsk.s6_addr32[1])) || | |
1057 | (frwl->fw_src.s6_addr32[2] & (~frwl->fw_smsk.s6_addr32[2])) || | |
1058 | (frwl->fw_src.s6_addr32[3] & (~frwl->fw_smsk.s6_addr32[3])) || | |
1059 | (frwl->fw_dst.s6_addr32[0] & (~frwl->fw_dmsk.s6_addr32[0])) || | |
1060 | (frwl->fw_dst.s6_addr32[1] & (~frwl->fw_dmsk.s6_addr32[1])) || | |
1061 | (frwl->fw_dst.s6_addr32[2] & (~frwl->fw_dmsk.s6_addr32[2])) || | |
1062 | (frwl->fw_dst.s6_addr32[3] & (~frwl->fw_dmsk.s6_addr32[3]))) { | |
1063 | dprintf(("%s rule never matches\n", err_prefix)); | |
1064 | return(NULL); | |
1065 | } | |
1066 | ||
1067 | if ((frwl->fw_flg & IPV6_FW_F_FRAG) && | |
1068 | (frwl->fw_prot == IPPROTO_UDP || frwl->fw_prot == IPPROTO_TCP)) { | |
1069 | if (frwl->fw_nports) { | |
1070 | dprintf(("%s cannot mix 'frag' and ports\n", err_prefix)); | |
1071 | return(NULL); | |
1072 | } | |
1073 | if (frwl->fw_prot == IPPROTO_TCP && | |
1074 | frwl->fw_tcpf != frwl->fw_tcpnf) { | |
1075 | dprintf(("%s cannot mix 'frag' with TCP flags\n", err_prefix)); | |
1076 | return(NULL); | |
1077 | } | |
1078 | } | |
1079 | ||
1080 | /* Check command specific stuff */ | |
1081 | switch (frwl->fw_flg & IPV6_FW_F_COMMAND) | |
1082 | { | |
1083 | case IPV6_FW_F_REJECT: | |
1084 | if (frwl->fw_reject_code >= 0x100 | |
1085 | && !(frwl->fw_prot == IPPROTO_TCP | |
1086 | && frwl->fw_reject_code == IPV6_FW_REJECT_RST)) { | |
1087 | dprintf(("%s unknown reject code\n", err_prefix)); | |
1088 | return(NULL); | |
1089 | } | |
1090 | break; | |
1091 | case IPV6_FW_F_DIVERT: /* Diverting to port zero is invalid */ | |
1092 | case IPV6_FW_F_TEE: | |
1093 | if (frwl->fw_divert_port == 0) { | |
1094 | dprintf(("%s can't divert to port 0\n", err_prefix)); | |
1095 | return (NULL); | |
1096 | } | |
1097 | break; | |
1098 | case IPV6_FW_F_DENY: | |
1099 | case IPV6_FW_F_ACCEPT: | |
1100 | case IPV6_FW_F_COUNT: | |
1101 | case IPV6_FW_F_SKIPTO: | |
1102 | break; | |
1103 | default: | |
1104 | dprintf(("%s invalid command\n", err_prefix)); | |
1105 | return(NULL); | |
1106 | } | |
1107 | ||
1108 | return frwl; | |
1109 | } | |
1110 | ||
1111 | /*#####*/ | |
1112 | #if 0 | |
1113 | static int | |
1114 | ip6_fw_ctl(int stage, struct mbuf **mm) | |
1115 | { | |
1116 | int error; | |
1117 | struct mbuf *m; | |
1118 | ||
1119 | if (stage == IPV6_FW_GET) { | |
1120 | struct ip6_fw_chain *fcp = ip6_fw_chain.lh_first; | |
1121 | *mm = m = m_get(M_WAIT, MT_DATA); /* XXX */ | |
1122 | if (!m) | |
1123 | return(ENOBUFS); | |
1124 | if (sizeof *(fcp->rule) > MLEN) { | |
1125 | MCLGET(m, M_WAIT); | |
1126 | if ((m->m_flags & M_EXT) == 0) { | |
1127 | m_free(m); | |
1128 | return(ENOBUFS); | |
1129 | } | |
1130 | } | |
1131 | for (; fcp; fcp = fcp->chain.le_next) { | |
1132 | bcopy(fcp->rule, m->m_data, sizeof *(fcp->rule)); | |
1133 | m->m_len = sizeof *(fcp->rule); | |
1134 | m->m_next = m_get(M_WAIT, MT_DATA); /* XXX */ | |
1135 | if (!m->m_next) { | |
1136 | m_freem(*mm); | |
1137 | return(ENOBUFS); | |
1138 | } | |
1139 | m = m->m_next; | |
1140 | if (sizeof *(fcp->rule) > MLEN) { | |
1141 | MCLGET(m, M_WAIT); | |
1142 | if ((m->m_flags & M_EXT) == 0) { | |
1143 | m_freem(*mm); | |
1144 | return(ENOBUFS); | |
1145 | } | |
1146 | } | |
1147 | m->m_len = 0; | |
1148 | } | |
1149 | return (0); | |
1150 | } | |
1151 | m = *mm; | |
1152 | /* only allow get calls if secure mode > 2 */ | |
1153 | if (securelevel > 2) { | |
1154 | if (m) { | |
1155 | (void)m_freem(m); | |
1156 | *mm = 0; | |
1157 | } | |
1158 | return(EPERM); | |
1159 | } | |
1160 | if (stage == IPV6_FW_FLUSH) { | |
1161 | while (ip6_fw_chain.lh_first != NULL && | |
1162 | ip6_fw_chain.lh_first->rule->fw_number != (u_short)-1) { | |
1163 | struct ip6_fw_chain *fcp = ip6_fw_chain.lh_first; | |
1164 | int s = splnet(); | |
1165 | LIST_REMOVE(ip6_fw_chain.lh_first, chain); | |
1166 | splx(s); | |
1167 | FREE(fcp->rule, M_IP6FW); | |
1168 | FREE(fcp, M_IP6FW); | |
1169 | } | |
1170 | if (m) { | |
1171 | (void)m_freem(m); | |
1172 | *mm = 0; | |
1173 | } | |
1174 | return (0); | |
1175 | } | |
1176 | if (stage == IPV6_FW_ZERO) { | |
1177 | error = zero_entry6(m); | |
1178 | if (m) { | |
1179 | (void)m_freem(m); | |
1180 | *mm = 0; | |
1181 | } | |
1182 | return (error); | |
1183 | } | |
1184 | if (m == NULL) { | |
1185 | printf("%s NULL mbuf ptr\n", err_prefix); | |
1186 | return (EINVAL); | |
1187 | } | |
1188 | ||
1189 | if (stage == IPV6_FW_ADD) { | |
1190 | struct ip6_fw *frwl = check_ip6fw_mbuf(m); | |
1191 | ||
1192 | if (!frwl) | |
1193 | error = EINVAL; | |
1194 | else | |
1195 | error = add_entry6(&ip6_fw_chain, frwl); | |
1196 | if (m) { | |
1197 | (void)m_freem(m); | |
1198 | *mm = 0; | |
1199 | } | |
1200 | return error; | |
1201 | } | |
1202 | if (stage == IPV6_FW_DEL) { | |
1203 | if (m->m_len != sizeof(struct ip6_fw)) { | |
1204 | dprintf(("%s len=%ld, want %lu\n", err_prefix, m->m_len, | |
1205 | sizeof(struct ip6_fw))); | |
1206 | error = EINVAL; | |
1207 | } else if (mtod(m, struct ip6_fw *)->fw_number == (u_short)-1) { | |
1208 | dprintf(("%s can't delete rule 65535\n", err_prefix)); | |
1209 | error = EINVAL; | |
1210 | } else | |
1211 | error = del_entry6(&ip6_fw_chain, | |
1212 | mtod(m, struct ip6_fw *)->fw_number); | |
1213 | if (m) { | |
1214 | (void)m_freem(m); | |
1215 | *mm = 0; | |
1216 | } | |
1217 | return error; | |
1218 | } | |
1219 | ||
1220 | dprintf(("%s unknown request %d\n", err_prefix, stage)); | |
1221 | if (m) { | |
1222 | (void)m_freem(m); | |
1223 | *mm = 0; | |
1224 | } | |
1225 | return (EINVAL); | |
1226 | } | |
1227 | #endif | |
1228 | ||
1229 | static int | |
1230 | ip6_fw_ctl(struct sockopt *sopt) | |
1231 | { | |
1232 | int error = 0; | |
1233 | int spl; | |
1234 | int valsize; | |
1235 | struct ip6_fw rule; | |
1236 | ||
1237 | if (securelevel >= 3 && | |
1238 | (sopt->sopt_dir != SOPT_GET || sopt->sopt_name != IPV6_FW_GET)) | |
1239 | return (EPERM); | |
1240 | ||
1241 | /* We ALWAYS expect the client to pass in a rule structure so that we can | |
1242 | * check the version of the API that they are using. In the case of a | |
1243 | * IPV6_FW_GET operation, the first rule of the output buffer passed to us | |
1244 | * must have the version set. */ | |
1245 | if (!sopt->sopt_val || sopt->sopt_valsize < sizeof rule) return EINVAL; | |
1246 | ||
1247 | /* save sopt->sopt_valsize */ | |
1248 | valsize = sopt->sopt_valsize; | |
1249 | if (error = sooptcopyin(sopt, &rule, sizeof(rule), sizeof(rule))) | |
1250 | return error; | |
1251 | ||
1252 | if (rule.version != IPV6_FW_CURRENT_API_VERSION) return EINVAL; | |
1253 | rule.version = 0xFFFFFFFF; /* version is meaningless once rules "make it in the door". */ | |
1254 | ||
1255 | switch (sopt->sopt_name) | |
1256 | { | |
1257 | case IPV6_FW_GET: | |
1258 | { | |
1259 | struct ip6_fw_chain *fcp; | |
1260 | struct ip6_fw *buf; | |
1261 | size_t size = 0; | |
1262 | ||
1263 | spl = splnet(); | |
1264 | LIST_FOREACH(fcp, &ip6_fw_chain, chain) | |
1265 | size += sizeof *buf; | |
1266 | ||
1267 | buf = _MALLOC(size, M_TEMP, M_WAITOK); | |
1268 | if (!buf) error = ENOBUFS; | |
1269 | else | |
1270 | { | |
1271 | struct ip6_fw *bp = buf; | |
1272 | LIST_FOREACH(fcp, &ip6_fw_chain, chain) | |
1273 | { | |
1274 | bcopy(fcp->rule, bp, sizeof *bp); | |
1275 | bp->version = IPV6_FW_CURRENT_API_VERSION; | |
1276 | bp++; | |
1277 | } | |
1278 | } | |
1279 | ||
1280 | splx(spl); | |
1281 | if (buf) | |
1282 | { | |
1283 | sopt->sopt_valsize = valsize; | |
1284 | error = sooptcopyout(sopt, buf, size); | |
1285 | FREE(buf, M_TEMP); | |
1286 | } | |
1287 | ||
1288 | break; | |
1289 | } | |
1290 | ||
1291 | case IPV6_FW_FLUSH: | |
1292 | spl = splnet(); | |
1293 | while (ip6_fw_chain.lh_first && | |
1294 | ip6_fw_chain.lh_first->rule->fw_number != (u_short)-1) | |
1295 | { | |
1296 | struct ip6_fw_chain *fcp = ip6_fw_chain.lh_first; | |
1297 | LIST_REMOVE(ip6_fw_chain.lh_first, chain); | |
1298 | FREE(fcp->rule, M_IP6FW); | |
1299 | FREE(fcp, M_IP6FW); | |
1300 | } | |
1301 | splx(spl); | |
1302 | break; | |
1303 | ||
1304 | case IPV6_FW_ZERO: | |
1305 | error = zero_entry6(&rule); | |
1306 | break; | |
1307 | ||
1308 | case IPV6_FW_ADD: | |
1309 | if (check_ip6fw_struct(&rule)) | |
1310 | error = add_entry6(&ip6_fw_chain, &rule); | |
1311 | else | |
1312 | error = EINVAL; | |
1313 | break; | |
1314 | ||
1315 | case IPV6_FW_DEL: | |
1316 | if (rule.fw_number == (u_short)-1) | |
1317 | { | |
1318 | dprintf(("%s can't delete rule 65535\n", err_prefix)); | |
1319 | error = EINVAL; | |
1320 | } | |
1321 | else | |
1322 | error = del_entry6(&ip6_fw_chain, rule.fw_number); | |
1323 | break; | |
1324 | ||
1325 | default: | |
1326 | dprintf(("%s invalid option %d\n", err_prefix, sopt->sopt_name)); | |
1327 | error = EINVAL; | |
1328 | } | |
1329 | ||
1330 | return error; | |
1331 | } | |
1332 | ||
1333 | void | |
1334 | ip6_fw_init(void) | |
1335 | { | |
1336 | struct ip6_fw default_rule; | |
1337 | ||
1338 | ip6_fw_chk_ptr = ip6_fw_chk; | |
1339 | ip6_fw_ctl_ptr = ip6_fw_ctl; | |
1340 | LIST_INIT(&ip6_fw_chain); | |
1341 | ||
1342 | bzero(&default_rule, sizeof default_rule); | |
1343 | default_rule.fw_prot = IPPROTO_IPV6; | |
1344 | default_rule.fw_number = (u_short)-1; | |
1345 | #ifdef IPV6FIREWALL_DEFAULT_TO_ACCEPT | |
1346 | default_rule.fw_flg |= IPV6_FW_F_ACCEPT; | |
1347 | #else | |
1348 | default_rule.fw_flg |= IPV6_FW_F_DENY; | |
1349 | #endif | |
1350 | default_rule.fw_flg |= IPV6_FW_F_IN | IPV6_FW_F_OUT; | |
1351 | if (check_ip6fw_struct(&default_rule) == NULL || | |
1352 | add_entry6(&ip6_fw_chain, &default_rule)) | |
1353 | panic(__FUNCTION__); | |
1354 | ||
1355 | printf("IPv6 packet filtering initialized, "); | |
1356 | #ifdef IPV6FIREWALL_DEFAULT_TO_ACCEPT | |
1357 | printf("default to accept, "); | |
1358 | #endif | |
1359 | #ifndef IPV6FIREWALL_VERBOSE | |
1360 | printf("logging disabled\n"); | |
1361 | #else | |
1362 | if (fw6_verbose_limit == 0) | |
1363 | printf("unlimited logging\n"); | |
1364 | else | |
1365 | printf("logging limited to %d packets/entry\n", | |
1366 | fw6_verbose_limit); | |
1367 | #endif | |
1368 | } | |
1369 |