/*
* 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
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2010 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#include <sys/sysmacros.h>
#include <net/pfpolicy.h>
#include <inet/ipsec_info.h>
#include <inet/ipsec_impl.h>
#include <inet/ipsec_impl.h>
/*
* Table of ND variables supported by ipsecah. These are loaded into
* ipsecah_g_nd in ipsecah_init_nd.
*/
/* min max value name */
{ 0, 3, 0, "ipsecah_debug"},
{ 1, 10, 1, "ipsecah_reap_delay"},
{ 1, 300, 15, "ipsecah_acquire_timeout"},
{ 1, 1800, 90, "ipsecah_larval_timeout"},
/* Default lifetime values for ACQUIRE messages. */
{ 0, 0xffffffffU, 0, "ipsecah_default_soft_bytes"},
{ 0, 0xffffffffU, 0, "ipsecah_default_hard_bytes"},
{ 0, 0xffffffffU, 24000, "ipsecah_default_soft_addtime"},
{ 0, 0xffffffffU, 28800, "ipsecah_default_hard_addtime"},
{ 0, 0xffffffffU, 0, "ipsecah_default_soft_usetime"},
{ 0, 0xffffffffU, 0, "ipsecah_default_hard_usetime"},
{ 0, 1, 0, "ipsecah_log_unknown_spi"},
};
/* NOTE: != 0 instead of > 0 so lint doesn't complain. */
/*
* XXX This is broken. Padding should be determined dynamically
* depending on the ICV size and IP version number so that the
* total AH header size is a multiple of 32 bits or 64 bits
* for V4 and V6 respectively. For 96bit ICVs we have no problems.
* Anything different from that, we need to fix our code.
*/
/*
* Helper macro. Avoids a call to msgdsize if there is only one
* mblk in the chain.
*/
boolean_t, ipsecah_stack_t *);
boolean_t, ipsecah_stack_t *);
static int ipsecah_close(queue_t *);
cred_t *);
};
};
};
};
static int ah_kstat_update(kstat_t *, int);
static boolean_t
{
return (B_FALSE);
KI(keysock_in);
return (B_TRUE);
}
static int
{
return (EIO);
if (rw == KSTAT_WRITE)
return (EACCES);
return (-1);
return (-1);
}
return (0);
}
/*
* Don't have to lock ipsec_age_interval, as only one thread will access it at
* a time, because I control the one function that does a qtimeout() on
* ah_pfkey_q.
*/
static void
{
}
/*
* Get an AH NDD parameter.
*/
/* ARGSUSED */
static int
queue_t *q;
{
return (0);
}
/*
* This routine sets an NDD variable in a ipsecahparam_t structure.
*/
/* ARGSUSED */
static int
queue_t *q;
char *value;
{
/*
* Fail the request if the new value does not lie within the
* required bounds.
*/
return (EINVAL);
}
/* Set the new value */
return (0);
}
/*
* Using lifetime NDD variables, fill in an extended combination's
* lifetime information.
*/
void
{
}
/*
* Initialize things for AH at module load time.
*/
ipsecah_ddi_init(void)
{
/*
* We want to be informed each time a stack is created or
* destroyed in the kernel, so we can maintain the
* set of ipsecah_stack_t's.
*/
return (B_TRUE);
}
/*
* Walk through the param array specified registering each element with the
* named dispatch handler.
*/
static boolean_t
{
ahp->ipsecah_param_name[0]) {
return (B_FALSE);
}
}
}
return (B_TRUE);
}
/*
* Initialize things for AH for each stack instance
*/
static void *
{
return (ahstack);
}
/*
* Destroy things for AH at module unload time.
*/
void
ipsecah_ddi_destroy(void)
{
}
/*
* Destroy things for AH for one stack... Never called?
*/
static void
{
}
}
/*
* AH module open routine, which is here for keysock plumbing.
* Keysock is pushed over {AH,ESP} which is an artifact from the Bad Old
* Days of export control, and fears that ESP would not be allowed
* to be shipped at all by default. Eventually, keysock should
* either access AH and ESP via modstubs or krtld dependencies, or
*/
/* ARGSUSED */
static int
{
return (EPERM);
return (0); /* Re-open of an already open instance. */
return (EINVAL);
qprocson(q);
return (0);
}
/*
* AH module close routine.
*/
static int
{
/*
* Clean up q_ptr, if needed.
*/
qprocsoff(q);
/* Keysock queue check is safe, because of OCEXCL perimeter. */
if (q == ahstack->ah_pfkey_q) {
("ipsecah_close: Ummm... keysock is closing AH.\n"));
/* Detach qtimeouts. */
}
return (0);
}
/*
* Construct an SADB_REGISTER message with the current algorithms.
*/
static boolean_t
{
/* Allocate the KEYSOCK_OUT. */
ah0dbg(("ah_register_out: couldn't allocate mblk.\n"));
return (B_FALSE);
}
}
}
/*
* Allocate the PF_KEY message that follows KEYSOCK_OUT.
* The alg reader lock needs to be held while allocating
* the variable part (i.e. the algorithms) of the message.
*/
/*
* Return only valid algorithms, so the number of algorithms
* to send up may be less than the number of algorithm entries
* in the table.
*/
for (num_aalgs = 0, i = 0; i < IPSEC_MAX_ALGS; i++)
num_aalgs++;
/*
* Fill SADB_REGISTER message's algorithm descriptors. Hold
* down the lock while filling it.
*/
if (num_aalgs != 0) {
}
return (B_FALSE);
}
if (num_aalgs != 0) {
numalgs_snap = 0;
for (i = 0;
i++) {
continue;
saalg->sadb_alg_ivlen = 0;
authalgs[i]->alg_increment;
/* For now, salt is meaningless in AH. */
numalgs_snap++;
saalg++;
}
#ifdef DEBUG
/*
* Reality check to make sure I snagged all of the
* algorithms.
*/
for (; i < IPSEC_MAX_ALGS; i++)
"ah_register_out()! Missed #%d.\n", i);
#endif /* DEBUG */
}
}
/* Now fill the restof the SADB_REGISTER message. */
samsg->sadb_msg_errno = 0;
samsg->sadb_msg_reserved = 0;
/*
* from me over a new alg., I could give two hoots about sequence.
*/
if (num_aalgs != 0) {
}
else {
}
return (rc);
}
/*
* Invoked when the algorithm table changes. Causes SADB_REGISTER
* messages continaining the current list of algorithms to be
* sent up to the AH listeners.
*/
void
{
/*
* Time to send a PF_KEY SADB_REGISTER message to AH listeners
* everywhere. (The function itself checks for NULL ah_pfkey_q.)
*/
}
/*
* Stub function that taskq_dispatch() invokes to take the mblk (in arg)
* and send it into AH and IP again.
*/
static void
{
/* The ill or ip_stack_t disappeared on us */
goto done;
}
done:
}
/*
* Restart ESP after the SA has been added.
*/
static void
{
return;
/*
* Either it failed or is pending. In the former case
* ipIfStatsInDiscards was increased.
*/
return;
}
}
/*
* Now that weak-key passed, actually ADD the security association, and
* send back a reply ADD message.
*/
static int
{
int rc;
int error;
/*
* Locate the appropriate table(s).
*/
&sq, diagnostic);
if (error)
return (error);
/*
* Use the direction flags provided by the KMD to determine
* if the inbound or outbound table should be the primary
* for this SA. If these flags were absent then make this
* decision based on the addresses.
*/
is_inbound = B_TRUE;
} else {
}
}
/*
* The KMD did not set a direction flag, determine which
* table to insert the SA into based on addresses.
*/
switch (ksi->ks_in_dsttype) {
case KS_IN_ADDR_MBCAST:
/* FALLTHRU */
/*
* If the source address is either one of mine, or unspecified
* (which is best summed up by saying "not 'not mine'"),
* then the association is potentially bi-directional,
* in that it can be used for inbound traffic and outbound
* traffic. The best example of such and SA is a multicast
* SA (which allows me to receive the outbound traffic).
*/
case KS_IN_ADDR_ME:
is_inbound = B_TRUE;
break;
/*
* If the source address literally not mine (either
* unspecified or not mine), then this SA may have an
* address that WILL be mine after some configuration.
* We pay the price for this by making it a bi-directional
* SA.
*/
case KS_IN_ADDR_NOTME:
}
break;
default:
return (EINVAL);
}
}
/*
* Find a ACQUIRE list entry if possible. If we've added an SA that
* suits the needs of an ACQUIRE list entry, we can eliminate the
* ACQUIRE list entry and transmit the enqueued packets. Use the
* high-bit of the sequence number to queue it. Key off destination
* addr, and change acqrec's state.
*/
/*
* Q: I only check sequence. Should I check dst?
* A: Yes, check dest because those are the packets
* that are queued up.
*/
break;
}
/*
* AHA! I found an ACQUIRE record for this SA.
* Grab the msg list, and free the acquire record.
* I already am holding the lock for this record,
* so all I have to do is free it.
*/
}
}
/*
* Find PF_KEY message, and see if I'm an update. If so, find entry
* in larval list (if there).
*/
}
ah0dbg(("Larval update, but larval disappeared.\n"));
return (ESRCH);
} /* Else sadb_common_add unlinks it for me! */
}
/*
* Hold again, because sadb_common_add() consumes a reference,
* and we don't want to clear_lpkt() without a reference.
*/
}
if (rc == 0) {
lpkt, TQ_NOSLEEP);
}
}
}
/*
* How much more stack will I create with all of these
* ah_outbound_*() calls?
*/
/* Handle the packets queued waiting for the SA */
/*
* Extract the ip_xmit_attr_t from the first mblk.
* Verifies that the netstack and ill is still around; could
* have vanished while iked was doing its work.
* disappear until we do the nce_refrele in ixa_cleanup.
*/
&ahstack->ah_dropper);
} else if (rc != 0) {
&ahstack->ah_dropper);
} else {
}
ixa_cleanup(&ixas);
}
return (rc);
}
/*
* Process one of the queued messages (from ipsacq_mp) once the SA
* has been added.
*/
static void
{
&ahstack->ah_dropper);
return;
}
return;
}
/*
* routine eventually.
*/
static int
{
/* We don't need sockaddr_in6 for now. */
/* I need certain extensions present for an ADD message. */
return (EINVAL);
}
return (EINVAL);
}
return (EINVAL);
}
return (EINVAL);
}
return (EINVAL);
}
return (EINVAL);
}
/* Sundry ADD-specific reality checks. */
return (EINVAL);
}
return (EINVAL);
}
return (EINVAL);
}
return (EINVAL);
/* Stuff I don't support, for now. XXX Diagnostic? */
return (EOPNOTSUPP);
if (!is_system_labeled())
return (EOPNOTSUPP);
}
if (!is_system_labeled())
return (EOPNOTSUPP);
}
/*
* XXX Policy : I'm not checking identities at this time, but
* if I did, I'd do them here, before I sent the weak key
* check up to the algorithm.
*/
/* verify that there is a mapping for the specified algorithm */
assoc->sadb_sa_auth));
return (EINVAL);
}
/* sanity check key sizes */
return (EINVAL);
}
/* check key and fix parity if needed */
diagnostic) != 0) {
return (EINVAL);
}
diagnostic, ahstack));
}
/* Refactor me */
/*
* Update a security association. Updates come in two varieties. The first
* is an update of lifetimes on a non-larval SA. The second is an update of
* a larval SA, which ends up looking a lot more like an add.
*/
static int
{
int rcode;
return (EINVAL);
}
(rcode != 0)) {
return (rcode);
}
return (rcode);
}
/* Refactor me */
/*
* Delete a security association. This is REALLY likely to be code common to
* both AH and ESP. Find the association, then unlink it.
*/
static int
{
else {
return (EINVAL);
}
}
}
/* Refactor me */
/*
* Convert the entire contents of all of AH's SA tables into PF_KEY SADB_DUMP
* messages.
*/
static void
{
int error;
/*
* Dump each fanout, bailing if error is non-zero.
*/
if (error != 0)
goto bail;
bail:
}
/*
* First-cut reality check for an inbound PF_KEY message.
*/
static boolean_t
{
int diagnostic;
return (B_TRUE);
}
goto badmsg;
}
goto badmsg;
}
goto badmsg;
}
return (B_FALSE); /* False ==> no failures */
return (B_TRUE); /* True ==> failures */
}
/*
* AH parsing of PF_KEY messages. Keysock did most of the really silly
* error cases. What I receive is a fully-formed, syntactically legal
* PF_KEY message. I then need to check semantics...
*
* This code may become common to AH and ESP. Stay tuned.
*
* I also make the assumption that db_ref's are cool. If this assumption
* is wrong, this means that someone other than keysock or me has been
* mucking with PF_KEY messages.
*/
static void
{
int error;
/*
* If applicable, convert unspecified AF_INET6 to unspecified
* AF_INET.
*/
ahstack->ipsecah_netstack) ||
return;
}
switch (samsg->sadb_msg_type) {
case SADB_ADD:
if (error != 0) {
}
/* else ah_add_sa() took care of things. */
break;
case SADB_DELETE:
case SADB_X_DELPAIR:
case SADB_X_DELPAIR_STATE:
if (error != 0) {
}
/* Else ah_del_sa() took care of things. */
break;
case SADB_GET:
if (error != 0) {
}
/* Else sadb_get_sa() took care of things. */
break;
case SADB_FLUSH:
break;
case SADB_REGISTER:
/*
* Hmmm, let's do it! Check for extensions (there should
* be none), extract the fields, call ah_register_out(),
* then either free or report an error.
*
* Keysock takes care of the PF_KEY bookkeeping for this.
*/
} else {
/*
* Only way this path hits is if there is a memory
* failure. It will not return B_FALSE because of
* lack of ah_pfkey_q if I am in wput().
*/
}
break;
case SADB_UPDATE:
case SADB_X_UPDATEPAIR:
/*
* Find a larval, if not there, find a full one and get
* strict.
*/
if (error != 0) {
}
/* else ah_update_sa() took care of things. */
break;
case SADB_GETSPI:
/*
* Reserve a new larval entry.
*/
break;
case SADB_ACQUIRE:
/*
* most likely an error. Inbound ACQUIRE messages should only
* have the base header.
*/
break;
case SADB_DUMP:
/*
* Dump all entries.
*/
/* ah_dump will take care of the return message, etc. */
break;
case SADB_EXPIRE:
/* Should never reach me. */
break;
default:
break;
}
}
/*
* Handle case where PF_KEY says it can't find a keysock for one of my
* ACQUIRE messages.
*/
static void
{
return;
}
/*
* If keysock can't find any registered, delete the acquire record
* immediately, and handle errors.
*/
/*
* Use the write-side of the ah_pfkey_q
*/
}
}
/*
* AH module write put routine.
*/
static void
{
/* NOTE: Each case must take care of freeing or passing mp. */
case M_CTL:
/* Not big enough message. */
break;
}
switch (ii->ipsec_info_type) {
case KEYSOCK_OUT_ERR:
break;
case KEYSOCK_IN:
/* Parse the message. */
break;
case KEYSOCK_HELLO:
break;
default:
ii->ipsec_info_type));
break;
}
break;
case M_IOCTL:
case ND_SET:
case ND_GET:
return;
} else {
}
/* FALLTHRU */
default:
/* We really don't support any other ioctls, do we? */
/* Return EINVAL */
return;
}
default:
("Got default message, type %d, passing to IP.\n",
}
}
/* Refactor me */
/*
* Updating use times can be tricky business if the ipsa_haspeer flag is
* set. This function is called once in an SA's lifetime.
*
* Caller has to REFRELE "assoc" which is passed in. This function has
* to REFRELE any peer SA that is obtained.
*/
static void
{
int outhash;
/* No peer? No problem! */
if (!assoc->ipsa_haspeer) {
return;
}
/*
* Otherwise, we want to grab both the original assoc and its peer.
* There might be a race for this, but if it's a real race, the times
* will be out-of-synch by at most a second, and since our time
* granularity is a second, this won't be a problem.
*
* If we need tight synchronization on the peer SA, then we need to
* reconsider.
*/
if (isv6) {
} else {
}
if (inbound) {
if (isv6)
else
/* Q: Do we wish to set haspeer == B_FALSE? */
ah0dbg(("ah_set_usetime: "
"can't find peer for inbound.\n"));
return;
}
} else {
/* Q: Do we wish to set haspeer == B_FALSE? */
ah0dbg(("ah_set_usetime: "
"can't find peer for outbound.\n"));
return;
}
}
/* Update usetime on both. */
/*
* REFRELE any peer SA.
*
* Because of the multi-line macro nature of IPSA_REFRELE, keep
* them in { }.
*/
if (inbound) {
} else {
}
}
/* Refactor me */
/*
* Add a number of bytes to what the SA has protected so far. Return
* B_TRUE if the SA can still protect that many bytes.
*
* Caller must REFRELE the passed-in assoc. This function must REFRELE
* any obtained peer SA.
*/
static boolean_t
{
int outhash;
/* No peer? No problem! */
if (!assoc->ipsa_haspeer) {
B_TRUE));
}
/*
* Otherwise, we want to grab both the original assoc and its peer.
* There might be a race for this, but if it's a real race, two
* expire messages may occur. We limit this by only sending the
* expire message on one of the peers, we'll pick the inbound
* arbitrarily.
*
* If we need tight synchronization on the peer SA, then we need to
* reconsider.
*/
if (isv6) {
} else {
}
if (inbound) {
if (isv6)
else
/* Q: Do we wish to set haspeer == B_FALSE? */
ah0dbg(("ah_age_bytes: "
"can't find peer for inbound.\n"));
}
} else {
/* Q: Do we wish to set haspeer == B_FALSE? */
ah0dbg(("ah_age_bytes: "
"can't find peer for outbound.\n"));
}
}
/*
* REFRELE any peer SA.
*
* Because of the multi-line macro nature of IPSA_REFRELE, keep
* them in { }.
*/
if (inbound) {
} else {
}
}
/*
* Perform the really difficult work of inserting the proposed situation.
* Called while holding the algorithm lock.
*/
static void
netstack_t *ns)
{
/*
* Based upon algorithm properties, and what-not, prioritize a
* proposal, based on the ordering of the AH algorithms in the
* alternatives in the policy rule or socket that was placed
* in the acquire record.
*/
continue;
[prot->ipp_auth_alg];
continue;
/* XXX check aalg for duplicates??.. */
comb->sadb_comb_flags = 0;
comb->sadb_comb_reserved = 0;
comb->sadb_comb_encrypt = 0;
/*
* The following may be based on algorithm
* properties, but in the meantime, we just pick
* some good, sensible numbers. Key mgmt. can
* (and perhaps should) be the place to finalize
* such decisions.
*/
/*
* No limits on allocations, since we really don't
* support that concept currently.
*/
/*
* These may want to come from policy rule..
*/
if (--combs == 0)
return; /* out of space.. */
comb++;
}
}
/*
* Prepare and actually send the SADB_ACQUIRE message to PF_KEY.
*/
static void
{
return;
}
/* Set up ACQUIRE. */
ns->netstack_ipsec);
ah0dbg(("sadb_setup_acquire failed.\n"));
return;
}
/* Insert proposal here. */
/*
* Must mutex_exit() before sending PF_KEY message up, in
* order to avoid recursive mutex_enter() if there are no registered
* listeners.
*
* Once I've sent the message, I'm cool anyway.
*/
}
}
/* Refactor me */
/*
* Handle the SADB_GETSPI message. Create a larval SA.
*/
static void
{
/*
* Randomly generate a proposed SPI value.
*/
if (cl_inet_getspi != NULL) {
} else {
sizeof (uint32_t));
}
ksi->ks_in_serial);
return;
ksi->ks_in_serial);
return;
}
/*
* XXX - We may randomly collide. We really should recover from this.
* Unfortunately, that could require spending way-too-much-time
* in here. For now, let the user retry.
*/
} else {
}
/*
* Check for collisions (i.e. did sadb_getspi() return with something
* that already exists?).
*
* Try outbound first. Even though SADB_GETSPI is traditionally
* for inbound SAs, you never know what a user might do.
*/
}
/*
* I don't have collisions elsewhere!
*/
} else {
/*
* sadb_insertassoc() also checks for collisions, so
* if there's a colliding larval entry, rc will be set
* to EEXIST.
*/
}
/*
* Can exit outbound mutex. Hold inbound until we're done with
* newbie.
*/
if (rc != 0) {
return;
}
/* Can write here because I'm still holding the bucket lock. */
/*
* Construct successful return message. We have one thing going
* for us in PF_KEY v2. That's the fact that
* sizeof (sadb_spirange_t) == sizeof (sadb_sa_t)
*/
/* Convert KEYSOCK_IN to KEYSOCK_OUT. */
/*
* Can safely putnext() to ah_pfkey_q, because this is a turnaround
* from the ah_pfkey_q.
*/
}
/*
* IPv6 sends up the ICMP errors for validation and the removal of the AH
* header.
* If succesful, the mp has been modified to not include the AH header so
* that the caller can fanout to the ULP's icmp error handler.
*/
static mblk_t *
{
/*
* Eat the cost of a pullupmsg() for now. It makes the rest of this
* code far less convoluted.
*/
&nexthdrp) ||
&ahstack->ah_dropper);
return (NULL);
}
&ahstack->ah_dropper);
return (NULL);
}
if (ahstack->ipsecah_log_unknown_spi) {
"Bad ICMP message - No association for the "
"attached AH header whose spi is 0x%x, "
"sender is 0x%x\n",
}
&ahstack->ah_dropper);
return (NULL);
}
/*
* There seems to be a valid association. If there is enough of AH
* header remove it, otherwise bail. One could check whether it has
* complete AH header plus 8 bytes but it does not make sense if an
* icmp error is returned for ICMP messages e.g ICMP time exceeded,
* that are being sent up. Let the caller figure out.
*
* NOTE: ah_length is the number of 32 bit words minus 2.
*/
&ahstack->ah_dropper);
return (NULL);
}
return (mp);
}
/*
* IP sends up the ICMP errors for validation and the removal of
* the AH header.
* If succesful, the mp has been modified to not include the AH header so
* that the caller can fanout to the ULP's icmp error handler.
*/
static mblk_t *
{
int iph_hdr_length;
int hdr_length;
int ah_length;
int alloc_size;
/*
* See if we have enough to locate the SPI
*/
"ICMP error: Small AH header\n");
&ahstack->ah_dropper);
return (NULL);
}
}
if (ahstack->ipsecah_log_unknown_spi) {
"Bad ICMP message - No association for the "
"attached AH header whose spi is 0x%x, "
"sender is 0x%x\n",
}
&ahstack->ah_dropper);
return (NULL);
}
/*
* There seems to be a valid association. If there
* is enough of AH header remove it, otherwise remove
* as much as possible and send it back. One could check
* whether it has complete AH header plus 8 bytes but it
* does not make sense if an icmp error is returned for
* ICMP messages e.g ICMP time exceeded, that are being
* sent up. Let the caller figure out.
*
* NOTE: ah_length is the number of 32 bit words minus 2.
*/
/*
* There is nothing to pullup. Just remove as
* much as possible. This is a common case for
* IPV4.
*/
hdr_length));
goto done;
}
/* Pullup the full ah header */
/*
* pullupmsg could have failed if there was not
* enough to pullup or memory allocation failed.
* We tried hard, give up now.
*/
&ahstack->ah_dropper);
return (NULL);
}
}
done:
/*
* Remove the AH header and change the protocol.
* Don't update the spi fields in the ip_recv_attr_t
* as we are called just to validate the
* message attached to the ICMP message.
*
* If we never pulled up since all of the message
* is in one single mblk, we can't remove the AH header
* by just setting the b_wptr to the beginning of the
* AH header. We need to allocate a mblk that can hold
* up until the inner IP header and copy them.
*/
&ahstack->ah_dropper);
return (NULL);
}
/*
* Skip whatever we have copied and as much of AH header
* possible. If we still have something left in the original
* message, tag on.
*/
} else {
}
ipha->ipha_hdr_checksum = 0;
return (mp1);
}
/*
* IP calls this to validate the ICMP errors that
* we got from the network.
*/
mblk_t *
{
else
}
static int
{
/*
* Copy the next header and hdr ext. len of the HOP-by-HOP
* and Destination option.
*/
ehdrlen -= 2;
/*
* Now handle all the TLV encoded options.
*/
while (ehdrlen != 0) {
if (opt_type == IP6OPT_PAD1) {
optlen = 1;
} else {
if (ehdrlen < 2)
goto bad_opt;
goto bad_opt;
}
} else {
if (optlen == 1) {
*pi_opt = 0;
} else {
/*
* Copy the type and data length fields.
* Zero the option data by skipping
* option type and option data len
* fields.
*/
}
}
}
return (0);
return (-1);
}
/*
* Construct a pseudo header for AH, processing all the options.
*
* oip6h is the IPv6 header of the incoming or outgoing packet.
* ip6h is the pointer to the pseudo headers IPV6 header. All
* the space needed for the options have been allocated including
* the AH header.
*
* If copy_always is set, all the options that appear before AH are copied
* blindly without checking for IP6OPT_MUTABLE. This is used by
* ah_auth_out_done(). Please refer to that function for details.
*
* NOTE :
*
* * AH header is never copied in this function even if copy_always
* is set. It just returns the ah_offset - offset of the AH header
* and the caller needs to do the copying. This is done so that we
* don't have pass extra arguments e.g. SA etc. and also,
* it is not needed when ah_auth_out_done is calling this function.
*/
static uint_t
{
int ehdrlen;
int ret;
/*
* In the outbound case for source route, ULP has already moved
* the first hop, which is now in ip6_dst. We need to re-arrange
* the header to make it look like how it would appear in the
* receiver i.e
*
* Because of ip_massage_options_v6 the header looks like
* this :
*
* ip6_src = S, ip6_dst = I1. followed by I2,I3,D.
*
* When it reaches the receiver, it would look like
*
* ip6_src = S, ip6_dst = D. followed by I1,I2,I3.
*
* NOTE : We assume that there are no problems with the options
* as IP should have already checked this.
*/
/*
* We set the prev_nexthdr properly in the pseudo header.
* After we finish authentication and come back from the
* algorithm module, pseudo header will become the real
* IP header.
*/
/* Assume IP has already stripped it */
for (;;) {
switch (nexthdr) {
case IPPROTO_HOPOPTS:
/*
* Return a zero offset indicating error if there
* was error.
*/
if (ret == -1)
return (0);
break;
case IPPROTO_ROUTING:
if (!copy_always && outbound) {
int i, left;
/*
* First eight bytes except seg_left
* does not change en route.
*/
prthdr->ip6r0_segleft = 0;
/*
* First address has been moved to
* the destination address of the
* ip header by ip_massage_options_v6.
* And the real destination address is
* in the last address part of the
* option.
*/
} else {
}
break;
case IPPROTO_DSTOPTS:
/*
* Destination options are tricky. If there is
* a terminal (e.g. non-IPv6-extension) header
* following the destination options, don't
* reset prev_nexthdr or advance the AH insertion
* point and just treat this as a terminal header.
*
* If this is an inbound packet, just deal with
* it as is.
*/
/*
* XXX I hope common-subexpression elimination
* saves us the double-evaluate.
*/
goto terminal_hdr;
/*
* Return a zero offset indicating error if there
* was error.
*/
if (ret == -1)
return (0);
break;
case IPPROTO_AH:
/*
* Be conservative in what you send. We shouldn't
* see two same-scoped AH's in one packet.
* (Inner-IP-scoped AH will be hit by terminal
* header of IP or IPv6.)
*/
default:
}
}
/* NOTREACHED */
}
static boolean_t
{
int i;
/*
* Padding :
*
* 1) Authentication data may have to be padded
* before ICV calculation if ICV is not a multiple
* of 64 bits. This padding is arbitrary and transmitted
* with the packet at the end of the authentication data.
* Payload length should include the padding bytes.
*
* 2) Explicit padding of the whole datagram may be
* required by the algorithm which need not be
* transmitted. It is assumed that this will be taken
* care by the algorithm module.
*/
if (inbound_ah == NULL) {
/* Outbound AH datagram. */
phdr_ah->ah_reserved = 0;
/*
* XXX We have replay counter wrapping. We probably
* want to nuke this SA (and its peer).
*/
"Outbound AH SA (0x%x), dst %s has wrapped "
/* Caller will free phdr_mp and return NULL. */
return (B_FALSE);
}
if (ah_data_sz != ah_align_sz) {
for (i = 0; i < (ah_align_sz - ah_data_sz); i++) {
}
}
} else {
/* Inbound AH datagram. */
phdr_ah->ah_reserved = 0;
if (ah_data_sz != ah_align_sz) {
sizeof (ah_t) + ah_data_sz);
for (i = 0; i < (ah_align_sz - ah_data_sz); i++) {
}
}
}
return (B_TRUE);
}
/*
* Called upon failing the inbound ICV check. The message passed as
* argument is freed.
*/
static void
{
int af;
void *addr;
if (isv4) {
} else {
}
/*
* Log the event. Don't print to the console, block
* potential denial-of-service attack.
*/
"AH Authentication failed spi %x, dst_addr %s",
&ahstack->ah_dropper);
}
/*
* Kernel crypto framework callback invoked after completion of async
* crypto requests for outbound packets.
*/
static void
{
/*
* First remove the ipsec_crypto_t mblk
* Note that we need to ipsec_free_crypto_data(mp) once done with ic.
*/
/*
* Extract the ip_xmit_attr_t from the first mblk.
* Verifies that the netstack and ill is still around; could
* have vanished while kEf was doing its work.
* disappear until we do the nce_refrele in ixa_cleanup.
*/
}
goto done;
}
if (status == CRYPTO_SUCCESS) {
goto done;
} else {
/* Outbound shouldn't see invalid MAC */
("ah_kcf_callback_outbound: crypto failed with 0x%x\n",
status));
&ahstack->ah_dropper);
}
done:
ixa_cleanup(&ixas);
(void) ipsec_free_crypto_data(mp);
}
/*
* Kernel crypto framework callback invoked after completion of async
* crypto requests for inbound packets.
*/
static void
{
/*
* First remove the ipsec_crypto_t mblk
* Note that we need to ipsec_free_crypto_data(mp) once done with ic.
*/
/*
* Extract the ip_xmit_attr_t from the first mblk.
* Verifies that the netstack and ill is still around; could
* have vanished while kEf was doing its work.
*/
/* The ill or ip_stack_t disappeared on us */
goto done;
}
if (status == CRYPTO_SUCCESS) {
goto done;
/* finish IPsec processing */
} else if (status == CRYPTO_INVALID_MAC) {
} else {
("ah_kcf_callback_inbound: crypto failed with 0x%x\n",
status));
&ahstack->ah_dropper);
}
done:
(void) ipsec_free_crypto_data(mp);
}
/*
* Invoked on kernel crypto failure during inbound and outbound processing.
*/
static void
{
&ahstack->ah_dropper);
if (is_inbound)
else
}
/*
* Helper macros for the ah_submit_req_{inbound,outbound}() functions.
*/
/*
* A statement-equivalent macro, _cr MUST point to a modifiable
* crypto_call_req_t.
*/
}
}
/*
* Submit an inbound packet for processing by the crypto framework.
*/
static mblk_t *
{
int kef_rc;
if (force) {
/* We are doing asynch; allocate mblks to hold state */
return (NULL);
}
} else {
/*
* If we know we are going to do sync then ipsec_crypto_t
* should be on the stack.
*/
}
/* init arguments for the crypto framework */
phdr_mp);
sizeof (ah_t));
/* call KEF to do the MAC operation */
switch (kef_rc) {
case CRYPTO_SUCCESS:
if (force) {
/* Free mp after we are done with ic */
(void) ip_recv_attr_free_mblk(mp);
}
return (phdr_mp);
case CRYPTO_QUEUED:
/* ah_kcf_callback_inbound() will be invoked on completion */
return (NULL);
case CRYPTO_INVALID_MAC:
/* Free mp after we are done with ic */
/* phdr_mp was passed to ip_drop_packet */
if (force) {
(void) ip_recv_attr_free_mblk(mp);
}
return (NULL);
}
if (force) {
}
/* phdr_mp was passed to ip_drop_packet */
return (NULL);
}
/*
* Submit an outbound packet for processing by the crypto framework.
*/
static mblk_t *
{
int kef_rc;
if (force) {
/* We are doing asynch; allocate mblks to hold state */
return (NULL);
}
} else {
/*
* If we know we are going to do sync then ipsec_crypto_t
* should be on the stack.
*/
}
/* init arguments for the crypto framework */
phdr_mp);
/* call KEF to do the MAC operation */
switch (kef_rc) {
case CRYPTO_SUCCESS:
if (force) {
/* Free mp after we are done with ic */
(void) ip_xmit_attr_free_mblk(mp);
}
return (phdr_mp);
case CRYPTO_QUEUED:
/* ah_kcf_callback_outbound() will be invoked on completion */
return (NULL);
}
if (force) {
}
/* phdr_mp was passed to ip_drop_packet */
return (NULL);
}
/*
* This function constructs a pseudo header by looking at the IP header
* and options if any. This is called for both outbound and inbound,
* before computing the ICV.
*/
static mblk_t *
{
int option_length;
int hdr_size;
/*
* Allocate space for the authentication data also. It is
* useful both during the ICV calculation where we need to
* feed in zeroes and while sending the datagram back to IP
* where we will be using the same space.
*
* We need to allocate space for padding bytes if it is not
* a multiple of IPV6_PADDING_ALIGN.
*
* In addition, we allocate space for the ICV computed by
* the kernel crypto framework, saving us a separate kmem
* allocation down the road.
*/
/* This was not included in ipsec_ah_get_hdr_size_v6() */
/*
* We have post-AH header options in a separate mblk,
* a pullup is required.
*/
return (NULL);
}
return (NULL);
}
/*
* Form the basic IP header first. Zero out the header
* so that the mutable fields are zeroed out.
*/
if (outbound) {
/*
* Include the size of AH and authentication data.
* This is how our recipient would compute the
* authentication data. Look at what we do in the
* inbound case below.
*/
sizeof (ah_t) + ah_align_sz);
} else {
}
if (option_length == 0) {
/* Form the AH header */
} else {
/* option_length does not include the AH header's size */
if (ah_offset == 0) {
return (NULL);
}
}
/*
* Returning NULL will tell the caller to
* IPSA_REFELE(), free the memory, etc.
*/
return (NULL);
}
if (!outbound)
return (phdr_mp);
}
/*
* This function constructs a pseudo header by looking at the IP header
* and options if any. This is called for both outbound and inbound,
* before computing the ICV.
*/
static mblk_t *
{
int size;
int ip_hdr_length;
#ifdef _BIG_ENDIAN
#else
#endif
/*
* Allocate space for the authentication data also. It is
* useful both during the ICV calculation where we need to
* feed in zeroes and while sending the datagram back to IP
* where we will be using the same space.
*
* We need to allocate space for padding bytes if it is not
* a multiple of IPV4_PADDING_ALIGN.
*
* In addition, we allocate space for the ICV computed by
* the kernel crypto framework, saving us a separate kmem
* allocation down the road.
*/
if (V_HLEN != IP_SIMPLE_HDR_VERSION) {
option_length <<= 2;
size += option_length;
}
return (NULL);
}
/*
* Form the basic IP header first.
*/
ipha->ipha_type_of_service = 0;
if (outbound) {
/*
* Include the size of AH and authentication data.
* This is how our recipient would compute the
* authentication data. Look at what we do in the
* inbound case below.
*/
sizeof (ah_t) + ah_align_sz);
} else {
}
ipha->ipha_hdr_checksum = 0;
/*
* If there is no option to process return now.
*/
if (V_HLEN == IP_SIMPLE_HDR_VERSION) {
/* Form the AH header */
goto ah_hdr;
}
/*
* We have options. In the outbound case for source route,
* ULP has already moved the first hop, which is now in
* ipha_dst. We need the final destination for the calculation
* of authentication data. And also make sure that mutable
* and experimental fields are zeroed out in the IP options.
*/
switch (optval) {
case IPOPT_EXTSEC:
case IPOPT_COMSEC:
case IPOPT_RA:
case IPOPT_SDMDD:
case IPOPT_SECURITY:
/*
* These options are Immutable, leave them as-is.
* Note that IPOPT_NOP is also Immutable, but it
* was skipped by ipoptp_next() and thus remains
* intact in the header.
*/
break;
case IPOPT_SSRR:
case IPOPT_LSRR:
goto bad_ipv4opt;
/*
* These two are mutable and will be zeroed, but
* first get the final destination.
*/
/*
* If one of the conditions is true, it means
* end of options and dst already has the right
* value. So, just fall through.
*/
}
/* FALLTHRU */
case IPOPT_RR:
case IPOPT_TS:
case IPOPT_SATID:
default:
/*
* optlen should include from the beginning of an
* option.
* NOTE : Stream Identifier Option (SID): RFC 791
* shows the bit pattern of optlen as 2 and documents
* the length as 4. We assume it to be 2 here.
*/
break;
}
}
return (NULL);
}
/*
* Don't change ipha_dst for an inbound datagram as it points
* because of ip_massage_options called by the ULP, ipha_dst
* points to the first hop and we need to use the final
* destination for computing the ICV.
*/
if (outbound)
/*
* Returning NULL will tell the caller to IPSA_REFELE(), free
* the memory, etc.
*/
return (NULL);
}
sizeof (ah_t) + ah_align_sz);
if (outbound)
else
return (phdr_mp);
}
/*
* Authenticate an outbound datagram. This function is called
* whenever IP sends an outbound datagram that needs authentication.
* Returns a modified packet if done. Returns NULL if error or queued.
* If error return then ipIfStatsOutDiscards has been increased.
*/
static mblk_t *
{
int length_to_skip;
/*
* Construct the chain of mblks
*
* PSEUDO_HDR->DATA
*
* one by one.
*/
/*
* Get the outer IP header in shape to escape this system..
*/
/*
* Need to update packet with any CIPSO option and update
* ixa_tsl to capture the new label.
* We allocate a separate ixa for that purpose.
*/
&ahstack->ah_dropper);
return (NULL);
}
/* Packet dropped by sadb_whack_label */
return (NULL);
}
}
/*
* Age SA according to number of bytes that will be sent after
* adding the AH header, ICV, and padding to the packet.
*/
} else {
sizeof (ah_t) + ah_align_sz;
}
/* rig things as if ipsec_getassocbyconn() failed */
"AH association 0x%x, dst %s had bytes expire.\n",
if (need_refrele)
return (NULL);
}
/*
* XXX We need to have fixed up the outer label before we get here.
* (AH is computing the checksum over the outer label).
*/
/*
* Insert pseudo header:
* [IP, ULP] => [IP, AH, ICV] -> ULP
*/
} else {
}
&ahstack->ah_dropper);
if (need_refrele)
return (NULL);
}
/*
* At this point data_mp points to
* an mblk containing the pseudo header (IP header,
* AH header, and ICV with mutable fields zero'ed out).
* mp points to the mblk containing the ULP data. The original
* IP header is kept before the ULP data in data_mp.
*/
/* submit MAC request to KCF */
if (need_refrele)
return (data_mp);
}
static mblk_t *
{
int length_to_skip;
int ah_length;
/*
* We may wish to check replay in-range-only here as an optimization.
* Include the reality check of ipsa->ipsa_replay >
* ipsa->ipsa_replay_wsize for times when it's the first N packets,
* where N == ipsa->ipsa_replay_wsize.
*
* Another check that may come here later is the "collision" check.
* If legitimate packets flow quickly enough, this won't be a problem,
* but collisions may cause authentication algorithm crunching to
* take place when it doesn't need to.
*/
&ahstack->ah_dropper);
return (NULL);
}
/*
* The offset of the AH header can be computed from its pointer
* within the data mblk, which was pulled up until the AH header
* by ipsec_inbound_ah_sa() during SA selection.
*/
/*
* We need to pullup until the ICV before we call
* ah_process_ip_options_v6.
*/
/*
* to re-assign following the pullup.
*/
"ah_inbound: Small AH header\n");
&ahstack->ah_dropper);
return (NULL);
}
}
/*
* Insert pseudo header:
* [IP, ULP] => [IP, AH, ICV] -> ULP
*/
} else {
}
&ahstack->ah_dropper);
return (NULL);
}
/* submit request to KCF */
assoc));
}
/*
* Invoked after processing of an inbound packet by the
* kernel crypto framework. Called by ah_submit_req() for a sync request,
* or by the kcf callback for an async request.
* Returns NULL if the mblk chain is consumed.
*/
static mblk_t *
{
&ahstack->ah_dropper);
return (NULL);
}
&ahstack->ah_dropper);
return (NULL);
}
if (isv4) {
ah_offset <<= 2;
} else {
}
/*
* We get here only when authentication passed.
*/
int af;
void *addr;
if (isv4) {
} else {
}
/*
* Log the event. As of now we print out an event.
* Do not print the replay failure number, or else
* syslog cannot collate the error messages. Printing
* the replay number that failed (or printing to the
* console) opens a denial-of-service attack.
*/
"Replay failed for AH spi %x, dst_addr %s",
goto ah_in_discard;
}
/*
* We need to remove the AH header from the original
* datagram. Best way to do this is to move the pre-AH headers
* forward in the (relatively simple) IPv4 case. In IPv6, it's
* a bit more complicated because of IPv6's next-header chaining,
* but it's doable.
*/
if (isv4) {
/*
* Assign the right protocol, adjust the length as we
* are removing the AH header and adjust the checksum to
* account for the protocol and length.
*/
/* The ipsa has hit hard expiration, LOG and AUDIT. */
"AH Association 0x%x, dst %s had bytes expire.\n",
goto ah_in_discard;
}
ipha->ipha_hdr_checksum = 0;
} else {
int hdrlen;
/*
* Make phdr_mp hold until the AH header and make
* mp hold everything past AH header.
*/
/* The ipsa has hit hard expiration, LOG and AUDIT. */
"AH Association 0x%x, dst %s had bytes "
goto ah_in_discard;
}
/*
* Update the next header field of the header preceding
* AH with the next header field of AH. Start with the
* IPv6 header and proceed with the extension headers
* until we find what we're looking for.
*/
while (*nexthdr != IPPROTO_AH) {
/* Assume IP has already stripped it */
switch (*nexthdr) {
case IPPROTO_HOPOPTS:
break;
case IPPROTO_DSTOPTS:
break;
case IPPROTO_ROUTING:
break;
}
}
}
/* Now that we've fixed the IP header, move it forward. */
} else {
}
/*
* If SA is labelled, use its label, else inherit the label
*/
return (NULL);
}
}
/*
* Cluster buffering case. Tell caller that we're
* handling the packet.
*/
return (NULL);
}
return (mp);
&ahstack->ah_dropper);
return (NULL);
}
/*
* Invoked after processing of an outbound packet by the
* kernel crypto framework, either by ah_submit_req() for a request
* executed syncrhonously, or by the KEF callback for a request
* executed asynchronously.
*/
static mblk_t *
{
int align_len;
&ahstack->ah_dropper);
return (NULL);
}
if (isv4) {
hdrs_length <<= 2;
/*
* phdr_mp must have the right amount of space for the
* combined IP and AH header. Copy the IP header and
* the ack_data onto AH. Note that the AH header was
* already formed before the ICV calculation and hence
* you don't have to copy it here.
*/
/*
* Compute the new header checksum as we are assigning
* IPPROTO_AH and adjusting the length here.
*/
nipha->ipha_hdr_checksum = 0;
} else {
/*
* phdr_mp must have the right amount of space for the
* combined IP and AH header. Copy the IP header with
* options into the pseudo header. When we constructed
* a pseudo header, we did not copy some of the mutable
* fields. We do it now by calling ah_fix_phdr_v6()
* with the last argument B_TRUE. It returns the
* ah_offset into the pseudo header.
*/
/*
* phdr_mp can hold exactly the whole IP header with options
* plus the AH header also. Thus subtracting the AH header's
* size should give exactly how much of the original header
* should be skipped.
*/
}
/* Skip the original IP header */
}
return (phdr_mp);
}
/* Refactor me */
/*
* Wrapper to allow IP to trigger an AH association failure message
* during SA inbound selection.
*/
void
{
if (ahstack->ipsecah_log_unknown_spi) {
}
&ahstack->ah_dropper);
}
/*
* Initialize the AH input and output processing functions.
*/
void
{
}