Line data Source code
1 : /* $OpenBSD: ip6_divert.c,v 1.56 2018/04/24 15:40:55 pirofti 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 0 : divert6_init(void)
70 : {
71 0 : in_pcbinit(&divb6table, divb6hashsize);
72 0 : div6counters = counters_alloc(div6s_ncounters);
73 0 : }
74 :
75 : int
76 0 : divert6_output(struct inpcb *inp, struct mbuf *m, struct mbuf *nam,
77 : struct mbuf *control)
78 : {
79 0 : struct sockaddr_in6 *sin6;
80 0 : int error, min_hdrlen, nxt, off, dir;
81 : struct ip6_hdr *ip6;
82 :
83 0 : m_freem(control);
84 :
85 0 : if ((error = in6_nam2sin6(nam, &sin6)))
86 : goto fail;
87 :
88 : /* Do basic sanity checks. */
89 0 : if (m->m_pkthdr.len < sizeof(struct ip6_hdr))
90 : goto fail;
91 0 : if ((m = m_pullup(m, sizeof(struct ip6_hdr))) == NULL) {
92 : /* m_pullup() has freed the mbuf, so just return. */
93 0 : div6stat_inc(div6s_errors);
94 0 : return (ENOBUFS);
95 : }
96 0 : ip6 = mtod(m, struct ip6_hdr *);
97 0 : if ((ip6->ip6_vfc & IPV6_VERSION_MASK) != IPV6_VERSION)
98 : goto fail;
99 0 : 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 0 : off = ip6_lasthdr(m, 0, IPPROTO_IPV6, &nxt);
107 0 : if (off < sizeof(struct ip6_hdr))
108 : goto fail;
109 :
110 0 : dir = (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr) ? PF_OUT : PF_IN);
111 :
112 0 : switch (nxt) {
113 : case IPPROTO_TCP:
114 : min_hdrlen = sizeof(struct tcphdr);
115 0 : m->m_pkthdr.csum_flags |= M_TCP_CSUM_OUT;
116 0 : break;
117 : case IPPROTO_UDP:
118 : min_hdrlen = sizeof(struct udphdr);
119 0 : m->m_pkthdr.csum_flags |= M_UDP_CSUM_OUT;
120 0 : break;
121 : case IPPROTO_ICMPV6:
122 : min_hdrlen = sizeof(struct icmp6_hdr);
123 0 : m->m_pkthdr.csum_flags |= M_ICMP_CSUM_OUT;
124 0 : break;
125 : default:
126 : min_hdrlen = 0;
127 0 : break;
128 : }
129 0 : if (min_hdrlen && m->m_pkthdr.len < off + min_hdrlen)
130 : goto fail;
131 :
132 0 : m->m_pkthdr.pf.flags |= PF_TAG_DIVERTED_PACKET;
133 :
134 0 : if (dir == PF_IN) {
135 : struct rtentry *rt;
136 : struct ifnet *ifp;
137 :
138 0 : rt = rtalloc(sin6tosa(sin6), 0, inp->inp_rtableid);
139 0 : if (!rtisvalid(rt) || !ISSET(rt->rt_flags, RTF_LOCAL)) {
140 0 : rtfree(rt);
141 : error = EADDRNOTAVAIL;
142 0 : goto fail;
143 : }
144 0 : m->m_pkthdr.ph_ifidx = rt->rt_ifidx;
145 0 : 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 0 : in6_proto_cksum_out(m, NULL);
153 :
154 0 : ifp = if_get(m->m_pkthdr.ph_ifidx);
155 0 : if (ifp == NULL) {
156 : error = ENETDOWN;
157 0 : goto fail;
158 : }
159 0 : ipv6_input(ifp, m);
160 0 : if_put(ifp);
161 0 : } else {
162 0 : m->m_pkthdr.ph_rtableid = inp->inp_rtableid;
163 :
164 0 : error = ip6_output(m, NULL, &inp->inp_route6,
165 : IP_ALLOWBROADCAST | IP_RAWOUTPUT, NULL, NULL);
166 : }
167 :
168 0 : div6stat_inc(div6s_opackets);
169 0 : return (error);
170 :
171 : fail:
172 0 : div6stat_inc(div6s_errors);
173 0 : m_freem(m);
174 0 : return (error ? error : EINVAL);
175 0 : }
176 :
177 : int
178 0 : divert6_packet(struct mbuf *m, int dir, u_int16_t divert_port)
179 : {
180 : struct inpcb *inp;
181 : struct socket *sa = NULL;
182 0 : struct sockaddr_in6 addr;
183 :
184 : inp = NULL;
185 0 : div6stat_inc(div6s_ipackets);
186 :
187 0 : if (m->m_len < sizeof(struct ip6_hdr) &&
188 0 : (m = m_pullup(m, sizeof(struct ip6_hdr))) == NULL) {
189 0 : div6stat_inc(div6s_errors);
190 0 : return (0);
191 : }
192 :
193 0 : TAILQ_FOREACH(inp, &divb6table.inpt_queue, inp_queue) {
194 0 : if (inp->inp_lport == divert_port)
195 : break;
196 : }
197 :
198 0 : memset(&addr, 0, sizeof(addr));
199 0 : addr.sin6_family = AF_INET6;
200 0 : addr.sin6_len = sizeof(addr);
201 :
202 0 : if (dir == PF_IN) {
203 : struct ifaddr *ifa;
204 : struct ifnet *ifp;
205 :
206 0 : ifp = if_get(m->m_pkthdr.ph_ifidx);
207 0 : if (ifp == NULL) {
208 0 : m_freem(m);
209 0 : return (0);
210 : }
211 0 : TAILQ_FOREACH(ifa, &ifp->if_addrlist, ifa_list) {
212 0 : if (ifa->ifa_addr->sa_family != AF_INET6)
213 : continue;
214 0 : addr.sin6_addr = satosin6(ifa->ifa_addr)->sin6_addr;
215 0 : break;
216 : }
217 0 : if_put(ifp);
218 0 : }
219 :
220 0 : if (inp) {
221 0 : sa = inp->inp_socket;
222 0 : if (sbappendaddr(sa, &sa->so_rcv, sin6tosa(&addr), m, NULL) == 0) {
223 0 : div6stat_inc(div6s_fullsock);
224 0 : m_freem(m);
225 0 : return (0);
226 : } else {
227 0 : KERNEL_LOCK();
228 0 : sorwakeup(inp->inp_socket);
229 0 : KERNEL_UNLOCK();
230 : }
231 0 : }
232 :
233 0 : if (sa == NULL) {
234 0 : div6stat_inc(div6s_noport);
235 0 : m_freem(m);
236 0 : }
237 0 : return (0);
238 0 : }
239 :
240 : /*ARGSUSED*/
241 : int
242 0 : divert6_usrreq(struct socket *so, int req, struct mbuf *m, struct mbuf *addr,
243 : struct mbuf *control, struct proc *p)
244 : {
245 0 : struct inpcb *inp = sotoinpcb(so);
246 : int error = 0;
247 :
248 0 : if (req == PRU_CONTROL) {
249 0 : return (in6_control(so, (u_long)m, (caddr_t)addr,
250 0 : (struct ifnet *)control));
251 : }
252 :
253 0 : soassertlocked(so);
254 :
255 0 : if (inp == NULL) {
256 : error = EINVAL;
257 0 : goto release;
258 : }
259 0 : switch (req) {
260 :
261 : case PRU_BIND:
262 0 : error = in_pcbbind(inp, addr, p);
263 0 : break;
264 :
265 : case PRU_SHUTDOWN:
266 0 : socantsendmore(so);
267 0 : break;
268 :
269 : case PRU_SEND:
270 0 : return (divert6_output(inp, m, addr, control));
271 :
272 : case PRU_ABORT:
273 0 : soisdisconnected(so);
274 0 : in_pcbdetach(inp);
275 0 : break;
276 :
277 : case PRU_SOCKADDR:
278 0 : in6_setsockaddr(inp, addr);
279 0 : break;
280 :
281 : case PRU_PEERADDR:
282 0 : in6_setpeeraddr(inp, addr);
283 0 : break;
284 :
285 : case PRU_SENSE:
286 0 : return (0);
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 : error = EOPNOTSUPP;
299 0 : break;
300 :
301 : case PRU_RCVD:
302 : case PRU_RCVOOB:
303 0 : return (EOPNOTSUPP); /* do not free mbuf's */
304 :
305 : default:
306 0 : panic("divert6_usrreq");
307 : }
308 :
309 : release:
310 0 : m_freem(control);
311 0 : m_freem(m);
312 0 : return (error);
313 0 : }
314 :
315 : int
316 0 : divert6_attach(struct socket *so, int proto)
317 : {
318 : int error;
319 :
320 0 : if (so->so_pcb != NULL)
321 0 : return EINVAL;
322 :
323 0 : if ((so->so_state & SS_PRIV) == 0)
324 0 : return EACCES;
325 :
326 0 : error = in_pcballoc(so, &divb6table);
327 0 : if (error)
328 0 : return (error);
329 :
330 0 : error = soreserve(so, divert6_sendspace, divert6_recvspace);
331 0 : if (error)
332 0 : return (error);
333 0 : sotoinpcb(so)->inp_flags |= INP_HDRINCL;
334 0 : return (0);
335 0 : }
336 :
337 : int
338 0 : divert6_detach(struct socket *so)
339 : {
340 0 : struct inpcb *inp = sotoinpcb(so);
341 :
342 0 : soassertlocked(so);
343 :
344 0 : if (inp == NULL)
345 0 : return (EINVAL);
346 :
347 0 : in_pcbdetach(inp);
348 :
349 0 : return (0);
350 0 : }
351 :
352 : int
353 0 : divert6_sysctl_div6stat(void *oldp, size_t *oldlenp, void *newp)
354 : {
355 0 : uint64_t counters[div6s_ncounters];
356 0 : struct div6stat div6stat;
357 0 : u_long *words = (u_long *)&div6stat;
358 : int i;
359 :
360 : CTASSERT(sizeof(div6stat) == (nitems(counters) * sizeof(u_long)));
361 :
362 0 : counters_read(div6counters, counters, nitems(counters));
363 :
364 0 : for (i = 0; i < nitems(counters); i++)
365 0 : words[i] = (u_long)counters[i];
366 :
367 0 : return (sysctl_rdstruct(oldp, oldlenp, newp,
368 : &div6stat, sizeof(div6stat)));
369 0 : }
370 :
371 : /*
372 : * Sysctl for divert variables.
373 : */
374 : int
375 0 : 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 0 : if (namelen != 1)
382 0 : return (ENOTDIR);
383 :
384 0 : switch (name[0]) {
385 : case DIVERT6CTL_SENDSPACE:
386 0 : NET_LOCK();
387 0 : error = sysctl_int(oldp, oldlenp, newp, newlen,
388 : &divert6_sendspace);
389 0 : NET_UNLOCK();
390 0 : return (error);
391 : case DIVERT6CTL_RECVSPACE:
392 0 : NET_LOCK();
393 0 : error = sysctl_int(oldp, oldlenp, newp, newlen,
394 : &divert6_recvspace);
395 0 : NET_UNLOCK();
396 0 : return (error);
397 : case DIVERT6CTL_STATS:
398 0 : return (divert6_sysctl_div6stat(oldp, oldlenp, newp));
399 : default:
400 0 : if (name[0] < DIVERT6CTL_MAXID) {
401 0 : NET_LOCK();
402 0 : error = sysctl_int_arr(divert6ctl_vars, name, namelen,
403 : oldp, oldlenp, newp, newlen);
404 0 : NET_UNLOCK();
405 0 : return (error);
406 : }
407 :
408 0 : return (ENOPROTOOPT);
409 : }
410 : /* NOTREACHED */
411 0 : }
|