xref: /openbsd-src/sys/netinet/ip_divert.c (revision f2da64fbbbf1b03f09f390ab01267c93dfd77c4c)
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