blob: eff4dfc0f8bbedbe52dcd808ee65044582943022 [file] [log] [blame]
/*
* 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