File: [local] / src / sys / netinet / ip_spd.c (download)
Revision 1.18, Mon Apr 23 10:00:09 2001 UTC (23 years, 1 month ago) by art
Branch: MAIN
CVS Tags: OPENBSD_2_9_BASE, OPENBSD_2_9 Changes since 1.17: +3 -1 lines
Missing splx in error handling.
|
/* $OpenBSD: ip_spd.c,v 1.18 2001/04/23 10:00:09 art Exp $ */
/*
* The author of this code is Angelos D. Keromytis (angelos@cis.upenn.edu)
*
* Copyright (c) 2000 Angelos D. Keromytis.
*
* Permission to use, copy, and modify this software 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/malloc.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <sys/errno.h>
#include <sys/kernel.h>
#include <sys/queue.h>
#include <machine/cpu.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 <net/pfkeyv2.h>
#include <netinet/ip_ipsp.h>
#ifdef ENCDEBUG
#define DPRINTF(x) if (encdebug) printf x
#else
#define DPRINTF(x)
#endif
/*
* 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:
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:
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);
ssrc.sin6.sin6_addr = ddst->sen_ip6_src;
sdst.sin6.sin6_addr = ddst->sen_ip6_dst;
/* 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)
{
*error = 0;
return NULL; /* Nothing found -- means no IPsec needed */
}
/* Sanity check */
if ((re->re_rt->rt_gateway == NULL) ||
(((struct sockaddr_encap *) re->re_rt->rt_gateway)->sen_type !=
SENT_IPSP))
{
DPRINTF(("ipsp_spd_lookup(): no gw, or gw data not IPSP\n"));
RTFREE(re->re_rt);
*error = EHOSTUNREACH;
return NULL;
}
ipo = ((struct sockaddr_encap *) (re->re_rt->rt_gateway))->sen_ipsp;
RTFREE(re->re_rt);
if (ipo == NULL)
{
DPRINTF(("ipsp_spd_lookup(): no policy present\n"));
*error = EHOSTUNREACH;
return NULL;
}
switch (ipo->ipo_type)
{
case IPSP_PERMIT:
*error = 0;
return NULL;
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. If a specific
* destination was specified, use that -- otherwise, use the relevant
* information from the packet.
*/
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))
{
if (direction == IPSP_DIRECTION_OUT)
bcopy(&ipo->ipo_dst, &sdst, sizeof(union sockaddr_union));
else
bcopy(&ipo->ipo_dst, &ssrc, sizeof(union sockaddr_union));
}
else
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))))
{
if (direction == IPSP_DIRECTION_OUT)
bcopy(&ipo->ipo_dst, &sdst, sizeof(union sockaddr_union));
else
bcopy(&ipo->ipo_dst, &ssrc, sizeof(union sockaddr_union));
}
else
dignore = 1;
break;
#endif /* INET6 */
}
switch (ipo->ipo_src.sa.sa_family)
{
#ifdef INET
case AF_INET:
if (ipo->ipo_src.sin.sin_addr.s_addr != INADDR_ANY)
{
if (direction == IPSP_DIRECTION_OUT)
bcopy(&ipo->ipo_src, &ssrc, sizeof(union sockaddr_union));
else
bcopy(&ipo->ipo_src, &sdst, sizeof(union sockaddr_union));
}
else
signore = 1;
break;
#endif /* INET */
#ifdef INET6
case AF_INET6:
if (!IN6_IS_ADDR_UNSPECIFIED(&ipo->ipo_src.sin6.sin6_addr))
{
if (direction == IPSP_DIRECTION_OUT)
bcopy(&ipo->ipo_src, &ssrc, sizeof(union sockaddr_union));
else
bcopy(&ipo->ipo_src, &sdst, sizeof(union sockaddr_union));
}
else
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 SPD lookup */
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 (bcmp(&sdst, &ipo->ipo_dst, sdst.sa.sa_len) == 0)
{
*error = 0;
return NULL;
}
/* Same-host */
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))
{
*error = 0;
return NULL;
}
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)))
{
*error = 0;
return NULL;
}
break;
#endif /* INET6 */
}
}
/* Check that the cached TDB (if present), is appropriate */
if (ipo->ipo_tdb)
{
if (bcmp(&sdst, &ipo->ipo_tdb->tdb_dst, sdst.sa.sa_len) ||
(ipo->ipo_last_searched <= ipsec_last_added))
{
TAILQ_REMOVE(&ipo->ipo_tdb->tdb_policy_head, ipo,
ipo_tdb_next);
ipo->ipo_tdb = NULL;
ipo->ipo_last_searched = 0;
/* Fall through to acquisition of TDB */
}
else
{
return ipo->ipo_tdb; /* Cached entry is good, we're done */
}
}
/*
* 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 (
#ifdef INET
((ipo->ipo_dst.sa.sa_family == AF_INET) &&
(ipo->ipo_dst.sin.sin_addr.s_addr != INADDR_ANY) &&
(ipo->ipo_dst.sin.sin_addr.s_addr != INADDR_BROADCAST)) ||
#endif /* INET */
#ifdef INET6
((ipo->ipo_dst.sa.sa_family == AF_INET6) &&
!IN6_IS_ADDR_UNSPECIFIED(&ipo->ipo_dst.sin6.sin6_addr) &&
bcmp(&ipo->ipo_dst.sin6.sin6_addr, &in6mask128,
sizeof(in6mask128))) ||
#endif /* INET6 */
0)
{
if (ipo->ipo_last_searched <= ipsec_last_added)
{
ipo->ipo_last_searched = time.tv_sec; /* "touch" the entry */
/* Find an appropriate SA from among the existing SAs */
ipo->ipo_tdb = gettdbbyaddr(&sdst, ipo->ipo_sproto, m, af);
if (ipo->ipo_tdb)
{
TAILQ_INSERT_TAIL(&ipo->ipo_tdb->tdb_policy_head, ipo,
ipo_tdb_next);
*error = 0;
return ipo->ipo_tdb;
}
}
}
else
{
ipo->ipo_last_searched = time.tv_sec; /* "touch" the entry */
/* Find an appropriate SA from among the existing SAs */
ipo->ipo_tdb = gettdbbyaddr(&sdst, ipo->ipo_sproto, m, af);
if (ipo->ipo_tdb)
{
TAILQ_INSERT_TAIL(&ipo->ipo_tdb->tdb_policy_head, ipo,
ipo_tdb_next);
*error = 0;
return ipo->ipo_tdb;
}
}
/* 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, &sdst, signore ? NULL : &ssrc,
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 */
if (ipsp_acquire_sa(ipo, &sdst, signore ? NULL : &ssrc,
ddst, NULL) != 0)
{
*error = EACCES;
return NULL;
}
/* Fall through */
case IPSP_IPSEC_USE:
*error = 0; /* Let packet through */
return NULL;
}
}
else /* IPSP_DIRECTION_IN */
{
/* Check the cached entry */
if ((ipo->ipo_tdb) &&
(((ipo->ipo_tdb->tdb_src.sa.sa_family != 0) &&
bcmp(&ssrc, &ipo->ipo_tdb->tdb_src, ssrc.sa.sa_len)) ||
(ipo->ipo_last_searched <= ipsec_last_added)))
{
TAILQ_REMOVE(&ipo->ipo_tdb->tdb_policy_head, ipo, ipo_tdb_next);
ipo->ipo_tdb = NULL;
ipo->ipo_last_searched = 0;
}
switch (ipo->ipo_type)
{
case IPSP_IPSEC_DONTACQ:
/* Does protection match stated policy ? */
if (tdbp && ipsp_match_policy(tdbp, ipo, m, af))
{
/* Accept packet */
*error = 0;
return NULL;
}
/* Silently drop packet */
*error = EHOSTUNREACH;
return NULL;
case IPSP_IPSEC_REQUIRE:
if (tdbp && ipsp_match_policy(tdbp, ipo, m, af))
{
/* Accept packet */
*error = 0;
return NULL;
}
/* If we have a cached entry, just discard the packet */
if (ipo->ipo_tdb)
{
*error = EHOSTUNREACH;
return NULL;
}
/*
* Find whether there exists an appropriate SA. If so, drop
* the packet. Otherwise, try to acquire one (from below).
*
* If no SA has been added since the last time we did a lookup,
* there's no point searching for one.
*/
if (ipo->ipo_last_searched <= ipsec_last_added)
{
ipo->ipo_last_searched = time.tv_sec; /* "touch" */
if ((ipo->ipo_tdb = gettdbbysrc(&ssrc, ipo->ipo_sproto,
m, af)) != NULL)
{
TAILQ_INSERT_TAIL(&ipo->ipo_tdb->tdb_policy_head, ipo,
ipo_tdb_next);
*error = EHOSTUNREACH;
return NULL;
}
}
/* Acquire SA through key management */
if ((*error = ipsp_acquire_sa(ipo, &ssrc,
dignore ? NULL : &sdst,
ddst, m)) != 0)
return NULL;
*error = -EINVAL;
return NULL;
case IPSP_IPSEC_USE:
/*
* It doesn't matter what protection it had (if any),
* just accept it -- equivalent to PERMIT for input.
* This means we can't say that we want in incoming
* packet to be unprotected -- at least not directly;
* we can always have a DENY policy for ESP/AH packets.
*/
*error = 0;
return NULL;
case IPSP_IPSEC_ACQUIRE:
/*
* We don't check for policy match, since we would
* accept clear-text packets as well.
*/
/* If we have a cached entry, just accept the packet */
if (ipo->ipo_tdb)
{
*error = 0;
return NULL;
}
/*
* Find whether there exists an appropriate SA. If so, accept
* the packet. Otherwise, try to acquire one (from below).
*
* If no SA has been added since the last time we did a lookup,
* there's no point searching for one.
*/
if (ipo->ipo_last_searched <= ipsec_last_added)
{
ipo->ipo_last_searched = time.tv_sec; /* "touch" */
if ((ipo->ipo_tdb = gettdbbysrc(&ssrc, ipo->ipo_sproto,
m, af)) != NULL)
{
TAILQ_INSERT_TAIL(&ipo->ipo_tdb->tdb_policy_head, ipo,
ipo_tdb_next);
*error = 0;
return NULL;
}
}
/* Acquire SA through key management */
if ((*error = ipsp_acquire_sa(ipo, &ssrc,
dignore ? NULL : &sdst,
ddst, NULL)) != 0)
return NULL;
/* Just accept the packet */
*error = 0;
return NULL;
}
}
/* Shouldn't ever get this far */
*error = EINVAL;
return NULL;
}
/*
* See if a specific SA satisfies stated policy. Return 0 if false, 1 (or
* non-zero) otherwise.
*/
int
ipsp_match_policy(struct tdb *tdb, struct ipsec_policy *ipo,
struct mbuf *m, int af)
{
union sockaddr_union peer;
int pflag = 0;
switch (ipo->ipo_dst.sa.sa_family)
{
#ifdef INET
case AF_INET:
if (ipo->ipo_dst.sin.sin_addr.s_addr == INADDR_ANY)
pflag = 1;
else
if (ipo->ipo_dst.sin.sin_addr.s_addr == INADDR_BROADCAST)
pflag = 2;
break;
#endif /* INET */
#ifdef INET6
case AF_INET6:
if (IN6_IS_ADDR_UNSPECIFIED(&ipo->ipo_dst.sin6.sin6_addr))
pflag = 1;
else
if (!bcmp(&ipo->ipo_dst.sin6.sin6_addr, &in6mask128,
sizeof(in6mask128)))
pflag = 2;
break;
#endif /* INET6 */
case 0: /* Just in case */
pflag = 1;
break;
default:
return 0; /* Unknown/unsupported network protocol */
}
if (pflag == 0)
{
bcopy(&ipo->ipo_dst, &peer, sizeof(union sockaddr_union));
}
else
if (pflag == 1)
{
bzero(&peer, sizeof(union sockaddr_union));
/* Need to copy the source address from the packet */
switch (af)
{
#ifdef INET
case AF_INET:
peer.sin.sin_family = AF_INET;
peer.sin.sin_len = sizeof(struct sockaddr_in);
m_copydata(m, offsetof(struct ip, ip_src),
sizeof(struct in_addr),
(caddr_t) &peer.sin.sin_addr);
break;
#endif /* INET */
#ifdef INET6
case AF_INET6:
peer.sin6.sin6_family = AF_INET6;
peer.sin6.sin6_len = sizeof(struct sockaddr_in6);
m_copydata(m, offsetof(struct ip6_hdr, ip6_src),
sizeof(struct in6_addr),
(caddr_t) &peer.sin6.sin6_addr);
break;
#endif /* INET6 */
default:
return 0; /* Unknown/unsupported network protocol */
}
}
/*
* Does the packet use the right security protocol and is coming from
* the right peer ?
*/
if (tdb->tdb_sproto == ipo->ipo_sproto)
{
/*
* We accept any peer that has a valid SA with us -- this means
* we depend on the higher-level (key mgmt.) protocol to enforce
* policy.
*/
if (pflag == 2)
return 1;
if (bcmp(&tdb->tdb_src, &peer, tdb->tdb_src.sa.sa_len))
{
switch (tdb->tdb_src.sa.sa_family)
{
#ifdef INET
case AF_INET:
if (tdb->tdb_src.sin.sin_addr.s_addr == INADDR_ANY)
return 1;
else
return 0;
#endif /* INET */
#ifdef INET6
case AF_INET6:
if (IN6_IS_ADDR_UNSPECIFIED(&tdb->tdb_src.sin6.sin6_addr))
return 1;
else
return 0;
#endif /* INET6 */
case 0:
return 1;
default:
return 0;
}
}
else
return 1;
}
return 0;
}
/*
* Delete a policy from the SPD.
*/
int
ipsec_delete_policy(struct ipsec_policy *ipo)
{
int err;
/* Delete */
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)
TAILQ_REMOVE(&ipo->ipo_tdb->tdb_policy_head, ipo, ipo_tdb_next);
TAILQ_REMOVE(&ipsec_policy_head, ipo, ipo_list);
if (ipo->ipo_srcid)
FREE(ipo->ipo_srcid, M_TEMP);
if (ipo->ipo_dstid)
FREE(ipo->ipo_dstid, M_TEMP);
FREE(ipo, M_TDB);
ipsec_in_use--;
return err;
}
/*
* Add a policy to the SPD.
*/
struct ipsec_policy *
ipsec_add_policy(struct sockaddr_encap *dst, struct sockaddr_encap *mask,
union sockaddr_union *sdst, int type, int sproto)
{
struct sockaddr_encap encapgw;
struct ipsec_policy *ipon;
MALLOC(ipon, struct ipsec_policy *, sizeof(struct ipsec_policy), M_TDB,
M_NOWAIT);
if (ipon == NULL)
return NULL;
bzero(ipon, sizeof(struct ipsec_policy));
bzero((caddr_t) &encapgw, sizeof(struct sockaddr_encap));
encapgw.sen_len = SENT_LEN;
encapgw.sen_family = PF_KEY;
encapgw.sen_type = SENT_IPSP;
encapgw.sen_ipsp = ipon;
if (rtrequest(RTM_ADD, (struct sockaddr *) dst,
(struct sockaddr *) &encapgw, (struct sockaddr *) mask,
RTF_UP | RTF_GATEWAY | RTF_STATIC,
(struct rtentry **) 0) != 0)
{
DPRINTF(("ipsec_add_policy: failed to add policy\n"));
FREE(ipon, M_TDB);
return NULL;
}
ipsec_in_use++;
bcopy(dst, &ipon->ipo_addr, sizeof(struct sockaddr_encap));
bcopy(mask, &ipon->ipo_mask, sizeof(struct sockaddr_encap));
bcopy(sdst, &ipon->ipo_dst, sizeof(union sockaddr_union));
ipon->ipo_sproto = sproto;
ipon->ipo_type = type;
TAILQ_INSERT_HEAD(&ipsec_policy_head, ipon, ipo_list);
return ipon;
}
/*
* Delete a pending ACQUIRE record.
*/
void
ipsp_delete_acquire(struct ipsec_acquire *ipa)
{
TAILQ_REMOVE(&ipsec_acquire_head, ipa, ipa_next);
if (ipa->ipa_packet)
m_freem(ipa->ipa_packet);
FREE(ipa, M_TDB);
}
/*
* Clear possibly pending ACQUIRE records.
*/
void
ipsp_clear_acquire(struct tdb *tdb)
{
struct ipsec_acquire *ipa;
struct ifqueue *ifq;
int s;
while ((ipa = ipsp_pending_acquire(&tdb->tdb_dst)) != NULL)
{
/* Retransmit */
if (ipa->ipa_packet)
{
switch (ipa->ipa_info.sen_type)
{
#ifdef INET
case SENT_IP4:
{
struct ip *ip;
switch (ipa->ipa_info.sen_direction)
{
case IPSP_DIRECTION_OUT:
ip = mtod(ipa->ipa_packet, struct ip *);
if (ipa->ipa_packet->m_len < sizeof(struct ip))
break;
/* Same as in ip_output() -- massage the header */
ip->ip_len = htons((u_short) ip->ip_len);
ip->ip_off = htons((u_short) ip->ip_off);
ipa->ipa_packet->m_flags &= ~(M_MCAST | M_BCAST);
ipsp_process_packet(ipa->ipa_packet, tdb,
AF_INET, 0, NULL);
ipa->ipa_packet = NULL;
break;
case IPSP_DIRECTION_IN:
ifq = &ipintrq;
s = splimp();
if (IF_QFULL(ifq))
{
IF_DROP(ifq);
splx(s);
break;
}
IF_ENQUEUE(ifq, ipa->ipa_packet);
ipa->ipa_packet = NULL;
schednetisr(NETISR_IP);
splx(s);
break;
}
}
break;
#endif /* INET */
#ifdef INET6
case SENT_IP6:
switch (ipa->ipa_info.sen_ip6_direction)
{
case IPSP_DIRECTION_OUT:
ipa->ipa_packet->m_flags &= ~(M_BCAST | M_MCAST);
ipsp_process_packet(ipa->ipa_packet, tdb,
AF_INET6, 0, NULL);
ipa->ipa_packet = NULL;
break;
case IPSP_DIRECTION_IN:
ifq = &ip6intrq;
s = splimp();
if (IF_QFULL(ifq))
{
IF_DROP(ifq);
splx(s);
break;
}
IF_ENQUEUE(ifq, ipa->ipa_packet);
ipa->ipa_packet = NULL;
schednetisr(NETISR_IPV6);
splx(s);
break;
}
break;
#endif /* INET6 */
}
}
ipsp_delete_acquire(ipa);
}
}
/*
* Expire old acquire requests to key management.
*/
void
ipsp_acquire_expirations(void *arg)
{
struct ipsec_acquire *ipa;
for (ipa = TAILQ_FIRST(&ipsec_acquire_head);
ipa;
ipa = TAILQ_FIRST(&ipsec_acquire_head))
{
if (ipa->ipa_expire <= time.tv_sec)
ipsp_delete_acquire(ipa); /* Delete */
else
{
/* Schedule us for another expiration */
timeout(ipsp_acquire_expirations, (void *) NULL,
hz * (ipa->ipa_expire - time.tv_sec));
return;
}
}
/* If there's no request pending, we don't need to schedule us */
return;
}
/*
* Find out if there's an ACQUIRE pending.
* XXX Need a better structure.
*/
struct ipsec_acquire *
ipsp_pending_acquire(union sockaddr_union *gw)
{
struct ipsec_acquire *ipa;
for (ipa = TAILQ_FIRST(&ipsec_acquire_head);
ipa;
ipa = TAILQ_NEXT(ipa, ipa_next))
{
if (!bcmp(gw, &ipa->ipa_addr, gw->sa.sa_len))
return ipa;
}
return NULL;
}
/*
* Signal key management that we need an SA. If we're given an mbuf, store
* it and retransmit the packet if/when we have an SA in place.
*/
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;
#ifdef INET6
int i;
#endif
/* Check whether request has been made already. */
if ((ipa = ipsp_pending_acquire(gw)) != NULL)
{
if (ipa->ipa_packet && m)
{
m_freem(ipa->ipa_packet);
ipa->ipa_packet = m_copym2(m, 0, M_COPYALL, M_DONTWAIT);
}
return 0;
}
/* Add request in cache and proceed */
MALLOC(ipa, struct ipsec_acquire *, sizeof(struct ipsec_acquire),
M_TDB, M_DONTWAIT);
if (ipa == NULL)
return ENOMEM;
bzero(ipa, sizeof(struct ipsec_acquire));
bcopy(gw, &ipa->ipa_addr, sizeof(union sockaddr_union));
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 ||
ipo->ipo_dst.sa.sa_family == 0)
{
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 ||
ipo->ipo_dst.sa.sa_family == 0)
{
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) ||
ipo->ipo_dst.sa.sa_family == 0)
{
ipa->ipa_info.sen_ip6_src = ddst->sen_ip6_src;
for (i = 0; i < 16; i++)
ipa->ipa_mask.sen_ip6_src.s6_addr8[i] = 0xff;
}
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) ||
ipo->ipo_dst.sa.sa_family == 0)
{
ipa->ipa_info.sen_ip6_dst = ddst->sen_ip6_dst;
for (i = 0; i < 16; i++)
ipa->ipa_mask.sen_ip6_dst.s6_addr8[i] = 0xff;
}
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:
FREE(ipa, M_TDB);
return 0;
}
/*
* Store the packet for eventual retransmission -- failure is not
* catastrophic.
*/
if (m)
ipa->ipa_packet = m_copym2(m, 0, M_COPYALL, M_DONTWAIT);
ipa->ipa_expire = time.tv_sec + ipsec_expire_acquire;
TAILQ_INSERT_TAIL(&ipsec_acquire_head, ipa, ipa_next);
if (TAILQ_FIRST(&ipsec_acquire_head) == ipa)
timeout(ipsp_acquire_expirations, (void *) NULL,
hz * (ipa->ipa_expire - time.tv_sec));
/* PF_KEYv2 notification message */
return pfkeyv2_acquire(ipo, gw, laddr, &ipa->ipa_seq, ddst);
}
/*
* 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;
for (ipa = TAILQ_FIRST(&ipsec_acquire_head);
ipa;
ipa = TAILQ_NEXT(ipa, ipa_next))
if (ipa->ipa_seq == seq)
return ipa;
return NULL;
}