Print this page
OS-5007 support SO_ATTACH_FILTER on ICMP sockets
Reviewed by: Cody Mello <melloc@joyent.com>
Reviewed by: Jerry Jelinek <jerry.jelinek@joyent.com>
Approved by: Jerry Jelinek <jerry.jelinek@joyent.com>

*** 20,29 **** --- 20,30 ---- */ /* * Copyright (c) 1991, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2013 by Delphix. All rights reserved. * Copyright 2014, OmniTI Computer Consulting, Inc. All rights reserved. + * Copyright 2016 Joyent, Inc. */ /* Copyright (c) 1990 Mentat Inc. */ #include <sys/types.h> #include <sys/stream.h>
*** 78,87 **** --- 79,89 ---- #include <sys/tsol/label.h> #include <sys/tsol/tnet.h> #include <inet/rawip_impl.h> + #include <net/bpf.h> #include <sys/disp.h> /* * Synchronization notes:
*** 1009,1018 **** --- 1011,1026 ---- if (icmp->icmp_filter != NULL) { kmem_free(icmp->icmp_filter, sizeof (icmp6_filter_t)); icmp->icmp_filter = NULL; } + if (icmp->icmp_bpf_len != 0) { + kmem_free(icmp->icmp_bpf_prog, icmp->icmp_bpf_len); + icmp->icmp_bpf_len = 0; + icmp->icmp_bpf_prog = NULL; + } + /* * Clear any fields which the kmem_cache constructor clears. * Only icmp_connp needs to be preserved. * TBD: We should make this more efficient to avoid clearing * everything.
*** 1962,1971 **** --- 1970,2077 ---- err = icmp_opt_get(connp, level, name, ptr); return (err); } + static int + icmp_attach_filter(icmp_t *icmp, uint_t inlen, const uchar_t *invalp) + { + struct bpf_program prog; + ip_bpf_insn_t *insns = NULL; + unsigned int size; + + #ifdef _LP64 + if (get_udatamodel() != DATAMODEL_NATIVE) { + struct bpf_program32 *prog32; + + if (inlen != sizeof (struct bpf_program32)) { + return (EINVAL); + } + prog32 = (struct bpf_program32 *)invalp; + prog.bf_len = prog32->bf_len; + prog.bf_insns = (void *)(uint64_t)prog32->bf_insns; + } else + #endif + if (inlen == sizeof (struct bpf_program)) { + bcopy(invalp, &prog, sizeof (prog)); + } else { + return (EINVAL); + } + + if (prog.bf_len > BPF_MAXINSNS || prog.bf_len == 0) { + return (EINVAL); + } + size = prog.bf_len * sizeof (struct bpf_insn); + insns = kmem_alloc(size, KM_SLEEP); + if (copyin(prog.bf_insns, insns, size) != 0) { + kmem_free(insns, size); + return (EFAULT); + } + if (!ip_bpf_validate(insns, prog.bf_len)) { + kmem_free(insns, size); + return (EINVAL); + } + + rw_enter(&icmp->icmp_bpf_lock, RW_WRITER); + if (icmp->icmp_bpf_len != 0) { + ASSERT(icmp->icmp_bpf_prog != NULL); + + kmem_free(icmp->icmp_bpf_prog, icmp->icmp_bpf_len); + } + icmp->icmp_bpf_len = size; + icmp->icmp_bpf_prog = insns; + rw_exit(&icmp->icmp_bpf_lock); + return (0); + } + + static int + icmp_detach_filter(icmp_t *icmp) + { + int error; + + rw_enter(&icmp->icmp_bpf_lock, RW_WRITER); + if (icmp->icmp_bpf_len == 0) { + ASSERT(icmp->icmp_bpf_prog == NULL); + error = ENOENT; + } else { + kmem_free(icmp->icmp_bpf_prog, + icmp->icmp_bpf_len); + icmp->icmp_bpf_len = 0; + icmp->icmp_bpf_prog = NULL; + error = 0; + } + rw_exit(&icmp->icmp_bpf_lock); + return (error); + } + + static boolean_t + icmp_eval_filter(icmp_t *icmp, mblk_t *mp, ip_recv_attr_t *ira) + { + boolean_t res; + uchar_t *buf = mp->b_rptr; + uint_t wirelen, len = MBLKL(mp); + + rw_enter(&icmp->icmp_bpf_lock, RW_READER); + if (icmp->icmp_bpf_len == 0) { + rw_exit(&icmp->icmp_bpf_lock); + return (B_FALSE); + } + if (ira->ira_flags & IRAF_IS_IPV4) { + ipha_t *ipha = (ipha_t *)buf; + + wirelen = ntohs(ipha->ipha_length); + } else { + ip6_t *ip6h = (ip6_t *)buf; + + wirelen = ntohs(ip6h->ip6_plen) + IPV6_HDR_LEN; + } + res = !ip_bpf_filter(icmp->icmp_bpf_prog, buf, wirelen, len); + rw_exit(&icmp->icmp_bpf_lock); + + return (res); + } + /* * This routine sets socket options. */ int icmp_do_opt_set(conn_opt_arg_t *coa, int level, int name,
*** 2051,2060 **** --- 2157,2170 ---- case SO_RCVBUF: if (*i1 > is->is_max_buf) { return (ENOBUFS); } break; + case SO_ATTACH_FILTER: + return (icmp_attach_filter(icmp, inlen, invalp)); + case SO_DETACH_FILTER: + return (icmp_detach_filter(icmp)); } break; case IPPROTO_IP: /*
*** 2596,2605 **** --- 2706,2723 ---- ASSERT(MBLKL(mp) >= ip_hdr_length); /* IP did a pullup */ /* Initialize regardless of IP version */ ipps.ipp_fields = 0; + /* Apply socket filter, if needed */ + if (icmp->icmp_bpf_len != 0) { + if (icmp_eval_filter(icmp, mp, ira)) { + freemsg(mp); + return; + } + } + if (ira->ira_flags & IRAF_IS_IPV4) { ASSERT(IPH_HDR_VERSION(rptr) == IPV4_VERSION); ASSERT(MBLKL(mp) >= sizeof (ipha_t)); ASSERT(ira->ira_ip_hdr_length == IPH_HDR_LENGTH(rptr));
*** 5025,5035 **** ldi_ident_release(is->is_ldi_ident); kmem_free(is, sizeof (*is)); } static void * ! rawip_kstat_init(netstackid_t stackid) { kstat_t *ksp; rawip_named_kstat_t template = { { "inDatagrams", KSTAT_DATA_UINT32, 0 }, { "inCksumErrs", KSTAT_DATA_UINT32, 0 }, --- 5143,5154 ---- ldi_ident_release(is->is_ldi_ident); kmem_free(is, sizeof (*is)); } static void * ! rawip_kstat_init(netstackid_t stackid) ! { kstat_t *ksp; rawip_named_kstat_t template = { { "inDatagrams", KSTAT_DATA_UINT32, 0 }, { "inCksumErrs", KSTAT_DATA_UINT32, 0 },
*** 5037,5049 **** { "outDatagrams", KSTAT_DATA_UINT32, 0 }, { "outErrors", KSTAT_DATA_UINT32, 0 }, }; ksp = kstat_create_netstack("icmp", 0, "rawip", "mib2", ! KSTAT_TYPE_NAMED, ! NUM_OF_FIELDS(rawip_named_kstat_t), ! 0, stackid); if (ksp == NULL || ksp->ks_data == NULL) return (NULL); bcopy(&template, ksp->ks_data, sizeof (template)); ksp->ks_update = rawip_kstat_update; --- 5156,5166 ---- { "outDatagrams", KSTAT_DATA_UINT32, 0 }, { "outErrors", KSTAT_DATA_UINT32, 0 }, }; ksp = kstat_create_netstack("icmp", 0, "rawip", "mib2", ! KSTAT_TYPE_NAMED, NUM_OF_FIELDS(rawip_named_kstat_t), 0, stackid); if (ksp == NULL || ksp->ks_data == NULL) return (NULL); bcopy(&template, ksp->ks_data, sizeof (template)); ksp->ks_update = rawip_kstat_update;