f889d2ef8d
- Split the code out into if_clone.[ch]. - Locked struct if_clone. [1] - Add a per-cloner match function rather then simply matching names of the form <name><unit> and <name>. - Use the match function to allow creation of <interface>.<tag> vlan interfaces. The old way is preserved unchanged! - Also the match function to allow creation of stf(4) interfaces named stf0, stf, or 6to4. This is the only major user visible change in that "ifconfig stf" creates the interface stf rather then stf0 and does not print "stf0" to stdout. - Allow destroy functions to fail so they can refuse to delete interfaces. Currently, we forbid the deletion of interfaces which were created in the init function, particularly lo0, pflog0, and pfsync0. In the case of lo0 this was a panic implementation so it does not count as a user visiable change. :-) - Since most interfaces do not need the new functionality, an family of wrapper functions, ifc_simple_*(), were created to wrap old style cloner functions. - The IF_CLONE_INITIALIZER macro is replaced with a new incompatible IFC_CLONE_INITIALIZER and ifc_simple consumers use IFC_SIMPLE_DECLARE instead. Submitted by: Maurycy Pawlowski-Wieronski <maurycy at fouk.org> [1] Reviewed by: andre, mlaier Discussed on: net
1673 lines
38 KiB
C
1673 lines
38 KiB
C
/* $FreeBSD$ */
|
|
/* $OpenBSD: if_pfsync.c,v 1.26 2004/03/28 18:14:20 mcbride 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"
|
|
#include "opt_random_ip_id.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/proc.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/module.h>
|
|
#include <sys/sockio.h>
|
|
#include <sys/lock.h>
|
|
#include <sys/mutex.h>
|
|
#else
|
|
#include <sys/ioctl.h>
|
|
#include <sys/timeout.h>
|
|
#endif
|
|
|
|
#include <net/if.h>
|
|
#if defined(__FreeBSD__)
|
|
#include <net/if_clone.h>
|
|
#endif
|
|
#include <net/if_types.h>
|
|
#include <net/route.h>
|
|
#include <net/bpf.h>
|
|
|
|
#ifdef INET
|
|
#include <netinet/in.h>
|
|
#include <netinet/in_systm.h>
|
|
#include <netinet/in_var.h>
|
|
#include <netinet/ip.h>
|
|
#include <netinet/ip_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
|
|
int pfsync_sync_ok;
|
|
struct pfsyncstats pfsyncstats;
|
|
|
|
#ifndef RANDOM_IP_ID
|
|
extern u_int16_t ip_randomid(void);
|
|
#endif
|
|
|
|
#ifdef __FreeBSD__
|
|
|
|
/*
|
|
* Locking notes:
|
|
* Whenever we really touch/look at the state table we have to hold the
|
|
* PF_LOCK. Functions that do just the interface handling, grab the per
|
|
* softc lock instead.
|
|
*
|
|
*/
|
|
|
|
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 *, int);
|
|
int pfsync_insert_net_state(struct pfsync_state *);
|
|
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 *, u_int8_t, void **);
|
|
int pfsync_request_update(struct pfsync_state_upd *, struct in_addr *);
|
|
int pfsync_sendout(struct pfsync_softc *);
|
|
void pfsync_timeout(void *);
|
|
void pfsync_send_bus(struct pfsync_softc *, u_int8_t);
|
|
void pfsync_bulk_update(void *);
|
|
void pfsync_bulkfail(void *);
|
|
|
|
#ifndef __FreeBSD__
|
|
extern int ifqmaxlen;
|
|
extern struct timeval time;
|
|
extern struct timeval mono_time;
|
|
extern int hz;
|
|
#endif
|
|
|
|
#ifdef __FreeBSD__
|
|
static MALLOC_DEFINE(M_PFSYNC, PFSYNCNAME, "Packet Filter State Sync. Interface");
|
|
static LIST_HEAD(pfsync_list, pfsync_softc) pfsync_list;
|
|
IFC_SIMPLE_DECLARE(pfsync, 1);
|
|
|
|
static void
|
|
pfsync_clone_destroy(struct ifnet *ifp)
|
|
{
|
|
struct pfsync_softc *sc;
|
|
|
|
sc = ifp->if_softc;
|
|
callout_stop(&sc->sc_tmo);
|
|
callout_stop(&sc->sc_bulk_tmo);
|
|
callout_stop(&sc->sc_bulkfail_tmo);
|
|
|
|
#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;
|
|
struct ifnet *ifp;
|
|
|
|
MALLOC(sc, struct pfsync_softc *, sizeof(*sc), M_PFSYNC,
|
|
M_WAITOK|M_ZERO);
|
|
|
|
pfsync_sync_ok = 1;
|
|
sc->sc_mbuf = NULL;
|
|
sc->sc_mbuf_net = NULL;
|
|
sc->sc_statep.s = NULL;
|
|
sc->sc_statep_net.s = NULL;
|
|
sc->sc_maxupdates = 128;
|
|
sc->sc_sendaddr.s_addr = htonl(INADDR_PFSYNC_GROUP);
|
|
sc->sc_ureq_received = 0;
|
|
sc->sc_ureq_sent = 0;
|
|
|
|
ifp = &sc->sc_if;
|
|
if_initname(ifp, ifc->ifc_name, unit);
|
|
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);
|
|
ifp->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);
|
|
callout_init(&sc->sc_bulk_tmo, 0);
|
|
callout_init(&sc->sc_bulkfail_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;
|
|
|
|
pfsync_sync_ok = 1;
|
|
bzero(&pfsyncif, sizeof(pfsyncif));
|
|
pfsyncif.sc_mbuf = NULL;
|
|
pfsyncif.sc_mbuf_net = NULL;
|
|
pfsyncif.sc_statep.s = NULL;
|
|
pfsyncif.sc_statep_net.s = NULL;
|
|
pfsyncif.sc_maxupdates = 128;
|
|
pfsyncif.sc_sendaddr.s_addr = INADDR_PFSYNC_GROUP;
|
|
pfsyncif.sc_ureq_received = 0;
|
|
pfsyncif.sc_ureq_sent = 0;
|
|
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;
|
|
pfsync_setmtu(&pfsyncif, MCLBYTES);
|
|
timeout_set(&pfsyncif.sc_tmo, pfsync_timeout, &pfsyncif);
|
|
timeout_set(&pfsyncif.sc_bulk_tmo, pfsync_bulk_update, &pfsyncif);
|
|
timeout_set(&pfsyncif.sc_bulkfail_tmo, pfsync_bulkfail, &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)
|
|
{
|
|
#ifdef __FreeBSD__
|
|
IF_LOCK(&ifp->if_snd);
|
|
_IF_DROP(&ifp->if_snd);
|
|
_IF_DRAIN(&ifp->if_snd);
|
|
IF_UNLOCK(&ifp->if_snd);
|
|
#else
|
|
struct mbuf *m;
|
|
int s;
|
|
|
|
for (;;) {
|
|
s = splimp();
|
|
IF_DROP(&ifp->if_snd);
|
|
IF_DEQUEUE(&ifp->if_snd, m);
|
|
splx(s);
|
|
|
|
if (m == NULL)
|
|
return;
|
|
else
|
|
m_freem(m);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
int
|
|
pfsync_insert_net_state(struct pfsync_state *sp)
|
|
{
|
|
struct pf_state *st = NULL;
|
|
struct pf_rule *r = NULL;
|
|
struct pfi_kif *kif;
|
|
|
|
#ifdef __FreeBSD__
|
|
PF_ASSERT(MA_OWNED);
|
|
#endif
|
|
if (sp->creatorid == 0 && pf_status.debug >= PF_DEBUG_MISC) {
|
|
printf("pfsync_insert_net_state: invalid creator id:"
|
|
" %08x\n", ntohl(sp->creatorid));
|
|
return (EINVAL);
|
|
}
|
|
|
|
kif = pfi_lookup_create(sp->ifname);
|
|
if (kif == NULL) {
|
|
if (pf_status.debug >= PF_DEBUG_MISC)
|
|
printf("pfsync_insert_net_state: "
|
|
"unknown interface: %s\n", sp->ifname);
|
|
/* skip this state */
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Just use the default rule until we have infrastructure to find the
|
|
* best matching rule.
|
|
*/
|
|
r = &pf_default_rule;
|
|
|
|
if (!r->max_states || r->states < r->max_states)
|
|
st = pool_get(&pf_state_pl, PR_NOWAIT);
|
|
if (st == NULL) {
|
|
pfi_maybe_destroy(kif);
|
|
return (ENOMEM);
|
|
}
|
|
bzero(st, sizeof(*st));
|
|
|
|
st->rule.ptr = r;
|
|
/* XXX get pointers to nat_rule and anchor */
|
|
|
|
/* fill in the rest of the state entry */
|
|
pf_state_host_ntoh(&sp->lan, &st->lan);
|
|
pf_state_host_ntoh(&sp->gwy, &st->gwy);
|
|
pf_state_host_ntoh(&sp->ext, &st->ext);
|
|
|
|
pf_state_peer_ntoh(&sp->src, &st->src);
|
|
pf_state_peer_ntoh(&sp->dst, &st->dst);
|
|
|
|
bcopy(&sp->rt_addr, &st->rt_addr, sizeof(st->rt_addr));
|
|
#ifdef __FreeBSD__
|
|
st->creation = ntohl(sp->creation) + time_second;
|
|
st->expire = ntohl(sp->expire) + time_second;
|
|
#else
|
|
st->creation = ntohl(sp->creation) + time.tv_sec;
|
|
st->expire = ntohl(sp->expire) + time.tv_sec;
|
|
#endif
|
|
|
|
st->af = sp->af;
|
|
st->proto = sp->proto;
|
|
st->direction = sp->direction;
|
|
st->log = sp->log;
|
|
st->timeout = sp->timeout;
|
|
st->allow_opts = sp->allow_opts;
|
|
|
|
bcopy(sp->id, &st->id, sizeof(st->id));
|
|
st->creatorid = sp->creatorid;
|
|
st->sync_flags = sp->sync_flags | PFSTATE_FROMSYNC;
|
|
|
|
|
|
if (pf_insert_state(kif, st)) {
|
|
pfi_maybe_destroy(kif);
|
|
pool_put(&pf_state_pl, st);
|
|
return (EINVAL);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
void
|
|
#ifdef __FreeBSD__
|
|
pfsync_input(struct mbuf *m, __unused int off)
|
|
#else
|
|
pfsync_input(struct mbuf *m, ...)
|
|
#endif
|
|
{
|
|
struct ip *ip = mtod(m, struct ip *);
|
|
struct pfsync_header *ph;
|
|
#ifdef __FreeBSD__
|
|
struct pfsync_softc *sc = LIST_FIRST(&pfsync_list);
|
|
#else
|
|
struct pfsync_softc *sc = &pfsyncif;
|
|
#endif
|
|
struct pf_state *st, key;
|
|
struct pfsync_state *sp;
|
|
struct pfsync_state_upd *up;
|
|
struct pfsync_state_del *dp;
|
|
struct pfsync_state_clr *cp;
|
|
struct pfsync_state_upd_req *rup;
|
|
struct pfsync_state_bus *bus;
|
|
struct in_addr src;
|
|
struct mbuf *mp;
|
|
int iplen, action, error, i, s, count, offp;
|
|
|
|
pfsyncstats.pfsyncs_ipackets++;
|
|
|
|
/* verify that we have a sync interface configured */
|
|
if (!sc->sc_sync_ifp || !pf_status.running) /* XXX PF_LOCK? */
|
|
goto done;
|
|
|
|
/* verify that the packet came in on the right interface */
|
|
if (sc->sc_sync_ifp != m->m_pkthdr.rcvif) {
|
|
pfsyncstats.pfsyncs_badif++;
|
|
goto done;
|
|
}
|
|
|
|
/* verify that the IP TTL is 255. */
|
|
if (ip->ip_ttl != PFSYNC_DFLTTL) {
|
|
pfsyncstats.pfsyncs_badttl++;
|
|
goto done;
|
|
}
|
|
|
|
iplen = ip->ip_hl << 2;
|
|
|
|
if (m->m_pkthdr.len < iplen + sizeof(*ph)) {
|
|
pfsyncstats.pfsyncs_hdrops++;
|
|
goto done;
|
|
}
|
|
|
|
if (iplen + sizeof(*ph) > m->m_len) {
|
|
if ((m = m_pullup(m, iplen + sizeof(*ph))) == NULL) {
|
|
pfsyncstats.pfsyncs_hdrops++;
|
|
goto done;
|
|
}
|
|
ip = mtod(m, struct ip *);
|
|
}
|
|
ph = (struct pfsync_header *)((char *)ip + iplen);
|
|
|
|
/* verify the version */
|
|
if (ph->version != PFSYNC_VERSION) {
|
|
pfsyncstats.pfsyncs_badver++;
|
|
goto done;
|
|
}
|
|
|
|
action = ph->action;
|
|
count = ph->count;
|
|
|
|
/* make sure it's a valid action code */
|
|
if (action >= PFSYNC_ACT_MAX) {
|
|
pfsyncstats.pfsyncs_badact++;
|
|
goto done;
|
|
}
|
|
|
|
/* Cheaper to grab this now than having to mess with mbufs later */
|
|
src = ip->ip_src;
|
|
|
|
switch (action) {
|
|
case PFSYNC_ACT_CLR: {
|
|
struct pfi_kif *kif;
|
|
u_int32_t creatorid;
|
|
if ((mp = m_pulldown(m, iplen + sizeof(*ph),
|
|
sizeof(*cp), &offp)) == NULL) {
|
|
pfsyncstats.pfsyncs_badlen++;
|
|
return;
|
|
}
|
|
cp = (struct pfsync_state_clr *)(mp->m_data + offp);
|
|
creatorid = cp->creatorid;
|
|
|
|
s = splsoftnet();
|
|
#ifdef __FreeBSD__
|
|
PF_LOCK();
|
|
#endif
|
|
if (cp->ifname[0] == '\0') {
|
|
RB_FOREACH(st, pf_state_tree_id, &tree_id) {
|
|
if (st->creatorid == creatorid)
|
|
st->timeout = PFTM_PURGE;
|
|
}
|
|
} else {
|
|
kif = pfi_lookup_if(cp->ifname);
|
|
if (kif == NULL) {
|
|
if (pf_status.debug >= PF_DEBUG_MISC)
|
|
printf("pfsync_input: PFSYNC_ACT_CLR "
|
|
"bad interface: %s\n", cp->ifname);
|
|
splx(s);
|
|
#ifdef __FreeBSD__
|
|
PF_UNLOCK();
|
|
#endif
|
|
goto done;
|
|
}
|
|
RB_FOREACH(st, pf_state_tree_lan_ext,
|
|
&kif->pfik_lan_ext) {
|
|
if (st->creatorid == creatorid)
|
|
st->timeout = PFTM_PURGE;
|
|
}
|
|
}
|
|
pf_purge_expired_states();
|
|
#ifdef __FreeBSD__
|
|
PF_UNLOCK();
|
|
#endif
|
|
splx(s);
|
|
|
|
break;
|
|
}
|
|
case PFSYNC_ACT_INS:
|
|
if ((mp = m_pulldown(m, iplen + sizeof(*ph),
|
|
count * sizeof(*sp), &offp)) == NULL) {
|
|
pfsyncstats.pfsyncs_badlen++;
|
|
return;
|
|
}
|
|
|
|
s = splsoftnet();
|
|
#ifdef __FreeBSD__
|
|
PF_LOCK();
|
|
#endif
|
|
for (i = 0, sp = (struct pfsync_state *)(mp->m_data + offp);
|
|
i < count; i++, sp++) {
|
|
/* check for invalid values */
|
|
if (sp->timeout >= PFTM_MAX ||
|
|
sp->src.state > PF_TCPS_PROXY_DST ||
|
|
sp->dst.state > PF_TCPS_PROXY_DST ||
|
|
sp->direction > PF_OUT ||
|
|
(sp->af != AF_INET && sp->af != AF_INET6)) {
|
|
if (pf_status.debug >= PF_DEBUG_MISC)
|
|
printf("pfsync_insert: PFSYNC_ACT_INS: "
|
|
"invalid value\n");
|
|
pfsyncstats.pfsyncs_badstate++;
|
|
continue;
|
|
}
|
|
|
|
if ((error = pfsync_insert_net_state(sp))) {
|
|
if (error == ENOMEM) {
|
|
splx(s);
|
|
#ifdef __FreeBSD__
|
|
PF_UNLOCK();
|
|
#endif
|
|
goto done;
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
#ifdef __FreeBSD__
|
|
PF_UNLOCK();
|
|
#endif
|
|
splx(s);
|
|
break;
|
|
case PFSYNC_ACT_UPD:
|
|
if ((mp = m_pulldown(m, iplen + sizeof(*ph),
|
|
count * sizeof(*sp), &offp)) == NULL) {
|
|
pfsyncstats.pfsyncs_badlen++;
|
|
return;
|
|
}
|
|
|
|
s = splsoftnet();
|
|
#ifdef __FreeBSD__
|
|
PF_LOCK();
|
|
#endif
|
|
for (i = 0, sp = (struct pfsync_state *)(mp->m_data + offp);
|
|
i < count; i++, sp++) {
|
|
/* check for invalid values */
|
|
if (sp->timeout >= PFTM_MAX ||
|
|
sp->src.state > PF_TCPS_PROXY_DST ||
|
|
sp->dst.state > PF_TCPS_PROXY_DST) {
|
|
if (pf_status.debug >= PF_DEBUG_MISC)
|
|
printf("pfsync_insert: PFSYNC_ACT_UPD: "
|
|
"invalid value\n");
|
|
pfsyncstats.pfsyncs_badstate++;
|
|
continue;
|
|
}
|
|
|
|
bcopy(sp->id, &key.id, sizeof(key.id));
|
|
key.creatorid = sp->creatorid;
|
|
|
|
st = pf_find_state_byid(&key);
|
|
if (st == NULL) {
|
|
/* insert the update */
|
|
if (pfsync_insert_net_state(sp))
|
|
pfsyncstats.pfsyncs_badstate++;
|
|
continue;
|
|
}
|
|
pf_state_peer_ntoh(&sp->src, &st->src);
|
|
pf_state_peer_ntoh(&sp->dst, &st->dst);
|
|
#ifdef __FreeBSD__
|
|
st->expire = ntohl(sp->expire) + time_second;
|
|
#else
|
|
st->expire = ntohl(sp->expire) + time.tv_sec;
|
|
#endif
|
|
st->timeout = sp->timeout;
|
|
|
|
}
|
|
#ifdef __FreeBSD__
|
|
PF_UNLOCK();
|
|
#endif
|
|
splx(s);
|
|
break;
|
|
/*
|
|
* It's not strictly necessary for us to support the "uncompressed"
|
|
* delete action, but it's relatively simple and maintains consistency.
|
|
*/
|
|
case PFSYNC_ACT_DEL:
|
|
if ((mp = m_pulldown(m, iplen + sizeof(*ph),
|
|
count * sizeof(*sp), &offp)) == NULL) {
|
|
pfsyncstats.pfsyncs_badlen++;
|
|
return;
|
|
}
|
|
|
|
s = splsoftnet();
|
|
#ifdef __FreeBSD__
|
|
PF_LOCK();
|
|
#endif
|
|
for (i = 0, sp = (struct pfsync_state *)(mp->m_data + offp);
|
|
i < count; i++, sp++) {
|
|
bcopy(sp->id, &key.id, sizeof(key.id));
|
|
key.creatorid = sp->creatorid;
|
|
|
|
st = pf_find_state_byid(&key);
|
|
if (st == NULL) {
|
|
pfsyncstats.pfsyncs_badstate++;
|
|
continue;
|
|
}
|
|
/*
|
|
* XXX
|
|
* pf_purge_expired_states() is expensive,
|
|
* we really want to purge the state directly.
|
|
*/
|
|
st->timeout = PFTM_PURGE;
|
|
st->sync_flags |= PFSTATE_FROMSYNC;
|
|
}
|
|
pf_purge_expired_states();
|
|
#ifdef __FreeBSD__
|
|
PF_UNLOCK();
|
|
#endif
|
|
splx(s);
|
|
break;
|
|
case PFSYNC_ACT_UPD_C: {
|
|
int update_requested = 0;
|
|
|
|
if ((mp = m_pulldown(m, iplen + sizeof(*ph),
|
|
count * sizeof(*up), &offp)) == NULL) {
|
|
pfsyncstats.pfsyncs_badlen++;
|
|
return;
|
|
}
|
|
|
|
s = splsoftnet();
|
|
#ifdef __FreeBSD__
|
|
PF_LOCK();
|
|
#endif
|
|
for (i = 0, up = (struct pfsync_state_upd *)(mp->m_data + offp);
|
|
i < count; i++, up++) {
|
|
/* check for invalid values */
|
|
if (up->timeout >= PFTM_MAX ||
|
|
up->src.state > PF_TCPS_PROXY_DST ||
|
|
up->dst.state > PF_TCPS_PROXY_DST) {
|
|
if (pf_status.debug >= PF_DEBUG_MISC)
|
|
printf("pfsync_insert: "
|
|
"PFSYNC_ACT_UPD_C: "
|
|
"invalid value\n");
|
|
pfsyncstats.pfsyncs_badstate++;
|
|
continue;
|
|
}
|
|
|
|
bcopy(up->id, &key.id, sizeof(key.id));
|
|
key.creatorid = up->creatorid;
|
|
|
|
st = pf_find_state_byid(&key);
|
|
if (st == NULL) {
|
|
/* We don't have this state. Ask for it. */
|
|
pfsync_request_update(up, &src);
|
|
update_requested = 1;
|
|
pfsyncstats.pfsyncs_badstate++;
|
|
continue;
|
|
}
|
|
pf_state_peer_ntoh(&up->src, &st->src);
|
|
pf_state_peer_ntoh(&up->dst, &st->dst);
|
|
#ifdef __FreeBSD__
|
|
st->expire = ntohl(up->expire) + time_second;
|
|
#else
|
|
st->expire = ntohl(up->expire) + time.tv_sec;
|
|
#endif
|
|
st->timeout = up->timeout;
|
|
}
|
|
if (update_requested)
|
|
pfsync_sendout(sc);
|
|
#ifdef __FreeBSD__
|
|
PF_UNLOCK();
|
|
#endif
|
|
splx(s);
|
|
break;
|
|
}
|
|
case PFSYNC_ACT_DEL_C:
|
|
if ((mp = m_pulldown(m, iplen + sizeof(*ph),
|
|
count * sizeof(*dp), &offp)) == NULL) {
|
|
pfsyncstats.pfsyncs_badlen++;
|
|
return;
|
|
}
|
|
|
|
s = splsoftnet();
|
|
#ifdef __FreeBSD__
|
|
PF_LOCK();
|
|
#endif
|
|
for (i = 0, dp = (struct pfsync_state_del *)(mp->m_data + offp);
|
|
i < count; i++, dp++) {
|
|
bcopy(dp->id, &key.id, sizeof(key.id));
|
|
key.creatorid = dp->creatorid;
|
|
|
|
st = pf_find_state_byid(&key);
|
|
if (st == NULL) {
|
|
pfsyncstats.pfsyncs_badstate++;
|
|
continue;
|
|
}
|
|
/*
|
|
* XXX
|
|
* pf_purge_expired_states() is expensive,
|
|
* we really want to purge the state directly.
|
|
*/
|
|
st->timeout = PFTM_PURGE;
|
|
st->sync_flags |= PFSTATE_FROMSYNC;
|
|
}
|
|
pf_purge_expired_states();
|
|
#ifdef __FreeBSD__
|
|
PF_UNLOCK();
|
|
#endif
|
|
splx(s);
|
|
break;
|
|
case PFSYNC_ACT_INS_F:
|
|
case PFSYNC_ACT_DEL_F:
|
|
/* not implemented */
|
|
break;
|
|
case PFSYNC_ACT_UREQ:
|
|
if ((mp = m_pulldown(m, iplen + sizeof(*ph),
|
|
count * sizeof(*rup), &offp)) == NULL) {
|
|
pfsyncstats.pfsyncs_badlen++;
|
|
return;
|
|
}
|
|
|
|
s = splsoftnet();
|
|
/* XXX send existing. pfsync_pack_state should handle this. */
|
|
#ifdef __FreeBSD__
|
|
PF_LOCK();
|
|
#endif
|
|
if (sc->sc_mbuf != NULL)
|
|
pfsync_sendout(sc);
|
|
for (i = 0,
|
|
rup = (struct pfsync_state_upd_req *)(mp->m_data + offp);
|
|
i < count; i++, rup++) {
|
|
bcopy(rup->id, &key.id, sizeof(key.id));
|
|
key.creatorid = rup->creatorid;
|
|
|
|
if (key.id == 0 && key.creatorid == 0) {
|
|
#ifdef __FreeBSD__
|
|
sc->sc_ureq_received = time_uptime;
|
|
#else
|
|
sc->sc_ureq_received = mono_time.tv_sec;
|
|
#endif
|
|
if (pf_status.debug >= PF_DEBUG_MISC)
|
|
printf("pfsync: received "
|
|
"bulk update request\n");
|
|
pfsync_send_bus(sc, PFSYNC_BUS_START);
|
|
#ifdef __FreeBSD__
|
|
callout_reset(&sc->sc_bulk_tmo, 1 * hz,
|
|
pfsync_bulk_update,
|
|
LIST_FIRST(&pfsync_list));
|
|
#else
|
|
timeout_add(&sc->sc_bulk_tmo, 1 * hz);
|
|
#endif
|
|
} else {
|
|
st = pf_find_state_byid(&key);
|
|
if (st == NULL) {
|
|
pfsyncstats.pfsyncs_badstate++;
|
|
continue;
|
|
}
|
|
pfsync_pack_state(PFSYNC_ACT_UPD, st, 0);
|
|
}
|
|
}
|
|
if (sc->sc_mbuf != NULL)
|
|
pfsync_sendout(sc);
|
|
#ifdef __FreeBSD__
|
|
PF_UNLOCK();
|
|
#endif
|
|
splx(s);
|
|
break;
|
|
case PFSYNC_ACT_BUS:
|
|
/* If we're not waiting for a bulk update, who cares. */
|
|
if (sc->sc_ureq_sent == 0)
|
|
break;
|
|
|
|
if ((mp = m_pulldown(m, iplen + sizeof(*ph),
|
|
sizeof(*bus), &offp)) == NULL) {
|
|
pfsyncstats.pfsyncs_badlen++;
|
|
return;
|
|
}
|
|
bus = (struct pfsync_state_bus *)(mp->m_data + offp);
|
|
switch (bus->status) {
|
|
case PFSYNC_BUS_START:
|
|
#ifdef __FreeBSD__
|
|
callout_reset(&sc->sc_bulkfail_tmo,
|
|
pf_pool_limits[PF_LIMIT_STATES].limit /
|
|
(PFSYNC_BULKPACKETS * sc->sc_maxcount),
|
|
pfsync_bulkfail, LIST_FIRST(&pfsync_list));
|
|
#else
|
|
timeout_add(&sc->sc_bulkfail_tmo,
|
|
pf_pool_limits[PF_LIMIT_STATES].limit /
|
|
(PFSYNC_BULKPACKETS * sc->sc_maxcount));
|
|
#endif
|
|
if (pf_status.debug >= PF_DEBUG_MISC)
|
|
printf("pfsync: received bulk "
|
|
"update start\n");
|
|
break;
|
|
case PFSYNC_BUS_END:
|
|
#ifdef __FreeBSD__
|
|
if (time_uptime - ntohl(bus->endtime) >=
|
|
#else
|
|
if (mono_time.tv_sec - ntohl(bus->endtime) >=
|
|
#endif
|
|
sc->sc_ureq_sent) {
|
|
/* that's it, we're happy */
|
|
sc->sc_ureq_sent = 0;
|
|
sc->sc_bulk_tries = 0;
|
|
#ifdef __FreeBSD__
|
|
callout_stop(&sc->sc_bulkfail_tmo);
|
|
#else
|
|
timeout_del(&sc->sc_bulkfail_tmo);
|
|
#endif
|
|
pfsync_sync_ok = 1;
|
|
if (pf_status.debug >= PF_DEBUG_MISC)
|
|
printf("pfsync: received valid "
|
|
"bulk update end\n");
|
|
} else {
|
|
if (pf_status.debug >= PF_DEBUG_MISC)
|
|
printf("pfsync: received invalid "
|
|
"bulk update end: bad timestamp\n");
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
done:
|
|
if (m)
|
|
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)
|
|
{
|
|
#ifndef __FreeBSD__
|
|
struct proc *p = curproc;
|
|
#endif
|
|
struct pfsync_softc *sc = ifp->if_softc;
|
|
struct ifreq *ifr = (struct ifreq *)data;
|
|
struct ip_moptions *imo = &sc->sc_imo;
|
|
struct pfsyncreq pfsyncr;
|
|
struct ifnet *sifp;
|
|
int s, error;
|
|
|
|
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();
|
|
#ifdef __FreeBSD__
|
|
PF_LOCK();
|
|
#endif
|
|
if (ifr->ifr_mtu < ifp->if_mtu) {
|
|
pfsync_sendout(sc);
|
|
}
|
|
pfsync_setmtu(sc, ifr->ifr_mtu);
|
|
#ifdef __FreeBSD__
|
|
PF_UNLOCK();
|
|
#endif
|
|
splx(s);
|
|
break;
|
|
case SIOCGETPFSYNC:
|
|
#ifdef __FreeBSD__
|
|
/* XXX: read unlocked */
|
|
#endif
|
|
bzero(&pfsyncr, sizeof(pfsyncr));
|
|
if (sc->sc_sync_ifp)
|
|
strlcpy(pfsyncr.pfsyncr_syncif,
|
|
sc->sc_sync_ifp->if_xname, IFNAMSIZ);
|
|
pfsyncr.pfsyncr_maxupdates = sc->sc_maxupdates;
|
|
if ((error = copyout(&pfsyncr, ifr->ifr_data, sizeof(pfsyncr))))
|
|
return (error);
|
|
break;
|
|
case SIOCSETPFSYNC:
|
|
#ifdef __FreeBSD__
|
|
if ((error = suser(curthread)) != 0)
|
|
#else
|
|
if ((error = suser(p, p->p_acflag)) != 0)
|
|
#endif
|
|
return (error);
|
|
if ((error = copyin(ifr->ifr_data, &pfsyncr, sizeof(pfsyncr))))
|
|
return (error);
|
|
|
|
if (pfsyncr.pfsyncr_maxupdates > 255)
|
|
return (EINVAL);
|
|
#ifdef __FreeBSD__
|
|
PF_LOCK();
|
|
#endif
|
|
sc->sc_maxupdates = pfsyncr.pfsyncr_maxupdates;
|
|
|
|
if (pfsyncr.pfsyncr_syncif[0] == 0) {
|
|
sc->sc_sync_ifp = NULL;
|
|
if (sc->sc_mbuf_net != NULL) {
|
|
/* Don't keep stale pfsync packets around. */
|
|
s = splnet();
|
|
m_freem(sc->sc_mbuf_net);
|
|
sc->sc_mbuf_net = NULL;
|
|
sc->sc_statep_net.s = NULL;
|
|
splx(s);
|
|
}
|
|
#ifdef __FreeBSD__
|
|
PF_UNLOCK();
|
|
#endif
|
|
break;
|
|
}
|
|
if ((sifp = ifunit(pfsyncr.pfsyncr_syncif)) == NULL) {
|
|
#ifdef __FreeBSD__
|
|
PF_UNLOCK();
|
|
#endif
|
|
return (EINVAL);
|
|
}
|
|
else if (sifp == sc->sc_sync_ifp) {
|
|
#ifdef __FreeBSD__
|
|
PF_UNLOCK();
|
|
#endif
|
|
break;
|
|
}
|
|
|
|
s = splnet();
|
|
if (sifp->if_mtu < sc->sc_if.if_mtu ||
|
|
(sc->sc_sync_ifp != NULL &&
|
|
sifp->if_mtu < sc->sc_sync_ifp->if_mtu) ||
|
|
sifp->if_mtu < MCLBYTES - sizeof(struct ip))
|
|
pfsync_sendout(sc);
|
|
sc->sc_sync_ifp = sifp;
|
|
|
|
pfsync_setmtu(sc, sc->sc_if.if_mtu);
|
|
|
|
if (imo->imo_num_memberships > 0) {
|
|
in_delmulti(imo->imo_membership[--imo->imo_num_memberships]);
|
|
imo->imo_multicast_ifp = NULL;
|
|
}
|
|
|
|
if (sc->sc_sync_ifp) {
|
|
struct in_addr addr;
|
|
|
|
#ifdef __FreeBSD__
|
|
PF_UNLOCK(); /* addmulti mallocs w/ WAITOK */
|
|
addr.s_addr = htonl(INADDR_PFSYNC_GROUP);
|
|
#else
|
|
addr.s_addr = INADDR_PFSYNC_GROUP;
|
|
#endif
|
|
if ((imo->imo_membership[0] =
|
|
in_addmulti(&addr, sc->sc_sync_ifp)) == NULL) {
|
|
splx(s);
|
|
return (ENOBUFS);
|
|
}
|
|
imo->imo_num_memberships++;
|
|
imo->imo_multicast_ifp = sc->sc_sync_ifp;
|
|
imo->imo_multicast_ttl = PFSYNC_DFLTTL;
|
|
imo->imo_multicast_loop = 0;
|
|
|
|
/* Request a full state table update. */
|
|
#ifdef __FreeBSD__
|
|
PF_LOCK();
|
|
sc->sc_ureq_sent = time_uptime;
|
|
#else
|
|
sc->sc_ureq_sent = mono_time.tv_sec;
|
|
#endif
|
|
pfsync_sync_ok = 0;
|
|
if (pf_status.debug >= PF_DEBUG_MISC)
|
|
printf("pfsync: requesting bulk update\n");
|
|
#ifdef __FreeBSD__
|
|
callout_reset(&sc->sc_bulkfail_tmo, 5 * hz,
|
|
pfsync_bulkfail, LIST_FIRST(&pfsync_list));
|
|
#else
|
|
timeout_add(&sc->sc_bulkfail_tmo, 5 * hz);
|
|
#endif
|
|
pfsync_request_update(NULL, NULL);
|
|
pfsync_sendout(sc);
|
|
}
|
|
#ifdef __FreeBSD__
|
|
PF_UNLOCK();
|
|
#endif
|
|
splx(s);
|
|
|
|
break;
|
|
|
|
default:
|
|
return (ENOTTY);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
void
|
|
pfsync_setmtu(struct pfsync_softc *sc, int mtu_req)
|
|
{
|
|
int mtu;
|
|
|
|
if (sc->sc_sync_ifp && sc->sc_sync_ifp->if_mtu < mtu_req)
|
|
mtu = sc->sc_sync_ifp->if_mtu;
|
|
else
|
|
mtu = mtu_req;
|
|
|
|
sc->sc_maxcount = (mtu - sizeof(struct pfsync_header)) /
|
|
sizeof(struct pfsync_state);
|
|
if (sc->sc_maxcount > 254)
|
|
sc->sc_maxcount = 254;
|
|
sc->sc_if.if_mtu = sizeof(struct pfsync_header) +
|
|
sc->sc_maxcount * sizeof(struct pfsync_state);
|
|
}
|
|
|
|
struct mbuf *
|
|
pfsync_get_mbuf(struct pfsync_softc *sc, u_int8_t action, void **sp)
|
|
{
|
|
struct pfsync_header *h;
|
|
struct mbuf *m;
|
|
int len;
|
|
|
|
#ifdef __FreeBSD__
|
|
PF_ASSERT(MA_OWNED);
|
|
#endif
|
|
MGETHDR(m, M_DONTWAIT, MT_DATA);
|
|
if (m == NULL) {
|
|
sc->sc_if.if_oerrors++;
|
|
return (NULL);
|
|
}
|
|
|
|
switch (action) {
|
|
case PFSYNC_ACT_CLR:
|
|
len = sizeof(struct pfsync_header) +
|
|
sizeof(struct pfsync_state_clr);
|
|
break;
|
|
case PFSYNC_ACT_UPD_C:
|
|
len = (sc->sc_maxcount * sizeof(struct pfsync_state_upd)) +
|
|
sizeof(struct pfsync_header);
|
|
break;
|
|
case PFSYNC_ACT_DEL_C:
|
|
len = (sc->sc_maxcount * sizeof(struct pfsync_state_del)) +
|
|
sizeof(struct pfsync_header);
|
|
break;
|
|
case PFSYNC_ACT_UREQ:
|
|
len = (sc->sc_maxcount * sizeof(struct pfsync_state_upd_req)) +
|
|
sizeof(struct pfsync_header);
|
|
break;
|
|
case PFSYNC_ACT_BUS:
|
|
len = sizeof(struct pfsync_header) +
|
|
sizeof(struct pfsync_state_bus);
|
|
break;
|
|
default:
|
|
len = (sc->sc_maxcount * sizeof(struct pfsync_state)) +
|
|
sizeof(struct pfsync_header);
|
|
break;
|
|
}
|
|
|
|
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_data += (MCLBYTES - len) &~ (sizeof(long) - 1);
|
|
} else
|
|
MH_ALIGN(m, len);
|
|
|
|
m->m_pkthdr.rcvif = NULL;
|
|
m->m_pkthdr.len = m->m_len = sizeof(struct pfsync_header);
|
|
h = mtod(m, struct pfsync_header *);
|
|
h->version = PFSYNC_VERSION;
|
|
h->af = 0;
|
|
h->count = 0;
|
|
h->action = action;
|
|
|
|
*sp = (void *)((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);
|
|
}
|
|
|
|
int
|
|
pfsync_pack_state(u_int8_t action, struct pf_state *st, int compress)
|
|
{
|
|
#ifdef __FreeBSD__
|
|
struct ifnet *ifp = &(LIST_FIRST(&pfsync_list))->sc_if;
|
|
#else
|
|
struct ifnet *ifp = &pfsyncif.sc_if;
|
|
#endif
|
|
struct pfsync_softc *sc = ifp->if_softc;
|
|
struct pfsync_header *h, *h_net;
|
|
struct pfsync_state *sp = NULL;
|
|
struct pfsync_state_upd *up = NULL;
|
|
struct pfsync_state_del *dp = NULL;
|
|
struct pf_rule *r;
|
|
u_long secs;
|
|
int s, ret = 0;
|
|
u_int8_t i = 255, newaction = 0;
|
|
|
|
#ifdef __FreeBSD__
|
|
PF_ASSERT(MA_OWNED);
|
|
#endif
|
|
/*
|
|
* If a packet falls in the forest and there's nobody around to
|
|
* hear, does it make a sound?
|
|
*/
|
|
if (ifp->if_bpf == NULL && sc->sc_sync_ifp == NULL) {
|
|
/* Don't leave any stale pfsync packets hanging around. */
|
|
if (sc->sc_mbuf != NULL) {
|
|
m_freem(sc->sc_mbuf);
|
|
sc->sc_mbuf = NULL;
|
|
sc->sc_statep.s = NULL;
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
if (action >= PFSYNC_ACT_MAX)
|
|
return (EINVAL);
|
|
|
|
s = splnet();
|
|
if (sc->sc_mbuf == NULL) {
|
|
if ((sc->sc_mbuf = pfsync_get_mbuf(sc, action,
|
|
(void *)&sc->sc_statep.s)) == NULL) {
|
|
splx(s);
|
|
return (ENOMEM);
|
|
}
|
|
h = mtod(sc->sc_mbuf, struct pfsync_header *);
|
|
} else {
|
|
h = mtod(sc->sc_mbuf, struct pfsync_header *);
|
|
if (h->action != action) {
|
|
pfsync_sendout(sc);
|
|
if ((sc->sc_mbuf = pfsync_get_mbuf(sc, action,
|
|
(void *)&sc->sc_statep.s)) == NULL) {
|
|
splx(s);
|
|
return (ENOMEM);
|
|
}
|
|
h = mtod(sc->sc_mbuf, struct pfsync_header *);
|
|
} else {
|
|
/*
|
|
* If it's an update, look in the packet to see if
|
|
* we already have an update for the state.
|
|
*/
|
|
if (action == PFSYNC_ACT_UPD && sc->sc_maxupdates) {
|
|
struct pfsync_state *usp =
|
|
(void *)((char *)h + PFSYNC_HDRLEN);
|
|
|
|
for (i = 0; i < h->count; i++) {
|
|
if (!memcmp(usp->id, &st->id,
|
|
PFSYNC_ID_LEN) &&
|
|
usp->creatorid == st->creatorid) {
|
|
sp = usp;
|
|
sp->updates++;
|
|
break;
|
|
}
|
|
usp++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef __FreeBSD__
|
|
secs = time_second;
|
|
|
|
st->pfsync_time = time_uptime;
|
|
#else
|
|
secs = time.tv_sec;
|
|
|
|
st->pfsync_time = mono_time.tv_sec;
|
|
#endif
|
|
TAILQ_REMOVE(&state_updates, st, u.s.entry_updates);
|
|
TAILQ_INSERT_TAIL(&state_updates, st, u.s.entry_updates);
|
|
|
|
if (sp == NULL) {
|
|
/* not a "duplicate" update */
|
|
i = 255;
|
|
sp = sc->sc_statep.s++;
|
|
sc->sc_mbuf->m_pkthdr.len =
|
|
sc->sc_mbuf->m_len += sizeof(struct pfsync_state);
|
|
h->count++;
|
|
bzero(sp, sizeof(*sp));
|
|
|
|
bcopy(&st->id, sp->id, sizeof(sp->id));
|
|
sp->creatorid = st->creatorid;
|
|
|
|
strlcpy(sp->ifname, st->u.s.kif->pfik_name, sizeof(sp->ifname));
|
|
pf_state_host_hton(&st->lan, &sp->lan);
|
|
pf_state_host_hton(&st->gwy, &sp->gwy);
|
|
pf_state_host_hton(&st->ext, &sp->ext);
|
|
|
|
bcopy(&st->rt_addr, &sp->rt_addr, sizeof(sp->rt_addr));
|
|
|
|
sp->creation = htonl(secs - st->creation);
|
|
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 = st->rule.ptr) == NULL)
|
|
sp->rule = htonl(-1);
|
|
else
|
|
sp->rule = htonl(r->nr);
|
|
if ((r = st->anchor.ptr) == NULL)
|
|
sp->anchor = htonl(-1);
|
|
else
|
|
sp->anchor = 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;
|
|
sp->timeout = st->timeout;
|
|
|
|
sp->sync_flags = st->sync_flags & PFSTATE_NOSYNC;
|
|
}
|
|
|
|
pf_state_peer_hton(&st->src, &sp->src);
|
|
pf_state_peer_hton(&st->dst, &sp->dst);
|
|
|
|
if (st->expire <= secs)
|
|
sp->expire = htonl(0);
|
|
else
|
|
sp->expire = htonl(st->expire - secs);
|
|
|
|
/* do we need to build "compressed" actions for network transfer? */
|
|
if (sc->sc_sync_ifp && compress) {
|
|
switch (action) {
|
|
case PFSYNC_ACT_UPD:
|
|
newaction = PFSYNC_ACT_UPD_C;
|
|
break;
|
|
case PFSYNC_ACT_DEL:
|
|
newaction = PFSYNC_ACT_DEL_C;
|
|
break;
|
|
default:
|
|
/* by default we just send the uncompressed states */
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (newaction) {
|
|
if (sc->sc_mbuf_net == NULL) {
|
|
if ((sc->sc_mbuf_net = pfsync_get_mbuf(sc, newaction,
|
|
(void *)&sc->sc_statep_net.s)) == NULL) {
|
|
splx(s);
|
|
return (ENOMEM);
|
|
}
|
|
}
|
|
h_net = mtod(sc->sc_mbuf_net, struct pfsync_header *);
|
|
|
|
switch (newaction) {
|
|
case PFSYNC_ACT_UPD_C:
|
|
if (i != 255) {
|
|
up = (void *)((char *)h_net +
|
|
PFSYNC_HDRLEN + (i * sizeof(*up)));
|
|
up->updates++;
|
|
} else {
|
|
h_net->count++;
|
|
sc->sc_mbuf_net->m_pkthdr.len =
|
|
sc->sc_mbuf_net->m_len += sizeof(*up);
|
|
up = sc->sc_statep_net.u++;
|
|
|
|
bzero(up, sizeof(*up));
|
|
bcopy(&st->id, up->id, sizeof(up->id));
|
|
up->creatorid = st->creatorid;
|
|
}
|
|
up->timeout = st->timeout;
|
|
up->expire = sp->expire;
|
|
up->src = sp->src;
|
|
up->dst = sp->dst;
|
|
break;
|
|
case PFSYNC_ACT_DEL_C:
|
|
sc->sc_mbuf_net->m_pkthdr.len =
|
|
sc->sc_mbuf_net->m_len += sizeof(*dp);
|
|
dp = sc->sc_statep_net.d++;
|
|
h_net->count++;
|
|
|
|
bzero(dp, sizeof(*dp));
|
|
bcopy(&st->id, dp->id, sizeof(dp->id));
|
|
dp->creatorid = st->creatorid;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (h->count == sc->sc_maxcount ||
|
|
(sc->sc_maxupdates && (sp->updates >= sc->sc_maxupdates)))
|
|
ret = pfsync_sendout(sc);
|
|
|
|
splx(s);
|
|
return (ret);
|
|
}
|
|
|
|
/* This must be called in splnet() */
|
|
int
|
|
pfsync_request_update(struct pfsync_state_upd *up, struct in_addr *src)
|
|
{
|
|
#ifdef __FreeBSD__
|
|
struct ifnet *ifp = &(LIST_FIRST(&pfsync_list))->sc_if;
|
|
#else
|
|
struct ifnet *ifp = &pfsyncif.sc_if;
|
|
#endif
|
|
struct pfsync_header *h;
|
|
struct pfsync_softc *sc = ifp->if_softc;
|
|
struct pfsync_state_upd_req *rup;
|
|
int s, ret = 0; /* make the compiler happy */
|
|
|
|
#ifdef __FreeBSD__
|
|
PF_ASSERT(MA_OWNED);
|
|
#endif
|
|
if (sc->sc_mbuf == NULL) {
|
|
if ((sc->sc_mbuf = pfsync_get_mbuf(sc, PFSYNC_ACT_UREQ,
|
|
(void *)&sc->sc_statep.s)) == NULL) {
|
|
splx(s);
|
|
return (ENOMEM);
|
|
}
|
|
h = mtod(sc->sc_mbuf, struct pfsync_header *);
|
|
} else {
|
|
h = mtod(sc->sc_mbuf, struct pfsync_header *);
|
|
if (h->action != PFSYNC_ACT_UREQ) {
|
|
pfsync_sendout(sc);
|
|
if ((sc->sc_mbuf = pfsync_get_mbuf(sc, PFSYNC_ACT_UREQ,
|
|
(void *)&sc->sc_statep.s)) == NULL) {
|
|
splx(s);
|
|
return (ENOMEM);
|
|
}
|
|
h = mtod(sc->sc_mbuf, struct pfsync_header *);
|
|
}
|
|
}
|
|
|
|
if (src != NULL)
|
|
sc->sc_sendaddr = *src;
|
|
sc->sc_mbuf->m_pkthdr.len = sc->sc_mbuf->m_len += sizeof(*rup);
|
|
h->count++;
|
|
rup = sc->sc_statep.r++;
|
|
bzero(rup, sizeof(*rup));
|
|
if (up != NULL) {
|
|
bcopy(up->id, rup->id, sizeof(rup->id));
|
|
rup->creatorid = up->creatorid;
|
|
}
|
|
|
|
if (h->count == sc->sc_maxcount)
|
|
ret = pfsync_sendout(sc);
|
|
|
|
return (ret);
|
|
}
|
|
|
|
int
|
|
pfsync_clear_states(u_int32_t creatorid, char *ifname)
|
|
{
|
|
#ifdef __FreeBSD__
|
|
struct ifnet *ifp = &(LIST_FIRST(&pfsync_list))->sc_if;
|
|
#else
|
|
struct ifnet *ifp = &pfsyncif.sc_if;
|
|
#endif
|
|
struct pfsync_softc *sc = ifp->if_softc;
|
|
struct pfsync_state_clr *cp;
|
|
int s, ret;
|
|
|
|
s = splnet();
|
|
#ifdef __FreeBSD__
|
|
PF_ASSERT(MA_OWNED);
|
|
#endif
|
|
if (sc->sc_mbuf != NULL)
|
|
pfsync_sendout(sc);
|
|
if ((sc->sc_mbuf = pfsync_get_mbuf(sc, PFSYNC_ACT_CLR,
|
|
(void *)&sc->sc_statep.c)) == NULL) {
|
|
splx(s);
|
|
return (ENOMEM);
|
|
}
|
|
sc->sc_mbuf->m_pkthdr.len = sc->sc_mbuf->m_len += sizeof(*cp);
|
|
cp = sc->sc_statep.c;
|
|
cp->creatorid = creatorid;
|
|
if (ifname != NULL)
|
|
strlcpy(cp->ifname, ifname, IFNAMSIZ);
|
|
|
|
ret = (pfsync_sendout(sc));
|
|
splx(s);
|
|
return (ret);
|
|
}
|
|
|
|
void
|
|
pfsync_timeout(void *v)
|
|
{
|
|
struct pfsync_softc *sc = v;
|
|
int s;
|
|
|
|
s = splnet();
|
|
#ifdef __FreeBSD__
|
|
PF_LOCK();
|
|
#endif
|
|
pfsync_sendout(sc);
|
|
#ifdef __FreeBSD__
|
|
PF_UNLOCK();
|
|
#endif
|
|
splx(s);
|
|
}
|
|
|
|
void
|
|
pfsync_send_bus(struct pfsync_softc *sc, u_int8_t status)
|
|
{
|
|
struct pfsync_state_bus *bus;
|
|
|
|
#ifdef __FreeBSD__
|
|
PF_ASSERT(MA_OWNED);
|
|
#endif
|
|
if (sc->sc_mbuf != NULL)
|
|
pfsync_sendout(sc);
|
|
|
|
if (pfsync_sync_ok &&
|
|
(sc->sc_mbuf = pfsync_get_mbuf(sc, PFSYNC_ACT_BUS,
|
|
(void *)&sc->sc_statep.b)) != NULL) {
|
|
sc->sc_mbuf->m_pkthdr.len = sc->sc_mbuf->m_len += sizeof(*bus);
|
|
bus = sc->sc_statep.b;
|
|
bus->creatorid = pf_status.hostid;
|
|
bus->status = status;
|
|
#ifdef __FreeBSD__
|
|
bus->endtime = htonl(time_uptime - sc->sc_ureq_received);
|
|
#else
|
|
bus->endtime = htonl(mono_time.tv_sec - sc->sc_ureq_received);
|
|
#endif
|
|
pfsync_sendout(sc);
|
|
}
|
|
}
|
|
|
|
void
|
|
pfsync_bulk_update(void *v)
|
|
{
|
|
struct pfsync_softc *sc = v;
|
|
int s, i = 0;
|
|
struct pf_state *state;
|
|
|
|
#ifdef __FreeBSD__
|
|
PF_LOCK();
|
|
#endif
|
|
s = splnet();
|
|
if (sc->sc_mbuf != NULL)
|
|
pfsync_sendout(sc);
|
|
|
|
/*
|
|
* Grab at most PFSYNC_BULKPACKETS worth of states which have not
|
|
* been sent since the latest request was made.
|
|
*/
|
|
while ((state = TAILQ_FIRST(&state_updates)) != NULL &&
|
|
++i < (sc->sc_maxcount * PFSYNC_BULKPACKETS)) {
|
|
if (state->pfsync_time > sc->sc_ureq_received) {
|
|
/* we're done */
|
|
pfsync_send_bus(sc, PFSYNC_BUS_END);
|
|
sc->sc_ureq_received = 0;
|
|
#ifdef __FreeBSD__
|
|
callout_stop(&sc->sc_bulk_tmo);
|
|
#else
|
|
timeout_del(&sc->sc_bulk_tmo);
|
|
#endif
|
|
if (pf_status.debug >= PF_DEBUG_MISC)
|
|
printf("pfsync: bulk update complete\n");
|
|
break;
|
|
} else {
|
|
/* send an update and move to end of list */
|
|
if (!state->sync_flags)
|
|
pfsync_pack_state(PFSYNC_ACT_UPD, state, 0);
|
|
#ifdef __FreeBSD__
|
|
state->pfsync_time = time_uptime;
|
|
#else
|
|
state->pfsync_time = mono_time.tv_sec;
|
|
#endif
|
|
TAILQ_REMOVE(&state_updates, state, u.s.entry_updates);
|
|
TAILQ_INSERT_TAIL(&state_updates, state,
|
|
u.s.entry_updates);
|
|
|
|
/* look again for more in a bit */
|
|
#ifdef __FreeBSD__
|
|
callout_reset(&sc->sc_bulk_tmo, 1, pfsync_timeout,
|
|
LIST_FIRST(&pfsync_list));
|
|
#else
|
|
timeout_add(&sc->sc_bulk_tmo, 1);
|
|
#endif
|
|
}
|
|
}
|
|
if (sc->sc_mbuf != NULL)
|
|
pfsync_sendout(sc);
|
|
splx(s);
|
|
#ifdef __FreeBSD__
|
|
PF_UNLOCK();
|
|
#endif
|
|
}
|
|
|
|
void
|
|
pfsync_bulkfail(void *v)
|
|
{
|
|
struct pfsync_softc *sc = v;
|
|
|
|
#ifdef __FreeBSD__
|
|
PF_LOCK();
|
|
#endif
|
|
if (sc->sc_bulk_tries++ < PFSYNC_MAX_BULKTRIES) {
|
|
/* Try again in a bit */
|
|
#ifdef __FreeBSD__
|
|
callout_reset(&sc->sc_bulkfail_tmo, 5 * hz, pfsync_bulkfail,
|
|
LIST_FIRST(&pfsync_list));
|
|
#else
|
|
timeout_add(&sc->sc_bulkfail_tmo, 5 * hz);
|
|
#endif
|
|
pfsync_request_update(NULL, NULL);
|
|
pfsync_sendout(sc);
|
|
} else {
|
|
/* Pretend like the transfer was ok */
|
|
sc->sc_ureq_sent = 0;
|
|
sc->sc_bulk_tries = 0;
|
|
pfsync_sync_ok = 1;
|
|
if (pf_status.debug >= PF_DEBUG_MISC)
|
|
printf("pfsync: failed to receive "
|
|
"bulk update status\n");
|
|
#ifdef __FreeBSD__
|
|
callout_stop(&sc->sc_bulkfail_tmo);
|
|
#else
|
|
timeout_del(&sc->sc_bulkfail_tmo);
|
|
#endif
|
|
}
|
|
#ifdef __FreeBSD__
|
|
PF_UNLOCK();
|
|
#endif
|
|
}
|
|
|
|
int
|
|
pfsync_sendout(sc)
|
|
struct pfsync_softc *sc;
|
|
{
|
|
struct ifnet *ifp = &sc->sc_if;
|
|
struct mbuf *m;
|
|
|
|
#ifdef __FreeBSD__
|
|
PF_ASSERT(MA_OWNED);
|
|
callout_stop(&sc->sc_tmo);
|
|
#else
|
|
timeout_del(&sc->sc_tmo);
|
|
#endif
|
|
|
|
if (sc->sc_mbuf == NULL)
|
|
return (0);
|
|
m = sc->sc_mbuf;
|
|
sc->sc_mbuf = NULL;
|
|
sc->sc_statep.s = 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
|
|
|
|
if (sc->sc_mbuf_net) {
|
|
m_freem(m);
|
|
m = sc->sc_mbuf_net;
|
|
sc->sc_mbuf_net = NULL;
|
|
sc->sc_statep_net.s = NULL;
|
|
}
|
|
|
|
if (sc->sc_sync_ifp) {
|
|
struct ip *ip;
|
|
struct ifaddr *ifa;
|
|
struct sockaddr sa;
|
|
|
|
M_PREPEND(m, sizeof(struct ip), M_DONTWAIT);
|
|
if (m == NULL) {
|
|
pfsyncstats.pfsyncs_onomem++;
|
|
return (0);
|
|
}
|
|
ip = mtod(m, struct ip *);
|
|
ip->ip_v = IPVERSION;
|
|
ip->ip_hl = sizeof(*ip) >> 2;
|
|
ip->ip_tos = IPTOS_LOWDELAY;
|
|
#ifdef __FreeBSD__
|
|
ip->ip_len = m->m_pkthdr.len;
|
|
#else
|
|
ip->ip_len = htons(m->m_pkthdr.len);
|
|
#endif
|
|
ip->ip_id = htons(ip_randomid());
|
|
#ifdef __FreeBSD__
|
|
ip->ip_off = IP_DF;
|
|
#else
|
|
ip->ip_off = htons(IP_DF);
|
|
#endif
|
|
ip->ip_ttl = PFSYNC_DFLTTL;
|
|
ip->ip_p = IPPROTO_PFSYNC;
|
|
ip->ip_sum = 0;
|
|
|
|
bzero(&sa, sizeof(sa));
|
|
sa.sa_family = AF_INET;
|
|
ifa = ifaof_ifpforaddr(&sa, sc->sc_sync_ifp);
|
|
if (ifa == NULL)
|
|
return (0);
|
|
ip->ip_src.s_addr = ifatoia(ifa)->ia_addr.sin_addr.s_addr;
|
|
|
|
#ifdef __FreeBSD__
|
|
if (sc->sc_sendaddr.s_addr == htonl(INADDR_PFSYNC_GROUP))
|
|
#else
|
|
if (sc->sc_sendaddr.s_addr == INADDR_PFSYNC_GROUP)
|
|
#endif
|
|
m->m_flags |= M_MCAST;
|
|
ip->ip_dst = sc->sc_sendaddr;
|
|
#ifdef __FreeBSD__
|
|
sc->sc_sendaddr.s_addr = htonl(INADDR_PFSYNC_GROUP);
|
|
#else
|
|
sc->sc_sendaddr.s_addr = INADDR_PFSYNC_GROUP;
|
|
#endif
|
|
|
|
pfsyncstats.pfsyncs_opackets++;
|
|
|
|
#ifdef __FreeBSD__
|
|
PF_UNLOCK();
|
|
#endif
|
|
if (ip_output(m, NULL, NULL, IP_RAWOUTPUT, &sc->sc_imo, NULL))
|
|
pfsyncstats.pfsyncs_oerrors++;
|
|
|
|
#ifdef __FreeBSD__
|
|
PF_LOCK();
|
|
#endif
|
|
} else
|
|
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__ */
|