diff options
author | sam <sam@FreeBSD.org> | 2002-10-16 02:10:08 +0000 |
---|---|---|
committer | sam <sam@FreeBSD.org> | 2002-10-16 02:10:08 +0000 |
commit | f6bdcf8ff2c152663769d4f1bcdb9872cdcb5453 (patch) | |
tree | a72213e1c49c93624cf054580ee4bc74717a7228 /sys/netipsec | |
parent | 92baaff27422749fe3e0b54cb403b84342c91f30 (diff) | |
download | FreeBSD-src-f6bdcf8ff2c152663769d4f1bcdb9872cdcb5453.zip FreeBSD-src-f6bdcf8ff2c152663769d4f1bcdb9872cdcb5453.tar.gz |
"Fast IPsec": this is an experimental IPsec implementation that is derived
from the KAME IPsec implementation, but with heavy borrowing and influence
of openbsd. A key feature of this implementation is that it uses the kernel
crypto framework to do all crypto work so when h/w crypto support is present
IPsec operation is automatically accelerated. Otherwise the protocol
implementations are rather differet while the SADB and policy management
code is very similar to KAME (for the moment).
Note that this implementation is enabled with a FAST_IPSEC option. With this
you get all protocols; i.e. there is no FAST_IPSEC_ESP option.
FAST_IPSEC and IPSEC are mutually exclusive; you cannot build both into a
single system.
This software is well tested with IPv4 but should be considered very
experimental (i.e. do not deploy in production environments). This software
does NOT currently support IPv6. In fact do not configure FAST_IPSEC and
INET6 in the same system.
Obtained from: KAME + openbsd
Supported by: Vernier Networks
Diffstat (limited to 'sys/netipsec')
-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 */ |