xref: /openbsd-src/sys/netinet/ipsec_output.c (revision 7c6c9ed7c5f5549392e68be8e2919d48c24c69b0)
1*7c6c9ed7Smvs /*	$OpenBSD: ipsec_output.c,v 1.99 2024/12/27 10:15:09 mvs Exp $ */
2b1efc16cSangelos /*
3b1efc16cSangelos  * The author of this code is Angelos D. Keromytis (angelos@cis.upenn.edu)
4b1efc16cSangelos  *
50775ebe4Sangelos  * Copyright (c) 2000-2001 Angelos D. Keromytis.
6b1efc16cSangelos  *
70775ebe4Sangelos  * Permission to use, copy, and modify this software with or without fee
8b1efc16cSangelos  * is hereby granted, provided that this entire notice is included in
9b1efc16cSangelos  * all copies of any software which is or includes a copy or
10b1efc16cSangelos  * modification of this software.
11b1efc16cSangelos  * You may use this code under the GNU public license if you so wish. Please
12b1efc16cSangelos  * contribute changes back to the authors under this freer than GPL license
13b1efc16cSangelos  * so that we may further the use of strong encryption without limitations to
14b1efc16cSangelos  * all.
15b1efc16cSangelos  *
16b1efc16cSangelos  * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR
17b1efc16cSangelos  * IMPLIED WARRANTY. IN PARTICULAR, NONE OF THE AUTHORS MAKES ANY
18b1efc16cSangelos  * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE
19b1efc16cSangelos  * MERCHANTABILITY OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR
20b1efc16cSangelos  * PURPOSE.
21b1efc16cSangelos  */
22b1efc16cSangelos 
23aa920ac7Sreyk #include "pf.h"
24aa920ac7Sreyk 
25b1efc16cSangelos #include <sys/param.h>
26b1efc16cSangelos #include <sys/systm.h>
27b1efc16cSangelos #include <sys/mbuf.h>
28b1efc16cSangelos #include <sys/socket.h>
29b1efc16cSangelos #include <sys/kernel.h>
30638c25e3Stedu #include <sys/timeout.h>
31b1efc16cSangelos 
32b1efc16cSangelos #include <net/if.h>
33377da2e0Sderaadt #include <net/route.h>
34aa920ac7Sreyk 
35b1efc16cSangelos #include <netinet/in.h>
36b1efc16cSangelos #include <netinet/ip.h>
37b1efc16cSangelos #include <netinet/in_pcb.h>
38b1efc16cSangelos #include <netinet/ip_var.h>
39940d25acSbluhm #include <netinet6/ip6_var.h>
40b1efc16cSangelos 
41377da2e0Sderaadt #if NPF > 0
42377da2e0Sderaadt #include <net/pfvar.h>
43377da2e0Sderaadt #endif
44b1efc16cSangelos 
45ed4bea91Smarkus #include <netinet/udp.h>
46ea784840Sbluhm #include <netinet/ip_ipip.h>
47b1efc16cSangelos #include <netinet/ip_ah.h>
48b1efc16cSangelos #include <netinet/ip_esp.h>
493ec0b612Sjjbg #include <netinet/ip_ipcomp.h>
507ced204eSmpi 
517ced204eSmpi #include <crypto/cryptodev.h>
526d7c3229Sprovos #include <crypto/xform.h>
53b1efc16cSangelos 
54b1efc16cSangelos #ifdef ENCDEBUG
55698a75ddSbluhm #define DPRINTF(fmt, args...)						\
56698a75ddSbluhm 	do {								\
57698a75ddSbluhm 		if (encdebug)						\
58698a75ddSbluhm 			printf("%s: " fmt "\n", __func__, ## args);	\
59698a75ddSbluhm 	} while (0)
60b1efc16cSangelos #else
61698a75ddSbluhm #define DPRINTF(fmt, args...)						\
62698a75ddSbluhm 	do { } while (0)
63b1efc16cSangelos #endif
64b1efc16cSangelos 
6559d46dd5Sho int	udpencap_enable = 1;	/* enabled by default */
66ed4bea91Smarkus int	udpencap_port = 4500;	/* triggers decapsulation */
67ed4bea91Smarkus 
68b1efc16cSangelos /*
69b1efc16cSangelos  * Loop over a tdb chain, taking into consideration protocol tunneling. The
70b1efc16cSangelos  * fourth argument is set if the first encapsulation header is already in
71b1efc16cSangelos  * place.
72b1efc16cSangelos  */
73b1efc16cSangelos int
74da628552Sangelos ipsp_process_packet(struct mbuf *m, struct tdb *tdb, int af, int tunalready)
75b1efc16cSangelos {
76ae52a733Smikeb 	int hlen, off, error;
77065a3e52Sitojun #ifdef INET6
78065a3e52Sitojun 	struct ip6_ext ip6e;
79065a3e52Sitojun 	int nxt;
80065a3e52Sitojun 	int dstopt = 0;
81065a3e52Sitojun #endif
82b1efc16cSangelos 
836d7c3229Sprovos 	int setdf = 0;
84b1efc16cSangelos 	struct ip *ip;
85b1efc16cSangelos #ifdef INET6
86b1efc16cSangelos 	struct ip6_hdr *ip6;
87b1efc16cSangelos #endif /* INET6 */
88b1efc16cSangelos 
893514aacbSmikeb #ifdef ENCDEBUG
903514aacbSmikeb 	char buf[INET6_ADDRSTRLEN];
913514aacbSmikeb #endif
923514aacbSmikeb 
936830693bSangelos 	/* Check that the transform is allowed by the administrator. */
94b1efc16cSangelos 	if ((tdb->tdb_sproto == IPPROTO_ESP && !esp_enable) ||
95*7c6c9ed7Smvs 	    (tdb->tdb_sproto == IPPROTO_AH && !atomic_load_int(&ah_enable)) ||
96*7c6c9ed7Smvs 	    (tdb->tdb_sproto == IPPROTO_IPCOMP &&
97*7c6c9ed7Smvs 	    !atomic_load_int(&ipcomp_enable))) {
98698a75ddSbluhm 		DPRINTF("IPsec outbound packet dropped due to policy "
99698a75ddSbluhm 		    "(check your sysctls)");
10066ffc2ddSbluhm 		error = EHOSTUNREACH;
10166ffc2ddSbluhm 		goto drop;
102b1efc16cSangelos 	}
103b1efc16cSangelos 
1046830693bSangelos 	/* Sanity check. */
1056830693bSangelos 	if (!tdb->tdb_xform) {
106698a75ddSbluhm 		DPRINTF("uninitialized TDB");
10766ffc2ddSbluhm 		error = EHOSTUNREACH;
10866ffc2ddSbluhm 		goto drop;
109b1efc16cSangelos 	}
110b1efc16cSangelos 
1116830693bSangelos 	/* Check if the SPI is invalid. */
1126830693bSangelos 	if (tdb->tdb_flags & TDBF_INVALID) {
113698a75ddSbluhm 		DPRINTF("attempt to use invalid SA %s/%08x/%u",
114698a75ddSbluhm 		    ipsp_address(&tdb->tdb_dst, buf, sizeof(buf)),
115698a75ddSbluhm 		    ntohl(tdb->tdb_spi), tdb->tdb_sproto);
11666ffc2ddSbluhm 		error = ENXIO;
11766ffc2ddSbluhm 		goto drop;
118b1efc16cSangelos 	}
119b1efc16cSangelos 
120b1efc16cSangelos 	/* Check that the network protocol is supported */
1216830693bSangelos 	switch (tdb->tdb_dst.sa.sa_family) {
122b1efc16cSangelos 	case AF_INET:
123b1efc16cSangelos 		break;
124b1efc16cSangelos 
125b1efc16cSangelos #ifdef INET6
126b1efc16cSangelos 	case AF_INET6:
127b1efc16cSangelos 		break;
128b1efc16cSangelos #endif /* INET6 */
129b1efc16cSangelos 
130b1efc16cSangelos 	default:
131698a75ddSbluhm 		DPRINTF("attempt to use SA %s/%08x/%u for protocol family %d",
1323514aacbSmikeb 		    ipsp_address(&tdb->tdb_dst, buf, sizeof(buf)),
1333514aacbSmikeb 		    ntohl(tdb->tdb_spi), tdb->tdb_sproto,
134698a75ddSbluhm 		    tdb->tdb_dst.sa.sa_family);
135bc489a1cSbluhm 		error = EPFNOSUPPORT;
13666ffc2ddSbluhm 		goto drop;
137b1efc16cSangelos 	}
138b1efc16cSangelos 
1396830693bSangelos 	/*
1406830693bSangelos 	 * Register first use if applicable, setup relevant expiration timer.
1416830693bSangelos 	 */
1426830693bSangelos 	if (tdb->tdb_first_use == 0) {
1433209772dScheloha 		tdb->tdb_first_use = gettime();
1444b0e5db3Sbluhm 		if (tdb->tdb_flags & TDBF_FIRSTUSE) {
1454b0e5db3Sbluhm 			if (timeout_add_sec(&tdb->tdb_first_tmo,
1464b0e5db3Sbluhm 			    tdb->tdb_exp_first_use))
1474b0e5db3Sbluhm 				tdb_ref(tdb);
1484b0e5db3Sbluhm 		}
1494b0e5db3Sbluhm 		if (tdb->tdb_flags & TDBF_SOFT_FIRSTUSE) {
1504b0e5db3Sbluhm 			if (timeout_add_sec(&tdb->tdb_sfirst_tmo,
1514b0e5db3Sbluhm 			    tdb->tdb_soft_first_use))
1524b0e5db3Sbluhm 				tdb_ref(tdb);
1534b0e5db3Sbluhm 		}
154b1efc16cSangelos 	}
155b1efc16cSangelos 
156b1efc16cSangelos 	/*
157b1efc16cSangelos 	 * Check for tunneling if we don't have the first header in place.
158b1efc16cSangelos 	 * When doing Ethernet-over-IP, we are handed an already-encapsulated
159b1efc16cSangelos 	 * frame, so we don't need to re-encapsulate.
160b1efc16cSangelos 	 */
1616830693bSangelos 	if (tunalready == 0) {
162b1efc16cSangelos 		/*
163b1efc16cSangelos 		 * If the target protocol family is different, we know we'll be
164b1efc16cSangelos 		 * doing tunneling.
165b1efc16cSangelos 		 */
1666830693bSangelos 		if (af == tdb->tdb_dst.sa.sa_family) {
1673882ce82Sbluhm 			switch (af) {
1683882ce82Sbluhm 			case AF_INET:
169ae52a733Smikeb 				hlen = sizeof(struct ip);
1703882ce82Sbluhm 				break;
171b1efc16cSangelos #ifdef INET6
1723882ce82Sbluhm 			case AF_INET6:
173ae52a733Smikeb 				hlen = sizeof(struct ip6_hdr);
1743882ce82Sbluhm 				break;
175b1efc16cSangelos #endif /* INET6 */
1763882ce82Sbluhm 			}
177b1efc16cSangelos 
1786830693bSangelos 			/* Bring the network header in the first mbuf. */
179ae52a733Smikeb 			if (m->m_len < hlen) {
18066ffc2ddSbluhm 				if ((m = m_pullup(m, hlen)) == NULL) {
18166ffc2ddSbluhm 					error = ENOBUFS;
18266ffc2ddSbluhm 					goto drop;
18366ffc2ddSbluhm 				}
184b1efc16cSangelos 			}
185b1efc16cSangelos 
186bf3ff4f4Sbluhm 			if (af == AF_INET) {
187b1efc16cSangelos 				ip = mtod(m, struct ip *);
1886830693bSangelos 
1896830693bSangelos 				/*
1906830693bSangelos 				 * This is not a bridge packet, remember if we
1916830693bSangelos 				 * had IP_DF.
1926830693bSangelos 				 */
19385f7599bSitojun 				setdf = ip->ip_off & htons(IP_DF);
194bf3ff4f4Sbluhm 			}
195b1efc16cSangelos 
196b1efc16cSangelos #ifdef INET6
197bf3ff4f4Sbluhm 			if (af == AF_INET6)
198b1efc16cSangelos 				ip6 = mtod(m, struct ip6_hdr *);
199b1efc16cSangelos #endif /* INET6 */
200b1efc16cSangelos 		}
201b1efc16cSangelos 
2026830693bSangelos 		/* Do the appropriate encapsulation, if necessary. */
203b1efc16cSangelos 		if ((tdb->tdb_dst.sa.sa_family != af) || /* PF mismatch */
2046830693bSangelos 		    (tdb->tdb_flags & TDBF_TUNNELING) || /* Tunneling needed */
205b1efc16cSangelos 		    (tdb->tdb_xform->xf_type == XF_IP4) || /* ditto */
206b1efc16cSangelos 		    ((tdb->tdb_dst.sa.sa_family == AF_INET) &&
207b1efc16cSangelos 		     (tdb->tdb_dst.sin.sin_addr.s_addr != INADDR_ANY) &&
208b1efc16cSangelos 		     (tdb->tdb_dst.sin.sin_addr.s_addr != ip->ip_dst.s_addr)) ||
209b1efc16cSangelos #ifdef INET6
210b1efc16cSangelos 		    ((tdb->tdb_dst.sa.sa_family == AF_INET6) &&
211b1efc16cSangelos 		     (!IN6_IS_ADDR_UNSPECIFIED(&tdb->tdb_dst.sin6.sin6_addr)) &&
212b1efc16cSangelos 		     (!IN6_ARE_ADDR_EQUAL(&tdb->tdb_dst.sin6.sin6_addr,
213b1efc16cSangelos 		      &ip6->ip6_dst))) ||
214b1efc16cSangelos #endif /* INET6 */
2156830693bSangelos 		    0) {
2166830693bSangelos 			/* Fix IPv4 header checksum and length. */
2176830693bSangelos 			if (af == AF_INET) {
2182b1f9644Sangelos 				if (m->m_len < sizeof(struct ip))
2196830693bSangelos 					if ((m = m_pullup(m,
22066ffc2ddSbluhm 					    sizeof(struct ip))) == NULL) {
22166ffc2ddSbluhm 						error = ENOBUFS;
22266ffc2ddSbluhm 						goto drop;
22366ffc2ddSbluhm 					}
224b1efc16cSangelos 
225b1efc16cSangelos 				ip = mtod(m, struct ip *);
226b1efc16cSangelos 				ip->ip_len = htons(m->m_pkthdr.len);
227b1efc16cSangelos 				ip->ip_sum = 0;
228b1efc16cSangelos 				ip->ip_sum = in_cksum(m, ip->ip_hl << 2);
229b1efc16cSangelos 			}
230b1efc16cSangelos 
231b1efc16cSangelos #ifdef INET6
2326830693bSangelos 			/* Fix IPv6 header payload length. */
2336830693bSangelos 			if (af == AF_INET6) {
2342b1f9644Sangelos 				if (m->m_len < sizeof(struct ip6_hdr))
2356830693bSangelos 					if ((m = m_pullup(m,
23666ffc2ddSbluhm 					    sizeof(struct ip6_hdr))) == NULL) {
23766ffc2ddSbluhm 						error = ENOBUFS;
23866ffc2ddSbluhm 						goto drop;
23966ffc2ddSbluhm 					}
240b1efc16cSangelos 
2416830693bSangelos 				if (m->m_pkthdr.len - sizeof(*ip6) >
2426830693bSangelos 				    IPV6_MAXPACKET) {
2436830693bSangelos 					/* No jumbogram support. */
24466ffc2ddSbluhm 					error = ENXIO;	/*?*/
24566ffc2ddSbluhm 					goto drop;
246b1efc16cSangelos 				}
247b1efc16cSangelos 				ip6 = mtod(m, struct ip6_hdr *);
2486830693bSangelos 				ip6->ip6_plen = htons(m->m_pkthdr.len
2496830693bSangelos 				    - sizeof(*ip6));
250b1efc16cSangelos 			}
251b1efc16cSangelos #endif /* INET6 */
252b1efc16cSangelos 
253ead5a062Sbluhm 			/* Encapsulate -- m may be changed or set to NULL. */
254ead5a062Sbluhm 			error = ipip_output(&m, tdb);
255ead5a062Sbluhm 			if ((m == NULL) && (!error))
256b1efc16cSangelos 				error = EFAULT;
25766ffc2ddSbluhm 			if (error)
25866ffc2ddSbluhm 				goto drop;
2596d7c3229Sprovos 
2606d7c3229Sprovos 			if (tdb->tdb_dst.sa.sa_family == AF_INET && setdf) {
2616d7c3229Sprovos 				if (m->m_len < sizeof(struct ip))
2626830693bSangelos 					if ((m = m_pullup(m,
26366ffc2ddSbluhm 					    sizeof(struct ip))) == NULL) {
26466ffc2ddSbluhm 						error = ENOBUFS;
26566ffc2ddSbluhm 						goto drop;
26666ffc2ddSbluhm 					}
2676d7c3229Sprovos 
268d9819910Sangelos 				ip = mtod(m, struct ip *);
26985f7599bSitojun 				ip->ip_off |= htons(IP_DF);
2706d7c3229Sprovos 			}
2716d7c3229Sprovos 
2726830693bSangelos 			/* Remember that we appended a tunnel header. */
27359b9936bSbluhm 			mtx_enter(&tdb->tdb_mtx);
2746d7c3229Sprovos 			tdb->tdb_flags |= TDBF_USEDTUNNEL;
27559b9936bSbluhm 			mtx_leave(&tdb->tdb_mtx);
276b1efc16cSangelos 		}
277ead5a062Sbluhm 	}
278b1efc16cSangelos 
279b1efc16cSangelos 	/*
280ead5a062Sbluhm 	 * If this is just an IP-IP TDB and we're told there's already an
281df8d9afdSjsg 	 * encapsulation header or ipip_output() has encapsulated it, move on.
282b1efc16cSangelos 	 */
283b1efc16cSangelos 	if (tdb->tdb_xform->xf_type == XF_IP4)
284da628552Sangelos 		return ipsp_process_done(m, tdb);
285b1efc16cSangelos 
2866830693bSangelos 	/* Extract some information off the headers. */
2876830693bSangelos 	switch (tdb->tdb_dst.sa.sa_family) {
288b1efc16cSangelos 	case AF_INET:
289b1efc16cSangelos 		ip = mtod(m, struct ip *);
290ae52a733Smikeb 		hlen = ip->ip_hl << 2;
291b1efc16cSangelos 		off = offsetof(struct ip, ip_p);
292b1efc16cSangelos 		break;
293b1efc16cSangelos 
294b1efc16cSangelos #ifdef INET6
295b1efc16cSangelos 	case AF_INET6:
296b1efc16cSangelos 		ip6 = mtod(m, struct ip6_hdr *);
297ae52a733Smikeb 		hlen = sizeof(struct ip6_hdr);
298b1efc16cSangelos 		off = offsetof(struct ip6_hdr, ip6_nxt);
299065a3e52Sitojun 		nxt = ip6->ip6_nxt;
300065a3e52Sitojun 		/*
301065a3e52Sitojun 		 * chase mbuf chain to find the appropriate place to
302065a3e52Sitojun 		 * put AH/ESP/IPcomp header.
303065a3e52Sitojun 		 *	IPv6 hbh dest1 rthdr ah* [esp* dest2 payload]
304065a3e52Sitojun 		 */
305065a3e52Sitojun 		do {
306065a3e52Sitojun 			switch (nxt) {
307065a3e52Sitojun 			case IPPROTO_AH:
308065a3e52Sitojun 			case IPPROTO_ESP:
309065a3e52Sitojun 			case IPPROTO_IPCOMP:
310065a3e52Sitojun 				/*
311065a3e52Sitojun 				 * we should not skip security header added
312065a3e52Sitojun 				 * beforehand.
313065a3e52Sitojun 				 */
314065a3e52Sitojun 				goto exitip6loop;
315065a3e52Sitojun 
316065a3e52Sitojun 			case IPPROTO_HOPOPTS:
317065a3e52Sitojun 			case IPPROTO_DSTOPTS:
318065a3e52Sitojun 			case IPPROTO_ROUTING:
319065a3e52Sitojun 				/*
320065a3e52Sitojun 				 * if we see 2nd destination option header,
321065a3e52Sitojun 				 * we should stop there.
322065a3e52Sitojun 				 */
323065a3e52Sitojun 				if (nxt == IPPROTO_DSTOPTS && dstopt)
324065a3e52Sitojun 					goto exitip6loop;
325065a3e52Sitojun 
326065a3e52Sitojun 				if (nxt == IPPROTO_DSTOPTS) {
327065a3e52Sitojun 					/*
328065a3e52Sitojun 					 * seen 1st or 2nd destination option.
329065a3e52Sitojun 					 * next time we see one, it must be 2nd.
330065a3e52Sitojun 					 */
331065a3e52Sitojun 					dstopt = 1;
332065a3e52Sitojun 				} else if (nxt == IPPROTO_ROUTING) {
333065a3e52Sitojun 					/*
334678831beSjsg 					 * if we see destination option next
335065a3e52Sitojun 					 * time, it must be dest2.
336065a3e52Sitojun 					 */
337065a3e52Sitojun 					dstopt = 2;
338065a3e52Sitojun 				}
3399a3d0d0aSbluhm 				if (m->m_pkthdr.len < hlen + sizeof(ip6e)) {
34066ffc2ddSbluhm 					error = EINVAL;
34166ffc2ddSbluhm 					goto drop;
3429a3d0d0aSbluhm 				}
343065a3e52Sitojun 				/* skip this header */
344ae52a733Smikeb 				m_copydata(m, hlen, sizeof(ip6e),
345ae52a733Smikeb 				    (caddr_t)&ip6e);
346065a3e52Sitojun 				nxt = ip6e.ip6e_nxt;
347ae52a733Smikeb 				off = hlen + offsetof(struct ip6_ext, ip6e_nxt);
348065a3e52Sitojun 				/*
349065a3e52Sitojun 				 * we will never see nxt == IPPROTO_AH
350065a3e52Sitojun 				 * so it is safe to omit AH case.
351065a3e52Sitojun 				 */
352ae52a733Smikeb 				hlen += (ip6e.ip6e_len + 1) << 3;
353065a3e52Sitojun 				break;
354065a3e52Sitojun 			default:
355065a3e52Sitojun 				goto exitip6loop;
356065a3e52Sitojun 			}
357ae52a733Smikeb 		} while (hlen < m->m_pkthdr.len);
35866ffc2ddSbluhm 	exitip6loop:
359b1efc16cSangelos 		break;
360b1efc16cSangelos #endif /* INET6 */
36166ffc2ddSbluhm 	default:
362bc489a1cSbluhm 		error = EPFNOSUPPORT;
36366ffc2ddSbluhm 		goto drop;
364b1efc16cSangelos 	}
365b1efc16cSangelos 
3669a3d0d0aSbluhm 	if (m->m_pkthdr.len < hlen) {
36766ffc2ddSbluhm 		error = EINVAL;
36866ffc2ddSbluhm 		goto drop;
3699a3d0d0aSbluhm 	}
3709a3d0d0aSbluhm 
3717ced204eSmpi 	ipsecstat_add(ipsec_ouncompbytes, m->m_pkthdr.len);
372d997d144Smvs 	tdbstat_add(tdb, tdb_ouncompbytes, m->m_pkthdr.len);
3737ced204eSmpi 
3743ec0b612Sjjbg 	/* Non expansion policy for IPCOMP */
3753ec0b612Sjjbg 	if (tdb->tdb_sproto == IPPROTO_IPCOMP) {
376ae52a733Smikeb 		if ((m->m_pkthdr.len - hlen) < tdb->tdb_compalgxform->minlen) {
3773ec0b612Sjjbg 			/* No need to compress, leave the packet untouched */
3786460338bSvisa 			ipcompstat_inc(ipcomps_minlen);
3793ec0b612Sjjbg 			return ipsp_process_done(m, tdb);
3803ec0b612Sjjbg 		}
3813ec0b612Sjjbg 	}
3823ec0b612Sjjbg 
3836830693bSangelos 	/* Invoke the IPsec transform. */
384ead5a062Sbluhm 	return (*(tdb->tdb_xform->xf_output))(m, tdb, hlen, off);
38566ffc2ddSbluhm 
38666ffc2ddSbluhm  drop:
38766ffc2ddSbluhm 	m_freem(m);
38866ffc2ddSbluhm 	return error;
389b1efc16cSangelos }
390b1efc16cSangelos 
391b1efc16cSangelos /*
392b1efc16cSangelos  * Called by the IPsec output transform callbacks, to transmit the packet
393b1efc16cSangelos  * or do further processing, as necessary.
394b1efc16cSangelos  */
395b1efc16cSangelos int
396da628552Sangelos ipsp_process_done(struct mbuf *m, struct tdb *tdb)
397b1efc16cSangelos {
398b1efc16cSangelos 	struct ip *ip;
399b1efc16cSangelos #ifdef INET6
400b1efc16cSangelos 	struct ip6_hdr *ip6;
401b1efc16cSangelos #endif /* INET6 */
4024b0e5db3Sbluhm 	struct tdb *tdbo;
40302755a9fSangelos 	struct tdb_ident *tdbi;
40402755a9fSangelos 	struct m_tag *mtag;
40566ffc2ddSbluhm 	int roff, error;
40602755a9fSangelos 
4070ae95220Sbluhm 	NET_ASSERT_LOCKED();
4080ae95220Sbluhm 
4093209772dScheloha 	tdb->tdb_last_used = gettime();
410a7914fe2Sangelos 
41148caea30Smarkus 	if ((tdb->tdb_flags & TDBF_UDPENCAP) != 0) {
412ed4bea91Smarkus 		struct mbuf *mi;
413ed4bea91Smarkus 		struct udphdr *uh;
41498d4dd3fSmikeb 		int iphlen;
415ed4bea91Smarkus 
41648caea30Smarkus 		if (!udpencap_enable || !udpencap_port) {
41766ffc2ddSbluhm 			error = ENXIO;
41866ffc2ddSbluhm 			goto drop;
41948caea30Smarkus 		}
42098d4dd3fSmikeb 
42198d4dd3fSmikeb 		switch (tdb->tdb_dst.sa.sa_family) {
42298d4dd3fSmikeb 		case AF_INET:
42398d4dd3fSmikeb 			iphlen = sizeof(struct ip);
42498d4dd3fSmikeb 			break;
42598d4dd3fSmikeb #ifdef INET6
42698d4dd3fSmikeb 		case AF_INET6:
42798d4dd3fSmikeb 			iphlen = sizeof(struct ip6_hdr);
42898d4dd3fSmikeb 			break;
42998d4dd3fSmikeb #endif /* INET6 */
43098d4dd3fSmikeb 		default:
431698a75ddSbluhm 			DPRINTF("unknown protocol family (%d)",
432698a75ddSbluhm 			    tdb->tdb_dst.sa.sa_family);
433bc489a1cSbluhm 			error = EPFNOSUPPORT;
43466ffc2ddSbluhm 			goto drop;
43598d4dd3fSmikeb 		}
43698d4dd3fSmikeb 
4375db30710Smarkus 		mi = m_makespace(m, iphlen, sizeof(struct udphdr), &roff);
438ed4bea91Smarkus 		if (mi == NULL) {
43966ffc2ddSbluhm 			error = ENOMEM;
44066ffc2ddSbluhm 			goto drop;
441ed4bea91Smarkus 		}
4425db30710Smarkus 		uh = (struct udphdr *)(mtod(mi, caddr_t) + roff);
443ed4bea91Smarkus 		uh->uh_sport = uh->uh_dport = htons(udpencap_port);
444ed4bea91Smarkus 		if (tdb->tdb_udpencap_port)
445ed4bea91Smarkus 			uh->uh_dport = tdb->tdb_udpencap_port;
446ed4bea91Smarkus 
44798d4dd3fSmikeb 		uh->uh_ulen = htons(m->m_pkthdr.len - iphlen);
448ed4bea91Smarkus 		uh->uh_sum = 0;
44998d4dd3fSmikeb #ifdef INET6
45098d4dd3fSmikeb 		if (tdb->tdb_dst.sa.sa_family == AF_INET6)
45198d4dd3fSmikeb 			m->m_pkthdr.csum_flags |= M_UDP_CSUM_OUT;
45298d4dd3fSmikeb #endif /* INET6 */
4536460338bSvisa 		espstat_inc(esps_udpencout);
454ed4bea91Smarkus 	}
455ed4bea91Smarkus 
4566830693bSangelos 	switch (tdb->tdb_dst.sa.sa_family) {
457b1efc16cSangelos 	case AF_INET:
4586830693bSangelos 		/* Fix the header length, for AH processing. */
459b1efc16cSangelos 		ip = mtod(m, struct ip *);
460b1efc16cSangelos 		ip->ip_len = htons(m->m_pkthdr.len);
461ed4bea91Smarkus 		if ((tdb->tdb_flags & TDBF_UDPENCAP) != 0)
462ed4bea91Smarkus 			ip->ip_p = IPPROTO_UDP;
463b1efc16cSangelos 		break;
464b1efc16cSangelos 
465b1efc16cSangelos #ifdef INET6
466b1efc16cSangelos 	case AF_INET6:
4676830693bSangelos 		/* Fix the header length, for AH processing. */
468b1efc16cSangelos 		if (m->m_pkthdr.len < sizeof(*ip6)) {
46966ffc2ddSbluhm 			error = ENXIO;
47066ffc2ddSbluhm 			goto drop;
471b1efc16cSangelos 		}
472b1efc16cSangelos 		if (m->m_pkthdr.len - sizeof(*ip6) > IPV6_MAXPACKET) {
4736830693bSangelos 			/* No jumbogram support. */
47466ffc2ddSbluhm 			error = ENXIO;
47566ffc2ddSbluhm 			goto drop;
476b1efc16cSangelos 		}
477b1efc16cSangelos 		ip6 = mtod(m, struct ip6_hdr *);
478b1efc16cSangelos 		ip6->ip6_plen = htons(m->m_pkthdr.len - sizeof(*ip6));
479ed4bea91Smarkus 		if ((tdb->tdb_flags & TDBF_UDPENCAP) != 0)
480ed4bea91Smarkus 			ip6->ip6_nxt = IPPROTO_UDP;
481b1efc16cSangelos 		break;
482b1efc16cSangelos #endif /* INET6 */
483b1efc16cSangelos 
484b1efc16cSangelos 	default:
485698a75ddSbluhm 		DPRINTF("unknown protocol family (%d)",
486698a75ddSbluhm 		    tdb->tdb_dst.sa.sa_family);
487bc489a1cSbluhm 		error = EPFNOSUPPORT;
48866ffc2ddSbluhm 		goto drop;
489b1efc16cSangelos 	}
490b1efc16cSangelos 
491ed834ee5Sangelos 	/*
492ed834ee5Sangelos 	 * Add a record of what we've done or what needs to be done to the
493ed834ee5Sangelos 	 * packet.
494ed834ee5Sangelos 	 */
4950245ebf4Smikeb 	mtag = m_tag_get(PACKET_TAG_IPSEC_OUT_DONE, sizeof(struct tdb_ident),
49602755a9fSangelos 	    M_NOWAIT);
4976830693bSangelos 	if (mtag == NULL) {
498698a75ddSbluhm 		DPRINTF("could not allocate packet tag");
49966ffc2ddSbluhm 		error = ENOMEM;
50066ffc2ddSbluhm 		goto drop;
50102755a9fSangelos 	}
502ed834ee5Sangelos 
50302755a9fSangelos 	tdbi = (struct tdb_ident *)(mtag + 1);
504d9c935f8Sdhill 	tdbi->dst = tdb->tdb_dst;
50502755a9fSangelos 	tdbi->proto = tdb->tdb_sproto;
50602755a9fSangelos 	tdbi->spi = tdb->tdb_spi;
50705d65ec4Sreyk 	tdbi->rdomain = tdb->tdb_rdomain;
50802755a9fSangelos 
50902755a9fSangelos 	m_tag_prepend(m, mtag);
51002755a9fSangelos 
511e4b51ed2Sbluhm 	ipsecstat_pkt(ipsec_opackets, ipsec_obytes, m->m_pkthdr.len);
512d997d144Smvs 	tdbstat_pkt(tdb, tdb_opackets, tdb_obytes, m->m_pkthdr.len);
5132edaa7baSmpi 
5146830693bSangelos 	/* If there's another (bundled) TDB to apply, do so. */
5154b0e5db3Sbluhm 	tdbo = tdb_ref(tdb->tdb_onext);
5164b0e5db3Sbluhm 	if (tdbo != NULL) {
5175ee194bcSbluhm 		KERNEL_ASSERT_LOCKED();
5184b0e5db3Sbluhm 		error = ipsp_process_packet(m, tdbo,
5196830693bSangelos 		    tdb->tdb_dst.sa.sa_family, 0);
5204b0e5db3Sbluhm 		tdb_unref(tdbo);
5214b0e5db3Sbluhm 		return error;
5224b0e5db3Sbluhm 	}
5234a6401e9Sangelos 
524aa920ac7Sreyk #if NPF > 0
525aa920ac7Sreyk 	/* Add pf tag if requested. */
526f1030b20Sbluhm 	pf_tag_packet(m, tdb->tdb_tag, -1);
527052ea9d2Shenning 	pf_pkt_addr_changed(m);
528aa920ac7Sreyk #endif
529652dbf1bStobhe 	if (tdb->tdb_rdomain != tdb->tdb_rdomain_post)
530652dbf1bStobhe 		m->m_pkthdr.ph_rtableid = tdb->tdb_rdomain_post;
531aa920ac7Sreyk 
532b1efc16cSangelos 	/*
533b1efc16cSangelos 	 * We're done with IPsec processing, transmit the packet using the
534b1efc16cSangelos 	 * appropriate network protocol (IP or IPv6). SPD lookup will be
535b1efc16cSangelos 	 * performed again there.
536b1efc16cSangelos 	 */
5376830693bSangelos 	switch (tdb->tdb_dst.sa.sa_family) {
538b1efc16cSangelos 	case AF_INET:
539bc489a1cSbluhm 		error = ip_output(m, NULL, NULL, IP_RAWOUTPUT, NULL, NULL, 0);
540bc489a1cSbluhm 		break;
541b1efc16cSangelos #ifdef INET6
542b1efc16cSangelos 	case AF_INET6:
543b1efc16cSangelos 		/*
544b1efc16cSangelos 		 * We don't need massage, IPv6 header fields are always in
5456830693bSangelos 		 * net endian.
546b1efc16cSangelos 		 */
547bc489a1cSbluhm 		error = ip6_output(m, NULL, NULL, 0, NULL, NULL);
548bc489a1cSbluhm 		break;
549b1efc16cSangelos #endif /* INET6 */
550bc489a1cSbluhm 	default:
551bc489a1cSbluhm 		error = EPFNOSUPPORT;
552bc489a1cSbluhm 		break;
553b1efc16cSangelos 	}
554bc489a1cSbluhm 	return error;
55566ffc2ddSbluhm 
55666ffc2ddSbluhm  drop:
55766ffc2ddSbluhm 	m_freem(m);
55866ffc2ddSbluhm 	return error;
559b1efc16cSangelos }
5606d7c3229Sprovos 
5616d7c3229Sprovos ssize_t
5626d7c3229Sprovos ipsec_hdrsz(struct tdb *tdbp)
5636d7c3229Sprovos {
5646d7c3229Sprovos 	ssize_t adjust;
5656d7c3229Sprovos 
5666d7c3229Sprovos 	switch (tdbp->tdb_sproto) {
5678a510a1cSmarkus 	case IPPROTO_IPIP:
5688a510a1cSmarkus 		adjust = 0;
5698a510a1cSmarkus 		break;
5708a510a1cSmarkus 
5716d7c3229Sprovos 	case IPPROTO_ESP:
5726d7c3229Sprovos 		if (tdbp->tdb_encalgxform == NULL)
5736d7c3229Sprovos 			return (-1);
5746d7c3229Sprovos 
5756d7c3229Sprovos 		/* Header length */
5766d7c3229Sprovos 		adjust = 2 * sizeof(u_int32_t) + tdbp->tdb_ivlen;
5773bea581aSmarkus 		if (tdbp->tdb_flags & TDBF_UDPENCAP)
5783bea581aSmarkus 			adjust += sizeof(struct udphdr);
5796d7c3229Sprovos 		/* Authenticator */
5806d7c3229Sprovos 		if (tdbp->tdb_authalgxform != NULL)
5818688c78cSmarkus 			adjust += tdbp->tdb_authalgxform->authsize;
5826d7c3229Sprovos 		/* Padding */
583d0bff1deSmarkus 		adjust += MAX(4, tdbp->tdb_encalgxform->blocksize);
5846d7c3229Sprovos 		break;
5856d7c3229Sprovos 
5866d7c3229Sprovos 	case IPPROTO_AH:
5876d7c3229Sprovos 		if (tdbp->tdb_authalgxform == NULL)
5886d7c3229Sprovos 			return (-1);
5896d7c3229Sprovos 
5906d7c3229Sprovos 		adjust = AH_FLENGTH + sizeof(u_int32_t);
5916d7c3229Sprovos 		adjust += tdbp->tdb_authalgxform->authsize;
5926d7c3229Sprovos 		break;
5936d7c3229Sprovos 
5946d7c3229Sprovos 	default:
5956d7c3229Sprovos 		return (-1);
5966d7c3229Sprovos 	}
5976d7c3229Sprovos 
5986d7c3229Sprovos 	if (!(tdbp->tdb_flags & TDBF_TUNNELING) &&
5996d7c3229Sprovos 	    !(tdbp->tdb_flags & TDBF_USEDTUNNEL))
6006d7c3229Sprovos 		return (adjust);
6016d7c3229Sprovos 
6026d7c3229Sprovos 	switch (tdbp->tdb_dst.sa.sa_family) {
6036d7c3229Sprovos 	case AF_INET:
6046d7c3229Sprovos 		adjust += sizeof(struct ip);
6056d7c3229Sprovos 		break;
6066d7c3229Sprovos #ifdef INET6
6076d7c3229Sprovos 	case AF_INET6:
6086d7c3229Sprovos 		adjust += sizeof(struct ip6_hdr);
6096d7c3229Sprovos 		break;
6106d7c3229Sprovos #endif /* INET6 */
6116d7c3229Sprovos 	}
6126d7c3229Sprovos 
6136d7c3229Sprovos 	return (adjust);
6146d7c3229Sprovos }
6156d7c3229Sprovos 
6166d7c3229Sprovos void
6176d7c3229Sprovos ipsec_adjust_mtu(struct mbuf *m, u_int32_t mtu)
6186d7c3229Sprovos {
6196d7c3229Sprovos 	struct tdb_ident *tdbi;
6206d7c3229Sprovos 	struct tdb *tdbp;
6216d7c3229Sprovos 	struct m_tag *mtag;
6226d7c3229Sprovos 	ssize_t adjust;
6236d7c3229Sprovos 
62466736630Smpi 	NET_ASSERT_LOCKED();
6256d7c3229Sprovos 
6266d7c3229Sprovos 	for (mtag = m_tag_find(m, PACKET_TAG_IPSEC_OUT_DONE, NULL); mtag;
6276d7c3229Sprovos 	     mtag = m_tag_find(m, PACKET_TAG_IPSEC_OUT_DONE, mtag)) {
6286d7c3229Sprovos 		tdbi = (struct tdb_ident *)(mtag + 1);
62905d65ec4Sreyk 		tdbp = gettdb(tdbi->rdomain, tdbi->spi, &tdbi->dst,
63005d65ec4Sreyk 		    tdbi->proto);
6316d7c3229Sprovos 		if (tdbp == NULL)
6326d7c3229Sprovos 			break;
6336d7c3229Sprovos 
6344b0e5db3Sbluhm 		if ((adjust = ipsec_hdrsz(tdbp)) == -1) {
6354b0e5db3Sbluhm 			tdb_unref(tdbp);
6366d7c3229Sprovos 			break;
6374b0e5db3Sbluhm 		}
6386d7c3229Sprovos 
6396d7c3229Sprovos 		mtu -= adjust;
6406d7c3229Sprovos 		tdbp->tdb_mtu = mtu;
6413209772dScheloha 		tdbp->tdb_mtutimeout = gettime() + ip_mtudisc_timeout;
642698a75ddSbluhm 		DPRINTF("spi %08x mtu %d adjust %ld mbuf %p",
643698a75ddSbluhm 		    ntohl(tdbp->tdb_spi), tdbp->tdb_mtu, adjust, m);
6444b0e5db3Sbluhm 		tdb_unref(tdbp);
6456d7c3229Sprovos 	}
6466d7c3229Sprovos }
647