/* $OpenBSD: ip_spd.c,v 1.50 2004/06/21 23:50:37 tholo Exp $ */ /* * The author of this code is Angelos D. Keromytis (angelos@cis.upenn.edu) * * Copyright (c) 2000-2001 Angelos D. Keromytis. * * Permission to use, copy, and modify this software with or without fee * is hereby granted, provided that this entire notice is included in * all copies of any software which is or includes a copy or * modification of this software. * You may use this code under the GNU public license if you so wish. Please * contribute changes back to the authors under this freer than GPL license * so that we may further the use of strong encryption without limitations to * all. * * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR * IMPLIED WARRANTY. IN PARTICULAR, NONE OF THE AUTHORS MAKES ANY * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE * MERCHANTABILITY OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR * PURPOSE. */ #include <sys/param.h> #include <sys/systm.h> #include <sys/mbuf.h> #include <sys/socket.h> #include <sys/kernel.h> #include <sys/socketvar.h> #include <sys/protosw.h> #include <net/if.h> #include <net/route.h> #include <net/netisr.h> #ifdef INET #include <netinet/in.h> #include <netinet/in_systm.h> #include <netinet/ip.h> #include <netinet/in_pcb.h> #include <netinet/in_var.h> #endif /* INET */ #ifdef INET6 #ifndef INET #include <netinet/in.h> #endif #include <netinet6/in6_var.h> #endif /* INET6 */ #include <netinet/ip_ipsp.h> #include <net/pfkeyv2.h> #ifdef ENCDEBUG #define DPRINTF(x) if (encdebug) printf x #else #define DPRINTF(x) #endif struct pool ipsec_policy_pool; struct pool ipsec_acquire_pool; int ipsec_policy_pool_initialized = 0; int ipsec_acquire_pool_initialized = 0; /* * Lookup at the SPD based on the headers contained on the mbuf. The second * argument indicates what protocol family the header at the beginning of * the mbuf is. hlen is the the offset of the transport protocol header * in the mbuf. * * Return combinations (of return value and in *error): * - NULL/0 -> no IPsec required on packet * - NULL/-EINVAL -> silently drop the packet * - NULL/errno -> drop packet and return error * or a pointer to a TDB (and 0 in *error). * * In the case of incoming flows, only the first three combinations are * returned. */ struct tdb * ipsp_spd_lookup(struct mbuf *m, int af, int hlen, int *error, int direction, struct tdb *tdbp, struct inpcb *inp) { struct route_enc re0, *re = &re0; union sockaddr_union sdst, ssrc; struct sockaddr_encap *ddst; struct ipsec_policy *ipo; int signore = 0, dignore = 0; /* * If there are no flows in place, there's no point * continuing with the SPD lookup. */ if (!ipsec_in_use && inp == NULL) { *error = 0; return NULL; } /* * If an input packet is destined to a BYPASS socket, just accept it. */ if ((inp != NULL) && (direction == IPSP_DIRECTION_IN) && (inp->inp_seclevel[SL_ESP_TRANS] == IPSEC_LEVEL_BYPASS) && (inp->inp_seclevel[SL_ESP_NETWORK] == IPSEC_LEVEL_BYPASS) && (inp->inp_seclevel[SL_AUTH] == IPSEC_LEVEL_BYPASS)) { *error = 0; return NULL; } bzero((caddr_t) re, sizeof(struct route_enc)); bzero((caddr_t) &sdst, sizeof(union sockaddr_union)); bzero((caddr_t) &ssrc, sizeof(union sockaddr_union)); ddst = (struct sockaddr_encap *) &re->re_dst; ddst->sen_family = PF_KEY; ddst->sen_len = SENT_LEN; switch (af) { #ifdef INET case AF_INET: if (hlen < sizeof (struct ip) || m->m_pkthdr.len < hlen) { *error = EINVAL; return NULL; } ddst->sen_direction = direction; ddst->sen_type = SENT_IP4; m_copydata(m, offsetof(struct ip, ip_src), sizeof(struct in_addr), (caddr_t) &(ddst->sen_ip_src)); m_copydata(m, offsetof(struct ip, ip_dst), sizeof(struct in_addr), (caddr_t) &(ddst->sen_ip_dst)); m_copydata(m, offsetof(struct ip, ip_p), sizeof(u_int8_t), (caddr_t) &(ddst->sen_proto)); sdst.sin.sin_family = ssrc.sin.sin_family = AF_INET; sdst.sin.sin_len = ssrc.sin.sin_len = sizeof(struct sockaddr_in); ssrc.sin.sin_addr = ddst->sen_ip_src; sdst.sin.sin_addr = ddst->sen_ip_dst; /* * If TCP/UDP, extract the port numbers to use in the lookup. */ switch (ddst->sen_proto) { case IPPROTO_UDP: case IPPROTO_TCP: /* Make sure there's enough data in the packet. */ if (m->m_pkthdr.len < hlen + 2 * sizeof(u_int16_t)) { *error = EINVAL; return NULL; } /* * Luckily, the offset of the src/dst ports in * both the UDP and TCP headers is the same (first * two 16-bit values in the respective headers), * so we can just copy them. */ m_copydata(m, hlen, sizeof(u_int16_t), (caddr_t) &(ddst->sen_sport)); m_copydata(m, hlen + sizeof(u_int16_t), sizeof(u_int16_t), (caddr_t) &(ddst->sen_dport)); break; default: ddst->sen_sport = 0; ddst->sen_dport = 0; } break; #endif /* INET */ #ifdef INET6 case AF_INET6: if (hlen < sizeof (struct ip6_hdr) || m->m_pkthdr.len < hlen) { *error = EINVAL; return NULL; } ddst->sen_type = SENT_IP6; ddst->sen_ip6_direction = direction; m_copydata(m, offsetof(struct ip6_hdr, ip6_src), sizeof(struct in6_addr), (caddr_t) &(ddst->sen_ip6_src)); m_copydata(m, offsetof(struct ip6_hdr, ip6_dst), sizeof(struct in6_addr), (caddr_t) &(ddst->sen_ip6_dst)); m_copydata(m, offsetof(struct ip6_hdr, ip6_nxt), sizeof(u_int8_t), (caddr_t) &(ddst->sen_ip6_proto)); sdst.sin6.sin6_family = ssrc.sin6.sin6_family = AF_INET6; sdst.sin6.sin6_len = ssrc.sin6.sin6_family = sizeof(struct sockaddr_in6); in6_recoverscope(&ssrc.sin6, &ddst->sen_ip6_src, NULL); in6_recoverscope(&sdst.sin6, &ddst->sen_ip6_dst, NULL); /* * If TCP/UDP, extract the port numbers to use in the lookup. */ switch (ddst->sen_ip6_proto) { case IPPROTO_UDP: case IPPROTO_TCP: /* Make sure there's enough data in the packet. */ if (m->m_pkthdr.len < hlen + 2 * sizeof(u_int16_t)) { *error = EINVAL; return NULL; } /* * Luckily, the offset of the src/dst ports in * both the UDP and TCP headers is the same * (first two 16-bit values in the respective * headers), so we can just copy them. */ m_copydata(m, hlen, sizeof(u_int16_t), (caddr_t) &(ddst->sen_ip6_sport)); m_copydata(m, hlen + sizeof(u_int16_t), sizeof(u_int16_t), (caddr_t) &(ddst->sen_ip6_dport)); break; default: ddst->sen_ip6_sport = 0; ddst->sen_ip6_dport = 0; } break; #endif /* INET6 */ default: *error = EAFNOSUPPORT; return NULL; } /* Actual SPD lookup. */ rtalloc((struct route *) re); if (re->re_rt == NULL) { /* * Return whatever the socket requirements are, there are no * system-wide policies. */ *error = 0; return ipsp_spd_inp(m, af, hlen, error, direction, tdbp, inp, NULL); } /* Sanity check. */ if ((re->re_rt->rt_gateway == NULL) || (((struct sockaddr_encap *) re->re_rt->rt_gateway)->sen_type != SENT_IPSP)) { RTFREE(re->re_rt); *error = EHOSTUNREACH; DPRINTF(("ip_spd_lookup: no gateway in SPD entry!")); return NULL; } ipo = ((struct sockaddr_encap *) (re->re_rt->rt_gateway))->sen_ipsp; RTFREE(re->re_rt); if (ipo == NULL) { *error = EHOSTUNREACH; DPRINTF(("ip_spd_lookup: no policy attached to SPD entry!")); return NULL; } switch (ipo->ipo_type) { case IPSP_PERMIT: *error = 0; return ipsp_spd_inp(m, af, hlen, error, direction, tdbp, inp, ipo); case IPSP_DENY: *error = EHOSTUNREACH; return NULL; case IPSP_IPSEC_USE: case IPSP_IPSEC_ACQUIRE: case IPSP_IPSEC_REQUIRE: case IPSP_IPSEC_DONTACQ: /* Nothing more needed here. */ break; default: *error = EINVAL; return NULL; } /* Check for non-specific destination in the policy. */ switch (ipo->ipo_dst.sa.sa_family) { #ifdef INET case AF_INET: if ((ipo->ipo_dst.sin.sin_addr.s_addr == INADDR_ANY) || (ipo->ipo_dst.sin.sin_addr.s_addr == INADDR_BROADCAST)) dignore = 1; break; #endif /* INET */ #ifdef INET6 case AF_INET6: if ((IN6_IS_ADDR_UNSPECIFIED(&ipo->ipo_dst.sin6.sin6_addr)) || (bcmp(&ipo->ipo_dst.sin6.sin6_addr, &in6mask128, sizeof(in6mask128)) == 0)) dignore = 1; break; #endif /* INET6 */ } /* Likewise for source. */ switch (ipo->ipo_src.sa.sa_family) { #ifdef INET case AF_INET: if (ipo->ipo_src.sin.sin_addr.s_addr == INADDR_ANY) signore = 1; break; #endif /* INET */ #ifdef INET6 case AF_INET6: if (IN6_IS_ADDR_UNSPECIFIED(&ipo->ipo_src.sin6.sin6_addr)) signore = 1; break; #endif /* INET6 */ } /* Do we have a cached entry ? If so, check if it's still valid. */ if ((ipo->ipo_tdb) && (ipo->ipo_tdb->tdb_flags & TDBF_INVALID)) { TAILQ_REMOVE(&ipo->ipo_tdb->tdb_policy_head, ipo, ipo_tdb_next); ipo->ipo_tdb = NULL; } /* Outgoing packet policy check. */ if (direction == IPSP_DIRECTION_OUT) { /* * If the packet is destined for the policy-specified * gateway/endhost, and the socket has the BYPASS * option set, skip IPsec processing. */ if ((inp != NULL) && (inp->inp_seclevel[SL_ESP_TRANS] == IPSEC_LEVEL_BYPASS) && (inp->inp_seclevel[SL_ESP_NETWORK] == IPSEC_LEVEL_BYPASS) && (inp->inp_seclevel[SL_AUTH] == IPSEC_LEVEL_BYPASS)) { /* Direct match. */ if (dignore || !bcmp(&sdst, &ipo->ipo_dst, sdst.sa.sa_len)) { *error = 0; return NULL; } } /* Check that the cached TDB (if present), is appropriate. */ if (ipo->ipo_tdb) { if ((ipo->ipo_last_searched <= ipsec_last_added) || (ipo->ipo_sproto != ipo->ipo_tdb->tdb_sproto) || bcmp(dignore ? &sdst : &ipo->ipo_dst, &ipo->ipo_tdb->tdb_dst, ipo->ipo_tdb->tdb_dst.sa.sa_len)) goto nomatchout; if (!ipsp_aux_match(ipo->ipo_tdb, ipo->ipo_srcid, ipo->ipo_dstid, ipo->ipo_local_cred, NULL, &ipo->ipo_addr, &ipo->ipo_mask)) goto nomatchout; /* Cached entry is good. */ *error = 0; return ipsp_spd_inp(m, af, hlen, error, direction, tdbp, inp, ipo); nomatchout: /* Cached TDB was not good. */ TAILQ_REMOVE(&ipo->ipo_tdb->tdb_policy_head, ipo, ipo_tdb_next); ipo->ipo_tdb = NULL; ipo->ipo_last_searched = 0; } /* * If no SA has been added since the last time we did a * lookup, there's no point searching for one. However, if the * destination gateway is left unspecified (or is all-1's), * always lookup since this is a generic-match rule * (otherwise, we can have situations where SAs to some * destinations exist but are not used, possibly leading to an * explosion in the number of acquired SAs). */ if (ipo->ipo_last_searched <= ipsec_last_added) { /* "Touch" the entry. */ if (dignore == 0) ipo->ipo_last_searched = time_second; /* Find an appropriate SA from the existing ones. */ ipo->ipo_tdb = gettdbbyaddr(dignore ? &sdst : &ipo->ipo_dst, ipo->ipo_sproto, ipo->ipo_srcid, ipo->ipo_dstid, ipo->ipo_local_cred, m, af, &ipo->ipo_addr, &ipo->ipo_mask); if (ipo->ipo_tdb) { TAILQ_INSERT_TAIL(&ipo->ipo_tdb->tdb_policy_head, ipo, ipo_tdb_next); *error = 0; return ipsp_spd_inp(m, af, hlen, error, direction, tdbp, inp, ipo); } } /* So, we don't have an SA -- just a policy. */ switch (ipo->ipo_type) { case IPSP_IPSEC_REQUIRE: /* Acquire SA through key management. */ if (ipsp_acquire_sa(ipo, dignore ? &sdst : &ipo->ipo_dst, signore ? NULL : &ipo->ipo_src, ddst, m) != 0) { *error = EACCES; return NULL; } /* Fall through */ case IPSP_IPSEC_DONTACQ: *error = -EINVAL; /* Silently drop packet. */ return NULL; case IPSP_IPSEC_ACQUIRE: /* Acquire SA through key management. */ ipsp_acquire_sa(ipo, dignore ? &sdst : &ipo->ipo_dst, signore ? NULL : &ipo->ipo_src, ddst, NULL); /* Fall through */ case IPSP_IPSEC_USE: *error = 0; return ipsp_spd_inp(m, af, hlen, error, direction, tdbp, inp, ipo); } } else { /* IPSP_DIRECTION_IN */ if (tdbp != NULL) { /* Direct match in the cache. */ if (ipo->ipo_tdb == tdbp) { *error = 0; return ipsp_spd_inp(m, af, hlen, error, direction, tdbp, inp, ipo); } if (bcmp(dignore ? &ssrc : &ipo->ipo_dst, &tdbp->tdb_src, tdbp->tdb_src.sa.sa_len) || (ipo->ipo_sproto != tdbp->tdb_sproto)) goto nomatchin; /* Match source ID. */ if (ipo->ipo_srcid) { if (tdbp->tdb_dstid == NULL || !ipsp_ref_match(ipo->ipo_srcid, tdbp->tdb_dstid)) goto nomatchin; } /* Match destination ID. */ if (ipo->ipo_dstid) { if (tdbp->tdb_srcid == NULL || !ipsp_ref_match(ipo->ipo_dstid, tdbp->tdb_srcid)) goto nomatchin; } /* Add it to the cache. */ if (ipo->ipo_tdb) TAILQ_REMOVE(&ipo->ipo_tdb->tdb_policy_head, ipo, ipo_tdb_next); ipo->ipo_tdb = tdbp; TAILQ_INSERT_TAIL(&tdbp->tdb_policy_head, ipo, ipo_tdb_next); *error = 0; return ipsp_spd_inp(m, af, hlen, error, direction, tdbp, inp, ipo); nomatchin: /* Nothing needed here, falling through */ ; } /* Check whether cached entry applies. */ if (ipo->ipo_tdb) { /* * We only need to check that the correct * security protocol and security gateway are * set; credentials/IDs will be the same, * since the cached entry is linked on this * policy. */ if (ipo->ipo_sproto == ipo->ipo_tdb->tdb_sproto && !bcmp(&ipo->ipo_tdb->tdb_src, dignore ? &ssrc : &ipo->ipo_dst, ipo->ipo_tdb->tdb_src.sa.sa_len)) goto skipinputsearch; /* Not applicable, unlink. */ TAILQ_REMOVE(&ipo->ipo_tdb->tdb_policy_head, ipo, ipo_tdb_next); ipo->ipo_last_searched = 0; ipo->ipo_tdb = NULL; } /* Find whether there exists an appropriate SA. */ if (ipo->ipo_last_searched <= ipsec_last_added) { if (dignore == 0) ipo->ipo_last_searched = time_second; ipo->ipo_tdb = gettdbbysrc(dignore ? &ssrc : &ipo->ipo_dst, ipo->ipo_sproto, ipo->ipo_srcid, ipo->ipo_dstid, m, af, &ipo->ipo_addr, &ipo->ipo_mask); if (ipo->ipo_tdb) TAILQ_INSERT_TAIL(&ipo->ipo_tdb->tdb_policy_head, ipo, ipo_tdb_next); } skipinputsearch: switch (ipo->ipo_type) { case IPSP_IPSEC_REQUIRE: /* If appropriate SA exists, don't acquire another. */ if (ipo->ipo_tdb) { *error = -EINVAL; return NULL; } /* Acquire SA through key management. */ if ((*error = ipsp_acquire_sa(ipo, dignore ? &ssrc : &ipo->ipo_dst, signore ? NULL : &ipo->ipo_src, ddst, m)) != 0) return NULL; /* Fall through */ case IPSP_IPSEC_DONTACQ: /* Drop packet. */ *error = -EINVAL; return NULL; case IPSP_IPSEC_ACQUIRE: /* If appropriate SA exists, don't acquire another. */ if (ipo->ipo_tdb) { *error = 0; return ipsp_spd_inp(m, af, hlen, error, direction, tdbp, inp, ipo); } /* Acquire SA through key management. */ ipsp_acquire_sa(ipo, dignore ? &ssrc : &ipo->ipo_dst, signore ? NULL : &ipo->ipo_src, ddst, NULL); /* Fall through */ case IPSP_IPSEC_USE: *error = 0; return ipsp_spd_inp(m, af, hlen, error, direction, tdbp, inp, ipo); } } /* Shouldn't ever get this far. */ *error = EINVAL; return NULL; } /* * Delete a policy from the SPD. */ int ipsec_delete_policy(struct ipsec_policy *ipo) { struct ipsec_acquire *ipa; int err = 0; if (--ipo->ipo_ref_count > 0) return 0; /* Delete from SPD. */ if (!(ipo->ipo_flags & IPSP_POLICY_SOCKET)) err = rtrequest(RTM_DELETE, (struct sockaddr *) &ipo->ipo_addr, (struct sockaddr *) 0, (struct sockaddr *) &ipo->ipo_mask, 0, (struct rtentry **) 0); if (ipo->ipo_tdb != NULL) TAILQ_REMOVE(&ipo->ipo_tdb->tdb_policy_head, ipo, ipo_tdb_next); while ((ipa = TAILQ_FIRST(&ipo->ipo_acquires)) != NULL) ipsp_delete_acquire(ipa); TAILQ_REMOVE(&ipsec_policy_head, ipo, ipo_list); if (ipo->ipo_srcid) ipsp_reffree(ipo->ipo_srcid); if (ipo->ipo_dstid) ipsp_reffree(ipo->ipo_dstid); if (ipo->ipo_local_cred) ipsp_reffree(ipo->ipo_local_cred); if (ipo->ipo_local_auth) ipsp_reffree(ipo->ipo_local_auth); pool_put(&ipsec_policy_pool, ipo); if (!(ipo->ipo_flags & IPSP_POLICY_SOCKET)) ipsec_in_use--; return err; } /* * Add a policy to the SPD. */ struct ipsec_policy * ipsec_add_policy(struct inpcb *inp, int af, int direction) { struct ipsec_policy *ipon; if (ipsec_policy_pool_initialized == 0) { ipsec_policy_pool_initialized = 1; pool_init(&ipsec_policy_pool, sizeof(struct ipsec_policy), 0, 0, 0, "ipsec policy", NULL); } ipon = pool_get(&ipsec_policy_pool, 0); if (ipon == NULL) return NULL; bzero(ipon, sizeof(struct ipsec_policy)); ipon->ipo_ref_count = 1; ipon->ipo_flags |= IPSP_POLICY_SOCKET; ipon->ipo_type = IPSP_IPSEC_REQUIRE; /* XXX */ /* XXX * We should actually be creating a linked list of * policies (for tunnel/transport and ESP/AH), as needed. */ ipon->ipo_sproto = IPPROTO_ESP; TAILQ_INIT(&ipon->ipo_acquires); TAILQ_INSERT_HEAD(&ipsec_policy_head, ipon, ipo_list); ipsec_update_policy(inp, ipon, af, direction); return ipon; } /* * Update a PCB-attached policy. */ void ipsec_update_policy(struct inpcb *inp, struct ipsec_policy *ipon, int af, int direction) { ipon->ipo_addr.sen_len = ipon->ipo_mask.sen_len = SENT_LEN; ipon->ipo_addr.sen_family = ipon->ipo_mask.sen_family = PF_KEY; ipon->ipo_src.sa.sa_family = ipon->ipo_dst.sa.sa_family = af; switch (af) { case AF_INET: #ifdef INET ipon->ipo_addr.sen_type = ipon->ipo_mask.sen_type = SENT_IP4; ipon->ipo_addr.sen_ip_src = inp->inp_laddr; ipon->ipo_addr.sen_ip_dst = inp->inp_faddr; ipon->ipo_addr.sen_sport = inp->inp_lport; ipon->ipo_addr.sen_dport = inp->inp_fport; ipon->ipo_addr.sen_proto = inp->inp_socket->so_proto->pr_protocol; ipon->ipo_addr.sen_direction = direction; ipon->ipo_mask.sen_ip_src.s_addr = 0xffffffff; ipon->ipo_mask.sen_ip_dst.s_addr = 0xffffffff; ipon->ipo_mask.sen_sport = ipon->ipo_mask.sen_dport = 0xffff; ipon->ipo_mask.sen_proto = 0xff; ipon->ipo_mask.sen_direction = direction; ipon->ipo_src.sa.sa_len = sizeof(struct sockaddr_in); ipon->ipo_dst.sa.sa_len = sizeof(struct sockaddr_in); ipon->ipo_src.sin.sin_addr = inp->inp_laddr; ipon->ipo_dst.sin.sin_addr = inp->inp_faddr; #endif /* INET */ break; case AF_INET6: #ifdef INET6 ipon->ipo_addr.sen_type = ipon->ipo_mask.sen_type = SENT_IP6; ipon->ipo_addr.sen_ip6_src = inp->inp_laddr6; ipon->ipo_addr.sen_ip6_dst = inp->inp_faddr6; ipon->ipo_addr.sen_ip6_sport = inp->inp_lport; ipon->ipo_addr.sen_ip6_dport = inp->inp_fport; ipon->ipo_addr.sen_ip6_proto = inp->inp_socket->so_proto->pr_protocol; ipon->ipo_addr.sen_ip6_direction = direction; ipon->ipo_mask.sen_ip6_src = in6mask128; ipon->ipo_mask.sen_ip6_dst = in6mask128; ipon->ipo_mask.sen_ip6_sport = 0xffff; ipon->ipo_mask.sen_ip6_dport = 0xffff; ipon->ipo_mask.sen_ip6_proto = 0xff; ipon->ipo_mask.sen_ip6_direction = direction; ipon->ipo_src.sa.sa_len = sizeof(struct sockaddr_in6); ipon->ipo_dst.sa.sa_len = sizeof(struct sockaddr_in6); ipon->ipo_src.sin6.sin6_addr = inp->inp_laddr6; ipon->ipo_dst.sin6.sin6_addr = inp->inp_faddr6; #endif /* INET6 */ break; } } /* * Delete a pending IPsec acquire record. */ void ipsp_delete_acquire(void *v) { struct ipsec_acquire *ipa = v; timeout_del(&ipa->ipa_timeout); TAILQ_REMOVE(&ipsec_acquire_head, ipa, ipa_next); if (ipa->ipa_policy != NULL) TAILQ_REMOVE(&ipa->ipa_policy->ipo_acquires, ipa, ipa_ipo_next); pool_put(&ipsec_acquire_pool, ipa); } /* * Find out if there's an ACQUIRE pending. * XXX Need a better structure. */ struct ipsec_acquire * ipsp_pending_acquire(struct ipsec_policy *ipo, union sockaddr_union *gw) { struct ipsec_acquire *ipa; TAILQ_FOREACH (ipa, &ipo->ipo_acquires, ipa_ipo_next) { if (!bcmp(gw, &ipa->ipa_addr, gw->sa.sa_len)) return ipa; } return NULL; } /* * Signal key management that we need an SA. * XXX For outgoing policies, we could try to hold on to the mbuf. */ int ipsp_acquire_sa(struct ipsec_policy *ipo, union sockaddr_union *gw, union sockaddr_union *laddr, struct sockaddr_encap *ddst, struct mbuf *m) { struct ipsec_acquire *ipa; /* * If this is a socket policy, it has to have authentication * information accompanying it --- can't tell key mgmt. to * "find" it for us. This avoids abusing key mgmt. to authenticate * on an application's behalf, even if the application doesn't * have/know (and shouldn't) the appropriate authentication * material (passphrase, private key, etc.) */ if (ipo->ipo_flags & IPSP_POLICY_SOCKET && ipo->ipo_local_auth == NULL) return EINVAL; /* Check whether request has been made already. */ if ((ipa = ipsp_pending_acquire(ipo, gw)) != NULL) return 0; /* Add request in cache and proceed. */ if (ipsec_acquire_pool_initialized == 0) { ipsec_acquire_pool_initialized = 1; pool_init(&ipsec_acquire_pool, sizeof(struct ipsec_acquire), 0, 0, 0, "ipsec acquire", NULL); } ipa = pool_get(&ipsec_acquire_pool, 0); if (ipa == NULL) return ENOMEM; bzero(ipa, sizeof(struct ipsec_acquire)); bcopy(gw, &ipa->ipa_addr, sizeof(union sockaddr_union)); timeout_set(&ipa->ipa_timeout, ipsp_delete_acquire, ipa); ipa->ipa_info.sen_len = ipa->ipa_mask.sen_len = SENT_LEN; ipa->ipa_info.sen_family = ipa->ipa_mask.sen_family = PF_KEY; /* Just copy the right information. */ switch (ipo->ipo_addr.sen_type) { #ifdef INET case SENT_IP4: ipa->ipa_info.sen_type = ipa->ipa_mask.sen_type = SENT_IP4; ipa->ipa_info.sen_direction = ipo->ipo_addr.sen_direction; ipa->ipa_mask.sen_direction = ipo->ipo_mask.sen_direction; if (ipo->ipo_mask.sen_ip_src.s_addr == INADDR_ANY || ipo->ipo_addr.sen_ip_src.s_addr == INADDR_ANY || ipsp_is_unspecified(ipo->ipo_dst)) { ipa->ipa_info.sen_ip_src = ddst->sen_ip_src; ipa->ipa_mask.sen_ip_src.s_addr = INADDR_BROADCAST; } else { ipa->ipa_info.sen_ip_src = ipo->ipo_addr.sen_ip_src; ipa->ipa_mask.sen_ip_src = ipo->ipo_mask.sen_ip_src; } if (ipo->ipo_mask.sen_ip_dst.s_addr == INADDR_ANY || ipo->ipo_addr.sen_ip_dst.s_addr == INADDR_ANY || ipsp_is_unspecified(ipo->ipo_dst)) { ipa->ipa_info.sen_ip_dst = ddst->sen_ip_dst; ipa->ipa_mask.sen_ip_dst.s_addr = INADDR_BROADCAST; } else { ipa->ipa_info.sen_ip_dst = ipo->ipo_addr.sen_ip_dst; ipa->ipa_mask.sen_ip_dst = ipo->ipo_mask.sen_ip_dst; } ipa->ipa_info.sen_proto = ipo->ipo_addr.sen_proto; ipa->ipa_mask.sen_proto = ipo->ipo_mask.sen_proto; if (ipo->ipo_addr.sen_proto) { ipa->ipa_info.sen_sport = ipo->ipo_addr.sen_sport; ipa->ipa_mask.sen_sport = ipo->ipo_mask.sen_sport; ipa->ipa_info.sen_dport = ipo->ipo_addr.sen_dport; ipa->ipa_mask.sen_dport = ipo->ipo_mask.sen_dport; } break; #endif /* INET */ #ifdef INET6 case SENT_IP6: ipa->ipa_info.sen_type = ipa->ipa_mask.sen_type = SENT_IP6; ipa->ipa_info.sen_ip6_direction = ipo->ipo_addr.sen_ip6_direction; ipa->ipa_mask.sen_ip6_direction = ipo->ipo_mask.sen_ip6_direction; if (IN6_IS_ADDR_UNSPECIFIED(&ipo->ipo_mask.sen_ip6_src) || IN6_IS_ADDR_UNSPECIFIED(&ipo->ipo_addr.sen_ip6_src) || ipsp_is_unspecified(ipo->ipo_dst)) { ipa->ipa_info.sen_ip6_src = ddst->sen_ip6_src; ipa->ipa_mask.sen_ip6_src = in6mask128; } else { ipa->ipa_info.sen_ip6_src = ipo->ipo_addr.sen_ip6_src; ipa->ipa_mask.sen_ip6_src = ipo->ipo_mask.sen_ip6_src; } if (IN6_IS_ADDR_UNSPECIFIED(&ipo->ipo_mask.sen_ip6_dst) || IN6_IS_ADDR_UNSPECIFIED(&ipo->ipo_addr.sen_ip6_dst) || ipsp_is_unspecified(ipo->ipo_dst)) { ipa->ipa_info.sen_ip6_dst = ddst->sen_ip6_dst; ipa->ipa_mask.sen_ip6_dst = in6mask128; } else { ipa->ipa_info.sen_ip6_dst = ipo->ipo_addr.sen_ip6_dst; ipa->ipa_mask.sen_ip6_dst = ipo->ipo_mask.sen_ip6_dst; } ipa->ipa_info.sen_ip6_proto = ipo->ipo_addr.sen_ip6_proto; ipa->ipa_mask.sen_ip6_proto = ipo->ipo_mask.sen_ip6_proto; if (ipo->ipo_mask.sen_ip6_proto) { ipa->ipa_info.sen_ip6_sport = ipo->ipo_addr.sen_ip6_sport; ipa->ipa_mask.sen_ip6_sport = ipo->ipo_mask.sen_ip6_sport; ipa->ipa_info.sen_ip6_dport = ipo->ipo_addr.sen_ip6_dport; ipa->ipa_mask.sen_ip6_dport = ipo->ipo_mask.sen_ip6_dport; } break; #endif /* INET6 */ default: pool_put(&ipsec_acquire_pool, ipa); return 0; } timeout_add(&ipa->ipa_timeout, ipsec_expire_acquire * hz); TAILQ_INSERT_TAIL(&ipsec_acquire_head, ipa, ipa_next); TAILQ_INSERT_TAIL(&ipo->ipo_acquires, ipa, ipa_ipo_next); ipa->ipa_policy = ipo; /* PF_KEYv2 notification message. */ return pfkeyv2_acquire(ipo, gw, laddr, &ipa->ipa_seq, ddst); } /* * Deal with PCB security requirements. */ struct tdb * ipsp_spd_inp(struct mbuf *m, int af, int hlen, int *error, int direction, struct tdb *tdbp, struct inpcb *inp, struct ipsec_policy *ipo) { struct ipsec_policy sipon; struct tdb_ident *tdbi; struct m_tag *mtag; struct tdb *tdb = NULL; /* Sanity check. */ if (inp == NULL) goto justreturn; /* Verify that we need to check for socket policy. */ if ((inp->inp_seclevel[SL_ESP_TRANS] == IPSEC_LEVEL_BYPASS || inp->inp_seclevel[SL_ESP_TRANS] == IPSEC_LEVEL_NONE) && (inp->inp_seclevel[SL_ESP_NETWORK] == IPSEC_LEVEL_BYPASS || inp->inp_seclevel[SL_ESP_NETWORK] == IPSEC_LEVEL_NONE) && (inp->inp_seclevel[SL_AUTH] == IPSEC_LEVEL_BYPASS || inp->inp_seclevel[SL_AUTH] == IPSEC_LEVEL_NONE)) goto justreturn; switch (direction) { case IPSP_DIRECTION_IN: /* * Some further checking: if the socket has specified * that it will accept unencrypted traffic, don't * bother checking any further -- just accept the packet. */ if ((inp->inp_seclevel[SL_ESP_TRANS] == IPSEC_LEVEL_AVAIL || inp->inp_seclevel[SL_ESP_TRANS] == IPSEC_LEVEL_USE) && (inp->inp_seclevel[SL_ESP_NETWORK] == IPSEC_LEVEL_AVAIL || inp->inp_seclevel[SL_ESP_NETWORK] == IPSEC_LEVEL_USE) && (inp->inp_seclevel[SL_AUTH] == IPSEC_LEVEL_AVAIL || inp->inp_seclevel[SL_AUTH] == IPSEC_LEVEL_USE)) goto justreturn; /* Initialize socket policy if unset. */ if (inp->inp_ipo == NULL) { inp->inp_ipo = ipsec_add_policy(inp, af, IPSP_DIRECTION_OUT); if (inp->inp_ipo == NULL) { *error = ENOBUFS; return NULL; } } /* * So we *must* have protected traffic. Let's see what * we have received then. */ if (inp->inp_tdb_in != NULL) { if (inp->inp_tdb_in == tdbp) goto justreturn; /* We received packet under a * previously-accepted TDB. */ /* * We should be receiving protected traffic, and * have an SA in place, but packet was received * unprotected. Simply discard. */ if (tdbp == NULL) { *error = -EINVAL; return NULL; } /* Update, since we may need all the relevant info. */ ipsec_update_policy(inp, inp->inp_ipo, af, IPSP_DIRECTION_OUT); /* * Check that the TDB the packet was received under * is acceptable under the socket policy. If so, * accept the packet; otherwise, discard. */ if (tdbp->tdb_sproto == inp->inp_ipo->ipo_sproto && !bcmp(&tdbp->tdb_src, &inp->inp_ipo->ipo_dst, SA_LEN(&tdbp->tdb_src.sa)) && ipsp_aux_match(tdbp, inp->inp_ipo->ipo_srcid, inp->inp_ipo->ipo_dstid, NULL, NULL, &inp->inp_ipo->ipo_addr, &inp->inp_ipo->ipo_mask)) goto justreturn; else { *error = -EINVAL; return NULL; } } else { /* Update, since we may need all the relevant info. */ ipsec_update_policy(inp, inp->inp_ipo, af, IPSP_DIRECTION_OUT); /* * If the packet was received under an SA, see if * it's acceptable under socket policy. If it is, * accept the packet. */ if (tdbp != NULL && tdbp->tdb_sproto == inp->inp_ipo->ipo_sproto && !bcmp(&tdbp->tdb_src, &inp->inp_ipo->ipo_dst, SA_LEN(&tdbp->tdb_src.sa)) && ipsp_aux_match(tdbp, inp->inp_ipo->ipo_srcid, inp->inp_ipo->ipo_dstid, NULL, NULL, &inp->inp_ipo->ipo_addr, &inp->inp_ipo->ipo_mask)) goto justreturn; /* * If the packet was not received under an SA, or * if the SA it was received under is not acceptable, * see if we already have an acceptable SA * established. If we do, discard packet. */ if (inp->inp_ipo->ipo_last_searched <= ipsec_last_added) { inp->inp_ipo->ipo_last_searched = time_second; /* Do we have an SA already established ? */ if (gettdbbysrc(&inp->inp_ipo->ipo_dst, inp->inp_ipo->ipo_sproto, inp->inp_ipo->ipo_srcid, inp->inp_ipo->ipo_dstid, m, af, &inp->inp_ipo->ipo_addr, &inp->inp_ipo->ipo_mask) != NULL) { *error = -EINVAL; return NULL; } /* Fall through */ } /* * If we don't have an appropriate SA, acquire one * and discard the packet. */ ipsp_acquire_sa(inp->inp_ipo, &inp->inp_ipo->ipo_dst, &inp->inp_ipo->ipo_src, &inp->inp_ipo->ipo_addr, m); *error = -EINVAL; return NULL; } break; case IPSP_DIRECTION_OUT: /* Do we have a cached entry ? */ if (inp->inp_tdb_out != NULL) { /* * If we also have to apply a different TDB as * a result of a system-wide policy, add a tag * to the packet. */ if (ipo != NULL && m != NULL && ipo->ipo_tdb != NULL && ipo->ipo_tdb != inp->inp_tdb_out) { tdb = inp->inp_tdb_out; goto tagandreturn; } else return inp->inp_tdb_out; } /* * We need to either find an SA with the appropriate * characteristics and link it to the PCB, or acquire * one. */ /* XXX Only support one policy/protocol for now. */ if (inp->inp_ipo != NULL) { if (inp->inp_ipo->ipo_last_searched <= ipsec_last_added) { inp->inp_ipo->ipo_last_searched = time_second; /* Update, just in case. */ ipsec_update_policy(inp, inp->inp_ipo, af, IPSP_DIRECTION_OUT); tdb = gettdbbyaddr(&inp->inp_ipo->ipo_dst, inp->inp_ipo->ipo_sproto, inp->inp_ipo->ipo_srcid, inp->inp_ipo->ipo_dstid, inp->inp_ipo->ipo_local_cred, m, af, &inp->inp_ipo->ipo_addr, &inp->inp_ipo->ipo_mask); } } else { /* * Construct a pseudo-policy, with just the necessary * fields. */ ipsec_update_policy(inp, &sipon, af, IPSP_DIRECTION_OUT); tdb = gettdbbyaddr(&sipon.ipo_dst, IPPROTO_ESP, NULL, NULL, NULL, m, af, &sipon.ipo_addr, &sipon.ipo_mask); } /* If we found an appropriate SA... */ if (tdb != NULL) { tdb_add_inp(tdb, inp, 0); /* Latch onto PCB. */ if (ipo != NULL && ipo->ipo_tdb != NULL && ipo->ipo_tdb != inp->inp_tdb_out && m != NULL) goto tagandreturn; else return tdb; } else { /* Do we need to acquire one ? */ switch (inp->inp_seclevel[SL_ESP_TRANS]) { case IPSEC_LEVEL_BYPASS: case IPSEC_LEVEL_AVAIL: /* No need to do anything. */ goto justreturn; case IPSEC_LEVEL_USE: case IPSEC_LEVEL_REQUIRE: case IPSEC_LEVEL_UNIQUE: /* Initialize socket policy if unset. */ if (inp->inp_ipo == NULL) { inp->inp_ipo = ipsec_add_policy(inp, af, IPSP_DIRECTION_OUT); if (inp->inp_ipo == NULL) { *error = ENOBUFS; return NULL; } } /* Acquire a new SA. */ if ((*error = ipsp_acquire_sa(inp->inp_ipo, &inp->inp_ipo->ipo_dst, &inp->inp_ipo->ipo_src, &inp->inp_ipo->ipo_addr, m)) == 0) *error = -EINVAL; return NULL; default: DPRINTF(("ipsp_spd_inp: unknown sock security" " level %d", inp->inp_seclevel[SL_ESP_TRANS])); *error = -EINVAL; return NULL; } } break; default: /* Should never happen. */ *error = -EINVAL; return NULL; } tagandreturn: if (tdb == NULL) goto justreturn; mtag = m_tag_get(PACKET_TAG_IPSEC_PENDING_TDB, sizeof (struct tdb_ident), M_NOWAIT); if (mtag == NULL) { *error = ENOMEM; return NULL; } tdbi = (struct tdb_ident *)(mtag + 1); tdbi->spi = ipo->ipo_tdb->tdb_spi; tdbi->proto = ipo->ipo_tdb->tdb_sproto; bcopy(&ipo->ipo_tdb->tdb_dst, &tdbi->dst, ipo->ipo_tdb->tdb_dst.sa.sa_len); m_tag_prepend(m, mtag); return tdb; justreturn: if (ipo != NULL) return ipo->ipo_tdb; else return NULL; } /* * Find a pending ACQUIRE record based on its sequence number. * XXX Need to use a better data structure. */ struct ipsec_acquire * ipsec_get_acquire(u_int32_t seq) { struct ipsec_acquire *ipa; TAILQ_FOREACH (ipa, &ipsec_acquire_head, ipa_next) if (ipa->ipa_seq == seq) return ipa; return NULL; }