/* $OpenBSD: ip_ipsp.c,v 1.152 2003/05/09 14:59:19 deraadt 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. */ #include #include #include #include #include #include #include #ifdef INET #include #include #include #include #endif /* INET */ #ifdef INET6 #ifndef INET #include #endif #include #endif /* INET6 */ #include #include #include #include #ifdef DDB #include void tdb_hashstats(void); #endif #ifdef ENCDEBUG #define DPRINTF(x) if (encdebug) printf x #else #define DPRINTF(x) #endif #ifdef __GNUC__ #define INLINE static __inline #endif int ipsp_kern(int, char **, int); u_int8_t get_sa_require(struct inpcb *); void tdb_rehash(void); void tdb_timeout(void *v); void tdb_firstuse(void *v); void tdb_soft_timeout(void *v); void tdb_soft_firstuse(void *v); extern int ipsec_auth_default_level; extern int ipsec_esp_trans_default_level; extern int ipsec_esp_network_default_level; extern int ipsec_ipcomp_default_level; extern int encdebug; int ipsec_in_use = 0; u_int64_t ipsec_last_added = 0; u_int32_t kernfs_epoch = 0; struct ipsec_policy_head ipsec_policy_head = TAILQ_HEAD_INITIALIZER(ipsec_policy_head); struct ipsec_acquire_head ipsec_acquire_head = TAILQ_HEAD_INITIALIZER(ipsec_acquire_head); /* * This is the proper place to define the various encapsulation transforms. */ struct xformsw xformsw[] = { { XF_IP4, 0, "IPv4 Simple Encapsulation", ipe4_attach, ipe4_init, ipe4_zeroize, (int (*)(struct mbuf *, struct tdb *, int, int))ipe4_input, ipip_output, }, { XF_AH, XFT_AUTH, "IPsec AH", ah_attach, ah_init, ah_zeroize, ah_input, ah_output, }, { XF_ESP, XFT_CONF|XFT_AUTH, "IPsec ESP", esp_attach, esp_init, esp_zeroize, esp_input, esp_output, }, { XF_IPCOMP, XFT_COMP, "IPcomp", ipcomp_attach, ipcomp_init, ipcomp_zeroize, ipcomp_input, ipcomp_output, }, #ifdef TCP_SIGNATURE { XF_TCPSIGNATURE, XFT_AUTH, "TCP MD5 Signature Option, RFC 2385", tcp_signature_tdb_attach, tcp_signature_tdb_init, tcp_signature_tdb_zeroize, tcp_signature_tdb_input, tcp_signature_tdb_output, } #endif /* TCP_SIGNATURE */ }; struct xformsw *xformswNXFORMSW = &xformsw[sizeof(xformsw)/sizeof(xformsw[0])]; unsigned char ipseczeroes[IPSEC_ZEROES_SIZE]; /* zeroes! */ #define TDB_HASHSIZE_INIT 32 static struct tdb **tdbh = NULL; static struct tdb **tdbaddr = NULL; static struct tdb **tdbsrc = NULL; static u_int tdb_hashmask = TDB_HASHSIZE_INIT - 1; static int tdb_count; /* * Our hashing function needs to stir things with a non-zero random multiplier * so we cannot be DoS-attacked via choosing of the data to hash. */ INLINE int tdb_hash(u_int32_t spi, union sockaddr_union *dst, u_int8_t proto) { static u_int32_t mult1 = 0, mult2 = 0; u_int8_t *ptr = (u_int8_t *) dst; int i, shift; u_int64_t hash; int val32 = 0; while (mult1 == 0) mult1 = arc4random(); while (mult2 == 0) mult2 = arc4random(); hash = (spi ^ proto) * mult1; for (i = 0; i < SA_LEN(&dst->sa); i++) { val32 = (val32 << 8) | ptr[i]; if (i % 4 == 3) { hash ^= val32 * mult2; val32 = 0; } } if (i % 4 != 0) hash ^= val32 * mult2; shift = ffs(tdb_hashmask + 1); while ((hash & ~tdb_hashmask) != 0) hash = (hash >> shift) ^ (hash & tdb_hashmask); return hash; } /* * Reserve an SPI; the SA is not valid yet though. We use 0 as * an error return value. */ u_int32_t reserve_spi(u_int32_t sspi, u_int32_t tspi, union sockaddr_union *src, union sockaddr_union *dst, u_int8_t sproto, int *errval) { struct tdb *tdbp; u_int32_t spi; int nums, s; /* Don't accept ranges only encompassing reserved SPIs. */ if (sproto != IPPROTO_IPCOMP && (tspi < sspi || tspi <= SPI_RESERVED_MAX)) { (*errval) = EINVAL; return 0; } if (sproto == IPPROTO_IPCOMP && (tspi < sspi || tspi <= CPI_RESERVED_MAX || tspi >= CPI_PRIVATE_MIN)) { (*errval) = EINVAL; return 0; } /* Limit the range to not include reserved areas. */ if (sspi <= SPI_RESERVED_MAX) sspi = SPI_RESERVED_MAX + 1; /* For IPCOMP the CPI is only 16 bits long, what a good idea.... */ if (sproto == IPPROTO_IPCOMP) { u_int32_t t; if (sspi >= 0x10000) sspi = 0xffff; if (tspi >= 0x10000) tspi = 0xffff; if (sspi > tspi) { t = sspi; sspi = tspi; tspi = t; } } if (sspi == tspi) /* Asking for a specific SPI. */ nums = 1; else nums = 100; /* Arbitrarily chosen */ while (nums--) { if (sspi == tspi) /* Specific SPI asked. */ spi = tspi; else /* Range specified */ spi = sspi + (arc4random() % (tspi - sspi)); /* Don't allocate reserved SPIs. */ if (spi >= SPI_RESERVED_MIN && spi <= SPI_RESERVED_MAX) continue; else spi = htonl(spi); /* Check whether we're using this SPI already. */ s = spltdb(); tdbp = gettdb(spi, dst, sproto); splx(s); if (tdbp != (struct tdb *) NULL) continue; tdbp = tdb_alloc(); tdbp->tdb_spi = spi; bcopy(&dst->sa, &tdbp->tdb_dst.sa, SA_LEN(&dst->sa)); bcopy(&src->sa, &tdbp->tdb_src.sa, SA_LEN(&src->sa)); tdbp->tdb_sproto = sproto; tdbp->tdb_flags |= TDBF_INVALID; /* Mark SA invalid for now. */ tdbp->tdb_satype = SADB_SATYPE_UNSPEC; puttdb(tdbp); /* Setup a "silent" expiration (since TDBF_INVALID's set). */ if (ipsec_keep_invalid > 0) { tdbp->tdb_flags |= TDBF_TIMER; tdbp->tdb_exp_timeout = ipsec_keep_invalid; timeout_add(&tdbp->tdb_timer_tmo, hz * ipsec_keep_invalid); } return spi; } (*errval) = EEXIST; return 0; } /* * An IPSP SAID is really the concatenation of the SPI found in the * packet, the destination address of the packet and the IPsec protocol. * When we receive an IPSP packet, we need to look up its tunnel descriptor * block, based on the SPI in the packet and the destination address (which * is really one of our addresses if we received the packet! * * Caller is responsible for setting at least spltdb(). */ struct tdb * gettdb(u_int32_t spi, union sockaddr_union *dst, u_int8_t proto) { u_int32_t hashval; struct tdb *tdbp; if (tdbh == NULL) return (struct tdb *) NULL; hashval = tdb_hash(spi, dst, proto); for (tdbp = tdbh[hashval]; tdbp != NULL; tdbp = tdbp->tdb_hnext) if ((tdbp->tdb_spi == spi) && !bcmp(&tdbp->tdb_dst, dst, SA_LEN(&dst->sa)) && (tdbp->tdb_sproto == proto)) break; return tdbp; } /* * Check that credentials and IDs match. Return true if so. The t* * range of arguments contains information from TDBs; the p* * range of arguments contains information from policies or * already established TDBs. */ int ipsp_aux_match(struct ipsec_ref *tsrcid, struct ipsec_ref *psrcid, struct ipsec_ref *tdstid, struct ipsec_ref *pdstid, struct ipsec_ref *tlcred, struct ipsec_ref *plcred, struct ipsec_ref *trcred, struct ipsec_ref *prcred, struct sockaddr_encap *tfilter, struct sockaddr_encap *pfilter, struct sockaddr_encap *tfiltermask, struct sockaddr_encap *pfiltermask) { if (psrcid != NULL) if (tsrcid == NULL || !ipsp_ref_match(tsrcid, psrcid)) return 0; if (pdstid != NULL) if (tdstid == NULL || !ipsp_ref_match(tdstid, pdstid)) return 0; if (plcred != NULL) if (tlcred == NULL || !ipsp_ref_match(tlcred, plcred)) return 0; if (prcred != NULL) if (trcred == NULL || !ipsp_ref_match(trcred, prcred)) return 0; /* Check for filter matches. */ if (tfilter->sen_type) { /* * XXX We should really be doing a subnet-check (see * whether the TDB-associated filter is a subset * of the policy's. For now, an exact match will solve * most problems (all this will do is make every * policy get its own SAs). */ if (bcmp(tfilter, pfilter, sizeof(struct sockaddr_encap)) || bcmp(tfiltermask, pfiltermask, sizeof(struct sockaddr_encap))) return 0; } return 1; } /* * Get an SA given the remote address, the security protocol type, and * the desired IDs. */ struct tdb * gettdbbyaddr(union sockaddr_union *dst, u_int8_t sproto, struct ipsec_ref *srcid, struct ipsec_ref *dstid, struct ipsec_ref *local_cred, struct mbuf *m, int af, struct sockaddr_encap *filter, struct sockaddr_encap *filtermask) { u_int32_t hashval; struct tdb *tdbp; if (tdbaddr == NULL) return (struct tdb *) NULL; hashval = tdb_hash(0, dst, sproto); for (tdbp = tdbaddr[hashval]; tdbp != NULL; tdbp = tdbp->tdb_anext) if ((tdbp->tdb_sproto == sproto) && ((tdbp->tdb_flags & TDBF_INVALID) == 0) && (!bcmp(&tdbp->tdb_dst, dst, SA_LEN(&dst->sa)))) { /* Do IDs and local credentials match ? */ if (!ipsp_aux_match(tdbp->tdb_srcid, srcid, tdbp->tdb_dstid, dstid, tdbp->tdb_local_cred, local_cred, NULL, NULL, &tdbp->tdb_filter, filter, &tdbp->tdb_filtermask, filtermask)) continue; break; } return tdbp; } /* * Get an SA given the source address, the security protocol type, and * the desired IDs. */ struct tdb * gettdbbysrc(union sockaddr_union *src, u_int8_t sproto, struct ipsec_ref *srcid, struct ipsec_ref *dstid, struct mbuf *m, int af, struct sockaddr_encap *filter, struct sockaddr_encap *filtermask) { u_int32_t hashval; struct tdb *tdbp; if (tdbsrc == NULL) return (struct tdb *) NULL; hashval = tdb_hash(0, src, sproto); for (tdbp = tdbsrc[hashval]; tdbp != NULL; tdbp = tdbp->tdb_snext) if ((tdbp->tdb_sproto == sproto) && ((tdbp->tdb_flags & TDBF_INVALID) == 0) && (!bcmp(&tdbp->tdb_src, src, SA_LEN(&src->sa)))) { /* Check whether IDs match */ if (!ipsp_aux_match(tdbp->tdb_srcid, dstid, tdbp->tdb_dstid, srcid, NULL, NULL, NULL, NULL, &tdbp->tdb_filter, filter, &tdbp->tdb_filtermask, filtermask)) continue; break; } return tdbp; } #if DDB void tdb_hashstats(void) { int i, cnt, buckets[16]; struct tdb *tdbp; if (tdbh == NULL) { db_printf("no tdb hash table\n"); return; } bzero (buckets, sizeof(buckets)); for (i = 0; i <= tdb_hashmask; i++) { cnt = 0; for (tdbp = tdbh[i]; cnt < 16 && tdbp != NULL; tdbp = tdbp->tdb_hnext) cnt++; buckets[cnt]++; } db_printf("tdb cnt\t\tbucket cnt\n"); for (i = 0; i < 16; i++) if (buckets[i] > 0) db_printf("%d%c\t\t%d\n", i, i == 15 ? "+" : "", buckets[i]); } #endif /* DDB */ /* * Caller is responsible for setting at least spltdb(). */ int tdb_walk(int (*walker)(struct tdb *, void *, int), void *arg) { int i, rval = 0; struct tdb *tdbp, *next; if (tdbh == NULL) return ENOENT; for (i = 0; i <= tdb_hashmask; i++) for (tdbp = tdbh[i]; rval == 0 && tdbp != NULL; tdbp = next) { next = tdbp->tdb_hnext; if (i == tdb_hashmask && next == NULL) rval = walker(tdbp, (void *)arg, 1); else rval = walker(tdbp, (void *)arg, 0); } return rval; } /* * Called at splsoftclock(). */ void tdb_timeout(void *v) { struct tdb *tdb = v; if (!(tdb->tdb_flags & TDBF_TIMER)) return; /* If it's an "invalid" TDB do a silent expiration. */ if (!(tdb->tdb_flags & TDBF_INVALID)) pfkeyv2_expire(tdb, SADB_EXT_LIFETIME_HARD); tdb_delete(tdb); } void tdb_firstuse(void *v) { struct tdb *tdb = v; if (!(tdb->tdb_flags & TDBF_SOFT_FIRSTUSE)) return; /* If the TDB hasn't been used, don't renew it. */ if (tdb->tdb_first_use != 0) pfkeyv2_expire(tdb, SADB_EXT_LIFETIME_HARD); tdb_delete(tdb); } void tdb_soft_timeout(void *v) { struct tdb *tdb = v; if (!(tdb->tdb_flags & TDBF_SOFT_TIMER)) return; /* Soft expirations. */ pfkeyv2_expire(tdb, SADB_EXT_LIFETIME_SOFT); tdb->tdb_flags &= ~TDBF_SOFT_TIMER; } void tdb_soft_firstuse(void *v) { struct tdb *tdb = v; if (!(tdb->tdb_flags & TDBF_SOFT_FIRSTUSE)) return; /* If the TDB hasn't been used, don't renew it. */ if (tdb->tdb_first_use != 0) pfkeyv2_expire(tdb, SADB_EXT_LIFETIME_SOFT); tdb->tdb_flags &= ~TDBF_SOFT_FIRSTUSE; } /* * Caller is responsible for spltdb(). */ void tdb_rehash(void) { struct tdb **new_tdbh, **new_tdbaddr, **new_srcaddr, *tdbp, *tdbnp; u_int i, old_hashmask = tdb_hashmask; u_int32_t hashval; tdb_hashmask = (tdb_hashmask << 1) | 1; MALLOC(new_tdbh, struct tdb **, sizeof(struct tdb *) * (tdb_hashmask + 1), M_TDB, M_WAITOK); MALLOC(new_tdbaddr, struct tdb **, sizeof(struct tdb *) * (tdb_hashmask + 1), M_TDB, M_WAITOK); MALLOC(new_srcaddr, struct tdb **, sizeof(struct tdb *) * (tdb_hashmask + 1), M_TDB, M_WAITOK); bzero(new_tdbh, sizeof(struct tdb *) * (tdb_hashmask + 1)); bzero(new_tdbaddr, sizeof(struct tdb *) * (tdb_hashmask + 1)); bzero(new_srcaddr, sizeof(struct tdb *) * (tdb_hashmask + 1)); for (i = 0; i <= old_hashmask; i++) { for (tdbp = tdbh[i]; tdbp != NULL; tdbp = tdbnp) { tdbnp = tdbp->tdb_hnext; hashval = tdb_hash(tdbp->tdb_spi, &tdbp->tdb_dst, tdbp->tdb_sproto); tdbp->tdb_hnext = new_tdbh[hashval]; new_tdbh[hashval] = tdbp; } for (tdbp = tdbaddr[i]; tdbp != NULL; tdbp = tdbnp) { tdbnp = tdbp->tdb_anext; hashval = tdb_hash(0, &tdbp->tdb_dst, tdbp->tdb_sproto); tdbp->tdb_anext = new_tdbaddr[hashval]; new_tdbaddr[hashval] = tdbp; } for (tdbp = tdbsrc[i]; tdbp != NULL; tdbp = tdbnp) { tdbnp = tdbp->tdb_snext; hashval = tdb_hash(0, &tdbp->tdb_src, tdbp->tdb_sproto); tdbp->tdb_snext = new_srcaddr[hashval]; new_srcaddr[hashval] = tdbp; } } FREE(tdbh, M_TDB); tdbh = new_tdbh; FREE(tdbaddr, M_TDB); tdbaddr = new_tdbaddr; FREE(tdbsrc, M_TDB); tdbsrc = new_srcaddr; } /* * Add TDB in the hash table. */ void puttdb(struct tdb *tdbp) { u_int32_t hashval; int s = spltdb(); if (tdbh == NULL) { MALLOC(tdbh, struct tdb **, sizeof(struct tdb *) * (tdb_hashmask + 1), M_TDB, M_WAITOK); MALLOC(tdbaddr, struct tdb **, sizeof(struct tdb *) * (tdb_hashmask + 1), M_TDB, M_WAITOK); MALLOC(tdbsrc, struct tdb **, sizeof(struct tdb *) * (tdb_hashmask + 1), M_TDB, M_WAITOK); bzero(tdbh, sizeof(struct tdb *) * (tdb_hashmask + 1)); bzero(tdbaddr, sizeof(struct tdb *) * (tdb_hashmask + 1)); bzero(tdbsrc, sizeof(struct tdb *) * (tdb_hashmask + 1)); } hashval = tdb_hash(tdbp->tdb_spi, &tdbp->tdb_dst, tdbp->tdb_sproto); /* * Rehash if this tdb would cause a bucket to have more than * two items and if the number of tdbs exceed 10% of the * bucket count. This number is arbitratily chosen and is * just a measure to not keep rehashing when adding and * removing tdbs which happens to always end up in the same * bucket, which is not uncommon when doing manual keying. */ if (tdbh[hashval] != NULL && tdbh[hashval]->tdb_hnext != NULL && tdb_count * 10 > tdb_hashmask + 1) { tdb_rehash(); hashval = tdb_hash(tdbp->tdb_spi, &tdbp->tdb_dst, tdbp->tdb_sproto); } tdbp->tdb_hnext = tdbh[hashval]; tdbh[hashval] = tdbp; hashval = tdb_hash(0, &tdbp->tdb_dst, tdbp->tdb_sproto); tdbp->tdb_anext = tdbaddr[hashval]; tdbaddr[hashval] = tdbp; hashval = tdb_hash(0, &tdbp->tdb_src, tdbp->tdb_sproto); tdbp->tdb_snext = tdbsrc[hashval]; tdbsrc[hashval] = tdbp; tdb_count++; ipsec_last_added = time.tv_sec; splx(s); } /* * Caller is responsible to set at least spltdb(). */ void tdb_delete(struct tdb *tdbp) { struct ipsec_policy *ipo; struct tdb *tdbpp; struct inpcb *inp; u_int32_t hashval; int s; if (tdbh == NULL) return; hashval = tdb_hash(tdbp->tdb_spi, &tdbp->tdb_dst, tdbp->tdb_sproto); s = spltdb(); if (tdbh[hashval] == tdbp) { tdbpp = tdbp; tdbh[hashval] = tdbp->tdb_hnext; } else { for (tdbpp = tdbh[hashval]; tdbpp != NULL; tdbpp = tdbpp->tdb_hnext) { if (tdbpp->tdb_hnext == tdbp) { tdbpp->tdb_hnext = tdbp->tdb_hnext; tdbpp = tdbp; break; } } } tdbp->tdb_hnext = NULL; hashval = tdb_hash(0, &tdbp->tdb_dst, tdbp->tdb_sproto); if (tdbaddr[hashval] == tdbp) { tdbpp = tdbp; tdbaddr[hashval] = tdbp->tdb_anext; } else { for (tdbpp = tdbaddr[hashval]; tdbpp != NULL; tdbpp = tdbpp->tdb_anext) { if (tdbpp->tdb_anext == tdbp) { tdbpp->tdb_anext = tdbp->tdb_anext; tdbpp = tdbp; break; } } } hashval = tdb_hash(0, &tdbp->tdb_src, tdbp->tdb_sproto); if (tdbsrc[hashval] == tdbp) { tdbpp = tdbp; tdbsrc[hashval] = tdbp->tdb_snext; } else { for (tdbpp = tdbsrc[hashval]; tdbpp != NULL; tdbpp = tdbpp->tdb_snext) { if (tdbpp->tdb_snext == tdbp) { tdbpp->tdb_snext = tdbp->tdb_snext; tdbpp = tdbp; break; } } } tdbp->tdb_snext = NULL; if (tdbp->tdb_xform) { (*(tdbp->tdb_xform->xf_zeroize))(tdbp); tdbp->tdb_xform = NULL; } /* Cleanup inp references. */ for (inp = TAILQ_FIRST(&tdbp->tdb_inp_in); inp; inp = TAILQ_FIRST(&tdbp->tdb_inp_in)) { TAILQ_REMOVE(&tdbp->tdb_inp_in, inp, inp_tdb_in_next); inp->inp_tdb_in = NULL; } for (inp = TAILQ_FIRST(&tdbp->tdb_inp_out); inp; inp = TAILQ_FIRST(&tdbp->tdb_inp_out)) { TAILQ_REMOVE(&tdbp->tdb_inp_out, inp, inp_tdb_out_next); inp->inp_tdb_out = NULL; } /* Cleanup SPD references. */ for (ipo = TAILQ_FIRST(&tdbp->tdb_policy_head); ipo; ipo = TAILQ_FIRST(&tdbp->tdb_policy_head)) { TAILQ_REMOVE(&tdbp->tdb_policy_head, ipo, ipo_tdb_next); ipo->ipo_tdb = NULL; ipo->ipo_last_searched = 0; /* Force a re-search. */ } /* Remove expiration timeouts. */ tdbp->tdb_flags &= ~(TDBF_FIRSTUSE | TDBF_SOFT_FIRSTUSE | TDBF_TIMER | TDBF_SOFT_TIMER); timeout_del(&tdbp->tdb_timer_tmo); timeout_del(&tdbp->tdb_first_tmo); timeout_del(&tdbp->tdb_stimer_tmo); timeout_del(&tdbp->tdb_sfirst_tmo); if (tdbp->tdb_local_auth) { ipsp_reffree(tdbp->tdb_local_auth); tdbp->tdb_local_auth = NULL; } if (tdbp->tdb_remote_auth) { ipsp_reffree(tdbp->tdb_remote_auth); tdbp->tdb_remote_auth = NULL; } if (tdbp->tdb_srcid) { ipsp_reffree(tdbp->tdb_srcid); tdbp->tdb_srcid = NULL; } if (tdbp->tdb_dstid) { ipsp_reffree(tdbp->tdb_dstid); tdbp->tdb_dstid = NULL; } if (tdbp->tdb_local_cred) { ipsp_reffree(tdbp->tdb_local_cred); tdbp->tdb_local_cred = NULL; } if (tdbp->tdb_remote_cred) { ipsp_reffree(tdbp->tdb_remote_cred); tdbp->tdb_local_cred = NULL; } if ((tdbp->tdb_onext) && (tdbp->tdb_onext->tdb_inext == tdbp)) tdbp->tdb_onext->tdb_inext = NULL; if ((tdbp->tdb_inext) && (tdbp->tdb_inext->tdb_onext == tdbp)) tdbp->tdb_inext->tdb_onext = NULL; FREE(tdbp, M_TDB); tdb_count--; splx(s); } /* * Allocate a TDB and initialize a few basic fields. */ struct tdb * tdb_alloc(void) { struct tdb *tdbp; MALLOC(tdbp, struct tdb *, sizeof(struct tdb), M_TDB, M_WAITOK); bzero((caddr_t) tdbp, sizeof(struct tdb)); /* Init Incoming SA-Binding Queues. */ TAILQ_INIT(&tdbp->tdb_inp_out); TAILQ_INIT(&tdbp->tdb_inp_in); TAILQ_INIT(&tdbp->tdb_policy_head); /* Record establishment time. */ tdbp->tdb_established = time.tv_sec; tdbp->tdb_epoch = kernfs_epoch - 1; /* Initialize timeouts. */ timeout_set(&tdbp->tdb_timer_tmo, tdb_timeout, tdbp); timeout_set(&tdbp->tdb_first_tmo, tdb_firstuse, tdbp); timeout_set(&tdbp->tdb_stimer_tmo, tdb_soft_timeout, tdbp); timeout_set(&tdbp->tdb_sfirst_tmo, tdb_soft_firstuse, tdbp); return tdbp; } /* * Do further initializations of a TDB. */ int tdb_init(struct tdb *tdbp, u_int16_t alg, struct ipsecinit *ii) { struct xformsw *xsp; int err; for (xsp = xformsw; xsp < xformswNXFORMSW; xsp++) { if (xsp->xf_type == alg) { err = (*(xsp->xf_init))(tdbp, xsp, ii); return err; } } DPRINTF(("tdb_init(): no alg %d for spi %08x, addr %s, proto %d\n", alg, ntohl(tdbp->tdb_spi), ipsp_address(tdbp->tdb_dst), tdbp->tdb_sproto)); return EINVAL; } #ifdef KERNFS /* * Print TDB information on a buffer. */ int ipsp_print_tdb(struct tdb *tdb, char *buffer, size_t buflen) { struct ctlname ipspflags[] = { { "unique", TDBF_UNIQUE }, { "invalid", TDBF_INVALID }, { "halfiv", TDBF_HALFIV }, { "pfs", TDBF_PFS }, { "tunneling", TDBF_TUNNELING }, { "noreplay", TDBF_NOREPLAY }, { "random padding", TDBF_RANDOMPADDING }, { "skipcrypto", TDBF_SKIPCRYPTO }, { "usedtunnel", TDBF_USEDTUNNEL }, }; int l, i, k; snprintf(buffer, buflen, "SPI = %08x, Destination = %s, Sproto = %u\n" "\tEstablished %d seconds ago\n" "\tSource = %s", ntohl(tdb->tdb_spi), ipsp_address(tdb->tdb_dst), tdb->tdb_sproto, time.tv_sec - tdb->tdb_established, ipsp_address(tdb->tdb_src)); l = strlen(buffer); if (tdb->tdb_proxy.sa.sa_family) { snprintf(buffer + l, buflen - l, ", Proxy = %s", ipsp_address(tdb->tdb_proxy)); l += strlen(buffer + l); } snprintf(buffer + l, buflen - l, "\n"); l += strlen(buffer + l); if (tdb->tdb_mtu && tdb->tdb_mtutimeout > time.tv_sec) { snprintf(buffer + l, buflen - l, "\tMTU: %d, expires in %llu seconds\n", tdb->tdb_mtu, tdb->tdb_mtutimeout - time.tv_sec); l += strlen(buffer + l); } if (tdb->tdb_local_cred) { snprintf(buffer + l, buflen - l, "\tLocal credential type %d\n", ((struct ipsec_ref *) tdb->tdb_local_cred)->ref_type); l += strlen(buffer + l); } if (tdb->tdb_remote_cred) { snprintf(buffer + l, buflen - l, "\tRemote credential type %d\n", ((struct ipsec_ref *) tdb->tdb_remote_cred)->ref_type); l += strlen(buffer + l); } if (tdb->tdb_local_auth) { snprintf(buffer + l, buflen - l, "\tLocal auth type %d\n", ((struct ipsec_ref *) tdb->tdb_local_auth)->ref_type); l += strlen(buffer + l); } if (tdb->tdb_remote_auth) { snprintf(buffer + l, buflen - l, "\tRemote auth type %d\n", ((struct ipsec_ref *) tdb->tdb_remote_auth)->ref_type); l += strlen(buffer + l); } snprintf(buffer + l, buflen - l, "\tFlags (%08x) = <", tdb->tdb_flags); l += strlen(buffer + l); if ((tdb->tdb_flags & ~(TDBF_TIMER | TDBF_BYTES | TDBF_ALLOCATIONS | TDBF_FIRSTUSE | TDBF_SOFT_TIMER | TDBF_SOFT_BYTES | TDBF_SOFT_FIRSTUSE | TDBF_SOFT_ALLOCATIONS)) == 0) { snprintf(buffer + l, buflen - l, "none>\n"); l += strlen(buffer + l); } else { for (k = 0, i = 0; k < sizeof(ipspflags) / sizeof(struct ctlname); k++) { if (tdb->tdb_flags & ipspflags[k].ctl_type) { snprintf(buffer + l, buflen - l, "%s,", ipspflags[k].ctl_name); l += strlen(buffer + l); i = 1; } } /* If we added flags, remove trailing comma. */ if (i) l--; snprintf(buffer + l, buflen - l, ">\n"); l += strlen(buffer + l); } snprintf(buffer + l, buflen - l, "\tCrypto ID: %llu\n", tdb->tdb_cryptoid); l += strlen(buffer + l); if (tdb->tdb_xform) { snprintf(buffer + l, buflen - l, "\txform = <%s>\n", tdb->tdb_xform->xf_name); l += strlen(buffer + l); } if (tdb->tdb_encalgxform) { snprintf(buffer + l, buflen - l, "\t\tEncryption = <%s>\n", tdb->tdb_encalgxform->name); l += strlen(buffer + l); } if (tdb->tdb_authalgxform) { snprintf(buffer + l, buflen - l, "\t\tAuthentication = <%s>\n", tdb->tdb_authalgxform->name); l += strlen(buffer + l); } if (tdb->tdb_compalgxform) { snprintf(buffer + l, buflen - l, "\t\tCompression = <%s>\n", tdb->tdb_compalgxform->name); l += strlen(buffer + l); } if (tdb->tdb_onext) { snprintf(buffer + l, buflen - l, "\tNext SA: SPI = %08x, Destination = %s, Sproto = %u\n", ntohl(tdb->tdb_onext->tdb_spi), ipsp_address(tdb->tdb_onext->tdb_dst), tdb->tdb_onext->tdb_sproto); l += strlen(buffer + l); } if (tdb->tdb_inext) { snprintf(buffer + l, buflen - l, "\tPrevious SA: SPI = %08x, " "Destination = %s, Sproto = %u\n", ntohl(tdb->tdb_inext->tdb_spi), ipsp_address(tdb->tdb_inext->tdb_dst), tdb->tdb_inext->tdb_sproto); l += strlen(buffer + l); } snprintf(buffer + l, buflen - l, "\t%llu bytes processed by this SA\n", tdb->tdb_cur_bytes); l += strlen(buffer + l); if (tdb->tdb_last_used) { snprintf(buffer + l, buflen - l, "\tLast used %llu seconds ago\n", time.tv_sec - tdb->tdb_last_used); l += strlen(buffer + l); } if (tdb->tdb_last_marked) { snprintf(buffer + l, buflen - l, "\tLast marked/unmarked %llu seconds ago\n", time.tv_sec - tdb->tdb_last_marked); l += strlen(buffer + l); } snprintf(buffer + l, buflen - l, "\tExpirations:\n"); l += strlen(buffer + l); if (tdb->tdb_flags & TDBF_TIMER) { snprintf(buffer + l, buflen -l, "\t\tHard expiration(1) in %llu seconds\n", tdb->tdb_established + tdb->tdb_exp_timeout - time.tv_sec); l += strlen(buffer + l); } if (tdb->tdb_flags & TDBF_SOFT_TIMER) { snprintf(buffer + l, buflen -l, "\t\tSoft expiration(1) in %llu seconds\n", tdb->tdb_established + tdb->tdb_soft_timeout - time.tv_sec); l += strlen(buffer + l); } if (tdb->tdb_flags & TDBF_BYTES) { snprintf(buffer + l, buflen -l, "\t\tHard expiration after %llu bytes\n", tdb->tdb_exp_bytes); l += strlen(buffer + l); } if (tdb->tdb_flags & TDBF_SOFT_BYTES) { snprintf(buffer + l, buflen -l, "\t\tSoft expiration after %llu bytes\n", tdb->tdb_soft_bytes); l += strlen(buffer + l); } if (tdb->tdb_flags & TDBF_ALLOCATIONS) { snprintf(buffer + l, buflen -l, "\t\tHard expiration after %u flows\n", tdb->tdb_exp_allocations); l += strlen(buffer + l); } if (tdb->tdb_flags & TDBF_SOFT_ALLOCATIONS) { snprintf(buffer + l, buflen -l, "\t\tSoft expiration after %u flows\n", tdb->tdb_soft_allocations); l += strlen(buffer + l); } if (tdb->tdb_flags & TDBF_FIRSTUSE) { if (tdb->tdb_first_use) { snprintf(buffer + l, buflen -l, "\t\tHard expiration(2) in %llu seconds\n", (tdb->tdb_first_use + tdb->tdb_exp_first_use) - time.tv_sec); l += strlen(buffer + l); } else { snprintf(buffer + l, buflen -l, "\t\tHard expiration in %llu seconds " "after first use\n", tdb->tdb_exp_first_use); l += strlen(buffer + l); } } if (tdb->tdb_flags & TDBF_SOFT_FIRSTUSE) { if (tdb->tdb_first_use) { snprintf(buffer + l, buflen -l, "\t\tSoft expiration(2) in %llu seconds\n", (tdb->tdb_first_use + tdb->tdb_soft_first_use) - time.tv_sec); l += strlen(buffer + l); } else { snprintf(buffer + l, buflen -l, "\t\tSoft expiration in %llu seconds " "after first use\n", tdb->tdb_soft_first_use); l += strlen(buffer + l); } } if (!(tdb->tdb_flags & (TDBF_TIMER | TDBF_SOFT_TIMER | TDBF_BYTES | TDBF_SOFT_ALLOCATIONS | TDBF_ALLOCATIONS | TDBF_SOFT_BYTES | TDBF_FIRSTUSE | TDBF_SOFT_FIRSTUSE))) { snprintf(buffer + l, buflen -l, "\t\t(none)\n"); l += strlen(buffer + l); } snprintf(buffer + l, buflen -l, "\n"); l += strlen(buffer + l); return l; } /* * Used by kernfs. */ int ipsp_kern(int off, char **bufp, int len) { static char buffer[IPSEC_KERNFS_BUFSIZE]; struct tdb *tdb; int i, s, l; if (bufp == NULL) return 0; bzero(buffer, IPSEC_KERNFS_BUFSIZE); *bufp = buffer; if (off == 0) { kernfs_epoch++; l = snprintf(buffer, sizeof buffer, "Hashmask: %d, policy entries: %d\n", tdb_hashmask, ipsec_in_use); return l; } if (tdbh == NULL) return 0; for (i = 0; i <= tdb_hashmask; i++) { s = spltdb(); for (tdb = tdbh[i]; tdb; tdb = tdb->tdb_hnext) { if (tdb->tdb_epoch != kernfs_epoch) { tdb->tdb_epoch = kernfs_epoch; l = ipsp_print_tdb(tdb, buffer, sizeof buffer); splx(s); return l; } } splx(s); } return 0; } #endif /* KERNFS */ /* * Check which transformations are required. */ u_int8_t get_sa_require(struct inpcb *inp) { u_int8_t sareq = 0; if (inp != NULL) { sareq |= inp->inp_seclevel[SL_AUTH] >= IPSEC_LEVEL_USE ? NOTIFY_SATYPE_AUTH : 0; sareq |= inp->inp_seclevel[SL_ESP_TRANS] >= IPSEC_LEVEL_USE ? NOTIFY_SATYPE_CONF : 0; sareq |= inp->inp_seclevel[SL_ESP_NETWORK] >= IPSEC_LEVEL_USE ? NOTIFY_SATYPE_TUNNEL : 0; } else { sareq |= ipsec_auth_default_level >= IPSEC_LEVEL_USE ? NOTIFY_SATYPE_AUTH : 0; sareq |= ipsec_esp_trans_default_level >= IPSEC_LEVEL_USE ? NOTIFY_SATYPE_CONF : 0; sareq |= ipsec_esp_network_default_level >= IPSEC_LEVEL_USE ? NOTIFY_SATYPE_TUNNEL : 0; } return (sareq); } /* * Add an inpcb to the list of inpcb which reference this tdb directly. */ void tdb_add_inp(struct tdb *tdb, struct inpcb *inp, int inout) { if (inout) { if (inp->inp_tdb_in) { if (inp->inp_tdb_in == tdb) return; TAILQ_REMOVE(&inp->inp_tdb_in->tdb_inp_in, inp, inp_tdb_in_next); } inp->inp_tdb_in = tdb; TAILQ_INSERT_TAIL(&tdb->tdb_inp_in, inp, inp_tdb_in_next); } else { if (inp->inp_tdb_out) { if (inp->inp_tdb_out == tdb) return; TAILQ_REMOVE(&inp->inp_tdb_out->tdb_inp_out, inp, inp_tdb_out_next); } inp->inp_tdb_out = tdb; TAILQ_INSERT_TAIL(&tdb->tdb_inp_out, inp, inp_tdb_out_next); } } /* Return a printable string for the IPv4 address. */ 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; snprintf(buf[i], sizeof buf[0], "%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 * ipsp_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)"; } } /* Check whether an IP{4,6} address is unspecified. */ int ipsp_is_unspecified(union sockaddr_union addr) { switch (addr.sa.sa_family) { #ifdef INET case AF_INET: if (addr.sin.sin_addr.s_addr == INADDR_ANY) return 1; else return 0; #endif /* INET */ #ifdef INET6 case AF_INET6: if (IN6_IS_ADDR_UNSPECIFIED(&addr.sin6.sin6_addr)) return 1; else return 0; #endif /* INET6 */ case 0: /* No family set. */ default: return 1; } } /* Free reference-counted structure. */ void ipsp_reffree(struct ipsec_ref *ipr) { #ifdef DIAGNOSTIC if (ipr->ref_count <= 0) printf("ipsp_reffree: illegal reference count %d for " "object %p (len = %d, malloctype = %d)\n", ipr->ref_count, ipr, ipr->ref_len, ipr->ref_malloctype); #endif if (--ipr->ref_count <= 0) FREE(ipr, ipr->ref_malloctype); } /* Mark a TDB as TDBF_SKIPCRYPTO. */ void ipsp_skipcrypto_mark(struct tdb_ident *tdbi) { struct tdb *tdb; int s = spltdb(); tdb = gettdb(tdbi->spi, &tdbi->dst, tdbi->proto); if (tdb != NULL) { tdb->tdb_flags |= TDBF_SKIPCRYPTO; tdb->tdb_last_marked = time.tv_sec; } splx(s); } /* Unmark a TDB as TDBF_SKIPCRYPTO. */ void ipsp_skipcrypto_unmark(struct tdb_ident *tdbi) { struct tdb *tdb; int s = spltdb(); tdb = gettdb(tdbi->spi, &tdbi->dst, tdbi->proto); if (tdb != NULL) { tdb->tdb_flags &= ~TDBF_SKIPCRYPTO; tdb->tdb_last_marked = time.tv_sec; } splx(s); } /* Return true if the two structures match. */ int ipsp_ref_match(struct ipsec_ref *ref1, struct ipsec_ref *ref2) { if (ref1->ref_type != ref2->ref_type || ref1->ref_len != ref2->ref_len || bcmp(ref1 + 1, ref2 + 1, ref1->ref_len)) return 0; return 1; } #ifdef notyet /* * Go down a chain of IPv4/IPv6/ESP/AH/IPiP chains creating an tag for each * IPsec header encountered. The offset where the first header, as well * as its type are given to us. */ struct m_tag * ipsp_parse_headers(struct mbuf *m, int off, u_int8_t proto) { int ipv4sa = 0, s, esphlen = 0, trail = 0, i; SLIST_HEAD(packet_tags, m_tag) tags; unsigned char lasteight[8]; struct tdb_ident *tdbi; struct m_tag *mtag; struct tdb *tdb; #ifdef INET struct ip iph; #endif /* INET */ #ifdef INET6 struct in6_addr ip6_dst; #endif /* INET6 */ /* We have to start with a known network protocol. */ if (proto != IPPROTO_IPV4 && proto != IPPROTO_IPV6) return NULL; SLIST_INIT(&tags); while (1) { switch (proto) { #ifdef INET case IPPROTO_IPV4: /* Also IPPROTO_IPIP */ { /* * Save the IP header (we need both the * address and ip_hl). */ m_copydata(m, off, sizeof(struct ip), (caddr_t) &iph); ipv4sa = 1; proto = iph.ip_p; off += iph.ip_hl << 2; break; } #endif /* INET */ #ifdef INET6 case IPPROTO_IPV6: { int nxtp, l; /* Copy the IPv6 address. */ m_copydata(m, off + offsetof(struct ip6_hdr, ip6_dst), sizeof(struct ip6_hdr), (caddr_t) &ip6_dst); ipv4sa = 0; /* * Go down the chain of headers until we encounter a * non-option. */ for (l = ip6_nexthdr(m, off, proto, &nxtp); l != -1; l = ip6_nexthdr(m, off, proto, &nxtp)) { off += l; proto = nxtp; /* Construct a tag. */ if (nxtp == IPPROTO_AH) { mtag = m_tag_get(PACKET_TAG_IPSEC_IN_CRYPTO_DONE, sizeof(struct tdb_ident), M_NOWAIT); if (mtag == NULL) return SLIST_FIRST(&tags); tdbi = (struct tdb_ident *) (mtag + 1); bzero(tdbi, sizeof(struct tdb_ident)); m_copydata(m, off + sizeof(u_int32_t), sizeof(u_int32_t), (caddr_t) &tdbi->spi); tdbi->proto = IPPROTO_AH; tdbi->dst.sin6.sin6_family = AF_INET6; tdbi->dst.sin6.sin6_len = sizeof(struct sockaddr_in6); tdbi->dst.sin6.sin6_addr = ip6_dst; SLIST_INSERT_HEAD(&tags, mtag, m_tag_link); } else if (nxtp == IPPROTO_IPV6) m_copydata(m, off + offsetof(struct ip6_hdr, ip6_dst), sizeof(struct ip6_hdr), (caddr_t) &ip6_dst); } break; } #endif /* INET6 */ case IPPROTO_ESP: /* Verify that this has been decrypted. */ { union sockaddr_union su; u_int32_t spi; m_copydata(m, off, sizeof(u_int32_t), (caddr_t) &spi); bzero(&su, sizeof(union sockaddr_union)); s = spltdb(); #ifdef INET if (ipv4sa) { su.sin.sin_family = AF_INET; su.sin.sin_len = sizeof(struct sockaddr_in); su.sin.sin_addr = iph.ip_dst; } #endif /* INET */ #ifdef INET6 if (!ipv4sa) { su.sin6.sin6_family = AF_INET6; su.sin6.sin6_len = sizeof(struct sockaddr_in6); su.sin6.sin6_addr = ip6_dst; } #endif /* INET6 */ tdb = gettdb(spi, &su, IPPROTO_ESP); if (tdb == NULL) { splx(s); return SLIST_FIRST(&tags); } /* How large is the ESP header ? We use this later. */ if (tdb->tdb_flags & TDBF_NOREPLAY) esphlen = sizeof(u_int32_t) + tdb->tdb_ivlen; else esphlen = 2 * sizeof(u_int32_t) + tdb->tdb_ivlen; /* * Verify decryption. If the SA is using * random padding (as the "old" ESP SAs were * bound to do, there's nothing we can do to * see if the payload has been decrypted. */ if (tdb->tdb_flags & TDBF_RANDOMPADDING) { splx(s); return SLIST_FIRST(&tags); } /* Update the length of trailing ESP authenticators. */ if (tdb->tdb_authalgxform) trail += AH_HMAC_HASHLEN; splx(s); /* Copy the last 10 bytes. */ m_copydata(m, m->m_pkthdr.len - trail - 8, 8, lasteight); /* Verify the self-describing padding values. */ if (lasteight[6] != 0) { if (lasteight[6] != lasteight[5]) return SLIST_FIRST(&tags); for (i = 4; lasteight[i + 1] != 1 && i >= 0; i--) if (lasteight[i + 1] != lasteight[i] + 1) return SLIST_FIRST(&tags); } } /* Fall through. */ case IPPROTO_AH: mtag = m_tag_get(PACKET_TAG_IPSEC_IN_CRYPTO_DONE, sizeof(struct tdb_ident), M_NOWAIT); if (mtag == NULL) return SLIST_FIRST(&tags); tdbi = (struct tdb_ident *) (mtag + 1); bzero(tdbi, sizeof(struct tdb_ident)); /* Get SPI off the relevant header. */ if (proto == IPPROTO_AH) m_copydata(m, off + sizeof(u_int32_t), sizeof(u_int32_t), (caddr_t) &tdbi->spi); else /* IPPROTO_ESP */ m_copydata(m, off, sizeof(u_int32_t), (caddr_t) &tdbi->spi); tdbi->proto = proto; /* AH or ESP */ #ifdef INET /* Last network header was IPv4. */ if (ipv4sa) { tdbi->dst.sin.sin_family = AF_INET; tdbi->dst.sin.sin_len = sizeof(struct sockaddr_in); tdbi->dst.sin.sin_addr = iph.ip_dst; } #endif /* INET */ #ifdef INET6 /* Last network header was IPv6. */ if (!ipv4sa) { tdbi->dst.sin6.sin6_family = AF_INET6; tdbi->dst.sin6.sin6_len = sizeof(struct sockaddr_in6); tdbi->dst.sin6.sin6_addr = ip6_dst; } #endif /* INET6 */ SLIST_INSERT_HEAD(&tags, mtag, m_tag_link); /* Update next protocol/header and header offset. */ if (proto == IPPROTO_AH) { u_int8_t foo[2]; m_copydata(m, off, 2 * sizeof(u_int8_t), foo); proto = foo[0]; off += (foo[1] + 2) << 2; } else {/* IPPROTO_ESP */ /* Initialized in IPPROTO_ESP case. */ off += esphlen; proto = lasteight[7]; } break; default: return SLIST_FIRST(&tags); /* We're done. */ } } } #endif /* notyet */