#include <sys/types.h>
#include <sys/stream.h>
#include <sys/strsubr.h>
#include <sys/sunddi.h>
#include <sys/ddi.h>
#include <sys/strlog.h>
#include <inet/common.h>
#include <inet/mib2.h>
#include <inet/ip.h>
#include <inet/ip6.h>
#include <net/pfkeyv2.h>
#include <inet/sadb.h>
#include <inet/ipsec_impl.h>
#include <inet/ipdrop.h>
#include <inet/ipsecesp.h>
#include <inet/ipsecah.h>
#include <sys/kstat.h>
static boolean_t
ipsec_match_outbound_ids(ipsec_latch_t *ipl, ipsa_t *sa)
{
ASSERT(ipl->ipl_ids_latched == B_TRUE);
return ipsid_equal(ipl->ipl_local_cid, sa->ipsa_src_cid) &&
ipsid_equal(ipl->ipl_remote_cid, sa->ipsa_dst_cid);
}
boolean_t
ipsec_label_match(ts_label_t *l1, ts_label_t *l2)
{
if (!is_system_labeled())
return (B_TRUE);
if (l2 == NULL)
return (B_TRUE);
if (l1 == NULL)
return (B_FALSE);
return (label_equal(l1, l2));
}
ipsa_t *
ipsec_getassocbyconn(isaf_t *bucket, ip_xmit_attr_t *ixa, uint32_t *src,
uint32_t *dst, sa_family_t af, uint8_t protocol, ts_label_t *tsl)
{
ipsa_t *retval, *candidate;
ipsec_action_t *candact;
boolean_t need_unique;
boolean_t tunnel_mode = (ixa->ixa_flags & IXAF_IPSEC_TUNNEL);
uint64_t unique_id;
uint32_t old_flags, excludeflags;
ipsec_policy_t *pp = ixa->ixa_ipsec_policy;
ipsec_action_t *actlist = ixa->ixa_ipsec_action;
ipsec_action_t *act;
ipsec_latch_t *ipl = ixa->ixa_ipsec_latch;
ipsa_ref_t *ipr = NULL;
sa_family_t inaf = ixa->ixa_ipsec_inaf;
uint32_t *insrc = ixa->ixa_ipsec_insrc;
uint32_t *indst = ixa->ixa_ipsec_indst;
uint8_t insrcpfx = ixa->ixa_ipsec_insrcpfx;
uint8_t indstpfx = ixa->ixa_ipsec_indstpfx;
ASSERT(MUTEX_HELD(&bucket->isaf_lock));
if (ipl != NULL) {
ASSERT((protocol == IPPROTO_AH) || (protocol == IPPROTO_ESP));
ipr = &ixa->ixa_ipsec_ref[protocol - IPPROTO_ESP];
retval = ipr->ipsr_sa;
if ((bucket == ipr->ipsr_bucket) &&
(bucket->isaf_gen == ipr->ipsr_gen) &&
(retval->ipsa_state != IPSA_STATE_DEAD) &&
!(retval->ipsa_flags & IPSA_F_CINVALID)) {
IPSA_REFHOLD(retval);
return (retval);
}
}
ASSERT((pp != NULL) || (actlist != NULL));
if (actlist == NULL)
actlist = pp->ipsp_act;
ASSERT(actlist != NULL);
need_unique = actlist->ipa_want_unique;
unique_id = SA_FORM_UNIQUE_ID(ixa);
excludeflags = IPSA_F_UNIQUE;
if (need_unique)
excludeflags |= IPSA_F_USED;
candidate = NULL;
for (retval = bucket->isaf_ipsa; retval != NULL;
retval = retval->ipsa_next) {
ASSERT((candidate == NULL) ||
MUTEX_HELD(&candidate->ipsa_lock));
mutex_enter(&retval->ipsa_lock);
if (!IPSA_ARE_ADDR_EQUAL(dst, retval->ipsa_dstaddr, af))
goto next_ipsa;
if (!IPSA_ARE_ADDR_EQUAL(src, retval->ipsa_srcaddr, af) &&
!IPSA_IS_ADDR_UNSPEC(retval->ipsa_srcaddr, af))
goto next_ipsa;
if (tunnel_mode) {
if (!(retval->ipsa_flags & IPSA_F_TUNNEL))
goto next_ipsa;
if (!IPSA_IS_ADDR_UNSPEC(retval->ipsa_innerdst, inaf)) {
if (!ip_addr_match((uint8_t *)indst,
min(indstpfx, retval->ipsa_innerdstpfx),
(in6_addr_t *)retval->ipsa_innerdst))
goto next_ipsa;
}
if (!IPSA_IS_ADDR_UNSPEC(retval->ipsa_innersrc, inaf)) {
if (!ip_addr_match((uint8_t *)insrc,
min(insrcpfx, retval->ipsa_innersrcpfx),
(in6_addr_t *)retval->ipsa_innersrc))
goto next_ipsa;
}
} else {
if (retval->ipsa_flags & IPSA_F_TUNNEL)
goto next_ipsa;
}
for (act = actlist; act != NULL; act = act->ipa_next) {
ipsec_act_t *ap = &act->ipa_act;
if (ap->ipa_type != IPSEC_POLICY_APPLY)
continue;
if (protocol == IPPROTO_AH) {
if (!(ap->ipa_apply.ipp_use_ah))
continue;
if (ap->ipa_apply.ipp_auth_alg !=
retval->ipsa_auth_alg)
continue;
if (ap->ipa_apply.ipp_ah_minbits >
retval->ipsa_authkeybits)
continue;
} else {
if (!(ap->ipa_apply.ipp_use_esp))
continue;
if ((ap->ipa_apply.ipp_encr_alg !=
retval->ipsa_encr_alg))
continue;
if (ap->ipa_apply.ipp_espe_minbits >
retval->ipsa_encrkeybits)
continue;
if (ap->ipa_apply.ipp_esp_auth_alg != 0) {
if (ap->ipa_apply.ipp_esp_auth_alg !=
retval->ipsa_auth_alg)
continue;
if (ap->ipa_apply.ipp_espa_minbits >
retval->ipsa_authkeybits)
continue;
}
}
if ((ap->ipa_apply.ipp_km_proto != 0) &&
(retval->ipsa_kmp != 0) &&
(ap->ipa_apply.ipp_km_proto != retval->ipsa_kmp))
continue;
if ((ap->ipa_apply.ipp_km_cookie != 0) &&
(retval->ipsa_kmc != 0) &&
(ap->ipa_apply.ipp_km_cookie != retval->ipsa_kmc))
continue;
break;
}
if (act == NULL)
goto next_ipsa;
if (ipl && ipl->ipl_ids_latched &&
!ipsec_match_outbound_ids(ipl, retval))
goto next_ipsa;
if (!ipsec_label_match(tsl, retval->ipsa_tsl))
goto next_ipsa;
if ((retval->ipsa_flags & IPSA_F_UNIQUE) &&
((unique_id & retval->ipsa_unique_mask) ==
retval->ipsa_unique_id))
break;
if (retval->ipsa_flags & excludeflags)
goto next_ipsa;
if (candidate == NULL) {
candidate = retval;
candact = act;
continue;
} else {
if (IPSA_IS_ADDR_UNSPEC(candidate->ipsa_srcaddr, af) &&
!IPSA_IS_ADDR_UNSPEC(retval->ipsa_srcaddr, af)) {
mutex_exit(&candidate->ipsa_lock);
candidate = retval;
candact = act;
continue;
}
}
next_ipsa:
mutex_exit(&retval->ipsa_lock);
}
ASSERT((retval == NULL) || MUTEX_HELD(&retval->ipsa_lock));
ASSERT((candidate == NULL) || MUTEX_HELD(&candidate->ipsa_lock));
ASSERT((retval == NULL) || (act != NULL));
ASSERT((candidate == NULL) || (candact != NULL));
if (retval == NULL && candidate == NULL)
return (NULL);
if (retval == NULL) {
ASSERT(MUTEX_HELD(&candidate->ipsa_lock));
retval = candidate;
act = candact;
} else if (candidate != NULL) {
mutex_exit(&candidate->ipsa_lock);
}
ASSERT(MUTEX_HELD(&retval->ipsa_lock));
ASSERT(act != NULL);
IPSA_REFHOLD(retval);
old_flags = retval->ipsa_flags;
retval->ipsa_flags |= IPSA_F_USED;
if (ipr != NULL) {
ipr->ipsr_bucket = bucket;
ipr->ipsr_gen = bucket->isaf_gen;
ipr->ipsr_sa = retval;
retval->ipsa_flags &= ~IPSA_F_CINVALID;
}
if (ipl != NULL) {
if (!ipl->ipl_ids_latched) {
ipsec_latch_ids(ipl,
retval->ipsa_src_cid, retval->ipsa_dst_cid);
}
if (ixa->ixa_ipsec_action == NULL) {
IPACT_REFHOLD(act);
ixa->ixa_ipsec_action = act;
}
}
if (need_unique && !(old_flags & IPSA_F_USED)) {
if (retval->ipsa_unique_id == 0) {
ASSERT((retval->ipsa_flags & IPSA_F_UNIQUE) == 0);
retval->ipsa_flags |= IPSA_F_UNIQUE;
retval->ipsa_unique_id = unique_id;
retval->ipsa_unique_mask = SA_UNIQUE_MASK(
ixa->ixa_ipsec_src_port, ixa->ixa_ipsec_dst_port,
protocol, 0);
}
if (IPSA_IS_ADDR_UNSPEC(retval->ipsa_srcaddr, af)) {
IPSA_REFHOLD(retval);
IPSA_COPY_ADDR(retval->ipsa_srcaddr, src, af);
mutex_exit(&retval->ipsa_lock);
sadb_unlinkassoc(retval);
#ifdef DEBUG
if (sadb_insertassoc(retval, bucket) != 0) {
cmn_err(CE_PANIC,
"sadb_insertassoc() failed in "
"ipsec_getassocbyconn().\n");
}
#else
(void) sadb_insertassoc(retval, bucket);
#endif
return (retval);
}
}
mutex_exit(&retval->ipsa_lock);
return (retval);
}
ipsa_t *
ipsec_getassocbyspi(isaf_t *bucket, uint32_t spi, uint32_t *src, uint32_t *dst,
sa_family_t af)
{
ipsa_t *retval;
ASSERT(MUTEX_HELD(&bucket->isaf_lock));
for (retval = bucket->isaf_ipsa; retval != NULL;
retval = retval->ipsa_next) {
if (retval->ipsa_spi != spi)
continue;
if (!IPSA_ARE_ADDR_EQUAL(dst, retval->ipsa_dstaddr, af))
continue;
if (IPSA_ARE_ADDR_EQUAL(src, retval->ipsa_srcaddr, af) ||
IPSA_IS_ADDR_UNSPEC(retval->ipsa_srcaddr, af) ||
IPSA_IS_ADDR_UNSPEC(src, af))
break;
}
if (retval != NULL) {
IPSA_REFHOLD(retval);
}
return (retval);
}
boolean_t
ipsec_outbound_sa(mblk_t *data_mp, ip_xmit_attr_t *ixa, uint_t proto)
{
ipaddr_t dst;
uint32_t *dst_ptr, *src_ptr;
isaf_t *bucket;
ipsa_t *assoc;
ip_pkt_t ipp;
in6_addr_t dst6;
ipsa_t **sa;
sadbp_t *sadbp;
sadb_t *sp;
sa_family_t af;
ip_stack_t *ipst = ixa->ixa_ipst;
netstack_t *ns = ipst->ips_netstack;
ASSERT(ixa->ixa_flags & IXAF_IPSEC_SECURE);
if (proto == IPPROTO_ESP) {
ipsecesp_stack_t *espstack;
espstack = ns->netstack_ipsecesp;
sa = &ixa->ixa_ipsec_esp_sa;
sadbp = &espstack->esp_sadb;
} else {
ipsecah_stack_t *ahstack;
ASSERT(proto == IPPROTO_AH);
ahstack = ns->netstack_ipsecah;
sa = &ixa->ixa_ipsec_ah_sa;
sadbp = &ahstack->ah_sadb;
}
ASSERT(*sa == NULL);
if (ixa->ixa_flags & IXAF_IS_IPV4) {
ipha_t *ipha = (ipha_t *)data_mp->b_rptr;
ASSERT(IPH_HDR_VERSION(ipha) == IPV4_VERSION);
dst = ip_get_dst(ipha);
sp = &sadbp->s_v4;
af = AF_INET;
bucket = OUTBOUND_BUCKET_V4(sp, dst);
src_ptr = (uint32_t *)&ipha->ipha_src;
dst_ptr = (uint32_t *)&dst;
} else {
ip6_t *ip6h = (ip6_t *)data_mp->b_rptr;
ASSERT(IPH_HDR_VERSION(ip6h) == IPV6_VERSION);
dst6 = ip_get_dst_v6(ip6h, data_mp, NULL);
af = AF_INET6;
bzero(&ipp, sizeof (ipp));
sp = &sadbp->s_v6;
bucket = OUTBOUND_BUCKET_V6(sp, dst6);
src_ptr = (uint32_t *)&ip6h->ip6_src;
dst_ptr = (uint32_t *)&dst6;
}
mutex_enter(&bucket->isaf_lock);
assoc = ipsec_getassocbyconn(bucket, ixa, src_ptr, dst_ptr, af,
proto, ixa->ixa_tsl);
mutex_exit(&bucket->isaf_lock);
if (assoc == NULL)
return (B_FALSE);
if (assoc->ipsa_state == IPSA_STATE_DEAD) {
IPSA_REFRELE(assoc);
return (B_FALSE);
}
ASSERT(assoc->ipsa_state != IPSA_STATE_LARVAL);
*sa = assoc;
return (B_TRUE);
}
mblk_t *
ipsec_inbound_ah_sa(mblk_t *mp, ip_recv_attr_t *ira, ah_t **ahp)
{
ipha_t *ipha;
ipsa_t *assoc;
ah_t *ah;
isaf_t *hptr;
boolean_t isv6;
ip6_t *ip6h;
int ah_offset;
uint32_t *src_ptr, *dst_ptr;
int pullup_len;
sadb_t *sp;
sa_family_t af;
netstack_t *ns = ira->ira_ill->ill_ipst->ips_netstack;
ipsec_stack_t *ipss = ns->netstack_ipsec;
ipsecah_stack_t *ahstack = ns->netstack_ipsecah;
IP_AH_BUMP_STAT(ipss, in_requests);
isv6 = !(ira->ira_flags & IRAF_IS_IPV4);
if (isv6) {
ip6h = (ip6_t *)mp->b_rptr;
ah_offset = ipsec_ah_get_hdr_size_v6(mp, B_TRUE);
} else {
ipha = (ipha_t *)mp->b_rptr;
ASSERT(ipha->ipha_protocol == IPPROTO_AH);
ah_offset = ipha->ipha_version_and_hdr_length -
(uint8_t)((IP_VERSION << 4));
ah_offset <<= 2;
}
pullup_len = ah_offset + sizeof (ah_t);
if (mp->b_rptr + pullup_len > mp->b_wptr) {
if (!pullupmsg(mp, pullup_len)) {
ipsec_rl_strlog(ns, ip_mod_info.mi_idnum, 0, 0,
SL_WARN | SL_ERROR,
"ipsec_inbound_ah_sa: Small AH header\n");
IP_AH_BUMP_STAT(ipss, in_discards);
ip_drop_packet(mp, B_TRUE, ira->ira_ill,
DROPPER(ipss, ipds_ah_bad_length),
&ipss->ipsec_dropper);
return (NULL);
}
if (isv6)
ip6h = (ip6_t *)mp->b_rptr;
else
ipha = (ipha_t *)mp->b_rptr;
}
ah = (ah_t *)(mp->b_rptr + ah_offset);
if (isv6) {
src_ptr = (uint32_t *)&ip6h->ip6_src;
dst_ptr = (uint32_t *)&ip6h->ip6_dst;
sp = &ahstack->ah_sadb.s_v6;
af = AF_INET6;
} else {
src_ptr = (uint32_t *)&ipha->ipha_src;
dst_ptr = (uint32_t *)&ipha->ipha_dst;
sp = &ahstack->ah_sadb.s_v4;
af = AF_INET;
}
hptr = INBOUND_BUCKET(sp, ah->ah_spi);
mutex_enter(&hptr->isaf_lock);
assoc = ipsec_getassocbyspi(hptr, ah->ah_spi, src_ptr, dst_ptr, af);
mutex_exit(&hptr->isaf_lock);
if (assoc == NULL || assoc->ipsa_state == IPSA_STATE_DEAD ||
assoc->ipsa_state == IPSA_STATE_ACTIVE_ELSEWHERE) {
IP_AH_BUMP_STAT(ipss, lookup_failure);
IP_AH_BUMP_STAT(ipss, in_discards);
ipsecah_in_assocfailure(mp, 0,
SL_ERROR | SL_CONSOLE | SL_WARN,
"ipsec_inbound_ah_sa: No association found for "
"spi 0x%x, dst addr %s\n",
ah->ah_spi, dst_ptr, af, ira);
if (assoc != NULL) {
IPSA_REFRELE(assoc);
}
return (NULL);
}
if (assoc->ipsa_state == IPSA_STATE_LARVAL) {
mp = sadb_set_lpkt(assoc, mp, ira);
if (mp == NULL) {
IPSA_REFRELE(assoc);
return (NULL);
}
}
if (!(ira->ira_flags & IRAF_IPSEC_SECURE)) {
ira->ira_ipsec_action = NULL;
ira->ira_ipsec_ah_sa = NULL;
ira->ira_ipsec_esp_sa = NULL;
}
if (ira->ira_ipsec_ah_sa != NULL) {
IPSA_REFRELE(ira->ira_ipsec_ah_sa);
}
ira->ira_flags |= IRAF_IPSEC_SECURE;
ira->ira_ipsec_ah_sa = assoc;
*ahp = ah;
return (mp);
}
mblk_t *
ipsec_inbound_esp_sa(mblk_t *data_mp, ip_recv_attr_t *ira, esph_t **esphp)
{
mblk_t *placeholder;
uint32_t *src_ptr, *dst_ptr;
ipha_t *ipha;
ip6_t *ip6h;
esph_t *esph;
ipsa_t *ipsa;
isaf_t *bucket;
uint_t preamble;
sa_family_t af;
boolean_t isv6;
sadb_t *sp;
netstack_t *ns = ira->ira_ill->ill_ipst->ips_netstack;
ipsec_stack_t *ipss = ns->netstack_ipsec;
ipsecesp_stack_t *espstack = ns->netstack_ipsecesp;
IP_ESP_BUMP_STAT(ipss, in_requests);
isv6 = !(ira->ira_flags & IRAF_IS_IPV4);
if (isv6) {
ip6h = (ip6_t *)data_mp->b_rptr;
} else {
ipha = (ipha_t *)data_mp->b_rptr;
}
if (data_mp->b_datap->db_ref > 1 ||
(data_mp->b_wptr - data_mp->b_rptr) < ira->ira_pktlen) {
placeholder = msgpullup(data_mp, -1);
if (placeholder == NULL) {
IP_ESP_BUMP_STAT(ipss, in_discards);
ip_drop_packet(data_mp, B_TRUE, ira->ira_ill,
DROPPER(ipss, ipds_esp_nomem),
&ipss->ipsec_dropper);
return (NULL);
} else {
freemsg(data_mp);
data_mp = placeholder;
}
}
if (isv6) {
ip6h = (ip6_t *)data_mp->b_rptr;
src_ptr = (uint32_t *)&ip6h->ip6_src;
dst_ptr = (uint32_t *)&ip6h->ip6_dst;
if (ip6h->ip6_nxt != IPPROTO_ESP) {
preamble = ip_hdr_length_v6(data_mp, ip6h);
} else {
preamble = sizeof (ip6_t);
}
sp = &espstack->esp_sadb.s_v6;
af = AF_INET6;
} else {
ipha = (ipha_t *)data_mp->b_rptr;
src_ptr = (uint32_t *)&ipha->ipha_src;
dst_ptr = (uint32_t *)&ipha->ipha_dst;
preamble = IPH_HDR_LENGTH(ipha);
sp = &espstack->esp_sadb.s_v4;
af = AF_INET;
}
esph = (esph_t *)(data_mp->b_rptr + preamble);
bucket = INBOUND_BUCKET(sp, esph->esph_spi);
mutex_enter(&bucket->isaf_lock);
ipsa = ipsec_getassocbyspi(bucket, esph->esph_spi, src_ptr, dst_ptr,
af);
mutex_exit(&bucket->isaf_lock);
if (ipsa == NULL || ipsa->ipsa_state == IPSA_STATE_DEAD ||
ipsa->ipsa_state == IPSA_STATE_ACTIVE_ELSEWHERE) {
IP_ESP_BUMP_STAT(ipss, lookup_failure);
IP_ESP_BUMP_STAT(ipss, in_discards);
ipsecesp_in_assocfailure(data_mp, 0,
SL_ERROR | SL_CONSOLE | SL_WARN,
"ipsec_inbound_esp_sa: No association found for "
"spi 0x%x, dst addr %s\n",
esph->esph_spi, dst_ptr, af, ira);
if (ipsa != NULL) {
IPSA_REFRELE(ipsa);
}
return (NULL);
}
if (ipsa->ipsa_state == IPSA_STATE_LARVAL) {
data_mp = sadb_set_lpkt(ipsa, data_mp, ira);
if (data_mp == NULL) {
IPSA_REFRELE(ipsa);
return (NULL);
}
}
if (!(ira->ira_flags & IRAF_IPSEC_SECURE)) {
ira->ira_ipsec_action = NULL;
ira->ira_ipsec_ah_sa = NULL;
ira->ira_ipsec_esp_sa = NULL;
}
if (ira->ira_ipsec_esp_sa != NULL) {
IPSA_REFRELE(ira->ira_ipsec_esp_sa);
}
ira->ira_flags |= IRAF_IPSEC_SECURE;
ira->ira_ipsec_esp_sa = ipsa;
*esphp = esph;
return (data_mp);
}