/*-
* Copyright (c) 1982, 1986, 1988, 1993
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD: src/sys/netinet/ip_divert.c,v 1.113.2.3 2006/12/29 19:25:48 jhb Exp $
*/
#if !defined(KLD_MODULE)
#include "opt_inet.h"
#include "opt_ipfw.h"
#include "opt_mac.h"
#ifndef INET6
#error "IPD6IVERT requires INET.6"
#endif
#ifndef IP6FIREWALL
#error "IP6DIVERT requires IP6FIREWALL"
#endif
#endif
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/mac.h>
#include <sys/mbuf.h>
#include <sys/module.h>
#include <sys/kernel.h>
#include <sys/proc.h>
#include <sys/protosw.h>
#include <sys/signalvar.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/sx.h>
#include <sys/sysctl.h>
#include <sys/systm.h>
#include <vm/uma.h>
#include <net/if.h>
#include <net/route.h>
#include <netinet/in.h>
#include <netinet/in_pcb.h>
#include <netinet6/in6_pcb.h>
#include <netinet/in_systm.h>
#include <netinet6/in6_var.h>
#include <netinet/ip6.h>
#include <netdivert/divert.h>
#include <netinet6/ip6_divert.h>
#include <netinet6/ip6_var.h>
#include <netinet6/ip6_fw.h>
/*
* Divert sockets
*/
/*
* Allocate enough space to hold a full IP packet
*/
#define DIVSNDQ (65536 + 100)
#define DIVRCVQ (65536 + 100)
/*
* Divert sockets work in conjunction with ipfw, see the divert(4)
* manpage for features.
* Internally, packets selected by ipfw in ip6_input() or ip6_output(),
* and never diverted before, are passed to the input queue of the
* divert socket with a given 'divert_port' number (as specified in
* the matching ipfw rule), and they are tagged with a 16 bit cookie
* (representing the rule number of the matching ipfw rule), which
* is passed to process reading from the socket.
*
* Packets written to the divert socket are again tagged with a cookie
* (usually the same as above) and a destination address.
* If the destination address is INADDR_ANY then the packet is
* treated as outgoing and sent to ip6_output(), otherwise it is
* treated as incoming and sent to ip6_input().
* In both cases, the packet is tagged with the cookie.
*
* On reinjection, processing in ip6_input() and ip6_output()
* will be exactly the same as for the original packet, except that
* ipfw processing will start at the rule number after the one
* written in the cookie (so, tagging a packet with a cookie of 0
* will cause it to be effectively considered as a standard packet).
*/
/* Internal variables. */
static struct inpcbhead divcb;
static struct inpcbinfo divcbinfo;
static u_long div_sendspace = DIVSNDQ; /* XXX sysctl ? */
static u_long div_recvspace = DIVRCVQ; /* XXX sysctl ? */
/*
* Initialize divert connection block queue.
*/
static void
div6_zone_change(void *tag)
{
uma_zone_set_max(divcbinfo.ipi_zone, maxsockets);
}
static int
div6_inpcb_init(void *mem, int size, int flags)
{
struct inpcb *inp = mem;
INP_LOCK_INIT(inp, "inp", "divinp");
return (0);
}
static void
div6_inpcb_fini(void *mem, int size)
{
struct inpcb *inp = mem;
INP_LOCK_DESTROY(inp);
}
void
div6_init(void)
{
INP_INFO_LOCK_INIT(&divcbinfo, "div");
LIST_INIT(&divcb);
divcbinfo.listhead = &divcb;
/*
* XXX We don't use the hash list for divert IP, but it's easier
* to allocate a one entry hash list than it is to check all
* over the place for hashbase == NULL.
*/
divcbinfo.hashbase = hashinit(1, M_PCB, &divcbinfo.hashmask);
divcbinfo.porthashbase = hashinit(1, M_PCB, &divcbinfo.porthashmask);
divcbinfo.ipi_zone = uma_zcreate("div6cb", sizeof(struct in6pcb),
NULL, NULL, div6_inpcb_init, div6_inpcb_fini, UMA_ALIGN_PTR,
UMA_ZONE_NOFREE);
uma_zone_set_max(divcbinfo.ipi_zone, maxsockets);
EVENTHANDLER_REGISTER(maxsockets_change, div6_zone_change,
NULL, EVENTHANDLER_PRI_ANY);
}
/*
* IPPROTO_DIVERT is not in the real IP protocol number space; this
* function should never be called. Just in case, drop any packets.
*/
void
div6_input(struct mbuf *m, int off)
{
ip6stat.ip6s_noproto++;
m_freem(m);
}
/*
* Divert a packet by passing it up to the divert socket at port 'port'.
*
* Setup generic address and protocol structures for div_input routine,
* then pass them along with mbuf chain.
*/
static void
divert6_packet(struct mbuf *m, int incoming)
{
struct in6pcb *in6p;
struct socket *sa;
u_int16_t nport;
struct sockaddr_div div6src;
struct m_tag *mtag;
mtag = m_tag_find(m, PACKET_TAG_DIVERT, NULL);
if (mtag == NULL) {
printf("%s: no divert tag\n", __func__);
m_freem(m);
return;
}
/*
* Record receive interface address, if any.
* But only for incoming packets.
*/
memset(&div6src, 0, sizeof(div6src));
div6src.div_len = sizeof(div6src);
div6src.div_family = AF_INET6;
div6src.div_cookie = divert6_cookie(mtag); /* record matching rule */
if (incoming) {
struct ifaddr *ifa;
/* Sanity check */
M_ASSERTPKTHDR(m);
/* Find IP address for receive interface */
TAILQ_FOREACH(ifa, &m->m_pkthdr.rcvif->if_addrhead, ifa_link) {
if (ifa->ifa_addr == NULL)
continue;
if (ifa->ifa_addr->sa_family != AF_INET6)
continue;
memcpy(&div6src.div6_addr,
&((struct sockaddr_div *) ifa->ifa_addr)->div6_addr,
sizeof(struct in6_addr));
break;
}
}
/*
* Record the incoming interface name whenever we have one.
*/
if (m->m_pkthdr.rcvif) {
memset(div6src.div_iface, 0, sizeof(div6src.div_iface));
strlcpy(div6src.div_iface, m->m_pkthdr.rcvif->if_xname,
sizeof(div6src.div_iface));
}
/* Put packet on socket queue, if any */
sa = NULL;
nport = htons((u_int16_t)divert6_info(mtag));
INP_INFO_RLOCK(&divcbinfo);
LIST_FOREACH(in6p, &divcb, inp_list) {
INP_LOCK(in6p);
/* XXX why does only one socket match? */
if (in6p->inp_lport == nport) {
sa = in6p->inp_socket;
SOCKBUF_LOCK(&sa->so_rcv);
if (sbappendaddr_locked(&sa->so_rcv,
(struct sockaddr *)&div6src, m,
(struct mbuf *)0) == 0) {
SOCKBUF_UNLOCK(&sa->so_rcv);
sa = NULL; /* force mbuf reclaim below */
} else
sorwakeup_locked(sa);
INP_UNLOCK(in6p);
break;
}
INP_UNLOCK(in6p);
}
INP_INFO_RUNLOCK(&divcbinfo);
if (sa == NULL) {
m_freem(m);
ip6stat.ip6s_noproto++;
ip6stat.ip6s_delivered--;
}
}
/*
* Deliver packet back into the IP processing machinery.
*
* If no address specified, or address is 0.0.0.0, send to ip_output();
* otherwise, send to ip_input() and mark as having been received on
* the interface with that address.
*/
static int
div6_output(struct socket *so, struct mbuf *m,
struct sockaddr_div *sdiv, struct mbuf *control)
{
struct m_tag *mtag;
struct divert6_tag *dt;
int error = 0;
printf("div6_output\n");
/*
* An mbuf may hasn't come from userland, but we pretend
* that it has.
*/
m->m_pkthdr.rcvif = NULL;
m->m_nextpkt = NULL;
if (control)
m_freem(control); /* XXX */
if ((mtag = m_tag_find(m, PACKET_TAG_DIVERT, NULL)) == NULL) {
mtag = m_tag_get(PACKET_TAG_DIVERT, sizeof(struct divert6_tag),
M_NOWAIT | M_ZERO);
if (mtag == NULL) {
error = ENOBUFS;
goto cantsend;
}
dt = (struct divert6_tag *)(mtag+1);
m_tag_prepend(m, mtag);
} else
dt = (struct divert6_tag *)(mtag+1);
/* Loopback avoidance and state recovery */
if (sdiv) {
int i;
dt->cookie = sdiv->div_cookie;
for (i = 0; i < sizeof(sdiv->div_iface) && sdiv->div_iface[i]; i++)
;
if ( i > 0 && i < sizeof(sdiv->div_iface))
m->m_pkthdr.rcvif = ifunit(sdiv->div_iface);
}
/* Reinject packet into the system as incoming or outgoing */
if (!sdiv || IN6_IS_ADDR_UNSPECIFIED(&(sdiv->div6_addr))) {
//struct ip6_hdr *const ip6 = mtod(m, struct ip6_hdr *);
struct in6pcb *in6p;
dt->info |= IPV6_FW_DIVERT_OUTPUT_FLAG;
INP_INFO_WLOCK(&divcbinfo);
in6p = sotoinpcb(so);
INP_LOCK(in6p);
/*
* Don't allow both user specified and setsockopt options,
* and don't allow packet length sizes that will crash
*/
// EG - NFI
#ifdef NFI
if (((ip6->ip_hl != (sizeof (*ip6) >> 2)) && in6p->inp_options) ||
((u_short)ntohs(ip6->ip_len) > m->m_pkthdr.len)) {
error = EINVAL;
m_freem(m);
} else {
/* Convert fields to host order for ip_output() */
ip6->ip_len = ntohs(ip6->ip_len);
ip6->ip_off = ntohs(ip6->ip_off);
/* Send packet to output processing */
ip6stat.ip6s_rawout++; /* XXX */
#ifdef MAC
mac_create_mbuf_from_inpcb(inp, m);
#endif
error = ip_output(m,
in6p->inp_options, NULL,
((so->so_options & SO_DONTROUTE) ?
IP_ROUTETOIF : 0) |
IP_ALLOWBROADCAST | IP_RAWOUTPUT,
inp->inp_moptions, NULL);
}
#endif /* NFI */
INP_UNLOCK(in6p);
INP_INFO_WUNLOCK(&divcbinfo);
} else {
dt->info |= IPV6_FW_DIVERT_LOOPBACK_FLAG;
if (m->m_pkthdr.rcvif == NULL) {
/*
* No luck with the name, check by IP address.
* Clear the port and the ifname to make sure
* there are no distractions for ifa_ifwithaddr.
*/
struct ifaddr *ifa;
memset(sdiv->div_iface, 0, sizeof(sdiv->div_iface));
sdiv->div_cookie = 0;
ifa = ifa_ifwithaddr((struct sockaddr *) sdiv);
if (ifa == NULL) {
error = EADDRNOTAVAIL;
goto cantsend;
}
m->m_pkthdr.rcvif = ifa->ifa_ifp;
}
#ifdef MAC
SOCK_LOCK(so);
mac_create_mbuf_from_socket(so, m);
SOCK_UNLOCK(so);
#endif
/* Send packet to input processing */
ip6_input(m);
}
return error;
cantsend:
m_freem(m);
return error;
}
static int
div6_attach(struct socket *so, int proto, struct thread *td)
{
struct in6pcb *in6p;
int error;
INP_INFO_WLOCK(&divcbinfo);
in6p = sotoin6pcb(so);
if (in6p != 0) {
INP_INFO_WUNLOCK(&divcbinfo);
return EINVAL;
}
if (td && (error = suser(td)) != 0) {
INP_INFO_WUNLOCK(&divcbinfo);
return error;
}
error = soreserve(so, div_sendspace, div_recvspace);
if (error) {
INP_INFO_WUNLOCK(&divcbinfo);
return error;
}
error = in_pcballoc(so, &divcbinfo);
if (error) {
INP_INFO_WUNLOCK(&divcbinfo);
return error;
}
in6p = (struct in6pcb *)so->so_pcb;
INP_INFO_WUNLOCK(&divcbinfo);
in6p->inp_ip_p = proto;
in6p->inp_vflag |= INP_IPV6;
in6p->inp_flags |= INP_HDRINCL;
INP_UNLOCK(in6p);
return 0;
}
static int
div6_detach(struct socket *so)
{
struct in6pcb *in6p;
INP_INFO_WLOCK(&divcbinfo);
in6p = sotoin6pcb(so);
if (in6p == 0) {
INP_INFO_WUNLOCK(&divcbinfo);
return EINVAL;
}
INP_LOCK(in6p);
in_pcbdetach(in6p);
INP_INFO_WUNLOCK(&divcbinfo);
return 0;
}
static int
div6_bind(struct socket *so, struct sockaddr *nam, struct thread *td)
{
struct in6pcb *in6p;
int error;
printf("div6_bind\n");
INP_INFO_WLOCK(&divcbinfo);
in6p = sotoin6pcb(so);
if (in6p == 0) {
INP_INFO_WUNLOCK(&divcbinfo);
return EINVAL;
}
/* in_pcbbind assumes that nam is a sockaddr_in6
* and in_pcbbind requires a valid address. Since divert
* sockets don't we need to make sure the address is
* filled in properly.
* XXX -- divert should not be abusing in_pcbind
* and should probably have its own family.
*/
if (nam->sa_family != AF_INET6)
error = EAFNOSUPPORT;
else {
((struct sockaddr_in6 *)nam)->sin6_addr = in6addr_any;
INP_LOCK(in6p);
error = in6_pcbbind(in6p, nam, td->td_ucred);
INP_UNLOCK(in6p);
}
INP_INFO_WUNLOCK(&divcbinfo);
return error;
}
static int
div6_shutdown(struct socket *so)
{
struct in6pcb *in6p;
INP_INFO_RLOCK(&divcbinfo);
in6p = sotoin6pcb(so);
if (in6p == 0) {
INP_INFO_RUNLOCK(&divcbinfo);
return EINVAL;
}
INP_LOCK(in6p);
INP_INFO_RUNLOCK(&divcbinfo);
socantsendmore(so);
INP_UNLOCK(in6p);
return 0;
}
static int
div6_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *nam,
struct mbuf *control, struct thread *td)
{
printf("div6_send\n");
/* Packet must have a header (but that's about it) */
if (m->m_len < sizeof (struct ip6_hdr) &&
(m = m_pullup(m, sizeof (struct ip6_hdr))) == 0) {
ip6stat.ip6s_toosmall++;
m_freem(m);
return EINVAL;
}
/* Send packet */
return div6_output(so, m, (struct sockaddr_div *)nam, control);
}
void
div6_ctlinput(int cmd, struct sockaddr *sa, void *vip)
{
struct in6_addr faddr;
faddr = ((struct sockaddr_in6 *)sa)->sin6_addr;
if (sa->sa_family != AF_INET6 || IN6_IS_ADDR_UNSPECIFIED(&faddr))
return;
if (PRC_IS_REDIRECT(cmd))
return;
}
#ifdef NFI
static int
div6_pcblist(SYSCTL_HANDLER_ARGS)
{
int error, i, n;
struct in6pcb *in6p, **in6p_list;
// EG - NFI
inp_gen_t gencnt;
struct xinpgen xig;
/*
* The process of preparing the TCB list is too time-consuming and
* resource-intensive to repeat twice on every request.
*/
if (req->oldptr == 0) {
n = divcbinfo.ipi_count;
req->oldidx = 2 * (sizeof xig)
+ (n + n/8) * sizeof(struct xinpcb);
return 0;
}
if (req->newptr != 0)
return EPERM;
/*
* OK, now we're committed to doing something.
*/
INP_INFO_RLOCK(&divcbinfo);
gencnt = divcbinfo.ipi_gencnt;
n = divcbinfo.ipi_count;
INP_INFO_RUNLOCK(&divcbinfo);
error = sysctl_wire_old_buffer(req,
2 * sizeof(xig) + n*sizeof(struct xinpcb));
if (error != 0)
return (error);
xig.xig_len = sizeof xig;
xig.xig_count = n;
xig.xig_gen = gencnt;
xig.xig_sogen = so_gencnt;
error = SYSCTL_OUT(req, &xig, sizeof xig);
if (error)
return error;
in6p_list = malloc(n * sizeof *in6p_list, M_TEMP, M_WAITOK);
if (in6p_list == NULL)
return ENOMEM;
INP_INFO_RLOCK(&divcbinfo);
for (in6p = LIST_FIRST(divcbinfo.listhead), i = 0; in6p && i < n;
in6p = LIST_NEXT(in6p, in6p_list)) {
INP_LOCK(in6p);
if (in6p->in6p_gencnt <= gencnt &&
cr_canseesocket(req->td->td_ucred, in6p->in6p_socket) == 0)
in6p_list[i++] = in6p;
INP_UNLOCK(in6p);
}
INP_INFO_RUNLOCK(&divcbinfo);
n = i;
error = 0;
for (i = 0; i < n; i++) {
in6p = in6p_list[i];
INP_LOCK(in6p);
if (in6p->in6p_gencnt <= gencnt) {
struct xinpcb xi;
memset(&xi, 0, sizeof(xi));
xi.xi_len = sizeof xi;
/* XXX should avoid extra copy */
bcopy(in6p, &xi.xi_in6p, sizeof *in6p);
if (in6p->in6p_socket)
sotoxsocket(in6p->in6p_socket, &xi.xi_socket);
INP_UNLOCK(in6p);
error = SYSCTL_OUT(req, &xi, sizeof xi);
} else
INP_UNLOCK(in6p);
}
if (!error) {
/*
* Give the user an updated idea of our state.
* If the generation differs from what we told
* her before, she knows that something happened
* while we were processing this request, and it
* might be necessary to retry.
*/
INP_INFO_RLOCK(&divcbinfo);
xig.xig_gen = divcbinfo.ipi_gencnt;
xig.xig_sogen = so_gencnt;
xig.xig_count = divcbinfo.ipi_count;
INP_INFO_RUNLOCK(&divcbinfo);
error = SYSCTL_OUT(req, &xig, sizeof xig);
}
free(in6p_list, M_TEMP);
return error;
}
#endif
/*
* This is the wrapper function for in_setsockaddr. We just pass down
* the pcbinfo for in_setpeeraddr to lock.
*/
static int
div6_sockaddr(struct socket *so, struct sockaddr **nam)
{
return (in6_setsockaddr(so, nam));
}
/*
* This is the wrapper function for in_setpeeraddr. We just pass down
* the pcbinfo for in_setpeeraddr to lock.
*/
static int
div6_peeraddr(struct socket *so, struct sockaddr **nam)
{
return (in6_setpeeraddr(so, nam));
}
#ifdef NFI
#ifdef SYSCTL_NODE
SYSCTL_NODE(_net_inet6, IPPROTO_DIVERT, divert6, CTLFLAG_RW, 0, "IP6DIVERT");
SYSCTL_PROC(_net_inet6_divert, OID_AUTO, pcblist, CTLFLAG_RD, 0, 0,
div6_pcblist, "S,xinpcb", "List of active divert sockets");
#endif
#endif
struct pr_usrreqs div6_usrreqs = {
.pru_attach = div6_attach,
.pru_bind = div6_bind,
.pru_control = in6_control,
.pru_detach = div6_detach,
.pru_peeraddr = div6_peeraddr,
.pru_send = div6_send,
.pru_shutdown = div6_shutdown,
.pru_sockaddr = div6_sockaddr,
.pru_sosetlabel = in_pcbsosetlabel
};
struct protosw div6_protosw = {
.pr_type = SOCK_RAW,
.pr_protocol = IPPROTO_DIVERT,
.pr_flags = PR_ATOMIC|PR_ADDR,
.pr_input = div6_input,
.pr_ctlinput = div6_ctlinput,
.pr_ctloutput = ip6_ctloutput,
.pr_init = div6_init,
.pr_usrreqs = &div6_usrreqs
};
static int
div6_modevent(module_t mod, int type, void *unused)
{
int err = 0;
int n;
switch (type) {
case MOD_LOAD:
/*
* Protocol will be initialized by pf_proto_register().
* We don't have to register ip_protox because we are not
* a true IP protocol that goes over the wire.
*/
err = pf_proto_register(PF_INET6, &div6_protosw);
if (err != 0) {
printf("div6_modevent: pf_proto_register() returned %d\n",
err);
}
ip6_divert_ptr = divert6_packet;
break;
case MOD_QUIESCE:
/*
* IPDIVERT may normally not be unloaded because of the
* potential race conditions. Tell kldunload we can't be
* unloaded unless the unload is forced.
*/
err = EPERM;
break;
case MOD_UNLOAD:
/*
* Forced unload.
*
* Module ipdivert can only be unloaded if no sockets are
* connected. Maybe this can be changed later to forcefully
* disconnect any open sockets.
*
* XXXRW: Note that there is a slight race here, as a new
* socket open request could be spinning on the lock and then
* we destroy the lock.
*/
INP_INFO_WLOCK(&divcbinfo);
n = divcbinfo.ipi_count;
if (n != 0) {
err = EBUSY;
INP_INFO_WUNLOCK(&divcbinfo);
break;
}
ip6_divert_ptr = NULL;
err = pf_proto_unregister(PF_INET6, IPPROTO_DIVERT, SOCK_RAW);
INP_INFO_WUNLOCK(&divcbinfo);
INP_INFO_LOCK_DESTROY(&divcbinfo);
uma_zdestroy(divcbinfo.ipi_zone);
break;
default:
err = EOPNOTSUPP;
break;
}
return err;
}
static moduledata_t ip6divertmod = {
"ip6divert",
div6_modevent,
0
};
DECLARE_MODULE(ip6divert, ip6divertmod, SI_SUB_PROTO_IFATTACHDOMAIN, SI_ORDER_ANY);
MODULE_DEPEND(dummynet, ipfw, 2, 2, 2);
MODULE_VERSION(ip6divert, 1);