| /* |
| * 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 2010 Sun Microsystems, Inc. All rights reserved. |
| * Use is subject to license terms. |
| * Copyright (c) 2012 Nexenta Systems, Inc. All rights reserved. |
| * Copyright (c) 2018 Joyent, Inc. |
| * Copyright 2022 MNX Cloud, Inc. |
| */ |
| |
| #include <sys/types.h> |
| #include <sys/stream.h> |
| #include <sys/stropts.h> |
| #include <sys/strsubr.h> |
| #include <sys/errno.h> |
| #include <sys/ddi.h> |
| #include <sys/debug.h> |
| #include <sys/cmn_err.h> |
| #include <sys/stream.h> |
| #include <sys/strlog.h> |
| #include <sys/kmem.h> |
| #include <sys/sunddi.h> |
| #include <sys/tihdr.h> |
| #include <sys/atomic.h> |
| #include <sys/socket.h> |
| #include <sys/sysmacros.h> |
| #include <sys/crypto/common.h> |
| #include <sys/crypto/api.h> |
| #include <sys/zone.h> |
| #include <netinet/in.h> |
| #include <net/if.h> |
| #include <net/pfkeyv2.h> |
| #include <net/pfpolicy.h> |
| #include <inet/common.h> |
| #include <netinet/ip6.h> |
| #include <inet/ip.h> |
| #include <inet/ip_ire.h> |
| #include <inet/ip6.h> |
| #include <inet/ipsec_info.h> |
| #include <inet/tcp.h> |
| #include <inet/sadb.h> |
| #include <inet/ipsec_impl.h> |
| #include <inet/ipsecah.h> |
| #include <inet/ipsecesp.h> |
| #include <sys/random.h> |
| #include <sys/dlpi.h> |
| #include <sys/strsun.h> |
| #include <sys/strsubr.h> |
| #include <inet/ip_if.h> |
| #include <inet/ipdrop.h> |
| #include <inet/ipclassifier.h> |
| #include <inet/sctp_ip.h> |
| #include <sys/tsol/tnet.h> |
| |
| /* |
| * This source file contains Security Association Database (SADB) common |
| * routines. They are linked in with the AH module. Since AH has no chance |
| * of falling under export control, it was safe to link it in there. |
| */ |
| |
| static uint8_t *sadb_action_to_ecomb(uint8_t *, uint8_t *, ipsec_action_t *, |
| netstack_t *); |
| static ipsa_t *sadb_torch_assoc(isaf_t *, ipsa_t *); |
| static void sadb_destroy_acqlist(iacqf_t **, uint_t, boolean_t, |
| netstack_t *); |
| static void sadb_destroy(sadb_t *, netstack_t *); |
| static mblk_t *sadb_sa2msg(ipsa_t *, sadb_msg_t *); |
| static ts_label_t *sadb_label_from_sens(sadb_sens_t *, uint64_t *); |
| |
| static time_t sadb_add_time(time_t, uint64_t); |
| static void lifetime_fuzz(ipsa_t *); |
| static void age_pair_peer_list(templist_t *, sadb_t *, boolean_t); |
| static int get_ipsa_pair(ipsa_query_t *, ipsap_t *, int *); |
| static void init_ipsa_pair(ipsap_t *); |
| static void destroy_ipsa_pair(ipsap_t *); |
| static int update_pairing(ipsap_t *, ipsa_query_t *, keysock_in_t *, int *); |
| static void ipsa_set_replay(ipsa_t *ipsa, uint32_t offset); |
| |
| /* |
| * ipsacq_maxpackets is defined here to make it tunable |
| * from /etc/system. |
| */ |
| extern uint64_t ipsacq_maxpackets; |
| |
| #define SET_EXPIRE(sa, delta, exp) { \ |
| if (((sa)->ipsa_ ## delta) != 0) { \ |
| (sa)->ipsa_ ## exp = sadb_add_time((sa)->ipsa_addtime, \ |
| (sa)->ipsa_ ## delta); \ |
| } \ |
| } |
| |
| #define UPDATE_EXPIRE(sa, delta, exp) { \ |
| if (((sa)->ipsa_ ## delta) != 0) { \ |
| time_t tmp = sadb_add_time((sa)->ipsa_usetime, \ |
| (sa)->ipsa_ ## delta); \ |
| if (((sa)->ipsa_ ## exp) == 0) \ |
| (sa)->ipsa_ ## exp = tmp; \ |
| else \ |
| (sa)->ipsa_ ## exp = \ |
| MIN((sa)->ipsa_ ## exp, tmp); \ |
| } \ |
| } |
| |
| |
| /* wrap the macro so we can pass it as a function pointer */ |
| void |
| sadb_sa_refrele(void *target) |
| { |
| IPSA_REFRELE(((ipsa_t *)target)); |
| } |
| |
| /* |
| * We presume that sizeof (long) == sizeof (time_t) and that time_t is |
| * a signed type. |
| */ |
| #define TIME_MAX LONG_MAX |
| |
| /* |
| * PF_KEY gives us lifetimes in uint64_t seconds. We presume that |
| * time_t is defined to be a signed type with the same range as |
| * "long". On ILP32 systems, we thus run the risk of wrapping around |
| * at end of time, as well as "overwrapping" the clock back around |
| * into a seemingly valid but incorrect future date earlier than the |
| * desired expiration. |
| * |
| * In order to avoid odd behavior (either negative lifetimes or loss |
| * of high order bits) when someone asks for bizarrely long SA |
| * lifetimes, we do a saturating add for expire times. |
| * |
| * We presume that ILP32 systems will be past end of support life when |
| * the 32-bit time_t overflows (a dangerous assumption, mind you..). |
| * |
| * On LP64, 2^64 seconds are about 5.8e11 years, at which point we |
| * will hopefully have figured out clever ways to avoid the use of |
| * fixed-sized integers in computation. |
| */ |
| static time_t |
| sadb_add_time(time_t base, uint64_t delta) |
| { |
| /* |
| * Clip delta to the maximum possible time_t value to |
| * prevent "overwrapping" back into a shorter-than-desired |
| * future time. |
| */ |
| if (delta > TIME_MAX) |
| delta = TIME_MAX; |
| |
| if (base > 0) { |
| if (TIME_MAX - base < delta) |
| return (TIME_MAX); /* Overflow */ |
| } |
| return (base + delta); |
| } |
| |
| /* |
| * Callers of this function have already created a working security |
| * association, and have found the appropriate table & hash chain. All this |
| * function does is check duplicates, and insert the SA. The caller needs to |
| * hold the hash bucket lock and increment the refcnt before insertion. |
| * |
| * Return 0 if success, EEXIST if collision. |
| */ |
| #define SA_UNIQUE_MATCH(sa1, sa2) \ |
| (((sa1)->ipsa_unique_id & (sa1)->ipsa_unique_mask) == \ |
| ((sa2)->ipsa_unique_id & (sa2)->ipsa_unique_mask)) |
| |
| int |
| sadb_insertassoc(ipsa_t *ipsa, isaf_t *bucket) |
| { |
| ipsa_t **ptpn = NULL; |
| ipsa_t *walker; |
| boolean_t unspecsrc; |
| |
| ASSERT(MUTEX_HELD(&bucket->isaf_lock)); |
| |
| unspecsrc = IPSA_IS_ADDR_UNSPEC(ipsa->ipsa_srcaddr, ipsa->ipsa_addrfam); |
| |
| walker = bucket->isaf_ipsa; |
| ASSERT(walker == NULL || ipsa->ipsa_addrfam == walker->ipsa_addrfam); |
| |
| /* |
| * Find insertion point (pointed to with **ptpn). Insert at the head |
| * of the list unless there's an unspecified source address, then |
| * insert it after the last SA with a specified source address. |
| * |
| * BTW, you'll have to walk the whole chain, matching on {DST, SPI} |
| * checking for collisions. |
| */ |
| |
| while (walker != NULL) { |
| if (IPSA_ARE_ADDR_EQUAL(walker->ipsa_dstaddr, |
| ipsa->ipsa_dstaddr, ipsa->ipsa_addrfam)) { |
| if (walker->ipsa_spi == ipsa->ipsa_spi) |
| return (EEXIST); |
| |
| mutex_enter(&walker->ipsa_lock); |
| if (ipsa->ipsa_state == IPSA_STATE_MATURE && |
| (walker->ipsa_flags & IPSA_F_USED) && |
| SA_UNIQUE_MATCH(walker, ipsa)) { |
| walker->ipsa_flags |= IPSA_F_CINVALID; |
| } |
| mutex_exit(&walker->ipsa_lock); |
| } |
| |
| if (ptpn == NULL && unspecsrc) { |
| if (IPSA_IS_ADDR_UNSPEC(walker->ipsa_srcaddr, |
| walker->ipsa_addrfam)) |
| ptpn = walker->ipsa_ptpn; |
| else if (walker->ipsa_next == NULL) |
| ptpn = &walker->ipsa_next; |
| } |
| |
| walker = walker->ipsa_next; |
| } |
| |
| if (ptpn == NULL) |
| ptpn = &bucket->isaf_ipsa; |
| ipsa->ipsa_next = *ptpn; |
| ipsa->ipsa_ptpn = ptpn; |
| if (ipsa->ipsa_next != NULL) |
| ipsa->ipsa_next->ipsa_ptpn = &ipsa->ipsa_next; |
| *ptpn = ipsa; |
| ipsa->ipsa_linklock = &bucket->isaf_lock; |
| |
| return (0); |
| } |
| #undef SA_UNIQUE_MATCH |
| |
| /* |
| * Free a security association. Its reference count is 0, which means |
| * I must free it. The SA must be unlocked and must not be linked into |
| * any fanout list. |
| */ |
| static void |
| sadb_freeassoc(ipsa_t *ipsa) |
| { |
| ipsec_stack_t *ipss = ipsa->ipsa_netstack->netstack_ipsec; |
| mblk_t *asyncmp, *mp; |
| |
| ASSERT(ipss != NULL); |
| ASSERT(MUTEX_NOT_HELD(&ipsa->ipsa_lock)); |
| ASSERT(ipsa->ipsa_refcnt == 0); |
| ASSERT(ipsa->ipsa_next == NULL); |
| ASSERT(ipsa->ipsa_ptpn == NULL); |
| |
| |
| asyncmp = sadb_clear_lpkt(ipsa); |
| if (asyncmp != NULL) { |
| mp = ip_recv_attr_free_mblk(asyncmp); |
| ip_drop_packet(mp, B_TRUE, NULL, |
| DROPPER(ipss, ipds_sadb_inlarval_timeout), |
| &ipss->ipsec_sadb_dropper); |
| } |
| mutex_enter(&ipsa->ipsa_lock); |
| |
| if (ipsa->ipsa_tsl != NULL) { |
| label_rele(ipsa->ipsa_tsl); |
| ipsa->ipsa_tsl = NULL; |
| } |
| |
| if (ipsa->ipsa_otsl != NULL) { |
| label_rele(ipsa->ipsa_otsl); |
| ipsa->ipsa_otsl = NULL; |
| } |
| |
| ipsec_destroy_ctx_tmpl(ipsa, IPSEC_ALG_AUTH); |
| ipsec_destroy_ctx_tmpl(ipsa, IPSEC_ALG_ENCR); |
| mutex_exit(&ipsa->ipsa_lock); |
| |
| /* bzero() these fields for paranoia's sake. */ |
| if (ipsa->ipsa_authkey != NULL) { |
| bzero(ipsa->ipsa_authkey, ipsa->ipsa_authkeylen); |
| kmem_free(ipsa->ipsa_authkey, ipsa->ipsa_authkeylen); |
| } |
| if (ipsa->ipsa_encrkey != NULL) { |
| bzero(ipsa->ipsa_encrkey, ipsa->ipsa_encrkeylen); |
| kmem_free(ipsa->ipsa_encrkey, ipsa->ipsa_encrkeylen); |
| } |
| if (ipsa->ipsa_nonce_buf != NULL) { |
| bzero(ipsa->ipsa_nonce_buf, sizeof (ipsec_nonce_t)); |
| kmem_free(ipsa->ipsa_nonce_buf, sizeof (ipsec_nonce_t)); |
| } |
| if (ipsa->ipsa_src_cid != NULL) { |
| IPSID_REFRELE(ipsa->ipsa_src_cid); |
| } |
| if (ipsa->ipsa_dst_cid != NULL) { |
| IPSID_REFRELE(ipsa->ipsa_dst_cid); |
| } |
| if (ipsa->ipsa_emech.cm_param != NULL) |
| kmem_free(ipsa->ipsa_emech.cm_param, |
| ipsa->ipsa_emech.cm_param_len); |
| |
| mutex_destroy(&ipsa->ipsa_lock); |
| kmem_free(ipsa, sizeof (*ipsa)); |
| } |
| |
| /* |
| * Unlink a security association from a hash bucket. Assume the hash bucket |
| * lock is held, but the association's lock is not. |
| * |
| * Note that we do not bump the bucket's generation number here because |
| * we might not be making a visible change to the set of visible SA's. |
| * All callers MUST bump the bucket's generation number before they unlock |
| * the bucket if they use sadb_unlinkassoc to permanetly remove an SA which |
| * was present in the bucket at the time it was locked. |
| */ |
| void |
| sadb_unlinkassoc(ipsa_t *ipsa) |
| { |
| ASSERT(ipsa->ipsa_linklock != NULL); |
| ASSERT(MUTEX_HELD(ipsa->ipsa_linklock)); |
| |
| /* Sometimes someone beats us here with the same SA. Check now. */ |
| if (ipsa->ipsa_ptpn == NULL) |
| return; |
| |
| /* These fields are protected by the link lock. */ |
| *(ipsa->ipsa_ptpn) = ipsa->ipsa_next; |
| if (ipsa->ipsa_next != NULL) { |
| ipsa->ipsa_next->ipsa_ptpn = ipsa->ipsa_ptpn; |
| ipsa->ipsa_next = NULL; |
| } |
| ipsa->ipsa_ptpn = NULL; |
| |
| /* This may destroy the SA. */ |
| IPSA_REFRELE(ipsa); |
| } |
| |
| void |
| sadb_delete_cluster(ipsa_t *assoc) |
| { |
| uint8_t protocol; |
| |
| if (cl_inet_deletespi && |
| ((assoc->ipsa_state == IPSA_STATE_LARVAL) || |
| (assoc->ipsa_state == IPSA_STATE_MATURE))) { |
| protocol = (assoc->ipsa_type == SADB_SATYPE_AH) ? |
| IPPROTO_AH : IPPROTO_ESP; |
| cl_inet_deletespi(assoc->ipsa_netstack->netstack_stackid, |
| protocol, assoc->ipsa_spi, NULL); |
| } |
| } |
| |
| /* |
| * Create a larval security association with the specified SPI. All other |
| * fields are zeroed. |
| */ |
| static ipsa_t * |
| sadb_makelarvalassoc(uint32_t spi, uint32_t *src, uint32_t *dst, int addrfam, |
| netstack_t *ns) |
| { |
| ipsa_t *newbie; |
| |
| /* |
| * Allocate... |
| */ |
| |
| newbie = (ipsa_t *)kmem_zalloc(sizeof (ipsa_t), KM_NOSLEEP); |
| if (newbie == NULL) { |
| /* Can't make new larval SA. */ |
| return (NULL); |
| } |
| |
| /* Assigned requested SPI, assume caller does SPI allocation magic. */ |
| newbie->ipsa_spi = spi; |
| newbie->ipsa_netstack = ns; /* No netstack_hold */ |
| |
| /* |
| * Copy addresses... |
| */ |
| |
| IPSA_COPY_ADDR(newbie->ipsa_srcaddr, src, addrfam); |
| IPSA_COPY_ADDR(newbie->ipsa_dstaddr, dst, addrfam); |
| |
| newbie->ipsa_addrfam = addrfam; |
| |
| /* |
| * Set common initialization values, including refcnt. |
| */ |
| mutex_init(&newbie->ipsa_lock, NULL, MUTEX_DEFAULT, NULL); |
| newbie->ipsa_state = IPSA_STATE_LARVAL; |
| newbie->ipsa_refcnt = 1; |
| newbie->ipsa_freefunc = sadb_freeassoc; |
| |
| /* |
| * There aren't a lot of other common initialization values, as |
| * they are copied in from the PF_KEY message. |
| */ |
| |
| return (newbie); |
| } |
| |
| /* |
| * Call me to initialize a security association fanout. |
| */ |
| static int |
| sadb_init_fanout(isaf_t **tablep, uint_t size, int kmflag) |
| { |
| isaf_t *table; |
| int i; |
| |
| table = (isaf_t *)kmem_alloc(size * sizeof (*table), kmflag); |
| *tablep = table; |
| |
| if (table == NULL) |
| return (ENOMEM); |
| |
| for (i = 0; i < size; i++) { |
| mutex_init(&(table[i].isaf_lock), NULL, MUTEX_DEFAULT, NULL); |
| table[i].isaf_ipsa = NULL; |
| table[i].isaf_gen = 0; |
| } |
| |
| return (0); |
| } |
| |
| /* |
| * Call me to initialize an acquire fanout |
| */ |
| static int |
| sadb_init_acfanout(iacqf_t **tablep, uint_t size, int kmflag) |
| { |
| iacqf_t *table; |
| int i; |
| |
| table = (iacqf_t *)kmem_alloc(size * sizeof (*table), kmflag); |
| *tablep = table; |
| |
| if (table == NULL) |
| return (ENOMEM); |
| |
| for (i = 0; i < size; i++) { |
| mutex_init(&(table[i].iacqf_lock), NULL, MUTEX_DEFAULT, NULL); |
| table[i].iacqf_ipsacq = NULL; |
| } |
| |
| return (0); |
| } |
| |
| /* |
| * Attempt to initialize an SADB instance. On failure, return ENOMEM; |
| * caller must clean up partial allocations. |
| */ |
| static int |
| sadb_init_trial(sadb_t *sp, uint_t size, int kmflag) |
| { |
| ASSERT(sp->sdb_of == NULL); |
| ASSERT(sp->sdb_if == NULL); |
| ASSERT(sp->sdb_acq == NULL); |
| |
| sp->sdb_hashsize = size; |
| if (sadb_init_fanout(&sp->sdb_of, size, kmflag) != 0) |
| return (ENOMEM); |
| if (sadb_init_fanout(&sp->sdb_if, size, kmflag) != 0) |
| return (ENOMEM); |
| if (sadb_init_acfanout(&sp->sdb_acq, size, kmflag) != 0) |
| return (ENOMEM); |
| |
| return (0); |
| } |
| |
| /* |
| * Call me to initialize an SADB instance; fall back to default size on failure. |
| */ |
| static void |
| sadb_init(const char *name, sadb_t *sp, uint_t size, uint_t ver, |
| netstack_t *ns) |
| { |
| ASSERT(sp->sdb_of == NULL); |
| ASSERT(sp->sdb_if == NULL); |
| ASSERT(sp->sdb_acq == NULL); |
| |
| if (size < IPSEC_DEFAULT_HASH_SIZE) |
| size = IPSEC_DEFAULT_HASH_SIZE; |
| |
| if (sadb_init_trial(sp, size, KM_NOSLEEP) != 0) { |
| |
| cmn_err(CE_WARN, |
| "Unable to allocate %u entry IPv%u %s SADB hash table", |
| size, ver, name); |
| |
| sadb_destroy(sp, ns); |
| size = IPSEC_DEFAULT_HASH_SIZE; |
| cmn_err(CE_WARN, "Falling back to %d entries", size); |
| (void) sadb_init_trial(sp, size, KM_SLEEP); |
| } |
| } |
| |
| |
| /* |
| * Initialize an SADB-pair. |
| */ |
| void |
| sadbp_init(const char *name, sadbp_t *sp, int type, int size, netstack_t *ns) |
| { |
| sadb_init(name, &sp->s_v4, size, 4, ns); |
| sadb_init(name, &sp->s_v6, size, 6, ns); |
| |
| sp->s_satype = type; |
| |
| ASSERT((type == SADB_SATYPE_AH) || (type == SADB_SATYPE_ESP)); |
| if (type == SADB_SATYPE_AH) { |
| ipsec_stack_t *ipss = ns->netstack_ipsec; |
| |
| ip_drop_register(&ipss->ipsec_sadb_dropper, "IPsec SADB"); |
| sp->s_addflags = AH_ADD_SETTABLE_FLAGS; |
| sp->s_updateflags = AH_UPDATE_SETTABLE_FLAGS; |
| } else { |
| sp->s_addflags = ESP_ADD_SETTABLE_FLAGS; |
| sp->s_updateflags = ESP_UPDATE_SETTABLE_FLAGS; |
| } |
| } |
| |
| /* |
| * Deliver a single SADB_DUMP message representing a single SA. This is |
| * called many times by sadb_dump(). |
| * |
| * If the return value of this is ENOBUFS (not the same as ENOMEM), then |
| * the caller should take that as a hint that dupb() on the "original answer" |
| * failed, and that perhaps the caller should try again with a copyb()ed |
| * "original answer". |
| */ |
| static int |
| sadb_dump_deliver(queue_t *pfkey_q, mblk_t *original_answer, ipsa_t *ipsa, |
| sadb_msg_t *samsg) |
| { |
| mblk_t *answer; |
| |
| answer = dupb(original_answer); |
| if (answer == NULL) |
| return (ENOBUFS); |
| answer->b_cont = sadb_sa2msg(ipsa, samsg); |
| if (answer->b_cont == NULL) { |
| freeb(answer); |
| return (ENOMEM); |
| } |
| |
| /* Just do a putnext, and let keysock deal with flow control. */ |
| putnext(pfkey_q, answer); |
| return (0); |
| } |
| |
| /* |
| * Common function to allocate and prepare a keysock_out_t M_CTL message. |
| */ |
| mblk_t * |
| sadb_keysock_out(minor_t serial) |
| { |
| mblk_t *mp; |
| keysock_out_t *kso; |
| |
| mp = allocb(sizeof (ipsec_info_t), BPRI_HI); |
| if (mp != NULL) { |
| mp->b_datap->db_type = M_CTL; |
| mp->b_wptr += sizeof (ipsec_info_t); |
| kso = (keysock_out_t *)mp->b_rptr; |
| kso->ks_out_type = KEYSOCK_OUT; |
| kso->ks_out_len = sizeof (*kso); |
| kso->ks_out_serial = serial; |
| } |
| |
| return (mp); |
| } |
| |
| /* |
| * Perform an SADB_DUMP, spewing out every SA in an array of SA fanouts |
| * to keysock. |
| */ |
| static int |
| sadb_dump_fanout(queue_t *pfkey_q, mblk_t *mp, minor_t serial, isaf_t *fanout, |
| int num_entries, boolean_t do_peers, time_t active_time) |
| { |
| int i, error = 0; |
| mblk_t *original_answer; |
| ipsa_t *walker; |
| sadb_msg_t *samsg; |
| time_t current; |
| |
| /* |
| * For each IPSA hash bucket do: |
| * - Hold the mutex |
| * - Walk each entry, doing an sadb_dump_deliver() on it. |
| */ |
| ASSERT(mp->b_cont != NULL); |
| samsg = (sadb_msg_t *)mp->b_cont->b_rptr; |
| |
| original_answer = sadb_keysock_out(serial); |
| if (original_answer == NULL) |
| return (ENOMEM); |
| |
| current = gethrestime_sec(); |
| for (i = 0; i < num_entries; i++) { |
| mutex_enter(&fanout[i].isaf_lock); |
| for (walker = fanout[i].isaf_ipsa; walker != NULL; |
| walker = walker->ipsa_next) { |
| if (!do_peers && walker->ipsa_haspeer) |
| continue; |
| if ((active_time != 0) && |
| ((current - walker->ipsa_lastuse) > active_time)) |
| continue; |
| error = sadb_dump_deliver(pfkey_q, original_answer, |
| walker, samsg); |
| if (error == ENOBUFS) { |
| mblk_t *new_original_answer; |
| |
| /* Ran out of dupb's. Try a copyb. */ |
| new_original_answer = copyb(original_answer); |
| if (new_original_answer == NULL) { |
| error = ENOMEM; |
| } else { |
| freeb(original_answer); |
| original_answer = new_original_answer; |
| error = sadb_dump_deliver(pfkey_q, |
| original_answer, walker, samsg); |
| } |
| } |
| if (error != 0) |
| break; /* out of for loop. */ |
| } |
| mutex_exit(&fanout[i].isaf_lock); |
| if (error != 0) |
| break; /* out of for loop. */ |
| } |
| |
| freeb(original_answer); |
| return (error); |
| } |
| |
| /* |
| * Dump an entire SADB; outbound first, then inbound. |
| */ |
| |
| int |
| sadb_dump(queue_t *pfkey_q, mblk_t *mp, keysock_in_t *ksi, sadb_t *sp) |
| { |
| int error; |
| time_t active_time = 0; |
| sadb_x_edump_t *edump = |
| (sadb_x_edump_t *)ksi->ks_in_extv[SADB_X_EXT_EDUMP]; |
| |
| if (edump != NULL) { |
| active_time = edump->sadb_x_edump_timeout; |
| } |
| |
| /* Dump outbound */ |
| error = sadb_dump_fanout(pfkey_q, mp, ksi->ks_in_serial, sp->sdb_of, |
| sp->sdb_hashsize, B_TRUE, active_time); |
| if (error) |
| return (error); |
| |
| /* Dump inbound */ |
| return sadb_dump_fanout(pfkey_q, mp, ksi->ks_in_serial, sp->sdb_if, |
| sp->sdb_hashsize, B_FALSE, active_time); |
| } |
| |
| /* |
| * Generic sadb table walker. |
| * |
| * Call "walkfn" for each SA in each bucket in "table"; pass the |
| * bucket, the entry and "cookie" to the callback function. |
| * Take care to ensure that walkfn can delete the SA without screwing |
| * up our traverse. |
| * |
| * The bucket is locked for the duration of the callback, both so that the |
| * callback can just call sadb_unlinkassoc() when it wants to delete something, |
| * and so that no new entries are added while we're walking the list. |
| */ |
| static void |
| sadb_walker(isaf_t *table, uint_t numentries, |
| void (*walkfn)(isaf_t *head, ipsa_t *entry, void *cookie), |
| void *cookie) |
| { |
| int i; |
| for (i = 0; i < numentries; i++) { |
| ipsa_t *entry, *next; |
| |
| mutex_enter(&table[i].isaf_lock); |
| |
| for (entry = table[i].isaf_ipsa; entry != NULL; |
| entry = next) { |
| next = entry->ipsa_next; |
| (*walkfn)(&table[i], entry, cookie); |
| } |
| mutex_exit(&table[i].isaf_lock); |
| } |
| } |
| |
| /* |
| * Call me to free up a security association fanout. Use the forever |
| * variable to indicate freeing up the SAs (forever == B_FALSE, e.g. |
| * an SADB_FLUSH message), or destroying everything (forever == B_TRUE, |
| * when a module is unloaded). |
| */ |
| static void |
| sadb_destroyer(isaf_t **tablep, uint_t numentries, boolean_t forever, |
| boolean_t inbound) |
| { |
| int i; |
| isaf_t *table = *tablep; |
| uint8_t protocol; |
| ipsa_t *sa; |
| netstackid_t sid; |
| |
| if (table == NULL) |
| return; |
| |
| for (i = 0; i < numentries; i++) { |
| mutex_enter(&table[i].isaf_lock); |
| while ((sa = table[i].isaf_ipsa) != NULL) { |
| if (inbound && cl_inet_deletespi && |
| (sa->ipsa_state != IPSA_STATE_ACTIVE_ELSEWHERE) && |
| (sa->ipsa_state != IPSA_STATE_IDLE)) { |
| protocol = (sa->ipsa_type == SADB_SATYPE_AH) ? |
| IPPROTO_AH : IPPROTO_ESP; |
| sid = sa->ipsa_netstack->netstack_stackid; |
| cl_inet_deletespi(sid, protocol, sa->ipsa_spi, |
| NULL); |
| } |
| sadb_unlinkassoc(sa); |
| } |
| table[i].isaf_gen++; |
| mutex_exit(&table[i].isaf_lock); |
| if (forever) |
| mutex_destroy(&(table[i].isaf_lock)); |
| } |
| |
| if (forever) { |
| *tablep = NULL; |
| kmem_free(table, numentries * sizeof (*table)); |
| } |
| } |
| |
| /* |
| * Entry points to sadb_destroyer(). |
| */ |
| static void |
| sadb_flush(sadb_t *sp, netstack_t *ns) |
| { |
| /* |
| * Flush out each bucket, one at a time. Were it not for keysock's |
| * enforcement, there would be a subtlety where I could add on the |
| * heels of a flush. With keysock's enforcement, however, this |
| * makes ESP's job easy. |
| */ |
| sadb_destroyer(&sp->sdb_of, sp->sdb_hashsize, B_FALSE, B_FALSE); |
| sadb_destroyer(&sp->sdb_if, sp->sdb_hashsize, B_FALSE, B_TRUE); |
| |
| /* For each acquire, destroy it; leave the bucket mutex alone. */ |
| sadb_destroy_acqlist(&sp->sdb_acq, sp->sdb_hashsize, B_FALSE, ns); |
| } |
| |
| static void |
| sadb_destroy(sadb_t *sp, netstack_t *ns) |
| { |
| sadb_destroyer(&sp->sdb_of, sp->sdb_hashsize, B_TRUE, B_FALSE); |
| sadb_destroyer(&sp->sdb_if, sp->sdb_hashsize, B_TRUE, B_TRUE); |
| |
| /* For each acquire, destroy it, including the bucket mutex. */ |
| sadb_destroy_acqlist(&sp->sdb_acq, sp->sdb_hashsize, B_TRUE, ns); |
| |
| ASSERT(sp->sdb_of == NULL); |
| ASSERT(sp->sdb_if == NULL); |
| ASSERT(sp->sdb_acq == NULL); |
| } |
| |
| void |
| sadbp_flush(sadbp_t *spp, netstack_t *ns) |
| { |
| sadb_flush(&spp->s_v4, ns); |
| sadb_flush(&spp->s_v6, ns); |
| } |
| |
| void |
| sadbp_destroy(sadbp_t *spp, netstack_t *ns) |
| { |
| sadb_destroy(&spp->s_v4, ns); |
| sadb_destroy(&spp->s_v6, ns); |
| |
| if (spp->s_satype == SADB_SATYPE_AH) { |
| ipsec_stack_t *ipss = ns->netstack_ipsec; |
| |
| ip_drop_unregister(&ipss->ipsec_sadb_dropper); |
| } |
| } |
| |
| |
| /* |
| * Check hard vs. soft lifetimes. If there's a reality mismatch (e.g. |
| * soft lifetimes > hard lifetimes) return an appropriate diagnostic for |
| * EINVAL. |
| */ |
| int |
| sadb_hardsoftchk(sadb_lifetime_t *hard, sadb_lifetime_t *soft, |
| sadb_lifetime_t *idle) |
| { |
| if (hard == NULL || soft == NULL) |
| return (0); |
| |
| if (hard->sadb_lifetime_allocations != 0 && |
| soft->sadb_lifetime_allocations != 0 && |
| hard->sadb_lifetime_allocations < soft->sadb_lifetime_allocations) |
| return (SADB_X_DIAGNOSTIC_ALLOC_HSERR); |
| |
| if (hard->sadb_lifetime_bytes != 0 && |
| soft->sadb_lifetime_bytes != 0 && |
| hard->sadb_lifetime_bytes < soft->sadb_lifetime_bytes) |
| return (SADB_X_DIAGNOSTIC_BYTES_HSERR); |
| |
| if (hard->sadb_lifetime_addtime != 0 && |
| soft->sadb_lifetime_addtime != 0 && |
| hard->sadb_lifetime_addtime < soft->sadb_lifetime_addtime) |
| return (SADB_X_DIAGNOSTIC_ADDTIME_HSERR); |
| |
| if (hard->sadb_lifetime_usetime != 0 && |
| soft->sadb_lifetime_usetime != 0 && |
| hard->sadb_lifetime_usetime < soft->sadb_lifetime_usetime) |
| return (SADB_X_DIAGNOSTIC_USETIME_HSERR); |
| |
| if (idle != NULL) { |
| if (hard->sadb_lifetime_addtime != 0 && |
| idle->sadb_lifetime_addtime != 0 && |
| hard->sadb_lifetime_addtime < idle->sadb_lifetime_addtime) |
| return (SADB_X_DIAGNOSTIC_ADDTIME_HSERR); |
| |
| if (soft->sadb_lifetime_addtime != 0 && |
| idle->sadb_lifetime_addtime != 0 && |
| soft->sadb_lifetime_addtime < idle->sadb_lifetime_addtime) |
| return (SADB_X_DIAGNOSTIC_ADDTIME_HSERR); |
| |
| if (hard->sadb_lifetime_usetime != 0 && |
| idle->sadb_lifetime_usetime != 0 && |
| hard->sadb_lifetime_usetime < idle->sadb_lifetime_usetime) |
| return (SADB_X_DIAGNOSTIC_USETIME_HSERR); |
| |
| if (soft->sadb_lifetime_usetime != 0 && |
| idle->sadb_lifetime_usetime != 0 && |
| soft->sadb_lifetime_usetime < idle->sadb_lifetime_usetime) |
| return (SADB_X_DIAGNOSTIC_USETIME_HSERR); |
| } |
| |
| return (0); |
| } |
| |
| /* |
| * Sanity check sensitivity labels. |
| * |
| * For now, just reject labels on unlabeled systems. |
| */ |
| int |
| sadb_labelchk(keysock_in_t *ksi) |
| { |
| if (!is_system_labeled()) { |
| if (ksi->ks_in_extv[SADB_EXT_SENSITIVITY] != NULL) |
| return (SADB_X_DIAGNOSTIC_BAD_LABEL); |
| |
| if (ksi->ks_in_extv[SADB_X_EXT_OUTER_SENS] != NULL) |
| return (SADB_X_DIAGNOSTIC_BAD_LABEL); |
| } |
| |
| return (0); |
| } |
| |
| /* |
| * Clone a security association for the purposes of inserting a single SA |
| * into inbound and outbound tables respectively. This function should only |
| * be called from sadb_common_add(). |
| */ |
| static ipsa_t * |
| sadb_cloneassoc(ipsa_t *ipsa) |
| { |
| ipsa_t *newbie; |
| boolean_t error = B_FALSE; |
| |
| ASSERT(MUTEX_NOT_HELD(&(ipsa->ipsa_lock))); |
| |
| newbie = kmem_alloc(sizeof (ipsa_t), KM_NOSLEEP); |
| if (newbie == NULL) |
| return (NULL); |
| |
| /* Copy over what we can. */ |
| *newbie = *ipsa; |
| |
| /* bzero and initialize locks, in case *_init() allocates... */ |
| mutex_init(&newbie->ipsa_lock, NULL, MUTEX_DEFAULT, NULL); |
| |
| if (newbie->ipsa_tsl != NULL) |
| label_hold(newbie->ipsa_tsl); |
| |
| if (newbie->ipsa_otsl != NULL) |
| label_hold(newbie->ipsa_otsl); |
| |
| /* |
| * While somewhat dain-bramaged, the most graceful way to |
| * recover from errors is to keep plowing through the |
| * allocations, and getting what I can. It's easier to call |
| * sadb_freeassoc() on the stillborn clone when all the |
| * pointers aren't pointing to the parent's data. |
| */ |
| |
| if (ipsa->ipsa_authkey != NULL) { |
| newbie->ipsa_authkey = kmem_alloc(newbie->ipsa_authkeylen, |
| KM_NOSLEEP); |
| if (newbie->ipsa_authkey == NULL) { |
| error = B_TRUE; |
| } else { |
| bcopy(ipsa->ipsa_authkey, newbie->ipsa_authkey, |
| newbie->ipsa_authkeylen); |
| |
| newbie->ipsa_kcfauthkey.ck_data = |
| newbie->ipsa_authkey; |
| } |
| |
| if (newbie->ipsa_amech.cm_param != NULL) { |
| newbie->ipsa_amech.cm_param = |
| (char *)&newbie->ipsa_mac_len; |
| } |
| } |
| |
| if (ipsa->ipsa_encrkey != NULL) { |
| newbie->ipsa_encrkey = kmem_alloc(newbie->ipsa_encrkeylen, |
| KM_NOSLEEP); |
| if (newbie->ipsa_encrkey == NULL) { |
| error = B_TRUE; |
| } else { |
| bcopy(ipsa->ipsa_encrkey, newbie->ipsa_encrkey, |
| newbie->ipsa_encrkeylen); |
| |
| newbie->ipsa_kcfencrkey.ck_data = |
| newbie->ipsa_encrkey; |
| } |
| } |
| |
| newbie->ipsa_authtmpl = NULL; |
| newbie->ipsa_encrtmpl = NULL; |
| newbie->ipsa_haspeer = B_TRUE; |
| |
| if (ipsa->ipsa_src_cid != NULL) { |
| newbie->ipsa_src_cid = ipsa->ipsa_src_cid; |
| IPSID_REFHOLD(ipsa->ipsa_src_cid); |
| } |
| |
| if (ipsa->ipsa_dst_cid != NULL) { |
| newbie->ipsa_dst_cid = ipsa->ipsa_dst_cid; |
| IPSID_REFHOLD(ipsa->ipsa_dst_cid); |
| } |
| |
| if (error) { |
| sadb_freeassoc(newbie); |
| return (NULL); |
| } |
| |
| return (newbie); |
| } |
| |
| /* |
| * Initialize a SADB address extension at the address specified by addrext. |
| * Return a pointer to the end of the new address extension. |
| */ |
| static uint8_t * |
| sadb_make_addr_ext(uint8_t *start, uint8_t *end, uint16_t exttype, |
| sa_family_t af, uint32_t *addr, uint16_t port, uint8_t proto, int prefix) |
| { |
| struct sockaddr_in *sin; |
| struct sockaddr_in6 *sin6; |
| uint8_t *cur = start; |
| int addrext_len; |
| int sin_len; |
| sadb_address_t *addrext = (sadb_address_t *)cur; |
| |
| if (cur == NULL) |
| return (NULL); |
| |
| cur += sizeof (*addrext); |
| if (cur > end) |
| return (NULL); |
| |
| addrext->sadb_address_proto = proto; |
| addrext->sadb_address_prefixlen = prefix; |
| addrext->sadb_address_reserved = 0; |
| addrext->sadb_address_exttype = exttype; |
| |
| switch (af) { |
| case AF_INET: |
| sin = (struct sockaddr_in *)cur; |
| sin_len = sizeof (*sin); |
| cur += sin_len; |
| if (cur > end) |
| return (NULL); |
| |
| sin->sin_family = af; |
| bzero(sin->sin_zero, sizeof (sin->sin_zero)); |
| sin->sin_port = port; |
| IPSA_COPY_ADDR(&sin->sin_addr, addr, af); |
| break; |
| case AF_INET6: |
| sin6 = (struct sockaddr_in6 *)cur; |
| sin_len = sizeof (*sin6); |
| cur += sin_len; |
| if (cur > end) |
| return (NULL); |
| |
| bzero(sin6, sizeof (*sin6)); |
| sin6->sin6_family = af; |
| sin6->sin6_port = port; |
| IPSA_COPY_ADDR(&sin6->sin6_addr, addr, af); |
| break; |
| } |
| |
| addrext_len = roundup(cur - start, sizeof (uint64_t)); |
| addrext->sadb_address_len = SADB_8TO64(addrext_len); |
| |
| cur = start + addrext_len; |
| if (cur > end) |
| cur = NULL; |
| |
| return (cur); |
| } |
| |
| /* |
| * Construct a key management cookie extension. |
| */ |
| |
| static uint8_t * |
| sadb_make_kmc_ext(uint8_t *cur, uint8_t *end, uint32_t kmp, uint64_t kmc) |
| { |
| sadb_x_kmc_t *kmcext = (sadb_x_kmc_t *)cur; |
| |
| if (cur == NULL) |
| return (NULL); |
| |
| cur += sizeof (*kmcext); |
| |
| if (cur > end) |
| return (NULL); |
| |
| kmcext->sadb_x_kmc_len = SADB_8TO64(sizeof (*kmcext)); |
| kmcext->sadb_x_kmc_exttype = SADB_X_EXT_KM_COOKIE; |
| kmcext->sadb_x_kmc_proto = kmp; |
| kmcext->sadb_x_kmc_cookie64 = kmc; |
| |
| return (cur); |
| } |
| |
| /* |
| * Given an original message header with sufficient space following it, and an |
| * SA, construct a full PF_KEY message with all of the relevant extensions. |
| * This is mostly used for SADB_GET, and SADB_DUMP. |
| */ |
| static mblk_t * |
| sadb_sa2msg(ipsa_t *ipsa, sadb_msg_t *samsg) |
| { |
| int alloclen, addrsize, paddrsize, authsize, encrsize; |
| int srcidsize, dstidsize, senslen, osenslen; |
| sa_family_t fam, pfam; /* Address family for SADB_EXT_ADDRESS */ |
| /* src/dst and proxy sockaddrs. */ |
| |
| authsize = 0; |
| encrsize = 0; |
| pfam = 0; |
| srcidsize = 0; |
| dstidsize = 0; |
| paddrsize = 0; |
| senslen = 0; |
| osenslen = 0; |
| /* |
| * The following are pointers into the PF_KEY message this PF_KEY |
| * message creates. |
| */ |
| sadb_msg_t *newsamsg; |
| sadb_sa_t *assoc; |
| sadb_lifetime_t *lt; |
| sadb_key_t *key; |
| sadb_ident_t *ident; |
| sadb_sens_t *sens; |
| sadb_ext_t *walker; /* For when we need a generic ext. pointer. */ |
| sadb_x_replay_ctr_t *repl_ctr; |
| sadb_x_pair_t *pair_ext; |
| |
| mblk_t *mp; |
| uint8_t *cur, *end; |
| /* These indicate the presence of the above extension fields. */ |
| boolean_t soft = B_FALSE, hard = B_FALSE; |
| boolean_t isrc = B_FALSE, idst = B_FALSE; |
| boolean_t auth = B_FALSE, encr = B_FALSE; |
| boolean_t sensinteg = B_FALSE, osensinteg = B_FALSE; |
| boolean_t srcid = B_FALSE, dstid = B_FALSE; |
| boolean_t idle; |
| boolean_t paired; |
| uint32_t otherspi; |
| |
| /* First off, figure out the allocation length for this message. */ |
| /* |
| * Constant stuff. This includes base, SA, address (src, dst), |
| * and lifetime (current). |
| */ |
| alloclen = sizeof (sadb_msg_t) + sizeof (sadb_sa_t) + |
| sizeof (sadb_lifetime_t); |
| otherspi = 0; |
| |
| fam = ipsa->ipsa_addrfam; |
| switch (fam) { |
| case AF_INET: |
| addrsize = roundup(sizeof (struct sockaddr_in) + |
| sizeof (sadb_address_t), sizeof (uint64_t)); |
| break; |
| case AF_INET6: |
| addrsize = roundup(sizeof (struct sockaddr_in6) + |
| sizeof (sadb_address_t), sizeof (uint64_t)); |
| break; |
| default: |
| return (NULL); |
| } |
| /* |
| * Allocate TWO address extensions, for source and destination. |
| * (Thus, the * 2.) |
| */ |
| alloclen += addrsize * 2; |
| if (ipsa->ipsa_flags & IPSA_F_NATT_REM) |
| alloclen += addrsize; |
| if (ipsa->ipsa_flags & IPSA_F_NATT_LOC) |
| alloclen += addrsize; |
| |
| if (ipsa->ipsa_flags & IPSA_F_PAIRED) { |
| paired = B_TRUE; |
| alloclen += sizeof (sadb_x_pair_t); |
| otherspi = ipsa->ipsa_otherspi; |
| } else { |
| paired = B_FALSE; |
| } |
| |
| /* How 'bout other lifetimes? */ |
| if (ipsa->ipsa_softaddlt != 0 || ipsa->ipsa_softuselt != 0 || |
| ipsa->ipsa_softbyteslt != 0 || ipsa->ipsa_softalloc != 0) { |
| alloclen += sizeof (sadb_lifetime_t); |
| soft = B_TRUE; |
| } |
| |
| if (ipsa->ipsa_hardaddlt != 0 || ipsa->ipsa_harduselt != 0 || |
| ipsa->ipsa_hardbyteslt != 0 || ipsa->ipsa_hardalloc != 0) { |
| alloclen += sizeof (sadb_lifetime_t); |
| hard = B_TRUE; |
| } |
| |
| if (ipsa->ipsa_idleaddlt != 0 || ipsa->ipsa_idleuselt != 0) { |
| alloclen += sizeof (sadb_lifetime_t); |
| idle = B_TRUE; |
| } else { |
| idle = B_FALSE; |
| } |
| |
| /* Inner addresses. */ |
| if (ipsa->ipsa_innerfam != 0) { |
| pfam = ipsa->ipsa_innerfam; |
| switch (pfam) { |
| case AF_INET6: |
| paddrsize = roundup(sizeof (struct sockaddr_in6) + |
| sizeof (sadb_address_t), sizeof (uint64_t)); |
| break; |
| case AF_INET: |
| paddrsize = roundup(sizeof (struct sockaddr_in) + |
| sizeof (sadb_address_t), sizeof (uint64_t)); |
| break; |
| default: |
| cmn_err(CE_PANIC, |
| "IPsec SADB: Proxy length failure.\n"); |
| break; |
| } |
| isrc = B_TRUE; |
| idst = B_TRUE; |
| alloclen += 2 * paddrsize; |
| } |
| |
| /* For the following fields, assume that length != 0 ==> stuff */ |
| if (ipsa->ipsa_authkeylen != 0) { |
| authsize = roundup(sizeof (sadb_key_t) + ipsa->ipsa_authkeylen, |
| sizeof (uint64_t)); |
| alloclen += authsize; |
| auth = B_TRUE; |
| } |
| |
| if (ipsa->ipsa_encrkeylen != 0) { |
| encrsize = roundup(sizeof (sadb_key_t) + ipsa->ipsa_encrkeylen + |
| ipsa->ipsa_nonce_len, sizeof (uint64_t)); |
| alloclen += encrsize; |
| encr = B_TRUE; |
| } else { |
| encr = B_FALSE; |
| } |
| |
| if (ipsa->ipsa_tsl != NULL) { |
| senslen = sadb_sens_len_from_label(ipsa->ipsa_tsl); |
| alloclen += senslen; |
| sensinteg = B_TRUE; |
| } |
| |
| if (ipsa->ipsa_otsl != NULL) { |
| osenslen = sadb_sens_len_from_label(ipsa->ipsa_otsl); |
| alloclen += osenslen; |
| osensinteg = B_TRUE; |
| } |
| |
| /* |
| * Must use strlen() here for lengths. Identities use NULL |
| * pointers to indicate their nonexistence. |
| */ |
| if (ipsa->ipsa_src_cid != NULL) { |
| srcidsize = roundup(sizeof (sadb_ident_t) + |
| strlen(ipsa->ipsa_src_cid->ipsid_cid) + 1, |
| sizeof (uint64_t)); |
| alloclen += srcidsize; |
| srcid = B_TRUE; |
| } |
| |
| if (ipsa->ipsa_dst_cid != NULL) { |
| dstidsize = roundup(sizeof (sadb_ident_t) + |
| strlen(ipsa->ipsa_dst_cid->ipsid_cid) + 1, |
| sizeof (uint64_t)); |
| alloclen += dstidsize; |
| dstid = B_TRUE; |
| } |
| |
| if ((ipsa->ipsa_kmp != 0) || (ipsa->ipsa_kmc != 0)) |
| alloclen += sizeof (sadb_x_kmc_t); |
| |
| if (ipsa->ipsa_replay != 0) { |
| alloclen += sizeof (sadb_x_replay_ctr_t); |
| } |
| |
| /* Make sure the allocation length is a multiple of 8 bytes. */ |
| ASSERT((alloclen & 0x7) == 0); |
| |
| /* XXX Possibly make it esballoc, with a bzero-ing free_ftn. */ |
| mp = allocb(alloclen, BPRI_HI); |
| if (mp == NULL) |
| return (NULL); |
| bzero(mp->b_rptr, alloclen); |
| |
| mp->b_wptr += alloclen; |
| end = mp->b_wptr; |
| newsamsg = (sadb_msg_t *)mp->b_rptr; |
| *newsamsg = *samsg; |
| newsamsg->sadb_msg_len = (uint16_t)SADB_8TO64(alloclen); |
| |
| mutex_enter(&ipsa->ipsa_lock); /* Since I'm grabbing SA fields... */ |
| |
| newsamsg->sadb_msg_satype = ipsa->ipsa_type; |
| |
| assoc = (sadb_sa_t *)(newsamsg + 1); |
| assoc->sadb_sa_len = SADB_8TO64(sizeof (*assoc)); |
| assoc->sadb_sa_exttype = SADB_EXT_SA; |
| assoc->sadb_sa_spi = ipsa->ipsa_spi; |
| assoc->sadb_sa_replay = ipsa->ipsa_replay_wsize; |
| assoc->sadb_sa_state = ipsa->ipsa_state; |
| assoc->sadb_sa_auth = ipsa->ipsa_auth_alg; |
| assoc->sadb_sa_encrypt = ipsa->ipsa_encr_alg; |
| assoc->sadb_sa_flags = ipsa->ipsa_flags; |
| |
| lt = (sadb_lifetime_t *)(assoc + 1); |
| lt->sadb_lifetime_len = SADB_8TO64(sizeof (*lt)); |
| lt->sadb_lifetime_exttype = SADB_EXT_LIFETIME_CURRENT; |
| /* We do not support the concept. */ |
| lt->sadb_lifetime_allocations = 0; |
| lt->sadb_lifetime_bytes = ipsa->ipsa_bytes; |
| lt->sadb_lifetime_addtime = ipsa->ipsa_addtime; |
| lt->sadb_lifetime_usetime = ipsa->ipsa_usetime; |
| |
| if (hard) { |
| lt++; |
| lt->sadb_lifetime_len = SADB_8TO64(sizeof (*lt)); |
| lt->sadb_lifetime_exttype = SADB_EXT_LIFETIME_HARD; |
| lt->sadb_lifetime_allocations = ipsa->ipsa_hardalloc; |
| lt->sadb_lifetime_bytes = ipsa->ipsa_hardbyteslt; |
| lt->sadb_lifetime_addtime = ipsa->ipsa_hardaddlt; |
| lt->sadb_lifetime_usetime = ipsa->ipsa_harduselt; |
| } |
| |
| if (soft) { |
| lt++; |
| lt->sadb_lifetime_len = SADB_8TO64(sizeof (*lt)); |
| lt->sadb_lifetime_exttype = SADB_EXT_LIFETIME_SOFT; |
| lt->sadb_lifetime_allocations = ipsa->ipsa_softalloc; |
| lt->sadb_lifetime_bytes = ipsa->ipsa_softbyteslt; |
| lt->sadb_lifetime_addtime = ipsa->ipsa_softaddlt; |
| lt->sadb_lifetime_usetime = ipsa->ipsa_softuselt; |
| } |
| |
| if (idle) { |
| lt++; |
| lt->sadb_lifetime_len = SADB_8TO64(sizeof (*lt)); |
| lt->sadb_lifetime_exttype = SADB_X_EXT_LIFETIME_IDLE; |
| lt->sadb_lifetime_addtime = ipsa->ipsa_idleaddlt; |
| lt->sadb_lifetime_usetime = ipsa->ipsa_idleuselt; |
| } |
| |
| cur = (uint8_t *)(lt + 1); |
| |
| /* NOTE: Don't fill in ports here if we are a tunnel-mode SA. */ |
| cur = sadb_make_addr_ext(cur, end, SADB_EXT_ADDRESS_SRC, fam, |
| ipsa->ipsa_srcaddr, (!isrc && !idst) ? SA_SRCPORT(ipsa) : 0, |
| SA_PROTO(ipsa), 0); |
| if (cur == NULL) { |
| freemsg(mp); |
| mp = NULL; |
| goto bail; |
| } |
| |
| cur = sadb_make_addr_ext(cur, end, SADB_EXT_ADDRESS_DST, fam, |
| ipsa->ipsa_dstaddr, (!isrc && !idst) ? SA_DSTPORT(ipsa) : 0, |
| SA_PROTO(ipsa), 0); |
| if (cur == NULL) { |
| freemsg(mp); |
| mp = NULL; |
| goto bail; |
| } |
| |
| if (ipsa->ipsa_flags & IPSA_F_NATT_LOC) { |
| cur = sadb_make_addr_ext(cur, end, SADB_X_EXT_ADDRESS_NATT_LOC, |
| fam, &ipsa->ipsa_natt_addr_loc, ipsa->ipsa_local_nat_port, |
| IPPROTO_UDP, 0); |
| if (cur == NULL) { |
| freemsg(mp); |
| mp = NULL; |
| goto bail; |
| } |
| } |
| |
| if (ipsa->ipsa_flags & IPSA_F_NATT_REM) { |
| cur = sadb_make_addr_ext(cur, end, SADB_X_EXT_ADDRESS_NATT_REM, |
| fam, &ipsa->ipsa_natt_addr_rem, ipsa->ipsa_remote_nat_port, |
| IPPROTO_UDP, 0); |
| if (cur == NULL) { |
| freemsg(mp); |
| mp = NULL; |
| goto bail; |
| } |
| } |
| |
| /* If we are a tunnel-mode SA, fill in the inner-selectors. */ |
| if (isrc) { |
| cur = sadb_make_addr_ext(cur, end, SADB_X_EXT_ADDRESS_INNER_SRC, |
| pfam, ipsa->ipsa_innersrc, SA_SRCPORT(ipsa), |
| SA_IPROTO(ipsa), ipsa->ipsa_innersrcpfx); |
| if (cur == NULL) { |
| freemsg(mp); |
| mp = NULL; |
| goto bail; |
| } |
| } |
| |
| if (idst) { |
| cur = sadb_make_addr_ext(cur, end, SADB_X_EXT_ADDRESS_INNER_DST, |
| pfam, ipsa->ipsa_innerdst, SA_DSTPORT(ipsa), |
| SA_IPROTO(ipsa), ipsa->ipsa_innerdstpfx); |
| if (cur == NULL) { |
| freemsg(mp); |
| mp = NULL; |
| goto bail; |
| } |
| } |
| |
| if ((ipsa->ipsa_kmp != 0) || (ipsa->ipsa_kmc != 0)) { |
| cur = sadb_make_kmc_ext(cur, end, |
| ipsa->ipsa_kmp, ipsa->ipsa_kmc); |
| if (cur == NULL) { |
| freemsg(mp); |
| mp = NULL; |
| goto bail; |
| } |
| } |
| |
| walker = (sadb_ext_t *)cur; |
| if (auth) { |
| key = (sadb_key_t *)walker; |
| key->sadb_key_len = SADB_8TO64(authsize); |
| key->sadb_key_exttype = SADB_EXT_KEY_AUTH; |
| key->sadb_key_bits = ipsa->ipsa_authkeybits; |
| key->sadb_key_reserved = 0; |
| bcopy(ipsa->ipsa_authkey, key + 1, ipsa->ipsa_authkeylen); |
| walker = (sadb_ext_t *)((uint64_t *)walker + |
| walker->sadb_ext_len); |
| } |
| |
| if (encr) { |
| uint8_t *buf_ptr; |
| key = (sadb_key_t *)walker; |
| key->sadb_key_len = SADB_8TO64(encrsize); |
| key->sadb_key_exttype = SADB_EXT_KEY_ENCRYPT; |
| key->sadb_key_bits = ipsa->ipsa_encrkeybits; |
| key->sadb_key_reserved = ipsa->ipsa_saltbits; |
| buf_ptr = (uint8_t *)(key + 1); |
| bcopy(ipsa->ipsa_encrkey, buf_ptr, ipsa->ipsa_encrkeylen); |
| if (ipsa->ipsa_salt != NULL) { |
| buf_ptr += ipsa->ipsa_encrkeylen; |
| bcopy(ipsa->ipsa_salt, buf_ptr, ipsa->ipsa_saltlen); |
| } |
| walker = (sadb_ext_t *)((uint64_t *)walker + |
| walker->sadb_ext_len); |
| } |
| |
| if (srcid) { |
| ident = (sadb_ident_t *)walker; |
| ident->sadb_ident_len = SADB_8TO64(srcidsize); |
| ident->sadb_ident_exttype = SADB_EXT_IDENTITY_SRC; |
| ident->sadb_ident_type = ipsa->ipsa_src_cid->ipsid_type; |
| ident->sadb_ident_id = 0; |
| ident->sadb_ident_reserved = 0; |
| (void) strcpy((char *)(ident + 1), |
| ipsa->ipsa_src_cid->ipsid_cid); |
| walker = (sadb_ext_t *)((uint64_t *)walker + |
| walker->sadb_ext_len); |
| } |
| |
| if (dstid) { |
| ident = (sadb_ident_t *)walker; |
| ident->sadb_ident_len = SADB_8TO64(dstidsize); |
| ident->sadb_ident_exttype = SADB_EXT_IDENTITY_DST; |
| ident->sadb_ident_type = ipsa->ipsa_dst_cid->ipsid_type; |
| ident->sadb_ident_id = 0; |
| ident->sadb_ident_reserved = 0; |
| (void) strcpy((char *)(ident + 1), |
| ipsa->ipsa_dst_cid->ipsid_cid); |
| walker = (sadb_ext_t *)((uint64_t *)walker + |
| walker->sadb_ext_len); |
| } |
| |
| if (sensinteg) { |
| sens = (sadb_sens_t *)walker; |
| sadb_sens_from_label(sens, SADB_EXT_SENSITIVITY, |
| ipsa->ipsa_tsl, senslen); |
| |
| walker = (sadb_ext_t *)((uint64_t *)walker + |
| walker->sadb_ext_len); |
| } |
| |
| if (osensinteg) { |
| sens = (sadb_sens_t *)walker; |
| |
| sadb_sens_from_label(sens, SADB_X_EXT_OUTER_SENS, |
| ipsa->ipsa_otsl, osenslen); |
| if (ipsa->ipsa_mac_exempt) |
| sens->sadb_x_sens_flags = SADB_X_SENS_IMPLICIT; |
| |
| walker = (sadb_ext_t *)((uint64_t *)walker + |
| walker->sadb_ext_len); |
| } |
| |
| if (paired) { |
| pair_ext = (sadb_x_pair_t *)walker; |
| |
| pair_ext->sadb_x_pair_len = SADB_8TO64(sizeof (sadb_x_pair_t)); |
| pair_ext->sadb_x_pair_exttype = SADB_X_EXT_PAIR; |
| pair_ext->sadb_x_pair_spi = otherspi; |
| |
| walker = (sadb_ext_t *)((uint64_t *)walker + |
| walker->sadb_ext_len); |
| } |
| |
| if (ipsa->ipsa_replay != 0) { |
| repl_ctr = (sadb_x_replay_ctr_t *)walker; |
| repl_ctr->sadb_x_rc_len = SADB_8TO64(sizeof (*repl_ctr)); |
| repl_ctr->sadb_x_rc_exttype = SADB_X_EXT_REPLAY_VALUE; |
| repl_ctr->sadb_x_rc_replay32 = ipsa->ipsa_replay; |
| repl_ctr->sadb_x_rc_replay64 = 0; |
| walker = (sadb_ext_t *)(repl_ctr + 1); |
| } |
| |
| bail: |
| /* Pardon any delays... */ |
| mutex_exit(&ipsa->ipsa_lock); |
| |
| return (mp); |
| } |
| |
| /* |
| * Strip out key headers or unmarked headers (SADB_EXT_KEY_*, SADB_EXT_UNKNOWN) |
| * and adjust base message accordingly. |
| * |
| * Assume message is pulled up in one piece of contiguous memory. |
| * |
| * Say if we start off with: |
| * |
| * +------+----+-------------+-----------+---------------+---------------+ |
| * | base | SA | source addr | dest addr | rsrvd. or key | soft lifetime | |
| * +------+----+-------------+-----------+---------------+---------------+ |
| * |
| * we will end up with |
| * |
| * +------+----+-------------+-----------+---------------+ |
| * | base | SA | source addr | dest addr | soft lifetime | |
| * +------+----+-------------+-----------+---------------+ |
| */ |
| static void |
| sadb_strip(sadb_msg_t *samsg) |
| { |
| sadb_ext_t *ext; |
| uint8_t *target = NULL; |
| uint8_t *msgend; |
| int sofar = SADB_8TO64(sizeof (*samsg)); |
| int copylen; |
| |
| ext = (sadb_ext_t *)(samsg + 1); |
| msgend = (uint8_t *)samsg; |
| msgend += SADB_64TO8(samsg->sadb_msg_len); |
| while ((uint8_t *)ext < msgend) { |
| if (ext->sadb_ext_type == SADB_EXT_RESERVED || |
| ext->sadb_ext_type == SADB_EXT_KEY_AUTH || |
| ext->sadb_ext_type == SADB_X_EXT_EDUMP || |
| ext->sadb_ext_type == SADB_EXT_KEY_ENCRYPT) { |
| /* |
| * Aha! I found a header to be erased. |
| */ |
| |
| if (target != NULL) { |
| /* |
| * If I had a previous header to be erased, |
| * copy over it. I can get away with just |
| * copying backwards because the target will |
| * always be 8 bytes behind the source. |
| */ |
| copylen = ((uint8_t *)ext) - (target + |
| SADB_64TO8( |
| ((sadb_ext_t *)target)->sadb_ext_len)); |
| ovbcopy(((uint8_t *)ext - copylen), target, |
| copylen); |
| target += copylen; |
| ((sadb_ext_t *)target)->sadb_ext_len = |
| SADB_8TO64(((uint8_t *)ext) - target + |
| SADB_64TO8(ext->sadb_ext_len)); |
| } else { |
| target = (uint8_t *)ext; |
| } |
| } else { |
| sofar += ext->sadb_ext_len; |
| } |
| |
| ext = (sadb_ext_t *)(((uint64_t *)ext) + ext->sadb_ext_len); |
| } |
| |
| ASSERT((uint8_t *)ext == msgend); |
| |
| if (target != NULL) { |
| copylen = ((uint8_t *)ext) - (target + |
| SADB_64TO8(((sadb_ext_t *)target)->sadb_ext_len)); |
| if (copylen != 0) |
| ovbcopy(((uint8_t *)ext - copylen), target, copylen); |
| } |
| |
| /* Adjust samsg. */ |
| samsg->sadb_msg_len = (uint16_t)sofar; |
| |
| /* Assume all of the rest is cleared by caller in sadb_pfkey_echo(). */ |
| } |
| |
| /* |
| * AH needs to send an error to PF_KEY. Assume mp points to an M_CTL |
| * followed by an M_DATA with a PF_KEY message in it. The serial of |
| * the sending keysock instance is included. |
| */ |
| void |
| sadb_pfkey_error(queue_t *pfkey_q, mblk_t *mp, int error, int diagnostic, |
| uint_t serial) |
| { |
| mblk_t *msg = mp->b_cont; |
| sadb_msg_t *samsg; |
| keysock_out_t *kso; |
| |
| /* |
| * Enough functions call this to merit a NULL queue check. |
| */ |
| if (pfkey_q == NULL) { |
| freemsg(mp); |
| return; |
| } |
| |
| ASSERT(msg != NULL); |
| ASSERT((mp->b_wptr - mp->b_rptr) == sizeof (ipsec_info_t)); |
| ASSERT((msg->b_wptr - msg->b_rptr) >= sizeof (sadb_msg_t)); |
| samsg = (sadb_msg_t *)msg->b_rptr; |
| kso = (keysock_out_t *)mp->b_rptr; |
| |
| kso->ks_out_type = KEYSOCK_OUT; |
| kso->ks_out_len = sizeof (*kso); |
| kso->ks_out_serial = serial; |
| |
| /* |
| * Only send the base message up in the event of an error. |
| * Don't worry about bzero()-ing, because it was probably bogus |
| * anyway. |
| */ |
| msg->b_wptr = msg->b_rptr + sizeof (*samsg); |
| samsg = (sadb_msg_t *)msg->b_rptr; |
| samsg->sadb_msg_len = SADB_8TO64(sizeof (*samsg)); |
| samsg->sadb_msg_errno = (uint8_t)error; |
| if (diagnostic != SADB_X_DIAGNOSTIC_PRESET) |
| samsg->sadb_x_msg_diagnostic = (uint16_t)diagnostic; |
| |
| putnext(pfkey_q, mp); |
| } |
| |
| /* |
| * Send a successful return packet back to keysock via the queue in pfkey_q. |
| * |
| * Often, an SA is associated with the reply message, it's passed in if needed, |
| * and NULL if not. BTW, that ipsa will have its refcnt appropriately held, |
| * and the caller will release said refcnt. |
| */ |
| void |
| sadb_pfkey_echo(queue_t *pfkey_q, mblk_t *mp, sadb_msg_t *samsg, |
| keysock_in_t *ksi, ipsa_t *ipsa) |
| { |
| keysock_out_t *kso; |
| mblk_t *mp1; |
| sadb_msg_t *newsamsg; |
| uint8_t *oldend; |
| |
| ASSERT((mp->b_cont != NULL) && |
| ((void *)samsg == (void *)mp->b_cont->b_rptr) && |
| ((void *)mp->b_rptr == (void *)ksi)); |
| |
| switch (samsg->sadb_msg_type) { |
| case SADB_ADD: |
| case SADB_UPDATE: |
| case SADB_X_UPDATEPAIR: |
| case SADB_X_DELPAIR_STATE: |
| case SADB_FLUSH: |
| case SADB_DUMP: |
| /* |
| * I have all of the message already. I just need to strip |
| * out the keying material and echo the message back. |
| * |
| * NOTE: for SADB_DUMP, the function sadb_dump() did the |
| * work. When DUMP reaches here, it should only be a base |
| * message. |
| */ |
| justecho: |
| if (ksi->ks_in_extv[SADB_EXT_KEY_AUTH] != NULL || |
| ksi->ks_in_extv[SADB_EXT_KEY_ENCRYPT] != NULL || |
| ksi->ks_in_extv[SADB_X_EXT_EDUMP] != NULL) { |
| sadb_strip(samsg); |
| /* Assume PF_KEY message is contiguous. */ |
| ASSERT(mp->b_cont->b_cont == NULL); |
| oldend = mp->b_cont->b_wptr; |
| mp->b_cont->b_wptr = mp->b_cont->b_rptr + |
| SADB_64TO8(samsg->sadb_msg_len); |
| bzero(mp->b_cont->b_wptr, oldend - mp->b_cont->b_wptr); |
| } |
| break; |
| case SADB_GET: |
| /* |
| * Do a lot of work here, because of the ipsa I just found. |
| * First construct the new PF_KEY message, then abandon |
| * the old one. |
| */ |
| mp1 = sadb_sa2msg(ipsa, samsg); |
| if (mp1 == NULL) { |
| sadb_pfkey_error(pfkey_q, mp, ENOMEM, |
| SADB_X_DIAGNOSTIC_NONE, ksi->ks_in_serial); |
| return; |
| } |
| freemsg(mp->b_cont); |
| mp->b_cont = mp1; |
| break; |
| case SADB_DELETE: |
| case SADB_X_DELPAIR: |
| if (ipsa == NULL) |
| goto justecho; |
| /* |
| * Because listening KMds may require more info, treat |
| * DELETE like a special case of GET. |
| */ |
| mp1 = sadb_sa2msg(ipsa, samsg); |
| if (mp1 == NULL) { |
| sadb_pfkey_error(pfkey_q, mp, ENOMEM, |
| SADB_X_DIAGNOSTIC_NONE, ksi->ks_in_serial); |
| return; |
| } |
| newsamsg = (sadb_msg_t *)mp1->b_rptr; |
| sadb_strip(newsamsg); |
| oldend = mp1->b_wptr; |
| mp1->b_wptr = mp1->b_rptr + SADB_64TO8(newsamsg->sadb_msg_len); |
| bzero(mp1->b_wptr, oldend - mp1->b_wptr); |
| freemsg(mp->b_cont); |
| mp->b_cont = mp1; |
| break; |
| default: |
| freemsg(mp); |
| return; |
| } |
| |
| /* ksi is now null and void. */ |
| kso = (keysock_out_t *)ksi; |
| kso->ks_out_type = KEYSOCK_OUT; |
| kso->ks_out_len = sizeof (*kso); |
| kso->ks_out_serial = ksi->ks_in_serial; |
| /* We're ready to send... */ |
| putnext(pfkey_q, mp); |
| } |
| |
| /* |
| * Set up a global pfkey_q instance for AH, ESP, or some other consumer. |
| */ |
| void |
| sadb_keysock_hello(queue_t **pfkey_qp, queue_t *q, mblk_t *mp, |
| void (*ager)(void *), void *agerarg, timeout_id_t *top, int satype) |
| { |
| keysock_hello_ack_t *kha; |
| queue_t *oldq; |
| |
| ASSERT(OTHERQ(q) != NULL); |
| |
| /* |
| * First, check atomically that I'm the first and only keysock |
| * instance. |
| * |
| * Use OTHERQ(q), because qreply(q, mp) == putnext(OTHERQ(q), mp), |
| * and I want this module to say putnext(*_pfkey_q, mp) for PF_KEY |
| * messages. |
| */ |
| |
| oldq = atomic_cas_ptr((void **)pfkey_qp, NULL, OTHERQ(q)); |
| if (oldq != NULL) { |
| ASSERT(oldq != q); |
| cmn_err(CE_WARN, "Danger! Multiple keysocks on top of %s.\n", |
| (satype == SADB_SATYPE_ESP)? "ESP" : "AH or other"); |
| freemsg(mp); |
| return; |
| } |
| |
| kha = (keysock_hello_ack_t *)mp->b_rptr; |
| kha->ks_hello_len = sizeof (keysock_hello_ack_t); |
| kha->ks_hello_type = KEYSOCK_HELLO_ACK; |
| kha->ks_hello_satype = (uint8_t)satype; |
| |
| /* |
| * If we made it past the atomic_cas_ptr, then we have "exclusive" |
| * access to the timeout handle. Fire it off after the default ager |
| * interval. |
| */ |
| *top = qtimeout(*pfkey_qp, ager, agerarg, |
| drv_usectohz(SADB_AGE_INTERVAL_DEFAULT * 1000)); |
| |
| putnext(*pfkey_qp, mp); |
| } |
| |
| /* |
| * Normalize IPv4-mapped IPv6 addresses (and prefixes) as appropriate. |
| * |
| * Check addresses themselves for wildcard or multicast. |
| * Check ire table for local/non-local/broadcast. |
| */ |
| int |
| sadb_addrcheck(queue_t *pfkey_q, mblk_t *mp, sadb_ext_t *ext, uint_t serial, |
| netstack_t *ns) |
| { |
| sadb_address_t *addr = (sadb_address_t *)ext; |
| struct sockaddr_in *sin; |
| struct sockaddr_in6 *sin6; |
| int diagnostic, type; |
| boolean_t normalized = B_FALSE; |
| |
| ASSERT(ext != NULL); |
| ASSERT((ext->sadb_ext_type == SADB_EXT_ADDRESS_SRC) || |
| (ext->sadb_ext_type == SADB_EXT_ADDRESS_DST) || |
| (ext->sadb_ext_type == SADB_X_EXT_ADDRESS_INNER_SRC) || |
| (ext->sadb_ext_type == SADB_X_EXT_ADDRESS_INNER_DST) || |
| (ext->sadb_ext_type == SADB_X_EXT_ADDRESS_NATT_LOC) || |
| (ext->sadb_ext_type == SADB_X_EXT_ADDRESS_NATT_REM)); |
| |
| diagnostic = 0; |
| |
| /* Assign both sockaddrs, the compiler will do the right thing. */ |
| sin = (struct sockaddr_in *)(addr + 1); |
| sin6 = (struct sockaddr_in6 *)(addr + 1); |
| |
| if (sin6->sin6_family == AF_INET6) { |
| if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) { |
| /* |
| * Convert to an AF_INET sockaddr. This means the |
| * return messages will have the extra space, but have |
| * AF_INET sockaddrs instead of AF_INET6. |
| * |
| * Yes, RFC 2367 isn't clear on what to do here w.r.t. |
| * mapped addresses, but since AF_INET6 ::ffff:<v4> is |
| * equal to AF_INET <v4>, it shouldnt be a huge |
| * problem. |
| */ |
| sin->sin_family = AF_INET; |
| IN6_V4MAPPED_TO_INADDR(&sin6->sin6_addr, |
| &sin->sin_addr); |
| bzero(&sin->sin_zero, sizeof (sin->sin_zero)); |
| normalized = B_TRUE; |
| } |
| } else if (sin->sin_family != AF_INET) { |
| switch (ext->sadb_ext_type) { |
| case SADB_EXT_ADDRESS_SRC: |
| diagnostic = SADB_X_DIAGNOSTIC_BAD_SRC_AF; |
| break; |
| case SADB_EXT_ADDRESS_DST: |
| diagnostic = SADB_X_DIAGNOSTIC_BAD_DST_AF; |
| break; |
| case SADB_X_EXT_ADDRESS_INNER_SRC: |
| diagnostic = SADB_X_DIAGNOSTIC_BAD_PROXY_AF; |
| break; |
| case SADB_X_EXT_ADDRESS_INNER_DST: |
| diagnostic = SADB_X_DIAGNOSTIC_BAD_INNER_DST_AF; |
| break; |
| case SADB_X_EXT_ADDRESS_NATT_LOC: |
| diagnostic = SADB_X_DIAGNOSTIC_BAD_NATT_LOC_AF; |
| break; |
| case SADB_X_EXT_ADDRESS_NATT_REM: |
| diagnostic = SADB_X_DIAGNOSTIC_BAD_NATT_REM_AF; |
| break; |
| /* There is no default, see above ASSERT. */ |
| } |
| bail: |
| if (pfkey_q != NULL) { |
| sadb_pfkey_error(pfkey_q, mp, EINVAL, diagnostic, |
| serial); |
| } else { |
| /* |
| * Scribble in sadb_msg that we got passed in. |
| * Overload "mp" to be an sadb_msg pointer. |
| */ |
| sadb_msg_t *samsg = (sadb_msg_t *)mp; |
| |
| samsg->sadb_msg_errno = EINVAL; |
| samsg->sadb_x_msg_diagnostic = diagnostic; |
| } |
| return (KS_IN_ADDR_UNKNOWN); |
| } |
| |
| if (ext->sadb_ext_type == SADB_X_EXT_ADDRESS_INNER_SRC || |
| ext->sadb_ext_type == SADB_X_EXT_ADDRESS_INNER_DST) { |
| /* |
| * We need only check for prefix issues. |
| */ |
| |
| /* Set diagnostic now, in case we need it later. */ |
| diagnostic = |
| (ext->sadb_ext_type == SADB_X_EXT_ADDRESS_INNER_SRC) ? |
| SADB_X_DIAGNOSTIC_PREFIX_INNER_SRC : |
| SADB_X_DIAGNOSTIC_PREFIX_INNER_DST; |
| |
| if (normalized) |
| addr->sadb_address_prefixlen -= 96; |
| |
| /* |
| * Verify and mask out inner-addresses based on prefix length. |
| */ |
| if (sin->sin_family == AF_INET) { |
| if (addr->sadb_address_prefixlen > 32) |
| goto bail; |
| sin->sin_addr.s_addr &= |
| ip_plen_to_mask(addr->sadb_address_prefixlen); |
| } else { |
| in6_addr_t mask; |
| |
| ASSERT(sin->sin_family == AF_INET6); |
| /* |
| * ip_plen_to_mask_v6() returns NULL if the value in |
| * question is out of range. |
| */ |
| if (ip_plen_to_mask_v6(addr->sadb_address_prefixlen, |
| &mask) == NULL) |
| goto bail; |
| sin6->sin6_addr.s6_addr32[0] &= mask.s6_addr32[0]; |
| sin6->sin6_addr.s6_addr32[1] &= mask.s6_addr32[1]; |
| sin6->sin6_addr.s6_addr32[2] &= mask.s6_addr32[2]; |
| sin6->sin6_addr.s6_addr32[3] &= mask.s6_addr32[3]; |
| } |
| |
| /* We don't care in these cases. */ |
| return (KS_IN_ADDR_DONTCARE); |
| } |
| |
| if (sin->sin_family == AF_INET6) { |
| /* Check the easy ones now. */ |
| if (IN6_IS_ADDR_MULTICAST(&sin6->sin6_addr)) |
| return (KS_IN_ADDR_MBCAST); |
| if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) |
| return (KS_IN_ADDR_UNSPEC); |
| /* |
| * At this point, we're a unicast IPv6 address. |
| * |
| * XXX Zones alert -> me/notme decision needs to be tempered |
| * by what zone we're in when we go to zone-aware IPsec. |
| */ |
| if (ip_type_v6(&sin6->sin6_addr, ns->netstack_ip) == |
| IRE_LOCAL) { |
| /* Hey hey, it's local. */ |
| return (KS_IN_ADDR_ME); |
| } |
| } else { |
| ASSERT(sin->sin_family == AF_INET); |
| if (sin->sin_addr.s_addr == INADDR_ANY) |
| return (KS_IN_ADDR_UNSPEC); |
| if (CLASSD(sin->sin_addr.s_addr)) |
| return (KS_IN_ADDR_MBCAST); |
| /* |
| * At this point we're a unicast or broadcast IPv4 address. |
| * |
| * Check if the address is IRE_BROADCAST or IRE_LOCAL. |
| * |
| * XXX Zones alert -> me/notme decision needs to be tempered |
| * by what zone we're in when we go to zone-aware IPsec. |
| */ |
| type = ip_type_v4(sin->sin_addr.s_addr, ns->netstack_ip); |
| switch (type) { |
| case IRE_LOCAL: |
| return (KS_IN_ADDR_ME); |
| case IRE_BROADCAST: |
| return (KS_IN_ADDR_MBCAST); |
| } |
| } |
| |
| return (KS_IN_ADDR_NOTME); |
| } |
| |
| /* |
| * Address normalizations and reality checks for inbound PF_KEY messages. |
| * |
| * For the case of src == unspecified AF_INET6, and dst == AF_INET, convert |
| * the source to AF_INET. Do the same for the inner sources. |
| */ |
| boolean_t |
| sadb_addrfix(keysock_in_t *ksi, queue_t *pfkey_q, mblk_t *mp, netstack_t *ns) |
| { |
| struct sockaddr_in *src, *isrc; |
| struct sockaddr_in6 *dst, *idst; |
| sadb_address_t *srcext, *dstext; |
| uint16_t sport; |
| sadb_ext_t **extv = ksi->ks_in_extv; |
| int rc; |
| |
| if (extv[SADB_EXT_ADDRESS_SRC] != NULL) { |
| rc = sadb_addrcheck(pfkey_q, mp, extv[SADB_EXT_ADDRESS_SRC], |
| ksi->ks_in_serial, ns); |
| if (rc == KS_IN_ADDR_UNKNOWN) |
| return (B_FALSE); |
| if (rc == KS_IN_ADDR_MBCAST) { |
| sadb_pfkey_error(pfkey_q, mp, EINVAL, |
| SADB_X_DIAGNOSTIC_BAD_SRC, ksi->ks_in_serial); |
| return (B_FALSE); |
| } |
| ksi->ks_in_srctype = rc; |
| } |
| |
| if (extv[SADB_EXT_ADDRESS_DST] != NULL) { |
| rc = sadb_addrcheck(pfkey_q, mp, extv[SADB_EXT_ADDRESS_DST], |
| ksi->ks_in_serial, ns); |
| if (rc == KS_IN_ADDR_UNKNOWN) |
| return (B_FALSE); |
| if (rc == KS_IN_ADDR_UNSPEC) { |
| sadb_pfkey_error(pfkey_q, mp, EINVAL, |
| SADB_X_DIAGNOSTIC_BAD_DST, ksi->ks_in_serial); |
| return (B_FALSE); |
| } |
| ksi->ks_in_dsttype = rc; |
| } |
| |
| /* |
| * NAT-Traversal addrs are simple enough to not require all of |
| * the checks in sadb_addrcheck(). Just normalize or reject if not |
| * AF_INET. |
| */ |
| if (extv[SADB_X_EXT_ADDRESS_NATT_LOC] != NULL) { |
| rc = sadb_addrcheck(pfkey_q, mp, |
| extv[SADB_X_EXT_ADDRESS_NATT_LOC], ksi->ks_in_serial, ns); |
| |
| /* |
| * Local NAT-T addresses never use an IRE_LOCAL, so it should |
| * always be NOTME, or UNSPEC (to handle both tunnel mode |
| * AND local-port flexibility). |
| */ |
| if (rc != KS_IN_ADDR_NOTME && rc != KS_IN_ADDR_UNSPEC) { |
| sadb_pfkey_error(pfkey_q, mp, EINVAL, |
| SADB_X_DIAGNOSTIC_MALFORMED_NATT_LOC, |
| ksi->ks_in_serial); |
| return (B_FALSE); |
| } |
| src = (struct sockaddr_in *) |
| (((sadb_address_t *)extv[SADB_X_EXT_ADDRESS_NATT_LOC]) + 1); |
| if (src->sin_family != AF_INET) { |
| sadb_pfkey_error(pfkey_q, mp, EINVAL, |
| SADB_X_DIAGNOSTIC_BAD_NATT_LOC_AF, |
| ksi->ks_in_serial); |
| return (B_FALSE); |
| } |
| } |
| |
| if (extv[SADB_X_EXT_ADDRESS_NATT_REM] != NULL) { |
| rc = sadb_addrcheck(pfkey_q, mp, |
| extv[SADB_X_EXT_ADDRESS_NATT_REM], ksi->ks_in_serial, ns); |
| |
| /* |
| * Remote NAT-T addresses never use an IRE_LOCAL, so it should |
| * always be NOTME, or UNSPEC if it's a tunnel-mode SA. |
| */ |
| if (rc != KS_IN_ADDR_NOTME && |
| !(extv[SADB_X_EXT_ADDRESS_INNER_SRC] != NULL && |
| rc == KS_IN_ADDR_UNSPEC)) { |
| sadb_pfkey_error(pfkey_q, mp, EINVAL, |
| SADB_X_DIAGNOSTIC_MALFORMED_NATT_REM, |
| ksi->ks_in_serial); |
| return (B_FALSE); |
| } |
| src = (struct sockaddr_in *) |
| (((sadb_address_t *)extv[SADB_X_EXT_ADDRESS_NATT_REM]) + 1); |
| if (src->sin_family != AF_INET) { |
| sadb_pfkey_error(pfkey_q, mp, EINVAL, |
| SADB_X_DIAGNOSTIC_BAD_NATT_REM_AF, |
| ksi->ks_in_serial); |
| return (B_FALSE); |
| } |
| } |
| |
| if (extv[SADB_X_EXT_ADDRESS_INNER_SRC] != NULL) { |
| if (extv[SADB_X_EXT_ADDRESS_INNER_DST] == NULL) { |
| sadb_pfkey_error(pfkey_q, mp, EINVAL, |
| SADB_X_DIAGNOSTIC_MISSING_INNER_DST, |
| ksi->ks_in_serial); |
| return (B_FALSE); |
| } |
| |
| if (sadb_addrcheck(pfkey_q, mp, |
| extv[SADB_X_EXT_ADDRESS_INNER_DST], ksi->ks_in_serial, ns) |
| == KS_IN_ADDR_UNKNOWN || |
| sadb_addrcheck(pfkey_q, mp, |
| extv[SADB_X_EXT_ADDRESS_INNER_SRC], ksi->ks_in_serial, ns) |
| == KS_IN_ADDR_UNKNOWN) |
| return (B_FALSE); |
| |
| isrc = (struct sockaddr_in *) |
| (((sadb_address_t *)extv[SADB_X_EXT_ADDRESS_INNER_SRC]) + |
| 1); |
| idst = (struct sockaddr_in6 *) |
| (((sadb_address_t *)extv[SADB_X_EXT_ADDRESS_INNER_DST]) + |
| 1); |
| if (isrc->sin_family != idst->sin6_family) { |
| sadb_pfkey_error(pfkey_q, mp, EINVAL, |
| SADB_X_DIAGNOSTIC_INNER_AF_MISMATCH, |
| ksi->ks_in_serial); |
| return (B_FALSE); |
| } |
| } else if (extv[SADB_X_EXT_ADDRESS_INNER_DST] != NULL) { |
| sadb_pfkey_error(pfkey_q, mp, EINVAL, |
| SADB_X_DIAGNOSTIC_MISSING_INNER_SRC, |
| ksi->ks_in_serial); |
| return (B_FALSE); |
| } else { |
| isrc = NULL; /* For inner/outer port check below. */ |
| } |
| |
| dstext = (sadb_address_t *)extv[SADB_EXT_ADDRESS_DST]; |
| srcext = (sadb_address_t *)extv[SADB_EXT_ADDRESS_SRC]; |
| |
| if (dstext == NULL || srcext == NULL) |
| return (B_TRUE); |
| |
| dst = (struct sockaddr_in6 *)(dstext + 1); |
| src = (struct sockaddr_in *)(srcext + 1); |
| |
| if (isrc != NULL && |
| (isrc->sin_port != 0 || idst->sin6_port != 0) && |
| (src->sin_port != 0 || dst->sin6_port != 0)) { |
| /* Can't set inner and outer ports in one SA. */ |
| sadb_pfkey_error(pfkey_q, mp, EINVAL, |
| SADB_X_DIAGNOSTIC_DUAL_PORT_SETS, |
| ksi->ks_in_serial); |
| return (B_FALSE); |
| } |
| |
| if (dst->sin6_family == src->sin_family) |
| return (B_TRUE); |
| |
| if (srcext->sadb_address_proto != dstext->sadb_address_proto) { |
| if (srcext->sadb_address_proto == 0) { |
| srcext->sadb_address_proto = dstext->sadb_address_proto; |
| } else if (dstext->sadb_address_proto == 0) { |
| dstext->sadb_address_proto = srcext->sadb_address_proto; |
| } else { |
| /* Inequal protocols, neither were 0. Report error. */ |
| sadb_pfkey_error(pfkey_q, mp, EINVAL, |
| SADB_X_DIAGNOSTIC_PROTO_MISMATCH, |
| ksi->ks_in_serial); |
| return (B_FALSE); |
| } |
| } |
| |
| /* |
| * With the exception of an unspec IPv6 source and an IPv4 |
| * destination, address families MUST me matched. |
| */ |
| if (src->sin_family == AF_INET || |
| ksi->ks_in_srctype != KS_IN_ADDR_UNSPEC) { |
| sadb_pfkey_error(pfkey_q, mp, EINVAL, |
| SADB_X_DIAGNOSTIC_AF_MISMATCH, ksi->ks_in_serial); |
| return (B_FALSE); |
| } |
| |
| /* |
| * Convert "src" to AF_INET INADDR_ANY. We rely on sin_port being |
| * in the same place for sockaddr_in and sockaddr_in6. |
| */ |
| sport = src->sin_port; |
| bzero(src, sizeof (*src)); |
| src->sin_family = AF_INET; |
| src->sin_port = sport; |
| |
| return (B_TRUE); |
| } |
| |
| /* |
| * Set the results in "addrtype", given an IRE as requested by |
| * sadb_addrcheck(). |
| */ |
| int |
| sadb_addrset(ire_t *ire) |
| { |
| if ((ire->ire_type & IRE_BROADCAST) || |
| (ire->ire_ipversion == IPV4_VERSION && CLASSD(ire->ire_addr)) || |
| (ire->ire_ipversion == IPV6_VERSION && |
| IN6_IS_ADDR_MULTICAST(&(ire->ire_addr_v6)))) |
| return (KS_IN_ADDR_MBCAST); |
| if (ire->ire_type & (IRE_LOCAL | IRE_LOOPBACK)) |
| return (KS_IN_ADDR_ME); |
| return (KS_IN_ADDR_NOTME); |
| } |
| |
| /* |
| * Match primitives.. |
| * !!! TODO: short term: inner selectors |
| * ipv6 scope id (ifindex) |
| * longer term: zone id. sensitivity label. uid. |
| */ |
| boolean_t |
| sadb_match_spi(ipsa_query_t *sq, ipsa_t *sa) |
| { |
| return (sq->spi == sa->ipsa_spi); |
| } |
| |
| boolean_t |
| sadb_match_dst_v6(ipsa_query_t *sq, ipsa_t *sa) |
| { |
| return (IPSA_ARE_ADDR_EQUAL(sa->ipsa_dstaddr, sq->dstaddr, AF_INET6)); |
| } |
| |
| boolean_t |
| sadb_match_src_v6(ipsa_query_t *sq, ipsa_t *sa) |
| { |
| return (IPSA_ARE_ADDR_EQUAL(sa->ipsa_srcaddr, sq->srcaddr, AF_INET6)); |
| } |
| |
| boolean_t |
| sadb_match_dst_v4(ipsa_query_t *sq, ipsa_t *sa) |
| { |
| return (sq->dstaddr[0] == sa->ipsa_dstaddr[0]); |
| } |
| |
| boolean_t |
| sadb_match_src_v4(ipsa_query_t *sq, ipsa_t *sa) |
| { |
| return (sq->srcaddr[0] == sa->ipsa_srcaddr[0]); |
| } |
| |
| boolean_t |
| sadb_match_dstid(ipsa_query_t *sq, ipsa_t *sa) |
| { |
| return ((sa->ipsa_dst_cid != NULL) && |
| (sq->didtype == sa->ipsa_dst_cid->ipsid_type) && |
| (strcmp(sq->didstr, sa->ipsa_dst_cid->ipsid_cid) == 0)); |
| |
| } |
| boolean_t |
| sadb_match_srcid(ipsa_query_t *sq, ipsa_t *sa) |
| { |
| return ((sa->ipsa_src_cid != NULL) && |
| (sq->sidtype == sa->ipsa_src_cid->ipsid_type) && |
| (strcmp(sq->sidstr, sa->ipsa_src_cid->ipsid_cid) == 0)); |
| } |
| |
| boolean_t |
| sadb_match_kmc(ipsa_query_t *sq, ipsa_t *sa) |
| { |
| #define M(a, b) (((a) == 0) || ((b) == 0) || ((a) == (b))) |
| |
| return (M(sq->kmc, sa->ipsa_kmc) && M(sq->kmp, sa->ipsa_kmp)); |
| |
| #undef M |
| } |
| |
| /* |
| * Common function which extracts several PF_KEY extensions for ease of |
| * SADB matching. |
| * |
| * XXX TODO: weed out ipsa_query_t fields not used during matching |
| * or afterwards? |
| */ |
| int |
| sadb_form_query(keysock_in_t *ksi, uint32_t req, uint32_t match, |
| ipsa_query_t *sq, int *diagnostic) |
| { |
| int i; |
| ipsa_match_fn_t *mfpp = &(sq->matchers[0]); |
| |
| for (i = 0; i < IPSA_NMATCH; i++) |
| sq->matchers[i] = NULL; |
| |
| ASSERT((req & ~match) == 0); |
| |
| sq->req = req; |
| sq->dstext = (sadb_address_t *)ksi->ks_in_extv[SADB_EXT_ADDRESS_DST]; |
| sq->srcext = (sadb_address_t *)ksi->ks_in_extv[SADB_EXT_ADDRESS_SRC]; |
| sq->assoc = (sadb_sa_t *)ksi->ks_in_extv[SADB_EXT_SA]; |
| |
| if ((req & IPSA_Q_DST) && (sq->dstext == NULL)) { |
| *diagnostic = SADB_X_DIAGNOSTIC_MISSING_DST; |
| return (EINVAL); |
| } |
| if ((req & IPSA_Q_SRC) && (sq->srcext == NULL)) { |
| *diagnostic = SADB_X_DIAGNOSTIC_MISSING_SRC; |
| return (EINVAL); |
| } |
| if ((req & IPSA_Q_SA) && (sq->assoc == NULL)) { |
| *diagnostic = SADB_X_DIAGNOSTIC_MISSING_SA; |
| return (EINVAL); |
| } |
| |
| if (match & IPSA_Q_SA) { |
| *mfpp++ = sadb_match_spi; |
| sq->spi = sq->assoc->sadb_sa_spi; |
| } |
| |
| if (sq->dstext != NULL) |
| sq->dst = (struct sockaddr_in *)(sq->dstext + 1); |
| else { |
| sq->dst = NULL; |
| sq->dst6 = NULL; |
| sq->dstaddr = NULL; |
| } |
| |
| if (sq->srcext != NULL) |
| sq->src = (struct sockaddr_in *)(sq->srcext + 1); |
| else { |
| sq->src = NULL; |
| sq->src6 = NULL; |
| sq->srcaddr = NULL; |
| } |
| |
| if (sq->dst != NULL) |
| sq->af = sq->dst->sin_family; |
| else if (sq->src != NULL) |
| sq->af = sq->src->sin_family; |
| else |
| sq->af = AF_INET; |
| |
| if (sq->af == AF_INET6) { |
| if ((match & IPSA_Q_DST) && (sq->dstext != NULL)) { |
| *mfpp++ = sadb_match_dst_v6; |
| sq->dst6 = (struct sockaddr_in6 *)sq->dst; |
| sq->dstaddr = (uint32_t *)&(sq->dst6->sin6_addr); |
| } else { |
| match &= ~IPSA_Q_DST; |
| sq->dstaddr = ALL_ZEROES_PTR; |
| } |
| |
| if ((match & IPSA_Q_SRC) && (sq->srcext != NULL)) { |
| sq->src6 = (struct sockaddr_in6 *)(sq->srcext + 1); |
| sq->srcaddr = (uint32_t *)&sq->src6->sin6_addr; |
| if (sq->src6->sin6_family != AF_INET6) { |
| *diagnostic = SADB_X_DIAGNOSTIC_AF_MISMATCH; |
| return (EINVAL); |
| } |
| *mfpp++ = sadb_match_src_v6; |
| } else { |
| match &= ~IPSA_Q_SRC; |
| sq->srcaddr = ALL_ZEROES_PTR; |
| } |
| } else { |
| sq->src6 = sq->dst6 = NULL; |
| if ((match & IPSA_Q_DST) && (sq->dstext != NULL)) { |
| *mfpp++ = sadb_match_dst_v4; |
| sq->dstaddr = (uint32_t *)&sq->dst->sin_addr; |
| } else { |
| match &= ~IPSA_Q_DST; |
| sq->dstaddr = ALL_ZEROES_PTR; |
| } |
| if ((match & IPSA_Q_SRC) && (sq->srcext != NULL)) { |
| sq->srcaddr = (uint32_t *)&sq->src->sin_addr; |
| if (sq->src->sin_family != AF_INET) { |
| *diagnostic = SADB_X_DIAGNOSTIC_AF_MISMATCH; |
| return (EINVAL); |
| } |
| *mfpp++ = sadb_match_src_v4; |
| } else { |
| match &= ~IPSA_Q_SRC; |
| sq->srcaddr = ALL_ZEROES_PTR; |
| } |
| } |
| |
| sq->dstid = (sadb_ident_t *)ksi->ks_in_extv[SADB_EXT_IDENTITY_DST]; |
| if ((match & IPSA_Q_DSTID) && (sq->dstid != NULL)) { |
| sq->didstr = (char *)(sq->dstid + 1); |
| sq->didtype = sq->dstid->sadb_ident_type; |
| *mfpp++ = sadb_match_dstid; |
| } |
| |
| sq->srcid = (sadb_ident_t *)ksi->ks_in_extv[SADB_EXT_IDENTITY_SRC]; |
| |
| if ((match & IPSA_Q_SRCID) && (sq->srcid != NULL)) { |
| sq->sidstr = (char *)(sq->srcid + 1); |
| sq->sidtype = sq->srcid->sadb_ident_type; |
| *mfpp++ = sadb_match_srcid; |
| } |
| |
| sq->kmcext = (sadb_x_kmc_t *)ksi->ks_in_extv[SADB_X_EXT_KM_COOKIE]; |
| sq->kmc = 0; |
| sq->kmp = 0; |
| |
| if ((match & IPSA_Q_KMC) && (sq->kmcext)) { |
| sq->kmp = sq->kmcext->sadb_x_kmc_proto; |
| /* |
| * Be liberal in what we receive. Special-case the IKEv1 |
| * cookie, which closed-source in.iked assumes is 32 bits. |
| * Now that we store all 64 bits, we should pre-zero the |
| * reserved field on behalf of closed-source in.iked. |
| */ |
| if (sq->kmp == SADB_X_KMP_IKE) { |
| /* Just in case in.iked is misbehaving... */ |
| sq->kmcext->sadb_x_kmc_reserved = 0; |
| } |
| sq->kmc = sq->kmcext->sadb_x_kmc_cookie64; |
| *mfpp++ = sadb_match_kmc; |
| } |
| |
| if (match & (IPSA_Q_INBOUND|IPSA_Q_OUTBOUND)) { |
| if (sq->af == AF_INET6) |
| sq->sp = &sq->spp->s_v6; |
| else |
| sq->sp = &sq->spp->s_v4; |
| } else { |
| sq->sp = NULL; |
| } |
| |
| if (match & IPSA_Q_INBOUND) { |
| sq->inhash = INBOUND_HASH(sq->sp, sq->assoc->sadb_sa_spi); |
| sq->inbound = &sq->sp->sdb_if[sq->inhash]; |
| } else { |
| sq->inhash = 0; |
| sq->inbound = NULL; |
| } |
| |
| if (match & IPSA_Q_OUTBOUND) { |
| if (sq->af == AF_INET6) { |
| sq->outhash = OUTBOUND_HASH_V6(sq->sp, *(sq->dstaddr)); |
| } else { |
| sq->outhash = OUTBOUND_HASH_V4(sq->sp, *(sq->dstaddr)); |
| } |
| sq->outbound = &sq->sp->sdb_of[sq->outhash]; |
| } else { |
| sq->outhash = 0; |
| sq->outbound = NULL; |
| } |
| sq->match = match; |
| return (0); |
| } |
| |
| /* |
| * Match an initialized query structure with a security association; |
| * return B_TRUE on a match, B_FALSE on a miss. |
| * Applies match functions set up by sadb_form_query() until one returns false. |
| */ |
| boolean_t |
| sadb_match_query(ipsa_query_t *sq, ipsa_t *sa) |
| { |
| ipsa_match_fn_t *mfpp = &(sq->matchers[0]); |
| ipsa_match_fn_t mfp; |
| |
| for (mfp = *mfpp++; mfp != NULL; mfp = *mfpp++) { |
| if (!mfp(sq, sa)) |
| return (B_FALSE); |
| } |
| return (B_TRUE); |
| } |
| |
| /* |
| * Walker callback function to delete sa's based on src/dst address. |
| * Assumes that we're called with *head locked, no other locks held; |
| * Conveniently, and not coincidentally, this is both what sadb_walker |
| * gives us and also what sadb_unlinkassoc expects. |
| */ |
| struct sadb_purge_state |
| { |
| ipsa_query_t sq; |
| boolean_t inbnd; |
| uint8_t sadb_sa_state; |
| }; |
| |
| static void |
| sadb_purge_cb(isaf_t *head, ipsa_t *entry, void *cookie) |
| { |
| struct sadb_purge_state *ps = (struct sadb_purge_state *)cookie; |
| |
| ASSERT(MUTEX_HELD(&head->isaf_lock)); |
| |
| mutex_enter(&entry->ipsa_lock); |
| |
| if (entry->ipsa_state == IPSA_STATE_LARVAL || |
| !sadb_match_query(&ps->sq, entry)) { |
| mutex_exit(&entry->ipsa_lock); |
| return; |
| } |
| |
| if (ps->inbnd) { |
| sadb_delete_cluster(entry); |
| } |
| entry->ipsa_state = IPSA_STATE_DEAD; |
| (void) sadb_torch_assoc(head, entry); |
| } |
| |
| /* |
| * Common code to purge an SA with a matching src or dst address. |
| * Don't kill larval SA's in such a purge. |
| */ |
| int |
| sadb_purge_sa(mblk_t *mp, keysock_in_t *ksi, sadb_t *sp, |
| int *diagnostic, queue_t *pfkey_q) |
| { |
| struct sadb_purge_state ps; |
| int error = sadb_form_query(ksi, 0, |
| IPSA_Q_SRC|IPSA_Q_DST|IPSA_Q_SRCID|IPSA_Q_DSTID|IPSA_Q_KMC, |
| &ps.sq, diagnostic); |
| |
| if (error != 0) |
| return (error); |
| |
| /* |
| * This is simple, crude, and effective. |
| * Unimplemented optimizations (TBD): |
| * - we can limit how many places we search based on where we |
| * think the SA is filed. |
| * - if we get a dst address, we can hash based on dst addr to find |
| * the correct bucket in the outbound table. |
| */ |
| ps.inbnd = B_TRUE; |
| sadb_walker(sp->sdb_if, sp->sdb_hashsize, sadb_purge_cb, &ps); |
| ps.inbnd = B_FALSE; |
| sadb_walker(sp->sdb_of, sp->sdb_hashsize, sadb_purge_cb, &ps); |
| |
| ASSERT(mp->b_cont != NULL); |
| sadb_pfkey_echo(pfkey_q, mp, (sadb_msg_t *)mp->b_cont->b_rptr, ksi, |
| NULL); |
| return (0); |
| } |
| |
| static void |
| sadb_delpair_state_one(isaf_t *head, ipsa_t *entry, void *cookie) |
| { |
| struct sadb_purge_state *ps = (struct sadb_purge_state *)cookie; |
| isaf_t *inbound_bucket; |
| ipsa_t *peer_assoc; |
| ipsa_query_t *sq = &ps->sq; |
| |
| ASSERT(MUTEX_HELD(&head->isaf_lock)); |
| |
| mutex_enter(&entry->ipsa_lock); |
| |
| if ((entry->ipsa_state != ps->sadb_sa_state) || |
| ((sq->srcaddr != NULL) && |
| !IPSA_ARE_ADDR_EQUAL(entry->ipsa_srcaddr, sq->srcaddr, sq->af))) { |
| mutex_exit(&entry->ipsa_lock); |
| return; |
| } |
| |
| /* |
| * The isaf_t *, which is passed in , is always an outbound bucket, |
| * and we are preserving the outbound-then-inbound hash-bucket lock |
| * ordering. The sadb_walker() which triggers this function is called |
| * only on the outbound fanout, and the corresponding inbound bucket |
| * lock is safe to acquire here. |
| */ |
| |
| if (entry->ipsa_haspeer) { |
| inbound_bucket = INBOUND_BUCKET(sq->sp, entry->ipsa_spi); |
| mutex_enter(&inbound_bucket->isaf_lock); |
| peer_assoc = ipsec_getassocbyspi(inbound_bucket, |
| entry->ipsa_spi, entry->ipsa_srcaddr, |
| entry->ipsa_dstaddr, entry->ipsa_addrfam); |
| } else { |
| inbound_bucket = INBOUND_BUCKET(sq->sp, entry->ipsa_otherspi); |
| mutex_enter(&inbound_bucket->isaf_lock); |
| peer_assoc = ipsec_getassocbyspi(inbound_bucket, |
| entry->ipsa_otherspi, entry->ipsa_dstaddr, |
| entry->ipsa_srcaddr, entry->ipsa_addrfam); |
| } |
| |
| entry->ipsa_state = IPSA_STATE_DEAD; |
| (void) sadb_torch_assoc(head, entry); |
| if (peer_assoc != NULL) { |
| mutex_enter(&peer_assoc->ipsa_lock); |
| peer_assoc->ipsa_state = IPSA_STATE_DEAD; |
| (void) sadb_torch_assoc(inbound_bucket, peer_assoc); |
| } |
| mutex_exit(&inbound_bucket->isaf_lock); |
| } |
| |
| static int |
| sadb_delpair_state(mblk_t *mp, keysock_in_t *ksi, sadbp_t *spp, |
| int *diagnostic, queue_t *pfkey_q) |
| { |
| sadb_sa_t *assoc = (sadb_sa_t *)ksi->ks_in_extv[SADB_EXT_SA]; |
| struct sadb_purge_state ps; |
| int error; |
| |
| ps.sq.spp = spp; /* XXX param */ |
| |
| error = sadb_form_query(ksi, IPSA_Q_DST|IPSA_Q_SRC, |
| IPSA_Q_SRC|IPSA_Q_DST|IPSA_Q_SRCID|IPSA_Q_DSTID|IPSA_Q_KMC, |
| &ps.sq, diagnostic); |
| if (error != 0) |
| return (error); |
| |
| ps.inbnd = B_FALSE; |
| ps.sadb_sa_state = assoc->sadb_sa_state; |
| sadb_walker(ps.sq.sp->sdb_of, ps.sq.sp->sdb_hashsize, |
| sadb_delpair_state_one, &ps); |
| |
| ASSERT(mp->b_cont != NULL); |
| sadb_pfkey_echo(pfkey_q, mp, (sadb_msg_t *)mp->b_cont->b_rptr, |
| ksi, NULL); |
| return (0); |
| } |
| |
| /* |
| * Common code to delete/get an SA. |
| */ |
| int |
| sadb_delget_sa(mblk_t *mp, keysock_in_t *ksi, sadbp_t *spp, |
| int *diagnostic, queue_t *pfkey_q, uint8_t sadb_msg_type) |
| { |
| ipsa_query_t sq; |
| ipsa_t *echo_target = NULL; |
| ipsap_t ipsapp; |
| uint_t error = 0; |
| |
| if (sadb_msg_type == SADB_X_DELPAIR_STATE) |
| return (sadb_delpair_state(mp, ksi, spp, diagnostic, pfkey_q)); |
| |
| sq.spp = spp; /* XXX param */ |
| error = sadb_form_query(ksi, IPSA_Q_DST|IPSA_Q_SA, |
| IPSA_Q_SRC|IPSA_Q_DST|IPSA_Q_SA|IPSA_Q_INBOUND|IPSA_Q_OUTBOUND, |
| &sq, diagnostic); |
| if (error != 0) |
| return (error); |
| |
| error = get_ipsa_pair(&sq, &ipsapp, diagnostic); |
| if (error != 0) { |
| return (error); |
| } |
| |
| echo_target = ipsapp.ipsap_sa_ptr; |
| if (echo_target == NULL) |
| echo_target = ipsapp.ipsap_psa_ptr; |
| |
| if (sadb_msg_type == SADB_DELETE || sadb_msg_type == SADB_X_DELPAIR) { |
| /* |
| * Bucket locks will be required if SA is actually unlinked. |
| * get_ipsa_pair() returns valid hash bucket pointers even |
| * if it can't find a pair SA pointer. To prevent a potential |
| * deadlock, always lock the outbound bucket before the inbound. |
| */ |
| if (ipsapp.in_inbound_table) { |
| mutex_enter(&ipsapp.ipsap_pbucket->isaf_lock); |
| mutex_enter(&ipsapp.ipsap_bucket->isaf_lock); |
| } else { |
| mutex_enter(&ipsapp.ipsap_bucket->isaf_lock); |
| mutex_enter(&ipsapp.ipsap_pbucket->isaf_lock); |
| } |
| |
| if (ipsapp.ipsap_sa_ptr != NULL) { |
| mutex_enter(&ipsapp.ipsap_sa_ptr->ipsa_lock); |
| if (ipsapp.ipsap_sa_ptr->ipsa_flags & IPSA_F_INBOUND) { |
| sadb_delete_cluster(ipsapp.ipsap_sa_ptr); |
| } |
| ipsapp.ipsap_sa_ptr->ipsa_state = IPSA_STATE_DEAD; |
| (void) sadb_torch_assoc(ipsapp.ipsap_bucket, |
| ipsapp.ipsap_sa_ptr); |
| /* |
| * sadb_torch_assoc() releases the ipsa_lock |
| * and calls sadb_unlinkassoc() which does a |
| * IPSA_REFRELE. |
| */ |
| } |
| if (ipsapp.ipsap_psa_ptr != NULL) { |
| mutex_enter(&ipsapp.ipsap_psa_ptr->ipsa_lock); |
| if (sadb_msg_type == SADB_X_DELPAIR || |
| ipsapp.ipsap_psa_ptr->ipsa_haspeer) { |
| if (ipsapp.ipsap_psa_ptr->ipsa_flags & |
| IPSA_F_INBOUND) { |
| sadb_delete_cluster |
| (ipsapp.ipsap_psa_ptr); |
| } |
| ipsapp.ipsap_psa_ptr->ipsa_state = |
| IPSA_STATE_DEAD; |
| (void) sadb_torch_assoc(ipsapp.ipsap_pbucket, |
| ipsapp.ipsap_psa_ptr); |
| } else { |
| /* |
| * Only half of the "pair" has been deleted. |
| * Update the remaining SA and remove references |
| * to its pair SA, which is now gone. |
| */ |
| ipsapp.ipsap_psa_ptr->ipsa_otherspi = 0; |
| ipsapp.ipsap_psa_ptr->ipsa_flags &= |
| ~IPSA_F_PAIRED; |
| mutex_exit(&ipsapp.ipsap_psa_ptr->ipsa_lock); |
| } |
| } else if (sadb_msg_type == SADB_X_DELPAIR) { |
| *diagnostic = SADB_X_DIAGNOSTIC_PAIR_SA_NOTFOUND; |
| error = ESRCH; |
| } |
| mutex_exit(&ipsapp.ipsap_bucket->isaf_lock); |
| mutex_exit(&ipsapp.ipsap_pbucket->isaf_lock); |
| } |
| |
| ASSERT(mp->b_cont != NULL); |
| |
| if (error == 0) |
| sadb_pfkey_echo(pfkey_q, mp, (sadb_msg_t *) |
| mp->b_cont->b_rptr, ksi, echo_target); |
| |
| destroy_ipsa_pair(&ipsapp); |
| |
| return (error); |
| } |
| |
| /* |
| * This function takes a sadb_sa_t and finds the ipsa_t structure |
| * and the isaf_t (hash bucket) that its stored under. If the security |
| * association has a peer, the ipsa_t structure and bucket for that security |
| * association are also searched for. The "pair" of ipsa_t's and isaf_t's |
| * are returned as a ipsap_t. |
| * |
| * The hash buckets are returned for convenience, if the calling function |
| * needs to use the hash bucket locks, say to remove the SA's, it should |
| * take care to observe the convention of locking outbound bucket then |
| * inbound bucket. The flag in_inbound_table provides direction. |
| * |
| * Note that a "pair" is defined as one (but not both) of the following: |
| * |
| * A security association which has a soft reference to another security |
| * association via its SPI. |
| * |
| * A security association that is not obviously "inbound" or "outbound" so |
| * it appears in both hash tables, the "peer" being the same security |
| * association in the other hash table. |
| * |
| * This function will return NULL if the ipsa_t can't be found in the |
| * inbound or outbound hash tables (not found). If only one ipsa_t is |
| * found, the pair ipsa_t will be NULL. Both isaf_t values are valid |
| * provided at least one ipsa_t is found. |
| */ |
| static int |
| get_ipsa_pair(ipsa_query_t *sq, ipsap_t *ipsapp, int *diagnostic) |
| { |
| uint32_t pair_srcaddr[IPSA_MAX_ADDRLEN]; |
| uint32_t pair_dstaddr[IPSA_MAX_ADDRLEN]; |
| uint32_t pair_spi; |
| |
| init_ipsa_pair(ipsapp); |
| |
| ipsapp->in_inbound_table = B_FALSE; |
| |
| /* Lock down both buckets. */ |
| mutex_enter(&sq->outbound->isaf_lock); |
| mutex_enter(&sq->inbound->isaf_lock); |
| |
| if (sq->assoc->sadb_sa_flags & IPSA_F_INBOUND) { |
| ipsapp->ipsap_sa_ptr = ipsec_getassocbyspi(sq->inbound, |
| sq->assoc->sadb_sa_spi, sq->srcaddr, sq->dstaddr, sq->af); |
| if (ipsapp->ipsap_sa_ptr != NULL) { |
| ipsapp->ipsap_bucket = sq->inbound; |
| ipsapp->ipsap_pbucket = sq->outbound; |
| ipsapp->in_inbound_table = B_TRUE; |
| } else { |
| ipsapp->ipsap_sa_ptr = ipsec_getassocbyspi(sq->outbound, |
| sq->assoc->sadb_sa_spi, sq->srcaddr, sq->dstaddr, |
| sq->af); |
| ipsapp->ipsap_bucket = sq->outbound; |
| ipsapp->ipsap_pbucket = sq->inbound; |
| } |
| } else { |
| /* IPSA_F_OUTBOUND is set *or* no directions flags set. */ |
| ipsapp->ipsap_sa_ptr = |
| ipsec_getassocbyspi(
|