/* $NetBSD: esp_output.c,v 1.32 2008/04/23 06:09:05 thorpej Exp $ */ /* $KAME: esp_output.c,v 1.44 2001/07/26 06:53:15 jinmei 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. */ #include __KERNEL_RCSID(0, "$NetBSD: esp_output.c,v 1.32 2008/04/23 06:09:05 thorpej Exp $"); #include "opt_inet.h" #include "opt_ipsec.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef IPSEC_NAT_T #include #endif #ifdef INET6 #include #include #include #endif #include #include #include #include #include #include #include static int esp_output __P((struct mbuf *, u_char *, struct mbuf *, struct ipsecrequest *, int)); /* * compute ESP header size. */ size_t esp_hdrsiz(struct ipsecrequest *isr) { struct secasvar *sav; const struct esp_algorithm *algo; const struct ah_algorithm *aalgo; size_t ivlen; size_t authlen; size_t hdrsiz; /* sanity check */ if (isr == NULL) panic("esp_hdrsiz: NULL was passed."); sav = isr->sav; if (isr->saidx.proto != IPPROTO_ESP) panic("unsupported mode passed to esp_hdrsiz"); if (sav == NULL) goto estimate; if (sav->state != SADB_SASTATE_MATURE && sav->state != SADB_SASTATE_DYING) goto estimate; /* we need transport mode ESP. */ algo = esp_algorithm_lookup(sav->alg_enc); if (!algo) goto estimate; ivlen = sav->ivlen; #if 0 if (ivlen < 0) goto estimate; #endif /* * XXX * right now we don't calcurate the padding size. simply * treat the padding size as constant, for simplicity. * * XXX variable size padding support */ if (sav->flags & SADB_X_EXT_OLD) { /* RFC 1827 */ hdrsiz = sizeof(struct esp) + ivlen + esp_max_padbound() - 1 + 2; } else { /* RFC 2406 */ aalgo = ah_algorithm_lookup(sav->alg_auth); if (aalgo && sav->replay && sav->key_auth) authlen = (aalgo->sumsiz)(sav); else authlen = 0; hdrsiz = sizeof(struct newesp) + ivlen + esp_max_padbound() - 1 + 2 + authlen; } #ifdef IPSEC_NAT_T /* * If NAT-T is enabled, add the space for UDP encapsulation */ if (sav->natt_type != 0) { hdrsiz += sizeof(struct udphdr); if (sav->natt_type == UDP_ENCAP_ESPINUDP_NON_IKE) hdrsiz += sizeof(u_int64_t); } #endif return hdrsiz; estimate: /* * ASSUMING: * sizeof(struct newesp) > sizeof(struct esp). * esp_max_ivlen() = max ivlen for CBC mode * esp_max_padbound - 1 = * maximum padding length without random padding length * 2 = (Pad Length field) + (Next Header field). * AH_MAXSUMSIZE = maximum ICV we support. * sizeof(u_int64_t) = non IKE marker (NAT-T) * sizeof(struct udphdr) = UDP encapsulation (NAT-T) */ return sizeof(struct newesp) + esp_max_ivlen() + esp_max_padbound() - 1 + 2 + AH_MAXSUMSIZE + #ifdef IPSEC_NAT_T sizeof(u_int64_t) + sizeof(struct udphdr) + #endif 0; } /* * Modify the packet so that the payload is encrypted. * The mbuf (m) must start with IPv4 or IPv6 header. * On failure, free the given mbuf and return NULL. * * on invocation: * m nexthdrp md * v v v * IP ......... payload * during the encryption: * m nexthdrp mprev md * v v v v * IP ............... esp iv payload pad padlen nxthdr * <--><-><------><---------------> * esplen plen extendsiz * ivlen * <-----> esphlen * <-> hlen * <-----------------> espoff */ static int esp_output(struct mbuf *m, u_char *nexthdrp, struct mbuf *md, struct ipsecrequest *isr, int af) { struct mbuf *n; struct mbuf *mprev; struct esp *esp; struct esptail *esptail; struct secasvar *sav = isr->sav; const struct esp_algorithm *algo; u_int32_t spi; u_int8_t nxt = 0; size_t plen; /* payload length to be encrypted */ size_t espoff; int ivlen; int afnumber; size_t extendsiz; int error = 0; percpu_t *stat; #ifdef IPSEC_NAT_T struct udphdr *udp = NULL; #endif switch (af) { #ifdef INET case AF_INET: afnumber = 4; stat = ipsecstat_percpu; break; #endif #ifdef INET6 case AF_INET6: afnumber = 6; stat = ipsec6stat_percpu; break; #endif default: ipseclog((LOG_ERR, "esp_output: unsupported af %d\n", af)); return 0; /* no change at all */ } /* some sanity check */ if ((sav->flags & SADB_X_EXT_OLD) == 0 && !sav->replay) { switch (af) { #ifdef INET case AF_INET: { struct ip *ip; ip = mtod(m, struct ip *); ipseclog((LOG_DEBUG, "esp4_output: internal error: " "sav->replay is null: %x->%x, SPI=%u\n", (u_int32_t)ntohl(ip->ip_src.s_addr), (u_int32_t)ntohl(ip->ip_dst.s_addr), (u_int32_t)ntohl(sav->spi))); IPSEC_STATINC(IPSEC_STAT_OUT_INVAL); break; } #endif /* INET */ #ifdef INET6 case AF_INET6: ipseclog((LOG_DEBUG, "esp6_output: internal error: " "sav->replay is null: SPI=%u\n", (u_int32_t)ntohl(sav->spi))); IPSEC6_STATINC(IPSEC_STAT_OUT_INVAL); break; #endif /* INET6 */ default: panic("esp_output: should not reach here"); } error = EINVAL; goto fail; } algo = esp_algorithm_lookup(sav->alg_enc); if (!algo) { ipseclog((LOG_ERR, "esp_output: unsupported algorithm: " "SPI=%u\n", (u_int32_t)ntohl(sav->spi))); error = EINVAL; goto fail; } spi = sav->spi; ivlen = sav->ivlen; /* should be okey */ if (ivlen < 0) { panic("invalid ivlen"); } { /* * insert ESP header. * XXX inserts ESP header right after IPv4 header. should * chase the header chain. * XXX sequential number */ #ifdef INET struct ip *ip = NULL; #endif #ifdef INET6 struct ip6_hdr *ip6 = NULL; #endif size_t esplen; /* sizeof(struct esp/newesp) */ size_t esphlen; /* sizeof(struct esp/newesp) + ivlen */ size_t hlen = 0; /* ip header len */ if (sav->flags & SADB_X_EXT_OLD) { /* RFC 1827 */ esplen = sizeof(struct esp); } else { /* RFC 2406 */ if (sav->flags & SADB_X_EXT_DERIV) esplen = sizeof(struct esp); else esplen = sizeof(struct newesp); } esphlen = esplen + ivlen; for (mprev = m; mprev && mprev->m_next != md; mprev = mprev->m_next) ; if (mprev == NULL || mprev->m_next != md) { ipseclog((LOG_DEBUG, "esp%d_output: md is not in chain\n", afnumber)); error = EINVAL; goto fail; } plen = 0; for (n = md; n; n = n->m_next) plen += n->m_len; switch (af) { #ifdef INET case AF_INET: ip = mtod(m, struct ip *); hlen = ip->ip_hl << 2; break; #endif #ifdef INET6 case AF_INET6: ip6 = mtod(m, struct ip6_hdr *); hlen = sizeof(*ip6); break; #endif } /* make the packet over-writable */ mprev->m_next = NULL; if ((md = ipsec_copypkt(md)) == NULL) { error = ENOBUFS; goto fail; } mprev->m_next = md; espoff = m->m_pkthdr.len - plen; #ifdef IPSEC_NAT_T if (sav->natt_type != 0) { esphlen += sizeof(struct udphdr); espoff += sizeof(struct udphdr); if (sav->natt_type == UDP_ENCAP_ESPINUDP_NON_IKE) { /* NON-IKE marker */ esphlen += sizeof(u_int64_t); espoff += sizeof(u_int64_t); } } #endif /* * grow the mbuf to accommodate ESP header. * before: IP ... payload * after (without NAT-T): IP ... ESP IV payload * after (with older NAT-T): IP ... UDP non-IKE-marker ESP IV payload * after (with newer NAT-T): IP ... UDP ESP IV payload */ if (M_LEADINGSPACE(md) < esphlen || (md->m_flags & M_EXT) != 0) { MGET(n, M_DONTWAIT, MT_DATA); if (!n) { error = ENOBUFS; goto fail; } n->m_len = esphlen; mprev->m_next = n; n->m_next = md; m->m_pkthdr.len += esphlen; esp = mtod(n, struct esp *); } else { md->m_len += esphlen; md->m_data -= esphlen; m->m_pkthdr.len += esphlen; esp = mtod(md, struct esp *); } #ifdef IPSEC_NAT_T if (sav->natt_type != 0) { udp = (struct udphdr *)esp; esp = (struct esp *)(udp + 1); if (sav->natt_type == UDP_ENCAP_ESPINUDP_NON_IKE) { u_int64_t *data = (u_int64_t *)esp; *data = 0; /* NON-IKE marker */ esp = (struct esp *)(data + 1); } } #endif nxt = *nexthdrp; *nexthdrp = IPPROTO_ESP; switch (af) { #ifdef INET case AF_INET: if (esphlen < (IP_MAXPACKET - ntohs(ip->ip_len))) ip->ip_len = htons(ntohs(ip->ip_len) + esphlen); else { ipseclog((LOG_ERR, "IPv4 ESP output: size exceeds limit\n")); IPSEC_STATINC(IPSEC_STAT_OUT_INVAL); error = EMSGSIZE; goto fail; } break; #endif #ifdef INET6 case AF_INET6: /* total packet length will be computed in ip6_output() */ break; #endif } } /* initialize esp header. */ esp->esp_spi = spi; if ((sav->flags & SADB_X_EXT_OLD) == 0) { struct newesp *nesp; nesp = (struct newesp *)esp; if (sav->replay->count == ~0) { if ((sav->flags & SADB_X_EXT_CYCSEQ) == 0) { /* XXX Is it noisy ? */ ipseclog((LOG_WARNING, "replay counter overflowed. %s\n", ipsec_logsastr(sav))); _NET_STATINC(stat, IPSEC_STAT_OUT_INVAL); error = EINVAL; goto fail; } } sav->replay->count++; /* * XXX sequence number must not be cycled, if the SA is * installed by IKE daemon. */ nesp->esp_seq = htonl(sav->replay->count & 0xffffffff); } { /* * find the last mbuf. make some room for ESP trailer. */ #ifdef INET struct ip *ip = NULL; #endif size_t padbound; u_char *extend; int i; if (algo->padbound) padbound = algo->padbound; else padbound = 4; /* ESP packet, including nxthdr field, must be length of 4n */ if (padbound < 4) padbound = 4; extendsiz = padbound - (plen % padbound); if (extendsiz == 1) extendsiz = padbound + 1; n = m; while (n->m_next) n = n->m_next; /* * if M_EXT, the external mbuf data may be shared among * two consequtive TCP packets, and it may be unsafe to use the * trailing space. */ if (!(n->m_flags & M_EXT) && extendsiz < M_TRAILINGSPACE(n)) { extend = mtod(n, u_char *) + n->m_len; n->m_len += extendsiz; m->m_pkthdr.len += extendsiz; } else { struct mbuf *nn; MGET(nn, M_DONTWAIT, MT_DATA); if (!nn) { ipseclog((LOG_DEBUG, "esp%d_output: can't alloc mbuf", afnumber)); error = ENOBUFS; goto fail; } extend = mtod(nn, u_char *); nn->m_len = extendsiz; nn->m_next = NULL; n->m_next = nn; n = nn; m->m_pkthdr.len += extendsiz; } switch (sav->flags & SADB_X_EXT_PMASK) { case SADB_X_EXT_PRAND: key_randomfill(extend, extendsiz); break; case SADB_X_EXT_PZERO: bzero(extend, extendsiz); break; case SADB_X_EXT_PSEQ: for (i = 0; i < extendsiz; i++) extend[i] = (i + 1) & 0xff; break; } #ifdef IPSEC_NAT_T if (sav->natt_type != 0) { *nexthdrp = IPPROTO_UDP; /* * Create the UDP encapsulation header for NAT-T * uh_len is set later, when the size is known. */ if (sav->natt_type == UDP_ENCAP_ESPINUDP_NON_IKE) udp->uh_sport = htons(UDP_ENCAP_ESPINUDP_PORT); else udp->uh_sport = KEY_PORTFROMSADDR(&sav->sah->saidx.src); udp->uh_dport = KEY_PORTFROMSADDR(&sav->sah->saidx.dst); udp->uh_sum = 0; } else { *nexthdrp = IPPROTO_ESP; } #endif /* initialize esp trailer. */ esptail = (struct esptail *) (mtod(n, u_int8_t *) + n->m_len - sizeof(struct esptail)); esptail->esp_nxt = nxt; esptail->esp_padlen = extendsiz - 2; /* modify IP header (for ESP header part only) */ switch (af) { #ifdef INET case AF_INET: ip = mtod(m, struct ip *); if (extendsiz < (IP_MAXPACKET - ntohs(ip->ip_len))) ip->ip_len = htons(ntohs(ip->ip_len) + extendsiz); else { ipseclog((LOG_ERR, "IPv4 ESP output: size exceeds limit\n")); IPSEC_STATINC(IPSEC_STAT_OUT_INVAL); error = EMSGSIZE; goto fail; } break; #endif #ifdef INET6 case AF_INET6: /* total packet length will be computed in ip6_output() */ break; #endif } } /* * pre-compute and cache intermediate key */ error = esp_schedule(algo, sav); if (error) { _NET_STATINC(stat, IPSEC_STAT_OUT_INVAL); goto fail; } /* * encrypt the packet, based on security association * and the algorithm specified. */ if (!algo->encrypt) panic("internal error: no encrypt function"); if ((*algo->encrypt)(m, espoff, plen + extendsiz, sav, algo, ivlen)) { /* m is already freed */ m = NULL; ipseclog((LOG_ERR, "packet encryption failure\n")); _NET_STATINC(stat, IPSEC_STAT_OUT_INVAL); error = EINVAL; goto fail; } /* * calculate ICV if required. */ if (!sav->replay) goto noantireplay; if (!sav->key_auth) goto noantireplay; if (sav->key_auth == SADB_AALG_NONE) goto noantireplay; { const struct ah_algorithm *aalgo; u_char authbuf[AH_MAXSUMSIZE]; u_char *p; size_t siz; #ifdef INET struct ip *ip; #endif aalgo = ah_algorithm_lookup(sav->alg_auth); if (!aalgo) goto noantireplay; siz = ((aalgo->sumsiz)(sav) + 3) & ~(4 - 1); if (AH_MAXSUMSIZE < siz) panic("assertion failed for AH_MAXSUMSIZE"); if (esp_auth(m, espoff, m->m_pkthdr.len - espoff, sav, authbuf)) { ipseclog((LOG_ERR, "ESP checksum generation failure\n")); error = EINVAL; _NET_STATINC(stat, IPSEC_STAT_OUT_INVAL); goto fail; } n = m; while (n->m_next) n = n->m_next; if (!(n->m_flags & M_EXT) && siz < M_TRAILINGSPACE(n)) { /* XXX */ n->m_len += siz; m->m_pkthdr.len += siz; p = mtod(n, u_char *) + n->m_len - siz; } else { struct mbuf *nn; MGET(nn, M_DONTWAIT, MT_DATA); if (!nn) { ipseclog((LOG_DEBUG, "can't alloc mbuf in esp%d_output", afnumber)); error = ENOBUFS; goto fail; } nn->m_len = siz; nn->m_next = NULL; n->m_next = nn; n = nn; m->m_pkthdr.len += siz; p = mtod(nn, u_char *); } bcopy(authbuf, p, siz); /* modify IP header (for ESP header part only) */ switch (af) { #ifdef INET case AF_INET: ip = mtod(m, struct ip *); if (siz < (IP_MAXPACKET - ntohs(ip->ip_len))) ip->ip_len = htons(ntohs(ip->ip_len) + siz); else { ipseclog((LOG_ERR, "IPv4 ESP output: size exceeds limit\n")); IPSEC_STATINC(IPSEC_STAT_OUT_INVAL); error = EMSGSIZE; goto fail; } break; #endif #ifdef INET6 case AF_INET6: /* total packet length will be computed in ip6_output() */ break; #endif } } noantireplay: #ifdef IPSEC_NAT_T if (sav->natt_type != 0) { struct ip *ip; ip = mtod(m, struct ip *); udp->uh_ulen = htons(ntohs(ip->ip_len) - (ip->ip_hl << 2)); } #endif /* IPSEC_NAT_T */ if (!m) { ipseclog((LOG_ERR, "NULL mbuf after encryption in esp%d_output", afnumber)); } else _NET_STATINC(stat, IPSEC_STAT_OUT_SUCCESS); _NET_STATINC(stat, IPSEC_STAT_OUT_ESPHIST + sav->alg_enc); key_sa_recordxfer(sav, m); return 0; fail: m_freem(m); #if 1 return error; #else panic("something bad in esp_output"); #endif } #ifdef INET int esp4_output(struct mbuf *m, struct ipsecrequest *isr) { struct ip *ip; if (m->m_len < sizeof(struct ip)) { ipseclog((LOG_DEBUG, "esp4_output: first mbuf too short\n")); m_freem(m); return EINVAL; } ip = mtod(m, struct ip *); /* XXX assumes that m->m_next points to payload */ return esp_output(m, &ip->ip_p, m->m_next, isr, AF_INET); } #endif /* INET */ #ifdef INET6 int esp6_output(struct mbuf *m, u_char *nexthdrp, struct mbuf *md, struct ipsecrequest *isr) { if (m->m_len < sizeof(struct ip6_hdr)) { ipseclog((LOG_DEBUG, "esp6_output: first mbuf too short\n")); m_freem(m); return EINVAL; } return esp_output(m, nexthdrp, md, isr, AF_INET6); } #endif /* INET6 */