1 /* $OpenBSD: ip_divert.c,v 1.39 2016/03/07 18:44:00 naddy 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/in_var.h> 34 #include <netinet/ip.h> 35 #include <netinet/ip_var.h> 36 #include <netinet/in_pcb.h> 37 #include <netinet/ip_divert.h> 38 #include <netinet/tcp.h> 39 #include <netinet/udp.h> 40 #include <netinet/ip_icmp.h> 41 42 #include <net/pfvar.h> 43 44 struct inpcbtable divbtable; 45 struct divstat divstat; 46 47 #ifndef DIVERT_SENDSPACE 48 #define DIVERT_SENDSPACE (65536 + 100) 49 #endif 50 u_int divert_sendspace = DIVERT_SENDSPACE; 51 #ifndef DIVERT_RECVSPACE 52 #define DIVERT_RECVSPACE (65536 + 100) 53 #endif 54 u_int divert_recvspace = DIVERT_RECVSPACE; 55 56 #ifndef DIVERTHASHSIZE 57 #define DIVERTHASHSIZE 128 58 #endif 59 60 int *divertctl_vars[DIVERTCTL_MAXID] = DIVERTCTL_VARS; 61 62 int divbhashsize = DIVERTHASHSIZE; 63 64 static struct sockaddr_in ipaddr = { sizeof(ipaddr), AF_INET }; 65 66 void divert_detach(struct inpcb *); 67 int divert_output(struct inpcb *, struct mbuf *, struct mbuf *, 68 struct mbuf *); 69 void 70 divert_init(void) 71 { 72 in_pcbinit(&divbtable, divbhashsize); 73 } 74 75 void 76 divert_input(struct mbuf *m, ...) 77 { 78 m_freem(m); 79 } 80 81 int 82 divert_output(struct inpcb *inp, struct mbuf *m, struct mbuf *nam, 83 struct mbuf *control) 84 { 85 struct sockaddr_in *sin; 86 struct socket *so; 87 struct ifaddr *ifa; 88 int error = 0, min_hdrlen = 0, dir; 89 struct ip *ip; 90 u_int16_t off; 91 92 m->m_pkthdr.ph_ifidx = 0; 93 m->m_nextpkt = NULL; 94 m->m_pkthdr.ph_rtableid = inp->inp_rtableid; 95 96 m_freem(control); 97 98 sin = mtod(nam, struct sockaddr_in *); 99 so = inp->inp_socket; 100 101 /* Do basic sanity checks. */ 102 if (m->m_pkthdr.len < sizeof(struct ip)) 103 goto fail; 104 if ((m = m_pullup(m, sizeof(struct ip))) == NULL) { 105 /* m_pullup() has freed the mbuf, so just return. */ 106 divstat.divs_errors++; 107 return (ENOBUFS); 108 } 109 ip = mtod(m, struct ip *); 110 if (ip->ip_v != IPVERSION) 111 goto fail; 112 off = ip->ip_hl << 2; 113 if (off < sizeof(struct ip) || ntohs(ip->ip_len) < off || 114 m->m_pkthdr.len < ntohs(ip->ip_len)) 115 goto fail; 116 117 dir = (sin->sin_addr.s_addr == INADDR_ANY ? PF_OUT : PF_IN); 118 119 switch (ip->ip_p) { 120 case IPPROTO_TCP: 121 min_hdrlen = sizeof(struct tcphdr); 122 m->m_pkthdr.csum_flags |= M_TCP_CSUM_OUT; 123 break; 124 case IPPROTO_UDP: 125 min_hdrlen = sizeof(struct udphdr); 126 m->m_pkthdr.csum_flags |= M_UDP_CSUM_OUT; 127 break; 128 case IPPROTO_ICMP: 129 min_hdrlen = ICMP_MINLEN; 130 m->m_pkthdr.csum_flags |= M_ICMP_CSUM_OUT; 131 break; 132 default: 133 /* nothing */ 134 break; 135 } 136 if (min_hdrlen && m->m_pkthdr.len < off + min_hdrlen) 137 goto fail; 138 139 m->m_pkthdr.pf.flags |= PF_TAG_DIVERTED_PACKET; 140 141 if (dir == PF_IN) { 142 ipaddr.sin_addr = sin->sin_addr; 143 ifa = ifa_ifwithaddr(sintosa(&ipaddr), m->m_pkthdr.ph_rtableid); 144 if (ifa == NULL) { 145 error = EADDRNOTAVAIL; 146 goto fail; 147 } 148 m->m_pkthdr.ph_ifidx = ifa->ifa_ifp->if_index; 149 150 /* 151 * Recalculate IP and protocol checksums for the inbound packet 152 * since the userspace application may have modified the packet 153 * prior to reinjection. 154 */ 155 ip->ip_sum = 0; 156 ip->ip_sum = in_cksum(m, off); 157 in_proto_cksum_out(m, NULL); 158 159 niq_enqueue(&ipintrq, m); 160 } else { 161 error = ip_output(m, NULL, &inp->inp_route, 162 IP_ALLOWBROADCAST | IP_RAWOUTPUT, NULL, NULL, 0); 163 if (error == EACCES) /* translate pf(4) error for userland */ 164 error = EHOSTUNREACH; 165 } 166 167 divstat.divs_opackets++; 168 return (error); 169 170 fail: 171 m_freem(m); 172 divstat.divs_errors++; 173 return (error ? error : EINVAL); 174 } 175 176 int 177 divert_packet(struct mbuf *m, int dir, u_int16_t divert_port) 178 { 179 struct inpcb *inp; 180 struct socket *sa = NULL; 181 struct sockaddr_in addr; 182 183 inp = NULL; 184 divstat.divs_ipackets++; 185 186 if (m->m_len < sizeof(struct ip) && 187 (m = m_pullup(m, sizeof(struct ip))) == NULL) { 188 divstat.divs_errors++; 189 return (0); 190 } 191 192 TAILQ_FOREACH(inp, &divbtable.inpt_queue, inp_queue) { 193 if (inp->inp_lport != divert_port) 194 continue; 195 if (inp->inp_divertfl == 0) 196 break; 197 if (dir == PF_IN && !(inp->inp_divertfl & IPPROTO_DIVERT_RESP)) 198 return (-1); 199 if (dir == PF_OUT && !(inp->inp_divertfl & IPPROTO_DIVERT_INIT)) 200 return (-1); 201 break; 202 } 203 204 memset(&addr, 0, sizeof(addr)); 205 addr.sin_family = AF_INET; 206 addr.sin_len = sizeof(addr); 207 208 if (dir == PF_IN) { 209 struct ifaddr *ifa; 210 struct ifnet *ifp; 211 212 ifp = if_get(m->m_pkthdr.ph_ifidx); 213 if (ifp == NULL) { 214 m_freem(m); 215 return (0); 216 } 217 TAILQ_FOREACH(ifa, &ifp->if_addrlist, ifa_list) { 218 if (ifa->ifa_addr->sa_family != AF_INET) 219 continue; 220 addr.sin_addr.s_addr = satosin( 221 ifa->ifa_addr)->sin_addr.s_addr; 222 break; 223 } 224 if_put(ifp); 225 } 226 227 if (inp) { 228 sa = inp->inp_socket; 229 if (sbappendaddr(&sa->so_rcv, sintosa(&addr), m, NULL) == 0) { 230 divstat.divs_fullsock++; 231 m_freem(m); 232 return (0); 233 } else 234 sorwakeup(inp->inp_socket); 235 } 236 237 if (sa == NULL) { 238 divstat.divs_noport++; 239 m_freem(m); 240 } 241 return (0); 242 } 243 244 /*ARGSUSED*/ 245 int 246 divert_usrreq(struct socket *so, int req, struct mbuf *m, struct mbuf *addr, 247 struct mbuf *control, struct proc *p) 248 { 249 struct inpcb *inp = sotoinpcb(so); 250 int error = 0; 251 int s; 252 253 if (req == PRU_CONTROL) { 254 return (in_control(so, (u_long)m, (caddr_t)addr, 255 (struct ifnet *)control)); 256 } 257 if (inp == NULL && req != PRU_ATTACH) { 258 error = EINVAL; 259 goto release; 260 } 261 switch (req) { 262 263 case PRU_ATTACH: 264 if (inp != NULL) { 265 error = EINVAL; 266 break; 267 } 268 if ((so->so_state & SS_PRIV) == 0) { 269 error = EACCES; 270 break; 271 } 272 s = splsoftnet(); 273 error = in_pcballoc(so, &divbtable); 274 splx(s); 275 if (error) 276 break; 277 278 error = soreserve(so, divert_sendspace, divert_recvspace); 279 if (error) 280 break; 281 sotoinpcb(so)->inp_flags |= INP_HDRINCL; 282 break; 283 284 case PRU_DETACH: 285 divert_detach(inp); 286 break; 287 288 case PRU_BIND: 289 s = splsoftnet(); 290 error = in_pcbbind(inp, addr, p); 291 splx(s); 292 break; 293 294 case PRU_SHUTDOWN: 295 socantsendmore(so); 296 break; 297 298 case PRU_SEND: 299 return (divert_output(inp, m, addr, control)); 300 301 case PRU_ABORT: 302 soisdisconnected(so); 303 divert_detach(inp); 304 break; 305 306 case PRU_SOCKADDR: 307 in_setsockaddr(inp, addr); 308 break; 309 310 case PRU_PEERADDR: 311 in_setpeeraddr(inp, addr); 312 break; 313 314 case PRU_SENSE: 315 return (0); 316 317 case PRU_LISTEN: 318 case PRU_CONNECT: 319 case PRU_CONNECT2: 320 case PRU_ACCEPT: 321 case PRU_DISCONNECT: 322 case PRU_SENDOOB: 323 case PRU_FASTTIMO: 324 case PRU_SLOWTIMO: 325 case PRU_PROTORCV: 326 case PRU_PROTOSEND: 327 error = EOPNOTSUPP; 328 break; 329 330 case PRU_RCVD: 331 case PRU_RCVOOB: 332 return (EOPNOTSUPP); /* do not free mbuf's */ 333 334 default: 335 panic("divert_usrreq"); 336 } 337 338 release: 339 m_freem(control); 340 m_freem(m); 341 return (error); 342 } 343 344 void 345 divert_detach(struct inpcb *inp) 346 { 347 int s = splsoftnet(); 348 349 in_pcbdetach(inp); 350 splx(s); 351 } 352 353 /* 354 * Sysctl for divert variables. 355 */ 356 int 357 divert_sysctl(int *name, u_int namelen, void *oldp, size_t *oldlenp, void *newp, 358 size_t newlen) 359 { 360 /* All sysctl names at this level are terminal. */ 361 if (namelen != 1) 362 return (ENOTDIR); 363 364 switch (name[0]) { 365 case DIVERTCTL_SENDSPACE: 366 return (sysctl_int(oldp, oldlenp, newp, newlen, 367 &divert_sendspace)); 368 case DIVERTCTL_RECVSPACE: 369 return (sysctl_int(oldp, oldlenp, newp, newlen, 370 &divert_recvspace)); 371 case DIVERTCTL_STATS: 372 if (newp != NULL) 373 return (EPERM); 374 return (sysctl_struct(oldp, oldlenp, newp, newlen, 375 &divstat, sizeof(divstat))); 376 default: 377 if (name[0] < DIVERTCTL_MAXID) 378 return sysctl_int_arr(divertctl_vars, name, namelen, 379 oldp, oldlenp, newp, newlen); 380 381 return (ENOPROTOOPT); 382 } 383 /* NOTREACHED */ 384 } 385