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