| /* |
| * 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 2009 Sun Microsystems, Inc. All rights reserved. |
| * Use is subject to license terms. |
| */ |
| |
| #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 <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/iphada.h> |
| #include <inet/ip_if.h> |
| #include <inet/ipdrop.h> |
| #include <inet/ipclassifier.h> |
| #include <inet/sctp_ip.h> |
| #include <inet/tun.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 mblk_t *sadb_extended_acquire(ipsec_selector_t *, ipsec_policy_t *, |
| ipsec_action_t *, boolean_t, uint32_t, uint32_t, netstack_t *); |
| static void sadb_ill_df(ill_t *, mblk_t *, isaf_t *, int, boolean_t); |
| static ipsa_t *sadb_torch_assoc(isaf_t *, ipsa_t *, boolean_t, mblk_t **); |
| static void sadb_drain_torchq(queue_t *, mblk_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 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 void ipsa_set_replay(ipsa_t *ipsa, uint32_t offset); |
| |
| extern void (*cl_inet_getspi)(netstackid_t stack_id, uint8_t protocol, |
| uint8_t *ptr, size_t len, void *args); |
| extern int (*cl_inet_checkspi)(netstackid_t stack_id, uint8_t protocol, |
| uint32_t spi, void *args); |
| extern void (*cl_inet_deletespi)(netstackid_t stack_id, uint8_t protocol, |
| uint32_t spi, void *args); |
| |
| /* |
| * 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) |
| { |
| time_t sum; |
| |
| /* |
| * 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; |
| /* |
| * This sum may still overflow. |
| */ |
| sum = base + delta; |
| |
| /* |
| * .. so if the result is less than the base, we overflowed. |
| */ |
| if (sum < base) |
| sum = TIME_MAX; |
| |
| return (sum); |
| } |
| |
| /* |
| * 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; |
| |
| 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); |
| |
| mutex_enter(&ipsa->ipsa_lock); |
| /* Don't call sadb_clear_lpkt() since we hold the ipsa_lock anyway. */ |
| ip_drop_packet(ipsa->ipsa_lpkt, B_TRUE, NULL, NULL, |
| DROPPER(ipss, ipds_sadb_inlarval_timeout), |
| &ipss->ipsec_sadb_dropper); |
| 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_src_cid != NULL) { |
| IPSID_REFRELE(ipsa->ipsa_src_cid); |
| } |
| if (ipsa->ipsa_dst_cid != NULL) { |
| IPSID_REFRELE(ipsa->ipsa_dst_cid); |
| } |
| if (ipsa->ipsa_integ != NULL) |
| kmem_free(ipsa->ipsa_integ, ipsa->ipsa_integlen); |
| if (ipsa->ipsa_sens != NULL) |
| kmem_free(ipsa->ipsa_sens, ipsa->ipsa_senslen); |
| |
| 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)); |
| |
| /* 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); |
| } |
| } |
| |
| /* |
| * From the given SA, construct a dl_ct_ipsec_key and |
| * a dl_ct_ipsec structures to be sent to the adapter as part |
| * of a DL_CONTROL_REQ. |
| * |
| * ct_sa must point to the storage allocated for the key |
| * structure and must be followed by storage allocated |
| * for the SA information that must be sent to the driver |
| * as part of the DL_CONTROL_REQ request. |
| * |
| * The is_inbound boolean indicates whether the specified |
| * SA is part of an inbound SA table. |
| * |
| * Returns B_TRUE if the corresponding SA must be passed to |
| * a provider, B_FALSE otherwise; frees *mp if it returns B_FALSE. |
| */ |
| static boolean_t |
| sadb_req_from_sa(ipsa_t *sa, mblk_t *mp, boolean_t is_inbound) |
| { |
| dl_ct_ipsec_key_t *keyp; |
| dl_ct_ipsec_t *sap; |
| void *ct_sa = mp->b_wptr; |
| |
| ASSERT(MUTEX_HELD(&sa->ipsa_lock)); |
| |
| keyp = (dl_ct_ipsec_key_t *)(ct_sa); |
| sap = (dl_ct_ipsec_t *)(keyp + 1); |
| |
| IPSECHW_DEBUG(IPSECHW_CAPAB, ("sadb_req_from_sa: " |
| "is_inbound = %d\n", is_inbound)); |
| |
| /* initialize flag */ |
| sap->sadb_sa_flags = 0; |
| if (is_inbound) { |
| sap->sadb_sa_flags |= DL_CT_IPSEC_INBOUND; |
| /* |
| * If an inbound SA has a peer, then mark it has being |
| * an outbound SA as well. |
| */ |
| if (sa->ipsa_haspeer) |
| sap->sadb_sa_flags |= DL_CT_IPSEC_OUTBOUND; |
| } else { |
| /* |
| * If an outbound SA has a peer, then don't send it, |
| * since we will send the copy from the inbound table. |
| */ |
| if (sa->ipsa_haspeer) { |
| freemsg(mp); |
| return (B_FALSE); |
| } |
| sap->sadb_sa_flags |= DL_CT_IPSEC_OUTBOUND; |
| } |
| |
| keyp->dl_key_spi = sa->ipsa_spi; |
| bcopy(sa->ipsa_dstaddr, keyp->dl_key_dest_addr, |
| DL_CTL_IPSEC_ADDR_LEN); |
| keyp->dl_key_addr_family = sa->ipsa_addrfam; |
| |
| sap->sadb_sa_auth = sa->ipsa_auth_alg; |
| sap->sadb_sa_encrypt = sa->ipsa_encr_alg; |
| |
| sap->sadb_key_len_a = sa->ipsa_authkeylen; |
| sap->sadb_key_bits_a = sa->ipsa_authkeybits; |
| bcopy(sa->ipsa_authkey, |
| sap->sadb_key_data_a, sap->sadb_key_len_a); |
| |
| sap->sadb_key_len_e = sa->ipsa_encrkeylen; |
| sap->sadb_key_bits_e = sa->ipsa_encrkeybits; |
| bcopy(sa->ipsa_encrkey, |
| sap->sadb_key_data_e, sap->sadb_key_len_e); |
| |
| mp->b_wptr += sizeof (dl_ct_ipsec_t) + sizeof (dl_ct_ipsec_key_t); |
| return (B_TRUE); |
| } |
| |
| /* |
| * Called from AH or ESP to format a message which will be used to inform |
| * IPsec-acceleration-capable ills of a SADB change. |
| * (It is not possible to send the message to IP directly from this function |
| * since the SA, if any, is locked during the call). |
| * |
| * dl_operation: DL_CONTROL_REQ operation (add, delete, update, etc) |
| * sa_type: identifies whether the operation applies to AH or ESP |
| * (must be one of SADB_SATYPE_AH or SADB_SATYPE_ESP) |
| * sa: Pointer to an SA. Must be non-NULL and locked |
| * for ADD, DELETE, GET, and UPDATE operations. |
| * This function returns an mblk chain that must be passed to IP |
| * for forwarding to the IPsec capable providers. |
| */ |
| mblk_t * |
| sadb_fmt_sa_req(uint_t dl_operation, uint_t sa_type, ipsa_t *sa, |
| boolean_t is_inbound) |
| { |
| mblk_t *mp; |
| dl_control_req_t *ctrl; |
| boolean_t need_key = B_FALSE; |
| mblk_t *ctl_mp = NULL; |
| ipsec_ctl_t *ctl; |
| |
| /* |
| * 1 allocate and initialize DL_CONTROL_REQ M_PROTO |
| * 2 if a key is needed for the operation |
| * 2.1 initialize key |
| * 2.2 if a full SA is needed for the operation |
| * 2.2.1 initialize full SA info |
| * 3 return message; caller will call ill_ipsec_capab_send_all() |
| * to send the resulting message to IPsec capable ills. |
| */ |
| |
| ASSERT(sa_type == SADB_SATYPE_AH || sa_type == SADB_SATYPE_ESP); |
| |
| /* |
| * Allocate DL_CONTROL_REQ M_PROTO |
| * We allocate room for the SA even if it's not needed |
| * by some of the operations (for example flush) |
| */ |
| mp = allocb(sizeof (dl_control_req_t) + |
| sizeof (dl_ct_ipsec_key_t) + sizeof (dl_ct_ipsec_t), BPRI_HI); |
| if (mp == NULL) |
| return (NULL); |
| mp->b_datap->db_type = M_PROTO; |
| |
| /* initialize dl_control_req_t */ |
| ctrl = (dl_control_req_t *)mp->b_wptr; |
| ctrl->dl_primitive = DL_CONTROL_REQ; |
| ctrl->dl_operation = dl_operation; |
| ctrl->dl_type = sa_type == SADB_SATYPE_AH ? DL_CT_IPSEC_AH : |
| DL_CT_IPSEC_ESP; |
| ctrl->dl_key_offset = sizeof (dl_control_req_t); |
| ctrl->dl_key_length = sizeof (dl_ct_ipsec_key_t); |
| ctrl->dl_data_offset = sizeof (dl_control_req_t) + |
| sizeof (dl_ct_ipsec_key_t); |
| ctrl->dl_data_length = sizeof (dl_ct_ipsec_t); |
| mp->b_wptr += sizeof (dl_control_req_t); |
| |
| if ((dl_operation == DL_CO_SET) || (dl_operation == DL_CO_DELETE)) { |
| ASSERT(sa != NULL); |
| ASSERT(MUTEX_HELD(&sa->ipsa_lock)); |
| |
| need_key = B_TRUE; |
| |
| /* |
| * Initialize key and SA data. Note that for some |
| * operations the SA data is ignored by the provider |
| * (delete, etc.) |
| */ |
| if (!sadb_req_from_sa(sa, mp, is_inbound)) |
| return (NULL); |
| } |
| |
| /* construct control message */ |
| ctl_mp = allocb(sizeof (ipsec_ctl_t), BPRI_HI); |
| if (ctl_mp == NULL) { |
| cmn_err(CE_WARN, "sadb_fmt_sa_req: allocb failed\n"); |
| freemsg(mp); |
| return (NULL); |
| } |
| |
| ctl_mp->b_datap->db_type = M_CTL; |
| ctl_mp->b_wptr += sizeof (ipsec_ctl_t); |
| ctl_mp->b_cont = mp; |
| |
| ctl = (ipsec_ctl_t *)ctl_mp->b_rptr; |
| ctl->ipsec_ctl_type = IPSEC_CTL; |
| ctl->ipsec_ctl_len = sizeof (ipsec_ctl_t); |
| ctl->ipsec_ctl_sa_type = sa_type; |
| |
| if (need_key) { |
| /* |
| * Keep an additional reference on SA, since it will be |
| * needed by IP to send control messages corresponding |
| * to that SA from its perimeter. IP will do a |
| * IPSA_REFRELE when done with the request. |
| */ |
| ASSERT(MUTEX_HELD(&sa->ipsa_lock)); |
| IPSA_REFHOLD(sa); |
| ctl->ipsec_ctl_sa = sa; |
| } else |
| ctl->ipsec_ctl_sa = NULL; |
| |
| return (ctl_mp); |
| } |
| |
| |
| /* |
| * Called by sadb_ill_download() to dump the entries for a specific |
| * fanout table. For each SA entry in the table passed as argument, |
| * use mp as a template and constructs a full DL_CONTROL message, and |
| * call ill_dlpi_send(), provided by IP, to send the resulting |
| * messages to the ill. |
| */ |
| static void |
| sadb_ill_df(ill_t *ill, mblk_t *mp, isaf_t *fanout, int num_entries, |
| boolean_t is_inbound) |
| { |
| ipsa_t *walker; |
| mblk_t *nmp, *salist; |
| int i, error = 0; |
| ip_stack_t *ipst = ill->ill_ipst; |
| netstack_t *ns = ipst->ips_netstack; |
| |
| IPSECHW_DEBUG(IPSECHW_SADB, ("sadb_ill_df: fanout at 0x%p ne=%d\n", |
| (void *)fanout, num_entries)); |
| /* |
| * For each IPSA hash bucket do: |
| * - Hold the mutex |
| * - Walk each entry, sending a corresponding request to IP |
| * for it. |
| */ |
| ASSERT(mp->b_datap->db_type == M_PROTO); |
| |
| for (i = 0; i < num_entries; i++) { |
| mutex_enter(&fanout[i].isaf_lock); |
| salist = NULL; |
| |
| for (walker = fanout[i].isaf_ipsa; walker != NULL; |
| walker = walker->ipsa_next) { |
| IPSECHW_DEBUG(IPSECHW_SADB, |
| ("sadb_ill_df: sending SA to ill via IP \n")); |
| /* |
| * Duplicate the template mp passed and |
| * complete DL_CONTROL_REQ data. |
| * To be more memory efficient, we could use |
| * dupb() for the M_CTL and copyb() for the M_PROTO |
| * as the M_CTL, since the M_CTL is the same for |
| * every SA entry passed down to IP for the same ill. |
| * |
| * Note that copymsg/copyb ensure that the new mblk |
| * is at least as large as the source mblk even if it's |
| * not using all its storage -- therefore, nmp |
| * has trailing space for sadb_req_from_sa to add |
| * the SA-specific bits. |
| */ |
| mutex_enter(&walker->ipsa_lock); |
| if (ipsec_capab_match(ill, |
| ill->ill_phyint->phyint_ifindex, ill->ill_isv6, |
| walker, ns)) { |
| nmp = copymsg(mp); |
| if (nmp == NULL) { |
| IPSECHW_DEBUG(IPSECHW_SADB, |
| ("sadb_ill_df: alloc error\n")); |
| error = ENOMEM; |
| mutex_exit(&walker->ipsa_lock); |
| break; |
| } |
| if (sadb_req_from_sa(walker, nmp, is_inbound)) { |
| nmp->b_next = salist; |
| salist = nmp; |
| } |
| } |
| mutex_exit(&walker->ipsa_lock); |
| } |
| mutex_exit(&fanout[i].isaf_lock); |
| while (salist != NULL) { |
| nmp = salist; |
| salist = nmp->b_next; |
| nmp->b_next = NULL; |
| ill_dlpi_send(ill, nmp); |
| } |
| if (error != 0) |
| break; /* out of for loop. */ |
| } |
| } |
| |
| /* |
| * Called by ill_ipsec_capab_add(). Sends a copy of the SADB of |
| * the type specified by sa_type to the specified ill. |
| * |
| * We call for each fanout table defined by the SADB (one per |
| * protocol). sadb_ill_df() finally calls ill_dlpi_send() for |
| * each SADB entry in order to send a corresponding DL_CONTROL_REQ |
| * message to the ill. |
| */ |
| void |
| sadb_ill_download(ill_t *ill, uint_t sa_type) |
| { |
| mblk_t *protomp; /* prototype message */ |
| dl_control_req_t *ctrl; |
| sadbp_t *spp; |
| sadb_t *sp; |
| int dlt; |
| ip_stack_t *ipst = ill->ill_ipst; |
| netstack_t *ns = ipst->ips_netstack; |
| |
| ASSERT(sa_type == SADB_SATYPE_AH || sa_type == SADB_SATYPE_ESP); |
| |
| /* |
| * Allocate and initialize prototype answer. A duplicate for |
| * each SA is sent down to the interface. |
| */ |
| |
| /* DL_CONTROL_REQ M_PROTO mblk_t */ |
| protomp = allocb(sizeof (dl_control_req_t) + |
| sizeof (dl_ct_ipsec_key_t) + sizeof (dl_ct_ipsec_t), BPRI_HI); |
| if (protomp == NULL) |
| return; |
| protomp->b_datap->db_type = M_PROTO; |
| |
| dlt = (sa_type == SADB_SATYPE_AH) ? DL_CT_IPSEC_AH : DL_CT_IPSEC_ESP; |
| if (sa_type == SADB_SATYPE_ESP) { |
| ipsecesp_stack_t *espstack = ns->netstack_ipsecesp; |
| |
| spp = &espstack->esp_sadb; |
| } else { |
| ipsecah_stack_t *ahstack = ns->netstack_ipsecah; |
| |
| spp = &ahstack->ah_sadb; |
| } |
| |
| ctrl = (dl_control_req_t *)protomp->b_wptr; |
| ctrl->dl_primitive = DL_CONTROL_REQ; |
| ctrl->dl_operation = DL_CO_SET; |
| ctrl->dl_type = dlt; |
| ctrl->dl_key_offset = sizeof (dl_control_req_t); |
| ctrl->dl_key_length = sizeof (dl_ct_ipsec_key_t); |
| ctrl->dl_data_offset = sizeof (dl_control_req_t) + |
| sizeof (dl_ct_ipsec_key_t); |
| ctrl->dl_data_length = sizeof (dl_ct_ipsec_t); |
| protomp->b_wptr += sizeof (dl_control_req_t); |
| |
| /* |
| * then for each SADB entry, we fill out the dl_ct_ipsec_key_t |
| * and dl_ct_ipsec_t |
| */ |
| sp = ill->ill_isv6 ? &(spp->s_v6) : &(spp->s_v4); |
| sadb_ill_df(ill, protomp, sp->sdb_of, sp->sdb_hashsize, B_FALSE); |
| sadb_ill_df(ill, protomp, sp->sdb_if, sp->sdb_hashsize, B_TRUE); |
| freemsg(protomp); |
| } |
| |
| /* |
| * 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); |
| } |
| |
| static void |
| sadb_send_flush_req(sadbp_t *spp) |
| { |
| mblk_t *ctl_mp; |
| |
| /* |
| * we've been unplumbed, or never were plumbed; don't go there. |
| */ |
| if (spp->s_ip_q == NULL) |
| return; |
| |
| /* have IP send a flush msg to the IPsec accelerators */ |
| ctl_mp = sadb_fmt_sa_req(DL_CO_FLUSH, spp->s_satype, NULL, B_TRUE); |
| if (ctl_mp != NULL) |
| putnext(spp->s_ip_q, ctl_mp); |
| } |
| |
| void |
| sadbp_flush(sadbp_t *spp, netstack_t *ns) |
| { |
| sadb_flush(&spp->s_v4, ns); |
| sadb_flush(&spp->s_v6, ns); |
| |
| sadb_send_flush_req(spp); |
| } |
| |
| void |
| sadbp_destroy(sadbp_t *spp, netstack_t *ns) |
| { |
| sadb_destroy(&spp->s_v4, ns); |
| sadb_destroy(&spp->s_v6, ns); |
| |
| sadb_send_flush_req(spp); |
| 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); |
| } |
| |
| /* |
| * 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); |
| |
| /* |
| * 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_integ != NULL) { |
| newbie->ipsa_integ = kmem_alloc(newbie->ipsa_integlen, |
| KM_NOSLEEP); |
| if (newbie->ipsa_integ == NULL) { |
| error = B_TRUE; |
| } else { |
| bcopy(ipsa->ipsa_integ, newbie->ipsa_integ, |
| newbie->ipsa_integlen); |
| } |
| } |
| |
| if (ipsa->ipsa_sens != NULL) { |
| newbie->ipsa_sens = kmem_alloc(newbie->ipsa_senslen, |
| KM_NOSLEEP); |
| if (newbie->ipsa_sens == NULL) { |
| error = B_TRUE; |
| } else { |
| bcopy(ipsa->ipsa_sens, newbie->ipsa_sens, |
| newbie->ipsa_senslen); |
| } |
| } |
| |
| 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, uint32_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_cookie = kmc; |
| kmcext->sadb_x_kmc_reserved = 0; |
| |
| 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; |
| sa_family_t fam, pfam; /* Address family for SADB_EXT_ADDRESS */ |
| /* src/dst and proxy sockaddrs. */ |
| /* |
| * 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; |
| uint64_t *bitmap; |
| uint8_t *cur, *end; |
| /* These indicate the presence of the above extension fields. */ |
| boolean_t soft, hard, isrc, idst, auth, encr, sensinteg, srcid, dstid; |
| 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); |
| |
| 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; |
| } else { |
| soft = B_FALSE; |
| } |
| |
| 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; |
| } else { |
| hard = B_FALSE; |
| } |
| |
| 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) { |
| isrc = B_FALSE; |
| idst = B_FALSE; |
| } else { |
| 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; |
| } else { |
| auth = B_FALSE; |
| } |
| |
| if (ipsa->ipsa_encrkeylen != 0) { |
| encrsize = roundup(sizeof (sadb_key_t) + ipsa->ipsa_encrkeylen, |
| sizeof (uint64_t)); |
| alloclen += encrsize; |
| encr = B_TRUE; |
| } else { |
| encr = B_FALSE; |
| } |
| |
| /* No need for roundup on sens and integ. */ |
| if (ipsa->ipsa_integlen != 0 || ipsa->ipsa_senslen != 0) { |
| alloclen += sizeof (sadb_key_t) + ipsa->ipsa_integlen + |
| ipsa->ipsa_senslen; |
| sensinteg = B_TRUE; |
| } else { |
| sensinteg = B_FALSE; |
| } |
| |
| /* |
| * 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; |
| } else { |
| srcid = B_FALSE; |
| } |
| |
| 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; |
| } else { |
| dstid = B_FALSE; |
| } |
| |
| 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); |
| |
| 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) { |
| 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 = 0; |
| bcopy(ipsa->ipsa_encrkey, key + 1, ipsa->ipsa_encrkeylen); |
| 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; |
| sens->sadb_sens_len = SADB_8TO64(sizeof (sadb_sens_t *) + |
| ipsa->ipsa_senslen + ipsa->ipsa_integlen); |
| sens->sadb_sens_dpd = ipsa->ipsa_dpd; |
| sens->sadb_sens_sens_level = ipsa->ipsa_senslevel; |
| sens->sadb_sens_integ_level = ipsa->ipsa_integlevel; |
| sens->sadb_sens_sens_len = SADB_8TO64(ipsa->ipsa_senslen); |
| sens->sadb_sens_integ_len = SADB_8TO64(ipsa->ipsa_integlen); |
| sens->sadb_sens_reserved = 0; |
| bitmap = (uint64_t *)(sens + 1); |
| if (ipsa->ipsa_sens != NULL) { |
| bcopy(ipsa->ipsa_sens, bitmap, ipsa->ipsa_senslen); |
| bitmap += sens->sadb_sens_sens_len; |
| } |
| if (ipsa->ipsa_integ != NULL) |
| bcopy(ipsa->ipsa_integ, bitmap, ipsa->ipsa_integlen); |
| 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: |
| if (mp != NULL) |
| 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 = casptr((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 casptr, then we have "exclusive" access |
| * to the timeout handle. Fire it off in 4 seconds, because it |
| * just seems like a good interval. |
| */ |
| *top = qtimeout(*pfkey_qp, ager, agerarg, drv_usectohz(4000000)); |
| |
| 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; |
| ire_t *ire; |
| 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)); |
| |
| /* 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. |
| * |
| * A ctable lookup for local is sufficient here. If we're |
| * local, return KS_IN_ADDR_ME, otherwise KS_IN_ADDR_NOTME. |
| * |
| * XXX Zones alert -> me/notme decision needs to be tempered |
| * by what zone we're in when we go to zone-aware IPsec. |
| */ |
| ire = ire_ctable_lookup_v6(&sin6->sin6_addr, NULL, |
| IRE_LOCAL, NULL, ALL_ZONES, NULL, MATCH_IRE_TYPE, |
| ns->netstack_ip); |
| if (ire != NULL) { |
| /* Hey hey, it's local. */ |
| IRE_REFRELE(ire); |
| 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. |
| * |
| * Lookup on the ctable for IRE_BROADCAST or IRE_LOCAL. |
| * A NULL return value is NOTME, otherwise, look at the |
| * returned ire for broadcast or not and return accordingly. |
| * |
| * XXX Zones alert -> me/notme decision needs to be tempered |
| * by what zone we're in when we go to zone-aware IPsec. |
| */ |
| ire = ire_ctable_lookup(sin->sin_addr.s_addr, 0, |
| IRE_LOCAL | IRE_BROADCAST, NULL, ALL_ZONES, NULL, |
| MATCH_IRE_TYPE, ns->netstack_ip); |
| if (ire != NULL) { |
| /* Check for local or broadcast */ |
| type = ire->ire_type; |
| IRE_REFRELE(ire); |
| ASSERT(type == IRE_LOCAL || type == IRE_BROADCAST); |
| return ((type == IRE_LOCAL) ? KS_IN_ADDR_ME : |
| 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); |
| } |
| |
| |
| /* |
| * 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 |
| { |
| uint32_t *src; |
| uint32_t *dst; |
| sa_family_t af; |
| boolean_t inbnd; |
| char *sidstr; |
| char *didstr; |
| uint16_t sidtype; |
| uint16_t didtype; |
| uint32_t kmproto; |
| uint8_t sadb_sa_state; |
| mblk_t *mq; |
| sadb_t *sp; |
| }; |
| |
| 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) || |
| (ps->src != NULL && |
| !IPSA_ARE_ADDR_EQUAL(entry->ipsa_srcaddr, ps->src, ps->af)) || |
| (ps->dst != NULL && |
| !IPSA_ARE_ADDR_EQUAL(entry->ipsa_dstaddr, ps->dst, ps->af)) || |
| (ps->didstr != NULL && (entry->ipsa_dst_cid != NULL) && |
| !(ps->didtype == entry->ipsa_dst_cid->ipsid_type && |
| strcmp(ps->didstr, entry->ipsa_dst_cid->ipsid_cid) == 0)) || |
| (ps->sidstr != NULL && (entry->ipsa_src_cid != NULL) && |
| !(ps->sidtype == entry->ipsa_src_cid->ipsid_type && |
| strcmp(ps->sidstr, entry->ipsa_src_cid->ipsid_cid) == 0)) || |
| (ps->kmproto <= SADB_X_KMP_MAX && ps->kmproto != entry->ipsa_kmp)) { |
| 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, ps->inbnd, &ps->mq); |
| } |
| |
| /* |
| * 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, queue_t *pfkey_q, |
| queue_t *ip_q) |
| { |
| sadb_address_t *dstext = |
| (sadb_address_t *)ksi->ks_in_extv[SADB_EXT_ADDRESS_DST]; |
| sadb_address_t *srcext = |
| (sadb_address_t *)ksi->ks_in_extv[SADB_EXT_ADDRESS_SRC]; |
| sadb_ident_t *dstid = |
| (sadb_ident_t *)ksi->ks_in_extv[SADB_EXT_IDENTITY_DST]; |
| sadb_ident_t *srcid = |
| (sadb_ident_t *)ksi->ks_in_extv[SADB_EXT_IDENTITY_SRC]; |
| sadb_x_kmc_t *kmc = |
| (sadb_x_kmc_t *)ksi->ks_in_extv[SADB_X_EXT_KM_COOKIE]; |
| struct sockaddr_in *src, *dst; |
| struct sockaddr_in6 *src6, *dst6; |
| struct sadb_purge_state ps; |
| |
| /* |
| * Don't worry about IPv6 v4-mapped addresses, sadb_addrcheck() |
| * takes care of them. |
| */ |
| |
| /* enforced by caller */ |
| ASSERT((dstext != NULL) || (srcext != NULL)); |
| |
| ps.src = NULL; |
| ps.dst = NULL; |
| #ifdef DEBUG |
| ps.af = (sa_family_t)-1; |
| #endif |
| ps.mq = NULL; |
| ps.sidstr = NULL; |
| ps.didstr = NULL; |
| ps.kmproto = SADB_X_KMP_MAX + 1; |
| |
| if (dstext != NULL) { |
| dst = (struct sockaddr_in *)(dstext + 1); |
| ps.af = dst->sin_family; |
| if (dst->sin_family == AF_INET6) { |
| dst6 = (struct sockaddr_in6 *)dst; |
| ps.dst = (uint32_t *)&dst6->sin6_addr; |
| } else { |
| ps.dst = (uint32_t *)&dst->sin_addr; |
| } |
| } |
| |
| if (srcext != NULL) { |
| src = (struct sockaddr_in *)(srcext + 1); |
| ps.af = src->sin_family; |
| if (src->sin_family == AF_INET6) { |
| src6 = (struct sockaddr_in6 *)(srcext + 1); |
| ps.src = (uint32_t *)&src6->sin6_addr; |
| } else { |
| ps.src = (uint32_t *)&src->sin_addr; |
| } |
| ASSERT(dstext == NULL || src->sin_family == dst->sin_family); |
| } |
| |
| ASSERT(ps.af != (sa_family_t)-1); |
| |
| if (dstid != NULL) { |
| /* |
| * NOTE: May need to copy string in the future |
| * if the inbound keysock message disappears for some strange |
| * reason. |
| */ |
| ps.didstr = (char *)(dstid + 1); |
| ps.didtype = dstid->sadb_ident_type; |
| } |
| |
| if (srcid != NULL) { |
| /* |
| * NOTE: May need to copy string in the future |
| * if the inbound keysock message disappears for some strange |
| * reason. |
| */ |
| ps.sidstr = (char *)(srcid + 1); |
| ps.sidtype = srcid->sadb_ident_type; |
| } |
| |
| if (kmc != NULL) |
| ps.kmproto = kmc->sadb_x_kmc_proto; |
| |
| /* |
| * 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); |
| |
| if (ps.mq != NULL) |
| sadb_drain_torchq(ip_q, ps.mq); |
| |
| 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(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; |
| |
| ASSERT(MUTEX_HELD(&head->isaf_lock)); |
| |
| mutex_enter(&entry->ipsa_lock); |
| |
| if ((entry->ipsa_state != ps->sadb_sa_state) || |
| ((ps->src != NULL) && |
| !IPSA_ARE_ADDR_EQUAL(entry->ipsa_srcaddr, ps->src, ps->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(ps->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(ps->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, B_FALSE, &ps->mq); |
| 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, |
| B_FALSE, &ps->mq); |
| } |
| mutex_exit(&inbound_bucket->isaf_lock); |
| } |
| |
| /* |
| * 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) |
| { |
| sadb_sa_t *assoc = (sadb_sa_t *)ksi->ks_in_extv[SADB_EXT_SA]; |
| sadb_address_t *srcext = |
| (sadb_address_t *)ksi->ks_in_extv[SADB_EXT_ADDRESS_SRC]; |
| sadb_address_t *dstext = |
| (sadb_address_t *)ksi->ks_in_extv[SADB_EXT_ADDRESS_DST]; |
| ipsa_t *echo_target = NULL; |
| ipsap_t *ipsapp; |
| mblk_t *torchq = NULL; |
| uint_t error = 0; |
| |
| if (assoc == NULL) { |
| *diagnostic = SADB_X_DIAGNOSTIC_MISSING_SA; |
| return (EINVAL); |
| } |
| |
| if (sadb_msg_type == SADB_X_DELPAIR_STATE) { |
| struct sockaddr_in *src; |
| struct sockaddr_in6 *src6; |
| struct sadb_purge_state ps; |
| |
| if (srcext == NULL) { |
| *diagnostic = SADB_X_DIAGNOSTIC_MISSING_SRC; |
| return (EINVAL); |
| } |
| ps.src = NULL; |
| ps.mq = NULL; |
| src = (struct sockaddr_in *)(srcext + 1); |
| ps |