xref: /openbsd/sys/netinet6/ip6_divert.c (revision 274d7c50)
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