1 /*- 2 * Copyright (c) 2016 Andrey V. Elsukov <ae@FreeBSD.org> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 16 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 17 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 18 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 19 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 20 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 21 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 22 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 24 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27 #include <sys/cdefs.h> 28 __FBSDID("$FreeBSD$"); 29 30 #include "opt_inet.h" 31 #include "opt_ipsec.h" 32 33 #include <sys/param.h> 34 #include <sys/systm.h> 35 #include <sys/kernel.h> 36 #include <sys/lock.h> 37 #include <sys/mbuf.h> 38 #include <sys/protosw.h> 39 #include <sys/socket.h> 40 #include <sys/sockopt.h> 41 #include <sys/sysctl.h> 42 43 #include <netinet/in.h> 44 #include <netinet/in_pcb.h> 45 #include <netinet/in_systm.h> 46 #include <netinet/ip.h> 47 #include <netinet/ip_var.h> 48 #include <netinet/tcp.h> 49 #include <netinet/udp.h> 50 #include <netinet/udp_var.h> 51 52 #include <net/vnet.h> 53 54 #include <netipsec/ipsec.h> 55 #include <netipsec/esp.h> 56 #include <netipsec/esp_var.h> 57 #include <netipsec/xform.h> 58 59 #include <netipsec/key.h> 60 #include <netipsec/key_debug.h> 61 #include <netipsec/ipsec_support.h> 62 #include <machine/in_cksum.h> 63 64 /* 65 * Handle UDP_ENCAP socket option. Always return with released INP_WLOCK. 66 */ 67 int 68 udp_ipsec_pcbctl(struct inpcb *inp, struct sockopt *sopt) 69 { 70 struct udpcb *up; 71 int error, optval; 72 73 INP_WLOCK_ASSERT(inp); 74 if (sopt->sopt_name != UDP_ENCAP) { 75 INP_WUNLOCK(inp); 76 return (ENOPROTOOPT); 77 } 78 79 up = intoudpcb(inp); 80 if (sopt->sopt_dir == SOPT_GET) { 81 if (up->u_flags & UF_ESPINUDP) 82 optval = UDP_ENCAP_ESPINUDP; 83 else 84 optval = 0; 85 INP_WUNLOCK(inp); 86 return (sooptcopyout(sopt, &optval, sizeof(optval))); 87 } 88 INP_WUNLOCK(inp); 89 90 error = sooptcopyin(sopt, &optval, sizeof(optval), sizeof(optval)); 91 if (error != 0) 92 return (error); 93 94 INP_WLOCK(inp); 95 switch (optval) { 96 case 0: 97 up->u_flags &= ~UF_ESPINUDP; 98 break; 99 case UDP_ENCAP_ESPINUDP: 100 up->u_flags |= UF_ESPINUDP; 101 break; 102 default: 103 error = EINVAL; 104 } 105 INP_WUNLOCK(inp); 106 return (error); 107 } 108 109 /* 110 * Potentially decap ESP in UDP frame. Check for an ESP header. 111 * If present, strip the UDP header and push the result through IPSec. 112 * 113 * Returns error if mbuf consumed and/or processed, otherwise 0. 114 */ 115 int 116 udp_ipsec_input(struct mbuf *m, int off, int af) 117 { 118 union sockaddr_union dst; 119 struct secasvar *sav; 120 struct udphdr *udp; 121 struct ip *ip; 122 uint32_t spi; 123 int hlen; 124 125 /* 126 * Just return if packet doesn't have enough data. 127 * We need at least [IP header + UDP header + ESP header]. 128 * NAT-Keepalive packet has only one byte of payload, so it 129 * by default will not be processed. 130 */ 131 if (m->m_pkthdr.len < off + sizeof(struct esp)) 132 return (0); 133 134 m_copydata(m, off, sizeof(uint32_t), (caddr_t)&spi); 135 if (spi == 0) /* Non-ESP marker. */ 136 return (0); 137 138 /* 139 * Find SA and check that it is configured for UDP 140 * encapsulation. 141 */ 142 bzero(&dst, sizeof(dst)); 143 dst.sa.sa_family = af; 144 switch (af) { 145 #ifdef INET 146 case AF_INET: 147 dst.sin.sin_len = sizeof(struct sockaddr_in); 148 ip = mtod(m, struct ip *); 149 ip->ip_p = IPPROTO_ESP; 150 off = offsetof(struct ip, ip_p); 151 hlen = ip->ip_hl << 2; 152 dst.sin.sin_addr = ip->ip_dst; 153 break; 154 #endif 155 #ifdef INET6 156 case AF_INET6: 157 /* Not yet */ 158 /* FALLTHROUGH */ 159 #endif 160 default: 161 ESPSTAT_INC(esps_nopf); 162 m_freem(m); 163 return (EPFNOSUPPORT); 164 } 165 166 sav = key_allocsa(&dst, IPPROTO_ESP, spi); 167 if (sav == NULL) { 168 ESPSTAT_INC(esps_notdb); 169 m_freem(m); 170 return (ENOENT); 171 } 172 udp = mtodo(m, hlen); 173 if (sav->natt == NULL || 174 sav->natt->sport != udp->uh_sport || 175 sav->natt->dport != udp->uh_dport) { 176 /* XXXAE: should we check source address? */ 177 ESPSTAT_INC(esps_notdb); 178 key_freesav(&sav); 179 m_freem(m); 180 return (ENOENT); 181 } 182 /* 183 * Remove the UDP header 184 * Before: 185 * <--- off ---> 186 * +----+------+-----+ 187 * | IP | UDP | ESP | 188 * +----+------+-----+ 189 * <-skip-> 190 * After: 191 * +----+-----+ 192 * | IP | ESP | 193 * +----+-----+ 194 * <-skip-> 195 */ 196 m_striphdr(m, hlen, sizeof(*udp)); 197 /* 198 * We cannot yet update the cksums so clear any h/w cksum flags 199 * as they are no longer valid. 200 */ 201 if (m->m_pkthdr.csum_flags & CSUM_DATA_VALID) 202 m->m_pkthdr.csum_flags &= ~(CSUM_DATA_VALID | CSUM_PSEUDO_HDR); 203 /* 204 * We can update ip_len and ip_sum here, but ipsec4_input_cb() 205 * will do this anyway, so don't touch them here. 206 */ 207 ESPSTAT_INC(esps_input); 208 (*sav->tdb_xform->xf_input)(m, sav, hlen, off); 209 return (EINPROGRESS); /* Consumed by IPsec. */ 210 } 211 212 int 213 udp_ipsec_output(struct mbuf *m, struct secasvar *sav) 214 { 215 struct udphdr *udp; 216 struct mbuf *n; 217 struct ip *ip; 218 int hlen, off; 219 220 IPSEC_ASSERT(sav->natt != NULL, ("UDP encapsulation isn't required.")); 221 222 if (sav->sah->saidx.dst.sa.sa_family == AF_INET6) 223 return (EAFNOSUPPORT); 224 225 ip = mtod(m, struct ip *); 226 hlen = ip->ip_hl << 2; 227 n = m_makespace(m, hlen, sizeof(*udp), &off); 228 if (n == NULL) { 229 DPRINTF(("%s: m_makespace for udphdr failed\n", __func__)); 230 return (ENOBUFS); 231 } 232 233 udp = mtodo(n, off); 234 udp->uh_dport = sav->natt->dport; 235 udp->uh_sport = sav->natt->sport; 236 udp->uh_sum = 0; 237 udp->uh_ulen = htons(m->m_pkthdr.len - hlen); 238 239 ip = mtod(m, struct ip *); 240 ip->ip_len = htons(m->m_pkthdr.len); 241 ip->ip_p = IPPROTO_UDP; 242 return (0); 243 } 244 245 void 246 udp_ipsec_adjust_cksum(struct mbuf *m, struct secasvar *sav, int proto, 247 int skip) 248 { 249 struct ip *ip; 250 uint16_t cksum, off; 251 252 IPSEC_ASSERT(sav->natt != NULL, ("NAT-T isn't required")); 253 IPSEC_ASSERT(proto == IPPROTO_UDP || proto == IPPROTO_TCP, 254 ("unexpected protocol %u", proto)); 255 256 if (proto == IPPROTO_UDP) 257 off = offsetof(struct udphdr, uh_sum); 258 else 259 off = offsetof(struct tcphdr, th_sum); 260 261 if (V_natt_cksum_policy == 0) { /* auto */ 262 if (sav->natt->cksum != 0) { 263 /* Incrementally recompute. */ 264 m_copydata(m, skip + off, sizeof(cksum), 265 (caddr_t)&cksum); 266 /* Do not adjust UDP checksum if it is zero. */ 267 if (proto == IPPROTO_UDP && cksum == 0) 268 return; 269 cksum = in_addword(cksum, sav->natt->cksum); 270 } else { 271 /* No OA from IKEd. */ 272 if (proto == IPPROTO_TCP) { 273 /* Ignore for TCP. */ 274 m->m_pkthdr.csum_data = 0xffff; 275 m->m_pkthdr.csum_flags |= (CSUM_DATA_VALID | 276 CSUM_PSEUDO_HDR); 277 return; 278 } 279 cksum = 0; /* Reset for UDP. */ 280 } 281 m_copyback(m, skip + off, sizeof(cksum), (caddr_t)&cksum); 282 } else { /* Fully recompute */ 283 ip = mtod(m, struct ip *); 284 cksum = in_pseudo(ip->ip_src.s_addr, ip->ip_dst.s_addr, 285 htons(m->m_pkthdr.len - skip + proto)); 286 m_copyback(m, skip + off, sizeof(cksum), (caddr_t)&cksum); 287 m->m_pkthdr.csum_flags = 288 (proto == IPPROTO_UDP) ? CSUM_UDP: CSUM_TCP; 289 m->m_pkthdr.csum_data = off; 290 in_delayed_cksum(m); 291 m->m_pkthdr.csum_flags &= ~CSUM_DELAY_DATA; 292 } 293 } 294