ipsec_output.c revision 170123
1112758Ssam/*- 2112758Ssam * Copyright (c) 2002, 2003 Sam Leffler, Errno Consulting 3112758Ssam * All rights reserved. 4112758Ssam * 5112758Ssam * Redistribution and use in source and binary forms, with or without 6112758Ssam * modification, are permitted provided that the following conditions 7112758Ssam * are met: 8112758Ssam * 1. Redistributions of source code must retain the above copyright 9112758Ssam * notice, this list of conditions and the following disclaimer. 10112758Ssam * 2. Redistributions in binary form must reproduce the above copyright 11112758Ssam * notice, this list of conditions and the following disclaimer in the 12112758Ssam * documentation and/or other materials provided with the distribution. 13112758Ssam * 14112758Ssam * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15112758Ssam * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16112758Ssam * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17112758Ssam * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18112758Ssam * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19112758Ssam * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20112758Ssam * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21112758Ssam * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22112758Ssam * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23112758Ssam * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24112758Ssam * SUCH DAMAGE. 25112758Ssam * 26112758Ssam * $FreeBSD: head/sys/netipsec/ipsec_output.c 170123 2007-05-29 22:44:24Z bz $ 27112758Ssam */ 28105197Ssam 29105197Ssam/* 30105197Ssam * IPsec output processing. 31105197Ssam */ 32105197Ssam#include "opt_inet.h" 33105197Ssam#include "opt_inet6.h" 34105197Ssam#include "opt_ipsec.h" 35159965Sthompsa#include "opt_enc.h" 36105197Ssam 37105197Ssam#include <sys/param.h> 38105197Ssam#include <sys/systm.h> 39105197Ssam#include <sys/mbuf.h> 40105197Ssam#include <sys/domain.h> 41105197Ssam#include <sys/protosw.h> 42105197Ssam#include <sys/socket.h> 43105197Ssam#include <sys/errno.h> 44105197Ssam#include <sys/syslog.h> 45105197Ssam 46105197Ssam#include <net/if.h> 47105197Ssam#include <net/route.h> 48105197Ssam 49105197Ssam#include <netinet/in.h> 50105197Ssam#include <netinet/in_systm.h> 51105197Ssam#include <netinet/ip.h> 52105197Ssam#include <netinet/ip_var.h> 53105197Ssam#include <netinet/in_var.h> 54105197Ssam#include <netinet/ip_ecn.h> 55105197Ssam#ifdef INET6 56105197Ssam#include <netinet6/ip6_ecn.h> 57105197Ssam#endif 58105197Ssam 59105197Ssam#include <netinet/ip6.h> 60105197Ssam#ifdef INET6 61105197Ssam#include <netinet6/ip6_var.h> 62105197Ssam#endif 63105197Ssam#include <netinet/in_pcb.h> 64105197Ssam#ifdef INET6 65105197Ssam#include <netinet/icmp6.h> 66105197Ssam#endif 67105197Ssam 68105197Ssam#include <netipsec/ipsec.h> 69105197Ssam#ifdef INET6 70105197Ssam#include <netipsec/ipsec6.h> 71105197Ssam#endif 72105197Ssam#include <netipsec/ah_var.h> 73105197Ssam#include <netipsec/esp_var.h> 74105197Ssam#include <netipsec/ipcomp_var.h> 75105197Ssam 76105197Ssam#include <netipsec/xform.h> 77105197Ssam 78105197Ssam#include <netipsec/key.h> 79105197Ssam#include <netipsec/keydb.h> 80105197Ssam#include <netipsec/key_debug.h> 81105197Ssam 82105197Ssam#include <machine/in_cksum.h> 83105197Ssam 84105197Ssamint 85105197Ssamipsec_process_done(struct mbuf *m, struct ipsecrequest *isr) 86105197Ssam{ 87105197Ssam struct tdb_ident *tdbi; 88105197Ssam struct m_tag *mtag; 89105197Ssam struct secasvar *sav; 90105197Ssam struct secasindex *saidx; 91105197Ssam int error; 92105197Ssam 93120585Ssam IPSEC_SPLASSERT_SOFTNET(__func__); 94105197Ssam 95120585Ssam IPSEC_ASSERT(m != NULL, ("null mbuf")); 96120585Ssam IPSEC_ASSERT(isr != NULL, ("null ISR")); 97105197Ssam sav = isr->sav; 98120585Ssam IPSEC_ASSERT(sav != NULL, ("null SA")); 99120585Ssam IPSEC_ASSERT(sav->sah != NULL, ("null SAH")); 100105197Ssam 101105197Ssam saidx = &sav->sah->saidx; 102105197Ssam switch (saidx->dst.sa.sa_family) { 103105197Ssam#ifdef INET 104105197Ssam case AF_INET: 105105197Ssam /* Fix the header length, for AH processing. */ 106105197Ssam mtod(m, struct ip *)->ip_len = htons(m->m_pkthdr.len); 107105197Ssam break; 108105197Ssam#endif /* INET */ 109105197Ssam#ifdef INET6 110105197Ssam case AF_INET6: 111105197Ssam /* Fix the header length, for AH processing. */ 112105197Ssam if (m->m_pkthdr.len < sizeof (struct ip6_hdr)) { 113105197Ssam error = ENXIO; 114105197Ssam goto bad; 115105197Ssam } 116105197Ssam if (m->m_pkthdr.len - sizeof (struct ip6_hdr) > IPV6_MAXPACKET) { 117105197Ssam /* No jumbogram support. */ 118105197Ssam error = ENXIO; /*?*/ 119105197Ssam goto bad; 120105197Ssam } 121105197Ssam mtod(m, struct ip6_hdr *)->ip6_plen = 122105197Ssam htons(m->m_pkthdr.len - sizeof(struct ip6_hdr)); 123105197Ssam break; 124105197Ssam#endif /* INET6 */ 125105197Ssam default: 126120585Ssam DPRINTF(("%s: unknown protocol family %u\n", __func__, 127105197Ssam saidx->dst.sa.sa_family)); 128105197Ssam error = ENXIO; 129105197Ssam goto bad; 130105197Ssam } 131105197Ssam 132105197Ssam /* 133105197Ssam * Add a record of what we've done or what needs to be done to the 134105197Ssam * packet. 135105197Ssam */ 136105197Ssam mtag = m_tag_get(PACKET_TAG_IPSEC_OUT_DONE, 137105197Ssam sizeof(struct tdb_ident), M_NOWAIT); 138105197Ssam if (mtag == NULL) { 139120585Ssam DPRINTF(("%s: could not get packet tag\n", __func__)); 140105197Ssam error = ENOMEM; 141105197Ssam goto bad; 142105197Ssam } 143105197Ssam 144105197Ssam tdbi = (struct tdb_ident *)(mtag + 1); 145105197Ssam tdbi->dst = saidx->dst; 146105197Ssam tdbi->proto = saidx->proto; 147105197Ssam tdbi->spi = sav->spi; 148105197Ssam m_tag_prepend(m, mtag); 149105197Ssam 150105197Ssam /* 151105197Ssam * If there's another (bundled) SA to apply, do so. 152105197Ssam * Note that this puts a burden on the kernel stack size. 153105197Ssam * If this is a problem we'll need to introduce a queue 154105197Ssam * to set the packet on so we can unwind the stack before 155105197Ssam * doing further processing. 156105197Ssam */ 157105197Ssam if (isr->next) { 158105197Ssam newipsecstat.ips_out_bundlesa++; 159105197Ssam return ipsec4_process_packet(m, isr->next, 0, 0); 160105197Ssam } 161117056Ssam key_sa_recordxfer(sav, m); /* record data transfer */ 162105197Ssam 163105197Ssam /* 164105197Ssam * We're done with IPsec processing, transmit the packet using the 165105197Ssam * appropriate network protocol (IP or IPv6). SPD lookup will be 166105197Ssam * performed again there. 167105197Ssam */ 168105197Ssam switch (saidx->dst.sa.sa_family) { 169105197Ssam#ifdef INET 170105197Ssam struct ip *ip; 171105197Ssam case AF_INET: 172105197Ssam ip = mtod(m, struct ip *); 173105197Ssam ip->ip_len = ntohs(ip->ip_len); 174105197Ssam ip->ip_off = ntohs(ip->ip_off); 175105197Ssam 176105197Ssam return ip_output(m, NULL, NULL, IP_RAWOUTPUT, NULL, NULL); 177105197Ssam#endif /* INET */ 178105197Ssam#ifdef INET6 179105197Ssam case AF_INET6: 180105197Ssam /* 181105197Ssam * We don't need massage, IPv6 header fields are always in 182105197Ssam * net endian. 183105197Ssam */ 184105197Ssam return ip6_output(m, NULL, NULL, 0, NULL, NULL, NULL); 185105197Ssam#endif /* INET6 */ 186105197Ssam } 187105197Ssam panic("ipsec_process_done"); 188105197Ssambad: 189105197Ssam m_freem(m); 190105197Ssam KEY_FREESAV(&sav); 191105197Ssam return (error); 192105197Ssam} 193105197Ssam 194105197Ssamstatic struct ipsecrequest * 195105197Ssamipsec_nextisr( 196105197Ssam struct mbuf *m, 197105197Ssam struct ipsecrequest *isr, 198105197Ssam int af, 199105197Ssam struct secasindex *saidx, 200105197Ssam int *error 201105197Ssam) 202105197Ssam{ 203105197Ssam#define IPSEC_OSTAT(x,y,z) (isr->saidx.proto == IPPROTO_ESP ? (x)++ : \ 204105197Ssam isr->saidx.proto == IPPROTO_AH ? (y)++ : (z)++) 205105197Ssam struct secasvar *sav; 206105197Ssam 207120585Ssam IPSEC_SPLASSERT_SOFTNET(__func__); 208120585Ssam IPSECREQUEST_LOCK_ASSERT(isr); 209120585Ssam 210120585Ssam IPSEC_ASSERT(af == AF_INET || af == AF_INET6, 211120585Ssam ("invalid address family %u", af)); 212105197Ssamagain: 213105197Ssam /* 214105197Ssam * Craft SA index to search for proper SA. Note that 215105197Ssam * we only fillin unspecified SA peers for transport 216105197Ssam * mode; for tunnel mode they must already be filled in. 217105197Ssam */ 218105197Ssam *saidx = isr->saidx; 219105197Ssam if (isr->saidx.mode == IPSEC_MODE_TRANSPORT) { 220105197Ssam /* Fillin unspecified SA peers only for transport mode */ 221105197Ssam if (af == AF_INET) { 222105197Ssam struct sockaddr_in *sin; 223105197Ssam struct ip *ip = mtod(m, struct ip *); 224105197Ssam 225105197Ssam if (saidx->src.sa.sa_len == 0) { 226105197Ssam sin = &saidx->src.sin; 227105197Ssam sin->sin_len = sizeof(*sin); 228105197Ssam sin->sin_family = AF_INET; 229105197Ssam sin->sin_port = IPSEC_PORT_ANY; 230105197Ssam sin->sin_addr = ip->ip_src; 231105197Ssam } 232105197Ssam if (saidx->dst.sa.sa_len == 0) { 233105197Ssam sin = &saidx->dst.sin; 234105197Ssam sin->sin_len = sizeof(*sin); 235105197Ssam sin->sin_family = AF_INET; 236105197Ssam sin->sin_port = IPSEC_PORT_ANY; 237105197Ssam sin->sin_addr = ip->ip_dst; 238105197Ssam } 239105197Ssam } else { 240105197Ssam struct sockaddr_in6 *sin6; 241105197Ssam struct ip6_hdr *ip6 = mtod(m, struct ip6_hdr *); 242105197Ssam 243105197Ssam if (saidx->src.sin6.sin6_len == 0) { 244105197Ssam sin6 = (struct sockaddr_in6 *)&saidx->src; 245105197Ssam sin6->sin6_len = sizeof(*sin6); 246105197Ssam sin6->sin6_family = AF_INET6; 247105197Ssam sin6->sin6_port = IPSEC_PORT_ANY; 248105197Ssam sin6->sin6_addr = ip6->ip6_src; 249105197Ssam if (IN6_IS_SCOPE_LINKLOCAL(&ip6->ip6_src)) { 250105197Ssam /* fix scope id for comparing SPD */ 251105197Ssam sin6->sin6_addr.s6_addr16[1] = 0; 252105197Ssam sin6->sin6_scope_id = 253105197Ssam ntohs(ip6->ip6_src.s6_addr16[1]); 254105197Ssam } 255105197Ssam } 256105197Ssam if (saidx->dst.sin6.sin6_len == 0) { 257105197Ssam sin6 = (struct sockaddr_in6 *)&saidx->dst; 258105197Ssam sin6->sin6_len = sizeof(*sin6); 259105197Ssam sin6->sin6_family = AF_INET6; 260105197Ssam sin6->sin6_port = IPSEC_PORT_ANY; 261105197Ssam sin6->sin6_addr = ip6->ip6_dst; 262105197Ssam if (IN6_IS_SCOPE_LINKLOCAL(&ip6->ip6_dst)) { 263105197Ssam /* fix scope id for comparing SPD */ 264105197Ssam sin6->sin6_addr.s6_addr16[1] = 0; 265105197Ssam sin6->sin6_scope_id = 266105197Ssam ntohs(ip6->ip6_dst.s6_addr16[1]); 267105197Ssam } 268105197Ssam } 269105197Ssam } 270105197Ssam } 271105197Ssam 272105197Ssam /* 273105197Ssam * Lookup SA and validate it. 274105197Ssam */ 275105197Ssam *error = key_checkrequest(isr, saidx); 276105197Ssam if (*error != 0) { 277105197Ssam /* 278105197Ssam * IPsec processing is required, but no SA found. 279105197Ssam * I assume that key_acquire() had been called 280105197Ssam * to get/establish the SA. Here I discard 281105197Ssam * this packet because it is responsibility for 282105197Ssam * upper layer to retransmit the packet. 283105197Ssam */ 284105197Ssam newipsecstat.ips_out_nosa++; 285105197Ssam goto bad; 286105197Ssam } 287105197Ssam sav = isr->sav; 288105197Ssam if (sav == NULL) { /* XXX valid return */ 289120585Ssam IPSEC_ASSERT(ipsec_get_reqlevel(isr) == IPSEC_LEVEL_USE, 290120585Ssam ("no SA found, but required; level %u", 291105197Ssam ipsec_get_reqlevel(isr))); 292120585Ssam IPSECREQUEST_UNLOCK(isr); 293105197Ssam isr = isr->next; 294105197Ssam if (isr == NULL) { 295105197Ssam /*XXXstatistic??*/ 296105197Ssam *error = EINVAL; /*XXX*/ 297105197Ssam return isr; 298105197Ssam } 299120585Ssam IPSECREQUEST_LOCK(isr); 300105197Ssam goto again; 301105197Ssam } 302105197Ssam 303105197Ssam /* 304105197Ssam * Check system global policy controls. 305105197Ssam */ 306105197Ssam if ((isr->saidx.proto == IPPROTO_ESP && !esp_enable) || 307105197Ssam (isr->saidx.proto == IPPROTO_AH && !ah_enable) || 308105197Ssam (isr->saidx.proto == IPPROTO_IPCOMP && !ipcomp_enable)) { 309120585Ssam DPRINTF(("%s: IPsec outbound packet dropped due" 310120585Ssam " to policy (check your sysctls)\n", __func__)); 311105197Ssam IPSEC_OSTAT(espstat.esps_pdrops, ahstat.ahs_pdrops, 312105197Ssam ipcompstat.ipcomps_pdrops); 313105197Ssam *error = EHOSTUNREACH; 314105197Ssam goto bad; 315105197Ssam } 316105197Ssam 317105197Ssam /* 318105197Ssam * Sanity check the SA contents for the caller 319105197Ssam * before they invoke the xform output method. 320105197Ssam */ 321105197Ssam if (sav->tdb_xform == NULL) { 322120585Ssam DPRINTF(("%s: no transform for SA\n", __func__)); 323105197Ssam IPSEC_OSTAT(espstat.esps_noxform, ahstat.ahs_noxform, 324105197Ssam ipcompstat.ipcomps_noxform); 325105197Ssam *error = EHOSTUNREACH; 326105197Ssam goto bad; 327105197Ssam } 328105197Ssam return isr; 329105197Ssambad: 330120585Ssam IPSEC_ASSERT(*error != 0, ("error return w/ no error code")); 331120585Ssam IPSECREQUEST_UNLOCK(isr); 332105197Ssam return NULL; 333105197Ssam#undef IPSEC_OSTAT 334105197Ssam} 335105197Ssam 336105197Ssam#ifdef INET 337105197Ssam/* 338105197Ssam * IPsec output logic for IPv4. 339105197Ssam */ 340105197Ssamint 341105197Ssamipsec4_process_packet( 342105197Ssam struct mbuf *m, 343105197Ssam struct ipsecrequest *isr, 344105197Ssam int flags, 345105197Ssam int tunalready) 346105197Ssam{ 347105197Ssam struct secasindex saidx; 348105197Ssam struct secasvar *sav; 349105197Ssam struct ip *ip; 350119643Ssam int error, i, off; 351105197Ssam 352120585Ssam IPSEC_ASSERT(m != NULL, ("null mbuf")); 353120585Ssam IPSEC_ASSERT(isr != NULL, ("null isr")); 354105197Ssam 355120585Ssam IPSECREQUEST_LOCK(isr); /* insure SA contents don't change */ 356105197Ssam 357105197Ssam isr = ipsec_nextisr(m, isr, AF_INET, &saidx, &error); 358105197Ssam if (isr == NULL) 359105197Ssam goto bad; 360105197Ssam 361105197Ssam sav = isr->sav; 362159965Sthompsa 363159965Sthompsa#ifdef DEV_ENC 364159965Sthompsa /* pass the mbuf to enc0 for packet filtering */ 365159965Sthompsa if ((error = ipsec_filter(&m, 2)) != 0) 366159965Sthompsa goto bad; 367159965Sthompsa#endif 368159965Sthompsa 369105197Ssam if (!tunalready) { 370105197Ssam union sockaddr_union *dst = &sav->sah->saidx.dst; 371105197Ssam int setdf; 372105197Ssam 373105197Ssam /* 374105197Ssam * Collect IP_DF state from the outer header. 375105197Ssam */ 376105197Ssam if (dst->sa.sa_family == AF_INET) { 377105197Ssam if (m->m_len < sizeof (struct ip) && 378105197Ssam (m = m_pullup(m, sizeof (struct ip))) == NULL) { 379105197Ssam error = ENOBUFS; 380105197Ssam goto bad; 381105197Ssam } 382105197Ssam ip = mtod(m, struct ip *); 383105197Ssam /* Honor system-wide control of how to handle IP_DF */ 384105197Ssam switch (ip4_ipsec_dfbit) { 385105197Ssam case 0: /* clear in outer header */ 386105197Ssam case 1: /* set in outer header */ 387105197Ssam setdf = ip4_ipsec_dfbit; 388105197Ssam break; 389105197Ssam default: /* propagate to outer header */ 390105197Ssam setdf = ntohs(ip->ip_off & IP_DF); 391105197Ssam break; 392105197Ssam } 393105197Ssam } else { 394105197Ssam ip = NULL; /* keep compiler happy */ 395105197Ssam setdf = 0; 396105197Ssam } 397105197Ssam /* Do the appropriate encapsulation, if necessary */ 398105197Ssam if (isr->saidx.mode == IPSEC_MODE_TUNNEL || /* Tunnel requ'd */ 399105197Ssam dst->sa.sa_family != AF_INET || /* PF mismatch */ 400105197Ssam#if 0 401105197Ssam (sav->flags & SADB_X_SAFLAGS_TUNNEL) || /* Tunnel requ'd */ 402105197Ssam sav->tdb_xform->xf_type == XF_IP4 || /* ditto */ 403105197Ssam#endif 404105197Ssam (dst->sa.sa_family == AF_INET && /* Proxy */ 405105197Ssam dst->sin.sin_addr.s_addr != INADDR_ANY && 406105197Ssam dst->sin.sin_addr.s_addr != ip->ip_dst.s_addr)) { 407105197Ssam struct mbuf *mp; 408105197Ssam 409105197Ssam /* Fix IPv4 header checksum and length */ 410105197Ssam if (m->m_len < sizeof (struct ip) && 411105197Ssam (m = m_pullup(m, sizeof (struct ip))) == NULL) { 412105197Ssam error = ENOBUFS; 413105197Ssam goto bad; 414105197Ssam } 415105197Ssam ip = mtod(m, struct ip *); 416105197Ssam ip->ip_len = htons(m->m_pkthdr.len); 417105197Ssam ip->ip_sum = 0; 418105197Ssam#ifdef _IP_VHL 419105197Ssam if (ip->ip_vhl == IP_VHL_BORING) 420105197Ssam ip->ip_sum = in_cksum_hdr(ip); 421105197Ssam else 422105197Ssam ip->ip_sum = in_cksum(m, 423105197Ssam _IP_VHL_HL(ip->ip_vhl) << 2); 424105197Ssam#else 425105197Ssam ip->ip_sum = in_cksum(m, ip->ip_hl << 2); 426105197Ssam#endif 427105197Ssam 428105197Ssam /* Encapsulate the packet */ 429105197Ssam error = ipip_output(m, isr, &mp, 0, 0); 430105197Ssam if (mp == NULL && !error) { 431105197Ssam /* Should never happen. */ 432120585Ssam DPRINTF(("%s: ipip_output returns no mbuf and " 433120585Ssam "no error!", __func__)); 434105197Ssam error = EFAULT; 435105197Ssam } 436105197Ssam if (error) { 437124765Ssam if (mp) { 438124765Ssam /* XXX: Should never happen! */ 439105197Ssam m_freem(mp); 440124765Ssam } 441124765Ssam m = NULL; /* ipip_output() already freed it */ 442105197Ssam goto bad; 443105197Ssam } 444105197Ssam m = mp, mp = NULL; 445105197Ssam /* 446105197Ssam * ipip_output clears IP_DF in the new header. If 447105197Ssam * we need to propagate IP_DF from the outer header, 448105197Ssam * then we have to do it here. 449105197Ssam * 450105197Ssam * XXX shouldn't assume what ipip_output does. 451105197Ssam */ 452105197Ssam if (dst->sa.sa_family == AF_INET && setdf) { 453105197Ssam if (m->m_len < sizeof (struct ip) && 454105197Ssam (m = m_pullup(m, sizeof (struct ip))) == NULL) { 455105197Ssam error = ENOBUFS; 456105197Ssam goto bad; 457105197Ssam } 458105197Ssam ip = mtod(m, struct ip *); 459105197Ssam ip->ip_off = ntohs(ip->ip_off); 460105197Ssam ip->ip_off |= IP_DF; 461105197Ssam ip->ip_off = htons(ip->ip_off); 462105197Ssam } 463105197Ssam } 464105197Ssam } 465105197Ssam 466159965Sthompsa#ifdef DEV_ENC 467159965Sthompsa /* pass the mbuf to enc0 for bpf processing */ 468159965Sthompsa ipsec_bpf(m, sav, AF_INET); 469159965Sthompsa#endif 470159965Sthompsa 471105197Ssam /* 472105197Ssam * Dispatch to the appropriate IPsec transform logic. The 473105197Ssam * packet will be returned for transmission after crypto 474105197Ssam * processing, etc. are completed. For encapsulation we 475105197Ssam * bypass this call because of the explicit call done above 476105197Ssam * (necessary to deal with IP_DF handling for IPv4). 477105197Ssam * 478105197Ssam * NB: m & sav are ``passed to caller'' who's reponsible for 479105197Ssam * for reclaiming their resources. 480105197Ssam */ 481105197Ssam if (sav->tdb_xform->xf_type != XF_IP4) { 482105197Ssam ip = mtod(m, struct ip *); 483105197Ssam i = ip->ip_hl << 2; 484105197Ssam off = offsetof(struct ip, ip_p); 485105197Ssam error = (*sav->tdb_xform->xf_output)(m, isr, NULL, i, off); 486105197Ssam } else { 487105197Ssam error = ipsec_process_done(m, isr); 488105197Ssam } 489120585Ssam IPSECREQUEST_UNLOCK(isr); 490105197Ssam return error; 491105197Ssambad: 492120585Ssam if (isr) 493120585Ssam IPSECREQUEST_UNLOCK(isr); 494105197Ssam if (m) 495105197Ssam m_freem(m); 496105197Ssam return error; 497105197Ssam} 498105197Ssam#endif 499105197Ssam 500105197Ssam#ifdef INET6 501105197Ssam/* 502105197Ssam * Chop IP6 header from the payload. 503105197Ssam */ 504105197Ssamstatic struct mbuf * 505105197Ssamipsec6_splithdr(struct mbuf *m) 506105197Ssam{ 507105197Ssam struct mbuf *mh; 508105197Ssam struct ip6_hdr *ip6; 509105197Ssam int hlen; 510105197Ssam 511120585Ssam IPSEC_ASSERT(m->m_len >= sizeof (struct ip6_hdr), 512120585Ssam ("first mbuf too short, len %u", m->m_len)); 513105197Ssam ip6 = mtod(m, struct ip6_hdr *); 514105197Ssam hlen = sizeof(struct ip6_hdr); 515105197Ssam if (m->m_len > hlen) { 516151967Sandre MGETHDR(mh, M_DONTWAIT, MT_DATA); 517105197Ssam if (!mh) { 518105197Ssam m_freem(m); 519105197Ssam return NULL; 520105197Ssam } 521108466Ssam M_MOVE_PKTHDR(mh, m); 522105197Ssam MH_ALIGN(mh, hlen); 523105197Ssam m->m_len -= hlen; 524105197Ssam m->m_data += hlen; 525105197Ssam mh->m_next = m; 526105197Ssam m = mh; 527105197Ssam m->m_len = hlen; 528105197Ssam bcopy((caddr_t)ip6, mtod(m, caddr_t), hlen); 529105197Ssam } else if (m->m_len < hlen) { 530105197Ssam m = m_pullup(m, hlen); 531105197Ssam if (!m) 532105197Ssam return NULL; 533105197Ssam } 534105197Ssam return m; 535105197Ssam} 536105197Ssam 537105197Ssam/* 538105197Ssam * IPsec output logic for IPv6, transport mode. 539105197Ssam */ 540105197Ssamint 541105197Ssamipsec6_output_trans( 542105197Ssam struct ipsec_output_state *state, 543105197Ssam u_char *nexthdrp, 544105197Ssam struct mbuf *mprev, 545105197Ssam struct secpolicy *sp, 546105197Ssam int flags, 547105197Ssam int *tun) 548105197Ssam{ 549105197Ssam struct ipsecrequest *isr; 550105197Ssam struct secasindex saidx; 551105197Ssam int error = 0; 552105197Ssam struct mbuf *m; 553105197Ssam 554120585Ssam IPSEC_ASSERT(state != NULL, ("null state")); 555120585Ssam IPSEC_ASSERT(state->m != NULL, ("null m")); 556120585Ssam IPSEC_ASSERT(nexthdrp != NULL, ("null nexthdrp")); 557120585Ssam IPSEC_ASSERT(mprev != NULL, ("null mprev")); 558120585Ssam IPSEC_ASSERT(sp != NULL, ("null sp")); 559120585Ssam IPSEC_ASSERT(tun != NULL, ("null tun")); 560105197Ssam 561105197Ssam KEYDEBUG(KEYDEBUG_IPSEC_DATA, 562170122Sbz printf("%s: applied SP\n", __func__); 563105197Ssam kdebug_secpolicy(sp)); 564105197Ssam 565105197Ssam isr = sp->req; 566105197Ssam if (isr->saidx.mode == IPSEC_MODE_TUNNEL) { 567105197Ssam /* the rest will be handled by ipsec6_output_tunnel() */ 568105197Ssam *tun = 1; /* need tunnel-mode processing */ 569105197Ssam return 0; 570105197Ssam } 571105197Ssam 572105197Ssam *tun = 0; 573105197Ssam m = state->m; 574105197Ssam 575105197Ssam isr = ipsec_nextisr(m, isr, AF_INET6, &saidx, &error); 576105197Ssam if (isr == NULL) { 577105197Ssam#ifdef notdef 578105197Ssam /* XXX should notification be done for all errors ? */ 579105197Ssam /* 580105197Ssam * Notify the fact that the packet is discarded 581105197Ssam * to ourselves. I believe this is better than 582105197Ssam * just silently discarding. (jinmei@kame.net) 583105197Ssam * XXX: should we restrict the error to TCP packets? 584105197Ssam * XXX: should we directly notify sockets via 585105197Ssam * pfctlinputs? 586105197Ssam */ 587105197Ssam icmp6_error(m, ICMP6_DST_UNREACH, 588105197Ssam ICMP6_DST_UNREACH_ADMIN, 0); 589105197Ssam m = NULL; /* NB: icmp6_error frees mbuf */ 590105197Ssam#endif 591105197Ssam goto bad; 592105197Ssam } 593105197Ssam 594105197Ssam return (*isr->sav->tdb_xform->xf_output)(m, isr, NULL, 595105197Ssam sizeof (struct ip6_hdr), 596105197Ssam offsetof(struct ip6_hdr, ip6_nxt)); 597105197Ssambad: 598105197Ssam if (m) 599105197Ssam m_freem(m); 600105197Ssam state->m = NULL; 601105197Ssam return error; 602105197Ssam} 603105197Ssam 604105197Ssamstatic int 605105197Ssamipsec6_encapsulate(struct mbuf *m, struct secasvar *sav) 606105197Ssam{ 607105197Ssam struct ip6_hdr *oip6; 608105197Ssam struct ip6_hdr *ip6; 609105197Ssam size_t plen; 610105197Ssam 611105197Ssam /* can't tunnel between different AFs */ 612105197Ssam if (sav->sah->saidx.src.sa.sa_family != AF_INET6 || 613105197Ssam sav->sah->saidx.dst.sa.sa_family != AF_INET6) { 614105197Ssam m_freem(m); 615105197Ssam return EINVAL; 616105197Ssam } 617120585Ssam IPSEC_ASSERT(m->m_len != sizeof (struct ip6_hdr), 618120585Ssam ("mbuf wrong size; len %u", m->m_len)); 619105197Ssam 620105197Ssam 621105197Ssam /* 622105197Ssam * grow the mbuf to accomodate the new IPv6 header. 623105197Ssam */ 624105197Ssam plen = m->m_pkthdr.len; 625105197Ssam if (M_LEADINGSPACE(m->m_next) < sizeof(struct ip6_hdr)) { 626105197Ssam struct mbuf *n; 627111119Simp MGET(n, M_DONTWAIT, MT_DATA); 628105197Ssam if (!n) { 629105197Ssam m_freem(m); 630105197Ssam return ENOBUFS; 631105197Ssam } 632105197Ssam n->m_len = sizeof(struct ip6_hdr); 633105197Ssam n->m_next = m->m_next; 634105197Ssam m->m_next = n; 635105197Ssam m->m_pkthdr.len += sizeof(struct ip6_hdr); 636105197Ssam oip6 = mtod(n, struct ip6_hdr *); 637105197Ssam } else { 638105197Ssam m->m_next->m_len += sizeof(struct ip6_hdr); 639105197Ssam m->m_next->m_data -= sizeof(struct ip6_hdr); 640105197Ssam m->m_pkthdr.len += sizeof(struct ip6_hdr); 641105197Ssam oip6 = mtod(m->m_next, struct ip6_hdr *); 642105197Ssam } 643105197Ssam ip6 = mtod(m, struct ip6_hdr *); 644113076Sdes bcopy((caddr_t)ip6, (caddr_t)oip6, sizeof(struct ip6_hdr)); 645105197Ssam 646105197Ssam /* Fake link-local scope-class addresses */ 647105197Ssam if (IN6_IS_SCOPE_LINKLOCAL(&oip6->ip6_src)) 648105197Ssam oip6->ip6_src.s6_addr16[1] = 0; 649105197Ssam if (IN6_IS_SCOPE_LINKLOCAL(&oip6->ip6_dst)) 650105197Ssam oip6->ip6_dst.s6_addr16[1] = 0; 651105197Ssam 652105197Ssam /* construct new IPv6 header. see RFC 2401 5.1.2.2 */ 653105197Ssam /* ECN consideration. */ 654105197Ssam ip6_ecn_ingress(ip6_ipsec_ecn, &ip6->ip6_flow, &oip6->ip6_flow); 655105197Ssam if (plen < IPV6_MAXPACKET - sizeof(struct ip6_hdr)) 656105197Ssam ip6->ip6_plen = htons(plen); 657105197Ssam else { 658105197Ssam /* ip6->ip6_plen will be updated in ip6_output() */ 659105197Ssam } 660105197Ssam ip6->ip6_nxt = IPPROTO_IPV6; 661105197Ssam sav->sah->saidx.src.sin6.sin6_addr = ip6->ip6_src; 662105197Ssam sav->sah->saidx.dst.sin6.sin6_addr = ip6->ip6_dst; 663105197Ssam ip6->ip6_hlim = IPV6_DEFHLIM; 664105197Ssam 665105197Ssam /* XXX Should ip6_src be updated later ? */ 666105197Ssam 667105197Ssam return 0; 668105197Ssam} 669105197Ssam 670105197Ssam/* 671105197Ssam * IPsec output logic for IPv6, tunnel mode. 672105197Ssam */ 673105197Ssamint 674105197Ssamipsec6_output_tunnel(struct ipsec_output_state *state, struct secpolicy *sp, int flags) 675105197Ssam{ 676105197Ssam struct ip6_hdr *ip6; 677105197Ssam struct ipsecrequest *isr; 678105197Ssam struct secasindex saidx; 679105197Ssam int error; 680105197Ssam struct sockaddr_in6* dst6; 681105197Ssam struct mbuf *m; 682105197Ssam 683120585Ssam IPSEC_ASSERT(state != NULL, ("null state")); 684120585Ssam IPSEC_ASSERT(state->m != NULL, ("null m")); 685120585Ssam IPSEC_ASSERT(sp != NULL, ("null sp")); 686105197Ssam 687105197Ssam KEYDEBUG(KEYDEBUG_IPSEC_DATA, 688170122Sbz printf("%s: applied SP\n", __func__); 689105197Ssam kdebug_secpolicy(sp)); 690105197Ssam 691105197Ssam m = state->m; 692105197Ssam /* 693105197Ssam * transport mode ipsec (before the 1st tunnel mode) is already 694105197Ssam * processed by ipsec6_output_trans(). 695105197Ssam */ 696105197Ssam for (isr = sp->req; isr; isr = isr->next) { 697105197Ssam if (isr->saidx.mode == IPSEC_MODE_TUNNEL) 698105197Ssam break; 699105197Ssam } 700170123Sbz 701170123Sbz IPSECREQUEST_LOCK(isr); /* insure SA contents don't change */ 702170123Sbz 703105197Ssam isr = ipsec_nextisr(m, isr, AF_INET6, &saidx, &error); 704105197Ssam if (isr == NULL) 705105197Ssam goto bad; 706105197Ssam 707105197Ssam /* 708105197Ssam * There may be the case that SA status will be changed when 709105197Ssam * we are refering to one. So calling splsoftnet(). 710105197Ssam */ 711105197Ssam if (isr->saidx.mode == IPSEC_MODE_TUNNEL) { 712105197Ssam /* 713105197Ssam * build IPsec tunnel. 714105197Ssam */ 715105197Ssam /* XXX should be processed with other familiy */ 716105197Ssam if (isr->sav->sah->saidx.src.sa.sa_family != AF_INET6) { 717120585Ssam ipseclog((LOG_ERR, "%s: family mismatched between " 718120585Ssam "inner and outer, spi=%u\n", __func__, 719105197Ssam ntohl(isr->sav->spi))); 720105197Ssam newipsecstat.ips_out_inval++; 721105197Ssam error = EAFNOSUPPORT; 722105197Ssam goto bad; 723105197Ssam } 724105197Ssam 725105197Ssam m = ipsec6_splithdr(m); 726105197Ssam if (!m) { 727105197Ssam newipsecstat.ips_out_nomem++; 728105197Ssam error = ENOMEM; 729105197Ssam goto bad; 730105197Ssam } 731105197Ssam error = ipsec6_encapsulate(m, isr->sav); 732105197Ssam if (error) { 733105197Ssam m = NULL; 734105197Ssam goto bad; 735105197Ssam } 736105197Ssam ip6 = mtod(m, struct ip6_hdr *); 737105197Ssam 738105197Ssam state->ro = &isr->sav->sah->sa_route; 739105197Ssam state->dst = (struct sockaddr *)&state->ro->ro_dst; 740105197Ssam dst6 = (struct sockaddr_in6 *)state->dst; 741105197Ssam if (state->ro->ro_rt 742105197Ssam && ((state->ro->ro_rt->rt_flags & RTF_UP) == 0 743105197Ssam || !IN6_ARE_ADDR_EQUAL(&dst6->sin6_addr, &ip6->ip6_dst))) { 744105197Ssam RTFREE(state->ro->ro_rt); 745105197Ssam state->ro->ro_rt = NULL; 746105197Ssam } 747105197Ssam if (state->ro->ro_rt == 0) { 748105197Ssam bzero(dst6, sizeof(*dst6)); 749105197Ssam dst6->sin6_family = AF_INET6; 750105197Ssam dst6->sin6_len = sizeof(*dst6); 751105197Ssam dst6->sin6_addr = ip6->ip6_dst; 752105197Ssam rtalloc(state->ro); 753105197Ssam } 754105197Ssam if (state->ro->ro_rt == 0) { 755105197Ssam ip6stat.ip6s_noroute++; 756105197Ssam newipsecstat.ips_out_noroute++; 757105197Ssam error = EHOSTUNREACH; 758105197Ssam goto bad; 759105197Ssam } 760105197Ssam 761105197Ssam /* adjust state->dst if tunnel endpoint is offlink */ 762105197Ssam if (state->ro->ro_rt->rt_flags & RTF_GATEWAY) { 763105197Ssam state->dst = (struct sockaddr *)state->ro->ro_rt->rt_gateway; 764105197Ssam dst6 = (struct sockaddr_in6 *)state->dst; 765105197Ssam } 766105197Ssam } 767105197Ssam 768105197Ssam m = ipsec6_splithdr(m); 769105197Ssam if (!m) { 770105197Ssam newipsecstat.ips_out_nomem++; 771105197Ssam error = ENOMEM; 772105197Ssam goto bad; 773105197Ssam } 774105197Ssam ip6 = mtod(m, struct ip6_hdr *); 775170123Sbz error = (*isr->sav->tdb_xform->xf_output)(m, isr, NULL, 776105197Ssam sizeof (struct ip6_hdr), 777105197Ssam offsetof(struct ip6_hdr, ip6_nxt)); 778170123Sbz IPSECREQUEST_UNLOCK(isr); 779170123Sbz return error; 780105197Ssambad: 781170123Sbz if (isr) 782170123Sbz IPSECREQUEST_UNLOCK(isr); 783105197Ssam if (m) 784105197Ssam m_freem(m); 785105197Ssam state->m = NULL; 786105197Ssam return error; 787105197Ssam} 788105197Ssam#endif /*INET6*/ 789