NetBSD/sys/netipsec/ipsec.c

2372 lines
57 KiB
C
Raw Normal View History

/* $NetBSD: ipsec.c,v 1.26 2007/02/10 09:43:05 degroote Exp $ */
/* $FreeBSD: /usr/local/www/cvsroot/FreeBSD/src/sys/netipsec/ipsec.c,v 1.2.2.2 2003/07/01 01:38:13 sam Exp $ */
/* $KAME: ipsec.c,v 1.103 2001/05/24 07:14:18 sakane Exp $ */
/*
* Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the project nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: ipsec.c,v 1.26 2007/02/10 09:43:05 degroote Exp $");
/*
* IPsec controller part.
*/
#include "opt_inet.h"
#ifdef __FreeBSD__
#include "opt_inet6.h"
#endif
#include "opt_ipsec.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/domain.h>
#include <sys/protosw.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/errno.h>
#include <sys/time.h>
#include <sys/kernel.h>
#include <sys/syslog.h>
#include <sys/sysctl.h>
#include <sys/proc.h>
#include <net/if.h>
#include <net/route.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/ip_var.h>
#include <netinet/in_var.h>
#include <netinet/udp.h>
#include <netinet/udp_var.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <netinet/ip6.h>
#ifdef INET6
#include <netinet6/ip6_var.h>
#endif
#include <netinet/in_pcb.h>
#ifdef INET6
#include <netinet6/in6_pcb.h>
#include <netinet/icmp6.h>
#endif
#include <netipsec/ipsec.h>
Redo net.inet.* sysctl subtree for fast-ipsec from scratch. Attach FAST-IPSEC statistics with 64-bit counters to new sysctl MIB. Rework netstat to show FAST_IPSEC statistics, via sysctl, for netstat -p ipsec. New kernel files: sys/netipsec/Makefile (new file; install *_var.h includes) sys/netipsec/ipsec_var.h (new 64-bit mib counter struct) Changed kernel files: sys/Makefile (recurse into sys/netipsec/) sys/netinet/in.h (fake IP_PROTO name for fast_ipsec sysctl subtree.) sys/netipsec/ipsec.h (minimal userspace inclusion) sys/netipsec/ipsec_osdep.h (minimal userspace inclusion) sys/netipsec/ipsec_netbsd.c (redo sysctl subtree from scratch) sys/netipsec/key*.c (fix broken net.key subtree) sys/netipsec/ah_var.h (increase all counters to 64 bits) sys/netipsec/esp_var.h (increase all counters to 64 bits) sys/netipsec/ipip_var.h (increase all counters to 64 bits) sys/netipsec/ipcomp_var.h (increase all counters to 64 bits) sys/netipsec/ipsec.c (add #include netipsec/ipsec_var.h) sys/netipsec/ipsec_mbuf.c (add #include netipsec/ipsec_var.h) sys/netipsec/ipsec_output.c (add #include netipsec/ipsec_var.h) sys/netinet/raw_ip.c (add #include netipsec/ipsec_var.h) sys/netinet/tcp_input.c (add #include netipsec/ipsec_var.h) sys/netinet/udp_usrreq.c (add #include netipsec/ipsec_var.h) Changes to usr.bin/netstat to print the new fast-ipsec sysctl tree for "netstat -s -p ipsec": New file: usr.bin/netstat/fast_ipsec.c (print fast-ipsec counters) Changed files: usr.bin/netstat/Makefile (add fast_ipsec.c) usr.bin/netstat/netstat.h (declarations for fast_ipsec.c) usr.bin/netstat/main.c (call KAME-vs-fast-ipsec dispatcher)
2004-05-07 04:55:14 +04:00
#include <netipsec/ipsec_var.h>
#ifdef INET6
#include <netipsec/ipsec6.h>
#endif
#include <netipsec/ah_var.h>
#include <netipsec/esp_var.h>
#include <netipsec/ipcomp.h> /*XXX*/
#include <netipsec/ipcomp_var.h>
#include <netipsec/key.h>
#include <netipsec/keydb.h>
#include <netipsec/key_debug.h>
#include <netipsec/xform.h>
#include <netipsec/ipsec_osdep.h>
#include <net/net_osdep.h>
#ifdef IPSEC_DEBUG
int ipsec_debug = 1;
/*
* When set to 1, IPsec will send packets with the same sequence number.
* This allows to verify if the other side has proper replay attacks detection.
*/
int ipsec_replay = 0;
/*
* When set 1, IPsec will send packets with corrupted HMAC.
* This allows to verify if the other side properly detects modified packets.
*/
int ipsec_integrity = 0;
#else
int ipsec_debug = 0;
#endif
/* NB: name changed so netstat doesn't use it */
struct newipsecstat newipsecstat;
int ip4_ah_offsetmask = 0; /* maybe IP_DF? */
int ip4_ipsec_dfbit = 2; /* DF bit on encap. 0: clear 1: set 2: copy */
int ip4_esp_trans_deflev = IPSEC_LEVEL_USE;
int ip4_esp_net_deflev = IPSEC_LEVEL_USE;
int ip4_ah_trans_deflev = IPSEC_LEVEL_USE;
int ip4_ah_net_deflev = IPSEC_LEVEL_USE;
struct secpolicy ip4_def_policy;
int ip4_ipsec_ecn = 0; /* ECN ignore(-1)/forbidden(0)/allowed(1) */
int ip4_esp_randpad = -1;
#ifdef __NetBSD__
u_int ipsec_spdgen = 1; /* SPD generation # */
static struct secpolicy *ipsec_checkpcbcache __P((struct mbuf *,
struct inpcbpolicy *, int));
static int ipsec_fillpcbcache __P((struct inpcbpolicy *, struct mbuf *,
struct secpolicy *, int));
static int ipsec_invalpcbcache __P((struct inpcbpolicy *, int));
#endif /* __NetBSD__ */
/*
* Crypto support requirements:
*
* 1 require hardware support
* -1 require software support
* 0 take anything
*/
int crypto_support = 0;
static struct secpolicy *ipsec_getpolicybysock(struct mbuf *, u_int,
PCB_T *, int *);
#ifdef __FreeBSD__
SYSCTL_DECL(_net_inet_ipsec);
/* net.inet.ipsec */
SYSCTL_INT(_net_inet_ipsec, IPSECCTL_DEF_POLICY,
def_policy, CTLFLAG_RW, &ip4_def_policy.policy, 0, "");
SYSCTL_INT(_net_inet_ipsec, IPSECCTL_DEF_ESP_TRANSLEV, esp_trans_deflev,
CTLFLAG_RW, &ip4_esp_trans_deflev, 0, "");
SYSCTL_INT(_net_inet_ipsec, IPSECCTL_DEF_ESP_NETLEV, esp_net_deflev,
CTLFLAG_RW, &ip4_esp_net_deflev, 0, "");
SYSCTL_INT(_net_inet_ipsec, IPSECCTL_DEF_AH_TRANSLEV, ah_trans_deflev,
CTLFLAG_RW, &ip4_ah_trans_deflev, 0, "");
SYSCTL_INT(_net_inet_ipsec, IPSECCTL_DEF_AH_NETLEV, ah_net_deflev,
CTLFLAG_RW, &ip4_ah_net_deflev, 0, "");
SYSCTL_INT(_net_inet_ipsec, IPSECCTL_AH_CLEARTOS,
ah_cleartos, CTLFLAG_RW, &ah_cleartos, 0, "");
SYSCTL_INT(_net_inet_ipsec, IPSECCTL_AH_OFFSETMASK,
ah_offsetmask, CTLFLAG_RW, &ip4_ah_offsetmask, 0, "");
SYSCTL_INT(_net_inet_ipsec, IPSECCTL_DFBIT,
dfbit, CTLFLAG_RW, &ip4_ipsec_dfbit, 0, "");
SYSCTL_INT(_net_inet_ipsec, IPSECCTL_ECN,
ecn, CTLFLAG_RW, &ip4_ipsec_ecn, 0, "");
SYSCTL_INT(_net_inet_ipsec, IPSECCTL_DEBUG,
debug, CTLFLAG_RW, &ipsec_debug, 0, "");
SYSCTL_INT(_net_inet_ipsec, IPSECCTL_ESP_RANDPAD,
esp_randpad, CTLFLAG_RW, &ip4_esp_randpad, 0, "");
SYSCTL_INT(_net_inet_ipsec, OID_AUTO,
crypto_support, CTLFLAG_RW, &crypto_support,0, "");
SYSCTL_STRUCT(_net_inet_ipsec, OID_AUTO,
ipsecstats, CTLFLAG_RD, &newipsecstat, newipsecstat, "");
SYSCTL_INT(_net_inet_ipsec, OID_AUTO, test_replay, CTLFLAG_RW, &ipsec_replay, 0,
"Emulate replay attack");
SYSCTL_INT(_net_inet_ipsec, OID_AUTO, test_integrity, CTLFLAG_RW,
&ipsec_integrity, 0, "Emulate man-in-the-middle attack");
#endif /* __FreeBSD__ */
#ifdef INET6
int ip6_esp_trans_deflev = IPSEC_LEVEL_USE;
int ip6_esp_net_deflev = IPSEC_LEVEL_USE;
int ip6_ah_trans_deflev = IPSEC_LEVEL_USE;
int ip6_ah_net_deflev = IPSEC_LEVEL_USE;
struct secpolicy ip6_def_policy;
int ip6_ipsec_ecn = 0; /* ECN ignore(-1)/forbidden(0)/allowed(1) */
int ip6_esp_randpad = -1;
#ifdef __FreeBSD__
SYSCTL_DECL(_net_inet6_ipsec6);
/* net.inet6.ipsec6 */
#ifdef COMPAT_KAME
SYSCTL_OID(_net_inet6_ipsec6, IPSECCTL_STATS, stats, CTLFLAG_RD,
0,0, compat_ipsecstats_sysctl, "S", "");
#endif /* COMPAT_KAME */
SYSCTL_INT(_net_inet6_ipsec6, IPSECCTL_DEF_POLICY,
def_policy, CTLFLAG_RW, &ip4_def_policy.policy, 0, "");
SYSCTL_INT(_net_inet6_ipsec6, IPSECCTL_DEF_ESP_TRANSLEV, esp_trans_deflev,
CTLFLAG_RW, &ip6_esp_trans_deflev, 0, "");
SYSCTL_INT(_net_inet6_ipsec6, IPSECCTL_DEF_ESP_NETLEV, esp_net_deflev,
CTLFLAG_RW, &ip6_esp_net_deflev, 0, "");
SYSCTL_INT(_net_inet6_ipsec6, IPSECCTL_DEF_AH_TRANSLEV, ah_trans_deflev,
CTLFLAG_RW, &ip6_ah_trans_deflev, 0, "");
SYSCTL_INT(_net_inet6_ipsec6, IPSECCTL_DEF_AH_NETLEV, ah_net_deflev,
CTLFLAG_RW, &ip6_ah_net_deflev, 0, "");
SYSCTL_INT(_net_inet6_ipsec6, IPSECCTL_ECN,
ecn, CTLFLAG_RW, &ip6_ipsec_ecn, 0, "");
SYSCTL_INT(_net_inet6_ipsec6, IPSECCTL_DEBUG,
debug, CTLFLAG_RW, &ipsec_debug, 0, "");
SYSCTL_INT(_net_inet6_ipsec6, IPSECCTL_ESP_RANDPAD,
esp_randpad, CTLFLAG_RW, &ip6_esp_randpad, 0, "");
#endif /* INET6 */
#endif /* __FreeBSD__ */
static int ipsec4_setspidx_inpcb __P((struct mbuf *, struct inpcb *pcb));
#ifdef INET6
static int ipsec6_setspidx_in6pcb __P((struct mbuf *, struct in6pcb *pcb));
#endif
static int ipsec_setspidx __P((struct mbuf *, struct secpolicyindex *, int));
static void ipsec4_get_ulp __P((struct mbuf *m, struct secpolicyindex *, int));
static int ipsec4_setspidx_ipaddr __P((struct mbuf *, struct secpolicyindex *));
#ifdef INET6
static void ipsec6_get_ulp __P((struct mbuf *m, struct secpolicyindex *, int));
static int ipsec6_setspidx_ipaddr __P((struct mbuf *, struct secpolicyindex *));
#endif
static void ipsec_delpcbpolicy __P((struct inpcbpolicy *));
static struct secpolicy *ipsec_deepcopy_policy __P((struct secpolicy *src));
static int ipsec_set_policy __P((struct secpolicy **pcb_sp,
int optname, caddr_t request, size_t len, int priv));
static int ipsec_get_policy __P((struct secpolicy *pcb_sp, struct mbuf **mp));
static void vshiftl __P((unsigned char *, int, int));
static size_t ipsec_hdrsiz __P((struct secpolicy *));
#ifdef __NetBSD__
/*
* Try to validate and use cached policy on a PCB.
*/
static struct secpolicy *
ipsec_checkpcbcache(struct mbuf *m, struct inpcbpolicy *pcbsp, int dir)
{
struct secpolicyindex spidx;
switch (dir) {
case IPSEC_DIR_INBOUND:
case IPSEC_DIR_OUTBOUND:
case IPSEC_DIR_ANY:
break;
default:
return NULL;
}
Redo net.inet.* sysctl subtree for fast-ipsec from scratch. Attach FAST-IPSEC statistics with 64-bit counters to new sysctl MIB. Rework netstat to show FAST_IPSEC statistics, via sysctl, for netstat -p ipsec. New kernel files: sys/netipsec/Makefile (new file; install *_var.h includes) sys/netipsec/ipsec_var.h (new 64-bit mib counter struct) Changed kernel files: sys/Makefile (recurse into sys/netipsec/) sys/netinet/in.h (fake IP_PROTO name for fast_ipsec sysctl subtree.) sys/netipsec/ipsec.h (minimal userspace inclusion) sys/netipsec/ipsec_osdep.h (minimal userspace inclusion) sys/netipsec/ipsec_netbsd.c (redo sysctl subtree from scratch) sys/netipsec/key*.c (fix broken net.key subtree) sys/netipsec/ah_var.h (increase all counters to 64 bits) sys/netipsec/esp_var.h (increase all counters to 64 bits) sys/netipsec/ipip_var.h (increase all counters to 64 bits) sys/netipsec/ipcomp_var.h (increase all counters to 64 bits) sys/netipsec/ipsec.c (add #include netipsec/ipsec_var.h) sys/netipsec/ipsec_mbuf.c (add #include netipsec/ipsec_var.h) sys/netipsec/ipsec_output.c (add #include netipsec/ipsec_var.h) sys/netinet/raw_ip.c (add #include netipsec/ipsec_var.h) sys/netinet/tcp_input.c (add #include netipsec/ipsec_var.h) sys/netinet/udp_usrreq.c (add #include netipsec/ipsec_var.h) Changes to usr.bin/netstat to print the new fast-ipsec sysctl tree for "netstat -s -p ipsec": New file: usr.bin/netstat/fast_ipsec.c (print fast-ipsec counters) Changed files: usr.bin/netstat/Makefile (add fast_ipsec.c) usr.bin/netstat/netstat.h (declarations for fast_ipsec.c) usr.bin/netstat/main.c (call KAME-vs-fast-ipsec dispatcher)
2004-05-07 04:55:14 +04:00
#ifdef DIAGNOSTIC
if (pcbsp == NULL) {
printf("ipsec_checkpcbcache: NULL pcbsp\n");
/* XXX panic? */
return NULL;
}
#endif
#ifdef DIAGNOSTIC
if (dir >= sizeof(pcbsp->sp_cache)/sizeof(pcbsp->sp_cache[0]))
panic("dir too big in ipsec_checkpcbcache");
#endif
/* SPD table change invalidate all the caches. */
if (ipsec_spdgen != pcbsp->sp_cache[dir].cachegen) {
ipsec_invalpcbcache(pcbsp, dir);
return NULL;
}
if (!pcbsp->sp_cache[dir].cachesp)
return NULL;
if (pcbsp->sp_cache[dir].cachesp->state != IPSEC_SPSTATE_ALIVE) {
ipsec_invalpcbcache(pcbsp, dir);
return NULL;
}
if ((pcbsp->sp_cacheflags & IPSEC_PCBSP_CONNECTED) == 0) {
if (!pcbsp->sp_cache[dir].cachesp)
return NULL;
if (ipsec_setspidx(m, &spidx, 1) != 0)
return NULL;
if (bcmp(&pcbsp->sp_cache[dir].cacheidx, &spidx,
sizeof(spidx))) {
if (!key_cmpspidx_withmask(&pcbsp->sp_cache[dir].cachesp->spidx,
&spidx))
return NULL;
pcbsp->sp_cache[dir].cacheidx = spidx;
}
} else {
/*
* The pcb is connected, and the L4 code is sure that:
* - outgoing side uses inp_[lf]addr
* - incoming side looks up policy after inpcb lookup
* and address pair is know to be stable. We do not need
* to generate spidx again, nor check the address match again.
*
* For IPv4/v6 SOCK_STREAM sockets, this assumptions holds
* and there are calls to ipsec_pcbconn() from in_pcbconnect().
*/
}
pcbsp->sp_cache[dir].cachesp->lastused = time_second;
pcbsp->sp_cache[dir].cachesp->refcnt++;
KEYDEBUG(KEYDEBUG_IPSEC_STAMP,
printf("DP ipsec_checkpcbcache cause refcnt++:%d SP:%p\n",
pcbsp->sp_cache[dir].cachesp->refcnt,
pcbsp->sp_cache[dir].cachesp));
return pcbsp->sp_cache[dir].cachesp;
}
static int
ipsec_fillpcbcache(struct inpcbpolicy *pcbsp, struct mbuf *m,
struct secpolicy *sp, int dir)
{
switch (dir) {
case IPSEC_DIR_INBOUND:
case IPSEC_DIR_OUTBOUND:
break;
default:
return EINVAL;
}
#ifdef DIAGNOSTIC
if (dir >= sizeof(pcbsp->sp_cache)/sizeof(pcbsp->sp_cache[0]))
panic("dir too big in ipsec_fillpcbcache");
#endif
if (pcbsp->sp_cache[dir].cachesp)
KEY_FREESP(&pcbsp->sp_cache[dir].cachesp);
pcbsp->sp_cache[dir].cachesp = NULL;
pcbsp->sp_cache[dir].cachehint = IPSEC_PCBHINT_MAYBE;
if (ipsec_setspidx(m, &pcbsp->sp_cache[dir].cacheidx, 1) != 0) {
return EINVAL;
}
pcbsp->sp_cache[dir].cachesp = sp;
if (pcbsp->sp_cache[dir].cachesp) {
pcbsp->sp_cache[dir].cachesp->refcnt++;
KEYDEBUG(KEYDEBUG_IPSEC_STAMP,
printf("DP ipsec_fillpcbcache cause refcnt++:%d SP:%p\n",
pcbsp->sp_cache[dir].cachesp->refcnt,
pcbsp->sp_cache[dir].cachesp));
/*
* If the PCB is connected, we can remember a hint to
* possibly short-circuit IPsec processing in other places.
*/
if (pcbsp->sp_cacheflags & IPSEC_PCBSP_CONNECTED) {
switch (pcbsp->sp_cache[dir].cachesp->policy) {
case IPSEC_POLICY_NONE:
case IPSEC_POLICY_BYPASS:
pcbsp->sp_cache[dir].cachehint =
IPSEC_PCBHINT_NO;
break;
default:
pcbsp->sp_cache[dir].cachehint =
IPSEC_PCBHINT_YES;
}
}
}
pcbsp->sp_cache[dir].cachegen = ipsec_spdgen;
return 0;
}
static int
ipsec_invalpcbcache(struct inpcbpolicy *pcbsp, int dir)
{
int i;
for (i = IPSEC_DIR_INBOUND; i <= IPSEC_DIR_OUTBOUND; i++) {
if (dir != IPSEC_DIR_ANY && i != dir)
continue;
if (pcbsp->sp_cache[i].cachesp)
KEY_FREESP(&pcbsp->sp_cache[i].cachesp);
pcbsp->sp_cache[i].cachesp = NULL;
pcbsp->sp_cache[i].cachehint = IPSEC_PCBHINT_MAYBE;
pcbsp->sp_cache[i].cachegen = 0;
bzero(&pcbsp->sp_cache[i].cacheidx,
sizeof(pcbsp->sp_cache[i].cacheidx));
}
return 0;
}
void
ipsec_pcbconn(struct inpcbpolicy *pcbsp)
{
pcbsp->sp_cacheflags |= IPSEC_PCBSP_CONNECTED;
ipsec_invalpcbcache(pcbsp, IPSEC_DIR_ANY);
}
void
ipsec_pcbdisconn(struct inpcbpolicy *pcbsp)
{
pcbsp->sp_cacheflags &= ~IPSEC_PCBSP_CONNECTED;
ipsec_invalpcbcache(pcbsp, IPSEC_DIR_ANY);
}
void
ipsec_invalpcbcacheall(void)
{
if (ipsec_spdgen == UINT_MAX)
ipsec_spdgen = 1;
else
ipsec_spdgen++;
}
#endif /* __NetBSD__ */
/*
* Return a held reference to the default SP.
*/
static struct secpolicy *
key_allocsp_default(const char* where, int tag)
{
struct secpolicy *sp;
KEYDEBUG(KEYDEBUG_IPSEC_STAMP,
printf("DP key_allocsp_default from %s:%u\n", where, tag));
sp = &ip4_def_policy;
if (sp->policy != IPSEC_POLICY_DISCARD &&
sp->policy != IPSEC_POLICY_NONE) {
ipseclog((LOG_INFO, "fixed system default policy: %d->%d\n",
sp->policy, IPSEC_POLICY_NONE));
sp->policy = IPSEC_POLICY_NONE;
}
sp->refcnt++;
KEYDEBUG(KEYDEBUG_IPSEC_STAMP,
printf("DP key_allocsp_default returns SP:%p (%u)\n",
sp, sp->refcnt));
return sp;
}
#define KEY_ALLOCSP_DEFAULT() \
key_allocsp_default(__FILE__, __LINE__)
/*
* For OUTBOUND packet having a socket. Searching SPD for packet,
* and return a pointer to SP.
* OUT: NULL: no apropreate SP found, the following value is set to error.
* 0 : bypass
* EACCES : discard packet.
* ENOENT : ipsec_acquire() in progress, maybe.
* others : error occurred.
* others: a pointer to SP
*
2006-02-25 05:28:55 +03:00
* NOTE: IPv6 mapped address concern is implemented here.
*/
struct secpolicy *
ipsec_getpolicy(struct tdb_ident *tdbi, u_int dir)
{
struct secpolicy *sp;
IPSEC_ASSERT(tdbi != NULL, ("ipsec_getpolicy: null tdbi"));
IPSEC_ASSERT(dir == IPSEC_DIR_INBOUND || dir == IPSEC_DIR_OUTBOUND,
("ipsec_getpolicy: invalid direction %u", dir));
sp = KEY_ALLOCSP2(tdbi->spi, &tdbi->dst, tdbi->proto, dir);
if (sp == NULL) /*XXX????*/
sp = KEY_ALLOCSP_DEFAULT();
IPSEC_ASSERT(sp != NULL, ("ipsec_getpolicy: null SP"));
return sp;
}
/*
* For OUTBOUND packet having a socket. Searching SPD for packet,
* and return a pointer to SP.
* OUT: NULL: no apropreate SP found, the following value is set to error.
* 0 : bypass
* EACCES : discard packet.
* ENOENT : ipsec_acquire() in progress, maybe.
* others : error occurred.
* others: a pointer to SP
*
2006-02-25 05:28:55 +03:00
* NOTE: IPv6 mapped address concern is implemented here.
*/
static struct secpolicy *
ipsec_getpolicybysock(m, dir, inp, error)
struct mbuf *m;
u_int dir;
PCB_T *inp;
int *error;
{
struct inpcbpolicy *pcbsp = NULL;
struct secpolicy *currsp = NULL; /* policy on socket */
struct secpolicy *sp;
int af;
IPSEC_ASSERT(m != NULL, ("ipsec_getpolicybysock: null mbuf"));
IPSEC_ASSERT(inp != NULL, ("ipsec_getpolicybysock: null inpcb"));
IPSEC_ASSERT(error != NULL, ("ipsec_getpolicybysock: null error"));
IPSEC_ASSERT(dir == IPSEC_DIR_INBOUND || dir == IPSEC_DIR_OUTBOUND,
("ipsec_getpolicybysock: invalid direction %u", dir));
IPSEC_ASSERT(PCB_SOCKET(inp) != NULL,
("ipsec_getppolicybysock: null socket\n"));
/* XXX FIXME inpcb/in6pcb vs socket*/
af = PCB_FAMILY(inp);
IPSEC_ASSERT(af == AF_INET || af == AF_INET6,
("ipsec_getpolicybysock: unexpected protocol family %u", af));
#ifdef __NetBSD__
Redo net.inet.* sysctl subtree for fast-ipsec from scratch. Attach FAST-IPSEC statistics with 64-bit counters to new sysctl MIB. Rework netstat to show FAST_IPSEC statistics, via sysctl, for netstat -p ipsec. New kernel files: sys/netipsec/Makefile (new file; install *_var.h includes) sys/netipsec/ipsec_var.h (new 64-bit mib counter struct) Changed kernel files: sys/Makefile (recurse into sys/netipsec/) sys/netinet/in.h (fake IP_PROTO name for fast_ipsec sysctl subtree.) sys/netipsec/ipsec.h (minimal userspace inclusion) sys/netipsec/ipsec_osdep.h (minimal userspace inclusion) sys/netipsec/ipsec_netbsd.c (redo sysctl subtree from scratch) sys/netipsec/key*.c (fix broken net.key subtree) sys/netipsec/ah_var.h (increase all counters to 64 bits) sys/netipsec/esp_var.h (increase all counters to 64 bits) sys/netipsec/ipip_var.h (increase all counters to 64 bits) sys/netipsec/ipcomp_var.h (increase all counters to 64 bits) sys/netipsec/ipsec.c (add #include netipsec/ipsec_var.h) sys/netipsec/ipsec_mbuf.c (add #include netipsec/ipsec_var.h) sys/netipsec/ipsec_output.c (add #include netipsec/ipsec_var.h) sys/netinet/raw_ip.c (add #include netipsec/ipsec_var.h) sys/netinet/tcp_input.c (add #include netipsec/ipsec_var.h) sys/netinet/udp_usrreq.c (add #include netipsec/ipsec_var.h) Changes to usr.bin/netstat to print the new fast-ipsec sysctl tree for "netstat -s -p ipsec": New file: usr.bin/netstat/fast_ipsec.c (print fast-ipsec counters) Changed files: usr.bin/netstat/Makefile (add fast_ipsec.c) usr.bin/netstat/netstat.h (declarations for fast_ipsec.c) usr.bin/netstat/main.c (call KAME-vs-fast-ipsec dispatcher)
2004-05-07 04:55:14 +04:00
IPSEC_ASSERT(inp->inph_sp != NULL, ("null PCB policy cache"));
/* If we have a cached entry, and if it is still valid, use it. */
ipsecstat.ips_spdcache_lookup++;
currsp = ipsec_checkpcbcache(m, /*inpcb_hdr*/inp->inph_sp, dir);
if (currsp) {
*error = 0;
return currsp;
}
ipsecstat.ips_spdcache_miss++;
#endif /* __NetBSD__ */
switch (af) {
case AF_INET: {
struct inpcb *in4p = PCB_TO_IN4PCB(inp);
/* set spidx in pcb */
*error = ipsec4_setspidx_inpcb(m, in4p);
pcbsp = in4p->inp_sp;
break;
}
#if defined(INET6)
case AF_INET6: {
struct in6pcb *in6p = PCB_TO_IN6PCB(inp);
/* set spidx in pcb */
*error = ipsec6_setspidx_in6pcb(m, in6p);
pcbsp = in6p->in6p_sp;
break;
}
#endif
default:
*error = EPFNOSUPPORT;
break;
}
if (*error)
return NULL;
IPSEC_ASSERT(pcbsp != NULL, ("ipsec_getpolicybysock: null pcbsp"));
switch (dir) {
case IPSEC_DIR_INBOUND:
currsp = pcbsp->sp_in;
break;
case IPSEC_DIR_OUTBOUND:
currsp = pcbsp->sp_out;
break;
}
IPSEC_ASSERT(currsp != NULL, ("ipsec_getpolicybysock: null currsp"));
if (pcbsp->priv) { /* when privilieged socket */
switch (currsp->policy) {
case IPSEC_POLICY_BYPASS:
case IPSEC_POLICY_IPSEC:
currsp->refcnt++;
sp = currsp;
break;
case IPSEC_POLICY_ENTRUST:
/* look for a policy in SPD */
sp = KEY_ALLOCSP(&currsp->spidx, dir);
if (sp == NULL) /* no SP found */
sp = KEY_ALLOCSP_DEFAULT();
break;
default:
ipseclog((LOG_ERR, "ipsec_getpolicybysock: "
"Invalid policy for PCB %d\n", currsp->policy));
*error = EINVAL;
return NULL;
}
} else { /* unpriv, SPD has policy */
sp = KEY_ALLOCSP(&currsp->spidx, dir);
if (sp == NULL) { /* no SP found */
switch (currsp->policy) {
case IPSEC_POLICY_BYPASS:
ipseclog((LOG_ERR, "ipsec_getpolicybysock: "
"Illegal policy for non-priviliged defined %d\n",
currsp->policy));
*error = EINVAL;
return NULL;
case IPSEC_POLICY_ENTRUST:
sp = KEY_ALLOCSP_DEFAULT();
break;
case IPSEC_POLICY_IPSEC:
currsp->refcnt++;
sp = currsp;
break;
default:
ipseclog((LOG_ERR, "ipsec_getpolicybysock: "
"Invalid policy for PCB %d\n", currsp->policy));
*error = EINVAL;
return NULL;
}
}
}
IPSEC_ASSERT(sp != NULL,
("ipsec_getpolicybysock: null SP (priv %u policy %u",
pcbsp->priv, currsp->policy));
KEYDEBUG(KEYDEBUG_IPSEC_STAMP,
printf("DP ipsec_getpolicybysock (priv %u policy %u) allocates "
"SP:%p (refcnt %u)\n", pcbsp->priv, currsp->policy,
sp, sp->refcnt));
#ifdef __NetBSD__
ipsec_fillpcbcache(pcbsp, m, sp, dir);
#endif /* __NetBSD__ */
return sp;
}
/*
* For FORWADING packet or OUTBOUND without a socket. Searching SPD for packet,
* and return a pointer to SP.
* OUT: positive: a pointer to the entry for security policy leaf matched.
* NULL: no apropreate SP found, the following value is set to error.
* 0 : bypass
* EACCES : discard packet.
* ENOENT : ipsec_acquire() in progress, maybe.
* others : error occurred.
*/
struct secpolicy *
ipsec_getpolicybyaddr(m, dir, flag, error)
struct mbuf *m;
u_int dir;
int flag;
int *error;
{
struct secpolicyindex spidx;
struct secpolicy *sp;
IPSEC_ASSERT(m != NULL, ("ipsec_getpolicybyaddr: null mbuf"));
IPSEC_ASSERT(error != NULL, ("ipsec_getpolicybyaddr: null error"));
IPSEC_ASSERT(dir == IPSEC_DIR_INBOUND || dir == IPSEC_DIR_OUTBOUND,
("ipsec4_getpolicybaddr: invalid direction %u", dir));
sp = NULL;
if (key_havesp(dir)) {
/* Make an index to look for a policy. */
*error = ipsec_setspidx(m, &spidx,
(flag & IP_FORWARDING) ? 0 : 1);
if (*error != 0) {
DPRINTF(("ipsec_getpolicybyaddr: setpidx failed,"
" dir %u flag %u\n", dir, flag));
bzero(&spidx, sizeof (spidx));
return NULL;
}
spidx.dir = dir;
sp = KEY_ALLOCSP(&spidx, dir);
}
if (sp == NULL) /* no SP found, use system default */
sp = KEY_ALLOCSP_DEFAULT();
IPSEC_ASSERT(sp != NULL, ("ipsec_getpolicybyaddr: null SP"));
return sp;
}
struct secpolicy *
ipsec4_checkpolicy(m, dir, flag, error, inp)
struct mbuf *m;
u_int dir, flag;
int *error;
struct inpcb *inp;
{
struct secpolicy *sp;
*error = 0;
/* XXX KAME IPv6 calls us with non-null inp but bogus inp_socket? */
if (inp == NULL || inp->inp_socket == NULL) {
sp = ipsec_getpolicybyaddr(m, dir, flag, error);
} else
sp = ipsec_getpolicybysock(m, dir, IN4PCB_TO_PCB(inp), error);
if (sp == NULL) {
IPSEC_ASSERT(*error != 0,
("ipsec4_checkpolicy: getpolicy failed w/o error"));
newipsecstat.ips_out_inval++;
return NULL;
}
IPSEC_ASSERT(*error == 0,
("ipsec4_checkpolicy: sp w/ error set to %u", *error));
switch (sp->policy) {
case IPSEC_POLICY_ENTRUST:
default:
printf("ipsec4_checkpolicy: invalid policy %u\n", sp->policy);
/* fall thru... */
case IPSEC_POLICY_DISCARD:
newipsecstat.ips_out_polvio++;
*error = -EINVAL; /* packet is discarded by caller */
break;
case IPSEC_POLICY_BYPASS:
case IPSEC_POLICY_NONE:
KEY_FREESP(&sp);
sp = NULL; /* NB: force NULL result */
break;
case IPSEC_POLICY_IPSEC:
if (sp->req == NULL) /* acquire an SA */
*error = key_spdacquire(sp);
break;
}
if (*error != 0) {
KEY_FREESP(&sp);
sp = NULL;
}
DPRINTF(("ipsecpol: done, sp %p error %d, \n", sp, *error));
return sp;
}
#ifdef INET6
struct secpolicy *
ipsec6_checkpolicy(m, dir, flag, error, in6p)
struct mbuf *m;
u_int dir, flag;
int *error;
struct in6pcb *in6p;
{
struct secpolicy *sp;
*error = 0;
/* XXX KAME IPv6 calls us with non-null inp but bogus inp_socket? */
if (in6p == NULL || in6p->in6p_socket == NULL) {
sp = ipsec_getpolicybyaddr(m, dir, flag, error);
} else
sp = ipsec_getpolicybysock(m, dir, IN6PCB_TO_PCB(in6p), error);
if (sp == NULL) {
IPSEC_ASSERT(*error != 0,
("ipsec6_checkpolicy: getpolicy failed w/o error"));
newipsecstat.ips_out_inval++;
return NULL;
}
IPSEC_ASSERT(*error == 0,
("ipsec6_checkpolicy: sp w/ error set to %u", *error));
switch (sp->policy) {
case IPSEC_POLICY_ENTRUST:
default:
printf("ipsec6_checkpolicy: invalid policy %u\n", sp->policy);
/* fall thru... */
case IPSEC_POLICY_DISCARD:
newipsecstat.ips_out_polvio++;
*error = -EINVAL; /* packet is discarded by caller */
break;
case IPSEC_POLICY_BYPASS:
case IPSEC_POLICY_NONE:
KEY_FREESP(&sp);
sp = NULL; /* NB: force NULL result */
break;
case IPSEC_POLICY_IPSEC:
if (sp->req == NULL) /* acquire an SA */
*error = key_spdacquire(sp);
break;
}
if (*error != 0) {
KEY_FREESP(&sp);
sp = NULL;
}
DPRINTF(("ipsecpol: done, sp %p error %d, \n", sp, *error));
return sp;
}
#endif /* INET6 */
static int
ipsec4_setspidx_inpcb(m, pcb)
struct mbuf *m;
struct inpcb *pcb;
{
int error;
IPSEC_ASSERT(pcb != NULL, ("ipsec4_setspidx_inpcb: null pcb"));
IPSEC_ASSERT(pcb->inp_sp != NULL, ("ipsec4_setspidx_inpcb: null inp_sp"));
IPSEC_ASSERT(pcb->inp_sp->sp_out != NULL && pcb->inp_sp->sp_in != NULL,
("ipsec4_setspidx_inpcb: null sp_in || sp_out"));
error = ipsec_setspidx(m, &pcb->inp_sp->sp_in->spidx, 1);
if (error == 0) {
pcb->inp_sp->sp_in->spidx.dir = IPSEC_DIR_INBOUND;
pcb->inp_sp->sp_out->spidx = pcb->inp_sp->sp_in->spidx;
pcb->inp_sp->sp_out->spidx.dir = IPSEC_DIR_OUTBOUND;
} else {
bzero(&pcb->inp_sp->sp_in->spidx,
sizeof (pcb->inp_sp->sp_in->spidx));
bzero(&pcb->inp_sp->sp_out->spidx,
sizeof (pcb->inp_sp->sp_in->spidx));
}
return error;
}
#ifdef INET6
static int
ipsec6_setspidx_in6pcb(m, pcb)
struct mbuf *m;
struct in6pcb *pcb;
{
struct secpolicyindex *spidx;
int error;
IPSEC_ASSERT(pcb != NULL, ("ipsec6_setspidx_in6pcb: null pcb"));
IPSEC_ASSERT(pcb->in6p_sp != NULL, ("ipsec6_setspidx_in6pcb: null inp_sp"));
IPSEC_ASSERT(pcb->in6p_sp->sp_out != NULL && pcb->in6p_sp->sp_in != NULL,
("ipsec6_setspidx_in6pcb: null sp_in || sp_out"));
bzero(&pcb->in6p_sp->sp_in->spidx, sizeof(*spidx));
bzero(&pcb->in6p_sp->sp_out->spidx, sizeof(*spidx));
spidx = &pcb->in6p_sp->sp_in->spidx;
error = ipsec_setspidx(m, spidx, 1);
if (error)
goto bad;
spidx->dir = IPSEC_DIR_INBOUND;
spidx = &pcb->in6p_sp->sp_out->spidx;
error = ipsec_setspidx(m, spidx, 1);
if (error)
goto bad;
spidx->dir = IPSEC_DIR_OUTBOUND;
return 0;
bad:
bzero(&pcb->in6p_sp->sp_in->spidx, sizeof(*spidx));
bzero(&pcb->in6p_sp->sp_out->spidx, sizeof(*spidx));
return error;
}
#endif
/*
* configure security policy index (src/dst/proto/sport/dport)
* by looking at the content of mbuf.
* the caller is responsible for error recovery (like clearing up spidx).
*/
static int
ipsec_setspidx(m, spidx, needport)
struct mbuf *m;
struct secpolicyindex *spidx;
int needport;
{
struct ip *ip = NULL;
struct ip ipbuf;
u_int v;
struct mbuf *n;
int len;
int error;
IPSEC_ASSERT(m != NULL, ("ipsec_setspidx: null mbuf"));
/*
* validate m->m_pkthdr.len. we see incorrect length if we
* mistakenly call this function with inconsistent mbuf chain
* (like 4.4BSD tcp/udp processing). XXX should we panic here?
*/
len = 0;
for (n = m; n; n = n->m_next)
len += n->m_len;
if (m->m_pkthdr.len != len) {
KEYDEBUG(KEYDEBUG_IPSEC_DUMP,
printf("ipsec_setspidx: "
"total of m_len(%d) != pkthdr.len(%d), "
"ignored.\n",
len, m->m_pkthdr.len));
return EINVAL;
}
if (m->m_pkthdr.len < sizeof(struct ip)) {
KEYDEBUG(KEYDEBUG_IPSEC_DUMP,
printf("ipsec_setspidx: "
"pkthdr.len(%d) < sizeof(struct ip), ignored.\n",
m->m_pkthdr.len));
return EINVAL;
}
if (m->m_len >= sizeof(*ip))
ip = mtod(m, struct ip *);
else {
m_copydata(m, 0, sizeof(ipbuf), (caddr_t)&ipbuf);
ip = &ipbuf;
}
#ifdef _IP_VHL
v = _IP_VHL_V(ip->ip_vhl);
#else
v = ip->ip_v;
#endif
switch (v) {
case 4:
error = ipsec4_setspidx_ipaddr(m, spidx);
if (error)
return error;
ipsec4_get_ulp(m, spidx, needport);
return 0;
#ifdef INET6
case 6:
if (m->m_pkthdr.len < sizeof(struct ip6_hdr)) {
KEYDEBUG(KEYDEBUG_IPSEC_DUMP,
printf("ipsec_setspidx: "
"pkthdr.len(%d) < sizeof(struct ip6_hdr), "
"ignored.\n", m->m_pkthdr.len));
return EINVAL;
}
error = ipsec6_setspidx_ipaddr(m, spidx);
if (error)
return error;
ipsec6_get_ulp(m, spidx, needport);
return 0;
#endif
default:
KEYDEBUG(KEYDEBUG_IPSEC_DUMP,
printf("ipsec_setspidx: "
"unknown IP version %u, ignored.\n", v));
return EINVAL;
}
}
static void
ipsec4_get_ulp(struct mbuf *m, struct secpolicyindex *spidx, int needport)
{
u_int8_t nxt;
int off;
/* sanity check */
IPSEC_ASSERT(m != NULL, ("ipsec4_get_ulp: null mbuf"));
IPSEC_ASSERT(m->m_pkthdr.len >= sizeof(struct ip),
("ipsec4_get_ulp: packet too short"));
/* NB: ip_input() flips it into host endian XXX need more checking */
if (m->m_len >= sizeof(struct ip)) {
struct ip *ip = mtod(m, struct ip *);
if (ip->ip_off & (IP_MF | IP_OFFMASK))
goto done;
#ifdef _IP_VHL
off = _IP_VHL_HL(ip->ip_vhl) << 2;
#else
off = ip->ip_hl << 2;
#endif
nxt = ip->ip_p;
} else {
struct ip ih;
m_copydata(m, 0, sizeof (struct ip), (caddr_t) &ih);
if (ih.ip_off & (IP_MF | IP_OFFMASK))
goto done;
#ifdef _IP_VHL
off = _IP_VHL_HL(ih.ip_vhl) << 2;
#else
off = ih.ip_hl << 2;
#endif
nxt = ih.ip_p;
}
while (off < m->m_pkthdr.len) {
struct ip6_ext ip6e;
struct tcphdr th;
struct udphdr uh;
switch (nxt) {
case IPPROTO_TCP:
spidx->ul_proto = nxt;
if (!needport)
goto done_proto;
if (off + sizeof(struct tcphdr) > m->m_pkthdr.len)
goto done;
m_copydata(m, off, sizeof (th), (caddr_t) &th);
spidx->src.sin.sin_port = th.th_sport;
spidx->dst.sin.sin_port = th.th_dport;
return;
case IPPROTO_UDP:
spidx->ul_proto = nxt;
if (!needport)
goto done_proto;
if (off + sizeof(struct udphdr) > m->m_pkthdr.len)
goto done;
m_copydata(m, off, sizeof (uh), (caddr_t) &uh);
spidx->src.sin.sin_port = uh.uh_sport;
spidx->dst.sin.sin_port = uh.uh_dport;
return;
case IPPROTO_AH:
if (m->m_pkthdr.len > off + sizeof(ip6e))
goto done;
/* XXX sigh, this works but is totally bogus */
m_copydata(m, off, sizeof(ip6e), (caddr_t) &ip6e);
off += (ip6e.ip6e_len + 2) << 2;
nxt = ip6e.ip6e_nxt;
break;
case IPPROTO_ICMP:
default:
/* XXX intermediate headers??? */
spidx->ul_proto = nxt;
goto done_proto;
}
}
done:
spidx->ul_proto = IPSEC_ULPROTO_ANY;
done_proto:
spidx->src.sin.sin_port = IPSEC_PORT_ANY;
spidx->dst.sin.sin_port = IPSEC_PORT_ANY;
}
/* assumes that m is sane */
static int
ipsec4_setspidx_ipaddr(struct mbuf *m, struct secpolicyindex *spidx)
{
static const struct sockaddr_in template = {
sizeof (struct sockaddr_in),
AF_INET,
0, { 0 }, { 0, 0, 0, 0, 0, 0, 0, 0 }
};
spidx->src.sin = template;
spidx->dst.sin = template;
if (m->m_len < sizeof (struct ip)) {
m_copydata(m, offsetof(struct ip, ip_src),
sizeof (struct in_addr),
(caddr_t) &spidx->src.sin.sin_addr);
m_copydata(m, offsetof(struct ip, ip_dst),
sizeof (struct in_addr),
(caddr_t) &spidx->dst.sin.sin_addr);
} else {
struct ip *ip = mtod(m, struct ip *);
spidx->src.sin.sin_addr = ip->ip_src;
spidx->dst.sin.sin_addr = ip->ip_dst;
}
spidx->prefs = sizeof(struct in_addr) << 3;
spidx->prefd = sizeof(struct in_addr) << 3;
return 0;
}
#ifdef INET6
static void
ipsec6_get_ulp(m, spidx, needport)
struct mbuf *m;
struct secpolicyindex *spidx;
int needport;
{
int off, nxt;
struct tcphdr th;
struct udphdr uh;
/* sanity check */
if (m == NULL)
2005-05-08 22:44:39 +04:00
panic("ipsec6_get_ulp: NULL pointer was passed");
KEYDEBUG(KEYDEBUG_IPSEC_DUMP,
printf("ipsec6_get_ulp:\n"); kdebug_mbuf(m));
/* set default */
spidx->ul_proto = IPSEC_ULPROTO_ANY;
((struct sockaddr_in6 *)&spidx->src)->sin6_port = IPSEC_PORT_ANY;
((struct sockaddr_in6 *)&spidx->dst)->sin6_port = IPSEC_PORT_ANY;
nxt = -1;
off = ip6_lasthdr(m, 0, IPPROTO_IPV6, &nxt);
if (off < 0 || m->m_pkthdr.len < off)
return;
switch (nxt) {
case IPPROTO_TCP:
spidx->ul_proto = nxt;
if (!needport)
break;
if (off + sizeof(struct tcphdr) > m->m_pkthdr.len)
break;
m_copydata(m, off, sizeof(th), (caddr_t)&th);
((struct sockaddr_in6 *)&spidx->src)->sin6_port = th.th_sport;
((struct sockaddr_in6 *)&spidx->dst)->sin6_port = th.th_dport;
break;
case IPPROTO_UDP:
spidx->ul_proto = nxt;
if (!needport)
break;
if (off + sizeof(struct udphdr) > m->m_pkthdr.len)
break;
m_copydata(m, off, sizeof(uh), (caddr_t)&uh);
((struct sockaddr_in6 *)&spidx->src)->sin6_port = uh.uh_sport;
((struct sockaddr_in6 *)&spidx->dst)->sin6_port = uh.uh_dport;
break;
case IPPROTO_ICMPV6:
default:
/* XXX intermediate headers??? */
spidx->ul_proto = nxt;
break;
}
}
/* assumes that m is sane */
static int
ipsec6_setspidx_ipaddr(m, spidx)
struct mbuf *m;
struct secpolicyindex *spidx;
{
struct ip6_hdr *ip6 = NULL;
struct ip6_hdr ip6buf;
struct sockaddr_in6 *sin6;
if (m->m_len >= sizeof(*ip6))
ip6 = mtod(m, struct ip6_hdr *);
else {
m_copydata(m, 0, sizeof(ip6buf), (caddr_t)&ip6buf);
ip6 = &ip6buf;
}
sin6 = (struct sockaddr_in6 *)&spidx->src;
bzero(sin6, sizeof(*sin6));
sin6->sin6_family = AF_INET6;
sin6->sin6_len = sizeof(struct sockaddr_in6);
bcopy(&ip6->ip6_src, &sin6->sin6_addr, sizeof(ip6->ip6_src));
if (IN6_IS_SCOPE_LINKLOCAL(&ip6->ip6_src)) {
sin6->sin6_addr.s6_addr16[1] = 0;
sin6->sin6_scope_id = ntohs(ip6->ip6_src.s6_addr16[1]);
}
spidx->prefs = sizeof(struct in6_addr) << 3;
sin6 = (struct sockaddr_in6 *)&spidx->dst;
bzero(sin6, sizeof(*sin6));
sin6->sin6_family = AF_INET6;
sin6->sin6_len = sizeof(struct sockaddr_in6);
bcopy(&ip6->ip6_dst, &sin6->sin6_addr, sizeof(ip6->ip6_dst));
if (IN6_IS_SCOPE_LINKLOCAL(&ip6->ip6_dst)) {
sin6->sin6_addr.s6_addr16[1] = 0;
sin6->sin6_scope_id = ntohs(ip6->ip6_dst.s6_addr16[1]);
}
spidx->prefd = sizeof(struct in6_addr) << 3;
return 0;
}
#endif
static void
ipsec_delpcbpolicy(p)
struct inpcbpolicy *p;
{
free(p, M_SECA);
}
/* initialize policy in PCB */
int
ipsec_init_policy(so, pcb_sp)
struct socket *so;
struct inpcbpolicy **pcb_sp;
{
struct inpcbpolicy *new;
/* sanity check. */
if (so == NULL || pcb_sp == NULL)
2005-05-08 22:44:39 +04:00
panic("ipsec_init_policy: NULL pointer was passed");
new = (struct inpcbpolicy *) malloc(sizeof(struct inpcbpolicy),
M_SECA, M_NOWAIT|M_ZERO);
if (new == NULL) {
ipseclog((LOG_DEBUG, "ipsec_init_policy: No more memory.\n"));
return ENOBUFS;
}
if (IPSEC_PRIVILEGED_SO(so))
new->priv = 1;
else
new->priv = 0;
if ((new->sp_in = KEY_NEWSP()) == NULL) {
ipsec_delpcbpolicy(new);
return ENOBUFS;
}
new->sp_in->state = IPSEC_SPSTATE_ALIVE;
new->sp_in->policy = IPSEC_POLICY_ENTRUST;
if ((new->sp_out = KEY_NEWSP()) == NULL) {
KEY_FREESP(&new->sp_in);
ipsec_delpcbpolicy(new);
return ENOBUFS;
}
new->sp_out->state = IPSEC_SPSTATE_ALIVE;
new->sp_out->policy = IPSEC_POLICY_ENTRUST;
*pcb_sp = new;
return 0;
}
/* copy old ipsec policy into new */
int
ipsec_copy_policy(old, new)
struct inpcbpolicy *old, *new;
{
struct secpolicy *sp;
sp = ipsec_deepcopy_policy(old->sp_in);
if (sp) {
KEY_FREESP(&new->sp_in);
new->sp_in = sp;
} else
return ENOBUFS;
sp = ipsec_deepcopy_policy(old->sp_out);
if (sp) {
KEY_FREESP(&new->sp_out);
new->sp_out = sp;
} else
return ENOBUFS;
new->priv = old->priv;
return 0;
}
/* deep-copy a policy in PCB */
static struct secpolicy *
ipsec_deepcopy_policy(src)
struct secpolicy *src;
{
struct ipsecrequest *newchain = NULL;
struct ipsecrequest *p;
struct ipsecrequest **q;
struct ipsecrequest *r;
struct secpolicy *dst;
if (src == NULL)
return NULL;
dst = KEY_NEWSP();
if (dst == NULL)
return NULL;
/*
* deep-copy IPsec request chain. This is required since struct
* ipsecrequest is not reference counted.
*/
q = &newchain;
for (p = src->req; p; p = p->next) {
*q = (struct ipsecrequest *)malloc(sizeof(struct ipsecrequest),
M_SECA, M_NOWAIT);
if (*q == NULL)
goto fail;
bzero(*q, sizeof(**q));
(*q)->next = NULL;
(*q)->saidx.proto = p->saidx.proto;
(*q)->saidx.mode = p->saidx.mode;
(*q)->level = p->level;
(*q)->saidx.reqid = p->saidx.reqid;
bcopy(&p->saidx.src, &(*q)->saidx.src, sizeof((*q)->saidx.src));
bcopy(&p->saidx.dst, &(*q)->saidx.dst, sizeof((*q)->saidx.dst));
(*q)->sav = NULL;
(*q)->sp = dst;
q = &((*q)->next);
}
dst->req = newchain;
dst->state = src->state;
dst->policy = src->policy;
/* do not touch the refcnt fields */
return dst;
fail:
for (p = newchain; p; p = r) {
r = p->next;
free(p, M_SECA);
p = NULL;
}
return NULL;
}
/* set policy and ipsec request if present. */
static int
2006-10-14 00:53:59 +04:00
ipsec_set_policy(
struct secpolicy **pcb_sp,
int optname,
caddr_t request,
size_t len,
int priv
2006-10-14 00:53:59 +04:00
)
{
struct sadb_x_policy *xpl;
struct secpolicy *newsp = NULL;
int error;
/* sanity check. */
if (pcb_sp == NULL || *pcb_sp == NULL || request == NULL)
return EINVAL;
if (len < sizeof(*xpl))
return EINVAL;
xpl = (struct sadb_x_policy *)request;
KEYDEBUG(KEYDEBUG_IPSEC_DUMP,
printf("ipsec_set_policy: passed policy\n");
kdebug_sadb_x_policy((struct sadb_ext *)xpl));
/* check policy type */
/* ipsec_set_policy() accepts IPSEC, ENTRUST and BYPASS. */
if (xpl->sadb_x_policy_type == IPSEC_POLICY_DISCARD
|| xpl->sadb_x_policy_type == IPSEC_POLICY_NONE)
return EINVAL;
/* check privileged socket */
if (priv == 0 && xpl->sadb_x_policy_type == IPSEC_POLICY_BYPASS)
return EACCES;
/* allocation new SP entry */
if ((newsp = key_msg2sp(xpl, len, &error)) == NULL)
return error;
newsp->state = IPSEC_SPSTATE_ALIVE;
/* clear old SP and set new SP */
KEY_FREESP(pcb_sp);
*pcb_sp = newsp;
KEYDEBUG(KEYDEBUG_IPSEC_DUMP,
printf("ipsec_set_policy: new policy\n");
kdebug_secpolicy(newsp));
return 0;
}
static int
ipsec_get_policy(pcb_sp, mp)
struct secpolicy *pcb_sp;
struct mbuf **mp;
{
/* sanity check. */
if (pcb_sp == NULL || mp == NULL)
return EINVAL;
*mp = key_sp2msg(pcb_sp);
if (!*mp) {
ipseclog((LOG_DEBUG, "ipsec_get_policy: No more memory.\n"));
return ENOBUFS;
}
(*mp)->m_type = MT_DATA;
KEYDEBUG(KEYDEBUG_IPSEC_DUMP,
printf("ipsec_get_policy:\n");
kdebug_mbuf(*mp));
return 0;
}
int
ipsec4_set_policy(inp, optname, request, len, priv)
struct inpcb *inp;
int optname;
caddr_t request;
size_t len;
int priv;
{
struct sadb_x_policy *xpl;
struct secpolicy **pcb_sp;
/* sanity check. */
if (inp == NULL || request == NULL)
return EINVAL;
if (len < sizeof(*xpl))
return EINVAL;
xpl = (struct sadb_x_policy *)request;
IPSEC_ASSERT(inp->inp_sp != NULL,
("ipsec4_set_policy(): null inp->in_sp"));
/* select direction */
switch (xpl->sadb_x_policy_dir) {
case IPSEC_DIR_INBOUND:
pcb_sp = &inp->inp_sp->sp_in;
break;
case IPSEC_DIR_OUTBOUND:
pcb_sp = &inp->inp_sp->sp_out;
break;
default:
ipseclog((LOG_ERR, "ipsec4_set_policy: invalid direction=%u\n",
xpl->sadb_x_policy_dir));
return EINVAL;
}
return ipsec_set_policy(pcb_sp, optname, request, len, priv);
}
int
ipsec4_get_policy(inp, request, len, mp)
struct inpcb *inp;
caddr_t request;
size_t len;
struct mbuf **mp;
{
struct sadb_x_policy *xpl;
struct secpolicy *pcb_sp;
/* sanity check. */
if (inp == NULL || request == NULL || mp == NULL)
return EINVAL;
IPSEC_ASSERT(inp->inp_sp != NULL, ("ipsec4_get_policy: null inp_sp"));
if (len < sizeof(*xpl))
return EINVAL;
xpl = (struct sadb_x_policy *)request;
/* select direction */
switch (xpl->sadb_x_policy_dir) {
case IPSEC_DIR_INBOUND:
pcb_sp = inp->inp_sp->sp_in;
break;
case IPSEC_DIR_OUTBOUND:
pcb_sp = inp->inp_sp->sp_out;
break;
default:
ipseclog((LOG_ERR, "ipsec4_set_policy: invalid direction=%u\n",
xpl->sadb_x_policy_dir));
return EINVAL;
}
return ipsec_get_policy(pcb_sp, mp);
}
/* delete policy in PCB */
int
ipsec4_delete_pcbpolicy(inp)
struct inpcb *inp;
{
IPSEC_ASSERT(inp != NULL, ("ipsec4_delete_pcbpolicy: null inp"));
if (inp->inp_sp == NULL)
return 0;
if (inp->inp_sp->sp_in != NULL)
KEY_FREESP(&inp->inp_sp->sp_in);
if (inp->inp_sp->sp_out != NULL)
KEY_FREESP(&inp->inp_sp->sp_out);
ipsec_delpcbpolicy(inp->inp_sp);
inp->inp_sp = NULL;
return 0;
}
#ifdef INET6
int
ipsec6_set_policy(in6p, optname, request, len, priv)
struct in6pcb *in6p;
int optname;
caddr_t request;
size_t len;
int priv;
{
struct sadb_x_policy *xpl;
struct secpolicy **pcb_sp;
/* sanity check. */
if (in6p == NULL || request == NULL)
return EINVAL;
if (len < sizeof(*xpl))
return EINVAL;
xpl = (struct sadb_x_policy *)request;
/* select direction */
switch (xpl->sadb_x_policy_dir) {
case IPSEC_DIR_INBOUND:
pcb_sp = &in6p->in6p_sp->sp_in;
break;
case IPSEC_DIR_OUTBOUND:
pcb_sp = &in6p->in6p_sp->sp_out;
break;
default:
ipseclog((LOG_ERR, "ipsec6_set_policy: invalid direction=%u\n",
xpl->sadb_x_policy_dir));
return EINVAL;
}
return ipsec_set_policy(pcb_sp, optname, request, len, priv);
}
int
ipsec6_get_policy(in6p, request, len, mp)
struct in6pcb *in6p;
caddr_t request;
size_t len;
struct mbuf **mp;
{
struct sadb_x_policy *xpl;
struct secpolicy *pcb_sp;
/* sanity check. */
if (in6p == NULL || request == NULL || mp == NULL)
return EINVAL;
IPSEC_ASSERT(in6p->in6p_sp != NULL, ("ipsec6_get_policy: null in6p_sp"));
if (len < sizeof(*xpl))
return EINVAL;
xpl = (struct sadb_x_policy *)request;
/* select direction */
switch (xpl->sadb_x_policy_dir) {
case IPSEC_DIR_INBOUND:
pcb_sp = in6p->in6p_sp->sp_in;
break;
case IPSEC_DIR_OUTBOUND:
pcb_sp = in6p->in6p_sp->sp_out;
break;
default:
ipseclog((LOG_ERR, "ipsec6_set_policy: invalid direction=%u\n",
xpl->sadb_x_policy_dir));
return EINVAL;
}
return ipsec_get_policy(pcb_sp, mp);
}
int
ipsec6_delete_pcbpolicy(in6p)
struct in6pcb *in6p;
{
IPSEC_ASSERT(in6p != NULL, ("ipsec6_delete_pcbpolicy: null in6p"));
if (in6p->in6p_sp == NULL)
return 0;
if (in6p->in6p_sp->sp_in != NULL)
KEY_FREESP(&in6p->in6p_sp->sp_in);
if (in6p->in6p_sp->sp_out != NULL)
KEY_FREESP(&in6p->in6p_sp->sp_out);
ipsec_delpcbpolicy(in6p->in6p_sp);
in6p->in6p_sp = NULL;
return 0;
}
#endif
/*
* return current level.
* Either IPSEC_LEVEL_USE or IPSEC_LEVEL_REQUIRE are always returned.
*/
u_int
ipsec_get_reqlevel(isr)
struct ipsecrequest *isr;
{
u_int level = 0;
u_int esp_trans_deflev, esp_net_deflev;
u_int ah_trans_deflev, ah_net_deflev;
IPSEC_ASSERT(isr != NULL && isr->sp != NULL,
("ipsec_get_reqlevel: null argument"));
IPSEC_ASSERT(isr->sp->spidx.src.sa.sa_family == isr->sp->spidx.dst.sa.sa_family,
("ipsec_get_reqlevel: af family mismatch, src %u, dst %u",
isr->sp->spidx.src.sa.sa_family,
isr->sp->spidx.dst.sa.sa_family));
/* XXX note that we have ipseclog() expanded here - code sync issue */
#define IPSEC_CHECK_DEFAULT(lev) \
(((lev) != IPSEC_LEVEL_USE && (lev) != IPSEC_LEVEL_REQUIRE \
&& (lev) != IPSEC_LEVEL_UNIQUE) \
? (ipsec_debug \
? log(LOG_INFO, "fixed system default level " #lev ":%d->%d\n",\
(lev), IPSEC_LEVEL_REQUIRE) \
: 0), \
(lev) = IPSEC_LEVEL_REQUIRE, \
(lev) \
: (lev))
/* set default level */
switch (((struct sockaddr *)&isr->sp->spidx.src)->sa_family) {
#ifdef INET
case AF_INET:
esp_trans_deflev = IPSEC_CHECK_DEFAULT(ip4_esp_trans_deflev);
esp_net_deflev = IPSEC_CHECK_DEFAULT(ip4_esp_net_deflev);
ah_trans_deflev = IPSEC_CHECK_DEFAULT(ip4_ah_trans_deflev);
ah_net_deflev = IPSEC_CHECK_DEFAULT(ip4_ah_net_deflev);
break;
#endif
#ifdef INET6
case AF_INET6:
esp_trans_deflev = IPSEC_CHECK_DEFAULT(ip6_esp_trans_deflev);
esp_net_deflev = IPSEC_CHECK_DEFAULT(ip6_esp_net_deflev);
ah_trans_deflev = IPSEC_CHECK_DEFAULT(ip6_ah_trans_deflev);
ah_net_deflev = IPSEC_CHECK_DEFAULT(ip6_ah_net_deflev);
break;
#endif /* INET6 */
default:
panic("key_get_reqlevel: unknown af %u",
isr->sp->spidx.src.sa.sa_family);
}
#undef IPSEC_CHECK_DEFAULT
/* set level */
switch (isr->level) {
case IPSEC_LEVEL_DEFAULT:
switch (isr->saidx.proto) {
case IPPROTO_ESP:
if (isr->saidx.mode == IPSEC_MODE_TUNNEL)
level = esp_net_deflev;
else
level = esp_trans_deflev;
break;
case IPPROTO_AH:
if (isr->saidx.mode == IPSEC_MODE_TUNNEL)
level = ah_net_deflev;
else
level = ah_trans_deflev;
break;
case IPPROTO_IPCOMP:
/*
* we don't really care, as IPcomp document says that
* we shouldn't compress small packets
*/
level = IPSEC_LEVEL_USE;
break;
default:
2005-05-08 22:44:39 +04:00
panic("ipsec_get_reqlevel: Illegal protocol defined %u",
isr->saidx.proto);
}
break;
case IPSEC_LEVEL_USE:
case IPSEC_LEVEL_REQUIRE:
level = isr->level;
break;
case IPSEC_LEVEL_UNIQUE:
level = IPSEC_LEVEL_REQUIRE;
break;
default:
2005-05-08 22:44:39 +04:00
panic("ipsec_get_reqlevel: Illegal IPsec level %u",
isr->level);
}
return level;
}
/*
* Check security policy requirements against the actual
* packet contents. Return one if the packet should be
* reject as "invalid"; otherwiser return zero to have the
* packet treated as "valid".
*
* OUT:
* 0: valid
* 1: invalid
*/
int
ipsec_in_reject(struct secpolicy *sp, struct mbuf *m)
{
struct ipsecrequest *isr;
int need_auth;
KEYDEBUG(KEYDEBUG_IPSEC_DATA,
printf("ipsec_in_reject: using SP\n");
kdebug_secpolicy(sp));
/* check policy */
switch (sp->policy) {
case IPSEC_POLICY_DISCARD:
return 1;
case IPSEC_POLICY_BYPASS:
case IPSEC_POLICY_NONE:
return 0;
}
IPSEC_ASSERT(sp->policy == IPSEC_POLICY_IPSEC,
("ipsec_in_reject: invalid policy %u", sp->policy));
/* XXX should compare policy against ipsec header history */
need_auth = 0;
for (isr = sp->req; isr != NULL; isr = isr->next) {
if (ipsec_get_reqlevel(isr) != IPSEC_LEVEL_REQUIRE)
continue;
switch (isr->saidx.proto) {
case IPPROTO_ESP:
if ((m->m_flags & M_DECRYPTED) == 0) {
KEYDEBUG(KEYDEBUG_IPSEC_DUMP,
printf("ipsec_in_reject: ESP m_flags:%x\n",
m->m_flags));
return 1;
}
if (!need_auth &&
isr->sav != NULL &&
isr->sav->tdb_authalgxform != NULL &&
(m->m_flags & M_AUTHIPDGM) == 0) {
KEYDEBUG(KEYDEBUG_IPSEC_DUMP,
printf("ipsec_in_reject: ESP/AH m_flags:%x\n",
m->m_flags));
return 1;
}
break;
case IPPROTO_AH:
need_auth = 1;
if ((m->m_flags & M_AUTHIPHDR) == 0) {
KEYDEBUG(KEYDEBUG_IPSEC_DUMP,
printf("ipsec_in_reject: AH m_flags:%x\n",
m->m_flags));
return 1;
}
break;
case IPPROTO_IPCOMP:
/*
* we don't really care, as IPcomp document
* says that we shouldn't compress small
* packets, IPComp policy should always be
* treated as being in "use" level.
*/
break;
}
}
return 0; /* valid */
}
/*
* Check AH/ESP integrity.
* This function is called from tcp_input(), udp_input(),
* and {ah,esp}4_input for tunnel mode
*/
int
ipsec4_in_reject(m, inp)
struct mbuf *m;
struct inpcb *inp;
{
struct secpolicy *sp;
int error;
int result;
IPSEC_ASSERT(m != NULL, ("ipsec4_in_reject_so: null mbuf"));
/* get SP for this packet.
* When we are called from ip_forward(), we call
* ipsec_getpolicybyaddr() with IP_FORWARDING flag.
*/
if (inp == NULL)
sp = ipsec_getpolicybyaddr(m, IPSEC_DIR_INBOUND, IP_FORWARDING, &error);
else
sp = ipsec_getpolicybysock(m, IPSEC_DIR_INBOUND,
IN4PCB_TO_PCB(inp), &error);
if (sp != NULL) {
result = ipsec_in_reject(sp, m);
if (result)
newipsecstat.ips_in_polvio++;
KEY_FREESP(&sp);
} else {
result = 0; /* XXX should be panic ?
* -> No, there may be error. */
}
return result;
}
#ifdef INET6
/*
* Check AH/ESP integrity.
* This function is called from tcp6_input(), udp6_input(),
* and {ah,esp}6_input for tunnel mode
*/
int
ipsec6_in_reject(m, in6p)
struct mbuf *m;
struct in6pcb *in6p;
{
struct secpolicy *sp = NULL;
int error;
int result;
/* sanity check */
if (m == NULL)
return 0; /* XXX should be panic ? */
/* get SP for this packet.
* When we are called from ip_forward(), we call
* ipsec_getpolicybyaddr() with IP_FORWARDING flag.
*/
if (in6p == NULL)
sp = ipsec_getpolicybyaddr(m, IPSEC_DIR_INBOUND, IP_FORWARDING, &error);
else
sp = ipsec_getpolicybysock(m, IPSEC_DIR_INBOUND,
IN6PCB_TO_PCB(in6p),
&error);
if (sp != NULL) {
result = ipsec_in_reject(sp, m);
if (result)
newipsecstat.ips_in_polvio++;
KEY_FREESP(&sp);
} else {
result = 0;
}
return result;
}
#endif
/*
* compute the byte size to be occupied by IPsec header.
* in case it is tunneled, it includes the size of outer IP header.
* NOTE: SP passed is free in this function.
*/
static size_t
ipsec_hdrsiz(struct secpolicy *sp)
{
struct ipsecrequest *isr;
size_t siz;
KEYDEBUG(KEYDEBUG_IPSEC_DATA,
printf("ipsec_hdrsiz: using SP\n");
kdebug_secpolicy(sp));
switch (sp->policy) {
case IPSEC_POLICY_DISCARD:
case IPSEC_POLICY_BYPASS:
case IPSEC_POLICY_NONE:
return 0;
}
IPSEC_ASSERT(sp->policy == IPSEC_POLICY_IPSEC,
("ipsec_hdrsiz: invalid policy %u", sp->policy));
siz = 0;
for (isr = sp->req; isr != NULL; isr = isr->next) {
size_t clen = 0;
switch (isr->saidx.proto) {
case IPPROTO_ESP:
clen = esp_hdrsiz(isr->sav);
break;
case IPPROTO_AH:
clen = ah_hdrsiz(isr->sav);
break;
case IPPROTO_IPCOMP:
clen = sizeof(struct ipcomp);
break;
}
if (isr->saidx.mode == IPSEC_MODE_TUNNEL) {
switch (isr->saidx.dst.sa.sa_family) {
case AF_INET:
clen += sizeof(struct ip);
break;
#ifdef INET6
case AF_INET6:
clen += sizeof(struct ip6_hdr);
break;
#endif
default:
ipseclog((LOG_ERR, "ipsec_hdrsiz: "
"unknown AF %d in IPsec tunnel SA\n",
((struct sockaddr *)&isr->saidx.dst)->sa_family));
break;
}
}
siz += clen;
}
return siz;
}
/* This function is called from ip_forward() and ipsec4_hdrsize_tcp(). */
size_t
ipsec4_hdrsiz(m, dir, inp)
struct mbuf *m;
u_int dir;
struct inpcb *inp;
{
struct secpolicy *sp;
int error;
size_t size;
IPSEC_ASSERT(m != NULL, ("ipsec4_hdrsiz: null mbuf"));
IPSEC_ASSERT(inp == NULL || inp->inp_socket != NULL,
("ipsec4_hdrsize: socket w/o inpcb"));
/* get SP for this packet.
* When we are called from ip_forward(), we call
* ipsec_getpolicybyaddr() with IP_FORWARDING flag.
*/
if (inp == NULL)
sp = ipsec_getpolicybyaddr(m, dir, IP_FORWARDING, &error);
else
sp = ipsec_getpolicybysock(m, dir,
IN4PCB_TO_PCB(inp), &error);
if (sp != NULL) {
size = ipsec_hdrsiz(sp);
KEYDEBUG(KEYDEBUG_IPSEC_DATA,
printf("ipsec4_hdrsiz: size:%lu.\n",
(unsigned long)size));
KEY_FREESP(&sp);
} else {
size = 0; /* XXX should be panic ? */
}
return size;
}
#ifdef INET6
/* This function is called from ipsec6_hdrsize_tcp(),
* and maybe from ip6_forward.()
*/
size_t
ipsec6_hdrsiz(m, dir, in6p)
struct mbuf *m;
u_int dir;
struct in6pcb *in6p;
{
struct secpolicy *sp;
int error;
size_t size;
IPSEC_ASSERT(m != NULL, ("ipsec6_hdrsiz: null mbuf"));
IPSEC_ASSERT(in6p == NULL || in6p->in6p_socket != NULL,
("ipsec6_hdrsize: socket w/o inpcb"));
/* get SP for this packet */
/* XXX Is it right to call with IP_FORWARDING. */
if (in6p == NULL)
sp = ipsec_getpolicybyaddr(m, dir, IP_FORWARDING, &error);
else
2005-02-27 01:45:09 +03:00
sp = ipsec_getpolicybysock(m, dir,
IN6PCB_TO_PCB(in6p),
&error);
if (sp == NULL)
return 0;
size = ipsec_hdrsiz(sp);
KEYDEBUG(KEYDEBUG_IPSEC_DATA,
printf("ipsec6_hdrsiz: size:%lu.\n", (unsigned long)size));
KEY_FREESP(&sp);
return size;
}
#endif /*INET6*/
/*
* Check the variable replay window.
* ipsec_chkreplay() performs replay check before ICV verification.
* ipsec_updatereplay() updates replay bitmap. This must be called after
* ICV verification (it also performs replay check, which is usually done
* beforehand).
* 0 (zero) is returned if packet disallowed, 1 if packet permitted.
*
* based on RFC 2401.
*/
int
ipsec_chkreplay(seq, sav)
u_int32_t seq;
struct secasvar *sav;
{
const struct secreplay *replay;
u_int32_t diff;
int fr;
u_int32_t wsizeb; /* constant: bits of window size */
int frlast; /* constant: last frame */
IPSEC_SPLASSERT_SOFTNET("ipsec_chkreplay");
IPSEC_ASSERT(sav != NULL, ("ipsec_chkreplay: Null SA"));
IPSEC_ASSERT(sav->replay != NULL, ("ipsec_chkreplay: Null replay state"));
replay = sav->replay;
if (replay->wsize == 0)
return 1; /* no need to check replay. */
/* constant */
frlast = replay->wsize - 1;
wsizeb = replay->wsize << 3;
/* sequence number of 0 is invalid */
if (seq == 0)
return 0;
/* first time is always okay */
if (replay->count == 0)
return 1;
if (seq > replay->lastseq) {
/* larger sequences are okay */
return 1;
} else {
/* seq is equal or less than lastseq. */
diff = replay->lastseq - seq;
/* over range to check, i.e. too old or wrapped */
if (diff >= wsizeb)
return 0;
fr = frlast - diff / 8;
/* this packet already seen ? */
if ((replay->bitmap)[fr] & (1 << (diff % 8)))
return 0;
/* out of order but good */
return 1;
}
}
/*
* check replay counter whether to update or not.
* OUT: 0: OK
* 1: NG
*/
int
ipsec_updatereplay(seq, sav)
u_int32_t seq;
struct secasvar *sav;
{
struct secreplay *replay;
u_int32_t diff;
int fr;
u_int32_t wsizeb; /* constant: bits of window size */
int frlast; /* constant: last frame */
IPSEC_SPLASSERT_SOFTNET("ipsec_updatereplay");
IPSEC_ASSERT(sav != NULL, ("ipsec_updatereplay: Null SA"));
IPSEC_ASSERT(sav->replay != NULL, ("ipsec_updatereplay: Null replay state"));
replay = sav->replay;
if (replay->wsize == 0)
goto ok; /* no need to check replay. */
/* constant */
frlast = replay->wsize - 1;
wsizeb = replay->wsize << 3;
/* sequence number of 0 is invalid */
if (seq == 0)
return 1;
/* first time */
if (replay->count == 0) {
replay->lastseq = seq;
bzero(replay->bitmap, replay->wsize);
(replay->bitmap)[frlast] = 1;
goto ok;
}
if (seq > replay->lastseq) {
/* seq is larger than lastseq. */
diff = seq - replay->lastseq;
/* new larger sequence number */
if (diff < wsizeb) {
/* In window */
/* set bit for this packet */
vshiftl(replay->bitmap, diff, replay->wsize);
(replay->bitmap)[frlast] |= 1;
} else {
/* this packet has a "way larger" */
bzero(replay->bitmap, replay->wsize);
(replay->bitmap)[frlast] = 1;
}
replay->lastseq = seq;
/* larger is good */
} else {
/* seq is equal or less than lastseq. */
diff = replay->lastseq - seq;
/* over range to check, i.e. too old or wrapped */
if (diff >= wsizeb)
return 1;
fr = frlast - diff / 8;
/* this packet already seen ? */
if ((replay->bitmap)[fr] & (1 << (diff % 8)))
return 1;
/* mark as seen */
(replay->bitmap)[fr] |= (1 << (diff % 8));
/* out of order but good */
}
ok:
if (replay->count == ~0) {
/* set overflow flag */
replay->overflow++;
/* don't increment, no more packets accepted */
if ((sav->flags & SADB_X_EXT_CYCSEQ) == 0)
return 1;
ipseclog((LOG_WARNING, "replay counter made %d cycle. %s\n",
replay->overflow, ipsec_logsastr(sav)));
}
replay->count++;
return 0;
}
/*
* shift variable length bunffer to left.
* IN: bitmap: pointer to the buffer
* nbit: the number of to shift.
* wsize: buffer size (bytes).
*/
static void
vshiftl(bitmap, nbit, wsize)
unsigned char *bitmap;
int nbit, wsize;
{
int s, j, i;
unsigned char over;
for (j = 0; j < nbit; j += 8) {
s = (nbit - j < 8) ? (nbit - j): 8;
bitmap[0] <<= s;
for (i = 1; i < wsize; i++) {
over = (bitmap[i] >> (8 - s));
bitmap[i] <<= s;
bitmap[i-1] |= over;
}
}
return;
}
/* Return a printable string for the IPv4 address. */
static char *
inet_ntoa4(struct in_addr ina)
{
static char buf[4][4 * sizeof "123" + 4];
unsigned char *ucp = (unsigned char *) &ina;
static int i = 3;
i = (i + 1) % 4;
2004-04-21 22:40:37 +04:00
snprintf(buf[i], sizeof(buf[i]), "%d.%d.%d.%d",
ucp[0] & 0xff, ucp[1] & 0xff, ucp[2] & 0xff, ucp[3] & 0xff);
return (buf[i]);
}
/* Return a printable string for the address. */
2005-06-10 17:22:42 +04:00
const char *
ipsec_address(union sockaddr_union* sa)
{
switch (sa->sa.sa_family) {
#if INET
case AF_INET:
return inet_ntoa4(sa->sin.sin_addr);
#endif /* INET */
#if INET6
case AF_INET6:
return ip6_sprintf(&sa->sin6.sin6_addr);
#endif /* INET6 */
default:
return "(unknown address family)";
}
}
const char *
ipsec_logsastr(sav)
struct secasvar *sav;
{
static char buf[256];
char *p;
struct secasindex *saidx = &sav->sah->saidx;
IPSEC_ASSERT(saidx->src.sa.sa_family == saidx->dst.sa.sa_family,
("ipsec_logsastr: address family mismatch"));
p = buf;
snprintf(buf, sizeof(buf), "SA(SPI=%u ", (u_int32_t)ntohl(sav->spi));
while (p && *p)
p++;
/* NB: only use ipsec_address on one address at a time */
snprintf(p, sizeof (buf) - (p - buf), "src=%s ",
ipsec_address(&saidx->src));
while (p && *p)
p++;
snprintf(p, sizeof (buf) - (p - buf), "dst=%s)",
ipsec_address(&saidx->dst));
return buf;
}
void
ipsec_dumpmbuf(m)
struct mbuf *m;
{
int totlen;
int i;
u_char *p;
totlen = 0;
printf("---\n");
while (m) {
p = mtod(m, u_char *);
for (i = 0; i < m->m_len; i++) {
printf("%02x ", p[i]);
totlen++;
if (totlen % 16 == 0)
printf("\n");
}
m = m->m_next;
}
if (totlen % 16 != 0)
printf("\n");
printf("---\n");
}
#ifdef INET6
struct secpolicy *
ipsec6_check_policy(m,so,flags,needipsecp,errorp)
struct mbuf * m;
const struct socket * so;
int flags;
int * needipsecp;
int * errorp;
{
struct in6pcb *in6p = NULL;
struct m_tag *mtag;
struct secpolicy *sp = NULL;
struct tdb_ident *tdbi;
int s;
int error = 0;
int needipsec = 0;
if (so != NULL && so->so_proto->pr_domain->dom_family == AF_INET6)
in6p = sotoin6pcb(so);
mtag = m_tag_find(m, PACKET_TAG_IPSEC_PENDING_TDB, NULL);
s = splsoftnet();
if (mtag != NULL) {
tdbi = (struct tdb_ident *)(mtag + 1);
sp = ipsec_getpolicy(tdbi, IPSEC_DIR_OUTBOUND);
if (sp == NULL)
error = -EINVAL; /* force silent drop */
m_tag_delete(m, mtag);
} else {
if (in6p != NULL &&
IPSEC_PCB_SKIP_IPSEC(in6p->in6p_sp, IPSEC_DIR_OUTBOUND))
goto skippolicycheck;
sp = ipsec6_checkpolicy(m, IPSEC_DIR_OUTBOUND, flags, &error,in6p);
}
/*
* There are four return cases:
* sp != NULL apply IPsec policy
* sp == NULL, error == 0 no IPsec handling needed
* sp == NULL, error == -EINVAL discard packet w/o error
* sp == NULL, error != 0 discard packet, report error
*/
if (sp == NULL) {
splx(s);
if (error != 0) {
needipsec = 0;
} else {
/* No IPsec processing for this packet. */
needipsec = 0;
}
} else {
/* Loop detection, check if ipsec processing already done */
IPSEC_ASSERT(sp->req != NULL, ("ip6_output: no ipsec request"));
for (mtag = m_tag_first(m); mtag != NULL;
mtag = m_tag_next(m, mtag)) {
#ifdef MTAG_ABI_COMPAT
if (mtag->m_tag_cookie != MTAG_ABI_COMPAT)
continue;
#endif
if (mtag->m_tag_id != PACKET_TAG_IPSEC_OUT_DONE &&
mtag->m_tag_id != PACKET_TAG_IPSEC_OUT_CRYPTO_NEEDED)
continue;
/*
* Check if policy has an SA associated with it.
* This can happen when an SP has yet to acquire
* an SA; e.g. on first reference. If it occurs,
* then we let ipsec4_process_packet do its thing.
*/
if (sp->req->sav == NULL)
break;
tdbi = (struct tdb_ident *)(mtag + 1);
if (tdbi->spi == sp->req->sav->spi &&
tdbi->proto == sp->req->sav->sah->saidx.proto &&
bcmp(&tdbi->dst, &sp->req->sav->sah->saidx.dst,
sizeof (union sockaddr_union)) == 0) {
/*
* No IPsec processing is needed, free
* reference to SP.
*
* NB: null pointer to avoid free at
* done: below.
*/
KEY_FREESP(&sp), sp = NULL;
needipsec = 0;
splx(s);
goto skippolicycheck;
}
}
splx(s);
needipsec = 1;
}
skippolicycheck:;
*errorp = error;
*needipsecp = needipsec;
return sp;
}
#endif
/* XXX this stuff doesn't belong here... */
static struct xformsw* xforms = NULL;
/*
* Register a transform; typically at system startup.
*/
void
xform_register(struct xformsw* xsp)
{
xsp->xf_next = xforms;
xforms = xsp;
}
/*
* Initialize transform support in an sav.
*/
int
xform_init(struct secasvar *sav, int xftype)
{
struct xformsw *xsp;
if (sav->tdb_xform != NULL) /* previously initialized */
return 0;
for (xsp = xforms; xsp; xsp = xsp->xf_next)
if (xsp->xf_type == xftype)
return (*xsp->xf_init)(sav, xsp);
DPRINTF(("xform_init: no match for xform type %d\n", xftype));
return EINVAL;
}
#ifdef __NetBSD__
void
ipsec_attach(void)
{
printf("initializing IPsec...");
ah_attach();
esp_attach();
ipcomp_attach();
ipe4_attach();
Initial commit of a port of the FreeBSD implementation of RFC 2385 (MD5 signatures for TCP, as used with BGP). Credit for original FreeBSD code goes to Bruce M. Simpson, with FreeBSD sponsorship credited to sentex.net. Shortening of the setsockopt() name attributed to Vincent Jardin. This commit is a minimal, working version of the FreeBSD code, as MFC'ed to FreeBSD-4. It has received minimal testing with a ttcp modified to set the TCP-MD5 option; BMS's additions to tcpdump-current (tcpdump -M) confirm that the MD5 signatures are correct. Committed as-is for further testing between a NetBSD BGP speaker (e.g., quagga) and industry-standard BGP speakers (e.g., Cisco, Juniper). NOTE: This version has two potential flaws. First, I do see any code that verifies recieved TCP-MD5 signatures. Second, the TCP-MD5 options are internally padded and assumed to be 32-bit aligned. A more space-efficient scheme is to pack all TCP options densely (and possibly unaligned) into the TCP header ; then do one final padding to a 4-byte boundary. Pre-existing comments note that accounting for TCP-option space when we add SACK is yet to be done. For now, I'm punting on that; we can solve it properly, in a way that will handle SACK blocks, as a separate exercise. In case a pullup to NetBSD-2 is requested, this adds sys/netipsec/xform_tcp.c ,and modifies: sys/net/pfkeyv2.h,v 1.15 sys/netinet/files.netinet,v 1.5 sys/netinet/ip.h,v 1.25 sys/netinet/tcp.h,v 1.15 sys/netinet/tcp_input.c,v 1.200 sys/netinet/tcp_output.c,v 1.109 sys/netinet/tcp_subr.c,v 1.165 sys/netinet/tcp_usrreq.c,v 1.89 sys/netinet/tcp_var.h,v 1.109 sys/netipsec/files.netipsec,v 1.3 sys/netipsec/ipsec.c,v 1.11 sys/netipsec/ipsec.h,v 1.7 sys/netipsec/key.c,v 1.11 share/man/man4/tcp.4,v 1.16 lib/libipsec/pfkey.c,v 1.20 lib/libipsec/pfkey_dump.c,v 1.17 lib/libipsec/policy_token.l,v 1.8 sbin/setkey/parse.y,v 1.14 sbin/setkey/setkey.8,v 1.27 sbin/setkey/token.l,v 1.15 Note that the preceding two revisions to tcp.4 will be required to cleanly apply this diff.
2004-04-26 02:25:03 +04:00
#ifdef TCP_SIGNATURE
tcpsignature_attach();
#endif
printf(" done\n");
}
#endif /* __NetBSD__ */