| /* |
| * CDDL HEADER START |
| * |
| * The contents of this file are subject to the terms of the |
| * Common Development and Distribution License (the "License"). |
| * You may not use this file except in compliance with the License. |
| * |
| * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE |
| * or http://www.opensolaris.org/os/licensing. |
| * See the License for the specific language governing permissions |
| * and limitations under the License. |
| * |
| * When distributing Covered Code, include this CDDL HEADER in each |
| * file and include the License file at usr/src/OPENSOLARIS.LICENSE. |
| * If applicable, add the following below this CDDL HEADER, with the |
| * fields enclosed by brackets "[]" replaced with your own identifying |
| * information: Portions Copyright [yyyy] [name of copyright owner] |
| * |
| * CDDL HEADER END |
| */ |
| /* |
| * Copyright 2006 Sun Microsystems, Inc. All rights reserved. |
| * Use is subject to license terms. |
| */ |
| |
| #pragma ident "%Z%%M% %I% %E% SMI" |
| |
| /* |
| * IPsec Security Policy Database. |
| * |
| * This module maintains the SPD and provides routines used by ip and ip6 |
| * to apply IPsec policy to inbound and outbound datagrams. |
| */ |
| |
| #include <sys/types.h> |
| #include <sys/stream.h> |
| #include <sys/stropts.h> |
| #include <sys/sysmacros.h> |
| #include <sys/strsubr.h> |
| #include <sys/strlog.h> |
| #include <sys/cmn_err.h> |
| #include <sys/zone.h> |
| |
| #include <sys/systm.h> |
| #include <sys/param.h> |
| #include <sys/kmem.h> |
| |
| #include <sys/crypto/api.h> |
| |
| #include <inet/common.h> |
| #include <inet/mi.h> |
| |
| #include <netinet/ip6.h> |
| #include <netinet/icmp6.h> |
| #include <netinet/udp.h> |
| |
| #include <inet/ip.h> |
| #include <inet/ip6.h> |
| |
| #include <net/pfkeyv2.h> |
| #include <net/pfpolicy.h> |
| #include <inet/ipsec_info.h> |
| #include <inet/sadb.h> |
| #include <inet/ipsec_impl.h> |
| #include <inet/ipsecah.h> |
| #include <inet/ipsecesp.h> |
| #include <inet/ipdrop.h> |
| #include <inet/ipclassifier.h> |
| |
| static void ipsec_update_present_flags(); |
| static ipsec_act_t *ipsec_act_wildcard_expand(ipsec_act_t *, uint_t *); |
| static void ipsec_out_free(void *); |
| static void ipsec_in_free(void *); |
| static boolean_t ipsec_init_inbound_sel(ipsec_selector_t *, mblk_t *, |
| ipha_t *, ip6_t *); |
| static mblk_t *ipsec_attach_global_policy(mblk_t *, conn_t *, |
| ipsec_selector_t *); |
| static mblk_t *ipsec_apply_global_policy(mblk_t *, conn_t *, |
| ipsec_selector_t *); |
| static mblk_t *ipsec_check_ipsecin_policy(queue_t *, mblk_t *, |
| ipsec_policy_t *, ipha_t *, ip6_t *); |
| static void ipsec_in_release_refs(ipsec_in_t *); |
| static void ipsec_out_release_refs(ipsec_out_t *); |
| static void ipsec_action_reclaim(void *); |
| static void ipsid_init(void); |
| static void ipsid_fini(void); |
| static boolean_t ipsec_check_ipsecin_action(struct ipsec_in_s *, mblk_t *, |
| struct ipsec_action_s *, ipha_t *ipha, ip6_t *ip6h, const char **, |
| kstat_named_t **); |
| static int32_t ipsec_act_ovhd(const ipsec_act_t *act); |
| static void ipsec_unregister_prov_update(void); |
| static boolean_t ipsec_compare_action(ipsec_policy_t *, ipsec_policy_t *); |
| static uint32_t selector_hash(ipsec_selector_t *); |
| |
| /* |
| * Policy rule index generator. We assume this won't wrap in the |
| * lifetime of a system. If we make 2^20 policy changes per second, |
| * this will last 2^44 seconds, or roughly 500,000 years, so we don't |
| * have to worry about reusing policy index values. |
| * |
| * Protected by ipsec_conf_lock. |
| */ |
| uint64_t ipsec_next_policy_index = 1; |
| |
| /* |
| * Active & Inactive system policy roots |
| */ |
| static ipsec_policy_head_t system_policy; |
| static ipsec_policy_head_t inactive_policy; |
| |
| /* Packet dropper for generic SPD drops. */ |
| static ipdropper_t spd_dropper; |
| |
| /* |
| * For now, use a trivially sized hash table for actions. |
| * In the future we can add the structure canonicalization necessary |
| * to get the hash function to behave correctly.. |
| */ |
| #define IPSEC_ACTION_HASH_SIZE 1 |
| |
| /* |
| * Selector hash table is statically sized at module load time. |
| * we default to 251 buckets, which is the largest prime number under 255 |
| */ |
| |
| #define IPSEC_SPDHASH_DEFAULT 251 |
| uint32_t ipsec_spd_hashsize = 0; |
| |
| #define IPSEC_SEL_NOHASH ((uint32_t)(~0)) |
| |
| static HASH_HEAD(ipsec_action_s) ipsec_action_hash[IPSEC_ACTION_HASH_SIZE]; |
| static HASH_HEAD(ipsec_sel) *ipsec_sel_hash; |
| |
| static kmem_cache_t *ipsec_action_cache; |
| static kmem_cache_t *ipsec_sel_cache; |
| static kmem_cache_t *ipsec_pol_cache; |
| static kmem_cache_t *ipsec_info_cache; |
| |
| boolean_t ipsec_inbound_v4_policy_present = B_FALSE; |
| boolean_t ipsec_outbound_v4_policy_present = B_FALSE; |
| boolean_t ipsec_inbound_v6_policy_present = B_FALSE; |
| boolean_t ipsec_outbound_v6_policy_present = B_FALSE; |
| |
| /* |
| * Because policy needs to know what algorithms are supported, keep the |
| * lists of algorithms here. |
| */ |
| |
| kmutex_t alg_lock; |
| uint8_t ipsec_nalgs[IPSEC_NALGTYPES]; |
| ipsec_alginfo_t *ipsec_alglists[IPSEC_NALGTYPES][IPSEC_MAX_ALGS]; |
| uint8_t ipsec_sortlist[IPSEC_NALGTYPES][IPSEC_MAX_ALGS]; |
| ipsec_algs_exec_mode_t ipsec_algs_exec_mode[IPSEC_NALGTYPES]; |
| static crypto_notify_handle_t prov_update_handle = NULL; |
| |
| int ipsec_hdr_pullup_needed = 0; |
| int ipsec_weird_null_inbound_policy = 0; |
| |
| #define ALGBITS_ROUND_DOWN(x, align) (((x)/(align))*(align)) |
| #define ALGBITS_ROUND_UP(x, align) ALGBITS_ROUND_DOWN((x)+(align)-1, align) |
| |
| /* |
| * Inbound traffic should have matching identities for both SA's. |
| */ |
| |
| #define SA_IDS_MATCH(sa1, sa2) \ |
| (((sa1) == NULL) || ((sa2) == NULL) || \ |
| (((sa1)->ipsa_src_cid == (sa2)->ipsa_src_cid) && \ |
| (((sa1)->ipsa_dst_cid == (sa2)->ipsa_dst_cid)))) |
| |
| #define IPPOL_UNCHAIN(php, ip) \ |
| HASHLIST_UNCHAIN((ip), ipsp_hash); \ |
| avl_remove(&(php)->iph_rulebyid, (ip)); \ |
| IPPOL_REFRELE(ip); |
| |
| /* |
| * Policy failure messages. |
| */ |
| static char *ipsec_policy_failure_msgs[] = { |
| |
| /* IPSEC_POLICY_NOT_NEEDED */ |
| "%s: Dropping the datagram because the incoming packet " |
| "is %s, but the recipient expects clear; Source %s, " |
| "Destination %s.\n", |
| |
| /* IPSEC_POLICY_MISMATCH */ |
| "%s: Policy Failure for the incoming packet (%s); Source %s, " |
| "Destination %s.\n", |
| |
| /* IPSEC_POLICY_AUTH_NOT_NEEDED */ |
| "%s: Authentication present while not expected in the " |
| "incoming %s packet; Source %s, Destination %s.\n", |
| |
| /* IPSEC_POLICY_ENCR_NOT_NEEDED */ |
| "%s: Encryption present while not expected in the " |
| "incoming %s packet; Source %s, Destination %s.\n", |
| |
| /* IPSEC_POLICY_SE_NOT_NEEDED */ |
| "%s: Self-Encapsulation present while not expected in the " |
| "incoming %s packet; Source %s, Destination %s.\n", |
| }; |
| /* |
| * Have a counter for every possible policy message in the previous array. |
| */ |
| static uint32_t ipsec_policy_failure_count[IPSEC_POLICY_MAX]; |
| /* Time since last ipsec policy failure that printed a message. */ |
| hrtime_t ipsec_policy_failure_last = 0; |
| |
| /* |
| * General overviews: |
| * |
| * Locking: |
| * |
| * All of the system policy structures are protected by a single |
| * rwlock, ipsec_conf_lock. These structures are threaded in a |
| * fairly complex fashion and are not expected to change on a |
| * regular basis, so this should not cause scaling/contention |
| * problems. As a result, policy checks should (hopefully) be MT-hot. |
| * |
| * Allocation policy: |
| * |
| * We use custom kmem cache types for the various |
| * bits & pieces of the policy data structures. All allocations |
| * use KM_NOSLEEP instead of KM_SLEEP for policy allocation. The |
| * policy table is of potentially unbounded size, so we don't |
| * want to provide a way to hog all system memory with policy |
| * entries.. |
| */ |
| |
| |
| /* |
| * AVL tree comparison function. |
| * the in-kernel avl assumes unique keys for all objects. |
| * Since sometimes policy will duplicate rules, we may insert |
| * multiple rules with the same rule id, so we need a tie-breaker. |
| */ |
| static int |
| ipsec_policy_cmpbyid(const void *a, const void *b) |
| { |
| const ipsec_policy_t *ipa, *ipb; |
| uint64_t idxa, idxb; |
| |
| ipa = (const ipsec_policy_t *)a; |
| ipb = (const ipsec_policy_t *)b; |
| idxa = ipa->ipsp_index; |
| idxb = ipb->ipsp_index; |
| |
| if (idxa < idxb) |
| return (-1); |
| if (idxa > idxb) |
| return (1); |
| /* |
| * Tie-breaker #1: All installed policy rules have a non-NULL |
| * ipsl_sel (selector set), so an entry with a NULL ipsp_sel is not |
| * actually in-tree but rather a template node being used in |
| * an avl_find query; see ipsec_policy_delete(). This gives us |
| * a placeholder in the ordering just before the the first entry with |
| * a key >= the one we're looking for, so we can walk forward from |
| * that point to get the remaining entries with the same id. |
| */ |
| if ((ipa->ipsp_sel == NULL) && (ipb->ipsp_sel != NULL)) |
| return (-1); |
| if ((ipb->ipsp_sel == NULL) && (ipa->ipsp_sel != NULL)) |
| return (1); |
| /* |
| * At most one of the arguments to the comparison should have a |
| * NULL selector pointer; if not, the tree is broken. |
| */ |
| ASSERT(ipa->ipsp_sel != NULL); |
| ASSERT(ipb->ipsp_sel != NULL); |
| /* |
| * Tie-breaker #2: use the virtual address of the policy node |
| * to arbitrarily break ties. Since we use the new tree node in |
| * the avl_find() in ipsec_insert_always, the new node will be |
| * inserted into the tree in the right place in the sequence. |
| */ |
| if (ipa < ipb) |
| return (-1); |
| if (ipa > ipb) |
| return (1); |
| return (0); |
| } |
| |
| static void |
| ipsec_polhead_free_table(ipsec_policy_head_t *iph) |
| { |
| int dir, nchains; |
| |
| nchains = ipsec_spd_hashsize; |
| |
| for (dir = 0; dir < IPSEC_NTYPES; dir++) { |
| ipsec_policy_root_t *ipr = &iph->iph_root[dir]; |
| |
| if (ipr->ipr_hash == NULL) |
| continue; |
| |
| kmem_free(ipr->ipr_hash, nchains * |
| sizeof (ipsec_policy_hash_t)); |
| } |
| } |
| |
| static void |
| ipsec_polhead_destroy(ipsec_policy_head_t *iph) |
| { |
| int dir; |
| |
| avl_destroy(&iph->iph_rulebyid); |
| rw_destroy(&iph->iph_lock); |
| |
| for (dir = 0; dir < IPSEC_NTYPES; dir++) { |
| ipsec_policy_root_t *ipr = &iph->iph_root[dir]; |
| int nchains = ipr->ipr_nchains; |
| int chain; |
| |
| for (chain = 0; chain < nchains; chain++) |
| mutex_destroy(&(ipr->ipr_hash[chain].hash_lock)); |
| |
| } |
| ipsec_polhead_free_table(iph); |
| } |
| |
| /* |
| * Module unload hook. |
| */ |
| void |
| ipsec_policy_destroy(void) |
| { |
| int i; |
| |
| ip_drop_unregister(&spd_dropper); |
| ip_drop_destroy(); |
| |
| ipsec_polhead_destroy(&system_policy); |
| ipsec_polhead_destroy(&inactive_policy); |
| |
| for (i = 0; i < IPSEC_ACTION_HASH_SIZE; i++) |
| mutex_destroy(&(ipsec_action_hash[i].hash_lock)); |
| |
| for (i = 0; i < ipsec_spd_hashsize; i++) |
| mutex_destroy(&(ipsec_sel_hash[i].hash_lock)); |
| |
| ipsec_unregister_prov_update(); |
| |
| mutex_destroy(&alg_lock); |
| |
| kmem_cache_destroy(ipsec_action_cache); |
| kmem_cache_destroy(ipsec_sel_cache); |
| kmem_cache_destroy(ipsec_pol_cache); |
| kmem_cache_destroy(ipsec_info_cache); |
| ipsid_gc(); |
| ipsid_fini(); |
| } |
| |
| |
| /* |
| * Called when table allocation fails to free the table. |
| */ |
| static int |
| ipsec_alloc_tables_failed() |
| { |
| if (ipsec_sel_hash != NULL) { |
| kmem_free(ipsec_sel_hash, ipsec_spd_hashsize * |
| sizeof (*ipsec_sel_hash)); |
| ipsec_sel_hash = NULL; |
| } |
| ipsec_polhead_free_table(&system_policy); |
| ipsec_polhead_free_table(&inactive_policy); |
| |
| return (ENOMEM); |
| } |
| |
| /* |
| * Attempt to allocate the tables in a single policy head. |
| * Return nonzero on failure after cleaning up any work in progress. |
| */ |
| static int |
| ipsec_alloc_table(ipsec_policy_head_t *iph, int kmflag) |
| { |
| int dir, nchains; |
| |
| nchains = ipsec_spd_hashsize; |
| |
| for (dir = 0; dir < IPSEC_NTYPES; dir++) { |
| ipsec_policy_root_t *ipr = &iph->iph_root[dir]; |
| |
| ipr->ipr_hash = kmem_zalloc(nchains * |
| sizeof (ipsec_policy_hash_t), kmflag); |
| if (ipr->ipr_hash == NULL) |
| return (ipsec_alloc_tables_failed()); |
| } |
| return (0); |
| } |
| |
| /* |
| * Attempt to allocate the various tables. Return nonzero on failure |
| * after cleaning up any work in progress. |
| */ |
| static int |
| ipsec_alloc_tables(int kmflag) |
| { |
| int error; |
| |
| error = ipsec_alloc_table(&system_policy, kmflag); |
| if (error != 0) |
| return (error); |
| |
| error = ipsec_alloc_table(&inactive_policy, kmflag); |
| if (error != 0) |
| return (error); |
| |
| ipsec_sel_hash = kmem_zalloc(ipsec_spd_hashsize * |
| sizeof (*ipsec_sel_hash), kmflag); |
| |
| if (ipsec_sel_hash == NULL) |
| return (ipsec_alloc_tables_failed()); |
| |
| return (0); |
| } |
| |
| /* |
| * After table allocation, initialize a policy head. |
| */ |
| static void |
| ipsec_polhead_init(ipsec_policy_head_t *iph) |
| { |
| int dir, chain, nchains; |
| |
| nchains = ipsec_spd_hashsize; |
| |
| rw_init(&iph->iph_lock, NULL, RW_DEFAULT, NULL); |
| avl_create(&iph->iph_rulebyid, ipsec_policy_cmpbyid, |
| sizeof (ipsec_policy_t), offsetof(ipsec_policy_t, ipsp_byid)); |
| |
| for (dir = 0; dir < IPSEC_NTYPES; dir++) { |
| ipsec_policy_root_t *ipr = &iph->iph_root[dir]; |
| ipr->ipr_nchains = nchains; |
| |
| for (chain = 0; chain < nchains; chain++) { |
| mutex_init(&(ipr->ipr_hash[chain].hash_lock), |
| NULL, MUTEX_DEFAULT, NULL); |
| } |
| } |
| } |
| |
| /* |
| * Module load hook. |
| */ |
| void |
| ipsec_policy_init() |
| { |
| int i; |
| |
| /* |
| * Make two attempts to allocate policy hash tables; try it at |
| * the "preferred" size (may be set in /etc/system) first, |
| * then fall back to the default size. |
| */ |
| if (ipsec_spd_hashsize == 0) |
| ipsec_spd_hashsize = IPSEC_SPDHASH_DEFAULT; |
| |
| if (ipsec_alloc_tables(KM_NOSLEEP) != 0) { |
| cmn_err(CE_WARN, |
| "Unable to allocate %d entry IPsec policy hash table", |
| ipsec_spd_hashsize); |
| ipsec_spd_hashsize = IPSEC_SPDHASH_DEFAULT; |
| cmn_err(CE_WARN, "Falling back to %d entries", |
| ipsec_spd_hashsize); |
| (void) ipsec_alloc_tables(KM_SLEEP); |
| } |
| |
| ipsid_init(); |
| ipsec_polhead_init(&system_policy); |
| ipsec_polhead_init(&inactive_policy); |
| |
| for (i = 0; i < IPSEC_ACTION_HASH_SIZE; i++) |
| mutex_init(&(ipsec_action_hash[i].hash_lock), |
| NULL, MUTEX_DEFAULT, NULL); |
| |
| for (i = 0; i < ipsec_spd_hashsize; i++) |
| mutex_init(&(ipsec_sel_hash[i].hash_lock), |
| NULL, MUTEX_DEFAULT, NULL); |
| |
| mutex_init(&alg_lock, NULL, MUTEX_DEFAULT, NULL); |
| |
| for (i = 0; i < IPSEC_NALGTYPES; i++) |
| ipsec_nalgs[i] = 0; |
| |
| ipsec_action_cache = kmem_cache_create("ipsec_actions", |
| sizeof (ipsec_action_t), _POINTER_ALIGNMENT, NULL, NULL, |
| ipsec_action_reclaim, NULL, NULL, 0); |
| ipsec_sel_cache = kmem_cache_create("ipsec_selectors", |
| sizeof (ipsec_sel_t), _POINTER_ALIGNMENT, NULL, NULL, |
| NULL, NULL, NULL, 0); |
| ipsec_pol_cache = kmem_cache_create("ipsec_policy", |
| sizeof (ipsec_policy_t), _POINTER_ALIGNMENT, NULL, NULL, |
| NULL, NULL, NULL, 0); |
| ipsec_info_cache = kmem_cache_create("ipsec_info", |
| sizeof (ipsec_info_t), _POINTER_ALIGNMENT, NULL, NULL, |
| NULL, NULL, NULL, 0); |
| |
| ip_drop_init(); |
| ip_drop_register(&spd_dropper, "IPsec SPD"); |
| } |
| |
| /* |
| * Sort algorithm lists. |
| * |
| * I may need to split this based on |
| * authentication/encryption, and I may wish to have an administrator |
| * configure this list. Hold on to some NDD variables... |
| * |
| * XXX For now, sort on minimum key size (GAG!). While minimum key size is |
| * not the ideal metric, it's the only quantifiable measure available. |
| * We need a better metric for sorting algorithms by preference. |
| */ |
| static void |
| alg_insert_sortlist(enum ipsec_algtype at, uint8_t algid) |
| { |
| ipsec_alginfo_t *ai = ipsec_alglists[at][algid]; |
| uint8_t holder, swap; |
| uint_t i; |
| uint_t count = ipsec_nalgs[at]; |
| ASSERT(ai != NULL); |
| ASSERT(algid == ai->alg_id); |
| |
| ASSERT(MUTEX_HELD(&alg_lock)); |
| |
| holder = algid; |
| |
| for (i = 0; i < count - 1; i++) { |
| ipsec_alginfo_t *alt; |
| |
| alt = ipsec_alglists[at][ipsec_sortlist[at][i]]; |
| /* |
| * If you want to give precedence to newly added algs, |
| * add the = in the > comparison. |
| */ |
| if ((holder != algid) || (ai->alg_minbits > alt->alg_minbits)) { |
| /* Swap sortlist[i] and holder. */ |
| swap = ipsec_sortlist[at][i]; |
| ipsec_sortlist[at][i] = holder; |
| holder = swap; |
| ai = alt; |
| } /* Else just continue. */ |
| } |
| |
| /* Store holder in last slot. */ |
| ipsec_sortlist[at][i] = holder; |
| } |
| |
| /* |
| * Remove an algorithm from a sorted algorithm list. |
| * This should be considerably easier, even with complex sorting. |
| */ |
| static void |
| alg_remove_sortlist(enum ipsec_algtype at, uint8_t algid) |
| { |
| boolean_t copyback = B_FALSE; |
| int i; |
| int newcount = ipsec_nalgs[at]; |
| |
| ASSERT(MUTEX_HELD(&alg_lock)); |
| |
| for (i = 0; i <= newcount; i++) { |
| if (copyback) |
| ipsec_sortlist[at][i-1] = ipsec_sortlist[at][i]; |
| else if (ipsec_sortlist[at][i] == algid) |
| copyback = B_TRUE; |
| } |
| } |
| |
| /* |
| * Add the specified algorithm to the algorithm tables. |
| * Must be called while holding the algorithm table writer lock. |
| */ |
| void |
| ipsec_alg_reg(ipsec_algtype_t algtype, ipsec_alginfo_t *alg) |
| { |
| ASSERT(MUTEX_HELD(&alg_lock)); |
| |
| ASSERT(ipsec_alglists[algtype][alg->alg_id] == NULL); |
| ipsec_alg_fix_min_max(alg, algtype); |
| ipsec_alglists[algtype][alg->alg_id] = alg; |
| |
| ipsec_nalgs[algtype]++; |
| alg_insert_sortlist(algtype, alg->alg_id); |
| } |
| |
| /* |
| * Remove the specified algorithm from the algorithm tables. |
| * Must be called while holding the algorithm table writer lock. |
| */ |
| void |
| ipsec_alg_unreg(ipsec_algtype_t algtype, uint8_t algid) |
| { |
| ASSERT(MUTEX_HELD(&alg_lock)); |
| |
| ASSERT(ipsec_alglists[algtype][algid] != NULL); |
| ipsec_alg_free(ipsec_alglists[algtype][algid]); |
| ipsec_alglists[algtype][algid] = NULL; |
| |
| ipsec_nalgs[algtype]--; |
| alg_remove_sortlist(algtype, algid); |
| } |
| |
| /* |
| * Hooks for spdsock to get a grip on system policy. |
| */ |
| |
| ipsec_policy_head_t * |
| ipsec_system_policy(void) |
| { |
| ipsec_policy_head_t *h = &system_policy; |
| IPPH_REFHOLD(h); |
| return (h); |
| } |
| |
| ipsec_policy_head_t * |
| ipsec_inactive_policy(void) |
| { |
| ipsec_policy_head_t *h = &inactive_policy; |
| IPPH_REFHOLD(h); |
| return (h); |
| } |
| |
| /* |
| * Lock inactive policy, then active policy, then exchange policy root |
| * pointers. |
| */ |
| void |
| ipsec_swap_policy(void) |
| { |
| int af, dir; |
| avl_tree_t r1, r2; |
| |
| rw_enter(&inactive_policy.iph_lock, RW_WRITER); |
| rw_enter(&system_policy.iph_lock, RW_WRITER); |
| |
| r1 = system_policy.iph_rulebyid; |
| r2 = inactive_policy.iph_rulebyid; |
| system_policy.iph_rulebyid = r2; |
| inactive_policy.iph_rulebyid = r1; |
| |
| for (dir = 0; dir < IPSEC_NTYPES; dir++) { |
| ipsec_policy_hash_t *h1, *h2; |
| |
| h1 = system_policy.iph_root[dir].ipr_hash; |
| h2 = inactive_policy.iph_root[dir].ipr_hash; |
| system_policy.iph_root[dir].ipr_hash = h2; |
| inactive_policy.iph_root[dir].ipr_hash = h1; |
| |
| for (af = 0; af < IPSEC_NAF; af++) { |
| ipsec_policy_t *t1, *t2; |
| |
| t1 = system_policy.iph_root[dir].ipr_nonhash[af]; |
| t2 = inactive_policy.iph_root[dir].ipr_nonhash[af]; |
| system_policy.iph_root[dir].ipr_nonhash[af] = t2; |
| inactive_policy.iph_root[dir].ipr_nonhash[af] = t1; |
| if (t1 != NULL) { |
| t1->ipsp_hash.hash_pp = |
| &(inactive_policy.iph_root[dir]. |
| ipr_nonhash[af]); |
| } |
| if (t2 != NULL) { |
| t2->ipsp_hash.hash_pp = |
| &(system_policy.iph_root[dir]. |
| ipr_nonhash[af]); |
| } |
| |
| } |
| } |
| system_policy.iph_gen++; |
| inactive_policy.iph_gen++; |
| ipsec_update_present_flags(); |
| rw_exit(&system_policy.iph_lock); |
| rw_exit(&inactive_policy.iph_lock); |
| } |
| |
| /* |
| * Clone one policy rule.. |
| */ |
| static ipsec_policy_t * |
| ipsec_copy_policy(const ipsec_policy_t *src) |
| { |
| ipsec_policy_t *dst = kmem_cache_alloc(ipsec_pol_cache, KM_NOSLEEP); |
| |
| if (dst == NULL) |
| return (NULL); |
| |
| /* |
| * Adjust refcounts of cloned state. |
| */ |
| IPACT_REFHOLD(src->ipsp_act); |
| src->ipsp_sel->ipsl_refs++; |
| |
| HASH_NULL(dst, ipsp_hash); |
| dst->ipsp_refs = 1; |
| dst->ipsp_sel = src->ipsp_sel; |
| dst->ipsp_act = src->ipsp_act; |
| dst->ipsp_prio = src->ipsp_prio; |
| dst->ipsp_index = src->ipsp_index; |
| |
| return (dst); |
| } |
| |
| void |
| ipsec_insert_always(avl_tree_t *tree, void *new_node) |
| { |
| void *node; |
| avl_index_t where; |
| |
| node = avl_find(tree, new_node, &where); |
| ASSERT(node == NULL); |
| avl_insert(tree, new_node, where); |
| } |
| |
| |
| static int |
| ipsec_copy_chain(ipsec_policy_head_t *dph, ipsec_policy_t *src, |
| ipsec_policy_t **dstp) |
| { |
| for (; src != NULL; src = src->ipsp_hash.hash_next) { |
| ipsec_policy_t *dst = ipsec_copy_policy(src); |
| if (dst == NULL) |
| return (ENOMEM); |
| |
| HASHLIST_INSERT(dst, ipsp_hash, *dstp); |
| ipsec_insert_always(&dph->iph_rulebyid, dst); |
| } |
| return (0); |
| } |
| |
| |
| |
| /* |
| * Make one policy head look exactly like another. |
| * |
| * As with ipsec_swap_policy, we lock the destination policy head first, then |
| * the source policy head. Note that we only need to read-lock the source |
| * policy head as we are not changing it. |
| */ |
| static int |
| ipsec_copy_polhead(ipsec_policy_head_t *sph, ipsec_policy_head_t *dph) |
| { |
| int af, dir, chain, nchains; |
| |
| rw_enter(&dph->iph_lock, RW_WRITER); |
| |
| ipsec_polhead_flush(dph); |
| |
| rw_enter(&sph->iph_lock, RW_READER); |
| |
| for (dir = 0; dir < IPSEC_NTYPES; dir++) { |
| ipsec_policy_root_t *dpr = &dph->iph_root[dir]; |
| ipsec_policy_root_t *spr = &sph->iph_root[dir]; |
| nchains = dpr->ipr_nchains; |
| |
| ASSERT(dpr->ipr_nchains == spr->ipr_nchains); |
| |
| for (af = 0; af < IPSEC_NAF; af++) { |
| if (ipsec_copy_chain(dph, spr->ipr_nonhash[af], |
| &dpr->ipr_nonhash[af])) |
| goto abort_copy; |
| } |
| |
| for (chain = 0; chain < nchains; chain++) { |
| if (ipsec_copy_chain(dph, |
| spr->ipr_hash[chain].hash_head, |
| &dpr->ipr_hash[chain].hash_head)) |
| goto abort_copy; |
| } |
| } |
| |
| dph->iph_gen++; |
| |
| rw_exit(&sph->iph_lock); |
| rw_exit(&dph->iph_lock); |
| return (0); |
| |
| abort_copy: |
| ipsec_polhead_flush(dph); |
| rw_exit(&sph->iph_lock); |
| rw_exit(&dph->iph_lock); |
| return (ENOMEM); |
| } |
| |
| /* |
| * Clone currently active policy to the inactive policy list. |
| */ |
| int |
| ipsec_clone_system_policy(void) |
| { |
| return (ipsec_copy_polhead(&system_policy, &inactive_policy)); |
| } |
| |
| |
| /* |
| * Extract the string from ipsec_policy_failure_msgs[type] and |
| * log it. |
| * |
| */ |
| void |
| ipsec_log_policy_failure(queue_t *q, int type, char *func_name, ipha_t *ipha, |
| ip6_t *ip6h, boolean_t secure) |
| { |
| char sbuf[INET6_ADDRSTRLEN]; |
| char dbuf[INET6_ADDRSTRLEN]; |
| char *s; |
| char *d; |
| short mid = 0; |
| |
| ASSERT((ipha == NULL && ip6h != NULL) || |
| (ip6h == NULL && ipha != NULL)); |
| |
| if (ipha != NULL) { |
| s = inet_ntop(AF_INET, &ipha->ipha_src, sbuf, sizeof (sbuf)); |
| d = inet_ntop(AF_INET, &ipha->ipha_dst, dbuf, sizeof (dbuf)); |
| } else { |
| s = inet_ntop(AF_INET6, &ip6h->ip6_src, sbuf, sizeof (sbuf)); |
| d = inet_ntop(AF_INET6, &ip6h->ip6_dst, dbuf, sizeof (dbuf)); |
| |
| } |
| |
| /* Always bump the policy failure counter. */ |
| ipsec_policy_failure_count[type]++; |
| |
| if (q != NULL) { |
| mid = q->q_qinfo->qi_minfo->mi_idnum; |
| } |
| ipsec_rl_strlog(mid, 0, 0, SL_ERROR|SL_WARN|SL_CONSOLE, |
| ipsec_policy_failure_msgs[type], |
| func_name, |
| (secure ? "secure" : "not secure"), s, d); |
| } |
| |
| /* |
| * Rate-limiting front-end to strlog() for AH and ESP. Uses the ndd variables |
| * in /dev/ip and the same rate-limiting clock so that there's a single |
| * knob to turn to throttle the rate of messages. |
| */ |
| void |
| ipsec_rl_strlog(short mid, short sid, char level, ushort_t sl, char *fmt, ...) |
| { |
| va_list adx; |
| hrtime_t current = gethrtime(); |
| |
| sl |= SL_CONSOLE; |
| /* |
| * Throttle logging to stop syslog from being swamped. If variable |
| * 'ipsec_policy_log_interval' is zero, don't log any messages at |
| * all, otherwise log only one message every 'ipsec_policy_log_interval' |
| * msec. Convert interval (in msec) to hrtime (in nsec). |
| */ |
| |
| if (ipsec_policy_log_interval) { |
| if (ipsec_policy_failure_last + |
| ((hrtime_t)ipsec_policy_log_interval * (hrtime_t)1000000) <= |
| current) { |
| va_start(adx, fmt); |
| (void) vstrlog(mid, sid, level, sl, fmt, adx); |
| va_end(adx); |
| ipsec_policy_failure_last = current; |
| } |
| } |
| } |
| |
| void |
| ipsec_config_flush() |
| { |
| rw_enter(&system_policy.iph_lock, RW_WRITER); |
| ipsec_polhead_flush(&system_policy); |
| ipsec_next_policy_index = 1; |
| rw_exit(&system_policy.iph_lock); |
| ipsec_action_reclaim(0); |
| } |
| |
| /* |
| * Clip a policy's min/max keybits vs. the capabilities of the |
| * algorithm. |
| */ |
| static void |
| act_alg_adjust(uint_t algtype, uint_t algid, |
| uint16_t *minbits, uint16_t *maxbits) |
| { |
| ipsec_alginfo_t *algp = ipsec_alglists[algtype][algid]; |
| if (algp != NULL) { |
| /* |
| * If passed-in minbits is zero, we assume the caller trusts |
| * us with setting the minimum key size. We pick the |
| * algorithms DEFAULT key size for the minimum in this case. |
| */ |
| if (*minbits == 0) { |
| *minbits = algp->alg_default_bits; |
| ASSERT(*minbits >= algp->alg_minbits); |
| } else { |
| *minbits = MAX(*minbits, algp->alg_minbits); |
| } |
| if (*maxbits == 0) |
| *maxbits = algp->alg_maxbits; |
| else |
| *maxbits = MIN(*maxbits, algp->alg_maxbits); |
| ASSERT(*minbits <= *maxbits); |
| } else { |
| *minbits = 0; |
| *maxbits = 0; |
| } |
| } |
| |
| /* |
| * Check an action's requested algorithms against the algorithms currently |
| * loaded in the system. |
| */ |
| boolean_t |
| ipsec_check_action(ipsec_act_t *act, int *diag) |
| { |
| ipsec_prot_t *ipp; |
| |
| ipp = &act->ipa_apply; |
| |
| if (ipp->ipp_use_ah && |
| ipsec_alglists[IPSEC_ALG_AUTH][ipp->ipp_auth_alg] == NULL) { |
| *diag = SPD_DIAGNOSTIC_UNSUPP_AH_ALG; |
| return (B_FALSE); |
| } |
| if (ipp->ipp_use_espa && |
| ipsec_alglists[IPSEC_ALG_AUTH][ipp->ipp_esp_auth_alg] == NULL) { |
| *diag = SPD_DIAGNOSTIC_UNSUPP_ESP_AUTH_ALG; |
| return (B_FALSE); |
| } |
| if (ipp->ipp_use_esp && |
| ipsec_alglists[IPSEC_ALG_ENCR][ipp->ipp_encr_alg] == NULL) { |
| *diag = SPD_DIAGNOSTIC_UNSUPP_ESP_ENCR_ALG; |
| return (B_FALSE); |
| } |
| |
| act_alg_adjust(IPSEC_ALG_AUTH, ipp->ipp_auth_alg, |
| &ipp->ipp_ah_minbits, &ipp->ipp_ah_maxbits); |
| act_alg_adjust(IPSEC_ALG_AUTH, ipp->ipp_esp_auth_alg, |
| &ipp->ipp_espa_minbits, &ipp->ipp_espa_maxbits); |
| act_alg_adjust(IPSEC_ALG_ENCR, ipp->ipp_encr_alg, |
| &ipp->ipp_espe_minbits, &ipp->ipp_espe_maxbits); |
| |
| if (ipp->ipp_ah_minbits > ipp->ipp_ah_maxbits) { |
| *diag = SPD_DIAGNOSTIC_UNSUPP_AH_KEYSIZE; |
| return (B_FALSE); |
| } |
| if (ipp->ipp_espa_minbits > ipp->ipp_espa_maxbits) { |
| *diag = SPD_DIAGNOSTIC_UNSUPP_ESP_AUTH_KEYSIZE; |
| return (B_FALSE); |
| } |
| if (ipp->ipp_espe_minbits > ipp->ipp_espe_maxbits) { |
| *diag = SPD_DIAGNOSTIC_UNSUPP_ESP_ENCR_KEYSIZE; |
| return (B_FALSE); |
| } |
| /* TODO: sanity check lifetimes */ |
| return (B_TRUE); |
| } |
| |
| /* |
| * Set up a single action during wildcard expansion.. |
| */ |
| static void |
| ipsec_setup_act(ipsec_act_t *outact, ipsec_act_t *act, |
| uint_t auth_alg, uint_t encr_alg, uint_t eauth_alg) |
| { |
| ipsec_prot_t *ipp; |
| |
| *outact = *act; |
| ipp = &outact->ipa_apply; |
| ipp->ipp_auth_alg = (uint8_t)auth_alg; |
| ipp->ipp_encr_alg = (uint8_t)encr_alg; |
| ipp->ipp_esp_auth_alg = (uint8_t)eauth_alg; |
| |
| act_alg_adjust(IPSEC_ALG_AUTH, auth_alg, |
| &ipp->ipp_ah_minbits, &ipp->ipp_ah_maxbits); |
| act_alg_adjust(IPSEC_ALG_AUTH, eauth_alg, |
| &ipp->ipp_espa_minbits, &ipp->ipp_espa_maxbits); |
| act_alg_adjust(IPSEC_ALG_ENCR, encr_alg, |
| &ipp->ipp_espe_minbits, &ipp->ipp_espe_maxbits); |
| } |
| |
| /* |
| * combinatoric expansion time: expand a wildcarded action into an |
| * array of wildcarded actions; we return the exploded action list, |
| * and return a count in *nact (output only). |
| */ |
| static ipsec_act_t * |
| ipsec_act_wildcard_expand(ipsec_act_t *act, uint_t *nact) |
| { |
| boolean_t use_ah, use_esp, use_espa; |
| boolean_t wild_auth, wild_encr, wild_eauth; |
| uint_t auth_alg, auth_idx, auth_min, auth_max; |
| uint_t eauth_alg, eauth_idx, eauth_min, eauth_max; |
| uint_t encr_alg, encr_idx, encr_min, encr_max; |
| uint_t action_count, ai; |
| ipsec_act_t *outact; |
| |
| if (act->ipa_type != IPSEC_ACT_APPLY) { |
| outact = kmem_alloc(sizeof (*act), KM_NOSLEEP); |
| *nact = 1; |
| if (outact != NULL) |
| bcopy(act, outact, sizeof (*act)); |
| return (outact); |
| } |
| /* |
| * compute the combinatoric explosion.. |
| * |
| * we assume a request for encr if esp_req is PREF_REQUIRED |
| * we assume a request for ah auth if ah_req is PREF_REQUIRED. |
| * we assume a request for esp auth if !ah and esp_req is PREF_REQUIRED |
| */ |
| |
| use_ah = act->ipa_apply.ipp_use_ah; |
| use_esp = act->ipa_apply.ipp_use_esp; |
| use_espa = act->ipa_apply.ipp_use_espa; |
| auth_alg = act->ipa_apply.ipp_auth_alg; |
| eauth_alg = act->ipa_apply.ipp_esp_auth_alg; |
| encr_alg = act->ipa_apply.ipp_encr_alg; |
| |
| wild_auth = use_ah && (auth_alg == 0); |
| wild_eauth = use_espa && (eauth_alg == 0); |
| wild_encr = use_esp && (encr_alg == 0); |
| |
| action_count = 1; |
| auth_min = auth_max = auth_alg; |
| eauth_min = eauth_max = eauth_alg; |
| encr_min = encr_max = encr_alg; |
| |
| /* |
| * set up for explosion.. for each dimension, expand output |
| * size by the explosion factor. |
| * |
| * Don't include the "any" algorithms, if defined, as no |
| * kernel policies should be set for these algorithms. |
| */ |
| |
| #define SET_EXP_MINMAX(type, wild, alg, min, max) if (wild) { \ |
| int nalgs = ipsec_nalgs[type]; \ |
| if (ipsec_alglists[type][alg] != NULL) \ |
| nalgs--; \ |
| action_count *= nalgs; \ |
| min = 0; \ |
| max = ipsec_nalgs[type] - 1; \ |
| } |
| |
| SET_EXP_MINMAX(IPSEC_ALG_AUTH, wild_auth, SADB_AALG_NONE, |
| auth_min, auth_max); |
| SET_EXP_MINMAX(IPSEC_ALG_AUTH, wild_eauth, SADB_AALG_NONE, |
| eauth_min, eauth_max); |
| SET_EXP_MINMAX(IPSEC_ALG_ENCR, wild_encr, SADB_EALG_NONE, |
| encr_min, encr_max); |
| |
| #undef SET_EXP_MINMAX |
| |
| /* |
| * ok, allocate the whole mess.. |
| */ |
| |
| outact = kmem_alloc(sizeof (*outact) * action_count, KM_NOSLEEP); |
| if (outact == NULL) |
| return (NULL); |
| |
| /* |
| * Now compute all combinations. Note that non-wildcarded |
| * dimensions just get a single value from auth_min, while |
| * wildcarded dimensions indirect through the sortlist. |
| * |
| * We do encryption outermost since, at this time, there's |
| * greater difference in security and performance between |
| * encryption algorithms vs. authentication algorithms. |
| */ |
| |
| ai = 0; |
| |
| #define WHICH_ALG(type, wild, idx) ((wild)?(ipsec_sortlist[type][idx]):(idx)) |
| |
| for (encr_idx = encr_min; encr_idx <= encr_max; encr_idx++) { |
| encr_alg = WHICH_ALG(IPSEC_ALG_ENCR, wild_encr, encr_idx); |
| if (wild_encr && encr_alg == SADB_EALG_NONE) |
| continue; |
| for (auth_idx = auth_min; auth_idx <= auth_max; auth_idx++) { |
| auth_alg = WHICH_ALG(IPSEC_ALG_AUTH, wild_auth, |
| auth_idx); |
| if (wild_auth && auth_alg == SADB_AALG_NONE) |
| continue; |
| for (eauth_idx = eauth_min; eauth_idx <= eauth_max; |
| eauth_idx++) { |
| eauth_alg = WHICH_ALG(IPSEC_ALG_AUTH, |
| wild_eauth, eauth_idx); |
| if (wild_eauth && eauth_alg == SADB_AALG_NONE) |
| continue; |
| |
| ipsec_setup_act(&outact[ai], act, |
| auth_alg, encr_alg, eauth_alg); |
| ai++; |
| } |
| } |
| } |
| |
| #undef WHICH_ALG |
| |
| ASSERT(ai == action_count); |
| *nact = action_count; |
| return (outact); |
| } |
| |
| /* |
| * Extract the parts of an ipsec_prot_t from an old-style ipsec_req_t. |
| */ |
| static void |
| ipsec_prot_from_req(ipsec_req_t *req, ipsec_prot_t *ipp) |
| { |
| bzero(ipp, sizeof (*ipp)); |
| /* |
| * ipp_use_* are bitfields. Look at "!!" in the following as a |
| * "boolean canonicalization" operator. |
| */ |
| ipp->ipp_use_ah = !!(req->ipsr_ah_req & IPSEC_PREF_REQUIRED); |
| ipp->ipp_use_esp = !!(req->ipsr_esp_req & IPSEC_PREF_REQUIRED); |
| ipp->ipp_use_espa = !!(req->ipsr_esp_auth_alg) || !ipp->ipp_use_ah; |
| ipp->ipp_use_se = !!(req->ipsr_self_encap_req & IPSEC_PREF_REQUIRED); |
| ipp->ipp_use_unique = !!((req->ipsr_ah_req|req->ipsr_esp_req) & |
| IPSEC_PREF_UNIQUE); |
| ipp->ipp_encr_alg = req->ipsr_esp_alg; |
| ipp->ipp_auth_alg = req->ipsr_auth_alg; |
| ipp->ipp_esp_auth_alg = req->ipsr_esp_auth_alg; |
| } |
| |
| /* |
| * Extract a new-style action from a request. |
| */ |
| void |
| ipsec_actvec_from_req(ipsec_req_t *req, ipsec_act_t **actp, uint_t *nactp) |
| { |
| struct ipsec_act act; |
| bzero(&act, sizeof (act)); |
| if ((req->ipsr_ah_req & IPSEC_PREF_NEVER) && |
| (req->ipsr_esp_req & IPSEC_PREF_NEVER)) { |
| act.ipa_type = IPSEC_ACT_BYPASS; |
| } else { |
| act.ipa_type = IPSEC_ACT_APPLY; |
| ipsec_prot_from_req(req, &act.ipa_apply); |
| } |
| *actp = ipsec_act_wildcard_expand(&act, nactp); |
| } |
| |
| /* |
| * Convert a new-style "prot" back to an ipsec_req_t (more backwards compat). |
| * We assume caller has already zero'ed *req for us. |
| */ |
| static int |
| ipsec_req_from_prot(ipsec_prot_t *ipp, ipsec_req_t *req) |
| { |
| req->ipsr_esp_alg = ipp->ipp_encr_alg; |
| req->ipsr_auth_alg = ipp->ipp_auth_alg; |
| req->ipsr_esp_auth_alg = ipp->ipp_esp_auth_alg; |
| |
| if (ipp->ipp_use_unique) { |
| req->ipsr_ah_req |= IPSEC_PREF_UNIQUE; |
| req->ipsr_esp_req |= IPSEC_PREF_UNIQUE; |
| } |
| if (ipp->ipp_use_se) |
| req->ipsr_self_encap_req |= IPSEC_PREF_REQUIRED; |
| if (ipp->ipp_use_ah) |
| req->ipsr_ah_req |= IPSEC_PREF_REQUIRED; |
| if (ipp->ipp_use_esp) |
| req->ipsr_esp_req |= IPSEC_PREF_REQUIRED; |
| return (sizeof (*req)); |
| } |
| |
| /* |
| * Convert a new-style action back to an ipsec_req_t (more backwards compat). |
| * We assume caller has already zero'ed *req for us. |
| */ |
| static int |
| ipsec_req_from_act(ipsec_action_t *ap, ipsec_req_t *req) |
| { |
| switch (ap->ipa_act.ipa_type) { |
| case IPSEC_ACT_BYPASS: |
| req->ipsr_ah_req = IPSEC_PREF_NEVER; |
| req->ipsr_esp_req = IPSEC_PREF_NEVER; |
| return (sizeof (*req)); |
| case IPSEC_ACT_APPLY: |
| return (ipsec_req_from_prot(&ap->ipa_act.ipa_apply, req)); |
| } |
| return (sizeof (*req)); |
| } |
| |
| /* |
| * Convert a new-style action back to an ipsec_req_t (more backwards compat). |
| * We assume caller has already zero'ed *req for us. |
| */ |
| static int |
| ipsec_req_from_head(ipsec_policy_head_t *ph, ipsec_req_t *req, int af) |
| { |
| ipsec_policy_t *p; |
| |
| /* |
| * FULL-PERSOCK: consult hash table, too? |
| */ |
| for (p = ph->iph_root[IPSEC_INBOUND].ipr_nonhash[af]; |
| p != NULL; |
| p = p->ipsp_hash.hash_next) { |
| if ((p->ipsp_sel->ipsl_key.ipsl_valid&IPSL_WILDCARD) == 0) |
| return (ipsec_req_from_act(p->ipsp_act, req)); |
| } |
| return (sizeof (*req)); |
| } |
| |
| /* |
| * Based on per-socket or latched policy, convert to an appropriate |
| * IP_SEC_OPT ipsec_req_t for the socket option; return size so we can |
| * be tail-called from ip. |
| */ |
| int |
| ipsec_req_from_conn(conn_t *connp, ipsec_req_t *req, int af) |
| { |
| ipsec_latch_t *ipl; |
| int rv = sizeof (ipsec_req_t); |
| |
| bzero(req, sizeof (*req)); |
| |
| mutex_enter(&connp->conn_lock); |
| ipl = connp->conn_latch; |
| |
| /* |
| * Find appropriate policy. First choice is latched action; |
| * failing that, see latched policy; failing that, |
| * look at configured policy. |
| */ |
| if (ipl != NULL) { |
| if (ipl->ipl_in_action != NULL) { |
| rv = ipsec_req_from_act(ipl->ipl_in_action, req); |
| goto done; |
| } |
| if (ipl->ipl_in_policy != NULL) { |
| rv = ipsec_req_from_act(ipl->ipl_in_policy->ipsp_act, |
| req); |
| goto done; |
| } |
| } |
| if (connp->conn_policy != NULL) |
| rv = ipsec_req_from_head(connp->conn_policy, req, af); |
| done: |
| mutex_exit(&connp->conn_lock); |
| return (rv); |
| } |
| |
| void |
| ipsec_actvec_free(ipsec_act_t *act, uint_t nact) |
| { |
| kmem_free(act, nact * sizeof (*act)); |
| } |
| |
| /* |
| * When outbound policy is not cached, look it up the hard way and attach |
| * an ipsec_out_t to the packet.. |
| */ |
| static mblk_t * |
| ipsec_attach_global_policy(mblk_t *mp, conn_t *connp, ipsec_selector_t *sel) |
| { |
| ipsec_policy_t *p; |
| |
| p = ipsec_find_policy(IPSEC_TYPE_OUTBOUND, connp, NULL, sel); |
| |
| if (p == NULL) |
| return (NULL); |
| return (ipsec_attach_ipsec_out(mp, connp, p, sel->ips_protocol)); |
| } |
| |
| /* |
| * We have an ipsec_out already, but don't have cached policy; fill it in |
| * with the right actions. |
| */ |
| static mblk_t * |
| ipsec_apply_global_policy(mblk_t *ipsec_mp, conn_t *connp, |
| ipsec_selector_t *sel) |
| { |
| ipsec_out_t *io; |
| ipsec_policy_t *p; |
| |
| ASSERT(ipsec_mp->b_datap->db_type == M_CTL); |
| ASSERT(ipsec_mp->b_cont->b_datap->db_type == M_DATA); |
| |
| io = (ipsec_out_t *)ipsec_mp->b_rptr; |
| |
| if (io->ipsec_out_policy == NULL) { |
| p = ipsec_find_policy(IPSEC_TYPE_OUTBOUND, connp, io, sel); |
| io->ipsec_out_policy = p; |
| } |
| return (ipsec_mp); |
| } |
| |
| |
| /* ARGSUSED */ |
| /* |
| * Consumes a reference to ipsp. |
| */ |
| static mblk_t * |
| ipsec_check_loopback_policy(queue_t *q, mblk_t *first_mp, |
| boolean_t mctl_present, ipsec_policy_t *ipsp) |
| { |
| mblk_t *ipsec_mp; |
| ipsec_in_t *ii; |
| |
| if (!mctl_present) |
| return (first_mp); |
| |
| ipsec_mp = first_mp; |
| |
| ii = (ipsec_in_t *)ipsec_mp->b_rptr; |
| ASSERT(ii->ipsec_in_loopback); |
| IPPOL_REFRELE(ipsp); |
| |
| /* |
| * We should do an actual policy check here. Revisit this |
| * when we revisit the IPsec API. |
| */ |
| |
| return (first_mp); |
| } |
| |
| /* |
| * Check that packet's inbound ports & proto match the selectors |
| * expected by the SAs it traversed on the way in. |
| */ |
| static boolean_t |
| ipsec_check_ipsecin_unique(ipsec_in_t *ii, mblk_t *mp, |
| ipha_t *ipha, ip6_t *ip6h, |
| const char **reason, kstat_named_t **counter) |
| { |
| uint64_t pkt_unique, ah_mask, esp_mask; |
| ipsa_t *ah_assoc; |
| ipsa_t *esp_assoc; |
| ipsec_selector_t sel; |
| |
| ASSERT(ii->ipsec_in_secure); |
| ASSERT(!ii->ipsec_in_loopback); |
| |
| ah_assoc = ii->ipsec_in_ah_sa; |
| esp_assoc = ii->ipsec_in_esp_sa; |
| ASSERT((ah_assoc != NULL) || (esp_assoc != NULL)); |
| |
| ah_mask = (ah_assoc != NULL) ? ah_assoc->ipsa_unique_mask : 0; |
| esp_mask = (esp_assoc != NULL) ? esp_assoc->ipsa_unique_mask : 0; |
| |
| if ((ah_mask == 0) && (esp_mask == 0)) |
| return (B_TRUE); |
| |
| if (!ipsec_init_inbound_sel(&sel, mp, ipha, ip6h)) { |
| /* |
| * Technically not a policy mismatch, but it is |
| * an internal failure. |
| */ |
| *reason = "ipsec_init_inbound_sel"; |
| *counter = &ipdrops_spd_nomem; |
| return (B_FALSE); |
| } |
| |
| pkt_unique = SA_UNIQUE_ID(sel.ips_remote_port, sel.ips_local_port, |
| sel.ips_protocol); |
| |
| if (ah_mask != 0) { |
| if (ah_assoc->ipsa_unique_id != (pkt_unique & ah_mask)) { |
| *reason = "AH inner header mismatch"; |
| *counter = &ipdrops_spd_ah_innermismatch; |
| return (B_FALSE); |
| } |
| } |
| if (esp_mask != 0) { |
| if (esp_assoc->ipsa_unique_id != (pkt_unique & esp_mask)) { |
| *reason = "ESP inner header mismatch"; |
| *counter = &ipdrops_spd_esp_innermismatch; |
| return (B_FALSE); |
| } |
| } |
| return (B_TRUE); |
| } |
| |
| static boolean_t |
| ipsec_check_ipsecin_action(ipsec_in_t *ii, mblk_t *mp, ipsec_action_t *ap, |
| ipha_t *ipha, ip6_t *ip6h, const char **reason, kstat_named_t **counter) |
| { |
| boolean_t ret = B_TRUE; |
| ipsec_prot_t *ipp; |
| ipsa_t *ah_assoc; |
| ipsa_t *esp_assoc; |
| boolean_t decaps; |
| |
| ASSERT((ipha == NULL && ip6h != NULL) || |
| (ip6h == NULL && ipha != NULL)); |
| |
| if (ii->ipsec_in_loopback) { |
| /* |
| * Besides accepting pointer-equivalent actions, we also |
| * accept any ICMP errors we generated for ourselves, |
| * regardless of policy. If we do not wish to make this |
| * assumption in the future, check here, and where |
| * icmp_loopback is initialized in ip.c and ip6.c. (Look for |
| * ipsec_out_icmp_loopback.) |
| */ |
| if (ap == ii->ipsec_in_action || ii->ipsec_in_icmp_loopback) |
| return (B_TRUE); |
| |
| /* Deep compare necessary here?? */ |
| *counter = &ipdrops_spd_loopback_mismatch; |
| *reason = "loopback policy mismatch"; |
| return (B_FALSE); |
| } |
| ASSERT(!ii->ipsec_in_icmp_loopback); |
| |
| ah_assoc = ii->ipsec_in_ah_sa; |
| esp_assoc = ii->ipsec_in_esp_sa; |
| |
| decaps = ii->ipsec_in_decaps; |
| |
| switch (ap->ipa_act.ipa_type) { |
| case IPSEC_ACT_DISCARD: |
| case IPSEC_ACT_REJECT: |
| /* Should "fail hard" */ |
| *counter = &ipdrops_spd_explicit; |
| *reason = "blocked by policy"; |
| return (B_FALSE); |
| |
| case IPSEC_ACT_BYPASS: |
| case IPSEC_ACT_CLEAR: |
| *counter = &ipdrops_spd_got_secure; |
| *reason = "expected clear, got protected"; |
| return (B_FALSE); |
| |
| case IPSEC_ACT_APPLY: |
| ipp = &ap->ipa_act.ipa_apply; |
| /* |
| * As of now we do the simple checks of whether |
| * the datagram has gone through the required IPSEC |
| * protocol constraints or not. We might have more |
| * in the future like sensitive levels, key bits, etc. |
| * If it fails the constraints, check whether we would |
| * have accepted this if it had come in clear. |
| */ |
| if (ipp->ipp_use_ah) { |
| if (ah_assoc == NULL) { |
| ret = ipsec_inbound_accept_clear(mp, ipha, |
| ip6h); |
| *counter = &ipdrops_spd_got_clear; |
| *reason = "unprotected not accepted"; |
| break; |
| } |
| ASSERT(ah_assoc != NULL); |
| ASSERT(ipp->ipp_auth_alg != 0); |
| |
| if (ah_assoc->ipsa_auth_alg != |
| ipp->ipp_auth_alg) { |
| *counter = &ipdrops_spd_bad_ahalg; |
| *reason = "unacceptable ah alg"; |
| ret = B_FALSE; |
| break; |
| } |
| } else if (ah_assoc != NULL) { |
| /* |
| * Don't allow this. Check IPSEC NOTE above |
| * ip_fanout_proto(). |
| */ |
| *counter = &ipdrops_spd_got_ah; |
| *reason = "unexpected AH"; |
| ret = B_FALSE; |
| break; |
| } |
| if (ipp->ipp_use_esp) { |
| if (esp_assoc == NULL) { |
| ret = ipsec_inbound_accept_clear(mp, ipha, |
| ip6h); |
| *counter = &ipdrops_spd_got_clear; |
| *reason = "unprotected not accepted"; |
| break; |
| } |
| ASSERT(esp_assoc != NULL); |
| ASSERT(ipp->ipp_encr_alg != 0); |
| |
| if (esp_assoc->ipsa_encr_alg != |
| ipp->ipp_encr_alg) { |
| *counter = &ipdrops_spd_bad_espealg; |
| *reason = "unacceptable esp alg"; |
| ret = B_FALSE; |
| break; |
| } |
| /* |
| * If the client does not need authentication, |
| * we don't verify the alogrithm. |
| */ |
| if (ipp->ipp_use_espa) { |
| if (esp_assoc->ipsa_auth_alg != |
| ipp->ipp_esp_auth_alg) { |
| *counter = &ipdrops_spd_bad_espaalg; |
| *reason = "unacceptable esp auth alg"; |
| ret = B_FALSE; |
| break; |
| } |
| } |
| } else if (esp_assoc != NULL) { |
| /* |
| * Don't allow this. Check IPSEC NOTE above |
| * ip_fanout_proto(). |
| */ |
| *counter = &ipdrops_spd_got_esp; |
| *reason = "unexpected ESP"; |
| ret = B_FALSE; |
| break; |
| } |
| if (ipp->ipp_use_se) { |
| if (!decaps) { |
| ret = ipsec_inbound_accept_clear(mp, ipha, |
| ip6h); |
| if (!ret) { |
| /* XXX mutant? */ |
| *counter = &ipdrops_spd_bad_selfencap; |
| *reason = "self encap not found"; |
| break; |
| } |
| } |
| } else if (decaps) { |
| /* |
| * XXX If the packet comes in tunneled and the |
| * recipient does not expect it to be tunneled, it |
| * is okay. But we drop to be consistent with the |
| * other cases. |
| */ |
| *counter = &ipdrops_spd_got_selfencap; |
| *reason = "unexpected self encap"; |
| ret = B_FALSE; |
| break; |
| } |
| if (ii->ipsec_in_action != NULL) { |
| /* |
| * This can happen if we do a double policy-check on |
| * a packet |
| * XXX XXX should fix this case! |
| */ |
| IPACT_REFRELE(ii->ipsec_in_action); |
| } |
| ASSERT(ii->ipsec_in_action == NULL); |
| IPACT_REFHOLD(ap); |
| ii->ipsec_in_action = ap; |
| break; /* from switch */ |
| } |
| return (ret); |
| } |
| |
| static boolean_t |
| spd_match_inbound_ids(ipsec_latch_t *ipl, ipsa_t *sa) |
| { |
| ASSERT(ipl->ipl_ids_latched == B_TRUE); |
| return ipsid_equal(ipl->ipl_remote_cid, sa->ipsa_src_cid) && |
| ipsid_equal(ipl->ipl_local_cid, sa->ipsa_dst_cid); |
| } |
| |
| /* |
| * Called to check policy on a latched connection, both from this file |
| * and from tcp.c |
| */ |
| boolean_t |
| ipsec_check_ipsecin_latch(ipsec_in_t *ii, mblk_t *mp, ipsec_latch_t *ipl, |
| ipha_t *ipha, ip6_t *ip6h, const char **reason, kstat_named_t **counter) |
| { |
| ASSERT(ipl->ipl_ids_latched == B_TRUE); |
| |
| if (!ii->ipsec_in_loopback) { |
| /* |
| * Over loopback, there aren't real security associations, |
| * so there are neither identities nor "unique" values |
| * for us to check the packet against. |
| */ |
| if ((ii->ipsec_in_ah_sa != NULL) && |
| (!spd_match_inbound_ids(ipl, ii->ipsec_in_ah_sa))) { |
| *counter = &ipdrops_spd_ah_badid; |
| *reason = "AH identity mismatch"; |
| return (B_FALSE); |
| } |
| |
| if ((ii->ipsec_in_esp_sa != NULL) && |
| (!spd_match_inbound_ids(ipl, ii->ipsec_in_esp_sa))) { |
| *counter = &ipdrops_spd_esp_badid; |
| *reason = "ESP identity mismatch"; |
| return (B_FALSE); |
| } |
| |
| if (!ipsec_check_ipsecin_unique(ii, mp, ipha, ip6h, reason, |
| counter)) { |
| return (B_FALSE); |
| } |
| } |
| |
| return (ipsec_check_ipsecin_action(ii, mp, ipl->ipl_in_action, |
| ipha, ip6h, reason, counter)); |
| } |
| |
| /* |
| * Check to see whether this secured datagram meets the policy |
| * constraints specified in ipsp. |
| * |
| * Called from ipsec_check_global_policy, and ipsec_check_inbound_policy. |
| * |
| * Consumes a reference to ipsp. |
| */ |
| static mblk_t * |
| ipsec_check_ipsecin_policy(queue_t *q, mblk_t *first_mp, ipsec_policy_t *ipsp, |
| ipha_t *ipha, ip6_t *ip6h) |
| { |
| ipsec_in_t *ii; |
| ipsec_action_t *ap; |
| const char *reason = "no policy actions found"; |
| mblk_t *data_mp, *ipsec_mp; |
| short mid = 0; |
| kstat_named_t *counter = &ipdrops_spd_got_secure; |
| |
| data_mp = first_mp->b_cont; |
| ipsec_mp = first_mp; |
| |
| ASSERT(ipsp != NULL); |
| |
| ASSERT((ipha == NULL && ip6h != NULL) || |
| (ip6h == NULL && ipha != NULL)); |
| |
| ii = (ipsec_in_t *)ipsec_mp->b_rptr; |
| |
| if (ii->ipsec_in_loopback) |
| return (ipsec_check_loopback_policy(q, first_mp, B_TRUE, ipsp)); |
| ASSERT(ii->ipsec_in_type == IPSEC_IN); |
| ASSERT(ii->ipsec_in_secure); |
| |
| if (ii->ipsec_in_action != NULL) { |
| /* |
| * this can happen if we do a double policy-check on a packet |
| * Would be nice to be able to delete this test.. |
| */ |
| IPACT_REFRELE(ii->ipsec_in_action); |
| } |
| ASSERT(ii->ipsec_in_action == NULL); |
| |
| if (!SA_IDS_MATCH(ii->ipsec_in_ah_sa, ii->ipsec_in_esp_sa)) { |
| reason = "inbound AH and ESP identities differ"; |
| counter = &ipdrops_spd_ahesp_diffid; |
| goto drop; |
| } |
| |
| if (!ipsec_check_ipsecin_unique(ii, data_mp, ipha, ip6h, |
| &reason, &counter)) |
| goto drop; |
| |
| /* |
| * Ok, now loop through the possible actions and see if any |
| * of them work for us. |
| */ |
| |
| for (ap = ipsp->ipsp_act; ap != NULL; ap = ap->ipa_next) { |
| if (ipsec_check_ipsecin_action(ii, data_mp, ap, |
| ipha, ip6h, &reason, &counter)) { |
| BUMP_MIB(&ip_mib, ipsecInSucceeded); |
| IPPOL_REFRELE(ipsp); |
| return (first_mp); |
| } |
| } |
| drop: |
| if (q != NULL) { |
| mid = q->q_qinfo->qi_minfo->mi_idnum; |
| } |
| ipsec_rl_strlog(mid, 0, 0, SL_ERROR|SL_WARN|SL_CONSOLE, |
| "ipsec inbound policy mismatch: %s, packet dropped\n", |
| reason); |
| IPPOL_REFRELE(ipsp); |
| ASSERT(ii->ipsec_in_action == NULL); |
| BUMP_MIB(&ip_mib, ipsecInFailed); |
| ip_drop_packet(first_mp, B_TRUE, NULL, NULL, counter, &spd_dropper); |
| return (NULL); |
| } |
| |
| /* |
| * sleazy prefix-length-based compare. |
| * another inlining candidate.. |
| */ |
| static boolean_t |
| ip_addr_match(uint8_t *addr1, int pfxlen, in6_addr_t *addr2p) |
| { |
| int offset = pfxlen>>3; |
| int bitsleft = pfxlen & 7; |
| uint8_t *addr2 = (uint8_t *)addr2p; |
| |
| /* |
| * and there was much evil.. |
| * XXX should inline-expand the bcmp here and do this 32 bits |
| * or 64 bits at a time.. |
| */ |
| return ((bcmp(addr1, addr2, offset) == 0) && |
| ((bitsleft == 0) || |
| (((addr1[offset] ^ addr2[offset]) & |
| (0xff<<(8-bitsleft))) == 0))); |
| } |
| |
| static ipsec_policy_t * |
| ipsec_find_policy_chain(ipsec_policy_t *best, ipsec_policy_t *chain, |
| ipsec_selector_t *sel, boolean_t is_icmp_inv_acq) |
| { |
| ipsec_selkey_t *isel; |
| ipsec_policy_t *p; |
| int bpri = best ? best->ipsp_prio : 0; |
| |
| for (p = chain; p != NULL; p = p->ipsp_hash.hash_next) { |
| uint32_t valid; |
| |
| if (p->ipsp_prio <= bpri) |
| continue; |
| isel = &p->ipsp_sel->ipsl_key; |
| valid = isel->ipsl_valid; |
| |
| if ((valid & IPSL_PROTOCOL) && |
| (isel->ipsl_proto != sel->ips_protocol)) |
| continue; |
| |
| if ((valid & IPSL_REMOTE_ADDR) && |
| !ip_addr_match((uint8_t *)&isel->ipsl_remote, |
| isel->ipsl_remote_pfxlen, |
| &sel->ips_remote_addr_v6)) |
| continue; |
| |
| if ((valid & IPSL_LOCAL_ADDR) && |
| !ip_addr_match((uint8_t *)&isel->ipsl_local, |
| isel->ipsl_local_pfxlen, |
| &sel->ips_local_addr_v6)) |
| continue; |
| |
| if ((valid & IPSL_REMOTE_PORT) && |
| isel->ipsl_rport != sel->ips_remote_port) |
| continue; |
| |
| if ((valid & IPSL_LOCAL_PORT) && |
| isel->ipsl_lport != sel->ips_local_port) |
| continue; |
| |
| if (!is_icmp_inv_acq) { |
| if ((valid & IPSL_ICMP_TYPE) && |
| (isel->ipsl_icmp_type > sel->ips_icmp_type || |
| isel->ipsl_icmp_type_end < sel->ips_icmp_type)) { |
| continue; |
| } |
| |
| if ((valid & IPSL_ICMP_CODE) && |
| (isel->ipsl_icmp_code > sel->ips_icmp_code || |
| isel->ipsl_icmp_code_end < |
| sel->ips_icmp_code)) { |
| continue; |
| } |
| } else { |
| /* |
| * special case for icmp inverse acquire |
| * we only want policies that aren't drop/pass |
| */ |
| if (p->ipsp_act->ipa_act.ipa_type != IPSEC_ACT_APPLY) |
| continue; |
| } |
| |
| /* we matched all the packet-port-field selectors! */ |
| best = p; |
| bpri = p->ipsp_prio; |
| } |
| |
| return (best); |
| } |
| |
| /* |
| * Try to find and return the best policy entry under a given policy |
| * root for a given set of selectors; the first parameter "best" is |
| * the current best policy so far. If "best" is non-null, we have a |
| * reference to it. We return a reference to a policy; if that policy |
| * is not the original "best", we need to release that reference |
| * before returning. |
| */ |
| static ipsec_policy_t * |
| ipsec_find_policy_head(ipsec_policy_t *best, |
| ipsec_policy_head_t *head, int direction, ipsec_selector_t *sel, |
| int selhash) |
| { |
| ipsec_policy_t *curbest; |
| ipsec_policy_root_t *root; |
| uint8_t is_icmp_inv_acq = sel->ips_is_icmp_inv_acq; |
| int af = sel->ips_isv4 ? IPSEC_AF_V4 : IPSEC_AF_V6; |
| |
| curbest = best; |
| root = &head->iph_root[direction]; |
| |
| #ifdef DEBUG |
| if (is_icmp_inv_acq) { |
| if (sel->ips_isv4) { |
| if (sel->ips_protocol != IPPROTO_ICMP) { |
| cmn_err(CE_WARN, "ipsec_find_policy_head:" |
| " expecting icmp, got %d", sel->ips_protocol); |
| } |
| } else { |
| if (sel->ips_protocol != IPPROTO_ICMPV6) { |
| cmn_err(CE_WARN, "ipsec_find_policy_head:" |
| " expecting icmpv6, got %d", sel->ips_protocol); |
| } |
| } |
| } |
| #endif |
| |
| rw_enter(&head->iph_lock, RW_READER); |
| |
| if (root->ipr_nchains > 0) { |
| curbest = ipsec_find_policy_chain(curbest, |
| root->ipr_hash[selhash].hash_head, sel, is_icmp_inv_acq); |
| } |
| curbest = ipsec_find_policy_chain(curbest, root->ipr_nonhash[af], sel, |
| is_icmp_inv_acq); |
| |
| /* |
| * Adjust reference counts if we found anything new. |
| */ |
| if (curbest != best) { |
| ASSERT(curbest != NULL); |
| IPPOL_REFHOLD(curbest); |
| |
| if (best != NULL) { |
| IPPOL_REFRELE(best); |
| } |
| } |
| |
| rw_exit(&head->iph_lock); |
| |
| return (curbest); |
| } |
| |
| /* |
| * Find the best system policy (either global or per-interface) which |
| * applies to the given selector; look in all the relevant policy roots |
| * to figure out which policy wins. |
| * |
| * Returns a reference to a policy; caller must release this |
| * reference when done. |
| */ |
| ipsec_policy_t * |
| ipsec_find_policy(int direction, conn_t *connp, ipsec_out_t *io, |
| ipsec_selector_t *sel) |
| { |
| ipsec_policy_t *p; |
| int selhash = selector_hash(sel); |
| |
| p = ipsec_find_policy_head(NULL, &system_policy, direction, sel, |
| selhash); |
| if ((connp != NULL) && (connp->conn_policy != NULL)) { |
| p = ipsec_find_policy_head(p, connp->conn_policy, |
| direction, sel, selhash); |
| } else if ((io != NULL) && (io->ipsec_out_polhead != NULL)) { |
| p = ipsec_find_policy_head(p, io->ipsec_out_polhead, |
| direction, sel, selhash); |
| } |
| |
| return (p); |
| } |
| |
| /* |
| * Check with global policy and see whether this inbound |
| * packet meets the policy constraints. |
| * |
| * Locate appropriate policy from global policy, supplemented by the |
| * conn's configured and/or cached policy if the conn is supplied. |
| * |
| * Dispatch to ipsec_check_ipsecin_policy if we have policy and an |
| * encrypted packet to see if they match. |
| * |
| * Otherwise, see if the policy allows cleartext; if not, drop it on the |
| * floor. |
| */ |
| mblk_t * |
| ipsec_check_global_policy(mblk_t *first_mp, conn_t *connp, |
| ipha_t *ipha, ip6_t *ip6h, boolean_t mctl_present) |
| { |
| ipsec_policy_t *p; |
| ipsec_selector_t sel; |
| queue_t *q = NULL; |
| mblk_t *data_mp, *ipsec_mp; |
| boolean_t policy_present; |
| kstat_named_t *counter; |
| ipsec_in_t *ii = NULL; |
| |
| data_mp = mctl_present ? first_mp->b_cont : first_mp; |
| ipsec_mp = mctl_present ? first_mp : NULL; |
| |
| sel.ips_is_icmp_inv_acq = 0; |
| |
| ASSERT((ipha == NULL && ip6h != NULL) || |
| (ip6h == NULL && ipha != NULL)); |
| |
| if (ipha != NULL) |
| policy_present = ipsec_inbound_v4_policy_present; |
| else |
| policy_present = ipsec_inbound_v6_policy_present; |
| |
| if (!policy_present && connp == NULL) { |
| /* |
| * No global policy and no per-socket policy; |
| * just pass it back (but we shouldn't get here in that case) |
| */ |
| return (first_mp); |
| } |
| |
| if (connp != NULL) |
| q = CONNP_TO_WQ(connp); |
| |
| if (ipsec_mp != NULL) { |
| ASSERT(ipsec_mp->b_datap->db_type == M_CTL); |
| ii = (ipsec_in_t *)(ipsec_mp->b_rptr); |
| ASSERT(ii->ipsec_in_type == IPSEC_IN); |
| } |
| |
| /* |
| * If we have cached policy, use it. |
| * Otherwise consult system policy. |
| */ |
| if ((connp != NULL) && (connp->conn_latch != NULL)) { |
| p = connp->conn_latch->ipl_in_policy; |
| if (p != NULL) { |
| IPPOL_REFHOLD(p); |
| } |
| } else { |
| /* Initialize the ports in the selector */ |
| if (!ipsec_init_inbound_sel(&sel, data_mp, ipha, ip6h)) { |
| /* |
| * Technically not a policy mismatch, but it is |
| * an internal failure. |
| */ |
| ipsec_log_policy_failure(q, IPSEC_POLICY_MISMATCH, |
| "ipsec_init_inbound_sel", ipha, ip6h, B_FALSE); |
| counter = &ipdrops_spd_nomem; |
| goto fail; |
| } |
| |
| /* |
| * Find the policy which best applies. |
| * |
| * If we find global policy, we should look at both |
| * local policy and global policy and see which is |
| * stronger and match accordingly. |
| * |
| * If we don't find a global policy, check with |
| * local policy alone. |
| */ |
| |
| p = ipsec_find_policy(IPSEC_TYPE_INBOUND, connp, NULL, &sel); |
| } |
| |
| if (p == NULL) { |
| if (ipsec_mp == NULL) { |
| /* |
| * We have no policy; default to succeeding. |
| * XXX paranoid system design doesn't do this. |
| */ |
| BUMP_MIB(&ip_mib, ipsecInSucceeded); |
| return (first_mp); |
| } else { |
| counter = &ipdrops_spd_got_secure; |
| ipsec_log_policy_failure(q, IPSEC_POLICY_NOT_NEEDED, |
| "ipsec_check_global_policy", ipha, ip6h, B_TRUE); |
| goto fail; |
| } |
| } |
| if ((ii != NULL) && (ii->ipsec_in_secure)) |
| return (ipsec_check_ipsecin_policy(q, ipsec_mp, p, ipha, ip6h)); |
| if (p->ipsp_act->ipa_allow_clear) { |
| BUMP_MIB(&ip_mib, ipsecInSucceeded); |
| IPPOL_REFRELE(p); |
| return (first_mp); |
| } |
| IPPOL_REFRELE(p); |
| /* |
| * If we reach here, we will drop the packet because it failed the |
| * global policy check because the packet was cleartext, and it |
| * should not have been. |
| */ |
| ipsec_log_policy_failure(q, IPSEC_POLICY_MISMATCH, |
| "ipsec_check_global_policy", ipha, ip6h, B_FALSE); |
| counter = &ipdrops_spd_got_clear; |
| |
| fail: |
| ip_drop_packet(first_mp, B_TRUE, NULL, NULL, counter, &spd_dropper); |
| BUMP_MIB(&ip_mib, ipsecInFailed); |
| return (NULL); |
| } |
| |
| /* |
| * We check whether an inbound datagram is a valid one |
| * to accept in clear. If it is secure, it is the job |
| * of IPSEC to log information appropriately if it |
| * suspects that it may not be the real one. |
| * |
| * It is called only while fanning out to the ULP |
| * where ULP accepts only secure data and the incoming |
| * is clear. Usually we never accept clear datagrams in |
| * such cases. ICMP is the only exception. |
| * |
| * NOTE : We don't call this function if the client (ULP) |
| * is willing to accept things in clear. |
| */ |
| boolean_t |
| ipsec_inbound_accept_clear(mblk_t *mp, ipha_t *ipha, ip6_t *ip6h) |
| { |
| ushort_t iph_hdr_length; |
| icmph_t *icmph; |
| icmp6_t *icmp6; |
| uint8_t *nexthdrp; |
| |
| ASSERT((ipha != NULL && ip6h == NULL) || |
| (ipha == NULL && ip6h != NULL)); |
| |
| if (ip6h != NULL) { |
| iph_hdr_length = ip_hdr_length_v6(mp, ip6h); |
| if (!ip_hdr_length_nexthdr_v6(mp, ip6h, &iph_hdr_length, |
| &nexthdrp)) { |
| return (B_FALSE); |
| } |
| if (*nexthdrp != IPPROTO_ICMPV6) |
| return (B_FALSE); |
| icmp6 = (icmp6_t *)(&mp->b_rptr[iph_hdr_length]); |
| /* Match IPv6 ICMP policy as closely as IPv4 as possible. */ |
| switch (icmp6->icmp6_type) { |
| case ICMP6_PARAM_PROB: |
| /* Corresponds to port/proto unreach in IPv4. */ |
| case ICMP6_ECHO_REQUEST: |
| /* Just like IPv4. */ |
| return (B_FALSE); |
| |
| case MLD_LISTENER_QUERY: |
| case MLD_LISTENER_REPORT: |
| case MLD_LISTENER_REDUCTION: |
| /* |
| * XXX Seperate NDD in IPv4 what about here? |
| * Plus, mcast is important to ND. |
| */ |
| case ICMP6_DST_UNREACH: |
| /* Corresponds to HOST/NET unreachable in IPv4. */ |
| case ICMP6_PACKET_TOO_BIG: |
| case ICMP6_ECHO_REPLY: |
| /* These are trusted in IPv4. */ |
| case ND_ROUTER_SOLICIT: |
| case ND_ROUTER_ADVERT: |
| case ND_NEIGHBOR_SOLICIT: |
| case ND_NEIGHBOR_ADVERT: |
| case ND_REDIRECT: |
| /* Trust ND messages for now. */ |
| case ICMP6_TIME_EXCEEDED: |
| default: |
| return (B_TRUE); |
| } |
| } else { |
| /* |
| * If it is not ICMP, fail this request. |
| */ |
| if (ipha->ipha_protocol != IPPROTO_ICMP) |
| return (B_FALSE); |
| iph_hdr_length = IPH_HDR_LENGTH(ipha); |
| icmph = (icmph_t *)&mp->b_rptr[iph_hdr_length]; |
| /* |
| * It is an insecure icmp message. Check to see whether we are |
| * willing to accept this one. |
| */ |
| |
| switch (icmph->icmph_type) { |
| case ICMP_ECHO_REPLY: |
| case ICMP_TIME_STAMP_REPLY: |
| case ICMP_INFO_REPLY: |
| case ICMP_ROUTER_ADVERTISEMENT: |
| /* |
| * We should not encourage clear replies if this |
| * client expects secure. If somebody is replying |
| * in clear some mailicious user watching both the |
| * request and reply, can do chosen-plain-text attacks. |
| * With global policy we might be just expecting secure |
| * but sending out clear. We don't know what the right |
| * thing is. We can't do much here as we can't control |
| * the sender here. Till we are sure of what to do, |
| * accept them. |
| */ |
| return (B_TRUE); |
| case ICMP_ECHO_REQUEST: |
| case ICMP_TIME_STAMP_REQUEST: |
| case ICMP_INFO_REQUEST: |
| case ICMP_ADDRESS_MASK_REQUEST: |
| case ICMP_ROUTER_SOLICITATION: |
| case ICMP_ADDRESS_MASK_REPLY: |
| /* |
| * Don't accept this as somebody could be sending |
| * us plain text to get encrypted data. If we reply, |
| * it will lead to chosen plain text attack. |
| */ |
| return (B_FALSE); |
| case ICMP_DEST_UNREACHABLE: |
| switch (icmph->icmph_code) { |
| case ICMP_FRAGMENTATION_NEEDED: |
| /* |
| * Be in sync with icmp_inbound, where we have |
| * already set ire_max_frag. |
| */ |
| return (B_TRUE); |
| case ICMP_HOST_UNREACHABLE: |
| case ICMP_NET_UNREACHABLE: |
| /* |
| * By accepting, we could reset a connection. |
| * How do we solve the problem of some |
| * intermediate router sending in-secure ICMP |
| * messages ? |
| */ |
| return (B_TRUE); |
| case ICMP_PORT_UNREACHABLE: |
| case ICMP_PROTOCOL_UNREACHABLE: |
| default : |
| return (B_FALSE); |
| } |
| case ICMP_SOURCE_QUENCH: |
| /* |
| * If this is an attack, TCP will slow start |
| * because of this. Is it very harmful ? |
| */ |
| return (B_TRUE); |
| case ICMP_PARAM_PROBLEM: |
| return (B_FALSE); |
| case ICMP_TIME_EXCEEDED: |
| return (B_TRUE); |
| case ICMP_REDIRECT: |
| return (B_FALSE); |
| default : |
| return (B_FALSE); |
| } |
| } |
| } |
| |
| void |
| ipsec_latch_ids(ipsec_latch_t *ipl, ipsid_t *local, ipsid_t *remote) |
| { |
| mutex_enter(&ipl->ipl_lock); |
| |
| if (ipl->ipl_ids_latched) { |
| /* I lost, someone else got here before me */ |
| mutex_exit(&ipl->ipl_lock); |
| return; |
| } |
| |
| if (local != NULL) |
| IPSID_REFHOLD(local); |
| if (remote != NULL) |
| IPSID_REFHOLD(remote); |
| |
| ipl->ipl_local_cid = local; |
| ipl->ipl_remote_cid = remote; |
| ipl->ipl_ids_latched = B_TRUE; |
| mutex_exit(&ipl->ipl_lock); |
| } |
| |
| void |
| ipsec_latch_inbound(ipsec_latch_t *ipl, ipsec_in_t *ii) |
| { |
| ipsa_t *sa; |
| |
| if (!ipl->ipl_ids_latched) { |
| ipsid_t *local = NULL; |
| ipsid_t *remote = NULL; |
| |
| if (!ii->ipsec_in_loopback) { |
| if (ii->ipsec_in_esp_sa != NULL) |
| sa = ii->ipsec_in_esp_sa; |
| else |
| sa = ii->ipsec_in_ah_sa; |
| ASSERT(sa != NULL); |
| local = sa->ipsa_dst_cid; |
| remote = sa->ipsa_src_cid; |
| } |
| ipsec_latch_ids(ipl, local, remote); |
| } |
| ipl->ipl_in_action = ii->ipsec_in_action; |
| IPACT_REFHOLD(ipl->ipl_in_action); |
| } |
| |
| /* |
| * Check whether the policy constraints are met either for an |
| * inbound datagram; called from IP in numerous places. |
| * |
| * Note that this is not a chokepoint for inbound policy checks; |
| * see also ipsec_check_ipsecin_latch() and ipsec_check_global_policy() |
| */ |
| mblk_t * |
| ipsec_check_inbound_policy(mblk_t *first_mp, conn_t *connp, |
| ipha_t *ipha, ip6_t *ip6h, boolean_t mctl_present) |
| { |
| ipsec_in_t *ii; |
| boolean_t ret; |
| queue_t *q; |
| short mid = 0; |
| mblk_t *mp = mctl_present ? first_mp->b_cont : first_mp; |
| mblk_t *ipsec_mp = mctl_present ? first_mp : NULL; |
| ipsec_latch_t *ipl; |
| |
| ASSERT(connp != NULL); |
| ipl = connp->conn_latch; |
| |
| if (ipsec_mp == NULL) { |
| clear: |
| /* |
| * This is the case where the incoming datagram is |
| * cleartext and we need to see whether this client |
| * would like to receive such untrustworthy things from |
| * the wire. |
| */ |
| ASSERT(mp != NULL); |
| |
| if (ipl != NULL) { |
| /* |
| * Policy is cached in the conn. |
| */ |
| if ((ipl->ipl_in_policy != NULL) && |
| (!ipl->ipl_in_policy->ipsp_act->ipa_allow_clear)) { |
| ret = ipsec_inbound_accept_clear(mp, |
| ipha, ip6h); |
| if (ret) { |
| BUMP_MIB(&ip_mib, ipsecInSucceeded); |
| return (first_mp); |
| } else { |
| ipsec_log_policy_failure( |
| CONNP_TO_WQ(connp), |
| IPSEC_POLICY_MISMATCH, |
| "ipsec_check_inbound_policy", ipha, |
| ip6h, B_FALSE); |
| ip_drop_packet(first_mp, B_TRUE, NULL, |
| NULL, &ipdrops_spd_got_clear, |
| &spd_dropper); |
| BUMP_MIB(&ip_mib, ipsecInFailed); |
| return (NULL); |
| } |
| } else { |
| BUMP_MIB(&ip_mib, ipsecInSucceeded); |
| return (first_mp); |
| } |
| } else { |
| /* |
| * As this is a non-hardbound connection we need |
| * to look at both per-socket policy and global |
| * policy. As this is cleartext, mark the mp as |
| * M_DATA in case if it is an ICMP error being |
| * reported before calling ipsec_check_global_policy |
| * so that it does not mistake it for IPSEC_IN. |
| */ |
| uchar_t db_type = mp->b_datap->db_type; |
| mp->b_datap->db_type = M_DATA; |
| first_mp = ipsec_check_global_policy(first_mp, connp, |
| ipha, ip6h, mctl_present); |
| if (first_mp != NULL) |
| mp->b_datap->db_type = db_type; |
| return (first_mp); |
| } |
| } |
| /* |
| * If it is inbound check whether the attached message |
| * is secure or not. We have a special case for ICMP, |
| * where we have a IPSEC_IN message and the attached |
| * message is not secure. See icmp_inbound_error_fanout |
| * for details. |
| */ |
| ASSERT(ipsec_mp != NULL); |
| ASSERT(ipsec_mp->b_datap->db_type == M_CTL); |
| ii = (ipsec_in_t *)ipsec_mp->b_rptr; |
| |
| if (!ii->ipsec_in_secure) |
| goto clear; |
| |
| /* |
| * mp->b_cont could be either a M_CTL message |
| * for icmp errors being sent up or a M_DATA message. |
| */ |
| ASSERT(mp->b_datap->db_type == M_CTL || |
| mp->b_datap->db_type == M_DATA); |
| |
| ASSERT(ii->ipsec_in_type == IPSEC_IN); |
| |
| if (ipl == NULL) { |
| /* |
| * We don't have policies cached in the conn |
| * for this stream. So, look at the global |
| * policy. It will check against conn or global |
| * depending on whichever is stronger. |
| */ |
| return (ipsec_check_global_policy(first_mp, connp, |
| ipha, ip6h, mctl_present)); |
| } |
| |
| if (ipl->ipl_in_action != NULL) { |
| /* Policy is cached & latched; fast(er) path */ |
| const char *reason; |
| kstat_named_t *counter; |
| if (ipsec_check_ipsecin_latch(ii, mp, ipl, |
| ipha, ip6h, &reason, &counter)) { |
| BUMP_MIB(&ip_mib, ipsecInSucceeded); |
| return (first_mp); |
| } |
| q = CONNP_TO_WQ(connp); |
| if (q != NULL) { |
| mid = q->q_qinfo->qi_minfo->mi_idnum; |
| } |
| ipsec_rl_strlog(mid, 0, 0, SL_ERROR|SL_WARN|SL_CONSOLE, |
| "ipsec inbound policy mismatch: %s, packet dropped\n", |
| reason); |
| ip_drop_packet(first_mp, B_TRUE, NULL, NULL, counter, |
| &spd_dropper); |
| BUMP_MIB(&ip_mib, ipsecInFailed); |
| return (NULL); |
| } else if (ipl->ipl_in_policy == NULL) { |
| ipsec_weird_null_inbound_policy++; |
| return (first_mp); |
| } |
| |
| IPPOL_REFHOLD(ipl->ipl_in_policy); |
| first_mp = ipsec_check_ipsecin_policy(CONNP_TO_WQ(connp), first_mp, |
| ipl->ipl_in_policy, ipha, ip6h); |
| /* |
| * NOTE: ipsecIn{Failed,Succeeeded} bumped by |
| * ipsec_check_ipsecin_policy(). |
| */ |
| if (first_mp != NULL) |
| ipsec_latch_inbound(ipl, ii); |
| return (first_mp); |
| } |
| |
| boolean_t |
| ipsec_init_inbound_sel(ipsec_selector_t *sel, mblk_t *mp, |
| ipha_t *ipha, ip6_t *ip6h) |
| { |
| uint16_t *ports; |
| ushort_t hdr_len; |
| mblk_t *spare_mp = NULL; |
| uint8_t *nexthdrp; |
| uint8_t nexthdr; |
| uint8_t *typecode; |
| uint8_t check_proto; |
| |
| ASSERT((ipha == NULL && ip6h != NULL) || |
| (ipha != NULL && ip6h == NULL)); |
| |
| if (ip6h != NULL) { |
| check_proto = IPPROTO_ICMPV6; |
| sel->ips_isv4 = B_FALSE; |
| sel->ips_local_addr_v6 = ip6h->ip6_dst; |
| sel->ips_remote_addr_v6 = ip6h->ip6_src; |
| |
| nexthdr = ip6h->ip6_nxt; |
| switch (nexthdr) { |
| case IPPROTO_HOPOPTS: |
| case IPPROTO_ROUTING: |
| case IPPROTO_DSTOPTS: |
| /* |
| * Use ip_hdr_length_nexthdr_v6(). And have a spare |
| * mblk that's contiguous to feed it |
| */ |
| if ((spare_mp = msgpullup(mp, -1)) == NULL) |
| return (B_FALSE); |
| if (!ip_hdr_length_nexthdr_v6(spare_mp, |
| (ip6_t *)spare_mp->b_rptr, &hdr_len, &nexthdrp)) { |
| /* Malformed packet - XXX ip_drop_packet()? */ |
| freemsg(spare_mp); |
| return (B_FALSE); |
| } |
| nexthdr = *nexthdrp; |
| /* We can just extract based on hdr_len now. */ |
| break; |
| default: |
| hdr_len = IPV6_HDR_LEN; |
| break; |
| } |
| } else { |
| check_proto = IPPROTO_ICMP; |
| sel->ips_isv4 = B_TRUE; |
| sel->ips_local_addr_v4 = ipha->ipha_dst; |
| sel->ips_remote_addr_v4 = ipha->ipha_src; |
| nexthdr = ipha->ipha_protocol; |
| hdr_len = IPH_HDR_LENGTH(ipha); |
| } |
| sel->ips_protocol = nexthdr; |
| |
| if (nexthdr != IPPROTO_TCP && nexthdr != IPPROTO_UDP && |
| nexthdr != IPPROTO_SCTP && nexthdr != check_proto) { |
| sel->ips_remote_port = sel->ips_local_port = 0; |
| freemsg(spare_mp); /* Always works, even if NULL. */ |
| return (B_TRUE); |
| } |
| |
| if (&mp->b_rptr[hdr_len] + 4 > mp->b_wptr) { |
| /* If we didn't pullup a copy already, do so now. */ |
| /* |
| * XXX performance, will upper-layers frequently split TCP/UDP |
| * apart from IP or options? If so, perhaps we should revisit |
| * the spare_mp strategy. |
| */ |
| ipsec_hdr_pullup_needed++; |
| if (spare_mp == NULL && |
| (spare_mp = msgpullup(mp, -1)) == NULL) { |
| return (B_FALSE); |
| } |
| ports = (uint16_t *)&spare_mp->b_rptr[hdr_len]; |
| } else { |
| ports = (uint16_t *)&mp->b_rptr[hdr_len]; |
| } |
| |
| if (nexthdr == check_proto) { |
| typecode = (uint8_t *)ports; |
| sel->ips_icmp_type = *typecode++; |
| sel->ips_icmp_code = *typecode; |
| sel->ips_remote_port = sel->ips_local_port = 0; |
| freemsg(spare_mp); /* Always works, even if NULL */ |
| return (B_TRUE); |
| } |
| |
| sel->ips_remote_port = *ports++; |
| sel->ips_local_port = *ports; |
| freemsg(spare_mp); /* Always works, even if NULL */ |
| return (B_TRUE); |
| } |
| |
| static boolean_t |
| ipsec_init_outbound_ports(ipsec_selector_t *sel, mblk_t *mp, ipha_t *ipha, |
| ip6_t *ip6h) |
| { |
| /* |
| * XXX cut&paste shared with ipsec_init_inbound_sel |
| */ |
| uint16_t *ports; |
| ushort_t hdr_len; |
| mblk_t *spare_mp = NULL; |
| uint8_t *nexthdrp; |
| uint8_t nexthdr; |
| uint8_t *typecode; |
| uint8_t check_proto; |
| |
| ASSERT((ipha == NULL && ip6h != NULL) || |
| (ipha != NULL && ip6h == NULL)); |
| |
| if (ip6h != NULL) { |
| check_proto = IPPROTO_ICMPV6; |
| nexthdr = ip6h->ip6_nxt; |
| switch (nexthdr) { |
| case IPPROTO_HOPOPTS: |
| case IPPROTO_ROUTING: |
| case IPPROTO_DSTOPTS: |
| /* |
| * Use ip_hdr_length_nexthdr_v6(). And have a spare |
| * mblk that's contiguous to feed it |
| */ |
| spare_mp = msgpullup(mp, -1); |
| if (spare_mp == NULL || |
| !ip_hdr_length_nexthdr_v6(spare_mp, |
| (ip6_t *)spare_mp->b_rptr, &hdr_len, |
| &nexthdrp)) { |
| /* Always works, even if NULL. */ |
| freemsg(spare_mp); |
| freemsg(mp); |
| return (B_FALSE); |
| } else { |
| nexthdr = *nexthdrp; |
| /* We can just extract based on hdr_len now. */ |
| } |
| break; |
| default: |
| hdr_len = IPV6_HDR_LEN; |
| break; |
| } |
| } else { |
| check_proto = IPPROTO_ICMP; |
| hdr_len = IPH_HDR_LENGTH(ipha); |
| nexthdr = ipha->ipha_protocol; |
| } |
| |
| sel->ips_protocol = nexthdr; |
| if (nexthdr != IPPROTO_TCP && nexthdr != IPPROTO_UDP && |
| nexthdr != IPPROTO_SCTP && nexthdr != check_proto) { |
| sel->ips_local_port = sel->ips_remote_port = 0; |
| freemsg(spare_mp); /* Always works, even if NULL. */ |
| return (B_TRUE); |
| } |
| |
| if (&mp->b_rptr[hdr_len] + 4 > mp->b_wptr) { |
| /* If we didn't pullup a copy already, do so now. */ |
| /* |
| * XXX performance, will upper-layers frequently split TCP/UDP |
| * apart from IP or options? If so, perhaps we should revisit |
| * the spare_mp strategy. |
| * |
| * XXX should this be msgpullup(mp, hdr_len+4) ??? |
| */ |
| if (spare_mp == NULL && |
| (spare_mp = msgpullup(mp, -1)) == NULL) { |
| freemsg(mp); |
| return (B_FALSE); |
| } |
| ports = (uint16_t *)&spare_mp->b_rptr[hdr_len]; |
| } else { |
| ports = (uint16_t *)&mp->b_rptr[hdr_len]; |
| } |
| |
| if (nexthdr == check_proto) { |
| typecode = (uint8_t *)ports; |
| sel->ips_icmp_type = *typecode++; |
| sel->ips_icmp_code = *typecode; |
| sel->ips_remote_port = sel->ips_local_port = 0; |
| freemsg(spare_mp); /* Always works, even if NULL */ |
| return (B_TRUE); |
| } |
| |
| sel->ips_local_port = *ports++; |
| sel->ips_remote_port = *ports; |
| freemsg(spare_mp); /* Always works, even if NULL */ |
| return (B_TRUE); |
| } |
| |
| /* |
| * Create an ipsec_action_t based on the way an inbound packet was protected. |
| * Used to reflect traffic back to a sender. |
| * |
| * We don't bother interning the action into the hash table. |
| */ |
| ipsec_action_t * |
| ipsec_in_to_out_action(ipsec_in_t *ii) |
| { |
| ipsa_t *ah_assoc, *esp_assoc; |
| uint_t auth_alg = 0, encr_alg = 0, espa_alg = 0; |
| ipsec_action_t *ap; |
| boolean_t unique; |
| |
| ap = kmem_cache_alloc(ipsec_action_cache, KM_NOSLEEP); |
| |
| if (ap == NULL) |
| return (NULL); |
| |
| bzero(ap, sizeof (*ap)); |
| HASH_NULL(ap, ipa_hash); |
| ap->ipa_next = NULL; |
| ap->ipa_refs = 1; |
| |
| /* |
| * Get the algorithms that were used for this packet. |
| */ |
| ap->ipa_act.ipa_type = IPSEC_ACT_APPLY; |
| ap->ipa_act.ipa_log = 0; |
| ah_assoc = ii->ipsec_in_ah_sa; |
| ap->ipa_act.ipa_apply.ipp_use_ah = (ah_assoc != NULL); |
| |
| esp_assoc = ii->ipsec_in_esp_sa; |
| ap->ipa_act.ipa_apply.ipp_use_esp = (esp_assoc != NULL); |
| |
| if (esp_assoc != NULL) { |
| encr_alg = esp_assoc->ipsa_encr_alg; |
| espa_alg = esp_assoc->ipsa_auth_alg; |
| ap->ipa_act.ipa_apply.ipp_use_espa = (espa_alg != 0); |
| } |
| if (ah_assoc != NULL) |
| auth_alg = ah_assoc->ipsa_auth_alg; |
| |
| ap->ipa_act.ipa_apply.ipp_encr_alg = (uint8_t)encr_alg; |
| ap->ipa_act.ipa_apply.ipp_auth_alg = (uint8_t)auth_alg; |
| ap->ipa_act.ipa_apply.ipp_esp_auth_alg = (uint8_t)espa_alg; |
| ap->ipa_act.ipa_apply.ipp_use_se = ii->ipsec_in_decaps; |
| unique = B_FALSE; |
| |
| if (esp_assoc != NULL) { |
| ap->ipa_act.ipa_apply.ipp_espa_minbits = |
| esp_assoc->ipsa_authkeybits; |
| ap->ipa_act.ipa_apply.ipp_espa_maxbits = |
| esp_assoc->ipsa_authkeybits; |
| ap->ipa_act.ipa_apply.ipp_espe_minbits = |
| esp_assoc->ipsa_encrkeybits; |
| ap->ipa_act.ipa_apply.ipp_espe_maxbits = |
| esp_assoc->ipsa_encrkeybits; |
| ap->ipa_act.ipa_apply.ipp_km_proto = esp_assoc->ipsa_kmp; |
| ap->ipa_act.ipa_apply.ipp_km_cookie = esp_assoc->ipsa_kmc; |
| if (esp_assoc->ipsa_flags & IPSA_F_UNIQUE) |
| unique = B_TRUE; |
| } |
| if (ah_assoc != NULL) { |
| ap->ipa_act.ipa_apply.ipp_ah_minbits = |
| ah_assoc->ipsa_authkeybits; |
| ap->ipa_act.ipa_apply.ipp_ah_maxbits = |
| ah_assoc->ipsa_authkeybits; |
| ap->ipa_act.ipa_apply.ipp_km_proto = ah_assoc->ipsa_kmp; |
| ap->ipa_act.ipa_apply.ipp_km_cookie = ah_assoc->ipsa_kmc; |
| if (ah_assoc->ipsa_flags & IPSA_F_UNIQUE) |
| unique = B_TRUE; |
| } |
| ap->ipa_act.ipa_apply.ipp_use_unique = unique; |
| ap->ipa_want_unique = unique; |
| ap->ipa_allow_clear = B_FALSE; |
| ap->ipa_want_se = ii->ipsec_in_decaps; |
| ap->ipa_want_ah = (ah_assoc != NULL); |
| ap->ipa_want_esp = (esp_assoc != NULL); |
| |
| ap->ipa_ovhd = ipsec_act_ovhd(&ap->ipa_act); |
| |
| ap->ipa_act.ipa_apply.ipp_replay_depth = 0; /* don't care */ |
| |
| return (ap); |
| } |
| |
| |
| /* |
| * Compute the worst-case amount of extra space required by an action. |
| * Note that, because of the ESP considerations listed below, this is |
| * actually not the same as the best-case reduction in the MTU; in the |
| * future, we should pass additional information to this function to |
| * allow the actual MTU impact to be computed. |
| * |
| * AH: Revisit this if we implement algorithms with |
| * a verifier size of more than 12 bytes. |
| * |
| * ESP: A more exact but more messy computation would take into |
| * account the interaction between the cipher block size and the |
| * effective MTU, yielding the inner payload size which reflects a |
| * packet with *minimum* ESP padding.. |
| */ |
| static int32_t |
| ipsec_act_ovhd(const ipsec_act_t *act) |
| { |
| int32_t overhead = 0; |
| |
| if (act->ipa_type == IPSEC_ACT_APPLY) { |
| const ipsec_prot_t *ipp = &act->ipa_apply; |
| |
| if (ipp->ipp_use_ah) |
| overhead += IPSEC_MAX_AH_HDR_SIZE; |
| if (ipp->ipp_use_esp) { |
| overhead += IPSEC_MAX_ESP_HDR_SIZE; |
| overhead += sizeof (struct udphdr); |
| } |
| if (ipp->ipp_use_se) |
| overhead += IP_SIMPLE_HDR_LENGTH; |
| } |
| return (overhead); |
| } |
| |
| /* |
| * This hash function is used only when creating policies and thus is not |
| * performance-critical for packet flows. |
| * |
| * Future work: canonicalize the structures hashed with this (i.e., |
| * zeroize padding) so the hash works correctly. |
| */ |
| /* ARGSUSED */ |
| static uint32_t |
| policy_hash(int size, const void *start, const void *end) |
| { |
| return (0); |
| } |
| |
| |
| /* |
| * Hash function macros for each address type. |
| * |
| * The IPV6 hash function assumes that the low order 32-bits of the |
| * address (typically containing the low order 24 bits of the mac |
| * address) are reasonably well-distributed. Revisit this if we run |
| * into trouble from lots of collisions on ::1 addresses and the like |
| * (seems unlikely). |
| */ |
| #define IPSEC_IPV4_HASH(a) ((a) % ipsec_spd_hashsize) |
| #define IPSEC_IPV6_HASH(a) ((a.s6_addr32[3]) % ipsec_spd_hashsize) |
| |
| /* |
| * These two hash functions should produce coordinated values |
| * but have slightly different roles. |
| */ |
| static uint32_t |
| selkey_hash(const ipsec_selkey_t *selkey) |
| { |
| uint32_t valid = selkey->ipsl_valid; |
| |
| if (!(valid & IPSL_REMOTE_ADDR)) |
| return (IPSEC_SEL_NOHASH); |
| |
| if (valid & IPSL_IPV4) { |
| if (selkey->ipsl_remote_pfxlen == 32) |
| return (IPSEC_IPV4_HASH(selkey->ipsl_remote.ipsad_v4)); |
| } |
| if (valid & IPSL_IPV6) { |
| if (selkey->ipsl_remote_pfxlen == 128) |
| return (IPSEC_IPV6_HASH(selkey->ipsl_remote.ipsad_v6)); |
| } |
| return (IPSEC_SEL_NOHASH); |
| } |
| |
| static uint32_t |
| selector_hash(ipsec_selector_t *sel) |
| { |
| if (sel->ips_isv4) { |
| return (IPSEC_IPV4_HASH(sel->ips_remote_addr_v4)); |
| } |
| return (IPSEC_IPV6_HASH(sel->ips_remote_addr_v6)); |
| } |
| |
| /* |
| * Intern actions into the action hash table. |
| */ |
| ipsec_action_t * |
| ipsec_act_find(const ipsec_act_t *a, int n) |
| { |
| int i; |
| uint32_t hval; |
| ipsec_action_t *ap; |
| ipsec_action_t *prev = NULL; |
| int32_t overhead, maxovhd = 0; |
| boolean_t allow_clear = B_FALSE; |
| boolean_t want_ah = B_FALSE; |
| boolean_t want_esp = B_FALSE; |
| boolean_t want_se = B_FALSE; |
| boolean_t want_unique = B_FALSE; |
| |
| /* |
| * TODO: should canonicalize a[] (i.e., zeroize any padding) |
| * so we can use a non-trivial policy_hash function. |
| */ |
| for (i = n-1; i >= 0; i--) { |
| hval = policy_hash(IPSEC_ACTION_HASH_SIZE, &a[i], &a[n]); |
| |
| HASH_LOCK(ipsec_action_hash, hval); |
| |
| for (HASH_ITERATE(ap, ipa_hash, ipsec_action_hash, hval)) { |
| if (bcmp(&ap->ipa_act, &a[i], sizeof (*a)) != 0) |
| continue; |
| if (ap->ipa_next != prev) |
| continue; |
| break; |
| } |
| if (ap != NULL) { |
| HASH_UNLOCK(ipsec_action_hash, hval); |
| prev = ap; |
| continue; |
| } |
| /* |
| * need to allocate a new one.. |
| */ |
| ap = kmem_cache_alloc(ipsec_action_cache, KM_NOSLEEP); |
| if (ap == NULL) { |
| HASH_UNLOCK(ipsec_action_hash, hval); |
| if (prev != NULL) |
| ipsec_action_free(prev); |
| return (NULL); |
| } |
| HASH_INSERT(ap, ipa_hash, ipsec_action_hash, hval); |
| |
| ap->ipa_next = prev; |
| ap->ipa_act = a[i]; |
| |
| overhead = ipsec_act_ovhd(&a[i]); |
| if (maxovhd < overhead) |
| maxovhd = overhead; |
| |
| if ((a[i].ipa_type == IPSEC_ACT_BYPASS) || |
| (a[i].ipa_type == IPSEC_ACT_CLEAR)) |
| allow_clear = B_TRUE; |
| if (a[i].ipa_type == IPSEC_ACT_APPLY) { |
| const ipsec_prot_t *ipp = &a[i].ipa_apply; |
| |
| ASSERT(ipp->ipp_use_ah || ipp->ipp_use_esp); |
| want_ah |= ipp->ipp_use_ah; |
| want_esp |= ipp->ipp_use_esp; |
| want_se |= ipp->ipp_use_se; |
| want_unique |= ipp->ipp_use_unique; |
| } |
| ap->ipa_allow_clear = allow_clear; |
| ap->ipa_want_ah = want_ah; |
| ap->ipa_want_esp = want_esp; |
| ap->ipa_want_se = want_se; |
| ap->ipa_want_unique = want_unique; |
| ap->ipa_refs = 1; /* from the hash table */ |
| ap->ipa_ovhd = maxovhd; |
| if (prev) |
| prev->ipa_refs++; |
| prev = ap; |
| HASH_UNLOCK(ipsec_action_hash, hval); |
| } |
| |
| ap->ipa_refs++; /* caller's reference */ |
| |
| return (ap); |
| } |
| |
| /* |
| * Called when refcount goes to 0, indicating that all references to this |
| * node are gone. |
| * |
| * This does not unchain the action from the hash table. |
| */ |
| void |
| ipsec_action_free(ipsec_action_t *ap) |
| { |
| for (;;) { |
| ipsec_action_t *np = ap->ipa_next; |
| ASSERT(ap->ipa_refs == 0); |
| ASSERT(ap->ipa_hash.hash_pp == NULL); |
| kmem_cache_free(ipsec_action_cache, ap); |
| ap = np; |
| /* Inlined IPACT_REFRELE -- avoid recursion */ |
| if (ap == NULL) |
| break; |
| membar_exit(); |
| if (atomic_add_32_nv(&(ap)->ipa_refs, -1) != 0) |
| break; |
| /* End inlined IPACT_REFRELE */ |
| } |
| } |
| |
| /* |
| * Periodically sweep action hash table for actions with refcount==1, and |
| * nuke them. We cannot do this "on demand" (i.e., from IPACT_REFRELE) |
| * because we can't close the race between another thread finding the action |
| * in the hash table without holding the bucket lock during IPACT_REFRELE. |
| * Instead, we run this function sporadically to clean up after ourselves; |
| * we also set it as the "reclaim" function for the action kmem_cache. |
| * |
| * Note that it may take several passes of ipsec_action_gc() to free all |
| * "stale" actions. |
| */ |
| /* ARGSUSED */ |
| static void |
| |