diff options
author | Michele Marchetto <michele@cvs.openbsd.org> | 2009-10-04 16:08:38 +0000 |
---|---|---|
committer | Michele Marchetto <michele@cvs.openbsd.org> | 2009-10-04 16:08:38 +0000 |
commit | 82b30916c9515794d7aca77e8c937fc5e0455586 (patch) | |
tree | ce06c3cb91a134e4d7d4cb69f04155cf3c8ba2dc /sys | |
parent | dd5c2f75fe5e092eb892c438f7f87faa2e57fcb6 (diff) |
Add (again) support for divert sockets. They allow you to:
- queue packets from pf(4) to a userspace application
- reinject packets from the application into the kernel stack.
The divert socket can be bound to a special "divert port" and will
receive every packet diverted to that port by pf(4).
The pf syntax is pretty simple, e.g.:
pass on em0 inet proto tcp from any to any port 80 divert-packet port 1
A lot of discussion have happened since my last commit that resulted
in many changes and improvements.
I would *really* like to thank everyone who took part in the discussion
especially canacar@ who spotted out which are the limitations of this approach.
OpenBSD divert(4) is meant to be compatible with software running on
top of FreeBSD's divert sockets even though they are pretty different and will
become even more with time.
discusses with many, but mainly reyk@ canacar@ deraadt@ dlg@ claudio@ beck@
tested by reyk@ and myself
ok reyk@ claudio@ beck@
manpage help and ok by jmc@
Diffstat (limited to 'sys')
-rw-r--r-- | sys/conf/files | 3 | ||||
-rw-r--r-- | sys/net/pf.c | 20 | ||||
-rw-r--r-- | sys/net/pfvar.h | 6 | ||||
-rw-r--r-- | sys/netinet/in.h | 26 | ||||
-rw-r--r-- | sys/netinet/in_proto.c | 14 | ||||
-rw-r--r-- | sys/netinet/ip_divert.c | 335 | ||||
-rw-r--r-- | sys/netinet/ip_divert.h | 65 | ||||
-rw-r--r-- | sys/sys/mbuf.h | 3 |
8 files changed, 463 insertions, 9 deletions
diff --git a/sys/conf/files b/sys/conf/files index 3a56607f91e..d068b1c666d 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -1,4 +1,4 @@ -# $OpenBSD: files,v 1.477 2009/10/03 19:51:53 kettenis Exp $ +# $OpenBSD: files,v 1.478 2009/10/04 16:08:37 michele Exp $ # $NetBSD: files,v 1.87 1996/05/19 17:17:50 jonathan Exp $ # @(#)files.newconf 7.5 (Berkeley) 5/10/93 @@ -831,6 +831,7 @@ file netinet/igmp.c inet file netinet/in.c inet file netinet/in_pcb.c inet file netinet/in_proto.c inet +file netinet/ip_divert.c inet & pf file netinet/ip_icmp.c inet file netinet/ip_id.c inet file netinet/ip_input.c inet diff --git a/sys/net/pf.c b/sys/net/pf.c index f8994dc86ec..269dc8c2581 100644 --- a/sys/net/pf.c +++ b/sys/net/pf.c @@ -1,4 +1,4 @@ -/* $OpenBSD: pf.c,v 1.662 2009/09/16 12:28:19 henning Exp $ */ +/* $OpenBSD: pf.c,v 1.663 2009/10/04 16:08:37 michele Exp $ */ /* * Copyright (c) 2001 Daniel Hartmeier @@ -75,6 +75,7 @@ #include <netinet/udp_var.h> #include <netinet/icmp_var.h> #include <netinet/if_ether.h> +#include <netinet/ip_divert.h> #include <dev/rndvar.h> #include <net/pfvar.h> @@ -5382,6 +5383,9 @@ pf_test(int dir, struct ifnet *ifp, struct mbuf **m0, if (m->m_pkthdr.pf.flags & PF_TAG_GENERATED) return (PF_PASS); + if (m->m_pkthdr.pf.flags & PF_TAG_DIVERTED_PACKET) + return (PF_PASS); + /* packet reassembly here if 1) enabled 2) we deal with a fragment */ h = mtod(m, struct ip *); if (pf_status.reass && (h->ip_off & htons(IP_MF | IP_OFFMASK)) && @@ -5601,6 +5605,15 @@ done: } } + if (action == PF_PASS && r->divert_packet.port) { + struct pf_divert *divert; + + if ((divert = pf_get_divert(m))) + divert->port = r->divert_packet.port; + + action = PF_DIVERT; + } + if (log) { struct pf_rule *lr; struct pf_rule_item *ri; @@ -5682,6 +5695,11 @@ done: *m0 = NULL; action = PF_PASS; break; + case PF_DIVERT: + divert_packet(m, dir); + *m0 = NULL; + action = PF_PASS; + break; default: /* pf_route can free the mbuf causing *m0 to become NULL */ if (r->rt) diff --git a/sys/net/pfvar.h b/sys/net/pfvar.h index d0328ba566a..9559bec1fa8 100644 --- a/sys/net/pfvar.h +++ b/sys/net/pfvar.h @@ -1,4 +1,4 @@ -/* $OpenBSD: pfvar.h,v 1.293 2009/09/08 17:52:17 michele Exp $ */ +/* $OpenBSD: pfvar.h,v 1.294 2009/10/04 16:08:37 michele Exp $ */ /* * Copyright (c) 2001 Daniel Hartmeier @@ -60,7 +60,7 @@ struct ip6_hdr; enum { PF_INOUT, PF_IN, PF_OUT }; enum { PF_PASS, PF_DROP, PF_SCRUB, PF_NOSCRUB, PF_NAT, PF_NONAT, PF_BINAT, PF_NOBINAT, PF_RDR, PF_NORDR, PF_SYNPROXY_DROP, PF_DEFER, - PF_MATCH }; + PF_MATCH, PF_DIVERT }; enum { PF_RULESET_FILTER, PF_RULESET_NAT, PF_RULESET_BINAT, PF_RULESET_RDR, PF_RULESET_MAX }; enum { PF_OP_NONE, PF_OP_IRG, PF_OP_EQ, PF_OP_NE, PF_OP_LT, @@ -622,7 +622,7 @@ struct pf_rule { struct { struct pf_addr addr; u_int16_t port; - } divert; + } divert, divert_packet; }; /* rule flags */ diff --git a/sys/netinet/in.h b/sys/netinet/in.h index 3e9669682a2..bd7a2289ca0 100644 --- a/sys/netinet/in.h +++ b/sys/netinet/in.h @@ -1,4 +1,4 @@ -/* $OpenBSD: in.h,v 1.81 2009/09/08 17:52:17 michele Exp $ */ +/* $OpenBSD: in.h,v 1.82 2009/10/04 16:08:37 michele Exp $ */ /* $NetBSD: in.h,v 1.20 1996/02/13 23:41:47 christos Exp $ */ /* @@ -78,6 +78,10 @@ #define IPPROTO_MAX 256 +/* Only used internally, so it can be outside the range of valid IP protocols */ +#define IPPROTO_DIVERT 258 /* Divert sockets */ + + /* * From FreeBSD: * @@ -326,7 +330,7 @@ struct ip_mreq { * Third level is protocol number. * Fourth level is desired variable within that protocol. */ -#define IPPROTO_MAXID (IPPROTO_PFSYNC + 1) /* don't list to IPPROTO_MAX */ +#define IPPROTO_MAXID (IPPROTO_DIVERT + 1) /* don't list to IPPROTO_MAX */ #define CTL_IPPROTO_NAMES { \ { "ip", CTLTYPE_NODE }, \ @@ -570,6 +574,24 @@ struct ip_mreq { { 0, 0 }, \ { 0, 0 }, \ { "pfsync", CTLTYPE_NODE }, \ + { 0, 0 }, \ + { 0, 0 }, \ + { 0, 0 }, \ + { 0, 0 }, \ + { 0, 0 }, \ + { 0, 0 }, \ + { 0, 0 }, \ + { 0, 0 }, \ + { 0, 0 }, \ + { 0, 0 }, \ + { 0, 0 }, \ + { 0, 0 }, \ + { 0, 0 }, \ + { 0, 0 }, \ + { 0, 0 }, \ + { 0, 0 }, \ + { 0, 0 }, \ + { "divert", CTLTYPE_NODE }, \ } /* diff --git a/sys/netinet/in_proto.c b/sys/netinet/in_proto.c index e76139e3074..c90b80afa61 100644 --- a/sys/netinet/in_proto.c +++ b/sys/netinet/in_proto.c @@ -1,4 +1,4 @@ -/* $OpenBSD: in_proto.c,v 1.50 2009/09/08 17:52:17 michele Exp $ */ +/* $OpenBSD: in_proto.c,v 1.51 2009/10/04 16:08:37 michele Exp $ */ /* $NetBSD: in_proto.c,v 1.14 1996/02/18 18:58:32 christos Exp $ */ /* @@ -171,6 +171,11 @@ #include <net/if_pfsync.h> #endif +#include "pf.h" +#if NPF > 0 +#include <netinet/ip_divert.h> +#endif + extern struct domain inetdomain; struct protosw inetsw[] = { @@ -286,6 +291,13 @@ struct protosw inetsw[] = { 0, 0, 0, 0, pfsync_sysctl }, #endif /* NPFSYNC > 0 */ +#if NPF > 0 +{ SOCK_RAW, &inetdomain, IPPROTO_DIVERT, PR_ATOMIC|PR_ADDR, + divert_input, 0, 0, rip_ctloutput, + divert_usrreq, + divert_init, 0, 0, 0, divert_sysctl +}, +#endif /* NPF > 0 */ /* raw wildcard */ { SOCK_RAW, &inetdomain, 0, PR_ATOMIC|PR_ADDR, rip_input, rip_output, 0, rip_ctloutput, diff --git a/sys/netinet/ip_divert.c b/sys/netinet/ip_divert.c new file mode 100644 index 00000000000..b47b0059845 --- /dev/null +++ b/sys/netinet/ip_divert.c @@ -0,0 +1,335 @@ +/* $OpenBSD: ip_divert.c,v 1.3 2009/10/04 16:08:37 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/ip_divert.h> + +struct inpcbtable divbtable; +struct divstat divstat; + +#ifndef DIVERT_SENDSPACE +#define DIVERT_SENDSPACE (65536 + 100) +#endif +u_int divert_sendspace = DIVERT_SENDSPACE; +#ifndef DIVERT_RECVSPACE +#define DIVERT_RECVSPACE (65536 + 100) +#endif +u_int divert_recvspace = DIVERT_RECVSPACE; + +#ifndef DIVERTHASHSIZE +#define DIVERTHASHSIZE 128 +#endif + +int *divertctl_vars[DIVERTCTL_MAXID] = DIVERTCTL_VARS; + +int divbhashsize = DIVERTHASHSIZE; + +void divert_detach(struct inpcb *); + +void +divert_init() +{ + in_pcbinit(&divbtable, divbhashsize); +} + +void +divert_input(struct mbuf *m, ...) +{ + m_freem(m); +} + +int +divert_output(struct mbuf *m, ...) +{ + struct inpcb *inp; + struct ifqueue *inq; + struct mbuf *nam, *control; + struct sockaddr_in *sin; + 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); + + sin = mtod(nam, struct sockaddr_in *); + so = inp->inp_socket; + + m->m_pkthdr.pf.flags |= PF_TAG_DIVERTED_PACKET; + + if (sin->sin_addr.s_addr != INADDR_ANY) { + ifa = ifa_ifwithaddr((struct sockaddr *)sin, 0); + if (ifa == NULL) { + divstat.divs_errors++; + m_freem(m); + return (EADDRNOTAVAIL); + } + m->m_pkthdr.rcvif = ifa->ifa_ifp; + + inq = &ipintrq; + + s = splnet(); + IF_INPUT_ENQUEUE(inq, m); + schednetisr(NETISR_IP); + splx(s); + } else { + error = ip_output(m, (void *)NULL, &inp->inp_route, + ((so->so_options & SO_DONTROUTE) ? IP_ROUTETOIF : 0) + | IP_ALLOWBROADCAST | IP_RAWOUTPUT, (void *)NULL, + (void *)NULL); + } + + divstat.divs_opackets++; + return (error); +} + +void +divert_packet(struct mbuf *m, int dir) +{ + struct inpcb *inp; + struct socket *sa = NULL; + struct sockaddr_in addr; + struct pf_divert *pd; + + divstat.divs_ipackets++; + + if (m->m_len < sizeof(struct ip) && + (m = m_pullup(m, sizeof(struct ip))) == NULL) { + divstat.divs_errors++; + return; + } + + pd = pf_find_divert(m); + if (pd == NULL) { + divstat.divs_errors++; + m_freem(m); + return; + } + + bzero(&addr, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_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_INET) + continue; + addr.sin_addr.s_addr = ((struct sockaddr_in *) + ifa->ifa_addr)->sin_addr.s_addr; + break; + } + } + + CIRCLEQ_FOREACH(inp, &divbtable.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) { + divstat.divs_fullsock++; + m_freem(m); + return; + } else + sorwakeup(inp->inp_socket); + break; + } + + if (sa == NULL) { + divstat.divs_noport++; + m_freem(m); + } +} + +/*ARGSUSED*/ +int +divert_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 (in_control(so, (u_long)m, (caddr_t)addr, + (struct ifnet *)control)); + } + 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, &divbtable); + splx(s); + if (error) + break; + + error = soreserve(so, divert_sendspace, divert_recvspace); + if (error) + break; + ((struct inpcb *) so->so_pcb)->inp_flags |= INP_HDRINCL; + break; + + case PRU_DETACH: + divert_detach(inp); + break; + + case PRU_BIND: + s = splsoftnet(); + error = in_pcbbind(inp, addr, p); + splx(s); + break; + + case PRU_SHUTDOWN: + socantsendmore(so); + break; + + case PRU_SEND: + return (divert_output(m, inp, addr, control)); + + case PRU_ABORT: + soisdisconnected(so); + divert_detach(inp); + break; + + case PRU_SOCKADDR: + in_setsockaddr(inp, addr); + break; + + case PRU_PEERADDR: + in_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("divert_usrreq"); + } + +release: + if (control) { + m_freem(control); + } + if (m) + m_freem(m); + return (error); +} + +void +divert_detach(struct inpcb *inp) +{ + int s = splsoftnet(); + + in_pcbdetach(inp); + splx(s); +} + +/* + * Sysctl for divert variables. + */ +int +divert_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 DIVERTCTL_SENDSPACE: + return (sysctl_int(oldp, oldlenp, newp, newlen, + &divert_sendspace)); + case DIVERTCTL_RECVSPACE: + return (sysctl_int(oldp, oldlenp, newp, newlen, + &divert_recvspace)); + case DIVERTCTL_STATS: + if (newp != NULL) + return (EPERM); + return (sysctl_struct(oldp, oldlenp, newp, newlen, + &divstat, sizeof(divstat))); + default: + if (name[0] < DIVERTCTL_MAXID) + return sysctl_int_arr(divertctl_vars, name, namelen, + oldp, oldlenp, newp, newlen); + + return (ENOPROTOOPT); + } + /* NOTREACHED */ +} diff --git a/sys/netinet/ip_divert.h b/sys/netinet/ip_divert.h new file mode 100644 index 00000000000..4e1b05f7695 --- /dev/null +++ b/sys/netinet/ip_divert.h @@ -0,0 +1,65 @@ +/* $OpenBSD: ip_divert.h,v 1.3 2009/10/04 16:08:37 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. + */ + +#ifndef _IP_DIVERT_H_ +#define _IP_DIVERT_H_ + +struct divstat { + u_long divs_ipackets; /* total input packets */ + u_long divs_noport; /* no socket on port */ + u_long divs_fullsock; /* not delivered, input socket full */ + u_long divs_opackets; /* total output packets */ + u_long divs_errors; /* generic errors */ +}; + +/* + * Names for divert sysctl objects + */ +#define DIVERTCTL_RECVSPACE 1 /* receive buffer space */ +#define DIVERTCTL_SENDSPACE 2 /* send buffer space */ +#define DIVERTCTL_STATS 3 /* divert statistics */ +#define DIVERTCTL_MAXID 4 + +#define DIVERTCTL_NAMES { \ + { 0, 0 }, \ + { "recvspace", CTLTYPE_INT }, \ + { "sendspace", CTLTYPE_INT }, \ + { "stats", CTLTYPE_STRUCT } \ +} + +#define DIVERTCTL_VARS { \ + NULL, \ + &divert_recvspace, \ + &divert_sendspace, \ + NULL \ +} + +#ifdef _KERNEL +extern struct inpcbtable divbtable; +extern struct divstat divstat; + +void divert_init(void); +void divert_input(struct mbuf *, ...); +void divert_packet(struct mbuf *, int); +int divert_output(struct mbuf *, ...); +int divert_sysctl(int *, u_int, void *, size_t *, void *, size_t); +int divert_usrreq(struct socket *, + int, struct mbuf *, struct mbuf *, struct mbuf *, struct proc *); + +#endif /* _KERNEL */ +#endif /* _IP_DIVERT_H_ */ diff --git a/sys/sys/mbuf.h b/sys/sys/mbuf.h index 09572fc3540..6773c4abf83 100644 --- a/sys/sys/mbuf.h +++ b/sys/sys/mbuf.h @@ -1,4 +1,4 @@ -/* $OpenBSD: mbuf.h,v 1.136 2009/09/13 14:42:52 krw Exp $ */ +/* $OpenBSD: mbuf.h,v 1.137 2009/10/04 16:08:37 michele Exp $ */ /* $NetBSD: mbuf.h,v 1.19 1996/02/09 18:25:14 christos Exp $ */ /* @@ -90,6 +90,7 @@ struct pkthdr_pf { #define PF_TAG_FRAGCACHE 0x02 #define PF_TAG_TRANSLATE_LOCALHOST 0x04 #define PF_TAG_DIVERTED 0x08 +#define PF_TAG_DIVERTED_PACKET 0x10 /* record/packet header in first mbuf of chain; valid if M_PKTHDR set */ struct pkthdr { |