diff options
author | Sebastian Huber <sebastian.huber@embedded-brains.de> | 2013-10-09 22:42:09 +0200 |
---|---|---|
committer | Sebastian Huber <sebastian.huber@embedded-brains.de> | 2013-10-10 09:06:58 +0200 |
commit | bceabc95c1c85d793200446fa85f1ddc6313ea29 (patch) | |
tree | 973c8bd8deca9fd69913f2895cc91e0e6114d46c /freebsd/sys/netipsec | |
parent | Add FreeBSD sources as a submodule (diff) | |
download | rtems-libbsd-bceabc95c1c85d793200446fa85f1ddc6313ea29.tar.bz2 |
Move files to match FreeBSD layout
Diffstat (limited to 'freebsd/sys/netipsec')
27 files changed, 18779 insertions, 0 deletions
diff --git a/freebsd/sys/netipsec/ah.h b/freebsd/sys/netipsec/ah.h new file mode 100644 index 00000000..6a35089b --- /dev/null +++ b/freebsd/sys/netipsec/ah.h @@ -0,0 +1,56 @@ +/* $FreeBSD$ */ +/* $KAME: ah.h,v 1.13 2000/10/18 21:28:00 itojun Exp $ */ + +/*- + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * RFC1826/2402 authentication header. + */ + +#ifndef _NETIPSEC_AH_HH_ +#define _NETIPSEC_AH_HH_ + +struct ah { + u_int8_t ah_nxt; /* Next Header */ + u_int8_t ah_len; /* Length of data, in 32bit */ + u_int16_t ah_reserve; /* Reserved for future use */ + u_int32_t ah_spi; /* Security parameter index */ + /* variable size, 32bit bound*/ /* Authentication data */ +}; + +struct newah { + u_int8_t ah_nxt; /* Next Header */ + u_int8_t ah_len; /* Length of data + 1, in 32bit */ + u_int16_t ah_reserve; /* Reserved for future use */ + u_int32_t ah_spi; /* Security parameter index */ + u_int32_t ah_seq; /* Sequence number field */ + /* variable size, 32bit bound*/ /* Authentication data */ +}; +#endif /*_NETIPSEC_AH_HH_*/ diff --git a/freebsd/sys/netipsec/ah_var.h b/freebsd/sys/netipsec/ah_var.h new file mode 100644 index 00000000..f82ea401 --- /dev/null +++ b/freebsd/sys/netipsec/ah_var.h @@ -0,0 +1,82 @@ +/* $FreeBSD$ */ +/* $OpenBSD: ip_ah.h,v 1.29 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 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 _NETIPSEC_AH_VAR_HH_ +#define _NETIPSEC_AH_VAR_HH_ + +/* + * These define the algorithm indices into the histogram. They're + * presently based on the PF_KEY v2 protocol values which is bogus; + * they should be decoupled from the protocol at which time we can + * pack them and reduce the size of the array to a minimum. + */ +#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 */ +}; + +#ifdef _KERNEL +VNET_DECLARE(int, ah_enable); +VNET_DECLARE(int, ah_cleartos); +VNET_DECLARE(struct ahstat, ahstat); + +#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_HH_*/ diff --git a/freebsd/sys/netipsec/esp.h b/freebsd/sys/netipsec/esp.h new file mode 100644 index 00000000..282883b2 --- /dev/null +++ b/freebsd/sys/netipsec/esp.h @@ -0,0 +1,69 @@ +/* $FreeBSD$ */ +/* $KAME: esp.h,v 1.16 2000/10/18 21:28:00 itojun Exp $ */ + +/*- + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * RFC1827/2406 Encapsulated Security Payload. + */ + +#ifndef _NETIPSEC_ESP_HH_ +#define _NETIPSEC_ESP_HH_ + +struct esp { + u_int32_t esp_spi; /* ESP */ + /*variable size, 32bit bound*/ /* Initialization Vector */ + /*variable size*/ /* Payload data */ + /*variable size*/ /* padding */ + /*8bit*/ /* pad size */ + /*8bit*/ /* next header */ + /*8bit*/ /* next header */ + /*variable size, 32bit bound*/ /* Authentication data (new IPsec) */ +}; + +struct newesp { + u_int32_t esp_spi; /* ESP */ + u_int32_t esp_seq; /* Sequence number */ + /*variable size*/ /* (IV and) Payload data */ + /*variable size*/ /* padding */ + /*8bit*/ /* pad size */ + /*8bit*/ /* next header */ + /*8bit*/ /* next header */ + /*variable size, 32bit bound*/ /* Authentication data */ +}; + +struct esptail { + u_int8_t esp_padlen; /* pad length */ + u_int8_t esp_nxt; /* Next header */ + /*variable size, 32bit bound*/ /* Authentication data (new IPsec)*/ +}; + +#define ESP_ALEN 12 /* 96-bit authenticator */ +#endif /*_NETIPSEC_ESP_HH_*/ diff --git a/freebsd/sys/netipsec/esp_var.h b/freebsd/sys/netipsec/esp_var.h new file mode 100644 index 00000000..eda4033b --- /dev/null +++ b/freebsd/sys/netipsec/esp_var.h @@ -0,0 +1,81 @@ +/* $FreeBSD$ */ +/* $OpenBSD: ip_esp.h,v 1.37 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 _NETIPSEC_ESP_VAR_HH_ +#define _NETIPSEC_ESP_VAR_HH_ + +/* + * These define the algorithm indices into the histogram. They're + * presently based on the PF_KEY v2 protocol values which is bogus; + * they should be decoupled from the protocol at which time we can + * pack them and reduce the size of the array to a reasonable value. + */ +#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 */ +}; + +#ifdef _KERNEL +VNET_DECLARE(int, esp_enable); +VNET_DECLARE(struct espstat, espstat); + +#define V_esp_enable VNET(esp_enable) +#define V_espstat VNET(espstat) +#endif /* _KERNEL */ +#endif /*_NETIPSEC_ESP_VAR_HH_*/ diff --git a/freebsd/sys/netipsec/ipcomp.h b/freebsd/sys/netipsec/ipcomp.h new file mode 100644 index 00000000..a43bf2e8 --- /dev/null +++ b/freebsd/sys/netipsec/ipcomp.h @@ -0,0 +1,55 @@ +/* $FreeBSD$ */ +/* $KAME: ipcomp.h,v 1.8 2000/09/26 07:55:14 itojun Exp $ */ + +/*- + * Copyright (C) 1999 WIDE Project. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * RFC2393 IP payload compression protocol (IPComp). + */ + +#ifndef _NETIPSEC_IPCOMP_HH_ +#define _NETIPSEC_IPCOMP_HH_ + +struct ipcomp { + u_int8_t comp_nxt; /* Next Header */ + u_int8_t comp_flags; /* reserved, must be zero */ + u_int16_t comp_cpi; /* Compression parameter index */ +}; + +#define IPCOMP_HLENGTH 4 /* Length of IPCOMP header */ + +/* well-known algorithm number (in CPI), from RFC2409 */ +#define IPCOMP_OUI 1 /* vendor specific */ +#define IPCOMP_DEFLATE 2 /* RFC2394 */ +#define IPCOMP_LZS 3 /* RFC2395 */ +#define IPCOMP_MAX 4 + +#define IPCOMP_CPI_NEGOTIATE_MIN 256 +#endif /*_NETIPSEC_IPCOMP_HH_*/ diff --git a/freebsd/sys/netipsec/ipcomp_var.h b/freebsd/sys/netipsec/ipcomp_var.h new file mode 100644 index 00000000..bf4598f4 --- /dev/null +++ b/freebsd/sys/netipsec/ipcomp_var.h @@ -0,0 +1,74 @@ +/* $FreeBSD$ */ +/* $KAME: ipcomp.h,v 1.8 2000/09/26 07:55:14 itojun Exp $ */ + +/*- + * Copyright (C) 1999 WIDE Project. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _NETIPSEC_IPCOMP_VAR_HH_ +#define _NETIPSEC_IPCOMP_VAR_HH_ + +/* + * These define the algorithm indices into the histogram. They're + * presently based on the PF_KEY v2 protocol values which is bogus; + * they should be decoupled from the protocol at which time we can + * pack them and reduce the size of the array to a minimum. + */ +#define IPCOMP_ALG_MAX 8 + +#define IPCOMPSTAT_VERSION 1 +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. */ +}; + +#ifdef _KERNEL +VNET_DECLARE(int, ipcomp_enable); +VNET_DECLARE(struct ipcompstat, ipcompstat); + +#define V_ipcomp_enable VNET(ipcomp_enable) +#define V_ipcompstat VNET(ipcompstat) +#endif /* _KERNEL */ +#endif /*_NETIPSEC_IPCOMP_VAR_HH_*/ diff --git a/freebsd/sys/netipsec/ipip_var.h b/freebsd/sys/netipsec/ipip_var.h new file mode 100644 index 00000000..51d8a554 --- /dev/null +++ b/freebsd/sys/netipsec/ipip_var.h @@ -0,0 +1,68 @@ +/* $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_HH_ +#define _NETINET_IPIP_HH_ + +/* + * 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 V_ipip_allow VNET(ipip_allow) +#define V_ipipstat VNET(ipipstat) +#endif /* _KERNEL */ +#endif /* _NETINET_IPIP_HH_ */ diff --git a/freebsd/sys/netipsec/ipsec.c b/freebsd/sys/netipsec/ipsec.c new file mode 100644 index 00000000..95aefca3 --- /dev/null +++ b/freebsd/sys/netipsec/ipsec.c @@ -0,0 +1,1751 @@ +#include <freebsd/machine/rtems-bsd-config.h> + +/* $FreeBSD$ */ +/* $KAME: ipsec.c,v 1.103 2001/05/24 07:14:18 sakane Exp $ */ + +/*- + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * IPsec controller part. + */ + +#include <freebsd/local/opt_inet.h> +#include <freebsd/local/opt_inet6.h> +#include <freebsd/local/opt_ipsec.h> + +#include <freebsd/sys/param.h> +#include <freebsd/sys/systm.h> +#include <freebsd/sys/malloc.h> +#include <freebsd/sys/mbuf.h> +#include <freebsd/sys/domain.h> +#include <freebsd/sys/priv.h> +#include <freebsd/sys/protosw.h> +#include <freebsd/sys/socket.h> +#include <freebsd/sys/socketvar.h> +#include <freebsd/sys/errno.h> +#include <freebsd/sys/time.h> +#include <freebsd/sys/kernel.h> +#include <freebsd/sys/syslog.h> +#include <freebsd/sys/sysctl.h> +#include <freebsd/sys/proc.h> + +#include <freebsd/net/if.h> +#include <freebsd/net/route.h> +#include <freebsd/net/vnet.h> + +#include <freebsd/netinet/in.h> +#include <freebsd/netinet/in_systm.h> +#include <freebsd/netinet/ip.h> +#include <freebsd/netinet/ip_var.h> +#include <freebsd/netinet/in_var.h> +#include <freebsd/netinet/udp.h> +#include <freebsd/netinet/udp_var.h> +#include <freebsd/netinet/tcp.h> +#include <freebsd/netinet/udp.h> + +#include <freebsd/netinet/ip6.h> +#ifdef INET6 +#include <freebsd/netinet6/ip6_var.h> +#endif +#include <freebsd/netinet/in_pcb.h> +#ifdef INET6 +#include <freebsd/netinet/icmp6.h> +#endif + +#include <freebsd/sys/types.h> +#include <freebsd/netipsec/ipsec.h> +#ifdef INET6 +#include <freebsd/netipsec/ipsec6.h> +#endif +#include <freebsd/netipsec/ah_var.h> +#include <freebsd/netipsec/esp_var.h> +#include <freebsd/netipsec/ipcomp.h> /*XXX*/ +#include <freebsd/netipsec/ipcomp_var.h> + +#include <freebsd/netipsec/key.h> +#include <freebsd/netipsec/keydb.h> +#include <freebsd/netipsec/key_debug.h> + +#include <freebsd/netipsec/xform.h> + +#include <freebsd/machine/in_cksum.h> + +#include <freebsd/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_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; +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; + +/* + * Crypto support requirements: + * + * 1 require hardware support + * -1 require software support + * 0 take anything + */ +VNET_DEFINE(int, crypto_support) = CRYPTOCAP_F_HARDWARE | CRYPTOCAP_F_SOFTWARE; + +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, + "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, + "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, + "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, + "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, + "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, + "Do not fragment bit on encap."); +SYSCTL_VNET_INT(_net_inet_ipsec, IPSECCTL_ECN, ecn, + 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, + "Crypto driver selection."); +SYSCTL_VNET_STRUCT(_net_inet_ipsec, OID_AUTO, ipsecstats, + CTLFLAG_RD, &VNET_NAME(ipsec4stat), ipsecstat, + "IPsec IPv4 statistics."); + +#ifdef REGRESSION +/* + * When set to 1, IPsec will send packets with the same sequence number. + * This allows to verify if the other side has proper replay attacks detection. + */ +VNET_DEFINE(int, ipsec_replay) = 0; +SYSCTL_VNET_INT(_net_inet_ipsec, OID_AUTO, test_replay, + 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, + "Emulate man-in-the-middle attack"); +#endif + +#ifdef INET6 +VNET_DEFINE(struct ipsecstat, ipsec6stat); +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) */ + +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, + "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, + "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, + "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, + "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, + "AH tunnel mode default level."); +SYSCTL_VNET_INT(_net_inet6_ipsec6, IPSECCTL_ECN, + ecn, 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."); +#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 *)); +#ifdef INET6 +static void ipsec6_get_ulp __P((struct mbuf *m, struct secpolicyindex *, int)); +static int ipsec6_setspidx_ipaddr __P((struct mbuf *, struct secpolicyindex *)); +#endif +static void ipsec_delpcbpolicy __P((struct inpcbpolicy *)); +static struct secpolicy *ipsec_deepcopy_policy __P((struct secpolicy *src)); +static 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) +{ + 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); +} +#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) +{ + 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); +} + +/* + * 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) +{ + 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)); + + /* Set spidx in pcb. */ + *error = ipsec_setspidx_inpcb(m, inp); + if (*error) + return (NULL); + + 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; + } + 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); + } + } + } + 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 (sp == NULL) /* No SP found, use system default. */ + sp = KEY_ALLOCSP_DEFAULT(); + IPSEC_ASSERT(sp != NULL, ("null SP")); + return (sp); +} + +struct secpolicy * +ipsec4_checkpolicy(struct mbuf *m, u_int dir, u_int flag, int *error, + struct inpcb *inp) +{ + 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")); + V_ipsec4stat.ips_out_inval++; + return (NULL); + } + IPSEC_ASSERT(*error == 0, ("sp w/ error set to %u", *error)); + switch (sp->policy) { + case IPSEC_POLICY_ENTRUST: + default: + printf("%s: invalid policy %u\n", __func__, sp->policy); + /* FALLTHROUGH */ + case IPSEC_POLICY_DISCARD: + V_ipsec4stat.ips_out_polvio++; + *error = -EINVAL; /* Packet is discarded by caller. */ + break; + case IPSEC_POLICY_BYPASS: + case IPSEC_POLICY_NONE: + KEY_FREESP(&sp); + sp = NULL; /* NB: force NULL result. */ + break; + case IPSEC_POLICY_IPSEC: + if (sp->req == NULL) /* Acquire a SA. */ + *error = key_spdacquire(sp); + break; + } + if (*error != 0) { + KEY_FREESP(&sp); + sp = NULL; + } + return (sp); +} + +static int +ipsec_setspidx_inpcb(struct mbuf *m, struct inpcb *inp) +{ + int error; + + 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")); + + 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; + } 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)); + } + 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? + */ + 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); + } + 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); + } +} + +static void +ipsec4_get_ulp(struct mbuf *m, struct secpolicyindex *spidx, int needport) +{ + u_int8_t nxt; + int off; + + /* Sanity check. */ + IPSEC_ASSERT(m != NULL, ("null mbuf")); + 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)) + goto done; +#ifdef _IP_VHL + off = _IP_VHL_HL(ip->ip_vhl) << 2; +#else + off = ip->ip_hl << 2; +#endif + nxt = ip->ip_p; + } else { + struct ip ih; + + m_copydata(m, 0, sizeof (struct ip), (caddr_t) &ih); + if (ih.ip_off & (IP_MF | IP_OFFMASK)) + goto done; +#ifdef _IP_VHL + off = _IP_VHL_HL(ih.ip_vhl) << 2; +#else + off = ih.ip_hl << 2; +#endif + nxt = ih.ip_p; + } + + while (off < m->m_pkthdr.len) { + struct ip6_ext ip6e; + struct tcphdr th; + struct udphdr uh; + + switch (nxt) { + case IPPROTO_TCP: + spidx->ul_proto = nxt; + if (!needport) + goto done_proto; + if (off + sizeof(struct tcphdr) > m->m_pkthdr.len) + goto done; + m_copydata(m, off, sizeof (th), (caddr_t) &th); + spidx->src.sin.sin_port = th.th_sport; + spidx->dst.sin.sin_port = th.th_dport; + return; + case IPPROTO_UDP: + spidx->ul_proto = nxt; + if (!needport) + goto done_proto; + if (off + sizeof(struct udphdr) > m->m_pkthdr.len) + goto done; + m_copydata(m, off, sizeof (uh), (caddr_t) &uh); + spidx->src.sin.sin_port = uh.uh_sport; + spidx->dst.sin.sin_port = uh.uh_dport; + return; + case IPPROTO_AH: + if (off + sizeof(ip6e) > m->m_pkthdr.len) + goto done; + /* XXX Sigh, this works but is totally bogus. */ + m_copydata(m, off, sizeof(ip6e), (caddr_t) &ip6e); + off += (ip6e.ip6e_len + 2) << 2; + nxt = ip6e.ip6e_nxt; + break; + case IPPROTO_ICMP: + default: + /* XXX Intermediate headers??? */ + spidx->ul_proto = nxt; + goto done_proto; + } + } +done: + spidx->ul_proto = IPSEC_ULPROTO_ANY; +done_proto: + spidx->src.sin.sin_port = IPSEC_PORT_ANY; + spidx->dst.sin.sin_port = IPSEC_PORT_ANY; +} + +/* Assumes that m is sane. */ +static int +ipsec4_setspidx_ipaddr(struct mbuf *m, struct secpolicyindex *spidx) +{ + static const struct sockaddr_in template = { + sizeof (struct sockaddr_in), + AF_INET, + 0, { 0 }, { 0, 0, 0, 0, 0, 0, 0, 0 } + }; + + spidx->src.sin = template; + spidx->dst.sin = template; + + if (m->m_len < sizeof (struct ip)) { + m_copydata(m, offsetof(struct ip, ip_src), + sizeof (struct in_addr), + (caddr_t) &spidx->src.sin.sin_addr); + m_copydata(m, offsetof(struct ip, ip_dst), + sizeof (struct in_addr), + (caddr_t) &spidx->dst.sin.sin_addr); + } else { + struct ip *ip = mtod(m, struct ip *); + spidx->src.sin.sin_addr = ip->ip_src; + spidx->dst.sin.sin_addr = ip->ip_dst; + } + + spidx->prefs = sizeof(struct in_addr) << 3; + spidx->prefd = sizeof(struct in_addr) << 3; + + return (0); +} + +#ifdef INET6 +static void +ipsec6_get_ulp(struct mbuf *m, struct secpolicyindex *spidx, int needport) +{ + int off, nxt; + struct tcphdr th; + struct udphdr uh; + struct icmp6_hdr ih; + + /* Sanity check. */ + if (m == NULL) + panic("%s: NULL pointer was passed.\n", __func__); + + KEYDEBUG(KEYDEBUG_IPSEC_DUMP, + printf("%s:\n", __func__); kdebug_mbuf(m)); + + /* Set default. */ + spidx->ul_proto = IPSEC_ULPROTO_ANY; + ((struct sockaddr_in6 *)&spidx->src)->sin6_port = IPSEC_PORT_ANY; + ((struct sockaddr_in6 *)&spidx->dst)->sin6_port = IPSEC_PORT_ANY; + + nxt = -1; + off = ip6_lasthdr(m, 0, IPPROTO_IPV6, &nxt); + if (off < 0 || m->m_pkthdr.len < off) + return; + + switch (nxt) { + case IPPROTO_TCP: + spidx->ul_proto = nxt; + if (!needport) + break; + if (off + sizeof(struct tcphdr) > m->m_pkthdr.len) + break; + m_copydata(m, off, sizeof(th), (caddr_t)&th); + ((struct sockaddr_in6 *)&spidx->src)->sin6_port = th.th_sport; + ((struct sockaddr_in6 *)&spidx->dst)->sin6_port = th.th_dport; + break; + case IPPROTO_UDP: + spidx->ul_proto = nxt; + if (!needport) + break; + if (off + sizeof(struct udphdr) > m->m_pkthdr.len) + break; + m_copydata(m, off, sizeof(uh), (caddr_t)&uh); + ((struct sockaddr_in6 *)&spidx->src)->sin6_port = uh.uh_sport; + ((struct sockaddr_in6 *)&spidx->dst)->sin6_port = uh.uh_dport; + break; + case IPPROTO_ICMPV6: + 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); + break; + default: + /* XXX Intermediate headers??? */ + spidx->ul_proto = nxt; + break; + } +} + +/* Assumes that m is sane. */ +static int +ipsec6_setspidx_ipaddr(struct mbuf *m, struct secpolicyindex *spidx) +{ + struct ip6_hdr *ip6 = NULL; + struct ip6_hdr ip6buf; + struct sockaddr_in6 *sin6; + + if (m->m_len >= sizeof(*ip6)) + ip6 = mtod(m, struct ip6_hdr *); + else { + m_copydata(m, 0, sizeof(ip6buf), (caddr_t)&ip6buf); + ip6 = &ip6buf; + } + + sin6 = (struct sockaddr_in6 *)&spidx->src; + bzero(sin6, sizeof(*sin6)); + sin6->sin6_family = AF_INET6; + sin6->sin6_len = sizeof(struct sockaddr_in6); + bcopy(&ip6->ip6_src, &sin6->sin6_addr, sizeof(ip6->ip6_src)); + if (IN6_IS_SCOPE_LINKLOCAL(&ip6->ip6_src)) { + sin6->sin6_addr.s6_addr16[1] = 0; + sin6->sin6_scope_id = ntohs(ip6->ip6_src.s6_addr16[1]); + } + spidx->prefs = sizeof(struct in6_addr) << 3; + + sin6 = (struct sockaddr_in6 *)&spidx->dst; + bzero(sin6, sizeof(*sin6)); + sin6->sin6_family = AF_INET6; + sin6->sin6_len = sizeof(struct sockaddr_in6); + bcopy(&ip6->ip6_dst, &sin6->sin6_addr, sizeof(ip6->ip6_dst)); + if (IN6_IS_SCOPE_LINKLOCAL(&ip6->ip6_dst)) { + sin6->sin6_addr.s6_addr16[1] = 0; + sin6->sin6_scope_id = ntohs(ip6->ip6_dst.s6_addr16[1]); + } + spidx->prefd = sizeof(struct in6_addr) << 3; + + return (0); +} +#endif + +static void +ipsec_delpcbpolicy(struct inpcbpolicy *p) +{ + + free(p, M_IPSEC_INPCB); +} + +/* Initialize policy in PCB. */ +int +ipsec_init_policy(struct socket *so, struct inpcbpolicy **pcb_sp) +{ + 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; + + if ((new->sp_out = KEY_NEWSP()) == NULL) { + KEY_FREESP(&new->sp_in); + ipsec_delpcbpolicy(new); + return (ENOBUFS); + } + new->sp_out->state = IPSEC_SPSTATE_ALIVE; + new->sp_out->policy = IPSEC_POLICY_ENTRUST; + + *pcb_sp = new; + + return (0); +} + +/* Copy old IPsec policy into new. */ +int +ipsec_copy_policy(struct inpcbpolicy *old, struct inpcbpolicy *new) +{ + struct secpolicy *sp; + + sp = ipsec_deepcopy_policy(old->sp_in); + if (sp) { + KEY_FREESP(&new->sp_in); + new->sp_in = sp; + } else + return (ENOBUFS); + + sp = ipsec_deepcopy_policy(old->sp_out); + if (sp) { + KEY_FREESP(&new->sp_out); + new->sp_out = sp; + } else + return (ENOBUFS); + + new->priv = old->priv; + + return (0); +} + +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; + } + return (NULL); +} + +/* 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) +{ + 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)); + + return (0); +} + +int +ipsec_set_policy(struct inpcb *inp, int optname, caddr_t request, + size_t len, struct ucred *cred) +{ + 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)); +} + +int +ipsec_get_policy(struct inpcb *inp, caddr_t request, size_t len, + struct mbuf **mp) +{ + struct sadb_x_policy *xpl; + struct secpolicy *pcb_sp; + + /* Sanity check. */ + if (inp == NULL || request == NULL || mp == NULL) + return (EINVAL); + IPSEC_ASSERT(inp->inp_sp != NULL, ("null inp_sp")); + if (len < sizeof(*xpl)) + return (EINVAL); + xpl = (struct sadb_x_policy *)request; + + /* Select direction. */ + switch (xpl->sadb_x_policy_dir) { + case IPSEC_DIR_INBOUND: + pcb_sp = inp->inp_sp->sp_in; + break; + case IPSEC_DIR_OUTBOUND: + pcb_sp = inp->inp_sp->sp_out; + break; + default: + ipseclog((LOG_ERR, "%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); + } + + (*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; + + return (0); +} + +/* + * Return current level. + * Either IPSEC_LEVEL_USE or IPSEC_LEVEL_REQUIRE are always returned. + */ +u_int +ipsec_get_reqlevel(struct ipsecrequest *isr) +{ + u_int level = 0; + u_int esp_trans_deflev, esp_net_deflev; + u_int ah_trans_deflev, ah_net_deflev; + + IPSEC_ASSERT(isr != NULL && isr->sp != NULL, ("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)); + +/* 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)) + + /* Set default level. */ + switch (((struct sockaddr *)&isr->sp->spidx.src)->sa_family) { +#ifdef INET + case AF_INET: + esp_trans_deflev = IPSEC_CHECK_DEFAULT(V_ip4_esp_trans_deflev); + esp_net_deflev = IPSEC_CHECK_DEFAULT(V_ip4_esp_net_deflev); + ah_trans_deflev = IPSEC_CHECK_DEFAULT(V_ip4_ah_trans_deflev); + ah_net_deflev = IPSEC_CHECK_DEFAULT(V_ip4_ah_net_deflev); + break; +#endif +#ifdef INET6 + case AF_INET6: + esp_trans_deflev = IPSEC_CHECK_DEFAULT(V_ip6_esp_trans_deflev); + esp_net_deflev = IPSEC_CHECK_DEFAULT(V_ip6_esp_net_deflev); + ah_trans_deflev = IPSEC_CHECK_DEFAULT(V_ip6_ah_trans_deflev); + ah_net_deflev = IPSEC_CHECK_DEFAULT(V_ip6_ah_net_deflev); + break; +#endif /* INET6 */ + default: + panic("%s: unknown af %u", + __func__, isr->sp->spidx.src.sa.sa_family); + } + +#undef IPSEC_CHECK_DEFAULT + + /* Set level. */ + switch (isr->level) { + case IPSEC_LEVEL_DEFAULT: + switch (isr->saidx.proto) { + case IPPROTO_ESP: + if (isr->saidx.mode == IPSEC_MODE_TUNNEL) + level = esp_net_deflev; + else + level = esp_trans_deflev; + break; + case IPPROTO_AH: + if (isr->saidx.mode == IPSEC_MODE_TUNNEL) + level = ah_net_deflev; + else + level = ah_trans_deflev; + break; + case IPPROTO_IPCOMP: + /* + * We don't really care, as IPcomp document says that + * we shouldn't compress small packets. + */ + level = IPSEC_LEVEL_USE; + break; + default: + panic("%s: Illegal protocol defined %u\n", __func__, + isr->saidx.proto); + } + break; + + case IPSEC_LEVEL_USE: + case IPSEC_LEVEL_REQUIRE: + level = isr->level; + break; + case IPSEC_LEVEL_UNIQUE: + level = IPSEC_LEVEL_REQUIRE; + break; + + default: + panic("%s: Illegal IPsec level %u\n", __func__, isr->level); + } + + return (level); +} + +/* + * Check security policy requirements against the actual + * packet contents. Return one if the packet should be + * reject as "invalid"; otherwiser return zero to have the + * packet treated as "valid". + * + * OUT: + * 0: valid + * 1: invalid + */ +int +ipsec_in_reject(struct secpolicy *sp, struct mbuf *m) +{ + struct ipsecrequest *isr; + int need_auth; + + KEYDEBUG(KEYDEBUG_IPSEC_DATA, + printf("%s: using SP\n", __func__); kdebug_secpolicy(sp)); + + /* Check policy. */ + switch (sp->policy) { + case IPSEC_POLICY_DISCARD: + return (1); + case IPSEC_POLICY_BYPASS: + case IPSEC_POLICY_NONE: + return (0); + } + + IPSEC_ASSERT(sp->policy == IPSEC_POLICY_IPSEC, + ("invalid policy %u", sp->policy)); + + /* XXX Should compare policy against IPsec header history. */ + + need_auth = 0; + for (isr = sp->req; isr != NULL; isr = isr->next) { + if (ipsec_get_reqlevel(isr) != IPSEC_LEVEL_REQUIRE) + continue; + switch (isr->saidx.proto) { + case IPPROTO_ESP: + if ((m->m_flags & M_DECRYPTED) == 0) { + KEYDEBUG(KEYDEBUG_IPSEC_DUMP, + printf("%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, + 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) + V_ipsec4stat.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) + V_ipsec6stat.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; + + KEYDEBUG(KEYDEBUG_IPSEC_DATA, + printf("%s: using SP\n", __func__); kdebug_secpolicy(sp)); + + switch (sp->policy) { + case IPSEC_POLICY_DISCARD: + case IPSEC_POLICY_BYPASS: + case IPSEC_POLICY_NONE: + return (0); + } + + IPSEC_ASSERT(sp->policy == IPSEC_POLICY_IPSEC, + ("invalid policy %u", sp->policy)); + + size = 0; + for (isr = sp->req; isr != NULL; isr = isr->next) { + size_t clen = 0; + + switch (isr->saidx.proto) { + case IPPROTO_ESP: + clen = esp_hdrsiz(isr->sav); + break; + case IPPROTO_AH: + clen = ah_hdrsiz(isr->sav); + break; + case IPPROTO_IPCOMP: + clen = sizeof(struct ipcomp); + break; + } + + if (isr->saidx.mode == IPSEC_MODE_TUNNEL) { + switch (isr->saidx.dst.sa.sa_family) { + case AF_INET: + clen += sizeof(struct ip); + break; +#ifdef INET6 + case AF_INET6: + clen += sizeof(struct ip6_hdr); + break; +#endif + default: + ipseclog((LOG_ERR, "%s: unknown AF %d in " + "IPsec tunnel SA\n", __func__, + ((struct sockaddr *)&isr->saidx.dst)->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(). + */ +size_t +ipsec_hdrsiz(struct mbuf *m, u_int dir, struct inpcb *inp) +{ + 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); + + 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. */ + } + return (size); +} + +/* + * Check the variable replay window. + * ipsec_chkreplay() performs replay check before ICV verification. + * ipsec_updatereplay() updates replay bitmap. This must be called after + * ICV verification (it also performs replay check, which is usually done + * beforehand). + * 0 (zero) is returned if packet disallowed, 1 if packet permitted. + * + * Based on RFC 2401. + */ +int +ipsec_chkreplay(u_int32_t seq, struct secasvar *sav) +{ + const struct secreplay *replay; + u_int32_t diff; + int fr; + u_int32_t wsizeb; /* Constant: bits of window size. */ + int frlast; /* Constant: last frame. */ + + IPSEC_ASSERT(sav != NULL, ("Null SA")); + IPSEC_ASSERT(sav->replay != NULL, ("Null replay state")); + + replay = sav->replay; + + if (replay->wsize == 0) + return (1); /* No need to check replay. */ + + /* Constant. */ + frlast = replay->wsize - 1; + wsizeb = replay->wsize << 3; + + /* Sequence number of 0 is invalid. */ + if (seq == 0) + return (0); + + /* First time is always okay. */ + if (replay->count == 0) + return (1); + + if (seq > replay->lastseq) { + /* Larger sequences are okay. */ + return (1); + } else { + /* seq is equal or less than lastseq. */ + diff = replay->lastseq - seq; + + /* Over range to check, i.e. too old or wrapped. */ + if (diff >= wsizeb) + return (0); + + fr = frlast - diff / 8; + + /* This packet already seen? */ + if ((replay->bitmap)[fr] & (1 << (diff % 8))) + return (0); + + /* Out of order but good. */ + return (1); + } +} + +/* + * Check replay counter whether to update or not. + * OUT: 0: OK + * 1: NG + */ +int +ipsec_updatereplay(u_int32_t seq, struct secasvar *sav) +{ + struct secreplay *replay; + u_int32_t diff; + int fr; + u_int32_t wsizeb; /* Constant: bits of window size. */ + int frlast; /* Constant: last frame. */ + + IPSEC_ASSERT(sav != NULL, ("Null SA")); + IPSEC_ASSERT(sav->replay != NULL, ("Null replay state")); + + replay = sav->replay; + + if (replay->wsize == 0) + goto ok; /* No need to check replay. */ + + /* Constant. */ + frlast = replay->wsize - 1; + wsizeb = replay->wsize << 3; + + /* Sequence number of 0 is invalid. */ + if (seq == 0) + return (1); + + /* First time. */ + if (replay->count == 0) { + replay->lastseq = seq; + bzero(replay->bitmap, replay->wsize); + (replay->bitmap)[frlast] = 1; + goto ok; + } + + if (seq > replay->lastseq) { + /* seq is larger than lastseq. */ + diff = seq - replay->lastseq; + + /* New larger sequence number. */ + if (diff < wsizeb) { + /* In window. */ + /* Set bit for this packet. */ + vshiftl(replay->bitmap, diff, replay->wsize); + (replay->bitmap)[frlast] |= 1; + } else { + /* This packet has a "way larger". */ + bzero(replay->bitmap, replay->wsize); + (replay->bitmap)[frlast] = 1; + } + replay->lastseq = seq; + + /* Larger is good. */ + } else { + /* seq is equal or less than lastseq. */ + diff = replay->lastseq - seq; + + /* Over range to check, i.e. too old or wrapped. */ + if (diff >= wsizeb) + return (1); + + fr = frlast - diff / 8; + + /* This packet already seen? */ + if ((replay->bitmap)[fr] & (1 << (diff % 8))) + return (1); + + /* Mark as seen. */ + (replay->bitmap)[fr] |= (1 << (diff % 8)); + + /* Out of order but good. */ + } + +ok: + if (replay->count == ~0) { + + /* Set overflow flag. */ + replay->overflow++; + + /* Don't increment, no more packets accepted. */ + if ((sav->flags & SADB_X_EXT_CYCSEQ) == 0) + return (1); + + ipseclog((LOG_WARNING, "%s: replay counter made %d cycle. %s\n", + __func__, replay->overflow, ipsec_logsastr(sav))); + } + + 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) +{ +#ifdef INET6 + char ip6buf[INET6_ADDRSTRLEN]; +#endif + + 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)"); + } +} + +const char * +ipsec_logsastr(struct secasvar *sav) +{ + static char buf[256]; + char *p; + struct secasindex *saidx = &sav->sah->saidx; + + IPSEC_ASSERT(saidx->src.sa.sa_family == saidx->dst.sa.sa_family, + ("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"); +} + +static void +ipsec_init(const void *unused __unused) +{ + + SECPOLICY_LOCK_INIT(&V_ip4_def_policy); + V_ip4_def_policy.refcnt = 1; /* NB: disallow free. */ +} +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) +{ + + xsp->xf_next = xforms; + xforms = xsp; +} + +/* + * Initialize transform support in an sav. + */ +int +xform_init(struct secasvar *sav, int xftype) +{ + struct xformsw *xsp; + + if (sav->tdb_xform != NULL) /* Previously initialized. */ + return (0); + for (xsp = xforms; xsp; xsp = xsp->xf_next) + if (xsp->xf_type == xftype) + return ((*xsp->xf_init)(sav, xsp)); + return (EINVAL); +} diff --git a/freebsd/sys/netipsec/ipsec.h b/freebsd/sys/netipsec/ipsec.h new file mode 100644 index 00000000..ca9a52f3 --- /dev/null +++ b/freebsd/sys/netipsec/ipsec.h @@ -0,0 +1,453 @@ +/* $FreeBSD$ */ +/* $KAME: ipsec.h,v 1.53 2001/11/20 08:32:38 itojun Exp $ */ + +/*- + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * IPsec controller part. + */ + +#ifndef _NETIPSEC_IPSEC_HH_ +#define _NETIPSEC_IPSEC_HH_ + +#if defined(_KERNEL) && !defined(_LKM) && !defined(KLD_MODULE) +#include <freebsd/local/opt_inet.h> +#include <freebsd/local/opt_ipsec.h> +#endif + +#include <freebsd/net/pfkeyv2.h> +#include <freebsd/netipsec/keydb.h> + +#ifdef _KERNEL + +#define IPSEC_ASSERT(_c,_m) KASSERT(_c, _m) + +#define IPSEC_IS_PRIVILEGED_SO(_so) \ + ((_so)->so_cred != NULL && \ + priv_check_cred((_so)->so_cred, PRIV_NETINET_IPSEC, 0) \ + == 0) + +/* + * Security Policy Index + * Ensure that both address families in the "src" and "dst" are same. + * When the value of the ul_proto is ICMPv6, the port field in "src" + * specifies ICMPv6 type, and the port field in "dst" specifies ICMPv6 code. + */ +struct secpolicyindex { + u_int8_t dir; /* direction of packet flow, see blow */ + 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 +}; + +/* Security Policy Data Base */ +struct secpolicy { + LIST_ENTRY(secpolicy) chain; + struct mtx lock; + + 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.*/ + + /* + * lifetime handler. + * the policy can be used without limitiation if both lifetime and + * validtime are zero. + * "lifetime" is passed by sadb_lifetime.sadb_lifetime_addtime. + * "validtime" is passed by sadb_lifetime.sadb_lifetime_usetime. + */ + time_t created; /* time created the policy */ + time_t lastused; /* updated every when kernel sends a packet */ + long lifetime; /* duration of the lifetime of this policy */ + 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 mtx 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... + */ +#define IPSECREQUEST_LOCK_INIT(_isr) \ + mtx_init(&(_isr)->lock, "ipsec request", NULL, MTX_DEF | MTX_RECURSE) +#define IPSECREQUEST_LOCK(_isr) mtx_lock(&(_isr)->lock) +#define IPSECREQUEST_UNLOCK(_isr) mtx_unlock(&(_isr)->lock) +#define IPSECREQUEST_LOCK_DESTROY(_isr) mtx_destroy(&(_isr)->lock) +#define IPSECREQUEST_LOCK_ASSERT(_isr) mtx_assert(&(_isr)->lock, MA_OWNED) + +/* security policy in PCB */ +struct inpcbpolicy { + struct secpolicy *sp_in; + struct secpolicy *sp_out; + int priv; /* privileged socket ? */ +}; + +/* SP acquiring list table. */ +struct secspacq { + LIST_ENTRY(secspacq) chain; + + struct secpolicyindex spidx; + + time_t created; /* for lifetime */ + int count; /* for lifetime */ + /* XXX: here is mbuf place holder to be sent ? */ +}; +#endif /* _KERNEL */ + +/* according to IANA assignment, port 0x0000 and proto 0xff are reserved. */ +#define IPSEC_PORT_ANY 0 +#define IPSEC_ULPROTO_ANY 255 +#define IPSEC_PROTO_ANY 255 + +/* mode of security protocol */ +/* NOTE: DON'T use IPSEC_MODE_ANY at SPD. It's only use in SAD */ +#define IPSEC_MODE_ANY 0 /* i.e. wildcard. */ +#define IPSEC_MODE_TRANSPORT 1 +#define IPSEC_MODE_TUNNEL 2 +#define IPSEC_MODE_TCPMD5 3 /* TCP MD5 mode */ + +/* + * Direction of security policy. + * NOTE: Since INVALID is used just as flag. + * The other are used for loop counter too. + */ +#define IPSEC_DIR_ANY 0 +#define IPSEC_DIR_INBOUND 1 +#define IPSEC_DIR_OUTBOUND 2 +#define IPSEC_DIR_MAX 3 +#define IPSEC_DIR_INVALID 4 + +/* Policy level */ +/* + * IPSEC, ENTRUST and BYPASS are allowed for setsockopt() in PCB, + * DISCARD, IPSEC and NONE are allowed for setkey() in SPD. + * DISCARD and NONE are allowed for system default. + */ +#define IPSEC_POLICY_DISCARD 0 /* discarding packet */ +#define IPSEC_POLICY_NONE 1 /* through IPsec engine */ +#define IPSEC_POLICY_IPSEC 2 /* do IPsec */ +#define IPSEC_POLICY_ENTRUST 3 /* consulting SPD if present. */ +#define IPSEC_POLICY_BYPASS 4 /* only for privileged socket. */ + +/* Security protocol level */ +#define IPSEC_LEVEL_DEFAULT 0 /* reference to system default */ +#define IPSEC_LEVEL_USE 1 /* use SA if present. */ +#define IPSEC_LEVEL_REQUIRE 2 /* require SA. */ +#define IPSEC_LEVEL_UNIQUE 3 /* unique SA. */ + +#define IPSEC_MANUAL_REQID_MAX 0x3fff + /* + * if security policy level == unique, this id + * indicate to a relative SA for use, else is + * zero. + * 1 - 0x3fff are reserved for manual keying. + * 0 are reserved for above reason. Others is + * for kernel use. + * Note that this id doesn't identify SA + * by only itself. + */ +#define IPSEC_REPLAYWSIZE 32 + +/* 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 */ + /* + * 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; +}; + +/* + * 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 */ +#define IPSECCTL_DEF_ESP_NETLEV 4 /* int; ESP tunnel mode */ +#define IPSECCTL_DEF_AH_TRANSLEV 5 /* int; AH transport mode */ +#define IPSECCTL_DEF_AH_NETLEV 6 /* int; AH tunnel mode */ +#if 0 /* obsolete, do not reuse */ +#define IPSECCTL_INBOUND_CALL_IKE 7 +#endif +#define IPSECCTL_AH_CLEARTOS 8 +#define IPSECCTL_AH_OFFSETMASK 9 +#define IPSECCTL_DFBIT 10 +#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; +}; + +struct ipsec_history { + int ih_proto; + u_int32_t ih_spi; +}; + +VNET_DECLARE(int, ipsec_debug); +#define V_ipsec_debug VNET(ipsec_debug) + +#ifdef REGRESSION +VNET_DECLARE(int, ipsec_replay); +VNET_DECLARE(int, ipsec_integrity); + +#define V_ipsec_replay VNET(ipsec_replay) +#define V_ipsec_integrity VNET(ipsec_integrity) +#endif + +VNET_DECLARE(struct ipsecstat, ipsec4stat); +VNET_DECLARE(struct secpolicy, ip4_def_policy); +VNET_DECLARE(int, ip4_esp_trans_deflev); +VNET_DECLARE(int, ip4_esp_net_deflev); +VNET_DECLARE(int, ip4_ah_trans_deflev); +VNET_DECLARE(int, ip4_ah_net_deflev); +VNET_DECLARE(int, ip4_ah_offsetmask); +VNET_DECLARE(int, ip4_ipsec_dfbit); +VNET_DECLARE(int, ip4_ipsec_ecn); +VNET_DECLARE(int, ip4_esp_randpad); +VNET_DECLARE(int, crypto_support); + +#define V_ipsec4stat VNET(ipsec4stat) +#define V_ip4_def_policy VNET(ip4_def_policy) +#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) +#define V_ip4_ah_net_deflev VNET(ip4_ah_net_deflev) +#define V_ip4_ah_offsetmask VNET(ip4_ah_offsetmask) +#define V_ip4_ipsec_dfbit VNET(ip4_ipsec_dfbit) +#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 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 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 *)); + +union sockaddr_union; +extern char * ipsec_address(union sockaddr_union* sa); +extern const char *ipsec_logsastr __P((struct secasvar *)); + +extern void ipsec_dumpmbuf __P((struct mbuf *)); + +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 *)); + +extern void m_checkalignment(const char* where, struct mbuf *m0, + int off, int len); +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)); + +#endif /* ! KERNEL */ + +#endif /* _NETIPSEC_IPSEC_HH_ */ diff --git a/freebsd/sys/netipsec/ipsec6.h b/freebsd/sys/netipsec/ipsec6.h new file mode 100644 index 00000000..7213fce7 --- /dev/null +++ b/freebsd/sys/netipsec/ipsec6.h @@ -0,0 +1,78 @@ +/* $FreeBSD$ */ +/* $KAME: ipsec.h,v 1.44 2001/03/23 08:08:47 itojun Exp $ */ + +/*- + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * IPsec controller part. + */ + +#ifndef _NETIPSEC_IPSEC6_HH_ +#define _NETIPSEC_IPSEC6_HH_ + +#include <freebsd/net/pfkeyv2.h> +#include <freebsd/netipsec/keydb.h> + +#ifdef _KERNEL +VNET_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 V_ipsec6stat VNET(ipsec6stat) +#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) +#define V_ip6_ah_net_deflev VNET(ip6_ah_net_deflev) +#define V_ip6_ipsec_ecn VNET(ip6_ipsec_ecn) + +struct inpcb; + +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 *); + +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)); +#endif /*_KERNEL*/ + +#endif /*_NETIPSEC_IPSEC6_HH_*/ diff --git a/freebsd/sys/netipsec/ipsec_input.c b/freebsd/sys/netipsec/ipsec_input.c new file mode 100644 index 00000000..d8714348 --- /dev/null +++ b/freebsd/sys/netipsec/ipsec_input.c @@ -0,0 +1,891 @@ +#include <freebsd/machine/rtems-bsd-config.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), + * Angelos D. Keromytis (kermit@csd.uch.gr) and + * Niels Provos (provos@physnet.uni-hamburg.de). + * + * 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. + */ + +/* + * IPsec input processing. + */ + +#include <freebsd/local/opt_inet.h> +#include <freebsd/local/opt_inet6.h> +#include <freebsd/local/opt_ipsec.h> +#include <freebsd/local/opt_enc.h> + +#include <freebsd/sys/param.h> +#include <freebsd/sys/systm.h> +#include <freebsd/sys/malloc.h> +#include <freebsd/sys/mbuf.h> +#include <freebsd/sys/domain.h> +#include <freebsd/sys/protosw.h> +#include <freebsd/sys/socket.h> +#include <freebsd/sys/errno.h> +#include <freebsd/sys/syslog.h> + +#include <freebsd/net/if.h> +#include <freebsd/net/pfil.h> +#include <freebsd/net/route.h> +#include <freebsd/net/netisr.h> +#include <freebsd/net/vnet.h> + +#include <freebsd/netinet/in.h> +#include <freebsd/netinet/in_systm.h> +#include <freebsd/netinet/ip.h> +#include <freebsd/netinet/ip_var.h> +#include <freebsd/netinet/in_var.h> + +#include <freebsd/netinet/ip6.h> +#ifdef INET6 +#include <freebsd/netinet6/ip6_var.h> +#endif +#include <freebsd/netinet/in_pcb.h> +#ifdef INET6 +#include <freebsd/netinet/icmp6.h> +#endif + +#include <freebsd/netipsec/ipsec.h> +#ifdef INET6 +#include <freebsd/netipsec/ipsec6.h> +#endif +#include <freebsd/netipsec/ah_var.h> +#include <freebsd/netipsec/esp.h> +#include <freebsd/netipsec/esp_var.h> +#include <freebsd/netipsec/ipcomp_var.h> + +#include <freebsd/netipsec/key.h> +#include <freebsd/netipsec/keydb.h> + +#include <freebsd/netipsec/xform.h> +#include <freebsd/netinet6/ip6protosw.h> + +#include <freebsd/machine/in_cksum.h> +#include <freebsd/machine/stdarg.h> + +#ifdef DEV_ENC +#include <freebsd/net/if_enc.h> +#endif + + +#define IPSEC_ISTAT(p,x,y,z) ((p) == IPPROTO_ESP ? (x)++ : \ + (p) == IPPROTO_AH ? (y)++ : (z)++) + +#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. It's job is to find the right SA + * and call the appropriate transform. The transform callback + * takes care of further processing (like ingress filtering). + */ +static int +ipsec_common_input(struct mbuf *m, int skip, int protoff, int af, int sproto) +{ + union sockaddr_union dst_address; + struct secasvar *sav; + u_int32_t spi; + int error; +#ifdef IPSEC_NAT_T + struct m_tag *tag; +#endif + + IPSEC_ISTAT(sproto, V_espstat.esps_input, V_ahstat.ahs_input, + V_ipcompstat.ipcomps_input); + + IPSEC_ASSERT(m != NULL, ("null packet")); + + IPSEC_ASSERT(sproto == IPPROTO_ESP || sproto == IPPROTO_AH || + sproto == IPPROTO_IPCOMP, + ("unexpected security protocol %u", sproto)); + + if ((sproto == IPPROTO_ESP && !V_esp_enable) || + (sproto == IPPROTO_AH && !V_ah_enable) || + (sproto == IPPROTO_IPCOMP && !V_ipcomp_enable)) { + m_freem(m); + IPSEC_ISTAT(sproto, V_espstat.esps_pdrops, V_ahstat.ahs_pdrops, + V_ipcompstat.ipcomps_pdrops); + return EOPNOTSUPP; + } + + if (m->m_pkthdr.len - skip < 2 * sizeof (u_int32_t)) { + m_freem(m); + IPSEC_ISTAT(sproto, V_espstat.esps_hdrops, V_ahstat.ahs_hdrops, + V_ipcompstat.ipcomps_hdrops); + DPRINTF(("%s: packet too small\n", __func__)); + return EINVAL; + } + + /* Retrieve the SPI from the relevant IPsec header */ + if (sproto == IPPROTO_ESP) + m_copydata(m, skip, sizeof(u_int32_t), (caddr_t) &spi); + else if (sproto == IPPROTO_AH) + m_copydata(m, skip + sizeof(u_int32_t), sizeof(u_int32_t), + (caddr_t) &spi); + else if (sproto == IPPROTO_IPCOMP) { + u_int16_t cpi; + m_copydata(m, skip + sizeof(u_int16_t), sizeof(u_int16_t), + (caddr_t) &cpi); + spi = ntohl(htons(cpi)); + } + + /* + * Find the SA and (indirectly) call the appropriate + * kernel crypto routine. The resulting mbuf chain is a valid + * IP packet ready to go through input processing. + */ + bzero(&dst_address, sizeof (dst_address)); + dst_address.sa.sa_family = af; + switch (af) { +#ifdef INET + case AF_INET: + dst_address.sin.sin_len = sizeof(struct sockaddr_in); + 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 + case AF_INET6: + dst_address.sin6.sin6_len = sizeof(struct sockaddr_in6); + m_copydata(m, offsetof(struct ip6_hdr, ip6_dst), + sizeof(struct in6_addr), + (caddr_t) &dst_address.sin6.sin6_addr); + break; +#endif /* INET6 */ + default: + DPRINTF(("%s: unsupported protocol family %u\n", __func__, af)); + m_freem(m); + IPSEC_ISTAT(sproto, V_espstat.esps_nopf, V_ahstat.ahs_nopf, + V_ipcompstat.ipcomps_nopf); + return EPFNOSUPPORT; + } + + /* NB: only pass dst since key_allocsa follows RFC2401 */ + 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)); + IPSEC_ISTAT(sproto, V_espstat.esps_notdb, V_ahstat.ahs_notdb, + V_ipcompstat.ipcomps_notdb); + m_freem(m); + return ENOENT; + } + + 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)); + IPSEC_ISTAT(sproto, V_espstat.esps_noxform, V_ahstat.ahs_noxform, + V_ipcompstat.ipcomps_noxform); + KEY_FREESAV(&sav); + m_freem(m); + return ENXIO; + } + + /* + * Call appropriate transform and return -- callback takes care of + * everything else. + */ + error = (*sav->tdb_xform->xf_input)(m, sav, skip, protoff); + KEY_FREESAV(&sav); + return error; +} + +#ifdef INET +/* + * Common input handler for IPv4 AH, ESP, and IPCOMP. + */ +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) +{ + 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); +} + +/* + * IPsec input callback for INET protocols. + * This routine is called as the transform callback. + * Takes care of filtering and other sanity checks on + * the processed packet. + */ +int +ipsec4_common_input_cb(struct mbuf *m, struct secasvar *sav, + int skip, int protoff, struct m_tag *mt) +{ + int prot, af, sproto; + struct ip *ip; + struct m_tag *mtag; + struct tdb_ident *tdbi; + struct secasindex *saidx; + int error; +#ifdef INET6 +#ifdef notyet + char ip6buf[INET6_ADDRSTRLEN]; +#endif +#endif + + IPSEC_ASSERT(m != NULL, ("null mbuf")); + IPSEC_ASSERT(sav != NULL, ("null SA")); + IPSEC_ASSERT(sav->sah != NULL, ("null SAH")); + saidx = &sav->sah->saidx; + af = saidx->dst.sa.sa_family; + IPSEC_ASSERT(af == AF_INET, ("unexpected af %u", af)); + sproto = saidx->proto; + IPSEC_ASSERT(sproto == IPPROTO_ESP || sproto == IPPROTO_AH || + sproto == IPPROTO_IPCOMP, + ("unexpected security protocol %u", sproto)); + + /* Sanity check */ + if (m == NULL) { + DPRINTF(("%s: null mbuf", __func__)); + IPSEC_ISTAT(sproto, V_espstat.esps_badkcr, V_ahstat.ahs_badkcr, + V_ipcompstat.ipcomps_badkcr); + KEY_FREESAV(&sav); + return EINVAL; + } + + if (skip != 0) { + /* 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))); + IPSEC_ISTAT(sproto, V_espstat.esps_hdrops, V_ahstat.ahs_hdrops, + V_ipcompstat.ipcomps_hdrops); + error = ENOBUFS; + goto bad; + } + + 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; + +#ifdef notyet + /* IP-in-IP encapsulation */ + if (prot == IPPROTO_IPIP) { + struct ip ipn; + + if (m->m_pkthdr.len - skip < sizeof(struct ip)) { + IPSEC_ISTAT(sproto, V_espstat.esps_hdrops, + V_ahstat.ahs_hdrops, + V_ipcompstat.ipcomps_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, V_espstat.esps_pdrops, + V_ahstat.ahs_pdrops, + V_ipcompstat.ipcomps_pdrops); + error = EACCES; + goto bad; + } + } +#ifdef INET6 + /* 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, V_espstat.esps_hdrops, + V_ahstat.ahs_hdrops, + V_ipcompstat.ipcomps_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); + + /* + * 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, V_espstat.esps_pdrops, + V_ahstat.ahs_pdrops, + V_ipcompstat.ipcomps_pdrops); + error = EACCES; + goto bad; + } + } +#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. + */ + if (mt == NULL && sproto != IPPROTO_IPCOMP) { + mtag = m_tag_get(PACKET_TAG_IPSEC_IN_DONE, + sizeof(struct tdb_ident), M_NOWAIT); + if (mtag == NULL) { + DPRINTF(("%s: failed to get tag\n", __func__)); + IPSEC_ISTAT(sproto, V_espstat.esps_hdrops, + V_ahstat.ahs_hdrops, V_ipcompstat.ipcomps_hdrops); + error = ENOMEM; + 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; + + 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 */ + +#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_INET, ENC_IN|ENC_BEFORE); + + if (prot != IPPROTO_IPIP) + if ((error = ipsec_filter(&m, PFIL_IN, ENC_IN|ENC_BEFORE)) != 0) + return (error); +#endif + + /* + * Re-dispatch via software interrupt. + */ + if ((error = netisr_queue_src(NETISR_IP, (uintptr_t)sav->spi, m))) { + IPSEC_ISTAT(sproto, V_espstat.esps_qfull, V_ahstat.ahs_qfull, + V_ipcompstat.ipcomps_qfull); + + DPRINTF(("%s: queue full; proto %u packet dropped\n", + __func__, sproto)); + return error; + } + 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 */ +} +#endif /* INET */ + +#ifdef INET6 +/* IPv6 AH wrapper. */ +int +ipsec6_common_input(struct mbuf **mp, int *offp, 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, V_espstat.esps_hdrops, + V_ahstat.ahs_hdrops, + V_ipcompstat.ipcomps_hdrops); + m_freem(*mp); + *mp = NULL; + return IPPROTO_DONE; + } + protoff += offsetof(struct ip6_ext, ip6e_nxt); + } + (void) ipsec_common_input(*mp, *offp, protoff, AF_INET6, proto); + return IPPROTO_DONE; +} + +/* + * IPsec input callback, called by the transform callback. Takes care of + * 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) +{ + int prot, af, sproto; + struct ip6_hdr *ip6; + struct m_tag *mtag; + struct tdb_ident *tdbi; + struct secasindex *saidx; + int nxt; + u_int8_t nxt8; + int error, nest; +#ifdef notyet + char ip6buf[INET6_ADDRSTRLEN]; +#endif + + IPSEC_ASSERT(m != NULL, ("null mbuf")); + IPSEC_ASSERT(sav != NULL, ("null SA")); + IPSEC_ASSERT(sav->sah != NULL, ("null SAH")); + saidx = &sav->sah->saidx; + af = saidx->dst.sa.sa_family; + IPSEC_ASSERT(af == AF_INET6, ("unexpected af %u", af)); + sproto = saidx->proto; + IPSEC_ASSERT(sproto == IPPROTO_ESP || sproto == IPPROTO_AH || + sproto == IPPROTO_IPCOMP, + ("unexpected security protocol %u", sproto)); + + /* Sanity check */ + if (m == NULL) { + DPRINTF(("%s: null mbuf", __func__)); + IPSEC_ISTAT(sproto, V_espstat.esps_badkcr, V_ahstat.ahs_badkcr, + V_ipcompstat.ipcomps_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))); + + IPSEC_ISTAT(sproto, V_espstat.esps_hdrops, V_ahstat.ahs_hdrops, + V_ipcompstat.ipcomps_hdrops); + error = EACCES; + 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); + +#ifdef notyet +#ifdef INET + /* IP-in-IP encapsulation */ + if (prot == IPPROTO_IPIP) { + struct ip ipn; + + if (m->m_pkthdr.len - skip < sizeof(struct ip)) { + IPSEC_ISTAT(sproto, V_espstat.esps_hdrops, + V_ahstat.ahs_hdrops, + V_ipcompstat.ipcomps_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_ISTATsproto, (V_espstat.esps_pdrops, + V_ahstat.ahs_pdrops, V_ipcompstat.ipcomps_pdrops); + error = EACCES; + goto bad; + } + } +#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, V_espstat.esps_hdrops, + V_ahstat.ahs_hdrops, + V_ipcompstat.ipcomps_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, V_espstat.esps_pdrops, + V_ahstat.ahs_pdrops, V_ipcompstat.ipcomps_pdrops); + error = EACCES; + goto bad; + } + } +#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. + */ + if (mt == NULL && sproto != IPPROTO_IPCOMP) { + mtag = m_tag_get(PACKET_TAG_IPSEC_IN_DONE, + sizeof(struct tdb_ident), M_NOWAIT); + if (mtag == NULL) { + DPRINTF(("%s: failed to get tag\n", __func__)); + IPSEC_ISTAT(sproto, V_espstat.esps_hdrops, + V_ahstat.ahs_hdrops, V_ipcompstat.ipcomps_hdrops); + error = ENOMEM; + 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; + + 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); +#endif + + /* Retrieve new protocol */ + m_copydata(m, protoff, sizeof(u_int8_t), (caddr_t) &nxt8); + + /* + * See the end of ip6_input for this logic. + * IPPROTO_IPV[46] case will be processed just like other ones + */ + nest = 0; + nxt = nxt8; + while (nxt != IPPROTO_DONE) { + if (V_ip6_hdrnestlimit && (++nest > V_ip6_hdrnestlimit)) { + V_ip6stat.ip6s_toomanyhdr++; + error = EINVAL; + goto bad; + } + + /* + * Protection against faulty packet - there should be + * more sanity checks in header chain processing. + */ + if (m->m_pkthdr.len < skip) { + V_ip6stat.ip6s_tooshort++; + in6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_truncated); + error = EINVAL; + goto bad; + } + /* + * Enforce IPsec policy checking if we are seeing last header. + * note that we do not visit this with protocols with pcb layer + * code - like udp/tcp/raw ip. + */ + if ((inet6sw[ip6_protox[nxt]].pr_flags & PR_LASTHDR) != 0 && + ipsec6_in_reject(m, NULL)) { + error = EINVAL; + goto bad; + } + nxt = (*inet6sw[ip6_protox[nxt]].pr_input)(&m, &skip, nxt); + } + return 0; +bad: + 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 */ + } +} +#endif /* INET6 */ diff --git a/freebsd/sys/netipsec/ipsec_mbuf.c b/freebsd/sys/netipsec/ipsec_mbuf.c new file mode 100644 index 00000000..f888e60d --- /dev/null +++ b/freebsd/sys/netipsec/ipsec_mbuf.c @@ -0,0 +1,329 @@ +#include <freebsd/machine/rtems-bsd-config.h> + +/*- + * Copyright (c) 2002, 2003 Sam Leffler, Errno Consulting + * 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 AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* + * IPsec-specific mbuf routines. + */ + +#include <freebsd/local/opt_param.h> + +#include <freebsd/sys/param.h> +#include <freebsd/sys/systm.h> +#include <freebsd/sys/mbuf.h> +#include <freebsd/sys/socket.h> + +#include <freebsd/net/route.h> +#include <freebsd/net/vnet.h> + +#include <freebsd/netinet/in.h> + +#include <freebsd/netipsec/ipsec.h> + +/* + * Make space for a new header of length hlen at skip bytes + * into the packet. When doing this we allocate new mbufs only + * when absolutely necessary. The mbuf where the new header + * is to go is returned together with an offset into the mbuf. + * If NULL is returned then the mbuf chain may have been modified; + * the caller is assumed to always free the chain. + */ +struct mbuf * +m_makespace(struct mbuf *m0, int skip, int hlen, int *off) +{ + struct mbuf *m; + unsigned remain; + + IPSEC_ASSERT(m0 != NULL, ("null mbuf")); + IPSEC_ASSERT(hlen < MHLEN, ("hlen too big: %u", hlen)); + + for (m = m0; m && skip > m->m_len; m = m->m_next) + skip -= m->m_len; + if (m == NULL) + return (NULL); + /* + * At this point skip is the offset into the mbuf m + * where the new header should be placed. Figure out + * if there's space to insert the new header. If so, + * and copying the remainder makes sense then do so. + * Otherwise insert a new mbuf in the chain, splitting + * the contents of m as needed. + */ + remain = m->m_len - skip; /* data to move */ + if (hlen > M_TRAILINGSPACE(m)) { + struct mbuf *n0, *n, **np; + int todo, len, done, alloc; + + n0 = NULL; + np = &n0; + alloc = 0; + done = 0; + todo = remain; + while (todo > 0) { + if (todo > MHLEN) { + n = m_getcl(M_DONTWAIT, m->m_type, 0); + len = MCLBYTES; + } + else { + n = m_get(M_DONTWAIT, m->m_type); + len = MHLEN; + } + if (n == NULL) { + m_freem(n0); + return NULL; + } + *np = n; + np = &n->m_next; + alloc++; + len = min(todo, len); + memcpy(n->m_data, mtod(m, char *) + skip + done, len); + n->m_len = len; + done += len; + todo -= len; + } + + if (hlen <= M_TRAILINGSPACE(m) + remain) { + m->m_len = skip + hlen; + *off = skip; + if (n0 != NULL) { + *np = m->m_next; + m->m_next = n0; + } + } + else { + n = m_get(M_DONTWAIT, m->m_type); + if (n == NULL) { + m_freem(n0); + return NULL; + } + alloc++; + + if ((n->m_next = n0) == NULL) + np = &n->m_next; + n0 = n; + + *np = m->m_next; + m->m_next = n0; + + n->m_len = hlen; + m->m_len = skip; + + m = n; /* header is at front ... */ + *off = 0; /* ... of new mbuf */ + } + V_ipsec4stat.ips_mbinserted++; + } else { + /* + * Copy the remainder to the back of the mbuf + * so there's space to write the new header. + */ + bcopy(mtod(m, caddr_t) + skip, + mtod(m, caddr_t) + skip + hlen, remain); + m->m_len += hlen; + *off = skip; + } + m0->m_pkthdr.len += hlen; /* adjust packet length */ + return m; +} + +/* + * m_pad(m, n) pads <m> with <n> bytes at the end. The packet header + * length is updated, and a pointer to the first byte of the padding + * (which is guaranteed to be all in one mbuf) is returned. + */ +caddr_t +m_pad(struct mbuf *m, int n) +{ + register struct mbuf *m0, *m1; + register int len, pad; + caddr_t retval; + + if (n <= 0) { /* No stupid arguments. */ + DPRINTF(("%s: pad length invalid (%d)\n", __func__, n)); + m_freem(m); + return NULL; + } + + len = m->m_pkthdr.len; + pad = n; + m0 = m; + + while (m0->m_len < len) { + len -= m0->m_len; + m0 = m0->m_next; + } + + if (m0->m_len != len) { + DPRINTF(("%s: length mismatch (should be %d instead of %d)\n", + __func__, m->m_pkthdr.len, + m->m_pkthdr.len + m0->m_len - len)); + + m_freem(m); + return NULL; + } + + /* Check for zero-length trailing mbufs, and find the last one. */ + for (m1 = m0; m1->m_next; m1 = m1->m_next) { + if (m1->m_next->m_len != 0) { + DPRINTF(("%s: length mismatch (should be %d instead " + "of %d)\n", __func__, + m->m_pkthdr.len, + m->m_pkthdr.len + m1->m_next->m_len)); + + m_freem(m); + return NULL; + } + + m0 = m1->m_next; + } + + if (pad > M_TRAILINGSPACE(m0)) { + /* Add an mbuf to the chain. */ + MGET(m1, M_DONTWAIT, MT_DATA); + if (m1 == 0) { + m_freem(m0); + DPRINTF(("%s: unable to get extra mbuf\n", __func__)); + return NULL; + } + + m0->m_next = m1; + m0 = m1; + m0->m_len = 0; + } + + retval = m0->m_data + m0->m_len; + m0->m_len += pad; + m->m_pkthdr.len += pad; + + return retval; +} + +/* + * Remove hlen data at offset skip in the packet. This is used by + * the protocols strip protocol headers and associated data (e.g. IV, + * authenticator) on input. + */ +int +m_striphdr(struct mbuf *m, int skip, int hlen) +{ + struct mbuf *m1; + int roff; + + /* Find beginning of header */ + m1 = m_getptr(m, skip, &roff); + if (m1 == NULL) + return (EINVAL); + + /* Remove the header and associated data from the mbuf. */ + if (roff == 0) { + /* The header was at the beginning of the mbuf */ + V_ipsec4stat.ips_input_front++; + m_adj(m1, hlen); + if ((m1->m_flags & M_PKTHDR) == 0) + m->m_pkthdr.len -= hlen; + } else if (roff + hlen >= m1->m_len) { + struct mbuf *mo; + + /* + * Part or all of the header is at the end of this mbuf, + * so first let's remove the remainder of the header from + * the beginning of the remainder of the mbuf chain, if any. + */ + V_ipsec4stat.ips_input_end++; + if (roff + hlen > m1->m_len) { + /* Adjust the next mbuf by the remainder */ + m_adj(m1->m_next, roff + hlen - m1->m_len); + + /* The second mbuf is guaranteed not to have a pkthdr... */ + m->m_pkthdr.len -= (roff + hlen - m1->m_len); + } + + /* Now, let's unlink the mbuf chain for a second...*/ + mo = m1->m_next; + m1->m_next = NULL; + + /* ...and trim the end of the first part of the chain...sick */ + m_adj(m1, -(m1->m_len - roff)); + if ((m1->m_flags & M_PKTHDR) == 0) + m->m_pkthdr.len -= (m1->m_len - roff); + + /* Finally, let's relink */ + m1->m_next = mo; + } else { + /* + * The header lies in the "middle" of the mbuf; copy + * the remainder of the mbuf down over the header. + */ + V_ipsec4stat.ips_input_middle++; + bcopy(mtod(m1, u_char *) + roff + hlen, + mtod(m1, u_char *) + roff, + m1->m_len - (roff + hlen)); + m1->m_len -= hlen; + m->m_pkthdr.len -= hlen; + } + return (0); +} + +/* + * Diagnostic routine to check mbuf alignment as required by the + * crypto device drivers (that use DMA). + */ +void +m_checkalignment(const char* where, struct mbuf *m0, int off, int len) +{ + int roff; + struct mbuf *m = m_getptr(m0, off, &roff); + caddr_t addr; + + if (m == NULL) + return; + printf("%s (off %u len %u): ", where, off, len); + addr = mtod(m, caddr_t) + roff; + do { + int mlen; + + if (((uintptr_t) addr) & 3) { + printf("addr misaligned %p,", addr); + break; + } + mlen = m->m_len; + if (mlen > len) + mlen = len; + len -= mlen; + if (len && (mlen & 3)) { + printf("len mismatch %u,", mlen); + break; + } + m = m->m_next; + addr = m ? mtod(m, caddr_t) : NULL; + } while (m && len > 0); + for (m = m0; m; m = m->m_next) + printf(" [%p:%u]", mtod(m, caddr_t), m->m_len); + printf("\n"); +} diff --git a/freebsd/sys/netipsec/ipsec_output.c b/freebsd/sys/netipsec/ipsec_output.c new file mode 100644 index 00000000..5fdaa0e5 --- /dev/null +++ b/freebsd/sys/netipsec/ipsec_output.c @@ -0,0 +1,892 @@ +#include <freebsd/machine/rtems-bsd-config.h> + +/*- + * Copyright (c) 2002, 2003 Sam Leffler, Errno Consulting + * 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 AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* + * IPsec output processing. + */ +#include <freebsd/local/opt_inet.h> +#include <freebsd/local/opt_inet6.h> +#include <freebsd/local/opt_ipsec.h> +#include <freebsd/local/opt_enc.h> + +#include <freebsd/sys/param.h> +#include <freebsd/sys/systm.h> +#include <freebsd/sys/mbuf.h> +#include <freebsd/sys/domain.h> +#include <freebsd/sys/protosw.h> +#include <freebsd/sys/socket.h> +#include <freebsd/sys/errno.h> +#include <freebsd/sys/syslog.h> + +#include <freebsd/net/if.h> +#include <freebsd/net/pfil.h> +#include <freebsd/net/route.h> +#include <freebsd/net/vnet.h> + +#include <freebsd/netinet/in.h> +#include <freebsd/netinet/in_systm.h> +#include <freebsd/netinet/ip.h> +#include <freebsd/netinet/ip_var.h> +#include <freebsd/netinet/in_var.h> +#include <freebsd/netinet/ip_ecn.h> +#ifdef INET6 +#include <freebsd/netinet6/ip6_ecn.h> +#endif + +#include <freebsd/netinet/ip6.h> +#ifdef INET6 +#include <freebsd/netinet6/ip6_var.h> +#endif +#include <freebsd/netinet/in_pcb.h> +#ifdef INET6 +#include <freebsd/netinet/icmp6.h> +#endif + +#include <freebsd/netipsec/ipsec.h> +#ifdef INET6 +#include <freebsd/netipsec/ipsec6.h> +#endif +#include <freebsd/netipsec/ah_var.h> +#include <freebsd/netipsec/esp_var.h> +#include <freebsd/netipsec/ipcomp_var.h> + +#include <freebsd/netipsec/xform.h> + +#include <freebsd/netipsec/key.h> +#include <freebsd/netipsec/keydb.h> +#include <freebsd/netipsec/key_debug.h> + +#include <freebsd/machine/in_cksum.h> + +#ifdef IPSEC_NAT_T +#include <freebsd/netinet/udp.h> +#endif + +#ifdef DEV_ENC +#include <freebsd/net/if_enc.h> +#endif + + +int +ipsec_process_done(struct mbuf *m, struct ipsecrequest *isr) +{ + struct tdb_ident *tdbi; + struct m_tag *mtag; + 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")); + + 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; + } + + /* + * Add a record of what we've done or what needs to be done to the + * packet. + */ + 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; + 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. + */ + if (isr->next) { + V_ipsec4stat.ips_out_bundlesa++; + return ipsec4_process_packet(m, isr->next, 0, 0); + } + key_sa_recordxfer(sav, m); /* record data transfer */ + + /* + * We're done with IPsec processing, transmit the packet using the + * appropriate network protocol (IP or IPv6). SPD lookup will be + * performed again there. + */ + switch (saidx->dst.sa.sa_family) { +#ifdef INET + struct ip *ip; + 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 */ +#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); +#endif /* INET6 */ + } + panic("ipsec_process_done"); +bad: + m_freem(m); + KEY_FREESAV(&sav); + return (error); +} + +static struct ipsecrequest * +ipsec_nextisr( + struct mbuf *m, + struct ipsecrequest *isr, + int af, + struct secasindex *saidx, + int *error +) +{ +#define IPSEC_OSTAT(x,y,z) (isr->saidx.proto == IPPROTO_ESP ? (x)++ : \ + isr->saidx.proto == IPPROTO_AH ? (y)++ : (z)++) + struct secasvar *sav; + + IPSECREQUEST_LOCK_ASSERT(isr); + + 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 *); + + 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 SA and validate it. + */ + *error = key_checkrequest(isr, saidx); + if (*error != 0) { + /* + * 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. + */ + V_ipsec4stat.ips_out_nosa++; + goto bad; + } + 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; + /* + * 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. + */ + if (isr == NULL) + return isr; + IPSECREQUEST_LOCK(isr); + goto again; + } + + /* + * Check system global policy controls. + */ + 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(V_espstat.esps_pdrops, V_ahstat.ahs_pdrops, + V_ipcompstat.ipcomps_pdrops); + *error = EHOSTUNREACH; + goto bad; + } + + /* + * Sanity check the SA contents for the caller + * before they invoke the xform output method. + */ + if (sav->tdb_xform == NULL) { + DPRINTF(("%s: no transform for SA\n", __func__)); + IPSEC_OSTAT(V_espstat.esps_noxform, V_ahstat.ahs_noxform, + V_ipcompstat.ipcomps_noxform); + *error = EHOSTUNREACH; + goto bad; + } + return isr; +bad: + IPSEC_ASSERT(*error != 0, ("error return w/ no error code")); + IPSECREQUEST_UNLOCK(isr); + return NULL; +#undef IPSEC_OSTAT +} + +#ifdef INET +/* + * IPsec output logic for IPv4. + */ +int +ipsec4_process_packet( + struct mbuf *m, + struct ipsecrequest *isr, + int flags, + int tunalready) +{ + struct secasindex saidx; + struct secasvar *sav; + struct ip *ip; + 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 */ + + isr = ipsec_nextisr(m, isr, AF_INET, &saidx, &error); + if (isr == NULL) { + if (error != 0) + goto bad; + return EJUSTRETURN; + } + + sav = isr->sav; + +#ifdef DEV_ENC + encif->if_opackets++; + encif->if_obytes += m->m_pkthdr.len; + + /* 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) + goto bad; +#endif + + if (!tunalready) { + union sockaddr_union *dst = &sav->sah->saidx.dst; + int setdf; + + /* + * 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 (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); + } + } + } + +#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) + 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) { + 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); + } + IPSECREQUEST_UNLOCK(isr); + return error; +bad: + if (isr) + IPSECREQUEST_UNLOCK(isr); + if (m) + m_freem(m); + return error; +} +#endif + +#ifdef INET6 +/* + * Chop IP6 header from the payload. + */ +static struct mbuf * +ipsec6_splithdr(struct mbuf *m) +{ + 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) { + m_freem(m); + return NULL; + } + 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; + } + return m; +} + +/* + * IPsec output logic for IPv6, transport mode. + */ +int +ipsec6_output_trans( + struct ipsec_output_state *state, + u_char *nexthdrp, + struct mbuf *mprev, + struct secpolicy *sp, + int flags, + int *tun) +{ + 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; + } + + 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; +} + +static int +ipsec6_encapsulate(struct mbuf *m, struct secasvar *sav) +{ + 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. + */ + 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() */ + } + 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; +} + +/* + * IPsec output logic for IPv6, tunnel mode. + */ +int +ipsec6_output_tunnel(struct ipsec_output_state *state, struct secpolicy *sp, int flags) +{ + struct ip6_hdr *ip6; + struct ipsecrequest *isr; + struct secasindex saidx; + 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)); + + m = state->m; + /* + * transport mode ipsec (before the 1st tunnel mode) is already + * processed by ipsec6_output_trans(). + */ + 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; + } + +#ifdef DEV_ENC + encif->if_opackets++; + encif->if_obytes += m->m_pkthdr.len; + + /* 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 + + /* + * There may be the case that SA status will be changed when + * we are refering to one. So calling splsoftnet(). + */ + 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))); + V_ipsec6stat.ips_out_inval++; + error = EAFNOSUPPORT; + goto bad; + } + + m = ipsec6_splithdr(m); + if (!m) { + V_ipsec6stat.ips_out_nomem++; + error = ENOMEM; + goto bad; + } + error = ipsec6_encapsulate(m, isr->sav); + if (error) { + m = NULL; + 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(state->ro); + } + if (state->ro->ro_rt == NULL) { + V_ip6stat.ip6s_noroute++; + V_ipsec6stat.ips_out_noroute++; + error = EHOSTUNREACH; + 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; + } + + m = ipsec6_splithdr(m); + if (!m) { + V_ipsec6stat.ips_out_nomem++; + error = ENOMEM; + goto bad; + } + ip6 = mtod(m, struct ip6_hdr *); + +#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; +#endif + + 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; +} +#endif /*INET6*/ diff --git a/freebsd/sys/netipsec/key.c b/freebsd/sys/netipsec/key.c new file mode 100644 index 00000000..1bf41660 --- /dev/null +++ b/freebsd/sys/netipsec/key.c @@ -0,0 +1,8086 @@ +#include <freebsd/machine/rtems-bsd-config.h> + +/* $FreeBSD$ */ +/* $KAME: key.c,v 1.191 2001/06/27 10:46:49 sakane Exp $ */ + +/*- + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * This code is referd to RFC 2367 + */ + +#include <freebsd/local/opt_inet.h> +#include <freebsd/local/opt_inet6.h> +#include <freebsd/local/opt_ipsec.h> + +#include <freebsd/sys/types.h> +#include <freebsd/sys/param.h> +#include <freebsd/sys/systm.h> +#include <freebsd/sys/kernel.h> +#include <freebsd/sys/lock.h> +#include <freebsd/sys/mutex.h> +#include <freebsd/sys/mbuf.h> +#include <freebsd/sys/domain.h> +#include <freebsd/sys/protosw.h> +#include <freebsd/sys/malloc.h> +#include <freebsd/sys/socket.h> +#include <freebsd/sys/socketvar.h> +#include <freebsd/sys/sysctl.h> +#include <freebsd/sys/errno.h> +#include <freebsd/sys/proc.h> +#include <freebsd/sys/queue.h> +#include <freebsd/sys/refcount.h> +#include <freebsd/sys/syslog.h> + +#include <freebsd/net/if.h> +#include <freebsd/net/route.h> +#include <freebsd/net/raw_cb.h> +#include <freebsd/net/vnet.h> + +#include <freebsd/netinet/in.h> +#include <freebsd/netinet/in_systm.h> +#include <freebsd/netinet/ip.h> +#include <freebsd/netinet/in_var.h> + +#ifdef INET6 +#include <freebsd/netinet/ip6.h> +#include <freebsd/netinet6/in6_var.h> +#include <freebsd/netinet6/ip6_var.h> +#endif /* INET6 */ + +#ifdef INET +#include <freebsd/netinet/in_pcb.h> +#endif +#ifdef INET6 +#include <freebsd/netinet6/in6_pcb.h> +#endif /* INET6 */ + +#include <freebsd/net/pfkeyv2.h> +#include <freebsd/netipsec/keydb.h> +#include <freebsd/netipsec/key.h> +#include <freebsd/netipsec/keysock.h> +#include <freebsd/netipsec/key_debug.h> + +#include <freebsd/netipsec/ipsec.h> +#ifdef INET6 +#include <freebsd/netipsec/ipsec6.h> +#endif + +#include <freebsd/netipsec/xform.h> + +#include <freebsd/machine/stdarg.h> + +/* randomness */ +#include <freebsd/sys/random.h> + +#define FULLMASK 0xff +#define _BITS(bytes) ((bytes) << 3) + +/* + * Note on SA reference counting: + * - SAs that are not in DEAD state will have (total external reference + 1) + * following value in reference count field. they cannot be freed and are + * referenced from SA header. + * - SAs that are in DEAD state will have (total external reference) + * in reference count field. they are ready to be freed. reference from + * SA header will be removed in key_delsav(), when the reference count + * field hits 0 (= no external reference other than from SA header. + */ + +VNET_DEFINE(u_int32_t, key_debug_level) = 0; +static VNET_DEFINE(u_int, key_spi_trycnt) = 1000; +static VNET_DEFINE(u_int32_t, key_spi_minval) = 0x100; +static VNET_DEFINE(u_int32_t, key_spi_maxval) = 0x0fffffff; /* XXX */ +static VNET_DEFINE(u_int32_t, policy_id) = 0; +/*interval to initialize randseed,1(m)*/ +static VNET_DEFINE(u_int, key_int_random) = 60; +/* interval to expire acquiring, 30(s)*/ +static VNET_DEFINE(u_int, key_larval_lifetime) = 30; +/* counter for blocking SADB_ACQUIRE.*/ +static VNET_DEFINE(int, key_blockacq_count) = 10; +/* lifetime for blocking SADB_ACQUIRE.*/ +static VNET_DEFINE(int, key_blockacq_lifetime) = 20; +/* preferred old sa rather than new sa.*/ +static VNET_DEFINE(int, key_preferred_oldsa) = 1; +#define V_key_spi_trycnt VNET(key_spi_trycnt) +#define V_key_spi_minval VNET(key_spi_minval) +#define V_key_spi_maxval VNET(key_spi_maxval) +#define V_policy_id VNET(policy_id) +#define V_key_int_random VNET(key_int_random) +#define V_key_larval_lifetime VNET(key_larval_lifetime) +#define V_key_blockacq_count VNET(key_blockacq_count) +#define V_key_blockacq_lifetime VNET(key_blockacq_lifetime) +#define V_key_preferred_oldsa VNET(key_preferred_oldsa) + +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]); +#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_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) + + /* registed list */ +static VNET_DEFINE(LIST_HEAD(_regtree, secreg), regtree[SADB_SATYPE_MAX + 1]); +#define V_regtree VNET(regtree) +static struct mtx regtree_lock; +#define REGTREE_LOCK_INIT() \ + mtx_init(®tree_lock, "regtree", "fast ipsec regtree", MTX_DEF) +#define REGTREE_LOCK_DESTROY() mtx_destroy(®tree_lock) +#define REGTREE_LOCK() mtx_lock(®tree_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 */ +#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) +#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) + + /* SP acquiring list */ +static VNET_DEFINE(LIST_HEAD(_spacqtree, secspacq), spacqtree); +#define V_spacqtree VNET(spacqtree) +static struct mtx spacq_lock; +#define SPACQ_LOCK_INIT() \ + mtx_init(&spacq_lock, "spacqtree", \ + "fast ipsec security policy acquire list", MTX_DEF) +#define SPACQ_LOCK_DESTROY() mtx_destroy(&spacq_lock) +#define SPACQ_LOCK() mtx_lock(&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 */ + sizeof(struct sadb_lifetime), /* SADB_EXT_LIFETIME_CURRENT */ + sizeof(struct sadb_lifetime), /* SADB_EXT_LIFETIME_HARD */ + sizeof(struct sadb_lifetime), /* SADB_EXT_LIFETIME_SOFT */ + sizeof(struct sadb_address), /* SADB_EXT_ADDRESS_SRC */ + sizeof(struct sadb_address), /* SADB_EXT_ADDRESS_DST */ + sizeof(struct sadb_address), /* SADB_EXT_ADDRESS_PROXY */ + sizeof(struct sadb_key), /* SADB_EXT_KEY_AUTH */ + sizeof(struct sadb_key), /* SADB_EXT_KEY_ENCRYPT */ + sizeof(struct sadb_ident), /* SADB_EXT_IDENTITY_SRC */ + sizeof(struct sadb_ident), /* SADB_EXT_IDENTITY_DST */ + sizeof(struct sadb_sens), /* SADB_EXT_SENSITIVITY */ + sizeof(struct sadb_prop), /* SADB_EXT_PROPOSAL */ + sizeof(struct sadb_supported), /* SADB_EXT_SUPPORTED_AUTH */ + sizeof(struct sadb_supported), /* SADB_EXT_SUPPORTED_ENCRYPT */ + sizeof(struct sadb_spirange), /* SADB_EXT_SPIRANGE */ + 0, /* SADB_X_EXT_KMPRIVATE */ + sizeof(struct sadb_x_policy), /* SADB_X_EXT_POLICY */ + sizeof(struct sadb_x_sa2), /* SADB_X_SA2 */ + sizeof(struct sadb_x_nat_t_type),/* SADB_X_EXT_NAT_T_TYPE */ + sizeof(struct sadb_x_nat_t_port),/* SADB_X_EXT_NAT_T_SPORT */ + sizeof(struct sadb_x_nat_t_port),/* SADB_X_EXT_NAT_T_DPORT */ + 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 */ +}; +static const int maxsize[] = { + sizeof(struct sadb_msg), /* SADB_EXT_RESERVED */ + sizeof(struct sadb_sa), /* SADB_EXT_SA */ + sizeof(struct sadb_lifetime), /* SADB_EXT_LIFETIME_CURRENT */ + sizeof(struct sadb_lifetime), /* SADB_EXT_LIFETIME_HARD */ + sizeof(struct sadb_lifetime), /* SADB_EXT_LIFETIME_SOFT */ + 0, /* SADB_EXT_ADDRESS_SRC */ + 0, /* SADB_EXT_ADDRESS_DST */ + 0, /* SADB_EXT_ADDRESS_PROXY */ + 0, /* SADB_EXT_KEY_AUTH */ + 0, /* SADB_EXT_KEY_ENCRYPT */ + 0, /* SADB_EXT_IDENTITY_SRC */ + 0, /* SADB_EXT_IDENTITY_DST */ + 0, /* SADB_EXT_SENSITIVITY */ + 0, /* SADB_EXT_PROPOSAL */ + 0, /* SADB_EXT_SUPPORTED_AUTH */ + 0, /* SADB_EXT_SUPPORTED_ENCRYPT */ + sizeof(struct sadb_spirange), /* SADB_EXT_SPIRANGE */ + 0, /* SADB_X_EXT_KMPRIVATE */ + 0, /* SADB_X_EXT_POLICY */ + sizeof(struct sadb_x_sa2), /* SADB_X_SA2 */ + sizeof(struct sadb_x_nat_t_type),/* SADB_X_EXT_NAT_T_TYPE */ + sizeof(struct sadb_x_nat_t_port),/* SADB_X_EXT_NAT_T_SPORT */ + sizeof(struct sadb_x_nat_t_port),/* SADB_X_EXT_NAT_T_DPORT */ + 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 */ +}; + +static VNET_DEFINE(int, ipsec_esp_keymin) = 256; +static VNET_DEFINE(int, ipsec_esp_auth) = 0; +static VNET_DEFINE(int, ipsec_ah_keymin) = 128; + +#define V_ipsec_esp_keymin VNET(ipsec_esp_keymin) +#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); +#endif + +SYSCTL_VNET_INT(_net_key, KEYCTL_DEBUG_LEVEL, debug, + 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, ""); + +/* 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, ""); + +/* 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, ""); + +/* interval to initialize randseed */ +SYSCTL_VNET_INT(_net_key, KEYCTL_RANDOM_INT, + int_random, 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, ""); + +/* 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, ""); + +/* 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, ""); + +/* ESP auth */ +SYSCTL_VNET_INT(_net_key, KEYCTL_ESP_AUTH, esp_auth, + 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, ""); + +/* minimum AH key length */ +SYSCTL_VNET_INT(_net_key, KEYCTL_AH_KEYMIN, ah_keymin, + 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, ""); + +#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"); +MALLOC_DEFINE(M_IPSEC_SP, "ipsecpolicy", "ipsec security policy"); +MALLOC_DEFINE(M_IPSEC_SR, "ipsecrequest", "ipsec security request"); +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"); + +/* + * set parameters into secpolicyindex buffer. + * Must allocate secpolicyindex buffer passed to this function. + */ +#define KEY_SETSECSPIDX(_dir, s, d, ps, pd, ulp, idx) \ +do { \ + bzero((idx), sizeof(struct secpolicyindex)); \ + (idx)->dir = (_dir); \ + (idx)->prefs = (ps); \ + (idx)->prefd = (pd); \ + (idx)->ul_proto = (ulp); \ + bcopy((s), &(idx)->src, ((const struct sockaddr *)(s))->sa_len); \ + bcopy((d), &(idx)->dst, ((const struct sockaddr *)(d))->sa_len); \ +} while (0) + +/* + * set parameters into secasindex buffer. + * Must allocate secasindex buffer before calling this function. + */ +#define KEY_SETSECASIDX(p, m, r, s, d, idx) \ +do { \ + bzero((idx), sizeof(struct secasindex)); \ + (idx)->proto = (p); \ + (idx)->mode = (m); \ + (idx)->reqid = (r); \ + bcopy((s), &(idx)->src, ((const struct sockaddr *)(s))->sa_len); \ + bcopy((d), &(idx)->dst, ((const struct sockaddr *)(d))->sa_len); \ +} while (0) + +/* key statistics */ +struct _keystat { + u_long getspi_count; /* the avarage of count to try to get new SPI */ +} keystat; + +struct sadb_msghdr { + struct sadb_msg *msg; + struct sadb_ext *ext[SADB_EXT_MAX + 1]; + int extoff[SADB_EXT_MAX + 1]; + 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 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 seclifetime *key_dup_lifemsg(const struct sadb_lifetime *src, + struct malloc_type *type); +#ifdef INET6 +static int key_ismyaddr6 __P((struct sockaddr_in6 *)); +#endif + +/* 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) +{ + + refcount_init(&sav->refcnt, 1); +} +static __inline void +sa_addref(struct secasvar *sav) +{ + + refcount_acquire(&sav->refcnt); + IPSEC_ASSERT(sav->refcnt != 0, ("SA refcnt overflow")); +} +static __inline int +sa_delref(struct secasvar *sav) +{ + + IPSEC_ASSERT(sav->refcnt > 0, ("SA refcnt underflow")); + return (refcount_release(&sav->refcnt)); +} + +#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) + + +/* + * Update the refcnt while holding the SPTREE lock. + */ +void +key_addref(struct secpolicy *sp) +{ + SPTREE_LOCK(); + SP_ADDREF(sp); + SPTREE_UNLOCK(); +} + +/* + * Return 0 when there are known to be no SP's for the specified + * direction. Otherwise return 1. This is used by IPsec code + * to optimize performance. + */ +int +key_havesp(u_int dir) +{ + + return (dir == IPSEC_DIR_INBOUND || dir == IPSEC_DIR_OUTBOUND ? + LIST_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. + */ +struct secpolicy * +key_allocsp(struct secpolicyindex *spidx, u_int dir, const char* where, int tag) +{ + 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)); + + /* get a SP entry */ + KEYDEBUG(KEYDEBUG_IPSEC_DATA, + printf("*** objects\n"); + kdebug_secpolicyindex(spidx)); + + 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; + if (key_cmpspidx_withmask(&sp->spidx, spidx)) + goto found; + } + sp = NULL; +found: + if (sp) { + /* sanity check */ + KEY_CHKSPDIR(sp->spidx.dir, dir, __func__); + + /* found a SPD entry */ + sp->lastused = time_second; + SP_ADDREF(sp); + } + 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; +} + +/* + * allocating a SP for OUTBOUND or INBOUND packet. + * Must call key_freesp() later. + * OUT: NULL: not 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) +{ + struct secpolicy *sp; + + IPSEC_ASSERT(dst != NULL, ("null dst")); + 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; + } + sp = NULL; +found: + if (sp) { + /* sanity check */ + KEY_CHKSPDIR(sp->spidx.dir, dir, __func__); + + /* found a SPD entry */ + sp->lastused = time_second; + SP_ADDREF(sp); + } + 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; +} + +#if 0 +/* + * return a policy that matches this particular inbound packet. + * XXX slow + */ +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 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; + } + + SPTREE_LOCK(); + LIST_FOREACH(sp, &V_sptree[dir], chain) { + if (sp->state == IPSEC_SPSTATE_DEAD) + 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; + } + } + sp = NULL; +found: + if (sp) { + sp->lastused = time_second; + SP_ADDREF(sp); + } + 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; +} +#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. + */ +int +key_checkrequest(struct ipsecrequest *isr, const struct secasindex *saidx) +{ + u_int level; + int error; + + 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); +#if 0 + /* + * We do allocate new SA only if the state of SA in the holder is + * SADB_SASTATE_DEAD. The SA for outbound must be the oldest. + */ + if (isr->sav != NULL) { + if (isr->sav->sah == NULL) + panic("%s: sah is null.\n", __func__); + if (isr->sav == (struct secasvar *)LIST_FIRST( + &isr->sav->sah->savtree[SADB_SASTATE_DEAD])) { + KEY_FREESAV(&isr->sav); + isr->sav = NULL; + } + } +#else + /* + * we free any SA stashed 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. + * + * The operation may have negative impact to performance. We may + * want to check cached SA carefully, rather than picking new SA + * every time. + */ + if (isr->sav != NULL) { + KEY_FREESAV(&isr->sav); + isr->sav = NULL; + } +#endif + + /* + * new SA allocation if no SA found. + * key_allocsa_policy should allocate the oldest SA available. + * See key_do_allocsa_policy(), and draft-jenkins-ipsec-rekeying-03.txt. + */ + if (isr->sav == NULL) + isr->sav = key_allocsa_policy(saidx); + + /* 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; + + 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_UNLOCK(); + goto found; + } + } + SAHTREE_UNLOCK(); + + return NULL; + + found: + /* 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; + + /* + * prepared to delete the SA when there is more + * suitable candidate and the lifetime of the SA is not + * permanent. + */ + 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 (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)); + } + SAHTREE_UNLOCK(); + + return candidate; +} + +/* + * 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. + * + * 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) +{ + struct secashead *sah; + 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 + + /* + * 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. + */ + SAHTREE_LOCK(); + if (V_key_preferred_oldsa) { + saorder_state_valid = saorder_state_valid_prefer_old; + arraysize = _ARRAYLEN(saorder_state_valid_prefer_old); + } 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; + } + } + } + sav = NULL; +done: + SAHTREE_UNLOCK(); + + KEYDEBUG(KEYDEBUG_IPSEC_STAMP, + printf("DP %s return SA:%p; refcnt %u\n", __func__, + sav, sav ? sav->refcnt : 0)); + 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) +{ + struct secpolicy *sp = *spp; + + IPSEC_ASSERT(sp != NULL, ("null sp")); + + SPTREE_LOCK(); + SP_DELREF(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)); + + if (sp->refcnt == 0) { + *spp = NULL; + key_delsp(sp); + } + SPTREE_UNLOCK(); +} + +/* + * Must be called after calling key_allocsp(). + * For the packet with socket. + */ +void +key_freeso(struct socket *so) +{ + 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); + + /* 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; + } +} + +static void +key_freesp_so(struct secpolicy **sp) +{ + IPSEC_ASSERT(sp != NULL && *sp != NULL, ("null sp")); + + if ((*sp)->policy == IPSEC_POLICY_ENTRUST || + (*sp)->policy == IPSEC_POLICY_BYPASS) + return; + + IPSEC_ASSERT((*sp)->policy == IPSEC_POLICY_IPSEC, + ("invalid policy %u", (*sp)->policy)); + KEY_FREESP(sp); +} + +/* + * Must be called after calling key_allocsa(). + * This function is called by key_freesp() to free some SA allocated + * for a policy. + */ +void +key_freesav(struct secasvar **psav, const char* where, int tag) +{ + struct secasvar *sav = *psav; + + IPSEC_ASSERT(sav != NULL, ("null sav")); + + 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)); + } +} + +/* %%% SPD management */ +/* + * free security policy entry. + */ +static void +key_delsp(struct secpolicy *sp) +{ + 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); + + for (isr = sp->req; isr != NULL; isr = nextisr) { + if (isr->sav != NULL) { + KEY_FREESAV(&isr->sav); + isr->sav = NULL; + } + + nextisr = isr->next; + ipsec_delisr(isr); + } + _key_delsp(sp); +} + +/* + * search SPD + * OUT: NULL : not found + * others : found, pointer to a SP. + */ +static struct secpolicy * +key_getsp(struct secpolicyindex *spidx) +{ + 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; + if (key_cmpspidx_exactly(spidx, &sp->spidx)) { + SP_ADDREF(sp); + break; + } + } + SPTREE_UNLOCK(); + + return sp; +} + +/* + * get SP by index. + * OUT: NULL : not found + * others : found, pointer to a SP. + */ +static struct secpolicy * +key_getspbyid(u_int32_t id) +{ + 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; + if (sp->id == id) { + SP_ADDREF(sp); + goto done; + } + } +done: + SPTREE_UNLOCK(); + + return sp; +} + +struct secpolicy * +key_newsp(const char* where, int tag) +{ + struct secpolicy *newsp = NULL; + + 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; + } + + KEYDEBUG(KEYDEBUG_IPSEC_STAMP, + printf("DP %s from %s:%u return SP:%p\n", __func__, + where, tag, newsp)); + return newsp; +} + +static void +_key_delsp(struct secpolicy *sp) +{ + SECPOLICY_LOCK_DESTROY(sp); + free(sp, M_IPSEC_SP); +} + +/* + * create secpolicy structure from sadb_x_policy structure. + * NOTE: `state', `secpolicyindex' 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; +{ + struct secpolicy *newsp; + + IPSEC_ASSERT(xpl0 != NULL, ("null xpl0")); + IPSEC_ASSERT(len >= sizeof(*xpl0), ("policy too short: %zu", len)); + + if (len != PFKEY_EXTLEN(xpl0)) { + ipseclog((LOG_DEBUG, "%s: Invalid msg length.\n", __func__)); + *error = EINVAL; + return 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; + + /* check policy */ + switch (xpl0->sadb_x_policy_type) { + case IPSEC_POLICY_DISCARD: + 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; + + /* validity check */ + if (PFKEY_EXTLEN(xpl0) < sizeof(*xpl0)) { + ipseclog((LOG_DEBUG, "%s: Invalid msg length.\n", + __func__)); + KEY_FREESP(&newsp); + *error = EINVAL; + return NULL; + } + + tlen = PFKEY_EXTLEN(xpl0) - sizeof(*xpl0); + xisr = (struct sadb_x_ipsecrequest *)(xpl0 + 1); + + while (tlen > 0) { + /* length check */ + if (xisr->sadb_x_ipsecrequest_len < sizeof(*xisr)) { + ipseclog((LOG_DEBUG, "%s: invalid ipsecrequest " + "length.\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) { + ipseclog((LOG_DEBUG, + "%s: No more memory.\n", __func__)); + KEY_FREESP(&newsp); + *error = ENOBUFS; + return NULL; + } + + /* set values */ + switch (xisr->sadb_x_ipsecrequest_proto) { + case IPPROTO_ESP: + case IPPROTO_AH: + case IPPROTO_IPCOMP: + break; + default: + ipseclog((LOG_DEBUG, + "%s: invalid proto type=%u\n", __func__, + xisr->sadb_x_ipsecrequest_proto)); + KEY_FREESP(&newsp); + *error = EPROTONOSUPPORT; + return NULL; + } + (*p_isr)->saidx.proto = xisr->sadb_x_ipsecrequest_proto; + + switch (xisr->sadb_x_ipsecrequest_mode) { + case IPSEC_MODE_TRANSPORT: + case IPSEC_MODE_TUNNEL: + break; + case IPSEC_MODE_ANY: + default: + ipseclog((LOG_DEBUG, + "%s: invalid mode=%u\n", __func__, + xisr->sadb_x_ipsecrequest_mode)); + KEY_FREESP(&newsp); + *error = EINVAL; + return NULL; + } + (*p_isr)->saidx.mode = xisr->sadb_x_ipsecrequest_mode; + + switch (xisr->sadb_x_ipsecrequest_level) { + case IPSEC_LEVEL_DEFAULT: + case IPSEC_LEVEL_USE: + case IPSEC_LEVEL_REQUIRE: + break; + case IPSEC_LEVEL_UNIQUE: + /* validity check */ + /* + * If range violation of reqid, kernel will + * update it, don't refuse it. + */ + if (xisr->sadb_x_ipsecrequest_reqid + > IPSEC_MANUAL_REQID_MAX) { + ipseclog((LOG_DEBUG, + "%s: reqid=%d range " + "violation, updated by kernel.\n", + __func__, + xisr->sadb_x_ipsecrequest_reqid)); + xisr->sadb_x_ipsecrequest_reqid = 0; + } + + /* allocate new reqid id if reqid is zero. */ + if (xisr->sadb_x_ipsecrequest_reqid == 0) { + u_int32_t reqid; + if ((reqid = key_newreqid()) == 0) { + KEY_FREESP(&newsp); + *error = ENOBUFS; + return NULL; + } + (*p_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; + } + break; + + default: + ipseclog((LOG_DEBUG, "%s: invalid level=%u\n", + __func__, + xisr->sadb_x_ipsecrequest_level)); + KEY_FREESP(&newsp); + *error = EINVAL; + return NULL; + } + (*p_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)) { + ipseclog((LOG_DEBUG, "%s: invalid " + "request address length.\n", + __func__)); + 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); + + /* validity check */ + if (paddr->sa_len + > sizeof((*p_isr)->saidx.dst)) { + ipseclog((LOG_DEBUG, "%s: invalid " + "request address length.\n", + __func__)); + KEY_FREESP(&newsp); + *error = EINVAL; + return NULL; + } + bcopy(paddr, &(*p_isr)->saidx.dst, + paddr->sa_len); + } + + (*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); + *error = EINVAL; + return NULL; + } + + xisr = (struct sadb_x_ipsecrequest *)((caddr_t)xisr + + xisr->sadb_x_ipsecrequest_len); + } + } + break; + default: + ipseclog((LOG_DEBUG, "%s: invalid policy type.\n", __func__)); + KEY_FREESP(&newsp); + *error = EINVAL; + return NULL; + } + + *error = 0; + return newsp; +} + +static u_int32_t +key_newreqid() +{ + static u_int32_t auto_reqid = IPSEC_MANUAL_REQID_MAX + 1; + + auto_reqid = (auto_reqid == ~0 + ? IPSEC_MANUAL_REQID_MAX + 1 : auto_reqid + 1); + + /* XXX should be unique check */ + + return auto_reqid; +} + +/* + * copy secpolicy struct to sadb_x_policy structure indicated. + */ +struct mbuf * +key_sp2msg(sp) + struct secpolicy *sp; +{ + struct sadb_x_policy *xpl; + int tlen; + caddr_t p; + struct mbuf *m; + + IPSEC_ASSERT(sp != NULL, ("null policy")); + + tlen = key_getspreqmsglen(sp); + + m = key_alloc_mbuf(tlen); + if (!m || m->m_next) { /*XXX*/ + if (m) + m_freem(m); + return NULL; + } + + m->m_len = tlen; + m->m_next = NULL; + xpl = mtod(m, struct sadb_x_policy *); + bzero(xpl, tlen); + + xpl->sadb_x_policy_len = PFKEY_UNIT64(tlen); + 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); + + /* 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) { + + xisr = (struct sadb_x_ipsecrequest *)p; + + xisr->sadb_x_ipsecrequest_proto = isr->saidx.proto; + xisr->sadb_x_ipsecrequest_mode = isr->saidx.mode; + xisr->sadb_x_ipsecrequest_level = isr->level; + xisr->sadb_x_ipsecrequest_reqid = isr->saidx.reqid; + + p += sizeof(*xisr); + 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); + } + } + + return m; +} + +/* 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 +{ + va_list ap; + int idx; + int i; + struct mbuf *result = NULL, *n; + int len; + + IPSEC_ASSERT(m != NULL, ("null mbuf")); + IPSEC_ASSERT(mhp != NULL, ("null msghdr")); + + va_start(ap, nitem); + for (i = 0; i < nitem; i++) { + idx = va_arg(ap, int); + if (idx < 0 || idx > SADB_EXT_MAX) + goto fail; + /* don't attempt to pull empty extension */ + if (idx == SADB_EXT_RESERVED && mhp->msg == NULL) + continue; + if (idx != SADB_EXT_RESERVED && + (mhp->ext[idx] == NULL || mhp->extlen[idx] == 0)) + continue; + + if (idx == SADB_EXT_RESERVED) { + len = PFKEY_ALIGN8(sizeof(struct sadb_msg)); + + IPSEC_ASSERT(len <= MHLEN, ("header too big %u", len)); + + MGETHDR(n, M_DONTWAIT, MT_DATA); + if (!n) + goto fail; + n->m_len = len; + n->m_next = NULL; + m_copydata(m, 0, sizeof(struct sadb_msg), + 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); + goto fail; + } + 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); + } + if (n == NULL) + goto fail; + + if (result) + m_cat(result, n); + else + result = n; + } + va_end(ap); + + if ((result->m_flags & M_PKTHDR) != 0) { + result->m_pkthdr.len = 0; + for (n = result; n; n = n->m_next) + result->m_pkthdr.len += n->m_len; + } + + return result; + +fail: + m_freem(result); + return NULL; +} + +/* + * SADB_X_SPDADD, SADB_X_SPDSETIDX or SADB_X_SPDUPDATE processing + * add an entry to SP database, when received + * <base, address(SD), (lifetime(H),) policy> + * from the user(?). + * Adding to SP database, + * and send + * <base, address(SD), (lifetime(H),) policy> + * to the socket which was send. + * + * SPDADD set a unique policy entry. + * SPDSETIDX like SPDADD without a part of policy requests. + * SPDUPDATE replace a unique policy entry. + * + * m will always be freed. + */ +static int +key_spdadd(so, m, mhp) + struct socket *so; + struct mbuf *m; + const struct sadb_msghdr *mhp; +{ + struct sadb_address *src0, *dst0; + struct sadb_x_policy *xpl0, *xpl; + struct sadb_lifetime *lft = NULL; + struct secpolicyindex spidx; + struct secpolicy *newsp; + int error; + + 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 || + mhp->ext[SADB_X_EXT_POLICY] == NULL) { + ipseclog((LOG_DEBUG, "key_spdadd: invalid message is passed.\n")); + 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__)); + 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__)); + return key_senderror(so, m, EINVAL); + } + lft = (struct sadb_lifetime *)mhp->ext[SADB_EXT_LIFETIME_HARD]; + } + + src0 = (struct sadb_address *)mhp->ext[SADB_EXT_ADDRESS_SRC]; + 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. */ + 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; + } + + /* 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__)); + 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__)); + 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) { + 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 */ + 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) { + struct sockaddr *sa; + sa = (struct sockaddr *)(src0 + 1); + if (sa->sa_family != newsp->req->saidx.src.sa.sa_family) { + _key_delsp(newsp); + return key_senderror(so, m, EINVAL); + } + } + if (newsp->req && newsp->req->saidx.dst.sa.sa_family) { + struct sockaddr *sa; + sa = (struct sockaddr *)(dst0 + 1); + if (sa->sa_family != newsp->req->saidx.dst.sa.sa_family) { + _key_delsp(newsp); + return key_senderror(so, m, EINVAL); + } + } +#endif + + newsp->created = time_second; + newsp->lastused = newsp->created; + newsp->lifetime = lft ? lft->sadb_lifetime_addtime : 0; + newsp->validtime = lft ? lft->sadb_lifetime_usetime : 0; + + 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(); + } + } + + { + 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, + SADB_X_EXT_POLICY, SADB_EXT_LIFETIME_HARD, + SADB_EXT_ADDRESS_SRC, SADB_EXT_ADDRESS_DST); + } else { + n = key_gather_mbuf(m, mhp, 2, 4, SADB_EXT_RESERVED, + SADB_X_EXT_POLICY, + SADB_EXT_ADDRESS_SRC, SADB_EXT_ADDRESS_DST); + } + if (!n) + return key_senderror(so, m, ENOBUFS); + + if (n->m_len < sizeof(*newmsg)) { + n = m_pullup(n, sizeof(*newmsg)); + if (!n) + return key_senderror(so, m, ENOBUFS); + } + newmsg = mtod(n, struct sadb_msg *); + newmsg->sadb_msg_errno = 0; + newmsg->sadb_msg_len = PFKEY_UNIT64(n->m_pkthdr.len); + + off = 0; + mpolicy = m_pulldown(n, PFKEY_ALIGN8(sizeof(struct sadb_msg)), + sizeof(*xpl), &off); + if (mpolicy == NULL) { + /* n is already freed */ + return key_senderror(so, m, ENOBUFS); + } + xpl = (struct sadb_x_policy *)(mtod(mpolicy, caddr_t) + off); + if (xpl->sadb_x_policy_exttype != SADB_X_EXT_POLICY) { + m_freem(n); + return key_senderror(so, m, EINVAL); + } + xpl->sadb_x_policy_id = newsp->id; + + m_freem(m); + return key_sendup_mbuf(so, n, KEY_SENDUP_ALL); + } +} + +/* + * get new policy id. + * OUT: + * 0: failure. + * others: success. + */ +static u_int32_t +key_getnewspid() +{ + u_int32_t newid = 0; + int count = V_key_spi_trycnt; /* XXX */ + struct secpolicy *sp; + + /* when requesting to allocate spi ranged */ + while (count--) { + newid = (V_policy_id = (V_policy_id == ~0 ? 1 : V_policy_id + 1)); + + if ((sp = key_getspbyid(newid)) == NULL) + break; + + KEY_FREESP(&sp); + } + + if (count == 0 || newid == 0) { + ipseclog((LOG_DEBUG, "%s: to allocate policy id is failed.\n", + __func__)); + return 0; + } + + return newid; +} + +/* + * SADB_SPDDELETE processing + * receive + * <base, address(SD), policy(*)> + * from the user(?), and set SADB_SASTATE_DEAD, + * and send, + * <base, address(SD), policy(*)> + * to the ikmpd. + * policy(*) including direction of policy. + * + * m will always be freed. + */ +static int +key_spddelete(so, m, mhp) + struct socket *so; + struct mbuf *m; + const struct sadb_msghdr *mhp; +{ + struct sadb_address *src0, *dst0; + struct sadb_x_policy *xpl0; + struct secpolicyindex spidx; + struct secpolicy *sp; + + IPSEC_ASSERT(so != NULL, ("null so")); + 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 || + mhp->ext[SADB_X_EXT_POLICY] == NULL) { + ipseclog((LOG_DEBUG, "%s: invalid message is passed.\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__)); + 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]; + 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. */ + 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__)); + return key_senderror(so, m, EINVAL); + } + + /* 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); + + { + 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); + if (!n) + return key_senderror(so, m, ENOBUFS); + + newmsg = mtod(n, struct sadb_msg *); + 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_ALL); + } +} + +/* + * SADB_SPDDELETE2 processing + * receive + * <base, policy(*)> + * from the user(?), and set SADB_SASTATE_DEAD, + * and send, + * <base, policy(*)> + * to the ikmpd. + * policy(*) including direction of policy. + * + * m will always be freed. + */ +static int +key_spddelete2(so, m, mhp) + struct socket *so; + struct mbuf *m; + const struct sadb_msghdr *mhp; +{ + u_int32_t id; + struct secpolicy *sp; + + 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__)); + return key_senderror(so, m, EINVAL); + } + + 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)); + return key_senderror(so, m, EINVAL); + } + + SPTREE_LOCK(); + sp->state = IPSEC_SPSTATE_DEAD; + SPTREE_UNLOCK(); + KEY_FREESP(&sp); + + { + struct mbuf *n, *nn; + struct sadb_msg *newmsg; + int off, len; + + /* create new sadb_msg to reply. */ + len = PFKEY_ALIGN8(sizeof(struct sadb_msg)); + + MGETHDR(n, M_DONTWAIT, MT_DATA); + if (n && len > MHLEN) { + MCLGET(n, M_DONTWAIT); + if ((n->m_flags & M_EXT) == 0) { + m_freem(n); + n = NULL; + } + } + if (!n) + return key_senderror(so, m, ENOBUFS); + + n->m_len = len; + n->m_next = NULL; + off = 0; + + m_copydata(m, 0, sizeof(struct sadb_msg), mtod(n, caddr_t) + off); + off += PFKEY_ALIGN8(sizeof(struct sadb_msg)); + + IPSEC_ASSERT(off == len, ("length inconsistency (off %u len %u)", + off, len)); + + n->m_next = m_copym(m, mhp->extoff[SADB_X_EXT_POLICY], + mhp->extlen[SADB_X_EXT_POLICY], M_DONTWAIT); + if (!n->m_next) { + m_freem(n); + return key_senderror(so, m, ENOBUFS); + } + + n->m_pkthdr.len = 0; + for (nn = n; nn; nn = nn->m_next) + n->m_pkthdr.len += nn->m_len; + + newmsg = mtod(n, struct sadb_msg *); + 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_ALL); + } +} + +/* + * SADB_X_GET processing + * receive + * <base, policy(*)> + * from the user(?), + * and send, + * <base, address(SD), policy> + * to the ikmpd. + * policy(*) including direction of policy. + * + * m will always be freed. + */ +static int +key_spdget(so, m, mhp) + struct socket *so; + struct mbuf *m; + const struct sadb_msghdr *mhp; +{ + u_int32_t id; + struct secpolicy *sp; + struct mbuf *n; + + 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__)); + return key_senderror(so, m, EINVAL); + } + + 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)); + return key_senderror(so, m, ENOENT); + } + + n = key_setdumpsp(sp, SADB_X_SPDGET, 0, mhp->msg->sadb_msg_pid); + if (n != NULL) { + m_freem(m); + return key_sendup_mbuf(so, n, KEY_SENDUP_ONE); + } else + return key_senderror(so, m, ENOBUFS); +} + +/* + * SADB_X_SPDACQUIRE processing. + * Acquire policy and SA(s) for a *OUTBOUND* packet. + * send + * <base, policy(*)> + * to KMD, and expect to receive + * <base> with SADB_X_SPDACQUIRE if error occured, + * or + * <base, policy> + * with SADB_X_SPDUPDATE from KMD by PF_KEY. + * policy(*) is without policy requests. + * + * 0 : succeed + * others: error number + */ +int +key_spdacquire(sp) + struct secpolicy *sp; +{ + struct mbuf *result = NULL, *m; + struct secspacq *newspacq; + + IPSEC_ASSERT(sp != NULL, ("null secpolicy")); + IPSEC_ASSERT(sp->req == NULL, ("policy exists")); + IPSEC_ASSERT(sp->policy == IPSEC_POLICY_IPSEC, + ("policy not IPSEC %u", sp->policy)); + + /* Get an entry to check whether sent message or not. */ + newspacq = key_getspacq(&sp->spidx); + if (newspacq != NULL) { + if (V_key_blockacq_count < newspacq->count) { + /* reset counter and do send message. */ + newspacq->count = 0; + } else { + /* increment counter and do nothing. */ + newspacq->count++; + return 0; + } + SPACQ_UNLOCK(); + } else { + /* make new entry for blocking to send SADB_ACQUIRE. */ + newspacq = key_newspacq(&sp->spidx); + if (newspacq == NULL) + return ENOBUFS; + } + + /* create new sadb_msg to reply. */ + m = key_setsadbmsg(SADB_X_SPDACQUIRE, 0, 0, 0, 0, 0); + if (!m) + return ENOBUFS; + + result = m; + + 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); + + return key_sendup_mbuf(NULL, m, KEY_SENDUP_REGISTERED); +} + +/* + * SADB_SPDFLUSH processing + * receive + * <base> + * from the user, and free all entries in secpctree. + * and send, + * <base> + * to the user. + * NOTE: what to do is only marking SADB_SASTATE_DEAD. + * + * m will always be freed. + */ +static int +key_spdflush(so, m, mhp) + struct socket *so; + struct mbuf *m; + const struct sadb_msghdr *mhp; +{ + struct sadb_msg *newmsg; + struct secpolicy *sp; + u_int dir; + + 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 (m->m_len != PFKEY_ALIGN8(sizeof(struct sadb_msg))) + return key_senderror(so, m, EINVAL); + + for (dir = 0; dir < IPSEC_DIR_MAX; dir++) { + SPTREE_LOCK(); + LIST_FOREACH(sp, &V_sptree[dir], chain) + sp->state = IPSEC_SPSTATE_DEAD; + SPTREE_UNLOCK(); + } + + if (sizeof(struct sadb_msg) > m->m_len + M_TRAILINGSPACE(m)) { + ipseclog((LOG_DEBUG, "%s: No more memory.\n", __func__)); + return key_senderror(so, m, ENOBUFS); + } + + if (m->m_next) + m_freem(m->m_next); + m->m_next = NULL; + m->m_pkthdr.len = m->m_len = PFKEY_ALIGN8(sizeof(struct sadb_msg)); + newmsg = mtod(m, struct sadb_msg *); + newmsg->sadb_msg_errno = 0; + newmsg->sadb_msg_len = PFKEY_UNIT64(m->m_pkthdr.len); + + return key_sendup_mbuf(so, m, KEY_SENDUP_ALL); +} + +/* + * SADB_SPDDUMP processing + * receive + * <base> + * from the user, and dump all SP leaves + * and send, + * <base> ..... + * to the ikmpd. + * + * m will always be freed. + */ +static int +key_spddump(so, m, mhp) + struct socket *so; + struct mbuf *m; + const struct sadb_msghdr *mhp; +{ + struct secpolicy *sp; + int cnt; + u_int dir; + struct mbuf *n; + + 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")); + + /* search SPD entry and get buffer size. */ + cnt = 0; + SPTREE_LOCK(); + for (dir = 0; dir < IPSEC_DIR_MAX; dir++) { + LIST_FOREACH(sp, &V_sptree[dir], chain) { + cnt++; + } + } + + if (cnt == 0) { + SPTREE_UNLOCK(); + 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 (n) + key_sendup_mbuf(so, n, KEY_SENDUP_ONE); + } + } + + SPTREE_UNLOCK(); + m_freem(m); + return 0; +} + +static struct mbuf * +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; + + m = key_setsadbmsg(type, 0, SADB_SATYPE_UNSPEC, seq, pid, sp->refcnt); + if (!m) + 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); + if (!m) + goto fail; + m_cat(result, m); + + m = key_setsadbaddr(SADB_EXT_ADDRESS_DST, + &sp->spidx.dst.sa, sp->spidx.prefd, + sp->spidx.ul_proto); + if (!m) + goto fail; + m_cat(result, m); + + m = key_sp2msg(sp); + if (!m) + goto fail; + m_cat(result, m); + + if(sp->lifetime){ + lt.addtime=sp->created; + lt.usetime= sp->lastused; + m = key_setlifetime(<, SADB_EXT_LIFETIME_CURRENT); + if (!m) + goto fail; + m_cat(result, m); + + lt.addtime=sp->lifetime; + lt.usetime= sp->validtime; + m = key_setlifetime(<, SADB_EXT_LIFETIME_HARD); + if (!m) + goto fail; + m_cat(result, m); + } + + if ((result->m_flags & M_PKTHDR) == 0) + goto fail; + + if (result->m_len < sizeof(struct sadb_msg)) { + result = m_pullup(result, sizeof(struct sadb_msg)); + if (result == NULL) + goto fail; + } + + 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); + + return result; + +fail: + m_freem(result); + return NULL; +} + +/* + * get PFKEY message length for security policy and request. + */ +static u_int +key_getspreqmsglen(sp) + struct secpolicy *sp; +{ + u_int tlen; + + tlen = sizeof(struct sadb_x_policy); + + /* if is the policy for ipsec ? */ + if (sp->policy != IPSEC_POLICY_IPSEC) + return tlen; + + /* get length of ipsec requests */ + { + struct ipsecrequest *isr; + int len; + + for (isr = sp->req; isr != NULL; isr = isr->next) { + len = sizeof(struct sadb_x_ipsecrequest) + + isr->saidx.src.sa.sa_len + + isr->saidx.dst.sa.sa_len; + + tlen += PFKEY_ALIGN8(len); + } + } + + return tlen; +} + +/* + * SADB_SPDEXPIRE processing + * send + * <base, address(SD), lifetime(CH), policy> + * to KMD by PF_KEY. + * + * OUT: 0 : succeed + * others : error number + */ +static int +key_spdexpire(sp) + struct secpolicy *sp; +{ + struct mbuf *result = NULL, *m; + int len; + int error = -1; + struct sadb_lifetime *lt; + + /* XXX: Why do we lock ? */ + + IPSEC_ASSERT(sp != NULL, ("null secpolicy")); + + /* set msg header */ + m = key_setsadbmsg(SADB_X_SPDEXPIRE, 0, 0, 0, 0, 0); + if (!m) { + error = ENOBUFS; + goto fail; + } + result = m; + + /* 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); + error = ENOBUFS; + goto fail; + } + 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 = 0; + lt->sadb_lifetime_bytes = 0; + lt->sadb_lifetime_addtime = sp->created; + lt->sadb_lifetime_usetime = sp->lastused; + 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_HARD; + lt->sadb_lifetime_allocations = 0; + lt->sadb_lifetime_bytes = 0; + lt->sadb_lifetime_addtime = sp->lifetime; + 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, + sp->spidx.prefs, sp->spidx.ul_proto); + if (!m) { + error = ENOBUFS; + goto fail; + } + m_cat(result, m); + + /* set sadb_address for destination */ + m = key_setsadbaddr(SADB_EXT_ADDRESS_DST, + &sp->spidx.dst.sa, + sp->spidx.prefd, sp->spidx.ul_proto); + if (!m) { + error = ENOBUFS; + goto fail; + } + m_cat(result, m); + + /* set secpolicy */ + m = key_sp2msg(sp); + if (!m) { + error = ENOBUFS; + goto fail; + } + m_cat(result, m); + + if ((result->m_flags & M_PKTHDR) == 0) { + error = EINVAL; + goto fail; + } + + if (result->m_len < sizeof(struct sadb_msg)) { + result = m_pullup(result, sizeof(struct sadb_msg)); + if (result == NULL) { + error = ENOBUFS; + goto fail; + } + } + + 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); + + return key_sendup_mbuf(NULL, result, KEY_SENDUP_REGISTERED); + + fail: + if (result) + m_freem(result); + return error; +} + +/* %%% SAD management */ +/* + * allocating a memory for new SA head, and copy from the values of mhp. + * OUT: NULL : failure due to the lack of memory. + * others : pointer to new SA head. + */ +static struct secashead * +key_newsah(saidx) + struct secasindex *saidx; +{ + struct secashead *newsah; + + IPSEC_ASSERT(saidx != NULL, ("null saidx")); + + 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; + + /* add to saidxtree */ + newsah->state = SADB_SASTATE_MATURE; + + SAHTREE_LOCK(); + LIST_INSERT_HEAD(&V_sahtree, newsah, chain); + SAHTREE_UNLOCK(); + } + return(newsah); +} + +/* + * delete SA index and all SA registerd. + */ +static void +key_delsah(sah) + 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); + } +} + +/* + * allocating a new SA with LARVAL state. 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(). + * 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; +{ + struct secasvar *newsav; + const struct sadb_sa *xsa; + + 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; + + 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__)); + *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; + } + + + /* 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; + goto done; + } + } + + SECASVAR_LOCK_INIT(newsav); + + /* reset created */ + newsav->created = time_second; + newsav->pid = mhp->msg->sadb_msg_pid; + + /* add to satree */ + newsav->sah = sah; + sa_initref(newsav); + newsav->state = SADB_SASTATE_LARVAL; + + /* XXX locking??? */ + LIST_INSERT_TAIL(&sah->savtree[SADB_SASTATE_LARVAL], newsav, + secasvar, chain); +done: + KEYDEBUG(KEYDEBUG_IPSEC_STAMP, + printf("DP %s from %s:%u return SP:%p\n", __func__, + where, tag, newsav)); + + return newsav; +} + +/* + * free() SA variable entry. + */ +static void +key_cleansav(struct secasvar *sav) +{ + /* + * Cleanup xform state. Note that zeroize'ing causes the + * keys to be cleared; otherwise we must do it ourself. + */ + if (sav->tdb_xform != NULL) { + 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) + bzero(sav->key_enc->key_data, _KEYLEN(sav->key_enc)); + } + if (sav->key_auth != NULL) { + if (sav->key_auth->key_data != NULL) + free(sav->key_auth->key_data, M_IPSEC_MISC); + free(sav->key_auth, M_IPSEC_MISC); + sav->key_auth = NULL; + } + if (sav->key_enc != NULL) { + if (sav->key_enc->key_data != NULL) + free(sav->key_enc->key_data, M_IPSEC_MISC); + 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) { + 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; + } + if (sav->lft_s != NULL) { + free(sav->lft_s, M_IPSEC_MISC); + sav->lft_s = NULL; + } +} + +/* + * free() SA variable entry. + */ +static void +key_delsav(sav) + struct secasvar *sav; +{ + IPSEC_ASSERT(sav != NULL, ("null sav")); + IPSEC_ASSERT(sav->refcnt == 0, ("reference count %u > 0", sav->refcnt)); + + /* remove from SA header */ + if (__LIST_CHAINED(sav)) + LIST_REMOVE(sav, chain); + key_cleansav(sav); + SECASVAR_LOCK_DESTROY(sav); + free(sav, M_IPSEC_SA); +} + +/* + * search SAD. + * OUT: + * NULL : not found + * others : found, pointer to a SA. + */ +static struct secashead * +key_getsah(saidx) + struct secasindex *saidx; +{ + 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_UNLOCK(); + + return sah; +} + +/* + * check not to be duplicated SPI. + * NOTE: this function is too slow due to searching all SAD. + * OUT: + * NULL : not found + * others : found, pointer to a SA. + */ +static struct secasvar * +key_checkspidup(saidx, spi) + struct secasindex *saidx; + u_int32_t spi; +{ + struct secashead *sah; + 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) + break; + } + SAHTREE_UNLOCK(); + + return sav; +} + +/* + * search SAD litmited alive SA, protocol, SPI. + * OUT: + * NULL : not found + * others : found, pointer to a SA. + */ +static struct secasvar * +key_getsavbyspi(sah, spi) + struct secashead *sah; + u_int32_t spi; +{ + 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; + } + } + + return NULL; +} + +/* + * copy SA values from PF_KEY message except *SPI, SEQ, PID, STATE and TYPE*. + * You must update these if need. + * 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; +{ + int error = 0; + + IPSEC_ASSERT(m != NULL, ("null mbuf")); + IPSEC_ASSERT(mhp != NULL, ("null msghdr")); + IPSEC_ASSERT(mhp->msg != NULL, ("null msg")); + + /* 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; + + /* 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)) { + error = EINVAL; + goto fail; + } + + sav->alg_auth = sa0->sadb_sa_auth; + sav->alg_enc = sa0->sadb_sa_encrypt; + sav->flags = sa0->sadb_sa_flags; + + /* 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) { + 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; + } + } + + /* 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)) { + error = EINVAL; + goto fail; + } + switch (mhp->msg->sadb_msg_satype) { + case SADB_SATYPE_AH: + case SADB_SATYPE_ESP: + case SADB_X_SATYPE_TCPSIGNATURE: + if (len == PFKEY_ALIGN8(sizeof(struct sadb_key)) && + sav->alg_auth != SADB_X_AALG_NULL) + error = EINVAL; + break; + case SADB_X_SATYPE_IPCOMP: + default: + error = EINVAL; + break; + } + if (error) { + ipseclog((LOG_DEBUG, "%s: invalid key_auth values.\n", + __func__)); + goto fail; + } + + sav->key_auth = (struct seckey *)key_dup_keymsg(key0, len, + M_IPSEC_MISC); + if (sav->key_auth == NULL ) { + ipseclog((LOG_DEBUG, "%s: No more memory.\n", + __func__)); + 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)) { + error = EINVAL; + goto fail; + } + switch (mhp->msg->sadb_msg_satype) { + case SADB_SATYPE_ESP: + if (len == PFKEY_ALIGN8(sizeof(struct sadb_key)) && + sav->alg_enc != SADB_EALG_NULL) { + error = EINVAL; + break; + } + sav->key_enc = (struct seckey *)key_dup_keymsg(key0, + len, + M_IPSEC_MISC); + if (sav->key_enc == NULL) { + ipseclog((LOG_DEBUG, "%s: No more memory.\n", + __func__)); + error = ENOBUFS; + goto fail; + } + break; + case SADB_X_SATYPE_IPCOMP: + if (len != PFKEY_ALIGN8(sizeof(struct sadb_key))) + error = EINVAL; + sav->key_enc = NULL; /*just in case*/ + break; + case SADB_SATYPE_AH: + case SADB_X_SATYPE_TCPSIGNATURE: + default: + error = EINVAL; + break; + } + if (error) { + ipseclog((LOG_DEBUG, "%s: invalid key_enc value.\n", + __func__)); + goto fail; + } + } + + /* 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)) { + 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)) { + 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; + } + 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)) { + ipseclog((LOG_DEBUG, "%s: invalid flag (derived) " + "given to old-esp.\n", __func__)); + return EINVAL; + } + 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: + if (sav->alg_auth != SADB_AALG_NONE) { + ipseclog((LOG_DEBUG, "%s: protocol and algorithm " + "mismated.\n", __func__)); + return(EINVAL); + } + if ((sav->flags & SADB_X_EXT_RAWCPI) == 0 + && ntohl(sav->spi) >= 0x10000) { + ipseclog((LOG_DEBUG, "%s: invalid cpi for IPComp.\n", + __func__)); + return(EINVAL); + } + error = xform_init(sav, XF_IPCOMP); + break; + case IPPROTO_TCP: + if (sav->alg_enc != SADB_EALG_NONE) { + ipseclog((LOG_DEBUG, "%s: protocol and algorithm " + "mismated.\n", __func__)); + return(EINVAL); + } + error = xform_init(sav, XF_TCPSIGNATURE); + break; + default: + ipseclog((LOG_DEBUG, "%s: Invalid satype.\n", __func__)); + error = EPROTONOSUPPORT; + break; + } + if (error == 0) { + SAHTREE_LOCK(); + key_sa_chgstate(sav, SADB_SASTATE_MATURE); + SAHTREE_UNLOCK(); + } + return (error); +} + +/* + * 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) +{ + struct mbuf *result = NULL, *tres = NULL, *m; + int i; + int dumporder[] = { + SADB_EXT_SA, SADB_X_EXT_SA2, + 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_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 + }; + + 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--) { + m = NULL; + switch (dumporder[i]) { + case SADB_EXT_SA: + m = key_setsadbsa(sav); + if (!m) + goto fail; + break; + + case SADB_X_EXT_SA2: + m = key_setsadbxsa2(sav->sah->saidx.mode, + sav->replay ? sav->replay->count : 0, + sav->sah->saidx.reqid); + if (!m) + goto fail; + break; + + case SADB_EXT_ADDRESS_SRC: + m = key_setsadbaddr(SADB_EXT_ADDRESS_SRC, + &sav->sah->saidx.src.sa, + FULLMASK, IPSEC_ULPROTO_ANY); + if (!m) + goto fail; + break; + + case SADB_EXT_ADDRESS_DST: + m = key_setsadbaddr(SADB_EXT_ADDRESS_DST, + &sav->sah->saidx.dst.sa, + FULLMASK, IPSEC_ULPROTO_ANY); + if (!m) + goto fail; + break; + + case SADB_EXT_KEY_AUTH: + if (!sav->key_auth) + continue; + m = key_setkey(sav->key_auth, SADB_EXT_KEY_AUTH); + if (!m) + goto fail; + break; + + case SADB_EXT_KEY_ENCRYPT: + if (!sav->key_enc) + continue; + m = key_setkey(sav->key_enc, SADB_EXT_KEY_ENCRYPT); + if (!m) + goto fail; + break; + + case SADB_EXT_LIFETIME_CURRENT: + if (!sav->lft_c) + continue; + m = key_setlifetime(sav->lft_c, + SADB_EXT_LIFETIME_CURRENT); + if (!m) + goto fail; + break; + + case SADB_EXT_LIFETIME_HARD: + if (!sav->lft_h) + continue; + m = key_setlifetime(sav->lft_h, + SADB_EXT_LIFETIME_HARD); + if (!m) + goto fail; + break; + + case SADB_EXT_LIFETIME_SOFT: + if (!sav->lft_s) + continue; + m = key_setlifetime(sav->lft_s, + SADB_EXT_LIFETIME_SOFT); + + if (!m) + goto fail; + break; + +#ifdef IPSEC_NAT_T + case SADB_X_EXT_NAT_T_TYPE: + m = key_setsadbxtype(sav->natt_type); + if (!m) + goto fail; + break; + + case SADB_X_EXT_NAT_T_DPORT: + m = key_setsadbxport( + KEY_PORTFROMSADDR(&sav->sah->saidx.dst), + 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), + SADB_X_EXT_NAT_T_SPORT); + if (!m) + goto fail; + break; + + case SADB_X_EXT_NAT_T_OAI: + case SADB_X_EXT_NAT_T_OAR: + 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: + case SADB_EXT_IDENTITY_DST: + /* XXX: should we brought from SPD ? */ + case SADB_EXT_SENSITIVITY: + default: + continue; + } + + if (!m) + goto fail; + if (tres) + m_cat(m, tres); + tres = m; + + } + + m_cat(result, tres); + if (result->m_len < sizeof(struct sadb_msg)) { + result = m_pullup(result, sizeof(struct sadb_msg)); + if (result == NULL) + goto fail; + } + + 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); + + return result; + +fail: + m_freem(result); + m_freem(tres); + return NULL; +} + +/* + * set data into sadb_msg. + */ +static struct mbuf * +key_setsadbmsg(u_int8_t type, u_int16_t tlen, u_int8_t satype, u_int32_t seq, + pid_t pid, u_int16_t reserved) +{ + struct mbuf *m; + struct sadb_msg *p; + int len; + + len = PFKEY_ALIGN8(sizeof(struct sadb_msg)); + if (len > MCLBYTES) + return NULL; + MGETHDR(m, M_DONTWAIT, MT_DATA); + if (m && len > MHLEN) { + MCLGET(m, M_DONTWAIT); + if ((m->m_flags & M_EXT) == 0) { + m_freem(m); + m = NULL; + } + } + if (!m) + return NULL; + m->m_pkthdr.len = m->m_len = len; + m->m_next = NULL; + + p = mtod(m, struct sadb_msg *); + + bzero(p, len); + p->sadb_msg_version = PF_KEY_V2; + p->sadb_msg_type = type; + p->sadb_msg_errno = 0; + p->sadb_msg_satype = satype; + p->sadb_msg_len = PFKEY_UNIT64(tlen); + p->sadb_msg_reserved = reserved; + p->sadb_msg_seq = seq; + p->sadb_msg_pid = (u_int32_t)pid; + + return m; +} + +/* + * copy secasvar data into sadb_address. + */ +static struct mbuf * +key_setsadbsa(sav) + 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; + } + + 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_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; +} + +/* + * 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) +{ + struct mbuf *m; + struct sadb_address *p; + size_t len; + + 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; + } + + p = mtod(m, struct sadb_address *); + + bzero(p, len); + p->sadb_address_len = PFKEY_UNIT64(len); + p->sadb_address_exttype = exttype; + p->sadb_address_proto = ul_proto; + if (prefixlen == FULLMASK) { + switch (saddr->sa_family) { + case AF_INET: + prefixlen = sizeof(struct in_addr) << 3; + break; + case AF_INET6: + prefixlen = sizeof(struct in6_addr) << 3; + break; + default: + ; /*XXX*/ + } + } + p->sadb_address_prefixlen = prefixlen; + p->sadb_address_reserved = 0; + + bcopy(saddr, + mtod(m, caddr_t) + PFKEY_ALIGN8(sizeof(struct sadb_address)), + saddr->sa_len); + + return m; +} + +/* + * set data into sadb_x_sa2. + */ +static struct mbuf * +key_setsadbxsa2(u_int8_t mode, u_int32_t seq, u_int32_t reqid) +{ + struct mbuf *m; + struct sadb_x_sa2 *p; + 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; + } + + p = mtod(m, struct sadb_x_sa2 *); + + bzero(p, len); + p->sadb_x_sa2_len = PFKEY_UNIT64(len); + p->sadb_x_sa2_exttype = SADB_X_EXT_SA2; + p->sadb_x_sa2_mode = mode; + p->sadb_x_sa2_reserved1 = 0; + p->sadb_x_sa2_reserved2 = 0; + p->sadb_x_sa2_sequence = seq; + p->sadb_x_sa2_reqid = reqid; + + return m; +} + +#ifdef IPSEC_NAT_T +/* + * Set a type in sadb_x_nat_t_type. + */ +static struct mbuf * +key_setsadbxtype(u_int16_t type) +{ + struct mbuf *m; + size_t len; + struct sadb_x_nat_t_type *p; + + 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); + return (NULL); + } + + p = mtod(m, struct sadb_x_nat_t_type *); + + bzero(p, len); + p->sadb_x_nat_t_type_len = PFKEY_UNIT64(len); + p->sadb_x_nat_t_type_exttype = SADB_X_EXT_NAT_T_TYPE; + p->sadb_x_nat_t_type_type = type; + + return (m); +} +/* + * Set a port in sadb_x_nat_t_port. + * In contrast to default RFC 2367 behaviour, port is in network byte order. + */ +static struct mbuf * +key_setsadbxport(u_int16_t port, u_int16_t type) +{ + struct mbuf *m; + size_t len; + struct sadb_x_nat_t_port *p; + + 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); + return (NULL); + } + + p = mtod(m, struct sadb_x_nat_t_port *); + + bzero(p, len); + p->sadb_x_nat_t_port_len = PFKEY_UNIT64(len); + p->sadb_x_nat_t_port_exttype = type; + p->sadb_x_nat_t_port_port = port; + + return (m); +} + +/* + * Get port from sockaddr. Port is in network byte order. + */ +u_int16_t +key_portfromsaddr(struct sockaddr *sa) +{ + + switch (sa->sa_family) { +#ifdef INET + case AF_INET: + return ((struct sockaddr_in *)sa)->sin_port; +#endif +#ifdef INET6 + case AF_INET6: + 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) +{ + + switch (sa->sa_family) { +#ifdef INET + case AF_INET: + ((struct sockaddr_in *)sa)->sin_port = port; + break; +#endif +#ifdef INET6 + case AF_INET6: + ((struct sockaddr_in6 *)sa)->sin6_port = port; + break; +#endif + default: + ipseclog((LOG_DEBUG, "%s: unexpected address family %d.\n", + __func__, sa->sa_family)); + break; + } +} + +/* + * set data into sadb_x_policy + */ +static struct mbuf * +key_setsadbxpolicy(u_int16_t type, u_int8_t dir, u_int32_t id) +{ + 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; + } + + p = mtod(m, struct sadb_x_policy *); + + bzero(p, len); + p->sadb_x_policy_len = PFKEY_UNIT64(len); + p->sadb_x_policy_exttype = SADB_X_EXT_POLICY; + p->sadb_x_policy_type = type; + p->sadb_x_policy_dir = dir; + p->sadb_x_policy_id = id; + + return m; +} + +/* %%% utilities */ +/* Take a key message (sadb_key) from the socket and turn it into one + * of the kernel's key structures (seckey). + * + * IN: pointer to the src + * OUT: NULL no more memory + */ +struct seckey * +key_dup_keymsg(const struct sadb_key *src, u_int len, + struct malloc_type *type) +{ + struct seckey *dst; + dst = (struct seckey *)malloc(sizeof(struct seckey), type, M_NOWAIT); + if (dst != NULL) { + dst->bits = src->sadb_key_bits; + dst->key_data = (char *)malloc(len, type, M_NOWAIT); + if (dst->key_data != NULL) { + bcopy((const char *)src + sizeof(struct sadb_key), + dst->key_data, len); + } else { + ipseclog((LOG_DEBUG, "%s: No more memory.\n", + __func__)); + free(dst, type); + dst = NULL; + } + } else { + ipseclog((LOG_DEBUG, "%s: No more memory.\n", + __func__)); + + } + return dst; +} + +/* Take a lifetime message (sadb_lifetime) passed in on a socket and + * turn it into one of the kernel's lifetime structures (seclifetime). + * + * IN: pointer to the destination, source and malloc type + * OUT: NULL, no more memory + */ + +static struct seclifetime * +key_dup_lifemsg(const struct sadb_lifetime *src, + struct malloc_type *type) +{ + struct seclifetime *dst = NULL; + + dst = (struct seclifetime *)malloc(sizeof(struct seclifetime), + 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 <freebsd/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 + } + IN6_IFADDR_RUNLOCK(); + + /* loopback, just for safety */ + if (IN6_IS_ADDR_LOOPBACK(&sin6->sin6_addr)) + return 1; + + return 0; +} +#endif /*INET6*/ + +/* + * compare two secasindex structure. + * flag can specify to compare 2 saidxes. + * compare two secasindex structure without both mode and reqid. + * don't compare port. + * IN: + * saidx0: source, it can be in SAD. + * saidx1: object. + * OUT: + * 1 : equal + * 0 : not equal + */ +static int +key_cmpsaidx( + const struct secasindex *saidx0, + const struct secasindex *saidx1, + int flag) +{ + int chkport = 0; + + /* sanity */ + if (saidx0 == NULL && saidx1 == NULL) + return 1; + + if (saidx0 == NULL || saidx1 == NULL) + return 0; + + if (saidx0->proto != saidx1->proto) + return 0; + + if (flag == CMP_EXACTLY) { + if (saidx0->mode != saidx1->mode) + 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) + return 0; + } else { + + /* CMP_MODE_REQID, CMP_REQID, CMP_HEAD */ + 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) + return 0; + } + + if (flag == CMP_MODE_REQID) { + if (saidx0->mode != IPSEC_MODE_ANY + && saidx0->mode != saidx1->mode) + 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) { + return 0; + } + if (key_sockaddrcmp(&saidx0->dst.sa, &saidx1->dst.sa, chkport) != 0) { + return 0; + } + } + + return 1; +} + +/* + * compare two secindex structure exactly. + * IN: + * spidx0: source, it is often in SPD. + * spidx1: object, it is often from PFKEY message. + * OUT: + * 1 : equal + * 0 : not equal + */ +static int +key_cmpspidx_exactly( + struct secpolicyindex *spidx0, + struct secpolicyindex *spidx1) +{ + /* sanity */ + if (spidx0 == NULL && spidx1 == NULL) + return 1; + + if (spidx0 == NULL || spidx1 == NULL) + return 0; + + if (spidx0->prefs != spidx1->prefs + || spidx0->prefd != spidx1->prefd + || spidx0->ul_proto != spidx1->ul_proto) + return 0; + + return key_sockaddrcmp(&spidx0->src.sa, &spidx1->src.sa, 1) == 0 && + key_sockaddrcmp(&spidx0->dst.sa, &spidx1->dst.sa, 1) == 0; +} + +/* + * compare two secindex structure with mask. + * IN: + * spidx0: source, it is often in SPD. + * spidx1: object, it is often from IP header. + * OUT: + * 1 : equal + * 0 : not equal + */ +static int +key_cmpspidx_withmask( + struct secpolicyindex *spidx0, + struct secpolicyindex *spidx1) +{ + /* sanity */ + if (spidx0 == NULL && spidx1 == NULL) + return 1; + + if (spidx0 == NULL || spidx1 == NULL) + return 0; + + if (spidx0->src.sa.sa_family != spidx1->src.sa.sa_family || + spidx0->dst.sa.sa_family != spidx1->dst.sa.sa_family || + spidx0->src.sa.sa_len != spidx1->src.sa.sa_len || + spidx0->dst.sa.sa_len != spidx1->dst.sa.sa_len) + return 0; + + /* if spidx.ul_proto == IPSEC_ULPROTO_ANY, ignore. */ + if (spidx0->ul_proto != (u_int16_t)IPSEC_ULPROTO_ANY + && spidx0->ul_proto != spidx1->ul_proto) + return 0; + + switch (spidx0->src.sa.sa_family) { + case AF_INET: + if (spidx0->src.sin.sin_port != IPSEC_PORT_ANY + && spidx0->src.sin.sin_port != spidx1->src.sin.sin_port) + return 0; + if (!key_bbcmp(&spidx0->src.sin.sin_addr, + &spidx1->src.sin.sin_addr, spidx0->prefs)) + return 0; + break; + case AF_INET6: + if (spidx0->src.sin6.sin6_port != IPSEC_PORT_ANY + && spidx0->src.sin6.sin6_port != spidx1->src.sin6.sin6_port) + return 0; + /* + * scope_id check. if sin6_scope_id is 0, we regard it + * as a wildcard scope, which matches any scope zone ID. + */ + if (spidx0->src.sin6.sin6_scope_id && + spidx1->src.sin6.sin6_scope_id && + spidx0->src.sin6.sin6_scope_id != spidx1->src.sin6.sin6_scope_id) + return 0; + if (!key_bbcmp(&spidx0->src.sin6.sin6_addr, + &spidx1->src.sin6.sin6_addr, spidx0->prefs)) + return 0; + break; + default: + /* XXX */ + if (bcmp(&spidx0->src, &spidx1->src, spidx0->src.sa.sa_len) != 0) + return 0; + break; + } + + switch (spidx0->dst.sa.sa_family) { + case AF_INET: + if (spidx0->dst.sin.sin_port != IPSEC_PORT_ANY + && spidx0->dst.sin.sin_port != spidx1->dst.sin.sin_port) + return 0; + if (!key_bbcmp(&spidx0->dst.sin.sin_addr, + &spidx1->dst.sin.sin_addr, spidx0->prefd)) + return 0; + break; + case AF_INET6: + if (spidx0->dst.sin6.sin6_port != IPSEC_PORT_ANY + && spidx0->dst.sin6.sin6_port != spidx1->dst.sin6.sin6_port) + return 0; + /* + * scope_id check. if sin6_scope_id is 0, we regard it + * as a wildcard scope, which matches any scope zone ID. + */ + if (spidx0->dst.sin6.sin6_scope_id && + spidx1->dst.sin6.sin6_scope_id && + spidx0->dst.sin6.sin6_scope_id != spidx1->dst.sin6.sin6_scope_id) + return 0; + if (!key_bbcmp(&spidx0->dst.sin6.sin6_addr, + &spidx1->dst.sin6.sin6_addr, spidx0->prefd)) + return 0; + break; + default: + /* XXX */ + if (bcmp(&spidx0->dst, &spidx1->dst, spidx0->dst.sa.sa_len) != 0) + return 0; + break; + } + + /* XXX Do we check other field ? e.g. flowinfo */ + + 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 +#define satosin(s) ((const struct sockaddr_in *)s) +#ifdef satosin6 +#undef satosin6 +#endif +#define satosin6(s) ((const struct sockaddr_in6 *)s) + if (sa1->sa_family != sa2->sa_family || sa1->sa_len != sa2->sa_len) + return 1; + + switch (sa1->sa_family) { + case AF_INET: + if (sa1->sa_len != sizeof(struct sockaddr_in)) + return 1; + if (satosin(sa1)->sin_addr.s_addr != + satosin(sa2)->sin_addr.s_addr) { + return 1; + } + if (port && satosin(sa1)->sin_port != satosin(sa2)->sin_port) + return 1; + break; + case AF_INET6: + if (sa1->sa_len != sizeof(struct sockaddr_in6)) + return 1; /*EINVAL*/ + if (satosin6(sa1)->sin6_scope_id != + satosin6(sa2)->sin6_scope_id) { + return 1; + } + if (!IN6_ARE_ADDR_EQUAL(&satosin6(sa1)->sin6_addr, + &satosin6(sa2)->sin6_addr)) { + return 1; + } + if (port && + satosin6(sa1)->sin6_port != satosin6(sa2)->sin6_port) { + return 1; + } + break; + default: + if (bcmp(sa1, sa2, sa1->sa_len) != 0) + return 1; + break; + } + + return 0; +#undef satosin +#undef satosin6 +} + +/* + * compare two buffers with mask. + * IN: + * addr1: source + * addr2: object + * bits: Number of bits to compare + * OUT: + * 1 : equal + * 0 : not equal + */ +static int +key_bbcmp(const void *a1, const void *a2, u_int bits) +{ + const unsigned char *p1 = a1; + const unsigned char *p2 = a2; + + /* XXX: This could be considerably faster if we compare a word + * at a time, but it is complicated on LSB Endian machines */ + + /* Handle null pointers */ + if (p1 == NULL || p2 == NULL) + return (p1 == p2); + + while (bits >= 8) { + if (*p1++ != *p2++) + return 0; + bits -= 8; + } + + if (bits > 0) { + u_int8_t mask = ~((1<<(8-bits))-1); + if ((*p1 & mask) != (*p2 & mask)) + return 0; + } + return 1; /* Match! */ +} + +static void +key_flush_spd(time_t now) +{ + static u_int16_t sptree_scangen = 0; + u_int16_t gen = sptree_scangen++; + struct secpolicy *sp; + u_int dir; + + /* SPD */ + 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; + } + 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; + } + } + SPTREE_UNLOCK(); + } +} + +static void +key_flush_sad(time_t now) +{ + 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); + 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); + } + + /* + * 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) + continue; + + /* sanity check */ + if (sav->lft_c == NULL) { + ipseclog((LOG_DEBUG,"%s: there is no CURRENT " + "time, why?\n", __func__)); + 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. + */ + 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) + continue; + + /* sanity check */ + if (sav->lft_c == NULL) { + ipseclog((LOG_DEBUG, "%s: there is no CURRENT " + "time, why?\n", __func__)); + continue; + } + + 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 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); + } + } + + /* 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). + */ + } + } + SAHTREE_UNLOCK(); +} + +static void +key_flush_acq(time_t now) +{ + struct secacq *acq, *nextacq; + + /* ACQ tree */ + ACQ_LOCK(); + for (acq = LIST_FIRST(&V_acqtree); acq != NULL; acq = nextacq) { + nextacq = LIST_NEXT(acq, chain); + if (now - acq->created > V_key_blockacq_lifetime + && __LIST_CHAINED(acq)) { + LIST_REMOVE(acq, chain); + free(acq, M_IPSEC_SAQ); + } + } + ACQ_UNLOCK(); +} + +static void +key_flush_spacq(time_t now) +{ + struct secspacq *acq, *nextacq; + + /* SP ACQ tree */ + SPACQ_LOCK(); + for (acq = LIST_FIRST(&V_spacqtree); acq != NULL; acq = nextacq) { + nextacq = LIST_NEXT(acq, chain); + if (now - acq->created > V_key_blockacq_lifetime + && __LIST_CHAINED(acq)) { + LIST_REMOVE(acq, chain); + free(acq, M_IPSEC_SAQ); + } + } + SPACQ_UNLOCK(); +} + +/* + * time handler. + * scanning SPD and SAD to check status for each entries, + * and do to remove or to expire. + * XXX: year 2038 problem may remain. + */ +void +key_timehandler(void) +{ + VNET_ITERATOR_DECL(vnet_iter); + time_t now = time_second; + + VNET_LIST_RLOCK_NOSLEEP(); + VNET_FOREACH(vnet_iter) { + CURVNET_SET(vnet_iter); + key_flush_spd(now); + key_flush_sad(now); + key_flush_acq(now); + key_flush_spacq(now); + CURVNET_RESTORE(); + } + VNET_LIST_RUNLOCK_NOSLEEP(); + +#ifndef IPSEC_DEBUG2 + /* do exchange to tick time !! */ + (void)timeout((void *)key_timehandler, (void *)0, hz); +#endif /* IPSEC_DEBUG2 */ +} + +u_long +key_random() +{ + u_long value; + + key_randomfill(&value, sizeof(value)); + return value; +} + +void +key_randomfill(p, l) + void *p; + size_t l; +{ + size_t n; + u_long v; + static int warn = 1; + + n = 0; + n = (size_t)read_random(p, (u_int)l); + /* last resort */ + while (n < l) { + v = random(); + bcopy(&v, (u_int8_t *)p + n, + l - n < sizeof(v) ? l - n : sizeof(v)); + n += sizeof(v); + + if (warn) { + printf("WARNING: pseudo-random number generator " + "used for IPsec processing\n"); + warn = 0; + } + } +} + +/* + * map SADB_SATYPE_* to IPPROTO_*. + * if satype == SADB_SATYPE then satype is mapped to ~0. + * OUT: + * 0: invalid satype. + */ +static u_int16_t +key_satype2proto(u_int8_t satype) +{ + switch (satype) { + case SADB_SATYPE_UNSPEC: + return IPSEC_PROTO_ANY; + case SADB_SATYPE_AH: + return IPPROTO_AH; + case SADB_SATYPE_ESP: + return IPPROTO_ESP; + case SADB_X_SATYPE_IPCOMP: + return IPPROTO_IPCOMP; + case SADB_X_SATYPE_TCPSIGNATURE: + return IPPROTO_TCP; + default: + return 0; + } + /* NOTREACHED */ +} + +/* + * map IPPROTO_* to SADB_SATYPE_* + * OUT: + * 0: invalid protocol type. + */ +static u_int8_t +key_proto2satype(u_int16_t proto) +{ + switch (proto) { + case IPPROTO_AH: + return SADB_SATYPE_AH; + case IPPROTO_ESP: + return SADB_SATYPE_ESP; + case IPPROTO_IPCOMP: + return SADB_X_SATYPE_IPCOMP; + case IPPROTO_TCP: + return SADB_X_SATYPE_TCPSIGNATURE; + default: + return 0; + } + /* NOTREACHED */ +} + +/* %%% PF_KEY */ +/* + * SADB_GETSPI processing is to receive + * <base, (SA2), src address, dst address, (SPI range)> + * from the IKMPd, to assign a unique spi value, to hang on the INBOUND + * tree with the status of LARVAL, and send + * <base, SA(*), address(SD)> + * to the IKMPd. + * + * IN: mhp: pointer to the pointer to each header. + * OUT: NULL if fail. + * 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; +{ + 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; + int error; + + 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 (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 (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 { + mode = IPSEC_MODE_ANY; + reqid = 0; + } + + src0 = (struct sadb_address *)(mhp->ext[SADB_EXT_ADDRESS_SRC]); + dst0 = (struct sadb_address *)(mhp->ext[SADB_EXT_ADDRESS_DST]); + + /* map satype to proto */ + 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: + ; /*???*/ + } + 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: + ; /*???*/ + } + + /* 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); + } + } + + /* 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); + } + + /* 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; + } + } + + { + struct mbuf *n, *nn; + struct sadb_sa *m_sa; + struct sadb_msg *newmsg; + int off, len; + + /* create new sadb_msg to reply. */ + len = PFKEY_ALIGN8(sizeof(struct sadb_msg)) + + PFKEY_ALIGN8(sizeof(struct sadb_sa)); + + MGETHDR(n, M_DONTWAIT, MT_DATA); + if (len > MHLEN) { + MCLGET(n, M_DONTWAIT); + if ((n->m_flags & M_EXT) == 0) { + m_freem(n); + n = NULL; + } + } + if (!n) + return key_senderror(so, m, ENOBUFS); + + n->m_len = len; + n->m_next = NULL; + off = 0; + + m_copydata(m, 0, sizeof(struct sadb_msg), mtod(n, caddr_t) + off); + off += PFKEY_ALIGN8(sizeof(struct sadb_msg)); + + 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); + off += PFKEY_ALIGN8(sizeof(struct sadb_sa)); + + IPSEC_ASSERT(off == len, + ("length inconsistency (off %u len %u)", off, len)); + + n->m_next = key_gather_mbuf(m, mhp, 0, 2, SADB_EXT_ADDRESS_SRC, + SADB_EXT_ADDRESS_DST); + if (!n->m_next) { + m_freem(n); + return key_senderror(so, m, ENOBUFS); + } + + if (n->m_len < sizeof(struct sadb_msg)) { + n = m_pullup(n, sizeof(struct sadb_msg)); + if (n == NULL) + return key_sendup_mbuf(so, m, KEY_SENDUP_ONE); + } + + n->m_pkthdr.len = 0; + for (nn = n; nn; nn = nn->m_next) + n->m_pkthdr.len += nn->m_len; + + newmsg = mtod(n, struct sadb_msg *); + newmsg->sadb_msg_seq = newsav->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); + } +} + +/* + * allocating new SPI + * called by key_getspi(). + * OUT: + * 0: failure. + * others: success. + */ +static u_int32_t +key_do_getnewspi(spirange, saidx) + struct sadb_spirange *spirange; + struct secasindex *saidx; +{ + u_int32_t newspi; + u_int32_t min, max; + int count = V_key_spi_trycnt; + + /* set spi range to allocate */ + if (spirange != NULL) { + min = spirange->sadb_spirange_min; + max = spirange->sadb_spirange_max; + } else { + min = V_key_spi_minval; + max = V_key_spi_maxval; + } + /* IPCOMP needs 2-byte SPI */ + if (saidx->proto == IPPROTO_IPCOMP) { + u_int32_t t; + if (min >= 0x10000) + min = 0xffff; + if (max >= 0x10000) + max = 0xffff; + if (min > max) { + t = min; min = max; max = t; + } + } + + if (min == max) { + if (key_checkspidup(saidx, min) != NULL) { + ipseclog((LOG_DEBUG, "%s: SPI %u exists already.\n", + __func__, min)); + return 0; + } + + count--; /* taking one cost. */ + newspi = min; + + } else { + + /* init SPI */ + newspi = 0; + + /* when requesting to allocate spi ranged */ + while (count--) { + /* generate pseudo-random SPI value ranged. */ + newspi = min + (key_random() % (max - min + 1)); + + if (key_checkspidup(saidx, newspi) == NULL) + break; + } + + if (count == 0 || newspi == 0) { + ipseclog((LOG_DEBUG, "%s: to allocate spi is failed.\n", + __func__)); + return 0; + } + } + + /* statistics */ + keystat.getspi_count = + (keystat.getspi_count + V_key_spi_trycnt - count) / 2; + + return newspi; +} + +/* + * SADB_UPDATE processing + * receive + * <base, SA, (SA2), (lifetime(HSC),) address(SD), (address(P),) + * key(AE), (identity(SD),) (sensitivity)> + * from the ikmpd, and update a secasvar entry whose status is SADB_SASTATE_LARVAL. + * and send + * <base, SA, (SA2), (lifetime(HSC),) address(SD), (address(P),) + * (identity(SD),) (sensitivity)> + * to the ikmpd. + * + * m will always be freed. + */ +static int +key_update(so, m, mhp) + 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 secasvar *sav; + u_int16_t proto; + u_int8_t mode; + u_int32_t reqid; + int error; + + 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")); + + /* map satype to proto */ + 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); + } + + 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__)); + 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__)); + 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 { + mode = IPSEC_MODE_ANY; + reqid = 0; + } + /* 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. + */ + 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; + } +#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) + 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(); + 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)); + 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 + 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)); + 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); + } + + { + struct mbuf *n; + + /* set msg buf from mhp */ + n = key_getmsgbuf_x1(m, mhp); + if (n == NULL) { + ipseclog((LOG_DEBUG, "%s: No more memory.\n", __func__)); + return key_senderror(so, m, ENOBUFS); + } + + m_freem(m); + return key_sendup_mbuf(so, n, KEY_SENDUP_ALL); + } +} + +/* + * 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),) + * key(AE), (identity(SD),) (sensitivity)> + * from the ikmpd, + * and send + * <base, SA, (SA2), (lifetime(HSC),) address(SD), (address(P),) + * (identity(SD),) (sensitivity)> + * to the ikmpd. + * + * IGNORE identity and sensitivity messages. + * + * m will always be freed. + */ +static int +key_add(so, m, mhp) + 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; + int error; + + 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")); + + /* map satype to proto */ + 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); + } + + 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__)); + 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__)); + 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 { + mode = IPSEC_MODE_ANY; + reqid = 0; + } + + 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. + */ + 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 (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; + } +#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) { + 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); + return key_senderror(so, m, error); + } + + /* + * don't call key_freesav() here, as we would like to keep the SA + * in the database on success. + */ + + { + struct mbuf *n; + + /* set msg buf from mhp */ + n = key_getmsgbuf_x1(m, mhp); + if (n == NULL) { + ipseclog((LOG_DEBUG, "%s: No more memory.\n", __func__)); + return key_senderror(so, m, ENOBUFS); + } + + m_freem(m); + return key_sendup_mbuf(so, n, KEY_SENDUP_ALL); + } +} + +/* m is retained */ +static int +key_setident(sah, m, mhp) + struct secashead *sah; + struct mbuf *m; + 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) { + sah->idents = NULL; + sah->identd = NULL; + return 0; + } + + if (mhp->ext[SADB_EXT_IDENTITY_SRC] == NULL || + mhp->ext[SADB_EXT_IDENTITY_DST] == NULL) { + ipseclog((LOG_DEBUG, "%s: invalid identity.\n", __func__)); + return EINVAL; + } + + idsrc = (const struct sadb_ident *)mhp->ext[SADB_EXT_IDENTITY_SRC]; + iddst = (const struct sadb_ident *)mhp->ext[SADB_EXT_IDENTITY_DST]; + idsrclen = mhp->extlen[SADB_EXT_IDENTITY_SRC]; + iddstlen = mhp->extlen[SADB_EXT_IDENTITY_DST]; + + /* validity check */ + if (idsrc->sadb_ident_type != iddst->sadb_ident_type) { + ipseclog((LOG_DEBUG, "%s: ident type mismatch.\n", __func__)); + return EINVAL; + } + + switch (idsrc->sadb_ident_type) { + case SADB_IDENTTYPE_PREFIX: + case SADB_IDENTTYPE_FQDN: + case SADB_IDENTTYPE_USERFQDN: + default: + /* XXX do nothing */ + sah->idents = NULL; + sah->identd = NULL; + return 0; + } + + /* make structure */ + sah->idents = malloc(sizeof(struct secident), M_IPSEC_MISC, M_NOWAIT); + if (sah->idents == NULL) { + ipseclog((LOG_DEBUG, "%s: No more memory.\n", __func__)); + return ENOBUFS; + } + sah->identd = malloc(sizeof(struct secident), M_IPSEC_MISC, M_NOWAIT); + if (sah->identd == NULL) { + free(sah->idents, M_IPSEC_MISC); + sah->idents = NULL; + ipseclog((LOG_DEBUG, "%s: No more memory.\n", __func__)); + return ENOBUFS; + } + sah->idents->type = idsrc->sadb_ident_type; + sah->idents->id = idsrc->sadb_ident_id; + + sah->identd->type = iddst->sadb_ident_type; + sah->identd->id = iddst->sadb_ident_id; + + return 0; +} + +/* + * m will not be freed on return. + * it is caller's responsibility to free the result. + */ +static struct mbuf * +key_getmsgbuf_x1(m, mhp) + struct mbuf *m; + const struct sadb_msghdr *mhp; +{ + struct mbuf *n; + + IPSEC_ASSERT(m != NULL, ("null mbuf")); + IPSEC_ASSERT(mhp != NULL, ("null msghdr")); + IPSEC_ASSERT(mhp->msg != NULL, ("null msg")); + + /* create new sadb_msg to reply. */ + n = key_gather_mbuf(m, mhp, 1, 9, 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); + if (!n) + return NULL; + + if (n->m_len < sizeof(struct sadb_msg)) { + n = m_pullup(n, sizeof(struct sadb_msg)); + if (n == NULL) + return NULL; + } + mtod(n, struct sadb_msg *)->sadb_msg_errno = 0; + mtod(n, struct sadb_msg *)->sadb_msg_len = + PFKEY_UNIT64(n->m_pkthdr.len); + + return n; +} + +static int key_delete_all __P((struct socket *, struct mbuf *, + const struct sadb_msghdr *, u_int16_t)); + +/* + * SADB_DELETE processing + * receive + * <base, SA(*), address(SD)> + * from the ikmpd, and set SADB_SASTATE_DEAD, + * and send, + * <base, SA(*), address(SD)> + * to the ikmpd. + * + * m will always be freed. + */ +static int +key_delete(so, m, mhp) + 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; + + 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")); + + /* map satype to proto */ + 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); + } + + 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 (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 (mhp->ext[SADB_EXT_SA] == NULL) { + /* + * Caller wants us to delete all non-LARVAL SAs + * that match the src/dst. This is used during + * IKE INITIAL-CONTACT. + */ + 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); + } + + 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); + } + + key_sa_chgstate(sav, SADB_SASTATE_DEAD); + SAHTREE_UNLOCK(); + 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) + return key_senderror(so, m, ENOBUFS); + + if (n->m_len < sizeof(struct sadb_msg)) { + n = m_pullup(n, sizeof(struct sadb_msg)); + if (n == NULL) + return key_senderror(so, m, ENOBUFS); + } + newmsg = mtod(n, struct sadb_msg *); + 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_ALL); + } +} + +/* + * 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) +{ + struct sadb_address *src0, *dst0; + struct secasindex saidx; + 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) + 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); + } + } + } + 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) + return key_senderror(so, m, ENOBUFS); + + if (n->m_len < sizeof(struct sadb_msg)) { + n = m_pullup(n, sizeof(struct sadb_msg)); + if (n == NULL) + return key_senderror(so, m, ENOBUFS); + } + newmsg = mtod(n, struct sadb_msg *); + 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_ALL); + } +} + +/* + * SADB_GET processing + * receive + * <base, SA(*), address(SD)> + * from the ikmpd, and get a SP and a SA to respond, + * and send, + * <base, SA, (lifetime(HSC),) address(SD), (address(P),) key(AE), + * (identity(SD),) (sensitivity)> + * to the ikmpd. + * + * m will always be freed. + */ +static int +key_get(so, m, mhp) + 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; + + 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")); + + /* map satype to proto */ + 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); + } + + 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__)); + 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__)); + return key_senderror(so, m, EINVAL); + } + + 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; + } + SAHTREE_UNLOCK(); + if (sah == NULL) { + ipseclog((LOG_DEBUG, "%s: no SA found.\n", __func__)); + return key_senderror(so, m, ENOENT); + } + + { + struct mbuf *n; + u_int8_t satype; + + /* map proto to satype */ + if ((satype = key_proto2satype(sah->saidx.proto)) == 0) { + ipseclog((LOG_DEBUG, "%s: there was invalid proto in SAD.\n", + __func__)); + 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); + if (!n) + return key_senderror(so, m, ENOBUFS); + + m_freem(m); + return key_sendup_mbuf(so, n, KEY_SENDUP_ONE); + } +} + +/* XXX make it sysctl-configurable? */ +static void +key_getcomb_setlifetime(comb) + struct sadb_comb *comb; +{ + + comb->sadb_comb_soft_allocations = 1; + comb->sadb_comb_hard_allocations = 1; + comb->sadb_comb_soft_bytes = 0; + comb->sadb_comb_hard_bytes = 0; + comb->sadb_comb_hard_addtime = 86400; /* 1 day */ + comb->sadb_comb_soft_addtime = comb->sadb_comb_soft_addtime * 80 / 100; + comb->sadb_comb_soft_usetime = 28800; /* 8 hours */ + comb->sadb_comb_hard_usetime = comb->sadb_comb_hard_usetime * 80 / 100; +} + +/* + * XXX reorder combinations by preference + * XXX no idea if the user wants ESP authentication or not + */ +static struct mbuf * +key_getcomb_esp() +{ + struct sadb_comb *comb; + struct enc_xform *algo; + struct mbuf *result = NULL, *m, *n; + int encmin; + int i, off, o; + int totlen; + const int l = PFKEY_ALIGN8(sizeof(struct sadb_comb)); + + m = NULL; + for (i = 1; i <= SADB_EALG_MAX; i++) { + algo = esp_algorithm_lookup(i); + if (algo == NULL) + continue; + + /* discard algorithms with key size smaller than system min */ + if (_BITS(algo->maxkey) < V_ipsec_esp_keymin) + continue; + if (_BITS(algo->minkey) < V_ipsec_esp_keymin) + encmin = V_ipsec_esp_keymin; + else + encmin = _BITS(algo->minkey); + + if (V_ipsec_esp_auth) + m = key_getcomb_ah(); + else { + IPSEC_ASSERT(l <= MLEN, + ("l=%u > MLEN=%lu", l, (u_long) MLEN)); + MGET(m, M_DONTWAIT, MT_DATA); + if (m) { + M_ALIGN(m, l); + m->m_len = l; + m->m_next = NULL; + bzero(mtod(m, caddr_t), m->m_len); + } + } + if (!m) + goto fail; + + totlen = 0; + for (n = m; n; n = n->m_next) + totlen += n->m_len; + IPSEC_ASSERT((totlen % l) == 0, ("totlen=%u, l=%u", totlen, l)); + + for (off = 0; off < totlen; off += l) { + n = m_pulldown(m, off, l, &o); + if (!n) { + /* m is already freed */ + goto fail; + } + comb = (struct sadb_comb *)(mtod(n, caddr_t) + o); + bzero(comb, sizeof(*comb)); + key_getcomb_setlifetime(comb); + comb->sadb_comb_encrypt = i; + comb->sadb_comb_encrypt_minbits = encmin; + comb->sadb_comb_encrypt_maxbits = _BITS(algo->maxkey); + } + + if (!result) + result = m; + else + m_cat(result, m); + } + + return result; + + fail: + if (result) + m_freem(result); + return NULL; +} + +static void +key_getsizes_ah( + const struct auth_hash *ah, + int alg, + u_int16_t* min, + u_int16_t* max) +{ + + *min = *max = ah->keysize; + if (ah->keysize == 0) { + /* + * Transform takes arbitrary key size but algorithm + * key size is restricted. Enforce this here. + */ + switch (alg) { + case SADB_X_AALG_MD5: *min = *max = 16; break; + case SADB_X_AALG_SHA: *min = *max = 20; break; + case SADB_X_AALG_NULL: *min = 1; *max = 256; break; + default: + DPRINTF(("%s: unknown AH algorithm %u\n", + __func__, alg)); + break; + } + } +} + +/* + * XXX reorder combinations by preference + */ +static struct mbuf * +key_getcomb_ah() +{ + struct sadb_comb *comb; + struct auth_hash *algo; + struct mbuf *m; + u_int16_t minkeysize, maxkeysize; + int i; + const int l = PFKEY_ALIGN8(sizeof(struct sadb_comb)); + + m = NULL; + for (i = 1; i <= SADB_AALG_MAX; i++) { +#if 1 + /* we prefer HMAC algorithms, not old algorithms */ + if (i != SADB_AALG_SHA1HMAC && i != SADB_AALG_MD5HMAC) + continue; +#endif + algo = ah_algorithm_lookup(i); + if (!algo) + continue; + key_getsizes_ah(algo, i, &minkeysize, &maxkeysize); + /* discard algorithms with key size smaller than system min */ + if (_BITS(minkeysize) < V_ipsec_ah_keymin) + continue; + + if (!m) { + IPSEC_ASSERT(l <= MLEN, + ("l=%u > MLEN=%lu", l, (u_long) MLEN)); + MGET(m, M_DONTWAIT, MT_DATA); + if (m) { + M_ALIGN(m, l); + m->m_len = l; + m->m_next = NULL; + } + } else + M_PREPEND(m, l, M_DONTWAIT); + if (!m) + return NULL; + + comb = mtod(m, struct sadb_comb *); + bzero(comb, sizeof(*comb)); + key_getcomb_setlifetime(comb); + comb->sadb_comb_auth = i; + comb->sadb_comb_auth_minbits = _BITS(minkeysize); + comb->sadb_comb_auth_maxbits = _BITS(maxkeysize); + } + + return m; +} + +/* + * not really an official behavior. discussed in pf_key@inner.net in Sep2000. + * XXX reorder combinations by preference + */ +static struct mbuf * +key_getcomb_ipcomp() +{ + 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); + if (!algo) + continue; + + if (!m) { + IPSEC_ASSERT(l <= MLEN, + ("l=%u > MLEN=%lu", l, (u_long) MLEN)); + MGET(m, M_DONTWAIT, MT_DATA); + if (m) { + M_ALIGN(m, l); + m->m_len = l; + m->m_next = NULL; + } + } else + M_PREPEND(m, l, M_DONTWAIT); + if (!m) + return NULL; + + comb = mtod(m, struct sadb_comb *); + bzero(comb, sizeof(*comb)); + key_getcomb_setlifetime(comb); + comb->sadb_comb_encrypt = i; + /* what should we set into sadb_comb_*_{min,max}bits? */ + } + + return m; +} + +/* + * XXX no way to pass mode (transport/tunnel) to userland + * XXX replay checking? + * XXX sysctl interface to ipsec_{ah,esp}_keymin + */ +static struct mbuf * +key_getprop(saidx) + const struct secasindex *saidx; +{ + struct sadb_prop *prop; + struct mbuf *m, *n; + const int l = PFKEY_ALIGN8(sizeof(struct sadb_prop)); + int totlen; + + switch (saidx->proto) { + case IPPROTO_ESP: + m = key_getcomb_esp(); + break; + case IPPROTO_AH: + m = key_getcomb_ah(); + break; + case IPPROTO_IPCOMP: + m = key_getcomb_ipcomp(); + break; + default: + return NULL; + } + + if (!m) + return NULL; + M_PREPEND(m, l, M_DONTWAIT); + if (!m) + return NULL; + + totlen = 0; + for (n = m; n; n = n->m_next) + totlen += n->m_len; + + prop = mtod(m, struct sadb_prop *); + bzero(prop, sizeof(*prop)); + prop->sadb_prop_len = PFKEY_UNIT64(totlen); + prop->sadb_prop_exttype = SADB_EXT_PROPOSAL; + prop->sadb_prop_replay = 32; /* XXX */ + + return m; +} + +/* + * SADB_ACQUIRE processing called by key_checkrequest() and key_acquire2(). + * send + * <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, + * or + * <base, src address, dst address, (SPI range)> with SADB_GETSPI + * from KMD by PF_KEY. + * + * XXX x_policy is outside of RFC2367 (KAME extension). + * XXX sensitivity is not supported. + * XXX for ipcomp, RFC2367 does not define how to fill in proposal. + * see comment for key_getcomb_ipcomp(). + * + * OUT: + * 0 : succeed + * others: error number + */ +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; + + 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; + } + + + seq = newacq->seq; + m = key_setsadbmsg(SADB_ACQUIRE, 0, satype, seq, 0, 0); + if (!m) { + error = ENOBUFS; + goto fail; + } + 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. */ + m = key_setsadbaddr(SADB_EXT_ADDRESS_SRC, + &saidx->src.sa, FULLMASK, IPSEC_ULPROTO_ANY); + if (!m) { + error = ENOBUFS; + goto fail; + } + m_cat(result, m); + + m = key_setsadbaddr(SADB_EXT_ADDRESS_DST, + &saidx->dst.sa, FULLMASK, IPSEC_ULPROTO_ANY); + if (!m) { + error = ENOBUFS; + goto fail; + } + m_cat(result, m); + + /* XXX proxy address (optional) */ + + /* set sadb_x_policy */ + if (sp) { + m = key_setsadbxpolicy(sp->policy, sp->spidx.dir, sp->id); + if (!m) { + error = ENOBUFS; + goto fail; + } + m_cat(result, m); + } + + /* XXX identity (optional) */ +#if 0 + if (idexttype && fqdn) { + /* create identity extension (FQDN) */ + struct sadb_ident *id; + int fqdnlen; + + fqdnlen = strlen(fqdn) + 1; /* +1 for terminating-NUL */ + id = (struct sadb_ident *)p; + bzero(id, sizeof(*id) + PFKEY_ALIGN8(fqdnlen)); + id->sadb_ident_len = PFKEY_UNIT64(sizeof(*id) + PFKEY_ALIGN8(fqdnlen)); + id->sadb_ident_exttype = idexttype; + id->sadb_ident_type = SADB_IDENTTYPE_FQDN; + bcopy(fqdn, id + 1, fqdnlen); + p += sizeof(struct sadb_ident) + PFKEY_ALIGN8(fqdnlen); + } + + if (idexttype) { + /* create identity extension (USERFQDN) */ + struct sadb_ident *id; + int userfqdnlen; + + if (userfqdn) { + /* +1 for terminating-NUL */ + userfqdnlen = strlen(userfqdn) + 1; + } else + userfqdnlen = 0; + id = (struct sadb_ident *)p; + bzero(id, sizeof(*id) + PFKEY_ALIGN8(userfqdnlen)); + id->sadb_ident_len = PFKEY_UNIT64(sizeof(*id) + PFKEY_ALIGN8(userfqdnlen)); + id->sadb_ident_exttype = idexttype; + id->sadb_ident_type = SADB_IDENTTYPE_USERFQDN; + /* XXX is it correct? */ + if (curproc && curproc->p_cred) + id->sadb_ident_id = curproc->p_cred->p_ruid; + if (userfqdn && userfqdnlen) + bcopy(userfqdn, id + 1, userfqdnlen); + p += sizeof(struct sadb_ident) + PFKEY_ALIGN8(userfqdnlen); + } +#endif + + /* XXX sensitivity (optional) */ + + /* create proposal/combination extension */ + m = key_getprop(saidx); +#if 0 + /* + * spec conformant: always attach proposal/combination extension, + * the problem is that we have no way to attach it for ipcomp, + * due to the way sadb_comb is declared in RFC2367. + */ + if (!m) { + error = ENOBUFS; + goto fail; + } + m_cat(result, m); +#else + /* + * outside of spec; make proposal/combination extension optional. + */ + if (m) + m_cat(result, m); +#endif + + if ((result->m_flags & M_PKTHDR) == 0) { + error = EINVAL; + goto fail; + } + + if (result->m_len < sizeof(struct sadb_msg)) { + result = m_pullup(result, sizeof(struct sadb_msg)); + if (result == NULL) { + error = ENOBUFS; + goto fail; + } + } + + 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); + + return key_sendup_mbuf(NULL, result, KEY_SENDUP_REGISTERED); + + fail: + if (result) + m_freem(result); + return error; +} + +static struct secacq * +key_newacq(const struct secasindex *saidx) +{ + struct secacq *newacq; + + /* get new entry */ + newacq = malloc(sizeof(struct secacq), M_IPSEC_SAQ, M_NOWAIT|M_ZERO); + if (newacq == NULL) { + ipseclog((LOG_DEBUG, "%s: No more memory.\n", __func__)); + return NULL; + } + + /* 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; + + /* add to acqtree */ + ACQ_LOCK(); + LIST_INSERT_HEAD(&V_acqtree, newacq, chain); + ACQ_UNLOCK(); + + return newacq; +} + +static struct secacq * +key_getacq(const struct secasindex *saidx) +{ + struct secacq *acq; + + ACQ_LOCK(); + LIST_FOREACH(acq, &V_acqtree, chain) { + if (key_cmpsaidx(saidx, &acq->saidx, CMP_EXACTLY)) + break; + } + ACQ_UNLOCK(); + + return acq; +} + +static struct secacq * +key_getacqbyseq(seq) + u_int32_t seq; +{ + struct secacq *acq; + + ACQ_LOCK(); + LIST_FOREACH(acq, &V_acqtree, chain) { + if (acq->seq == seq) + break; + } + ACQ_UNLOCK(); + + return acq; +} + +static struct secspacq * +key_newspacq(spidx) + struct secpolicyindex *spidx; +{ + struct secspacq *acq; + + /* get new entry */ + acq = malloc(sizeof(struct secspacq), M_IPSEC_SAQ, M_NOWAIT|M_ZERO); + if (acq == NULL) { + ipseclog((LOG_DEBUG, "%s: No more memory.\n", __func__)); + return NULL; + } + + /* copy secindex */ + bcopy(spidx, &acq->spidx, sizeof(acq->spidx)); + acq->created = time_second; + acq->count = 0; + + /* add to spacqtree */ + SPACQ_LOCK(); + LIST_INSERT_HEAD(&V_spacqtree, acq, chain); + SPACQ_UNLOCK(); + + return acq; +} + +static struct secspacq * +key_getspacq(spidx) + struct secpolicyindex *spidx; +{ + struct secspacq *acq; + + SPACQ_LOCK(); + LIST_FOREACH(acq, &V_spacqtree, chain) { + if (key_cmpspidx_exactly(spidx, &acq->spidx)) { + /* NB: return holding spacq_lock */ + return acq; + } + } + SPACQ_UNLOCK(); + + return NULL; +} + +/* + * SADB_ACQUIRE processing, + * in first situation, is receiving + * <base> + * from the ikmpd, and clear sequence of its secasvar entry. + * + * In second situation, is receiving + * <base, address(SD), (address(P),) (identity(SD),) (sensitivity,) proposal> + * from a user land process, and return + * <base, address(SD), (address(P),) (identity(SD),) (sensitivity,) proposal> + * to the socket. + * + * m will always be freed. + */ +static int +key_acquire2(so, m, mhp) + struct socket *so; + struct mbuf *m; + const struct sadb_msghdr *mhp; +{ + const struct sadb_address *src0, *dst0; + struct secasindex saidx; + struct secashead *sah; + u_int16_t proto; + int error; + + 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")); + + /* + * Error message from KMd. + * We assume that if error was occured 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. + */ + 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) { + 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) { + /* + * the specified larval SA is already gone, or we got + * a bogus sequence number. we can silently ignore it. + */ + m_freem(m); + return 0; + } + + /* reset acq counter in order to deletion by timehander. */ + acq->created = time_second; + acq->count = 0; + m_freem(m); + return 0; + } + + /* + * This message is from user land. + */ + + /* map satype to proto */ + 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); + } + + 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__)); + 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__)); + 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", + __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 index */ + SAHTREE_LOCK(); + LIST_FOREACH(sah, &V_sahtree, chain) { + if (sah->state == SADB_SASTATE_DEAD) + continue; + if (key_cmpsaidx(&sah->saidx, &saidx, CMP_MODE_REQID)) + break; + } + SAHTREE_UNLOCK(); + if (sah != NULL) { + ipseclog((LOG_DEBUG, "%s: a SA exists already.\n", __func__)); + return key_senderror(so, m, EEXIST); + } + + 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)); + return key_senderror(so, m, error); + } + + return key_sendup_mbuf(so, m, KEY_SENDUP_REGISTERED); +} + +/* + * SADB_REGISTER processing. + * If SATYPE_UNSPEC has been passed as satype, only return sabd_supported. + * receive + * <base> + * from the ikmpd, and register a socket to send PF_KEY messages, + * and send + * <base, supported> + * to KMD by PF_KEY. + * If socket is detached, must free from regnode. + * + * m will always be freed. + */ +static int +key_register(so, m, mhp) + struct socket *so; + struct mbuf *m; + const struct sadb_msghdr *mhp; +{ + struct secreg *reg, *newreg = 0; + + 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")); + + /* check for invalid register message */ + if (mhp->msg->sadb_msg_satype >= sizeof(V_regtree)/sizeof(V_regtree[0])) + return key_senderror(so, m, EINVAL); + + /* When SATYPE_UNSPEC is specified, only return sabd_supported. */ + if (mhp->msg->sadb_msg_satype == SADB_SATYPE_UNSPEC) + goto setmsg; + + /* check whether existing or not */ + REGTREE_LOCK(); + LIST_FOREACH(reg, &V_regtree[mhp->msg->sadb_msg_satype], chain) { + if (reg->so == so) { + REGTREE_UNLOCK(); + ipseclog((LOG_DEBUG, "%s: socket exists already.\n", + __func__)); + return key_senderror(so, m, EEXIST); + } + } + + /* create regnode */ + newreg = malloc(sizeof(struct secreg), M_IPSEC_SAR, M_NOWAIT|M_ZERO); + if (newreg == NULL) { + REGTREE_UNLOCK(); + ipseclog((LOG_DEBUG, "%s: No more memory.\n", __func__)); + return key_senderror(so, m, ENOBUFS); + } + + newreg->so = so; + ((struct keycb *)sotorawcb(so))->kp_registered++; + + /* add regnode to regtree. */ + LIST_INSERT_HEAD(&V_regtree[mhp->msg->sadb_msg_satype], newreg, chain); + REGTREE_UNLOCK(); + + setmsg: + { + struct mbuf *n; + struct sadb_msg *newmsg; + struct sadb_supported *sup; + u_int len, alen, elen; + int off; + int i; + struct sadb_alg *alg; + + /* create new sadb_msg to reply. */ + alen = 0; + for (i = 1; i <= SADB_AALG_MAX; i++) { + if (ah_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)) + elen += sizeof(struct sadb_alg); + } + if (elen) + elen += sizeof(struct sadb_supported); + + len = sizeof(struct sadb_msg) + alen + elen; + + if (len > MCLBYTES) + return key_senderror(so, m, ENOBUFS); + + MGETHDR(n, M_DONTWAIT, MT_DATA); + if (len > MHLEN) { + MCLGET(n, M_DONTWAIT); + if ((n->m_flags & M_EXT) == 0) { + m_freem(n); + n = NULL; + } + } + if (!n) + return key_senderror(so, m, ENOBUFS); + + n->m_pkthdr.len = n->m_len = len; + n->m_next = NULL; + off = 0; + + m_copydata(m, 0, sizeof(struct sadb_msg), mtod(n, caddr_t) + off); + newmsg = mtod(n, struct sadb_msg *); + newmsg->sadb_msg_errno = 0; + newmsg->sadb_msg_len = PFKEY_UNIT64(len); + off += PFKEY_ALIGN8(sizeof(struct sadb_msg)); + + /* for authentication algorithm */ + if (alen) { + sup = (struct sadb_supported *)(mtod(n, caddr_t) + off); + sup->sadb_supported_len = PFKEY_UNIT64(alen); + sup->sadb_supported_exttype = SADB_EXT_SUPPORTED_AUTH; + off += PFKEY_ALIGN8(sizeof(*sup)); + + for (i = 1; i <= SADB_AALG_MAX; i++) { + struct auth_hash *aalgo; + u_int16_t minkeysize, maxkeysize; + + aalgo = ah_algorithm_lookup(i); + if (!aalgo) + continue; + alg = (struct sadb_alg *)(mtod(n, caddr_t) + off); + alg->sadb_alg_id = i; + alg->sadb_alg_ivlen = 0; + key_getsizes_ah(aalgo, i, &minkeysize, &maxkeysize); + alg->sadb_alg_minbits = _BITS(minkeysize); + alg->sadb_alg_maxbits = _BITS(maxkeysize); + off += PFKEY_ALIGN8(sizeof(*alg)); + } + } + + /* for encryption algorithm */ + if (elen) { + sup = (struct sadb_supported *)(mtod(n, caddr_t) + off); + sup->sadb_supported_len = PFKEY_UNIT64(elen); + sup->sadb_supported_exttype = SADB_EXT_SUPPORTED_ENCRYPT; + off += PFKEY_ALIGN8(sizeof(*sup)); + + for (i = 1; i <= SADB_EALG_MAX; i++) { + struct enc_xform *ealgo; + + ealgo = esp_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_minbits = _BITS(ealgo->minkey); + alg->sadb_alg_maxbits = _BITS(ealgo->maxkey); + off += PFKEY_ALIGN8(sizeof(struct sadb_alg)); + } + } + + IPSEC_ASSERT(off == len, + ("length assumption failed (off %u len %u)", off, len)); + + m_freem(m); + return key_sendup_mbuf(so, n, KEY_SENDUP_REGISTERED); + } +} + +/* + * free secreg entry registered. + * XXX: I want to do free a socket marked done SADB_RESIGER to socket. + */ +void +key_freereg(struct socket *so) +{ + struct secreg *reg; + int i; + + IPSEC_ASSERT(so != NULL, ("NULL so")); + + /* + * check whether existing or not. + * check all type of SA, because there is a potential that + * one socket is registered to multiple type of SA. + */ + REGTREE_LOCK(); + for (i = 0; i <= SADB_SATYPE_MAX; i++) { + LIST_FOREACH(reg, &V_regtree[i], chain) { + if (reg->so == so && __LIST_CHAINED(reg)) { + LIST_REMOVE(reg, chain); + free(reg, M_IPSEC_SAR); + break; + } + } + } + REGTREE_UNLOCK(); +} + +/* + * SADB_EXPIRE processing + * send + * <base, SA, SA2, lifetime(C and one of HS), address(SD)> + * to KMD by PF_KEY. + * NOTE: We send only soft lifetime extension. + * + * OUT: 0 : succeed + * others : error number + */ +static int +key_expire(struct secasvar *sav) +{ + 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()*/ + + IPSEC_ASSERT (sav != NULL, ("null sav")); + IPSEC_ASSERT (sav->sah != NULL, ("null sa header")); + + /* set msg header */ + satype = key_proto2satype(sav->sah->saidx.proto); + IPSEC_ASSERT(satype != 0, ("invalid proto, satype %u", satype)); + m = key_setsadbmsg(SADB_EXPIRE, 0, satype, sav->seq, 0, sav->refcnt); + if (!m) { + error = ENOBUFS; + goto fail; + } + result = m; + + /* create SA extension */ + m = key_setsadbsa(sav); + if (!m) { + error = ENOBUFS; + goto fail; + } + m_cat(result, m); + + /* create SA extension */ + m = key_setsadbxsa2(sav->sah->saidx.mode, + sav->replay ? sav->replay->count : 0, + sav->sah->saidx.reqid); + 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); + error = ENOBUFS; + goto fail; + } + 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 = (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; + m_cat(result, m); + + /* set sadb_address for source */ + m = key_setsadbaddr(SADB_EXT_ADDRESS_SRC, + &sav->sah->saidx.src.sa, + FULLMASK, IPSEC_ULPROTO_ANY); + if (!m) { + error = ENOBUFS; + goto fail; + } + m_cat(result, m); + + /* set sadb_address for destination */ + m = key_setsadbaddr(SADB_EXT_ADDRESS_DST, + &sav->sah->saidx.dst.sa, + FULLMASK, IPSEC_ULPROTO_ANY); + if (!m) { + error = ENOBUFS; + goto fail; + } + m_cat(result, m); + + /* + * XXX-BZ Handle NAT-T extensions here. + */ + + if ((result->m_flags & M_PKTHDR) == 0) { + error = EINVAL; + goto fail; + } + + if (result->m_len < sizeof(struct sadb_msg)) { + result = m_pullup(result, sizeof(struct sadb_msg)); + if (result == NULL) { + error = ENOBUFS; + goto fail; + } + } + + 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); + + splx(s); + return key_sendup_mbuf(NULL, result, KEY_SENDUP_REGISTERED); + + fail: + if (result) + m_freem(result); + splx(s); + return error; +} + +/* + * SADB_FLUSH processing + * receive + * <base> + * from the ikmpd, and free all entries in secastree. + * and send, + * <base> + * to the ikmpd. + * NOTE: to do is only marking SADB_SASTATE_DEAD. + * + * m will always be freed. + */ +static int +key_flush(so, m, mhp) + struct socket *so; + struct mbuf *m; + const struct sadb_msghdr *mhp; +{ + struct sadb_msg *newmsg; + struct secashead *sah, *nextsah; + struct secasvar *sav, *nextsav; + u_int16_t proto; + u_int8_t state; + u_int stateidx; + + IPSEC_ASSERT(so != NULL, ("null socket")); + IPSEC_ASSERT(mhp != NULL, ("null msghdr")); + IPSEC_ASSERT(mhp->msg != NULL, ("null msg")); + + /* map satype to proto */ + 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); + } + + /* 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); + } + } + + sah->state = SADB_SASTATE_DEAD; + } + SAHTREE_UNLOCK(); + + 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__)); + return key_senderror(so, m, ENOBUFS); + } + + if (m->m_next) + m_freem(m->m_next); + m->m_next = NULL; + m->m_pkthdr.len = m->m_len = sizeof(struct sadb_msg); + newmsg = mtod(m, struct sadb_msg *); + newmsg->sadb_msg_errno = 0; + newmsg->sadb_msg_len = PFKEY_UNIT64(m->m_pkthdr.len); + + return key_sendup_mbuf(so, m, KEY_SENDUP_ALL); +} + +/* + * SADB_DUMP processing + * dump all entries including status of DEAD in SAD. + * receive + * <base> + * from the ikmpd, and dump all secasvar leaves + * and send, + * <base> ..... + * to the ikmpd. + * + * m will always be freed. + */ +static int +key_dump(so, m, mhp) + struct socket *so; + struct mbuf *m; + const struct sadb_msghdr *mhp; +{ + 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; + + 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")); + + /* map satype to proto */ + 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); + } + + /* 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) + continue; + + for (stateidx = 0; + stateidx < _ARRAYLEN(saorder_state_any); + stateidx++) { + state = saorder_state_any[stateidx]; + LIST_FOREACH(sav, &sah->savtree[state], chain) { + cnt++; + } + } + } + + if (cnt == 0) { + SAHTREE_UNLOCK(); + 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) + continue; + + /* map proto to satype */ + if ((satype = key_proto2satype(sah->saidx.proto)) == 0) { + SAHTREE_UNLOCK(); + ipseclog((LOG_DEBUG, "%s: there was invalid proto in " + "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); + } + } + } + SAHTREE_UNLOCK(); + + m_freem(m); + 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; +{ + int olen; + + 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")); + + olen = PFKEY_UNUNIT64(mhp->msg->sadb_msg_len); + + if (olen < sizeof(struct sadb_msg)) { +#if 1 + return key_senderror(so, m, EINVAL); +#else + m_freem(m); + return 0; +#endif + } else if (olen == sizeof(struct sadb_msg)) { + /* enable/disable promisc mode */ + struct keycb *kp; + + if ((kp = (struct keycb *)sotorawcb(so)) == NULL) + return key_senderror(so, m, EINVAL); + mhp->msg->sadb_msg_errno = 0; + switch (mhp->msg->sadb_msg_satype) { + case 0: + case 1: + kp->kp_promisc = mhp->msg->sadb_msg_satype; + break; + default: + return key_senderror(so, m, EINVAL); + } + + /* send the original message back to everyone */ + mhp->msg->sadb_msg_errno = 0; + return key_sendup_mbuf(so, m, KEY_SENDUP_ALL); + } else { + /* send packet as is */ + + m_adj(m, PFKEY_ALIGN8(sizeof(struct sadb_msg))); + + /* TODO: if sadb_msg_seq is specified, send to specific pid */ + return key_sendup_mbuf(so, m, KEY_SENDUP_ALL); + } +} + +static int (*key_typesw[]) __P((struct socket *, struct mbuf *, + const struct sadb_msghdr *)) = { + NULL, /* SADB_RESERVED */ + key_getspi, /* SADB_GETSPI */ + key_update, /* SADB_UPDATE */ + key_add, /* SADB_ADD */ + key_delete, /* SADB_DELETE */ + key_get, /* SADB_GET */ + key_acquire2, /* SADB_ACQUIRE */ + key_register, /* SADB_REGISTER */ + NULL, /* SADB_EXPIRE */ + key_flush, /* SADB_FLUSH */ + key_dump, /* SADB_DUMP */ + key_promisc, /* SADB_X_PROMISC */ + NULL, /* SADB_X_PCHANGE */ + key_spdadd, /* SADB_X_SPDUPDATE */ + key_spdadd, /* SADB_X_SPDADD */ + key_spddelete, /* SADB_X_SPDDELETE */ + key_spdget, /* SADB_X_SPDGET */ + NULL, /* SADB_X_SPDACQUIRE */ + key_spddump, /* SADB_X_SPDDUMP */ + key_spdflush, /* SADB_X_SPDFLUSH */ + key_spdadd, /* SADB_X_SPDSETIDX */ + NULL, /* SADB_X_SPDEXPIRE */ + key_spddelete2, /* SADB_X_SPDDELETE2 */ +}; + +/* + * parse sadb_msg buffer to process PFKEYv2, + * and create a data to response if needed. + * I think to be dealed with mbuf directly. + * IN: + * msgp : pointer to pointer to a received buffer pulluped. + * This is rewrited to response. + * so : pointer to socket. + * OUT: + * length for buffer to send to user process. + */ +int +key_parse(m, so) + struct mbuf *m; + struct socket *so; +{ + struct sadb_msg *msg; + struct sadb_msghdr mh; + u_int orglen; + int error; + int target; + + 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) + return ENOBUFS; + } + msg = mtod(m, struct sadb_msg *); + 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) { + ipseclog((LOG_DEBUG, "%s: invalid message length.\n",__func__)); + V_pfkeystat.out_invlen++; + error = EINVAL; + goto senderror; + } + + if (msg->sadb_msg_version != PF_KEY_V2) { + ipseclog((LOG_DEBUG, "%s: PF_KEY version %u is mismatched.\n", + __func__, msg->sadb_msg_version)); + V_pfkeystat.out_invver++; + error = EINVAL; + goto senderror; + } + + if (msg->sadb_msg_type > SADB_MAX) { + ipseclog((LOG_DEBUG, "%s: invalid type %u is passed.\n", + __func__, msg->sadb_msg_type)); + V_pfkeystat.out_invmsgtype++; + error = EINVAL; + goto senderror; + } + + /* for old-fashioned code - should be nuked */ + if (m->m_pkthdr.len > MCLBYTES) { + m_freem(m); + return ENOBUFS; + } + if (m->m_next) { + struct mbuf *n; + + MGETHDR(n, M_DONTWAIT, MT_DATA); + if (n && m->m_pkthdr.len > MHLEN) { + MCLGET(n, M_DONTWAIT); + if ((n->m_flags & M_EXT) == 0) { + m_free(n); + n = NULL; + } + } + if (!n) { + m_freem(m); + return ENOBUFS; + } + m_copydata(m, 0, m->m_pkthdr.len, mtod(n, caddr_t)); + n->m_pkthdr.len = n->m_len = m->m_pkthdr.len; + n->m_next = NULL; + m_freem(m); + m = n; + } + + /* align the mbuf chain so that extensions are in contiguous region. */ + error = key_align(m, &mh); + if (error) + return error; + + 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)); + V_pfkeystat.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)); + V_pfkeystat.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)); + V_pfkeystat.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)); + V_pfkeystat.out_invsatype++; + error = EINVAL; + goto senderror; + } + + /* check field of upper layer protocol and address family */ + if (mh.ext[SADB_EXT_ADDRESS_SRC] != NULL + && mh.ext[SADB_EXT_ADDRESS_DST] != NULL) { + struct sadb_address *src0, *dst0; + u_int plen; + + src0 = (struct sadb_address *)(mh.ext[SADB_EXT_ADDRESS_SRC]); + dst0 = (struct sadb_address *)(mh.ext[SADB_EXT_ADDRESS_DST]); + + /* check upper layer protocol */ + if (src0->sadb_address_proto != dst0->sadb_address_proto) { + ipseclog((LOG_DEBUG, "%s: upper layer protocol " + "mismatched.\n", __func__)); + V_pfkeystat.out_invaddr++; + error = EINVAL; + goto senderror; + } + + /* check family */ + if (PFKEY_ADDR_SADDR(src0)->sa_family != + PFKEY_ADDR_SADDR(dst0)->sa_family) { + ipseclog((LOG_DEBUG, "%s: address family mismatched.\n", + __func__)); + V_pfkeystat.out_invaddr++; + error = EINVAL; + goto senderror; + } + if (PFKEY_ADDR_SADDR(src0)->sa_len != + PFKEY_ADDR_SADDR(dst0)->sa_len) { + ipseclog((LOG_DEBUG, "%s: address struct size " + "mismatched.\n", __func__)); + V_pfkeystat.out_invaddr++; + error = EINVAL; + goto senderror; + } + + switch (PFKEY_ADDR_SADDR(src0)->sa_family) { + case AF_INET: + if (PFKEY_ADDR_SADDR(src0)->sa_len != + sizeof(struct sockaddr_in)) { + V_pfkeystat.out_invaddr++; + error = EINVAL; + goto senderror; + } + break; + case AF_INET6: + if (PFKEY_ADDR_SADDR(src0)->sa_len != + sizeof(struct sockaddr_in6)) { + V_pfkeystat.out_invaddr++; + error = EINVAL; + goto senderror; + } + break; + default: + ipseclog((LOG_DEBUG, "%s: unsupported address family\n", + __func__)); + V_pfkeystat.out_invaddr++; + error = EAFNOSUPPORT; + goto senderror; + } + + switch (PFKEY_ADDR_SADDR(src0)->sa_family) { + case AF_INET: + plen = sizeof(struct in_addr) << 3; + break; + case AF_INET6: + plen = sizeof(struct in6_addr) << 3; + break; + default: + plen = 0; /*fool gcc*/ + break; + } + + /* check max prefix length */ + if (src0->sadb_address_prefixlen > plen || + dst0->sadb_address_prefixlen > plen) { + ipseclog((LOG_DEBUG, "%s: illegal prefixlen.\n", + __func__)); + V_pfkeystat.out_invaddr++; + error = EINVAL; + goto senderror; + } + + /* + * prefixlen == 0 is valid because there can be a case when + * all addresses are matched. + */ + } + + if (msg->sadb_msg_type >= sizeof(key_typesw)/sizeof(key_typesw[0]) || + key_typesw[msg->sadb_msg_type] == NULL) { + V_pfkeystat.out_invmsgtype++; + error = EINVAL; + goto senderror; + } + + return (*key_typesw[msg->sadb_msg_type])(so, m, &mh); + +senderror: + msg->sadb_msg_errno = error; + return key_sendup_mbuf(so, m, target); +} + +static int +key_senderror(so, m, code) + struct socket *so; + struct mbuf *m; + int code; +{ + struct sadb_msg *msg; + + IPSEC_ASSERT(m->m_len >= sizeof(struct sadb_msg), + ("mbuf too small, len %u", m->m_len)); + + msg = mtod(m, struct sadb_msg *); + msg->sadb_msg_errno = code; + return key_sendup_mbuf(so, m, KEY_SENDUP_ONE); +} + +/* + * set the pointer to each header into message buffer. + * m will be freed on error. + * XXX larger-than-MCLBYTES extension? + */ +static int +key_align(m, mhp) + struct mbuf *m; + struct sadb_msghdr *mhp; +{ + struct mbuf *n; + struct sadb_ext *ext; + size_t off, end; + int extlen; + int toff; + + IPSEC_ASSERT(m != NULL, ("null mbuf")); + IPSEC_ASSERT(mhp != NULL, ("null msghdr")); + IPSEC_ASSERT(m->m_len >= sizeof(struct sadb_msg), + ("mbuf too small, len %u", m->m_len)); + + /* initialize */ + bzero(mhp, sizeof(*mhp)); + + mhp->msg = mtod(m, struct sadb_msg *); + mhp->ext[0] = (struct sadb_ext *)mhp->msg; /*XXX backward compat */ + + end = PFKEY_UNUNIT64(mhp->msg->sadb_msg_len); + extlen = end; /*just in case extlen is not updated*/ + for (off = sizeof(struct sadb_msg); off < end; off += extlen) { + n = m_pulldown(m, off, sizeof(struct sadb_ext), &toff); + if (!n) { + /* m is already freed */ + return ENOBUFS; + } + ext = (struct sadb_ext *)(mtod(n, caddr_t) + toff); + + /* set pointer */ + switch (ext->sadb_ext_type) { + case SADB_EXT_SA: + case SADB_EXT_ADDRESS_SRC: + case SADB_EXT_ADDRESS_DST: + case SADB_EXT_ADDRESS_PROXY: + case SADB_EXT_LIFETIME_CURRENT: + case SADB_EXT_LIFETIME_HARD: + case SADB_EXT_LIFETIME_SOFT: + case SADB_EXT_KEY_AUTH: + case SADB_EXT_KEY_ENCRYPT: + case SADB_EXT_IDENTITY_SRC: + case SADB_EXT_IDENTITY_DST: + case SADB_EXT_SENSITIVITY: + case SADB_EXT_PROPOSAL: + case SADB_EXT_SUPPORTED_AUTH: + case SADB_EXT_SUPPORTED_ENCRYPT: + 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 + /* duplicate check */ + /* + * XXX Are there duplication payloads of either + * KEY_AUTH or KEY_ENCRYPT ? + */ + if (mhp->ext[ext->sadb_ext_type] != NULL) { + ipseclog((LOG_DEBUG, "%s: duplicate ext_type " + "%u\n", __func__, ext->sadb_ext_type)); + m_freem(m); + V_pfkeystat.out_dupext++; + return EINVAL; + } + break; + default: + ipseclog((LOG_DEBUG, "%s: invalid ext_type %u\n", + __func__, ext->sadb_ext_type)); + m_freem(m); + V_pfkeystat.out_invexttype++; + return EINVAL; + } + + extlen = PFKEY_UNUNIT64(ext->sadb_ext_len); + + if (key_validate_ext(ext, extlen)) { + m_freem(m); + V_pfkeystat.out_invlen++; + return EINVAL; + } + + n = m_pulldown(m, off, extlen, &toff); + if (!n) { + /* m is already freed */ + return ENOBUFS; + } + ext = (struct sadb_ext *)(mtod(n, caddr_t) + toff); + + mhp->ext[ext->sadb_ext_type] = ext; + mhp->extoff[ext->sadb_ext_type] = off; + mhp->extlen[ext->sadb_ext_type] = extlen; + } + + if (off != end) { + m_freem(m); + V_pfkeystat.out_invlen++; + return EINVAL; + } + + return 0; +} + +static int +key_validate_ext(ext, len) + const struct sadb_ext *ext; + int len; +{ + const struct sockaddr *sa; + enum { NONE, ADDR } checktype = NONE; + int baselen = 0; + const int sal = offsetof(struct sockaddr, sa_len) + sizeof(sa->sa_len); + + if (len != PFKEY_UNUNIT64(ext->sadb_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])) + return EINVAL; + if (!minsize[ext->sadb_ext_type] || len < minsize[ext->sadb_ext_type]) + return EINVAL; + if (maxsize[ext->sadb_ext_type] && len > maxsize[ext->sadb_ext_type]) + return EINVAL; + + /* more checks based on sadb_ext_type XXX need more */ + switch (ext->sadb_ext_type) { + case SADB_EXT_ADDRESS_SRC: + case SADB_EXT_ADDRESS_DST: + case SADB_EXT_ADDRESS_PROXY: + baselen = PFKEY_ALIGN8(sizeof(struct sadb_address)); + checktype = ADDR; + break; + case SADB_EXT_IDENTITY_SRC: + case SADB_EXT_IDENTITY_DST: + if (((const struct sadb_ident *)ext)->sadb_ident_type == + SADB_X_IDENTTYPE_ADDR) { + baselen = PFKEY_ALIGN8(sizeof(struct sadb_ident)); + checktype = ADDR; + } else + checktype = NONE; + break; + default: + checktype = NONE; + break; + } + + switch (checktype) { + case NONE: + break; + case ADDR: + sa = (const struct sockaddr *)(((const u_int8_t*)ext)+baselen); + if (len < baselen + sal) + return EINVAL; + if (baselen + PFKEY_ALIGN8(sa->sa_len) != len) + return EINVAL; + break; + } + + return 0; +} + +void +key_init(void) +{ + int i; + + for (i = 0; i < IPSEC_DIR_MAX; i++) + LIST_INIT(&V_sptree[i]); + + LIST_INIT(&V_sahtree); + + for (i = 0; i <= SADB_SATYPE_MAX; i++) + LIST_INIT(&V_regtree[i]); + + 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; + + SPTREE_LOCK_INIT(); + REGTREE_LOCK_INIT(); + SAHTREE_LOCK_INIT(); + ACQ_LOCK_INIT(); + SPACQ_LOCK_INIT(); + +#ifndef IPSEC_DEBUG2 + timeout((void *)key_timehandler, (void *)0, hz); +#endif /*IPSEC_DEBUG2*/ + + /* initialize key statistics */ + keystat.getspi_count = 1; + + printf("IPsec: Initialized Security Association Processing.\n"); +} + +#ifdef VIMAGE +void +key_destroy(void) +{ + struct secpolicy *sp, *nextsp; + struct secacq *acq, *nextacq; + struct secspacq *spacq, *nextspacq; + struct secashead *sah, *nextsah; + struct secreg *reg; + int i; + + SPTREE_LOCK(); + 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); + } + } + } + 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); + } + } + SAHTREE_UNLOCK(); + + REGTREE_LOCK(); + for (i = 0; i <= SADB_SATYPE_MAX; i++) { + LIST_FOREACH(reg, &V_regtree[i], chain) { + if (__LIST_CHAINED(reg)) { + LIST_REMOVE(reg, chain); + free(reg, M_IPSEC_SAR); + break; + } + } + } + REGTREE_UNLOCK(); + + ACQ_LOCK(); + for (acq = LIST_FIRST(&V_acqtree); acq != NULL; acq = nextacq) { + nextacq = LIST_NEXT(acq, chain); + if (__LIST_CHAINED(acq)) { + LIST_REMOVE(acq, chain); + free(acq, M_IPSEC_SAQ); + } + } + ACQ_UNLOCK(); + + SPACQ_LOCK(); + for (spacq = LIST_FIRST(&V_spacqtree); spacq != NULL; + spacq = nextspacq) { + nextspacq = LIST_NEXT(spacq, chain); + if (__LIST_CHAINED(spacq)) { + LIST_REMOVE(spacq, chain); + free(spacq, M_IPSEC_SAQ); + } + } + SPACQ_UNLOCK(); +} +#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; +{ + 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(). */ + + /* + * 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? */ + + /* + * NOTE: We record CURRENT usetime by using wall clock, + * in seconds. HARD and SOFT lifetime are measured by the time + * difference (again in seconds) from usetime. + * + * usetime + * v expire expire + * -----+-----+--------+---> t + * <--------------> 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; +} + +/* + * Take one of the kernel's security keys and convert it into a PF_KEY + * structure within an mbuf, suitable for sending up to a waiting + * application in user land. + * + * IN: + * src: A pointer to a kernel security key. + * exttype: Which type of key this is. Refer to the PF_KEY data structures. + * OUT: + * a valid mbuf or NULL indicating an error + * + */ + +static struct mbuf * +key_setkey(struct seckey *src, u_int16_t exttype) +{ + struct mbuf *m; + struct sadb_key *p; + int len; + + if (src == NULL) + return NULL; + + len = PFKEY_ALIGN8(sizeof(struct sadb_key) + _KEYLEN(src)); + m = key_alloc_mbuf(len); + if (m == NULL) + return NULL; + p = mtod(m, struct sadb_key *); + bzero(p, len); + p->sadb_key_len = PFKEY_UNIT64(len); + p->sadb_key_exttype = exttype; + p->sadb_key_bits = src->bits; + bcopy(src->key_data, _KEYBUF(p), _KEYLEN(src)); + + return m; +} + +/* + * Take one of the kernel's lifetime data structures and convert it + * into a PF_KEY structure within an mbuf, suitable for sending up to + * a waiting application in user land. + * + * IN: + * src: A pointer to a kernel lifetime structure. + * exttype: Which type of lifetime this is. Refer to the PF_KEY + * data structures for more information. + * OUT: + * a valid mbuf or NULL indicating an error + * + */ + +static struct mbuf * +key_setlifetime(struct seclifetime *src, u_int16_t exttype) +{ + struct mbuf *m = NULL; + struct sadb_lifetime *p; + int len = PFKEY_ALIGN8(sizeof(struct sadb_lifetime)); + + if (src == NULL) + return NULL; + + m = key_alloc_mbuf(len); + if (m == NULL) + return m; + p = mtod(m, struct sadb_lifetime *); + + bzero(p, len); + p->sadb_lifetime_len = PFKEY_UNIT64(len); + p->sadb_lifetime_exttype = exttype; + p->sadb_lifetime_allocations = src->allocations; + p->sadb_lifetime_bytes = src->bytes; + p->sadb_lifetime_addtime = src->addtime; + p->sadb_lifetime_usetime = src->usetime; + + return m; + +} diff --git a/freebsd/sys/netipsec/key.h b/freebsd/sys/netipsec/key.h new file mode 100644 index 00000000..4d2b3083 --- /dev/null +++ b/freebsd/sys/netipsec/key.h @@ -0,0 +1,127 @@ +/* $FreeBSD$ */ +/* $KAME: key.h,v 1.21 2001/07/27 03:51:30 itojun Exp $ */ + +/*- + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _NETIPSEC_KEY_HH_ +#define _NETIPSEC_KEY_HH_ + +#ifdef _KERNEL + +struct secpolicy; +struct secpolicyindex; +struct ipsecrequest; +struct secasvar; +struct sockaddr; +struct socket; +struct sadb_msg; +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__) + +extern struct secasvar *key_allocsa(union sockaddr_union *, u_int, u_int32_t, + const char*, int); +extern void key_freesav(struct secasvar **, const char*, int); + +#define KEY_ALLOCSA(dst, proto, spi) \ + key_allocsa(dst, proto, spi, __FILE__, __LINE__) +#define KEY_FREESAV(psav) \ + key_freesav(psav, __FILE__, __LINE__) + +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 *)); + +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)); +#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 + +#ifdef MALLOC_DECLARE +MALLOC_DECLARE(M_IPSEC_SA); +MALLOC_DECLARE(M_IPSEC_SAH); +MALLOC_DECLARE(M_IPSEC_SP); +MALLOC_DECLARE(M_IPSEC_SR); +MALLOC_DECLARE(M_IPSEC_MISC); +MALLOC_DECLARE(M_IPSEC_SAQ); +MALLOC_DECLARE(M_IPSEC_SAR); +MALLOC_DECLARE(M_IPSEC_INPCB); +#endif /* MALLOC_DECLARE */ + +#endif /* defined(_KERNEL) */ +#endif /* _NETIPSEC_KEY_HH_ */ diff --git a/freebsd/sys/netipsec/key_debug.c b/freebsd/sys/netipsec/key_debug.c new file mode 100644 index 00000000..b8d909b3 --- /dev/null +++ b/freebsd/sys/netipsec/key_debug.c @@ -0,0 +1,771 @@ +#include <freebsd/machine/rtems-bsd-config.h> + +/* $FreeBSD$ */ +/* $KAME: key_debug.c,v 1.26 2001/06/27 10:46:50 sakane Exp $ */ + +/*- + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifdef _KERNEL +#include <freebsd/local/opt_inet.h> +#include <freebsd/local/opt_inet6.h> +#include <freebsd/local/opt_ipsec.h> +#endif + +#include <freebsd/sys/types.h> +#include <freebsd/sys/param.h> +#ifdef _KERNEL +#include <freebsd/sys/systm.h> +#include <freebsd/sys/mbuf.h> +#include <freebsd/sys/queue.h> +#endif +#include <freebsd/sys/socket.h> + +#include <freebsd/net/route.h> +#include <freebsd/net/vnet.h> + +#include <freebsd/netipsec/key_var.h> +#include <freebsd/netipsec/key_debug.h> + +#include <freebsd/netinet/in.h> +#include <freebsd/netipsec/ipsec.h> +#ifdef _KERNEL +#include <freebsd/netipsec/keydb.h> +#endif + +#ifndef _KERNEL +#include <freebsd/ctype.h> +#include <freebsd/stdio.h> +#include <freebsd/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 *)); + +#ifdef _KERNEL +static void kdebug_secreplay __P((struct secreplay *)); +#endif + +#ifndef _KERNEL +#define panic(fmt, ...) { printf(fmt, ## __VA_ARGS__); exit(-1); } +#endif + +/* NOTE: host byte order */ + +/* %%%: about struct sadb_msg */ +void +kdebug_sadb(base) + struct sadb_msg *base; +{ + struct sadb_ext *ext; + int tlen, extlen; + + /* sanity check */ + if (base == NULL) + panic("%s: NULL pointer was passed.\n", __func__); + + printf("sadb_msg{ version=%u type=%u errno=%u satype=%u\n", + base->sadb_msg_version, base->sadb_msg_type, + base->sadb_msg_errno, base->sadb_msg_satype); + printf(" len=%u reserved=%u seq=%u pid=%u\n", + base->sadb_msg_len, base->sadb_msg_reserved, + base->sadb_msg_seq, base->sadb_msg_pid); + + tlen = PFKEY_UNUNIT64(base->sadb_msg_len) - sizeof(struct sadb_msg); + ext = (struct sadb_ext *)((caddr_t)base + sizeof(struct sadb_msg)); + + while (tlen > 0) { + printf("sadb_ext{ len=%u type=%u }\n", + ext->sadb_ext_len, ext->sadb_ext_type); + + if (ext->sadb_ext_len == 0) { + printf("%s: invalid ext_len=0 was passed.\n", __func__); + return; + } + if (ext->sadb_ext_len > tlen) { + printf("%s: ext_len too big (%u > %u).\n", + __func__, ext->sadb_ext_len, tlen); + return; + } + + switch (ext->sadb_ext_type) { + case SADB_EXT_SA: + kdebug_sadb_sa(ext); + break; + case SADB_EXT_LIFETIME_CURRENT: + case SADB_EXT_LIFETIME_HARD: + case SADB_EXT_LIFETIME_SOFT: + kdebug_sadb_lifetime(ext); + break; + case SADB_EXT_ADDRESS_SRC: + case SADB_EXT_ADDRESS_DST: + case SADB_EXT_ADDRESS_PROXY: + kdebug_sadb_address(ext); + break; + case SADB_EXT_KEY_AUTH: + case SADB_EXT_KEY_ENCRYPT: + kdebug_sadb_key(ext); + break; + case SADB_EXT_IDENTITY_SRC: + case SADB_EXT_IDENTITY_DST: + kdebug_sadb_identity(ext); + break; + case SADB_EXT_SENSITIVITY: + break; + case SADB_EXT_PROPOSAL: + kdebug_sadb_prop(ext); + break; + case SADB_EXT_SUPPORTED_AUTH: + case SADB_EXT_SUPPORTED_ENCRYPT: + kdebug_sadb_supported(ext); + break; + case SADB_EXT_SPIRANGE: + case SADB_X_EXT_KMPRIVATE: + break; + case SADB_X_EXT_POLICY: + kdebug_sadb_x_policy(ext); + break; + case SADB_X_EXT_SA2: + kdebug_sadb_x_sa2(ext); + break; + default: + printf("%s: invalid ext_type %u\n", __func__, + ext->sadb_ext_type); + return; + } + + extlen = PFKEY_UNUNIT64(ext->sadb_ext_len); + tlen -= extlen; + ext = (struct sadb_ext *)((caddr_t)ext + extlen); + } + + return; +} + +static void +kdebug_sadb_prop(ext) + struct sadb_ext *ext; +{ + struct sadb_prop *prop = (struct sadb_prop *)ext; + struct sadb_comb *comb; + int len; + + /* sanity check */ + if (ext == NULL) + panic("%s: NULL pointer was passed.\n", __func__); + + len = (PFKEY_UNUNIT64(prop->sadb_prop_len) - sizeof(*prop)) + / sizeof(*comb); + comb = (struct sadb_comb *)(prop + 1); + printf("sadb_prop{ replay=%u\n", prop->sadb_prop_replay); + + while (len--) { + printf("sadb_comb{ auth=%u encrypt=%u " + "flags=0x%04x reserved=0x%08x\n", + comb->sadb_comb_auth, comb->sadb_comb_encrypt, + comb->sadb_comb_flags, comb->sadb_comb_reserved); + + printf(" auth_minbits=%u auth_maxbits=%u " + "encrypt_minbits=%u encrypt_maxbits=%u\n", + comb->sadb_comb_auth_minbits, + comb->sadb_comb_auth_maxbits, + comb->sadb_comb_encrypt_minbits, + comb->sadb_comb_encrypt_maxbits); + + printf(" soft_alloc=%u hard_alloc=%u " + "soft_bytes=%lu hard_bytes=%lu\n", + comb->sadb_comb_soft_allocations, + comb->sadb_comb_hard_allocations, + (unsigned long)comb->sadb_comb_soft_bytes, + (unsigned long)comb->sadb_comb_hard_bytes); + + printf(" soft_alloc=%lu hard_alloc=%lu " + "soft_bytes=%lu hard_bytes=%lu }\n", + (unsigned long)comb->sadb_comb_soft_addtime, + (unsigned long)comb->sadb_comb_hard_addtime, + (unsigned long)comb->sadb_comb_soft_usetime, + (unsigned long)comb->sadb_comb_hard_usetime); + comb++; + } + printf("}\n"); + + return; +} + +static void +kdebug_sadb_identity(ext) + struct sadb_ext *ext; +{ + struct sadb_ident *id = (struct sadb_ident *)ext; + int len; + + /* sanity check */ + if (ext == NULL) + panic("%s: NULL pointer was passed.\n", __func__); + + len = PFKEY_UNUNIT64(id->sadb_ident_len) - sizeof(*id); + printf("sadb_ident_%s{", + id->sadb_ident_exttype == SADB_EXT_IDENTITY_SRC ? "src" : "dst"); + switch (id->sadb_ident_type) { + default: + printf(" type=%d id=%lu", + id->sadb_ident_type, (u_long)id->sadb_ident_id); + if (len) { +#ifdef _KERNEL + ipsec_hexdump((caddr_t)(id + 1), len); /*XXX cast ?*/ +#else + char *p, *ep; + printf("\n str=\""); + p = (char *)(id + 1); + ep = p + len; + for (/*nothing*/; *p && p < ep; p++) { + if (isprint(*p)) + printf("%c", *p & 0xff); + else + printf("\\%03o", *p & 0xff); + } +#endif + printf("\""); + } + break; + } + + printf(" }\n"); + + return; +} + +static void +kdebug_sadb_supported(ext) + struct sadb_ext *ext; +{ + struct sadb_supported *sup = (struct sadb_supported *)ext; + struct sadb_alg *alg; + int len; + + /* sanity check */ + if (ext == NULL) + panic("%s: NULL pointer was passed.\n", __func__); + + len = (PFKEY_UNUNIT64(sup->sadb_supported_len) - sizeof(*sup)) + / sizeof(*alg); + alg = (struct sadb_alg *)(sup + 1); + printf("sadb_sup{\n"); + while (len--) { + printf(" { id=%d ivlen=%d min=%d max=%d }\n", + alg->sadb_alg_id, alg->sadb_alg_ivlen, + alg->sadb_alg_minbits, alg->sadb_alg_maxbits); + alg++; + } + printf("}\n"); + + return; +} + +static void +kdebug_sadb_lifetime(ext) + struct sadb_ext *ext; +{ + struct sadb_lifetime *lft = (struct sadb_lifetime *)ext; + + /* sanity check */ + if (ext == NULL) + panic("%s: NULL pointer was passed.\n", __func__); + + printf("sadb_lifetime{ alloc=%u, bytes=%u\n", + lft->sadb_lifetime_allocations, + (u_int32_t)lft->sadb_lifetime_bytes); + printf(" addtime=%u, usetime=%u }\n", + (u_int32_t)lft->sadb_lifetime_addtime, + (u_int32_t)lft->sadb_lifetime_usetime); + + return; +} + +static void +kdebug_sadb_sa(ext) + struct sadb_ext *ext; +{ + struct sadb_sa *sa = (struct sadb_sa *)ext; + + /* sanity check */ + if (ext == NULL) + panic("%s: NULL pointer was passed.\n", __func__); + + printf("sadb_sa{ spi=%u replay=%u state=%u\n", + (u_int32_t)ntohl(sa->sadb_sa_spi), sa->sadb_sa_replay, + sa->sadb_sa_state); + printf(" auth=%u encrypt=%u flags=0x%08x }\n", + sa->sadb_sa_auth, sa->sadb_sa_encrypt, sa->sadb_sa_flags); + + return; +} + +static void +kdebug_sadb_address(ext) + struct sadb_ext *ext; +{ + struct sadb_address *addr = (struct sadb_address *)ext; + + /* sanity check */ + if (ext == NULL) + panic("%s: NULL pointer was passed.\n", __func__); + + printf("sadb_address{ proto=%u prefixlen=%u reserved=0x%02x%02x }\n", + addr->sadb_address_proto, addr->sadb_address_prefixlen, + ((u_char *)&addr->sadb_address_reserved)[0], + ((u_char *)&addr->sadb_address_reserved)[1]); + + kdebug_sockaddr((struct sockaddr *)((caddr_t)ext + sizeof(*addr))); + + return; +} + +static void +kdebug_sadb_key(ext) + struct sadb_ext *ext; +{ + struct sadb_key *key = (struct sadb_key *)ext; + + /* sanity check */ + if (ext == NULL) + panic("%s: NULL pointer was passed.\n", __func__); + + printf("sadb_key{ bits=%u reserved=%u\n", + key->sadb_key_bits, key->sadb_key_reserved); + printf(" key="); + + /* sanity check 2 */ + if ((key->sadb_key_bits >> 3) > + (PFKEY_UNUNIT64(key->sadb_key_len) - sizeof(struct sadb_key))) { + printf("%s: key length mismatch, bit:%d len:%ld.\n", + __func__, + key->sadb_key_bits >> 3, + (long)PFKEY_UNUNIT64(key->sadb_key_len) - sizeof(struct sadb_key)); + } + + ipsec_hexdump((caddr_t)key + sizeof(struct sadb_key), + key->sadb_key_bits >> 3); + printf(" }\n"); + return; +} + +static void +kdebug_sadb_x_sa2(ext) + struct sadb_ext *ext; +{ + struct sadb_x_sa2 *sa2 = (struct sadb_x_sa2 *)ext; + + /* sanity check */ + if (ext == NULL) + panic("%s: NULL pointer was passed.\n", __func__); + + printf("sadb_x_sa2{ mode=%u reqid=%u\n", + sa2->sadb_x_sa2_mode, sa2->sadb_x_sa2_reqid); + printf(" reserved1=%u reserved2=%u sequence=%u }\n", + sa2->sadb_x_sa2_reserved1, sa2->sadb_x_sa2_reserved2, + sa2->sadb_x_sa2_sequence); + + return; +} + +void +kdebug_sadb_x_policy(ext) + struct sadb_ext *ext; +{ + struct sadb_x_policy *xpl = (struct sadb_x_policy *)ext; + struct sockaddr *addr; + + /* sanity check */ + if (ext == NULL) + panic("%s: NULL pointer was passed.\n", __func__); + + printf("sadb_x_policy{ type=%u dir=%u id=%x }\n", + xpl->sadb_x_policy_type, xpl->sadb_x_policy_dir, + xpl->sadb_x_policy_id); + + if (xpl->sadb_x_policy_type == IPSEC_POLICY_IPSEC) { + int tlen; + struct sadb_x_ipsecrequest *xisr; + + tlen = PFKEY_UNUNIT64(xpl->sadb_x_policy_len) - sizeof(*xpl); + xisr = (struct sadb_x_ipsecrequest *)(xpl + 1); + + while (tlen > 0) { + printf(" { len=%u proto=%u mode=%u level=%u reqid=%u\n", + xisr->sadb_x_ipsecrequest_len, + xisr->sadb_x_ipsecrequest_proto, + xisr->sadb_x_ipsecrequest_mode, + xisr->sadb_x_ipsecrequest_level, + xisr->sadb_x_ipsecrequest_reqid); + + if (xisr->sadb_x_ipsecrequest_len > sizeof(*xisr)) { + addr = (struct sockaddr *)(xisr + 1); + kdebug_sockaddr(addr); + addr = (struct sockaddr *)((caddr_t)addr + + addr->sa_len); + kdebug_sockaddr(addr); + } + + printf(" }\n"); + + /* prevent infinite loop */ + if (xisr->sadb_x_ipsecrequest_len <= 0) { + printf("%s: wrong policy struct.\n", __func__); + return; + } + /* prevent overflow */ + if (xisr->sadb_x_ipsecrequest_len > tlen) { + printf("%s: invalid ipsec policy length " + "(%u > %u)\n", __func__, + xisr->sadb_x_ipsecrequest_len, tlen); + return; + } + + tlen -= xisr->sadb_x_ipsecrequest_len; + + xisr = (struct sadb_x_ipsecrequest *)((caddr_t)xisr + + xisr->sadb_x_ipsecrequest_len); + } + + if (tlen != 0) + panic("%s: wrong policy struct.\n", __func__); + } + + return; +} + +#ifdef _KERNEL +/* %%%: about SPD and SAD */ +void +kdebug_secpolicy(sp) + struct secpolicy *sp; +{ + /* 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); + + kdebug_secpolicyindex(&sp->spidx); + + switch (sp->policy) { + case IPSEC_POLICY_DISCARD: + printf(" type=discard }\n"); + break; + case IPSEC_POLICY_NONE: + printf(" type=none }\n"); + break; + 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; + case IPSEC_POLICY_ENTRUST: + printf(" type=entrust }\n"); + break; + default: + printf("%s: Invalid policy found. %d\n", __func__, sp->policy); + break; + } + + return; +} + +void +kdebug_secpolicyindex(spidx) + struct secpolicyindex *spidx; +{ + /* 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); + + 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"); + + return; +} + +void +kdebug_secasindex(saidx) + struct secasindex *saidx; +{ + /* sanity check */ + if (saidx == NULL) + panic("%s: NULL pointer was passed.\n", __func__); + + printf("secasindex{ mode=%u proto=%u\n", + saidx->mode, saidx->proto); + + 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"); + + return; +} + +static void +kdebug_sec_lifetime(struct seclifetime *lft) +{ + /* 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); + + return; +} + +void +kdebug_secasv(sav) + struct secasvar *sav; +{ + /* sanity check */ + if (sav == NULL) + panic("%s: NULL pointer was passed.\n", __func__); + + printf("secas{"); + kdebug_secasindex(&sav->sah->saidx); + + 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); + + 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"); + } + + 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); + +#ifdef notyet + /* XXX: misc[123] ? */ +#endif + + return; +} + +static void +kdebug_secreplay(rpl) + 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); + + if (rpl->bitmap == NULL) { + printf(" }\n"); + return; + } + + printf("\n bitmap { "); + + for (len = 0; len < rpl->wsize; len++) { + for (l = 7; l >= 0; l--) + printf("%u", (((rpl->bitmap)[len] >> l) & 1) ? 1 : 0); + } + printf(" }\n"); + + return; +} + +void +kdebug_mbufhdr(m) + struct mbuf *m; +{ + /* sanity check */ + if (m == NULL) + return; + + printf("mbuf(%p){ m_next:%p m_nextpkt:%p m_data:%p " + "m_len:%d m_type:0x%02x m_flags:0x%02x }\n", + m, m->m_next, m->m_nextpkt, m->m_data, + m->m_len, m->m_type, m->m_flags); + + if (m->m_flags & M_PKTHDR) { + printf(" m_pkthdr{ len:%d rcvif:%p }\n", + m->m_pkthdr.len, m->m_pkthdr.rcvif); + } + + if (m->m_flags & M_EXT) { + printf(" m_ext{ ext_buf:%p ext_free:%p " + "ext_size:%u ref_cnt:%p }\n", + m->m_ext.ext_buf, m->m_ext.ext_free, + m->m_ext.ext_size, m->m_ext.ref_cnt); + } + + return; +} + +void +kdebug_mbuf(m0) + struct mbuf *m0; +{ + struct mbuf *m = m0; + int i, j; + + for (j = 0; m; m = m->m_next) { + kdebug_mbufhdr(m); + printf(" m_data:\n"); + for (i = 0; i < m->m_len; i++) { + if (i && i % 32 == 0) + printf("\n"); + if (i % 4 == 0) + printf(" "); + printf("%02x", mtod(m, u_char *)[i]); + j++; + } + printf("\n"); + } + + return; +} +#endif /* _KERNEL */ + +void +kdebug_sockaddr(addr) + struct sockaddr *addr; +{ + struct sockaddr_in *sin4; +#ifdef INET6 + struct sockaddr_in6 *sin6; +#endif + + /* sanity check */ + if (addr == NULL) + panic("%s: NULL pointer was passed.\n", __func__); + + /* NOTE: We deal with port number as host byte order. */ + printf("sockaddr{ len=%u family=%u", addr->sa_len, addr->sa_family); + + switch (addr->sa_family) { + case AF_INET: + sin4 = (struct sockaddr_in *)addr; + printf(" port=%u\n", ntohs(sin4->sin_port)); + ipsec_hexdump((caddr_t)&sin4->sin_addr, sizeof(sin4->sin_addr)); + break; +#ifdef INET6 + case AF_INET6: + sin6 = (struct sockaddr_in6 *)addr; + printf(" port=%u\n", ntohs(sin6->sin6_port)); + printf(" flowinfo=0x%08x, scope_id=0x%08x\n", + sin6->sin6_flowinfo, sin6->sin6_scope_id); + ipsec_hexdump((caddr_t)&sin6->sin6_addr, + sizeof(sin6->sin6_addr)); + break; +#endif + } + + printf(" }\n"); + + return; +} + +void +ipsec_bindump(buf, len) + caddr_t buf; + int len; +{ + int i; + + for (i = 0; i < len; i++) + printf("%c", (unsigned char)buf[i]); + + return; +} + + +void +ipsec_hexdump(buf, len) + caddr_t buf; + int len; +{ + int i; + + for (i = 0; i < len; i++) { + if (i != 0 && i % 32 == 0) printf("\n"); + if (i % 4 == 0) printf(" "); + printf("%02x", (unsigned char)buf[i]); + } +#if 0 + if (i % 32 != 0) printf("\n"); +#endif + + return; +} diff --git a/freebsd/sys/netipsec/key_debug.h b/freebsd/sys/netipsec/key_debug.h new file mode 100644 index 00000000..26dfdbcf --- /dev/null +++ b/freebsd/sys/netipsec/key_debug.h @@ -0,0 +1,89 @@ +/* $FreeBSD$ */ +/* $KAME: key_debug.h,v 1.10 2001/08/05 08:37:52 itojun Exp $ */ + +/*- + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _NETIPSEC_KEY_DEBUG_HH_ +#define _NETIPSEC_KEY_DEBUG_HH_ + +#ifdef _KERNEL +/* debug flags */ +#define KEYDEBUG_STAMP 0x00000001 /* path */ +#define KEYDEBUG_DATA 0x00000002 /* data */ +#define KEYDEBUG_DUMP 0x00000004 /* dump */ + +#define KEYDEBUG_KEY 0x00000010 /* key processing */ +#define KEYDEBUG_ALG 0x00000020 /* ciph & auth algorithm */ +#define KEYDEBUG_IPSEC 0x00000040 /* ipsec processing */ + +#define KEYDEBUG_KEY_STAMP (KEYDEBUG_KEY | KEYDEBUG_STAMP) +#define KEYDEBUG_KEY_DATA (KEYDEBUG_KEY | KEYDEBUG_DATA) +#define KEYDEBUG_KEY_DUMP (KEYDEBUG_KEY | KEYDEBUG_DUMP) +#define KEYDEBUG_ALG_STAMP (KEYDEBUG_ALG | KEYDEBUG_STAMP) +#define KEYDEBUG_ALG_DATA (KEYDEBUG_ALG | KEYDEBUG_DATA) +#define KEYDEBUG_ALG_DUMP (KEYDEBUG_ALG | KEYDEBUG_DUMP) +#define KEYDEBUG_IPSEC_STAMP (KEYDEBUG_IPSEC | KEYDEBUG_STAMP) +#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) + +VNET_DECLARE(u_int32_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 *)); + +#ifdef _KERNEL +struct secpolicy; +struct secpolicyindex; +struct secasindex; +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 *)); +#endif /*_KERNEL*/ + +struct sockaddr; +extern void kdebug_sockaddr __P((struct sockaddr *)); + +extern void ipsec_hexdump __P((caddr_t, int)); +extern void ipsec_bindump __P((caddr_t, int)); + +#endif /* _NETIPSEC_KEY_DEBUG_HH_ */ diff --git a/freebsd/sys/netipsec/key_var.h b/freebsd/sys/netipsec/key_var.h new file mode 100644 index 00000000..127b6abf --- /dev/null +++ b/freebsd/sys/netipsec/key_var.h @@ -0,0 +1,74 @@ +/* $FreeBSD$ */ +/* $KAME: key_var.h,v 1.11 2001/09/12 23:05:07 sakane Exp $ */ + +/*- + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _NETIPSEC_KEY_VAR_HH_ +#define _NETIPSEC_KEY_VAR_HH_ + +/* sysctl */ +#define KEYCTL_DEBUG_LEVEL 1 +#define KEYCTL_SPI_TRY 2 +#define KEYCTL_SPI_MIN_VALUE 3 +#define KEYCTL_SPI_MAX_VALUE 4 +#define KEYCTL_RANDOM_INT 5 +#define KEYCTL_LARVAL_LIFETIME 6 +#define KEYCTL_BLOCKACQ_COUNT 7 +#define KEYCTL_BLOCKACQ_LIFETIME 8 +#define KEYCTL_ESP_KEYMIN 9 +#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])) +#define _KEYLEN(key) ((u_int)((key)->bits >> 3)) +#define _KEYBITS(key) ((u_int)((key)->bits)) +#define _KEYBUF(key) ((caddr_t)((caddr_t)(key) + sizeof(struct sadb_key))) +#endif /*_KERNEL*/ + +#endif /* _NETIPSEC_KEY_VAR_HH_ */ diff --git a/freebsd/sys/netipsec/keydb.h b/freebsd/sys/netipsec/keydb.h new file mode 100644 index 00000000..7fe8ab39 --- /dev/null +++ b/freebsd/sys/netipsec/keydb.h @@ -0,0 +1,227 @@ +/* $FreeBSD$ */ +/* $KAME: keydb.h,v 1.14 2000/08/02 17:58:26 sakane Exp $ */ + +/*- + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _NETIPSEC_KEYDB_HH_ +#define _NETIPSEC_KEYDB_HH_ + +#ifdef _KERNEL + +#include <freebsd/netipsec/key_var.h> + +#ifndef _SOCKADDR_UNION_DEFINED +#define _SOCKADDR_UNION_DEFINED +/* + * The union of all possible address formats we handle. + */ +union sockaddr_union { + struct sockaddr sa; + struct sockaddr_in sin; + struct sockaddr_in6 sin6; +}; +#endif /* _SOCKADDR_UNION_DEFINED */ + +/* Security Assocciation Index */ +/* NOTE: Ensure to be same address family */ +struct secasindex { + union sockaddr_union src; /* srouce 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 */ + /* see IPSEC_MANUAL_REQID_MAX. */ +}; + +/* + * In order to split out the keydb implementation from that of the + * PF_KEY sockets we need to define a few structures that while they + * may seem common are likely to diverge over time. + */ + +/* sadb_identity */ +struct secident { + u_int16_t type; + u_int64_t id; +}; + +/* sadb_key */ +struct seckey { + u_int16_t bits; + char *key_data; +}; + +struct seclifetime { + u_int32_t allocations; + u_int64_t bytes; + u_int64_t addtime; + u_int64_t usetime; +}; + +union sa_route_union { + struct route sa_route; + struct route sin_route; /* Duplicate for consistency. */ + struct route_in6 sin6_route; +}; + +/* Security Association Data Base */ +struct secashead { + LIST_ENTRY(secashead) chain; + + struct secasindex saidx; + + struct secident *idents; /* source identity */ + 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; +}; + +struct xformsw; +struct enc_xform; +struct auth_hash; +struct comp_algo; + +/* Security Association */ +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 */ + + 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 seclifetime *lft_h; /* HARD lifetime */ + struct seclifetime *lft_s; /* SOFT lifetime */ + + u_int32_t seq; /* sequence number */ + pid_t pid; /* message's pid */ + + struct secashead *sah; /* back pointer to the secashead */ + + /* + * 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. */ +}; + +#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 */ +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 */ +}; + +/* socket table due to send PF_KEY messages. */ +struct secreg { + LIST_ENTRY(secreg) chain; + + struct socket *so; +}; + +/* acquiring list table. */ +struct secacq { + LIST_ENTRY(secacq) chain; + + struct secasindex saidx; + + u_int32_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_HH_ */ diff --git a/freebsd/sys/netipsec/keysock.c b/freebsd/sys/netipsec/keysock.c new file mode 100644 index 00000000..bd8d2170 --- /dev/null +++ b/freebsd/sys/netipsec/keysock.c @@ -0,0 +1,584 @@ +#include <freebsd/machine/rtems-bsd-config.h> + +/* $FreeBSD$ */ +/* $KAME: keysock.c,v 1.25 2001/08/13 20:07:41 itojun Exp $ */ + +/*- + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <freebsd/local/opt_ipsec.h> + +/* This code has derived from sys/net/rtsock.c on FreeBSD2.2.5 */ + +#include <freebsd/sys/types.h> +#include <freebsd/sys/param.h> +#include <freebsd/sys/domain.h> +#include <freebsd/sys/errno.h> +#include <freebsd/sys/kernel.h> +#include <freebsd/sys/lock.h> +#include <freebsd/sys/malloc.h> +#include <freebsd/sys/mbuf.h> +#include <freebsd/sys/mutex.h> +#include <freebsd/sys/priv.h> +#include <freebsd/sys/protosw.h> +#include <freebsd/sys/signalvar.h> +#include <freebsd/sys/socket.h> +#include <freebsd/sys/socketvar.h> +#include <freebsd/sys/sysctl.h> +#include <freebsd/sys/systm.h> + +#include <freebsd/net/if.h> +#include <freebsd/net/raw_cb.h> +#include <freebsd/net/route.h> +#include <freebsd/net/vnet.h> + +#include <freebsd/netinet/in.h> + +#include <freebsd/net/pfkeyv2.h> +#include <freebsd/netipsec/key.h> +#include <freebsd/netipsec/keysock.h> +#include <freebsd/netipsec/key_debug.h> +#include <freebsd/netipsec/ipsec.h> + +#include <freebsd/machine/stdarg.h> + +struct key_cb { + int key_count; + int any_count; +}; +static VNET_DEFINE(struct key_cb, key_cb); +#define V_key_cb VNET(key_cb) + +static struct sockaddr key_src = { 2, PF_KEY, }; + +static int key_sendup0 __P((struct rawcb *, struct mbuf *, int)); + +VNET_DEFINE(struct pfkeystat, pfkeystat); + +/* + * key_output() + */ +int +key_output(struct mbuf *m, struct socket *so) +{ + struct sadb_msg *msg; + int len, error = 0; + + if (m == 0) + panic("%s: NULL pointer was passed.\n", __func__); + + V_pfkeystat.out_total++; + V_pfkeystat.out_bytes += m->m_pkthdr.len; + + len = m->m_pkthdr.len; + if (len < sizeof(struct sadb_msg)) { + V_pfkeystat.out_tooshort++; + error = EINVAL; + goto end; + } + + if (m->m_len < sizeof(struct sadb_msg)) { + if ((m = m_pullup(m, sizeof(struct sadb_msg))) == 0) { + V_pfkeystat.out_nomem++; + error = ENOBUFS; + goto end; + } + } + + M_ASSERTPKTHDR(m); + + KEYDEBUG(KEYDEBUG_KEY_DUMP, kdebug_mbuf(m)); + + msg = mtod(m, struct sadb_msg *); + V_pfkeystat.out_msgtype[msg->sadb_msg_type]++; + if (len != PFKEY_UNUNIT64(msg->sadb_msg_len)) { + V_pfkeystat.out_invlen++; + error = EINVAL; + goto end; + } + + error = key_parse(m, so); + m = NULL; +end: + if (m) + m_freem(m); + return error; +} + +/* + * send message to the socket. + */ +static int +key_sendup0(rp, m, promisc) + 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) { + V_pfkeystat.in_nomem++; + m_freem(m); + 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; + pmsg->sadb_msg_type = SADB_X_PROMISC; + pmsg->sadb_msg_len = PFKEY_UNIT64(m->m_pkthdr.len); + /* pid and seq? */ + + V_pfkeystat.in_msgtype[pmsg->sadb_msg_type]++; + } + + if (!sbappendaddr(&rp->rcb_socket->so_rcv, (struct sockaddr *)&key_src, + m, NULL)) { + V_pfkeystat.in_nomem++; + m_freem(m); + error = ENOBUFS; + } else + error = 0; + sorwakeup(rp->rcb_socket); + return error; +} + +/* 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*/ +{ + struct mbuf *m, *n, *mprev; + int tlen; + + /* sanity check */ + if (so == 0 || msg == 0) + panic("%s: NULL pointer was passed.\n", __func__); + + KEYDEBUG(KEYDEBUG_KEY_DUMP, + printf("%s: \n", __func__); + kdebug_sadb(msg)); + + /* + * we increment statistics here, just in case we have ENOBUFS + * in this function. + */ + V_pfkeystat.in_total++; + V_pfkeystat.in_bytes += len; + V_pfkeystat.in_msgtype[msg->sadb_msg_type]++; + + /* + * Get mbuf chain whenever possible (not clusters), + * to save socket buffer. We'll be generating many SADB_ACQUIRE + * messages to listening key sockets. If we simply allocate clusters, + * sbappendaddr() will raise ENOBUFS due to too little sbspace(). + * sbspace() computes # of actual data bytes AND mbuf region. + * + * TODO: SADB_ACQUIRE filters should be implemented. + */ + tlen = len; + m = mprev = NULL; + while (tlen > 0) { + if (tlen == len) { + MGETHDR(n, M_DONTWAIT, MT_DATA); + if (n == NULL) { + V_pfkeystat.in_nomem++; + return ENOBUFS; + } + n->m_len = MHLEN; + } else { + MGET(n, M_DONTWAIT, MT_DATA); + if (n == NULL) { + V_pfkeystat.in_nomem++; + return ENOBUFS; + } + n->m_len = MLEN; + } + if (tlen >= MCLBYTES) { /*XXX better threshold? */ + MCLGET(n, M_DONTWAIT); + if ((n->m_flags & M_EXT) == 0) { + m_free(n); + m_freem(m); + V_pfkeystat.in_nomem++; + return ENOBUFS; + } + n->m_len = MCLBYTES; + } + + if (tlen < n->m_len) + n->m_len = tlen; + n->m_next = NULL; + if (m == NULL) + m = mprev = n; + else { + mprev->m_next = n; + mprev = n; + } + tlen -= n->m_len; + n = NULL; + } + m->m_pkthdr.len = len; + m->m_pkthdr.rcvif = NULL; + m_copyback(m, 0, len, (caddr_t)msg); + + /* avoid duplicated statistics */ + V_pfkeystat.in_total--; + V_pfkeystat.in_bytes -= len; + V_pfkeystat.in_msgtype[msg->sadb_msg_type]--; + + return key_sendup_mbuf(so, m, 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; +{ + struct mbuf *n; + struct keycb *kp; + int sendup; + struct rawcb *rp; + int error = 0; + + if (m == NULL) + panic("key_sendup_mbuf: NULL pointer was passed.\n"); + if (so == NULL && target == KEY_SENDUP_ONE) + panic("%s: NULL pointer was passed.\n", __func__); + + V_pfkeystat.in_total++; + V_pfkeystat.in_bytes += m->m_pkthdr.len; + if (m->m_len < sizeof(struct sadb_msg)) { + m = m_pullup(m, sizeof(struct sadb_msg)); + if (m == NULL) { + V_pfkeystat.in_nomem++; + return ENOBUFS; + } + } + if (m->m_len >= sizeof(struct sadb_msg)) { + struct sadb_msg *msg; + msg = mtod(m, struct sadb_msg *); + V_pfkeystat.in_msgtype[msg->sadb_msg_type]++; + } + mtx_lock(&rawcb_mtx); + LIST_FOREACH(rp, &V_rawcb_list, list) + { + if (rp->rcb_proto.sp_family != PF_KEY) + continue; + if (rp->rcb_proto.sp_protocol + && rp->rcb_proto.sp_protocol != PF_KEY_V2) { + continue; + } + + kp = (struct keycb *)rp; + + /* + * If you are in promiscuous mode, and when you get broadcasted + * reply, you'll get two PF_KEY messages. + * (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) { + (void)key_sendup0(rp, n, 1); + n = NULL; + } + } + + /* the exact target will be processed later */ + if (so && sotorawcb(so) == rp) + continue; + + sendup = 0; + switch (target) { + case KEY_SENDUP_ONE: + /* the statement has no effect */ + if (so && sotorawcb(so) == rp) + sendup++; + break; + case KEY_SENDUP_ALL: + sendup++; + break; + case KEY_SENDUP_REGISTERED: + if (kp->kp_registered) + sendup++; + break; + } + V_pfkeystat.in_msgtarget[target]++; + + if (!sendup) + continue; + + if ((n = m_copy(m, 0, (int)M_COPYALL)) == NULL) { + m_freem(m); + V_pfkeystat.in_nomem++; + mtx_unlock(&rawcb_mtx); + return ENOBUFS; + } + + if ((error = key_sendup0(rp, n, 0)) != 0) { + m_freem(m); + mtx_unlock(&rawcb_mtx); + return error; + } + + n = NULL; + } + + if (so) { + error = key_sendup0(sotorawcb(so), m, 0); + m = NULL; + } else { + error = 0; + m_freem(m); + } + mtx_unlock(&rawcb_mtx); + return error; +} + +/* + * key_abort() + * derived from net/rtsock.c:rts_abort() + */ +static void +key_abort(struct socket *so) +{ + raw_usrreqs.pru_abort(so); +} + +/* + * key_attach() + * derived from net/rtsock.c:rts_attach() + */ +static int +key_attach(struct socket *so, int proto, struct thread *td) +{ + struct keycb *kp; + int error; + + KASSERT(so->so_pcb == NULL, ("key_attach: so_pcb != NULL")); + + if (td != NULL) { + error = priv_check(td, PRIV_NET_RAW); + if (error) + return error; + } + + /* XXX */ + kp = malloc(sizeof *kp, M_PCB, M_WAITOK | M_ZERO); + if (kp == 0) + return ENOBUFS; + + so->so_pcb = (caddr_t)kp; + error = raw_attach(so, proto); + kp = (struct keycb *)sotorawcb(so); + if (error) { + free(kp, M_PCB); + so->so_pcb = (caddr_t) 0; + return error; + } + + kp->kp_promisc = kp->kp_registered = 0; + + if (kp->kp_raw.rcb_proto.sp_protocol == PF_KEY) /* XXX: AF_KEY */ + V_key_cb.key_count++; + V_key_cb.any_count++; + soisconnected(so); + so->so_options |= SO_USELOOPBACK; + + return 0; +} + +/* + * key_bind() + * derived from net/rtsock.c:rts_bind() + */ +static int +key_bind(struct socket *so, struct sockaddr *nam, struct thread *td) +{ + return EINVAL; +} + +/* + * key_close() + * derived from net/rtsock.c:rts_close(). + */ +static void +key_close(struct socket *so) +{ + + raw_usrreqs.pru_close(so); +} + +/* + * key_connect() + * derived from net/rtsock.c:rts_connect() + */ +static int +key_connect(struct socket *so, struct sockaddr *nam, struct thread *td) +{ + return EINVAL; +} + +/* + * key_detach() + * derived from net/rtsock.c:rts_detach() + */ +static void +key_detach(struct socket *so) +{ + struct keycb *kp = (struct keycb *)sotorawcb(so); + + KASSERT(kp != NULL, ("key_detach: kp == NULL")); + if (kp->kp_raw.rcb_proto.sp_protocol + == PF_KEY) /* XXX: AF_KEY */ + V_key_cb.key_count--; + V_key_cb.any_count--; + + key_freereg(so); + raw_usrreqs.pru_detach(so); +} + +/* + * key_disconnect() + * derived from net/rtsock.c:key_disconnect() + */ +static int +key_disconnect(struct socket *so) +{ + return(raw_usrreqs.pru_disconnect(so)); +} + +/* + * key_peeraddr() + * derived from net/rtsock.c:rts_peeraddr() + */ +static int +key_peeraddr(struct socket *so, struct sockaddr **nam) +{ + return(raw_usrreqs.pru_peeraddr(so, nam)); +} + +/* + * key_send() + * derived from net/rtsock.c:rts_send() + */ +static int +key_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *nam, + struct mbuf *control, struct thread *td) +{ + return(raw_usrreqs.pru_send(so, flags, m, nam, control, td)); +} + +/* + * key_shutdown() + * derived from net/rtsock.c:rts_shutdown() + */ +static int +key_shutdown(struct socket *so) +{ + return(raw_usrreqs.pru_shutdown(so)); +} + +/* + * key_sockaddr() + * derived from net/rtsock.c:rts_sockaddr() + */ +static int +key_sockaddr(struct socket *so, struct sockaddr **nam) +{ + return(raw_usrreqs.pru_sockaddr(so, nam)); +} + +struct pr_usrreqs key_usrreqs = { + .pru_abort = key_abort, + .pru_attach = key_attach, + .pru_bind = key_bind, + .pru_connect = key_connect, + .pru_detach = key_detach, + .pru_disconnect = key_disconnect, + .pru_peeraddr = key_peeraddr, + .pru_send = key_send, + .pru_shutdown = key_shutdown, + .pru_sockaddr = key_sockaddr, + .pru_close = key_close, +}; + +/* sysctl */ +SYSCTL_NODE(_net, PF_KEY, key, CTLFLAG_RW, 0, "Key Family"); + +/* + * Definitions of protocols supported in the KEY domain. + */ + +extern struct domain keydomain; + +struct protosw keysw[] = { +{ + .pr_type = SOCK_RAW, + .pr_domain = &keydomain, + .pr_protocol = PF_KEY_V2, + .pr_flags = PR_ATOMIC|PR_ADDR, + .pr_output = key_output, + .pr_ctlinput = raw_ctlinput, + .pr_init = raw_init, + .pr_usrreqs = &key_usrreqs +} +}; + +static void +key_init0(void) +{ + + bzero((caddr_t)&V_key_cb, sizeof(V_key_cb)); + key_init(); +} + +struct domain keydomain = { + .dom_family = PF_KEY, + .dom_name = "key", + .dom_init = key_init0, +#ifdef VIMAGE + .dom_destroy = key_destroy, +#endif + .dom_protosw = keysw, + .dom_protoswNPROTOSW = &keysw[sizeof(keysw)/sizeof(keysw[0])] +}; + +VNET_DOMAIN_SET(key); diff --git a/freebsd/sys/netipsec/keysock.h b/freebsd/sys/netipsec/keysock.h new file mode 100644 index 00000000..2d147b7a --- /dev/null +++ b/freebsd/sys/netipsec/keysock.h @@ -0,0 +1,83 @@ +/* $FreeBSD$ */ +/* $KAME: keysock.h,v 1.8 2000/03/27 05:11:06 sumikawa Exp $ */ + +/*- + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _NETIPSEC_KEYSOCK_HH_ +#define _NETIPSEC_KEYSOCK_HH_ + +/* 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 */ + /* 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 */ + /* others */ + u_quad_t sockerr; /* # of socket related errors */ +}; + +#define KEY_SENDUP_ONE 0 +#define KEY_SENDUP_ALL 1 +#define KEY_SENDUP_REGISTERED 2 + +#ifdef _KERNEL +struct keycb { + struct rawcb kp_raw; /* rawcb */ + int kp_promisc; /* promiscuous mode */ + int kp_registered; /* registered socket */ +}; + +VNET_DECLARE(struct pfkeystat, pfkeystat); +#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_sendup __P((struct socket *, struct sadb_msg *, u_int, int)); +extern int key_sendup_mbuf __P((struct socket *, struct mbuf *, int)); +#endif /* _KERNEL */ + +#endif /*_NETIPSEC_KEYSOCK_HH_*/ diff --git a/freebsd/sys/netipsec/xform.h b/freebsd/sys/netipsec/xform.h new file mode 100644 index 00000000..d229e17d --- /dev/null +++ b/freebsd/sys/netipsec/xform.h @@ -0,0 +1,129 @@ +/* $FreeBSD$ */ +/* $OpenBSD: ip_ipsp.h,v 1.119 2002/03/14 01:27:11 millert Exp $ */ +/*- + * The authors of this code are John Ioannidis (ji@tla.org), + * Angelos D. Keromytis (kermit@csd.uch.gr), + * Niels Provos (provos@physnet.uni-hamburg.de) and + * Niklas Hallqvist (niklas@appli.se). + * + * 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 and Niklas Hallqvist. + * + * Copyright (c) 1995, 1996, 1997, 1998, 1999 by John Ioannidis, + * Angelos D. Keromytis and Niels Provos. + * Copyright (c) 1999 Niklas Hallqvist. + * 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 _NETIPSEC_XFORM_HH_ +#define _NETIPSEC_XFORM_HH_ + +#include <freebsd/sys/types.h> +#include <freebsd/netinet/in.h> +#include <freebsd/opencrypto/xform.h> + +#define AH_HMAC_HASHLEN 12 /* 96 bits of authenticator */ +#define AH_HMAC_INITIAL_RPL 1 /* replay counter initial value */ + +/* + * Packet tag assigned on completion of IPsec processing; used + * to speedup processing when/if the packet comes back for more + * processing. + */ +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; +}; + +/* + * 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; +struct ipescrequest; + +struct xformsw { + u_short xf_type; /* xform ID */ +#define XF_IP4 1 /* IP inside IP */ +#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 */ + 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 */ +}; + +#ifdef _KERNEL +extern void xform_register(struct xformsw*); +extern int xform_init(struct secasvar *sav, int xftype); + +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); + +/* XF_AH */ +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_HH_ */ diff --git a/freebsd/sys/netipsec/xform_ah.c b/freebsd/sys/netipsec/xform_ah.c new file mode 100644 index 00000000..34014eef --- /dev/null +++ b/freebsd/sys/netipsec/xform_ah.c @@ -0,0 +1,1219 @@ +#include <freebsd/machine/rtems-bsd-config.h> + +/* $FreeBSD$ */ +/* $OpenBSD: ip_ah.c,v 1.63 2001/06/26 06:18:58 angelos 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 and Niklas Hallqvist. + * + * Copyright (c) 1995, 1996, 1997, 1998, 1999 by John Ioannidis, + * Angelos D. Keromytis and Niels Provos. + * Copyright (c) 1999 Niklas Hallqvist. + * 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. + */ +#include <freebsd/local/opt_inet.h> +#include <freebsd/local/opt_inet6.h> + +#include <freebsd/sys/param.h> +#include <freebsd/sys/systm.h> +#include <freebsd/sys/mbuf.h> +#include <freebsd/sys/socket.h> +#include <freebsd/sys/syslog.h> +#include <freebsd/sys/kernel.h> +#include <freebsd/sys/sysctl.h> + +#include <freebsd/net/if.h> +#include <freebsd/net/vnet.h> + +#include <freebsd/netinet/in.h> +#include <freebsd/netinet/in_systm.h> +#include <freebsd/netinet/ip.h> +#include <freebsd/netinet/ip_ecn.h> +#include <freebsd/netinet/ip6.h> + +#include <freebsd/net/route.h> +#include <freebsd/netipsec/ipsec.h> +#include <freebsd/netipsec/ah.h> +#include <freebsd/netipsec/ah_var.h> +#include <freebsd/netipsec/xform.h> + +#ifdef INET6 +#include <freebsd/netinet6/ip6_var.h> +#include <freebsd/netipsec/ipsec6.h> +#include <freebsd/netinet6/ip6_ecn.h> +#endif + +#include <freebsd/netipsec/key.h> +#include <freebsd/netipsec/key_debug.h> + +#include <freebsd/opencrypto/cryptodev.h> + +/* + * Return header size in bytes. The old protocol did not support + * the replay counter; the new protocol always includes the counter. + */ +#define HDRSIZE(sav) \ + (((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. + */ +#define AUTHSIZE(sav) \ + ((sav->flags & SADB_X_EXT_OLD) ? 16 : AH_HMAC_HASHLEN) + +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); + +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, ""); + +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*); + +/* + * 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; + } + return NULL; +} + +size_t +ah_hdrsiz(struct secasvar *sav) +{ + size_t size; + + if (sav != NULL) { + int authsize; + IPSEC_ASSERT(sav->tdb_authalgxform != NULL, ("null xform")); + /*XXX not right for null algorithm--does it matter??*/ + authsize = AUTHSIZE(sav); + size = roundup(authsize, sizeof (u_int32_t)) + HDRSIZE(sav); + } else { + /* default guess */ + size = sizeof (struct ah) + sizeof (u_int32_t) + 16; + } + return size; +} + +/* + * NB: public for use by esp_init. + */ +int +ah_init0(struct secasvar *sav, struct xformsw *xsp, struct cryptoini *cria) +{ + struct auth_hash *thash; + int keylen; + + thash = ah_algorithm_lookup(sav->alg_auth); + if (thash == NULL) { + DPRINTF(("%s: unsupported authentication algorithm %u\n", + __func__, sav->alg_auth)); + return EINVAL; + } + /* + * Verify the replay state block allocation is consistent with + * the protocol type. We check here so we can make assumptions + * later during protocol processing. + */ + /* NB: replay state is setup elsewhere (sigh) */ + if (((sav->flags&SADB_X_EXT_OLD) == 0) ^ (sav->replay != NULL)) { + DPRINTF(("%s: replay state block inconsistency, " + "%s algorithm %s replay state\n", __func__, + (sav->flags & SADB_X_EXT_OLD) ? "old" : "new", + sav->replay == NULL ? "without" : "with")); + return EINVAL; + } + if (sav->key_auth == NULL) { + DPRINTF(("%s: no authentication key for %s algorithm\n", + __func__, thash->name)); + return EINVAL; + } + keylen = _KEYLEN(sav->key_auth); + if (keylen != thash->keysize && thash->keysize != 0) { + DPRINTF(("%s: invalid keylength %d, algorithm %s requires " + "keysize %d\n", __func__, + keylen, thash->name, thash->keysize)); + return EINVAL; + } + + sav->tdb_xform = xsp; + sav->tdb_authalgxform = thash; + + /* Initialize crypto session. */ + bzero(cria, sizeof (*cria)); + cria->cri_alg = sav->tdb_authalgxform->type; + cria->cri_klen = _KEYBITS(sav->key_auth); + cria->cri_key = sav->key_auth->key_data; + cria->cri_mlen = AUTHSIZE(sav); + + return 0; +} + +/* + * ah_init() is called when an SPI is being set up. + */ +static int +ah_init(struct secasvar *sav, struct xformsw *xsp) +{ + struct cryptoini cria; + int error; + + error = ah_init0(sav, xsp, &cria); + return error ? error : + crypto_newsession(&sav->tdb_cryptoid, &cria, V_crypto_support); +} + +/* + * Paranoia. + * + * NB: public for use by esp_zeroize (XXX). + */ +int +ah_zeroize(struct secasvar *sav) +{ + int err; + + if (sav->key_auth) + bzero(sav->key_auth->key_data, _KEYLEN(sav->key_auth)); + + err = crypto_freesession(sav->tdb_cryptoid); + sav->tdb_cryptoid = 0; + sav->tdb_authalgxform = NULL; + sav->tdb_xform = NULL; + return err; +} + +/* + * Massage IPv4/IPv6 headers for AH processing. + */ +static int +ah_massage_headers(struct mbuf **m0, int proto, int skip, int alg, int out) +{ + struct mbuf *m = *m0; + unsigned char *ptr; + int off, count; + +#ifdef INET + struct ip *ip; +#endif /* INET */ + +#ifdef INET6 + struct ip6_ext *ip6e; + struct ip6_hdr ip6; + int alloc, len, ad; +#endif /* INET6 */ + + switch (proto) { +#ifdef INET + case AF_INET: + /* + * This is the least painful way of dealing with IPv4 header + * and option processing -- just make sure they're in + * contiguous memory. + */ + *m0 = m = m_pullup(m, skip); + if (m == NULL) { + DPRINTF(("%s: m_pullup failed\n", __func__)); + return ENOBUFS; + } + + /* Fix the IP header */ + ip = mtod(m, struct ip *); + if (V_ah_cleartos) + ip->ip_tos = 0; + 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; + } + + ptr = mtod(m, unsigned char *) + sizeof(struct ip); + + /* IPv4 option processing */ + for (off = sizeof(struct ip); off < skip;) { + if (ptr[off] == IPOPT_EOL || ptr[off] == IPOPT_NOP || + off + 1 < skip) + ; + else { + DPRINTF(("%s: illegal IPv4 option length for " + "option %d\n", __func__, ptr[off])); + + m_freem(m); + return EINVAL; + } + + switch (ptr[off]) { + case IPOPT_EOL: + off = skip; /* End the loop. */ + break; + + case IPOPT_NOP: + off++; + break; + + case IPOPT_SECURITY: /* 0x82 */ + case 0x85: /* Extended security. */ + case 0x86: /* Commercial security. */ + case 0x94: /* Router alert */ + case 0x95: /* RFC1770 */ + /* Sanity check for option length. */ + if (ptr[off + 1] < 2) { + DPRINTF(("%s: illegal IPv4 option " + "length for option %d\n", + __func__, ptr[off])); + + m_freem(m); + return EINVAL; + } + + off += ptr[off + 1]; + break; + + case IPOPT_LSRR: + case IPOPT_SSRR: + /* Sanity check for option length. */ + if (ptr[off + 1] < 2) { + DPRINTF(("%s: illegal IPv4 option " + "length for option %d\n", + __func__, ptr[off])); + + m_freem(m); + return EINVAL; + } + + /* + * On output, if we have either of the + * source routing options, we should + * swap the destination address of the + * IP header with the last address + * specified in the option, as that is + * what the destination's IP header + * will look like. + */ + if (out) + bcopy(ptr + off + ptr[off + 1] - + sizeof(struct in_addr), + &(ip->ip_dst), sizeof(struct in_addr)); + + /* Fall through */ + default: + /* Sanity check for option length. */ + if (ptr[off + 1] < 2) { + DPRINTF(("%s: illegal IPv4 option " + "length for option %d\n", + __func__, ptr[off])); + m_freem(m); + return EINVAL; + } + + /* Zeroize all other options. */ + count = ptr[off + 1]; + bcopy(ipseczeroes, ptr, count); + off += count; + break; + } + + /* Sanity check. */ + if (off > skip) { + DPRINTF(("%s: malformed IPv4 options header\n", + __func__)); + + m_freem(m); + return EINVAL; + } + } + + break; +#endif /* INET */ + +#ifdef INET6 + case AF_INET6: /* Ugly... */ + /* Copy and "cook" the IPv6 header. */ + m_copydata(m, 0, sizeof(ip6), (caddr_t) &ip6); + + /* We don't do IPv6 Jumbograms. */ + if (ip6.ip6_plen == 0) { + DPRINTF(("%s: unsupported IPv6 jumbogram\n", __func__)); + m_freem(m); + return EMSGSIZE; + } + + ip6.ip6_flow = 0; + ip6.ip6_hlim = 0; + ip6.ip6_vfc &= ~IPV6_VERSION_MASK; + ip6.ip6_vfc |= IPV6_VERSION; + + /* Scoped address handling. */ + 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; + + /* Done with IPv6 header. */ + m_copyback(m, 0, sizeof(struct ip6_hdr), (caddr_t) &ip6); + + /* Let's deal with the remaining headers (if any). */ + if (skip - sizeof(struct ip6_hdr) > 0) { + if (m->m_len <= skip) { + ptr = (unsigned char *) malloc( + skip - sizeof(struct ip6_hdr), + M_XDATA, M_NOWAIT); + if (ptr == NULL) { + DPRINTF(("%s: failed to allocate memory" + "for IPv6 headers\n",__func__)); + m_freem(m); + return ENOBUFS; + } + + /* + * Copy all the protocol headers after + * the IPv6 header. + */ + m_copydata(m, sizeof(struct ip6_hdr), + skip - sizeof(struct ip6_hdr), ptr); + alloc = 1; + } else { + /* No need to allocate memory. */ + ptr = mtod(m, unsigned char *) + + sizeof(struct ip6_hdr); + alloc = 0; + } + } else + break; + + off = ip6.ip6_nxt & 0xff; /* Next header type. */ + + for (len = 0; len < skip - sizeof(struct ip6_hdr);) + switch (off) { + case IPPROTO_HOPOPTS: + case IPPROTO_DSTOPTS: + ip6e = (struct ip6_ext *) (ptr + len); + + /* + * Process the mutable/immutable + * options -- borrows heavily from the + * KAME code. + */ + for (count = len + sizeof(struct ip6_ext); + count < len + ((ip6e->ip6e_len + 1) << 3);) { + if (ptr[count] == IP6OPT_PAD1) { + count++; + continue; /* Skip padding. */ + } + + /* Sanity check. */ + if (count > len + + ((ip6e->ip6e_len + 1) << 3)) { + m_freem(m); + + /* Free, if we allocated. */ + if (alloc) + free(ptr, M_XDATA); + return EINVAL; + } + + ad = ptr[count + 1]; + + /* If mutable option, zeroize. */ + if (ptr[count] & IP6OPT_MUTABLE) + bcopy(ipseczeroes, ptr + count, + ptr[count + 1]); + + count += ad; + + /* Sanity check. */ + if (count > + skip - sizeof(struct ip6_hdr)) { + m_freem(m); + + /* Free, if we allocated. */ + if (alloc) + free(ptr, M_XDATA); + return EINVAL; + } + } + + /* Advance. */ + len += ((ip6e->ip6e_len + 1) << 3); + off = ip6e->ip6e_nxt; + break; + + case IPPROTO_ROUTING: + /* + * Always include routing headers in + * computation. + */ + ip6e = (struct ip6_ext *) (ptr + len); + len += ((ip6e->ip6e_len + 1) << 3); + off = ip6e->ip6e_nxt; + break; + + default: + DPRINTF(("%s: unexpected IPv6 header type %d", + __func__, off)); + if (alloc) + free(ptr, M_XDATA); + m_freem(m); + return EINVAL; + } + + /* Copyback and free, if we allocated. */ + if (alloc) { + m_copyback(m, sizeof(struct ip6_hdr), + skip - sizeof(struct ip6_hdr), ptr); + free(ptr, M_XDATA); + } + + break; +#endif /* INET6 */ + } + + return 0; +} + +/* + * ah_input() gets called to verify that an input packet + * passes authentication. + */ +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; + + struct cryptodesc *crda; + struct cryptop *crp; + + IPSEC_ASSERT(sav != NULL, ("null SA")); + IPSEC_ASSERT(sav->key_auth != NULL, ("null authentication key")); + IPSEC_ASSERT(sav->tdb_authalgxform != NULL, + ("null authentication xform")); + + /* Figure out header size. */ + rplen = HDRSIZE(sav); + + /* XXX don't pullup, just copy header */ + IP6_EXTHDR_GET(ah, struct newah *, m, skip, rplen); + if (ah == NULL) { + DPRINTF(("ah_input: cannot pullup header\n")); + V_ahstat.ahs_hdrops++; /*XXX*/ + m_freem(m); + return ENOBUFS; + } + + /* Check replay window, if applicable. */ + if (sav->replay && !ipsec_chkreplay(ntohl(ah->ah_seq), sav)) { + V_ahstat.ahs_replay++; + DPRINTF(("%s: packet replay failure: %s\n", __func__, + ipsec_logsastr(sav))); + m_freem(m); + return ENOBUFS; + } + + /* Verify AH header length. */ + hl = ah->ah_len * sizeof (u_int32_t); + ahx = sav->tdb_authalgxform; + 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))); + V_ahstat.ahs_badauthl++; + m_freem(m); + return EACCES; + } + V_ahstat.ahs_ibytes += m->m_pkthdr.len - skip - hl; + + /* Get crypto descriptors. */ + crp = crypto_getreq(1); + if (crp == NULL) { + DPRINTF(("%s: failed to acquire crypto descriptor\n",__func__)); + V_ahstat.ahs_crypto++; + m_freem(m); + return ENOBUFS; + } + + crda = crp->crp_desc; + IPSEC_ASSERT(crda != NULL, ("null crypto descriptor")); + + crda->crd_skip = 0; + crda->crd_len = m->m_pkthdr.len; + crda->crd_inject = skip + rplen; + + /* Authentication operation. */ + crda->crd_alg = ahx->type; + 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__)); + V_ahstat.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)(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 */ + V_ahstat.ahs_hdrops++; + free(tc, M_XDATA); + crypto_freereq(crp); + return error; + } + } + + /* 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 = ah_input_cb; + crp->crp_sid = sav->tdb_cryptoid; + crp->crp_opaque = (caddr_t) tc; + + /* 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. */ + + if (mtag == NULL) + return crypto_dispatch(crp); + else + return ah_input_cb(crp); +} + +#ifdef INET6 +#define IPSEC_COMMON_INPUT_CB(m, sav, skip, protoff, mtag) do { \ + if (saidx->dst.sa.sa_family == AF_INET6) { \ + error = ipsec6_common_input_cb(m, sav, skip, protoff, mtag); \ + } else { \ + error = ipsec4_common_input_cb(m, sav, skip, protoff, mtag); \ + } \ +} while (0) +#else +#define IPSEC_COMMON_INPUT_CB(m, sav, skip, protoff, mtag) \ + (error = ipsec4_common_input_cb(m, sav, skip, protoff, mtag)) +#endif + +/* + * AH input callback from the crypto driver. + */ +static int +ah_input_cb(struct cryptop *crp) +{ + int rplen, error, skip, protoff; + unsigned char calc[AH_ALEN_MAX]; + struct mbuf *m; + struct cryptodesc *crd; + struct auth_hash *ahx; + struct tdb_crypto *tc; + struct m_tag *mtag; + struct secasvar *sav; + struct secasindex *saidx; + u_int8_t nxt; + caddr_t ptr; + int authsize; + + 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 = KEY_ALLOCSA(&tc->tc_dst, tc->tc_proto, tc->tc_spi); + if (sav == NULL) { + V_ahstat.ahs_notdb++; + DPRINTF(("%s: SA expired while in crypto\n", __func__)); + error = ENOBUFS; /*XXX*/ + goto bad; + } + + 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; + + /* 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; + } + + V_ahstat.ahs_noxform++; + DPRINTF(("%s: crypto error %d\n", __func__, crp->crp_etype)); + error = crp->crp_etype; + goto bad; + } else { + V_ahstat.ahs_hist[sav->alg_auth]++; + crypto_freereq(crp); /* No longer needed. */ + crp = NULL; + } + + /* Shouldn't happen... */ + if (m == NULL) { + V_ahstat.ahs_crypto++; + DPRINTF(("%s: bogus returned buffer from crypto\n", __func__)); + error = EINVAL; + goto bad; + } + + /* Figure out header size. */ + rplen = HDRSIZE(sav); + authsize = AUTHSIZE(sav); + + /* 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))); + V_ahstat.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); + } + + free(tc, M_XDATA), tc = NULL; /* No longer needed */ + + /* + * Header is now authenticated. + */ + m->m_flags |= M_AUTHIPHDR|M_AUTHIPDGM; + + /* + * Update replay sequence number, if appropriate. + */ + if (sav->replay) { + u_int32_t seq; + + m_copydata(m, skip + offsetof(struct newah, ah_seq), + sizeof (seq), (caddr_t) &seq); + if (ipsec_updatereplay(ntohl(seq), sav)) { + V_ahstat.ahs_replay++; + error = ENOBUFS; /*XXX as above*/ + goto bad; + } + } + + /* + * Remove the AH header and authenticator from the mbuf. + */ + 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))); + + V_ahstat.ahs_hdrops++; + goto bad; + } + + IPSEC_COMMON_INPUT_CB(m, sav, skip, protoff, mtag); + + KEY_FREESAV(&sav); + return error; +bad: + if (sav) + KEY_FREESAV(&sav); + if (m != NULL) + m_freem(m); + if (tc != NULL) + free(tc, M_XDATA); + if (crp != NULL) + crypto_freereq(crp); + return error; +} + +/* + * AH output routine, called by ipsec[46]_process_packet(). + */ +static int +ah_output( + struct mbuf *m, + struct ipsecrequest *isr, + struct mbuf **mp, + int skip, + int protoff) +{ + struct secasvar *sav; + struct auth_hash *ahx; + struct cryptodesc *crda; + struct tdb_crypto *tc; + struct mbuf *mi; + struct cryptop *crp; + u_int16_t iplen; + int error, rplen, authsize, maxpacketsize, roff; + u_int8_t prot; + struct newah *ah; + + sav = isr->sav; + IPSEC_ASSERT(sav != NULL, ("null SA")); + ahx = sav->tdb_authalgxform; + IPSEC_ASSERT(ahx != NULL, ("null authentication xform")); + + V_ahstat.ahs_output++; + + /* Figure out header size. */ + rplen = HDRSIZE(sav); + + /* Check for maximum packet size violations. */ + switch (sav->sah->saidx.dst.sa.sa_family) { +#ifdef INET + case AF_INET: + maxpacketsize = IP_MAXPACKET; + break; +#endif /* INET */ +#ifdef INET6 + case AF_INET6: + maxpacketsize = IPV6_MAXPACKET; + break; +#endif /* INET6 */ + default: + 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), + (u_long) ntohl(sav->spi))); + V_ahstat.ahs_nopf++; + error = EPFNOSUPPORT; + goto bad; + } + authsize = AUTHSIZE(sav); + 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), + (u_long) ntohl(sav->spi), + rplen + authsize + m->m_pkthdr.len, maxpacketsize)); + V_ahstat.ahs_toobig++; + error = EMSGSIZE; + goto bad; + } + + /* Update the counters. */ + V_ahstat.ahs_obytes += m->m_pkthdr.len - skip; + + 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), + (u_long) ntohl(sav->spi))); + V_ahstat.ahs_hdrops++; + error = ENOBUFS; + goto bad; + } + + /* Inject AH header. */ + mi = m_makespace(m, skip, rplen + authsize, &roff); + if (mi == NULL) { + DPRINTF(("%s: failed to inject %u byte AH header for SA " + "%s/%08lx\n", __func__, + rplen + authsize, + ipsec_address(&sav->sah->saidx.dst), + (u_long) ntohl(sav->spi))); + V_ahstat.ahs_hdrops++; /*XXX differs from openbsd */ + error = ENOBUFS; + goto bad; + } + + /* + * The AH header is guaranteed by m_makespace() to be in + * contiguous memory, at roff bytes offset into the returned mbuf. + */ + ah = (struct newah *)(mtod(mi, caddr_t) + roff); + + /* Initialize the AH header. */ + m_copydata(m, protoff, sizeof(u_int8_t), (caddr_t) &ah->ah_nxt); + ah->ah_len = (rplen + authsize - sizeof(struct ah)) / sizeof(u_int32_t); + ah->ah_reserve = 0; + ah->ah_spi = sav->spi; + + /* Zeroize authenticator. */ + m_copyback(m, skip + rplen, authsize, ipseczeroes); + + /* Insert packet replay counter, as requested. */ + if (sav->replay) { + if (sav->replay->count == ~0 && + (sav->flags & SADB_X_EXT_CYCSEQ) == 0) { + DPRINTF(("%s: replay counter wrapped for SA %s/%08lx\n", + __func__, + ipsec_address(&sav->sah->saidx.dst), + (u_long) ntohl(sav->spi))); + V_ahstat.ahs_wrap++; + error = EINVAL; + goto bad; + } +#ifdef REGRESSION + /* Emulate replay attack when ipsec_replay is TRUE. */ + if (!V_ipsec_replay) +#endif + sav->replay->count++; + ah->ah_seq = htonl(sav->replay->count); + } + + /* Get crypto descriptors. */ + crp = crypto_getreq(1); + if (crp == NULL) { + DPRINTF(("%s: failed to acquire crypto descriptors\n", + __func__)); + V_ahstat.ahs_crypto++; + error = ENOBUFS; + goto bad; + } + + crda = crp->crp_desc; + + crda->crd_skip = 0; + crda->crd_inject = skip + rplen; + crda->crd_len = m->m_pkthdr.len; + + /* Authentication operation. */ + crda->crd_alg = ahx->type; + crda->crd_key = sav->key_auth->key_data; + 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) { + crypto_freereq(crp); + DPRINTF(("%s: failed to allocate tdb_crypto\n", __func__)); + V_ahstat.ahs_crypto++; + error = ENOBUFS; + goto bad; + } + + /* Save the skipped portion of the packet. */ + m_copydata(m, 0, skip, (caddr_t) (tc + 1)); + + /* + * Fix IP header length on the header used for + * authentication. We don't need to fix the original + * header length as it will be fixed by our caller. + */ + switch (sav->sah->saidx.dst.sa.sa_family) { +#ifdef INET + case AF_INET: + bcopy(((caddr_t)(tc + 1)) + + offsetof(struct ip, ip_len), + (caddr_t) &iplen, sizeof(u_int16_t)); + iplen = htons(ntohs(iplen) + rplen + authsize); + m_copyback(m, offsetof(struct ip, ip_len), + sizeof(u_int16_t), (caddr_t) &iplen); + break; +#endif /* INET */ + +#ifdef INET6 + case AF_INET6: + bcopy(((caddr_t)(tc + 1)) + + offsetof(struct ip6_hdr, ip6_plen), + (caddr_t) &iplen, sizeof(u_int16_t)); + iplen = htons(ntohs(iplen) + rplen + authsize); + m_copyback(m, offsetof(struct ip6_hdr, ip6_plen), + sizeof(u_int16_t), (caddr_t) &iplen); + break; +#endif /* INET6 */ + } + + /* Fix the Next Header field in saved header. */ + ((u_int8_t *) (tc + 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); + + /* "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); + crypto_freereq(crp); + goto bad; + } + + /* 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 = ah_output_cb; + crp->crp_sid = sav->tdb_cryptoid; + crp->crp_opaque = (caddr_t) tc; + + /* These are passed as-is to the callback. */ + tc->tc_isr = isr; + 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; + + return crypto_dispatch(crp); +bad: + if (m) + m_freem(m); + return (error); +} + +/* + * AH output callback from the crypto driver. + */ +static int +ah_output_cb(struct cryptop *crp) +{ + int skip, protoff, error; + struct tdb_crypto *tc; + struct ipsecrequest *isr; + struct secasvar *sav; + struct mbuf *m; + caddr_t ptr; + int err; + + 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 = KEY_ALLOCSA(&tc->tc_dst, tc->tc_proto, tc->tc_spi); + if (sav == NULL) { + V_ahstat.ahs_notdb++; + DPRINTF(("%s: SA expired while in crypto\n", __func__)); + error = ENOBUFS; /*XXX*/ + goto bad; + } + IPSEC_ASSERT(isr->sav == sav, ("SA changed\n")); + + /* Check for crypto errors. */ + if (crp->crp_etype) { + if (sav->tdb_cryptoid != 0) + sav->tdb_cryptoid = crp->crp_sid; + + if (crp->crp_etype == EAGAIN) { + KEY_FREESAV(&sav); + IPSECREQUEST_UNLOCK(isr); + error = crypto_dispatch(crp); + return error; + } + + V_ahstat.ahs_noxform++; + DPRINTF(("%s: crypto error %d\n", __func__, crp->crp_etype)); + error = crp->crp_etype; + goto bad; + } + + /* Shouldn't happen... */ + if (m == NULL) { + V_ahstat.ahs_crypto++; + DPRINTF(("%s: bogus returned buffer from crypto\n", __func__)); + error = EINVAL; + goto bad; + } + V_ahstat.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); + crypto_freereq(crp); + +#ifdef REGRESSION + /* Emulate man-in-the-middle attack when ipsec_integrity is TRUE. */ + if (V_ipsec_integrity) { + int alen; + + /* + * Corrupt HMAC if we want to test integrity verification of + * the other side. + */ + alen = AUTHSIZE(sav); + m_copyback(m, m->m_pkthdr.len - alen, alen, ipseczeroes); + } +#endif + + /* NB: m is reclaimed by ipsec_process_done. */ + err = ipsec_process_done(m, isr); + KEY_FREESAV(&sav); + IPSECREQUEST_UNLOCK(isr); + return err; +bad: + if (sav) + KEY_FREESAV(&sav); + IPSECREQUEST_UNLOCK(isr); + if (m) + m_freem(m); + free(tc, M_XDATA); + crypto_freereq(crp); + return error; +} + +static struct xformsw ah_xformsw = { + XF_AH, XFT_AUTH, "IPsec AH", + ah_init, ah_zeroize, ah_input, 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); diff --git a/freebsd/sys/netipsec/xform_esp.c b/freebsd/sys/netipsec/xform_esp.c new file mode 100644 index 00000000..3a550d17 --- /dev/null +++ b/freebsd/sys/netipsec/xform_esp.c @@ -0,0 +1,1005 @@ +#include <freebsd/machine/rtems-bsd-config.h> + +/* $FreeBSD$ */ +/* $OpenBSD: ip_esp.c,v 1.69 2001/06/26 06:18:59 angelos 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. + */ +#include <freebsd/local/opt_inet.h> +#include <freebsd/local/opt_inet6.h> + +#include <freebsd/sys/param.h> +#include <freebsd/sys/systm.h> +#include <freebsd/sys/mbuf.h> +#include <freebsd/sys/socket.h> +#include <freebsd/sys/syslog.h> +#include <freebsd/sys/kernel.h> +#include <freebsd/sys/random.h> +#include <freebsd/sys/sysctl.h> + +#include <freebsd/net/if.h> +#include <freebsd/net/vnet.h> + +#include <freebsd/netinet/in.h> +#include <freebsd/netinet/in_systm.h> +#include <freebsd/netinet/ip.h> +#include <freebsd/netinet/ip_ecn.h> +#include <freebsd/netinet/ip6.h> + +#include <freebsd/net/route.h> +#include <freebsd/netipsec/ipsec.h> +#include <freebsd/netipsec/ah.h> +#include <freebsd/netipsec/ah_var.h> +#include <freebsd/netipsec/esp.h> +#include <freebsd/netipsec/esp_var.h> +#include <freebsd/netipsec/xform.h> + +#ifdef INET6 +#include <freebsd/netinet6/ip6_var.h> +#include <freebsd/netipsec/ipsec6.h> +#include <freebsd/netinet6/ip6_ecn.h> +#endif + +#include <freebsd/netipsec/key.h> +#include <freebsd/netipsec/key_debug.h> + +#include <freebsd/opencrypto/cryptodev.h> +#include <freebsd/opencrypto/xform.h> + +VNET_DEFINE(int, esp_enable) = 1; +VNET_DEFINE(struct espstat, 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, ""); + +static VNET_DEFINE(int, esp_max_ivlen); /* max iv length over all algorithms */ +#define V_esp_max_ivlen VNET(esp_max_ivlen) + +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) +{ + size_t size; + + if (sav != NULL) { + /*XXX not right for null algorithm--does it matter??*/ + IPSEC_ASSERT(sav->tdb_encalgxform != NULL, + ("SA with null xform")); + if (sav->flags & SADB_X_EXT_OLD) + size = sizeof (struct esp); + else + size = sizeof (struct newesp); + size += sav->tdb_encalgxform->blocksize + 9; + /*XXX need alg check???*/ + if (sav->tdb_authalgxform != NULL && sav->replay) + size += ah_hdrsiz(sav); + } else { + /* + * base header size + * + max iv length for CBC mode + * + max pad length + * + sizeof (pad length field) + * + sizeof (next header field) + * + max icv supported. + */ + size = sizeof (struct newesp) + V_esp_max_ivlen + 9 + 16; + } + return size; +} + +/* + * esp_init() is called when an SPI is being set up. + */ +static int +esp_init(struct secasvar *sav, struct xformsw *xsp) +{ + struct enc_xform *txform; + struct cryptoini cria, crie; + int keylen; + int error; + + txform = esp_algorithm_lookup(sav->alg_enc); + if (txform == NULL) { + DPRINTF(("%s: unsupported encryption algorithm %d\n", + __func__, sav->alg_enc)); + return EINVAL; + } + if (sav->key_enc == NULL) { + DPRINTF(("%s: no encoding key for %s algorithm\n", + __func__, txform->name)); + return EINVAL; + } + 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); + 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__, + keylen, txform->minkey, txform->maxkey, + txform->name)); + 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*/ + + /* + * Setup AH-related state. + */ + if (sav->alg_auth != 0) { + error = ah_init0(sav, xsp, &cria); + if (error) + return error; + } + + /* NB: override anything set in ah_init0 */ + sav->tdb_xform = xsp; + sav->tdb_encalgxform = txform; + + /* Initialize crypto session. */ + 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 ? */ + + if (sav->tdb_authalgxform && sav->tdb_encalgxform) { + /* init both auth & enc */ + crie.cri_next = &cria; + error = crypto_newsession(&sav->tdb_cryptoid, + &crie, V_crypto_support); + } else if (sav->tdb_encalgxform) { + error = crypto_newsession(&sav->tdb_cryptoid, + &crie, V_crypto_support); + } else if (sav->tdb_authalgxform) { + error = crypto_newsession(&sav->tdb_cryptoid, + &cria, V_crypto_support); + } else { + /* XXX cannot happen? */ + DPRINTF(("%s: no encoding OR authentication xform!\n", + __func__)); + error = EINVAL; + } + return error; +} + +/* + * Paranoia. + */ +static int +esp_zeroize(struct secasvar *sav) +{ + /* NB: ah_zerorize free's the crypto session state */ + int error = ah_zeroize(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; +} + +/* + * ESP input processing, called (eventually) through the protocol switch. + */ +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; + + struct cryptodesc *crde; + struct cryptop *crp; + + IPSEC_ASSERT(sav != NULL, ("null SA")); + IPSEC_ASSERT(sav->tdb_encalgxform != NULL, ("null encoding xform")); + IPSEC_ASSERT((skip&3) == 0 && (m->m_pkthdr.len&3) == 0, + ("misaligned packet, skip %u pkt len %u", + skip, m->m_pkthdr.len)); + + /* 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 */ + if (sav->flags & SADB_X_EXT_OLD) + hlen = sizeof (struct esp) + sav->ivlen; + else + hlen = sizeof (struct newesp) + sav->ivlen; + /* Authenticator hash size */ + alen = esph ? AH_HMAC_HASHLEN : 0; + + /* + * Verify payload length is multiple of encryption algorithm + * block size. + * + * NB: This works for the null algorithm because the blocksize + * is 4 and all packets must be 4-byte aligned regardless + * of the algorithm. + */ + 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))); + V_espstat.esps_badilen++; + m_freem(m); + return EINVAL; + } + + /* + * 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*/ + V_espstat.esps_replay++; + m_freem(m); + return ENOBUFS; /*XXX*/ + } + + /* Update the counters */ + V_espstat.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) { + DPRINTF(("%s: failed to acquire crypto descriptors\n", + __func__)); + V_espstat.esps_crypto++; + m_freem(m); + return ENOBUFS; + } + + /* 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__)); + V_espstat.esps_crypto++; + m_freem(m); + return ENOBUFS; + } + + tc->tc_ptr = (caddr_t) mtag; + + if (esph) { + 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); + 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)); + + /* Chain authentication request */ + crde = crda->crd_next; + } else { + crde = crp->crp_desc; + } + + /* 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_input_cb; + crp->crp_sid = sav->tdb_cryptoid; + crp->crp_opaque = (caddr_t) tc; + + /* 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; + + /* 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 ? */ + } + + if (mtag == NULL) + return crypto_dispatch(crp); + else + return esp_input_cb(crp); +} + +#ifdef INET6 +#define IPSEC_COMMON_INPUT_CB(m, sav, skip, protoff, mtag) do { \ + if (saidx->dst.sa.sa_family == AF_INET6) { \ + error = ipsec6_common_input_cb(m, sav, skip, protoff, mtag); \ + } else { \ + error = ipsec4_common_input_cb(m, sav, skip, protoff, mtag); \ + } \ +} while (0) +#else +#define IPSEC_COMMON_INPUT_CB(m, sav, skip, protoff, mtag) \ + (error = ipsec4_common_input_cb(m, sav, skip, protoff, mtag)) +#endif + +/* + * ESP input callback from the crypto driver. + */ +static int +esp_input_cb(struct cryptop *crp) +{ + u_int8_t lastthree[3], aalg[AH_HMAC_HASHLEN]; + int hlen, skip, protoff, error; + struct mbuf *m; + struct cryptodesc *crd; + struct auth_hash *esph; + struct enc_xform *espx; + struct tdb_crypto *tc; + struct m_tag *mtag; + struct secasvar *sav; + struct secasindex *saidx; + caddr_t ptr; + + 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 = KEY_ALLOCSA(&tc->tc_dst, tc->tc_proto, tc->tc_spi); + if (sav == NULL) { + V_espstat.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; + } + + 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) { + KEY_FREESAV(&sav); + error = crypto_dispatch(crp); + return error; + } + + V_espstat.esps_noxform++; + DPRINTF(("%s: crypto error %d\n", __func__, crp->crp_etype)); + error = crp->crp_etype; + goto bad; + } + + /* Shouldn't happen... */ + if (m == NULL) { + V_espstat.esps_crypto++; + DPRINTF(("%s: bogus returned buffer from crypto\n", __func__)); + error = EINVAL; + goto bad; + } + V_espstat.esps_hist[sav->alg_enc]++; + + /* If authentication was performed, check now. */ + if (esph != NULL) { + /* + * If we have a tag, it means an IPsec-aware NIC did + * the verification for us. Otherwise we need to + * check the authentication calculation. + */ + V_ahstat.ahs_hist[sav->alg_auth]++; + if (mtag == NULL) { + /* Copy the authenticator from the packet */ + m_copydata(m, m->m_pkthdr.len - AH_HMAC_HASHLEN, + AH_HMAC_HASHLEN, aalg); + + ptr = (caddr_t) (tc + 1); + + /* Verify authenticator */ + if (bcmp(ptr, aalg, AH_HMAC_HASHLEN) != 0) { + DPRINTF(("%s: " + "authentication hash mismatch for packet in SA %s/%08lx\n", + __func__, + ipsec_address(&saidx->dst), + (u_long) ntohl(sav->spi))); + V_espstat.esps_badauth++; + error = EACCES; + goto bad; + } + } + + /* Remove trailing authenticator */ + m_adj(m, -AH_HMAC_HASHLEN); + } + + /* Release the crypto descriptors */ + free(tc, M_XDATA), tc = NULL; + crypto_freereq(crp), crp = NULL; + + /* + * Packet is now decrypted. + */ + m->m_flags |= M_DECRYPTED; + + /* + * Update replay sequence number, if appropriate. + */ + if (sav->replay) { + u_int32_t seq; + + m_copydata(m, skip + offsetof(struct newesp, esp_seq), + sizeof (seq), (caddr_t) &seq); + if (ipsec_updatereplay(ntohl(seq), sav)) { + DPRINTF(("%s: packet replay check for %s\n", __func__, + ipsec_logsastr(sav))); + V_espstat.esps_replay++; + error = ENOBUFS; + goto bad; + } + } + + /* Determine the ESP header length */ + if (sav->flags & SADB_X_EXT_OLD) + hlen = sizeof (struct esp) + sav->ivlen; + else + hlen = sizeof (struct newesp) + sav->ivlen; + + /* Remove the ESP header and IV from the mbuf. */ + error = m_striphdr(m, skip, hlen); + if (error) { + V_espstat.esps_hdrops++; + DPRINTF(("%s: bad mbuf chain, SA %s/%08lx\n", __func__, + ipsec_address(&sav->sah->saidx.dst), + (u_long) ntohl(sav->spi))); + goto bad; + } + + /* Save the last three bytes of decrypted data */ + m_copydata(m, m->m_pkthdr.len - 3, 3, lastthree); + + /* Verify pad length */ + if (lastthree[1] + 2 > m->m_pkthdr.len - skip) { + V_espstat.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))); + error = EINVAL; + goto bad; + } + + /* Verify correct decryption by checking the last padding bytes */ + if ((sav->flags & SADB_X_EXT_PMASK) != SADB_X_EXT_PRAND) { + if (lastthree[1] != lastthree[0] && lastthree[1] != 0) { + V_espstat.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))); + error = EINVAL; + goto bad; + } + } + + /* Trim the mbuf chain to remove trailing authenticator and padding */ + m_adj(m, -(lastthree[1] + 2)); + + /* Restore the Next Protocol field */ + m_copyback(m, protoff, sizeof (u_int8_t), lastthree + 2); + + IPSEC_COMMON_INPUT_CB(m, sav, skip, protoff, mtag); + + KEY_FREESAV(&sav); + return error; +bad: + if (sav) + KEY_FREESAV(&sav); + if (m != NULL) + m_freem(m); + if (tc != NULL) + free(tc, M_XDATA); + if (crp != NULL) + crypto_freereq(crp); + return error; +} + +/* + * ESP output routine, called by ipsec[46]_process_packet(). + */ +static int +esp_output( + struct mbuf *m, + struct ipsecrequest *isr, + struct mbuf **mp, + 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; + struct secasindex *saidx; + unsigned char *pad; + u_int8_t prot; + int error, maxpacketsize; + + 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; + IPSEC_ASSERT(espx != NULL, ("null encoding xform")); + + if (sav->flags & SADB_X_EXT_OLD) + hlen = sizeof (struct esp) + sav->ivlen; + else + hlen = sizeof (struct newesp) + sav->ivlen; + + 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. + */ + blks = espx->blocksize; /* IV blocksize */ + + /* XXX clamp padding length a la KAME??? */ + padding = ((blks - ((rlen + 2) % blks)) % blks) + 2; + plen = rlen + padding; /* Padded payload length. */ + + if (esph) + alen = AH_HMAC_HASHLEN; + else + alen = 0; + + V_espstat.esps_output++; + + saidx = &sav->sah->saidx; + /* Check for maximum packet size violations. */ + switch (saidx->dst.sa.sa_family) { +#ifdef INET + case AF_INET: + maxpacketsize = IP_MAXPACKET; + break; +#endif /* INET */ +#ifdef INET6 + case AF_INET6: + maxpacketsize = IPV6_MAXPACKET; + break; +#endif /* INET6 */ + 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))); + V_espstat.esps_nopf++; + error = EPFNOSUPPORT; + goto bad; + } + 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), + skip + hlen + rlen + padding + alen, maxpacketsize)); + V_espstat.esps_toobig++; + error = EMSGSIZE; + goto bad; + } + + /* Update the counters. */ + V_espstat.esps_obytes += m->m_pkthdr.len - skip; + + 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))); + V_espstat.esps_hdrops++; + error = ENOBUFS; + goto bad; + } + + /* Inject ESP header. */ + 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))); + V_espstat.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)); + if (sav->replay) { + u_int32_t replay; + +#ifdef REGRESSION + /* Emulate replay attack when ipsec_replay is TRUE. */ + if (!V_ipsec_replay) +#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)); + } + + /* + * Add padding -- better to do it ourselves than use the crypto engine, + * although if/when we support compression, we'd have to do that. + */ + 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))); + m = NULL; /* NB: free'd by m_pad */ + error = ENOBUFS; + goto bad; + } + + /* + * Add padding: random, zero, or self-describing. + * XXX catch unexpected setting + */ + switch (sav->flags & SADB_X_EXT_PMASK) { + case SADB_X_EXT_PRAND: + (void) read_random(pad, padding - 2); + break; + case SADB_X_EXT_PZERO: + bzero(pad, padding - 2); + break; + case SADB_X_EXT_PSEQ: + for (i = 0; i < padding - 2; i++) + pad[i] = i+1; + break; + } + + /* Fix padding length and Next Protocol in padding itself. */ + pad[padding - 2] = padding - 2; + m_copydata(m, protoff, sizeof(u_int8_t), pad + padding - 1); + + /* Fix Next Protocol in IPv4/IPv6 header. */ + prot = IPPROTO_ESP; + m_copyback(m, protoff, sizeof(u_int8_t), (u_char *) &prot); + + /* Get crypto descriptors. */ + crp = crypto_getreq(esph && espx ? 2 : 1); + if (crp == NULL) { + DPRINTF(("%s: failed to acquire crypto descriptors\n", + __func__)); + V_espstat.esps_crypto++; + error = ENOBUFS; + 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) { + crypto_freereq(crp); + DPRINTF(("%s: failed to allocate tdb_crypto\n", __func__)); + V_espstat.esps_crypto++; + error = ENOBUFS; + goto bad; + } + + /* Callback parameters */ + tc->tc_isr = isr; + tc->tc_spi = sav->spi; + tc->tc_dst = saidx->dst; + tc->tc_proto = saidx->proto; + + /* 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; + + if (esph) { + /* Authentication descriptor. */ + crda->crd_skip = skip; + 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); +bad: + if (m) + 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 secasvar *sav; + struct mbuf *m; + int err, error; + + tc = (struct tdb_crypto *) crp->crp_opaque; + IPSEC_ASSERT(tc != NULL, ("null opaque data area!")); + m = (struct mbuf *) crp->crp_buf; + + isr = tc->tc_isr; + IPSECREQUEST_LOCK(isr); + sav = KEY_ALLOCSA(&tc->tc_dst, tc->tc_proto, tc->tc_spi); + if (sav == NULL) { + V_espstat.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; + } + IPSEC_ASSERT(isr->sav == sav, + ("SA changed was %p now %p\n", isr->sav, sav)); + + /* 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) { + KEY_FREESAV(&sav); + IPSECREQUEST_UNLOCK(isr); + error = crypto_dispatch(crp); + return error; + } + + V_espstat.esps_noxform++; + DPRINTF(("%s: crypto error %d\n", __func__, crp->crp_etype)); + error = crp->crp_etype; + goto bad; + } + + /* Shouldn't happen... */ + if (m == NULL) { + V_espstat.esps_crypto++; + DPRINTF(("%s: bogus returned buffer from crypto\n", __func__)); + error = EINVAL; + goto bad; + } + V_espstat.esps_hist[sav->alg_enc]++; + if (sav->tdb_authalgxform != NULL) + V_ahstat.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_HASHLEN]; + struct auth_hash *esph; + + /* + * Corrupt HMAC if we want to test integrity verification of + * the other side. + */ + esph = sav->tdb_authalgxform; + if (esph != NULL) { + m_copyback(m, m->m_pkthdr.len - AH_HMAC_HASHLEN, + AH_HMAC_HASHLEN, ipseczeroes); + } + } +#endif + + /* NB: m is reclaimed by ipsec_process_done. */ + err = ipsec_process_done(m, isr); + KEY_FREESAV(&sav); + IPSECREQUEST_UNLOCK(isr); + return err; +bad: + if (sav) + KEY_FREESAV(&sav); + IPSECREQUEST_UNLOCK(isr); + if (m) + m_freem(m); + free(tc, M_XDATA); + crypto_freereq(crp); + return error; +} + +static struct xformsw esp_xformsw = { + XF_ESP, XFT_CONF|XFT_AUTH, "IPsec ESP", + esp_init, esp_zeroize, esp_input, + 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); diff --git a/freebsd/sys/netipsec/xform_ipcomp.c b/freebsd/sys/netipsec/xform_ipcomp.c new file mode 100644 index 00000000..a4a65953 --- /dev/null +++ b/freebsd/sys/netipsec/xform_ipcomp.c @@ -0,0 +1,625 @@ +#include <freebsd/machine/rtems-bsd-config.h> + +/* $FreeBSD$ */ +/* $OpenBSD: ip_ipcomp.c,v 1.1 2001/07/05 12:08:52 jjbg Exp $ */ + +/*- + * Copyright (c) 2001 Jean-Jacques Bernard-Gundol (jj@wabbitt.org) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * 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. + */ + +/* IP payload compression protocol (IPComp), see RFC 2393 */ +#include <freebsd/local/opt_inet.h> +#include <freebsd/local/opt_inet6.h> + +#include <freebsd/sys/param.h> +#include <freebsd/sys/systm.h> +#include <freebsd/sys/mbuf.h> +#include <freebsd/sys/lock.h> +#include <freebsd/sys/mutex.h> +#include <freebsd/sys/socket.h> +#include <freebsd/sys/kernel.h> +#include <freebsd/sys/protosw.h> +#include <freebsd/sys/sysctl.h> + +#include <freebsd/netinet/in.h> +#include <freebsd/netinet/in_systm.h> +#include <freebsd/netinet/ip.h> +#include <freebsd/netinet/ip_var.h> + +#include <freebsd/net/route.h> +#include <freebsd/net/vnet.h> + +#include <freebsd/netipsec/ipsec.h> +#include <freebsd/netipsec/xform.h> + +#ifdef INET6 +#include <freebsd/netinet/ip6.h> +#include <freebsd/netipsec/ipsec6.h> +#endif + +#include <freebsd/netipsec/ipcomp.h> +#include <freebsd/netipsec/ipcomp_var.h> + +#include <freebsd/netipsec/key.h> +#include <freebsd/netipsec/key_debug.h> + +#include <freebsd/opencrypto/cryptodev.h> +#include <freebsd/opencrypto/deflate.h> +#include <freebsd/opencrypto/xform.h> + +VNET_DEFINE(int, ipcomp_enable) = 1; +VNET_DEFINE(struct ipcompstat, ipcompstat); + +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, ""); + +static int ipcomp_input_cb(struct cryptop *crp); +static int ipcomp_output_cb(struct cryptop *crp); + +struct comp_algo * +ipcomp_algorithm_lookup(int alg) +{ + if (alg >= IPCOMP_ALG_MAX) + return NULL; + switch (alg) { + case SADB_X_CALG_DEFLATE: + return &comp_algo_deflate; + } + return NULL; +} + +/* + * ipcomp_init() is called when an CPI is being set up. + */ +static int +ipcomp_init(struct secasvar *sav, struct xformsw *xsp) +{ + 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); + if (tcomp == NULL) { + DPRINTF(("%s: unsupported compression algorithm %d\n", __func__, + sav->alg_comp)); + return EINVAL; + } + sav->alg_comp = sav->alg_enc; /* set for doing histogram */ + sav->tdb_xform = xsp; + sav->tdb_compalgxform = tcomp; + + /* Initialize crypto session */ + bzero(&cric, sizeof (cric)); + cric.cri_alg = sav->tdb_compalgxform->type; + + return crypto_newsession(&sav->tdb_cryptoid, &cric, V_crypto_support); +} + +/* + * ipcomp_zeroize() used when IPCA is deleted + */ +static int +ipcomp_zeroize(struct secasvar *sav) +{ + int err; + + err = crypto_freesession(sav->tdb_cryptoid); + sav->tdb_cryptoid = 0; + return err; +} + +/* + * ipcomp_input() gets called to uncompress an input packet + */ +static int +ipcomp_input(struct mbuf *m, struct secasvar *sav, int skip, int protoff) +{ + struct tdb_crypto *tc; + struct cryptodesc *crdc; + struct cryptop *crp; + int hlen = IPCOMP_HLENGTH; + + /* Get crypto descriptors */ + crp = crypto_getreq(1); + if (crp == NULL) { + m_freem(m); + DPRINTF(("%s: no crypto descriptors\n", __func__)); + V_ipcompstat.ipcomps_crypto++; + 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__)); + V_ipcompstat.ipcomps_crypto++; + return ENOBUFS; + } + crdc = crp->crp_desc; + + crdc->crd_skip = skip + hlen; + 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; + + /* 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; + + return crypto_dispatch(crp); +} + +#ifdef INET6 +#define IPSEC_COMMON_INPUT_CB(m, sav, skip, protoff, mtag) do { \ + if (saidx->dst.sa.sa_family == AF_INET6) { \ + error = ipsec6_common_input_cb(m, sav, skip, protoff, mtag); \ + } else { \ + error = ipsec4_common_input_cb(m, sav, skip, protoff, mtag); \ + } \ +} while (0) +#else +#define IPSEC_COMMON_INPUT_CB(m, sav, skip, protoff, mtag) \ + (error = ipsec4_common_input_cb(m, sav, skip, protoff, mtag)) +#endif + +/* + * IPComp input callback from the crypto driver. + */ +static int +ipcomp_input_cb(struct cryptop *crp) +{ + struct cryptodesc *crd; + struct tdb_crypto *tc; + int skip, protoff; + struct mtag *mtag; + struct mbuf *m; + struct secasvar *sav; + struct secasindex *saidx; + int hlen = IPCOMP_HLENGTH, error, clen; + u_int8_t nproto; + caddr_t addr; + + 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 = KEY_ALLOCSA(&tc->tc_dst, tc->tc_proto, tc->tc_spi); + if (sav == NULL) { + V_ipcompstat.ipcomps_notdb++; + DPRINTF(("%s: SA expired while in crypto\n", __func__)); + error = ENOBUFS; /*XXX*/ + goto bad; + } + + 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)); + + /* 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) { + KEY_FREESAV(&sav); + return crypto_dispatch(crp); + } + V_ipcompstat.ipcomps_noxform++; + DPRINTF(("%s: crypto error %d\n", __func__, crp->crp_etype)); + error = crp->crp_etype; + goto bad; + } + /* Shouldn't happen... */ + if (m == NULL) { + V_ipcompstat.ipcomps_crypto++; + DPRINTF(("%s: null mbuf returned from crypto\n", __func__)); + error = EINVAL; + goto bad; + } + V_ipcompstat.ipcomps_hist[sav->alg_comp]++; + + clen = crp->crp_olen; /* Length of data after processing */ + + /* Release the crypto descriptors */ + free(tc, M_XDATA), tc = 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) { + V_ipcompstat.ipcomps_hdrops++; /*XXX*/ + DPRINTF(("%s: m_pullup failed\n", __func__)); + error = EINVAL; /*XXX*/ + goto bad; + } + + /* Keep the next protocol field */ + addr = (caddr_t) mtod(m, struct ip *) + skip; + nproto = ((struct ipcomp *) addr)->comp_nxt; + + /* Remove the IPCOMP header */ + error = m_striphdr(m, skip, hlen); + if (error) { + V_ipcompstat.ipcomps_hdrops++; + DPRINTF(("%s: bad mbuf chain, IPCA %s/%08lx\n", __func__, + ipsec_address(&sav->sah->saidx.dst), + (u_long) ntohl(sav->spi))); + goto bad; + } + + /* Restore the Next Protocol field */ + m_copyback(m, protoff, sizeof (u_int8_t), (u_int8_t *) &nproto); + + IPSEC_COMMON_INPUT_CB(m, sav, skip, protoff, NULL); + + KEY_FREESAV(&sav); + return error; +bad: + if (sav) + KEY_FREESAV(&sav); + if (m) + m_freem(m); + if (tc != NULL) + free(tc, M_XDATA); + if (crp) + crypto_freereq(crp); + return error; +} + +/* + * IPComp output routine, called by ipsec[46]_process_packet() + */ +static int +ipcomp_output( + struct mbuf *m, + struct ipsecrequest *isr, + struct mbuf **mp, + int skip, + int protoff +) +{ + struct secasvar *sav; + struct comp_algo *ipcompx; + int error, ralen, maxpacketsize; + struct cryptodesc *crdc; + struct cryptop *crp; + struct tdb_crypto *tc; + + sav = isr->sav; + IPSEC_ASSERT(sav != NULL, ("null SA")); + ipcompx = sav->tdb_compalgxform; + IPSEC_ASSERT(ipcompx != NULL, ("null compression xform")); + + /* + * Do not touch the packet in case our payload to compress + * is lower than the minimal threshold of the compression + * alogrithm. We will just send out the data uncompressed. + * See RFC 3173, 2.2. Non-Expansion Policy. + */ + if (m->m_pkthdr.len <= ipcompx->minlen) { + V_ipcompstat.ipcomps_threshold++; + return ipsec_process_done(m, isr); + } + + ralen = m->m_pkthdr.len - skip; /* Raw payload length before comp. */ + V_ipcompstat.ipcomps_output++; + + /* Check for maximum packet size violations. */ + switch (sav->sah->saidx.dst.sa.sa_family) { +#ifdef INET + case AF_INET: + maxpacketsize = IP_MAXPACKET; + break; +#endif /* INET */ +#ifdef INET6 + case AF_INET6: + maxpacketsize = IPV6_MAXPACKET; + break; +#endif /* INET6 */ + default: + V_ipcompstat.ipcomps_nopf++; + 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))); + error = EPFNOSUPPORT; + goto bad; + } + if (ralen + skip + IPCOMP_HLENGTH > maxpacketsize) { + V_ipcompstat.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), + (u_long) ntohl(sav->spi), + ralen + skip + IPCOMP_HLENGTH, maxpacketsize)); + error = EMSGSIZE; + goto bad; + } + + /* Update the counters */ + V_ipcompstat.ipcomps_obytes += m->m_pkthdr.len - skip; + + m = m_unshare(m, M_NOWAIT); + if (m == NULL) { + V_ipcompstat.ipcomps_hdrops++; + DPRINTF(("%s: cannot clone mbuf chain, IPCA %s/%08lx\n", + __func__, ipsec_address(&sav->sah->saidx.dst), + (u_long) ntohl(sav->spi))); + error = ENOBUFS; + goto bad; + } + + /* Ok now, we can pass to the crypto processing. */ + + /* Get crypto descriptors */ + crp = crypto_getreq(1); + if (crp == NULL) { + V_ipcompstat.ipcomps_crypto++; + DPRINTF(("%s: failed to acquire crypto descriptor\n",__func__)); + error = ENOBUFS; + goto bad; + } + crdc = crp->crp_desc; + + /* Compression descriptor */ + crdc->crd_skip = skip; + crdc->crd_len = ralen; + crdc->crd_flags = CRD_F_COMP; + crdc->crd_inject = skip; + + /* Compression operation */ + 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) { + V_ipcompstat.ipcomps_crypto++; + DPRINTF(("%s: failed to allocate tdb_crypto\n", __func__)); + crypto_freereq(crp); + error = ENOBUFS; + goto bad; + } + + tc->tc_isr = isr; + 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; + + /* 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; + + return crypto_dispatch(crp); +bad: + if (m) + m_freem(m); + return (error); +} + +/* + * IPComp output callback from the crypto driver. + */ +static int +ipcomp_output_cb(struct cryptop *crp) +{ + struct tdb_crypto *tc; + struct ipsecrequest *isr; + struct secasvar *sav; + struct mbuf *m; + int error, skip; + + 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 = KEY_ALLOCSA(&tc->tc_dst, tc->tc_proto, tc->tc_spi); + if (sav == NULL) { + V_ipcompstat.ipcomps_notdb++; + DPRINTF(("%s: SA expired while in crypto\n", __func__)); + error = ENOBUFS; /*XXX*/ + goto bad; + } + IPSEC_ASSERT(isr->sav == sav, ("SA changed\n")); + + /* 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) { + KEY_FREESAV(&sav); + IPSECREQUEST_UNLOCK(isr); + return crypto_dispatch(crp); + } + V_ipcompstat.ipcomps_noxform++; + DPRINTF(("%s: crypto error %d\n", __func__, crp->crp_etype)); + error = crp->crp_etype; + goto bad; + } + /* Shouldn't happen... */ + if (m == NULL) { + V_ipcompstat.ipcomps_crypto++; + DPRINTF(("%s: bogus return buffer from crypto\n", __func__)); + error = EINVAL; + goto bad; + } + V_ipcompstat.ipcomps_hist[sav->alg_comp]++; + + if (crp->crp_ilen - skip > crp->crp_olen) { + struct mbuf *mo; + struct ipcomp *ipcomp; + int roff; + uint8_t prot; + + /* Compression helped, inject IPCOMP header. */ + mo = m_makespace(m, skip, IPCOMP_HLENGTH, &roff); + if (mo == NULL) { + V_ipcompstat.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))); + error = ENOBUFS; + goto bad; + } + ipcomp = (struct ipcomp *)(mtod(mo, caddr_t) + roff); + + /* Initialize the IPCOMP header */ + /* XXX alignment always correct? */ + switch (sav->sah->saidx.dst.sa.sa_family) { +#ifdef INET + case AF_INET: + ipcomp->comp_nxt = mtod(m, struct ip *)->ip_p; + break; +#endif /* INET */ +#ifdef INET6 + case AF_INET6: + ipcomp->comp_nxt = mtod(m, struct ip6_hdr *)->ip6_nxt; + break; +#endif + } + ipcomp->comp_flags = 0; + ipcomp->comp_cpi = htons((u_int16_t) ntohl(sav->spi)); + + /* Fix Next Protocol in IPv4/IPv6 header */ + prot = IPPROTO_IPCOMP; + m_copyback(m, tc->tc_protoff, sizeof(u_int8_t), + (u_char *)&prot); + + /* Adjust the length in the IP header */ + switch (sav->sah->saidx.dst.sa.sa_family) { +#ifdef INET + case AF_INET: + mtod(m, struct ip *)->ip_len = htons(m->m_pkthdr.len); + break; +#endif /* INET */ +#ifdef INET6 + case AF_INET6: + mtod(m, struct ip6_hdr *)->ip6_plen = + htons(m->m_pkthdr.len) - sizeof(struct ip6_hdr); + break; +#endif /* INET6 */ + default: + V_ipcompstat.ipcomps_nopf++; + 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))); + error = EPFNOSUPPORT; + goto bad; + } + } else { + /* Compression was useless, we have lost time. */ + V_ipcompstat.ipcomps_uncompr++; + DPRINTF(("%s: compressions was useless %d - %d <= %d\n", + __func__, crp->crp_ilen, skip, crp->crp_olen)); + /* XXX remember state to not compress the next couple + * of packets, RFC 3173, 2.2. Non-Expansion Policy */ + } + + /* Release the crypto descriptor */ + free(tc, 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; +bad: + if (sav) + KEY_FREESAV(&sav); + IPSECREQUEST_UNLOCK(isr); + if (m) + m_freem(m); + free(tc, M_XDATA); + crypto_freereq(crp); + return error; +} + +static struct xformsw ipcomp_xformsw = { + XF_IPCOMP, XFT_COMP, "IPcomp", + ipcomp_init, ipcomp_zeroize, ipcomp_input, + ipcomp_output +}; + +static void +ipcomp_attach(void) +{ + + xform_register(&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) +{ + + V_ipcompstat.version = IPCOMPSTAT_VERSION; +} + +VNET_SYSINIT(vnet_ipcomp_xform_init, SI_SUB_PROTO_DOMAIN, SI_ORDER_MIDDLE, + vnet_ipcomp_attach, NULL); diff --git a/freebsd/sys/netipsec/xform_ipip.c b/freebsd/sys/netipsec/xform_ipip.c new file mode 100644 index 00000000..cf89239a --- /dev/null +++ b/freebsd/sys/netipsec/xform_ipip.c @@ -0,0 +1,708 @@ +#include <freebsd/machine/rtems-bsd-config.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 <freebsd/local/opt_inet.h> +#include <freebsd/local/opt_inet6.h> +#include <freebsd/local/opt_enc.h> + +#include <freebsd/sys/param.h> +#include <freebsd/sys/systm.h> +#include <freebsd/sys/mbuf.h> +#include <freebsd/sys/socket.h> +#include <freebsd/sys/kernel.h> +#include <freebsd/sys/protosw.h> +#include <freebsd/sys/sysctl.h> + +#include <freebsd/net/if.h> +#include <freebsd/net/pfil.h> +#include <freebsd/net/route.h> +#include <freebsd/net/netisr.h> +#include <freebsd/net/vnet.h> + +#include <freebsd/netinet/in.h> +#include <freebsd/netinet/in_systm.h> +#include <freebsd/netinet/in_var.h> +#include <freebsd/netinet/ip.h> +#include <freebsd/netinet/ip_ecn.h> +#include <freebsd/netinet/ip_var.h> +#include <freebsd/netinet/ip_encap.h> +#ifdef MROUTING +#include <freebsd/netinet/ip_mroute.h> +#endif + +#include <freebsd/netipsec/ipsec.h> +#include <freebsd/netipsec/xform.h> + +#include <freebsd/netipsec/ipip_var.h> + +#ifdef INET6 +#include <freebsd/netinet/ip6.h> +#include <freebsd/netipsec/ipsec6.h> +#include <freebsd/netinet6/ip6_ecn.h> +#include <freebsd/netinet6/in6_var.h> +#include <freebsd/netinet6/ip6protosw.h> +#endif + +#include <freebsd/netipsec/key.h> +#include <freebsd/netipsec/key_debug.h> + +#include <freebsd/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__)); + V_ipipstat.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__)); + V_ipipstat.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; + + V_ipipstat.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: + V_ipipstat.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__)); + V_ipipstat.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)) { + V_ipipstat.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: + V_ipipstat.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__)); + V_ipipstat.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) { + V_ipipstat.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)) { + V_ipipstat.ipips_spoof++; + m_freem(m); + IFNET_RUNLOCK_NOSLEEP(); + return; + } + + } +#endif /* INET6 */ + } + } + IFNET_RUNLOCK_NOSLEEP(); + } + + /* Statistics */ + V_ipipstat.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); + } + + if (netisr_queue(isr, m)) { /* (0) on success. */ + V_ipipstat.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; +#ifdef INET + u_int8_t itos; + 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))); + V_ipipstat.ipips_unspec++; + error = EINVAL; + goto bad; + } + + M_PREPEND(m, sizeof(struct ip), M_DONTWAIT); + if (m == 0) { + DPRINTF(("%s: M_PREPEND failed\n", __func__)); + V_ipipstat.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... */ + if (tp == 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); + } +#ifdef INET6 + else if (tp == (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; + } +#endif /* INET6 */ + else { + 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))); + V_ipipstat.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__)); + V_ipipstat.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; + +#ifdef INET + if (tp == 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; + } else +#endif /* INET */ + if (tp == (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; + } else { + 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)); + V_ipipstat.ipips_family++; + error = EAFNOSUPPORT; /* XXX diffs from openbsd */ + goto bad; + } + + V_ipipstat.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 + V_ipipstat.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 + V_ipipstat.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 +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; +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 +}; +#ifdef INET6 +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 + +/* + * 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); +} + +static void +ipe4_attach(void) +{ + + xform_register(&ipe4_xformsw); + /* attach to encapsulation framework */ + /* XXX save return cookie for detach on module remove */ + (void) encap_attach_func(AF_INET, -1, + ipe4_encapcheck, &ipe4_protosw, NULL); +#ifdef INET6 + (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 new file mode 100644 index 00000000..2b099639 --- /dev/null +++ b/freebsd/sys/netipsec/xform_tcp.c @@ -0,0 +1,173 @@ +#include <freebsd/machine/rtems-bsd-config.h> + +/* $FreeBSD$ */ + +/*- + * Copyright (c) 2003 Bruce M. Simpson <bms@spc.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * 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. + */ + +/* TCP MD5 Signature Option (RFC2385) */ +#include <freebsd/local/opt_inet.h> +#include <freebsd/local/opt_inet6.h> + +#include <freebsd/sys/param.h> +#include <freebsd/sys/systm.h> +#include <freebsd/sys/mbuf.h> +#include <freebsd/sys/lock.h> +#include <freebsd/sys/socket.h> +#include <freebsd/sys/kernel.h> +#include <freebsd/sys/protosw.h> +#include <freebsd/sys/sysctl.h> + +#include <freebsd/netinet/in.h> +#include <freebsd/netinet/in_systm.h> +#include <freebsd/netinet/ip.h> +#include <freebsd/netinet/ip_var.h> +#include <freebsd/netinet/tcp.h> +#include <freebsd/netinet/tcp_var.h> + +#include <freebsd/net/route.h> +#include <freebsd/net/vnet.h> + +#include <freebsd/netipsec/ipsec.h> +#include <freebsd/netipsec/xform.h> + +#ifdef INET6 +#include <freebsd/netinet/ip6.h> +#include <freebsd/netipsec/ipsec6.h> +#endif + +#include <freebsd/netipsec/key.h> +#include <freebsd/netipsec/key_debug.h> + +/* + * 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. + * + * This allows per-host granularity without affecting the userland + * interface, which is a simple socket option toggle switch, + * TCP_SIGNATURE_ENABLE. + * + * To allow per-service granularity requires that we have a means + * of mapping port to SPI. The mandated way of doing this is to + * use SPD entries to specify packet flows which get the TCP-MD5 + * treatment, however the code to do this is currently unstable + * and unsuitable for production use. + * + * Therefore we use this compromise in the meantime. + */ +static int +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)); + return (EINVAL); + } + if (sav->key_auth == NULL) { + DPRINTF(("%s: no authentication key present\n", __func__)); + return (EINVAL); + } + keylen = _KEYLEN(sav->key_auth); + if ((keylen < TCP_KEYLEN_MIN) || (keylen > TCP_KEYLEN_MAX)) { + DPRINTF(("%s: invalid key length %u\n", __func__, keylen)); + return (EINVAL); + } + + return (0); +} + +/* + * Paranoia. + * + * Called when the SA is deleted. + */ +static int +tcpsignature_zeroize(struct secasvar *sav) +{ + + if (sav->key_auth) + 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) +{ + + return (0); +} + +/* + * 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) +{ + + return (EINVAL); +} + +static struct xformsw tcpsignature_xformsw = { + XF_TCPSIGNATURE, XFT_AUTH, "TCPMD5", + tcpsignature_init, tcpsignature_zeroize, + tcpsignature_input, tcpsignature_output +}; + +static void +tcpsignature_attach(void) +{ + + xform_register(&tcpsignature_xformsw); +} + +SYSINIT(tcpsignature_xform_init, SI_SUB_DRIVERS, SI_ORDER_FIRST, + tcpsignature_attach, NULL); |