freebsd-dev/sys/contrib/pf/net/if_pfsync.c
2004-04-14 00:57:49 +00:00

556 lines
12 KiB
C

/* $FreeBSD$ */
/* $OpenBSD: if_pfsync.c,v 1.6 2003/06/21 09:07:01 djm Exp $ */
/*
* Copyright (c) 2002 Michael Shalayeff
* 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.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR OR HIS RELATIVES 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 MIND, 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.
*/
#ifdef __FreeBSD__
#include "opt_inet.h"
#include "opt_inet6.h"
#endif
#ifndef __FreeBSD__
#include "bpfilter.h"
#include "pfsync.h"
#elif __FreeBSD__ >= 5
#include "opt_bpf.h"
#include "opt_pf.h"
#define NBPFILTER DEV_BPF
#define NPFSYNC DEV_PFSYNC
#endif
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/time.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#ifdef __FreeBSD__
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/sockio.h>
#else
#include <sys/ioctl.h>
#include <sys/timeout.h>
#endif
#include <net/if.h>
#include <net/if_types.h>
#include <net/route.h>
#include <net/bpf.h>
#ifdef INET
#include <netinet/in.h>
#include <netinet/in_var.h>
#endif
#ifdef INET6
#ifndef INET
#include <netinet/in.h>
#endif
#include <netinet6/nd6.h>
#endif /* INET6 */
#include <net/pfvar.h>
#include <net/if_pfsync.h>
#ifdef __FreeBSD__
#define PFSYNCNAME "pfsync"
#endif
#define PFSYNC_MINMTU \
(sizeof(struct pfsync_header) + sizeof(struct pf_state))
#ifdef PFSYNCDEBUG
#define DPRINTF(x) do { if (pfsyncdebug) printf x ; } while (0)
int pfsyncdebug;
#else
#define DPRINTF(x)
#endif
#ifndef __FreeBSD__
struct pfsync_softc pfsyncif;
#endif
#ifdef __FreeBSD__
static void pfsync_clone_destroy(struct ifnet *);
static int pfsync_clone_create(struct if_clone *, int);
#else
void pfsyncattach(int);
#endif
void pfsync_setmtu(struct pfsync_softc *sc, int);
int pfsyncoutput(struct ifnet *, struct mbuf *, struct sockaddr *,
struct rtentry *);
int pfsyncioctl(struct ifnet *, u_long, caddr_t);
void pfsyncstart(struct ifnet *);
struct mbuf *pfsync_get_mbuf(struct pfsync_softc *sc, u_int8_t action);
int pfsync_sendout(struct pfsync_softc *sc);
void pfsync_timeout(void *v);
#ifndef __FreeBSD__
extern int ifqmaxlen;
#endif
#ifdef __FreeBSD__
static MALLOC_DEFINE(M_PFSYNC, PFSYNCNAME, "Packet Filter State Sync. Interface");
static LIST_HEAD(pfsync_list, pfsync_softc) pfsync_list;
struct if_clone pfsync_cloner = IF_CLONE_INITIALIZER(PFSYNCNAME,
pfsync_clone_create, pfsync_clone_destroy, 1, IF_MAXUNIT);
static void
pfsync_clone_destroy(struct ifnet *ifp)
{
struct pfsync_softc *sc;
sc = ifp->if_softc;
callout_stop(&sc->sc_tmo);
/*
* Does we really need this?
*/
IF_DRAIN(&ifp->if_snd);
#if NBPFILTER > 0
bpfdetach(ifp);
#endif
if_detach(ifp);
LIST_REMOVE(sc, sc_next);
free(sc, M_PFSYNC);
}
static int
pfsync_clone_create(struct if_clone *ifc, int unit)
{
struct pfsync_softc *sc;
MALLOC(sc, struct pfsync_softc *, sizeof(*sc), M_PFSYNC,
M_WAITOK|M_ZERO);
sc->sc_count = 8;
#if (__FreeBSD_version < 501113)
sc->sc_if.if_name = PFSYNCNAME;
sc->sc_if.if_unit = unit;
#else
if_initname(&sc->sc_if, ifc->ifc_name, unit);
#endif
sc->sc_if.if_ioctl = pfsyncioctl;
sc->sc_if.if_output = pfsyncoutput;
sc->sc_if.if_start = pfsyncstart;
sc->sc_if.if_type = IFT_PFSYNC;
sc->sc_if.if_snd.ifq_maxlen = ifqmaxlen;
sc->sc_if.if_hdrlen = PFSYNC_HDRLEN;
sc->sc_if.if_baudrate = IF_Mbps(100);
sc->sc_if.if_softc = sc;
pfsync_setmtu(sc, MCLBYTES);
/*
* XXX
* The 2nd arg. 0 to callout_init(9) shoule be set to CALLOUT_MPSAFE
* if Gaint lock is removed from the network stack.
*/
callout_init(&sc->sc_tmo, 0);
if_attach(&sc->sc_if);
LIST_INSERT_HEAD(&pfsync_list, sc, sc_next);
#if NBPFILTER > 0
bpfattach(&sc->sc_if, DLT_PFSYNC, PFSYNC_HDRLEN);
#endif
return (0);
}
#else /* !__FreeBSD__ */
void
pfsyncattach(int npfsync)
{
struct ifnet *ifp;
pfsyncif.sc_mbuf = NULL;
pfsyncif.sc_ptr = NULL;
pfsyncif.sc_count = 8;
ifp = &pfsyncif.sc_if;
strlcpy(ifp->if_xname, "pfsync0", sizeof ifp->if_xname);
ifp->if_softc = &pfsyncif;
ifp->if_ioctl = pfsyncioctl;
ifp->if_output = pfsyncoutput;
ifp->if_start = pfsyncstart;
ifp->if_type = IFT_PFSYNC;
ifp->if_snd.ifq_maxlen = ifqmaxlen;
ifp->if_hdrlen = PFSYNC_HDRLEN;
ifp->if_baudrate = IF_Mbps(100);
pfsync_setmtu(&pfsyncif, MCLBYTES);
timeout_set(&pfsyncif.sc_tmo, pfsync_timeout, &pfsyncif);
if_attach(ifp);
if_alloc_sadl(ifp);
#if NBPFILTER > 0
bpfattach(&pfsyncif.sc_if.if_bpf, ifp, DLT_PFSYNC, PFSYNC_HDRLEN);
#endif
}
#endif
/*
* Start output on the pfsync interface.
*/
void
pfsyncstart(struct ifnet *ifp)
{
struct mbuf *m;
#if defined(__FreeBSD__) && defined(ALTQ)
struct ifaltq *ifq;
#else
struct ifqueue *ifq;
#endif
int s;
#ifdef __FreeBSD__
ifq = &ifp->if_snd;
#endif
for (;;) {
s = splimp();
#ifdef __FreeBSD__
IF_LOCK(ifq);
_IF_DROP(ifq);
_IF_DEQUEUE(ifq, m);
IF_UNLOCK(ifq);
#else
IF_DROP(&ifp->if_snd);
IF_DEQUEUE(&ifp->if_snd, m);
#endif
splx(s);
if (m == NULL)
return;
else
m_freem(m);
}
}
int
pfsyncoutput(struct ifnet *ifp, struct mbuf *m, struct sockaddr *dst,
struct rtentry *rt)
{
m_freem(m);
return (0);
}
/* ARGSUSED */
int
pfsyncioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
{
struct pfsync_softc *sc = ifp->if_softc;
struct ifreq *ifr = (struct ifreq *)data;
int s;
switch (cmd) {
case SIOCSIFADDR:
case SIOCAIFADDR:
case SIOCSIFDSTADDR:
case SIOCSIFFLAGS:
if (ifp->if_flags & IFF_UP)
ifp->if_flags |= IFF_RUNNING;
else
ifp->if_flags &= ~IFF_RUNNING;
break;
case SIOCSIFMTU:
if (ifr->ifr_mtu < PFSYNC_MINMTU)
return (EINVAL);
if (ifr->ifr_mtu > MCLBYTES)
ifr->ifr_mtu = MCLBYTES;
s = splnet();
if (ifr->ifr_mtu < ifp->if_mtu)
pfsync_sendout(sc);
pfsync_setmtu(sc, ifr->ifr_mtu);
splx(s);
break;
default:
return (ENOTTY);
}
return (0);
}
void
pfsync_setmtu(sc, mtu)
struct pfsync_softc *sc;
int mtu;
{
sc->sc_count = (mtu - sizeof(struct pfsync_header)) /
sizeof(struct pf_state);
sc->sc_if.if_mtu = sizeof(struct pfsync_header) +
sc->sc_count * sizeof(struct pf_state);
}
struct mbuf *
pfsync_get_mbuf(sc, action)
struct pfsync_softc *sc;
u_int8_t action;
{
#ifndef __FreeBSD__
extern int hz;
#endif
struct pfsync_header *h;
struct mbuf *m;
int len;
MGETHDR(m, M_DONTWAIT, MT_DATA);
if (m == NULL) {
sc->sc_if.if_oerrors++;
return (NULL);
}
len = sc->sc_if.if_mtu;
if (len > MHLEN) {
MCLGET(m, M_DONTWAIT);
if ((m->m_flags & M_EXT) == 0) {
m_free(m);
sc->sc_if.if_oerrors++;
return (NULL);
}
}
m->m_pkthdr.rcvif = NULL;
m->m_pkthdr.len = m->m_len = len;
h = mtod(m, struct pfsync_header *);
h->version = PFSYNC_VERSION;
h->af = 0;
h->count = 0;
h->action = action;
sc->sc_mbuf = m;
sc->sc_ptr = (struct pf_state *)((char *)h + PFSYNC_HDRLEN);
#ifdef __FreeBSD__
callout_reset(&sc->sc_tmo, hz, pfsync_timeout,
LIST_FIRST(&pfsync_list));
#else
timeout_add(&sc->sc_tmo, hz);
#endif
return (m);
}
/*
* XXX: This function should be called with PF_LOCK held as it references
* pf_state.
*/
int
pfsync_pack_state(action, st)
u_int8_t action;
struct pf_state *st;
{
#ifdef __FreeBSD__
struct pfsync_softc *sc = LIST_FIRST(&pfsync_list);
#else
extern struct timeval time;
struct ifnet *ifp = &pfsyncif.sc_if;
struct pfsync_softc *sc = ifp->if_softc;
#endif
struct pfsync_header *h;
struct pf_state *sp;
struct pf_rule *r = st->rule.ptr;
struct mbuf *m;
u_long secs;
int s, ret;
if (action >= PFSYNC_ACT_MAX)
return (EINVAL);
#ifdef __FreeBSD__
/*
* XXX
* If we need to check mutex owned, PF_LOCK should be
* declared in pflog.ko.
*
* PF_LOCK_ASSERT();
*/
KASSERT((!LIST_EMPTY(&pfsync_list)), ("pfsync: no interface"));
#endif
s = splnet();
m = sc->sc_mbuf;
if (m == NULL) {
if ((m = pfsync_get_mbuf(sc, action)) == NULL) {
splx(s);
return (ENOMEM);
}
h = mtod(m, struct pfsync_header *);
} else {
h = mtod(m, struct pfsync_header *);
if (h->action != action) {
pfsync_sendout(sc);
if ((m = pfsync_get_mbuf(sc, action)) == NULL) {
splx(s);
return (ENOMEM);
}
h = mtod(m, struct pfsync_header *);
}
}
sp = sc->sc_ptr++;
h->count++;
bzero(sp, sizeof(*sp));
bcopy(&st->lan, &sp->lan, sizeof(sp->lan));
bcopy(&st->gwy, &sp->gwy, sizeof(sp->gwy));
bcopy(&st->ext, &sp->ext, sizeof(sp->ext));
pf_state_peer_hton(&st->src, &sp->src);
pf_state_peer_hton(&st->dst, &sp->dst);
bcopy(&st->rt_addr, &sp->rt_addr, sizeof(sp->rt_addr));
#ifdef __FreeBSD__
secs = time_second;
#else
secs = time.tv_sec;
#endif
sp->creation = htonl(secs - st->creation);
if (st->expire <= secs)
sp->expire = htonl(0);
else
sp->expire = htonl(st->expire - secs);
sp->packets[0] = htonl(st->packets[0]);
sp->packets[1] = htonl(st->packets[1]);
sp->bytes[0] = htonl(st->bytes[0]);
sp->bytes[1] = htonl(st->bytes[1]);
if (r == NULL)
sp->rule.nr = htonl(-1);
else
sp->rule.nr = htonl(r->nr);
sp->af = st->af;
sp->proto = st->proto;
sp->direction = st->direction;
sp->log = st->log;
sp->allow_opts = st->allow_opts;
ret = 0;
if (h->count == sc->sc_count)
ret = pfsync_sendout(sc);
splx(s);
return (0);
}
int
pfsync_clear_state(st)
struct pf_state *st;
{
#ifdef __FreeBSD__
struct pfsync_softc *sc = LIST_FIRST(&pfsync_list);
#else
struct ifnet *ifp = &pfsyncif.sc_if;
struct pfsync_softc *sc = ifp->if_softc;
#endif
struct mbuf *m = sc->sc_mbuf;
int s, ret;
s = splnet();
if (m == NULL && (m = pfsync_get_mbuf(sc, PFSYNC_ACT_CLR)) == NULL) {
splx(s);
return (ENOMEM);
}
ret = (pfsync_sendout(sc));
splx(s);
return (ret);
}
void
pfsync_timeout(void *v)
{
struct pfsync_softc *sc = v;
int s;
/* We don't need PF_LOCK/PF_UNLOCK here! */
s = splnet();
pfsync_sendout(sc);
splx(s);
}
int
pfsync_sendout(sc)
struct pfsync_softc *sc;
{
struct ifnet *ifp = &sc->sc_if;
struct mbuf *m = sc->sc_mbuf;
#ifdef __FreeBSD__
callout_stop(&sc->sc_tmo);
#else
timeout_del(&sc->sc_tmo);
#endif
sc->sc_mbuf = NULL;
sc->sc_ptr = NULL;
#ifdef __FreeBSD__
KASSERT(m != NULL, ("pfsync_sendout: null mbuf"));
#endif
#if NBPFILTER > 0
if (ifp->if_bpf)
bpf_mtap(ifp->if_bpf, m);
#endif
m_freem(m);
return (0);
}
#ifdef __FreeBSD__
static int
pfsync_modevent(module_t mod, int type, void *data)
{
int error = 0;
switch (type) {
case MOD_LOAD:
LIST_INIT(&pfsync_list);
if_clone_attach(&pfsync_cloner);
break;
case MOD_UNLOAD:
if_clone_detach(&pfsync_cloner);
while (!LIST_EMPTY(&pfsync_list))
pfsync_clone_destroy(
&LIST_FIRST(&pfsync_list)->sc_if);
break;
default:
error = EINVAL;
break;
}
return error;
}
static moduledata_t pfsync_mod = {
"pfsync",
pfsync_modevent,
0
};
#define PFSYNC_MODVER 1
DECLARE_MODULE(pfsync, pfsync_mod, SI_SUB_PSEUDO, SI_ORDER_ANY);
MODULE_VERSION(pfsync, PFSYNC_MODVER);
#endif /* __FreeBSD__ */