8b07e49a00
This particular implementation is designed to be fully backwards compatible and to be MFC-able to 7.x (and 6.x) Currently the only protocol that can make use of the multiple tables is IPv4 Similar functionality exists in OpenBSD and Linux. From my notes: ----- One thing where FreeBSD has been falling behind, and which by chance I have some time to work on is "policy based routing", which allows different packet streams to be routed by more than just the destination address. Constraints: ------------ I want to make some form of this available in the 6.x tree (and by extension 7.x) , but FreeBSD in general needs it so I might as well do it in -current and back port the portions I need. One of the ways that this can be done is to have the ability to instantiate multiple kernel routing tables (which I will now refer to as "Forwarding Information Bases" or "FIBs" for political correctness reasons). Which FIB a particular packet uses to make the next hop decision can be decided by a number of mechanisms. The policies these mechanisms implement are the "Policies" referred to in "Policy based routing". One of the constraints I have if I try to back port this work to 6.x is that it must be implemented as a EXTENSION to the existing ABIs in 6.x so that third party applications do not need to be recompiled in timespan of the branch. This first version will not have some of the bells and whistles that will come with later versions. It will, for example, be limited to 16 tables in the first commit. Implementation method, Compatible version. (part 1) ------------------------------- For this reason I have implemented a "sufficient subset" of a multiple routing table solution in Perforce, and back-ported it to 6.x. (also in Perforce though not always caught up with what I have done in -current/P4). The subset allows a number of FIBs to be defined at compile time (8 is sufficient for my purposes in 6.x) and implements the changes needed to allow IPV4 to use them. I have not done the changes for ipv6 simply because I do not need it, and I do not have enough knowledge of ipv6 (e.g. neighbor discovery) needed to do it. Other protocol families are left untouched and should there be users with proprietary protocol families, they should continue to work and be oblivious to the existence of the extra FIBs. To understand how this is done, one must know that the current FIB code starts everything off with a single dimensional array of pointers to FIB head structures (One per protocol family), each of which in turn points to the trie of routes available to that family. The basic change in the ABI compatible version of the change is to extent that array to be a 2 dimensional array, so that instead of protocol family X looking at rt_tables[X] for the table it needs, it looks at rt_tables[Y][X] when for all protocol families except ipv4 Y is always 0. Code that is unaware of the change always just sees the first row of the table, which of course looks just like the one dimensional array that existed before. The entry points rtrequest(), rtalloc(), rtalloc1(), rtalloc_ign() are all maintained, but refer only to the first row of the array, so that existing callers in proprietary protocols can continue to do the "right thing". Some new entry points are added, for the exclusive use of ipv4 code called in_rtrequest(), in_rtalloc(), in_rtalloc1() and in_rtalloc_ign(), which have an extra argument which refers the code to the correct row. In addition, there are some new entry points (currently called rtalloc_fib() and friends) that check the Address family being looked up and call either rtalloc() (and friends) if the protocol is not IPv4 forcing the action to row 0 or to the appropriate row if it IS IPv4 (and that info is available). These are for calling from code that is not specific to any particular protocol. The way these are implemented would change in the non ABI preserving code to be added later. One feature of the first version of the code is that for ipv4, the interface routes show up automatically on all the FIBs, so that no matter what FIB you select you always have the basic direct attached hosts available to you. (rtinit() does this automatically). You CAN delete an interface route from one FIB should you want to but by default it's there. ARP information is also available in each FIB. It's assumed that the same machine would have the same MAC address, regardless of which FIB you are using to get to it. This brings us as to how the correct FIB is selected for an outgoing IPV4 packet. Firstly, all packets have a FIB associated with them. if nothing has been done to change it, it will be FIB 0. The FIB is changed in the following ways. Packets fall into one of a number of classes. 1/ locally generated packets, coming from a socket/PCB. Such packets select a FIB from a number associated with the socket/PCB. This in turn is inherited from the process, but can be changed by a socket option. The process in turn inherits it on fork. I have written a utility call setfib that acts a bit like nice.. setfib -3 ping target.example.com # will use fib 3 for ping. It is an obvious extension to make it a property of a jail but I have not done so. It can be achieved by combining the setfib and jail commands. 2/ packets received on an interface for forwarding. By default these packets would use table 0, (or possibly a number settable in a sysctl(not yet)). but prior to routing the firewall can inspect them (see below). (possibly in the future you may be able to associate a FIB with packets received on an interface.. An ifconfig arg, but not yet.) 3/ packets inspected by a packet classifier, which can arbitrarily associate a fib with it on a packet by packet basis. A fib assigned to a packet by a packet classifier (such as ipfw) would over-ride a fib associated by a more default source. (such as cases 1 or 2). 4/ a tcp listen socket associated with a fib will generate accept sockets that are associated with that same fib. 5/ Packets generated in response to some other packet (e.g. reset or icmp packets). These should use the FIB associated with the packet being reponded to. 6/ Packets generated during encapsulation. gif, tun and other tunnel interfaces will encapsulate using the FIB that was in effect withthe proces that set up the tunnel. thus setfib 1 ifconfig gif0 [tunnel instructions] will set the fib for the tunnel to use to be fib 1. Routing messages would be associated with their process, and thus select one FIB or another. messages from the kernel would be associated with the fib they refer to and would only be received by a routing socket associated with that fib. (not yet implemented) In addition Netstat has been edited to be able to cope with the fact that the array is now 2 dimensional. (It looks in system memory using libkvm (!)). Old versions of netstat see only the first FIB. In addition two sysctls are added to give: a) the number of FIBs compiled in (active) b) the default FIB of the calling process. Early testing experience: ------------------------- Basically our (IronPort's) appliance does this functionality already using ipfw fwd but that method has some drawbacks. For example, It can't fully simulate a routing table because it can't influence the socket's choice of local address when a connect() is done. Testing during the generating of these changes has been remarkably smooth so far. Multiple tables have co-existed with no notable side effects, and packets have been routes accordingly. ipfw has grown 2 new keywords: setfib N ip from anay to any count ip from any to any fib N In pf there seems to be a requirement to be able to give symbolic names to the fibs but I do not have that capacity. I am not sure if it is required. SCTP has interestingly enough built in support for this, called VRFs in Cisco parlance. it will be interesting to see how that handles it when it suddenly actually does something. Where to next: -------------------- After committing the ABI compatible version and MFCing it, I'd like to proceed in a forward direction in -current. this will result in some roto-tilling in the routing code. Firstly: the current code's idea of having a separate tree per protocol family, all of the same format, and pointed to by the 1 dimensional array is a bit silly. Especially when one considers that there is code that makes assumptions about every protocol having the same internal structures there. Some protocols don't WANT that sort of structure. (for example the whole idea of a netmask is foreign to appletalk). This needs to be made opaque to the external code. My suggested first change is to add routing method pointers to the 'domain' structure, along with information pointing the data. instead of having an array of pointers to uniform structures, there would be an array pointing to the 'domain' structures for each protocol address domain (protocol family), and the methods this reached would be called. The methods would have an argument that gives FIB number, but the protocol would be free to ignore it. When the ABI can be changed it raises the possibilty of the addition of a fib entry into the "struct route". Currently, the structure contains the sockaddr of the desination, and the resulting fib entry. To make this work fully, one could add a fib number so that given an address and a fib, one can find the third element, the fib entry. Interaction with the ARP layer/ LL layer would need to be revisited as well. Qing Li has been working on this already. This work was sponsored by Ironport Systems/Cisco Reviewed by: several including rwatson, bz and mlair (parts each) Obtained from: Ironport systems/Cisco
3895 lines
92 KiB
C
3895 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>
|
|
#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>
|
|
|
|
#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 (minor(dev) >= 1)
|
|
return (ENXIO);
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
pfclose(struct cdev *dev, int flags, int fmt, struct proc *p)
|
|
{
|
|
if (minor(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__ /* ROUTEING */
|
|
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)
|
|
{
|
|
/*
|
|
* 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 ? &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__ */
|