summaryrefslogtreecommitdiff
path: root/sys/netinet6/ip6_divert.c
diff options
context:
space:
mode:
authorMichele Marchetto <michele@cvs.openbsd.org>2009-11-05 20:50:15 +0000
committerMichele Marchetto <michele@cvs.openbsd.org>2009-11-05 20:50:15 +0000
commiteb9eb7c1d2dad15a5d5956bab64bb1316092b507 (patch)
tree5dead20dd9a7eadd90c64f8e52f63c26d3c06ad9 /sys/netinet6/ip6_divert.c
parent91278afb30123fa565d911dd57793d102f7a23c8 (diff)
IPv6 support for divert sockets.
tested by phessler@ pyr@ ok claudio@ "go ahead" deraadt@
Diffstat (limited to 'sys/netinet6/ip6_divert.c')
-rw-r--r--sys/netinet6/ip6_divert.c339
1 files changed, 339 insertions, 0 deletions
diff --git a/sys/netinet6/ip6_divert.c b/sys/netinet6/ip6_divert.c
new file mode 100644
index 00000000000..e39dfc757c7
--- /dev/null
+++ b/sys/netinet6/ip6_divert.c
@@ -0,0 +1,339 @@
+/* $OpenBSD: ip6_divert.c,v 1.1 2009/11/05 20:50:14 michele Exp $ */
+
+/*
+ * Copyright (c) 2009 Michele Marchetto <michele@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/mbuf.h>
+#include <sys/protosw.h>
+#include <sys/socket.h>
+#include <sys/socketvar.h>
+#include <sys/sysctl.h>
+
+#include <net/if.h>
+#include <net/route.h>
+#include <net/netisr.h>
+#include <net/pfvar.h>
+
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/in_var.h>
+#include <netinet/ip.h>
+#include <netinet/ip_var.h>
+#include <netinet/in_pcb.h>
+#include <netinet/ip6.h>
+#include <netinet6/in6_var.h>
+#include <netinet6/in6_var.h>
+#include <netinet6/ip6_divert.h>
+
+struct inpcbtable divb6table;
+struct div6stat div6stat;
+
+#ifndef DIVERT_SENDSPACE
+#define DIVERT_SENDSPACE (65536 + 100)
+#endif
+u_int divert6_sendspace = DIVERT_SENDSPACE;
+#ifndef DIVERT_RECVSPACE
+#define DIVERT_RECVSPACE (65536 + 100)
+#endif
+u_int divert6_recvspace = DIVERT_RECVSPACE;
+
+#ifndef DIVERTHASHSIZE
+#define DIVERTHASHSIZE 128
+#endif
+
+int *divert6ctl_vars[DIVERT6CTL_MAXID] = DIVERT6CTL_VARS;
+
+int divb6hashsize = DIVERTHASHSIZE;
+
+void divert6_detach(struct inpcb *);
+
+void
+divert6_init()
+{
+ in_pcbinit(&divb6table, divb6hashsize);
+}
+
+int
+divert6_input(struct mbuf **mp, int *offp, int proto)
+{
+ m_freem(*mp);
+
+ return (0);
+}
+
+int
+divert6_output(struct mbuf *m, ...)
+{
+ struct inpcb *inp;
+ struct ifqueue *inq;
+ struct mbuf *nam, *control;
+ struct sockaddr_in6 *sin6;
+ struct socket *so;
+ struct ifaddr *ifa;
+ int s, error = 0;
+ va_list ap;
+
+ va_start(ap, m);
+ inp = va_arg(ap, struct inpcb *);
+ nam = va_arg(ap, struct mbuf *);
+ control = va_arg(ap, struct mbuf *);
+ va_end(ap);
+
+ m->m_pkthdr.rcvif = NULL;
+ m->m_nextpkt = NULL;
+ m->m_pkthdr.rdomain = inp->inp_rdomain;
+
+ if (control)
+ m_freem(control);
+
+ sin6 = mtod(nam, struct sockaddr_in6 *);
+ so = inp->inp_socket;
+
+ m->m_pkthdr.pf.flags |= PF_TAG_DIVERTED_PACKET;
+
+ if (!IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) {
+ ifa = ifa_ifwithaddr((struct sockaddr *)sin6, 0);
+ if (ifa == NULL) {
+ div6stat.divs_errors++;
+ m_freem(m);
+ return (EADDRNOTAVAIL);
+ }
+ m->m_pkthdr.rcvif = ifa->ifa_ifp;
+
+ inq = &ip6intrq;
+
+ s = splnet();
+ IF_INPUT_ENQUEUE(inq, m);
+ schednetisr(NETISR_IPV6);
+ splx(s);
+ } else {
+ error = ip6_output(m, NULL, &inp->inp_route6,
+ ((so->so_options & SO_DONTROUTE) ? IP_ROUTETOIF : 0)
+ | IP_ALLOWBROADCAST | IP_RAWOUTPUT, NULL, NULL, NULL);
+ }
+
+ div6stat.divs_opackets++;
+ return (error);
+}
+
+void
+divert6_packet(struct mbuf *m, int dir)
+{
+ struct inpcb *inp;
+ struct socket *sa = NULL;
+ struct sockaddr_in6 addr;
+ struct pf_divert *pd;
+
+ div6stat.divs_ipackets++;
+
+ if (m->m_len < sizeof(struct ip6_hdr) &&
+ (m = m_pullup(m, sizeof(struct ip6_hdr))) == NULL) {
+ div6stat.divs_errors++;
+ return;
+ }
+
+ pd = pf_find_divert(m);
+ if (pd == NULL) {
+ div6stat.divs_errors++;
+ m_freem(m);
+ return;
+ }
+
+ bzero(&addr, sizeof(addr));
+ addr.sin6_family = AF_INET6;
+ addr.sin6_len = sizeof(addr);
+
+ if (dir == PF_IN) {
+ struct ifaddr *ifa;
+ struct ifnet *ifp;
+
+ ifp = m->m_pkthdr.rcvif;
+ TAILQ_FOREACH(ifa, &ifp->if_addrlist, ifa_list) {
+ if (ifa->ifa_addr->sa_family != AF_INET6)
+ continue;
+ addr.sin6_addr = ((struct sockaddr_in6 *)
+ ifa->ifa_addr)->sin6_addr;
+ break;
+ }
+ }
+
+ CIRCLEQ_FOREACH(inp, &divb6table.inpt_queue, inp_queue) {
+ if (inp->inp_lport != pd->port)
+ continue;
+
+ sa = inp->inp_socket;
+ if (sbappendaddr(&sa->so_rcv, (struct sockaddr *)&addr,
+ m, NULL) == 0) {
+ div6stat.divs_fullsock++;
+ m_freem(m);
+ return;
+ } else
+ sorwakeup(inp->inp_socket);
+ break;
+ }
+
+ if (sa == NULL) {
+ div6stat.divs_noport++;
+ m_freem(m);
+ }
+}
+
+/*ARGSUSED*/
+int
+divert6_usrreq(struct socket *so, int req, struct mbuf *m, struct mbuf *addr,
+ struct mbuf *control, struct proc *p)
+{
+ struct inpcb *inp = sotoinpcb(so);
+ int error = 0;
+ int s;
+
+ if (req == PRU_CONTROL) {
+ return (in6_control(so, (u_long)m, (caddr_t)addr,
+ (struct ifnet *)control, p));
+ }
+ if (inp == NULL && req != PRU_ATTACH) {
+ error = EINVAL;
+ goto release;
+ }
+ switch (req) {
+
+ case PRU_ATTACH:
+ if (inp != NULL) {
+ error = EINVAL;
+ break;
+ }
+ if ((so->so_state & SS_PRIV) == 0) {
+ error = EACCES;
+ break;
+ }
+ s = splsoftnet();
+ error = in_pcballoc(so, &divb6table);
+ splx(s);
+ if (error)
+ break;
+
+ error = soreserve(so, divert6_sendspace, divert6_recvspace);
+ if (error)
+ break;
+ ((struct inpcb *) so->so_pcb)->inp_flags |= INP_HDRINCL;
+ break;
+
+ case PRU_DETACH:
+ divert6_detach(inp);
+ break;
+
+ case PRU_BIND:
+ s = splsoftnet();
+ error = in6_pcbbind(inp, addr, p);
+ splx(s);
+ break;
+
+ case PRU_SHUTDOWN:
+ socantsendmore(so);
+ break;
+
+ case PRU_SEND:
+ return (divert6_output(m, inp, addr, control));
+
+ case PRU_ABORT:
+ soisdisconnected(so);
+ divert6_detach(inp);
+ break;
+
+ case PRU_SOCKADDR:
+ in6_setsockaddr(inp, addr);
+ break;
+
+ case PRU_PEERADDR:
+ in6_setpeeraddr(inp, addr);
+ break;
+
+ case PRU_SENSE:
+ return (0);
+
+ case PRU_LISTEN:
+ case PRU_CONNECT:
+ case PRU_CONNECT2:
+ case PRU_ACCEPT:
+ case PRU_DISCONNECT:
+ case PRU_SENDOOB:
+ case PRU_FASTTIMO:
+ case PRU_SLOWTIMO:
+ case PRU_PROTORCV:
+ case PRU_PROTOSEND:
+ error = EOPNOTSUPP;
+ break;
+
+ case PRU_RCVD:
+ case PRU_RCVOOB:
+ return (EOPNOTSUPP); /* do not free mbuf's */
+
+ default:
+ panic("divert6_usrreq");
+ }
+
+release:
+ if (control) {
+ m_freem(control);
+ }
+ if (m)
+ m_freem(m);
+ return (error);
+}
+
+void
+divert6_detach(struct inpcb *inp)
+{
+ int s = splsoftnet();
+
+ in_pcbdetach(inp);
+ splx(s);
+}
+
+/*
+ * Sysctl for divert variables.
+ */
+int
+divert6_sysctl(int *name, u_int namelen, void *oldp, size_t *oldlenp,
+ void *newp, size_t newlen)
+{
+ /* All sysctl names at this level are terminal. */
+ if (namelen != 1)
+ return (ENOTDIR);
+
+ switch (name[0]) {
+ case DIVERT6CTL_SENDSPACE:
+ return (sysctl_int(oldp, oldlenp, newp, newlen,
+ &divert6_sendspace));
+ case DIVERT6CTL_RECVSPACE:
+ return (sysctl_int(oldp, oldlenp, newp, newlen,
+ &divert6_recvspace));
+ case DIVERT6CTL_STATS:
+ if (newp != NULL)
+ return (EPERM);
+ return (sysctl_struct(oldp, oldlenp, newp, newlen,
+ &div6stat, sizeof(div6stat)));
+ default:
+ if (name[0] < DIVERT6CTL_MAXID)
+ return sysctl_int_arr(divert6ctl_vars, name, namelen,
+ oldp, oldlenp, newp, newlen);
+
+ return (ENOPROTOOPT);
+ }
+ /* NOTREACHED */
+}