diff options
-rw-r--r-- | sys/netipsec/ah.h | 56 | ||||
-rw-r--r-- | sys/netipsec/ah_var.h | 78 | ||||
-rw-r--r-- | sys/netipsec/esp.h | 69 | ||||
-rw-r--r-- | sys/netipsec/esp_var.h | 78 | ||||
-rw-r--r-- | sys/netipsec/ipcomp.h | 55 | ||||
-rw-r--r-- | sys/netipsec/ipcomp_var.h | 67 | ||||
-rw-r--r-- | sys/netipsec/ipip_var.h | 65 | ||||
-rw-r--r-- | sys/netipsec/ipsec.c | 1941 | ||||
-rw-r--r-- | sys/netipsec/ipsec.h | 389 | ||||
-rw-r--r-- | sys/netipsec/ipsec6.h | 89 | ||||
-rw-r--r-- | sys/netipsec/ipsec_input.c | 728 | ||||
-rw-r--r-- | sys/netipsec/ipsec_mbuf.c | 401 | ||||
-rw-r--r-- | sys/netipsec/ipsec_output.c | 737 | ||||
-rw-r--r-- | sys/netipsec/key.c | 7287 | ||||
-rw-r--r-- | sys/netipsec/key.h | 107 | ||||
-rw-r--r-- | sys/netipsec/key_debug.c | 747 | ||||
-rw-r--r-- | sys/netipsec/key_debug.h | 88 | ||||
-rw-r--r-- | sys/netipsec/key_var.h | 74 | ||||
-rw-r--r-- | sys/netipsec/keydb.h | 181 | ||||
-rw-r--r-- | sys/netipsec/keysock.c | 603 | ||||
-rw-r--r-- | sys/netipsec/keysock.h | 82 | ||||
-rw-r--r-- | sys/netipsec/xform.h | 126 | ||||
-rw-r--r-- | sys/netipsec/xform_ah.c | 1209 | ||||
-rw-r--r-- | sys/netipsec/xform_esp.c | 966 | ||||
-rw-r--r-- | sys/netipsec/xform_ipcomp.c | 608 | ||||
-rw-r--r-- | sys/netipsec/xform_ipip.c | 699 |
26 files changed, 17530 insertions, 0 deletions
diff --git a/sys/netipsec/ah.h b/sys/netipsec/ah.h new file mode 100644 index 0000000..1ba959a2 --- /dev/null +++ b/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_H_ +#define _NETIPSEC_AH_H_ + +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_H_*/ diff --git a/sys/netipsec/ah_var.h b/sys/netipsec/ah_var.h new file mode 100644 index 0000000..a4cf4c9 --- /dev/null +++ b/sys/netipsec/ah_var.h @@ -0,0 +1,78 @@ +/* $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_H_ +#define _NETIPSEC_AH_VAR_H_ + +/* + * 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 +extern int ah_enable; +extern int ah_cleartos; +extern struct ahstat ahstat; +#endif /* _KERNEL */ +#endif /*_NETIPSEC_AH_VAR_H_*/ diff --git a/sys/netipsec/esp.h b/sys/netipsec/esp.h new file mode 100644 index 0000000..920d334 --- /dev/null +++ b/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_H_ +#define _NETIPSEC_ESP_H_ + +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_H_*/ diff --git a/sys/netipsec/esp_var.h b/sys/netipsec/esp_var.h new file mode 100644 index 0000000..22cf3aa --- /dev/null +++ b/sys/netipsec/esp_var.h @@ -0,0 +1,78 @@ +/* $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_H_ +#define _NETIPSEC_ESP_VAR_H_ + +/* + * 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 +extern int esp_enable; +extern struct espstat espstat; +#endif /* _KERNEL */ +#endif /*_NETIPSEC_ESP_VAR_H_*/ diff --git a/sys/netipsec/ipcomp.h b/sys/netipsec/ipcomp.h new file mode 100644 index 0000000..26f3759 --- /dev/null +++ b/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_H_ +#define _NETIPSEC_IPCOMP_H_ + +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_H_*/ diff --git a/sys/netipsec/ipcomp_var.h b/sys/netipsec/ipcomp_var.h new file mode 100644 index 0000000..9b10b1c --- /dev/null +++ b/sys/netipsec/ipcomp_var.h @@ -0,0 +1,67 @@ +/* $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_H_ +#define _NETIPSEC_IPCOMP_VAR_H_ + +/* + * 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 + +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 */ +}; + +#ifdef _KERNEL +extern int ipcomp_enable; +extern struct ipcompstat ipcompstat; +#endif /* _KERNEL */ +#endif /*_NETIPSEC_IPCOMP_VAR_H_*/ diff --git a/sys/netipsec/ipip_var.h b/sys/netipsec/ipip_var.h new file mode 100644 index 0000000..3004beb --- /dev/null +++ b/sys/netipsec/ipip_var.h @@ -0,0 +1,65 @@ +/* $FreeBSD$ */ +/* $OpenBSD: ip_ipip.h,v 1.5 2002/06/09 16:26:10 itojun Exp $ */ +/* + * The authors of this code are John Ioannidis (ji@tla.org), + * Angelos D. Keromytis (kermit@csd.uch.gr) and + * Niels Provos (provos@physnet.uni-hamburg.de). + * + * The original version of this code was written by John Ioannidis + * for BSD/OS in Athens, Greece, in November 1995. + * + * Ported to OpenBSD and NetBSD, with additional transforms, in December 1996, + * by Angelos D. Keromytis. + * + * Additional transforms and features in 1997 and 1998 by Angelos D. Keromytis + * and Niels Provos. + * + * Additional features in 1999 by Angelos D. Keromytis. + * + * Copyright (C) 1995, 1996, 1997, 1998, 1999 by John Ioannidis, + * Angelos D. Keromytis and Niels Provos. + * Copyright (c) 2001, Angelos D. Keromytis. + * + * Permission to use, copy, and modify this software with or without fee + * is hereby granted, provided that this entire notice is included in + * all copies of any software which is or includes a copy or + * modification of this software. + * You may use this code under the GNU public license if you so wish. Please + * contribute changes back to the authors under this freer than GPL license + * so that we may further the use of strong encryption without limitations to + * all. + * + * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTY. IN PARTICULAR, NONE OF THE AUTHORS MAKES ANY + * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE + * MERCHANTABILITY OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR + * PURPOSE. + */ + +#ifndef _NETINET_IPIP_H_ +#define _NETINET_IPIP_H_ + +/* + * IP-inside-IP processing. + * Not quite all the functionality of RFC-1853, but the main idea is there. + */ + +struct ipipstat +{ + u_int32_t ipips_ipackets; /* total input packets */ + u_int32_t ipips_opackets; /* total output packets */ + u_int32_t ipips_hdrops; /* packet shorter than header shows */ + u_int32_t ipips_qfull; + u_int64_t ipips_ibytes; + u_int64_t ipips_obytes; + u_int32_t ipips_pdrops; /* packet dropped due to policy */ + u_int32_t ipips_spoof; /* IP spoofing attempts */ + u_int32_t ipips_family; /* Protocol family mismatch */ + u_int32_t ipips_unspec; /* Missing tunnel endpoint address */ +}; + +#ifdef _KERNEL +extern int ipip_allow; +extern struct ipipstat ipipstat; +#endif /* _KERNEL */ +#endif /* _NETINET_IPIP_H_ */ diff --git a/sys/netipsec/ipsec.c b/sys/netipsec/ipsec.c new file mode 100644 index 0000000..9f126c5 --- /dev/null +++ b/sys/netipsec/ipsec.c @@ -0,0 +1,1941 @@ +/* $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 "opt_inet.h" +#include "opt_inet6.h" +#include "opt_ipsec.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/malloc.h> +#include <sys/mbuf.h> +#include <sys/domain.h> +#include <sys/protosw.h> +#include <sys/socket.h> +#include <sys/socketvar.h> +#include <sys/errno.h> +#include <sys/time.h> +#include <sys/kernel.h> +#include <sys/syslog.h> +#include <sys/sysctl.h> +#include <sys/proc.h> + +#include <net/if.h> +#include <net/route.h> + +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <netinet/ip_var.h> +#include <netinet/in_var.h> +#include <netinet/udp.h> +#include <netinet/udp_var.h> +#include <netinet/tcp.h> +#include <netinet/udp.h> + +#include <netinet/ip6.h> +#ifdef INET6 +#include <netinet6/ip6_var.h> +#endif +#include <netinet/in_pcb.h> +#ifdef INET6 +#include <netinet/icmp6.h> +#endif + +#include <netipsec/ipsec.h> +#ifdef INET6 +#include <netipsec/ipsec6.h> +#endif +#include <netipsec/ah_var.h> +#include <netipsec/esp_var.h> +#include <netipsec/ipcomp.h> /*XXX*/ +#include <netipsec/ipcomp_var.h> + +#include <netipsec/key.h> +#include <netipsec/keydb.h> +#include <netipsec/key_debug.h> + +#include <netipsec/xform.h> + +#include <machine/in_cksum.h> + +#include <net/net_osdep.h> + +#ifdef IPSEC_DEBUG +int ipsec_debug = 1; +#else +int ipsec_debug = 0; +#endif + +/* NB: name changed so netstat doesn't use it */ +struct newipsecstat newipsecstat; +int ip4_ah_offsetmask = 0; /* maybe IP_DF? */ +int ip4_ipsec_dfbit = 0; /* DF bit on encap. 0: clear 1: set 2: copy */ +int ip4_esp_trans_deflev = IPSEC_LEVEL_USE; +int ip4_esp_net_deflev = IPSEC_LEVEL_USE; +int ip4_ah_trans_deflev = IPSEC_LEVEL_USE; +int ip4_ah_net_deflev = IPSEC_LEVEL_USE; +struct secpolicy ip4_def_policy; +int ip4_ipsec_ecn = 0; /* ECN ignore(-1)/forbidden(0)/allowed(1) */ +int ip4_esp_randpad = -1; +/* + * Crypto support requirements: + * + * 1 require hardware support + * -1 require software support + * 0 take anything + */ +int crypto_support = 0; + +SYSCTL_DECL(_net_inet_ipsec); + +/* net.inet.ipsec */ +SYSCTL_INT(_net_inet_ipsec, IPSECCTL_DEF_POLICY, + def_policy, CTLFLAG_RW, &ip4_def_policy.policy, 0, ""); +SYSCTL_INT(_net_inet_ipsec, IPSECCTL_DEF_ESP_TRANSLEV, esp_trans_deflev, + CTLFLAG_RW, &ip4_esp_trans_deflev, 0, ""); +SYSCTL_INT(_net_inet_ipsec, IPSECCTL_DEF_ESP_NETLEV, esp_net_deflev, + CTLFLAG_RW, &ip4_esp_net_deflev, 0, ""); +SYSCTL_INT(_net_inet_ipsec, IPSECCTL_DEF_AH_TRANSLEV, ah_trans_deflev, + CTLFLAG_RW, &ip4_ah_trans_deflev, 0, ""); +SYSCTL_INT(_net_inet_ipsec, IPSECCTL_DEF_AH_NETLEV, ah_net_deflev, + CTLFLAG_RW, &ip4_ah_net_deflev, 0, ""); +SYSCTL_INT(_net_inet_ipsec, IPSECCTL_AH_CLEARTOS, + ah_cleartos, CTLFLAG_RW, &ah_cleartos, 0, ""); +SYSCTL_INT(_net_inet_ipsec, IPSECCTL_AH_OFFSETMASK, + ah_offsetmask, CTLFLAG_RW, &ip4_ah_offsetmask, 0, ""); +SYSCTL_INT(_net_inet_ipsec, IPSECCTL_DFBIT, + dfbit, CTLFLAG_RW, &ip4_ipsec_dfbit, 0, ""); +SYSCTL_INT(_net_inet_ipsec, IPSECCTL_ECN, + ecn, CTLFLAG_RW, &ip4_ipsec_ecn, 0, ""); +SYSCTL_INT(_net_inet_ipsec, IPSECCTL_DEBUG, + debug, CTLFLAG_RW, &ipsec_debug, 0, ""); +SYSCTL_INT(_net_inet_ipsec, IPSECCTL_ESP_RANDPAD, + esp_randpad, CTLFLAG_RW, &ip4_esp_randpad, 0, ""); +SYSCTL_INT(_net_inet_ipsec, OID_AUTO, + crypto_support, CTLFLAG_RW, &crypto_support,0, ""); +SYSCTL_STRUCT(_net_inet_ipsec, OID_AUTO, + ipsecstats, CTLFLAG_RD, &newipsecstat, newipsecstat, ""); + +#ifdef INET6 +int ip6_esp_trans_deflev = IPSEC_LEVEL_USE; +int ip6_esp_net_deflev = IPSEC_LEVEL_USE; +int ip6_ah_trans_deflev = IPSEC_LEVEL_USE; +int ip6_ah_net_deflev = IPSEC_LEVEL_USE; +int ip6_ipsec_ecn = 0; /* ECN ignore(-1)/forbidden(0)/allowed(1) */ +int ip6_esp_randpad = -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", ""); +#endif /* COMPAT_KAME */ +SYSCTL_INT(_net_inet6_ipsec6, IPSECCTL_DEF_POLICY, + def_policy, CTLFLAG_RW, &ip4_def_policy.policy, 0, ""); +SYSCTL_INT(_net_inet6_ipsec6, IPSECCTL_DEF_ESP_TRANSLEV, esp_trans_deflev, + CTLFLAG_RW, &ip6_esp_trans_deflev, 0, ""); +SYSCTL_INT(_net_inet6_ipsec6, IPSECCTL_DEF_ESP_NETLEV, esp_net_deflev, + CTLFLAG_RW, &ip6_esp_net_deflev, 0, ""); +SYSCTL_INT(_net_inet6_ipsec6, IPSECCTL_DEF_AH_TRANSLEV, ah_trans_deflev, + CTLFLAG_RW, &ip6_ah_trans_deflev, 0, ""); +SYSCTL_INT(_net_inet6_ipsec6, IPSECCTL_DEF_AH_NETLEV, ah_net_deflev, + CTLFLAG_RW, &ip6_ah_net_deflev, 0, ""); +SYSCTL_INT(_net_inet6_ipsec6, IPSECCTL_ECN, + ecn, CTLFLAG_RW, &ip6_ipsec_ecn, 0, ""); +SYSCTL_INT(_net_inet6_ipsec6, IPSECCTL_DEBUG, + debug, CTLFLAG_RW, &ipsec_debug, 0, ""); +SYSCTL_INT(_net_inet6_ipsec6, IPSECCTL_ESP_RANDPAD, + esp_randpad, CTLFLAG_RW, &ip6_esp_randpad, 0, ""); +#endif /* INET6 */ + +static int ipsec4_setspidx_inpcb __P((struct mbuf *, struct inpcb *pcb)); +#ifdef INET6 +static int ipsec6_setspidx_in6pcb __P((struct mbuf *, struct in6pcb *pcb)); +#endif +static int ipsec_setspidx __P((struct mbuf *, struct secpolicyindex *, int)); +static void ipsec4_get_ulp __P((struct mbuf *m, struct secpolicyindex *, int)); +static int ipsec4_setspidx_ipaddr __P((struct mbuf *, struct secpolicyindex *)); +#ifdef INET6 +static void ipsec6_get_ulp __P((struct mbuf *m, struct secpolicyindex *, int)); +static int ipsec6_setspidx_ipaddr __P((struct mbuf *, struct secpolicyindex *)); +#endif +static void ipsec_delpcbpolicy __P((struct inpcbpolicy *)); +static struct secpolicy *ipsec_deepcopy_policy __P((struct secpolicy *src)); +static int ipsec_set_policy __P((struct secpolicy **pcb_sp, + int optname, caddr_t request, size_t len, int priv)); +static int ipsec_get_policy __P((struct secpolicy *pcb_sp, struct mbuf **mp)); +static void vshiftl __P((unsigned char *, int, int)); +static size_t ipsec_hdrsiz __P((struct secpolicy *)); + +/* + * Return a held reference to the default SP. + */ +static struct secpolicy * +key_allocsp_default(const char* where, int tag) +{ + struct secpolicy *sp; + + KEYDEBUG(KEYDEBUG_IPSEC_STAMP, + printf("DP key_allocsp_default from %s:%u\n", where, tag)); + + sp = &ip4_def_policy; + if (sp->policy != IPSEC_POLICY_DISCARD && + sp->policy != IPSEC_POLICY_NONE) { + ipseclog((LOG_INFO, "fixed system default policy: %d->%d\n", + sp->policy, IPSEC_POLICY_NONE)); + sp->policy = IPSEC_POLICY_NONE; + } + sp->refcnt++; + + KEYDEBUG(KEYDEBUG_IPSEC_STAMP, + printf("DP key_allocsp_default returns SP:%p (%u)\n", + sp, sp->refcnt)); + return sp; +} +#define KEY_ALLOCSP_DEFAULT() \ + key_allocsp_default(__FILE__, __LINE__) + +/* + * For OUTBOUND packet having a socket. Searching SPD for packet, + * and return a pointer to SP. + * OUT: NULL: no apropreate SP found, the following value is set to error. + * 0 : bypass + * EACCES : discard packet. + * ENOENT : ipsec_acquire() in progress, maybe. + * others : error 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; + + KASSERT(tdbi != NULL, ("ipsec_getpolicy: null tdbi")); + KASSERT(dir == IPSEC_DIR_INBOUND || dir == IPSEC_DIR_OUTBOUND, + ("ipsec_getpolicy: invalid direction %u", dir)); + + sp = KEY_ALLOCSP2(tdbi->spi, &tdbi->dst, tdbi->proto, dir); + if (sp == NULL) /*XXX????*/ + sp = KEY_ALLOCSP_DEFAULT(); + KASSERT(sp != NULL, ("ipsec_getpolicy: null SP")); + return sp; +} + +/* + * For OUTBOUND packet having a socket. Searching SPD for packet, + * and return a pointer to SP. + * OUT: NULL: no apropreate SP found, the following value is set to error. + * 0 : bypass + * EACCES : discard packet. + * ENOENT : ipsec_acquire() in progress, maybe. + * others : error occured. + * others: a pointer to SP + * + * NOTE: IPv6 mapped adddress concern is implemented here. + */ +struct secpolicy * +ipsec_getpolicybysock(m, dir, inp, error) + struct mbuf *m; + u_int dir; + struct inpcb *inp; + int *error; +{ + struct inpcbpolicy *pcbsp = NULL; + struct secpolicy *currsp = NULL; /* policy on socket */ + struct secpolicy *sp; + int af; + + KASSERT(m != NULL, ("ipsec_getpolicybysock: null mbuf")); + KASSERT(inp != NULL, ("ipsec_getpolicybysock: null inpcb")); + KASSERT(error != NULL, ("ipsec_getpolicybysock: null error")); + KASSERT(dir == IPSEC_DIR_INBOUND || dir == IPSEC_DIR_OUTBOUND, + ("ipsec_getpolicybysock: invalid direction %u", dir)); + + af = inp->inp_socket->so_proto->pr_domain->dom_family; + KASSERT(af == AF_INET || af == AF_INET6, + ("ipsec_getpolicybysock: unexpected protocol family %u", af)); + + switch (af) { + case AF_INET: + /* set spidx in pcb */ + *error = ipsec4_setspidx_inpcb(m, inp); + pcbsp = inp->inp_sp; + break; +#ifdef INET6 + case AF_INET6: + /* set spidx in pcb */ + *error = ipsec6_setspidx_in6pcb(m, inp); + pcbsp = inp->in6p_sp; + break; +#endif + default: + *error = EPFNOSUPPORT; + break; + } + if (*error) + return NULL; + + KASSERT(pcbsp != NULL, ("ipsec_getpolicybysock: null pcbsp")); + switch (dir) { + case IPSEC_DIR_INBOUND: + currsp = pcbsp->sp_in; + break; + case IPSEC_DIR_OUTBOUND: + currsp = pcbsp->sp_out; + break; + } + KASSERT(currsp != NULL, ("ipsec_getpolicybysock: null currsp")); + + if (pcbsp->priv) { /* when privilieged socket */ + switch (currsp->policy) { + case IPSEC_POLICY_BYPASS: + case IPSEC_POLICY_IPSEC: + currsp->refcnt++; + sp = currsp; + break; + + case IPSEC_POLICY_ENTRUST: + /* look for a policy in SPD */ + sp = KEY_ALLOCSP(&currsp->spidx, dir); + if (sp == NULL) /* no SP found */ + sp = KEY_ALLOCSP_DEFAULT(); + break; + + default: + ipseclog((LOG_ERR, "ipsec_getpolicybysock: " + "Invalid policy for PCB %d\n", currsp->policy)); + *error = EINVAL; + return NULL; + } + } else { /* unpriv, SPD has policy */ + sp = KEY_ALLOCSP(&currsp->spidx, dir); + if (sp == NULL) { /* no SP found */ + switch (currsp->policy) { + case IPSEC_POLICY_BYPASS: + ipseclog((LOG_ERR, "ipsec_getpolicybysock: " + "Illegal policy for non-priviliged defined %d\n", + currsp->policy)); + *error = EINVAL; + return NULL; + + case IPSEC_POLICY_ENTRUST: + sp = KEY_ALLOCSP_DEFAULT(); + break; + + case IPSEC_POLICY_IPSEC: + currsp->refcnt++; + sp = currsp; + break; + + default: + ipseclog((LOG_ERR, "ipsec_getpolicybysock: " + "Invalid policy for PCB %d\n", currsp->policy)); + *error = EINVAL; + return NULL; + } + } + } + KASSERT(sp != NULL, + ("ipsec_getpolicybysock: null SP (priv %u policy %u", + pcbsp->priv, currsp->policy)); + KEYDEBUG(KEYDEBUG_IPSEC_STAMP, + printf("DP ipsec_getpolicybysock (priv %u policy %u) allocates " + "SP:%p (refcnt %u)\n", pcbsp->priv, currsp->policy, + sp, sp->refcnt)); + 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(m, dir, flag, error) + struct mbuf *m; + u_int dir; + int flag; + int *error; +{ + struct secpolicyindex spidx; + struct secpolicy *sp; + + KASSERT(m != NULL, ("ipsec_getpolicybyaddr: null mbuf")); + KASSERT(error != NULL, ("ipsec_getpolicybyaddr: null error")); + KASSERT(dir == IPSEC_DIR_INBOUND || dir == IPSEC_DIR_OUTBOUND, + ("ipsec4_getpolicybaddr: invalid direction %u", dir)); + + sp = NULL; + if (key_havesp(dir)) { + /* make a index to look for a policy */ + *error = ipsec_setspidx(m, &spidx, + (flag & IP_FORWARDING) ? 0 : 1); + if (*error != 0) { + DPRINTF(("ipsec_getpolicybyaddr: setpidx failed," + " dir %u flag %u\n", dir, flag)); + bzero(&spidx, sizeof (spidx)); + return NULL; + } + spidx.dir = dir; + + sp = KEY_ALLOCSP(&spidx, dir); + } + if (sp == NULL) /* no SP found, use system default */ + sp = KEY_ALLOCSP_DEFAULT(); + KASSERT(sp != NULL, ("ipsec_getpolicybyaddr: null SP")); + return sp; +} + +struct secpolicy * +ipsec4_checkpolicy(m, dir, flag, error, inp) + struct mbuf *m; + u_int dir, flag; + int *error; + struct inpcb *inp; +{ + struct secpolicy *sp; + + *error = 0; + if (inp == NULL) + sp = ipsec_getpolicybyaddr(m, dir, flag, error); + else + sp = ipsec_getpolicybysock(m, dir, inp, error); + if (sp == NULL) { + KASSERT(*error != 0, + ("ipsec4_checkpolicy: getpolicy failed w/o error")); + newipsecstat.ips_out_inval++; + return NULL; + } + KASSERT(*error == 0, + ("ipsec4_checkpolicy: sp w/ error set to %u", *error)); + switch (sp->policy) { + case IPSEC_POLICY_ENTRUST: + default: + printf("ipsec4_checkpolicy: invalid policy %u\n", sp->policy); + /* fall thru... */ + case IPSEC_POLICY_DISCARD: + newipsecstat.ips_out_polvio++; + *error = -EINVAL; /* packet is discarded by caller */ + break; + case IPSEC_POLICY_BYPASS: + case IPSEC_POLICY_NONE: + KEY_FREESP(&sp); + sp = NULL; /* NB: force NULL result */ + break; + case IPSEC_POLICY_IPSEC: + if (sp->req == NULL) /* acquire an SA */ + *error = key_spdacquire(sp); + break; + } + if (*error != 0) { + KEY_FREESP(&sp); + sp = NULL; + } + return sp; +} + +static int +ipsec4_setspidx_inpcb(m, pcb) + struct mbuf *m; + struct inpcb *pcb; +{ + int error; + + KASSERT(pcb != NULL, ("ipsec4_setspidx_inpcb: null pcb")); + KASSERT(pcb->inp_sp != NULL, ("ipsec4_setspidx_inpcb: null inp_sp")); + KASSERT(pcb->inp_sp->sp_out != NULL && pcb->inp_sp->sp_in != NULL, + ("ipsec4_setspidx_inpcb: null sp_in || sp_out")); + + error = ipsec_setspidx(m, &pcb->inp_sp->sp_in->spidx, 1); + if (error == 0) { + pcb->inp_sp->sp_in->spidx.dir = IPSEC_DIR_INBOUND; + pcb->inp_sp->sp_out->spidx = pcb->inp_sp->sp_in->spidx; + pcb->inp_sp->sp_out->spidx.dir = IPSEC_DIR_OUTBOUND; + } else { + bzero(&pcb->inp_sp->sp_in->spidx, + sizeof (pcb->inp_sp->sp_in->spidx)); + bzero(&pcb->inp_sp->sp_out->spidx, + sizeof (pcb->inp_sp->sp_in->spidx)); + } + return error; +} + +#ifdef INET6 +static int +ipsec6_setspidx_in6pcb(m, pcb) + struct mbuf *m; + struct in6pcb *pcb; +{ + struct secpolicyindex *spidx; + int error; + + KASSERT(pcb != NULL, ("ipsec6_setspidx_in6pcb: null pcb")); + KASSERT(pcb->in6p_sp != NULL, ("ipsec6_setspidx_in6pcb: null inp_sp")); + KASSERT(pcb->in6p_sp->sp_out != NULL && pcb->in6p_sp->sp_in != NULL, + ("ipsec6_setspidx_in6pcb: null sp_in || sp_out")); + + bzero(&pcb->in6p_sp->sp_in->spidx, sizeof(*spidx)); + bzero(&pcb->in6p_sp->sp_out->spidx, sizeof(*spidx)); + + spidx = &pcb->in6p_sp->sp_in->spidx; + error = ipsec_setspidx(m, spidx, 1); + if (error) + goto bad; + spidx->dir = IPSEC_DIR_INBOUND; + + spidx = &pcb->in6p_sp->sp_out->spidx; + error = ipsec_setspidx(m, spidx, 1); + if (error) + goto bad; + spidx->dir = IPSEC_DIR_OUTBOUND; + + return 0; + +bad: + bzero(&pcb->in6p_sp->sp_in->spidx, sizeof(*spidx)); + bzero(&pcb->in6p_sp->sp_out->spidx, sizeof(*spidx)); + return error; +} +#endif + +/* + * configure security policy index (src/dst/proto/sport/dport) + * by looking at the content of mbuf. + * the caller is responsible for error recovery (like clearing up spidx). + */ +static int +ipsec_setspidx(m, spidx, needport) + struct mbuf *m; + struct secpolicyindex *spidx; + int needport; +{ + struct ip *ip = NULL; + struct ip ipbuf; + u_int v; + struct mbuf *n; + int len; + int error; + + KASSERT(m != NULL, ("ipsec_setspidx: null mbuf")); + + /* + * validate m->m_pkthdr.len. we see incorrect length if we + * mistakenly call this function with inconsistent mbuf chain + * (like 4.4BSD tcp/udp processing). XXX should we panic here? + */ + len = 0; + for (n = m; n; n = n->m_next) + len += n->m_len; + if (m->m_pkthdr.len != len) { + KEYDEBUG(KEYDEBUG_IPSEC_DUMP, + printf("ipsec_setspidx: " + "total of m_len(%d) != pkthdr.len(%d), " + "ignored.\n", + len, m->m_pkthdr.len)); + return EINVAL; + } + + if (m->m_pkthdr.len < sizeof(struct ip)) { + KEYDEBUG(KEYDEBUG_IPSEC_DUMP, + printf("ipsec_setspidx: " + "pkthdr.len(%d) < sizeof(struct ip), ignored.\n", + m->m_pkthdr.len)); + return EINVAL; + } + + if (m->m_len >= sizeof(*ip)) + ip = mtod(m, struct ip *); + else { + m_copydata(m, 0, sizeof(ipbuf), (caddr_t)&ipbuf); + ip = &ipbuf; + } +#ifdef _IP_VHL + v = _IP_VHL_V(ip->ip_vhl); +#else + v = ip->ip_v; +#endif + switch (v) { + case 4: + error = ipsec4_setspidx_ipaddr(m, spidx); + if (error) + return error; + ipsec4_get_ulp(m, spidx, needport); + return 0; +#ifdef INET6 + case 6: + if (m->m_pkthdr.len < sizeof(struct ip6_hdr)) { + KEYDEBUG(KEYDEBUG_IPSEC_DUMP, + printf("ipsec_setspidx: " + "pkthdr.len(%d) < sizeof(struct ip6_hdr), " + "ignored.\n", m->m_pkthdr.len)); + return EINVAL; + } + error = ipsec6_setspidx_ipaddr(m, spidx); + if (error) + return error; + ipsec6_get_ulp(m, spidx, needport); + return 0; +#endif + default: + KEYDEBUG(KEYDEBUG_IPSEC_DUMP, + printf("ipsec_setspidx: " + "unknown IP version %u, ignored.\n", v)); + return EINVAL; + } +} + +static void +ipsec4_get_ulp(struct mbuf *m, struct secpolicyindex *spidx, int needport) +{ + u_int8_t nxt; + int off; + + /* sanity check */ + KASSERT(m != NULL, ("ipsec4_get_ulp: null mbuf")); + KASSERT(m->m_pkthdr.len >= sizeof(struct ip), + ("ipsec4_get_ulp: packet too short")); + + /* NB: ip_input() flips it into host endian XXX need more checking */ + if (m->m_len < sizeof (struct ip)) { + struct ip *ip = mtod(m, struct ip *); + if (ip->ip_off & (IP_MF | IP_OFFMASK)) + goto done; +#ifdef _IP_VHL + off = _IP_VHL_HL(ip->ip_vhl) << 2; +#else + off = ip->ip_hl << 2; +#endif + nxt = ip->ip_p; + } else { + struct ip ih; + + m_copydata(m, 0, sizeof (struct ip), (caddr_t) &ih); + if (ih.ip_off & (IP_MF | IP_OFFMASK)) + goto done; +#ifdef _IP_VHL + off = _IP_VHL_HL(ih.ip_vhl) << 2; +#else + off = ih.ip_hl << 2; +#endif + nxt = ih.ip_p; + } + + while (off < m->m_pkthdr.len) { + struct ip6_ext ip6e; + struct tcphdr th; + struct udphdr uh; + + switch (nxt) { + case IPPROTO_TCP: + spidx->ul_proto = nxt; + if (!needport) + goto done_proto; + if (off + sizeof(struct tcphdr) > m->m_pkthdr.len) + goto done; + m_copydata(m, off, sizeof (th), (caddr_t) &th); + spidx->src.sin.sin_port = th.th_sport; + spidx->dst.sin.sin_port = th.th_dport; + return; + case IPPROTO_UDP: + spidx->ul_proto = nxt; + if (!needport) + goto done_proto; + if (off + sizeof(struct udphdr) > m->m_pkthdr.len) + goto done; + m_copydata(m, off, sizeof (uh), (caddr_t) &uh); + spidx->src.sin.sin_port = uh.uh_sport; + spidx->dst.sin.sin_port = uh.uh_dport; + return; + case IPPROTO_AH: + if (m->m_pkthdr.len > off + sizeof(ip6e)) + goto done; + /* XXX sigh, this works but is totally bogus */ + m_copydata(m, off, sizeof(ip6e), (caddr_t) &ip6e); + off += (ip6e.ip6e_len + 2) << 2; + nxt = ip6e.ip6e_nxt; + break; + case IPPROTO_ICMP: + default: + /* XXX intermediate headers??? */ + spidx->ul_proto = nxt; + goto done_proto; + } + } +done: + spidx->ul_proto = IPSEC_ULPROTO_ANY; +done_proto: + spidx->src.sin.sin_port = IPSEC_PORT_ANY; + spidx->dst.sin.sin_port = IPSEC_PORT_ANY; +} + +/* assumes that m is sane */ +static int +ipsec4_setspidx_ipaddr(struct mbuf *m, struct secpolicyindex *spidx) +{ + static const struct sockaddr_in template = { + sizeof (struct sockaddr_in), + AF_INET, + 0, { 0 }, { 0, 0, 0, 0, 0, 0, 0, 0 } + }; + + spidx->src.sin = template; + spidx->dst.sin = template; + + if (m->m_len < sizeof (struct ip)) { + m_copydata(m, offsetof(struct ip, ip_src), + sizeof (struct in_addr), + (caddr_t) &spidx->src.sin.sin_addr); + m_copydata(m, offsetof(struct ip, ip_dst), + sizeof (struct in_addr), + (caddr_t) &spidx->dst.sin.sin_addr); + } else { + struct ip *ip = mtod(m, struct ip *); + spidx->src.sin.sin_addr = ip->ip_src; + spidx->dst.sin.sin_addr = ip->ip_dst; + } + + spidx->prefs = sizeof(struct in_addr) << 3; + spidx->prefd = sizeof(struct in_addr) << 3; + + return 0; +} + +#ifdef INET6 +static void +ipsec6_get_ulp(m, spidx, needport) + struct mbuf *m; + struct secpolicyindex *spidx; + int needport; +{ + int off, nxt; + struct tcphdr th; + struct udphdr uh; + + /* sanity check */ + if (m == NULL) + panic("ipsec6_get_ulp: NULL pointer was passed.\n"); + + KEYDEBUG(KEYDEBUG_IPSEC_DUMP, + printf("ipsec6_get_ulp:\n"); kdebug_mbuf(m)); + + /* set default */ + spidx->ul_proto = IPSEC_ULPROTO_ANY; + ((struct sockaddr_in6 *)&spidx->src)->sin6_port = IPSEC_PORT_ANY; + ((struct sockaddr_in6 *)&spidx->dst)->sin6_port = IPSEC_PORT_ANY; + + nxt = -1; + off = ip6_lasthdr(m, 0, IPPROTO_IPV6, &nxt); + if (off < 0 || m->m_pkthdr.len < off) + return; + + switch (nxt) { + case IPPROTO_TCP: + spidx->ul_proto = nxt; + if (!needport) + break; + if (off + sizeof(struct tcphdr) > m->m_pkthdr.len) + break; + m_copydata(m, off, sizeof(th), (caddr_t)&th); + ((struct sockaddr_in6 *)&spidx->src)->sin6_port = th.th_sport; + ((struct sockaddr_in6 *)&spidx->dst)->sin6_port = th.th_dport; + break; + case IPPROTO_UDP: + spidx->ul_proto = nxt; + if (!needport) + break; + if (off + sizeof(struct udphdr) > m->m_pkthdr.len) + break; + m_copydata(m, off, sizeof(uh), (caddr_t)&uh); + ((struct sockaddr_in6 *)&spidx->src)->sin6_port = uh.uh_sport; + ((struct sockaddr_in6 *)&spidx->dst)->sin6_port = uh.uh_dport; + break; + case IPPROTO_ICMPV6: + default: + /* XXX intermediate headers??? */ + spidx->ul_proto = nxt; + break; + } +} + +/* assumes that m is sane */ +static int +ipsec6_setspidx_ipaddr(m, spidx) + struct mbuf *m; + struct secpolicyindex *spidx; +{ + struct ip6_hdr *ip6 = NULL; + struct ip6_hdr ip6buf; + struct sockaddr_in6 *sin6; + + if (m->m_len >= sizeof(*ip6)) + ip6 = mtod(m, struct ip6_hdr *); + else { + m_copydata(m, 0, sizeof(ip6buf), (caddr_t)&ip6buf); + ip6 = &ip6buf; + } + + sin6 = (struct sockaddr_in6 *)&spidx->src; + bzero(sin6, sizeof(*sin6)); + sin6->sin6_family = AF_INET6; + sin6->sin6_len = sizeof(struct sockaddr_in6); + bcopy(&ip6->ip6_src, &sin6->sin6_addr, sizeof(ip6->ip6_src)); + if (IN6_IS_SCOPE_LINKLOCAL(&ip6->ip6_src)) { + sin6->sin6_addr.s6_addr16[1] = 0; + sin6->sin6_scope_id = ntohs(ip6->ip6_src.s6_addr16[1]); + } + spidx->prefs = sizeof(struct in6_addr) << 3; + + sin6 = (struct sockaddr_in6 *)&spidx->dst; + bzero(sin6, sizeof(*sin6)); + sin6->sin6_family = AF_INET6; + sin6->sin6_len = sizeof(struct sockaddr_in6); + bcopy(&ip6->ip6_dst, &sin6->sin6_addr, sizeof(ip6->ip6_dst)); + if (IN6_IS_SCOPE_LINKLOCAL(&ip6->ip6_dst)) { + sin6->sin6_addr.s6_addr16[1] = 0; + sin6->sin6_scope_id = ntohs(ip6->ip6_dst.s6_addr16[1]); + } + spidx->prefd = sizeof(struct in6_addr) << 3; + + return 0; +} +#endif + +static void +ipsec_delpcbpolicy(p) + struct inpcbpolicy *p; +{ + free(p, M_SECA); +} + +/* initialize policy in PCB */ +int +ipsec_init_policy(so, pcb_sp) + struct socket *so; + struct inpcbpolicy **pcb_sp; +{ + struct inpcbpolicy *new; + + /* sanity check. */ + if (so == NULL || pcb_sp == NULL) + panic("ipsec_init_policy: NULL pointer was passed.\n"); + + new = (struct inpcbpolicy *) malloc(sizeof(struct inpcbpolicy), + M_SECA, M_NOWAIT|M_ZERO); + if (new == NULL) { + ipseclog((LOG_DEBUG, "ipsec_init_policy: No more memory.\n")); + return ENOBUFS; + } + + if (so->so_cred != 0 && so->so_cred->cr_uid == 0) + new->priv = 1; + else + new->priv = 0; + + if ((new->sp_in = KEY_NEWSP()) == NULL) { + ipsec_delpcbpolicy(new); + return ENOBUFS; + } + new->sp_in->state = IPSEC_SPSTATE_ALIVE; + new->sp_in->policy = IPSEC_POLICY_ENTRUST; + + if ((new->sp_out = KEY_NEWSP()) == NULL) { + KEY_FREESP(&new->sp_in); + ipsec_delpcbpolicy(new); + return ENOBUFS; + } + new->sp_out->state = IPSEC_SPSTATE_ALIVE; + new->sp_out->policy = IPSEC_POLICY_ENTRUST; + + *pcb_sp = new; + + return 0; +} + +/* copy old ipsec policy into new */ +int +ipsec_copy_policy(old, new) + struct inpcbpolicy *old, *new; +{ + struct secpolicy *sp; + + sp = ipsec_deepcopy_policy(old->sp_in); + if (sp) { + KEY_FREESP(&new->sp_in); + new->sp_in = sp; + } else + return ENOBUFS; + + sp = ipsec_deepcopy_policy(old->sp_out); + if (sp) { + KEY_FREESP(&new->sp_out); + new->sp_out = sp; + } else + return ENOBUFS; + + new->priv = old->priv; + + return 0; +} + +/* deep-copy a policy in PCB */ +static struct secpolicy * +ipsec_deepcopy_policy(src) + struct secpolicy *src; +{ + struct ipsecrequest *newchain = NULL; + struct ipsecrequest *p; + struct ipsecrequest **q; + struct ipsecrequest *r; + struct secpolicy *dst; + + if (src == NULL) + return NULL; + dst = KEY_NEWSP(); + if (dst == NULL) + return NULL; + + /* + * deep-copy IPsec request chain. This is required since struct + * ipsecrequest is not reference counted. + */ + q = &newchain; + for (p = src->req; p; p = p->next) { + *q = (struct ipsecrequest *)malloc(sizeof(struct ipsecrequest), + M_SECA, M_NOWAIT); + if (*q == NULL) + goto fail; + bzero(*q, sizeof(**q)); + (*q)->next = NULL; + + (*q)->saidx.proto = p->saidx.proto; + (*q)->saidx.mode = p->saidx.mode; + (*q)->level = p->level; + (*q)->saidx.reqid = p->saidx.reqid; + + bcopy(&p->saidx.src, &(*q)->saidx.src, sizeof((*q)->saidx.src)); + bcopy(&p->saidx.dst, &(*q)->saidx.dst, sizeof((*q)->saidx.dst)); + + (*q)->sav = NULL; + (*q)->sp = dst; + + q = &((*q)->next); + } + + dst->req = newchain; + dst->state = src->state; + dst->policy = src->policy; + /* do not touch the refcnt fields */ + + return dst; + +fail: + for (p = newchain; p; p = r) { + r = p->next; + free(p, M_SECA); + p = NULL; + } + return NULL; +} + +/* set policy and ipsec request if present. */ +static int +ipsec_set_policy(pcb_sp, optname, request, len, priv) + struct secpolicy **pcb_sp; + int optname; + caddr_t request; + size_t len; + int priv; +{ + struct sadb_x_policy *xpl; + struct secpolicy *newsp = NULL; + int error; + + /* sanity check. */ + if (pcb_sp == NULL || *pcb_sp == NULL || request == NULL) + return EINVAL; + if (len < sizeof(*xpl)) + return EINVAL; + xpl = (struct sadb_x_policy *)request; + + KEYDEBUG(KEYDEBUG_IPSEC_DUMP, + printf("ipsec_set_policy: passed policy\n"); + kdebug_sadb_x_policy((struct sadb_ext *)xpl)); + + /* check policy type */ + /* ipsec_set_policy() accepts IPSEC, ENTRUST and BYPASS. */ + if (xpl->sadb_x_policy_type == IPSEC_POLICY_DISCARD + || xpl->sadb_x_policy_type == IPSEC_POLICY_NONE) + return EINVAL; + + /* check privileged socket */ + if (priv == 0 && xpl->sadb_x_policy_type == IPSEC_POLICY_BYPASS) + return EACCES; + + /* allocation new SP entry */ + if ((newsp = key_msg2sp(xpl, len, &error)) == NULL) + return error; + + newsp->state = IPSEC_SPSTATE_ALIVE; + + /* clear old SP and set new SP */ + KEY_FREESP(pcb_sp); + *pcb_sp = newsp; + KEYDEBUG(KEYDEBUG_IPSEC_DUMP, + printf("ipsec_set_policy: new policy\n"); + kdebug_secpolicy(newsp)); + + return 0; +} + +static int +ipsec_get_policy(pcb_sp, mp) + struct secpolicy *pcb_sp; + struct mbuf **mp; +{ + + /* sanity check. */ + if (pcb_sp == NULL || mp == NULL) + return EINVAL; + + *mp = key_sp2msg(pcb_sp); + if (!*mp) { + ipseclog((LOG_DEBUG, "ipsec_get_policy: No more memory.\n")); + return ENOBUFS; + } + + (*mp)->m_type = MT_DATA; + KEYDEBUG(KEYDEBUG_IPSEC_DUMP, + printf("ipsec_get_policy:\n"); + kdebug_mbuf(*mp)); + + return 0; +} + +int +ipsec4_set_policy(inp, optname, request, len, priv) + struct inpcb *inp; + int optname; + caddr_t request; + size_t len; + int priv; +{ + struct sadb_x_policy *xpl; + struct secpolicy **pcb_sp; + + /* sanity check. */ + if (inp == NULL || request == NULL) + return EINVAL; + if (len < sizeof(*xpl)) + return EINVAL; + xpl = (struct sadb_x_policy *)request; + + /* select direction */ + switch (xpl->sadb_x_policy_dir) { + case IPSEC_DIR_INBOUND: + pcb_sp = &inp->inp_sp->sp_in; + break; + case IPSEC_DIR_OUTBOUND: + pcb_sp = &inp->inp_sp->sp_out; + break; + default: + ipseclog((LOG_ERR, "ipsec4_set_policy: invalid direction=%u\n", + xpl->sadb_x_policy_dir)); + return EINVAL; + } + + return ipsec_set_policy(pcb_sp, optname, request, len, priv); +} + +int +ipsec4_get_policy(inp, request, len, mp) + struct inpcb *inp; + caddr_t request; + size_t len; + struct mbuf **mp; +{ + struct sadb_x_policy *xpl; + struct secpolicy *pcb_sp; + + /* sanity check. */ + if (inp == NULL || request == NULL || mp == NULL) + return EINVAL; + KASSERT(inp->inp_sp != NULL, ("ipsec4_get_policy: null inp_sp")); + if (len < sizeof(*xpl)) + return EINVAL; + xpl = (struct sadb_x_policy *)request; + + /* select direction */ + switch (xpl->sadb_x_policy_dir) { + case IPSEC_DIR_INBOUND: + pcb_sp = inp->inp_sp->sp_in; + break; + case IPSEC_DIR_OUTBOUND: + pcb_sp = inp->inp_sp->sp_out; + break; + default: + ipseclog((LOG_ERR, "ipsec4_set_policy: invalid direction=%u\n", + xpl->sadb_x_policy_dir)); + return EINVAL; + } + + return ipsec_get_policy(pcb_sp, mp); +} + +/* delete policy in PCB */ +int +ipsec4_delete_pcbpolicy(inp) + struct inpcb *inp; +{ + KASSERT(inp != NULL, ("ipsec4_delete_pcbpolicy: null inp")); + + if (inp->inp_sp == NULL) + return 0; + + if (inp->inp_sp->sp_in != NULL) + KEY_FREESP(&inp->inp_sp->sp_in); + + if (inp->inp_sp->sp_out != NULL) + KEY_FREESP(&inp->inp_sp->sp_out); + + ipsec_delpcbpolicy(inp->inp_sp); + inp->inp_sp = NULL; + + return 0; +} + +#ifdef INET6 +int +ipsec6_set_policy(in6p, optname, request, len, priv) + struct in6pcb *in6p; + int optname; + caddr_t request; + size_t len; + int priv; +{ + struct sadb_x_policy *xpl; + struct secpolicy **pcb_sp; + + /* sanity check. */ + if (in6p == NULL || request == NULL) + return EINVAL; + if (len < sizeof(*xpl)) + return EINVAL; + xpl = (struct sadb_x_policy *)request; + + /* select direction */ + switch (xpl->sadb_x_policy_dir) { + case IPSEC_DIR_INBOUND: + pcb_sp = &in6p->in6p_sp->sp_in; + break; + case IPSEC_DIR_OUTBOUND: + pcb_sp = &in6p->in6p_sp->sp_out; + break; + default: + ipseclog((LOG_ERR, "ipsec6_set_policy: invalid direction=%u\n", + xpl->sadb_x_policy_dir)); + return EINVAL; + } + + return ipsec_set_policy(pcb_sp, optname, request, len, priv); +} + +int +ipsec6_get_policy(in6p, request, len, mp) + struct in6pcb *in6p; + caddr_t request; + size_t len; + struct mbuf **mp; +{ + struct sadb_x_policy *xpl; + struct secpolicy *pcb_sp; + + /* sanity check. */ + if (in6p == NULL || request == NULL || mp == NULL) + return EINVAL; + KASSERT(in6p->in6p_sp != NULL, ("ipsec6_get_policy: null in6p_sp")); + if (len < sizeof(*xpl)) + return EINVAL; + xpl = (struct sadb_x_policy *)request; + + /* select direction */ + switch (xpl->sadb_x_policy_dir) { + case IPSEC_DIR_INBOUND: + pcb_sp = in6p->in6p_sp->sp_in; + break; + case IPSEC_DIR_OUTBOUND: + pcb_sp = in6p->in6p_sp->sp_out; + break; + default: + ipseclog((LOG_ERR, "ipsec6_set_policy: invalid direction=%u\n", + xpl->sadb_x_policy_dir)); + return EINVAL; + } + + return ipsec_get_policy(pcb_sp, mp); +} + +int +ipsec6_delete_pcbpolicy(in6p) + struct in6pcb *in6p; +{ + KASSERT(in6p != NULL, ("ipsec6_delete_pcbpolicy: null in6p")); + + if (in6p->in6p_sp == NULL) + return 0; + + if (in6p->in6p_sp->sp_in != NULL) + KEY_FREESP(&in6p->in6p_sp->sp_in); + + if (in6p->in6p_sp->sp_out != NULL) + KEY_FREESP(&in6p->in6p_sp->sp_out); + + ipsec_delpcbpolicy(in6p->in6p_sp); + in6p->in6p_sp = NULL; + + return 0; +} +#endif + +/* + * return current level. + * Either IPSEC_LEVEL_USE or IPSEC_LEVEL_REQUIRE are always returned. + */ +u_int +ipsec_get_reqlevel(isr) + struct ipsecrequest *isr; +{ + u_int level = 0; + u_int esp_trans_deflev, esp_net_deflev; + u_int ah_trans_deflev, ah_net_deflev; + + KASSERT(isr != NULL && isr->sp != NULL, + ("ipsec_get_reqlevel: null argument")); + KASSERT(isr->sp->spidx.src.sa.sa_family == isr->sp->spidx.dst.sa.sa_family, + ("ipsec_get_reqlevel: af family mismatch, src %u, dst %u", + isr->sp->spidx.src.sa.sa_family, + isr->sp->spidx.dst.sa.sa_family)); + +/* XXX note that we have ipseclog() expanded here - code sync issue */ +#define IPSEC_CHECK_DEFAULT(lev) \ + (((lev) != IPSEC_LEVEL_USE && (lev) != IPSEC_LEVEL_REQUIRE \ + && (lev) != IPSEC_LEVEL_UNIQUE) \ + ? (ipsec_debug \ + ? log(LOG_INFO, "fixed system default level " #lev ":%d->%d\n",\ + (lev), IPSEC_LEVEL_REQUIRE) \ + : 0), \ + (lev) = IPSEC_LEVEL_REQUIRE, \ + (lev) \ + : (lev)) + + /* set default level */ + switch (((struct sockaddr *)&isr->sp->spidx.src)->sa_family) { +#ifdef INET + case AF_INET: + esp_trans_deflev = IPSEC_CHECK_DEFAULT(ip4_esp_trans_deflev); + esp_net_deflev = IPSEC_CHECK_DEFAULT(ip4_esp_net_deflev); + ah_trans_deflev = IPSEC_CHECK_DEFAULT(ip4_ah_trans_deflev); + ah_net_deflev = IPSEC_CHECK_DEFAULT(ip4_ah_net_deflev); + break; +#endif +#ifdef INET6 + case AF_INET6: + esp_trans_deflev = IPSEC_CHECK_DEFAULT(ip6_esp_trans_deflev); + esp_net_deflev = IPSEC_CHECK_DEFAULT(ip6_esp_net_deflev); + ah_trans_deflev = IPSEC_CHECK_DEFAULT(ip6_ah_trans_deflev); + ah_net_deflev = IPSEC_CHECK_DEFAULT(ip6_ah_net_deflev); + break; +#endif /* INET6 */ + default: + panic("key_get_reqlevel: unknown af %u", + isr->sp->spidx.src.sa.sa_family); + } + +#undef IPSEC_CHECK_DEFAULT + + /* set level */ + switch (isr->level) { + case IPSEC_LEVEL_DEFAULT: + switch (isr->saidx.proto) { + case IPPROTO_ESP: + if (isr->saidx.mode == IPSEC_MODE_TUNNEL) + level = esp_net_deflev; + else + level = esp_trans_deflev; + break; + case IPPROTO_AH: + if (isr->saidx.mode == IPSEC_MODE_TUNNEL) + level = ah_net_deflev; + else + level = ah_trans_deflev; + 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("ipsec_get_reqlevel: " + "Illegal protocol defined %u\n", + 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("ipsec_get_reqlevel: Illegal IPsec level %u\n", + isr->level); + } + + return level; +} + +/* + * Check security policy requirements against the actual + * packet contents. Return one if the packet should be + * reject as "invalid"; otherwiser return zero to have the + * packet treated as "valid". + * + * OUT: + * 0: valid + * 1: invalid + */ +int +ipsec_in_reject(struct secpolicy *sp, struct mbuf *m) +{ + struct ipsecrequest *isr; + int need_auth; + + KEYDEBUG(KEYDEBUG_IPSEC_DATA, + printf("ipsec_in_reject: using SP\n"); + kdebug_secpolicy(sp)); + + /* check policy */ + switch (sp->policy) { + case IPSEC_POLICY_DISCARD: + return 1; + case IPSEC_POLICY_BYPASS: + case IPSEC_POLICY_NONE: + return 0; + } + + KASSERT(sp->policy == IPSEC_POLICY_IPSEC, + ("ipsec_in_reject: invalid policy %u", sp->policy)); + + /* XXX should compare policy against ipsec header history */ + + need_auth = 0; + for (isr = sp->req; isr != NULL; isr = isr->next) { + if (ipsec_get_reqlevel(isr) != IPSEC_LEVEL_REQUIRE) + continue; + switch (isr->saidx.proto) { + case IPPROTO_ESP: + if ((m->m_flags & M_DECRYPTED) == 0) { + KEYDEBUG(KEYDEBUG_IPSEC_DUMP, + printf("ipsec_in_reject: ESP m_flags:%x\n", + m->m_flags)); + return 1; + } + + if (!need_auth && + isr->sav != NULL && + isr->sav->tdb_authalgxform != NULL && + (m->m_flags & M_AUTHIPDGM) == 0) { + KEYDEBUG(KEYDEBUG_IPSEC_DUMP, + printf("ipsec_in_reject: ESP/AH m_flags:%x\n", + m->m_flags)); + return 1; + } + break; + case IPPROTO_AH: + need_auth = 1; + if ((m->m_flags & M_AUTHIPHDR) == 0) { + KEYDEBUG(KEYDEBUG_IPSEC_DUMP, + printf("ipsec_in_reject: AH m_flags:%x\n", + m->m_flags)); + return 1; + } + break; + case IPPROTO_IPCOMP: + /* + * we don't really care, as IPcomp document + * says that we shouldn't compress small + * packets, IPComp policy should always be + * treated as being in "use" level. + */ + break; + } + } + return 0; /* valid */ +} + +/* + * Check AH/ESP integrity. + * This function is called from tcp_input(), udp_input(), + * and {ah,esp}4_input for tunnel mode + */ +int +ipsec4_in_reject(m, inp) + struct mbuf *m; + struct inpcb *inp; +{ + struct secpolicy *sp; + int error; + int result; + + KASSERT(m != NULL, ("ipsec4_in_reject_so: null mbuf")); + + /* get SP for this packet. + * When we are called from ip_forward(), we call + * ipsec_getpolicybyaddr() with IP_FORWARDING flag. + */ + if (inp == NULL) + sp = ipsec_getpolicybyaddr(m, IPSEC_DIR_INBOUND, IP_FORWARDING, &error); + else + sp = ipsec_getpolicybysock(m, IPSEC_DIR_INBOUND, inp, &error); + + if (sp != NULL) { + result = ipsec_in_reject(sp, m); + if (result) + newipsecstat.ips_in_polvio++; + KEY_FREESP(&sp); + } else { + result = 0; /* XXX should be panic ? + * -> No, there may be error. */ + } + return result; +} + +#ifdef INET6 +/* + * Check AH/ESP integrity. + * This function is called from tcp6_input(), udp6_input(), + * and {ah,esp}6_input for tunnel mode + */ +int +ipsec6_in_reject(m, inp) + struct mbuf *m; + struct inpcb *inp; +{ + struct secpolicy *sp = NULL; + int error; + int result; + + /* sanity check */ + if (m == NULL) + return 0; /* XXX should be panic ? */ + + /* get SP for this packet. + * When we are called from ip_forward(), we call + * ipsec_getpolicybyaddr() with IP_FORWARDING flag. + */ + if (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); + if (result) + newipsecstat.ips_in_polvio++; + KEY_FREESP(&sp); + } else { + result = 0; + } + return result; +} +#endif + +/* + * compute the byte size to be occupied by IPsec header. + * in case it is tunneled, it includes the size of outer IP header. + * NOTE: SP passed is free in this function. + */ +static size_t +ipsec_hdrsiz(struct secpolicy *sp) +{ + struct ipsecrequest *isr; + size_t siz; + + KEYDEBUG(KEYDEBUG_IPSEC_DATA, + printf("ipsec_hdrsiz: using SP\n"); + kdebug_secpolicy(sp)); + + switch (sp->policy) { + case IPSEC_POLICY_DISCARD: + case IPSEC_POLICY_BYPASS: + case IPSEC_POLICY_NONE: + return 0; + } + + KASSERT(sp->policy == IPSEC_POLICY_IPSEC, + ("ipsec_hdrsiz: invalid policy %u", sp->policy)); + + siz = 0; + for (isr = sp->req; isr != NULL; isr = isr->next) { + size_t clen = 0; + + switch (isr->saidx.proto) { + case IPPROTO_ESP: + clen = esp_hdrsiz(isr->sav); + break; + case IPPROTO_AH: + clen = ah_hdrsiz(isr->sav); + break; + case IPPROTO_IPCOMP: + clen = sizeof(struct ipcomp); + break; + } + + if (isr->saidx.mode == IPSEC_MODE_TUNNEL) { + switch (isr->saidx.dst.sa.sa_family) { + case AF_INET: + clen += sizeof(struct ip); + break; +#ifdef INET6 + case AF_INET6: + clen += sizeof(struct ip6_hdr); + break; +#endif + default: + ipseclog((LOG_ERR, "ipsec_hdrsiz: " + "unknown AF %d in IPsec tunnel SA\n", + ((struct sockaddr *)&isr->saidx.dst)->sa_family)); + break; + } + } + siz += clen; + } + + return siz; +} + +/* This function is called from ip_forward() and ipsec4_hdrsize_tcp(). */ +size_t +ipsec4_hdrsiz(m, dir, inp) + struct mbuf *m; + u_int dir; + struct inpcb *inp; +{ + struct secpolicy *sp; + int error; + size_t size; + + KASSERT(m != NULL, ("ipsec4_hdrsiz: null mbuf")); + KASSERT(inp == NULL || inp->inp_socket != NULL, + ("ipsec4_hdrsize: socket w/o inpcb")); + + /* get SP for this packet. + * When we are called from ip_forward(), we call + * ipsec_getpolicybyaddr() with IP_FORWARDING flag. + */ + if (inp == NULL) + sp = ipsec_getpolicybyaddr(m, dir, IP_FORWARDING, &error); + else + sp = ipsec_getpolicybysock(m, dir, inp, &error); + + if (sp != NULL) { + size = ipsec_hdrsiz(sp); + KEYDEBUG(KEYDEBUG_IPSEC_DATA, + printf("ipsec4_hdrsiz: size:%lu.\n", + (unsigned long)size)); + + KEY_FREESP(&sp); + } else { + size = 0; /* XXX should be panic ? */ + } + return size; +} + +#ifdef INET6 +/* This function is called from ipsec6_hdrsize_tcp(), + * and maybe from ip6_forward.() + */ +size_t +ipsec6_hdrsiz(m, dir, in6p) + struct mbuf *m; + u_int dir; + struct in6pcb *in6p; +{ + struct secpolicy *sp; + int error; + size_t size; + + KASSERT(m != NULL, ("ipsec6_hdrsiz: null mbuf")); + KASSERT(in6p == NULL || in6p->in6p_socket != NULL, + ("ipsec6_hdrsize: socket w/o inpcb")); + + /* get SP for this packet */ + /* XXX Is it right to call with IP_FORWARDING. */ + if (in6p == NULL) + sp = ipsec_getpolicybyaddr(m, dir, IP_FORWARDING, &error); + else + sp = ipsec_getpolicybysock(m, dir, in6p, &error); + + if (sp == NULL) + return 0; + size = ipsec_hdrsiz(sp); + KEYDEBUG(KEYDEBUG_IPSEC_DATA, + printf("ipsec6_hdrsiz: size:%lu.\n", (unsigned long)size)); + KEY_FREESP(&sp); + + return size; +} +#endif /*INET6*/ + +/* + * Check the variable replay window. + * ipsec_chkreplay() performs replay check before ICV verification. + * ipsec_updatereplay() updates replay bitmap. This must be called after + * ICV verification (it also performs replay check, which is usually done + * beforehand). + * 0 (zero) is returned if packet disallowed, 1 if packet permitted. + * + * based on RFC 2401. + */ +int +ipsec_chkreplay(seq, sav) + u_int32_t seq; + struct secasvar *sav; +{ + const struct secreplay *replay; + u_int32_t diff; + int fr; + u_int32_t wsizeb; /* constant: bits of window size */ + int frlast; /* constant: last frame */ + +#if 0 + SPLASSERT(net, "ipsec_chkreplay"); +#endif + + KASSERT(sav != NULL, ("ipsec_chkreplay: Null SA")); + KASSERT(sav->replay != NULL, ("ipsec_chkreplay: Null replay state")); + + replay = sav->replay; + + if (replay->wsize == 0) + return 1; /* no need to check replay. */ + + /* constant */ + frlast = replay->wsize - 1; + wsizeb = replay->wsize << 3; + + /* sequence number of 0 is invalid */ + if (seq == 0) + return 0; + + /* first time is always okay */ + if (replay->count == 0) + return 1; + + if (seq > replay->lastseq) { + /* larger sequences are okay */ + return 1; + } else { + /* seq is equal or less than lastseq. */ + diff = replay->lastseq - seq; + + /* over range to check, i.e. too old or wrapped */ + if (diff >= wsizeb) + return 0; + + fr = frlast - diff / 8; + + /* this packet already seen ? */ + if ((replay->bitmap)[fr] & (1 << (diff % 8))) + return 0; + + /* out of order but good */ + return 1; + } +} + +/* + * check replay counter whether to update or not. + * OUT: 0: OK + * 1: NG + */ +int +ipsec_updatereplay(seq, sav) + u_int32_t seq; + struct secasvar *sav; +{ + struct secreplay *replay; + u_int32_t diff; + int fr; + u_int32_t wsizeb; /* constant: bits of window size */ + int frlast; /* constant: last frame */ + +#if 0 + SPLASSERT(net, "ipsec_updatereplay"); +#endif + + KASSERT(sav != NULL, ("ipsec_updatereplay: Null SA")); + KASSERT(sav->replay != NULL, ("ipsec_updatereplay: Null replay state")); + + replay = sav->replay; + + if (replay->wsize == 0) + goto ok; /* no need to check replay. */ + + /* constant */ + frlast = replay->wsize - 1; + wsizeb = replay->wsize << 3; + + /* sequence number of 0 is invalid */ + if (seq == 0) + return 1; + + /* first time */ + if (replay->count == 0) { + replay->lastseq = seq; + bzero(replay->bitmap, replay->wsize); + (replay->bitmap)[frlast] = 1; + goto ok; + } + + if (seq > replay->lastseq) { + /* seq is larger than lastseq. */ + diff = seq - replay->lastseq; + + /* new larger sequence number */ + if (diff < wsizeb) { + /* In window */ + /* set bit for this packet */ + vshiftl(replay->bitmap, diff, replay->wsize); + (replay->bitmap)[frlast] |= 1; + } else { + /* this packet has a "way larger" */ + bzero(replay->bitmap, replay->wsize); + (replay->bitmap)[frlast] = 1; + } + replay->lastseq = seq; + + /* larger is good */ + } else { + /* seq is equal or less than lastseq. */ + diff = replay->lastseq - seq; + + /* over range to check, i.e. too old or wrapped */ + if (diff >= wsizeb) + return 1; + + fr = frlast - diff / 8; + + /* this packet already seen ? */ + if ((replay->bitmap)[fr] & (1 << (diff % 8))) + return 1; + + /* mark as seen */ + (replay->bitmap)[fr] |= (1 << (diff % 8)); + + /* out of order but good */ + } + +ok: + if (replay->count == ~0) { + + /* set overflow flag */ + replay->overflow++; + + /* don't increment, no more packets accepted */ + if ((sav->flags & SADB_X_EXT_CYCSEQ) == 0) + return 1; + + ipseclog((LOG_WARNING, "replay counter made %d cycle. %s\n", + replay->overflow, ipsec_logsastr(sav))); + } + + replay->count++; + + return 0; +} + +/* + * shift variable length bunffer to left. + * IN: bitmap: pointer to the buffer + * nbit: the number of to shift. + * wsize: buffer size (bytes). + */ +static void +vshiftl(bitmap, nbit, wsize) + unsigned char *bitmap; + int nbit, wsize; +{ + int s, j, i; + unsigned char over; + + for (j = 0; j < nbit; j += 8) { + s = (nbit - j < 8) ? (nbit - j): 8; + bitmap[0] <<= s; + for (i = 1; i < wsize; i++) { + over = (bitmap[i] >> (8 - s)); + bitmap[i] <<= s; + bitmap[i-1] |= over; + } + } + + return; +} + +/* Return a printable string for the IPv4 address. */ +static char * +inet_ntoa4(struct in_addr ina) +{ + static char buf[4][4 * sizeof "123" + 4]; + unsigned char *ucp = (unsigned char *) &ina; + static int i = 3; + + i = (i + 1) % 4; + sprintf(buf[i], "%d.%d.%d.%d", ucp[0] & 0xff, ucp[1] & 0xff, + ucp[2] & 0xff, ucp[3] & 0xff); + return (buf[i]); +} + +/* Return a printable string for the address. */ +char * +ipsec_address(union sockaddr_union* sa) +{ + switch (sa->sa.sa_family) { +#if INET + case AF_INET: + return inet_ntoa4(sa->sin.sin_addr); +#endif /* INET */ + +#if INET6 + case AF_INET6: + return ip6_sprintf(&sa->sin6.sin6_addr); +#endif /* INET6 */ + + default: + return "(unknown address family)"; + } +} + +const char * +ipsec_logsastr(sav) + struct secasvar *sav; +{ + static char buf[256]; + char *p; + struct secasindex *saidx = &sav->sah->saidx; + + KASSERT(saidx->src.sa.sa_family == saidx->dst.sa.sa_family, + ("ipsec_logsastr: address family mismatch")); + + p = buf; + snprintf(buf, sizeof(buf), "SA(SPI=%u ", (u_int32_t)ntohl(sav->spi)); + while (p && *p) + p++; + /* NB: only use ipsec_address on one address at a time */ + snprintf(p, sizeof (buf) - (p - buf), "src=%s ", + ipsec_address(&saidx->src)); + while (p && *p) + p++; + snprintf(p, sizeof (buf) - (p - buf), "dst=%s)", + ipsec_address(&saidx->dst)); + + return buf; +} + +void +ipsec_dumpmbuf(m) + struct mbuf *m; +{ + int totlen; + int i; + u_char *p; + + totlen = 0; + printf("---\n"); + while (m) { + p = mtod(m, u_char *); + for (i = 0; i < m->m_len; i++) { + printf("%02x ", p[i]); + totlen++; + if (totlen % 16 == 0) + printf("\n"); + } + m = m->m_next; + } + if (totlen % 16 != 0) + printf("\n"); + printf("---\n"); +} + +/* 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; + + for (xsp = xforms; xsp; xsp = xsp->xf_next) + if (xsp->xf_type == xftype) + return (*xsp->xf_init)(sav, xsp); + return EINVAL; +} diff --git a/sys/netipsec/ipsec.h b/sys/netipsec/ipsec.h new file mode 100644 index 0000000..93b1e23 --- /dev/null +++ b/sys/netipsec/ipsec.h @@ -0,0 +1,389 @@ +/* $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_H_ +#define _NETIPSEC_IPSEC_H_ + +#if defined(_KERNEL) && !defined(_LKM) && !defined(KLD_MODULE) +#include "opt_inet.h" +#include "opt_ipsec.h" +#endif + +#include <net/pfkeyv2.h> +#include <netipsec/keydb.h> + +#ifdef _KERNEL + +/* + * 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; + + 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_int policy; /* DISCARD, NONE or IPSEC, see keyv2.h */ + 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. + */ + long created; /* time created the policy */ + long 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 */ +}; + +/* 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 */ +}; + +/* 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; + + long 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 + +/* + * 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 + +/* old 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]; +}; + +/* statistics for ipsec processing */ +struct newipsecstat { + 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; +}; + +extern int ipsec_debug; + +extern struct newipsecstat newipsecstat; +extern struct secpolicy ip4_def_policy; +extern int ip4_esp_trans_deflev; +extern int ip4_esp_net_deflev; +extern int ip4_ah_trans_deflev; +extern int ip4_ah_net_deflev; +extern int ip4_ah_cleartos; +extern int ip4_ah_offsetmask; +extern int ip4_ipsec_dfbit; +extern int ip4_ipsec_ecn; +extern int ip4_esp_randpad; +extern int crypto_support; + +#define ipseclog(x) do { if (ipsec_debug) log x; } while (0) +/* for openbsd compatibility */ +#define DPRINTF(x) do { if (ipsec_debug) printf x; } while (0) + +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_getpolicybysock(struct mbuf *, u_int, + struct inpcb *, int *); +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 ipsec4_set_policy __P((struct inpcb *inp, int optname, + caddr_t request, size_t len, int priv)); +extern int ipsec4_get_policy __P((struct inpcb *inpcb, caddr_t request, + size_t len, struct mbuf **mp)); +extern int ipsec4_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 ipsec4_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 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_clone(struct mbuf *m0); +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); +#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_H_ */ diff --git a/sys/netipsec/ipsec6.h b/sys/netipsec/ipsec6.h new file mode 100644 index 0000000..d2315965 --- /dev/null +++ b/sys/netipsec/ipsec6.h @@ -0,0 +1,89 @@ +/* $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_H_ +#define _NETIPSEC_IPSEC6_H_ + +#include <net/pfkeyv2.h> +#include <netipsec/keydb.h> + +#ifdef _KERNEL +extern int ip6_esp_trans_deflev; +extern int ip6_esp_net_deflev; +extern int ip6_ah_trans_deflev; +extern int ip6_ah_net_deflev; +extern int ip6_ipsec_ecn; +extern int ip6_esp_randpad; + +struct inpcb; + +/* KAME compatibility shims */ +#define ipsec6_getpolicybyaddr ipsec_getpolicybyaddr +#define ipsec6_getpolicybysock ipsec_getpolicybysock +#define ipsec6stat newipsecstat +#define out_inval ips_out_inval +#define in_polvio ips_in_polvio +#define out_polvio ips_out_polvio +#define key_freesp(_x) KEY_FREESP(&_x) + +extern int ipsec6_delete_pcbpolicy __P((struct inpcb *)); +extern int ipsec6_set_policy __P((struct inpcb *inp, int optname, + caddr_t request, size_t len, int priv)); +extern int ipsec6_get_policy + __P((struct inpcb *inp, caddr_t request, size_t len, struct mbuf **mp)); +extern int ipsec6_in_reject __P((struct mbuf *, struct inpcb *)); + +struct tcp6cb; + +extern size_t ipsec6_hdrsiz __P((struct mbuf *, u_int, 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_H_*/ diff --git a/sys/netipsec/ipsec_input.c b/sys/netipsec/ipsec_input.c new file mode 100644 index 0000000..1d5a3c9 --- /dev/null +++ b/sys/netipsec/ipsec_input.c @@ -0,0 +1,728 @@ +/* $FreeBSD$ */ +/* $KAME: ipsec.c,v 1.103 2001/05/24 07:14:18 sakane Exp $ */ + +/* + * IPsec input processing. + */ + +#include "opt_inet.h" +#include "opt_inet6.h" +#include "opt_ipsec.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/malloc.h> +#include <sys/mbuf.h> +#include <sys/domain.h> +#include <sys/protosw.h> +#include <sys/socket.h> +#include <sys/errno.h> +#include <sys/syslog.h> + +#include <net/if.h> +#include <net/route.h> +#include <net/netisr.h> + +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <netinet/ip_var.h> +#include <netinet/in_var.h> + +#include <netinet/ip6.h> +#ifdef INET6 +#include <netinet6/ip6_var.h> +#endif +#include <netinet/in_pcb.h> +#ifdef INET6 +#include <netinet/icmp6.h> +#endif + +#include <netipsec/ipsec.h> +#ifdef INET6 +#include <netipsec/ipsec6.h> +#endif +#include <netipsec/ah_var.h> +#include <netipsec/esp.h> +#include <netipsec/esp_var.h> +#include <netipsec/ipcomp_var.h> + +#include <netipsec/key.h> +#include <netipsec/keydb.h> + +#include <netipsec/xform.h> +#include <netinet6/ip6protosw.h> + +#include <machine/in_cksum.h> +#include <machine/stdarg.h> + +#include <net/net_osdep.h> + +#define IPSEC_ISTAT(p,x,y,z) ((p) == IPPROTO_ESP ? (x)++ : \ + (p) == IPPROTO_AH ? (y)++ : (z)++) + +/* + * 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 s, error; + + IPSEC_ISTAT(sproto, espstat.esps_input, ahstat.ahs_input, + ipcompstat.ipcomps_input); + + KASSERT(m != NULL, ("ipsec_common_input: null packet")); + + if ((sproto == IPPROTO_ESP && !esp_enable) || + (sproto == IPPROTO_AH && !ah_enable) || + (sproto == IPPROTO_IPCOMP && !ipcomp_enable)) { + m_freem(m); + IPSEC_ISTAT(sproto, espstat.esps_pdrops, ahstat.ahs_pdrops, + ipcompstat.ipcomps_pdrops); + return EOPNOTSUPP; + } + + if (m->m_pkthdr.len - skip < 2 * sizeof (u_int32_t)) { + m_freem(m); + IPSEC_ISTAT(sproto, espstat.esps_hdrops, ahstat.ahs_hdrops, + ipcompstat.ipcomps_hdrops); + DPRINTF(("ipsec_common_input: packet too small\n")); + 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); + 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(("ipsec_common_input: unsupported protocol " + "family %u\n", af)); + m_freem(m); + IPSEC_ISTAT(sproto, espstat.esps_nopf, ahstat.ahs_nopf, + ipcompstat.ipcomps_nopf); + return EPFNOSUPPORT; + } + + s = splnet(); + + /* NB: only pass dst since key_allocsa follows RFC2401 */ + sav = KEY_ALLOCSA(&dst_address, sproto, spi); + if (sav == NULL) { + DPRINTF(("ipsec_common_input: no key association found for" + " SA %s/%08lx/%u\n", + ipsec_address(&dst_address), + (u_long) ntohl(spi), sproto)); + IPSEC_ISTAT(sproto, espstat.esps_notdb, ahstat.ahs_notdb, + ipcompstat.ipcomps_notdb); + splx(s); + m_freem(m); + return ENOENT; + } + + if (sav->tdb_xform == NULL) { + DPRINTF(("ipsec_common_input: attempted to use uninitialized" + " SA %s/%08lx/%u\n", + ipsec_address(&dst_address), + (u_long) ntohl(spi), sproto)); + IPSEC_ISTAT(sproto, espstat.esps_noxform, ahstat.ahs_noxform, + ipcompstat.ipcomps_noxform); + KEY_FREESAV(&sav); + splx(s); + 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); + splx(s); + 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); +} + +/* + * 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; + +#if 0 + SPLASSERT(net, "ipsec4_common_input_cb"); +#endif + + KASSERT(m != NULL, ("ipsec4_common_input_cb: null mbuf")); + KASSERT(sav != NULL, ("ipsec4_common_input_cb: null SA")); + KASSERT(sav->sah != NULL, ("ipsec4_common_input_cb: null SAH")); + saidx = &sav->sah->saidx; + af = saidx->dst.sa.sa_family; + KASSERT(af == AF_INET, ("ipsec4_common_input_cb: unexpected af %u",af)); + sproto = saidx->proto; + KASSERT(sproto == IPPROTO_ESP || sproto == IPPROTO_AH || + sproto == IPPROTO_IPCOMP, + ("ipsec4_common_input_cb: unexpected security protocol %u", + sproto)); + + /* Sanity check */ + if (m == NULL) { + DPRINTF(("ipsec4_common_input_cb: null mbuf")); + IPSEC_ISTAT(sproto, espstat.esps_badkcr, ahstat.ahs_badkcr, + 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(("ipsec4_common_input_cb: processing failed " + "for SA %s/%08lx\n", + ipsec_address(&sav->sah->saidx.dst), + (u_long) ntohl(sav->spi))); + IPSEC_ISTAT(sproto, espstat.esps_hdrops, ahstat.ahs_hdrops, + 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; + + /* IP-in-IP encapsulation */ + if (prot == IPPROTO_IPIP) { + struct ip ipn; + + /* ipn will now contain the inner IPv4 header */ + m_copydata(m, ip->ip_hl << 2, sizeof(struct ip), + (caddr_t) &ipn); + +#ifdef notyet + /* 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(("ipsec4_common_input_cb: inner " + "source address %s doesn't correspond to " + "expected proxy source %s, SA %s/%08lx\n", + inet_ntoa4(ipn.ip_src), + ipsp_address(saidx->proxy), + ipsp_address(saidx->dst), + (u_long) ntohl(sav->spi))); + + IPSEC_ISTAT(sproto, espstat.esps_pdrops, + ahstat.ahs_pdrops, + ipcompstat.ipcomps_pdrops); + error = EACCES; + goto bad; + } +#endif /*XXX*/ + } +#if INET6 + /* IPv6-in-IP encapsulation. */ + if (prot == IPPROTO_IPV6) { + struct ip6_hdr ip6n; + + /* ip6n will now contain the inner IPv6 header. */ + m_copydata(m, ip->ip_hl << 2, sizeof(struct ip6_hdr), + (caddr_t) &ip6n); + +#ifdef notyet + /* + * 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(("ipsec4_common_input_cb: inner " + "source address %s doesn't correspond to " + "expected proxy source %s, SA %s/%08lx\n", + ip6_sprintf(&ip6n.ip6_src), + ipsec_address(&saidx->proxy), + ipsec_address(&saidx->dst), + (u_long) ntohl(sav->spi))); + + IPSEC_ISTAT(sproto, espstat.esps_pdrops, + ahstat.ahs_pdrops, + ipcompstat.ipcomps_pdrops); + error = EACCES; + goto bad; + } +#endif /*XXX*/ + } +#endif /* INET6 */ + + /* + * 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(("ipsec4_common_input_cb: failed to get tag\n")); + IPSEC_ISTAT(sproto, espstat.esps_hdrops, + ahstat.ahs_hdrops, 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; + + m_tag_prepend(m, mtag); + } else { + 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 */ + + /* + * Re-dispatch via software interrupt. + */ + if (!IF_HANDOFF(&ipintrq, m, NULL)) { + IPSEC_ISTAT(sproto, espstat.esps_qfull, ahstat.ahs_qfull, + ipcompstat.ipcomps_qfull); + + DPRINTF(("ipsec4_common_input_cb: queue full; " + "proto %u packet dropped\n", sproto)); + return ENOBUFS; + } + schednetisr(NETISR_IP); + return 0; +bad: + m_freem(m); + return error; +} +#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(("ipsec6_common_input: bad offset %u\n", *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; + KASSERT(l > 0, ("ah6_input: l went zero or negative")); + } while (protoff + l < *offp); + + /* Malformed packet check */ + if (protoff + l != *offp) { + DPRINTF(("ipsec6_common_input: bad packet header chain, " + "protoff %u, l %u, off %u\n", protoff, l, *offp)); + IPSEC_ISTAT(proto, espstat.esps_hdrops, + ahstat.ahs_hdrops, + 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; +} + +void +esp6_ctlinput(int cmd, struct sockaddr *sa, void *d) +{ + 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) { + struct ip6ctlparam *ip6cp = (struct ip6ctlparam *)d; + struct mbuf *m = ip6cp->ip6c_m; + int off = ip6cp->ip6c_off; + + 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 */ + } +} + +/* + * 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; + + KASSERT(m != NULL, ("ipsec6_common_input_cb: null mbuf")); + KASSERT(sav != NULL, ("ipsec6_common_input_cb: null SA")); + KASSERT(sav->sah != NULL, ("ipsec6_common_input_cb: null SAH")); + saidx = &sav->sah->saidx; + af = saidx->dst.sa.sa_family; + KASSERT(af == AF_INET6, + ("ipsec6_common_input_cb: unexpected af %u", af)); + sproto = saidx->proto; + KASSERT(sproto == IPPROTO_ESP || sproto == IPPROTO_AH || + sproto == IPPROTO_IPCOMP, + ("ipsec6_common_input_cb: unexpected security protocol %u", + sproto)); + + /* Sanity check */ + if (m == NULL) { + DPRINTF(("ipsec4_common_input_cb: null mbuf")); + IPSEC_ISTAT(sproto, espstat.esps_badkcr, ahstat.ahs_badkcr, + 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(("ipsec_common_input_cb: processing failed " + "for SA %s/%08lx\n", ipsec_address(&sav->sah->saidx.dst), + (u_long) ntohl(sav->spi))); + + IPSEC_ISTAT(sproto, espstat.esps_hdrops, ahstat.ahs_hdrops, + 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 INET + /* IP-in-IP encapsulation */ + if (prot == IPPROTO_IPIP) { + struct ip ipn; + + /* ipn will now contain the inner IPv4 header */ + m_copydata(m, skip, sizeof(struct ip), (caddr_t) &ipn); + +#ifdef notyet + /* + * 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(("ipsec_common_input_cb: inner " + "source address %s doesn't correspond to " + "expected proxy source %s, SA %s/%08lx\n", + inet_ntoa4(ipn.ip_src), + ipsec_address(&saidx->proxy), + ipsec_address(&saidx->dst), + (u_long) ntohl(sav->spi))); + + IPSEC_ISTATsproto, (espstat.esps_pdrops, + ahstat.ahs_pdrops, ipcompstat.ipcomps_pdrops); + error = EACCES; + goto bad; + } +#endif /*XXX*/ + } +#endif /* INET */ + + /* IPv6-in-IP encapsulation */ + if (prot == IPPROTO_IPV6) { + struct ip6_hdr ip6n; + + /* ip6n will now contain the inner IPv6 header. */ + m_copydata(m, skip, sizeof(struct ip6_hdr), + (caddr_t) &ip6n); + +#ifdef notyet + /* + * 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(("ipsec_common_input_cb: inner " + "source address %s doesn't correspond to " + "expected proxy source %s, SA %s/%08lx\n", + ip6_sprintf(&ip6n.ip6_src), + ipsec_address(&saidx->proxy), + ipsec_address(&saidx->dst), + (u_long) ntohl(sav->spi))); + + IPSEC_ISTAT(sproto, espstat.esps_pdrops, + ahstat.ahs_pdrops, 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(("ipsec_common_input_cb: failed to " + "get tag\n")); + IPSEC_ISTAT(sproto, espstat.esps_hdrops, + ahstat.ahs_hdrops, 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; + + m_tag_prepend(m, mtag); + } else { + mt->m_tag_id = PACKET_TAG_IPSEC_IN_DONE; + /* XXX do we need to mark m_flags??? */ + } + + key_sa_recordxfer(sav, m); + + /* 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 (ip6_hdrnestlimit && (++nest > ip6_hdrnestlimit)) { + 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) { + 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; +} +#endif /* INET6 */ diff --git a/sys/netipsec/ipsec_mbuf.c b/sys/netipsec/ipsec_mbuf.c new file mode 100644 index 0000000..cf85896 --- /dev/null +++ b/sys/netipsec/ipsec_mbuf.c @@ -0,0 +1,401 @@ +/* $FreeBSD$ */ + +/* + * IPsec-specific mbuf routines. + */ + +#include "opt_param.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/mbuf.h> +#include <sys/socket.h> + +#include <net/route.h> +#include <netinet/in.h> + +#include <netipsec/ipsec.h> + +extern struct mbuf *m_getptr(struct mbuf *, int, int *); + +/* + * Create a writable copy of the mbuf chain. While doing this + * we compact the chain with a goal of producing a chain with + * at most two mbufs. The second mbuf in this chain is likely + * to be a cluster. The primary purpose of this work is to create + * a writable packet for encryption, compression, etc. The + * secondary goal is to linearize the data so the data can be + * passed to crypto hardware in the most efficient manner possible. + */ +struct mbuf * +m_clone(struct mbuf *m0) +{ + struct mbuf *m, *mprev; + + KASSERT(m0 != NULL, ("m_clone: null mbuf")); + + mprev = NULL; + for (m = m0; m != NULL; m = mprev->m_next) { + /* + * Regular mbufs are ignored unless there's a cluster + * in front of it that we can use to coalesce. We do + * the latter mainly so later clusters can be coalesced + * also w/o having to handle them specially (i.e. convert + * mbuf+cluster -> cluster). This optimization is heavily + * influenced by the assumption that we're running over + * Ethernet where MCBYTES is large enough that the max + * packet size will permit lots of coalescing into a + * single cluster. This in turn permits efficient + * crypto operations, especially when using hardware. + */ + if ((m->m_flags & M_EXT) == 0) { + if (mprev && (mprev->m_flags & M_EXT) && + m->m_len <= M_TRAILINGSPACE(mprev)) { + /* XXX: this ignores mbuf types */ + memcpy(mtod(mprev, caddr_t) + mprev->m_len, + mtod(m, caddr_t), m->m_len); + mprev->m_len += m->m_len; + mprev->m_next = m->m_next; /* unlink from chain */ + m_free(m); /* reclaim mbuf */ + newipsecstat.ips_mbcoalesced++; + } else { + mprev = m; + } + continue; + } + /* + * Cluster'd mbufs are left alone (for now). + */ + if (!MEXT_IS_REF(m)) { + mprev = m; + continue; + } + /* + * Not writable, replace with a copy or coalesce with + * the previous mbuf if possible (since we have to copy + * it anyway, we try to reduce the number of mbufs and + * clusters so that future work is easier). + */ + /* XXX why can M_PKTHDR be set past the first mbuf? */ + KASSERT(m->m_flags & M_EXT, + ("m_clone: m_flags 0x%x", m->m_flags)); + /* NB: we only coalesce into a cluster */ + if (mprev == NULL || (mprev->m_flags & M_EXT) == 0 || + m->m_len > M_TRAILINGSPACE(mprev)) { + struct mbuf *n; + + /* + * Allocate a new page, copy the data to the front + * and release the reference to the old page. + */ + n = m_getcl(M_DONTWAIT, m->m_type, m->m_flags); + if (n == NULL) { + m_freem(m0); + return (NULL); + } + if (mprev == NULL && (m->m_flags & M_PKTHDR)) + M_COPY_PKTHDR(n, m); + memcpy(mtod(n, caddr_t), mtod(m, caddr_t), m->m_len); + n->m_len = m->m_len; + n->m_next = m->m_next; + if (mprev == NULL) + m0 = n; /* new head of chain */ + else + mprev->m_next = n; /* replace old mbuf */ + m_free(m); /* release old mbuf */ + mprev = n; + newipsecstat.ips_clcopied++; + } else { + /* XXX: this ignores mbuf types */ + memcpy(mtod(mprev, caddr_t) + mprev->m_len, + mtod(m, caddr_t), m->m_len); + mprev->m_len += m->m_len; + mprev->m_next = m->m_next; /* unlink from chain */ + m_free(m); /* reclaim mbuf */ + newipsecstat.ips_clcoalesced++; + } + } + return (m0); +} + +/* + * Make space for a new header of length hlen at offset off + * in 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; + + KASSERT(m0 != NULL, ("m_dmakespace: null mbuf")); + KASSERT(hlen < MHLEN, ("m_makespace: 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 makese 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 */ + /* XXX code doesn't handle clusters XXX */ + KASSERT(remain < MLEN, ("m_makespace: remainder too big: %u", remain)); + if (hlen > M_TRAILINGSPACE(m)) { + struct mbuf *n; + + /* + * Not enough space in m, split the contents + * of m, inserting new mbufs as required. + * + * NB: this ignores mbuf types. + */ + MGET(n, M_DONTWAIT, MT_DATA); + if (n == NULL) + return (NULL); + n->m_next = m->m_next; /* splice new mbuf */ + m->m_next = n; + newipsecstat.ips_mbinserted++; + if (hlen <= M_TRAILINGSPACE(m) + remain) { + /* + * New header fits in the old mbuf if we copy + * the remainder; just do the copy to the new + * mbuf and we're good to go. + */ + memcpy(mtod(n, caddr_t), + mtod(m, caddr_t) + skip, remain); + n->m_len = remain; + m->m_len = skip + hlen; + *off = skip; + } else { + /* + * No space in the old mbuf for the new header. + * Make space in the new mbuf and check the + * remainder'd data fits too. If not then we + * must allocate an additional mbuf (yech). + */ + n->m_len = 0; + if (remain + hlen > M_TRAILINGSPACE(n)) { + struct mbuf *n2; + + MGET(n2, M_DONTWAIT, MT_DATA); + /* NB: new mbuf is on chain, let caller free */ + if (n2 == NULL) + return (NULL); + n2->m_len = 0; + memcpy(mtod(n2, caddr_t), + mtod(m, caddr_t) + skip, remain); + n2->m_len = remain; + /* splice in second mbuf */ + n2->m_next = n->m_next; + n->m_next = n2; + newipsecstat.ips_mbinserted++; + } else { + memcpy(mtod(n, caddr_t) + hlen, + mtod(m, caddr_t) + skip, remain); + n->m_len += remain; + } + m->m_len -= remain; + n->m_len += hlen; + m = n; /* header is at front ... */ + *off = 0; /* ... of new mbuf */ + } + } else { + /* + * Copy the remainder to the back of the mbuf + * so there's space to write the new header. + */ + /* XXX can this be memcpy? does it handle overlap? */ + ovbcopy(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(("m_pad: pad length invalid (%d)\n", n)); + m_freem(m); + return NULL; + } + + len = m->m_pkthdr.len; + pad = n; + m0 = m; + + while (m0->m_len < len) { +KASSERT(m0->m_next != NULL, ("m_pad: m0 null, len %u m_len %u", len, m0->m_len));/*XXX*/ + len -= m0->m_len; + m0 = m0->m_next; + } + + if (m0->m_len != len) { + DPRINTF(("m_pad: length mismatch (should be %d instead of %d)\n", + 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(("m_pad: length mismatch (should be %d " + "instead of %d)\n", + 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(("m_pad: unable to get extra mbuf\n")); + 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 */ + newipsecstat.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. + */ + newipsecstat.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. + */ + newipsecstat.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/sys/netipsec/ipsec_output.c b/sys/netipsec/ipsec_output.c new file mode 100644 index 0000000..836e17b --- /dev/null +++ b/sys/netipsec/ipsec_output.c @@ -0,0 +1,737 @@ +/* $FreeBSD$ */ +/* $KAME: ipsec.c,v 1.103 2001/05/24 07:14:18 sakane Exp $ */ + +/* + * IPsec output processing. + */ +#include "opt_inet.h" +#include "opt_inet6.h" +#include "opt_ipsec.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/mbuf.h> +#include <sys/domain.h> +#include <sys/protosw.h> +#include <sys/socket.h> +#include <sys/errno.h> +#include <sys/syslog.h> + +#include <net/if.h> +#include <net/route.h> + +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <netinet/ip_var.h> +#include <netinet/in_var.h> +#include <netinet/ip_ecn.h> +#ifdef INET6 +#include <netinet6/ip6_ecn.h> +#endif + +#include <netinet/ip6.h> +#ifdef INET6 +#include <netinet6/ip6_var.h> +#endif +#include <netinet/in_pcb.h> +#ifdef INET6 +#include <netinet/icmp6.h> +#endif + +#include <netipsec/ipsec.h> +#ifdef INET6 +#include <netipsec/ipsec6.h> +#endif +#include <netipsec/ah_var.h> +#include <netipsec/esp_var.h> +#include <netipsec/ipcomp_var.h> + +#include <netipsec/xform.h> + +#include <netipsec/key.h> +#include <netipsec/keydb.h> +#include <netipsec/key_debug.h> + +#include <machine/in_cksum.h> + +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; + +#if 0 + SPLASSERT(net, "ipsec_process_done"); +#endif + + KASSERT(m != NULL, ("ipsec_process_done: null mbuf")); + KASSERT(isr != NULL, ("ipsec_process_done: null ISR")); + sav = isr->sav; + KASSERT(sav != NULL, ("ipsec_process_done: null SA")); + KASSERT(sav->sah != NULL, ("ipsec_process_done: 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(("ipsec_process_done: unknown protocol family %u\n", + 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(("ipsec_process_done: could not get packet tag\n")); + 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) { + newipsecstat.ips_out_bundlesa++; + return ipsec4_process_packet(m, isr->next, 0, 0); + } + + /* + * 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); + + 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; + +#if 0 + SPLASSERT(net, "ipsec_nextisr"); +#endif + KASSERT(af == AF_INET || af == AF_INET6, + ("ipsec_nextisr: 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. + */ + newipsecstat.ips_out_nosa++; + goto bad; + } + sav = isr->sav; + if (sav == NULL) { /* XXX valid return */ + KASSERT(ipsec_get_reqlevel(isr) == IPSEC_LEVEL_USE, + ("ipsec_nextisr: no SA found, but required; level %u", + ipsec_get_reqlevel(isr))); + isr = isr->next; + if (isr == NULL) { + /*XXXstatistic??*/ + *error = EINVAL; /*XXX*/ + return isr; + } + goto again; + } + + /* + * Check system global policy controls. + */ + if ((isr->saidx.proto == IPPROTO_ESP && !esp_enable) || + (isr->saidx.proto == IPPROTO_AH && !ah_enable) || + (isr->saidx.proto == IPPROTO_IPCOMP && !ipcomp_enable)) { + DPRINTF(("ipsec_nextisr: IPsec outbound packet dropped due" + " to policy (check your sysctls)\n")); + IPSEC_OSTAT(espstat.esps_pdrops, ahstat.ahs_pdrops, + 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(("ipsec_nextisr: no transform for SA\n")); + IPSEC_OSTAT(espstat.esps_noxform, ahstat.ahs_noxform, + ipcompstat.ipcomps_noxform); + *error = EHOSTUNREACH; + goto bad; + } + return isr; +bad: + KASSERT(*error != 0, ("ipsec_nextisr: error return w/ no error code")); + 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 s, error, i, off; + + KASSERT(m != NULL, ("ipsec4_process_packet: null mbuf")); + KASSERT(isr != NULL, ("ipsec4_process_packet: null isr")); + + s = splnet(); /* insure SA contents don't change */ + + isr = ipsec_nextisr(m, isr, AF_INET, &saidx, &error); + if (isr == NULL) + goto bad; + + sav = isr->sav; + 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 (ip4_ipsec_dfbit) { + case 0: /* clear in outer header */ + case 1: /* set in outer header */ + setdf = 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(("ipsec4_process_packet: ipip_output " + "returns no mbuf and no error!")); + error = EFAULT; + } + if (error) { + if (mp) + m_freem(mp); + 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); + } + } + } + + /* + * 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); + } + splx(s); + return error; +bad: + splx(s); + 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; + + KASSERT(m->m_len >= sizeof (struct ip6_hdr), + ("ipsec6_splithdr: 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_HEADER); + if (!mh) { + m_freem(m); + return NULL; + } + M_COPY_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; + + KASSERT(state != NULL, ("ipsec6_output: null state")); + KASSERT(state->m != NULL, ("ipsec6_output: null m")); + KASSERT(nexthdrp != NULL, ("ipsec6_output: null nexthdrp")); + KASSERT(mprev != NULL, ("ipsec6_output: null mprev")); + KASSERT(sp != NULL, ("ipsec6_output: null sp")); + KASSERT(tun != NULL, ("ipsec6_output: null tun")); + + KEYDEBUG(KEYDEBUG_IPSEC_DATA, + printf("ipsec6_output_trans: applyed SP\n"); + 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; + + isr = ipsec_nextisr(m, isr, AF_INET6, &saidx, &error); + if (isr == NULL) { +#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 (*isr->sav->tdb_xform->xf_output)(m, isr, NULL, + sizeof (struct ip6_hdr), + offsetof(struct ip6_hdr, ip6_nxt)); +bad: + 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; + } + KASSERT(m->m_len != sizeof (struct ip6_hdr), + ("ipsec6_encapsulate: 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 *); + ovbcopy((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(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; + sav->sah->saidx.src.sin6.sin6_addr = ip6->ip6_src; + sav->sah->saidx.dst.sin6.sin6_addr = ip6->ip6_dst; + 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; + + KASSERT(state != NULL, ("ipsec6_output: null state")); + KASSERT(state->m != NULL, ("ipsec6_output: null m")); + KASSERT(sp != NULL, ("ipsec6_output: null sp")); + + KEYDEBUG(KEYDEBUG_IPSEC_DATA, + printf("ipsec6_output_tunnel: applyed SP\n"); + 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; + } + isr = ipsec_nextisr(m, isr, AF_INET6, &saidx, &error); + if (isr == NULL) + goto bad; + + /* + * 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, "ipsec6_output_tunnel: " + "family mismatched between inner and outer, spi=%u\n", + ntohl(isr->sav->spi))); + newipsecstat.ips_out_inval++; + error = EAFNOSUPPORT; + goto bad; + } + + m = ipsec6_splithdr(m); + if (!m) { + newipsecstat.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 = &isr->sav->sah->sa_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 == 0) { + 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 == 0) { + ip6stat.ip6s_noroute++; + newipsecstat.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; + dst6 = (struct sockaddr_in6 *)state->dst; + } + } + + m = ipsec6_splithdr(m); + if (!m) { + newipsecstat.ips_out_nomem++; + error = ENOMEM; + goto bad; + } + ip6 = mtod(m, struct ip6_hdr *); + return (*isr->sav->tdb_xform->xf_output)(m, isr, NULL, + sizeof (struct ip6_hdr), + offsetof(struct ip6_hdr, ip6_nxt)); +bad: + if (m) + m_freem(m); + state->m = NULL; + return error; +} +#endif /*INET6*/ diff --git a/sys/netipsec/key.c b/sys/netipsec/key.c new file mode 100644 index 0000000..8fc161c --- /dev/null +++ b/sys/netipsec/key.c @@ -0,0 +1,7287 @@ +/* $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 "opt_inet.h" +#include "opt_inet6.h" +#include "opt_ipsec.h" + +#include <sys/types.h> +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/mbuf.h> +#include <sys/domain.h> +#include <sys/protosw.h> +#include <sys/malloc.h> +#include <sys/socket.h> +#include <sys/socketvar.h> +#include <sys/sysctl.h> +#include <sys/errno.h> +#include <sys/proc.h> +#include <sys/queue.h> +#include <sys/syslog.h> + +#include <net/if.h> +#include <net/route.h> +#include <net/raw_cb.h> + +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <netinet/in_var.h> + +#ifdef INET6 +#include <netinet/ip6.h> +#include <netinet6/in6_var.h> +#include <netinet6/ip6_var.h> +#endif /* INET6 */ + +#ifdef INET +#include <netinet/in_pcb.h> +#endif +#ifdef INET6 +#include <netinet6/in6_pcb.h> +#endif /* INET6 */ + +#include <net/pfkeyv2.h> +#include <netipsec/keydb.h> +#include <netipsec/key.h> +#include <netipsec/keysock.h> +#include <netipsec/key_debug.h> + +#include <netipsec/ipsec.h> +#ifdef INET6 +#include <netipsec/ipsec6.h> +#endif + +#include <netipsec/xform.h> + +#include <machine/stdarg.h> + +/* randomness */ +#include <sys/random.h> + +#include <net/net_osdep.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. + */ + +u_int32_t key_debug_level = 0; +static u_int key_spi_trycnt = 1000; +static u_int32_t key_spi_minval = 0x100; +static u_int32_t key_spi_maxval = 0x0fffffff; /* XXX */ +static u_int32_t policy_id = 0; +static u_int key_int_random = 60; /*interval to initialize randseed,1(m)*/ +static u_int key_larval_lifetime = 30; /* interval to expire acquiring, 30(s)*/ +static int key_blockacq_count = 10; /* counter for blocking SADB_ACQUIRE.*/ +static int key_blockacq_lifetime = 20; /* lifetime for blocking SADB_ACQUIRE.*/ +static int key_prefered_oldsa = 1; /* prefered old sa rather than new sa.*/ + +static u_int32_t acq_seq = 0; +static int key_tick_init_random = 0; + +static LIST_HEAD(_sptree, secpolicy) sptree[IPSEC_DIR_MAX]; /* SPD */ +static LIST_HEAD(_sahtree, secashead) sahtree; /* SAD */ +static LIST_HEAD(_regtree, secreg) regtree[SADB_SATYPE_MAX + 1]; + /* registed list */ +#ifndef IPSEC_NONBLOCK_ACQUIRE +static LIST_HEAD(_acqtree, secacq) acqtree; /* acquiring list */ +#endif +static LIST_HEAD(_spacqtree, secspacq) spacqtree; /* SP acquiring list */ + +/* search order for SAs */ +static u_int saorder_state_valid[] = { + SADB_SASTATE_DYING, SADB_SASTATE_MATURE, + /* + * This order is important because we must select a oldest SA + * for outbound processing. For inbound, This is not important. + */ +}; +static u_int saorder_state_alive[] = { + /* except DEAD */ + SADB_SASTATE_MATURE, SADB_SASTATE_DYING, SADB_SASTATE_LARVAL +}; +static 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 */ +}; +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 */ +}; + +static int ipsec_esp_keymin = 256; +static int ipsec_esp_auth = 0; +static int ipsec_ah_keymin = 128; + +#ifdef SYSCTL_DECL +SYSCTL_DECL(_net_key); +#endif + +SYSCTL_INT(_net_key, KEYCTL_DEBUG_LEVEL, debug, CTLFLAG_RW, \ + &key_debug_level, 0, ""); + +/* max count of trial for the decision of spi value */ +SYSCTL_INT(_net_key, KEYCTL_SPI_TRY, spi_trycnt, CTLFLAG_RW, \ + &key_spi_trycnt, 0, ""); + +/* minimum spi value to allocate automatically. */ +SYSCTL_INT(_net_key, KEYCTL_SPI_MIN_VALUE, spi_minval, CTLFLAG_RW, \ + &key_spi_minval, 0, ""); + +/* maximun spi value to allocate automatically. */ +SYSCTL_INT(_net_key, KEYCTL_SPI_MAX_VALUE, spi_maxval, CTLFLAG_RW, \ + &key_spi_maxval, 0, ""); + +/* interval to initialize randseed */ +SYSCTL_INT(_net_key, KEYCTL_RANDOM_INT, int_random, CTLFLAG_RW, \ + &key_int_random, 0, ""); + +/* lifetime for larval SA */ +SYSCTL_INT(_net_key, KEYCTL_LARVAL_LIFETIME, larval_lifetime, CTLFLAG_RW, \ + &key_larval_lifetime, 0, ""); + +/* counter for blocking to send SADB_ACQUIRE to IKEd */ +SYSCTL_INT(_net_key, KEYCTL_BLOCKACQ_COUNT, blockacq_count, CTLFLAG_RW, \ + &key_blockacq_count, 0, ""); + +/* lifetime for blocking to send SADB_ACQUIRE to IKEd */ +SYSCTL_INT(_net_key, KEYCTL_BLOCKACQ_LIFETIME, blockacq_lifetime, CTLFLAG_RW, \ + &key_blockacq_lifetime, 0, ""); + +/* ESP auth */ +SYSCTL_INT(_net_key, KEYCTL_ESP_AUTH, esp_auth, CTLFLAG_RW, \ + &ipsec_esp_auth, 0, ""); + +/* minimum ESP key length */ +SYSCTL_INT(_net_key, KEYCTL_ESP_KEYMIN, esp_keymin, CTLFLAG_RW, \ + &ipsec_esp_keymin, 0, ""); + +/* minimum AH key length */ +SYSCTL_INT(_net_key, KEYCTL_AH_KEYMIN, ah_keymin, CTLFLAG_RW, \ + &ipsec_ah_keymin, 0, ""); + +/* perfered old SA rather than new SA */ +SYSCTL_INT(_net_key, KEYCTL_PREFERED_OLDSA, prefered_oldsa, CTLFLAG_RW,\ + &key_prefered_oldsa, 0, ""); + +#ifndef LIST_FOREACH +#define LIST_FOREACH(elm, head, field) \ + for (elm = LIST_FIRST(head); elm; elm = LIST_NEXT(elm, field)) +#endif +#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_SECA, "key mgmt", "security associations, key management"); + +#if 1 +#define KMALLOC(p, t, n) \ + ((p) = (t) malloc((unsigned long)(n), M_SECA, M_NOWAIT)) +#define KFREE(p) \ + free((caddr_t)(p), M_SECA) +#else +#define KMALLOC(p, t, n) \ +do { \ + ((p) = (t)malloc((unsigned long)(n), M_SECA, M_NOWAIT)); \ + printf("%s %d: %p <- KMALLOC(%s, %d)\n", \ + __FILE__, __LINE__, (p), #t, n); \ +} while (0) + +#define KFREE(p) \ + do { \ + printf("%s %d: %p -> KFREE()\n", __FILE__, __LINE__, (p)); \ + free((caddr_t)(p), M_SECA); \ + } while (0) +#endif + +/* + * 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 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)); +#if 0 +static struct mbuf *key_setsadbident __P((u_int16_t, u_int16_t, caddr_t, + int, u_int64_t)); +#endif +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 void *key_newbuf __P((const void *, u_int)); +#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 void key_srandom __P((void)); +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 *)); +#ifndef IPSEC_NONBLOCK_ACQUIRE +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)); +#endif +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 *)); +#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)); + +#define SA_ADDREF(p) do { \ + (p)->refcnt++; \ + KASSERT((p)->refcnt != 0, \ + ("SA refcnt overflow at %s:%u", __FILE__, __LINE__)); \ +} while (0) +#define SA_DELREF(p) do { \ + KASSERT((p)->refcnt > 0, \ + ("SA refcnt underflow at %s:%u", __FILE__, __LINE__)); \ + (p)->refcnt--; \ +} while (0) + +#define SP_ADDREF(p) do { \ + (p)->refcnt++; \ + KASSERT((p)->refcnt != 0, \ + ("SP refcnt overflow at %s:%u", __FILE__, __LINE__)); \ +} while (0) +#define SP_DELREF(p) do { \ + KASSERT((p)->refcnt > 0, \ + ("SP refcnt underflow at %s:%u", __FILE__, __LINE__)); \ + (p)->refcnt--; \ +} while (0) + +/* + * 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(&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; + int s; + + KASSERT(spidx != NULL, ("key_allocsp: null spidx")); + KASSERT(dir == IPSEC_DIR_INBOUND || dir == IPSEC_DIR_OUTBOUND, + ("key_allocsp: invalid direction %u", dir)); + + KEYDEBUG(KEYDEBUG_IPSEC_STAMP, + printf("DP key_allocsp from %s:%u\n", where, tag)); + + /* get a SP entry */ + s = splnet(); /*called from softclock()*/ + KEYDEBUG(KEYDEBUG_IPSEC_DATA, + printf("*** objects\n"); + kdebug_secpolicyindex(spidx)); + + LIST_FOREACH(sp, &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, "key_allocsp"); + + /* found a SPD entry */ + sp->lastused = time_second; + SP_ADDREF(sp); + } + splx(s); + + KEYDEBUG(KEYDEBUG_IPSEC_STAMP, + printf("DP key_allocsp return SP:%p (ID=%u) refcnt %u\n", + 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; + int s; + + KASSERT(dst != NULL, ("key_allocsp2: null dst")); + KASSERT(dir == IPSEC_DIR_INBOUND || dir == IPSEC_DIR_OUTBOUND, + ("key_allocsp2: invalid direction %u", dir)); + + KEYDEBUG(KEYDEBUG_IPSEC_STAMP, + printf("DP key_allocsp2 from %s:%u\n", where, tag)); + + /* get a SP entry */ + s = splnet(); /*called from softclock()*/ + KEYDEBUG(KEYDEBUG_IPSEC_DATA, + printf("*** objects\n"); + printf("spi %u proto %u dir %u\n", spi, proto, dir); + kdebug_sockaddr(&dst->sa)); + + LIST_FOREACH(sp, &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, "key_allocsp2"); + + /* found a SPD entry */ + sp->lastused = time_second; + SP_ADDREF(sp); + } + splx(s); + + KEYDEBUG(KEYDEBUG_IPSEC_STAMP, + printf("DP key_allocsp2 return SP:%p (ID=%u) refcnt %u\n", + sp, sp ? sp->id : 0, sp ? sp->refcnt : 0)); + return sp; +} + +/* + * 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; + int s; + struct ipsecrequest *r1, *r2, *p; + struct secpolicyindex spidx; + + KEYDEBUG(KEYDEBUG_IPSEC_STAMP, + printf("DP key_gettunnel from %s:%u\n", where, tag)); + + if (isrc->sa_family != idst->sa_family) { + ipseclog((LOG_ERR, "protocol family mismatched %d != %d\n.", + isrc->sa_family, idst->sa_family)); + sp = NULL; + goto done; + } + + s = splnet(); /*called from softclock()*/ + LIST_FOREACH(sp, &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); + } + splx(s); +done: + KEYDEBUG(KEYDEBUG_IPSEC_STAMP, + printf("DP key_gettunnel return SP:%p (ID=%u) refcnt %u\n", + sp, sp ? sp->id : 0, sp ? sp->refcnt : 0)); + return sp; +} + +/* + * 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; + + KASSERT(isr != NULL, ("key_checkrequest: null isr")); + KASSERT(saidx != NULL, ("key_checkrequest: null saidx")); + KASSERT(saidx->mode == IPSEC_MODE_TRANSPORT || + saidx->mode == IPSEC_MODE_TUNNEL, + ("key_checkrequest: unexpected policy %u", saidx->mode)); + + /* get current level */ + level = ipsec_get_reqlevel(isr); + + /* + * 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. + */ +#if 0 + SPLASSERT(net, "key_checkrequest"); +#endif +#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("key_checkrequest: sah is null.\n"); + 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, "key_checkrequest: error %d returned " + "from key_acquire.\n", error)); + return error; + } + + if (level != IPSEC_LEVEL_REQUIRE) { + /* XXX sigh, the interface to this routine is botched */ + KASSERT(isr->sav == NULL, ("key_checkrequest: 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) +{ + struct secashead *sah; + struct secasvar *sav; + u_int stateidx, state; + + LIST_FOREACH(sah, &sahtree, chain) { + if (sah->state == SADB_SASTATE_DEAD) + continue; + if (key_cmpsaidx(&sah->saidx, saidx, CMP_MODE_REQID)) + goto found; + } + + return NULL; + + found: + + /* search valid state */ + for (stateidx = 0; + stateidx < _ARRAYLEN(saorder_state_valid); + stateidx++) { + + state = saorder_state_valid[stateidx]; + + sav = key_do_allocsa_policy(sah, state); + if (sav != NULL) + return sav; + } + + return NULL; +} + +/* + * 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; + + for (sav = LIST_FIRST(&sah->savtree[state]); + sav != NULL; + sav = nextsav) { + + nextsav = LIST_NEXT(sav, chain); + + /* sanity check */ + KEY_CHKSASTATE(sav->state, state, "key_do_allocsa_policy"); + + /* initialize */ + if (candidate == NULL) { + candidate = sav; + continue; + } + + /* Which SA is the better ? */ + + /* sanity check 2 */ + if (candidate->lft_c == NULL || sav->lft_c == NULL) + panic("key_do_allocsa_policy: " + "lifetime_current is NULL.\n"); + + /* What the best method is to compare ? */ + if (key_prefered_oldsa) { + if (candidate->lft_c->sadb_lifetime_addtime > + sav->lft_c->sadb_lifetime_addtime) { + candidate = sav; + } + continue; + /*NOTREACHED*/ + } + + /* prefered new sa rather than old sa */ + if (candidate->lft_c->sadb_lifetime_addtime < + sav->lft_c->sadb_lifetime_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_c->sadb_lifetime_addtime != 0) { + struct mbuf *m, *result; + + key_sa_chgstate(d, SADB_SASTATE_DEAD); + + KASSERT(d->refcnt > 0, + ("key_do_allocsa_policy: bogus ref count")); + m = key_setsadbmsg(SADB_DELETE, 0, + d->sah->saidx.proto, 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.src.sa, + d->sah->saidx.src.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 allocsa_policy cause " + "refcnt++:%d SA:%p\n", + candidate->refcnt, candidate)); + } + 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, state; + int s; + + KASSERT(dst != NULL, ("key_allocsa: null dst address")); + + KEYDEBUG(KEYDEBUG_IPSEC_STAMP, + printf("DP key_allocsa from %s:%u\n", where, tag)); + + /* + * 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. + */ + s = splnet(); /*called from softclock()*/ + LIST_FOREACH(sah, &sahtree, chain) { + /* search valid state */ + for (stateidx = 0; + stateidx < _ARRAYLEN(saorder_state_valid); + stateidx++) { + state = saorder_state_valid[stateidx]; + LIST_FOREACH(sav, &sah->savtree[state], chain) { + /* sanity check */ + KEY_CHKSASTATE(sav->state, state, "key_allocsav"); + /* 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, 0) != 0) + continue; +#endif + /* check dst address */ + if (key_sockaddrcmp(&dst->sa, &sav->sah->saidx.dst.sa, 0) != 0) + continue; + SA_ADDREF(sav); + goto done; + } + } + } + sav = NULL; +done: + splx(s); + + KEYDEBUG(KEYDEBUG_IPSEC_STAMP, + printf("DP key_allocsa return SA:%p; refcnt %u\n", + 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; + + KASSERT(sp != NULL, ("key_freesp: null sp")); + + SP_DELREF(sp); + + KEYDEBUG(KEYDEBUG_IPSEC_STAMP, + printf("DP key_freesp SP:%p (ID=%u) from %s:%u; refcnt now %u\n", + sp, sp->id, where, tag, sp->refcnt)); + + if (sp->refcnt == 0) { + *spp = NULL; + key_delsp(sp); + } +} + +/* + * Must be called after calling key_allocsp(). + * For the packet with socket. + */ +void +key_freeso(struct socket *so) +{ + /* sanity check */ + KASSERT(so != NULL, ("key_freeso: null so")); + + switch (so->so_proto->pr_domain->dom_family) { +#ifdef INET + case PF_INET: + { + 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 +#ifdef INET6 + case PF_INET6: + { +#ifdef HAVE_NRL_INPCB + 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); +#else + struct in6pcb *pcb = sotoin6pcb(so); + + /* Does it have a PCB ? */ + if (pcb == NULL) + return; + key_freesp_so(&pcb->in6p_sp->sp_in); + key_freesp_so(&pcb->in6p_sp->sp_out); +#endif + } + break; +#endif /* INET6 */ + default: + ipseclog((LOG_DEBUG, "key_freeso: unknown address family=%d.\n", + so->so_proto->pr_domain->dom_family)); + return; + } +} + +static void +key_freesp_so(struct secpolicy **sp) +{ + KASSERT(sp != NULL && *sp != NULL, ("key_freesp_so: null sp")); + + if ((*sp)->policy == IPSEC_POLICY_ENTRUST || + (*sp)->policy == IPSEC_POLICY_BYPASS) + return; + + KASSERT((*sp)->policy == IPSEC_POLICY_IPSEC, + ("key_freesp_so: 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; + + KASSERT(sav != NULL, ("key_freesav: null sav")); + + SA_DELREF(sav); + + KEYDEBUG(KEYDEBUG_IPSEC_STAMP, + printf("DP key_freesav SA:%p (SPI %u) from %s:%u; refcnt now %u\n", + sav, ntohl(sav->spi), where, tag, sav->refcnt)); + + if (sav->refcnt == 0) { + *psav = NULL; + key_delsav(sav); + } +} + +/* %%% SPD management */ +/* + * free security policy entry. + */ +static void +key_delsp(struct secpolicy *sp) +{ + int s; + + KASSERT(sp != NULL, ("key_delsp: null sp")); + + sp->state = IPSEC_SPSTATE_DEAD; + + KASSERT(sp->refcnt == 0, + ("key_delsp: SP with references deleted (refcnt %u)", + sp->refcnt)); + + s = splnet(); /*called from softclock()*/ + /* remove from SP index */ + if (__LIST_CHAINED(sp)) + LIST_REMOVE(sp, chain); + + { + struct ipsecrequest *isr = sp->req, *nextisr; + + while (isr != NULL) { + if (isr->sav != NULL) { + KEY_FREESAV(&isr->sav); + isr->sav = NULL; + } + + nextisr = isr->next; + KFREE(isr); + isr = nextisr; + } + } + + KFREE(sp); + + splx(s); +} + +/* + * search SPD + * OUT: NULL : not found + * others : found, pointer to a SP. + */ +static struct secpolicy * +key_getsp(struct secpolicyindex *spidx) +{ + struct secpolicy *sp; + + KASSERT(spidx != NULL, ("key_getsp: null spidx")); + + LIST_FOREACH(sp, &sptree[spidx->dir], chain) { + if (sp->state == IPSEC_SPSTATE_DEAD) + continue; + if (key_cmpspidx_exactly(spidx, &sp->spidx)) { + SP_ADDREF(sp); + return sp; + } + } + + return NULL; +} + +/* + * 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; + + LIST_FOREACH(sp, &sptree[IPSEC_DIR_INBOUND], chain) { + if (sp->state == IPSEC_SPSTATE_DEAD) + continue; + if (sp->id == id) { + SP_ADDREF(sp); + return sp; + } + } + + LIST_FOREACH(sp, &sptree[IPSEC_DIR_OUTBOUND], chain) { + if (sp->state == IPSEC_SPSTATE_DEAD) + continue; + if (sp->id == id) { + SP_ADDREF(sp); + return sp; + } + } + + return NULL; +} + +struct secpolicy * +key_newsp(const char* where, int tag) +{ + struct secpolicy *newsp = NULL; + + newsp = (struct secpolicy *) + malloc(sizeof(struct secpolicy), M_SECA, M_NOWAIT|M_ZERO); + if (newsp) { + newsp->refcnt = 1; + newsp->req = NULL; + } + + KEYDEBUG(KEYDEBUG_IPSEC_STAMP, + printf("DP key_newsp from %s:%u return SP:%p\n", + where, tag, newsp)); + return newsp; +} + +/* + * 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; + + /* sanity check */ + if (xpl0 == NULL) + panic("key_msg2sp: NULL pointer was passed.\n"); + if (len < sizeof(*xpl0)) + panic("key_msg2sp: invalid length.\n"); + if (len != PFKEY_EXTLEN(xpl0)) { + ipseclog((LOG_DEBUG, "key_msg2sp: Invalid msg length.\n")); + *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, + "key_msg2sp: Invalid msg length.\n")); + 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, "key_msg2sp: " + "invalid ipsecrequest length.\n")); + KEY_FREESP(&newsp); + *error = EINVAL; + return NULL; + } + + /* allocate request buffer */ + KMALLOC(*p_isr, struct ipsecrequest *, sizeof(**p_isr)); + if ((*p_isr) == NULL) { + ipseclog((LOG_DEBUG, + "key_msg2sp: No more memory.\n")); + KEY_FREESP(&newsp); + *error = ENOBUFS; + return NULL; + } + bzero(*p_isr, sizeof(**p_isr)); + + /* set values */ + (*p_isr)->next = NULL; + + switch (xisr->sadb_x_ipsecrequest_proto) { + case IPPROTO_ESP: + case IPPROTO_AH: + case IPPROTO_IPCOMP: + break; + default: + ipseclog((LOG_DEBUG, + "key_msg2sp: invalid proto type=%u\n", + 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, + "key_msg2sp: invalid mode=%u\n", + 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, + "key_msg2sp: reqid=%d range " + "violation, updated by kernel.\n", + 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, "key_msg2sp: invalid level=%u\n", + 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, "key_msg2sp: invalid request " + "address length.\n")); + 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, "key_msg2sp: invalid request " + "address length.\n")); + KEY_FREESP(&newsp); + *error = EINVAL; + return NULL; + } + bcopy(paddr, &(*p_isr)->saidx.dst, + paddr->sa_len); + } + + (*p_isr)->sav = NULL; + (*p_isr)->sp = newsp; + + /* initialization for the next. */ + p_isr = &(*p_isr)->next; + tlen -= xisr->sadb_x_ipsecrequest_len; + + /* validity check */ + if (tlen < 0) { + ipseclog((LOG_DEBUG, "key_msg2sp: becoming tlen < 0.\n")); + 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, "key_msg2sp: invalid policy type.\n")); + 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; + + /* sanity check. */ + if (sp == NULL) + panic("key_sp2msg: NULL pointer was passed.\n"); + + 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; + + if (m == NULL || mhp == NULL) + panic("null pointer passed to key_gather"); + + 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)); +#ifdef DIAGNOSTIC + if (len > MHLEN) + panic("assumption failed"); +#endif + 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 a 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; + + /* sanity check */ + if (so == NULL || m == NULL || mhp == NULL || mhp->msg == NULL) + panic("key_spdadd: NULL pointer is passed.\n"); + + 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, "key_spdadd: invalid message is passed.\n")); + 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, "key_spdadd: invalid message is passed.\n")); + 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]; + + /* 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, "key_spdadd: Invalid SP direction.\n")); + 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, "key_spdadd: Invalid policy type.\n")); + 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, "key_spdadd: some policy requests part required.\n")); + 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) { + newsp->state = IPSEC_SPSTATE_DEAD; + KEY_FREESP(&newsp); + } + } else { + if (newsp != NULL) { + KEY_FREESP(&newsp); + ipseclog((LOG_DEBUG, "key_spdadd: a SP entry exists already.\n")); + 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) { + KFREE(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) { + KFREE(newsp); + return key_senderror(so, m, EINVAL); + } + if (((struct sockaddr *)(src0 + 1))->sa_len != + ((struct sockaddr *)(dst0+ 1))->sa_len) { + KFREE(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) { + KFREE(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) { + KFREE(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(&sptree[newsp->spidx.dir], newsp, secpolicy, chain); + + /* delete the entry in spacqtree */ + if (mhp->msg->sadb_msg_type == SADB_X_SPDUPDATE) { + struct secspacq *spacq; + if ((spacq = key_getspacq(&spidx)) != NULL) { + /* reset counter in order to deletion by timehandler. */ + spacq->created = time_second; + spacq->count = 0; + } + } + + { + struct mbuf *n, *mpolicy; + struct sadb_msg *newmsg; + int off; + + /* 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 = key_spi_trycnt; /* XXX */ + struct secpolicy *sp; + + /* when requesting to allocate spi ranged */ + while (count--) { + newid = (policy_id = (policy_id == ~0 ? 1 : policy_id + 1)); + + if ((sp = key_getspbyid(newid)) == NULL) + break; + + KEY_FREESP(&sp); + } + + if (count == 0 || newid == 0) { + ipseclog((LOG_DEBUG, "key_getnewspid: to allocate policy id is failed.\n")); + 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; + + /* sanity check */ + if (so == NULL || m == NULL || mhp == NULL || mhp->msg == NULL) + panic("key_spddelete: NULL pointer is passed.\n"); + + 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_spddelete: 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, "key_spddelete: invalid message is passed.\n")); + 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]; + + /* 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, "key_spddelete: Invalid SP direction.\n")); + return key_senderror(so, m, EINVAL); + } + + /* Is there SP in SPD ? */ + if ((sp = key_getsp(&spidx)) == NULL) { + ipseclog((LOG_DEBUG, "key_spddelete: no SP found.\n")); + return key_senderror(so, m, EINVAL); + } + + /* save policy id to buffer to be returned. */ + xpl0->sadb_x_policy_id = sp->id; + + sp->state = IPSEC_SPSTATE_DEAD; + KEY_FREESP(&sp); + + { + struct mbuf *n; + struct sadb_msg *newmsg; + + /* 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; + + /* sanity check */ + if (so == NULL || m == NULL || mhp == NULL || mhp->msg == NULL) + panic("key_spddelete2: NULL pointer is passed.\n"); + + if (mhp->ext[SADB_X_EXT_POLICY] == NULL || + mhp->extlen[SADB_X_EXT_POLICY] < sizeof(struct sadb_x_policy)) { + ipseclog((LOG_DEBUG, "key_spddelete2: invalid message is passed.\n")); + key_senderror(so, m, EINVAL); + return 0; + } + + 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, "key_spddelete2: no SP found id:%u.\n", id)); + key_senderror(so, m, EINVAL); + } + + sp->state = IPSEC_SPSTATE_DEAD; + 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)); + + if (len > MCLBYTES) + return key_senderror(so, m, ENOBUFS); + 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)); + +#ifdef DIAGNOSTIC + if (off != len) + panic("length inconsistency in key_spddelete2"); +#endif + + 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; + + /* sanity check */ + if (so == NULL || m == NULL || mhp == NULL || mhp->msg == NULL) + panic("key_spdget: NULL pointer is passed.\n"); + + if (mhp->ext[SADB_X_EXT_POLICY] == NULL || + mhp->extlen[SADB_X_EXT_POLICY] < sizeof(struct sadb_x_policy)) { + ipseclog((LOG_DEBUG, "key_spdget: invalid message is passed.\n")); + 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, "key_spdget: no SP found id:%u.\n", 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; + int error; + + /* sanity check */ + if (sp == NULL) + panic("key_spdacquire: NULL pointer is passed.\n"); + if (sp->req != NULL) + panic("key_spdacquire: called but there is request.\n"); + if (sp->policy != IPSEC_POLICY_IPSEC) + panic("key_spdacquire: policy mismathed. IPsec is expected.\n"); + + /* get a entry to check whether sent message or not. */ + if ((newspacq = key_getspacq(&sp->spidx)) != NULL) { + if (key_blockacq_count < newspacq->count) { + /* reset counter and do send message. */ + newspacq->count = 0; + } else { + /* increment counter and do nothing. */ + newspacq->count++; + return 0; + } + } else { + /* make new entry for blocking to send SADB_ACQUIRE. */ + if ((newspacq = key_newspacq(&sp->spidx)) == NULL) + return ENOBUFS; + + /* add to acqtree */ + LIST_INSERT_HEAD(&spacqtree, newspacq, chain); + } + + /* create new sadb_msg to reply. */ + m = key_setsadbmsg(SADB_X_SPDACQUIRE, 0, 0, 0, 0, 0); + if (!m) { + error = ENOBUFS; + goto fail; + } + 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); + +fail: + if (result) + m_freem(result); + return error; +} + +/* + * 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; + + /* sanity check */ + if (so == NULL || m == NULL || mhp == NULL || mhp->msg == NULL) + panic("key_spdflush: NULL pointer is passed.\n"); + + if (m->m_len != PFKEY_ALIGN8(sizeof(struct sadb_msg))) + return key_senderror(so, m, EINVAL); + + for (dir = 0; dir < IPSEC_DIR_MAX; dir++) { + LIST_FOREACH(sp, &sptree[dir], chain) { + sp->state = IPSEC_SPSTATE_DEAD; + } + } + + if (sizeof(struct sadb_msg) > m->m_len + M_TRAILINGSPACE(m)) { + ipseclog((LOG_DEBUG, "key_spdflush: No more memory.\n")); + 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; + + /* sanity check */ + if (so == NULL || m == NULL || mhp == NULL || mhp->msg == NULL) + panic("key_spddump: NULL pointer is passed.\n"); + + /* search SPD entry and get buffer size. */ + cnt = 0; + for (dir = 0; dir < IPSEC_DIR_MAX; dir++) { + LIST_FOREACH(sp, &sptree[dir], chain) { + cnt++; + } + } + + if (cnt == 0) + return key_senderror(so, m, ENOENT); + + for (dir = 0; dir < IPSEC_DIR_MAX; dir++) { + LIST_FOREACH(sp, &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); + } + } + + m_freem(m); + return 0; +} + +static struct mbuf * +key_setdumpsp(sp, type, seq, pid) + struct secpolicy *sp; + u_int8_t type; + u_int32_t seq, pid; +{ + struct mbuf *result = NULL, *m; + + m = key_setsadbmsg(type, 0, SADB_SATYPE_UNSPEC, seq, pid, sp->refcnt); + if (!m) + goto fail; + result = m; + + 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 ((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; +{ + int s; + struct mbuf *result = NULL, *m; + int len; + int error = -1; + struct sadb_lifetime *lt; + + /* XXX: Why do we lock ? */ + s = splnet(); /*called from softclock()*/ + + /* sanity check */ + if (sp == NULL) + panic("key_spdexpire: NULL pointer is passed.\n"); + + /* 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); + + /* 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); + splx(s); + 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; + + KASSERT(saidx != NULL, ("key_newsaidx: null saidx")); + + newsah = (struct secashead *) + malloc(sizeof(struct secashead), M_SECA, 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; + LIST_INSERT_HEAD(&sahtree, newsah, chain); + } + return(newsah); +} + +/* + * delete SA index and all SA registerd. + */ +static void +key_delsah(sah) + struct secashead *sah; +{ + struct secasvar *sav, *nextsav; + u_int stateidx, state; + int s; + int zombie = 0; + + /* sanity check */ + if (sah == NULL) + panic("key_delsah: NULL pointer is passed.\n"); + + s = splnet(); /*called from softclock()*/ + + /* searching all SA registerd in the secindex. */ + for (stateidx = 0; + stateidx < _ARRAYLEN(saorder_state_any); + stateidx++) { + + state = saorder_state_any[stateidx]; + for (sav = (struct secasvar *)LIST_FIRST(&sah->savtree[state]); + sav != NULL; + sav = nextsav) { + + nextsav = LIST_NEXT(sav, chain); + + if (sav->refcnt == 0) { + /* sanity check */ + KEY_CHKSASTATE(state, sav->state, "key_delsah"); + KEY_FREESAV(&sav); + } else { + /* give up to delete this sa */ + zombie++; + } + } + } + + /* don't delete sah only if there are savs. */ + if (zombie) { + splx(s); + return; + } + + if (sah->sa_route.ro_rt) { + RTFREE(sah->sa_route.ro_rt); + sah->sa_route.ro_rt = (struct rtentry *)NULL; + } + + /* remove from tree of SA index */ + if (__LIST_CHAINED(sah)) + LIST_REMOVE(sah, chain); + + KFREE(sah); + + splx(s); + return; +} + +/* + * 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; + + /* sanity check */ + if (m == NULL || mhp == NULL || mhp->msg == NULL || sah == NULL) + panic("key_newsa: NULL pointer is passed.\n"); + + KMALLOC(newsav, struct secasvar *, sizeof(struct secasvar)); + if (newsav == NULL) { + ipseclog((LOG_DEBUG, "key_newsa: No more memory.\n")); + *errp = ENOBUFS; + goto done; + } + bzero((caddr_t)newsav, sizeof(struct secasvar)); + + 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 = + (acq_seq = (acq_seq == ~0 ? 1 : ++acq_seq)); + else +#endif + newsav->seq = mhp->msg->sadb_msg_seq; + break; + + case SADB_ADD: + /* sanity check */ + if (mhp->ext[SADB_EXT_SA] == NULL) { + KFREE(newsav), newsav = NULL; + ipseclog((LOG_DEBUG, "key_newsa: invalid message is passed.\n")); + *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: + KFREE(newsav), 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) { + KFREE(newsav), newsav = NULL; + goto done; + } + } + + /* reset created */ + newsav->created = time_second; + newsav->pid = mhp->msg->sadb_msg_pid; + + /* add to satree */ + newsav->sah = sah; + newsav->refcnt = 1; + newsav->state = SADB_SASTATE_LARVAL; + LIST_INSERT_TAIL(&sah->savtree[SADB_SASTATE_LARVAL], newsav, + secasvar, chain); +done: + KEYDEBUG(KEYDEBUG_IPSEC_STAMP, + printf("DP key_newsav from %s:%u return SP:%p\n", + where, tag, newsav)); + + return newsav; +} + +/* + * free() SA variable entry. + */ +static void +key_delsav(sav) + struct secasvar *sav; +{ + KASSERT(sav != NULL, ("key_delsav: null sav")); + KASSERT(sav->refcnt == 0, + ("key_delsav: reference count %u > 0", sav->refcnt)); + + /* remove from SA header */ + if (__LIST_CHAINED(sav)) + LIST_REMOVE(sav, chain); + + if (sav->key_auth != NULL) { + bzero(_KEYBUF(sav->key_auth), _KEYLEN(sav->key_auth)); + KFREE(sav->key_auth); + sav->key_auth = NULL; + } + if (sav->key_enc != NULL) { + bzero(_KEYBUF(sav->key_enc), _KEYLEN(sav->key_enc)); + KFREE(sav->key_enc); + sav->key_enc = NULL; + } + if (sav->sched) { + bzero(sav->sched, sav->schedlen); + KFREE(sav->sched); + sav->sched = NULL; + } + if (sav->replay != NULL) { + KFREE(sav->replay); + sav->replay = NULL; + } + if (sav->lft_c != NULL) { + KFREE(sav->lft_c); + sav->lft_c = NULL; + } + if (sav->lft_h != NULL) { + KFREE(sav->lft_h); + sav->lft_h = NULL; + } + if (sav->lft_s != NULL) { + KFREE(sav->lft_s); + sav->lft_s = NULL; + } + if (sav->iv != NULL) { + KFREE(sav->iv); + sav->iv = NULL; + } + + KFREE(sav); + + return; +} + +/* + * search SAD. + * OUT: + * NULL : not found + * others : found, pointer to a SA. + */ +static struct secashead * +key_getsah(saidx) + struct secasindex *saidx; +{ + struct secashead *sah; + + LIST_FOREACH(sah, &sahtree, chain) { + if (sah->state == SADB_SASTATE_DEAD) + continue; + if (key_cmpsaidx(&sah->saidx, saidx, CMP_REQID)) + return sah; + } + + return NULL; +} + +/* + * 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, "key_checkspidup: address family mismatched.\n")); + return NULL; + } + + /* check all SAD */ + LIST_FOREACH(sah, &sahtree, chain) { + if (!key_ismyaddr((struct sockaddr *)&sah->saidx.dst)) + continue; + sav = key_getsavbyspi(sah, spi); + if (sav != NULL) + return sav; + } + + return NULL; +} + +/* + * 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; + + /* 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, "key_getsavbyspi: " + "invalid sav->state (queue: %d SA: %d)\n", + 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; + + /* sanity check */ + if (m == NULL || mhp == NULL || mhp->msg == NULL) + panic("key_setsaval: NULL pointer is passed.\n"); + + /* 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 */ + + /* 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_SECA, M_NOWAIT|M_ZERO); + if (sav->replay == NULL) { + ipseclog((LOG_DEBUG, "key_setsaval: No more memory.\n")); + 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: + 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, "key_setsaval: invalid key_auth values.\n")); + goto fail; + } + + sav->key_auth = (struct sadb_key *)key_newbuf(key0, len); + if (sav->key_auth == NULL) { + ipseclog((LOG_DEBUG, "key_setsaval: No more memory.\n")); + 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 sadb_key *)key_newbuf(key0, len); + if (sav->key_enc == NULL) { + ipseclog((LOG_DEBUG, "key_setsaval: No more memory.\n")); + 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: + default: + error = EINVAL; + break; + } + if (error) { + ipseclog((LOG_DEBUG, "key_setsatval: invalid key_enc value.\n")); + 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; + } + if (error) { + ipseclog((LOG_DEBUG, + "key_setsaval: unable to initialize SA type %u.\n", + mhp->msg->sadb_msg_satype)); + goto fail; + } + + /* reset created */ + sav->created = time_second; + + /* make lifetime for CURRENT */ + KMALLOC(sav->lft_c, struct sadb_lifetime *, + sizeof(struct sadb_lifetime)); + if (sav->lft_c == NULL) { + ipseclog((LOG_DEBUG, "key_setsaval: No more memory.\n")); + error = ENOBUFS; + goto fail; + } + + sav->lft_c->sadb_lifetime_len = + PFKEY_UNIT64(sizeof(struct sadb_lifetime)); + sav->lft_c->sadb_lifetime_exttype = SADB_EXT_LIFETIME_CURRENT; + sav->lft_c->sadb_lifetime_allocations = 0; + sav->lft_c->sadb_lifetime_bytes = 0; + sav->lft_c->sadb_lifetime_addtime = time_second; + sav->lft_c->sadb_lifetime_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 = (struct sadb_lifetime *)key_newbuf(lft0, + sizeof(*lft0)); + if (sav->lft_h == NULL) { + ipseclog((LOG_DEBUG, "key_setsaval: No more memory.\n")); + 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 = (struct sadb_lifetime *)key_newbuf(lft0, + sizeof(*lft0)); + if (sav->lft_s == NULL) { + ipseclog((LOG_DEBUG, "key_setsaval: No more memory.\n")); + error = ENOBUFS; + goto fail; + } + /* to be initialize ? */ + } + } + + return 0; + + fail: + /* initialization */ + if (sav->replay != NULL) { + KFREE(sav->replay); + sav->replay = NULL; + } + if (sav->key_auth != NULL) { + KFREE(sav->key_auth); + sav->key_auth = NULL; + } + if (sav->key_enc != NULL) { + KFREE(sav->key_enc); + sav->key_enc = NULL; + } + if (sav->sched) { + KFREE(sav->sched); + sav->sched = NULL; + } + if (sav->iv != NULL) { + KFREE(sav->iv); + sav->iv = NULL; + } + if (sav->lft_c != NULL) { + KFREE(sav->lft_c); + sav->lft_c = NULL; + } + if (sav->lft_h != NULL) { + KFREE(sav->lft_h); + sav->lft_h = NULL; + } + if (sav->lft_s != NULL) { + KFREE(sav->lft_s); + sav->lft_s = NULL; + } + + return error; +} + +/* + * validation with a secasvar entry, and set SADB_SATYPE_MATURE. + * OUT: 0: valid + * other: errno + */ +static int +key_mature(sav) + struct secasvar *sav; +{ + int error; + + /* check SPI value */ + switch (sav->sah->saidx.proto) { + case IPPROTO_ESP: + case IPPROTO_AH: + if (ntohl(sav->spi) >= 0 && ntohl(sav->spi) <= 255) { + ipseclog((LOG_DEBUG, + "key_mature: illegal range of SPI %u.\n", + (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, "key_mature: " + "invalid flag (derived) given to old-esp.\n")); + return EINVAL; + } + error = xform_init(sav, XF_ESP); + break; + case IPPROTO_AH: + /* check flags */ + if (sav->flags & SADB_X_EXT_DERIV) { + ipseclog((LOG_DEBUG, "key_mature: " + "invalid flag (derived) given to AH SA.\n")); + return EINVAL; + } + if (sav->alg_enc != SADB_EALG_NONE) { + ipseclog((LOG_DEBUG, "key_mature: " + "protocol and algorithm mismated.\n")); + return(EINVAL); + } + error = xform_init(sav, XF_AH); + break; + case IPPROTO_IPCOMP: + if (sav->alg_auth != SADB_AALG_NONE) { + ipseclog((LOG_DEBUG, "key_mature: " + "protocol and algorithm mismated.\n")); + return(EINVAL); + } + if ((sav->flags & SADB_X_EXT_RAWCPI) == 0 + && ntohl(sav->spi) >= 0x10000) { + ipseclog((LOG_DEBUG, "key_mature: invalid cpi for IPComp.\n")); + return(EINVAL); + } + error = xform_init(sav, XF_IPCOMP); + break; + default: + ipseclog((LOG_DEBUG, "key_mature: Invalid satype.\n")); + error = EPROTONOSUPPORT; + break; + } + if (error == 0) + key_sa_chgstate(sav, SADB_SASTATE_MATURE); + return (error); +} + +/* + * subroutine for SADB_GET and SADB_DUMP. + */ +static struct mbuf * +key_setdumpsa(sav, type, satype, seq, pid) + struct secasvar *sav; + u_int8_t type, satype; + u_int32_t seq, pid; +{ + struct mbuf *result = NULL, *tres = NULL, *m; + int l = 0; + int i; + void *p; + 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, + }; + + 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; + p = 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; + l = PFKEY_UNUNIT64(sav->key_auth->sadb_key_len); + p = sav->key_auth; + break; + + case SADB_EXT_KEY_ENCRYPT: + if (!sav->key_enc) + continue; + l = PFKEY_UNUNIT64(sav->key_enc->sadb_key_len); + p = sav->key_enc; + break; + + case SADB_EXT_LIFETIME_CURRENT: + if (!sav->lft_c) + continue; + l = PFKEY_UNUNIT64(((struct sadb_ext *)sav->lft_c)->sadb_ext_len); + p = sav->lft_c; + break; + + case SADB_EXT_LIFETIME_HARD: + if (!sav->lft_h) + continue; + l = PFKEY_UNUNIT64(((struct sadb_ext *)sav->lft_h)->sadb_ext_len); + p = sav->lft_h; + break; + + case SADB_EXT_LIFETIME_SOFT: + if (!sav->lft_s) + continue; + l = PFKEY_UNUNIT64(((struct sadb_ext *)sav->lft_s)->sadb_ext_len); + p = sav->lft_s; + break; + + 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 && !p) || (m && p)) + goto fail; + if (p && tres) { + M_PREPEND(tres, l, M_DONTWAIT); + if (!tres) + goto fail; + bcopy(p, mtod(tres, caddr_t), l); + continue; + } + if (p) { + m = key_alloc_mbuf(l); + if (!m) + goto fail; + m_copyback(m, 0, l, p); + } + + 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(type, tlen, satype, seq, pid, reserved) + u_int8_t type, satype; + u_int16_t tlen; + 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(exttype, saddr, prefixlen, ul_proto) + 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; +} + +#if 0 +/* + * set data into sadb_ident. + */ +static struct mbuf * +key_setsadbident(exttype, idtype, string, stringlen, id) + u_int16_t exttype, idtype; + caddr_t string; + int stringlen; + u_int64_t id; +{ + struct mbuf *m; + struct sadb_ident *p; + size_t len; + + len = PFKEY_ALIGN8(sizeof(struct sadb_ident)) + PFKEY_ALIGN8(stringlen); + m = key_alloc_mbuf(len); + if (!m || m->m_next) { /*XXX*/ + if (m) + m_freem(m); + return NULL; + } + + p = mtod(m, struct sadb_ident *); + + bzero(p, len); + p->sadb_ident_len = PFKEY_UNIT64(len); + p->sadb_ident_exttype = exttype; + p->sadb_ident_type = idtype; + p->sadb_ident_reserved = 0; + p->sadb_ident_id = id; + + bcopy(string, + mtod(m, caddr_t) + PFKEY_ALIGN8(sizeof(struct sadb_ident)), + stringlen); + + return m; +} +#endif + +/* + * set data into sadb_x_sa2. + */ +static struct mbuf * +key_setsadbxsa2(mode, seq, reqid) + u_int8_t mode; + u_int32_t seq, 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; +} + +/* + * set data into sadb_x_policy + */ +static struct mbuf * +key_setsadbxpolicy(type, dir, id) + 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 */ +/* + * copy a buffer into the new buffer allocated. + */ +static void * +key_newbuf(src, len) + const void *src; + u_int len; +{ + caddr_t new; + + KMALLOC(new, caddr_t, len); + if (new == NULL) { + ipseclog((LOG_DEBUG, "key_newbuf: No more memory.\n")); + return NULL; + } + bcopy(src, new, len); + + return new; +} + +/* 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 + + /* sanity check */ + if (sa == NULL) + panic("key_ismyaddr: NULL pointer is passed.\n"); + + switch (sa->sa_family) { +#ifdef INET + case AF_INET: + sin = (struct sockaddr_in *)sa; + for (ia = 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) + { + return 1; + } + } + break; +#endif +#ifdef INET6 + case AF_INET6: + return key_ismyaddr6((struct sockaddr_in6 *)sa); +#endif + } + + return 0; +} + +#ifdef INET6 +/* + * compare my own address for IPv6. + * 1: ours + * 0: other + * NOTE: derived ip6_input() in KAME. This is necessary to modify more. + */ +#include <netinet6/in6_var.h> + +static int +key_ismyaddr6(sin6) + struct sockaddr_in6 *sin6; +{ + struct in6_ifaddr *ia; + struct in6_multi *in6m; + + for (ia = in6_ifaddr; ia; ia = ia->ia_next) { + if (key_sockaddrcmp((struct sockaddr *)&sin6, + (struct sockaddr *)&ia->ia_addr, 0) == 0) + return 1; + + /* + * 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) + return 1; + } + + /* 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) +{ + /* 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; + } + + if (key_sockaddrcmp(&saidx0->src.sa, &saidx1->src.sa, 0) != 0) { + return 0; + } + if (key_sockaddrcmp(&saidx0->dst.sa, &saidx1->dst.sa, 0) != 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->src.sin6.sin6_scope_id && + spidx1->src.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; + } + 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! */ +} + +/* + * 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) +{ + u_int dir; + int s; + time_t now = time_second; + + s = splnet(); /*called from softclock()*/ + + /* SPD */ + { + struct secpolicy *sp, *nextsp; + + for (dir = 0; dir < IPSEC_DIR_MAX; dir++) { + for (sp = LIST_FIRST(&sptree[dir]); + sp != NULL; + sp = nextsp) { + + nextsp = LIST_NEXT(sp, chain); + + if (sp->state == IPSEC_SPSTATE_DEAD) { + KEY_FREESP(&sp); + continue; + } + + if (sp->lifetime == 0 && sp->validtime == 0) + continue; + + /* the deletion will occur next time */ + if ((sp->lifetime && now - sp->created > sp->lifetime) + || (sp->validtime && now - sp->lastused > sp->validtime)) { + sp->state = IPSEC_SPSTATE_DEAD; + key_spdexpire(sp); + continue; + } + } + } + } + + /* SAD */ + { + struct secashead *sah, *nextsah; + struct secasvar *sav, *nextsav; + + for (sah = LIST_FIRST(&sahtree); + sah != NULL; + sah = nextsah) { + + nextsah = LIST_NEXT(sah, chain); + + /* 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. */ + for (sav = LIST_FIRST(&sah->savtree[SADB_SASTATE_LARVAL]); + sav != NULL; + sav = nextsav) { + + nextsav = LIST_NEXT(sav, chain); + + if (now - sav->created > key_larval_lifetime) { + KEY_FREESAV(&sav); + } + } + + /* + * check MATURE entry to start to send expire message + * whether or not. + */ + for (sav = LIST_FIRST(&sah->savtree[SADB_SASTATE_MATURE]); + sav != NULL; + sav = nextsav) { + + nextsav = LIST_NEXT(sav, chain); + + /* we don't need to check. */ + if (sav->lft_s == NULL) + continue; + + /* sanity check */ + if (sav->lft_c == NULL) { + ipseclog((LOG_DEBUG,"key_timehandler: " + "There is no CURRENT time, why?\n")); + continue; + } + + /* check SOFT lifetime */ + if (sav->lft_s->sadb_lifetime_addtime != 0 + && now - sav->created > sav->lft_s->sadb_lifetime_addtime) { + /* + * check SA to be used whether or not. + * when SA hasn't been used, delete it. + */ + if (sav->lft_c->sadb_lifetime_usetime == 0) { + key_sa_chgstate(sav, SADB_SASTATE_DEAD); + KEY_FREESAV(&sav); + } else { + 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 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->sadb_lifetime_bytes != 0 + && sav->lft_s->sadb_lifetime_bytes < sav->lft_c->sadb_lifetime_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. */ + for (sav = LIST_FIRST(&sah->savtree[SADB_SASTATE_DYING]); + sav != NULL; + sav = nextsav) { + + nextsav = LIST_NEXT(sav, chain); + + /* we don't need to check. */ + if (sav->lft_h == NULL) + continue; + + /* sanity check */ + if (sav->lft_c == NULL) { + ipseclog((LOG_DEBUG, "key_timehandler: " + "There is no CURRENT time, why?\n")); + continue; + } + + if (sav->lft_h->sadb_lifetime_addtime != 0 + && now - sav->created > sav->lft_h->sadb_lifetime_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->sadb_lifetime_addtime != 0 + && now - sav->created > sav->lft_s->sadb_lifetime_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->sadb_lifetime_bytes != 0 + && sav->lft_h->sadb_lifetime_bytes < sav->lft_c->sadb_lifetime_bytes) { + key_sa_chgstate(sav, SADB_SASTATE_DEAD); + KEY_FREESAV(&sav); + } + } + + /* delete entry in DEAD */ + for (sav = LIST_FIRST(&sah->savtree[SADB_SASTATE_DEAD]); + sav != NULL; + sav = nextsav) { + + nextsav = LIST_NEXT(sav, chain); + + /* sanity check */ + if (sav->state != SADB_SASTATE_DEAD) { + ipseclog((LOG_DEBUG, "key_timehandler: " + "invalid sav->state " + "(queue: %d SA: %d): " + "kill it anyway\n", + 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). + */ + } + } + } + +#ifndef IPSEC_NONBLOCK_ACQUIRE + /* ACQ tree */ + { + struct secacq *acq, *nextacq; + + for (acq = LIST_FIRST(&acqtree); + acq != NULL; + acq = nextacq) { + + nextacq = LIST_NEXT(acq, chain); + + if (now - acq->created > key_blockacq_lifetime + && __LIST_CHAINED(acq)) { + LIST_REMOVE(acq, chain); + KFREE(acq); + } + } + } +#endif + + /* SP ACQ tree */ + { + struct secspacq *acq, *nextacq; + + for (acq = LIST_FIRST(&spacqtree); + acq != NULL; + acq = nextacq) { + + nextacq = LIST_NEXT(acq, chain); + + if (now - acq->created > key_blockacq_lifetime + && __LIST_CHAINED(acq)) { + LIST_REMOVE(acq, chain); + KFREE(acq); + } + } + } + + /* initialize random seed */ + if (key_tick_init_random++ > key_int_random) { + key_tick_init_random = 0; + key_srandom(); + } + +#ifndef IPSEC_DEBUG2 + /* do exchange to tick time !! */ + (void)timeout((void *)key_timehandler, (void *)0, hz); +#endif /* IPSEC_DEBUG2 */ + + splx(s); + return; +} + +/* + * to initialize a seed for random() + */ +static void +key_srandom() +{ + srandom(time_second); +} + +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(satype) + 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; + default: + return 0; + } + /* NOTREACHED */ +} + +/* + * map IPPROTO_* to SADB_SATYPE_* + * OUT: + * 0: invalid protocol type. + */ +static u_int8_t +key_proto2satype(proto) + 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; + 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; + + /* sanity check */ + if (so == NULL || m == NULL || mhp == NULL || mhp->msg == NULL) + panic("key_getspi: NULL pointer is passed.\n"); + + if (mhp->ext[SADB_EXT_ADDRESS_SRC] == NULL || + mhp->ext[SADB_EXT_ADDRESS_DST] == NULL) { + ipseclog((LOG_DEBUG, "key_getspi: 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)) { + ipseclog((LOG_DEBUG, "key_getspi: invalid message is passed.\n")); + 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, "key_getspi: invalid satype is passed.\n")); + return key_senderror(so, m, EINVAL); + } + + /* make sure if port number is zero. */ + 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); + + /* 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, "key_getspi: No more memory.\n")); + 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); + +#ifndef IPSEC_NONBLOCK_ACQUIRE + /* 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; + } + } +#endif + + { + 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)); + 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_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)); + +#ifdef DIAGNOSTIC + if (off != len) + panic("length inconsistency in key_getspi"); +#endif + + 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 = key_spi_trycnt; + + /* set spi range to allocate */ + if (spirange != NULL) { + min = spirange->sadb_spirange_min; + max = spirange->sadb_spirange_max; + } else { + min = key_spi_minval; + max = 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, "key_do_getnewspi: SPI %u exists already.\n", 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, "key_do_getnewspi: to allocate spi is failed.\n")); + return 0; + } + } + + /* statistics */ + keystat.getspi_count = + (keystat.getspi_count + 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; + struct secasindex saidx; + struct secashead *sah; + struct secasvar *sav; + u_int16_t proto; + u_int8_t mode; + u_int32_t reqid; + int error; + + /* sanity check */ + if (so == NULL || m == NULL || mhp == NULL || mhp->msg == NULL) + panic("key_update: NULL pointer is passed.\n"); + + /* map satype to proto */ + if ((proto = key_satype2proto(mhp->msg->sadb_msg_satype)) == 0) { + ipseclog((LOG_DEBUG, "key_update: invalid satype is passed.\n")); + 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, "key_update: invalid message is passed.\n")); + 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, "key_update: invalid message is passed.\n")); + 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); + + /* get a SA header */ + if ((sah = key_getsah(&saidx)) == NULL) { + ipseclog((LOG_DEBUG, "key_update: no SA index found.\n")); + 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, + "key_update: no larval SA with sequence %u exists.\n", + mhp->msg->sadb_msg_seq)); + return key_senderror(so, m, ENOENT); + } +#else + if ((sav = key_getsavbyspi(sah, sa0->sadb_sa_spi)) == NULL) { + ipseclog((LOG_DEBUG, + "key_update: no such a SA found (spi:%u)\n", + (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, + "key_update: protocol mismatched (DB=%u param=%u)\n", + sav->sah->saidx.proto, proto)); + return key_senderror(so, m, EINVAL); + } +#ifdef IPSEC_DOSEQCHECK + if (sav->spi != sa0->sadb_sa_spi) { + ipseclog((LOG_DEBUG, + "key_update: SPI mismatched (DB:%u param:%u)\n", + (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, + "key_update: pid mismatched (DB:%u param:%u)\n", + 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); + } + + /* 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, "key_update: No more memory.\n")); + 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, "key_getsabyseq"); + + if (sav->seq == seq) { + SA_ADDREF(sav); + KEYDEBUG(KEYDEBUG_IPSEC_STAMP, + printf("DP key_getsavbyseq cause " + "refcnt++:%d SA:%p\n", + sav->refcnt, sav)); + return sav; + } + } + + return NULL; +} +#endif + +/* + * SADB_ADD processing + * add a 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; + struct secasindex saidx; + struct secashead *newsah; + struct secasvar *newsav; + u_int16_t proto; + u_int8_t mode; + u_int32_t reqid; + int error; + + /* sanity check */ + if (so == NULL || m == NULL || mhp == NULL || mhp->msg == NULL) + panic("key_add: NULL pointer is passed.\n"); + + /* map satype to proto */ + if ((proto = key_satype2proto(mhp->msg->sadb_msg_satype)) == 0) { + ipseclog((LOG_DEBUG, "key_add: invalid satype is passed.\n")); + 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, "key_add: invalid message is passed.\n")); + 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, "key_add: invalid message is passed.\n")); + 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); + + /* get a SA header */ + if ((newsah = key_getsah(&saidx)) == NULL) { + /* create a new SA header */ + if ((newsah = key_newsah(&saidx)) == NULL) { + ipseclog((LOG_DEBUG, "key_add: No more memory.\n")); + 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. */ + if (key_getsavbyspi(newsah, sa0->sadb_sa_spi)) { + ipseclog((LOG_DEBUG, "key_add: SA already exists.\n")); + return key_senderror(so, m, EEXIST); + } + newsav = KEY_NEWSAV(m, mhp, newsah, &error); + if (newsav == NULL) { + return key_senderror(so, m, error); + } + + /* 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, "key_update: No more memory.\n")); + 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; + + /* sanity check */ + if (sah == NULL || m == NULL || mhp == NULL || mhp->msg == NULL) + panic("key_setident: NULL pointer is passed.\n"); + + /* 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, "key_setident: invalid identity.\n")); + 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, "key_setident: ident type mismatch.\n")); + 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 */ + KMALLOC(sah->idents, struct sadb_ident *, idsrclen); + if (sah->idents == NULL) { + ipseclog((LOG_DEBUG, "key_setident: No more memory.\n")); + return ENOBUFS; + } + KMALLOC(sah->identd, struct sadb_ident *, iddstlen); + if (sah->identd == NULL) { + KFREE(sah->idents); + sah->idents = NULL; + ipseclog((LOG_DEBUG, "key_setident: No more memory.\n")); + return ENOBUFS; + } + bcopy(idsrc, sah->idents, idsrclen); + bcopy(iddst, sah->identd, iddstlen); + + 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; + + /* sanity check */ + if (m == NULL || mhp == NULL || mhp->msg == NULL) + panic("key_getmsgbuf_x1: NULL pointer is passed.\n"); + + /* 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; + + /* sanity check */ + if (so == NULL || m == NULL || mhp == NULL || mhp->msg == NULL) + panic("key_delete: NULL pointer is passed.\n"); + + /* map satype to proto */ + if ((proto = key_satype2proto(mhp->msg->sadb_msg_satype)) == 0) { + ipseclog((LOG_DEBUG, "key_delete: invalid satype is passed.\n")); + return key_senderror(so, m, EINVAL); + } + + if (mhp->ext[SADB_EXT_ADDRESS_SRC] == NULL || + mhp->ext[SADB_EXT_ADDRESS_DST] == NULL) { + ipseclog((LOG_DEBUG, "key_delete: 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)) { + ipseclog((LOG_DEBUG, "key_delete: invalid message is passed.\n")); + 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, "key_delete: doing delete all.\n")); + return key_delete_all(so, m, mhp, proto); + } else if (mhp->extlen[SADB_EXT_SA] < sizeof(struct sadb_sa)) { + ipseclog((LOG_DEBUG, "key_delete: invalid message is passed.\n")); + 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); + + /* get a SA header */ + LIST_FOREACH(sah, &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) { + ipseclog((LOG_DEBUG, "key_delete: no SA found.\n")); + return key_senderror(so, m, ENOENT); + } + + key_sa_chgstate(sav, SADB_SASTATE_DEAD); + KEY_FREESAV(&sav); + + { + struct mbuf *n; + struct sadb_msg *newmsg; + + /* create new sadb_msg to reply. */ + 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(so, m, mhp, proto) + 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); + + LIST_FOREACH(sah, &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, "key_delete_all: " + "invalid sav->state " + "(queue: %d SA: %d)\n", + state, sav->state)); + continue; + } + + key_sa_chgstate(sav, SADB_SASTATE_DEAD); + KEY_FREESAV(&sav); + } + } + } + { + struct mbuf *n; + struct sadb_msg *newmsg; + + /* create new sadb_msg to reply. */ + 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; + + /* sanity check */ + if (so == NULL || m == NULL || mhp == NULL || mhp->msg == NULL) + panic("key_get: NULL pointer is passed.\n"); + + /* map satype to proto */ + if ((proto = key_satype2proto(mhp->msg->sadb_msg_satype)) == 0) { + ipseclog((LOG_DEBUG, "key_get: invalid satype is passed.\n")); + 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, "key_get: invalid message is passed.\n")); + 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, "key_get: invalid message is passed.\n")); + 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); + + /* get a SA header */ + LIST_FOREACH(sah, &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) { + ipseclog((LOG_DEBUG, "key_get: no SA found.\n")); + 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, "key_get: there was invalid proto in SAD.\n")); + 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) < ipsec_esp_keymin) + continue; + if (_BITS(algo->minkey) < ipsec_esp_keymin) + encmin = ipsec_esp_keymin; + else + encmin = _BITS(algo->minkey); + + if (ipsec_esp_auth) + m = key_getcomb_ah(); + else { + KASSERT(l <= MLEN, + ("key_getcomb_esp: 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; + KASSERT((totlen % l) == 0, + ("key_getcomb_esp: 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(("key_getsizes_ah: unknown AH algorithm %u\n", + 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) < ipsec_ah_keymin) + continue; + + if (!m) { + KASSERT(l <= MLEN, + ("key_getcomb_ah: 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) { + KASSERT(l <= MLEN, + ("key_getcomb_ipcomp: 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; +#ifndef IPSEC_NONBLOCK_ACQUIRE + struct secacq *newacq; +#endif + u_int8_t satype; + int error = -1; + u_int32_t seq; + + /* sanity check */ + KASSERT(saidx != NULL, ("key_acquire: null saidx")); + satype = key_proto2satype(saidx->proto); + KASSERT(satype != 0, + ("key_acquire: null satype, protocol %u", saidx->proto)); + +#ifndef IPSEC_NONBLOCK_ACQUIRE + /* + * 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 a entry to check whether sending message or not. */ + if ((newacq = key_getacq(saidx)) != NULL) { + if (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; + + /* add to acqtree */ + LIST_INSERT_HEAD(&acqtree, newacq, chain); + } +#endif + + +#ifndef IPSEC_NONBLOCK_ACQUIRE + seq = newacq->seq; +#else + seq = (acq_seq = (acq_seq == ~0 ? 1 : ++acq_seq)); +#endif + m = key_setsadbmsg(SADB_ACQUIRE, 0, satype, seq, 0, 0); + if (!m) { + error = ENOBUFS; + goto fail; + } + result = m; + + /* 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; +} + +#ifndef IPSEC_NONBLOCK_ACQUIRE +static struct secacq * +key_newacq(const struct secasindex *saidx) +{ + struct secacq *newacq; + + /* get new entry */ + KMALLOC(newacq, struct secacq *, sizeof(struct secacq)); + if (newacq == NULL) { + ipseclog((LOG_DEBUG, "key_newacq: No more memory.\n")); + return NULL; + } + bzero(newacq, sizeof(*newacq)); + + /* copy secindex */ + bcopy(saidx, &newacq->saidx, sizeof(newacq->saidx)); + newacq->seq = (acq_seq == ~0 ? 1 : ++acq_seq); + newacq->created = time_second; + newacq->count = 0; + + return newacq; +} + +static struct secacq * +key_getacq(const struct secasindex *saidx) +{ + struct secacq *acq; + + LIST_FOREACH(acq, &acqtree, chain) { + if (key_cmpsaidx(saidx, &acq->saidx, CMP_EXACTLY)) + return acq; + } + + return NULL; +} + +static struct secacq * +key_getacqbyseq(seq) + u_int32_t seq; +{ + struct secacq *acq; + + LIST_FOREACH(acq, &acqtree, chain) { + if (acq->seq == seq) + return acq; + } + + return NULL; +} +#endif + +static struct secspacq * +key_newspacq(spidx) + struct secpolicyindex *spidx; +{ + struct secspacq *acq; + + /* get new entry */ + KMALLOC(acq, struct secspacq *, sizeof(struct secspacq)); + if (acq == NULL) { + ipseclog((LOG_DEBUG, "key_newspacq: No more memory.\n")); + return NULL; + } + bzero(acq, sizeof(*acq)); + + /* copy secindex */ + bcopy(spidx, &acq->spidx, sizeof(acq->spidx)); + acq->created = time_second; + acq->count = 0; + + return acq; +} + +static struct secspacq * +key_getspacq(spidx) + struct secpolicyindex *spidx; +{ + struct secspacq *acq; + + LIST_FOREACH(acq, &spacqtree, chain) { + if (key_cmpspidx_exactly(spidx, &acq->spidx)) + return acq; + } + + 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; + + /* sanity check */ + if (so == NULL || m == NULL || mhp == NULL || mhp->msg == NULL) + panic("key_acquire2: NULL pointer is passed.\n"); + + /* + * 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))) { +#ifndef IPSEC_NONBLOCK_ACQUIRE + struct secacq *acq; + + /* check sequence number */ + if (mhp->msg->sadb_msg_seq == 0) { + ipseclog((LOG_DEBUG, "key_acquire2: must specify sequence number.\n")); + 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; +#endif + 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, "key_acquire2: invalid satype is passed.\n")); + 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, "key_acquire2: 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_EXT_PROPOSAL] < sizeof(struct sadb_prop)) { + /* error */ + ipseclog((LOG_DEBUG, "key_acquire2: invalid message is passed.\n")); + 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); + + /* get a SA index */ + LIST_FOREACH(sah, &sahtree, chain) { + if (sah->state == SADB_SASTATE_DEAD) + continue; + if (key_cmpsaidx(&sah->saidx, &saidx, CMP_MODE_REQID)) + break; + } + if (sah != NULL) { + ipseclog((LOG_DEBUG, "key_acquire2: a SA exists already.\n")); + return key_senderror(so, m, EEXIST); + } + + error = key_acquire(&saidx, NULL); + if (error != 0) { + ipseclog((LOG_DEBUG, "key_acquire2: error %d returned " + "from key_acquire.\n", 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; + + /* sanity check */ + if (so == NULL || m == NULL || mhp == NULL || mhp->msg == NULL) + panic("key_register: NULL pointer is passed.\n"); + + /* check for invalid register message */ + if (mhp->msg->sadb_msg_satype >= sizeof(regtree)/sizeof(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 */ + LIST_FOREACH(reg, ®tree[mhp->msg->sadb_msg_satype], chain) { + if (reg->so == so) { + ipseclog((LOG_DEBUG, "key_register: socket exists already.\n")); + return key_senderror(so, m, EEXIST); + } + } + + /* create regnode */ + KMALLOC(newreg, struct secreg *, sizeof(*newreg)); + if (newreg == NULL) { + ipseclog((LOG_DEBUG, "key_register: No more memory.\n")); + return key_senderror(so, m, ENOBUFS); + } + bzero((caddr_t)newreg, sizeof(*newreg)); + + newreg->so = so; + ((struct keycb *)sotorawcb(so))->kp_registered++; + + /* add regnode to regtree. */ + LIST_INSERT_HEAD(®tree[mhp->msg->sadb_msg_satype], newreg, chain); + + 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)); + } + } + +#ifdef DIGAGNOSTIC + if (off != len) + panic("length assumption failed in key_register"); +#endif + + 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(so) + struct socket *so; +{ + struct secreg *reg; + int i; + + /* sanity check */ + if (so == NULL) + panic("key_freereg: NULL pointer is passed.\n"); + + /* + * 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. + */ + for (i = 0; i <= SADB_SATYPE_MAX; i++) { + LIST_FOREACH(reg, ®tree[i], chain) { + if (reg->so == so + && __LIST_CHAINED(reg)) { + LIST_REMOVE(reg, chain); + KFREE(reg); + break; + } + } + } + + return; +} + +/* + * 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(sav) + 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()*/ + + /* sanity check */ + if (sav == NULL) + panic("key_expire: NULL pointer is passed.\n"); + if (sav->sah == NULL) + panic("key_expire: Why was SA index in SA NULL.\n"); + if ((satype = key_proto2satype(sav->sah->saidx.proto)) == 0) + panic("key_expire: invalid proto is passed.\n"); + + /* set msg header */ + 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->sadb_lifetime_allocations; + lt->sadb_lifetime_bytes = sav->lft_c->sadb_lifetime_bytes; + lt->sadb_lifetime_addtime = sav->lft_c->sadb_lifetime_addtime; + lt->sadb_lifetime_usetime = sav->lft_c->sadb_lifetime_usetime; + lt = (struct sadb_lifetime *)(mtod(m, caddr_t) + len / 2); + bcopy(sav->lft_s, lt, sizeof(*lt)); + 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); + + 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; + + /* sanity check */ + if (so == NULL || mhp == NULL || mhp->msg == NULL) + panic("key_flush: NULL pointer is passed.\n"); + + /* map satype to proto */ + if ((proto = key_satype2proto(mhp->msg->sadb_msg_satype)) == 0) { + ipseclog((LOG_DEBUG, "key_flush: invalid satype is passed.\n")); + return key_senderror(so, m, EINVAL); + } + + /* no SATYPE specified, i.e. flushing all SA. */ + for (sah = LIST_FIRST(&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; + } + + if (m->m_len < sizeof(struct sadb_msg) || + sizeof(struct sadb_msg) > m->m_len + M_TRAILINGSPACE(m)) { + ipseclog((LOG_DEBUG, "key_flush: No more memory.\n")); + 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; + + /* sanity check */ + if (so == NULL || m == NULL || mhp == NULL || mhp->msg == NULL) + panic("key_dump: NULL pointer is passed.\n"); + + /* map satype to proto */ + if ((proto = key_satype2proto(mhp->msg->sadb_msg_satype)) == 0) { + ipseclog((LOG_DEBUG, "key_dump: invalid satype is passed.\n")); + return key_senderror(so, m, EINVAL); + } + + /* count sav entries to be sent to the userland. */ + cnt = 0; + LIST_FOREACH(sah, &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) + return key_senderror(so, m, ENOENT); + + /* send this to the userland, one at a time. */ + newmsg = NULL; + LIST_FOREACH(sah, &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) { + ipseclog((LOG_DEBUG, "key_dump: there was invalid proto in SAD.\n")); + 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) + return key_senderror(so, m, ENOBUFS); + + key_sendup_mbuf(so, n, KEY_SENDUP_ONE); + } + } + } + + 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; + + /* sanity check */ + if (so == NULL || m == NULL || mhp == NULL || mhp->msg == NULL) + panic("key_promisc: NULL pointer is passed.\n"); + + 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; + + /* sanity check */ + if (m == NULL || so == NULL) + panic("key_parse: NULL pointer is passed.\n"); + +#if 0 /*kdebug_sadb assumes msg in linear buffer*/ + KEYDEBUG(KEYDEBUG_KEY_DUMP, + ipseclog((LOG_DEBUG, "key_parse: passed sadb_msg\n")); + 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, "key_parse: invalid message length.\n")); + pfkeystat.out_invlen++; + error = EINVAL; + goto senderror; + } + + if (msg->sadb_msg_version != PF_KEY_V2) { + ipseclog((LOG_DEBUG, + "key_parse: PF_KEY version %u is mismatched.\n", + msg->sadb_msg_version)); + pfkeystat.out_invver++; + error = EINVAL; + goto senderror; + } + + if (msg->sadb_msg_type > SADB_MAX) { + ipseclog((LOG_DEBUG, "key_parse: invalid type %u is passed.\n", + msg->sadb_msg_type)); + 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; + + if (m->m_next) { /*XXX*/ + m_freem(m); + return ENOBUFS; + } + + 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, "key_parse: must specify satype " + "when msg type=%u.\n", msg->sadb_msg_type)); + pfkeystat.out_invsatype++; + error = EINVAL; + goto senderror; + } + break; + case SADB_SATYPE_AH: + case SADB_SATYPE_ESP: + case SADB_X_SATYPE_IPCOMP: + 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, "key_parse: illegal satype=%u\n", + msg->sadb_msg_type)); + 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, "key_parse: type %u isn't supported.\n", + msg->sadb_msg_satype)); + 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, "key_parse: invalid type %u is passed.\n", + msg->sadb_msg_satype)); + 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, "key_parse: upper layer protocol mismatched.\n")); + 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, "key_parse: address family mismatched.\n")); + pfkeystat.out_invaddr++; + error = EINVAL; + goto senderror; + } + if (PFKEY_ADDR_SADDR(src0)->sa_len != + PFKEY_ADDR_SADDR(dst0)->sa_len) { + ipseclog((LOG_DEBUG, + "key_parse: address struct size mismatched.\n")); + 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)) { + pfkeystat.out_invaddr++; + error = EINVAL; + goto senderror; + } + break; + case AF_INET6: + if (PFKEY_ADDR_SADDR(src0)->sa_len != + sizeof(struct sockaddr_in6)) { + pfkeystat.out_invaddr++; + error = EINVAL; + goto senderror; + } + break; + default: + ipseclog((LOG_DEBUG, + "key_parse: unsupported address family.\n")); + 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, + "key_parse: illegal prefixlen.\n")); + 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) { + 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; + + if (m->m_len < sizeof(struct sadb_msg)) + panic("invalid mbuf passed to key_senderror"); + + 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; + + /* sanity check */ + if (m == NULL || mhp == NULL) + panic("key_align: NULL pointer is passed.\n"); + if (m->m_len < sizeof(struct sadb_msg)) + panic("invalid mbuf passed to key_align"); + + /* 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: + /* 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, + "key_align: duplicate ext_type %u " + "is passed.\n", ext->sadb_ext_type)); + m_freem(m); + pfkeystat.out_dupext++; + return EINVAL; + } + break; + default: + ipseclog((LOG_DEBUG, + "key_align: invalid ext_type %u is passed.\n", + ext->sadb_ext_type)); + m_freem(m); + pfkeystat.out_invexttype++; + return EINVAL; + } + + extlen = PFKEY_UNUNIT64(ext->sadb_ext_len); + + if (key_validate_ext(ext, extlen)) { + m_freem(m); + 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); + 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() +{ + int i; + + for (i = 0; i < IPSEC_DIR_MAX; i++) { + LIST_INIT(&sptree[i]); + } + + LIST_INIT(&sahtree); + + for (i = 0; i <= SADB_SATYPE_MAX; i++) { + LIST_INIT(®tree[i]); + } + +#ifndef IPSEC_NONBLOCK_ACQUIRE + LIST_INIT(&acqtree); +#endif + LIST_INIT(&spacqtree); + + /* system default */ + ip4_def_policy.policy = IPSEC_POLICY_NONE; + ip4_def_policy.refcnt++; /*never reclaim this*/ + +#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"); + + return; +} + +/* + * 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; +{ + /* sanity check */ + if (sav->sah == NULL) + panic("sav->sah == NULL at key_checktunnelsanity"); + + /* XXX: check inner IP header */ + + return 1; +} + +#if 0 +#define hostnamelen strlen(hostname) + +/* + * Get FQDN for the host. + * If the administrator configured hostname (by hostname(1)) without + * domain name, returns nothing. + */ +static const char * +key_getfqdn() +{ + int i; + int hasdot; + static char fqdn[MAXHOSTNAMELEN + 1]; + + if (!hostnamelen) + return NULL; + + /* check if it comes with domain name. */ + hasdot = 0; + for (i = 0; i < hostnamelen; i++) { + if (hostname[i] == '.') + hasdot++; + } + if (!hasdot) + return NULL; + + /* NOTE: hostname may not be NUL-terminated. */ + bzero(fqdn, sizeof(fqdn)); + bcopy(hostname, fqdn, hostnamelen); + fqdn[hostnamelen] = '\0'; + return fqdn; +} + +/* + * get username@FQDN for the host/user. + */ +static const char * +key_getuserfqdn() +{ + const char *host; + static char userfqdn[MAXHOSTNAMELEN + MAXLOGNAME + 2]; + struct proc *p = curproc; + char *q; + + if (!p || !p->p_pgrp || !p->p_pgrp->pg_session) + return NULL; + if (!(host = key_getfqdn())) + return NULL; + + /* NOTE: s_login may not be-NUL terminated. */ + bzero(userfqdn, sizeof(userfqdn)); + bcopy(p->p_pgrp->pg_session->s_login, userfqdn, MAXLOGNAME); + userfqdn[MAXLOGNAME] = '\0'; /* safeguard */ + q = userfqdn + strlen(userfqdn); + *q++ = '@'; + bcopy(host, q, strlen(host)); + q += strlen(host); + *q++ = '\0'; + + return userfqdn; +} +#endif + +/* record data transfer on SA, and update timestamps */ +void +key_sa_recordxfer(sav, m) + struct secasvar *sav; + struct mbuf *m; +{ + KASSERT(sav != NULL, ("key_sa_recordxfer: Null secasvar")); + KASSERT(m != NULL, ("key_sa_recordxfer: Null mbuf")); + if (!sav->lft_c) + return; + + /* + * XXX Currently, there is a difference of bytes size + * between inbound and outbound processing. + */ + sav->lft_c->sadb_lifetime_bytes += m->m_pkthdr.len; + /* to check bytes lifetime is done in key_timehandler(). */ + + /* + * We use the number of packets as the unit of + * sadb_lifetime_allocations. We increment the variable + * whenever {esp,ah}_{in,out}put is called. + */ + sav->lft_c->sadb_lifetime_allocations++; + /* XXX check for expires? */ + + /* + * NOTE: We record CURRENT sadb_lifetime_usetime by using wall clock, + * in seconds. HARD and SOFT lifetime are measured by the time + * difference (again in seconds) from sadb_lifetime_usetime. + * + * usetime + * v expire expire + * -----+-----+--------+---> t + * <--------------> HARD + * <-----> SOFT + */ + sav->lft_c->sadb_lifetime_usetime = time_second; + /* XXX check for expires? */ + + return; +} + +/* dumb version */ +void +key_sa_routechange(dst) + struct sockaddr *dst; +{ + struct secashead *sah; + struct route *ro; + + LIST_FOREACH(sah, &sahtree, chain) { + ro = &sah->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; + } + } + + return; +} + +static void +key_sa_chgstate(sav, state) + struct secasvar *sav; + u_int8_t state; +{ + if (sav == NULL) + panic("key_sa_chgstate called with sav == NULL"); + + if (sav->state == state) + return; + + 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; +{ + + if (!sav->iv) + panic("key_sa_stir_iv called with sav == NULL"); + 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; +} diff --git a/sys/netipsec/key.h b/sys/netipsec/key.h new file mode 100644 index 0000000..879a7cf --- /dev/null +++ b/sys/netipsec/key.h @@ -0,0 +1,107 @@ +/* $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_H_ +#define _NETIPSEC_KEY_H_ + +#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 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); +extern struct secpolicy *key_gettunnel(const struct sockaddr *, + const struct sockaddr *, const struct sockaddr *, + const struct sockaddr *, const char*, int); +/* 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__) +#define KEY_GETTUNNEL(osrc, odst, isrc, idst) \ + key_gettunnel(osrc, odst, isrc, idst, __FILE__, __LINE__) +#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)); +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 MALLOC_DECLARE +MALLOC_DECLARE(M_SECA); +#endif /* MALLOC_DECLARE */ + +#endif /* defined(_KERNEL) */ +#endif /* _NETIPSEC_KEY_H_ */ diff --git a/sys/netipsec/key_debug.c b/sys/netipsec/key_debug.c new file mode 100644 index 0000000..b38fc61 --- /dev/null +++ b/sys/netipsec/key_debug.c @@ -0,0 +1,747 @@ +/* $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 "opt_inet.h" +#include "opt_inet6.h" +#include "opt_ipsec.h" +#endif + +#include <sys/types.h> +#include <sys/param.h> +#ifdef _KERNEL +#include <sys/systm.h> +#include <sys/mbuf.h> +#include <sys/queue.h> +#endif +#include <sys/socket.h> + +#include <net/route.h> + +#include <netipsec/key_var.h> +#include <netipsec/key_debug.h> + +#include <netinet/in.h> +#include <netipsec/ipsec.h> + +#ifndef _KERNEL +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#endif /* !_KERNEL */ + +static void kdebug_sadb_prop __P((struct sadb_ext *)); +static void kdebug_sadb_identity __P((struct sadb_ext *)); +static void kdebug_sadb_supported __P((struct sadb_ext *)); +static void kdebug_sadb_lifetime __P((struct sadb_ext *)); +static void kdebug_sadb_sa __P((struct sadb_ext *)); +static void kdebug_sadb_address __P((struct sadb_ext *)); +static void kdebug_sadb_key __P((struct sadb_ext *)); +static void kdebug_sadb_x_sa2 __P((struct sadb_ext *)); + +#ifdef _KERNEL +static void kdebug_secreplay __P((struct secreplay *)); +#endif + +#ifndef _KERNEL +#define panic(param) { printf(param); 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("kdebug_sadb: NULL pointer was passed.\n"); + + 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("kdebug_sadb: invalid ext_len=0 was passed.\n"); + return; + } + if (ext->sadb_ext_len > tlen) { + printf("kdebug_sadb: ext_len exceeds end of buffer.\n"); + 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("kdebug_sadb: invalid ext_type %u was passed.\n", + 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("kdebug_sadb_prop: NULL pointer was passed.\n"); + + 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("kdebug_sadb_identity: NULL pointer was passed.\n"); + + 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("kdebug_sadb_supported: NULL pointer was passed.\n"); + + 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) + printf("kdebug_sadb_lifetime: NULL pointer was passed.\n"); + + 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("kdebug_sadb_sa: NULL pointer was passed.\n"); + + 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("kdebug_sadb_address: NULL pointer was passed.\n"); + + 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("kdebug_sadb_key: NULL pointer was passed.\n"); + + 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("kdebug_sadb_key: key length mismatch, bit:%d len:%ld.\n", + 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("kdebug_sadb_x_sa2: NULL pointer was passed.\n"); + + 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("kdebug_sadb_x_policy: NULL pointer was passed.\n"); + + 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("kdebug_sadb_x_policy: wrong policy struct.\n"); + return; + } + /* prevent overflow */ + if (xisr->sadb_x_ipsecrequest_len > tlen) { + printf("invalid ipsec policy length\n"); + return; + } + + tlen -= xisr->sadb_x_ipsecrequest_len; + + xisr = (struct sadb_x_ipsecrequest *)((caddr_t)xisr + + xisr->sadb_x_ipsecrequest_len); + } + + if (tlen != 0) + panic("kdebug_sadb_x_policy: wrong policy struct.\n"); + } + + return; +} + +#ifdef _KERNEL +/* %%%: about SPD and SAD */ +void +kdebug_secpolicy(sp) + struct secpolicy *sp; +{ + /* sanity check */ + if (sp == NULL) + panic("kdebug_secpolicy: NULL pointer was passed.\n"); + + 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("kdebug_secpolicy: Invalid policy found. %d\n", + sp->policy); + break; + } + + return; +} + +void +kdebug_secpolicyindex(spidx) + struct secpolicyindex *spidx; +{ + /* sanity check */ + if (spidx == NULL) + panic("kdebug_secpolicyindex: NULL pointer was passed.\n"); + + 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("kdebug_secpolicyindex: NULL pointer was passed.\n"); + + 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; +} + +void +kdebug_secasv(sav) + struct secasvar *sav; +{ + /* sanity check */ + if (sav == NULL) + panic("kdebug_secasv: NULL pointer was passed.\n"); + + 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_sadb_lifetime((struct sadb_ext *)sav->lft_c); + if (sav->lft_h != NULL) + kdebug_sadb_lifetime((struct sadb_ext *)sav->lft_h); + if (sav->lft_s != NULL) + kdebug_sadb_lifetime((struct sadb_ext *)sav->lft_s); + +#if notyet + /* XXX: misc[123] ? */ +#endif + + return; +} + +static void +kdebug_secreplay(rpl) + struct secreplay *rpl; +{ + int len, l; + + /* sanity check */ + if (rpl == NULL) + panic("kdebug_secreplay: NULL pointer was passed.\n"); + + 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("kdebug_sockaddr: NULL pointer was passed.\n"); + + /* 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/sys/netipsec/key_debug.h b/sys/netipsec/key_debug.h new file mode 100644 index 0000000..f105e85 --- /dev/null +++ b/sys/netipsec/key_debug.h @@ -0,0 +1,88 @@ +/* $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_H_ +#define _NETIPSEC_KEY_DEBUG_H_ + +#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 ((key_debug_level & (lev)) == (lev)) { arg; } } while (0) + +extern u_int32_t 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_H_ */ diff --git a/sys/netipsec/key_var.h b/sys/netipsec/key_var.h new file mode 100644 index 0000000..e10cb99 --- /dev/null +++ b/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_H_ +#define _NETIPSEC_KEY_VAR_H_ + +/* 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)->sadb_key_bits >> 3)) +#define _KEYBITS(key) ((u_int)((key)->sadb_key_bits)) +#define _KEYBUF(key) ((caddr_t)((caddr_t)(key) + sizeof(struct sadb_key))) +#endif /*_KERNEL*/ + +#endif /* _NETIPSEC_KEY_VAR_H_ */ diff --git a/sys/netipsec/keydb.h b/sys/netipsec/keydb.h new file mode 100644 index 0000000..7739856 --- /dev/null +++ b/sys/netipsec/keydb.h @@ -0,0 +1,181 @@ +/* $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_H_ +#define _NETIPSEC_KEYDB_H_ + +#ifdef _KERNEL + +#include <netipsec/key_var.h> + +/* + * The union of all possible address formats we handle. + */ +union sockaddr_union { + struct sockaddr sa; + struct sockaddr_in sin; + struct sockaddr_in6 sin6; +}; + +/* 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. */ +}; + +/* Security Association Data Base */ +struct secashead { + LIST_ENTRY(secashead) chain; + + struct secasindex saidx; + + struct sadb_ident *idents; /* source identity */ + struct sadb_ident *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 */ + + struct route sa_route; /* route cache */ +}; + +struct xformsw; +struct enc_xform; +struct auth_hash; +struct comp_algo; + +/* Security Association */ +struct secasvar { + LIST_ENTRY(secasvar) chain; + + 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 sadb_key *key_auth; /* Key for Authentication */ + struct sadb_key *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 */ + long created; /* for lifetime */ + + struct sadb_lifetime *lft_c; /* CURRENT lifetime, it's constant. */ + struct sadb_lifetime *lft_h; /* HARD lifetime */ + struct sadb_lifetime *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 */ +}; + +/* 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; +}; + +#ifndef IPSEC_NONBLOCK_ACQUIRE +/* acquiring list table. */ +struct secacq { + LIST_ENTRY(secacq) chain; + + struct secasindex saidx; + + u_int32_t seq; /* sequence number */ + long created; /* for lifetime */ + int count; /* for lifetime */ +}; +#endif + +/* Sensitivity Level Specification */ +/* nothing */ + +#define SADB_KILL_INTERVAL 600 /* six seconds */ + +/* secpolicy */ +extern struct secpolicy *keydb_newsecpolicy __P((void)); +extern void keydb_delsecpolicy __P((struct secpolicy *)); +/* secashead */ +extern struct secashead *keydb_newsecashead __P((void)); +extern void keydb_delsecashead __P((struct secashead *)); +/* secasvar */ +extern struct secasvar *keydb_newsecasvar __P((void)); +extern void keydb_refsecasvar __P((struct secasvar *)); +extern void keydb_freesecasvar __P((struct secasvar *)); +/* secreplay */ +extern struct secreplay *keydb_newsecreplay __P((size_t)); +extern void keydb_delsecreplay __P((struct secreplay *)); +/* secreg */ +extern struct secreg *keydb_newsecreg __P((void)); +extern void keydb_delsecreg __P((struct secreg *)); + +#endif /* _KERNEL */ + +#endif /* _NETIPSEC_KEYDB_H_ */ diff --git a/sys/netipsec/keysock.c b/sys/netipsec/keysock.c new file mode 100644 index 0000000..39f57e5 --- /dev/null +++ b/sys/netipsec/keysock.c @@ -0,0 +1,603 @@ +/* $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 "opt_ipsec.h" + +/* This code has derived from sys/net/rtsock.c on FreeBSD2.2.5 */ + +#include <sys/types.h> +#include <sys/param.h> +#include <sys/domain.h> +#include <sys/errno.h> +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <sys/mbuf.h> +#include <sys/protosw.h> +#include <sys/signalvar.h> +#include <sys/socket.h> +#include <sys/socketvar.h> +#include <sys/sysctl.h> +#include <sys/systm.h> + +#include <net/raw_cb.h> +#include <net/route.h> + +#include <net/pfkeyv2.h> +#include <netipsec/key.h> +#include <netipsec/keysock.h> +#include <netipsec/key_debug.h> + +#include <machine/stdarg.h> + +struct key_cb { + int key_count; + int any_count; +}; +static struct key_cb key_cb; + +static struct sockaddr key_dst = { 2, PF_KEY, }; +static struct sockaddr key_src = { 2, PF_KEY, }; + +static int key_sendup0 __P((struct rawcb *, struct mbuf *, int)); + +struct pfkeystat pfkeystat; + +/* + * key_output() + */ +int +#if __STDC__ +key_output(struct mbuf *m, ...) +#else +key_output(m, va_alist) + struct mbuf *m; + va_dcl +#endif +{ + struct sadb_msg *msg; + int len, error = 0; + int s; + struct socket *so; + va_list ap; + + va_start(ap, m); + so = va_arg(ap, struct socket *); + va_end(ap); + + if (m == 0) + panic("key_output: NULL pointer was passed.\n"); + + pfkeystat.out_total++; + pfkeystat.out_bytes += m->m_pkthdr.len; + + len = m->m_pkthdr.len; + if (len < sizeof(struct sadb_msg)) { + 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) { + pfkeystat.out_nomem++; + error = ENOBUFS; + goto end; + } + } + + if ((m->m_flags & M_PKTHDR) == 0) + panic("key_output: not M_PKTHDR ??"); + + KEYDEBUG(KEYDEBUG_KEY_DUMP, kdebug_mbuf(m)); + + msg = mtod(m, struct sadb_msg *); + pfkeystat.out_msgtype[msg->sadb_msg_type]++; + if (len != PFKEY_UNUNIT64(msg->sadb_msg_len)) { + pfkeystat.out_invlen++; + error = EINVAL; + goto end; + } + + /*XXX giant lock*/ + s = splnet(); + error = key_parse(m, so); + m = NULL; + splx(s); +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_NOWAIT); + if (m && m->m_len < sizeof(struct sadb_msg)) + m = m_pullup(m, sizeof(struct sadb_msg)); + if (!m) { + 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? */ + + pfkeystat.in_msgtype[pmsg->sadb_msg_type]++; + } + + if (!sbappendaddr(&rp->rcb_socket->so_rcv, (struct sockaddr *)&key_src, + m, NULL)) { + 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("key_sendup: NULL pointer was passed.\n"); + + KEYDEBUG(KEYDEBUG_KEY_DUMP, + printf("key_sendup: \n"); + kdebug_sadb(msg)); + + /* + * we increment statistics here, just in case we have ENOBUFS + * in this function. + */ + pfkeystat.in_total++; + pfkeystat.in_bytes += len; + 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); + n->m_len = MHLEN; + } else { + MGET(n, M_DONTWAIT, MT_DATA); + n->m_len = MLEN; + } + if (!n) { + pfkeystat.in_nomem++; + return ENOBUFS; + } + if (tlen >= MCLBYTES) { /*XXX better threshold? */ + MCLGET(n, M_DONTWAIT); + if ((n->m_flags & M_EXT) == 0) { + m_free(n); + m_freem(m); + 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 */ + pfkeystat.in_total--; + pfkeystat.in_bytes -= len; + 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("key_sendup_mbuf: NULL pointer was passed.\n"); + + pfkeystat.in_total++; + pfkeystat.in_bytes += m->m_pkthdr.len; + if (m->m_len < sizeof(struct sadb_msg)) { +#if 1 + m = m_pullup(m, sizeof(struct sadb_msg)); + if (m == NULL) { + pfkeystat.in_nomem++; + return ENOBUFS; + } +#else + /* don't bother pulling it up just for stats */ +#endif + } + if (m->m_len >= sizeof(struct sadb_msg)) { + struct sadb_msg *msg; + msg = mtod(m, struct sadb_msg *); + pfkeystat.in_msgtype[msg->sadb_msg_type]++; + } + + LIST_FOREACH(rp, &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; + } + pfkeystat.in_msgtarget[target]++; + + if (!sendup) + continue; + + if ((n = m_copy(m, 0, (int)M_COPYALL)) == NULL) { + m_freem(m); + pfkeystat.in_nomem++; + return ENOBUFS; + } + + if ((error = key_sendup0(rp, n, 0)) != 0) { + m_freem(m); + return error; + } + + n = NULL; + } + + if (so) { + error = key_sendup0(sotorawcb(so), m, 0); + m = NULL; + } else { + error = 0; + m_freem(m); + } + return error; +} + +/* + * key_abort() + * derived from net/rtsock.c:rts_abort() + */ +static int +key_abort(struct socket *so) +{ + int s, error; + s = splnet(); + error = raw_usrreqs.pru_abort(so); + splx(s); + return error; +} + +/* + * 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 s, error; + + if (sotorawcb(so) != 0) + return EISCONN; /* XXX panic? */ + kp = (struct keycb *)malloc(sizeof *kp, M_PCB, M_WAITOK|M_ZERO); /* XXX */ + if (kp == 0) + return ENOBUFS; + + /* + * The splnet() is necessary to block protocols from sending + * error notifications (like RTM_REDIRECT or RTM_LOSING) while + * this PCB is extant but incompletely initialized. + * Probably we should try to do more of this work beforehand and + * eliminate the spl. + */ + s = splnet(); + so->so_pcb = (caddr_t)kp; + error = raw_usrreqs.pru_attach(so, proto, td); + kp = (struct keycb *)sotorawcb(so); + if (error) { + free(kp, M_PCB); + so->so_pcb = (caddr_t) 0; + splx(s); + return error; + } + + kp->kp_promisc = kp->kp_registered = 0; + + if (kp->kp_raw.rcb_proto.sp_protocol == PF_KEY) /* XXX: AF_KEY */ + key_cb.key_count++; + key_cb.any_count++; + kp->kp_raw.rcb_laddr = &key_src; + kp->kp_raw.rcb_faddr = &key_dst; + soisconnected(so); + so->so_options |= SO_USELOOPBACK; + + splx(s); + return 0; +} + +/* + * key_bind() + * derived from net/rtsock.c:rts_bind() + */ +static int +key_bind(struct socket *so, struct sockaddr *nam, struct thread *td) +{ + int s, error; + s = splnet(); + error = raw_usrreqs.pru_bind(so, nam, td); /* xxx just EINVAL */ + splx(s); + return error; +} + +/* + * key_connect() + * derived from net/rtsock.c:rts_connect() + */ +static int +key_connect(struct socket *so, struct sockaddr *nam, struct thread *td) +{ + int s, error; + s = splnet(); + error = raw_usrreqs.pru_connect(so, nam, td); /* XXX just EINVAL */ + splx(s); + return error; +} + +/* + * key_detach() + * derived from net/rtsock.c:rts_detach() + */ +static int +key_detach(struct socket *so) +{ + struct keycb *kp = (struct keycb *)sotorawcb(so); + int s, error; + + s = splnet(); + if (kp != 0) { + if (kp->kp_raw.rcb_proto.sp_protocol + == PF_KEY) /* XXX: AF_KEY */ + key_cb.key_count--; + key_cb.any_count--; + + key_freereg(so); + } + error = raw_usrreqs.pru_detach(so); + splx(s); + return error; +} + +/* + * key_disconnect() + * derived from net/rtsock.c:key_disconnect() + */ +static int +key_disconnect(struct socket *so) +{ + int s, error; + s = splnet(); + error = raw_usrreqs.pru_disconnect(so); + splx(s); + return error; +} + +/* + * key_peeraddr() + * derived from net/rtsock.c:rts_peeraddr() + */ +static int +key_peeraddr(struct socket *so, struct sockaddr **nam) +{ + int s, error; + s = splnet(); + error = raw_usrreqs.pru_peeraddr(so, nam); + splx(s); + return error; +} + +/* + * 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) +{ + int s, error; + s = splnet(); + error = raw_usrreqs.pru_send(so, flags, m, nam, control, td); + splx(s); + return error; +} + +/* + * key_shutdown() + * derived from net/rtsock.c:rts_shutdown() + */ +static int +key_shutdown(struct socket *so) +{ + int s, error; + s = splnet(); + error = raw_usrreqs.pru_shutdown(so); + splx(s); + return error; +} + +/* + * key_sockaddr() + * derived from net/rtsock.c:rts_sockaddr() + */ +static int +key_sockaddr(struct socket *so, struct sockaddr **nam) +{ + int s, error; + s = splnet(); + error = raw_usrreqs.pru_sockaddr(so, nam); + splx(s); + return error; +} + +struct pr_usrreqs key_usrreqs = { + key_abort, pru_accept_notsupp, key_attach, key_bind, + key_connect, + pru_connect2_notsupp, pru_control_notsupp, key_detach, + key_disconnect, pru_listen_notsupp, key_peeraddr, + pru_rcvd_notsupp, + pru_rcvoob_notsupp, key_send, pru_sense_null, key_shutdown, + key_sockaddr, sosend, soreceive, sopoll +}; + +/* 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[] = { +{ SOCK_RAW, &keydomain, PF_KEY_V2, PR_ATOMIC|PR_ADDR, + 0, (pr_output_t *)key_output, raw_ctlinput, 0, + 0, + raw_init, 0, 0, 0, + &key_usrreqs +} +}; + +static void +key_init0(void) +{ + bzero((caddr_t)&key_cb, sizeof(key_cb)); + key_init(); +} + +struct domain keydomain = + { PF_KEY, "key", key_init0, 0, 0, + keysw, &keysw[sizeof(keysw)/sizeof(keysw[0])] }; + +DOMAIN_SET(key); diff --git a/sys/netipsec/keysock.h b/sys/netipsec/keysock.h new file mode 100644 index 0000000..7d9080a --- /dev/null +++ b/sys/netipsec/keysock.h @@ -0,0 +1,82 @@ +/* $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_H_ +#define _NETIPSEC_KEYSOCK_H_ + +/* 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 */ +}; + +extern struct pfkeystat pfkeystat; + +extern int key_output __P((struct mbuf *, ...)); +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_H_*/ diff --git a/sys/netipsec/xform.h b/sys/netipsec/xform.h new file mode 100644 index 0000000..04c2e07 --- /dev/null +++ b/sys/netipsec/xform.h @@ -0,0 +1,126 @@ +/* $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_H_ +#define _NETIPSEC_XFORM_H_ + +#include <sys/types.h> +#include <netinet/in.h> +#include <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; +}; + +/* + * 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, ...); +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_H_ */ diff --git a/sys/netipsec/xform_ah.c b/sys/netipsec/xform_ah.c new file mode 100644 index 0000000..1063aad --- /dev/null +++ b/sys/netipsec/xform_ah.c @@ -0,0 +1,1209 @@ +/* $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 "opt_inet.h" +#include "opt_inet6.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/mbuf.h> +#include <sys/socket.h> +#include <sys/syslog.h> +#include <sys/kernel.h> +#include <sys/sysctl.h> + +#include <net/if.h> + +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <netinet/ip_ecn.h> +#include <netinet/ip6.h> + +#include <net/route.h> +#include <netipsec/ipsec.h> +#include <netipsec/ah.h> +#include <netipsec/ah_var.h> +#include <netipsec/xform.h> + +#ifdef INET6 +#include <netinet6/ip6_var.h> +#include <netipsec/ipsec6.h> +#include <netinet6/ip6_ecn.h> +#endif + +#include <netipsec/key.h> +#include <netipsec/key_debug.h> + +#include <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 gets + * this size from the xform but is (currently) always 12. + */ +#define AUTHSIZE(sav) \ + ((sav->flags & SADB_X_EXT_OLD) ? 16 : (sav)->tdb_authalgxform->authsize) + +int ah_enable = 1; /* control flow of packets with AH */ +int ah_cleartos = 1; /* clear ip_tos when doing AH calc */ +struct ahstat ahstat; + +SYSCTL_DECL(_net_inet_ah); +SYSCTL_INT(_net_inet_ah, OID_AUTO, + ah_enable, CTLFLAG_RW, &ah_enable, 0, ""); +SYSCTL_INT(_net_inet_ah, OID_AUTO, + ah_cleartos, CTLFLAG_RW, &ah_cleartos, 0, ""); +SYSCTL_STRUCT(_net_inet_ah, IPSECCTL_STATS, + stats, CTLFLAG_RD, &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 >= AH_ALG_MAX) + return NULL; + switch (alg) { + case SADB_X_AALG_NULL: + return &auth_hash_null; + case SADB_AALG_MD5HMAC: + return &auth_hash_hmac_md5_96; + case SADB_AALG_SHA1HMAC: + return &auth_hash_hmac_sha1_96; + case SADB_X_AALG_RIPEMD160HMAC: + return &auth_hash_hmac_ripemd_160_96; + 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; + KASSERT(sav->tdb_authalgxform != NULL, + ("ah_hdrsiz: 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(("ah_init: unsupported authentication algorithm %u\n", + 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(("ah_init: replay state block inconsistency, " + "%s algorithm %s replay state\n", + (sav->flags & SADB_X_EXT_OLD) ? "old" : "new", + sav->replay == NULL ? "without" : "with")); + return EINVAL; + } + if (sav->key_auth == NULL) { + DPRINTF(("ah_init: no authentication key for %s " + "algorithm\n", thash->name)); + return EINVAL; + } + keylen = _KEYLEN(sav->key_auth); + if (keylen != thash->keysize && thash->keysize != 0) { + DPRINTF(("ah_init: invalid keylength %d, algorithm " + "%s requires keysize %d\n", + 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 = _KEYBUF(sav->key_auth); + + 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, crypto_support); +} + +/* + * Paranoia. + * + * NB: public for use by esp_zeroize (XXX). + */ +int +ah_zeroize(struct secasvar *sav) +{ + int err; + + if (sav->key_auth) + bzero(_KEYBUF(sav->key_auth), _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(("ah_massage_headers: m_pullup failed\n")); + return ENOBUFS; + } + + /* Fix the IP header */ + ip = mtod(m, struct ip *); + if (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(("ah_massage_headers: illegal IPv4 " + "option length for option %d\n", + 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(("ah_massage_headers: " + "illegal IPv4 option length for " + "option %d\n", 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(("ah_massage_headers: " + "illegal IPv4 option length for " + "option %d\n", 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(("ah_massage_headers: " + "illegal IPv4 option length for " + "option %d\n", 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(("ah_massage_headers(): malformed " + "IPv4 options header\n")); + + 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(("ah_massage_headers: unsupported IPv6 jumbogram\n")); + 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(("ah_massage_headers: failed " + "to allocate memory for IPv6 " + "headers\n")); + 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(("ah_massage_headers: unexpected " + "IPv6 header type %d", 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; + +#if 0 + SPLASSERT(net, "ah_input"); +#endif + + KASSERT(sav != NULL, ("ah_input: null SA")); + KASSERT(sav->key_auth != NULL, + ("ah_input: null authentication key")); + KASSERT(sav->tdb_authalgxform != NULL, + ("ah_input: 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")); + ahstat.ahs_hdrops++; /*XXX*/ + m_freem(m); + return ENOBUFS; + } + + /* Check replay window, if applicable. */ + if (sav->replay && !ipsec_chkreplay(ntohl(ah->ah_seq), sav)) { + ahstat.ahs_replay++; + DPRINTF(("ah_input: packet replay failure: %s\n", + 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(("ah_input: bad authenticator length %u (expecting %lu)" + " for packet in SA %s/%08lx\n", + hl, (u_long) (authsize + rplen - sizeof (struct ah)), + ipsec_address(&sav->sah->saidx.dst), + (u_long) ntohl(sav->spi))); + ahstat.ahs_badauthl++; + m_freem(m); + return EACCES; + } + ahstat.ahs_ibytes += m->m_pkthdr.len - skip - hl; + + /* Get crypto descriptors. */ + crp = crypto_getreq(1); + if (crp == NULL) { + DPRINTF(("ah_input: failed to acquire crypto descriptor\n")); + ahstat.ahs_crypto++; + m_freem(m); + return ENOBUFS; + } + + crda = crp->crp_desc; + KASSERT(crda != NULL, ("ah_input: 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_key = _KEYBUF(sav->key_auth); + crda->crd_klen = _KEYBITS(sav->key_auth); + + /* 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(("ah_input: failed to allocate tdb_crypto\n")); + 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 */ + 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_NODELAY; + 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 s, authsize; + + crd = crp->crp_desc; + + tc = (struct tdb_crypto *) crp->crp_opaque; + KASSERT(tc != NULL, ("ah_input_cb: 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; + + s = splnet(); + + sav = KEY_ALLOCSA(&tc->tc_dst, tc->tc_proto, tc->tc_spi); + if (sav == NULL) { + ahstat.ahs_notdb++; + DPRINTF(("ah_input_cb: SA expired while in crypto\n")); + error = ENOBUFS; /*XXX*/ + goto bad; + } + + saidx = &sav->sah->saidx; + KASSERT(saidx->dst.sa.sa_family == AF_INET || + saidx->dst.sa.sa_family == AF_INET6, + ("ah_input_cb: 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) + return crypto_dispatch(crp); + + ahstat.ahs_noxform++; + DPRINTF(("ah_input_cb: crypto error %d\n", crp->crp_etype)); + error = crp->crp_etype; + goto bad; + } else { + ahstat.ahs_hist[sav->alg_auth]++; + crypto_freereq(crp); /* No longer needed. */ + crp = NULL; + } + + /* Shouldn't happen... */ + if (m == NULL) { + ahstat.ahs_crypto++; + DPRINTF(("ah_input_cb: bogus returned buffer from crypto\n")); + 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(("ah_input: authentication hash mismatch " + "for packet in SA %s/%08lx\n", + ipsec_address(&saidx->dst), + (u_long) ntohl(sav->spi))); + 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)) { + 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(("ah_input_cb: mangled mbuf chain for SA %s/%08lx\n", + ipsec_address(&saidx->dst), (u_long) ntohl(sav->spi))); + + ahstat.ahs_hdrops++; + goto bad; + } + + IPSEC_COMMON_INPUT_CB(m, sav, skip, protoff, mtag); + + KEY_FREESAV(&sav); + splx(s); + return error; +bad: + if (sav) + KEY_FREESAV(&sav); + splx(s); + 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; + +#if 0 + SPLASSERT(net, "ah_output"); +#endif + + sav = isr->sav; + KASSERT(sav != NULL, ("ah_output: null SA")); + ahx = sav->tdb_authalgxform; + KASSERT(ahx != NULL, ("ah_output: null authentication xform")); + + 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(("ah_output: unknown/unsupported protocol " + "family %u, SA %s/%08lx\n", + sav->sah->saidx.dst.sa.sa_family, + ipsec_address(&sav->sah->saidx.dst), + (u_long) ntohl(sav->spi))); + ahstat.ahs_nopf++; + error = EPFNOSUPPORT; + goto bad; + } + authsize = AUTHSIZE(sav); + if (rplen + authsize + m->m_pkthdr.len > maxpacketsize) { + DPRINTF(("ah_output: packet in SA %s/%08lx got too big " + "(len %u, max len %u)\n", + ipsec_address(&sav->sah->saidx.dst), + (u_long) ntohl(sav->spi), + rplen + authsize + m->m_pkthdr.len, maxpacketsize)); + ahstat.ahs_toobig++; + error = EMSGSIZE; + goto bad; + } + + /* Update the counters. */ + ahstat.ahs_obytes += m->m_pkthdr.len - skip; + + m = m_clone(m); + if (m == NULL) { + DPRINTF(("ah_output: cannot clone mbuf chain, SA %s/%08lx\n", + ipsec_address(&sav->sah->saidx.dst), + (u_long) ntohl(sav->spi))); + ahstat.ahs_hdrops++; + error = ENOBUFS; + goto bad; + } + + /* Inject AH header. */ + mi = m_makespace(m, skip, rplen + authsize, &roff); + if (mi == NULL) { + DPRINTF(("ah_output: failed to inject %u byte AH header for SA " + "%s/%08lx\n", + rplen + authsize, + ipsec_address(&sav->sah->saidx.dst), + (u_long) ntohl(sav->spi))); + 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(("ah_output: replay counter wrapped for SA " + "%s/%08lx\n", + ipsec_address(&sav->sah->saidx.dst), + (u_long) ntohl(sav->spi))); + ahstat.ahs_wrap++; + error = EINVAL; + goto bad; + } + sav->replay->count++; + ah->ah_seq = htonl(sav->replay->count); + } + + /* Get crypto descriptors. */ + crp = crypto_getreq(1); + if (crp == NULL) { + DPRINTF(("ah_output: failed to acquire crypto descriptors\n")); + 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 = _KEYBUF(sav->key_auth); + 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(("ah_output: failed to allocate tdb_crypto\n")); + 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; + 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 s, err; + + tc = (struct tdb_crypto *) crp->crp_opaque; + KASSERT(tc != NULL, ("ah_output_cb: null opaque data area!")); + skip = tc->tc_skip; + protoff = tc->tc_protoff; + ptr = (caddr_t) (tc + 1); + m = (struct mbuf *) crp->crp_buf; + + s = splnet(); + + isr = tc->tc_isr; + sav = KEY_ALLOCSA(&tc->tc_dst, tc->tc_proto, tc->tc_spi); + if (sav == NULL) { + ahstat.ahs_notdb++; + DPRINTF(("ah_output_cb: SA expired while in crypto\n")); + error = ENOBUFS; /*XXX*/ + goto bad; + } + KASSERT(isr->sav == sav, ("ah_output_cb: 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); + splx(s); + return crypto_dispatch(crp); + } + + ahstat.ahs_noxform++; + DPRINTF(("ah_output_cb: crypto error %d\n", crp->crp_etype)); + error = crp->crp_etype; + goto bad; + } + + /* Shouldn't happen... */ + if (m == NULL) { + ahstat.ahs_crypto++; + DPRINTF(("ah_output_cb: bogus returned buffer from crypto\n")); + error = EINVAL; + goto bad; + } + 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); + + /* NB: m is reclaimed by ipsec_process_done. */ + err = ipsec_process_done(m, isr); + KEY_FREESAV(&sav); + splx(s); + return err; +bad: + if (sav) + KEY_FREESAV(&sav); + splx(s); + 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/sys/netipsec/xform_esp.c b/sys/netipsec/xform_esp.c new file mode 100644 index 0000000..f91d3cf --- /dev/null +++ b/sys/netipsec/xform_esp.c @@ -0,0 +1,966 @@ +/* $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 "opt_inet.h" +#include "opt_inet6.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/mbuf.h> +#include <sys/socket.h> +#include <sys/syslog.h> +#include <sys/kernel.h> +#include <sys/random.h> +#include <sys/sysctl.h> + +#include <net/if.h> + +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <netinet/ip_ecn.h> +#include <netinet/ip6.h> + +#include <net/route.h> +#include <netipsec/ipsec.h> +#include <netipsec/ah.h> +#include <netipsec/ah_var.h> +#include <netipsec/esp.h> +#include <netipsec/esp_var.h> +#include <netipsec/xform.h> + +#ifdef INET6 +#include <netinet6/ip6_var.h> +#include <netipsec/ipsec6.h> +#include <netinet6/ip6_ecn.h> +#endif + +#include <netipsec/key.h> +#include <netipsec/key_debug.h> + +#include <opencrypto/cryptodev.h> +#include <opencrypto/xform.h> + +int esp_enable = 1; +struct espstat espstat; + +SYSCTL_DECL(_net_inet_esp); +SYSCTL_INT(_net_inet_esp, OID_AUTO, + esp_enable, CTLFLAG_RW, &esp_enable, 0, ""); +SYSCTL_STRUCT(_net_inet_esp, IPSECCTL_STATS, + stats, CTLFLAG_RD, &espstat, espstat, ""); + +static int esp_max_ivlen; /* max iv length over all algorithms */ + +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; + } + return NULL; +} + +size_t +esp_hdrsiz(struct secasvar *sav) +{ + size_t size; + + if (sav != NULL) { + /*XXX not right for null algorithm--does it matter??*/ + KASSERT(sav->tdb_encalgxform != NULL, + ("esp_hdrsiz: 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) + 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(("esp_init: unsupported encryption algorithm %d\n", + sav->alg_enc)); + return EINVAL; + } + if (sav->key_enc == NULL) { + DPRINTF(("esp_init: no encoding key for %s algorithm\n", + txform->name)); + return EINVAL; + } + if ((sav->flags&(SADB_X_EXT_OLD|SADB_X_EXT_IV4B)) == SADB_X_EXT_IV4B) { + DPRINTF(("esp_init: 4-byte IV not supported with protocol\n")); + return EINVAL; + } + keylen = _KEYLEN(sav->key_enc); + if (txform->minkey > keylen || keylen > txform->maxkey) { + DPRINTF(("esp_init: invalid key length %u, must be in " + "the range [%u..%u] for algorithm %s\n", + 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(("esp_init: no memory for IV\n")); + 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 = _KEYBUF(sav->key_enc); + /* XXX Rounds ? */ + + if (sav->tdb_authalgxform && sav->tdb_encalgxform) { + /* init both auth & enc */ + crie.cri_next = &cria; + error = crypto_newsession(&sav->tdb_cryptoid, + &crie, crypto_support); + } else if (sav->tdb_encalgxform) { + error = crypto_newsession(&sav->tdb_cryptoid, + &crie, crypto_support); + } else if (sav->tdb_authalgxform) { + error = crypto_newsession(&sav->tdb_cryptoid, + &cria, crypto_support); + } else { + /* XXX cannot happen? */ + DPRINTF(("esp_init: no encoding OR authentication xform!\n")); + 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(_KEYBUF(sav->key_enc), _KEYLEN(sav->key_enc)); + /* NB: sav->iv is freed elsewhere, even though we malloc it! */ + 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; + +#if 0 + SPLASSERT(net, "esp_input"); +#endif + + KASSERT(sav != NULL, ("esp_input: null SA")); + KASSERT(sav->tdb_encalgxform != NULL, + ("esp_input: null encoding xform")); + KASSERT((skip&3) == 0 && (m->m_pkthdr.len&3) == 0, + ("esp_input: 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(("esp_input: " + "payload of %d octets not a multiple of %d octets," + " SA %s/%08lx\n", + plen, espx->blocksize, + ipsec_address(&sav->sah->saidx.dst), + (u_long) ntohl(sav->spi))); + espstat.esps_badilen++; + m_freem(m); + return EINVAL; + } + + /* + * Check sequence number. + */ + if (esph && sav->replay && !ipsec_chkreplay(ntohl(esp->esp_seq), sav)) { + DPRINTF(("esp_input: packet replay check for %s\n", + ipsec_logsastr(sav))); /*XXX*/ + espstat.esps_replay++; + m_freem(m); + return ENOBUFS; /*XXX*/ + } + + /* Update the counters */ + 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(("esp_input: failed to acquire crypto descriptors\n")); + 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(("esp_input: failed to allocate tdb_crypto\n")); + espstat.esps_crypto++; + m_freem(m); + return ENOBUFS; + } + + tc->tc_ptr = (caddr_t) mtag; + + if (esph) { + struct cryptodesc *crda = crp->crp_desc; + + KASSERT(crda != NULL, ("esp_input: 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 = _KEYBUF(sav->key_auth); + 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_NODELAY; + 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) { + KASSERT(crde != NULL, ("esp_input: 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 = _KEYBUF(sav->key_enc); + 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 s, 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; + KASSERT(crd != NULL, ("esp_input_cb: null crypto descriptor!")); + + tc = (struct tdb_crypto *) crp->crp_opaque; + KASSERT(tc != NULL, ("esp_input_cb: 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; + + s = splnet(); + + sav = KEY_ALLOCSA(&tc->tc_dst, tc->tc_proto, tc->tc_spi); + if (sav == NULL) { + espstat.esps_notdb++; + DPRINTF(("esp_input_cb: SA expired while in crypto " + "(SA %s/%08lx proto %u)\n", ipsec_address(&tc->tc_dst), + (u_long) ntohl(tc->tc_spi), tc->tc_proto)); + error = ENOBUFS; /*XXX*/ + goto bad; + } + + saidx = &sav->sah->saidx; + KASSERT(saidx->dst.sa.sa_family == AF_INET || + saidx->dst.sa.sa_family == AF_INET6, + ("ah_input_cb: 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); + splx(s); + return crypto_dispatch(crp); + } + + espstat.esps_noxform++; + DPRINTF(("esp_input_cb: crypto error %d\n", crp->crp_etype)); + error = crp->crp_etype; + goto bad; + } + + /* Shouldn't happen... */ + if (m == NULL) { + espstat.esps_crypto++; + DPRINTF(("esp_input_cb: bogus returned buffer from crypto\n")); + error = EINVAL; + goto bad; + } + 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. + */ + ahstat.ahs_hist[sav->alg_auth]++; + if (mtag == NULL) { + /* Copy the authenticator from the packet */ + m_copydata(m, m->m_pkthdr.len - esph->authsize, + esph->authsize, aalg); + + ptr = (caddr_t) (tc + 1); + + /* Verify authenticator */ + if (bcmp(ptr, aalg, esph->authsize) != 0) { + DPRINTF(("esp_input_cb: " + "authentication hash mismatch for packet in SA %s/%08lx\n", + ipsec_address(&saidx->dst), + (u_long) ntohl(sav->spi))); + espstat.esps_badauth++; + error = EACCES; + goto bad; + } + } + + /* Remove trailing authenticator */ + m_adj(m, -(esph->authsize)); + } + + /* Release the crypto descriptors */ + free(tc, M_XDATA), tc = NULL; + crypto_freereq(crp), crp = NULL; + + /* + * Packet is now decrypted. + */ + m->m_flags |= M_DECRYPTED; + + /* 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) { + espstat.esps_hdrops++; + DPRINTF(("esp_input_cb: bad mbuf chain, SA %s/%08lx\n", + 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) { + espstat.esps_badilen++; + DPRINTF(("esp_input_cb: invalid padding length %d " + "for %u byte packet in SA %s/%08lx\n", + 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) { + espstat.esps_badenc++; + DPRINTF(("esp_input_cb: decryption failed " + "for packet in SA %s/%08lx\n", + ipsec_address(&sav->sah->saidx.dst), + (u_long) ntohl(sav->spi))); +DPRINTF(("esp_input_cb: %x %x\n", lastthree[0], lastthree[1])); + 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); + splx(s); + return error; +bad: + if (sav) + KEY_FREESAV(&sav); + splx(s); + 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; + +#if 0 + SPLASSERT(net, "esp_output"); +#endif + + sav = isr->sav; + KASSERT(sav != NULL, ("esp_output: null SA")); + esph = sav->tdb_authalgxform; + espx = sav->tdb_encalgxform; + KASSERT(espx != NULL, ("esp_output: 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; + + 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(("esp_output: unknown/unsupported protocol " + "family %d, SA %s/%08lx\n", + saidx->dst.sa.sa_family, ipsec_address(&saidx->dst), + (u_long) ntohl(sav->spi))); + espstat.esps_nopf++; + error = EPFNOSUPPORT; + goto bad; + } + if (skip + hlen + rlen + padding + alen > maxpacketsize) { + DPRINTF(("esp_output: packet in SA %s/%08lx got too big " + "(len %u, max len %u)\n", + ipsec_address(&saidx->dst), (u_long) ntohl(sav->spi), + skip + hlen + rlen + padding + alen, maxpacketsize)); + espstat.esps_toobig++; + error = EMSGSIZE; + goto bad; + } + + /* Update the counters. */ + espstat.esps_obytes += m->m_pkthdr.len - skip; + + m = m_clone(m); + if (m == NULL) { + DPRINTF(("esp_output: cannot clone mbuf chain, SA %s/%08lx\n", + ipsec_address(&saidx->dst), (u_long) ntohl(sav->spi))); + espstat.esps_hdrops++; + error = ENOBUFS; + goto bad; + } + + /* Inject ESP header. */ + mo = m_makespace(m, skip, hlen, &roff); + if (mo == NULL) { + DPRINTF(("esp_output: failed to inject %u byte ESP hdr for SA " + "%s/%08lx\n", + hlen, ipsec_address(&saidx->dst), + (u_long) ntohl(sav->spi))); + 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 = 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(("esp_output: m_pad failed for SA %s/%08lx\n", + 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(("esp_output: failed to acquire crypto descriptors\n")); + 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 = _KEYBUF(sav->key_enc); + 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(("esp_output: failed to allocate tdb_crypto\n")); + 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; + 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 = _KEYBUF(sav->key_auth); + 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 s, err, error; + + tc = (struct tdb_crypto *) crp->crp_opaque; + KASSERT(tc != NULL, ("esp_output_cb: null opaque data area!")); + m = (struct mbuf *) crp->crp_buf; + + s = splnet(); + + isr = tc->tc_isr; + sav = KEY_ALLOCSA(&tc->tc_dst, tc->tc_proto, tc->tc_spi); + if (sav == NULL) { + espstat.esps_notdb++; + DPRINTF(("esp_output_cb: SA expired while in crypto " + "(SA %s/%08lx proto %u)\n", ipsec_address(&tc->tc_dst), + (u_long) ntohl(tc->tc_spi), tc->tc_proto)); + error = ENOBUFS; /*XXX*/ + goto bad; + } + KASSERT(isr->sav == sav, + ("esp_output_cb: 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); + splx(s); + return crypto_dispatch(crp); + } + + espstat.esps_noxform++; + DPRINTF(("esp_output_cb: crypto error %d\n", crp->crp_etype)); + error = crp->crp_etype; + goto bad; + } + + /* Shouldn't happen... */ + if (m == NULL) { + espstat.esps_crypto++; + DPRINTF(("esp_output_cb: bogus returned buffer from crypto\n")); + error = EINVAL; + goto bad; + } + espstat.esps_hist[sav->alg_enc]++; + if (sav->tdb_authalgxform != NULL) + ahstat.ahs_hist[sav->alg_auth]++; + + /* Release crypto descriptors. */ + free(tc, M_XDATA); + crypto_freereq(crp); + + /* NB: m is reclaimed by ipsec_process_done. */ + err = ipsec_process_done(m, isr); + KEY_FREESAV(&sav); + splx(s); + return err; +bad: + if (sav) + KEY_FREESAV(&sav); + splx(s); + 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 > esp_max_ivlen) \ + esp_max_ivlen = xform.blocksize \ + + esp_max_ivlen = 0; + 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 */ + + xform_register(&esp_xformsw); +#undef MAXIV +} +SYSINIT(esp_xform_init, SI_SUB_DRIVERS, SI_ORDER_FIRST, esp_attach, NULL) diff --git a/sys/netipsec/xform_ipcomp.c b/sys/netipsec/xform_ipcomp.c new file mode 100644 index 0000000..108d356 --- /dev/null +++ b/sys/netipsec/xform_ipcomp.c @@ -0,0 +1,608 @@ +/* $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 "opt_inet.h" +#include "opt_inet6.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/mbuf.h> +#include <sys/socket.h> +#include <sys/kernel.h> +#include <sys/protosw.h> +#include <sys/sysctl.h> + +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <netinet/ip_var.h> + +#include <net/route.h> +#include <netipsec/ipsec.h> +#include <netipsec/xform.h> + +#ifdef INET6 +#include <netinet/ip6.h> +#include <netipsec/ipsec6.h> +#endif + +#include <netipsec/ipcomp.h> +#include <netipsec/ipcomp_var.h> + +#include <netipsec/key.h> +#include <netipsec/key_debug.h> + +#include <opencrypto/cryptodev.h> +#include <opencrypto/deflate.h> +#include <opencrypto/xform.h> + +int ipcomp_enable = 0; +struct ipcompstat ipcompstat; + +SYSCTL_DECL(_net_inet_ipcomp); +SYSCTL_INT(_net_inet_ipcomp, OID_AUTO, + ipcomp_enable, CTLFLAG_RW, &ipcomp_enable, 0, ""); +SYSCTL_STRUCT(_net_inet_ipcomp, IPSECCTL_STATS, + stats, CTLFLAG_RD, &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(("ipcomp_init: unsupported compression algorithm %d\n", + 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, 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; + +#if 0 + SPLASSERT(net, "ipcomp_input"); +#endif + + /* Get crypto descriptors */ + crp = crypto_getreq(1); + if (crp == NULL) { + m_freem(m); + DPRINTF(("ipcomp_input: no crypto descriptors\n")); + 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(("ipcomp_input: cannot allocate tdb_crypto\n")); + 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_NODELAY; + 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 s, hlen = IPCOMP_HLENGTH, error, clen; + u_int8_t nproto; + caddr_t addr; + + crd = crp->crp_desc; + + tc = (struct tdb_crypto *) crp->crp_opaque; + KASSERT(tc != NULL, ("ipcomp_input_cb: 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; + + s = splnet(); + + sav = KEY_ALLOCSA(&tc->tc_dst, tc->tc_proto, tc->tc_spi); + if (sav == NULL) { + ipcompstat.ipcomps_notdb++; + DPRINTF(("ipcomp_input_cb: SA expired while in crypto\n")); + error = ENOBUFS; /*XXX*/ + goto bad; + } + + saidx = &sav->sah->saidx; + KASSERT(saidx->dst.sa.sa_family == AF_INET || + saidx->dst.sa.sa_family == AF_INET6, + ("ah_input_cb: 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); + splx(s); + return crypto_dispatch(crp); + } + + ipcompstat.ipcomps_noxform++; + DPRINTF(("ipcomp_input_cb: crypto error %d\n", crp->crp_etype)); + error = crp->crp_etype; + goto bad; + } + /* Shouldn't happen... */ + if (m == NULL) { + ipcompstat.ipcomps_crypto++; + DPRINTF(("ipcomp_input_cb: null mbuf returned from crypto\n")); + error = EINVAL; + goto bad; + } + 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) { + ipcompstat.ipcomps_hdrops++; /*XXX*/ + DPRINTF(("ipcomp_input_cb: m_pullup failed\n")); + 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) { + ipcompstat.ipcomps_hdrops++; + DPRINTF(("ipcomp_input_cb: bad mbuf chain, IPCA %s/%08lx\n", + 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); + splx(s); + return error; +bad: + if (sav) + KEY_FREESAV(&sav); + splx(s); + 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, hlen, maxpacketsize, roff; + u_int8_t prot; + struct cryptodesc *crdc; + struct cryptop *crp; + struct tdb_crypto *tc; + struct mbuf *mo; + struct ipcomp *ipcomp; + +#if 0 + SPLASSERT(net, "ipcomp_output"); +#endif + + sav = isr->sav; + KASSERT(sav != NULL, ("ipcomp_output: null SA")); + ipcompx = sav->tdb_compalgxform; + KASSERT(ipcompx != NULL, ("ipcomp_output: null compression xform")); + + ralen = m->m_pkthdr.len - skip; /* Raw payload length before comp. */ + hlen = IPCOMP_HLENGTH; + + 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: + ipcompstat.ipcomps_nopf++; + DPRINTF(("ipcomp_output: unknown/unsupported protocol family %d" + ", IPCA %s/%08lx\n", + sav->sah->saidx.dst.sa.sa_family, + ipsec_address(&sav->sah->saidx.dst), + (u_long) ntohl(sav->spi))); + error = EPFNOSUPPORT; + goto bad; + } + if (skip + hlen + ralen > maxpacketsize) { + ipcompstat.ipcomps_toobig++; + DPRINTF(("ipcomp_output: packet in IPCA %s/%08lx got too big " + "(len %u, max len %u)\n", + ipsec_address(&sav->sah->saidx.dst), + (u_long) ntohl(sav->spi), + skip + hlen + ralen, maxpacketsize)); + error = EMSGSIZE; + goto bad; + } + + /* Update the counters */ + ipcompstat.ipcomps_obytes += m->m_pkthdr.len - skip; + + m = m_clone(m); + if (m == NULL) { + ipcompstat.ipcomps_hdrops++; + DPRINTF(("ipcomp_output: cannot clone mbuf chain, IPCA %s/%08lx\n", + ipsec_address(&sav->sah->saidx.dst), + (u_long) ntohl(sav->spi))); + error = ENOBUFS; + goto bad; + } + + /* Inject IPCOMP header */ + mo = m_makespace(m, skip, hlen, &roff); + if (mo == NULL) { + ipcompstat.ipcomps_wrap++; + DPRINTF(("ipcomp_output: failed to inject IPCOMP header for " + "IPCA %s/%08lx\n", + 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, protoff, sizeof(u_int8_t), (u_char *) &prot); + + /* Ok now, we can pass to the crypto processing */ + + /* Get crypto descriptors */ + crp = crypto_getreq(1); + if (crp == NULL) { + ipcompstat.ipcomps_crypto++; + DPRINTF(("ipcomp_output: failed to acquire crypto descriptor\n")); + error = ENOBUFS; + goto bad; + } + crdc = crp->crp_desc; + + /* Compression descriptor */ + crdc->crd_skip = skip + hlen; + crdc->crd_len = m->m_pkthdr.len - (skip + hlen); + crdc->crd_flags = CRD_F_COMP; + crdc->crd_inject = skip + hlen; + + /* 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) { + ipcompstat.ipcomps_crypto++; + DPRINTF(("ipcomp_output: failed to allocate tdb_crypto\n")); + 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_skip = skip + hlen; + + /* Crypto operation descriptor */ + crp->crp_ilen = m->m_pkthdr.len; /* Total input length */ + crp->crp_flags = CRYPTO_F_IMBUF; + 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 s, error, skip, rlen; + + tc = (struct tdb_crypto *) crp->crp_opaque; + KASSERT(tc != NULL, ("ipcomp_output_cb: null opaque data area!")); + m = (struct mbuf *) crp->crp_buf; + skip = tc->tc_skip; + rlen = crp->crp_ilen - skip; + + s = splnet(); + + isr = tc->tc_isr; + sav = KEY_ALLOCSA(&tc->tc_dst, tc->tc_proto, tc->tc_spi); + if (sav == NULL) { + ipcompstat.ipcomps_notdb++; + DPRINTF(("ipcomp_output_cb: SA expired while in crypto\n")); + error = ENOBUFS; /*XXX*/ + goto bad; + } + KASSERT(isr->sav == sav, ("ipcomp_output_cb: SA changed\n")); + + /* 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); + splx(s); + return crypto_dispatch(crp); + } + ipcompstat.ipcomps_noxform++; + DPRINTF(("ipcomp_output_cb: crypto error %d\n", crp->crp_etype)); + error = crp->crp_etype; + goto bad; + } + /* Shouldn't happen... */ + if (m == NULL) { + ipcompstat.ipcomps_crypto++; + DPRINTF(("ipcomp_output_cb: bogus return buffer from crypto\n")); + error = EINVAL; + goto bad; + } + ipcompstat.ipcomps_hist[sav->alg_comp]++; + + if (rlen > crp->crp_olen) { + /* 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: + ipcompstat.ipcomps_nopf++; + DPRINTF(("ipcomp_output: unknown/unsupported protocol " + "family %d, IPCA %s/%08lx\n", + 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 */ + /* XXX add statistic */ + } + + /* 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); + splx(s); + return error; +bad: + if (sav) + KEY_FREESAV(&sav); + splx(s); + 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_DRIVERS, SI_ORDER_FIRST, ipcomp_attach, NULL) diff --git a/sys/netipsec/xform_ipip.c b/sys/netipsec/xform_ipip.c new file mode 100644 index 0000000..a9c0edb --- /dev/null +++ b/sys/netipsec/xform_ipip.c @@ -0,0 +1,699 @@ +/* $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 "opt_inet.h" +#include "opt_inet6.h" +#include "opt_random_ip_id.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/mbuf.h> +#include <sys/socket.h> +#include <sys/kernel.h> +#include <sys/protosw.h> +#include <sys/sysctl.h> + +#include <net/if.h> +#include <net/route.h> +#include <net/netisr.h> + +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/in_var.h> +#include <netinet/ip.h> +#include <netinet/ip_ecn.h> +#include <netinet/ip_var.h> +#include <netinet/ip_encap.h> +#include <netinet/ipprotosw.h> + +#include <netipsec/ipsec.h> +#include <netipsec/xform.h> + +#include <netipsec/ipip_var.h> + +#ifdef MROUTING +#include <netinet/ip_mroute.h> +#endif + +#ifdef INET6 +#include <netinet/ip6.h> +#include <netipsec/ipsec6.h> +#include <netinet6/ip6_ecn.h> +#include <netinet6/in6_var.h> +#include <netinet6/ip6protosw.h> +#endif + +#include <netipsec/key.h> +#include <netipsec/key_debug.h> + +#include <machine/stdarg.h> + +/* + * We can control the acceptance of IP4 packets by altering the sysctl + * net.inet.ipip.allow value. Zero means drop them, all else is acceptance. + */ +int ipip_allow = 0; +struct ipipstat ipipstat; + +SYSCTL_DECL(_net_inet_ipip); +SYSCTL_INT(_net_inet_ipip, OID_AUTO, + ipip_allow, CTLFLAG_RW, &ipip_allow, 0, ""); +SYSCTL_STRUCT(_net_inet_ipip, IPSECCTL_STATS, + stats, CTLFLAG_RD, &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 (!ipip_allow && ((*m)->m_flags & M_IPSEC) == 0) { + DPRINTF(("ip4_input6: dropped due to policy\n")); + 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, ...) +{ + va_list ap; + int iphlen; + +#if 0 + /* If we do not accept IP-in-IP explicitly, drop. */ + if (!ipip_allow && (m->m_flags & M_IPSEC) == 0) { + DPRINTF(("ip4_input: dropped due to policy\n")); + ipipstat.ipips_pdrops++; + m_freem(m); + return; + } +#endif + va_start(ap, m); + iphlen = va_arg(ap, int); + va_end(ap); + + _ipip_input(m, iphlen, 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) +{ + register struct sockaddr_in *sin; + register struct ifnet *ifp; + register struct ifaddr *ifa; + struct ifqueue *ifq = NULL; + struct ip *ipo; +#ifdef INET6 + register struct sockaddr_in6 *sin6; + struct ip6_hdr *ip6 = NULL; + u_int8_t itos; +#endif + u_int8_t nxt; + int isr; + u_int8_t otos; + u_int8_t v; + int hlen; + + ipipstat.ipips_ipackets++; + + m_copydata(m, 0, 1, &v); + + switch (v >> 4) { +#ifdef INET + case 4: + hlen = sizeof(struct ip); + break; +#endif /* INET */ +#ifdef INET6 + case 6: + hlen = sizeof(struct ip6_hdr); + break; +#endif + default: + ipipstat.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(("ipip_input: m_pullup (1) failed\n")); + 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)) { + 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: + 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(("ipip_input: m_pullup (2) failed\n")); + 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(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(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)) && + ipip_allow != 2) { + for (ifp = ifnet.tqh_first; ifp != 0; + ifp = ifp->if_list.tqe_next) { + for (ifa = ifp->if_addrlist.tqh_first; ifa != 0; + ifa = ifa->ifa_list.tqe_next) { +#ifdef INET + if (ipo) { + if (ifa->ifa_addr->sa_family != + AF_INET) + continue; + + sin = (struct sockaddr_in *) ifa->ifa_addr; + + if (sin->sin_addr.s_addr == + ipo->ip_src.s_addr) { + ipipstat.ipips_spoof++; + m_freem(m); + return; + } + } +#endif /* INET */ + +#ifdef INET6 + if (ip6) { + if (ifa->ifa_addr->sa_family != + AF_INET6) + continue; + + sin6 = (struct sockaddr_in6 *) ifa->ifa_addr; + + if (IN6_ARE_ADDR_EQUAL(&sin6->sin6_addr, &ip6->ip6_src)) { + ipipstat.ipips_spoof++; + m_freem(m); + return; + } + + } +#endif /* INET6 */ + } + } + } + + /* Statistics */ + ipipstat.ipips_ibytes += m->m_pkthdr.len - iphlen; + + /* + * 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: + ifq = &ipintrq; + isr = NETISR_IP; + break; +#endif +#ifdef INET6 + case 6: + ifq = &ip6intrq; + isr = NETISR_IPV6; + break; +#endif + default: + panic("ipip_input: should never reach here"); + } + + if (!IF_HANDOFF(ifq, m, NULL)) { + ipipstat.ipips_qfull++; + + DPRINTF(("ipip_input: packet dropped because of full queue\n")); + } else { + schednetisr(isr); + } +} + +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 */ + +#if 0 + SPLASSERT(net, "ipip_output"); +#endif + + sav = isr->sav; + KASSERT(sav != NULL, ("ipip_output: null SA")); + KASSERT(sav->sah != NULL, ("ipip_output: 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(("ipip_output: unspecified tunnel endpoint " + "address in SA %s/%08lx\n", + ipsec_address(&saidx->dst), + (u_long) ntohl(sav->spi))); + ipipstat.ipips_unspec++; + error = EINVAL; + goto bad; + } + + M_PREPEND(m, sizeof(struct ip), M_DONTWAIT); + if (m == 0) { + DPRINTF(("ipip_output: M_PREPEND failed\n")); + 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 = ip_defttl; + ipo->ip_sum = 0; + ipo->ip_src = saidx->src.sin.sin_addr; + ipo->ip_dst = saidx->dst.sin.sin_addr; + +#ifdef RANDOM_IP_ID + ipo->ip_id = ip_randomid(); +#else + ipo->ip_id = htons(ip_id++); +#endif + + /* 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(("ipip_output: unspecified tunnel endpoint " + "address in SA %s/%08lx\n", + ipsec_address(&saidx->dst), + (u_long) ntohl(sav->spi))); + 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(("ipip_output: M_PREPEND failed\n")); + ipipstat.ipips_hdrops++; + *mp = NULL; + 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 = 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(("ipip_output: unsupported protocol family %u\n", + saidx->dst.sa.sa_family)); + ipipstat.ipips_family++; + error = EAFNOSUPPORT; /* XXX diffs from openbsd */ + goto bad; + } + + 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 + 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 + 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 FAST_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("ipe4_input: should never be called\n"); + 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 ipprotosw ipe4_protosw[] = { +{ SOCK_RAW, &inetdomain, IPPROTO_IPV4, PR_ATOMIC|PR_ADDR|PR_LASTHDR, + (pr_in_input_t*) ip4_input, + 0, 0, rip_ctloutput, + 0, + 0, 0, 0, 0, + &rip_usrreqs +}, +#ifdef INET6 +{ SOCK_RAW, &inetdomain, IPPROTO_IPV6, PR_ATOMIC|PR_ADDR|PR_LASTHDR, + (pr_in_input_t*) ip4_input, + 0, 0, rip_ctloutput, + 0, + 0, 0, 0, 0, + &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, (struct protosw*) &ipe4_protosw[0], NULL); +#ifdef INET6 + (void) encap_attach_func(AF_INET6, -1, + ipe4_encapcheck, (struct protosw*) &ipe4_protosw[1], NULL); +#endif +} +SYSINIT(ipe4_xform_init, SI_SUB_PROTO_DOMAIN, SI_ORDER_MIDDLE, ipe4_attach, NULL); +#endif /* FAST_IPSEC */ |