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