1 /* $OpenBSD: ip6_divert.c,v 1.88 2022/10/17 14:49:02 mvs 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 const struct sysctl_bounded_args divert6ctl_vars[] = {
62 { DIVERT6CTL_RECVSPACE, &divert6_recvspace, 0, INT_MAX },
63 { DIVERT6CTL_SENDSPACE, &divert6_sendspace, 0, INT_MAX },
64 };
65
66 const struct pr_usrreqs divert6_usrreqs = {
67 .pru_attach = divert6_attach,
68 .pru_detach = divert6_detach,
69 .pru_lock = divert6_lock,
70 .pru_unlock = divert6_unlock,
71 .pru_bind = divert6_bind,
72 .pru_shutdown = divert6_shutdown,
73 .pru_send = divert6_send,
74 .pru_control = in6_control,
75 .pru_sockaddr = in6_sockaddr,
76 .pru_peeraddr = in6_peeraddr,
77 };
78
79 int divb6hashsize = DIVERTHASHSIZE;
80
81 int divert6_output(struct inpcb *, struct mbuf *, struct mbuf *,
82 struct mbuf *);
83
84 void
85 divert6_init(void)
86 {
87 in_pcbinit(&divb6table, divb6hashsize);
88 div6counters = counters_alloc(div6s_ncounters);
89 }
90
91 int
92 divert6_output(struct inpcb *inp, struct mbuf *m, struct mbuf *nam,
93 struct mbuf *control)
94 {
95 struct sockaddr_in6 *sin6;
96 int error, min_hdrlen, nxt, off, dir;
97 struct ip6_hdr *ip6;
98
99 m_freem(control);
100
101 if ((error = in6_nam2sin6(nam, &sin6)))
102 goto fail;
103
104 /* Do basic sanity checks. */
105 if (m->m_pkthdr.len < sizeof(struct ip6_hdr))
106 goto fail;
107 if ((m = m_pullup(m, sizeof(struct ip6_hdr))) == NULL) {
108 /* m_pullup() has freed the mbuf, so just return. */
109 div6stat_inc(div6s_errors);
110 return (ENOBUFS);
111 }
112 ip6 = mtod(m, struct ip6_hdr *);
113 if ((ip6->ip6_vfc & IPV6_VERSION_MASK) != IPV6_VERSION)
114 goto fail;
115 if (m->m_pkthdr.len < sizeof(struct ip6_hdr) + ntohs(ip6->ip6_plen))
116 goto fail;
117
118 /*
119 * Recalculate the protocol checksum since the userspace application
120 * may have modified the packet prior to reinjection.
121 */
122 off = ip6_lasthdr(m, 0, IPPROTO_IPV6, &nxt);
123 if (off < sizeof(struct ip6_hdr))
124 goto fail;
125
126 dir = (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr) ? PF_OUT : PF_IN);
127
128 switch (nxt) {
129 case IPPROTO_TCP:
130 min_hdrlen = sizeof(struct tcphdr);
131 m->m_pkthdr.csum_flags |= M_TCP_CSUM_OUT;
132 break;
133 case IPPROTO_UDP:
134 min_hdrlen = sizeof(struct udphdr);
135 m->m_pkthdr.csum_flags |= M_UDP_CSUM_OUT;
136 break;
137 case IPPROTO_ICMPV6:
138 min_hdrlen = sizeof(struct icmp6_hdr);
139 m->m_pkthdr.csum_flags |= M_ICMP_CSUM_OUT;
140 break;
141 default:
142 min_hdrlen = 0;
143 break;
144 }
145 if (min_hdrlen && m->m_pkthdr.len < off + min_hdrlen)
146 goto fail;
147
148 m->m_pkthdr.pf.flags |= PF_TAG_DIVERTED_PACKET;
149
150 if (dir == PF_IN) {
151 struct rtentry *rt;
152 struct ifnet *ifp;
153
154 rt = rtalloc(sin6tosa(sin6), 0, inp->inp_rtableid);
155 if (!rtisvalid(rt) || !ISSET(rt->rt_flags, RTF_LOCAL)) {
156 rtfree(rt);
157 error = EADDRNOTAVAIL;
158 goto fail;
159 }
160 m->m_pkthdr.ph_ifidx = rt->rt_ifidx;
161 rtfree(rt);
162
163 /*
164 * Recalculate the protocol checksum for the inbound packet
165 * since the userspace application may have modified the packet
166 * prior to reinjection.
167 */
168 in6_proto_cksum_out(m, NULL);
169
170 ifp = if_get(m->m_pkthdr.ph_ifidx);
171 if (ifp == NULL) {
172 error = ENETDOWN;
173 goto fail;
174 }
175 ipv6_input(ifp, m);
176 if_put(ifp);
177 } else {
178 m->m_pkthdr.ph_rtableid = inp->inp_rtableid;
179
180 error = ip6_output(m, NULL, &inp->inp_route6,
181 IP_ALLOWBROADCAST | IP_RAWOUTPUT, NULL, NULL);
182 }
183
184 div6stat_inc(div6s_opackets);
185 return (error);
186
187 fail:
188 div6stat_inc(div6s_errors);
189 m_freem(m);
190 return (error ? error : EINVAL);
191 }
192
193 void
194 divert6_packet(struct mbuf *m, int dir, u_int16_t divert_port)
195 {
196 struct inpcb *inp = NULL;
197 struct socket *so;
198 struct sockaddr_in6 sin6;
199
200 div6stat_inc(div6s_ipackets);
201
202 if (m->m_len < sizeof(struct ip6_hdr) &&
203 (m = m_pullup(m, sizeof(struct ip6_hdr))) == NULL) {
204 div6stat_inc(div6s_errors);
205 goto bad;
206 }
207
208 mtx_enter(&divb6table.inpt_mtx);
209 TAILQ_FOREACH(inp, &divb6table.inpt_queue, inp_queue) {
210 if (inp->inp_lport != divert_port)
211 continue;
212 in_pcbref(inp);
213 break;
214 }
215 mtx_leave(&divb6table.inpt_mtx);
216 if (inp == NULL) {
217 div6stat_inc(div6s_noport);
218 goto bad;
219 }
220
221 memset(&sin6, 0, sizeof(sin6));
222 sin6.sin6_family = AF_INET6;
223 sin6.sin6_len = sizeof(sin6);
224
225 if (dir == PF_IN) {
226 struct ifaddr *ifa;
227 struct ifnet *ifp;
228
229 ifp = if_get(m->m_pkthdr.ph_ifidx);
230 if (ifp == NULL) {
231 div6stat_inc(div6s_errors);
232 goto bad;
233 }
234 TAILQ_FOREACH(ifa, &ifp->if_addrlist, ifa_list) {
235 if (ifa->ifa_addr->sa_family != AF_INET6)
236 continue;
237 sin6.sin6_addr = satosin6(ifa->ifa_addr)->sin6_addr;
238 break;
239 }
240 if_put(ifp);
241 }
242
243 mtx_enter(&inp->inp_mtx);
244 so = inp->inp_socket;
245 if (sbappendaddr(so, &so->so_rcv, sin6tosa(&sin6), m, NULL) == 0) {
246 mtx_leave(&inp->inp_mtx);
247 div6stat_inc(div6s_fullsock);
248 goto bad;
249 }
250 mtx_leave(&inp->inp_mtx);
251 sorwakeup(so);
252
253 in_pcbunref(inp);
254 return;
255
256 bad:
257 if (inp != NULL)
258 in_pcbunref(inp);
259 m_freem(m);
260 }
261
262 int
263 divert6_attach(struct socket *so, int proto, int wait)
264 {
265 int error;
266
267 if (so->so_pcb != NULL)
268 return EINVAL;
269
270 if ((so->so_state & SS_PRIV) == 0)
271 return EACCES;
272
273 error = in_pcballoc(so, &divb6table, wait);
274 if (error)
275 return (error);
276
277 error = soreserve(so, divert6_sendspace, divert6_recvspace);
278 if (error)
279 return (error);
280 sotoinpcb(so)->inp_flags |= INP_HDRINCL;
281 return (0);
282 }
283
284 int
285 divert6_detach(struct socket *so)
286 {
287 struct inpcb *inp = sotoinpcb(so);
288
289 soassertlocked(so);
290
291 if (inp == NULL)
292 return (EINVAL);
293
294 in_pcbdetach(inp);
295
296 return (0);
297 }
298
299 void
300 divert6_lock(struct socket *so)
301 {
302 struct inpcb *inp = sotoinpcb(so);
303
304 NET_ASSERT_LOCKED();
305 mtx_enter(&inp->inp_mtx);
306 }
307
308 void
309 divert6_unlock(struct socket *so)
310 {
311 struct inpcb *inp = sotoinpcb(so);
312
313 NET_ASSERT_LOCKED();
314 mtx_leave(&inp->inp_mtx);
315 }
316
317 int
318 divert6_bind(struct socket *so, struct mbuf *addr, struct proc *p)
319 {
320 struct inpcb *inp = sotoinpcb(so);
321
322 soassertlocked(so);
323 return in_pcbbind(inp, addr, p);
324 }
325
326 int
327 divert6_shutdown(struct socket *so)
328 {
329 soassertlocked(so);
330 socantsendmore(so);
331
332 return (0);
333 }
334
335 int
336 divert6_send(struct socket *so, struct mbuf *m, struct mbuf *addr,
337 struct mbuf *control)
338 {
339 struct inpcb *inp = sotoinpcb(so);
340
341 soassertlocked(so);
342 return (divert6_output(inp, m, addr, control));
343 }
344
345 int
346 divert6_sysctl_div6stat(void *oldp, size_t *oldlenp, void *newp)
347 {
348 uint64_t counters[div6s_ncounters];
349 struct div6stat div6stat;
350 u_long *words = (u_long *)&div6stat;
351 int i;
352
353 CTASSERT(sizeof(div6stat) == (nitems(counters) * sizeof(u_long)));
354
355 counters_read(div6counters, counters, nitems(counters));
356
357 for (i = 0; i < nitems(counters); i++)
358 words[i] = (u_long)counters[i];
359
360 return (sysctl_rdstruct(oldp, oldlenp, newp,
361 &div6stat, sizeof(div6stat)));
362 }
363
364 /*
365 * Sysctl for divert variables.
366 */
367 int
368 divert6_sysctl(int *name, u_int namelen, void *oldp, size_t *oldlenp,
369 void *newp, size_t newlen)
370 {
371 int error;
372
373 /* All sysctl names at this level are terminal. */
374 if (namelen != 1)
375 return (ENOTDIR);
376
377 switch (name[0]) {
378 case DIVERT6CTL_STATS:
379 return (divert6_sysctl_div6stat(oldp, oldlenp, newp));
380 default:
381 NET_LOCK();
382 error = sysctl_bounded_arr(divert6ctl_vars,
383 nitems(divert6ctl_vars), name, namelen, oldp, oldlenp,
384 newp, newlen);
385 NET_UNLOCK();
386 return (error);
387 }
388 /* NOTREACHED */
389 }
Cache object: 979842fd1685006c450f469a1381aa81
|