diff options
author | Sebastian Huber <sebastian.huber@embedded-brains.de> | 2017-04-04 09:36:57 +0200 |
---|---|---|
committer | Sebastian Huber <sebastian.huber@embedded-brains.de> | 2017-04-04 14:46:23 +0200 |
commit | de8a76da2f374792594ce03a203b3f30e4889f6f (patch) | |
tree | 12b5e1e59358005c3c522955c08aee4795e4829c /freebsd/sys/netipsec | |
parent | Enable bridging by default (diff) | |
download | rtems-libbsd-de8a76da2f374792594ce03a203b3f30e4889f6f.tar.bz2 |
Update to FreeBSD head 2017-04-04
Git mirror commit 642b174daddbd0efd9bb5f242c43f4ab4db6869f.
Diffstat (limited to 'freebsd/sys/netipsec')
30 files changed, 8877 insertions, 8150 deletions
diff --git a/freebsd/sys/netipsec/ah_var.h b/freebsd/sys/netipsec/ah_var.h index 812fe2dc..9b992c07 100644 --- a/freebsd/sys/netipsec/ah_var.h +++ b/freebsd/sys/netipsec/ah_var.h @@ -48,37 +48,39 @@ #define AH_ALG_MAX 16 struct ahstat { - u_int32_t ahs_hdrops; /* Packet shorter than header shows */ - u_int32_t ahs_nopf; /* Protocol family not supported */ - u_int32_t ahs_notdb; - u_int32_t ahs_badkcr; - u_int32_t ahs_badauth; - u_int32_t ahs_noxform; - u_int32_t ahs_qfull; - u_int32_t ahs_wrap; - u_int32_t ahs_replay; - u_int32_t ahs_badauthl; /* Bad authenticator length */ - u_int32_t ahs_input; /* Input AH packets */ - u_int32_t ahs_output; /* Output AH packets */ - u_int32_t ahs_invalid; /* Trying to use an invalid TDB */ - u_int64_t ahs_ibytes; /* Input bytes */ - u_int64_t ahs_obytes; /* Output bytes */ - u_int32_t ahs_toobig; /* Packet got larger than IP_MAXPACKET */ - u_int32_t ahs_pdrops; /* Packet blocked due to policy */ - u_int32_t ahs_crypto; /* Crypto processing failure */ - u_int32_t ahs_tunnel; /* Tunnel sanity check failure */ - u_int32_t ahs_hist[AH_ALG_MAX]; /* Per-algorithm op count */ + uint64_t ahs_hdrops; /* Packet shorter than header shows */ + uint64_t ahs_nopf; /* Protocol family not supported */ + uint64_t ahs_notdb; + uint64_t ahs_badkcr; + uint64_t ahs_badauth; + uint64_t ahs_noxform; + uint64_t ahs_qfull; + uint64_t ahs_wrap; + uint64_t ahs_replay; + uint64_t ahs_badauthl; /* Bad authenticator length */ + uint64_t ahs_input; /* Input AH packets */ + uint64_t ahs_output; /* Output AH packets */ + uint64_t ahs_invalid; /* Trying to use an invalid TDB */ + uint64_t ahs_ibytes; /* Input bytes */ + uint64_t ahs_obytes; /* Output bytes */ + uint64_t ahs_toobig; /* Packet got larger than IP_MAXPACKET */ + uint64_t ahs_pdrops; /* Packet blocked due to policy */ + uint64_t ahs_crypto; /* Crypto processing failure */ + uint64_t ahs_tunnel; /* Tunnel sanity check failure */ + uint64_t ahs_hist[AH_ALG_MAX]; /* Per-algorithm op count */ }; #ifdef _KERNEL +#include <sys/counter.h> + VNET_DECLARE(int, ah_enable); VNET_DECLARE(int, ah_cleartos); -VNET_DECLARE(struct ahstat, ahstat); +VNET_PCPUSTAT_DECLARE(struct ahstat, ahstat); -#define AHSTAT_ADD(name, val) V_ahstat.name += (val) +#define AHSTAT_ADD(name, val) \ + VNET_PCPUSTAT_ADD(struct ahstat, ahstat, name , (val)) #define AHSTAT_INC(name) AHSTAT_ADD(name, 1) #define V_ah_enable VNET(ah_enable) #define V_ah_cleartos VNET(ah_cleartos) -#define V_ahstat VNET(ahstat) #endif /* _KERNEL */ #endif /*_NETIPSEC_AH_VAR_H_*/ diff --git a/freebsd/sys/netipsec/esp.h b/freebsd/sys/netipsec/esp.h index eb373970..8eb09630 100644 --- a/freebsd/sys/netipsec/esp.h +++ b/freebsd/sys/netipsec/esp.h @@ -42,8 +42,7 @@ struct esp { /*variable size, 32bit bound*/ /* Initialization Vector */ /*variable size*/ /* Payload data */ /*variable size*/ /* padding */ - /*8bit*/ /* pad size */ - /*8bit*/ /* next header */ + /*8bit*/ /* pad length */ /*8bit*/ /* next header */ /*variable size, 32bit bound*/ /* Authentication data (new IPsec) */ }; @@ -53,8 +52,7 @@ struct newesp { u_int32_t esp_seq; /* Sequence number */ /*variable size*/ /* (IV and) Payload data */ /*variable size*/ /* padding */ - /*8bit*/ /* pad size */ - /*8bit*/ /* next header */ + /*8bit*/ /* pad length */ /*8bit*/ /* next header */ /*variable size, 32bit bound*/ /* Authentication data */ }; diff --git a/freebsd/sys/netipsec/esp_var.h b/freebsd/sys/netipsec/esp_var.h index c6133614..48240418 100644 --- a/freebsd/sys/netipsec/esp_var.h +++ b/freebsd/sys/netipsec/esp_var.h @@ -48,36 +48,38 @@ #define ESP_ALG_MAX 256 /* NB: could be < but skipjack is 249 */ struct espstat { - u_int32_t esps_hdrops; /* Packet shorter than header shows */ - u_int32_t esps_nopf; /* Protocol family not supported */ - u_int32_t esps_notdb; - u_int32_t esps_badkcr; - u_int32_t esps_qfull; - u_int32_t esps_noxform; - u_int32_t esps_badilen; - u_int32_t esps_wrap; /* Replay counter wrapped around */ - u_int32_t esps_badenc; /* Bad encryption detected */ - u_int32_t esps_badauth; /* Only valid for transforms with auth */ - u_int32_t esps_replay; /* Possible packet replay detected */ - u_int32_t esps_input; /* Input ESP packets */ - u_int32_t esps_output; /* Output ESP packets */ - u_int32_t esps_invalid; /* Trying to use an invalid TDB */ - u_int64_t esps_ibytes; /* Input bytes */ - u_int64_t esps_obytes; /* Output bytes */ - u_int32_t esps_toobig; /* Packet got larger than IP_MAXPACKET */ - u_int32_t esps_pdrops; /* Packet blocked due to policy */ - u_int32_t esps_crypto; /* Crypto processing failure */ - u_int32_t esps_tunnel; /* Tunnel sanity check failure */ - u_int32_t esps_hist[ESP_ALG_MAX]; /* Per-algorithm op count */ + uint64_t esps_hdrops; /* Packet shorter than header shows */ + uint64_t esps_nopf; /* Protocol family not supported */ + uint64_t esps_notdb; + uint64_t esps_badkcr; + uint64_t esps_qfull; + uint64_t esps_noxform; + uint64_t esps_badilen; + uint64_t esps_wrap; /* Replay counter wrapped around */ + uint64_t esps_badenc; /* Bad encryption detected */ + uint64_t esps_badauth; /* Only valid for transforms with auth */ + uint64_t esps_replay; /* Possible packet replay detected */ + uint64_t esps_input; /* Input ESP packets */ + uint64_t esps_output; /* Output ESP packets */ + uint64_t esps_invalid; /* Trying to use an invalid TDB */ + uint64_t esps_ibytes; /* Input bytes */ + uint64_t esps_obytes; /* Output bytes */ + uint64_t esps_toobig; /* Packet got larger than IP_MAXPACKET */ + uint64_t esps_pdrops; /* Packet blocked due to policy */ + uint64_t esps_crypto; /* Crypto processing failure */ + uint64_t esps_tunnel; /* Tunnel sanity check failure */ + uint64_t esps_hist[ESP_ALG_MAX]; /* Per-algorithm op count */ }; #ifdef _KERNEL +#include <sys/counter.h> + VNET_DECLARE(int, esp_enable); -VNET_DECLARE(struct espstat, espstat); +VNET_PCPUSTAT_DECLARE(struct espstat, espstat); -#define ESPSTAT_ADD(name, val) V_espstat.name += (val) +#define ESPSTAT_ADD(name, val) \ + VNET_PCPUSTAT_ADD(struct espstat, espstat, name, (val)) #define ESPSTAT_INC(name) ESPSTAT_ADD(name, 1) #define V_esp_enable VNET(esp_enable) -#define V_espstat VNET(espstat) #endif /* _KERNEL */ #endif /*_NETIPSEC_ESP_VAR_H_*/ diff --git a/freebsd/sys/netipsec/ipcomp_var.h b/freebsd/sys/netipsec/ipcomp_var.h index ee15598f..5062c9dd 100644 --- a/freebsd/sys/netipsec/ipcomp_var.h +++ b/freebsd/sys/netipsec/ipcomp_var.h @@ -41,36 +41,37 @@ */ #define IPCOMP_ALG_MAX 8 -#define IPCOMPSTAT_VERSION 1 +#define IPCOMPSTAT_VERSION 2 struct ipcompstat { - u_int32_t ipcomps_hdrops; /* Packet shorter than header shows */ - u_int32_t ipcomps_nopf; /* Protocol family not supported */ - u_int32_t ipcomps_notdb; - u_int32_t ipcomps_badkcr; - u_int32_t ipcomps_qfull; - u_int32_t ipcomps_noxform; - u_int32_t ipcomps_wrap; - u_int32_t ipcomps_input; /* Input IPcomp packets */ - u_int32_t ipcomps_output; /* Output IPcomp packets */ - u_int32_t ipcomps_invalid;/* Trying to use an invalid TDB */ - u_int64_t ipcomps_ibytes; /* Input bytes */ - u_int64_t ipcomps_obytes; /* Output bytes */ - u_int32_t ipcomps_toobig; /* Packet got > IP_MAXPACKET */ - u_int32_t ipcomps_pdrops; /* Packet blocked due to policy */ - u_int32_t ipcomps_crypto; /* "Crypto" processing failure */ - u_int32_t ipcomps_hist[IPCOMP_ALG_MAX];/* Per-algorithm op count */ - u_int32_t version; /* Version of this structure. */ - u_int32_t ipcomps_threshold; /* Packet < comp. algo. threshold. */ - u_int32_t ipcomps_uncompr; /* Compression was useles. */ + uint64_t ipcomps_hdrops; /* Packet shorter than header shows */ + uint64_t ipcomps_nopf; /* Protocol family not supported */ + uint64_t ipcomps_notdb; + uint64_t ipcomps_badkcr; + uint64_t ipcomps_qfull; + uint64_t ipcomps_noxform; + uint64_t ipcomps_wrap; + uint64_t ipcomps_input; /* Input IPcomp packets */ + uint64_t ipcomps_output; /* Output IPcomp packets */ + uint64_t ipcomps_invalid;/* Trying to use an invalid TDB */ + uint64_t ipcomps_ibytes; /* Input bytes */ + uint64_t ipcomps_obytes; /* Output bytes */ + uint64_t ipcomps_toobig; /* Packet got > IP_MAXPACKET */ + uint64_t ipcomps_pdrops; /* Packet blocked due to policy */ + uint64_t ipcomps_crypto; /* "Crypto" processing failure */ + uint64_t ipcomps_hist[IPCOMP_ALG_MAX];/* Per-algorithm op count */ + uint64_t ipcomps_threshold; /* Packet < comp. algo. threshold. */ + uint64_t ipcomps_uncompr; /* Compression was useles. */ }; #ifdef _KERNEL +#include <sys/counter.h> + VNET_DECLARE(int, ipcomp_enable); -VNET_DECLARE(struct ipcompstat, ipcompstat); +VNET_PCPUSTAT_DECLARE(struct ipcompstat, ipcompstat); -#define IPCOMPSTAT_ADD(name, val) V_ipcompstat.name += (val) +#define IPCOMPSTAT_ADD(name, val) \ + VNET_PCPUSTAT_ADD(struct ipcompstat, ipcompstat, name, (val)) #define IPCOMPSTAT_INC(name) IPCOMPSTAT_ADD(name, 1) #define V_ipcomp_enable VNET(ipcomp_enable) -#define V_ipcompstat VNET(ipcompstat) #endif /* _KERNEL */ #endif /*_NETIPSEC_IPCOMP_VAR_H_*/ diff --git a/freebsd/sys/netipsec/ipip_var.h b/freebsd/sys/netipsec/ipip_var.h deleted file mode 100644 index 415d5c10..00000000 --- a/freebsd/sys/netipsec/ipip_var.h +++ /dev/null @@ -1,70 +0,0 @@ -/* $FreeBSD$ */ -/* $OpenBSD: ip_ipip.h,v 1.5 2002/06/09 16:26:10 itojun Exp $ */ -/*- - * The authors of this code are John Ioannidis (ji@tla.org), - * Angelos D. Keromytis (kermit@csd.uch.gr) and - * Niels Provos (provos@physnet.uni-hamburg.de). - * - * The original version of this code was written by John Ioannidis - * for BSD/OS in Athens, Greece, in November 1995. - * - * Ported to OpenBSD and NetBSD, with additional transforms, in December 1996, - * by Angelos D. Keromytis. - * - * Additional transforms and features in 1997 and 1998 by Angelos D. Keromytis - * and Niels Provos. - * - * Additional features in 1999 by Angelos D. Keromytis. - * - * Copyright (C) 1995, 1996, 1997, 1998, 1999 by John Ioannidis, - * Angelos D. Keromytis and Niels Provos. - * Copyright (c) 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. - */ - -#ifndef _NETINET_IPIP_H_ -#define _NETINET_IPIP_H_ - -/* - * IP-inside-IP processing. - * Not quite all the functionality of RFC-1853, but the main idea is there. - */ - -struct ipipstat -{ - u_int32_t ipips_ipackets; /* total input packets */ - u_int32_t ipips_opackets; /* total output packets */ - u_int32_t ipips_hdrops; /* packet shorter than header shows */ - u_int32_t ipips_qfull; - u_int64_t ipips_ibytes; - u_int64_t ipips_obytes; - u_int32_t ipips_pdrops; /* packet dropped due to policy */ - u_int32_t ipips_spoof; /* IP spoofing attempts */ - u_int32_t ipips_family; /* Protocol family mismatch */ - u_int32_t ipips_unspec; /* Missing tunnel endpoint address */ -}; - -#ifdef _KERNEL -VNET_DECLARE(int, ipip_allow); -VNET_DECLARE(struct ipipstat, ipipstat); - -#define IPIPSTAT_ADD(name, val) V_ipipstat.name += (val) -#define IPIPSTAT_INC(name) IPIPSTAT_ADD(name, 1) -#define V_ipip_allow VNET(ipip_allow) -#define V_ipipstat VNET(ipipstat) -#endif /* _KERNEL */ -#endif /* _NETINET_IPIP_H_ */ diff --git a/freebsd/sys/netipsec/ipsec.c b/freebsd/sys/netipsec/ipsec.c index c1eed678..20aad0b6 100644 --- a/freebsd/sys/netipsec/ipsec.c +++ b/freebsd/sys/netipsec/ipsec.c @@ -50,6 +50,7 @@ #include <sys/socket.h> #include <sys/socketvar.h> #include <rtems/bsd/sys/errno.h> +#include <sys/hhook.h> #include <sys/time.h> #include <sys/kernel.h> #include <sys/syslog.h> @@ -57,7 +58,8 @@ #include <sys/proc.h> #include <net/if.h> -#include <net/route.h> +#include <net/if_enc.h> +#include <net/if_var.h> #include <net/vnet.h> #include <netinet/in.h> @@ -88,6 +90,7 @@ #include <netipsec/esp_var.h> #include <netipsec/ipcomp.h> /*XXX*/ #include <netipsec/ipcomp_var.h> +#include <netipsec/ipsec_support.h> #include <netipsec/key.h> #include <netipsec/keydb.h> @@ -99,14 +102,14 @@ #include <opencrypto/cryptodev.h> -#ifdef IPSEC_DEBUG -VNET_DEFINE(int, ipsec_debug) = 1; -#else -VNET_DEFINE(int, ipsec_debug) = 0; -#endif - /* NB: name changed so netstat doesn't use it. */ -VNET_DEFINE(struct ipsecstat, ipsec4stat); +VNET_PCPUSTAT_DEFINE(struct ipsecstat, ipsec4stat); +VNET_PCPUSTAT_SYSINIT(ipsec4stat); + +#ifdef VIMAGE +VNET_PCPUSTAT_SYSUNINIT(ipsec4stat); +#endif /* VIMAGE */ + VNET_DEFINE(int, ip4_ah_offsetmask) = 0; /* maybe IP_DF? */ /* DF bit on encap. 0: clear 1: set 2: copy */ VNET_DEFINE(int, ip4_ipsec_dfbit) = 0; @@ -114,11 +117,32 @@ VNET_DEFINE(int, ip4_esp_trans_deflev) = IPSEC_LEVEL_USE; VNET_DEFINE(int, ip4_esp_net_deflev) = IPSEC_LEVEL_USE; VNET_DEFINE(int, ip4_ah_trans_deflev) = IPSEC_LEVEL_USE; VNET_DEFINE(int, ip4_ah_net_deflev) = IPSEC_LEVEL_USE; -VNET_DEFINE(struct secpolicy, ip4_def_policy); /* ECN ignore(-1)/forbidden(0)/allowed(1) */ VNET_DEFINE(int, ip4_ipsec_ecn) = 0; VNET_DEFINE(int, ip4_esp_randpad) = -1; +static VNET_DEFINE(int, ip4_filtertunnel) = 0; +#define V_ip4_filtertunnel VNET(ip4_filtertunnel) +static VNET_DEFINE(int, check_policy_history) = 0; +#define V_check_policy_history VNET(check_policy_history) +static VNET_DEFINE(struct secpolicy *, def_policy) = NULL; +#define V_def_policy VNET(def_policy) +static int +sysctl_def_policy(SYSCTL_HANDLER_ARGS) +{ + int error, value; + + value = V_def_policy->policy; + error = sysctl_handle_int(oidp, &value, 0, req); + if (error == 0) { + if (value != IPSEC_POLICY_DISCARD && + value != IPSEC_POLICY_NONE) + return (EINVAL); + V_def_policy->policy = value; + } + return (error); +} + /* * Crypto support requirements: * @@ -127,51 +151,63 @@ VNET_DEFINE(int, ip4_esp_randpad) = -1; * 0 take anything */ VNET_DEFINE(int, crypto_support) = CRYPTOCAP_F_HARDWARE | CRYPTOCAP_F_SOFTWARE; +/* + * TCP/UDP checksum handling policy for transport mode NAT-T (RFC3948) + * + * 0 - auto: incrementally recompute, when checksum delta is known; + * if checksum delta isn't known, reset checksum to zero for UDP, + * and mark csum_flags as valid for TCP. + * 1 - fully recompute TCP/UDP checksum. + */ +VNET_DEFINE(int, natt_cksum_policy) = 0; FEATURE(ipsec, "Internet Protocol Security (IPsec)"); -#ifdef IPSEC_NAT_T FEATURE(ipsec_natt, "UDP Encapsulation of IPsec ESP Packets ('NAT-T')"); -#endif SYSCTL_DECL(_net_inet_ipsec); /* net.inet.ipsec */ -SYSCTL_VNET_INT(_net_inet_ipsec, IPSECCTL_DEF_POLICY, def_policy, - CTLFLAG_RW, &VNET_NAME(ip4_def_policy).policy, 0, +SYSCTL_PROC(_net_inet_ipsec, IPSECCTL_DEF_POLICY, def_policy, + CTLTYPE_INT | CTLFLAG_VNET | CTLFLAG_RW, 0, 0, sysctl_def_policy, "I", "IPsec default policy."); -SYSCTL_VNET_INT(_net_inet_ipsec, IPSECCTL_DEF_ESP_TRANSLEV, esp_trans_deflev, - CTLFLAG_RW, &VNET_NAME(ip4_esp_trans_deflev), 0, +SYSCTL_INT(_net_inet_ipsec, IPSECCTL_DEF_ESP_TRANSLEV, esp_trans_deflev, + CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(ip4_esp_trans_deflev), 0, "Default ESP transport mode level"); -SYSCTL_VNET_INT(_net_inet_ipsec, IPSECCTL_DEF_ESP_NETLEV, esp_net_deflev, - CTLFLAG_RW, &VNET_NAME(ip4_esp_net_deflev), 0, +SYSCTL_INT(_net_inet_ipsec, IPSECCTL_DEF_ESP_NETLEV, esp_net_deflev, + CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(ip4_esp_net_deflev), 0, "Default ESP tunnel mode level."); -SYSCTL_VNET_INT(_net_inet_ipsec, IPSECCTL_DEF_AH_TRANSLEV, ah_trans_deflev, - CTLFLAG_RW, &VNET_NAME(ip4_ah_trans_deflev), 0, +SYSCTL_INT(_net_inet_ipsec, IPSECCTL_DEF_AH_TRANSLEV, ah_trans_deflev, + CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(ip4_ah_trans_deflev), 0, "AH transfer mode default level."); -SYSCTL_VNET_INT(_net_inet_ipsec, IPSECCTL_DEF_AH_NETLEV, ah_net_deflev, - CTLFLAG_RW, &VNET_NAME(ip4_ah_net_deflev), 0, +SYSCTL_INT(_net_inet_ipsec, IPSECCTL_DEF_AH_NETLEV, ah_net_deflev, + CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(ip4_ah_net_deflev), 0, "AH tunnel mode default level."); -SYSCTL_VNET_INT(_net_inet_ipsec, IPSECCTL_AH_CLEARTOS, ah_cleartos, - CTLFLAG_RW, &VNET_NAME(ah_cleartos), 0, - "If set clear type-of-service field when doing AH computation."); -SYSCTL_VNET_INT(_net_inet_ipsec, IPSECCTL_AH_OFFSETMASK, ah_offsetmask, - CTLFLAG_RW, &VNET_NAME(ip4_ah_offsetmask), 0, - "If not set clear offset field mask when doing AH computation."); -SYSCTL_VNET_INT(_net_inet_ipsec, IPSECCTL_DFBIT, dfbit, - CTLFLAG_RW, &VNET_NAME(ip4_ipsec_dfbit), 0, +SYSCTL_INT(_net_inet_ipsec, IPSECCTL_AH_CLEARTOS, ah_cleartos, + CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(ah_cleartos), 0, + "If set, clear type-of-service field when doing AH computation."); +SYSCTL_INT(_net_inet_ipsec, IPSECCTL_AH_OFFSETMASK, ah_offsetmask, + CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(ip4_ah_offsetmask), 0, + "If not set, clear offset field mask when doing AH computation."); +SYSCTL_INT(_net_inet_ipsec, IPSECCTL_DFBIT, dfbit, + CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(ip4_ipsec_dfbit), 0, "Do not fragment bit on encap."); -SYSCTL_VNET_INT(_net_inet_ipsec, IPSECCTL_ECN, ecn, - CTLFLAG_RW, &VNET_NAME(ip4_ipsec_ecn), 0, +SYSCTL_INT(_net_inet_ipsec, IPSECCTL_ECN, ecn, + CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(ip4_ipsec_ecn), 0, "Explicit Congestion Notification handling."); -SYSCTL_VNET_INT(_net_inet_ipsec, IPSECCTL_DEBUG, debug, - CTLFLAG_RW, &VNET_NAME(ipsec_debug), 0, - "Enable IPsec debugging output when set."); -SYSCTL_VNET_INT(_net_inet_ipsec, OID_AUTO, crypto_support, - CTLFLAG_RW, &VNET_NAME(crypto_support), 0, +SYSCTL_INT(_net_inet_ipsec, OID_AUTO, crypto_support, + CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(crypto_support), 0, "Crypto driver selection."); -SYSCTL_VNET_STRUCT(_net_inet_ipsec, OID_AUTO, ipsecstats, - CTLFLAG_RD, &VNET_NAME(ipsec4stat), ipsecstat, - "IPsec IPv4 statistics."); +SYSCTL_INT(_net_inet_ipsec, OID_AUTO, check_policy_history, + CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(check_policy_history), 0, + "Use strict check of inbound packets to security policy compliance."); +SYSCTL_INT(_net_inet_ipsec, OID_AUTO, natt_cksum_policy, + CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(natt_cksum_policy), 0, + "Method to fix TCP/UDP checksum for transport mode IPsec after NAT."); +SYSCTL_INT(_net_inet_ipsec, OID_AUTO, filtertunnel, + CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(ip4_filtertunnel), 0, + "If set, filter packets from an IPsec tunnel."); +SYSCTL_VNET_PCPUSTAT(_net_inet_ipsec, OID_AUTO, ipsecstats, struct ipsecstat, + ipsec4stat, "IPsec IPv4 statistics."); #ifdef REGRESSION /* @@ -179,448 +215,293 @@ SYSCTL_VNET_STRUCT(_net_inet_ipsec, OID_AUTO, ipsecstats, * This allows to verify if the other side has proper replay attacks detection. */ VNET_DEFINE(int, ipsec_replay) = 0; -SYSCTL_VNET_INT(_net_inet_ipsec, OID_AUTO, test_replay, - CTLFLAG_RW, &VNET_NAME(ipsec_replay), 0, +SYSCTL_INT(_net_inet_ipsec, OID_AUTO, test_replay, + CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(ipsec_replay), 0, "Emulate replay attack"); /* * When set 1, IPsec will send packets with corrupted HMAC. * This allows to verify if the other side properly detects modified packets. */ VNET_DEFINE(int, ipsec_integrity) = 0; -SYSCTL_VNET_INT(_net_inet_ipsec, OID_AUTO, test_integrity, - CTLFLAG_RW, &VNET_NAME(ipsec_integrity), 0, +SYSCTL_INT(_net_inet_ipsec, OID_AUTO, test_integrity, + CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(ipsec_integrity), 0, "Emulate man-in-the-middle attack"); #endif #ifdef INET6 -VNET_DEFINE(struct ipsecstat, ipsec6stat); +VNET_PCPUSTAT_DEFINE(struct ipsecstat, ipsec6stat); +VNET_PCPUSTAT_SYSINIT(ipsec6stat); + +#ifdef VIMAGE +VNET_PCPUSTAT_SYSUNINIT(ipsec6stat); +#endif /* VIMAGE */ + VNET_DEFINE(int, ip6_esp_trans_deflev) = IPSEC_LEVEL_USE; VNET_DEFINE(int, ip6_esp_net_deflev) = IPSEC_LEVEL_USE; VNET_DEFINE(int, ip6_ah_trans_deflev) = IPSEC_LEVEL_USE; VNET_DEFINE(int, ip6_ah_net_deflev) = IPSEC_LEVEL_USE; VNET_DEFINE(int, ip6_ipsec_ecn) = 0; /* ECN ignore(-1)/forbidden(0)/allowed(1) */ +static VNET_DEFINE(int, ip6_filtertunnel) = 0; +#define V_ip6_filtertunnel VNET(ip6_filtertunnel) + 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", "IPsec IPv6 statistics."); -#endif /* COMPAT_KAME */ -SYSCTL_VNET_INT(_net_inet6_ipsec6, IPSECCTL_DEF_POLICY, def_policy, CTLFLAG_RW, - &VNET_NAME(ip4_def_policy).policy, 0, +SYSCTL_PROC(_net_inet6_ipsec6, IPSECCTL_DEF_POLICY, def_policy, + CTLTYPE_INT | CTLFLAG_VNET | CTLFLAG_RW, 0, 0, sysctl_def_policy, "I", "IPsec default policy."); -SYSCTL_VNET_INT(_net_inet6_ipsec6, IPSECCTL_DEF_ESP_TRANSLEV, - esp_trans_deflev, CTLFLAG_RW, &VNET_NAME(ip6_esp_trans_deflev), 0, +SYSCTL_INT(_net_inet6_ipsec6, IPSECCTL_DEF_ESP_TRANSLEV, esp_trans_deflev, + CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(ip6_esp_trans_deflev), 0, "Default ESP transport mode level."); -SYSCTL_VNET_INT(_net_inet6_ipsec6, IPSECCTL_DEF_ESP_NETLEV, - esp_net_deflev, CTLFLAG_RW, &VNET_NAME(ip6_esp_net_deflev), 0, +SYSCTL_INT(_net_inet6_ipsec6, IPSECCTL_DEF_ESP_NETLEV, esp_net_deflev, + CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(ip6_esp_net_deflev), 0, "Default ESP tunnel mode level."); -SYSCTL_VNET_INT(_net_inet6_ipsec6, IPSECCTL_DEF_AH_TRANSLEV, - ah_trans_deflev, CTLFLAG_RW, &VNET_NAME(ip6_ah_trans_deflev), 0, +SYSCTL_INT(_net_inet6_ipsec6, IPSECCTL_DEF_AH_TRANSLEV, ah_trans_deflev, + CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(ip6_ah_trans_deflev), 0, "AH transfer mode default level."); -SYSCTL_VNET_INT(_net_inet6_ipsec6, IPSECCTL_DEF_AH_NETLEV, - ah_net_deflev, CTLFLAG_RW, &VNET_NAME(ip6_ah_net_deflev), 0, +SYSCTL_INT(_net_inet6_ipsec6, IPSECCTL_DEF_AH_NETLEV, ah_net_deflev, + CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(ip6_ah_net_deflev), 0, "AH tunnel mode default level."); -SYSCTL_VNET_INT(_net_inet6_ipsec6, IPSECCTL_ECN, - ecn, CTLFLAG_RW, &VNET_NAME(ip6_ipsec_ecn), 0, +SYSCTL_INT(_net_inet6_ipsec6, IPSECCTL_ECN, ecn, + CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(ip6_ipsec_ecn), 0, "Explicit Congestion Notification handling."); -SYSCTL_VNET_INT(_net_inet6_ipsec6, IPSECCTL_DEBUG, debug, CTLFLAG_RW, - &VNET_NAME(ipsec_debug), 0, - "Enable IPsec debugging output when set."); -SYSCTL_VNET_STRUCT(_net_inet6_ipsec6, IPSECCTL_STATS, - ipsecstats, CTLFLAG_RD, &VNET_NAME(ipsec6stat), ipsecstat, - "IPsec IPv6 statistics."); +SYSCTL_INT(_net_inet6_ipsec6, OID_AUTO, filtertunnel, + CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(ip6_filtertunnel), 0, + "If set, filter packets from an IPsec tunnel."); +SYSCTL_VNET_PCPUSTAT(_net_inet6_ipsec6, IPSECCTL_STATS, ipsecstats, + struct ipsecstat, ipsec6stat, "IPsec IPv6 statistics."); #endif /* INET6 */ -static int ipsec_setspidx_inpcb __P((struct mbuf *, struct inpcb *)); -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 *)); +static int ipsec_in_reject(struct secpolicy *, struct inpcb *, + const struct mbuf *); + +#ifdef INET +static void ipsec4_get_ulp(const struct mbuf *, struct secpolicyindex *, int); +static void ipsec4_setspidx_ipaddr(const struct mbuf *, + struct secpolicyindex *); +#endif #ifdef INET6 -static void ipsec6_get_ulp __P((struct mbuf *m, struct secpolicyindex *, int)); -static int ipsec6_setspidx_ipaddr __P((struct mbuf *, struct secpolicyindex *)); +static void ipsec6_get_ulp(const struct mbuf *m, struct secpolicyindex *, int); +static void ipsec6_setspidx_ipaddr(const struct mbuf *, + struct secpolicyindex *); #endif -static void ipsec_delpcbpolicy __P((struct inpcbpolicy *)); -static struct secpolicy *ipsec_deepcopy_policy __P((struct secpolicy *src)); -static void vshiftl __P((unsigned char *, int, int)); - -MALLOC_DEFINE(M_IPSEC_INPCB, "inpcbpolicy", "inpcb-resident ipsec policy"); /* * Return a held reference to the default SP. */ static struct secpolicy * -key_allocsp_default(const char* where, int tag) +key_allocsp_default(void) { - struct secpolicy *sp; - - KEYDEBUG(KEYDEBUG_IPSEC_STAMP, - printf("DP key_allocsp_default from %s:%u\n", where, tag)); - - sp = &V_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; - } - key_addref(sp); - KEYDEBUG(KEYDEBUG_IPSEC_STAMP, - printf("DP key_allocsp_default returns SP:%p (%u)\n", - sp, sp->refcnt)); - return (sp); + key_addref(V_def_policy); + return (V_def_policy); } -#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 occured. - * others: a pointer to SP - * - * NOTE: IPv6 mapped adddress concern is implemented here. - */ -struct secpolicy * -ipsec_getpolicy(struct tdb_ident *tdbi, u_int dir) +static void +ipsec_invalidate_cache(struct inpcb *inp, u_int dir) { struct secpolicy *sp; - IPSEC_ASSERT(tdbi != NULL, ("null tdbi")); - IPSEC_ASSERT(dir == IPSEC_DIR_INBOUND || dir == IPSEC_DIR_OUTBOUND, - ("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, ("null SP")); - return (sp); + INP_WLOCK_ASSERT(inp); + if (dir == IPSEC_DIR_OUTBOUND) { + if (inp->inp_sp->flags & INP_INBOUND_POLICY) + return; + sp = inp->inp_sp->sp_in; + inp->inp_sp->sp_in = NULL; + } else { + if (inp->inp_sp->flags & INP_OUTBOUND_POLICY) + return; + sp = inp->inp_sp->sp_out; + inp->inp_sp->sp_out = NULL; + } + if (sp != NULL) + key_freesp(&sp); /* release extra reference */ } -/* - * 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 occured. - * others: a pointer to SP - * - * NOTE: IPv6 mapped adddress concern is implemented here. - */ -static struct secpolicy * -ipsec_getpolicybysock(struct mbuf *m, u_int dir, struct inpcb *inp, int *error) +static void +ipsec_cachepolicy(struct inpcb *inp, struct secpolicy *sp, u_int dir) { - struct inpcbpolicy *pcbsp; - struct secpolicy *currsp = NULL; /* Policy on socket. */ - struct secpolicy *sp; - - IPSEC_ASSERT(m != NULL, ("null mbuf")); - IPSEC_ASSERT(inp != NULL, ("null inpcb")); - IPSEC_ASSERT(error != NULL, ("null error")); - IPSEC_ASSERT(dir == IPSEC_DIR_INBOUND || dir == IPSEC_DIR_OUTBOUND, - ("invalid direction %u", dir)); + uint32_t genid; + int downgrade; - /* Set spidx in pcb. */ - *error = ipsec_setspidx_inpcb(m, inp); - if (*error) - return (NULL); + INP_LOCK_ASSERT(inp); - pcbsp = inp->inp_sp; - IPSEC_ASSERT(pcbsp != NULL, ("null pcbsp")); - switch (dir) { - case IPSEC_DIR_INBOUND: - currsp = pcbsp->sp_in; - break; - case IPSEC_DIR_OUTBOUND: - currsp = pcbsp->sp_out; - break; + if (dir == IPSEC_DIR_OUTBOUND) { + /* Do we have configured PCB policy? */ + if (inp->inp_sp->flags & INP_OUTBOUND_POLICY) + return; + /* Another thread has already set cached policy */ + if (inp->inp_sp->sp_out != NULL) + return; + /* + * Do not cache OUTBOUND policy if PCB isn't connected, + * i.e. foreign address is INADDR_ANY/UNSPECIFIED. + */ +#ifdef INET + if ((inp->inp_vflag & INP_IPV4) != 0 && + inp->inp_faddr.s_addr == INADDR_ANY) + return; +#endif +#ifdef INET6 + if ((inp->inp_vflag & INP_IPV6) != 0 && + IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_faddr)) + return; +#endif + } else { + /* Do we have configured PCB policy? */ + if (inp->inp_sp->flags & INP_INBOUND_POLICY) + return; + /* Another thread has already set cached policy */ + if (inp->inp_sp->sp_in != NULL) + return; + /* + * Do not cache INBOUND policy for listen socket, + * that is bound to INADDR_ANY/UNSPECIFIED address. + */ +#ifdef INET + if ((inp->inp_vflag & INP_IPV4) != 0 && + inp->inp_faddr.s_addr == INADDR_ANY) + return; +#endif +#ifdef INET6 + if ((inp->inp_vflag & INP_IPV6) != 0 && + IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_faddr)) + return; +#endif } - IPSEC_ASSERT(currsp != NULL, ("null currsp")); - - if (pcbsp->priv) { /* When privilieged socket. */ - switch (currsp->policy) { - case IPSEC_POLICY_BYPASS: - case IPSEC_POLICY_IPSEC: - key_addref(currsp); - 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, "%s: Invalid policy for PCB %d\n", - __func__, 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, "%s: Illegal policy for " - "non-priviliged defined %d\n", - __func__, currsp->policy)); - *error = EINVAL; - return (NULL); - - case IPSEC_POLICY_ENTRUST: - sp = KEY_ALLOCSP_DEFAULT(); - break; - - case IPSEC_POLICY_IPSEC: - key_addref(currsp); - sp = currsp; - break; - - default: - ipseclog((LOG_ERR, "%s: Invalid policy for " - "PCB %d\n", __func__, currsp->policy)); - *error = EINVAL; - return (NULL); - } - } + downgrade = 0; + if (!INP_WLOCKED(inp)) { + if ((downgrade = INP_TRY_UPGRADE(inp)) == 0) + return; } - IPSEC_ASSERT(sp != NULL, - ("null SP (priv %u policy %u", pcbsp->priv, currsp->policy)); - KEYDEBUG(KEYDEBUG_IPSEC_STAMP, - printf("DP %s (priv %u policy %u) allocate SP:%p (refcnt %u)\n", - __func__, pcbsp->priv, currsp->policy, sp, sp->refcnt)); - 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 occured. - */ -struct secpolicy * -ipsec_getpolicybyaddr(struct mbuf *m, u_int dir, int flag, int *error) -{ - struct secpolicyindex spidx; - struct secpolicy *sp; - - IPSEC_ASSERT(m != NULL, ("null mbuf")); - IPSEC_ASSERT(error != NULL, ("null error")); - IPSEC_ASSERT(dir == IPSEC_DIR_INBOUND || dir == IPSEC_DIR_OUTBOUND, - ("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(("%s: setpidx failed, dir %u flag %u\n", - __func__, dir, flag)); - return (NULL); - } - spidx.dir = dir; - - sp = KEY_ALLOCSP(&spidx, dir); + if (dir == IPSEC_DIR_OUTBOUND) + inp->inp_sp->sp_out = sp; + else + inp->inp_sp->sp_in = sp; + /* + * SP is already referenced by the lookup code. + * We take extra reference here to avoid race in the + * ipsec_getpcbpolicy() function - SP will not be freed in the + * time between we take SP pointer from the cache and key_addref() + * call. + */ + key_addref(sp); + genid = key_getspgen(); + if (genid != inp->inp_sp->genid) { + ipsec_invalidate_cache(inp, dir); + inp->inp_sp->genid = genid; } - if (sp == NULL) /* No SP found, use system default. */ - sp = KEY_ALLOCSP_DEFAULT(); - IPSEC_ASSERT(sp != NULL, ("null SP")); - return (sp); + KEYDBG(IPSEC_STAMP, + printf("%s: PCB(%p): cached %s SP(%p)\n", + __func__, inp, dir == IPSEC_DIR_OUTBOUND ? "OUTBOUND": + "INBOUND", sp)); + if (downgrade != 0) + INP_DOWNGRADE(inp); } -struct secpolicy * -ipsec4_checkpolicy(struct mbuf *m, u_int dir, u_int flag, int *error, - struct inpcb *inp) +static struct secpolicy * +ipsec_checkpolicy(struct secpolicy *sp, struct inpcb *inp, int *error) { - struct secpolicy *sp; - *error = 0; - if (inp == NULL) - sp = ipsec_getpolicybyaddr(m, dir, flag, error); - else - sp = ipsec_getpolicybysock(m, dir, inp, error); - if (sp == NULL) { - IPSEC_ASSERT(*error != 0, ("getpolicy failed w/o error")); - IPSECSTAT_INC(ips_out_inval); - return (NULL); - } - IPSEC_ASSERT(*error == 0, ("sp w/ error set to %u", *error)); + /* Save found OUTBOUND policy into PCB SP cache. */ + if (inp != NULL && inp->inp_sp != NULL && inp->inp_sp->sp_out == NULL) + ipsec_cachepolicy(inp, sp, IPSEC_DIR_OUTBOUND); + switch (sp->policy) { - case IPSEC_POLICY_ENTRUST: default: printf("%s: invalid policy %u\n", __func__, sp->policy); /* FALLTHROUGH */ case IPSEC_POLICY_DISCARD: - IPSECSTAT_INC(ips_out_polvio); *error = -EINVAL; /* Packet is discarded by caller. */ - break; + /* FALLTHROUGH */ case IPSEC_POLICY_BYPASS: case IPSEC_POLICY_NONE: - KEY_FREESP(&sp); + key_freesp(&sp); sp = NULL; /* NB: force NULL result. */ break; case IPSEC_POLICY_IPSEC: - if (sp->req == NULL) /* Acquire a SA. */ - *error = key_spdacquire(sp); + /* XXXAE: handle LARVAL SP */ break; } - if (*error != 0) { - KEY_FREESP(&sp); - sp = NULL; - } + KEYDBG(IPSEC_DUMP, + printf("%s: get SP(%p), error %d\n", __func__, sp, *error)); return (sp); } -static int -ipsec_setspidx_inpcb(struct mbuf *m, struct inpcb *inp) +static struct secpolicy * +ipsec_getpcbpolicy(struct inpcb *inp, u_int dir) { - int error; + struct secpolicy *sp; + int flags, downgrade; + + if (inp == NULL || inp->inp_sp == NULL) + return (NULL); - IPSEC_ASSERT(inp != NULL, ("null inp")); - IPSEC_ASSERT(inp->inp_sp != NULL, ("null inp_sp")); - IPSEC_ASSERT(inp->inp_sp->sp_out != NULL && inp->inp_sp->sp_in != NULL, - ("null sp_in || sp_out")); + INP_LOCK_ASSERT(inp); - error = ipsec_setspidx(m, &inp->inp_sp->sp_in->spidx, 1); - if (error == 0) { - inp->inp_sp->sp_in->spidx.dir = IPSEC_DIR_INBOUND; - inp->inp_sp->sp_out->spidx = inp->inp_sp->sp_in->spidx; - inp->inp_sp->sp_out->spidx.dir = IPSEC_DIR_OUTBOUND; + flags = inp->inp_sp->flags; + if (dir == IPSEC_DIR_OUTBOUND) { + sp = inp->inp_sp->sp_out; + flags &= INP_OUTBOUND_POLICY; } else { - bzero(&inp->inp_sp->sp_in->spidx, - sizeof (inp->inp_sp->sp_in->spidx)); - bzero(&inp->inp_sp->sp_out->spidx, - sizeof (inp->inp_sp->sp_in->spidx)); + sp = inp->inp_sp->sp_in; + flags &= INP_INBOUND_POLICY; } - return (error); -} - -/* - * 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(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, ("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? + * Check flags. If we have PCB SP, just return it. + * Otherwise we need to check that cached SP entry isn't stale. */ - 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("%s: pkthdr len(%d) mismatch (%d), ignored.\n", - __func__, len, m->m_pkthdr.len)); - return (EINVAL); - } - - if (m->m_pkthdr.len < sizeof(struct ip)) { - KEYDEBUG(KEYDEBUG_IPSEC_DUMP, - printf("%s: pkthdr len(%d) too small (v4), ignored.\n", - __func__, 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("%s: pkthdr len(%d) too small (v6), " - "ignored\n", __func__, m->m_pkthdr.len)); - return (EINVAL); + if (flags == 0) { + if (sp == NULL) + return (NULL); + if (inp->inp_sp->genid != key_getspgen()) { + /* Invalidate the cache. */ + downgrade = 0; + if (!INP_WLOCKED(inp)) { + if ((downgrade = INP_TRY_UPGRADE(inp)) == 0) + return (NULL); + } + ipsec_invalidate_cache(inp, IPSEC_DIR_OUTBOUND); + ipsec_invalidate_cache(inp, IPSEC_DIR_INBOUND); + if (downgrade != 0) + INP_DOWNGRADE(inp); + return (NULL); } - 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("%s: " "unknown IP version %u, ignored.\n", - __func__, v)); - return (EINVAL); + KEYDBG(IPSEC_STAMP, + printf("%s: PCB(%p): cache hit SP(%p)\n", + __func__, inp, sp)); + /* Return referenced cached policy */ } + key_addref(sp); + return (sp); } +#ifdef INET static void -ipsec4_get_ulp(struct mbuf *m, struct secpolicyindex *spidx, int needport) +ipsec4_get_ulp(const struct mbuf *m, struct secpolicyindex *spidx, + int needport) { - u_int8_t nxt; + uint8_t nxt; int off; /* Sanity check. */ - IPSEC_ASSERT(m != NULL, ("null mbuf")); - IPSEC_ASSERT(m->m_pkthdr.len >= sizeof(struct ip),("packet too short")); + IPSEC_ASSERT(m->m_pkthdr.len >= sizeof(struct ip), + ("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)) + const struct ip *ip = mtod(m, const struct ip *); + if (ip->ip_off & htons(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)) + if (ih.ip_off & htons(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; } @@ -670,60 +551,134 @@ done: done_proto: spidx->src.sin.sin_port = IPSEC_PORT_ANY; spidx->dst.sin.sin_port = IPSEC_PORT_ANY; + KEYDBG(IPSEC_DUMP, + printf("%s: ", __func__); kdebug_secpolicyindex(spidx, NULL)); } -/* Assumes that m is sane. */ -static int -ipsec4_setspidx_ipaddr(struct mbuf *m, struct secpolicyindex *spidx) +static void +ipsec4_setspidx_ipaddr(const 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; + ipsec4_setsockaddrs(m, &spidx->src, &spidx->dst); + spidx->prefs = sizeof(struct in_addr) << 3; + spidx->prefd = sizeof(struct in_addr) << 3; +} - 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; +static struct secpolicy * +ipsec4_getpolicy(const struct mbuf *m, struct inpcb *inp, u_int dir) +{ + struct secpolicyindex spidx; + struct secpolicy *sp; + + sp = ipsec_getpcbpolicy(inp, dir); + if (sp == NULL && key_havesp(dir)) { + /* Make an index to look for a policy. */ + ipsec4_setspidx_ipaddr(m, &spidx); + /* Fill ports in spidx if we have inpcb. */ + ipsec4_get_ulp(m, &spidx, inp != NULL); + spidx.dir = dir; + sp = key_allocsp(&spidx, dir); } + if (sp == NULL) /* No SP found, use system default. */ + sp = key_allocsp_default(); + return (sp); +} - spidx->prefs = sizeof(struct in_addr) << 3; - spidx->prefd = sizeof(struct in_addr) << 3; +/* + * Check security policy for *OUTBOUND* IPv4 packet. + */ +struct secpolicy * +ipsec4_checkpolicy(const struct mbuf *m, struct inpcb *inp, int *error) +{ + struct secpolicy *sp; - return (0); + *error = 0; + sp = ipsec4_getpolicy(m, inp, IPSEC_DIR_OUTBOUND); + if (sp != NULL) + sp = ipsec_checkpolicy(sp, inp, error); + if (sp == NULL) { + switch (*error) { + case 0: /* No IPsec required: BYPASS or NONE */ + break; + case -EINVAL: + IPSECSTAT_INC(ips_out_polvio); + break; + default: + IPSECSTAT_INC(ips_out_inval); + } + } + KEYDBG(IPSEC_STAMP, + printf("%s: using SP(%p), error %d\n", __func__, sp, *error)); + if (sp != NULL) + KEYDBG(IPSEC_DATA, kdebug_secpolicy(sp)); + return (sp); } +/* + * Check IPv4 packet against *INBOUND* security policy. + * This function is called from tcp_input(), udp_input(), + * rip_input() and sctp_input(). + */ +int +ipsec4_in_reject(const struct mbuf *m, struct inpcb *inp) +{ + struct secpolicy *sp; + int result; + + sp = ipsec4_getpolicy(m, inp, IPSEC_DIR_INBOUND); + result = ipsec_in_reject(sp, inp, m); + key_freesp(&sp); + if (result != 0) + IPSECSTAT_INC(ips_in_polvio); + return (result); +} + +/* + * IPSEC_CAP() method implementation for IPv4. + */ +int +ipsec4_capability(struct mbuf *m, u_int cap) +{ + + switch (cap) { + case IPSEC_CAP_BYPASS_FILTER: + /* + * Bypass packet filtering for packets previously handled + * by IPsec. + */ + if (!V_ip4_filtertunnel && + m_tag_find(m, PACKET_TAG_IPSEC_IN_DONE, NULL) != NULL) + return (1); + return (0); + case IPSEC_CAP_OPERABLE: + /* Do we have active security policies? */ + if (key_havesp(IPSEC_DIR_INBOUND) != 0 || + key_havesp(IPSEC_DIR_OUTBOUND) != 0) + return (1); + return (0); + }; + return (EOPNOTSUPP); +} + +#endif /* INET */ + #ifdef INET6 static void -ipsec6_get_ulp(struct mbuf *m, struct secpolicyindex *spidx, int needport) +ipsec6_get_ulp(const struct mbuf *m, struct secpolicyindex *spidx, + int needport) { - int off, nxt; struct tcphdr th; struct udphdr uh; struct icmp6_hdr ih; + int off, nxt; - /* Sanity check. */ - if (m == NULL) - panic("%s: NULL pointer was passed.\n", __func__); - - KEYDEBUG(KEYDEBUG_IPSEC_DUMP, - printf("%s:\n", __func__); kdebug_mbuf(m)); + IPSEC_ASSERT(m->m_pkthdr.len >= sizeof(struct ip6_hdr), + ("packet too short")); /* 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; + spidx->src.sin6.sin6_port = IPSEC_PORT_ANY; + spidx->dst.sin6.sin6_port = IPSEC_PORT_ANY; nxt = -1; off = ip6_lasthdr(m, 0, IPPROTO_IPV6, &nxt); @@ -738,8 +693,8 @@ ipsec6_get_ulp(struct mbuf *m, struct secpolicyindex *spidx, int needport) 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; + spidx->src.sin6.sin6_port = th.th_sport; + spidx->dst.sin6.sin6_port = th.th_dport; break; case IPPROTO_UDP: spidx->ul_proto = nxt; @@ -748,355 +703,157 @@ ipsec6_get_ulp(struct mbuf *m, struct secpolicyindex *spidx, int needport) 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; + spidx->src.sin6.sin6_port = uh.uh_sport; + spidx->dst.sin6.sin6_port = uh.uh_dport; break; case IPPROTO_ICMPV6: spidx->ul_proto = nxt; if (off + sizeof(struct icmp6_hdr) > m->m_pkthdr.len) break; m_copydata(m, off, sizeof(ih), (caddr_t)&ih); - ((struct sockaddr_in6 *)&spidx->src)->sin6_port = - htons((uint16_t)ih.icmp6_type); - ((struct sockaddr_in6 *)&spidx->dst)->sin6_port = - htons((uint16_t)ih.icmp6_code); + spidx->src.sin6.sin6_port = htons((uint16_t)ih.icmp6_type); + spidx->dst.sin6.sin6_port = htons((uint16_t)ih.icmp6_code); break; default: /* XXX Intermediate headers??? */ spidx->ul_proto = nxt; break; } + KEYDBG(IPSEC_DUMP, + printf("%s: ", __func__); kdebug_secpolicyindex(spidx, NULL)); } -/* Assumes that m is sane. */ -static int -ipsec6_setspidx_ipaddr(struct mbuf *m, struct secpolicyindex *spidx) +static void +ipsec6_setspidx_ipaddr(const 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]); - } + ipsec6_setsockaddrs(m, &spidx->src, &spidx->dst); 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(struct inpcbpolicy *p) -{ - - free(p, M_IPSEC_INPCB); -} - -/* Initialize policy in PCB. */ -int -ipsec_init_policy(struct socket *so, struct inpcbpolicy **pcb_sp) +static struct secpolicy * +ipsec6_getpolicy(const struct mbuf *m, struct inpcb *inp, u_int dir) { - struct inpcbpolicy *new; - - /* Sanity check. */ - if (so == NULL || pcb_sp == NULL) - panic("%s: NULL pointer was passed.\n", __func__); - - new = (struct inpcbpolicy *) malloc(sizeof(struct inpcbpolicy), - M_IPSEC_INPCB, M_NOWAIT|M_ZERO); - if (new == NULL) { - ipseclog((LOG_DEBUG, "%s: No more memory.\n", __func__)); - return (ENOBUFS); - } - - new->priv = IPSEC_IS_PRIVILEGED_SO(so); - - 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; + struct secpolicyindex spidx; + struct secpolicy *sp; - if ((new->sp_out = KEY_NEWSP()) == NULL) { - KEY_FREESP(&new->sp_in); - ipsec_delpcbpolicy(new); - return (ENOBUFS); + sp = ipsec_getpcbpolicy(inp, dir); + if (sp == NULL && key_havesp(dir)) { + /* Make an index to look for a policy. */ + ipsec6_setspidx_ipaddr(m, &spidx); + /* Fill ports in spidx if we have inpcb. */ + ipsec6_get_ulp(m, &spidx, inp != NULL); + spidx.dir = dir; + sp = key_allocsp(&spidx, dir); } - new->sp_out->state = IPSEC_SPSTATE_ALIVE; - new->sp_out->policy = IPSEC_POLICY_ENTRUST; - - *pcb_sp = new; - - return (0); + if (sp == NULL) /* No SP found, use system default. */ + sp = key_allocsp_default(); + return (sp); } -/* Copy old IPsec policy into new. */ -int -ipsec_copy_policy(struct inpcbpolicy *old, struct inpcbpolicy *new) +/* + * Check security policy for *OUTBOUND* IPv6 packet. + */ +struct secpolicy * +ipsec6_checkpolicy(const struct mbuf *m, struct inpcb *inp, int *error) { 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); -} - -struct ipsecrequest * -ipsec_newisr(void) -{ - struct ipsecrequest *p; - - p = malloc(sizeof(struct ipsecrequest), M_IPSEC_SR, M_NOWAIT|M_ZERO); - if (p != NULL) - IPSECREQUEST_LOCK_INIT(p); - return (p); -} - -void -ipsec_delisr(struct ipsecrequest *p) -{ - - IPSECREQUEST_LOCK_DESTROY(p); - free(p, M_IPSEC_SR); -} - -/* Deep-copy a policy in PCB. */ -static struct secpolicy * -ipsec_deepcopy_policy(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 = ipsec_newisr(); - if (*q == NULL) - goto fail; - (*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)->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; - ipsec_delisr(p); - p = NULL; + *error = 0; + sp = ipsec6_getpolicy(m, inp, IPSEC_DIR_OUTBOUND); + if (sp != NULL) + sp = ipsec_checkpolicy(sp, inp, error); + if (sp == NULL) { + switch (*error) { + case 0: /* No IPsec required: BYPASS or NONE */ + break; + case -EINVAL: + IPSEC6STAT_INC(ips_out_polvio); + break; + default: + IPSEC6STAT_INC(ips_out_inval); + } } - return (NULL); + KEYDBG(IPSEC_STAMP, + printf("%s: using SP(%p), error %d\n", __func__, sp, *error)); + if (sp != NULL) + KEYDBG(IPSEC_DATA, kdebug_secpolicy(sp)); + return (sp); } -/* Set policy and IPsec request if present. */ -static int -ipsec_set_policy_internal(struct secpolicy **pcb_sp, int optname, - caddr_t request, size_t len, struct ucred *cred) +/* + * Check IPv6 packet against inbound security policy. + * This function is called from tcp6_input(), udp6_input(), + * rip6_input() and sctp_input(). + */ +int +ipsec6_in_reject(const struct mbuf *m, struct inpcb *inp) { - 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("%s: passed policy\n", __func__); - kdebug_sadb_x_policy((struct sadb_ext *)xpl)); - - /* Check policy type. */ - /* ipsec_set_policy_internal() 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 (cred != NULL && xpl->sadb_x_policy_type == IPSEC_POLICY_BYPASS) { - error = priv_check_cred(cred, PRIV_NETINET_IPSEC, 0); - if (error) - return (EACCES); - } - - /* Allocating 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("%s: new policy\n", __func__); - kdebug_secpolicy(newsp)); + struct secpolicy *sp; + int result; - return (0); + sp = ipsec6_getpolicy(m, inp, IPSEC_DIR_INBOUND); + result = ipsec_in_reject(sp, inp, m); + key_freesp(&sp); + if (result) + IPSEC6STAT_INC(ips_in_polvio); + return (result); } +/* + * IPSEC_CAP() method implementation for IPv6. + */ int -ipsec_set_policy(struct inpcb *inp, int optname, caddr_t request, - size_t len, struct ucred *cred) +ipsec6_capability(struct mbuf *m, u_int cap) { - 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; - - /* 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, "%s: invalid direction=%u\n", __func__, - xpl->sadb_x_policy_dir)); - return (EINVAL); - } - return (ipsec_set_policy_internal(pcb_sp, optname, request, len, cred)); + switch (cap) { + case IPSEC_CAP_BYPASS_FILTER: + /* + * Bypass packet filtering for packets previously handled + * by IPsec. + */ + if (!V_ip6_filtertunnel && + m_tag_find(m, PACKET_TAG_IPSEC_IN_DONE, NULL) != NULL) + return (1); + return (0); + case IPSEC_CAP_OPERABLE: + /* Do we have active security policies? */ + if (key_havesp(IPSEC_DIR_INBOUND) != 0 || + key_havesp(IPSEC_DIR_OUTBOUND) != 0) + return (1); + return (0); + }; + return (EOPNOTSUPP); } +#endif /* INET6 */ int -ipsec_get_policy(struct inpcb *inp, caddr_t request, size_t len, - struct mbuf **mp) +ipsec_run_hhooks(struct ipsec_ctx_data *ctx, int type) { - struct sadb_x_policy *xpl; - struct secpolicy *pcb_sp; + int idx; - /* Sanity check. */ - if (inp == NULL || request == NULL || mp == NULL) - return (EINVAL); - IPSEC_ASSERT(inp->inp_sp != NULL, ("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; + switch (ctx->af) { +#ifdef INET + case AF_INET: + idx = HHOOK_IPSEC_INET; break; - case IPSEC_DIR_OUTBOUND: - pcb_sp = inp->inp_sp->sp_out; +#endif +#ifdef INET6 + case AF_INET6: + idx = HHOOK_IPSEC_INET6; break; +#endif default: - ipseclog((LOG_ERR, "%s: invalid direction=%u\n", __func__, - xpl->sadb_x_policy_dir)); - return (EINVAL); - } - - /* Sanity check. Should be an IPSEC_ASSERT. */ - if (pcb_sp == NULL) - return (EINVAL); - - *mp = key_sp2msg(pcb_sp); - if (!*mp) { - ipseclog((LOG_DEBUG, "%s: No more memory.\n", __func__)); - return (ENOBUFS); + return (EPFNOSUPPORT); } - - (*mp)->m_type = MT_DATA; - KEYDEBUG(KEYDEBUG_IPSEC_DUMP, - printf("%s:\n", __func__); kdebug_mbuf(*mp)); - - return (0); -} - -/* Delete policy in PCB. */ -int -ipsec_delete_pcbpolicy(struct inpcb *inp) -{ - IPSEC_ASSERT(inp != NULL, ("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; - + if (type == HHOOK_TYPE_IPSEC_IN) + HHOOKS_RUN_IF(V_ipsec_hhh_in[idx], ctx, NULL); + else + HHOOKS_RUN_IF(V_ipsec_hhh_out[idx], ctx, NULL); + if (*ctx->mp == NULL) + return (EACCES); return (0); } @@ -1105,32 +862,36 @@ ipsec_delete_pcbpolicy(struct inpcb *inp) * Either IPSEC_LEVEL_USE or IPSEC_LEVEL_REQUIRE are always returned. */ u_int -ipsec_get_reqlevel(struct ipsecrequest *isr) +ipsec_get_reqlevel(struct secpolicy *sp, u_int idx) { - u_int level = 0; + struct ipsecrequest *isr; u_int esp_trans_deflev, esp_net_deflev; u_int ah_trans_deflev, ah_net_deflev; + u_int level = 0; - IPSEC_ASSERT(isr != NULL && isr->sp != NULL, ("null argument")); - IPSEC_ASSERT(isr->sp->spidx.src.sa.sa_family == isr->sp->spidx.dst.sa.sa_family, - ("af family mismatch, src %u, dst %u", - isr->sp->spidx.src.sa.sa_family, - isr->sp->spidx.dst.sa.sa_family)); - + IPSEC_ASSERT(idx < sp->tcount, ("Wrong IPsec request index %d", idx)); /* 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) \ - ? (V_ipsec_debug \ - ? log(LOG_INFO, "fixed system default level " #lev ":%d->%d\n",\ - (lev), IPSEC_LEVEL_REQUIRE) \ - : 0), \ - (lev) = IPSEC_LEVEL_REQUIRE, \ - (lev) \ - : (lev)) + (((lev) != IPSEC_LEVEL_USE && (lev) != IPSEC_LEVEL_REQUIRE && \ + (lev) != IPSEC_LEVEL_UNIQUE) \ + ? (V_ipsec_debug ? \ + log(LOG_INFO, "fixed system default level " #lev ":%d->%d\n",\ + (lev), IPSEC_LEVEL_REQUIRE) : 0), \ + (lev) = IPSEC_LEVEL_REQUIRE, (lev) : (lev)) + + /* + * IPsec VTI uses unique security policy with fake spidx filled + * with zeroes. Just return IPSEC_LEVEL_REQUIRE instead of doing + * full level lookup for such policies. + */ + if (sp->state == IPSEC_SPSTATE_IFNET) { + IPSEC_ASSERT(sp->req[idx]->level == IPSEC_LEVEL_UNIQUE, + ("Wrong IPsec request level %d", sp->req[idx]->level)); + return (IPSEC_LEVEL_REQUIRE); + } /* Set default level. */ - switch (((struct sockaddr *)&isr->sp->spidx.src)->sa_family) { + switch (sp->spidx.src.sa.sa_family) { #ifdef INET case AF_INET: esp_trans_deflev = IPSEC_CHECK_DEFAULT(V_ip4_esp_trans_deflev); @@ -1149,11 +910,12 @@ ipsec_get_reqlevel(struct ipsecrequest *isr) #endif /* INET6 */ default: panic("%s: unknown af %u", - __func__, isr->sp->spidx.src.sa.sa_family); + __func__, sp->spidx.src.sa.sa_family); } #undef IPSEC_CHECK_DEFAULT + isr = sp->req[idx]; /* Set level. */ switch (isr->level) { case IPSEC_LEVEL_DEFAULT: @@ -1198,6 +960,45 @@ ipsec_get_reqlevel(struct ipsecrequest *isr) return (level); } +static int +ipsec_check_history(const struct mbuf *m, struct secpolicy *sp, u_int idx) +{ + struct xform_history *xh; + struct m_tag *mtag; + + mtag = NULL; + while ((mtag = m_tag_find(__DECONST(struct mbuf *, m), + PACKET_TAG_IPSEC_IN_DONE, mtag)) != NULL) { + xh = (struct xform_history *)(mtag + 1); + KEYDBG(IPSEC_DATA, + char buf[IPSEC_ADDRSTRLEN]; + printf("%s: mode %s proto %u dst %s\n", __func__, + kdebug_secasindex_mode(xh->mode), xh->proto, + ipsec_address(&xh->dst, buf, sizeof(buf)))); + if (xh->proto != sp->req[idx]->saidx.proto) + continue; + /* If SA had IPSEC_MODE_ANY, consider this as match. */ + if (xh->mode != sp->req[idx]->saidx.mode && + xh->mode != IPSEC_MODE_ANY) + continue; + /* + * For transport mode IPsec request doesn't contain + * addresses. We need to use address from spidx. + */ + if (sp->req[idx]->saidx.mode == IPSEC_MODE_TRANSPORT) { + if (key_sockaddrcmp_withmask(&xh->dst.sa, + &sp->spidx.dst.sa, sp->spidx.prefd) != 0) + continue; + } else { + if (key_sockaddrcmp(&xh->dst.sa, + &sp->req[idx]->saidx.dst.sa, 0) != 0) + continue; + } + return (0); /* matched */ + } + return (1); +} + /* * Check security policy requirements against the actual * packet contents. Return one if the packet should be @@ -1208,14 +1009,17 @@ ipsec_get_reqlevel(struct ipsecrequest *isr) * 0: valid * 1: invalid */ -int -ipsec_in_reject(struct secpolicy *sp, struct mbuf *m) +static int +ipsec_in_reject(struct secpolicy *sp, struct inpcb *inp, const struct mbuf *m) { - struct ipsecrequest *isr; - int need_auth; + int i; + + KEYDBG(IPSEC_STAMP, + printf("%s: PCB(%p): using SP(%p)\n", __func__, inp, sp)); + KEYDBG(IPSEC_DATA, kdebug_secpolicy(sp)); - KEYDEBUG(KEYDEBUG_IPSEC_DATA, - printf("%s: using SP\n", __func__); kdebug_secpolicy(sp)); + if (inp != NULL && inp->inp_sp != NULL && inp->inp_sp->sp_in == NULL) + ipsec_cachepolicy(inp, sp, IPSEC_DIR_INBOUND); /* Check policy. */ switch (sp->policy) { @@ -1229,131 +1033,59 @@ ipsec_in_reject(struct secpolicy *sp, struct mbuf *m) IPSEC_ASSERT(sp->policy == IPSEC_POLICY_IPSEC, ("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) + /* + * ipsec[46]_common_input_cb after each transform adds + * PACKET_TAG_IPSEC_IN_DONE mbuf tag. It contains SPI, proto, mode + * and destination address from saidx. We can compare info from + * these tags with requirements in SP. + */ + for (i = 0; i < sp->tcount; i++) { + /* + * Do not check IPcomp, since IPcomp document + * says that we shouldn't compress small packets. + * IPComp policy should always be treated as being + * in "use" level. + */ + if (sp->req[i]->saidx.proto == IPPROTO_IPCOMP || + ipsec_get_reqlevel(sp, i) != IPSEC_LEVEL_REQUIRE) continue; - switch (isr->saidx.proto) { + if (V_check_policy_history != 0 && + ipsec_check_history(m, sp, i) != 0) + return (1); + else switch (sp->req[i]->saidx.proto) { case IPPROTO_ESP: if ((m->m_flags & M_DECRYPTED) == 0) { - KEYDEBUG(KEYDEBUG_IPSEC_DUMP, + KEYDBG(IPSEC_DUMP, printf("%s: ESP m_flags:%x\n", __func__, 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("%s: ESP/AH m_flags:%x\n", __func__, - m->m_flags)); - return (1); - } break; case IPPROTO_AH: - need_auth = 1; if ((m->m_flags & M_AUTHIPHDR) == 0) { - KEYDEBUG(KEYDEBUG_IPSEC_DUMP, + KEYDBG(IPSEC_DUMP, printf("%s: AH m_flags:%x\n", __func__, 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. */ } -static int -ipsec46_in_reject(struct mbuf *m, struct inpcb *inp) -{ - struct secpolicy *sp; - int error; - int result; - - IPSEC_ASSERT(m != NULL, ("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, inp, &error); - - if (sp != NULL) { - result = ipsec_in_reject(sp, m); - KEY_FREESP(&sp); - } else { - result = 0; /* XXX Should be panic? - * -> No, there may be error. */ - } - return (result); -} - -/* - * 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(struct mbuf *m, struct inpcb *inp) -{ - int result; - - result = ipsec46_in_reject(m, inp); - if (result) - IPSECSTAT_INC(ips_in_polvio); - - 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(struct mbuf *m, struct inpcb *inp) -{ - int result; - - result = ipsec46_in_reject(m, inp); - if (result) - IPSEC6STAT_INC(ips_in_polvio); - - return (result); -} -#endif - /* * Compute the byte size to be occupied by IPsec header. * In case it is tunnelled, it includes the size of outer IP header. - * NOTE: SP passed is freed in this function. */ static size_t ipsec_hdrsiz_internal(struct secpolicy *sp) { - struct ipsecrequest *isr; size_t size; + int i; - KEYDEBUG(KEYDEBUG_IPSEC_DATA, - printf("%s: using SP\n", __func__); kdebug_secpolicy(sp)); + KEYDBG(IPSEC_STAMP, printf("%s: using SP(%p)\n", __func__, sp)); + KEYDBG(IPSEC_DATA, kdebug_secpolicy(sp)); switch (sp->policy) { case IPSEC_POLICY_DISCARD: @@ -1365,80 +1097,69 @@ ipsec_hdrsiz_internal(struct secpolicy *sp) IPSEC_ASSERT(sp->policy == IPSEC_POLICY_IPSEC, ("invalid policy %u", sp->policy)); + /* + * XXX: for each transform we need to lookup suitable SA + * and use info from SA to calculate headers size. + * XXX: for NAT-T we need to cosider UDP header size. + */ size = 0; - for (isr = sp->req; isr != NULL; isr = isr->next) { - size_t clen = 0; - - switch (isr->saidx.proto) { + for (i = 0; i < sp->tcount; i++) { + switch (sp->req[i]->saidx.proto) { case IPPROTO_ESP: - clen = esp_hdrsiz(isr->sav); + size += esp_hdrsiz(NULL); break; case IPPROTO_AH: - clen = ah_hdrsiz(isr->sav); + size += ah_hdrsiz(NULL); break; case IPPROTO_IPCOMP: - clen = sizeof(struct ipcomp); + size += sizeof(struct ipcomp); break; } - if (isr->saidx.mode == IPSEC_MODE_TUNNEL) { - switch (isr->saidx.dst.sa.sa_family) { + if (sp->req[i]->saidx.mode == IPSEC_MODE_TUNNEL) { + switch (sp->req[i]->saidx.dst.sa.sa_family) { +#ifdef INET case AF_INET: - clen += sizeof(struct ip); + size += sizeof(struct ip); break; +#endif #ifdef INET6 case AF_INET6: - clen += sizeof(struct ip6_hdr); + size += sizeof(struct ip6_hdr); break; #endif default: ipseclog((LOG_ERR, "%s: unknown AF %d in " "IPsec tunnel SA\n", __func__, - ((struct sockaddr *)&isr->saidx.dst)->sa_family)); + sp->req[i]->saidx.dst.sa.sa_family)); break; } } - size += clen; } - return (size); } -/* - * This function is called from ipsec_hdrsiz_tcp(), ip_ipsec_mtu(), - * disabled ip6_ipsec_mtu() and ip6_forward(). +/* + * Compute ESP/AH header size for protocols with PCB, including + * outer IP header. Currently only tcp_output() uses it. */ size_t -ipsec_hdrsiz(struct mbuf *m, u_int dir, struct inpcb *inp) +ipsec_hdrsiz_inpcb(struct inpcb *inp) { + struct secpolicyindex spidx; struct secpolicy *sp; - int error; - size_t size; - - IPSEC_ASSERT(m != NULL, ("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, dir, IP_FORWARDING, &error); - else - sp = ipsec_getpolicybysock(m, dir, inp, &error); + size_t sz; - if (sp != NULL) { - size = ipsec_hdrsiz_internal(sp); - KEYDEBUG(KEYDEBUG_IPSEC_DATA, - printf("%s: size:%lu.\n", __func__, - (unsigned long)size)); - - KEY_FREESP(&sp); - } else { - size = 0; /* XXX Should be panic? - * -> No, we are called w/o knowing if - * IPsec processing is needed. */ + sp = ipsec_getpcbpolicy(inp, IPSEC_DIR_OUTBOUND); + if (sp == NULL && key_havesp(IPSEC_DIR_OUTBOUND)) { + ipsec_setspidx_inpcb(inp, &spidx, IPSEC_DIR_OUTBOUND); + sp = key_allocsp(&spidx, IPSEC_DIR_OUTBOUND); } - return (size); + if (sp == NULL) + sp = key_allocsp_default(); + sz = ipsec_hdrsiz_internal(sp); + key_freesp(&sp); + return (sz); } /* @@ -1449,27 +1170,31 @@ ipsec_hdrsiz(struct mbuf *m, u_int dir, struct inpcb *inp) * beforehand). * 0 (zero) is returned if packet disallowed, 1 if packet permitted. * - * Based on RFC 2401. + * Based on RFC 6479. Blocks are 32 bits unsigned integers */ + +#define IPSEC_BITMAP_INDEX_MASK(w) (w - 1) +#define IPSEC_REDUNDANT_BIT_SHIFTS 5 +#define IPSEC_REDUNDANT_BITS (1 << IPSEC_REDUNDANT_BIT_SHIFTS) +#define IPSEC_BITMAP_LOC_MASK (IPSEC_REDUNDANT_BITS - 1) + int -ipsec_chkreplay(u_int32_t seq, struct secasvar *sav) +ipsec_chkreplay(uint32_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. */ + uint32_t wsizeb; /* Constant: window size. */ + int index, bit_location; IPSEC_ASSERT(sav != NULL, ("Null SA")); IPSEC_ASSERT(sav->replay != NULL, ("Null replay state")); replay = sav->replay; + /* No need to check replay if disabled. */ if (replay->wsize == 0) - return (1); /* No need to check replay. */ + return (1); /* Constant. */ - frlast = replay->wsize - 1; wsizeb = replay->wsize << 3; /* Sequence number of 0 is invalid. */ @@ -1480,26 +1205,26 @@ ipsec_chkreplay(u_int32_t seq, struct secasvar *sav) if (replay->count == 0) return (1); - if (seq > replay->lastseq) { - /* Larger sequences are okay. */ + /* Larger sequences are okay. */ + if (seq > replay->lastseq) 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; + /* Over range to check, i.e. too old or wrapped. */ + if (replay->lastseq - seq >= wsizeb) + return (0); - /* This packet already seen? */ - if ((replay->bitmap)[fr] & (1 << (diff % 8))) - return (0); + /* The sequence is inside the sliding window + * now check the bit in the bitmap + * bit location only depends on the sequence number + */ + bit_location = seq & IPSEC_BITMAP_LOC_MASK; + index = (seq >> IPSEC_REDUNDANT_BIT_SHIFTS) + & IPSEC_BITMAP_INDEX_MASK(replay->bitmap_size); - /* Out of order but good. */ - return (1); - } + /* This packet already seen? */ + if ((replay->bitmap)[index] & (1 << bit_location)) + return (0); + return (1); } /* @@ -1508,13 +1233,12 @@ ipsec_chkreplay(u_int32_t seq, struct secasvar *sav) * 1: NG */ int -ipsec_updatereplay(u_int32_t seq, struct secasvar *sav) +ipsec_updatereplay(uint32_t seq, struct secasvar *sav) { + char buf[128]; struct secreplay *replay; - u_int32_t diff; - int fr; - u_int32_t wsizeb; /* Constant: bits of window size. */ - int frlast; /* Constant: last frame. */ + uint32_t wsizeb; /* Constant: window size. */ + int diff, index, bit_location; IPSEC_ASSERT(sav != NULL, ("Null SA")); IPSEC_ASSERT(sav->replay != NULL, ("Null replay state")); @@ -1525,58 +1249,46 @@ ipsec_updatereplay(u_int32_t seq, struct secasvar *sav) 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; + /* The packet is too old, no need to update */ + if (wsizeb + seq < replay->lastseq) goto ok; - } + /* Now update the bit */ + index = (seq >> IPSEC_REDUNDANT_BIT_SHIFTS); + + /* First check if the sequence number is in the range */ 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; + int id; + int index_cur = replay->lastseq >> IPSEC_REDUNDANT_BIT_SHIFTS; - /* Larger is good. */ - } else { - /* seq is equal or less than lastseq. */ - diff = replay->lastseq - seq; + diff = index - index_cur; + if (diff > replay->bitmap_size) { + /* something unusual in this case */ + diff = replay->bitmap_size; + } - /* Over range to check, i.e. too old or wrapped. */ - if (diff >= wsizeb) - return (1); + for (id = 0; id < diff; ++id) { + replay->bitmap[(id + index_cur + 1) + & IPSEC_BITMAP_INDEX_MASK(replay->bitmap_size)] = 0; + } - fr = frlast - diff / 8; + replay->lastseq = seq; + } - /* This packet already seen? */ - if ((replay->bitmap)[fr] & (1 << (diff % 8))) - return (1); + index &= IPSEC_BITMAP_INDEX_MASK(replay->bitmap_size); + bit_location = seq & IPSEC_BITMAP_LOC_MASK; - /* Mark as seen. */ - (replay->bitmap)[fr] |= (1 << (diff % 8)); + /* this packet has already been received */ + if (replay->bitmap[index] & (1 << bit_location)) + return (1); - /* Out of order but good. */ - } + replay->bitmap[index] |= (1 << bit_location); ok: if (replay->count == ~0) { @@ -1585,167 +1297,99 @@ ok: replay->overflow++; /* Don't increment, no more packets accepted. */ - if ((sav->flags & SADB_X_EXT_CYCSEQ) == 0) + if ((sav->flags & SADB_X_EXT_CYCSEQ) == 0) { + if (sav->sah->saidx.proto == IPPROTO_AH) + AHSTAT_INC(ahs_wrap); + else if (sav->sah->saidx.proto == IPPROTO_ESP) + ESPSTAT_INC(esps_wrap); return (1); + } ipseclog((LOG_WARNING, "%s: replay counter made %d cycle. %s\n", - __func__, replay->overflow, ipsec_logsastr(sav))); + __func__, replay->overflow, + ipsec_sa2str(sav, buf, sizeof(buf)))); } - - replay->count++; - return (0); } -/* - * Shift variable length buffer to left. - * IN: bitmap: pointer to the buffer - * nbit: the number of to shift. - * wsize: buffer size (bytes). - */ -static void -vshiftl(unsigned char *bitmap, int nbit, int 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; - } - } -} - -#ifdef INET -/* 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; - - /* XXX-BZ Returns static buffer. */ - i = (i + 1) % 4; - sprintf(buf[i], "%d.%d.%d.%d", ucp[0] & 0xff, ucp[1] & 0xff, - ucp[2] & 0xff, ucp[3] & 0xff); - return (buf[i]); -} -#endif - -/* Return a printable string for the address. */ -char * -ipsec_address(union sockaddr_union* sa) +int +ipsec_updateid(struct secasvar *sav, uint64_t *new, uint64_t *old) { -#ifdef INET6 - char ip6buf[INET6_ADDRSTRLEN]; -#endif + uint64_t tmp; - switch (sa->sa.sa_family) { -#ifdef INET - case AF_INET: - return (inet_ntoa4(sa->sin.sin_addr)); -#endif /* INET */ -#ifdef INET6 - case AF_INET6: - return (ip6_sprintf(ip6buf, &sa->sin6.sin6_addr)); -#endif /* INET6 */ - default: - return ("(unknown address family)"); + /* + * tdb_cryptoid is initialized by xform_init(). + * Then it can be changed only when some crypto error occurred or + * when SA is deleted. We stored used cryptoid in the xform_data + * structure. In case when crypto error occurred and crypto + * subsystem has reinited the session, it returns new cryptoid + * and EAGAIN error code. + * + * This function will be called when we got EAGAIN from crypto + * subsystem. + * *new is cryptoid that was returned by crypto subsystem in + * the crp_sid. + * *old is the original cryptoid that we stored in xform_data. + * + * For first failed request *old == sav->tdb_cryptoid, then + * we update sav->tdb_cryptoid and redo crypto_dispatch(). + * For next failed request *old != sav->tdb_cryptoid, then + * we store cryptoid from first request into the *new variable + * and crp_sid from this second session will be returned via + * *old pointer, so caller can release second session. + * + * XXXAE: check this more carefully. + */ + KEYDBG(IPSEC_STAMP, + printf("%s: SA(%p) moves cryptoid %jd -> %jd\n", + __func__, sav, (uintmax_t)(*old), (uintmax_t)(*new))); + KEYDBG(IPSEC_DATA, kdebug_secasv(sav)); + SECASVAR_LOCK(sav); + if (sav->tdb_cryptoid != *old) { + /* cryptoid was already updated */ + tmp = *new; + *new = sav->tdb_cryptoid; + *old = tmp; + SECASVAR_UNLOCK(sav); + return (1); } + sav->tdb_cryptoid = *new; + SECASVAR_UNLOCK(sav); + return (0); } -const char * -ipsec_logsastr(struct secasvar *sav) +int +ipsec_initialized(void) { - static char buf[256]; - char *p; - struct secasindex *saidx = &sav->sah->saidx; - - IPSEC_ASSERT(saidx->src.sa.sa_family == saidx->dst.sa.sa_family, - ("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(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"); + return (V_def_policy != NULL); } static void -ipsec_init(const void *unused __unused) +def_policy_init(const void *unused __unused) { - SECPOLICY_LOCK_INIT(&V_ip4_def_policy); - V_ip4_def_policy.refcnt = 1; /* NB: disallow free. */ + V_def_policy = key_newsp(); + if (V_def_policy != NULL) { + V_def_policy->policy = IPSEC_POLICY_NONE; + /* Force INPCB SP cache invalidation */ + key_bumpspgen(); + } else + printf("%s: failed to initialize default policy\n", __func__); } -VNET_SYSINIT(ipsec_init, SI_SUB_PROTO_DOMAININIT, SI_ORDER_ANY, ipsec_init, - NULL); - - -/* 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) +static void +def_policy_uninit(const void *unused __unused) { - xsp->xf_next = xforms; - xforms = xsp; + if (V_def_policy != NULL) { + key_freesp(&V_def_policy); + key_bumpspgen(); + } } -/* - * 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)); - return (EINVAL); -} +VNET_SYSINIT(def_policy_init, SI_SUB_PROTO_DOMAIN, SI_ORDER_FIRST, + def_policy_init, NULL); +VNET_SYSUNINIT(def_policy_uninit, SI_SUB_PROTO_DOMAIN, SI_ORDER_FIRST, + def_policy_uninit, NULL); diff --git a/freebsd/sys/netipsec/ipsec.h b/freebsd/sys/netipsec/ipsec.h index f3415872..7653e4d4 100644 --- a/freebsd/sys/netipsec/ipsec.h +++ b/freebsd/sys/netipsec/ipsec.h @@ -47,12 +47,11 @@ #ifdef _KERNEL -#define IPSEC_ASSERT(_c,_m) KASSERT(_c, _m) +#include <sys/_lock.h> +#include <sys/_mutex.h> +#include <sys/_rwlock.h> -#define IPSEC_IS_PRIVILEGED_SO(_so) \ - ((_so)->so_cred != NULL && \ - priv_check_cred((_so)->so_cred, PRIV_NETINET_IPSEC, 0) \ - == 0) +#define IPSEC_ASSERT(_c,_m) KASSERT(_c, _m) /* * Security Policy Index @@ -61,37 +60,41 @@ * specifies ICMPv6 type, and the port field in "dst" specifies ICMPv6 code. */ struct secpolicyindex { - u_int8_t dir; /* direction of packet flow, see below */ union sockaddr_union src; /* IP src address for SP */ union sockaddr_union dst; /* IP dst address for SP */ - u_int8_t prefs; /* prefix length in bits for src */ - u_int8_t prefd; /* prefix length in bits for dst */ - u_int16_t ul_proto; /* upper layer Protocol */ -#ifdef notyet - uid_t uids; - uid_t uidd; - gid_t gids; - gid_t gidd; -#endif + uint8_t ul_proto; /* upper layer Protocol */ + uint8_t dir; /* direction of packet flow */ + uint8_t prefs; /* prefix length in bits for src */ + uint8_t prefd; /* prefix length in bits for dst */ +}; + +/* Request for IPsec */ +struct ipsecrequest { + struct secasindex saidx;/* hint for search proper SA */ + /* if __ss_len == 0 then no address specified.*/ + u_int level; /* IPsec level defined below. */ }; /* Security Policy Data Base */ struct secpolicy { - LIST_ENTRY(secpolicy) chain; - struct mtx lock; + TAILQ_ENTRY(secpolicy) chain; + LIST_ENTRY(secpolicy) idhash; + LIST_ENTRY(secpolicy) drainq; - u_int refcnt; /* reference count */ struct secpolicyindex spidx; /* selector */ - u_int32_t id; /* It's unique number on the system. */ - u_int state; /* 0: dead, others: alive */ -#define IPSEC_SPSTATE_DEAD 0 -#define IPSEC_SPSTATE_ALIVE 1 - u_int16_t policy; /* policy_type per pfkeyv2.h */ - u_int16_t scangen; /* scan generation # */ - struct ipsecrequest *req; - /* pointer to the ipsec request tree, */ - /* if policy == IPSEC else this value == NULL.*/ - +#define IPSEC_MAXREQ 4 + struct ipsecrequest *req[IPSEC_MAXREQ]; + u_int tcount; /* IPsec transforms count */ + volatile u_int refcnt; /* reference count */ + u_int policy; /* policy_type per pfkeyv2.h */ + u_int state; +#define IPSEC_SPSTATE_DEAD 0 +#define IPSEC_SPSTATE_LARVAL 1 +#define IPSEC_SPSTATE_ALIVE 2 +#define IPSEC_SPSTATE_PCB 3 +#define IPSEC_SPSTATE_IFNET 4 + uint32_t priority; /* priority of this policy */ + uint32_t id; /* It's unique number on the system. */ /* * lifetime handler. * the policy can be used without limitiation if both lifetime and @@ -105,48 +108,25 @@ struct secpolicy { long validtime; /* duration this policy is valid without use */ }; -#define SECPOLICY_LOCK_INIT(_sp) \ - mtx_init(&(_sp)->lock, "ipsec policy", NULL, MTX_DEF) -#define SECPOLICY_LOCK(_sp) mtx_lock(&(_sp)->lock) -#define SECPOLICY_UNLOCK(_sp) mtx_unlock(&(_sp)->lock) -#define SECPOLICY_LOCK_DESTROY(_sp) mtx_destroy(&(_sp)->lock) -#define SECPOLICY_LOCK_ASSERT(_sp) mtx_assert(&(_sp)->lock, MA_OWNED) - -/* Request for IPsec */ -struct ipsecrequest { - struct ipsecrequest *next; - /* pointer to next structure */ - /* If NULL, it means the end of chain. */ - struct secasindex saidx;/* hint for search proper SA */ - /* if __ss_len == 0 then no address specified.*/ - u_int level; /* IPsec level defined below. */ - - struct secasvar *sav; /* place holder of SA for use */ - struct secpolicy *sp; /* back pointer to SP */ - struct rwlock lock; /* to interlock updates */ -}; - /* - * Need recursion for when crypto callbacks happen directly, - * as in the case of software crypto. Need to look at how - * hard it is to remove this... + * PCB security policies. + * Application can setup private security policies for socket. + * Such policies can have IPSEC, BYPASS and ENTRUST type. + * By default, policies are set to NULL. This means that they have ENTRUST type. + * When application sets BYPASS or IPSEC type policy, the flags field + * is also updated. When flags is not set, the system could store + * used security policy into the sp_in/sp_out pointer to speed up further + * lookups. */ -#define IPSECREQUEST_LOCK_INIT(_isr) \ - rw_init_flags(&(_isr)->lock, "ipsec request", RW_RECURSE) -#define IPSECREQUEST_LOCK(_isr) rw_rlock(&(_isr)->lock) -#define IPSECREQUEST_UNLOCK(_isr) rw_runlock(&(_isr)->lock) -#define IPSECREQUEST_WLOCK(_isr) rw_wlock(&(_isr)->lock) -#define IPSECREQUEST_WUNLOCK(_isr) rw_wunlock(&(_isr)->lock) -#define IPSECREQUEST_UPGRADE(_isr) rw_try_upgrade(&(_isr)->lock) -#define IPSECREQUEST_DOWNGRADE(_isr) rw_downgrade(&(_isr)->lock) -#define IPSECREQUEST_LOCK_DESTROY(_isr) rw_destroy(&(_isr)->lock) -#define IPSECREQUEST_LOCK_ASSERT(_isr) rw_assert(&(_isr)->lock, RA_LOCKED) - -/* security policy in PCB */ struct inpcbpolicy { - struct secpolicy *sp_in; - struct secpolicy *sp_out; - int priv; /* privileged socket ? */ + struct secpolicy *sp_in; + struct secpolicy *sp_out; + + uint32_t genid; + uint16_t flags; +#define INP_INBOUND_POLICY 0x0001 +#define INP_OUTBOUND_POLICY 0x0002 + uint16_t hdrsz; }; /* SP acquiring list table. */ @@ -161,6 +141,9 @@ struct secspacq { }; #endif /* _KERNEL */ +/* buffer size for formatted output of ipsec address */ +#define IPSEC_ADDRSTRLEN (INET6_ADDRSTRLEN + 11) + /* according to IANA assignment, port 0x0000 and proto 0xff are reserved. */ #define IPSEC_PORT_ANY 0 #define IPSEC_ULPROTO_ANY 255 @@ -196,6 +179,12 @@ struct secspacq { #define IPSEC_POLICY_ENTRUST 3 /* consulting SPD if present. */ #define IPSEC_POLICY_BYPASS 4 /* only for privileged socket. */ +/* Policy scope */ +#define IPSEC_POLICYSCOPE_ANY 0x00 /* unspecified */ +#define IPSEC_POLICYSCOPE_GLOBAL 0x01 /* global scope */ +#define IPSEC_POLICYSCOPE_IFNET 0x02 /* if_ipsec(4) scope */ +#define IPSEC_POLICYSCOPE_PCB 0x04 /* PCB scope */ + /* Security protocol level */ #define IPSEC_LEVEL_DEFAULT 0 /* reference to system default */ #define IPSEC_LEVEL_USE 1 /* use SA if present. */ @@ -217,62 +206,33 @@ struct secspacq { /* statistics for ipsec processing */ struct ipsecstat { - u_quad_t in_success; /* succeeded inbound process */ - u_quad_t in_polvio; - /* security policy violation for inbound process */ - u_quad_t in_nosa; /* inbound SA is unavailable */ - u_quad_t in_inval; /* inbound processing failed due to EINVAL */ - u_quad_t in_nomem; /* inbound processing failed due to ENOBUFS */ - u_quad_t in_badspi; /* failed getting a SPI */ - u_quad_t in_ahreplay; /* AH replay check failed */ - u_quad_t in_espreplay; /* ESP replay check failed */ - u_quad_t in_ahauthsucc; /* AH authentication success */ - u_quad_t in_ahauthfail; /* AH authentication failure */ - u_quad_t in_espauthsucc; /* ESP authentication success */ - u_quad_t in_espauthfail; /* ESP authentication failure */ - u_quad_t in_esphist[256]; - u_quad_t in_ahhist[256]; - u_quad_t in_comphist[256]; - u_quad_t out_success; /* succeeded outbound process */ - u_quad_t out_polvio; - /* security policy violation for outbound process */ - u_quad_t out_nosa; /* outbound SA is unavailable */ - u_quad_t out_inval; /* outbound process failed due to EINVAL */ - u_quad_t out_nomem; /* inbound processing failed due to ENOBUFS */ - u_quad_t out_noroute; /* there is no route */ - u_quad_t out_esphist[256]; - u_quad_t out_ahhist[256]; - u_quad_t out_comphist[256]; - - u_quad_t spdcachelookup; - u_quad_t spdcachemiss; - - u_int32_t ips_in_polvio; /* input: sec policy violation */ - u_int32_t ips_out_polvio; /* output: sec policy violation */ - u_int32_t ips_out_nosa; /* output: SA unavailable */ - u_int32_t ips_out_nomem; /* output: no memory available */ - u_int32_t ips_out_noroute; /* output: no route available */ - u_int32_t ips_out_inval; /* output: generic error */ - u_int32_t ips_out_bundlesa; /* output: bundled SA processed */ - u_int32_t ips_mbcoalesced; /* mbufs coalesced during clone */ - u_int32_t ips_clcoalesced; /* clusters coalesced during clone */ - u_int32_t ips_clcopied; /* clusters copied during clone */ - u_int32_t ips_mbinserted; /* mbufs inserted during makespace */ + uint64_t ips_in_polvio; /* input: sec policy violation */ + uint64_t ips_in_nomem; /* input: no memory available */ + uint64_t ips_in_inval; /* input: generic error */ + + uint64_t ips_out_polvio; /* output: sec policy violation */ + uint64_t ips_out_nosa; /* output: SA unavailable */ + uint64_t ips_out_nomem; /* output: no memory available */ + uint64_t ips_out_noroute; /* output: no route available */ + uint64_t ips_out_inval; /* output: generic error */ + uint64_t ips_out_bundlesa; /* output: bundled SA processed */ + + uint64_t ips_mbcoalesced; /* mbufs coalesced during clone */ + uint64_t ips_clcoalesced; /* clusters coalesced during clone */ + uint64_t ips_clcopied; /* clusters copied during clone */ + uint64_t ips_mbinserted; /* mbufs inserted during makespace */ /* * Temporary statistics for performance analysis. */ /* See where ESP/AH/IPCOMP header land in mbuf on input */ - u_int32_t ips_input_front; - u_int32_t ips_input_middle; - u_int32_t ips_input_end; + uint64_t ips_input_front; + uint64_t ips_input_middle; + uint64_t ips_input_end; }; /* * Definitions for IPsec & Key sysctl operations. */ -/* - * Names for IPsec & Key sysctl objects - */ #define IPSECCTL_STATS 1 /* stats */ #define IPSECCTL_DEF_POLICY 2 #define IPSECCTL_DEF_ESP_TRANSLEV 3 /* int; ESP transport mode */ @@ -288,53 +248,18 @@ struct ipsecstat { #define IPSECCTL_ECN 11 #define IPSECCTL_DEBUG 12 #define IPSECCTL_ESP_RANDPAD 13 -#define IPSECCTL_MAXID 14 - -#define IPSECCTL_NAMES { \ - { 0, 0 }, \ - { 0, 0 }, \ - { "def_policy", CTLTYPE_INT }, \ - { "esp_trans_deflev", CTLTYPE_INT }, \ - { "esp_net_deflev", CTLTYPE_INT }, \ - { "ah_trans_deflev", CTLTYPE_INT }, \ - { "ah_net_deflev", CTLTYPE_INT }, \ - { 0, 0 }, \ - { "ah_cleartos", CTLTYPE_INT }, \ - { "ah_offsetmask", CTLTYPE_INT }, \ - { "dfbit", CTLTYPE_INT }, \ - { "ecn", CTLTYPE_INT }, \ - { "debug", CTLTYPE_INT }, \ - { "esp_randpad", CTLTYPE_INT }, \ -} - -#define IPSEC6CTL_NAMES { \ - { 0, 0 }, \ - { 0, 0 }, \ - { "def_policy", CTLTYPE_INT }, \ - { "esp_trans_deflev", CTLTYPE_INT }, \ - { "esp_net_deflev", CTLTYPE_INT }, \ - { "ah_trans_deflev", CTLTYPE_INT }, \ - { "ah_net_deflev", CTLTYPE_INT }, \ - { 0, 0 }, \ - { 0, 0 }, \ - { 0, 0 }, \ - { 0, 0 }, \ - { "ecn", CTLTYPE_INT }, \ - { "debug", CTLTYPE_INT }, \ - { "esp_randpad", CTLTYPE_INT }, \ -} #ifdef _KERNEL -struct ipsec_output_state { - struct mbuf *m; - struct route *ro; - struct sockaddr *dst; -}; +#include <sys/counter.h> -struct ipsec_history { - int ih_proto; - u_int32_t ih_spi; -}; +struct ipsec_ctx_data; +#define IPSEC_INIT_CTX(_ctx, _mp, _sav, _af, _enc) do { \ + (_ctx)->mp = (_mp); \ + (_ctx)->sav = (_sav); \ + (_ctx)->af = (_af); \ + (_ctx)->enc = (_enc); \ +} while(0) +int ipsec_run_hhooks(struct ipsec_ctx_data *ctx, int direction); VNET_DECLARE(int, ipsec_debug); #define V_ipsec_debug VNET(ipsec_debug) @@ -347,8 +272,7 @@ VNET_DECLARE(int, ipsec_integrity); #define V_ipsec_integrity VNET(ipsec_integrity) #endif -VNET_DECLARE(struct ipsecstat, ipsec4stat); -VNET_DECLARE(struct secpolicy, ip4_def_policy); +VNET_PCPUSTAT_DECLARE(struct ipsecstat, ipsec4stat); VNET_DECLARE(int, ip4_esp_trans_deflev); VNET_DECLARE(int, ip4_esp_net_deflev); VNET_DECLARE(int, ip4_ah_trans_deflev); @@ -358,10 +282,10 @@ VNET_DECLARE(int, ip4_ipsec_dfbit); VNET_DECLARE(int, ip4_ipsec_ecn); VNET_DECLARE(int, ip4_esp_randpad); VNET_DECLARE(int, crypto_support); +VNET_DECLARE(int, natt_cksum_policy); -#define IPSECSTAT_INC(name) V_ipsec4stat.name += 1 -#define V_ipsec4stat VNET(ipsec4stat) -#define V_ip4_def_policy VNET(ip4_def_policy) +#define IPSECSTAT_INC(name) \ + VNET_PCPUSTAT_ADD(struct ipsecstat, ipsec4stat, name, 1) #define V_ip4_esp_trans_deflev VNET(ip4_esp_trans_deflev) #define V_ip4_esp_net_deflev VNET(ip4_esp_net_deflev) #define V_ip4_ah_trans_deflev VNET(ip4_ah_trans_deflev) @@ -371,64 +295,52 @@ VNET_DECLARE(int, crypto_support); #define V_ip4_ipsec_ecn VNET(ip4_ipsec_ecn) #define V_ip4_esp_randpad VNET(ip4_esp_randpad) #define V_crypto_support VNET(crypto_support) +#define V_natt_cksum_policy VNET(natt_cksum_policy) #define ipseclog(x) do { if (V_ipsec_debug) log x; } while (0) /* for openbsd compatibility */ #define DPRINTF(x) do { if (V_ipsec_debug) printf x; } while (0) -extern struct ipsecrequest *ipsec_newisr(void); -extern void ipsec_delisr(struct ipsecrequest *); - -struct tdb_ident; -extern struct secpolicy *ipsec_getpolicy __P((struct tdb_ident*, u_int)); struct inpcb; -extern struct secpolicy *ipsec4_checkpolicy __P((struct mbuf *, u_int, u_int, - int *, struct inpcb *)); -extern struct secpolicy * ipsec_getpolicybyaddr(struct mbuf *, u_int, - int, int *); +struct m_tag; +struct secasvar; +struct sockopt; +struct tcphdr; +union sockaddr_union; -struct inpcb; -extern int ipsec_init_policy __P((struct socket *so, struct inpcbpolicy **)); -extern int ipsec_copy_policy - __P((struct inpcbpolicy *, struct inpcbpolicy *)); -extern u_int ipsec_get_reqlevel __P((struct ipsecrequest *)); -extern int ipsec_in_reject __P((struct secpolicy *, struct mbuf *)); - -extern int ipsec_set_policy __P((struct inpcb *inp, int optname, - caddr_t request, size_t len, struct ucred *cred)); -extern int ipsec_get_policy __P((struct inpcb *inpcb, caddr_t request, - size_t len, struct mbuf **mp)); -extern int ipsec_delete_pcbpolicy __P((struct inpcb *)); -extern int ipsec4_in_reject __P((struct mbuf *, struct inpcb *)); - -struct secas; -struct tcpcb; -extern int ipsec_chkreplay __P((u_int32_t, struct secasvar *)); -extern int ipsec_updatereplay __P((u_int32_t, struct secasvar *)); - -extern size_t ipsec_hdrsiz __P((struct mbuf *, u_int, struct inpcb *)); -extern size_t ipsec_hdrsiz_tcp __P((struct tcpcb *)); +int ipsec_if_input(struct mbuf *, struct secasvar *, uint32_t); -union sockaddr_union; -extern char * ipsec_address(union sockaddr_union* sa); -extern const char *ipsec_logsastr __P((struct secasvar *)); +struct ipsecrequest *ipsec_newisr(void); +void ipsec_delisr(struct ipsecrequest *); +struct secpolicy *ipsec4_checkpolicy(const struct mbuf *, struct inpcb *, + int *); -extern void ipsec_dumpmbuf __P((struct mbuf *)); +u_int ipsec_get_reqlevel(struct secpolicy *, u_int); -struct m_tag; -extern void ah4_input(struct mbuf *m, int off); -extern void ah4_ctlinput(int cmd, struct sockaddr *sa, void *); -extern void esp4_input(struct mbuf *m, int off); -extern void esp4_ctlinput(int cmd, struct sockaddr *sa, void *); -extern void ipcomp4_input(struct mbuf *m, int off); -extern int ipsec4_common_input(struct mbuf *m, ...); -extern int ipsec4_common_input_cb(struct mbuf *m, struct secasvar *sav, - int skip, int protoff, struct m_tag *mt); -extern int ipsec4_process_packet __P((struct mbuf *, struct ipsecrequest *, - int, int)); -extern int ipsec_process_done __P((struct mbuf *, struct ipsecrequest *)); - -extern struct mbuf *ipsec_copypkt __P((struct mbuf *)); +void udp_ipsec_adjust_cksum(struct mbuf *, struct secasvar *, int, int); +int udp_ipsec_output(struct mbuf *, struct secasvar *); +int udp_ipsec_input(struct mbuf *, int, int); +int udp_ipsec_pcbctl(struct inpcb *, struct sockopt *); + +int ipsec_chkreplay(uint32_t, struct secasvar *); +int ipsec_updatereplay(uint32_t, struct secasvar *); +int ipsec_updateid(struct secasvar *, uint64_t *, uint64_t *); +int ipsec_initialized(void); + +void ipsec_setspidx_inpcb(struct inpcb *, struct secpolicyindex *, u_int); + +void ipsec4_setsockaddrs(const struct mbuf *, union sockaddr_union *, + union sockaddr_union *); +int ipsec4_in_reject(const struct mbuf *, struct inpcb *); +int ipsec4_input(struct mbuf *, int, int); +int ipsec4_forward(struct mbuf *); +int ipsec4_pcbctl(struct inpcb *, struct sockopt *); +int ipsec4_output(struct mbuf *, struct inpcb *); +int ipsec4_capability(struct mbuf *, u_int); +int ipsec4_common_input_cb(struct mbuf *, struct secasvar *, int, int); +int ipsec4_process_packet(struct mbuf *, struct secpolicy *, struct inpcb *); +int ipsec_process_done(struct mbuf *, struct secpolicy *, struct secasvar *, + u_int); extern void m_checkalignment(const char* where, struct mbuf *m0, int off, int len); @@ -436,22 +348,13 @@ extern struct mbuf *m_makespace(struct mbuf *m0, int skip, int hlen, int *off); extern caddr_t m_pad(struct mbuf *m, int n); extern int m_striphdr(struct mbuf *m, int skip, int hlen); -#ifdef DEV_ENC -#define ENC_BEFORE 0x0001 -#define ENC_AFTER 0x0002 -#define ENC_IN 0x0100 -#define ENC_OUT 0x0200 -extern int ipsec_filter(struct mbuf **, int, int); -extern void ipsec_bpf(struct mbuf *, struct secasvar *, int, int); -#endif #endif /* _KERNEL */ #ifndef _KERNEL -extern caddr_t ipsec_set_policy __P((char *, int)); -extern int ipsec_get_policylen __P((caddr_t)); -extern char *ipsec_dump_policy __P((caddr_t, char *)); - -extern const char *ipsec_strerror __P((void)); +extern caddr_t ipsec_set_policy(char *, int); +extern int ipsec_get_policylen(caddr_t); +extern char *ipsec_dump_policy(caddr_t, char *); +extern const char *ipsec_strerror(void); #endif /* ! KERNEL */ diff --git a/freebsd/sys/netipsec/ipsec6.h b/freebsd/sys/netipsec/ipsec6.h index 21ec6b36..a5fae4d1 100644 --- a/freebsd/sys/netipsec/ipsec6.h +++ b/freebsd/sys/netipsec/ipsec6.h @@ -41,15 +41,17 @@ #include <netipsec/keydb.h> #ifdef _KERNEL -VNET_DECLARE(struct ipsecstat, ipsec6stat); +#include <sys/counter.h> + +VNET_PCPUSTAT_DECLARE(struct ipsecstat, ipsec6stat); VNET_DECLARE(int, ip6_esp_trans_deflev); VNET_DECLARE(int, ip6_esp_net_deflev); VNET_DECLARE(int, ip6_ah_trans_deflev); VNET_DECLARE(int, ip6_ah_net_deflev); VNET_DECLARE(int, ip6_ipsec_ecn); -#define IPSEC6STAT_INC(name) V_ipsec6stat.name += 1 -#define V_ipsec6stat VNET(ipsec6stat) +#define IPSEC6STAT_INC(name) \ + VNET_PCPUSTAT_ADD(struct ipsecstat, ipsec6stat, name, 1) #define V_ip6_esp_trans_deflev VNET(ip6_esp_trans_deflev) #define V_ip6_esp_net_deflev VNET(ip6_esp_net_deflev) #define V_ip6_ah_trans_deflev VNET(ip6_ah_trans_deflev) @@ -57,23 +59,22 @@ VNET_DECLARE(int, ip6_ipsec_ecn); #define V_ip6_ipsec_ecn VNET(ip6_ipsec_ecn) struct inpcb; +struct secpolicy *ipsec6_checkpolicy(const struct mbuf *, + struct inpcb *, int *); -extern int ipsec6_in_reject __P((struct mbuf *, struct inpcb *)); - -struct ip6_hdr; -extern const char *ipsec6_logpacketstr __P((struct ip6_hdr *, u_int32_t)); - -struct m_tag; -extern int ipsec6_common_input(struct mbuf **mp, int *offp, int proto); -extern int ipsec6_common_input_cb(struct mbuf *m, struct secasvar *sav, - int skip, int protoff, struct m_tag *mt); -extern void esp6_ctlinput(int, struct sockaddr *, void *); +void ipsec6_setsockaddrs(const struct mbuf *, union sockaddr_union *, + union sockaddr_union *); +int ipsec6_input(struct mbuf *, int, int); +int ipsec6_in_reject(const struct mbuf *, struct inpcb *); +int ipsec6_forward(struct mbuf *); +int ipsec6_pcbctl(struct inpcb *, struct sockopt *); +int ipsec6_output(struct mbuf *, struct inpcb *); +int ipsec6_capability(struct mbuf *, u_int); +int ipsec6_common_input_cb(struct mbuf *, struct secasvar *, int, int); +int ipsec6_process_packet(struct mbuf *, struct secpolicy *, struct inpcb *); -struct ipsec_output_state; -extern int ipsec6_output_trans __P((struct ipsec_output_state *, u_char *, - struct mbuf *, struct secpolicy *, int, int *)); -extern int ipsec6_output_tunnel __P((struct ipsec_output_state *, - struct secpolicy *, int)); +int ip6_ipsec_filtertunnel(struct mbuf *); +int ip6_ipsec_pcbctl(struct inpcb *, struct sockopt *); #endif /*_KERNEL*/ #endif /*_NETIPSEC_IPSEC6_H_*/ diff --git a/freebsd/sys/netipsec/ipsec_input.c b/freebsd/sys/netipsec/ipsec_input.c index d910de71..50e7d646 100644 --- a/freebsd/sys/netipsec/ipsec_input.c +++ b/freebsd/sys/netipsec/ipsec_input.c @@ -1,6 +1,5 @@ #include <machine/rtems-bsd-kernel-space.h> -/* $FreeBSD$ */ /* $OpenBSD: ipsec_input.c,v 1.63 2003/02/20 18:35:43 deraadt Exp $ */ /*- * The authors of this code are John Ioannidis (ji@tla.org), @@ -21,6 +20,7 @@ * Copyright (C) 1995, 1996, 1997, 1998, 1999 by John Ioannidis, * Angelos D. Keromytis and Niels Provos. * Copyright (c) 2001, Angelos D. Keromytis. + * Copyright (c) 2016 Andrey V. Elsukov <ae@FreeBSD.org> * * Permission to use, copy, and modify this software with or without fee * is hereby granted, provided that this entire notice is included in @@ -42,10 +42,12 @@ * IPsec input processing. */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + #include <rtems/bsd/local/opt_inet.h> #include <rtems/bsd/local/opt_inet6.h> #include <rtems/bsd/local/opt_ipsec.h> -#include <rtems/bsd/local/opt_enc.h> #include <rtems/bsd/sys/param.h> #include <sys/systm.h> @@ -55,11 +57,12 @@ #include <sys/protosw.h> #include <sys/socket.h> #include <rtems/bsd/sys/errno.h> +#include <sys/hhook.h> #include <sys/syslog.h> #include <net/if.h> -#include <net/pfil.h> -#include <net/route.h> +#include <net/if_var.h> +#include <net/if_enc.h> #include <net/netisr.h> #include <net/vnet.h> @@ -89,6 +92,7 @@ #include <netipsec/key.h> #include <netipsec/keydb.h> +#include <netipsec/key_debug.h> #include <netipsec/xform.h> #include <netinet6/ip6protosw.h> @@ -96,10 +100,6 @@ #include <machine/in_cksum.h> #include <machine/stdarg.h> -#ifdef DEV_ENC -#include <net/if_enc.h> -#endif - #define IPSEC_ISTAT(proto, name) do { \ if ((proto) == IPPROTO_ESP) \ @@ -110,10 +110,6 @@ IPCOMPSTAT_INC(ipcomps_##name); \ } while (0) -#ifdef INET -static void ipsec4_common_ctlinput(int, struct sockaddr *, void *, int); -#endif - /* * ipsec_common_input gets called when an IPsec-protected packet * is received by IPv4 or IPv6. Its job is to find the right SA @@ -123,15 +119,11 @@ static void ipsec4_common_ctlinput(int, struct sockaddr *, void *, int); static int ipsec_common_input(struct mbuf *m, int skip, int protoff, int af, int sproto) { + char buf[IPSEC_ADDRSTRLEN]; union sockaddr_union dst_address; struct secasvar *sav; - u_int32_t spi; + uint32_t spi; int error; -#ifdef INET -#ifdef IPSEC_NAT_T - struct m_tag *tag; -#endif -#endif IPSEC_ISTAT(sproto, input); @@ -183,12 +175,6 @@ ipsec_common_input(struct mbuf *m, int skip, int protoff, int af, int sproto) m_copydata(m, offsetof(struct ip, ip_dst), sizeof(struct in_addr), (caddr_t) &dst_address.sin.sin_addr); -#ifdef IPSEC_NAT_T - /* Find the source port for NAT-T; see udp*_espdecap. */ - tag = m_tag_find(m, PACKET_TAG_IPSEC_NAT_T_PORTS, NULL); - if (tag != NULL) - dst_address.sin.sin_port = ((u_int16_t *)(tag + 1))[1]; -#endif /* IPSEC_NAT_T */ break; #endif /* INET */ #ifdef INET6 @@ -197,6 +183,13 @@ ipsec_common_input(struct mbuf *m, int skip, int protoff, int af, int sproto) m_copydata(m, offsetof(struct ip6_hdr, ip6_dst), sizeof(struct in6_addr), (caddr_t) &dst_address.sin6.sin6_addr); + /* We keep addresses in SADB without embedded scope id */ + if (IN6_IS_SCOPE_LINKLOCAL(&dst_address.sin6.sin6_addr)) { + /* XXX: sa6_recoverscope() */ + dst_address.sin6.sin6_scope_id = + ntohs(dst_address.sin6.sin6_addr.s6_addr16[1]); + dst_address.sin6.sin6_addr.s6_addr16[1] = 0; + } break; #endif /* INET6 */ default: @@ -207,11 +200,11 @@ ipsec_common_input(struct mbuf *m, int skip, int protoff, int af, int sproto) } /* NB: only pass dst since key_allocsa follows RFC2401 */ - sav = KEY_ALLOCSA(&dst_address, sproto, spi); + sav = key_allocsa(&dst_address, sproto, spi); if (sav == NULL) { DPRINTF(("%s: no key association found for SA %s/%08lx/%u\n", - __func__, ipsec_address(&dst_address), - (u_long) ntohl(spi), sproto)); + __func__, ipsec_address(&dst_address, buf, sizeof(buf)), + (u_long) ntohl(spi), sproto)); IPSEC_ISTAT(sproto, notdb); m_freem(m); return ENOENT; @@ -219,10 +212,10 @@ ipsec_common_input(struct mbuf *m, int skip, int protoff, int af, int sproto) if (sav->tdb_xform == NULL) { DPRINTF(("%s: attempted to use uninitialized SA %s/%08lx/%u\n", - __func__, ipsec_address(&dst_address), - (u_long) ntohl(spi), sproto)); + __func__, ipsec_address(&dst_address, buf, sizeof(buf)), + (u_long) ntohl(spi), sproto)); IPSEC_ISTAT(sproto, noxform); - KEY_FREESAV(&sav); + key_freesav(&sav); m_freem(m); return ENXIO; } @@ -232,59 +225,50 @@ ipsec_common_input(struct mbuf *m, int skip, int protoff, int af, int sproto) * everything else. */ error = (*sav->tdb_xform->xf_input)(m, sav, skip, protoff); - KEY_FREESAV(&sav); - return error; + if (error != 0) + key_freesav(&sav); + return (error); } #ifdef INET +extern struct protosw inetsw[]; + /* - * Common input handler for IPv4 AH, ESP, and IPCOMP. + * IPSEC_INPUT() method implementation for IPv4. + * 0 - Permitted by inbound security policy for further processing. + * EACCES - Forbidden by inbound security policy. + * EINPROGRESS - consumed by IPsec. */ int -ipsec4_common_input(struct mbuf *m, ...) -{ - va_list ap; - int off, nxt; - - va_start(ap, m); - off = va_arg(ap, int); - nxt = va_arg(ap, int); - va_end(ap); - - return ipsec_common_input(m, off, offsetof(struct ip, ip_p), - AF_INET, nxt); -} - -void -ah4_input(struct mbuf *m, int off) -{ - ipsec4_common_input(m, off, IPPROTO_AH); -} -void -ah4_ctlinput(int cmd, struct sockaddr *sa, void *v) -{ - if (sa->sa_family == AF_INET && - sa->sa_len == sizeof(struct sockaddr_in)) - ipsec4_common_ctlinput(cmd, sa, v, IPPROTO_AH); -} - -void -esp4_input(struct mbuf *m, int off) -{ - ipsec4_common_input(m, off, IPPROTO_ESP); -} -void -esp4_ctlinput(int cmd, struct sockaddr *sa, void *v) +ipsec4_input(struct mbuf *m, int offset, int proto) { - if (sa->sa_family == AF_INET && - sa->sa_len == sizeof(struct sockaddr_in)) - ipsec4_common_ctlinput(cmd, sa, v, IPPROTO_ESP); -} -void -ipcomp4_input(struct mbuf *m, int off) -{ - ipsec4_common_input(m, off, IPPROTO_IPCOMP); + switch (proto) { + case IPPROTO_AH: + case IPPROTO_ESP: + case IPPROTO_IPCOMP: + /* Do inbound IPsec processing for AH/ESP/IPCOMP */ + ipsec_common_input(m, offset, + offsetof(struct ip, ip_p), AF_INET, proto); + return (EINPROGRESS); /* mbuf consumed by IPsec */ + default: + /* + * Protocols with further headers get their IPsec treatment + * within the protocol specific processing. + */ + if ((inetsw[ip_protox[proto]].pr_flags & PR_LASTHDR) == 0) + return (0); + /* FALLTHROUGH */ + }; + /* + * Enforce IPsec policy checking if we are seeing last header. + */ + if (ipsec4_in_reject(m, NULL) != 0) { + /* Forbidden by inbound security policy */ + m_freem(m); + return (EACCES); + } + return (0); } /* @@ -294,22 +278,17 @@ ipcomp4_input(struct mbuf *m, int off) * the processed packet. */ int -ipsec4_common_input_cb(struct mbuf *m, struct secasvar *sav, - int skip, int protoff, struct m_tag *mt) +ipsec4_common_input_cb(struct mbuf *m, struct secasvar *sav, int skip, + int protoff) { - int prot, af, sproto; - struct ip *ip; - struct m_tag *mtag; - struct tdb_ident *tdbi; + char buf[IPSEC_ADDRSTRLEN]; + struct ipsec_ctx_data ctx; + struct xform_history *xh; struct secasindex *saidx; - int error; -#ifdef INET6 -#ifdef notyet - char ip6buf[INET6_ADDRSTRLEN]; -#endif -#endif + struct m_tag *mtag; + struct ip *ip; + int error, prot, af, sproto, isr_prot; - IPSEC_ASSERT(m != NULL, ("null mbuf")); IPSEC_ASSERT(sav != NULL, ("null SA")); IPSEC_ASSERT(sav->sah != NULL, ("null SAH")); saidx = &sav->sah->saidx; @@ -320,20 +299,14 @@ ipsec4_common_input_cb(struct mbuf *m, struct secasvar *sav, sproto == IPPROTO_IPCOMP, ("unexpected security protocol %u", sproto)); - /* Sanity check */ - if (m == NULL) { - DPRINTF(("%s: null mbuf", __func__)); - IPSEC_ISTAT(sproto, badkcr); - KEY_FREESAV(&sav); - return EINVAL; - } - if (skip != 0) { - /* Fix IPv4 header */ + /* + * Fix IPv4 header + */ if (m->m_len < skip && (m = m_pullup(m, skip)) == NULL) { DPRINTF(("%s: processing failed for SA %s/%08lx\n", - __func__, ipsec_address(&sav->sah->saidx.dst), - (u_long) ntohl(sav->spi))); + __func__, ipsec_address(&sav->sah->saidx.dst, + buf, sizeof(buf)), (u_long) ntohl(sav->spi))); IPSEC_ISTAT(sproto, hdrops); error = ENOBUFS; goto bad; @@ -341,106 +314,67 @@ ipsec4_common_input_cb(struct mbuf *m, struct secasvar *sav, ip = mtod(m, struct ip *); ip->ip_len = htons(m->m_pkthdr.len); - ip->ip_off = htons(ip->ip_off); ip->ip_sum = 0; ip->ip_sum = in_cksum(m, ip->ip_hl << 2); } else { ip = mtod(m, struct ip *); } prot = ip->ip_p; + /* + * Check that we have NAT-T enabled and apply transport mode + * decapsulation NAT procedure (RFC3948). + * Do this before invoking into the PFIL. + */ + if (sav->natt != NULL && + (prot == IPPROTO_UDP || prot == IPPROTO_TCP)) + udp_ipsec_adjust_cksum(m, sav, prot, skip); -#ifdef notyet - /* IP-in-IP encapsulation */ - if (prot == IPPROTO_IPIP) { - struct ip ipn; + IPSEC_INIT_CTX(&ctx, &m, sav, AF_INET, IPSEC_ENC_BEFORE); + if ((error = ipsec_run_hhooks(&ctx, HHOOK_TYPE_IPSEC_IN)) != 0) + goto bad; + ip = mtod(m, struct ip *); /* update pointer */ + /* IP-in-IP encapsulation */ + if (prot == IPPROTO_IPIP && + saidx->mode != IPSEC_MODE_TRANSPORT) { if (m->m_pkthdr.len - skip < sizeof(struct ip)) { IPSEC_ISTAT(sproto, hdrops); error = EINVAL; goto bad; } - /* ipn will now contain the inner IPv4 header */ - m_copydata(m, ip->ip_hl << 2, sizeof(struct ip), - (caddr_t) &ipn); - - /* XXX PROXY address isn't recorded in SAH */ - /* - * Check that the inner source address is the same as - * the proxy address, if available. - */ - if ((saidx->proxy.sa.sa_family == AF_INET && - saidx->proxy.sin.sin_addr.s_addr != - INADDR_ANY && - ipn.ip_src.s_addr != - saidx->proxy.sin.sin_addr.s_addr) || - (saidx->proxy.sa.sa_family != AF_INET && - saidx->proxy.sa.sa_family != 0)) { - - DPRINTF(("%s: inner source address %s doesn't " - "correspond to expected proxy source %s, " - "SA %s/%08lx\n", __func__, - inet_ntoa4(ipn.ip_src), - ipsp_address(saidx->proxy), - ipsp_address(saidx->dst), - (u_long) ntohl(sav->spi))); - - IPSEC_ISTAT(sproto, pdrops); - error = EACCES; - goto bad; - } + /* enc0: strip outer IPv4 header */ + m_striphdr(m, 0, ip->ip_hl << 2); } #ifdef INET6 /* IPv6-in-IP encapsulation. */ - if (prot == IPPROTO_IPV6) { - struct ip6_hdr ip6n; - + else if (prot == IPPROTO_IPV6 && + saidx->mode != IPSEC_MODE_TRANSPORT) { if (m->m_pkthdr.len - skip < sizeof(struct ip6_hdr)) { IPSEC_ISTAT(sproto, hdrops); error = EINVAL; goto bad; } - /* ip6n will now contain the inner IPv6 header. */ - m_copydata(m, ip->ip_hl << 2, sizeof(struct ip6_hdr), - (caddr_t) &ip6n); - + /* enc0: strip IPv4 header, keep IPv6 header only */ + m_striphdr(m, 0, ip->ip_hl << 2); + } +#endif /* INET6 */ + else if (prot != IPPROTO_IPV6 && saidx->mode == IPSEC_MODE_ANY) { /* - * Check that the inner source address is the same as - * the proxy address, if available. + * When mode is wildcard, inner protocol is IPv6 and + * we have no INET6 support - drop this packet a bit later. + * In other cases we assume transport mode. Set prot to + * correctly choose netisr. */ - if ((saidx->proxy.sa.sa_family == AF_INET6 && - !IN6_IS_ADDR_UNSPECIFIED(&saidx->proxy.sin6.sin6_addr) && - !IN6_ARE_ADDR_EQUAL(&ip6n.ip6_src, - &saidx->proxy.sin6.sin6_addr)) || - (saidx->proxy.sa.sa_family != AF_INET6 && - saidx->proxy.sa.sa_family != 0)) { - - DPRINTF(("%s: inner source address %s doesn't " - "correspond to expected proxy source %s, " - "SA %s/%08lx\n", __func__, - ip6_sprintf(ip6buf, &ip6n.ip6_src), - ipsec_address(&saidx->proxy), - ipsec_address(&saidx->dst), - (u_long) ntohl(sav->spi))); - - IPSEC_ISTAT(sproto, pdrops); - error = EACCES; - goto bad; - } + prot = IPPROTO_IPIP; } -#endif /* INET6 */ -#endif /*XXX*/ /* * Record what we've done to the packet (under what SA it was - * processed). If we've been passed an mtag, it means the packet - * was already processed by an ethernet/crypto combo card and - * thus has a tag attached with all the right information, but - * with a PACKET_TAG_IPSEC_IN_CRYPTO_DONE as opposed to - * PACKET_TAG_IPSEC_IN_DONE type; in that case, just change the type. + * processed). */ - if (mt == NULL && sproto != IPPROTO_IPCOMP) { + if (sproto != IPPROTO_IPCOMP) { mtag = m_tag_get(PACKET_TAG_IPSEC_IN_DONE, - sizeof(struct tdb_ident), M_NOWAIT); + sizeof(struct xform_history), M_NOWAIT); if (mtag == NULL) { DPRINTF(("%s: failed to get tag\n", __func__)); IPSEC_ISTAT(sproto, hdrops); @@ -448,104 +382,106 @@ ipsec4_common_input_cb(struct mbuf *m, struct secasvar *sav, goto bad; } - tdbi = (struct tdb_ident *)(mtag + 1); - bcopy(&saidx->dst, &tdbi->dst, saidx->dst.sa.sa_len); - tdbi->proto = sproto; - tdbi->spi = sav->spi; - /* Cache those two for enc(4) in xform_ipip. */ - tdbi->alg_auth = sav->alg_auth; - tdbi->alg_enc = sav->alg_enc; - + xh = (struct xform_history *)(mtag + 1); + bcopy(&saidx->dst, &xh->dst, saidx->dst.sa.sa_len); + xh->spi = sav->spi; + xh->proto = sproto; + xh->mode = saidx->mode; m_tag_prepend(m, mtag); - } else if (mt != NULL) { - mt->m_tag_id = PACKET_TAG_IPSEC_IN_DONE; - /* XXX do we need to mark m_flags??? */ } key_sa_recordxfer(sav, m); /* record data transfer */ - m_addr_changed(m); - -#ifdef DEV_ENC - encif->if_ipackets++; - encif->if_ibytes += m->m_pkthdr.len; - /* - * Pass the mbuf to enc0 for bpf and pfil. We will filter the IPIP - * packet later after it has been decapsulated. + * In transport mode requeue decrypted mbuf back to IPv4 protocol + * handler. This is necessary to correctly expose rcvif. */ - ipsec_bpf(m, sav, AF_INET, ENC_IN|ENC_BEFORE); - - if (prot != IPPROTO_IPIP) - if ((error = ipsec_filter(&m, PFIL_IN, ENC_IN|ENC_BEFORE)) != 0) - return (error); -#endif - + if (saidx->mode == IPSEC_MODE_TRANSPORT) + prot = IPPROTO_IPIP; /* * Re-dispatch via software interrupt. */ - if ((error = netisr_queue_src(NETISR_IP, (uintptr_t)sav->spi, m))) { - IPSEC_ISTAT(sproto, qfull); - DPRINTF(("%s: queue full; proto %u packet dropped\n", - __func__, sproto)); - return error; + switch (prot) { + case IPPROTO_IPIP: + isr_prot = NETISR_IP; + af = AF_INET; + break; +#ifdef INET6 + case IPPROTO_IPV6: + isr_prot = NETISR_IPV6; + af = AF_INET6; + break; +#endif + default: + DPRINTF(("%s: cannot handle inner ip proto %d\n", + __func__, prot)); + IPSEC_ISTAT(sproto, nopf); + error = EPFNOSUPPORT; + goto bad; } - return 0; -bad: - m_freem(m); - return error; -} -void -ipsec4_common_ctlinput(int cmd, struct sockaddr *sa, void *v, int proto) -{ - /* XXX nothing just yet */ + IPSEC_INIT_CTX(&ctx, &m, sav, af, IPSEC_ENC_AFTER); + if ((error = ipsec_run_hhooks(&ctx, HHOOK_TYPE_IPSEC_IN)) != 0) + goto bad; + + /* Handle virtual tunneling interfaces */ + if (saidx->mode == IPSEC_MODE_TUNNEL) + error = ipsec_if_input(m, sav, af); + if (error == 0) { + error = netisr_queue_src(isr_prot, (uintptr_t)sav->spi, m); + if (error) { + IPSEC_ISTAT(sproto, qfull); + DPRINTF(("%s: queue full; proto %u packet dropped\n", + __func__, sproto)); + } + } + key_freesav(&sav); + return (error); +bad: + key_freesav(&sav); + if (m != NULL) + m_freem(m); + return (error); } #endif /* INET */ #ifdef INET6 -/* IPv6 AH wrapper. */ +/* + * IPSEC_INPUT() method implementation for IPv6. + * 0 - Permitted by inbound security policy for further processing. + * EACCES - Forbidden by inbound security policy. + * EINPROGRESS - consumed by IPsec. + */ int -ipsec6_common_input(struct mbuf **mp, int *offp, int proto) +ipsec6_input(struct mbuf *m, int offset, int proto) { - int l = 0; - int protoff; - struct ip6_ext ip6e; - - if (*offp < sizeof(struct ip6_hdr)) { - DPRINTF(("%s: bad offset %u\n", __func__, *offp)); - return IPPROTO_DONE; - } else if (*offp == sizeof(struct ip6_hdr)) { - protoff = offsetof(struct ip6_hdr, ip6_nxt); - } else { - /* Chase down the header chain... */ - protoff = sizeof(struct ip6_hdr); - - do { - protoff += l; - m_copydata(*mp, protoff, sizeof(ip6e), - (caddr_t) &ip6e); - - if (ip6e.ip6e_nxt == IPPROTO_AH) - l = (ip6e.ip6e_len + 2) << 2; - else - l = (ip6e.ip6e_len + 1) << 3; - IPSEC_ASSERT(l > 0, ("l went zero or negative")); - } while (protoff + l < *offp); - - /* Malformed packet check */ - if (protoff + l != *offp) { - DPRINTF(("%s: bad packet header chain, protoff %u, " - "l %u, off %u\n", __func__, protoff, l, *offp)); - IPSEC_ISTAT(proto, hdrops); - m_freem(*mp); - *mp = NULL; - return IPPROTO_DONE; - } - protoff += offsetof(struct ip6_ext, ip6e_nxt); + + switch (proto) { + case IPPROTO_AH: + case IPPROTO_ESP: + case IPPROTO_IPCOMP: + /* Do inbound IPsec processing for AH/ESP/IPCOMP */ + ipsec_common_input(m, offset, + offsetof(struct ip6_hdr, ip6_nxt), AF_INET6, proto); + return (EINPROGRESS); /* mbuf consumed by IPsec */ + default: + /* + * Protocols with further headers get their IPsec treatment + * within the protocol specific processing. + */ + if ((inet6sw[ip6_protox[proto]].pr_flags & PR_LASTHDR) == 0) + return (0); + /* FALLTHROUGH */ + }; + /* + * Enforce IPsec policy checking if we are seeing last header. + */ + if (ipsec6_in_reject(m, NULL) != 0) { + /* Forbidden by inbound security policy */ + m_freem(m); + return (EACCES); } - (void) ipsec_common_input(*mp, *offp, protoff, AF_INET6, proto); - return IPPROTO_DONE; + return (0); } /* @@ -553,22 +489,20 @@ ipsec6_common_input(struct mbuf **mp, int *offp, int proto) * filtering and other sanity checks on the processed packet. */ int -ipsec6_common_input_cb(struct mbuf *m, struct secasvar *sav, int skip, int protoff, - struct m_tag *mt) +ipsec6_common_input_cb(struct mbuf *m, struct secasvar *sav, int skip, + int protoff) { - int prot, af, sproto; + char buf[IPSEC_ADDRSTRLEN]; + struct ipsec_ctx_data ctx; + struct xform_history *xh; + struct secasindex *saidx; struct ip6_hdr *ip6; struct m_tag *mtag; - struct tdb_ident *tdbi; - struct secasindex *saidx; - int nxt; - u_int8_t nxt8; + int prot, af, sproto; + int nxt, isr_prot; int error, nest; -#ifdef notyet - char ip6buf[INET6_ADDRSTRLEN]; -#endif + uint8_t nxt8; - IPSEC_ASSERT(m != NULL, ("null mbuf")); IPSEC_ASSERT(sav != NULL, ("null SA")); IPSEC_ASSERT(sav->sah != NULL, ("null SAH")); saidx = &sav->sah->saidx; @@ -579,122 +513,67 @@ ipsec6_common_input_cb(struct mbuf *m, struct secasvar *sav, int skip, int proto sproto == IPPROTO_IPCOMP, ("unexpected security protocol %u", sproto)); - /* Sanity check */ - if (m == NULL) { - DPRINTF(("%s: null mbuf", __func__)); - IPSEC_ISTAT(sproto, badkcr); - error = EINVAL; - goto bad; - } - /* Fix IPv6 header */ if (m->m_len < sizeof(struct ip6_hdr) && (m = m_pullup(m, sizeof(struct ip6_hdr))) == NULL) { DPRINTF(("%s: processing failed for SA %s/%08lx\n", - __func__, ipsec_address(&sav->sah->saidx.dst), - (u_long) ntohl(sav->spi))); + __func__, ipsec_address(&sav->sah->saidx.dst, buf, + sizeof(buf)), (u_long) ntohl(sav->spi))); IPSEC_ISTAT(sproto, hdrops); error = EACCES; goto bad; } + IPSEC_INIT_CTX(&ctx, &m, sav, af, IPSEC_ENC_BEFORE); + if ((error = ipsec_run_hhooks(&ctx, HHOOK_TYPE_IPSEC_IN)) != 0) + goto bad; + ip6 = mtod(m, struct ip6_hdr *); ip6->ip6_plen = htons(m->m_pkthdr.len - sizeof(struct ip6_hdr)); /* Save protocol */ - m_copydata(m, protoff, 1, (unsigned char *) &prot); + m_copydata(m, protoff, 1, &nxt8); + prot = nxt8; -#ifdef notyet + /* IPv6-in-IP encapsulation */ + if (prot == IPPROTO_IPV6 && + saidx->mode != IPSEC_MODE_TRANSPORT) { + if (m->m_pkthdr.len - skip < sizeof(struct ip6_hdr)) { + IPSEC_ISTAT(sproto, hdrops); + error = EINVAL; + goto bad; + } + /* ip6n will now contain the inner IPv6 header. */ + m_striphdr(m, 0, skip); + skip = 0; + } #ifdef INET /* IP-in-IP encapsulation */ - if (prot == IPPROTO_IPIP) { - struct ip ipn; - + else if (prot == IPPROTO_IPIP && + saidx->mode != IPSEC_MODE_TRANSPORT) { if (m->m_pkthdr.len - skip < sizeof(struct ip)) { IPSEC_ISTAT(sproto, hdrops); error = EINVAL; goto bad; } /* ipn will now contain the inner IPv4 header */ - m_copydata(m, skip, sizeof(struct ip), (caddr_t) &ipn); - - /* - * Check that the inner source address is the same as - * the proxy address, if available. - */ - if ((saidx->proxy.sa.sa_family == AF_INET && - saidx->proxy.sin.sin_addr.s_addr != INADDR_ANY && - ipn.ip_src.s_addr != saidx->proxy.sin.sin_addr.s_addr) || - (saidx->proxy.sa.sa_family != AF_INET && - saidx->proxy.sa.sa_family != 0)) { - - DPRINTF(("%s: inner source address %s doesn't " - "correspond to expected proxy source %s, " - "SA %s/%08lx\n", __func__, - inet_ntoa4(ipn.ip_src), - ipsec_address(&saidx->proxy), - ipsec_address(&saidx->dst), - (u_long) ntohl(sav->spi))); - - IPSEC_ISTAT(sproto, pdrops); - error = EACCES; - goto bad; - } + m_striphdr(m, 0, skip); + skip = 0; } #endif /* INET */ - - /* IPv6-in-IP encapsulation */ - if (prot == IPPROTO_IPV6) { - struct ip6_hdr ip6n; - - if (m->m_pkthdr.len - skip < sizeof(struct ip6_hdr)) { - IPSEC_ISTAT(sproto, hdrops); - error = EINVAL; - goto bad; - } - /* ip6n will now contain the inner IPv6 header. */ - m_copydata(m, skip, sizeof(struct ip6_hdr), - (caddr_t) &ip6n); - - /* - * Check that the inner source address is the same as - * the proxy address, if available. - */ - if ((saidx->proxy.sa.sa_family == AF_INET6 && - !IN6_IS_ADDR_UNSPECIFIED(&saidx->proxy.sin6.sin6_addr) && - !IN6_ARE_ADDR_EQUAL(&ip6n.ip6_src, - &saidx->proxy.sin6.sin6_addr)) || - (saidx->proxy.sa.sa_family != AF_INET6 && - saidx->proxy.sa.sa_family != 0)) { - - DPRINTF(("%s: inner source address %s doesn't " - "correspond to expected proxy source %s, " - "SA %s/%08lx\n", __func__, - ip6_sprintf(ip6buf, &ip6n.ip6_src), - ipsec_address(&saidx->proxy), - ipsec_address(&saidx->dst), - (u_long) ntohl(sav->spi))); - - IPSEC_ISTAT(sproto, pdrops); - error = EACCES; - goto bad; - } + else { + prot = IPPROTO_IPV6; /* for correct BPF processing */ } -#endif /*XXX*/ /* * Record what we've done to the packet (under what SA it was - * processed). If we've been passed an mtag, it means the packet - * was already processed by an ethernet/crypto combo card and - * thus has a tag attached with all the right information, but - * with a PACKET_TAG_IPSEC_IN_CRYPTO_DONE as opposed to - * PACKET_TAG_IPSEC_IN_DONE type; in that case, just change the type. + * processed). */ - if (mt == NULL && sproto != IPPROTO_IPCOMP) { + if (sproto != IPPROTO_IPCOMP) { mtag = m_tag_get(PACKET_TAG_IPSEC_IN_DONE, - sizeof(struct tdb_ident), M_NOWAIT); + sizeof(struct xform_history), M_NOWAIT); if (mtag == NULL) { DPRINTF(("%s: failed to get tag\n", __func__)); IPSEC_ISTAT(sproto, hdrops); @@ -702,42 +581,61 @@ ipsec6_common_input_cb(struct mbuf *m, struct secasvar *sav, int skip, int proto goto bad; } - tdbi = (struct tdb_ident *)(mtag + 1); - bcopy(&saidx->dst, &tdbi->dst, sizeof(union sockaddr_union)); - tdbi->proto = sproto; - tdbi->spi = sav->spi; - /* Cache those two for enc(4) in xform_ipip. */ - tdbi->alg_auth = sav->alg_auth; - tdbi->alg_enc = sav->alg_enc; - + xh = (struct xform_history *)(mtag + 1); + bcopy(&saidx->dst, &xh->dst, saidx->dst.sa.sa_len); + xh->spi = sav->spi; + xh->proto = sproto; + xh->mode = saidx->mode; m_tag_prepend(m, mtag); - } else { - if (mt != NULL) - mt->m_tag_id = PACKET_TAG_IPSEC_IN_DONE; - /* XXX do we need to mark m_flags??? */ } key_sa_recordxfer(sav, m); -#ifdef DEV_ENC - encif->if_ipackets++; - encif->if_ibytes += m->m_pkthdr.len; - - /* - * Pass the mbuf to enc0 for bpf and pfil. We will filter the IPIP - * packet later after it has been decapsulated. - */ - ipsec_bpf(m, sav, AF_INET6, ENC_IN|ENC_BEFORE); - - /* XXX-BZ does not make sense. */ - if (prot != IPPROTO_IPIP) - if ((error = ipsec_filter(&m, PFIL_IN, ENC_IN|ENC_BEFORE)) != 0) - return (error); +#ifdef INET + if (prot == IPPROTO_IPIP) + af = AF_INET; + else #endif - - /* Retrieve new protocol */ - m_copydata(m, protoff, sizeof(u_int8_t), (caddr_t) &nxt8); - + af = AF_INET6; + IPSEC_INIT_CTX(&ctx, &m, sav, af, IPSEC_ENC_AFTER); + if ((error = ipsec_run_hhooks(&ctx, HHOOK_TYPE_IPSEC_IN)) != 0) + goto bad; + if (skip == 0) { + /* + * We stripped outer IPv6 header. + * Now we should requeue decrypted packet via netisr. + */ + switch (prot) { +#ifdef INET + case IPPROTO_IPIP: + isr_prot = NETISR_IP; + break; +#endif + case IPPROTO_IPV6: + isr_prot = NETISR_IPV6; + break; + default: + DPRINTF(("%s: cannot handle inner ip proto %d\n", + __func__, prot)); + IPSEC_ISTAT(sproto, nopf); + error = EPFNOSUPPORT; + goto bad; + } + /* Handle virtual tunneling interfaces */ + if (saidx->mode == IPSEC_MODE_TUNNEL) + error = ipsec_if_input(m, sav, af); + if (error == 0) { + error = netisr_queue_src(isr_prot, + (uintptr_t)sav->spi, m); + if (error) { + IPSEC_ISTAT(sproto, qfull); + DPRINTF(("%s: queue full; proto %u packet" + " dropped\n", __func__, sproto)); + } + } + key_freesav(&sav); + return (error); + } /* * See the end of ip6_input for this logic. * IPPROTO_IPV[46] case will be processed just like other ones @@ -773,99 +671,12 @@ ipsec6_common_input_cb(struct mbuf *m, struct secasvar *sav, int skip, int proto } nxt = (*inet6sw[ip6_protox[nxt]].pr_input)(&m, &skip, nxt); } - return 0; + key_freesav(&sav); + return (0); bad: + key_freesav(&sav); if (m) m_freem(m); - return error; -} - -void -esp6_ctlinput(int cmd, struct sockaddr *sa, void *d) -{ - struct ip6ctlparam *ip6cp = NULL; - struct mbuf *m = NULL; - struct ip6_hdr *ip6; - int off; - - if (sa->sa_family != AF_INET6 || - sa->sa_len != sizeof(struct sockaddr_in6)) - return; - if ((unsigned)cmd >= PRC_NCMDS) - return; - - /* if the parameter is from icmp6, decode it. */ - if (d != NULL) { - ip6cp = (struct ip6ctlparam *)d; - m = ip6cp->ip6c_m; - ip6 = ip6cp->ip6c_ip6; - off = ip6cp->ip6c_off; - } else { - m = NULL; - ip6 = NULL; - off = 0; /* calm gcc */ - } - - if (ip6 != NULL) { - - struct ip6ctlparam ip6cp1; - - /* - * Notify the error to all possible sockets via pfctlinput2. - * Since the upper layer information (such as protocol type, - * source and destination ports) is embedded in the encrypted - * data and might have been cut, we can't directly call - * an upper layer ctlinput function. However, the pcbnotify - * function will consider source and destination addresses - * as well as the flow info value, and may be able to find - * some PCB that should be notified. - * Although pfctlinput2 will call esp6_ctlinput(), there is - * no possibility of an infinite loop of function calls, - * because we don't pass the inner IPv6 header. - */ - bzero(&ip6cp1, sizeof(ip6cp1)); - ip6cp1.ip6c_src = ip6cp->ip6c_src; - pfctlinput2(cmd, sa, (void *)&ip6cp1); - - /* - * Then go to special cases that need ESP header information. - * XXX: We assume that when ip6 is non NULL, - * M and OFF are valid. - */ - - if (cmd == PRC_MSGSIZE) { - struct secasvar *sav; - u_int32_t spi; - int valid; - - /* check header length before using m_copydata */ - if (m->m_pkthdr.len < off + sizeof (struct esp)) - return; - m_copydata(m, off + offsetof(struct esp, esp_spi), - sizeof(u_int32_t), (caddr_t) &spi); - /* - * Check to see if we have a valid SA corresponding to - * the address in the ICMP message payload. - */ - sav = KEY_ALLOCSA((union sockaddr_union *)sa, - IPPROTO_ESP, spi); - valid = (sav != NULL); - if (sav) - KEY_FREESAV(&sav); - - /* XXX Further validation? */ - - /* - * Depending on whether the SA is "valid" and - * routing table size (mtudisc_{hi,lo}wat), we will: - * - recalcurate the new MTU and create the - * corresponding routing entry, or - * - ignore the MTU change notification. - */ - icmp6_mtudisc_update(ip6cp, valid); - } - } else { - /* we normally notify any pcb here */ - } + return (error); } #endif /* INET6 */ diff --git a/freebsd/sys/netipsec/ipsec_mbuf.c b/freebsd/sys/netipsec/ipsec_mbuf.c index 2cafe058..d81c0deb 100644 --- a/freebsd/sys/netipsec/ipsec_mbuf.c +++ b/freebsd/sys/netipsec/ipsec_mbuf.c @@ -32,18 +32,14 @@ * IPsec-specific mbuf routines. */ -#include <rtems/bsd/local/opt_param.h> - #include <rtems/bsd/sys/param.h> #include <sys/systm.h> +#include <sys/malloc.h> #include <sys/mbuf.h> #include <sys/socket.h> -#include <net/route.h> #include <net/vnet.h> - #include <netinet/in.h> - #include <netipsec/ipsec.h> /* @@ -76,7 +72,21 @@ m_makespace(struct mbuf *m0, int skip, int hlen, int *off) * the contents of m as needed. */ remain = m->m_len - skip; /* data to move */ - if (hlen > M_TRAILINGSPACE(m)) { + if (remain > skip && + hlen + max_linkhdr < M_LEADINGSPACE(m)) { + /* + * mbuf has enough free space at the beginning. + * XXX: which operation is the most heavy - copying of + * possible several hundred of bytes or allocation + * of new mbuf? We can remove max_linkhdr check + * here, but it is possible that this will lead + * to allocation of new mbuf in Layer 2 code. + */ + m->m_data -= hlen; + bcopy(mtodo(m, hlen), mtod(m, caddr_t), skip); + m->m_len += hlen; + *off = skip; + } else if (hlen > M_TRAILINGSPACE(m)) { struct mbuf *n0, *n, **np; int todo, len, done, alloc; @@ -87,11 +97,11 @@ m_makespace(struct mbuf *m0, int skip, int hlen, int *off) todo = remain; while (todo > 0) { if (todo > MHLEN) { - n = m_getcl(M_DONTWAIT, m->m_type, 0); + n = m_getcl(M_NOWAIT, m->m_type, 0); len = MCLBYTES; } else { - n = m_get(M_DONTWAIT, m->m_type); + n = m_get(M_NOWAIT, m->m_type); len = MHLEN; } if (n == NULL) { @@ -117,7 +127,7 @@ m_makespace(struct mbuf *m0, int skip, int hlen, int *off) } } else { - n = m_get(M_DONTWAIT, m->m_type); + n = m_get(M_NOWAIT, m->m_type); if (n == NULL) { m_freem(n0); return NULL; @@ -144,7 +154,7 @@ m_makespace(struct mbuf *m0, int skip, int hlen, int *off) * so there's space to write the new header. */ bcopy(mtod(m, caddr_t) + skip, - mtod(m, caddr_t) + skip + hlen, remain); + mtod(m, caddr_t) + skip + hlen, remain); m->m_len += hlen; *off = skip; } @@ -205,8 +215,8 @@ m_pad(struct mbuf *m, int n) if (pad > M_TRAILINGSPACE(m0)) { /* Add an mbuf to the chain. */ - MGET(m1, M_DONTWAIT, MT_DATA); - if (m1 == 0) { + MGET(m1, M_NOWAIT, MT_DATA); + if (m1 == NULL) { m_freem(m0); DPRINTF(("%s: unable to get extra mbuf\n", __func__)); return NULL; diff --git a/freebsd/sys/netipsec/ipsec_mod.c b/freebsd/sys/netipsec/ipsec_mod.c new file mode 100644 index 00000000..78b9f1da --- /dev/null +++ b/freebsd/sys/netipsec/ipsec_mod.c @@ -0,0 +1,150 @@ +#include <machine/rtems-bsd-kernel-space.h> + +/*- + * Copyright (c) 2016 Andrey V. Elsukov <ae@FreeBSD.org> + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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 <rtems/bsd/local/opt_inet.h> +#include <rtems/bsd/local/opt_inet6.h> +#include <rtems/bsd/local/opt_ipsec.h> + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <rtems/bsd/sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <rtems/bsd/sys/lock.h> +#include <sys/malloc.h> +#include <sys/mbuf.h> +#include <sys/module.h> +#include <sys/priv.h> +#include <sys/rmlock.h> +#include <sys/socket.h> +#include <sys/sockopt.h> +#include <sys/syslog.h> +#include <sys/proc.h> + +#include <netinet/in.h> +#include <netinet/in_pcb.h> + +#include <netipsec/ipsec.h> +#include <netipsec/ipsec6.h> +#include <netipsec/key.h> +#include <netipsec/key_debug.h> + +#include <netipsec/ipsec_support.h> + +#ifdef INET +static const struct ipsec_methods ipv4_methods = { + .input = ipsec4_input, + .forward = ipsec4_forward, + .output = ipsec4_output, + .pcbctl = ipsec4_pcbctl, + .capability = ipsec4_capability, + .check_policy = ipsec4_in_reject, + .hdrsize = ipsec_hdrsiz_inpcb, + .udp_input = udp_ipsec_input, + .udp_pcbctl = udp_ipsec_pcbctl, +}; +#ifndef KLD_MODULE +static const struct ipsec_support ipv4_ipsec = { + .enabled = IPSEC_MODULE_ENABLED, + .methods = &ipv4_methods +}; +const struct ipsec_support * const ipv4_ipsec_support = &ipv4_ipsec; +#endif /* !KLD_MODULE */ +#endif /* INET */ + +#ifdef INET6 +static const struct ipsec_methods ipv6_methods = { + .input = ipsec6_input, + .forward = ipsec6_forward, + .output = ipsec6_output, + .pcbctl = ipsec6_pcbctl, + .capability = ipsec6_capability, + .check_policy = ipsec6_in_reject, + .hdrsize = ipsec_hdrsiz_inpcb, +}; +#ifndef KLD_MODULE +static const struct ipsec_support ipv6_ipsec = { + .enabled = IPSEC_MODULE_ENABLED, + .methods = &ipv6_methods +}; +const struct ipsec_support * const ipv6_ipsec_support = &ipv6_ipsec; +#endif /* !KLD_MODULE */ +#endif /* INET6 */ + +/* + * Always register ipsec module. + * Even when IPsec is build in the kernel, we need to have + * module registered. This will prevent to load ipsec.ko. + */ +static int +ipsec_modevent(module_t mod, int type, void *data) +{ + + switch (type) { + case MOD_LOAD: + /* All xforms are registered via SYSINIT */ + if (!ipsec_initialized()) + return (ENOMEM); +#ifdef KLD_MODULE +#ifdef INET + ipsec_support_enable(ipv4_ipsec_support, &ipv4_methods); +#endif +#ifdef INET6 + ipsec_support_enable(ipv6_ipsec_support, &ipv6_methods); +#endif +#endif /* KLD_MODULE */ + break; + case MOD_UNLOAD: + /* All xforms are unregistered via SYSUNINIT */ +#ifdef KLD_MODULE +#ifdef INET + ipsec_support_disable(ipv4_ipsec_support); +#endif +#ifdef INET6 + ipsec_support_disable(ipv6_ipsec_support); +#endif +#endif /* KLD_MODULE */ + break; + default: + return (EOPNOTSUPP); + } + return (0); +} + +static moduledata_t ipsec_mod = { + "ipsec", + ipsec_modevent, + 0 +}; + +DECLARE_MODULE(ipsec, ipsec_mod, SI_SUB_PROTO_DOMAIN, SI_ORDER_ANY); +MODULE_VERSION(ipsec, 1); +#ifdef KLD_MODULE +MODULE_DEPEND(ipsec, ipsec_support, 1, 1, 1); +#endif diff --git a/freebsd/sys/netipsec/ipsec_output.c b/freebsd/sys/netipsec/ipsec_output.c index a02b6ce2..3403b0bc 100644 --- a/freebsd/sys/netipsec/ipsec_output.c +++ b/freebsd/sys/netipsec/ipsec_output.c @@ -2,6 +2,7 @@ /*- * Copyright (c) 2002, 2003 Sam Leffler, Errno Consulting + * Copyright (c) 2016 Andrey V. Elsukov <ae@FreeBSD.org> * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -34,7 +35,7 @@ #include <rtems/bsd/local/opt_inet.h> #include <rtems/bsd/local/opt_inet6.h> #include <rtems/bsd/local/opt_ipsec.h> -#include <rtems/bsd/local/opt_enc.h> +#include <rtems/bsd/local/opt_sctp.h> #include <rtems/bsd/sys/param.h> #include <sys/systm.h> @@ -43,11 +44,12 @@ #include <sys/protosw.h> #include <sys/socket.h> #include <rtems/bsd/sys/errno.h> +#include <sys/hhook.h> #include <sys/syslog.h> #include <net/if.h> -#include <net/pfil.h> -#include <net/route.h> +#include <net/if_enc.h> +#include <net/if_var.h> #include <net/vnet.h> #include <netinet/in.h> @@ -63,12 +65,19 @@ #include <netinet/ip6.h> #ifdef INET6 #include <netinet6/ip6_var.h> +#include <netinet6/scope6_var.h> #endif #include <netinet/in_pcb.h> #ifdef INET6 #include <netinet/icmp6.h> #endif +#ifdef SCTP +#include <netinet/sctp_crc32.h> +#endif +#include <netinet/udp.h> +#include <netipsec/ah.h> +#include <netipsec/esp.h> #include <netipsec/ipsec.h> #ifdef INET6 #include <netipsec/ipsec6.h> @@ -85,834 +94,880 @@ #include <machine/in_cksum.h> -#ifdef IPSEC_NAT_T -#include <netinet/udp.h> -#endif - -#ifdef DEV_ENC -#include <net/if_enc.h> -#endif +#define IPSEC_OSTAT_INC(proto, name) do { \ + if ((proto) == IPPROTO_ESP) \ + ESPSTAT_INC(esps_##name); \ + else if ((proto) == IPPROTO_AH)\ + AHSTAT_INC(ahs_##name); \ + else \ + IPCOMPSTAT_INC(ipcomps_##name); \ +} while (0) +static int ipsec_encap(struct mbuf **mp, struct secasindex *saidx); -int -ipsec_process_done(struct mbuf *m, struct ipsecrequest *isr) +#ifdef INET +static struct secasvar * +ipsec4_allocsa(struct mbuf *m, struct secpolicy *sp, u_int *pidx, int *error) { - struct tdb_ident *tdbi; - struct m_tag *mtag; + struct secasindex *saidx, tmpsaidx; + struct ipsecrequest *isr; + struct sockaddr_in *sin; struct secasvar *sav; - struct secasindex *saidx; - int error; - - IPSEC_ASSERT(m != NULL, ("null mbuf")); - IPSEC_ASSERT(isr != NULL, ("null ISR")); - sav = isr->sav; - IPSEC_ASSERT(sav != NULL, ("null SA")); - IPSEC_ASSERT(sav->sah != NULL, ("null SAH")); + struct ip *ip; - saidx = &sav->sah->saidx; - switch (saidx->dst.sa.sa_family) { -#ifdef INET - case AF_INET: - /* Fix the header length, for AH processing. */ - mtod(m, struct ip *)->ip_len = htons(m->m_pkthdr.len); - break; -#endif /* INET */ -#ifdef INET6 - case AF_INET6: - /* Fix the header length, for AH processing. */ - if (m->m_pkthdr.len < sizeof (struct ip6_hdr)) { - error = ENXIO; - goto bad; + /* + * Check system global policy controls. + */ +next: + isr = sp->req[*pidx]; + if ((isr->saidx.proto == IPPROTO_ESP && !V_esp_enable) || + (isr->saidx.proto == IPPROTO_AH && !V_ah_enable) || + (isr->saidx.proto == IPPROTO_IPCOMP && !V_ipcomp_enable)) { + DPRINTF(("%s: IPsec outbound packet dropped due" + " to policy (check your sysctls)\n", __func__)); + IPSEC_OSTAT_INC(isr->saidx.proto, pdrops); + *error = EHOSTUNREACH; + return (NULL); + } + /* + * Craft SA index to search for proper SA. Note that + * we only initialize unspecified SA peers for transport + * mode; for tunnel mode they must already be filled in. + */ + if (isr->saidx.mode == IPSEC_MODE_TRANSPORT) { + saidx = &tmpsaidx; + *saidx = isr->saidx; + ip = mtod(m, struct ip *); + if (saidx->src.sa.sa_len == 0) { + sin = &saidx->src.sin; + sin->sin_len = sizeof(*sin); + sin->sin_family = AF_INET; + sin->sin_port = IPSEC_PORT_ANY; + sin->sin_addr = ip->ip_src; } - if (m->m_pkthdr.len - sizeof (struct ip6_hdr) > IPV6_MAXPACKET) { - /* No jumbogram support. */ - error = ENXIO; /*?*/ - goto bad; + if (saidx->dst.sa.sa_len == 0) { + sin = &saidx->dst.sin; + sin->sin_len = sizeof(*sin); + sin->sin_family = AF_INET; + sin->sin_port = IPSEC_PORT_ANY; + sin->sin_addr = ip->ip_dst; } - mtod(m, struct ip6_hdr *)->ip6_plen = - htons(m->m_pkthdr.len - sizeof(struct ip6_hdr)); - break; -#endif /* INET6 */ - default: - DPRINTF(("%s: unknown protocol family %u\n", __func__, - saidx->dst.sa.sa_family)); - error = ENXIO; - goto bad; + } else + saidx = &sp->req[*pidx]->saidx; + /* + * Lookup SA and validate it. + */ + sav = key_allocsa_policy(sp, saidx, error); + if (sav == NULL) { + IPSECSTAT_INC(ips_out_nosa); + if (*error != 0) + return (NULL); + if (ipsec_get_reqlevel(sp, *pidx) != IPSEC_LEVEL_REQUIRE) { + /* + * We have no SA and policy that doesn't require + * this IPsec transform, thus we can continue w/o + * IPsec processing, i.e. return EJUSTRETURN. + * But first check if there is some bundled transform. + */ + if (sp->tcount > ++(*pidx)) + goto next; + *error = EJUSTRETURN; + } + return (NULL); } + IPSEC_ASSERT(sav->tdb_xform != NULL, ("SA with NULL tdb_xform")); + return (sav); +} + +/* + * IPsec output logic for IPv4. + */ +static int +ipsec4_perform_request(struct mbuf *m, struct secpolicy *sp, u_int idx) +{ + char sbuf[IPSEC_ADDRSTRLEN], dbuf[IPSEC_ADDRSTRLEN]; + struct ipsec_ctx_data ctx; + union sockaddr_union *dst; + struct secasvar *sav; + struct ip *ip; + int error, i, off; + + IPSEC_ASSERT(idx < sp->tcount, ("Wrong IPsec request index %d", idx)); /* - * Add a record of what we've done or what needs to be done to the - * packet. + * We hold the reference to SP. Content of SP couldn't be changed. + * Craft secasindex and do lookup for suitable SA. + * Then do encapsulation if needed and call xform's output. + * We need to store SP in the xform callback parameters. + * In xform callback we will extract SP and it can be used to + * determine next transform. At the end of transform we can + * release reference to SP. */ - mtag = m_tag_get(PACKET_TAG_IPSEC_OUT_DONE, - sizeof(struct tdb_ident), M_NOWAIT); - if (mtag == NULL) { - DPRINTF(("%s: could not get packet tag\n", __func__)); - error = ENOMEM; + sav = ipsec4_allocsa(m, sp, &idx, &error); + if (sav == NULL) { + if (error == EJUSTRETURN) { /* No IPsec required */ + key_freesp(&sp); + return (error); + } goto bad; } - - tdbi = (struct tdb_ident *)(mtag + 1); - tdbi->dst = saidx->dst; - tdbi->proto = saidx->proto; - tdbi->spi = sav->spi; - m_tag_prepend(m, mtag); - /* - * If there's another (bundled) SA to apply, do so. - * Note that this puts a burden on the kernel stack size. - * If this is a problem we'll need to introduce a queue - * to set the packet on so we can unwind the stack before - * doing further processing. + * XXXAE: most likely ip_sum at this point is wrong. */ - if (isr->next) { - IPSECSTAT_INC(ips_out_bundlesa); - /* XXX-BZ currently only support same AF bundles. */ - switch (saidx->dst.sa.sa_family) { -#ifdef INET - case AF_INET: - return ipsec4_process_packet(m, isr->next, 0, 0); - /* NOTREACHED */ -#endif -#ifdef notyet -#ifdef INET6 - case AF_INET6: - /* XXX */ - ipsec6_output_trans() - ipsec6_output_tunnel() - /* NOTREACHED */ -#endif /* INET6 */ -#endif - default: - DPRINTF(("%s: unknown protocol family %u\n", __func__, - saidx->dst.sa.sa_family)); - error = ENXIO; + IPSEC_INIT_CTX(&ctx, &m, sav, AF_INET, IPSEC_ENC_BEFORE); + if ((error = ipsec_run_hhooks(&ctx, HHOOK_TYPE_IPSEC_OUT)) != 0) + goto bad; + + ip = mtod(m, struct ip *); + dst = &sav->sah->saidx.dst; + /* Do the appropriate encapsulation, if necessary */ + if (sp->req[idx]->saidx.mode == IPSEC_MODE_TUNNEL || /* Tunnel requ'd */ + dst->sa.sa_family != AF_INET || /* PF mismatch */ + (dst->sa.sa_family == AF_INET && /* Proxy */ + dst->sin.sin_addr.s_addr != INADDR_ANY && + dst->sin.sin_addr.s_addr != ip->ip_dst.s_addr)) { + /* Fix IPv4 header checksum and length */ + ip->ip_len = htons(m->m_pkthdr.len); + ip->ip_sum = 0; + ip->ip_sum = in_cksum(m, ip->ip_hl << 2); + error = ipsec_encap(&m, &sav->sah->saidx); + if (error != 0) { + DPRINTF(("%s: encapsulation for SA %s->%s " + "SPI 0x%08x failed with error %d\n", __func__, + ipsec_address(&sav->sah->saidx.src, sbuf, + sizeof(sbuf)), + ipsec_address(&sav->sah->saidx.dst, dbuf, + sizeof(dbuf)), ntohl(sav->spi), error)); + /* XXXAE: IPSEC_OSTAT_INC(tunnel); */ goto bad; } } - key_sa_recordxfer(sav, m); /* record data transfer */ - m_addr_changed(m); + IPSEC_INIT_CTX(&ctx, &m, sav, dst->sa.sa_family, IPSEC_ENC_AFTER); + if ((error = ipsec_run_hhooks(&ctx, HHOOK_TYPE_IPSEC_OUT)) != 0) + goto bad; /* - * We're done with IPsec processing, transmit the packet using the - * appropriate network protocol (IP or IPv6). SPD lookup will be - * performed again there. + * Dispatch to the appropriate IPsec transform logic. The + * packet will be returned for transmission after crypto + * processing, etc. are completed. + * + * NB: m & sav are ``passed to caller'' who's responsible for + * reclaiming their resources. */ - switch (saidx->dst.sa.sa_family) { -#ifdef INET - struct ip *ip; + switch(dst->sa.sa_family) { case AF_INET: ip = mtod(m, struct ip *); - ip->ip_len = ntohs(ip->ip_len); - ip->ip_off = ntohs(ip->ip_off); - -#ifdef IPSEC_NAT_T - /* - * If NAT-T is enabled, now that all IPsec processing is done - * insert UDP encapsulation header after IP header. - */ - if (sav->natt_type) { -#ifdef _IP_VHL - const int hlen = IP_VHL_HL(ip->ip_vhl); -#else - const int hlen = (ip->ip_hl << 2); -#endif - int size, off; - struct mbuf *mi; - struct udphdr *udp; - - size = sizeof(struct udphdr); - if (sav->natt_type == UDP_ENCAP_ESPINUDP_NON_IKE) { - /* - * draft-ietf-ipsec-nat-t-ike-0[01].txt and - * draft-ietf-ipsec-udp-encaps-(00/)01.txt, - * ignoring possible AH mode - * non-IKE marker + non-ESP marker - * from draft-ietf-ipsec-udp-encaps-00.txt. - */ - size += sizeof(u_int64_t); - } - mi = m_makespace(m, hlen, size, &off); - if (mi == NULL) { - DPRINTF(("%s: m_makespace for udphdr failed\n", - __func__)); - error = ENOBUFS; - goto bad; - } - - udp = (struct udphdr *)(mtod(mi, caddr_t) + off); - if (sav->natt_type == UDP_ENCAP_ESPINUDP_NON_IKE) - udp->uh_sport = htons(UDP_ENCAP_ESPINUDP_PORT); - else - udp->uh_sport = - KEY_PORTFROMSADDR(&sav->sah->saidx.src); - udp->uh_dport = KEY_PORTFROMSADDR(&sav->sah->saidx.dst); - udp->uh_sum = 0; - udp->uh_ulen = htons(m->m_pkthdr.len - hlen); - ip->ip_len = m->m_pkthdr.len; - ip->ip_p = IPPROTO_UDP; - - if (sav->natt_type == UDP_ENCAP_ESPINUDP_NON_IKE) - *(u_int64_t *)(udp + 1) = 0; - } -#endif /* IPSEC_NAT_T */ - - return ip_output(m, NULL, NULL, IP_RAWOUTPUT, NULL, NULL); -#endif /* INET */ + i = ip->ip_hl << 2; + off = offsetof(struct ip, ip_p); + break; #ifdef INET6 case AF_INET6: - /* - * We don't need massage, IPv6 header fields are always in - * net endian. - */ - return ip6_output(m, NULL, NULL, 0, NULL, NULL, NULL); + i = sizeof(struct ip6_hdr); + off = offsetof(struct ip6_hdr, ip6_nxt); + break; #endif /* INET6 */ + default: + DPRINTF(("%s: unsupported protocol family %u\n", + __func__, dst->sa.sa_family)); + error = EPFNOSUPPORT; + IPSEC_OSTAT_INC(sav->sah->saidx.proto, nopf); + goto bad; } - panic("ipsec_process_done"); + error = (*sav->tdb_xform->xf_output)(m, sp, sav, idx, i, off); + if (error != 0) { + key_freesav(&sav); + key_freesp(&sp); + } + return (error); bad: - m_freem(m); + IPSECSTAT_INC(ips_out_inval); + if (m != NULL) + m_freem(m); + if (sav != NULL) + key_freesav(&sav); + key_freesp(&sp); return (error); } -static struct ipsecrequest * -ipsec_nextisr( - struct mbuf *m, - struct ipsecrequest *isr, - int af, - struct secasindex *saidx, - int *error -) +int +ipsec4_process_packet(struct mbuf *m, struct secpolicy *sp, + struct inpcb *inp) { -#define IPSEC_OSTAT(name) do { \ - if (isr->saidx.proto == IPPROTO_ESP) \ - ESPSTAT_INC(esps_##name); \ - else if (isr->saidx.proto == IPPROTO_AH)\ - AHSTAT_INC(ahs_##name); \ - else \ - IPCOMPSTAT_INC(ipcomps_##name); \ -} while (0) - struct secasvar *sav; - IPSECREQUEST_LOCK_ASSERT(isr); + return (ipsec4_perform_request(m, sp, 0)); +} - IPSEC_ASSERT(af == AF_INET || af == AF_INET6, - ("invalid address family %u", af)); -again: - /* - * Craft SA index to search for proper SA. Note that - * we only fillin unspecified SA peers for transport - * mode; for tunnel mode they must already be filled in. - */ - *saidx = isr->saidx; - if (isr->saidx.mode == IPSEC_MODE_TRANSPORT) { - /* Fillin unspecified SA peers only for transport mode */ - if (af == AF_INET) { - struct sockaddr_in *sin; - struct ip *ip = mtod(m, struct ip *); +static int +ipsec4_common_output(struct mbuf *m, struct inpcb *inp, int forwarding) +{ + struct secpolicy *sp; + int error; - if (saidx->src.sa.sa_len == 0) { - sin = &saidx->src.sin; - sin->sin_len = sizeof(*sin); - sin->sin_family = AF_INET; - sin->sin_port = IPSEC_PORT_ANY; - sin->sin_addr = ip->ip_src; - } - if (saidx->dst.sa.sa_len == 0) { - sin = &saidx->dst.sin; - sin->sin_len = sizeof(*sin); - sin->sin_family = AF_INET; - sin->sin_port = IPSEC_PORT_ANY; - sin->sin_addr = ip->ip_dst; - } - } else { - struct sockaddr_in6 *sin6; - struct ip6_hdr *ip6 = mtod(m, struct ip6_hdr *); - - if (saidx->src.sin6.sin6_len == 0) { - sin6 = (struct sockaddr_in6 *)&saidx->src; - sin6->sin6_len = sizeof(*sin6); - sin6->sin6_family = AF_INET6; - sin6->sin6_port = IPSEC_PORT_ANY; - sin6->sin6_addr = ip6->ip6_src; - if (IN6_IS_SCOPE_LINKLOCAL(&ip6->ip6_src)) { - /* fix scope id for comparing SPD */ - sin6->sin6_addr.s6_addr16[1] = 0; - sin6->sin6_scope_id = - ntohs(ip6->ip6_src.s6_addr16[1]); - } - } - if (saidx->dst.sin6.sin6_len == 0) { - sin6 = (struct sockaddr_in6 *)&saidx->dst; - sin6->sin6_len = sizeof(*sin6); - sin6->sin6_family = AF_INET6; - sin6->sin6_port = IPSEC_PORT_ANY; - sin6->sin6_addr = ip6->ip6_dst; - if (IN6_IS_SCOPE_LINKLOCAL(&ip6->ip6_dst)) { - /* fix scope id for comparing SPD */ - sin6->sin6_addr.s6_addr16[1] = 0; - sin6->sin6_scope_id = - ntohs(ip6->ip6_dst.s6_addr16[1]); - } - } + /* Lookup for the corresponding outbound security policy */ + sp = ipsec4_checkpolicy(m, inp, &error); + if (sp == NULL) { + if (error == -EINVAL) { + /* Discarded by policy. */ + m_freem(m); + return (EACCES); } + return (0); /* No IPsec required. */ } /* - * Lookup SA and validate it. + * Usually we have to have tunnel mode IPsec security policy + * when we are forwarding a packet. Otherwise we could not handle + * encrypted replies, because they are not destined for us. But + * some users are doing source address translation for forwarded + * packets, and thus, even if they are forwarded, the replies will + * return back to us. */ - *error = key_checkrequest(isr, saidx); - if (*error != 0) { + if (!forwarding) { /* - * IPsec processing is required, but no SA found. - * I assume that key_acquire() had been called - * to get/establish the SA. Here I discard - * this packet because it is responsibility for - * upper layer to retransmit the packet. + * Do delayed checksums now because we send before + * this is done in the normal processing path. */ - IPSECSTAT_INC(ips_out_nosa); - goto bad; + if (m->m_pkthdr.csum_flags & CSUM_DELAY_DATA) { + in_delayed_cksum(m); + m->m_pkthdr.csum_flags &= ~CSUM_DELAY_DATA; + } +#ifdef SCTP + if (m->m_pkthdr.csum_flags & CSUM_SCTP) { + struct ip *ip = mtod(m, struct ip *); + + sctp_delayed_cksum(m, (uint32_t)(ip->ip_hl << 2)); + m->m_pkthdr.csum_flags &= ~CSUM_SCTP; + } +#endif } - sav = isr->sav; - if (sav == NULL) { - IPSEC_ASSERT(ipsec_get_reqlevel(isr) == IPSEC_LEVEL_USE, - ("no SA found, but required; level %u", - ipsec_get_reqlevel(isr))); - IPSECREQUEST_UNLOCK(isr); - isr = isr->next; + /* NB: callee frees mbuf and releases reference to SP */ + error = ipsec4_process_packet(m, sp, inp); + if (error == EJUSTRETURN) { /* - * If isr is NULL, we found a 'use' policy w/o SA. - * Return w/o error and w/o isr so we can drop out - * and continue w/o IPsec processing. + * We had a SP with a level of 'use' and no SA. We + * will just continue to process the packet without + * IPsec processing and return without error. */ - if (isr == NULL) - return isr; - IPSECREQUEST_LOCK(isr); - goto again; + return (0); } + if (error == 0) + return (EINPROGRESS); /* consumed by IPsec */ + return (error); +} + +/* + * IPSEC_OUTPUT() method implementation for IPv4. + * 0 - no IPsec handling needed + * other values - mbuf consumed by IPsec. + */ +int +ipsec4_output(struct mbuf *m, struct inpcb *inp) +{ + + /* + * If the packet is resubmitted to ip_output (e.g. after + * AH, ESP, etc. processing), there will be a tag to bypass + * the lookup and related policy checking. + */ + if (m_tag_find(m, PACKET_TAG_IPSEC_OUT_DONE, NULL) != NULL) + return (0); + + return (ipsec4_common_output(m, inp, 0)); +} + +/* + * IPSEC_FORWARD() method implementation for IPv4. + * 0 - no IPsec handling needed + * other values - mbuf consumed by IPsec. + */ +int +ipsec4_forward(struct mbuf *m) +{ + + /* + * Check if this packet has an active inbound SP and needs to be + * dropped instead of forwarded. + */ + if (ipsec4_in_reject(m, NULL) != 0) { + m_freem(m); + return (EACCES); + } + return (ipsec4_common_output(m, NULL, 1)); +} +#endif + +#ifdef INET6 +static int +in6_sa_equal_addrwithscope(const struct sockaddr_in6 *sa, + const struct in6_addr *ia) +{ + struct in6_addr ia2; + + if (IN6_IS_SCOPE_LINKLOCAL(&sa->sin6_addr)) { + memcpy(&ia2, &sa->sin6_addr, sizeof(ia2)); + ia2.s6_addr16[1] = htons(sa->sin6_scope_id); + return (IN6_ARE_ADDR_EQUAL(ia, &ia2)); + } + return (IN6_ARE_ADDR_EQUAL(&sa->sin6_addr, ia)); +} + +static struct secasvar * +ipsec6_allocsa(struct mbuf *m, struct secpolicy *sp, u_int *pidx, int *error) +{ + struct secasindex *saidx, tmpsaidx; + struct ipsecrequest *isr; + struct sockaddr_in6 *sin6; + struct secasvar *sav; + struct ip6_hdr *ip6; /* * Check system global policy controls. */ +next: + isr = sp->req[*pidx]; if ((isr->saidx.proto == IPPROTO_ESP && !V_esp_enable) || (isr->saidx.proto == IPPROTO_AH && !V_ah_enable) || (isr->saidx.proto == IPPROTO_IPCOMP && !V_ipcomp_enable)) { DPRINTF(("%s: IPsec outbound packet dropped due" " to policy (check your sysctls)\n", __func__)); - IPSEC_OSTAT(pdrops); + IPSEC_OSTAT_INC(isr->saidx.proto, pdrops); *error = EHOSTUNREACH; - goto bad; + return (NULL); } - /* - * Sanity check the SA contents for the caller - * before they invoke the xform output method. + * Craft SA index to search for proper SA. Note that + * we only fillin unspecified SA peers for transport + * mode; for tunnel mode they must already be filled in. */ - if (sav->tdb_xform == NULL) { - DPRINTF(("%s: no transform for SA\n", __func__)); - IPSEC_OSTAT(noxform); - *error = EHOSTUNREACH; - goto bad; + if (isr->saidx.mode == IPSEC_MODE_TRANSPORT) { + saidx = &tmpsaidx; + *saidx = isr->saidx; + ip6 = mtod(m, struct ip6_hdr *); + if (saidx->src.sin6.sin6_len == 0) { + sin6 = (struct sockaddr_in6 *)&saidx->src; + sin6->sin6_len = sizeof(*sin6); + sin6->sin6_family = AF_INET6; + sin6->sin6_port = IPSEC_PORT_ANY; + sin6->sin6_addr = ip6->ip6_src; + if (IN6_IS_SCOPE_LINKLOCAL(&ip6->ip6_src)) { + /* fix scope id for comparing SPD */ + sin6->sin6_addr.s6_addr16[1] = 0; + sin6->sin6_scope_id = + ntohs(ip6->ip6_src.s6_addr16[1]); + } + } + if (saidx->dst.sin6.sin6_len == 0) { + sin6 = (struct sockaddr_in6 *)&saidx->dst; + sin6->sin6_len = sizeof(*sin6); + sin6->sin6_family = AF_INET6; + sin6->sin6_port = IPSEC_PORT_ANY; + sin6->sin6_addr = ip6->ip6_dst; + if (IN6_IS_SCOPE_LINKLOCAL(&ip6->ip6_dst)) { + /* fix scope id for comparing SPD */ + sin6->sin6_addr.s6_addr16[1] = 0; + sin6->sin6_scope_id = + ntohs(ip6->ip6_dst.s6_addr16[1]); + } + } + } else + saidx = &sp->req[*pidx]->saidx; + /* + * Lookup SA and validate it. + */ + sav = key_allocsa_policy(sp, saidx, error); + if (sav == NULL) { + IPSEC6STAT_INC(ips_out_nosa); + if (*error != 0) + return (NULL); + if (ipsec_get_reqlevel(sp, *pidx) != IPSEC_LEVEL_REQUIRE) { + /* + * We have no SA and policy that doesn't require + * this IPsec transform, thus we can continue w/o + * IPsec processing, i.e. return EJUSTRETURN. + * But first check if there is some bundled transform. + */ + if (sp->tcount > ++(*pidx)) + goto next; + *error = EJUSTRETURN; + } + return (NULL); } - return isr; -bad: - IPSEC_ASSERT(*error != 0, ("error return w/ no error code")); - IPSECREQUEST_UNLOCK(isr); - return NULL; -#undef IPSEC_OSTAT + IPSEC_ASSERT(sav->tdb_xform != NULL, ("SA with NULL tdb_xform")); + return (sav); } -#ifdef INET /* - * IPsec output logic for IPv4. + * IPsec output logic for IPv6. */ -int -ipsec4_process_packet( - struct mbuf *m, - struct ipsecrequest *isr, - int flags, - int tunalready) +static int +ipsec6_perform_request(struct mbuf *m, struct secpolicy *sp, u_int idx) { - struct secasindex saidx; + char sbuf[IPSEC_ADDRSTRLEN], dbuf[IPSEC_ADDRSTRLEN]; + struct ipsec_ctx_data ctx; + union sockaddr_union *dst; struct secasvar *sav; - struct ip *ip; + struct ip6_hdr *ip6; int error, i, off; - IPSEC_ASSERT(m != NULL, ("null mbuf")); - IPSEC_ASSERT(isr != NULL, ("null isr")); - - IPSECREQUEST_LOCK(isr); /* insure SA contents don't change */ + IPSEC_ASSERT(idx < sp->tcount, ("Wrong IPsec request index %d", idx)); - isr = ipsec_nextisr(m, isr, AF_INET, &saidx, &error); - if (isr == NULL) { - if (error != 0) - goto bad; - return EJUSTRETURN; + sav = ipsec6_allocsa(m, sp, &idx, &error); + if (sav == NULL) { + if (error == EJUSTRETURN) { /* No IPsec required */ + key_freesp(&sp); + return (error); + } + goto bad; } - sav = isr->sav; - -#ifdef DEV_ENC - encif->if_opackets++; - encif->if_obytes += m->m_pkthdr.len; + /* Fix IP length in case if it is not set yet. */ + ip6 = mtod(m, struct ip6_hdr *); + ip6->ip6_plen = htons(m->m_pkthdr.len - sizeof(*ip6)); - /* pass the mbuf to enc0 for bpf processing */ - ipsec_bpf(m, sav, AF_INET, ENC_OUT|ENC_BEFORE); - /* pass the mbuf to enc0 for packet filtering */ - if ((error = ipsec_filter(&m, PFIL_OUT, ENC_OUT|ENC_BEFORE)) != 0) + IPSEC_INIT_CTX(&ctx, &m, sav, AF_INET6, IPSEC_ENC_BEFORE); + if ((error = ipsec_run_hhooks(&ctx, HHOOK_TYPE_IPSEC_OUT)) != 0) goto bad; -#endif - if (!tunalready) { - union sockaddr_union *dst = &sav->sah->saidx.dst; - int setdf; + ip6 = mtod(m, struct ip6_hdr *); /* pfil can change mbuf */ + dst = &sav->sah->saidx.dst; - /* - * Collect IP_DF state from the outer header. - */ - if (dst->sa.sa_family == AF_INET) { - if (m->m_len < sizeof (struct ip) && - (m = m_pullup(m, sizeof (struct ip))) == NULL) { - error = ENOBUFS; - goto bad; - } - ip = mtod(m, struct ip *); - /* Honor system-wide control of how to handle IP_DF */ - switch (V_ip4_ipsec_dfbit) { - case 0: /* clear in outer header */ - case 1: /* set in outer header */ - setdf = V_ip4_ipsec_dfbit; - break; - default: /* propagate to outer header */ - setdf = ntohs(ip->ip_off & IP_DF); - break; - } - } else { - ip = NULL; /* keep compiler happy */ - setdf = 0; + /* Do the appropriate encapsulation, if necessary */ + if (sp->req[idx]->saidx.mode == IPSEC_MODE_TUNNEL || /* Tunnel requ'd */ + dst->sa.sa_family != AF_INET6 || /* PF mismatch */ + ((dst->sa.sa_family == AF_INET6) && + (!IN6_IS_ADDR_UNSPECIFIED(&dst->sin6.sin6_addr)) && + (!in6_sa_equal_addrwithscope(&dst->sin6, &ip6->ip6_dst)))) { + if (m->m_pkthdr.len - sizeof(*ip6) > IPV6_MAXPACKET) { + /* No jumbogram support. */ + error = ENXIO; /*XXX*/ + goto bad; } - /* Do the appropriate encapsulation, if necessary */ - if (isr->saidx.mode == IPSEC_MODE_TUNNEL || /* Tunnel requ'd */ - dst->sa.sa_family != AF_INET || /* PF mismatch */ -#if 0 - (sav->flags & SADB_X_SAFLAGS_TUNNEL) || /* Tunnel requ'd */ - sav->tdb_xform->xf_type == XF_IP4 || /* ditto */ -#endif - (dst->sa.sa_family == AF_INET && /* Proxy */ - dst->sin.sin_addr.s_addr != INADDR_ANY && - dst->sin.sin_addr.s_addr != ip->ip_dst.s_addr)) { - struct mbuf *mp; - - /* Fix IPv4 header checksum and length */ - if (m->m_len < sizeof (struct ip) && - (m = m_pullup(m, sizeof (struct ip))) == NULL) { - error = ENOBUFS; - goto bad; - } - ip = mtod(m, struct ip *); - ip->ip_len = htons(m->m_pkthdr.len); - ip->ip_sum = 0; -#ifdef _IP_VHL - if (ip->ip_vhl == IP_VHL_BORING) - ip->ip_sum = in_cksum_hdr(ip); - else - ip->ip_sum = in_cksum(m, - _IP_VHL_HL(ip->ip_vhl) << 2); -#else - ip->ip_sum = in_cksum(m, ip->ip_hl << 2); -#endif - - /* Encapsulate the packet */ - error = ipip_output(m, isr, &mp, 0, 0); - if (mp == NULL && !error) { - /* Should never happen. */ - DPRINTF(("%s: ipip_output returns no mbuf and " - "no error!", __func__)); - error = EFAULT; - } - if (error) { - if (mp) { - /* XXX: Should never happen! */ - m_freem(mp); - } - m = NULL; /* ipip_output() already freed it */ - goto bad; - } - m = mp, mp = NULL; - /* - * ipip_output clears IP_DF in the new header. If - * we need to propagate IP_DF from the outer header, - * then we have to do it here. - * - * XXX shouldn't assume what ipip_output does. - */ - if (dst->sa.sa_family == AF_INET && setdf) { - if (m->m_len < sizeof (struct ip) && - (m = m_pullup(m, sizeof (struct ip))) == NULL) { - error = ENOBUFS; - goto bad; - } - ip = mtod(m, struct ip *); - ip->ip_off = ntohs(ip->ip_off); - ip->ip_off |= IP_DF; - ip->ip_off = htons(ip->ip_off); - } + error = ipsec_encap(&m, &sav->sah->saidx); + if (error != 0) { + DPRINTF(("%s: encapsulation for SA %s->%s " + "SPI 0x%08x failed with error %d\n", __func__, + ipsec_address(&sav->sah->saidx.src, sbuf, + sizeof(sbuf)), + ipsec_address(&sav->sah->saidx.dst, dbuf, + sizeof(dbuf)), ntohl(sav->spi), error)); + /* XXXAE: IPSEC_OSTAT_INC(tunnel); */ + goto bad; } } -#ifdef DEV_ENC - /* pass the mbuf to enc0 for bpf processing */ - ipsec_bpf(m, sav, AF_INET, ENC_OUT|ENC_AFTER); - /* pass the mbuf to enc0 for packet filtering */ - if ((error = ipsec_filter(&m, PFIL_OUT, ENC_OUT|ENC_AFTER)) != 0) + IPSEC_INIT_CTX(&ctx, &m, sav, dst->sa.sa_family, IPSEC_ENC_AFTER); + if ((error = ipsec_run_hhooks(&ctx, HHOOK_TYPE_IPSEC_OUT)) != 0) goto bad; -#endif - /* - * Dispatch to the appropriate IPsec transform logic. The - * packet will be returned for transmission after crypto - * processing, etc. are completed. For encapsulation we - * bypass this call because of the explicit call done above - * (necessary to deal with IP_DF handling for IPv4). - * - * NB: m & sav are ``passed to caller'' who's reponsible for - * for reclaiming their resources. - */ - if (sav->tdb_xform->xf_type != XF_IP4) { + switch(dst->sa.sa_family) { +#ifdef INET + case AF_INET: + { + struct ip *ip; ip = mtod(m, struct ip *); i = ip->ip_hl << 2; off = offsetof(struct ip, ip_p); - error = (*sav->tdb_xform->xf_output)(m, isr, NULL, i, off); - } else { - error = ipsec_process_done(m, isr); + } + break; +#endif /* AF_INET */ + case AF_INET6: + i = sizeof(struct ip6_hdr); + off = offsetof(struct ip6_hdr, ip6_nxt); + break; + default: + DPRINTF(("%s: unsupported protocol family %u\n", + __func__, dst->sa.sa_family)); + error = EPFNOSUPPORT; + IPSEC_OSTAT_INC(sav->sah->saidx.proto, nopf); + goto bad; } - IPSECREQUEST_UNLOCK(isr); - return error; + error = (*sav->tdb_xform->xf_output)(m, sp, sav, idx, i, off); + if (error != 0) { + key_freesav(&sav); + key_freesp(&sp); + } + return (error); bad: - if (isr) - IPSECREQUEST_UNLOCK(isr); - if (m) + IPSEC6STAT_INC(ips_out_inval); + if (m != NULL) m_freem(m); - return error; + if (sav != NULL) + key_freesav(&sav); + key_freesp(&sp); + return (error); } -#endif -#ifdef INET6 -/* - * Chop IP6 header from the payload. - */ -static struct mbuf * -ipsec6_splithdr(struct mbuf *m) +int +ipsec6_process_packet(struct mbuf *m, struct secpolicy *sp, + struct inpcb *inp) { - struct mbuf *mh; - struct ip6_hdr *ip6; - int hlen; - IPSEC_ASSERT(m->m_len >= sizeof (struct ip6_hdr), - ("first mbuf too short, len %u", m->m_len)); - ip6 = mtod(m, struct ip6_hdr *); - hlen = sizeof(struct ip6_hdr); - if (m->m_len > hlen) { - MGETHDR(mh, M_DONTWAIT, MT_DATA); - if (!mh) { + return (ipsec6_perform_request(m, sp, 0)); +} + +static int +ipsec6_common_output(struct mbuf *m, struct inpcb *inp, int forwarding) +{ + struct secpolicy *sp; + int error; + + /* Lookup for the corresponding outbound security policy */ + sp = ipsec6_checkpolicy(m, inp, &error); + if (sp == NULL) { + if (error == -EINVAL) { + /* Discarded by policy. */ m_freem(m); - return NULL; + return (EACCES); + } + return (0); /* No IPsec required. */ + } + + if (!forwarding) { + /* + * Do delayed checksums now because we send before + * this is done in the normal processing path. + */ + if (m->m_pkthdr.csum_flags & CSUM_DELAY_DATA_IPV6) { + in6_delayed_cksum(m, m->m_pkthdr.len - + sizeof(struct ip6_hdr), sizeof(struct ip6_hdr)); + m->m_pkthdr.csum_flags &= ~CSUM_DELAY_DATA_IPV6; } - M_MOVE_PKTHDR(mh, m); - MH_ALIGN(mh, hlen); - m->m_len -= hlen; - m->m_data += hlen; - mh->m_next = m; - m = mh; - m->m_len = hlen; - bcopy((caddr_t)ip6, mtod(m, caddr_t), hlen); - } else if (m->m_len < hlen) { - m = m_pullup(m, hlen); - if (!m) - return NULL; +#ifdef SCTP + if (m->m_pkthdr.csum_flags & CSUM_SCTP_IPV6) { + sctp_delayed_cksum(m, sizeof(struct ip6_hdr)); + m->m_pkthdr.csum_flags &= ~CSUM_SCTP_IPV6; + } +#endif + } + /* NB: callee frees mbuf and releases reference to SP */ + error = ipsec6_process_packet(m, sp, inp); + if (error == EJUSTRETURN) { + /* + * We had a SP with a level of 'use' and no SA. We + * will just continue to process the packet without + * IPsec processing and return without error. + */ + return (0); } - return m; + if (error == 0) + return (EINPROGRESS); /* consumed by IPsec */ + return (error); } /* - * IPsec output logic for IPv6, transport mode. + * IPSEC_OUTPUT() method implementation for IPv6. + * 0 - no IPsec handling needed + * other values - mbuf consumed by IPsec. */ int -ipsec6_output_trans( - struct ipsec_output_state *state, - u_char *nexthdrp, - struct mbuf *mprev, - struct secpolicy *sp, - int flags, - int *tun) +ipsec6_output(struct mbuf *m, struct inpcb *inp) { - struct ipsecrequest *isr; - struct secasindex saidx; - int error = 0; - struct mbuf *m; - - IPSEC_ASSERT(state != NULL, ("null state")); - IPSEC_ASSERT(state->m != NULL, ("null m")); - IPSEC_ASSERT(nexthdrp != NULL, ("null nexthdrp")); - IPSEC_ASSERT(mprev != NULL, ("null mprev")); - IPSEC_ASSERT(sp != NULL, ("null sp")); - IPSEC_ASSERT(tun != NULL, ("null tun")); - - KEYDEBUG(KEYDEBUG_IPSEC_DATA, - printf("%s: applied SP\n", __func__); - kdebug_secpolicy(sp)); - - isr = sp->req; - if (isr->saidx.mode == IPSEC_MODE_TUNNEL) { - /* the rest will be handled by ipsec6_output_tunnel() */ - *tun = 1; /* need tunnel-mode processing */ - return 0; - } - *tun = 0; - m = state->m; - - IPSECREQUEST_LOCK(isr); /* insure SA contents don't change */ - isr = ipsec_nextisr(m, isr, AF_INET6, &saidx, &error); - if (isr == NULL) { - if (error != 0) { -#ifdef notdef - /* XXX should notification be done for all errors ? */ - /* - * Notify the fact that the packet is discarded - * to ourselves. I believe this is better than - * just silently discarding. (jinmei@kame.net) - * XXX: should we restrict the error to TCP packets? - * XXX: should we directly notify sockets via - * pfctlinputs? - */ - icmp6_error(m, ICMP6_DST_UNREACH, - ICMP6_DST_UNREACH_ADMIN, 0); - m = NULL; /* NB: icmp6_error frees mbuf */ -#endif - goto bad; - } - return EJUSTRETURN; - } + /* + * If the packet is resubmitted to ip_output (e.g. after + * AH, ESP, etc. processing), there will be a tag to bypass + * the lookup and related policy checking. + */ + if (m_tag_find(m, PACKET_TAG_IPSEC_OUT_DONE, NULL) != NULL) + return (0); - error = (*isr->sav->tdb_xform->xf_output)(m, isr, NULL, - sizeof (struct ip6_hdr), - offsetof(struct ip6_hdr, - ip6_nxt)); - IPSECREQUEST_UNLOCK(isr); - return error; -bad: - if (isr) - IPSECREQUEST_UNLOCK(isr); - if (m) - m_freem(m); - state->m = NULL; - return error; + return (ipsec6_common_output(m, inp, 0)); } -static int -ipsec6_encapsulate(struct mbuf *m, struct secasvar *sav) +/* + * IPSEC_FORWARD() method implementation for IPv6. + * 0 - no IPsec handling needed + * other values - mbuf consumed by IPsec. + */ +int +ipsec6_forward(struct mbuf *m) { - struct ip6_hdr *oip6; - struct ip6_hdr *ip6; - size_t plen; - - /* can't tunnel between different AFs */ - if (sav->sah->saidx.src.sa.sa_family != AF_INET6 || - sav->sah->saidx.dst.sa.sa_family != AF_INET6) { - m_freem(m); - return EINVAL; - } - IPSEC_ASSERT(m->m_len == sizeof (struct ip6_hdr), - ("mbuf wrong size; len %u", m->m_len)); - /* - * grow the mbuf to accomodate the new IPv6 header. + * Check if this packet has an active inbound SP and needs to be + * dropped instead of forwarded. */ - plen = m->m_pkthdr.len; - if (M_LEADINGSPACE(m->m_next) < sizeof(struct ip6_hdr)) { - struct mbuf *n; - MGET(n, M_DONTWAIT, MT_DATA); - if (!n) { - m_freem(m); - return ENOBUFS; - } - n->m_len = sizeof(struct ip6_hdr); - n->m_next = m->m_next; - m->m_next = n; - m->m_pkthdr.len += sizeof(struct ip6_hdr); - oip6 = mtod(n, struct ip6_hdr *); - } else { - m->m_next->m_len += sizeof(struct ip6_hdr); - m->m_next->m_data -= sizeof(struct ip6_hdr); - m->m_pkthdr.len += sizeof(struct ip6_hdr); - oip6 = mtod(m->m_next, struct ip6_hdr *); - } - ip6 = mtod(m, struct ip6_hdr *); - bcopy((caddr_t)ip6, (caddr_t)oip6, sizeof(struct ip6_hdr)); - - /* Fake link-local scope-class addresses */ - if (IN6_IS_SCOPE_LINKLOCAL(&oip6->ip6_src)) - oip6->ip6_src.s6_addr16[1] = 0; - if (IN6_IS_SCOPE_LINKLOCAL(&oip6->ip6_dst)) - oip6->ip6_dst.s6_addr16[1] = 0; - - /* construct new IPv6 header. see RFC 2401 5.1.2.2 */ - /* ECN consideration. */ - ip6_ecn_ingress(V_ip6_ipsec_ecn, &ip6->ip6_flow, &oip6->ip6_flow); - if (plen < IPV6_MAXPACKET - sizeof(struct ip6_hdr)) - ip6->ip6_plen = htons(plen); - else { - /* ip6->ip6_plen will be updated in ip6_output() */ + if (ipsec6_in_reject(m, NULL) != 0) { + m_freem(m); + return (EACCES); } - ip6->ip6_nxt = IPPROTO_IPV6; - ip6->ip6_src = sav->sah->saidx.src.sin6.sin6_addr; - ip6->ip6_dst = sav->sah->saidx.dst.sin6.sin6_addr; - ip6->ip6_hlim = IPV6_DEFHLIM; - - /* XXX Should ip6_src be updated later ? */ - - return 0; + return (ipsec6_common_output(m, NULL, 1)); } +#endif /* INET6 */ -/* - * IPsec output logic for IPv6, tunnel mode. - */ int -ipsec6_output_tunnel(struct ipsec_output_state *state, struct secpolicy *sp, int flags) +ipsec_process_done(struct mbuf *m, struct secpolicy *sp, struct secasvar *sav, + u_int idx) { - struct ip6_hdr *ip6; - struct ipsecrequest *isr; - struct secasindex saidx; + struct xform_history *xh; + struct secasindex *saidx; + struct m_tag *mtag; int error; - struct sockaddr_in6 *dst6; - struct mbuf *m; - IPSEC_ASSERT(state != NULL, ("null state")); - IPSEC_ASSERT(state->m != NULL, ("null m")); - IPSEC_ASSERT(sp != NULL, ("null sp")); - - KEYDEBUG(KEYDEBUG_IPSEC_DATA, - printf("%s: applied SP\n", __func__); - kdebug_secpolicy(sp)); + saidx = &sav->sah->saidx; + switch (saidx->dst.sa.sa_family) { +#ifdef INET + case AF_INET: + /* Fix the header length, for AH processing. */ + mtod(m, struct ip *)->ip_len = htons(m->m_pkthdr.len); + break; +#endif /* INET */ +#ifdef INET6 + case AF_INET6: + /* Fix the header length, for AH processing. */ + if (m->m_pkthdr.len < sizeof (struct ip6_hdr)) { + error = ENXIO; + goto bad; + } + if (m->m_pkthdr.len - sizeof (struct ip6_hdr) > IPV6_MAXPACKET) { + /* No jumbogram support. */ + error = ENXIO; /*?*/ + goto bad; + } + mtod(m, struct ip6_hdr *)->ip6_plen = + htons(m->m_pkthdr.len - sizeof(struct ip6_hdr)); + break; +#endif /* INET6 */ + default: + DPRINTF(("%s: unknown protocol family %u\n", __func__, + saidx->dst.sa.sa_family)); + error = ENXIO; + goto bad; + } - m = state->m; /* - * transport mode ipsec (before the 1st tunnel mode) is already - * processed by ipsec6_output_trans(). + * Add a record of what we've done to the packet. */ - for (isr = sp->req; isr; isr = isr->next) { - if (isr->saidx.mode == IPSEC_MODE_TUNNEL) - break; - } - - IPSECREQUEST_LOCK(isr); /* insure SA contents don't change */ - isr = ipsec_nextisr(m, isr, AF_INET6, &saidx, &error); - if (isr == NULL) { - if (error != 0) - goto bad; - return EJUSTRETURN; + mtag = m_tag_get(PACKET_TAG_IPSEC_OUT_DONE, sizeof(*xh), M_NOWAIT); + if (mtag == NULL) { + DPRINTF(("%s: could not get packet tag\n", __func__)); + error = ENOMEM; + goto bad; } -#ifdef DEV_ENC - encif->if_opackets++; - encif->if_obytes += m->m_pkthdr.len; + xh = (struct xform_history *)(mtag + 1); + xh->dst = saidx->dst; + xh->proto = saidx->proto; + xh->mode = saidx->mode; + xh->spi = sav->spi; + m_tag_prepend(m, mtag); - /* pass the mbuf to enc0 for bpf processing */ - ipsec_bpf(m, isr->sav, AF_INET6, ENC_OUT|ENC_BEFORE); - /* pass the mbuf to enc0 for packet filtering */ - if ((error = ipsec_filter(&m, PFIL_OUT, ENC_OUT|ENC_BEFORE)) != 0) - goto bad; -#endif + key_sa_recordxfer(sav, m); /* record data transfer */ /* - * There may be the case that SA status will be changed when - * we are refering to one. So calling splsoftnet(). + * If there's another (bundled) SA to apply, do so. + * Note that this puts a burden on the kernel stack size. + * If this is a problem we'll need to introduce a queue + * to set the packet on so we can unwind the stack before + * doing further processing. */ - if (isr->saidx.mode == IPSEC_MODE_TUNNEL) { - /* - * build IPsec tunnel. - */ - /* XXX should be processed with other familiy */ - if (isr->sav->sah->saidx.src.sa.sa_family != AF_INET6) { - ipseclog((LOG_ERR, "%s: family mismatched between " - "inner and outer, spi=%u\n", __func__, - ntohl(isr->sav->spi))); - IPSEC6STAT_INC(ips_out_inval); - error = EAFNOSUPPORT; - goto bad; - } - - m = ipsec6_splithdr(m); - if (!m) { - IPSEC6STAT_INC(ips_out_nomem); - error = ENOMEM; - goto bad; - } - error = ipsec6_encapsulate(m, isr->sav); - if (error) { - m = NULL; + if (++idx < sp->tcount) { + switch (saidx->dst.sa.sa_family) { +#ifdef INET + case AF_INET: + key_freesav(&sav); + IPSECSTAT_INC(ips_out_bundlesa); + return (ipsec4_perform_request(m, sp, idx)); + /* NOTREACHED */ +#endif +#ifdef INET6 + case AF_INET6: + key_freesav(&sav); + IPSEC6STAT_INC(ips_out_bundlesa); + return (ipsec6_perform_request(m, sp, idx)); + /* NOTREACHED */ +#endif /* INET6 */ + default: + DPRINTF(("%s: unknown protocol family %u\n", __func__, + saidx->dst.sa.sa_family)); + error = EPFNOSUPPORT; goto bad; } - ip6 = mtod(m, struct ip6_hdr *); + } - state->ro = - (struct route *)&isr->sav->sah->route_cache.sin6_route; - state->dst = (struct sockaddr *)&state->ro->ro_dst; - dst6 = (struct sockaddr_in6 *)state->dst; - if (state->ro->ro_rt - && ((state->ro->ro_rt->rt_flags & RTF_UP) == 0 - || !IN6_ARE_ADDR_EQUAL(&dst6->sin6_addr, &ip6->ip6_dst))) { - RTFREE(state->ro->ro_rt); - state->ro->ro_rt = NULL; - } - if (state->ro->ro_rt == NULL) { - bzero(dst6, sizeof(*dst6)); - dst6->sin6_family = AF_INET6; - dst6->sin6_len = sizeof(*dst6); - dst6->sin6_addr = ip6->ip6_dst; - rtalloc_ign_fib(state->ro, 0UL, M_GETFIB(m)); - } - if (state->ro->ro_rt == NULL) { - IP6STAT_INC(ip6s_noroute); - IPSEC6STAT_INC(ips_out_noroute); - error = EHOSTUNREACH; + key_freesp(&sp), sp = NULL; /* Release reference to SP */ +#ifdef INET + /* + * Do UDP encapsulation if SA requires it. + */ + if (sav->natt != NULL) { + error = udp_ipsec_output(m, sav); + if (error != 0) goto bad; - } - - /* adjust state->dst if tunnel endpoint is offlink */ - if (state->ro->ro_rt->rt_flags & RTF_GATEWAY) - state->dst = (struct sockaddr *)state->ro->ro_rt->rt_gateway; } +#endif /* INET */ + /* + * We're done with IPsec processing, transmit the packet using the + * appropriate network protocol (IP or IPv6). + */ + switch (saidx->dst.sa.sa_family) { +#ifdef INET + case AF_INET: + key_freesav(&sav); + return ip_output(m, NULL, NULL, IP_RAWOUTPUT, NULL, NULL); +#endif /* INET */ +#ifdef INET6 + case AF_INET6: + key_freesav(&sav); + return ip6_output(m, NULL, NULL, 0, NULL, NULL, NULL); +#endif /* INET6 */ + } + panic("ipsec_process_done"); +bad: + m_freem(m); + key_freesav(&sav); + if (sp != NULL) + key_freesp(&sp); + return (error); +} - m = ipsec6_splithdr(m); - if (!m) { - IPSEC6STAT_INC(ips_out_nomem); - error = ENOMEM; - goto bad; +/* + * ipsec_prepend() is optimized version of M_PREPEND(). + * ipsec_encap() is called by IPsec output routine for tunnel mode SA. + * It is expected that after IP encapsulation some IPsec transform will + * be performed. Each IPsec transform inserts its variable length header + * just after outer IP header using m_makespace(). If given mbuf has not + * enough free space at the beginning, we allocate new mbuf and reserve + * some space at the beginning and at the end. + * This helps avoid allocating of new mbuf and data copying in m_makespace(), + * we place outer header in the middle of mbuf's data with reserved leading + * and trailing space: + * [ LEADINGSPACE ][ Outer IP header ][ TRAILINGSPACE ] + * LEADINGSPACE will be used to add ethernet header, TRAILINGSPACE will + * be used to inject AH/ESP/IPCOMP header. + */ +#define IPSEC_TRAILINGSPACE (sizeof(struct udphdr) +/* NAT-T */ \ + max(sizeof(struct newesp) + EALG_MAX_BLOCK_LEN, /* ESP + IV */ \ + sizeof(struct newah) + HASH_MAX_LEN /* AH + ICV */)) +static struct mbuf * +ipsec_prepend(struct mbuf *m, int len, int how) +{ + struct mbuf *n; + + M_ASSERTPKTHDR(m); + IPSEC_ASSERT(len < MHLEN, ("wrong length")); + if (M_LEADINGSPACE(m) >= len) { + /* No need to allocate new mbuf. */ + m->m_data -= len; + m->m_len += len; + m->m_pkthdr.len += len; + return (m); } - ip6 = mtod(m, struct ip6_hdr *); + n = m_gethdr(how, m->m_type); + if (n == NULL) { + m_freem(m); + return (NULL); + } + m_move_pkthdr(n, m); + n->m_next = m; + if (len + IPSEC_TRAILINGSPACE < M_SIZE(n)) + m_align(n, len + IPSEC_TRAILINGSPACE); + n->m_len = len; + n->m_pkthdr.len += len; + return (n); +} -#ifdef DEV_ENC - /* pass the mbuf to enc0 for bpf processing */ - ipsec_bpf(m, isr->sav, AF_INET6, ENC_OUT|ENC_AFTER); - /* pass the mbuf to enc0 for packet filtering */ - if ((error = ipsec_filter(&m, PFIL_OUT, ENC_OUT|ENC_AFTER)) != 0) - goto bad; +static int +ipsec_encap(struct mbuf **mp, struct secasindex *saidx) +{ +#ifdef INET6 + struct ip6_hdr *ip6; #endif + struct ip *ip; + int setdf; + uint8_t itos, proto; - error = (*isr->sav->tdb_xform->xf_output)(m, isr, NULL, - sizeof (struct ip6_hdr), - offsetof(struct ip6_hdr, ip6_nxt)); - IPSECREQUEST_UNLOCK(isr); - return error; -bad: - if (isr) - IPSECREQUEST_UNLOCK(isr); - if (m) - m_freem(m); - state->m = NULL; - return error; + ip = mtod(*mp, struct ip *); + switch (ip->ip_v) { +#ifdef INET + case IPVERSION: + proto = IPPROTO_IPIP; + /* + * Collect IP_DF state from the inner header + * and honor system-wide control of how to handle it. + */ + switch (V_ip4_ipsec_dfbit) { + case 0: /* clear in outer header */ + case 1: /* set in outer header */ + setdf = V_ip4_ipsec_dfbit; + break; + default:/* propagate to outer header */ + setdf = (ip->ip_off & htons(IP_DF)) != 0; + } + itos = ip->ip_tos; + break; +#endif +#ifdef INET6 + case (IPV6_VERSION >> 4): + proto = IPPROTO_IPV6; + ip6 = mtod(*mp, struct ip6_hdr *); + itos = (ntohl(ip6->ip6_flow) >> 20) & 0xff; + setdf = V_ip4_ipsec_dfbit ? 1: 0; + /* scoped address handling */ + in6_clearscope(&ip6->ip6_src); + in6_clearscope(&ip6->ip6_dst); + break; +#endif + default: + return (EAFNOSUPPORT); + } + switch (saidx->dst.sa.sa_family) { +#ifdef INET + case AF_INET: + if (saidx->src.sa.sa_family != AF_INET || + saidx->src.sin.sin_addr.s_addr == INADDR_ANY || + saidx->dst.sin.sin_addr.s_addr == INADDR_ANY) + return (EINVAL); + *mp = ipsec_prepend(*mp, sizeof(struct ip), M_NOWAIT); + if (*mp == NULL) + return (ENOBUFS); + ip = mtod(*mp, struct ip *); + ip->ip_v = IPVERSION; + ip->ip_hl = sizeof(struct ip) >> 2; + ip->ip_p = proto; + ip->ip_len = htons((*mp)->m_pkthdr.len); + ip->ip_ttl = V_ip_defttl; + ip->ip_sum = 0; + ip->ip_off = setdf ? htons(IP_DF): 0; + ip->ip_src = saidx->src.sin.sin_addr; + ip->ip_dst = saidx->dst.sin.sin_addr; + ip_ecn_ingress(V_ip4_ipsec_ecn, &ip->ip_tos, &itos); + ip_fillid(ip); + break; +#endif /* INET */ +#ifdef INET6 + case AF_INET6: + if (saidx->src.sa.sa_family != AF_INET6 || + IN6_IS_ADDR_UNSPECIFIED(&saidx->src.sin6.sin6_addr) || + IN6_IS_ADDR_UNSPECIFIED(&saidx->dst.sin6.sin6_addr)) + return (EINVAL); + *mp = ipsec_prepend(*mp, sizeof(struct ip6_hdr), M_NOWAIT); + if (*mp == NULL) + return (ENOBUFS); + ip6 = mtod(*mp, struct ip6_hdr *); + ip6->ip6_flow = 0; + ip6->ip6_vfc = IPV6_VERSION; + ip6->ip6_hlim = V_ip6_defhlim; + ip6->ip6_nxt = proto; + ip6->ip6_dst = saidx->dst.sin6.sin6_addr; + /* For link-local address embed scope zone id */ + if (IN6_IS_SCOPE_LINKLOCAL(&ip6->ip6_dst)) + ip6->ip6_dst.s6_addr16[1] = + htons(saidx->dst.sin6.sin6_scope_id & 0xffff); + ip6->ip6_src = saidx->src.sin6.sin6_addr; + if (IN6_IS_SCOPE_LINKLOCAL(&ip6->ip6_src)) + ip6->ip6_src.s6_addr16[1] = + htons(saidx->src.sin6.sin6_scope_id & 0xffff); + ip6->ip6_plen = htons((*mp)->m_pkthdr.len - sizeof(*ip6)); + ip_ecn_ingress(V_ip6_ipsec_ecn, &proto, &itos); + ip6->ip6_flow |= htonl((uint32_t)proto << 20); + break; +#endif /* INET6 */ + default: + return (EAFNOSUPPORT); + } + (*mp)->m_flags &= ~(M_BCAST | M_MCAST); + return (0); } -#endif /*INET6*/ + diff --git a/freebsd/sys/netipsec/ipsec_pcb.c b/freebsd/sys/netipsec/ipsec_pcb.c new file mode 100644 index 00000000..21a8182d --- /dev/null +++ b/freebsd/sys/netipsec/ipsec_pcb.c @@ -0,0 +1,481 @@ +#include <machine/rtems-bsd-kernel-space.h> + +/*- + * Copyright (c) 2016 Andrey V. Elsukov <ae@FreeBSD.org> + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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> +__FBSDID("$FreeBSD$"); + +#include <rtems/bsd/local/opt_inet.h> +#include <rtems/bsd/local/opt_inet6.h> +#include <rtems/bsd/local/opt_ipsec.h> + +#include <rtems/bsd/sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <rtems/bsd/sys/lock.h> +#include <sys/malloc.h> +#include <sys/mbuf.h> +#include <sys/priv.h> +#include <sys/socket.h> +#include <sys/sockopt.h> +#include <sys/syslog.h> +#include <sys/proc.h> + +#include <netinet/in.h> +#include <netinet/in_pcb.h> + +#include <netipsec/ipsec.h> +#include <netipsec/ipsec6.h> +#include <netipsec/ipsec_support.h> +#include <netipsec/key.h> +#include <netipsec/key_debug.h> + +MALLOC_DEFINE(M_IPSEC_INPCB, "inpcbpolicy", "inpcb-resident ipsec policy"); + +static void +ipsec_setsockaddrs_inpcb(struct inpcb *inp, union sockaddr_union *src, + union sockaddr_union *dst, u_int dir) +{ + +#ifdef INET6 + if (inp->inp_vflag & INP_IPV6) { + struct sockaddr_in6 *sin6; + + bzero(&src->sin6, sizeof(src->sin6)); + bzero(&dst->sin6, sizeof(dst->sin6)); + src->sin6.sin6_family = AF_INET6; + src->sin6.sin6_len = sizeof(struct sockaddr_in6); + dst->sin6.sin6_family = AF_INET6; + dst->sin6.sin6_len = sizeof(struct sockaddr_in6); + + if (dir == IPSEC_DIR_OUTBOUND) + sin6 = &src->sin6; + else + sin6 = &dst->sin6; + sin6->sin6_addr = inp->in6p_laddr; + sin6->sin6_port = inp->inp_lport; + if (IN6_IS_SCOPE_LINKLOCAL(&inp->in6p_laddr)) { + /* XXXAE: use in6p_zoneid */ + sin6->sin6_addr.s6_addr16[1] = 0; + sin6->sin6_scope_id = ntohs( + inp->in6p_laddr.s6_addr16[1]); + } + + if (dir == IPSEC_DIR_OUTBOUND) + sin6 = &dst->sin6; + else + sin6 = &src->sin6; + sin6->sin6_addr = inp->in6p_faddr; + sin6->sin6_port = inp->inp_fport; + if (IN6_IS_SCOPE_LINKLOCAL(&inp->in6p_faddr)) { + /* XXXAE: use in6p_zoneid */ + sin6->sin6_addr.s6_addr16[1] = 0; + sin6->sin6_scope_id = ntohs( + inp->in6p_faddr.s6_addr16[1]); + } + } +#endif +#ifdef INET + if (inp->inp_vflag & INP_IPV4) { + struct sockaddr_in *sin; + + bzero(&src->sin, sizeof(src->sin)); + bzero(&dst->sin, sizeof(dst->sin)); + src->sin.sin_family = AF_INET; + src->sin.sin_len = sizeof(struct sockaddr_in); + dst->sin.sin_family = AF_INET; + dst->sin.sin_len = sizeof(struct sockaddr_in); + + if (dir == IPSEC_DIR_OUTBOUND) + sin = &src->sin; + else + sin = &dst->sin; + sin->sin_addr = inp->inp_laddr; + sin->sin_port = inp->inp_lport; + + if (dir == IPSEC_DIR_OUTBOUND) + sin = &dst->sin; + else + sin = &src->sin; + sin->sin_addr = inp->inp_faddr; + sin->sin_port = inp->inp_fport; + } +#endif +} + +void +ipsec_setspidx_inpcb(struct inpcb *inp, struct secpolicyindex *spidx, + u_int dir) +{ + + ipsec_setsockaddrs_inpcb(inp, &spidx->src, &spidx->dst, dir); +#ifdef INET6 + if (inp->inp_vflag & INP_IPV6) { + spidx->prefs = sizeof(struct in6_addr) << 3; + spidx->prefd = sizeof(struct in6_addr) << 3; + } +#endif +#ifdef INET + if (inp->inp_vflag & INP_IPV4) { + spidx->prefs = sizeof(struct in_addr) << 3; + spidx->prefd = sizeof(struct in_addr) << 3; + } +#endif + spidx->ul_proto = IPPROTO_TCP; /* XXX: currently only TCP uses this */ + spidx->dir = dir; + KEYDBG(IPSEC_DUMP, + printf("%s: ", __func__); kdebug_secpolicyindex(spidx, NULL)); +} + +/* Initialize PCB policy. */ +int +ipsec_init_pcbpolicy(struct inpcb *inp) +{ + + IPSEC_ASSERT(inp != NULL, ("null inp")); + IPSEC_ASSERT(inp->inp_sp == NULL, ("inp_sp already initialized")); + + inp->inp_sp = malloc(sizeof(struct inpcbpolicy), M_IPSEC_INPCB, + M_NOWAIT | M_ZERO); + if (inp->inp_sp == NULL) + return (ENOBUFS); + return (0); +} + +/* Delete PCB policy. */ +int +ipsec_delete_pcbpolicy(struct inpcb *inp) +{ + + if (inp->inp_sp == NULL) + return (0); + + if (inp->inp_sp->flags & INP_INBOUND_POLICY) + key_freesp(&inp->inp_sp->sp_in); + + if (inp->inp_sp->flags & INP_OUTBOUND_POLICY) + key_freesp(&inp->inp_sp->sp_out); + + free(inp->inp_sp, M_IPSEC_INPCB); + inp->inp_sp = NULL; + return (0); +} + +/* Deep-copy a policy in PCB. */ +static struct secpolicy * +ipsec_deepcopy_pcbpolicy(struct secpolicy *src) +{ + struct secpolicy *dst; + int i; + + if (src == NULL) + return (NULL); + + IPSEC_ASSERT(src->state == IPSEC_SPSTATE_PCB, ("SP isn't PCB")); + + dst = key_newsp(); + if (dst == NULL) + return (NULL); + + /* spidx is not copied here */ + dst->policy = src->policy; + dst->state = src->state; + dst->priority = src->priority; + /* Do not touch the refcnt field. */ + + /* Copy IPsec request chain. */ + for (i = 0; i < src->tcount; i++) { + dst->req[i] = ipsec_newisr(); + if (dst->req[i] == NULL) { + key_freesp(&dst); + return (NULL); + } + bcopy(src->req[i], dst->req[i], sizeof(struct ipsecrequest)); + dst->tcount++; + } + KEYDBG(IPSEC_DUMP, + printf("%s: copied SP(%p) -> SP(%p)\n", __func__, src, dst); + kdebug_secpolicy(dst)); + return (dst); +} + +/* + * Copy IPsec policy from old INPCB into new. + * It is expected that new INPCB has not configured policies. + */ +int +ipsec_copy_pcbpolicy(struct inpcb *old, struct inpcb *new) +{ + struct secpolicy *sp; + + /* + * old->inp_sp can be NULL if PCB was created when an IPsec + * support was unavailable. This is not an error, we don't have + * policies in this PCB, so nothing to copy. + */ + if (old->inp_sp == NULL) + return (0); + + IPSEC_ASSERT(new->inp_sp != NULL, ("new inp_sp is NULL")); + IPSEC_ASSERT((new->inp_sp->flags & ( + INP_INBOUND_POLICY | INP_OUTBOUND_POLICY)) == 0, + ("new PCB already has configured policies")); + INP_WLOCK_ASSERT(new); + INP_LOCK_ASSERT(old); + + if (old->inp_sp->flags & INP_INBOUND_POLICY) { + sp = ipsec_deepcopy_pcbpolicy(old->inp_sp->sp_in); + if (sp == NULL) + return (ENOBUFS); + ipsec_setspidx_inpcb(new, &sp->spidx, IPSEC_DIR_INBOUND); + new->inp_sp->sp_in = sp; + new->inp_sp->flags |= INP_INBOUND_POLICY; + } + if (old->inp_sp->flags & INP_OUTBOUND_POLICY) { + sp = ipsec_deepcopy_pcbpolicy(old->inp_sp->sp_out); + if (sp == NULL) + return (ENOBUFS); + ipsec_setspidx_inpcb(new, &sp->spidx, IPSEC_DIR_OUTBOUND); + new->inp_sp->sp_out = sp; + new->inp_sp->flags |= INP_OUTBOUND_POLICY; + } + return (0); +} + +static int +ipsec_set_pcbpolicy(struct inpcb *inp, struct ucred *cred, + void *request, size_t len) +{ + struct sadb_x_policy *xpl; + struct secpolicy **spp, *newsp; + int error, flags; + + xpl = (struct sadb_x_policy *)request; + /* Select direction. */ + switch (xpl->sadb_x_policy_dir) { + case IPSEC_DIR_INBOUND: + case IPSEC_DIR_OUTBOUND: + break; + default: + ipseclog((LOG_ERR, "%s: invalid direction=%u\n", __func__, + xpl->sadb_x_policy_dir)); + return (EINVAL); + } + /* + * Privileged sockets are allowed to set own security policy + * and configure IPsec bypass. Unprivileged sockets only can + * have ENTRUST policy. + */ + switch (xpl->sadb_x_policy_type) { + case IPSEC_POLICY_IPSEC: + case IPSEC_POLICY_BYPASS: + if (cred != NULL && + priv_check_cred(cred, PRIV_NETINET_IPSEC, 0) != 0) + return (EACCES); + /* Allocate new SP entry. */ + newsp = key_msg2sp(xpl, len, &error); + if (newsp == NULL) + return (error); + newsp->state = IPSEC_SPSTATE_PCB; + newsp->spidx.ul_proto = IPSEC_ULPROTO_ANY; +#ifdef INET + if (inp->inp_vflag & INP_IPV4) { + newsp->spidx.src.sin.sin_family = + newsp->spidx.dst.sin.sin_family = AF_INET; + newsp->spidx.src.sin.sin_len = + newsp->spidx.dst.sin.sin_len = + sizeof(struct sockaddr_in); + } +#endif +#ifdef INET6 + if (inp->inp_vflag & INP_IPV6) { + newsp->spidx.src.sin6.sin6_family = + newsp->spidx.dst.sin6.sin6_family = AF_INET6; + newsp->spidx.src.sin6.sin6_len = + newsp->spidx.dst.sin6.sin6_len = + sizeof(struct sockaddr_in6); + } +#endif + break; + case IPSEC_POLICY_ENTRUST: + /* We just use NULL pointer for ENTRUST policy */ + newsp = NULL; + break; + default: + /* Other security policy types aren't allowed for PCB */ + return (EINVAL); + } + + INP_WLOCK(inp); + if (xpl->sadb_x_policy_dir == IPSEC_DIR_INBOUND) { + spp = &inp->inp_sp->sp_in; + flags = INP_INBOUND_POLICY; + } else { + spp = &inp->inp_sp->sp_out; + flags = INP_OUTBOUND_POLICY; + } + /* Clear old SP and set new SP. */ + if (*spp != NULL) + key_freesp(spp); + *spp = newsp; + KEYDBG(IPSEC_DUMP, + printf("%s: new SP(%p)\n", __func__, newsp)); + if (newsp == NULL) + inp->inp_sp->flags &= ~flags; + else { + inp->inp_sp->flags |= flags; + KEYDBG(IPSEC_DUMP, kdebug_secpolicy(newsp)); + } + INP_WUNLOCK(inp); + return (0); +} + +static int +ipsec_get_pcbpolicy(struct inpcb *inp, void *request, size_t *len) +{ + struct sadb_x_policy *xpl; + struct secpolicy *sp; + int error, flags; + + xpl = (struct sadb_x_policy *)request; + + INP_RLOCK(inp); + flags = inp->inp_sp->flags; + /* Select direction. */ + switch (xpl->sadb_x_policy_dir) { + case IPSEC_DIR_INBOUND: + sp = inp->inp_sp->sp_in; + flags &= INP_INBOUND_POLICY; + break; + case IPSEC_DIR_OUTBOUND: + sp = inp->inp_sp->sp_out; + flags &= INP_OUTBOUND_POLICY; + break; + default: + INP_RUNLOCK(inp); + ipseclog((LOG_ERR, "%s: invalid direction=%u\n", __func__, + xpl->sadb_x_policy_dir)); + return (EINVAL); + } + + if (flags == 0) { + /* Return ENTRUST policy */ + INP_RUNLOCK(inp); + xpl->sadb_x_policy_exttype = SADB_X_EXT_POLICY; + xpl->sadb_x_policy_type = IPSEC_POLICY_ENTRUST; + xpl->sadb_x_policy_id = 0; + xpl->sadb_x_policy_priority = 0; + xpl->sadb_x_policy_len = PFKEY_UNIT64(sizeof(*xpl)); + *len = sizeof(*xpl); + return (0); + } + + IPSEC_ASSERT(sp != NULL, + ("sp is NULL, but flags is 0x%04x", inp->inp_sp->flags)); + + key_addref(sp); + INP_RUNLOCK(inp); + error = key_sp2msg(sp, request, len); + key_freesp(&sp); + if (error == EINVAL) + return (error); + /* + * We return "success", but user should check *len. + * *len will be set to size of valid data and + * sadb_x_policy_len will contain needed size. + */ + return (0); +} + +/* Handle socket option control request for PCB */ +static int +ipsec_control_pcbpolicy(struct inpcb *inp, struct sockopt *sopt) +{ + void *optdata; + size_t optlen; + int error; + + if (inp->inp_sp == NULL) + return (ENOPROTOOPT); + + /* Limit maximum request size to PAGE_SIZE */ + optlen = sopt->sopt_valsize; + if (optlen < sizeof(struct sadb_x_policy) || optlen > PAGE_SIZE) + return (EINVAL); + + optdata = malloc(optlen, M_TEMP, sopt->sopt_td ? M_WAITOK: M_NOWAIT); + if (optdata == NULL) + return (ENOBUFS); + /* + * We need a hint from the user, what policy is requested - input + * or output? User should specify it in the buffer, even for + * setsockopt(). + */ + error = sooptcopyin(sopt, optdata, optlen, optlen); + if (error == 0) { + if (sopt->sopt_dir == SOPT_SET) + error = ipsec_set_pcbpolicy(inp, + sopt->sopt_td ? sopt->sopt_td->td_ucred: NULL, + optdata, optlen); + else { + error = ipsec_get_pcbpolicy(inp, optdata, &optlen); + if (error == 0) + error = sooptcopyout(sopt, optdata, optlen); + } + } + free(optdata, M_TEMP); + return (error); +} + +#ifdef INET +/* + * IPSEC_PCBCTL() method implementation for IPv4. + */ +int +ipsec4_pcbctl(struct inpcb *inp, struct sockopt *sopt) +{ + + if (sopt->sopt_name != IP_IPSEC_POLICY) + return (ENOPROTOOPT); + return (ipsec_control_pcbpolicy(inp, sopt)); +} +#endif + +#ifdef INET6 +/* + * IPSEC_PCBCTL() method implementation for IPv6. + */ +int +ipsec6_pcbctl(struct inpcb *inp, struct sockopt *sopt) +{ + + if (sopt->sopt_name != IPV6_IPSEC_POLICY) + return (ENOPROTOOPT); + return (ipsec_control_pcbpolicy(inp, sopt)); +} +#endif + diff --git a/freebsd/sys/netipsec/ipsec_support.h b/freebsd/sys/netipsec/ipsec_support.h new file mode 100644 index 00000000..b72aee20 --- /dev/null +++ b/freebsd/sys/netipsec/ipsec_support.h @@ -0,0 +1,190 @@ +/*- + * Copyright (c) 2016 Andrey V. Elsukov <ae@FreeBSD.org> + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + * + * $FreeBSD$ + */ + +#ifndef _NETIPSEC_IPSEC_SUPPORT_H_ +#define _NETIPSEC_IPSEC_SUPPORT_H_ + +#ifdef _KERNEL +#if defined(IPSEC) || defined(IPSEC_SUPPORT) +struct mbuf; +struct inpcb; +struct tcphdr; +struct sockopt; +struct sockaddr; +struct ipsec_support; +struct tcpmd5_support; + +size_t ipsec_hdrsiz_inpcb(struct inpcb *); +int ipsec_init_pcbpolicy(struct inpcb *); +int ipsec_delete_pcbpolicy(struct inpcb *); +int ipsec_copy_pcbpolicy(struct inpcb *, struct inpcb *); + +struct ipsec_methods { + int (*input)(struct mbuf *, int, int); + int (*check_policy)(const struct mbuf *, struct inpcb *); + int (*forward)(struct mbuf *); + int (*output)(struct mbuf *, struct inpcb *); + int (*pcbctl)(struct inpcb *, struct sockopt *); + size_t (*hdrsize)(struct inpcb *); + int (*capability)(struct mbuf *, u_int); + int (*ctlinput)(int, struct sockaddr *, void *); + + int (*udp_input)(struct mbuf *, int, int); + int (*udp_pcbctl)(struct inpcb *, struct sockopt *); +}; +#define IPSEC_CAP_OPERABLE 1 +#define IPSEC_CAP_BYPASS_FILTER 2 + +struct tcpmd5_methods { + int (*input)(struct mbuf *, struct tcphdr *, u_char *); + int (*output)(struct mbuf *, struct tcphdr *, u_char *); + int (*pcbctl)(struct inpcb *, struct sockopt *); +}; + +#define IPSEC_MODULE_ENABLED 0x0001 +#define IPSEC_ENABLED(proto) \ + ((proto ## _ipsec_support)->enabled & IPSEC_MODULE_ENABLED) +#define TCPMD5_ENABLED() IPSEC_ENABLED(tcp) + +#ifdef TCP_SIGNATURE +/* TCP-MD5 build in the kernel */ +struct tcpmd5_support { + const u_int enabled; + const struct tcpmd5_methods * const methods; +}; +extern const struct tcpmd5_support * const tcp_ipsec_support; + +#define TCPMD5_INPUT(m, ...) \ + (*tcp_ipsec_support->methods->input)(m, __VA_ARGS__) +#define TCPMD5_OUTPUT(m, ...) \ + (*tcp_ipsec_support->methods->output)(m, __VA_ARGS__) +#define TCPMD5_PCBCTL(inp, sopt) \ + (*tcp_ipsec_support->methods->pcbctl)(inp, sopt) +#elif defined(IPSEC_SUPPORT) +/* TCP-MD5 build as module */ +struct tcpmd5_support { + volatile u_int enabled; + const struct tcpmd5_methods * volatile methods; +}; +extern struct tcpmd5_support * const tcp_ipsec_support; + +void tcpmd5_support_enable(const struct tcpmd5_methods * const); +void tcpmd5_support_disable(void); + +int tcpmd5_kmod_pcbctl(struct tcpmd5_support * const, struct inpcb *, + struct sockopt *); +int tcpmd5_kmod_input(struct tcpmd5_support * const, struct mbuf *, + struct tcphdr *, u_char *); +int tcpmd5_kmod_output(struct tcpmd5_support * const, struct mbuf *, + struct tcphdr *, u_char *); +#define TCPMD5_INPUT(m, ...) \ + tcpmd5_kmod_input(tcp_ipsec_support, m, __VA_ARGS__) +#define TCPMD5_OUTPUT(m, ...) \ + tcpmd5_kmod_output(tcp_ipsec_support, m, __VA_ARGS__) +#define TCPMD5_PCBCTL(inp, sopt) \ + tcpmd5_kmod_pcbctl(tcp_ipsec_support, inp, sopt) +#endif + +#endif /* IPSEC || IPSEC_SUPPORT */ + +#if defined(IPSEC) +struct ipsec_support { + const u_int enabled; + const struct ipsec_methods * const methods; +}; +extern const struct ipsec_support * const ipv4_ipsec_support; +extern const struct ipsec_support * const ipv6_ipsec_support; + +#define IPSEC_INPUT(proto, m, ...) \ + (*(proto ## _ipsec_support)->methods->input)(m, __VA_ARGS__) +#define IPSEC_CHECK_POLICY(proto, m, ...) \ + (*(proto ## _ipsec_support)->methods->check_policy)(m, __VA_ARGS__) +#define IPSEC_FORWARD(proto, m) \ + (*(proto ## _ipsec_support)->methods->forward)(m) +#define IPSEC_OUTPUT(proto, m, ...) \ + (*(proto ## _ipsec_support)->methods->output)(m, __VA_ARGS__) +#define IPSEC_PCBCTL(proto, inp, sopt) \ + (*(proto ## _ipsec_support)->methods->pcbctl)(inp, sopt) +#define IPSEC_CAPS(proto, m, ...) \ + (*(proto ## _ipsec_support)->methods->capability)(m, __VA_ARGS__) +#define IPSEC_HDRSIZE(proto, inp) \ + (*(proto ## _ipsec_support)->methods->hdrsize)(inp) + +#define UDPENCAP_INPUT(m, ...) \ + (*ipv4_ipsec_support->methods->udp_input)(m, __VA_ARGS__) +#define UDPENCAP_PCBCTL(inp, sopt) \ + (*ipv4_ipsec_support->methods->udp_pcbctl)(inp, sopt) + +#elif defined(IPSEC_SUPPORT) +struct ipsec_support { + volatile u_int enabled; + const struct ipsec_methods * volatile methods; +}; +extern struct ipsec_support * const ipv4_ipsec_support; +extern struct ipsec_support * const ipv6_ipsec_support; + +void ipsec_support_enable(struct ipsec_support * const, + const struct ipsec_methods * const); +void ipsec_support_disable(struct ipsec_support * const); + +int ipsec_kmod_input(struct ipsec_support * const, struct mbuf *, int, int); +int ipsec_kmod_check_policy(struct ipsec_support * const, struct mbuf *, + struct inpcb *); +int ipsec_kmod_forward(struct ipsec_support * const, struct mbuf *); +int ipsec_kmod_output(struct ipsec_support * const, struct mbuf *, + struct inpcb *); +int ipsec_kmod_pcbctl(struct ipsec_support * const, struct inpcb *, + struct sockopt *); +int ipsec_kmod_capability(struct ipsec_support * const, struct mbuf *, u_int); +size_t ipsec_kmod_hdrsize(struct ipsec_support * const, struct inpcb *); +int ipsec_kmod_udp_input(struct ipsec_support * const, struct mbuf *, int, int); +int ipsec_kmod_udp_pcbctl(struct ipsec_support * const, struct inpcb *, + struct sockopt *); + +#define UDPENCAP_INPUT(m, ...) \ + ipsec_kmod_udp_input(ipv4_ipsec_support, m, __VA_ARGS__) +#define UDPENCAP_PCBCTL(inp, sopt) \ + ipsec_kmod_udp_pcbctl(ipv4_ipsec_support, inp, sopt) + +#define IPSEC_INPUT(proto, ...) \ + ipsec_kmod_input(proto ## _ipsec_support, __VA_ARGS__) +#define IPSEC_CHECK_POLICY(proto, ...) \ + ipsec_kmod_check_policy(proto ## _ipsec_support, __VA_ARGS__) +#define IPSEC_FORWARD(proto, ...) \ + ipsec_kmod_forward(proto ## _ipsec_support, __VA_ARGS__) +#define IPSEC_OUTPUT(proto, ...) \ + ipsec_kmod_output(proto ## _ipsec_support, __VA_ARGS__) +#define IPSEC_PCBCTL(proto, ...) \ + ipsec_kmod_pcbctl(proto ## _ipsec_support, __VA_ARGS__) +#define IPSEC_CAPS(proto, ...) \ + ipsec_kmod_capability(proto ## _ipsec_support, __VA_ARGS__) +#define IPSEC_HDRSIZE(proto, ...) \ + ipsec_kmod_hdrsize(proto ## _ipsec_support, __VA_ARGS__) +#endif /* IPSEC_SUPPORT */ +#endif /* _KERNEL */ +#endif /* _NETIPSEC_IPSEC_SUPPORT_H_ */ diff --git a/freebsd/sys/netipsec/key.c b/freebsd/sys/netipsec/key.c index 6619c6db..5d3a612d 100644 --- a/freebsd/sys/netipsec/key.c +++ b/freebsd/sys/netipsec/key.c @@ -44,12 +44,14 @@ #include <rtems/bsd/sys/param.h> #include <sys/systm.h> #include <sys/kernel.h> +#include <sys/fnv_hash.h> #include <rtems/bsd/sys/lock.h> #include <sys/mutex.h> #include <sys/mbuf.h> #include <sys/domain.h> #include <sys/protosw.h> #include <sys/malloc.h> +#include <sys/rmlock.h> #include <sys/socket.h> #include <sys/socketvar.h> #include <sys/sysctl.h> @@ -59,15 +61,18 @@ #include <sys/refcount.h> #include <sys/syslog.h> +#include <vm/uma.h> + #include <net/if.h> -#include <net/route.h> -#include <net/raw_cb.h> +#include <net/if_var.h> #include <net/vnet.h> +#include <net/raw_cb.h> #include <netinet/in.h> #include <netinet/in_systm.h> #include <netinet/ip.h> #include <netinet/in_var.h> +#include <netinet/udp.h> #ifdef INET6 #include <netinet/ip6.h> @@ -75,13 +80,6 @@ #include <netinet6/ip6_var.h> #endif /* INET6 */ -#if defined(INET) || defined(INET6) -#include <netinet/in_pcb.h> -#endif -#ifdef INET6 -#include <netinet6/in6_pcb.h> -#endif /* INET6 */ - #include <net/pfkeyv2.h> #include <netipsec/keydb.h> #include <netipsec/key.h> @@ -94,7 +92,7 @@ #endif #include <netipsec/xform.h> - +#include <machine/in_cksum.h> #include <machine/stdarg.h> /* randomness */ @@ -142,28 +140,118 @@ static VNET_DEFINE(int, key_preferred_oldsa) = 1; static VNET_DEFINE(u_int32_t, acq_seq) = 0; #define V_acq_seq VNET(acq_seq) - /* SPD */ -static VNET_DEFINE(LIST_HEAD(_sptree, secpolicy), sptree[IPSEC_DIR_MAX]); +static VNET_DEFINE(uint32_t, sp_genid) = 0; +#define V_sp_genid VNET(sp_genid) + +/* SPD */ +TAILQ_HEAD(secpolicy_queue, secpolicy); +LIST_HEAD(secpolicy_list, secpolicy); +static VNET_DEFINE(struct secpolicy_queue, sptree[IPSEC_DIR_MAX]); +static VNET_DEFINE(struct secpolicy_queue, sptree_ifnet[IPSEC_DIR_MAX]); +static struct rmlock sptree_lock; #define V_sptree VNET(sptree) -static struct mtx sptree_lock; -#define SPTREE_LOCK_INIT() \ - mtx_init(&sptree_lock, "sptree", \ - "fast ipsec security policy database", MTX_DEF) -#define SPTREE_LOCK_DESTROY() mtx_destroy(&sptree_lock) -#define SPTREE_LOCK() mtx_lock(&sptree_lock) -#define SPTREE_UNLOCK() mtx_unlock(&sptree_lock) -#define SPTREE_LOCK_ASSERT() mtx_assert(&sptree_lock, MA_OWNED) - -static VNET_DEFINE(LIST_HEAD(_sahtree, secashead), sahtree); /* SAD */ +#define V_sptree_ifnet VNET(sptree_ifnet) +#define SPTREE_LOCK_INIT() rm_init(&sptree_lock, "sptree") +#define SPTREE_LOCK_DESTROY() rm_destroy(&sptree_lock) +#define SPTREE_RLOCK_TRACKER struct rm_priotracker sptree_tracker +#define SPTREE_RLOCK() rm_rlock(&sptree_lock, &sptree_tracker) +#define SPTREE_RUNLOCK() rm_runlock(&sptree_lock, &sptree_tracker) +#define SPTREE_RLOCK_ASSERT() rm_assert(&sptree_lock, RA_RLOCKED) +#define SPTREE_WLOCK() rm_wlock(&sptree_lock) +#define SPTREE_WUNLOCK() rm_wunlock(&sptree_lock) +#define SPTREE_WLOCK_ASSERT() rm_assert(&sptree_lock, RA_WLOCKED) +#define SPTREE_UNLOCK_ASSERT() rm_assert(&sptree_lock, RA_UNLOCKED) + +/* Hash table for lookup SP using unique id */ +static VNET_DEFINE(struct secpolicy_list *, sphashtbl); +static VNET_DEFINE(u_long, sphash_mask); +#define V_sphashtbl VNET(sphashtbl) +#define V_sphash_mask VNET(sphash_mask) + +#define SPHASH_NHASH_LOG2 7 +#define SPHASH_NHASH (1 << SPHASH_NHASH_LOG2) +#define SPHASH_HASHVAL(id) (key_u32hash(id) & V_sphash_mask) +#define SPHASH_HASH(id) &V_sphashtbl[SPHASH_HASHVAL(id)] + +/* SAD */ +TAILQ_HEAD(secashead_queue, secashead); +LIST_HEAD(secashead_list, secashead); +static VNET_DEFINE(struct secashead_queue, sahtree); +static struct rmlock sahtree_lock; #define V_sahtree VNET(sahtree) -static struct mtx sahtree_lock; -#define SAHTREE_LOCK_INIT() \ - mtx_init(&sahtree_lock, "sahtree", \ - "fast ipsec security association database", MTX_DEF) -#define SAHTREE_LOCK_DESTROY() mtx_destroy(&sahtree_lock) -#define SAHTREE_LOCK() mtx_lock(&sahtree_lock) -#define SAHTREE_UNLOCK() mtx_unlock(&sahtree_lock) -#define SAHTREE_LOCK_ASSERT() mtx_assert(&sahtree_lock, MA_OWNED) +#define SAHTREE_LOCK_INIT() rm_init(&sahtree_lock, "sahtree") +#define SAHTREE_LOCK_DESTROY() rm_destroy(&sahtree_lock) +#define SAHTREE_RLOCK_TRACKER struct rm_priotracker sahtree_tracker +#define SAHTREE_RLOCK() rm_rlock(&sahtree_lock, &sahtree_tracker) +#define SAHTREE_RUNLOCK() rm_runlock(&sahtree_lock, &sahtree_tracker) +#define SAHTREE_RLOCK_ASSERT() rm_assert(&sahtree_lock, RA_RLOCKED) +#define SAHTREE_WLOCK() rm_wlock(&sahtree_lock) +#define SAHTREE_WUNLOCK() rm_wunlock(&sahtree_lock) +#define SAHTREE_WLOCK_ASSERT() rm_assert(&sahtree_lock, RA_WLOCKED) +#define SAHTREE_UNLOCK_ASSERT() rm_assert(&sahtree_lock, RA_UNLOCKED) + +/* Hash table for lookup in SAD using SA addresses */ +static VNET_DEFINE(struct secashead_list *, sahaddrhashtbl); +static VNET_DEFINE(u_long, sahaddrhash_mask); +#define V_sahaddrhashtbl VNET(sahaddrhashtbl) +#define V_sahaddrhash_mask VNET(sahaddrhash_mask) + +#define SAHHASH_NHASH_LOG2 7 +#define SAHHASH_NHASH (1 << SAHHASH_NHASH_LOG2) +#define SAHADDRHASH_HASHVAL(saidx) \ + (key_saidxhash(saidx) & V_sahaddrhash_mask) +#define SAHADDRHASH_HASH(saidx) \ + &V_sahaddrhashtbl[SAHADDRHASH_HASHVAL(saidx)] + +/* Hash table for lookup in SAD using SPI */ +LIST_HEAD(secasvar_list, secasvar); +static VNET_DEFINE(struct secasvar_list *, savhashtbl); +static VNET_DEFINE(u_long, savhash_mask); +#define V_savhashtbl VNET(savhashtbl) +#define V_savhash_mask VNET(savhash_mask) +#define SAVHASH_NHASH_LOG2 7 +#define SAVHASH_NHASH (1 << SAVHASH_NHASH_LOG2) +#define SAVHASH_HASHVAL(spi) (key_u32hash(spi) & V_savhash_mask) +#define SAVHASH_HASH(spi) &V_savhashtbl[SAVHASH_HASHVAL(spi)] + +static uint32_t +key_saidxhash(const struct secasindex *saidx) +{ + uint32_t hval; + + hval = fnv_32_buf(&saidx->proto, sizeof(saidx->proto), + FNV1_32_INIT); + switch (saidx->dst.sa.sa_family) { +#ifdef INET + case AF_INET: + hval = fnv_32_buf(&saidx->src.sin.sin_addr, + sizeof(in_addr_t), hval); + hval = fnv_32_buf(&saidx->dst.sin.sin_addr, + sizeof(in_addr_t), hval); + break; +#endif +#ifdef INET6 + case AF_INET6: + hval = fnv_32_buf(&saidx->src.sin6.sin6_addr, + sizeof(struct in6_addr), hval); + hval = fnv_32_buf(&saidx->dst.sin6.sin6_addr, + sizeof(struct in6_addr), hval); + break; +#endif + default: + hval = 0; + ipseclog((LOG_DEBUG, "%s: unknown address family %d", + __func__, saidx->dst.sa.sa_family)); + } + return (hval); +} + +static uint32_t +key_u32hash(uint32_t val) +{ + + return (fnv_32_buf(&val, sizeof(val), FNV1_32_INIT)); +} /* registed list */ static VNET_DEFINE(LIST_HEAD(_regtree, secreg), regtree[SADB_SATYPE_MAX + 1]); @@ -176,16 +264,40 @@ static struct mtx regtree_lock; #define REGTREE_UNLOCK() mtx_unlock(®tree_lock) #define REGTREE_LOCK_ASSERT() mtx_assert(®tree_lock, MA_OWNED) -static VNET_DEFINE(LIST_HEAD(_acqtree, secacq), acqtree); /* acquiring list */ +/* Acquiring list */ +LIST_HEAD(secacq_list, secacq); +static VNET_DEFINE(struct secacq_list, acqtree); #define V_acqtree VNET(acqtree) static struct mtx acq_lock; #define ACQ_LOCK_INIT() \ - mtx_init(&acq_lock, "acqtree", "fast ipsec acquire list", MTX_DEF) + mtx_init(&acq_lock, "acqtree", "ipsec SA acquiring list", MTX_DEF) #define ACQ_LOCK_DESTROY() mtx_destroy(&acq_lock) #define ACQ_LOCK() mtx_lock(&acq_lock) #define ACQ_UNLOCK() mtx_unlock(&acq_lock) #define ACQ_LOCK_ASSERT() mtx_assert(&acq_lock, MA_OWNED) +/* Hash table for lookup in ACQ list using SA addresses */ +static VNET_DEFINE(struct secacq_list *, acqaddrhashtbl); +static VNET_DEFINE(u_long, acqaddrhash_mask); +#define V_acqaddrhashtbl VNET(acqaddrhashtbl) +#define V_acqaddrhash_mask VNET(acqaddrhash_mask) + +/* Hash table for lookup in ACQ list using SEQ number */ +static VNET_DEFINE(struct secacq_list *, acqseqhashtbl); +static VNET_DEFINE(u_long, acqseqhash_mask); +#define V_acqseqhashtbl VNET(acqseqhashtbl) +#define V_acqseqhash_mask VNET(acqseqhash_mask) + +#define ACQHASH_NHASH_LOG2 7 +#define ACQHASH_NHASH (1 << ACQHASH_NHASH_LOG2) +#define ACQADDRHASH_HASHVAL(saidx) \ + (key_saidxhash(saidx) & V_acqaddrhash_mask) +#define ACQSEQHASH_HASHVAL(seq) \ + (key_u32hash(seq) & V_acqseqhash_mask) +#define ACQADDRHASH_HASH(saidx) \ + &V_acqaddrhashtbl[ACQADDRHASH_HASHVAL(saidx)] +#define ACQSEQHASH_HASH(seq) \ + &V_acqseqhashtbl[ACQSEQHASH_HASHVAL(seq)] /* SP acquiring list */ static VNET_DEFINE(LIST_HEAD(_spacqtree, secspacq), spacqtree); #define V_spacqtree VNET(spacqtree) @@ -198,22 +310,6 @@ static struct mtx spacq_lock; #define SPACQ_UNLOCK() mtx_unlock(&spacq_lock) #define SPACQ_LOCK_ASSERT() mtx_assert(&spacq_lock, MA_OWNED) -/* search order for SAs */ -static const u_int saorder_state_valid_prefer_old[] = { - SADB_SASTATE_DYING, SADB_SASTATE_MATURE, -}; -static const u_int saorder_state_valid_prefer_new[] = { - SADB_SASTATE_MATURE, SADB_SASTATE_DYING, -}; -static const u_int saorder_state_alive[] = { - /* except DEAD */ - SADB_SASTATE_MATURE, SADB_SASTATE_DYING, SADB_SASTATE_LARVAL -}; -static const u_int saorder_state_any[] = { - SADB_SASTATE_MATURE, SADB_SASTATE_DYING, - SADB_SASTATE_LARVAL, SADB_SASTATE_DEAD -}; - static const int minsize[] = { sizeof(struct sadb_msg), /* SADB_EXT_RESERVED */ sizeof(struct sadb_sa), /* SADB_EXT_SA */ @@ -241,7 +337,12 @@ static const int minsize[] = { sizeof(struct sadb_address), /* SADB_X_EXT_NAT_T_OAI */ sizeof(struct sadb_address), /* SADB_X_EXT_NAT_T_OAR */ sizeof(struct sadb_x_nat_t_frag),/* SADB_X_EXT_NAT_T_FRAG */ + sizeof(struct sadb_x_sa_replay), /* SADB_X_EXT_SA_REPLAY */ + sizeof(struct sadb_address), /* SADB_X_EXT_NEW_ADDRESS_SRC */ + sizeof(struct sadb_address), /* SADB_X_EXT_NEW_ADDRESS_DST */ }; +_Static_assert(sizeof(minsize)/sizeof(int) == SADB_EXT_MAX + 1, "minsize size mismatch"); + static const int maxsize[] = { sizeof(struct sadb_msg), /* SADB_EXT_RESERVED */ sizeof(struct sadb_sa), /* SADB_EXT_SA */ @@ -269,7 +370,23 @@ static const int maxsize[] = { 0, /* SADB_X_EXT_NAT_T_OAI */ 0, /* SADB_X_EXT_NAT_T_OAR */ sizeof(struct sadb_x_nat_t_frag),/* SADB_X_EXT_NAT_T_FRAG */ + sizeof(struct sadb_x_sa_replay), /* SADB_X_EXT_SA_REPLAY */ + 0, /* SADB_X_EXT_NEW_ADDRESS_SRC */ + 0, /* SADB_X_EXT_NEW_ADDRESS_DST */ }; +_Static_assert(sizeof(maxsize)/sizeof(int) == SADB_EXT_MAX + 1, "minsize size mismatch"); + +/* + * Internal values for SA flags: + * SADB_X_EXT_F_CLONED means that SA was cloned by key_updateaddresses, + * thus we will not free the most of SA content in key_delsav(). + */ +#define SADB_X_EXT_F_CLONED 0x80000000 + +#define SADB_CHECKLEN(_mhp, _ext) \ + ((_mhp)->extlen[(_ext)] < minsize[(_ext)] || (maxsize[(_ext)] != 0 && \ + ((_mhp)->extlen[(_ext)] > maxsize[(_ext)]))) +#define SADB_CHECKHDR(_mhp, _ext) ((_mhp)->ext[(_ext)] == NULL) static VNET_DEFINE(int, ipsec_esp_keymin) = 256; static VNET_DEFINE(int, ipsec_esp_auth) = 0; @@ -279,88 +396,75 @@ static VNET_DEFINE(int, ipsec_ah_keymin) = 128; #define V_ipsec_esp_auth VNET(ipsec_esp_auth) #define V_ipsec_ah_keymin VNET(ipsec_ah_keymin) -#ifdef SYSCTL_DECL -SYSCTL_DECL(_net_key); +#ifdef IPSEC_DEBUG +VNET_DEFINE(int, ipsec_debug) = 1; +#else +VNET_DEFINE(int, ipsec_debug) = 0; +#endif + +#ifdef INET +SYSCTL_DECL(_net_inet_ipsec); +SYSCTL_INT(_net_inet_ipsec, IPSECCTL_DEBUG, debug, + CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(ipsec_debug), 0, + "Enable IPsec debugging output when set."); +#endif +#ifdef INET6 +SYSCTL_DECL(_net_inet6_ipsec6); +SYSCTL_INT(_net_inet6_ipsec6, IPSECCTL_DEBUG, debug, + CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(ipsec_debug), 0, + "Enable IPsec debugging output when set."); #endif -SYSCTL_VNET_INT(_net_key, KEYCTL_DEBUG_LEVEL, debug, - CTLFLAG_RW, &VNET_NAME(key_debug_level), 0, ""); +SYSCTL_DECL(_net_key); +SYSCTL_INT(_net_key, KEYCTL_DEBUG_LEVEL, debug, + CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(key_debug_level), 0, ""); /* max count of trial for the decision of spi value */ -SYSCTL_VNET_INT(_net_key, KEYCTL_SPI_TRY, spi_trycnt, - CTLFLAG_RW, &VNET_NAME(key_spi_trycnt), 0, ""); +SYSCTL_INT(_net_key, KEYCTL_SPI_TRY, spi_trycnt, + CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(key_spi_trycnt), 0, ""); /* minimum spi value to allocate automatically. */ -SYSCTL_VNET_INT(_net_key, KEYCTL_SPI_MIN_VALUE, - spi_minval, CTLFLAG_RW, &VNET_NAME(key_spi_minval), 0, ""); +SYSCTL_INT(_net_key, KEYCTL_SPI_MIN_VALUE, spi_minval, + CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(key_spi_minval), 0, ""); /* maximun spi value to allocate automatically. */ -SYSCTL_VNET_INT(_net_key, KEYCTL_SPI_MAX_VALUE, - spi_maxval, CTLFLAG_RW, &VNET_NAME(key_spi_maxval), 0, ""); +SYSCTL_INT(_net_key, KEYCTL_SPI_MAX_VALUE, spi_maxval, + CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(key_spi_maxval), 0, ""); /* interval to initialize randseed */ -SYSCTL_VNET_INT(_net_key, KEYCTL_RANDOM_INT, - int_random, CTLFLAG_RW, &VNET_NAME(key_int_random), 0, ""); +SYSCTL_INT(_net_key, KEYCTL_RANDOM_INT, int_random, + CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(key_int_random), 0, ""); /* lifetime for larval SA */ -SYSCTL_VNET_INT(_net_key, KEYCTL_LARVAL_LIFETIME, - larval_lifetime, CTLFLAG_RW, &VNET_NAME(key_larval_lifetime), 0, ""); +SYSCTL_INT(_net_key, KEYCTL_LARVAL_LIFETIME, larval_lifetime, + CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(key_larval_lifetime), 0, ""); /* counter for blocking to send SADB_ACQUIRE to IKEd */ -SYSCTL_VNET_INT(_net_key, KEYCTL_BLOCKACQ_COUNT, - blockacq_count, CTLFLAG_RW, &VNET_NAME(key_blockacq_count), 0, ""); +SYSCTL_INT(_net_key, KEYCTL_BLOCKACQ_COUNT, blockacq_count, + CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(key_blockacq_count), 0, ""); /* lifetime for blocking to send SADB_ACQUIRE to IKEd */ -SYSCTL_VNET_INT(_net_key, KEYCTL_BLOCKACQ_LIFETIME, - blockacq_lifetime, CTLFLAG_RW, &VNET_NAME(key_blockacq_lifetime), 0, ""); +SYSCTL_INT(_net_key, KEYCTL_BLOCKACQ_LIFETIME, blockacq_lifetime, + CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(key_blockacq_lifetime), 0, ""); /* ESP auth */ -SYSCTL_VNET_INT(_net_key, KEYCTL_ESP_AUTH, esp_auth, - CTLFLAG_RW, &VNET_NAME(ipsec_esp_auth), 0, ""); +SYSCTL_INT(_net_key, KEYCTL_ESP_AUTH, esp_auth, + CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(ipsec_esp_auth), 0, ""); /* minimum ESP key length */ -SYSCTL_VNET_INT(_net_key, KEYCTL_ESP_KEYMIN, - esp_keymin, CTLFLAG_RW, &VNET_NAME(ipsec_esp_keymin), 0, ""); +SYSCTL_INT(_net_key, KEYCTL_ESP_KEYMIN, esp_keymin, + CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(ipsec_esp_keymin), 0, ""); /* minimum AH key length */ -SYSCTL_VNET_INT(_net_key, KEYCTL_AH_KEYMIN, ah_keymin, - CTLFLAG_RW, &VNET_NAME(ipsec_ah_keymin), 0, ""); +SYSCTL_INT(_net_key, KEYCTL_AH_KEYMIN, ah_keymin, + CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(ipsec_ah_keymin), 0, ""); /* perfered old SA rather than new SA */ -SYSCTL_VNET_INT(_net_key, KEYCTL_PREFERED_OLDSA, - preferred_oldsa, CTLFLAG_RW, &VNET_NAME(key_preferred_oldsa), 0, ""); +SYSCTL_INT(_net_key, KEYCTL_PREFERED_OLDSA, preferred_oldsa, + CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(key_preferred_oldsa), 0, ""); #define __LIST_CHAINED(elm) \ (!((elm)->chain.le_next == NULL && (elm)->chain.le_prev == NULL)) -#define LIST_INSERT_TAIL(head, elm, type, field) \ -do {\ - struct type *curelm = LIST_FIRST(head); \ - if (curelm == NULL) {\ - LIST_INSERT_HEAD(head, elm, field); \ - } else { \ - while (LIST_NEXT(curelm, field)) \ - curelm = LIST_NEXT(curelm, field);\ - LIST_INSERT_AFTER(curelm, elm, field);\ - }\ -} while (0) - -#define KEY_CHKSASTATE(head, sav, name) \ -do { \ - if ((head) != (sav)) { \ - ipseclog((LOG_DEBUG, "%s: state mismatched (TREE=%d SA=%d)\n", \ - (name), (head), (sav))); \ - continue; \ - } \ -} while (0) - -#define KEY_CHKSPDIR(head, sp, name) \ -do { \ - if ((head) != (sp)) { \ - ipseclog((LOG_DEBUG, "%s: direction mismatched (TREE=%d SP=%d), " \ - "anyway continue.\n", \ - (name), (head), (sp))); \ - } \ -} while (0) MALLOC_DEFINE(M_IPSEC_SA, "secasvar", "ipsec security association"); MALLOC_DEFINE(M_IPSEC_SAH, "sahead", "ipsec sa head"); @@ -370,6 +474,17 @@ MALLOC_DEFINE(M_IPSEC_MISC, "ipsec-misc", "ipsec miscellaneous"); MALLOC_DEFINE(M_IPSEC_SAQ, "ipsec-saq", "ipsec sa acquire"); MALLOC_DEFINE(M_IPSEC_SAR, "ipsec-reg", "ipsec sa acquire"); +static VNET_DEFINE(uma_zone_t, key_lft_zone); +#define V_key_lft_zone VNET(key_lft_zone) + +static LIST_HEAD(xforms_list, xformsw) xforms = LIST_HEAD_INITIALIZER(); +static struct mtx xforms_lock; +#define XFORMS_LOCK_INIT() \ + mtx_init(&xforms_lock, "xforms_list", "IPsec transforms list", MTX_DEF) +#define XFORMS_LOCK_DESTROY() mtx_destroy(&xforms_lock) +#define XFORMS_LOCK() mtx_lock(&xforms_lock) +#define XFORMS_UNLOCK() mtx_unlock(&xforms_lock) + /* * set parameters into secpolicyindex buffer. * Must allocate secpolicyindex buffer passed to this function. @@ -397,6 +512,8 @@ do { \ (idx)->reqid = (r); \ bcopy((s), &(idx)->src, ((const struct sockaddr *)(s))->sa_len); \ bcopy((d), &(idx)->dst, ((const struct sockaddr *)(d))->sa_len); \ + key_porttosaddr(&(idx)->src.sa, 0); \ + key_porttosaddr(&(idx)->dst.sa, 0); \ } while (0) /* key statistics */ @@ -411,176 +528,206 @@ struct sadb_msghdr { int extlen[SADB_EXT_MAX + 1]; }; -static struct secasvar *key_allocsa_policy __P((const struct secasindex *)); -static void key_freesp_so __P((struct secpolicy **)); -static struct secasvar *key_do_allocsa_policy __P((struct secashead *, u_int)); -static void key_delsp __P((struct secpolicy *)); -static struct secpolicy *key_getsp __P((struct secpolicyindex *)); -static void _key_delsp(struct secpolicy *sp); -static struct secpolicy *key_getspbyid __P((u_int32_t)); -static u_int32_t key_newreqid __P((void)); -static struct mbuf *key_gather_mbuf __P((struct mbuf *, - const struct sadb_msghdr *, int, int, ...)); -static int key_spdadd __P((struct socket *, struct mbuf *, - const struct sadb_msghdr *)); -static u_int32_t key_getnewspid __P((void)); -static int key_spddelete __P((struct socket *, struct mbuf *, - const struct sadb_msghdr *)); -static int key_spddelete2 __P((struct socket *, struct mbuf *, - const struct sadb_msghdr *)); -static int key_spdget __P((struct socket *, struct mbuf *, - const struct sadb_msghdr *)); -static int key_spdflush __P((struct socket *, struct mbuf *, - const struct sadb_msghdr *)); -static int key_spddump __P((struct socket *, struct mbuf *, - const struct sadb_msghdr *)); -static struct mbuf *key_setdumpsp __P((struct secpolicy *, - u_int8_t, u_int32_t, u_int32_t)); -static u_int key_getspreqmsglen __P((struct secpolicy *)); -static int key_spdexpire __P((struct secpolicy *)); -static struct secashead *key_newsah __P((struct secasindex *)); -static void key_delsah __P((struct secashead *)); -static struct secasvar *key_newsav __P((struct mbuf *, - const struct sadb_msghdr *, struct secashead *, int *, - const char*, int)); -#define KEY_NEWSAV(m, sadb, sah, e) \ - key_newsav(m, sadb, sah, e, __FILE__, __LINE__) -static void key_delsav __P((struct secasvar *)); -static struct secashead *key_getsah __P((struct secasindex *)); -static struct secasvar *key_checkspidup __P((struct secasindex *, u_int32_t)); -static struct secasvar *key_getsavbyspi __P((struct secashead *, u_int32_t)); -static int key_setsaval __P((struct secasvar *, struct mbuf *, - const struct sadb_msghdr *)); -static int key_mature __P((struct secasvar *)); -static struct mbuf *key_setdumpsa __P((struct secasvar *, u_int8_t, - u_int8_t, u_int32_t, u_int32_t)); -static struct mbuf *key_setsadbmsg __P((u_int8_t, u_int16_t, u_int8_t, - u_int32_t, pid_t, u_int16_t)); -static struct mbuf *key_setsadbsa __P((struct secasvar *)); -static struct mbuf *key_setsadbaddr __P((u_int16_t, - const struct sockaddr *, u_int8_t, u_int16_t)); -#ifdef IPSEC_NAT_T +static struct supported_ealgs { + int sadb_alg; + const struct enc_xform *xform; +} supported_ealgs[] = { + { SADB_EALG_DESCBC, &enc_xform_des }, + { SADB_EALG_3DESCBC, &enc_xform_3des }, + { SADB_X_EALG_AES, &enc_xform_rijndael128 }, + { SADB_X_EALG_BLOWFISHCBC, &enc_xform_blf }, + { SADB_X_EALG_CAST128CBC, &enc_xform_cast5 }, + { SADB_EALG_NULL, &enc_xform_null }, + { SADB_X_EALG_CAMELLIACBC, &enc_xform_camellia }, + { SADB_X_EALG_AESCTR, &enc_xform_aes_icm }, + { SADB_X_EALG_AESGCM16, &enc_xform_aes_nist_gcm }, + { SADB_X_EALG_AESGMAC, &enc_xform_aes_nist_gmac }, +}; + +static struct supported_aalgs { + int sadb_alg; + const struct auth_hash *xform; +} supported_aalgs[] = { + { SADB_X_AALG_NULL, &auth_hash_null }, + { SADB_AALG_MD5HMAC, &auth_hash_hmac_md5 }, + { SADB_AALG_SHA1HMAC, &auth_hash_hmac_sha1 }, + { SADB_X_AALG_RIPEMD160HMAC, &auth_hash_hmac_ripemd_160 }, + { SADB_X_AALG_MD5, &auth_hash_key_md5 }, + { SADB_X_AALG_SHA, &auth_hash_key_sha1 }, + { SADB_X_AALG_SHA2_256, &auth_hash_hmac_sha2_256 }, + { SADB_X_AALG_SHA2_384, &auth_hash_hmac_sha2_384 }, + { SADB_X_AALG_SHA2_512, &auth_hash_hmac_sha2_512 }, + { SADB_X_AALG_AES128GMAC, &auth_hash_nist_gmac_aes_128 }, + { SADB_X_AALG_AES192GMAC, &auth_hash_nist_gmac_aes_192 }, + { SADB_X_AALG_AES256GMAC, &auth_hash_nist_gmac_aes_256 }, +}; + +static struct supported_calgs { + int sadb_alg; + const struct comp_algo *xform; +} supported_calgs[] = { + { SADB_X_CALG_DEFLATE, &comp_algo_deflate }, +}; + +#ifndef IPSEC_DEBUG2 +static struct callout key_timer; +#endif + +static void key_unlink(struct secpolicy *); +static struct secpolicy *key_getsp(struct secpolicyindex *); +static struct secpolicy *key_getspbyid(u_int32_t); +static struct mbuf *key_gather_mbuf(struct mbuf *, + const struct sadb_msghdr *, int, int, ...); +static int key_spdadd(struct socket *, struct mbuf *, + const struct sadb_msghdr *); +static uint32_t key_getnewspid(void); +static int key_spddelete(struct socket *, struct mbuf *, + const struct sadb_msghdr *); +static int key_spddelete2(struct socket *, struct mbuf *, + const struct sadb_msghdr *); +static int key_spdget(struct socket *, struct mbuf *, + const struct sadb_msghdr *); +static int key_spdflush(struct socket *, struct mbuf *, + const struct sadb_msghdr *); +static int key_spddump(struct socket *, struct mbuf *, + const struct sadb_msghdr *); +static struct mbuf *key_setdumpsp(struct secpolicy *, + u_int8_t, u_int32_t, u_int32_t); +static struct mbuf *key_sp2mbuf(struct secpolicy *); +static size_t key_getspreqmsglen(struct secpolicy *); +static int key_spdexpire(struct secpolicy *); +static struct secashead *key_newsah(struct secasindex *); +static void key_freesah(struct secashead **); +static void key_delsah(struct secashead *); +static struct secasvar *key_newsav(const struct sadb_msghdr *, + struct secasindex *, uint32_t, int *); +static void key_delsav(struct secasvar *); +static void key_unlinksav(struct secasvar *); +static struct secashead *key_getsah(struct secasindex *); +static int key_checkspidup(uint32_t); +static struct secasvar *key_getsavbyspi(uint32_t); +static int key_setnatt(struct secasvar *, const struct sadb_msghdr *); +static int key_setsaval(struct secasvar *, const struct sadb_msghdr *); +static int key_updatelifetimes(struct secasvar *, const struct sadb_msghdr *); +static int key_updateaddresses(struct socket *, struct mbuf *, + const struct sadb_msghdr *, struct secasvar *, struct secasindex *); + +static struct mbuf *key_setdumpsa(struct secasvar *, u_int8_t, + u_int8_t, u_int32_t, u_int32_t); +static struct mbuf *key_setsadbmsg(u_int8_t, u_int16_t, u_int8_t, + u_int32_t, pid_t, u_int16_t); +static struct mbuf *key_setsadbsa(struct secasvar *); +static struct mbuf *key_setsadbaddr(u_int16_t, + const struct sockaddr *, u_int8_t, u_int16_t); static struct mbuf *key_setsadbxport(u_int16_t, u_int16_t); static struct mbuf *key_setsadbxtype(u_int16_t); -#endif -static void key_porttosaddr(struct sockaddr *, u_int16_t); -#define KEY_PORTTOSADDR(saddr, port) \ - key_porttosaddr((struct sockaddr *)(saddr), (port)) -static struct mbuf *key_setsadbxsa2 __P((u_int8_t, u_int32_t, u_int32_t)); -static struct mbuf *key_setsadbxpolicy __P((u_int16_t, u_int8_t, - u_int32_t)); -static struct seckey *key_dup_keymsg(const struct sadb_key *, u_int, - struct malloc_type *); +static struct mbuf *key_setsadbxsa2(u_int8_t, u_int32_t, u_int32_t); +static struct mbuf *key_setsadbxsareplay(u_int32_t); +static struct mbuf *key_setsadbxpolicy(u_int16_t, u_int8_t, + u_int32_t, u_int32_t); +static struct seckey *key_dup_keymsg(const struct sadb_key *, size_t, + struct malloc_type *); static struct seclifetime *key_dup_lifemsg(const struct sadb_lifetime *src, - struct malloc_type *type); -#ifdef INET6 -static int key_ismyaddr6 __P((struct sockaddr_in6 *)); -#endif + struct malloc_type *); /* flags for key_cmpsaidx() */ #define CMP_HEAD 1 /* protocol, addresses. */ #define CMP_MODE_REQID 2 /* additionally HEAD, reqid, mode. */ #define CMP_REQID 3 /* additionally HEAD, reaid. */ #define CMP_EXACTLY 4 /* all elements. */ -static int key_cmpsaidx - __P((const struct secasindex *, const struct secasindex *, int)); - -static int key_cmpspidx_exactly - __P((struct secpolicyindex *, struct secpolicyindex *)); -static int key_cmpspidx_withmask - __P((struct secpolicyindex *, struct secpolicyindex *)); -static int key_sockaddrcmp __P((const struct sockaddr *, const struct sockaddr *, int)); -static int key_bbcmp __P((const void *, const void *, u_int)); -static u_int16_t key_satype2proto __P((u_int8_t)); -static u_int8_t key_proto2satype __P((u_int16_t)); - -static int key_getspi __P((struct socket *, struct mbuf *, - const struct sadb_msghdr *)); -static u_int32_t key_do_getnewspi __P((struct sadb_spirange *, - struct secasindex *)); -static int key_update __P((struct socket *, struct mbuf *, - const struct sadb_msghdr *)); -#ifdef IPSEC_DOSEQCHECK -static struct secasvar *key_getsavbyseq __P((struct secashead *, u_int32_t)); -#endif -static int key_add __P((struct socket *, struct mbuf *, - const struct sadb_msghdr *)); -static int key_setident __P((struct secashead *, struct mbuf *, - const struct sadb_msghdr *)); -static struct mbuf *key_getmsgbuf_x1 __P((struct mbuf *, - const struct sadb_msghdr *)); -static int key_delete __P((struct socket *, struct mbuf *, - const struct sadb_msghdr *)); -static int key_get __P((struct socket *, struct mbuf *, - const struct sadb_msghdr *)); - -static void key_getcomb_setlifetime __P((struct sadb_comb *)); -static struct mbuf *key_getcomb_esp __P((void)); -static struct mbuf *key_getcomb_ah __P((void)); -static struct mbuf *key_getcomb_ipcomp __P((void)); -static struct mbuf *key_getprop __P((const struct secasindex *)); - -static int key_acquire __P((const struct secasindex *, struct secpolicy *)); -static struct secacq *key_newacq __P((const struct secasindex *)); -static struct secacq *key_getacq __P((const struct secasindex *)); -static struct secacq *key_getacqbyseq __P((u_int32_t)); -static struct secspacq *key_newspacq __P((struct secpolicyindex *)); -static struct secspacq *key_getspacq __P((struct secpolicyindex *)); -static int key_acquire2 __P((struct socket *, struct mbuf *, - const struct sadb_msghdr *)); -static int key_register __P((struct socket *, struct mbuf *, - const struct sadb_msghdr *)); -static int key_expire __P((struct secasvar *)); -static int key_flush __P((struct socket *, struct mbuf *, - const struct sadb_msghdr *)); -static int key_dump __P((struct socket *, struct mbuf *, - const struct sadb_msghdr *)); -static int key_promisc __P((struct socket *, struct mbuf *, - const struct sadb_msghdr *)); -static int key_senderror __P((struct socket *, struct mbuf *, int)); -static int key_validate_ext __P((const struct sadb_ext *, int)); -static int key_align __P((struct mbuf *, struct sadb_msghdr *)); -static struct mbuf *key_setlifetime(struct seclifetime *src, - u_int16_t exttype); -static struct mbuf *key_setkey(struct seckey *src, u_int16_t exttype); - -#if 0 -static const char *key_getfqdn __P((void)); -static const char *key_getuserfqdn __P((void)); -#endif -static void key_sa_chgstate __P((struct secasvar *, u_int8_t)); -static struct mbuf *key_alloc_mbuf __P((int)); - -static __inline void -sa_initref(struct secasvar *sav) -{ +static int key_cmpsaidx(const struct secasindex *, + const struct secasindex *, int); +static int key_cmpspidx_exactly(struct secpolicyindex *, + struct secpolicyindex *); +static int key_cmpspidx_withmask(struct secpolicyindex *, + struct secpolicyindex *); +static int key_bbcmp(const void *, const void *, u_int); +static uint8_t key_satype2proto(uint8_t); +static uint8_t key_proto2satype(uint8_t); + +static int key_getspi(struct socket *, struct mbuf *, + const struct sadb_msghdr *); +static uint32_t key_do_getnewspi(struct sadb_spirange *, struct secasindex *); +static int key_update(struct socket *, struct mbuf *, + const struct sadb_msghdr *); +static int key_add(struct socket *, struct mbuf *, + const struct sadb_msghdr *); +static int key_setident(struct secashead *, const struct sadb_msghdr *); +static struct mbuf *key_getmsgbuf_x1(struct mbuf *, + const struct sadb_msghdr *); +static int key_delete(struct socket *, struct mbuf *, + const struct sadb_msghdr *); +static int key_delete_all(struct socket *, struct mbuf *, + const struct sadb_msghdr *, struct secasindex *); +static void key_delete_xform(const struct xformsw *); +static int key_get(struct socket *, struct mbuf *, + const struct sadb_msghdr *); + +static void key_getcomb_setlifetime(struct sadb_comb *); +static struct mbuf *key_getcomb_ealg(void); +static struct mbuf *key_getcomb_ah(void); +static struct mbuf *key_getcomb_ipcomp(void); +static struct mbuf *key_getprop(const struct secasindex *); + +static int key_acquire(const struct secasindex *, struct secpolicy *); +static uint32_t key_newacq(const struct secasindex *, int *); +static uint32_t key_getacq(const struct secasindex *, int *); +static int key_acqdone(const struct secasindex *, uint32_t); +static int key_acqreset(uint32_t); +static struct secspacq *key_newspacq(struct secpolicyindex *); +static struct secspacq *key_getspacq(struct secpolicyindex *); +static int key_acquire2(struct socket *, struct mbuf *, + const struct sadb_msghdr *); +static int key_register(struct socket *, struct mbuf *, + const struct sadb_msghdr *); +static int key_expire(struct secasvar *, int); +static int key_flush(struct socket *, struct mbuf *, + const struct sadb_msghdr *); +static int key_dump(struct socket *, struct mbuf *, + const struct sadb_msghdr *); +static int key_promisc(struct socket *, struct mbuf *, + const struct sadb_msghdr *); +static int key_senderror(struct socket *, struct mbuf *, int); +static int key_validate_ext(const struct sadb_ext *, int); +static int key_align(struct mbuf *, struct sadb_msghdr *); +static struct mbuf *key_setlifetime(struct seclifetime *, uint16_t); +static struct mbuf *key_setkey(struct seckey *, uint16_t); +static int xform_init(struct secasvar *, u_short); + +#define DBG_IPSEC_INITREF(t, p) do { \ + refcount_init(&(p)->refcnt, 1); \ + KEYDBG(KEY_STAMP, \ + printf("%s: Initialize refcnt %s(%p) = %u\n", \ + __func__, #t, (p), (p)->refcnt)); \ +} while (0) +#define DBG_IPSEC_ADDREF(t, p) do { \ + refcount_acquire(&(p)->refcnt); \ + KEYDBG(KEY_STAMP, \ + printf("%s: Acquire refcnt %s(%p) -> %u\n", \ + __func__, #t, (p), (p)->refcnt)); \ +} while (0) +#define DBG_IPSEC_DELREF(t, p) do { \ + KEYDBG(KEY_STAMP, \ + printf("%s: Release refcnt %s(%p) -> %u\n", \ + __func__, #t, (p), (p)->refcnt - 1)); \ + refcount_release(&(p)->refcnt); \ +} while (0) - refcount_init(&sav->refcnt, 1); -} -static __inline void -sa_addref(struct secasvar *sav) -{ +#define IPSEC_INITREF(t, p) refcount_init(&(p)->refcnt, 1) +#define IPSEC_ADDREF(t, p) refcount_acquire(&(p)->refcnt) +#define IPSEC_DELREF(t, p) refcount_release(&(p)->refcnt) - refcount_acquire(&sav->refcnt); - IPSEC_ASSERT(sav->refcnt != 0, ("SA refcnt overflow")); -} -static __inline int -sa_delref(struct secasvar *sav) -{ +#define SP_INITREF(p) IPSEC_INITREF(SP, p) +#define SP_ADDREF(p) IPSEC_ADDREF(SP, p) +#define SP_DELREF(p) IPSEC_DELREF(SP, p) - IPSEC_ASSERT(sav->refcnt > 0, ("SA refcnt underflow")); - return (refcount_release(&sav->refcnt)); -} +#define SAH_INITREF(p) IPSEC_INITREF(SAH, p) +#define SAH_ADDREF(p) IPSEC_ADDREF(SAH, p) +#define SAH_DELREF(p) IPSEC_DELREF(SAH, p) -#define SP_ADDREF(p) do { \ - (p)->refcnt++; \ - IPSEC_ASSERT((p)->refcnt != 0, ("SP refcnt overflow")); \ -} while (0) -#define SP_DELREF(p) do { \ - IPSEC_ASSERT((p)->refcnt > 0, ("SP refcnt underflow")); \ - (p)->refcnt--; \ -} while (0) - +#define SAV_INITREF(p) IPSEC_INITREF(SAV, p) +#define SAV_ADDREF(p) IPSEC_ADDREF(SAV, p) +#define SAV_DELREF(p) IPSEC_DELREF(SAV, p) /* * Update the refcnt while holding the SPTREE lock. @@ -588,9 +735,8 @@ sa_delref(struct secasvar *sav) void key_addref(struct secpolicy *sp) { - SPTREE_LOCK(); + SP_ADDREF(sp); - SPTREE_UNLOCK(); } /* @@ -603,60 +749,54 @@ key_havesp(u_int dir) { return (dir == IPSEC_DIR_INBOUND || dir == IPSEC_DIR_OUTBOUND ? - LIST_FIRST(&V_sptree[dir]) != NULL : 1); + TAILQ_FIRST(&V_sptree[dir]) != NULL : 1); } /* %%% IPsec policy management */ /* - * allocating a SP for OUTBOUND or INBOUND packet. - * Must call key_freesp() later. - * OUT: NULL: not found - * others: found and return the pointer. + * Return current SPDB generation. */ -struct secpolicy * -key_allocsp(struct secpolicyindex *spidx, u_int dir, const char* where, int tag) +uint32_t +key_getspgen(void) { - struct secpolicy *sp; - IPSEC_ASSERT(spidx != NULL, ("null spidx")); - IPSEC_ASSERT(dir == IPSEC_DIR_INBOUND || dir == IPSEC_DIR_OUTBOUND, - ("invalid direction %u", dir)); - - KEYDEBUG(KEYDEBUG_IPSEC_STAMP, - printf("DP %s from %s:%u\n", __func__, where, tag)); + return (V_sp_genid); +} - /* get a SP entry */ - KEYDEBUG(KEYDEBUG_IPSEC_DATA, - printf("*** objects\n"); - kdebug_secpolicyindex(spidx)); +void +key_bumpspgen(void) +{ - SPTREE_LOCK(); - LIST_FOREACH(sp, &V_sptree[dir], chain) { - KEYDEBUG(KEYDEBUG_IPSEC_DATA, - printf("*** in SPD\n"); - kdebug_secpolicyindex(&sp->spidx)); + V_sp_genid++; +} - if (sp->state == IPSEC_SPSTATE_DEAD) - continue; - if (key_cmpspidx_withmask(&sp->spidx, spidx)) - goto found; - } - sp = NULL; -found: - if (sp) { - /* sanity check */ - KEY_CHKSPDIR(sp->spidx.dir, dir, __func__); +static int +key_checksockaddrs(struct sockaddr *src, struct sockaddr *dst) +{ - /* found a SPD entry */ - sp->lastused = time_second; - SP_ADDREF(sp); + /* family match */ + if (src->sa_family != dst->sa_family) + return (EINVAL); + /* sa_len match */ + if (src->sa_len != dst->sa_len) + return (EINVAL); + switch (src->sa_family) { +#ifdef INET + case AF_INET: + if (src->sa_len != sizeof(struct sockaddr_in)) + return (EINVAL); + break; +#endif +#ifdef INET6 + case AF_INET6: + if (src->sa_len != sizeof(struct sockaddr_in6)) + return (EINVAL); + break; +#endif + default: + return (EAFNOSUPPORT); } - SPTREE_UNLOCK(); - - KEYDEBUG(KEYDEBUG_IPSEC_STAMP, - printf("DP %s return SP:%p (ID=%u) refcnt %u\n", __func__, - sp, sp ? sp->id : 0, sp ? sp->refcnt : 0)); - return sp; + return (0); } /* @@ -666,577 +806,415 @@ found: * others: found and return the pointer. */ struct secpolicy * -key_allocsp2(u_int32_t spi, - union sockaddr_union *dst, - u_int8_t proto, - u_int dir, - const char* where, int tag) +key_allocsp(struct secpolicyindex *spidx, u_int dir) { + SPTREE_RLOCK_TRACKER; struct secpolicy *sp; - IPSEC_ASSERT(dst != NULL, ("null dst")); + IPSEC_ASSERT(spidx != NULL, ("null spidx")); IPSEC_ASSERT(dir == IPSEC_DIR_INBOUND || dir == IPSEC_DIR_OUTBOUND, ("invalid direction %u", dir)); - KEYDEBUG(KEYDEBUG_IPSEC_STAMP, - printf("DP %s from %s:%u\n", __func__, where, tag)); - - /* get a SP entry */ - KEYDEBUG(KEYDEBUG_IPSEC_DATA, - printf("*** objects\n"); - printf("spi %u proto %u dir %u\n", spi, proto, dir); - kdebug_sockaddr(&dst->sa)); - - SPTREE_LOCK(); - LIST_FOREACH(sp, &V_sptree[dir], chain) { - KEYDEBUG(KEYDEBUG_IPSEC_DATA, - printf("*** in SPD\n"); - kdebug_secpolicyindex(&sp->spidx)); - - if (sp->state == IPSEC_SPSTATE_DEAD) - continue; - /* compare simple values, then dst address */ - if (sp->spidx.ul_proto != proto) - continue; - /* NB: spi's must exist and match */ - if (!sp->req || !sp->req->sav || sp->req->sav->spi != spi) - continue; - if (key_sockaddrcmp(&sp->spidx.dst.sa, &dst->sa, 1) == 0) - goto found; + SPTREE_RLOCK(); + TAILQ_FOREACH(sp, &V_sptree[dir], chain) { + if (key_cmpspidx_withmask(&sp->spidx, spidx)) { + SP_ADDREF(sp); + break; + } } - sp = NULL; -found: - if (sp) { - /* sanity check */ - KEY_CHKSPDIR(sp->spidx.dir, dir, __func__); + SPTREE_RUNLOCK(); - /* found a SPD entry */ + if (sp != NULL) { /* found a SPD entry */ sp->lastused = time_second; - SP_ADDREF(sp); + KEYDBG(IPSEC_STAMP, + printf("%s: return SP(%p)\n", __func__, sp)); + KEYDBG(IPSEC_DATA, kdebug_secpolicy(sp)); + } else { + KEYDBG(IPSEC_DATA, + printf("%s: lookup failed for ", __func__); + kdebug_secpolicyindex(spidx, NULL)); } - SPTREE_UNLOCK(); - - KEYDEBUG(KEYDEBUG_IPSEC_STAMP, - printf("DP %s return SP:%p (ID=%u) refcnt %u\n", __func__, - sp, sp ? sp->id : 0, sp ? sp->refcnt : 0)); - return sp; + return (sp); } -#if 0 /* - * return a policy that matches this particular inbound packet. - * XXX slow + * Allocating an SA entry for an *INBOUND* or *OUTBOUND* TCP packet, signed + * or should be signed by MD5 signature. + * We don't use key_allocsa() for such lookups, because we don't know SPI. + * Unlike ESP and AH protocols, SPI isn't transmitted in the TCP header with + * signed packet. We use SADB only as storage for password. + * OUT: positive: corresponding SA for given saidx found. + * NULL: SA not found */ -struct secpolicy * -key_gettunnel(const struct sockaddr *osrc, - const struct sockaddr *odst, - const struct sockaddr *isrc, - const struct sockaddr *idst, - const char* where, int tag) +struct secasvar * +key_allocsa_tcpmd5(struct secasindex *saidx) { - struct secpolicy *sp; - const int dir = IPSEC_DIR_INBOUND; - struct ipsecrequest *r1, *r2, *p; - struct secpolicyindex spidx; - - KEYDEBUG(KEYDEBUG_IPSEC_STAMP, - printf("DP %s from %s:%u\n", __func__, where, tag)); - - if (isrc->sa_family != idst->sa_family) { - ipseclog((LOG_ERR, "%s: protocol family mismatched %d != %d\n.", - __func__, isrc->sa_family, idst->sa_family)); - sp = NULL; - goto done; - } + SAHTREE_RLOCK_TRACKER; + struct secashead *sah; + struct secasvar *sav; - SPTREE_LOCK(); - LIST_FOREACH(sp, &V_sptree[dir], chain) { - if (sp->state == IPSEC_SPSTATE_DEAD) + IPSEC_ASSERT(saidx->proto == IPPROTO_TCP, + ("unexpected security protocol %u", saidx->proto)); + IPSEC_ASSERT(saidx->mode == IPSEC_MODE_TCPMD5, + ("unexpected mode %u", saidx->mode)); + + SAHTREE_RLOCK(); + LIST_FOREACH(sah, SAHADDRHASH_HASH(saidx), addrhash) { + KEYDBG(IPSEC_DUMP, + printf("%s: checking SAH\n", __func__); + kdebug_secash(sah, " ")); + if (sah->saidx.proto != IPPROTO_TCP) continue; - - r1 = r2 = NULL; - for (p = sp->req; p; p = p->next) { - if (p->saidx.mode != IPSEC_MODE_TUNNEL) - continue; - - r1 = r2; - r2 = p; - - if (!r1) { - /* here we look at address matches only */ - spidx = sp->spidx; - if (isrc->sa_len > sizeof(spidx.src) || - idst->sa_len > sizeof(spidx.dst)) - continue; - bcopy(isrc, &spidx.src, isrc->sa_len); - bcopy(idst, &spidx.dst, idst->sa_len); - if (!key_cmpspidx_withmask(&sp->spidx, &spidx)) - continue; - } else { - if (key_sockaddrcmp(&r1->saidx.src.sa, isrc, 0) || - key_sockaddrcmp(&r1->saidx.dst.sa, idst, 0)) - continue; - } - - if (key_sockaddrcmp(&r2->saidx.src.sa, osrc, 0) || - key_sockaddrcmp(&r2->saidx.dst.sa, odst, 0)) - continue; - - goto found; - } + if (!key_sockaddrcmp(&saidx->dst.sa, &sah->saidx.dst.sa, 0)) + break; } - sp = NULL; -found: - if (sp) { - sp->lastused = time_second; - SP_ADDREF(sp); + if (sah != NULL) { + if (V_key_preferred_oldsa) + sav = TAILQ_LAST(&sah->savtree_alive, secasvar_queue); + else + sav = TAILQ_FIRST(&sah->savtree_alive); + if (sav != NULL) + SAV_ADDREF(sav); + } else + sav = NULL; + SAHTREE_RUNLOCK(); + + if (sav != NULL) { + KEYDBG(IPSEC_STAMP, + printf("%s: return SA(%p)\n", __func__, sav)); + KEYDBG(IPSEC_DATA, kdebug_secasv(sav)); + } else { + KEYDBG(IPSEC_STAMP, + printf("%s: SA not found\n", __func__)); + KEYDBG(IPSEC_DATA, kdebug_secasindex(saidx, NULL)); } - SPTREE_UNLOCK(); -done: - KEYDEBUG(KEYDEBUG_IPSEC_STAMP, - printf("DP %s return SP:%p (ID=%u) refcnt %u\n", __func__, - sp, sp ? sp->id : 0, sp ? sp->refcnt : 0)); - return sp; + return (sav); } -#endif /* - * allocating an SA entry for an *OUTBOUND* packet. - * checking each request entries in SP, and acquire an SA if need. - * OUT: 0: there are valid requests. - * ENOENT: policy may be valid, but SA with REQUIRE is on acquiring. + * Allocating an SA entry for an *OUTBOUND* packet. + * OUT: positive: corresponding SA for given saidx found. + * NULL: SA not found, but will be acquired, check *error + * for acquiring status. */ -int -key_checkrequest(struct ipsecrequest *isr, const struct secasindex *saidx) +struct secasvar * +key_allocsa_policy(struct secpolicy *sp, const struct secasindex *saidx, + int *error) { - u_int level; - int error; + SAHTREE_RLOCK_TRACKER; + struct secashead *sah; struct secasvar *sav; - IPSEC_ASSERT(isr != NULL, ("null isr")); IPSEC_ASSERT(saidx != NULL, ("null saidx")); IPSEC_ASSERT(saidx->mode == IPSEC_MODE_TRANSPORT || saidx->mode == IPSEC_MODE_TUNNEL, ("unexpected policy %u", saidx->mode)); /* - * XXX guard against protocol callbacks from the crypto - * thread as they reference ipsecrequest.sav which we - * temporarily null out below. Need to rethink how we - * handle bundled SA's in the callback thread. - */ - IPSECREQUEST_LOCK_ASSERT(isr); - - /* get current level */ - level = ipsec_get_reqlevel(isr); - - /* * We check new SA in the IPsec request because a different * SA may be involved each time this request is checked, either * because new SAs are being configured, or this request is * associated with an unconnected datagram socket, or this request * is associated with a system default policy. - * - * key_allocsa_policy should allocate the oldest SA available. - * See key_do_allocsa_policy(), and draft-jenkins-ipsec-rekeying-03.txt. */ - sav = key_allocsa_policy(saidx); - if (sav != isr->sav) { - /* SA need to be updated. */ - if (!IPSECREQUEST_UPGRADE(isr)) { - /* Kick everyone off. */ - IPSECREQUEST_UNLOCK(isr); - IPSECREQUEST_WLOCK(isr); - } - if (isr->sav != NULL) - KEY_FREESAV(&isr->sav); - isr->sav = sav; - IPSECREQUEST_DOWNGRADE(isr); - } else if (sav != NULL) - KEY_FREESAV(&sav); - - /* When there is SA. */ - if (isr->sav != NULL) { - if (isr->sav->state != SADB_SASTATE_MATURE && - isr->sav->state != SADB_SASTATE_DYING) - return EINVAL; - return 0; - } - - /* there is no SA */ - error = key_acquire(saidx, isr->sp); - if (error != 0) { - /* XXX What should I do ? */ - ipseclog((LOG_DEBUG, "%s: error %d returned from key_acquire\n", - __func__, error)); - return error; - } - - if (level != IPSEC_LEVEL_REQUIRE) { - /* XXX sigh, the interface to this routine is botched */ - IPSEC_ASSERT(isr->sav == NULL, ("unexpected SA")); - return 0; - } else { - return ENOENT; - } -} - -/* - * allocating a SA for policy entry from SAD. - * NOTE: searching SAD of aliving state. - * OUT: NULL: not found. - * others: found and return the pointer. - */ -static struct secasvar * -key_allocsa_policy(const struct secasindex *saidx) -{ -#define N(a) _ARRAYLEN(a) - struct secashead *sah; - struct secasvar *sav; - u_int stateidx, arraysize; - const u_int *state_valid; - - state_valid = NULL; /* silence gcc */ - arraysize = 0; /* silence gcc */ - - SAHTREE_LOCK(); - LIST_FOREACH(sah, &V_sahtree, chain) { - if (sah->state == SADB_SASTATE_DEAD) - continue; - if (key_cmpsaidx(&sah->saidx, saidx, CMP_MODE_REQID)) { - if (V_key_preferred_oldsa) { - state_valid = saorder_state_valid_prefer_old; - arraysize = N(saorder_state_valid_prefer_old); - } else { - state_valid = saorder_state_valid_prefer_new; - arraysize = N(saorder_state_valid_prefer_new); - } + SAHTREE_RLOCK(); + LIST_FOREACH(sah, SAHADDRHASH_HASH(saidx), addrhash) { + KEYDBG(IPSEC_DUMP, + printf("%s: checking SAH\n", __func__); + kdebug_secash(sah, " ")); + if (key_cmpsaidx(&sah->saidx, saidx, CMP_MODE_REQID)) break; - } - } - SAHTREE_UNLOCK(); - if (sah == NULL) - return NULL; - /* search valid state */ - for (stateidx = 0; stateidx < arraysize; stateidx++) { - sav = key_do_allocsa_policy(sah, state_valid[stateidx]); - if (sav != NULL) - return sav; } - - return NULL; -#undef N -} - -/* - * searching SAD with direction, protocol, mode and state. - * called by key_allocsa_policy(). - * OUT: - * NULL : not found - * others : found, pointer to a SA. - */ -static struct secasvar * -key_do_allocsa_policy(struct secashead *sah, u_int state) -{ - struct secasvar *sav, *nextsav, *candidate, *d; - - /* initilize */ - candidate = NULL; - - SAHTREE_LOCK(); - for (sav = LIST_FIRST(&sah->savtree[state]); - sav != NULL; - sav = nextsav) { - - nextsav = LIST_NEXT(sav, chain); - - /* sanity check */ - KEY_CHKSASTATE(sav->state, state, __func__); - - /* initialize */ - if (candidate == NULL) { - candidate = sav; - continue; - } - - /* Which SA is the better ? */ - - IPSEC_ASSERT(candidate->lft_c != NULL, - ("null candidate lifetime")); - IPSEC_ASSERT(sav->lft_c != NULL, ("null sav lifetime")); - - /* What the best method is to compare ? */ - if (V_key_preferred_oldsa) { - if (candidate->lft_c->addtime > - sav->lft_c->addtime) { - candidate = sav; - } - continue; - /*NOTREACHED*/ - } - - /* preferred new sa rather than old sa */ - if (candidate->lft_c->addtime < - sav->lft_c->addtime) { - d = candidate; - candidate = sav; - } else - d = sav; - + if (sah != NULL) { /* - * prepared to delete the SA when there is more - * suitable candidate and the lifetime of the SA is not - * permanent. + * Allocate the oldest SA available according to + * draft-jenkins-ipsec-rekeying-03. */ - if (d->lft_h->addtime != 0) { - struct mbuf *m, *result; - u_int8_t satype; - - key_sa_chgstate(d, SADB_SASTATE_DEAD); - - IPSEC_ASSERT(d->refcnt > 0, ("bogus ref count")); - - satype = key_proto2satype(d->sah->saidx.proto); - if (satype == 0) - goto msgfail; - - m = key_setsadbmsg(SADB_DELETE, 0, - satype, 0, 0, d->refcnt - 1); - if (!m) - goto msgfail; - result = m; - - /* set sadb_address for saidx's. */ - m = key_setsadbaddr(SADB_EXT_ADDRESS_SRC, - &d->sah->saidx.src.sa, - d->sah->saidx.src.sa.sa_len << 3, - IPSEC_ULPROTO_ANY); - if (!m) - goto msgfail; - m_cat(result, m); - - /* set sadb_address for saidx's. */ - m = key_setsadbaddr(SADB_EXT_ADDRESS_DST, - &d->sah->saidx.dst.sa, - d->sah->saidx.dst.sa.sa_len << 3, - IPSEC_ULPROTO_ANY); - if (!m) - goto msgfail; - m_cat(result, m); - - /* create SA extension */ - m = key_setsadbsa(d); - if (!m) - goto msgfail; - m_cat(result, m); - - if (result->m_len < sizeof(struct sadb_msg)) { - result = m_pullup(result, - sizeof(struct sadb_msg)); - if (result == NULL) - goto msgfail; - } - - result->m_pkthdr.len = 0; - for (m = result; m; m = m->m_next) - result->m_pkthdr.len += m->m_len; - mtod(result, struct sadb_msg *)->sadb_msg_len = - PFKEY_UNIT64(result->m_pkthdr.len); + if (V_key_preferred_oldsa) + sav = TAILQ_LAST(&sah->savtree_alive, secasvar_queue); + else + sav = TAILQ_FIRST(&sah->savtree_alive); + if (sav != NULL) + SAV_ADDREF(sav); + } else + sav = NULL; + SAHTREE_RUNLOCK(); - if (key_sendup_mbuf(NULL, result, - KEY_SENDUP_REGISTERED)) - goto msgfail; - msgfail: - KEY_FREESAV(&d); - } - } - if (candidate) { - sa_addref(candidate); - KEYDEBUG(KEYDEBUG_IPSEC_STAMP, - printf("DP %s cause refcnt++:%d SA:%p\n", - __func__, candidate->refcnt, candidate)); + if (sav != NULL) { + *error = 0; + KEYDBG(IPSEC_STAMP, + printf("%s: chosen SA(%p) for SP(%p)\n", __func__, + sav, sp)); + KEYDBG(IPSEC_DATA, kdebug_secasv(sav)); + return (sav); /* return referenced SA */ } - SAHTREE_UNLOCK(); - return candidate; + /* there is no SA */ + *error = key_acquire(saidx, sp); + if ((*error) != 0) + ipseclog((LOG_DEBUG, + "%s: error %d returned from key_acquire()\n", + __func__, *error)); + KEYDBG(IPSEC_STAMP, + printf("%s: acquire SA for SP(%p), error %d\n", + __func__, sp, *error)); + KEYDBG(IPSEC_DATA, kdebug_secasindex(saidx, NULL)); + return (NULL); } /* * allocating a usable SA entry for a *INBOUND* packet. * Must call key_freesav() later. * OUT: positive: pointer to a usable sav (i.e. MATURE or DYING state). - * NULL: not found, or error occured. + * NULL: not found, or error occurred. + * + * According to RFC 2401 SA is uniquely identified by a triple SPI, + * destination address, and security protocol. But according to RFC 4301, + * SPI by itself suffices to specify an SA. * - * In the comparison, no source address is used--for RFC2401 conformance. - * To quote, from section 4.1: - * A security association is uniquely identified by a triple consisting - * of a Security Parameter Index (SPI), an IP Destination Address, and a - * security protocol (AH or ESP) identifier. * Note that, however, we do need to keep source address in IPsec SA. * IKE specification and PF_KEY specification do assume that we * keep source address in IPsec SA. We see a tricky situation here. */ struct secasvar * -key_allocsa( - union sockaddr_union *dst, - u_int proto, - u_int32_t spi, - const char* where, int tag) +key_allocsa(union sockaddr_union *dst, uint8_t proto, uint32_t spi) { - struct secashead *sah; + SAHTREE_RLOCK_TRACKER; struct secasvar *sav; - u_int stateidx, arraysize, state; - const u_int *saorder_state_valid; - int chkport; - - IPSEC_ASSERT(dst != NULL, ("null dst address")); - KEYDEBUG(KEYDEBUG_IPSEC_STAMP, - printf("DP %s from %s:%u\n", __func__, where, tag)); - -#ifdef IPSEC_NAT_T - chkport = (dst->sa.sa_family == AF_INET && - dst->sa.sa_len == sizeof(struct sockaddr_in) && - dst->sin.sin_port != 0); -#else - chkport = 0; -#endif + IPSEC_ASSERT(proto == IPPROTO_ESP || proto == IPPROTO_AH || + proto == IPPROTO_IPCOMP, ("unexpected security protocol %u", + proto)); + SAHTREE_RLOCK(); + LIST_FOREACH(sav, SAVHASH_HASH(spi), spihash) { + if (sav->spi == spi) + break; + } /* - * searching SAD. - * XXX: to be checked internal IP header somewhere. Also when - * IPsec tunnel packet is received. But ESP tunnel mode is - * encrypted so we can't check internal IP header. + * We use single SPI namespace for all protocols, so it is + * impossible to have SPI duplicates in the SAVHASH. */ - SAHTREE_LOCK(); - if (V_key_preferred_oldsa) { - saorder_state_valid = saorder_state_valid_prefer_old; - arraysize = _ARRAYLEN(saorder_state_valid_prefer_old); + if (sav != NULL) { + if (sav->state != SADB_SASTATE_LARVAL && + sav->sah->saidx.proto == proto && + key_sockaddrcmp(&dst->sa, + &sav->sah->saidx.dst.sa, 0) == 0) + SAV_ADDREF(sav); + else + sav = NULL; + } + SAHTREE_RUNLOCK(); + + if (sav == NULL) { + KEYDBG(IPSEC_STAMP, + char buf[IPSEC_ADDRSTRLEN]; + printf("%s: SA not found for spi %u proto %u dst %s\n", + __func__, ntohl(spi), proto, ipsec_address(dst, buf, + sizeof(buf)))); } else { - saorder_state_valid = saorder_state_valid_prefer_new; - arraysize = _ARRAYLEN(saorder_state_valid_prefer_new); - } - LIST_FOREACH(sah, &V_sahtree, chain) { - /* search valid state */ - for (stateidx = 0; stateidx < arraysize; stateidx++) { - state = saorder_state_valid[stateidx]; - LIST_FOREACH(sav, &sah->savtree[state], chain) { - /* sanity check */ - KEY_CHKSASTATE(sav->state, state, __func__); - /* do not return entries w/ unusable state */ - if (sav->state != SADB_SASTATE_MATURE && - sav->state != SADB_SASTATE_DYING) - continue; - if (proto != sav->sah->saidx.proto) - continue; - if (spi != sav->spi) - continue; -#if 0 /* don't check src */ - /* check src address */ - if (key_sockaddrcmp(&src->sa, &sav->sah->saidx.src.sa, chkport) != 0) - continue; -#endif - /* check dst address */ - if (key_sockaddrcmp(&dst->sa, &sav->sah->saidx.dst.sa, chkport) != 0) - continue; - sa_addref(sav); - goto done; - } - } + KEYDBG(IPSEC_STAMP, + printf("%s: return SA(%p)\n", __func__, sav)); + KEYDBG(IPSEC_DATA, kdebug_secasv(sav)); } - sav = NULL; -done: - SAHTREE_UNLOCK(); + return (sav); +} + +struct secasvar * +key_allocsa_tunnel(union sockaddr_union *src, union sockaddr_union *dst, + uint8_t proto) +{ + SAHTREE_RLOCK_TRACKER; + struct secasindex saidx; + struct secashead *sah; + struct secasvar *sav; + + IPSEC_ASSERT(src != NULL, ("null src address")); + IPSEC_ASSERT(dst != NULL, ("null dst address")); + + KEY_SETSECASIDX(proto, IPSEC_MODE_TUNNEL, 0, &src->sa, + &dst->sa, &saidx); - KEYDEBUG(KEYDEBUG_IPSEC_STAMP, - printf("DP %s return SA:%p; refcnt %u\n", __func__, - sav, sav ? sav->refcnt : 0)); - return sav; + sav = NULL; + SAHTREE_RLOCK(); + LIST_FOREACH(sah, SAHADDRHASH_HASH(&saidx), addrhash) { + if (IPSEC_MODE_TUNNEL != sah->saidx.mode) + continue; + if (proto != sah->saidx.proto) + continue; + if (key_sockaddrcmp(&src->sa, &sah->saidx.src.sa, 0) != 0) + continue; + if (key_sockaddrcmp(&dst->sa, &sah->saidx.dst.sa, 0) != 0) + continue; + /* XXXAE: is key_preferred_oldsa reasonably?*/ + if (V_key_preferred_oldsa) + sav = TAILQ_LAST(&sah->savtree_alive, secasvar_queue); + else + sav = TAILQ_FIRST(&sah->savtree_alive); + if (sav != NULL) { + SAV_ADDREF(sav); + break; + } + } + SAHTREE_RUNLOCK(); + KEYDBG(IPSEC_STAMP, + printf("%s: return SA(%p)\n", __func__, sav)); + if (sav != NULL) + KEYDBG(IPSEC_DATA, kdebug_secasv(sav)); + return (sav); } /* * Must be called after calling key_allocsp(). - * For both the packet without socket and key_freeso(). */ void -_key_freesp(struct secpolicy **spp, const char* where, int tag) +key_freesp(struct secpolicy **spp) { struct secpolicy *sp = *spp; IPSEC_ASSERT(sp != NULL, ("null sp")); + if (SP_DELREF(sp) == 0) + return; - SPTREE_LOCK(); - SP_DELREF(sp); + KEYDBG(IPSEC_STAMP, + printf("%s: last reference to SP(%p)\n", __func__, sp)); + KEYDBG(IPSEC_DATA, kdebug_secpolicy(sp)); + + *spp = NULL; + while (sp->tcount > 0) + ipsec_delisr(sp->req[--sp->tcount]); + free(sp, M_IPSEC_SP); +} - KEYDEBUG(KEYDEBUG_IPSEC_STAMP, - printf("DP %s SP:%p (ID=%u) from %s:%u; refcnt now %u\n", - __func__, sp, sp->id, where, tag, sp->refcnt)); +static void +key_unlink(struct secpolicy *sp) +{ - if (sp->refcnt == 0) { - *spp = NULL; - key_delsp(sp); + IPSEC_ASSERT(sp->spidx.dir == IPSEC_DIR_INBOUND || + sp->spidx.dir == IPSEC_DIR_OUTBOUND, + ("invalid direction %u", sp->spidx.dir)); + SPTREE_UNLOCK_ASSERT(); + + KEYDBG(KEY_STAMP, + printf("%s: SP(%p)\n", __func__, sp)); + SPTREE_WLOCK(); + if (sp->state != IPSEC_SPSTATE_ALIVE) { + /* SP is already unlinked */ + SPTREE_WUNLOCK(); + return; } - SPTREE_UNLOCK(); + sp->state = IPSEC_SPSTATE_DEAD; + TAILQ_REMOVE(&V_sptree[sp->spidx.dir], sp, chain); + LIST_REMOVE(sp, idhash); + V_sp_genid++; + SPTREE_WUNLOCK(); + key_freesp(&sp); } /* - * Must be called after calling key_allocsp(). - * For the packet with socket. + * insert a secpolicy into the SP database. Lower priorities first */ -void -key_freeso(struct socket *so) +static void +key_insertsp(struct secpolicy *newsp) { - IPSEC_ASSERT(so != NULL, ("null so")); - - switch (so->so_proto->pr_domain->dom_family) { -#if defined(INET) || defined(INET6) -#ifdef INET - case PF_INET: -#endif -#ifdef INET6 - case PF_INET6: -#endif - { - struct inpcb *pcb = sotoinpcb(so); + struct secpolicy *sp; - /* Does it have a PCB ? */ - if (pcb == NULL) - return; - key_freesp_so(&pcb->inp_sp->sp_in); - key_freesp_so(&pcb->inp_sp->sp_out); - } - break; -#endif /* INET || INET6 */ - default: - ipseclog((LOG_DEBUG, "%s: unknown address family=%d.\n", - __func__, so->so_proto->pr_domain->dom_family)); - return; + SPTREE_WLOCK_ASSERT(); + TAILQ_FOREACH(sp, &V_sptree[newsp->spidx.dir], chain) { + if (newsp->priority < sp->priority) { + TAILQ_INSERT_BEFORE(sp, newsp, chain); + goto done; + } } + TAILQ_INSERT_TAIL(&V_sptree[newsp->spidx.dir], newsp, chain); +done: + LIST_INSERT_HEAD(SPHASH_HASH(newsp->id), newsp, idhash); + newsp->state = IPSEC_SPSTATE_ALIVE; + V_sp_genid++; } -static void -key_freesp_so(struct secpolicy **sp) +/* + * Insert a bunch of VTI secpolicies into the SPDB. + * We keep VTI policies in the separate list due to following reasons: + * 1) they should be immutable to user's or some deamon's attempts to + * delete. The only way delete such policies - destroy or unconfigure + * corresponding virtual inteface. + * 2) such policies have traffic selector that matches all traffic per + * address family. + * Since all VTI policies have the same priority, we don't care about + * policies order. + */ +int +key_register_ifnet(struct secpolicy **spp, u_int count) { - IPSEC_ASSERT(sp != NULL && *sp != NULL, ("null sp")); + struct mbuf *m; + u_int i; - if ((*sp)->policy == IPSEC_POLICY_ENTRUST || - (*sp)->policy == IPSEC_POLICY_BYPASS) - return; + SPTREE_WLOCK(); + /* + * First of try to acquire id for each SP. + */ + for (i = 0; i < count; i++) { + IPSEC_ASSERT(spp[i]->spidx.dir == IPSEC_DIR_INBOUND || + spp[i]->spidx.dir == IPSEC_DIR_OUTBOUND, + ("invalid direction %u", spp[i]->spidx.dir)); - IPSEC_ASSERT((*sp)->policy == IPSEC_POLICY_IPSEC, - ("invalid policy %u", (*sp)->policy)); - KEY_FREESP(sp); + if ((spp[i]->id = key_getnewspid()) == 0) { + SPTREE_WUNLOCK(); + return (EAGAIN); + } + } + for (i = 0; i < count; i++) { + TAILQ_INSERT_TAIL(&V_sptree_ifnet[spp[i]->spidx.dir], + spp[i], chain); + /* + * NOTE: despite the fact that we keep VTI SP in the + * separate list, SPHASH contains policies from both + * sources. Thus SADB_X_SPDGET will correctly return + * SP by id, because it uses SPHASH for lookups. + */ + LIST_INSERT_HEAD(SPHASH_HASH(spp[i]->id), spp[i], idhash); + spp[i]->state = IPSEC_SPSTATE_IFNET; + } + SPTREE_WUNLOCK(); + /* + * Notify user processes about new SP. + */ + for (i = 0; i < count; i++) { + m = key_setdumpsp(spp[i], SADB_X_SPDADD, 0, 0); + if (m != NULL) + key_sendup_mbuf(NULL, m, KEY_SENDUP_ALL); + } + return (0); } void -key_addrefsa(struct secasvar *sav, const char* where, int tag) +key_unregister_ifnet(struct secpolicy **spp, u_int count) { + struct mbuf *m; + u_int i; - IPSEC_ASSERT(sav != NULL, ("null sav")); - IPSEC_ASSERT(sav->refcnt > 0, ("refcount must exist")); + SPTREE_WLOCK(); + for (i = 0; i < count; i++) { + IPSEC_ASSERT(spp[i]->spidx.dir == IPSEC_DIR_INBOUND || + spp[i]->spidx.dir == IPSEC_DIR_OUTBOUND, + ("invalid direction %u", spp[i]->spidx.dir)); + + if (spp[i]->state != IPSEC_SPSTATE_IFNET) + continue; + spp[i]->state = IPSEC_SPSTATE_DEAD; + TAILQ_REMOVE(&V_sptree_ifnet[spp[i]->spidx.dir], + spp[i], chain); + LIST_REMOVE(spp[i], idhash); + } + SPTREE_WUNLOCK(); - sa_addref(sav); + for (i = 0; i < count; i++) { + m = key_setdumpsp(spp[i], SADB_X_SPDDELETE, 0, 0); + if (m != NULL) + key_sendup_mbuf(NULL, m, KEY_SENDUP_ALL); + } } /* @@ -1245,58 +1223,57 @@ key_addrefsa(struct secasvar *sav, const char* where, int tag) * for a policy. */ void -key_freesav(struct secasvar **psav, const char* where, int tag) +key_freesav(struct secasvar **psav) { struct secasvar *sav = *psav; IPSEC_ASSERT(sav != NULL, ("null sav")); + if (SAV_DELREF(sav) == 0) + return; - if (sa_delref(sav)) { - KEYDEBUG(KEYDEBUG_IPSEC_STAMP, - printf("DP %s SA:%p (SPI %u) from %s:%u; refcnt now %u\n", - __func__, sav, ntohl(sav->spi), where, tag, sav->refcnt)); - *psav = NULL; - key_delsav(sav); - } else { - KEYDEBUG(KEYDEBUG_IPSEC_STAMP, - printf("DP %s SA:%p (SPI %u) from %s:%u; refcnt now %u\n", - __func__, sav, ntohl(sav->spi), where, tag, sav->refcnt)); - } + KEYDBG(IPSEC_STAMP, + printf("%s: last reference to SA(%p)\n", __func__, sav)); + + *psav = NULL; + key_delsav(sav); } -/* %%% SPD management */ /* - * free security policy entry. + * Unlink SA from SAH and SPI hash under SAHTREE_WLOCK. + * Expect that SA has extra reference due to lookup. + * Release this references, also release SAH reference after unlink. */ static void -key_delsp(struct secpolicy *sp) +key_unlinksav(struct secasvar *sav) { - struct ipsecrequest *isr, *nextisr; - - IPSEC_ASSERT(sp != NULL, ("null sp")); - SPTREE_LOCK_ASSERT(); - - sp->state = IPSEC_SPSTATE_DEAD; - - IPSEC_ASSERT(sp->refcnt == 0, - ("SP with references deleted (refcnt %u)", sp->refcnt)); - - /* remove from SP index */ - if (__LIST_CHAINED(sp)) - LIST_REMOVE(sp, chain); + struct secashead *sah; - for (isr = sp->req; isr != NULL; isr = nextisr) { - if (isr->sav != NULL) { - KEY_FREESAV(&isr->sav); - isr->sav = NULL; - } + KEYDBG(KEY_STAMP, + printf("%s: SA(%p)\n", __func__, sav)); - nextisr = isr->next; - ipsec_delisr(isr); + SAHTREE_UNLOCK_ASSERT(); + SAHTREE_WLOCK(); + if (sav->state == SADB_SASTATE_DEAD) { + /* SA is already unlinked */ + SAHTREE_WUNLOCK(); + return; } - _key_delsp(sp); + /* Unlink from SAH */ + if (sav->state == SADB_SASTATE_LARVAL) + TAILQ_REMOVE(&sav->sah->savtree_larval, sav, chain); + else + TAILQ_REMOVE(&sav->sah->savtree_alive, sav, chain); + /* Unlink from SPI hash */ + LIST_REMOVE(sav, spihash); + sav->state = SADB_SASTATE_DEAD; + sah = sav->sah; + SAHTREE_WUNLOCK(); + key_freesav(&sav); + /* Since we are unlinked, release reference to SAH */ + key_freesah(&sah); } +/* %%% SPD management */ /* * search SPD * OUT: NULL : not found @@ -1305,20 +1282,19 @@ key_delsp(struct secpolicy *sp) static struct secpolicy * key_getsp(struct secpolicyindex *spidx) { + SPTREE_RLOCK_TRACKER; struct secpolicy *sp; IPSEC_ASSERT(spidx != NULL, ("null spidx")); - SPTREE_LOCK(); - LIST_FOREACH(sp, &V_sptree[spidx->dir], chain) { - if (sp->state == IPSEC_SPSTATE_DEAD) - continue; + SPTREE_RLOCK(); + TAILQ_FOREACH(sp, &V_sptree[spidx->dir], chain) { if (key_cmpspidx_exactly(spidx, &sp->spidx)) { SP_ADDREF(sp); break; } } - SPTREE_UNLOCK(); + SPTREE_RUNLOCK(); return sp; } @@ -1326,73 +1302,58 @@ key_getsp(struct secpolicyindex *spidx) /* * get SP by index. * OUT: NULL : not found - * others : found, pointer to a SP. + * others : found, pointer to referenced SP. */ static struct secpolicy * -key_getspbyid(u_int32_t id) +key_getspbyid(uint32_t id) { + SPTREE_RLOCK_TRACKER; struct secpolicy *sp; - SPTREE_LOCK(); - LIST_FOREACH(sp, &V_sptree[IPSEC_DIR_INBOUND], chain) { - if (sp->state == IPSEC_SPSTATE_DEAD) - continue; - if (sp->id == id) { - SP_ADDREF(sp); - goto done; - } - } - - LIST_FOREACH(sp, &V_sptree[IPSEC_DIR_OUTBOUND], chain) { - if (sp->state == IPSEC_SPSTATE_DEAD) - continue; + SPTREE_RLOCK(); + LIST_FOREACH(sp, SPHASH_HASH(id), idhash) { if (sp->id == id) { SP_ADDREF(sp); - goto done; + break; } } -done: - SPTREE_UNLOCK(); - - return sp; + SPTREE_RUNLOCK(); + return (sp); } struct secpolicy * -key_newsp(const char* where, int tag) +key_newsp(void) { - struct secpolicy *newsp = NULL; + struct secpolicy *sp; - newsp = (struct secpolicy *) - malloc(sizeof(struct secpolicy), M_IPSEC_SP, M_NOWAIT|M_ZERO); - if (newsp) { - SECPOLICY_LOCK_INIT(newsp); - newsp->refcnt = 1; - newsp->req = NULL; - } + sp = malloc(sizeof(*sp), M_IPSEC_SP, M_NOWAIT | M_ZERO); + if (sp != NULL) + SP_INITREF(sp); + return (sp); +} - KEYDEBUG(KEYDEBUG_IPSEC_STAMP, - printf("DP %s from %s:%u return SP:%p\n", __func__, - where, tag, newsp)); - return newsp; +struct ipsecrequest * +ipsec_newisr(void) +{ + + return (malloc(sizeof(struct ipsecrequest), M_IPSEC_SR, + M_NOWAIT | M_ZERO)); } -static void -_key_delsp(struct secpolicy *sp) +void +ipsec_delisr(struct ipsecrequest *p) { - SECPOLICY_LOCK_DESTROY(sp); - free(sp, M_IPSEC_SP); + + free(p, M_IPSEC_SR); } /* * create secpolicy structure from sadb_x_policy structure. - * NOTE: `state', `secpolicyindex' in secpolicy structure are not set, - * so must be set properly later. + * NOTE: `state', `secpolicyindex' and 'id' in secpolicy structure + * are not set, so must be set properly later. */ struct secpolicy * -key_msg2sp(xpl0, len, error) - struct sadb_x_policy *xpl0; - size_t len; - int *error; +key_msg2sp(struct sadb_x_policy *xpl0, size_t len, int *error) { struct secpolicy *newsp; @@ -1405,13 +1366,15 @@ key_msg2sp(xpl0, len, error) return NULL; } - if ((newsp = KEY_NEWSP()) == NULL) { + if ((newsp = key_newsp()) == NULL) { *error = ENOBUFS; return NULL; } newsp->spidx.dir = xpl0->sadb_x_policy_dir; newsp->policy = xpl0->sadb_x_policy_type; + newsp->priority = xpl0->sadb_x_policy_priority; + newsp->tcount = 0; /* check policy */ switch (xpl0->sadb_x_policy_type) { @@ -1419,20 +1382,19 @@ key_msg2sp(xpl0, len, error) case IPSEC_POLICY_NONE: case IPSEC_POLICY_ENTRUST: case IPSEC_POLICY_BYPASS: - newsp->req = NULL; break; case IPSEC_POLICY_IPSEC: { - int tlen; struct sadb_x_ipsecrequest *xisr; - struct ipsecrequest **p_isr = &newsp->req; + struct ipsecrequest *isr; + int tlen; /* validity check */ if (PFKEY_EXTLEN(xpl0) < sizeof(*xpl0)) { ipseclog((LOG_DEBUG, "%s: Invalid msg length.\n", __func__)); - KEY_FREESP(&newsp); + key_freesp(&newsp); *error = EINVAL; return NULL; } @@ -1445,22 +1407,33 @@ key_msg2sp(xpl0, len, error) if (xisr->sadb_x_ipsecrequest_len < sizeof(*xisr)) { ipseclog((LOG_DEBUG, "%s: invalid ipsecrequest " "length.\n", __func__)); - KEY_FREESP(&newsp); + key_freesp(&newsp); *error = EINVAL; return NULL; } + if (newsp->tcount >= IPSEC_MAXREQ) { + ipseclog((LOG_DEBUG, + "%s: too many ipsecrequests.\n", + __func__)); + key_freesp(&newsp); + *error = EINVAL; + return (NULL); + } + /* allocate request buffer */ /* NB: data structure is zero'd */ - *p_isr = ipsec_newisr(); - if ((*p_isr) == NULL) { + isr = ipsec_newisr(); + if (isr == NULL) { ipseclog((LOG_DEBUG, "%s: No more memory.\n", __func__)); - KEY_FREESP(&newsp); + key_freesp(&newsp); *error = ENOBUFS; return NULL; } + newsp->req[newsp->tcount++] = isr; + /* set values */ switch (xisr->sadb_x_ipsecrequest_proto) { case IPPROTO_ESP: @@ -1471,11 +1444,12 @@ key_msg2sp(xpl0, len, error) ipseclog((LOG_DEBUG, "%s: invalid proto type=%u\n", __func__, xisr->sadb_x_ipsecrequest_proto)); - KEY_FREESP(&newsp); + key_freesp(&newsp); *error = EPROTONOSUPPORT; return NULL; } - (*p_isr)->saidx.proto = xisr->sadb_x_ipsecrequest_proto; + isr->saidx.proto = + (uint8_t)xisr->sadb_x_ipsecrequest_proto; switch (xisr->sadb_x_ipsecrequest_mode) { case IPSEC_MODE_TRANSPORT: @@ -1486,11 +1460,11 @@ key_msg2sp(xpl0, len, error) ipseclog((LOG_DEBUG, "%s: invalid mode=%u\n", __func__, xisr->sadb_x_ipsecrequest_mode)); - KEY_FREESP(&newsp); + key_freesp(&newsp); *error = EINVAL; return NULL; } - (*p_isr)->saidx.mode = xisr->sadb_x_ipsecrequest_mode; + isr->saidx.mode = xisr->sadb_x_ipsecrequest_mode; switch (xisr->sadb_x_ipsecrequest_level) { case IPSEC_LEVEL_DEFAULT: @@ -1517,16 +1491,16 @@ key_msg2sp(xpl0, len, error) if (xisr->sadb_x_ipsecrequest_reqid == 0) { u_int32_t reqid; if ((reqid = key_newreqid()) == 0) { - KEY_FREESP(&newsp); + key_freesp(&newsp); *error = ENOBUFS; return NULL; } - (*p_isr)->saidx.reqid = reqid; + isr->saidx.reqid = reqid; xisr->sadb_x_ipsecrequest_reqid = reqid; } else { /* set it for manual keying. */ - (*p_isr)->saidx.reqid = - xisr->sadb_x_ipsecrequest_reqid; + isr->saidx.reqid = + xisr->sadb_x_ipsecrequest_reqid; } break; @@ -1534,59 +1508,72 @@ key_msg2sp(xpl0, len, error) ipseclog((LOG_DEBUG, "%s: invalid level=%u\n", __func__, xisr->sadb_x_ipsecrequest_level)); - KEY_FREESP(&newsp); + key_freesp(&newsp); *error = EINVAL; return NULL; } - (*p_isr)->level = xisr->sadb_x_ipsecrequest_level; + isr->level = xisr->sadb_x_ipsecrequest_level; /* set IP addresses if there */ if (xisr->sadb_x_ipsecrequest_len > sizeof(*xisr)) { struct sockaddr *paddr; paddr = (struct sockaddr *)(xisr + 1); - /* validity check */ if (paddr->sa_len - > sizeof((*p_isr)->saidx.src)) { + > sizeof(isr->saidx.src)) { ipseclog((LOG_DEBUG, "%s: invalid " "request address length.\n", __func__)); - KEY_FREESP(&newsp); + key_freesp(&newsp); *error = EINVAL; return NULL; } - bcopy(paddr, &(*p_isr)->saidx.src, - paddr->sa_len); - - paddr = (struct sockaddr *)((caddr_t)paddr - + paddr->sa_len); + bcopy(paddr, &isr->saidx.src, paddr->sa_len); + paddr = (struct sockaddr *)((caddr_t)paddr + + paddr->sa_len); /* validity check */ if (paddr->sa_len - > sizeof((*p_isr)->saidx.dst)) { + > sizeof(isr->saidx.dst)) { ipseclog((LOG_DEBUG, "%s: invalid " "request address length.\n", __func__)); - KEY_FREESP(&newsp); + key_freesp(&newsp); *error = EINVAL; return NULL; } - bcopy(paddr, &(*p_isr)->saidx.dst, - paddr->sa_len); + /* AF family should match */ + if (paddr->sa_family != + isr->saidx.src.sa.sa_family) { + ipseclog((LOG_DEBUG, "%s: address " + "family doesn't match.\n", + __func__)); + key_freesp(&newsp); + *error = EINVAL; + return (NULL); + } + bcopy(paddr, &isr->saidx.dst, paddr->sa_len); + } else { + /* + * Addresses for TUNNEL mode requests are + * mandatory. + */ + if (isr->saidx.mode == IPSEC_MODE_TUNNEL) { + ipseclog((LOG_DEBUG, "%s: missing " + "request addresses.\n", __func__)); + key_freesp(&newsp); + *error = EINVAL; + return (NULL); + } } - - (*p_isr)->sp = newsp; - - /* initialization for the next. */ - p_isr = &(*p_isr)->next; tlen -= xisr->sadb_x_ipsecrequest_len; /* validity check */ if (tlen < 0) { ipseclog((LOG_DEBUG, "%s: becoming tlen < 0.\n", __func__)); - KEY_FREESP(&newsp); + key_freesp(&newsp); *error = EINVAL; return NULL; } @@ -1594,76 +1581,114 @@ key_msg2sp(xpl0, len, error) xisr = (struct sadb_x_ipsecrequest *)((caddr_t)xisr + xisr->sadb_x_ipsecrequest_len); } + /* XXXAE: LARVAL SP */ + if (newsp->tcount < 1) { + ipseclog((LOG_DEBUG, "%s: valid IPSEC transforms " + "not found.\n", __func__)); + key_freesp(&newsp); + *error = EINVAL; + return (NULL); + } } break; default: ipseclog((LOG_DEBUG, "%s: invalid policy type.\n", __func__)); - KEY_FREESP(&newsp); + key_freesp(&newsp); *error = EINVAL; return NULL; } *error = 0; - return newsp; + return (newsp); } -static u_int32_t -key_newreqid() +uint32_t +key_newreqid(void) { - static u_int32_t auto_reqid = IPSEC_MANUAL_REQID_MAX + 1; + static uint32_t auto_reqid = IPSEC_MANUAL_REQID_MAX + 1; - auto_reqid = (auto_reqid == ~0 - ? IPSEC_MANUAL_REQID_MAX + 1 : auto_reqid + 1); + if (auto_reqid == ~0) + auto_reqid = IPSEC_MANUAL_REQID_MAX + 1; + else + auto_reqid++; /* XXX should be unique check */ - - return auto_reqid; + return (auto_reqid); } /* * copy secpolicy struct to sadb_x_policy structure indicated. */ -struct mbuf * -key_sp2msg(sp) - struct secpolicy *sp; +static struct mbuf * +key_sp2mbuf(struct secpolicy *sp) { - struct sadb_x_policy *xpl; - int tlen; - caddr_t p; struct mbuf *m; - - IPSEC_ASSERT(sp != NULL, ("null policy")); + size_t tlen; tlen = key_getspreqmsglen(sp); - - m = key_alloc_mbuf(tlen); - if (!m || m->m_next) { /*XXX*/ - if (m) - m_freem(m); - return NULL; + m = m_get2(tlen, M_NOWAIT, MT_DATA, 0); + if (m == NULL) + return (NULL); + m_align(m, tlen); + m->m_len = tlen; + if (key_sp2msg(sp, m->m_data, &tlen) != 0) { + m_freem(m); + return (NULL); } + return (m); +} - m->m_len = tlen; - m->m_next = NULL; - xpl = mtod(m, struct sadb_x_policy *); - bzero(xpl, tlen); +int +key_sp2msg(struct secpolicy *sp, void *request, size_t *len) +{ + struct sadb_x_ipsecrequest *xisr; + struct sadb_x_policy *xpl; + struct ipsecrequest *isr; + size_t xlen, ilen; + caddr_t p; + int error, i; + + IPSEC_ASSERT(sp != NULL, ("null policy")); + + xlen = sizeof(*xpl); + if (*len < xlen) + return (EINVAL); - xpl->sadb_x_policy_len = PFKEY_UNIT64(tlen); + error = 0; + bzero(request, *len); + xpl = (struct sadb_x_policy *)request; xpl->sadb_x_policy_exttype = SADB_X_EXT_POLICY; xpl->sadb_x_policy_type = sp->policy; xpl->sadb_x_policy_dir = sp->spidx.dir; xpl->sadb_x_policy_id = sp->id; - p = (caddr_t)xpl + sizeof(*xpl); + xpl->sadb_x_policy_priority = sp->priority; + switch (sp->state) { + case IPSEC_SPSTATE_IFNET: + xpl->sadb_x_policy_scope = IPSEC_POLICYSCOPE_IFNET; + break; + case IPSEC_SPSTATE_PCB: + xpl->sadb_x_policy_scope = IPSEC_POLICYSCOPE_PCB; + break; + default: + xpl->sadb_x_policy_scope = IPSEC_POLICYSCOPE_GLOBAL; + } /* if is the policy for ipsec ? */ if (sp->policy == IPSEC_POLICY_IPSEC) { - struct sadb_x_ipsecrequest *xisr; - struct ipsecrequest *isr; - - for (isr = sp->req; isr != NULL; isr = isr->next) { - + p = (caddr_t)xpl + sizeof(*xpl); + for (i = 0; i < sp->tcount; i++) { + isr = sp->req[i]; + ilen = PFKEY_ALIGN8(sizeof(*xisr) + + isr->saidx.src.sa.sa_len + + isr->saidx.dst.sa.sa_len); + xlen += ilen; + if (xlen > *len) { + error = ENOBUFS; + /* Calculate needed size */ + continue; + } xisr = (struct sadb_x_ipsecrequest *)p; - + xisr->sadb_x_ipsecrequest_len = ilen; xisr->sadb_x_ipsecrequest_proto = isr->saidx.proto; xisr->sadb_x_ipsecrequest_mode = isr->saidx.mode; xisr->sadb_x_ipsecrequest_level = isr->level; @@ -1673,31 +1698,21 @@ key_sp2msg(sp) bcopy(&isr->saidx.src, p, isr->saidx.src.sa.sa_len); p += isr->saidx.src.sa.sa_len; bcopy(&isr->saidx.dst, p, isr->saidx.dst.sa.sa_len); - p += isr->saidx.src.sa.sa_len; - - xisr->sadb_x_ipsecrequest_len = - PFKEY_ALIGN8(sizeof(*xisr) - + isr->saidx.src.sa.sa_len - + isr->saidx.dst.sa.sa_len); + p += isr->saidx.dst.sa.sa_len; } } - - return m; + xpl->sadb_x_policy_len = PFKEY_UNIT64(xlen); + if (error == 0) + *len = xlen; + else + *len = sizeof(*xpl); + return (error); } /* m will not be freed nor modified */ static struct mbuf * -#ifdef __STDC__ key_gather_mbuf(struct mbuf *m, const struct sadb_msghdr *mhp, - int ndeep, int nitem, ...) -#else -key_gather_mbuf(m, mhp, ndeep, nitem, va_alist) - struct mbuf *m; - const struct sadb_msghdr *mhp; - int ndeep; - int nitem; - va_dcl -#endif + int ndeep, int nitem, ...) { va_list ap; int idx; @@ -1725,7 +1740,7 @@ key_gather_mbuf(m, mhp, ndeep, nitem, va_alist) IPSEC_ASSERT(len <= MHLEN, ("header too big %u", len)); - MGETHDR(n, M_DONTWAIT, MT_DATA); + MGETHDR(n, M_NOWAIT, MT_DATA); if (!n) goto fail; n->m_len = len; @@ -1734,17 +1749,16 @@ key_gather_mbuf(m, mhp, ndeep, nitem, va_alist) mtod(n, caddr_t)); } else if (i < ndeep) { len = mhp->extlen[idx]; - n = key_alloc_mbuf(len); - if (!n || n->m_next) { /*XXX*/ - if (n) - m_freem(n); + n = m_get2(len, M_NOWAIT, MT_DATA, 0); + if (n == NULL) goto fail; - } + m_align(n, len); + n->m_len = len; m_copydata(m, mhp->extoff[idx], mhp->extlen[idx], mtod(n, caddr_t)); } else { n = m_copym(m, mhp->extoff[idx], mhp->extlen[idx], - M_DONTWAIT); + M_NOWAIT); } if (n == NULL) goto fail; @@ -1784,18 +1798,16 @@ fail: * SPDSETIDX like SPDADD without a part of policy requests. * SPDUPDATE replace a unique policy entry. * + * XXXAE: serialize this in PF_KEY to avoid races. * m will always be freed. */ static int -key_spdadd(so, m, mhp) - struct socket *so; - struct mbuf *m; - const struct sadb_msghdr *mhp; +key_spdadd(struct socket *so, struct mbuf *m, const struct sadb_msghdr *mhp) { + struct secpolicyindex spidx; struct sadb_address *src0, *dst0; struct sadb_x_policy *xpl0, *xpl; struct sadb_lifetime *lft = NULL; - struct secpolicyindex spidx; struct secpolicy *newsp; int error; @@ -1804,24 +1816,26 @@ key_spdadd(so, m, mhp) IPSEC_ASSERT(mhp != NULL, ("null msghdr")); IPSEC_ASSERT(mhp->msg != NULL, ("null msg")); - if (mhp->ext[SADB_EXT_ADDRESS_SRC] == NULL || - mhp->ext[SADB_EXT_ADDRESS_DST] == NULL || - mhp->ext[SADB_X_EXT_POLICY] == NULL) { - ipseclog((LOG_DEBUG, "key_spdadd: invalid message is passed.\n")); + if (SADB_CHECKHDR(mhp, SADB_EXT_ADDRESS_SRC) || + SADB_CHECKHDR(mhp, SADB_EXT_ADDRESS_DST) || + SADB_CHECKHDR(mhp, SADB_X_EXT_POLICY)) { + ipseclog((LOG_DEBUG, + "%s: invalid message: missing required header.\n", + __func__)); return key_senderror(so, m, EINVAL); } - if (mhp->extlen[SADB_EXT_ADDRESS_SRC] < sizeof(struct sadb_address) || - mhp->extlen[SADB_EXT_ADDRESS_DST] < sizeof(struct sadb_address) || - mhp->extlen[SADB_X_EXT_POLICY] < sizeof(struct sadb_x_policy)) { - ipseclog((LOG_DEBUG, "%s: invalid message is passed.\n", - __func__)); + if (SADB_CHECKLEN(mhp, SADB_EXT_ADDRESS_SRC) || + SADB_CHECKLEN(mhp, SADB_EXT_ADDRESS_DST) || + SADB_CHECKLEN(mhp, SADB_X_EXT_POLICY)) { + ipseclog((LOG_DEBUG, + "%s: invalid message: wrong header size.\n", __func__)); return key_senderror(so, m, EINVAL); } - if (mhp->ext[SADB_EXT_LIFETIME_HARD] != NULL) { - if (mhp->extlen[SADB_EXT_LIFETIME_HARD] - < sizeof(struct sadb_lifetime)) { - ipseclog((LOG_DEBUG, "%s: invalid message is passed.\n", - __func__)); + if (!SADB_CHECKHDR(mhp, SADB_EXT_LIFETIME_HARD)) { + if (SADB_CHECKLEN(mhp, SADB_EXT_LIFETIME_HARD)) { + ipseclog((LOG_DEBUG, + "%s: invalid message: wrong header size.\n", + __func__)); return key_senderror(so, m, EINVAL); } lft = (struct sadb_lifetime *)mhp->ext[SADB_EXT_LIFETIME_HARD]; @@ -1831,141 +1845,93 @@ key_spdadd(so, m, mhp) dst0 = (struct sadb_address *)mhp->ext[SADB_EXT_ADDRESS_DST]; xpl0 = (struct sadb_x_policy *)mhp->ext[SADB_X_EXT_POLICY]; - /* - * Note: do not parse SADB_X_EXT_NAT_T_* here: - * we are processing traffic endpoints. - */ - - /* make secindex */ - /* XXX boundary check against sa_len */ - KEY_SETSECSPIDX(xpl0->sadb_x_policy_dir, - src0 + 1, - dst0 + 1, - src0->sadb_address_prefixlen, - dst0->sadb_address_prefixlen, - src0->sadb_address_proto, - &spidx); - - /* checking the direciton. */ + /* check the direciton */ switch (xpl0->sadb_x_policy_dir) { case IPSEC_DIR_INBOUND: case IPSEC_DIR_OUTBOUND: break; default: - ipseclog((LOG_DEBUG, "%s: Invalid SP direction.\n", __func__)); - mhp->msg->sadb_msg_errno = EINVAL; - return 0; + ipseclog((LOG_DEBUG, "%s: invalid SP direction.\n", __func__)); + return key_senderror(so, m, EINVAL); } - - /* check policy */ /* key_spdadd() accepts DISCARD, NONE and IPSEC. */ - if (xpl0->sadb_x_policy_type == IPSEC_POLICY_ENTRUST - || xpl0->sadb_x_policy_type == IPSEC_POLICY_BYPASS) { - ipseclog((LOG_DEBUG, "%s: Invalid policy type.\n", __func__)); + if (xpl0->sadb_x_policy_type != IPSEC_POLICY_DISCARD && + xpl0->sadb_x_policy_type != IPSEC_POLICY_NONE && + xpl0->sadb_x_policy_type != IPSEC_POLICY_IPSEC) { + ipseclog((LOG_DEBUG, "%s: invalid policy type.\n", __func__)); return key_senderror(so, m, EINVAL); } /* policy requests are mandatory when action is ipsec. */ - if (mhp->msg->sadb_msg_type != SADB_X_SPDSETIDX - && xpl0->sadb_x_policy_type == IPSEC_POLICY_IPSEC - && mhp->extlen[SADB_X_EXT_POLICY] <= sizeof(*xpl0)) { - ipseclog((LOG_DEBUG, "%s: some policy requests part required\n", - __func__)); + if (xpl0->sadb_x_policy_type == IPSEC_POLICY_IPSEC && + mhp->extlen[SADB_X_EXT_POLICY] <= sizeof(*xpl0)) { + ipseclog((LOG_DEBUG, + "%s: policy requests required.\n", __func__)); return key_senderror(so, m, EINVAL); } - /* - * checking there is SP already or not. - * SPDUPDATE doesn't depend on whether there is a SP or not. - * If the type is either SPDADD or SPDSETIDX AND a SP is found, - * then error. - */ - newsp = key_getsp(&spidx); - if (mhp->msg->sadb_msg_type == SADB_X_SPDUPDATE) { - if (newsp) { - SPTREE_LOCK(); - newsp->state = IPSEC_SPSTATE_DEAD; - SPTREE_UNLOCK(); - KEY_FREESP(&newsp); - } - } else { - if (newsp != NULL) { - KEY_FREESP(&newsp); - ipseclog((LOG_DEBUG, "%s: a SP entry exists already.\n", - __func__)); - return key_senderror(so, m, EEXIST); - } - } - - /* allocation new SP entry */ - if ((newsp = key_msg2sp(xpl0, PFKEY_EXTLEN(xpl0), &error)) == NULL) { + error = key_checksockaddrs((struct sockaddr *)(src0 + 1), + (struct sockaddr *)(dst0 + 1)); + if (error != 0 || + src0->sadb_address_proto != dst0->sadb_address_proto) { + ipseclog((LOG_DEBUG, "%s: invalid sockaddr.\n", __func__)); return key_senderror(so, m, error); } - - if ((newsp->id = key_getnewspid()) == 0) { - _key_delsp(newsp); - return key_senderror(so, m, ENOBUFS); - } - - /* XXX boundary check against sa_len */ + /* make secindex */ KEY_SETSECSPIDX(xpl0->sadb_x_policy_dir, src0 + 1, dst0 + 1, src0->sadb_address_prefixlen, dst0->sadb_address_prefixlen, src0->sadb_address_proto, - &newsp->spidx); - - /* sanity check on addr pair */ - if (((struct sockaddr *)(src0 + 1))->sa_family != - ((struct sockaddr *)(dst0+ 1))->sa_family) { - _key_delsp(newsp); - return key_senderror(so, m, EINVAL); - } - if (((struct sockaddr *)(src0 + 1))->sa_len != - ((struct sockaddr *)(dst0+ 1))->sa_len) { - _key_delsp(newsp); - return key_senderror(so, m, EINVAL); - } -#if 1 - if (newsp->req && newsp->req->saidx.src.sa.sa_family && newsp->req->saidx.dst.sa.sa_family) { - if (newsp->req->saidx.src.sa.sa_family != newsp->req->saidx.dst.sa.sa_family) { - _key_delsp(newsp); - return key_senderror(so, m, EINVAL); + &spidx); + /* Checking there is SP already or not. */ + newsp = key_getsp(&spidx); + if (newsp != NULL) { + if (mhp->msg->sadb_msg_type == SADB_X_SPDUPDATE) { + KEYDBG(KEY_STAMP, + printf("%s: unlink SP(%p) for SPDUPDATE\n", + __func__, newsp)); + KEYDBG(KEY_DATA, kdebug_secpolicy(newsp)); + key_unlink(newsp); + key_freesp(&newsp); + } else { + key_freesp(&newsp); + ipseclog((LOG_DEBUG, "%s: a SP entry exists already.", + __func__)); + return (key_senderror(so, m, EEXIST)); } } -#endif - newsp->created = time_second; - newsp->lastused = newsp->created; + /* allocate new SP entry */ + if ((newsp = key_msg2sp(xpl0, PFKEY_EXTLEN(xpl0), &error)) == NULL) { + return key_senderror(so, m, error); + } + + newsp->lastused = newsp->created = time_second; newsp->lifetime = lft ? lft->sadb_lifetime_addtime : 0; newsp->validtime = lft ? lft->sadb_lifetime_usetime : 0; + bcopy(&spidx, &newsp->spidx, sizeof(spidx)); - newsp->refcnt = 1; /* do not reclaim until I say I do */ - newsp->state = IPSEC_SPSTATE_ALIVE; - LIST_INSERT_TAIL(&V_sptree[newsp->spidx.dir], newsp, secpolicy, chain); - - /* delete the entry in spacqtree */ - if (mhp->msg->sadb_msg_type == SADB_X_SPDUPDATE) { - struct secspacq *spacq = key_getspacq(&spidx); - if (spacq != NULL) { - /* reset counter in order to deletion by timehandler. */ - spacq->created = time_second; - spacq->count = 0; - SPACQ_UNLOCK(); - } - } + /* XXXAE: there is race between key_getsp() and key_insertsp() */ + SPTREE_WLOCK(); + if ((newsp->id = key_getnewspid()) == 0) { + SPTREE_WUNLOCK(); + key_freesp(&newsp); + return key_senderror(so, m, ENOBUFS); + } + key_insertsp(newsp); + SPTREE_WUNLOCK(); + + KEYDBG(KEY_STAMP, + printf("%s: SP(%p)\n", __func__, newsp)); + KEYDBG(KEY_DATA, kdebug_secpolicy(newsp)); { struct mbuf *n, *mpolicy; struct sadb_msg *newmsg; int off; - /* - * Note: do not send SADB_X_EXT_NAT_T_* here: - * we are sending traffic endpoints. - */ - /* create new sadb_msg to reply. */ if (lft) { n = key_gather_mbuf(m, mhp, 2, 5, SADB_EXT_RESERVED, @@ -2013,30 +1979,32 @@ key_spdadd(so, m, mhp) * 0: failure. * others: success. */ -static u_int32_t -key_getnewspid() +static uint32_t +key_getnewspid(void) { - u_int32_t newid = 0; - int count = V_key_spi_trycnt; /* XXX */ struct secpolicy *sp; + uint32_t newid = 0; + int count = V_key_spi_trycnt; /* XXX */ - /* when requesting to allocate spi ranged */ + SPTREE_WLOCK_ASSERT(); while (count--) { - newid = (V_policy_id = (V_policy_id == ~0 ? 1 : V_policy_id + 1)); - - if ((sp = key_getspbyid(newid)) == NULL) + if (V_policy_id == ~0) /* overflowed */ + newid = V_policy_id = 1; + else + newid = ++V_policy_id; + LIST_FOREACH(sp, SPHASH_HASH(newid), idhash) { + if (sp->id == newid) + break; + } + if (sp == NULL) break; - - KEY_FREESP(&sp); } - if (count == 0 || newid == 0) { - ipseclog((LOG_DEBUG, "%s: to allocate policy id is failed.\n", - __func__)); - return 0; + ipseclog((LOG_DEBUG, "%s: failed to allocate policy id.\n", + __func__)); + return (0); } - - return newid; + return (newid); } /* @@ -2052,14 +2020,12 @@ key_getnewspid() * m will always be freed. */ static int -key_spddelete(so, m, mhp) - struct socket *so; - struct mbuf *m; - const struct sadb_msghdr *mhp; +key_spddelete(struct socket *so, struct mbuf *m, + const struct sadb_msghdr *mhp) { + struct secpolicyindex spidx; struct sadb_address *src0, *dst0; struct sadb_x_policy *xpl0; - struct secpolicyindex spidx; struct secpolicy *sp; IPSEC_ASSERT(so != NULL, ("null so")); @@ -2067,18 +2033,19 @@ key_spddelete(so, m, mhp) IPSEC_ASSERT(mhp != NULL, ("null msghdr")); IPSEC_ASSERT(mhp->msg != NULL, ("null msg")); - if (mhp->ext[SADB_EXT_ADDRESS_SRC] == NULL || - mhp->ext[SADB_EXT_ADDRESS_DST] == NULL || - mhp->ext[SADB_X_EXT_POLICY] == NULL) { - ipseclog((LOG_DEBUG, "%s: invalid message is passed.\n", - __func__)); + if (SADB_CHECKHDR(mhp, SADB_EXT_ADDRESS_SRC) || + SADB_CHECKHDR(mhp, SADB_EXT_ADDRESS_DST) || + SADB_CHECKHDR(mhp, SADB_X_EXT_POLICY)) { + ipseclog((LOG_DEBUG, + "%s: invalid message: missing required header.\n", + __func__)); return key_senderror(so, m, EINVAL); } - if (mhp->extlen[SADB_EXT_ADDRESS_SRC] < sizeof(struct sadb_address) || - mhp->extlen[SADB_EXT_ADDRESS_DST] < sizeof(struct sadb_address) || - mhp->extlen[SADB_X_EXT_POLICY] < sizeof(struct sadb_x_policy)) { - ipseclog((LOG_DEBUG, "%s: invalid message is passed.\n", - __func__)); + if (SADB_CHECKLEN(mhp, SADB_EXT_ADDRESS_SRC) || + SADB_CHECKLEN(mhp, SADB_EXT_ADDRESS_DST) || + SADB_CHECKLEN(mhp, SADB_X_EXT_POLICY)) { + ipseclog((LOG_DEBUG, + "%s: invalid message: wrong header size.\n", __func__)); return key_senderror(so, m, EINVAL); } @@ -2086,13 +2053,29 @@ key_spddelete(so, m, mhp) dst0 = (struct sadb_address *)mhp->ext[SADB_EXT_ADDRESS_DST]; xpl0 = (struct sadb_x_policy *)mhp->ext[SADB_X_EXT_POLICY]; - /* - * Note: do not parse SADB_X_EXT_NAT_T_* here: - * we are processing traffic endpoints. - */ - + /* check the direciton */ + switch (xpl0->sadb_x_policy_dir) { + case IPSEC_DIR_INBOUND: + case IPSEC_DIR_OUTBOUND: + break; + default: + ipseclog((LOG_DEBUG, "%s: invalid SP direction.\n", __func__)); + return key_senderror(so, m, EINVAL); + } + /* Only DISCARD, NONE and IPSEC are allowed */ + if (xpl0->sadb_x_policy_type != IPSEC_POLICY_DISCARD && + xpl0->sadb_x_policy_type != IPSEC_POLICY_NONE && + xpl0->sadb_x_policy_type != IPSEC_POLICY_IPSEC) { + ipseclog((LOG_DEBUG, "%s: invalid policy type.\n", __func__)); + return key_senderror(so, m, EINVAL); + } + if (key_checksockaddrs((struct sockaddr *)(src0 + 1), + (struct sockaddr *)(dst0 + 1)) != 0 || + src0->sadb_address_proto != dst0->sadb_address_proto) { + ipseclog((LOG_DEBUG, "%s: invalid sockaddr.\n", __func__)); + return key_senderror(so, m, EINVAL); + } /* make secindex */ - /* XXX boundary check against sa_len */ KEY_SETSECSPIDX(xpl0->sadb_x_policy_dir, src0 + 1, dst0 + 1, @@ -2101,16 +2084,6 @@ key_spddelete(so, m, mhp) src0->sadb_address_proto, &spidx); - /* checking the direciton. */ - switch (xpl0->sadb_x_policy_dir) { - case IPSEC_DIR_INBOUND: - case IPSEC_DIR_OUTBOUND: - break; - default: - ipseclog((LOG_DEBUG, "%s: Invalid SP direction.\n", __func__)); - return key_senderror(so, m, EINVAL); - } - /* Is there SP in SPD ? */ if ((sp = key_getsp(&spidx)) == NULL) { ipseclog((LOG_DEBUG, "%s: no SP found.\n", __func__)); @@ -2120,20 +2093,16 @@ key_spddelete(so, m, mhp) /* save policy id to buffer to be returned. */ xpl0->sadb_x_policy_id = sp->id; - SPTREE_LOCK(); - sp->state = IPSEC_SPSTATE_DEAD; - SPTREE_UNLOCK(); - KEY_FREESP(&sp); + KEYDBG(KEY_STAMP, + printf("%s: SP(%p)\n", __func__, sp)); + KEYDBG(KEY_DATA, kdebug_secpolicy(sp)); + key_unlink(sp); + key_freesp(&sp); { struct mbuf *n; struct sadb_msg *newmsg; - /* - * Note: do not send SADB_X_EXT_NAT_T_* here: - * we are sending traffic endpoints. - */ - /* create new sadb_msg to reply. */ n = key_gather_mbuf(m, mhp, 1, 4, SADB_EXT_RESERVED, SADB_X_EXT_POLICY, SADB_EXT_ADDRESS_SRC, SADB_EXT_ADDRESS_DST); @@ -2162,37 +2131,45 @@ key_spddelete(so, m, mhp) * m will always be freed. */ static int -key_spddelete2(so, m, mhp) - struct socket *so; - struct mbuf *m; - const struct sadb_msghdr *mhp; +key_spddelete2(struct socket *so, struct mbuf *m, + const struct sadb_msghdr *mhp) { - u_int32_t id; struct secpolicy *sp; + uint32_t id; IPSEC_ASSERT(so != NULL, ("null socket")); IPSEC_ASSERT(m != NULL, ("null mbuf")); IPSEC_ASSERT(mhp != NULL, ("null msghdr")); IPSEC_ASSERT(mhp->msg != NULL, ("null msg")); - if (mhp->ext[SADB_X_EXT_POLICY] == NULL || - mhp->extlen[SADB_X_EXT_POLICY] < sizeof(struct sadb_x_policy)) { - ipseclog((LOG_DEBUG, "%s: invalid message is passed.\n", __func__)); + if (SADB_CHECKHDR(mhp, SADB_X_EXT_POLICY) || + SADB_CHECKLEN(mhp, SADB_X_EXT_POLICY)) { + ipseclog((LOG_DEBUG, "%s: invalid message is passed.\n", + __func__)); return key_senderror(so, m, EINVAL); } - id = ((struct sadb_x_policy *)mhp->ext[SADB_X_EXT_POLICY])->sadb_x_policy_id; + id = ((struct sadb_x_policy *) + mhp->ext[SADB_X_EXT_POLICY])->sadb_x_policy_id; /* Is there SP in SPD ? */ if ((sp = key_getspbyid(id)) == NULL) { - ipseclog((LOG_DEBUG, "%s: no SP found id:%u.\n", __func__, id)); + ipseclog((LOG_DEBUG, "%s: no SP found for id %u.\n", + __func__, id)); return key_senderror(so, m, EINVAL); } - SPTREE_LOCK(); - sp->state = IPSEC_SPSTATE_DEAD; - SPTREE_UNLOCK(); - KEY_FREESP(&sp); + KEYDBG(KEY_STAMP, + printf("%s: SP(%p)\n", __func__, sp)); + KEYDBG(KEY_DATA, kdebug_secpolicy(sp)); + key_unlink(sp); + if (sp->state != IPSEC_SPSTATE_DEAD) { + ipseclog((LOG_DEBUG, "%s: failed to delete SP with id %u.\n", + __func__, id)); + key_freesp(&sp); + return (key_senderror(so, m, EACCES)); + } + key_freesp(&sp); { struct mbuf *n, *nn; @@ -2202,10 +2179,9 @@ key_spddelete2(so, m, mhp) /* create new sadb_msg to reply. */ len = PFKEY_ALIGN8(sizeof(struct sadb_msg)); - MGETHDR(n, M_DONTWAIT, MT_DATA); + MGETHDR(n, M_NOWAIT, MT_DATA); if (n && len > MHLEN) { - MCLGET(n, M_DONTWAIT); - if ((n->m_flags & M_EXT) == 0) { + if (!(MCLGET(n, M_NOWAIT))) { m_freem(n); n = NULL; } @@ -2224,7 +2200,7 @@ key_spddelete2(so, m, mhp) off, len)); n->m_next = m_copym(m, mhp->extoff[SADB_X_EXT_POLICY], - mhp->extlen[SADB_X_EXT_POLICY], M_DONTWAIT); + mhp->extlen[SADB_X_EXT_POLICY], M_NOWAIT); if (!n->m_next) { m_freem(n); return key_senderror(so, m, ENOBUFS); @@ -2244,7 +2220,7 @@ key_spddelete2(so, m, mhp) } /* - * SADB_X_GET processing + * SADB_X_SPDGET processing * receive * <base, policy(*)> * from the user(?), @@ -2256,37 +2232,37 @@ key_spddelete2(so, m, mhp) * m will always be freed. */ static int -key_spdget(so, m, mhp) - struct socket *so; - struct mbuf *m; - const struct sadb_msghdr *mhp; +key_spdget(struct socket *so, struct mbuf *m, const struct sadb_msghdr *mhp) { - u_int32_t id; struct secpolicy *sp; struct mbuf *n; + uint32_t id; IPSEC_ASSERT(so != NULL, ("null socket")); IPSEC_ASSERT(m != NULL, ("null mbuf")); IPSEC_ASSERT(mhp != NULL, ("null msghdr")); IPSEC_ASSERT(mhp->msg != NULL, ("null msg")); - if (mhp->ext[SADB_X_EXT_POLICY] == NULL || - mhp->extlen[SADB_X_EXT_POLICY] < sizeof(struct sadb_x_policy)) { + if (SADB_CHECKHDR(mhp, SADB_X_EXT_POLICY) || + SADB_CHECKLEN(mhp, SADB_X_EXT_POLICY)) { ipseclog((LOG_DEBUG, "%s: invalid message is passed.\n", - __func__)); + __func__)); return key_senderror(so, m, EINVAL); } - id = ((struct sadb_x_policy *)mhp->ext[SADB_X_EXT_POLICY])->sadb_x_policy_id; + id = ((struct sadb_x_policy *) + mhp->ext[SADB_X_EXT_POLICY])->sadb_x_policy_id; /* Is there SP in SPD ? */ if ((sp = key_getspbyid(id)) == NULL) { - ipseclog((LOG_DEBUG, "%s: no SP found id:%u.\n", __func__, id)); + ipseclog((LOG_DEBUG, "%s: no SP found for id %u.\n", + __func__, id)); return key_senderror(so, m, ENOENT); } - n = key_setdumpsp(sp, SADB_X_SPDGET, 0, mhp->msg->sadb_msg_pid); - KEY_FREESP(&sp); + n = key_setdumpsp(sp, SADB_X_SPDGET, mhp->msg->sadb_msg_seq, + mhp->msg->sadb_msg_pid); + key_freesp(&sp); if (n != NULL) { m_freem(m); return key_sendup_mbuf(so, n, KEY_SENDUP_ONE); @@ -2300,7 +2276,7 @@ key_spdget(so, m, mhp) * send * <base, policy(*)> * to KMD, and expect to receive - * <base> with SADB_X_SPDACQUIRE if error occured, + * <base> with SADB_X_SPDACQUIRE if error occurred, * or * <base, policy> * with SADB_X_SPDUPDATE from KMD by PF_KEY. @@ -2310,8 +2286,7 @@ key_spdget(so, m, mhp) * others: error number */ int -key_spdacquire(sp) - struct secpolicy *sp; +key_spdacquire(struct secpolicy *sp) { struct mbuf *result = NULL, *m; struct secspacq *newspacq; @@ -2330,7 +2305,8 @@ key_spdacquire(sp) } else { /* increment counter and do nothing. */ newspacq->count++; - return 0; + SPACQ_UNLOCK(); + return (0); } SPACQ_UNLOCK(); } else { @@ -2370,13 +2346,11 @@ key_spdacquire(sp) * m will always be freed. */ static int -key_spdflush(so, m, mhp) - struct socket *so; - struct mbuf *m; - const struct sadb_msghdr *mhp; +key_spdflush(struct socket *so, struct mbuf *m, const struct sadb_msghdr *mhp) { + struct secpolicy_queue drainq; struct sadb_msg *newmsg; - struct secpolicy *sp; + struct secpolicy *sp, *nextsp; u_int dir; IPSEC_ASSERT(so != NULL, ("null socket")); @@ -2387,11 +2361,27 @@ key_spdflush(so, m, mhp) if (m->m_len != PFKEY_ALIGN8(sizeof(struct sadb_msg))) return key_senderror(so, m, EINVAL); + TAILQ_INIT(&drainq); + SPTREE_WLOCK(); for (dir = 0; dir < IPSEC_DIR_MAX; dir++) { - SPTREE_LOCK(); - LIST_FOREACH(sp, &V_sptree[dir], chain) - sp->state = IPSEC_SPSTATE_DEAD; - SPTREE_UNLOCK(); + TAILQ_CONCAT(&drainq, &V_sptree[dir], chain); + } + /* + * We need to set state to DEAD for each policy to be sure, + * that another thread won't try to unlink it. + * Also remove SP from sphash. + */ + TAILQ_FOREACH(sp, &drainq, chain) { + sp->state = IPSEC_SPSTATE_DEAD; + LIST_REMOVE(sp, idhash); + } + V_sp_genid++; + SPTREE_WUNLOCK(); + sp = TAILQ_FIRST(&drainq); + while (sp != NULL) { + nextsp = TAILQ_NEXT(sp, chain); + key_freesp(&sp); + sp = nextsp; } if (sizeof(struct sadb_msg) > m->m_len + M_TRAILINGSPACE(m)) { @@ -2410,27 +2400,34 @@ key_spdflush(so, m, mhp) return key_sendup_mbuf(so, m, KEY_SENDUP_ALL); } +static uint8_t +key_satype2scopemask(uint8_t satype) +{ + + if (satype == IPSEC_POLICYSCOPE_ANY) + return (0xff); + return (satype); +} /* * SADB_SPDDUMP processing * receive * <base> - * from the user, and dump all SP leaves - * and send, + * from the user, and dump all SP leaves and send, * <base> ..... * to the ikmpd. * - * m will always be freed. + * NOTE: + * sadb_msg_satype is considered as mask of policy scopes. + * m will always be freed. */ static int -key_spddump(so, m, mhp) - struct socket *so; - struct mbuf *m; - const struct sadb_msghdr *mhp; +key_spddump(struct socket *so, struct mbuf *m, const struct sadb_msghdr *mhp) { + SPTREE_RLOCK_TRACKER; struct secpolicy *sp; - int cnt; - u_int dir; struct mbuf *n; + int cnt; + u_int dir, scope; IPSEC_ASSERT(so != NULL, ("null socket")); IPSEC_ASSERT(m != NULL, ("null mbuf")); @@ -2439,36 +2436,55 @@ key_spddump(so, m, mhp) /* search SPD entry and get buffer size. */ cnt = 0; - SPTREE_LOCK(); + scope = key_satype2scopemask(mhp->msg->sadb_msg_satype); + SPTREE_RLOCK(); for (dir = 0; dir < IPSEC_DIR_MAX; dir++) { - LIST_FOREACH(sp, &V_sptree[dir], chain) { - cnt++; + if (scope & IPSEC_POLICYSCOPE_GLOBAL) { + TAILQ_FOREACH(sp, &V_sptree[dir], chain) + cnt++; + } + if (scope & IPSEC_POLICYSCOPE_IFNET) { + TAILQ_FOREACH(sp, &V_sptree_ifnet[dir], chain) + cnt++; } } if (cnt == 0) { - SPTREE_UNLOCK(); + SPTREE_RUNLOCK(); return key_senderror(so, m, ENOENT); } for (dir = 0; dir < IPSEC_DIR_MAX; dir++) { - LIST_FOREACH(sp, &V_sptree[dir], chain) { - --cnt; - n = key_setdumpsp(sp, SADB_X_SPDDUMP, cnt, - mhp->msg->sadb_msg_pid); + if (scope & IPSEC_POLICYSCOPE_GLOBAL) { + TAILQ_FOREACH(sp, &V_sptree[dir], chain) { + --cnt; + n = key_setdumpsp(sp, SADB_X_SPDDUMP, cnt, + mhp->msg->sadb_msg_pid); + + if (n != NULL) + key_sendup_mbuf(so, n, KEY_SENDUP_ONE); + } + } + if (scope & IPSEC_POLICYSCOPE_IFNET) { + TAILQ_FOREACH(sp, &V_sptree_ifnet[dir], chain) { + --cnt; + n = key_setdumpsp(sp, SADB_X_SPDDUMP, cnt, + mhp->msg->sadb_msg_pid); - if (n) - key_sendup_mbuf(so, n, KEY_SENDUP_ONE); + if (n != NULL) + key_sendup_mbuf(so, n, KEY_SENDUP_ONE); + } } } - SPTREE_UNLOCK(); + SPTREE_RUNLOCK(); m_freem(m); - return 0; + return (0); } static struct mbuf * -key_setdumpsp(struct secpolicy *sp, u_int8_t type, u_int32_t seq, u_int32_t pid) +key_setdumpsp(struct secpolicy *sp, u_int8_t type, u_int32_t seq, + u_int32_t pid) { struct mbuf *result = NULL, *m; struct seclifetime lt; @@ -2478,10 +2494,6 @@ key_setdumpsp(struct secpolicy *sp, u_int8_t type, u_int32_t seq, u_int32_t pid) goto fail; result = m; - /* - * Note: do not send SADB_X_EXT_NAT_T_* here: - * we are sending traffic endpoints. - */ m = key_setsadbaddr(SADB_EXT_ADDRESS_SRC, &sp->spidx.src.sa, sp->spidx.prefs, sp->spidx.ul_proto); @@ -2496,7 +2508,7 @@ key_setdumpsp(struct secpolicy *sp, u_int8_t type, u_int32_t seq, u_int32_t pid) goto fail; m_cat(result, m); - m = key_sp2msg(sp); + m = key_sp2mbuf(sp); if (!m) goto fail; m_cat(result, m); @@ -2539,37 +2551,29 @@ fail: m_freem(result); return NULL; } - /* * get PFKEY message length for security policy and request. */ -static u_int -key_getspreqmsglen(sp) - struct secpolicy *sp; +static size_t +key_getspreqmsglen(struct secpolicy *sp) { - u_int tlen; + size_t tlen, len; + int i; tlen = sizeof(struct sadb_x_policy); - /* if is the policy for ipsec ? */ if (sp->policy != IPSEC_POLICY_IPSEC) - return tlen; + return (tlen); /* get length of ipsec requests */ - { - struct ipsecrequest *isr; - int len; - - for (isr = sp->req; isr != NULL; isr = isr->next) { + for (i = 0; i < sp->tcount; i++) { len = sizeof(struct sadb_x_ipsecrequest) - + isr->saidx.src.sa.sa_len - + isr->saidx.dst.sa.sa_len; + + sp->req[i]->saidx.src.sa.sa_len + + sp->req[i]->saidx.dst.sa.sa_len; tlen += PFKEY_ALIGN8(len); } - } - - return tlen; + return (tlen); } /* @@ -2582,18 +2586,18 @@ key_getspreqmsglen(sp) * others : error number */ static int -key_spdexpire(sp) - struct secpolicy *sp; +key_spdexpire(struct secpolicy *sp) { - struct mbuf *result = NULL, *m; - int len; - int error = -1; struct sadb_lifetime *lt; - - /* XXX: Why do we lock ? */ + struct mbuf *result = NULL, *m; + int len, error = -1; IPSEC_ASSERT(sp != NULL, ("null secpolicy")); + KEYDBG(KEY_STAMP, + printf("%s: SP(%p)\n", __func__, sp)); + KEYDBG(KEY_DATA, kdebug_secpolicy(sp)); + /* set msg header */ m = key_setsadbmsg(SADB_X_SPDEXPIRE, 0, 0, 0, 0, 0); if (!m) { @@ -2604,13 +2608,13 @@ key_spdexpire(sp) /* create lifetime extension (current and hard) */ len = PFKEY_ALIGN8(sizeof(*lt)) * 2; - m = key_alloc_mbuf(len); - if (!m || m->m_next) { /*XXX*/ - if (m) - m_freem(m); + m = m_get2(len, M_NOWAIT, MT_DATA, 0); + if (m == NULL) { error = ENOBUFS; goto fail; } + m_align(m, len); + m->m_len = len; bzero(mtod(m, caddr_t), len); lt = mtod(m, struct sadb_lifetime *); lt->sadb_lifetime_len = PFKEY_UNIT64(sizeof(struct sadb_lifetime)); @@ -2628,11 +2632,6 @@ key_spdexpire(sp) lt->sadb_lifetime_usetime = sp->validtime; m_cat(result, m); - /* - * Note: do not send SADB_X_EXT_NAT_T_* here: - * we are sending traffic endpoints. - */ - /* set sadb_address for source */ m = key_setsadbaddr(SADB_EXT_ADDRESS_SRC, &sp->spidx.src.sa, @@ -2654,7 +2653,7 @@ key_spdexpire(sp) m_cat(result, m); /* set secpolicy */ - m = key_sp2msg(sp); + m = key_sp2mbuf(sp); if (!m) { error = ENOBUFS; goto fail; @@ -2691,185 +2690,220 @@ key_spdexpire(sp) /* %%% SAD management */ /* - * allocating a memory for new SA head, and copy from the values of mhp. + * allocating and initialize new SA head. * OUT: NULL : failure due to the lack of memory. * others : pointer to new SA head. */ static struct secashead * -key_newsah(saidx) - struct secasindex *saidx; +key_newsah(struct secasindex *saidx) { - struct secashead *newsah; + struct secashead *sah; - IPSEC_ASSERT(saidx != NULL, ("null saidx")); + sah = malloc(sizeof(struct secashead), M_IPSEC_SAH, + M_NOWAIT | M_ZERO); + if (sah == NULL) { + PFKEYSTAT_INC(in_nomem); + return (NULL); + } + TAILQ_INIT(&sah->savtree_larval); + TAILQ_INIT(&sah->savtree_alive); + sah->saidx = *saidx; + sah->state = SADB_SASTATE_DEAD; + SAH_INITREF(sah); - newsah = malloc(sizeof(struct secashead), M_IPSEC_SAH, M_NOWAIT|M_ZERO); - if (newsah != NULL) { - int i; - for (i = 0; i < sizeof(newsah->savtree)/sizeof(newsah->savtree[0]); i++) - LIST_INIT(&newsah->savtree[i]); - newsah->saidx = *saidx; + KEYDBG(KEY_STAMP, + printf("%s: SAH(%p)\n", __func__, sah)); + KEYDBG(KEY_DATA, kdebug_secash(sah, NULL)); + return (sah); +} - /* add to saidxtree */ - newsah->state = SADB_SASTATE_MATURE; +static void +key_freesah(struct secashead **psah) +{ + struct secashead *sah = *psah; - SAHTREE_LOCK(); - LIST_INSERT_HEAD(&V_sahtree, newsah, chain); - SAHTREE_UNLOCK(); - } - return(newsah); + if (SAH_DELREF(sah) == 0) + return; + + KEYDBG(KEY_STAMP, + printf("%s: last reference to SAH(%p)\n", __func__, sah)); + KEYDBG(KEY_DATA, kdebug_secash(sah, NULL)); + + *psah = NULL; + key_delsah(sah); } -/* - * delete SA index and all SA registerd. - */ static void -key_delsah(sah) - struct secashead *sah; +key_delsah(struct secashead *sah) { - struct secasvar *sav, *nextsav; - u_int stateidx; - int zombie = 0; - IPSEC_ASSERT(sah != NULL, ("NULL sah")); - SAHTREE_LOCK_ASSERT(); - - /* searching all SA registerd in the secindex. */ - for (stateidx = 0; - stateidx < _ARRAYLEN(saorder_state_any); - stateidx++) { - u_int state = saorder_state_any[stateidx]; - LIST_FOREACH_SAFE(sav, &sah->savtree[state], chain, nextsav) { - if (sav->refcnt == 0) { - /* sanity check */ - KEY_CHKSASTATE(state, sav->state, __func__); - /* - * do NOT call KEY_FREESAV here: - * it will only delete the sav if refcnt == 1, - * where we already know that refcnt == 0 - */ - key_delsav(sav); - } else { - /* give up to delete this sa */ - zombie++; - } - } - } - if (!zombie) { /* delete only if there are savs */ - /* remove from tree of SA index */ - if (__LIST_CHAINED(sah)) - LIST_REMOVE(sah, chain); - if (sah->route_cache.sa_route.ro_rt) { - RTFREE(sah->route_cache.sa_route.ro_rt); - sah->route_cache.sa_route.ro_rt = (struct rtentry *)NULL; - } - free(sah, M_IPSEC_SAH); - } + IPSEC_ASSERT(sah->state == SADB_SASTATE_DEAD, + ("Attempt to free non DEAD SAH %p", sah)); + IPSEC_ASSERT(TAILQ_EMPTY(&sah->savtree_larval), + ("Attempt to free SAH %p with LARVAL SA", sah)); + IPSEC_ASSERT(TAILQ_EMPTY(&sah->savtree_alive), + ("Attempt to free SAH %p with ALIVE SA", sah)); + + free(sah, M_IPSEC_SAH); } /* - * allocating a new SA with LARVAL state. key_add() and key_getspi() call, + * allocating a new SA for key_add() and key_getspi() call, * and copy the values of mhp into new buffer. - * When SAD message type is GETSPI: - * to set sequence number from acq_seq++, - * to set zero to SPI. - * not to call key_setsava(). + * When SAD message type is SADB_GETSPI set SA state to LARVAL. + * For SADB_ADD create and initialize SA with MATURE state. * OUT: NULL : fail * others : pointer to new secasvar. - * - * does not modify mbuf. does not free mbuf on error. */ static struct secasvar * -key_newsav(m, mhp, sah, errp, where, tag) - struct mbuf *m; - const struct sadb_msghdr *mhp; - struct secashead *sah; - int *errp; - const char* where; - int tag; +key_newsav(const struct sadb_msghdr *mhp, struct secasindex *saidx, + uint32_t spi, int *errp) { - struct secasvar *newsav; - const struct sadb_sa *xsa; + struct secashead *sah; + struct secasvar *sav; + int isnew; - IPSEC_ASSERT(m != NULL, ("null mbuf")); IPSEC_ASSERT(mhp != NULL, ("null msghdr")); IPSEC_ASSERT(mhp->msg != NULL, ("null msg")); - IPSEC_ASSERT(sah != NULL, ("null secashead")); - - newsav = malloc(sizeof(struct secasvar), M_IPSEC_SA, M_NOWAIT|M_ZERO); - if (newsav == NULL) { - ipseclog((LOG_DEBUG, "%s: No more memory.\n", __func__)); - *errp = ENOBUFS; - goto done; - } - - switch (mhp->msg->sadb_msg_type) { - case SADB_GETSPI: - newsav->spi = 0; - -#ifdef IPSEC_DOSEQCHECK - /* sync sequence number */ - if (mhp->msg->sadb_msg_seq == 0) - newsav->seq = - (V_acq_seq = (V_acq_seq == ~0 ? 1 : ++V_acq_seq)); - else -#endif - newsav->seq = mhp->msg->sadb_msg_seq; - break; + IPSEC_ASSERT(mhp->msg->sadb_msg_type == SADB_GETSPI || + mhp->msg->sadb_msg_type == SADB_ADD, ("wrong message type")); - case SADB_ADD: - /* sanity check */ - if (mhp->ext[SADB_EXT_SA] == NULL) { - free(newsav, M_IPSEC_SA); - newsav = NULL; - ipseclog((LOG_DEBUG, "%s: invalid message is passed.\n", - __func__)); + sav = NULL; + sah = NULL; + /* check SPI value */ + switch (saidx->proto) { + case IPPROTO_ESP: + case IPPROTO_AH: + /* + * RFC 4302, 2.4. Security Parameters Index (SPI), SPI values + * 1-255 reserved by IANA for future use, + * 0 for implementation specific, local use. + */ + if (ntohl(spi) <= 255) { + ipseclog((LOG_DEBUG, "%s: illegal range of SPI %u.\n", + __func__, ntohl(spi))); *errp = EINVAL; goto done; } - xsa = (const struct sadb_sa *)mhp->ext[SADB_EXT_SA]; - newsav->spi = xsa->sadb_sa_spi; - newsav->seq = mhp->msg->sadb_msg_seq; break; - default: - free(newsav, M_IPSEC_SA); - newsav = NULL; - *errp = EINVAL; - goto done; } + sav = malloc(sizeof(struct secasvar), M_IPSEC_SA, M_NOWAIT | M_ZERO); + if (sav == NULL) { + *errp = ENOBUFS; + goto done; + } + sav->lock = malloc(sizeof(struct mtx), M_IPSEC_MISC, + M_NOWAIT | M_ZERO); + if (sav->lock == NULL) { + *errp = ENOBUFS; + goto done; + } + mtx_init(sav->lock, "ipsec association", NULL, MTX_DEF); + sav->lft_c = uma_zalloc(V_key_lft_zone, M_NOWAIT); + if (sav->lft_c == NULL) { + *errp = ENOBUFS; + goto done; + } + counter_u64_zero(sav->lft_c_allocations); + counter_u64_zero(sav->lft_c_bytes); - /* copy sav values */ - if (mhp->msg->sadb_msg_type != SADB_GETSPI) { - *errp = key_setsaval(newsav, m, mhp); - if (*errp) { - free(newsav, M_IPSEC_SA); - newsav = NULL; + sav->spi = spi; + sav->seq = mhp->msg->sadb_msg_seq; + sav->state = SADB_SASTATE_LARVAL; + sav->pid = (pid_t)mhp->msg->sadb_msg_pid; + SAV_INITREF(sav); +again: + sah = key_getsah(saidx); + if (sah == NULL) { + /* create a new SA index */ + sah = key_newsah(saidx); + if (sah == NULL) { + ipseclog((LOG_DEBUG, + "%s: No more memory.\n", __func__)); + *errp = ENOBUFS; goto done; } - } - - SECASVAR_LOCK_INIT(newsav); - - /* reset created */ - newsav->created = time_second; - newsav->pid = mhp->msg->sadb_msg_pid; + isnew = 1; + } else + isnew = 0; - /* add to satree */ - newsav->sah = sah; - sa_initref(newsav); - newsav->state = SADB_SASTATE_LARVAL; + sav->sah = sah; + if (mhp->msg->sadb_msg_type == SADB_GETSPI) { + sav->created = time_second; + } else if (sav->state == SADB_SASTATE_LARVAL) { + /* + * Do not call key_setsaval() second time in case + * of `goto again`. We will have MATURE state. + */ + *errp = key_setsaval(sav, mhp); + if (*errp != 0) + goto done; + sav->state = SADB_SASTATE_MATURE; + } - SAHTREE_LOCK(); - LIST_INSERT_TAIL(&sah->savtree[SADB_SASTATE_LARVAL], newsav, - secasvar, chain); - SAHTREE_UNLOCK(); + SAHTREE_WLOCK(); + /* + * Check that existing SAH wasn't unlinked. + * Since we didn't hold the SAHTREE lock, it is possible, + * that callout handler or key_flush() or key_delete() could + * unlink this SAH. + */ + if (isnew == 0 && sah->state == SADB_SASTATE_DEAD) { + SAHTREE_WUNLOCK(); + key_freesah(&sah); /* reference from key_getsah() */ + goto again; + } + if (isnew != 0) { + /* + * Add new SAH into SADB. + * + * XXXAE: we can serialize key_add and key_getspi calls, so + * several threads will not fight in the race. + * Otherwise we should check under SAHTREE lock, that this + * SAH would not added twice. + */ + TAILQ_INSERT_HEAD(&V_sahtree, sah, chain); + /* Add new SAH into hash by addresses */ + LIST_INSERT_HEAD(SAHADDRHASH_HASH(saidx), sah, addrhash); + /* Now we are linked in the chain */ + sah->state = SADB_SASTATE_MATURE; + /* + * SAV references this new SAH. + * In case of existing SAH we reuse reference + * from key_getsah(). + */ + SAH_ADDREF(sah); + } + /* Link SAV with SAH */ + if (sav->state == SADB_SASTATE_MATURE) + TAILQ_INSERT_HEAD(&sah->savtree_alive, sav, chain); + else + TAILQ_INSERT_HEAD(&sah->savtree_larval, sav, chain); + /* Add SAV into SPI hash */ + LIST_INSERT_HEAD(SAVHASH_HASH(sav->spi), sav, spihash); + SAHTREE_WUNLOCK(); + *errp = 0; /* success */ done: - KEYDEBUG(KEYDEBUG_IPSEC_STAMP, - printf("DP %s from %s:%u return SP:%p\n", __func__, - where, tag, newsav)); - - return newsav; + if (*errp != 0) { + if (sav != NULL) { + if (sav->lock != NULL) { + mtx_destroy(sav->lock); + free(sav->lock, M_IPSEC_MISC); + } + if (sav->lft_c != NULL) + uma_zfree(V_key_lft_zone, sav->lft_c); + free(sav, M_IPSEC_SA), sav = NULL; + } + if (sah != NULL) + key_freesah(&sah); + if (*errp == ENOBUFS) { + ipseclog((LOG_DEBUG, "%s: No more memory.\n", + __func__)); + PFKEYSTAT_INC(in_nomem); + } + } + return (sav); } /* @@ -2878,6 +2912,13 @@ done: static void key_cleansav(struct secasvar *sav) { + + if (sav->natt != NULL) { + free(sav->natt, M_IPSEC_MISC); + sav->natt = NULL; + } + if (sav->flags & SADB_X_EXT_F_CLONED) + return; /* * Cleanup xform state. Note that zeroize'ing causes the * keys to be cleared; otherwise we must do it ourself. @@ -2886,7 +2927,6 @@ key_cleansav(struct secasvar *sav) sav->tdb_xform->xf_zeroize(sav); sav->tdb_xform = NULL; } else { - KASSERT(sav->iv == NULL, ("iv but no xform")); if (sav->key_auth != NULL) bzero(sav->key_auth->key_data, _KEYLEN(sav->key_auth)); if (sav->key_enc != NULL) @@ -2904,19 +2944,12 @@ key_cleansav(struct secasvar *sav) free(sav->key_enc, M_IPSEC_MISC); sav->key_enc = NULL; } - if (sav->sched) { - bzero(sav->sched, sav->schedlen); - free(sav->sched, M_IPSEC_MISC); - sav->sched = NULL; - } if (sav->replay != NULL) { + if (sav->replay->bitmap != NULL) + free(sav->replay->bitmap, M_IPSEC_MISC); free(sav->replay, M_IPSEC_MISC); sav->replay = NULL; } - if (sav->lft_c != NULL) { - free(sav->lft_c, M_IPSEC_MISC); - sav->lft_c = NULL; - } if (sav->lft_h != NULL) { free(sav->lft_h, M_IPSEC_MISC); sav->lft_h = NULL; @@ -2931,202 +2964,288 @@ key_cleansav(struct secasvar *sav) * free() SA variable entry. */ static void -key_delsav(sav) - struct secasvar *sav; +key_delsav(struct secasvar *sav) { IPSEC_ASSERT(sav != NULL, ("null sav")); - IPSEC_ASSERT(sav->refcnt == 0, ("reference count %u > 0", sav->refcnt)); + IPSEC_ASSERT(sav->state == SADB_SASTATE_DEAD, + ("attempt to free non DEAD SA %p", sav)); + IPSEC_ASSERT(sav->refcnt == 0, ("reference count %u > 0", + sav->refcnt)); - /* remove from SA header */ - if (__LIST_CHAINED(sav)) - LIST_REMOVE(sav, chain); + /* + * SA must be unlinked from the chain and hashtbl. + * If SA was cloned, we leave all fields untouched, + * except NAT-T config. + */ key_cleansav(sav); - SECASVAR_LOCK_DESTROY(sav); + if ((sav->flags & SADB_X_EXT_F_CLONED) == 0) { + mtx_destroy(sav->lock); + free(sav->lock, M_IPSEC_MISC); + uma_zfree(V_key_lft_zone, sav->lft_c); + } free(sav, M_IPSEC_SA); } /* - * search SAD. + * search SAH. * OUT: * NULL : not found - * others : found, pointer to a SA. + * others : found, referenced pointer to a SAH. */ static struct secashead * -key_getsah(saidx) - struct secasindex *saidx; +key_getsah(struct secasindex *saidx) { + SAHTREE_RLOCK_TRACKER; struct secashead *sah; - SAHTREE_LOCK(); - LIST_FOREACH(sah, &V_sahtree, chain) { - if (sah->state == SADB_SASTATE_DEAD) - continue; - if (key_cmpsaidx(&sah->saidx, saidx, CMP_REQID)) - break; + SAHTREE_RLOCK(); + LIST_FOREACH(sah, SAHADDRHASH_HASH(saidx), addrhash) { + if (key_cmpsaidx(&sah->saidx, saidx, CMP_MODE_REQID) != 0) { + SAH_ADDREF(sah); + break; + } } - SAHTREE_UNLOCK(); - - return sah; + SAHTREE_RUNLOCK(); + return (sah); } /* - * check not to be duplicated SPI. - * NOTE: this function is too slow due to searching all SAD. + * Check not to be duplicated SPI. * OUT: - * NULL : not found - * others : found, pointer to a SA. + * 0 : not found + * 1 : found SA with given SPI. */ -static struct secasvar * -key_checkspidup(saidx, spi) - struct secasindex *saidx; - u_int32_t spi; +static int +key_checkspidup(uint32_t spi) { - struct secashead *sah; + SAHTREE_RLOCK_TRACKER; struct secasvar *sav; - /* check address family */ - if (saidx->src.sa.sa_family != saidx->dst.sa.sa_family) { - ipseclog((LOG_DEBUG, "%s: address family mismatched.\n", - __func__)); - return NULL; - } - - sav = NULL; - /* check all SAD */ - SAHTREE_LOCK(); - LIST_FOREACH(sah, &V_sahtree, chain) { - if (!key_ismyaddr((struct sockaddr *)&sah->saidx.dst)) - continue; - sav = key_getsavbyspi(sah, spi); - if (sav != NULL) + /* Assume SPI is in network byte order */ + SAHTREE_RLOCK(); + LIST_FOREACH(sav, SAVHASH_HASH(spi), spihash) { + if (sav->spi == spi) break; } - SAHTREE_UNLOCK(); - - return sav; + SAHTREE_RUNLOCK(); + return (sav != NULL); } /* - * search SAD litmited alive SA, protocol, SPI. + * Search SA by SPI. * OUT: * NULL : not found - * others : found, pointer to a SA. + * others : found, referenced pointer to a SA. */ static struct secasvar * -key_getsavbyspi(sah, spi) - struct secashead *sah; - u_int32_t spi; +key_getsavbyspi(uint32_t spi) { + SAHTREE_RLOCK_TRACKER; struct secasvar *sav; - u_int stateidx, state; - - sav = NULL; - SAHTREE_LOCK_ASSERT(); - /* search all status */ - for (stateidx = 0; - stateidx < _ARRAYLEN(saorder_state_alive); - stateidx++) { - - state = saorder_state_alive[stateidx]; - LIST_FOREACH(sav, &sah->savtree[state], chain) { - - /* sanity check */ - if (sav->state != state) { - ipseclog((LOG_DEBUG, "%s: " - "invalid sav->state (queue: %d SA: %d)\n", - __func__, state, sav->state)); - continue; - } - if (sav->spi == spi) - return sav; - } + /* Assume SPI is in network byte order */ + SAHTREE_RLOCK(); + LIST_FOREACH(sav, SAVHASH_HASH(spi), spihash) { + if (sav->spi != spi) + continue; + SAV_ADDREF(sav); + break; } + SAHTREE_RUNLOCK(); + return (sav); +} - return NULL; +static int +key_updatelifetimes(struct secasvar *sav, const struct sadb_msghdr *mhp) +{ + struct seclifetime *lft_h, *lft_s, *tmp; + + /* Lifetime extension is optional, check that it is present. */ + if (SADB_CHECKHDR(mhp, SADB_EXT_LIFETIME_HARD) && + SADB_CHECKHDR(mhp, SADB_EXT_LIFETIME_SOFT)) { + /* + * In case of SADB_UPDATE we may need to change + * existing lifetimes. + */ + if (sav->state == SADB_SASTATE_MATURE) { + lft_h = lft_s = NULL; + goto reset; + } + return (0); + } + /* Both HARD and SOFT extensions must present */ + if ((SADB_CHECKHDR(mhp, SADB_EXT_LIFETIME_HARD) && + !SADB_CHECKHDR(mhp, SADB_EXT_LIFETIME_SOFT)) || + (SADB_CHECKHDR(mhp, SADB_EXT_LIFETIME_SOFT) && + !SADB_CHECKHDR(mhp, SADB_EXT_LIFETIME_HARD))) { + ipseclog((LOG_DEBUG, + "%s: invalid message: missing required header.\n", + __func__)); + return (EINVAL); + } + if (SADB_CHECKLEN(mhp, SADB_EXT_LIFETIME_HARD) || + SADB_CHECKLEN(mhp, SADB_EXT_LIFETIME_SOFT)) { + ipseclog((LOG_DEBUG, + "%s: invalid message: wrong header size.\n", __func__)); + return (EINVAL); + } + lft_h = key_dup_lifemsg((const struct sadb_lifetime *) + mhp->ext[SADB_EXT_LIFETIME_HARD], M_IPSEC_MISC); + if (lft_h == NULL) { + PFKEYSTAT_INC(in_nomem); + ipseclog((LOG_DEBUG, "%s: No more memory.\n", __func__)); + return (ENOBUFS); + } + lft_s = key_dup_lifemsg((const struct sadb_lifetime *) + mhp->ext[SADB_EXT_LIFETIME_SOFT], M_IPSEC_MISC); + if (lft_s == NULL) { + PFKEYSTAT_INC(in_nomem); + free(lft_h, M_IPSEC_MISC); + ipseclog((LOG_DEBUG, "%s: No more memory.\n", __func__)); + return (ENOBUFS); + } +reset: + if (sav->state != SADB_SASTATE_LARVAL) { + /* + * key_update() holds reference to this SA, + * so it won't be deleted in meanwhile. + */ + SECASVAR_LOCK(sav); + tmp = sav->lft_h; + sav->lft_h = lft_h; + lft_h = tmp; + + tmp = sav->lft_s; + sav->lft_s = lft_s; + lft_s = tmp; + SECASVAR_UNLOCK(sav); + if (lft_h != NULL) + free(lft_h, M_IPSEC_MISC); + if (lft_s != NULL) + free(lft_s, M_IPSEC_MISC); + return (0); + } + /* We can update lifetime without holding a lock */ + IPSEC_ASSERT(sav->lft_h == NULL, ("lft_h is already initialized\n")); + IPSEC_ASSERT(sav->lft_s == NULL, ("lft_s is already initialized\n")); + sav->lft_h = lft_h; + sav->lft_s = lft_s; + return (0); } /* - * copy SA values from PF_KEY message except *SPI, SEQ, PID, STATE and TYPE*. - * You must update these if need. + * copy SA values from PF_KEY message except *SPI, SEQ, PID and TYPE*. + * You must update these if need. Expects only LARVAL SAs. * OUT: 0: success. * !0: failure. - * - * does not modify mbuf. does not free mbuf on error. */ static int -key_setsaval(sav, m, mhp) - struct secasvar *sav; - struct mbuf *m; - const struct sadb_msghdr *mhp; +key_setsaval(struct secasvar *sav, const struct sadb_msghdr *mhp) { - int error = 0; + const struct sadb_sa *sa0; + const struct sadb_key *key0; + uint32_t replay; + size_t len; + int error; - IPSEC_ASSERT(m != NULL, ("null mbuf")); IPSEC_ASSERT(mhp != NULL, ("null msghdr")); IPSEC_ASSERT(mhp->msg != NULL, ("null msg")); + IPSEC_ASSERT(sav->state == SADB_SASTATE_LARVAL, + ("Attempt to update non LARVAL SA")); - /* initialization */ - sav->replay = NULL; - sav->key_auth = NULL; - sav->key_enc = NULL; - sav->sched = NULL; - sav->schedlen = 0; - sav->iv = NULL; - sav->lft_c = NULL; - sav->lft_h = NULL; - sav->lft_s = NULL; - sav->tdb_xform = NULL; /* transform */ - sav->tdb_encalgxform = NULL; /* encoding algorithm */ - sav->tdb_authalgxform = NULL; /* authentication algorithm */ - sav->tdb_compalgxform = NULL; /* compression algorithm */ - /* Initialize even if NAT-T not compiled in: */ - sav->natt_type = 0; - sav->natt_esp_frag_len = 0; + /* XXX rewrite */ + error = key_setident(sav->sah, mhp); + if (error != 0) + goto fail; /* SA */ - if (mhp->ext[SADB_EXT_SA] != NULL) { - const struct sadb_sa *sa0; - - sa0 = (const struct sadb_sa *)mhp->ext[SADB_EXT_SA]; - if (mhp->extlen[SADB_EXT_SA] < sizeof(*sa0)) { + if (!SADB_CHECKHDR(mhp, SADB_EXT_SA)) { + if (SADB_CHECKLEN(mhp, SADB_EXT_SA)) { error = EINVAL; goto fail; } - + sa0 = (const struct sadb_sa *)mhp->ext[SADB_EXT_SA]; sav->alg_auth = sa0->sadb_sa_auth; sav->alg_enc = sa0->sadb_sa_encrypt; sav->flags = sa0->sadb_sa_flags; + if ((sav->flags & SADB_KEY_FLAGS_MAX) != sav->flags) { + ipseclog((LOG_DEBUG, + "%s: invalid sa_flags 0x%08x.\n", __func__, + sav->flags)); + error = EINVAL; + goto fail; + } + + /* Optional replay window */ + replay = 0; + if ((sa0->sadb_sa_flags & SADB_X_EXT_OLD) == 0) + replay = sa0->sadb_sa_replay; + if (!SADB_CHECKHDR(mhp, SADB_X_EXT_SA_REPLAY)) { + if (SADB_CHECKLEN(mhp, SADB_X_EXT_SA_REPLAY)) { + error = EINVAL; + goto fail; + } + replay = ((const struct sadb_x_sa_replay *) + mhp->ext[SADB_X_EXT_SA_REPLAY])->sadb_x_sa_replay_replay; + + if (replay > UINT32_MAX - 32) { + ipseclog((LOG_DEBUG, + "%s: replay window too big.\n", __func__)); + error = EINVAL; + goto fail; + } + + replay = (replay + 7) >> 3; + } + + sav->replay = malloc(sizeof(struct secreplay), M_IPSEC_MISC, + M_NOWAIT | M_ZERO); + if (sav->replay == NULL) { + PFKEYSTAT_INC(in_nomem); + ipseclog((LOG_DEBUG, "%s: No more memory.\n", + __func__)); + error = ENOBUFS; + goto fail; + } + + if (replay != 0) { + /* number of 32b blocks to be allocated */ + uint32_t bitmap_size; - /* replay window */ - if ((sa0->sadb_sa_flags & SADB_X_EXT_OLD) == 0) { - sav->replay = (struct secreplay *) - malloc(sizeof(struct secreplay)+sa0->sadb_sa_replay, M_IPSEC_MISC, M_NOWAIT|M_ZERO); - if (sav->replay == NULL) { + /* RFC 6479: + * - the allocated replay window size must be + * a power of two. + * - use an extra 32b block as a redundant window. + */ + bitmap_size = 1; + while (replay + 4 > bitmap_size) + bitmap_size <<= 1; + bitmap_size = bitmap_size / 4; + + sav->replay->bitmap = malloc( + bitmap_size * sizeof(uint32_t), M_IPSEC_MISC, + M_NOWAIT | M_ZERO); + if (sav->replay->bitmap == NULL) { + PFKEYSTAT_INC(in_nomem); ipseclog((LOG_DEBUG, "%s: No more memory.\n", __func__)); error = ENOBUFS; goto fail; } - if (sa0->sadb_sa_replay != 0) - sav->replay->bitmap = (caddr_t)(sav->replay+1); - sav->replay->wsize = sa0->sadb_sa_replay; + sav->replay->bitmap_size = bitmap_size; + sav->replay->wsize = replay; } } /* Authentication keys */ - if (mhp->ext[SADB_EXT_KEY_AUTH] != NULL) { - const struct sadb_key *key0; - int len; - - key0 = (const struct sadb_key *)mhp->ext[SADB_EXT_KEY_AUTH]; - len = mhp->extlen[SADB_EXT_KEY_AUTH]; - - error = 0; - if (len < sizeof(*key0)) { + if (!SADB_CHECKHDR(mhp, SADB_EXT_KEY_AUTH)) { + if (SADB_CHECKLEN(mhp, SADB_EXT_KEY_AUTH)) { error = EINVAL; goto fail; } + error = 0; + key0 = (const struct sadb_key *)mhp->ext[SADB_EXT_KEY_AUTH]; + len = mhp->extlen[SADB_EXT_KEY_AUTH]; switch (mhp->msg->sadb_msg_satype) { case SADB_SATYPE_AH: case SADB_SATYPE_ESP: @@ -3146,29 +3265,25 @@ key_setsaval(sav, m, mhp) goto fail; } - sav->key_auth = (struct seckey *)key_dup_keymsg(key0, len, - M_IPSEC_MISC); + sav->key_auth = key_dup_keymsg(key0, len, M_IPSEC_MISC); if (sav->key_auth == NULL ) { ipseclog((LOG_DEBUG, "%s: No more memory.\n", __func__)); + PFKEYSTAT_INC(in_nomem); error = ENOBUFS; goto fail; } } /* Encryption key */ - if (mhp->ext[SADB_EXT_KEY_ENCRYPT] != NULL) { - const struct sadb_key *key0; - int len; - - key0 = (const struct sadb_key *)mhp->ext[SADB_EXT_KEY_ENCRYPT]; - len = mhp->extlen[SADB_EXT_KEY_ENCRYPT]; - - error = 0; - if (len < sizeof(*key0)) { + if (!SADB_CHECKHDR(mhp, SADB_EXT_KEY_ENCRYPT)) { + if (SADB_CHECKLEN(mhp, SADB_EXT_KEY_ENCRYPT)) { error = EINVAL; goto fail; } + error = 0; + key0 = (const struct sadb_key *)mhp->ext[SADB_EXT_KEY_ENCRYPT]; + len = mhp->extlen[SADB_EXT_KEY_ENCRYPT]; switch (mhp->msg->sadb_msg_satype) { case SADB_SATYPE_ESP: if (len == PFKEY_ALIGN8(sizeof(struct sadb_key)) && @@ -3176,12 +3291,11 @@ key_setsaval(sav, m, mhp) error = EINVAL; break; } - sav->key_enc = (struct seckey *)key_dup_keymsg(key0, - len, - M_IPSEC_MISC); + sav->key_enc = key_dup_keymsg(key0, len, M_IPSEC_MISC); if (sav->key_enc == NULL) { ipseclog((LOG_DEBUG, "%s: No more memory.\n", __func__)); + PFKEYSTAT_INC(in_nomem); error = ENOBUFS; goto fail; } @@ -3206,172 +3320,83 @@ key_setsaval(sav, m, mhp) /* set iv */ sav->ivlen = 0; - switch (mhp->msg->sadb_msg_satype) { case SADB_SATYPE_AH: - error = xform_init(sav, XF_AH); - break; - case SADB_SATYPE_ESP: - error = xform_init(sav, XF_ESP); - break; - case SADB_X_SATYPE_IPCOMP: - error = xform_init(sav, XF_IPCOMP); - break; - case SADB_X_SATYPE_TCPSIGNATURE: - error = xform_init(sav, XF_TCPSIGNATURE); - break; - } - if (error) { - ipseclog((LOG_DEBUG, "%s: unable to initialize SA type %u.\n", - __func__, mhp->msg->sadb_msg_satype)); - goto fail; - } - - /* reset created */ - sav->created = time_second; - - /* make lifetime for CURRENT */ - sav->lft_c = malloc(sizeof(struct seclifetime), M_IPSEC_MISC, M_NOWAIT); - if (sav->lft_c == NULL) { - ipseclog((LOG_DEBUG, "%s: No more memory.\n", __func__)); - error = ENOBUFS; - goto fail; - } - - sav->lft_c->allocations = 0; - sav->lft_c->bytes = 0; - sav->lft_c->addtime = time_second; - sav->lft_c->usetime = 0; - - /* lifetimes for HARD and SOFT */ - { - const struct sadb_lifetime *lft0; - - lft0 = (struct sadb_lifetime *)mhp->ext[SADB_EXT_LIFETIME_HARD]; - if (lft0 != NULL) { - if (mhp->extlen[SADB_EXT_LIFETIME_HARD] < sizeof(*lft0)) { + if (sav->flags & SADB_X_EXT_DERIV) { + ipseclog((LOG_DEBUG, "%s: invalid flag (derived) " + "given to AH SA.\n", __func__)); error = EINVAL; goto fail; } - sav->lft_h = key_dup_lifemsg(lft0, M_IPSEC_MISC); - if (sav->lft_h == NULL) { - ipseclog((LOG_DEBUG, "%s: No more memory.\n",__func__)); - error = ENOBUFS; - goto fail; - } - /* to be initialize ? */ - } - - lft0 = (struct sadb_lifetime *)mhp->ext[SADB_EXT_LIFETIME_SOFT]; - if (lft0 != NULL) { - if (mhp->extlen[SADB_EXT_LIFETIME_SOFT] < sizeof(*lft0)) { + if (sav->alg_enc != SADB_EALG_NONE) { + ipseclog((LOG_DEBUG, "%s: protocol and algorithm " + "mismated.\n", __func__)); error = EINVAL; goto fail; } - sav->lft_s = key_dup_lifemsg(lft0, M_IPSEC_MISC); - if (sav->lft_s == NULL) { - ipseclog((LOG_DEBUG, "%s: No more memory.\n",__func__)); - error = ENOBUFS; - goto fail; - } - /* to be initialize ? */ - } - } - - return 0; - - fail: - /* initialization */ - key_cleansav(sav); - - return error; -} - -/* - * validation with a secasvar entry, and set SADB_SATYPE_MATURE. - * OUT: 0: valid - * other: errno - */ -static int -key_mature(struct secasvar *sav) -{ - int error; - - /* check SPI value */ - switch (sav->sah->saidx.proto) { - case IPPROTO_ESP: - case IPPROTO_AH: - /* - * RFC 4302, 2.4. Security Parameters Index (SPI), SPI values - * 1-255 reserved by IANA for future use, - * 0 for implementation specific, local use. - */ - if (ntohl(sav->spi) <= 255) { - ipseclog((LOG_DEBUG, "%s: illegal range of SPI %u.\n", - __func__, (u_int32_t)ntohl(sav->spi))); - return EINVAL; - } + error = xform_init(sav, XF_AH); break; - } - - /* check satype */ - switch (sav->sah->saidx.proto) { - case IPPROTO_ESP: - /* check flags */ - if ((sav->flags & (SADB_X_EXT_OLD|SADB_X_EXT_DERIV)) == - (SADB_X_EXT_OLD|SADB_X_EXT_DERIV)) { + case SADB_SATYPE_ESP: + if ((sav->flags & (SADB_X_EXT_OLD | SADB_X_EXT_DERIV)) == + (SADB_X_EXT_OLD | SADB_X_EXT_DERIV)) { ipseclog((LOG_DEBUG, "%s: invalid flag (derived) " - "given to old-esp.\n", __func__)); - return EINVAL; + "given to old-esp.\n", __func__)); + error = EINVAL; + goto fail; } error = xform_init(sav, XF_ESP); break; - case IPPROTO_AH: - /* check flags */ - if (sav->flags & SADB_X_EXT_DERIV) { - ipseclog((LOG_DEBUG, "%s: invalid flag (derived) " - "given to AH SA.\n", __func__)); - return EINVAL; - } - if (sav->alg_enc != SADB_EALG_NONE) { - ipseclog((LOG_DEBUG, "%s: protocol and algorithm " - "mismated.\n", __func__)); - return(EINVAL); - } - error = xform_init(sav, XF_AH); - break; - case IPPROTO_IPCOMP: + case SADB_X_SATYPE_IPCOMP: if (sav->alg_auth != SADB_AALG_NONE) { ipseclog((LOG_DEBUG, "%s: protocol and algorithm " - "mismated.\n", __func__)); - return(EINVAL); + "mismated.\n", __func__)); + error = EINVAL; + goto fail; } - if ((sav->flags & SADB_X_EXT_RAWCPI) == 0 - && ntohl(sav->spi) >= 0x10000) { + if ((sav->flags & SADB_X_EXT_RAWCPI) == 0 && + ntohl(sav->spi) >= 0x10000) { ipseclog((LOG_DEBUG, "%s: invalid cpi for IPComp.\n", - __func__)); - return(EINVAL); + __func__)); + error = EINVAL; + goto fail; } error = xform_init(sav, XF_IPCOMP); break; - case IPPROTO_TCP: + case SADB_X_SATYPE_TCPSIGNATURE: if (sav->alg_enc != SADB_EALG_NONE) { ipseclog((LOG_DEBUG, "%s: protocol and algorithm " - "mismated.\n", __func__)); - return(EINVAL); + "mismated.\n", __func__)); + error = EINVAL; + goto fail; } error = xform_init(sav, XF_TCPSIGNATURE); break; default: ipseclog((LOG_DEBUG, "%s: Invalid satype.\n", __func__)); error = EPROTONOSUPPORT; - break; + goto fail; } - if (error == 0) { - SAHTREE_LOCK(); - key_sa_chgstate(sav, SADB_SASTATE_MATURE); - SAHTREE_UNLOCK(); + if (error) { + ipseclog((LOG_DEBUG, "%s: unable to initialize SA type %u.\n", + __func__, mhp->msg->sadb_msg_satype)); + goto fail; } + + /* Handle NAT-T headers */ + error = key_setnatt(sav, mhp); + if (error != 0) + goto fail; + + /* Initialize lifetime for CURRENT */ + sav->firstused = 0; + sav->created = time_second; + + /* lifetimes for HARD and SOFT */ + error = key_updatelifetimes(sav, mhp); + if (error == 0) + return (0); +fail: + key_cleansav(sav); return (error); } @@ -3379,32 +3404,32 @@ key_mature(struct secasvar *sav) * subroutine for SADB_GET and SADB_DUMP. */ static struct mbuf * -key_setdumpsa(struct secasvar *sav, u_int8_t type, u_int8_t satype, - u_int32_t seq, u_int32_t pid) +key_setdumpsa(struct secasvar *sav, uint8_t type, uint8_t satype, + uint32_t seq, uint32_t pid) { + struct seclifetime lft_c; struct mbuf *result = NULL, *tres = NULL, *m; - int i; - int dumporder[] = { - SADB_EXT_SA, SADB_X_EXT_SA2, + int i, dumporder[] = { + SADB_EXT_SA, SADB_X_EXT_SA2, SADB_X_EXT_SA_REPLAY, SADB_EXT_LIFETIME_HARD, SADB_EXT_LIFETIME_SOFT, SADB_EXT_LIFETIME_CURRENT, SADB_EXT_ADDRESS_SRC, - SADB_EXT_ADDRESS_DST, SADB_EXT_ADDRESS_PROXY, SADB_EXT_KEY_AUTH, - SADB_EXT_KEY_ENCRYPT, SADB_EXT_IDENTITY_SRC, - SADB_EXT_IDENTITY_DST, SADB_EXT_SENSITIVITY, -#ifdef IPSEC_NAT_T + SADB_EXT_ADDRESS_DST, SADB_EXT_ADDRESS_PROXY, + SADB_EXT_KEY_AUTH, SADB_EXT_KEY_ENCRYPT, + SADB_EXT_IDENTITY_SRC, SADB_EXT_IDENTITY_DST, + SADB_EXT_SENSITIVITY, SADB_X_EXT_NAT_T_TYPE, SADB_X_EXT_NAT_T_SPORT, SADB_X_EXT_NAT_T_DPORT, SADB_X_EXT_NAT_T_OAI, SADB_X_EXT_NAT_T_OAR, SADB_X_EXT_NAT_T_FRAG, -#endif }; + uint32_t replay_count; m = key_setsadbmsg(type, 0, satype, seq, pid, sav->refcnt); if (m == NULL) goto fail; result = m; - for (i = sizeof(dumporder)/sizeof(dumporder[0]) - 1; i >= 0; i--) { + for (i = nitems(dumporder) - 1; i >= 0; i--) { m = NULL; switch (dumporder[i]) { case SADB_EXT_SA: @@ -3414,13 +3439,25 @@ key_setdumpsa(struct secasvar *sav, u_int8_t type, u_int8_t satype, break; case SADB_X_EXT_SA2: - m = key_setsadbxsa2(sav->sah->saidx.mode, - sav->replay ? sav->replay->count : 0, + SECASVAR_LOCK(sav); + replay_count = sav->replay ? sav->replay->count : 0; + SECASVAR_UNLOCK(sav); + m = key_setsadbxsa2(sav->sah->saidx.mode, replay_count, sav->sah->saidx.reqid); if (!m) goto fail; break; + case SADB_X_EXT_SA_REPLAY: + if (sav->replay == NULL || + sav->replay->wsize <= UINT8_MAX) + continue; + + m = key_setsadbxsareplay(sav->replay->wsize); + if (!m) + goto fail; + break; + case SADB_EXT_ADDRESS_SRC: m = key_setsadbaddr(SADB_EXT_ADDRESS_SRC, &sav->sah->saidx.src.sa, @@ -3454,10 +3491,12 @@ key_setdumpsa(struct secasvar *sav, u_int8_t type, u_int8_t satype, break; case SADB_EXT_LIFETIME_CURRENT: - if (!sav->lft_c) - continue; - m = key_setlifetime(sav->lft_c, - SADB_EXT_LIFETIME_CURRENT); + lft_c.addtime = sav->created; + lft_c.allocations = (uint32_t)counter_u64_fetch( + sav->lft_c_allocations); + lft_c.bytes = counter_u64_fetch(sav->lft_c_bytes); + lft_c.usetime = sav->firstused; + m = key_setlifetime(&lft_c, SADB_EXT_LIFETIME_CURRENT); if (!m) goto fail; break; @@ -3481,35 +3520,53 @@ key_setdumpsa(struct secasvar *sav, u_int8_t type, u_int8_t satype, goto fail; break; -#ifdef IPSEC_NAT_T case SADB_X_EXT_NAT_T_TYPE: - m = key_setsadbxtype(sav->natt_type); + if (sav->natt == NULL) + continue; + m = key_setsadbxtype(UDP_ENCAP_ESPINUDP); if (!m) goto fail; break; - + case SADB_X_EXT_NAT_T_DPORT: - m = key_setsadbxport( - KEY_PORTFROMSADDR(&sav->sah->saidx.dst), + if (sav->natt == NULL) + continue; + m = key_setsadbxport(sav->natt->dport, SADB_X_EXT_NAT_T_DPORT); if (!m) goto fail; break; case SADB_X_EXT_NAT_T_SPORT: - m = key_setsadbxport( - KEY_PORTFROMSADDR(&sav->sah->saidx.src), + if (sav->natt == NULL) + continue; + m = key_setsadbxport(sav->natt->sport, SADB_X_EXT_NAT_T_SPORT); if (!m) goto fail; break; case SADB_X_EXT_NAT_T_OAI: + if (sav->natt == NULL || + (sav->natt->flags & IPSEC_NATT_F_OAI) == 0) + continue; + m = key_setsadbaddr(SADB_X_EXT_NAT_T_OAI, + &sav->natt->oai.sa, FULLMASK, IPSEC_ULPROTO_ANY); + if (!m) + goto fail; + break; case SADB_X_EXT_NAT_T_OAR: + if (sav->natt == NULL || + (sav->natt->flags & IPSEC_NATT_F_OAR) == 0) + continue; + m = key_setsadbaddr(SADB_X_EXT_NAT_T_OAR, + &sav->natt->oar.sa, FULLMASK, IPSEC_ULPROTO_ANY); + if (!m) + goto fail; + break; case SADB_X_EXT_NAT_T_FRAG: /* We do not (yet) support those. */ continue; -#endif case SADB_EXT_ADDRESS_PROXY: case SADB_EXT_IDENTITY_SRC: @@ -3525,10 +3582,10 @@ key_setdumpsa(struct secasvar *sav, u_int8_t type, u_int8_t satype, if (tres) m_cat(m, tres); tres = m; - } m_cat(result, tres); + tres = NULL; if (result->m_len < sizeof(struct sadb_msg)) { result = m_pullup(result, sizeof(struct sadb_msg)); if (result == NULL) @@ -3564,10 +3621,9 @@ key_setsadbmsg(u_int8_t type, u_int16_t tlen, u_int8_t satype, u_int32_t seq, len = PFKEY_ALIGN8(sizeof(struct sadb_msg)); if (len > MCLBYTES) return NULL; - MGETHDR(m, M_DONTWAIT, MT_DATA); + MGETHDR(m, M_NOWAIT, MT_DATA); if (m && len > MHLEN) { - MCLGET(m, M_DONTWAIT); - if ((m->m_flags & M_EXT) == 0) { + if (!(MCLGET(m, M_NOWAIT))) { m_freem(m); m = NULL; } @@ -3596,41 +3652,39 @@ key_setsadbmsg(u_int8_t type, u_int16_t tlen, u_int8_t satype, u_int32_t seq, * copy secasvar data into sadb_address. */ static struct mbuf * -key_setsadbsa(sav) - struct secasvar *sav; +key_setsadbsa(struct secasvar *sav) { struct mbuf *m; struct sadb_sa *p; int len; len = PFKEY_ALIGN8(sizeof(struct sadb_sa)); - m = key_alloc_mbuf(len); - if (!m || m->m_next) { /*XXX*/ - if (m) - m_freem(m); - return NULL; - } - + m = m_get2(len, M_NOWAIT, MT_DATA, 0); + if (m == NULL) + return (NULL); + m_align(m, len); + m->m_len = len; p = mtod(m, struct sadb_sa *); - bzero(p, len); p->sadb_sa_len = PFKEY_UNIT64(len); p->sadb_sa_exttype = SADB_EXT_SA; p->sadb_sa_spi = sav->spi; - p->sadb_sa_replay = (sav->replay != NULL ? sav->replay->wsize : 0); + p->sadb_sa_replay = sav->replay ? + (sav->replay->wsize > UINT8_MAX ? UINT8_MAX : + sav->replay->wsize): 0; p->sadb_sa_state = sav->state; p->sadb_sa_auth = sav->alg_auth; p->sadb_sa_encrypt = sav->alg_enc; - p->sadb_sa_flags = sav->flags; - - return m; + p->sadb_sa_flags = sav->flags & SADB_KEY_FLAGS_MAX; + return (m); } /* * set data into sadb_address. */ static struct mbuf * -key_setsadbaddr(u_int16_t exttype, const struct sockaddr *saddr, u_int8_t prefixlen, u_int16_t ul_proto) +key_setsadbaddr(u_int16_t exttype, const struct sockaddr *saddr, + u_int8_t prefixlen, u_int16_t ul_proto) { struct mbuf *m; struct sadb_address *p; @@ -3638,13 +3692,11 @@ key_setsadbaddr(u_int16_t exttype, const struct sockaddr *saddr, u_int8_t prefix len = PFKEY_ALIGN8(sizeof(struct sadb_address)) + PFKEY_ALIGN8(saddr->sa_len); - m = key_alloc_mbuf(len); - if (!m || m->m_next) { /*XXX*/ - if (m) - m_freem(m); - return NULL; - } - + m = m_get2(len, M_NOWAIT, MT_DATA, 0); + if (m == NULL) + return (NULL); + m_align(m, len); + m->m_len = len; p = mtod(m, struct sadb_address *); bzero(p, len); @@ -3684,13 +3736,11 @@ key_setsadbxsa2(u_int8_t mode, u_int32_t seq, u_int32_t reqid) size_t len; len = PFKEY_ALIGN8(sizeof(struct sadb_x_sa2)); - m = key_alloc_mbuf(len); - if (!m || m->m_next) { /*XXX*/ - if (m) - m_freem(m); - return NULL; - } - + m = m_get2(len, M_NOWAIT, MT_DATA, 0); + if (m == NULL) + return (NULL); + m_align(m, len); + m->m_len = len; p = mtod(m, struct sadb_x_sa2 *); bzero(p, len); @@ -3705,7 +3755,32 @@ key_setsadbxsa2(u_int8_t mode, u_int32_t seq, u_int32_t reqid) return m; } -#ifdef IPSEC_NAT_T +/* + * Set data into sadb_x_sa_replay. + */ +static struct mbuf * +key_setsadbxsareplay(u_int32_t replay) +{ + struct mbuf *m; + struct sadb_x_sa_replay *p; + size_t len; + + len = PFKEY_ALIGN8(sizeof(struct sadb_x_sa_replay)); + m = m_get2(len, M_NOWAIT, MT_DATA, 0); + if (m == NULL) + return (NULL); + m_align(m, len); + m->m_len = len; + p = mtod(m, struct sadb_x_sa_replay *); + + bzero(p, len); + p->sadb_x_sa_replay_len = PFKEY_UNIT64(len); + p->sadb_x_sa_replay_exttype = SADB_X_EXT_SA_REPLAY; + p->sadb_x_sa_replay_replay = (replay << 3); + + return m; +} + /* * Set a type in sadb_x_nat_t_type. */ @@ -3718,13 +3793,11 @@ key_setsadbxtype(u_int16_t type) len = PFKEY_ALIGN8(sizeof(struct sadb_x_nat_t_type)); - m = key_alloc_mbuf(len); - if (!m || m->m_next) { /*XXX*/ - if (m) - m_freem(m); + m = m_get2(len, M_NOWAIT, MT_DATA, 0); + if (m == NULL) return (NULL); - } - + m_align(m, len); + m->m_len = len; p = mtod(m, struct sadb_x_nat_t_type *); bzero(p, len); @@ -3747,13 +3820,11 @@ key_setsadbxport(u_int16_t port, u_int16_t type) len = PFKEY_ALIGN8(sizeof(struct sadb_x_nat_t_port)); - m = key_alloc_mbuf(len); - if (!m || m->m_next) { /*XXX*/ - if (m) - m_freem(m); + m = m_get2(len, M_NOWAIT, MT_DATA, 0); + if (m == NULL) return (NULL); - } - + m_align(m, len); + m->m_len = len; p = mtod(m, struct sadb_x_nat_t_port *); bzero(p, len); @@ -3764,10 +3835,10 @@ key_setsadbxport(u_int16_t port, u_int16_t type) return (m); } -/* +/* * Get port from sockaddr. Port is in network byte order. */ -u_int16_t +uint16_t key_portfromsaddr(struct sockaddr *sa) { @@ -3781,18 +3852,14 @@ key_portfromsaddr(struct sockaddr *sa) return ((struct sockaddr_in6 *)sa)->sin6_port; #endif } - KEYDEBUG(KEYDEBUG_IPSEC_STAMP, - printf("DP %s unexpected address family %d\n", - __func__, sa->sa_family)); return (0); } -#endif /* IPSEC_NAT_T */ /* * Set port in struct sockaddr. Port is in network byte order. */ -static void -key_porttosaddr(struct sockaddr *sa, u_int16_t port) +void +key_porttosaddr(struct sockaddr *sa, uint16_t port) { switch (sa->sa_family) { @@ -3817,20 +3884,18 @@ key_porttosaddr(struct sockaddr *sa, u_int16_t port) * set data into sadb_x_policy */ static struct mbuf * -key_setsadbxpolicy(u_int16_t type, u_int8_t dir, u_int32_t id) +key_setsadbxpolicy(u_int16_t type, u_int8_t dir, u_int32_t id, u_int32_t priority) { struct mbuf *m; struct sadb_x_policy *p; size_t len; len = PFKEY_ALIGN8(sizeof(struct sadb_x_policy)); - m = key_alloc_mbuf(len); - if (!m || m->m_next) { /*XXX*/ - if (m) - m_freem(m); - return NULL; - } - + m = m_get2(len, M_NOWAIT, MT_DATA, 0); + if (m == NULL) + return (NULL); + m_align(m, len); + m->m_len = len; p = mtod(m, struct sadb_x_policy *); bzero(p, len); @@ -3839,6 +3904,7 @@ key_setsadbxpolicy(u_int16_t type, u_int8_t dir, u_int32_t id) p->sadb_x_policy_type = type; p->sadb_x_policy_dir = dir; p->sadb_x_policy_id = id; + p->sadb_x_policy_priority = priority; return m; } @@ -3851,29 +3917,29 @@ key_setsadbxpolicy(u_int16_t type, u_int8_t dir, u_int32_t id) * OUT: NULL no more memory */ struct seckey * -key_dup_keymsg(const struct sadb_key *src, u_int len, - struct malloc_type *type) +key_dup_keymsg(const struct sadb_key *src, size_t len, + struct malloc_type *type) { struct seckey *dst; - dst = (struct seckey *)malloc(sizeof(struct seckey), type, M_NOWAIT); + + dst = malloc(sizeof(*dst), type, M_NOWAIT); if (dst != NULL) { dst->bits = src->sadb_key_bits; - dst->key_data = (char *)malloc(len, type, M_NOWAIT); + dst->key_data = malloc(len, type, M_NOWAIT); if (dst->key_data != NULL) { - bcopy((const char *)src + sizeof(struct sadb_key), - dst->key_data, len); + bcopy((const char *)(src + 1), dst->key_data, len); } else { - ipseclog((LOG_DEBUG, "%s: No more memory.\n", - __func__)); + ipseclog((LOG_DEBUG, "%s: No more memory.\n", + __func__)); free(dst, type); dst = NULL; } } else { - ipseclog((LOG_DEBUG, "%s: No more memory.\n", - __func__)); + ipseclog((LOG_DEBUG, "%s: No more memory.\n", + __func__)); } - return dst; + return (dst); } /* Take a lifetime message (sadb_lifetime) passed in on a socket and @@ -3884,118 +3950,21 @@ key_dup_keymsg(const struct sadb_key *src, u_int len, */ static struct seclifetime * -key_dup_lifemsg(const struct sadb_lifetime *src, - struct malloc_type *type) +key_dup_lifemsg(const struct sadb_lifetime *src, struct malloc_type *type) { - struct seclifetime *dst = NULL; + struct seclifetime *dst; - dst = (struct seclifetime *)malloc(sizeof(struct seclifetime), - type, M_NOWAIT); + dst = malloc(sizeof(*dst), type, M_NOWAIT); if (dst == NULL) { - /* XXX counter */ ipseclog((LOG_DEBUG, "%s: No more memory.\n", __func__)); - } else { - dst->allocations = src->sadb_lifetime_allocations; - dst->bytes = src->sadb_lifetime_bytes; - dst->addtime = src->sadb_lifetime_addtime; - dst->usetime = src->sadb_lifetime_usetime; - } - return dst; -} - -/* compare my own address - * OUT: 1: true, i.e. my address. - * 0: false - */ -int -key_ismyaddr(sa) - struct sockaddr *sa; -{ -#ifdef INET - struct sockaddr_in *sin; - struct in_ifaddr *ia; -#endif - - IPSEC_ASSERT(sa != NULL, ("null sockaddr")); - - switch (sa->sa_family) { -#ifdef INET - case AF_INET: - sin = (struct sockaddr_in *)sa; - IN_IFADDR_RLOCK(); - for (ia = V_in_ifaddrhead.tqh_first; ia; - ia = ia->ia_link.tqe_next) - { - if (sin->sin_family == ia->ia_addr.sin_family && - sin->sin_len == ia->ia_addr.sin_len && - sin->sin_addr.s_addr == ia->ia_addr.sin_addr.s_addr) - { - IN_IFADDR_RUNLOCK(); - return 1; - } - } - IN_IFADDR_RUNLOCK(); - break; -#endif -#ifdef INET6 - case AF_INET6: - return key_ismyaddr6((struct sockaddr_in6 *)sa); -#endif - } - - return 0; -} - -#ifdef INET6 -/* - * compare my own address for IPv6. - * 1: ours - * 0: other - * NOTE: derived ip6_input() in KAME. This is necessary to modify more. - */ -#include <netinet6/in6_var.h> - -static int -key_ismyaddr6(sin6) - struct sockaddr_in6 *sin6; -{ - struct in6_ifaddr *ia; -#if 0 - struct in6_multi *in6m; -#endif - - IN6_IFADDR_RLOCK(); - TAILQ_FOREACH(ia, &V_in6_ifaddrhead, ia_link) { - if (key_sockaddrcmp((struct sockaddr *)&sin6, - (struct sockaddr *)&ia->ia_addr, 0) == 0) { - IN6_IFADDR_RUNLOCK(); - return 1; - } - -#if 0 - /* - * XXX Multicast - * XXX why do we care about multlicast here while we don't care - * about IPv4 multicast?? - * XXX scope - */ - in6m = NULL; - IN6_LOOKUP_MULTI(sin6->sin6_addr, ia->ia_ifp, in6m); - if (in6m) { - IN6_IFADDR_RUNLOCK(); - return 1; - } -#endif + return (NULL); } - IN6_IFADDR_RUNLOCK(); - - /* loopback, just for safety */ - if (IN6_IS_ADDR_LOOPBACK(&sin6->sin6_addr)) - return 1; - - return 0; + dst->allocations = src->sadb_lifetime_allocations; + dst->bytes = src->sadb_lifetime_bytes; + dst->addtime = src->sadb_lifetime_addtime; + dst->usetime = src->sadb_lifetime_usetime; + return (dst); } -#endif /*INET6*/ /* * compare two secasindex structure. @@ -4010,12 +3979,9 @@ key_ismyaddr6(sin6) * 0 : not equal */ static int -key_cmpsaidx( - const struct secasindex *saidx0, - const struct secasindex *saidx1, - int flag) +key_cmpsaidx(const struct secasindex *saidx0, const struct secasindex *saidx1, + int flag) { - int chkport = 0; /* sanity */ if (saidx0 == NULL && saidx1 == NULL) @@ -4032,19 +3998,21 @@ key_cmpsaidx( return 0; if (saidx0->reqid != saidx1->reqid) return 0; - if (bcmp(&saidx0->src, &saidx1->src, saidx0->src.sa.sa_len) != 0 || - bcmp(&saidx0->dst, &saidx1->dst, saidx0->dst.sa.sa_len) != 0) + if (bcmp(&saidx0->src, &saidx1->src, + saidx0->src.sa.sa_len) != 0 || + bcmp(&saidx0->dst, &saidx1->dst, + saidx0->dst.sa.sa_len) != 0) return 0; } else { /* CMP_MODE_REQID, CMP_REQID, CMP_HEAD */ - if (flag == CMP_MODE_REQID - ||flag == CMP_REQID) { + if (flag == CMP_MODE_REQID || flag == CMP_REQID) { /* * If reqid of SPD is non-zero, unique SA is required. * The result must be of same reqid in this case. */ - if (saidx1->reqid != 0 && saidx0->reqid != saidx1->reqid) + if (saidx1->reqid != 0 && + saidx0->reqid != saidx1->reqid) return 0; } @@ -4054,27 +4022,10 @@ key_cmpsaidx( return 0; } -#ifdef IPSEC_NAT_T - /* - * If NAT-T is enabled, check ports for tunnel mode. - * Do not check ports if they are set to zero in the SPD. - * Also do not do it for transport mode, as there is no - * port information available in the SP. - */ - if (saidx1->mode == IPSEC_MODE_TUNNEL && - saidx1->src.sa.sa_family == AF_INET && - saidx1->dst.sa.sa_family == AF_INET && - ((const struct sockaddr_in *)(&saidx1->src))->sin_port && - ((const struct sockaddr_in *)(&saidx1->dst))->sin_port) - chkport = 1; -#endif /* IPSEC_NAT_T */ - - if (key_sockaddrcmp(&saidx0->src.sa, &saidx1->src.sa, chkport) != 0) { + if (key_sockaddrcmp(&saidx0->src.sa, &saidx1->src.sa, 0) != 0) return 0; - } - if (key_sockaddrcmp(&saidx0->dst.sa, &saidx1->dst.sa, chkport) != 0) { + if (key_sockaddrcmp(&saidx0->dst.sa, &saidx1->dst.sa, 0) != 0) return 0; - } } return 1; @@ -4090,9 +4041,8 @@ key_cmpsaidx( * 0 : not equal */ static int -key_cmpspidx_exactly( - struct secpolicyindex *spidx0, - struct secpolicyindex *spidx1) +key_cmpspidx_exactly(struct secpolicyindex *spidx0, + struct secpolicyindex *spidx1) { /* sanity */ if (spidx0 == NULL && spidx1 == NULL) @@ -4120,9 +4070,8 @@ key_cmpspidx_exactly( * 0 : not equal */ static int -key_cmpspidx_withmask( - struct secpolicyindex *spidx0, - struct secpolicyindex *spidx1) +key_cmpspidx_withmask(struct secpolicyindex *spidx0, + struct secpolicyindex *spidx1) { /* sanity */ if (spidx0 == NULL && spidx1 == NULL) @@ -4211,13 +4160,6 @@ key_cmpspidx_withmask( return 1; } -/* returns 0 on match */ -static int -key_sockaddrcmp( - const struct sockaddr *sa1, - const struct sockaddr *sa2, - int port) -{ #ifdef satosin #undef satosin #endif @@ -4226,10 +4168,16 @@ key_sockaddrcmp( #undef satosin6 #endif #define satosin6(s) ((const struct sockaddr_in6 *)s) +/* returns 0 on match */ +int +key_sockaddrcmp(const struct sockaddr *sa1, const struct sockaddr *sa2, + int port) +{ if (sa1->sa_family != sa2->sa_family || sa1->sa_len != sa2->sa_len) return 1; switch (sa1->sa_family) { +#ifdef INET case AF_INET: if (sa1->sa_len != sizeof(struct sockaddr_in)) return 1; @@ -4240,6 +4188,8 @@ key_sockaddrcmp( if (port && satosin(sa1)->sin_port != satosin(sa2)->sin_port) return 1; break; +#endif +#ifdef INET6 case AF_INET6: if (sa1->sa_len != sizeof(struct sockaddr_in6)) return 1; /*EINVAL*/ @@ -4256,6 +4206,7 @@ key_sockaddrcmp( return 1; } break; +#endif default: if (bcmp(sa1, sa2, sa1->sa_len) != 0) return 1; @@ -4263,9 +4214,35 @@ key_sockaddrcmp( } return 0; +} + +/* returns 0 on match */ +int +key_sockaddrcmp_withmask(const struct sockaddr *sa1, + const struct sockaddr *sa2, size_t mask) +{ + if (sa1->sa_family != sa2->sa_family || sa1->sa_len != sa2->sa_len) + return (1); + + switch (sa1->sa_family) { +#ifdef INET + case AF_INET: + return (!key_bbcmp(&satosin(sa1)->sin_addr, + &satosin(sa2)->sin_addr, mask)); +#endif +#ifdef INET6 + case AF_INET6: + if (satosin6(sa1)->sin6_scope_id != + satosin6(sa2)->sin6_scope_id) + return (1); + return (!key_bbcmp(&satosin6(sa1)->sin6_addr, + &satosin6(sa2)->sin6_addr, mask)); +#endif + } + return (1); +} #undef satosin #undef satosin6 -} /* * compare two buffers with mask. @@ -4307,185 +4284,256 @@ key_bbcmp(const void *a1, const void *a2, u_int bits) static void key_flush_spd(time_t now) { - static u_int16_t sptree_scangen = 0; - u_int16_t gen = sptree_scangen++; - struct secpolicy *sp; + SPTREE_RLOCK_TRACKER; + struct secpolicy_list drainq; + struct secpolicy *sp, *nextsp; u_int dir; - /* SPD */ + LIST_INIT(&drainq); + SPTREE_RLOCK(); for (dir = 0; dir < IPSEC_DIR_MAX; dir++) { -restart: - SPTREE_LOCK(); - LIST_FOREACH(sp, &V_sptree[dir], chain) { - if (sp->scangen == gen) /* previously handled */ - continue; - sp->scangen = gen; - if (sp->state == IPSEC_SPSTATE_DEAD && - sp->refcnt == 1) { - /* - * Ensure that we only decrease refcnt once, - * when we're the last consumer. - * Directly call SP_DELREF/key_delsp instead - * of KEY_FREESP to avoid unlocking/relocking - * SPTREE_LOCK before key_delsp: may refcnt - * be increased again during that time ? - * NB: also clean entries created by - * key_spdflush - */ - SP_DELREF(sp); - key_delsp(sp); - SPTREE_UNLOCK(); - goto restart; - } + TAILQ_FOREACH(sp, &V_sptree[dir], chain) { if (sp->lifetime == 0 && sp->validtime == 0) continue; - if ((sp->lifetime && now - sp->created > sp->lifetime) - || (sp->validtime && now - sp->lastused > sp->validtime)) { - sp->state = IPSEC_SPSTATE_DEAD; - SPTREE_UNLOCK(); - key_spdexpire(sp); - goto restart; + if ((sp->lifetime && + now - sp->created > sp->lifetime) || + (sp->validtime && + now - sp->lastused > sp->validtime)) { + /* Hold extra reference to send SPDEXPIRE */ + SP_ADDREF(sp); + LIST_INSERT_HEAD(&drainq, sp, drainq); } } - SPTREE_UNLOCK(); + } + SPTREE_RUNLOCK(); + if (LIST_EMPTY(&drainq)) + return; + + SPTREE_WLOCK(); + sp = LIST_FIRST(&drainq); + while (sp != NULL) { + nextsp = LIST_NEXT(sp, drainq); + /* Check that SP is still linked */ + if (sp->state != IPSEC_SPSTATE_ALIVE) { + LIST_REMOVE(sp, drainq); + key_freesp(&sp); /* release extra reference */ + sp = nextsp; + continue; + } + TAILQ_REMOVE(&V_sptree[sp->spidx.dir], sp, chain); + LIST_REMOVE(sp, idhash); + sp->state = IPSEC_SPSTATE_DEAD; + sp = nextsp; + } + V_sp_genid++; + SPTREE_WUNLOCK(); + + sp = LIST_FIRST(&drainq); + while (sp != NULL) { + nextsp = LIST_NEXT(sp, drainq); + key_spdexpire(sp); + key_freesp(&sp); /* release extra reference */ + key_freesp(&sp); /* release last reference */ + sp = nextsp; } } static void key_flush_sad(time_t now) { + SAHTREE_RLOCK_TRACKER; + struct secashead_list emptyq; + struct secasvar_list drainq, hexpireq, sexpireq, freeq; struct secashead *sah, *nextsah; struct secasvar *sav, *nextsav; - /* SAD */ - SAHTREE_LOCK(); - LIST_FOREACH_SAFE(sah, &V_sahtree, chain, nextsah) { - /* if sah has been dead, then delete it and process next sah. */ - if (sah->state == SADB_SASTATE_DEAD) { - key_delsah(sah); + LIST_INIT(&drainq); + LIST_INIT(&hexpireq); + LIST_INIT(&sexpireq); + LIST_INIT(&emptyq); + + SAHTREE_RLOCK(); + TAILQ_FOREACH(sah, &V_sahtree, chain) { + /* Check for empty SAH */ + if (TAILQ_EMPTY(&sah->savtree_larval) && + TAILQ_EMPTY(&sah->savtree_alive)) { + SAH_ADDREF(sah); + LIST_INSERT_HEAD(&emptyq, sah, drainq); continue; } - - /* if LARVAL entry doesn't become MATURE, delete it. */ - LIST_FOREACH_SAFE(sav, &sah->savtree[SADB_SASTATE_LARVAL], chain, nextsav) { - /* Need to also check refcnt for a larval SA ??? */ - if (now - sav->created > V_key_larval_lifetime) - KEY_FREESAV(&sav); + /* Add all stale LARVAL SAs into drainq */ + TAILQ_FOREACH(sav, &sah->savtree_larval, chain) { + if (now - sav->created < V_key_larval_lifetime) + continue; + SAV_ADDREF(sav); + LIST_INSERT_HEAD(&drainq, sav, drainq); } - - /* - * check MATURE entry to start to send expire message - * whether or not. - */ - LIST_FOREACH_SAFE(sav, &sah->savtree[SADB_SASTATE_MATURE], chain, nextsav) { - /* we don't need to check. */ - if (sav->lft_s == NULL) + TAILQ_FOREACH(sav, &sah->savtree_alive, chain) { + /* lifetimes aren't specified */ + if (sav->lft_h == NULL) continue; - - /* sanity check */ - if (sav->lft_c == NULL) { - ipseclog((LOG_DEBUG,"%s: there is no CURRENT " - "time, why?\n", __func__)); + SECASVAR_LOCK(sav); + /* + * Check again with lock held, because it may + * be updated by SADB_UPDATE. + */ + if (sav->lft_h == NULL) { + SECASVAR_UNLOCK(sav); continue; } - - /* check SOFT lifetime */ - if (sav->lft_s->addtime != 0 && - now - sav->created > sav->lft_s->addtime) { - key_sa_chgstate(sav, SADB_SASTATE_DYING); - /* - * Actually, only send expire message if - * SA has been used, as it was done before, - * but should we always send such message, - * and let IKE daemon decide if it should be - * renegotiated or not ? - * XXX expire message will actually NOT be - * sent if SA is only used after soft - * lifetime has been reached, see below - * (DYING state) - */ - if (sav->lft_c->usetime != 0) - key_expire(sav); - } - /* check SOFT lifetime by bytes */ /* - * XXX I don't know the way to delete this SA - * when new SA is installed. Caution when it's - * installed too big lifetime by time. + * RFC 2367: + * HARD lifetimes MUST take precedence over SOFT + * lifetimes, meaning if the HARD and SOFT lifetimes + * are the same, the HARD lifetime will appear on the + * EXPIRE message. */ - else if (sav->lft_s->bytes != 0 && - sav->lft_s->bytes < sav->lft_c->bytes) { - - key_sa_chgstate(sav, SADB_SASTATE_DYING); - /* - * XXX If we keep to send expire - * message in the status of - * DYING. Do remove below code. - */ - key_expire(sav); - } - } - - /* check DYING entry to change status to DEAD. */ - LIST_FOREACH_SAFE(sav, &sah->savtree[SADB_SASTATE_DYING], chain, nextsav) { - /* we don't need to check. */ - if (sav->lft_h == NULL) + /* check HARD lifetime */ + if ((sav->lft_h->addtime != 0 && + now - sav->created > sav->lft_h->addtime) || + (sav->lft_h->usetime != 0 && sav->firstused && + now - sav->firstused > sav->lft_h->usetime) || + (sav->lft_h->bytes != 0 && counter_u64_fetch( + sav->lft_c_bytes) > sav->lft_h->bytes)) { + SECASVAR_UNLOCK(sav); + SAV_ADDREF(sav); + LIST_INSERT_HEAD(&hexpireq, sav, drainq); continue; - - /* sanity check */ - if (sav->lft_c == NULL) { - ipseclog((LOG_DEBUG, "%s: there is no CURRENT " - "time, why?\n", __func__)); + } + /* check SOFT lifetime (only for MATURE SAs) */ + if (sav->state == SADB_SASTATE_MATURE && ( + (sav->lft_s->addtime != 0 && + now - sav->created > sav->lft_s->addtime) || + (sav->lft_s->usetime != 0 && sav->firstused && + now - sav->firstused > sav->lft_s->usetime) || + (sav->lft_s->bytes != 0 && counter_u64_fetch( + sav->lft_c_bytes) > sav->lft_s->bytes))) { + SECASVAR_UNLOCK(sav); + SAV_ADDREF(sav); + LIST_INSERT_HEAD(&sexpireq, sav, drainq); continue; } + SECASVAR_UNLOCK(sav); + } + } + SAHTREE_RUNLOCK(); - if (sav->lft_h->addtime != 0 && - now - sav->created > sav->lft_h->addtime) { - key_sa_chgstate(sav, SADB_SASTATE_DEAD); - KEY_FREESAV(&sav); - } -#if 0 /* XXX Should we keep to send expire message until HARD lifetime ? */ - else if (sav->lft_s != NULL - && sav->lft_s->addtime != 0 - && now - sav->created > sav->lft_s->addtime) { - /* - * XXX: should be checked to be - * installed the valid SA. - */ + if (LIST_EMPTY(&emptyq) && LIST_EMPTY(&drainq) && + LIST_EMPTY(&hexpireq) && LIST_EMPTY(&sexpireq)) + return; - /* - * If there is no SA then sending - * expire message. - */ - key_expire(sav); - } -#endif - /* check HARD lifetime by bytes */ - else if (sav->lft_h->bytes != 0 && - sav->lft_h->bytes < sav->lft_c->bytes) { - key_sa_chgstate(sav, SADB_SASTATE_DEAD); - KEY_FREESAV(&sav); - } + LIST_INIT(&freeq); + SAHTREE_WLOCK(); + /* Unlink stale LARVAL SAs */ + sav = LIST_FIRST(&drainq); + while (sav != NULL) { + nextsav = LIST_NEXT(sav, drainq); + /* Check that SA is still LARVAL */ + if (sav->state != SADB_SASTATE_LARVAL) { + LIST_REMOVE(sav, drainq); + LIST_INSERT_HEAD(&freeq, sav, drainq); + sav = nextsav; + continue; } - - /* delete entry in DEAD */ - LIST_FOREACH_SAFE(sav, &sah->savtree[SADB_SASTATE_DEAD], chain, nextsav) { - /* sanity check */ - if (sav->state != SADB_SASTATE_DEAD) { - ipseclog((LOG_DEBUG, "%s: invalid sav->state " - "(queue: %d SA: %d): kill it anyway\n", - __func__, - SADB_SASTATE_DEAD, sav->state)); - } - /* - * do not call key_freesav() here. - * sav should already be freed, and sav->refcnt - * shows other references to sav - * (such as from SPD). - */ + TAILQ_REMOVE(&sav->sah->savtree_larval, sav, chain); + LIST_REMOVE(sav, spihash); + sav->state = SADB_SASTATE_DEAD; + sav = nextsav; + } + /* Unlink all SAs with expired HARD lifetime */ + sav = LIST_FIRST(&hexpireq); + while (sav != NULL) { + nextsav = LIST_NEXT(sav, drainq); + /* Check that SA is not unlinked */ + if (sav->state == SADB_SASTATE_DEAD) { + LIST_REMOVE(sav, drainq); + LIST_INSERT_HEAD(&freeq, sav, drainq); + sav = nextsav; + continue; + } + TAILQ_REMOVE(&sav->sah->savtree_alive, sav, chain); + LIST_REMOVE(sav, spihash); + sav->state = SADB_SASTATE_DEAD; + sav = nextsav; + } + /* Mark all SAs with expired SOFT lifetime as DYING */ + sav = LIST_FIRST(&sexpireq); + while (sav != NULL) { + nextsav = LIST_NEXT(sav, drainq); + /* Check that SA is not unlinked */ + if (sav->state == SADB_SASTATE_DEAD) { + LIST_REMOVE(sav, drainq); + LIST_INSERT_HEAD(&freeq, sav, drainq); + sav = nextsav; + continue; + } + /* + * NOTE: this doesn't change SA order in the chain. + */ + sav->state = SADB_SASTATE_DYING; + sav = nextsav; + } + /* Unlink empty SAHs */ + sah = LIST_FIRST(&emptyq); + while (sah != NULL) { + nextsah = LIST_NEXT(sah, drainq); + /* Check that SAH is still empty and not unlinked */ + if (sah->state == SADB_SASTATE_DEAD || + !TAILQ_EMPTY(&sah->savtree_larval) || + !TAILQ_EMPTY(&sah->savtree_alive)) { + LIST_REMOVE(sah, drainq); + key_freesah(&sah); /* release extra reference */ + sah = nextsah; + continue; } + TAILQ_REMOVE(&V_sahtree, sah, chain); + LIST_REMOVE(sah, addrhash); + sah->state = SADB_SASTATE_DEAD; + sah = nextsah; + } + SAHTREE_WUNLOCK(); + + /* Send SPDEXPIRE messages */ + sav = LIST_FIRST(&hexpireq); + while (sav != NULL) { + nextsav = LIST_NEXT(sav, drainq); + key_expire(sav, 1); + key_freesah(&sav->sah); /* release reference from SAV */ + key_freesav(&sav); /* release extra reference */ + key_freesav(&sav); /* release last reference */ + sav = nextsav; + } + sav = LIST_FIRST(&sexpireq); + while (sav != NULL) { + nextsav = LIST_NEXT(sav, drainq); + key_expire(sav, 0); + key_freesav(&sav); /* release extra reference */ + sav = nextsav; + } + /* Free stale LARVAL SAs */ + sav = LIST_FIRST(&drainq); + while (sav != NULL) { + nextsav = LIST_NEXT(sav, drainq); + key_freesah(&sav->sah); /* release reference from SAV */ + key_freesav(&sav); /* release extra reference */ + key_freesav(&sav); /* release last reference */ + sav = nextsav; + } + /* Free SAs that were unlinked/changed by someone else */ + sav = LIST_FIRST(&freeq); + while (sav != NULL) { + nextsav = LIST_NEXT(sav, drainq); + key_freesav(&sav); /* release extra reference */ + sav = nextsav; + } + /* Free empty SAH */ + sah = LIST_FIRST(&emptyq); + while (sah != NULL) { + nextsah = LIST_NEXT(sah, drainq); + key_freesah(&sah); /* release extra reference */ + key_freesah(&sah); /* release last reference */ + sah = nextsah; } - SAHTREE_UNLOCK(); } static void @@ -4495,13 +4543,16 @@ key_flush_acq(time_t now) /* ACQ tree */ ACQ_LOCK(); - for (acq = LIST_FIRST(&V_acqtree); acq != NULL; acq = nextacq) { + acq = LIST_FIRST(&V_acqtree); + while (acq != NULL) { nextacq = LIST_NEXT(acq, chain); - if (now - acq->created > V_key_blockacq_lifetime - && __LIST_CHAINED(acq)) { + if (now - acq->created > V_key_blockacq_lifetime) { LIST_REMOVE(acq, chain); + LIST_REMOVE(acq, addrhash); + LIST_REMOVE(acq, seqhash); free(acq, M_IPSEC_SAQ); } + acq = nextacq; } ACQ_UNLOCK(); } @@ -4530,8 +4581,8 @@ key_flush_spacq(time_t now) * and do to remove or to expire. * XXX: year 2038 problem may remain. */ -void -key_timehandler(void) +static void +key_timehandler(void *arg) { VNET_ITERATOR_DECL(vnet_iter); time_t now = time_second; @@ -4549,7 +4600,7 @@ key_timehandler(void) #ifndef IPSEC_DEBUG2 /* do exchange to tick time !! */ - (void)timeout((void *)key_timehandler, (void *)0, hz); + callout_schedule(&key_timer, hz); #endif /* IPSEC_DEBUG2 */ } @@ -4563,9 +4614,7 @@ key_random() } void -key_randomfill(p, l) - void *p; - size_t l; +key_randomfill(void *p, size_t l) { size_t n; u_long v; @@ -4594,8 +4643,8 @@ key_randomfill(p, l) * OUT: * 0: invalid satype. */ -static u_int16_t -key_satype2proto(u_int8_t satype) +static uint8_t +key_satype2proto(uint8_t satype) { switch (satype) { case SADB_SATYPE_UNSPEC: @@ -4619,8 +4668,8 @@ key_satype2proto(u_int8_t satype) * OUT: * 0: invalid protocol type. */ -static u_int8_t -key_proto2satype(u_int16_t proto) +static uint8_t +key_proto2satype(uint8_t proto) { switch (proto) { case IPPROTO_AH: @@ -4651,44 +4700,58 @@ key_proto2satype(u_int16_t proto) * other if success, return pointer to the message to send. */ static int -key_getspi(so, m, mhp) - struct socket *so; - struct mbuf *m; - const struct sadb_msghdr *mhp; +key_getspi(struct socket *so, struct mbuf *m, const struct sadb_msghdr *mhp) { - struct sadb_address *src0, *dst0; struct secasindex saidx; - struct secashead *newsah; - struct secasvar *newsav; - u_int8_t proto; - u_int32_t spi; - u_int8_t mode; - u_int32_t reqid; + struct sadb_address *src0, *dst0; + struct secasvar *sav; + uint32_t reqid, spi; int error; + uint8_t mode, proto; IPSEC_ASSERT(so != NULL, ("null socket")); IPSEC_ASSERT(m != NULL, ("null mbuf")); IPSEC_ASSERT(mhp != NULL, ("null msghdr")); IPSEC_ASSERT(mhp->msg != NULL, ("null msg")); - if (mhp->ext[SADB_EXT_ADDRESS_SRC] == NULL || - mhp->ext[SADB_EXT_ADDRESS_DST] == NULL) { - ipseclog((LOG_DEBUG, "%s: invalid message is passed.\n", - __func__)); - return key_senderror(so, m, EINVAL); + if (SADB_CHECKHDR(mhp, SADB_EXT_ADDRESS_SRC) || + SADB_CHECKHDR(mhp, SADB_EXT_ADDRESS_DST) +#ifdef PFKEY_STRICT_CHECKS + || SADB_CHECKHDR(mhp, SADB_EXT_SPIRANGE) +#endif + ) { + ipseclog((LOG_DEBUG, + "%s: invalid message: missing required header.\n", + __func__)); + error = EINVAL; + goto fail; } - if (mhp->extlen[SADB_EXT_ADDRESS_SRC] < sizeof(struct sadb_address) || - mhp->extlen[SADB_EXT_ADDRESS_DST] < sizeof(struct sadb_address)) { - ipseclog((LOG_DEBUG, "%s: invalid message is passed.\n", - __func__)); - return key_senderror(so, m, EINVAL); + if (SADB_CHECKLEN(mhp, SADB_EXT_ADDRESS_SRC) || + SADB_CHECKLEN(mhp, SADB_EXT_ADDRESS_DST) +#ifdef PFKEY_STRICT_CHECKS + || SADB_CHECKLEN(mhp, SADB_EXT_SPIRANGE) +#endif + ) { + ipseclog((LOG_DEBUG, + "%s: invalid message: wrong header size.\n", __func__)); + error = EINVAL; + goto fail; } - if (mhp->ext[SADB_X_EXT_SA2] != NULL) { - mode = ((struct sadb_x_sa2 *)mhp->ext[SADB_X_EXT_SA2])->sadb_x_sa2_mode; - reqid = ((struct sadb_x_sa2 *)mhp->ext[SADB_X_EXT_SA2])->sadb_x_sa2_reqid; - } else { + if (SADB_CHECKHDR(mhp, SADB_X_EXT_SA2)) { mode = IPSEC_MODE_ANY; reqid = 0; + } else { + if (SADB_CHECKLEN(mhp, SADB_X_EXT_SA2)) { + ipseclog((LOG_DEBUG, + "%s: invalid message: wrong header size.\n", + __func__)); + error = EINVAL; + goto fail; + } + mode = ((struct sadb_x_sa2 *) + mhp->ext[SADB_X_EXT_SA2])->sadb_x_sa2_mode; + reqid = ((struct sadb_x_sa2 *) + mhp->ext[SADB_X_EXT_SA2])->sadb_x_sa2_reqid; } src0 = (struct sadb_address *)(mhp->ext[SADB_EXT_ADDRESS_SRC]); @@ -4698,121 +4761,55 @@ key_getspi(so, m, mhp) if ((proto = key_satype2proto(mhp->msg->sadb_msg_satype)) == 0) { ipseclog((LOG_DEBUG, "%s: invalid satype is passed.\n", __func__)); - return key_senderror(so, m, EINVAL); - } - - /* - * Make sure the port numbers are zero. - * In case of NAT-T we will update them later if needed. - */ - switch (((struct sockaddr *)(src0 + 1))->sa_family) { - case AF_INET: - if (((struct sockaddr *)(src0 + 1))->sa_len != - sizeof(struct sockaddr_in)) - return key_senderror(so, m, EINVAL); - ((struct sockaddr_in *)(src0 + 1))->sin_port = 0; - break; - case AF_INET6: - if (((struct sockaddr *)(src0 + 1))->sa_len != - sizeof(struct sockaddr_in6)) - return key_senderror(so, m, EINVAL); - ((struct sockaddr_in6 *)(src0 + 1))->sin6_port = 0; - break; - default: - ; /*???*/ + error = EINVAL; + goto fail; } - switch (((struct sockaddr *)(dst0 + 1))->sa_family) { - case AF_INET: - if (((struct sockaddr *)(dst0 + 1))->sa_len != - sizeof(struct sockaddr_in)) - return key_senderror(so, m, EINVAL); - ((struct sockaddr_in *)(dst0 + 1))->sin_port = 0; - break; - case AF_INET6: - if (((struct sockaddr *)(dst0 + 1))->sa_len != - sizeof(struct sockaddr_in6)) - return key_senderror(so, m, EINVAL); - ((struct sockaddr_in6 *)(dst0 + 1))->sin6_port = 0; - break; - default: - ; /*???*/ + error = key_checksockaddrs((struct sockaddr *)(src0 + 1), + (struct sockaddr *)(dst0 + 1)); + if (error != 0) { + ipseclog((LOG_DEBUG, "%s: invalid sockaddr.\n", __func__)); + error = EINVAL; + goto fail; } - - /* XXX boundary check against sa_len */ KEY_SETSECASIDX(proto, mode, reqid, src0 + 1, dst0 + 1, &saidx); -#ifdef IPSEC_NAT_T - /* - * Handle NAT-T info if present. - * We made sure the port numbers are zero above, so we do - * not have to worry in case we do not update them. - */ - if (mhp->ext[SADB_X_EXT_NAT_T_OAI] != NULL) - ipseclog((LOG_DEBUG, "%s: NAT-T OAi present\n", __func__)); - if (mhp->ext[SADB_X_EXT_NAT_T_OAR] != NULL) - ipseclog((LOG_DEBUG, "%s: NAT-T OAr present\n", __func__)); - - if (mhp->ext[SADB_X_EXT_NAT_T_TYPE] != NULL && - mhp->ext[SADB_X_EXT_NAT_T_SPORT] != NULL && - mhp->ext[SADB_X_EXT_NAT_T_DPORT] != NULL) { - struct sadb_x_nat_t_type *type; - struct sadb_x_nat_t_port *sport, *dport; - - if (mhp->extlen[SADB_X_EXT_NAT_T_TYPE] < sizeof(*type) || - mhp->extlen[SADB_X_EXT_NAT_T_SPORT] < sizeof(*sport) || - mhp->extlen[SADB_X_EXT_NAT_T_DPORT] < sizeof(*dport)) { - ipseclog((LOG_DEBUG, "%s: invalid nat-t message " - "passed.\n", __func__)); - return key_senderror(so, m, EINVAL); - } - - sport = (struct sadb_x_nat_t_port *) - mhp->ext[SADB_X_EXT_NAT_T_SPORT]; - dport = (struct sadb_x_nat_t_port *) - mhp->ext[SADB_X_EXT_NAT_T_DPORT]; - - if (sport) - KEY_PORTTOSADDR(&saidx.src, sport->sadb_x_nat_t_port_port); - if (dport) - KEY_PORTTOSADDR(&saidx.dst, dport->sadb_x_nat_t_port_port); - } -#endif - /* SPI allocation */ - spi = key_do_getnewspi((struct sadb_spirange *)mhp->ext[SADB_EXT_SPIRANGE], - &saidx); - if (spi == 0) - return key_senderror(so, m, EINVAL); - - /* get a SA index */ - if ((newsah = key_getsah(&saidx)) == NULL) { - /* create a new SA index */ - if ((newsah = key_newsah(&saidx)) == NULL) { - ipseclog((LOG_DEBUG, "%s: No more memory.\n",__func__)); - return key_senderror(so, m, ENOBUFS); - } + spi = key_do_getnewspi( + (struct sadb_spirange *)mhp->ext[SADB_EXT_SPIRANGE], &saidx); + if (spi == 0) { + /* + * Requested SPI or SPI range is not available or + * already used. + */ + error = EEXIST; + goto fail; } + sav = key_newsav(mhp, &saidx, spi, &error); + if (sav == NULL) + goto fail; - /* get a new SA */ - /* XXX rewrite */ - newsav = KEY_NEWSAV(m, mhp, newsah, &error); - if (newsav == NULL) { - /* XXX don't free new SA index allocated in above. */ - return key_senderror(so, m, error); + if (sav->seq != 0) { + /* + * RFC2367: + * If the SADB_GETSPI message is in response to a + * kernel-generated SADB_ACQUIRE, the sadb_msg_seq + * MUST be the same as the SADB_ACQUIRE message. + * + * XXXAE: However it doesn't definethe behaviour how to + * check this and what to do if it doesn't match. + * Also what we should do if it matches? + * + * We can compare saidx used in SADB_ACQUIRE with saidx + * used in SADB_GETSPI, but this probably can break + * existing software. For now just warn if it doesn't match. + * + * XXXAE: anyway it looks useless. + */ + key_acqdone(&saidx, sav->seq); } - - /* set spi */ - newsav->spi = htonl(spi); - - /* delete the entry in acqtree */ - if (mhp->msg->sadb_msg_seq != 0) { - struct secacq *acq; - if ((acq = key_getacqbyseq(mhp->msg->sadb_msg_seq)) != NULL) { - /* reset counter in order to deletion by timehandler. */ - acq->created = time_second; - acq->count = 0; - } - } + KEYDBG(KEY_STAMP, + printf("%s: SA(%p)\n", __func__, sav)); + KEYDBG(KEY_DATA, kdebug_secasv(sav)); { struct mbuf *n, *nn; @@ -4824,16 +4821,17 @@ key_getspi(so, m, mhp) len = PFKEY_ALIGN8(sizeof(struct sadb_msg)) + PFKEY_ALIGN8(sizeof(struct sadb_sa)); - MGETHDR(n, M_DONTWAIT, MT_DATA); + MGETHDR(n, M_NOWAIT, MT_DATA); if (len > MHLEN) { - MCLGET(n, M_DONTWAIT); - if ((n->m_flags & M_EXT) == 0) { + if (!(MCLGET(n, M_NOWAIT))) { m_freem(n); n = NULL; } } - if (!n) - return key_senderror(so, m, ENOBUFS); + if (!n) { + error = ENOBUFS; + goto fail; + } n->m_len = len; n->m_next = NULL; @@ -4845,7 +4843,7 @@ key_getspi(so, m, mhp) m_sa = (struct sadb_sa *)(mtod(n, caddr_t) + off); m_sa->sadb_sa_len = PFKEY_UNIT64(sizeof(struct sadb_sa)); m_sa->sadb_sa_exttype = SADB_EXT_SA; - m_sa->sadb_sa_spi = htonl(spi); + m_sa->sadb_sa_spi = spi; /* SPI is already in network byte order */ off += PFKEY_ALIGN8(sizeof(struct sadb_sa)); IPSEC_ASSERT(off == len, @@ -4855,7 +4853,8 @@ key_getspi(so, m, mhp) SADB_EXT_ADDRESS_DST); if (!n->m_next) { m_freem(n); - return key_senderror(so, m, ENOBUFS); + error = ENOBUFS; + goto fail; } if (n->m_len < sizeof(struct sadb_msg)) { @@ -4869,13 +4868,16 @@ key_getspi(so, m, mhp) n->m_pkthdr.len += nn->m_len; newmsg = mtod(n, struct sadb_msg *); - newmsg->sadb_msg_seq = newsav->seq; + newmsg->sadb_msg_seq = sav->seq; newmsg->sadb_msg_errno = 0; newmsg->sadb_msg_len = PFKEY_UNIT64(n->m_pkthdr.len); m_freem(m); return key_sendup_mbuf(so, n, KEY_SENDUP_ONE); } + +fail: + return (key_senderror(so, m, error)); } /* @@ -4883,15 +4885,12 @@ key_getspi(so, m, mhp) * called by key_getspi(). * OUT: * 0: failure. - * others: success. + * others: success, SPI in network byte order. */ -static u_int32_t -key_do_getnewspi(spirange, saidx) - struct sadb_spirange *spirange; - struct secasindex *saidx; +static uint32_t +key_do_getnewspi(struct sadb_spirange *spirange, struct secasindex *saidx) { - u_int32_t newspi; - u_int32_t min, max; + uint32_t min, max, newspi, t; int count = V_key_spi_trycnt; /* set spi range to allocate */ @@ -4904,7 +4903,6 @@ key_do_getnewspi(spirange, saidx) } /* IPCOMP needs 2-byte SPI */ if (saidx->proto == IPPROTO_IPCOMP) { - u_int32_t t; if (min >= 0x10000) min = 0xffff; if (max >= 0x10000) @@ -4915,15 +4913,14 @@ key_do_getnewspi(spirange, saidx) } if (min == max) { - if (key_checkspidup(saidx, min) != NULL) { + if (!key_checkspidup(htonl(min))) { ipseclog((LOG_DEBUG, "%s: SPI %u exists already.\n", - __func__, min)); + __func__, min)); return 0; } count--; /* taking one cost. */ newspi = min; - } else { /* init SPI */ @@ -4933,23 +4930,244 @@ key_do_getnewspi(spirange, saidx) while (count--) { /* generate pseudo-random SPI value ranged. */ newspi = min + (key_random() % (max - min + 1)); - - if (key_checkspidup(saidx, newspi) == NULL) + if (!key_checkspidup(htonl(newspi))) break; } if (count == 0 || newspi == 0) { - ipseclog((LOG_DEBUG, "%s: to allocate spi is failed.\n", - __func__)); + ipseclog((LOG_DEBUG, + "%s: failed to allocate SPI.\n", __func__)); return 0; } } /* statistics */ keystat.getspi_count = - (keystat.getspi_count + V_key_spi_trycnt - count) / 2; + (keystat.getspi_count + V_key_spi_trycnt - count) / 2; + + return (htonl(newspi)); +} + +/* + * Find TCP-MD5 SA with corresponding secasindex. + * If not found, return NULL and fill SPI with usable value if needed. + */ +static struct secasvar * +key_getsav_tcpmd5(struct secasindex *saidx, uint32_t *spi) +{ + SAHTREE_RLOCK_TRACKER; + struct secashead *sah; + struct secasvar *sav; + + IPSEC_ASSERT(saidx->proto == IPPROTO_TCP, ("wrong proto")); + SAHTREE_RLOCK(); + LIST_FOREACH(sah, SAHADDRHASH_HASH(saidx), addrhash) { + if (sah->saidx.proto != IPPROTO_TCP) + continue; + if (!key_sockaddrcmp(&saidx->dst.sa, &sah->saidx.dst.sa, 0)) + break; + } + if (sah != NULL) { + if (V_key_preferred_oldsa) + sav = TAILQ_LAST(&sah->savtree_alive, secasvar_queue); + else + sav = TAILQ_FIRST(&sah->savtree_alive); + if (sav != NULL) { + SAV_ADDREF(sav); + SAHTREE_RUNLOCK(); + return (sav); + } + } + if (spi == NULL) { + /* No SPI required */ + SAHTREE_RUNLOCK(); + return (NULL); + } + /* Check that SPI is unique */ + LIST_FOREACH(sav, SAVHASH_HASH(*spi), spihash) { + if (sav->spi == *spi) + break; + } + if (sav == NULL) { + SAHTREE_RUNLOCK(); + /* SPI is already unique */ + return (NULL); + } + SAHTREE_RUNLOCK(); + /* XXX: not optimal */ + *spi = key_do_getnewspi(NULL, saidx); + return (NULL); +} + +static int +key_updateaddresses(struct socket *so, struct mbuf *m, + const struct sadb_msghdr *mhp, struct secasvar *sav, + struct secasindex *saidx) +{ + struct sockaddr *newaddr; + struct secashead *sah; + struct secasvar *newsav, *tmp; + struct mbuf *n; + int error, isnew; + + /* Check that we need to change SAH */ + if (!SADB_CHECKHDR(mhp, SADB_X_EXT_NEW_ADDRESS_SRC)) { + newaddr = (struct sockaddr *)( + ((struct sadb_address *) + mhp->ext[SADB_X_EXT_NEW_ADDRESS_SRC]) + 1); + bcopy(newaddr, &saidx->src, newaddr->sa_len); + key_porttosaddr(&saidx->src.sa, 0); + } + if (!SADB_CHECKHDR(mhp, SADB_X_EXT_NEW_ADDRESS_DST)) { + newaddr = (struct sockaddr *)( + ((struct sadb_address *) + mhp->ext[SADB_X_EXT_NEW_ADDRESS_DST]) + 1); + bcopy(newaddr, &saidx->dst, newaddr->sa_len); + key_porttosaddr(&saidx->dst.sa, 0); + } + if (!SADB_CHECKHDR(mhp, SADB_X_EXT_NEW_ADDRESS_SRC) || + !SADB_CHECKHDR(mhp, SADB_X_EXT_NEW_ADDRESS_DST)) { + error = key_checksockaddrs(&saidx->src.sa, &saidx->dst.sa); + if (error != 0) { + ipseclog((LOG_DEBUG, "%s: invalid new sockaddr.\n", + __func__)); + return (error); + } + + sah = key_getsah(saidx); + if (sah == NULL) { + /* create a new SA index */ + sah = key_newsah(saidx); + if (sah == NULL) { + ipseclog((LOG_DEBUG, + "%s: No more memory.\n", __func__)); + return (ENOBUFS); + } + isnew = 2; /* SAH is new */ + } else + isnew = 1; /* existing SAH is referenced */ + } else { + /* + * src and dst addresses are still the same. + * Do we want to change NAT-T config? + */ + if (sav->sah->saidx.proto != IPPROTO_ESP || + SADB_CHECKHDR(mhp, SADB_X_EXT_NAT_T_TYPE) || + SADB_CHECKHDR(mhp, SADB_X_EXT_NAT_T_SPORT) || + SADB_CHECKHDR(mhp, SADB_X_EXT_NAT_T_DPORT)) { + ipseclog((LOG_DEBUG, + "%s: invalid message: missing required header.\n", + __func__)); + return (EINVAL); + } + /* We hold reference to SA, thus SAH will be referenced too. */ + sah = sav->sah; + isnew = 0; + } + + newsav = malloc(sizeof(struct secasvar), M_IPSEC_SA, + M_NOWAIT | M_ZERO); + if (newsav == NULL) { + ipseclog((LOG_DEBUG, "%s: No more memory.\n", __func__)); + error = ENOBUFS; + goto fail; + } + + /* Clone SA's content into newsav */ + SAV_INITREF(newsav); + bcopy(sav, newsav, offsetof(struct secasvar, chain)); + /* + * We create new NAT-T config if it is needed. + * Old NAT-T config will be freed by key_cleansav() when + * last reference to SA will be released. + */ + newsav->natt = NULL; + newsav->sah = sah; + newsav->state = SADB_SASTATE_MATURE; + error = key_setnatt(sav, mhp); + if (error != 0) + goto fail; + + SAHTREE_WLOCK(); + /* Check that SA is still alive */ + if (sav->state == SADB_SASTATE_DEAD) { + /* SA was unlinked */ + SAHTREE_WUNLOCK(); + error = ESRCH; + goto fail; + } + + /* Unlink SA from SAH and SPI hash */ + IPSEC_ASSERT((sav->flags & SADB_X_EXT_F_CLONED) == 0, + ("SA is already cloned")); + IPSEC_ASSERT(sav->state == SADB_SASTATE_MATURE || + sav->state == SADB_SASTATE_DYING, + ("Wrong SA state %u\n", sav->state)); + TAILQ_REMOVE(&sav->sah->savtree_alive, sav, chain); + LIST_REMOVE(sav, spihash); + sav->state = SADB_SASTATE_DEAD; + + /* + * Link new SA with SAH. Keep SAs ordered by + * create time (newer are first). + */ + TAILQ_FOREACH(tmp, &sah->savtree_alive, chain) { + if (newsav->created > tmp->created) { + TAILQ_INSERT_BEFORE(tmp, newsav, chain); + break; + } + } + if (tmp == NULL) + TAILQ_INSERT_TAIL(&sah->savtree_alive, newsav, chain); + + /* Add new SA into SPI hash. */ + LIST_INSERT_HEAD(SAVHASH_HASH(newsav->spi), newsav, spihash); + + /* Add new SAH into SADB. */ + if (isnew == 2) { + TAILQ_INSERT_HEAD(&V_sahtree, sah, chain); + LIST_INSERT_HEAD(SAHADDRHASH_HASH(saidx), sah, addrhash); + sah->state = SADB_SASTATE_MATURE; + SAH_ADDREF(sah); /* newsav references new SAH */ + } + /* + * isnew == 1 -> @sah was referenced by key_getsah(). + * isnew == 0 -> we use the same @sah, that was used by @sav, + * and we use its reference for @newsav. + */ + SECASVAR_LOCK(sav); + /* XXX: replace cntr with pointer? */ + newsav->cntr = sav->cntr; + sav->flags |= SADB_X_EXT_F_CLONED; + SECASVAR_UNLOCK(sav); + + SAHTREE_WUNLOCK(); - return newspi; + KEYDBG(KEY_STAMP, + printf("%s: SA(%p) cloned into SA(%p)\n", + __func__, sav, newsav)); + KEYDBG(KEY_DATA, kdebug_secasv(newsav)); + + key_freesav(&sav); /* release last reference */ + + /* set msg buf from mhp */ + n = key_getmsgbuf_x1(m, mhp); + if (n == NULL) { + ipseclog((LOG_DEBUG, "%s: No more memory.\n", __func__)); + return (ENOBUFS); + } + m_freem(m); + key_sendup_mbuf(so, n, KEY_SENDUP_ALL); + return (0); +fail: + if (isnew != 0) + key_freesah(&sah); + if (newsav != NULL) { + if (newsav->natt != NULL) + free(newsav->natt, M_IPSEC_MISC); + free(newsav, M_IPSEC_SA); + } + return (error); } /* @@ -4966,26 +5184,15 @@ key_do_getnewspi(spirange, saidx) * m will always be freed. */ static int -key_update(so, m, mhp) - struct socket *so; - struct mbuf *m; - const struct sadb_msghdr *mhp; +key_update(struct socket *so, struct mbuf *m, const struct sadb_msghdr *mhp) { - struct sadb_sa *sa0; - struct sadb_address *src0, *dst0; -#ifdef IPSEC_NAT_T - struct sadb_x_nat_t_type *type; - struct sadb_x_nat_t_port *sport, *dport; - struct sadb_address *iaddr, *raddr; - struct sadb_x_nat_t_frag *frag; -#endif struct secasindex saidx; - struct secashead *sah; + struct sadb_address *src0, *dst0; + struct sadb_sa *sa0; struct secasvar *sav; - u_int16_t proto; - u_int8_t mode; - u_int32_t reqid; + uint32_t reqid; int error; + uint8_t mode, proto; IPSEC_ASSERT(so != NULL, ("null socket")); IPSEC_ASSERT(m != NULL, ("null mbuf")); @@ -4995,199 +5202,182 @@ key_update(so, m, mhp) /* map satype to proto */ if ((proto = key_satype2proto(mhp->msg->sadb_msg_satype)) == 0) { ipseclog((LOG_DEBUG, "%s: invalid satype is passed.\n", - __func__)); + __func__)); return key_senderror(so, m, EINVAL); } - if (mhp->ext[SADB_EXT_SA] == NULL || - mhp->ext[SADB_EXT_ADDRESS_SRC] == NULL || - mhp->ext[SADB_EXT_ADDRESS_DST] == NULL || - (mhp->msg->sadb_msg_satype == SADB_SATYPE_ESP && - mhp->ext[SADB_EXT_KEY_ENCRYPT] == NULL) || - (mhp->msg->sadb_msg_satype == SADB_SATYPE_AH && - mhp->ext[SADB_EXT_KEY_AUTH] == NULL) || - (mhp->ext[SADB_EXT_LIFETIME_HARD] != NULL && - mhp->ext[SADB_EXT_LIFETIME_SOFT] == NULL) || - (mhp->ext[SADB_EXT_LIFETIME_HARD] == NULL && - mhp->ext[SADB_EXT_LIFETIME_SOFT] != NULL)) { - ipseclog((LOG_DEBUG, "%s: invalid message is passed.\n", - __func__)); + if (SADB_CHECKHDR(mhp, SADB_EXT_SA) || + SADB_CHECKHDR(mhp, SADB_EXT_ADDRESS_SRC) || + SADB_CHECKHDR(mhp, SADB_EXT_ADDRESS_DST) || + (SADB_CHECKHDR(mhp, SADB_EXT_LIFETIME_HARD) && + !SADB_CHECKHDR(mhp, SADB_EXT_LIFETIME_SOFT)) || + (SADB_CHECKHDR(mhp, SADB_EXT_LIFETIME_SOFT) && + !SADB_CHECKHDR(mhp, SADB_EXT_LIFETIME_HARD))) { + ipseclog((LOG_DEBUG, + "%s: invalid message: missing required header.\n", + __func__)); return key_senderror(so, m, EINVAL); } - if (mhp->extlen[SADB_EXT_SA] < sizeof(struct sadb_sa) || - mhp->extlen[SADB_EXT_ADDRESS_SRC] < sizeof(struct sadb_address) || - mhp->extlen[SADB_EXT_ADDRESS_DST] < sizeof(struct sadb_address)) { - ipseclog((LOG_DEBUG, "%s: invalid message is passed.\n", - __func__)); + if (SADB_CHECKLEN(mhp, SADB_EXT_SA) || + SADB_CHECKLEN(mhp, SADB_EXT_ADDRESS_SRC) || + SADB_CHECKLEN(mhp, SADB_EXT_ADDRESS_DST)) { + ipseclog((LOG_DEBUG, + "%s: invalid message: wrong header size.\n", __func__)); return key_senderror(so, m, EINVAL); } - if (mhp->ext[SADB_X_EXT_SA2] != NULL) { - mode = ((struct sadb_x_sa2 *)mhp->ext[SADB_X_EXT_SA2])->sadb_x_sa2_mode; - reqid = ((struct sadb_x_sa2 *)mhp->ext[SADB_X_EXT_SA2])->sadb_x_sa2_reqid; - } else { + if (SADB_CHECKHDR(mhp, SADB_X_EXT_SA2)) { mode = IPSEC_MODE_ANY; reqid = 0; + } else { + if (SADB_CHECKLEN(mhp, SADB_X_EXT_SA2)) { + ipseclog((LOG_DEBUG, + "%s: invalid message: wrong header size.\n", + __func__)); + return key_senderror(so, m, EINVAL); + } + mode = ((struct sadb_x_sa2 *) + mhp->ext[SADB_X_EXT_SA2])->sadb_x_sa2_mode; + reqid = ((struct sadb_x_sa2 *) + mhp->ext[SADB_X_EXT_SA2])->sadb_x_sa2_reqid; } - /* XXX boundary checking for other extensions */ sa0 = (struct sadb_sa *)mhp->ext[SADB_EXT_SA]; src0 = (struct sadb_address *)(mhp->ext[SADB_EXT_ADDRESS_SRC]); dst0 = (struct sadb_address *)(mhp->ext[SADB_EXT_ADDRESS_DST]); - /* XXX boundary check against sa_len */ - KEY_SETSECASIDX(proto, mode, reqid, src0 + 1, dst0 + 1, &saidx); - - /* - * Make sure the port numbers are zero. - * In case of NAT-T we will update them later if needed. - */ - KEY_PORTTOSADDR(&saidx.src, 0); - KEY_PORTTOSADDR(&saidx.dst, 0); - -#ifdef IPSEC_NAT_T /* - * Handle NAT-T info if present. + * Only SADB_SASTATE_MATURE SAs may be submitted in an + * SADB_UPDATE message. */ - if (mhp->ext[SADB_X_EXT_NAT_T_TYPE] != NULL && - mhp->ext[SADB_X_EXT_NAT_T_SPORT] != NULL && - mhp->ext[SADB_X_EXT_NAT_T_DPORT] != NULL) { - - if (mhp->extlen[SADB_X_EXT_NAT_T_TYPE] < sizeof(*type) || - mhp->extlen[SADB_X_EXT_NAT_T_SPORT] < sizeof(*sport) || - mhp->extlen[SADB_X_EXT_NAT_T_DPORT] < sizeof(*dport)) { - ipseclog((LOG_DEBUG, "%s: invalid message.\n", - __func__)); - return key_senderror(so, m, EINVAL); - } - - type = (struct sadb_x_nat_t_type *) - mhp->ext[SADB_X_EXT_NAT_T_TYPE]; - sport = (struct sadb_x_nat_t_port *) - mhp->ext[SADB_X_EXT_NAT_T_SPORT]; - dport = (struct sadb_x_nat_t_port *) - mhp->ext[SADB_X_EXT_NAT_T_DPORT]; - } else { - type = 0; - sport = dport = 0; - } - if (mhp->ext[SADB_X_EXT_NAT_T_OAI] != NULL && - mhp->ext[SADB_X_EXT_NAT_T_OAR] != NULL) { - if (mhp->extlen[SADB_X_EXT_NAT_T_OAI] < sizeof(*iaddr) || - mhp->extlen[SADB_X_EXT_NAT_T_OAR] < sizeof(*raddr)) { - ipseclog((LOG_DEBUG, "%s: invalid message\n", - __func__)); - return key_senderror(so, m, EINVAL); - } - iaddr = (struct sadb_address *)mhp->ext[SADB_X_EXT_NAT_T_OAI]; - raddr = (struct sadb_address *)mhp->ext[SADB_X_EXT_NAT_T_OAR]; - ipseclog((LOG_DEBUG, "%s: NAT-T OAi/r present\n", __func__)); - } else { - iaddr = raddr = NULL; - } - if (mhp->ext[SADB_X_EXT_NAT_T_FRAG] != NULL) { - if (mhp->extlen[SADB_X_EXT_NAT_T_FRAG] < sizeof(*frag)) { - ipseclog((LOG_DEBUG, "%s: invalid message\n", - __func__)); - return key_senderror(so, m, EINVAL); - } - frag = (struct sadb_x_nat_t_frag *) - mhp->ext[SADB_X_EXT_NAT_T_FRAG]; - } else { - frag = 0; - } + if (sa0->sadb_sa_state != SADB_SASTATE_MATURE) { + ipseclog((LOG_DEBUG, "%s: invalid state.\n", __func__)); +#ifdef PFKEY_STRICT_CHECKS + return key_senderror(so, m, EINVAL); #endif - - /* get a SA header */ - if ((sah = key_getsah(&saidx)) == NULL) { - ipseclog((LOG_DEBUG, "%s: no SA index found.\n", __func__)); - return key_senderror(so, m, ENOENT); } - - /* set spidx if there */ - /* XXX rewrite */ - error = key_setident(sah, m, mhp); - if (error) + error = key_checksockaddrs((struct sockaddr *)(src0 + 1), + (struct sockaddr *)(dst0 + 1)); + if (error != 0) { + ipseclog((LOG_DEBUG, "%s: invalid sockaddr.\n", __func__)); return key_senderror(so, m, error); - - /* find a SA with sequence number. */ -#ifdef IPSEC_DOSEQCHECK - if (mhp->msg->sadb_msg_seq != 0 - && (sav = key_getsavbyseq(sah, mhp->msg->sadb_msg_seq)) == NULL) { - ipseclog((LOG_DEBUG, "%s: no larval SA with sequence %u " - "exists.\n", __func__, mhp->msg->sadb_msg_seq)); - return key_senderror(so, m, ENOENT); } -#else - SAHTREE_LOCK(); - sav = key_getsavbyspi(sah, sa0->sadb_sa_spi); - SAHTREE_UNLOCK(); + KEY_SETSECASIDX(proto, mode, reqid, src0 + 1, dst0 + 1, &saidx); + sav = key_getsavbyspi(sa0->sadb_sa_spi); if (sav == NULL) { - ipseclog((LOG_DEBUG, "%s: no such a SA found (spi:%u)\n", - __func__, (u_int32_t)ntohl(sa0->sadb_sa_spi))); - return key_senderror(so, m, EINVAL); - } -#endif - - /* validity check */ - if (sav->sah->saidx.proto != proto) { - ipseclog((LOG_DEBUG, "%s: protocol mismatched " - "(DB=%u param=%u)\n", __func__, - sav->sah->saidx.proto, proto)); + ipseclog((LOG_DEBUG, "%s: no SA found for SPI %u\n", + __func__, ntohl(sa0->sadb_sa_spi))); return key_senderror(so, m, EINVAL); } -#ifdef IPSEC_DOSEQCHECK - if (sav->spi != sa0->sadb_sa_spi) { - ipseclog((LOG_DEBUG, "%s: SPI mismatched (DB:%u param:%u)\n", - __func__, - (u_int32_t)ntohl(sav->spi), - (u_int32_t)ntohl(sa0->sadb_sa_spi))); - return key_senderror(so, m, EINVAL); - } -#endif + /* + * Check that SADB_UPDATE issued by the same process that did + * SADB_GETSPI or SADB_ADD. + */ if (sav->pid != mhp->msg->sadb_msg_pid) { - ipseclog((LOG_DEBUG, "%s: pid mismatched (DB:%u param:%u)\n", - __func__, sav->pid, mhp->msg->sadb_msg_pid)); + ipseclog((LOG_DEBUG, + "%s: pid mismatched (SPI %u, pid %u vs. %u)\n", __func__, + ntohl(sav->spi), sav->pid, mhp->msg->sadb_msg_pid)); + key_freesav(&sav); return key_senderror(so, m, EINVAL); } - - /* copy sav values */ - error = key_setsaval(sav, m, mhp); - if (error) { - KEY_FREESAV(&sav); - return key_senderror(so, m, error); - } - -#ifdef IPSEC_NAT_T - /* - * Handle more NAT-T info if present, - * now that we have a sav to fill. - */ - if (type) - sav->natt_type = type->sadb_x_nat_t_type_type; - - if (sport) - KEY_PORTTOSADDR(&sav->sah->saidx.src, - sport->sadb_x_nat_t_port_port); - if (dport) - KEY_PORTTOSADDR(&sav->sah->saidx.dst, - dport->sadb_x_nat_t_port_port); - -#if 0 - /* - * In case SADB_X_EXT_NAT_T_FRAG was not given, leave it at 0. - * We should actually check for a minimum MTU here, if we - * want to support it in ip_output. - */ - if (frag) - sav->natt_esp_frag_len = frag->sadb_x_nat_t_frag_fraglen; -#endif -#endif - - /* check SA values to be mature. */ - if ((mhp->msg->sadb_msg_errno = key_mature(sav)) != 0) { - KEY_FREESAV(&sav); - return key_senderror(so, m, 0); + /* saidx should match with SA. */ + if (key_cmpsaidx(&sav->sah->saidx, &saidx, CMP_MODE_REQID) == 0) { + ipseclog((LOG_DEBUG, "%s: saidx mismatched for SPI %u", + __func__, ntohl(sav->spi))); + key_freesav(&sav); + return key_senderror(so, m, ESRCH); + } + + if (sav->state == SADB_SASTATE_LARVAL) { + if ((mhp->msg->sadb_msg_satype == SADB_SATYPE_ESP && + SADB_CHECKHDR(mhp, SADB_EXT_KEY_ENCRYPT)) || + (mhp->msg->sadb_msg_satype == SADB_SATYPE_AH && + SADB_CHECKHDR(mhp, SADB_EXT_KEY_AUTH))) { + ipseclog((LOG_DEBUG, + "%s: invalid message: missing required header.\n", + __func__)); + key_freesav(&sav); + return key_senderror(so, m, EINVAL); + } + /* + * We can set any values except src, dst and SPI. + */ + error = key_setsaval(sav, mhp); + if (error != 0) { + key_freesav(&sav); + return (key_senderror(so, m, error)); + } + /* Change SA state to MATURE */ + SAHTREE_WLOCK(); + if (sav->state != SADB_SASTATE_LARVAL) { + /* SA was deleted or another thread made it MATURE. */ + SAHTREE_WUNLOCK(); + key_freesav(&sav); + return (key_senderror(so, m, ESRCH)); + } + /* + * NOTE: we keep SAs in savtree_alive ordered by created + * time. When SA's state changed from LARVAL to MATURE, + * we update its created time in key_setsaval() and move + * it into head of savtree_alive. + */ + TAILQ_REMOVE(&sav->sah->savtree_larval, sav, chain); + TAILQ_INSERT_HEAD(&sav->sah->savtree_alive, sav, chain); + sav->state = SADB_SASTATE_MATURE; + SAHTREE_WUNLOCK(); + } else { + /* + * For DYING and MATURE SA we can change only state + * and lifetimes. Report EINVAL if something else attempted + * to change. + */ + if (!SADB_CHECKHDR(mhp, SADB_EXT_KEY_ENCRYPT) || + !SADB_CHECKHDR(mhp, SADB_EXT_KEY_AUTH)) { + key_freesav(&sav); + return (key_senderror(so, m, EINVAL)); + } + error = key_updatelifetimes(sav, mhp); + if (error != 0) { + key_freesav(&sav); + return (key_senderror(so, m, error)); + } + /* + * This is FreeBSD extension to RFC2367. + * IKEd can specify SADB_X_EXT_NEW_ADDRESS_SRC and/or + * SADB_X_EXT_NEW_ADDRESS_DST when it wants to change + * SA addresses (for example to implement MOBIKE protocol + * as described in RFC4555). Also we allow to change + * NAT-T config. + */ + if (!SADB_CHECKHDR(mhp, SADB_X_EXT_NEW_ADDRESS_SRC) || + !SADB_CHECKHDR(mhp, SADB_X_EXT_NEW_ADDRESS_DST) || + !SADB_CHECKHDR(mhp, SADB_X_EXT_NAT_T_TYPE) || + sav->natt != NULL) { + error = key_updateaddresses(so, m, mhp, sav, &saidx); + key_freesav(&sav); + if (error != 0) + return (key_senderror(so, m, error)); + return (0); + } + /* Check that SA is still alive */ + SAHTREE_WLOCK(); + if (sav->state == SADB_SASTATE_DEAD) { + /* SA was unlinked */ + SAHTREE_WUNLOCK(); + key_freesav(&sav); + return (key_senderror(so, m, ESRCH)); + } + /* + * NOTE: there is possible state moving from DYING to MATURE, + * but this doesn't change created time, so we won't reorder + * this SA. + */ + sav->state = SADB_SASTATE_MATURE; + SAHTREE_WUNLOCK(); } + KEYDBG(KEY_STAMP, + printf("%s: SA(%p)\n", __func__, sav)); + KEYDBG(KEY_DATA, kdebug_secasv(sav)); + key_freesav(&sav); { struct mbuf *n; @@ -5205,42 +5395,6 @@ key_update(so, m, mhp) } /* - * search SAD with sequence for a SA which state is SADB_SASTATE_LARVAL. - * only called by key_update(). - * OUT: - * NULL : not found - * others : found, pointer to a SA. - */ -#ifdef IPSEC_DOSEQCHECK -static struct secasvar * -key_getsavbyseq(sah, seq) - struct secashead *sah; - u_int32_t seq; -{ - struct secasvar *sav; - u_int state; - - state = SADB_SASTATE_LARVAL; - - /* search SAD with sequence number ? */ - LIST_FOREACH(sav, &sah->savtree[state], chain) { - - KEY_CHKSASTATE(state, sav->state, __func__); - - if (sav->seq == seq) { - sa_addref(sav); - KEYDEBUG(KEYDEBUG_IPSEC_STAMP, - printf("DP %s cause refcnt++:%d SA:%p\n", - __func__, sav->refcnt, sav)); - return sav; - } - } - - return NULL; -} -#endif - -/* * SADB_ADD processing * add an entry to SA database, when received * <base, SA, (SA2), (lifetime(HSC),) address(SD), (address(P),) @@ -5256,24 +5410,14 @@ key_getsavbyseq(sah, seq) * m will always be freed. */ static int -key_add(so, m, mhp) - struct socket *so; - struct mbuf *m; - const struct sadb_msghdr *mhp; +key_add(struct socket *so, struct mbuf *m, const struct sadb_msghdr *mhp) { - struct sadb_sa *sa0; - struct sadb_address *src0, *dst0; -#ifdef IPSEC_NAT_T - struct sadb_x_nat_t_type *type; - struct sadb_address *iaddr, *raddr; - struct sadb_x_nat_t_frag *frag; -#endif struct secasindex saidx; - struct secashead *newsah; - struct secasvar *newsav; - u_int16_t proto; - u_int8_t mode; - u_int32_t reqid; + struct sadb_address *src0, *dst0; + struct sadb_sa *sa0; + struct secasvar *sav; + uint32_t reqid, spi; + uint8_t mode, proto; int error; IPSEC_ASSERT(so != NULL, ("null socket")); @@ -5284,176 +5428,115 @@ key_add(so, m, mhp) /* map satype to proto */ if ((proto = key_satype2proto(mhp->msg->sadb_msg_satype)) == 0) { ipseclog((LOG_DEBUG, "%s: invalid satype is passed.\n", - __func__)); + __func__)); return key_senderror(so, m, EINVAL); } - if (mhp->ext[SADB_EXT_SA] == NULL || - mhp->ext[SADB_EXT_ADDRESS_SRC] == NULL || - mhp->ext[SADB_EXT_ADDRESS_DST] == NULL || - (mhp->msg->sadb_msg_satype == SADB_SATYPE_ESP && - mhp->ext[SADB_EXT_KEY_ENCRYPT] == NULL) || - (mhp->msg->sadb_msg_satype == SADB_SATYPE_AH && - mhp->ext[SADB_EXT_KEY_AUTH] == NULL) || - (mhp->ext[SADB_EXT_LIFETIME_HARD] != NULL && - mhp->ext[SADB_EXT_LIFETIME_SOFT] == NULL) || - (mhp->ext[SADB_EXT_LIFETIME_HARD] == NULL && - mhp->ext[SADB_EXT_LIFETIME_SOFT] != NULL)) { - ipseclog((LOG_DEBUG, "%s: invalid message is passed.\n", - __func__)); + if (SADB_CHECKHDR(mhp, SADB_EXT_SA) || + SADB_CHECKHDR(mhp, SADB_EXT_ADDRESS_SRC) || + SADB_CHECKHDR(mhp, SADB_EXT_ADDRESS_DST) || + (mhp->msg->sadb_msg_satype == SADB_SATYPE_ESP && ( + SADB_CHECKHDR(mhp, SADB_EXT_KEY_ENCRYPT) || + SADB_CHECKLEN(mhp, SADB_EXT_KEY_ENCRYPT))) || + (mhp->msg->sadb_msg_satype == SADB_SATYPE_AH && ( + SADB_CHECKHDR(mhp, SADB_EXT_KEY_AUTH) || + SADB_CHECKLEN(mhp, SADB_EXT_KEY_AUTH))) || + (SADB_CHECKHDR(mhp, SADB_EXT_LIFETIME_HARD) && + !SADB_CHECKHDR(mhp, SADB_EXT_LIFETIME_SOFT)) || + (SADB_CHECKHDR(mhp, SADB_EXT_LIFETIME_SOFT) && + !SADB_CHECKHDR(mhp, SADB_EXT_LIFETIME_HARD))) { + ipseclog((LOG_DEBUG, + "%s: invalid message: missing required header.\n", + __func__)); return key_senderror(so, m, EINVAL); } - if (mhp->extlen[SADB_EXT_SA] < sizeof(struct sadb_sa) || - mhp->extlen[SADB_EXT_ADDRESS_SRC] < sizeof(struct sadb_address) || - mhp->extlen[SADB_EXT_ADDRESS_DST] < sizeof(struct sadb_address)) { - /* XXX need more */ - ipseclog((LOG_DEBUG, "%s: invalid message is passed.\n", - __func__)); + if (SADB_CHECKLEN(mhp, SADB_EXT_SA) || + SADB_CHECKLEN(mhp, SADB_EXT_ADDRESS_SRC) || + SADB_CHECKLEN(mhp, SADB_EXT_ADDRESS_DST)) { + ipseclog((LOG_DEBUG, + "%s: invalid message: wrong header size.\n", __func__)); return key_senderror(so, m, EINVAL); } - if (mhp->ext[SADB_X_EXT_SA2] != NULL) { - mode = ((struct sadb_x_sa2 *)mhp->ext[SADB_X_EXT_SA2])->sadb_x_sa2_mode; - reqid = ((struct sadb_x_sa2 *)mhp->ext[SADB_X_EXT_SA2])->sadb_x_sa2_reqid; - } else { + if (SADB_CHECKHDR(mhp, SADB_X_EXT_SA2)) { mode = IPSEC_MODE_ANY; reqid = 0; + } else { + if (SADB_CHECKLEN(mhp, SADB_X_EXT_SA2)) { + ipseclog((LOG_DEBUG, + "%s: invalid message: wrong header size.\n", + __func__)); + return key_senderror(so, m, EINVAL); + } + mode = ((struct sadb_x_sa2 *) + mhp->ext[SADB_X_EXT_SA2])->sadb_x_sa2_mode; + reqid = ((struct sadb_x_sa2 *) + mhp->ext[SADB_X_EXT_SA2])->sadb_x_sa2_reqid; } sa0 = (struct sadb_sa *)mhp->ext[SADB_EXT_SA]; src0 = (struct sadb_address *)mhp->ext[SADB_EXT_ADDRESS_SRC]; dst0 = (struct sadb_address *)mhp->ext[SADB_EXT_ADDRESS_DST]; - /* XXX boundary check against sa_len */ - KEY_SETSECASIDX(proto, mode, reqid, src0 + 1, dst0 + 1, &saidx); - - /* - * Make sure the port numbers are zero. - * In case of NAT-T we will update them later if needed. - */ - KEY_PORTTOSADDR(&saidx.src, 0); - KEY_PORTTOSADDR(&saidx.dst, 0); - -#ifdef IPSEC_NAT_T /* - * Handle NAT-T info if present. + * Only SADB_SASTATE_MATURE SAs may be submitted in an + * SADB_ADD message. */ - if (mhp->ext[SADB_X_EXT_NAT_T_TYPE] != NULL && - mhp->ext[SADB_X_EXT_NAT_T_SPORT] != NULL && - mhp->ext[SADB_X_EXT_NAT_T_DPORT] != NULL) { - struct sadb_x_nat_t_port *sport, *dport; - - if (mhp->extlen[SADB_X_EXT_NAT_T_TYPE] < sizeof(*type) || - mhp->extlen[SADB_X_EXT_NAT_T_SPORT] < sizeof(*sport) || - mhp->extlen[SADB_X_EXT_NAT_T_DPORT] < sizeof(*dport)) { - ipseclog((LOG_DEBUG, "%s: invalid message.\n", - __func__)); - return key_senderror(so, m, EINVAL); - } - - type = (struct sadb_x_nat_t_type *) - mhp->ext[SADB_X_EXT_NAT_T_TYPE]; - sport = (struct sadb_x_nat_t_port *) - mhp->ext[SADB_X_EXT_NAT_T_SPORT]; - dport = (struct sadb_x_nat_t_port *) - mhp->ext[SADB_X_EXT_NAT_T_DPORT]; - - if (sport) - KEY_PORTTOSADDR(&saidx.src, - sport->sadb_x_nat_t_port_port); - if (dport) - KEY_PORTTOSADDR(&saidx.dst, - dport->sadb_x_nat_t_port_port); - } else { - type = 0; + if (sa0->sadb_sa_state != SADB_SASTATE_MATURE) { + ipseclog((LOG_DEBUG, "%s: invalid state.\n", __func__)); +#ifdef PFKEY_STRICT_CHECKS + return key_senderror(so, m, EINVAL); +#endif } - if (mhp->ext[SADB_X_EXT_NAT_T_OAI] != NULL && - mhp->ext[SADB_X_EXT_NAT_T_OAR] != NULL) { - if (mhp->extlen[SADB_X_EXT_NAT_T_OAI] < sizeof(*iaddr) || - mhp->extlen[SADB_X_EXT_NAT_T_OAR] < sizeof(*raddr)) { - ipseclog((LOG_DEBUG, "%s: invalid message\n", - __func__)); - return key_senderror(so, m, EINVAL); - } - iaddr = (struct sadb_address *)mhp->ext[SADB_X_EXT_NAT_T_OAI]; - raddr = (struct sadb_address *)mhp->ext[SADB_X_EXT_NAT_T_OAR]; - ipseclog((LOG_DEBUG, "%s: NAT-T OAi/r present\n", __func__)); - } else { - iaddr = raddr = NULL; + error = key_checksockaddrs((struct sockaddr *)(src0 + 1), + (struct sockaddr *)(dst0 + 1)); + if (error != 0) { + ipseclog((LOG_DEBUG, "%s: invalid sockaddr.\n", __func__)); + return key_senderror(so, m, error); } - if (mhp->ext[SADB_X_EXT_NAT_T_FRAG] != NULL) { - if (mhp->extlen[SADB_X_EXT_NAT_T_FRAG] < sizeof(*frag)) { - ipseclog((LOG_DEBUG, "%s: invalid message\n", + KEY_SETSECASIDX(proto, mode, reqid, src0 + 1, dst0 + 1, &saidx); + spi = sa0->sadb_sa_spi; + /* + * For TCP-MD5 SAs we don't use SPI. Check the uniqueness using + * secasindex. + * XXXAE: IPComp seems also doesn't use SPI. + */ + if (proto == IPPROTO_TCP) { + sav = key_getsav_tcpmd5(&saidx, &spi); + if (sav == NULL && spi == 0) { + /* Failed to allocate SPI */ + ipseclog((LOG_DEBUG, "%s: SA already exists.\n", __func__)); - return key_senderror(so, m, EINVAL); + return key_senderror(so, m, EEXIST); } - frag = (struct sadb_x_nat_t_frag *) - mhp->ext[SADB_X_EXT_NAT_T_FRAG]; + /* XXX: SPI that we report back can have another value */ } else { - frag = 0; + /* We can create new SA only if SPI is different. */ + sav = key_getsavbyspi(spi); } -#endif - - /* get a SA header */ - if ((newsah = key_getsah(&saidx)) == NULL) { - /* create a new SA header */ - if ((newsah = key_newsah(&saidx)) == NULL) { - ipseclog((LOG_DEBUG, "%s: No more memory.\n",__func__)); - return key_senderror(so, m, ENOBUFS); - } - } - - /* set spidx if there */ - /* XXX rewrite */ - error = key_setident(newsah, m, mhp); - if (error) { - return key_senderror(so, m, error); - } - - /* create new SA entry. */ - /* We can create new SA only if SPI is differenct. */ - SAHTREE_LOCK(); - newsav = key_getsavbyspi(newsah, sa0->sadb_sa_spi); - SAHTREE_UNLOCK(); - if (newsav != NULL) { + if (sav != NULL) { + key_freesav(&sav); ipseclog((LOG_DEBUG, "%s: SA already exists.\n", __func__)); return key_senderror(so, m, EEXIST); } - newsav = KEY_NEWSAV(m, mhp, newsah, &error); - if (newsav == NULL) { - return key_senderror(so, m, error); - } -#ifdef IPSEC_NAT_T - /* - * Handle more NAT-T info if present, - * now that we have a sav to fill. - */ - if (type) - newsav->natt_type = type->sadb_x_nat_t_type_type; - -#if 0 - /* - * In case SADB_X_EXT_NAT_T_FRAG was not given, leave it at 0. - * We should actually check for a minimum MTU here, if we - * want to support it in ip_output. - */ - if (frag) - newsav->natt_esp_frag_len = frag->sadb_x_nat_t_frag_fraglen; -#endif -#endif - - /* check SA values to be mature. */ - if ((error = key_mature(newsav)) != 0) { - KEY_FREESAV(&newsav); + sav = key_newsav(mhp, &saidx, spi, &error); + if (sav == NULL) return key_senderror(so, m, error); - } - + KEYDBG(KEY_STAMP, + printf("%s: return SA(%p)\n", __func__, sav)); + KEYDBG(KEY_DATA, kdebug_secasv(sav)); /* - * don't call key_freesav() here, as we would like to keep the SA - * in the database on success. + * If SADB_ADD was in response to SADB_ACQUIRE, we need to schedule + * ACQ for deletion. */ + if (sav->seq != 0) + key_acqdone(&saidx, sav->seq); { + /* + * Don't call key_freesav() on error here, as we would like to + * keep the SA in the database. + */ struct mbuf *n; /* set msg buf from mhp */ @@ -5468,33 +5551,199 @@ key_add(so, m, mhp) } } -/* m is retained */ +/* + * NAT-T support. + * IKEd may request the use ESP in UDP encapsulation when it detects the + * presence of NAT. It uses NAT-T extension headers for such SAs to specify + * parameters needed for encapsulation and decapsulation. These PF_KEY + * extension headers are not standardized, so this comment addresses our + * implementation. + * SADB_X_EXT_NAT_T_TYPE specifies type of encapsulation, we support only + * UDP_ENCAP_ESPINUDP as described in RFC3948. + * SADB_X_EXT_NAT_T_SPORT/DPORT specifies source and destination ports for + * UDP header. We use these ports in UDP encapsulation procedure, also we + * can check them in UDP decapsulation procedure. + * SADB_X_EXT_NAT_T_OA[IR] specifies original address of initiator or + * responder. These addresses can be used for transport mode to adjust + * checksum after decapsulation and decryption. Since original IP addresses + * used by peer usually different (we detected presence of NAT), TCP/UDP + * pseudo header checksum and IP header checksum was calculated using original + * addresses. After decapsulation and decryption we need to adjust checksum + * to have correct datagram. + * + * We expect presence of NAT-T extension headers only in SADB_ADD and + * SADB_UPDATE messages. We report NAT-T extension headers in replies + * to SADB_ADD, SADB_UPDATE, SADB_GET, and SADB_DUMP messages. + */ static int -key_setident(sah, m, mhp) - struct secashead *sah; - struct mbuf *m; - const struct sadb_msghdr *mhp; +key_setnatt(struct secasvar *sav, const struct sadb_msghdr *mhp) +{ + struct sadb_x_nat_t_port *port; + struct sadb_x_nat_t_type *type; + struct sadb_address *oai, *oar; + struct sockaddr *sa; + uint32_t addr; + uint16_t cksum; + + IPSEC_ASSERT(sav->natt == NULL, ("natt is already initialized")); + /* + * Ignore NAT-T headers if sproto isn't ESP. + */ + if (sav->sah->saidx.proto != IPPROTO_ESP) + return (0); + + if (!SADB_CHECKHDR(mhp, SADB_X_EXT_NAT_T_TYPE) && + !SADB_CHECKHDR(mhp, SADB_X_EXT_NAT_T_SPORT) && + !SADB_CHECKHDR(mhp, SADB_X_EXT_NAT_T_DPORT)) { + if (SADB_CHECKLEN(mhp, SADB_X_EXT_NAT_T_TYPE) || + SADB_CHECKLEN(mhp, SADB_X_EXT_NAT_T_SPORT) || + SADB_CHECKLEN(mhp, SADB_X_EXT_NAT_T_DPORT)) { + ipseclog((LOG_DEBUG, + "%s: invalid message: wrong header size.\n", + __func__)); + return (EINVAL); + } + } else + return (0); + + type = (struct sadb_x_nat_t_type *)mhp->ext[SADB_X_EXT_NAT_T_TYPE]; + if (type->sadb_x_nat_t_type_type != UDP_ENCAP_ESPINUDP) { + ipseclog((LOG_DEBUG, "%s: unsupported NAT-T type %u.\n", + __func__, type->sadb_x_nat_t_type_type)); + return (EINVAL); + } + /* + * Allocate storage for NAT-T config. + * On error it will be released by key_cleansav(). + */ + sav->natt = malloc(sizeof(struct secnatt), M_IPSEC_MISC, + M_NOWAIT | M_ZERO); + if (sav->natt == NULL) { + PFKEYSTAT_INC(in_nomem); + ipseclog((LOG_DEBUG, "%s: No more memory.\n", __func__)); + return (ENOBUFS); + } + port = (struct sadb_x_nat_t_port *)mhp->ext[SADB_X_EXT_NAT_T_SPORT]; + if (port->sadb_x_nat_t_port_port == 0) { + ipseclog((LOG_DEBUG, "%s: invalid NAT-T sport specified.\n", + __func__)); + return (EINVAL); + } + sav->natt->sport = port->sadb_x_nat_t_port_port; + port = (struct sadb_x_nat_t_port *)mhp->ext[SADB_X_EXT_NAT_T_DPORT]; + if (port->sadb_x_nat_t_port_port == 0) { + ipseclog((LOG_DEBUG, "%s: invalid NAT-T dport specified.\n", + __func__)); + return (EINVAL); + } + sav->natt->dport = port->sadb_x_nat_t_port_port; + + /* + * SADB_X_EXT_NAT_T_OAI and SADB_X_EXT_NAT_T_OAR are optional + * and needed only for transport mode IPsec. + * Usually NAT translates only one address, but it is possible, + * that both addresses could be translated. + * NOTE: Value of SADB_X_EXT_NAT_T_OAI is equal to SADB_X_EXT_NAT_T_OA. + */ + if (!SADB_CHECKHDR(mhp, SADB_X_EXT_NAT_T_OAI)) { + if (SADB_CHECKLEN(mhp, SADB_X_EXT_NAT_T_OAI)) { + ipseclog((LOG_DEBUG, + "%s: invalid message: wrong header size.\n", + __func__)); + return (EINVAL); + } + oai = (struct sadb_address *)mhp->ext[SADB_X_EXT_NAT_T_OAI]; + } else + oai = NULL; + if (!SADB_CHECKHDR(mhp, SADB_X_EXT_NAT_T_OAR)) { + if (SADB_CHECKLEN(mhp, SADB_X_EXT_NAT_T_OAR)) { + ipseclog((LOG_DEBUG, + "%s: invalid message: wrong header size.\n", + __func__)); + return (EINVAL); + } + oar = (struct sadb_address *)mhp->ext[SADB_X_EXT_NAT_T_OAR]; + } else + oar = NULL; + + /* Initialize addresses only for transport mode */ + if (sav->sah->saidx.mode != IPSEC_MODE_TUNNEL) { + cksum = 0; + if (oai != NULL) { + /* Currently we support only AF_INET */ + sa = (struct sockaddr *)(oai + 1); + if (sa->sa_family != AF_INET || + sa->sa_len != sizeof(struct sockaddr_in)) { + ipseclog((LOG_DEBUG, + "%s: wrong NAT-OAi header.\n", + __func__)); + return (EINVAL); + } + /* Ignore address if it the same */ + if (((struct sockaddr_in *)sa)->sin_addr.s_addr != + sav->sah->saidx.src.sin.sin_addr.s_addr) { + bcopy(sa, &sav->natt->oai.sa, sa->sa_len); + sav->natt->flags |= IPSEC_NATT_F_OAI; + /* Calculate checksum delta */ + addr = sav->sah->saidx.src.sin.sin_addr.s_addr; + cksum = in_addword(cksum, ~addr >> 16); + cksum = in_addword(cksum, ~addr & 0xffff); + addr = sav->natt->oai.sin.sin_addr.s_addr; + cksum = in_addword(cksum, addr >> 16); + cksum = in_addword(cksum, addr & 0xffff); + } + } + if (oar != NULL) { + /* Currently we support only AF_INET */ + sa = (struct sockaddr *)(oar + 1); + if (sa->sa_family != AF_INET || + sa->sa_len != sizeof(struct sockaddr_in)) { + ipseclog((LOG_DEBUG, + "%s: wrong NAT-OAr header.\n", + __func__)); + return (EINVAL); + } + /* Ignore address if it the same */ + if (((struct sockaddr_in *)sa)->sin_addr.s_addr != + sav->sah->saidx.dst.sin.sin_addr.s_addr) { + bcopy(sa, &sav->natt->oar.sa, sa->sa_len); + sav->natt->flags |= IPSEC_NATT_F_OAR; + /* Calculate checksum delta */ + addr = sav->sah->saidx.dst.sin.sin_addr.s_addr; + cksum = in_addword(cksum, ~addr >> 16); + cksum = in_addword(cksum, ~addr & 0xffff); + addr = sav->natt->oar.sin.sin_addr.s_addr; + cksum = in_addword(cksum, addr >> 16); + cksum = in_addword(cksum, addr & 0xffff); + } + } + sav->natt->cksum = cksum; + } + return (0); +} + +static int +key_setident(struct secashead *sah, const struct sadb_msghdr *mhp) { const struct sadb_ident *idsrc, *iddst; int idsrclen, iddstlen; IPSEC_ASSERT(sah != NULL, ("null secashead")); - IPSEC_ASSERT(m != NULL, ("null mbuf")); IPSEC_ASSERT(mhp != NULL, ("null msghdr")); IPSEC_ASSERT(mhp->msg != NULL, ("null msg")); /* don't make buffer if not there */ - if (mhp->ext[SADB_EXT_IDENTITY_SRC] == NULL && - mhp->ext[SADB_EXT_IDENTITY_DST] == NULL) { + if (SADB_CHECKHDR(mhp, SADB_EXT_IDENTITY_SRC) && + SADB_CHECKHDR(mhp, SADB_EXT_IDENTITY_DST)) { sah->idents = NULL; sah->identd = NULL; - return 0; + return (0); } - - if (mhp->ext[SADB_EXT_IDENTITY_SRC] == NULL || - mhp->ext[SADB_EXT_IDENTITY_DST] == NULL) { + + if (SADB_CHECKHDR(mhp, SADB_EXT_IDENTITY_SRC) || + SADB_CHECKHDR(mhp, SADB_EXT_IDENTITY_DST)) { ipseclog((LOG_DEBUG, "%s: invalid identity.\n", __func__)); - return EINVAL; + return (EINVAL); } idsrc = (const struct sadb_ident *)mhp->ext[SADB_EXT_IDENTITY_SRC]; @@ -5543,12 +5792,13 @@ key_setident(sah, m, mhp) /* * m will not be freed on return. - * it is caller's responsibility to free the result. + * it is caller's responsibility to free the result. + * + * Called from SADB_ADD and SADB_UPDATE. Reply will contain headers + * from the request in defined order. */ static struct mbuf * -key_getmsgbuf_x1(m, mhp) - struct mbuf *m; - const struct sadb_msghdr *mhp; +key_getmsgbuf_x1(struct mbuf *m, const struct sadb_msghdr *mhp) { struct mbuf *n; @@ -5557,11 +5807,15 @@ key_getmsgbuf_x1(m, mhp) IPSEC_ASSERT(mhp->msg != NULL, ("null msg")); /* create new sadb_msg to reply. */ - n = key_gather_mbuf(m, mhp, 1, 9, SADB_EXT_RESERVED, + n = key_gather_mbuf(m, mhp, 1, 16, SADB_EXT_RESERVED, SADB_EXT_SA, SADB_X_EXT_SA2, SADB_EXT_ADDRESS_SRC, SADB_EXT_ADDRESS_DST, SADB_EXT_LIFETIME_HARD, SADB_EXT_LIFETIME_SOFT, - SADB_EXT_IDENTITY_SRC, SADB_EXT_IDENTITY_DST); + SADB_EXT_IDENTITY_SRC, SADB_EXT_IDENTITY_DST, + SADB_X_EXT_NAT_T_TYPE, SADB_X_EXT_NAT_T_SPORT, + SADB_X_EXT_NAT_T_DPORT, SADB_X_EXT_NAT_T_OAI, + SADB_X_EXT_NAT_T_OAR, SADB_X_EXT_NEW_ADDRESS_SRC, + SADB_X_EXT_NEW_ADDRESS_DST); if (!n) return NULL; @@ -5577,9 +5831,6 @@ key_getmsgbuf_x1(m, mhp) return n; } -static int key_delete_all __P((struct socket *, struct mbuf *, - const struct sadb_msghdr *, u_int16_t)); - /* * SADB_DELETE processing * receive @@ -5592,17 +5843,13 @@ static int key_delete_all __P((struct socket *, struct mbuf *, * m will always be freed. */ static int -key_delete(so, m, mhp) - struct socket *so; - struct mbuf *m; - const struct sadb_msghdr *mhp; +key_delete(struct socket *so, struct mbuf *m, const struct sadb_msghdr *mhp) { - struct sadb_sa *sa0; - struct sadb_address *src0, *dst0; struct secasindex saidx; - struct secashead *sah; - struct secasvar *sav = NULL; - u_int16_t proto; + struct sadb_address *src0, *dst0; + struct secasvar *sav; + struct sadb_sa *sa0; + uint8_t proto; IPSEC_ASSERT(so != NULL, ("null socket")); IPSEC_ASSERT(m != NULL, ("null mbuf")); @@ -5612,110 +5859,70 @@ key_delete(so, m, mhp) /* map satype to proto */ if ((proto = key_satype2proto(mhp->msg->sadb_msg_satype)) == 0) { ipseclog((LOG_DEBUG, "%s: invalid satype is passed.\n", - __func__)); + __func__)); return key_senderror(so, m, EINVAL); } - if (mhp->ext[SADB_EXT_ADDRESS_SRC] == NULL || - mhp->ext[SADB_EXT_ADDRESS_DST] == NULL) { + if (SADB_CHECKHDR(mhp, SADB_EXT_ADDRESS_SRC) || + SADB_CHECKHDR(mhp, SADB_EXT_ADDRESS_DST) || + SADB_CHECKLEN(mhp, SADB_EXT_ADDRESS_SRC) || + SADB_CHECKLEN(mhp, SADB_EXT_ADDRESS_DST)) { ipseclog((LOG_DEBUG, "%s: invalid message is passed.\n", - __func__)); + __func__)); return key_senderror(so, m, EINVAL); } - if (mhp->extlen[SADB_EXT_ADDRESS_SRC] < sizeof(struct sadb_address) || - mhp->extlen[SADB_EXT_ADDRESS_DST] < sizeof(struct sadb_address)) { - ipseclog((LOG_DEBUG, "%s: invalid message is passed.\n", - __func__)); - return key_senderror(so, m, EINVAL); - } + src0 = (struct sadb_address *)(mhp->ext[SADB_EXT_ADDRESS_SRC]); + dst0 = (struct sadb_address *)(mhp->ext[SADB_EXT_ADDRESS_DST]); - if (mhp->ext[SADB_EXT_SA] == NULL) { + if (key_checksockaddrs((struct sockaddr *)(src0 + 1), + (struct sockaddr *)(dst0 + 1)) != 0) { + ipseclog((LOG_DEBUG, "%s: invalid sockaddr.\n", __func__)); + return (key_senderror(so, m, EINVAL)); + } + KEY_SETSECASIDX(proto, IPSEC_MODE_ANY, 0, src0 + 1, dst0 + 1, &saidx); + if (SADB_CHECKHDR(mhp, SADB_EXT_SA)) { /* * Caller wants us to delete all non-LARVAL SAs * that match the src/dst. This is used during * IKE INITIAL-CONTACT. + * XXXAE: this looks like some extension to RFC2367. */ ipseclog((LOG_DEBUG, "%s: doing delete all.\n", __func__)); - return key_delete_all(so, m, mhp, proto); - } else if (mhp->extlen[SADB_EXT_SA] < sizeof(struct sadb_sa)) { - ipseclog((LOG_DEBUG, "%s: invalid message is passed.\n", - __func__)); - return key_senderror(so, m, EINVAL); + return (key_delete_all(so, m, mhp, &saidx)); } - - sa0 = (struct sadb_sa *)mhp->ext[SADB_EXT_SA]; - src0 = (struct sadb_address *)(mhp->ext[SADB_EXT_ADDRESS_SRC]); - dst0 = (struct sadb_address *)(mhp->ext[SADB_EXT_ADDRESS_DST]); - - /* XXX boundary check against sa_len */ - KEY_SETSECASIDX(proto, IPSEC_MODE_ANY, 0, src0 + 1, dst0 + 1, &saidx); - - /* - * Make sure the port numbers are zero. - * In case of NAT-T we will update them later if needed. - */ - KEY_PORTTOSADDR(&saidx.src, 0); - KEY_PORTTOSADDR(&saidx.dst, 0); - -#ifdef IPSEC_NAT_T - /* - * Handle NAT-T info if present. - */ - if (mhp->ext[SADB_X_EXT_NAT_T_SPORT] != NULL && - mhp->ext[SADB_X_EXT_NAT_T_DPORT] != NULL) { - struct sadb_x_nat_t_port *sport, *dport; - - if (mhp->extlen[SADB_X_EXT_NAT_T_SPORT] < sizeof(*sport) || - mhp->extlen[SADB_X_EXT_NAT_T_DPORT] < sizeof(*dport)) { - ipseclog((LOG_DEBUG, "%s: invalid message.\n", - __func__)); - return key_senderror(so, m, EINVAL); - } - - sport = (struct sadb_x_nat_t_port *) - mhp->ext[SADB_X_EXT_NAT_T_SPORT]; - dport = (struct sadb_x_nat_t_port *) - mhp->ext[SADB_X_EXT_NAT_T_DPORT]; - - if (sport) - KEY_PORTTOSADDR(&saidx.src, - sport->sadb_x_nat_t_port_port); - if (dport) - KEY_PORTTOSADDR(&saidx.dst, - dport->sadb_x_nat_t_port_port); - } -#endif - - /* get a SA header */ - SAHTREE_LOCK(); - LIST_FOREACH(sah, &V_sahtree, chain) { - if (sah->state == SADB_SASTATE_DEAD) - continue; - if (key_cmpsaidx(&sah->saidx, &saidx, CMP_HEAD) == 0) - continue; - - /* get a SA with SPI. */ - sav = key_getsavbyspi(sah, sa0->sadb_sa_spi); - if (sav) - break; - } - if (sah == NULL) { - SAHTREE_UNLOCK(); - ipseclog((LOG_DEBUG, "%s: no SA found.\n", __func__)); - return key_senderror(so, m, ENOENT); + if (SADB_CHECKLEN(mhp, SADB_EXT_SA)) { + ipseclog((LOG_DEBUG, + "%s: invalid message: wrong header size.\n", __func__)); + return (key_senderror(so, m, EINVAL)); } - - key_sa_chgstate(sav, SADB_SASTATE_DEAD); - KEY_FREESAV(&sav); - SAHTREE_UNLOCK(); + sa0 = (struct sadb_sa *)mhp->ext[SADB_EXT_SA]; + if (proto == IPPROTO_TCP) + sav = key_getsav_tcpmd5(&saidx, NULL); + else + sav = key_getsavbyspi(sa0->sadb_sa_spi); + if (sav == NULL) { + ipseclog((LOG_DEBUG, "%s: no SA found for SPI %u.\n", + __func__, ntohl(sa0->sadb_sa_spi))); + return (key_senderror(so, m, ESRCH)); + } + if (key_cmpsaidx(&sav->sah->saidx, &saidx, CMP_HEAD) == 0) { + ipseclog((LOG_DEBUG, "%s: saidx mismatched for SPI %u.\n", + __func__, ntohl(sav->spi))); + key_freesav(&sav); + return (key_senderror(so, m, ESRCH)); + } + KEYDBG(KEY_STAMP, + printf("%s: SA(%p)\n", __func__, sav)); + KEYDBG(KEY_DATA, kdebug_secasv(sav)); + key_unlinksav(sav); + key_freesav(&sav); { struct mbuf *n; struct sadb_msg *newmsg; /* create new sadb_msg to reply. */ - /* XXX-BZ NAT-T extensions? */ n = key_gather_mbuf(m, mhp, 1, 4, SADB_EXT_RESERVED, SADB_EXT_SA, SADB_EXT_ADDRESS_SRC, SADB_EXT_ADDRESS_DST); if (!n) @@ -5739,95 +5946,44 @@ key_delete(so, m, mhp) * delete all SAs for src/dst. Called from key_delete(). */ static int -key_delete_all(struct socket *so, struct mbuf *m, const struct sadb_msghdr *mhp, - u_int16_t proto) +key_delete_all(struct socket *so, struct mbuf *m, + const struct sadb_msghdr *mhp, struct secasindex *saidx) { - struct sadb_address *src0, *dst0; - struct secasindex saidx; + struct secasvar_queue drainq; struct secashead *sah; struct secasvar *sav, *nextsav; - u_int stateidx, state; - - src0 = (struct sadb_address *)(mhp->ext[SADB_EXT_ADDRESS_SRC]); - dst0 = (struct sadb_address *)(mhp->ext[SADB_EXT_ADDRESS_DST]); - - /* XXX boundary check against sa_len */ - KEY_SETSECASIDX(proto, IPSEC_MODE_ANY, 0, src0 + 1, dst0 + 1, &saidx); - /* - * Make sure the port numbers are zero. - * In case of NAT-T we will update them later if needed. - */ - KEY_PORTTOSADDR(&saidx.src, 0); - KEY_PORTTOSADDR(&saidx.dst, 0); - -#ifdef IPSEC_NAT_T - /* - * Handle NAT-T info if present. - */ - - if (mhp->ext[SADB_X_EXT_NAT_T_SPORT] != NULL && - mhp->ext[SADB_X_EXT_NAT_T_DPORT] != NULL) { - struct sadb_x_nat_t_port *sport, *dport; - - if (mhp->extlen[SADB_X_EXT_NAT_T_SPORT] < sizeof(*sport) || - mhp->extlen[SADB_X_EXT_NAT_T_DPORT] < sizeof(*dport)) { - ipseclog((LOG_DEBUG, "%s: invalid message.\n", - __func__)); - return key_senderror(so, m, EINVAL); - } - - sport = (struct sadb_x_nat_t_port *) - mhp->ext[SADB_X_EXT_NAT_T_SPORT]; - dport = (struct sadb_x_nat_t_port *) - mhp->ext[SADB_X_EXT_NAT_T_DPORT]; - - if (sport) - KEY_PORTTOSADDR(&saidx.src, - sport->sadb_x_nat_t_port_port); - if (dport) - KEY_PORTTOSADDR(&saidx.dst, - dport->sadb_x_nat_t_port_port); - } -#endif - - SAHTREE_LOCK(); - LIST_FOREACH(sah, &V_sahtree, chain) { - if (sah->state == SADB_SASTATE_DEAD) - continue; - if (key_cmpsaidx(&sah->saidx, &saidx, CMP_HEAD) == 0) + TAILQ_INIT(&drainq); + SAHTREE_WLOCK(); + LIST_FOREACH(sah, SAHADDRHASH_HASH(saidx), addrhash) { + if (key_cmpsaidx(&sah->saidx, saidx, CMP_HEAD) == 0) continue; - - /* Delete all non-LARVAL SAs. */ - for (stateidx = 0; - stateidx < _ARRAYLEN(saorder_state_alive); - stateidx++) { - state = saorder_state_alive[stateidx]; - if (state == SADB_SASTATE_LARVAL) - continue; - for (sav = LIST_FIRST(&sah->savtree[state]); - sav != NULL; sav = nextsav) { - nextsav = LIST_NEXT(sav, chain); - /* sanity check */ - if (sav->state != state) { - ipseclog((LOG_DEBUG, "%s: invalid " - "sav->state (queue %d SA %d)\n", - __func__, state, sav->state)); - continue; - } - - key_sa_chgstate(sav, SADB_SASTATE_DEAD); - KEY_FREESAV(&sav); - } - } + /* Move all ALIVE SAs into drainq */ + TAILQ_CONCAT(&drainq, &sah->savtree_alive, chain); + } + /* Unlink all queued SAs from SPI hash */ + TAILQ_FOREACH(sav, &drainq, chain) { + sav->state = SADB_SASTATE_DEAD; + LIST_REMOVE(sav, spihash); + } + SAHTREE_WUNLOCK(); + /* Now we can release reference for all SAs in drainq */ + sav = TAILQ_FIRST(&drainq); + while (sav != NULL) { + KEYDBG(KEY_STAMP, + printf("%s: SA(%p)\n", __func__, sav)); + KEYDBG(KEY_DATA, kdebug_secasv(sav)); + nextsav = TAILQ_NEXT(sav, chain); + key_freesah(&sav->sah); /* release reference from SAV */ + key_freesav(&sav); /* release last reference */ + sav = nextsav; } - SAHTREE_UNLOCK(); + { struct mbuf *n; struct sadb_msg *newmsg; /* create new sadb_msg to reply. */ - /* XXX-BZ NAT-T extensions? */ n = key_gather_mbuf(m, mhp, 1, 3, SADB_EXT_RESERVED, SADB_EXT_ADDRESS_SRC, SADB_EXT_ADDRESS_DST); if (!n) @@ -5848,6 +6004,52 @@ key_delete_all(struct socket *so, struct mbuf *m, const struct sadb_msghdr *mhp, } /* + * Delete all alive SAs for corresponding xform. + * Larval SAs have not initialized tdb_xform, so it is safe to leave them + * here when xform disappears. + */ +static void +key_delete_xform(const struct xformsw *xsp) +{ + struct secasvar_queue drainq; + struct secashead *sah; + struct secasvar *sav, *nextsav; + + TAILQ_INIT(&drainq); + SAHTREE_WLOCK(); + TAILQ_FOREACH(sah, &V_sahtree, chain) { + sav = TAILQ_FIRST(&sah->savtree_alive); + if (sav == NULL) + continue; + if (sav->tdb_xform != xsp) + continue; + /* + * It is supposed that all SAs in the chain are related to + * one xform. + */ + TAILQ_CONCAT(&drainq, &sah->savtree_alive, chain); + } + /* Unlink all queued SAs from SPI hash */ + TAILQ_FOREACH(sav, &drainq, chain) { + sav->state = SADB_SASTATE_DEAD; + LIST_REMOVE(sav, spihash); + } + SAHTREE_WUNLOCK(); + + /* Now we can release reference for all SAs in drainq */ + sav = TAILQ_FIRST(&drainq); + while (sav != NULL) { + KEYDBG(KEY_STAMP, + printf("%s: SA(%p)\n", __func__, sav)); + KEYDBG(KEY_DATA, kdebug_secasv(sav)); + nextsav = TAILQ_NEXT(sav, chain); + key_freesah(&sav->sah); /* release reference from SAV */ + key_freesav(&sav); /* release last reference */ + sav = nextsav; + } +} + +/* * SADB_GET processing * receive * <base, SA(*), address(SD)> @@ -5860,17 +6062,13 @@ key_delete_all(struct socket *so, struct mbuf *m, const struct sadb_msghdr *mhp, * m will always be freed. */ static int -key_get(so, m, mhp) - struct socket *so; - struct mbuf *m; - const struct sadb_msghdr *mhp; +key_get(struct socket *so, struct mbuf *m, const struct sadb_msghdr *mhp) { - struct sadb_sa *sa0; - struct sadb_address *src0, *dst0; struct secasindex saidx; - struct secashead *sah; - struct secasvar *sav = NULL; - u_int16_t proto; + struct sadb_address *src0, *dst0; + struct sadb_sa *sa0; + struct secasvar *sav; + uint8_t proto; IPSEC_ASSERT(so != NULL, ("null socket")); IPSEC_ASSERT(m != NULL, ("null mbuf")); @@ -5884,18 +6082,19 @@ key_get(so, m, mhp) return key_senderror(so, m, EINVAL); } - if (mhp->ext[SADB_EXT_SA] == NULL || - mhp->ext[SADB_EXT_ADDRESS_SRC] == NULL || - mhp->ext[SADB_EXT_ADDRESS_DST] == NULL) { - ipseclog((LOG_DEBUG, "%s: invalid message is passed.\n", - __func__)); + if (SADB_CHECKHDR(mhp, SADB_EXT_SA) || + SADB_CHECKHDR(mhp, SADB_EXT_ADDRESS_SRC) || + SADB_CHECKHDR(mhp, SADB_EXT_ADDRESS_DST)) { + ipseclog((LOG_DEBUG, + "%s: invalid message: missing required header.\n", + __func__)); return key_senderror(so, m, EINVAL); } - if (mhp->extlen[SADB_EXT_SA] < sizeof(struct sadb_sa) || - mhp->extlen[SADB_EXT_ADDRESS_SRC] < sizeof(struct sadb_address) || - mhp->extlen[SADB_EXT_ADDRESS_DST] < sizeof(struct sadb_address)) { - ipseclog((LOG_DEBUG, "%s: invalid message is passed.\n", - __func__)); + if (SADB_CHECKLEN(mhp, SADB_EXT_SA) || + SADB_CHECKLEN(mhp, SADB_EXT_ADDRESS_SRC) || + SADB_CHECKLEN(mhp, SADB_EXT_ADDRESS_DST)) { + ipseclog((LOG_DEBUG, + "%s: invalid message: wrong header size.\n", __func__)); return key_senderror(so, m, EINVAL); } @@ -5903,79 +6102,45 @@ key_get(so, m, mhp) src0 = (struct sadb_address *)mhp->ext[SADB_EXT_ADDRESS_SRC]; dst0 = (struct sadb_address *)mhp->ext[SADB_EXT_ADDRESS_DST]; - /* XXX boundary check against sa_len */ - KEY_SETSECASIDX(proto, IPSEC_MODE_ANY, 0, src0 + 1, dst0 + 1, &saidx); - - /* - * Make sure the port numbers are zero. - * In case of NAT-T we will update them later if needed. - */ - KEY_PORTTOSADDR(&saidx.src, 0); - KEY_PORTTOSADDR(&saidx.dst, 0); - -#ifdef IPSEC_NAT_T - /* - * Handle NAT-T info if present. - */ - - if (mhp->ext[SADB_X_EXT_NAT_T_SPORT] != NULL && - mhp->ext[SADB_X_EXT_NAT_T_DPORT] != NULL) { - struct sadb_x_nat_t_port *sport, *dport; - - if (mhp->extlen[SADB_X_EXT_NAT_T_SPORT] < sizeof(*sport) || - mhp->extlen[SADB_X_EXT_NAT_T_DPORT] < sizeof(*dport)) { - ipseclog((LOG_DEBUG, "%s: invalid message.\n", - __func__)); - return key_senderror(so, m, EINVAL); - } - - sport = (struct sadb_x_nat_t_port *) - mhp->ext[SADB_X_EXT_NAT_T_SPORT]; - dport = (struct sadb_x_nat_t_port *) - mhp->ext[SADB_X_EXT_NAT_T_DPORT]; - - if (sport) - KEY_PORTTOSADDR(&saidx.src, - sport->sadb_x_nat_t_port_port); - if (dport) - KEY_PORTTOSADDR(&saidx.dst, - dport->sadb_x_nat_t_port_port); + if (key_checksockaddrs((struct sockaddr *)(src0 + 1), + (struct sockaddr *)(dst0 + 1)) != 0) { + ipseclog((LOG_DEBUG, "%s: invalid sockaddr.\n", __func__)); + return key_senderror(so, m, EINVAL); } -#endif - - /* get a SA header */ - SAHTREE_LOCK(); - LIST_FOREACH(sah, &V_sahtree, chain) { - if (sah->state == SADB_SASTATE_DEAD) - continue; - if (key_cmpsaidx(&sah->saidx, &saidx, CMP_HEAD) == 0) - continue; + KEY_SETSECASIDX(proto, IPSEC_MODE_ANY, 0, src0 + 1, dst0 + 1, &saidx); - /* get a SA with SPI. */ - sav = key_getsavbyspi(sah, sa0->sadb_sa_spi); - if (sav) - break; - } - SAHTREE_UNLOCK(); - if (sah == NULL) { + if (proto == IPPROTO_TCP) + sav = key_getsav_tcpmd5(&saidx, NULL); + else + sav = key_getsavbyspi(sa0->sadb_sa_spi); + if (sav == NULL) { ipseclog((LOG_DEBUG, "%s: no SA found.\n", __func__)); - return key_senderror(so, m, ENOENT); + return key_senderror(so, m, ESRCH); + } + if (key_cmpsaidx(&sav->sah->saidx, &saidx, CMP_HEAD) == 0) { + ipseclog((LOG_DEBUG, "%s: saidx mismatched for SPI %u.\n", + __func__, ntohl(sa0->sadb_sa_spi))); + key_freesav(&sav); + return (key_senderror(so, m, ESRCH)); } { struct mbuf *n; - u_int8_t satype; + uint8_t satype; /* map proto to satype */ - if ((satype = key_proto2satype(sah->saidx.proto)) == 0) { + if ((satype = key_proto2satype(sav->sah->saidx.proto)) == 0) { ipseclog((LOG_DEBUG, "%s: there was invalid proto in SAD.\n", - __func__)); + __func__)); + key_freesav(&sav); return key_senderror(so, m, EINVAL); } /* create new sadb_msg to reply. */ n = key_setdumpsa(sav, SADB_GET, satype, mhp->msg->sadb_msg_seq, mhp->msg->sadb_msg_pid); + + key_freesav(&sav); if (!n) return key_senderror(so, m, ENOBUFS); @@ -5986,8 +6151,7 @@ key_get(so, m, mhp) /* XXX make it sysctl-configurable? */ static void -key_getcomb_setlifetime(comb) - struct sadb_comb *comb; +key_getcomb_setlifetime(struct sadb_comb *comb) { comb->sadb_comb_soft_allocations = 1; @@ -6005,10 +6169,10 @@ key_getcomb_setlifetime(comb) * XXX no idea if the user wants ESP authentication or not */ static struct mbuf * -key_getcomb_esp() +key_getcomb_ealg(void) { struct sadb_comb *comb; - struct enc_xform *algo; + const struct enc_xform *algo; struct mbuf *result = NULL, *m, *n; int encmin; int i, off, o; @@ -6017,7 +6181,7 @@ key_getcomb_esp() m = NULL; for (i = 1; i <= SADB_EALG_MAX; i++) { - algo = esp_algorithm_lookup(i); + algo = enc_algorithm_lookup(i); if (algo == NULL) continue; @@ -6034,7 +6198,7 @@ key_getcomb_esp() else { IPSEC_ASSERT(l <= MLEN, ("l=%u > MLEN=%lu", l, (u_long) MLEN)); - MGET(m, M_DONTWAIT, MT_DATA); + MGET(m, M_NOWAIT, MT_DATA); if (m) { M_ALIGN(m, l); m->m_len = l; @@ -6079,11 +6243,8 @@ key_getcomb_esp() } static void -key_getsizes_ah( - const struct auth_hash *ah, - int alg, - u_int16_t* min, - u_int16_t* max) +key_getsizes_ah(const struct auth_hash *ah, int alg, u_int16_t* min, + u_int16_t* max) { *min = *max = ah->keysize; @@ -6113,8 +6274,8 @@ key_getsizes_ah( static struct mbuf * key_getcomb_ah() { + const struct auth_hash *algo; struct sadb_comb *comb; - struct auth_hash *algo; struct mbuf *m; u_int16_t minkeysize, maxkeysize; int i; @@ -6131,7 +6292,7 @@ key_getcomb_ah() i != SADB_X_AALG_SHA2_512) continue; #endif - algo = ah_algorithm_lookup(i); + algo = auth_algorithm_lookup(i); if (!algo) continue; key_getsizes_ah(algo, i, &minkeysize, &maxkeysize); @@ -6142,14 +6303,14 @@ key_getcomb_ah() if (!m) { IPSEC_ASSERT(l <= MLEN, ("l=%u > MLEN=%lu", l, (u_long) MLEN)); - MGET(m, M_DONTWAIT, MT_DATA); + MGET(m, M_NOWAIT, MT_DATA); if (m) { M_ALIGN(m, l); m->m_len = l; m->m_next = NULL; } } else - M_PREPEND(m, l, M_DONTWAIT); + M_PREPEND(m, l, M_NOWAIT); if (!m) return NULL; @@ -6171,29 +6332,29 @@ key_getcomb_ah() static struct mbuf * key_getcomb_ipcomp() { + const struct comp_algo *algo; struct sadb_comb *comb; - struct comp_algo *algo; struct mbuf *m; int i; const int l = PFKEY_ALIGN8(sizeof(struct sadb_comb)); m = NULL; for (i = 1; i <= SADB_X_CALG_MAX; i++) { - algo = ipcomp_algorithm_lookup(i); + algo = comp_algorithm_lookup(i); if (!algo) continue; if (!m) { IPSEC_ASSERT(l <= MLEN, ("l=%u > MLEN=%lu", l, (u_long) MLEN)); - MGET(m, M_DONTWAIT, MT_DATA); + MGET(m, M_NOWAIT, MT_DATA); if (m) { M_ALIGN(m, l); m->m_len = l; m->m_next = NULL; } } else - M_PREPEND(m, l, M_DONTWAIT); + M_PREPEND(m, l, M_NOWAIT); if (!m) return NULL; @@ -6213,8 +6374,7 @@ key_getcomb_ipcomp() * XXX sysctl interface to ipsec_{ah,esp}_keymin */ static struct mbuf * -key_getprop(saidx) - const struct secasindex *saidx; +key_getprop(const struct secasindex *saidx) { struct sadb_prop *prop; struct mbuf *m, *n; @@ -6223,7 +6383,7 @@ key_getprop(saidx) switch (saidx->proto) { case IPPROTO_ESP: - m = key_getcomb_esp(); + m = key_getcomb_ealg(); break; case IPPROTO_AH: m = key_getcomb_ah(); @@ -6237,7 +6397,7 @@ key_getprop(saidx) if (!m) return NULL; - M_PREPEND(m, l, M_DONTWAIT); + M_PREPEND(m, l, M_NOWAIT); if (!m) return NULL; @@ -6260,7 +6420,7 @@ key_getprop(saidx) * <base, SA, address(SD), (address(P)), x_policy, * (identity(SD),) (sensitivity,) proposal> * to KMD, and expect to receive - * <base> with SADB_ACQUIRE if error occured, + * <base> with SADB_ACQUIRE if error occurred, * or * <base, src address, dst address, (SPI range)> with SADB_GETSPI * from KMD by PF_KEY. @@ -6277,40 +6437,26 @@ key_getprop(saidx) static int key_acquire(const struct secasindex *saidx, struct secpolicy *sp) { - struct mbuf *result = NULL, *m; - struct secacq *newacq; - u_int8_t satype; - int error = -1; - u_int32_t seq; + union sockaddr_union addr; + struct mbuf *result, *m; + uint32_t seq; + int error; + uint16_t ul_proto; + uint8_t mask, satype; IPSEC_ASSERT(saidx != NULL, ("null saidx")); satype = key_proto2satype(saidx->proto); IPSEC_ASSERT(satype != 0, ("null satype, protocol %u", saidx->proto)); - /* - * We never do anything about acquirng SA. There is anather - * solution that kernel blocks to send SADB_ACQUIRE message until - * getting something message from IKEd. In later case, to be - * managed with ACQUIRING list. - */ - /* Get an entry to check whether sending message or not. */ - if ((newacq = key_getacq(saidx)) != NULL) { - if (V_key_blockacq_count < newacq->count) { - /* reset counter and do send message. */ - newacq->count = 0; - } else { - /* increment counter and do nothing. */ - newacq->count++; - return 0; - } - } else { - /* make new entry for blocking to send SADB_ACQUIRE. */ - if ((newacq = key_newacq(saidx)) == NULL) - return ENOBUFS; - } + error = -1; + result = NULL; + ul_proto = IPSEC_ULPROTO_ANY; + /* Get seq number to check whether sending message or not. */ + seq = key_getacq(saidx, &error); + if (seq == 0) + return (error); - seq = newacq->seq; m = key_setsadbmsg(SADB_ACQUIRE, 0, satype, seq, 0, 0); if (!m) { error = ENOBUFS; @@ -6319,21 +6465,69 @@ key_acquire(const struct secasindex *saidx, struct secpolicy *sp) result = m; /* - * No SADB_X_EXT_NAT_T_* here: we do not know - * anything related to NAT-T at this time. + * set sadb_address for saidx's. + * + * Note that if sp is supplied, then we're being called from + * key_allocsa_policy() and should supply port and protocol + * information. + * XXXAE: why only TCP and UDP? ICMP and SCTP looks applicable too. + * XXXAE: probably we can handle this in the ipsec[46]_allocsa(). + * XXXAE: it looks like we should save this info in the ACQ entry. */ - - /* set sadb_address for saidx's. */ - m = key_setsadbaddr(SADB_EXT_ADDRESS_SRC, - &saidx->src.sa, FULLMASK, IPSEC_ULPROTO_ANY); + if (sp != NULL && (sp->spidx.ul_proto == IPPROTO_TCP || + sp->spidx.ul_proto == IPPROTO_UDP)) + ul_proto = sp->spidx.ul_proto; + + addr = saidx->src; + mask = FULLMASK; + if (ul_proto != IPSEC_ULPROTO_ANY) { + switch (sp->spidx.src.sa.sa_family) { + case AF_INET: + if (sp->spidx.src.sin.sin_port != IPSEC_PORT_ANY) { + addr.sin.sin_port = sp->spidx.src.sin.sin_port; + mask = sp->spidx.prefs; + } + break; + case AF_INET6: + if (sp->spidx.src.sin6.sin6_port != IPSEC_PORT_ANY) { + addr.sin6.sin6_port = + sp->spidx.src.sin6.sin6_port; + mask = sp->spidx.prefs; + } + break; + default: + break; + } + } + m = key_setsadbaddr(SADB_EXT_ADDRESS_SRC, &addr.sa, mask, ul_proto); if (!m) { error = ENOBUFS; goto fail; } m_cat(result, m); - m = key_setsadbaddr(SADB_EXT_ADDRESS_DST, - &saidx->dst.sa, FULLMASK, IPSEC_ULPROTO_ANY); + addr = saidx->dst; + mask = FULLMASK; + if (ul_proto != IPSEC_ULPROTO_ANY) { + switch (sp->spidx.dst.sa.sa_family) { + case AF_INET: + if (sp->spidx.dst.sin.sin_port != IPSEC_PORT_ANY) { + addr.sin.sin_port = sp->spidx.dst.sin.sin_port; + mask = sp->spidx.prefd; + } + break; + case AF_INET6: + if (sp->spidx.dst.sin6.sin6_port != IPSEC_PORT_ANY) { + addr.sin6.sin6_port = + sp->spidx.dst.sin6.sin6_port; + mask = sp->spidx.prefd; + } + break; + default: + break; + } + } + m = key_setsadbaddr(SADB_EXT_ADDRESS_DST, &addr.sa, mask, ul_proto); if (!m) { error = ENOBUFS; goto fail; @@ -6343,8 +6537,9 @@ key_acquire(const struct secasindex *saidx, struct secpolicy *sp) /* XXX proxy address (optional) */ /* set sadb_x_policy */ - if (sp) { - m = key_setsadbxpolicy(sp->policy, sp->spidx.dir, sp->id); + if (sp != NULL) { + m = key_setsadbxpolicy(sp->policy, sp->spidx.dir, sp->id, + sp->priority); if (!m) { error = ENOBUFS; goto fail; @@ -6436,6 +6631,10 @@ key_acquire(const struct secasindex *saidx, struct secpolicy *sp) mtod(result, struct sadb_msg *)->sadb_msg_len = PFKEY_UNIT64(result->m_pkthdr.len); + KEYDBG(KEY_STAMP, + printf("%s: SP(%p)\n", __func__, sp)); + KEYDBG(KEY_DATA, kdebug_secasindex(saidx, NULL)); + return key_sendup_mbuf(NULL, result, KEY_SENDUP_REGISTERED); fail: @@ -6444,66 +6643,126 @@ key_acquire(const struct secasindex *saidx, struct secpolicy *sp) return error; } -static struct secacq * -key_newacq(const struct secasindex *saidx) +static uint32_t +key_newacq(const struct secasindex *saidx, int *perror) { - struct secacq *newacq; + struct secacq *acq; + uint32_t seq; - /* get new entry */ - newacq = malloc(sizeof(struct secacq), M_IPSEC_SAQ, M_NOWAIT|M_ZERO); - if (newacq == NULL) { + acq = malloc(sizeof(*acq), M_IPSEC_SAQ, M_NOWAIT | M_ZERO); + if (acq == NULL) { ipseclog((LOG_DEBUG, "%s: No more memory.\n", __func__)); - return NULL; + *perror = ENOBUFS; + return (0); } /* copy secindex */ - bcopy(saidx, &newacq->saidx, sizeof(newacq->saidx)); - newacq->seq = (V_acq_seq == ~0 ? 1 : ++V_acq_seq); - newacq->created = time_second; - newacq->count = 0; + bcopy(saidx, &acq->saidx, sizeof(acq->saidx)); + acq->created = time_second; + acq->count = 0; /* add to acqtree */ ACQ_LOCK(); - LIST_INSERT_HEAD(&V_acqtree, newacq, chain); + seq = acq->seq = (V_acq_seq == ~0 ? 1 : ++V_acq_seq); + LIST_INSERT_HEAD(&V_acqtree, acq, chain); + LIST_INSERT_HEAD(ACQADDRHASH_HASH(saidx), acq, addrhash); + LIST_INSERT_HEAD(ACQSEQHASH_HASH(seq), acq, seqhash); ACQ_UNLOCK(); - - return newacq; + *perror = 0; + return (seq); } -static struct secacq * -key_getacq(const struct secasindex *saidx) +static uint32_t +key_getacq(const struct secasindex *saidx, int *perror) { struct secacq *acq; + uint32_t seq; ACQ_LOCK(); - LIST_FOREACH(acq, &V_acqtree, chain) { - if (key_cmpsaidx(saidx, &acq->saidx, CMP_EXACTLY)) + LIST_FOREACH(acq, ACQADDRHASH_HASH(saidx), addrhash) { + if (key_cmpsaidx(&acq->saidx, saidx, CMP_EXACTLY)) { + if (acq->count > V_key_blockacq_count) { + /* + * Reset counter and send message. + * Also reset created time to keep ACQ for + * this saidx. + */ + acq->created = time_second; + acq->count = 0; + seq = acq->seq; + } else { + /* + * Increment counter and do nothing. + * We send SADB_ACQUIRE message only + * for each V_key_blockacq_count packet. + */ + acq->count++; + seq = 0; + } break; + } } ACQ_UNLOCK(); - - return acq; + if (acq != NULL) { + *perror = 0; + return (seq); + } + /* allocate new entry */ + return (key_newacq(saidx, perror)); } -static struct secacq * -key_getacqbyseq(seq) - u_int32_t seq; +static int +key_acqreset(uint32_t seq) { struct secacq *acq; ACQ_LOCK(); - LIST_FOREACH(acq, &V_acqtree, chain) { - if (acq->seq == seq) + LIST_FOREACH(acq, ACQSEQHASH_HASH(seq), seqhash) { + if (acq->seq == seq) { + acq->count = 0; + acq->created = time_second; break; + } } ACQ_UNLOCK(); + if (acq == NULL) + return (ESRCH); + return (0); +} +/* + * Mark ACQ entry as stale to remove it in key_flush_acq(). + * Called after successful SADB_GETSPI message. + */ +static int +key_acqdone(const struct secasindex *saidx, uint32_t seq) +{ + struct secacq *acq; - return acq; + ACQ_LOCK(); + LIST_FOREACH(acq, ACQSEQHASH_HASH(seq), seqhash) { + if (acq->seq == seq) + break; + } + if (acq != NULL) { + if (key_cmpsaidx(&acq->saidx, saidx, CMP_EXACTLY) == 0) { + ipseclog((LOG_DEBUG, + "%s: Mismatched saidx for ACQ %u", __func__, seq)); + acq = NULL; + } else { + acq->created = 0; + } + } else { + ipseclog((LOG_DEBUG, + "%s: ACQ %u is not found.", __func__, seq)); + } + ACQ_UNLOCK(); + if (acq == NULL) + return (ESRCH); + return (0); } static struct secspacq * -key_newspacq(spidx) - struct secpolicyindex *spidx; +key_newspacq(struct secpolicyindex *spidx) { struct secspacq *acq; @@ -6528,8 +6787,7 @@ key_newspacq(spidx) } static struct secspacq * -key_getspacq(spidx) - struct secpolicyindex *spidx; +key_getspacq(struct secpolicyindex *spidx) { struct secspacq *acq; @@ -6560,16 +6818,15 @@ key_getspacq(spidx) * m will always be freed. */ static int -key_acquire2(so, m, mhp) - struct socket *so; - struct mbuf *m; - const struct sadb_msghdr *mhp; +key_acquire2(struct socket *so, struct mbuf *m, const struct sadb_msghdr *mhp) { - const struct sadb_address *src0, *dst0; + SAHTREE_RLOCK_TRACKER; + struct sadb_address *src0, *dst0; struct secasindex saidx; struct secashead *sah; - u_int16_t proto; + uint32_t reqid; int error; + uint8_t mode, proto; IPSEC_ASSERT(so != NULL, ("null socket")); IPSEC_ASSERT(m != NULL, ("null mbuf")); @@ -6578,35 +6835,28 @@ key_acquire2(so, m, mhp) /* * Error message from KMd. - * We assume that if error was occured in IKEd, the length of PFKEY + * We assume that if error was occurred in IKEd, the length of PFKEY * message is equal to the size of sadb_msg structure. - * We do not raise error even if error occured in this function. + * We do not raise error even if error occurred in this function. */ if (mhp->msg->sadb_msg_len == PFKEY_UNIT64(sizeof(struct sadb_msg))) { - struct secacq *acq; - /* check sequence number */ - if (mhp->msg->sadb_msg_seq == 0) { + if (mhp->msg->sadb_msg_seq == 0 || + mhp->msg->sadb_msg_errno == 0) { ipseclog((LOG_DEBUG, "%s: must specify sequence " - "number.\n", __func__)); - m_freem(m); - return 0; - } - - if ((acq = key_getacqbyseq(mhp->msg->sadb_msg_seq)) == NULL) { + "number and errno.\n", __func__)); + } else { /* - * the specified larval SA is already gone, or we got - * a bogus sequence number. we can silently ignore it. + * IKEd reported that error occurred. + * XXXAE: what it expects from the kernel? + * Probably we should send SADB_ACQUIRE again? + * If so, reset ACQ's state. + * XXXAE: it looks useless. */ - m_freem(m); - return 0; + key_acqreset(mhp->msg->sadb_msg_seq); } - - /* reset acq counter in order to deletion by timehander. */ - acq->created = time_second; - acq->count = 0; m_freem(m); - return 0; + return (0); } /* @@ -6616,79 +6866,60 @@ key_acquire2(so, m, mhp) /* map satype to proto */ if ((proto = key_satype2proto(mhp->msg->sadb_msg_satype)) == 0) { ipseclog((LOG_DEBUG, "%s: invalid satype is passed.\n", - __func__)); + __func__)); return key_senderror(so, m, EINVAL); } - if (mhp->ext[SADB_EXT_ADDRESS_SRC] == NULL || - mhp->ext[SADB_EXT_ADDRESS_DST] == NULL || - mhp->ext[SADB_EXT_PROPOSAL] == NULL) { - /* error */ - ipseclog((LOG_DEBUG, "%s: invalid message is passed.\n", - __func__)); + if (SADB_CHECKHDR(mhp, SADB_EXT_ADDRESS_SRC) || + SADB_CHECKHDR(mhp, SADB_EXT_ADDRESS_DST) || + SADB_CHECKHDR(mhp, SADB_EXT_PROPOSAL)) { + ipseclog((LOG_DEBUG, + "%s: invalid message: missing required header.\n", + __func__)); return key_senderror(so, m, EINVAL); } - if (mhp->extlen[SADB_EXT_ADDRESS_SRC] < sizeof(struct sadb_address) || - mhp->extlen[SADB_EXT_ADDRESS_DST] < sizeof(struct sadb_address) || - mhp->extlen[SADB_EXT_PROPOSAL] < sizeof(struct sadb_prop)) { - /* error */ - ipseclog((LOG_DEBUG, "%s: invalid message is passed.\n", - __func__)); + if (SADB_CHECKLEN(mhp, SADB_EXT_ADDRESS_SRC) || + SADB_CHECKLEN(mhp, SADB_EXT_ADDRESS_DST) || + SADB_CHECKLEN(mhp, SADB_EXT_PROPOSAL)) { + ipseclog((LOG_DEBUG, + "%s: invalid message: wrong header size.\n", __func__)); return key_senderror(so, m, EINVAL); } - src0 = (struct sadb_address *)mhp->ext[SADB_EXT_ADDRESS_SRC]; - dst0 = (struct sadb_address *)mhp->ext[SADB_EXT_ADDRESS_DST]; - - /* XXX boundary check against sa_len */ - KEY_SETSECASIDX(proto, IPSEC_MODE_ANY, 0, src0 + 1, dst0 + 1, &saidx); - - /* - * Make sure the port numbers are zero. - * In case of NAT-T we will update them later if needed. - */ - KEY_PORTTOSADDR(&saidx.src, 0); - KEY_PORTTOSADDR(&saidx.dst, 0); - -#ifndef IPSEC_NAT_T - /* - * Handle NAT-T info if present. - */ - - if (mhp->ext[SADB_X_EXT_NAT_T_SPORT] != NULL && - mhp->ext[SADB_X_EXT_NAT_T_DPORT] != NULL) { - struct sadb_x_nat_t_port *sport, *dport; - - if (mhp->extlen[SADB_X_EXT_NAT_T_SPORT] < sizeof(*sport) || - mhp->extlen[SADB_X_EXT_NAT_T_DPORT] < sizeof(*dport)) { - ipseclog((LOG_DEBUG, "%s: invalid message.\n", + if (SADB_CHECKHDR(mhp, SADB_X_EXT_SA2)) { + mode = IPSEC_MODE_ANY; + reqid = 0; + } else { + if (SADB_CHECKLEN(mhp, SADB_X_EXT_SA2)) { + ipseclog((LOG_DEBUG, + "%s: invalid message: wrong header size.\n", __func__)); return key_senderror(so, m, EINVAL); } + mode = ((struct sadb_x_sa2 *) + mhp->ext[SADB_X_EXT_SA2])->sadb_x_sa2_mode; + reqid = ((struct sadb_x_sa2 *) + mhp->ext[SADB_X_EXT_SA2])->sadb_x_sa2_reqid; + } - sport = (struct sadb_x_nat_t_port *) - mhp->ext[SADB_X_EXT_NAT_T_SPORT]; - dport = (struct sadb_x_nat_t_port *) - mhp->ext[SADB_X_EXT_NAT_T_DPORT]; + src0 = (struct sadb_address *)mhp->ext[SADB_EXT_ADDRESS_SRC]; + dst0 = (struct sadb_address *)mhp->ext[SADB_EXT_ADDRESS_DST]; - if (sport) - KEY_PORTTOSADDR(&saidx.src, - sport->sadb_x_nat_t_port_port); - if (dport) - KEY_PORTTOSADDR(&saidx.dst, - dport->sadb_x_nat_t_port_port); + error = key_checksockaddrs((struct sockaddr *)(src0 + 1), + (struct sockaddr *)(dst0 + 1)); + if (error != 0) { + ipseclog((LOG_DEBUG, "%s: invalid sockaddr.\n", __func__)); + return key_senderror(so, m, EINVAL); } -#endif + KEY_SETSECASIDX(proto, mode, reqid, src0 + 1, dst0 + 1, &saidx); /* get a SA index */ - SAHTREE_LOCK(); - LIST_FOREACH(sah, &V_sahtree, chain) { - if (sah->state == SADB_SASTATE_DEAD) - continue; + SAHTREE_RLOCK(); + LIST_FOREACH(sah, SAHADDRHASH_HASH(&saidx), addrhash) { if (key_cmpsaidx(&sah->saidx, &saidx, CMP_MODE_REQID)) break; } - SAHTREE_UNLOCK(); + SAHTREE_RUNLOCK(); if (sah != NULL) { ipseclog((LOG_DEBUG, "%s: a SA exists already.\n", __func__)); return key_senderror(so, m, EEXIST); @@ -6696,12 +6927,13 @@ key_acquire2(so, m, mhp) error = key_acquire(&saidx, NULL); if (error != 0) { - ipseclog((LOG_DEBUG, "%s: error %d returned from key_acquire\n", - __func__, mhp->msg->sadb_msg_errno)); + ipseclog((LOG_DEBUG, + "%s: error %d returned from key_acquire()\n", + __func__, error)); return key_senderror(so, m, error); } - - return key_sendup_mbuf(so, m, KEY_SENDUP_REGISTERED); + m_freem(m); + return (0); } /* @@ -6718,12 +6950,9 @@ key_acquire2(so, m, mhp) * m will always be freed. */ static int -key_register(so, m, mhp) - struct socket *so; - struct mbuf *m; - const struct sadb_msghdr *mhp; +key_register(struct socket *so, struct mbuf *m, const struct sadb_msghdr *mhp) { - struct secreg *reg, *newreg = 0; + struct secreg *reg, *newreg = NULL; IPSEC_ASSERT(so != NULL, ("null socket")); IPSEC_ASSERT(m != NULL, ("null mbuf")); @@ -6777,14 +7006,14 @@ key_register(so, m, mhp) /* create new sadb_msg to reply. */ alen = 0; for (i = 1; i <= SADB_AALG_MAX; i++) { - if (ah_algorithm_lookup(i)) + if (auth_algorithm_lookup(i)) alen += sizeof(struct sadb_alg); } if (alen) alen += sizeof(struct sadb_supported); elen = 0; for (i = 1; i <= SADB_EALG_MAX; i++) { - if (esp_algorithm_lookup(i)) + if (enc_algorithm_lookup(i)) elen += sizeof(struct sadb_alg); } if (elen) @@ -6795,10 +7024,9 @@ key_register(so, m, mhp) if (len > MCLBYTES) return key_senderror(so, m, ENOBUFS); - MGETHDR(n, M_DONTWAIT, MT_DATA); + MGETHDR(n, M_NOWAIT, MT_DATA); if (len > MHLEN) { - MCLGET(n, M_DONTWAIT); - if ((n->m_flags & M_EXT) == 0) { + if (!(MCLGET(n, M_NOWAIT))) { m_freem(n); n = NULL; } @@ -6824,10 +7052,10 @@ key_register(so, m, mhp) off += PFKEY_ALIGN8(sizeof(*sup)); for (i = 1; i <= SADB_AALG_MAX; i++) { - struct auth_hash *aalgo; + const struct auth_hash *aalgo; u_int16_t minkeysize, maxkeysize; - aalgo = ah_algorithm_lookup(i); + aalgo = auth_algorithm_lookup(i); if (!aalgo) continue; alg = (struct sadb_alg *)(mtod(n, caddr_t) + off); @@ -6848,14 +7076,14 @@ key_register(so, m, mhp) off += PFKEY_ALIGN8(sizeof(*sup)); for (i = 1; i <= SADB_EALG_MAX; i++) { - struct enc_xform *ealgo; + const struct enc_xform *ealgo; - ealgo = esp_algorithm_lookup(i); + ealgo = enc_algorithm_lookup(i); if (!ealgo) continue; alg = (struct sadb_alg *)(mtod(n, caddr_t) + off); alg->sadb_alg_id = i; - alg->sadb_alg_ivlen = ealgo->blocksize; + alg->sadb_alg_ivlen = ealgo->ivsize; alg->sadb_alg_minbits = _BITS(ealgo->minkey); alg->sadb_alg_maxbits = _BITS(ealgo->maxkey); off += PFKEY_ALIGN8(sizeof(struct sadb_alg)); @@ -6911,21 +7139,21 @@ key_freereg(struct socket *so) * others : error number */ static int -key_expire(struct secasvar *sav) +key_expire(struct secasvar *sav, int hard) { - int s; - int satype; struct mbuf *result = NULL, *m; - int len; - int error = -1; struct sadb_lifetime *lt; - - /* XXX: Why do we lock ? */ - s = splnet(); /*called from softclock()*/ + uint32_t replay_count; + int error, len; + uint8_t satype; IPSEC_ASSERT (sav != NULL, ("null sav")); IPSEC_ASSERT (sav->sah != NULL, ("null sa header")); + KEYDBG(KEY_STAMP, + printf("%s: SA(%p) expired %s lifetime\n", __func__, + sav, hard ? "hard": "soft")); + KEYDBG(KEY_DATA, kdebug_secasv(sav)); /* set msg header */ satype = key_proto2satype(sav->sah->saidx.proto); IPSEC_ASSERT(satype != 0, ("invalid proto, satype %u", satype)); @@ -6945,8 +7173,11 @@ key_expire(struct secasvar *sav) m_cat(result, m); /* create SA extension */ - m = key_setsadbxsa2(sav->sah->saidx.mode, - sav->replay ? sav->replay->count : 0, + SECASVAR_LOCK(sav); + replay_count = sav->replay ? sav->replay->count : 0; + SECASVAR_UNLOCK(sav); + + m = key_setsadbxsa2(sav->sah->saidx.mode, replay_count, sav->sah->saidx.reqid); if (!m) { error = ENOBUFS; @@ -6954,30 +7185,49 @@ key_expire(struct secasvar *sav) } m_cat(result, m); + if (sav->replay && sav->replay->wsize > UINT8_MAX) { + m = key_setsadbxsareplay(sav->replay->wsize); + if (!m) { + error = ENOBUFS; + goto fail; + } + m_cat(result, m); + } + /* create lifetime extension (current and soft) */ len = PFKEY_ALIGN8(sizeof(*lt)) * 2; - m = key_alloc_mbuf(len); - if (!m || m->m_next) { /*XXX*/ - if (m) - m_freem(m); + m = m_get2(len, M_NOWAIT, MT_DATA, 0); + if (m == NULL) { error = ENOBUFS; goto fail; } + m_align(m, len); + m->m_len = len; bzero(mtod(m, caddr_t), len); lt = mtod(m, struct sadb_lifetime *); lt->sadb_lifetime_len = PFKEY_UNIT64(sizeof(struct sadb_lifetime)); lt->sadb_lifetime_exttype = SADB_EXT_LIFETIME_CURRENT; - lt->sadb_lifetime_allocations = sav->lft_c->allocations; - lt->sadb_lifetime_bytes = sav->lft_c->bytes; - lt->sadb_lifetime_addtime = sav->lft_c->addtime; - lt->sadb_lifetime_usetime = sav->lft_c->usetime; + lt->sadb_lifetime_allocations = + (uint32_t)counter_u64_fetch(sav->lft_c_allocations); + lt->sadb_lifetime_bytes = + counter_u64_fetch(sav->lft_c_bytes); + lt->sadb_lifetime_addtime = sav->created; + lt->sadb_lifetime_usetime = sav->firstused; lt = (struct sadb_lifetime *)(mtod(m, caddr_t) + len / 2); lt->sadb_lifetime_len = PFKEY_UNIT64(sizeof(struct sadb_lifetime)); - lt->sadb_lifetime_exttype = SADB_EXT_LIFETIME_SOFT; - lt->sadb_lifetime_allocations = sav->lft_s->allocations; - lt->sadb_lifetime_bytes = sav->lft_s->bytes; - lt->sadb_lifetime_addtime = sav->lft_s->addtime; - lt->sadb_lifetime_usetime = sav->lft_s->usetime; + if (hard) { + lt->sadb_lifetime_exttype = SADB_EXT_LIFETIME_HARD; + lt->sadb_lifetime_allocations = sav->lft_h->allocations; + lt->sadb_lifetime_bytes = sav->lft_h->bytes; + lt->sadb_lifetime_addtime = sav->lft_h->addtime; + lt->sadb_lifetime_usetime = sav->lft_h->usetime; + } else { + lt->sadb_lifetime_exttype = SADB_EXT_LIFETIME_SOFT; + lt->sadb_lifetime_allocations = sav->lft_s->allocations; + lt->sadb_lifetime_bytes = sav->lft_s->bytes; + lt->sadb_lifetime_addtime = sav->lft_s->addtime; + lt->sadb_lifetime_usetime = sav->lft_s->usetime; + } m_cat(result, m); /* set sadb_address for source */ @@ -7002,6 +7252,8 @@ key_expire(struct secasvar *sav) /* * XXX-BZ Handle NAT-T extensions here. + * XXXAE: it doesn't seem quite useful. IKEs should not depend on + * this information, we report only significant SA fields. */ if ((result->m_flags & M_PKTHDR) == 0) { @@ -7024,16 +7276,44 @@ key_expire(struct secasvar *sav) mtod(result, struct sadb_msg *)->sadb_msg_len = PFKEY_UNIT64(result->m_pkthdr.len); - splx(s); return key_sendup_mbuf(NULL, result, KEY_SENDUP_REGISTERED); fail: if (result) m_freem(result); - splx(s); return error; } +static void +key_freesah_flushed(struct secashead_queue *flushq) +{ + struct secashead *sah, *nextsah; + struct secasvar *sav, *nextsav; + + sah = TAILQ_FIRST(flushq); + while (sah != NULL) { + sav = TAILQ_FIRST(&sah->savtree_larval); + while (sav != NULL) { + nextsav = TAILQ_NEXT(sav, chain); + TAILQ_REMOVE(&sah->savtree_larval, sav, chain); + key_freesav(&sav); /* release last reference */ + key_freesah(&sah); /* release reference from SAV */ + sav = nextsav; + } + sav = TAILQ_FIRST(&sah->savtree_alive); + while (sav != NULL) { + nextsav = TAILQ_NEXT(sav, chain); + TAILQ_REMOVE(&sah->savtree_alive, sav, chain); + key_freesav(&sav); /* release last reference */ + key_freesah(&sah); /* release reference from SAV */ + sav = nextsav; + } + nextsah = TAILQ_NEXT(sah, chain); + key_freesah(&sah); /* release last reference */ + sah = nextsah; + } +} + /* * SADB_FLUSH processing * receive @@ -7047,17 +7327,14 @@ key_expire(struct secasvar *sav) * m will always be freed. */ static int -key_flush(so, m, mhp) - struct socket *so; - struct mbuf *m; - const struct sadb_msghdr *mhp; +key_flush(struct socket *so, struct mbuf *m, const struct sadb_msghdr *mhp) { + struct secashead_queue flushq; struct sadb_msg *newmsg; struct secashead *sah, *nextsah; - struct secasvar *sav, *nextsav; - u_int16_t proto; - u_int8_t state; - u_int stateidx; + struct secasvar *sav; + uint8_t proto; + int i; IPSEC_ASSERT(so != NULL, ("null socket")); IPSEC_ASSERT(mhp != NULL, ("null msghdr")); @@ -7069,37 +7346,71 @@ key_flush(so, m, mhp) __func__)); return key_senderror(so, m, EINVAL); } - - /* no SATYPE specified, i.e. flushing all SA. */ - SAHTREE_LOCK(); - for (sah = LIST_FIRST(&V_sahtree); - sah != NULL; - sah = nextsah) { - nextsah = LIST_NEXT(sah, chain); - - if (mhp->msg->sadb_msg_satype != SADB_SATYPE_UNSPEC - && proto != sah->saidx.proto) - continue; - - for (stateidx = 0; - stateidx < _ARRAYLEN(saorder_state_alive); - stateidx++) { - state = saorder_state_any[stateidx]; - for (sav = LIST_FIRST(&sah->savtree[state]); - sav != NULL; - sav = nextsav) { - - nextsav = LIST_NEXT(sav, chain); - - key_sa_chgstate(sav, SADB_SASTATE_DEAD); - KEY_FREESAV(&sav); + KEYDBG(KEY_STAMP, + printf("%s: proto %u\n", __func__, proto)); + + TAILQ_INIT(&flushq); + if (proto == IPSEC_PROTO_ANY) { + /* no SATYPE specified, i.e. flushing all SA. */ + SAHTREE_WLOCK(); + /* Move all SAHs into flushq */ + TAILQ_CONCAT(&flushq, &V_sahtree, chain); + /* Flush all buckets in SPI hash */ + for (i = 0; i < V_savhash_mask + 1; i++) + LIST_INIT(&V_savhashtbl[i]); + /* Flush all buckets in SAHADDRHASH */ + for (i = 0; i < V_sahaddrhash_mask + 1; i++) + LIST_INIT(&V_sahaddrhashtbl[i]); + /* Mark all SAHs as unlinked */ + TAILQ_FOREACH(sah, &flushq, chain) { + sah->state = SADB_SASTATE_DEAD; + /* + * Callout handler makes its job using + * RLOCK and drain queues. In case, when this + * function will be called just before it + * acquires WLOCK, we need to mark SAs as + * unlinked to prevent second unlink. + */ + TAILQ_FOREACH(sav, &sah->savtree_larval, chain) { + sav->state = SADB_SASTATE_DEAD; + } + TAILQ_FOREACH(sav, &sah->savtree_alive, chain) { + sav->state = SADB_SASTATE_DEAD; } } - - sah->state = SADB_SASTATE_DEAD; + SAHTREE_WUNLOCK(); + } else { + SAHTREE_WLOCK(); + sah = TAILQ_FIRST(&V_sahtree); + while (sah != NULL) { + IPSEC_ASSERT(sah->state != SADB_SASTATE_DEAD, + ("DEAD SAH %p in SADB_FLUSH", sah)); + nextsah = TAILQ_NEXT(sah, chain); + if (sah->saidx.proto != proto) { + sah = nextsah; + continue; + } + sah->state = SADB_SASTATE_DEAD; + TAILQ_REMOVE(&V_sahtree, sah, chain); + LIST_REMOVE(sah, addrhash); + /* Unlink all SAs from SPI hash */ + TAILQ_FOREACH(sav, &sah->savtree_larval, chain) { + LIST_REMOVE(sav, spihash); + sav->state = SADB_SASTATE_DEAD; + } + TAILQ_FOREACH(sav, &sah->savtree_alive, chain) { + LIST_REMOVE(sav, spihash); + sav->state = SADB_SASTATE_DEAD; + } + /* Add SAH into flushq */ + TAILQ_INSERT_HEAD(&flushq, sah, chain); + sah = nextsah; + } + SAHTREE_WUNLOCK(); } - SAHTREE_UNLOCK(); + key_freesah_flushed(&flushq); + /* Free all queued SAs and SAHs */ if (m->m_len < sizeof(struct sadb_msg) || sizeof(struct sadb_msg) > m->m_len + M_TRAILINGSPACE(m)) { ipseclog((LOG_DEBUG, "%s: No more memory.\n", __func__)); @@ -7130,20 +7441,15 @@ key_flush(so, m, mhp) * m will always be freed. */ static int -key_dump(so, m, mhp) - struct socket *so; - struct mbuf *m; - const struct sadb_msghdr *mhp; +key_dump(struct socket *so, struct mbuf *m, const struct sadb_msghdr *mhp) { + SAHTREE_RLOCK_TRACKER; struct secashead *sah; struct secasvar *sav; - u_int16_t proto; - u_int stateidx; - u_int8_t satype; - u_int8_t state; - int cnt; struct sadb_msg *newmsg; struct mbuf *n; + uint32_t cnt; + uint8_t proto, satype; IPSEC_ASSERT(so != NULL, ("null socket")); IPSEC_ASSERT(m != NULL, ("null mbuf")); @@ -7153,79 +7459,73 @@ key_dump(so, m, mhp) /* map satype to proto */ if ((proto = key_satype2proto(mhp->msg->sadb_msg_satype)) == 0) { ipseclog((LOG_DEBUG, "%s: invalid satype is passed.\n", - __func__)); + __func__)); return key_senderror(so, m, EINVAL); } /* count sav entries to be sent to the userland. */ cnt = 0; - SAHTREE_LOCK(); - LIST_FOREACH(sah, &V_sahtree, chain) { - if (mhp->msg->sadb_msg_satype != SADB_SATYPE_UNSPEC - && proto != sah->saidx.proto) + SAHTREE_RLOCK(); + TAILQ_FOREACH(sah, &V_sahtree, chain) { + if (mhp->msg->sadb_msg_satype != SADB_SATYPE_UNSPEC && + proto != sah->saidx.proto) continue; - for (stateidx = 0; - stateidx < _ARRAYLEN(saorder_state_any); - stateidx++) { - state = saorder_state_any[stateidx]; - LIST_FOREACH(sav, &sah->savtree[state], chain) { - cnt++; - } - } + TAILQ_FOREACH(sav, &sah->savtree_larval, chain) + cnt++; + TAILQ_FOREACH(sav, &sah->savtree_alive, chain) + cnt++; } if (cnt == 0) { - SAHTREE_UNLOCK(); + SAHTREE_RUNLOCK(); return key_senderror(so, m, ENOENT); } /* send this to the userland, one at a time. */ newmsg = NULL; - LIST_FOREACH(sah, &V_sahtree, chain) { - if (mhp->msg->sadb_msg_satype != SADB_SATYPE_UNSPEC - && proto != sah->saidx.proto) + TAILQ_FOREACH(sah, &V_sahtree, chain) { + if (mhp->msg->sadb_msg_satype != SADB_SATYPE_UNSPEC && + proto != sah->saidx.proto) continue; /* map proto to satype */ if ((satype = key_proto2satype(sah->saidx.proto)) == 0) { - SAHTREE_UNLOCK(); + SAHTREE_RUNLOCK(); ipseclog((LOG_DEBUG, "%s: there was invalid proto in " - "SAD.\n", __func__)); + "SAD.\n", __func__)); return key_senderror(so, m, EINVAL); } - - for (stateidx = 0; - stateidx < _ARRAYLEN(saorder_state_any); - stateidx++) { - state = saorder_state_any[stateidx]; - LIST_FOREACH(sav, &sah->savtree[state], chain) { - n = key_setdumpsa(sav, SADB_DUMP, satype, - --cnt, mhp->msg->sadb_msg_pid); - if (!n) { - SAHTREE_UNLOCK(); - return key_senderror(so, m, ENOBUFS); - } - key_sendup_mbuf(so, n, KEY_SENDUP_ONE); + TAILQ_FOREACH(sav, &sah->savtree_larval, chain) { + n = key_setdumpsa(sav, SADB_DUMP, satype, + --cnt, mhp->msg->sadb_msg_pid); + if (n == NULL) { + SAHTREE_RUNLOCK(); + return key_senderror(so, m, ENOBUFS); + } + key_sendup_mbuf(so, n, KEY_SENDUP_ONE); + } + TAILQ_FOREACH(sav, &sah->savtree_alive, chain) { + n = key_setdumpsa(sav, SADB_DUMP, satype, + --cnt, mhp->msg->sadb_msg_pid); + if (n == NULL) { + SAHTREE_RUNLOCK(); + return key_senderror(so, m, ENOBUFS); } + key_sendup_mbuf(so, n, KEY_SENDUP_ONE); } } - SAHTREE_UNLOCK(); - + SAHTREE_RUNLOCK(); m_freem(m); - return 0; + return (0); } - /* * SADB_X_PROMISC processing * * m will always be freed. */ static int -key_promisc(so, m, mhp) - struct socket *so; - struct mbuf *m; - const struct sadb_msghdr *mhp; +key_promisc(struct socket *so, struct mbuf *m, const struct sadb_msghdr *mhp) { int olen; @@ -7272,8 +7572,8 @@ key_promisc(so, m, mhp) } } -static int (*key_typesw[]) __P((struct socket *, struct mbuf *, - const struct sadb_msghdr *)) = { +static int (*key_typesw[])(struct socket *, struct mbuf *, + const struct sadb_msghdr *) = { NULL, /* SADB_RESERVED */ key_getspi, /* SADB_GETSPI */ key_update, /* SADB_UPDATE */ @@ -7311,9 +7611,7 @@ static int (*key_typesw[]) __P((struct socket *, struct mbuf *, * length for buffer to send to user process. */ int -key_parse(m, so) - struct mbuf *m; - struct socket *so; +key_parse(struct mbuf *m, struct socket *so) { struct sadb_msg *msg; struct sadb_msghdr mh; @@ -7324,12 +7622,6 @@ key_parse(m, so) IPSEC_ASSERT(so != NULL, ("null socket")); IPSEC_ASSERT(m != NULL, ("null mbuf")); -#if 0 /*kdebug_sadb assumes msg in linear buffer*/ - KEYDEBUG(KEYDEBUG_KEY_DUMP, - ipseclog((LOG_DEBUG, "%s: passed sadb_msg\n", __func__)); - kdebug_sadb(msg)); -#endif - if (m->m_len < sizeof(struct sadb_msg)) { m = m_pullup(m, sizeof(struct sadb_msg)); if (!m) @@ -7339,8 +7631,7 @@ key_parse(m, so) orglen = PFKEY_UNUNIT64(msg->sadb_msg_len); target = KEY_SENDUP_ONE; - if ((m->m_flags & M_PKTHDR) == 0 || - m->m_pkthdr.len != m->m_pkthdr.len) { + if ((m->m_flags & M_PKTHDR) == 0 || m->m_pkthdr.len != orglen) { ipseclog((LOG_DEBUG, "%s: invalid message length.\n",__func__)); PFKEYSTAT_INC(out_invlen); error = EINVAL; @@ -7371,10 +7662,9 @@ key_parse(m, so) if (m->m_next) { struct mbuf *n; - MGETHDR(n, M_DONTWAIT, MT_DATA); + MGETHDR(n, M_NOWAIT, MT_DATA); if (n && m->m_pkthdr.len > MHLEN) { - MCLGET(n, M_DONTWAIT); - if ((n->m_flags & M_EXT) == 0) { + if (!(MCLGET(n, M_NOWAIT))) { m_free(n); n = NULL; } @@ -7397,64 +7687,79 @@ key_parse(m, so) msg = mh.msg; - /* check SA type */ - switch (msg->sadb_msg_satype) { - case SADB_SATYPE_UNSPEC: - switch (msg->sadb_msg_type) { - case SADB_GETSPI: - case SADB_UPDATE: - case SADB_ADD: - case SADB_DELETE: - case SADB_GET: - case SADB_ACQUIRE: - case SADB_EXPIRE: - ipseclog((LOG_DEBUG, "%s: must specify satype " - "when msg type=%u.\n", __func__, - msg->sadb_msg_type)); + /* We use satype as scope mask for spddump */ + if (msg->sadb_msg_type == SADB_X_SPDDUMP) { + switch (msg->sadb_msg_satype) { + case IPSEC_POLICYSCOPE_ANY: + case IPSEC_POLICYSCOPE_GLOBAL: + case IPSEC_POLICYSCOPE_IFNET: + case IPSEC_POLICYSCOPE_PCB: + break; + default: + ipseclog((LOG_DEBUG, "%s: illegal satype=%u\n", + __func__, msg->sadb_msg_type)); PFKEYSTAT_INC(out_invsatype); error = EINVAL; goto senderror; } - break; - case SADB_SATYPE_AH: - case SADB_SATYPE_ESP: - case SADB_X_SATYPE_IPCOMP: - case SADB_X_SATYPE_TCPSIGNATURE: - switch (msg->sadb_msg_type) { - case SADB_X_SPDADD: - case SADB_X_SPDDELETE: - case SADB_X_SPDGET: - case SADB_X_SPDDUMP: - case SADB_X_SPDFLUSH: - case SADB_X_SPDSETIDX: - case SADB_X_SPDUPDATE: - case SADB_X_SPDDELETE2: - ipseclog((LOG_DEBUG, "%s: illegal satype=%u\n", - __func__, msg->sadb_msg_type)); + } else { + switch (msg->sadb_msg_satype) { /* check SA type */ + case SADB_SATYPE_UNSPEC: + switch (msg->sadb_msg_type) { + case SADB_GETSPI: + case SADB_UPDATE: + case SADB_ADD: + case SADB_DELETE: + case SADB_GET: + case SADB_ACQUIRE: + case SADB_EXPIRE: + ipseclog((LOG_DEBUG, "%s: must specify satype " + "when msg type=%u.\n", __func__, + msg->sadb_msg_type)); + PFKEYSTAT_INC(out_invsatype); + error = EINVAL; + goto senderror; + } + break; + case SADB_SATYPE_AH: + case SADB_SATYPE_ESP: + case SADB_X_SATYPE_IPCOMP: + case SADB_X_SATYPE_TCPSIGNATURE: + switch (msg->sadb_msg_type) { + case SADB_X_SPDADD: + case SADB_X_SPDDELETE: + case SADB_X_SPDGET: + case SADB_X_SPDFLUSH: + case SADB_X_SPDSETIDX: + case SADB_X_SPDUPDATE: + case SADB_X_SPDDELETE2: + ipseclog((LOG_DEBUG, "%s: illegal satype=%u\n", + __func__, msg->sadb_msg_type)); + PFKEYSTAT_INC(out_invsatype); + error = EINVAL; + goto senderror; + } + break; + case SADB_SATYPE_RSVP: + case SADB_SATYPE_OSPFV2: + case SADB_SATYPE_RIPV2: + case SADB_SATYPE_MIP: + ipseclog((LOG_DEBUG, "%s: type %u isn't supported.\n", + __func__, msg->sadb_msg_satype)); + PFKEYSTAT_INC(out_invsatype); + error = EOPNOTSUPP; + goto senderror; + case 1: /* XXX: What does it do? */ + if (msg->sadb_msg_type == SADB_X_PROMISC) + break; + /*FALLTHROUGH*/ + default: + ipseclog((LOG_DEBUG, "%s: invalid type %u is passed.\n", + __func__, msg->sadb_msg_satype)); PFKEYSTAT_INC(out_invsatype); error = EINVAL; goto senderror; } - break; - case SADB_SATYPE_RSVP: - case SADB_SATYPE_OSPFV2: - case SADB_SATYPE_RIPV2: - case SADB_SATYPE_MIP: - ipseclog((LOG_DEBUG, "%s: type %u isn't supported.\n", - __func__, msg->sadb_msg_satype)); - PFKEYSTAT_INC(out_invsatype); - error = EOPNOTSUPP; - goto senderror; - case 1: /* XXX: What does it do? */ - if (msg->sadb_msg_type == SADB_X_PROMISC) - break; - /*FALLTHROUGH*/ - default: - ipseclog((LOG_DEBUG, "%s: invalid type %u is passed.\n", - __func__, msg->sadb_msg_satype)); - PFKEYSTAT_INC(out_invsatype); - error = EINVAL; - goto senderror; } /* check field of upper layer protocol and address family */ @@ -7546,7 +7851,7 @@ key_parse(m, so) */ } - if (msg->sadb_msg_type >= sizeof(key_typesw)/sizeof(key_typesw[0]) || + if (msg->sadb_msg_type >= nitems(key_typesw) || key_typesw[msg->sadb_msg_type] == NULL) { PFKEYSTAT_INC(out_invmsgtype); error = EINVAL; @@ -7561,10 +7866,7 @@ senderror: } static int -key_senderror(so, m, code) - struct socket *so; - struct mbuf *m; - int code; +key_senderror(struct socket *so, struct mbuf *m, int code) { struct sadb_msg *msg; @@ -7582,9 +7884,7 @@ key_senderror(so, m, code) * XXX larger-than-MCLBYTES extension? */ static int -key_align(m, mhp) - struct mbuf *m; - struct sadb_msghdr *mhp; +key_align(struct mbuf *m, struct sadb_msghdr *mhp) { struct mbuf *n; struct sadb_ext *ext; @@ -7633,14 +7933,15 @@ key_align(m, mhp) case SADB_EXT_SPIRANGE: case SADB_X_EXT_POLICY: case SADB_X_EXT_SA2: -#ifdef IPSEC_NAT_T case SADB_X_EXT_NAT_T_TYPE: case SADB_X_EXT_NAT_T_SPORT: case SADB_X_EXT_NAT_T_DPORT: case SADB_X_EXT_NAT_T_OAI: case SADB_X_EXT_NAT_T_OAR: case SADB_X_EXT_NAT_T_FRAG: -#endif + case SADB_X_EXT_SA_REPLAY: + case SADB_X_EXT_NEW_ADDRESS_SRC: + case SADB_X_EXT_NEW_ADDRESS_DST: /* duplicate check */ /* * XXX Are there duplication payloads of either @@ -7692,9 +7993,7 @@ key_align(m, mhp) } static int -key_validate_ext(ext, len) - const struct sadb_ext *ext; - int len; +key_validate_ext(const struct sadb_ext *ext, int len) { const struct sockaddr *sa; enum { NONE, ADDR } checktype = NONE; @@ -7705,8 +8004,8 @@ key_validate_ext(ext, len) return EINVAL; /* if it does not match minimum/maximum length, bail */ - if (ext->sadb_ext_type >= sizeof(minsize) / sizeof(minsize[0]) || - ext->sadb_ext_type >= sizeof(maxsize) / sizeof(maxsize[0])) + if (ext->sadb_ext_type >= nitems(minsize) || + ext->sadb_ext_type >= nitems(maxsize)) return EINVAL; if (!minsize[ext->sadb_ext_type] || len < minsize[ext->sadb_ext_type]) return EINVAL; @@ -7718,6 +8017,10 @@ key_validate_ext(ext, len) case SADB_EXT_ADDRESS_SRC: case SADB_EXT_ADDRESS_DST: case SADB_EXT_ADDRESS_PROXY: + case SADB_X_EXT_NAT_T_OAI: + case SADB_X_EXT_NAT_T_OAR: + case SADB_X_EXT_NEW_ADDRESS_SRC: + case SADB_X_EXT_NEW_ADDRESS_DST: baselen = PFKEY_ALIGN8(sizeof(struct sadb_address)); checktype = ADDR; break; @@ -7755,10 +8058,24 @@ key_init(void) { int i; - for (i = 0; i < IPSEC_DIR_MAX; i++) - LIST_INIT(&V_sptree[i]); + for (i = 0; i < IPSEC_DIR_MAX; i++) { + TAILQ_INIT(&V_sptree[i]); + TAILQ_INIT(&V_sptree_ifnet[i]); + } + + V_key_lft_zone = uma_zcreate("IPsec SA lft_c", + sizeof(uint64_t) * 2, NULL, NULL, NULL, NULL, + UMA_ALIGN_PTR, UMA_ZONE_PCPU); - LIST_INIT(&V_sahtree); + TAILQ_INIT(&V_sahtree); + V_sphashtbl = hashinit(SPHASH_NHASH, M_IPSEC_SP, &V_sphash_mask); + V_savhashtbl = hashinit(SAVHASH_NHASH, M_IPSEC_SA, &V_savhash_mask); + V_sahaddrhashtbl = hashinit(SAHHASH_NHASH, M_IPSEC_SAH, + &V_sahaddrhash_mask); + V_acqaddrhashtbl = hashinit(ACQHASH_NHASH, M_IPSEC_SAQ, + &V_acqaddrhash_mask); + V_acqseqhashtbl = hashinit(ACQHASH_NHASH, M_IPSEC_SAQ, + &V_acqseqhash_mask); for (i = 0; i <= SADB_SATYPE_MAX; i++) LIST_INIT(&V_regtree[i]); @@ -7766,13 +8083,10 @@ key_init(void) LIST_INIT(&V_acqtree); LIST_INIT(&V_spacqtree); - /* system default */ - V_ip4_def_policy.policy = IPSEC_POLICY_NONE; - V_ip4_def_policy.refcnt++; /*never reclaim this*/ - if (!IS_DEFAULT_VNET(curvnet)) return; + XFORMS_LOCK_INIT(); SPTREE_LOCK_INIT(); REGTREE_LOCK_INIT(); SAHTREE_LOCK_INIT(); @@ -7780,48 +8094,71 @@ key_init(void) SPACQ_LOCK_INIT(); #ifndef IPSEC_DEBUG2 - timeout((void *)key_timehandler, (void *)0, hz); + callout_init(&key_timer, 1); + callout_reset(&key_timer, hz, key_timehandler, NULL); #endif /*IPSEC_DEBUG2*/ /* initialize key statistics */ keystat.getspi_count = 1; - printf("IPsec: Initialized Security Association Processing.\n"); + if (bootverbose) + printf("IPsec: Initialized Security Association Processing.\n"); } #ifdef VIMAGE void key_destroy(void) { + struct secashead_queue sahdrainq; + struct secpolicy_queue drainq; struct secpolicy *sp, *nextsp; struct secacq *acq, *nextacq; struct secspacq *spacq, *nextspacq; - struct secashead *sah, *nextsah; + struct secashead *sah; + struct secasvar *sav; struct secreg *reg; int i; - SPTREE_LOCK(); + /* + * XXX: can we just call free() for each object without + * walking through safe way with releasing references? + */ + TAILQ_INIT(&drainq); + SPTREE_WLOCK(); for (i = 0; i < IPSEC_DIR_MAX; i++) { - for (sp = LIST_FIRST(&V_sptree[i]); - sp != NULL; sp = nextsp) { - nextsp = LIST_NEXT(sp, chain); - if (__LIST_CHAINED(sp)) { - LIST_REMOVE(sp, chain); - free(sp, M_IPSEC_SP); - } + TAILQ_CONCAT(&drainq, &V_sptree[i], chain); + TAILQ_CONCAT(&drainq, &V_sptree_ifnet[i], chain); + } + SPTREE_WUNLOCK(); + sp = TAILQ_FIRST(&drainq); + while (sp != NULL) { + nextsp = TAILQ_NEXT(sp, chain); + key_freesp(&sp); + sp = nextsp; + } + + TAILQ_INIT(&sahdrainq); + SAHTREE_WLOCK(); + TAILQ_CONCAT(&sahdrainq, &V_sahtree, chain); + for (i = 0; i < V_savhash_mask + 1; i++) + LIST_INIT(&V_savhashtbl[i]); + for (i = 0; i < V_sahaddrhash_mask + 1; i++) + LIST_INIT(&V_sahaddrhashtbl[i]); + TAILQ_FOREACH(sah, &sahdrainq, chain) { + sah->state = SADB_SASTATE_DEAD; + TAILQ_FOREACH(sav, &sah->savtree_larval, chain) { + sav->state = SADB_SASTATE_DEAD; } - } - SPTREE_UNLOCK(); - - SAHTREE_LOCK(); - for (sah = LIST_FIRST(&V_sahtree); sah != NULL; sah = nextsah) { - nextsah = LIST_NEXT(sah, chain); - if (__LIST_CHAINED(sah)) { - LIST_REMOVE(sah, chain); - free(sah, M_IPSEC_SAH); + TAILQ_FOREACH(sav, &sah->savtree_alive, chain) { + sav->state = SADB_SASTATE_DEAD; } } - SAHTREE_UNLOCK(); + SAHTREE_WUNLOCK(); + + key_freesah_flushed(&sahdrainq); + hashdestroy(V_sphashtbl, M_IPSEC_SP, V_sphash_mask); + hashdestroy(V_savhashtbl, M_IPSEC_SA, V_savhash_mask); + hashdestroy(V_sahaddrhashtbl, M_IPSEC_SAH, V_sahaddrhash_mask); REGTREE_LOCK(); for (i = 0; i <= SADB_SATYPE_MAX; i++) { @@ -7836,12 +8173,12 @@ key_destroy(void) REGTREE_UNLOCK(); ACQ_LOCK(); - for (acq = LIST_FIRST(&V_acqtree); acq != NULL; acq = nextacq) { + acq = LIST_FIRST(&V_acqtree); + while (acq != NULL) { nextacq = LIST_NEXT(acq, chain); - if (__LIST_CHAINED(acq)) { - LIST_REMOVE(acq, chain); - free(acq, M_IPSEC_SAQ); - } + LIST_REMOVE(acq, chain); + free(acq, M_IPSEC_SAQ); + acq = nextacq; } ACQ_UNLOCK(); @@ -7855,56 +8192,31 @@ key_destroy(void) } } SPACQ_UNLOCK(); + hashdestroy(V_acqaddrhashtbl, M_IPSEC_SAQ, V_acqaddrhash_mask); + hashdestroy(V_acqseqhashtbl, M_IPSEC_SAQ, V_acqseqhash_mask); + uma_zdestroy(V_key_lft_zone); } #endif -/* - * XXX: maybe This function is called after INBOUND IPsec processing. - * - * Special check for tunnel-mode packets. - * We must make some checks for consistency between inner and outer IP header. - * - * xxx more checks to be provided - */ -int -key_checktunnelsanity(sav, family, src, dst) - struct secasvar *sav; - u_int family; - caddr_t src; - caddr_t dst; -{ - IPSEC_ASSERT(sav->sah != NULL, ("null SA header")); - - /* XXX: check inner IP header */ - - return 1; -} - /* record data transfer on SA, and update timestamps */ void -key_sa_recordxfer(sav, m) - struct secasvar *sav; - struct mbuf *m; +key_sa_recordxfer(struct secasvar *sav, struct mbuf *m) { IPSEC_ASSERT(sav != NULL, ("Null secasvar")); IPSEC_ASSERT(m != NULL, ("Null mbuf")); - if (!sav->lft_c) - return; /* * XXX Currently, there is a difference of bytes size * between inbound and outbound processing. */ - sav->lft_c->bytes += m->m_pkthdr.len; - /* to check bytes lifetime is done in key_timehandler(). */ + counter_u64_add(sav->lft_c_bytes, m->m_pkthdr.len); /* * We use the number of packets as the unit of * allocations. We increment the variable * whenever {esp,ah}_{in,out}put is called. */ - sav->lft_c->allocations++; - /* XXX check for expires? */ + counter_u64_add(sav->lft_c_allocations, 1); /* * NOTE: We record CURRENT usetime by using wall clock, @@ -7917,92 +8229,8 @@ key_sa_recordxfer(sav, m) * <--------------> HARD * <-----> SOFT */ - sav->lft_c->usetime = time_second; - /* XXX check for expires? */ - - return; -} - -/* dumb version */ -void -key_sa_routechange(dst) - struct sockaddr *dst; -{ - struct secashead *sah; - struct route *ro; - - SAHTREE_LOCK(); - LIST_FOREACH(sah, &V_sahtree, chain) { - ro = &sah->route_cache.sa_route; - if (ro->ro_rt && dst->sa_len == ro->ro_dst.sa_len - && bcmp(dst, &ro->ro_dst, dst->sa_len) == 0) { - RTFREE(ro->ro_rt); - ro->ro_rt = (struct rtentry *)NULL; - } - } - SAHTREE_UNLOCK(); -} - -static void -key_sa_chgstate(struct secasvar *sav, u_int8_t state) -{ - IPSEC_ASSERT(sav != NULL, ("NULL sav")); - SAHTREE_LOCK_ASSERT(); - - if (sav->state != state) { - if (__LIST_CHAINED(sav)) - LIST_REMOVE(sav, chain); - sav->state = state; - LIST_INSERT_HEAD(&sav->sah->savtree[state], sav, chain); - } -} - -void -key_sa_stir_iv(sav) - struct secasvar *sav; -{ - - IPSEC_ASSERT(sav->iv != NULL, ("null IV")); - key_randomfill(sav->iv, sav->ivlen); -} - -/* XXX too much? */ -static struct mbuf * -key_alloc_mbuf(l) - int l; -{ - struct mbuf *m = NULL, *n; - int len, t; - - len = l; - while (len > 0) { - MGET(n, M_DONTWAIT, MT_DATA); - if (n && len > MLEN) - MCLGET(n, M_DONTWAIT); - if (!n) { - m_freem(m); - return NULL; - } - - n->m_next = NULL; - n->m_len = 0; - n->m_len = M_TRAILINGSPACE(n); - /* use the bottom of mbuf, hoping we can prepend afterwards */ - if (n->m_len > len) { - t = (n->m_len - len) & ~(sizeof(long) - 1); - n->m_data += t; - n->m_len = len; - } - - len -= n->m_len; - - if (m) - m_cat(m, n); - else - m = n; - } - - return m; + if (sav->firstused == 0) + sav->firstused = time_second; } /* @@ -8019,7 +8247,7 @@ key_alloc_mbuf(l) */ static struct mbuf * -key_setkey(struct seckey *src, u_int16_t exttype) +key_setkey(struct seckey *src, uint16_t exttype) { struct mbuf *m; struct sadb_key *p; @@ -8029,9 +8257,11 @@ key_setkey(struct seckey *src, u_int16_t exttype) return NULL; len = PFKEY_ALIGN8(sizeof(struct sadb_key) + _KEYLEN(src)); - m = key_alloc_mbuf(len); + m = m_get2(len, M_NOWAIT, MT_DATA, 0); if (m == NULL) return NULL; + m_align(m, len); + m->m_len = len; p = mtod(m, struct sadb_key *); bzero(p, len); p->sadb_key_len = PFKEY_UNIT64(len); @@ -8057,7 +8287,7 @@ key_setkey(struct seckey *src, u_int16_t exttype) */ static struct mbuf * -key_setlifetime(struct seclifetime *src, u_int16_t exttype) +key_setlifetime(struct seclifetime *src, uint16_t exttype) { struct mbuf *m = NULL; struct sadb_lifetime *p; @@ -8066,9 +8296,11 @@ key_setlifetime(struct seclifetime *src, u_int16_t exttype) if (src == NULL) return NULL; - m = key_alloc_mbuf(len); + m = m_get2(len, M_NOWAIT, MT_DATA, 0); if (m == NULL) return m; + m_align(m, len); + m->m_len = len; p = mtod(m, struct sadb_lifetime *); bzero(p, len); @@ -8082,3 +8314,104 @@ key_setlifetime(struct seclifetime *src, u_int16_t exttype) return m; } + +const struct enc_xform * +enc_algorithm_lookup(int alg) +{ + int i; + + for (i = 0; i < nitems(supported_ealgs); i++) + if (alg == supported_ealgs[i].sadb_alg) + return (supported_ealgs[i].xform); + return (NULL); +} + +const struct auth_hash * +auth_algorithm_lookup(int alg) +{ + int i; + + for (i = 0; i < nitems(supported_aalgs); i++) + if (alg == supported_aalgs[i].sadb_alg) + return (supported_aalgs[i].xform); + return (NULL); +} + +const struct comp_algo * +comp_algorithm_lookup(int alg) +{ + int i; + + for (i = 0; i < nitems(supported_calgs); i++) + if (alg == supported_calgs[i].sadb_alg) + return (supported_calgs[i].xform); + return (NULL); +} + +/* + * Register a transform. + */ +static int +xform_register(struct xformsw* xsp) +{ + struct xformsw *entry; + + XFORMS_LOCK(); + LIST_FOREACH(entry, &xforms, chain) { + if (entry->xf_type == xsp->xf_type) { + XFORMS_UNLOCK(); + return (EEXIST); + } + } + LIST_INSERT_HEAD(&xforms, xsp, chain); + XFORMS_UNLOCK(); + return (0); +} + +void +xform_attach(void *data) +{ + struct xformsw *xsp = (struct xformsw *)data; + + if (xform_register(xsp) != 0) + printf("%s: failed to register %s xform\n", __func__, + xsp->xf_name); +} + +void +xform_detach(void *data) +{ + struct xformsw *xsp = (struct xformsw *)data; + + XFORMS_LOCK(); + LIST_REMOVE(xsp, chain); + XFORMS_UNLOCK(); + + /* Delete all SAs related to this xform. */ + key_delete_xform(xsp); +} + +/* + * Initialize transform support in an sav. + */ +static int +xform_init(struct secasvar *sav, u_short xftype) +{ + struct xformsw *entry; + int ret; + + IPSEC_ASSERT(sav->tdb_xform == NULL, + ("tdb_xform is already initialized")); + + ret = EINVAL; + XFORMS_LOCK(); + LIST_FOREACH(entry, &xforms, chain) { + if (entry->xf_type == xftype) { + ret = (*entry->xf_init)(sav, entry); + break; + } + } + XFORMS_UNLOCK(); + return (ret); +} + diff --git a/freebsd/sys/netipsec/key.h b/freebsd/sys/netipsec/key.h index f246dbcf..a646832e 100644 --- a/freebsd/sys/netipsec/key.h +++ b/freebsd/sys/netipsec/key.h @@ -37,7 +37,6 @@ struct secpolicy; struct secpolicyindex; -struct ipsecrequest; struct secasvar; struct sockaddr; struct socket; @@ -46,74 +45,44 @@ struct sadb_x_policy; struct secasindex; union sockaddr_union; -extern void key_addref(struct secpolicy *sp); -extern int key_havesp(u_int dir); -extern struct secpolicy *key_allocsp(struct secpolicyindex *, u_int, - const char*, int); -extern struct secpolicy *key_allocsp2(u_int32_t spi, union sockaddr_union *dst, - u_int8_t proto, u_int dir, const char*, int); -extern struct secpolicy *key_newsp(const char*, int); -#if 0 -extern struct secpolicy *key_gettunnel(const struct sockaddr *, - const struct sockaddr *, const struct sockaddr *, - const struct sockaddr *, const char*, int); -#endif -/* NB: prepend with _ for KAME IPv6 compatbility */ -extern void _key_freesp(struct secpolicy **, const char*, int); - -#define KEY_ALLOCSP(spidx, dir) \ - key_allocsp(spidx, dir, __FILE__, __LINE__) -#define KEY_ALLOCSP2(spi, dst, proto, dir) \ - key_allocsp2(spi, dst, proto, dir, __FILE__, __LINE__) -#define KEY_NEWSP() \ - key_newsp(__FILE__, __LINE__) -#if 0 -#define KEY_GETTUNNEL(osrc, odst, isrc, idst) \ - key_gettunnel(osrc, odst, isrc, idst, __FILE__, __LINE__) -#endif -#define KEY_FREESP(spp) \ - _key_freesp(spp, __FILE__, __LINE__) +struct secpolicy *key_newsp(void); +struct secpolicy *key_allocsp(struct secpolicyindex *, u_int); +struct secpolicy *key_msg2sp(struct sadb_x_policy *, size_t, int *); +int key_sp2msg(struct secpolicy *, void *, size_t *); +void key_addref(struct secpolicy *); +void key_freesp(struct secpolicy **); +int key_spdacquire(struct secpolicy *); +int key_havesp(u_int); +void key_bumpspgen(void); +uint32_t key_getspgen(void); +uint32_t key_newreqid(void); -extern struct secasvar *key_allocsa(union sockaddr_union *, u_int, u_int32_t, - const char*, int); -extern void key_addrefsa(struct secasvar *, const char*, int); -extern void key_freesav(struct secasvar **, const char*, int); +struct secasvar *key_allocsa(union sockaddr_union *, uint8_t, uint32_t); +struct secasvar *key_allocsa_tunnel(union sockaddr_union *, + union sockaddr_union *, uint8_t); +struct secasvar *key_allocsa_policy(struct secpolicy *, + const struct secasindex *, int *); +struct secasvar *key_allocsa_tcpmd5(struct secasindex *); +void key_freesav(struct secasvar **); -#define KEY_ALLOCSA(dst, proto, spi) \ - key_allocsa(dst, proto, spi, __FILE__, __LINE__) -#define KEY_ADDREFSA(sav) \ - key_addrefsa(sav, __FILE__, __LINE__) -#define KEY_FREESAV(psav) \ - key_freesav(psav, __FILE__, __LINE__) +int key_sockaddrcmp(const struct sockaddr *, const struct sockaddr *, int); +int key_sockaddrcmp_withmask(const struct sockaddr *, const struct sockaddr *, + size_t); -extern void key_freeso __P((struct socket *)); -extern int key_checktunnelsanity __P((struct secasvar *, u_int, - caddr_t, caddr_t)); -extern int key_checkrequest - __P((struct ipsecrequest *isr, const struct secasindex *)); +int key_register_ifnet(struct secpolicy **, u_int); +void key_unregister_ifnet(struct secpolicy **, u_int); -extern struct secpolicy *key_msg2sp __P((struct sadb_x_policy *, - size_t, int *)); -extern struct mbuf *key_sp2msg __P((struct secpolicy *)); -extern int key_ismyaddr __P((struct sockaddr *)); -extern int key_spdacquire __P((struct secpolicy *)); -extern void key_timehandler __P((void)); -extern u_long key_random __P((void)); -extern void key_randomfill __P((void *, size_t)); -extern void key_freereg __P((struct socket *)); -extern int key_parse __P((struct mbuf *, struct socket *)); -extern void key_init __P((void)); +extern u_long key_random(void); +extern void key_randomfill(void *, size_t); +extern void key_freereg(struct socket *); +extern int key_parse(struct mbuf *, struct socket *); +extern void key_init(void); #ifdef VIMAGE extern void key_destroy(void); #endif -extern void key_sa_recordxfer __P((struct secasvar *, struct mbuf *)); -extern void key_sa_routechange __P((struct sockaddr *)); -extern void key_sa_stir_iv __P((struct secasvar *)); -#ifdef IPSEC_NAT_T -u_int16_t key_portfromsaddr(struct sockaddr *); -#define KEY_PORTFROMSADDR(saddr) \ - key_portfromsaddr((struct sockaddr *)(saddr)) -#endif +extern void key_sa_recordxfer(struct secasvar *, struct mbuf *); +uint16_t key_portfromsaddr(struct sockaddr *); +void key_porttosaddr(struct sockaddr *, uint16_t port); #ifdef MALLOC_DECLARE MALLOC_DECLARE(M_IPSEC_SA); diff --git a/freebsd/sys/netipsec/key_debug.c b/freebsd/sys/netipsec/key_debug.c index 0b54ffab..51ac1fdc 100644 --- a/freebsd/sys/netipsec/key_debug.c +++ b/freebsd/sys/netipsec/key_debug.c @@ -38,16 +38,17 @@ #include <rtems/bsd/local/opt_ipsec.h> #endif -#include <sys/types.h> #include <rtems/bsd/sys/param.h> #ifdef _KERNEL #include <sys/systm.h> +#include <rtems/bsd/sys/lock.h> +#include <sys/malloc.h> #include <sys/mbuf.h> +#include <sys/mutex.h> #include <sys/queue.h> #endif #include <sys/socket.h> -#include <net/route.h> #include <net/vnet.h> #include <netipsec/key_var.h> @@ -57,6 +58,7 @@ #include <netipsec/ipsec.h> #ifdef _KERNEL #include <netipsec/keydb.h> +#include <netipsec/xform.h> #endif #ifndef _KERNEL @@ -65,17 +67,17 @@ #include <stdlib.h> #endif /* !_KERNEL */ -static void kdebug_sadb_prop __P((struct sadb_ext *)); -static void kdebug_sadb_identity __P((struct sadb_ext *)); -static void kdebug_sadb_supported __P((struct sadb_ext *)); -static void kdebug_sadb_lifetime __P((struct sadb_ext *)); -static void kdebug_sadb_sa __P((struct sadb_ext *)); -static void kdebug_sadb_address __P((struct sadb_ext *)); -static void kdebug_sadb_key __P((struct sadb_ext *)); -static void kdebug_sadb_x_sa2 __P((struct sadb_ext *)); +static void kdebug_sadb_prop(struct sadb_ext *); +static void kdebug_sadb_identity(struct sadb_ext *); +static void kdebug_sadb_supported(struct sadb_ext *); +static void kdebug_sadb_lifetime(struct sadb_ext *); +static void kdebug_sadb_sa(struct sadb_ext *); +static void kdebug_sadb_address(struct sadb_ext *); +static void kdebug_sadb_key(struct sadb_ext *); +static void kdebug_sadb_x_sa2(struct sadb_ext *); #ifdef _KERNEL -static void kdebug_secreplay __P((struct secreplay *)); +static void kdebug_secreplay(struct secreplay *); #endif #ifndef _KERNEL @@ -86,8 +88,7 @@ static void kdebug_secreplay __P((struct secreplay *)); /* %%%: about struct sadb_msg */ void -kdebug_sadb(base) - struct sadb_msg *base; +kdebug_sadb(struct sadb_msg *base) { struct sadb_ext *ext; int tlen, extlen; @@ -175,8 +176,7 @@ kdebug_sadb(base) } static void -kdebug_sadb_prop(ext) - struct sadb_ext *ext; +kdebug_sadb_prop(struct sadb_ext *ext) { struct sadb_prop *prop = (struct sadb_prop *)ext; struct sadb_comb *comb; @@ -225,8 +225,7 @@ kdebug_sadb_prop(ext) } static void -kdebug_sadb_identity(ext) - struct sadb_ext *ext; +kdebug_sadb_identity(struct sadb_ext *ext) { struct sadb_ident *id = (struct sadb_ident *)ext; int len; @@ -268,8 +267,7 @@ kdebug_sadb_identity(ext) } static void -kdebug_sadb_supported(ext) - struct sadb_ext *ext; +kdebug_sadb_supported(struct sadb_ext *ext) { struct sadb_supported *sup = (struct sadb_supported *)ext; struct sadb_alg *alg; @@ -295,8 +293,7 @@ kdebug_sadb_supported(ext) } static void -kdebug_sadb_lifetime(ext) - struct sadb_ext *ext; +kdebug_sadb_lifetime(struct sadb_ext *ext) { struct sadb_lifetime *lft = (struct sadb_lifetime *)ext; @@ -315,8 +312,7 @@ kdebug_sadb_lifetime(ext) } static void -kdebug_sadb_sa(ext) - struct sadb_ext *ext; +kdebug_sadb_sa(struct sadb_ext *ext) { struct sadb_sa *sa = (struct sadb_sa *)ext; @@ -334,8 +330,7 @@ kdebug_sadb_sa(ext) } static void -kdebug_sadb_address(ext) - struct sadb_ext *ext; +kdebug_sadb_address(struct sadb_ext *ext) { struct sadb_address *addr = (struct sadb_address *)ext; @@ -354,8 +349,7 @@ kdebug_sadb_address(ext) } static void -kdebug_sadb_key(ext) - struct sadb_ext *ext; +kdebug_sadb_key(struct sadb_ext *ext) { struct sadb_key *key = (struct sadb_key *)ext; @@ -383,8 +377,7 @@ kdebug_sadb_key(ext) } static void -kdebug_sadb_x_sa2(ext) - struct sadb_ext *ext; +kdebug_sadb_x_sa2(struct sadb_ext *ext) { struct sadb_x_sa2 *sa2 = (struct sadb_x_sa2 *)ext; @@ -402,8 +395,7 @@ kdebug_sadb_x_sa2(ext) } void -kdebug_sadb_x_policy(ext) - struct sadb_ext *ext; +kdebug_sadb_x_policy(struct sadb_ext *ext) { struct sadb_x_policy *xpl = (struct sadb_x_policy *)ext; struct sockaddr *addr; @@ -469,185 +461,304 @@ kdebug_sadb_x_policy(ext) #ifdef _KERNEL /* %%%: about SPD and SAD */ -void -kdebug_secpolicy(sp) - struct secpolicy *sp; +const char* +kdebug_secpolicy_state(u_int state) { - /* sanity check */ - if (sp == NULL) - panic("%s: NULL pointer was passed.\n", __func__); - printf("secpolicy{ refcnt=%u state=%u policy=%u\n", - sp->refcnt, sp->state, sp->policy); + switch (state) { + case IPSEC_SPSTATE_DEAD: + return ("dead"); + case IPSEC_SPSTATE_LARVAL: + return ("larval"); + case IPSEC_SPSTATE_ALIVE: + return ("alive"); + case IPSEC_SPSTATE_PCB: + return ("pcb"); + case IPSEC_SPSTATE_IFNET: + return ("ifnet"); + } + return ("unknown"); +} - kdebug_secpolicyindex(&sp->spidx); +const char* +kdebug_secpolicy_policy(u_int policy) +{ - switch (sp->policy) { + switch (policy) { case IPSEC_POLICY_DISCARD: - printf(" type=discard }\n"); - break; + return ("discard"); case IPSEC_POLICY_NONE: - printf(" type=none }\n"); - break; + return ("none"); case IPSEC_POLICY_IPSEC: - { - struct ipsecrequest *isr; - for (isr = sp->req; isr != NULL; isr = isr->next) { - - printf(" level=%u\n", isr->level); - kdebug_secasindex(&isr->saidx); - - if (isr->sav != NULL) - kdebug_secasv(isr->sav); - } - printf(" }\n"); - } - break; - case IPSEC_POLICY_BYPASS: - printf(" type=bypass }\n"); - break; + return ("ipsec"); case IPSEC_POLICY_ENTRUST: - printf(" type=entrust }\n"); - break; - default: - printf("%s: Invalid policy found. %d\n", __func__, sp->policy); - break; + return ("entrust"); + case IPSEC_POLICY_BYPASS: + return ("bypass"); } - - return; + return ("unknown"); } -void -kdebug_secpolicyindex(spidx) - struct secpolicyindex *spidx; +const char* +kdebug_secpolicyindex_dir(u_int dir) { - /* sanity check */ - if (spidx == NULL) - panic("%s: NULL pointer was passed.\n", __func__); - printf("secpolicyindex{ dir=%u prefs=%u prefd=%u ul_proto=%u\n", - spidx->dir, spidx->prefs, spidx->prefd, spidx->ul_proto); + switch (dir) { + case IPSEC_DIR_ANY: + return ("any"); + case IPSEC_DIR_INBOUND: + return ("in"); + case IPSEC_DIR_OUTBOUND: + return ("out"); + } + return ("unknown"); +} - ipsec_hexdump((caddr_t)&spidx->src, - ((struct sockaddr *)&spidx->src)->sa_len); - printf("\n"); - ipsec_hexdump((caddr_t)&spidx->dst, - ((struct sockaddr *)&spidx->dst)->sa_len); - printf("}\n"); +const char* +kdebug_ipsecrequest_level(u_int level) +{ - return; + switch (level) { + case IPSEC_LEVEL_DEFAULT: + return ("default"); + case IPSEC_LEVEL_USE: + return ("use"); + case IPSEC_LEVEL_REQUIRE: + return ("require"); + case IPSEC_LEVEL_UNIQUE: + return ("unique"); + } + return ("unknown"); } -void -kdebug_secasindex(saidx) - struct secasindex *saidx; +const char* +kdebug_secasindex_mode(u_int mode) { - /* sanity check */ - if (saidx == NULL) - panic("%s: NULL pointer was passed.\n", __func__); - printf("secasindex{ mode=%u proto=%u\n", - saidx->mode, saidx->proto); + switch (mode) { + case IPSEC_MODE_ANY: + return ("any"); + case IPSEC_MODE_TRANSPORT: + return ("transport"); + case IPSEC_MODE_TUNNEL: + return ("tunnel"); + case IPSEC_MODE_TCPMD5: + return ("tcp-md5"); + } + return ("unknown"); +} - ipsec_hexdump((caddr_t)&saidx->src, - ((struct sockaddr *)&saidx->src)->sa_len); - printf("\n"); - ipsec_hexdump((caddr_t)&saidx->dst, - ((struct sockaddr *)&saidx->dst)->sa_len); - printf("\n"); +const char* +kdebug_secasv_state(u_int state) +{ - return; + switch (state) { + case SADB_SASTATE_LARVAL: + return ("larval"); + case SADB_SASTATE_MATURE: + return ("mature"); + case SADB_SASTATE_DYING: + return ("dying"); + case SADB_SASTATE_DEAD: + return ("dead"); + } + return ("unknown"); } -static void -kdebug_sec_lifetime(struct seclifetime *lft) +static char* +kdebug_port2str(const struct sockaddr *sa, char *buf, size_t len) { - /* sanity check */ - if (lft == NULL) - panic("%s: NULL pointer was passed.\n", __func__); - - printf("sec_lifetime{ alloc=%u, bytes=%u\n", - lft->allocations, (u_int32_t)lft->bytes); - printf(" addtime=%u, usetime=%u }\n", - (u_int32_t)lft->addtime, (u_int32_t)lft->usetime); + uint16_t port; - return; + IPSEC_ASSERT(sa != NULL, ("null sa")); + switch (sa->sa_family) { +#ifdef INET + case AF_INET: + port = ntohs(((const struct sockaddr_in *)sa)->sin_port); + break; +#endif +#ifdef INET6 + case AF_INET6: + port = ntohs(((const struct sockaddr_in6 *)sa)->sin6_port); + break; +#endif + default: + port = 0; + } + if (port == 0) + return ("*"); + snprintf(buf, len, "%u", port); + return (buf); } void -kdebug_secasv(sav) - struct secasvar *sav; +kdebug_secpolicy(struct secpolicy *sp) { - /* sanity check */ - if (sav == NULL) - panic("%s: NULL pointer was passed.\n", __func__); + u_int idx; + + IPSEC_ASSERT(sp != NULL, ("null sp")); + printf("SP { refcnt=%u id=%u priority=%u state=%s policy=%s\n", + sp->refcnt, sp->id, sp->priority, + kdebug_secpolicy_state(sp->state), + kdebug_secpolicy_policy(sp->policy)); + kdebug_secpolicyindex(&sp->spidx, " "); + for (idx = 0; idx < sp->tcount; idx++) { + printf(" req[%u]{ level=%s ", idx, + kdebug_ipsecrequest_level(sp->req[idx]->level)); + kdebug_secasindex(&sp->req[idx]->saidx, NULL); + printf(" }\n"); + } + printf("}\n"); +} - printf("secas{"); - kdebug_secasindex(&sav->sah->saidx); +void +kdebug_secpolicyindex(struct secpolicyindex *spidx, const char *indent) +{ + char buf[IPSEC_ADDRSTRLEN]; + + IPSEC_ASSERT(spidx != NULL, ("null spidx")); + if (indent != NULL) + printf("%s", indent); + printf("spidx { dir=%s ul_proto=", + kdebug_secpolicyindex_dir(spidx->dir)); + if (spidx->ul_proto == IPSEC_ULPROTO_ANY) + printf("* "); + else + printf("%u ", spidx->ul_proto); + printf("%s/%u -> ", ipsec_address(&spidx->src, buf, sizeof(buf)), + spidx->prefs); + printf("%s/%u }\n", ipsec_address(&spidx->dst, buf, sizeof(buf)), + spidx->prefd); +} - printf(" refcnt=%u state=%u auth=%u enc=%u\n", - sav->refcnt, sav->state, sav->alg_auth, sav->alg_enc); - printf(" spi=%u flags=%u\n", - (u_int32_t)ntohl(sav->spi), sav->flags); +void +kdebug_secasindex(const struct secasindex *saidx, const char *indent) +{ + char buf[IPSEC_ADDRSTRLEN], port[6]; + + IPSEC_ASSERT(saidx != NULL, ("null saidx")); + if (indent != NULL) + printf("%s", indent); + printf("saidx { mode=%s proto=%u reqid=%u ", + kdebug_secasindex_mode(saidx->mode), saidx->proto, saidx->reqid); + printf("%s:%s -> ", ipsec_address(&saidx->src, buf, sizeof(buf)), + kdebug_port2str(&saidx->src.sa, port, sizeof(port))); + printf("%s:%s }\n", ipsec_address(&saidx->dst, buf, sizeof(buf)), + kdebug_port2str(&saidx->dst.sa, port, sizeof(port))); +} - if (sav->key_auth != NULL) - kdebug_sadb_key((struct sadb_ext *)sav->key_auth); - if (sav->key_enc != NULL) - kdebug_sadb_key((struct sadb_ext *)sav->key_enc); - if (sav->iv != NULL) { - printf(" iv="); - ipsec_hexdump(sav->iv, sav->ivlen ? sav->ivlen : 8); - printf("\n"); - } +static void +kdebug_sec_lifetime(struct seclifetime *lft, const char *indent) +{ - if (sav->replay != NULL) - kdebug_secreplay(sav->replay); - if (sav->lft_c != NULL) - kdebug_sec_lifetime(sav->lft_c); - if (sav->lft_h != NULL) - kdebug_sec_lifetime(sav->lft_h); - if (sav->lft_s != NULL) - kdebug_sec_lifetime(sav->lft_s); + IPSEC_ASSERT(lft != NULL, ("null lft")); + if (indent != NULL) + printf("%s", indent); + printf("lifetime { alloc=%u, bytes=%ju addtime=%ju usetime=%ju }\n", + lft->allocations, (uintmax_t)lft->bytes, (uintmax_t)lft->addtime, + (uintmax_t)lft->usetime); +} -#ifdef notyet - /* XXX: misc[123] ? */ -#endif +void +kdebug_secash(struct secashead *sah, const char *indent) +{ - return; + IPSEC_ASSERT(sah != NULL, ("null sah")); + if (indent != NULL) + printf("%s", indent); + printf("SAH { refcnt=%u state=%s\n", sah->refcnt, + kdebug_secasv_state(sah->state)); + if (indent != NULL) + printf("%s", indent); + kdebug_secasindex(&sah->saidx, indent); + if (indent != NULL) + printf("%s", indent); + printf("}\n"); } static void -kdebug_secreplay(rpl) - struct secreplay *rpl; +kdebug_secreplay(struct secreplay *rpl) { int len, l; - /* sanity check */ - if (rpl == NULL) - panic("%s: NULL pointer was passed.\n", __func__); - - printf(" secreplay{ count=%u wsize=%u seq=%u lastseq=%u", - rpl->count, rpl->wsize, rpl->seq, rpl->lastseq); + IPSEC_ASSERT(rpl != NULL, ("null rpl")); + printf(" secreplay{ count=%u bitmap_size=%u wsize=%u seq=%u lastseq=%u", + rpl->count, rpl->bitmap_size, rpl->wsize, rpl->seq, rpl->lastseq); if (rpl->bitmap == NULL) { - printf(" }\n"); + printf(" }\n"); return; } - printf("\n bitmap { "); - - for (len = 0; len < rpl->wsize; len++) { + printf("\n bitmap { "); + for (len = 0; len < rpl->bitmap_size*4; len++) { for (l = 7; l >= 0; l--) printf("%u", (((rpl->bitmap)[len] >> l) & 1) ? 1 : 0); } - printf(" }\n"); + printf(" }\n"); +} - return; +static void +kdebug_secnatt(struct secnatt *natt) +{ + char buf[IPSEC_ADDRSTRLEN]; + + IPSEC_ASSERT(natt != NULL, ("null natt")); + printf(" natt{ sport=%u dport=%u ", ntohs(natt->sport), + ntohs(natt->dport)); + if (natt->flags & IPSEC_NATT_F_OAI) + printf("oai=%s ", ipsec_address(&natt->oai, buf, sizeof(buf))); + if (natt->flags & IPSEC_NATT_F_OAR) + printf("oar=%s ", ipsec_address(&natt->oar, buf, sizeof(buf))); + printf("}\n"); +} + +void +kdebug_secasv(struct secasvar *sav) +{ + struct seclifetime lft_c; + + IPSEC_ASSERT(sav != NULL, ("null sav")); + + printf("SA { refcnt=%u spi=%u seq=%u pid=%u flags=0x%x state=%s\n", + sav->refcnt, ntohl(sav->spi), sav->seq, (uint32_t)sav->pid, + sav->flags, kdebug_secasv_state(sav->state)); + kdebug_secash(sav->sah, " "); + + lft_c.addtime = sav->created; + lft_c.allocations = (uint32_t)counter_u64_fetch( + sav->lft_c_allocations); + lft_c.bytes = counter_u64_fetch(sav->lft_c_bytes); + lft_c.usetime = sav->firstused; + kdebug_sec_lifetime(&lft_c, " c_"); + if (sav->lft_h != NULL) + kdebug_sec_lifetime(sav->lft_h, " h_"); + if (sav->lft_s != NULL) + kdebug_sec_lifetime(sav->lft_s, " s_"); + + if (sav->tdb_authalgxform != NULL) + printf(" alg_auth=%s\n", sav->tdb_authalgxform->name); + if (sav->key_auth != NULL) + KEYDBG(DUMP, + kdebug_sadb_key((struct sadb_ext *)sav->key_auth)); + if (sav->tdb_encalgxform != NULL) + printf(" alg_enc=%s\n", sav->tdb_encalgxform->name); + if (sav->key_enc != NULL) + KEYDBG(DUMP, + kdebug_sadb_key((struct sadb_ext *)sav->key_enc)); + if (sav->natt != NULL) + kdebug_secnatt(sav->natt); + if (sav->replay != NULL) { + KEYDBG(DUMP, + SECASVAR_LOCK(sav); + kdebug_secreplay(sav->replay); + SECASVAR_UNLOCK(sav)); + } + printf("}\n"); } void -kdebug_mbufhdr(m) - struct mbuf *m; +kdebug_mbufhdr(const struct mbuf *m) { /* sanity check */ if (m == NULL) @@ -665,19 +776,18 @@ kdebug_mbufhdr(m) if (m->m_flags & M_EXT) { printf(" m_ext{ ext_buf:%p ext_free:%p " - "ext_size:%u ref_cnt:%p }\n", + "ext_size:%u ext_cnt:%p }\n", m->m_ext.ext_buf, m->m_ext.ext_free, - m->m_ext.ext_size, m->m_ext.ref_cnt); + m->m_ext.ext_size, m->m_ext.ext_cnt); } return; } void -kdebug_mbuf(m0) - struct mbuf *m0; +kdebug_mbuf(const struct mbuf *m0) { - struct mbuf *m = m0; + const struct mbuf *m = m0; int i, j; for (j = 0; m; m = m->m_next) { @@ -688,7 +798,7 @@ kdebug_mbuf(m0) printf("\n"); if (i % 4 == 0) printf(" "); - printf("%02x", mtod(m, u_char *)[i]); + printf("%02x", mtod(m, const u_char *)[i]); j++; } printf("\n"); @@ -696,11 +806,51 @@ kdebug_mbuf(m0) return; } + +/* Return a printable string for the address. */ +char * +ipsec_address(const union sockaddr_union* sa, char *buf, socklen_t size) +{ + + switch (sa->sa.sa_family) { +#ifdef INET + case AF_INET: + return (inet_ntop(AF_INET, &sa->sin.sin_addr, buf, size)); +#endif /* INET */ +#ifdef INET6 + case AF_INET6: + if (IN6_IS_SCOPE_LINKLOCAL(&sa->sin6.sin6_addr)) { + snprintf(buf, size, "%s%%%u", inet_ntop(AF_INET6, + &sa->sin6.sin6_addr, buf, size), + sa->sin6.sin6_scope_id); + return (buf); + } else + return (inet_ntop(AF_INET6, &sa->sin6.sin6_addr, + buf, size)); +#endif /* INET6 */ + case 0: + return ("*"); + default: + return ("(unknown address family)"); + } +} + +char * +ipsec_sa2str(struct secasvar *sav, char *buf, size_t size) +{ + char sbuf[IPSEC_ADDRSTRLEN], dbuf[IPSEC_ADDRSTRLEN]; + + snprintf(buf, size, "SA(SPI=%08lx src=%s dst=%s)", + (u_long)ntohl(sav->spi), + ipsec_address(&sav->sah->saidx.src, sbuf, sizeof(sbuf)), + ipsec_address(&sav->sah->saidx.dst, dbuf, sizeof(dbuf))); + return (buf); +} + #endif /* _KERNEL */ void -kdebug_sockaddr(addr) - struct sockaddr *addr; +kdebug_sockaddr(struct sockaddr *addr) { struct sockaddr_in *sin4; #ifdef INET6 @@ -738,9 +888,7 @@ kdebug_sockaddr(addr) } void -ipsec_bindump(buf, len) - caddr_t buf; - int len; +ipsec_bindump(caddr_t buf, int len) { int i; @@ -752,9 +900,7 @@ ipsec_bindump(buf, len) void -ipsec_hexdump(buf, len) - caddr_t buf; - int len; +ipsec_hexdump(caddr_t buf, int len) { int i; diff --git a/freebsd/sys/netipsec/key_debug.h b/freebsd/sys/netipsec/key_debug.h index 1a3782b1..18150b53 100644 --- a/freebsd/sys/netipsec/key_debug.h +++ b/freebsd/sys/netipsec/key_debug.h @@ -53,37 +53,50 @@ #define KEYDEBUG_IPSEC_DATA (KEYDEBUG_IPSEC | KEYDEBUG_DATA) #define KEYDEBUG_IPSEC_DUMP (KEYDEBUG_IPSEC | KEYDEBUG_DUMP) -#define KEYDEBUG(lev,arg) \ - do { if ((V_key_debug_level & (lev)) == (lev)) { arg; } } while (0) +#define KEYDBG(lev, arg) \ + if ((V_key_debug_level & (KEYDEBUG_ ## lev)) == (KEYDEBUG_ ## lev)) { \ + arg; \ + } -VNET_DECLARE(u_int32_t, key_debug_level); +VNET_DECLARE(uint32_t, key_debug_level); #define V_key_debug_level VNET(key_debug_level) #endif /*_KERNEL*/ struct sadb_msg; struct sadb_ext; -extern void kdebug_sadb __P((struct sadb_msg *)); -extern void kdebug_sadb_x_policy __P((struct sadb_ext *)); +extern void kdebug_sadb(struct sadb_msg *); +extern void kdebug_sadb_x_policy(struct sadb_ext *); #ifdef _KERNEL struct secpolicy; struct secpolicyindex; struct secasindex; +struct secashead; struct secasvar; struct secreplay; struct mbuf; -extern void kdebug_secpolicy __P((struct secpolicy *)); -extern void kdebug_secpolicyindex __P((struct secpolicyindex *)); -extern void kdebug_secasindex __P((struct secasindex *)); -extern void kdebug_secasv __P((struct secasvar *)); -extern void kdebug_mbufhdr __P((struct mbuf *)); -extern void kdebug_mbuf __P((struct mbuf *)); +union sockaddr_union; +const char* kdebug_secpolicy_state(u_int); +const char* kdebug_secpolicy_policy(u_int); +const char* kdebug_secpolicyindex_dir(u_int); +const char* kdebug_ipsecrequest_level(u_int); +const char* kdebug_secasindex_mode(u_int); +const char* kdebug_secasv_state(u_int); +void kdebug_secpolicy(struct secpolicy *); +void kdebug_secpolicyindex(struct secpolicyindex *, const char *); +void kdebug_secasindex(const struct secasindex *, const char *); +void kdebug_secash(struct secashead *, const char *); +void kdebug_secasv(struct secasvar *); +void kdebug_mbufhdr(const struct mbuf *); +void kdebug_mbuf(const struct mbuf *); +char *ipsec_address(const union sockaddr_union *, char *, socklen_t); +char *ipsec_sa2str(struct secasvar *, char *, size_t); #endif /*_KERNEL*/ struct sockaddr; -extern void kdebug_sockaddr __P((struct sockaddr *)); +extern void kdebug_sockaddr(struct sockaddr *); -extern void ipsec_hexdump __P((caddr_t, int)); -extern void ipsec_bindump __P((caddr_t, int)); +extern void ipsec_hexdump(caddr_t, int); +extern void ipsec_bindump(caddr_t, int); #endif /* _NETIPSEC_KEY_DEBUG_H_ */ diff --git a/freebsd/sys/netipsec/key_var.h b/freebsd/sys/netipsec/key_var.h index edf232d8..ecef2360 100644 --- a/freebsd/sys/netipsec/key_var.h +++ b/freebsd/sys/netipsec/key_var.h @@ -46,23 +46,6 @@ #define KEYCTL_ESP_AUTH 10 #define KEYCTL_AH_KEYMIN 11 #define KEYCTL_PREFERED_OLDSA 12 -#define KEYCTL_MAXID 13 - -#define KEYCTL_NAMES { \ - { 0, 0 }, \ - { "debug", CTLTYPE_INT }, \ - { "spi_try", CTLTYPE_INT }, \ - { "spi_min_value", CTLTYPE_INT }, \ - { "spi_max_value", CTLTYPE_INT }, \ - { "random_int", CTLTYPE_INT }, \ - { "larval_lifetime", CTLTYPE_INT }, \ - { "blockacq_count", CTLTYPE_INT }, \ - { "blockacq_lifetime", CTLTYPE_INT }, \ - { "esp_keymin", CTLTYPE_INT }, \ - { "esp_auth", CTLTYPE_INT }, \ - { "ah_keymin", CTLTYPE_INT }, \ - { "prefered_oldsa", CTLTYPE_INT }, \ -} #ifdef _KERNEL #define _ARRAYLEN(p) (sizeof(p)/sizeof(p[0])) diff --git a/freebsd/sys/netipsec/keydb.h b/freebsd/sys/netipsec/keydb.h index 7494f5f4..e3650146 100644 --- a/freebsd/sys/netipsec/keydb.h +++ b/freebsd/sys/netipsec/keydb.h @@ -34,6 +34,9 @@ #define _NETIPSEC_KEYDB_H_ #ifdef _KERNEL +#include <sys/counter.h> +#include <rtems/bsd/sys/lock.h> +#include <sys/mutex.h> #include <netipsec/key_var.h> @@ -54,9 +57,9 @@ union sockaddr_union { struct secasindex { union sockaddr_union src; /* source address for SA */ union sockaddr_union dst; /* destination address for SA */ - u_int16_t proto; /* IPPROTO_ESP or IPPROTO_AH */ - u_int8_t mode; /* mode of protocol, see ipsec.h */ - u_int32_t reqid; /* reqid id who owned this SA */ + uint8_t proto; /* IPPROTO_ESP or IPPROTO_AH */ + uint8_t mode; /* mode of protocol, see ipsec.h */ + uint32_t reqid; /* reqid id who owned this SA */ /* see IPSEC_MANUAL_REQID_MAX. */ }; @@ -85,15 +88,23 @@ struct seclifetime { u_int64_t usetime; }; -union sa_route_union { - struct route sa_route; - struct route sin_route; /* Duplicate for consistency. */ - struct route_in6 sin6_route; +struct secnatt { + union sockaddr_union oai; /* original addresses of initiator */ + union sockaddr_union oar; /* original address of responder */ + uint16_t sport; /* source port */ + uint16_t dport; /* destination port */ + uint16_t cksum; /* checksum delta */ + uint16_t flags; +#define IPSEC_NATT_F_OAI 0x0001 +#define IPSEC_NATT_F_OAR 0x0002 }; /* Security Association Data Base */ +TAILQ_HEAD(secasvar_queue, secasvar); struct secashead { - LIST_ENTRY(secashead) chain; + TAILQ_ENTRY(secashead) chain; + LIST_ENTRY(secashead) addrhash; /* hash by sproto+src+dst addresses */ + LIST_ENTRY(secashead) drainq; /* used ONLY by flush callout */ struct secasindex saidx; @@ -101,12 +112,10 @@ struct secashead { struct secident *identd; /* destination identity */ /* XXX I don't know how to use them. */ - u_int8_t state; /* MATURE or DEAD. */ - LIST_HEAD(_satree, secasvar) savtree[SADB_SASTATE_MAX+1]; - /* SA chain */ - /* The first of this list is newer SA */ - - union sa_route_union route_cache; + volatile u_int refcnt; /* reference count */ + uint8_t state; /* MATURE or DEAD. */ + struct secasvar_queue savtree_alive; /* MATURE and DYING SA */ + struct secasvar_queue savtree_larval; /* LARVAL SA */ }; struct xformsw; @@ -114,72 +123,89 @@ struct enc_xform; struct auth_hash; struct comp_algo; -/* Security Association */ +/* + * Security Association + * + * For INBOUND packets we do SA lookup using SPI, thus only SPIHASH is used. + * For OUTBOUND packets there may be several SA suitable for packet. + * We use key_preferred_oldsa variable to choose better SA. First of we do + * lookup for suitable SAH using packet's saidx. Then we use SAH's savtree + * to search better candidate. The newer SA (by created time) are placed + * in the beginning of the savtree list. There is no preference between + * DYING and MATURE. + * + * NB: Fields with a tdb_ prefix are part of the "glue" used + * to interface to the OpenBSD crypto support. This was done + * to distinguish this code from the mainline KAME code. + * NB: Fields are sorted on the basis of the frequency of changes, i.e. + * constants and unchangeable fields are going first. + * NB: if you want to change this structure, check that this will not break + * key_updateaddresses(). + */ struct secasvar { - LIST_ENTRY(secasvar) chain; - struct mtx lock; /* update/access lock */ - - u_int refcnt; /* reference count */ - u_int8_t state; /* Status of this Association */ - - u_int8_t alg_auth; /* Authentication Algorithm Identifier*/ - u_int8_t alg_enc; /* Cipher Algorithm Identifier */ - u_int8_t alg_comp; /* Compression Algorithm Identifier */ - u_int32_t spi; /* SPI Value, network byte order */ - u_int32_t flags; /* holder for SADB_KEY_FLAGS */ + uint32_t spi; /* SPI Value, network byte order */ + uint32_t flags; /* holder for SADB_KEY_FLAGS */ + uint32_t seq; /* sequence number */ + pid_t pid; /* message's pid */ + u_int ivlen; /* length of IV */ + struct secashead *sah; /* back pointer to the secashead */ struct seckey *key_auth; /* Key for Authentication */ struct seckey *key_enc; /* Key for Encryption */ - caddr_t iv; /* Initilization Vector */ - u_int ivlen; /* length of IV */ - void *sched; /* intermediate encryption key */ - size_t schedlen; - struct secreplay *replay; /* replay prevention */ - time_t created; /* for lifetime */ - - struct seclifetime *lft_c; /* CURRENT lifetime, it's constant. */ + struct secnatt *natt; /* NAT-T config */ + struct mtx *lock; /* update/access lock */ + + const struct xformsw *tdb_xform; /* transform */ + const struct enc_xform *tdb_encalgxform;/* encoding algorithm */ + const struct auth_hash *tdb_authalgxform;/* authentication algorithm */ + const struct comp_algo *tdb_compalgxform;/* compression algorithm */ + uint64_t tdb_cryptoid; /* crypto session id */ + + uint8_t alg_auth; /* Authentication Algorithm Identifier*/ + uint8_t alg_enc; /* Cipher Algorithm Identifier */ + uint8_t alg_comp; /* Compression Algorithm Identifier */ + uint8_t state; /* Status of this SA (pfkeyv2.h) */ + + counter_u64_t lft_c; /* CURRENT lifetime */ +#define lft_c_allocations lft_c +#define lft_c_bytes lft_c + 1 struct seclifetime *lft_h; /* HARD lifetime */ struct seclifetime *lft_s; /* SOFT lifetime */ - u_int32_t seq; /* sequence number */ - pid_t pid; /* message's pid */ + uint64_t created; /* time when SA was created */ + uint64_t firstused; /* time when SA was first used */ - struct secashead *sah; /* back pointer to the secashead */ + TAILQ_ENTRY(secasvar) chain; + LIST_ENTRY(secasvar) spihash; + LIST_ENTRY(secasvar) drainq; /* used ONLY by flush callout */ - /* - * NB: Fields with a tdb_ prefix are part of the "glue" used - * to interface to the OpenBSD crypto support. This was done - * to distinguish this code from the mainline KAME code. - */ - struct xformsw *tdb_xform; /* transform */ - struct enc_xform *tdb_encalgxform; /* encoding algorithm */ - struct auth_hash *tdb_authalgxform; /* authentication algorithm */ - struct comp_algo *tdb_compalgxform; /* compression algorithm */ - u_int64_t tdb_cryptoid; /* crypto session id */ - - /* - * NAT-Traversal. - */ - u_int16_t natt_type; /* IKE/ESP-marker in output. */ - u_int16_t natt_esp_frag_len; /* MTU for payload fragmentation. */ + uint64_t cntr; /* counter for GCM and CTR */ + volatile u_int refcnt; /* reference count */ }; -#define SECASVAR_LOCK_INIT(_sav) \ - mtx_init(&(_sav)->lock, "ipsec association", NULL, MTX_DEF) -#define SECASVAR_LOCK(_sav) mtx_lock(&(_sav)->lock) -#define SECASVAR_UNLOCK(_sav) mtx_unlock(&(_sav)->lock) -#define SECASVAR_LOCK_DESTROY(_sav) mtx_destroy(&(_sav)->lock) -#define SECASVAR_LOCK_ASSERT(_sav) mtx_assert(&(_sav)->lock, MA_OWNED) - -/* replay prevention */ +#define SECASVAR_LOCK(_sav) mtx_lock((_sav)->lock) +#define SECASVAR_UNLOCK(_sav) mtx_unlock((_sav)->lock) +#define SECASVAR_LOCK_ASSERT(_sav) mtx_assert((_sav)->lock, MA_OWNED) +#define SAV_ISGCM(_sav) \ + ((_sav)->alg_enc == SADB_X_EALG_AESGCM8 || \ + (_sav)->alg_enc == SADB_X_EALG_AESGCM12 || \ + (_sav)->alg_enc == SADB_X_EALG_AESGCM16) +#define SAV_ISCTR(_sav) ((_sav)->alg_enc == SADB_X_EALG_AESCTR) +#define SAV_ISCTRORGCM(_sav) (SAV_ISCTR((_sav)) || SAV_ISGCM((_sav))) + +/* Replay prevention, protected by SECASVAR_LOCK: + * (m) locked by mtx + * (c) read only except during creation / free + */ struct secreplay { - u_int32_t count; - u_int wsize; /* window size, i.g. 4 bytes */ - u_int32_t seq; /* used by sender */ - u_int32_t lastseq; /* used by receiver */ - caddr_t bitmap; /* used by receiver */ - int overflow; /* overflow flag */ + u_int32_t count; /* (m) */ + u_int wsize; /* (c) window size, i.g. 4 bytes */ + u_int32_t seq; /* (m) used by sender */ + u_int32_t lastseq; /* (m) used by receiver */ + u_int32_t *bitmap; /* (m) used by receiver */ + u_int bitmap_size; /* (c) size of the bitmap array */ + int overflow; /* (m) overflow flag */ }; /* socket table due to send PF_KEY messages. */ @@ -192,36 +218,15 @@ struct secreg { /* acquiring list table. */ struct secacq { LIST_ENTRY(secacq) chain; + LIST_ENTRY(secacq) addrhash; + LIST_ENTRY(secacq) seqhash; struct secasindex saidx; - - u_int32_t seq; /* sequence number */ + uint32_t seq; /* sequence number */ time_t created; /* for lifetime */ int count; /* for lifetime */ }; -/* Sensitivity Level Specification */ -/* nothing */ - -#define SADB_KILL_INTERVAL 600 /* six seconds */ - -/* secpolicy */ -extern struct secpolicy *keydb_newsecpolicy __P((void)); -extern void keydb_delsecpolicy __P((struct secpolicy *)); -/* secashead */ -extern struct secashead *keydb_newsecashead __P((void)); -extern void keydb_delsecashead __P((struct secashead *)); -/* secasvar */ -extern struct secasvar *keydb_newsecasvar __P((void)); -extern void keydb_refsecasvar __P((struct secasvar *)); -extern void keydb_freesecasvar __P((struct secasvar *)); -/* secreplay */ -extern struct secreplay *keydb_newsecreplay __P((size_t)); -extern void keydb_delsecreplay __P((struct secreplay *)); -/* secreg */ -extern struct secreg *keydb_newsecreg __P((void)); -extern void keydb_delsecreg __P((struct secreg *)); - #endif /* _KERNEL */ #endif /* _NETIPSEC_KEYDB_H_ */ diff --git a/freebsd/sys/netipsec/keysock.c b/freebsd/sys/netipsec/keysock.c index 29e23bf3..21430347 100644 --- a/freebsd/sys/netipsec/keysock.c +++ b/freebsd/sys/netipsec/keysock.c @@ -54,9 +54,8 @@ #include <sys/systm.h> #include <net/if.h> -#include <net/raw_cb.h> -#include <net/route.h> #include <net/vnet.h> +#include <net/raw_cb.h> #include <netinet/in.h> @@ -77,20 +76,25 @@ static VNET_DEFINE(struct key_cb, key_cb); static struct sockaddr key_src = { 2, PF_KEY, }; -static int key_sendup0 __P((struct rawcb *, struct mbuf *, int)); +static int key_sendup0(struct rawcb *, struct mbuf *, int); + +VNET_PCPUSTAT_DEFINE(struct pfkeystat, pfkeystat); +VNET_PCPUSTAT_SYSINIT(pfkeystat); -VNET_DEFINE(struct pfkeystat, pfkeystat); +#ifdef VIMAGE +VNET_PCPUSTAT_SYSUNINIT(pfkeystat); +#endif /* VIMAGE */ /* * key_output() */ int -key_output(struct mbuf *m, struct socket *so) +key_output(struct mbuf *m, struct socket *so, ...) { struct sadb_msg *msg; int len, error = 0; - if (m == 0) + if (m == NULL) panic("%s: NULL pointer was passed.\n", __func__); PFKEYSTAT_INC(out_total); @@ -104,7 +108,7 @@ key_output(struct mbuf *m, struct socket *so) } if (m->m_len < sizeof(struct sadb_msg)) { - if ((m = m_pullup(m, sizeof(struct sadb_msg))) == 0) { + if ((m = m_pullup(m, sizeof(struct sadb_msg))) == NULL) { PFKEYSTAT_INC(out_nomem); error = ENOBUFS; goto end; @@ -113,7 +117,7 @@ key_output(struct mbuf *m, struct socket *so) M_ASSERTPKTHDR(m); - KEYDEBUG(KEYDEBUG_KEY_DUMP, kdebug_mbuf(m)); + KEYDBG(KEY_DUMP, kdebug_mbuf(m)); msg = mtod(m, struct sadb_msg *); PFKEYSTAT_INC(out_msgtype[msg->sadb_msg_type]); @@ -135,26 +139,18 @@ end: * send message to the socket. */ static int -key_sendup0(rp, m, promisc) - struct rawcb *rp; - struct mbuf *m; - int promisc; +key_sendup0(struct rawcb *rp, struct mbuf *m, int promisc) { int error; if (promisc) { struct sadb_msg *pmsg; - M_PREPEND(m, sizeof(struct sadb_msg), M_DONTWAIT); - if (m && m->m_len < sizeof(struct sadb_msg)) - m = m_pullup(m, sizeof(struct sadb_msg)); - if (!m) { + M_PREPEND(m, sizeof(struct sadb_msg), M_NOWAIT); + if (m == NULL) { PFKEYSTAT_INC(in_nomem); - m_freem(m); - return ENOBUFS; + return (ENOBUFS); } - m->m_pkthdr.len += sizeof(*pmsg); - pmsg = mtod(m, struct sadb_msg *); bzero(pmsg, sizeof(*pmsg)); pmsg->sadb_msg_version = PF_KEY_V2; @@ -178,22 +174,18 @@ key_sendup0(rp, m, promisc) /* XXX this interface should be obsoleted. */ int -key_sendup(so, msg, len, target) - struct socket *so; - struct sadb_msg *msg; - u_int len; - int target; /*target of the resulting message*/ +key_sendup(struct socket *so, struct sadb_msg *msg, u_int len, int target) { struct mbuf *m, *n, *mprev; int tlen; /* sanity check */ - if (so == 0 || msg == 0) + if (so == NULL || msg == NULL) panic("%s: NULL pointer was passed.\n", __func__); - KEYDEBUG(KEYDEBUG_KEY_DUMP, - printf("%s: \n", __func__); - kdebug_sadb(msg)); + KEYDBG(KEY_DUMP, + printf("%s: \n", __func__); + kdebug_sadb(msg)); /* * we increment statistics here, just in case we have ENOBUFS @@ -216,14 +208,14 @@ key_sendup(so, msg, len, target) m = mprev = NULL; while (tlen > 0) { if (tlen == len) { - MGETHDR(n, M_DONTWAIT, MT_DATA); + MGETHDR(n, M_NOWAIT, MT_DATA); if (n == NULL) { PFKEYSTAT_INC(in_nomem); return ENOBUFS; } n->m_len = MHLEN; } else { - MGET(n, M_DONTWAIT, MT_DATA); + MGET(n, M_NOWAIT, MT_DATA); if (n == NULL) { PFKEYSTAT_INC(in_nomem); return ENOBUFS; @@ -231,8 +223,7 @@ key_sendup(so, msg, len, target) n->m_len = MLEN; } if (tlen >= MCLBYTES) { /*XXX better threshold? */ - MCLGET(n, M_DONTWAIT); - if ((n->m_flags & M_EXT) == 0) { + if (!(MCLGET(n, M_NOWAIT))) { m_free(n); m_freem(m); PFKEYSTAT_INC(in_nomem); @@ -267,10 +258,7 @@ key_sendup(so, msg, len, target) /* so can be NULL if target != KEY_SENDUP_ONE */ int -key_sendup_mbuf(so, m, target) - struct socket *so; - struct mbuf *m; - int target; +key_sendup_mbuf(struct socket *so, struct mbuf *m, int target) { struct mbuf *n; struct keycb *kp; @@ -315,7 +303,7 @@ key_sendup_mbuf(so, m, target) * (based on pf_key@inner.net message on 14 Oct 1998) */ if (((struct keycb *)rp)->kp_promisc) { - if ((n = m_copy(m, 0, (int)M_COPYALL)) != NULL) { + if ((n = m_copym(m, 0, M_COPYALL, M_NOWAIT)) != NULL) { (void)key_sendup0(rp, n, 1); n = NULL; } @@ -345,7 +333,7 @@ key_sendup_mbuf(so, m, target) if (!sendup) continue; - if ((n = m_copy(m, 0, (int)M_COPYALL)) == NULL) { + if ((n = m_copym(m, 0, M_COPYALL, M_NOWAIT)) == NULL) { m_freem(m); PFKEYSTAT_INC(in_nomem); mtx_unlock(&rawcb_mtx); @@ -402,7 +390,7 @@ key_attach(struct socket *so, int proto, struct thread *td) /* XXX */ kp = malloc(sizeof *kp, M_PCB, M_WAITOK | M_ZERO); - if (kp == 0) + if (kp == NULL) return ENOBUFS; so->so_pcb = (caddr_t)kp; @@ -578,7 +566,7 @@ struct domain keydomain = { .dom_destroy = key_destroy, #endif .dom_protosw = keysw, - .dom_protoswNPROTOSW = &keysw[sizeof(keysw)/sizeof(keysw[0])] + .dom_protoswNPROTOSW = &keysw[nitems(keysw)] }; VNET_DOMAIN_SET(key); diff --git a/freebsd/sys/netipsec/keysock.h b/freebsd/sys/netipsec/keysock.h index 6039dbba..8fbf4a02 100644 --- a/freebsd/sys/netipsec/keysock.h +++ b/freebsd/sys/netipsec/keysock.h @@ -36,26 +36,26 @@ /* statistics for pfkey socket */ struct pfkeystat { /* kernel -> userland */ - u_quad_t out_total; /* # of total calls */ - u_quad_t out_bytes; /* total bytecount */ - u_quad_t out_msgtype[256]; /* message type histogram */ - u_quad_t out_invlen; /* invalid length field */ - u_quad_t out_invver; /* invalid version field */ - u_quad_t out_invmsgtype; /* invalid message type field */ - u_quad_t out_tooshort; /* msg too short */ - u_quad_t out_nomem; /* memory allocation failure */ - u_quad_t out_dupext; /* duplicate extension */ - u_quad_t out_invexttype; /* invalid extension type */ - u_quad_t out_invsatype; /* invalid sa type */ - u_quad_t out_invaddr; /* invalid address extension */ + uint64_t out_total; /* # of total calls */ + uint64_t out_bytes; /* total bytecount */ + uint64_t out_msgtype[256]; /* message type histogram */ + uint64_t out_invlen; /* invalid length field */ + uint64_t out_invver; /* invalid version field */ + uint64_t out_invmsgtype; /* invalid message type field */ + uint64_t out_tooshort; /* msg too short */ + uint64_t out_nomem; /* memory allocation failure */ + uint64_t out_dupext; /* duplicate extension */ + uint64_t out_invexttype; /* invalid extension type */ + uint64_t out_invsatype; /* invalid sa type */ + uint64_t out_invaddr; /* invalid address extension */ /* userland -> kernel */ - u_quad_t in_total; /* # of total calls */ - u_quad_t in_bytes; /* total bytecount */ - u_quad_t in_msgtype[256]; /* message type histogram */ - u_quad_t in_msgtarget[3]; /* one/all/registered */ - u_quad_t in_nomem; /* memory allocation failure */ + uint64_t in_total; /* # of total calls */ + uint64_t in_bytes; /* total bytecount */ + uint64_t in_msgtype[256]; /* message type histogram */ + uint64_t in_msgtarget[3]; /* one/all/registered */ + uint64_t in_nomem; /* memory allocation failure */ /* others */ - u_quad_t sockerr; /* # of socket related errors */ + uint64_t sockerr; /* # of socket related errors */ }; #define KEY_SENDUP_ONE 0 @@ -63,23 +63,25 @@ struct pfkeystat { #define KEY_SENDUP_REGISTERED 2 #ifdef _KERNEL +#include <sys/counter.h> + struct keycb { struct rawcb kp_raw; /* rawcb */ int kp_promisc; /* promiscuous mode */ int kp_registered; /* registered socket */ }; -VNET_DECLARE(struct pfkeystat, pfkeystat); -#define PFKEYSTAT_ADD(name, val) V_pfkeystat.name += (val) +VNET_PCPUSTAT_DECLARE(struct pfkeystat, pfkeystat); +#define PFKEYSTAT_ADD(name, val) \ + VNET_PCPUSTAT_ADD(struct pfkeystat, pfkeystat, name, (val)) #define PFKEYSTAT_INC(name) PFKEYSTAT_ADD(name, 1) -#define V_pfkeystat VNET(pfkeystat) -extern int key_output(struct mbuf *m, struct socket *so); -extern int key_usrreq __P((struct socket *, - int, struct mbuf *, struct mbuf *, struct mbuf *)); +extern int key_output(struct mbuf *m, struct socket *so, ...); +extern int key_usrreq(struct socket *, int, struct mbuf *, + struct mbuf *, struct mbuf *); -extern int key_sendup __P((struct socket *, struct sadb_msg *, u_int, int)); -extern int key_sendup_mbuf __P((struct socket *, struct mbuf *, int)); +extern int key_sendup(struct socket *, struct sadb_msg *, u_int, int); +extern int key_sendup_mbuf(struct socket *, struct mbuf *, int); #endif /* _KERNEL */ #endif /*_NETIPSEC_KEYSOCK_H_*/ diff --git a/freebsd/sys/netipsec/subr_ipsec.c b/freebsd/sys/netipsec/subr_ipsec.c new file mode 100644 index 00000000..ff830564 --- /dev/null +++ b/freebsd/sys/netipsec/subr_ipsec.c @@ -0,0 +1,356 @@ +#include <machine/rtems-bsd-kernel-space.h> + +/*- + * Copyright (c) 2016 Andrey V. Elsukov <ae@FreeBSD.org> + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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 <rtems/bsd/local/opt_inet.h> +#include <rtems/bsd/local/opt_inet6.h> +#include <rtems/bsd/local/opt_ipsec.h> + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <rtems/bsd/sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <rtems/bsd/sys/lock.h> +#include <sys/malloc.h> +#include <sys/mbuf.h> +#include <sys/module.h> +#include <sys/priv.h> +#include <sys/socket.h> +#include <sys/sockopt.h> +#include <sys/syslog.h> +#include <sys/proc.h> + +#include <netinet/in.h> +#include <netinet/in_pcb.h> +#include <netinet/ip.h> +#include <netinet/ip6.h> + +#include <netipsec/ipsec_support.h> +#include <netipsec/ipsec.h> +#include <netipsec/ipsec6.h> +#include <netipsec/key.h> +#include <netipsec/key_debug.h> + +#include <machine/atomic.h> +/* + * This file is build in the kernel only when 'options IPSEC' or + * 'options IPSEC_SUPPORT' is enabled. + */ + +#ifdef INET +void +ipsec4_setsockaddrs(const struct mbuf *m, union sockaddr_union *src, + union sockaddr_union *dst) +{ + static const struct sockaddr_in template = { + sizeof (struct sockaddr_in), + AF_INET, + 0, { 0 }, { 0, 0, 0, 0, 0, 0, 0, 0 } + }; + + src->sin = template; + dst->sin = template; + + if (m->m_len < sizeof (struct ip)) { + m_copydata(m, offsetof(struct ip, ip_src), + sizeof (struct in_addr), + (caddr_t) &src->sin.sin_addr); + m_copydata(m, offsetof(struct ip, ip_dst), + sizeof (struct in_addr), + (caddr_t) &dst->sin.sin_addr); + } else { + const struct ip *ip = mtod(m, const struct ip *); + src->sin.sin_addr = ip->ip_src; + dst->sin.sin_addr = ip->ip_dst; + } +} +#endif +#ifdef INET6 +void +ipsec6_setsockaddrs(const struct mbuf *m, union sockaddr_union *src, + union sockaddr_union *dst) +{ + struct ip6_hdr ip6buf; + const struct ip6_hdr *ip6; + + if (m->m_len >= sizeof(*ip6)) + ip6 = mtod(m, const struct ip6_hdr *); + else { + m_copydata(m, 0, sizeof(ip6buf), (caddr_t)&ip6buf); + ip6 = &ip6buf; + } + + bzero(&src->sin6, sizeof(struct sockaddr_in6)); + src->sin6.sin6_family = AF_INET6; + src->sin6.sin6_len = sizeof(struct sockaddr_in6); + bcopy(&ip6->ip6_src, &src->sin6.sin6_addr, sizeof(ip6->ip6_src)); + if (IN6_IS_SCOPE_LINKLOCAL(&ip6->ip6_src)) { + src->sin6.sin6_addr.s6_addr16[1] = 0; + src->sin6.sin6_scope_id = ntohs(ip6->ip6_src.s6_addr16[1]); + } + + bzero(&dst->sin6, sizeof(struct sockaddr_in6)); + dst->sin6.sin6_family = AF_INET6; + dst->sin6.sin6_len = sizeof(struct sockaddr_in6); + bcopy(&ip6->ip6_dst, &dst->sin6.sin6_addr, sizeof(ip6->ip6_dst)); + if (IN6_IS_SCOPE_LINKLOCAL(&ip6->ip6_dst)) { + dst->sin6.sin6_addr.s6_addr16[1] = 0; + dst->sin6.sin6_scope_id = ntohs(ip6->ip6_dst.s6_addr16[1]); + } +} +#endif + +#ifdef IPSEC_SUPPORT +/* + * IPSEC_SUPPORT - loading of ipsec.ko and tcpmd5.ko is supported. + * IPSEC + IPSEC_SUPPORT - loading tcpmd5.ko is supported. + * IPSEC + TCP_SIGNATURE - all is build in the kernel, do not build + * IPSEC_SUPPORT. + */ +#if !defined(IPSEC) || !defined(TCP_SIGNATURE) +#define IPSEC_MODULE_INCR 2 +static int +ipsec_kmod_enter(volatile u_int *cntr) +{ + u_int old, new; + + do { + old = *cntr; + if ((old & IPSEC_MODULE_ENABLED) == 0) + return (ENXIO); + new = old + IPSEC_MODULE_INCR; + } while(atomic_cmpset_acq_int(cntr, old, new) == 0); + return (0); +} + +static void +ipsec_kmod_exit(volatile u_int *cntr) +{ + u_int old, new; + + do { + old = *cntr; + new = old - IPSEC_MODULE_INCR; + } while (atomic_cmpset_rel_int(cntr, old, new) == 0); +} + +static void +ipsec_kmod_drain(volatile u_int *cntr) +{ + u_int old, new; + + do { + old = *cntr; + new = old & ~IPSEC_MODULE_ENABLED; + } while (atomic_cmpset_acq_int(cntr, old, new) == 0); + while (atomic_cmpset_int(cntr, 0, 0) == 0) + pause("ipsecd", hz/2); +} + +#define METHOD_DECL(...) __VA_ARGS__ +#define METHOD_ARGS(...) __VA_ARGS__ +#define IPSEC_KMOD_METHOD(type, name, sc, method, decl, args) \ +type name (decl) \ +{ \ + type ret = (type)ipsec_kmod_enter(&sc->enabled); \ + if (ret == 0) { \ + ret = (*sc->methods->method)(args); \ + ipsec_kmod_exit(&sc->enabled); \ + } \ + return (ret); \ +} + +static int +ipsec_support_modevent(module_t mod, int type, void *data) +{ + + switch (type) { + case MOD_LOAD: + return (0); + case MOD_UNLOAD: + return (EBUSY); + default: + return (EOPNOTSUPP); + } +} + +static moduledata_t ipsec_support_mod = { + "ipsec_support", + ipsec_support_modevent, + 0 +}; +DECLARE_MODULE(ipsec_support, ipsec_support_mod, SI_SUB_PROTO_DOMAIN, + SI_ORDER_ANY); +MODULE_VERSION(ipsec_support, 1); +#endif /* !IPSEC || !TCP_SIGNATURE */ + +#ifndef TCP_SIGNATURE +/* Declare TCP-MD5 support as kernel module. */ +static struct tcpmd5_support tcpmd5_ipsec = { + .enabled = 0, + .methods = NULL +}; +struct tcpmd5_support * const tcp_ipsec_support = &tcpmd5_ipsec; + +IPSEC_KMOD_METHOD(int, tcpmd5_kmod_input, sc, + input, METHOD_DECL(struct tcpmd5_support * const sc, struct mbuf *m, + struct tcphdr *th, u_char *buf), METHOD_ARGS(m, th, buf) +) + +IPSEC_KMOD_METHOD(int, tcpmd5_kmod_output, sc, + output, METHOD_DECL(struct tcpmd5_support * const sc, struct mbuf *m, + struct tcphdr *th, u_char *buf), METHOD_ARGS(m, th, buf) +) + +IPSEC_KMOD_METHOD(int, tcpmd5_kmod_pcbctl, sc, + pcbctl, METHOD_DECL(struct tcpmd5_support * const sc, struct inpcb *inp, + struct sockopt *sopt), METHOD_ARGS(inp, sopt) +) + +void +tcpmd5_support_enable(const struct tcpmd5_methods * const methods) +{ + + KASSERT(tcp_ipsec_support->enabled == 0, ("TCP-MD5 already enabled")); + tcp_ipsec_support->methods = methods; + tcp_ipsec_support->enabled |= IPSEC_MODULE_ENABLED; +} + +void +tcpmd5_support_disable(void) +{ + + if (tcp_ipsec_support->enabled & IPSEC_MODULE_ENABLED) { + ipsec_kmod_drain(&tcp_ipsec_support->enabled); + tcp_ipsec_support->methods = NULL; + } +} +#endif /* !TCP_SIGNATURE */ + +#ifndef IPSEC +/* + * IPsec support is build as kernel module. + */ +#ifdef INET +static struct ipsec_support ipv4_ipsec = { + .enabled = 0, + .methods = NULL +}; +struct ipsec_support * const ipv4_ipsec_support = &ipv4_ipsec; + +IPSEC_KMOD_METHOD(int, ipsec_kmod_udp_input, sc, + udp_input, METHOD_DECL(struct ipsec_support * const sc, struct mbuf *m, + int off, int af), METHOD_ARGS(m, off, af) +) + +IPSEC_KMOD_METHOD(int, ipsec_kmod_udp_pcbctl, sc, + udp_pcbctl, METHOD_DECL(struct ipsec_support * const sc, struct inpcb *inp, + struct sockopt *sopt), METHOD_ARGS(inp, sopt) +) +#endif + +#ifdef INET6 +static struct ipsec_support ipv6_ipsec = { + .enabled = 0, + .methods = NULL +}; +struct ipsec_support * const ipv6_ipsec_support = &ipv6_ipsec; +#endif + +IPSEC_KMOD_METHOD(int, ipsec_kmod_input, sc, + input, METHOD_DECL(struct ipsec_support * const sc, struct mbuf *m, + int offset, int proto), METHOD_ARGS(m, offset, proto) +) + +IPSEC_KMOD_METHOD(int, ipsec_kmod_check_policy, sc, + check_policy, METHOD_DECL(struct ipsec_support * const sc, struct mbuf *m, + struct inpcb *inp), METHOD_ARGS(m, inp) +) + +IPSEC_KMOD_METHOD(int, ipsec_kmod_forward, sc, + forward, METHOD_DECL(struct ipsec_support * const sc, struct mbuf *m), + (m) +) + +IPSEC_KMOD_METHOD(int, ipsec_kmod_output, sc, + output, METHOD_DECL(struct ipsec_support * const sc, struct mbuf *m, + struct inpcb *inp), METHOD_ARGS(m, inp) +) + +IPSEC_KMOD_METHOD(int, ipsec_kmod_pcbctl, sc, + pcbctl, METHOD_DECL(struct ipsec_support * const sc, struct inpcb *inp, + struct sockopt *sopt), METHOD_ARGS(inp, sopt) +) + +IPSEC_KMOD_METHOD(size_t, ipsec_kmod_hdrsize, sc, + hdrsize, METHOD_DECL(struct ipsec_support * const sc, struct inpcb *inp), + (inp) +) + +static IPSEC_KMOD_METHOD(int, ipsec_kmod_caps, sc, + capability, METHOD_DECL(struct ipsec_support * const sc, struct mbuf *m, + u_int cap), METHOD_ARGS(m, cap) +) + +int +ipsec_kmod_capability(struct ipsec_support * const sc, struct mbuf *m, + u_int cap) +{ + + /* + * Since PF_KEY is build in the kernel, we can directly + * call key_havesp() without additional synchronizations. + */ + if (cap == IPSEC_CAP_OPERABLE) + return (key_havesp(IPSEC_DIR_INBOUND) != 0 || + key_havesp(IPSEC_DIR_OUTBOUND) != 0); + return (ipsec_kmod_caps(sc, m, cap)); +} + +void +ipsec_support_enable(struct ipsec_support * const sc, + const struct ipsec_methods * const methods) +{ + + KASSERT(sc->enabled == 0, ("IPsec already enabled")); + sc->methods = methods; + sc->enabled |= IPSEC_MODULE_ENABLED; +} + +void +ipsec_support_disable(struct ipsec_support * const sc) +{ + + if (sc->enabled & IPSEC_MODULE_ENABLED) { + ipsec_kmod_drain(&sc->enabled); + sc->methods = NULL; + } +} +#endif /* !IPSEC */ +#endif /* IPSEC_SUPPORT */ diff --git a/freebsd/sys/netipsec/udpencap.c b/freebsd/sys/netipsec/udpencap.c new file mode 100644 index 00000000..fd2ca444 --- /dev/null +++ b/freebsd/sys/netipsec/udpencap.c @@ -0,0 +1,299 @@ +#include <machine/rtems-bsd-kernel-space.h> + +/*- + * Copyright (c) 2016 Andrey V. Elsukov <ae@FreeBSD.org> + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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> +__FBSDID("$FreeBSD$"); + +#include <rtems/bsd/local/opt_inet.h> +#include <rtems/bsd/local/opt_ipsec.h> + +#include <rtems/bsd/sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <rtems/bsd/sys/lock.h> +#include <sys/mbuf.h> +#include <sys/protosw.h> +#include <sys/socket.h> +#include <sys/sockopt.h> +#include <sys/sysctl.h> + +#include <netinet/in.h> +#include <netinet/in_pcb.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <netinet/ip_var.h> +#include <netinet/tcp.h> +#include <netinet/udp.h> +#include <netinet/udp_var.h> + +#include <net/vnet.h> + +#include <netipsec/ipsec.h> +#include <netipsec/esp.h> +#include <netipsec/esp_var.h> +#include <netipsec/xform.h> + +#include <netipsec/key.h> +#include <netipsec/key_debug.h> +#include <netipsec/ipsec_support.h> +#include <machine/in_cksum.h> + +/* + * Handle UDP_ENCAP socket option. Always return with released INP_WLOCK. + */ +int +udp_ipsec_pcbctl(struct inpcb *inp, struct sockopt *sopt) +{ + struct udpcb *up; + int error, optval; + + INP_WLOCK_ASSERT(inp); + if (sopt->sopt_name != UDP_ENCAP) { + INP_WUNLOCK(inp); + return (ENOPROTOOPT); + } + + up = intoudpcb(inp); + if (sopt->sopt_dir == SOPT_GET) { + if (up->u_flags & UF_ESPINUDP) + optval = UDP_ENCAP_ESPINUDP; + else + optval = 0; + INP_WUNLOCK(inp); + return (sooptcopyout(sopt, &optval, sizeof(optval))); + } + INP_WUNLOCK(inp); + + error = sooptcopyin(sopt, &optval, sizeof(optval), sizeof(optval)); + if (error != 0) + return (error); + + INP_WLOCK(inp); + switch (optval) { + case 0: + up->u_flags &= ~UF_ESPINUDP; + break; + case UDP_ENCAP_ESPINUDP: + up->u_flags |= UF_ESPINUDP; + break; + default: + error = EINVAL; + } + INP_WUNLOCK(inp); + return (error); +} + +/* + * Potentially decap ESP in UDP frame. Check for an ESP header. + * If present, strip the UDP header and push the result through IPSec. + * + * Returns error if mbuf consumed and/or processed, otherwise 0. + */ +int +udp_ipsec_input(struct mbuf *m, int off, int af) +{ + union sockaddr_union dst; + struct secasvar *sav; + struct udphdr *udp; + struct ip *ip; + uint32_t spi; + int error, hlen; + + /* + * Just return if packet doesn't have enough data. + * We need at least [IP header + UDP header + ESP header]. + * NAT-Keepalive packet has only one byte of payload, so it + * by default will not be processed. + */ + if (m->m_pkthdr.len < off + sizeof(struct esp)) + return (0); + + m_copydata(m, off, sizeof(uint32_t), (caddr_t)&spi); + if (spi == 0) /* Non-ESP marker. */ + return (0); + + /* + * Find SA and check that it is configured for UDP + * encapsulation. + */ + bzero(&dst, sizeof(dst)); + dst.sa.sa_family = af; + switch (af) { +#ifdef INET + case AF_INET: + dst.sin.sin_len = sizeof(struct sockaddr_in); + ip = mtod(m, struct ip *); + ip->ip_p = IPPROTO_ESP; + off = offsetof(struct ip, ip_p); + hlen = ip->ip_hl << 2; + dst.sin.sin_addr = ip->ip_dst; + break; +#endif +#ifdef INET6 + case AF_INET6: + /* Not yet */ + /* FALLTHROUGH */ +#endif + default: + ESPSTAT_INC(esps_nopf); + m_freem(m); + return (EPFNOSUPPORT); + } + + sav = key_allocsa(&dst, IPPROTO_ESP, spi); + if (sav == NULL) { + ESPSTAT_INC(esps_notdb); + m_freem(m); + return (ENOENT); + } + udp = mtodo(m, hlen); + if (sav->natt == NULL || + sav->natt->sport != udp->uh_sport || + sav->natt->dport != udp->uh_dport) { + /* XXXAE: should we check source address? */ + ESPSTAT_INC(esps_notdb); + key_freesav(&sav); + m_freem(m); + return (ENOENT); + } + /* + * Remove the UDP header + * Before: + * <--- off ---> + * +----+------+-----+ + * | IP | UDP | ESP | + * +----+------+-----+ + * <-skip-> + * After: + * +----+-----+ + * | IP | ESP | + * +----+-----+ + * <-skip-> + */ + m_striphdr(m, hlen, sizeof(*udp)); + /* + * We cannot yet update the cksums so clear any h/w cksum flags + * as they are no longer valid. + */ + if (m->m_pkthdr.csum_flags & CSUM_DATA_VALID) + m->m_pkthdr.csum_flags &= ~(CSUM_DATA_VALID | CSUM_PSEUDO_HDR); + /* + * We can update ip_len and ip_sum here, but ipsec4_input_cb() + * will do this anyway, so don't touch them here. + */ + ESPSTAT_INC(esps_input); + error = (*sav->tdb_xform->xf_input)(m, sav, hlen, off); + if (error != 0) + key_freesav(&sav); + + return (EINPROGRESS); /* Consumed by IPsec. */ +} + +int +udp_ipsec_output(struct mbuf *m, struct secasvar *sav) +{ + struct udphdr *udp; + struct mbuf *n; + struct ip *ip; + int hlen, off; + + IPSEC_ASSERT(sav->natt != NULL, ("UDP encapsulation isn't required.")); + + if (sav->sah->saidx.dst.sa.sa_family == AF_INET6) + return (EAFNOSUPPORT); + + ip = mtod(m, struct ip *); + hlen = ip->ip_hl << 2; + n = m_makespace(m, hlen, sizeof(*udp), &off); + if (n == NULL) { + DPRINTF(("%s: m_makespace for udphdr failed\n", __func__)); + return (ENOBUFS); + } + + udp = mtodo(n, off); + udp->uh_dport = sav->natt->dport; + udp->uh_sport = sav->natt->sport; + udp->uh_sum = 0; + udp->uh_ulen = htons(m->m_pkthdr.len - hlen); + + ip = mtod(m, struct ip *); + ip->ip_len = htons(m->m_pkthdr.len); + ip->ip_p = IPPROTO_UDP; + return (0); +} + +void +udp_ipsec_adjust_cksum(struct mbuf *m, struct secasvar *sav, int proto, + int skip) +{ + struct ip *ip; + uint16_t cksum, off; + + IPSEC_ASSERT(sav->natt != NULL, ("NAT-T isn't required")); + IPSEC_ASSERT(proto == IPPROTO_UDP || proto == IPPROTO_TCP, + ("unexpected protocol %u", proto)); + + if (proto == IPPROTO_UDP) + off = offsetof(struct udphdr, uh_sum); + else + off = offsetof(struct tcphdr, th_sum); + + if (V_natt_cksum_policy == 0) { /* auto */ + if (sav->natt->cksum != 0) { + /* Incrementally recompute. */ + m_copydata(m, skip + off, sizeof(cksum), + (caddr_t)&cksum); + /* Do not adjust UDP checksum if it is zero. */ + if (proto == IPPROTO_UDP && cksum == 0) + return; + cksum = in_addword(cksum, sav->natt->cksum); + } else { + /* No OA from IKEd. */ + if (proto == IPPROTO_TCP) { + /* Ignore for TCP. */ + m->m_pkthdr.csum_data = 0xffff; + m->m_pkthdr.csum_flags |= (CSUM_DATA_VALID | + CSUM_PSEUDO_HDR); + return; + } + cksum = 0; /* Reset for UDP. */ + } + m_copyback(m, skip + off, sizeof(cksum), (caddr_t)&cksum); + } else { /* Fully recompute */ + ip = mtod(m, struct ip *); + cksum = in_pseudo(ip->ip_src.s_addr, ip->ip_dst.s_addr, + htons(m->m_pkthdr.len - skip + proto)); + m_copyback(m, skip + off, sizeof(cksum), (caddr_t)&cksum); + m->m_pkthdr.csum_flags = + (proto == IPPROTO_UDP) ? CSUM_UDP: CSUM_TCP; + m->m_pkthdr.csum_data = off; + in_delayed_cksum(m); + m->m_pkthdr.csum_flags &= ~CSUM_DELAY_DATA; + } +} + diff --git a/freebsd/sys/netipsec/xform.h b/freebsd/sys/netipsec/xform.h index e389cab3..8e6f8bdb 100644 --- a/freebsd/sys/netipsec/xform.h +++ b/freebsd/sys/netipsec/xform.h @@ -42,6 +42,7 @@ #define _NETIPSEC_XFORM_H_ #include <sys/types.h> +#include <sys/queue.h> #include <netinet/in.h> #include <opencrypto/xform.h> @@ -49,83 +50,68 @@ #define AH_HMAC_MAXHASHLEN (SHA2_512_HASH_LEN/2) /* Keep this updated */ #define AH_HMAC_INITIAL_RPL 1 /* replay counter initial value */ +#ifdef _KERNEL +struct secpolicy; +struct secasvar; + /* * Packet tag assigned on completion of IPsec processing; used - * to speedup processing when/if the packet comes back for more - * processing. + * to speedup security policy checking for INBOUND packets. */ -struct tdb_ident { - u_int32_t spi; - union sockaddr_union dst; - u_int8_t proto; - /* Cache those two for enc(4) in xform_ipip. */ - u_int8_t alg_auth; - u_int8_t alg_enc; +struct xform_history { + union sockaddr_union dst; /* destination address */ + uint32_t spi; /* Security Parameters Index */ + uint8_t proto; /* IPPROTO_ESP or IPPROTO_AH */ + uint8_t mode; /* transport or tunnel */ }; /* * Opaque data structure hung off a crypto operation descriptor. */ -struct tdb_crypto { - struct ipsecrequest *tc_isr; /* ipsec request state */ - u_int32_t tc_spi; /* associated SPI */ - union sockaddr_union tc_dst; /* dst addr of packet */ - u_int8_t tc_proto; /* current protocol, e.g. AH */ - u_int8_t tc_nxt; /* next protocol, e.g. IPV4 */ - int tc_protoff; /* current protocol offset */ - int tc_skip; /* data offset */ - caddr_t tc_ptr; /* associated crypto data */ - struct secasvar *tc_sav; /* related SA */ +struct xform_data { + struct secpolicy *sp; /* security policy */ + struct secasvar *sav; /* related SA */ + uint64_t cryptoid; /* used crypto session id */ + u_int idx; /* IPsec request index */ + int protoff; /* current protocol offset */ + int skip; /* data offset */ + uint8_t nxt; /* next protocol, e.g. IPV4 */ }; -struct secasvar; -struct ipescrequest; - -struct xformsw { - u_short xf_type; /* xform ID */ -#define XF_IP4 1 /* IP inside IP */ +#define XF_IP4 1 /* unused */ #define XF_AH 2 /* AH */ #define XF_ESP 3 /* ESP */ #define XF_TCPSIGNATURE 5 /* TCP MD5 Signature option, RFC 2358 */ #define XF_IPCOMP 6 /* IPCOMP */ - u_short xf_flags; -#define XFT_AUTH 0x0001 -#define XFT_CONF 0x0100 -#define XFT_COMP 0x1000 - char *xf_name; /* human-readable name */ + +struct xformsw { + u_short xf_type; /* xform ID */ + char *xf_name; /* human-readable name */ int (*xf_init)(struct secasvar*, struct xformsw*); /* setup */ int (*xf_zeroize)(struct secasvar*); /* cleanup */ int (*xf_input)(struct mbuf*, struct secasvar*, /* input */ int, int); - int (*xf_output)(struct mbuf*, /* output */ - struct ipsecrequest *, struct mbuf **, int, int); - struct xformsw *xf_next; /* list of registered xforms */ + int (*xf_output)(struct mbuf*, /* output */ + struct secpolicy *, struct secasvar *, u_int, int, int); + LIST_ENTRY(xformsw) chain; }; -#ifdef _KERNEL -extern void xform_register(struct xformsw*); -extern int xform_init(struct secasvar *sav, int xftype); +const struct enc_xform * enc_algorithm_lookup(int); +const struct auth_hash * auth_algorithm_lookup(int); +const struct comp_algo * comp_algorithm_lookup(int); -struct cryptoini; - -/* XF_IP4 */ -extern int ip4_input6(struct mbuf **m, int *offp, int proto); -extern void ip4_input(struct mbuf *m, int); -extern int ipip_output(struct mbuf *, struct ipsecrequest *, - struct mbuf **, int, int); +void xform_attach(void *); +void xform_detach(void *); +struct cryptoini; /* XF_AH */ +int xform_ah_authsize(const struct auth_hash *); extern int ah_init0(struct secasvar *, struct xformsw *, struct cryptoini *); extern int ah_zeroize(struct secasvar *sav); -extern struct auth_hash *ah_algorithm_lookup(int alg); extern size_t ah_hdrsiz(struct secasvar *); /* XF_ESP */ -extern struct enc_xform *esp_algorithm_lookup(int alg); extern size_t esp_hdrsiz(struct secasvar *sav); -/* XF_COMP */ -extern struct comp_algo *ipcomp_algorithm_lookup(int alg); - #endif /* _KERNEL */ #endif /* _NETIPSEC_XFORM_H_ */ diff --git a/freebsd/sys/netipsec/xform_ah.c b/freebsd/sys/netipsec/xform_ah.c index f1304c24..5dd41282 100644 --- a/freebsd/sys/netipsec/xform_ah.c +++ b/freebsd/sys/netipsec/xform_ah.c @@ -47,6 +47,8 @@ #include <sys/socket.h> #include <sys/syslog.h> #include <sys/kernel.h> +#include <rtems/bsd/sys/lock.h> +#include <sys/mutex.h> #include <sys/sysctl.h> #include <net/if.h> @@ -58,7 +60,6 @@ #include <netinet/ip_ecn.h> #include <netinet/ip6.h> -#include <net/route.h> #include <netipsec/ipsec.h> #include <netipsec/ah.h> #include <netipsec/ah_var.h> @@ -83,24 +84,29 @@ (((sav)->flags & SADB_X_EXT_OLD) ? \ sizeof (struct ah) : sizeof (struct ah) + sizeof (u_int32_t)) /* - * Return authenticator size in bytes. The old protocol is known - * to use a fixed 16-byte authenticator. The new algorithm use 12-byte - * authenticator. + * Return authenticator size in bytes, based on a field in the + * algorithm descriptor. */ -#define AUTHSIZE(sav) ah_authsize(sav) +#define AUTHSIZE(sav) ((sav->flags & SADB_X_EXT_OLD) ? 16 : \ + xform_ah_authsize((sav)->tdb_authalgxform)) VNET_DEFINE(int, ah_enable) = 1; /* control flow of packets with AH */ VNET_DEFINE(int, ah_cleartos) = 1; /* clear ip_tos when doing AH calc */ -VNET_DEFINE(struct ahstat, ahstat); +VNET_PCPUSTAT_DEFINE(struct ahstat, ahstat); +VNET_PCPUSTAT_SYSINIT(ahstat); + +#ifdef VIMAGE +VNET_PCPUSTAT_SYSUNINIT(ahstat); +#endif /* VIMAGE */ #ifdef INET SYSCTL_DECL(_net_inet_ah); -SYSCTL_VNET_INT(_net_inet_ah, OID_AUTO, - ah_enable, CTLFLAG_RW, &VNET_NAME(ah_enable), 0, ""); -SYSCTL_VNET_INT(_net_inet_ah, OID_AUTO, - ah_cleartos, CTLFLAG_RW, &VNET_NAME(ah_cleartos), 0, ""); -SYSCTL_VNET_STRUCT(_net_inet_ah, IPSECCTL_STATS, - stats, CTLFLAG_RD, &VNET_NAME(ahstat), ahstat, ""); +SYSCTL_INT(_net_inet_ah, OID_AUTO, ah_enable, + CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(ah_enable), 0, ""); +SYSCTL_INT(_net_inet_ah, OID_AUTO, ah_cleartos, + CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(ah_cleartos), 0, ""); +SYSCTL_VNET_PCPUSTAT(_net_inet_ah, IPSECCTL_STATS, stats, struct ahstat, + ahstat, "AH statistics (struct ahstat, netipsec/ah_var.h)"); #endif static unsigned char ipseczeroes[256]; /* larger than an ip6 extension hdr */ @@ -108,56 +114,33 @@ static unsigned char ipseczeroes[256]; /* larger than an ip6 extension hdr */ static int ah_input_cb(struct cryptop*); static int ah_output_cb(struct cryptop*); -static int -ah_authsize(struct secasvar *sav) +int +xform_ah_authsize(const struct auth_hash *esph) { + int alen; - IPSEC_ASSERT(sav != NULL, ("%s: sav == NULL", __func__)); + if (esph == NULL) + return 0; - if (sav->flags & SADB_X_EXT_OLD) - return 16; + switch (esph->type) { + case CRYPTO_SHA2_256_HMAC: + case CRYPTO_SHA2_384_HMAC: + case CRYPTO_SHA2_512_HMAC: + alen = esph->hashsize / 2; /* RFC4868 2.3 */ + break; + + case CRYPTO_AES_128_NIST_GMAC: + case CRYPTO_AES_192_NIST_GMAC: + case CRYPTO_AES_256_NIST_GMAC: + alen = esph->hashsize; + break; - switch (sav->alg_auth) { - case SADB_X_AALG_SHA2_256: - return 16; - case SADB_X_AALG_SHA2_384: - return 24; - case SADB_X_AALG_SHA2_512: - return 32; default: - return AH_HMAC_HASHLEN; - } - /* NOTREACHED */ -} -/* - * NB: this is public for use by the PF_KEY support. - */ -struct auth_hash * -ah_algorithm_lookup(int alg) -{ - if (alg > SADB_AALG_MAX) - return NULL; - switch (alg) { - case SADB_X_AALG_NULL: - return &auth_hash_null; - case SADB_AALG_MD5HMAC: - return &auth_hash_hmac_md5; - case SADB_AALG_SHA1HMAC: - return &auth_hash_hmac_sha1; - case SADB_X_AALG_RIPEMD160HMAC: - return &auth_hash_hmac_ripemd_160; - case SADB_X_AALG_MD5: - return &auth_hash_key_md5; - case SADB_X_AALG_SHA: - return &auth_hash_key_sha1; - case SADB_X_AALG_SHA2_256: - return &auth_hash_hmac_sha2_256; - case SADB_X_AALG_SHA2_384: - return &auth_hash_hmac_sha2_384; - case SADB_X_AALG_SHA2_512: - return &auth_hash_hmac_sha2_512; + alen = AH_HMAC_HASHLEN; + break; } - return NULL; + + return alen; } size_t @@ -184,10 +167,10 @@ ah_hdrsiz(struct secasvar *sav) int ah_init0(struct secasvar *sav, struct xformsw *xsp, struct cryptoini *cria) { - struct auth_hash *thash; + const struct auth_hash *thash; int keylen; - thash = ah_algorithm_lookup(sav->alg_auth); + thash = auth_algorithm_lookup(sav->alg_auth); if (thash == NULL) { DPRINTF(("%s: unsupported authentication algorithm %u\n", __func__, sav->alg_auth)); @@ -307,23 +290,10 @@ ah_massage_headers(struct mbuf **m0, int proto, int skip, int alg, int out) ip->ip_ttl = 0; ip->ip_sum = 0; - /* - * On input, fix ip_len which has been byte-swapped - * at ip_input(). - */ - if (!out) { - ip->ip_len = htons(ip->ip_len + skip); - - if (alg == CRYPTO_MD5_KPDK || alg == CRYPTO_SHA1_KPDK) - ip->ip_off = htons(ip->ip_off & IP_DF); - else - ip->ip_off = 0; - } else { - if (alg == CRYPTO_MD5_KPDK || alg == CRYPTO_SHA1_KPDK) - ip->ip_off = htons(ntohs(ip->ip_off) & IP_DF); - else - ip->ip_off = 0; - } + if (alg == CRYPTO_MD5_KPDK || alg == CRYPTO_SHA1_KPDK) + ip->ip_off &= htons(IP_DF); + else + ip->ip_off = htons(0); ptr = mtod(m, unsigned char *) + sizeof(struct ip); @@ -576,15 +546,14 @@ ah_massage_headers(struct mbuf **m0, int proto, int skip, int alg, int out) static int ah_input(struct mbuf *m, struct secasvar *sav, int skip, int protoff) { - struct auth_hash *ahx; - struct tdb_ident *tdbi; - struct tdb_crypto *tc; - struct m_tag *mtag; - struct newah *ah; - int hl, rplen, authsize; - + char buf[128]; + const struct auth_hash *ahx; struct cryptodesc *crda; struct cryptop *crp; + struct xform_data *xd; + struct newah *ah; + uint64_t cryptoid; + int hl, rplen, authsize, error; IPSEC_ASSERT(sav != NULL, ("null SA")); IPSEC_ASSERT(sav->key_auth != NULL, ("null authentication key")); @@ -604,13 +573,18 @@ ah_input(struct mbuf *m, struct secasvar *sav, int skip, int protoff) } /* Check replay window, if applicable. */ - if (sav->replay && !ipsec_chkreplay(ntohl(ah->ah_seq), sav)) { + SECASVAR_LOCK(sav); + if (sav->replay != NULL && sav->replay->wsize != 0 && + ipsec_chkreplay(ntohl(ah->ah_seq), sav) == 0) { + SECASVAR_UNLOCK(sav); AHSTAT_INC(ahs_replay); DPRINTF(("%s: packet replay failure: %s\n", __func__, - ipsec_logsastr(sav))); + ipsec_sa2str(sav, buf, sizeof(buf)))); m_freem(m); - return ENOBUFS; + return (EACCES); } + cryptoid = sav->tdb_cryptoid; + SECASVAR_UNLOCK(sav); /* Verify AH header length. */ hl = ah->ah_len * sizeof (u_int32_t); @@ -618,10 +592,10 @@ ah_input(struct mbuf *m, struct secasvar *sav, int skip, int protoff) authsize = AUTHSIZE(sav); if (hl != authsize + rplen - sizeof (struct ah)) { DPRINTF(("%s: bad authenticator length %u (expecting %lu)" - " for packet in SA %s/%08lx\n", __func__, - hl, (u_long) (authsize + rplen - sizeof (struct ah)), - ipsec_address(&sav->sah->saidx.dst), - (u_long) ntohl(sav->spi))); + " for packet in SA %s/%08lx\n", __func__, hl, + (u_long) (authsize + rplen - sizeof (struct ah)), + ipsec_address(&sav->sah->saidx.dst, buf, sizeof(buf)), + (u_long) ntohl(sav->spi))); AHSTAT_INC(ahs_badauthl); m_freem(m); return EACCES; @@ -631,7 +605,8 @@ ah_input(struct mbuf *m, struct secasvar *sav, int skip, int protoff) /* Get crypto descriptors. */ crp = crypto_getreq(1); if (crp == NULL) { - DPRINTF(("%s: failed to acquire crypto descriptor\n",__func__)); + DPRINTF(("%s: failed to acquire crypto descriptor\n", + __func__)); AHSTAT_INC(ahs_crypto); m_freem(m); return ENOBUFS; @@ -649,58 +624,35 @@ ah_input(struct mbuf *m, struct secasvar *sav, int skip, int protoff) crda->crd_klen = _KEYBITS(sav->key_auth); crda->crd_key = sav->key_auth->key_data; - /* Find out if we've already done crypto. */ - for (mtag = m_tag_find(m, PACKET_TAG_IPSEC_IN_CRYPTO_DONE, NULL); - mtag != NULL; - mtag = m_tag_find(m, PACKET_TAG_IPSEC_IN_CRYPTO_DONE, mtag)) { - tdbi = (struct tdb_ident *) (mtag + 1); - if (tdbi->proto == sav->sah->saidx.proto && - tdbi->spi == sav->spi && - !bcmp(&tdbi->dst, &sav->sah->saidx.dst, - sizeof (union sockaddr_union))) - break; - } - /* Allocate IPsec-specific opaque crypto info. */ - if (mtag == NULL) { - tc = (struct tdb_crypto *) malloc(sizeof (struct tdb_crypto) + - skip + rplen + authsize, M_XDATA, M_NOWAIT|M_ZERO); - } else { - /* Hash verification has already been done successfully. */ - tc = (struct tdb_crypto *) malloc(sizeof (struct tdb_crypto), - M_XDATA, M_NOWAIT|M_ZERO); - } - if (tc == NULL) { - DPRINTF(("%s: failed to allocate tdb_crypto\n", __func__)); + xd = malloc(sizeof(*xd) + skip + rplen + authsize, M_XDATA, + M_NOWAIT | M_ZERO); + if (xd == NULL) { + DPRINTF(("%s: failed to allocate xform_data\n", __func__)); AHSTAT_INC(ahs_crypto); crypto_freereq(crp); m_freem(m); return ENOBUFS; } - /* Only save information if crypto processing is needed. */ - if (mtag == NULL) { - int error; + /* + * Save the authenticator, the skipped portion of the packet, + * and the AH header. + */ + m_copydata(m, 0, skip + rplen + authsize, (caddr_t)(xd + 1)); - /* - * Save the authenticator, the skipped portion of the packet, - * and the AH header. - */ - m_copydata(m, 0, skip + rplen + authsize, (caddr_t)(tc+1)); - - /* Zeroize the authenticator on the packet. */ - m_copyback(m, skip + rplen, authsize, ipseczeroes); - - /* "Massage" the packet headers for crypto processing. */ - error = ah_massage_headers(&m, sav->sah->saidx.dst.sa.sa_family, - skip, ahx->type, 0); - if (error != 0) { - /* NB: mbuf is free'd by ah_massage_headers */ - AHSTAT_INC(ahs_hdrops); - free(tc, M_XDATA); - crypto_freereq(crp); - return error; - } + /* Zeroize the authenticator on the packet. */ + m_copyback(m, skip + rplen, authsize, ipseczeroes); + + /* "Massage" the packet headers for crypto processing. */ + error = ah_massage_headers(&m, sav->sah->saidx.dst.sa.sa_family, + skip, ahx->type, 0); + if (error != 0) { + /* NB: mbuf is free'd by ah_massage_headers */ + AHSTAT_INC(ahs_hdrops); + free(xd, M_XDATA); + crypto_freereq(crp); + return (error); } /* Crypto operation descriptor. */ @@ -708,24 +660,16 @@ ah_input(struct mbuf *m, struct secasvar *sav, int skip, int protoff) crp->crp_flags = CRYPTO_F_IMBUF | CRYPTO_F_CBIFSYNC; crp->crp_buf = (caddr_t) m; crp->crp_callback = ah_input_cb; - crp->crp_sid = sav->tdb_cryptoid; - crp->crp_opaque = (caddr_t) tc; + crp->crp_sid = cryptoid; + crp->crp_opaque = (caddr_t) xd; /* These are passed as-is to the callback. */ - tc->tc_spi = sav->spi; - tc->tc_dst = sav->sah->saidx.dst; - tc->tc_proto = sav->sah->saidx.proto; - tc->tc_nxt = ah->ah_nxt; - tc->tc_protoff = protoff; - tc->tc_skip = skip; - tc->tc_ptr = (caddr_t) mtag; /* Save the mtag we've identified. */ - KEY_ADDREFSA(sav); - tc->tc_sav = sav; - - if (mtag == NULL) - return crypto_dispatch(crp); - else - return ah_input_cb(crp); + xd->sav = sav; + xd->nxt = ah->ah_nxt; + xd->protoff = protoff; + xd->skip = skip; + xd->cryptoid = cryptoid; + return (crypto_dispatch(crp)); } /* @@ -734,49 +678,43 @@ ah_input(struct mbuf *m, struct secasvar *sav, int skip, int protoff) static int ah_input_cb(struct cryptop *crp) { - int rplen, error, skip, protoff; + char buf[IPSEC_ADDRSTRLEN]; unsigned char calc[AH_ALEN_MAX]; + const struct auth_hash *ahx; struct mbuf *m; struct cryptodesc *crd; - struct auth_hash *ahx; - struct tdb_crypto *tc; - struct m_tag *mtag; + struct xform_data *xd; struct secasvar *sav; struct secasindex *saidx; - u_int8_t nxt; caddr_t ptr; - int authsize; + uint64_t cryptoid; + int authsize, rplen, error, skip, protoff; + uint8_t nxt; crd = crp->crp_desc; - - tc = (struct tdb_crypto *) crp->crp_opaque; - IPSEC_ASSERT(tc != NULL, ("null opaque crypto data area!")); - skip = tc->tc_skip; - nxt = tc->tc_nxt; - protoff = tc->tc_protoff; - mtag = (struct m_tag *) tc->tc_ptr; m = (struct mbuf *) crp->crp_buf; - - sav = tc->tc_sav; - IPSEC_ASSERT(sav != NULL, ("null SA!")); - + xd = (struct xform_data *) crp->crp_opaque; + sav = xd->sav; + skip = xd->skip; + nxt = xd->nxt; + protoff = xd->protoff; + cryptoid = xd->cryptoid; saidx = &sav->sah->saidx; IPSEC_ASSERT(saidx->dst.sa.sa_family == AF_INET || saidx->dst.sa.sa_family == AF_INET6, ("unexpected protocol family %u", saidx->dst.sa.sa_family)); - ahx = (struct auth_hash *) sav->tdb_authalgxform; + ahx = sav->tdb_authalgxform; /* Check for crypto errors. */ if (crp->crp_etype) { - if (sav->tdb_cryptoid != 0) - sav->tdb_cryptoid = crp->crp_sid; - if (crp->crp_etype == EAGAIN) { - error = crypto_dispatch(crp); - return error; + /* Reset the session ID */ + if (ipsec_updateid(sav, &crp->crp_sid, &cryptoid) != 0) + crypto_freesession(cryptoid); + xd->cryptoid = crp->crp_sid; + return (crypto_dispatch(crp)); } - AHSTAT_INC(ahs_noxform); DPRINTF(("%s: crypto error %d\n", __func__, crp->crp_etype)); error = crp->crp_etype; @@ -802,35 +740,23 @@ ah_input_cb(struct cryptop *crp) /* Copy authenticator off the packet. */ m_copydata(m, skip + rplen, authsize, calc); - /* - * If we have an mtag, we don't need to verify the authenticator -- - * it has been verified by an IPsec-aware NIC. - */ - if (mtag == NULL) { - ptr = (caddr_t) (tc + 1); - - /* Verify authenticator. */ - if (bcmp(ptr + skip + rplen, calc, authsize)) { - DPRINTF(("%s: authentication hash mismatch for packet " - "in SA %s/%08lx\n", __func__, - ipsec_address(&saidx->dst), - (u_long) ntohl(sav->spi))); - AHSTAT_INC(ahs_badauth); - error = EACCES; - goto bad; - } - - /* Fix the Next Protocol field. */ - ((u_int8_t *) ptr)[protoff] = nxt; - - /* Copyback the saved (uncooked) network headers. */ - m_copyback(m, 0, skip, ptr); - } else { - /* Fix the Next Protocol field. */ - m_copyback(m, protoff, sizeof(u_int8_t), &nxt); + /* Verify authenticator. */ + ptr = (caddr_t) (xd + 1); + if (timingsafe_bcmp(ptr + skip + rplen, calc, authsize)) { + DPRINTF(("%s: authentication hash mismatch for packet " + "in SA %s/%08lx\n", __func__, + ipsec_address(&saidx->dst, buf, sizeof(buf)), + (u_long) ntohl(sav->spi))); + AHSTAT_INC(ahs_badauth); + error = EACCES; + goto bad; } + /* Fix the Next Protocol field. */ + ((uint8_t *) ptr)[protoff] = nxt; - free(tc, M_XDATA), tc = NULL; /* No longer needed */ + /* Copyback the saved (uncooked) network headers. */ + m_copyback(m, 0, skip, ptr); + free(xd, M_XDATA), xd = NULL; /* No longer needed */ /* * Header is now authenticated. @@ -845,11 +771,14 @@ ah_input_cb(struct cryptop *crp) m_copydata(m, skip + offsetof(struct newah, ah_seq), sizeof (seq), (caddr_t) &seq); + SECASVAR_LOCK(sav); if (ipsec_updatereplay(ntohl(seq), sav)) { + SECASVAR_UNLOCK(sav); AHSTAT_INC(ahs_replay); - error = ENOBUFS; /*XXX as above*/ + error = EACCES; goto bad; } + SECASVAR_UNLOCK(sav); } /* @@ -858,8 +787,8 @@ ah_input_cb(struct cryptop *crp) error = m_striphdr(m, skip, rplen + authsize); if (error) { DPRINTF(("%s: mangled mbuf chain for SA %s/%08lx\n", __func__, - ipsec_address(&saidx->dst), (u_long) ntohl(sav->spi))); - + ipsec_address(&saidx->dst, buf, sizeof(buf)), + (u_long) ntohl(sav->spi))); AHSTAT_INC(ahs_hdrops); goto bad; } @@ -867,56 +796,50 @@ ah_input_cb(struct cryptop *crp) switch (saidx->dst.sa.sa_family) { #ifdef INET6 case AF_INET6: - error = ipsec6_common_input_cb(m, sav, skip, protoff, mtag); + error = ipsec6_common_input_cb(m, sav, skip, protoff); break; #endif #ifdef INET case AF_INET: - error = ipsec4_common_input_cb(m, sav, skip, protoff, mtag); + error = ipsec4_common_input_cb(m, sav, skip, protoff); break; #endif default: panic("%s: Unexpected address family: %d saidx=%p", __func__, saidx->dst.sa.sa_family, saidx); } - - KEY_FREESAV(&sav); return error; bad: if (sav) - KEY_FREESAV(&sav); + key_freesav(&sav); if (m != NULL) m_freem(m); - if (tc != NULL) - free(tc, M_XDATA); + if (xd != NULL) + free(xd, M_XDATA); if (crp != NULL) crypto_freereq(crp); return error; } /* - * AH output routine, called by ipsec[46]_process_packet(). + * AH output routine, called by ipsec[46]_perform_request(). */ static int -ah_output( - struct mbuf *m, - struct ipsecrequest *isr, - struct mbuf **mp, - int skip, - int protoff) +ah_output(struct mbuf *m, struct secpolicy *sp, struct secasvar *sav, + u_int idx, int skip, int protoff) { - struct secasvar *sav; - struct auth_hash *ahx; + char buf[IPSEC_ADDRSTRLEN]; + const struct auth_hash *ahx; struct cryptodesc *crda; - struct tdb_crypto *tc; + struct xform_data *xd; struct mbuf *mi; struct cryptop *crp; - u_int16_t iplen; - int error, rplen, authsize, maxpacketsize, roff; - u_int8_t prot; struct newah *ah; + uint64_t cryptoid; + uint16_t iplen; + int error, rplen, authsize, maxpacketsize, roff; + uint8_t prot; - sav = isr->sav; IPSEC_ASSERT(sav != NULL, ("null SA")); ahx = sav->tdb_authalgxform; IPSEC_ASSERT(ahx != NULL, ("null authentication xform")); @@ -942,7 +865,7 @@ ah_output( DPRINTF(("%s: unknown/unsupported protocol family %u, " "SA %s/%08lx\n", __func__, sav->sah->saidx.dst.sa.sa_family, - ipsec_address(&sav->sah->saidx.dst), + ipsec_address(&sav->sah->saidx.dst, buf, sizeof(buf)), (u_long) ntohl(sav->spi))); AHSTAT_INC(ahs_nopf); error = EPFNOSUPPORT; @@ -952,7 +875,7 @@ ah_output( if (rplen + authsize + m->m_pkthdr.len > maxpacketsize) { DPRINTF(("%s: packet in SA %s/%08lx got too big " "(len %u, max len %u)\n", __func__, - ipsec_address(&sav->sah->saidx.dst), + ipsec_address(&sav->sah->saidx.dst, buf, sizeof(buf)), (u_long) ntohl(sav->spi), rplen + authsize + m->m_pkthdr.len, maxpacketsize)); AHSTAT_INC(ahs_toobig); @@ -966,7 +889,7 @@ ah_output( m = m_unshare(m, M_NOWAIT); if (m == NULL) { DPRINTF(("%s: cannot clone mbuf chain, SA %s/%08lx\n", __func__, - ipsec_address(&sav->sah->saidx.dst), + ipsec_address(&sav->sah->saidx.dst, buf, sizeof(buf)), (u_long) ntohl(sav->spi))); AHSTAT_INC(ahs_hdrops); error = ENOBUFS; @@ -979,7 +902,7 @@ ah_output( DPRINTF(("%s: failed to inject %u byte AH header for SA " "%s/%08lx\n", __func__, rplen + authsize, - ipsec_address(&sav->sah->saidx.dst), + ipsec_address(&sav->sah->saidx.dst, buf, sizeof(buf)), (u_long) ntohl(sav->spi))); AHSTAT_INC(ahs_hdrops); /*XXX differs from openbsd */ error = ENOBUFS; @@ -1002,15 +925,16 @@ ah_output( m_copyback(m, skip + rplen, authsize, ipseczeroes); /* Insert packet replay counter, as requested. */ + SECASVAR_LOCK(sav); if (sav->replay) { if (sav->replay->count == ~0 && (sav->flags & SADB_X_EXT_CYCSEQ) == 0) { + SECASVAR_UNLOCK(sav); DPRINTF(("%s: replay counter wrapped for SA %s/%08lx\n", - __func__, - ipsec_address(&sav->sah->saidx.dst), - (u_long) ntohl(sav->spi))); + __func__, ipsec_address(&sav->sah->saidx.dst, buf, + sizeof(buf)), (u_long) ntohl(sav->spi))); AHSTAT_INC(ahs_wrap); - error = EINVAL; + error = EACCES; goto bad; } #ifdef REGRESSION @@ -1020,6 +944,8 @@ ah_output( sav->replay->count++; ah->ah_seq = htonl(sav->replay->count); } + cryptoid = sav->tdb_cryptoid; + SECASVAR_UNLOCK(sav); /* Get crypto descriptors. */ crp = crypto_getreq(1); @@ -1032,7 +958,6 @@ ah_output( } crda = crp->crp_desc; - crda->crd_skip = 0; crda->crd_inject = skip + rplen; crda->crd_len = m->m_pkthdr.len; @@ -1043,18 +968,18 @@ ah_output( crda->crd_klen = _KEYBITS(sav->key_auth); /* Allocate IPsec-specific opaque crypto info. */ - tc = (struct tdb_crypto *) malloc( - sizeof(struct tdb_crypto) + skip, M_XDATA, M_NOWAIT|M_ZERO); - if (tc == NULL) { + xd = malloc(sizeof(struct xform_data) + skip, M_XDATA, + M_NOWAIT | M_ZERO); + if (xd == NULL) { crypto_freereq(crp); - DPRINTF(("%s: failed to allocate tdb_crypto\n", __func__)); + DPRINTF(("%s: failed to allocate xform_data\n", __func__)); AHSTAT_INC(ahs_crypto); error = ENOBUFS; goto bad; } /* Save the skipped portion of the packet. */ - m_copydata(m, 0, skip, (caddr_t) (tc + 1)); + m_copydata(m, 0, skip, (caddr_t) (xd + 1)); /* * Fix IP header length on the header used for @@ -1064,7 +989,7 @@ ah_output( switch (sav->sah->saidx.dst.sa.sa_family) { #ifdef INET case AF_INET: - bcopy(((caddr_t)(tc + 1)) + + bcopy(((caddr_t)(xd + 1)) + offsetof(struct ip, ip_len), (caddr_t) &iplen, sizeof(u_int16_t)); iplen = htons(ntohs(iplen) + rplen + authsize); @@ -1075,29 +1000,29 @@ ah_output( #ifdef INET6 case AF_INET6: - bcopy(((caddr_t)(tc + 1)) + + bcopy(((caddr_t)(xd + 1)) + offsetof(struct ip6_hdr, ip6_plen), - (caddr_t) &iplen, sizeof(u_int16_t)); + (caddr_t) &iplen, sizeof(uint16_t)); iplen = htons(ntohs(iplen) + rplen + authsize); m_copyback(m, offsetof(struct ip6_hdr, ip6_plen), - sizeof(u_int16_t), (caddr_t) &iplen); + sizeof(uint16_t), (caddr_t) &iplen); break; #endif /* INET6 */ } /* Fix the Next Header field in saved header. */ - ((u_int8_t *) (tc + 1))[protoff] = IPPROTO_AH; + ((uint8_t *) (xd + 1))[protoff] = IPPROTO_AH; /* Update the Next Protocol field in the IP header. */ prot = IPPROTO_AH; - m_copyback(m, protoff, sizeof(u_int8_t), (caddr_t) &prot); + m_copyback(m, protoff, sizeof(uint8_t), (caddr_t) &prot); /* "Massage" the packet headers for crypto processing. */ error = ah_massage_headers(&m, sav->sah->saidx.dst.sa.sa_family, skip, ahx->type, 1); if (error != 0) { m = NULL; /* mbuf was free'd by ah_massage_headers. */ - free(tc, M_XDATA); + free(xd, M_XDATA); crypto_freereq(crp); goto bad; } @@ -1107,18 +1032,15 @@ ah_output( crp->crp_flags = CRYPTO_F_IMBUF | CRYPTO_F_CBIFSYNC; crp->crp_buf = (caddr_t) m; crp->crp_callback = ah_output_cb; - crp->crp_sid = sav->tdb_cryptoid; - crp->crp_opaque = (caddr_t) tc; + crp->crp_sid = cryptoid; + crp->crp_opaque = (caddr_t) xd; /* These are passed as-is to the callback. */ - tc->tc_isr = isr; - KEY_ADDREFSA(sav); - tc->tc_sav = sav; - tc->tc_spi = sav->spi; - tc->tc_dst = sav->sah->saidx.dst; - tc->tc_proto = sav->sah->saidx.proto; - tc->tc_skip = skip; - tc->tc_protoff = protoff; + xd->sp = sp; + xd->sav = sav; + xd->skip = skip; + xd->idx = idx; + xd->cryptoid = cryptoid; return crypto_dispatch(crp); bad: @@ -1133,46 +1055,37 @@ bad: static int ah_output_cb(struct cryptop *crp) { - int skip, protoff, error; - struct tdb_crypto *tc; - struct ipsecrequest *isr; + struct xform_data *xd; + struct secpolicy *sp; struct secasvar *sav; struct mbuf *m; + uint64_t cryptoid; caddr_t ptr; - int err; + u_int idx; + int skip, error; - tc = (struct tdb_crypto *) crp->crp_opaque; - IPSEC_ASSERT(tc != NULL, ("null opaque data area!")); - skip = tc->tc_skip; - protoff = tc->tc_protoff; - ptr = (caddr_t) (tc + 1); m = (struct mbuf *) crp->crp_buf; - - isr = tc->tc_isr; - IPSECREQUEST_LOCK(isr); - sav = tc->tc_sav; - /* With the isr lock released SA pointer can be updated. */ - if (sav != isr->sav) { - AHSTAT_INC(ahs_notdb); - DPRINTF(("%s: SA expired while in crypto\n", __func__)); - error = ENOBUFS; /*XXX*/ - goto bad; - } + xd = (struct xform_data *) crp->crp_opaque; + sp = xd->sp; + sav = xd->sav; + skip = xd->skip; + idx = xd->idx; + cryptoid = xd->cryptoid; + ptr = (caddr_t) (xd + 1); /* Check for crypto errors. */ if (crp->crp_etype) { - if (sav->tdb_cryptoid != 0) - sav->tdb_cryptoid = crp->crp_sid; - if (crp->crp_etype == EAGAIN) { - IPSECREQUEST_UNLOCK(isr); - error = crypto_dispatch(crp); - return error; + /* Reset the session ID */ + if (ipsec_updateid(sav, &crp->crp_sid, &cryptoid) != 0) + crypto_freesession(cryptoid); + xd->cryptoid = crp->crp_sid; + return (crypto_dispatch(crp)); } - AHSTAT_INC(ahs_noxform); DPRINTF(("%s: crypto error %d\n", __func__, crp->crp_etype)); error = crp->crp_etype; + m_freem(m); goto bad; } @@ -1183,18 +1096,15 @@ ah_output_cb(struct cryptop *crp) error = EINVAL; goto bad; } - AHSTAT_INC(ahs_hist[sav->alg_auth]); - /* * Copy original headers (with the new protocol number) back * in place. */ m_copyback(m, 0, skip, ptr); - /* No longer needed. */ - free(tc, M_XDATA); + free(xd, M_XDATA); crypto_freereq(crp); - + AHSTAT_INC(ahs_hist[sav->alg_auth]); #ifdef REGRESSION /* Emulate man-in-the-middle attack when ipsec_integrity is TRUE. */ if (V_ipsec_integrity) { @@ -1210,31 +1120,26 @@ ah_output_cb(struct cryptop *crp) #endif /* NB: m is reclaimed by ipsec_process_done. */ - err = ipsec_process_done(m, isr); - KEY_FREESAV(&sav); - IPSECREQUEST_UNLOCK(isr); - return err; + error = ipsec_process_done(m, sp, sav, idx); + return (error); bad: - if (sav) - KEY_FREESAV(&sav); - IPSECREQUEST_UNLOCK(isr); - if (m) - m_freem(m); - free(tc, M_XDATA); + free(xd, M_XDATA); crypto_freereq(crp); - return error; + key_freesav(&sav); + key_freesp(&sp); + return (error); } static struct xformsw ah_xformsw = { - XF_AH, XFT_AUTH, "IPsec AH", - ah_init, ah_zeroize, ah_input, ah_output, + .xf_type = XF_AH, + .xf_name = "IPsec AH", + .xf_init = ah_init, + .xf_zeroize = ah_zeroize, + .xf_input = ah_input, + .xf_output = ah_output, }; -static void -ah_attach(void) -{ - - xform_register(&ah_xformsw); -} - -SYSINIT(ah_xform_init, SI_SUB_PROTO_DOMAIN, SI_ORDER_MIDDLE, ah_attach, NULL); +SYSINIT(ah_xform_init, SI_SUB_PROTO_DOMAIN, SI_ORDER_MIDDLE, + xform_attach, &ah_xformsw); +SYSUNINIT(ah_xform_uninit, SI_SUB_PROTO_DOMAIN, SI_ORDER_MIDDLE, + xform_detach, &ah_xformsw); diff --git a/freebsd/sys/netipsec/xform_esp.c b/freebsd/sys/netipsec/xform_esp.c index 20790d0d..bf98dc03 100644 --- a/freebsd/sys/netipsec/xform_esp.c +++ b/freebsd/sys/netipsec/xform_esp.c @@ -46,8 +46,12 @@ #include <sys/socket.h> #include <sys/syslog.h> #include <sys/kernel.h> +#include <rtems/bsd/sys/lock.h> #include <sys/random.h> +#include <sys/mutex.h> #include <sys/sysctl.h> +#include <sys/mutex.h> +#include <machine/atomic.h> #include <net/if.h> #include <net/vnet.h> @@ -58,7 +62,6 @@ #include <netinet/ip_ecn.h> #include <netinet/ip6.h> -#include <net/route.h> #include <netipsec/ipsec.h> #include <netipsec/ah.h> #include <netipsec/ah_var.h> @@ -79,50 +82,23 @@ #include <opencrypto/xform.h> VNET_DEFINE(int, esp_enable) = 1; -VNET_DEFINE(struct espstat, espstat); +VNET_PCPUSTAT_DEFINE(struct espstat, espstat); +VNET_PCPUSTAT_SYSINIT(espstat); -SYSCTL_DECL(_net_inet_esp); -SYSCTL_VNET_INT(_net_inet_esp, OID_AUTO, - esp_enable, CTLFLAG_RW, &VNET_NAME(esp_enable), 0, ""); -SYSCTL_VNET_STRUCT(_net_inet_esp, IPSECCTL_STATS, - stats, CTLFLAG_RD, &VNET_NAME(espstat), espstat, ""); +#ifdef VIMAGE +VNET_PCPUSTAT_SYSUNINIT(espstat); +#endif /* VIMAGE */ -static VNET_DEFINE(int, esp_max_ivlen); /* max iv length over all algorithms */ -#define V_esp_max_ivlen VNET(esp_max_ivlen) +SYSCTL_DECL(_net_inet_esp); +SYSCTL_INT(_net_inet_esp, OID_AUTO, esp_enable, + CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(esp_enable), 0, ""); +SYSCTL_VNET_PCPUSTAT(_net_inet_esp, IPSECCTL_STATS, stats, + struct espstat, espstat, + "ESP statistics (struct espstat, netipsec/esp_var.h"); static int esp_input_cb(struct cryptop *op); static int esp_output_cb(struct cryptop *crp); -/* - * NB: this is public for use by the PF_KEY support. - * NB: if you add support here; be sure to add code to esp_attach below! - */ -struct enc_xform * -esp_algorithm_lookup(int alg) -{ - if (alg >= ESP_ALG_MAX) - return NULL; - switch (alg) { - case SADB_EALG_DESCBC: - return &enc_xform_des; - case SADB_EALG_3DESCBC: - return &enc_xform_3des; - case SADB_X_EALG_AES: - return &enc_xform_rijndael128; - case SADB_X_EALG_BLOWFISHCBC: - return &enc_xform_blf; - case SADB_X_EALG_CAST128CBC: - return &enc_xform_cast5; - case SADB_X_EALG_SKIPJACK: - return &enc_xform_skipjack; - case SADB_EALG_NULL: - return &enc_xform_null; - case SADB_X_EALG_CAMELLIACBC: - return &enc_xform_camellia; - } - return NULL; -} - size_t esp_hdrsiz(struct secasvar *sav) { @@ -149,7 +125,7 @@ esp_hdrsiz(struct secasvar *sav) * + sizeof (next header field) * + max icv supported. */ - size = sizeof (struct newesp) + V_esp_max_ivlen + 9 + 16; + size = sizeof (struct newesp) + EALG_MAX_BLOCK_LEN + 9 + 16; } return size; } @@ -160,12 +136,12 @@ esp_hdrsiz(struct secasvar *sav) static int esp_init(struct secasvar *sav, struct xformsw *xsp) { - struct enc_xform *txform; + const struct enc_xform *txform; struct cryptoini cria, crie; int keylen; int error; - txform = esp_algorithm_lookup(sav->alg_enc); + txform = enc_algorithm_lookup(sav->alg_enc); if (txform == NULL) { DPRINTF(("%s: unsupported encryption algorithm %d\n", __func__, sav->alg_enc)); @@ -176,12 +152,14 @@ esp_init(struct secasvar *sav, struct xformsw *xsp) __func__, txform->name)); return EINVAL; } - if ((sav->flags&(SADB_X_EXT_OLD|SADB_X_EXT_IV4B)) == SADB_X_EXT_IV4B) { + if ((sav->flags & (SADB_X_EXT_OLD | SADB_X_EXT_IV4B)) == + SADB_X_EXT_IV4B) { DPRINTF(("%s: 4-byte IV not supported with protocol\n", __func__)); return EINVAL; } - keylen = _KEYLEN(sav->key_enc); + /* subtract off the salt, RFC4106, 8.1 and RFC3686, 5.1 */ + keylen = _KEYLEN(sav->key_enc) - SAV_ISCTRORGCM(sav) * 4; if (txform->minkey > keylen || keylen > txform->maxkey) { DPRINTF(("%s: invalid key length %u, must be in the range " "[%u..%u] for algorithm %s\n", __func__, @@ -190,19 +168,10 @@ esp_init(struct secasvar *sav, struct xformsw *xsp) return EINVAL; } - /* - * NB: The null xform needs a non-zero blocksize to keep the - * crypto code happy but if we use it to set ivlen then - * the ESP header will be processed incorrectly. The - * compromise is to force it to zero here. - */ - sav->ivlen = (txform == &enc_xform_null ? 0 : txform->blocksize); - sav->iv = (caddr_t) malloc(sav->ivlen, M_XDATA, M_WAITOK); - if (sav->iv == NULL) { - DPRINTF(("%s: no memory for IV\n", __func__)); - return EINVAL; - } - key_randomfill(sav->iv, sav->ivlen); /*XXX*/ + if (SAV_ISCTRORGCM(sav)) + sav->ivlen = 8; /* RFC4106 3.1 and RFC3686 3.1 */ + else + sav->ivlen = txform->ivsize; /* * Setup AH-related state. @@ -217,12 +186,42 @@ esp_init(struct secasvar *sav, struct xformsw *xsp) sav->tdb_xform = xsp; sav->tdb_encalgxform = txform; + /* + * Whenever AES-GCM is used for encryption, one + * of the AES authentication algorithms is chosen + * as well, based on the key size. + */ + if (sav->alg_enc == SADB_X_EALG_AESGCM16) { + switch (keylen) { + case AES_128_GMAC_KEY_LEN: + sav->alg_auth = SADB_X_AALG_AES128GMAC; + sav->tdb_authalgxform = &auth_hash_nist_gmac_aes_128; + break; + case AES_192_GMAC_KEY_LEN: + sav->alg_auth = SADB_X_AALG_AES192GMAC; + sav->tdb_authalgxform = &auth_hash_nist_gmac_aes_192; + break; + case AES_256_GMAC_KEY_LEN: + sav->alg_auth = SADB_X_AALG_AES256GMAC; + sav->tdb_authalgxform = &auth_hash_nist_gmac_aes_256; + break; + default: + DPRINTF(("%s: invalid key length %u" + "for algorithm %s\n", __func__, + keylen, txform->name)); + return EINVAL; + } + bzero(&cria, sizeof(cria)); + cria.cri_alg = sav->tdb_authalgxform->type; + cria.cri_key = sav->key_enc->key_data; + cria.cri_klen = _KEYBITS(sav->key_enc) - SAV_ISGCM(sav) * 32; + } + /* Initialize crypto session. */ - bzero(&crie, sizeof (crie)); + bzero(&crie, sizeof(crie)); crie.cri_alg = sav->tdb_encalgxform->type; - crie.cri_klen = _KEYBITS(sav->key_enc); crie.cri_key = sav->key_enc->key_data; - /* XXX Rounds ? */ + crie.cri_klen = _KEYBITS(sav->key_enc) - SAV_ISCTRORGCM(sav) * 32; if (sav->tdb_authalgxform && sav->tdb_encalgxform) { /* init both auth & enc */ @@ -255,10 +254,6 @@ esp_zeroize(struct secasvar *sav) if (sav->key_enc) bzero(sav->key_enc->key_data, _KEYLEN(sav->key_enc)); - if (sav->iv) { - free(sav->iv, M_XDATA); - sav->iv = NULL; - } sav->tdb_encalgxform = NULL; sav->tdb_xform = NULL; return error; @@ -270,16 +265,16 @@ esp_zeroize(struct secasvar *sav) static int esp_input(struct mbuf *m, struct secasvar *sav, int skip, int protoff) { - struct auth_hash *esph; - struct enc_xform *espx; - struct tdb_ident *tdbi; - struct tdb_crypto *tc; - int plen, alen, hlen; - struct m_tag *mtag; - struct newesp *esp; - + char buf[128]; + const struct auth_hash *esph; + const struct enc_xform *espx; + struct xform_data *xd; struct cryptodesc *crde; struct cryptop *crp; + struct newesp *esp; + uint8_t *ivp; + uint64_t cryptoid; + int plen, alen, hlen; IPSEC_ASSERT(sav != NULL, ("null SA")); IPSEC_ASSERT(sav->tdb_encalgxform != NULL, ("null encoding xform")); @@ -292,32 +287,19 @@ esp_input(struct mbuf *m, struct secasvar *sav, int skip, int protoff) m_freem(m); return EINVAL; } - /* XXX don't pullup, just copy header */ IP6_EXTHDR_GET(esp, struct newesp *, m, skip, sizeof (struct newesp)); esph = sav->tdb_authalgxform; espx = sav->tdb_encalgxform; - /* Determine the ESP header length */ + /* Determine the ESP header and auth length */ if (sav->flags & SADB_X_EXT_OLD) hlen = sizeof (struct esp) + sav->ivlen; else hlen = sizeof (struct newesp) + sav->ivlen; - /* Authenticator hash size */ - if (esph != NULL) { - switch (esph->type) { - case CRYPTO_SHA2_256_HMAC: - case CRYPTO_SHA2_384_HMAC: - case CRYPTO_SHA2_512_HMAC: - alen = esph->hashsize/2; - break; - default: - alen = AH_HMAC_HASHLEN; - break; - } - }else - alen = 0; + + alen = xform_ah_authsize(esph); /* * Verify payload length is multiple of encryption algorithm @@ -330,10 +312,9 @@ esp_input(struct mbuf *m, struct secasvar *sav, int skip, int protoff) plen = m->m_pkthdr.len - (skip + hlen + alen); if ((plen & (espx->blocksize - 1)) || (plen <= 0)) { DPRINTF(("%s: payload of %d octets not a multiple of %d octets," - " SA %s/%08lx\n", __func__, - plen, espx->blocksize, - ipsec_address(&sav->sah->saidx.dst), - (u_long) ntohl(sav->spi))); + " SA %s/%08lx\n", __func__, plen, espx->blocksize, + ipsec_address(&sav->sah->saidx.dst, buf, sizeof(buf)), + (u_long)ntohl(sav->spi))); ESPSTAT_INC(esps_badilen); m_freem(m); return EINVAL; @@ -342,29 +323,23 @@ esp_input(struct mbuf *m, struct secasvar *sav, int skip, int protoff) /* * Check sequence number. */ - if (esph && sav->replay && !ipsec_chkreplay(ntohl(esp->esp_seq), sav)) { - DPRINTF(("%s: packet replay check for %s\n", __func__, - ipsec_logsastr(sav))); /*XXX*/ - ESPSTAT_INC(esps_replay); - m_freem(m); - return ENOBUFS; /*XXX*/ + SECASVAR_LOCK(sav); + if (esph != NULL && sav->replay != NULL && sav->replay->wsize != 0) { + if (ipsec_chkreplay(ntohl(esp->esp_seq), sav) == 0) { + SECASVAR_UNLOCK(sav); + DPRINTF(("%s: packet replay check for %s\n", __func__, + ipsec_sa2str(sav, buf, sizeof(buf)))); + ESPSTAT_INC(esps_replay); + m_freem(m); + return (EACCES); + } } + cryptoid = sav->tdb_cryptoid; + SECASVAR_UNLOCK(sav); /* Update the counters */ ESPSTAT_ADD(esps_ibytes, m->m_pkthdr.len - (skip + hlen + alen)); - /* Find out if we've already done crypto */ - for (mtag = m_tag_find(m, PACKET_TAG_IPSEC_IN_CRYPTO_DONE, NULL); - mtag != NULL; - mtag = m_tag_find(m, PACKET_TAG_IPSEC_IN_CRYPTO_DONE, mtag)) { - tdbi = (struct tdb_ident *) (mtag + 1); - if (tdbi->proto == sav->sah->saidx.proto && - tdbi->spi == sav->spi && - !bcmp(&tdbi->dst, &sav->sah->saidx.dst, - sizeof(union sockaddr_union))) - break; - } - /* Get crypto descriptors */ crp = crypto_getreq(esph && espx ? 2 : 1); if (crp == NULL) { @@ -376,40 +351,33 @@ esp_input(struct mbuf *m, struct secasvar *sav, int skip, int protoff) } /* Get IPsec-specific opaque pointer */ - if (esph == NULL || mtag != NULL) - tc = (struct tdb_crypto *) malloc(sizeof(struct tdb_crypto), - M_XDATA, M_NOWAIT|M_ZERO); - else - tc = (struct tdb_crypto *) malloc(sizeof(struct tdb_crypto) + alen, - M_XDATA, M_NOWAIT|M_ZERO); - if (tc == NULL) { - crypto_freereq(crp); - DPRINTF(("%s: failed to allocate tdb_crypto\n", __func__)); + xd = malloc(sizeof(*xd) + alen, M_XDATA, M_NOWAIT | M_ZERO); + if (xd == NULL) { + DPRINTF(("%s: failed to allocate xform_data\n", __func__)); ESPSTAT_INC(esps_crypto); + crypto_freereq(crp); m_freem(m); return ENOBUFS; } - tc->tc_ptr = (caddr_t) mtag; - - if (esph) { + if (esph != NULL) { struct cryptodesc *crda = crp->crp_desc; IPSEC_ASSERT(crda != NULL, ("null ah crypto descriptor")); /* Authentication descriptor */ crda->crd_skip = skip; - crda->crd_len = m->m_pkthdr.len - (skip + alen); + if (SAV_ISGCM(sav)) + crda->crd_len = 8; /* RFC4106 5, SPI + SN */ + else + crda->crd_len = m->m_pkthdr.len - (skip + alen); crda->crd_inject = m->m_pkthdr.len - alen; crda->crd_alg = esph->type; - crda->crd_key = sav->key_auth->key_data; - crda->crd_klen = _KEYBITS(sav->key_auth); /* Copy the authenticator */ - if (mtag == NULL) - m_copydata(m, m->m_pkthdr.len - alen, alen, - (caddr_t) (tc + 1)); + m_copydata(m, m->m_pkthdr.len - alen, alen, + (caddr_t) (xd + 1)); /* Chain authentication request */ crde = crda->crd_next; @@ -422,35 +390,43 @@ esp_input(struct mbuf *m, struct secasvar *sav, int skip, int protoff) crp->crp_flags = CRYPTO_F_IMBUF | CRYPTO_F_CBIFSYNC; crp->crp_buf = (caddr_t) m; crp->crp_callback = esp_input_cb; - crp->crp_sid = sav->tdb_cryptoid; - crp->crp_opaque = (caddr_t) tc; + crp->crp_sid = cryptoid; + crp->crp_opaque = (caddr_t) xd; /* These are passed as-is to the callback */ - tc->tc_spi = sav->spi; - tc->tc_dst = sav->sah->saidx.dst; - tc->tc_proto = sav->sah->saidx.proto; - tc->tc_protoff = protoff; - tc->tc_skip = skip; - KEY_ADDREFSA(sav); - tc->tc_sav = sav; + xd->sav = sav; + xd->protoff = protoff; + xd->skip = skip; + xd->cryptoid = cryptoid; /* Decryption descriptor */ - if (espx) { - IPSEC_ASSERT(crde != NULL, ("null esp crypto descriptor")); - crde->crd_skip = skip + hlen; - crde->crd_len = m->m_pkthdr.len - (skip + hlen + alen); - crde->crd_inject = skip + hlen - sav->ivlen; - - crde->crd_alg = espx->type; - crde->crd_key = sav->key_enc->key_data; - crde->crd_klen = _KEYBITS(sav->key_enc); - /* XXX Rounds ? */ + IPSEC_ASSERT(crde != NULL, ("null esp crypto descriptor")); + crde->crd_skip = skip + hlen; + crde->crd_len = m->m_pkthdr.len - (skip + hlen + alen); + crde->crd_inject = skip + hlen - sav->ivlen; + + if (SAV_ISCTRORGCM(sav)) { + ivp = &crde->crd_iv[0]; + + /* GCM IV Format: RFC4106 4 */ + /* CTR IV Format: RFC3686 4 */ + /* Salt is last four bytes of key, RFC4106 8.1 */ + /* Nonce is last four bytes of key, RFC3686 5.1 */ + memcpy(ivp, sav->key_enc->key_data + + _KEYLEN(sav->key_enc) - 4, 4); + + if (SAV_ISCTR(sav)) { + /* Initial block counter is 1, RFC3686 4 */ + be32enc(&ivp[sav->ivlen + 4], 1); + } + + m_copydata(m, skip + hlen - sav->ivlen, sav->ivlen, &ivp[4]); + crde->crd_flags |= CRD_F_IV_EXPLICIT; } - if (mtag == NULL) - return crypto_dispatch(crp); - else - return esp_input_cb(crp); + crde->crd_alg = espx->type; + + return (crypto_dispatch(crp)); } /* @@ -459,50 +435,41 @@ esp_input(struct mbuf *m, struct secasvar *sav, int skip, int protoff) static int esp_input_cb(struct cryptop *crp) { + char buf[128]; u_int8_t lastthree[3], aalg[AH_HMAC_MAXHASHLEN]; - int hlen, skip, protoff, error, alen; + const struct auth_hash *esph; + const struct enc_xform *espx; struct mbuf *m; struct cryptodesc *crd; - struct auth_hash *esph; - struct enc_xform *espx; - struct tdb_crypto *tc; - struct m_tag *mtag; + struct xform_data *xd; struct secasvar *sav; struct secasindex *saidx; caddr_t ptr; + uint64_t cryptoid; + int hlen, skip, protoff, error, alen; crd = crp->crp_desc; IPSEC_ASSERT(crd != NULL, ("null crypto descriptor!")); - tc = (struct tdb_crypto *) crp->crp_opaque; - IPSEC_ASSERT(tc != NULL, ("null opaque crypto data area!")); - skip = tc->tc_skip; - protoff = tc->tc_protoff; - mtag = (struct m_tag *) tc->tc_ptr; m = (struct mbuf *) crp->crp_buf; - - sav = tc->tc_sav; - IPSEC_ASSERT(sav != NULL, ("null SA!")); - + xd = (struct xform_data *) crp->crp_opaque; + sav = xd->sav; + skip = xd->skip; + protoff = xd->protoff; + cryptoid = xd->cryptoid; saidx = &sav->sah->saidx; - IPSEC_ASSERT(saidx->dst.sa.sa_family == AF_INET || - saidx->dst.sa.sa_family == AF_INET6, - ("unexpected protocol family %u", saidx->dst.sa.sa_family)); - esph = sav->tdb_authalgxform; espx = sav->tdb_encalgxform; /* Check for crypto errors */ if (crp->crp_etype) { - /* Reset the session ID */ - if (sav->tdb_cryptoid != 0) - sav->tdb_cryptoid = crp->crp_sid; - if (crp->crp_etype == EAGAIN) { - error = crypto_dispatch(crp); - return error; + /* Reset the session ID */ + if (ipsec_updateid(sav, &crp->crp_sid, &cryptoid) != 0) + crypto_freesession(cryptoid); + xd->cryptoid = crp->crp_sid; + return (crypto_dispatch(crp)); } - ESPSTAT_INC(esps_noxform); DPRINTF(("%s: crypto error %d\n", __func__, crp->crp_etype)); error = crp->crp_etype; @@ -520,48 +487,29 @@ esp_input_cb(struct cryptop *crp) /* If authentication was performed, check now. */ if (esph != NULL) { - switch (esph->type) { - case CRYPTO_SHA2_256_HMAC: - case CRYPTO_SHA2_384_HMAC: - case CRYPTO_SHA2_512_HMAC: - alen = esph->hashsize/2; - break; - default: - alen = AH_HMAC_HASHLEN; - break; - } - /* - * If we have a tag, it means an IPsec-aware NIC did - * the verification for us. Otherwise we need to - * check the authentication calculation. - */ + alen = xform_ah_authsize(esph); AHSTAT_INC(ahs_hist[sav->alg_auth]); - if (mtag == NULL) { - /* Copy the authenticator from the packet */ - m_copydata(m, m->m_pkthdr.len - alen, - alen, aalg); - - ptr = (caddr_t) (tc + 1); - - /* Verify authenticator */ - if (bcmp(ptr, aalg, alen) != 0) { - DPRINTF(("%s: " - "authentication hash mismatch for packet in SA %s/%08lx\n", - __func__, - ipsec_address(&saidx->dst), - (u_long) ntohl(sav->spi))); - ESPSTAT_INC(esps_badauth); - error = EACCES; - goto bad; - } + /* Copy the authenticator from the packet */ + m_copydata(m, m->m_pkthdr.len - alen, alen, aalg); + ptr = (caddr_t) (xd + 1); + + /* Verify authenticator */ + if (timingsafe_bcmp(ptr, aalg, alen) != 0) { + DPRINTF(("%s: authentication hash mismatch for " + "packet in SA %s/%08lx\n", __func__, + ipsec_address(&saidx->dst, buf, sizeof(buf)), + (u_long) ntohl(sav->spi))); + ESPSTAT_INC(esps_badauth); + error = EACCES; + goto bad; } - + m->m_flags |= M_AUTHIPDGM; /* Remove trailing authenticator */ m_adj(m, -alen); } /* Release the crypto descriptors */ - free(tc, M_XDATA), tc = NULL; + free(xd, M_XDATA), xd = NULL; crypto_freereq(crp), crp = NULL; /* @@ -577,13 +525,16 @@ esp_input_cb(struct cryptop *crp) m_copydata(m, skip + offsetof(struct newesp, esp_seq), sizeof (seq), (caddr_t) &seq); + SECASVAR_LOCK(sav); if (ipsec_updatereplay(ntohl(seq), sav)) { + SECASVAR_UNLOCK(sav); DPRINTF(("%s: packet replay check for %s\n", __func__, - ipsec_logsastr(sav))); + ipsec_sa2str(sav, buf, sizeof(buf)))); ESPSTAT_INC(esps_replay); - error = ENOBUFS; + error = EACCES; goto bad; } + SECASVAR_UNLOCK(sav); } /* Determine the ESP header length */ @@ -597,7 +548,7 @@ esp_input_cb(struct cryptop *crp) if (error) { ESPSTAT_INC(esps_hdrops); DPRINTF(("%s: bad mbuf chain, SA %s/%08lx\n", __func__, - ipsec_address(&sav->sah->saidx.dst), + ipsec_address(&sav->sah->saidx.dst, buf, sizeof(buf)), (u_long) ntohl(sav->spi))); goto bad; } @@ -609,10 +560,10 @@ esp_input_cb(struct cryptop *crp) if (lastthree[1] + 2 > m->m_pkthdr.len - skip) { ESPSTAT_INC(esps_badilen); DPRINTF(("%s: invalid padding length %d for %u byte packet " - "in SA %s/%08lx\n", __func__, - lastthree[1], m->m_pkthdr.len - skip, - ipsec_address(&sav->sah->saidx.dst), - (u_long) ntohl(sav->spi))); + "in SA %s/%08lx\n", __func__, lastthree[1], + m->m_pkthdr.len - skip, + ipsec_address(&sav->sah->saidx.dst, buf, sizeof(buf)), + (u_long) ntohl(sav->spi))); error = EINVAL; goto bad; } @@ -622,9 +573,9 @@ esp_input_cb(struct cryptop *crp) if (lastthree[1] != lastthree[0] && lastthree[1] != 0) { ESPSTAT_INC(esps_badenc); DPRINTF(("%s: decryption failed for packet in " - "SA %s/%08lx\n", __func__, - ipsec_address(&sav->sah->saidx.dst), - (u_long) ntohl(sav->spi))); + "SA %s/%08lx\n", __func__, ipsec_address( + &sav->sah->saidx.dst, buf, sizeof(buf)), + (u_long) ntohl(sav->spi))); error = EINVAL; goto bad; } @@ -639,60 +590,52 @@ esp_input_cb(struct cryptop *crp) switch (saidx->dst.sa.sa_family) { #ifdef INET6 case AF_INET6: - error = ipsec6_common_input_cb(m, sav, skip, protoff, mtag); + error = ipsec6_common_input_cb(m, sav, skip, protoff); break; #endif #ifdef INET case AF_INET: - error = ipsec4_common_input_cb(m, sav, skip, protoff, mtag); + error = ipsec4_common_input_cb(m, sav, skip, protoff); break; #endif default: panic("%s: Unexpected address family: %d saidx=%p", __func__, saidx->dst.sa.sa_family, saidx); } - - KEY_FREESAV(&sav); return error; bad: - if (sav) - KEY_FREESAV(&sav); + if (sav != NULL) + key_freesav(&sav); if (m != NULL) m_freem(m); - if (tc != NULL) - free(tc, M_XDATA); + if (xd != NULL) + free(xd, M_XDATA); if (crp != NULL) crypto_freereq(crp); return error; } - /* - * ESP output routine, called by ipsec[46]_process_packet(). + * ESP output routine, called by ipsec[46]_perform_request(). */ static int -esp_output( - struct mbuf *m, - struct ipsecrequest *isr, - struct mbuf **mp, - int skip, - int protoff -) +esp_output(struct mbuf *m, struct secpolicy *sp, struct secasvar *sav, + u_int idx, int skip, int protoff) { - struct enc_xform *espx; - struct auth_hash *esph; - int hlen, rlen, plen, padding, blks, alen, i, roff; - struct mbuf *mo = (struct mbuf *) NULL; - struct tdb_crypto *tc; - struct secasvar *sav; + char buf[IPSEC_ADDRSTRLEN]; + struct cryptodesc *crde = NULL, *crda = NULL; + struct cryptop *crp; + const struct auth_hash *esph; + const struct enc_xform *espx; + struct mbuf *mo = NULL; + struct xform_data *xd; struct secasindex *saidx; unsigned char *pad; - u_int8_t prot; + uint8_t *ivp; + uint64_t cntr, cryptoid; + int hlen, rlen, padding, blks, alen, i, roff; int error, maxpacketsize; + uint8_t prot; - struct cryptodesc *crde = NULL, *crda = NULL; - struct cryptop *crp; - - sav = isr->sav; IPSEC_ASSERT(sav != NULL, ("null SA")); esph = sav->tdb_authalgxform; espx = sav->tdb_encalgxform; @@ -705,28 +648,14 @@ esp_output( rlen = m->m_pkthdr.len - skip; /* Raw payload length. */ /* - * NB: The null encoding transform has a blocksize of 4 - * so that headers are properly aligned. + * RFC4303 2.4 Requires 4 byte alignment. */ - blks = espx->blocksize; /* IV blocksize */ + blks = MAX(4, espx->blocksize); /* Cipher blocksize */ /* XXX clamp padding length a la KAME??? */ padding = ((blks - ((rlen + 2) % blks)) % blks) + 2; - plen = rlen + padding; /* Padded payload length. */ - - if (esph) - switch (esph->type) { - case CRYPTO_SHA2_256_HMAC: - case CRYPTO_SHA2_384_HMAC: - case CRYPTO_SHA2_512_HMAC: - alen = esph->hashsize/2; - break; - default: - alen = AH_HMAC_HASHLEN; - break; - } - else - alen = 0; + + alen = xform_ah_authsize(esph); ESPSTAT_INC(esps_output); @@ -746,16 +675,20 @@ esp_output( default: DPRINTF(("%s: unknown/unsupported protocol " "family %d, SA %s/%08lx\n", __func__, - saidx->dst.sa.sa_family, ipsec_address(&saidx->dst), - (u_long) ntohl(sav->spi))); + saidx->dst.sa.sa_family, ipsec_address(&saidx->dst, + buf, sizeof(buf)), (u_long) ntohl(sav->spi))); ESPSTAT_INC(esps_nopf); error = EPFNOSUPPORT; goto bad; } + /* + DPRINTF(("%s: skip %d hlen %d rlen %d padding %d alen %d blksd %d\n", + __func__, skip, hlen, rlen, padding, alen, blks)); */ if (skip + hlen + rlen + padding + alen > maxpacketsize) { DPRINTF(("%s: packet in SA %s/%08lx got too big " "(len %u, max len %u)\n", __func__, - ipsec_address(&saidx->dst), (u_long) ntohl(sav->spi), + ipsec_address(&saidx->dst, buf, sizeof(buf)), + (u_long) ntohl(sav->spi), skip + hlen + rlen + padding + alen, maxpacketsize)); ESPSTAT_INC(esps_toobig); error = EMSGSIZE; @@ -768,7 +701,8 @@ esp_output( m = m_unshare(m, M_NOWAIT); if (m == NULL) { DPRINTF(("%s: cannot clone mbuf chain, SA %s/%08lx\n", __func__, - ipsec_address(&saidx->dst), (u_long) ntohl(sav->spi))); + ipsec_address(&saidx->dst, buf, sizeof(buf)), + (u_long) ntohl(sav->spi))); ESPSTAT_INC(esps_hdrops); error = ENOBUFS; goto bad; @@ -778,17 +712,19 @@ esp_output( mo = m_makespace(m, skip, hlen, &roff); if (mo == NULL) { DPRINTF(("%s: %u byte ESP hdr inject failed for SA %s/%08lx\n", - __func__, hlen, ipsec_address(&saidx->dst), - (u_long) ntohl(sav->spi))); - ESPSTAT_INC(esps_hdrops); /* XXX diffs from openbsd */ + __func__, hlen, ipsec_address(&saidx->dst, buf, + sizeof(buf)), (u_long) ntohl(sav->spi))); + ESPSTAT_INC(esps_hdrops); /* XXX diffs from openbsd */ error = ENOBUFS; goto bad; } /* Initialize ESP header. */ - bcopy((caddr_t) &sav->spi, mtod(mo, caddr_t) + roff, sizeof(u_int32_t)); + bcopy((caddr_t) &sav->spi, mtod(mo, caddr_t) + roff, + sizeof(uint32_t)); + SECASVAR_LOCK(sav); if (sav->replay) { - u_int32_t replay; + uint32_t replay; #ifdef REGRESSION /* Emulate replay attack when ipsec_replay is TRUE. */ @@ -796,10 +732,14 @@ esp_output( #endif sav->replay->count++; replay = htonl(sav->replay->count); - bcopy((caddr_t) &replay, - mtod(mo, caddr_t) + roff + sizeof(u_int32_t), - sizeof(u_int32_t)); + + bcopy((caddr_t) &replay, mtod(mo, caddr_t) + roff + + sizeof(uint32_t), sizeof(uint32_t)); } + cryptoid = sav->tdb_cryptoid; + if (SAV_ISCTRORGCM(sav)) + cntr = sav->cntr++; + SECASVAR_UNLOCK(sav); /* * Add padding -- better to do it ourselves than use the crypto engine, @@ -808,7 +748,8 @@ esp_output( pad = (u_char *) m_pad(m, padding + alen); if (pad == NULL) { DPRINTF(("%s: m_pad failed for SA %s/%08lx\n", __func__, - ipsec_address(&saidx->dst), (u_long) ntohl(sav->spi))); + ipsec_address(&saidx->dst, buf, sizeof(buf)), + (u_long) ntohl(sav->spi))); m = NULL; /* NB: free'd by m_pad */ error = ENOBUFS; goto bad; @@ -840,7 +781,7 @@ esp_output( m_copyback(m, protoff, sizeof(u_int8_t), (u_char *) &prot); /* Get crypto descriptors. */ - crp = crypto_getreq(esph && espx ? 2 : 1); + crp = crypto_getreq(esph != NULL ? 2 : 1); if (crp == NULL) { DPRINTF(("%s: failed to acquire crypto descriptors\n", __func__)); @@ -849,61 +790,70 @@ esp_output( goto bad; } - if (espx) { - crde = crp->crp_desc; - crda = crde->crd_next; - - /* Encryption descriptor. */ - crde->crd_skip = skip + hlen; - crde->crd_len = m->m_pkthdr.len - (skip + hlen + alen); - crde->crd_flags = CRD_F_ENCRYPT; - crde->crd_inject = skip + hlen - sav->ivlen; - - /* Encryption operation. */ - crde->crd_alg = espx->type; - crde->crd_key = sav->key_enc->key_data; - crde->crd_klen = _KEYBITS(sav->key_enc); - /* XXX Rounds ? */ - } else - crda = crp->crp_desc; - /* IPsec-specific opaque crypto info. */ - tc = (struct tdb_crypto *) malloc(sizeof(struct tdb_crypto), - M_XDATA, M_NOWAIT|M_ZERO); - if (tc == NULL) { + xd = malloc(sizeof(struct xform_data), M_XDATA, M_NOWAIT | M_ZERO); + if (xd == NULL) { crypto_freereq(crp); - DPRINTF(("%s: failed to allocate tdb_crypto\n", __func__)); + DPRINTF(("%s: failed to allocate xform_data\n", __func__)); ESPSTAT_INC(esps_crypto); error = ENOBUFS; goto bad; } + crde = crp->crp_desc; + crda = crde->crd_next; + + /* Encryption descriptor. */ + crde->crd_skip = skip + hlen; + crde->crd_len = m->m_pkthdr.len - (skip + hlen + alen); + crde->crd_flags = CRD_F_ENCRYPT; + crde->crd_inject = skip + hlen - sav->ivlen; + + /* Encryption operation. */ + crde->crd_alg = espx->type; + if (SAV_ISCTRORGCM(sav)) { + ivp = &crde->crd_iv[0]; + + /* GCM IV Format: RFC4106 4 */ + /* CTR IV Format: RFC3686 4 */ + /* Salt is last four bytes of key, RFC4106 8.1 */ + /* Nonce is last four bytes of key, RFC3686 5.1 */ + memcpy(ivp, sav->key_enc->key_data + + _KEYLEN(sav->key_enc) - 4, 4); + be64enc(&ivp[4], cntr); + if (SAV_ISCTR(sav)) { + /* Initial block counter is 1, RFC3686 4 */ + /* XXXAE: should we use this only for first packet? */ + be32enc(&ivp[sav->ivlen + 4], 1); + } + + m_copyback(m, skip + hlen - sav->ivlen, sav->ivlen, &ivp[4]); + crde->crd_flags |= CRD_F_IV_EXPLICIT|CRD_F_IV_PRESENT; + } + /* Callback parameters */ - tc->tc_isr = isr; - KEY_ADDREFSA(sav); - tc->tc_sav = sav; - tc->tc_spi = sav->spi; - tc->tc_dst = saidx->dst; - tc->tc_proto = saidx->proto; + xd->sp = sp; + xd->sav = sav; + xd->idx = idx; + xd->cryptoid = cryptoid; /* Crypto operation descriptor. */ crp->crp_ilen = m->m_pkthdr.len; /* Total input length. */ crp->crp_flags = CRYPTO_F_IMBUF | CRYPTO_F_CBIFSYNC; crp->crp_buf = (caddr_t) m; crp->crp_callback = esp_output_cb; - crp->crp_opaque = (caddr_t) tc; - crp->crp_sid = sav->tdb_cryptoid; + crp->crp_opaque = (caddr_t) xd; + crp->crp_sid = cryptoid; if (esph) { /* Authentication descriptor. */ + crda->crd_alg = esph->type; crda->crd_skip = skip; - crda->crd_len = m->m_pkthdr.len - (skip + alen); + if (SAV_ISGCM(sav)) + crda->crd_len = 8; /* RFC4106 5, SPI + SN */ + else + crda->crd_len = m->m_pkthdr.len - (skip + alen); crda->crd_inject = m->m_pkthdr.len - alen; - - /* Authentication operation. */ - crda->crd_alg = esph->type; - crda->crd_key = sav->key_auth->key_data; - crda->crd_klen = _KEYBITS(sav->key_auth); } return crypto_dispatch(crp); @@ -912,51 +862,40 @@ bad: m_freem(m); return (error); } - /* * ESP output callback from the crypto driver. */ static int esp_output_cb(struct cryptop *crp) { - struct tdb_crypto *tc; - struct ipsecrequest *isr; + struct xform_data *xd; + struct secpolicy *sp; struct secasvar *sav; struct mbuf *m; - int err, error; + uint64_t cryptoid; + u_int idx; + int error; - tc = (struct tdb_crypto *) crp->crp_opaque; - IPSEC_ASSERT(tc != NULL, ("null opaque data area!")); + xd = (struct xform_data *) crp->crp_opaque; m = (struct mbuf *) crp->crp_buf; - - isr = tc->tc_isr; - IPSECREQUEST_LOCK(isr); - sav = tc->tc_sav; - /* With the isr lock released SA pointer can be updated. */ - if (sav != isr->sav) { - ESPSTAT_INC(esps_notdb); - DPRINTF(("%s: SA gone during crypto (SA %s/%08lx proto %u)\n", - __func__, ipsec_address(&tc->tc_dst), - (u_long) ntohl(tc->tc_spi), tc->tc_proto)); - error = ENOBUFS; /*XXX*/ - goto bad; - } + sp = xd->sp; + sav = xd->sav; + idx = xd->idx; + cryptoid = xd->cryptoid; /* Check for crypto errors. */ if (crp->crp_etype) { - /* Reset session ID. */ - if (sav->tdb_cryptoid != 0) - sav->tdb_cryptoid = crp->crp_sid; - if (crp->crp_etype == EAGAIN) { - IPSECREQUEST_UNLOCK(isr); - error = crypto_dispatch(crp); - return error; + /* Reset the session ID */ + if (ipsec_updateid(sav, &crp->crp_sid, &cryptoid) != 0) + crypto_freesession(cryptoid); + xd->cryptoid = crp->crp_sid; + return (crypto_dispatch(crp)); } - ESPSTAT_INC(esps_noxform); DPRINTF(("%s: crypto error %d\n", __func__, crp->crp_etype)); error = crp->crp_etype; + m_freem(m); goto bad; } @@ -967,19 +906,17 @@ esp_output_cb(struct cryptop *crp) error = EINVAL; goto bad; } + free(xd, M_XDATA); + crypto_freereq(crp); ESPSTAT_INC(esps_hist[sav->alg_enc]); if (sav->tdb_authalgxform != NULL) AHSTAT_INC(ahs_hist[sav->alg_auth]); - /* Release crypto descriptors. */ - free(tc, M_XDATA); - crypto_freereq(crp); - #ifdef REGRESSION /* Emulate man-in-the-middle attack when ipsec_integrity is TRUE. */ if (V_ipsec_integrity) { static unsigned char ipseczeroes[AH_HMAC_MAXHASHLEN]; - struct auth_hash *esph; + const struct auth_hash *esph; /* * Corrupt HMAC if we want to test integrity verification of @@ -989,16 +926,7 @@ esp_output_cb(struct cryptop *crp) if (esph != NULL) { int alen; - switch (esph->type) { - case CRYPTO_SHA2_256_HMAC: - case CRYPTO_SHA2_384_HMAC: - case CRYPTO_SHA2_512_HMAC: - alen = esph->hashsize/2; - break; - default: - alen = AH_HMAC_HASHLEN; - break; - } + alen = xform_ah_authsize(esph); m_copyback(m, m->m_pkthdr.len - alen, alen, ipseczeroes); } @@ -1006,44 +934,26 @@ esp_output_cb(struct cryptop *crp) #endif /* NB: m is reclaimed by ipsec_process_done. */ - err = ipsec_process_done(m, isr); - KEY_FREESAV(&sav); - IPSECREQUEST_UNLOCK(isr); - return err; + error = ipsec_process_done(m, sp, sav, idx); + return (error); bad: - if (sav) - KEY_FREESAV(&sav); - IPSECREQUEST_UNLOCK(isr); - if (m) - m_freem(m); - free(tc, M_XDATA); + free(xd, M_XDATA); crypto_freereq(crp); - return error; + key_freesav(&sav); + key_freesp(&sp); + return (error); } static struct xformsw esp_xformsw = { - XF_ESP, XFT_CONF|XFT_AUTH, "IPsec ESP", - esp_init, esp_zeroize, esp_input, - esp_output + .xf_type = XF_ESP, + .xf_name = "IPsec ESP", + .xf_init = esp_init, + .xf_zeroize = esp_zeroize, + .xf_input = esp_input, + .xf_output = esp_output, }; -static void -esp_attach(void) -{ -#define MAXIV(xform) \ - if (xform.blocksize > V_esp_max_ivlen) \ - V_esp_max_ivlen = xform.blocksize \ - - MAXIV(enc_xform_des); /* SADB_EALG_DESCBC */ - MAXIV(enc_xform_3des); /* SADB_EALG_3DESCBC */ - MAXIV(enc_xform_rijndael128); /* SADB_X_EALG_AES */ - MAXIV(enc_xform_blf); /* SADB_X_EALG_BLOWFISHCBC */ - MAXIV(enc_xform_cast5); /* SADB_X_EALG_CAST128CBC */ - MAXIV(enc_xform_skipjack); /* SADB_X_EALG_SKIPJACK */ - MAXIV(enc_xform_null); /* SADB_EALG_NULL */ - MAXIV(enc_xform_camellia); /* SADB_X_EALG_CAMELLIACBC */ - - xform_register(&esp_xformsw); -#undef MAXIV -} -SYSINIT(esp_xform_init, SI_SUB_PROTO_DOMAIN, SI_ORDER_MIDDLE, esp_attach, NULL); +SYSINIT(esp_xform_init, SI_SUB_PROTO_DOMAIN, SI_ORDER_MIDDLE, + xform_attach, &esp_xformsw); +SYSUNINIT(esp_xform_uninit, SI_SUB_PROTO_DOMAIN, SI_ORDER_MIDDLE, + xform_detach, &esp_xformsw); diff --git a/freebsd/sys/netipsec/xform_ipcomp.c b/freebsd/sys/netipsec/xform_ipcomp.c index 2478c948..f5a7aad4 100644 --- a/freebsd/sys/netipsec/xform_ipcomp.c +++ b/freebsd/sys/netipsec/xform_ipcomp.c @@ -39,7 +39,6 @@ #include <sys/mbuf.h> #include <rtems/bsd/sys/lock.h> #include <sys/mutex.h> -#include <sys/rwlock.h> #include <sys/socket.h> #include <sys/kernel.h> #include <sys/protosw.h> @@ -49,8 +48,9 @@ #include <netinet/in_systm.h> #include <netinet/ip.h> #include <netinet/ip_var.h> +#include <netinet/ip_encap.h> -#include <net/route.h> +#include <net/netisr.h> #include <net/vnet.h> #include <netipsec/ipsec.h> @@ -58,6 +58,7 @@ #ifdef INET6 #include <netinet/ip6.h> +#include <netinet6/ip6_var.h> #include <netipsec/ipsec6.h> #endif @@ -72,27 +73,76 @@ #include <opencrypto/xform.h> VNET_DEFINE(int, ipcomp_enable) = 1; -VNET_DEFINE(struct ipcompstat, ipcompstat); +VNET_PCPUSTAT_DEFINE(struct ipcompstat, ipcompstat); +VNET_PCPUSTAT_SYSINIT(ipcompstat); + +#ifdef VIMAGE +VNET_PCPUSTAT_SYSUNINIT(ipcompstat); +#endif /* VIMAGE */ SYSCTL_DECL(_net_inet_ipcomp); -SYSCTL_VNET_INT(_net_inet_ipcomp, OID_AUTO, - ipcomp_enable, CTLFLAG_RW, &VNET_NAME(ipcomp_enable), 0, ""); -SYSCTL_VNET_STRUCT(_net_inet_ipcomp, IPSECCTL_STATS, - stats, CTLFLAG_RD, &VNET_NAME(ipcompstat), ipcompstat, ""); +SYSCTL_INT(_net_inet_ipcomp, OID_AUTO, ipcomp_enable, + CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(ipcomp_enable), 0, ""); +SYSCTL_VNET_PCPUSTAT(_net_inet_ipcomp, IPSECCTL_STATS, stats, + struct ipcompstat, ipcompstat, + "IPCOMP statistics (struct ipcompstat, netipsec/ipcomp_var.h"); static int ipcomp_input_cb(struct cryptop *crp); static int ipcomp_output_cb(struct cryptop *crp); -struct comp_algo * -ipcomp_algorithm_lookup(int alg) +/* + * RFC 3173 p 2.2. Non-Expansion Policy: + * If the total size of a compressed payload and the IPComp header, as + * defined in section 3, is not smaller than the size of the original + * payload, the IP datagram MUST be sent in the original non-compressed + * form. + * + * When we use IPComp in tunnel mode, for small packets we will receive + * encapsulated IP-IP datagrams without any compression and without IPComp + * header. + */ +static int +ipcomp_encapcheck(union sockaddr_union *src, union sockaddr_union *dst) +{ + struct secasvar *sav; + + sav = key_allocsa_tunnel(src, dst, IPPROTO_IPCOMP); + if (sav == NULL) + return (0); + key_freesav(&sav); + + if (src->sa.sa_family == AF_INET) + return (sizeof(struct in_addr) << 4); + else + return (sizeof(struct in6_addr) << 4); +} + +static int +ipcomp_nonexp_input(struct mbuf **mp, int *offp, int proto) { - if (alg >= IPCOMP_ALG_MAX) - return NULL; - switch (alg) { - case SADB_X_CALG_DEFLATE: - return &comp_algo_deflate; + int isr; + + switch (proto) { +#ifdef INET + case IPPROTO_IPV4: + isr = NETISR_IP; + break; +#endif +#ifdef INET6 + case IPPROTO_IPV6: + isr = NETISR_IPV6; + break; +#endif + default: + IPCOMPSTAT_INC(ipcomps_nopf); + m_freem(*mp); + return (IPPROTO_DONE); } - return NULL; + m_adj(*mp, *offp); + IPCOMPSTAT_ADD(ipcomps_ibytes, (*mp)->m_pkthdr.len); + IPCOMPSTAT_INC(ipcomps_input); + netisr_dispatch(isr, *mp); + return (IPPROTO_DONE); } /* @@ -101,11 +151,11 @@ ipcomp_algorithm_lookup(int alg) static int ipcomp_init(struct secasvar *sav, struct xformsw *xsp) { - struct comp_algo *tcomp; + const struct comp_algo *tcomp; struct cryptoini cric; /* NB: algorithm really comes in alg_enc and not alg_comp! */ - tcomp = ipcomp_algorithm_lookup(sav->alg_enc); + tcomp = comp_algorithm_lookup(sav->alg_enc); if (tcomp == NULL) { DPRINTF(("%s: unsupported compression algorithm %d\n", __func__, sav->alg_comp)); @@ -141,7 +191,7 @@ ipcomp_zeroize(struct secasvar *sav) static int ipcomp_input(struct mbuf *m, struct secasvar *sav, int skip, int protoff) { - struct tdb_crypto *tc; + struct xform_data *xd; struct cryptodesc *crdc; struct cryptop *crp; struct ipcomp *ipcomp; @@ -176,12 +226,12 @@ ipcomp_input(struct mbuf *m, struct secasvar *sav, int skip, int protoff) return ENOBUFS; } /* Get IPsec-specific opaque pointer */ - tc = (struct tdb_crypto *) malloc(sizeof (*tc), M_XDATA, M_NOWAIT|M_ZERO); - if (tc == NULL) { - m_freem(m); - crypto_freereq(crp); - DPRINTF(("%s: cannot allocate tdb_crypto\n", __func__)); + xd = malloc(sizeof(*xd), M_XDATA, M_NOWAIT | M_ZERO); + if (xd == NULL) { + DPRINTF(("%s: cannot allocate xform_data\n", __func__)); IPCOMPSTAT_INC(ipcomps_crypto); + crypto_freereq(crp); + m_freem(m); return ENOBUFS; } crdc = crp->crp_desc; @@ -190,27 +240,25 @@ ipcomp_input(struct mbuf *m, struct secasvar *sav, int skip, int protoff) crdc->crd_len = m->m_pkthdr.len - (skip + hlen); crdc->crd_inject = skip; - tc->tc_ptr = 0; - /* Decompression operation */ crdc->crd_alg = sav->tdb_compalgxform->type; + /* Crypto operation descriptor */ crp->crp_ilen = m->m_pkthdr.len - (skip + hlen); crp->crp_flags = CRYPTO_F_IMBUF | CRYPTO_F_CBIFSYNC; crp->crp_buf = (caddr_t) m; crp->crp_callback = ipcomp_input_cb; - crp->crp_sid = sav->tdb_cryptoid; - crp->crp_opaque = (caddr_t) tc; + crp->crp_opaque = (caddr_t) xd; /* These are passed as-is to the callback */ - tc->tc_spi = sav->spi; - tc->tc_dst = sav->sah->saidx.dst; - tc->tc_proto = sav->sah->saidx.proto; - tc->tc_protoff = protoff; - tc->tc_skip = skip; - KEY_ADDREFSA(sav); - tc->tc_sav = sav; + xd->sav = sav; + xd->protoff = protoff; + xd->skip = skip; + + SECASVAR_LOCK(sav); + crp->crp_sid = xd->cryptoid = sav->tdb_cryptoid; + SECASVAR_UNLOCK(sav); return crypto_dispatch(crp); } @@ -221,29 +269,26 @@ ipcomp_input(struct mbuf *m, struct secasvar *sav, int skip, int protoff) static int ipcomp_input_cb(struct cryptop *crp) { + char buf[IPSEC_ADDRSTRLEN]; struct cryptodesc *crd; - struct tdb_crypto *tc; - int skip, protoff; - struct mtag *mtag; + struct xform_data *xd; struct mbuf *m; struct secasvar *sav; struct secasindex *saidx; - int hlen = IPCOMP_HLENGTH, error, clen; - u_int8_t nproto; caddr_t addr; + uint64_t cryptoid; + int hlen = IPCOMP_HLENGTH, error, clen; + int skip, protoff; + uint8_t nproto; crd = crp->crp_desc; - tc = (struct tdb_crypto *) crp->crp_opaque; - IPSEC_ASSERT(tc != NULL, ("null opaque crypto data area!")); - skip = tc->tc_skip; - protoff = tc->tc_protoff; - mtag = (struct mtag *) tc->tc_ptr; m = (struct mbuf *) crp->crp_buf; - - sav = tc->tc_sav; - IPSEC_ASSERT(sav != NULL, ("null SA!")); - + xd = (struct xform_data *) crp->crp_opaque; + sav = xd->sav; + skip = xd->skip; + protoff = xd->protoff; + cryptoid = xd->cryptoid; saidx = &sav->sah->saidx; IPSEC_ASSERT(saidx->dst.sa.sa_family == AF_INET || saidx->dst.sa.sa_family == AF_INET6, @@ -251,12 +296,12 @@ ipcomp_input_cb(struct cryptop *crp) /* Check for crypto errors */ if (crp->crp_etype) { - /* Reset the session ID */ - if (sav->tdb_cryptoid != 0) - sav->tdb_cryptoid = crp->crp_sid; - if (crp->crp_etype == EAGAIN) { - return crypto_dispatch(crp); + /* Reset the session ID */ + if (ipsec_updateid(sav, &crp->crp_sid, &cryptoid) != 0) + crypto_freesession(cryptoid); + xd->cryptoid = crp->crp_sid; + return (crypto_dispatch(crp)); } IPCOMPSTAT_INC(ipcomps_noxform); DPRINTF(("%s: crypto error %d\n", __func__, crp->crp_etype)); @@ -275,13 +320,13 @@ ipcomp_input_cb(struct cryptop *crp) clen = crp->crp_olen; /* Length of data after processing */ /* Release the crypto descriptors */ - free(tc, M_XDATA), tc = NULL; + free(xd, M_XDATA), xd = NULL; crypto_freereq(crp), crp = NULL; /* In case it's not done already, adjust the size of the mbuf chain */ m->m_pkthdr.len = clen + hlen + skip; - if (m->m_len < skip + hlen && (m = m_pullup(m, skip + hlen)) == 0) { + if (m->m_len < skip + hlen && (m = m_pullup(m, skip + hlen)) == NULL) { IPCOMPSTAT_INC(ipcomps_hdrops); /*XXX*/ DPRINTF(("%s: m_pullup failed\n", __func__)); error = EINVAL; /*XXX*/ @@ -297,8 +342,8 @@ ipcomp_input_cb(struct cryptop *crp) if (error) { IPCOMPSTAT_INC(ipcomps_hdrops); DPRINTF(("%s: bad mbuf chain, IPCA %s/%08lx\n", __func__, - ipsec_address(&sav->sah->saidx.dst), - (u_long) ntohl(sav->spi))); + ipsec_address(&sav->sah->saidx.dst, buf, sizeof(buf)), + (u_long) ntohl(sav->spi))); goto bad; } @@ -308,53 +353,45 @@ ipcomp_input_cb(struct cryptop *crp) switch (saidx->dst.sa.sa_family) { #ifdef INET6 case AF_INET6: - error = ipsec6_common_input_cb(m, sav, skip, protoff, NULL); + error = ipsec6_common_input_cb(m, sav, skip, protoff); break; #endif #ifdef INET case AF_INET: - error = ipsec4_common_input_cb(m, sav, skip, protoff, NULL); + error = ipsec4_common_input_cb(m, sav, skip, protoff); break; #endif default: panic("%s: Unexpected address family: %d saidx=%p", __func__, saidx->dst.sa.sa_family, saidx); } - - KEY_FREESAV(&sav); return error; bad: - if (sav) - KEY_FREESAV(&sav); - if (m) + if (sav != NULL) + key_freesav(&sav); + if (m != NULL) m_freem(m); - if (tc != NULL) - free(tc, M_XDATA); - if (crp) + if (xd != NULL) + free(xd, M_XDATA); + if (crp != NULL) crypto_freereq(crp); return error; } /* - * IPComp output routine, called by ipsec[46]_process_packet() + * IPComp output routine, called by ipsec[46]_perform_request() */ static int -ipcomp_output( - struct mbuf *m, - struct ipsecrequest *isr, - struct mbuf **mp, - int skip, - int protoff -) +ipcomp_output(struct mbuf *m, struct secpolicy *sp, struct secasvar *sav, + u_int idx, int skip, int protoff) { - struct secasvar *sav; - struct comp_algo *ipcompx; - int error, ralen, maxpacketsize; + char buf[IPSEC_ADDRSTRLEN]; + const struct comp_algo *ipcompx; struct cryptodesc *crdc; struct cryptop *crp; - struct tdb_crypto *tc; + struct xform_data *xd; + int error, ralen, maxpacketsize; - sav = isr->sav; IPSEC_ASSERT(sav != NULL, ("null SA")); ipcompx = sav->tdb_compalgxform; IPSEC_ASSERT(ipcompx != NULL, ("null compression xform")); @@ -367,7 +404,7 @@ ipcomp_output( */ if (m->m_pkthdr.len <= ipcompx->minlen) { IPCOMPSTAT_INC(ipcomps_threshold); - return ipsec_process_done(m, isr); + return ipsec_process_done(m, sp, sav, idx); } ralen = m->m_pkthdr.len - skip; /* Raw payload length before comp. */ @@ -390,7 +427,7 @@ ipcomp_output( DPRINTF(("%s: unknown/unsupported protocol family %d, " "IPCA %s/%08lx\n", __func__, sav->sah->saidx.dst.sa.sa_family, - ipsec_address(&sav->sah->saidx.dst), + ipsec_address(&sav->sah->saidx.dst, buf, sizeof(buf)), (u_long) ntohl(sav->spi))); error = EPFNOSUPPORT; goto bad; @@ -399,7 +436,7 @@ ipcomp_output( IPCOMPSTAT_INC(ipcomps_toobig); DPRINTF(("%s: packet in IPCA %s/%08lx got too big " "(len %u, max len %u)\n", __func__, - ipsec_address(&sav->sah->saidx.dst), + ipsec_address(&sav->sah->saidx.dst, buf, sizeof(buf)), (u_long) ntohl(sav->spi), ralen + skip + IPCOMP_HLENGTH, maxpacketsize)); error = EMSGSIZE; @@ -413,8 +450,8 @@ ipcomp_output( if (m == NULL) { IPCOMPSTAT_INC(ipcomps_hdrops); DPRINTF(("%s: cannot clone mbuf chain, IPCA %s/%08lx\n", - __func__, ipsec_address(&sav->sah->saidx.dst), - (u_long) ntohl(sav->spi))); + __func__, ipsec_address(&sav->sah->saidx.dst, buf, + sizeof(buf)), (u_long) ntohl(sav->spi))); error = ENOBUFS; goto bad; } @@ -441,32 +478,31 @@ ipcomp_output( crdc->crd_alg = ipcompx->type; /* IPsec-specific opaque crypto info */ - tc = (struct tdb_crypto *) malloc(sizeof(struct tdb_crypto), - M_XDATA, M_NOWAIT|M_ZERO); - if (tc == NULL) { + xd = malloc(sizeof(struct xform_data), M_XDATA, M_NOWAIT | M_ZERO); + if (xd == NULL) { IPCOMPSTAT_INC(ipcomps_crypto); - DPRINTF(("%s: failed to allocate tdb_crypto\n", __func__)); + DPRINTF(("%s: failed to allocate xform_data\n", __func__)); crypto_freereq(crp); error = ENOBUFS; goto bad; } - tc->tc_isr = isr; - KEY_ADDREFSA(sav); - tc->tc_sav = sav; - tc->tc_spi = sav->spi; - tc->tc_dst = sav->sah->saidx.dst; - tc->tc_proto = sav->sah->saidx.proto; - tc->tc_protoff = protoff; - tc->tc_skip = skip; + xd->sp = sp; + xd->sav = sav; + xd->idx = idx; + xd->skip = skip; + xd->protoff = protoff; /* Crypto operation descriptor */ crp->crp_ilen = m->m_pkthdr.len; /* Total input length */ crp->crp_flags = CRYPTO_F_IMBUF | CRYPTO_F_CBIFSYNC; crp->crp_buf = (caddr_t) m; crp->crp_callback = ipcomp_output_cb; - crp->crp_opaque = (caddr_t) tc; - crp->crp_sid = sav->tdb_cryptoid; + crp->crp_opaque = (caddr_t) xd; + + SECASVAR_LOCK(sav); + crp->crp_sid = xd->cryptoid = sav->tdb_cryptoid; + SECASVAR_UNLOCK(sav); return crypto_dispatch(crp); bad: @@ -481,37 +517,32 @@ bad: static int ipcomp_output_cb(struct cryptop *crp) { - struct tdb_crypto *tc; - struct ipsecrequest *isr; + char buf[IPSEC_ADDRSTRLEN]; + struct xform_data *xd; + struct secpolicy *sp; struct secasvar *sav; struct mbuf *m; - int error, skip; + uint64_t cryptoid; + u_int idx; + int error, skip, protoff; - tc = (struct tdb_crypto *) crp->crp_opaque; - IPSEC_ASSERT(tc != NULL, ("null opaque data area!")); m = (struct mbuf *) crp->crp_buf; - skip = tc->tc_skip; - - isr = tc->tc_isr; - IPSECREQUEST_LOCK(isr); - sav = tc->tc_sav; - /* With the isr lock released SA pointer can be updated. */ - if (sav != isr->sav) { - IPCOMPSTAT_INC(ipcomps_notdb); - DPRINTF(("%s: SA expired while in crypto\n", __func__)); - error = ENOBUFS; /*XXX*/ - goto bad; - } + xd = (struct xform_data *) crp->crp_opaque; + idx = xd->idx; + sp = xd->sp; + sav = xd->sav; + skip = xd->skip; + protoff = xd->protoff; + cryptoid = xd->cryptoid; /* Check for crypto errors */ if (crp->crp_etype) { - /* Reset the session ID */ - if (sav->tdb_cryptoid != 0) - sav->tdb_cryptoid = crp->crp_sid; - if (crp->crp_etype == EAGAIN) { - IPSECREQUEST_UNLOCK(isr); - return crypto_dispatch(crp); + /* Reset the session ID */ + if (ipsec_updateid(sav, &crp->crp_sid, &cryptoid) != 0) + crypto_freesession(cryptoid); + xd->cryptoid = crp->crp_sid; + return (crypto_dispatch(crp)); } IPCOMPSTAT_INC(ipcomps_noxform); DPRINTF(("%s: crypto error %d\n", __func__, crp->crp_etype)); @@ -537,9 +568,10 @@ ipcomp_output_cb(struct cryptop *crp) mo = m_makespace(m, skip, IPCOMP_HLENGTH, &roff); if (mo == NULL) { IPCOMPSTAT_INC(ipcomps_wrap); - DPRINTF(("%s: IPCOMP header inject failed for IPCA %s/%08lx\n", - __func__, ipsec_address(&sav->sah->saidx.dst), - (u_long) ntohl(sav->spi))); + DPRINTF(("%s: IPCOMP header inject failed " + "for IPCA %s/%08lx\n", + __func__, ipsec_address(&sav->sah->saidx.dst, buf, + sizeof(buf)), (u_long) ntohl(sav->spi))); error = ENOBUFS; goto bad; } @@ -564,7 +596,7 @@ ipcomp_output_cb(struct cryptop *crp) /* Fix Next Protocol in IPv4/IPv6 header */ prot = IPPROTO_IPCOMP; - m_copyback(m, tc->tc_protoff, sizeof(u_int8_t), + m_copyback(m, protoff, sizeof(u_int8_t), (u_char *)&prot); /* Adjust the length in the IP header */ @@ -585,8 +617,8 @@ ipcomp_output_cb(struct cryptop *crp) DPRINTF(("%s: unknown/unsupported protocol " "family %d, IPCA %s/%08lx\n", __func__, sav->sah->saidx.dst.sa.sa_family, - ipsec_address(&sav->sah->saidx.dst), - (u_long) ntohl(sav->spi))); + ipsec_address(&sav->sah->saidx.dst, buf, + sizeof(buf)), (u_long) ntohl(sav->spi))); error = EPFNOSUPPORT; goto bad; } @@ -600,47 +632,143 @@ ipcomp_output_cb(struct cryptop *crp) } /* Release the crypto descriptor */ - free(tc, M_XDATA); + free(xd, M_XDATA); crypto_freereq(crp); /* NB: m is reclaimed by ipsec_process_done. */ - error = ipsec_process_done(m, isr); - KEY_FREESAV(&sav); - IPSECREQUEST_UNLOCK(isr); - return error; + error = ipsec_process_done(m, sp, sav, idx); + return (error); bad: - if (sav) - KEY_FREESAV(&sav); - IPSECREQUEST_UNLOCK(isr); if (m) m_freem(m); - free(tc, M_XDATA); + free(xd, M_XDATA); crypto_freereq(crp); - return error; + key_freesav(&sav); + key_freesp(&sp); + return (error); +} + +#ifdef INET +static const struct encaptab *ipe4_cookie = NULL; +extern struct domain inetdomain; +static struct protosw ipcomp4_protosw = { + .pr_type = SOCK_RAW, + .pr_domain = &inetdomain, + .pr_protocol = 0 /* IPPROTO_IPV[46] */, + .pr_flags = PR_ATOMIC | PR_ADDR | PR_LASTHDR, + .pr_input = ipcomp_nonexp_input, + .pr_output = rip_output, + .pr_ctloutput = rip_ctloutput, + .pr_usrreqs = &rip_usrreqs +}; + +static int +ipcomp4_nonexp_encapcheck(const struct mbuf *m, int off, int proto, + void *arg __unused) +{ + union sockaddr_union src, dst; + const struct ip *ip; + + if (V_ipcomp_enable == 0) + return (0); + if (proto != IPPROTO_IPV4 && proto != IPPROTO_IPV6) + return (0); + bzero(&src, sizeof(src)); + bzero(&dst, sizeof(dst)); + src.sa.sa_family = dst.sa.sa_family = AF_INET; + src.sin.sin_len = dst.sin.sin_len = sizeof(struct sockaddr_in); + ip = mtod(m, const struct ip *); + src.sin.sin_addr = ip->ip_src; + dst.sin.sin_addr = ip->ip_dst; + return (ipcomp_encapcheck(&src, &dst)); } +#endif +#ifdef INET6 +static const struct encaptab *ipe6_cookie = NULL; +extern struct domain inet6domain; +static struct protosw ipcomp6_protosw = { + .pr_type = SOCK_RAW, + .pr_domain = &inet6domain, + .pr_protocol = 0 /* IPPROTO_IPV[46] */, + .pr_flags = PR_ATOMIC | PR_ADDR | PR_LASTHDR, + .pr_input = ipcomp_nonexp_input, + .pr_output = rip6_output, + .pr_ctloutput = rip6_ctloutput, + .pr_usrreqs = &rip6_usrreqs +}; + +static int +ipcomp6_nonexp_encapcheck(const struct mbuf *m, int off, int proto, + void *arg __unused) +{ + union sockaddr_union src, dst; + const struct ip6_hdr *ip6; + + if (V_ipcomp_enable == 0) + return (0); + if (proto != IPPROTO_IPV4 && proto != IPPROTO_IPV6) + return (0); + bzero(&src, sizeof(src)); + bzero(&dst, sizeof(dst)); + src.sa.sa_family = dst.sa.sa_family = AF_INET; + src.sin6.sin6_len = dst.sin6.sin6_len = sizeof(struct sockaddr_in6); + ip6 = mtod(m, const struct ip6_hdr *); + src.sin6.sin6_addr = ip6->ip6_src; + dst.sin6.sin6_addr = ip6->ip6_dst; + if (IN6_IS_SCOPE_LINKLOCAL(&src.sin6.sin6_addr)) { + /* XXX: sa6_recoverscope() */ + src.sin6.sin6_scope_id = + ntohs(src.sin6.sin6_addr.s6_addr16[1]); + src.sin6.sin6_addr.s6_addr16[1] = 0; + } + if (IN6_IS_SCOPE_LINKLOCAL(&dst.sin6.sin6_addr)) { + /* XXX: sa6_recoverscope() */ + dst.sin6.sin6_scope_id = + ntohs(dst.sin6.sin6_addr.s6_addr16[1]); + dst.sin6.sin6_addr.s6_addr16[1] = 0; + } + return (ipcomp_encapcheck(&src, &dst)); +} +#endif static struct xformsw ipcomp_xformsw = { - XF_IPCOMP, XFT_COMP, "IPcomp", - ipcomp_init, ipcomp_zeroize, ipcomp_input, - ipcomp_output + .xf_type = XF_IPCOMP, + .xf_name = "IPcomp", + .xf_init = ipcomp_init, + .xf_zeroize = ipcomp_zeroize, + .xf_input = ipcomp_input, + .xf_output = ipcomp_output, }; static void ipcomp_attach(void) { - xform_register(&ipcomp_xformsw); +#ifdef INET + ipe4_cookie = encap_attach_func(AF_INET, -1, + ipcomp4_nonexp_encapcheck, &ipcomp4_protosw, NULL); +#endif +#ifdef INET6 + ipe6_cookie = encap_attach_func(AF_INET6, -1, + ipcomp6_nonexp_encapcheck, &ipcomp6_protosw, NULL); +#endif + xform_attach(&ipcomp_xformsw); } -SYSINIT(ipcomp_xform_init, SI_SUB_PROTO_DOMAIN, SI_ORDER_MIDDLE, ipcomp_attach, NULL); - static void -vnet_ipcomp_attach(const void *unused __unused) +ipcomp_detach(void) { - /* XXX */ - V_ipcompstat.version = IPCOMPSTAT_VERSION; +#ifdef INET + encap_detach(ipe4_cookie); +#endif +#ifdef INET6 + encap_detach(ipe6_cookie); +#endif + xform_detach(&ipcomp_xformsw); } -VNET_SYSINIT(vnet_ipcomp_xform_init, SI_SUB_PROTO_DOMAIN, SI_ORDER_MIDDLE, - vnet_ipcomp_attach, NULL); +SYSINIT(ipcomp_xform_init, SI_SUB_PROTO_DOMAIN, SI_ORDER_MIDDLE, + ipcomp_attach, NULL); +SYSUNINIT(ipcomp_xform_uninit, SI_SUB_PROTO_DOMAIN, SI_ORDER_MIDDLE, + ipcomp_detach, NULL); diff --git a/freebsd/sys/netipsec/xform_ipip.c b/freebsd/sys/netipsec/xform_ipip.c deleted file mode 100644 index b7234be9..00000000 --- a/freebsd/sys/netipsec/xform_ipip.c +++ /dev/null @@ -1,728 +0,0 @@ -#include <machine/rtems-bsd-kernel-space.h> - -/* $FreeBSD$ */ -/* $OpenBSD: ip_ipip.c,v 1.25 2002/06/10 18:04:55 itojun Exp $ */ -/*- - * The authors of this code are John Ioannidis (ji@tla.org), - * Angelos D. Keromytis (kermit@csd.uch.gr) and - * Niels Provos (provos@physnet.uni-hamburg.de). - * - * The original version of this code was written by John Ioannidis - * for BSD/OS in Athens, Greece, in November 1995. - * - * Ported to OpenBSD and NetBSD, with additional transforms, in December 1996, - * by Angelos D. Keromytis. - * - * Additional transforms and features in 1997 and 1998 by Angelos D. Keromytis - * and Niels Provos. - * - * Additional features in 1999 by Angelos D. Keromytis. - * - * Copyright (C) 1995, 1996, 1997, 1998, 1999 by John Ioannidis, - * Angelos D. Keromytis and Niels Provos. - * Copyright (c) 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. - */ - -/* - * IP-inside-IP processing - */ -#include <rtems/bsd/local/opt_inet.h> -#include <rtems/bsd/local/opt_inet6.h> -#include <rtems/bsd/local/opt_enc.h> - -#include <rtems/bsd/sys/param.h> -#include <sys/systm.h> -#include <sys/mbuf.h> -#include <sys/socket.h> -#include <sys/kernel.h> -#include <sys/protosw.h> -#include <sys/sysctl.h> - -#include <net/if.h> -#include <net/pfil.h> -#include <net/route.h> -#include <net/netisr.h> -#include <net/vnet.h> - -#include <netinet/in.h> -#include <netinet/in_systm.h> -#include <netinet/in_var.h> -#include <netinet/ip.h> -#include <netinet/ip_ecn.h> -#include <netinet/ip_var.h> -#include <netinet/ip_encap.h> -#ifdef MROUTING -#include <netinet/ip_mroute.h> -#endif - -#include <netipsec/ipsec.h> -#include <netipsec/xform.h> - -#include <netipsec/ipip_var.h> - -#ifdef INET6 -#include <netinet/ip6.h> -#include <netipsec/ipsec6.h> -#include <netinet6/ip6_ecn.h> -#include <netinet6/in6_var.h> -#include <netinet6/ip6protosw.h> -#endif - -#include <netipsec/key.h> -#include <netipsec/key_debug.h> - -#include <machine/stdarg.h> - -/* - * We can control the acceptance of IP4 packets by altering the sysctl - * net.inet.ipip.allow value. Zero means drop them, all else is acceptance. - */ -VNET_DEFINE(int, ipip_allow) = 0; -VNET_DEFINE(struct ipipstat, ipipstat); - -SYSCTL_DECL(_net_inet_ipip); -SYSCTL_VNET_INT(_net_inet_ipip, OID_AUTO, - ipip_allow, CTLFLAG_RW, &VNET_NAME(ipip_allow), 0, ""); -SYSCTL_VNET_STRUCT(_net_inet_ipip, IPSECCTL_STATS, - stats, CTLFLAG_RD, &VNET_NAME(ipipstat), ipipstat, ""); - -/* XXX IPCOMP */ -#define M_IPSEC (M_AUTHIPHDR|M_AUTHIPDGM|M_DECRYPTED) - -static void _ipip_input(struct mbuf *m, int iphlen, struct ifnet *gifp); - -#ifdef INET6 -/* - * Really only a wrapper for ipip_input(), for use with IPv6. - */ -int -ip4_input6(struct mbuf **m, int *offp, int proto) -{ -#if 0 - /* If we do not accept IP-in-IP explicitly, drop. */ - if (!V_ipip_allow && ((*m)->m_flags & M_IPSEC) == 0) { - DPRINTF(("%s: dropped due to policy\n", __func__)); - IPIPSTAT_INC(ipips_pdrops); - m_freem(*m); - return IPPROTO_DONE; - } -#endif - _ipip_input(*m, *offp, NULL); - return IPPROTO_DONE; -} -#endif /* INET6 */ - -#ifdef INET -/* - * Really only a wrapper for ipip_input(), for use with IPv4. - */ -void -ip4_input(struct mbuf *m, int off) -{ -#if 0 - /* If we do not accept IP-in-IP explicitly, drop. */ - if (!V_ipip_allow && (m->m_flags & M_IPSEC) == 0) { - DPRINTF(("%s: dropped due to policy\n", __func__)); - IPIPSTAT_INC(ipips_pdrops); - m_freem(m); - return; - } -#endif - _ipip_input(m, off, NULL); -} -#endif /* INET */ - -/* - * ipip_input gets called when we receive an IP{46} encapsulated packet, - * either because we got it at a real interface, or because AH or ESP - * were being used in tunnel mode (in which case the rcvif element will - * contain the address of the encX interface associated with the tunnel. - */ - -static void -_ipip_input(struct mbuf *m, int iphlen, struct ifnet *gifp) -{ -#ifdef INET - register struct sockaddr_in *sin; -#endif - register struct ifnet *ifp; - register struct ifaddr *ifa; - struct ip *ipo; -#ifdef INET6 - register struct sockaddr_in6 *sin6; - struct ip6_hdr *ip6 = NULL; - u_int8_t itos; -#endif - u_int8_t nxt; - int isr; - u_int8_t otos; - u_int8_t v; - int hlen; - - IPIPSTAT_INC(ipips_ipackets); - - m_copydata(m, 0, 1, &v); - - switch (v >> 4) { -#ifdef INET - case 4: - hlen = sizeof(struct ip); - break; -#endif /* INET */ -#ifdef INET6 - case 6: - hlen = sizeof(struct ip6_hdr); - break; -#endif - default: - IPIPSTAT_INC(ipips_family); - m_freem(m); - return /* EAFNOSUPPORT */; - } - - /* Bring the IP header in the first mbuf, if not there already */ - if (m->m_len < hlen) { - if ((m = m_pullup(m, hlen)) == NULL) { - DPRINTF(("%s: m_pullup (1) failed\n", __func__)); - IPIPSTAT_INC(ipips_hdrops); - return; - } - } - - ipo = mtod(m, struct ip *); - -#ifdef MROUTING - if (ipo->ip_v == IPVERSION && ipo->ip_p == IPPROTO_IPV4) { - if (IN_MULTICAST(((struct ip *)((char *) ipo + iphlen))->ip_dst.s_addr)) { - ipip_mroute_input (m, iphlen); - return; - } - } -#endif /* MROUTING */ - - /* Keep outer ecn field. */ - switch (v >> 4) { -#ifdef INET - case 4: - otos = ipo->ip_tos; - break; -#endif /* INET */ -#ifdef INET6 - case 6: - otos = (ntohl(mtod(m, struct ip6_hdr *)->ip6_flow) >> 20) & 0xff; - break; -#endif - default: - panic("ipip_input: unknown ip version %u (outer)", v>>4); - } - - /* Remove outer IP header */ - m_adj(m, iphlen); - - /* Sanity check */ - if (m->m_pkthdr.len < sizeof(struct ip)) { - IPIPSTAT_INC(ipips_hdrops); - m_freem(m); - return; - } - - m_copydata(m, 0, 1, &v); - - switch (v >> 4) { -#ifdef INET - case 4: - hlen = sizeof(struct ip); - break; -#endif /* INET */ - -#ifdef INET6 - case 6: - hlen = sizeof(struct ip6_hdr); - break; -#endif - default: - IPIPSTAT_INC(ipips_family); - m_freem(m); - return; /* EAFNOSUPPORT */ - } - - /* - * Bring the inner IP header in the first mbuf, if not there already. - */ - if (m->m_len < hlen) { - if ((m = m_pullup(m, hlen)) == NULL) { - DPRINTF(("%s: m_pullup (2) failed\n", __func__)); - IPIPSTAT_INC(ipips_hdrops); - return; - } - } - - /* - * RFC 1853 specifies that the inner TTL should not be touched on - * decapsulation. There's no reason this comment should be here, but - * this is as good as any a position. - */ - - /* Some sanity checks in the inner IP header */ - switch (v >> 4) { -#ifdef INET - case 4: - ipo = mtod(m, struct ip *); - nxt = ipo->ip_p; - ip_ecn_egress(V_ip4_ipsec_ecn, &otos, &ipo->ip_tos); - break; -#endif /* INET */ -#ifdef INET6 - case 6: - ip6 = (struct ip6_hdr *) ipo; - nxt = ip6->ip6_nxt; - itos = (ntohl(ip6->ip6_flow) >> 20) & 0xff; - ip_ecn_egress(V_ip6_ipsec_ecn, &otos, &itos); - ip6->ip6_flow &= ~htonl(0xff << 20); - ip6->ip6_flow |= htonl((u_int32_t) itos << 20); - break; -#endif - default: - panic("ipip_input: unknown ip version %u (inner)", v>>4); - } - - /* Check for local address spoofing. */ - if ((m->m_pkthdr.rcvif == NULL || - !(m->m_pkthdr.rcvif->if_flags & IFF_LOOPBACK)) && - V_ipip_allow != 2) { - IFNET_RLOCK_NOSLEEP(); - TAILQ_FOREACH(ifp, &V_ifnet, if_link) { - TAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) { -#ifdef INET - if (ipo) { - if (ifa->ifa_addr->sa_family != - AF_INET) - continue; - - sin = (struct sockaddr_in *) ifa->ifa_addr; - - if (sin->sin_addr.s_addr == - ipo->ip_src.s_addr) { - IPIPSTAT_INC(ipips_spoof); - m_freem(m); - IFNET_RUNLOCK_NOSLEEP(); - return; - } - } -#endif /* INET */ - -#ifdef INET6 - if (ip6) { - if (ifa->ifa_addr->sa_family != - AF_INET6) - continue; - - sin6 = (struct sockaddr_in6 *) ifa->ifa_addr; - - if (IN6_ARE_ADDR_EQUAL(&sin6->sin6_addr, &ip6->ip6_src)) { - IPIPSTAT_INC(ipips_spoof); - m_freem(m); - IFNET_RUNLOCK_NOSLEEP(); - return; - } - - } -#endif /* INET6 */ - } - } - IFNET_RUNLOCK_NOSLEEP(); - } - - /* Statistics */ - IPIPSTAT_ADD(ipips_ibytes, m->m_pkthdr.len - iphlen); - -#ifdef DEV_ENC - switch (v >> 4) { -#ifdef INET - case 4: - ipsec_bpf(m, NULL, AF_INET, ENC_IN|ENC_AFTER); - break; -#endif -#ifdef INET6 - case 6: - ipsec_bpf(m, NULL, AF_INET6, ENC_IN|ENC_AFTER); - break; -#endif - default: - panic("%s: bogus ip version %u", __func__, v>>4); - } - /* pass the mbuf to enc0 for packet filtering */ - if (ipsec_filter(&m, PFIL_IN, ENC_IN|ENC_AFTER) != 0) - return; -#endif - - /* - * Interface pointer stays the same; if no IPsec processing has - * been done (or will be done), this will point to a normal - * interface. Otherwise, it'll point to an enc interface, which - * will allow a packet filter to distinguish between secure and - * untrusted packets. - */ - - switch (v >> 4) { -#ifdef INET - case 4: - isr = NETISR_IP; - break; -#endif -#ifdef INET6 - case 6: - isr = NETISR_IPV6; - break; -#endif - default: - panic("%s: bogus ip version %u", __func__, v>>4); - } - - m_addr_changed(m); - - if (netisr_queue(isr, m)) { /* (0) on success. */ - IPIPSTAT_INC(ipips_qfull); - DPRINTF(("%s: packet dropped because of full queue\n", - __func__)); - } -} - -int -ipip_output( - struct mbuf *m, - struct ipsecrequest *isr, - struct mbuf **mp, - int skip, - int protoff -) -{ - struct secasvar *sav; - u_int8_t tp, otos; - struct secasindex *saidx; - int error; -#if defined(INET) || defined(INET6) - u_int8_t itos; -#endif -#ifdef INET - struct ip *ipo; -#endif /* INET */ -#ifdef INET6 - struct ip6_hdr *ip6, *ip6o; -#endif /* INET6 */ - - sav = isr->sav; - IPSEC_ASSERT(sav != NULL, ("null SA")); - IPSEC_ASSERT(sav->sah != NULL, ("null SAH")); - - /* XXX Deal with empty TDB source/destination addresses. */ - - m_copydata(m, 0, 1, &tp); - tp = (tp >> 4) & 0xff; /* Get the IP version number. */ - - saidx = &sav->sah->saidx; - switch (saidx->dst.sa.sa_family) { -#ifdef INET - case AF_INET: - if (saidx->src.sa.sa_family != AF_INET || - saidx->src.sin.sin_addr.s_addr == INADDR_ANY || - saidx->dst.sin.sin_addr.s_addr == INADDR_ANY) { - DPRINTF(("%s: unspecified tunnel endpoint " - "address in SA %s/%08lx\n", __func__, - ipsec_address(&saidx->dst), - (u_long) ntohl(sav->spi))); - IPIPSTAT_INC(ipips_unspec); - error = EINVAL; - goto bad; - } - - M_PREPEND(m, sizeof(struct ip), M_DONTWAIT); - if (m == 0) { - DPRINTF(("%s: M_PREPEND failed\n", __func__)); - IPIPSTAT_INC(ipips_hdrops); - error = ENOBUFS; - goto bad; - } - - ipo = mtod(m, struct ip *); - - ipo->ip_v = IPVERSION; - ipo->ip_hl = 5; - ipo->ip_len = htons(m->m_pkthdr.len); - ipo->ip_ttl = V_ip_defttl; - ipo->ip_sum = 0; - ipo->ip_src = saidx->src.sin.sin_addr; - ipo->ip_dst = saidx->dst.sin.sin_addr; - - ipo->ip_id = ip_newid(); - - /* If the inner protocol is IP... */ - switch (tp) { - case IPVERSION: - /* Save ECN notification */ - m_copydata(m, sizeof(struct ip) + - offsetof(struct ip, ip_tos), - sizeof(u_int8_t), (caddr_t) &itos); - - ipo->ip_p = IPPROTO_IPIP; - - /* - * We should be keeping tunnel soft-state and - * send back ICMPs if needed. - */ - m_copydata(m, sizeof(struct ip) + - offsetof(struct ip, ip_off), - sizeof(u_int16_t), (caddr_t) &ipo->ip_off); - ipo->ip_off = ntohs(ipo->ip_off); - ipo->ip_off &= ~(IP_DF | IP_MF | IP_OFFMASK); - ipo->ip_off = htons(ipo->ip_off); - break; -#ifdef INET6 - case (IPV6_VERSION >> 4): - { - u_int32_t itos32; - - /* Save ECN notification. */ - m_copydata(m, sizeof(struct ip) + - offsetof(struct ip6_hdr, ip6_flow), - sizeof(u_int32_t), (caddr_t) &itos32); - itos = ntohl(itos32) >> 20; - ipo->ip_p = IPPROTO_IPV6; - ipo->ip_off = 0; - break; - } -#endif /* INET6 */ - default: - goto nofamily; - } - - otos = 0; - ip_ecn_ingress(ECN_ALLOWED, &otos, &itos); - ipo->ip_tos = otos; - break; -#endif /* INET */ - -#ifdef INET6 - case AF_INET6: - if (IN6_IS_ADDR_UNSPECIFIED(&saidx->dst.sin6.sin6_addr) || - saidx->src.sa.sa_family != AF_INET6 || - IN6_IS_ADDR_UNSPECIFIED(&saidx->src.sin6.sin6_addr)) { - DPRINTF(("%s: unspecified tunnel endpoint " - "address in SA %s/%08lx\n", __func__, - ipsec_address(&saidx->dst), - (u_long) ntohl(sav->spi))); - IPIPSTAT_INC(ipips_unspec); - error = ENOBUFS; - goto bad; - } - - /* scoped address handling */ - ip6 = mtod(m, struct ip6_hdr *); - if (IN6_IS_SCOPE_LINKLOCAL(&ip6->ip6_src)) - ip6->ip6_src.s6_addr16[1] = 0; - if (IN6_IS_SCOPE_LINKLOCAL(&ip6->ip6_dst)) - ip6->ip6_dst.s6_addr16[1] = 0; - - M_PREPEND(m, sizeof(struct ip6_hdr), M_DONTWAIT); - if (m == 0) { - DPRINTF(("%s: M_PREPEND failed\n", __func__)); - IPIPSTAT_INC(ipips_hdrops); - error = ENOBUFS; - goto bad; - } - - /* Initialize IPv6 header */ - ip6o = mtod(m, struct ip6_hdr *); - ip6o->ip6_flow = 0; - ip6o->ip6_vfc &= ~IPV6_VERSION_MASK; - ip6o->ip6_vfc |= IPV6_VERSION; - ip6o->ip6_plen = htons(m->m_pkthdr.len); - ip6o->ip6_hlim = V_ip_defttl; - ip6o->ip6_dst = saidx->dst.sin6.sin6_addr; - ip6o->ip6_src = saidx->src.sin6.sin6_addr; - - switch (tp) { -#ifdef INET - case IPVERSION: - /* Save ECN notification */ - m_copydata(m, sizeof(struct ip6_hdr) + - offsetof(struct ip, ip_tos), sizeof(u_int8_t), - (caddr_t) &itos); - - /* This is really IPVERSION. */ - ip6o->ip6_nxt = IPPROTO_IPIP; - break; -#endif /* INET */ - case (IPV6_VERSION >> 4): - { - u_int32_t itos32; - - /* Save ECN notification. */ - m_copydata(m, sizeof(struct ip6_hdr) + - offsetof(struct ip6_hdr, ip6_flow), - sizeof(u_int32_t), (caddr_t) &itos32); - itos = ntohl(itos32) >> 20; - - ip6o->ip6_nxt = IPPROTO_IPV6; - break; - } - default: - goto nofamily; - } - - otos = 0; - ip_ecn_ingress(ECN_ALLOWED, &otos, &itos); - ip6o->ip6_flow |= htonl((u_int32_t) otos << 20); - break; -#endif /* INET6 */ - - default: -nofamily: - DPRINTF(("%s: unsupported protocol family %u\n", __func__, - saidx->dst.sa.sa_family)); - IPIPSTAT_INC(ipips_family); - error = EAFNOSUPPORT; /* XXX diffs from openbsd */ - goto bad; - } - - IPIPSTAT_INC(ipips_opackets); - *mp = m; - -#ifdef INET - if (saidx->dst.sa.sa_family == AF_INET) { -#if 0 - if (sav->tdb_xform->xf_type == XF_IP4) - tdb->tdb_cur_bytes += - m->m_pkthdr.len - sizeof(struct ip); -#endif - IPIPSTAT_ADD(ipips_obytes, - m->m_pkthdr.len - sizeof(struct ip)); - } -#endif /* INET */ - -#ifdef INET6 - if (saidx->dst.sa.sa_family == AF_INET6) { -#if 0 - if (sav->tdb_xform->xf_type == XF_IP4) - tdb->tdb_cur_bytes += - m->m_pkthdr.len - sizeof(struct ip6_hdr); -#endif - IPIPSTAT_ADD(ipips_obytes, - m->m_pkthdr.len - sizeof(struct ip6_hdr)); - } -#endif /* INET6 */ - - return 0; -bad: - if (m) - m_freem(m); - *mp = NULL; - return (error); -} - -#ifdef IPSEC -#if defined(INET) || defined(INET6) -static int -ipe4_init(struct secasvar *sav, struct xformsw *xsp) -{ - sav->tdb_xform = xsp; - return 0; -} - -static int -ipe4_zeroize(struct secasvar *sav) -{ - sav->tdb_xform = NULL; - return 0; -} - -static int -ipe4_input(struct mbuf *m, struct secasvar *sav, int skip, int protoff) -{ - /* This is a rather serious mistake, so no conditional printing. */ - printf("%s: should never be called\n", __func__); - if (m) - m_freem(m); - return EOPNOTSUPP; -} - -static struct xformsw ipe4_xformsw = { - XF_IP4, 0, "IPv4 Simple Encapsulation", - ipe4_init, ipe4_zeroize, ipe4_input, ipip_output, -}; - -extern struct domain inetdomain; -#endif /* INET || INET6 */ -#ifdef INET -static struct protosw ipe4_protosw = { - .pr_type = SOCK_RAW, - .pr_domain = &inetdomain, - .pr_protocol = IPPROTO_IPV4, - .pr_flags = PR_ATOMIC|PR_ADDR|PR_LASTHDR, - .pr_input = ip4_input, - .pr_ctloutput = rip_ctloutput, - .pr_usrreqs = &rip_usrreqs -}; -#endif /* INET */ -#if defined(INET6) && defined(INET) -static struct ip6protosw ipe6_protosw = { - .pr_type = SOCK_RAW, - .pr_domain = &inetdomain, - .pr_protocol = IPPROTO_IPV6, - .pr_flags = PR_ATOMIC|PR_ADDR|PR_LASTHDR, - .pr_input = ip4_input6, - .pr_ctloutput = rip_ctloutput, - .pr_usrreqs = &rip_usrreqs -}; -#endif /* INET6 && INET */ - -#if defined(INET) -/* - * Check the encapsulated packet to see if we want it - */ -static int -ipe4_encapcheck(const struct mbuf *m, int off, int proto, void *arg) -{ - /* - * Only take packets coming from IPSEC tunnels; the rest - * must be handled by the gif tunnel code. Note that we - * also return a minimum priority when we want the packet - * so any explicit gif tunnels take precedence. - */ - return ((m->m_flags & M_IPSEC) != 0 ? 1 : 0); -} -#endif /* INET */ - -static void -ipe4_attach(void) -{ - - xform_register(&ipe4_xformsw); - /* attach to encapsulation framework */ - /* XXX save return cookie for detach on module remove */ -#ifdef INET - (void) encap_attach_func(AF_INET, -1, - ipe4_encapcheck, &ipe4_protosw, NULL); -#endif -#if defined(INET6) && defined(INET) - (void) encap_attach_func(AF_INET6, -1, - ipe4_encapcheck, (struct protosw *)&ipe6_protosw, NULL); -#endif -} -SYSINIT(ipe4_xform_init, SI_SUB_PROTO_DOMAIN, SI_ORDER_MIDDLE, ipe4_attach, NULL); -#endif /* IPSEC */ diff --git a/freebsd/sys/netipsec/xform_tcp.c b/freebsd/sys/netipsec/xform_tcp.c index 398dca13..81b4f1d0 100644 --- a/freebsd/sys/netipsec/xform_tcp.c +++ b/freebsd/sys/netipsec/xform_tcp.c @@ -1,9 +1,8 @@ #include <machine/rtems-bsd-kernel-space.h> -/* $FreeBSD$ */ - /*- * Copyright (c) 2003 Bruce M. Simpson <bms@spc.org> + * Copyright (c) 2016 Andrey V. Elsukov <ae@FreeBSD.org> * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -30,29 +29,37 @@ */ /* TCP MD5 Signature Option (RFC2385) */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + #include <rtems/bsd/local/opt_inet.h> #include <rtems/bsd/local/opt_inet6.h> +#include <rtems/bsd/local/opt_ipsec.h> #include <rtems/bsd/sys/param.h> #include <sys/systm.h> #include <sys/mbuf.h> #include <rtems/bsd/sys/lock.h> +#include <sys/md5.h> +#include <sys/rmlock.h> #include <sys/socket.h> +#include <sys/sockopt.h> #include <sys/kernel.h> +#include <sys/module.h> #include <sys/protosw.h> -#include <sys/sysctl.h> #include <netinet/in.h> +#include <netinet/in_pcb.h> #include <netinet/in_systm.h> #include <netinet/ip.h> #include <netinet/ip_var.h> #include <netinet/tcp.h> #include <netinet/tcp_var.h> -#include <net/route.h> #include <net/vnet.h> #include <netipsec/ipsec.h> +#include <netipsec/ipsec_support.h> #include <netipsec/xform.h> #ifdef INET6 @@ -63,13 +70,256 @@ #include <netipsec/key.h> #include <netipsec/key_debug.h> +#define TCP_SIGLEN 16 /* length of computed digest in bytes */ +#define TCP_KEYLEN_MIN 1 /* minimum length of TCP-MD5 key */ +#define TCP_KEYLEN_MAX 80 /* maximum length of TCP-MD5 key */ + +static int +tcp_ipsec_pcbctl(struct inpcb *inp, struct sockopt *sopt) +{ + struct tcpcb *tp; + int error, optval; + + INP_WLOCK_ASSERT(inp); + if (sopt->sopt_name != TCP_MD5SIG) { + INP_WUNLOCK(inp); + return (ENOPROTOOPT); + } + + tp = intotcpcb(inp); + if (sopt->sopt_dir == SOPT_GET) { + optval = (tp->t_flags & TF_SIGNATURE) ? 1 : 0; + INP_WUNLOCK(inp); + + /* On success return with released INP_WLOCK */ + return (sooptcopyout(sopt, &optval, sizeof(optval))); + } + + INP_WUNLOCK(inp); + + error = sooptcopyin(sopt, &optval, sizeof(optval), sizeof(optval)); + if (error != 0) + return (error); + + /* INP_WLOCK_RECHECK */ + INP_WLOCK(inp); + if (inp->inp_flags & (INP_TIMEWAIT | INP_DROPPED)) { + INP_WUNLOCK(inp); + return (ECONNRESET); + } + if (optval > 0) + tp->t_flags |= TF_SIGNATURE; + else + tp->t_flags &= ~TF_SIGNATURE; + + /* On success return with acquired INP_WLOCK */ + return (error); +} + +/* + * Callback function invoked by m_apply() to digest TCP segment data + * contained within an mbuf chain. + */ +static int +tcp_signature_apply(void *fstate, void *data, u_int len) +{ + + MD5Update(fstate, (u_char *)data, len); + return (0); +} + +#ifdef INET +static int +ip_pseudo_compute(struct mbuf *m, MD5_CTX *ctx) +{ + struct ippseudo ipp; + struct ip *ip; + + ip = mtod(m, struct ip *); + ipp.ippseudo_src.s_addr = ip->ip_src.s_addr; + ipp.ippseudo_dst.s_addr = ip->ip_dst.s_addr; + ipp.ippseudo_p = IPPROTO_TCP; + ipp.ippseudo_pad = 0; + ipp.ippseudo_len = htons(m->m_pkthdr.len - (ip->ip_hl << 2)); + MD5Update(ctx, (char *)&ipp, sizeof(ipp)); + return (ip->ip_hl << 2); +} +#endif + +#ifdef INET6 +static int +ip6_pseudo_compute(struct mbuf *m, MD5_CTX *ctx) +{ + struct ip6_pseudo { + struct in6_addr src, dst; + uint32_t len; + uint32_t nxt; + } ip6p __aligned(4); + struct ip6_hdr *ip6; + + ip6 = mtod(m, struct ip6_hdr *); + ip6p.src = ip6->ip6_src; + ip6p.dst = ip6->ip6_dst; + ip6p.len = htonl(m->m_pkthdr.len - sizeof(*ip6)); /* XXX: ext headers */ + ip6p.nxt = htonl(IPPROTO_TCP); + MD5Update(ctx, (char *)&ip6p, sizeof(ip6p)); + return (sizeof(*ip6)); +} +#endif + +static int +tcp_signature_compute(struct mbuf *m, struct tcphdr *th, + struct secasvar *sav, u_char *buf) +{ + MD5_CTX ctx; + int len; + u_short csum; + + MD5Init(&ctx); + /* Step 1: Update MD5 hash with IP(v6) pseudo-header. */ + switch (sav->sah->saidx.dst.sa.sa_family) { +#ifdef INET + case AF_INET: + len = ip_pseudo_compute(m, &ctx); + break; +#endif +#ifdef INET6 + case AF_INET6: + len = ip6_pseudo_compute(m, &ctx); + break; +#endif + default: + return (EAFNOSUPPORT); + } + /* + * Step 2: Update MD5 hash with TCP header, excluding options. + * The TCP checksum must be set to zero. + */ + csum = th->th_sum; + th->th_sum = 0; + MD5Update(&ctx, (char *)th, sizeof(struct tcphdr)); + th->th_sum = csum; + /* + * Step 3: Update MD5 hash with TCP segment data. + * Use m_apply() to avoid an early m_pullup(). + */ + len += (th->th_off << 2); + if (m->m_pkthdr.len - len > 0) + m_apply(m, len, m->m_pkthdr.len - len, + tcp_signature_apply, &ctx); + /* + * Step 4: Update MD5 hash with shared secret. + */ + MD5Update(&ctx, sav->key_auth->key_data, _KEYLEN(sav->key_auth)); + MD5Final(buf, &ctx); + key_sa_recordxfer(sav, m); + return (0); +} + +static void +setsockaddrs(const struct mbuf *m, union sockaddr_union *src, + union sockaddr_union *dst) +{ + struct ip *ip; + + IPSEC_ASSERT(m->m_len >= sizeof(*ip), ("unexpected mbuf len")); + + ip = mtod(m, struct ip *); + switch (ip->ip_v) { +#ifdef INET + case IPVERSION: + ipsec4_setsockaddrs(m, src, dst); + break; +#endif +#ifdef INET6 + case (IPV6_VERSION >> 4): + ipsec6_setsockaddrs(m, src, dst); + break; +#endif + default: + bzero(src, sizeof(*src)); + bzero(dst, sizeof(*dst)); + } +} + +/* + * Compute TCP-MD5 hash of an *INBOUND* TCP segment. + * Parameters: + * m pointer to head of mbuf chain + * th pointer to TCP header + * buf pointer to storage for computed MD5 digest + * + * Return 0 if successful, otherwise return -1. + */ +static int +tcp_ipsec_input(struct mbuf *m, struct tcphdr *th, u_char *buf) +{ + char tmpdigest[TCP_SIGLEN]; + struct secasindex saidx; + struct secasvar *sav; + + setsockaddrs(m, &saidx.src, &saidx.dst); + saidx.proto = IPPROTO_TCP; + saidx.mode = IPSEC_MODE_TCPMD5; + saidx.reqid = 0; + sav = key_allocsa_tcpmd5(&saidx); + if (sav == NULL) { + KMOD_TCPSTAT_INC(tcps_sig_err_buildsig); + return (EACCES); + } + /* + * tcp_input() operates with TCP header fields in host + * byte order. We expect them in network byte order. + */ + tcp_fields_to_net(th); + tcp_signature_compute(m, th, sav, tmpdigest); + tcp_fields_to_host(th); + key_freesav(&sav); + if (bcmp(buf, tmpdigest, TCP_SIGLEN) != 0) { + KMOD_TCPSTAT_INC(tcps_sig_rcvbadsig); + return (EACCES); + } + KMOD_TCPSTAT_INC(tcps_sig_rcvgoodsig); + return (0); +} + +/* + * Compute TCP-MD5 hash of an *OUTBOUND* TCP segment. + * Parameters: + * m pointer to head of mbuf chain + * th pointer to TCP header + * buf pointer to storage for computed MD5 digest + * + * Return 0 if successful, otherwise return error code. + */ +static int +tcp_ipsec_output(struct mbuf *m, struct tcphdr *th, u_char *buf) +{ + struct secasindex saidx; + struct secasvar *sav; + + setsockaddrs(m, &saidx.src, &saidx.dst); + saidx.proto = IPPROTO_TCP; + saidx.mode = IPSEC_MODE_TCPMD5; + saidx.reqid = 0; + sav = key_allocsa_tcpmd5(&saidx); + if (sav == NULL) { + KMOD_TCPSTAT_INC(tcps_sig_err_buildsig); + return (EACCES); + } + tcp_signature_compute(m, th, sav, buf); + key_freesav(&sav); + return (0); +} + /* * Initialize a TCP-MD5 SA. Called when the SA is being set up. * * We don't need to set up the tdb prefixed fields, as we don't use the * opencrypto code; we just perform a key length check. * - * XXX: Currently we only allow a single 'magic' SPI to be used. + * XXX: Currently we have used single 'magic' SPI and need to still + * support this. * * This allows per-host granularity without affecting the userland * interface, which is a simple socket option toggle switch, @@ -88,11 +338,6 @@ tcpsignature_init(struct secasvar *sav, struct xformsw *xsp) { int keylen; - if (sav->spi != htonl(TCP_SIG_SPI)) { - DPRINTF(("%s: SPI must be TCP_SIG_SPI (0x1000)\n", - __func__)); - return (EINVAL); - } if (sav->alg_auth != SADB_X_AALG_TCP_MD5) { DPRINTF(("%s: unsupported authentication algorithm %u\n", __func__, sav->alg_auth)); @@ -107,67 +352,76 @@ tcpsignature_init(struct secasvar *sav, struct xformsw *xsp) DPRINTF(("%s: invalid key length %u\n", __func__, keylen)); return (EINVAL); } - + sav->tdb_xform = xsp; return (0); } /* - * Paranoia. - * * Called when the SA is deleted. */ static int tcpsignature_zeroize(struct secasvar *sav) { - if (sav->key_auth) + if (sav->key_auth != NULL) bzero(sav->key_auth->key_data, _KEYLEN(sav->key_auth)); - - sav->tdb_cryptoid = 0; - sav->tdb_authalgxform = NULL; sav->tdb_xform = NULL; - return (0); } -/* - * Verify that an input packet passes authentication. - * Called from the ipsec layer. - * We do this from within tcp itself, so this routine is just a stub. - */ -static int -tcpsignature_input(struct mbuf *m, struct secasvar *sav, int skip, - int protoff) -{ +static struct xformsw tcpsignature_xformsw = { + .xf_type = XF_TCPSIGNATURE, + .xf_name = "TCP-MD5", + .xf_init = tcpsignature_init, + .xf_zeroize = tcpsignature_zeroize, +}; - return (0); -} +static const struct tcpmd5_methods tcpmd5_methods = { + .input = tcp_ipsec_input, + .output = tcp_ipsec_output, + .pcbctl = tcp_ipsec_pcbctl, +}; + +#ifndef KLD_MODULE +/* TCP-MD5 support is build in the kernel */ +static const struct tcpmd5_support tcpmd5_ipsec = { + .enabled = IPSEC_MODULE_ENABLED, + .methods = &tcpmd5_methods +}; +const struct tcpmd5_support * const tcp_ipsec_support = &tcpmd5_ipsec; +#endif /* !KLD_MODULE */ -/* - * Prepend the authentication header. - * Called from the ipsec layer. - * We do this from within tcp itself, so this routine is just a stub. - */ static int -tcpsignature_output(struct mbuf *m, struct ipsecrequest *isr, - struct mbuf **mp, int skip, int protoff) +tcpmd5_modevent(module_t mod, int type, void *data) { - return (EINVAL); + switch (type) { + case MOD_LOAD: + xform_attach(&tcpsignature_xformsw); +#ifdef KLD_MODULE + tcpmd5_support_enable(&tcpmd5_methods); +#endif + break; + case MOD_UNLOAD: +#ifdef KLD_MODULE + tcpmd5_support_disable(); +#endif + xform_detach(&tcpsignature_xformsw); + break; + default: + return (EOPNOTSUPP); + } + return (0); } -static struct xformsw tcpsignature_xformsw = { - XF_TCPSIGNATURE, XFT_AUTH, "TCPMD5", - tcpsignature_init, tcpsignature_zeroize, - tcpsignature_input, tcpsignature_output +static moduledata_t tcpmd5_mod = { + "tcpmd5", + tcpmd5_modevent, + 0 }; -static void -tcpsignature_attach(void) -{ - - xform_register(&tcpsignature_xformsw); -} - -SYSINIT(tcpsignature_xform_init, SI_SUB_DRIVERS, SI_ORDER_FIRST, - tcpsignature_attach, NULL); +DECLARE_MODULE(tcpmd5, tcpmd5_mod, SI_SUB_PROTO_DOMAIN, SI_ORDER_ANY); +MODULE_VERSION(tcpmd5, 1); +#ifdef KLD_MODULE +MODULE_DEPEND(tcpmd5, ipsec_support, 1, 1, 1); +#endif |