1 /* $OpenBSD: ip6_divert.c,v 1.59 2019/02/04 21:40:52 bluhm Exp $ */ 2 3 /* 4 * Copyright (c) 2009 Michele Marchetto <michele@openbsd.org> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 #include <sys/param.h> 20 #include <sys/systm.h> 21 #include <sys/mbuf.h> 22 #include <sys/protosw.h> 23 #include <sys/socket.h> 24 #include <sys/socketvar.h> 25 #include <sys/sysctl.h> 26 27 #include <net/if.h> 28 #include <net/route.h> 29 #include <net/if_var.h> 30 #include <net/netisr.h> 31 32 #include <netinet/in.h> 33 #include <netinet/ip.h> 34 #include <netinet/ip_var.h> 35 #include <netinet/in_pcb.h> 36 #include <netinet/ip6.h> 37 #include <netinet6/in6_var.h> 38 #include <netinet6/ip6_divert.h> 39 #include <netinet/tcp.h> 40 #include <netinet/udp.h> 41 #include <netinet/icmp6.h> 42 43 #include <net/pfvar.h> 44 45 struct inpcbtable divb6table; 46 struct cpumem *div6counters; 47 48 #ifndef DIVERT_SENDSPACE 49 #define DIVERT_SENDSPACE (65536 + 100) 50 #endif 51 u_int divert6_sendspace = DIVERT_SENDSPACE; 52 #ifndef DIVERT_RECVSPACE 53 #define DIVERT_RECVSPACE (65536 + 100) 54 #endif 55 u_int divert6_recvspace = DIVERT_RECVSPACE; 56 57 #ifndef DIVERTHASHSIZE 58 #define DIVERTHASHSIZE 128 59 #endif 60 61 int *divert6ctl_vars[DIVERT6CTL_MAXID] = DIVERT6CTL_VARS; 62 63 int divb6hashsize = DIVERTHASHSIZE; 64 65 int divert6_output(struct inpcb *, struct mbuf *, struct mbuf *, 66 struct mbuf *); 67 68 void 69 divert6_init(void) 70 { 71 in_pcbinit(&divb6table, divb6hashsize); 72 div6counters = counters_alloc(div6s_ncounters); 73 } 74 75 int 76 divert6_output(struct inpcb *inp, struct mbuf *m, struct mbuf *nam, 77 struct mbuf *control) 78 { 79 struct sockaddr_in6 *sin6; 80 int error, min_hdrlen, nxt, off, dir; 81 struct ip6_hdr *ip6; 82 83 m_freem(control); 84 85 if ((error = in6_nam2sin6(nam, &sin6))) 86 goto fail; 87 88 /* Do basic sanity checks. */ 89 if (m->m_pkthdr.len < sizeof(struct ip6_hdr)) 90 goto fail; 91 if ((m = m_pullup(m, sizeof(struct ip6_hdr))) == NULL) { 92 /* m_pullup() has freed the mbuf, so just return. */ 93 div6stat_inc(div6s_errors); 94 return (ENOBUFS); 95 } 96 ip6 = mtod(m, struct ip6_hdr *); 97 if ((ip6->ip6_vfc & IPV6_VERSION_MASK) != IPV6_VERSION) 98 goto fail; 99 if (m->m_pkthdr.len < sizeof(struct ip6_hdr) + ntohs(ip6->ip6_plen)) 100 goto fail; 101 102 /* 103 * Recalculate the protocol checksum since the userspace application 104 * may have modified the packet prior to reinjection. 105 */ 106 off = ip6_lasthdr(m, 0, IPPROTO_IPV6, &nxt); 107 if (off < sizeof(struct ip6_hdr)) 108 goto fail; 109 110 dir = (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr) ? PF_OUT : PF_IN); 111 112 switch (nxt) { 113 case IPPROTO_TCP: 114 min_hdrlen = sizeof(struct tcphdr); 115 m->m_pkthdr.csum_flags |= M_TCP_CSUM_OUT; 116 break; 117 case IPPROTO_UDP: 118 min_hdrlen = sizeof(struct udphdr); 119 m->m_pkthdr.csum_flags |= M_UDP_CSUM_OUT; 120 break; 121 case IPPROTO_ICMPV6: 122 min_hdrlen = sizeof(struct icmp6_hdr); 123 m->m_pkthdr.csum_flags |= M_ICMP_CSUM_OUT; 124 break; 125 default: 126 min_hdrlen = 0; 127 break; 128 } 129 if (min_hdrlen && m->m_pkthdr.len < off + min_hdrlen) 130 goto fail; 131 132 m->m_pkthdr.pf.flags |= PF_TAG_DIVERTED_PACKET; 133 134 if (dir == PF_IN) { 135 struct rtentry *rt; 136 struct ifnet *ifp; 137 138 rt = rtalloc(sin6tosa(sin6), 0, inp->inp_rtableid); 139 if (!rtisvalid(rt) || !ISSET(rt->rt_flags, RTF_LOCAL)) { 140 rtfree(rt); 141 error = EADDRNOTAVAIL; 142 goto fail; 143 } 144 m->m_pkthdr.ph_ifidx = rt->rt_ifidx; 145 rtfree(rt); 146 147 /* 148 * Recalculate the protocol checksum for the inbound packet 149 * since the userspace application may have modified the packet 150 * prior to reinjection. 151 */ 152 in6_proto_cksum_out(m, NULL); 153 154 ifp = if_get(m->m_pkthdr.ph_ifidx); 155 if (ifp == NULL) { 156 error = ENETDOWN; 157 goto fail; 158 } 159 ipv6_input(ifp, m); 160 if_put(ifp); 161 } else { 162 m->m_pkthdr.ph_rtableid = inp->inp_rtableid; 163 164 error = ip6_output(m, NULL, &inp->inp_route6, 165 IP_ALLOWBROADCAST | IP_RAWOUTPUT, NULL, NULL); 166 } 167 168 div6stat_inc(div6s_opackets); 169 return (error); 170 171 fail: 172 div6stat_inc(div6s_errors); 173 m_freem(m); 174 return (error ? error : EINVAL); 175 } 176 177 int 178 divert6_packet(struct mbuf *m, int dir, u_int16_t divert_port) 179 { 180 struct inpcb *inp; 181 struct socket *sa = NULL; 182 struct sockaddr_in6 addr; 183 184 inp = NULL; 185 div6stat_inc(div6s_ipackets); 186 187 if (m->m_len < sizeof(struct ip6_hdr) && 188 (m = m_pullup(m, sizeof(struct ip6_hdr))) == NULL) { 189 div6stat_inc(div6s_errors); 190 return (0); 191 } 192 193 TAILQ_FOREACH(inp, &divb6table.inpt_queue, inp_queue) { 194 if (inp->inp_lport == divert_port) 195 break; 196 } 197 198 memset(&addr, 0, sizeof(addr)); 199 addr.sin6_family = AF_INET6; 200 addr.sin6_len = sizeof(addr); 201 202 if (dir == PF_IN) { 203 struct ifaddr *ifa; 204 struct ifnet *ifp; 205 206 ifp = if_get(m->m_pkthdr.ph_ifidx); 207 if (ifp == NULL) { 208 m_freem(m); 209 return (0); 210 } 211 TAILQ_FOREACH(ifa, &ifp->if_addrlist, ifa_list) { 212 if (ifa->ifa_addr->sa_family != AF_INET6) 213 continue; 214 addr.sin6_addr = satosin6(ifa->ifa_addr)->sin6_addr; 215 break; 216 } 217 if_put(ifp); 218 } 219 220 if (inp) { 221 sa = inp->inp_socket; 222 if (sbappendaddr(sa, &sa->so_rcv, sin6tosa(&addr), m, NULL) == 0) { 223 div6stat_inc(div6s_fullsock); 224 m_freem(m); 225 return (0); 226 } else { 227 KERNEL_LOCK(); 228 sorwakeup(inp->inp_socket); 229 KERNEL_UNLOCK(); 230 } 231 } 232 233 if (sa == NULL) { 234 div6stat_inc(div6s_noport); 235 m_freem(m); 236 } 237 return (0); 238 } 239 240 /*ARGSUSED*/ 241 int 242 divert6_usrreq(struct socket *so, int req, struct mbuf *m, struct mbuf *addr, 243 struct mbuf *control, struct proc *p) 244 { 245 struct inpcb *inp = sotoinpcb(so); 246 int error = 0; 247 248 if (req == PRU_CONTROL) { 249 return (in6_control(so, (u_long)m, (caddr_t)addr, 250 (struct ifnet *)control)); 251 } 252 253 soassertlocked(so); 254 255 if (inp == NULL) { 256 error = EINVAL; 257 goto release; 258 } 259 switch (req) { 260 261 case PRU_BIND: 262 error = in_pcbbind(inp, addr, p); 263 break; 264 265 case PRU_SHUTDOWN: 266 socantsendmore(so); 267 break; 268 269 case PRU_SEND: 270 return (divert6_output(inp, m, addr, control)); 271 272 case PRU_ABORT: 273 soisdisconnected(so); 274 in_pcbdetach(inp); 275 break; 276 277 case PRU_SOCKADDR: 278 in6_setsockaddr(inp, addr); 279 break; 280 281 case PRU_PEERADDR: 282 in6_setpeeraddr(inp, addr); 283 break; 284 285 case PRU_SENSE: 286 break; 287 288 case PRU_LISTEN: 289 case PRU_CONNECT: 290 case PRU_CONNECT2: 291 case PRU_ACCEPT: 292 case PRU_DISCONNECT: 293 case PRU_SENDOOB: 294 case PRU_FASTTIMO: 295 case PRU_SLOWTIMO: 296 case PRU_PROTORCV: 297 case PRU_PROTOSEND: 298 case PRU_RCVD: 299 case PRU_RCVOOB: 300 error = EOPNOTSUPP; 301 break; 302 303 default: 304 panic("divert6_usrreq"); 305 } 306 307 release: 308 if (req != PRU_RCVD && req != PRU_RCVOOB && req != PRU_SENSE) { 309 m_freem(control); 310 m_freem(m); 311 } 312 return (error); 313 } 314 315 int 316 divert6_attach(struct socket *so, int proto) 317 { 318 int error; 319 320 if (so->so_pcb != NULL) 321 return EINVAL; 322 323 if ((so->so_state & SS_PRIV) == 0) 324 return EACCES; 325 326 error = in_pcballoc(so, &divb6table); 327 if (error) 328 return (error); 329 330 error = soreserve(so, divert6_sendspace, divert6_recvspace); 331 if (error) 332 return (error); 333 sotoinpcb(so)->inp_flags |= INP_HDRINCL; 334 return (0); 335 } 336 337 int 338 divert6_detach(struct socket *so) 339 { 340 struct inpcb *inp = sotoinpcb(so); 341 342 soassertlocked(so); 343 344 if (inp == NULL) 345 return (EINVAL); 346 347 in_pcbdetach(inp); 348 349 return (0); 350 } 351 352 int 353 divert6_sysctl_div6stat(void *oldp, size_t *oldlenp, void *newp) 354 { 355 uint64_t counters[div6s_ncounters]; 356 struct div6stat div6stat; 357 u_long *words = (u_long *)&div6stat; 358 int i; 359 360 CTASSERT(sizeof(div6stat) == (nitems(counters) * sizeof(u_long))); 361 362 counters_read(div6counters, counters, nitems(counters)); 363 364 for (i = 0; i < nitems(counters); i++) 365 words[i] = (u_long)counters[i]; 366 367 return (sysctl_rdstruct(oldp, oldlenp, newp, 368 &div6stat, sizeof(div6stat))); 369 } 370 371 /* 372 * Sysctl for divert variables. 373 */ 374 int 375 divert6_sysctl(int *name, u_int namelen, void *oldp, size_t *oldlenp, 376 void *newp, size_t newlen) 377 { 378 int error; 379 380 /* All sysctl names at this level are terminal. */ 381 if (namelen != 1) 382 return (ENOTDIR); 383 384 switch (name[0]) { 385 case DIVERT6CTL_SENDSPACE: 386 NET_LOCK(); 387 error = sysctl_int(oldp, oldlenp, newp, newlen, 388 &divert6_sendspace); 389 NET_UNLOCK(); 390 return (error); 391 case DIVERT6CTL_RECVSPACE: 392 NET_LOCK(); 393 error = sysctl_int(oldp, oldlenp, newp, newlen, 394 &divert6_recvspace); 395 NET_UNLOCK(); 396 return (error); 397 case DIVERT6CTL_STATS: 398 return (divert6_sysctl_div6stat(oldp, oldlenp, newp)); 399 default: 400 if (name[0] < DIVERT6CTL_MAXID) { 401 NET_LOCK(); 402 error = sysctl_int_arr(divert6ctl_vars, name, namelen, 403 oldp, oldlenp, newp, newlen); 404 NET_UNLOCK(); 405 return (error); 406 } 407 408 return (ENOPROTOOPT); 409 } 410 /* NOTREACHED */ 411 } 412