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