55b043392b
Even though the code seems to be FreeBSD kernel code, it isn't compiled on FreeBSD. I could have known this, because I was a little amazed that I couldn't find a prototype of pfopen()/pfclose() somewhere else, because it isn't marked as static. Apart from that, removing these functions wouldn't have been harmful anyway, because there are some other strange things about them (the implementation isn't consistent with the prototype at the top). Still, it's better to leave it, because it makes merging code back to older branches a little harder. Requested by: mlaier
3901 lines
92 KiB
C
3901 lines
92 KiB
C
/* $OpenBSD: pf_ioctl.c,v 1.175 2007/02/26 22:47:43 deraadt Exp $ */
|
|
|
|
/*
|
|
* Copyright (c) 2001 Daniel Hartmeier
|
|
* Copyright (c) 2002,2003 Henning Brauer
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* - Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* - 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 COPYRIGHT HOLDERS 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
|
|
* COPYRIGHT HOLDERS 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.
|
|
*
|
|
* Effort sponsored in part by the Defense Advanced Research Projects
|
|
* Agency (DARPA) and Air Force Research Laboratory, Air Force
|
|
* Materiel Command, USAF, under agreement number F30602-01-2-0537.
|
|
*
|
|
*/
|
|
|
|
#ifdef __FreeBSD__
|
|
#include "opt_inet.h"
|
|
#include "opt_inet6.h"
|
|
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
#endif
|
|
|
|
#ifdef __FreeBSD__
|
|
#include "opt_bpf.h"
|
|
#include "opt_pf.h"
|
|
|
|
#ifdef DEV_BPF
|
|
#define NBPFILTER DEV_BPF
|
|
#else
|
|
#define NBPFILTER 0
|
|
#endif
|
|
|
|
#ifdef DEV_PFLOG
|
|
#define NPFLOG DEV_PFLOG
|
|
#else
|
|
#define NPFLOG 0
|
|
#endif
|
|
|
|
#ifdef DEV_PFSYNC
|
|
#define NPFSYNC DEV_PFSYNC
|
|
#else
|
|
#define NPFSYNC 0
|
|
#endif
|
|
|
|
#else
|
|
#include "bpfilter.h"
|
|
#include "pflog.h"
|
|
#include "pfsync.h"
|
|
#endif
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/mbuf.h>
|
|
#include <sys/filio.h>
|
|
#include <sys/fcntl.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/socketvar.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/time.h>
|
|
#include <sys/malloc.h>
|
|
#ifdef __FreeBSD__
|
|
#include <sys/module.h>
|
|
#include <sys/conf.h>
|
|
#include <sys/proc.h>
|
|
#include <sys/sysctl.h>
|
|
#include <sys/vimage.h>
|
|
#else
|
|
#include <sys/timeout.h>
|
|
#include <sys/pool.h>
|
|
#endif
|
|
#include <sys/proc.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/kthread.h>
|
|
#ifndef __FreeBSD__
|
|
#include <sys/rwlock.h>
|
|
#include <uvm/uvm_extern.h>
|
|
#endif
|
|
|
|
#include <net/if.h>
|
|
#include <net/if_types.h>
|
|
#include <net/route.h>
|
|
#ifdef __FreeBSD__
|
|
#include <net/vnet.h>
|
|
#endif
|
|
|
|
#include <netinet/in.h>
|
|
#include <netinet/in_var.h>
|
|
#include <netinet/in_systm.h>
|
|
#include <netinet/ip.h>
|
|
#include <netinet/ip_var.h>
|
|
#include <netinet/ip_icmp.h>
|
|
|
|
#ifdef __FreeBSD__
|
|
#include <sys/md5.h>
|
|
#else
|
|
#include <dev/rndvar.h>
|
|
#include <crypto/md5.h>
|
|
#endif
|
|
#include <net/pfvar.h>
|
|
|
|
#if NPFSYNC > 0
|
|
#include <net/if_pfsync.h>
|
|
#endif /* NPFSYNC > 0 */
|
|
|
|
#include <net/if_pflog.h>
|
|
|
|
#ifdef INET6
|
|
#include <netinet/ip6.h>
|
|
#include <netinet/in_pcb.h>
|
|
#endif /* INET6 */
|
|
|
|
#ifdef ALTQ
|
|
#include <altq/altq.h>
|
|
#endif
|
|
|
|
#ifdef __FreeBSD__
|
|
#include <sys/limits.h>
|
|
#include <sys/lock.h>
|
|
#include <sys/mutex.h>
|
|
#include <net/pfil.h>
|
|
#endif /* __FreeBSD__ */
|
|
|
|
#ifdef __FreeBSD__
|
|
void init_zone_var(void);
|
|
void cleanup_pf_zone(void);
|
|
int pfattach(void);
|
|
#else
|
|
void pfattach(int);
|
|
void pf_thread_create(void *);
|
|
int pfopen(dev_t, int, int, struct proc *);
|
|
int pfclose(dev_t, int, int, struct proc *);
|
|
#endif
|
|
struct pf_pool *pf_get_pool(char *, u_int32_t, u_int8_t, u_int32_t,
|
|
u_int8_t, u_int8_t, u_int8_t);
|
|
|
|
void pf_mv_pool(struct pf_palist *, struct pf_palist *);
|
|
void pf_empty_pool(struct pf_palist *);
|
|
#ifdef __FreeBSD__
|
|
int pfioctl(struct cdev *, u_long, caddr_t, int, struct thread *);
|
|
#else
|
|
int pfioctl(struct cdev *, u_long, caddr_t, int, struct proc *);
|
|
#endif
|
|
#ifdef ALTQ
|
|
int pf_begin_altq(u_int32_t *);
|
|
int pf_rollback_altq(u_int32_t);
|
|
int pf_commit_altq(u_int32_t);
|
|
int pf_enable_altq(struct pf_altq *);
|
|
int pf_disable_altq(struct pf_altq *);
|
|
#endif /* ALTQ */
|
|
int pf_begin_rules(u_int32_t *, int, const char *);
|
|
int pf_rollback_rules(u_int32_t, int, char *);
|
|
int pf_setup_pfsync_matching(struct pf_ruleset *);
|
|
void pf_hash_rule(MD5_CTX *, struct pf_rule *);
|
|
void pf_hash_rule_addr(MD5_CTX *, struct pf_rule_addr *);
|
|
int pf_commit_rules(u_int32_t, int, char *);
|
|
|
|
struct pf_rule pf_default_rule;
|
|
#ifdef __FreeBSD__
|
|
struct sx pf_consistency_lock;
|
|
SX_SYSINIT(pf_consistency_lock, &pf_consistency_lock, "pf_statetbl_lock");
|
|
#else
|
|
struct rwlock pf_consistency_lock = RWLOCK_INITIALIZER;
|
|
#endif
|
|
#ifdef ALTQ
|
|
static int pf_altq_running;
|
|
#endif
|
|
|
|
#define TAGID_MAX 50000
|
|
TAILQ_HEAD(pf_tags, pf_tagname) pf_tags = TAILQ_HEAD_INITIALIZER(pf_tags),
|
|
pf_qids = TAILQ_HEAD_INITIALIZER(pf_qids);
|
|
|
|
#if (PF_QNAME_SIZE != PF_TAG_NAME_SIZE)
|
|
#error PF_QNAME_SIZE must be equal to PF_TAG_NAME_SIZE
|
|
#endif
|
|
u_int16_t tagname2tag(struct pf_tags *, char *);
|
|
void tag2tagname(struct pf_tags *, u_int16_t, char *);
|
|
void tag_unref(struct pf_tags *, u_int16_t);
|
|
int pf_rtlabel_add(struct pf_addr_wrap *);
|
|
void pf_rtlabel_remove(struct pf_addr_wrap *);
|
|
void pf_rtlabel_copyout(struct pf_addr_wrap *);
|
|
|
|
#define DPFPRINTF(n, x) if (pf_status.debug >= (n)) printf x
|
|
|
|
|
|
#ifdef __FreeBSD__
|
|
static struct cdev *pf_dev;
|
|
|
|
/*
|
|
* XXX - These are new and need to be checked when moveing to a new version
|
|
*/
|
|
static void pf_clear_states(void);
|
|
static int pf_clear_tables(void);
|
|
static void pf_clear_srcnodes(void);
|
|
/*
|
|
* XXX - These are new and need to be checked when moveing to a new version
|
|
*/
|
|
|
|
/*
|
|
* Wrapper functions for pfil(9) hooks
|
|
*/
|
|
static int pf_check_in(void *arg, struct mbuf **m, struct ifnet *ifp,
|
|
int dir, struct inpcb *inp);
|
|
static int pf_check_out(void *arg, struct mbuf **m, struct ifnet *ifp,
|
|
int dir, struct inpcb *inp);
|
|
#ifdef INET6
|
|
static int pf_check6_in(void *arg, struct mbuf **m, struct ifnet *ifp,
|
|
int dir, struct inpcb *inp);
|
|
static int pf_check6_out(void *arg, struct mbuf **m, struct ifnet *ifp,
|
|
int dir, struct inpcb *inp);
|
|
#endif
|
|
|
|
static int hook_pf(void);
|
|
static int dehook_pf(void);
|
|
static int shutdown_pf(void);
|
|
static int pf_load(void);
|
|
static int pf_unload(void);
|
|
|
|
static struct cdevsw pf_cdevsw = {
|
|
.d_ioctl = pfioctl,
|
|
.d_name = PF_NAME,
|
|
.d_version = D_VERSION,
|
|
};
|
|
|
|
static volatile int pf_pfil_hooked = 0;
|
|
int pf_end_threads = 0;
|
|
struct mtx pf_task_mtx;
|
|
pflog_packet_t *pflog_packet_ptr = NULL;
|
|
|
|
int debug_pfugidhack = 0;
|
|
SYSCTL_INT(_debug, OID_AUTO, pfugidhack, CTLFLAG_RW, &debug_pfugidhack, 0,
|
|
"Enable/disable pf user/group rules mpsafe hack");
|
|
|
|
void
|
|
init_pf_mutex(void)
|
|
{
|
|
mtx_init(&pf_task_mtx, "pf task mtx", NULL, MTX_DEF);
|
|
}
|
|
|
|
void
|
|
destroy_pf_mutex(void)
|
|
{
|
|
mtx_destroy(&pf_task_mtx);
|
|
}
|
|
|
|
void
|
|
init_zone_var(void)
|
|
{
|
|
pf_src_tree_pl = pf_rule_pl = NULL;
|
|
pf_state_pl = pf_altq_pl = pf_pooladdr_pl = NULL;
|
|
pf_frent_pl = pf_frag_pl = pf_cache_pl = pf_cent_pl = NULL;
|
|
pf_state_scrub_pl = NULL;
|
|
pfr_ktable_pl = pfr_kentry_pl = NULL;
|
|
}
|
|
|
|
void
|
|
cleanup_pf_zone(void)
|
|
{
|
|
UMA_DESTROY(pf_src_tree_pl);
|
|
UMA_DESTROY(pf_rule_pl);
|
|
UMA_DESTROY(pf_state_pl);
|
|
UMA_DESTROY(pf_altq_pl);
|
|
UMA_DESTROY(pf_pooladdr_pl);
|
|
UMA_DESTROY(pf_frent_pl);
|
|
UMA_DESTROY(pf_frag_pl);
|
|
UMA_DESTROY(pf_cache_pl);
|
|
UMA_DESTROY(pf_cent_pl);
|
|
UMA_DESTROY(pfr_ktable_pl);
|
|
UMA_DESTROY(pfr_kentry_pl2);
|
|
UMA_DESTROY(pfr_kentry_pl);
|
|
UMA_DESTROY(pf_state_scrub_pl);
|
|
UMA_DESTROY(pfi_addr_pl);
|
|
}
|
|
|
|
int
|
|
pfattach(void)
|
|
{
|
|
u_int32_t *my_timeout = pf_default_rule.timeout;
|
|
int error = 1;
|
|
|
|
do {
|
|
UMA_CREATE(pf_src_tree_pl,struct pf_src_node, "pfsrctrpl");
|
|
UMA_CREATE(pf_rule_pl, struct pf_rule, "pfrulepl");
|
|
UMA_CREATE(pf_state_pl, struct pf_state, "pfstatepl");
|
|
UMA_CREATE(pf_altq_pl, struct pf_altq, "pfaltqpl");
|
|
UMA_CREATE(pf_pooladdr_pl, struct pf_pooladdr, "pfpooladdrpl");
|
|
UMA_CREATE(pfr_ktable_pl, struct pfr_ktable, "pfrktable");
|
|
UMA_CREATE(pfr_kentry_pl, struct pfr_kentry, "pfrkentry");
|
|
UMA_CREATE(pfr_kentry_pl2, struct pfr_kentry, "pfrkentry2");
|
|
UMA_CREATE(pf_frent_pl, struct pf_frent, "pffrent");
|
|
UMA_CREATE(pf_frag_pl, struct pf_fragment, "pffrag");
|
|
UMA_CREATE(pf_cache_pl, struct pf_fragment, "pffrcache");
|
|
UMA_CREATE(pf_cent_pl, struct pf_frcache, "pffrcent");
|
|
UMA_CREATE(pf_state_scrub_pl, struct pf_state_scrub,
|
|
"pfstatescrub");
|
|
UMA_CREATE(pfi_addr_pl, struct pfi_dynaddr, "pfiaddrpl");
|
|
error = 0;
|
|
} while(0);
|
|
if (error) {
|
|
cleanup_pf_zone();
|
|
return (error);
|
|
}
|
|
pfr_initialize();
|
|
pfi_initialize();
|
|
if ( (error = pf_osfp_initialize()) ) {
|
|
cleanup_pf_zone();
|
|
pf_osfp_cleanup();
|
|
return (error);
|
|
}
|
|
|
|
pf_pool_limits[PF_LIMIT_STATES].pp = pf_state_pl;
|
|
pf_pool_limits[PF_LIMIT_STATES].limit = PFSTATE_HIWAT;
|
|
pf_pool_limits[PF_LIMIT_SRC_NODES].pp = pf_src_tree_pl;
|
|
pf_pool_limits[PF_LIMIT_SRC_NODES].limit = PFSNODE_HIWAT;
|
|
pf_pool_limits[PF_LIMIT_FRAGS].pp = pf_frent_pl;
|
|
pf_pool_limits[PF_LIMIT_FRAGS].limit = PFFRAG_FRENT_HIWAT;
|
|
pf_pool_limits[PF_LIMIT_TABLES].pp = pfr_ktable_pl;
|
|
pf_pool_limits[PF_LIMIT_TABLES].limit = PFR_KTABLE_HIWAT;
|
|
pf_pool_limits[PF_LIMIT_TABLE_ENTRIES].pp = pfr_kentry_pl;
|
|
pf_pool_limits[PF_LIMIT_TABLE_ENTRIES].limit = PFR_KENTRY_HIWAT;
|
|
uma_zone_set_max(pf_pool_limits[PF_LIMIT_STATES].pp,
|
|
pf_pool_limits[PF_LIMIT_STATES].limit);
|
|
|
|
RB_INIT(&tree_src_tracking);
|
|
RB_INIT(&pf_anchors);
|
|
pf_init_ruleset(&pf_main_ruleset);
|
|
TAILQ_INIT(&pf_altqs[0]);
|
|
TAILQ_INIT(&pf_altqs[1]);
|
|
TAILQ_INIT(&pf_pabuf);
|
|
pf_altqs_active = &pf_altqs[0];
|
|
pf_altqs_inactive = &pf_altqs[1];
|
|
TAILQ_INIT(&state_list);
|
|
|
|
/* default rule should never be garbage collected */
|
|
pf_default_rule.entries.tqe_prev = &pf_default_rule.entries.tqe_next;
|
|
pf_default_rule.action = PF_PASS;
|
|
pf_default_rule.nr = -1;
|
|
pf_default_rule.rtableid = -1;
|
|
|
|
/* initialize default timeouts */
|
|
my_timeout[PFTM_TCP_FIRST_PACKET] = PFTM_TCP_FIRST_PACKET_VAL;
|
|
my_timeout[PFTM_TCP_OPENING] = PFTM_TCP_OPENING_VAL;
|
|
my_timeout[PFTM_TCP_ESTABLISHED] = PFTM_TCP_ESTABLISHED_VAL;
|
|
my_timeout[PFTM_TCP_CLOSING] = PFTM_TCP_CLOSING_VAL;
|
|
my_timeout[PFTM_TCP_FIN_WAIT] = PFTM_TCP_FIN_WAIT_VAL;
|
|
my_timeout[PFTM_TCP_CLOSED] = PFTM_TCP_CLOSED_VAL;
|
|
my_timeout[PFTM_UDP_FIRST_PACKET] = PFTM_UDP_FIRST_PACKET_VAL;
|
|
my_timeout[PFTM_UDP_SINGLE] = PFTM_UDP_SINGLE_VAL;
|
|
my_timeout[PFTM_UDP_MULTIPLE] = PFTM_UDP_MULTIPLE_VAL;
|
|
my_timeout[PFTM_ICMP_FIRST_PACKET] = PFTM_ICMP_FIRST_PACKET_VAL;
|
|
my_timeout[PFTM_ICMP_ERROR_REPLY] = PFTM_ICMP_ERROR_REPLY_VAL;
|
|
my_timeout[PFTM_OTHER_FIRST_PACKET] = PFTM_OTHER_FIRST_PACKET_VAL;
|
|
my_timeout[PFTM_OTHER_SINGLE] = PFTM_OTHER_SINGLE_VAL;
|
|
my_timeout[PFTM_OTHER_MULTIPLE] = PFTM_OTHER_MULTIPLE_VAL;
|
|
my_timeout[PFTM_FRAG] = PFTM_FRAG_VAL;
|
|
my_timeout[PFTM_INTERVAL] = PFTM_INTERVAL_VAL;
|
|
my_timeout[PFTM_SRC_NODE] = PFTM_SRC_NODE_VAL;
|
|
my_timeout[PFTM_TS_DIFF] = PFTM_TS_DIFF_VAL;
|
|
my_timeout[PFTM_ADAPTIVE_START] = PFSTATE_ADAPT_START;
|
|
my_timeout[PFTM_ADAPTIVE_END] = PFSTATE_ADAPT_END;
|
|
|
|
pf_normalize_init();
|
|
bzero(&pf_status, sizeof(pf_status));
|
|
pf_status.debug = PF_DEBUG_URGENT;
|
|
|
|
pf_pfil_hooked = 0;
|
|
|
|
/* XXX do our best to avoid a conflict */
|
|
pf_status.hostid = arc4random();
|
|
|
|
if (kproc_create(pf_purge_thread, NULL, NULL, 0, 0, "pfpurge"))
|
|
return (ENXIO);
|
|
|
|
return (error);
|
|
}
|
|
#else /* !__FreeBSD__ */
|
|
void
|
|
pfattach(int num)
|
|
{
|
|
u_int32_t *timeout = pf_default_rule.timeout;
|
|
|
|
pool_init(&pf_rule_pl, sizeof(struct pf_rule), 0, 0, 0, "pfrulepl",
|
|
&pool_allocator_nointr);
|
|
pool_init(&pf_src_tree_pl, sizeof(struct pf_src_node), 0, 0, 0,
|
|
"pfsrctrpl", NULL);
|
|
pool_init(&pf_state_pl, sizeof(struct pf_state), 0, 0, 0, "pfstatepl",
|
|
NULL);
|
|
pool_init(&pf_altq_pl, sizeof(struct pf_altq), 0, 0, 0, "pfaltqpl",
|
|
&pool_allocator_nointr);
|
|
pool_init(&pf_pooladdr_pl, sizeof(struct pf_pooladdr), 0, 0, 0,
|
|
"pfpooladdrpl", &pool_allocator_nointr);
|
|
pfr_initialize();
|
|
pfi_initialize();
|
|
pf_osfp_initialize();
|
|
|
|
pool_sethardlimit(pf_pool_limits[PF_LIMIT_STATES].pp,
|
|
pf_pool_limits[PF_LIMIT_STATES].limit, NULL, 0);
|
|
|
|
if (ctob(physmem) <= 100*1024*1024)
|
|
pf_pool_limits[PF_LIMIT_TABLE_ENTRIES].limit =
|
|
PFR_KENTRY_HIWAT_SMALL;
|
|
|
|
RB_INIT(&tree_src_tracking);
|
|
RB_INIT(&pf_anchors);
|
|
pf_init_ruleset(&pf_main_ruleset);
|
|
TAILQ_INIT(&pf_altqs[0]);
|
|
TAILQ_INIT(&pf_altqs[1]);
|
|
TAILQ_INIT(&pf_pabuf);
|
|
pf_altqs_active = &pf_altqs[0];
|
|
pf_altqs_inactive = &pf_altqs[1];
|
|
TAILQ_INIT(&state_list);
|
|
|
|
/* default rule should never be garbage collected */
|
|
pf_default_rule.entries.tqe_prev = &pf_default_rule.entries.tqe_next;
|
|
pf_default_rule.action = PF_PASS;
|
|
pf_default_rule.nr = -1;
|
|
pf_default_rule.rtableid = -1;
|
|
|
|
/* initialize default timeouts */
|
|
timeout[PFTM_TCP_FIRST_PACKET] = PFTM_TCP_FIRST_PACKET_VAL;
|
|
timeout[PFTM_TCP_OPENING] = PFTM_TCP_OPENING_VAL;
|
|
timeout[PFTM_TCP_ESTABLISHED] = PFTM_TCP_ESTABLISHED_VAL;
|
|
timeout[PFTM_TCP_CLOSING] = PFTM_TCP_CLOSING_VAL;
|
|
timeout[PFTM_TCP_FIN_WAIT] = PFTM_TCP_FIN_WAIT_VAL;
|
|
timeout[PFTM_TCP_CLOSED] = PFTM_TCP_CLOSED_VAL;
|
|
timeout[PFTM_UDP_FIRST_PACKET] = PFTM_UDP_FIRST_PACKET_VAL;
|
|
timeout[PFTM_UDP_SINGLE] = PFTM_UDP_SINGLE_VAL;
|
|
timeout[PFTM_UDP_MULTIPLE] = PFTM_UDP_MULTIPLE_VAL;
|
|
timeout[PFTM_ICMP_FIRST_PACKET] = PFTM_ICMP_FIRST_PACKET_VAL;
|
|
timeout[PFTM_ICMP_ERROR_REPLY] = PFTM_ICMP_ERROR_REPLY_VAL;
|
|
timeout[PFTM_OTHER_FIRST_PACKET] = PFTM_OTHER_FIRST_PACKET_VAL;
|
|
timeout[PFTM_OTHER_SINGLE] = PFTM_OTHER_SINGLE_VAL;
|
|
timeout[PFTM_OTHER_MULTIPLE] = PFTM_OTHER_MULTIPLE_VAL;
|
|
timeout[PFTM_FRAG] = PFTM_FRAG_VAL;
|
|
timeout[PFTM_INTERVAL] = PFTM_INTERVAL_VAL;
|
|
timeout[PFTM_SRC_NODE] = PFTM_SRC_NODE_VAL;
|
|
timeout[PFTM_TS_DIFF] = PFTM_TS_DIFF_VAL;
|
|
timeout[PFTM_ADAPTIVE_START] = PFSTATE_ADAPT_START;
|
|
timeout[PFTM_ADAPTIVE_END] = PFSTATE_ADAPT_END;
|
|
|
|
pf_normalize_init();
|
|
bzero(&pf_status, sizeof(pf_status));
|
|
pf_status.debug = PF_DEBUG_URGENT;
|
|
|
|
/* XXX do our best to avoid a conflict */
|
|
pf_status.hostid = arc4random();
|
|
|
|
/* require process context to purge states, so perform in a thread */
|
|
kproc_create_deferred(pf_thread_create, NULL);
|
|
}
|
|
|
|
void
|
|
pf_thread_create(void *v)
|
|
{
|
|
if (kproc_create(pf_purge_thread, NULL, NULL, "pfpurge"))
|
|
panic("pfpurge thread");
|
|
}
|
|
|
|
int
|
|
pfopen(struct cdev *dev, int flags, int fmt, struct proc *p)
|
|
{
|
|
if (dev2unit(dev) >= 1)
|
|
return (ENXIO);
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
pfclose(struct cdev *dev, int flags, int fmt, struct proc *p)
|
|
{
|
|
if (dev2unit(dev) >= 1)
|
|
return (ENXIO);
|
|
return (0);
|
|
}
|
|
#endif /* __FreeBSD__ */
|
|
|
|
struct pf_pool *
|
|
pf_get_pool(char *anchor, u_int32_t ticket, u_int8_t rule_action,
|
|
u_int32_t rule_number, u_int8_t r_last, u_int8_t active,
|
|
u_int8_t check_ticket)
|
|
{
|
|
struct pf_ruleset *ruleset;
|
|
struct pf_rule *rule;
|
|
int rs_num;
|
|
|
|
ruleset = pf_find_ruleset(anchor);
|
|
if (ruleset == NULL)
|
|
return (NULL);
|
|
rs_num = pf_get_ruleset_number(rule_action);
|
|
if (rs_num >= PF_RULESET_MAX)
|
|
return (NULL);
|
|
if (active) {
|
|
if (check_ticket && ticket !=
|
|
ruleset->rules[rs_num].active.ticket)
|
|
return (NULL);
|
|
if (r_last)
|
|
rule = TAILQ_LAST(ruleset->rules[rs_num].active.ptr,
|
|
pf_rulequeue);
|
|
else
|
|
rule = TAILQ_FIRST(ruleset->rules[rs_num].active.ptr);
|
|
} else {
|
|
if (check_ticket && ticket !=
|
|
ruleset->rules[rs_num].inactive.ticket)
|
|
return (NULL);
|
|
if (r_last)
|
|
rule = TAILQ_LAST(ruleset->rules[rs_num].inactive.ptr,
|
|
pf_rulequeue);
|
|
else
|
|
rule = TAILQ_FIRST(ruleset->rules[rs_num].inactive.ptr);
|
|
}
|
|
if (!r_last) {
|
|
while ((rule != NULL) && (rule->nr != rule_number))
|
|
rule = TAILQ_NEXT(rule, entries);
|
|
}
|
|
if (rule == NULL)
|
|
return (NULL);
|
|
|
|
return (&rule->rpool);
|
|
}
|
|
|
|
void
|
|
pf_mv_pool(struct pf_palist *poola, struct pf_palist *poolb)
|
|
{
|
|
struct pf_pooladdr *mv_pool_pa;
|
|
|
|
while ((mv_pool_pa = TAILQ_FIRST(poola)) != NULL) {
|
|
TAILQ_REMOVE(poola, mv_pool_pa, entries);
|
|
TAILQ_INSERT_TAIL(poolb, mv_pool_pa, entries);
|
|
}
|
|
}
|
|
|
|
void
|
|
pf_empty_pool(struct pf_palist *poola)
|
|
{
|
|
struct pf_pooladdr *empty_pool_pa;
|
|
|
|
while ((empty_pool_pa = TAILQ_FIRST(poola)) != NULL) {
|
|
pfi_dynaddr_remove(&empty_pool_pa->addr);
|
|
pf_tbladdr_remove(&empty_pool_pa->addr);
|
|
pfi_kif_unref(empty_pool_pa->kif, PFI_KIF_REF_RULE);
|
|
TAILQ_REMOVE(poola, empty_pool_pa, entries);
|
|
pool_put(&pf_pooladdr_pl, empty_pool_pa);
|
|
}
|
|
}
|
|
|
|
void
|
|
pf_rm_rule(struct pf_rulequeue *rulequeue, struct pf_rule *rule)
|
|
{
|
|
if (rulequeue != NULL) {
|
|
if (rule->states <= 0) {
|
|
/*
|
|
* XXX - we need to remove the table *before* detaching
|
|
* the rule to make sure the table code does not delete
|
|
* the anchor under our feet.
|
|
*/
|
|
pf_tbladdr_remove(&rule->src.addr);
|
|
pf_tbladdr_remove(&rule->dst.addr);
|
|
if (rule->overload_tbl)
|
|
pfr_detach_table(rule->overload_tbl);
|
|
}
|
|
TAILQ_REMOVE(rulequeue, rule, entries);
|
|
rule->entries.tqe_prev = NULL;
|
|
rule->nr = -1;
|
|
}
|
|
|
|
if (rule->states > 0 || rule->src_nodes > 0 ||
|
|
rule->entries.tqe_prev != NULL)
|
|
return;
|
|
pf_tag_unref(rule->tag);
|
|
pf_tag_unref(rule->match_tag);
|
|
#ifdef ALTQ
|
|
if (rule->pqid != rule->qid)
|
|
pf_qid_unref(rule->pqid);
|
|
pf_qid_unref(rule->qid);
|
|
#endif
|
|
pf_rtlabel_remove(&rule->src.addr);
|
|
pf_rtlabel_remove(&rule->dst.addr);
|
|
pfi_dynaddr_remove(&rule->src.addr);
|
|
pfi_dynaddr_remove(&rule->dst.addr);
|
|
if (rulequeue == NULL) {
|
|
pf_tbladdr_remove(&rule->src.addr);
|
|
pf_tbladdr_remove(&rule->dst.addr);
|
|
if (rule->overload_tbl)
|
|
pfr_detach_table(rule->overload_tbl);
|
|
}
|
|
pfi_kif_unref(rule->kif, PFI_KIF_REF_RULE);
|
|
pf_anchor_remove(rule);
|
|
pf_empty_pool(&rule->rpool.list);
|
|
pool_put(&pf_rule_pl, rule);
|
|
}
|
|
|
|
u_int16_t
|
|
tagname2tag(struct pf_tags *head, char *tagname)
|
|
{
|
|
struct pf_tagname *tag, *p = NULL;
|
|
u_int16_t new_tagid = 1;
|
|
|
|
TAILQ_FOREACH(tag, head, entries)
|
|
if (strcmp(tagname, tag->name) == 0) {
|
|
tag->ref++;
|
|
return (tag->tag);
|
|
}
|
|
|
|
/*
|
|
* to avoid fragmentation, we do a linear search from the beginning
|
|
* and take the first free slot we find. if there is none or the list
|
|
* is empty, append a new entry at the end.
|
|
*/
|
|
|
|
/* new entry */
|
|
if (!TAILQ_EMPTY(head))
|
|
for (p = TAILQ_FIRST(head); p != NULL &&
|
|
p->tag == new_tagid; p = TAILQ_NEXT(p, entries))
|
|
new_tagid = p->tag + 1;
|
|
|
|
if (new_tagid > TAGID_MAX)
|
|
return (0);
|
|
|
|
/* allocate and fill new struct pf_tagname */
|
|
tag = (struct pf_tagname *)malloc(sizeof(struct pf_tagname),
|
|
M_TEMP, M_NOWAIT);
|
|
if (tag == NULL)
|
|
return (0);
|
|
bzero(tag, sizeof(struct pf_tagname));
|
|
strlcpy(tag->name, tagname, sizeof(tag->name));
|
|
tag->tag = new_tagid;
|
|
tag->ref++;
|
|
|
|
if (p != NULL) /* insert new entry before p */
|
|
TAILQ_INSERT_BEFORE(p, tag, entries);
|
|
else /* either list empty or no free slot in between */
|
|
TAILQ_INSERT_TAIL(head, tag, entries);
|
|
|
|
return (tag->tag);
|
|
}
|
|
|
|
void
|
|
tag2tagname(struct pf_tags *head, u_int16_t tagid, char *p)
|
|
{
|
|
struct pf_tagname *tag;
|
|
|
|
TAILQ_FOREACH(tag, head, entries)
|
|
if (tag->tag == tagid) {
|
|
strlcpy(p, tag->name, PF_TAG_NAME_SIZE);
|
|
return;
|
|
}
|
|
}
|
|
|
|
void
|
|
tag_unref(struct pf_tags *head, u_int16_t tag)
|
|
{
|
|
struct pf_tagname *p, *next;
|
|
|
|
if (tag == 0)
|
|
return;
|
|
|
|
for (p = TAILQ_FIRST(head); p != NULL; p = next) {
|
|
next = TAILQ_NEXT(p, entries);
|
|
if (tag == p->tag) {
|
|
if (--p->ref == 0) {
|
|
TAILQ_REMOVE(head, p, entries);
|
|
free(p, M_TEMP);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
u_int16_t
|
|
pf_tagname2tag(char *tagname)
|
|
{
|
|
return (tagname2tag(&pf_tags, tagname));
|
|
}
|
|
|
|
void
|
|
pf_tag2tagname(u_int16_t tagid, char *p)
|
|
{
|
|
tag2tagname(&pf_tags, tagid, p);
|
|
}
|
|
|
|
void
|
|
pf_tag_ref(u_int16_t tag)
|
|
{
|
|
struct pf_tagname *t;
|
|
|
|
TAILQ_FOREACH(t, &pf_tags, entries)
|
|
if (t->tag == tag)
|
|
break;
|
|
if (t != NULL)
|
|
t->ref++;
|
|
}
|
|
|
|
void
|
|
pf_tag_unref(u_int16_t tag)
|
|
{
|
|
tag_unref(&pf_tags, tag);
|
|
}
|
|
|
|
int
|
|
pf_rtlabel_add(struct pf_addr_wrap *a)
|
|
{
|
|
#ifdef __FreeBSD__
|
|
/* XXX_IMPORT: later */
|
|
return (0);
|
|
#else
|
|
if (a->type == PF_ADDR_RTLABEL &&
|
|
(a->v.rtlabel = rtlabel_name2id(a->v.rtlabelname)) == 0)
|
|
return (-1);
|
|
return (0);
|
|
#endif
|
|
}
|
|
|
|
void
|
|
pf_rtlabel_remove(struct pf_addr_wrap *a)
|
|
{
|
|
#ifdef __FreeBSD__
|
|
/* XXX_IMPORT: later */
|
|
#else
|
|
if (a->type == PF_ADDR_RTLABEL)
|
|
rtlabel_unref(a->v.rtlabel);
|
|
#endif
|
|
}
|
|
|
|
void
|
|
pf_rtlabel_copyout(struct pf_addr_wrap *a)
|
|
{
|
|
#ifdef __FreeBSD__
|
|
/* XXX_IMPORT: later */
|
|
if (a->type == PF_ADDR_RTLABEL && a->v.rtlabel)
|
|
strlcpy(a->v.rtlabelname, "?", sizeof(a->v.rtlabelname));
|
|
#else
|
|
const char *name;
|
|
|
|
if (a->type == PF_ADDR_RTLABEL && a->v.rtlabel) {
|
|
if ((name = rtlabel_id2name(a->v.rtlabel)) == NULL)
|
|
strlcpy(a->v.rtlabelname, "?",
|
|
sizeof(a->v.rtlabelname));
|
|
else
|
|
strlcpy(a->v.rtlabelname, name,
|
|
sizeof(a->v.rtlabelname));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#ifdef ALTQ
|
|
u_int32_t
|
|
pf_qname2qid(char *qname)
|
|
{
|
|
return ((u_int32_t)tagname2tag(&pf_qids, qname));
|
|
}
|
|
|
|
void
|
|
pf_qid2qname(u_int32_t qid, char *p)
|
|
{
|
|
tag2tagname(&pf_qids, (u_int16_t)qid, p);
|
|
}
|
|
|
|
void
|
|
pf_qid_unref(u_int32_t qid)
|
|
{
|
|
tag_unref(&pf_qids, (u_int16_t)qid);
|
|
}
|
|
|
|
int
|
|
pf_begin_altq(u_int32_t *ticket)
|
|
{
|
|
struct pf_altq *altq;
|
|
int error = 0;
|
|
|
|
/* Purge the old altq list */
|
|
while ((altq = TAILQ_FIRST(pf_altqs_inactive)) != NULL) {
|
|
TAILQ_REMOVE(pf_altqs_inactive, altq, entries);
|
|
#ifdef __FreeBSD__
|
|
if (altq->qname[0] == 0 &&
|
|
(altq->local_flags & PFALTQ_FLAG_IF_REMOVED) == 0) {
|
|
#else
|
|
if (altq->qname[0] == 0) {
|
|
#endif
|
|
/* detach and destroy the discipline */
|
|
error = altq_remove(altq);
|
|
} else
|
|
pf_qid_unref(altq->qid);
|
|
pool_put(&pf_altq_pl, altq);
|
|
}
|
|
if (error)
|
|
return (error);
|
|
*ticket = ++ticket_altqs_inactive;
|
|
altqs_inactive_open = 1;
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
pf_rollback_altq(u_int32_t ticket)
|
|
{
|
|
struct pf_altq *altq;
|
|
int error = 0;
|
|
|
|
if (!altqs_inactive_open || ticket != ticket_altqs_inactive)
|
|
return (0);
|
|
/* Purge the old altq list */
|
|
while ((altq = TAILQ_FIRST(pf_altqs_inactive)) != NULL) {
|
|
TAILQ_REMOVE(pf_altqs_inactive, altq, entries);
|
|
#ifdef __FreeBSD__
|
|
if (altq->qname[0] == 0 &&
|
|
(altq->local_flags & PFALTQ_FLAG_IF_REMOVED) == 0) {
|
|
#else
|
|
if (altq->qname[0] == 0) {
|
|
#endif
|
|
/* detach and destroy the discipline */
|
|
error = altq_remove(altq);
|
|
} else
|
|
pf_qid_unref(altq->qid);
|
|
pool_put(&pf_altq_pl, altq);
|
|
}
|
|
altqs_inactive_open = 0;
|
|
return (error);
|
|
}
|
|
|
|
int
|
|
pf_commit_altq(u_int32_t ticket)
|
|
{
|
|
struct pf_altqqueue *old_altqs;
|
|
struct pf_altq *altq;
|
|
int s, err, error = 0;
|
|
|
|
if (!altqs_inactive_open || ticket != ticket_altqs_inactive)
|
|
return (EBUSY);
|
|
|
|
/* swap altqs, keep the old. */
|
|
s = splsoftnet();
|
|
old_altqs = pf_altqs_active;
|
|
pf_altqs_active = pf_altqs_inactive;
|
|
pf_altqs_inactive = old_altqs;
|
|
ticket_altqs_active = ticket_altqs_inactive;
|
|
|
|
/* Attach new disciplines */
|
|
TAILQ_FOREACH(altq, pf_altqs_active, entries) {
|
|
#ifdef __FreeBSD__
|
|
if (altq->qname[0] == 0 &&
|
|
(altq->local_flags & PFALTQ_FLAG_IF_REMOVED) == 0) {
|
|
#else
|
|
if (altq->qname[0] == 0) {
|
|
#endif
|
|
/* attach the discipline */
|
|
error = altq_pfattach(altq);
|
|
if (error == 0 && pf_altq_running)
|
|
error = pf_enable_altq(altq);
|
|
if (error != 0) {
|
|
splx(s);
|
|
return (error);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Purge the old altq list */
|
|
while ((altq = TAILQ_FIRST(pf_altqs_inactive)) != NULL) {
|
|
TAILQ_REMOVE(pf_altqs_inactive, altq, entries);
|
|
#ifdef __FreeBSD__
|
|
if (altq->qname[0] == 0 &&
|
|
(altq->local_flags & PFALTQ_FLAG_IF_REMOVED) == 0) {
|
|
#else
|
|
if (altq->qname[0] == 0) {
|
|
#endif
|
|
/* detach and destroy the discipline */
|
|
if (pf_altq_running)
|
|
error = pf_disable_altq(altq);
|
|
err = altq_pfdetach(altq);
|
|
if (err != 0 && error == 0)
|
|
error = err;
|
|
err = altq_remove(altq);
|
|
if (err != 0 && error == 0)
|
|
error = err;
|
|
} else
|
|
pf_qid_unref(altq->qid);
|
|
pool_put(&pf_altq_pl, altq);
|
|
}
|
|
splx(s);
|
|
|
|
altqs_inactive_open = 0;
|
|
return (error);
|
|
}
|
|
|
|
int
|
|
pf_enable_altq(struct pf_altq *altq)
|
|
{
|
|
struct ifnet *ifp;
|
|
struct tb_profile tb;
|
|
int s, error = 0;
|
|
|
|
if ((ifp = ifunit(altq->ifname)) == NULL)
|
|
return (EINVAL);
|
|
|
|
if (ifp->if_snd.altq_type != ALTQT_NONE)
|
|
error = altq_enable(&ifp->if_snd);
|
|
|
|
/* set tokenbucket regulator */
|
|
if (error == 0 && ifp != NULL && ALTQ_IS_ENABLED(&ifp->if_snd)) {
|
|
tb.rate = altq->ifbandwidth;
|
|
tb.depth = altq->tbrsize;
|
|
s = splnet();
|
|
#ifdef __FreeBSD__
|
|
PF_UNLOCK();
|
|
#endif
|
|
error = tbr_set(&ifp->if_snd, &tb);
|
|
#ifdef __FreeBSD__
|
|
PF_LOCK();
|
|
#endif
|
|
splx(s);
|
|
}
|
|
|
|
return (error);
|
|
}
|
|
|
|
int
|
|
pf_disable_altq(struct pf_altq *altq)
|
|
{
|
|
struct ifnet *ifp;
|
|
struct tb_profile tb;
|
|
int s, error;
|
|
|
|
if ((ifp = ifunit(altq->ifname)) == NULL)
|
|
return (EINVAL);
|
|
|
|
/*
|
|
* when the discipline is no longer referenced, it was overridden
|
|
* by a new one. if so, just return.
|
|
*/
|
|
if (altq->altq_disc != ifp->if_snd.altq_disc)
|
|
return (0);
|
|
|
|
error = altq_disable(&ifp->if_snd);
|
|
|
|
if (error == 0) {
|
|
/* clear tokenbucket regulator */
|
|
tb.rate = 0;
|
|
s = splnet();
|
|
#ifdef __FreeBSD__
|
|
PF_UNLOCK();
|
|
#endif
|
|
error = tbr_set(&ifp->if_snd, &tb);
|
|
#ifdef __FreeBSD__
|
|
PF_LOCK();
|
|
#endif
|
|
splx(s);
|
|
}
|
|
|
|
return (error);
|
|
}
|
|
|
|
#ifdef __FreeBSD__
|
|
void
|
|
pf_altq_ifnet_event(struct ifnet *ifp, int remove)
|
|
{
|
|
struct ifnet *ifp1;
|
|
struct pf_altq *a1, *a2, *a3;
|
|
u_int32_t ticket;
|
|
int error = 0;
|
|
|
|
/* Interrupt userland queue modifications */
|
|
if (altqs_inactive_open)
|
|
pf_rollback_altq(ticket_altqs_inactive);
|
|
|
|
/* Start new altq ruleset */
|
|
if (pf_begin_altq(&ticket))
|
|
return;
|
|
|
|
/* Copy the current active set */
|
|
TAILQ_FOREACH(a1, pf_altqs_active, entries) {
|
|
a2 = pool_get(&pf_altq_pl, PR_NOWAIT);
|
|
if (a2 == NULL) {
|
|
error = ENOMEM;
|
|
break;
|
|
}
|
|
bcopy(a1, a2, sizeof(struct pf_altq));
|
|
|
|
if (a2->qname[0] != 0) {
|
|
if ((a2->qid = pf_qname2qid(a2->qname)) == 0) {
|
|
error = EBUSY;
|
|
pool_put(&pf_altq_pl, a2);
|
|
break;
|
|
}
|
|
a2->altq_disc = NULL;
|
|
TAILQ_FOREACH(a3, pf_altqs_inactive, entries) {
|
|
if (strncmp(a3->ifname, a2->ifname,
|
|
IFNAMSIZ) == 0 && a3->qname[0] == 0) {
|
|
a2->altq_disc = a3->altq_disc;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
/* Deactivate the interface in question */
|
|
a2->local_flags &= ~PFALTQ_FLAG_IF_REMOVED;
|
|
if ((ifp1 = ifunit(a2->ifname)) == NULL ||
|
|
(remove && ifp1 == ifp)) {
|
|
a2->local_flags |= PFALTQ_FLAG_IF_REMOVED;
|
|
} else {
|
|
PF_UNLOCK();
|
|
error = altq_add(a2);
|
|
PF_LOCK();
|
|
|
|
if (ticket != ticket_altqs_inactive)
|
|
error = EBUSY;
|
|
|
|
if (error) {
|
|
pool_put(&pf_altq_pl, a2);
|
|
break;
|
|
}
|
|
}
|
|
|
|
TAILQ_INSERT_TAIL(pf_altqs_inactive, a2, entries);
|
|
}
|
|
|
|
if (error != 0)
|
|
pf_rollback_altq(ticket);
|
|
else
|
|
pf_commit_altq(ticket);
|
|
}
|
|
#endif
|
|
#endif /* ALTQ */
|
|
|
|
int
|
|
pf_begin_rules(u_int32_t *ticket, int rs_num, const char *anchor)
|
|
{
|
|
struct pf_ruleset *rs;
|
|
struct pf_rule *rule;
|
|
|
|
if (rs_num < 0 || rs_num >= PF_RULESET_MAX)
|
|
return (EINVAL);
|
|
rs = pf_find_or_create_ruleset(anchor);
|
|
if (rs == NULL)
|
|
return (EINVAL);
|
|
while ((rule = TAILQ_FIRST(rs->rules[rs_num].inactive.ptr)) != NULL) {
|
|
pf_rm_rule(rs->rules[rs_num].inactive.ptr, rule);
|
|
rs->rules[rs_num].inactive.rcount--;
|
|
}
|
|
*ticket = ++rs->rules[rs_num].inactive.ticket;
|
|
rs->rules[rs_num].inactive.open = 1;
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
pf_rollback_rules(u_int32_t ticket, int rs_num, char *anchor)
|
|
{
|
|
struct pf_ruleset *rs;
|
|
struct pf_rule *rule;
|
|
|
|
if (rs_num < 0 || rs_num >= PF_RULESET_MAX)
|
|
return (EINVAL);
|
|
rs = pf_find_ruleset(anchor);
|
|
if (rs == NULL || !rs->rules[rs_num].inactive.open ||
|
|
rs->rules[rs_num].inactive.ticket != ticket)
|
|
return (0);
|
|
while ((rule = TAILQ_FIRST(rs->rules[rs_num].inactive.ptr)) != NULL) {
|
|
pf_rm_rule(rs->rules[rs_num].inactive.ptr, rule);
|
|
rs->rules[rs_num].inactive.rcount--;
|
|
}
|
|
rs->rules[rs_num].inactive.open = 0;
|
|
return (0);
|
|
}
|
|
|
|
#define PF_MD5_UPD(st, elm) \
|
|
MD5Update(ctx, (u_int8_t *) &(st)->elm, sizeof((st)->elm))
|
|
|
|
#define PF_MD5_UPD_STR(st, elm) \
|
|
MD5Update(ctx, (u_int8_t *) (st)->elm, strlen((st)->elm))
|
|
|
|
#define PF_MD5_UPD_HTONL(st, elm, stor) do { \
|
|
(stor) = htonl((st)->elm); \
|
|
MD5Update(ctx, (u_int8_t *) &(stor), sizeof(u_int32_t));\
|
|
} while (0)
|
|
|
|
#define PF_MD5_UPD_HTONS(st, elm, stor) do { \
|
|
(stor) = htons((st)->elm); \
|
|
MD5Update(ctx, (u_int8_t *) &(stor), sizeof(u_int16_t));\
|
|
} while (0)
|
|
|
|
void
|
|
pf_hash_rule_addr(MD5_CTX *ctx, struct pf_rule_addr *pfr)
|
|
{
|
|
PF_MD5_UPD(pfr, addr.type);
|
|
switch (pfr->addr.type) {
|
|
case PF_ADDR_DYNIFTL:
|
|
PF_MD5_UPD(pfr, addr.v.ifname);
|
|
PF_MD5_UPD(pfr, addr.iflags);
|
|
break;
|
|
case PF_ADDR_TABLE:
|
|
PF_MD5_UPD(pfr, addr.v.tblname);
|
|
break;
|
|
case PF_ADDR_ADDRMASK:
|
|
/* XXX ignore af? */
|
|
PF_MD5_UPD(pfr, addr.v.a.addr.addr32);
|
|
PF_MD5_UPD(pfr, addr.v.a.mask.addr32);
|
|
break;
|
|
case PF_ADDR_RTLABEL:
|
|
PF_MD5_UPD(pfr, addr.v.rtlabelname);
|
|
break;
|
|
}
|
|
|
|
PF_MD5_UPD(pfr, port[0]);
|
|
PF_MD5_UPD(pfr, port[1]);
|
|
PF_MD5_UPD(pfr, neg);
|
|
PF_MD5_UPD(pfr, port_op);
|
|
}
|
|
|
|
void
|
|
pf_hash_rule(MD5_CTX *ctx, struct pf_rule *rule)
|
|
{
|
|
u_int16_t x;
|
|
u_int32_t y;
|
|
|
|
pf_hash_rule_addr(ctx, &rule->src);
|
|
pf_hash_rule_addr(ctx, &rule->dst);
|
|
PF_MD5_UPD_STR(rule, label);
|
|
PF_MD5_UPD_STR(rule, ifname);
|
|
PF_MD5_UPD_STR(rule, match_tagname);
|
|
PF_MD5_UPD_HTONS(rule, match_tag, x); /* dup? */
|
|
PF_MD5_UPD_HTONL(rule, os_fingerprint, y);
|
|
PF_MD5_UPD_HTONL(rule, prob, y);
|
|
PF_MD5_UPD_HTONL(rule, uid.uid[0], y);
|
|
PF_MD5_UPD_HTONL(rule, uid.uid[1], y);
|
|
PF_MD5_UPD(rule, uid.op);
|
|
PF_MD5_UPD_HTONL(rule, gid.gid[0], y);
|
|
PF_MD5_UPD_HTONL(rule, gid.gid[1], y);
|
|
PF_MD5_UPD(rule, gid.op);
|
|
PF_MD5_UPD_HTONL(rule, rule_flag, y);
|
|
PF_MD5_UPD(rule, action);
|
|
PF_MD5_UPD(rule, direction);
|
|
PF_MD5_UPD(rule, af);
|
|
PF_MD5_UPD(rule, quick);
|
|
PF_MD5_UPD(rule, ifnot);
|
|
PF_MD5_UPD(rule, match_tag_not);
|
|
PF_MD5_UPD(rule, natpass);
|
|
PF_MD5_UPD(rule, keep_state);
|
|
PF_MD5_UPD(rule, proto);
|
|
PF_MD5_UPD(rule, type);
|
|
PF_MD5_UPD(rule, code);
|
|
PF_MD5_UPD(rule, flags);
|
|
PF_MD5_UPD(rule, flagset);
|
|
PF_MD5_UPD(rule, allow_opts);
|
|
PF_MD5_UPD(rule, rt);
|
|
PF_MD5_UPD(rule, tos);
|
|
}
|
|
|
|
int
|
|
pf_commit_rules(u_int32_t ticket, int rs_num, char *anchor)
|
|
{
|
|
struct pf_ruleset *rs;
|
|
struct pf_rule *rule, **old_array;
|
|
struct pf_rulequeue *old_rules;
|
|
int s, error;
|
|
u_int32_t old_rcount;
|
|
|
|
if (rs_num < 0 || rs_num >= PF_RULESET_MAX)
|
|
return (EINVAL);
|
|
rs = pf_find_ruleset(anchor);
|
|
if (rs == NULL || !rs->rules[rs_num].inactive.open ||
|
|
ticket != rs->rules[rs_num].inactive.ticket)
|
|
return (EBUSY);
|
|
|
|
/* Calculate checksum for the main ruleset */
|
|
if (rs == &pf_main_ruleset) {
|
|
error = pf_setup_pfsync_matching(rs);
|
|
if (error != 0)
|
|
return (error);
|
|
}
|
|
|
|
/* Swap rules, keep the old. */
|
|
s = splsoftnet();
|
|
old_rules = rs->rules[rs_num].active.ptr;
|
|
old_rcount = rs->rules[rs_num].active.rcount;
|
|
old_array = rs->rules[rs_num].active.ptr_array;
|
|
|
|
rs->rules[rs_num].active.ptr =
|
|
rs->rules[rs_num].inactive.ptr;
|
|
rs->rules[rs_num].active.ptr_array =
|
|
rs->rules[rs_num].inactive.ptr_array;
|
|
rs->rules[rs_num].active.rcount =
|
|
rs->rules[rs_num].inactive.rcount;
|
|
rs->rules[rs_num].inactive.ptr = old_rules;
|
|
rs->rules[rs_num].inactive.ptr_array = old_array;
|
|
rs->rules[rs_num].inactive.rcount = old_rcount;
|
|
|
|
rs->rules[rs_num].active.ticket =
|
|
rs->rules[rs_num].inactive.ticket;
|
|
pf_calc_skip_steps(rs->rules[rs_num].active.ptr);
|
|
|
|
|
|
/* Purge the old rule list. */
|
|
while ((rule = TAILQ_FIRST(old_rules)) != NULL)
|
|
pf_rm_rule(old_rules, rule);
|
|
if (rs->rules[rs_num].inactive.ptr_array)
|
|
free(rs->rules[rs_num].inactive.ptr_array, M_TEMP);
|
|
rs->rules[rs_num].inactive.ptr_array = NULL;
|
|
rs->rules[rs_num].inactive.rcount = 0;
|
|
rs->rules[rs_num].inactive.open = 0;
|
|
pf_remove_if_empty_ruleset(rs);
|
|
splx(s);
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
pf_setup_pfsync_matching(struct pf_ruleset *rs)
|
|
{
|
|
MD5_CTX ctx;
|
|
struct pf_rule *rule;
|
|
int rs_cnt;
|
|
u_int8_t digest[PF_MD5_DIGEST_LENGTH];
|
|
|
|
MD5Init(&ctx);
|
|
for (rs_cnt = 0; rs_cnt < PF_RULESET_MAX; rs_cnt++) {
|
|
/* XXX PF_RULESET_SCRUB as well? */
|
|
if (rs_cnt == PF_RULESET_SCRUB)
|
|
continue;
|
|
|
|
if (rs->rules[rs_cnt].inactive.ptr_array)
|
|
free(rs->rules[rs_cnt].inactive.ptr_array, M_TEMP);
|
|
rs->rules[rs_cnt].inactive.ptr_array = NULL;
|
|
|
|
if (rs->rules[rs_cnt].inactive.rcount) {
|
|
rs->rules[rs_cnt].inactive.ptr_array =
|
|
malloc(sizeof(caddr_t) *
|
|
rs->rules[rs_cnt].inactive.rcount,
|
|
M_TEMP, M_NOWAIT);
|
|
|
|
if (!rs->rules[rs_cnt].inactive.ptr_array)
|
|
return (ENOMEM);
|
|
}
|
|
|
|
TAILQ_FOREACH(rule, rs->rules[rs_cnt].inactive.ptr,
|
|
entries) {
|
|
pf_hash_rule(&ctx, rule);
|
|
(rs->rules[rs_cnt].inactive.ptr_array)[rule->nr] = rule;
|
|
}
|
|
}
|
|
|
|
MD5Final(digest, &ctx);
|
|
memcpy(pf_status.pf_chksum, digest, sizeof(pf_status.pf_chksum));
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
#ifdef __FreeBSD__
|
|
pfioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flags, struct thread *td)
|
|
#else
|
|
pfioctl(dev_t dev, u_long cmd, caddr_t addr, int flags, struct proc *p)
|
|
#endif
|
|
{
|
|
struct pf_pooladdr *pa = NULL;
|
|
struct pf_pool *pool = NULL;
|
|
#ifndef __FreeBSD__
|
|
int s;
|
|
#endif
|
|
int error = 0;
|
|
|
|
/* XXX keep in sync with switch() below */
|
|
#ifdef __FreeBSD__
|
|
if (securelevel_gt(td->td_ucred, 2))
|
|
#else
|
|
if (securelevel > 1)
|
|
#endif
|
|
switch (cmd) {
|
|
case DIOCGETRULES:
|
|
case DIOCGETRULE:
|
|
case DIOCGETADDRS:
|
|
case DIOCGETADDR:
|
|
case DIOCGETSTATE:
|
|
case DIOCSETSTATUSIF:
|
|
case DIOCGETSTATUS:
|
|
case DIOCCLRSTATUS:
|
|
case DIOCNATLOOK:
|
|
case DIOCSETDEBUG:
|
|
case DIOCGETSTATES:
|
|
case DIOCGETTIMEOUT:
|
|
case DIOCCLRRULECTRS:
|
|
case DIOCGETLIMIT:
|
|
case DIOCGETALTQS:
|
|
case DIOCGETALTQ:
|
|
case DIOCGETQSTATS:
|
|
case DIOCGETRULESETS:
|
|
case DIOCGETRULESET:
|
|
case DIOCRGETTABLES:
|
|
case DIOCRGETTSTATS:
|
|
case DIOCRCLRTSTATS:
|
|
case DIOCRCLRADDRS:
|
|
case DIOCRADDADDRS:
|
|
case DIOCRDELADDRS:
|
|
case DIOCRSETADDRS:
|
|
case DIOCRGETADDRS:
|
|
case DIOCRGETASTATS:
|
|
case DIOCRCLRASTATS:
|
|
case DIOCRTSTADDRS:
|
|
case DIOCOSFPGET:
|
|
case DIOCGETSRCNODES:
|
|
case DIOCCLRSRCNODES:
|
|
case DIOCIGETIFACES:
|
|
#ifdef __FreeBSD__
|
|
case DIOCGIFSPEED:
|
|
#endif
|
|
case DIOCSETIFFLAG:
|
|
case DIOCCLRIFFLAG:
|
|
break;
|
|
case DIOCRCLRTABLES:
|
|
case DIOCRADDTABLES:
|
|
case DIOCRDELTABLES:
|
|
case DIOCRSETTFLAGS:
|
|
if (((struct pfioc_table *)addr)->pfrio_flags &
|
|
PFR_FLAG_DUMMY)
|
|
break; /* dummy operation ok */
|
|
return (EPERM);
|
|
default:
|
|
return (EPERM);
|
|
}
|
|
|
|
if (!(flags & FWRITE))
|
|
switch (cmd) {
|
|
case DIOCGETRULES:
|
|
case DIOCGETADDRS:
|
|
case DIOCGETADDR:
|
|
case DIOCGETSTATE:
|
|
case DIOCGETSTATUS:
|
|
case DIOCGETSTATES:
|
|
case DIOCGETTIMEOUT:
|
|
case DIOCGETLIMIT:
|
|
case DIOCGETALTQS:
|
|
case DIOCGETALTQ:
|
|
case DIOCGETQSTATS:
|
|
case DIOCGETRULESETS:
|
|
case DIOCGETRULESET:
|
|
case DIOCNATLOOK:
|
|
case DIOCRGETTABLES:
|
|
case DIOCRGETTSTATS:
|
|
case DIOCRGETADDRS:
|
|
case DIOCRGETASTATS:
|
|
case DIOCRTSTADDRS:
|
|
case DIOCOSFPGET:
|
|
case DIOCGETSRCNODES:
|
|
case DIOCIGETIFACES:
|
|
#ifdef __FreeBSD__
|
|
case DIOCGIFSPEED:
|
|
#endif
|
|
break;
|
|
case DIOCRCLRTABLES:
|
|
case DIOCRADDTABLES:
|
|
case DIOCRDELTABLES:
|
|
case DIOCRCLRTSTATS:
|
|
case DIOCRCLRADDRS:
|
|
case DIOCRADDADDRS:
|
|
case DIOCRDELADDRS:
|
|
case DIOCRSETADDRS:
|
|
case DIOCRSETTFLAGS:
|
|
if (((struct pfioc_table *)addr)->pfrio_flags &
|
|
PFR_FLAG_DUMMY) {
|
|
flags |= FWRITE; /* need write lock for dummy */
|
|
break; /* dummy operation ok */
|
|
}
|
|
return (EACCES);
|
|
case DIOCGETRULE:
|
|
if (((struct pfioc_rule *)addr)->action == PF_GET_CLR_CNTR)
|
|
return (EACCES);
|
|
break;
|
|
default:
|
|
return (EACCES);
|
|
}
|
|
|
|
if (flags & FWRITE)
|
|
#ifdef __FreeBSD__
|
|
sx_xlock(&pf_consistency_lock);
|
|
else
|
|
sx_slock(&pf_consistency_lock);
|
|
#else
|
|
rw_enter_write(&pf_consistency_lock);
|
|
else
|
|
rw_enter_read(&pf_consistency_lock);
|
|
#endif
|
|
|
|
#ifdef __FreeBSD__
|
|
PF_LOCK();
|
|
#else
|
|
s = splsoftnet();
|
|
#endif
|
|
switch (cmd) {
|
|
|
|
case DIOCSTART:
|
|
if (pf_status.running)
|
|
error = EEXIST;
|
|
else {
|
|
#ifdef __FreeBSD__
|
|
PF_UNLOCK();
|
|
error = hook_pf();
|
|
PF_LOCK();
|
|
if (error) {
|
|
DPFPRINTF(PF_DEBUG_MISC,
|
|
("pf: pfil registeration fail\n"));
|
|
break;
|
|
}
|
|
#endif
|
|
pf_status.running = 1;
|
|
pf_status.since = time_second;
|
|
if (pf_status.stateid == 0) {
|
|
pf_status.stateid = time_second;
|
|
pf_status.stateid = pf_status.stateid << 32;
|
|
}
|
|
DPFPRINTF(PF_DEBUG_MISC, ("pf: started\n"));
|
|
}
|
|
break;
|
|
|
|
case DIOCSTOP:
|
|
if (!pf_status.running)
|
|
error = ENOENT;
|
|
else {
|
|
pf_status.running = 0;
|
|
#ifdef __FreeBSD__
|
|
PF_UNLOCK();
|
|
error = dehook_pf();
|
|
PF_LOCK();
|
|
if (error) {
|
|
pf_status.running = 1;
|
|
DPFPRINTF(PF_DEBUG_MISC,
|
|
("pf: pfil unregisteration failed\n"));
|
|
}
|
|
#endif
|
|
pf_status.since = time_second;
|
|
DPFPRINTF(PF_DEBUG_MISC, ("pf: stopped\n"));
|
|
}
|
|
break;
|
|
|
|
case DIOCADDRULE: {
|
|
struct pfioc_rule *pr = (struct pfioc_rule *)addr;
|
|
struct pf_ruleset *ruleset;
|
|
struct pf_rule *rule, *tail;
|
|
struct pf_pooladdr *pa;
|
|
int rs_num;
|
|
|
|
pr->anchor[sizeof(pr->anchor) - 1] = 0;
|
|
ruleset = pf_find_ruleset(pr->anchor);
|
|
if (ruleset == NULL) {
|
|
error = EINVAL;
|
|
break;
|
|
}
|
|
rs_num = pf_get_ruleset_number(pr->rule.action);
|
|
if (rs_num >= PF_RULESET_MAX) {
|
|
error = EINVAL;
|
|
break;
|
|
}
|
|
if (pr->rule.return_icmp >> 8 > ICMP_MAXTYPE) {
|
|
error = EINVAL;
|
|
break;
|
|
}
|
|
if (pr->ticket != ruleset->rules[rs_num].inactive.ticket) {
|
|
#ifdef __FreeBSD__
|
|
DPFPRINTF(PF_DEBUG_MISC,
|
|
("ticket: %d != [%d]%d\n", pr->ticket, rs_num,
|
|
ruleset->rules[rs_num].inactive.ticket));
|
|
#endif
|
|
error = EBUSY;
|
|
break;
|
|
}
|
|
if (pr->pool_ticket != ticket_pabuf) {
|
|
#ifdef __FreeBSD__
|
|
DPFPRINTF(PF_DEBUG_MISC,
|
|
("pool_ticket: %d != %d\n", pr->pool_ticket,
|
|
ticket_pabuf));
|
|
#endif
|
|
error = EBUSY;
|
|
break;
|
|
}
|
|
rule = pool_get(&pf_rule_pl, PR_NOWAIT);
|
|
if (rule == NULL) {
|
|
error = ENOMEM;
|
|
break;
|
|
}
|
|
bcopy(&pr->rule, rule, sizeof(struct pf_rule));
|
|
#ifdef __FreeBSD__
|
|
rule->cuid = td->td_ucred->cr_ruid;
|
|
rule->cpid = td->td_proc ? td->td_proc->p_pid : 0;
|
|
#else
|
|
rule->cuid = p->p_cred->p_ruid;
|
|
rule->cpid = p->p_pid;
|
|
#endif
|
|
rule->anchor = NULL;
|
|
rule->kif = NULL;
|
|
TAILQ_INIT(&rule->rpool.list);
|
|
/* initialize refcounting */
|
|
rule->states = 0;
|
|
rule->src_nodes = 0;
|
|
rule->entries.tqe_prev = NULL;
|
|
#ifndef INET
|
|
if (rule->af == AF_INET) {
|
|
pool_put(&pf_rule_pl, rule);
|
|
error = EAFNOSUPPORT;
|
|
break;
|
|
}
|
|
#endif /* INET */
|
|
#ifndef INET6
|
|
if (rule->af == AF_INET6) {
|
|
pool_put(&pf_rule_pl, rule);
|
|
error = EAFNOSUPPORT;
|
|
break;
|
|
}
|
|
#endif /* INET6 */
|
|
tail = TAILQ_LAST(ruleset->rules[rs_num].inactive.ptr,
|
|
pf_rulequeue);
|
|
if (tail)
|
|
rule->nr = tail->nr + 1;
|
|
else
|
|
rule->nr = 0;
|
|
if (rule->ifname[0]) {
|
|
rule->kif = pfi_kif_get(rule->ifname);
|
|
if (rule->kif == NULL) {
|
|
pool_put(&pf_rule_pl, rule);
|
|
error = EINVAL;
|
|
break;
|
|
}
|
|
pfi_kif_ref(rule->kif, PFI_KIF_REF_RULE);
|
|
}
|
|
|
|
#ifdef __FreeBSD__ /* ROUTING */
|
|
if (rule->rtableid > 0 && rule->rtableid > rt_numfibs)
|
|
#else
|
|
if (rule->rtableid > 0 && !rtable_exists(rule->rtableid))
|
|
#endif
|
|
error = EBUSY;
|
|
|
|
#ifdef ALTQ
|
|
/* set queue IDs */
|
|
if (rule->qname[0] != 0) {
|
|
if ((rule->qid = pf_qname2qid(rule->qname)) == 0)
|
|
error = EBUSY;
|
|
else if (rule->pqname[0] != 0) {
|
|
if ((rule->pqid =
|
|
pf_qname2qid(rule->pqname)) == 0)
|
|
error = EBUSY;
|
|
} else
|
|
rule->pqid = rule->qid;
|
|
}
|
|
#endif
|
|
if (rule->tagname[0])
|
|
if ((rule->tag = pf_tagname2tag(rule->tagname)) == 0)
|
|
error = EBUSY;
|
|
if (rule->match_tagname[0])
|
|
if ((rule->match_tag =
|
|
pf_tagname2tag(rule->match_tagname)) == 0)
|
|
error = EBUSY;
|
|
if (rule->rt && !rule->direction)
|
|
error = EINVAL;
|
|
#if NPFLOG > 0
|
|
#ifdef __FreeBSD__
|
|
if (!rule->log)
|
|
rule->logif = 0;
|
|
#endif
|
|
if (rule->logif >= PFLOGIFS_MAX)
|
|
error = EINVAL;
|
|
#endif
|
|
if (pf_rtlabel_add(&rule->src.addr) ||
|
|
pf_rtlabel_add(&rule->dst.addr))
|
|
error = EBUSY;
|
|
if (pfi_dynaddr_setup(&rule->src.addr, rule->af))
|
|
error = EINVAL;
|
|
if (pfi_dynaddr_setup(&rule->dst.addr, rule->af))
|
|
error = EINVAL;
|
|
if (pf_tbladdr_setup(ruleset, &rule->src.addr))
|
|
error = EINVAL;
|
|
if (pf_tbladdr_setup(ruleset, &rule->dst.addr))
|
|
error = EINVAL;
|
|
if (pf_anchor_setup(rule, ruleset, pr->anchor_call))
|
|
error = EINVAL;
|
|
TAILQ_FOREACH(pa, &pf_pabuf, entries)
|
|
if (pf_tbladdr_setup(ruleset, &pa->addr))
|
|
error = EINVAL;
|
|
|
|
if (rule->overload_tblname[0]) {
|
|
if ((rule->overload_tbl = pfr_attach_table(ruleset,
|
|
rule->overload_tblname)) == NULL)
|
|
error = EINVAL;
|
|
else
|
|
rule->overload_tbl->pfrkt_flags |=
|
|
PFR_TFLAG_ACTIVE;
|
|
}
|
|
|
|
pf_mv_pool(&pf_pabuf, &rule->rpool.list);
|
|
if (((((rule->action == PF_NAT) || (rule->action == PF_RDR) ||
|
|
(rule->action == PF_BINAT)) && rule->anchor == NULL) ||
|
|
(rule->rt > PF_FASTROUTE)) &&
|
|
(TAILQ_FIRST(&rule->rpool.list) == NULL))
|
|
error = EINVAL;
|
|
|
|
if (error) {
|
|
pf_rm_rule(NULL, rule);
|
|
break;
|
|
}
|
|
|
|
#ifdef __FreeBSD__
|
|
if (!debug_pfugidhack && (rule->uid.op || rule->gid.op ||
|
|
rule->log & PF_LOG_SOCKET_LOOKUP)) {
|
|
DPFPRINTF(PF_DEBUG_MISC,
|
|
("pf: debug.pfugidhack enabled\n"));
|
|
debug_pfugidhack = 1;
|
|
}
|
|
#endif
|
|
|
|
rule->rpool.cur = TAILQ_FIRST(&rule->rpool.list);
|
|
rule->evaluations = rule->packets[0] = rule->packets[1] =
|
|
rule->bytes[0] = rule->bytes[1] = 0;
|
|
TAILQ_INSERT_TAIL(ruleset->rules[rs_num].inactive.ptr,
|
|
rule, entries);
|
|
ruleset->rules[rs_num].inactive.rcount++;
|
|
break;
|
|
}
|
|
|
|
case DIOCGETRULES: {
|
|
struct pfioc_rule *pr = (struct pfioc_rule *)addr;
|
|
struct pf_ruleset *ruleset;
|
|
struct pf_rule *tail;
|
|
int rs_num;
|
|
|
|
pr->anchor[sizeof(pr->anchor) - 1] = 0;
|
|
ruleset = pf_find_ruleset(pr->anchor);
|
|
if (ruleset == NULL) {
|
|
error = EINVAL;
|
|
break;
|
|
}
|
|
rs_num = pf_get_ruleset_number(pr->rule.action);
|
|
if (rs_num >= PF_RULESET_MAX) {
|
|
error = EINVAL;
|
|
break;
|
|
}
|
|
tail = TAILQ_LAST(ruleset->rules[rs_num].active.ptr,
|
|
pf_rulequeue);
|
|
if (tail)
|
|
pr->nr = tail->nr + 1;
|
|
else
|
|
pr->nr = 0;
|
|
pr->ticket = ruleset->rules[rs_num].active.ticket;
|
|
break;
|
|
}
|
|
|
|
case DIOCGETRULE: {
|
|
struct pfioc_rule *pr = (struct pfioc_rule *)addr;
|
|
struct pf_ruleset *ruleset;
|
|
struct pf_rule *rule;
|
|
int rs_num, i;
|
|
|
|
pr->anchor[sizeof(pr->anchor) - 1] = 0;
|
|
ruleset = pf_find_ruleset(pr->anchor);
|
|
if (ruleset == NULL) {
|
|
error = EINVAL;
|
|
break;
|
|
}
|
|
rs_num = pf_get_ruleset_number(pr->rule.action);
|
|
if (rs_num >= PF_RULESET_MAX) {
|
|
error = EINVAL;
|
|
break;
|
|
}
|
|
if (pr->ticket != ruleset->rules[rs_num].active.ticket) {
|
|
error = EBUSY;
|
|
break;
|
|
}
|
|
rule = TAILQ_FIRST(ruleset->rules[rs_num].active.ptr);
|
|
while ((rule != NULL) && (rule->nr != pr->nr))
|
|
rule = TAILQ_NEXT(rule, entries);
|
|
if (rule == NULL) {
|
|
error = EBUSY;
|
|
break;
|
|
}
|
|
bcopy(rule, &pr->rule, sizeof(struct pf_rule));
|
|
if (pf_anchor_copyout(ruleset, rule, pr)) {
|
|
error = EBUSY;
|
|
break;
|
|
}
|
|
pfi_dynaddr_copyout(&pr->rule.src.addr);
|
|
pfi_dynaddr_copyout(&pr->rule.dst.addr);
|
|
pf_tbladdr_copyout(&pr->rule.src.addr);
|
|
pf_tbladdr_copyout(&pr->rule.dst.addr);
|
|
pf_rtlabel_copyout(&pr->rule.src.addr);
|
|
pf_rtlabel_copyout(&pr->rule.dst.addr);
|
|
for (i = 0; i < PF_SKIP_COUNT; ++i)
|
|
if (rule->skip[i].ptr == NULL)
|
|
pr->rule.skip[i].nr = -1;
|
|
else
|
|
pr->rule.skip[i].nr =
|
|
rule->skip[i].ptr->nr;
|
|
|
|
if (pr->action == PF_GET_CLR_CNTR) {
|
|
rule->evaluations = 0;
|
|
rule->packets[0] = rule->packets[1] = 0;
|
|
rule->bytes[0] = rule->bytes[1] = 0;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case DIOCCHANGERULE: {
|
|
struct pfioc_rule *pcr = (struct pfioc_rule *)addr;
|
|
struct pf_ruleset *ruleset;
|
|
struct pf_rule *oldrule = NULL, *newrule = NULL;
|
|
u_int32_t nr = 0;
|
|
int rs_num;
|
|
|
|
if (!(pcr->action == PF_CHANGE_REMOVE ||
|
|
pcr->action == PF_CHANGE_GET_TICKET) &&
|
|
pcr->pool_ticket != ticket_pabuf) {
|
|
error = EBUSY;
|
|
break;
|
|
}
|
|
|
|
if (pcr->action < PF_CHANGE_ADD_HEAD ||
|
|
pcr->action > PF_CHANGE_GET_TICKET) {
|
|
error = EINVAL;
|
|
break;
|
|
}
|
|
ruleset = pf_find_ruleset(pcr->anchor);
|
|
if (ruleset == NULL) {
|
|
error = EINVAL;
|
|
break;
|
|
}
|
|
rs_num = pf_get_ruleset_number(pcr->rule.action);
|
|
if (rs_num >= PF_RULESET_MAX) {
|
|
error = EINVAL;
|
|
break;
|
|
}
|
|
|
|
if (pcr->action == PF_CHANGE_GET_TICKET) {
|
|
pcr->ticket = ++ruleset->rules[rs_num].active.ticket;
|
|
break;
|
|
} else {
|
|
if (pcr->ticket !=
|
|
ruleset->rules[rs_num].active.ticket) {
|
|
error = EINVAL;
|
|
break;
|
|
}
|
|
if (pcr->rule.return_icmp >> 8 > ICMP_MAXTYPE) {
|
|
error = EINVAL;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (pcr->action != PF_CHANGE_REMOVE) {
|
|
newrule = pool_get(&pf_rule_pl, PR_NOWAIT);
|
|
if (newrule == NULL) {
|
|
error = ENOMEM;
|
|
break;
|
|
}
|
|
bcopy(&pcr->rule, newrule, sizeof(struct pf_rule));
|
|
#ifdef __FreeBSD__
|
|
newrule->cuid = td->td_ucred->cr_ruid;
|
|
newrule->cpid = td->td_proc ? td->td_proc->p_pid : 0;
|
|
#else
|
|
newrule->cuid = p->p_cred->p_ruid;
|
|
newrule->cpid = p->p_pid;
|
|
#endif
|
|
TAILQ_INIT(&newrule->rpool.list);
|
|
/* initialize refcounting */
|
|
newrule->states = 0;
|
|
newrule->entries.tqe_prev = NULL;
|
|
#ifndef INET
|
|
if (newrule->af == AF_INET) {
|
|
pool_put(&pf_rule_pl, newrule);
|
|
error = EAFNOSUPPORT;
|
|
break;
|
|
}
|
|
#endif /* INET */
|
|
#ifndef INET6
|
|
if (newrule->af == AF_INET6) {
|
|
pool_put(&pf_rule_pl, newrule);
|
|
error = EAFNOSUPPORT;
|
|
break;
|
|
}
|
|
#endif /* INET6 */
|
|
if (newrule->ifname[0]) {
|
|
newrule->kif = pfi_kif_get(newrule->ifname);
|
|
if (newrule->kif == NULL) {
|
|
pool_put(&pf_rule_pl, newrule);
|
|
error = EINVAL;
|
|
break;
|
|
}
|
|
pfi_kif_ref(newrule->kif, PFI_KIF_REF_RULE);
|
|
} else
|
|
newrule->kif = NULL;
|
|
|
|
if (newrule->rtableid > 0 &&
|
|
#ifdef __FreeBSD__ /* ROUTING */
|
|
newrule->rtableid > rt_numfibs)
|
|
#else
|
|
!rtable_exists(newrule->rtableid))
|
|
#endif
|
|
error = EBUSY;
|
|
|
|
#ifdef ALTQ
|
|
/* set queue IDs */
|
|
if (newrule->qname[0] != 0) {
|
|
if ((newrule->qid =
|
|
pf_qname2qid(newrule->qname)) == 0)
|
|
error = EBUSY;
|
|
else if (newrule->pqname[0] != 0) {
|
|
if ((newrule->pqid =
|
|
pf_qname2qid(newrule->pqname)) == 0)
|
|
error = EBUSY;
|
|
} else
|
|
newrule->pqid = newrule->qid;
|
|
}
|
|
#endif /* ALTQ */
|
|
if (newrule->tagname[0])
|
|
if ((newrule->tag =
|
|
pf_tagname2tag(newrule->tagname)) == 0)
|
|
error = EBUSY;
|
|
if (newrule->match_tagname[0])
|
|
if ((newrule->match_tag = pf_tagname2tag(
|
|
newrule->match_tagname)) == 0)
|
|
error = EBUSY;
|
|
if (newrule->rt && !newrule->direction)
|
|
error = EINVAL;
|
|
#ifdef __FreeBSD__
|
|
#if NPFLOG > 0
|
|
if (!newrule->log)
|
|
newrule->logif = 0;
|
|
if (newrule->logif >= PFLOGIFS_MAX)
|
|
error = EINVAL;
|
|
#endif
|
|
#endif
|
|
if (pf_rtlabel_add(&newrule->src.addr) ||
|
|
pf_rtlabel_add(&newrule->dst.addr))
|
|
error = EBUSY;
|
|
if (pfi_dynaddr_setup(&newrule->src.addr, newrule->af))
|
|
error = EINVAL;
|
|
if (pfi_dynaddr_setup(&newrule->dst.addr, newrule->af))
|
|
error = EINVAL;
|
|
if (pf_tbladdr_setup(ruleset, &newrule->src.addr))
|
|
error = EINVAL;
|
|
if (pf_tbladdr_setup(ruleset, &newrule->dst.addr))
|
|
error = EINVAL;
|
|
if (pf_anchor_setup(newrule, ruleset, pcr->anchor_call))
|
|
error = EINVAL;
|
|
TAILQ_FOREACH(pa, &pf_pabuf, entries)
|
|
if (pf_tbladdr_setup(ruleset, &pa->addr))
|
|
error = EINVAL;
|
|
|
|
if (newrule->overload_tblname[0]) {
|
|
if ((newrule->overload_tbl = pfr_attach_table(
|
|
ruleset, newrule->overload_tblname)) ==
|
|
NULL)
|
|
error = EINVAL;
|
|
else
|
|
newrule->overload_tbl->pfrkt_flags |=
|
|
PFR_TFLAG_ACTIVE;
|
|
}
|
|
|
|
pf_mv_pool(&pf_pabuf, &newrule->rpool.list);
|
|
if (((((newrule->action == PF_NAT) ||
|
|
(newrule->action == PF_RDR) ||
|
|
(newrule->action == PF_BINAT) ||
|
|
(newrule->rt > PF_FASTROUTE)) &&
|
|
!newrule->anchor)) &&
|
|
(TAILQ_FIRST(&newrule->rpool.list) == NULL))
|
|
error = EINVAL;
|
|
|
|
if (error) {
|
|
pf_rm_rule(NULL, newrule);
|
|
break;
|
|
}
|
|
|
|
#ifdef __FreeBSD__
|
|
if (!debug_pfugidhack && (newrule->uid.op ||
|
|
newrule->gid.op ||
|
|
newrule->log & PF_LOG_SOCKET_LOOKUP)) {
|
|
DPFPRINTF(PF_DEBUG_MISC,
|
|
("pf: debug.pfugidhack enabled\n"));
|
|
debug_pfugidhack = 1;
|
|
}
|
|
#endif
|
|
|
|
newrule->rpool.cur = TAILQ_FIRST(&newrule->rpool.list);
|
|
newrule->evaluations = 0;
|
|
newrule->packets[0] = newrule->packets[1] = 0;
|
|
newrule->bytes[0] = newrule->bytes[1] = 0;
|
|
}
|
|
pf_empty_pool(&pf_pabuf);
|
|
|
|
if (pcr->action == PF_CHANGE_ADD_HEAD)
|
|
oldrule = TAILQ_FIRST(
|
|
ruleset->rules[rs_num].active.ptr);
|
|
else if (pcr->action == PF_CHANGE_ADD_TAIL)
|
|
oldrule = TAILQ_LAST(
|
|
ruleset->rules[rs_num].active.ptr, pf_rulequeue);
|
|
else {
|
|
oldrule = TAILQ_FIRST(
|
|
ruleset->rules[rs_num].active.ptr);
|
|
while ((oldrule != NULL) && (oldrule->nr != pcr->nr))
|
|
oldrule = TAILQ_NEXT(oldrule, entries);
|
|
if (oldrule == NULL) {
|
|
if (newrule != NULL)
|
|
pf_rm_rule(NULL, newrule);
|
|
error = EINVAL;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (pcr->action == PF_CHANGE_REMOVE) {
|
|
pf_rm_rule(ruleset->rules[rs_num].active.ptr, oldrule);
|
|
ruleset->rules[rs_num].active.rcount--;
|
|
} else {
|
|
if (oldrule == NULL)
|
|
TAILQ_INSERT_TAIL(
|
|
ruleset->rules[rs_num].active.ptr,
|
|
newrule, entries);
|
|
else if (pcr->action == PF_CHANGE_ADD_HEAD ||
|
|
pcr->action == PF_CHANGE_ADD_BEFORE)
|
|
TAILQ_INSERT_BEFORE(oldrule, newrule, entries);
|
|
else
|
|
TAILQ_INSERT_AFTER(
|
|
ruleset->rules[rs_num].active.ptr,
|
|
oldrule, newrule, entries);
|
|
ruleset->rules[rs_num].active.rcount++;
|
|
}
|
|
|
|
nr = 0;
|
|
TAILQ_FOREACH(oldrule,
|
|
ruleset->rules[rs_num].active.ptr, entries)
|
|
oldrule->nr = nr++;
|
|
|
|
ruleset->rules[rs_num].active.ticket++;
|
|
|
|
pf_calc_skip_steps(ruleset->rules[rs_num].active.ptr);
|
|
pf_remove_if_empty_ruleset(ruleset);
|
|
|
|
break;
|
|
}
|
|
|
|
case DIOCCLRSTATES: {
|
|
struct pf_state *state, *nexts;
|
|
struct pfioc_state_kill *psk = (struct pfioc_state_kill *)addr;
|
|
int killed = 0;
|
|
|
|
for (state = RB_MIN(pf_state_tree_id, &tree_id); state;
|
|
state = nexts) {
|
|
nexts = RB_NEXT(pf_state_tree_id, &tree_id, state);
|
|
|
|
if (!psk->psk_ifname[0] || !strcmp(psk->psk_ifname,
|
|
state->u.s.kif->pfik_name)) {
|
|
#if NPFSYNC
|
|
/* don't send out individual delete messages */
|
|
state->sync_flags = PFSTATE_NOSYNC;
|
|
#endif
|
|
pf_unlink_state(state);
|
|
killed++;
|
|
}
|
|
}
|
|
psk->psk_af = killed;
|
|
#if NPFSYNC
|
|
pfsync_clear_states(pf_status.hostid, psk->psk_ifname);
|
|
#endif
|
|
break;
|
|
}
|
|
|
|
case DIOCKILLSTATES: {
|
|
struct pf_state *state, *nexts;
|
|
struct pf_state_host *src, *dst;
|
|
struct pfioc_state_kill *psk = (struct pfioc_state_kill *)addr;
|
|
int killed = 0;
|
|
|
|
for (state = RB_MIN(pf_state_tree_id, &tree_id); state;
|
|
state = nexts) {
|
|
nexts = RB_NEXT(pf_state_tree_id, &tree_id, state);
|
|
|
|
if (state->direction == PF_OUT) {
|
|
src = &state->lan;
|
|
dst = &state->ext;
|
|
} else {
|
|
src = &state->ext;
|
|
dst = &state->lan;
|
|
}
|
|
if ((!psk->psk_af || state->af == psk->psk_af)
|
|
&& (!psk->psk_proto || psk->psk_proto ==
|
|
state->proto) &&
|
|
PF_MATCHA(psk->psk_src.neg,
|
|
&psk->psk_src.addr.v.a.addr,
|
|
&psk->psk_src.addr.v.a.mask,
|
|
&src->addr, state->af) &&
|
|
PF_MATCHA(psk->psk_dst.neg,
|
|
&psk->psk_dst.addr.v.a.addr,
|
|
&psk->psk_dst.addr.v.a.mask,
|
|
&dst->addr, state->af) &&
|
|
(psk->psk_src.port_op == 0 ||
|
|
pf_match_port(psk->psk_src.port_op,
|
|
psk->psk_src.port[0], psk->psk_src.port[1],
|
|
src->port)) &&
|
|
(psk->psk_dst.port_op == 0 ||
|
|
pf_match_port(psk->psk_dst.port_op,
|
|
psk->psk_dst.port[0], psk->psk_dst.port[1],
|
|
dst->port)) &&
|
|
(!psk->psk_ifname[0] || !strcmp(psk->psk_ifname,
|
|
state->u.s.kif->pfik_name))) {
|
|
#if NPFSYNC > 0
|
|
/* send immediate delete of state */
|
|
pfsync_delete_state(state);
|
|
state->sync_flags |= PFSTATE_NOSYNC;
|
|
#endif
|
|
pf_unlink_state(state);
|
|
killed++;
|
|
}
|
|
}
|
|
psk->psk_af = killed;
|
|
break;
|
|
}
|
|
|
|
case DIOCADDSTATE: {
|
|
struct pfioc_state *ps = (struct pfioc_state *)addr;
|
|
struct pf_state *state;
|
|
struct pfi_kif *kif;
|
|
|
|
if (ps->state.timeout >= PFTM_MAX &&
|
|
ps->state.timeout != PFTM_UNTIL_PACKET) {
|
|
error = EINVAL;
|
|
break;
|
|
}
|
|
state = pool_get(&pf_state_pl, PR_NOWAIT);
|
|
if (state == NULL) {
|
|
error = ENOMEM;
|
|
break;
|
|
}
|
|
kif = pfi_kif_get(ps->state.u.ifname);
|
|
if (kif == NULL) {
|
|
pool_put(&pf_state_pl, state);
|
|
error = ENOENT;
|
|
break;
|
|
}
|
|
bcopy(&ps->state, state, sizeof(struct pf_state));
|
|
bzero(&state->u, sizeof(state->u));
|
|
state->rule.ptr = &pf_default_rule;
|
|
state->nat_rule.ptr = NULL;
|
|
state->anchor.ptr = NULL;
|
|
state->rt_kif = NULL;
|
|
state->creation = time_second;
|
|
state->pfsync_time = 0;
|
|
state->packets[0] = state->packets[1] = 0;
|
|
state->bytes[0] = state->bytes[1] = 0;
|
|
|
|
if (pf_insert_state(kif, state)) {
|
|
pfi_kif_unref(kif, PFI_KIF_REF_NONE);
|
|
pool_put(&pf_state_pl, state);
|
|
error = ENOMEM;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case DIOCGETSTATE: {
|
|
struct pfioc_state *ps = (struct pfioc_state *)addr;
|
|
struct pf_state *state;
|
|
u_int32_t nr;
|
|
int secs;
|
|
|
|
nr = 0;
|
|
RB_FOREACH(state, pf_state_tree_id, &tree_id) {
|
|
if (nr >= ps->nr)
|
|
break;
|
|
nr++;
|
|
}
|
|
if (state == NULL) {
|
|
error = EBUSY;
|
|
break;
|
|
}
|
|
secs = time_second;
|
|
bcopy(state, &ps->state, sizeof(ps->state));
|
|
strlcpy(ps->state.u.ifname, state->u.s.kif->pfik_name,
|
|
sizeof(ps->state.u.ifname));
|
|
ps->state.rule.nr = state->rule.ptr->nr;
|
|
ps->state.nat_rule.nr = (state->nat_rule.ptr == NULL) ?
|
|
-1 : state->nat_rule.ptr->nr;
|
|
ps->state.anchor.nr = (state->anchor.ptr == NULL) ?
|
|
-1 : state->anchor.ptr->nr;
|
|
ps->state.creation = secs - ps->state.creation;
|
|
ps->state.expire = pf_state_expires(state);
|
|
if (ps->state.expire > secs)
|
|
ps->state.expire -= secs;
|
|
else
|
|
ps->state.expire = 0;
|
|
break;
|
|
}
|
|
|
|
case DIOCGETSTATES: {
|
|
struct pfioc_states *ps = (struct pfioc_states *)addr;
|
|
struct pf_state *state;
|
|
struct pf_state *p, *pstore;
|
|
u_int32_t nr = 0;
|
|
int space = ps->ps_len;
|
|
|
|
if (space == 0) {
|
|
nr = pf_status.states;
|
|
ps->ps_len = sizeof(struct pf_state) * nr;
|
|
break;
|
|
}
|
|
|
|
#ifdef __FreeBSD__
|
|
PF_UNLOCK();
|
|
#endif
|
|
pstore = malloc(sizeof(*pstore), M_TEMP, M_WAITOK);
|
|
#ifdef __FreeBSD__
|
|
PF_LOCK();
|
|
#endif
|
|
|
|
p = ps->ps_states;
|
|
|
|
state = TAILQ_FIRST(&state_list);
|
|
while (state) {
|
|
if (state->timeout != PFTM_UNLINKED) {
|
|
int secs = time_second;
|
|
|
|
if ((nr+1) * sizeof(*p) > (unsigned)ps->ps_len)
|
|
break;
|
|
|
|
bcopy(state, pstore, sizeof(*pstore));
|
|
strlcpy(pstore->u.ifname,
|
|
state->u.s.kif->pfik_name,
|
|
sizeof(pstore->u.ifname));
|
|
pstore->rule.nr = state->rule.ptr->nr;
|
|
pstore->nat_rule.nr = (state->nat_rule.ptr ==
|
|
NULL) ? -1 : state->nat_rule.ptr->nr;
|
|
pstore->anchor.nr = (state->anchor.ptr ==
|
|
NULL) ? -1 : state->anchor.ptr->nr;
|
|
pstore->creation = secs - pstore->creation;
|
|
pstore->expire = pf_state_expires(state);
|
|
if (pstore->expire > secs)
|
|
pstore->expire -= secs;
|
|
else
|
|
pstore->expire = 0;
|
|
#ifdef __FreeBSD__
|
|
PF_COPYOUT(pstore, p, sizeof(*p), error);
|
|
#else
|
|
error = copyout(pstore, p, sizeof(*p));
|
|
#endif
|
|
if (error) {
|
|
free(pstore, M_TEMP);
|
|
goto fail;
|
|
}
|
|
p++;
|
|
nr++;
|
|
}
|
|
state = TAILQ_NEXT(state, u.s.entry_list);
|
|
}
|
|
|
|
ps->ps_len = sizeof(struct pf_state) * nr;
|
|
|
|
free(pstore, M_TEMP);
|
|
break;
|
|
}
|
|
|
|
case DIOCGETSTATUS: {
|
|
struct pf_status *s = (struct pf_status *)addr;
|
|
bcopy(&pf_status, s, sizeof(struct pf_status));
|
|
pfi_fill_oldstatus(s);
|
|
break;
|
|
}
|
|
|
|
case DIOCSETSTATUSIF: {
|
|
struct pfioc_if *pi = (struct pfioc_if *)addr;
|
|
|
|
if (pi->ifname[0] == 0) {
|
|
bzero(pf_status.ifname, IFNAMSIZ);
|
|
break;
|
|
}
|
|
if (ifunit(pi->ifname) == NULL) {
|
|
error = EINVAL;
|
|
break;
|
|
}
|
|
strlcpy(pf_status.ifname, pi->ifname, IFNAMSIZ);
|
|
break;
|
|
}
|
|
|
|
case DIOCCLRSTATUS: {
|
|
bzero(pf_status.counters, sizeof(pf_status.counters));
|
|
bzero(pf_status.fcounters, sizeof(pf_status.fcounters));
|
|
bzero(pf_status.scounters, sizeof(pf_status.scounters));
|
|
pf_status.since = time_second;
|
|
if (*pf_status.ifname)
|
|
pfi_clr_istats(pf_status.ifname);
|
|
break;
|
|
}
|
|
|
|
case DIOCNATLOOK: {
|
|
struct pfioc_natlook *pnl = (struct pfioc_natlook *)addr;
|
|
struct pf_state *state;
|
|
struct pf_state_cmp key;
|
|
int m = 0, direction = pnl->direction;
|
|
|
|
key.af = pnl->af;
|
|
key.proto = pnl->proto;
|
|
|
|
if (!pnl->proto ||
|
|
PF_AZERO(&pnl->saddr, pnl->af) ||
|
|
PF_AZERO(&pnl->daddr, pnl->af) ||
|
|
((pnl->proto == IPPROTO_TCP ||
|
|
pnl->proto == IPPROTO_UDP) &&
|
|
(!pnl->dport || !pnl->sport)))
|
|
error = EINVAL;
|
|
else {
|
|
/*
|
|
* userland gives us source and dest of connection,
|
|
* reverse the lookup so we ask for what happens with
|
|
* the return traffic, enabling us to find it in the
|
|
* state tree.
|
|
*/
|
|
if (direction == PF_IN) {
|
|
PF_ACPY(&key.ext.addr, &pnl->daddr, pnl->af);
|
|
key.ext.port = pnl->dport;
|
|
PF_ACPY(&key.gwy.addr, &pnl->saddr, pnl->af);
|
|
key.gwy.port = pnl->sport;
|
|
state = pf_find_state_all(&key, PF_EXT_GWY, &m);
|
|
} else {
|
|
PF_ACPY(&key.lan.addr, &pnl->daddr, pnl->af);
|
|
key.lan.port = pnl->dport;
|
|
PF_ACPY(&key.ext.addr, &pnl->saddr, pnl->af);
|
|
key.ext.port = pnl->sport;
|
|
state = pf_find_state_all(&key, PF_LAN_EXT, &m);
|
|
}
|
|
if (m > 1)
|
|
error = E2BIG; /* more than one state */
|
|
else if (state != NULL) {
|
|
if (direction == PF_IN) {
|
|
PF_ACPY(&pnl->rsaddr, &state->lan.addr,
|
|
state->af);
|
|
pnl->rsport = state->lan.port;
|
|
PF_ACPY(&pnl->rdaddr, &pnl->daddr,
|
|
pnl->af);
|
|
pnl->rdport = pnl->dport;
|
|
} else {
|
|
PF_ACPY(&pnl->rdaddr, &state->gwy.addr,
|
|
state->af);
|
|
pnl->rdport = state->gwy.port;
|
|
PF_ACPY(&pnl->rsaddr, &pnl->saddr,
|
|
pnl->af);
|
|
pnl->rsport = pnl->sport;
|
|
}
|
|
} else
|
|
error = ENOENT;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case DIOCSETTIMEOUT: {
|
|
struct pfioc_tm *pt = (struct pfioc_tm *)addr;
|
|
int old;
|
|
|
|
if (pt->timeout < 0 || pt->timeout >= PFTM_MAX ||
|
|
pt->seconds < 0) {
|
|
error = EINVAL;
|
|
goto fail;
|
|
}
|
|
old = pf_default_rule.timeout[pt->timeout];
|
|
if (pt->timeout == PFTM_INTERVAL && pt->seconds == 0)
|
|
pt->seconds = 1;
|
|
pf_default_rule.timeout[pt->timeout] = pt->seconds;
|
|
if (pt->timeout == PFTM_INTERVAL && pt->seconds < old)
|
|
wakeup(pf_purge_thread);
|
|
pt->seconds = old;
|
|
break;
|
|
}
|
|
|
|
case DIOCGETTIMEOUT: {
|
|
struct pfioc_tm *pt = (struct pfioc_tm *)addr;
|
|
|
|
if (pt->timeout < 0 || pt->timeout >= PFTM_MAX) {
|
|
error = EINVAL;
|
|
goto fail;
|
|
}
|
|
pt->seconds = pf_default_rule.timeout[pt->timeout];
|
|
break;
|
|
}
|
|
|
|
case DIOCGETLIMIT: {
|
|
struct pfioc_limit *pl = (struct pfioc_limit *)addr;
|
|
|
|
if (pl->index < 0 || pl->index >= PF_LIMIT_MAX) {
|
|
error = EINVAL;
|
|
goto fail;
|
|
}
|
|
pl->limit = pf_pool_limits[pl->index].limit;
|
|
break;
|
|
}
|
|
|
|
case DIOCSETLIMIT: {
|
|
struct pfioc_limit *pl = (struct pfioc_limit *)addr;
|
|
int old_limit;
|
|
|
|
if (pl->index < 0 || pl->index >= PF_LIMIT_MAX ||
|
|
pf_pool_limits[pl->index].pp == NULL) {
|
|
error = EINVAL;
|
|
goto fail;
|
|
}
|
|
#ifdef __FreeBSD__
|
|
uma_zone_set_max(pf_pool_limits[pl->index].pp, pl->limit);
|
|
#else
|
|
if (pool_sethardlimit(pf_pool_limits[pl->index].pp,
|
|
pl->limit, NULL, 0) != 0) {
|
|
error = EBUSY;
|
|
goto fail;
|
|
}
|
|
#endif
|
|
old_limit = pf_pool_limits[pl->index].limit;
|
|
pf_pool_limits[pl->index].limit = pl->limit;
|
|
pl->limit = old_limit;
|
|
break;
|
|
}
|
|
|
|
case DIOCSETDEBUG: {
|
|
u_int32_t *level = (u_int32_t *)addr;
|
|
|
|
pf_status.debug = *level;
|
|
break;
|
|
}
|
|
|
|
case DIOCCLRRULECTRS: {
|
|
/* obsoleted by DIOCGETRULE with action=PF_GET_CLR_CNTR */
|
|
struct pf_ruleset *ruleset = &pf_main_ruleset;
|
|
struct pf_rule *rule;
|
|
|
|
TAILQ_FOREACH(rule,
|
|
ruleset->rules[PF_RULESET_FILTER].active.ptr, entries) {
|
|
rule->evaluations = 0;
|
|
rule->packets[0] = rule->packets[1] = 0;
|
|
rule->bytes[0] = rule->bytes[1] = 0;
|
|
}
|
|
break;
|
|
}
|
|
|
|
#ifdef __FreeBSD__
|
|
case DIOCGIFSPEED: {
|
|
struct pf_ifspeed *psp = (struct pf_ifspeed *)addr;
|
|
struct pf_ifspeed ps;
|
|
struct ifnet *ifp;
|
|
|
|
if (psp->ifname[0] != 0) {
|
|
/* Can we completely trust user-land? */
|
|
strlcpy(ps.ifname, psp->ifname, IFNAMSIZ);
|
|
ifp = ifunit(ps.ifname);
|
|
if (ifp != NULL)
|
|
psp->baudrate = ifp->if_baudrate;
|
|
else
|
|
error = EINVAL;
|
|
} else
|
|
error = EINVAL;
|
|
break;
|
|
}
|
|
#endif /* __FreeBSD__ */
|
|
|
|
#ifdef ALTQ
|
|
case DIOCSTARTALTQ: {
|
|
struct pf_altq *altq;
|
|
|
|
/* enable all altq interfaces on active list */
|
|
TAILQ_FOREACH(altq, pf_altqs_active, entries) {
|
|
#ifdef __FreeBSD__
|
|
if (altq->qname[0] == 0 && (altq->local_flags &
|
|
PFALTQ_FLAG_IF_REMOVED) == 0) {
|
|
#else
|
|
if (altq->qname[0] == 0) {
|
|
#endif
|
|
error = pf_enable_altq(altq);
|
|
if (error != 0)
|
|
break;
|
|
}
|
|
}
|
|
if (error == 0)
|
|
pf_altq_running = 1;
|
|
DPFPRINTF(PF_DEBUG_MISC, ("altq: started\n"));
|
|
break;
|
|
}
|
|
|
|
case DIOCSTOPALTQ: {
|
|
struct pf_altq *altq;
|
|
|
|
/* disable all altq interfaces on active list */
|
|
TAILQ_FOREACH(altq, pf_altqs_active, entries) {
|
|
#ifdef __FreeBSD__
|
|
if (altq->qname[0] == 0 && (altq->local_flags &
|
|
PFALTQ_FLAG_IF_REMOVED) == 0) {
|
|
#else
|
|
if (altq->qname[0] == 0) {
|
|
#endif
|
|
error = pf_disable_altq(altq);
|
|
if (error != 0)
|
|
break;
|
|
}
|
|
}
|
|
if (error == 0)
|
|
pf_altq_running = 0;
|
|
DPFPRINTF(PF_DEBUG_MISC, ("altq: stopped\n"));
|
|
break;
|
|
}
|
|
|
|
case DIOCADDALTQ: {
|
|
struct pfioc_altq *pa = (struct pfioc_altq *)addr;
|
|
struct pf_altq *altq, *a;
|
|
|
|
if (pa->ticket != ticket_altqs_inactive) {
|
|
error = EBUSY;
|
|
break;
|
|
}
|
|
altq = pool_get(&pf_altq_pl, PR_NOWAIT);
|
|
if (altq == NULL) {
|
|
error = ENOMEM;
|
|
break;
|
|
}
|
|
bcopy(&pa->altq, altq, sizeof(struct pf_altq));
|
|
#ifdef __FreeBSD__
|
|
altq->local_flags = 0;
|
|
#endif
|
|
|
|
/*
|
|
* if this is for a queue, find the discipline and
|
|
* copy the necessary fields
|
|
*/
|
|
if (altq->qname[0] != 0) {
|
|
if ((altq->qid = pf_qname2qid(altq->qname)) == 0) {
|
|
error = EBUSY;
|
|
pool_put(&pf_altq_pl, altq);
|
|
break;
|
|
}
|
|
altq->altq_disc = NULL;
|
|
TAILQ_FOREACH(a, pf_altqs_inactive, entries) {
|
|
if (strncmp(a->ifname, altq->ifname,
|
|
IFNAMSIZ) == 0 && a->qname[0] == 0) {
|
|
altq->altq_disc = a->altq_disc;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef __FreeBSD__
|
|
struct ifnet *ifp;
|
|
|
|
if ((ifp = ifunit(altq->ifname)) == NULL) {
|
|
altq->local_flags |= PFALTQ_FLAG_IF_REMOVED;
|
|
} else {
|
|
PF_UNLOCK();
|
|
#endif
|
|
error = altq_add(altq);
|
|
#ifdef __FreeBSD__
|
|
PF_LOCK();
|
|
}
|
|
#endif
|
|
if (error) {
|
|
pool_put(&pf_altq_pl, altq);
|
|
break;
|
|
}
|
|
|
|
TAILQ_INSERT_TAIL(pf_altqs_inactive, altq, entries);
|
|
bcopy(altq, &pa->altq, sizeof(struct pf_altq));
|
|
break;
|
|
}
|
|
|
|
case DIOCGETALTQS: {
|
|
struct pfioc_altq *pa = (struct pfioc_altq *)addr;
|
|
struct pf_altq *altq;
|
|
|
|
pa->nr = 0;
|
|
TAILQ_FOREACH(altq, pf_altqs_active, entries)
|
|
pa->nr++;
|
|
pa->ticket = ticket_altqs_active;
|
|
break;
|
|
}
|
|
|
|
case DIOCGETALTQ: {
|
|
struct pfioc_altq *pa = (struct pfioc_altq *)addr;
|
|
struct pf_altq *altq;
|
|
u_int32_t nr;
|
|
|
|
if (pa->ticket != ticket_altqs_active) {
|
|
error = EBUSY;
|
|
break;
|
|
}
|
|
nr = 0;
|
|
altq = TAILQ_FIRST(pf_altqs_active);
|
|
while ((altq != NULL) && (nr < pa->nr)) {
|
|
altq = TAILQ_NEXT(altq, entries);
|
|
nr++;
|
|
}
|
|
if (altq == NULL) {
|
|
error = EBUSY;
|
|
break;
|
|
}
|
|
bcopy(altq, &pa->altq, sizeof(struct pf_altq));
|
|
break;
|
|
}
|
|
|
|
case DIOCCHANGEALTQ:
|
|
/* CHANGEALTQ not supported yet! */
|
|
error = ENODEV;
|
|
break;
|
|
|
|
case DIOCGETQSTATS: {
|
|
struct pfioc_qstats *pq = (struct pfioc_qstats *)addr;
|
|
struct pf_altq *altq;
|
|
u_int32_t nr;
|
|
int nbytes;
|
|
|
|
if (pq->ticket != ticket_altqs_active) {
|
|
error = EBUSY;
|
|
break;
|
|
}
|
|
nbytes = pq->nbytes;
|
|
nr = 0;
|
|
altq = TAILQ_FIRST(pf_altqs_active);
|
|
while ((altq != NULL) && (nr < pq->nr)) {
|
|
altq = TAILQ_NEXT(altq, entries);
|
|
nr++;
|
|
}
|
|
if (altq == NULL) {
|
|
error = EBUSY;
|
|
break;
|
|
}
|
|
#ifdef __FreeBSD__
|
|
if ((altq->local_flags & PFALTQ_FLAG_IF_REMOVED) != 0) {
|
|
error = ENXIO;
|
|
break;
|
|
}
|
|
PF_UNLOCK();
|
|
#endif
|
|
error = altq_getqstats(altq, pq->buf, &nbytes);
|
|
#ifdef __FreeBSD__
|
|
PF_LOCK();
|
|
#endif
|
|
if (error == 0) {
|
|
pq->scheduler = altq->scheduler;
|
|
pq->nbytes = nbytes;
|
|
}
|
|
break;
|
|
}
|
|
#endif /* ALTQ */
|
|
|
|
case DIOCBEGINADDRS: {
|
|
struct pfioc_pooladdr *pp = (struct pfioc_pooladdr *)addr;
|
|
|
|
pf_empty_pool(&pf_pabuf);
|
|
pp->ticket = ++ticket_pabuf;
|
|
break;
|
|
}
|
|
|
|
case DIOCADDADDR: {
|
|
struct pfioc_pooladdr *pp = (struct pfioc_pooladdr *)addr;
|
|
|
|
if (pp->ticket != ticket_pabuf) {
|
|
error = EBUSY;
|
|
break;
|
|
}
|
|
#ifndef INET
|
|
if (pp->af == AF_INET) {
|
|
error = EAFNOSUPPORT;
|
|
break;
|
|
}
|
|
#endif /* INET */
|
|
#ifndef INET6
|
|
if (pp->af == AF_INET6) {
|
|
error = EAFNOSUPPORT;
|
|
break;
|
|
}
|
|
#endif /* INET6 */
|
|
if (pp->addr.addr.type != PF_ADDR_ADDRMASK &&
|
|
pp->addr.addr.type != PF_ADDR_DYNIFTL &&
|
|
pp->addr.addr.type != PF_ADDR_TABLE) {
|
|
error = EINVAL;
|
|
break;
|
|
}
|
|
pa = pool_get(&pf_pooladdr_pl, PR_NOWAIT);
|
|
if (pa == NULL) {
|
|
error = ENOMEM;
|
|
break;
|
|
}
|
|
bcopy(&pp->addr, pa, sizeof(struct pf_pooladdr));
|
|
if (pa->ifname[0]) {
|
|
pa->kif = pfi_kif_get(pa->ifname);
|
|
if (pa->kif == NULL) {
|
|
pool_put(&pf_pooladdr_pl, pa);
|
|
error = EINVAL;
|
|
break;
|
|
}
|
|
pfi_kif_ref(pa->kif, PFI_KIF_REF_RULE);
|
|
}
|
|
if (pfi_dynaddr_setup(&pa->addr, pp->af)) {
|
|
pfi_dynaddr_remove(&pa->addr);
|
|
pfi_kif_unref(pa->kif, PFI_KIF_REF_RULE);
|
|
pool_put(&pf_pooladdr_pl, pa);
|
|
error = EINVAL;
|
|
break;
|
|
}
|
|
TAILQ_INSERT_TAIL(&pf_pabuf, pa, entries);
|
|
break;
|
|
}
|
|
|
|
case DIOCGETADDRS: {
|
|
struct pfioc_pooladdr *pp = (struct pfioc_pooladdr *)addr;
|
|
|
|
pp->nr = 0;
|
|
pool = pf_get_pool(pp->anchor, pp->ticket, pp->r_action,
|
|
pp->r_num, 0, 1, 0);
|
|
if (pool == NULL) {
|
|
error = EBUSY;
|
|
break;
|
|
}
|
|
TAILQ_FOREACH(pa, &pool->list, entries)
|
|
pp->nr++;
|
|
break;
|
|
}
|
|
|
|
case DIOCGETADDR: {
|
|
struct pfioc_pooladdr *pp = (struct pfioc_pooladdr *)addr;
|
|
u_int32_t nr = 0;
|
|
|
|
pool = pf_get_pool(pp->anchor, pp->ticket, pp->r_action,
|
|
pp->r_num, 0, 1, 1);
|
|
if (pool == NULL) {
|
|
error = EBUSY;
|
|
break;
|
|
}
|
|
pa = TAILQ_FIRST(&pool->list);
|
|
while ((pa != NULL) && (nr < pp->nr)) {
|
|
pa = TAILQ_NEXT(pa, entries);
|
|
nr++;
|
|
}
|
|
if (pa == NULL) {
|
|
error = EBUSY;
|
|
break;
|
|
}
|
|
bcopy(pa, &pp->addr, sizeof(struct pf_pooladdr));
|
|
pfi_dynaddr_copyout(&pp->addr.addr);
|
|
pf_tbladdr_copyout(&pp->addr.addr);
|
|
pf_rtlabel_copyout(&pp->addr.addr);
|
|
break;
|
|
}
|
|
|
|
case DIOCCHANGEADDR: {
|
|
struct pfioc_pooladdr *pca = (struct pfioc_pooladdr *)addr;
|
|
struct pf_pooladdr *oldpa = NULL, *newpa = NULL;
|
|
struct pf_ruleset *ruleset;
|
|
|
|
if (pca->action < PF_CHANGE_ADD_HEAD ||
|
|
pca->action > PF_CHANGE_REMOVE) {
|
|
error = EINVAL;
|
|
break;
|
|
}
|
|
if (pca->addr.addr.type != PF_ADDR_ADDRMASK &&
|
|
pca->addr.addr.type != PF_ADDR_DYNIFTL &&
|
|
pca->addr.addr.type != PF_ADDR_TABLE) {
|
|
error = EINVAL;
|
|
break;
|
|
}
|
|
|
|
ruleset = pf_find_ruleset(pca->anchor);
|
|
if (ruleset == NULL) {
|
|
error = EBUSY;
|
|
break;
|
|
}
|
|
pool = pf_get_pool(pca->anchor, pca->ticket, pca->r_action,
|
|
pca->r_num, pca->r_last, 1, 1);
|
|
if (pool == NULL) {
|
|
error = EBUSY;
|
|
break;
|
|
}
|
|
if (pca->action != PF_CHANGE_REMOVE) {
|
|
newpa = pool_get(&pf_pooladdr_pl, PR_NOWAIT);
|
|
if (newpa == NULL) {
|
|
error = ENOMEM;
|
|
break;
|
|
}
|
|
bcopy(&pca->addr, newpa, sizeof(struct pf_pooladdr));
|
|
#ifndef INET
|
|
if (pca->af == AF_INET) {
|
|
pool_put(&pf_pooladdr_pl, newpa);
|
|
error = EAFNOSUPPORT;
|
|
break;
|
|
}
|
|
#endif /* INET */
|
|
#ifndef INET6
|
|
if (pca->af == AF_INET6) {
|
|
pool_put(&pf_pooladdr_pl, newpa);
|
|
error = EAFNOSUPPORT;
|
|
break;
|
|
}
|
|
#endif /* INET6 */
|
|
if (newpa->ifname[0]) {
|
|
newpa->kif = pfi_kif_get(newpa->ifname);
|
|
if (newpa->kif == NULL) {
|
|
pool_put(&pf_pooladdr_pl, newpa);
|
|
error = EINVAL;
|
|
break;
|
|
}
|
|
pfi_kif_ref(newpa->kif, PFI_KIF_REF_RULE);
|
|
} else
|
|
newpa->kif = NULL;
|
|
if (pfi_dynaddr_setup(&newpa->addr, pca->af) ||
|
|
pf_tbladdr_setup(ruleset, &newpa->addr)) {
|
|
pfi_dynaddr_remove(&newpa->addr);
|
|
pfi_kif_unref(newpa->kif, PFI_KIF_REF_RULE);
|
|
pool_put(&pf_pooladdr_pl, newpa);
|
|
error = EINVAL;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (pca->action == PF_CHANGE_ADD_HEAD)
|
|
oldpa = TAILQ_FIRST(&pool->list);
|
|
else if (pca->action == PF_CHANGE_ADD_TAIL)
|
|
oldpa = TAILQ_LAST(&pool->list, pf_palist);
|
|
else {
|
|
int i = 0;
|
|
|
|
oldpa = TAILQ_FIRST(&pool->list);
|
|
while ((oldpa != NULL) && (i < pca->nr)) {
|
|
oldpa = TAILQ_NEXT(oldpa, entries);
|
|
i++;
|
|
}
|
|
if (oldpa == NULL) {
|
|
error = EINVAL;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (pca->action == PF_CHANGE_REMOVE) {
|
|
TAILQ_REMOVE(&pool->list, oldpa, entries);
|
|
pfi_dynaddr_remove(&oldpa->addr);
|
|
pf_tbladdr_remove(&oldpa->addr);
|
|
pfi_kif_unref(oldpa->kif, PFI_KIF_REF_RULE);
|
|
pool_put(&pf_pooladdr_pl, oldpa);
|
|
} else {
|
|
if (oldpa == NULL)
|
|
TAILQ_INSERT_TAIL(&pool->list, newpa, entries);
|
|
else if (pca->action == PF_CHANGE_ADD_HEAD ||
|
|
pca->action == PF_CHANGE_ADD_BEFORE)
|
|
TAILQ_INSERT_BEFORE(oldpa, newpa, entries);
|
|
else
|
|
TAILQ_INSERT_AFTER(&pool->list, oldpa,
|
|
newpa, entries);
|
|
}
|
|
|
|
pool->cur = TAILQ_FIRST(&pool->list);
|
|
PF_ACPY(&pool->counter, &pool->cur->addr.v.a.addr,
|
|
pca->af);
|
|
break;
|
|
}
|
|
|
|
case DIOCGETRULESETS: {
|
|
struct pfioc_ruleset *pr = (struct pfioc_ruleset *)addr;
|
|
struct pf_ruleset *ruleset;
|
|
struct pf_anchor *anchor;
|
|
|
|
pr->path[sizeof(pr->path) - 1] = 0;
|
|
if ((ruleset = pf_find_ruleset(pr->path)) == NULL) {
|
|
error = EINVAL;
|
|
break;
|
|
}
|
|
pr->nr = 0;
|
|
if (ruleset->anchor == NULL) {
|
|
/* XXX kludge for pf_main_ruleset */
|
|
RB_FOREACH(anchor, pf_anchor_global, &pf_anchors)
|
|
if (anchor->parent == NULL)
|
|
pr->nr++;
|
|
} else {
|
|
RB_FOREACH(anchor, pf_anchor_node,
|
|
&ruleset->anchor->children)
|
|
pr->nr++;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case DIOCGETRULESET: {
|
|
struct pfioc_ruleset *pr = (struct pfioc_ruleset *)addr;
|
|
struct pf_ruleset *ruleset;
|
|
struct pf_anchor *anchor;
|
|
u_int32_t nr = 0;
|
|
|
|
pr->path[sizeof(pr->path) - 1] = 0;
|
|
if ((ruleset = pf_find_ruleset(pr->path)) == NULL) {
|
|
error = EINVAL;
|
|
break;
|
|
}
|
|
pr->name[0] = 0;
|
|
if (ruleset->anchor == NULL) {
|
|
/* XXX kludge for pf_main_ruleset */
|
|
RB_FOREACH(anchor, pf_anchor_global, &pf_anchors)
|
|
if (anchor->parent == NULL && nr++ == pr->nr) {
|
|
strlcpy(pr->name, anchor->name,
|
|
sizeof(pr->name));
|
|
break;
|
|
}
|
|
} else {
|
|
RB_FOREACH(anchor, pf_anchor_node,
|
|
&ruleset->anchor->children)
|
|
if (nr++ == pr->nr) {
|
|
strlcpy(pr->name, anchor->name,
|
|
sizeof(pr->name));
|
|
break;
|
|
}
|
|
}
|
|
if (!pr->name[0])
|
|
error = EBUSY;
|
|
break;
|
|
}
|
|
|
|
case DIOCRCLRTABLES: {
|
|
struct pfioc_table *io = (struct pfioc_table *)addr;
|
|
|
|
if (io->pfrio_esize != 0) {
|
|
error = ENODEV;
|
|
break;
|
|
}
|
|
error = pfr_clr_tables(&io->pfrio_table, &io->pfrio_ndel,
|
|
io->pfrio_flags | PFR_FLAG_USERIOCTL);
|
|
break;
|
|
}
|
|
|
|
case DIOCRADDTABLES: {
|
|
struct pfioc_table *io = (struct pfioc_table *)addr;
|
|
|
|
if (io->pfrio_esize != sizeof(struct pfr_table)) {
|
|
error = ENODEV;
|
|
break;
|
|
}
|
|
error = pfr_add_tables(io->pfrio_buffer, io->pfrio_size,
|
|
&io->pfrio_nadd, io->pfrio_flags | PFR_FLAG_USERIOCTL);
|
|
break;
|
|
}
|
|
|
|
case DIOCRDELTABLES: {
|
|
struct pfioc_table *io = (struct pfioc_table *)addr;
|
|
|
|
if (io->pfrio_esize != sizeof(struct pfr_table)) {
|
|
error = ENODEV;
|
|
break;
|
|
}
|
|
error = pfr_del_tables(io->pfrio_buffer, io->pfrio_size,
|
|
&io->pfrio_ndel, io->pfrio_flags | PFR_FLAG_USERIOCTL);
|
|
break;
|
|
}
|
|
|
|
case DIOCRGETTABLES: {
|
|
struct pfioc_table *io = (struct pfioc_table *)addr;
|
|
|
|
if (io->pfrio_esize != sizeof(struct pfr_table)) {
|
|
error = ENODEV;
|
|
break;
|
|
}
|
|
error = pfr_get_tables(&io->pfrio_table, io->pfrio_buffer,
|
|
&io->pfrio_size, io->pfrio_flags | PFR_FLAG_USERIOCTL);
|
|
break;
|
|
}
|
|
|
|
case DIOCRGETTSTATS: {
|
|
struct pfioc_table *io = (struct pfioc_table *)addr;
|
|
|
|
if (io->pfrio_esize != sizeof(struct pfr_tstats)) {
|
|
error = ENODEV;
|
|
break;
|
|
}
|
|
error = pfr_get_tstats(&io->pfrio_table, io->pfrio_buffer,
|
|
&io->pfrio_size, io->pfrio_flags | PFR_FLAG_USERIOCTL);
|
|
break;
|
|
}
|
|
|
|
case DIOCRCLRTSTATS: {
|
|
struct pfioc_table *io = (struct pfioc_table *)addr;
|
|
|
|
if (io->pfrio_esize != sizeof(struct pfr_table)) {
|
|
error = ENODEV;
|
|
break;
|
|
}
|
|
error = pfr_clr_tstats(io->pfrio_buffer, io->pfrio_size,
|
|
&io->pfrio_nzero, io->pfrio_flags | PFR_FLAG_USERIOCTL);
|
|
break;
|
|
}
|
|
|
|
case DIOCRSETTFLAGS: {
|
|
struct pfioc_table *io = (struct pfioc_table *)addr;
|
|
|
|
if (io->pfrio_esize != sizeof(struct pfr_table)) {
|
|
error = ENODEV;
|
|
break;
|
|
}
|
|
error = pfr_set_tflags(io->pfrio_buffer, io->pfrio_size,
|
|
io->pfrio_setflag, io->pfrio_clrflag, &io->pfrio_nchange,
|
|
&io->pfrio_ndel, io->pfrio_flags | PFR_FLAG_USERIOCTL);
|
|
break;
|
|
}
|
|
|
|
case DIOCRCLRADDRS: {
|
|
struct pfioc_table *io = (struct pfioc_table *)addr;
|
|
|
|
if (io->pfrio_esize != 0) {
|
|
error = ENODEV;
|
|
break;
|
|
}
|
|
error = pfr_clr_addrs(&io->pfrio_table, &io->pfrio_ndel,
|
|
io->pfrio_flags | PFR_FLAG_USERIOCTL);
|
|
break;
|
|
}
|
|
|
|
case DIOCRADDADDRS: {
|
|
struct pfioc_table *io = (struct pfioc_table *)addr;
|
|
|
|
if (io->pfrio_esize != sizeof(struct pfr_addr)) {
|
|
error = ENODEV;
|
|
break;
|
|
}
|
|
error = pfr_add_addrs(&io->pfrio_table, io->pfrio_buffer,
|
|
io->pfrio_size, &io->pfrio_nadd, io->pfrio_flags |
|
|
PFR_FLAG_USERIOCTL);
|
|
break;
|
|
}
|
|
|
|
case DIOCRDELADDRS: {
|
|
struct pfioc_table *io = (struct pfioc_table *)addr;
|
|
|
|
if (io->pfrio_esize != sizeof(struct pfr_addr)) {
|
|
error = ENODEV;
|
|
break;
|
|
}
|
|
error = pfr_del_addrs(&io->pfrio_table, io->pfrio_buffer,
|
|
io->pfrio_size, &io->pfrio_ndel, io->pfrio_flags |
|
|
PFR_FLAG_USERIOCTL);
|
|
break;
|
|
}
|
|
|
|
case DIOCRSETADDRS: {
|
|
struct pfioc_table *io = (struct pfioc_table *)addr;
|
|
|
|
if (io->pfrio_esize != sizeof(struct pfr_addr)) {
|
|
error = ENODEV;
|
|
break;
|
|
}
|
|
error = pfr_set_addrs(&io->pfrio_table, io->pfrio_buffer,
|
|
io->pfrio_size, &io->pfrio_size2, &io->pfrio_nadd,
|
|
&io->pfrio_ndel, &io->pfrio_nchange, io->pfrio_flags |
|
|
PFR_FLAG_USERIOCTL, 0);
|
|
break;
|
|
}
|
|
|
|
case DIOCRGETADDRS: {
|
|
struct pfioc_table *io = (struct pfioc_table *)addr;
|
|
|
|
if (io->pfrio_esize != sizeof(struct pfr_addr)) {
|
|
error = ENODEV;
|
|
break;
|
|
}
|
|
error = pfr_get_addrs(&io->pfrio_table, io->pfrio_buffer,
|
|
&io->pfrio_size, io->pfrio_flags | PFR_FLAG_USERIOCTL);
|
|
break;
|
|
}
|
|
|
|
case DIOCRGETASTATS: {
|
|
struct pfioc_table *io = (struct pfioc_table *)addr;
|
|
|
|
if (io->pfrio_esize != sizeof(struct pfr_astats)) {
|
|
error = ENODEV;
|
|
break;
|
|
}
|
|
error = pfr_get_astats(&io->pfrio_table, io->pfrio_buffer,
|
|
&io->pfrio_size, io->pfrio_flags | PFR_FLAG_USERIOCTL);
|
|
break;
|
|
}
|
|
|
|
case DIOCRCLRASTATS: {
|
|
struct pfioc_table *io = (struct pfioc_table *)addr;
|
|
|
|
if (io->pfrio_esize != sizeof(struct pfr_addr)) {
|
|
error = ENODEV;
|
|
break;
|
|
}
|
|
error = pfr_clr_astats(&io->pfrio_table, io->pfrio_buffer,
|
|
io->pfrio_size, &io->pfrio_nzero, io->pfrio_flags |
|
|
PFR_FLAG_USERIOCTL);
|
|
break;
|
|
}
|
|
|
|
case DIOCRTSTADDRS: {
|
|
struct pfioc_table *io = (struct pfioc_table *)addr;
|
|
|
|
if (io->pfrio_esize != sizeof(struct pfr_addr)) {
|
|
error = ENODEV;
|
|
break;
|
|
}
|
|
error = pfr_tst_addrs(&io->pfrio_table, io->pfrio_buffer,
|
|
io->pfrio_size, &io->pfrio_nmatch, io->pfrio_flags |
|
|
PFR_FLAG_USERIOCTL);
|
|
break;
|
|
}
|
|
|
|
case DIOCRINADEFINE: {
|
|
struct pfioc_table *io = (struct pfioc_table *)addr;
|
|
|
|
if (io->pfrio_esize != sizeof(struct pfr_addr)) {
|
|
error = ENODEV;
|
|
break;
|
|
}
|
|
error = pfr_ina_define(&io->pfrio_table, io->pfrio_buffer,
|
|
io->pfrio_size, &io->pfrio_nadd, &io->pfrio_naddr,
|
|
io->pfrio_ticket, io->pfrio_flags | PFR_FLAG_USERIOCTL);
|
|
break;
|
|
}
|
|
|
|
case DIOCOSFPADD: {
|
|
struct pf_osfp_ioctl *io = (struct pf_osfp_ioctl *)addr;
|
|
error = pf_osfp_add(io);
|
|
break;
|
|
}
|
|
|
|
case DIOCOSFPGET: {
|
|
struct pf_osfp_ioctl *io = (struct pf_osfp_ioctl *)addr;
|
|
error = pf_osfp_get(io);
|
|
break;
|
|
}
|
|
|
|
case DIOCXBEGIN: {
|
|
struct pfioc_trans *io = (struct pfioc_trans *)addr;
|
|
struct pfioc_trans_e *ioe;
|
|
struct pfr_table *table;
|
|
int i;
|
|
|
|
if (io->esize != sizeof(*ioe)) {
|
|
error = ENODEV;
|
|
goto fail;
|
|
}
|
|
#ifdef __FreeBSD__
|
|
PF_UNLOCK();
|
|
#endif
|
|
ioe = (struct pfioc_trans_e *)malloc(sizeof(*ioe),
|
|
M_TEMP, M_WAITOK);
|
|
table = (struct pfr_table *)malloc(sizeof(*table),
|
|
M_TEMP, M_WAITOK);
|
|
#ifdef __FreeBSD__
|
|
PF_LOCK();
|
|
#endif
|
|
for (i = 0; i < io->size; i++) {
|
|
#ifdef __FreeBSD__
|
|
PF_COPYIN(io->array+i, ioe, sizeof(*ioe), error);
|
|
if (error) {
|
|
#else
|
|
if (copyin(io->array+i, ioe, sizeof(*ioe))) {
|
|
#endif
|
|
free(table, M_TEMP);
|
|
free(ioe, M_TEMP);
|
|
error = EFAULT;
|
|
goto fail;
|
|
}
|
|
switch (ioe->rs_num) {
|
|
#ifdef ALTQ
|
|
case PF_RULESET_ALTQ:
|
|
if (ioe->anchor[0]) {
|
|
free(table, M_TEMP);
|
|
free(ioe, M_TEMP);
|
|
error = EINVAL;
|
|
goto fail;
|
|
}
|
|
if ((error = pf_begin_altq(&ioe->ticket))) {
|
|
free(table, M_TEMP);
|
|
free(ioe, M_TEMP);
|
|
goto fail;
|
|
}
|
|
break;
|
|
#endif /* ALTQ */
|
|
case PF_RULESET_TABLE:
|
|
bzero(table, sizeof(*table));
|
|
strlcpy(table->pfrt_anchor, ioe->anchor,
|
|
sizeof(table->pfrt_anchor));
|
|
if ((error = pfr_ina_begin(table,
|
|
&ioe->ticket, NULL, 0))) {
|
|
free(table, M_TEMP);
|
|
free(ioe, M_TEMP);
|
|
goto fail;
|
|
}
|
|
break;
|
|
default:
|
|
if ((error = pf_begin_rules(&ioe->ticket,
|
|
ioe->rs_num, ioe->anchor))) {
|
|
free(table, M_TEMP);
|
|
free(ioe, M_TEMP);
|
|
goto fail;
|
|
}
|
|
break;
|
|
}
|
|
#ifdef __FreeBSD__
|
|
PF_COPYOUT(ioe, io->array+i, sizeof(io->array[i]),
|
|
error);
|
|
if (error) {
|
|
#else
|
|
if (copyout(ioe, io->array+i, sizeof(io->array[i]))) {
|
|
#endif
|
|
free(table, M_TEMP);
|
|
free(ioe, M_TEMP);
|
|
error = EFAULT;
|
|
goto fail;
|
|
}
|
|
}
|
|
free(table, M_TEMP);
|
|
free(ioe, M_TEMP);
|
|
break;
|
|
}
|
|
|
|
case DIOCXROLLBACK: {
|
|
struct pfioc_trans *io = (struct pfioc_trans *)addr;
|
|
struct pfioc_trans_e *ioe;
|
|
struct pfr_table *table;
|
|
int i;
|
|
|
|
if (io->esize != sizeof(*ioe)) {
|
|
error = ENODEV;
|
|
goto fail;
|
|
}
|
|
#ifdef __FreeBSD__
|
|
PF_UNLOCK();
|
|
#endif
|
|
ioe = (struct pfioc_trans_e *)malloc(sizeof(*ioe),
|
|
M_TEMP, M_WAITOK);
|
|
table = (struct pfr_table *)malloc(sizeof(*table),
|
|
M_TEMP, M_WAITOK);
|
|
#ifdef __FreeBSD__
|
|
PF_LOCK();
|
|
#endif
|
|
for (i = 0; i < io->size; i++) {
|
|
#ifdef __FreeBSD__
|
|
PF_COPYIN(io->array+i, ioe, sizeof(*ioe), error);
|
|
if (error) {
|
|
#else
|
|
if (copyin(io->array+i, ioe, sizeof(*ioe))) {
|
|
#endif
|
|
free(table, M_TEMP);
|
|
free(ioe, M_TEMP);
|
|
error = EFAULT;
|
|
goto fail;
|
|
}
|
|
switch (ioe->rs_num) {
|
|
#ifdef ALTQ
|
|
case PF_RULESET_ALTQ:
|
|
if (ioe->anchor[0]) {
|
|
free(table, M_TEMP);
|
|
free(ioe, M_TEMP);
|
|
error = EINVAL;
|
|
goto fail;
|
|
}
|
|
if ((error = pf_rollback_altq(ioe->ticket))) {
|
|
free(table, M_TEMP);
|
|
free(ioe, M_TEMP);
|
|
goto fail; /* really bad */
|
|
}
|
|
break;
|
|
#endif /* ALTQ */
|
|
case PF_RULESET_TABLE:
|
|
bzero(table, sizeof(*table));
|
|
strlcpy(table->pfrt_anchor, ioe->anchor,
|
|
sizeof(table->pfrt_anchor));
|
|
if ((error = pfr_ina_rollback(table,
|
|
ioe->ticket, NULL, 0))) {
|
|
free(table, M_TEMP);
|
|
free(ioe, M_TEMP);
|
|
goto fail; /* really bad */
|
|
}
|
|
break;
|
|
default:
|
|
if ((error = pf_rollback_rules(ioe->ticket,
|
|
ioe->rs_num, ioe->anchor))) {
|
|
free(table, M_TEMP);
|
|
free(ioe, M_TEMP);
|
|
goto fail; /* really bad */
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
free(table, M_TEMP);
|
|
free(ioe, M_TEMP);
|
|
break;
|
|
}
|
|
|
|
case DIOCXCOMMIT: {
|
|
struct pfioc_trans *io = (struct pfioc_trans *)addr;
|
|
struct pfioc_trans_e *ioe;
|
|
struct pfr_table *table;
|
|
struct pf_ruleset *rs;
|
|
int i;
|
|
|
|
if (io->esize != sizeof(*ioe)) {
|
|
error = ENODEV;
|
|
goto fail;
|
|
}
|
|
#ifdef __FreeBSD__
|
|
PF_UNLOCK();
|
|
#endif
|
|
ioe = (struct pfioc_trans_e *)malloc(sizeof(*ioe),
|
|
M_TEMP, M_WAITOK);
|
|
table = (struct pfr_table *)malloc(sizeof(*table),
|
|
M_TEMP, M_WAITOK);
|
|
#ifdef __FreeBSD__
|
|
PF_LOCK();
|
|
#endif
|
|
/* first makes sure everything will succeed */
|
|
for (i = 0; i < io->size; i++) {
|
|
#ifdef __FreeBSD__
|
|
PF_COPYIN(io->array+i, ioe, sizeof(*ioe), error);
|
|
if (error) {
|
|
#else
|
|
if (copyin(io->array+i, ioe, sizeof(*ioe))) {
|
|
#endif
|
|
free(table, M_TEMP);
|
|
free(ioe, M_TEMP);
|
|
error = EFAULT;
|
|
goto fail;
|
|
}
|
|
switch (ioe->rs_num) {
|
|
#ifdef ALTQ
|
|
case PF_RULESET_ALTQ:
|
|
if (ioe->anchor[0]) {
|
|
free(table, M_TEMP);
|
|
free(ioe, M_TEMP);
|
|
error = EINVAL;
|
|
goto fail;
|
|
}
|
|
if (!altqs_inactive_open || ioe->ticket !=
|
|
ticket_altqs_inactive) {
|
|
free(table, M_TEMP);
|
|
free(ioe, M_TEMP);
|
|
error = EBUSY;
|
|
goto fail;
|
|
}
|
|
break;
|
|
#endif /* ALTQ */
|
|
case PF_RULESET_TABLE:
|
|
rs = pf_find_ruleset(ioe->anchor);
|
|
if (rs == NULL || !rs->topen || ioe->ticket !=
|
|
rs->tticket) {
|
|
free(table, M_TEMP);
|
|
free(ioe, M_TEMP);
|
|
error = EBUSY;
|
|
goto fail;
|
|
}
|
|
break;
|
|
default:
|
|
if (ioe->rs_num < 0 || ioe->rs_num >=
|
|
PF_RULESET_MAX) {
|
|
free(table, M_TEMP);
|
|
free(ioe, M_TEMP);
|
|
error = EINVAL;
|
|
goto fail;
|
|
}
|
|
rs = pf_find_ruleset(ioe->anchor);
|
|
if (rs == NULL ||
|
|
!rs->rules[ioe->rs_num].inactive.open ||
|
|
rs->rules[ioe->rs_num].inactive.ticket !=
|
|
ioe->ticket) {
|
|
free(table, M_TEMP);
|
|
free(ioe, M_TEMP);
|
|
error = EBUSY;
|
|
goto fail;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
/* now do the commit - no errors should happen here */
|
|
for (i = 0; i < io->size; i++) {
|
|
#ifdef __FreeBSD__
|
|
PF_COPYIN(io->array+i, ioe, sizeof(*ioe), error);
|
|
if (error) {
|
|
#else
|
|
if (copyin(io->array+i, ioe, sizeof(*ioe))) {
|
|
#endif
|
|
free(table, M_TEMP);
|
|
free(ioe, M_TEMP);
|
|
error = EFAULT;
|
|
goto fail;
|
|
}
|
|
switch (ioe->rs_num) {
|
|
#ifdef ALTQ
|
|
case PF_RULESET_ALTQ:
|
|
if ((error = pf_commit_altq(ioe->ticket))) {
|
|
free(table, M_TEMP);
|
|
free(ioe, M_TEMP);
|
|
goto fail; /* really bad */
|
|
}
|
|
break;
|
|
#endif /* ALTQ */
|
|
case PF_RULESET_TABLE:
|
|
bzero(table, sizeof(*table));
|
|
strlcpy(table->pfrt_anchor, ioe->anchor,
|
|
sizeof(table->pfrt_anchor));
|
|
if ((error = pfr_ina_commit(table, ioe->ticket,
|
|
NULL, NULL, 0))) {
|
|
free(table, M_TEMP);
|
|
free(ioe, M_TEMP);
|
|
goto fail; /* really bad */
|
|
}
|
|
break;
|
|
default:
|
|
if ((error = pf_commit_rules(ioe->ticket,
|
|
ioe->rs_num, ioe->anchor))) {
|
|
free(table, M_TEMP);
|
|
free(ioe, M_TEMP);
|
|
goto fail; /* really bad */
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
free(table, M_TEMP);
|
|
free(ioe, M_TEMP);
|
|
break;
|
|
}
|
|
|
|
case DIOCGETSRCNODES: {
|
|
struct pfioc_src_nodes *psn = (struct pfioc_src_nodes *)addr;
|
|
struct pf_src_node *n, *p, *pstore;
|
|
u_int32_t nr = 0;
|
|
int space = psn->psn_len;
|
|
|
|
if (space == 0) {
|
|
RB_FOREACH(n, pf_src_tree, &tree_src_tracking)
|
|
nr++;
|
|
psn->psn_len = sizeof(struct pf_src_node) * nr;
|
|
break;
|
|
}
|
|
|
|
#ifdef __FreeBSD__
|
|
PF_UNLOCK();
|
|
#endif
|
|
pstore = malloc(sizeof(*pstore), M_TEMP, M_WAITOK);
|
|
#ifdef __FreeBSD__
|
|
PF_LOCK();
|
|
#endif
|
|
|
|
p = psn->psn_src_nodes;
|
|
RB_FOREACH(n, pf_src_tree, &tree_src_tracking) {
|
|
int secs = time_second, diff;
|
|
|
|
if ((nr + 1) * sizeof(*p) > (unsigned)psn->psn_len)
|
|
break;
|
|
|
|
bcopy(n, pstore, sizeof(*pstore));
|
|
if (n->rule.ptr != NULL)
|
|
pstore->rule.nr = n->rule.ptr->nr;
|
|
pstore->creation = secs - pstore->creation;
|
|
if (pstore->expire > secs)
|
|
pstore->expire -= secs;
|
|
else
|
|
pstore->expire = 0;
|
|
|
|
/* adjust the connection rate estimate */
|
|
diff = secs - n->conn_rate.last;
|
|
if (diff >= n->conn_rate.seconds)
|
|
pstore->conn_rate.count = 0;
|
|
else
|
|
pstore->conn_rate.count -=
|
|
n->conn_rate.count * diff /
|
|
n->conn_rate.seconds;
|
|
|
|
#ifdef __FreeBSD__
|
|
PF_COPYOUT(pstore, p, sizeof(*p), error);
|
|
#else
|
|
error = copyout(pstore, p, sizeof(*p));
|
|
#endif
|
|
if (error) {
|
|
free(pstore, M_TEMP);
|
|
goto fail;
|
|
}
|
|
p++;
|
|
nr++;
|
|
}
|
|
psn->psn_len = sizeof(struct pf_src_node) * nr;
|
|
|
|
free(pstore, M_TEMP);
|
|
break;
|
|
}
|
|
|
|
case DIOCCLRSRCNODES: {
|
|
struct pf_src_node *n;
|
|
struct pf_state *state;
|
|
|
|
RB_FOREACH(state, pf_state_tree_id, &tree_id) {
|
|
state->src_node = NULL;
|
|
state->nat_src_node = NULL;
|
|
}
|
|
RB_FOREACH(n, pf_src_tree, &tree_src_tracking) {
|
|
n->expire = 1;
|
|
n->states = 0;
|
|
}
|
|
pf_purge_expired_src_nodes(1);
|
|
pf_status.src_nodes = 0;
|
|
break;
|
|
}
|
|
|
|
case DIOCKILLSRCNODES: {
|
|
struct pf_src_node *sn;
|
|
struct pf_state *s;
|
|
struct pfioc_src_node_kill *psnk = \
|
|
(struct pfioc_src_node_kill *) addr;
|
|
int killed = 0;
|
|
|
|
RB_FOREACH(sn, pf_src_tree, &tree_src_tracking) {
|
|
if (PF_MATCHA(psnk->psnk_src.neg, \
|
|
&psnk->psnk_src.addr.v.a.addr, \
|
|
&psnk->psnk_src.addr.v.a.mask, \
|
|
&sn->addr, sn->af) &&
|
|
PF_MATCHA(psnk->psnk_dst.neg, \
|
|
&psnk->psnk_dst.addr.v.a.addr, \
|
|
&psnk->psnk_dst.addr.v.a.mask, \
|
|
&sn->raddr, sn->af)) {
|
|
/* Handle state to src_node linkage */
|
|
if (sn->states != 0) {
|
|
RB_FOREACH(s, pf_state_tree_id,
|
|
&tree_id) {
|
|
if (s->src_node == sn)
|
|
s->src_node = NULL;
|
|
if (s->nat_src_node == sn)
|
|
s->nat_src_node = NULL;
|
|
}
|
|
sn->states = 0;
|
|
}
|
|
sn->expire = 1;
|
|
killed++;
|
|
}
|
|
}
|
|
|
|
if (killed > 0)
|
|
pf_purge_expired_src_nodes(1);
|
|
|
|
psnk->psnk_af = killed;
|
|
break;
|
|
}
|
|
|
|
case DIOCSETHOSTID: {
|
|
u_int32_t *hostid = (u_int32_t *)addr;
|
|
|
|
if (*hostid == 0)
|
|
pf_status.hostid = arc4random();
|
|
else
|
|
pf_status.hostid = *hostid;
|
|
break;
|
|
}
|
|
|
|
case DIOCOSFPFLUSH:
|
|
pf_osfp_flush();
|
|
break;
|
|
|
|
case DIOCIGETIFACES: {
|
|
struct pfioc_iface *io = (struct pfioc_iface *)addr;
|
|
|
|
if (io->pfiio_esize != sizeof(struct pfi_kif)) {
|
|
error = ENODEV;
|
|
break;
|
|
}
|
|
error = pfi_get_ifaces(io->pfiio_name, io->pfiio_buffer,
|
|
&io->pfiio_size);
|
|
break;
|
|
}
|
|
|
|
case DIOCSETIFFLAG: {
|
|
struct pfioc_iface *io = (struct pfioc_iface *)addr;
|
|
|
|
error = pfi_set_flags(io->pfiio_name, io->pfiio_flags);
|
|
break;
|
|
}
|
|
|
|
case DIOCCLRIFFLAG: {
|
|
struct pfioc_iface *io = (struct pfioc_iface *)addr;
|
|
|
|
error = pfi_clear_flags(io->pfiio_name, io->pfiio_flags);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
error = ENODEV;
|
|
break;
|
|
}
|
|
fail:
|
|
#ifdef __FreeBSD__
|
|
PF_UNLOCK();
|
|
|
|
if (flags & FWRITE)
|
|
sx_xunlock(&pf_consistency_lock);
|
|
else
|
|
sx_sunlock(&pf_consistency_lock);
|
|
#else
|
|
splx(s);
|
|
/* XXX: Lock order? */
|
|
if (flags & FWRITE)
|
|
rw_exit_write(&pf_consistency_lock);
|
|
else
|
|
rw_exit_read(&pf_consistency_lock);
|
|
#endif
|
|
return (error);
|
|
}
|
|
|
|
#ifdef __FreeBSD__
|
|
/*
|
|
* XXX - Check for version missmatch!!!
|
|
*/
|
|
static void
|
|
pf_clear_states(void)
|
|
{
|
|
struct pf_state *state;
|
|
|
|
RB_FOREACH(state, pf_state_tree_id, &tree_id) {
|
|
state->timeout = PFTM_PURGE;
|
|
#if NPFSYNC
|
|
/* don't send out individual delete messages */
|
|
state->sync_flags = PFSTATE_NOSYNC;
|
|
#endif
|
|
pf_unlink_state(state);
|
|
}
|
|
|
|
#if 0 /* NPFSYNC */
|
|
/*
|
|
* XXX This is called on module unload, we do not want to sync that over? */
|
|
*/
|
|
pfsync_clear_states(pf_status.hostid, psk->psk_ifname);
|
|
#endif
|
|
}
|
|
|
|
static int
|
|
pf_clear_tables(void)
|
|
{
|
|
struct pfioc_table io;
|
|
int error;
|
|
|
|
bzero(&io, sizeof(io));
|
|
|
|
error = pfr_clr_tables(&io.pfrio_table, &io.pfrio_ndel,
|
|
io.pfrio_flags);
|
|
|
|
return (error);
|
|
}
|
|
|
|
static void
|
|
pf_clear_srcnodes(void)
|
|
{
|
|
struct pf_src_node *n;
|
|
struct pf_state *state;
|
|
|
|
RB_FOREACH(state, pf_state_tree_id, &tree_id) {
|
|
state->src_node = NULL;
|
|
state->nat_src_node = NULL;
|
|
}
|
|
RB_FOREACH(n, pf_src_tree, &tree_src_tracking) {
|
|
n->expire = 1;
|
|
n->states = 0;
|
|
}
|
|
}
|
|
/*
|
|
* XXX - Check for version missmatch!!!
|
|
*/
|
|
|
|
/*
|
|
* Duplicate pfctl -Fa operation to get rid of as much as we can.
|
|
*/
|
|
static int
|
|
shutdown_pf(void)
|
|
{
|
|
int error = 0;
|
|
u_int32_t t[5];
|
|
char nn = '\0';
|
|
|
|
pf_status.running = 0;
|
|
do {
|
|
if ((error = pf_begin_rules(&t[0], PF_RULESET_SCRUB, &nn))
|
|
!= 0) {
|
|
DPFPRINTF(PF_DEBUG_MISC, ("shutdown_pf: SCRUB\n"));
|
|
break;
|
|
}
|
|
if ((error = pf_begin_rules(&t[1], PF_RULESET_FILTER, &nn))
|
|
!= 0) {
|
|
DPFPRINTF(PF_DEBUG_MISC, ("shutdown_pf: FILTER\n"));
|
|
break; /* XXX: rollback? */
|
|
}
|
|
if ((error = pf_begin_rules(&t[2], PF_RULESET_NAT, &nn))
|
|
!= 0) {
|
|
DPFPRINTF(PF_DEBUG_MISC, ("shutdown_pf: NAT\n"));
|
|
break; /* XXX: rollback? */
|
|
}
|
|
if ((error = pf_begin_rules(&t[3], PF_RULESET_BINAT, &nn))
|
|
!= 0) {
|
|
DPFPRINTF(PF_DEBUG_MISC, ("shutdown_pf: BINAT\n"));
|
|
break; /* XXX: rollback? */
|
|
}
|
|
if ((error = pf_begin_rules(&t[4], PF_RULESET_RDR, &nn))
|
|
!= 0) {
|
|
DPFPRINTF(PF_DEBUG_MISC, ("shutdown_pf: RDR\n"));
|
|
break; /* XXX: rollback? */
|
|
}
|
|
|
|
/* XXX: these should always succeed here */
|
|
pf_commit_rules(t[0], PF_RULESET_SCRUB, &nn);
|
|
pf_commit_rules(t[1], PF_RULESET_FILTER, &nn);
|
|
pf_commit_rules(t[2], PF_RULESET_NAT, &nn);
|
|
pf_commit_rules(t[3], PF_RULESET_BINAT, &nn);
|
|
pf_commit_rules(t[4], PF_RULESET_RDR, &nn);
|
|
|
|
if ((error = pf_clear_tables()) != 0)
|
|
break;
|
|
|
|
#ifdef ALTQ
|
|
if ((error = pf_begin_altq(&t[0])) != 0) {
|
|
DPFPRINTF(PF_DEBUG_MISC, ("shutdown_pf: ALTQ\n"));
|
|
break;
|
|
}
|
|
pf_commit_altq(t[0]);
|
|
#endif
|
|
|
|
pf_clear_states();
|
|
|
|
pf_clear_srcnodes();
|
|
|
|
/* status does not use malloced mem so no need to cleanup */
|
|
/* fingerprints and interfaces have thier own cleanup code */
|
|
} while(0);
|
|
|
|
return (error);
|
|
}
|
|
|
|
static int
|
|
pf_check_in(void *arg, struct mbuf **m, struct ifnet *ifp, int dir,
|
|
struct inpcb *inp)
|
|
{
|
|
/*
|
|
* XXX Wed Jul 9 22:03:16 2003 UTC
|
|
* OpenBSD has changed its byte ordering convention on ip_len/ip_off
|
|
* in network stack. OpenBSD's network stack have converted
|
|
* ip_len/ip_off to host byte order frist as FreeBSD.
|
|
* Now this is not true anymore , so we should convert back to network
|
|
* byte order.
|
|
*/
|
|
struct ip *h = NULL;
|
|
int chk;
|
|
|
|
if ((*m)->m_pkthdr.len >= (int)sizeof(struct ip)) {
|
|
/* if m_pkthdr.len is less than ip header, pf will handle. */
|
|
h = mtod(*m, struct ip *);
|
|
HTONS(h->ip_len);
|
|
HTONS(h->ip_off);
|
|
}
|
|
chk = pf_test(PF_IN, ifp, m, NULL, inp);
|
|
if (chk && *m) {
|
|
m_freem(*m);
|
|
*m = NULL;
|
|
}
|
|
if (*m != NULL) {
|
|
/* pf_test can change ip header location */
|
|
h = mtod(*m, struct ip *);
|
|
NTOHS(h->ip_len);
|
|
NTOHS(h->ip_off);
|
|
}
|
|
return chk;
|
|
}
|
|
|
|
static int
|
|
pf_check_out(void *arg, struct mbuf **m, struct ifnet *ifp, int dir,
|
|
struct inpcb *inp)
|
|
{
|
|
/*
|
|
* XXX Wed Jul 9 22:03:16 2003 UTC
|
|
* OpenBSD has changed its byte ordering convention on ip_len/ip_off
|
|
* in network stack. OpenBSD's network stack have converted
|
|
* ip_len/ip_off to host byte order frist as FreeBSD.
|
|
* Now this is not true anymore , so we should convert back to network
|
|
* byte order.
|
|
*/
|
|
struct ip *h = NULL;
|
|
int chk;
|
|
|
|
/* We need a proper CSUM befor we start (s. OpenBSD ip_output) */
|
|
if ((*m)->m_pkthdr.csum_flags & CSUM_DELAY_DATA) {
|
|
in_delayed_cksum(*m);
|
|
(*m)->m_pkthdr.csum_flags &= ~CSUM_DELAY_DATA;
|
|
}
|
|
if ((*m)->m_pkthdr.len >= (int)sizeof(*h)) {
|
|
/* if m_pkthdr.len is less than ip header, pf will handle. */
|
|
h = mtod(*m, struct ip *);
|
|
HTONS(h->ip_len);
|
|
HTONS(h->ip_off);
|
|
}
|
|
chk = pf_test(PF_OUT, ifp, m, NULL, inp);
|
|
if (chk && *m) {
|
|
m_freem(*m);
|
|
*m = NULL;
|
|
}
|
|
if (*m != NULL) {
|
|
/* pf_test can change ip header location */
|
|
h = mtod(*m, struct ip *);
|
|
NTOHS(h->ip_len);
|
|
NTOHS(h->ip_off);
|
|
}
|
|
return chk;
|
|
}
|
|
|
|
#ifdef INET6
|
|
static int
|
|
pf_check6_in(void *arg, struct mbuf **m, struct ifnet *ifp, int dir,
|
|
struct inpcb *inp)
|
|
{
|
|
INIT_VNET_NET(curvnet);
|
|
|
|
/*
|
|
* IPv6 is not affected by ip_len/ip_off byte order changes.
|
|
*/
|
|
int chk;
|
|
|
|
/*
|
|
* In case of loopback traffic IPv6 uses the real interface in
|
|
* order to support scoped addresses. In order to support stateful
|
|
* filtering we have change this to lo0 as it is the case in IPv4.
|
|
*/
|
|
chk = pf_test6(PF_IN, (*m)->m_flags & M_LOOP ? &V_loif[0] : ifp, m,
|
|
NULL, inp);
|
|
if (chk && *m) {
|
|
m_freem(*m);
|
|
*m = NULL;
|
|
}
|
|
return chk;
|
|
}
|
|
|
|
static int
|
|
pf_check6_out(void *arg, struct mbuf **m, struct ifnet *ifp, int dir,
|
|
struct inpcb *inp)
|
|
{
|
|
/*
|
|
* IPv6 does not affected ip_len/ip_off byte order changes.
|
|
*/
|
|
int chk;
|
|
|
|
/* We need a proper CSUM befor we start (s. OpenBSD ip_output) */
|
|
if ((*m)->m_pkthdr.csum_flags & CSUM_DELAY_DATA) {
|
|
in_delayed_cksum(*m);
|
|
(*m)->m_pkthdr.csum_flags &= ~CSUM_DELAY_DATA;
|
|
}
|
|
chk = pf_test6(PF_OUT, ifp, m, NULL, inp);
|
|
if (chk && *m) {
|
|
m_freem(*m);
|
|
*m = NULL;
|
|
}
|
|
return chk;
|
|
}
|
|
#endif /* INET6 */
|
|
|
|
static int
|
|
hook_pf(void)
|
|
{
|
|
struct pfil_head *pfh_inet;
|
|
#ifdef INET6
|
|
struct pfil_head *pfh_inet6;
|
|
#endif
|
|
|
|
PF_ASSERT(MA_NOTOWNED);
|
|
|
|
if (pf_pfil_hooked)
|
|
return (0);
|
|
|
|
pfh_inet = pfil_head_get(PFIL_TYPE_AF, AF_INET);
|
|
if (pfh_inet == NULL)
|
|
return (ESRCH); /* XXX */
|
|
pfil_add_hook(pf_check_in, NULL, PFIL_IN | PFIL_WAITOK, pfh_inet);
|
|
pfil_add_hook(pf_check_out, NULL, PFIL_OUT | PFIL_WAITOK, pfh_inet);
|
|
#ifdef INET6
|
|
pfh_inet6 = pfil_head_get(PFIL_TYPE_AF, AF_INET6);
|
|
if (pfh_inet6 == NULL) {
|
|
pfil_remove_hook(pf_check_in, NULL, PFIL_IN | PFIL_WAITOK,
|
|
pfh_inet);
|
|
pfil_remove_hook(pf_check_out, NULL, PFIL_OUT | PFIL_WAITOK,
|
|
pfh_inet);
|
|
return (ESRCH); /* XXX */
|
|
}
|
|
pfil_add_hook(pf_check6_in, NULL, PFIL_IN | PFIL_WAITOK, pfh_inet6);
|
|
pfil_add_hook(pf_check6_out, NULL, PFIL_OUT | PFIL_WAITOK, pfh_inet6);
|
|
#endif
|
|
|
|
pf_pfil_hooked = 1;
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
dehook_pf(void)
|
|
{
|
|
struct pfil_head *pfh_inet;
|
|
#ifdef INET6
|
|
struct pfil_head *pfh_inet6;
|
|
#endif
|
|
|
|
PF_ASSERT(MA_NOTOWNED);
|
|
|
|
if (pf_pfil_hooked == 0)
|
|
return (0);
|
|
|
|
pfh_inet = pfil_head_get(PFIL_TYPE_AF, AF_INET);
|
|
if (pfh_inet == NULL)
|
|
return (ESRCH); /* XXX */
|
|
pfil_remove_hook(pf_check_in, NULL, PFIL_IN | PFIL_WAITOK,
|
|
pfh_inet);
|
|
pfil_remove_hook(pf_check_out, NULL, PFIL_OUT | PFIL_WAITOK,
|
|
pfh_inet);
|
|
#ifdef INET6
|
|
pfh_inet6 = pfil_head_get(PFIL_TYPE_AF, AF_INET6);
|
|
if (pfh_inet6 == NULL)
|
|
return (ESRCH); /* XXX */
|
|
pfil_remove_hook(pf_check6_in, NULL, PFIL_IN | PFIL_WAITOK,
|
|
pfh_inet6);
|
|
pfil_remove_hook(pf_check6_out, NULL, PFIL_OUT | PFIL_WAITOK,
|
|
pfh_inet6);
|
|
#endif
|
|
|
|
pf_pfil_hooked = 0;
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
pf_load(void)
|
|
{
|
|
init_zone_var();
|
|
init_pf_mutex();
|
|
pf_dev = make_dev(&pf_cdevsw, 0, 0, 0, 0600, PF_NAME);
|
|
if (pfattach() < 0) {
|
|
destroy_dev(pf_dev);
|
|
destroy_pf_mutex();
|
|
return (ENOMEM);
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
pf_unload(void)
|
|
{
|
|
int error = 0;
|
|
|
|
PF_LOCK();
|
|
pf_status.running = 0;
|
|
PF_UNLOCK();
|
|
error = dehook_pf();
|
|
if (error) {
|
|
/*
|
|
* Should not happen!
|
|
* XXX Due to error code ESRCH, kldunload will show
|
|
* a message like 'No such process'.
|
|
*/
|
|
printf("%s : pfil unregisteration fail\n", __FUNCTION__);
|
|
return error;
|
|
}
|
|
PF_LOCK();
|
|
shutdown_pf();
|
|
pf_end_threads = 1;
|
|
while (pf_end_threads < 2) {
|
|
wakeup_one(pf_purge_thread);
|
|
msleep(pf_purge_thread, &pf_task_mtx, 0, "pftmo", hz);
|
|
}
|
|
pfi_cleanup();
|
|
pf_osfp_flush();
|
|
pf_osfp_cleanup();
|
|
cleanup_pf_zone();
|
|
PF_UNLOCK();
|
|
destroy_dev(pf_dev);
|
|
destroy_pf_mutex();
|
|
return error;
|
|
}
|
|
|
|
static int
|
|
pf_modevent(module_t mod, int type, void *data)
|
|
{
|
|
int error = 0;
|
|
|
|
switch(type) {
|
|
case MOD_LOAD:
|
|
error = pf_load();
|
|
break;
|
|
|
|
case MOD_UNLOAD:
|
|
error = pf_unload();
|
|
break;
|
|
default:
|
|
error = EINVAL;
|
|
break;
|
|
}
|
|
return error;
|
|
}
|
|
|
|
static moduledata_t pf_mod = {
|
|
"pf",
|
|
pf_modevent,
|
|
0
|
|
};
|
|
|
|
DECLARE_MODULE(pf, pf_mod, SI_SUB_PROTO_IFATTACHDOMAIN, SI_ORDER_FIRST);
|
|
MODULE_VERSION(pf, PF_MODVER);
|
|
#endif /* __FreeBSD__ */
|