diff options
Diffstat (limited to 'sbin/pfctl')
327 files changed, 25208 insertions, 0 deletions
diff --git a/sbin/pfctl/Makefile b/sbin/pfctl/Makefile new file mode 100644 index 000000000000..08ca9a7af81b --- /dev/null +++ b/sbin/pfctl/Makefile @@ -0,0 +1,34 @@ +.include <src.opts.mk> + +PACKAGE=pf +CONFS= pf.os +PROG= pfctl +MAN= pfctl.8 + +SRCS = pfctl.c parse.y pfctl_parser.c pf_print_state.c pfctl_altq.c +SRCS+= pfctl_osfp.c pfctl_radix.c pfctl_table.c pfctl_qstats.c +SRCS+= pfctl_optimize.c +SRCS+= pf_ruleset.c + +WARNS?= 2 +CFLAGS+= -Wall -Wmissing-prototypes -Wno-uninitialized +CFLAGS+= -Wstrict-prototypes +CFLAGS+= -DENABLE_ALTQ -I${.CURDIR} +CFLAGS+= -I${SRCTOP}/lib/libpfctl -I${OBJTOP}/lib/libpfctl + +# Need to use "WITH_" prefix to not conflict with the l/y INET/INET6 keywords +.if ${MK_INET6_SUPPORT} != "no" +CFLAGS+= -DWITH_INET6 +.endif +.if ${MK_INET_SUPPORT} != "no" +CFLAGS+= -DWITH_INET +.endif + +YFLAGS= + +LIBADD= m md pfctl + +HAS_TESTS= +SUBDIR.${MK_TESTS}+= tests + +.include <bsd.prog.mk> diff --git a/sbin/pfctl/Makefile.depend b/sbin/pfctl/Makefile.depend new file mode 100644 index 000000000000..bc09f07d0227 --- /dev/null +++ b/sbin/pfctl/Makefile.depend @@ -0,0 +1,21 @@ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + include \ + include/arpa \ + include/xlocale \ + lib/${CSU_DIR} \ + lib/libc \ + lib/libcompiler_rt \ + lib/libmd \ + lib/libnv \ + lib/libpfctl \ + lib/msun \ + usr.bin/yacc.host \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/sbin/pfctl/parse.y b/sbin/pfctl/parse.y new file mode 100644 index 000000000000..127e2c257d69 --- /dev/null +++ b/sbin/pfctl/parse.y @@ -0,0 +1,7879 @@ +/* $OpenBSD: parse.y,v 1.554 2008/10/17 12:59:53 henning Exp $ */ + +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2001 Markus Friedl. All rights reserved. + * Copyright (c) 2001 Daniel Hartmeier. All rights reserved. + * Copyright (c) 2001 Theo de Raadt. All rights reserved. + * 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: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR 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. + */ +%{ +#include <sys/cdefs.h> +#define PFIOC_USE_LATEST + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/stat.h> +#ifdef __FreeBSD__ +#include <sys/sysctl.h> +#endif +#include <net/if.h> +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <netinet/ip_icmp.h> +#include <netinet/icmp6.h> +#include <net/pfvar.h> +#include <arpa/inet.h> +#include <net/altq/altq.h> +#include <net/altq/altq_cbq.h> +#include <net/altq/altq_codel.h> +#include <net/altq/altq_priq.h> +#include <net/altq/altq_hfsc.h> +#include <net/altq/altq_fairq.h> + +#include <assert.h> +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <netdb.h> +#include <stdarg.h> +#include <errno.h> +#include <string.h> +#include <ctype.h> +#include <math.h> +#include <err.h> +#include <limits.h> +#include <pwd.h> +#include <grp.h> +#include <md5.h> + +#include "pfctl_parser.h" +#include "pfctl.h" + +static struct pfctl *pf = NULL; +static int debug = 0; +static int rulestate = 0; +static u_int16_t returnicmpdefault = + (ICMP_UNREACH << 8) | ICMP_UNREACH_PORT; +static u_int16_t returnicmp6default = + (ICMP6_DST_UNREACH << 8) | ICMP6_DST_UNREACH_NOPORT; +static int blockpolicy = PFRULE_DROP; +static int failpolicy = PFRULE_DROP; +static int require_order = 1; +static int default_statelock; + +static TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files); +static struct file { + TAILQ_ENTRY(file) entry; + FILE *stream; + char *name; + size_t ungetpos; + size_t ungetsize; + u_char *ungetbuf; + int eof_reached; + int lineno; + int errors; +} *file, *topfile; +struct file *pushfile(const char *, int); +int popfile(void); +int check_file_secrecy(int, const char *); +int yyparse(void); +int yylex(void); +int yyerror(const char *, ...); +int kw_cmp(const void *, const void *); +int lookup(char *); +int igetc(void); +int lgetc(int); +void lungetc(int); +int findeol(void); + +static TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead); +struct sym { + TAILQ_ENTRY(sym) entry; + int used; + int persist; + char *nam; + char *val; +}; +int symset(const char *, const char *, int); +char *symget(const char *); + +int atoul(char *, u_long *); + +enum { + PFCTL_STATE_NONE, + PFCTL_STATE_OPTION, + PFCTL_STATE_ETHER, + PFCTL_STATE_SCRUB, + PFCTL_STATE_QUEUE, + PFCTL_STATE_NAT, + PFCTL_STATE_FILTER +}; + +struct node_etherproto { + u_int16_t proto; + struct node_etherproto *next; + struct node_etherproto *tail; +}; + +struct node_proto { + u_int8_t proto; + struct node_proto *next; + struct node_proto *tail; +}; + +struct node_port { + u_int16_t port[2]; + u_int8_t op; + struct node_port *next; + struct node_port *tail; +}; + +struct node_uid { + uid_t uid[2]; + u_int8_t op; + struct node_uid *next; + struct node_uid *tail; +}; + +struct node_gid { + gid_t gid[2]; + u_int8_t op; + struct node_gid *next; + struct node_gid *tail; +}; + +struct node_icmp { + uint16_t code; + uint16_t type; + u_int8_t proto; + struct node_icmp *next; + struct node_icmp *tail; +}; + +enum { PF_STATE_OPT_MAX, PF_STATE_OPT_NOSYNC, PF_STATE_OPT_SRCTRACK, + PF_STATE_OPT_MAX_SRC_STATES, PF_STATE_OPT_MAX_SRC_CONN, + PF_STATE_OPT_MAX_SRC_CONN_RATE, PF_STATE_OPT_MAX_SRC_NODES, + PF_STATE_OPT_OVERLOAD, PF_STATE_OPT_STATELOCK, + PF_STATE_OPT_TIMEOUT, PF_STATE_OPT_SLOPPY, + PF_STATE_OPT_PFLOW, PF_STATE_OPT_ALLOW_RELATED }; + +enum { PF_SRCTRACK_NONE, PF_SRCTRACK, PF_SRCTRACK_GLOBAL, PF_SRCTRACK_RULE }; + +struct node_state_opt { + int type; + union { + u_int32_t max_states; + u_int32_t max_src_states; + u_int32_t max_src_conn; + struct { + u_int32_t limit; + u_int32_t seconds; + } max_src_conn_rate; + struct { + u_int8_t flush; + char tblname[PF_TABLE_NAME_SIZE]; + } overload; + u_int32_t max_src_nodes; + u_int8_t src_track; + u_int32_t statelock; + struct { + int number; + u_int32_t seconds; + } timeout; + } data; + struct node_state_opt *next; + struct node_state_opt *tail; +}; + +struct peer { + struct node_host *host; + struct node_port *port; +}; + +static struct node_queue { + char queue[PF_QNAME_SIZE]; + char parent[PF_QNAME_SIZE]; + char ifname[IFNAMSIZ]; + int scheduler; + struct node_queue *next; + struct node_queue *tail; +} *queues = NULL; + +struct node_qassign { + char *qname; + char *pqname; +}; + +struct range { + int a; + int b; + int t; +}; + +static struct pool_opts { + int marker; +#define POM_TYPE 0x01 +#define POM_STICKYADDRESS 0x02 +#define POM_ENDPI 0x04 +#define POM_IPV6NH 0x08 + u_int8_t opts; + int type; + int staticport; + struct pf_poolhashkey *key; + struct pf_mape_portset mape; +} pool_opts; + +struct redirspec { + struct node_host *host; + struct range rport; + struct pool_opts pool_opts; + sa_family_t af; + bool binat; +}; + +static struct filter_opts { + int marker; +#define FOM_FLAGS 0x0001 +#define FOM_ICMP 0x0002 +#define FOM_TOS 0x0004 +#define FOM_KEEP 0x0008 +#define FOM_SRCTRACK 0x0010 +#define FOM_MINTTL 0x0020 +#define FOM_MAXMSS 0x0040 +#define FOM_AFTO 0x0080 +#define FOM_SETTOS 0x0100 +#define FOM_SCRUB_TCP 0x0200 +#define FOM_SETPRIO 0x0400 +#define FOM_ONCE 0x1000 +#define FOM_PRIO 0x2000 +#define FOM_SETDELAY 0x4000 +#define FOM_FRAGCACHE 0x8000 /* does not exist in OpenBSD */ + struct node_uid *uid; + struct node_gid *gid; + struct node_if *rcv; + struct { + u_int8_t b1; + u_int8_t b2; + u_int16_t w; + u_int16_t w2; + } flags; + struct node_icmp *icmpspec; + u_int32_t tos; + u_int32_t prob; + u_int32_t ridentifier; + struct { + int action; + struct node_state_opt *options; + } keep; + int fragment; + int allowopts; + char *label[PF_RULE_MAX_LABEL_COUNT]; + int labelcount; + struct node_qassign queues; + char *tag; + char *match_tag; + u_int8_t match_tag_not; + u_int16_t dnpipe; + u_int16_t dnrpipe; + u_int32_t free_flags; + u_int rtableid; + u_int8_t prio; + u_int8_t set_prio[2]; + struct { + struct node_host *addr; + u_int16_t port; + } divert; + struct redirspec *nat; + struct redirspec *rdr; + /* new-style scrub opts */ + int nodf; + int minttl; + int settos; + int randomid; + int max_mss; + struct { + uint32_t limit; + uint32_t seconds; + } pktrate; + int max_pkt_size; +} filter_opts; + +static struct antispoof_opts { + char *label[PF_RULE_MAX_LABEL_COUNT]; + int labelcount; + u_int32_t ridentifier; + u_int rtableid; +} antispoof_opts; + +static struct scrub_opts { + int marker; + int nodf; + int minttl; + int maxmss; + int settos; + int fragcache; + int randomid; + int reassemble_tcp; + char *match_tag; + u_int8_t match_tag_not; + u_int rtableid; +} scrub_opts; + +static struct queue_opts { + int marker; +#define QOM_BWSPEC 0x01 +#define QOM_SCHEDULER 0x02 +#define QOM_PRIORITY 0x04 +#define QOM_TBRSIZE 0x08 +#define QOM_QLIMIT 0x10 + struct node_queue_bw queue_bwspec; + struct node_queue_opt scheduler; + int priority; + unsigned int tbrsize; + int qlimit; +} queue_opts; + +static struct table_opts { + int flags; + int init_addr; + struct node_tinithead init_nodes; +} table_opts; + +static struct codel_opts codel_opts; +static struct node_hfsc_opts hfsc_opts; +static struct node_fairq_opts fairq_opts; +static struct node_state_opt *keep_state_defaults = NULL; +static struct pfctl_watermarks syncookie_opts; + +int validate_range(uint8_t, uint16_t, uint16_t); +int disallow_table(struct node_host *, const char *); +int disallow_urpf_failed(struct node_host *, const char *); +int disallow_alias(struct node_host *, const char *); +int rule_consistent(struct pfctl_rule *); +int filter_consistent(struct pfctl_rule *); +int nat_consistent(struct pfctl_rule *); +int rdr_consistent(struct pfctl_rule *); +int process_tabledef(char *, struct table_opts *, int); +void expand_label_str(char *, size_t, const char *, const char *); +void expand_label_if(const char *, char *, size_t, const char *); +void expand_label_addr(const char *, char *, size_t, sa_family_t, + struct pf_rule_addr *); +void expand_label_port(const char *, char *, size_t, + struct pf_rule_addr *); +void expand_label_proto(const char *, char *, size_t, u_int8_t); +void expand_label_nr(const char *, char *, size_t, + struct pfctl_rule *); +void expand_eth_rule(struct pfctl_eth_rule *, + struct node_if *, struct node_etherproto *, + struct node_mac *, struct node_mac *, + struct node_host *, struct node_host *, const char *, + const char *); +int apply_rdr_ports(struct pfctl_rule *r, struct pfctl_pool *, struct redirspec *); +int apply_nat_ports(struct pfctl_pool *, struct redirspec *); +int apply_redirspec(struct pfctl_pool *, struct redirspec *); +int check_binat_redirspec(struct node_host *, struct pfctl_rule *, sa_family_t); +void add_binat_rdr_rule(struct pfctl_rule *, struct redirspec *, + struct node_host *, struct pfctl_rule *, struct redirspec **, + struct node_host **); +void expand_rule(struct pfctl_rule *, bool, struct node_if *, + struct redirspec *, struct redirspec *, struct redirspec *, + struct node_proto *, struct node_os *, struct node_host *, + struct node_port *, struct node_host *, struct node_port *, + struct node_uid *, struct node_gid *, struct node_if *, + struct node_icmp *); +int expand_altq(struct pf_altq *, struct node_if *, + struct node_queue *, struct node_queue_bw bwspec, + struct node_queue_opt *); +int expand_queue(struct pf_altq *, struct node_if *, + struct node_queue *, struct node_queue_bw, + struct node_queue_opt *); +int expand_skip_interface(struct node_if *); + +int check_rulestate(int); +int getservice(char *); +int rule_label(struct pfctl_rule *, char *s[PF_RULE_MAX_LABEL_COUNT]); +int eth_rule_label(struct pfctl_eth_rule *, char *s[PF_RULE_MAX_LABEL_COUNT]); +int rt_tableid_max(void); + +void mv_rules(struct pfctl_ruleset *, struct pfctl_ruleset *); +void mv_eth_rules(struct pfctl_eth_ruleset *, struct pfctl_eth_ruleset *); +void mv_tables(struct pfctl *, struct pfr_ktablehead *, + struct pfctl_anchor *, struct pfctl_anchor *); +void decide_address_family(struct node_host *, sa_family_t *); +void remove_invalid_hosts(struct node_host **, sa_family_t *); +int invalid_redirect(struct node_host *, sa_family_t); +u_int16_t parseicmpspec(char *, sa_family_t); +int kw_casecmp(const void *, const void *); +int map_tos(char *string, int *); +int filteropts_to_rule(struct pfctl_rule *, struct filter_opts *); +struct node_mac* node_mac_from_string(const char *); +struct node_mac* node_mac_from_string_masklen(const char *, int); +struct node_mac* node_mac_from_string_mask(const char *, const char *); +static bool pfctl_setup_anchor(struct pfctl_rule *, struct pfctl *, char *); + +static TAILQ_HEAD(loadanchorshead, loadanchors) + loadanchorshead = TAILQ_HEAD_INITIALIZER(loadanchorshead); + +struct loadanchors { + TAILQ_ENTRY(loadanchors) entries; + char *anchorname; + char *filename; +}; + +typedef struct { + union { + int64_t number; + double probability; + int i; + char *string; + u_int rtableid; + struct { + u_int8_t b1; + u_int8_t b2; + u_int16_t w; + u_int16_t w2; + } b; + struct range range; + struct node_if *interface; + struct node_proto *proto; + struct node_etherproto *etherproto; + struct node_icmp *icmp; + struct node_host *host; + struct node_os *os; + struct node_port *port; + struct node_uid *uid; + struct node_gid *gid; + struct node_state_opt *state_opt; + struct peer peer; + struct { + struct peer src, dst; + struct node_os *src_os; + } fromto; + struct { + struct node_mac *src; + struct node_mac *dst; + } etherfromto; + struct node_mac *mac; + struct { + struct node_mac *mac; + } etheraddr; + char *bridge_to; + struct { + struct redirspec *redirspec; + u_int8_t rt; + } route; + struct redirspec *redirspec; + struct { + int action; + struct node_state_opt *options; + } keep_state; + struct { + u_int8_t log; + u_int8_t logif; + u_int8_t quick; + } logquick; + struct { + int neg; + char *name; + } tagged; + struct pf_poolhashkey *hashkey; + struct node_queue *queue; + struct node_queue_opt queue_options; + struct node_queue_bw queue_bwspec; + struct node_qassign qassign; + struct filter_opts filter_opts; + struct antispoof_opts antispoof_opts; + struct queue_opts queue_opts; + struct scrub_opts scrub_opts; + struct table_opts table_opts; + struct pool_opts pool_opts; + struct node_hfsc_opts hfsc_opts; + struct node_fairq_opts fairq_opts; + struct codel_opts codel_opts; + struct pfctl_watermarks *watermarks; + } v; + int lineno; +} YYSTYPE; + +#define PPORT_RANGE 1 +#define PPORT_STAR 2 +int parseport(char *, struct range *r, int); + +#define DYNIF_MULTIADDR(addr) ((addr).type == PF_ADDR_DYNIFTL && \ + (!((addr).iflags & PFI_AFLAG_NOALIAS) || \ + !isdigit((addr).v.ifname[strlen((addr).v.ifname)-1]))) + +%} + +%token PASS BLOCK MATCH SCRUB RETURN IN OS OUT LOG QUICK ON FROM TO FLAGS +%token RETURNRST RETURNICMP RETURNICMP6 PROTO INET INET6 ALL ANY ICMPTYPE +%token ICMP6TYPE CODE KEEP MODULATE STATE PORT RDR NAT BINAT ARROW NODF +%token MINTTL ERROR ALLOWOPTS FASTROUTE FILENAME ROUTETO DUPTO REPLYTO NO LABEL +%token NOROUTE URPFFAILED FRAGMENT USER GROUP MAXMSS MAXIMUM TTL TOS DROP TABLE +%token REASSEMBLE ANCHOR NATANCHOR RDRANCHOR BINATANCHOR +%token SET OPTIMIZATION TIMEOUT LIMIT LOGINTERFACE BLOCKPOLICY FAILPOLICY +%token RANDOMID REQUIREORDER SYNPROXY FINGERPRINTS NOSYNC DEBUG SKIP HOSTID +%token ANTISPOOF FOR INCLUDE KEEPCOUNTERS SYNCOOKIES L3 MATCHES +%token ETHER +%token BITMASK RANDOM SOURCEHASH ROUNDROBIN STATICPORT PROBABILITY MAPEPORTSET +%token ALTQ CBQ CODEL PRIQ HFSC FAIRQ BANDWIDTH TBRSIZE LINKSHARE REALTIME +%token UPPERLIMIT QUEUE PRIORITY QLIMIT HOGS BUCKETS RTABLE TARGET INTERVAL +%token DNPIPE DNQUEUE RIDENTIFIER +%token LOAD RULESET_OPTIMIZATION PRIO ONCE +%token STICKYADDRESS ENDPI MAXSRCSTATES MAXSRCNODES SOURCETRACK GLOBAL RULE +%token MAXSRCCONN MAXSRCCONNRATE OVERLOAD FLUSH SLOPPY PFLOW ALLOW_RELATED +%token TAGGED TAG IFBOUND FLOATING STATEPOLICY STATEDEFAULTS ROUTE SETTOS +%token DIVERTTO DIVERTREPLY BRIDGE_TO RECEIVEDON NE LE GE AFTO NATTO RDRTO +%token BINATTO MAXPKTRATE MAXPKTSIZE IPV6NH +%token <v.string> STRING +%token <v.number> NUMBER +%token <v.i> PORTBINARY +%type <v.interface> interface if_list if_item_not if_item +%type <v.number> number icmptype icmp6type uid gid +%type <v.number> tos not yesno optnodf +%type <v.probability> probability +%type <v.i> no dir af fragcache optimizer syncookie_val +%type <v.i> sourcetrack flush unaryop statelock +%type <v.i> etherprotoval +%type <v.b> action nataction natpasslog scrubaction +%type <v.b> flags flag blockspec prio +%type <v.range> portplain portstar portrange +%type <v.hashkey> hashkey +%type <v.proto> proto proto_list proto_item +%type <v.number> protoval +%type <v.icmp> icmpspec +%type <v.icmp> icmp_list icmp_item +%type <v.icmp> icmp6_list icmp6_item +%type <v.number> reticmpspec reticmp6spec +%type <v.fromto> fromto l3fromto +%type <v.peer> ipportspec from to +%type <v.host> ipspec toipspec xhost host dynaddr host_list +%type <v.host> redir_host redir_host_list routespec +%type <v.host> route_host route_host_list +%type <v.os> os xos os_list +%type <v.port> portspec port_list port_item +%type <v.uid> uids uid_list uid_item +%type <v.gid> gids gid_list gid_item +%type <v.route> route +%type <v.redirspec> no_port_redirspec port_redirspec route_redirspec +%type <v.redirspec> binat_redirspec nat_redirspec +%type <v.string> label stringall tag anchorname +%type <v.string> string varstring numberstring +%type <v.keep_state> keep +%type <v.state_opt> state_opt_spec state_opt_list state_opt_item +%type <v.logquick> logquick quick log logopts logopt +%type <v.interface> antispoof_ifspc antispoof_iflst antispoof_if +%type <v.qassign> qname etherqname +%type <v.queue> qassign qassign_list qassign_item +%type <v.queue_options> scheduler +%type <v.number> cbqflags_list cbqflags_item +%type <v.number> priqflags_list priqflags_item +%type <v.hfsc_opts> hfscopts_list hfscopts_item hfsc_opts +%type <v.fairq_opts> fairqopts_list fairqopts_item fairq_opts +%type <v.codel_opts> codelopts_list codelopts_item codel_opts +%type <v.queue_bwspec> bandwidth +%type <v.filter_opts> filter_opts filter_opt filter_opts_l etherfilter_opts etherfilter_opt etherfilter_opts_l +%type <v.filter_opts> filter_sets filter_set filter_sets_l +%type <v.antispoof_opts> antispoof_opts antispoof_opt antispoof_opts_l +%type <v.queue_opts> queue_opts queue_opt queue_opts_l +%type <v.scrub_opts> scrub_opts scrub_opt scrub_opts_l +%type <v.table_opts> table_opts table_opt table_opts_l +%type <v.pool_opts> pool_opts pool_opt pool_opts_l +%type <v.tagged> tagged +%type <v.rtableid> rtable +%type <v.watermarks> syncookie_opts +%type <v.etherproto> etherproto etherproto_list etherproto_item +%type <v.etherfromto> etherfromto +%type <v.etheraddr> etherfrom etherto +%type <v.bridge_to> bridge +%type <v.mac> xmac mac mac_list macspec +%% + +ruleset : /* empty */ + | ruleset include '\n' + | ruleset '\n' + | ruleset option '\n' + | ruleset etherrule '\n' + | ruleset etheranchorrule '\n' + | ruleset scrubrule '\n' + | ruleset natrule '\n' + | ruleset binatrule '\n' + | ruleset pfrule '\n' + | ruleset anchorrule '\n' + | ruleset loadrule '\n' + | ruleset altqif '\n' + | ruleset queuespec '\n' + | ruleset varset '\n' + | ruleset antispoof '\n' + | ruleset tabledef '\n' + | '{' fakeanchor '}' '\n'; + | ruleset error '\n' { file->errors++; } + ; + +include : INCLUDE STRING { + struct file *nfile; + + if ((nfile = pushfile($2, 0)) == NULL) { + yyerror("failed to include file %s", $2); + free($2); + YYERROR; + } + free($2); + + file = nfile; + lungetc('\n'); + } + ; + +/* + * apply to previouslys specified rule: must be careful to note + * what that is: pf or nat or binat or rdr + */ +fakeanchor : fakeanchor '\n' + | fakeanchor anchorrule '\n' + | fakeanchor binatrule '\n' + | fakeanchor natrule '\n' + | fakeanchor pfrule '\n' + | fakeanchor error '\n' + ; + +optimizer : string { + if (!strcmp($1, "none")) + $$ = 0; + else if (!strcmp($1, "basic")) + $$ = PF_OPTIMIZE_BASIC; + else if (!strcmp($1, "profile")) + $$ = PF_OPTIMIZE_BASIC | PF_OPTIMIZE_PROFILE; + else { + yyerror("unknown ruleset-optimization %s", $1); + YYERROR; + } + } + ; + +optnodf : /* empty */ { $$ = 0; } + | NODF { $$ = 1; } + ; + +option : SET REASSEMBLE yesno optnodf { + if (check_rulestate(PFCTL_STATE_OPTION)) + YYERROR; + pfctl_set_reassembly(pf, $3, $4); + } + | SET OPTIMIZATION STRING { + if (check_rulestate(PFCTL_STATE_OPTION)) { + free($3); + YYERROR; + } + if (pfctl_set_optimization(pf, $3) != 0) { + yyerror("unknown optimization %s", $3); + free($3); + YYERROR; + } + free($3); + } + | SET RULESET_OPTIMIZATION optimizer { + if (!(pf->opts & PF_OPT_OPTIMIZE)) { + pf->opts |= PF_OPT_OPTIMIZE; + pf->optimize = $3; + } + } + | SET TIMEOUT timeout_spec + | SET TIMEOUT '{' optnl timeout_list '}' + | SET LIMIT limit_spec + | SET LIMIT '{' optnl limit_list '}' + | SET LOGINTERFACE stringall { + if (check_rulestate(PFCTL_STATE_OPTION)) { + free($3); + YYERROR; + } + if (pfctl_set_logif(pf, $3) != 0) { + yyerror("error setting loginterface %s", $3); + free($3); + YYERROR; + } + free($3); + } + | SET HOSTID number { + if ($3 == 0 || $3 > UINT_MAX) { + yyerror("hostid must be non-zero"); + YYERROR; + } + pfctl_set_hostid(pf, $3); + } + | SET BLOCKPOLICY DROP { + if (pf->opts & PF_OPT_VERBOSE) + printf("set block-policy drop\n"); + if (check_rulestate(PFCTL_STATE_OPTION)) + YYERROR; + blockpolicy = PFRULE_DROP; + } + | SET BLOCKPOLICY RETURN { + if (pf->opts & PF_OPT_VERBOSE) + printf("set block-policy return\n"); + if (check_rulestate(PFCTL_STATE_OPTION)) + YYERROR; + blockpolicy = PFRULE_RETURN; + } + | SET FAILPOLICY DROP { + if (pf->opts & PF_OPT_VERBOSE) + printf("set fail-policy drop\n"); + if (check_rulestate(PFCTL_STATE_OPTION)) + YYERROR; + failpolicy = PFRULE_DROP; + } + | SET FAILPOLICY RETURN { + if (pf->opts & PF_OPT_VERBOSE) + printf("set fail-policy return\n"); + if (check_rulestate(PFCTL_STATE_OPTION)) + YYERROR; + failpolicy = PFRULE_RETURN; + } + | SET REQUIREORDER yesno { + if (pf->opts & PF_OPT_VERBOSE) + printf("set require-order %s\n", + $3 == 1 ? "yes" : "no"); + require_order = $3; + } + | SET FINGERPRINTS STRING { + if (pf->opts & PF_OPT_VERBOSE) + printf("set fingerprints \"%s\"\n", $3); + if (check_rulestate(PFCTL_STATE_OPTION)) { + free($3); + YYERROR; + } + if (!pf->anchor->name[0]) { + if (pfctl_file_fingerprints(pf->dev, + pf->opts, $3)) { + yyerror("error loading " + "fingerprints %s", $3); + free($3); + YYERROR; + } + } + free($3); + } + | SET STATEPOLICY statelock { + if (pf->opts & PF_OPT_VERBOSE) + switch ($3) { + case 0: + printf("set state-policy floating\n"); + break; + case PFRULE_IFBOUND: + printf("set state-policy if-bound\n"); + break; + } + default_statelock = $3; + } + | SET DEBUG STRING { + if (check_rulestate(PFCTL_STATE_OPTION)) { + free($3); + YYERROR; + } + if (pfctl_do_set_debug(pf, $3) != 0) { + yyerror("error setting debuglevel %s", $3); + free($3); + YYERROR; + } + free($3); + } + | SET SKIP interface { + if (expand_skip_interface($3) != 0) { + yyerror("error setting skip interface(s)"); + YYERROR; + } + } + | SET STATEDEFAULTS state_opt_list { + if (keep_state_defaults != NULL) { + yyerror("cannot redefine state-defaults"); + YYERROR; + } + keep_state_defaults = $3; + } + | SET KEEPCOUNTERS { + pf->keep_counters = true; + } + | SET SYNCOOKIES syncookie_val syncookie_opts { + if (pfctl_cfg_syncookies(pf, $3, $4)) { + yyerror("error setting syncookies"); + YYERROR; + } + } + ; + +syncookie_val : STRING { + if (!strcmp($1, "never")) + $$ = PFCTL_SYNCOOKIES_NEVER; + else if (!strcmp($1, "adaptive")) + $$ = PFCTL_SYNCOOKIES_ADAPTIVE; + else if (!strcmp($1, "always")) + $$ = PFCTL_SYNCOOKIES_ALWAYS; + else { + yyerror("illegal value for syncookies"); + YYERROR; + } + } + ; +syncookie_opts : /* empty */ { $$ = NULL; } + | { + memset(&syncookie_opts, 0, sizeof(syncookie_opts)); + } '(' syncookie_opt_l ')' { $$ = &syncookie_opts; } + ; + +syncookie_opt_l : syncookie_opt_l comma syncookie_opt + | syncookie_opt + ; + +syncookie_opt : STRING STRING { + double val; + char *cp; + + val = strtod($2, &cp); + if (cp == NULL || strcmp(cp, "%")) + YYERROR; + if (val <= 0 || val > 100) { + yyerror("illegal percentage value"); + YYERROR; + } + if (!strcmp($1, "start")) { + syncookie_opts.hi = val; + } else if (!strcmp($1, "end")) { + syncookie_opts.lo = val; + } else { + yyerror("illegal syncookie option"); + YYERROR; + } + } + ; + +stringall : STRING { $$ = $1; } + | ALL { + if (($$ = strdup("all")) == NULL) { + err(1, "stringall: strdup"); + } + } + ; + +string : STRING string { + if (asprintf(&$$, "%s %s", $1, $2) == -1) + err(1, "string: asprintf"); + free($1); + free($2); + } + | STRING + ; + +varstring : numberstring varstring { + if (asprintf(&$$, "%s %s", $1, $2) == -1) + err(1, "string: asprintf"); + free($1); + free($2); + } + | numberstring + ; + +numberstring : NUMBER { + char *s; + if (asprintf(&s, "%lld", (long long)$1) == -1) { + yyerror("string: asprintf"); + YYERROR; + } + $$ = s; + } + | STRING + ; + +varset : STRING '=' varstring { + char *s = $1; + if (pf->opts & PF_OPT_VERBOSE) + printf("%s = \"%s\"\n", $1, $3); + while (*s++) { + if (isspace((unsigned char)*s)) { + yyerror("macro name cannot contain " + "whitespace"); + free($1); + free($3); + YYERROR; + } + } + if (symset($1, $3, 0) == -1) + err(1, "cannot store variable %s", $1); + free($1); + free($3); + } + ; + +anchorname : STRING { + if ($1[0] == '\0') { + free($1); + yyerror("anchor name must not be empty"); + YYERROR; + } + if (strlen(pf->anchor->path) + 1 + + strlen($1) >= PATH_MAX) { + free($1); + yyerror("anchor name is longer than %u", + PATH_MAX - 1); + YYERROR; + } + if ($1[0] == '_' || strstr($1, "/_") != NULL) { + free($1); + yyerror("anchor names beginning with '_' " + "are reserved for internal use"); + YYERROR; + } + $$ = $1; + } + | /* empty */ { $$ = NULL; } + ; + +pfa_anchorlist : /* empty */ + | pfa_anchorlist '\n' + | pfa_anchorlist tabledef '\n' + | pfa_anchorlist pfrule '\n' + | pfa_anchorlist anchorrule '\n' + | pfa_anchorlist include '\n' + ; + +pfa_anchor : '{' + { + char ta[PF_ANCHOR_NAME_SIZE]; + struct pfctl_ruleset *rs; + + /* stepping into a brace anchor */ + if (pf->asd >= PFCTL_ANCHOR_STACK_DEPTH) + errx(1, "pfa_anchor: anchors too deep"); + pf->asd++; + pf->bn++; + + /* + * Anchor contents are parsed before the anchor rule + * production completes, so we don't know the real + * location yet. Create a holding ruleset in the root; + * contents will be moved afterwards. + */ + snprintf(ta, PF_ANCHOR_NAME_SIZE, "_%d", pf->bn); + rs = pf_find_or_create_ruleset(ta); + if (rs == NULL) + err(1, "pfa_anchor: pf_find_or_create_ruleset (%s)", ta); + pf->astack[pf->asd] = rs->anchor; + pf->anchor = rs->anchor; + } '\n' pfa_anchorlist '}' + { + pf->alast = pf->anchor; + pf->asd--; + pf->anchor = pf->astack[pf->asd]; + } + | /* empty */ + ; + +anchorrule : ANCHOR anchorname dir quick interface af proto fromto + filter_opts pfa_anchor + { + struct pfctl_rule r; + struct node_proto *proto; + + if (check_rulestate(PFCTL_STATE_FILTER)) { + if ($2) + free($2); + YYERROR; + } + + pfctl_init_rule(&r); + if (! pfctl_setup_anchor(&r, pf, $2)) + YYERROR; + + r.direction = $3; + r.quick = $4.quick; + r.af = $6; + + if ($9.flags.b1 || $9.flags.b2 || $8.src_os) { + for (proto = $7; proto != NULL && + proto->proto != IPPROTO_TCP; + proto = proto->next) + ; /* nothing */ + if (proto == NULL && $7 != NULL) { + if ($9.flags.b1 || $9.flags.b2) + yyerror( + "flags only apply to tcp"); + if ($8.src_os) + yyerror( + "OS fingerprinting only " + "applies to tcp"); + YYERROR; + } + } + + if (filteropts_to_rule(&r, &$9)) + YYERROR; + + if ($9.keep.action) { + yyerror("cannot specify state handling " + "on anchors"); + YYERROR; + } + + decide_address_family($8.src.host, &r.af); + decide_address_family($8.dst.host, &r.af); + + expand_rule(&r, false, $5, NULL, NULL, NULL, + $7, $8.src_os, $8.src.host, $8.src.port, $8.dst.host, + $8.dst.port, $9.uid, $9.gid, $9.rcv, $9.icmpspec); + free($2); + pf->astack[pf->asd + 1] = NULL; + } + | NATANCHOR string interface af proto fromto rtable { + struct pfctl_rule r; + + if (check_rulestate(PFCTL_STATE_NAT)) { + free($2); + YYERROR; + } + + pfctl_init_rule(&r); + if (! pfctl_setup_anchor(&r, pf, $2)) + YYERROR; + + r.action = PF_NAT; + r.af = $4; + r.rtableid = $7; + + decide_address_family($6.src.host, &r.af); + decide_address_family($6.dst.host, &r.af); + + expand_rule(&r, false, $3, NULL, NULL, NULL, + $5, $6.src_os, $6.src.host, $6.src.port, $6.dst.host, + $6.dst.port, 0, 0, 0, 0); + free($2); + } + | RDRANCHOR string interface af proto fromto rtable { + struct pfctl_rule r; + + if (check_rulestate(PFCTL_STATE_NAT)) { + free($2); + YYERROR; + } + + pfctl_init_rule(&r); + if (! pfctl_setup_anchor(&r, pf, $2)) + YYERROR; + + r.action = PF_RDR; + r.af = $4; + r.rtableid = $7; + + decide_address_family($6.src.host, &r.af); + decide_address_family($6.dst.host, &r.af); + + if ($6.src.port != NULL) { + yyerror("source port parameter not supported" + " in rdr-anchor"); + YYERROR; + } + if ($6.dst.port != NULL) { + if ($6.dst.port->next != NULL) { + yyerror("destination port list " + "expansion not supported in " + "rdr-anchor"); + YYERROR; + } else if ($6.dst.port->op != PF_OP_EQ) { + yyerror("destination port operators" + " not supported in rdr-anchor"); + YYERROR; + } + r.dst.port[0] = $6.dst.port->port[0]; + r.dst.port[1] = $6.dst.port->port[1]; + r.dst.port_op = $6.dst.port->op; + } + + expand_rule(&r, false, $3, NULL, NULL, NULL, + $5, $6.src_os, $6.src.host, $6.src.port, $6.dst.host, + $6.dst.port, 0, 0, 0, 0); + free($2); + } + | BINATANCHOR string interface af proto fromto rtable { + struct pfctl_rule r; + + if (check_rulestate(PFCTL_STATE_NAT)) { + free($2); + YYERROR; + } + + pfctl_init_rule(&r); + if (! pfctl_setup_anchor(&r, pf, $2)) + YYERROR; + + r.action = PF_BINAT; + r.af = $4; + r.rtableid = $7; + if ($5 != NULL) { + if ($5->next != NULL) { + yyerror("proto list expansion" + " not supported in binat-anchor"); + YYERROR; + } + r.proto = $5->proto; + free($5); + } + + if ($6.src.host != NULL || $6.src.port != NULL || + $6.dst.host != NULL || $6.dst.port != NULL) { + yyerror("fromto parameter not supported" + " in binat-anchor"); + YYERROR; + } + + decide_address_family($6.src.host, &r.af); + decide_address_family($6.dst.host, &r.af); + + pfctl_append_rule(pf, &r); + free($2); + } + ; + +loadrule : LOAD ANCHOR anchorname FROM string { + struct loadanchors *loadanchor; + + if ($3 == NULL) { + yyerror("anchor name is missing"); + YYERROR; + } + loadanchor = calloc(1, sizeof(struct loadanchors)); + if (loadanchor == NULL) + err(1, "loadrule: calloc"); + if ((loadanchor->anchorname = malloc(MAXPATHLEN)) == + NULL) + err(1, "loadrule: malloc"); + if (pf->anchor->name[0]) + snprintf(loadanchor->anchorname, MAXPATHLEN, + "%s/%s", pf->anchor->path, $3); + else + strlcpy(loadanchor->anchorname, $3, MAXPATHLEN); + if ((loadanchor->filename = strdup($5)) == NULL) + err(1, "loadrule: strdup"); + + TAILQ_INSERT_TAIL(&loadanchorshead, loadanchor, + entries); + + free($3); + free($5); + }; + +scrubaction : no SCRUB { + $$.b2 = $$.w = 0; + if ($1) + $$.b1 = PF_NOSCRUB; + else + $$.b1 = PF_SCRUB; + } + ; + +etherrule : ETHER action dir quick interface bridge etherproto etherfromto l3fromto etherfilter_opts + { + struct pfctl_eth_rule r; + + bzero(&r, sizeof(r)); + + if (check_rulestate(PFCTL_STATE_ETHER)) + YYERROR; + + r.action = $2.b1; + r.direction = $3; + r.quick = $4.quick; + if ($10.tag != NULL) + strlcpy(r.tagname, $10.tag, sizeof(r.tagname)); + if ($10.match_tag) + if (strlcpy(r.match_tagname, $10.match_tag, + PF_TAG_NAME_SIZE) >= PF_TAG_NAME_SIZE) { + yyerror("tag too long, max %u chars", + PF_TAG_NAME_SIZE - 1); + YYERROR; + } + r.match_tag_not = $10.match_tag_not; + if ($10.queues.qname != NULL) + strlcpy(r.qname, $10.queues.qname, sizeof(r.qname)); + r.dnpipe = $10.dnpipe; + r.dnflags = $10.free_flags; + if (eth_rule_label(&r, $10.label)) + YYERROR; + for (int i = 0; i < PF_RULE_MAX_LABEL_COUNT; i++) + free($10.label[i]); + r.ridentifier = $10.ridentifier; + + expand_eth_rule(&r, $5, $7, $8.src, $8.dst, + $9.src.host, $9.dst.host, $6, ""); + } + ; + +etherpfa_anchorlist : /* empty */ + | etherpfa_anchorlist '\n' + | etherpfa_anchorlist etherrule '\n' + | etherpfa_anchorlist etheranchorrule '\n' + ; + +etherpfa_anchor : '{' + { + char ta[PF_ANCHOR_NAME_SIZE]; + struct pfctl_eth_ruleset *rs; + + /* steping into a brace anchor */ + if (pf->asd >= PFCTL_ANCHOR_STACK_DEPTH) + errx(1, "pfa_anchor: anchors too deep"); + pf->asd++; + pf->bn++; + + /* create a holding ruleset in the root */ + snprintf(ta, PF_ANCHOR_NAME_SIZE, "_%d", pf->bn); + rs = pf_find_or_create_eth_ruleset(ta); + if (rs == NULL) + err(1, "etherpfa_anchor: pf_find_or_create_eth_ruleset"); + pf->eastack[pf->asd] = rs->anchor; + pf->eanchor = rs->anchor; + } '\n' etherpfa_anchorlist '}' + { + pf->ealast = pf->eanchor; + pf->asd--; + pf->eanchor = pf->eastack[pf->asd]; + } + | /* empty */ + ; + +etheranchorrule : ETHER ANCHOR anchorname dir quick interface etherproto etherfromto l3fromto etherpfa_anchor + { + struct pfctl_eth_rule r; + + if (check_rulestate(PFCTL_STATE_ETHER)) { + free($3); + YYERROR; + } + + if ($3 && ($3[0] == '_' || strstr($3, "/_") != NULL)) { + free($3); + yyerror("anchor names beginning with '_' " + "are reserved for internal use"); + YYERROR; + } + + memset(&r, 0, sizeof(r)); + if (pf->eastack[pf->asd + 1]) { + if ($3 && strchr($3, '/') != NULL) { + free($3); + yyerror("anchor paths containing '/' " + "cannot be used for inline anchors."); + YYERROR; + } + + /* Move inline rules into relative location. */ + pfctl_eth_anchor_setup(pf, &r, + &pf->eastack[pf->asd]->ruleset, + $3 ? $3 : pf->ealast->name); + if (r.anchor == NULL) + err(1, "etheranchorrule: unable to " + "create ruleset"); + + if (pf->ealast != r.anchor) { + if (r.anchor->match) { + yyerror("inline anchor '%s' " + "already exists", + r.anchor->name); + YYERROR; + } + mv_eth_rules(&pf->ealast->ruleset, + &r.anchor->ruleset); + } + pf_remove_if_empty_eth_ruleset(&pf->ealast->ruleset); + pf->ealast = r.anchor; + } else { + if (!$3) { + yyerror("anchors without explicit " + "rules must specify a name"); + YYERROR; + } + } + + r.direction = $4; + r.quick = $5.quick; + + expand_eth_rule(&r, $6, $7, $8.src, $8.dst, + $9.src.host, $9.dst.host, NULL, + pf->eastack[pf->asd + 1] ? pf->ealast->name : $3); + + free($3); + pf->eastack[pf->asd + 1] = NULL; + } + ; + +etherfilter_opts : { + bzero(&filter_opts, sizeof filter_opts); + } + etherfilter_opts_l + { $$ = filter_opts; } + | /* empty */ { + bzero(&filter_opts, sizeof filter_opts); + $$ = filter_opts; + } + ; + +etherfilter_opts_l : etherfilter_opts_l etherfilter_opt + | etherfilter_opt + +etherfilter_opt : etherqname { + if (filter_opts.queues.qname) { + yyerror("queue cannot be redefined"); + YYERROR; + } + filter_opts.queues = $1; + } + | RIDENTIFIER number { + filter_opts.ridentifier = $2; + } + | label { + if (filter_opts.labelcount >= PF_RULE_MAX_LABEL_COUNT) { + yyerror("label can only be used %d times", PF_RULE_MAX_LABEL_COUNT); + YYERROR; + } + filter_opts.label[filter_opts.labelcount++] = $1; + } + | TAG string { + filter_opts.tag = $2; + } + | not TAGGED string { + filter_opts.match_tag = $3; + filter_opts.match_tag_not = $1; + } + | DNPIPE number { + filter_opts.dnpipe = $2; + filter_opts.free_flags |= PFRULE_DN_IS_PIPE; + } + | DNQUEUE number { + filter_opts.dnpipe = $2; + filter_opts.free_flags |= PFRULE_DN_IS_QUEUE; + } + ; + +bridge : /* empty */ { + $$ = NULL; + } + | BRIDGE_TO STRING { + $$ = strdup($2); + } + ; + +scrubrule : scrubaction dir logquick interface af proto fromto scrub_opts + { + struct pfctl_rule r; + + if (check_rulestate(PFCTL_STATE_SCRUB)) + YYERROR; + + pfctl_init_rule(&r); + + r.action = $1.b1; + r.direction = $2; + + r.log = $3.log; + r.logif = $3.logif; + if ($3.quick) { + yyerror("scrub rules do not support 'quick'"); + YYERROR; + } + + r.af = $5; + if ($8.nodf) + r.rule_flag |= PFRULE_NODF; + if ($8.randomid) + r.rule_flag |= PFRULE_RANDOMID; + if ($8.reassemble_tcp) { + if (r.direction != PF_INOUT) { + yyerror("reassemble tcp rules can not " + "specify direction"); + YYERROR; + } + r.rule_flag |= PFRULE_REASSEMBLE_TCP; + } + if ($8.minttl) + r.min_ttl = $8.minttl; + if ($8.maxmss) + r.max_mss = $8.maxmss; + if ($8.marker & FOM_SETTOS) { + r.rule_flag |= PFRULE_SET_TOS; + r.set_tos = $8.settos; + } + if ($8.fragcache) + r.rule_flag |= $8.fragcache; + if ($8.match_tag) + if (strlcpy(r.match_tagname, $8.match_tag, + PF_TAG_NAME_SIZE) >= PF_TAG_NAME_SIZE) { + yyerror("tag too long, max %u chars", + PF_TAG_NAME_SIZE - 1); + YYERROR; + } + r.match_tag_not = $8.match_tag_not; + r.rtableid = $8.rtableid; + + expand_rule(&r, false, $4, NULL, NULL, NULL, + $6, $7.src_os, $7.src.host, $7.src.port, $7.dst.host, + $7.dst.port, NULL, NULL, NULL, NULL); + } + ; + +scrub_opts : { + bzero(&scrub_opts, sizeof scrub_opts); + scrub_opts.rtableid = -1; + } + scrub_opts_l + { $$ = scrub_opts; } + | /* empty */ { + bzero(&scrub_opts, sizeof scrub_opts); + scrub_opts.rtableid = -1; + $$ = scrub_opts; + } + ; + +scrub_opts_l : scrub_opts_l comma scrub_opt + | scrub_opt + ; + +scrub_opt : NODF { + if (scrub_opts.nodf) { + yyerror("no-df cannot be respecified"); + YYERROR; + } + scrub_opts.nodf = 1; + } + | MINTTL NUMBER { + if (scrub_opts.marker & FOM_MINTTL) { + yyerror("min-ttl cannot be respecified"); + YYERROR; + } + if ($2 < 0 || $2 > 255) { + yyerror("illegal min-ttl value %d", $2); + YYERROR; + } + scrub_opts.marker |= FOM_MINTTL; + scrub_opts.minttl = $2; + } + | MAXMSS NUMBER { + if (scrub_opts.marker & FOM_MAXMSS) { + yyerror("max-mss cannot be respecified"); + YYERROR; + } + if ($2 < 0 || $2 > 65535) { + yyerror("illegal max-mss value %d", $2); + YYERROR; + } + scrub_opts.marker |= FOM_MAXMSS; + scrub_opts.maxmss = $2; + } + | SETTOS tos { + if (scrub_opts.marker & FOM_SETTOS) { + yyerror("set-tos cannot be respecified"); + YYERROR; + } + scrub_opts.marker |= FOM_SETTOS; + scrub_opts.settos = $2; + } + | fragcache { + if (scrub_opts.marker & FOM_FRAGCACHE) { + yyerror("fragcache cannot be respecified"); + YYERROR; + } + scrub_opts.marker |= FOM_FRAGCACHE; + scrub_opts.fragcache = $1; + } + | REASSEMBLE STRING { + if (strcasecmp($2, "tcp") != 0) { + yyerror("scrub reassemble supports only tcp, " + "not '%s'", $2); + free($2); + YYERROR; + } + free($2); + if (scrub_opts.reassemble_tcp) { + yyerror("reassemble tcp cannot be respecified"); + YYERROR; + } + scrub_opts.reassemble_tcp = 1; + } + | RANDOMID { + if (scrub_opts.randomid) { + yyerror("random-id cannot be respecified"); + YYERROR; + } + scrub_opts.randomid = 1; + } + | RTABLE NUMBER { + if ($2 < 0 || $2 > rt_tableid_max()) { + yyerror("invalid rtable id"); + YYERROR; + } + scrub_opts.rtableid = $2; + } + | not TAGGED string { + scrub_opts.match_tag = $3; + scrub_opts.match_tag_not = $1; + } + ; + +fragcache : FRAGMENT REASSEMBLE { $$ = 0; /* default */ } + | FRAGMENT NO REASSEMBLE { $$ = PFRULE_FRAGMENT_NOREASS; } + ; + +antispoof : ANTISPOOF logquick antispoof_ifspc af antispoof_opts { + struct pfctl_rule r; + struct node_host *h = NULL, *hh; + struct node_if *i, *j; + + if (check_rulestate(PFCTL_STATE_FILTER)) + YYERROR; + + for (i = $3; i; i = i->next) { + pfctl_init_rule(&r); + + r.action = PF_DROP; + r.direction = PF_IN; + r.log = $2.log; + r.logif = $2.logif; + r.quick = $2.quick; + r.af = $4; + r.ridentifier = $5.ridentifier; + if (rule_label(&r, $5.label)) + YYERROR; + r.rtableid = $5.rtableid; + j = calloc(1, sizeof(struct node_if)); + if (j == NULL) + err(1, "antispoof: calloc"); + if (strlcpy(j->ifname, i->ifname, + sizeof(j->ifname)) >= sizeof(j->ifname)) { + free(j); + yyerror("interface name too long"); + YYERROR; + } + j->not = 1; + if (i->dynamic) { + h = calloc(1, sizeof(*h)); + if (h == NULL) + err(1, "address: calloc"); + h->addr.type = PF_ADDR_DYNIFTL; + set_ipmask(h, 128); + if (strlcpy(h->addr.v.ifname, i->ifname, + sizeof(h->addr.v.ifname)) >= + sizeof(h->addr.v.ifname)) { + free(h); + yyerror( + "interface name too long"); + YYERROR; + } + hh = malloc(sizeof(*hh)); + if (hh == NULL) + err(1, "address: malloc"); + bcopy(h, hh, sizeof(*hh)); + h->addr.iflags = PFI_AFLAG_NETWORK; + } else { + h = ifa_lookup(j->ifname, + PFI_AFLAG_NETWORK); + hh = NULL; + } + + if (h != NULL) + expand_rule(&r, false, j, NULL, NULL, + NULL, NULL, NULL, h, NULL, NULL, + NULL, NULL, NULL, NULL, NULL); + + if ((i->ifa_flags & IFF_LOOPBACK) == 0) { + bzero(&r, sizeof(r)); + + r.action = PF_DROP; + r.direction = PF_IN; + r.log = $2.log; + r.logif = $2.logif; + r.quick = $2.quick; + r.af = $4; + r.ridentifier = $5.ridentifier; + if (rule_label(&r, $5.label)) + YYERROR; + r.rtableid = $5.rtableid; + if (hh != NULL) + h = hh; + else + h = ifa_lookup(i->ifname, 0); + if (h != NULL) + expand_rule(&r, false, NULL, + NULL, NULL, NULL, NULL, + NULL, h, NULL, NULL, NULL, + NULL, NULL, NULL, NULL); + } else + free(hh); + } + for (int i = 0; i < PF_RULE_MAX_LABEL_COUNT; i++) + free($5.label[i]); + } + ; + +antispoof_ifspc : FOR antispoof_if { $$ = $2; } + | FOR '{' optnl antispoof_iflst '}' { $$ = $4; } + ; + +antispoof_iflst : antispoof_if optnl { $$ = $1; } + | antispoof_iflst comma antispoof_if optnl { + $1->tail->next = $3; + $1->tail = $3; + $$ = $1; + } + ; + +antispoof_if : if_item { $$ = $1; } + | '(' if_item ')' { + $2->dynamic = 1; + $$ = $2; + } + ; + +antispoof_opts : { + bzero(&antispoof_opts, sizeof antispoof_opts); + antispoof_opts.rtableid = -1; + } + antispoof_opts_l + { $$ = antispoof_opts; } + | /* empty */ { + bzero(&antispoof_opts, sizeof antispoof_opts); + antispoof_opts.rtableid = -1; + $$ = antispoof_opts; + } + ; + +antispoof_opts_l : antispoof_opts_l antispoof_opt + | antispoof_opt + ; + +antispoof_opt : label { + if (antispoof_opts.labelcount >= PF_RULE_MAX_LABEL_COUNT) { + yyerror("label can only be used %d times", PF_RULE_MAX_LABEL_COUNT); + YYERROR; + } + antispoof_opts.label[antispoof_opts.labelcount++] = $1; + } + | RIDENTIFIER number { + antispoof_opts.ridentifier = $2; + } + | RTABLE NUMBER { + if ($2 < 0 || $2 > rt_tableid_max()) { + yyerror("invalid rtable id"); + YYERROR; + } + antispoof_opts.rtableid = $2; + } + ; + +not : '!' { $$ = 1; } + | /* empty */ { $$ = 0; } + ; + +tabledef : TABLE '<' STRING '>' table_opts { + struct node_host *h, *nh; + struct node_tinit *ti, *nti; + + if (strlen($3) >= PF_TABLE_NAME_SIZE) { + yyerror("table name too long, max %d chars", + PF_TABLE_NAME_SIZE - 1); + free($3); + YYERROR; + } + if (pf->loadopt & PFCTL_FLAG_TABLE) + if (process_tabledef($3, &$5, pf->opts)) { + free($3); + YYERROR; + } + free($3); + for (ti = SIMPLEQ_FIRST(&$5.init_nodes); + ti != SIMPLEQ_END(&$5.init_nodes); ti = nti) { + if (ti->file) + free(ti->file); + for (h = ti->host; h != NULL; h = nh) { + nh = h->next; + free(h); + } + nti = SIMPLEQ_NEXT(ti, entries); + free(ti); + } + } + ; + +table_opts : { + bzero(&table_opts, sizeof table_opts); + SIMPLEQ_INIT(&table_opts.init_nodes); + } + table_opts_l + { $$ = table_opts; } + | /* empty */ + { + bzero(&table_opts, sizeof table_opts); + SIMPLEQ_INIT(&table_opts.init_nodes); + $$ = table_opts; + } + ; + +table_opts_l : table_opts_l table_opt + | table_opt + ; + +table_opt : STRING { + if (!strcmp($1, "const")) + table_opts.flags |= PFR_TFLAG_CONST; + else if (!strcmp($1, "persist")) + table_opts.flags |= PFR_TFLAG_PERSIST; + else if (!strcmp($1, "counters")) + table_opts.flags |= PFR_TFLAG_COUNTERS; + else { + yyerror("invalid table option '%s'", $1); + free($1); + YYERROR; + } + free($1); + } + | '{' optnl '}' { table_opts.init_addr = 1; } + | '{' optnl host_list '}' { + struct node_host *n; + struct node_tinit *ti; + + for (n = $3; n != NULL; n = n->next) { + switch (n->addr.type) { + case PF_ADDR_ADDRMASK: + continue; /* ok */ + case PF_ADDR_RANGE: + yyerror("address ranges are not " + "permitted inside tables"); + break; + case PF_ADDR_DYNIFTL: + yyerror("dynamic addresses are not " + "permitted inside tables"); + break; + case PF_ADDR_TABLE: + yyerror("tables cannot contain tables"); + break; + case PF_ADDR_NOROUTE: + yyerror("\"no-route\" is not permitted " + "inside tables"); + break; + case PF_ADDR_URPFFAILED: + yyerror("\"urpf-failed\" is not " + "permitted inside tables"); + break; + default: + yyerror("unknown address type %d", + n->addr.type); + } + YYERROR; + } + if (!(ti = calloc(1, sizeof(*ti)))) + err(1, "table_opt: calloc"); + ti->host = $3; + SIMPLEQ_INSERT_TAIL(&table_opts.init_nodes, ti, + entries); + table_opts.init_addr = 1; + } + | FILENAME STRING { + struct node_tinit *ti; + + if (!(ti = calloc(1, sizeof(*ti)))) + err(1, "table_opt: calloc"); + ti->file = $2; + SIMPLEQ_INSERT_TAIL(&table_opts.init_nodes, ti, + entries); + table_opts.init_addr = 1; + } + ; + +altqif : ALTQ interface queue_opts QUEUE qassign { + struct pf_altq a; + + if (check_rulestate(PFCTL_STATE_QUEUE)) + YYERROR; + + memset(&a, 0, sizeof(a)); + if ($3.scheduler.qtype == ALTQT_NONE) { + yyerror("no scheduler specified!"); + YYERROR; + } + a.scheduler = $3.scheduler.qtype; + a.qlimit = $3.qlimit; + a.tbrsize = $3.tbrsize; + if ($5 == NULL && $3.scheduler.qtype != ALTQT_CODEL) { + yyerror("no child queues specified"); + YYERROR; + } + if (expand_altq(&a, $2, $5, $3.queue_bwspec, + &$3.scheduler)) + YYERROR; + } + ; + +queuespec : QUEUE STRING interface queue_opts qassign { + struct pf_altq a; + + if (check_rulestate(PFCTL_STATE_QUEUE)) { + free($2); + YYERROR; + } + + memset(&a, 0, sizeof(a)); + + if (strlcpy(a.qname, $2, sizeof(a.qname)) >= + sizeof(a.qname)) { + yyerror("queue name too long (max " + "%d chars)", PF_QNAME_SIZE-1); + free($2); + YYERROR; + } + free($2); + if ($4.tbrsize) { + yyerror("cannot specify tbrsize for queue"); + YYERROR; + } + if ($4.priority > 255) { + yyerror("priority out of range: max 255"); + YYERROR; + } + a.priority = $4.priority; + a.qlimit = $4.qlimit; + a.scheduler = $4.scheduler.qtype; + if (expand_queue(&a, $3, $5, $4.queue_bwspec, + &$4.scheduler)) { + yyerror("errors in queue definition"); + YYERROR; + } + } + ; + +queue_opts : { + bzero(&queue_opts, sizeof queue_opts); + queue_opts.priority = DEFAULT_PRIORITY; + queue_opts.qlimit = DEFAULT_QLIMIT; + queue_opts.scheduler.qtype = ALTQT_NONE; + queue_opts.queue_bwspec.bw_percent = 100; + } + queue_opts_l + { $$ = queue_opts; } + | /* empty */ { + bzero(&queue_opts, sizeof queue_opts); + queue_opts.priority = DEFAULT_PRIORITY; + queue_opts.qlimit = DEFAULT_QLIMIT; + queue_opts.scheduler.qtype = ALTQT_NONE; + queue_opts.queue_bwspec.bw_percent = 100; + $$ = queue_opts; + } + ; + +queue_opts_l : queue_opts_l queue_opt + | queue_opt + ; + +queue_opt : BANDWIDTH bandwidth { + if (queue_opts.marker & QOM_BWSPEC) { + yyerror("bandwidth cannot be respecified"); + YYERROR; + } + queue_opts.marker |= QOM_BWSPEC; + queue_opts.queue_bwspec = $2; + } + | PRIORITY NUMBER { + if (queue_opts.marker & QOM_PRIORITY) { + yyerror("priority cannot be respecified"); + YYERROR; + } + if ($2 < 0 || $2 > 255) { + yyerror("priority out of range: max 255"); + YYERROR; + } + queue_opts.marker |= QOM_PRIORITY; + queue_opts.priority = $2; + } + | QLIMIT NUMBER { + if (queue_opts.marker & QOM_QLIMIT) { + yyerror("qlimit cannot be respecified"); + YYERROR; + } + if ($2 < 0 || $2 > 65535) { + yyerror("qlimit out of range: max 65535"); + YYERROR; + } + queue_opts.marker |= QOM_QLIMIT; + queue_opts.qlimit = $2; + } + | scheduler { + if (queue_opts.marker & QOM_SCHEDULER) { + yyerror("scheduler cannot be respecified"); + YYERROR; + } + queue_opts.marker |= QOM_SCHEDULER; + queue_opts.scheduler = $1; + } + | TBRSIZE NUMBER { + if (queue_opts.marker & QOM_TBRSIZE) { + yyerror("tbrsize cannot be respecified"); + YYERROR; + } + if ($2 < 0 || $2 > UINT_MAX) { + yyerror("tbrsize too big: max %u", UINT_MAX); + YYERROR; + } + queue_opts.marker |= QOM_TBRSIZE; + queue_opts.tbrsize = $2; + } + ; + +bandwidth : STRING { + double bps; + char *cp; + + $$.bw_percent = 0; + + bps = strtod($1, &cp); + if (cp != NULL) { + if (strlen(cp) > 1) { + char *cu = cp + 1; + if (!strcmp(cu, "Bit") || + !strcmp(cu, "B") || + !strcmp(cu, "bit") || + !strcmp(cu, "b")) { + *cu = 0; + } + } + if (!strcmp(cp, "b")) + ; /* nothing */ + else if (!strcmp(cp, "K")) + bps *= 1000; + else if (!strcmp(cp, "M")) + bps *= 1000 * 1000; + else if (!strcmp(cp, "G")) + bps *= 1000 * 1000 * 1000; + else if (!strcmp(cp, "%")) { + if (bps < 0 || bps > 100) { + yyerror("bandwidth spec " + "out of range"); + free($1); + YYERROR; + } + $$.bw_percent = bps; + bps = 0; + } else { + yyerror("unknown unit %s", cp); + free($1); + YYERROR; + } + } + free($1); + $$.bw_absolute = (u_int64_t)bps; + } + | NUMBER { + if ($1 < 0 || $1 >= LLONG_MAX) { + yyerror("bandwidth number too big"); + YYERROR; + } + $$.bw_percent = 0; + $$.bw_absolute = $1; + } + ; + +scheduler : CBQ { + $$.qtype = ALTQT_CBQ; + $$.data.cbq_opts.flags = 0; + } + | CBQ '(' cbqflags_list ')' { + $$.qtype = ALTQT_CBQ; + $$.data.cbq_opts.flags = $3; + } + | PRIQ { + $$.qtype = ALTQT_PRIQ; + $$.data.priq_opts.flags = 0; + } + | PRIQ '(' priqflags_list ')' { + $$.qtype = ALTQT_PRIQ; + $$.data.priq_opts.flags = $3; + } + | HFSC { + $$.qtype = ALTQT_HFSC; + bzero(&$$.data.hfsc_opts, + sizeof(struct node_hfsc_opts)); + } + | HFSC '(' hfsc_opts ')' { + $$.qtype = ALTQT_HFSC; + $$.data.hfsc_opts = $3; + } + | FAIRQ { + $$.qtype = ALTQT_FAIRQ; + bzero(&$$.data.fairq_opts, + sizeof(struct node_fairq_opts)); + } + | FAIRQ '(' fairq_opts ')' { + $$.qtype = ALTQT_FAIRQ; + $$.data.fairq_opts = $3; + } + | CODEL { + $$.qtype = ALTQT_CODEL; + bzero(&$$.data.codel_opts, + sizeof(struct codel_opts)); + } + | CODEL '(' codel_opts ')' { + $$.qtype = ALTQT_CODEL; + $$.data.codel_opts = $3; + } + ; + +cbqflags_list : cbqflags_item { $$ |= $1; } + | cbqflags_list comma cbqflags_item { $$ |= $3; } + ; + +cbqflags_item : STRING { + if (!strcmp($1, "default")) + $$ = CBQCLF_DEFCLASS; + else if (!strcmp($1, "borrow")) + $$ = CBQCLF_BORROW; + else if (!strcmp($1, "red")) + $$ = CBQCLF_RED; + else if (!strcmp($1, "ecn")) + $$ = CBQCLF_RED|CBQCLF_ECN; + else if (!strcmp($1, "rio")) + $$ = CBQCLF_RIO; + else if (!strcmp($1, "codel")) + $$ = CBQCLF_CODEL; + else { + yyerror("unknown cbq flag \"%s\"", $1); + free($1); + YYERROR; + } + free($1); + } + ; + +priqflags_list : priqflags_item { $$ |= $1; } + | priqflags_list comma priqflags_item { $$ |= $3; } + ; + +priqflags_item : STRING { + if (!strcmp($1, "default")) + $$ = PRCF_DEFAULTCLASS; + else if (!strcmp($1, "red")) + $$ = PRCF_RED; + else if (!strcmp($1, "ecn")) + $$ = PRCF_RED|PRCF_ECN; + else if (!strcmp($1, "rio")) + $$ = PRCF_RIO; + else if (!strcmp($1, "codel")) + $$ = PRCF_CODEL; + else { + yyerror("unknown priq flag \"%s\"", $1); + free($1); + YYERROR; + } + free($1); + } + ; + +hfsc_opts : { + bzero(&hfsc_opts, + sizeof(struct node_hfsc_opts)); + } + hfscopts_list { + $$ = hfsc_opts; + } + ; + +hfscopts_list : hfscopts_item + | hfscopts_list comma hfscopts_item + ; + +hfscopts_item : LINKSHARE bandwidth { + if (hfsc_opts.linkshare.used) { + yyerror("linkshare already specified"); + YYERROR; + } + hfsc_opts.linkshare.m2 = $2; + hfsc_opts.linkshare.used = 1; + } + | LINKSHARE '(' bandwidth comma NUMBER comma bandwidth ')' + { + if ($5 < 0 || $5 > INT_MAX) { + yyerror("timing in curve out of range"); + YYERROR; + } + if (hfsc_opts.linkshare.used) { + yyerror("linkshare already specified"); + YYERROR; + } + hfsc_opts.linkshare.m1 = $3; + hfsc_opts.linkshare.d = $5; + hfsc_opts.linkshare.m2 = $7; + hfsc_opts.linkshare.used = 1; + } + | REALTIME bandwidth { + if (hfsc_opts.realtime.used) { + yyerror("realtime already specified"); + YYERROR; + } + hfsc_opts.realtime.m2 = $2; + hfsc_opts.realtime.used = 1; + } + | REALTIME '(' bandwidth comma NUMBER comma bandwidth ')' + { + if ($5 < 0 || $5 > INT_MAX) { + yyerror("timing in curve out of range"); + YYERROR; + } + if (hfsc_opts.realtime.used) { + yyerror("realtime already specified"); + YYERROR; + } + hfsc_opts.realtime.m1 = $3; + hfsc_opts.realtime.d = $5; + hfsc_opts.realtime.m2 = $7; + hfsc_opts.realtime.used = 1; + } + | UPPERLIMIT bandwidth { + if (hfsc_opts.upperlimit.used) { + yyerror("upperlimit already specified"); + YYERROR; + } + hfsc_opts.upperlimit.m2 = $2; + hfsc_opts.upperlimit.used = 1; + } + | UPPERLIMIT '(' bandwidth comma NUMBER comma bandwidth ')' + { + if ($5 < 0 || $5 > INT_MAX) { + yyerror("timing in curve out of range"); + YYERROR; + } + if (hfsc_opts.upperlimit.used) { + yyerror("upperlimit already specified"); + YYERROR; + } + hfsc_opts.upperlimit.m1 = $3; + hfsc_opts.upperlimit.d = $5; + hfsc_opts.upperlimit.m2 = $7; + hfsc_opts.upperlimit.used = 1; + } + | STRING { + if (!strcmp($1, "default")) + hfsc_opts.flags |= HFCF_DEFAULTCLASS; + else if (!strcmp($1, "red")) + hfsc_opts.flags |= HFCF_RED; + else if (!strcmp($1, "ecn")) + hfsc_opts.flags |= HFCF_RED|HFCF_ECN; + else if (!strcmp($1, "rio")) + hfsc_opts.flags |= HFCF_RIO; + else if (!strcmp($1, "codel")) + hfsc_opts.flags |= HFCF_CODEL; + else { + yyerror("unknown hfsc flag \"%s\"", $1); + free($1); + YYERROR; + } + free($1); + } + ; + +fairq_opts : { + bzero(&fairq_opts, + sizeof(struct node_fairq_opts)); + } + fairqopts_list { + $$ = fairq_opts; + } + ; + +fairqopts_list : fairqopts_item + | fairqopts_list comma fairqopts_item + ; + +fairqopts_item : LINKSHARE bandwidth { + if (fairq_opts.linkshare.used) { + yyerror("linkshare already specified"); + YYERROR; + } + fairq_opts.linkshare.m2 = $2; + fairq_opts.linkshare.used = 1; + } + | LINKSHARE '(' bandwidth number bandwidth ')' { + if (fairq_opts.linkshare.used) { + yyerror("linkshare already specified"); + YYERROR; + } + fairq_opts.linkshare.m1 = $3; + fairq_opts.linkshare.d = $4; + fairq_opts.linkshare.m2 = $5; + fairq_opts.linkshare.used = 1; + } + | HOGS bandwidth { + fairq_opts.hogs_bw = $2; + } + | BUCKETS number { + fairq_opts.nbuckets = $2; + } + | STRING { + if (!strcmp($1, "default")) + fairq_opts.flags |= FARF_DEFAULTCLASS; + else if (!strcmp($1, "red")) + fairq_opts.flags |= FARF_RED; + else if (!strcmp($1, "ecn")) + fairq_opts.flags |= FARF_RED|FARF_ECN; + else if (!strcmp($1, "rio")) + fairq_opts.flags |= FARF_RIO; + else if (!strcmp($1, "codel")) + fairq_opts.flags |= FARF_CODEL; + else { + yyerror("unknown fairq flag \"%s\"", $1); + free($1); + YYERROR; + } + free($1); + } + ; + +codel_opts : { + bzero(&codel_opts, + sizeof(struct codel_opts)); + } + codelopts_list { + $$ = codel_opts; + } + ; + +codelopts_list : codelopts_item + | codelopts_list comma codelopts_item + ; + +codelopts_item : INTERVAL number { + if (codel_opts.interval) { + yyerror("interval already specified"); + YYERROR; + } + codel_opts.interval = $2; + } + | TARGET number { + if (codel_opts.target) { + yyerror("target already specified"); + YYERROR; + } + codel_opts.target = $2; + } + | STRING { + if (!strcmp($1, "ecn")) + codel_opts.ecn = 1; + else { + yyerror("unknown codel option \"%s\"", $1); + free($1); + YYERROR; + } + free($1); + } + ; + +qassign : /* empty */ { $$ = NULL; } + | qassign_item { $$ = $1; } + | '{' optnl qassign_list '}' { $$ = $3; } + ; + +qassign_list : qassign_item optnl { $$ = $1; } + | qassign_list comma qassign_item optnl { + $1->tail->next = $3; + $1->tail = $3; + $$ = $1; + } + ; + +qassign_item : STRING { + $$ = calloc(1, sizeof(struct node_queue)); + if ($$ == NULL) + err(1, "qassign_item: calloc"); + if (strlcpy($$->queue, $1, sizeof($$->queue)) >= + sizeof($$->queue)) { + yyerror("queue name '%s' too long (max " + "%d chars)", $1, sizeof($$->queue)-1); + free($1); + free($$); + YYERROR; + } + free($1); + $$->next = NULL; + $$->tail = $$; + } + ; + +pfrule : action dir logquick interface route af proto fromto + filter_opts + { + struct pfctl_rule r; + struct node_state_opt *o; + struct node_proto *proto; + int srctrack = 0; + int statelock = 0; + int adaptive = 0; + int defaults = 0; + + if (check_rulestate(PFCTL_STATE_FILTER)) + YYERROR; + + pfctl_init_rule(&r); + + r.action = $1.b1; + switch ($1.b2) { + case PFRULE_RETURNRST: + r.rule_flag |= PFRULE_RETURNRST; + r.return_ttl = $1.w; + break; + case PFRULE_RETURNICMP: + r.rule_flag |= PFRULE_RETURNICMP; + r.return_icmp = $1.w; + r.return_icmp6 = $1.w2; + break; + case PFRULE_RETURN: + r.rule_flag |= PFRULE_RETURN; + r.return_icmp = $1.w; + r.return_icmp6 = $1.w2; + break; + } + r.direction = $2; + r.log = $3.log; + r.logif = $3.logif; + r.quick = $3.quick; + r.af = $6; + + if (filteropts_to_rule(&r, &$9)) + YYERROR; + + if ($9.flags.b1 || $9.flags.b2 || $8.src_os) { + for (proto = $7; proto != NULL && + proto->proto != IPPROTO_TCP; + proto = proto->next) + ; /* nothing */ + if (proto == NULL && $7 != NULL) { + if ($9.flags.b1 || $9.flags.b2) + yyerror( + "flags only apply to tcp"); + if ($8.src_os) + yyerror( + "OS fingerprinting only " + "apply to tcp"); + YYERROR; + } + } + + o = $9.keep.options; + + /* 'keep state' by default on pass rules. */ + if (!r.keep_state && !r.action && + !($9.marker & FOM_KEEP)) { + r.keep_state = PF_STATE_NORMAL; + o = keep_state_defaults; + defaults = 1; + } + + while (o) { + struct node_state_opt *p = o; + + switch (o->type) { + case PF_STATE_OPT_MAX: + if (r.max_states) { + yyerror("state option 'max' " + "multiple definitions"); + YYERROR; + } + r.max_states = o->data.max_states; + break; + case PF_STATE_OPT_NOSYNC: + if (r.rule_flag & PFRULE_NOSYNC) { + yyerror("state option 'sync' " + "multiple definitions"); + YYERROR; + } + r.rule_flag |= PFRULE_NOSYNC; + break; + case PF_STATE_OPT_SRCTRACK: + if (srctrack) { + yyerror("state option " + "'source-track' " + "multiple definitions"); + YYERROR; + } + srctrack = o->data.src_track; + r.rule_flag |= PFRULE_SRCTRACK; + break; + case PF_STATE_OPT_MAX_SRC_STATES: + if (r.max_src_states) { + yyerror("state option " + "'max-src-states' " + "multiple definitions"); + YYERROR; + } + if (o->data.max_src_states == 0) { + yyerror("'max-src-states' must " + "be > 0"); + YYERROR; + } + r.max_src_states = + o->data.max_src_states; + r.rule_flag |= PFRULE_SRCTRACK; + break; + case PF_STATE_OPT_OVERLOAD: + if (r.overload_tblname[0]) { + yyerror("multiple 'overload' " + "table definitions"); + YYERROR; + } + if (strlcpy(r.overload_tblname, + o->data.overload.tblname, + PF_TABLE_NAME_SIZE) >= + PF_TABLE_NAME_SIZE) { + yyerror("state option: " + "strlcpy"); + YYERROR; + } + r.flush = o->data.overload.flush; + break; + case PF_STATE_OPT_MAX_SRC_CONN: + if (r.max_src_conn) { + yyerror("state option " + "'max-src-conn' " + "multiple definitions"); + YYERROR; + } + if (o->data.max_src_conn == 0) { + yyerror("'max-src-conn' " + "must be > 0"); + YYERROR; + } + r.max_src_conn = + o->data.max_src_conn; + r.rule_flag |= PFRULE_SRCTRACK | + PFRULE_RULESRCTRACK; + break; + case PF_STATE_OPT_MAX_SRC_CONN_RATE: + if (r.max_src_conn_rate.limit) { + yyerror("state option " + "'max-src-conn-rate' " + "multiple definitions"); + YYERROR; + } + if (!o->data.max_src_conn_rate.limit || + !o->data.max_src_conn_rate.seconds) { + yyerror("'max-src-conn-rate' " + "values must be > 0"); + YYERROR; + } + if (o->data.max_src_conn_rate.limit > + PF_THRESHOLD_MAX) { + yyerror("'max-src-conn-rate' " + "maximum rate must be < %u", + PF_THRESHOLD_MAX); + YYERROR; + } + r.max_src_conn_rate.limit = + o->data.max_src_conn_rate.limit; + r.max_src_conn_rate.seconds = + o->data.max_src_conn_rate.seconds; + r.rule_flag |= PFRULE_SRCTRACK | + PFRULE_RULESRCTRACK; + break; + case PF_STATE_OPT_MAX_SRC_NODES: + if (r.max_src_nodes) { + yyerror("state option " + "'max-src-nodes' " + "multiple definitions"); + YYERROR; + } + if (o->data.max_src_nodes == 0) { + yyerror("'max-src-nodes' must " + "be > 0"); + YYERROR; + } + r.max_src_nodes = + o->data.max_src_nodes; + r.rule_flag |= PFRULE_SRCTRACK | + PFRULE_RULESRCTRACK; + break; + case PF_STATE_OPT_STATELOCK: + if (statelock) { + yyerror("state locking option: " + "multiple definitions"); + YYERROR; + } + statelock = 1; + r.rule_flag |= o->data.statelock; + break; + case PF_STATE_OPT_SLOPPY: + if (r.rule_flag & PFRULE_STATESLOPPY) { + yyerror("state sloppy option: " + "multiple definitions"); + YYERROR; + } + r.rule_flag |= PFRULE_STATESLOPPY; + break; + case PF_STATE_OPT_PFLOW: + if (r.rule_flag & PFRULE_PFLOW) { + yyerror("state pflow option: " + "multiple definitions"); + YYERROR; + } + r.rule_flag |= PFRULE_PFLOW; + break; + case PF_STATE_OPT_ALLOW_RELATED: + if (r.rule_flag & PFRULE_ALLOW_RELATED) { + yyerror("state allow-related option: " + "multiple definitions"); + YYERROR; + } + r.rule_flag |= PFRULE_ALLOW_RELATED; + break; + case PF_STATE_OPT_TIMEOUT: + if (o->data.timeout.number == + PFTM_ADAPTIVE_START || + o->data.timeout.number == + PFTM_ADAPTIVE_END) + adaptive = 1; + if (r.timeout[o->data.timeout.number]) { + yyerror("state timeout %s " + "multiple definitions", + pf_timeouts[o->data. + timeout.number].name); + YYERROR; + } + r.timeout[o->data.timeout.number] = + o->data.timeout.seconds; + } + o = o->next; + if (!defaults) + free(p); + } + + /* 'flags S/SA' by default on stateful rules */ + if (!r.action && !r.flags && !r.flagset && + !$9.fragment && !($9.marker & FOM_FLAGS) && + r.keep_state) { + r.flags = parse_flags("S"); + r.flagset = parse_flags("SA"); + } + if (!adaptive && r.max_states) { + r.timeout[PFTM_ADAPTIVE_START] = + (r.max_states / 10) * 6; + r.timeout[PFTM_ADAPTIVE_END] = + (r.max_states / 10) * 12; + } + if (r.rule_flag & PFRULE_SRCTRACK) { + if (srctrack == PF_SRCTRACK_GLOBAL && + r.max_src_nodes) { + yyerror("'max-src-nodes' is " + "incompatible with " + "'source-track global'"); + YYERROR; + } + if (srctrack == PF_SRCTRACK_GLOBAL && + r.max_src_conn) { + yyerror("'max-src-conn' is " + "incompatible with " + "'source-track global'"); + YYERROR; + } + if (srctrack == PF_SRCTRACK_GLOBAL && + r.max_src_conn_rate.seconds) { + yyerror("'max-src-conn-rate' is " + "incompatible with " + "'source-track global'"); + YYERROR; + } + if (r.timeout[PFTM_SRC_NODE] < + r.max_src_conn_rate.seconds) + r.timeout[PFTM_SRC_NODE] = + r.max_src_conn_rate.seconds; + r.rule_flag |= PFRULE_SRCTRACK; + if (srctrack == PF_SRCTRACK_RULE) + r.rule_flag |= PFRULE_RULESRCTRACK; + } + if (r.keep_state && !statelock) + r.rule_flag |= default_statelock; + + decide_address_family($8.src.host, &r.af); + decide_address_family($8.dst.host, &r.af); + + if ($5.rt) { + if (!r.direction) { + yyerror("direction must be explicit " + "with rules that specify routing"); + YYERROR; + } + r.rt = $5.rt; + + if (!($5.redirspec->pool_opts.opts & PF_POOL_IPV6NH)) { + decide_address_family($5.redirspec->host, &r.af); + if (!(r.rule_flag & PFRULE_AFTO)) + remove_invalid_hosts(&($5.redirspec->host), &r.af); + if ($5.redirspec->host == NULL) { + yyerror("no routing address with " + "matching address family found."); + YYERROR; + } + } + } +#ifdef __FreeBSD__ + r.divert.port = $9.divert.port; +#else + if ((r.divert.port = $9.divert.port)) { + if (r.direction == PF_OUT) { + if ($9.divert.addr) { + yyerror("address specified " + "for outgoing divert"); + YYERROR; + } + bzero(&r.divert.addr, + sizeof(r.divert.addr)); + } else { + if (!$9.divert.addr) { + yyerror("no address specified " + "for incoming divert"); + YYERROR; + } + if ($9.divert.addr->af != r.af) { + yyerror("address family " + "mismatch for divert"); + YYERROR; + } + r.divert.addr = + $9.divert.addr->addr.v.a.addr; + } + } +#endif + + if ($9.dnpipe || $9.dnrpipe) { + r.dnpipe = $9.dnpipe; + r.dnrpipe = $9.dnrpipe; + if ($9.free_flags & PFRULE_DN_IS_PIPE) + r.free_flags |= PFRULE_DN_IS_PIPE; + else + r.free_flags |= PFRULE_DN_IS_QUEUE; + } + + if ($9.marker & FOM_AFTO) { + r.naf = $9.nat->af; + } else { + if ($9.nat) { + if (!r.af && ! $9.nat->host->ifindex) + r.af = $9.nat->host->af; + remove_invalid_hosts(&($9.nat->host), &r.af); + if (invalid_redirect($9.nat->host, r.af)) + YYERROR; + if ($9.nat->host->addr.type == PF_ADDR_DYNIFTL) { + if (($9.nat->host = gen_dynnode($9.nat->host, r.af)) == NULL) + err(1, "calloc"); + } + if (check_netmask($9.nat->host, r.af)) + YYERROR; + } + if ($9.rdr) { + if (!r.af && ! $9.rdr->host->ifindex) + r.af = $9.rdr->host->af; + remove_invalid_hosts(&($9.rdr->host), &r.af); + if (invalid_redirect($9.rdr->host, r.af)) + YYERROR; + if ($9.rdr->host->addr.type == PF_ADDR_DYNIFTL) { + if (($9.rdr->host = gen_dynnode($9.rdr->host, r.af)) == NULL) + err(1, "calloc"); + } + if (check_netmask($9.rdr->host, r.af)) + YYERROR; + } + } + + expand_rule(&r, false, $4, $9.nat, $9.rdr, $5.redirspec, + $7, $8.src_os, $8.src.host, $8.src.port, $8.dst.host, + $8.dst.port, $9.uid, $9.gid, $9.rcv, $9.icmpspec); + } + ; + +filter_opts : { + bzero(&filter_opts, sizeof filter_opts); + filter_opts.rtableid = -1; + } + filter_opts_l + { $$ = filter_opts; } + | /* empty */ { + bzero(&filter_opts, sizeof filter_opts); + filter_opts.rtableid = -1; + $$ = filter_opts; + } + ; + +filter_opts_l : filter_opts_l filter_opt + | filter_opt + ; + +filter_opt : USER uids { + if (filter_opts.uid) + $2->tail->next = filter_opts.uid; + filter_opts.uid = $2; + } + | GROUP gids { + if (filter_opts.gid) + $2->tail->next = filter_opts.gid; + filter_opts.gid = $2; + } + | flags { + if (filter_opts.marker & FOM_FLAGS) { + yyerror("flags cannot be redefined"); + YYERROR; + } + filter_opts.marker |= FOM_FLAGS; + filter_opts.flags.b1 |= $1.b1; + filter_opts.flags.b2 |= $1.b2; + filter_opts.flags.w |= $1.w; + filter_opts.flags.w2 |= $1.w2; + } + | icmpspec { + if (filter_opts.marker & FOM_ICMP) { + yyerror("icmp-type cannot be redefined"); + YYERROR; + } + filter_opts.marker |= FOM_ICMP; + filter_opts.icmpspec = $1; + } + | PRIO NUMBER { + if (filter_opts.marker & FOM_PRIO) { + yyerror("prio cannot be redefined"); + YYERROR; + } + if ($2 < 0 || $2 > PF_PRIO_MAX) { + yyerror("prio must be 0 - %u", PF_PRIO_MAX); + YYERROR; + } + filter_opts.marker |= FOM_PRIO; + filter_opts.prio = $2; + } + | TOS tos { + if (filter_opts.marker & FOM_TOS) { + yyerror("tos cannot be redefined"); + YYERROR; + } + filter_opts.marker |= FOM_TOS; + filter_opts.tos = $2; + } + | keep { + if (filter_opts.marker & FOM_KEEP) { + yyerror("modulate or keep cannot be redefined"); + YYERROR; + } + filter_opts.marker |= FOM_KEEP; + filter_opts.keep.action = $1.action; + filter_opts.keep.options = $1.options; + } + | RIDENTIFIER number { + filter_opts.ridentifier = $2; + } + | FRAGMENT { + filter_opts.fragment = 1; + } + | ALLOWOPTS { + filter_opts.allowopts = 1; + } + | label { + if (filter_opts.labelcount >= PF_RULE_MAX_LABEL_COUNT) { + yyerror("label can only be used %d times", PF_RULE_MAX_LABEL_COUNT); + YYERROR; + } + filter_opts.label[filter_opts.labelcount++] = $1; + } + | qname { + if (filter_opts.queues.qname) { + yyerror("queue cannot be redefined"); + YYERROR; + } + filter_opts.queues = $1; + } + | DNPIPE number { + filter_opts.dnpipe = $2; + filter_opts.free_flags |= PFRULE_DN_IS_PIPE; + } + | DNPIPE '(' number ')' { + filter_opts.dnpipe = $3; + filter_opts.free_flags |= PFRULE_DN_IS_PIPE; + } + | DNPIPE '(' number comma number ')' { + filter_opts.dnrpipe = $5; + filter_opts.dnpipe = $3; + filter_opts.free_flags |= PFRULE_DN_IS_PIPE; + } + | DNQUEUE number { + filter_opts.dnpipe = $2; + filter_opts.free_flags |= PFRULE_DN_IS_QUEUE; + } + | DNQUEUE '(' number comma number ')' { + filter_opts.dnrpipe = $5; + filter_opts.dnpipe = $3; + filter_opts.free_flags |= PFRULE_DN_IS_QUEUE; + } + | DNQUEUE '(' number ')' { + filter_opts.dnpipe = $3; + filter_opts.free_flags |= PFRULE_DN_IS_QUEUE; + } + | TAG string { + filter_opts.tag = $2; + } + | not TAGGED string { + filter_opts.match_tag = $3; + filter_opts.match_tag_not = $1; + } + | not RECEIVEDON if_item { + if (filter_opts.rcv) { + yyerror("cannot respecify received-on"); + YYERROR; + } + filter_opts.rcv = $3; + filter_opts.rcv->not = $1; + } + | PROBABILITY probability { + double p; + + p = floor($2 * UINT_MAX + 0.5); + if (p < 0.0 || p > UINT_MAX) { + yyerror("invalid probability: %lf", p); + YYERROR; + } + filter_opts.prob = (u_int32_t)p; + if (filter_opts.prob == 0) + filter_opts.prob = 1; + } + | RTABLE NUMBER { + if ($2 < 0 || $2 > rt_tableid_max()) { + yyerror("invalid rtable id"); + YYERROR; + } + filter_opts.rtableid = $2; + } + | DIVERTTO portplain { +#ifdef __FreeBSD__ + filter_opts.divert.port = $2.a; + if (!filter_opts.divert.port) { + yyerror("invalid divert port: %u", ntohs($2.a)); + YYERROR; + } +#endif + } + | DIVERTTO STRING PORT portplain { +#ifndef __FreeBSD__ + if ((filter_opts.divert.addr = host($2, pf->opts)) == NULL) { + yyerror("could not parse divert address: %s", + $2); + free($2); + YYERROR; + } +#else + if ($2) +#endif + free($2); + filter_opts.divert.port = $4.a; + if (!filter_opts.divert.port) { + yyerror("invalid divert port: %u", ntohs($4.a)); + YYERROR; + } + } + | DIVERTREPLY { +#ifdef __FreeBSD__ + yyerror("divert-reply has no meaning in FreeBSD pf(4)"); + YYERROR; +#else + filter_opts.divert.port = 1; /* some random value */ +#endif + } + | SCRUB '(' scrub_opts ')' { + filter_opts.nodf = $3.nodf; + filter_opts.minttl = $3.minttl; + if ($3.marker & FOM_SETTOS) { + /* Old style rules are "scrub set-tos 0x42" + * New style are "set tos 0x42 scrub (...)" + * What is in "scrub(...)"" is unfortunately the + * original scrub syntax so it would overwrite + * "set tos" of a pass/match rule. + */ + filter_opts.settos = $3.settos; + } + filter_opts.randomid = $3.randomid; + filter_opts.max_mss = $3.maxmss; + if ($3.reassemble_tcp) + filter_opts.marker |= FOM_SCRUB_TCP; + filter_opts.marker |= $3.marker; + } + | NATTO port_redirspec { + if (filter_opts.nat) { + yyerror("cannot respecify nat-to/binat-to"); + YYERROR; + } + filter_opts.nat = $2; + } + | RDRTO port_redirspec { + if (filter_opts.rdr) { + yyerror("cannot respecify rdr-to"); + YYERROR; + } + filter_opts.rdr = $2; + } + | BINATTO port_redirspec { + if (filter_opts.nat) { + yyerror("cannot respecify nat-to/binat-to"); + YYERROR; + } + filter_opts.nat = $2; + filter_opts.nat->binat = 1; + filter_opts.nat->pool_opts.staticport = 1; + } + | AFTO af FROM port_redirspec { + if (filter_opts.nat) { + yyerror("cannot respecify af-to"); + YYERROR; + } + if ($2 == 0) { + yyerror("no address family specified"); + YYERROR; + } + + filter_opts.nat = $4; + filter_opts.nat->af = $2; + remove_invalid_hosts(&($4->host), &(filter_opts.nat->af)); + if ($4->host == NULL) { + yyerror("af-to addresses must be in the " + "target address family"); + YYERROR; + } + filter_opts.marker |= FOM_AFTO; + } + | AFTO af FROM port_redirspec TO port_redirspec { + if (filter_opts.nat) { + yyerror("cannot respecify af-to"); + YYERROR; + } + if ($2 == 0) { + yyerror("no address family specified"); + YYERROR; + } + filter_opts.nat = $4; + filter_opts.nat->af = $2; + filter_opts.rdr = $6; + filter_opts.rdr->af = $2; + remove_invalid_hosts(&($4->host), &(filter_opts.nat->af)); + remove_invalid_hosts(&($6->host), &(filter_opts.rdr->af)); + if ($4->host == NULL || $6->host == NULL) { + yyerror("af-to addresses must be in the " + "target address family"); + YYERROR; + } + filter_opts.marker |= FOM_AFTO; + } + | MAXPKTRATE NUMBER '/' NUMBER { + if ($2 < 0 || $2 > UINT_MAX || + $4 < 0 || $4 > UINT_MAX) { + yyerror("only positive values permitted"); + YYERROR; + } + if (filter_opts.pktrate.limit) { + yyerror("cannot respecify max-pkt-rate"); + YYERROR; + } + filter_opts.pktrate.limit = $2; + filter_opts.pktrate.seconds = $4; + } + | MAXPKTSIZE NUMBER { + if ($2 < 0 || $2 > UINT16_MAX) { + yyerror("only positive values permitted"); + YYERROR; + } + filter_opts.max_pkt_size = $2; + } + | ONCE { + filter_opts.marker |= FOM_ONCE; + } + | filter_sets + ; + +filter_sets : SET '(' filter_sets_l ')' { $$ = filter_opts; } + | SET filter_set { $$ = filter_opts; } + ; + +filter_sets_l : filter_sets_l comma filter_set + | filter_set + ; + +filter_set : prio { + if (filter_opts.marker & FOM_SETPRIO) { + yyerror("prio cannot be redefined"); + YYERROR; + } + filter_opts.marker |= FOM_SETPRIO; + filter_opts.set_prio[0] = $1.b1; + filter_opts.set_prio[1] = $1.b2; + } + | TOS tos { + if (filter_opts.marker & FOM_SETTOS) { + yyerror("tos cannot be respecified"); + YYERROR; + } + filter_opts.marker |= FOM_SETTOS; + filter_opts.settos = $2; + } +prio : PRIO NUMBER { + if ($2 < 0 || $2 > PF_PRIO_MAX) { + yyerror("prio must be 0 - %u", PF_PRIO_MAX); + YYERROR; + } + $$.b1 = $$.b2 = $2; + } + | PRIO '(' NUMBER comma NUMBER ')' { + if ($3 < 0 || $3 > PF_PRIO_MAX || + $5 < 0 || $5 > PF_PRIO_MAX) { + yyerror("prio must be 0 - %u", PF_PRIO_MAX); + YYERROR; + } + $$.b1 = $3; + $$.b2 = $5; + } + ; + +probability : STRING { + char *e; + double p = strtod($1, &e); + + if (*e == '%') { + p *= 0.01; + e++; + } + if (*e) { + yyerror("invalid probability: %s", $1); + free($1); + YYERROR; + } + free($1); + $$ = p; + } + | NUMBER { + $$ = (double)$1; + } + ; + + +action : PASS { + $$.b1 = PF_PASS; + $$.b2 = failpolicy; + $$.w = returnicmpdefault; + $$.w2 = returnicmp6default; + } + | MATCH { $$.b1 = PF_MATCH; $$.b2 = $$.w = 0; } + | BLOCK blockspec { $$ = $2; $$.b1 = PF_DROP; } + ; + +blockspec : /* empty */ { + $$.b2 = blockpolicy; + $$.w = returnicmpdefault; + $$.w2 = returnicmp6default; + } + | DROP { + $$.b2 = PFRULE_DROP; + $$.w = 0; + $$.w2 = 0; + } + | RETURNRST { + $$.b2 = PFRULE_RETURNRST; + $$.w = 0; + $$.w2 = 0; + } + | RETURNRST '(' TTL NUMBER ')' { + if ($4 < 0 || $4 > 255) { + yyerror("illegal ttl value %d", $4); + YYERROR; + } + $$.b2 = PFRULE_RETURNRST; + $$.w = $4; + $$.w2 = 0; + } + | RETURNICMP { + $$.b2 = PFRULE_RETURNICMP; + $$.w = returnicmpdefault; + $$.w2 = returnicmp6default; + } + | RETURNICMP6 { + $$.b2 = PFRULE_RETURNICMP; + $$.w = returnicmpdefault; + $$.w2 = returnicmp6default; + } + | RETURNICMP '(' reticmpspec ')' { + $$.b2 = PFRULE_RETURNICMP; + $$.w = $3; + $$.w2 = returnicmpdefault; + } + | RETURNICMP6 '(' reticmp6spec ')' { + $$.b2 = PFRULE_RETURNICMP; + $$.w = returnicmpdefault; + $$.w2 = $3; + } + | RETURNICMP '(' reticmpspec comma reticmp6spec ')' { + $$.b2 = PFRULE_RETURNICMP; + $$.w = $3; + $$.w2 = $5; + } + | RETURN { + $$.b2 = PFRULE_RETURN; + $$.w = returnicmpdefault; + $$.w2 = returnicmp6default; + } + ; + +reticmpspec : STRING { + if (!($$ = parseicmpspec($1, AF_INET))) { + free($1); + YYERROR; + } + free($1); + } + | NUMBER { + u_int8_t icmptype; + + if ($1 < 0 || $1 > 255) { + yyerror("invalid icmp code %lu", $1); + YYERROR; + } + icmptype = returnicmpdefault >> 8; + $$ = (icmptype << 8 | $1); + } + ; + +reticmp6spec : STRING { + if (!($$ = parseicmpspec($1, AF_INET6))) { + free($1); + YYERROR; + } + free($1); + } + | NUMBER { + u_int8_t icmptype; + + if ($1 < 0 || $1 > 255) { + yyerror("invalid icmp code %lu", $1); + YYERROR; + } + icmptype = returnicmp6default >> 8; + $$ = (icmptype << 8 | $1); + } + ; + +dir : /* empty */ { $$ = PF_INOUT; } + | IN { $$ = PF_IN; } + | OUT { $$ = PF_OUT; } + ; + +quick : /* empty */ { $$.quick = 0; } + | QUICK { $$.quick = 1; } + ; + +logquick : /* empty */ { $$.log = 0; $$.quick = 0; $$.logif = 0; } + | log { $$ = $1; $$.quick = 0; } + | QUICK { $$.quick = 1; $$.log = 0; $$.logif = 0; } + | log QUICK { $$ = $1; $$.quick = 1; } + | QUICK log { $$ = $2; $$.quick = 1; } + ; + +log : LOG { $$.log = PF_LOG; $$.logif = 0; } + | LOG '(' logopts ')' { + $$.log = PF_LOG | $3.log; + $$.logif = $3.logif; + } + ; + +logopts : logopt { $$ = $1; } + | logopts comma logopt { + $$.log = $1.log | $3.log; + $$.logif = $3.logif; + if ($$.logif == 0) + $$.logif = $1.logif; + } + ; + +logopt : ALL { $$.log = PF_LOG_ALL; $$.logif = 0; } + | MATCHES { $$.log = PF_LOG_MATCHES; $$.logif = 0; } + | USER { $$.log = PF_LOG_USER; $$.logif = 0; } + | TO string { + const char *errstr; + u_int i; + + $$.log = 0; + if (strncmp($2, "pflog", 5)) { + yyerror("%s: should be a pflog interface", $2); + free($2); + YYERROR; + } + i = strtonum($2 + 5, 0, 255, &errstr); + if (errstr) { + yyerror("%s: %s", $2, errstr); + free($2); + YYERROR; + } + free($2); + $$.logif = i; + } + ; + +interface : /* empty */ { $$ = NULL; } + | ON if_item_not { $$ = $2; } + | ON '{' optnl if_list '}' { $$ = $4; } + ; + +if_list : if_item_not optnl { $$ = $1; } + | if_list comma if_item_not optnl { + $1->tail->next = $3; + $1->tail = $3; + $$ = $1; + } + ; + +if_item_not : not if_item { $$ = $2; $$->not = $1; } + ; + +if_item : STRING { + struct node_host *n; + + $$ = calloc(1, sizeof(struct node_if)); + if ($$ == NULL) + err(1, "if_item: calloc"); + if (strlcpy($$->ifname, $1, sizeof($$->ifname)) >= + sizeof($$->ifname)) { + free($1); + free($$); + yyerror("interface name too long"); + YYERROR; + } + + if ((n = ifa_exists($1)) != NULL) + $$->ifa_flags = n->ifa_flags; + + free($1); + $$->not = 0; + $$->next = NULL; + $$->tail = $$; + } + | ANY { + $$ = calloc(1, sizeof(struct node_if)); + if ($$ == NULL) + err(1, "if_item: calloc"); + strlcpy($$->ifname, "any", sizeof($$->ifname)); + $$->not = 0; + $$->next = NULL; + $$->tail = $$; + } + ; + +af : /* empty */ { $$ = 0; } + | INET { $$ = AF_INET; } + | INET6 { $$ = AF_INET6; } + ; + +etherproto : /* empty */ { $$ = NULL; } + | PROTO etherproto_item { $$ = $2; } + | PROTO '{' optnl etherproto_list '}' { $$ = $4; } + ; + +etherproto_list : etherproto_item optnl { $$ = $1; } + | etherproto_list comma etherproto_item optnl { + $1->tail->next = $3; + $1->tail = $3; + $$ = $1; + } + ; + +etherproto_item : etherprotoval { + u_int16_t pr; + + pr = (u_int16_t)$1; + if (pr == 0) { + yyerror("proto 0 cannot be used"); + YYERROR; + } + $$ = calloc(1, sizeof(struct node_proto)); + if ($$ == NULL) + err(1, "proto_item: calloc"); + $$->proto = pr; + $$->next = NULL; + $$->tail = $$; + } + ; + +etherprotoval : NUMBER { + if ($1 < 0 || $1 > 65565) { + yyerror("protocol outside range"); + YYERROR; + } + } + | STRING + { + if (!strncmp($1, "0x", 2)) { + if (sscanf($1, "0x%4x", &$$) != 1) { + free($1); + yyerror("invalid EtherType hex"); + YYERROR; + } + } else { + yyerror("Symbolic EtherType not yet supported"); + } + } + ; + +proto : /* empty */ { $$ = NULL; } + | PROTO proto_item { $$ = $2; } + | PROTO '{' optnl proto_list '}' { $$ = $4; } + ; + +proto_list : proto_item optnl { $$ = $1; } + | proto_list comma proto_item optnl { + $1->tail->next = $3; + $1->tail = $3; + $$ = $1; + } + ; + +proto_item : protoval { + u_int8_t pr; + + pr = (u_int8_t)$1; + if (pr == 0) { + yyerror("proto 0 cannot be used"); + YYERROR; + } + $$ = calloc(1, sizeof(struct node_proto)); + if ($$ == NULL) + err(1, "proto_item: calloc"); + $$->proto = pr; + $$->next = NULL; + $$->tail = $$; + } + ; + +protoval : STRING { + struct protoent *p; + + p = getprotobyname($1); + if (p == NULL) { + yyerror("unknown protocol %s", $1); + free($1); + YYERROR; + } + $$ = p->p_proto; + free($1); + } + | NUMBER { + if ($1 < 0 || $1 > 255) { + yyerror("protocol outside range"); + YYERROR; + } + } + ; + +l3fromto : /* empty */ { + bzero(&$$, sizeof($$)); + } + | L3 fromto { + if ($2.src.host != NULL && + $2.src.host->addr.type != PF_ADDR_ADDRMASK && + $2.src.host->addr.type != PF_ADDR_TABLE) { + yyerror("from must be an address or table"); + YYERROR; + } + if ($2.dst.host != NULL && + $2.dst.host->addr.type != PF_ADDR_ADDRMASK && + $2.dst.host->addr.type != PF_ADDR_TABLE) { + yyerror("to must be an address or table"); + YYERROR; + } + $$ = $2; + } + ; +etherfromto : ALL { + $$.src = NULL; + $$.dst = NULL; + } + | etherfrom etherto { + $$.src = $1.mac; + $$.dst = $2.mac; + } + ; + +etherfrom : /* emtpy */ { + bzero(&$$, sizeof($$)); + } + | FROM macspec { + $$.mac = $2; + } + ; + +etherto : /* empty */ { + bzero(&$$, sizeof($$)); + } + | TO macspec { + $$.mac = $2; + } + ; + +mac : string '/' NUMBER { + $$ = node_mac_from_string_masklen($1, $3); + free($1); + if ($$ == NULL) + YYERROR; + } + | string { + if (strchr($1, '&')) { + /* mac&mask */ + char *mac = strtok($1, "&"); + char *mask = strtok(NULL, "&"); + $$ = node_mac_from_string_mask(mac, mask); + } else { + $$ = node_mac_from_string($1); + } + free($1); + if ($$ == NULL) + YYERROR; + + } +xmac : not mac { + struct node_mac *n; + + for (n = $2; n != NULL; n = n->next) + n->neg = $1; + $$ = $2; + } + ; +macspec : xmac { + $$ = $1; + } + | '{' optnl mac_list '}' + { + $$ = $3; + } + ; +mac_list : xmac optnl { + $$ = $1; + } + | mac_list comma xmac { + if ($3 == NULL) + $$ = $1; + else if ($1 == NULL) + $$ = $3; + else { + $1->tail->next = $3; + $1->tail = $3->tail; + $$ = $1; + } + } + +fromto : ALL { + $$.src.host = NULL; + $$.src.port = NULL; + $$.dst.host = NULL; + $$.dst.port = NULL; + $$.src_os = NULL; + } + | from os to { + $$.src = $1; + $$.src_os = $2; + $$.dst = $3; + } + ; + +os : /* empty */ { $$ = NULL; } + | OS xos { $$ = $2; } + | OS '{' optnl os_list '}' { $$ = $4; } + ; + +xos : STRING { + $$ = calloc(1, sizeof(struct node_os)); + if ($$ == NULL) + err(1, "os: calloc"); + $$->os = $1; + $$->tail = $$; + } + ; + +os_list : xos optnl { $$ = $1; } + | os_list comma xos optnl { + $1->tail->next = $3; + $1->tail = $3; + $$ = $1; + } + ; + +from : /* empty */ { + $$.host = NULL; + $$.port = NULL; + } + | FROM ipportspec { + $$ = $2; + } + ; + +to : /* empty */ { + $$.host = NULL; + $$.port = NULL; + } + | TO ipportspec { + if (disallow_urpf_failed($2.host, "\"urpf-failed\" is " + "not permitted in a destination address")) + YYERROR; + $$ = $2; + } + ; + +ipportspec : ipspec { + $$.host = $1; + $$.port = NULL; + } + | ipspec PORT portspec { + $$.host = $1; + $$.port = $3; + } + | PORT portspec { + $$.host = NULL; + $$.port = $2; + } + ; + +optnl : '\n' optnl + | + ; + +ipspec : ANY { $$ = NULL; } + | xhost { $$ = $1; } + | '{' optnl host_list '}' { $$ = $3; } + ; + +toipspec : TO ipspec { $$ = $2; } + | /* empty */ { $$ = NULL; } + ; + +host_list : ipspec optnl { $$ = $1; } + | host_list comma ipspec optnl { + if ($1 == NULL) { + freehostlist($3); + $$ = $1; + } else if ($3 == NULL) { + freehostlist($1); + $$ = $3; + } else { + $1->tail->next = $3; + $1->tail = $3->tail; + $$ = $1; + } + } + ; + +xhost : not host { + struct node_host *n; + + for (n = $2; n != NULL; n = n->next) + n->not = $1; + $$ = $2; + } + | not NOROUTE { + $$ = calloc(1, sizeof(struct node_host)); + if ($$ == NULL) + err(1, "xhost: calloc"); + $$->addr.type = PF_ADDR_NOROUTE; + $$->next = NULL; + $$->not = $1; + $$->tail = $$; + } + | not URPFFAILED { + $$ = calloc(1, sizeof(struct node_host)); + if ($$ == NULL) + err(1, "xhost: calloc"); + $$->addr.type = PF_ADDR_URPFFAILED; + $$->next = NULL; + $$->not = $1; + $$->tail = $$; + } + ; + +host : STRING { + if (($$ = host($1, pf->opts)) == NULL) { + /* error. "any" is handled elsewhere */ + free($1); + yyerror("could not parse host specification"); + YYERROR; + } + free($1); + + } + | STRING '-' STRING { + struct node_host *b, *e; + + if ((b = host($1, pf->opts)) == NULL || + (e = host($3, pf->opts)) == NULL) { + free($1); + free($3); + yyerror("could not parse host specification"); + YYERROR; + } + if (b->af != e->af || + b->addr.type != PF_ADDR_ADDRMASK || + e->addr.type != PF_ADDR_ADDRMASK || + unmask(&b->addr.v.a.mask) != + (b->af == AF_INET ? 32 : 128) || + unmask(&e->addr.v.a.mask) != + (e->af == AF_INET ? 32 : 128) || + b->next != NULL || b->not || + e->next != NULL || e->not) { + free(b); + free(e); + free($1); + free($3); + yyerror("invalid address range"); + YYERROR; + } + memcpy(&b->addr.v.a.mask, &e->addr.v.a.addr, + sizeof(b->addr.v.a.mask)); + b->addr.type = PF_ADDR_RANGE; + $$ = b; + free(e); + free($1); + free($3); + } + | STRING '/' NUMBER { + char *buf; + + if (asprintf(&buf, "%s/%lld", $1, (long long)$3) == -1) + err(1, "host: asprintf"); + free($1); + if (($$ = host(buf, pf->opts)) == NULL) { + /* error. "any" is handled elsewhere */ + free(buf); + yyerror("could not parse host specification"); + YYERROR; + } + free(buf); + } + | NUMBER '/' NUMBER { + char *buf; + + /* ie. for 10/8 parsing */ +#ifdef __FreeBSD__ + if (asprintf(&buf, "%lld/%lld", (long long)$1, (long long)$3) == -1) +#else + if (asprintf(&buf, "%lld/%lld", $1, $3) == -1) +#endif + err(1, "host: asprintf"); + if (($$ = host(buf, pf->opts)) == NULL) { + /* error. "any" is handled elsewhere */ + free(buf); + yyerror("could not parse host specification"); + YYERROR; + } + free(buf); + } + | dynaddr + | dynaddr '/' NUMBER { + struct node_host *n; + + if ($3 < 0 || $3 > 128) { + yyerror("bit number too big"); + YYERROR; + } + $$ = $1; + for (n = $1; n != NULL; n = n->next) + set_ipmask(n, $3); + } + | '<' STRING '>' { + if (strlen($2) >= PF_TABLE_NAME_SIZE) { + yyerror("table name '%s' too long", $2); + free($2); + YYERROR; + } + $$ = calloc(1, sizeof(struct node_host)); + if ($$ == NULL) + err(1, "host: calloc"); + $$->addr.type = PF_ADDR_TABLE; + if (strlcpy($$->addr.v.tblname, $2, + sizeof($$->addr.v.tblname)) >= + sizeof($$->addr.v.tblname)) + errx(1, "host: strlcpy"); + free($2); + $$->next = NULL; + $$->tail = $$; + } + ; + +number : NUMBER + | STRING { + u_long ulval; + + if (atoul($1, &ulval) == -1) { + yyerror("%s is not a number", $1); + free($1); + YYERROR; + } else + $$ = ulval; + free($1); + } + ; + +dynaddr : '(' STRING ')' { + int flags = 0; + char *p, *op; + + op = $2; + if (!isalpha(op[0])) { + yyerror("invalid interface name '%s'", op); + free(op); + YYERROR; + } + while ((p = strrchr($2, ':')) != NULL) { + if (!strcmp(p+1, "network")) + flags |= PFI_AFLAG_NETWORK; + else if (!strcmp(p+1, "broadcast")) + flags |= PFI_AFLAG_BROADCAST; + else if (!strcmp(p+1, "peer")) + flags |= PFI_AFLAG_PEER; + else if (!strcmp(p+1, "0")) + flags |= PFI_AFLAG_NOALIAS; + else { + yyerror("interface %s has bad modifier", + $2); + free(op); + YYERROR; + } + *p = '\0'; + } + if (flags & (flags - 1) & PFI_AFLAG_MODEMASK) { + free(op); + yyerror("illegal combination of " + "interface modifiers"); + YYERROR; + } + $$ = calloc(1, sizeof(struct node_host)); + if ($$ == NULL) + err(1, "address: calloc"); + $$->af = 0; + set_ipmask($$, 128); + $$->addr.type = PF_ADDR_DYNIFTL; + $$->addr.iflags = flags; + if (strlcpy($$->addr.v.ifname, $2, + sizeof($$->addr.v.ifname)) >= + sizeof($$->addr.v.ifname)) { + free(op); + free($$); + yyerror("interface name too long"); + YYERROR; + } + free(op); + $$->next = NULL; + $$->tail = $$; + } + ; + +portspec : port_item { $$ = $1; } + | '{' optnl port_list '}' { $$ = $3; } + ; + +port_list : port_item optnl { $$ = $1; } + | port_list comma port_item optnl { + $1->tail->next = $3; + $1->tail = $3; + $$ = $1; + } + ; + +port_item : portrange { + $$ = calloc(1, sizeof(struct node_port)); + if ($$ == NULL) + err(1, "port_item: calloc"); + $$->port[0] = $1.a; + $$->port[1] = $1.b; + if ($1.t) { + $$->op = PF_OP_RRG; + if (validate_range($$->op, $$->port[0], + $$->port[1])) { + yyerror("invalid port range"); + YYERROR; + } + } else + $$->op = PF_OP_EQ; + $$->next = NULL; + $$->tail = $$; + } + | unaryop portrange { + if ($2.t) { + yyerror("':' cannot be used with an other " + "port operator"); + YYERROR; + } + $$ = calloc(1, sizeof(struct node_port)); + if ($$ == NULL) + err(1, "port_item: calloc"); + $$->port[0] = $2.a; + $$->port[1] = $2.b; + $$->op = $1; + if (validate_range($$->op, $$->port[0], $$->port[1])) { + yyerror("invalid port range"); + YYERROR; + } + $$->next = NULL; + $$->tail = $$; + } + | portrange PORTBINARY portrange { + if ($1.t || $3.t) { + yyerror("':' cannot be used with an other " + "port operator"); + YYERROR; + } + $$ = calloc(1, sizeof(struct node_port)); + if ($$ == NULL) + err(1, "port_item: calloc"); + $$->port[0] = $1.a; + $$->port[1] = $3.a; + $$->op = $2; + if (validate_range($$->op, $$->port[0], $$->port[1])) { + yyerror("invalid port range"); + YYERROR; + } + $$->next = NULL; + $$->tail = $$; + } + ; + +portplain : numberstring { + if (parseport($1, &$$, 0) == -1) { + free($1); + YYERROR; + } + free($1); + } + ; + +portrange : numberstring { + if (parseport($1, &$$, PPORT_RANGE) == -1) { + free($1); + YYERROR; + } + free($1); + } + ; + +uids : uid_item { $$ = $1; } + | '{' optnl uid_list '}' { $$ = $3; } + ; + +uid_list : uid_item optnl { $$ = $1; } + | uid_list comma uid_item optnl { + $1->tail->next = $3; + $1->tail = $3; + $$ = $1; + } + ; + +uid_item : uid { + $$ = calloc(1, sizeof(struct node_uid)); + if ($$ == NULL) + err(1, "uid_item: calloc"); + $$->uid[0] = $1; + $$->uid[1] = $1; + $$->op = PF_OP_EQ; + $$->next = NULL; + $$->tail = $$; + } + | unaryop uid { + if ($2 == -1 && $1 != PF_OP_EQ && $1 != PF_OP_NE) { + yyerror("user unknown requires operator = or " + "!="); + YYERROR; + } + $$ = calloc(1, sizeof(struct node_uid)); + if ($$ == NULL) + err(1, "uid_item: calloc"); + $$->uid[0] = $2; + $$->uid[1] = $2; + $$->op = $1; + $$->next = NULL; + $$->tail = $$; + } + | uid PORTBINARY uid { + if ($1 == -1 || $3 == -1) { + yyerror("user unknown requires operator = or " + "!="); + YYERROR; + } + $$ = calloc(1, sizeof(struct node_uid)); + if ($$ == NULL) + err(1, "uid_item: calloc"); + $$->uid[0] = $1; + $$->uid[1] = $3; + $$->op = $2; + $$->next = NULL; + $$->tail = $$; + } + ; + +uid : STRING { + if (!strcmp($1, "unknown")) + $$ = -1; + else { + uid_t uid; + + if (uid_from_user($1, &uid) == -1) { + yyerror("unknown user %s", $1); + free($1); + YYERROR; + } + $$ = uid; + } + free($1); + } + | NUMBER { + if ($1 < 0 || $1 >= UID_MAX) { + yyerror("illegal uid value %lu", $1); + YYERROR; + } + $$ = $1; + } + ; + +gids : gid_item { $$ = $1; } + | '{' optnl gid_list '}' { $$ = $3; } + ; + +gid_list : gid_item optnl { $$ = $1; } + | gid_list comma gid_item optnl { + $1->tail->next = $3; + $1->tail = $3; + $$ = $1; + } + ; + +gid_item : gid { + $$ = calloc(1, sizeof(struct node_gid)); + if ($$ == NULL) + err(1, "gid_item: calloc"); + $$->gid[0] = $1; + $$->gid[1] = $1; + $$->op = PF_OP_EQ; + $$->next = NULL; + $$->tail = $$; + } + | unaryop gid { + if ($2 == -1 && $1 != PF_OP_EQ && $1 != PF_OP_NE) { + yyerror("group unknown requires operator = or " + "!="); + YYERROR; + } + $$ = calloc(1, sizeof(struct node_gid)); + if ($$ == NULL) + err(1, "gid_item: calloc"); + $$->gid[0] = $2; + $$->gid[1] = $2; + $$->op = $1; + $$->next = NULL; + $$->tail = $$; + } + | gid PORTBINARY gid { + if ($1 == -1 || $3 == -1) { + yyerror("group unknown requires operator = or " + "!="); + YYERROR; + } + $$ = calloc(1, sizeof(struct node_gid)); + if ($$ == NULL) + err(1, "gid_item: calloc"); + $$->gid[0] = $1; + $$->gid[1] = $3; + $$->op = $2; + $$->next = NULL; + $$->tail = $$; + } + ; + +gid : STRING { + if (!strcmp($1, "unknown")) + $$ = -1; + else { + gid_t gid; + + if (gid_from_group($1, &gid) == -1) { + yyerror("unknown group %s", $1); + free($1); + YYERROR; + } + $$ = gid; + } + free($1); + } + | NUMBER { + if ($1 < 0 || $1 >= GID_MAX) { + yyerror("illegal gid value %lu", $1); + YYERROR; + } + $$ = $1; + } + ; + +flag : STRING { + int f; + + if ((f = parse_flags($1)) < 0) { + yyerror("bad flags %s", $1); + free($1); + YYERROR; + } + free($1); + $$.b1 = f; + } + ; + +flags : FLAGS flag '/' flag { $$.b1 = $2.b1; $$.b2 = $4.b1; } + | FLAGS '/' flag { $$.b1 = 0; $$.b2 = $3.b1; } + | FLAGS ANY { $$.b1 = 0; $$.b2 = 0; } + ; + +icmpspec : ICMPTYPE icmp_item { $$ = $2; } + | ICMPTYPE '{' optnl icmp_list '}' { $$ = $4; } + | ICMP6TYPE icmp6_item { $$ = $2; } + | ICMP6TYPE '{' optnl icmp6_list '}' { $$ = $4; } + ; + +icmp_list : icmp_item optnl { $$ = $1; } + | icmp_list comma icmp_item optnl { + $1->tail->next = $3; + $1->tail = $3; + $$ = $1; + } + ; + +icmp6_list : icmp6_item optnl { $$ = $1; } + | icmp6_list comma icmp6_item optnl { + $1->tail->next = $3; + $1->tail = $3; + $$ = $1; + } + ; + +icmp_item : icmptype { + $$ = calloc(1, sizeof(struct node_icmp)); + if ($$ == NULL) + err(1, "icmp_item: calloc"); + $$->type = $1; + $$->code = 0; + $$->proto = IPPROTO_ICMP; + $$->next = NULL; + $$->tail = $$; + } + | icmptype CODE STRING { + const struct icmpcodeent *p; + + if ((p = geticmpcodebyname($1-1, $3, AF_INET)) == NULL) { + yyerror("unknown icmp-code %s", $3); + free($3); + YYERROR; + } + + free($3); + $$ = calloc(1, sizeof(struct node_icmp)); + if ($$ == NULL) + err(1, "icmp_item: calloc"); + $$->type = $1; + $$->code = p->code + 1; + $$->proto = IPPROTO_ICMP; + $$->next = NULL; + $$->tail = $$; + } + | icmptype CODE NUMBER { + if ($3 < 0 || $3 > 255) { + yyerror("illegal icmp-code %lu", $3); + YYERROR; + } + $$ = calloc(1, sizeof(struct node_icmp)); + if ($$ == NULL) + err(1, "icmp_item: calloc"); + $$->type = $1; + $$->code = $3 + 1; + $$->proto = IPPROTO_ICMP; + $$->next = NULL; + $$->tail = $$; + } + ; + +icmp6_item : icmp6type { + $$ = calloc(1, sizeof(struct node_icmp)); + if ($$ == NULL) + err(1, "icmp_item: calloc"); + $$->type = $1; + $$->code = 0; + $$->proto = IPPROTO_ICMPV6; + $$->next = NULL; + $$->tail = $$; + } + | icmp6type CODE STRING { + const struct icmpcodeent *p; + + if ((p = geticmpcodebyname($1-1, $3, AF_INET6)) == NULL) { + yyerror("unknown icmp6-code %s", $3); + free($3); + YYERROR; + } + free($3); + + $$ = calloc(1, sizeof(struct node_icmp)); + if ($$ == NULL) + err(1, "icmp_item: calloc"); + $$->type = $1; + $$->code = p->code + 1; + $$->proto = IPPROTO_ICMPV6; + $$->next = NULL; + $$->tail = $$; + } + | icmp6type CODE NUMBER { + if ($3 < 0 || $3 > 255) { + yyerror("illegal icmp-code %lu", $3); + YYERROR; + } + $$ = calloc(1, sizeof(struct node_icmp)); + if ($$ == NULL) + err(1, "icmp_item: calloc"); + $$->type = $1; + $$->code = $3 + 1; + $$->proto = IPPROTO_ICMPV6; + $$->next = NULL; + $$->tail = $$; + } + ; + +icmptype : STRING { + const struct icmptypeent *p; + + if ((p = geticmptypebyname($1, AF_INET)) == NULL) { + yyerror("unknown icmp-type %s", $1); + free($1); + YYERROR; + } + $$ = p->type + 1; + free($1); + } + | NUMBER { + if ($1 < 0 || $1 > 255) { + yyerror("illegal icmp-type %lu", $1); + YYERROR; + } + $$ = $1 + 1; + } + ; + +icmp6type : STRING { + const struct icmptypeent *p; + + if ((p = geticmptypebyname($1, AF_INET6)) == + NULL) { + yyerror("unknown icmp6-type %s", $1); + free($1); + YYERROR; + } + $$ = p->type + 1; + free($1); + } + | NUMBER { + if ($1 < 0 || $1 > 255) { + yyerror("illegal icmp6-type %lu", $1); + YYERROR; + } + $$ = $1 + 1; + } + ; + +tos : STRING { + int val; + char *end; + + if (map_tos($1, &val)) + $$ = val; + else if ($1[0] == '0' && $1[1] == 'x') { + errno = 0; + $$ = strtoul($1, &end, 16); + if (errno || *end != '\0') + $$ = 256; + } else + $$ = 256; /* flag bad argument */ + if ($$ < 0 || $$ > 255) { + yyerror("illegal tos value %s", $1); + free($1); + YYERROR; + } + free($1); + } + | NUMBER { + $$ = $1; + if ($$ < 0 || $$ > 255) { + yyerror("illegal tos value %lu", $1); + YYERROR; + } + } + ; + +sourcetrack : SOURCETRACK { $$ = PF_SRCTRACK; } + | SOURCETRACK GLOBAL { $$ = PF_SRCTRACK_GLOBAL; } + | SOURCETRACK RULE { $$ = PF_SRCTRACK_RULE; } + ; + +statelock : IFBOUND { + $$ = PFRULE_IFBOUND; + } + | FLOATING { + $$ = 0; + } + ; + +keep : NO STATE { + $$.action = 0; + $$.options = NULL; + } + | KEEP STATE state_opt_spec { + $$.action = PF_STATE_NORMAL; + $$.options = $3; + } + | MODULATE STATE state_opt_spec { + $$.action = PF_STATE_MODULATE; + $$.options = $3; + } + | SYNPROXY STATE state_opt_spec { + $$.action = PF_STATE_SYNPROXY; + $$.options = $3; + } + ; + +flush : /* empty */ { $$ = 0; } + | FLUSH { $$ = PF_FLUSH; } + | FLUSH GLOBAL { + $$ = PF_FLUSH | PF_FLUSH_GLOBAL; + } + ; + +state_opt_spec : '(' state_opt_list ')' { $$ = $2; } + | /* empty */ { $$ = NULL; } + ; + +state_opt_list : state_opt_item { $$ = $1; } + | state_opt_list comma state_opt_item { + $1->tail->next = $3; + $1->tail = $3; + $$ = $1; + } + ; + +state_opt_item : MAXIMUM NUMBER { + if ($2 < 0 || $2 > UINT_MAX) { + yyerror("only positive values permitted"); + YYERROR; + } + $$ = calloc(1, sizeof(struct node_state_opt)); + if ($$ == NULL) + err(1, "state_opt_item: calloc"); + $$->type = PF_STATE_OPT_MAX; + $$->data.max_states = $2; + $$->next = NULL; + $$->tail = $$; + } + | NOSYNC { + $$ = calloc(1, sizeof(struct node_state_opt)); + if ($$ == NULL) + err(1, "state_opt_item: calloc"); + $$->type = PF_STATE_OPT_NOSYNC; + $$->next = NULL; + $$->tail = $$; + } + | MAXSRCSTATES NUMBER { + if ($2 < 0 || $2 > UINT_MAX) { + yyerror("only positive values permitted"); + YYERROR; + } + $$ = calloc(1, sizeof(struct node_state_opt)); + if ($$ == NULL) + err(1, "state_opt_item: calloc"); + $$->type = PF_STATE_OPT_MAX_SRC_STATES; + $$->data.max_src_states = $2; + $$->next = NULL; + $$->tail = $$; + } + | MAXSRCCONN NUMBER { + if ($2 < 0 || $2 > UINT_MAX) { + yyerror("only positive values permitted"); + YYERROR; + } + $$ = calloc(1, sizeof(struct node_state_opt)); + if ($$ == NULL) + err(1, "state_opt_item: calloc"); + $$->type = PF_STATE_OPT_MAX_SRC_CONN; + $$->data.max_src_conn = $2; + $$->next = NULL; + $$->tail = $$; + } + | MAXSRCCONNRATE NUMBER '/' NUMBER { + if ($2 < 0 || $2 > UINT_MAX || + $4 < 0 || $4 > UINT_MAX) { + yyerror("only positive values permitted"); + YYERROR; + } + $$ = calloc(1, sizeof(struct node_state_opt)); + if ($$ == NULL) + err(1, "state_opt_item: calloc"); + $$->type = PF_STATE_OPT_MAX_SRC_CONN_RATE; + $$->data.max_src_conn_rate.limit = $2; + $$->data.max_src_conn_rate.seconds = $4; + $$->next = NULL; + $$->tail = $$; + } + | OVERLOAD '<' STRING '>' flush { + if (strlen($3) >= PF_TABLE_NAME_SIZE) { + yyerror("table name '%s' too long", $3); + free($3); + YYERROR; + } + $$ = calloc(1, sizeof(struct node_state_opt)); + if ($$ == NULL) + err(1, "state_opt_item: calloc"); + if (strlcpy($$->data.overload.tblname, $3, + PF_TABLE_NAME_SIZE) >= PF_TABLE_NAME_SIZE) + errx(1, "state_opt_item: strlcpy"); + free($3); + $$->type = PF_STATE_OPT_OVERLOAD; + $$->data.overload.flush = $5; + $$->next = NULL; + $$->tail = $$; + } + | MAXSRCNODES NUMBER { + if ($2 < 0 || $2 > UINT_MAX) { + yyerror("only positive values permitted"); + YYERROR; + } + $$ = calloc(1, sizeof(struct node_state_opt)); + if ($$ == NULL) + err(1, "state_opt_item: calloc"); + $$->type = PF_STATE_OPT_MAX_SRC_NODES; + $$->data.max_src_nodes = $2; + $$->next = NULL; + $$->tail = $$; + } + | sourcetrack { + $$ = calloc(1, sizeof(struct node_state_opt)); + if ($$ == NULL) + err(1, "state_opt_item: calloc"); + $$->type = PF_STATE_OPT_SRCTRACK; + $$->data.src_track = $1; + $$->next = NULL; + $$->tail = $$; + } + | statelock { + $$ = calloc(1, sizeof(struct node_state_opt)); + if ($$ == NULL) + err(1, "state_opt_item: calloc"); + $$->type = PF_STATE_OPT_STATELOCK; + $$->data.statelock = $1; + $$->next = NULL; + $$->tail = $$; + } + | SLOPPY { + $$ = calloc(1, sizeof(struct node_state_opt)); + if ($$ == NULL) + err(1, "state_opt_item: calloc"); + $$->type = PF_STATE_OPT_SLOPPY; + $$->next = NULL; + $$->tail = $$; + } + | PFLOW { + $$ = calloc(1, sizeof(struct node_state_opt)); + if ($$ == NULL) + err(1, "state_opt_item: calloc"); + $$->type = PF_STATE_OPT_PFLOW; + $$->next = NULL; + $$->tail = $$; + } + | ALLOW_RELATED { + $$ = calloc(1, sizeof(struct node_state_opt)); + if ($$ == NULL) + err(1, "state_opt_item: calloc"); + $$->type = PF_STATE_OPT_ALLOW_RELATED; + $$->next = NULL; + $$->tail = $$; + } + | STRING NUMBER { + int i; + + if ($2 < 0 || $2 > UINT_MAX) { + yyerror("only positive values permitted"); + YYERROR; + } + for (i = 0; pf_timeouts[i].name && + strcmp(pf_timeouts[i].name, $1); ++i) + ; /* nothing */ + if (!pf_timeouts[i].name) { + yyerror("illegal timeout name %s", $1); + free($1); + YYERROR; + } + if (strchr(pf_timeouts[i].name, '.') == NULL) { + yyerror("illegal state timeout %s", $1); + free($1); + YYERROR; + } + free($1); + $$ = calloc(1, sizeof(struct node_state_opt)); + if ($$ == NULL) + err(1, "state_opt_item: calloc"); + $$->type = PF_STATE_OPT_TIMEOUT; + $$->data.timeout.number = pf_timeouts[i].timeout; + $$->data.timeout.seconds = $2; + $$->next = NULL; + $$->tail = $$; + } + ; + +label : LABEL STRING { + $$ = $2; + } + ; + +etherqname : QUEUE STRING { + $$.qname = $2; + } + | QUEUE '(' STRING ')' { + $$.qname = $3; + } + ; + +qname : QUEUE STRING { + $$.qname = $2; + $$.pqname = NULL; + } + | QUEUE '(' STRING ')' { + $$.qname = $3; + $$.pqname = NULL; + } + | QUEUE '(' STRING comma STRING ')' { + $$.qname = $3; + $$.pqname = $5; + } + ; + +no : /* empty */ { $$ = 0; } + | NO { $$ = 1; } + ; + +portstar : numberstring { + if (parseport($1, &$$, PPORT_RANGE|PPORT_STAR) == -1) { + free($1); + YYERROR; + } + free($1); + } + ; + +redir_host : host { $$ = $1; } + | '{' optnl redir_host_list '}' { $$ = $3; } + ; + +redir_host_list : host optnl { $$ = $1; } + | redir_host_list comma host optnl { + $1->tail->next = $3; + $1->tail = $3->tail; + $$ = $1; + } + ; + +/* Redirection without port */ +no_port_redirspec: redir_host pool_opts { + $$ = calloc(1, sizeof(struct redirspec)); + if ($$ == NULL) + err(1, "redirspec: calloc"); + $$->host = $1; + $$->pool_opts = $2; + $$->rport.a = $$->rport.b = $$->rport.t = 0; + } + ; + +/* Redirection with optional port */ +port_redirspec : no_port_redirspec; + | redir_host PORT portstar pool_opts { + $$ = calloc(1, sizeof(struct redirspec)); + if ($$ == NULL) + err(1, "redirspec: calloc"); + $$->host = $1; + $$->rport = $3; + $$->pool_opts = $4; + } + +/* Redirection with an arrow and an optional port: FreeBSD NAT rules */ +nat_redirspec : /* empty */ { $$ = NULL; } + | ARROW port_redirspec { + $$ = $2; + } + ; + +/* Redirection with interfaces and without ports: route-to rules */ +route_redirspec : routespec pool_opts { + $$ = calloc(1, sizeof(struct redirspec)); + if ($$ == NULL) + err(1, "redirspec: calloc"); + $$->host = $1; + $$->pool_opts = $2; + } + ; + +hashkey : /* empty */ + { + $$ = calloc(1, sizeof(struct pf_poolhashkey)); + if ($$ == NULL) + err(1, "hashkey: calloc"); + $$->key32[0] = arc4random(); + $$->key32[1] = arc4random(); + $$->key32[2] = arc4random(); + $$->key32[3] = arc4random(); + } + | string + { + if (!strncmp($1, "0x", 2)) { + if (strlen($1) != 34) { + free($1); + yyerror("hex key must be 128 bits " + "(32 hex digits) long"); + YYERROR; + } + $$ = calloc(1, sizeof(struct pf_poolhashkey)); + if ($$ == NULL) + err(1, "hashkey: calloc"); + + if (sscanf($1, "0x%8x%8x%8x%8x", + &$$->key32[0], &$$->key32[1], + &$$->key32[2], &$$->key32[3]) != 4) { + free($$); + free($1); + yyerror("invalid hex key"); + YYERROR; + } + } else { + MD5_CTX context; + + $$ = calloc(1, sizeof(struct pf_poolhashkey)); + if ($$ == NULL) + err(1, "hashkey: calloc"); + MD5Init(&context); + MD5Update(&context, (unsigned char *)$1, + strlen($1)); + MD5Final((unsigned char *)$$, &context); + HTONL($$->key32[0]); + HTONL($$->key32[1]); + HTONL($$->key32[2]); + HTONL($$->key32[3]); + } + free($1); + } + ; + +pool_opts : { bzero(&pool_opts, sizeof pool_opts); } + pool_opts_l + { $$ = pool_opts; } + | /* empty */ { + bzero(&pool_opts, sizeof pool_opts); + $$ = pool_opts; + } + ; + +pool_opts_l : pool_opts_l pool_opt + | pool_opt + ; + +pool_opt : BITMASK { + if (pool_opts.type) { + yyerror("pool type cannot be redefined"); + YYERROR; + } + pool_opts.type = PF_POOL_BITMASK; + } + | RANDOM { + if (pool_opts.type) { + yyerror("pool type cannot be redefined"); + YYERROR; + } + pool_opts.type = PF_POOL_RANDOM; + } + | SOURCEHASH hashkey { + if (pool_opts.type) { + yyerror("pool type cannot be redefined"); + YYERROR; + } + pool_opts.type = PF_POOL_SRCHASH; + pool_opts.key = $2; + } + | ROUNDROBIN { + if (pool_opts.type) { + yyerror("pool type cannot be redefined"); + YYERROR; + } + pool_opts.type = PF_POOL_ROUNDROBIN; + } + | STATICPORT { + if (pool_opts.staticport) { + yyerror("static-port cannot be redefined"); + YYERROR; + } + pool_opts.staticport = 1; + } + | STICKYADDRESS { + if (pool_opts.marker & POM_STICKYADDRESS) { + yyerror("sticky-address cannot be redefined"); + YYERROR; + } + pool_opts.marker |= POM_STICKYADDRESS; + pool_opts.opts |= PF_POOL_STICKYADDR; + } + | ENDPI { + if (pool_opts.marker & POM_ENDPI) { + yyerror("endpoint-independent cannot be redefined"); + YYERROR; + } + pool_opts.marker |= POM_ENDPI; + pool_opts.opts |= PF_POOL_ENDPI; + } + | IPV6NH { + if (pool_opts.marker & POM_IPV6NH) { + yyerror("prefer-ipv6-nexthop cannot be redefined"); + YYERROR; + } + pool_opts.marker |= POM_IPV6NH; + pool_opts.opts |= PF_POOL_IPV6NH; + } + | MAPEPORTSET number '/' number '/' number { + if (pool_opts.mape.offset) { + yyerror("map-e-portset cannot be redefined"); + YYERROR; + } + if (pool_opts.type) { + yyerror("map-e-portset cannot be used with " + "address pools"); + YYERROR; + } + if ($2 <= 0 || $2 >= 16) { + yyerror("MAP-E PSID offset must be 1-15"); + YYERROR; + } + if ($4 < 0 || $4 >= 16 || $2 + $4 > 16) { + yyerror("Invalid MAP-E PSID length"); + YYERROR; + } else if ($4 == 0) { + yyerror("PSID Length = 0: this means" + " you do not need MAP-E"); + YYERROR; + } + if ($6 < 0 || $6 > 65535) { + yyerror("Invalid MAP-E PSID"); + YYERROR; + } + pool_opts.mape.offset = $2; + pool_opts.mape.psidlen = $4; + pool_opts.mape.psid = $6; + } + ; + +binat_redirspec : /* empty */ { $$ = NULL; } + | ARROW host { + $$ = calloc(1, sizeof(struct redirspec)); + if ($$ == NULL) + err(1, "redirspec: calloc"); + $$->host = $2; + $$->rport.a = $$->rport.b = $$->rport.t = 0; + } + | ARROW host PORT portstar { + $$ = calloc(1, sizeof(struct redirspec)); + if ($$ == NULL) + err(1, "redirspec: calloc"); + $$->host = $2; + $$->rport = $4; + } + ; + +natpasslog : /* empty */ { $$.b1 = $$.b2 = 0; $$.w2 = 0; } + | PASS { $$.b1 = 1; $$.b2 = 0; $$.w2 = 0; } + | PASS log { $$.b1 = 1; $$.b2 = $2.log; $$.w2 = $2.logif; } + | log { $$.b1 = 0; $$.b2 = $1.log; $$.w2 = $1.logif; } + ; + +nataction : no NAT natpasslog { + if ($1 && $3.b1) { + yyerror("\"pass\" not valid with \"no\""); + YYERROR; + } + if ($1) + $$.b1 = PF_NONAT; + else + $$.b1 = PF_NAT; + $$.b2 = $3.b1; + $$.w = $3.b2; + $$.w2 = $3.w2; + } + | no RDR natpasslog { + if ($1 && $3.b1) { + yyerror("\"pass\" not valid with \"no\""); + YYERROR; + } + if ($1) + $$.b1 = PF_NORDR; + else + $$.b1 = PF_RDR; + $$.b2 = $3.b1; + $$.w = $3.b2; + $$.w2 = $3.w2; + } + ; + +natrule : nataction interface af proto fromto tag tagged rtable + nat_redirspec + { + struct pfctl_rule r; + struct node_state_opt *o; + + if (check_rulestate(PFCTL_STATE_NAT)) + YYERROR; + + pfctl_init_rule(&r); + + r.action = $1.b1; + r.natpass = $1.b2; + r.log = $1.w; + r.logif = $1.w2; + r.af = $3; + + if (!r.af) { + if ($5.src.host && $5.src.host->af && + !$5.src.host->ifindex) + r.af = $5.src.host->af; + else if ($5.dst.host && $5.dst.host->af && + !$5.dst.host->ifindex) + r.af = $5.dst.host->af; + } + + if ($6 != NULL) + if (strlcpy(r.tagname, $6, PF_TAG_NAME_SIZE) >= + PF_TAG_NAME_SIZE) { + yyerror("tag too long, max %u chars", + PF_TAG_NAME_SIZE - 1); + YYERROR; + } + + if ($7.name) + if (strlcpy(r.match_tagname, $7.name, + PF_TAG_NAME_SIZE) >= PF_TAG_NAME_SIZE) { + yyerror("tag too long, max %u chars", + PF_TAG_NAME_SIZE - 1); + YYERROR; + } + r.match_tag_not = $7.neg; + r.rtableid = $8; + + if (r.action == PF_NONAT || r.action == PF_NORDR) { + if ($9 != NULL) { + yyerror("translation rule with 'no' " + "does not need '->'"); + YYERROR; + } + } else { + if ($9 == NULL || $9->host == NULL) { + yyerror("translation rule requires '-> " + "address'"); + YYERROR; + } + if ($9->pool_opts.opts & PF_POOL_IPV6NH) { + yyerror("The prefer-ipv6-nexthop option " + "can't be used for nat/rdr/binat pools" + ); + YYERROR; + } + if (!r.af && ! $9->host->ifindex) + r.af = $9->host->af; + + remove_invalid_hosts(&$9->host, &r.af); + if (invalid_redirect($9->host, r.af)) + YYERROR; + if ($9->host->addr.type == PF_ADDR_DYNIFTL) { + if (($9->host = gen_dynnode($9->host, r.af)) == NULL) + err(1, "calloc"); + } + if (check_netmask($9->host, r.af)) + YYERROR; + } + + o = keep_state_defaults; + while (o) { + switch (o->type) { + case PF_STATE_OPT_PFLOW: + if (r.rule_flag & PFRULE_PFLOW) { + yyerror("state pflow option: " + "multiple definitions"); + YYERROR; + } + r.rule_flag |= PFRULE_PFLOW; + break; + } + o = o->next; + } + + expand_rule(&r, false, $2, NULL, $9, NULL, $4, + $5.src_os, $5.src.host, $5.src.port, $5.dst.host, + $5.dst.port, 0, 0, 0, 0); + } + ; + +binatrule : no BINAT natpasslog interface af proto FROM ipspec toipspec tag + tagged rtable binat_redirspec + { + struct pfctl_rule binat; + struct pfctl_pooladdr *pa; + + if (check_rulestate(PFCTL_STATE_NAT)) + YYERROR; + if (disallow_urpf_failed($9, "\"urpf-failed\" is not " + "permitted as a binat destination")) + YYERROR; + + pfctl_init_rule(&binat); + + if ($1 && $3.b1) { + yyerror("\"pass\" not valid with \"no\""); + YYERROR; + } + if ($1) + binat.action = PF_NOBINAT; + else + binat.action = PF_BINAT; + binat.natpass = $3.b1; + binat.log = $3.b2; + binat.logif = $3.w2; + binat.af = $5; + if (!binat.af && $8 != NULL && $8->af) + binat.af = $8->af; + if (!binat.af && $9 != NULL && $9->af) + binat.af = $9->af; + + if (!binat.af && $13 != NULL && $13->host) + binat.af = $13->host->af; + if (!binat.af) { + yyerror("address family (inet/inet6) " + "undefined"); + YYERROR; + } + + if ($4 != NULL) { + memcpy(binat.ifname, $4->ifname, + sizeof(binat.ifname)); + binat.ifnot = $4->not; + free($4); + } + + if ($10 != NULL) + if (strlcpy(binat.tagname, $10, + PF_TAG_NAME_SIZE) >= PF_TAG_NAME_SIZE) { + yyerror("tag too long, max %u chars", + PF_TAG_NAME_SIZE - 1); + YYERROR; + } + if ($11.name) + if (strlcpy(binat.match_tagname, $11.name, + PF_TAG_NAME_SIZE) >= PF_TAG_NAME_SIZE) { + yyerror("tag too long, max %u chars", + PF_TAG_NAME_SIZE - 1); + YYERROR; + } + binat.match_tag_not = $11.neg; + binat.rtableid = $12; + + if ($6 != NULL) { + binat.proto = $6->proto; + free($6); + } + + if ($8 != NULL && disallow_table($8, "invalid use of " + "table <%s> as the source address of a binat rule")) + YYERROR; + if ($8 != NULL && disallow_alias($8, "invalid use of " + "interface (%s) as the source address of a binat " + "rule")) + YYERROR; + if ($13 != NULL && $13->host != NULL && disallow_table( + $13->host, "invalid use of table <%s> as the " + "redirect address of a binat rule")) + YYERROR; + if ($13 != NULL && $13->host != NULL && disallow_alias( + $13->host, "invalid use of interface (%s) as the " + "redirect address of a binat rule")) + YYERROR; + + if ($8 != NULL) { + if ($8->next) { + yyerror("multiple binat ip addresses"); + YYERROR; + } + if ($8->addr.type == PF_ADDR_DYNIFTL) + $8->af = binat.af; + if ($8->af != binat.af) { + yyerror("binat ip versions must match"); + YYERROR; + } + if ($8->addr.type == PF_ADDR_DYNIFTL) { + if (($8 = gen_dynnode($8, binat.af)) == NULL) + err(1, "calloc"); + } + if (check_netmask($8, binat.af)) + YYERROR; + memcpy(&binat.src.addr, &$8->addr, + sizeof(binat.src.addr)); + free($8); + } + if ($9 != NULL) { + if ($9->next) { + yyerror("multiple binat ip addresses"); + YYERROR; + } + if ($9->af != binat.af && $9->af) { + yyerror("binat ip versions must match"); + YYERROR; + } + if ($9->addr.type == PF_ADDR_DYNIFTL) { + if (($9 = gen_dynnode($9, binat.af)) == NULL) + err(1, "calloc"); + } + if (check_netmask($9, binat.af)) + YYERROR; + memcpy(&binat.dst.addr, &$9->addr, + sizeof(binat.dst.addr)); + binat.dst.neg = $9->not; + free($9); + } + + if (binat.action == PF_NOBINAT) { + if ($13 != NULL) { + yyerror("'no binat' rule does not need" + " '->'"); + YYERROR; + } + } else { + if ($13 == NULL || $13->host == NULL) { + yyerror("'binat' rule requires" + " '-> address'"); + YYERROR; + } + + remove_invalid_hosts(&$13->host, &binat.af); + if (invalid_redirect($13->host, binat.af)) + YYERROR; + if ($13->host->next != NULL) { + yyerror("binat rule must redirect to " + "a single address"); + YYERROR; + } + if ($13->host->addr.type == PF_ADDR_DYNIFTL) { + if (($13->host = gen_dynnode($13->host, binat.af)) == NULL) + err(1, "calloc"); + } + if (check_netmask($13->host, binat.af)) + YYERROR; + + if (!PF_AZERO(&binat.src.addr.v.a.mask, + binat.af) && + !PF_AEQ(&binat.src.addr.v.a.mask, + &$13->host->addr.v.a.mask, binat.af)) { + yyerror("'binat' source mask and " + "redirect mask must be the same"); + YYERROR; + } + + pa = calloc(1, sizeof(struct pfctl_pooladdr)); + if (pa == NULL) + err(1, "binat: calloc"); + pa->addr = $13->host->addr; + pa->ifname[0] = 0; + pa->af = $13->host->af; + TAILQ_INSERT_TAIL(&binat.rdr.list, + pa, entries); + + free($13); + } + + pfctl_append_rule(pf, &binat); + } + ; + +tag : /* empty */ { $$ = NULL; } + | TAG STRING { $$ = $2; } + ; + +tagged : /* empty */ { $$.neg = 0; $$.name = NULL; } + | not TAGGED string { $$.neg = $1; $$.name = $3; } + ; + +rtable : /* empty */ { $$ = -1; } + | RTABLE NUMBER { + if ($2 < 0 || $2 > rt_tableid_max()) { + yyerror("invalid rtable id"); + YYERROR; + } + $$ = $2; + } + ; + +route_host : STRING { + $$ = calloc(1, sizeof(struct node_host)); + if ($$ == NULL) + err(1, "route_host: calloc"); + if (strlen($1) >= IFNAMSIZ) { + yyerror("interface name too long"); + YYERROR; + } + $$->ifname = strdup($1); + set_ipmask($$, 128); + $$->next = NULL; + $$->tail = $$; + } + | '(' STRING host ')' { + struct node_host *n; + + $$ = $3; + for (n = $3; n != NULL; n = n->next) { + if (strlen($2) >= IFNAMSIZ) { + yyerror("interface name too long"); + YYERROR; + } + n->ifname = strdup($2); + } + } + ; + +route_host_list : route_host optnl { $$ = $1; } + | route_host_list comma route_host optnl { + $1->tail->next = $3; + $1->tail = $3->tail; + $$ = $1; + } + ; + +routespec : route_host { $$ = $1; } + | '{' optnl route_host_list '}' { $$ = $3; } + ; + +route : /* empty */ { + $$.rt = PF_NOPFROUTE; + } + | FASTROUTE { + /* backwards-compat */ + $$.rt = PF_NOPFROUTE; + } + | ROUTETO route_redirspec { + $$.rt = PF_ROUTETO; + $$.redirspec = $2; + } + | REPLYTO route_redirspec { + $$.rt = PF_REPLYTO; + $$.redirspec = $2; + } + | DUPTO route_redirspec { + $$.rt = PF_DUPTO; + $$.redirspec = $2; + } + ; + +timeout_spec : STRING NUMBER + { + if (check_rulestate(PFCTL_STATE_OPTION)) { + free($1); + YYERROR; + } + if ($2 < 0 || $2 > UINT_MAX) { + yyerror("only positive values permitted"); + YYERROR; + } + if (pfctl_apply_timeout(pf, $1, $2, 0) != 0) { + yyerror("unknown timeout %s", $1); + free($1); + YYERROR; + } + free($1); + } + | INTERVAL NUMBER { + if (check_rulestate(PFCTL_STATE_OPTION)) + YYERROR; + if ($2 < 0 || $2 > UINT_MAX) { + yyerror("only positive values permitted"); + YYERROR; + } + if (pfctl_apply_timeout(pf, "interval", $2, 0) != 0) + YYERROR; + } + ; + +timeout_list : timeout_list comma timeout_spec optnl + | timeout_spec optnl + ; + +limit_spec : STRING NUMBER + { + if (check_rulestate(PFCTL_STATE_OPTION)) { + free($1); + YYERROR; + } + if ($2 < 0 || $2 > UINT_MAX) { + yyerror("only positive values permitted"); + YYERROR; + } + if (pfctl_apply_limit(pf, $1, $2) != 0) { + yyerror("unable to set limit %s %u", $1, $2); + free($1); + YYERROR; + } + free($1); + } + ; + +limit_list : limit_list comma limit_spec optnl + | limit_spec optnl + ; + +comma : ',' + | /* empty */ + ; + +yesno : NO { $$ = 0; } + | STRING { + if (!strcmp($1, "yes")) + $$ = 1; + else { + yyerror("invalid value '%s', expected 'yes' " + "or 'no'", $1); + free($1); + YYERROR; + } + free($1); + } + ; + +unaryop : '=' { $$ = PF_OP_EQ; } + | NE { $$ = PF_OP_NE; } + | LE { $$ = PF_OP_LE; } + | '<' { $$ = PF_OP_LT; } + | GE { $$ = PF_OP_GE; } + | '>' { $$ = PF_OP_GT; } + ; + +%% + +int +yyerror(const char *fmt, ...) +{ + va_list ap; + + file->errors++; + va_start(ap, fmt); + fprintf(stderr, "%s:%d: ", file->name, yylval.lineno); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); + return (0); +} + +int +validate_range(uint8_t op, uint16_t p1, uint16_t p2) +{ + uint16_t a = ntohs(p1); + uint16_t b = ntohs(p2); + + if ((op == PF_OP_RRG && a > b) || /* 34:12, i.e. none */ + (op == PF_OP_IRG && a >= b) || /* 34><12, i.e. none */ + (op == PF_OP_XRG && a > b)) /* 34<>22, i.e. all */ + return 1; + return 0; +} + +int +disallow_table(struct node_host *h, const char *fmt) +{ + for (; h != NULL; h = h->next) + if (h->addr.type == PF_ADDR_TABLE) { + yyerror(fmt, h->addr.v.tblname); + return (1); + } + return (0); +} + +int +disallow_urpf_failed(struct node_host *h, const char *fmt) +{ + for (; h != NULL; h = h->next) + if (h->addr.type == PF_ADDR_URPFFAILED) { + yyerror(fmt); + return (1); + } + return (0); +} + +int +disallow_alias(struct node_host *h, const char *fmt) +{ + for (; h != NULL; h = h->next) + if (DYNIF_MULTIADDR(h->addr)) { + yyerror(fmt, h->addr.v.tblname); + return (1); + } + return (0); +} + +int +rule_consistent(struct pfctl_rule *r) +{ + int problems = 0; + + switch (r->action) { + case PF_PASS: + case PF_MATCH: + case PF_DROP: + case PF_SCRUB: + case PF_NOSCRUB: + problems = filter_consistent(r); + break; + case PF_NAT: + case PF_NONAT: + problems = nat_consistent(r); + break; + case PF_RDR: + case PF_NORDR: + problems = rdr_consistent(r); + break; + case PF_BINAT: + case PF_NOBINAT: + default: + break; + } + return (problems); +} + +int +filter_consistent(struct pfctl_rule *r) +{ + int problems = 0; + + if (r->proto != IPPROTO_TCP && r->proto != IPPROTO_UDP && + r->proto != IPPROTO_SCTP && + (r->src.port_op || r->dst.port_op)) { + yyerror("port only applies to tcp/udp/sctp"); + problems++; + } + if (r->proto != IPPROTO_ICMP && r->proto != IPPROTO_ICMPV6 && + (r->type || r->code)) { + yyerror("icmp-type/code only applies to icmp"); + problems++; + } + if (!r->af && (r->type || r->code)) { + yyerror("must indicate address family with icmp-type/code"); + problems++; + } + if (r->rule_flag & PFRULE_AFTO && r->af == r->naf) { + yyerror("must indicate different address family with af-to"); + problems++; + } + if (r->overload_tblname[0] && + r->max_src_conn == 0 && r->max_src_conn_rate.seconds == 0) { + yyerror("'overload' requires 'max-src-conn' " + "or 'max-src-conn-rate'"); + problems++; + } + if ((r->proto == IPPROTO_ICMP && r->af == AF_INET6) || + (r->proto == IPPROTO_ICMPV6 && r->af == AF_INET)) { + yyerror("proto %s doesn't match address family %s", + r->proto == IPPROTO_ICMP ? "icmp" : "icmp6", + r->af == AF_INET ? "inet" : "inet6"); + problems++; + } + if (r->allow_opts && r->action != PF_PASS && r->action != PF_MATCH) { + yyerror("allow-opts can only be specified for pass or " + "match rules"); + problems++; + } + if (r->rule_flag & PFRULE_FRAGMENT && (r->src.port_op || + r->dst.port_op || r->flagset || r->type || r->code)) { + yyerror("fragments can be filtered only on IP header fields"); + problems++; + } + if (r->rule_flag & PFRULE_RETURNRST && r->proto != IPPROTO_TCP) { + yyerror("return-rst can only be applied to TCP rules"); + problems++; + } + if (r->max_src_nodes && !(r->rule_flag & PFRULE_RULESRCTRACK)) { + yyerror("max-src-nodes requires 'source-track rule'"); + problems++; + } + if (r->action != PF_PASS && r->keep_state) { + yyerror("keep state is great, but only for pass rules"); + problems++; + } + if (r->rule_flag & PFRULE_STATESLOPPY && + (r->keep_state == PF_STATE_MODULATE || + r->keep_state == PF_STATE_SYNPROXY)) { + yyerror("sloppy state matching cannot be used with " + "synproxy state or modulate state"); + problems++; + } + if ((r->keep_state == PF_STATE_SYNPROXY) && (r->direction != PF_IN)) + fprintf(stderr, "%s:%d: warning: " + "synproxy used for inbound rules only, " + "ignored for outbound\n", file->name, yylval.lineno); + if (r->rule_flag & PFRULE_AFTO && r->rt) { + if (r->rt != PF_ROUTETO && r->rt != PF_REPLYTO) { + yyerror("dup-to " + "must not be used on af-to rules"); + problems++; + } + } + /* Basic rule sanity check. */ + switch (r->action) { + case PF_MATCH: + if (r->divert.port) { + yyerror("divert is not supported on match rules"); + problems++; + } + if (r->rt) { + yyerror("route-to, reply-to, dup-to and fastroute " + "must not be used on match rules"); + problems++; + } + if (r->rule_flag & PFRULE_AFTO) { + yyerror("af-to is not supported on match rules"); + problems++; + } + break; + case PF_DROP: + if (r->rt) { + yyerror("route-to, reply-to and dup-to " + "are not supported on block rules"); + problems++; + } + break; + default:; + } + if (!TAILQ_EMPTY(&(r->nat.list)) || !TAILQ_EMPTY(&(r->rdr.list))) { + if (r->action != PF_MATCH && !r->keep_state) { + yyerror("nat-to and rdr-to require keep state"); + problems++; + } + if (r->direction == PF_INOUT) { + yyerror("nat-to and rdr-to require a direction"); + problems++; + } + } + if (r->route.opts & PF_POOL_STICKYADDR && !r->keep_state) { + yyerror("'sticky-address' requires 'keep state'"); + } + return (-problems); +} + +int +nat_consistent(struct pfctl_rule *r) +{ + return (0); /* yeah! */ +} + +int +rdr_consistent(struct pfctl_rule *r) +{ + int problems = 0; + + if (r->proto != IPPROTO_TCP && r->proto != IPPROTO_UDP && + r->proto != IPPROTO_SCTP) { + if (r->src.port_op) { + yyerror("src port only applies to tcp/udp/sctp"); + problems++; + } + if (r->dst.port_op) { + yyerror("dst port only applies to tcp/udp/sctp"); + problems++; + } + if (r->rdr.proxy_port[0]) { + yyerror("rdr port only applies to tcp/udp/sctp"); + problems++; + } + } + if (r->dst.port_op && + r->dst.port_op != PF_OP_EQ && r->dst.port_op != PF_OP_RRG) { + yyerror("invalid port operator for rdr destination port"); + problems++; + } + return (-problems); +} + +int +process_tabledef(char *name, struct table_opts *opts, int popts) +{ + struct pfr_buffer ab; + struct node_tinit *ti; + struct pfr_uktable *ukt; + unsigned long maxcount; + size_t s = sizeof(maxcount); + + bzero(&ab, sizeof(ab)); + ab.pfrb_type = PFRB_ADDRS; + SIMPLEQ_FOREACH(ti, &opts->init_nodes, entries) { + if (ti->file) + if (pfr_buf_load(&ab, ti->file, 0, append_addr, popts)) { + if (errno) + yyerror("cannot load \"%s\": %s", + ti->file, strerror(errno)); + else + yyerror("file \"%s\" contains bad data", + ti->file); + goto _error; + } + if (ti->host) + if (append_addr_host(&ab, ti->host, 0, 0)) { + yyerror("cannot create address buffer: %s", + strerror(errno)); + goto _error; + } + } + if (pf->opts & PF_OPT_VERBOSE) + print_tabledef(name, opts->flags, opts->init_addr, + &opts->init_nodes); + if (!(pf->opts & PF_OPT_NOACTION) || + (pf->opts & PF_OPT_DUMMYACTION)) + warn_duplicate_tables(name, pf->anchor->path); + else if (pf->opts & PF_OPT_VERBOSE) + fprintf(stderr, "%s:%d: skipping duplicate table checks" + " for <%s>\n", file->name, yylval.lineno, name); + /* + * postpone definition of non-root tables to moment + * when path is fully resolved. + */ + if (pf->asd > 0) { + ukt = calloc(1, sizeof(struct pfr_uktable)); + if (ukt == NULL) { + DBGPRINT( + "%s:%d: not enough memory for <%s>\n", file->name, + yylval.lineno, name); + goto _error; + } + } else + ukt = NULL; + if (!(pf->opts & PF_OPT_NOACTION) && + pfctl_define_table(name, opts->flags, opts->init_addr, + pf->anchor->path, &ab, pf->anchor->ruleset.tticket, ukt)) { + + if (sysctlbyname("net.pf.request_maxcount", &maxcount, &s, + NULL, 0) == -1) + maxcount = 65535; + + if (ab.pfrb_size > maxcount) + yyerror("cannot define table %s: too many elements.\n" + "Consider increasing net.pf.request_maxcount.", + name); + else + yyerror("cannot define table %s: %s", name, + pf_strerror(errno)); + + goto _error; + } + + if (ukt != NULL) { + ukt->pfrukt_init_addr = opts->init_addr; + if (RB_INSERT(pfr_ktablehead, &pfr_ktables, + &ukt->pfrukt_kt) != NULL) { + /* + * I think this should not happen, because + * pfctl_define_table() above does the same check + * effectively. + */ + DBGPRINT( + "%s:%d table %s already exists in %s\n", + file->name, yylval.lineno, + ukt->pfrukt_name, pf->anchor->path); + free(ukt); + goto _error; + } + DBGPRINT("%s %s@%s inserted to tree\n", + __func__, ukt->pfrukt_name, pf->anchor->path); + } else + DBGPRINT("%s ukt is null\n", __func__); + + pf->tdirty = 1; + pfr_buf_clear(&ab); + return (0); +_error: + pfr_buf_clear(&ab); + return (-1); +} + +struct keywords { + const char *k_name; + int k_val; +}; + +/* macro gore, but you should've seen the prior indentation nightmare... */ + +#define FREE_LIST(T,r) \ + do { \ + T *p, *node = r; \ + while (node != NULL) { \ + p = node; \ + node = node->next; \ + free(p); \ + } \ + } while (0) + +#define LOOP_THROUGH(T,n,r,C) \ + do { \ + T *n; \ + if (r == NULL) { \ + r = calloc(1, sizeof(T)); \ + if (r == NULL) \ + err(1, "LOOP: calloc"); \ + r->next = NULL; \ + } \ + n = r; \ + while (n != NULL) { \ + do { \ + C; \ + } while (0); \ + n = n->next; \ + } \ + } while (0) + +void +expand_label_str(char *label, size_t len, const char *srch, const char *repl) +{ + char *tmp; + char *p, *q; + + if ((tmp = calloc(1, len)) == NULL) + err(1, "%s: calloc", __func__); + p = q = label; + while ((q = strstr(p, srch)) != NULL) { + *q = '\0'; + if ((strlcat(tmp, p, len) >= len) || + (strlcat(tmp, repl, len) >= len)) + errx(1, "%s: label too long", __func__); + q += strlen(srch); + p = q; + } + if (strlcat(tmp, p, len) >= len) + errx(1, "%s: label too long", __func__); + strlcpy(label, tmp, len); /* always fits */ + free(tmp); +} + +void +expand_label_if(const char *name, char *label, size_t len, const char *ifname) +{ + if (strstr(label, name) != NULL) { + if (!*ifname) + expand_label_str(label, len, name, "any"); + else + expand_label_str(label, len, name, ifname); + } +} + +void +expand_label_addr(const char *name, char *label, size_t len, sa_family_t af, + struct pf_rule_addr *addr) +{ + char tmp[64], tmp_not[66]; + + if (strstr(label, name) != NULL) { + switch (addr->addr.type) { + case PF_ADDR_DYNIFTL: + snprintf(tmp, sizeof(tmp), "(%s)", addr->addr.v.ifname); + break; + case PF_ADDR_TABLE: + snprintf(tmp, sizeof(tmp), "<%s>", addr->addr.v.tblname); + break; + case PF_ADDR_NOROUTE: + snprintf(tmp, sizeof(tmp), "no-route"); + break; + case PF_ADDR_URPFFAILED: + snprintf(tmp, sizeof(tmp), "urpf-failed"); + break; + case PF_ADDR_ADDRMASK: + if (!af || (PF_AZERO(&addr->addr.v.a.addr, af) && + PF_AZERO(&addr->addr.v.a.mask, af))) + snprintf(tmp, sizeof(tmp), "any"); + else { + char a[48]; + int bits; + + if (inet_ntop(af, &addr->addr.v.a.addr, a, + sizeof(a)) == NULL) + snprintf(tmp, sizeof(tmp), "?"); + else { + bits = unmask(&addr->addr.v.a.mask); + if ((af == AF_INET && bits < 32) || + (af == AF_INET6 && bits < 128)) + snprintf(tmp, sizeof(tmp), + "%s/%d", a, bits); + else + snprintf(tmp, sizeof(tmp), + "%s", a); + } + } + break; + default: + snprintf(tmp, sizeof(tmp), "?"); + break; + } + + if (addr->neg) { + snprintf(tmp_not, sizeof(tmp_not), "! %s", tmp); + expand_label_str(label, len, name, tmp_not); + } else + expand_label_str(label, len, name, tmp); + } +} + +void +expand_label_port(const char *name, char *label, size_t len, + struct pf_rule_addr *addr) +{ + char a1[6], a2[6], op[13] = ""; + + if (strstr(label, name) != NULL) { + snprintf(a1, sizeof(a1), "%u", ntohs(addr->port[0])); + snprintf(a2, sizeof(a2), "%u", ntohs(addr->port[1])); + if (!addr->port_op) + ; + else if (addr->port_op == PF_OP_IRG) + snprintf(op, sizeof(op), "%s><%s", a1, a2); + else if (addr->port_op == PF_OP_XRG) + snprintf(op, sizeof(op), "%s<>%s", a1, a2); + else if (addr->port_op == PF_OP_EQ) + snprintf(op, sizeof(op), "%s", a1); + else if (addr->port_op == PF_OP_NE) + snprintf(op, sizeof(op), "!=%s", a1); + else if (addr->port_op == PF_OP_LT) + snprintf(op, sizeof(op), "<%s", a1); + else if (addr->port_op == PF_OP_LE) + snprintf(op, sizeof(op), "<=%s", a1); + else if (addr->port_op == PF_OP_GT) + snprintf(op, sizeof(op), ">%s", a1); + else if (addr->port_op == PF_OP_GE) + snprintf(op, sizeof(op), ">=%s", a1); + expand_label_str(label, len, name, op); + } +} + +void +expand_label_proto(const char *name, char *label, size_t len, u_int8_t proto) +{ + const char *protoname; + char n[4]; + + if (strstr(label, name) != NULL) { + protoname = pfctl_proto2name(proto); + if (protoname != NULL) + expand_label_str(label, len, name, protoname); + else { + snprintf(n, sizeof(n), "%u", proto); + expand_label_str(label, len, name, n); + } + } +} + +void +expand_label_nr(const char *name, char *label, size_t len, + struct pfctl_rule *r) +{ + char n[11]; + + if (strstr(label, name) != NULL) { + snprintf(n, sizeof(n), "%u", r->nr); + expand_label_str(label, len, name, n); + } +} + +void +expand_label(char *label, size_t len, struct pfctl_rule *r) +{ + expand_label_if("$if", label, len, r->ifname); + expand_label_addr("$srcaddr", label, len, r->af, &r->src); + expand_label_addr("$dstaddr", label, len, r->af, &r->dst); + expand_label_port("$srcport", label, len, &r->src); + expand_label_port("$dstport", label, len, &r->dst); + expand_label_proto("$proto", label, len, r->proto); + expand_label_nr("$nr", label, len, r); +} + +int +expand_altq(struct pf_altq *a, struct node_if *interfaces, + struct node_queue *nqueues, struct node_queue_bw bwspec, + struct node_queue_opt *opts) +{ + struct pf_altq pa, pb; + char qname[PF_QNAME_SIZE]; + struct node_queue *n; + struct node_queue_bw bw; + int errs = 0; + + if ((pf->loadopt & PFCTL_FLAG_ALTQ) == 0) { + FREE_LIST(struct node_if, interfaces); + if (nqueues) + FREE_LIST(struct node_queue, nqueues); + return (0); + } + + LOOP_THROUGH(struct node_if, interface, interfaces, + memcpy(&pa, a, sizeof(struct pf_altq)); + if (strlcpy(pa.ifname, interface->ifname, + sizeof(pa.ifname)) >= sizeof(pa.ifname)) + errx(1, "%s: strlcpy", __func__); + + if (interface->not) { + yyerror("altq on ! <interface> is not supported"); + errs++; + } else { + if (eval_pfaltq(pf, &pa, &bwspec, opts)) + errs++; + else + if (pfctl_add_altq(pf, &pa)) + errs++; + + if (pf->opts & PF_OPT_VERBOSE) { + print_altq(&pf->paltq->altq, 0, + &bwspec, opts); + if (nqueues && nqueues->tail) { + printf("queue { "); + LOOP_THROUGH(struct node_queue, queue, + nqueues, + printf("%s ", + queue->queue); + ); + printf("}"); + } + printf("\n"); + } + + if (pa.scheduler == ALTQT_CBQ || + pa.scheduler == ALTQT_HFSC || + pa.scheduler == ALTQT_FAIRQ) { + /* now create a root queue */ + memset(&pb, 0, sizeof(struct pf_altq)); + if (strlcpy(qname, "root_", sizeof(qname)) >= + sizeof(qname)) + errx(1, "%s: strlcpy", __func__); + if (strlcat(qname, interface->ifname, + sizeof(qname)) >= sizeof(qname)) + errx(1, "%s: strlcat", __func__); + if (strlcpy(pb.qname, qname, + sizeof(pb.qname)) >= sizeof(pb.qname)) + errx(1, "%s: strlcpy", __func__); + if (strlcpy(pb.ifname, interface->ifname, + sizeof(pb.ifname)) >= sizeof(pb.ifname)) + errx(1, "%s: strlcpy", __func__); + pb.qlimit = pa.qlimit; + pb.scheduler = pa.scheduler; + bw.bw_absolute = pa.ifbandwidth; + bw.bw_percent = 0; + if (eval_pfqueue(pf, &pb, &bw, opts)) + errs++; + else + if (pfctl_add_altq(pf, &pb)) + errs++; + } + + LOOP_THROUGH(struct node_queue, queue, nqueues, + n = calloc(1, sizeof(struct node_queue)); + if (n == NULL) + err(1, "%s: calloc", __func__); + if (pa.scheduler == ALTQT_CBQ || + pa.scheduler == ALTQT_HFSC || + pa.scheduler == ALTQT_FAIRQ) + if (strlcpy(n->parent, qname, + sizeof(n->parent)) >= + sizeof(n->parent)) + errx(1, "%s: strlcpy", __func__); + if (strlcpy(n->queue, queue->queue, + sizeof(n->queue)) >= sizeof(n->queue)) + errx(1, "%s: strlcpy", __func__); + if (strlcpy(n->ifname, interface->ifname, + sizeof(n->ifname)) >= sizeof(n->ifname)) + errx(1, "%s: strlcpy", __func__); + n->scheduler = pa.scheduler; + n->next = NULL; + n->tail = n; + if (queues == NULL) + queues = n; + else { + queues->tail->next = n; + queues->tail = n; + } + ); + } + ); + FREE_LIST(struct node_if, interfaces); + if (nqueues) + FREE_LIST(struct node_queue, nqueues); + + return (errs); +} + +int +expand_queue(struct pf_altq *a, struct node_if *interfaces, + struct node_queue *nqueues, struct node_queue_bw bwspec, + struct node_queue_opt *opts) +{ + struct node_queue *n, *nq; + struct pf_altq pa; + u_int8_t found = 0; + u_int8_t errs = 0; + + if ((pf->loadopt & PFCTL_FLAG_ALTQ) == 0) { + FREE_LIST(struct node_queue, nqueues); + return (0); + } + + if (queues == NULL) { + yyerror("queue %s has no parent", a->qname); + FREE_LIST(struct node_queue, nqueues); + return (1); + } + + LOOP_THROUGH(struct node_if, interface, interfaces, + LOOP_THROUGH(struct node_queue, tqueue, queues, + if (!strncmp(a->qname, tqueue->queue, PF_QNAME_SIZE) && + (interface->ifname[0] == 0 || + (!interface->not && !strncmp(interface->ifname, + tqueue->ifname, IFNAMSIZ)) || + (interface->not && strncmp(interface->ifname, + tqueue->ifname, IFNAMSIZ)))) { + /* found ourself in queues */ + found++; + + memcpy(&pa, a, sizeof(struct pf_altq)); + + if (pa.scheduler != ALTQT_NONE && + pa.scheduler != tqueue->scheduler) { + yyerror("exactly one scheduler type " + "per interface allowed"); + return (1); + } + pa.scheduler = tqueue->scheduler; + + /* scheduler dependent error checking */ + switch (pa.scheduler) { + case ALTQT_PRIQ: + if (nqueues != NULL) { + yyerror("priq queues cannot " + "have child queues"); + return (1); + } + if (bwspec.bw_absolute > 0 || + bwspec.bw_percent < 100) { + yyerror("priq doesn't take " + "bandwidth"); + return (1); + } + break; + default: + break; + } + + if (strlcpy(pa.ifname, tqueue->ifname, + sizeof(pa.ifname)) >= sizeof(pa.ifname)) + errx(1, "%s: strlcpy", __func__); + if (strlcpy(pa.parent, tqueue->parent, + sizeof(pa.parent)) >= sizeof(pa.parent)) + errx(1, "%s: strlcpy", __func__); + + if (eval_pfqueue(pf, &pa, &bwspec, opts)) + errs++; + else + if (pfctl_add_altq(pf, &pa)) + errs++; + + for (nq = nqueues; nq != NULL; nq = nq->next) { + if (!strcmp(a->qname, nq->queue)) { + yyerror("queue cannot have " + "itself as child"); + errs++; + continue; + } + n = calloc(1, + sizeof(struct node_queue)); + if (n == NULL) + err(1, "%s: calloc", __func__); + if (strlcpy(n->parent, a->qname, + sizeof(n->parent)) >= + sizeof(n->parent)) + errx(1, "%s strlcpy", __func__); + if (strlcpy(n->queue, nq->queue, + sizeof(n->queue)) >= + sizeof(n->queue)) + errx(1, "%s strlcpy", __func__); + if (strlcpy(n->ifname, tqueue->ifname, + sizeof(n->ifname)) >= + sizeof(n->ifname)) + errx(1, "%s strlcpy", __func__); + n->scheduler = tqueue->scheduler; + n->next = NULL; + n->tail = n; + if (queues == NULL) + queues = n; + else { + queues->tail->next = n; + queues->tail = n; + } + } + if ((pf->opts & PF_OPT_VERBOSE) && ( + (found == 1 && interface->ifname[0] == 0) || + (found > 0 && interface->ifname[0] != 0))) { + print_queue(&pf->paltq->altq, 0, + &bwspec, interface->ifname[0] != 0, + opts); + if (nqueues && nqueues->tail) { + printf("{ "); + LOOP_THROUGH(struct node_queue, + queue, nqueues, + printf("%s ", + queue->queue); + ); + printf("}"); + } + printf("\n"); + } + } + ); + ); + + FREE_LIST(struct node_queue, nqueues); + FREE_LIST(struct node_if, interfaces); + + if (!found) { + yyerror("queue %s has no parent", a->qname); + errs++; + } + + if (errs) + return (1); + else + return (0); +} + +static int +pf_af_to_proto(sa_family_t af) +{ + if (af == AF_INET) + return (ETHERTYPE_IP); + if (af == AF_INET6) + return (ETHERTYPE_IPV6); + + return (0); +} + +void +expand_eth_rule(struct pfctl_eth_rule *r, + struct node_if *interfaces, struct node_etherproto *protos, + struct node_mac *srcs, struct node_mac *dsts, + struct node_host *ipsrcs, struct node_host *ipdsts, + const char *bridge_to, const char *anchor_call) +{ + char tagname[PF_TAG_NAME_SIZE]; + char match_tagname[PF_TAG_NAME_SIZE]; + char qname[PF_QNAME_SIZE]; + + if (strlcpy(tagname, r->tagname, sizeof(tagname)) >= sizeof(tagname)) + errx(1, "%s: tagname", __func__); + if (strlcpy(match_tagname, r->match_tagname, sizeof(match_tagname)) >= + sizeof(match_tagname)) + errx(1, "%s: match_tagname", __func__); + if (strlcpy(qname, r->qname, sizeof(qname)) >= sizeof(qname)) + errx(1, "%s: qname", __func__); + + LOOP_THROUGH(struct node_if, interface, interfaces, + LOOP_THROUGH(struct node_etherproto, proto, protos, + LOOP_THROUGH(struct node_mac, src, srcs, + LOOP_THROUGH(struct node_mac, dst, dsts, + LOOP_THROUGH(struct node_host, ipsrc, ipsrcs, + LOOP_THROUGH(struct node_host, ipdst, ipdsts, + strlcpy(r->ifname, interface->ifname, + sizeof(r->ifname)); + r->ifnot = interface->not; + r->proto = proto->proto; + if (!r->proto && ipsrc->af) + r->proto = pf_af_to_proto(ipsrc->af); + else if (!r->proto && ipdst->af) + r->proto = pf_af_to_proto(ipdst->af); + bcopy(src->mac, r->src.addr, ETHER_ADDR_LEN); + bcopy(src->mask, r->src.mask, ETHER_ADDR_LEN); + r->src.neg = src->neg; + r->src.isset = src->isset; + r->ipsrc.addr = ipsrc->addr; + r->ipsrc.neg = ipsrc->not; + r->ipdst.addr = ipdst->addr; + r->ipdst.neg = ipdst->not; + bcopy(dst->mac, r->dst.addr, ETHER_ADDR_LEN); + bcopy(dst->mask, r->dst.mask, ETHER_ADDR_LEN); + r->dst.neg = dst->neg; + r->dst.isset = dst->isset; + r->nr = pf->eastack[pf->asd]->match++; + + if (strlcpy(r->tagname, tagname, sizeof(r->tagname)) >= + sizeof(r->tagname)) + errx(1, "%s: r->tagname", __func__); + if (strlcpy(r->match_tagname, match_tagname, + sizeof(r->match_tagname)) >= sizeof(r->match_tagname)) + errx(1, "%s: r->match_tagname", __func__); + if (strlcpy(r->qname, qname, sizeof(r->qname)) >= sizeof(r->qname)) + errx(1, "%s: r->qname", __func__); + + if (bridge_to) + strlcpy(r->bridge_to, bridge_to, sizeof(r->bridge_to)); + + pfctl_append_eth_rule(pf, r, anchor_call); + )))))); + + FREE_LIST(struct node_if, interfaces); + FREE_LIST(struct node_etherproto, protos); + FREE_LIST(struct node_mac, srcs); + FREE_LIST(struct node_mac, dsts); + FREE_LIST(struct node_host, ipsrcs); + FREE_LIST(struct node_host, ipdsts); +} + +int +apply_rdr_ports(struct pfctl_rule *r, struct pfctl_pool *rpool, struct redirspec *rs) +{ + if (rs == NULL) + return 0; + + rpool->proxy_port[0] = ntohs(rs->rport.a); + + if (!rs->rport.b && rs->rport.t) { + rpool->proxy_port[1] = ntohs(rs->rport.a) + + (ntohs(r->dst.port[1]) - ntohs(r->dst.port[0])); + } else { + if (validate_range(rs->rport.t, rs->rport.a, + rs->rport.b)) { + yyerror("invalid rdr-to port range"); + return (1); + } + r->rdr.proxy_port[1] = ntohs(rs->rport.b); + } + + if (rs->pool_opts.staticport) { + yyerror("the 'static-port' option is only valid with nat rules"); + return 1; + } + + if (rs->pool_opts.mape.offset) { + yyerror("the 'map-e-portset' option is only valid with nat rules"); + return 1; + } + + return 0; +} + +int +apply_nat_ports(struct pfctl_pool *rpool, struct redirspec *rs) +{ + if (rs == NULL) + return 0; + + rpool->proxy_port[0] = ntohs(rs->rport.a); + rpool->proxy_port[1] = ntohs(rs->rport.b); + if (!rpool->proxy_port[0] && !rpool->proxy_port[1]) { + rpool->proxy_port[0] = PF_NAT_PROXY_PORT_LOW; + rpool->proxy_port[1] = PF_NAT_PROXY_PORT_HIGH; + } else if (!rpool->proxy_port[1]) + rpool->proxy_port[1] = rpool->proxy_port[0]; + + if (rs->pool_opts.staticport) { + if (rpool->proxy_port[0] != PF_NAT_PROXY_PORT_LOW && + rpool->proxy_port[1] != PF_NAT_PROXY_PORT_HIGH) { + yyerror("the 'static-port' option can't" + " be used when specifying a port" + " range"); + return 1; + } + rpool->proxy_port[0] = 0; + rpool->proxy_port[1] = 0; + } + + if (rs->pool_opts.mape.offset) { + if (rs->pool_opts.staticport) { + yyerror("the 'map-e-portset' option" + " can't be used 'static-port'"); + return 1; + } + if (rpool->proxy_port[0] != PF_NAT_PROXY_PORT_LOW && + rpool->proxy_port[1] != PF_NAT_PROXY_PORT_HIGH) { + yyerror("the 'map-e-portset' option" + " can't be used when specifying" + " a port range"); + return 1; + } + rpool->mape = rs->pool_opts.mape; + } + + return 0; +} + +int +apply_redirspec(struct pfctl_pool *rpool, struct redirspec *rs) +{ + struct node_host *h; + struct pfctl_pooladdr *pa; + + if (rs == NULL) + return 0; + + rpool->opts = rs->pool_opts.type; + + if ((rpool->opts & PF_POOL_TYPEMASK) == PF_POOL_NONE && + (rs->host->next != NULL || + rs->host->addr.type == PF_ADDR_TABLE || + DYNIF_MULTIADDR(rs->host->addr))) + rpool->opts = PF_POOL_ROUNDROBIN; + + if (!PF_POOL_DYNTYPE(rpool->opts) && + (disallow_table(rs->host, "tables are not supported by pool type") || + disallow_alias(rs->host, "interface (%s) is not supported by pool type"))) + return 1; + + if (rs->host->next != NULL && + ((rpool->opts & PF_POOL_TYPEMASK) != PF_POOL_ROUNDROBIN)) { + yyerror("r.route.opts must be PF_POOL_ROUNDROBIN"); + return 1; + } + + if (rs->host->next != NULL) { + if ((rpool->opts & PF_POOL_TYPEMASK) != + PF_POOL_ROUNDROBIN) { + yyerror("only round-robin valid for multiple " + "redirection addresses"); + return 1; + } + } + + rpool->opts |= rs->pool_opts.opts; + + if (rs->pool_opts.key != NULL) + memcpy(&(rpool->key), rs->pool_opts.key, + sizeof(struct pf_poolhashkey)); + + for (h = rs->host; h != NULL; h = h->next) { + pa = calloc(1, sizeof(struct pfctl_pooladdr)); + if (pa == NULL) + err(1, "%s: calloc", __func__); + pa->addr = h->addr; + pa->af = h->af; + if (h->ifname != NULL) { + if (strlcpy(pa->ifname, h->ifname, + sizeof(pa->ifname)) >= sizeof(pa->ifname)) + errx(1, "%s: strlcpy", __func__); + } else + pa->ifname[0] = 0; + TAILQ_INSERT_TAIL(&(rpool->list), pa, entries); + } + + return 0; +} + +int +check_binat_redirspec(struct node_host *src_host, struct pfctl_rule *r, + sa_family_t af) +{ + struct pfctl_pooladdr *nat_pool = TAILQ_FIRST(&(r->nat.list)); + int error = 0; + + /* XXX: FreeBSD allows syntax like "{ host1 host2 }" for redirection + * pools but does not covert them to tables automatically, because + * syntax "{ (iface1 host1), (iface2 iface2) }" is allowed for route-to + * redirection. Add a FreeBSD-specific guard against using multiple + * hosts for source and redirection. + */ + if (src_host->next) { + yyerror("invalid use of table as the source address " + "of a binat-to rule"); + error++; + } + if (TAILQ_NEXT(nat_pool, entries)) { + yyerror ("tables cannot be used as the redirect " + "address of a binat-to rule"); + error++; + } + + if (disallow_table(src_host, "invalid use of table " + "<%s> as the source address of a binat-to rule") || + disallow_alias(src_host, "invalid use of interface " + "(%s) as the source address of a binat-to rule")) { + error++; + } else if ((r->src.addr.type != PF_ADDR_ADDRMASK && + r->src.addr.type != PF_ADDR_DYNIFTL) || + (nat_pool->addr.type != PF_ADDR_ADDRMASK && + nat_pool->addr.type != PF_ADDR_DYNIFTL)) { + yyerror("binat-to requires a specified " + "source and redirect address"); + error++; + } + if (DYNIF_MULTIADDR(r->src.addr) || + DYNIF_MULTIADDR(nat_pool->addr)) { + yyerror ("dynamic interfaces must be " + "used with:0 in a binat-to rule"); + error++; + } + if (PF_AZERO(&r->src.addr.v.a.mask, af) || + PF_AZERO(&(nat_pool->addr.v.a.mask), af)) { + yyerror ("source and redir addresses must have " + "a matching network mask in binat-rule"); + error++; + } + if (nat_pool->addr.type == PF_ADDR_TABLE) { + yyerror ("tables cannot be used as the redirect " + "address of a binat-to rule"); + error++; + } + if (r->direction != PF_INOUT) { + yyerror("binat-to cannot be specified " + "with a direction"); + error++; + } + + /* first specify outbound NAT rule */ + r->direction = PF_OUT; + + return (error); +} + +void +add_binat_rdr_rule( + struct pfctl_rule *binat_rule, + struct redirspec *binat_nat_redirspec, struct node_host *binat_src_host, + struct pfctl_rule *rdr_rule, struct redirspec **rdr_redirspec, + struct node_host **rdr_dst_host) +{ + struct node_host *rdr_src_host; + + /* + * We're copying the whole rule, but we must re-init redir pools. + * FreeBSD uses lists of pfctl_pooladdr, we can't just overwrite them. + */ + bcopy(binat_rule, rdr_rule, sizeof(struct pfctl_rule)); + TAILQ_INIT(&(rdr_rule->rdr.list)); + TAILQ_INIT(&(rdr_rule->nat.list)); + + /* now specify inbound rdr rule */ + rdr_rule->direction = PF_IN; + + if ((rdr_src_host = calloc(1, sizeof(*rdr_src_host))) == NULL) + err(1, "%s", __func__); + bcopy(binat_src_host, rdr_src_host, sizeof(*rdr_src_host)); + rdr_src_host->ifname = NULL; + rdr_src_host->next = NULL; + rdr_src_host->tail = NULL; + + if (((*rdr_dst_host) = calloc(1, sizeof(**rdr_dst_host))) == NULL) + err(1, "%s", __func__); + bcopy(&(binat_nat_redirspec->host->addr), &((*rdr_dst_host)->addr), + sizeof((*rdr_dst_host)->addr)); + (*rdr_dst_host)->ifname = NULL; + (*rdr_dst_host)->next = NULL; + (*rdr_dst_host)->tail = NULL; + + if (((*rdr_redirspec) = calloc(1, sizeof(**rdr_redirspec))) == NULL) + err(1, "%s", __func__); + bcopy(binat_nat_redirspec, (*rdr_redirspec), sizeof(**rdr_redirspec)); + (*rdr_redirspec)->pool_opts.staticport = 0; + (*rdr_redirspec)->host = rdr_src_host; +} + +void +expand_rule(struct pfctl_rule *r, bool keeprule, + struct node_if *interfaces, struct redirspec *nat, + struct redirspec *rdr, struct redirspec *route, + struct node_proto *protos, + struct node_os *src_oses, struct node_host *src_hosts, + struct node_port *src_ports, struct node_host *dst_hosts, + struct node_port *dst_ports, struct node_uid *uids, struct node_gid *gids, + struct node_if *rcv, struct node_icmp *icmp_types) +{ + sa_family_t af = r->af; + int added = 0, error = 0; + char ifname[IF_NAMESIZE]; + char label[PF_RULE_MAX_LABEL_COUNT][PF_RULE_LABEL_SIZE]; + char tagname[PF_TAG_NAME_SIZE]; + char match_tagname[PF_TAG_NAME_SIZE]; + struct node_host *osrch, *odsth; + u_int8_t flags, flagset, keep_state; + + memcpy(label, r->label, sizeof(r->label)); + assert(sizeof(r->label) == sizeof(label)); + if (strlcpy(tagname, r->tagname, sizeof(tagname)) >= sizeof(tagname)) + errx(1, "%s: strlcpy", __func__); + if (strlcpy(match_tagname, r->match_tagname, sizeof(match_tagname)) >= + sizeof(match_tagname)) + errx(1, "%s: strlcpy", __func__); + flags = r->flags; + flagset = r->flagset; + keep_state = r->keep_state; + + LOOP_THROUGH(struct node_if, interface, interfaces, + LOOP_THROUGH(struct node_proto, proto, protos, + LOOP_THROUGH(struct node_icmp, icmp_type, icmp_types, + LOOP_THROUGH(struct node_host, src_host, src_hosts, + LOOP_THROUGH(struct node_host, dst_host, dst_hosts, + LOOP_THROUGH(struct node_port, src_port, src_ports, + LOOP_THROUGH(struct node_port, dst_port, dst_ports, + LOOP_THROUGH(struct node_os, src_os, src_oses, + LOOP_THROUGH(struct node_uid, uid, uids, + LOOP_THROUGH(struct node_gid, gid, gids, + + r->af = af; + + if (r->rule_flag & PFRULE_AFTO) { + assert(nat != NULL); + r->naf = nat->af; + } + + /* for link-local IPv6 address, interface must match up */ + if ((r->af && src_host->af && r->af != src_host->af) || + (r->af && dst_host->af && r->af != dst_host->af) || + (src_host->af && dst_host->af && + src_host->af != dst_host->af) || + (src_host->ifindex && dst_host->ifindex && + src_host->ifindex != dst_host->ifindex) || + (src_host->ifindex && *interface->ifname && + src_host->ifindex != ifa_nametoindex(interface->ifname)) || + (dst_host->ifindex && *interface->ifname && + dst_host->ifindex != ifa_nametoindex(interface->ifname))) + continue; + if (!r->af && src_host->af) + r->af = src_host->af; + else if (!r->af && dst_host->af) + r->af = dst_host->af; + + if (*interface->ifname) + strlcpy(r->ifname, interface->ifname, + sizeof(r->ifname)); + else if (ifa_indextoname(src_host->ifindex, ifname)) + strlcpy(r->ifname, ifname, sizeof(r->ifname)); + else if (ifa_indextoname(dst_host->ifindex, ifname)) + strlcpy(r->ifname, ifname, sizeof(r->ifname)); + else + memset(r->ifname, '\0', sizeof(r->ifname)); + + memcpy(r->label, label, sizeof(r->label)); + if (strlcpy(r->tagname, tagname, sizeof(r->tagname)) >= + sizeof(r->tagname)) + errx(1, "%s: strlcpy", __func__); + if (strlcpy(r->match_tagname, match_tagname, + sizeof(r->match_tagname)) >= sizeof(r->match_tagname)) + errx(1, "%s: strlcpy", __func__); + + osrch = odsth = NULL; + if (src_host->addr.type == PF_ADDR_DYNIFTL) { + osrch = src_host; + if ((src_host = gen_dynnode(src_host, r->af)) == NULL) + err(1, "%s: calloc", __func__); + } + if (dst_host->addr.type == PF_ADDR_DYNIFTL) { + odsth = dst_host; + if ((dst_host = gen_dynnode(dst_host, r->af)) == NULL) + err(1, "%s: calloc", __func__); + } + + error += check_netmask(src_host, r->af); + error += check_netmask(dst_host, r->af); + + r->ifnot = interface->not; + r->proto = proto->proto; + r->src.addr = src_host->addr; + r->src.neg = src_host->not; + r->src.port[0] = src_port->port[0]; + r->src.port[1] = src_port->port[1]; + r->src.port_op = src_port->op; + r->dst.addr = dst_host->addr; + r->dst.neg = dst_host->not; + r->dst.port[0] = dst_port->port[0]; + r->dst.port[1] = dst_port->port[1]; + r->dst.port_op = dst_port->op; + r->uid.op = uid->op; + r->uid.uid[0] = uid->uid[0]; + r->uid.uid[1] = uid->uid[1]; + r->gid.op = gid->op; + r->gid.gid[0] = gid->gid[0]; + r->gid.gid[1] = gid->gid[1]; + if (rcv) { + strlcpy(r->rcv_ifname, rcv->ifname, + sizeof(r->rcv_ifname)); + r->rcvifnot = rcv->not; + } + r->type = icmp_type->type; + r->code = icmp_type->code; + + if ((keep_state == PF_STATE_MODULATE || + keep_state == PF_STATE_SYNPROXY) && + r->proto && r->proto != IPPROTO_TCP) + r->keep_state = PF_STATE_NORMAL; + else + r->keep_state = keep_state; + + if (r->proto && r->proto != IPPROTO_TCP) { + r->flags = 0; + r->flagset = 0; + } else { + r->flags = flags; + r->flagset = flagset; + } + if (icmp_type->proto && r->proto != icmp_type->proto) { + yyerror("icmp-type mismatch"); + error++; + } + + if (src_os && src_os->os) { + r->os_fingerprint = pfctl_get_fingerprint(src_os->os); + if ((pf->opts & PF_OPT_VERBOSE2) && + r->os_fingerprint == PF_OSFP_NOMATCH) + fprintf(stderr, + "warning: unknown '%s' OS fingerprint\n", + src_os->os); + } else { + r->os_fingerprint = PF_OSFP_ANY; + } + + if (r->action == PF_RDR) { + /* Pre-FreeBSD 15 "rdr" rule */ + error += apply_rdr_ports(r, &(r->rdr), rdr); + error += apply_redirspec(&(r->rdr), rdr); + } else if (r->action == PF_NAT) { + /* Pre-FreeBSD 15 "nat" rule */ + error += apply_nat_ports(&(r->rdr), rdr); + error += apply_redirspec(&(r->rdr), rdr); + } else { + /* Modern rule with optional NAT, BINAT, RDR or ROUTE*/ + error += apply_redirspec(&(r->route), route); + + error += apply_nat_ports(&(r->nat), nat); + error += apply_redirspec(&(r->nat), nat); + error += apply_rdr_ports(r, &(r->rdr), rdr); + error += apply_redirspec(&(r->rdr), rdr); + + if (nat && nat->binat) + error += check_binat_redirspec(src_host, r, af); + } + + if (rule_consistent(r) < 0 || error) + yyerror("skipping rule due to errors"); + else { + r->nr = pf->astack[pf->asd]->match++; + pfctl_append_rule(pf, r); + added++; + } + + /* Generate binat's matching inbound rule */ + if (!error && nat && nat->binat) { + struct pfctl_rule rdr_rule; + struct redirspec *rdr_redirspec; + struct node_host *rdr_dst_host; + + add_binat_rdr_rule( + r, nat, src_hosts, + &rdr_rule, &rdr_redirspec, &rdr_dst_host); + + expand_rule(&rdr_rule, true, interface, NULL, rdr_redirspec, + NULL, proto, src_os, dst_host, dst_port, + rdr_dst_host, src_port, uid, gid, rcv, icmp_type); + } + + if (osrch && src_host->addr.type == PF_ADDR_DYNIFTL) { + free(src_host); + src_host = osrch; + } + if (odsth && dst_host->addr.type == PF_ADDR_DYNIFTL) { + free(dst_host); + dst_host = odsth; + } + + )))))))))); + + if (!keeprule) { + FREE_LIST(struct node_if, interfaces); + FREE_LIST(struct node_proto, protos); + FREE_LIST(struct node_host, src_hosts); + FREE_LIST(struct node_port, src_ports); + FREE_LIST(struct node_os, src_oses); + FREE_LIST(struct node_host, dst_hosts); + FREE_LIST(struct node_port, dst_ports); + FREE_LIST(struct node_uid, uids); + FREE_LIST(struct node_gid, gids); + FREE_LIST(struct node_icmp, icmp_types); + if (nat) { + FREE_LIST(struct node_host, nat->host); + free(nat); + } + if (rdr) { + FREE_LIST(struct node_host, rdr->host); + free(rdr); + } + if (route) { + FREE_LIST(struct node_host, route->host); + free(route); + } + } + + if (!added) + yyerror("rule expands to no valid combination"); +} + +int +expand_skip_interface(struct node_if *interfaces) +{ + int errs = 0; + + if (!interfaces || (!interfaces->next && !interfaces->not && + !strcmp(interfaces->ifname, "none"))) { + if (pf->opts & PF_OPT_VERBOSE) + printf("set skip on none\n"); + errs = pfctl_set_interface_flags(pf, "", PFI_IFLAG_SKIP, 0); + return (errs); + } + + if (pf->opts & PF_OPT_VERBOSE) + printf("set skip on {"); + LOOP_THROUGH(struct node_if, interface, interfaces, + if (pf->opts & PF_OPT_VERBOSE) + printf(" %s", interface->ifname); + if (interface->not) { + yyerror("skip on ! <interface> is not supported"); + errs++; + } else + errs += pfctl_set_interface_flags(pf, + interface->ifname, PFI_IFLAG_SKIP, 1); + ); + if (pf->opts & PF_OPT_VERBOSE) + printf(" }\n"); + + FREE_LIST(struct node_if, interfaces); + + if (errs) + return (1); + else + return (0); +} + +void +freehostlist(struct node_host *h) +{ + FREE_LIST(struct node_host, h); +} + +#undef FREE_LIST +#undef LOOP_THROUGH + +int +check_rulestate(int desired_state) +{ + if (require_order && (rulestate > desired_state)) { + yyerror("Rules must be in order: options, ethernet, " + "normalization, queueing, translation, filtering"); + return (1); + } + rulestate = desired_state; + return (0); +} + +int +kw_cmp(const void *k, const void *e) +{ + return (strcmp(k, ((const struct keywords *)e)->k_name)); +} + +int +lookup(char *s) +{ + /* this has to be sorted always */ + static const struct keywords keywords[] = { + { "af-to", AFTO}, + { "all", ALL}, + { "allow-opts", ALLOWOPTS}, + { "allow-related", ALLOW_RELATED}, + { "altq", ALTQ}, + { "anchor", ANCHOR}, + { "antispoof", ANTISPOOF}, + { "any", ANY}, + { "bandwidth", BANDWIDTH}, + { "binat", BINAT}, + { "binat-anchor", BINATANCHOR}, + { "binat-to", BINATTO}, + { "bitmask", BITMASK}, + { "block", BLOCK}, + { "block-policy", BLOCKPOLICY}, + { "bridge-to", BRIDGE_TO}, + { "buckets", BUCKETS}, + { "cbq", CBQ}, + { "code", CODE}, + { "codelq", CODEL}, + { "debug", DEBUG}, + { "divert-reply", DIVERTREPLY}, + { "divert-to", DIVERTTO}, + { "dnpipe", DNPIPE}, + { "dnqueue", DNQUEUE}, + { "drop", DROP}, + { "dup-to", DUPTO}, + { "endpoint-independent", ENDPI}, + { "ether", ETHER}, + { "fail-policy", FAILPOLICY}, + { "fairq", FAIRQ}, + { "fastroute", FASTROUTE}, + { "file", FILENAME}, + { "fingerprints", FINGERPRINTS}, + { "flags", FLAGS}, + { "floating", FLOATING}, + { "flush", FLUSH}, + { "for", FOR}, + { "fragment", FRAGMENT}, + { "from", FROM}, + { "global", GLOBAL}, + { "group", GROUP}, + { "hfsc", HFSC}, + { "hogs", HOGS}, + { "hostid", HOSTID}, + { "icmp-type", ICMPTYPE}, + { "icmp6-type", ICMP6TYPE}, + { "if-bound", IFBOUND}, + { "in", IN}, + { "include", INCLUDE}, + { "inet", INET}, + { "inet6", INET6}, + { "interval", INTERVAL}, + { "keep", KEEP}, + { "keepcounters", KEEPCOUNTERS}, + { "l3", L3}, + { "label", LABEL}, + { "limit", LIMIT}, + { "linkshare", LINKSHARE}, + { "load", LOAD}, + { "log", LOG}, + { "loginterface", LOGINTERFACE}, + { "map-e-portset", MAPEPORTSET}, + { "match", MATCH}, + { "matches", MATCHES}, + { "max", MAXIMUM}, + { "max-mss", MAXMSS}, + { "max-pkt-rate", MAXPKTRATE}, + { "max-pkt-size", MAXPKTSIZE}, + { "max-src-conn", MAXSRCCONN}, + { "max-src-conn-rate", MAXSRCCONNRATE}, + { "max-src-nodes", MAXSRCNODES}, + { "max-src-states", MAXSRCSTATES}, + { "min-ttl", MINTTL}, + { "modulate", MODULATE}, + { "nat", NAT}, + { "nat-anchor", NATANCHOR}, + { "nat-to", NATTO}, + { "no", NO}, + { "no-df", NODF}, + { "no-route", NOROUTE}, + { "no-sync", NOSYNC}, + { "on", ON}, + { "once", ONCE}, + { "optimization", OPTIMIZATION}, + { "os", OS}, + { "out", OUT}, + { "overload", OVERLOAD}, + { "pass", PASS}, + { "pflow", PFLOW}, + { "port", PORT}, + { "prefer-ipv6-nexthop", IPV6NH}, + { "prio", PRIO}, + { "priority", PRIORITY}, + { "priq", PRIQ}, + { "probability", PROBABILITY}, + { "proto", PROTO}, + { "qlimit", QLIMIT}, + { "queue", QUEUE}, + { "quick", QUICK}, + { "random", RANDOM}, + { "random-id", RANDOMID}, + { "rdr", RDR}, + { "rdr-anchor", RDRANCHOR}, + { "rdr-to", RDRTO}, + { "realtime", REALTIME}, + { "reassemble", REASSEMBLE}, + { "received-on", RECEIVEDON}, + { "reply-to", REPLYTO}, + { "require-order", REQUIREORDER}, + { "return", RETURN}, + { "return-icmp", RETURNICMP}, + { "return-icmp6", RETURNICMP6}, + { "return-rst", RETURNRST}, + { "ridentifier", RIDENTIFIER}, + { "round-robin", ROUNDROBIN}, + { "route", ROUTE}, + { "route-to", ROUTETO}, + { "rtable", RTABLE}, + { "rule", RULE}, + { "ruleset-optimization", RULESET_OPTIMIZATION}, + { "scrub", SCRUB}, + { "set", SET}, + { "set-tos", SETTOS}, + { "skip", SKIP}, + { "sloppy", SLOPPY}, + { "source-hash", SOURCEHASH}, + { "source-track", SOURCETRACK}, + { "state", STATE}, + { "state-defaults", STATEDEFAULTS}, + { "state-policy", STATEPOLICY}, + { "static-port", STATICPORT}, + { "sticky-address", STICKYADDRESS}, + { "syncookies", SYNCOOKIES}, + { "synproxy", SYNPROXY}, + { "table", TABLE}, + { "tag", TAG}, + { "tagged", TAGGED}, + { "target", TARGET}, + { "tbrsize", TBRSIZE}, + { "timeout", TIMEOUT}, + { "to", TO}, + { "tos", TOS}, + { "ttl", TTL}, + { "upperlimit", UPPERLIMIT}, + { "urpf-failed", URPFFAILED}, + { "user", USER}, + }; + const struct keywords *p; + + p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]), + sizeof(keywords[0]), kw_cmp); + + if (p) { + if (debug > 1) + fprintf(stderr, "%s: %d\n", s, p->k_val); + return (p->k_val); + } else { + if (debug > 1) + fprintf(stderr, "string: %s\n", s); + return (STRING); + } +} + +#define START_EXPAND 1 +#define DONE_EXPAND 2 + +static int expanding; + +int +igetc(void) +{ + int c; + while (1) { + if (file->ungetpos > 0) + c = file->ungetbuf[--file->ungetpos]; + else + c = getc(file->stream); + if (c == START_EXPAND) + expanding = 1; + else if (c == DONE_EXPAND) + expanding = 0; + else + break; + } + return (c); +} + +int +lgetc(int quotec) +{ + int c, next; + + if (quotec) { + if ((c = igetc()) == EOF) { + yyerror("reached end of file while parsing quoted string"); + if (file == topfile || popfile() == EOF) + return (EOF); + return (quotec); + } + return (c); + } + + while ((c = igetc()) == '\\') { + next = igetc(); + if (next != '\n') { + c = next; + break; + } + yylval.lineno = file->lineno; + file->lineno++; + } + + if (c == EOF) { + /* + * Fake EOL when hit EOF for the first time. This gets line + * count right if last line in included file is syntactically + * invalid and has no newline. + */ + if (file->eof_reached == 0) { + file->eof_reached = 1; + return ('\n'); + } + while (c == EOF) { + if (file == topfile || popfile() == EOF) + return (EOF); + c = igetc(); + } + } + return (c); +} + +void +lungetc(int c) +{ + if (c == EOF) + return; + if (file->ungetpos >= file->ungetsize) { + void *p = reallocarray(file->ungetbuf, file->ungetsize, 2); + if (p == NULL) + err(1, "%s", __func__); + file->ungetbuf = p; + file->ungetsize *= 2; + } + file->ungetbuf[file->ungetpos++] = c; +} + +int +findeol(void) +{ + int c; + + /* skip to either EOF or the first real EOL */ + while (1) { + c = lgetc(0); + if (c == '\n') { + file->lineno++; + break; + } + if (c == EOF) + break; + } + return (ERROR); +} + +int +yylex(void) +{ + char buf[8096]; + char *p, *val; + int quotec, next, c; + int token; + +top: + p = buf; + while ((c = lgetc(0)) == ' ' || c == '\t') + ; /* nothing */ + + yylval.lineno = file->lineno; + if (c == '#') + while ((c = lgetc(0)) != '\n' && c != EOF) + ; /* nothing */ + if (c == '$' && !expanding) { + while (1) { + if ((c = lgetc(0)) == EOF) + return (0); + + if (p + 1 >= buf + sizeof(buf) - 1) { + yyerror("string too long"); + return (findeol()); + } + if (isalnum(c) || c == '_') { + *p++ = (char)c; + continue; + } + *p = '\0'; + lungetc(c); + break; + } + val = symget(buf); + if (val == NULL) { + yyerror("macro '%s' not defined", buf); + return (findeol()); + } + p = val + strlen(val) - 1; + lungetc(DONE_EXPAND); + while (p >= val) { + lungetc(*p); + p--; + } + lungetc(START_EXPAND); + goto top; + } + + switch (c) { + case '\'': + case '"': + quotec = c; + while (1) { + if ((c = lgetc(quotec)) == EOF) + return (0); + if (c == '\n') { + file->lineno++; + continue; + } else if (c == '\\') { + if ((next = lgetc(quotec)) == EOF) + return (0); + if (next == quotec || next == ' ' || + next == '\t') + c = next; + else if (next == '\n') { + file->lineno++; + continue; + } + else + lungetc(next); + } else if (c == quotec) { + *p = '\0'; + break; + } else if (c == '\0') { + yyerror("syntax error"); + return (findeol()); + } + if (p + 1 >= buf + sizeof(buf) - 1) { + yyerror("string too long"); + return (findeol()); + } + *p++ = (char)c; + } + yylval.v.string = strdup(buf); + if (yylval.v.string == NULL) + err(1, "%s: strdup", __func__); + return (STRING); + case '!': + next = lgetc(0); + if (next == '=') + return (NE); + lungetc(next); + break; + case '<': + next = lgetc(0); + if (next == '>') { + yylval.v.i = PF_OP_XRG; + return (PORTBINARY); + } else if (next == '=') + return (LE); + lungetc(next); + break; + case '>': + next = lgetc(0); + if (next == '<') { + yylval.v.i = PF_OP_IRG; + return (PORTBINARY); + } else if (next == '=') + return (GE); + lungetc(next); + break; + case '-': + next = lgetc(0); + if (next == '>') + return (ARROW); + lungetc(next); + break; + } + +#define allowed_to_end_number(x) \ + (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=') + + if (c == '-' || isdigit(c)) { + do { + *p++ = c; + if ((size_t)(p-buf) >= sizeof(buf)) { + yyerror("string too long"); + return (findeol()); + } + } while ((c = lgetc(0)) != EOF && isdigit(c)); + lungetc(c); + if (p == buf + 1 && buf[0] == '-') + goto nodigits; + if (c == EOF || allowed_to_end_number(c)) { + const char *errstr = NULL; + + *p = '\0'; + yylval.v.number = strtonum(buf, LLONG_MIN, + LLONG_MAX, &errstr); + if (errstr) { + yyerror("\"%s\" invalid number: %s", + buf, errstr); + return (findeol()); + } + return (NUMBER); + } else { +nodigits: + while (p > buf + 1) + lungetc(*--p); + c = *--p; + if (c == '-') + return (c); + } + } + +#define allowed_in_string(x) \ + (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \ + x != '{' && x != '}' && x != '<' && x != '>' && \ + x != '!' && x != '=' && x != '/' && x != '#' && \ + x != ',')) + + if (isalnum(c) || c == ':' || c == '_') { + do { + *p++ = c; + if ((size_t)(p-buf) >= sizeof(buf)) { + yyerror("string too long"); + return (findeol()); + } + } while ((c = lgetc(0)) != EOF && (allowed_in_string(c))); + lungetc(c); + *p = '\0'; + if ((token = lookup(buf)) == STRING) + if ((yylval.v.string = strdup(buf)) == NULL) + err(1, "%s: strdup", __func__); + return (token); + } + if (c == '\n') { + yylval.lineno = file->lineno; + file->lineno++; + } + if (c == EOF) + return (0); + return (c); +} + +int +check_file_secrecy(int fd, const char *fname) +{ + struct stat st; + + if (fstat(fd, &st)) { + warn("cannot stat %s", fname); + return (-1); + } + if (st.st_uid != 0 && st.st_uid != getuid()) { + warnx("%s: owner not root or current user", fname); + return (-1); + } + if (st.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)) { + warnx("%s: group writable or world read/writable", fname); + return (-1); + } + return (0); +} + +struct file * +pushfile(const char *name, int secret) +{ + struct file *nfile; + + if ((nfile = calloc(1, sizeof(struct file))) == NULL || + (nfile->name = strdup(name)) == NULL) { + warn("%s", __func__); + if (nfile) + free(nfile); + return (NULL); + } + if (TAILQ_FIRST(&files) == NULL && strcmp(nfile->name, "-") == 0) { + nfile->stream = stdin; + free(nfile->name); + if ((nfile->name = strdup("stdin")) == NULL) { + warn("%s", __func__); + free(nfile); + return (NULL); + } + } else if ((nfile->stream = pfctl_fopen(nfile->name, "r")) == NULL) { + warn("%s: %s", __func__, nfile->name); + free(nfile->name); + free(nfile); + return (NULL); + } else if (secret && + check_file_secrecy(fileno(nfile->stream), nfile->name)) { + fclose(nfile->stream); + free(nfile->name); + free(nfile); + return (NULL); + } + nfile->lineno = TAILQ_EMPTY(&files) ? 1 : 0; + nfile->ungetsize = 16; + nfile->ungetbuf = malloc(nfile->ungetsize); + if (nfile->ungetbuf == NULL) { + warn("%s", __func__); + fclose(nfile->stream); + free(nfile->name); + free(nfile); + return (NULL); + } + TAILQ_INSERT_TAIL(&files, nfile, entry); + return (nfile); +} + +int +popfile(void) +{ + struct file *prev; + + if ((prev = TAILQ_PREV(file, files, entry)) != NULL) + prev->errors += file->errors; + + TAILQ_REMOVE(&files, file, entry); + fclose(file->stream); + free(file->name); + free(file->ungetbuf); + free(file); + file = prev; + + return (file ? 0 : EOF); +} + +int +parse_config(char *filename, struct pfctl *xpf) +{ + int errors = 0; + struct sym *sym; + + pf = xpf; + errors = 0; + rulestate = PFCTL_STATE_NONE; + returnicmpdefault = (ICMP_UNREACH << 8) | ICMP_UNREACH_PORT; + returnicmp6default = + (ICMP6_DST_UNREACH << 8) | ICMP6_DST_UNREACH_NOPORT; + blockpolicy = PFRULE_DROP; + failpolicy = PFRULE_DROP; + require_order = 1; + + if ((file = pushfile(filename, 0)) == NULL) { + warn("cannot open the main config file!"); + return (-1); + } + topfile = file; + + yyparse(); + errors = file->errors; + popfile(); + + /* Free macros and check which have not been used. */ + while ((sym = TAILQ_FIRST(&symhead))) { + if ((pf->opts & PF_OPT_VERBOSE2) && !sym->used) + fprintf(stderr, "warning: macro '%s' not " + "used\n", sym->nam); + free(sym->nam); + free(sym->val); + TAILQ_REMOVE(&symhead, sym, entry); + free(sym); + } + + return (errors ? -1 : 0); +} + +int +symset(const char *nam, const char *val, int persist) +{ + struct sym *sym; + + TAILQ_FOREACH(sym, &symhead, entry) { + if (strcmp(nam, sym->nam) == 0) + break; + } + + if (sym != NULL) { + if (sym->persist == 1) + return (0); + else { + free(sym->nam); + free(sym->val); + TAILQ_REMOVE(&symhead, sym, entry); + free(sym); + } + } + if ((sym = calloc(1, sizeof(*sym))) == NULL) + return (-1); + + sym->nam = strdup(nam); + if (sym->nam == NULL) { + free(sym); + return (-1); + } + sym->val = strdup(val); + if (sym->val == NULL) { + free(sym->nam); + free(sym); + return (-1); + } + sym->used = 0; + sym->persist = persist; + TAILQ_INSERT_TAIL(&symhead, sym, entry); + return (0); +} + +int +pfctl_cmdline_symset(char *s) +{ + char *sym, *val; + int ret; + + if ((val = strrchr(s, '=')) == NULL) + return (-1); + + sym = strndup(s, val - s); + if (sym == NULL) + err(1, "%s: malloc", __func__); + + ret = symset(sym, val + 1, 1); + free(sym); + + return (ret); +} + +char * +symget(const char *nam) +{ + struct sym *sym; + + TAILQ_FOREACH(sym, &symhead, entry) { + if (strcmp(nam, sym->nam) == 0) { + sym->used = 1; + return (sym->val); + } + } + return (NULL); +} + +void +mv_rules(struct pfctl_ruleset *src, struct pfctl_ruleset *dst) +{ + int i; + struct pfctl_rule *r; + + for (i = 0; i < PF_RULESET_MAX; ++i) { + TAILQ_FOREACH(r, src->rules[i].active.ptr, entries) + dst->anchor->match++; + TAILQ_CONCAT(dst->rules[i].active.ptr, src->rules[i].active.ptr, entries); + src->anchor->match = 0; + TAILQ_CONCAT(dst->rules[i].inactive.ptr, src->rules[i].inactive.ptr, entries); + } +} + +void +mv_eth_rules(struct pfctl_eth_ruleset *src, struct pfctl_eth_ruleset *dst) +{ + struct pfctl_eth_rule *r; + + while ((r = TAILQ_FIRST(&src->rules)) != NULL) { + TAILQ_REMOVE(&src->rules, r, entries); + TAILQ_INSERT_TAIL(&dst->rules, r, entries); + dst->anchor->match++; + } + src->anchor->match = 0; +} + +void +mv_tables(struct pfctl *pf, struct pfr_ktablehead *ktables, + struct pfctl_anchor *a, struct pfctl_anchor *alast) +{ + struct pfr_ktable *kt, *kt_safe; + char new_path[PF_ANCHOR_MAXPATH]; + char *path_cut; + int sz; + struct pfr_uktable *ukt; + SLIST_HEAD(, pfr_uktable) ukt_list; + + /* + * Here we need to rename anchor path from temporal names such as + * _1/_2/foo to _1/bar/foo etc. + * + * This also means we need to remove and insert table to ktables + * tree as anchor path is being updated. + */ + SLIST_INIT(&ukt_list); + DBGPRINT("%s [ %s ] (%s)\n", __func__, a->path, alast->path); + RB_FOREACH_SAFE(kt, pfr_ktablehead, ktables, kt_safe) { + path_cut = strstr(kt->pfrkt_anchor, alast->path); + if (path_cut != NULL) { + path_cut += strlen(alast->path); + if (*path_cut) + sz = snprintf(new_path, sizeof (new_path), + "%s%s", a->path, path_cut); + else + sz = snprintf(new_path, sizeof (new_path), + "%s", a->path); + if (sz >= sizeof (new_path)) + errx(1, "new path is too long for %s@%s\n", + kt->pfrkt_name, kt->pfrkt_anchor); + + DBGPRINT("%s %s@%s -> %s@%s\n", __func__, + kt->pfrkt_name, kt->pfrkt_anchor, + kt->pfrkt_name, new_path); + RB_REMOVE(pfr_ktablehead, ktables, kt); + strlcpy(kt->pfrkt_anchor, new_path, + sizeof(kt->pfrkt_anchor)); + SLIST_INSERT_HEAD(&ukt_list, (struct pfr_uktable *)kt, + pfrukt_entry); + } + } + + while ((ukt = SLIST_FIRST(&ukt_list)) != NULL) { + SLIST_REMOVE_HEAD(&ukt_list, pfrukt_entry); + if (RB_INSERT(pfr_ktablehead, ktables, + (struct pfr_ktable *)ukt) != NULL) + errx(1, "%s@%s exists already\n", + ukt->pfrukt_name, + ukt->pfrukt_anchor); + } +} + +void +decide_address_family(struct node_host *n, sa_family_t *af) +{ + if (*af != 0 || n == NULL) + return; + *af = n->af; + while ((n = n->next) != NULL) { + if (n->af != *af) { + *af = 0; + return; + } + } +} + +void +remove_invalid_hosts(struct node_host **nh, sa_family_t *af) +{ + struct node_host *n = *nh, *prev = NULL; + + while (n != NULL) { + if (*af && n->af && n->af != *af) { + /* unlink and free n */ + struct node_host *next = n->next; + + /* adjust tail pointer */ + if (n == (*nh)->tail) + (*nh)->tail = prev; + /* adjust previous node's next pointer */ + if (prev == NULL) + *nh = next; + else + prev->next = next; + /* free node */ + if (n->ifname != NULL) + free(n->ifname); + free(n); + n = next; + } else { + if (n->af && !*af) + *af = n->af; + prev = n; + n = n->next; + } + } +} + +int +invalid_redirect(struct node_host *nh, sa_family_t af) +{ + if (!af) { + struct node_host *n; + + /* tables and dyniftl are ok without an address family */ + for (n = nh; n != NULL; n = n->next) { + if (n->addr.type != PF_ADDR_TABLE && + n->addr.type != PF_ADDR_DYNIFTL) { + yyerror("address family not given and " + "translation address expands to multiple " + "address families"); + return (1); + } + } + } + if (nh == NULL) { + yyerror("no translation address with matching address family " + "found."); + return (1); + } + return (0); +} + +int +atoul(char *s, u_long *ulvalp) +{ + u_long ulval; + char *ep; + + errno = 0; + ulval = strtoul(s, &ep, 0); + if (s[0] == '\0' || *ep != '\0') + return (-1); + if (errno == ERANGE && ulval == ULONG_MAX) + return (-1); + *ulvalp = ulval; + return (0); +} + +int +getservice(char *n) +{ + struct servent *s; + u_long ulval; + + if (atoul(n, &ulval) == 0) { + if (ulval > 65535) { + yyerror("illegal port value %lu", ulval); + return (-1); + } + return (htons(ulval)); + } else { + s = getservbyname(n, "tcp"); + if (s == NULL) + s = getservbyname(n, "udp"); + if (s == NULL) + s = getservbyname(n, "sctp"); + if (s == NULL) { + yyerror("unknown port %s", n); + return (-1); + } + return (s->s_port); + } +} + +int +rule_label(struct pfctl_rule *r, char *s[PF_RULE_MAX_LABEL_COUNT]) +{ + for (int i = 0; i < PF_RULE_MAX_LABEL_COUNT; i++) { + if (s[i] == NULL) + return (0); + + if (strlcpy(r->label[i], s[i], sizeof(r->label[0])) >= + sizeof(r->label[0])) { + yyerror("rule label too long (max %d chars)", + sizeof(r->label[0])-1); + return (-1); + } + } + return (0); +} + +int +eth_rule_label(struct pfctl_eth_rule *r, char *s[PF_RULE_MAX_LABEL_COUNT]) +{ + for (int i = 0; i < PF_RULE_MAX_LABEL_COUNT; i++) { + if (s[i] == NULL) + return (0); + + if (strlcpy(r->label[i], s[i], sizeof(r->label[0])) >= + sizeof(r->label[0])) { + yyerror("rule label too long (max %d chars)", + sizeof(r->label[0])-1); + return (-1); + } + } + return (0); +} + +u_int16_t +parseicmpspec(char *w, sa_family_t af) +{ + const struct icmpcodeent *p; + u_long ulval; + u_int8_t icmptype; + + if (af == AF_INET) + icmptype = returnicmpdefault >> 8; + else + icmptype = returnicmp6default >> 8; + + if (atoul(w, &ulval) == -1) { + if ((p = geticmpcodebyname(icmptype, w, af)) == NULL) { + yyerror("unknown icmp code %s", w); + return (0); + } + ulval = p->code; + } + if (ulval > 255) { + yyerror("invalid icmp code %lu", ulval); + return (0); + } + return (icmptype << 8 | ulval); +} + +int +parseport(char *port, struct range *r, int extensions) +{ + char *p = strchr(port, ':'); + + if (p == NULL) { + if ((r->a = getservice(port)) == -1) + return (-1); + r->b = 0; + r->t = PF_OP_NONE; + return (0); + } + if ((extensions & PPORT_STAR) && !strcmp(p+1, "*")) { + *p = 0; + if ((r->a = getservice(port)) == -1) + return (-1); + r->b = 0; + r->t = PF_OP_IRG; + return (0); + } + if ((extensions & PPORT_RANGE)) { + *p++ = 0; + if ((r->a = getservice(port)) == -1 || + (r->b = getservice(p)) == -1) + return (-1); + if (r->a == r->b) { + r->b = 0; + r->t = PF_OP_NONE; + } else + r->t = PF_OP_RRG; + return (0); + } + return (-1); +} + +int +pfctl_load_anchors(int dev, struct pfctl *pf) +{ + struct loadanchors *la; + + TAILQ_FOREACH(la, &loadanchorshead, entries) { + if (pf->opts & PF_OPT_VERBOSE) + fprintf(stderr, "\nLoading anchor %s from %s\n", + la->anchorname, la->filename); + if (pfctl_rules(dev, la->filename, pf->opts, pf->optimize, + la->anchorname, pf->trans) == -1) + return (-1); + } + + return (0); +} + +int +kw_casecmp(const void *k, const void *e) +{ + return (strcasecmp(k, ((const struct keywords *)e)->k_name)); +} + +int +map_tos(char *s, int *val) +{ + /* DiffServ Codepoints and other TOS mappings */ + const struct keywords toswords[] = { + { "af11", IPTOS_DSCP_AF11 }, + { "af12", IPTOS_DSCP_AF12 }, + { "af13", IPTOS_DSCP_AF13 }, + { "af21", IPTOS_DSCP_AF21 }, + { "af22", IPTOS_DSCP_AF22 }, + { "af23", IPTOS_DSCP_AF23 }, + { "af31", IPTOS_DSCP_AF31 }, + { "af32", IPTOS_DSCP_AF32 }, + { "af33", IPTOS_DSCP_AF33 }, + { "af41", IPTOS_DSCP_AF41 }, + { "af42", IPTOS_DSCP_AF42 }, + { "af43", IPTOS_DSCP_AF43 }, + { "critical", IPTOS_PREC_CRITIC_ECP }, + { "cs0", IPTOS_DSCP_CS0 }, + { "cs1", IPTOS_DSCP_CS1 }, + { "cs2", IPTOS_DSCP_CS2 }, + { "cs3", IPTOS_DSCP_CS3 }, + { "cs4", IPTOS_DSCP_CS4 }, + { "cs5", IPTOS_DSCP_CS5 }, + { "cs6", IPTOS_DSCP_CS6 }, + { "cs7", IPTOS_DSCP_CS7 }, + { "ef", IPTOS_DSCP_EF }, + { "inetcontrol", IPTOS_PREC_INTERNETCONTROL }, + { "lowdelay", IPTOS_LOWDELAY }, + { "netcontrol", IPTOS_PREC_NETCONTROL }, + { "reliability", IPTOS_RELIABILITY }, + { "throughput", IPTOS_THROUGHPUT }, + { "va", IPTOS_DSCP_VA } + }; + const struct keywords *p; + + p = bsearch(s, toswords, sizeof(toswords)/sizeof(toswords[0]), + sizeof(toswords[0]), kw_casecmp); + + if (p) { + *val = p->k_val; + return (1); + } + return (0); +} + +int +rt_tableid_max(void) +{ +#ifdef __FreeBSD__ + int fibs; + size_t l = sizeof(fibs); + + if (sysctlbyname("net.fibs", &fibs, &l, NULL, 0) == -1) + fibs = 16; /* XXX RT_MAXFIBS, at least limit it some. */ + /* + * As the OpenBSD code only compares > and not >= we need to adjust + * here given we only accept values of 0..n and want to avoid #ifdefs + * in the grammar. + */ + return (fibs - 1); +#else + return (RT_TABLEID_MAX); +#endif +} + +struct node_mac* +node_mac_from_string(const char *str) +{ + struct node_mac *m; + + m = calloc(1, sizeof(struct node_mac)); + if (m == NULL) + err(1, "%s: calloc", __func__); + + if (sscanf(str, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx", + &m->mac[0], &m->mac[1], &m->mac[2], &m->mac[3], &m->mac[4], + &m->mac[5]) != 6) { + free(m); + yyerror("invalid MAC address"); + return (NULL); + } + + memset(m->mask, 0xff, ETHER_ADDR_LEN); + m->isset = true; + m->next = NULL; + m->tail = m; + + return (m); +} + +struct node_mac* +node_mac_from_string_masklen(const char *str, int masklen) +{ + struct node_mac *m; + + if (masklen < 0 || masklen > (ETHER_ADDR_LEN * 8)) { + yyerror("invalid MAC mask length"); + return (NULL); + } + + m = node_mac_from_string(str); + if (m == NULL) + return (NULL); + + memset(m->mask, 0, ETHER_ADDR_LEN); + for (int i = 0; i < masklen; i++) + m->mask[i / 8] |= 1 << (i % 8); + + return (m); +} + +struct node_mac* +node_mac_from_string_mask(const char *str, const char *mask) +{ + struct node_mac *m; + + m = node_mac_from_string(str); + if (m == NULL) + return (NULL); + + if (sscanf(mask, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx", + &m->mask[0], &m->mask[1], &m->mask[2], &m->mask[3], &m->mask[4], + &m->mask[5]) != 6) { + free(m); + yyerror("invalid MAC mask"); + return (NULL); + } + + return (m); +} + +int +filteropts_to_rule(struct pfctl_rule *r, struct filter_opts *opts) +{ + if (opts->marker & FOM_ONCE) { + if ((r->action != PF_PASS && r->action != PF_DROP) || r->anchor) { + yyerror("'once' only applies to pass/block rules"); + return (1); + } + r->rule_flag |= PFRULE_ONCE; + } + + r->keep_state = opts->keep.action; + r->pktrate.limit = opts->pktrate.limit; + r->pktrate.seconds = opts->pktrate.seconds; + r->prob = opts->prob; + r->rtableid = opts->rtableid; + r->ridentifier = opts->ridentifier; + r->max_pkt_size = opts->max_pkt_size; + r->tos = opts->tos; + + if (opts->nodf) + r->scrub_flags |= PFSTATE_NODF; + if (opts->randomid) + r->scrub_flags |= PFSTATE_RANDOMID; + if (opts->minttl) + r->min_ttl = opts->minttl; + if (opts->max_mss) + r->max_mss = opts->max_mss; + + if (opts->tag) + if (strlcpy(r->tagname, opts->tag, + PF_TAG_NAME_SIZE) >= PF_TAG_NAME_SIZE) { + yyerror("tag too long, max %u chars", + PF_TAG_NAME_SIZE - 1); + return (1); + } + if (opts->match_tag) + if (strlcpy(r->match_tagname, opts->match_tag, + PF_TAG_NAME_SIZE) >= PF_TAG_NAME_SIZE) { + yyerror("tag too long, max %u chars", + PF_TAG_NAME_SIZE - 1); + return (1); + } + r->match_tag_not = opts->match_tag_not; + + if (rule_label(r, opts->label)) + return (1); + for (int i = 0; i < PF_RULE_MAX_LABEL_COUNT; i++) + free(opts->label[i]); + + if (opts->marker & FOM_AFTO) + r->rule_flag |= PFRULE_AFTO; + if (opts->marker & FOM_SCRUB_TCP) + r->scrub_flags |= PFSTATE_SCRUB_TCP; + if (opts->marker & FOM_PRIO) + r->prio = opts->prio ? opts->prio : PF_PRIO_ZERO; + if (opts->marker & FOM_SETPRIO) { + r->set_prio[0] = opts->set_prio[0]; + r->set_prio[1] = opts->set_prio[1]; + r->scrub_flags |= PFSTATE_SETPRIO; + } + if (opts->marker & FOM_SETTOS) { + r->scrub_flags |= PFSTATE_SETTOS; + r->set_tos = opts->settos; + } + + r->flags = opts->flags.b1; + r->flagset = opts->flags.b2; + if ((opts->flags.b1 & opts->flags.b2) != opts->flags.b1) { + yyerror("flags always false"); + return (1); + } + + if (opts->queues.qname != NULL) { + if (strlcpy(r->qname, opts->queues.qname, + sizeof(r->qname)) >= sizeof(r->qname)) { + yyerror("rule qname too long (max " + "%d chars)", sizeof(r->qname)-1); + return (1); + } + free(opts->queues.qname); + } + if (opts->queues.pqname != NULL) { + if (strlcpy(r->pqname, opts->queues.pqname, + sizeof(r->pqname)) >= sizeof(r->pqname)) { + yyerror("rule pqname too long (max " + "%d chars)", sizeof(r->pqname)-1); + return (1); + } + free(opts->queues.pqname); + } + + if (opts->fragment) + r->rule_flag |= PFRULE_FRAGMENT; + r->allow_opts = opts->allowopts; + + return (0); +} + +static bool +pfctl_setup_anchor(struct pfctl_rule *r, struct pfctl *pf, char *anchorname) +{ + char *p; + + if (pf->astack[pf->asd + 1]) { + if (anchorname && strchr(anchorname, '/') != NULL) { + free(anchorname); + yyerror("anchor paths containing '/' " + "cannot be used for inline anchors."); + return (false); + } + + /* Move inline rules into relative location. */ + pfctl_anchor_setup(r, + &pf->astack[pf->asd]->ruleset, + anchorname ? anchorname : pf->alast->name); + + if (r->anchor == NULL) + err(1, "anchorrule: unable to " + "create ruleset"); + + if (pf->alast != r->anchor) { + if (r->anchor->match) { + yyerror("inline anchor '%s' " + "already exists", + r->anchor->name); + return (false); + } + mv_rules(&pf->alast->ruleset, + &r->anchor->ruleset); + mv_tables(pf, &pfr_ktables, r->anchor, pf->alast); + } + pf_remove_if_empty_ruleset(&pf->alast->ruleset); + pf->alast = r->anchor; + } else { + if (! anchorname) { + yyerror("anchors without explicit " + "rules must specify a name"); + return (false); + } + /* + * Don't make non-brace anchors part of the main anchor pool. + */ + if ((r->anchor = calloc(1, sizeof(*r->anchor))) == NULL) { + err(1, "anchorrule: calloc"); + } + pf_init_ruleset(&r->anchor->ruleset); + r->anchor->ruleset.anchor = r->anchor; + if (strlcpy(r->anchor->path, anchorname, + sizeof(r->anchor->path)) >= sizeof(r->anchor->path)) { + errx(1, "anchorrule: strlcpy"); + } + if ((p = strrchr(anchorname, '/')) != NULL) { + if (strlen(p) == 1) { + yyerror("anchorrule: bad anchor name %s", + anchorname); + return (false); + } + } else + p = anchorname; + if (strlcpy(r->anchor->name, p, + sizeof(r->anchor->name)) >= sizeof(r->anchor->name)) { + errx(1, "anchorrule: strlcpy"); + } + } + + return (true); +} diff --git a/sbin/pfctl/pf.os b/sbin/pfctl/pf.os new file mode 100644 index 000000000000..e131d1b54756 --- /dev/null +++ b/sbin/pfctl/pf.os @@ -0,0 +1,708 @@ +# $OpenBSD: pf.os,v 1.27 2016/09/03 17:08:57 sthen Exp $ +# passive OS fingerprinting +# ------------------------- +# +# SYN signatures. Those signatures work for SYN packets only (duh!). +# +# (C) Copyright 2000-2003 by Michal Zalewski <lcamtuf@coredump.cx> +# (C) Copyright 2003 by Mike Frantzen <frantzen@w4g.org> +# +# Permission to use, copy, modify, and distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +# +# +# This fingerprint database is adapted from Michal Zalewski's p0f passive +# operating system package. The last database sync was from a Nov 3 2003 +# p0f.fp. +# +# +# Each line in this file specifies a single fingerprint. Please read the +# information below carefully before attempting to append any signatures +# reported as UNKNOWN to this file to avoid mistakes. +# +# We use the following set metrics for fingerprinting: +# +# - Window size (WSS) - a highly OS dependent setting used for TCP/IP +# performance control (max. amount of data to be sent without ACK). +# Some systems use a fixed value for initial packets. On other +# systems, it is a multiple of MSS or MTU (MSS+40). In some rare +# cases, the value is just arbitrary. +# +# NEW SIGNATURE: if p0f reported a special value of 'Snn', the number +# appears to be a multiple of MSS (MSS*nn); a special value of 'Tnn' +# means it is a multiple of MTU ((MSS+40)*nn). Unless you notice the +# value of nn is not fixed (unlikely), just copy the Snn or Tnn token +# literally. If you know this device has a simple stack and a fixed +# MTU, you can however multiply S value by MSS, or T value by MSS+40, +# and put it instead of Snn or Tnn. +# +# If WSS otherwise looks like a fixed value (for example a multiple +# of two), or if you can confirm the value is fixed, please quote +# it literally. If there's no apparent pattern in WSS chosen, you +# should consider wildcarding this value. +# +# - Overall packet size - a function of all IP and TCP options and bugs. +# +# NEW SIGNATURE: Copy this value literally. +# +# - Initial TTL - We check the actual TTL of a received packet. It can't +# be higher than the initial TTL, and also shouldn't be dramatically +# lower (maximum distance is defined as 40 hops). +# +# NEW SIGNATURE: *Never* copy TTL from a p0f-reported signature literally. +# You need to determine the initial TTL. The best way to do it is to +# check the documentation for a remote system, or check its settings. +# A fairly good method is to simply round the observed TTL up to +# 32, 64, 128, or 255, but it should be noted that some obscure devices +# might not use round TTLs (in particular, some shoddy appliances use +# "original" initial TTL settings). If not sure, you can see how many +# hops you're away from the remote party with traceroute or mtr. +# +# - Don't fragment flag (DF) - some modern OSes set this to implement PMTU +# discovery. Others do not bother. +# +# NEW SIGNATURE: Copy this value literally. +# +# - Maximum segment size (MSS) - this setting is usually link-dependent. P0f +# uses it to determine link type of the remote host. +# +# NEW SIGNATURE: Always wildcard this value, except for rare cases when +# you have an appliance with a fixed value, know the system supports only +# a very limited number of network interface types, or know the system +# is using a value it pulled out of nowhere. Specific unique MSS +# can be used to tell Google crawlbots from the rest of the population. +# +# - Window scaling (WSCALE) - this feature is used to scale WSS. +# It extends the size of a TCP/IP window to 32 bits. Some modern +# systems implement this feature. +# +# NEW SIGNATURE: Observe several signatures. Initial WSCALE is often set +# to zero or other low value. There's usually no need to wildcard this +# parameter. +# +# - Timestamp - some systems that implement timestamps set them to +# zero in the initial SYN. This case is detected and handled appropriately. +# +# - Selective ACK permitted - a flag set by systems that implement +# selective ACK functionality. +# +# - The sequence of TCP all options (MSS, window scaling, selective ACK +# permitted, timestamp, NOP). Other than the options previously +# discussed, p0f also checks for timestamp option (a silly +# extension to broadcast your uptime ;-), NOP options (used for +# header padding) and sackOK option (selective ACK feature). +# +# NEW SIGNATURE: Copy the sequence literally. +# +# To wildcard any value (except for initial TTL or TCP options), replace +# it with '*'. You can also use a modulo operator to match any values +# that divide by nnn - '%nnn'. +# +# Fingerprint entry format: +# +# wwww:ttt:D:ss:OOO...:OS:Version:Subtype:Details +# +# wwww - window size (can be *, %nnn, Snn or Tnn). The special values +# "S" and "T" which are a multiple of MSS or a multiple of MTU +# respectively. +# ttt - initial TTL +# D - don't fragment bit (0 - not set, 1 - set) +# ss - overall SYN packet size +# OOO - option value and order specification (see below) +# OS - OS genre (Linux, Solaris, Windows) +# Version - OS Version (2.0.27 on x86, etc) +# Subtype - OS subtype or patchlevel (SP3, lo0) +# details - Generic OS details +# +# If OS genre starts with '*', p0f will not show distance, link type +# and timestamp data. It is useful for userland TCP/IP stacks of +# network scanners and so on, where many settings are randomized or +# bogus. +# +# If OS genre starts with @, it denotes an approximate hit for a group +# of operating systems (signature reporting still enabled in this case). +# Use this feature at the end of this file to catch cases for which +# you don't have a precise match, but can tell it's Windows or FreeBSD +# or whatnot by looking at, say, flag layout alone. +# +# Option block description is a list of comma or space separated +# options in the order they appear in the packet: +# +# N - NOP option +# Wnnn - window scaling option, value nnn (or * or %nnn) +# Mnnn - maximum segment size option, value nnn (or * or %nnn) +# S - selective ACK OK +# T - timestamp +# T0 - timestamp with a zero value +# +# To denote no TCP options, use a single '.'. +# +# Please report any additions to this file, or any inaccuracies or +# problems spotted, to the maintainers: lcamtuf@coredump.cx, +# frantzen@openbsd.org and bugs@openbsd.org with a tcpdump packet +# capture of the relevant SYN packet(s) +# +# A test and submission page is available at +# http://lcamtuf.coredump.cx/p0f-help/ +# +# +# WARNING WARNING WARNING +# ----------------------- +# +# Do not add a system X as OS Y just because NMAP says so. It is often +# the case that X is a NAT firewall. While nmap is talking to the +# device itself, p0f is fingerprinting the guy behind the firewall +# instead. +# +# When in doubt, use common sense, don't add something that looks like +# a completely different system as Linux or FreeBSD or LinkSys router. +# Check DNS name, establish a connection to the remote host and look +# at SYN+ACK - does it look similar? +# +# Some users tweak their TCP/IP settings - enable or disable RFC1323 +# functionality, enable or disable timestamps or selective ACK, +# disable PMTU discovery, change MTU and so on. Always compare a new rule +# to other fingerprints for this system, and verify the system isn't +# "customized" before adding it. It is OK to add signature variants +# caused by a commonly used software (personal firewalls, security +# packages, etc), but it makes no sense to try to add every single +# possible /proc/sys/net/ipv4 tweak on Linux or so. +# +# KEEP IN MIND: Some packet firewalls configured to normalize outgoing +# traffic (OpenBSD pf with "scrub" enabled, for example) will, well, +# normalize packets. Signatures will not correspond to the originating +# system (and probably not quite to the firewall either). +# +# NOTE: Try to keep this file in some reasonable order, from most to +# least likely systems. This will speed up operation. Also keep most +# generic and broad rules near the end. +# + +########################## +# Standard OS signatures # +########################## + +# ----------------- AIX --------------------- + +# AIX is first because its signatures are close to NetBSD, MacOS X and +# Linux 2.0, but it uses a fairly rare MSSes, at least sometimes... +# This is a shoddy hack, though. + +45046:64:0:44:M*: AIX:4.3::AIX 4.3 +16384:64:0:44:M512: AIX:4.3:2-3:AIX 4.3.2 and earlier + +16384:64:0:60:M512,N,W%2,N,N,T: AIX:4.3:3:AIX 4.3.3-5.2 +16384:64:0:60:M512,N,W%2,N,N,T: AIX:5.1-5.2::AIX 4.3.3-5.2 +32768:64:0:60:M512,N,W%2,N,N,T: AIX:4.3:3:AIX 4.3.3-5.2 +32768:64:0:60:M512,N,W%2,N,N,T: AIX:5.1-5.2::AIX 4.3.3-5.2 +65535:64:0:60:M512,N,W%2,N,N,T: AIX:4.3:3:AIX 4.3.3-5.2 +65535:64:0:60:M512,N,W%2,N,N,T: AIX:5.1-5.2::AIX 4.3.3-5.2 +65535:64:0:64:M*,N,W1,N,N,T,N,N,S: AIX:5.3:ML1:AIX 5.3 ML1 + +# ----------------- Linux ------------------- + +# S1:64:0:44:M*:A: Linux:1.2::Linux 1.2.x (XXX quirks support) +512:64:0:44:M*: Linux:2.0:3x:Linux 2.0.3x +16384:64:0:44:M*: Linux:2.0:3x:Linux 2.0.3x + +# Endian snafu! Nelson says "ha-ha": +2:64:0:44:M*: Linux:2.0:3x:Linux 2.0.3x (MkLinux) on Mac +64:64:0:44:M*: Linux:2.0:3x:Linux 2.0.3x (MkLinux) on Mac + + +S4:64:1:60:M1360,S,T,N,W0: Linux:google::Linux (Google crawlbot) + +S2:64:1:60:M*,S,T,N,W0: Linux:2.4::Linux 2.4 (big boy) +S3:64:1:60:M*,S,T,N,W0: Linux:2.4:.18-21:Linux 2.4.18 and newer +S4:64:1:60:M*,S,T,N,W0: Linux:2.4::Linux 2.4/2.6 <= 2.6.7 +S4:64:1:60:M*,S,T,N,W0: Linux:2.6:.1-7:Linux 2.4/2.6 <= 2.6.7 + +S4:64:1:60:M*,S,T,N,W5: Linux:2.6::Linux 2.6 (newer, 1) +S4:64:1:60:M*,S,T,N,W6: Linux:2.6::Linux 2.6 (newer, 2) +S4:64:1:60:M*,S,T,N,W7: Linux:2.6::Linux 2.6 (newer, 3) +T4:64:1:60:M*,S,T,N,W7: Linux:2.6::Linux 2.6 (newer, 4) + +S10:64:1:60:M*,S,T,N,W4: Linux:3.0::Linux 3.0 + +S3:64:1:60:M*,S,T,N,W1: Linux:2.5::Linux 2.5 (sometimes 2.4) +S4:64:1:60:M*,S,T,N,W1: Linux:2.5-2.6::Linux 2.5/2.6 +S3:64:1:60:M*,S,T,N,W2: Linux:2.5::Linux 2.5 (sometimes 2.4) +S4:64:1:60:M*,S,T,N,W2: Linux:2.5::Linux 2.5 (sometimes 2.4) + +S20:64:1:60:M*,S,T,N,W0: Linux:2.2:20-25:Linux 2.2.20 and newer +S22:64:1:60:M*,S,T,N,W0: Linux:2.2::Linux 2.2 +S11:64:1:60:M*,S,T,N,W0: Linux:2.2::Linux 2.2 + +# Popular cluster config scripts disable timestamps and +# selective ACK: +S4:64:1:48:M1460,N,W0: Linux:2.4:cluster:Linux 2.4 in cluster + +# This needs to be investigated. On some systems, WSS +# is selected as a multiple of MTU instead of MSS. I got +# many submissions for this for many late versions of 2.4: +T4:64:1:60:M1412,S,T,N,W0: Linux:2.4::Linux 2.4 (late, uncommon) + +# This happens only over loopback, but let's make folks happy: +32767:64:1:60:M16396,S,T,N,W0: Linux:2.4:lo0:Linux 2.4 (local) +S8:64:1:60:M3884,S,T,N,W0: Linux:2.2:lo0:Linux 2.2 (local) + +# Opera visitors: +16384:64:1:60:M*,S,T,N,W0: Linux:2.2:Opera:Linux 2.2 (Opera?) +32767:64:1:60:M*,S,T,N,W0: Linux:2.4:Opera:Linux 2.4 (Opera?) + +# Some fairly common mods: +S4:64:1:52:M*,N,N,S,N,W0: Linux:2.4:ts:Linux 2.4 w/o timestamps +S22:64:1:52:M*,N,N,S,N,W0: Linux:2.2:ts:Linux 2.2 w/o timestamps + + +# ----------------- FreeBSD ----------------- + +16384:64:1:44:M*: FreeBSD:2.0-2.2::FreeBSD 2.0-4.2 +16384:64:1:44:M*: FreeBSD:3.0-3.5::FreeBSD 2.0-4.2 +16384:64:1:44:M*: FreeBSD:4.0-4.2::FreeBSD 2.0-4.2 +16384:64:1:60:M*,N,W0,N,N,T: FreeBSD:4.4::FreeBSD 4.4 + +1024:64:1:60:M*,N,W0,N,N,T: FreeBSD:4.4::FreeBSD 4.4 + +57344:64:1:44:M*: FreeBSD:4.6-4.8:noRFC1323:FreeBSD 4.6-4.8 (no RFC1323) +57344:64:1:60:M*,N,W0,N,N,T: FreeBSD:4.6-4.9::FreeBSD 4.6-4.9 + +32768:64:1:60:M*,N,W0,N,N,T: FreeBSD:4.8-4.11::FreeBSD 4.8-5.1 (or MacOS X) +32768:64:1:60:M*,N,W0,N,N,T: FreeBSD:5.0-5.1::FreeBSD 4.8-5.1 (or MacOS X) +65535:64:1:60:M*,N,W0,N,N,T: FreeBSD:4.8-4.11::FreeBSD 4.8-5.2 (or MacOS X) +65535:64:1:60:M*,N,W0,N,N,T: FreeBSD:5.0-5.2::FreeBSD 4.8-5.2 (or MacOS X) +65535:64:1:60:M*,N,W1,N,N,T: FreeBSD:4.7-4.11::FreeBSD 4.7-5.2 +65535:64:1:60:M*,N,W1,N,N,T: FreeBSD:5.0-5.2::FreeBSD 4.7-5.2 + +# XXX need quirks support +# 65535:64:1:60:M*,N,W0,N,N,T:Z:FreeBSD:5.1-5.4::5.1-current (1) +# 65535:64:1:60:M*,N,W1,N,N,T:Z:FreeBSD:5.1-5.4::5.1-current (2) +# 65535:64:1:60:M*,N,W2,N,N,T:Z:FreeBSD:5.1-5.4::5.1-current (3) +# 65535:64:1:44:M*:Z:FreeBSD:5.2::FreeBSD 5.2 (no RFC1323) + +# 16384:64:1:60:M*,N,N,N,N,N,N,T:FreeBSD:4.4:noTS:FreeBSD 4.4 (w/o timestamps) + +# ----------------- NetBSD ------------------ + +16384:64:0:60:M*,N,W0,N,N,T: NetBSD:1.3::NetBSD 1.3 +65535:64:0:60:M*,N,W0,N,N,T0: NetBSD:1.6:opera:NetBSD 1.6 (Opera) +16384:64:0:60:M*,N,W0,N,N,T0: NetBSD:1.6::NetBSD 1.6 +16384:64:1:60:M*,N,W0,N,N,T0: NetBSD:1.6:df:NetBSD 1.6 (DF) +65535:64:1:60:M*,N,W1,N,N,T0: NetBSD:1.6::NetBSD 1.6W-current (DF) +65535:64:1:60:M*,N,W0,N,N,T0: NetBSD:1.6::NetBSD 1.6X (DF) +32768:64:1:60:M*,N,W0,N,N,T0: NetBSD:1.6:randomization:NetBSD 1.6ZH-current (w/ ip_id randomization) + +# ----------------- OpenBSD ----------------- + +16384:64:0:60:M*,N,W0,N,N,T: OpenBSD:2.6::NetBSD 1.3 (or OpenBSD 2.6) +16384:64:1:64:M*,N,N,S,N,W0,N,N,T: OpenBSD:3.0-4.8::OpenBSD 3.0-4.8 +16384:64:0:64:M*,N,N,S,N,W0,N,N,T: OpenBSD:3.0-4.8:no-df:OpenBSD 3.0-4.8 (scrub no-df) +57344:64:1:64:M*,N,N,S,N,W0,N,N,T: OpenBSD:3.3-4.0::OpenBSD 3.3-4.0 +57344:64:0:64:M*,N,N,S,N,W0,N,N,T: OpenBSD:3.3-4.0:no-df:OpenBSD 3.3-4.0 (scrub no-df) + +65535:64:1:64:M*,N,N,S,N,W0,N,N,T: OpenBSD:3.0-4.0:opera:OpenBSD 3.0-4.0 (Opera) + +16384:64:1:64:M*,N,N,S,N,W3,N,N,T: OpenBSD:4.9::OpenBSD 4.9 +16384:64:0:64:M*,N,N,S,N,W3,N,N,T: OpenBSD:4.9:no-df:OpenBSD 4.9 (scrub no-df) + +16384:64:1:64:M*,N,N,S,N,W6,N,N,T: OpenBSD:6.1::OpenBSD 6.1 +16384:64:0:64:M*,N,N,S,N,W6,N,N,T: OpenBSD:6.1:no-df:OpenBSD 6.1 (scrub no-df) + +# ----------------- DragonFly BSD ----------------- + +57344:64:1:60:M*,N,W0,N,N,T: DragonFly:1.0:A:DragonFly 1.0A +57344:64:0:64:M*,N,W0,N,N,S,N,N,T: DragonFly:1.2-1.12::DragonFly 1.2-1.12 +5840:64:1:60:M*,S,T,N,W4: DragonFly:2.0-2.1::DragonFly 2.0-2.1 +57344:64:0:64:M*,N,W0,N,N,S,N,N,T: DragonFly:2.2-2.3::DragonFly 2.2-2.3 +57344:64:0:64:M*,N,W5,N,N,S,N,N,T: DragonFly:2.4-2.7::DragonFly 2.4-2.7 + +# ----------------- Solaris ----------------- + +S17:64:1:64:N,W3,N,N,T0,N,N,S,M*: Solaris:8:RFC1323:Solaris 8 RFC1323 +S17:64:1:48:N,N,S,M*: Solaris:8::Solaris 8 +S17:255:1:44:M*: Solaris:2.5-2.7::Solaris 2.5 to 7 + +S6:255:1:44:M*: Solaris:2.6-2.7::Solaris 2.6 to 7 +S23:255:1:44:M*: Solaris:2.5:1:Solaris 2.5.1 +S34:64:1:48:M*,N,N,S: Solaris:2.9::Solaris 9 +S44:255:1:44:M*: Solaris:2.7::Solaris 7 + +4096:64:0:44:M1460: SunOS:4.1::SunOS 4.1.x + +S34:64:1:52:M*,N,W0,N,N,S: Solaris:10:beta:Solaris 10 (beta) +32850:64:1:64:M*,N,N,T,N,W1,N,N,S: Solaris:10::Solaris 10 1203 + +# ----------------- IRIX -------------------- + +49152:64:0:44:M*: IRIX:6.4::IRIX 6.4 +61440:64:0:44:M*: IRIX:6.2-6.5::IRIX 6.2-6.5 +49152:64:0:52:M*,N,W2,N,N,S: IRIX:6.5:RFC1323:IRIX 6.5 (RFC1323) +49152:64:0:52:M*,N,W3,N,N,S: IRIX:6.5:RFC1323:IRIX 6.5 (RFC1323) + +61440:64:0:48:M*,N,N,S: IRIX:6.5:12-21:IRIX 6.5.12 - 6.5.21 +49152:64:0:48:M*,N,N,S: IRIX:6.5:15-21:IRIX 6.5.15 - 6.5.21 + +49152:60:0:64:M*,N,W2,N,N,T,N,N,S: IRIX:6.5:IP27:IRIX 6.5 IP27 + + +# ----------------- Tru64 ------------------- + +32768:64:1:48:M*,N,W0: Tru64:4.0::Tru64 4.0 (or OS/2 Warp 4) +32768:64:0:48:M*,N,W0: Tru64:5.0::Tru64 5.0 +8192:64:0:44:M1460: Tru64:5.1:noRFC1323:Tru64 6.1 (no RFC1323) (or QNX 6) +61440:64:0:48:M*,N,W0: Tru64:5.1a:JP4:Tru64 v5.1a JP4 (or OpenVMS 7.x on Compaq 5.x stack) + +# ----------------- OpenVMS ----------------- + +6144:64:1:60:M*,N,W0,N,N,T: OpenVMS:7.2::OpenVMS 7.2 (Multinet 4.4 stack) + +# ----------------- MacOS ------------------- + +# XXX Need EOL tcp opt support +# S2:255:1:48:M*,W0,E:.:MacOS:8.6 classic + +# XXX some of these use EOL too +16616:255:1:48:M*,W0: MacOS:7.3-7.6:OTTCP:MacOS 7.3-8.6 (OTTCP) +16616:255:1:48:M*,W0: MacOS:8.0-8.6:OTTCP:MacOS 7.3-8.6 (OTTCP) +16616:255:1:48:M*,N,N,N: MacOS:8.1-8.6:OTTCP:MacOS 8.1-8.6 (OTTCP) +32768:255:1:48:M*,W0,N: MacOS:9.0-9.2::MacOS 9.0-9.2 +65535:255:1:48:M*,N,N,N,N: MacOS:9.1::MacOS 9.1 (OT 2.7.4) + + +# ----------------- Windows ----------------- + +# Windows TCP/IP stack is a mess. For most recent XP, 2000 and +# even 98, the patchlevel, not the actual OS version, is more +# relevant to the signature. They share the same code, so it would +# seem. Luckily for us, almost all Windows 9x boxes have an +# awkward MSS of 536, which I use to tell one from another +# in most difficult cases. + +8192:32:1:44:M*: Windows:3.11::Windows 3.11 (Tucows) +S44:64:1:64:M*,N,W0,N,N,T0,N,N,S: Windows:95::Windows 95 +8192:128:1:64:M*,N,W0,N,N,T0,N,N,S: Windows:95:b:Windows 95b + +# There were so many tweaking tools and so many stack versions for +# Windows 98 it is no longer possible to tell them from each other +# without some very serious research. Until then, there's an insane +# number of signatures, for your amusement: + +S44:32:1:48:M*,N,N,S: Windows:98:lowTTL:Windows 98 (low TTL) +8192:32:1:48:M*,N,N,S: Windows:98:lowTTL:Windows 98 (low TTL) +%8192:64:1:48:M536,N,N,S: Windows:98::Windows 98 +%8192:128:1:48:M536,N,N,S: Windows:98::Windows 98 +S4:64:1:48:M*,N,N,S: Windows:98::Windows 98 +S6:64:1:48:M*,N,N,S: Windows:98::Windows 98 +S12:64:1:48:M*,N,N,S: Windows:98::Windows 98 +T30:64:1:64:M1460,N,W0,N,N,T0,N,N,S: Windows:98::Windows 98 +32767:64:1:48:M*,N,N,S: Windows:98::Windows 98 +37300:64:1:48:M*,N,N,S: Windows:98::Windows 98 +46080:64:1:52:M*,N,W3,N,N,S: Windows:98:RFC1323:Windows 98 (RFC1323) +65535:64:1:44:M*: Windows:98:noSack:Windows 98 (no sack) +S16:128:1:48:M*,N,N,S: Windows:98::Windows 98 +S16:128:1:64:M*,N,W0,N,N,T0,N,N,S: Windows:98::Windows 98 +S26:128:1:48:M*,N,N,S: Windows:98::Windows 98 +T30:128:1:48:M*,N,N,S: Windows:98::Windows 98 +32767:128:1:52:M*,N,W0,N,N,S: Windows:98::Windows 98 +60352:128:1:48:M*,N,N,S: Windows:98::Windows 98 +60352:128:1:64:M*,N,W2,N,N,T0,N,N,S: Windows:98::Windows 98 + +# What's with 1414 on NT? +T31:128:1:44:M1414: Windows:NT:4.0:Windows NT 4.0 SP6a +64512:128:1:44:M1414: Windows:NT:4.0:Windows NT 4.0 SP6a +8192:128:1:44:M*: Windows:NT:4.0:Windows NT 4.0 (older) + +# Windows XP and 2000. Most of the signatures that were +# either dubious or non-specific (no service pack data) +# were deleted and replaced with generics at the end. + +65535:128:1:48:M*,N,N,S: Windows:2000:SP4:Windows 2000 SP4, XP SP1 +65535:128:1:48:M*,N,N,S: Windows:XP:SP1:Windows 2000 SP4, XP SP1 +%8192:128:1:48:M*,N,N,S: Windows:2000:SP2+:Windows 2000 SP2, XP SP1 (seldom 98 4.10.2222) +%8192:128:1:48:M*,N,N,S: Windows:XP:SP1:Windows 2000 SP2, XP SP1 (seldom 98 4.10.2222) +S20:128:1:48:M*,N,N,S: Windows:2000::Windows 2000/XP SP3 +S20:128:1:48:M*,N,N,S: Windows:XP:SP3:Windows 2000/XP SP3 +S45:128:1:48:M*,N,N,S: Windows:2000:SP4:Windows 2000 SP4, XP SP 1 +S45:128:1:48:M*,N,N,S: Windows:XP:SP1:Windows 2000 SP4, XP SP 1 +40320:128:1:48:M*,N,N,S: Windows:2000:SP4:Windows 2000 SP4 + +S6:128:1:48:M*,N,N,S: Windows:2000:SP2:Windows XP, 2000 SP2+ +S6:128:1:48:M*,N,N,S: Windows:XP::Windows XP, 2000 SP2+ +S12:128:1:48:M*,N,N,S: Windows:XP:SP1:Windows XP SP1 +S44:128:1:48:M*,N,N,S: Windows:2000:SP3:Windows Pro SP1, 2000 SP3 +S44:128:1:48:M*,N,N,S: Windows:XP:SP1:Windows Pro SP1, 2000 SP3 +64512:128:1:48:M*,N,N,S: Windows:2000:SP3:Windows SP1, 2000 SP3 +64512:128:1:48:M*,N,N,S: Windows:XP:SP1:Windows SP1, 2000 SP3 +32767:128:1:48:M*,N,N,S: Windows:2000:SP4:Windows SP1, 2000 SP4 +32767:128:1:48:M*,N,N,S: Windows:XP:SP1:Windows SP1, 2000 SP4 + +8192:128:1:52:M*,N,W2,N,N,S: Windows:Vista::Windows Vista/7 + +# Odds, ends, mods: + +S52:128:1:48:M1260,N,N,S: Windows:2000:cisco:Windows XP/2000 via Cisco +S52:128:1:48:M1260,N,N,S: Windows:XP:cisco:Windows XP/2000 via Cisco +65520:128:1:48:M*,N,N,S: Windows:XP::Windows XP bare-bone +16384:128:1:52:M536,N,W0,N,N,S: Windows:2000:ZoneAlarm:Windows 2000 w/ZoneAlarm? +2048:255:0:40:.: Windows:.NET::Windows .NET Enterprise Server + +44620:64:0:48:M*,N,N,S: Windows:ME::Windows ME no SP (?) +S6:255:1:48:M536,N,N,S: Windows:95:winsock2:Windows 95 winsock 2 +32768:32:1:52:M1460,N,W0,N,N,S: Windows:2003:AS:Windows 2003 AS + + +# No need to be more specific, it passes: +# *:128:1:48:M*,N,N,S:U:-Windows:XP/2000 while downloading (leak!) XXX quirk +# there is an equiv similar generic sig w/o the quirk + +# ----------------- HP/UX ------------------- + +32768:64:1:44:M*: HP-UX:B.10.20::HP-UX B.10.20 +32768:64:0:48:M*,W0,N: HP-UX:11.0::HP-UX 11.0 +32768:64:1:48:M*,W0,N: HP-UX:11.10::HP-UX 11.0 or 11.11 +32768:64:1:48:M*,W0,N: HP-UX:11.11::HP-UX 11.0 or 11.11 + +# Whoa. Hardcore WSS. +0:64:0:48:M*,W0,N: HP-UX:B.11.00:A:HP-UX B.11.00 A (RFC1323) + +# ----------------- RiscOS ------------------ + +# We don't yet support the ?12 TCP option +#16384:64:1:68:M1460,N,W0,N,N,T,N,N,?12: RISCOS:3.70-4.36::RISC OS 3.70-4.36 +12288:32:0:44:M536: RISC OS:3.70:4.10:RISC OS 3.70 inet 4.10 + +# XXX quirk +# 4096:64:1:56:M1460,N,N,T:T: RISC OS:3.70:freenet:RISC OS 3.70 freenet 2.00 + + + +# ----------------- BSD/OS ------------------ + +# Once again, power of two WSS is also shared by MacOS X with DF set +8192:64:1:60:M1460,N,W0,N,N,T: BSD/OS:3.1::BSD/OS 3.1-4.3 (or MacOS X 10.2 w/DF) +8192:64:1:60:M1460,N,W0,N,N,T: BSD/OS:4.0-4.3::BSD/OS 3.1-4.3 (or MacOS X 10.2) + + +# ---------------- NewtonOS ----------------- + +4096:64:0:44:M1420: NewtonOS:2.1::NewtonOS 2.1 + +# ---------------- NeXTSTEP ----------------- + +S4:64:0:44:M1024: NeXTSTEP:3.3::NeXTSTEP 3.3 +S8:64:0:44:M512: NeXTSTEP:3.3::NeXTSTEP 3.3 + +# ------------------ BeOS ------------------- + +1024:255:0:48:M*,N,W0: BeOS:5.0-5.1::BeOS 5.0-5.1 +12288:255:0:44:M1402: BeOS:5.0::BeOS 5.0.x + +# ------------------ OS/400 ----------------- + +8192:64:1:60:M1440,N,W0,N,N,T: OS/400:VR4::OS/400 VR4/R5 +8192:64:1:60:M1440,N,W0,N,N,T: OS/400:VR5::OS/400 VR4/R5 +4096:64:1:60:M1440,N,W0,N,N,T: OS/400:V4R5:CF67032:OS/400 V4R5 + CF67032 + +# XXX quirk +# 28672:64:0:44:M1460:A:OS/390:? + +# ------------------ ULTRIX ----------------- + +16384:64:0:40:.: ULTRIX:4.5::ULTRIX 4.5 + +# ------------------- QNX ------------------- + +S16:64:0:44:M512: QNX:::QNX demodisk + +# ------------------ Novell ----------------- + +16384:128:1:44:M1460: Novell:NetWare:5.0:Novel Netware 5.0 +6144:128:1:44:M1460: Novell:IntranetWare:4.11:Novell IntranetWare 4.11 +6144:128:1:44:M1368: Novell:BorderManager::Novell BorderManager ? + +6144:128:1:52:M*,W0,N,S,N,N: Novell:Netware:6:Novell Netware 6 SP3 + + +# ----------------- SCO ------------------ +S3:64:1:60:M1460,N,W0,N,N,T: SCO:UnixWare:7.1:SCO UnixWare 7.1 +S17:64:1:60:M1380,N,W0,N,N,T: SCO:UnixWare:7.1:SCO UnixWare 7.1.3 MP3 +S23:64:1:44:M1380: SCO:OpenServer:5.0:SCO OpenServer 5.0 + +# ------------------- DOS ------------------- + +2048:255:0:44:M536: DOS:WATTCP:1.05:DOS Arachne via WATTCP/1.05 +T2:255:0:44:M984: DOS:WATTCP:1.05Arachne:Arachne via WATTCP/1.05 (eepro) + +# ------------------ OS/2 ------------------- + +S56:64:0:44:M512: OS/2:4::OS/2 4 +28672:64:0:44:M1460: OS/2:4::OS/2 Warp 4.0 + +# ----------------- TOPS-20 ----------------- + +# Another hardcore MSS, one of the ACK leakers hunted down. +# XXX QUIRK 0:64:0:44:M1460:A:TOPS-20:version 7 +0:64:0:44:M1460: TOPS-20:7::TOPS-20 version 7 + +# ----------------- FreeMiNT ---------------- + +S44:255:0:44:M536: FreeMiNT:1:16A:FreeMiNT 1 patch 16A (Atari) + +# ------------------ AMIGA ------------------ + +# XXX TCP option 12 +# S32:64:1:56:M*,N,N,S,N,N,?12:.:AMIGA:3.9 BB2 with Miami stack + +# ------------------ Plan9 ------------------ + +65535:255:0:48:M1460,W0,N: Plan9:4::Plan9 edition 4 + +# ----------------- AMIGAOS ----------------- + +16384:64:1:48:M1560,N,N,S: AMIGAOS:3.9::AMIGAOS 3.9 BB2 MiamiDX + +########################################### +# Appliance / embedded / other signatures # +########################################### + +# ---------- Firewalls / routers ------------ + +S12:64:1:44:M1460: @Checkpoint:::Checkpoint (unknown 1) +S12:64:1:48:N,N,S,M1460: @Checkpoint:::Checkpoint (unknown 2) +4096:32:0:44:M1460: ExtremeWare:4.x::ExtremeWare 4.x + +# XXX TCP option 12 +# S32:64:0:68:M512,N,W0,N,N,T,N,N,?12:.:Nokia:IPSO w/Checkpoint NG FP3 +# S16:64:0:68:M1024,N,W0,N,N,T,N,N,?12:.:Nokia:IPSO 3.7 build 026 + +S4:64:1:60:W0,N,S,T,M1460: FortiNet:FortiGate:50:FortiNet FortiGate 50 + +8192:64:1:44:M1460: Eagle:::Eagle Secure Gateway + +S52:128:1:48:M1260,N,N,N,N: LinkSys:WRV54G::LinkSys WRV54G VPN router + + + +# ------- Switches and other stuff ---------- + +4128:255:0:44:M*: Cisco:::Cisco Catalyst 3500, 7500 etc +S8:255:0:44:M*: Cisco:12008::Cisco 12008 +60352:128:1:64:M1460,N,W2,N,N,T,N,N,S: Alteon:ACEswitch::Alteon ACEswitch +64512:128:1:44:M1370: Nortel:Contivity Client::Nortel Conectivity Client + + +# ---------- Caches and whatnots ------------ + +S4:64:1:52:M1460,N,N,S,N,W0: AOL:web cache::AOL web cache + +32850:64:1:64:N,W1,N,N,T,N,N,S,M*: NetApp:5.x::NetApp Data OnTap 5.x +16384:64:1:64:M1460,N,N,S,N,W0,N: NetApp:5.3:1:NetApp 5.3.1 +65535:64:0:64:M1460,N,N,S,N,W*,N,N,T: NetApp:5.3-5.5::NetApp 5.3-5.5 +65535:64:0:60:M1460,N,W0,N,N,T: NetApp:CacheFlow::NetApp CacheFlow +8192:64:1:64:M1460,N,N,S,N,W0,N,N,T: NetApp:5.2:1:NetApp NetCache 5.2.1 +20480:64:1:64:M1460,N,N,S,N,W0,N,N,T: NetApp:4.1::NetApp NetCache4.1 + +65535:64:0:60:M1460,N,W0,N,N,T: CacheFlow:4.1::CacheFlow CacheOS 4.1 +8192:64:0:60:M1380,N,N,N,N,N,N,T: CacheFlow:1.1::CacheFlow CacheOS 1.1 + +S4:64:0:48:M1460,N,N,S: Cisco:Content Engine::Cisco Content Engine + +27085:128:0:40:.: Dell:PowerApp cache::Dell PowerApp (Linux-based) + +65535:255:1:48:N,W1,M1460: Inktomi:crawler::Inktomi crawler +S1:255:1:60:M1460,S,T,N,W0: LookSmart:ZyBorg::LookSmart ZyBorg + +16384:255:0:40:.: Proxyblocker:::Proxyblocker (what's this?) + +65535:255:0:48:M*,N,N,S: Redline:::Redline T|X 2200 + +32696:128:0:40:M1460: Spirent:Avalanche::Spirent Web Avalanche HTTP benchmarking engine + +# ----------- Embedded systems -------------- + +S9:255:0:44:M536: PalmOS:Tungsten:C:PalmOS Tungsten C +S5:255:0:44:M536: PalmOS:3::PalmOS 3/4 +S5:255:0:44:M536: PalmOS:4::PalmOS 3/4 +S4:255:0:44:M536: PalmOS:3:5:PalmOS 3.5 +2948:255:0:44:M536: PalmOS:3:5:PalmOS 3.5.3 (Handera) +S29:255:0:44:M536: PalmOS:5::PalmOS 5.0 +16384:255:0:44:M1398: PalmOS:5.2:Clie:PalmOS 5.2 (Clie) +S14:255:0:44:M1350: PalmOS:5.2:Treo:PalmOS 5.2.1 (Treo) + +S23:64:1:64:N,W1,N,N,T,N,N,S,M1460: SymbianOS:7::SymbianOS 7 + +8192:255:0:44:M1460: SymbianOS:6048::Symbian OS 6048 (Nokia 7650?) +8192:255:0:44:M536: SymbianOS:9210::Symbian OS (Nokia 9210?) +S22:64:1:56:M1460,T,S: SymbianOS:P800::Symbian OS ? (SE P800?) +S36:64:1:56:M1360,T,S: SymbianOS:6600::Symbian OS 60xx (Nokia 6600?) + + +# Perhaps S4? +5840:64:1:60:M1452,S,T,N,W1: Zaurus:3.10::Zaurus 3.10 + +32768:128:1:64:M1460,N,W0,N,N,T0,N,N,S: PocketPC:2002::PocketPC 2002 + +S1:255:0:44:M346: Contiki:1.1:rc0:Contiki 1.1-rc0 + +4096:128:0:44:M1460: Sega:Dreamcast:3.0:Sega Dreamcast Dreamkey 3.0 +T5:64:0:44:M536: Sega:Dreamcast:HKT-3020:Sega Dreamcast HKT-3020 (browser disc 51027) +S22:64:1:44:M1460: Sony:PS2::Sony Playstation 2 (SOCOM?) + +S12:64:0:44:M1452: AXIS:5600:v5.64:AXIS Printer Server 5600 v5.64 + +3100:32:1:44:M1460: Windows:CE:2.0:Windows CE 2.0 + +#################### +# Fancy signatures # +#################### + +1024:64:0:40:.: *NMAP:syn scan:1:NMAP syn scan (1) +2048:64:0:40:.: *NMAP:syn scan:2:NMAP syn scan (2) +3072:64:0:40:.: *NMAP:syn scan:3:NMAP syn scan (3) +4096:64:0:40:.: *NMAP:syn scan:4:NMAP syn scan (4) + +# Requires quirks support +# 1024:64:0:40:.:A:*NMAP:TCP sweep probe (1) +# 2048:64:0:40:.:A:*NMAP:TCP sweep probe (2) +# 3072:64:0:40:.:A:*NMAP:TCP sweep probe (3) +# 4096:64:0:40:.:A:*NMAP:TCP sweep probe (4) + +1024:64:0:60:W10,N,M265,T: *NMAP:OS:1:NMAP OS detection probe (1) +2048:64:0:60:W10,N,M265,T: *NMAP:OS:2:NMAP OS detection probe (2) +3072:64:0:60:W10,N,M265,T: *NMAP:OS:3:NMAP OS detection probe (3) +4096:64:0:60:W10,N,M265,T: *NMAP:OS:4:NMAP OS detection probe (4) + +32767:64:0:40:.: *NAST:::NASTsyn scan + +# Requires quirks support +# 12345:255:0:40:.:A:-p0f:sendsyn utility + + +##################################### +# Generic signatures - just in case # +##################################### + +#*:64:1:60:M*,N,W*,N,N,T: @FreeBSD:4.0-4.9::FreeBSD 4.x/5.x +#*:64:1:60:M*,N,W*,N,N,T: @FreeBSD:5.0-5.1::FreeBSD 4.x/5.x + +*:128:1:52:M*,N,W0,N,N,S: @Windows:XP:RFC1323:Windows XP/2000 (RFC1323 no tstamp) +*:128:1:52:M*,N,W0,N,N,S: @Windows:2000:RFC1323:Windows XP/2000 (RFC1323 no tstamp) +*:128:1:52:M*,N,W*,N,N,S: @Windows:XP:RFC1323:Windows XP/2000 (RFC1323 no tstamp) +*:128:1:52:M*,N,W*,N,N,S: @Windows:2000:RFC1323:Windows XP/2000 (RFC1323 no tstamp) +*:128:1:64:M*,N,W0,N,N,T0,N,N,S: @Windows:XP:RFC1323:Windows XP/2000 (RFC1323) +*:128:1:64:M*,N,W0,N,N,T0,N,N,S: @Windows:2000:RFC1323:Windows XP/2000 (RFC1323) +*:128:1:64:M*,N,W*,N,N,T0,N,N,S: @Windows:XP:RFC1323:Windows XP (RFC1323, w+) +*:128:1:48:M536,N,N,S: @Windows:98::Windows 98 +*:128:1:48:M*,N,N,S: @Windows:XP::Windows XP/2000 +*:128:1:48:M*,N,N,S: @Windows:2000::Windows XP/2000 + + diff --git a/sbin/pfctl/pf_print_state.c b/sbin/pfctl/pf_print_state.c new file mode 100644 index 000000000000..417ff70de975 --- /dev/null +++ b/sbin/pfctl/pf_print_state.c @@ -0,0 +1,461 @@ +/* $OpenBSD: pf_print_state.c,v 1.52 2008/08/12 16:40:18 david Exp $ */ + +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2001 Daniel Hartmeier + * 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. + * + */ + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/endian.h> +#include <net/if.h> +#define TCPSTATES +#include <netinet/tcp_fsm.h> +#include <netinet/sctp.h> +#include <net/pfvar.h> +#include <arpa/inet.h> +#include <netdb.h> + +#include <stdint.h> +#include <stdio.h> +#include <string.h> + +#include "pfctl_parser.h" +#include "pfctl.h" + +void print_name(struct pf_addr *, sa_family_t); + +void +print_addr(struct pf_addr_wrap *addr, sa_family_t af, int verbose) +{ + switch (addr->type) { + case PF_ADDR_DYNIFTL: + printf("(%s", addr->v.ifname); + if (addr->iflags & PFI_AFLAG_NETWORK) + printf(":network"); + if (addr->iflags & PFI_AFLAG_BROADCAST) + printf(":broadcast"); + if (addr->iflags & PFI_AFLAG_PEER) + printf(":peer"); + if (addr->iflags & PFI_AFLAG_NOALIAS) + printf(":0"); + if (verbose) { + if (addr->p.dyncnt <= 0) + printf(":*"); + else + printf(":%d", addr->p.dyncnt); + } + printf(")"); + break; + case PF_ADDR_TABLE: + if (verbose) + if (addr->p.tblcnt == -1) + printf("<%s:*>", addr->v.tblname); + else + printf("<%s:%d>", addr->v.tblname, + addr->p.tblcnt); + else + printf("<%s>", addr->v.tblname); + return; + case PF_ADDR_RANGE: { + print_addr_str(af, &addr->v.a.addr); + printf(" - "); + print_addr_str(af, &addr->v.a.mask); + + break; + } + case PF_ADDR_ADDRMASK: + if (PF_AZERO(&addr->v.a.addr, AF_INET6) && + PF_AZERO(&addr->v.a.mask, AF_INET6)) + printf("any"); + else + print_addr_str(af, &addr->v.a.addr); + break; + case PF_ADDR_NOROUTE: + printf("no-route"); + return; + case PF_ADDR_URPFFAILED: + printf("urpf-failed"); + return; + default: + printf("?"); + return; + } + + /* mask if not _both_ address and mask are zero */ + if (addr->type != PF_ADDR_RANGE && + !(PF_AZERO(&addr->v.a.addr, AF_INET6) && + PF_AZERO(&addr->v.a.mask, AF_INET6))) { + if (af == AF_INET || af == AF_INET6) { + int bits = unmask(&addr->v.a.mask); + if (bits < (af == AF_INET ? 32 : 128)) + printf("/%d", bits); + } + } +} + +void +print_addr_str(sa_family_t af, struct pf_addr *addr) +{ + static char buf[48]; + + if (inet_ntop(af, addr, buf, sizeof(buf)) == NULL) + printf("?"); + else + printf("%s", buf); +} + +void +print_name(struct pf_addr *addr, sa_family_t af) +{ + struct sockaddr_storage ss; + struct sockaddr_in *sin; + struct sockaddr_in6 *sin6; + char host[NI_MAXHOST]; + + memset(&ss, 0, sizeof(ss)); + ss.ss_family = af; + if (ss.ss_family == AF_INET) { + sin = (struct sockaddr_in *)&ss; + sin->sin_len = sizeof(*sin); + sin->sin_addr = addr->v4; + } else { + sin6 = (struct sockaddr_in6 *)&ss; + sin6->sin6_len = sizeof(*sin6); + sin6->sin6_addr = addr->v6; + } + + if (getnameinfo((struct sockaddr *)&ss, ss.ss_len, host, sizeof(host), + NULL, 0, NI_NOFQDN) != 0) + printf("?"); + else + printf("%s", host); +} + +void +print_host(struct pf_addr *addr, u_int16_t port, sa_family_t af, int opts) +{ + struct pf_addr_wrap aw; + + if (opts & PF_OPT_USEDNS) + print_name(addr, af); + else { + memset(&aw, 0, sizeof(aw)); + aw.v.a.addr = *addr; + memset(&aw.v.a.mask, 0xff, sizeof(aw.v.a.mask)); + print_addr(&aw, af, opts & PF_OPT_VERBOSE2); + } + + if (port) { + if (af == AF_INET) + printf(":%u", ntohs(port)); + else + printf("[%u]", ntohs(port)); + } +} + +void +print_seq(struct pfctl_state_peer *p) +{ + if (p->seqdiff) + printf("[%u + %u](+%u)", p->seqlo, + p->seqhi - p->seqlo, p->seqdiff); + else + printf("[%u + %u]", p->seqlo, + p->seqhi - p->seqlo); +} + + +static const char * +sctp_state_name(int state) +{ + switch (state) { + case SCTP_CLOSED: + return ("CLOSED"); + case SCTP_BOUND: + return ("BOUND"); + case SCTP_LISTEN: + return ("LISTEN"); + case SCTP_COOKIE_WAIT: + return ("COOKIE_WAIT"); + case SCTP_COOKIE_ECHOED: + return ("COOKIE_ECHOED"); + case SCTP_ESTABLISHED: + return ("ESTABLISHED"); + case SCTP_SHUTDOWN_SENT: + return ("SHUTDOWN_SENT"); + case SCTP_SHUTDOWN_RECEIVED: + return ("SHUTDOWN_RECEIVED"); + case SCTP_SHUTDOWN_ACK_SENT: + return ("SHUTDOWN_ACK_SENT"); + case SCTP_SHUTDOWN_PENDING: + return ("SHUTDOWN_PENDING"); + default: + return ("?"); + } +} + +void +print_state(struct pfctl_state *s, int opts) +{ + struct pfctl_state_peer *src, *dst; + struct pfctl_state_key *key, *sk, *nk; + const char *protoname; + int min, sec; + uint8_t proto; + int afto = (s->key[PF_SK_STACK].af != s->key[PF_SK_WIRE].af); + int idx; + const char *sn_type_names[] = PF_SN_TYPE_NAMES; +#ifndef __NO_STRICT_ALIGNMENT + struct pfctl_state_key aligned_key[2]; + + bcopy(&s->key, aligned_key, sizeof(aligned_key)); + key = aligned_key; +#else + key = s->key; +#endif + + proto = s->key[PF_SK_WIRE].proto; + + if (s->direction == PF_OUT) { + src = &s->src; + dst = &s->dst; + sk = &key[PF_SK_STACK]; + nk = &key[PF_SK_WIRE]; + if (proto == IPPROTO_ICMP || proto == IPPROTO_ICMPV6) + sk->port[0] = nk->port[0]; + } else { + src = &s->dst; + dst = &s->src; + sk = &key[PF_SK_WIRE]; + nk = &key[PF_SK_STACK]; + if (proto == IPPROTO_ICMP || proto == IPPROTO_ICMPV6) + sk->port[1] = nk->port[1]; + } + printf("%s ", s->ifname); + if ((protoname = pfctl_proto2name(proto)) != NULL) + printf("%s ", protoname); + else + printf("%u ", proto); + + print_host(&nk->addr[1], nk->port[1], nk->af, opts); + if (nk->af != sk->af || PF_ANEQ(&nk->addr[1], &sk->addr[1], nk->af) || + nk->port[1] != sk->port[1]) { + idx = afto ? 0 : 1; + printf(" ("); + print_host(&sk->addr[idx], sk->port[idx], sk->af, + opts); + printf(")"); + } + if (s->direction == PF_OUT || (afto && s->direction == PF_IN)) + printf(" -> "); + else + printf(" <- "); + print_host(&nk->addr[0], nk->port[0], nk->af, opts); + if (nk->af != sk->af || PF_ANEQ(&nk->addr[0], &sk->addr[0], nk->af) || + nk->port[0] != sk->port[0]) { + idx = afto ? 1 : 0; + printf(" ("); + print_host(&sk->addr[idx], sk->port[idx], sk->af, + opts); + printf(")"); + } + + printf(" "); + if (proto == IPPROTO_TCP) { + if (src->state <= TCPS_TIME_WAIT && + dst->state <= TCPS_TIME_WAIT) + printf(" %s:%s\n", tcpstates[src->state], + tcpstates[dst->state]); + else if (src->state == PF_TCPS_PROXY_SRC || + dst->state == PF_TCPS_PROXY_SRC) + printf(" PROXY:SRC\n"); + else if (src->state == PF_TCPS_PROXY_DST || + dst->state == PF_TCPS_PROXY_DST) + printf(" PROXY:DST\n"); + else + printf(" <BAD STATE LEVELS %u:%u>\n", + src->state, dst->state); + if (opts & PF_OPT_VERBOSE) { + printf(" "); + print_seq(src); + if (src->wscale && dst->wscale) + printf(" wscale %u", + src->wscale & PF_WSCALE_MASK); + printf(" "); + print_seq(dst); + if (src->wscale && dst->wscale) + printf(" wscale %u", + dst->wscale & PF_WSCALE_MASK); + printf("\n"); + } + } else if (proto == IPPROTO_UDP && src->state < PFUDPS_NSTATES && + dst->state < PFUDPS_NSTATES) { + const char *states[] = PFUDPS_NAMES; + + printf(" %s:%s\n", states[src->state], states[dst->state]); + } else if (proto == IPPROTO_SCTP) { + printf(" %s:%s\n", sctp_state_name(src->state), + sctp_state_name(dst->state)); +#ifndef INET6 + } else if (proto != IPPROTO_ICMP && src->state < PFOTHERS_NSTATES && + dst->state < PFOTHERS_NSTATES) { +#else + } else if (proto != IPPROTO_ICMP && proto != IPPROTO_ICMPV6 && + src->state < PFOTHERS_NSTATES && dst->state < PFOTHERS_NSTATES) { +#endif + /* XXX ICMP doesn't really have state levels */ + const char *states[] = PFOTHERS_NAMES; + + printf(" %s:%s\n", states[src->state], states[dst->state]); + } else { + printf(" %u:%u\n", src->state, dst->state); + } + + if (opts & PF_OPT_VERBOSE) { + u_int32_t creation = s->creation; + u_int32_t expire = s->expire; + + sec = creation % 60; + creation /= 60; + min = creation % 60; + creation /= 60; + printf(" age %.2u:%.2u:%.2u", creation, min, sec); + sec = expire % 60; + expire /= 60; + min = expire % 60; + expire /= 60; + printf(", expires in %.2u:%.2u:%.2u", expire, min, sec); + + printf(", %ju:%ju pkts, %ju:%ju bytes", + s->packets[0], + s->packets[1], + s->bytes[0], + s->bytes[1]); + if (s->anchor != -1) + printf(", anchor %u", s->anchor); + if (s->rule != -1) + printf(", rule %u", s->rule); + if (s->state_flags & PFSTATE_ALLOWOPTS) + printf(", allow-opts"); + if (s->state_flags & PFSTATE_SLOPPY) + printf(", sloppy"); + if (s->state_flags & PFSTATE_NOSYNC) + printf(", no-sync"); + if (s->state_flags & PFSTATE_PFLOW) + printf(", pflow"); + if (s->state_flags & PFSTATE_ACK) + printf(", psync-ack"); + if (s->state_flags & PFSTATE_NODF) + printf(", no-df"); + if (s->state_flags & PFSTATE_SETTOS) + printf(", set-tos 0x%2.2x", s->set_tos); + if (s->state_flags & PFSTATE_RANDOMID) + printf(", random-id"); + if (s->state_flags & PFSTATE_SCRUB_TCP) + printf(", reassemble-tcp"); + if (s->state_flags & PFSTATE_SETPRIO) + printf(", set-prio (0x%02x 0x%02x)", + s->set_prio[0], s->set_prio[1]); + if (s->dnpipe || s->dnrpipe) { + if (s->state_flags & PFSTATE_DN_IS_PIPE) + printf(", dummynet pipe (%d %d)", + s->dnpipe, s->dnrpipe); + if (s->state_flags & PFSTATE_DN_IS_QUEUE) + printf(", dummynet queue (%d %d)", + s->dnpipe, s->dnrpipe); + } + if (s->src_node_flags & PFSTATE_SRC_NODE_LIMIT) + printf(", %s", sn_type_names[PF_SN_LIMIT]); + if (s->src_node_flags & PFSTATE_SRC_NODE_LIMIT_GLOBAL) + printf(" global"); + if (s->src_node_flags & PFSTATE_SRC_NODE_NAT) + printf(", %s", sn_type_names[PF_SN_NAT]); + if (s->src_node_flags & PFSTATE_SRC_NODE_ROUTE) + printf(", %s", sn_type_names[PF_SN_ROUTE]); + if (s->log) + printf(", log"); + if (s->log & PF_LOG_ALL) + printf(" (all)"); + if (s->min_ttl) + printf(", min-ttl %d", s->min_ttl); + if (s->max_mss) + printf(", max-mss %d", s->max_mss); + printf("\n"); + } + if (opts & PF_OPT_VERBOSE2) { + u_int64_t id; + + bcopy(&s->id, &id, sizeof(u_int64_t)); + printf(" id: %016jx creatorid: %08x", id, s->creatorid); + if (s->rt) { + switch (s->rt) { + case PF_ROUTETO: + printf(" route-to: "); + break; + case PF_DUPTO: + printf(" dup-to: "); + break; + case PF_REPLYTO: + printf(" reply-to: "); + break; + default: + printf(" gateway: "); + } + print_host(&s->rt_addr, 0, s->rt_af, opts); + if (s->rt_ifname[0]) + printf("@%s", s->rt_ifname); + } + if (s->rtableid != -1) + printf(" rtable: %d", s->rtableid); + printf("\n"); + + if (strcmp(s->ifname, s->orig_ifname) != 0) + printf(" origif: %s\n", s->orig_ifname); + } +} + +int +unmask(struct pf_addr *m) +{ + int i = 31, j = 0, b = 0; + u_int32_t tmp; + + while (j < 4 && m->addr32[j] == 0xffffffff) { + b += 32; + j++; + } + if (j < 4) { + tmp = ntohl(m->addr32[j]); + for (i = 31; tmp & (1 << i); --i) + b++; + } + return (b); +} diff --git a/sbin/pfctl/pf_ruleset.c b/sbin/pfctl/pf_ruleset.c new file mode 100644 index 000000000000..2b7ec09f28aa --- /dev/null +++ b/sbin/pfctl/pf_ruleset.c @@ -0,0 +1,539 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * 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. + * + * $OpenBSD: pf_ruleset.c,v 1.2 2008/12/18 15:31:37 dhill Exp $ + */ + +#include <sys/param.h> +#include <sys/socket.h> +#include <sys/mbuf.h> + +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <netinet/tcp.h> + +#include <net/if.h> +#include <net/vnet.h> +#include <net/pfvar.h> + +#ifdef INET6 +#include <netinet/ip6.h> +#endif /* INET6 */ + +#include <arpa/inet.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#define rs_malloc(x) calloc(1, x) +#define rs_free(x) free(x) + +#include "pfctl.h" +#include "pfctl_parser.h" + +#ifdef PFDEBUG +#include <sys/stdarg.h> +#define DPFPRINTF(format, x...) fprintf(stderr, format , ##x) +#else +#define DPFPRINTF(format, x...) ((void)0) +#endif /* PFDEBUG */ + +struct pfctl_anchor_global pf_anchors; +extern struct pfctl_anchor pf_main_anchor; +extern struct pfctl_eth_anchor pf_eth_main_anchor; +#undef V_pf_anchors +#define V_pf_anchors pf_anchors +#undef pf_main_ruleset +#define pf_main_ruleset pf_main_anchor.ruleset + +static __inline int pf_anchor_compare(struct pfctl_anchor *, + struct pfctl_anchor *); +static struct pfctl_anchor *pf_find_anchor(const char *); + +RB_GENERATE(pfctl_anchor_global, pfctl_anchor, entry_global, + pf_anchor_compare); +RB_GENERATE(pfctl_anchor_node, pfctl_anchor, entry_node, pf_anchor_compare); + +static __inline int +pf_anchor_compare(struct pfctl_anchor *a, struct pfctl_anchor *b) +{ + int c = strcmp(a->path, b->path); + + return (c ? (c < 0 ? -1 : 1) : 0); +} + +int +pf_get_ruleset_number(u_int8_t action) +{ + switch (action) { + case PF_SCRUB: + case PF_NOSCRUB: + return (PF_RULESET_SCRUB); + break; + case PF_PASS: + case PF_DROP: + case PF_MATCH: + return (PF_RULESET_FILTER); + break; + case PF_NAT: + case PF_NONAT: + return (PF_RULESET_NAT); + break; + case PF_BINAT: + case PF_NOBINAT: + return (PF_RULESET_BINAT); + break; + case PF_RDR: + case PF_NORDR: + return (PF_RULESET_RDR); + break; + default: + return (PF_RULESET_MAX); + break; + } +} + +void +pf_init_ruleset(struct pfctl_ruleset *ruleset) +{ + int i; + + memset(ruleset, 0, sizeof(struct pfctl_ruleset)); + for (i = 0; i < PF_RULESET_MAX; i++) { + TAILQ_INIT(&ruleset->rules[i].queues[0]); + TAILQ_INIT(&ruleset->rules[i].queues[1]); + ruleset->rules[i].active.ptr = &ruleset->rules[i].queues[0]; + ruleset->rules[i].inactive.ptr = &ruleset->rules[i].queues[1]; + } +} + +static struct pfctl_anchor * +pf_find_anchor(const char *path) +{ + struct pfctl_anchor *key, *found; + + key = (struct pfctl_anchor *)rs_malloc(sizeof(*key)); + if (key == NULL) + return (NULL); + strlcpy(key->path, path, sizeof(key->path)); + found = RB_FIND(pfctl_anchor_global, &V_pf_anchors, key); + rs_free(key); + return (found); +} + +struct pfctl_ruleset * +pf_find_ruleset(const char *path) +{ + struct pfctl_anchor *anchor; + + while (*path == '/') + path++; + if (!*path) + return (&pf_main_ruleset); + anchor = pf_find_anchor(path); + if (anchor == NULL) + return (NULL); + else + return (&anchor->ruleset); +} + +struct pfctl_ruleset * +pf_find_or_create_ruleset(const char *path) +{ + char *p, *q, *r; + struct pfctl_ruleset *ruleset; + struct pfctl_anchor *anchor = NULL, *dup, *parent = NULL; + + if (path[0] == 0) + return (&pf_main_ruleset); + while (*path == '/') + path++; + ruleset = pf_find_ruleset(path); + if (ruleset != NULL) + return (ruleset); + p = (char *)rs_malloc(MAXPATHLEN); + if (p == NULL) + return (NULL); + strlcpy(p, path, MAXPATHLEN); + while (parent == NULL && (q = strrchr(p, '/')) != NULL) { + *q = 0; + if ((ruleset = pf_find_ruleset(p)) != NULL) { + parent = ruleset->anchor; + break; + } + } + if (q == NULL) + q = p; + else + q++; + strlcpy(p, path, MAXPATHLEN); + if (!*q) { + rs_free(p); + return (NULL); + } + while ((r = strchr(q, '/')) != NULL || *q) { + if (r != NULL) + *r = 0; + if (!*q || strlen(q) >= PF_ANCHOR_NAME_SIZE || + (parent != NULL && strlen(parent->path) >= + MAXPATHLEN - PF_ANCHOR_NAME_SIZE - 1)) { + rs_free(p); + return (NULL); + } + anchor = (struct pfctl_anchor *)rs_malloc(sizeof(*anchor)); + if (anchor == NULL) { + rs_free(p); + return (NULL); + } + RB_INIT(&anchor->children); + strlcpy(anchor->name, q, sizeof(anchor->name)); + if (parent != NULL) { + strlcpy(anchor->path, parent->path, + sizeof(anchor->path)); + strlcat(anchor->path, "/", sizeof(anchor->path)); + } + strlcat(anchor->path, anchor->name, sizeof(anchor->path)); + if ((dup = RB_INSERT(pfctl_anchor_global, &V_pf_anchors, anchor)) != + NULL) { + printf("pf_find_or_create_ruleset: RB_INSERT1 " + "'%s' '%s' collides with '%s' '%s'\n", + anchor->path, anchor->name, dup->path, dup->name); + rs_free(anchor); + rs_free(p); + return (NULL); + } + if (parent != NULL) { + anchor->parent = parent; + if ((dup = RB_INSERT(pfctl_anchor_node, &parent->children, + anchor)) != NULL) { + printf("pf_find_or_create_ruleset: " + "RB_INSERT2 '%s' '%s' collides with " + "'%s' '%s'\n", anchor->path, anchor->name, + dup->path, dup->name); + RB_REMOVE(pfctl_anchor_global, &V_pf_anchors, + anchor); + rs_free(anchor); + rs_free(p); + return (NULL); + } + } + pf_init_ruleset(&anchor->ruleset); + anchor->ruleset.anchor = anchor; + parent = anchor; + if (r != NULL) + q = r + 1; + else + *q = 0; + } + rs_free(p); + return (&anchor->ruleset); +} + +void +pf_remove_if_empty_ruleset(struct pfctl_ruleset *ruleset) +{ + struct pfctl_anchor *parent; + int i; + + while (ruleset != NULL) { + if (ruleset == &pf_main_ruleset || ruleset->anchor == NULL || + !RB_EMPTY(&ruleset->anchor->children) || + ruleset->anchor->refcnt > 0 || ruleset->tables > 0 || + ruleset->topen) + return; + for (i = 0; i < PF_RULESET_MAX; ++i) + if (!TAILQ_EMPTY(ruleset->rules[i].active.ptr) || + !TAILQ_EMPTY(ruleset->rules[i].inactive.ptr) || + ruleset->rules[i].inactive.open) + return; + RB_REMOVE(pfctl_anchor_global, &V_pf_anchors, ruleset->anchor); + if ((parent = ruleset->anchor->parent) != NULL) + RB_REMOVE(pfctl_anchor_node, &parent->children, + ruleset->anchor); + rs_free(ruleset->anchor); + if (parent == NULL) + return; + ruleset = &parent->ruleset; + } +} + +void +pf_remove_if_empty_eth_ruleset(struct pfctl_eth_ruleset *ruleset) +{ + struct pfctl_eth_anchor *parent; + + return; + while (ruleset != NULL) { + if (ruleset == &pf_eth_main_anchor.ruleset || + ruleset->anchor == NULL || ruleset->anchor->refcnt > 0) + return; + if (!TAILQ_EMPTY(&ruleset->rules)) + return; + rs_free(ruleset->anchor); + if (parent == NULL) + return; + ruleset = &parent->ruleset; + } +} + +void +pf_init_eth_ruleset(struct pfctl_eth_ruleset *ruleset) +{ + + memset(ruleset, 0, sizeof(*ruleset)); + TAILQ_INIT(&ruleset->rules); +} + + +static struct pfctl_eth_anchor* +_pf_find_eth_anchor(struct pfctl_eth_anchor *anchor, const char *path) +{ + struct pfctl_eth_rule *r; + struct pfctl_eth_anchor *a; + + if (strcmp(path, anchor->path) == 0) + return (anchor); + + TAILQ_FOREACH(r, &anchor->ruleset.rules, entries) { + if (! r->anchor) + continue; + + /* Step into anchor */ + a = _pf_find_eth_anchor(r->anchor, path); + if (a) + return (a); + } + + return (NULL); +} + +static struct pfctl_eth_anchor* +pf_find_eth_anchor(const char *path) +{ + return (_pf_find_eth_anchor(&pf_eth_main_anchor, path)); +} + +static struct pfctl_eth_ruleset* +pf_find_eth_ruleset(const char *path) +{ + struct pfctl_eth_anchor *anchor; + + while (*path == '/') + path++; + if (!*path) + return (&pf_eth_main_anchor.ruleset); + anchor = pf_find_eth_anchor(path); + if (anchor == NULL) + return (NULL); + else + return (&anchor->ruleset); +} + +struct pfctl_eth_ruleset * +pf_find_or_create_eth_ruleset(const char *path) +{ + char *p, *q, *r; + struct pfctl_eth_ruleset *ruleset; + struct pfctl_eth_anchor *anchor = NULL, *parent = NULL; + + if (path[0] == 0) + return (&pf_eth_main_anchor.ruleset); + while (*path == '/') + path++; + ruleset = pf_find_eth_ruleset(path); + if (ruleset != NULL) + return (ruleset); + p = (char *)rs_malloc(MAXPATHLEN); + if (p == NULL) + return (NULL); + strlcpy(p, path, MAXPATHLEN); + while (parent == NULL && (q = strrchr(p, '/')) != NULL) { + *q = 0; + if ((ruleset = pf_find_eth_ruleset(p)) != NULL) { + parent = ruleset->anchor; + break; + } + } + if (q == NULL) + q = p; + else + q++; + strlcpy(p, path, MAXPATHLEN); + if (!*q) { + rs_free(p); + return (NULL); + } + while ((r = strchr(q, '/')) != NULL || *q) { + if (r != NULL) + *r = 0; + if (!*q || strlen(q) >= PF_ANCHOR_NAME_SIZE || + (parent != NULL && strlen(parent->path) >= + MAXPATHLEN - PF_ANCHOR_NAME_SIZE - 1)) { + rs_free(p); + return (NULL); + } + anchor = (struct pfctl_eth_anchor *)rs_malloc(sizeof(*anchor)); + if (anchor == NULL) { + rs_free(p); + return (NULL); + } + strlcpy(anchor->name, q, sizeof(anchor->name)); + if (parent != NULL) { + strlcpy(anchor->path, parent->path, + sizeof(anchor->path)); + strlcat(anchor->path, "/", sizeof(anchor->path)); + } + strlcat(anchor->path, anchor->name, sizeof(anchor->path)); + if (parent != NULL) + anchor->parent = parent; + pf_init_eth_ruleset(&anchor->ruleset); + anchor->ruleset.anchor = anchor; + parent = anchor; + if (r != NULL) + q = r + 1; + else + *q = 0; + } + rs_free(p); + return (&anchor->ruleset); +} + +int +pfctl_anchor_setup(struct pfctl_rule *r, const struct pfctl_ruleset *s, + const char *name) +{ + char *p, *path; + struct pfctl_ruleset *ruleset; + + r->anchor = NULL; + r->anchor_relative = 0; + r->anchor_wildcard = 0; + if (!name[0]) + return (0); + path = (char *)rs_malloc(MAXPATHLEN); + if (path == NULL) + return (1); + if (name[0] == '/') + strlcpy(path, name + 1, MAXPATHLEN); + else { + /* relative path */ + r->anchor_relative = 1; + if (s->anchor == NULL || !s->anchor->path[0]) + path[0] = 0; + else + strlcpy(path, s->anchor->path, MAXPATHLEN); + while (name[0] == '.' && name[1] == '.' && name[2] == '/') { + if (!path[0]) { + printf("pfctl_anchor_setup: .. beyond root\n"); + rs_free(path); + return (1); + } + if ((p = strrchr(path, '/')) != NULL) + *p = 0; + else + path[0] = 0; + r->anchor_relative++; + name += 3; + } + if (path[0]) + strlcat(path, "/", MAXPATHLEN); + strlcat(path, name, MAXPATHLEN); + } + if ((p = strrchr(path, '/')) != NULL && !strcmp(p, "/*")) { + r->anchor_wildcard = 1; + *p = 0; + } + ruleset = pf_find_or_create_ruleset(path); + rs_free(path); + if (ruleset == NULL || ruleset->anchor == NULL) { + printf("pfctl_anchor_setup: ruleset\n"); + return (1); + } + r->anchor = ruleset->anchor; + r->anchor->refcnt++; + return (0); +} + +int +pfctl_eth_anchor_setup(struct pfctl *pf, struct pfctl_eth_rule *r, + const struct pfctl_eth_ruleset *s, const char *name) +{ + char *p, *path; + struct pfctl_eth_ruleset *ruleset; + + r->anchor = NULL; + if (!name[0]) + return (0); + path = (char *)rs_malloc(MAXPATHLEN); + if (path == NULL) + return (1); + if (name[0] == '/') + strlcpy(path, name + 1, MAXPATHLEN); + else { + /* relative path */ + if (s->anchor == NULL || !s->anchor->path[0]) + path[0] = 0; + else + strlcpy(path, s->anchor->path, MAXPATHLEN); + while (name[0] == '.' && name[1] == '.' && name[2] == '/') { + if (!path[0]) { + printf("%s: .. beyond root\n", __func__); + rs_free(path); + return (1); + } + if ((p = strrchr(path, '/')) != NULL) + *p = 0; + else + path[0] = 0; + name += 3; + } + if (path[0]) + strlcat(path, "/", MAXPATHLEN); + strlcat(path, name, MAXPATHLEN); + } + if ((p = strrchr(path, '/')) != NULL && !strcmp(p, "/*")) { + *p = 0; + } + ruleset = pf_find_or_create_eth_ruleset(path); + rs_free(path); + if (ruleset == NULL || ruleset->anchor == NULL) { + printf("%s: ruleset\n", __func__); + return (1); + } + r->anchor = ruleset->anchor; + r->anchor->refcnt++; + return (0); +} diff --git a/sbin/pfctl/pfctl.8 b/sbin/pfctl/pfctl.8 new file mode 100644 index 000000000000..58de54cdf923 --- /dev/null +++ b/sbin/pfctl/pfctl.8 @@ -0,0 +1,830 @@ +.\" $OpenBSD: pfctl.8,v 1.138 2008/06/10 20:55:02 mcbride Exp $ +.\" +.\" Copyright (c) 2001 Kjell Wooding. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. The name of the author may not be used to endorse or promote products +.\" derived from this software without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +.\" IN NO EVENT SHALL THE AUTHOR 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. +.\" +.Dd August 28, 2025 +.Dt PFCTL 8 +.Os +.Sh NAME +.Nm pfctl +.Nd control the packet filter (PF) device +.Sh SYNOPSIS +.Nm pfctl +.Bk -words +.Op Fl AdeghMmNnOPqRrvz +.Op Fl a Ar anchor +.Oo Fl D Ar macro Ns = +.Ar value Oc +.Op Fl F Ar modifier +.Op Fl f Ar file +.Op Fl i Ar interface +.Op Fl K Ar host | network +.Xo +.Oo Fl k +.Ar host | network | label | id | gateway | nat +.Oc Xc +.Op Fl o Ar level +.Op Fl p Ar device +.Op Fl s Ar modifier +.Xo +.Oo Fl t Ar table +.Fl T Ar command +.Op Ar address ... +.Oc Xc +.Op Fl x Ar level +.Ek +.Sh DESCRIPTION +The +.Nm +utility communicates with the packet filter device using the +ioctl interface described in +.Xr pf 4 . +It allows ruleset and parameter configuration and retrieval of status +information from the packet filter. +.Pp +Packet filtering restricts the types of packets that pass through +network interfaces entering or leaving the host based on filter +rules as described in +.Xr pf.conf 5 . +The packet filter can also replace addresses and ports of packets. +Replacing source addresses and ports of outgoing packets is called +NAT (Network Address Translation) and is used to connect an internal +network (usually reserved address space) to an external one (the +Internet) by making all connections to external hosts appear to +come from the gateway. +Replacing destination addresses and ports of incoming packets +is used to redirect connections to different hosts and/or ports. +A combination of both translations, bidirectional NAT, is also +supported. +Translation rules are described in +.Xr pf.conf 5 . +.Pp +When the variable +.Va pf_enable +is set to +.Dv YES +in +.Xr rc.conf 5 , +the rule file specified with the variable +.Va pf_rules +is loaded automatically by the +.Xr rc 8 +scripts and the packet filter is enabled. +.Pp +The packet filter does not itself forward packets between interfaces. +Forwarding can be enabled by setting the +.Xr sysctl 8 +variables +.Em net.inet.ip.forwarding +and/or +.Em net.inet6.ip6.forwarding +to 1. +Set them permanently in +.Xr sysctl.conf 5 . +.Pp +At least one option must be specified. +The options are as follows: +.Bl -tag -width Ds +.It Fl A +Load only the queue rules present in the rule file. +Other rules and options are ignored. +.It Fl a Ar anchor +Apply flags +.Fl f , +.Fl F , +.Fl s , +.Fl T , +and +.Fl z +only to the rules in the specified +.Ar anchor . +In addition to the main ruleset, +.Nm +can load and manipulate additional rulesets by name, +called anchors. +The main ruleset is the default anchor. +.Pp +Anchors are referenced by name and may be nested, +with the various components of the anchor path separated by +.Sq / +characters, similar to how file system hierarchies are laid out. +The last component of the anchor path is where ruleset operations are +performed. +.Pp +Evaluation of +.Ar anchor +rules from the main ruleset is described in +.Xr pf.conf 5 . +.Pp +For example, the following will show all filter rules (see the +.Fl s +flag below) inside the anchor +.Dq authpf/smith(1234) , +which would have been created for user +.Dq smith +by +.Xr authpf 8 , +PID 1234: +.Bd -literal -offset indent +# pfctl -a "authpf/smith(1234)" -s rules +.Ed +.Pp +Private tables can also be put inside anchors, either by having table +statements in the +.Xr pf.conf 5 +file that is loaded in the anchor, or by using regular table commands, as in: +.Bd -literal -offset indent +# pfctl -a foo/bar -t mytable -T add 1.2.3.4 5.6.7.8 +.Ed +.Pp +When a rule referring to a table is loaded in an anchor, the rule will use the +private table if one is defined, and then fall back to the table defined in the +main ruleset, if there is one. +This is similar to C rules for variable scope. +It is possible to create distinct tables with the same name in the global +ruleset and in an anchor, but this is often bad design and a warning will be +issued in that case. +.Pp +By default, recursive inline printing of anchors applies only to unnamed +anchors specified inline in the ruleset. +If the anchor name is terminated with a +.Sq * +character, the +.Fl s +flag will recursively print all anchors in a brace delimited block. +For example the following will print the +.Dq authpf +ruleset recursively: +.Bd -literal -offset indent +# pfctl -a 'authpf/*' -sr +.Ed +.Pp +To print the main ruleset recursively, specify only +.Sq * +as the anchor name: +.Bd -literal -offset indent +# pfctl -a '*' -sr +.Ed +.Pp +To flush all rulesets and tables recursively, specify only +.Sq * +as the anchor name: +.Bd -literal -offset indent +# pfctl -a '*' -Fa +.Ed +.It Fl D Ar macro Ns = Ns Ar value +Define +.Ar macro +to be set to +.Ar value +on the command line. +Overrides the definition of +.Ar macro +in the ruleset. +.It Fl d +Disable the packet filter. +.It Fl e +Enable the packet filter. +.It Fl F Ar modifier +Flush the filter parameters specified by +.Ar modifier +(may be abbreviated): +.Pp +.Bl -tag -width xxxxxxxxx -compact +.It Cm nat +Flush the NAT rules. +.It Cm queue +Flush the queue rules. +.It Cm ethernet +Flush the Ethernet filter rules. +.It Cm rules +Flush the filter rules. +.It Cm states +Flush the state table (NAT and filter). +.It Cm Sources +Flush the source tracking table. +.It Cm info +Flush the filter information (statistics that are not bound to rules). +.It Cm Tables +Flush the tables. +.It Cm osfp +Flush the passive operating system fingerprints. +.It Cm Reset +Reset limits, timeouts and other options back to default settings. +See the OPTIONS section in +.Xr pf.conf 5 +for details. +.It Cm all +Flush all of the above. +.El +.Pp +If +.Fl a +is specified as well and +.Ar anchor +is terminated with a +.Sq * +character, +.Cm rules , +.Cm Tables +and +.Cm all +flush the given anchor recursively. +.It Fl f Ar file +Load the rules contained in +.Ar file . +This +.Ar file +may contain macros, tables, options, and normalization, queueing, +translation, and filtering rules. +With the exception of macros and tables, the statements must appear in that +order. +.It Fl g +Include output helpful for debugging. +.It Fl h +Help. +.It Fl i Ar interface +Restrict the operation to the given +.Ar interface . +.It Fl K Ar host | network +Kill all of the source tracking entries originating from the specified +.Ar host +or +.Ar network . +A second +.Fl K Ar host +or +.Fl K Ar network +option may be specified, which will kill all the source tracking +entries from the first host/network to the second. +.It Xo +.Fl k +.Ar host | network | label | id | key | gateway | nat +.Xc +Kill all of the state entries matching the specified +.Ar host , +.Ar network , +.Ar label , +.Ar id , +.Ar key , +.Ar gateway, +or +.Ar nat. +.Pp +For example, to kill all of the state entries originating from +.Dq host : +.Pp +.Dl # pfctl -k host +.Pp +A second +.Fl k Ar host +or +.Fl k Ar network +option may be specified, which will kill all the state entries +from the first host/network to the second. +To kill all of the state entries from +.Dq host1 +to +.Dq host2 : +.Pp +.Dl # pfctl -k host1 -k host2 +.Pp +To kill all states originating from 192.168.1.0/24 to 172.16.0.0/16: +.Pp +.Dl # pfctl -k 192.168.1.0/24 -k 172.16.0.0/16 +.Pp +A network prefix length of 0 can be used as a wildcard. +To kill all states with the target +.Dq host2 : +.Pp +.Dl # pfctl -k 0.0.0.0/0 -k host2 +.Pp +It is also possible to kill states by rule label, state key or state ID. +In this mode the first +.Fl k +argument is used to specify the type +of the second argument. +The following command would kill all states that have been created +from rules carrying the label +.Dq foobar : +.Pp +.Dl # pfctl -k label -k foobar +.Pp +To kill one specific state by its key +(protocol, host1, port1, direction, host2 and port2 in the same format +of pfctl -s state), +use the +.Ar key +modifier and as a second argument the state key. +To kill a state whose protocol is TCP and originating from +10.0.0.101:32123 to 10.0.0.1:80 use: +.Pp +.Dl # pfctl -k key -k 'tcp 10.0.0.1:80 <- 10.0.0.101:32123' +.Pp +To kill one specific state by its unique state ID +(as shown by pfctl -s state -vv), +use the +.Ar id +modifier and as a second argument the state ID and optional creator ID. +To kill a state with ID 4823e84500000003 use: +.Pp +.Dl # pfctl -k id -k 4823e84500000003 +.Pp +To kill a state with ID 4823e84500000018 created from a backup +firewall with hostid 00000002 use: +.Pp +.Dl # pfctl -k id -k 4823e84500000018/2 +.Pp +It is also possible to kill states created from a rule with the route-to/reply-to +parameter set to route the connection through a particular gateway. +Note that rules routing via the default routing table (not via a route-to +rule) will have their rt_addr set as 0.0.0.0 or ::. +To kill all states using a gateway of 192.168.0.1 use: +.Pp +.Dl # pfctl -k gateway -k 192.168.0.1 +.Pp +A network prefix length can also be specified. +To kill all states using a gateway in 192.168.0.0/24: +.Pp +.Dl # pfctl -k gateway -k 192.168.0.0/24 +.Pp +States can also be killed based on their pre-NAT address: +.Pp +.Dl # pfctl -k nat -k 192.168.0.1 +.Pp +.It Fl M +Kill matching states in the opposite direction (on other interfaces) when +killing states. +This applies to states killed using the -k option and also will apply to the +flush command when flushing states. +This is useful when an interface is specified when flushing states. +Example: +.Pp +.Dl # pfctl -M -i interface -Fs +.Pp +.It Fl m +Merge in explicitly given options without resetting those +which are omitted. +Allows single options to be modified without disturbing the others: +.Bd -literal -offset indent +# echo "set loginterface fxp0" | pfctl -mf - +.Ed +.It Fl N +Load only the NAT rules present in the rule file. +Other rules and options are ignored. +.It Fl n +Do not actually load rules, just parse them. +.It Fl O +Load only the options present in the rule file. +Other rules and options are ignored. +.It Fl o Ar level +Control the ruleset optimizer, overriding any rule file settings. +.Pp +.Bl -tag -width xxxxxxxxx -compact +.It Cm none +Disable the ruleset optimizer. +.It Cm basic +Enable basic ruleset optimizations. +This is the default behaviour. +.It Cm profile +Enable basic ruleset optimizations with profiling. +.El +.Pp +For further information on the ruleset optimizer, see +.Xr pf.conf 5 . +.It Fl P +Do not perform service name lookup for port specific rules, +instead display the ports numerically. +.It Fl p Ar device +Use the device file +.Ar device +instead of the default +.Pa /dev/pf . +.It Fl q +Only print errors and warnings. +.It Fl R +Load only the filter rules present in the rule file. +Other rules and options are ignored. +.It Fl r +Perform reverse DNS lookups on states and tables when displaying them. +.Fl N +and +.Fl r +are mutually exclusive. +.It Fl s Ar modifier Op Fl R Ar id +Show the filter parameters specified by +.Ar modifier +(may be abbreviated): +.Pp +.Bl -tag -width xxxxxxxxxxx -compact +.It Cm nat +Show the currently loaded NAT rules. +.It Cm queue +Show the currently loaded queue rules. +When used together with +.Fl v , +per-queue statistics are also shown. +When used together with +.Fl v v , +.Nm +will loop and show updated queue statistics every five seconds, including +measured bandwidth and packets per second. +.It Cm ether +Show the currently loaded Ethernet rules. +When used together with +.Fl v , +the per-rule statistics (number of evaluations, +packets, and bytes) are also shown. +.It Cm rules +Show the currently loaded filter rules. +When used together with +.Fl v , +the per-rule statistics (number of evaluations, +packets, and bytes) are also shown. +When used together with +.Fl g +or +.Fl vv , +expired rules +.Pq marked as Dq # expired +are also shown. +Note that the +.Dq skip step +optimization done automatically by the kernel +will skip evaluation of rules where possible. +Packets passed statefully are counted in the rule that created the state +(even though the rule is not evaluated more than once for the entire +connection). +.It Cm Anchors +Show the currently loaded anchors directly attached to the main ruleset. +If +.Fl a Ar anchor +is specified as well, the anchors loaded directly below the given +.Ar anchor +are shown instead. +If +.Fl v +is specified, all anchors attached under the target anchor will be +displayed recursively. +.It Cm states +Show the contents of the state table. +.It Cm Sources +Show the contents of the source tracking table. +.It Cm info +Show filter information (statistics and counters). +When used together with +.Fl v , +source tracking statistics, the firewall's 32-bit hostid number and the +main ruleset's MD5 checksum for use with +.Xr pfsync 4 +are also shown. +.It Cm Running +Show the running status and provide a non-zero exit status when disabled. +.It Cm labels +Show per-rule statistics (label, evaluations, packets total, bytes total, +packets in, bytes in, packets out, bytes out, state creations) of +filter rules with labels, useful for accounting. +.It Cm timeouts +Show the current global timeouts. +.It Cm memory +Show the current pool memory hard limits. +.It Cm Tables +Show the list of tables. +.It Cm osfp +Show the list of operating system fingerprints. +.It Cm Interfaces +Show the list of interfaces and interface groups available to PF. +When used together with +.Fl v , +it additionally lists which interfaces have skip rules activated. +When used together with +.Fl vv , +interface statistics are also shown. +.Fl i +can be used to select an interface or a group of interfaces. +.It Cm all +Show all of the above, except for the lists of interfaces and operating +system fingerprints. +.El +.Pp +Counters shown with +.Fl s Cm info +are: +.Pp +.Bl -tag -width xxxxxxxxxxxxxx -compact +.It match +explicit rule match +.It bad-offset +currently unused +.It fragment +invalid fragments dropped +.It short +short packets dropped +.It normalize +dropped by normalizer: illegal packets +.It memory +memory could not be allocated +.It bad-timestamp +bad TCP timestamp; RFC 1323 +.It congestion +network interface queue congested +.It ip-option +bad IP/IPv6 options +.It proto-cksum +invalid protocol checksum +.It state-mismatch +packet was associated with a state entry, but sequence numbers did not match +.It state-insert +state insertion failure +.It state-limit +configured state limit was reached +.It src-limit +source node/connection limit +.It synproxy +dropped by synproxy +.It map-failed +address mapping failed +.It translate +no free ports in translation port range +.El +.It Fl S +Do not perform domain name resolution. +If a name cannot be resolved without DNS, an error will be reported. +.It Fl t Ar table Fl T Ar command Op Ar address ... +Specify the +.Ar command +(may be abbreviated) to apply to +.Ar table . +Commands include: +.Pp +.Bl -tag -width "expire number" -compact +.It Cm add +Add one or more addresses to a table. +Automatically create a persistent table if it does not exist. +.It Cm delete +Delete one or more addresses from a table. +.It Cm expire Ar number +Delete addresses which had their statistics cleared more than +.Ar number +seconds ago. +For entries which have never had their statistics cleared, +.Ar number +refers to the time they were added to the table. +.It Cm flush +Flush all addresses in a table. +.It Cm kill +Kill a table. +.It Cm replace +Replace the addresses of the table. +Automatically create a persistent table if it does not exist. +.It Cm show +Show the content (addresses) of a table. +.It Cm test +Test if the given addresses match a table. +.It Cm zero Op Ar address ... +Clear all the statistics of a table, or only for specified addresses. +.It Cm reset +Clear statistics only for addresses with non-zero statistics. Addresses +with counter values at zero and their +.Dq Cleared +timestamp are left untouched. +.It Cm load +Load only the table definitions from +.Xr pf.conf 5 . +This is used in conjunction with the +.Fl f +flag, as in: +.Bd -literal -offset indent +# pfctl -Tl -f pf.conf +.Ed +.El +.Pp +For the +.Cm add , +.Cm delete , +.Cm replace , +and +.Cm test +commands, the list of addresses can be specified either directly on the command +line and/or in an unformatted text file, using the +.Fl f +flag. +Comments starting with a +.Sq # +or +.Sq \; +are allowed in the text file. +With these commands, the +.Fl v +flag can also be used once or twice, in which case +.Nm +will print the +detailed result of the operation for each individual address, prefixed by +one of the following letters: +.Pp +.Bl -tag -width XXX -compact +.It A +The address/network has been added. +.It C +The address/network has been changed (negated). +.It D +The address/network has been deleted. +.It M +The address matches +.Po +.Cm test +operation only +.Pc . +.It X +The address/network is duplicated and therefore ignored. +.It Y +The address/network cannot be added/deleted due to conflicting +.Sq \&! +attributes. +.It Z +The address/network has been cleared (statistics). +.El +.Pp +Each table can maintain a set of counters that can be retrieved using the +.Fl v +flag of +.Nm . +For example, the following commands define a wide open firewall which will keep +track of packets going to or coming from the +.Ox +FTP server. +The following commands configure the firewall and send 10 pings to the FTP +server: +.Bd -literal -offset indent +# printf "table <test> counters { ftp.openbsd.org }\en \e + pass out to <test>\en" | pfctl -f- +# ping -qc10 ftp.openbsd.org +.Ed +.Pp +We can now use the table +.Cm show +command to output, for each address and packet direction, the number of packets +and bytes that are being passed or blocked by rules referencing the table. +The time at which the current accounting started is also shown with the +.Dq Cleared +line. +.Bd -literal -offset indent +# pfctl -t test -vTshow + 129.128.5.191 + Cleared: Thu Feb 13 18:55:18 2003 + In/Block: [ Packets: 0 Bytes: 0 ] + In/Pass: [ Packets: 10 Bytes: 840 ] + Out/Block: [ Packets: 0 Bytes: 0 ] + Out/Pass: [ Packets: 10 Bytes: 840 ] +.Ed +.Pp +Similarly, it is possible to view global information about the tables +by using the +.Fl v +modifier twice and the +.Fl s +.Cm Tables +command. +This will display the number of addresses on each table, +the number of rules which reference the table, and the global +packet statistics for the whole table: +.Bd -literal -offset indent +# pfctl -vvsTables +--a-r-C test + Addresses: 1 + Cleared: Thu Feb 13 18:55:18 2003 + References: [ Anchors: 0 Rules: 1 ] + Evaluations: [ NoMatch: 3496 Match: 1 ] + In/Block: [ Packets: 0 Bytes: 0 ] + In/Pass: [ Packets: 10 Bytes: 840 ] + In/XPass: [ Packets: 0 Bytes: 0 ] + Out/Block: [ Packets: 0 Bytes: 0 ] + Out/Pass: [ Packets: 10 Bytes: 840 ] + Out/XPass: [ Packets: 0 Bytes: 0 ] +.Ed +.Pp +As we can see here, only one packet \- the initial ping request \- matched the +table, but all packets passing as the result of the state are correctly +accounted for. +Reloading the table(s) or ruleset will not affect packet accounting in any way. +The two +.Dq XPass +counters are incremented instead of the +.Dq Pass +counters when a +.Dq stateful +packet is passed but does not match the table anymore. +This will happen in our example if someone flushes the table while the +.Xr ping 8 +command is running. +.Pp +When used with a single +.Fl v , +.Nm +will only display the first line containing the table flags and name. +The flags are defined as follows: +.Pp +.Bl -tag -width XXX -compact +.It c +For constant tables, which cannot be altered outside +.Xr pf.conf 5 . +.It p +For persistent tables, which do not get automatically killed when no rules +refer to them. +.It a +For tables which are part of the +.Em active +tableset. +Tables without this flag do not really exist, cannot contain addresses, and are +only listed if the +.Fl g +flag is given. +.It i +For tables which are part of the +.Em inactive +tableset. +This flag can only be witnessed briefly during the loading of +.Xr pf.conf 5 . +.It r +For tables which are referenced (used) by rules. +.It h +This flag is set when a table in the main ruleset is hidden by one or more +tables of the same name from anchors attached below it. +.It C +This flag is set when per-address counters are enabled on the table. +.El +.It Fl v +Produce more verbose output. +A second use of +.Fl v +will produce even more verbose output including ruleset warnings. +See the previous section for its effect on table commands. +.It Fl x Ar level +Set the debug +.Ar level +(may be abbreviated) to one of the following: +.Pp +.Bl -tag -width xxxxxxxxxxxx -compact +.It Fl x Cm none +Do not generate debug messages. +.It Fl x Cm urgent +Generate debug messages only for serious errors. +.It Fl x Cm misc +Generate debug messages for various errors. +.It Fl x Cm loud +Generate debug messages for common conditions. +.El +.It Fl z +Clear per-rule statistics. +.El +.Sh FILES +.Bl -tag -width "/etc/pf.conf" -compact +.It Pa /etc/pf.conf +Packet filter rules file. +.It Pa /etc/pf.os +Passive operating system fingerprint database. +.El +.Sh SEE ALSO +.Xr pf 4 , +.Xr pf.conf 5 , +.Xr pf.os 5 , +.Xr rc.conf 5 , +.Xr services 5 , +.Xr sysctl.conf 5 , +.Xr authpf 8 , +.Xr ftp-proxy 8 , +.Xr rc 8 , +.Xr sysctl 8 +.Sh HISTORY +The +.Nm +program and the +.Xr pf 4 +filter mechanism appeared in +.Ox 3.0 . +They first appeared in +.Fx 5.3 +ported from the version in +.Ox 3.5 diff --git a/sbin/pfctl/pfctl.c b/sbin/pfctl/pfctl.c new file mode 100644 index 000000000000..02d6c9c84a32 --- /dev/null +++ b/sbin/pfctl/pfctl.c @@ -0,0 +1,3759 @@ +/* $OpenBSD: pfctl.c,v 1.278 2008/08/31 20:18:17 jmc Exp $ */ + +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * 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. + * + */ + +#include <sys/cdefs.h> +#define PFIOC_USE_LATEST + +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/endian.h> + +#include <net/if.h> +#include <netinet/in.h> +#include <net/pfvar.h> +#include <arpa/inet.h> +#include <net/altq/altq.h> + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <libpfctl.h> +#include <limits.h> +#include <netdb.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <stdarg.h> +#include <libgen.h> + +#include "pfctl_parser.h" +#include "pfctl.h" + +void usage(void); +int pfctl_enable(int, int); +int pfctl_disable(int, int); +void pfctl_clear_stats(struct pfctl_handle *, int); +void pfctl_get_skip_ifaces(void); +void pfctl_check_skip_ifaces(char *); +void pfctl_adjust_skip_ifaces(struct pfctl *); +void pfctl_clear_interface_flags(int, int); +void pfctl_flush_eth_rules(int, int, char *); +int pfctl_flush_rules(int, int, char *); +void pfctl_flush_nat(int, int, char *); +int pfctl_clear_altq(int, int); +void pfctl_clear_src_nodes(int, int); +void pfctl_clear_iface_states(int, const char *, int); +struct addrinfo * + pfctl_addrprefix(char *, struct pf_addr *, int); +void pfctl_kill_src_nodes(int, int); +void pfctl_net_kill_states(int, const char *, int); +void pfctl_gateway_kill_states(int, const char *, int); +void pfctl_label_kill_states(int, const char *, int); +void pfctl_id_kill_states(int, const char *, int); +void pfctl_key_kill_states(int, const char *, int); +int pfctl_parse_host(char *, struct pf_rule_addr *); +void pfctl_init_options(struct pfctl *); +int pfctl_load_options(struct pfctl *); +int pfctl_load_limit(struct pfctl *, unsigned int, unsigned int); +int pfctl_load_timeout(struct pfctl *, unsigned int, unsigned int); +int pfctl_load_debug(struct pfctl *, unsigned int); +int pfctl_load_logif(struct pfctl *, char *); +int pfctl_load_hostid(struct pfctl *, u_int32_t); +int pfctl_load_reassembly(struct pfctl *, u_int32_t); +int pfctl_load_syncookies(struct pfctl *, u_int8_t); +int pfctl_get_pool(int, struct pfctl_pool *, u_int32_t, u_int32_t, int, + const char *, int); +void pfctl_print_eth_rule_counters(struct pfctl_eth_rule *, int); +void pfctl_print_rule_counters(struct pfctl_rule *, int); +int pfctl_show_eth_rules(int, char *, int, enum pfctl_show, char *, int, int); +int pfctl_show_rules(int, char *, int, enum pfctl_show, char *, int, int); +int pfctl_show_nat(int, const char *, int, char *, int, int); +int pfctl_show_src_nodes(int, int); +int pfctl_show_states(int, const char *, int); +int pfctl_show_status(int, int); +int pfctl_show_running(int); +int pfctl_show_timeouts(int, int); +int pfctl_show_limits(int, int); +void pfctl_read_limits(struct pfctl_handle *); +void pfctl_restore_limits(void); +void pfctl_debug(int, u_int32_t, int); +int pfctl_test_altqsupport(int, int); +int pfctl_show_anchors(int, int, char *); +int pfctl_show_eth_anchors(int, int, char *); +int pfctl_ruleset_trans(struct pfctl *, char *, struct pfctl_anchor *, bool); +int pfctl_eth_ruleset_trans(struct pfctl *, char *, + struct pfctl_eth_anchor *); +int pfctl_load_eth_ruleset(struct pfctl *, char *, + struct pfctl_eth_ruleset *, int); +int pfctl_load_eth_rule(struct pfctl *, char *, struct pfctl_eth_rule *, + int); +int pfctl_load_ruleset(struct pfctl *, char *, + struct pfctl_ruleset *, int, int); +int pfctl_load_rule(struct pfctl *, char *, struct pfctl_rule *, int); +const char *pfctl_lookup_option(char *, const char * const *); +void pfctl_reset(int, int); +int pfctl_walk_show(int, struct pfioc_ruleset *, void *); +int pfctl_walk_get(int, struct pfioc_ruleset *, void *); +int pfctl_walk_anchors(int, int, const char *, + int(*)(int, struct pfioc_ruleset *, void *), void *); +struct pfr_anchors * + pfctl_get_anchors(int, const char *, int); +int pfctl_recurse(int, int, const char *, + int(*)(int, int, struct pfr_anchoritem *)); +int pfctl_call_clearrules(int, int, struct pfr_anchoritem *); +int pfctl_call_cleartables(int, int, struct pfr_anchoritem *); +int pfctl_call_clearanchors(int, int, struct pfr_anchoritem *); +int pfctl_call_showtables(int, int, struct pfr_anchoritem *); + +static struct pfctl_anchor_global pf_anchors; +struct pfctl_anchor pf_main_anchor; +struct pfctl_eth_anchor pf_eth_main_anchor; +static struct pfr_buffer skip_b; + +static const char *clearopt; +static char *rulesopt; +static const char *showopt; +static const char *debugopt; +static char *anchoropt; +static const char *optiopt = NULL; +static const char *pf_device = PF_DEVICE; +static char *ifaceopt; +static char *tableopt; +static const char *tblcmdopt; +static int src_node_killers; +static char *src_node_kill[2]; +static int state_killers; +static char *state_kill[2]; +int loadopt; +int altqsupport; + +int dev = -1; +struct pfctl_handle *pfh = NULL; +static int first_title = 1; +static int labels = 0; +static int exit_val = 0; + +#define INDENT(d, o) do { \ + if (o) { \ + int i; \ + for (i=0; i < d; i++) \ + printf(" "); \ + } \ + } while (0); \ + + +static const struct { + const char *name; + int index; +} pf_limits[] = { + { "states", PF_LIMIT_STATES }, + { "src-nodes", PF_LIMIT_SRC_NODES }, + { "frags", PF_LIMIT_FRAGS }, + { "table-entries", PF_LIMIT_TABLE_ENTRIES }, + { "anchors", PF_LIMIT_ANCHORS }, + { "eth-anchors", PF_LIMIT_ETH_ANCHORS }, + { NULL, 0 } +}; + +static unsigned int limit_curr[PF_LIMIT_MAX]; + +struct pf_hint { + const char *name; + int timeout; +}; +static const struct pf_hint pf_hint_normal[] = { + { "tcp.first", 2 * 60 }, + { "tcp.opening", 30 }, + { "tcp.established", 24 * 60 * 60 }, + { "tcp.closing", 15 * 60 }, + { "tcp.finwait", 45 }, + { "tcp.closed", 90 }, + { "tcp.tsdiff", 30 }, + { NULL, 0 } +}; +static const struct pf_hint pf_hint_satellite[] = { + { "tcp.first", 3 * 60 }, + { "tcp.opening", 30 + 5 }, + { "tcp.established", 24 * 60 * 60 }, + { "tcp.closing", 15 * 60 + 5 }, + { "tcp.finwait", 45 + 5 }, + { "tcp.closed", 90 + 5 }, + { "tcp.tsdiff", 60 }, + { NULL, 0 } +}; +static const struct pf_hint pf_hint_conservative[] = { + { "tcp.first", 60 * 60 }, + { "tcp.opening", 15 * 60 }, + { "tcp.established", 5 * 24 * 60 * 60 }, + { "tcp.closing", 60 * 60 }, + { "tcp.finwait", 10 * 60 }, + { "tcp.closed", 3 * 60 }, + { "tcp.tsdiff", 60 }, + { NULL, 0 } +}; +static const struct pf_hint pf_hint_aggressive[] = { + { "tcp.first", 30 }, + { "tcp.opening", 5 }, + { "tcp.established", 5 * 60 * 60 }, + { "tcp.closing", 60 }, + { "tcp.finwait", 30 }, + { "tcp.closed", 30 }, + { "tcp.tsdiff", 10 }, + { NULL, 0 } +}; + +static const struct { + const char *name; + const struct pf_hint *hint; +} pf_hints[] = { + { "normal", pf_hint_normal }, + { "satellite", pf_hint_satellite }, + { "high-latency", pf_hint_satellite }, + { "conservative", pf_hint_conservative }, + { "aggressive", pf_hint_aggressive }, + { NULL, NULL } +}; + +static const char * const clearopt_list[] = { + "nat", "queue", "rules", "Sources", + "states", "info", "Tables", "osfp", "all", + "ethernet", "Reset", NULL +}; + +static const char * const showopt_list[] = { + "ether", "nat", "queue", "rules", "Anchors", "Sources", "states", + "info", "Interfaces", "labels", "timeouts", "memory", "Tables", + "osfp", "Running", "all", "creatorids", NULL +}; + +static const char * const tblcmdopt_list[] = { + "kill", "flush", "add", "delete", "load", "replace", "show", + "test", "zero", "expire", "reset", NULL +}; + +static const char * const debugopt_list[] = { + "none", "urgent", "misc", "loud", NULL +}; + +static const char * const optiopt_list[] = { + "none", "basic", "profile", NULL +}; + +void +usage(void) +{ + extern char *__progname; + + fprintf(stderr, +"usage: %s [-AdeghMmNnOPqRSrvz] [-a anchor] [-D macro=value] [-F modifier]\n" + "\t[-f file] [-i interface] [-K host | network]\n" + "\t[-k host | network | gateway | label | id] [-o level] [-p device]\n" + "\t[-s modifier] [-t table -T command [address ...]] [-x level]\n", + __progname); + + exit(1); +} + +void +pfctl_err(int opts, int eval, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + + if ((opts & PF_OPT_IGNFAIL) == 0) + verr(eval, fmt, ap); + else + vwarn(fmt, ap); + + va_end(ap); + + exit_val = eval; +} + +void +pfctl_errx(int opts, int eval, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + + if ((opts & PF_OPT_IGNFAIL) == 0) + verrx(eval, fmt, ap); + else + vwarnx(fmt, ap); + + va_end(ap); + + exit_val = eval; +} + +/* + * Cache protocol number to name translations. + * + * Translation is performed a lot e.g., when dumping states and + * getprotobynumber is incredibly expensive. + * + * Note from the getprotobynumber(3) manpage: + * <quote> + * These functions use a thread-specific data space; if the data is needed + * for future use, it should be copied before any subsequent calls overwrite + * it. Only the Internet protocols are currently understood. + * </quote> + * + * Consequently we only cache the name and strdup it for safety. + * + * At the time of writing this comment the last entry in /etc/protocols is: + * divert 258 DIVERT # Divert pseudo-protocol [non IANA] + */ +const char * +pfctl_proto2name(int proto) +{ + static const char *pfctl_proto_cache[259]; + struct protoent *p; + + if (proto >= nitems(pfctl_proto_cache)) { + p = getprotobynumber(proto); + if (p == NULL) { + return (NULL); + } + return (p->p_name); + } + + if (pfctl_proto_cache[proto] == NULL) { + p = getprotobynumber(proto); + if (p == NULL) { + return (NULL); + } + pfctl_proto_cache[proto] = strdup(p->p_name); + } + + return (pfctl_proto_cache[proto]); +} + +int +pfctl_enable(int dev, int opts) +{ + int ret; + + if ((ret = pfctl_startstop(pfh, 1)) != 0) { + if (ret == EEXIST) + errx(1, "pf already enabled"); + else if (ret == ESRCH) + errx(1, "pfil registeration failed"); + else + errc(1, ret, "DIOCSTART"); + } + if ((opts & PF_OPT_QUIET) == 0) + fprintf(stderr, "pf enabled\n"); + + if (altqsupport && ioctl(dev, DIOCSTARTALTQ)) + if (errno != EEXIST) + err(1, "DIOCSTARTALTQ"); + + return (0); +} + +int +pfctl_disable(int dev, int opts) +{ + int ret; + + if ((ret = pfctl_startstop(pfh, 0)) != 0) { + if (ret == ENOENT) + errx(1, "pf not enabled"); + else + errc(1, ret, "DIOCSTOP"); + } + if ((opts & PF_OPT_QUIET) == 0) + fprintf(stderr, "pf disabled\n"); + + if (altqsupport && ioctl(dev, DIOCSTOPALTQ)) + if (errno != ENOENT) + err(1, "DIOCSTOPALTQ"); + + return (0); +} + +void +pfctl_clear_stats(struct pfctl_handle *h, int opts) +{ + int ret; + if ((ret = pfctl_clear_status(h)) != 0) + pfctl_err(opts, 1, "DIOCCLRSTATUS"); + if ((opts & PF_OPT_QUIET) == 0) + fprintf(stderr, "pf: statistics cleared\n"); +} + +void +pfctl_get_skip_ifaces(void) +{ + bzero(&skip_b, sizeof(skip_b)); + skip_b.pfrb_type = PFRB_IFACES; + for (;;) { + pfr_buf_grow(&skip_b, skip_b.pfrb_size); + skip_b.pfrb_size = skip_b.pfrb_msize; + if (pfi_get_ifaces(NULL, skip_b.pfrb_caddr, &skip_b.pfrb_size)) + err(1, "pfi_get_ifaces"); + if (skip_b.pfrb_size <= skip_b.pfrb_msize) + break; + } +} + +void +pfctl_check_skip_ifaces(char *ifname) +{ + struct pfi_kif *p; + struct node_host *h = NULL, *n = NULL; + + PFRB_FOREACH(p, &skip_b) { + if (!strcmp(ifname, p->pfik_name) && + (p->pfik_flags & PFI_IFLAG_SKIP)) + p->pfik_flags &= ~PFI_IFLAG_SKIP; + if (!strcmp(ifname, p->pfik_name) && p->pfik_group != NULL) { + if ((h = ifa_grouplookup(p->pfik_name, 0)) == NULL) + continue; + + for (n = h; n != NULL; n = n->next) { + if (strncmp(p->pfik_name, ifname, IFNAMSIZ)) + continue; + + p->pfik_flags &= ~PFI_IFLAG_SKIP; + } + } + } +} + +void +pfctl_adjust_skip_ifaces(struct pfctl *pf) +{ + struct pfi_kif *p, *pp; + struct node_host *h = NULL, *n = NULL; + + PFRB_FOREACH(p, &skip_b) { + if (p->pfik_group == NULL || !(p->pfik_flags & PFI_IFLAG_SKIP)) + continue; + + pfctl_set_interface_flags(pf, p->pfik_name, PFI_IFLAG_SKIP, 0); + if ((h = ifa_grouplookup(p->pfik_name, 0)) == NULL) + continue; + + for (n = h; n != NULL; n = n->next) + PFRB_FOREACH(pp, &skip_b) { + if (strncmp(pp->pfik_name, n->ifname, IFNAMSIZ)) + continue; + + if (!(pp->pfik_flags & PFI_IFLAG_SKIP)) + pfctl_set_interface_flags(pf, + pp->pfik_name, PFI_IFLAG_SKIP, 1); + if (pp->pfik_flags & PFI_IFLAG_SKIP) + pp->pfik_flags &= ~PFI_IFLAG_SKIP; + } + } + + PFRB_FOREACH(p, &skip_b) { + if (! (p->pfik_flags & PFI_IFLAG_SKIP)) + continue; + + pfctl_set_interface_flags(pf, p->pfik_name, PFI_IFLAG_SKIP, 0); + } +} + +void +pfctl_clear_interface_flags(int dev, int opts) +{ + struct pfioc_iface pi; + + if ((opts & PF_OPT_NOACTION) == 0) { + bzero(&pi, sizeof(pi)); + pi.pfiio_flags = PFI_IFLAG_SKIP; + + if (ioctl(dev, DIOCCLRIFFLAG, &pi)) + err(1, "DIOCCLRIFFLAG"); + if ((opts & PF_OPT_QUIET) == 0) + fprintf(stderr, "pf: interface flags reset\n"); + } +} + +void +pfctl_flush_eth_rules(int dev, int opts, char *anchorname) +{ + int ret; + + ret = pfctl_clear_eth_rules(dev, anchorname); + if (ret != 0) + err(1, "pfctl_clear_eth_rules"); + + if ((opts & PF_OPT_QUIET) == 0) + fprintf(stderr, "Ethernet rules cleared\n"); +} + +int +pfctl_flush_rules(int dev, int opts, char *anchorname) +{ + int ret; + + ret = pfctl_clear_rules(dev, anchorname); + if (ret != 0) { + pfctl_err(opts, 1, "%s", __func__); + return (1); + } else if ((opts & PF_OPT_QUIET) == 0) + fprintf(stderr, "rules cleared\n"); + + return (0); +} + +void +pfctl_flush_nat(int dev, int opts, char *anchorname) +{ + int ret; + + ret = pfctl_clear_nat(dev, anchorname); + if (ret != 0) + err(1, "pfctl_clear_nat"); + if ((opts & PF_OPT_QUIET) == 0) + fprintf(stderr, "nat cleared\n"); +} + +int +pfctl_clear_altq(int dev, int opts) +{ + struct pfr_buffer t; + + if (!altqsupport) + return (-1); + memset(&t, 0, sizeof(t)); + t.pfrb_type = PFRB_TRANS; + if (pfctl_add_trans(&t, PF_RULESET_ALTQ, "") || + pfctl_trans(dev, &t, DIOCXBEGIN, 0) || + pfctl_trans(dev, &t, DIOCXCOMMIT, 0)) + err(1, "pfctl_clear_altq"); + if ((opts & PF_OPT_QUIET) == 0) + fprintf(stderr, "altq cleared\n"); + return (0); +} + +void +pfctl_clear_src_nodes(int dev, int opts) +{ + if (ioctl(dev, DIOCCLRSRCNODES)) + pfctl_err(opts, 1, "DIOCCLRSRCNODES"); + if ((opts & PF_OPT_QUIET) == 0) + fprintf(stderr, "source tracking entries cleared\n"); +} + +void +pfctl_clear_iface_states(int dev, const char *iface, int opts) +{ + struct pfctl_kill kill; + unsigned int killed; + int ret; + + memset(&kill, 0, sizeof(kill)); + if (iface != NULL && strlcpy(kill.ifname, iface, + sizeof(kill.ifname)) >= sizeof(kill.ifname)) + pfctl_errx(opts, 1, "invalid interface: %s", iface); + + if (opts & PF_OPT_KILLMATCH) + kill.kill_match = true; + + if ((ret = pfctl_clear_states_h(pfh, &kill, &killed)) != 0) + pfctl_err(opts, 1, "DIOCCLRSTATUS"); + if ((opts & PF_OPT_QUIET) == 0) + fprintf(stderr, "%d states cleared\n", killed); +} + +struct addrinfo * +pfctl_addrprefix(char *addr, struct pf_addr *mask, int numeric) +{ + char *p; + const char *errstr; + int prefix, ret_ga, q, r; + struct addrinfo hints, *res; + + bzero(&hints, sizeof(hints)); + hints.ai_socktype = SOCK_DGRAM; /* dummy */ + if (numeric) + hints.ai_flags = AI_NUMERICHOST; + + if ((p = strchr(addr, '/')) != NULL) { + *p++ = '\0'; + /* prefix only with numeric addresses */ + hints.ai_flags |= AI_NUMERICHOST; + } + + if ((ret_ga = getaddrinfo(addr, NULL, &hints, &res))) { + errx(1, "getaddrinfo: %s", gai_strerror(ret_ga)); + /* NOTREACHED */ + } + + if (p == NULL) + return (res); + + prefix = strtonum(p, 0, res->ai_family == AF_INET6 ? 128 : 32, &errstr); + if (errstr) + errx(1, "prefix is %s: %s", errstr, p); + + q = prefix >> 3; + r = prefix & 7; + switch (res->ai_family) { + case AF_INET: + bzero(&mask->v4, sizeof(mask->v4)); + mask->v4.s_addr = htonl((u_int32_t) + (0xffffffffffULL << (32 - prefix))); + break; + case AF_INET6: + bzero(&mask->v6, sizeof(mask->v6)); + if (q > 0) + memset((void *)&mask->v6, 0xff, q); + if (r > 0) + *((u_char *)&mask->v6 + q) = + (0xff00 >> r) & 0xff; + break; + } + + return (res); +} + +void +pfctl_kill_src_nodes(int dev, int opts) +{ + struct pfioc_src_node_kill psnk; + struct addrinfo *res[2], *resp[2]; + struct sockaddr last_src, last_dst; + int killed, sources, dests; + + killed = sources = dests = 0; + + memset(&psnk, 0, sizeof(psnk)); + memset(&psnk.psnk_src.addr.v.a.mask, 0xff, + sizeof(psnk.psnk_src.addr.v.a.mask)); + memset(&last_src, 0xff, sizeof(last_src)); + memset(&last_dst, 0xff, sizeof(last_dst)); + + res[0] = pfctl_addrprefix(src_node_kill[0], + &psnk.psnk_src.addr.v.a.mask, (opts & PF_OPT_NODNS)); + + for (resp[0] = res[0]; resp[0]; resp[0] = resp[0]->ai_next) { + if (resp[0]->ai_addr == NULL) + continue; + /* We get lots of duplicates. Catch the easy ones */ + if (memcmp(&last_src, resp[0]->ai_addr, sizeof(last_src)) == 0) + continue; + last_src = *(struct sockaddr *)resp[0]->ai_addr; + + psnk.psnk_af = resp[0]->ai_family; + sources++; + + copy_satopfaddr(&psnk.psnk_src.addr.v.a.addr, resp[0]->ai_addr); + + if (src_node_killers > 1) { + dests = 0; + memset(&psnk.psnk_dst.addr.v.a.mask, 0xff, + sizeof(psnk.psnk_dst.addr.v.a.mask)); + memset(&last_dst, 0xff, sizeof(last_dst)); + res[1] = pfctl_addrprefix(src_node_kill[1], + &psnk.psnk_dst.addr.v.a.mask, + (opts & PF_OPT_NODNS)); + for (resp[1] = res[1]; resp[1]; + resp[1] = resp[1]->ai_next) { + if (resp[1]->ai_addr == NULL) + continue; + if (psnk.psnk_af != resp[1]->ai_family) + continue; + + if (memcmp(&last_dst, resp[1]->ai_addr, + sizeof(last_dst)) == 0) + continue; + last_dst = *(struct sockaddr *)resp[1]->ai_addr; + + dests++; + + copy_satopfaddr(&psnk.psnk_dst.addr.v.a.addr, + resp[1]->ai_addr); + if (ioctl(dev, DIOCKILLSRCNODES, &psnk)) + err(1, "DIOCKILLSRCNODES"); + killed += psnk.psnk_killed; + } + freeaddrinfo(res[1]); + } else { + if (ioctl(dev, DIOCKILLSRCNODES, &psnk)) + err(1, "DIOCKILLSRCNODES"); + killed += psnk.psnk_killed; + } + } + + freeaddrinfo(res[0]); + + if ((opts & PF_OPT_QUIET) == 0) + fprintf(stderr, "killed %d src nodes from %d sources and %d " + "destinations\n", killed, sources, dests); +} + +void +pfctl_net_kill_states(int dev, const char *iface, int opts) +{ + struct pfctl_kill kill; + struct addrinfo *res[2], *resp[2]; + struct sockaddr last_src, last_dst; + unsigned int newkilled; + int killed, sources, dests; + int ret; + + killed = sources = dests = 0; + + memset(&kill, 0, sizeof(kill)); + memset(&kill.src.addr.v.a.mask, 0xff, + sizeof(kill.src.addr.v.a.mask)); + memset(&last_src, 0xff, sizeof(last_src)); + memset(&last_dst, 0xff, sizeof(last_dst)); + if (iface != NULL && strlcpy(kill.ifname, iface, + sizeof(kill.ifname)) >= sizeof(kill.ifname)) + pfctl_errx(opts, 1, "invalid interface: %s", iface); + + if (state_killers == 2 && (strcmp(state_kill[0], "nat") == 0)) { + kill.nat = true; + state_kill[0] = state_kill[1]; + state_killers = 1; + } + + res[0] = pfctl_addrprefix(state_kill[0], + &kill.src.addr.v.a.mask, (opts & PF_OPT_NODNS)); + + if (opts & PF_OPT_KILLMATCH) + kill.kill_match = true; + + for (resp[0] = res[0]; resp[0]; resp[0] = resp[0]->ai_next) { + if (resp[0]->ai_addr == NULL) + continue; + /* We get lots of duplicates. Catch the easy ones */ + if (memcmp(&last_src, resp[0]->ai_addr, sizeof(last_src)) == 0) + continue; + last_src = *(struct sockaddr *)resp[0]->ai_addr; + + kill.af = resp[0]->ai_family; + sources++; + + copy_satopfaddr(&kill.src.addr.v.a.addr, resp[0]->ai_addr); + + if (state_killers > 1) { + dests = 0; + memset(&kill.dst.addr.v.a.mask, 0xff, + sizeof(kill.dst.addr.v.a.mask)); + memset(&last_dst, 0xff, sizeof(last_dst)); + res[1] = pfctl_addrprefix(state_kill[1], + &kill.dst.addr.v.a.mask, + (opts & PF_OPT_NODNS)); + for (resp[1] = res[1]; resp[1]; + resp[1] = resp[1]->ai_next) { + if (resp[1]->ai_addr == NULL) + continue; + if (kill.af != resp[1]->ai_family) + continue; + + if (memcmp(&last_dst, resp[1]->ai_addr, + sizeof(last_dst)) == 0) + continue; + last_dst = *(struct sockaddr *)resp[1]->ai_addr; + + dests++; + + copy_satopfaddr(&kill.dst.addr.v.a.addr, + resp[1]->ai_addr); + + if ((ret = pfctl_kill_states_h(pfh, &kill, &newkilled)) != 0) + pfctl_errx(opts, 1, "DIOCKILLSTATES"); + killed += newkilled; + } + freeaddrinfo(res[1]); + } else { + if ((ret = pfctl_kill_states_h(pfh, &kill, &newkilled)) != 0) + pfctl_errx(opts, 1, "DIOCKILLSTATES"); + killed += newkilled; + } + } + + freeaddrinfo(res[0]); + + if ((opts & PF_OPT_QUIET) == 0) + fprintf(stderr, "killed %d states from %d sources and %d " + "destinations\n", killed, sources, dests); +} + +void +pfctl_gateway_kill_states(int dev, const char *iface, int opts) +{ + struct pfctl_kill kill; + struct addrinfo *res, *resp; + struct sockaddr last_src; + unsigned int newkilled; + int killed = 0; + + if (state_killers != 2 || (strlen(state_kill[1]) == 0)) { + warnx("no gateway specified"); + usage(); + } + + memset(&kill, 0, sizeof(kill)); + memset(&kill.rt_addr.addr.v.a.mask, 0xff, + sizeof(kill.rt_addr.addr.v.a.mask)); + memset(&last_src, 0xff, sizeof(last_src)); + if (iface != NULL && strlcpy(kill.ifname, iface, + sizeof(kill.ifname)) >= sizeof(kill.ifname)) + pfctl_errx(opts, 1, "invalid interface: %s", iface); + + if (opts & PF_OPT_KILLMATCH) + kill.kill_match = true; + + res = pfctl_addrprefix(state_kill[1], &kill.rt_addr.addr.v.a.mask, + (opts & PF_OPT_NODNS)); + + for (resp = res; resp; resp = resp->ai_next) { + if (resp->ai_addr == NULL) + continue; + /* We get lots of duplicates. Catch the easy ones */ + if (memcmp(&last_src, resp->ai_addr, sizeof(last_src)) == 0) + continue; + last_src = *(struct sockaddr *)resp->ai_addr; + + kill.af = resp->ai_family; + + copy_satopfaddr(&kill.rt_addr.addr.v.a.addr, + resp->ai_addr); + if (pfctl_kill_states_h(pfh, &kill, &newkilled)) + pfctl_errx(opts, 1, "DIOCKILLSTATES"); + killed += newkilled; + } + + freeaddrinfo(res); + + if ((opts & PF_OPT_QUIET) == 0) + fprintf(stderr, "killed %d states\n", killed); +} + +void +pfctl_label_kill_states(int dev, const char *iface, int opts) +{ + struct pfctl_kill kill; + unsigned int killed; + int ret; + + if (state_killers != 2 || (strlen(state_kill[1]) == 0)) { + warnx("no label specified"); + usage(); + } + memset(&kill, 0, sizeof(kill)); + if (iface != NULL && strlcpy(kill.ifname, iface, + sizeof(kill.ifname)) >= sizeof(kill.ifname)) + pfctl_errx(opts, 1, "invalid interface: %s", iface); + + if (opts & PF_OPT_KILLMATCH) + kill.kill_match = true; + + if (strlcpy(kill.label, state_kill[1], sizeof(kill.label)) >= + sizeof(kill.label)) + errx(1, "label too long: %s", state_kill[1]); + + if ((ret = pfctl_kill_states_h(pfh, &kill, &killed)) != 0) + pfctl_errx(opts, 1, "DIOCKILLSTATES"); + + if ((opts & PF_OPT_QUIET) == 0) + fprintf(stderr, "killed %d states\n", killed); +} + +void +pfctl_id_kill_states(int dev, const char *iface, int opts) +{ + struct pfctl_kill kill; + unsigned int killed; + int ret; + + if (state_killers != 2 || (strlen(state_kill[1]) == 0)) { + warnx("no id specified"); + usage(); + } + + memset(&kill, 0, sizeof(kill)); + + if (opts & PF_OPT_KILLMATCH) + kill.kill_match = true; + + if ((sscanf(state_kill[1], "%jx/%x", + &kill.cmp.id, &kill.cmp.creatorid)) == 2) { + } + else if ((sscanf(state_kill[1], "%jx", &kill.cmp.id)) == 1) { + kill.cmp.creatorid = 0; + } else { + warnx("wrong id format specified"); + usage(); + } + if (kill.cmp.id == 0) { + warnx("cannot kill id 0"); + usage(); + } + + if ((ret = pfctl_kill_states_h(pfh, &kill, &killed)) != 0) + pfctl_errx(opts, 1, "DIOCKILLSTATES"); + + if ((opts & PF_OPT_QUIET) == 0) + fprintf(stderr, "killed %d states\n", killed); +} + +void +pfctl_key_kill_states(int dev, const char *iface, int opts) +{ + struct pfctl_kill kill; + char *s, *token, *tokens[4]; + struct protoent *p; + u_int i, sidx, didx; + int ret, killed; + + if (state_killers != 2 || (strlen(state_kill[1]) == 0)) { + warnx("no key specified"); + usage(); + } + memset(&kill, 0, sizeof(kill)); + + if (iface != NULL && + strlcpy(kill.ifname, iface, sizeof(kill.ifname)) >= + sizeof(kill.ifname)) + pfctl_errx(opts, 1, "invalid interface: %s", iface); + + s = strdup(state_kill[1]); + if (!s) + errx(1, "%s: strdup", __func__); + i = 0; + while ((token = strsep(&s, " \t")) != NULL) + if (*token != '\0') { + if (i < 4) + tokens[i] = token; + i++; + } + if (i != 4) + errx(1, "%s: key must be " + "\"protocol host1:port1 direction host2:port2\" format", + __func__); + + if ((p = getprotobyname(tokens[0])) == NULL) + errx(1, "invalid protocol: %s", tokens[0]); + kill.proto = p->p_proto; + + if (strcmp(tokens[2], "->") == 0) { + sidx = 1; + didx = 3; + } else if (strcmp(tokens[2], "<-") == 0) { + sidx = 3; + didx = 1; + } else + errx(1, "invalid direction: %s", tokens[2]); + + if (pfctl_parse_host(tokens[sidx], &kill.src) == -1) + errx(1, "invalid host: %s", tokens[sidx]); + if (pfctl_parse_host(tokens[didx], &kill.dst) == -1) + errx(1, "invalid host: %s", tokens[didx]); + + if ((ret = pfctl_kill_states_h(pfh, &kill, &killed)) != 0) + pfctl_errx(opts, 1, "DIOCKILLSTATES"); + + if ((opts & PF_OPT_QUIET) == 0) + fprintf(stderr, "killed %d states\n", killed); +} + +int +pfctl_parse_host(char *str, struct pf_rule_addr *addr) +{ + char *s = NULL, *sbs, *sbe; + struct addrinfo hints, *ai; + + s = strdup(str); + if (!s) + errx(1, "pfctl_parse_host: strdup"); + + memset(&hints, 0, sizeof(hints)); + hints.ai_socktype = SOCK_DGRAM; /* dummy */ + hints.ai_flags = AI_NUMERICHOST; + + if ((sbs = strchr(s, '[')) != NULL && (sbe = strrchr(s, ']')) != NULL) { + hints.ai_family = AF_INET6; + *(sbs++) = *sbe = '\0'; + } else if ((sbs = strchr(s, ':')) != NULL) { + hints.ai_family = AF_INET; + *(sbs++) = '\0'; + } else { + /* Assume that no ':<number>' means port 0 */ + } + + if (getaddrinfo(s, sbs, &hints, &ai) != 0) + goto error; + + copy_satopfaddr(&addr->addr.v.a.addr, ai->ai_addr); + addr->port[0] = ai->ai_family == AF_INET6 ? + ((struct sockaddr_in6 *)ai->ai_addr)->sin6_port : + ((struct sockaddr_in *)ai->ai_addr)->sin_port; + freeaddrinfo(ai); + free(s); + + memset(&addr->addr.v.a.mask, 0xff, sizeof(struct pf_addr)); + addr->port_op = PF_OP_EQ; + addr->addr.type = PF_ADDR_ADDRMASK; + + return (0); + +error: + free(s); + return (-1); +} + +int +pfctl_get_pool(int dev, struct pfctl_pool *pool, u_int32_t nr, + u_int32_t ticket, int r_action, const char *anchorname, int which) +{ + struct pfioc_pooladdr pp; + struct pfctl_pooladdr *pa; + u_int32_t pnr, mpnr; + int ret; + + memset(&pp, 0, sizeof(pp)); + if ((ret = pfctl_get_addrs(pfh, ticket, nr, r_action, anchorname, &mpnr, which)) != 0) { + warnc(ret, "DIOCGETADDRS"); + return (-1); + } + + TAILQ_INIT(&pool->list); + for (pnr = 0; pnr < mpnr; ++pnr) { + if ((ret = pfctl_get_addr(pfh, ticket, nr, r_action, anchorname, pnr, &pp, which)) != 0) { + warnc(ret, "DIOCGETADDR"); + return (-1); + } + pa = calloc(1, sizeof(struct pfctl_pooladdr)); + if (pa == NULL) + err(1, "calloc"); + bcopy(&pp.addr, pa, sizeof(struct pfctl_pooladdr)); + pa->af = pp.af; + TAILQ_INSERT_TAIL(&pool->list, pa, entries); + } + + return (0); +} + +void +pfctl_move_pool(struct pfctl_pool *src, struct pfctl_pool *dst) +{ + struct pfctl_pooladdr *pa; + + while ((pa = TAILQ_FIRST(&src->list)) != NULL) { + TAILQ_REMOVE(&src->list, pa, entries); + TAILQ_INSERT_TAIL(&dst->list, pa, entries); + } +} + +void +pfctl_clear_pool(struct pfctl_pool *pool) +{ + struct pfctl_pooladdr *pa; + + while ((pa = TAILQ_FIRST(&pool->list)) != NULL) { + TAILQ_REMOVE(&pool->list, pa, entries); + free(pa); + } +} + +void +pfctl_print_eth_rule_counters(struct pfctl_eth_rule *rule, int opts) +{ + if (opts & PF_OPT_VERBOSE) { + printf(" [ Evaluations: %-8llu Packets: %-8llu " + "Bytes: %-10llu]\n", + (unsigned long long)rule->evaluations, + (unsigned long long)(rule->packets[0] + + rule->packets[1]), + (unsigned long long)(rule->bytes[0] + + rule->bytes[1])); + } + if (opts & PF_OPT_VERBOSE2) { + char timestr[30]; + + if (rule->last_active_timestamp != 0) { + bcopy(ctime(&rule->last_active_timestamp), timestr, + sizeof(timestr)); + *strchr(timestr, '\n') = '\0'; + } else { + snprintf(timestr, sizeof(timestr), "N/A"); + } + printf(" [ Last Active Time: %s ]\n", timestr); + } +} + +void +pfctl_print_rule_counters(struct pfctl_rule *rule, int opts) +{ + if (opts & PF_OPT_DEBUG) { + const char *t[PF_SKIP_COUNT] = { "i", "d", "f", + "p", "sa", "da", "sp", "dp" }; + int i; + + printf(" [ Skip steps: "); + for (i = 0; i < PF_SKIP_COUNT; ++i) { + if (rule->skip[i].nr == rule->nr + 1) + continue; + printf("%s=", t[i]); + if (rule->skip[i].nr == -1) + printf("end "); + else + printf("%u ", rule->skip[i].nr); + } + printf("]\n"); + + printf(" [ queue: qname=%s qid=%u pqname=%s pqid=%u ]\n", + rule->qname, rule->qid, rule->pqname, rule->pqid); + if (rule->rule_flag & PFRULE_EXPIRED) + printf(" [ Expired: %lld secs ago ]\n", + (long long)(time(NULL) - rule->exptime)); + } + if (opts & PF_OPT_VERBOSE) { + printf(" [ Evaluations: %-8llu Packets: %-8llu " + "Bytes: %-10llu States: %-6ju]\n", + (unsigned long long)rule->evaluations, + (unsigned long long)(rule->packets[0] + + rule->packets[1]), + (unsigned long long)(rule->bytes[0] + + rule->bytes[1]), (uintmax_t)rule->states_cur); + printf(" [ Source Nodes: %-6ju " + "Limit: %-6ju " + "NAT/RDR: %-6ju " + "Route: %-6ju " + "]\n", + (uintmax_t)rule->src_nodes, + (uintmax_t)rule->src_nodes_type[PF_SN_LIMIT], + (uintmax_t)rule->src_nodes_type[PF_SN_NAT], + (uintmax_t)rule->src_nodes_type[PF_SN_ROUTE]); + if (!(opts & PF_OPT_DEBUG)) + printf(" [ Inserted: uid %u pid %u " + "State Creations: %-6ju]\n", + (unsigned)rule->cuid, (unsigned)rule->cpid, + (uintmax_t)rule->states_tot); + } + if (opts & PF_OPT_VERBOSE2) { + char timestr[30]; + if (rule->last_active_timestamp != 0) { + bcopy(ctime(&rule->last_active_timestamp), timestr, + sizeof(timestr)); + *strchr(timestr, '\n') = '\0'; + } else { + snprintf(timestr, sizeof(timestr), "N/A"); + } + printf(" [ Last Active Time: %s ]\n", timestr); + } +} + +void +pfctl_print_title(char *title) +{ + if (!first_title) + printf("\n"); + first_title = 0; + printf("%s\n", title); +} + +int +pfctl_show_eth_rules(int dev, char *path, int opts, enum pfctl_show format, + char *anchorname, int depth, int wildcard) +{ + char anchor_call[MAXPATHLEN]; + struct pfctl_eth_rules_info info; + struct pfctl_eth_rule rule; + int brace; + int dotitle = opts & PF_OPT_SHOWALL; + int len = strlen(path); + int ret; + char *npath, *p; + + /* + * Truncate a trailing / and * on an anchorname before searching for + * the ruleset, this is syntactic sugar that doesn't actually make it + * to the kernel. + */ + if ((p = strrchr(anchorname, '/')) != NULL && + p[1] == '*' && p[2] == '\0') { + p[0] = '\0'; + } + + if (anchorname[0] == '/') { + if ((npath = calloc(1, MAXPATHLEN)) == NULL) + errx(1, "calloc"); + snprintf(npath, MAXPATHLEN, "%s", anchorname); + } else { + if (path[0]) + snprintf(&path[len], MAXPATHLEN - len, "/%s", anchorname); + else + snprintf(&path[len], MAXPATHLEN - len, "%s", anchorname); + npath = path; + } + + /* + * If this anchor was called with a wildcard path, go through + * the rulesets in the anchor rather than the rules. + */ + if (wildcard && (opts & PF_OPT_RECURSE)) { + struct pfctl_eth_rulesets_info ri; + u_int32_t mnr, nr; + + if ((ret = pfctl_get_eth_rulesets_info(dev, &ri, npath)) != 0) { + if (ret == EINVAL) { + fprintf(stderr, "Anchor '%s' " + "not found.\n", anchorname); + } else { + warnc(ret, "DIOCGETETHRULESETS"); + return (-1); + } + } + mnr = ri.nr; + + pfctl_print_eth_rule_counters(&rule, opts); + for (nr = 0; nr < mnr; ++nr) { + struct pfctl_eth_ruleset_info rs; + + if ((ret = pfctl_get_eth_ruleset(dev, npath, nr, &rs)) != 0) + errc(1, ret, "DIOCGETETHRULESET"); + INDENT(depth, !(opts & PF_OPT_VERBOSE)); + printf("anchor \"%s\" all {\n", rs.name); + pfctl_show_eth_rules(dev, npath, opts, + format, rs.name, depth + 1, 0); + INDENT(depth, !(opts & PF_OPT_VERBOSE)); + printf("}\n"); + } + path[len] = '\0'; + return (0); + } + + if ((ret = pfctl_get_eth_rules_info(dev, &info, path)) != 0) { + warnc(ret, "DIOCGETETHRULES"); + return (-1); + } + for (int nr = 0; nr < info.nr; nr++) { + brace = 0; + INDENT(depth, !(opts & PF_OPT_VERBOSE)); + if ((ret = pfctl_get_eth_rule(dev, nr, info.ticket, path, &rule, + opts & PF_OPT_CLRRULECTRS, anchor_call)) != 0) { + warnc(ret, "DIOCGETETHRULE"); + return (-1); + } + if (anchor_call[0] && + ((((p = strrchr(anchor_call, '_')) != NULL) && + (p == anchor_call || + *(--p) == '/')) || (opts & PF_OPT_RECURSE))) { + brace++; + int aclen = strlen(anchor_call); + if (anchor_call[aclen - 1] == '*') + anchor_call[aclen - 2] = '\0'; + } + p = &anchor_call[0]; + if (dotitle) { + pfctl_print_title("ETH RULES:"); + dotitle = 0; + } + print_eth_rule(&rule, anchor_call, + opts & (PF_OPT_VERBOSE2 | PF_OPT_DEBUG)); + if (brace) + printf(" {\n"); + else + printf("\n"); + pfctl_print_eth_rule_counters(&rule, opts); + if (brace) { + pfctl_show_eth_rules(dev, path, opts, format, + p, depth + 1, rule.anchor_wildcard); + INDENT(depth, !(opts & PF_OPT_VERBOSE)); + printf("}\n"); + } + } + + path[len] = '\0'; + return (0); +} + +int +pfctl_show_rules(int dev, char *path, int opts, enum pfctl_show format, + char *anchorname, int depth, int wildcard) +{ + struct pfctl_rules_info ri; + struct pfctl_rule rule; + char anchor_call[MAXPATHLEN]; + u_int32_t nr, header = 0; + int numeric = opts & PF_OPT_NUMERIC; + int len = strlen(path), ret = 0; + char *npath, *p; + + /* + * Truncate a trailing / and * on an anchorname before searching for + * the ruleset, this is syntactic sugar that doesn't actually make it + * to the kernel. + */ + if ((p = strrchr(anchorname, '/')) != NULL && + p[1] == '*' && p[2] == '\0') { + p[0] = '\0'; + } + + if (anchorname[0] == '/') { + if ((npath = calloc(1, MAXPATHLEN)) == NULL) + errx(1, "calloc"); + strlcpy(npath, anchorname, MAXPATHLEN); + } else { + if (path[0]) + snprintf(&path[len], MAXPATHLEN - len, "/%s", anchorname); + else + snprintf(&path[len], MAXPATHLEN - len, "%s", anchorname); + npath = path; + } + + /* + * If this anchor was called with a wildcard path, go through + * the rulesets in the anchor rather than the rules. + */ + if (wildcard && (opts & PF_OPT_RECURSE)) { + struct pfioc_ruleset prs; + u_int32_t mnr, nr; + + memset(&prs, 0, sizeof(prs)); + if ((ret = pfctl_get_rulesets(pfh, npath, &mnr)) != 0) + errx(1, "%s", pf_strerror(ret)); + + for (nr = 0; nr < mnr; ++nr) { + if ((ret = pfctl_get_ruleset(pfh, npath, nr, &prs)) != 0) + errx(1, "%s", pf_strerror(ret)); + INDENT(depth, !(opts & PF_OPT_VERBOSE)); + printf("anchor \"%s\" all {\n", prs.name); + pfctl_show_rules(dev, npath, opts, + format, prs.name, depth + 1, 0); + INDENT(depth, !(opts & PF_OPT_VERBOSE)); + printf("}\n"); + } + path[len] = '\0'; + return (0); + } + + if (opts & PF_OPT_SHOWALL) { + ret = pfctl_get_rules_info_h(pfh, &ri, PF_PASS, path); + if (ret != 0) { + warnx("%s", pf_strerror(ret)); + goto error; + } + header++; + } + ret = pfctl_get_rules_info_h(pfh, &ri, PF_SCRUB, path); + if (ret != 0) { + warnx("%s", pf_strerror(ret)); + goto error; + } + if (opts & PF_OPT_SHOWALL) { + if (format == PFCTL_SHOW_RULES && (ri.nr > 0 || header)) + pfctl_print_title("FILTER RULES:"); + else if (format == PFCTL_SHOW_LABELS && labels) + pfctl_print_title("LABEL COUNTERS:"); + } + + for (nr = 0; nr < ri.nr; ++nr) { + if ((ret = pfctl_get_clear_rule_h(pfh, nr, ri.ticket, path, PF_SCRUB, + &rule, anchor_call, opts & PF_OPT_CLRRULECTRS)) != 0) { + warnc(ret, "DIOCGETRULENV"); + goto error; + } + + if (pfctl_get_pool(dev, &rule.rdr, + nr, ri.ticket, PF_SCRUB, path, PF_RDR) != 0) + goto error; + + if (pfctl_get_pool(dev, &rule.nat, + nr, ri.ticket, PF_SCRUB, path, PF_NAT) != 0) + goto error; + + if (pfctl_get_pool(dev, &rule.route, + nr, ri.ticket, PF_SCRUB, path, PF_RT) != 0) + goto error; + + switch (format) { + case PFCTL_SHOW_LABELS: + break; + case PFCTL_SHOW_RULES: + if (rule.label[0][0] && (opts & PF_OPT_SHOWALL)) + labels = 1; + print_rule(&rule, anchor_call, opts, numeric); + /* + * Do not print newline, when we have not + * printed expired rule. + */ + if (!(rule.rule_flag & PFRULE_EXPIRED) || + (opts & (PF_OPT_VERBOSE2|PF_OPT_DEBUG))) + printf("\n"); + pfctl_print_rule_counters(&rule, opts); + break; + case PFCTL_SHOW_NOTHING: + break; + } + pfctl_clear_pool(&rule.rdr); + pfctl_clear_pool(&rule.nat); + pfctl_clear_pool(&rule.route); + } + ret = pfctl_get_rules_info_h(pfh, &ri, PF_PASS, path); + if (ret != 0) { + warnc(ret, "DIOCGETRULES"); + goto error; + } + for (nr = 0; nr < ri.nr; ++nr) { + if ((ret = pfctl_get_clear_rule_h(pfh, nr, ri.ticket, path, PF_PASS, + &rule, anchor_call, opts & PF_OPT_CLRRULECTRS)) != 0) { + warnc(ret, "DIOCGETRULE"); + goto error; + } + + if (pfctl_get_pool(dev, &rule.rdr, + nr, ri.ticket, PF_PASS, path, PF_RDR) != 0) + goto error; + + if (pfctl_get_pool(dev, &rule.nat, + nr, ri.ticket, PF_PASS, path, PF_NAT) != 0) + goto error; + + if (pfctl_get_pool(dev, &rule.route, + nr, ri.ticket, PF_PASS, path, PF_RT) != 0) + goto error; + + switch (format) { + case PFCTL_SHOW_LABELS: { + bool show = false; + int i = 0; + + while (rule.label[i][0]) { + printf("%s ", rule.label[i++]); + show = true; + } + + if (show) { + printf("%llu %llu %llu %llu" + " %llu %llu %llu %ju\n", + (unsigned long long)rule.evaluations, + (unsigned long long)(rule.packets[0] + + rule.packets[1]), + (unsigned long long)(rule.bytes[0] + + rule.bytes[1]), + (unsigned long long)rule.packets[0], + (unsigned long long)rule.bytes[0], + (unsigned long long)rule.packets[1], + (unsigned long long)rule.bytes[1], + (uintmax_t)rule.states_tot); + } + + if (anchor_call[0] && + (((p = strrchr(anchor_call, '/')) ? + p[1] == '_' : anchor_call[0] == '_') || + opts & PF_OPT_RECURSE)) { + pfctl_show_rules(dev, npath, opts, format, + anchor_call, depth, rule.anchor_wildcard); + } + break; + } + case PFCTL_SHOW_RULES: + if (rule.label[0][0] && (opts & PF_OPT_SHOWALL)) + labels = 1; + INDENT(depth, !(opts & PF_OPT_VERBOSE)); + print_rule(&rule, anchor_call, opts, numeric); + + /* + * If this is a 'unnamed' brace notation + * anchor, OR the user has explicitly requested + * recursion, print it recursively. + */ + if (anchor_call[0] && + (((p = strrchr(anchor_call, '/')) ? + p[1] == '_' : anchor_call[0] == '_') || + opts & PF_OPT_RECURSE)) { + printf(" {\n"); + pfctl_print_rule_counters(&rule, opts); + pfctl_show_rules(dev, npath, opts, format, + anchor_call, depth + 1, + rule.anchor_wildcard); + INDENT(depth, !(opts & PF_OPT_VERBOSE)); + printf("}\n"); + } else { + printf("\n"); + pfctl_print_rule_counters(&rule, opts); + } + break; + case PFCTL_SHOW_NOTHING: + break; + } + pfctl_clear_pool(&rule.rdr); + pfctl_clear_pool(&rule.nat); + } + + error: + path[len] = '\0'; + return (ret); +} + +int +pfctl_show_nat(int dev, const char *path, int opts, char *anchorname, int depth, + int wildcard) +{ + struct pfctl_rules_info ri; + struct pfctl_rule rule; + char anchor_call[MAXPATHLEN]; + u_int32_t nr; + static int nattype[3] = { PF_NAT, PF_RDR, PF_BINAT }; + int i, dotitle = opts & PF_OPT_SHOWALL; + int ret; + int len = strlen(path); + char *npath, *p; + + /* + * Truncate a trailing / and * on an anchorname before searching for + * the ruleset, this is syntactic sugar that doesn't actually make it + * to the kernel. + */ + if ((p = strrchr(anchorname, '/')) != NULL && + p[1] == '*' && p[2] == '\0') { + p[0] = '\0'; + } + + if ((npath = calloc(1, MAXPATHLEN)) == NULL) + errx(1, "calloc"); + + if (anchorname[0] == '/') { + snprintf(npath, MAXPATHLEN, "%s", anchorname); + } else { + snprintf(npath, MAXPATHLEN, "%s", path); + if (npath[0]) + snprintf(&npath[len], MAXPATHLEN - len, "/%s", anchorname); + else + snprintf(&npath[len], MAXPATHLEN - len, "%s", anchorname); + } + + /* + * If this anchor was called with a wildcard path, go through + * the rulesets in the anchor rather than the rules. + */ + if (wildcard && (opts & PF_OPT_RECURSE)) { + struct pfioc_ruleset prs; + u_int32_t mnr, nr; + memset(&prs, 0, sizeof(prs)); + if ((ret = pfctl_get_rulesets(pfh, npath, &mnr)) != 0) { + if (ret == EINVAL) + fprintf(stderr, "NAT anchor '%s' " + "not found.\n", anchorname); + else + errx(1, "%s", pf_strerror(ret)); + } + + for (nr = 0; nr < mnr; ++nr) { + if ((ret = pfctl_get_ruleset(pfh, npath, nr, &prs)) != 0) + errx(1, "%s", pf_strerror(ret)); + INDENT(depth, !(opts & PF_OPT_VERBOSE)); + printf("nat-anchor \"%s\" all {\n", prs.name); + pfctl_show_nat(dev, npath, opts, + prs.name, depth + 1, 0); + INDENT(depth, !(opts & PF_OPT_VERBOSE)); + printf("}\n"); + } + npath[len] = '\0'; + return (0); + } + + for (i = 0; i < 3; i++) { + ret = pfctl_get_rules_info_h(pfh, &ri, nattype[i], npath); + if (ret != 0) { + warnc(ret, "DIOCGETRULES"); + return (-1); + } + for (nr = 0; nr < ri.nr; ++nr) { + INDENT(depth, !(opts & PF_OPT_VERBOSE)); + + if ((ret = pfctl_get_rule_h(pfh, nr, ri.ticket, npath, + nattype[i], &rule, anchor_call)) != 0) { + warnc(ret, "DIOCGETRULE"); + return (-1); + } + if (pfctl_get_pool(dev, &rule.rdr, nr, + ri.ticket, nattype[i], npath, PF_RDR) != 0) + return (-1); + if (pfctl_get_pool(dev, &rule.nat, nr, + ri.ticket, nattype[i], npath, PF_NAT) != 0) + return (-1); + if (pfctl_get_pool(dev, &rule.route, nr, + ri.ticket, nattype[i], npath, PF_RT) != 0) + return (-1); + + if (dotitle) { + pfctl_print_title("TRANSLATION RULES:"); + dotitle = 0; + } + print_rule(&rule, anchor_call, + opts & PF_OPT_VERBOSE2, opts & PF_OPT_NUMERIC); + if (anchor_call[0] && + (((p = strrchr(anchor_call, '/')) ? + p[1] == '_' : anchor_call[0] == '_') || + opts & PF_OPT_RECURSE)) { + printf(" {\n"); + pfctl_print_rule_counters(&rule, opts); + pfctl_show_nat(dev, npath, opts, anchor_call, + depth + 1, rule.anchor_wildcard); + INDENT(depth, !(opts & PF_OPT_VERBOSE)); + printf("}\n"); + } else { + printf("\n"); + pfctl_print_rule_counters(&rule, opts); + } + } + } + return (0); +} + +static int +pfctl_print_src_node(struct pfctl_src_node *sn, void *arg) +{ + int *opts = (int *)arg; + + if (*opts & PF_OPT_SHOWALL) { + pfctl_print_title("SOURCE TRACKING NODES:"); + *opts &= ~PF_OPT_SHOWALL; + } + + print_src_node(sn, *opts); + + return (0); +} + +int +pfctl_show_src_nodes(int dev, int opts) +{ + int error; + + error = pfctl_get_srcnodes(pfh, pfctl_print_src_node, &opts); + + return (error); +} + +struct pfctl_show_state_arg { + int opts; + int dotitle; + const char *iface; +}; + +static int +pfctl_show_state(struct pfctl_state *s, void *arg) +{ + struct pfctl_show_state_arg *a = (struct pfctl_show_state_arg *)arg; + + if (a->dotitle) { + pfctl_print_title("STATES:"); + a->dotitle = 0; + } + print_state(s, a->opts); + + return (0); +} + +int +pfctl_show_states(int dev, const char *iface, int opts) +{ + struct pfctl_show_state_arg arg; + struct pfctl_state_filter filter = {}; + + if (iface != NULL) + strlcpy(filter.ifname, iface, IFNAMSIZ); + + arg.opts = opts; + arg.dotitle = opts & PF_OPT_SHOWALL; + arg.iface = iface; + + if (pfctl_get_filtered_states_iter(&filter, pfctl_show_state, &arg)) + return (-1); + + return (0); +} + +int +pfctl_show_status(int dev, int opts) +{ + struct pfctl_status *status; + struct pfctl_syncookies cookies; + int ret; + + if ((status = pfctl_get_status_h(pfh)) == NULL) { + warn("DIOCGETSTATUS"); + return (-1); + } + if ((ret = pfctl_get_syncookies(dev, &cookies)) != 0) { + pfctl_free_status(status); + warnc(ret, "DIOCGETSYNCOOKIES"); + return (-1); + } + if (opts & PF_OPT_SHOWALL) + pfctl_print_title("INFO:"); + print_status(status, &cookies, opts); + pfctl_free_status(status); + return (0); +} + +int +pfctl_show_running(int dev) +{ + struct pfctl_status *status; + int running; + + if ((status = pfctl_get_status_h(pfh)) == NULL) { + warn("DIOCGETSTATUS"); + return (-1); + } + + running = status->running; + + print_running(status); + pfctl_free_status(status); + return (!running); +} + +int +pfctl_show_timeouts(int dev, int opts) +{ + uint32_t seconds; + int i; + int ret; + + if (opts & PF_OPT_SHOWALL) + pfctl_print_title("TIMEOUTS:"); + for (i = 0; pf_timeouts[i].name; i++) { + if ((ret = pfctl_get_timeout(pfh, pf_timeouts[i].timeout, &seconds)) != 0) + errc(1, ret, "DIOCGETTIMEOUT"); + printf("%-20s %10d", pf_timeouts[i].name, seconds); + if (pf_timeouts[i].timeout >= PFTM_ADAPTIVE_START && + pf_timeouts[i].timeout <= PFTM_ADAPTIVE_END) + printf(" states"); + else + printf("s"); + printf("\n"); + } + return (0); + +} + +int +pfctl_show_limits(int dev, int opts) +{ + unsigned int limit; + int i; + int ret; + + if (opts & PF_OPT_SHOWALL) + pfctl_print_title("LIMITS:"); + for (i = 0; pf_limits[i].name; i++) { + if ((ret = pfctl_get_limit(pfh, pf_limits[i].index, &limit)) != 0) + errc(1, ret, "DIOCGETLIMIT"); + printf("%-13s ", pf_limits[i].name); + if (limit == UINT_MAX) + printf("unlimited\n"); + else + printf("hard limit %8u\n", limit); + } + return (0); +} + +void +pfctl_read_limits(struct pfctl_handle *h) +{ + int i; + + for (i = 0; pf_limits[i].name; i++) { + if (pfctl_get_limit(h, i, &limit_curr[i])) + err(1, "DIOCGETLIMIT"); + } +} + +void +pfctl_restore_limits(void) +{ + int i; + + if (pfh == NULL) + return; + + for (i = 0; pf_limits[i].name; i++) { + if (pfctl_set_limit(pfh, i, limit_curr[i])) + warn("DIOCSETLIMIT (%s)", pf_limits[i].name); + } +} + +void +pfctl_show_creators(int opts) +{ + int ret; + uint32_t creators[16]; + size_t count = nitems(creators); + + ret = pfctl_get_creatorids(pfh, creators, &count); + if (ret != 0) + errx(ret, "Failed to retrieve creators"); + + printf("Creator IDs:\n"); + for (size_t i = 0; i < count; i++) + printf("%08x\n", creators[i]); +} + +/* callbacks for rule/nat/rdr/addr */ +int +pfctl_add_pool(struct pfctl *pf, struct pfctl_pool *p, int which) +{ + struct pfctl_pooladdr *pa; + int ret; + + TAILQ_FOREACH(pa, &p->list, entries) { + memcpy(&pf->paddr.addr, pa, sizeof(struct pfctl_pooladdr)); + pf->paddr.af = pa->af; + if ((pf->opts & PF_OPT_NOACTION) == 0) { + if ((ret = pfctl_add_addr(pf->h, &pf->paddr, which)) != 0) + errc(1, ret, "DIOCADDADDR"); + } + } + return (0); +} + +void +pfctl_init_rule(struct pfctl_rule *r) +{ + memset(r, 0, sizeof(struct pfctl_rule)); + TAILQ_INIT(&(r->rdr.list)); + TAILQ_INIT(&(r->nat.list)); + TAILQ_INIT(&(r->route.list)); +} + +void +pfctl_append_rule(struct pfctl *pf, struct pfctl_rule *r) +{ + u_int8_t rs_num; + struct pfctl_rule *rule; + struct pfctl_ruleset *rs; + + rs_num = pf_get_ruleset_number(r->action); + if (rs_num == PF_RULESET_MAX) + errx(1, "Invalid rule type %d", r->action); + + rs = &pf->anchor->ruleset; + + if ((rule = calloc(1, sizeof(*rule))) == NULL) + err(1, "calloc"); + bcopy(r, rule, sizeof(*rule)); + TAILQ_INIT(&rule->rdr.list); + pfctl_move_pool(&r->rdr, &rule->rdr); + TAILQ_INIT(&rule->nat.list); + pfctl_move_pool(&r->nat, &rule->nat); + TAILQ_INIT(&rule->route.list); + pfctl_move_pool(&r->route, &rule->route); + + TAILQ_INSERT_TAIL(rs->rules[rs_num].active.ptr, rule, entries); +} + +int +pfctl_append_eth_rule(struct pfctl *pf, struct pfctl_eth_rule *r, + const char *anchor_call) +{ + struct pfctl_eth_rule *rule; + struct pfctl_eth_ruleset *rs; + char *p; + + rs = &pf->eanchor->ruleset; + + if (anchor_call[0] && r->anchor == NULL) { + /* + * Don't make non-brace anchors part of the main anchor pool. + */ + if ((r->anchor = calloc(1, sizeof(*r->anchor))) == NULL) + err(1, "pfctl_append_rule: calloc"); + + pf_init_eth_ruleset(&r->anchor->ruleset); + r->anchor->ruleset.anchor = r->anchor; + if (strlcpy(r->anchor->path, anchor_call, + sizeof(rule->anchor->path)) >= sizeof(rule->anchor->path)) + errx(1, "pfctl_append_rule: strlcpy"); + if ((p = strrchr(anchor_call, '/')) != NULL) { + if (!strlen(p)) + err(1, "pfctl_append_eth_rule: bad anchor name %s", + anchor_call); + } else + p = (char *)anchor_call; + if (strlcpy(r->anchor->name, p, + sizeof(rule->anchor->name)) >= sizeof(rule->anchor->name)) + errx(1, "pfctl_append_eth_rule: strlcpy"); + } + + if ((rule = calloc(1, sizeof(*rule))) == NULL) + err(1, "calloc"); + bcopy(r, rule, sizeof(*rule)); + + TAILQ_INSERT_TAIL(&rs->rules, rule, entries); + return (0); +} + +int +pfctl_eth_ruleset_trans(struct pfctl *pf, char *path, + struct pfctl_eth_anchor *a) +{ + int osize = pf->trans->pfrb_size; + + if ((pf->loadopt & PFCTL_FLAG_ETH) != 0) { + if (pfctl_add_trans(pf->trans, PF_RULESET_ETH, path)) + return (1); + } + if (pfctl_trans(pf->dev, pf->trans, DIOCXBEGIN, osize)) + return (5); + + return (0); +} + +int +pfctl_ruleset_trans(struct pfctl *pf, char *path, struct pfctl_anchor *a, bool do_eth) +{ + int osize = pf->trans->pfrb_size; + + if ((pf->loadopt & PFCTL_FLAG_ETH) != 0 && do_eth) { + if (pfctl_add_trans(pf->trans, PF_RULESET_ETH, path)) + return (1); + } + if ((pf->loadopt & PFCTL_FLAG_NAT) != 0) { + if (pfctl_add_trans(pf->trans, PF_RULESET_NAT, path) || + pfctl_add_trans(pf->trans, PF_RULESET_BINAT, path) || + pfctl_add_trans(pf->trans, PF_RULESET_RDR, path)) + return (1); + } + if (a == pf->astack[0] && ((altqsupport && + (pf->loadopt & PFCTL_FLAG_ALTQ) != 0))) { + if (pfctl_add_trans(pf->trans, PF_RULESET_ALTQ, path)) + return (2); + } + if ((pf->loadopt & PFCTL_FLAG_FILTER) != 0) { + if (pfctl_add_trans(pf->trans, PF_RULESET_SCRUB, path) || + pfctl_add_trans(pf->trans, PF_RULESET_FILTER, path)) + return (3); + } + if (pf->loadopt & PFCTL_FLAG_TABLE) + if (pfctl_add_trans(pf->trans, PF_RULESET_TABLE, path)) + return (4); + if (pfctl_trans(pf->dev, pf->trans, DIOCXBEGIN, osize)) + return (5); + + return (0); +} + +int +pfctl_load_eth_ruleset(struct pfctl *pf, char *path, + struct pfctl_eth_ruleset *rs, int depth) +{ + struct pfctl_eth_rule *r; + int error, len = strlen(path); + int brace = 0; + + pf->eanchor = rs->anchor; + if (path[0]) + snprintf(&path[len], MAXPATHLEN - len, "/%s", pf->eanchor->name); + else + snprintf(&path[len], MAXPATHLEN - len, "%s", pf->eanchor->name); + + if (depth) { + if (TAILQ_FIRST(&rs->rules) != NULL) { + brace++; + if (pf->opts & PF_OPT_VERBOSE) + printf(" {\n"); + if ((pf->opts & PF_OPT_NOACTION) == 0 && + (error = pfctl_eth_ruleset_trans(pf, + path, rs->anchor))) { + printf("pfctl_load_eth_rulesets: " + "pfctl_eth_ruleset_trans %d\n", error); + goto error; + } + } else if (pf->opts & PF_OPT_VERBOSE) + printf("\n"); + } + + while ((r = TAILQ_FIRST(&rs->rules)) != NULL) { + TAILQ_REMOVE(&rs->rules, r, entries); + + error = pfctl_load_eth_rule(pf, path, r, depth); + if (error) + return (error); + + if (r->anchor) { + if ((error = pfctl_load_eth_ruleset(pf, path, + &r->anchor->ruleset, depth + 1))) + return (error); + } else if (pf->opts & PF_OPT_VERBOSE) + printf("\n"); + free(r); + } + if (brace && pf->opts & PF_OPT_VERBOSE) { + INDENT(depth - 1, (pf->opts & PF_OPT_VERBOSE)); + printf("}\n"); + } + path[len] = '\0'; + + return (0); +error: + path[len] = '\0'; + return (error); +} + +int +pfctl_load_eth_rule(struct pfctl *pf, char *path, struct pfctl_eth_rule *r, + int depth) +{ + char *name; + char anchor[PF_ANCHOR_NAME_SIZE]; + int len = strlen(path); + int ret; + + if (strlcpy(anchor, path, sizeof(anchor)) >= sizeof(anchor)) + errx(1, "pfctl_load_eth_rule: strlcpy"); + + if (r->anchor) { + if (r->anchor->match) { + if (path[0]) + snprintf(&path[len], MAXPATHLEN - len, + "/%s", r->anchor->name); + else + snprintf(&path[len], MAXPATHLEN - len, + "%s", r->anchor->name); + name = r->anchor->name; + } else + name = r->anchor->path; + } else + name = ""; + + if ((pf->opts & PF_OPT_NOACTION) == 0) + if ((ret = pfctl_add_eth_rule(pf->dev, r, anchor, name, + pf->eth_ticket)) != 0) + errc(1, ret, "DIOCADDETHRULENV"); + + if (pf->opts & PF_OPT_VERBOSE) { + INDENT(depth, !(pf->opts & PF_OPT_VERBOSE2)); + print_eth_rule(r, r->anchor ? r->anchor->name : "", + pf->opts & (PF_OPT_VERBOSE2 | PF_OPT_DEBUG)); + } + + path[len] = '\0'; + + return (0); +} + +static int +pfctl_load_tables(struct pfctl *pf, char *path, struct pfctl_anchor *a, + int rs_num) +{ + struct pfr_ktable *kt, *ktw; + struct pfr_uktable *ukt; + char anchor_path[PF_ANCHOR_MAXPATH]; + int e; + + RB_FOREACH_SAFE(kt, pfr_ktablehead, &pfr_ktables, ktw) { + if (strcmp(kt->pfrkt_anchor, a->path) != 0) + continue; + + if (path != NULL && *path) { + strlcpy(anchor_path, kt->pfrkt_anchor, + sizeof(anchor_path)); + snprintf(kt->pfrkt_anchor, PF_ANCHOR_MAXPATH, "%s/%s", + path, anchor_path); + } + ukt = (struct pfr_uktable *)kt; + e = pfr_ina_define(&ukt->pfrukt_t, ukt->pfrukt_addrs.pfrb_caddr, + ukt->pfrukt_addrs.pfrb_size, NULL, NULL, + pf->anchor->ruleset.tticket, + ukt->pfrukt_init_addr ? PFR_FLAG_ADDRSTOO : 0); + if (e != 0) + err(1, "%s pfr_ina_define() %s@%s", __func__, + kt->pfrkt_name, kt->pfrkt_anchor); + RB_REMOVE(pfr_ktablehead, &pfr_ktables, kt); + pfr_buf_clear(&ukt->pfrukt_addrs); + free(ukt); + } + + return (0); +} + +int +pfctl_load_ruleset(struct pfctl *pf, char *path, struct pfctl_ruleset *rs, + int rs_num, int depth) +{ + struct pfctl_rule *r; + int error, len = strlen(path); + int brace = 0; + + pf->anchor = rs->anchor; + + if (path[0]) + snprintf(&path[len], MAXPATHLEN - len, "/%s", pf->anchor->name); + else + snprintf(&path[len], MAXPATHLEN - len, "%s", pf->anchor->name); + + if (depth) { + if (TAILQ_FIRST(rs->rules[rs_num].active.ptr) != NULL) { + brace++; + if (pf->opts & PF_OPT_VERBOSE) + printf(" {\n"); + if ((pf->opts & PF_OPT_NOACTION) == 0 && + (error = pfctl_ruleset_trans(pf, + path, rs->anchor, false))) { + printf("%s: " + "pfctl_ruleset_trans %d\n", __func__, error); + goto error; + } + } else if (pf->opts & PF_OPT_VERBOSE) + printf("\n"); + } + + if (pf->optimize && rs_num == PF_RULESET_FILTER) + pfctl_optimize_ruleset(pf, rs); + + while ((r = TAILQ_FIRST(rs->rules[rs_num].active.ptr)) != NULL) { + TAILQ_REMOVE(rs->rules[rs_num].active.ptr, r, entries); + + for (int i = 0; i < PF_RULE_MAX_LABEL_COUNT; i++) + expand_label(r->label[i], PF_RULE_LABEL_SIZE, r); + expand_label(r->tagname, PF_TAG_NAME_SIZE, r); + expand_label(r->match_tagname, PF_TAG_NAME_SIZE, r); + + if ((error = pfctl_load_rule(pf, path, r, depth))) + goto error; + if (r->anchor) { + if ((error = pfctl_load_ruleset(pf, path, + &r->anchor->ruleset, rs_num, depth + 1))) + goto error; + if ((error = pfctl_load_tables(pf, path, r->anchor, rs_num))) + goto error; + } else if (pf->opts & PF_OPT_VERBOSE) + printf("\n"); + free(r); + } + if (brace && pf->opts & PF_OPT_VERBOSE) { + INDENT(depth - 1, (pf->opts & PF_OPT_VERBOSE)); + printf("}\n"); + } + path[len] = '\0'; + return (0); + + error: + path[len] = '\0'; + return (error); + +} + +int +pfctl_load_rule(struct pfctl *pf, char *path, struct pfctl_rule *r, int depth) +{ + u_int8_t rs_num = pf_get_ruleset_number(r->action); + char *name; + uint32_t ticket; + char anchor[PF_ANCHOR_NAME_SIZE]; + int len = strlen(path); + int error; + bool was_present; + + /* set up anchor before adding to path for anchor_call */ + if ((pf->opts & PF_OPT_NOACTION) == 0) { + if (pf->trans == NULL) + errx(1, "pfctl_load_rule: no transaction"); + ticket = pfctl_get_ticket(pf->trans, rs_num, path); + if (rs_num == PF_RULESET_FILTER) + pf->anchor->ruleset.tticket = ticket; + } + if (strlcpy(anchor, path, sizeof(anchor)) >= sizeof(anchor)) + errx(1, "pfctl_load_rule: strlcpy"); + + if (r->anchor) { + if (r->anchor->match) { + if (path[0]) + snprintf(&path[len], MAXPATHLEN - len, + "/%s", r->anchor->name); + else + snprintf(&path[len], MAXPATHLEN - len, + "%s", r->anchor->name); + name = r->anchor->name; + } else + name = r->anchor->path; + } else + name = ""; + + was_present = false; + if ((pf->opts & PF_OPT_NOACTION) == 0) { + if ((pf->opts & PF_OPT_NOACTION) == 0) { + if ((error = pfctl_begin_addrs(pf->h, + &pf->paddr.ticket)) != 0) + errc(1, error, "DIOCBEGINADDRS"); + } + + if (pfctl_add_pool(pf, &r->rdr, PF_RDR)) + return (1); + if (pfctl_add_pool(pf, &r->nat, PF_NAT)) + return (1); + if (pfctl_add_pool(pf, &r->route, PF_RT)) + return (1); + error = pfctl_add_rule_h(pf->h, r, anchor, name, ticket, + pf->paddr.ticket); + switch (error) { + case 0: + /* things worked, do nothing */ + break; + case EEXIST: + /* an identical rule is already present */ + was_present = true; + break; + default: + errc(1, error, "DIOCADDRULE"); + } + } + + if (pf->opts & PF_OPT_VERBOSE) { + INDENT(depth, !(pf->opts & PF_OPT_VERBOSE2)); + print_rule(r, name, + pf->opts & PF_OPT_VERBOSE2, + pf->opts & PF_OPT_NUMERIC); + if (was_present) + printf(" -- rule was already present"); + } + path[len] = '\0'; + pfctl_clear_pool(&r->rdr); + pfctl_clear_pool(&r->nat); + return (0); +} + +int +pfctl_add_altq(struct pfctl *pf, struct pf_altq *a) +{ + if (altqsupport && + (loadopt & PFCTL_FLAG_ALTQ) != 0) { + memcpy(&pf->paltq->altq, a, sizeof(struct pf_altq)); + if ((pf->opts & PF_OPT_NOACTION) == 0) { + if (ioctl(pf->dev, DIOCADDALTQ, pf->paltq)) { + if (errno == ENXIO) + errx(1, "qtype not configured"); + else if (errno == ENODEV) + errx(1, "%s: driver does not support " + "altq", a->ifname); + else + err(1, "DIOCADDALTQ"); + } + } + pfaltq_store(&pf->paltq->altq); + } + return (0); +} + +int +pfctl_rules(int dev, char *filename, int opts, int optimize, + char *anchorname, struct pfr_buffer *trans) +{ +#define ERR(...) do { warn(__VA_ARGS__); goto _error; } while(0) +#define ERRX(...) do { warnx(__VA_ARGS__); goto _error; } while(0) + + struct pfr_buffer *t, buf; + struct pfioc_altq pa; + struct pfctl pf; + struct pfctl_ruleset *rs; + struct pfctl_eth_ruleset *ethrs; + struct pfr_table trs; + char *path = NULL; + int osize; + + RB_INIT(&pf_anchors); + memset(&pf_main_anchor, 0, sizeof(pf_main_anchor)); + pf_init_ruleset(&pf_main_anchor.ruleset); + memset(&pf, 0, sizeof(pf)); + memset(&trs, 0, sizeof(trs)); + pf_main_anchor.ruleset.anchor = &pf_main_anchor; + + memset(&pf_eth_main_anchor, 0, sizeof(pf_eth_main_anchor)); + pf_init_eth_ruleset(&pf_eth_main_anchor.ruleset); + pf_eth_main_anchor.ruleset.anchor = &pf_eth_main_anchor; + + if (trans == NULL) { + bzero(&buf, sizeof(buf)); + buf.pfrb_type = PFRB_TRANS; + pf.trans = &buf; + t = &buf; + osize = 0; + } else { + t = trans; + osize = t->pfrb_size; + } + + memset(&pa, 0, sizeof(pa)); + pa.version = PFIOC_ALTQ_VERSION; + memset(&pf, 0, sizeof(pf)); + memset(&trs, 0, sizeof(trs)); + if ((path = calloc(1, MAXPATHLEN)) == NULL) + ERRX("%s: calloc", __func__); + if (strlcpy(trs.pfrt_anchor, anchorname, + sizeof(trs.pfrt_anchor)) >= sizeof(trs.pfrt_anchor)) + ERRX("%s: strlcpy", __func__); + pf.dev = dev; + pf.h = pfh; + pf.opts = opts; + pf.optimize = optimize; + pf.loadopt = loadopt; + + /* non-brace anchor, create without resolving the path */ + if ((pf.anchor = calloc(1, sizeof(*pf.anchor))) == NULL) + ERRX("%s: calloc", __func__); + rs = &pf.anchor->ruleset; + pf_init_ruleset(rs); + rs->anchor = pf.anchor; + if (strlcpy(pf.anchor->path, anchorname, + sizeof(pf.anchor->path)) >= sizeof(pf.anchor->path)) + errx(1, "%s: strlcpy", __func__); + if (strlcpy(pf.anchor->name, anchorname, + sizeof(pf.anchor->name)) >= sizeof(pf.anchor->name)) + errx(1, "%s: strlcpy", __func__); + + + pf.astack[0] = pf.anchor; + pf.asd = 0; + if (anchorname[0]) + pf.loadopt &= ~PFCTL_FLAG_ALTQ; + pf.paltq = &pa; + pf.trans = t; + pfctl_init_options(&pf); + + /* Set up ethernet anchor */ + if ((pf.eanchor = calloc(1, sizeof(*pf.eanchor))) == NULL) + ERRX("%s: calloc", __func__); + + if (strlcpy(pf.eanchor->path, anchorname, + sizeof(pf.eanchor->path)) >= sizeof(pf.eanchor->path)) + errx(1, "%s: strlcpy", __func__); + if (strlcpy(pf.eanchor->name, anchorname, + sizeof(pf.eanchor->name)) >= sizeof(pf.eanchor->name)) + errx(1, "%s: strlcpy", __func__); + + ethrs = &pf.eanchor->ruleset; + pf_init_eth_ruleset(ethrs); + ethrs->anchor = pf.eanchor; + pf.eastack[0] = pf.eanchor; + + if ((opts & PF_OPT_NOACTION) == 0) { + /* + * XXX For the time being we need to open transactions for + * the main ruleset before parsing, because tables are still + * loaded at parse time. + */ + if (pfctl_ruleset_trans(&pf, anchorname, pf.anchor, true)) + ERRX("%s", __func__); + if (pf.loadopt & PFCTL_FLAG_ETH) + pf.eth_ticket = pfctl_get_ticket(t, PF_RULESET_ETH, anchorname); + if (altqsupport && (pf.loadopt & PFCTL_FLAG_ALTQ)) + pa.ticket = + pfctl_get_ticket(t, PF_RULESET_ALTQ, anchorname); + if (pf.loadopt & PFCTL_FLAG_TABLE) + pf.astack[0]->ruleset.tticket = + pfctl_get_ticket(t, PF_RULESET_TABLE, anchorname); + } + + if (parse_config(filename, &pf) < 0) { + if ((opts & PF_OPT_NOACTION) == 0) + ERRX("Syntax error in config file: " + "pf rules not loaded"); + else + goto _error; + } + if (loadopt & PFCTL_FLAG_OPTION) + pfctl_adjust_skip_ifaces(&pf); + + if ((pf.loadopt & PFCTL_FLAG_FILTER && + (pfctl_load_ruleset(&pf, path, rs, PF_RULESET_SCRUB, 0))) || + (pf.loadopt & PFCTL_FLAG_ETH && + (pfctl_load_eth_ruleset(&pf, path, ethrs, 0))) || + (pf.loadopt & PFCTL_FLAG_NAT && + (pfctl_load_ruleset(&pf, path, rs, PF_RULESET_NAT, 0) || + pfctl_load_ruleset(&pf, path, rs, PF_RULESET_RDR, 0) || + pfctl_load_ruleset(&pf, path, rs, PF_RULESET_BINAT, 0))) || + (pf.loadopt & PFCTL_FLAG_FILTER && + pfctl_load_ruleset(&pf, path, rs, PF_RULESET_FILTER, 0))) { + if ((opts & PF_OPT_NOACTION) == 0) + ERRX("Unable to load rules into kernel"); + else + goto _error; + } + + if ((altqsupport && (pf.loadopt & PFCTL_FLAG_ALTQ) != 0)) + if (check_commit_altq(dev, opts) != 0) + ERRX("errors in altq config"); + + if (trans == NULL) { + /* process "load anchor" directives */ + if (pfctl_load_anchors(dev, &pf) == -1) + ERRX("load anchors"); + + if ((opts & PF_OPT_NOACTION) == 0) { + if (!anchorname[0] && pfctl_load_options(&pf)) + goto _error; + if (pfctl_trans(dev, t, DIOCXCOMMIT, osize)) + ERR("DIOCXCOMMIT"); + } + } + free(path); + return (0); + +_error: + if (trans == NULL) { /* main ruleset */ + if ((opts & PF_OPT_NOACTION) == 0) + if (pfctl_trans(dev, t, DIOCXROLLBACK, osize)) + err(1, "DIOCXROLLBACK"); + exit(1); + } else { /* sub ruleset */ + free(path); + return (-1); + } + +#undef ERR +#undef ERRX +} + +FILE * +pfctl_fopen(const char *name, const char *mode) +{ + struct stat st; + FILE *fp; + + fp = fopen(name, mode); + if (fp == NULL) + return (NULL); + if (fstat(fileno(fp), &st)) { + fclose(fp); + return (NULL); + } + if (S_ISDIR(st.st_mode)) { + fclose(fp); + errno = EISDIR; + return (NULL); + } + return (fp); +} + +void +pfctl_init_options(struct pfctl *pf) +{ + + pf->timeout[PFTM_TCP_FIRST_PACKET] = PFTM_TCP_FIRST_PACKET_VAL; + pf->timeout[PFTM_TCP_OPENING] = PFTM_TCP_OPENING_VAL; + pf->timeout[PFTM_TCP_ESTABLISHED] = PFTM_TCP_ESTABLISHED_VAL; + pf->timeout[PFTM_TCP_CLOSING] = PFTM_TCP_CLOSING_VAL; + pf->timeout[PFTM_TCP_FIN_WAIT] = PFTM_TCP_FIN_WAIT_VAL; + pf->timeout[PFTM_TCP_CLOSED] = PFTM_TCP_CLOSED_VAL; + pf->timeout[PFTM_SCTP_FIRST_PACKET] = PFTM_TCP_FIRST_PACKET_VAL; + pf->timeout[PFTM_SCTP_OPENING] = PFTM_TCP_OPENING_VAL; + pf->timeout[PFTM_SCTP_ESTABLISHED] = PFTM_TCP_ESTABLISHED_VAL; + pf->timeout[PFTM_SCTP_CLOSING] = PFTM_TCP_CLOSING_VAL; + pf->timeout[PFTM_SCTP_CLOSED] = PFTM_TCP_CLOSED_VAL; + pf->timeout[PFTM_UDP_FIRST_PACKET] = PFTM_UDP_FIRST_PACKET_VAL; + pf->timeout[PFTM_UDP_SINGLE] = PFTM_UDP_SINGLE_VAL; + pf->timeout[PFTM_UDP_MULTIPLE] = PFTM_UDP_MULTIPLE_VAL; + pf->timeout[PFTM_ICMP_FIRST_PACKET] = PFTM_ICMP_FIRST_PACKET_VAL; + pf->timeout[PFTM_ICMP_ERROR_REPLY] = PFTM_ICMP_ERROR_REPLY_VAL; + pf->timeout[PFTM_OTHER_FIRST_PACKET] = PFTM_OTHER_FIRST_PACKET_VAL; + pf->timeout[PFTM_OTHER_SINGLE] = PFTM_OTHER_SINGLE_VAL; + pf->timeout[PFTM_OTHER_MULTIPLE] = PFTM_OTHER_MULTIPLE_VAL; + pf->timeout[PFTM_FRAG] = PFTM_FRAG_VAL; + pf->timeout[PFTM_INTERVAL] = PFTM_INTERVAL_VAL; + pf->timeout[PFTM_SRC_NODE] = PFTM_SRC_NODE_VAL; + pf->timeout[PFTM_TS_DIFF] = PFTM_TS_DIFF_VAL; + pf->timeout[PFTM_ADAPTIVE_START] = PFSTATE_ADAPT_START; + pf->timeout[PFTM_ADAPTIVE_END] = PFSTATE_ADAPT_END; + + pf->limit[PF_LIMIT_STATES] = PFSTATE_HIWAT; + pf->limit[PF_LIMIT_FRAGS] = PFFRAG_FRENT_HIWAT; + + pf->limit[PF_LIMIT_SRC_NODES] = (limit_curr[PF_LIMIT_SRC_NODES] == 0) ? + PFSNODE_HIWAT : limit_curr[PF_LIMIT_SRC_NODES]; + pf->limit[PF_LIMIT_TABLE_ENTRIES] = + (limit_curr[PF_LIMIT_TABLE_ENTRIES] == 0) ? + PFR_KENTRY_HIWAT : limit_curr[PF_LIMIT_TABLE_ENTRIES]; + pf->limit[PF_LIMIT_ANCHORS] = (limit_curr[PF_LIMIT_ANCHORS] == 0) ? + PF_ANCHOR_HIWAT : limit_curr[PF_LIMIT_ANCHORS]; + + pf->debug = PF_DEBUG_URGENT; + pf->reassemble = 0; + + pf->syncookies = false; + pf->syncookieswat[0] = PF_SYNCOOKIES_LOWATPCT; + pf->syncookieswat[1] = PF_SYNCOOKIES_HIWATPCT; +} + +int +pfctl_load_options(struct pfctl *pf) +{ + int i, error = 0; + + if ((loadopt & PFCTL_FLAG_OPTION) == 0) + return (0); + + /* load limits */ + for (i = 0; i < PF_LIMIT_MAX; i++) { + if ((pf->opts & PF_OPT_MERGE) && !pf->limit_set[i]) + continue; + if (pfctl_load_limit(pf, i, pf->limit[i])) + error = 1; + } + + /* + * If we've set the states limit, but haven't explicitly set adaptive + * timeouts, do it now with a start of 60% and end of 120%. + */ + if (pf->limit_set[PF_LIMIT_STATES] && + !pf->timeout_set[PFTM_ADAPTIVE_START] && + !pf->timeout_set[PFTM_ADAPTIVE_END]) { + pf->timeout[PFTM_ADAPTIVE_START] = + (pf->limit[PF_LIMIT_STATES] / 10) * 6; + pf->timeout_set[PFTM_ADAPTIVE_START] = 1; + pf->timeout[PFTM_ADAPTIVE_END] = + (pf->limit[PF_LIMIT_STATES] / 10) * 12; + pf->timeout_set[PFTM_ADAPTIVE_END] = 1; + } + + /* load timeouts */ + for (i = 0; i < PFTM_MAX; i++) { + if ((pf->opts & PF_OPT_MERGE) && !pf->timeout_set[i]) + continue; + if (pfctl_load_timeout(pf, i, pf->timeout[i])) + error = 1; + } + + /* load debug */ + if (!(pf->opts & PF_OPT_MERGE) || pf->debug_set) + if (pfctl_load_debug(pf, pf->debug)) + error = 1; + + /* load logif */ + if (!(pf->opts & PF_OPT_MERGE) || pf->ifname_set) + if (pfctl_load_logif(pf, pf->ifname)) + error = 1; + + /* load hostid */ + if (!(pf->opts & PF_OPT_MERGE) || pf->hostid_set) + if (pfctl_load_hostid(pf, pf->hostid)) + error = 1; + + /* load reassembly settings */ + if (!(pf->opts & PF_OPT_MERGE) || pf->reass_set) + if (pfctl_load_reassembly(pf, pf->reassemble)) + error = 1; + + /* load keepcounters */ + if (pfctl_set_keepcounters(pf->dev, pf->keep_counters)) + error = 1; + + /* load syncookies settings */ + if (pfctl_load_syncookies(pf, pf->syncookies)) + error = 1; + + return (error); +} + +int +pfctl_apply_limit(struct pfctl *pf, const char *opt, unsigned int limit) +{ + int i; + + + for (i = 0; pf_limits[i].name; i++) { + if (strcasecmp(opt, pf_limits[i].name) == 0) { + pf->limit[pf_limits[i].index] = limit; + pf->limit_set[pf_limits[i].index] = 1; + break; + } + } + if (pf_limits[i].name == NULL) { + warnx("Bad pool name."); + return (1); + } + + if (pf->opts & PF_OPT_VERBOSE) + printf("set limit %s %d\n", opt, limit); + + if ((pf->opts & PF_OPT_NOACTION) == 0) + pfctl_load_options(pf); + + return (0); +} + +int +pfctl_load_limit(struct pfctl *pf, unsigned int index, unsigned int limit) +{ + static int restore_limit_handler_armed = 0; + + if (pfctl_set_limit(pf->h, index, limit)) { + if (errno == EBUSY) + warnx("Current pool size exceeds requested %s limit %u", + pf_limits[index].name, limit); + else + warnx("Cannot set %s limit to %u", + pf_limits[index].name, limit); + return (1); + } else if (restore_limit_handler_armed == 0) { + atexit(pfctl_restore_limits); + restore_limit_handler_armed = 1; + } + return (0); +} + +int +pfctl_apply_timeout(struct pfctl *pf, const char *opt, int seconds, int quiet) +{ + int i; + + if ((loadopt & PFCTL_FLAG_OPTION) == 0) + return (0); + + for (i = 0; pf_timeouts[i].name; i++) { + if (strcasecmp(opt, pf_timeouts[i].name) == 0) { + pf->timeout[pf_timeouts[i].timeout] = seconds; + pf->timeout_set[pf_timeouts[i].timeout] = 1; + break; + } + } + + if (pf_timeouts[i].name == NULL) { + warnx("Bad timeout name."); + return (1); + } + + + if (pf->opts & PF_OPT_VERBOSE && ! quiet) + printf("set timeout %s %d\n", opt, seconds); + + return (0); +} + +int +pfctl_load_timeout(struct pfctl *pf, unsigned int timeout, unsigned int seconds) +{ + if (pfctl_set_timeout(pf->h, timeout, seconds)) { + warnx("DIOCSETTIMEOUT"); + return (1); + } + return (0); +} + +int +pfctl_set_reassembly(struct pfctl *pf, int on, int nodf) +{ + if ((loadopt & PFCTL_FLAG_OPTION) == 0) + return (0); + + pf->reass_set = 1; + if (on) { + pf->reassemble = PF_REASS_ENABLED; + if (nodf) + pf->reassemble |= PF_REASS_NODF; + } else { + pf->reassemble = 0; + } + + if (pf->opts & PF_OPT_VERBOSE) + printf("set reassemble %s %s\n", on ? "yes" : "no", + nodf ? "no-df" : ""); + + return (0); +} + +int +pfctl_set_optimization(struct pfctl *pf, const char *opt) +{ + const struct pf_hint *hint; + int i, r; + + if ((loadopt & PFCTL_FLAG_OPTION) == 0) + return (0); + + for (i = 0; pf_hints[i].name; i++) + if (strcasecmp(opt, pf_hints[i].name) == 0) + break; + + hint = pf_hints[i].hint; + if (hint == NULL) { + warnx("invalid state timeouts optimization"); + return (1); + } + + for (i = 0; hint[i].name; i++) + if ((r = pfctl_apply_timeout(pf, hint[i].name, + hint[i].timeout, 1))) + return (r); + + if (pf->opts & PF_OPT_VERBOSE) + printf("set optimization %s\n", opt); + + return (0); +} + +int +pfctl_set_logif(struct pfctl *pf, char *ifname) +{ + + if ((loadopt & PFCTL_FLAG_OPTION) == 0) + return (0); + + if (!strcmp(ifname, "none")) { + free(pf->ifname); + pf->ifname = NULL; + } else { + pf->ifname = strdup(ifname); + if (!pf->ifname) + errx(1, "pfctl_set_logif: strdup"); + } + pf->ifname_set = 1; + + if (pf->opts & PF_OPT_VERBOSE) + printf("set loginterface %s\n", ifname); + + return (0); +} + +int +pfctl_load_logif(struct pfctl *pf, char *ifname) +{ + if (ifname != NULL && strlen(ifname) >= IFNAMSIZ) { + warnx("pfctl_load_logif: strlcpy"); + return (1); + } + return (pfctl_set_statusif(pfh, ifname ? ifname : "")); +} + +void +pfctl_set_hostid(struct pfctl *pf, u_int32_t hostid) +{ + if ((loadopt & PFCTL_FLAG_OPTION) == 0) + return; + + HTONL(hostid); + + pf->hostid = hostid; + pf->hostid_set = 1; + + if (pf->opts & PF_OPT_VERBOSE) + printf("set hostid 0x%08x\n", ntohl(hostid)); +} + +int +pfctl_load_hostid(struct pfctl *pf, u_int32_t hostid) +{ + if (ioctl(dev, DIOCSETHOSTID, &hostid)) { + warnx("DIOCSETHOSTID"); + return (1); + } + return (0); +} + +int +pfctl_load_reassembly(struct pfctl *pf, u_int32_t reassembly) +{ + if (ioctl(dev, DIOCSETREASS, &reassembly)) { + warnx("DIOCSETREASS"); + return (1); + } + return (0); +} + +int +pfctl_load_syncookies(struct pfctl *pf, u_int8_t val) +{ + struct pfctl_syncookies cookies; + + bzero(&cookies, sizeof(cookies)); + + cookies.mode = val; + cookies.lowwater = pf->syncookieswat[0]; + cookies.highwater = pf->syncookieswat[1]; + + if (pfctl_set_syncookies(dev, &cookies)) { + warnx("DIOCSETSYNCOOKIES"); + return (1); + } + return (0); +} + +int +pfctl_cfg_syncookies(struct pfctl *pf, uint8_t val, struct pfctl_watermarks *w) +{ + if (val != PF_SYNCOOKIES_ADAPTIVE && w != NULL) { + warnx("syncookies start/end only apply to adaptive"); + return (1); + } + if (val == PF_SYNCOOKIES_ADAPTIVE && w != NULL) { + if (!w->hi) + w->hi = PF_SYNCOOKIES_HIWATPCT; + if (!w->lo) + w->lo = w->hi / 2; + if (w->lo >= w->hi) { + warnx("start must be higher than end"); + return (1); + } + pf->syncookieswat[0] = w->lo; + pf->syncookieswat[1] = w->hi; + pf->syncookieswat_set = 1; + } + + if (pf->opts & PF_OPT_VERBOSE) { + if (val == PF_SYNCOOKIES_NEVER) + printf("set syncookies never\n"); + else if (val == PF_SYNCOOKIES_ALWAYS) + printf("set syncookies always\n"); + else if (val == PF_SYNCOOKIES_ADAPTIVE) { + if (pf->syncookieswat_set) + printf("set syncookies adaptive (start %u%%, " + "end %u%%)\n", pf->syncookieswat[1], + pf->syncookieswat[0]); + else + printf("set syncookies adaptive\n"); + } else { /* cannot happen */ + warnx("king bula ate all syncookies"); + return (1); + } + } + + pf->syncookies = val; + return (0); +} + +int +pfctl_do_set_debug(struct pfctl *pf, char *d) +{ + u_int32_t level; + int ret; + + if ((loadopt & PFCTL_FLAG_OPTION) == 0) + return (0); + + if (!strcmp(d, "none")) + pf->debug = PF_DEBUG_NONE; + else if (!strcmp(d, "urgent")) + pf->debug = PF_DEBUG_URGENT; + else if (!strcmp(d, "misc")) + pf->debug = PF_DEBUG_MISC; + else if (!strcmp(d, "loud")) + pf->debug = PF_DEBUG_NOISY; + else { + warnx("unknown debug level \"%s\"", d); + return (-1); + } + + pf->debug_set = 1; + level = pf->debug; + + if ((pf->opts & PF_OPT_NOACTION) == 0) + if ((ret = pfctl_set_debug(pfh, level)) != 0) + errc(1, ret, "DIOCSETDEBUG"); + + if (pf->opts & PF_OPT_VERBOSE) + printf("set debug %s\n", d); + + return (0); +} + +int +pfctl_load_debug(struct pfctl *pf, unsigned int level) +{ + if (pfctl_set_debug(pf->h, level)) { + warnx("DIOCSETDEBUG"); + return (1); + } + return (0); +} + +int +pfctl_set_interface_flags(struct pfctl *pf, char *ifname, int flags, int how) +{ + struct pfioc_iface pi; + struct node_host *h = NULL, *n = NULL; + + if ((loadopt & PFCTL_FLAG_OPTION) == 0) + return (0); + + bzero(&pi, sizeof(pi)); + + pi.pfiio_flags = flags; + + /* Make sure our cache matches the kernel. If we set or clear the flag + * for a group this applies to all members. */ + h = ifa_grouplookup(ifname, 0); + for (n = h; n != NULL; n = n->next) + pfctl_set_interface_flags(pf, n->ifname, flags, how); + + if (strlcpy(pi.pfiio_name, ifname, sizeof(pi.pfiio_name)) >= + sizeof(pi.pfiio_name)) + errx(1, "pfctl_set_interface_flags: strlcpy"); + + if ((pf->opts & PF_OPT_NOACTION) == 0) { + if (how == 0) { + if (ioctl(pf->dev, DIOCCLRIFFLAG, &pi)) + pfctl_err(pf->opts, 1, "DIOCCLRIFFLAG"); + } else { + if (ioctl(pf->dev, DIOCSETIFFLAG, &pi)) + err(1, "DIOCSETIFFLAG"); + pfctl_check_skip_ifaces(ifname); + } + } + return (0); +} + +void +pfctl_debug(int dev, u_int32_t level, int opts) +{ + int ret; + + if ((ret = pfctl_set_debug(pfh, level)) != 0) + errc(1, ret, "DIOCSETDEBUG"); + if ((opts & PF_OPT_QUIET) == 0) { + fprintf(stderr, "debug level set to '"); + switch (level) { + case PF_DEBUG_NONE: + fprintf(stderr, "none"); + break; + case PF_DEBUG_URGENT: + fprintf(stderr, "urgent"); + break; + case PF_DEBUG_MISC: + fprintf(stderr, "misc"); + break; + case PF_DEBUG_NOISY: + fprintf(stderr, "loud"); + break; + default: + fprintf(stderr, "<invalid>"); + break; + } + fprintf(stderr, "'\n"); + } +} + +int +pfctl_test_altqsupport(int dev, int opts) +{ + struct pfioc_altq pa; + + pa.version = PFIOC_ALTQ_VERSION; + if (ioctl(dev, DIOCGETALTQS, &pa)) { + if (errno == ENODEV) { + if (opts & PF_OPT_VERBOSE) + fprintf(stderr, "No ALTQ support in kernel\n" + "ALTQ related functions disabled\n"); + return (0); + } else + err(1, "DIOCGETALTQS"); + } + return (1); +} + +int +pfctl_walk_show(int opts, struct pfioc_ruleset *pr, void *warg) +{ + if (pr->path[0]) { + if (pr->path[0] != '_' || (opts & PF_OPT_VERBOSE)) + printf(" %s/%s\n", pr->path, pr->name); + } else if (pr->name[0] != '_' || (opts & PF_OPT_VERBOSE)) + printf(" %s\n", pr->name); + + return (0); +} + +int +pfctl_walk_get(int opts, struct pfioc_ruleset *pr, void *warg) +{ + struct pfr_anchoritem *pfra; + struct pfr_anchors *anchors; + int e; + + anchors = (struct pfr_anchors *)warg; + + pfra = malloc(sizeof(*pfra)); + if (pfra == NULL) + err(1, "%s", __func__); + + if (pr->path[0]) + e = asprintf(&pfra->pfra_anchorname, "%s/%s", pr->path, + pr->name); + else + e = asprintf(&pfra->pfra_anchorname, "%s", pr->name); + + if (e == -1) + err(1, "%s", __func__); + + SLIST_INSERT_HEAD(anchors, pfra, pfra_sle); + + return (0); +} + +int +pfctl_walk_anchors(int dev, int opts, const char *anchor, + int(walkf)(int, struct pfioc_ruleset *, void *), void *warg) +{ + struct pfioc_ruleset pr; + u_int32_t mnr, nr; + int ret; + + memset(&pr, 0, sizeof(pr)); + if ((ret = pfctl_get_rulesets(pfh, anchor, &mnr)) != 0) + errx(1, "%s", pf_strerror(ret)); + for (nr = 0; nr < mnr; ++nr) { + char sub[MAXPATHLEN]; + + if ((ret = pfctl_get_ruleset(pfh, anchor, nr, &pr)) != 0) + errc(1, ret, "DIOCGETRULESET"); + if (!strcmp(pr.name, PF_RESERVED_ANCHOR)) + continue; + sub[0] = '\0'; + if (walkf(opts, &pr, warg)) + return (-1); + + if (pr.path[0]) + snprintf(sub, sizeof(sub), "%s/%s", pr.path, pr.name); + else + snprintf(sub, sizeof(sub), "%s", pr.name); + if (pfctl_walk_anchors(dev, opts, sub, walkf, warg)) + return (-1); + } + return (0); +} + +int +pfctl_show_anchors(int dev, int opts, char *anchor) +{ + return ( + pfctl_walk_anchors(dev, opts, anchor, pfctl_walk_show, NULL)); +} + +struct pfr_anchors * +pfctl_get_anchors(int dev, const char *anchor, int opts) +{ + struct pfioc_ruleset pr; + static struct pfr_anchors anchors; + char anchorbuf[PATH_MAX]; + char *n; + + SLIST_INIT(&anchors); + + memset(&pr, 0, sizeof(pr)); + if (*anchor != '\0') { + strlcpy(anchorbuf, anchor, sizeof(anchorbuf)); + n = dirname(anchorbuf); + if (n[0] != '.' && n[1] != '\0') + strlcpy(pr.path, n, sizeof(pr.path)); + strlcpy(anchorbuf, anchor, sizeof(anchorbuf)); + n = basename(anchorbuf); + if (n != NULL) + strlcpy(pr.name, n, sizeof(pr.name)); + } + + /* insert a root anchor first. */ + pfctl_walk_get(opts, &pr, &anchors); + + if (pfctl_walk_anchors(dev, opts, anchor, pfctl_walk_get, &anchors)) + errx(1, "%s failed to retrieve list of anchors, can't continue", + __func__); + + return (&anchors); +} + +int +pfctl_call_cleartables(int dev, int opts, struct pfr_anchoritem *pfra) +{ + /* + * PF_OPT_QUIET makes pfctl_clear_tables() to stop printing number of + * tables cleared for given anchor. + */ + opts |= PF_OPT_QUIET; + return ((pfctl_do_clear_tables(pfra->pfra_anchorname, opts) == -1) ? + 1 : 0); +} + +int +pfctl_call_clearrules(int dev, int opts, struct pfr_anchoritem *pfra) +{ + /* + * PF_OPT_QUIET makes pfctl_clear_rules() to stop printing a 'rules + * cleared' message for every anchor it deletes. + */ + opts |= PF_OPT_QUIET; + return (pfctl_flush_rules(dev, opts, pfra->pfra_anchorname)); +} + +int +pfctl_call_clearanchors(int dev, int opts, struct pfr_anchoritem *pfra) +{ + int rv = 0; + + rv |= pfctl_call_cleartables(dev, opts, pfra); + rv |= pfctl_call_clearrules(dev, opts, pfra); + + return (rv); +} + +int +pfctl_call_showtables(int dev, int opts, struct pfr_anchoritem *pfra) +{ + pfctl_show_tables(pfra->pfra_anchorname, opts); + return (0); +} + +int +pfctl_recurse(int dev, int opts, const char *anchorname, + int(*walkf)(int, int, struct pfr_anchoritem *)) +{ + int rv = 0; + struct pfr_anchors *anchors; + struct pfr_anchoritem *pfra, *pfra_save; + + anchors = pfctl_get_anchors(dev, anchorname, opts); + /* + * While traversing the list, pfctl_clear_*() must always return + * so that failures on one anchor do not prevent clearing others. + */ + opts |= PF_OPT_IGNFAIL; + if ((opts & PF_OPT_CALLSHOW) == 0) + printf("Removing:\n"); + SLIST_FOREACH_SAFE(pfra, anchors, pfra_sle, pfra_save) { + if ((opts & PF_OPT_CALLSHOW) == 0) + printf(" %s\n", + (*pfra->pfra_anchorname == '\0') ? "/" : + pfra->pfra_anchorname); + rv |= walkf(dev, opts, pfra); + SLIST_REMOVE(anchors, pfra, pfr_anchoritem, pfra_sle); + free(pfra->pfra_anchorname); + free(pfra); + } + + return (rv); +} + +int +pfctl_show_eth_anchors(int dev, int opts, char *anchorname) +{ + struct pfctl_eth_rulesets_info ri; + struct pfctl_eth_ruleset_info rs; + int ret; + + if ((ret = pfctl_get_eth_rulesets_info(dev, &ri, anchorname)) != 0) { + if (ret != ENOENT) + errc(1, ret, "DIOCGETETHRULESETS"); + return (-1); + } + + for (int nr = 0; nr < ri.nr; nr++) { + char sub[MAXPATHLEN]; + + if ((ret = pfctl_get_eth_ruleset(dev, anchorname, nr, &rs)) != 0) + errc(1, ret, "DIOCGETETHRULESET"); + + if (!strcmp(rs.name, PF_RESERVED_ANCHOR)) + continue; + sub[0] = 0; + if (rs.path[0]) { + strlcat(sub, rs.path, sizeof(sub)); + strlcat(sub, "/", sizeof(sub)); + } + strlcat(sub, rs.name, sizeof(sub)); + if (sub[0] != '_' || (opts & PF_OPT_VERBOSE)) + printf(" %s\n", sub); + if ((opts & PF_OPT_VERBOSE) && pfctl_show_eth_anchors(dev, opts, sub)) + return (-1); + } + return (0); +} + +const char * +pfctl_lookup_option(char *cmd, const char * const *list) +{ + if (cmd != NULL && *cmd) + for (; *list; list++) + if (!strncmp(cmd, *list, strlen(cmd))) + return (*list); + return (NULL); +} + +void +pfctl_reset(int dev, int opts) +{ + struct pfctl pf; + struct pfr_buffer t; + int i; + + memset(&pf, 0, sizeof(pf)); + pf.dev = dev; + pf.h = pfh; + pfctl_init_options(&pf); + + /* Force reset upon pfctl_load_options() */ + pf.debug_set = 1; + pf.reass_set = 1; + pf.syncookieswat_set = 1; + pf.ifname = strdup("none"); + if (pf.ifname == NULL) + err(1, "%s: strdup", __func__); + pf.ifname_set = 1; + + memset(&t, 0, sizeof(t)); + t.pfrb_type = PFRB_TRANS; + if (pfctl_trans(dev, &t, DIOCXBEGIN, 0)) + err(1, "%s: DIOCXBEGIN", __func__); + + for (i = 0; pf_limits[i].name; i++) + pf.limit_set[pf_limits[i].index] = 1; + + for (i = 0; pf_timeouts[i].name; i++) + pf.timeout_set[pf_timeouts[i].timeout] = 1; + + pfctl_load_options(&pf); + + if (pfctl_trans(dev, &t, DIOCXCOMMIT, 0)) + err(1, "%s: DIOCXCOMMIT", __func__); + + pfctl_clear_interface_flags(dev, opts); +} + +int +main(int argc, char *argv[]) +{ + int ch; + int mode = O_RDONLY; + int opts = 0; + int optimize = PF_OPTIMIZE_BASIC; + char anchorname[MAXPATHLEN]; + char *path; + + if (argc < 2) + usage(); + + while ((ch = getopt(argc, argv, + "a:AdD:eqf:F:ghi:k:K:mMnNOo:Pp:rRs:St:T:vx:z")) != -1) { + switch (ch) { + case 'a': + anchoropt = optarg; + break; + case 'd': + opts |= PF_OPT_DISABLE; + mode = O_RDWR; + break; + case 'D': + if (pfctl_cmdline_symset(optarg) < 0) + warnx("could not parse macro definition %s", + optarg); + break; + case 'e': + opts |= PF_OPT_ENABLE; + mode = O_RDWR; + break; + case 'q': + opts |= PF_OPT_QUIET; + break; + case 'F': + clearopt = pfctl_lookup_option(optarg, clearopt_list); + if (clearopt == NULL) { + warnx("Unknown flush modifier '%s'", optarg); + usage(); + } + mode = O_RDWR; + break; + case 'i': + ifaceopt = optarg; + break; + case 'k': + if (state_killers >= 2) { + warnx("can only specify -k twice"); + usage(); + /* NOTREACHED */ + } + state_kill[state_killers++] = optarg; + mode = O_RDWR; + break; + case 'K': + if (src_node_killers >= 2) { + warnx("can only specify -K twice"); + usage(); + /* NOTREACHED */ + } + src_node_kill[src_node_killers++] = optarg; + mode = O_RDWR; + break; + case 'm': + opts |= PF_OPT_MERGE; + break; + case 'M': + opts |= PF_OPT_KILLMATCH; + break; + case 'n': + opts |= PF_OPT_NOACTION; + break; + case 'N': + loadopt |= PFCTL_FLAG_NAT; + break; + case 'r': + opts |= PF_OPT_USEDNS; + break; + case 'f': + rulesopt = optarg; + mode = O_RDWR; + break; + case 'g': + opts |= PF_OPT_DEBUG; + break; + case 'A': + loadopt |= PFCTL_FLAG_ALTQ; + break; + case 'R': + loadopt |= PFCTL_FLAG_FILTER; + break; + case 'o': + optiopt = pfctl_lookup_option(optarg, optiopt_list); + if (optiopt == NULL) { + warnx("Unknown optimization '%s'", optarg); + usage(); + } + opts |= PF_OPT_OPTIMIZE; + break; + case 'O': + loadopt |= PFCTL_FLAG_OPTION; + break; + case 'p': + pf_device = optarg; + break; + case 'P': + opts |= PF_OPT_NUMERIC; + break; + case 's': + showopt = pfctl_lookup_option(optarg, showopt_list); + if (showopt == NULL) { + warnx("Unknown show modifier '%s'", optarg); + usage(); + } + break; + case 'S': + opts |= PF_OPT_NODNS; + break; + case 't': + tableopt = optarg; + break; + case 'T': + tblcmdopt = pfctl_lookup_option(optarg, tblcmdopt_list); + if (tblcmdopt == NULL) { + warnx("Unknown table command '%s'", optarg); + usage(); + } + break; + case 'v': + if (opts & PF_OPT_VERBOSE) + opts |= PF_OPT_VERBOSE2; + opts |= PF_OPT_VERBOSE; + break; + case 'x': + debugopt = pfctl_lookup_option(optarg, debugopt_list); + if (debugopt == NULL) { + warnx("Unknown debug level '%s'", optarg); + usage(); + } + mode = O_RDWR; + break; + case 'z': + opts |= PF_OPT_CLRRULECTRS; + mode = O_RDWR; + break; + case 'h': + /* FALLTHROUGH */ + default: + usage(); + /* NOTREACHED */ + } + } + + if ((opts & PF_OPT_NODNS) && (opts & PF_OPT_USEDNS)) + errx(1, "-N and -r are mutually exclusive"); + + if ((tblcmdopt == NULL) ^ (tableopt == NULL)) + usage(); + + if (tblcmdopt != NULL) { + argc -= optind; + argv += optind; + ch = *tblcmdopt; + if (ch == 'l') { + loadopt |= PFCTL_FLAG_TABLE; + tblcmdopt = NULL; + } else + mode = strchr("st", ch) ? O_RDONLY : O_RDWR; + } else if (argc != optind) { + warnx("unknown command line argument: %s ...", argv[optind]); + usage(); + /* NOTREACHED */ + } + if (loadopt == 0) + loadopt = ~0; + + memset(anchorname, 0, sizeof(anchorname)); + if (anchoropt != NULL) { + int len = strlen(anchoropt); + + if (anchoropt[0] == '\0') + errx(1, "anchor name must not be empty"); + if (mode == O_RDONLY && showopt == NULL && tblcmdopt == NULL) { + warnx("anchors apply to -f, -F, -s, and -T only"); + usage(); + } + if (mode == O_RDWR && tblcmdopt == NULL && + (anchoropt[0] == '_' || strstr(anchoropt, "/_") != NULL)) + errx(1, "anchor names beginning with '_' cannot " + "be modified from the command line"); + + if (len >= 1 && anchoropt[len - 1] == '*') { + if (len >= 2 && anchoropt[len - 2] == '/') + anchoropt[len - 2] = '\0'; + else + anchoropt[len - 1] = '\0'; + opts |= PF_OPT_RECURSE; + } + if (strlcpy(anchorname, anchoropt, + sizeof(anchorname)) >= sizeof(anchorname)) + errx(1, "anchor name '%s' too long", + anchoropt); + loadopt &= PFCTL_FLAG_FILTER|PFCTL_FLAG_NAT|PFCTL_FLAG_TABLE|PFCTL_FLAG_ETH; + } + + if ((opts & PF_OPT_NOACTION) == 0) { + dev = open(pf_device, mode); + if (dev == -1) + err(1, "%s", pf_device); + altqsupport = pfctl_test_altqsupport(dev, opts); + } else { + dev = open(pf_device, O_RDONLY); + if (dev >= 0) + opts |= PF_OPT_DUMMYACTION; + /* turn off options */ + opts &= ~ (PF_OPT_DISABLE | PF_OPT_ENABLE); + clearopt = showopt = debugopt = NULL; +#if !defined(ENABLE_ALTQ) + altqsupport = 0; +#else + altqsupport = 1; +#endif + } + pfh = pfctl_open(pf_device); + if (pfh == NULL) + err(1, "Failed to open netlink"); + + if ((opts & PF_OPT_NOACTION) == 0) { + pfctl_read_limits(pfh); + } + + if (opts & PF_OPT_DISABLE) + if (pfctl_disable(dev, opts)) + exit_val = 1; + + if ((path = calloc(1, MAXPATHLEN)) == NULL) + errx(1, "%s: calloc", __func__); + + if (showopt != NULL) { + switch (*showopt) { + case 'A': + pfctl_show_anchors(dev, opts, anchorname); + if (opts & PF_OPT_VERBOSE2) + printf("Ethernet:\n"); + pfctl_show_eth_anchors(dev, opts, anchorname); + break; + case 'r': + pfctl_load_fingerprints(dev, opts); + pfctl_show_rules(dev, path, opts, PFCTL_SHOW_RULES, + anchorname, 0, 0); + break; + case 'l': + pfctl_load_fingerprints(dev, opts); + pfctl_show_rules(dev, path, opts, PFCTL_SHOW_LABELS, + anchorname, 0, 0); + break; + case 'n': + pfctl_load_fingerprints(dev, opts); + pfctl_show_nat(dev, path, opts, anchorname, 0, 0); + break; + case 'q': + pfctl_show_altq(dev, ifaceopt, opts, + opts & PF_OPT_VERBOSE2); + break; + case 's': + pfctl_show_states(dev, ifaceopt, opts); + break; + case 'S': + pfctl_show_src_nodes(dev, opts); + break; + case 'i': + pfctl_show_status(dev, opts); + break; + case 'R': + exit_val = pfctl_show_running(dev); + break; + case 't': + pfctl_show_timeouts(dev, opts); + break; + case 'm': + pfctl_show_limits(dev, opts); + break; + case 'e': + pfctl_show_eth_rules(dev, path, opts, 0, anchorname, 0, + 0); + break; + case 'a': + opts |= PF_OPT_SHOWALL; + pfctl_load_fingerprints(dev, opts); + + pfctl_show_eth_rules(dev, path, opts, 0, anchorname, 0, + 0); + + pfctl_show_nat(dev, path, opts, anchorname, 0, 0); + pfctl_show_rules(dev, path, opts, PFCTL_SHOW_RULES, + anchorname, 0, 0); + pfctl_show_altq(dev, ifaceopt, opts, 0); + pfctl_show_states(dev, ifaceopt, opts); + pfctl_show_src_nodes(dev, opts); + pfctl_show_status(dev, opts); + pfctl_show_rules(dev, path, opts, PFCTL_SHOW_LABELS, + anchorname, 0, 0); + pfctl_show_timeouts(dev, opts); + pfctl_show_limits(dev, opts); + pfctl_show_tables(anchorname, opts); + pfctl_show_fingerprints(opts); + break; + case 'T': + if (opts & PF_OPT_RECURSE) { + opts |= PF_OPT_CALLSHOW; + pfctl_recurse(dev, opts, anchorname, + pfctl_call_showtables); + } else + pfctl_show_tables(anchorname, opts); + break; + case 'o': + pfctl_load_fingerprints(dev, opts); + pfctl_show_fingerprints(opts); + break; + case 'I': + pfctl_show_ifaces(ifaceopt, opts); + break; + case 'c': + pfctl_show_creators(opts); + break; + } + } + + if ((opts & PF_OPT_CLRRULECTRS) && showopt == NULL) { + pfctl_show_eth_rules(dev, path, opts, PFCTL_SHOW_NOTHING, + anchorname, 0, 0); + pfctl_show_rules(dev, path, opts, PFCTL_SHOW_NOTHING, + anchorname, 0, 0); + } + + if (clearopt != NULL) { + int mnr; + + /* Check if anchor exists. */ + if ((pfctl_get_rulesets(pfh, anchorname, &mnr)) == ENOENT) + errx(1, "No such anchor %s", anchorname); + + switch (*clearopt) { + case 'e': + pfctl_flush_eth_rules(dev, opts, anchorname); + break; + case 'r': + if (opts & PF_OPT_RECURSE) + pfctl_recurse(dev, opts, anchorname, + pfctl_call_clearrules); + else + pfctl_flush_rules(dev, opts, anchorname); + break; + case 'n': + pfctl_flush_nat(dev, opts, anchorname); + break; + case 'q': + pfctl_clear_altq(dev, opts); + break; + case 's': + pfctl_clear_iface_states(dev, ifaceopt, opts); + break; + case 'S': + pfctl_clear_src_nodes(dev, opts); + break; + case 'i': + pfctl_clear_stats(pfh, opts); + break; + case 'a': + if (ifaceopt) { + warnx("don't specify an interface with -Fall"); + usage(); + /* NOTREACHED */ + } + pfctl_flush_eth_rules(dev, opts, anchorname); + pfctl_flush_rules(dev, opts, anchorname); + pfctl_flush_nat(dev, opts, anchorname); + if (opts & PF_OPT_RECURSE) + pfctl_recurse(dev, opts, anchorname, + pfctl_call_clearanchors); + else { + pfctl_do_clear_tables(anchorname, opts); + pfctl_flush_rules(dev, opts, anchorname); + } + if (!*anchorname) { + pfctl_clear_altq(dev, opts); + pfctl_clear_iface_states(dev, ifaceopt, opts); + pfctl_clear_src_nodes(dev, opts); + pfctl_clear_stats(pfh, opts); + pfctl_clear_fingerprints(dev, opts); + pfctl_reset(dev, opts); + } + break; + case 'o': + pfctl_clear_fingerprints(dev, opts); + break; + case 'T': + if ((opts & PF_OPT_RECURSE) == 0) + pfctl_do_clear_tables(anchorname, opts); + else + pfctl_recurse(dev, opts, anchorname, + pfctl_call_cleartables); + break; + case 'R': + pfctl_reset(dev, opts); + break; + } + } + if (state_killers) { + if (!strcmp(state_kill[0], "label")) + pfctl_label_kill_states(dev, ifaceopt, opts); + else if (!strcmp(state_kill[0], "id")) + pfctl_id_kill_states(dev, ifaceopt, opts); + else if (!strcmp(state_kill[0], "gateway")) + pfctl_gateway_kill_states(dev, ifaceopt, opts); + else if (!strcmp(state_kill[0], "key")) + pfctl_key_kill_states(dev, ifaceopt, opts); + else + pfctl_net_kill_states(dev, ifaceopt, opts); + } + + if (src_node_killers) + pfctl_kill_src_nodes(dev, opts); + + if (tblcmdopt != NULL) { + exit_val = pfctl_table(argc, argv, tableopt, + tblcmdopt, rulesopt, anchorname, opts); + rulesopt = NULL; + } + if (optiopt != NULL) { + switch (*optiopt) { + case 'n': + optimize = 0; + break; + case 'b': + optimize |= PF_OPTIMIZE_BASIC; + break; + case 'o': + case 'p': + optimize |= PF_OPTIMIZE_PROFILE; + break; + } + } + + if ((rulesopt != NULL) && (loadopt & PFCTL_FLAG_OPTION) && + !anchorname[0] && !(opts & PF_OPT_NOACTION)) + pfctl_get_skip_ifaces(); + + if (rulesopt != NULL && !(opts & PF_OPT_MERGE) && + !anchorname[0] && (loadopt & PFCTL_FLAG_OPTION)) + if (pfctl_file_fingerprints(dev, opts, PF_OSFP_FILE)) + exit_val = 1; + + if (rulesopt != NULL) { + if (pfctl_rules(dev, rulesopt, opts, optimize, + anchorname, NULL)) + exit_val = 1; + } + + if (opts & PF_OPT_ENABLE) + if (pfctl_enable(dev, opts)) + exit_val = 1; + + if (debugopt != NULL) { + switch (*debugopt) { + case 'n': + pfctl_debug(dev, PF_DEBUG_NONE, opts); + break; + case 'u': + pfctl_debug(dev, PF_DEBUG_URGENT, opts); + break; + case 'm': + pfctl_debug(dev, PF_DEBUG_MISC, opts); + break; + case 'l': + pfctl_debug(dev, PF_DEBUG_NOISY, opts); + break; + } + } + + /* + * prevent pfctl_restore_limits() exit handler from restoring + * pf(4) options settings on successful exit. + */ + if (exit_val == 0) { + close(dev); + dev = -1; + pfctl_close(pfh); + pfh = NULL; + } + + return (exit_val); +} + +char * +pf_strerror(int errnum) +{ + switch (errnum) { + case ESRCH: + return "Table does not exist."; + case EINVAL: + case ENOENT: + return "Anchor does not exist."; + default: + return strerror(errnum); + } +} diff --git a/sbin/pfctl/pfctl.h b/sbin/pfctl/pfctl.h new file mode 100644 index 000000000000..c540c6348d84 --- /dev/null +++ b/sbin/pfctl/pfctl.h @@ -0,0 +1,202 @@ +/* $OpenBSD: pfctl.h,v 1.42 2007/12/05 12:01:47 chl Exp $ */ + +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2001 Daniel Hartmeier + * 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. + */ + +#ifndef _PFCTL_H_ +#define _PFCTL_H_ + +#include <libpfctl.h> + +#ifdef PFCTL_DEBUG +#define DBGPRINT(...) fprintf(stderr, __VA_ARGS__) +#else +#define DBGPRINT(...) (void)(0) +#endif + +extern struct pfctl_handle *pfh; + +struct pfctl; + +enum pfctl_show { PFCTL_SHOW_RULES, PFCTL_SHOW_LABELS, PFCTL_SHOW_NOTHING }; + +enum { PFRB_TABLES = 1, PFRB_TSTATS, PFRB_ADDRS, PFRB_ASTATS, + PFRB_IFACES, PFRB_TRANS, PFRB_MAX }; +struct pfr_buffer { + int pfrb_type; /* type of content, see enum above */ + int pfrb_size; /* number of objects in buffer */ + int pfrb_msize; /* maximum number of objects in buffer */ + void *pfrb_caddr; /* malloc'ated memory area */ +}; +#define PFRB_FOREACH(var, buf) \ + for ((var) = pfr_buf_next((buf), NULL); \ + (var) != NULL; \ + (var) = pfr_buf_next((buf), (var))) + +RB_HEAD(pfr_ktablehead, pfr_ktable); +struct pfr_ktable { + struct pfr_tstats pfrkt_ts; + RB_ENTRY(pfr_ktable) pfrkt_tree; + SLIST_ENTRY(pfr_ktable) pfrkt_workq; + struct radix_node_head *pfrkt_ip4; + struct radix_node_head *pfrkt_ip6; + struct pfr_ktable *pfrkt_shadow; + struct pfr_ktable *pfrkt_root; + struct pf_kruleset *pfrkt_rs; + long pfrkt_larg; + int pfrkt_nflags; +}; +#define pfrkt_t pfrkt_ts.pfrts_t +#define pfrkt_name pfrkt_t.pfrt_name +#define pfrkt_anchor pfrkt_t.pfrt_anchor +#define pfrkt_ruleset pfrkt_t.pfrt_ruleset +#define pfrkt_flags pfrkt_t.pfrt_flags +#define pfrkt_cnt pfrkt_kts.pfrkts_cnt +#define pfrkt_refcnt pfrkt_kts.pfrkts_refcnt +#define pfrkt_tzero pfrkt_kts.pfrkts_tzero + +struct pfr_uktable { + struct pfr_ktable pfrukt_kt; + struct pfr_buffer pfrukt_addrs; + int pfrukt_init_addr; + SLIST_ENTRY(pfr_uktable) pfrukt_entry; +}; + +#define pfrukt_t pfrukt_kt.pfrkt_ts.pfrts_t +#define pfrukt_name pfrukt_kt.pfrkt_t.pfrt_name +#define pfrukt_anchor pfrukt_kt.pfrkt_t.pfrt_anchor + +extern struct pfr_ktablehead pfr_ktables; + +struct pfr_anchoritem { + SLIST_ENTRY(pfr_anchoritem) pfra_sle; + char *pfra_anchorname; +}; + +SLIST_HEAD(pfr_anchors, pfr_anchoritem); + +int pfr_add_table(struct pfr_table *, int *, int); +int pfr_del_table(struct pfr_table *, int *, int); +int pfr_get_tables(struct pfr_table *, struct pfr_table *, int *, int); +int pfr_clr_astats(struct pfr_table *, struct pfr_addr *, int, int *, int); +int pfr_clr_addrs(struct pfr_table *, int *, int); +int pfr_add_addrs(struct pfr_table *, struct pfr_addr *, int, int *, int); +int pfr_del_addrs(struct pfr_table *, struct pfr_addr *, int, int *, int); +int pfr_set_addrs(struct pfr_table *, struct pfr_addr *, int, + int *, int *, int *, int); +int pfr_get_addrs(struct pfr_table *, struct pfr_addr *, int *, int); +int pfr_get_astats(struct pfr_table *, struct pfr_astats *, int *, int); +int pfr_tst_addrs(struct pfr_table *, struct pfr_addr *, int, int *, int); +int pfr_ina_define(struct pfr_table *, struct pfr_addr *, int, int *, + int *, int, int); +void pfr_buf_clear(struct pfr_buffer *); +int pfr_buf_add(struct pfr_buffer *, const void *); +void *pfr_buf_next(struct pfr_buffer *, const void *); +int pfr_buf_grow(struct pfr_buffer *, int); +int pfr_buf_load(struct pfr_buffer *, char *, int, + int (*)(struct pfr_buffer *, char *, int, int), int); +char *pf_strerror(int); +int pfi_get_ifaces(const char *, struct pfi_kif *, int *); + +void pfctl_print_title(char *); +int pfctl_do_clear_tables(const char *, int); +void pfctl_show_tables(const char *, int); +int pfctl_table(int, char *[], char *, const char *, char *, + const char *, int); +int pfctl_show_altq(int, const char *, int, int); +void warn_duplicate_tables(const char *, const char *); +void pfctl_show_ifaces(const char *, int); +void pfctl_show_creators(int); +FILE *pfctl_fopen(const char *, const char *); + +#ifdef __FreeBSD__ +extern int altqsupport; +extern int dummynetsupport; +#define HTONL(x) (x) = htonl((__uint32_t)(x)) +#endif + +#ifndef DEFAULT_PRIORITY +#define DEFAULT_PRIORITY 1 +#endif + +#ifndef DEFAULT_QLIMIT +#define DEFAULT_QLIMIT 50 +#endif + +/* + * generalized service curve used for admission control + */ +struct segment { + LIST_ENTRY(segment) _next; + double x, y, d, m; +}; + +extern int loadopt; + +int check_commit_altq(int, int); +void pfaltq_store(struct pf_altq *); +char *rate2str(double); + +void print_addr(struct pf_addr_wrap *, sa_family_t, int); +void print_addr_str(sa_family_t, struct pf_addr *); +void print_host(struct pf_addr *, u_int16_t p, sa_family_t, int); +void print_seq(struct pfctl_state_peer *); +void print_state(struct pfctl_state *, int); + +int pfctl_cmdline_symset(char *); +int pfctl_add_trans(struct pfr_buffer *, int, const char *); +u_int32_t + pfctl_get_ticket(struct pfr_buffer *, int, const char *); +int pfctl_trans(int, struct pfr_buffer *, u_long, int); + +int pf_get_ruleset_number(u_int8_t); +void pf_init_ruleset(struct pfctl_ruleset *); +int pfctl_anchor_setup(struct pfctl_rule *, + const struct pfctl_ruleset *, const char *); +void pf_remove_if_empty_ruleset(struct pfctl_ruleset *); +struct pfctl_ruleset *pf_find_ruleset(const char *); +struct pfctl_ruleset *pf_find_or_create_ruleset(const char *); +void pf_init_eth_ruleset(struct pfctl_eth_ruleset *); +int pfctl_eth_anchor_setup(struct pfctl *, + struct pfctl_eth_rule *, + const struct pfctl_eth_ruleset *, const char *); +struct pfctl_eth_ruleset *pf_find_or_create_eth_ruleset(const char *); +void pf_remove_if_empty_eth_ruleset( + struct pfctl_eth_ruleset *); + +void expand_label(char *, size_t, struct pfctl_rule *); + +const char *pfctl_proto2name(int); + +void pfctl_err(int, int, const char *, ...); +void pfctl_errx(int, int, const char *, ...); + +#endif /* _PFCTL_H_ */ diff --git a/sbin/pfctl/pfctl_altq.c b/sbin/pfctl/pfctl_altq.c new file mode 100644 index 000000000000..1b32e90aea10 --- /dev/null +++ b/sbin/pfctl/pfctl_altq.c @@ -0,0 +1,1437 @@ +/* $OpenBSD: pfctl_altq.c,v 1.93 2007/10/15 02:16:35 deraadt Exp $ */ + +/* + * Copyright (c) 2002 + * Sony Computer Science Laboratories Inc. + * Copyright (c) 2002, 2003 Henning Brauer <henning@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/cdefs.h> +#define PFIOC_USE_LATEST +#define _WANT_FREEBSD_BITSET + +#include <sys/types.h> +#include <sys/bitset.h> +#include <sys/ioctl.h> +#include <sys/socket.h> + +#include <net/if.h> +#include <netinet/in.h> +#include <net/pfvar.h> + +#include <err.h> +#include <errno.h> +#include <inttypes.h> +#include <limits.h> +#include <math.h> +#include <search.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <net/altq/altq.h> +#include <net/altq/altq_cbq.h> +#include <net/altq/altq_codel.h> +#include <net/altq/altq_priq.h> +#include <net/altq/altq_hfsc.h> +#include <net/altq/altq_fairq.h> + +#include "pfctl_parser.h" +#include "pfctl.h" + +#define is_sc_null(sc) (((sc) == NULL) || ((sc)->m1 == 0 && (sc)->m2 == 0)) + +static STAILQ_HEAD(interfaces, pfctl_altq) interfaces = STAILQ_HEAD_INITIALIZER(interfaces); +static struct hsearch_data queue_map; +static struct hsearch_data if_map; +static struct hsearch_data qid_map; + +static struct pfctl_altq *pfaltq_lookup(char *ifname); +static struct pfctl_altq *qname_to_pfaltq(const char *, const char *); +static u_int32_t qname_to_qid(char *); + +static int eval_pfqueue_cbq(struct pfctl *, struct pf_altq *, + struct pfctl_altq *); +static int cbq_compute_idletime(struct pfctl *, struct pf_altq *); +static int check_commit_cbq(int, int, struct pfctl_altq *); +static int print_cbq_opts(const struct pf_altq *); + +static int print_codel_opts(const struct pf_altq *, + const struct node_queue_opt *); + +static int eval_pfqueue_priq(struct pfctl *, struct pf_altq *, + struct pfctl_altq *); +static int check_commit_priq(int, int, struct pfctl_altq *); +static int print_priq_opts(const struct pf_altq *); + +static int eval_pfqueue_hfsc(struct pfctl *, struct pf_altq *, + struct pfctl_altq *, struct pfctl_altq *); +static int check_commit_hfsc(int, int, struct pfctl_altq *); +static int print_hfsc_opts(const struct pf_altq *, + const struct node_queue_opt *); + +static int eval_pfqueue_fairq(struct pfctl *, struct pf_altq *, + struct pfctl_altq *, struct pfctl_altq *); +static int print_fairq_opts(const struct pf_altq *, + const struct node_queue_opt *); +static int check_commit_fairq(int, int, struct pfctl_altq *); + +static void gsc_add_sc(struct gen_sc *, struct service_curve *); +static int is_gsc_under_sc(struct gen_sc *, + struct service_curve *); +static struct segment *gsc_getentry(struct gen_sc *, double); +static int gsc_add_seg(struct gen_sc *, double, double, double, + double); +static double sc_x2y(struct service_curve *, double); + +u_int32_t getifspeed(char *); +u_long getifmtu(char *); +int eval_queue_opts(struct pf_altq *, struct node_queue_opt *, + u_int64_t); +u_int64_t eval_bwspec(struct node_queue_bw *, u_int64_t); +void print_hfsc_sc(const char *, u_int, u_int, u_int, + const struct node_hfsc_sc *); +void print_fairq_sc(const char *, u_int, u_int, u_int, + const struct node_fairq_sc *); + +static __attribute__((constructor)) void +pfctl_altq_init(void) +{ + /* + * As hdestroy() will never be called on these tables, it will be + * safe to use references into the stored data as keys. + */ + if (hcreate_r(0, &queue_map) == 0) + err(1, "Failed to create altq queue map"); + if (hcreate_r(0, &if_map) == 0) + err(1, "Failed to create altq interface map"); + if (hcreate_r(0, &qid_map) == 0) + err(1, "Failed to create altq queue id map"); +} + +void +pfaltq_store(struct pf_altq *a) +{ + struct pfctl_altq *altq; + ENTRY item; + ENTRY *ret_item; + size_t key_size; + + if ((altq = malloc(sizeof(*altq))) == NULL) + err(1, "queue malloc"); + memcpy(&altq->pa, a, sizeof(struct pf_altq)); + memset(&altq->meta, 0, sizeof(altq->meta)); + + if (a->qname[0] == 0) { + item.key = altq->pa.ifname; + item.data = altq; + if (hsearch_r(item, ENTER, &ret_item, &if_map) == 0) + err(1, "interface map insert"); + STAILQ_INSERT_TAIL(&interfaces, altq, meta.link); + } else { + key_size = sizeof(a->ifname) + sizeof(a->qname); + if ((item.key = malloc(key_size)) == NULL) + err(1, "queue map key malloc"); + snprintf(item.key, key_size, "%s:%s", a->ifname, a->qname); + item.data = altq; + if (hsearch_r(item, ENTER, &ret_item, &queue_map) == 0) + err(1, "queue map insert"); + + item.key = altq->pa.qname; + item.data = &altq->pa.qid; + if (hsearch_r(item, ENTER, &ret_item, &qid_map) == 0) + err(1, "qid map insert"); + } +} + +static struct pfctl_altq * +pfaltq_lookup(char *ifname) +{ + ENTRY item; + ENTRY *ret_item; + + item.key = ifname; + if (hsearch_r(item, FIND, &ret_item, &if_map) == 0) + return (NULL); + + return (ret_item->data); +} + +static struct pfctl_altq * +qname_to_pfaltq(const char *qname, const char *ifname) +{ + ENTRY item; + ENTRY *ret_item; + char key[IFNAMSIZ + PF_QNAME_SIZE]; + + item.key = key; + snprintf(item.key, sizeof(key), "%s:%s", ifname, qname); + if (hsearch_r(item, FIND, &ret_item, &queue_map) == 0) + return (NULL); + + return (ret_item->data); +} + +static u_int32_t +qname_to_qid(char *qname) +{ + ENTRY item; + ENTRY *ret_item; + uint32_t qid; + + /* + * We guarantee that same named queues on different interfaces + * have the same qid. + */ + item.key = qname; + if (hsearch_r(item, FIND, &ret_item, &qid_map) == 0) + return (0); + + qid = *(uint32_t *)ret_item->data; + return (qid); +} + +void +print_altq(const struct pf_altq *a, unsigned int level, + struct node_queue_bw *bw, struct node_queue_opt *qopts) +{ + if (a->qname[0] != 0) { + print_queue(a, level, bw, 1, qopts); + return; + } + +#ifdef __FreeBSD__ + if (a->local_flags & PFALTQ_FLAG_IF_REMOVED) + printf("INACTIVE "); +#endif + + printf("altq on %s ", a->ifname); + + switch (a->scheduler) { + case ALTQT_CBQ: + if (!print_cbq_opts(a)) + printf("cbq "); + break; + case ALTQT_PRIQ: + if (!print_priq_opts(a)) + printf("priq "); + break; + case ALTQT_HFSC: + if (!print_hfsc_opts(a, qopts)) + printf("hfsc "); + break; + case ALTQT_FAIRQ: + if (!print_fairq_opts(a, qopts)) + printf("fairq "); + break; + case ALTQT_CODEL: + if (!print_codel_opts(a, qopts)) + printf("codel "); + break; + } + + if (bw != NULL && bw->bw_percent > 0) { + if (bw->bw_percent < 100) + printf("bandwidth %u%% ", bw->bw_percent); + } else + printf("bandwidth %s ", rate2str((double)a->ifbandwidth)); + + if (a->qlimit != DEFAULT_QLIMIT) + printf("qlimit %u ", a->qlimit); + printf("tbrsize %u ", a->tbrsize); +} + +void +print_queue(const struct pf_altq *a, unsigned int level, + struct node_queue_bw *bw, int print_interface, + struct node_queue_opt *qopts) +{ + unsigned int i; + +#ifdef __FreeBSD__ + if (a->local_flags & PFALTQ_FLAG_IF_REMOVED) + printf("INACTIVE "); +#endif + printf("queue "); + for (i = 0; i < level; ++i) + printf(" "); + printf("%s ", a->qname); + if (print_interface) + printf("on %s ", a->ifname); + if (a->scheduler == ALTQT_CBQ || a->scheduler == ALTQT_HFSC || + a->scheduler == ALTQT_FAIRQ) { + if (bw != NULL && bw->bw_percent > 0) { + if (bw->bw_percent < 100) + printf("bandwidth %u%% ", bw->bw_percent); + } else + printf("bandwidth %s ", rate2str((double)a->bandwidth)); + } + if (a->priority != DEFAULT_PRIORITY) + printf("priority %u ", a->priority); + if (a->qlimit != DEFAULT_QLIMIT) + printf("qlimit %u ", a->qlimit); + switch (a->scheduler) { + case ALTQT_CBQ: + print_cbq_opts(a); + break; + case ALTQT_PRIQ: + print_priq_opts(a); + break; + case ALTQT_HFSC: + print_hfsc_opts(a, qopts); + break; + case ALTQT_FAIRQ: + print_fairq_opts(a, qopts); + break; + } +} + +/* + * eval_pfaltq computes the discipline parameters. + */ +int +eval_pfaltq(struct pfctl *pf, struct pf_altq *pa, struct node_queue_bw *bw, + struct node_queue_opt *opts) +{ + u_int64_t rate; + u_int size, errors = 0; + + if (bw->bw_absolute > 0) + pa->ifbandwidth = bw->bw_absolute; + else + if ((rate = getifspeed(pa->ifname)) == 0) { + fprintf(stderr, "interface %s does not know its bandwidth, " + "please specify an absolute bandwidth\n", + pa->ifname); + errors++; + } else if ((pa->ifbandwidth = eval_bwspec(bw, rate)) == 0) + pa->ifbandwidth = rate; + + /* + * Limit bandwidth to UINT_MAX for schedulers that aren't 64-bit ready. + */ + if ((pa->scheduler != ALTQT_HFSC) && (pa->ifbandwidth > UINT_MAX)) { + pa->ifbandwidth = UINT_MAX; + warnx("interface %s bandwidth limited to %" PRIu64 " bps " + "because selected scheduler is 32-bit limited\n", pa->ifname, + pa->ifbandwidth); + } + errors += eval_queue_opts(pa, opts, pa->ifbandwidth); + + /* if tbrsize is not specified, use heuristics */ + if (pa->tbrsize == 0) { + rate = pa->ifbandwidth; + if (rate <= 1 * 1000 * 1000) + size = 1; + else if (rate <= 10 * 1000 * 1000) + size = 4; + else if (rate <= 200 * 1000 * 1000) + size = 8; + else if (rate <= 2500 * 1000 * 1000ULL) + size = 24; + else + size = 128; + size = size * getifmtu(pa->ifname); + pa->tbrsize = size; + } + return (errors); +} + +/* + * check_commit_altq does consistency check for each interface + */ +int +check_commit_altq(int dev, int opts) +{ + struct pfctl_altq *if_ppa; + int error = 0; + + /* call the discipline check for each interface. */ + STAILQ_FOREACH(if_ppa, &interfaces, meta.link) { + switch (if_ppa->pa.scheduler) { + case ALTQT_CBQ: + error = check_commit_cbq(dev, opts, if_ppa); + break; + case ALTQT_PRIQ: + error = check_commit_priq(dev, opts, if_ppa); + break; + case ALTQT_HFSC: + error = check_commit_hfsc(dev, opts, if_ppa); + break; + case ALTQT_FAIRQ: + error = check_commit_fairq(dev, opts, if_ppa); + break; + default: + break; + } + } + return (error); +} + +/* + * eval_pfqueue computes the queue parameters. + */ +int +eval_pfqueue(struct pfctl *pf, struct pf_altq *pa, struct node_queue_bw *bw, + struct node_queue_opt *opts) +{ + /* should be merged with expand_queue */ + struct pfctl_altq *if_ppa, *parent; + int error = 0; + + /* find the corresponding interface and copy fields used by queues */ + if ((if_ppa = pfaltq_lookup(pa->ifname)) == NULL) { + fprintf(stderr, "altq not defined on %s\n", pa->ifname); + return (1); + } + pa->scheduler = if_ppa->pa.scheduler; + pa->ifbandwidth = if_ppa->pa.ifbandwidth; + + if (qname_to_pfaltq(pa->qname, pa->ifname) != NULL) { + fprintf(stderr, "queue %s already exists on interface %s\n", + pa->qname, pa->ifname); + return (1); + } + pa->qid = qname_to_qid(pa->qname); + + parent = NULL; + if (pa->parent[0] != 0) { + parent = qname_to_pfaltq(pa->parent, pa->ifname); + if (parent == NULL) { + fprintf(stderr, "parent %s not found for %s\n", + pa->parent, pa->qname); + return (1); + } + pa->parent_qid = parent->pa.qid; + } + if (pa->qlimit == 0) + pa->qlimit = DEFAULT_QLIMIT; + + if (pa->scheduler == ALTQT_CBQ || pa->scheduler == ALTQT_HFSC || + pa->scheduler == ALTQT_FAIRQ) { + pa->bandwidth = eval_bwspec(bw, + parent == NULL ? pa->ifbandwidth : parent->pa.bandwidth); + + if (pa->bandwidth > pa->ifbandwidth) { + fprintf(stderr, "bandwidth for %s higher than " + "interface\n", pa->qname); + return (1); + } + /* + * If not HFSC, then check that the sum of the child + * bandwidths is less than the parent's bandwidth. For + * HFSC, the equivalent concept is to check that the sum of + * the child linkshare service curves are under the parent's + * linkshare service curve, and that check is performed by + * eval_pfqueue_hfsc(). + */ + if ((parent != NULL) && (pa->scheduler != ALTQT_HFSC)) { + if (pa->bandwidth > parent->pa.bandwidth) { + warnx("bandwidth for %s higher than parent", + pa->qname); + return (1); + } + parent->meta.bwsum += pa->bandwidth; + if (parent->meta.bwsum > parent->pa.bandwidth) { + warnx("the sum of the child bandwidth (%" PRIu64 + ") higher than parent \"%s\" (%" PRIu64 ")", + parent->meta.bwsum, parent->pa.qname, + parent->pa.bandwidth); + } + } + } + + if (eval_queue_opts(pa, opts, + parent == NULL ? pa->ifbandwidth : parent->pa.bandwidth)) + return (1); + + if (parent != NULL) + parent->meta.children++; + + switch (pa->scheduler) { + case ALTQT_CBQ: + error = eval_pfqueue_cbq(pf, pa, if_ppa); + break; + case ALTQT_PRIQ: + error = eval_pfqueue_priq(pf, pa, if_ppa); + break; + case ALTQT_HFSC: + error = eval_pfqueue_hfsc(pf, pa, if_ppa, parent); + break; + case ALTQT_FAIRQ: + error = eval_pfqueue_fairq(pf, pa, if_ppa, parent); + break; + default: + break; + } + return (error); +} + +/* + * CBQ support functions + */ +#define RM_FILTER_GAIN 5 /* log2 of gain, e.g., 5 => 31/32 */ +#define RM_NS_PER_SEC (1000000000) + +static int +eval_pfqueue_cbq(struct pfctl *pf, struct pf_altq *pa, struct pfctl_altq *if_ppa) +{ + struct cbq_opts *opts; + u_int ifmtu; + + if (pa->priority >= CBQ_MAXPRI) { + warnx("priority out of range: max %d", CBQ_MAXPRI - 1); + return (-1); + } + + ifmtu = getifmtu(pa->ifname); + opts = &pa->pq_u.cbq_opts; + + if (opts->pktsize == 0) { /* use default */ + opts->pktsize = ifmtu; + if (opts->pktsize > MCLBYTES) /* do what TCP does */ + opts->pktsize &= ~MCLBYTES; + } else if (opts->pktsize > ifmtu) + opts->pktsize = ifmtu; + if (opts->maxpktsize == 0) /* use default */ + opts->maxpktsize = ifmtu; + else if (opts->maxpktsize > ifmtu) + opts->pktsize = ifmtu; + + if (opts->pktsize > opts->maxpktsize) + opts->pktsize = opts->maxpktsize; + + if (pa->parent[0] == 0) + opts->flags |= (CBQCLF_ROOTCLASS | CBQCLF_WRR); + + if (pa->pq_u.cbq_opts.flags & CBQCLF_ROOTCLASS) + if_ppa->meta.root_classes++; + if (pa->pq_u.cbq_opts.flags & CBQCLF_DEFCLASS) + if_ppa->meta.default_classes++; + + cbq_compute_idletime(pf, pa); + return (0); +} + +/* + * compute ns_per_byte, maxidle, minidle, and offtime + */ +static int +cbq_compute_idletime(struct pfctl *pf, struct pf_altq *pa) +{ + struct cbq_opts *opts; + double maxidle_s, maxidle, minidle; + double offtime, nsPerByte, ifnsPerByte, ptime, cptime; + double z, g, f, gton, gtom; + u_int minburst, maxburst; + + opts = &pa->pq_u.cbq_opts; + ifnsPerByte = (1.0 / (double)pa->ifbandwidth) * RM_NS_PER_SEC * 8; + minburst = opts->minburst; + maxburst = opts->maxburst; + + if (pa->bandwidth == 0) + f = 0.0001; /* small enough? */ + else + f = ((double) pa->bandwidth / (double) pa->ifbandwidth); + + nsPerByte = ifnsPerByte / f; + ptime = (double)opts->pktsize * ifnsPerByte; + cptime = ptime * (1.0 - f) / f; + + if (nsPerByte * (double)opts->maxpktsize > (double)INT_MAX) { + /* + * this causes integer overflow in kernel! + * (bandwidth < 6Kbps when max_pkt_size=1500) + */ + if (pa->bandwidth != 0 && (pf->opts & PF_OPT_QUIET) == 0) { + warnx("queue bandwidth must be larger than %s", + rate2str(ifnsPerByte * (double)opts->maxpktsize / + (double)INT_MAX * (double)pa->ifbandwidth)); + fprintf(stderr, "cbq: queue %s is too slow!\n", + pa->qname); + } + nsPerByte = (double)(INT_MAX / opts->maxpktsize); + } + + if (maxburst == 0) { /* use default */ + if (cptime > 10.0 * 1000000) + maxburst = 4; + else + maxburst = 16; + } + if (minburst == 0) /* use default */ + minburst = 2; + if (minburst > maxburst) + minburst = maxburst; + + z = (double)(1 << RM_FILTER_GAIN); + g = (1.0 - 1.0 / z); + gton = pow(g, (double)maxburst); + gtom = pow(g, (double)(minburst-1)); + maxidle = ((1.0 / f - 1.0) * ((1.0 - gton) / gton)); + maxidle_s = (1.0 - g); + if (maxidle > maxidle_s) + maxidle = ptime * maxidle; + else + maxidle = ptime * maxidle_s; + offtime = cptime * (1.0 + 1.0/(1.0 - g) * (1.0 - gtom) / gtom); + minidle = -((double)opts->maxpktsize * (double)nsPerByte); + + /* scale parameters */ + maxidle = ((maxidle * 8.0) / nsPerByte) * + pow(2.0, (double)RM_FILTER_GAIN); + offtime = (offtime * 8.0) / nsPerByte * + pow(2.0, (double)RM_FILTER_GAIN); + minidle = ((minidle * 8.0) / nsPerByte) * + pow(2.0, (double)RM_FILTER_GAIN); + + maxidle = maxidle / 1000.0; + offtime = offtime / 1000.0; + minidle = minidle / 1000.0; + + opts->minburst = minburst; + opts->maxburst = maxburst; + opts->ns_per_byte = (u_int)nsPerByte; + opts->maxidle = (u_int)fabs(maxidle); + opts->minidle = (int)minidle; + opts->offtime = (u_int)fabs(offtime); + + return (0); +} + +static int +check_commit_cbq(int dev, int opts, struct pfctl_altq *if_ppa) +{ + int error = 0; + + /* + * check if cbq has one root queue and one default queue + * for this interface + */ + if (if_ppa->meta.root_classes != 1) { + warnx("should have one root queue on %s", if_ppa->pa.ifname); + error++; + } + if (if_ppa->meta.default_classes != 1) { + warnx("should have one default queue on %s", if_ppa->pa.ifname); + error++; + } + return (error); +} + +static int +print_cbq_opts(const struct pf_altq *a) +{ + const struct cbq_opts *opts; + + opts = &a->pq_u.cbq_opts; + if (opts->flags) { + printf("cbq("); + if (opts->flags & CBQCLF_RED) + printf(" red"); + if (opts->flags & CBQCLF_ECN) + printf(" ecn"); + if (opts->flags & CBQCLF_RIO) + printf(" rio"); + if (opts->flags & CBQCLF_CODEL) + printf(" codel"); + if (opts->flags & CBQCLF_CLEARDSCP) + printf(" cleardscp"); + if (opts->flags & CBQCLF_FLOWVALVE) + printf(" flowvalve"); + if (opts->flags & CBQCLF_BORROW) + printf(" borrow"); + if (opts->flags & CBQCLF_WRR) + printf(" wrr"); + if (opts->flags & CBQCLF_EFFICIENT) + printf(" efficient"); + if (opts->flags & CBQCLF_ROOTCLASS) + printf(" root"); + if (opts->flags & CBQCLF_DEFCLASS) + printf(" default"); + printf(" ) "); + + return (1); + } else + return (0); +} + +/* + * PRIQ support functions + */ +static int +eval_pfqueue_priq(struct pfctl *pf, struct pf_altq *pa, struct pfctl_altq *if_ppa) +{ + + if (pa->priority >= PRIQ_MAXPRI) { + warnx("priority out of range: max %d", PRIQ_MAXPRI - 1); + return (-1); + } + if (BIT_ISSET(QPRI_BITSET_SIZE, pa->priority, &if_ppa->meta.qpris)) { + warnx("%s does not have a unique priority on interface %s", + pa->qname, pa->ifname); + return (-1); + } else + BIT_SET(QPRI_BITSET_SIZE, pa->priority, &if_ppa->meta.qpris); + + if (pa->pq_u.priq_opts.flags & PRCF_DEFAULTCLASS) + if_ppa->meta.default_classes++; + return (0); +} + +static int +check_commit_priq(int dev, int opts, struct pfctl_altq *if_ppa) +{ + + /* + * check if priq has one default class for this interface + */ + if (if_ppa->meta.default_classes != 1) { + warnx("should have one default queue on %s", if_ppa->pa.ifname); + return (1); + } + return (0); +} + +static int +print_priq_opts(const struct pf_altq *a) +{ + const struct priq_opts *opts; + + opts = &a->pq_u.priq_opts; + + if (opts->flags) { + printf("priq("); + if (opts->flags & PRCF_RED) + printf(" red"); + if (opts->flags & PRCF_ECN) + printf(" ecn"); + if (opts->flags & PRCF_RIO) + printf(" rio"); + if (opts->flags & PRCF_CODEL) + printf(" codel"); + if (opts->flags & PRCF_CLEARDSCP) + printf(" cleardscp"); + if (opts->flags & PRCF_DEFAULTCLASS) + printf(" default"); + printf(" ) "); + + return (1); + } else + return (0); +} + +/* + * HFSC support functions + */ +static int +eval_pfqueue_hfsc(struct pfctl *pf, struct pf_altq *pa, struct pfctl_altq *if_ppa, + struct pfctl_altq *parent) +{ + struct hfsc_opts_v1 *opts; + struct service_curve sc; + + opts = &pa->pq_u.hfsc_opts; + + if (parent == NULL) { + /* root queue */ + opts->lssc_m1 = pa->ifbandwidth; + opts->lssc_m2 = pa->ifbandwidth; + opts->lssc_d = 0; + return (0); + } + + /* First child initializes the parent's service curve accumulators. */ + if (parent->meta.children == 1) { + LIST_INIT(&parent->meta.rtsc); + LIST_INIT(&parent->meta.lssc); + } + + if (parent->pa.pq_u.hfsc_opts.flags & HFCF_DEFAULTCLASS) { + warnx("adding %s would make default queue %s not a leaf", + pa->qname, pa->parent); + return (-1); + } + + if (pa->pq_u.hfsc_opts.flags & HFCF_DEFAULTCLASS) + if_ppa->meta.default_classes++; + + /* if link_share is not specified, use bandwidth */ + if (opts->lssc_m2 == 0) + opts->lssc_m2 = pa->bandwidth; + + if ((opts->rtsc_m1 > 0 && opts->rtsc_m2 == 0) || + (opts->lssc_m1 > 0 && opts->lssc_m2 == 0) || + (opts->ulsc_m1 > 0 && opts->ulsc_m2 == 0)) { + warnx("m2 is zero for %s", pa->qname); + return (-1); + } + + if ((opts->rtsc_m1 < opts->rtsc_m2 && opts->rtsc_m1 != 0) || + (opts->lssc_m1 < opts->lssc_m2 && opts->lssc_m1 != 0) || + (opts->ulsc_m1 < opts->ulsc_m2 && opts->ulsc_m1 != 0)) { + warnx("m1 must be zero for convex curve: %s", pa->qname); + return (-1); + } + + /* + * admission control: + * for the real-time service curve, the sum of the service curves + * should not exceed 80% of the interface bandwidth. 20% is reserved + * not to over-commit the actual interface bandwidth. + * for the linkshare service curve, the sum of the child service + * curve should not exceed the parent service curve. + * for the upper-limit service curve, the assigned bandwidth should + * be smaller than the interface bandwidth, and the upper-limit should + * be larger than the real-time service curve when both are defined. + */ + + /* check the real-time service curve. reserve 20% of interface bw */ + if (opts->rtsc_m2 != 0) { + /* add this queue to the sum */ + sc.m1 = opts->rtsc_m1; + sc.d = opts->rtsc_d; + sc.m2 = opts->rtsc_m2; + gsc_add_sc(&parent->meta.rtsc, &sc); + /* compare the sum with 80% of the interface */ + sc.m1 = 0; + sc.d = 0; + sc.m2 = pa->ifbandwidth / 100 * 80; + if (!is_gsc_under_sc(&parent->meta.rtsc, &sc)) { + warnx("real-time sc exceeds 80%% of the interface " + "bandwidth (%s)", rate2str((double)sc.m2)); + return (-1); + } + } + + /* check the linkshare service curve. */ + if (opts->lssc_m2 != 0) { + /* add this queue to the child sum */ + sc.m1 = opts->lssc_m1; + sc.d = opts->lssc_d; + sc.m2 = opts->lssc_m2; + gsc_add_sc(&parent->meta.lssc, &sc); + /* compare the sum of the children with parent's sc */ + sc.m1 = parent->pa.pq_u.hfsc_opts.lssc_m1; + sc.d = parent->pa.pq_u.hfsc_opts.lssc_d; + sc.m2 = parent->pa.pq_u.hfsc_opts.lssc_m2; + if (!is_gsc_under_sc(&parent->meta.lssc, &sc)) { + warnx("linkshare sc exceeds parent's sc"); + return (-1); + } + } + + /* check the upper-limit service curve. */ + if (opts->ulsc_m2 != 0) { + if (opts->ulsc_m1 > pa->ifbandwidth || + opts->ulsc_m2 > pa->ifbandwidth) { + warnx("upper-limit larger than interface bandwidth"); + return (-1); + } + if (opts->rtsc_m2 != 0 && opts->rtsc_m2 > opts->ulsc_m2) { + warnx("upper-limit sc smaller than real-time sc"); + return (-1); + } + } + + return (0); +} + +/* + * FAIRQ support functions + */ +static int +eval_pfqueue_fairq(struct pfctl *pf __unused, struct pf_altq *pa, + struct pfctl_altq *if_ppa, struct pfctl_altq *parent) +{ + struct fairq_opts *opts; + struct service_curve sc; + + opts = &pa->pq_u.fairq_opts; + + if (parent == NULL) { + /* root queue */ + opts->lssc_m1 = pa->ifbandwidth; + opts->lssc_m2 = pa->ifbandwidth; + opts->lssc_d = 0; + return (0); + } + + /* First child initializes the parent's service curve accumulator. */ + if (parent->meta.children == 1) + LIST_INIT(&parent->meta.lssc); + + if (parent->pa.pq_u.fairq_opts.flags & FARF_DEFAULTCLASS) { + warnx("adding %s would make default queue %s not a leaf", + pa->qname, pa->parent); + return (-1); + } + + if (pa->pq_u.fairq_opts.flags & FARF_DEFAULTCLASS) + if_ppa->meta.default_classes++; + + /* if link_share is not specified, use bandwidth */ + if (opts->lssc_m2 == 0) + opts->lssc_m2 = pa->bandwidth; + + /* + * admission control: + * for the real-time service curve, the sum of the service curves + * should not exceed 80% of the interface bandwidth. 20% is reserved + * not to over-commit the actual interface bandwidth. + * for the link-sharing service curve, the sum of the child service + * curve should not exceed the parent service curve. + * for the upper-limit service curve, the assigned bandwidth should + * be smaller than the interface bandwidth, and the upper-limit should + * be larger than the real-time service curve when both are defined. + */ + + /* check the linkshare service curve. */ + if (opts->lssc_m2 != 0) { + /* add this queue to the child sum */ + sc.m1 = opts->lssc_m1; + sc.d = opts->lssc_d; + sc.m2 = opts->lssc_m2; + gsc_add_sc(&parent->meta.lssc, &sc); + /* compare the sum of the children with parent's sc */ + sc.m1 = parent->pa.pq_u.fairq_opts.lssc_m1; + sc.d = parent->pa.pq_u.fairq_opts.lssc_d; + sc.m2 = parent->pa.pq_u.fairq_opts.lssc_m2; + if (!is_gsc_under_sc(&parent->meta.lssc, &sc)) { + warnx("link-sharing sc exceeds parent's sc"); + return (-1); + } + } + + return (0); +} + +static int +check_commit_hfsc(int dev, int opts, struct pfctl_altq *if_ppa) +{ + + /* check if hfsc has one default queue for this interface */ + if (if_ppa->meta.default_classes != 1) { + warnx("should have one default queue on %s", if_ppa->pa.ifname); + return (1); + } + return (0); +} + +static int +check_commit_fairq(int dev __unused, int opts __unused, struct pfctl_altq *if_ppa) +{ + + /* check if fairq has one default queue for this interface */ + if (if_ppa->meta.default_classes != 1) { + warnx("should have one default queue on %s", if_ppa->pa.ifname); + return (1); + } + return (0); +} + +static int +print_hfsc_opts(const struct pf_altq *a, const struct node_queue_opt *qopts) +{ + const struct hfsc_opts_v1 *opts; + const struct node_hfsc_sc *rtsc, *lssc, *ulsc; + + opts = &a->pq_u.hfsc_opts; + if (qopts == NULL) + rtsc = lssc = ulsc = NULL; + else { + rtsc = &qopts->data.hfsc_opts.realtime; + lssc = &qopts->data.hfsc_opts.linkshare; + ulsc = &qopts->data.hfsc_opts.upperlimit; + } + + if (opts->flags || opts->rtsc_m2 != 0 || opts->ulsc_m2 != 0 || + (opts->lssc_m2 != 0 && (opts->lssc_m2 != a->bandwidth || + opts->lssc_d != 0))) { + printf("hfsc("); + if (opts->flags & HFCF_RED) + printf(" red"); + if (opts->flags & HFCF_ECN) + printf(" ecn"); + if (opts->flags & HFCF_RIO) + printf(" rio"); + if (opts->flags & HFCF_CODEL) + printf(" codel"); + if (opts->flags & HFCF_CLEARDSCP) + printf(" cleardscp"); + if (opts->flags & HFCF_DEFAULTCLASS) + printf(" default"); + if (opts->rtsc_m2 != 0) + print_hfsc_sc("realtime", opts->rtsc_m1, opts->rtsc_d, + opts->rtsc_m2, rtsc); + if (opts->lssc_m2 != 0 && (opts->lssc_m2 != a->bandwidth || + opts->lssc_d != 0)) + print_hfsc_sc("linkshare", opts->lssc_m1, opts->lssc_d, + opts->lssc_m2, lssc); + if (opts->ulsc_m2 != 0) + print_hfsc_sc("upperlimit", opts->ulsc_m1, opts->ulsc_d, + opts->ulsc_m2, ulsc); + printf(" ) "); + + return (1); + } else + return (0); +} + +static int +print_codel_opts(const struct pf_altq *a, const struct node_queue_opt *qopts) +{ + const struct codel_opts *opts; + + opts = &a->pq_u.codel_opts; + if (opts->target || opts->interval || opts->ecn) { + printf("codel("); + if (opts->target) + printf(" target %d", opts->target); + if (opts->interval) + printf(" interval %d", opts->interval); + if (opts->ecn) + printf("ecn"); + printf(" ) "); + + return (1); + } + + return (0); +} + +static int +print_fairq_opts(const struct pf_altq *a, const struct node_queue_opt *qopts) +{ + const struct fairq_opts *opts; + const struct node_fairq_sc *loc_lssc; + + opts = &a->pq_u.fairq_opts; + if (qopts == NULL) + loc_lssc = NULL; + else + loc_lssc = &qopts->data.fairq_opts.linkshare; + + if (opts->flags || + (opts->lssc_m2 != 0 && (opts->lssc_m2 != a->bandwidth || + opts->lssc_d != 0))) { + printf("fairq("); + if (opts->flags & FARF_RED) + printf(" red"); + if (opts->flags & FARF_ECN) + printf(" ecn"); + if (opts->flags & FARF_RIO) + printf(" rio"); + if (opts->flags & FARF_CODEL) + printf(" codel"); + if (opts->flags & FARF_CLEARDSCP) + printf(" cleardscp"); + if (opts->flags & FARF_DEFAULTCLASS) + printf(" default"); + if (opts->lssc_m2 != 0 && (opts->lssc_m2 != a->bandwidth || + opts->lssc_d != 0)) + print_fairq_sc("linkshare", opts->lssc_m1, opts->lssc_d, + opts->lssc_m2, loc_lssc); + printf(" ) "); + + return (1); + } else + return (0); +} + +/* + * admission control using generalized service curve + */ + +/* add a new service curve to a generalized service curve */ +static void +gsc_add_sc(struct gen_sc *gsc, struct service_curve *sc) +{ + if (is_sc_null(sc)) + return; + if (sc->d != 0) + gsc_add_seg(gsc, 0.0, 0.0, (double)sc->d, (double)sc->m1); + gsc_add_seg(gsc, (double)sc->d, 0.0, INFINITY, (double)sc->m2); +} + +/* + * check whether all points of a generalized service curve have + * their y-coordinates no larger than a given two-piece linear + * service curve. + */ +static int +is_gsc_under_sc(struct gen_sc *gsc, struct service_curve *sc) +{ + struct segment *s, *last, *end; + double y; + + if (is_sc_null(sc)) { + if (LIST_EMPTY(gsc)) + return (1); + LIST_FOREACH(s, gsc, _next) { + if (s->m != 0) + return (0); + } + return (1); + } + /* + * gsc has a dummy entry at the end with x = INFINITY. + * loop through up to this dummy entry. + */ + end = gsc_getentry(gsc, INFINITY); + if (end == NULL) + return (1); + last = NULL; + for (s = LIST_FIRST(gsc); s != end; s = LIST_NEXT(s, _next)) { + if (s->y > sc_x2y(sc, s->x)) + return (0); + last = s; + } + /* last now holds the real last segment */ + if (last == NULL) + return (1); + if (last->m > sc->m2) + return (0); + if (last->x < sc->d && last->m > sc->m1) { + y = last->y + (sc->d - last->x) * last->m; + if (y > sc_x2y(sc, sc->d)) + return (0); + } + return (1); +} + +/* + * return a segment entry starting at x. + * if gsc has no entry starting at x, a new entry is created at x. + */ +static struct segment * +gsc_getentry(struct gen_sc *gsc, double x) +{ + struct segment *new, *prev, *s; + + prev = NULL; + LIST_FOREACH(s, gsc, _next) { + if (s->x == x) + return (s); /* matching entry found */ + else if (s->x < x) + prev = s; + else + break; + } + + /* we have to create a new entry */ + if ((new = calloc(1, sizeof(struct segment))) == NULL) + return (NULL); + + new->x = x; + if (x == INFINITY || s == NULL) + new->d = 0; + else if (s->x == INFINITY) + new->d = INFINITY; + else + new->d = s->x - x; + if (prev == NULL) { + /* insert the new entry at the head of the list */ + new->y = 0; + new->m = 0; + LIST_INSERT_HEAD(gsc, new, _next); + } else { + /* + * the start point intersects with the segment pointed by + * prev. divide prev into 2 segments + */ + if (x == INFINITY) { + prev->d = INFINITY; + if (prev->m == 0) + new->y = prev->y; + else + new->y = INFINITY; + } else { + prev->d = x - prev->x; + new->y = prev->d * prev->m + prev->y; + } + new->m = prev->m; + LIST_INSERT_AFTER(prev, new, _next); + } + return (new); +} + +/* add a segment to a generalized service curve */ +static int +gsc_add_seg(struct gen_sc *gsc, double x, double y, double d, double m) +{ + struct segment *start, *end, *s; + double x2; + + if (d == INFINITY) + x2 = INFINITY; + else + x2 = x + d; + start = gsc_getentry(gsc, x); + end = gsc_getentry(gsc, x2); + if (start == NULL || end == NULL) + return (-1); + + for (s = start; s != end; s = LIST_NEXT(s, _next)) { + s->m += m; + s->y += y + (s->x - x) * m; + } + + end = gsc_getentry(gsc, INFINITY); + for (; s != end; s = LIST_NEXT(s, _next)) { + s->y += m * d; + } + + return (0); +} + +/* get y-projection of a service curve */ +static double +sc_x2y(struct service_curve *sc, double x) +{ + double y; + + if (x <= (double)sc->d) + /* y belongs to the 1st segment */ + y = x * (double)sc->m1; + else + /* y belongs to the 2nd segment */ + y = (double)sc->d * (double)sc->m1 + + (x - (double)sc->d) * (double)sc->m2; + return (y); +} + +/* + * misc utilities + */ +#define R2S_BUFS 8 +#define RATESTR_MAX 16 + +char * +rate2str(double rate) +{ + char *buf; + static char r2sbuf[R2S_BUFS][RATESTR_MAX]; /* ring buffer */ + static int idx = 0; + int i; + static const char unit[] = " KMG"; + + buf = r2sbuf[idx++]; + if (idx == R2S_BUFS) + idx = 0; + + for (i = 0; rate >= 1000 && i <= 3; i++) + rate /= 1000; + + if ((int)(rate * 100) % 100) + snprintf(buf, RATESTR_MAX, "%.2f%cb", rate, unit[i]); + else + snprintf(buf, RATESTR_MAX, "%d%cb", (int)rate, unit[i]); + + return (buf); +} + +u_int32_t +getifspeed(char *ifname) +{ + int s; + struct ifreq ifr; + struct if_data ifrdat; + + s = get_query_socket(); + bzero(&ifr, sizeof(ifr)); + if (strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)) >= + sizeof(ifr.ifr_name)) + errx(1, "getifspeed: strlcpy"); + ifr.ifr_data = (caddr_t)&ifrdat; + if (ioctl(s, SIOCGIFDATA, (caddr_t)&ifr) == -1) + err(1, "SIOCGIFDATA"); + return ((u_int32_t)ifrdat.ifi_baudrate); +} + +u_long +getifmtu(char *ifname) +{ + int s; + struct ifreq ifr; + + s = get_query_socket(); + bzero(&ifr, sizeof(ifr)); + if (strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)) >= + sizeof(ifr.ifr_name)) + errx(1, "getifmtu: strlcpy"); + if (ioctl(s, SIOCGIFMTU, (caddr_t)&ifr) == -1) +#ifdef __FreeBSD__ + ifr.ifr_mtu = 1500; +#else + err(1, "SIOCGIFMTU"); +#endif + if (ifr.ifr_mtu > 0) + return (ifr.ifr_mtu); + else { + warnx("could not get mtu for %s, assuming 1500", ifname); + return (1500); + } +} + +int +eval_queue_opts(struct pf_altq *pa, struct node_queue_opt *opts, + u_int64_t ref_bw) +{ + int errors = 0; + + switch (pa->scheduler) { + case ALTQT_CBQ: + pa->pq_u.cbq_opts = opts->data.cbq_opts; + break; + case ALTQT_PRIQ: + pa->pq_u.priq_opts = opts->data.priq_opts; + break; + case ALTQT_HFSC: + pa->pq_u.hfsc_opts.flags = opts->data.hfsc_opts.flags; + if (opts->data.hfsc_opts.linkshare.used) { + pa->pq_u.hfsc_opts.lssc_m1 = + eval_bwspec(&opts->data.hfsc_opts.linkshare.m1, + ref_bw); + pa->pq_u.hfsc_opts.lssc_m2 = + eval_bwspec(&opts->data.hfsc_opts.linkshare.m2, + ref_bw); + pa->pq_u.hfsc_opts.lssc_d = + opts->data.hfsc_opts.linkshare.d; + } + if (opts->data.hfsc_opts.realtime.used) { + pa->pq_u.hfsc_opts.rtsc_m1 = + eval_bwspec(&opts->data.hfsc_opts.realtime.m1, + ref_bw); + pa->pq_u.hfsc_opts.rtsc_m2 = + eval_bwspec(&opts->data.hfsc_opts.realtime.m2, + ref_bw); + pa->pq_u.hfsc_opts.rtsc_d = + opts->data.hfsc_opts.realtime.d; + } + if (opts->data.hfsc_opts.upperlimit.used) { + pa->pq_u.hfsc_opts.ulsc_m1 = + eval_bwspec(&opts->data.hfsc_opts.upperlimit.m1, + ref_bw); + pa->pq_u.hfsc_opts.ulsc_m2 = + eval_bwspec(&opts->data.hfsc_opts.upperlimit.m2, + ref_bw); + pa->pq_u.hfsc_opts.ulsc_d = + opts->data.hfsc_opts.upperlimit.d; + } + break; + case ALTQT_FAIRQ: + pa->pq_u.fairq_opts.flags = opts->data.fairq_opts.flags; + pa->pq_u.fairq_opts.nbuckets = opts->data.fairq_opts.nbuckets; + pa->pq_u.fairq_opts.hogs_m1 = + eval_bwspec(&opts->data.fairq_opts.hogs_bw, ref_bw); + + if (opts->data.fairq_opts.linkshare.used) { + pa->pq_u.fairq_opts.lssc_m1 = + eval_bwspec(&opts->data.fairq_opts.linkshare.m1, + ref_bw); + pa->pq_u.fairq_opts.lssc_m2 = + eval_bwspec(&opts->data.fairq_opts.linkshare.m2, + ref_bw); + pa->pq_u.fairq_opts.lssc_d = + opts->data.fairq_opts.linkshare.d; + } + break; + case ALTQT_CODEL: + pa->pq_u.codel_opts.target = opts->data.codel_opts.target; + pa->pq_u.codel_opts.interval = opts->data.codel_opts.interval; + pa->pq_u.codel_opts.ecn = opts->data.codel_opts.ecn; + break; + default: + warnx("eval_queue_opts: unknown scheduler type %u", + opts->qtype); + errors++; + break; + } + + return (errors); +} + +/* + * If absolute bandwidth if set, return the lesser of that value and the + * reference bandwidth. Limiting to the reference bandwidth allows simple + * limiting of configured bandwidth parameters for schedulers that are + * 32-bit limited, as the root/interface bandwidth (top-level reference + * bandwidth) will be properly limited in that case. + * + * Otherwise, if the absolute bandwidth is not set, return given percentage + * of reference bandwidth. + */ +u_int64_t +eval_bwspec(struct node_queue_bw *bw, u_int64_t ref_bw) +{ + if (bw->bw_absolute > 0) + return (MIN(bw->bw_absolute, ref_bw)); + + if (bw->bw_percent > 0) + return (ref_bw / 100 * bw->bw_percent); + + return (0); +} + +void +print_hfsc_sc(const char *scname, u_int m1, u_int d, u_int m2, + const struct node_hfsc_sc *sc) +{ + printf(" %s", scname); + + if (d != 0) { + printf("("); + if (sc != NULL && sc->m1.bw_percent > 0) + printf("%u%%", sc->m1.bw_percent); + else + printf("%s", rate2str((double)m1)); + printf(" %u", d); + } + + if (sc != NULL && sc->m2.bw_percent > 0) + printf(" %u%%", sc->m2.bw_percent); + else + printf(" %s", rate2str((double)m2)); + + if (d != 0) + printf(")"); +} + +void +print_fairq_sc(const char *scname, u_int m1, u_int d, u_int m2, + const struct node_fairq_sc *sc) +{ + printf(" %s", scname); + + if (d != 0) { + printf("("); + if (sc != NULL && sc->m1.bw_percent > 0) + printf("%u%%", sc->m1.bw_percent); + else + printf("%s", rate2str((double)m1)); + printf(" %u", d); + } + + if (sc != NULL && sc->m2.bw_percent > 0) + printf(" %u%%", sc->m2.bw_percent); + else + printf(" %s", rate2str((double)m2)); + + if (d != 0) + printf(")"); +} diff --git a/sbin/pfctl/pfctl_ioctl.h b/sbin/pfctl/pfctl_ioctl.h new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/sbin/pfctl/pfctl_ioctl.h diff --git a/sbin/pfctl/pfctl_optimize.c b/sbin/pfctl/pfctl_optimize.c new file mode 100644 index 000000000000..2d16bbd22b39 --- /dev/null +++ b/sbin/pfctl/pfctl_optimize.c @@ -0,0 +1,1684 @@ +/* $OpenBSD: pfctl_optimize.c,v 1.17 2008/05/06 03:45:21 mpf Exp $ */ + +/* + * Copyright (c) 2004 Mike Frantzen <frantzen@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/socket.h> + +#include <net/if.h> +#include <net/pfvar.h> + +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <assert.h> +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <libpfctl.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "pfctl_parser.h" +#include "pfctl.h" + +/* The size at which a table becomes faster than individual rules */ +#define TABLE_THRESHOLD 6 + + +/* #define OPT_DEBUG 1 */ +#ifdef OPT_DEBUG +# define DEBUG(str, v...) \ + printf("%s: " str "\n", __FUNCTION__ , ## v) +#else +# define DEBUG(str, v...) ((void)0) +#endif + + +/* + * A container that lets us sort a superblock to optimize the skip step jumps + */ +struct pf_skip_step { + int ps_count; /* number of items */ + TAILQ_HEAD( , pf_opt_rule) ps_rules; + TAILQ_ENTRY(pf_skip_step) ps_entry; +}; + + +/* + * A superblock is a block of adjacent rules of similar action. If there + * are five PASS rules in a row, they all become members of a superblock. + * Once we have a superblock, we are free to re-order any rules within it + * in order to improve performance; if a packet is passed, it doesn't matter + * who passed it. + */ +struct superblock { + TAILQ_HEAD( , pf_opt_rule) sb_rules; + TAILQ_ENTRY(superblock) sb_entry; + struct superblock *sb_profiled_block; + TAILQ_HEAD(skiplist, pf_skip_step) sb_skipsteps[PF_SKIP_COUNT]; +}; +TAILQ_HEAD(superblocks, superblock); + + +/* + * Description of the PF rule structure. + */ +enum { + BARRIER, /* the presence of the field puts the rule in its own block */ + BREAK, /* the field may not differ between rules in a superblock */ + NOMERGE, /* the field may not differ between rules when combined */ + COMBINED, /* the field may itself be combined with other rules */ + DC, /* we just don't care about the field */ + NEVER}; /* we should never see this field set?!? */ +static struct pf_rule_field { + const char *prf_name; + int prf_type; + size_t prf_offset; + size_t prf_size; +} pf_rule_desc[] = { +#define PF_RULE_FIELD(field, ty) \ + {#field, \ + ty, \ + offsetof(struct pfctl_rule, field), \ + sizeof(((struct pfctl_rule *)0)->field)} + + + /* + * The presence of these fields in a rule put the rule in its own + * superblock. Thus it will not be optimized. It also prevents the + * rule from being re-ordered at all. + */ + PF_RULE_FIELD(label, BARRIER), + PF_RULE_FIELD(prob, BARRIER), + PF_RULE_FIELD(max_states, BARRIER), + PF_RULE_FIELD(max_src_nodes, BARRIER), + PF_RULE_FIELD(max_src_states, BARRIER), + PF_RULE_FIELD(max_src_conn, BARRIER), + PF_RULE_FIELD(max_src_conn_rate, BARRIER), + PF_RULE_FIELD(anchor, BARRIER), /* for now */ + + /* + * These fields must be the same between all rules in the same superblock. + * These rules are allowed to be re-ordered but only among like rules. + * For instance we can re-order all 'tag "foo"' rules because they have the + * same tag. But we can not re-order between a 'tag "foo"' and a + * 'tag "bar"' since that would change the meaning of the ruleset. + */ + PF_RULE_FIELD(tagname, BREAK), + PF_RULE_FIELD(keep_state, BREAK), + PF_RULE_FIELD(qname, BREAK), + PF_RULE_FIELD(pqname, BREAK), + PF_RULE_FIELD(rt, BREAK), + PF_RULE_FIELD(allow_opts, BREAK), + PF_RULE_FIELD(rule_flag, BREAK), + PF_RULE_FIELD(action, BREAK), + PF_RULE_FIELD(log, BREAK), + PF_RULE_FIELD(quick, BREAK), + PF_RULE_FIELD(return_ttl, BREAK), + PF_RULE_FIELD(overload_tblname, BREAK), + PF_RULE_FIELD(flush, BREAK), + PF_RULE_FIELD(rdr, BREAK), + PF_RULE_FIELD(nat, BREAK), + PF_RULE_FIELD(route, BREAK), + PF_RULE_FIELD(logif, BREAK), + + /* + * Any fields not listed in this structure act as BREAK fields + */ + + + /* + * These fields must not differ when we merge two rules together but + * their difference isn't enough to put the rules in different superblocks. + * There are no problems re-ordering any rules with these fields. + */ + PF_RULE_FIELD(af, NOMERGE), + PF_RULE_FIELD(ifnot, NOMERGE), + PF_RULE_FIELD(ifname, NOMERGE), /* hack for IF groups */ + PF_RULE_FIELD(match_tag_not, NOMERGE), + PF_RULE_FIELD(match_tagname, NOMERGE), + PF_RULE_FIELD(os_fingerprint, NOMERGE), + PF_RULE_FIELD(timeout, NOMERGE), + PF_RULE_FIELD(return_icmp, NOMERGE), + PF_RULE_FIELD(return_icmp6, NOMERGE), + PF_RULE_FIELD(uid, NOMERGE), + PF_RULE_FIELD(gid, NOMERGE), + PF_RULE_FIELD(direction, NOMERGE), + PF_RULE_FIELD(proto, NOMERGE), + PF_RULE_FIELD(type, NOMERGE), + PF_RULE_FIELD(code, NOMERGE), + PF_RULE_FIELD(flags, NOMERGE), + PF_RULE_FIELD(flagset, NOMERGE), + PF_RULE_FIELD(tos, NOMERGE), + PF_RULE_FIELD(src.port, NOMERGE), + PF_RULE_FIELD(dst.port, NOMERGE), + PF_RULE_FIELD(src.port_op, NOMERGE), + PF_RULE_FIELD(dst.port_op, NOMERGE), + PF_RULE_FIELD(src.neg, NOMERGE), + PF_RULE_FIELD(dst.neg, NOMERGE), + PF_RULE_FIELD(af, NOMERGE), + + /* These fields can be merged */ + PF_RULE_FIELD(src.addr, COMBINED), + PF_RULE_FIELD(dst.addr, COMBINED), + + /* We just don't care about these fields. They're set by the kernel */ + PF_RULE_FIELD(skip, DC), + PF_RULE_FIELD(evaluations, DC), + PF_RULE_FIELD(packets, DC), + PF_RULE_FIELD(bytes, DC), + PF_RULE_FIELD(kif, DC), + PF_RULE_FIELD(states_cur, DC), + PF_RULE_FIELD(states_tot, DC), + PF_RULE_FIELD(src_nodes, DC), + PF_RULE_FIELD(nr, DC), + PF_RULE_FIELD(entries, DC), + PF_RULE_FIELD(qid, DC), + PF_RULE_FIELD(pqid, DC), + PF_RULE_FIELD(anchor_relative, DC), + PF_RULE_FIELD(anchor_wildcard, DC), + PF_RULE_FIELD(tag, DC), + PF_RULE_FIELD(match_tag, DC), + PF_RULE_FIELD(overload_tbl, DC), + + /* These fields should never be set in a PASS/BLOCK rule */ + PF_RULE_FIELD(natpass, NEVER), + PF_RULE_FIELD(max_mss, NEVER), + PF_RULE_FIELD(min_ttl, NEVER), + PF_RULE_FIELD(set_tos, NEVER), +}; + + + +int add_opt_table(struct pfctl *, struct pf_opt_tbl **, sa_family_t, + struct pf_rule_addr *); +int addrs_combineable(struct pf_rule_addr *, struct pf_rule_addr *); +int addrs_equal(struct pf_rule_addr *, struct pf_rule_addr *); +int block_feedback(struct pfctl *, struct superblock *); +int combine_rules(struct pfctl *, struct superblock *); +void comparable_rule(struct pfctl_rule *, const struct pfctl_rule *, int); +int construct_superblocks(struct pfctl *, struct pf_opt_queue *, + struct superblocks *); +void exclude_supersets(struct pfctl_rule *, struct pfctl_rule *); +int interface_group(const char *); +int load_feedback_profile(struct pfctl *, struct superblocks *); +int optimize_superblock(struct pfctl *, struct superblock *); +int pf_opt_create_table(struct pfctl *, struct pf_opt_tbl *); +void remove_from_skipsteps(struct skiplist *, struct superblock *, + struct pf_opt_rule *, struct pf_skip_step *); +int remove_identical_rules(struct pfctl *, struct superblock *); +int reorder_rules(struct pfctl *, struct superblock *, int); +int rules_combineable(struct pfctl_rule *, struct pfctl_rule *); +void skip_append(struct superblock *, int, struct pf_skip_step *, + struct pf_opt_rule *); +int skip_compare(int, struct pf_skip_step *, struct pf_opt_rule *); +void skip_init(void); +int skip_cmp_af(struct pfctl_rule *, struct pfctl_rule *); +int skip_cmp_dir(struct pfctl_rule *, struct pfctl_rule *); +int skip_cmp_dst_addr(struct pfctl_rule *, struct pfctl_rule *); +int skip_cmp_dst_port(struct pfctl_rule *, struct pfctl_rule *); +int skip_cmp_ifp(struct pfctl_rule *, struct pfctl_rule *); +int skip_cmp_proto(struct pfctl_rule *, struct pfctl_rule *); +int skip_cmp_src_addr(struct pfctl_rule *, struct pfctl_rule *); +int skip_cmp_src_port(struct pfctl_rule *, struct pfctl_rule *); +int superblock_inclusive(struct superblock *, struct pf_opt_rule *); +void superblock_free(struct pfctl *, struct superblock *); +struct pf_opt_tbl *pf_opt_table_ref(struct pf_opt_tbl *); +void pf_opt_table_unref(struct pf_opt_tbl *); + + +static int (*skip_comparitors[PF_SKIP_COUNT])(struct pfctl_rule *, + struct pfctl_rule *); +static const char *skip_comparitors_names[PF_SKIP_COUNT]; +#define PF_SKIP_COMPARITORS { \ + { "ifp", PF_SKIP_IFP, skip_cmp_ifp }, \ + { "dir", PF_SKIP_DIR, skip_cmp_dir }, \ + { "af", PF_SKIP_AF, skip_cmp_af }, \ + { "proto", PF_SKIP_PROTO, skip_cmp_proto }, \ + { "saddr", PF_SKIP_SRC_ADDR, skip_cmp_src_addr }, \ + { "daddr", PF_SKIP_DST_ADDR, skip_cmp_dst_addr }, \ + { "sport", PF_SKIP_SRC_PORT, skip_cmp_src_port }, \ + { "dport", PF_SKIP_DST_PORT, skip_cmp_dst_port } \ +} + +static struct pfr_buffer table_buffer; +static int table_identifier; + + +int +pfctl_optimize_ruleset(struct pfctl *pf, struct pfctl_ruleset *rs) +{ + struct superblocks superblocks; + struct pf_opt_queue opt_queue; + struct superblock *block; + struct pf_opt_rule *por; + struct pfctl_rule *r; + struct pfctl_rulequeue *old_rules; + + if (TAILQ_EMPTY(rs->rules[PF_RULESET_FILTER].active.ptr)) + return (0); + + DEBUG("optimizing ruleset \"%s\"", rs->anchor->path); + memset(&table_buffer, 0, sizeof(table_buffer)); + skip_init(); + TAILQ_INIT(&opt_queue); + + old_rules = rs->rules[PF_RULESET_FILTER].active.ptr; + rs->rules[PF_RULESET_FILTER].active.ptr = + rs->rules[PF_RULESET_FILTER].inactive.ptr; + rs->rules[PF_RULESET_FILTER].inactive.ptr = old_rules; + + /* + * XXX expanding the pf_opt_rule format throughout pfctl might allow + * us to avoid all this copying. + */ + while ((r = TAILQ_FIRST(rs->rules[PF_RULESET_FILTER].inactive.ptr)) + != NULL) { + TAILQ_REMOVE(rs->rules[PF_RULESET_FILTER].inactive.ptr, r, + entries); + if ((por = calloc(1, sizeof(*por))) == NULL) + err(1, "calloc"); + memcpy(&por->por_rule, r, sizeof(*r)); + if (TAILQ_FIRST(&r->rdr.list) != NULL) { + TAILQ_INIT(&por->por_rule.rdr.list); + pfctl_move_pool(&r->rdr, &por->por_rule.rdr); + } else + bzero(&por->por_rule.rdr, + sizeof(por->por_rule.rdr)); + if (TAILQ_FIRST(&r->nat.list) != NULL) { + TAILQ_INIT(&por->por_rule.nat.list); + pfctl_move_pool(&r->nat, &por->por_rule.nat); + } else + bzero(&por->por_rule.nat, + sizeof(por->por_rule.nat)); + if (TAILQ_FIRST(&r->route.list) != NULL) { + TAILQ_INIT(&por->por_rule.route.list); + pfctl_move_pool(&r->route, &por->por_rule.route); + } else + bzero(&por->por_rule.route, + sizeof(por->por_rule.route)); + + TAILQ_INSERT_TAIL(&opt_queue, por, por_entry); + } + + TAILQ_INIT(&superblocks); + if (construct_superblocks(pf, &opt_queue, &superblocks)) + goto error; + + if (pf->optimize & PF_OPTIMIZE_PROFILE) { + if (load_feedback_profile(pf, &superblocks)) + goto error; + } + + TAILQ_FOREACH(block, &superblocks, sb_entry) { + if (optimize_superblock(pf, block)) + goto error; + } + + rs->anchor->refcnt = 0; + while ((block = TAILQ_FIRST(&superblocks))) { + TAILQ_REMOVE(&superblocks, block, sb_entry); + + while ((por = TAILQ_FIRST(&block->sb_rules))) { + TAILQ_REMOVE(&block->sb_rules, por, por_entry); + por->por_rule.nr = rs->anchor->refcnt++; + if ((r = calloc(1, sizeof(*r))) == NULL) + err(1, "calloc"); + memcpy(r, &por->por_rule, sizeof(*r)); + TAILQ_INIT(&r->rdr.list); + pfctl_move_pool(&por->por_rule.rdr, &r->rdr); + TAILQ_INIT(&r->nat.list); + pfctl_move_pool(&por->por_rule.nat, &r->nat); + TAILQ_INSERT_TAIL( + rs->rules[PF_RULESET_FILTER].active.ptr, + r, entries); + pf_opt_table_unref(por->por_src_tbl); + pf_opt_table_unref(por->por_dst_tbl); + free(por); + } + superblock_free(pf, block); + } + + return (0); + +error: + while ((por = TAILQ_FIRST(&opt_queue))) { + TAILQ_REMOVE(&opt_queue, por, por_entry); + pf_opt_table_unref(por->por_src_tbl); + pf_opt_table_unref(por->por_dst_tbl); + free(por); + } + while ((block = TAILQ_FIRST(&superblocks))) { + TAILQ_REMOVE(&superblocks, block, sb_entry); + superblock_free(pf, block); + } + return (1); +} + + +/* + * Go ahead and optimize a superblock + */ +int +optimize_superblock(struct pfctl *pf, struct superblock *block) +{ +#ifdef OPT_DEBUG + struct pf_opt_rule *por; +#endif /* OPT_DEBUG */ + + /* We have a few optimization passes: + * 1) remove duplicate rules or rules that are a subset of other + * rules + * 2) combine otherwise identical rules with different IP addresses + * into a single rule and put the addresses in a table. + * 3) re-order the rules to improve kernel skip steps + * 4) re-order the 'quick' rules based on feedback from the + * active ruleset statistics + * + * XXX combine_rules() doesn't combine v4 and v6 rules. would just + * have to keep af in the table container, make af 'COMBINE' and + * twiddle the af on the merged rule + * XXX maybe add a weighting to the metric on skipsteps when doing + * reordering. sometimes two sequential tables will be better + * that four consecutive interfaces. + * XXX need to adjust the skipstep count of everything after PROTO, + * since they aren't actually checked on a proto mismatch in + * pf_test_{tcp, udp, icmp}() + * XXX should i treat proto=0, af=0 or dir=0 special in skepstep + * calculation since they are a DC? + * XXX keep last skiplist of last superblock to influence this + * superblock. '5 inet6 log' should make '3 inet6' come before '4 + * inet' in the next superblock. + * XXX would be useful to add tables for ports + * XXX we can also re-order some mutually exclusive superblocks to + * try merging superblocks before any of these optimization passes. + * for instance a single 'log in' rule in the middle of non-logging + * out rules. + */ + + /* shortcut. there will be a lot of 1-rule superblocks */ + if (!TAILQ_NEXT(TAILQ_FIRST(&block->sb_rules), por_entry)) + return (0); + +#ifdef OPT_DEBUG + printf("--- Superblock ---\n"); + TAILQ_FOREACH(por, &block->sb_rules, por_entry) { + printf(" "); + print_rule(&por->por_rule, por->por_rule.anchor ? + por->por_rule.anchor->name : "", 1, 0); + } +#endif /* OPT_DEBUG */ + + + if (remove_identical_rules(pf, block)) + return (1); + if (combine_rules(pf, block)) + return (1); + if ((pf->optimize & PF_OPTIMIZE_PROFILE) && + TAILQ_FIRST(&block->sb_rules)->por_rule.quick && + block->sb_profiled_block) { + if (block_feedback(pf, block)) + return (1); + } else if (reorder_rules(pf, block, 0)) { + return (1); + } + + /* + * Don't add any optimization passes below reorder_rules(). It will + * have divided superblocks into smaller blocks for further refinement + * and doesn't put them back together again. What once was a true + * superblock might have been split into multiple superblocks. + */ + +#ifdef OPT_DEBUG + printf("--- END Superblock ---\n"); +#endif /* OPT_DEBUG */ + return (0); +} + + +/* + * Optimization pass #1: remove identical rules + */ +int +remove_identical_rules(struct pfctl *pf, struct superblock *block) +{ + struct pf_opt_rule *por1, *por2, *por_next, *por2_next; + struct pfctl_rule a, a2, b, b2; + + for (por1 = TAILQ_FIRST(&block->sb_rules); por1; por1 = por_next) { + por_next = TAILQ_NEXT(por1, por_entry); + for (por2 = por_next; por2; por2 = por2_next) { + por2_next = TAILQ_NEXT(por2, por_entry); + comparable_rule(&a, &por1->por_rule, DC); + comparable_rule(&b, &por2->por_rule, DC); + memcpy(&a2, &a, sizeof(a2)); + memcpy(&b2, &b, sizeof(b2)); + + exclude_supersets(&a, &b); + exclude_supersets(&b2, &a2); + if (memcmp(&a, &b, sizeof(a)) == 0) { + DEBUG("removing identical rule nr%d = *nr%d*", + por1->por_rule.nr, por2->por_rule.nr); + TAILQ_REMOVE(&block->sb_rules, por2, por_entry); + if (por_next == por2) + por_next = TAILQ_NEXT(por1, por_entry); + free(por2); + } else if (memcmp(&a2, &b2, sizeof(a2)) == 0) { + DEBUG("removing identical rule *nr%d* = nr%d", + por1->por_rule.nr, por2->por_rule.nr); + TAILQ_REMOVE(&block->sb_rules, por1, por_entry); + free(por1); + break; + } + } + } + + return (0); +} + + +/* + * Optimization pass #2: combine similar rules with different addresses + * into a single rule and a table + */ +int +combine_rules(struct pfctl *pf, struct superblock *block) +{ + struct pf_opt_rule *p1, *p2, *por_next; + int src_eq, dst_eq; + + if ((pf->loadopt & PFCTL_FLAG_TABLE) == 0) { + warnx("Must enable table loading for optimizations"); + return (1); + } + + /* First we make a pass to combine the rules. O(n log n) */ + TAILQ_FOREACH(p1, &block->sb_rules, por_entry) { + for (p2 = TAILQ_NEXT(p1, por_entry); p2; p2 = por_next) { + por_next = TAILQ_NEXT(p2, por_entry); + + src_eq = addrs_equal(&p1->por_rule.src, + &p2->por_rule.src); + dst_eq = addrs_equal(&p1->por_rule.dst, + &p2->por_rule.dst); + + if (src_eq && !dst_eq && p1->por_src_tbl == NULL && + p2->por_dst_tbl == NULL && + p2->por_src_tbl == NULL && + rules_combineable(&p1->por_rule, &p2->por_rule) && + addrs_combineable(&p1->por_rule.dst, + &p2->por_rule.dst)) { + DEBUG("can combine rules nr%d = nr%d", + p1->por_rule.nr, p2->por_rule.nr); + if (p1->por_dst_tbl == NULL && + add_opt_table(pf, &p1->por_dst_tbl, + p1->por_rule.af, &p1->por_rule.dst)) + return (1); + if (add_opt_table(pf, &p1->por_dst_tbl, + p1->por_rule.af, &p2->por_rule.dst)) + return (1); + if (p1->por_dst_tbl->pt_rulecount >= + TABLE_THRESHOLD) { + TAILQ_REMOVE(&block->sb_rules, p2, + por_entry); + free(p2); + } else { + p2->por_dst_tbl = + pf_opt_table_ref(p1->por_dst_tbl); + } + } else if (!src_eq && dst_eq && p1->por_dst_tbl == NULL + && p2->por_src_tbl == NULL && + p2->por_dst_tbl == NULL && + rules_combineable(&p1->por_rule, &p2->por_rule) && + addrs_combineable(&p1->por_rule.src, + &p2->por_rule.src)) { + DEBUG("can combine rules nr%d = nr%d", + p1->por_rule.nr, p2->por_rule.nr); + if (p1->por_src_tbl == NULL && + add_opt_table(pf, &p1->por_src_tbl, + p1->por_rule.af, &p1->por_rule.src)) + return (1); + if (add_opt_table(pf, &p1->por_src_tbl, + p1->por_rule.af, &p2->por_rule.src)) + return (1); + if (p1->por_src_tbl->pt_rulecount >= + TABLE_THRESHOLD) { + TAILQ_REMOVE(&block->sb_rules, p2, + por_entry); + free(p2); + } else { + p2->por_src_tbl = + pf_opt_table_ref(p1->por_src_tbl); + } + } + } + } + + + /* + * Then we make a final pass to create a valid table name and + * insert the name into the rules. + */ + for (p1 = TAILQ_FIRST(&block->sb_rules); p1; p1 = por_next) { + por_next = TAILQ_NEXT(p1, por_entry); + assert(p1->por_src_tbl == NULL || p1->por_dst_tbl == NULL); + + if (p1->por_src_tbl && p1->por_src_tbl->pt_rulecount >= + TABLE_THRESHOLD) { + if (p1->por_src_tbl->pt_generated) { + /* This rule is included in a table */ + TAILQ_REMOVE(&block->sb_rules, p1, por_entry); + free(p1); + continue; + } + p1->por_src_tbl->pt_generated = 1; + + if ((pf->opts & PF_OPT_NOACTION) == 0 && + pf_opt_create_table(pf, p1->por_src_tbl)) + return (1); + + pf->tdirty = 1; + + if (pf->opts & PF_OPT_VERBOSE) + print_tabledef(p1->por_src_tbl->pt_name, + PFR_TFLAG_CONST, 1, + &p1->por_src_tbl->pt_nodes); + + memset(&p1->por_rule.src.addr, 0, + sizeof(p1->por_rule.src.addr)); + p1->por_rule.src.addr.type = PF_ADDR_TABLE; + strlcpy(p1->por_rule.src.addr.v.tblname, + p1->por_src_tbl->pt_name, + sizeof(p1->por_rule.src.addr.v.tblname)); + + pfr_buf_clear(p1->por_src_tbl->pt_buf); + free(p1->por_src_tbl->pt_buf); + p1->por_src_tbl->pt_buf = NULL; + } + if (p1->por_dst_tbl && p1->por_dst_tbl->pt_rulecount >= + TABLE_THRESHOLD) { + if (p1->por_dst_tbl->pt_generated) { + /* This rule is included in a table */ + TAILQ_REMOVE(&block->sb_rules, p1, por_entry); + free(p1); + continue; + } + p1->por_dst_tbl->pt_generated = 1; + + if ((pf->opts & PF_OPT_NOACTION) == 0 && + pf_opt_create_table(pf, p1->por_dst_tbl)) + return (1); + pf->tdirty = 1; + + if (pf->opts & PF_OPT_VERBOSE) + print_tabledef(p1->por_dst_tbl->pt_name, + PFR_TFLAG_CONST, 1, + &p1->por_dst_tbl->pt_nodes); + + memset(&p1->por_rule.dst.addr, 0, + sizeof(p1->por_rule.dst.addr)); + p1->por_rule.dst.addr.type = PF_ADDR_TABLE; + strlcpy(p1->por_rule.dst.addr.v.tblname, + p1->por_dst_tbl->pt_name, + sizeof(p1->por_rule.dst.addr.v.tblname)); + + pfr_buf_clear(p1->por_dst_tbl->pt_buf); + free(p1->por_dst_tbl->pt_buf); + p1->por_dst_tbl->pt_buf = NULL; + } + } + + return (0); +} + + +/* + * Optimization pass #3: re-order rules to improve skip steps + */ +int +reorder_rules(struct pfctl *pf, struct superblock *block, int depth) +{ + struct superblock *newblock; + struct pf_skip_step *skiplist; + struct pf_opt_rule *por; + int i, largest, largest_list, rule_count = 0; + TAILQ_HEAD( , pf_opt_rule) head; + + /* + * Calculate the best-case skip steps. We put each rule in a list + * of other rules with common fields + */ + for (i = 0; i < PF_SKIP_COUNT; i++) { + TAILQ_FOREACH(por, &block->sb_rules, por_entry) { + TAILQ_FOREACH(skiplist, &block->sb_skipsteps[i], + ps_entry) { + if (skip_compare(i, skiplist, por) == 0) + break; + } + if (skiplist == NULL) { + if ((skiplist = calloc(1, sizeof(*skiplist))) == + NULL) + err(1, "calloc"); + TAILQ_INIT(&skiplist->ps_rules); + TAILQ_INSERT_TAIL(&block->sb_skipsteps[i], + skiplist, ps_entry); + } + skip_append(block, i, skiplist, por); + } + } + + TAILQ_FOREACH(por, &block->sb_rules, por_entry) + rule_count++; + + /* + * Now we're going to ignore any fields that are identical between + * all of the rules in the superblock and those fields which differ + * between every rule in the superblock. + */ + largest = 0; + for (i = 0; i < PF_SKIP_COUNT; i++) { + skiplist = TAILQ_FIRST(&block->sb_skipsteps[i]); + if (skiplist->ps_count == rule_count) { + DEBUG("(%d) original skipstep '%s' is all rules", + depth, skip_comparitors_names[i]); + skiplist->ps_count = 0; + } else if (skiplist->ps_count == 1) { + skiplist->ps_count = 0; + } else { + DEBUG("(%d) original skipstep '%s' largest jump is %d", + depth, skip_comparitors_names[i], + skiplist->ps_count); + if (skiplist->ps_count > largest) + largest = skiplist->ps_count; + } + } + if (largest == 0) { + /* Ugh. There is NO commonality in the superblock on which + * optimize the skipsteps optimization. + */ + goto done; + } + + /* + * Now we're going to empty the superblock rule list and re-create + * it based on a more optimal skipstep order. + */ + TAILQ_INIT(&head); + TAILQ_CONCAT(&head, &block->sb_rules, por_entry); + + while (!TAILQ_EMPTY(&head)) { + largest = 1; + + /* + * Find the most useful skip steps remaining + */ + for (i = 0; i < PF_SKIP_COUNT; i++) { + skiplist = TAILQ_FIRST(&block->sb_skipsteps[i]); + if (skiplist->ps_count > largest) { + largest = skiplist->ps_count; + largest_list = i; + } + } + + if (largest <= 1) { + /* + * Nothing useful left. Leave remaining rules in order. + */ + DEBUG("(%d) no more commonality for skip steps", depth); + TAILQ_CONCAT(&block->sb_rules, &head, por_entry); + } else { + /* + * There is commonality. Extract those common rules + * and place them in the ruleset adjacent to each + * other. + */ + skiplist = TAILQ_FIRST(&block->sb_skipsteps[ + largest_list]); + DEBUG("(%d) skipstep '%s' largest jump is %d @ #%d", + depth, skip_comparitors_names[largest_list], + largest, TAILQ_FIRST(&TAILQ_FIRST(&block-> + sb_skipsteps [largest_list])->ps_rules)-> + por_rule.nr); + TAILQ_REMOVE(&block->sb_skipsteps[largest_list], + skiplist, ps_entry); + + + /* + * There may be further commonality inside these + * rules. So we'll split them off into they're own + * superblock and pass it back into the optimizer. + */ + if (skiplist->ps_count > 2) { + if ((newblock = calloc(1, sizeof(*newblock))) + == NULL) { + warn("calloc"); + return (1); + } + TAILQ_INIT(&newblock->sb_rules); + for (i = 0; i < PF_SKIP_COUNT; i++) + TAILQ_INIT(&newblock->sb_skipsteps[i]); + TAILQ_INSERT_BEFORE(block, newblock, sb_entry); + DEBUG("(%d) splitting off %d rules from superblock @ #%d", + depth, skiplist->ps_count, + TAILQ_FIRST(&skiplist->ps_rules)-> + por_rule.nr); + } else { + newblock = block; + } + + while ((por = TAILQ_FIRST(&skiplist->ps_rules))) { + TAILQ_REMOVE(&head, por, por_entry); + TAILQ_REMOVE(&skiplist->ps_rules, por, + por_skip_entry[largest_list]); + TAILQ_INSERT_TAIL(&newblock->sb_rules, por, + por_entry); + + /* Remove this rule from all other skiplists */ + remove_from_skipsteps(&block->sb_skipsteps[ + largest_list], block, por, skiplist); + } + free(skiplist); + if (newblock != block) + if (reorder_rules(pf, newblock, depth + 1)) + return (1); + } + } + +done: + for (i = 0; i < PF_SKIP_COUNT; i++) { + while ((skiplist = TAILQ_FIRST(&block->sb_skipsteps[i]))) { + TAILQ_REMOVE(&block->sb_skipsteps[i], skiplist, + ps_entry); + free(skiplist); + } + } + + return (0); +} + + +/* + * Optimization pass #4: re-order 'quick' rules based on feedback from the + * currently running ruleset + */ +int +block_feedback(struct pfctl *pf, struct superblock *block) +{ + TAILQ_HEAD( , pf_opt_rule) queue; + struct pf_opt_rule *por1, *por2; + struct pfctl_rule a, b; + + + /* + * Walk through all of the profiled superblock's rules and copy + * the counters onto our rules. + */ + TAILQ_FOREACH(por1, &block->sb_profiled_block->sb_rules, por_entry) { + comparable_rule(&a, &por1->por_rule, DC); + TAILQ_FOREACH(por2, &block->sb_rules, por_entry) { + if (por2->por_profile_count) + continue; + comparable_rule(&b, &por2->por_rule, DC); + if (memcmp(&a, &b, sizeof(a)) == 0) { + por2->por_profile_count = + por1->por_rule.packets[0] + + por1->por_rule.packets[1]; + break; + } + } + } + superblock_free(pf, block->sb_profiled_block); + block->sb_profiled_block = NULL; + + /* + * Now we pull all of the rules off the superblock and re-insert them + * in sorted order. + */ + + TAILQ_INIT(&queue); + TAILQ_CONCAT(&queue, &block->sb_rules, por_entry); + + while ((por1 = TAILQ_FIRST(&queue)) != NULL) { + TAILQ_REMOVE(&queue, por1, por_entry); +/* XXX I should sort all of the unused rules based on skip steps */ + TAILQ_FOREACH(por2, &block->sb_rules, por_entry) { + if (por1->por_profile_count > por2->por_profile_count) { + TAILQ_INSERT_BEFORE(por2, por1, por_entry); + break; + } + } +#ifdef __FreeBSD__ + if (por2 == NULL) +#else + if (por2 == TAILQ_END(&block->sb_rules)) +#endif + TAILQ_INSERT_TAIL(&block->sb_rules, por1, por_entry); + } + + return (0); +} + + +/* + * Load the current ruleset from the kernel and try to associate them with + * the ruleset we're optimizing. + */ +int +load_feedback_profile(struct pfctl *pf, struct superblocks *superblocks) +{ + char anchor_call[MAXPATHLEN] = ""; + struct superblock *block, *blockcur; + struct superblocks prof_superblocks; + struct pf_opt_rule *por; + struct pf_opt_queue queue; + struct pfctl_rules_info rules; + struct pfctl_rule a, b, rule; + int nr, mnr, ret; + + TAILQ_INIT(&queue); + TAILQ_INIT(&prof_superblocks); + + if ((ret = pfctl_get_rules_info_h(pf->h, &rules, PF_PASS, "")) != 0) { + warnx("%s", pf_strerror(ret)); + return (1); + } + mnr = rules.nr; + + DEBUG("Loading %d active rules for a feedback profile", mnr); + for (nr = 0; nr < mnr; ++nr) { + struct pfctl_ruleset *rs; + if ((por = calloc(1, sizeof(*por))) == NULL) { + warn("calloc"); + return (1); + } + + if (pfctl_get_rule_h(pf->h, nr, rules.ticket, "", PF_PASS, + &rule, anchor_call)) { + warnx("%s", pf_strerror(ret)); + free(por); + return (1); + } + memcpy(&por->por_rule, &rule, sizeof(por->por_rule)); + rs = pf_find_or_create_ruleset(anchor_call); + por->por_rule.anchor = rs->anchor; + if (TAILQ_EMPTY(&por->por_rule.rdr.list)) + memset(&por->por_rule.rdr, 0, + sizeof(por->por_rule.rdr)); + if (TAILQ_EMPTY(&por->por_rule.nat.list)) + memset(&por->por_rule.nat, 0, + sizeof(por->por_rule.nat)); + TAILQ_INSERT_TAIL(&queue, por, por_entry); + + /* XXX pfctl_get_pool(pf->dev, &rule.rdr, nr, pr.ticket, + * PF_PASS, pf->anchor) ??? + * ... pfctl_clear_pool(&rule.rdr) + */ + } + + if (construct_superblocks(pf, &queue, &prof_superblocks)) + return (1); + + + /* + * Now we try to associate the active ruleset's superblocks with + * the superblocks we're compiling. + */ + block = TAILQ_FIRST(superblocks); + blockcur = TAILQ_FIRST(&prof_superblocks); + while (block && blockcur) { + comparable_rule(&a, &TAILQ_FIRST(&block->sb_rules)->por_rule, + BREAK); + comparable_rule(&b, &TAILQ_FIRST(&blockcur->sb_rules)->por_rule, + BREAK); + if (memcmp(&a, &b, sizeof(a)) == 0) { + /* The two superblocks lined up */ + block->sb_profiled_block = blockcur; + } else { + DEBUG("superblocks don't line up between #%d and #%d", + TAILQ_FIRST(&block->sb_rules)->por_rule.nr, + TAILQ_FIRST(&blockcur->sb_rules)->por_rule.nr); + break; + } + block = TAILQ_NEXT(block, sb_entry); + blockcur = TAILQ_NEXT(blockcur, sb_entry); + } + + + + /* Free any superblocks we couldn't link */ + while (blockcur) { + block = TAILQ_NEXT(blockcur, sb_entry); + superblock_free(pf, blockcur); + blockcur = block; + } + return (0); +} + + +/* + * Compare a rule to a skiplist to see if the rule is a member + */ +int +skip_compare(int skipnum, struct pf_skip_step *skiplist, + struct pf_opt_rule *por) +{ + struct pfctl_rule *a, *b; + if (skipnum >= PF_SKIP_COUNT || skipnum < 0) + errx(1, "skip_compare() out of bounds"); + a = &por->por_rule; + b = &TAILQ_FIRST(&skiplist->ps_rules)->por_rule; + + return ((skip_comparitors[skipnum])(a, b)); +} + + +/* + * Add a rule to a skiplist + */ +void +skip_append(struct superblock *superblock, int skipnum, + struct pf_skip_step *skiplist, struct pf_opt_rule *por) +{ + struct pf_skip_step *prev; + + skiplist->ps_count++; + TAILQ_INSERT_TAIL(&skiplist->ps_rules, por, por_skip_entry[skipnum]); + + /* Keep the list of skiplists sorted by whichever is larger */ + while ((prev = TAILQ_PREV(skiplist, skiplist, ps_entry)) && + prev->ps_count < skiplist->ps_count) { + TAILQ_REMOVE(&superblock->sb_skipsteps[skipnum], + skiplist, ps_entry); + TAILQ_INSERT_BEFORE(prev, skiplist, ps_entry); + } +} + + +/* + * Remove a rule from the other skiplist calculations. + */ +void +remove_from_skipsteps(struct skiplist *head, struct superblock *block, + struct pf_opt_rule *por, struct pf_skip_step *active_list) +{ + struct pf_skip_step *sk, *next; + struct pf_opt_rule *p2; + int i, found; + + for (i = 0; i < PF_SKIP_COUNT; i++) { + sk = TAILQ_FIRST(&block->sb_skipsteps[i]); + if (sk == NULL || sk == active_list || sk->ps_count <= 1) + continue; + found = 0; + do { + TAILQ_FOREACH(p2, &sk->ps_rules, por_skip_entry[i]) + if (p2 == por) { + TAILQ_REMOVE(&sk->ps_rules, p2, + por_skip_entry[i]); + found = 1; + sk->ps_count--; + break; + } + } while (!found && (sk = TAILQ_NEXT(sk, ps_entry))); + if (found && sk) { + /* Does this change the sorting order? */ + while ((next = TAILQ_NEXT(sk, ps_entry)) && + next->ps_count > sk->ps_count) { + TAILQ_REMOVE(head, sk, ps_entry); + TAILQ_INSERT_AFTER(head, next, sk, ps_entry); + } +#ifdef OPT_DEBUG + next = TAILQ_NEXT(sk, ps_entry); + assert(next == NULL || next->ps_count <= sk->ps_count); +#endif /* OPT_DEBUG */ + } + } +} + + +/* Compare two rules AF field for skiplist construction */ +int +skip_cmp_af(struct pfctl_rule *a, struct pfctl_rule *b) +{ + if (a->af != b->af || a->af == 0) + return (1); + return (0); +} + +/* Compare two rules DIRECTION field for skiplist construction */ +int +skip_cmp_dir(struct pfctl_rule *a, struct pfctl_rule *b) +{ + if (a->direction == 0 || a->direction != b->direction) + return (1); + return (0); +} + +/* Compare two rules DST Address field for skiplist construction */ +int +skip_cmp_dst_addr(struct pfctl_rule *a, struct pfctl_rule *b) +{ + if (a->dst.neg != b->dst.neg || + a->dst.addr.type != b->dst.addr.type) + return (1); + /* XXX if (a->proto != b->proto && a->proto != 0 && b->proto != 0 + * && (a->proto == IPPROTO_TCP || a->proto == IPPROTO_UDP || + * a->proto == IPPROTO_ICMP + * return (1); + */ + switch (a->dst.addr.type) { + case PF_ADDR_ADDRMASK: + if (memcmp(&a->dst.addr.v.a.addr, &b->dst.addr.v.a.addr, + sizeof(a->dst.addr.v.a.addr)) || + memcmp(&a->dst.addr.v.a.mask, &b->dst.addr.v.a.mask, + sizeof(a->dst.addr.v.a.mask)) || + (a->dst.addr.v.a.addr.addr32[0] == 0 && + a->dst.addr.v.a.addr.addr32[1] == 0 && + a->dst.addr.v.a.addr.addr32[2] == 0 && + a->dst.addr.v.a.addr.addr32[3] == 0)) + return (1); + return (0); + case PF_ADDR_DYNIFTL: + if (strcmp(a->dst.addr.v.ifname, b->dst.addr.v.ifname) != 0 || + a->dst.addr.iflags != b->dst.addr.iflags || + memcmp(&a->dst.addr.v.a.mask, &b->dst.addr.v.a.mask, + sizeof(a->dst.addr.v.a.mask))) + return (1); + return (0); + case PF_ADDR_NOROUTE: + case PF_ADDR_URPFFAILED: + return (0); + case PF_ADDR_TABLE: + return (strcmp(a->dst.addr.v.tblname, b->dst.addr.v.tblname)); + } + return (1); +} + +/* Compare two rules DST port field for skiplist construction */ +int +skip_cmp_dst_port(struct pfctl_rule *a, struct pfctl_rule *b) +{ + /* XXX if (a->proto != b->proto && a->proto != 0 && b->proto != 0 + * && (a->proto == IPPROTO_TCP || a->proto == IPPROTO_UDP || + * a->proto == IPPROTO_ICMP + * return (1); + */ + if (a->dst.port_op == PF_OP_NONE || a->dst.port_op != b->dst.port_op || + a->dst.port[0] != b->dst.port[0] || + a->dst.port[1] != b->dst.port[1]) + return (1); + return (0); +} + +/* Compare two rules IFP field for skiplist construction */ +int +skip_cmp_ifp(struct pfctl_rule *a, struct pfctl_rule *b) +{ + if (strcmp(a->ifname, b->ifname) || a->ifname[0] == '\0') + return (1); + return (a->ifnot != b->ifnot); +} + +/* Compare two rules PROTO field for skiplist construction */ +int +skip_cmp_proto(struct pfctl_rule *a, struct pfctl_rule *b) +{ + return (a->proto != b->proto || a->proto == 0); +} + +/* Compare two rules SRC addr field for skiplist construction */ +int +skip_cmp_src_addr(struct pfctl_rule *a, struct pfctl_rule *b) +{ + if (a->src.neg != b->src.neg || + a->src.addr.type != b->src.addr.type) + return (1); + /* XXX if (a->proto != b->proto && a->proto != 0 && b->proto != 0 + * && (a->proto == IPPROTO_TCP || a->proto == IPPROTO_UDP || + * a->proto == IPPROTO_ICMP + * return (1); + */ + switch (a->src.addr.type) { + case PF_ADDR_ADDRMASK: + if (memcmp(&a->src.addr.v.a.addr, &b->src.addr.v.a.addr, + sizeof(a->src.addr.v.a.addr)) || + memcmp(&a->src.addr.v.a.mask, &b->src.addr.v.a.mask, + sizeof(a->src.addr.v.a.mask)) || + (a->src.addr.v.a.addr.addr32[0] == 0 && + a->src.addr.v.a.addr.addr32[1] == 0 && + a->src.addr.v.a.addr.addr32[2] == 0 && + a->src.addr.v.a.addr.addr32[3] == 0)) + return (1); + return (0); + case PF_ADDR_DYNIFTL: + if (strcmp(a->src.addr.v.ifname, b->src.addr.v.ifname) != 0 || + a->src.addr.iflags != b->src.addr.iflags || + memcmp(&a->src.addr.v.a.mask, &b->src.addr.v.a.mask, + sizeof(a->src.addr.v.a.mask))) + return (1); + return (0); + case PF_ADDR_NOROUTE: + case PF_ADDR_URPFFAILED: + return (0); + case PF_ADDR_TABLE: + return (strcmp(a->src.addr.v.tblname, b->src.addr.v.tblname)); + } + return (1); +} + +/* Compare two rules SRC port field for skiplist construction */ +int +skip_cmp_src_port(struct pfctl_rule *a, struct pfctl_rule *b) +{ + if (a->src.port_op == PF_OP_NONE || a->src.port_op != b->src.port_op || + a->src.port[0] != b->src.port[0] || + a->src.port[1] != b->src.port[1]) + return (1); + /* XXX if (a->proto != b->proto && a->proto != 0 && b->proto != 0 + * && (a->proto == IPPROTO_TCP || a->proto == IPPROTO_UDP || + * a->proto == IPPROTO_ICMP + * return (1); + */ + return (0); +} + + +void +skip_init(void) +{ + struct { + char *name; + int skipnum; + int (*func)(struct pfctl_rule *, struct pfctl_rule *); + } comps[] = PF_SKIP_COMPARITORS; + int skipnum, i; + + for (skipnum = 0; skipnum < PF_SKIP_COUNT; skipnum++) { + for (i = 0; i < sizeof(comps)/sizeof(*comps); i++) + if (comps[i].skipnum == skipnum) { + skip_comparitors[skipnum] = comps[i].func; + skip_comparitors_names[skipnum] = comps[i].name; + } + } + for (skipnum = 0; skipnum < PF_SKIP_COUNT; skipnum++) + if (skip_comparitors[skipnum] == NULL) + errx(1, "Need to add skip step comparitor to pfctl?!"); +} + +/* + * Add a host/netmask to a table + */ +int +add_opt_table(struct pfctl *pf, struct pf_opt_tbl **tbl, sa_family_t af, + struct pf_rule_addr *addr) +{ +#ifdef OPT_DEBUG + char buf[128]; +#endif /* OPT_DEBUG */ + static int tablenum = 0; + struct node_host node_host; + + if (*tbl == NULL) { + if ((*tbl = calloc(1, sizeof(**tbl))) == NULL || + ((*tbl)->pt_buf = calloc(1, sizeof(*(*tbl)->pt_buf))) == + NULL) + err(1, "calloc"); + (*tbl)->pt_refcnt = 1; + (*tbl)->pt_buf->pfrb_type = PFRB_ADDRS; + SIMPLEQ_INIT(&(*tbl)->pt_nodes); + + /* This is just a temporary table name */ + snprintf((*tbl)->pt_name, sizeof((*tbl)->pt_name), "%s%d", + PF_OPTIMIZER_TABLE_PFX, tablenum++); + DEBUG("creating table <%s>", (*tbl)->pt_name); + } + + memset(&node_host, 0, sizeof(node_host)); + node_host.af = af; + node_host.addr = addr->addr; + +#ifdef OPT_DEBUG + DEBUG("<%s> adding %s/%d", (*tbl)->pt_name, inet_ntop(af, + &node_host.addr.v.a.addr, buf, sizeof(buf)), + unmask(&node_host.addr.v.a.mask)); +#endif /* OPT_DEBUG */ + + if (append_addr_host((*tbl)->pt_buf, &node_host, 0, 0)) { + warn("failed to add host"); + return (1); + } + if (pf->opts & PF_OPT_VERBOSE) { + struct node_tinit *ti; + + if ((ti = calloc(1, sizeof(*ti))) == NULL) + err(1, "malloc"); + if ((ti->host = malloc(sizeof(*ti->host))) == NULL) + err(1, "malloc"); + memcpy(ti->host, &node_host, sizeof(*ti->host)); + SIMPLEQ_INSERT_TAIL(&(*tbl)->pt_nodes, ti, entries); + } + + (*tbl)->pt_rulecount++; + if ((*tbl)->pt_rulecount == TABLE_THRESHOLD) + DEBUG("table <%s> now faster than skip steps", (*tbl)->pt_name); + + return (0); +} + + +/* + * Do the dirty work of choosing an unused table name and creating it. + * (be careful with the table name, it might already be used in another anchor) + */ +int +pf_opt_create_table(struct pfctl *pf, struct pf_opt_tbl *tbl) +{ + static int tablenum; + struct pfr_table *t; + + if (table_buffer.pfrb_type == 0) { + /* Initialize the list of tables */ + table_buffer.pfrb_type = PFRB_TABLES; + for (;;) { + pfr_buf_grow(&table_buffer, table_buffer.pfrb_size); + table_buffer.pfrb_size = table_buffer.pfrb_msize; + if (pfr_get_tables(NULL, table_buffer.pfrb_caddr, + &table_buffer.pfrb_size, PFR_FLAG_ALLRSETS)) + err(1, "pfr_get_tables"); + if (table_buffer.pfrb_size <= table_buffer.pfrb_msize) + break; + } + table_identifier = arc4random(); + } + + /* XXX would be *really* nice to avoid duplicating identical tables */ + + /* Now we have to pick a table name that isn't used */ +again: + DEBUG("translating temporary table <%s> to <%s%x_%d>", tbl->pt_name, + PF_OPTIMIZER_TABLE_PFX, table_identifier, tablenum); + snprintf(tbl->pt_name, sizeof(tbl->pt_name), "%s%x_%d", + PF_OPTIMIZER_TABLE_PFX, table_identifier, tablenum); + PFRB_FOREACH(t, &table_buffer) { + if (strcasecmp(t->pfrt_name, tbl->pt_name) == 0) { + /* Collision. Try again */ + DEBUG("wow, table <%s> in use. trying again", + tbl->pt_name); + table_identifier = arc4random(); + goto again; + } + } + tablenum++; + + + if (pfctl_define_table(tbl->pt_name, PFR_TFLAG_CONST, 1, + pf->astack[0]->path, tbl->pt_buf, pf->astack[0]->ruleset.tticket, + NULL)) { + warn("failed to create table %s in %s", + tbl->pt_name, pf->astack[0]->name); + return (1); + } + return (0); +} + +/* + * Partition the flat ruleset into a list of distinct superblocks + */ +int +construct_superblocks(struct pfctl *pf, struct pf_opt_queue *opt_queue, + struct superblocks *superblocks) +{ + struct superblock *block = NULL; + struct pf_opt_rule *por; + int i; + + while (!TAILQ_EMPTY(opt_queue)) { + por = TAILQ_FIRST(opt_queue); + TAILQ_REMOVE(opt_queue, por, por_entry); + if (block == NULL || !superblock_inclusive(block, por)) { + if ((block = calloc(1, sizeof(*block))) == NULL) { + warn("calloc"); + return (1); + } + TAILQ_INIT(&block->sb_rules); + for (i = 0; i < PF_SKIP_COUNT; i++) + TAILQ_INIT(&block->sb_skipsteps[i]); + TAILQ_INSERT_TAIL(superblocks, block, sb_entry); + } + TAILQ_INSERT_TAIL(&block->sb_rules, por, por_entry); + } + + return (0); +} + + +/* + * Compare two rule addresses + */ +int +addrs_equal(struct pf_rule_addr *a, struct pf_rule_addr *b) +{ + if (a->neg != b->neg) + return (0); + return (memcmp(&a->addr, &b->addr, sizeof(a->addr)) == 0); +} + + +/* + * The addresses are not equal, but can we combine them into one table? + */ +int +addrs_combineable(struct pf_rule_addr *a, struct pf_rule_addr *b) +{ + if (a->addr.type != PF_ADDR_ADDRMASK || + b->addr.type != PF_ADDR_ADDRMASK) + return (0); + if (a->neg != b->neg || a->port_op != b->port_op || + a->port[0] != b->port[0] || a->port[1] != b->port[1]) + return (0); + return (1); +} + + +/* + * Are we allowed to combine these two rules + */ +int +rules_combineable(struct pfctl_rule *p1, struct pfctl_rule *p2) +{ + struct pfctl_rule a, b; + + comparable_rule(&a, p1, COMBINED); + comparable_rule(&b, p2, COMBINED); + return (memcmp(&a, &b, sizeof(a)) == 0); +} + + +/* + * Can a rule be included inside a superblock + */ +int +superblock_inclusive(struct superblock *block, struct pf_opt_rule *por) +{ + struct pfctl_rule a, b; + int i, j; + + /* First check for hard breaks */ + for (i = 0; i < sizeof(pf_rule_desc)/sizeof(*pf_rule_desc); i++) { + if (pf_rule_desc[i].prf_type == BARRIER) { + for (j = 0; j < pf_rule_desc[i].prf_size; j++) + if (((char *)&por->por_rule)[j + + pf_rule_desc[i].prf_offset] != 0) + return (0); + } + } + + /* per-rule src-track is also a hard break */ + if (por->por_rule.rule_flag & PFRULE_RULESRCTRACK) + return (0); + + /* + * Have to handle interface groups separately. Consider the following + * rules: + * block on EXTIFS to any port 22 + * pass on em0 to any port 22 + * (where EXTIFS is an arbitrary interface group) + * The optimizer may decide to re-order the pass rule in front of the + * block rule. But what if EXTIFS includes em0??? Such a reordering + * would change the meaning of the ruleset. + * We can't just lookup the EXTIFS group and check if em0 is a member + * because the user is allowed to add interfaces to a group during + * runtime. + * Ergo interface groups become a defacto superblock break :-( + */ + if (interface_group(por->por_rule.ifname) || + interface_group(TAILQ_FIRST(&block->sb_rules)->por_rule.ifname)) { + if (strcasecmp(por->por_rule.ifname, + TAILQ_FIRST(&block->sb_rules)->por_rule.ifname) != 0) + return (0); + } + + comparable_rule(&a, &TAILQ_FIRST(&block->sb_rules)->por_rule, NOMERGE); + comparable_rule(&b, &por->por_rule, NOMERGE); + if (memcmp(&a, &b, sizeof(a)) == 0) + return (1); + +#ifdef OPT_DEBUG + for (i = 0; i < sizeof(por->por_rule); i++) { + int closest = -1; + if (((u_int8_t *)&a)[i] != ((u_int8_t *)&b)[i]) { + for (j = 0; j < sizeof(pf_rule_desc) / + sizeof(*pf_rule_desc); j++) { + if (i >= pf_rule_desc[j].prf_offset && + i < pf_rule_desc[j].prf_offset + + pf_rule_desc[j].prf_size) { + DEBUG("superblock break @ %d due to %s", + por->por_rule.nr, + pf_rule_desc[j].prf_name); + return (0); + } + if (i > pf_rule_desc[j].prf_offset) { + if (closest == -1 || + i-pf_rule_desc[j].prf_offset < + i-pf_rule_desc[closest].prf_offset) + closest = j; + } + } + + if (closest >= 0) + DEBUG("superblock break @ %d on %s+%zxh", + por->por_rule.nr, + pf_rule_desc[closest].prf_name, + i - pf_rule_desc[closest].prf_offset - + pf_rule_desc[closest].prf_size); + else + DEBUG("superblock break @ %d on field @ %d", + por->por_rule.nr, i); + return (0); + } + } +#endif /* OPT_DEBUG */ + + return (0); +} + + +/* + * Figure out if an interface name is an actual interface or actually a + * group of interfaces. + */ +int +interface_group(const char *ifname) +{ + int s; + struct ifgroupreq ifgr; + + if (ifname == NULL || !ifname[0]) + return (0); + + s = get_query_socket(); + + memset(&ifgr, 0, sizeof(ifgr)); + strlcpy(ifgr.ifgr_name, ifname, IFNAMSIZ); + if (ioctl(s, SIOCGIFGMEMB, (caddr_t)&ifgr) == -1) { + if (errno == ENOENT) + return (0); + else + err(1, "SIOCGIFGMEMB"); + } + + return (1); +} + + +/* + * Make a rule that can directly compared by memcmp() + */ +void +comparable_rule(struct pfctl_rule *dst, const struct pfctl_rule *src, int type) +{ + int i; + /* + * To simplify the comparison, we just zero out the fields that are + * allowed to be different and then do a simple memcmp() + */ + memcpy(dst, src, sizeof(*dst)); + for (i = 0; i < sizeof(pf_rule_desc)/sizeof(*pf_rule_desc); i++) + if (pf_rule_desc[i].prf_type >= type) { +#ifdef OPT_DEBUG + assert(pf_rule_desc[i].prf_type != NEVER || + *(((char *)dst) + pf_rule_desc[i].prf_offset) == 0); +#endif /* OPT_DEBUG */ + memset(((char *)dst) + pf_rule_desc[i].prf_offset, 0, + pf_rule_desc[i].prf_size); + } +} + + +/* + * Remove superset information from two rules so we can directly compare them + * with memcmp() + */ +void +exclude_supersets(struct pfctl_rule *super, struct pfctl_rule *sub) +{ + if (super->ifname[0] == '\0') + memset(sub->ifname, 0, sizeof(sub->ifname)); + if (super->direction == PF_INOUT) + sub->direction = PF_INOUT; + if ((super->proto == 0 || super->proto == sub->proto) && + super->flags == 0 && super->flagset == 0 && (sub->flags || + sub->flagset)) { + sub->flags = super->flags; + sub->flagset = super->flagset; + } + if (super->proto == 0) + sub->proto = 0; + + if (super->src.port_op == 0) { + sub->src.port_op = 0; + sub->src.port[0] = 0; + sub->src.port[1] = 0; + } + if (super->dst.port_op == 0) { + sub->dst.port_op = 0; + sub->dst.port[0] = 0; + sub->dst.port[1] = 0; + } + + if (super->src.addr.type == PF_ADDR_ADDRMASK && !super->src.neg && + !sub->src.neg && super->src.addr.v.a.mask.addr32[0] == 0 && + super->src.addr.v.a.mask.addr32[1] == 0 && + super->src.addr.v.a.mask.addr32[2] == 0 && + super->src.addr.v.a.mask.addr32[3] == 0) + memset(&sub->src.addr, 0, sizeof(sub->src.addr)); + else if (super->src.addr.type == PF_ADDR_ADDRMASK && + sub->src.addr.type == PF_ADDR_ADDRMASK && + super->src.neg == sub->src.neg && + super->af == sub->af && + unmask(&super->src.addr.v.a.mask) < + unmask(&sub->src.addr.v.a.mask) && + super->src.addr.v.a.addr.addr32[0] == + (sub->src.addr.v.a.addr.addr32[0] & + super->src.addr.v.a.mask.addr32[0]) && + super->src.addr.v.a.addr.addr32[1] == + (sub->src.addr.v.a.addr.addr32[1] & + super->src.addr.v.a.mask.addr32[1]) && + super->src.addr.v.a.addr.addr32[2] == + (sub->src.addr.v.a.addr.addr32[2] & + super->src.addr.v.a.mask.addr32[2]) && + super->src.addr.v.a.addr.addr32[3] == + (sub->src.addr.v.a.addr.addr32[3] & + super->src.addr.v.a.mask.addr32[3])) { + /* sub->src.addr is a subset of super->src.addr/mask */ + memcpy(&sub->src.addr, &super->src.addr, sizeof(sub->src.addr)); + } + + if (super->dst.addr.type == PF_ADDR_ADDRMASK && !super->dst.neg && + !sub->dst.neg && super->dst.addr.v.a.mask.addr32[0] == 0 && + super->dst.addr.v.a.mask.addr32[1] == 0 && + super->dst.addr.v.a.mask.addr32[2] == 0 && + super->dst.addr.v.a.mask.addr32[3] == 0) + memset(&sub->dst.addr, 0, sizeof(sub->dst.addr)); + else if (super->dst.addr.type == PF_ADDR_ADDRMASK && + sub->dst.addr.type == PF_ADDR_ADDRMASK && + super->dst.neg == sub->dst.neg && + super->af == sub->af && + unmask(&super->dst.addr.v.a.mask) < + unmask(&sub->dst.addr.v.a.mask) && + super->dst.addr.v.a.addr.addr32[0] == + (sub->dst.addr.v.a.addr.addr32[0] & + super->dst.addr.v.a.mask.addr32[0]) && + super->dst.addr.v.a.addr.addr32[1] == + (sub->dst.addr.v.a.addr.addr32[1] & + super->dst.addr.v.a.mask.addr32[1]) && + super->dst.addr.v.a.addr.addr32[2] == + (sub->dst.addr.v.a.addr.addr32[2] & + super->dst.addr.v.a.mask.addr32[2]) && + super->dst.addr.v.a.addr.addr32[3] == + (sub->dst.addr.v.a.addr.addr32[3] & + super->dst.addr.v.a.mask.addr32[3])) { + /* sub->dst.addr is a subset of super->dst.addr/mask */ + memcpy(&sub->dst.addr, &super->dst.addr, sizeof(sub->dst.addr)); + } + + if (super->af == 0) + sub->af = 0; +} + + +void +superblock_free(struct pfctl *pf, struct superblock *block) +{ + struct pf_opt_rule *por; + while ((por = TAILQ_FIRST(&block->sb_rules))) { + TAILQ_REMOVE(&block->sb_rules, por, por_entry); + pf_opt_table_unref(por->por_src_tbl); + pf_opt_table_unref(por->por_dst_tbl); + free(por); + } + if (block->sb_profiled_block) + superblock_free(pf, block->sb_profiled_block); + free(block); +} + +struct pf_opt_tbl * +pf_opt_table_ref(struct pf_opt_tbl *pt) +{ + /* parser does not run concurrently, we don't need atomic ops. */ + if (pt != NULL) + pt->pt_refcnt++; + + return (pt); +} + +void +pf_opt_table_unref(struct pf_opt_tbl *pt) +{ + if ((pt != NULL) && ((--pt->pt_refcnt) == 0)) { + if (pt->pt_buf != NULL) { + pfr_buf_clear(pt->pt_buf); + free(pt->pt_buf); + } + free(pt); + } +} diff --git a/sbin/pfctl/pfctl_osfp.c b/sbin/pfctl/pfctl_osfp.c new file mode 100644 index 000000000000..5770c8343a46 --- /dev/null +++ b/sbin/pfctl/pfctl_osfp.c @@ -0,0 +1,1098 @@ +/* $OpenBSD: pfctl_osfp.c,v 1.14 2006/04/08 02:13:14 ray Exp $ */ + +/* + * Copyright (c) 2003 Mike Frantzen <frantzen@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/socket.h> + +#include <net/if.h> +#include <net/pfvar.h> + +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <netinet/ip6.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "pfctl_parser.h" +#include "pfctl.h" + +#ifndef MIN +# define MIN(a,b) (((a) < (b)) ? (a) : (b)) +#endif /* MIN */ +#ifndef MAX +# define MAX(a,b) (((a) > (b)) ? (a) : (b)) +#endif /* MAX */ + + +#if 0 +# define DEBUG(fp, str, v...) \ + fprintf(stderr, "%s:%s:%s " str "\n", (fp)->fp_os.fp_class_nm, \ + (fp)->fp_os.fp_version_nm, (fp)->fp_os.fp_subtype_nm , ## v); +#else +# define DEBUG(fp, str, v...) ((void)0) +#endif + + +struct name_entry; +LIST_HEAD(name_list, name_entry); +struct name_entry { + LIST_ENTRY(name_entry) nm_entry; + int nm_num; + char nm_name[PF_OSFP_LEN]; + + struct name_list nm_sublist; + int nm_sublist_num; +}; +static struct name_list classes = LIST_HEAD_INITIALIZER(&classes); +static int class_count; +static int fingerprint_count; + +void add_fingerprint(int, int, struct pf_osfp_ioctl *); +struct name_entry *fingerprint_name_entry(struct name_list *, char *); +void pfctl_flush_my_fingerprints(struct name_list *); +char *get_field(char **, size_t *, int *); +int get_int(char **, size_t *, int *, int *, const char *, + int, int, const char *, int); +int get_str(char **, size_t *, char **, const char *, int, + const char *, int); +int get_tcpopts(const char *, int, const char *, + pf_tcpopts_t *, int *, int *, int *, int *, int *, + int *); +void import_fingerprint(struct pf_osfp_ioctl *); +const char *print_ioctl(struct pf_osfp_ioctl *); +void print_name_list(int, struct name_list *, const char *); +void sort_name_list(int, struct name_list *); +struct name_entry *lookup_name_list(struct name_list *, const char *); + +/* Load fingerprints from a file */ +int +pfctl_file_fingerprints(int dev, int opts, const char *fp_filename) +{ + FILE *in; + char *line; + size_t len; + int i, lineno = 0; + int window, w_mod, ttl, df, psize, p_mod, mss, mss_mod, wscale, + wscale_mod, optcnt, ts0; + pf_tcpopts_t packed_tcpopts; + char *class, *version, *subtype, *desc, *tcpopts; + struct pf_osfp_ioctl fp; + + pfctl_flush_my_fingerprints(&classes); + + if ((in = pfctl_fopen(fp_filename, "r")) == NULL) { + warn("%s", fp_filename); + return (1); + } + class = version = subtype = desc = tcpopts = NULL; + + if ((opts & PF_OPT_NOACTION) == 0) + pfctl_clear_fingerprints(dev, opts); + + while ((line = fgetln(in, &len)) != NULL) { + lineno++; + free(class); + free(version); + free(subtype); + free(desc); + free(tcpopts); + class = version = subtype = desc = tcpopts = NULL; + memset(&fp, 0, sizeof(fp)); + + /* Chop off comment */ + for (i = 0; i < len; i++) + if (line[i] == '#') { + len = i; + break; + } + /* Chop off whitespace */ + while (len > 0 && isspace(line[len - 1])) + len--; + while (len > 0 && isspace(line[0])) { + len--; + line++; + } + if (len == 0) + continue; + +#define T_DC 0x01 /* Allow don't care */ +#define T_MSS 0x02 /* Allow MSS multiple */ +#define T_MTU 0x04 /* Allow MTU multiple */ +#define T_MOD 0x08 /* Allow modulus */ + +#define GET_INT(v, mod, n, ty, mx) \ + get_int(&line, &len, &v, mod, n, ty, mx, fp_filename, lineno) +#define GET_STR(v, n, mn) \ + get_str(&line, &len, &v, n, mn, fp_filename, lineno) + + if (GET_INT(window, &w_mod, "window size", T_DC|T_MSS|T_MTU| + T_MOD, 0xffff) || + GET_INT(ttl, NULL, "ttl", 0, 0xff) || + GET_INT(df, NULL, "don't fragment frag", 0, 1) || + GET_INT(psize, &p_mod, "overall packet size", T_MOD|T_DC, + 8192) || + GET_STR(tcpopts, "TCP Options", 1) || + GET_STR(class, "OS class", 1) || + GET_STR(version, "OS version", 0) || + GET_STR(subtype, "OS subtype", 0) || + GET_STR(desc, "OS description", 2)) + continue; + if (get_tcpopts(fp_filename, lineno, tcpopts, &packed_tcpopts, + &optcnt, &mss, &mss_mod, &wscale, &wscale_mod, &ts0)) + continue; + if (len != 0) { + fprintf(stderr, "%s:%d excess field\n", fp_filename, + lineno); + continue; + } + + fp.fp_ttl = ttl; + if (df) + fp.fp_flags |= PF_OSFP_DF; + switch (w_mod) { + case 0: + break; + case T_DC: + fp.fp_flags |= PF_OSFP_WSIZE_DC; + break; + case T_MSS: + fp.fp_flags |= PF_OSFP_WSIZE_MSS; + break; + case T_MTU: + fp.fp_flags |= PF_OSFP_WSIZE_MTU; + break; + case T_MOD: + fp.fp_flags |= PF_OSFP_WSIZE_MOD; + break; + } + fp.fp_wsize = window; + + switch (p_mod) { + case T_DC: + fp.fp_flags |= PF_OSFP_PSIZE_DC; + break; + case T_MOD: + fp.fp_flags |= PF_OSFP_PSIZE_MOD; + } + fp.fp_psize = psize; + + + switch (wscale_mod) { + case T_DC: + fp.fp_flags |= PF_OSFP_WSCALE_DC; + break; + case T_MOD: + fp.fp_flags |= PF_OSFP_WSCALE_MOD; + } + fp.fp_wscale = wscale; + + switch (mss_mod) { + case T_DC: + fp.fp_flags |= PF_OSFP_MSS_DC; + break; + case T_MOD: + fp.fp_flags |= PF_OSFP_MSS_MOD; + break; + } + fp.fp_mss = mss; + + fp.fp_tcpopts = packed_tcpopts; + fp.fp_optcnt = optcnt; + if (ts0) + fp.fp_flags |= PF_OSFP_TS0; + + if (class[0] == '@') + fp.fp_os.fp_enflags |= PF_OSFP_GENERIC; + if (class[0] == '*') + fp.fp_os.fp_enflags |= PF_OSFP_NODETAIL; + + if (class[0] == '@' || class[0] == '*') + strlcpy(fp.fp_os.fp_class_nm, class + 1, + sizeof(fp.fp_os.fp_class_nm)); + else + strlcpy(fp.fp_os.fp_class_nm, class, + sizeof(fp.fp_os.fp_class_nm)); + strlcpy(fp.fp_os.fp_version_nm, version, + sizeof(fp.fp_os.fp_version_nm)); + strlcpy(fp.fp_os.fp_subtype_nm, subtype, + sizeof(fp.fp_os.fp_subtype_nm)); + + add_fingerprint(dev, opts, &fp); + + fp.fp_flags |= (PF_OSFP_DF | PF_OSFP_INET6); + fp.fp_psize += sizeof(struct ip6_hdr) - sizeof(struct ip); + add_fingerprint(dev, opts, &fp); + } + + free(class); + free(version); + free(subtype); + free(desc); + free(tcpopts); + + fclose(in); + + if (opts & PF_OPT_VERBOSE2) + printf("Loaded %d passive OS fingerprints\n", + fingerprint_count); + return (0); +} + +/* flush the kernel's fingerprints */ +void +pfctl_clear_fingerprints(int dev, int opts) +{ + if (ioctl(dev, DIOCOSFPFLUSH)) + pfctl_err(opts, 1, "DIOCOSFPFLUSH"); +} + +/* flush pfctl's view of the fingerprints */ +void +pfctl_flush_my_fingerprints(struct name_list *list) +{ + struct name_entry *nm; + + while ((nm = LIST_FIRST(list)) != NULL) { + LIST_REMOVE(nm, nm_entry); + pfctl_flush_my_fingerprints(&nm->nm_sublist); + free(nm); + } + fingerprint_count = 0; + class_count = 0; +} + +/* Fetch the active fingerprints from the kernel */ +int +pfctl_load_fingerprints(int dev, int opts) +{ + struct pf_osfp_ioctl io; + int i; + + pfctl_flush_my_fingerprints(&classes); + + for (i = 0; i >= 0; i++) { + memset(&io, 0, sizeof(io)); + io.fp_getnum = i; + if (ioctl(dev, DIOCOSFPGET, &io)) { + if (errno == EBUSY) + break; + warn("DIOCOSFPGET"); + return (1); + } + import_fingerprint(&io); + } + return (0); +} + +/* List the fingerprints */ +void +pfctl_show_fingerprints(int opts) +{ + if (LIST_FIRST(&classes) != NULL) { + if (opts & PF_OPT_SHOWALL) { + pfctl_print_title("OS FINGERPRINTS:"); + printf("%u fingerprints loaded\n", fingerprint_count); + } else { + printf("Class\tVersion\tSubtype(subversion)\n"); + printf("-----\t-------\t-------------------\n"); + sort_name_list(opts, &classes); + print_name_list(opts, &classes, ""); + } + } +} + +/* Lookup a fingerprint */ +pf_osfp_t +pfctl_get_fingerprint(const char *name) +{ + struct name_entry *nm, *class_nm, *version_nm, *subtype_nm; + pf_osfp_t ret = PF_OSFP_NOMATCH; + int class, version, subtype; + int unp_class, unp_version, unp_subtype; + int wr_len, version_len, subtype_len; + char *ptr, *wr_name; + + if (strcasecmp(name, "unknown") == 0) + return (PF_OSFP_UNKNOWN); + + /* Try most likely no version and no subtype */ + if ((nm = lookup_name_list(&classes, name))) { + class = nm->nm_num; + version = PF_OSFP_ANY; + subtype = PF_OSFP_ANY; + goto found; + } else { + + /* Chop it up into class/version/subtype */ + + if ((wr_name = strdup(name)) == NULL) + err(1, "malloc"); + if ((ptr = strchr(wr_name, ' ')) == NULL) { + free(wr_name); + return (PF_OSFP_NOMATCH); + } + *ptr++ = '\0'; + + /* The class is easy to find since it is delimited by a space */ + if ((class_nm = lookup_name_list(&classes, wr_name)) == NULL) { + free(wr_name); + return (PF_OSFP_NOMATCH); + } + class = class_nm->nm_num; + + /* Try no subtype */ + if ((version_nm = lookup_name_list(&class_nm->nm_sublist, ptr))) + { + version = version_nm->nm_num; + subtype = PF_OSFP_ANY; + free(wr_name); + goto found; + } + + + /* + * There must be a version and a subtype. + * We'll do some fuzzy matching to pick up things like: + * Linux 2.2.14 (version=2.2 subtype=14) + * FreeBSD 4.0-STABLE (version=4.0 subtype=STABLE) + * Windows 2000 SP2 (version=2000 subtype=SP2) + */ +#define CONNECTOR(x) ((x) == '.' || (x) == ' ' || (x) == '\t' || (x) == '-') + wr_len = strlen(ptr); + LIST_FOREACH(version_nm, &class_nm->nm_sublist, nm_entry) { + version_len = strlen(version_nm->nm_name); + if (wr_len < version_len + 2 || + !CONNECTOR(ptr[version_len])) + continue; + /* first part of the string must be version */ + if (strncasecmp(ptr, version_nm->nm_name, + version_len)) + continue; + + LIST_FOREACH(subtype_nm, &version_nm->nm_sublist, + nm_entry) { + subtype_len = strlen(subtype_nm->nm_name); + if (wr_len != version_len + subtype_len + 1) + continue; + + /* last part of the string must be subtype */ + if (strcasecmp(&ptr[version_len+1], + subtype_nm->nm_name) != 0) + continue; + + /* Found it!! */ + version = version_nm->nm_num; + subtype = subtype_nm->nm_num; + free(wr_name); + goto found; + } + } + + free(wr_name); + return (PF_OSFP_NOMATCH); + } + +found: + PF_OSFP_PACK(ret, class, version, subtype); + if (ret != PF_OSFP_NOMATCH) { + PF_OSFP_UNPACK(ret, unp_class, unp_version, unp_subtype); + if (class != unp_class) { + fprintf(stderr, "warning: fingerprint table overflowed " + "classes\n"); + return (PF_OSFP_NOMATCH); + } + if (version != unp_version) { + fprintf(stderr, "warning: fingerprint table overflowed " + "versions\n"); + return (PF_OSFP_NOMATCH); + } + if (subtype != unp_subtype) { + fprintf(stderr, "warning: fingerprint table overflowed " + "subtypes\n"); + return (PF_OSFP_NOMATCH); + } + } + if (ret == PF_OSFP_ANY) { + /* should never happen */ + fprintf(stderr, "warning: fingerprint packed to 'any'\n"); + return (PF_OSFP_NOMATCH); + } + + return (ret); +} + +/* Lookup a fingerprint name by ID */ +char * +pfctl_lookup_fingerprint(pf_osfp_t fp, char *buf, size_t len) +{ + int class, version, subtype; + struct name_list *list; + struct name_entry *nm; + + char *class_name, *version_name, *subtype_name; + class_name = version_name = subtype_name = NULL; + + if (fp == PF_OSFP_UNKNOWN) { + strlcpy(buf, "unknown", len); + return (buf); + } + if (fp == PF_OSFP_ANY) { + strlcpy(buf, "any", len); + return (buf); + } + + PF_OSFP_UNPACK(fp, class, version, subtype); + if (class >= (1 << _FP_CLASS_BITS) || + version >= (1 << _FP_VERSION_BITS) || + subtype >= (1 << _FP_SUBTYPE_BITS)) { + warnx("PF_OSFP_UNPACK(0x%x) failed!!", fp); + strlcpy(buf, "nomatch", len); + return (buf); + } + + LIST_FOREACH(nm, &classes, nm_entry) { + if (nm->nm_num == class) { + class_name = nm->nm_name; + if (version == PF_OSFP_ANY) + goto found; + list = &nm->nm_sublist; + LIST_FOREACH(nm, list, nm_entry) { + if (nm->nm_num == version) { + version_name = nm->nm_name; + if (subtype == PF_OSFP_ANY) + goto found; + list = &nm->nm_sublist; + LIST_FOREACH(nm, list, nm_entry) { + if (nm->nm_num == subtype) { + subtype_name = + nm->nm_name; + goto found; + } + } /* foreach subtype */ + strlcpy(buf, "nomatch", len); + return (buf); + } + } /* foreach version */ + strlcpy(buf, "nomatch", len); + return (buf); + } + } /* foreach class */ + + strlcpy(buf, "nomatch", len); + return (buf); + +found: + snprintf(buf, len, "%s", class_name); + if (version_name) { + strlcat(buf, " ", len); + strlcat(buf, version_name, len); + if (subtype_name) { + if (strchr(version_name, ' ')) + strlcat(buf, " ", len); + else if (strchr(version_name, '.') && + isdigit(*subtype_name)) + strlcat(buf, ".", len); + else + strlcat(buf, " ", len); + strlcat(buf, subtype_name, len); + } + } + return (buf); +} + +/* lookup a name in a list */ +struct name_entry * +lookup_name_list(struct name_list *list, const char *name) +{ + struct name_entry *nm; + LIST_FOREACH(nm, list, nm_entry) + if (strcasecmp(name, nm->nm_name) == 0) + return (nm); + + return (NULL); +} + + +void +add_fingerprint(int dev, int opts, struct pf_osfp_ioctl *fp) +{ + struct pf_osfp_ioctl fptmp; + struct name_entry *nm_class, *nm_version, *nm_subtype; + int class, version, subtype; + +/* We expand #-# or #.#-#.# version/subtypes into multiple fingerprints */ +#define EXPAND(field) do { \ + int _dot = -1, _start = -1, _end = -1, _i = 0; \ + /* pick major version out of #.# */ \ + if (isdigit(fp->field[_i]) && fp->field[_i+1] == '.') { \ + _dot = fp->field[_i] - '0'; \ + _i += 2; \ + } \ + if (isdigit(fp->field[_i])) \ + _start = fp->field[_i++] - '0'; \ + else \ + break; \ + if (isdigit(fp->field[_i])) \ + _start = (_start * 10) + fp->field[_i++] - '0'; \ + if (fp->field[_i++] != '-') \ + break; \ + if (isdigit(fp->field[_i]) && fp->field[_i+1] == '.' && \ + fp->field[_i] - '0' == _dot) \ + _i += 2; \ + else if (_dot != -1) \ + break; \ + if (isdigit(fp->field[_i])) \ + _end = fp->field[_i++] - '0'; \ + else \ + break; \ + if (isdigit(fp->field[_i])) \ + _end = (_end * 10) + fp->field[_i++] - '0'; \ + if (isdigit(fp->field[_i])) \ + _end = (_end * 10) + fp->field[_i++] - '0'; \ + if (fp->field[_i] != '\0') \ + break; \ + memcpy(&fptmp, fp, sizeof(fptmp)); \ + for (;_start <= _end; _start++) { \ + memset(fptmp.field, 0, sizeof(fptmp.field)); \ + fptmp.fp_os.fp_enflags |= PF_OSFP_EXPANDED; \ + if (_dot == -1) \ + snprintf(fptmp.field, sizeof(fptmp.field), \ + "%d", _start); \ + else \ + snprintf(fptmp.field, sizeof(fptmp.field), \ + "%d.%d", _dot, _start); \ + add_fingerprint(dev, opts, &fptmp); \ + } \ +} while(0) + + /* We allow "#-#" as a version or subtype and we'll expand it */ + EXPAND(fp_os.fp_version_nm); + EXPAND(fp_os.fp_subtype_nm); + + if (strcasecmp(fp->fp_os.fp_class_nm, "nomatch") == 0) + errx(1, "fingerprint class \"nomatch\" is reserved"); + + version = PF_OSFP_ANY; + subtype = PF_OSFP_ANY; + + nm_class = fingerprint_name_entry(&classes, fp->fp_os.fp_class_nm); + if (nm_class->nm_num == 0) + nm_class->nm_num = ++class_count; + class = nm_class->nm_num; + + nm_version = fingerprint_name_entry(&nm_class->nm_sublist, + fp->fp_os.fp_version_nm); + if (nm_version) { + if (nm_version->nm_num == 0) + nm_version->nm_num = ++nm_class->nm_sublist_num; + version = nm_version->nm_num; + nm_subtype = fingerprint_name_entry(&nm_version->nm_sublist, + fp->fp_os.fp_subtype_nm); + if (nm_subtype) { + if (nm_subtype->nm_num == 0) + nm_subtype->nm_num = + ++nm_version->nm_sublist_num; + subtype = nm_subtype->nm_num; + } + } + + + DEBUG(fp, "\tsignature %d:%d:%d %s", class, version, subtype, + print_ioctl(fp)); + + PF_OSFP_PACK(fp->fp_os.fp_os, class, version, subtype); + fingerprint_count++; + +#ifdef FAKE_PF_KERNEL + /* Linked to the sys/net/pf_osfp.c. Call pf_osfp_add() */ + if ((errno = pf_osfp_add(fp))) +#else + if ((opts & PF_OPT_NOACTION) == 0 && ioctl(dev, DIOCOSFPADD, fp)) +#endif /* FAKE_PF_KERNEL */ + { + if (errno == EEXIST) { + warn("Duplicate signature for %s %s %s", + fp->fp_os.fp_class_nm, + fp->fp_os.fp_version_nm, + fp->fp_os.fp_subtype_nm); + + } else { + err(1, "DIOCOSFPADD"); + } + } +} + +/* import a fingerprint from the kernel */ +void +import_fingerprint(struct pf_osfp_ioctl *fp) +{ + struct name_entry *nm_class, *nm_version, *nm_subtype; + int class, version, subtype; + + PF_OSFP_UNPACK(fp->fp_os.fp_os, class, version, subtype); + + nm_class = fingerprint_name_entry(&classes, fp->fp_os.fp_class_nm); + if (nm_class->nm_num == 0) { + nm_class->nm_num = class; + class_count = MAX(class_count, class); + } + + nm_version = fingerprint_name_entry(&nm_class->nm_sublist, + fp->fp_os.fp_version_nm); + if (nm_version) { + if (nm_version->nm_num == 0) { + nm_version->nm_num = version; + nm_class->nm_sublist_num = MAX(nm_class->nm_sublist_num, + version); + } + nm_subtype = fingerprint_name_entry(&nm_version->nm_sublist, + fp->fp_os.fp_subtype_nm); + if (nm_subtype) { + if (nm_subtype->nm_num == 0) { + nm_subtype->nm_num = subtype; + nm_version->nm_sublist_num = + MAX(nm_version->nm_sublist_num, subtype); + } + } + } + + + fingerprint_count++; + DEBUG(fp, "import signature %d:%d:%d", class, version, subtype); +} + +/* Find an entry for a fingerprints class/version/subtype */ +struct name_entry * +fingerprint_name_entry(struct name_list *list, char *name) +{ + struct name_entry *nm_entry; + + if (name == NULL || strlen(name) == 0) + return (NULL); + + LIST_FOREACH(nm_entry, list, nm_entry) { + if (strcasecmp(nm_entry->nm_name, name) == 0) { + /* We'll move this to the front of the list later */ + LIST_REMOVE(nm_entry, nm_entry); + break; + } + } + if (nm_entry == NULL) { + nm_entry = calloc(1, sizeof(*nm_entry)); + if (nm_entry == NULL) + err(1, "calloc"); + LIST_INIT(&nm_entry->nm_sublist); + strlcpy(nm_entry->nm_name, name, sizeof(nm_entry->nm_name)); + } + LIST_INSERT_HEAD(list, nm_entry, nm_entry); + return (nm_entry); +} + + +void +print_name_list(int opts, struct name_list *nml, const char *prefix) +{ + char newprefix[32]; + struct name_entry *nm; + + LIST_FOREACH(nm, nml, nm_entry) { + snprintf(newprefix, sizeof(newprefix), "%s%s\t", prefix, + nm->nm_name); + printf("%s\n", newprefix); + print_name_list(opts, &nm->nm_sublist, newprefix); + } +} + +void +sort_name_list(int opts, struct name_list *nml) +{ + struct name_list new; + struct name_entry *nm, *nmsearch, *nmlast; + + /* yes yes, it's a very slow sort. so sue me */ + + LIST_INIT(&new); + + while ((nm = LIST_FIRST(nml)) != NULL) { + LIST_REMOVE(nm, nm_entry); + nmlast = NULL; + LIST_FOREACH(nmsearch, &new, nm_entry) { + if (strcasecmp(nmsearch->nm_name, nm->nm_name) > 0) { + LIST_INSERT_BEFORE(nmsearch, nm, nm_entry); + break; + } + nmlast = nmsearch; + } + if (nmsearch == NULL) { + if (nmlast) + LIST_INSERT_AFTER(nmlast, nm, nm_entry); + else + LIST_INSERT_HEAD(&new, nm, nm_entry); + } + + sort_name_list(opts, &nm->nm_sublist); + } + nmlast = NULL; + while ((nm = LIST_FIRST(&new)) != NULL) { + LIST_REMOVE(nm, nm_entry); + if (nmlast == NULL) + LIST_INSERT_HEAD(nml, nm, nm_entry); + else + LIST_INSERT_AFTER(nmlast, nm, nm_entry); + nmlast = nm; + } +} + +/* parse the next integer in a formatted config file line */ +int +get_int(char **line, size_t *len, int *var, int *mod, + const char *name, int flags, int max, const char *filename, int lineno) +{ + int fieldlen, i; + char *field; + long val = 0; + + if (mod) + *mod = 0; + *var = 0; + + field = get_field(line, len, &fieldlen); + if (field == NULL) + return (1); + if (fieldlen == 0) { + fprintf(stderr, "%s:%d empty %s\n", filename, lineno, name); + return (1); + } + + i = 0; + if ((*field == '%' || *field == 'S' || *field == 'T' || *field == '*') + && fieldlen >= 1) { + switch (*field) { + case 'S': + if (mod && (flags & T_MSS)) + *mod = T_MSS; + if (fieldlen == 1) + return (0); + break; + case 'T': + if (mod && (flags & T_MTU)) + *mod = T_MTU; + if (fieldlen == 1) + return (0); + break; + case '*': + if (fieldlen != 1) { + fprintf(stderr, "%s:%d long '%c' %s\n", + filename, lineno, *field, name); + return (1); + } + if (mod && (flags & T_DC)) { + *mod = T_DC; + return (0); + } + case '%': + if (mod && (flags & T_MOD)) + *mod = T_MOD; + if (fieldlen == 1) { + fprintf(stderr, "%s:%d modulus %s must have a " + "value\n", filename, lineno, name); + return (1); + } + break; + } + if (mod == NULL || *mod == 0) { + fprintf(stderr, "%s:%d does not allow %c' %s\n", + filename, lineno, *field, name); + return (1); + } + i++; + } + + for (; i < fieldlen; i++) { + if (field[i] < '0' || field[i] > '9') { + fprintf(stderr, "%s:%d non-digit character in %s\n", + filename, lineno, name); + return (1); + } + val = val * 10 + field[i] - '0'; + if (val < 0) { + fprintf(stderr, "%s:%d %s overflowed\n", filename, + lineno, name); + return (1); + } + } + + if (val > max) { + fprintf(stderr, "%s:%d %s value %ld > %d\n", filename, lineno, + name, val, max); + return (1); + } + *var = (int)val; + + return (0); +} + +/* parse the next string in a formatted config file line */ +int +get_str(char **line, size_t *len, char **v, const char *name, int minlen, + const char *filename, int lineno) +{ + int fieldlen; + char *ptr; + + ptr = get_field(line, len, &fieldlen); + if (ptr == NULL) + return (1); + if (fieldlen < minlen) { + fprintf(stderr, "%s:%d too short %s\n", filename, lineno, name); + return (1); + } + if ((*v = malloc(fieldlen + 1)) == NULL) { + perror("malloc()"); + return (1); + } + memcpy(*v, ptr, fieldlen); + (*v)[fieldlen] = '\0'; + + return (0); +} + +/* Parse out the TCP opts */ +int +get_tcpopts(const char *filename, int lineno, const char *tcpopts, + pf_tcpopts_t *packed, int *optcnt, int *mss, int *mss_mod, int *wscale, + int *wscale_mod, int *ts0) +{ + int i, opt; + + *packed = 0; + *optcnt = 0; + *wscale = 0; + *wscale_mod = T_DC; + *mss = 0; + *mss_mod = T_DC; + *ts0 = 0; + if (strcmp(tcpopts, ".") == 0) + return (0); + + for (i = 0; tcpopts[i] && *optcnt < PF_OSFP_MAX_OPTS;) { + switch ((opt = toupper(tcpopts[i++]))) { + case 'N': /* FALLTHROUGH */ + case 'S': + *packed = (*packed << PF_OSFP_TCPOPT_BITS) | + (opt == 'N' ? PF_OSFP_TCPOPT_NOP : + PF_OSFP_TCPOPT_SACK); + break; + case 'W': /* FALLTHROUGH */ + case 'M': { + int *this_mod, *this; + + if (opt == 'W') { + this = wscale; + this_mod = wscale_mod; + } else { + this = mss; + this_mod = mss_mod; + } + *this = 0; + *this_mod = 0; + + *packed = (*packed << PF_OSFP_TCPOPT_BITS) | + (opt == 'W' ? PF_OSFP_TCPOPT_WSCALE : + PF_OSFP_TCPOPT_MSS); + if (tcpopts[i] == '*' && (tcpopts[i + 1] == '\0' || + tcpopts[i + 1] == ',')) { + *this_mod = T_DC; + i++; + break; + } + + if (tcpopts[i] == '%') { + *this_mod = T_MOD; + i++; + } + do { + if (!isdigit(tcpopts[i])) { + fprintf(stderr, "%s:%d unknown " + "character '%c' in %c TCP opt\n", + filename, lineno, tcpopts[i], opt); + return (1); + } + *this = (*this * 10) + tcpopts[i++] - '0'; + } while(tcpopts[i] != ',' && tcpopts[i] != '\0'); + break; + } + case 'T': + if (tcpopts[i] == '0') { + *ts0 = 1; + i++; + } + *packed = (*packed << PF_OSFP_TCPOPT_BITS) | + PF_OSFP_TCPOPT_TS; + break; + } + (*optcnt) ++; + if (tcpopts[i] == '\0') + break; + if (tcpopts[i] != ',') { + fprintf(stderr, "%s:%d unknown option to %c TCP opt\n", + filename, lineno, opt); + return (1); + } + i++; + } + + return (0); +} + +/* rip the next field ouf of a formatted config file line */ +char * +get_field(char **line, size_t *len, int *fieldlen) +{ + char *ret, *ptr = *line; + size_t plen = *len; + + + while (plen && isspace(*ptr)) { + plen--; + ptr++; + } + ret = ptr; + *fieldlen = 0; + + for (; plen > 0 && *ptr != ':'; plen--, ptr++) + (*fieldlen)++; + if (plen) { + *line = ptr + 1; + *len = plen - 1; + } else { + *len = 0; + } + while (*fieldlen && isspace(ret[*fieldlen - 1])) + (*fieldlen)--; + return (ret); +} + + +const char * +print_ioctl(struct pf_osfp_ioctl *fp) +{ + static char buf[1024]; + char tmp[32]; + int i, opt; + + *buf = '\0'; + if (fp->fp_flags & PF_OSFP_WSIZE_DC) + strlcat(buf, "*", sizeof(buf)); + else if (fp->fp_flags & PF_OSFP_WSIZE_MSS) + strlcat(buf, "S", sizeof(buf)); + else if (fp->fp_flags & PF_OSFP_WSIZE_MTU) + strlcat(buf, "T", sizeof(buf)); + else { + if (fp->fp_flags & PF_OSFP_WSIZE_MOD) + strlcat(buf, "%", sizeof(buf)); + snprintf(tmp, sizeof(tmp), "%d", fp->fp_wsize); + strlcat(buf, tmp, sizeof(buf)); + } + strlcat(buf, ":", sizeof(buf)); + + snprintf(tmp, sizeof(tmp), "%d", fp->fp_ttl); + strlcat(buf, tmp, sizeof(buf)); + strlcat(buf, ":", sizeof(buf)); + + if (fp->fp_flags & PF_OSFP_DF) + strlcat(buf, "1", sizeof(buf)); + else + strlcat(buf, "0", sizeof(buf)); + strlcat(buf, ":", sizeof(buf)); + + if (fp->fp_flags & PF_OSFP_PSIZE_DC) + strlcat(buf, "*", sizeof(buf)); + else { + if (fp->fp_flags & PF_OSFP_PSIZE_MOD) + strlcat(buf, "%", sizeof(buf)); + snprintf(tmp, sizeof(tmp), "%d", fp->fp_psize); + strlcat(buf, tmp, sizeof(buf)); + } + strlcat(buf, ":", sizeof(buf)); + + if (fp->fp_optcnt == 0) + strlcat(buf, ".", sizeof(buf)); + for (i = fp->fp_optcnt - 1; i >= 0; i--) { + opt = fp->fp_tcpopts >> (i * PF_OSFP_TCPOPT_BITS); + opt &= (1 << PF_OSFP_TCPOPT_BITS) - 1; + switch (opt) { + case PF_OSFP_TCPOPT_NOP: + strlcat(buf, "N", sizeof(buf)); + break; + case PF_OSFP_TCPOPT_SACK: + strlcat(buf, "S", sizeof(buf)); + break; + case PF_OSFP_TCPOPT_TS: + strlcat(buf, "T", sizeof(buf)); + if (fp->fp_flags & PF_OSFP_TS0) + strlcat(buf, "0", sizeof(buf)); + break; + case PF_OSFP_TCPOPT_MSS: + strlcat(buf, "M", sizeof(buf)); + if (fp->fp_flags & PF_OSFP_MSS_DC) + strlcat(buf, "*", sizeof(buf)); + else { + if (fp->fp_flags & PF_OSFP_MSS_MOD) + strlcat(buf, "%", sizeof(buf)); + snprintf(tmp, sizeof(tmp), "%d", fp->fp_mss); + strlcat(buf, tmp, sizeof(buf)); + } + break; + case PF_OSFP_TCPOPT_WSCALE: + strlcat(buf, "W", sizeof(buf)); + if (fp->fp_flags & PF_OSFP_WSCALE_DC) + strlcat(buf, "*", sizeof(buf)); + else { + if (fp->fp_flags & PF_OSFP_WSCALE_MOD) + strlcat(buf, "%", sizeof(buf)); + snprintf(tmp, sizeof(tmp), "%d", fp->fp_wscale); + strlcat(buf, tmp, sizeof(buf)); + } + break; + } + + if (i != 0) + strlcat(buf, ",", sizeof(buf)); + } + strlcat(buf, ":", sizeof(buf)); + + strlcat(buf, fp->fp_os.fp_class_nm, sizeof(buf)); + strlcat(buf, ":", sizeof(buf)); + strlcat(buf, fp->fp_os.fp_version_nm, sizeof(buf)); + strlcat(buf, ":", sizeof(buf)); + strlcat(buf, fp->fp_os.fp_subtype_nm, sizeof(buf)); + strlcat(buf, ":", sizeof(buf)); + + snprintf(tmp, sizeof(tmp), "TcpOpts %d 0x%llx", fp->fp_optcnt, + (long long int)fp->fp_tcpopts); + strlcat(buf, tmp, sizeof(buf)); + + return (buf); +} diff --git a/sbin/pfctl/pfctl_parser.c b/sbin/pfctl/pfctl_parser.c new file mode 100644 index 000000000000..b8531067d3f6 --- /dev/null +++ b/sbin/pfctl/pfctl_parser.c @@ -0,0 +1,2101 @@ +/* $OpenBSD: pfctl_parser.c,v 1.240 2008/06/10 20:55:02 mcbride Exp $ */ + +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * 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. + * + */ + +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <sys/param.h> +#include <sys/proc.h> +#include <net/if_dl.h> +#include <net/if.h> +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <netinet/ip_icmp.h> +#include <netinet/icmp6.h> +#include <netinet/tcp.h> +#include <net/pfvar.h> +#include <arpa/inet.h> + +#include <assert.h> +#include <search.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <netdb.h> +#include <stdarg.h> +#include <errno.h> +#include <err.h> +#include <ifaddrs.h> +#include <inttypes.h> +#include <unistd.h> + +#include "pfctl_parser.h" +#include "pfctl.h" + +void print_op (u_int8_t, const char *, const char *); +void print_port (u_int8_t, u_int16_t, u_int16_t, const char *, int); +void print_ugid (u_int8_t, id_t, id_t, const char *); +void print_flags (uint16_t); +void print_fromto(struct pf_rule_addr *, pf_osfp_t, + struct pf_rule_addr *, sa_family_t, u_int8_t, int, int); +int ifa_skip_if(const char *filter, struct node_host *p); + +struct node_host *host_if(const char *, int); +struct node_host *host_ip(const char *, int); +struct node_host *host_dns(const char *, int, int); + +const char * const tcpflags = "FSRPAUEWe"; + +static const struct icmptypeent icmp_type[] = { + { "echoreq", ICMP_ECHO }, + { "echorep", ICMP_ECHOREPLY }, + { "unreach", ICMP_UNREACH }, + { "squench", ICMP_SOURCEQUENCH }, + { "redir", ICMP_REDIRECT }, + { "althost", ICMP_ALTHOSTADDR }, + { "routeradv", ICMP_ROUTERADVERT }, + { "routersol", ICMP_ROUTERSOLICIT }, + { "timex", ICMP_TIMXCEED }, + { "paramprob", ICMP_PARAMPROB }, + { "timereq", ICMP_TSTAMP }, + { "timerep", ICMP_TSTAMPREPLY }, + { "inforeq", ICMP_IREQ }, + { "inforep", ICMP_IREQREPLY }, + { "maskreq", ICMP_MASKREQ }, + { "maskrep", ICMP_MASKREPLY }, + { "trace", ICMP_TRACEROUTE }, + { "dataconv", ICMP_DATACONVERR }, + { "mobredir", ICMP_MOBILE_REDIRECT }, + { "ipv6-where", ICMP_IPV6_WHEREAREYOU }, + { "ipv6-here", ICMP_IPV6_IAMHERE }, + { "mobregreq", ICMP_MOBILE_REGREQUEST }, + { "mobregrep", ICMP_MOBILE_REGREPLY }, + { "skip", ICMP_SKIP }, + { "photuris", ICMP_PHOTURIS } +}; + +static const struct icmptypeent icmp6_type[] = { + { "unreach", ICMP6_DST_UNREACH }, + { "toobig", ICMP6_PACKET_TOO_BIG }, + { "timex", ICMP6_TIME_EXCEEDED }, + { "paramprob", ICMP6_PARAM_PROB }, + { "echoreq", ICMP6_ECHO_REQUEST }, + { "echorep", ICMP6_ECHO_REPLY }, + { "groupqry", ICMP6_MEMBERSHIP_QUERY }, + { "listqry", MLD_LISTENER_QUERY }, + { "grouprep", ICMP6_MEMBERSHIP_REPORT }, + { "listenrep", MLD_LISTENER_REPORT }, + { "groupterm", ICMP6_MEMBERSHIP_REDUCTION }, + { "listendone", MLD_LISTENER_DONE }, + { "routersol", ND_ROUTER_SOLICIT }, + { "routeradv", ND_ROUTER_ADVERT }, + { "neighbrsol", ND_NEIGHBOR_SOLICIT }, + { "neighbradv", ND_NEIGHBOR_ADVERT }, + { "redir", ND_REDIRECT }, + { "routrrenum", ICMP6_ROUTER_RENUMBERING }, + { "wrureq", ICMP6_WRUREQUEST }, + { "wrurep", ICMP6_WRUREPLY }, + { "fqdnreq", ICMP6_FQDN_QUERY }, + { "fqdnrep", ICMP6_FQDN_REPLY }, + { "niqry", ICMP6_NI_QUERY }, + { "nirep", ICMP6_NI_REPLY }, + { "mtraceresp", MLD_MTRACE_RESP }, + { "mtrace", MLD_MTRACE }, + { "listenrepv2", MLDV2_LISTENER_REPORT }, +}; + +static const struct icmpcodeent icmp_code[] = { + { "net-unr", ICMP_UNREACH, ICMP_UNREACH_NET }, + { "host-unr", ICMP_UNREACH, ICMP_UNREACH_HOST }, + { "proto-unr", ICMP_UNREACH, ICMP_UNREACH_PROTOCOL }, + { "port-unr", ICMP_UNREACH, ICMP_UNREACH_PORT }, + { "needfrag", ICMP_UNREACH, ICMP_UNREACH_NEEDFRAG }, + { "srcfail", ICMP_UNREACH, ICMP_UNREACH_SRCFAIL }, + { "net-unk", ICMP_UNREACH, ICMP_UNREACH_NET_UNKNOWN }, + { "host-unk", ICMP_UNREACH, ICMP_UNREACH_HOST_UNKNOWN }, + { "isolate", ICMP_UNREACH, ICMP_UNREACH_ISOLATED }, + { "net-prohib", ICMP_UNREACH, ICMP_UNREACH_NET_PROHIB }, + { "host-prohib", ICMP_UNREACH, ICMP_UNREACH_HOST_PROHIB }, + { "net-tos", ICMP_UNREACH, ICMP_UNREACH_TOSNET }, + { "host-tos", ICMP_UNREACH, ICMP_UNREACH_TOSHOST }, + { "filter-prohib", ICMP_UNREACH, ICMP_UNREACH_FILTER_PROHIB }, + { "host-preced", ICMP_UNREACH, ICMP_UNREACH_HOST_PRECEDENCE }, + { "cutoff-preced", ICMP_UNREACH, ICMP_UNREACH_PRECEDENCE_CUTOFF }, + { "redir-net", ICMP_REDIRECT, ICMP_REDIRECT_NET }, + { "redir-host", ICMP_REDIRECT, ICMP_REDIRECT_HOST }, + { "redir-tos-net", ICMP_REDIRECT, ICMP_REDIRECT_TOSNET }, + { "redir-tos-host", ICMP_REDIRECT, ICMP_REDIRECT_TOSHOST }, + { "normal-adv", ICMP_ROUTERADVERT, ICMP_ROUTERADVERT_NORMAL }, + { "common-adv", ICMP_ROUTERADVERT, ICMP_ROUTERADVERT_NOROUTE_COMMON }, + { "transit", ICMP_TIMXCEED, ICMP_TIMXCEED_INTRANS }, + { "reassemb", ICMP_TIMXCEED, ICMP_TIMXCEED_REASS }, + { "badhead", ICMP_PARAMPROB, ICMP_PARAMPROB_ERRATPTR }, + { "optmiss", ICMP_PARAMPROB, ICMP_PARAMPROB_OPTABSENT }, + { "badlen", ICMP_PARAMPROB, ICMP_PARAMPROB_LENGTH }, + { "unknown-ind", ICMP_PHOTURIS, ICMP_PHOTURIS_UNKNOWN_INDEX }, + { "auth-fail", ICMP_PHOTURIS, ICMP_PHOTURIS_AUTH_FAILED }, + { "decrypt-fail", ICMP_PHOTURIS, ICMP_PHOTURIS_DECRYPT_FAILED } +}; + +static const struct icmpcodeent icmp6_code[] = { + { "admin-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_ADMIN }, + { "noroute-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_NOROUTE }, + { "notnbr-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_NOTNEIGHBOR }, + { "beyond-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_BEYONDSCOPE }, + { "addr-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_ADDR }, + { "port-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_NOPORT }, + { "transit", ICMP6_TIME_EXCEEDED, ICMP6_TIME_EXCEED_TRANSIT }, + { "reassemb", ICMP6_TIME_EXCEEDED, ICMP6_TIME_EXCEED_REASSEMBLY }, + { "badhead", ICMP6_PARAM_PROB, ICMP6_PARAMPROB_HEADER }, + { "nxthdr", ICMP6_PARAM_PROB, ICMP6_PARAMPROB_NEXTHEADER }, + { "redironlink", ND_REDIRECT, ND_REDIRECT_ONLINK }, + { "redirrouter", ND_REDIRECT, ND_REDIRECT_ROUTER } +}; + +const struct pf_timeout pf_timeouts[] = { + { "tcp.first", PFTM_TCP_FIRST_PACKET }, + { "tcp.opening", PFTM_TCP_OPENING }, + { "tcp.established", PFTM_TCP_ESTABLISHED }, + { "tcp.closing", PFTM_TCP_CLOSING }, + { "tcp.finwait", PFTM_TCP_FIN_WAIT }, + { "tcp.closed", PFTM_TCP_CLOSED }, + { "tcp.tsdiff", PFTM_TS_DIFF }, + { "sctp.first", PFTM_SCTP_FIRST_PACKET }, + { "sctp.opening", PFTM_SCTP_OPENING }, + { "sctp.established", PFTM_SCTP_ESTABLISHED }, + { "sctp.closing", PFTM_SCTP_CLOSING }, + { "sctp.closed", PFTM_SCTP_CLOSED }, + { "udp.first", PFTM_UDP_FIRST_PACKET }, + { "udp.single", PFTM_UDP_SINGLE }, + { "udp.multiple", PFTM_UDP_MULTIPLE }, + { "icmp.first", PFTM_ICMP_FIRST_PACKET }, + { "icmp.error", PFTM_ICMP_ERROR_REPLY }, + { "other.first", PFTM_OTHER_FIRST_PACKET }, + { "other.single", PFTM_OTHER_SINGLE }, + { "other.multiple", PFTM_OTHER_MULTIPLE }, + { "frag", PFTM_FRAG }, + { "interval", PFTM_INTERVAL }, + { "adaptive.start", PFTM_ADAPTIVE_START }, + { "adaptive.end", PFTM_ADAPTIVE_END }, + { "src.track", PFTM_SRC_NODE }, + { NULL, 0 } +}; + +static struct hsearch_data isgroup_map; + +static __attribute__((constructor)) void +pfctl_parser_init(void) +{ + /* + * As hdestroy() will never be called on these tables, it will be + * safe to use references into the stored data as keys. + */ + if (hcreate_r(0, &isgroup_map) == 0) + err(1, "Failed to create interface group query response map"); +} + +void +copy_satopfaddr(struct pf_addr *pfa, struct sockaddr *sa) +{ + if (sa->sa_family == AF_INET6) + pfa->v6 = ((struct sockaddr_in6 *)sa)->sin6_addr; + else if (sa->sa_family == AF_INET) + pfa->v4 = ((struct sockaddr_in *)sa)->sin_addr; + else + warnx("unhandled af %d", sa->sa_family); +} + +const struct icmptypeent * +geticmptypebynumber(u_int8_t type, sa_family_t af) +{ + size_t i; + + if (af != AF_INET6) { + for (i=0; i < nitems(icmp_type); i++) { + if (type == icmp_type[i].type) + return (&icmp_type[i]); + } + } else { + for (i=0; i < nitems(icmp6_type); i++) { + if (type == icmp6_type[i].type) + return (&icmp6_type[i]); + } + } + return (NULL); +} + +const struct icmptypeent * +geticmptypebyname(char *w, sa_family_t af) +{ + size_t i; + + if (af != AF_INET6) { + for (i=0; i < nitems(icmp_type); i++) { + if (!strcmp(w, icmp_type[i].name)) + return (&icmp_type[i]); + } + } else { + for (i=0; i < nitems(icmp6_type); i++) { + if (!strcmp(w, icmp6_type[i].name)) + return (&icmp6_type[i]); + } + } + return (NULL); +} + +const struct icmpcodeent * +geticmpcodebynumber(u_int8_t type, u_int8_t code, sa_family_t af) +{ + size_t i; + + if (af != AF_INET6) { + for (i=0; i < nitems(icmp_code); i++) { + if (type == icmp_code[i].type && + code == icmp_code[i].code) + return (&icmp_code[i]); + } + } else { + for (i=0; i < nitems(icmp6_code); i++) { + if (type == icmp6_code[i].type && + code == icmp6_code[i].code) + return (&icmp6_code[i]); + } + } + return (NULL); +} + +const struct icmpcodeent * +geticmpcodebyname(u_long type, char *w, sa_family_t af) +{ + size_t i; + + if (af != AF_INET6) { + for (i=0; i < nitems(icmp_code); i++) { + if (type == icmp_code[i].type && + !strcmp(w, icmp_code[i].name)) + return (&icmp_code[i]); + } + } else { + for (i=0; i < nitems(icmp6_code); i++) { + if (type == icmp6_code[i].type && + !strcmp(w, icmp6_code[i].name)) + return (&icmp6_code[i]); + } + } + return (NULL); +} + +void +print_op(u_int8_t op, const char *a1, const char *a2) +{ + if (op == PF_OP_IRG) + printf(" %s >< %s", a1, a2); + else if (op == PF_OP_XRG) + printf(" %s <> %s", a1, a2); + else if (op == PF_OP_EQ) + printf(" = %s", a1); + else if (op == PF_OP_NE) + printf(" != %s", a1); + else if (op == PF_OP_LT) + printf(" < %s", a1); + else if (op == PF_OP_LE) + printf(" <= %s", a1); + else if (op == PF_OP_GT) + printf(" > %s", a1); + else if (op == PF_OP_GE) + printf(" >= %s", a1); + else if (op == PF_OP_RRG) + printf(" %s:%s", a1, a2); +} + +void +print_port(u_int8_t op, u_int16_t p1, u_int16_t p2, const char *proto, int numeric) +{ + char a1[6], a2[6]; + struct servent *s; + + if (!numeric) + s = getservbyport(p1, proto); + else + s = NULL; + p1 = ntohs(p1); + p2 = ntohs(p2); + snprintf(a1, sizeof(a1), "%u", p1); + snprintf(a2, sizeof(a2), "%u", p2); + printf(" port"); + if (s != NULL && (op == PF_OP_EQ || op == PF_OP_NE)) + print_op(op, s->s_name, a2); + else + print_op(op, a1, a2); +} + +void +print_ugid(u_int8_t op, id_t i1, id_t i2, const char *t) +{ + char a1[11], a2[11]; + + snprintf(a1, sizeof(a1), "%ju", (uintmax_t)i1); + snprintf(a2, sizeof(a2), "%ju", (uintmax_t)i2); + printf(" %s", t); + if (i1 == -1 && (op == PF_OP_EQ || op == PF_OP_NE)) + print_op(op, "unknown", a2); + else + print_op(op, a1, a2); +} + +void +print_flags(uint16_t f) +{ + int i; + + for (i = 0; tcpflags[i]; ++i) + if (f & (1 << i)) + printf("%c", tcpflags[i]); +} + +void +print_fromto(struct pf_rule_addr *src, pf_osfp_t osfp, struct pf_rule_addr *dst, + sa_family_t af, u_int8_t proto, int opts, int numeric) +{ + char buf[PF_OSFP_LEN*3]; + int verbose = opts & (PF_OPT_VERBOSE2 | PF_OPT_DEBUG); + + if (src->addr.type == PF_ADDR_ADDRMASK && + dst->addr.type == PF_ADDR_ADDRMASK && + PF_AZERO(&src->addr.v.a.addr, AF_INET6) && + PF_AZERO(&src->addr.v.a.mask, AF_INET6) && + PF_AZERO(&dst->addr.v.a.addr, AF_INET6) && + PF_AZERO(&dst->addr.v.a.mask, AF_INET6) && + !src->neg && !dst->neg && + !src->port_op && !dst->port_op && + osfp == PF_OSFP_ANY) + printf(" all"); + else { + printf(" from "); + if (src->neg) + printf("! "); + print_addr(&src->addr, af, verbose); + if (src->port_op) + print_port(src->port_op, src->port[0], + src->port[1], + proto == IPPROTO_TCP ? "tcp" : "udp", + numeric); + if (osfp != PF_OSFP_ANY) + printf(" os \"%s\"", pfctl_lookup_fingerprint(osfp, buf, + sizeof(buf))); + + printf(" to "); + if (dst->neg) + printf("! "); + print_addr(&dst->addr, af, verbose); + if (dst->port_op) + print_port(dst->port_op, dst->port[0], + dst->port[1], + proto == IPPROTO_TCP ? "tcp" : "udp", + numeric); + } +} + +void +print_pool(struct pfctl_pool *pool, u_int16_t p1, u_int16_t p2, int id) +{ + struct pfctl_pooladdr *pooladdr; + + if ((TAILQ_FIRST(&pool->list) != NULL) && + TAILQ_NEXT(TAILQ_FIRST(&pool->list), entries) != NULL) + printf("{ "); + TAILQ_FOREACH(pooladdr, &pool->list, entries){ + switch (id) { + case PF_NAT: + case PF_RDR: + case PF_BINAT: + print_addr(&pooladdr->addr, pooladdr->af, 0); + break; + case PF_PASS: + case PF_MATCH: + if (PF_AZERO(&pooladdr->addr.v.a.addr, pooladdr->af)) + printf("%s", pooladdr->ifname); + else { + printf("(%s ", pooladdr->ifname); + print_addr(&pooladdr->addr, pooladdr->af, 0); + printf(")"); + } + break; + default: + break; + } + if (TAILQ_NEXT(pooladdr, entries) != NULL) + printf(", "); + else if (TAILQ_NEXT(TAILQ_FIRST(&pool->list), entries) != NULL) + printf(" }"); + } + switch (id) { + case PF_NAT: + if ((p1 != PF_NAT_PROXY_PORT_LOW || + p2 != PF_NAT_PROXY_PORT_HIGH) && (p1 != 0 || p2 != 0)) { + if (p1 == p2) + printf(" port %u", p1); + else + printf(" port %u:%u", p1, p2); + } + break; + case PF_RDR: + if (p1) { + printf(" port %u", p1); + if (p2 && (p2 != p1)) + printf(":%u", p2); + } + break; + default: + break; + } + switch (pool->opts & PF_POOL_TYPEMASK) { + case PF_POOL_NONE: + break; + case PF_POOL_BITMASK: + printf(" bitmask"); + break; + case PF_POOL_RANDOM: + printf(" random"); + break; + case PF_POOL_SRCHASH: + printf(" source-hash 0x%08x%08x%08x%08x", + pool->key.key32[0], pool->key.key32[1], + pool->key.key32[2], pool->key.key32[3]); + break; + case PF_POOL_ROUNDROBIN: + printf(" round-robin"); + break; + } + if (pool->opts & PF_POOL_STICKYADDR) + printf(" sticky-address"); + if (pool->opts & PF_POOL_ENDPI) + printf(" endpoint-independent"); + if (id == PF_NAT && p1 == 0 && p2 == 0) + printf(" static-port"); + if (pool->mape.offset > 0) + printf(" map-e-portset %u/%u/%u", + pool->mape.offset, pool->mape.psidlen, pool->mape.psid); + if (pool->opts & PF_POOL_IPV6NH) + printf(" prefer-ipv6-nexthop"); +} + +void +print_status(struct pfctl_status *s, struct pfctl_syncookies *cookies, int opts) +{ + struct pfctl_status_counter *c; + char statline[80], *running; + time_t runtime; + int i; + char buf[PF_MD5_DIGEST_LENGTH * 2 + 1]; + static const char hex[] = "0123456789abcdef"; + + runtime = time(NULL) - s->since; + running = s->running ? "Enabled" : "Disabled"; + + if (s->since) { + unsigned int sec, min, hrs; + time_t day = runtime; + + sec = day % 60; + day /= 60; + min = day % 60; + day /= 60; + hrs = day % 24; + day /= 24; + snprintf(statline, sizeof(statline), + "Status: %s for %lld days %.2u:%.2u:%.2u", + running, (long long)day, hrs, min, sec); + } else + snprintf(statline, sizeof(statline), "Status: %s", running); + printf("%-44s", statline); + switch (s->debug) { + case PF_DEBUG_NONE: + printf("%15s\n\n", "Debug: None"); + break; + case PF_DEBUG_URGENT: + printf("%15s\n\n", "Debug: Urgent"); + break; + case PF_DEBUG_MISC: + printf("%15s\n\n", "Debug: Misc"); + break; + case PF_DEBUG_NOISY: + printf("%15s\n\n", "Debug: Loud"); + break; + } + + if (opts & PF_OPT_VERBOSE) { + printf("Hostid: 0x%08x\n", s->hostid); + + for (i = 0; i < PF_MD5_DIGEST_LENGTH; i++) { + buf[i + i] = hex[s->pf_chksum[i] >> 4]; + buf[i + i + 1] = hex[s->pf_chksum[i] & 0x0f]; + } + buf[i + i] = '\0'; + printf("Checksum: 0x%s\n\n", buf); + } + + if (s->ifname[0] != 0) { + printf("Interface Stats for %-16s %5s %16s\n", + s->ifname, "IPv4", "IPv6"); + printf(" %-25s %14llu %16llu\n", "Bytes In", + (unsigned long long)s->bcounters[0][0], + (unsigned long long)s->bcounters[1][0]); + printf(" %-25s %14llu %16llu\n", "Bytes Out", + (unsigned long long)s->bcounters[0][1], + (unsigned long long)s->bcounters[1][1]); + printf(" Packets In\n"); + printf(" %-23s %14llu %16llu\n", "Passed", + (unsigned long long)s->pcounters[0][0][PF_PASS], + (unsigned long long)s->pcounters[1][0][PF_PASS]); + printf(" %-23s %14llu %16llu\n", "Blocked", + (unsigned long long)s->pcounters[0][0][PF_DROP], + (unsigned long long)s->pcounters[1][0][PF_DROP]); + printf(" Packets Out\n"); + printf(" %-23s %14llu %16llu\n", "Passed", + (unsigned long long)s->pcounters[0][1][PF_PASS], + (unsigned long long)s->pcounters[1][1][PF_PASS]); + printf(" %-23s %14llu %16llu\n\n", "Blocked", + (unsigned long long)s->pcounters[0][1][PF_DROP], + (unsigned long long)s->pcounters[1][1][PF_DROP]); + } + printf("%-27s %14s %16s\n", "State Table", "Total", "Rate"); + printf(" %-25s %14ju %14s\n", "current entries", s->states, ""); + TAILQ_FOREACH(c, &s->fcounters, entry) { + printf(" %-25s %14ju ", c->name, c->counter); + if (runtime > 0) + printf("%14.1f/s\n", + (double)c->counter / (double)runtime); + else + printf("%14s\n", ""); + } + if (opts & PF_OPT_VERBOSE) { + printf("Source Tracking Table\n"); + printf(" %-25s %14ju %14s\n", "current entries", + s->src_nodes, ""); + TAILQ_FOREACH(c, &s->scounters, entry) { + printf(" %-25s %14ju ", c->name, c->counter); + if (runtime > 0) + printf("%14.1f/s\n", + (double)c->counter / (double)runtime); + else + printf("%14s\n", ""); + } + } + if (opts & PF_OPT_VERBOSE) { + printf("Fragments\n"); + printf(" %-25s %14ju %14s\n", "current entries", + s->fragments, ""); + TAILQ_FOREACH(c, &s->ncounters, entry) { + printf(" %-25s %14ju ", c->name, + c->counter); + if (runtime > 0) + printf("%14.1f/s\n", + (double)c->counter / (double)runtime); + else + printf("%14s\n", ""); + } + } + printf("Counters\n"); + TAILQ_FOREACH(c, &s->counters, entry) { + printf(" %-25s %14ju ", c->name, c->counter); + if (runtime > 0) + printf("%14.1f/s\n", + (double)c->counter / (double)runtime); + else + printf("%14s\n", ""); + } + if (opts & PF_OPT_VERBOSE) { + printf("Limit Counters\n"); + TAILQ_FOREACH(c, &s->lcounters, entry) { + printf(" %-25s %14ju ", c->name, c->counter); + if (runtime > 0) + printf("%14.1f/s\n", + (double)c->counter / (double)runtime); + else + printf("%14s\n", ""); + } + + printf("Syncookies\n"); + assert(cookies->mode <= PFCTL_SYNCOOKIES_ADAPTIVE); + printf(" %-25s %s\n", "mode", + PFCTL_SYNCOOKIES_MODE_NAMES[cookies->mode]); + printf(" %-25s %s\n", "active", + s->syncookies_active ? "active" : "inactive"); + if (opts & PF_OPT_VERBOSE2) { + printf(" %-25s %d %%\n", "highwater", cookies->highwater); + printf(" %-25s %d %%\n", "lowwater", cookies->lowwater); + printf(" %-25s %d\n", "halfopen states", cookies->halfopen_states); + } + printf("Reassemble %24s %s\n", + s->reass & PF_REASS_ENABLED ? "yes" : "no", + s->reass & PF_REASS_NODF ? "no-df" : "" + ); + } +} + +void +print_running(struct pfctl_status *status) +{ + printf("%s\n", status->running ? "Enabled" : "Disabled"); +} + +void +print_src_node(struct pfctl_src_node *sn, int opts) +{ + struct pf_addr_wrap aw; + uint64_t min, sec; + const char *sn_type_names[] = PF_SN_TYPE_NAMES; + + memset(&aw, 0, sizeof(aw)); + if (sn->af == AF_INET) + aw.v.a.mask.addr32[0] = 0xffffffff; + else + memset(&aw.v.a.mask, 0xff, sizeof(aw.v.a.mask)); + + aw.v.a.addr = sn->addr; + print_addr(&aw, sn->af, opts & PF_OPT_VERBOSE2); + printf(" -> "); + aw.v.a.addr = sn->raddr; + print_addr(&aw, sn->raf, opts & PF_OPT_VERBOSE2); + printf(" ( states %u, connections %u, rate %u.%u/%us )\n", sn->states, + sn->conn, sn->conn_rate.count / 1000, + (sn->conn_rate.count % 1000) / 100, sn->conn_rate.seconds); + if (opts & PF_OPT_VERBOSE) { + sec = sn->creation % 60; + sn->creation /= 60; + min = sn->creation % 60; + sn->creation /= 60; + printf(" age %.2" PRIu64 ":%.2" PRIu64 ":%.2" PRIu64, + sn->creation, min, sec); + if (sn->states == 0) { + sec = sn->expire % 60; + sn->expire /= 60; + min = sn->expire % 60; + sn->expire /= 60; + printf(", expires in %.2" PRIu64 ":%.2" PRIu64 ":%.2" PRIu64, + sn->expire, min, sec); + } + printf(", %" PRIu64 " pkts, %" PRIu64 " bytes", + sn->packets[0] + sn->packets[1], + sn->bytes[0] + sn->bytes[1]); + switch (sn->ruletype) { + case PF_NAT: + if (sn->rule != -1) + printf(", nat rule %u", sn->rule); + break; + case PF_RDR: + if (sn->rule != -1) + printf(", rdr rule %u", sn->rule); + break; + case PF_PASS: + case PF_MATCH: + if (sn->rule != -1) + printf(", filter rule %u", sn->rule); + break; + } + printf(", %s", sn_type_names[sn->type]); + printf("\n"); + } +} + +static void +print_eth_addr(const struct pfctl_eth_addr *a) +{ + int i, masklen = ETHER_ADDR_LEN * 8; + bool seen_unset = false; + + for (i = 0; i < ETHER_ADDR_LEN; i++) { + if (a->addr[i] != 0) + break; + } + + /* Unset, so don't print anything. */ + if (i == ETHER_ADDR_LEN) + return; + + printf("%s%02x:%02x:%02x:%02x:%02x:%02x", a->neg ? "! " : "", + a->addr[0], a->addr[1], a->addr[2], a->addr[3], a->addr[4], + a->addr[5]); + + for (i = 0; i < (ETHER_ADDR_LEN * 8); i++) { + bool isset = a->mask[i / 8] & (1 << i % 8); + + if (! seen_unset) { + if (isset) + continue; + seen_unset = true; + masklen = i; + } else { + /* Not actually a continuous mask, so print the whole + * thing. */ + if (isset) + break; + continue; + } + } + + if (masklen == (ETHER_ADDR_LEN * 8)) + return; + + if (i == (ETHER_ADDR_LEN * 8)) { + printf("/%d", masklen); + return; + } + + printf("&%02x:%02x:%02x:%02x:%02x:%02x", + a->mask[0], a->mask[1], a->mask[2], a->mask[3], a->mask[4], + a->mask[5]); +} + +void +print_eth_rule(struct pfctl_eth_rule *r, const char *anchor_call, + int rule_numbers) +{ + static const char *actiontypes[] = { "pass", "block", "", "", "", "", + "", "", "", "", "", "", "match" }; + + int i; + + if (rule_numbers) + printf("@%u ", r->nr); + + printf("ether "); + if (anchor_call[0]) { + if (anchor_call[0] == '_') { + printf("anchor"); + } else + printf("anchor \"%s\"", anchor_call); + } else { + printf("%s", actiontypes[r->action]); + } + if (r->direction == PF_IN) + printf(" in"); + else if (r->direction == PF_OUT) + printf(" out"); + + if (r->quick) + printf(" quick"); + if (r->ifname[0]) { + if (r->ifnot) + printf(" on ! %s", r->ifname); + else + printf(" on %s", r->ifname); + } + if (r->bridge_to[0]) + printf(" bridge-to %s", r->bridge_to); + if (r->proto) + printf(" proto 0x%04x", r->proto); + + if (r->src.isset) { + printf(" from "); + print_eth_addr(&r->src); + } + if (r->dst.isset) { + printf(" to "); + print_eth_addr(&r->dst); + } + printf(" l3"); + print_fromto(&r->ipsrc, PF_OSFP_ANY, &r->ipdst, + r->proto == ETHERTYPE_IP ? AF_INET : AF_INET6, 0, + 0, 0); + + i = 0; + while (r->label[i][0]) + printf(" label \"%s\"", r->label[i++]); + if (r->ridentifier) + printf(" ridentifier %u", r->ridentifier); + + if (r->qname[0]) + printf(" queue %s", r->qname); + if (r->tagname[0]) + printf(" tag %s", r->tagname); + if (r->match_tagname[0]) { + if (r->match_tag_not) + printf(" !"); + printf(" tagged %s", r->match_tagname); + } + if (r->dnpipe) + printf(" %s %d", + r->dnflags & PFRULE_DN_IS_PIPE ? "dnpipe" : "dnqueue", + r->dnpipe); +} + +void +print_rule(struct pfctl_rule *r, const char *anchor_call, int opts, int numeric) +{ + static const char *actiontypes[] = { "pass", "block", "scrub", + "no scrub", "nat", "no nat", "binat", "no binat", "rdr", "no rdr", + "synproxy drop", "defer", "match", "af-rt", "route-to" }; + static const char *anchortypes[] = { "anchor", "anchor", "anchor", + "anchor", "nat-anchor", "nat-anchor", "binat-anchor", + "binat-anchor", "rdr-anchor", "rdr-anchor" }; + int i, ropts; + int verbose = opts & (PF_OPT_VERBOSE2 | PF_OPT_DEBUG); + char *p; + + if ((r->rule_flag & PFRULE_EXPIRED) && (!verbose)) + return; + + if (verbose) + printf("@%d ", r->nr); + if (anchor_call[0]) { + if (r->action >= nitems(anchortypes)) { + printf("anchor(%d)", r->action); + } else { + p = strrchr(anchor_call, '/'); + if (p ? p[1] == '_' : anchor_call[0] == '_') + printf("%s", anchortypes[r->action]); + else + printf("%s \"%s\"", anchortypes[r->action], + anchor_call); + } + } else { + if (r->action >= nitems(actiontypes)) + printf("action(%d)", r->action); + else + printf("%s", actiontypes[r->action]); + } + if (r->action == PF_DROP) { + if (r->rule_flag & PFRULE_RETURN) + printf(" return"); + else if (r->rule_flag & PFRULE_RETURNRST) { + if (!r->return_ttl) + printf(" return-rst"); + else + printf(" return-rst(ttl %d)", r->return_ttl); + } else if (r->rule_flag & PFRULE_RETURNICMP) { + const struct icmpcodeent *ic, *ic6; + + ic = geticmpcodebynumber(r->return_icmp >> 8, + r->return_icmp & 255, AF_INET); + ic6 = geticmpcodebynumber(r->return_icmp6 >> 8, + r->return_icmp6 & 255, AF_INET6); + + switch (r->af) { + case AF_INET: + printf(" return-icmp"); + if (ic == NULL) + printf("(%u)", r->return_icmp & 255); + else + printf("(%s)", ic->name); + break; + case AF_INET6: + printf(" return-icmp6"); + if (ic6 == NULL) + printf("(%u)", r->return_icmp6 & 255); + else + printf("(%s)", ic6->name); + break; + default: + printf(" return-icmp"); + if (ic == NULL) + printf("(%u, ", r->return_icmp & 255); + else + printf("(%s, ", ic->name); + if (ic6 == NULL) + printf("%u)", r->return_icmp6 & 255); + else + printf("%s)", ic6->name); + break; + } + } else + printf(" drop"); + } + if (r->direction == PF_IN) + printf(" in"); + else if (r->direction == PF_OUT) + printf(" out"); + if (r->log) { + printf(" log"); + if (r->log & ~PF_LOG || r->logif) { + int count = 0; + + printf(" ("); + if (r->log & PF_LOG_ALL) + printf("%sall", count++ ? ", " : ""); + if (r->log & PF_LOG_MATCHES) + printf("%smatches", count++ ? ", " : ""); + if (r->log & PF_LOG_USER) + printf("%suser", count++ ? ", " : ""); + if (r->logif) + printf("%sto pflog%u", count++ ? ", " : "", + r->logif); + printf(")"); + } + } + if (r->quick) + printf(" quick"); + if (r->ifname[0]) { + if (r->ifnot) + printf(" on ! %s", r->ifname); + else + printf(" on %s", r->ifname); + } + if (r->rt) { + if (r->rt == PF_ROUTETO) + printf(" route-to"); + else if (r->rt == PF_REPLYTO) + printf(" reply-to"); + else if (r->rt == PF_DUPTO) + printf(" dup-to"); + printf(" "); + print_pool(&r->route, 0, 0, PF_PASS); + } + if (r->af) { + if (r->af == AF_INET) + printf(" inet"); + else + printf(" inet6"); + } + if (r->proto) { + const char *protoname; + + if ((protoname = pfctl_proto2name(r->proto)) != NULL) + printf(" proto %s", protoname); + else + printf(" proto %u", r->proto); + } + print_fromto(&r->src, r->os_fingerprint, &r->dst, r->af, r->proto, + opts, numeric); + if (r->rcv_ifname[0]) + printf(" %sreceived-on %s", r->rcvifnot ? "!" : "", + r->rcv_ifname); + if (r->uid.op) + print_ugid(r->uid.op, r->uid.uid[0], r->uid.uid[1], "user"); + if (r->gid.op) + print_ugid(r->gid.op, r->gid.gid[0], r->gid.gid[1], "group"); + if (r->flags || r->flagset) { + printf(" flags "); + print_flags(r->flags); + printf("/"); + print_flags(r->flagset); + } else if ((r->action == PF_PASS || r->action == PF_MATCH) && + (!r->proto || r->proto == IPPROTO_TCP) && + !(r->rule_flag & PFRULE_FRAGMENT) && + !anchor_call[0] && r->keep_state) + printf(" flags any"); + if (r->type) { + const struct icmptypeent *it; + + it = geticmptypebynumber(r->type-1, r->af); + if (r->af != AF_INET6) + printf(" icmp-type"); + else + printf(" icmp6-type"); + if (it != NULL) + printf(" %s", it->name); + else + printf(" %u", r->type-1); + if (r->code) { + const struct icmpcodeent *ic; + + ic = geticmpcodebynumber(r->type-1, r->code-1, r->af); + if (ic != NULL) + printf(" code %s", ic->name); + else + printf(" code %u", r->code-1); + } + } + if (r->tos) + printf(" tos 0x%2.2x", r->tos); + if (r->prio) + printf(" prio %u", r->prio == PF_PRIO_ZERO ? 0 : r->prio); + if (r->pktrate.limit) + printf(" max-pkt-rate %u/%u", r->pktrate.limit, + r->pktrate.seconds); + if (r->max_pkt_size) + printf( " max-pkt-size %u", r->max_pkt_size); + if (r->scrub_flags & PFSTATE_SETMASK) { + char *comma = ""; + printf(" set ("); + if (r->scrub_flags & PFSTATE_SETPRIO) { + if (r->set_prio[0] == r->set_prio[1]) + printf("%s prio %u", comma, r->set_prio[0]); + else + printf("%s prio(%u, %u)", comma, r->set_prio[0], + r->set_prio[1]); + comma = ","; + } + if (r->scrub_flags & PFSTATE_SETTOS) { + printf("%s tos 0x%2.2x", comma, r->set_tos); + comma = ","; + } + printf(" )"); + } + if (!r->keep_state && r->action == PF_PASS && !anchor_call[0]) + printf(" no state"); + else if (r->keep_state == PF_STATE_NORMAL) + printf(" keep state"); + else if (r->keep_state == PF_STATE_MODULATE) + printf(" modulate state"); + else if (r->keep_state == PF_STATE_SYNPROXY) + printf(" synproxy state"); + if (r->prob) { + char buf[20]; + + snprintf(buf, sizeof(buf), "%f", r->prob*100.0/(UINT_MAX+1.0)); + for (i = strlen(buf)-1; i > 0; i--) { + if (buf[i] == '0') + buf[i] = '\0'; + else { + if (buf[i] == '.') + buf[i] = '\0'; + break; + } + } + printf(" probability %s%%", buf); + } + ropts = 0; + if (r->max_states || r->max_src_nodes || r->max_src_states) + ropts = 1; + if (r->rule_flag & PFRULE_NOSYNC) + ropts = 1; + if (r->rule_flag & PFRULE_SRCTRACK) + ropts = 1; + if (r->rule_flag & PFRULE_IFBOUND) + ropts = 1; + if (r->rule_flag & PFRULE_STATESLOPPY) + ropts = 1; + if (r->rule_flag & PFRULE_PFLOW) + ropts = 1; + for (i = 0; !ropts && i < PFTM_MAX; ++i) + if (r->timeout[i]) + ropts = 1; + if (ropts) { + printf(" ("); + if (r->max_states) { + printf("max %u", r->max_states); + ropts = 0; + } + if (r->rule_flag & PFRULE_NOSYNC) { + if (!ropts) + printf(", "); + printf("no-sync"); + ropts = 0; + } + if (r->rule_flag & PFRULE_SRCTRACK) { + if (!ropts) + printf(", "); + printf("source-track"); + if (r->rule_flag & PFRULE_RULESRCTRACK) + printf(" rule"); + else + printf(" global"); + ropts = 0; + } + if (r->max_src_states) { + if (!ropts) + printf(", "); + printf("max-src-states %u", r->max_src_states); + ropts = 0; + } + if (r->max_src_conn) { + if (!ropts) + printf(", "); + printf("max-src-conn %u", r->max_src_conn); + ropts = 0; + } + if (r->max_src_conn_rate.limit) { + if (!ropts) + printf(", "); + printf("max-src-conn-rate %u/%u", + r->max_src_conn_rate.limit, + r->max_src_conn_rate.seconds); + ropts = 0; + } + if (r->max_src_nodes) { + if (!ropts) + printf(", "); + printf("max-src-nodes %u", r->max_src_nodes); + ropts = 0; + } + if (r->overload_tblname[0]) { + if (!ropts) + printf(", "); + printf("overload <%s>", r->overload_tblname); + if (r->flush) + printf(" flush"); + if (r->flush & PF_FLUSH_GLOBAL) + printf(" global"); + } + if (r->rule_flag & PFRULE_IFBOUND) { + if (!ropts) + printf(", "); + printf("if-bound"); + ropts = 0; + } + if (r->rule_flag & PFRULE_STATESLOPPY) { + if (!ropts) + printf(", "); + printf("sloppy"); + ropts = 0; + } + if (r->rule_flag & PFRULE_PFLOW) { + if (!ropts) + printf(", "); + printf("pflow"); + ropts = 0; + } + for (i = 0; i < PFTM_MAX; ++i) + if (r->timeout[i]) { + int j; + + if (!ropts) + printf(", "); + ropts = 0; + for (j = 0; pf_timeouts[j].name != NULL; + ++j) + if (pf_timeouts[j].timeout == i) + break; + printf("%s %u", pf_timeouts[j].name == NULL ? + "inv.timeout" : pf_timeouts[j].name, + r->timeout[i]); + } + printf(")"); + } + if (r->allow_opts) + printf(" allow-opts"); + if (r->rule_flag & PFRULE_FRAGMENT) + printf(" fragment"); + if (r->action == PF_SCRUB) { + /* Scrub flags for old-style scrub. */ + if (r->rule_flag & PFRULE_NODF) + printf(" no-df"); + if (r->rule_flag & PFRULE_RANDOMID) + printf(" random-id"); + if (r->min_ttl) + printf(" min-ttl %d", r->min_ttl); + if (r->max_mss) + printf(" max-mss %d", r->max_mss); + if (r->rule_flag & PFRULE_SET_TOS) + printf(" set-tos 0x%2.2x", r->set_tos); + if (r->rule_flag & PFRULE_REASSEMBLE_TCP) + printf(" reassemble tcp"); + /* The PFRULE_FRAGMENT_NOREASS is set on all rules by default! */ + printf(" fragment %sreassemble", + r->rule_flag & PFRULE_FRAGMENT_NOREASS ? "no " : ""); + } else if (r->scrub_flags & PFSTATE_SCRUBMASK || r->min_ttl || r->max_mss) { + /* Scrub actions on normal rules. */ + printf(" scrub("); + if (r->scrub_flags & PFSTATE_NODF) + printf(" no-df"); + if (r->scrub_flags & PFSTATE_RANDOMID) + printf(" random-id"); + if (r->min_ttl) + printf(" min-ttl %d", r->min_ttl); + if (r->scrub_flags & PFSTATE_SETTOS) + printf(" set-tos 0x%2.2x", r->set_tos); + if (r->scrub_flags & PFSTATE_SCRUB_TCP) + printf(" reassemble tcp"); + if (r->max_mss) + printf(" max-mss %d", r->max_mss); + printf(")"); + } + i = 0; + while (r->label[i][0]) + printf(" label \"%s\"", r->label[i++]); + if (r->ridentifier) + printf(" ridentifier %u", r->ridentifier); + /* Only dnrpipe as we might do (0, 42) to only queue return traffic. */ + if (r->dnrpipe) + printf(" %s(%d, %d)", + r->free_flags & PFRULE_DN_IS_PIPE ? "dnpipe" : "dnqueue", + r->dnpipe, r->dnrpipe); + else if (r->dnpipe) + printf(" %s %d", + r->free_flags & PFRULE_DN_IS_PIPE ? "dnpipe" : "dnqueue", + r->dnpipe); + if (r->rule_flag & PFRULE_ONCE) + printf(" once"); + if (r->qname[0] && r->pqname[0]) + printf(" queue(%s, %s)", r->qname, r->pqname); + else if (r->qname[0]) + printf(" queue %s", r->qname); + if (r->tagname[0]) + printf(" tag %s", r->tagname); + if (r->match_tagname[0]) { + if (r->match_tag_not) + printf(" !"); + printf(" tagged %s", r->match_tagname); + } + if (r->rtableid != -1) + printf(" rtable %u", r->rtableid); + if (r->divert.port) { +#ifdef __FreeBSD__ + printf(" divert-to %u", ntohs(r->divert.port)); +#else + if (PF_AZERO(&r->divert.addr, r->af)) { + printf(" divert-reply"); + } else { + printf(" divert-to "); + print_addr_str(r->af, &r->divert.addr); + printf(" port %u", ntohs(r->divert.port)); + } +#endif + } + if (anchor_call[0]) + return; + if (r->action == PF_NAT || r->action == PF_BINAT || r->action == PF_RDR) { + printf(" -> "); + print_pool(&r->rdr, r->rdr.proxy_port[0], + r->rdr.proxy_port[1], r->action); + } else { + if (!TAILQ_EMPTY(&r->nat.list)) { + if (r->rule_flag & PFRULE_AFTO) { + printf(" af-to %s from ", r->naf == AF_INET ? "inet" : (r->naf == AF_INET6 ? "inet6" : "? ")); + } else { + printf(" nat-to "); + } + print_pool(&r->nat, r->nat.proxy_port[0], + r->nat.proxy_port[1], PF_NAT); + } + if (!TAILQ_EMPTY(&r->rdr.list)) { + if (r->rule_flag & PFRULE_AFTO) { + printf(" to "); + } else { + printf(" rdr-to "); + } + print_pool(&r->rdr, r->rdr.proxy_port[0], + r->rdr.proxy_port[1], PF_RDR); + } + } + + if (r->rule_flag & PFRULE_EXPIRED) { + printf(" # expired"); + + if (r->exptime != 0) + printf(" %s", ctime(&r->exptime)); + } +} + +void +print_tabledef(const char *name, int flags, int addrs, + struct node_tinithead *nodes) +{ + struct node_tinit *ti, *nti; + struct node_host *h; + + printf("table <%s>", name); + if (flags & PFR_TFLAG_CONST) + printf(" const"); + if (flags & PFR_TFLAG_PERSIST) + printf(" persist"); + if (flags & PFR_TFLAG_COUNTERS) + printf(" counters"); + SIMPLEQ_FOREACH(ti, nodes, entries) { + if (ti->file) { + printf(" file \"%s\"", ti->file); + continue; + } + printf(" {"); + for (;;) { + for (h = ti->host; h != NULL; h = h->next) { + printf(h->not ? " !" : " "); + print_addr(&h->addr, h->af, 0); + } + nti = SIMPLEQ_NEXT(ti, entries); + if (nti != NULL && nti->file == NULL) + ti = nti; /* merge lists */ + else + break; + } + printf(" }"); + } + if (addrs && SIMPLEQ_EMPTY(nodes)) + printf(" { }"); + printf("\n"); +} + +int +parse_flags(char *s) +{ + char *p, *q; + uint16_t f = 0; + + for (p = s; *p; p++) { + if ((q = strchr(tcpflags, *p)) == NULL) + return -1; + else + f |= 1 << (q - tcpflags); + } + return (f ? f : TH_FLAGS); +} + +void +set_ipmask(struct node_host *h, int bb) +{ + struct pf_addr *m, *n; + int i, j = 0; + uint8_t b; + + m = &h->addr.v.a.mask; + memset(m, 0, sizeof(*m)); + + if (bb == -1) + b = h->af == AF_INET ? 32 : 128; + else + b = bb; + + while (b >= 32) { + m->addr32[j++] = 0xffffffff; + b -= 32; + } + for (i = 31; i > 31-b; --i) + m->addr32[j] |= (1 << i); + if (b) + m->addr32[j] = htonl(m->addr32[j]); + + /* Mask off bits of the address that will never be used. */ + n = &h->addr.v.a.addr; + if (h->addr.type == PF_ADDR_ADDRMASK) + for (i = 0; i < 4; i++) + n->addr32[i] = n->addr32[i] & m->addr32[i]; +} + +int +check_netmask(struct node_host *h, sa_family_t af) +{ + struct node_host *n = NULL; + struct pf_addr *m; + + for (n = h; n != NULL; n = n->next) { + if (h->addr.type == PF_ADDR_TABLE) + continue; + m = &h->addr.v.a.mask; + /* netmasks > 32 bit are invalid on v4 */ + if (af == AF_INET && + (m->addr32[1] || m->addr32[2] || m->addr32[3])) { + fprintf(stderr, "netmask %u invalid for IPv4 address\n", + unmask(m)); + return (1); + } + } + return (0); +} + +struct node_host * +gen_dynnode(struct node_host *h, sa_family_t af) +{ + struct node_host *n; + + if (h->addr.type != PF_ADDR_DYNIFTL) + return (NULL); + + if ((n = calloc(1, sizeof(*n))) == NULL) + return (NULL); + bcopy(h, n, sizeof(*n)); + n->ifname = NULL; + n->next = NULL; + n->tail = NULL; + + /* fix up netmask */ + if (af == AF_INET && unmask(&n->addr.v.a.mask) > 32) + set_ipmask(n, 32); + + return (n); +} + +/* interface lookup routines */ + +static struct node_host *iftab; + +/* + * Retrieve the list of groups this interface is a member of and make sure + * each group is in the group map. + */ +static void +ifa_add_groups_to_map(char *ifa_name) +{ + int s, len; + struct ifgroupreq ifgr; + struct ifg_req *ifg; + + s = get_query_socket(); + + /* Get size of group list for this interface */ + memset(&ifgr, 0, sizeof(ifgr)); + strlcpy(ifgr.ifgr_name, ifa_name, IFNAMSIZ); + if (ioctl(s, SIOCGIFGROUP, (caddr_t)&ifgr) == -1) + err(1, "SIOCGIFGROUP"); + + /* Retrieve group list for this interface */ + len = ifgr.ifgr_len; + ifgr.ifgr_groups = + (struct ifg_req *)calloc(len / sizeof(struct ifg_req), + sizeof(struct ifg_req)); + if (ifgr.ifgr_groups == NULL) + err(1, "calloc"); + if (ioctl(s, SIOCGIFGROUP, (caddr_t)&ifgr) == -1) + err(1, "SIOCGIFGROUP"); + + ifg = ifgr.ifgr_groups; + for (; ifg && len >= sizeof(struct ifg_req); ifg++) { + len -= sizeof(struct ifg_req); + if (strcmp(ifg->ifgrq_group, "all")) { + ENTRY item; + ENTRY *ret_item; + int *answer; + + item.key = ifg->ifgrq_group; + if (hsearch_r(item, FIND, &ret_item, &isgroup_map) == 0) { + struct ifgroupreq ifgr2; + + /* Don't know the answer yet */ + if ((answer = malloc(sizeof(int))) == NULL) + err(1, "malloc"); + + bzero(&ifgr2, sizeof(ifgr2)); + strlcpy(ifgr2.ifgr_name, ifg->ifgrq_group, + sizeof(ifgr2.ifgr_name)); + if (ioctl(s, SIOCGIFGMEMB, (caddr_t)&ifgr2) == 0) + *answer = ifgr2.ifgr_len; + else + *answer = 0; + + item.key = strdup(ifg->ifgrq_group); + item.data = answer; + if (hsearch_r(item, ENTER, &ret_item, + &isgroup_map) == 0) + err(1, "interface group query response" + " map insert"); + } + } + } + free(ifgr.ifgr_groups); +} + +void +ifa_load(void) +{ + struct ifaddrs *ifap, *ifa; + struct node_host *n = NULL, *h = NULL; + + if (getifaddrs(&ifap) < 0) + err(1, "getifaddrs"); + + for (ifa = ifap; ifa; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == NULL || + !(ifa->ifa_addr->sa_family == AF_INET || + ifa->ifa_addr->sa_family == AF_INET6 || + ifa->ifa_addr->sa_family == AF_LINK)) + continue; + n = calloc(1, sizeof(struct node_host)); + if (n == NULL) + err(1, "%s: calloc", __func__); + n->af = ifa->ifa_addr->sa_family; + n->ifa_flags = ifa->ifa_flags; +#ifdef __KAME__ + if (n->af == AF_INET6 && + IN6_IS_ADDR_LINKLOCAL(&((struct sockaddr_in6 *) + ifa->ifa_addr)->sin6_addr) && + ((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_scope_id == + 0) { + struct sockaddr_in6 *sin6; + + sin6 = (struct sockaddr_in6 *)ifa->ifa_addr; + sin6->sin6_scope_id = sin6->sin6_addr.s6_addr[2] << 8 | + sin6->sin6_addr.s6_addr[3]; + sin6->sin6_addr.s6_addr[2] = 0; + sin6->sin6_addr.s6_addr[3] = 0; + } +#endif + n->ifindex = 0; + if (n->af == AF_LINK) { + n->ifindex = ((struct sockaddr_dl *) + ifa->ifa_addr)->sdl_index; + ifa_add_groups_to_map(ifa->ifa_name); + } else { + copy_satopfaddr(&n->addr.v.a.addr, ifa->ifa_addr); + ifa->ifa_netmask->sa_family = ifa->ifa_addr->sa_family; + copy_satopfaddr(&n->addr.v.a.mask, ifa->ifa_netmask); + if (ifa->ifa_broadaddr != NULL) { + ifa->ifa_broadaddr->sa_family = ifa->ifa_addr->sa_family; + copy_satopfaddr(&n->bcast, ifa->ifa_broadaddr); + } + if (ifa->ifa_dstaddr != NULL) { + ifa->ifa_dstaddr->sa_family = ifa->ifa_addr->sa_family; + copy_satopfaddr(&n->peer, ifa->ifa_dstaddr); + } + if (n->af == AF_INET6) + n->ifindex = ((struct sockaddr_in6 *) + ifa->ifa_addr) ->sin6_scope_id; + } + if ((n->ifname = strdup(ifa->ifa_name)) == NULL) + err(1, "%s: strdup", __func__); + n->next = NULL; + n->tail = n; + if (h == NULL) + h = n; + else { + h->tail->next = n; + h->tail = n; + } + } + + iftab = h; + freeifaddrs(ifap); +} + +static int +get_socket_domain(void) +{ + int sdom; + + sdom = AF_UNSPEC; +#ifdef WITH_INET6 + if (sdom == AF_UNSPEC && feature_present("inet6")) + sdom = AF_INET6; +#endif +#ifdef WITH_INET + if (sdom == AF_UNSPEC && feature_present("inet")) + sdom = AF_INET; +#endif + if (sdom == AF_UNSPEC) + sdom = AF_LINK; + + return (sdom); +} + +int +get_query_socket(void) +{ + static int s = -1; + + if (s == -1) { + if ((s = socket(get_socket_domain(), SOCK_DGRAM, 0)) == -1) + err(1, "socket"); + } + + return (s); +} + +/* + * Returns the response len if the name is a group, otherwise returns 0. + */ +static int +is_a_group(char *name) +{ + ENTRY item; + ENTRY *ret_item; + + item.key = name; + if (hsearch_r(item, FIND, &ret_item, &isgroup_map) == 0) + return (0); + + return (*(int *)ret_item->data); +} + +unsigned int +ifa_nametoindex(const char *ifa_name) +{ + struct node_host *p; + + for (p = iftab; p; p = p->next) { + if (p->af == AF_LINK && strcmp(p->ifname, ifa_name) == 0) + return (p->ifindex); + } + errno = ENXIO; + return (0); +} + +char * +ifa_indextoname(unsigned int ifindex, char *ifa_name) +{ + struct node_host *p; + + for (p = iftab; p; p = p->next) { + if (p->af == AF_LINK && ifindex == p->ifindex) { + strlcpy(ifa_name, p->ifname, IFNAMSIZ); + return (ifa_name); + } + } + errno = ENXIO; + return (NULL); +} + +struct node_host * +ifa_exists(char *ifa_name) +{ + struct node_host *n; + + if (iftab == NULL) + ifa_load(); + + /* check whether this is a group */ + if (is_a_group(ifa_name)) { + /* fake a node_host */ + if ((n = calloc(1, sizeof(*n))) == NULL) + err(1, "calloc"); + if ((n->ifname = strdup(ifa_name)) == NULL) + err(1, "strdup"); + return (n); + } + + for (n = iftab; n; n = n->next) { + if (n->af == AF_LINK && !strncmp(n->ifname, ifa_name, IFNAMSIZ)) + return (n); + } + + return (NULL); +} + +struct node_host * +ifa_grouplookup(char *ifa_name, int flags) +{ + struct ifg_req *ifg; + struct ifgroupreq ifgr; + int s, len; + struct node_host *n, *h = NULL; + + s = get_query_socket(); + len = is_a_group(ifa_name); + if (len == 0) + return (NULL); + bzero(&ifgr, sizeof(ifgr)); + strlcpy(ifgr.ifgr_name, ifa_name, sizeof(ifgr.ifgr_name)); + ifgr.ifgr_len = len; + if ((ifgr.ifgr_groups = calloc(1, len)) == NULL) + err(1, "calloc"); + if (ioctl(s, SIOCGIFGMEMB, (caddr_t)&ifgr) == -1) + err(1, "SIOCGIFGMEMB"); + + for (ifg = ifgr.ifgr_groups; ifg && len >= sizeof(struct ifg_req); + ifg++) { + len -= sizeof(struct ifg_req); + if ((n = ifa_lookup(ifg->ifgrq_member, flags)) == NULL) + continue; + if (h == NULL) + h = n; + else { + h->tail->next = n; + h->tail = n->tail; + } + } + free(ifgr.ifgr_groups); + + return (h); +} + +struct node_host * +ifa_lookup(char *ifa_name, int flags) +{ + struct node_host *p = NULL, *h = NULL, *n = NULL; + int got4 = 0, got6 = 0; + const char *last_if = NULL; + + /* first load iftab and isgroup_map */ + if (iftab == NULL) + ifa_load(); + + if ((h = ifa_grouplookup(ifa_name, flags)) != NULL) + return (h); + + if (!strncmp(ifa_name, "self", IFNAMSIZ)) + ifa_name = NULL; + + for (p = iftab; p; p = p->next) { + if (ifa_skip_if(ifa_name, p)) + continue; + if ((flags & PFI_AFLAG_BROADCAST) && p->af != AF_INET) + continue; + if ((flags & PFI_AFLAG_BROADCAST) && + !(p->ifa_flags & IFF_BROADCAST)) + continue; + if ((flags & PFI_AFLAG_BROADCAST) && p->bcast.v4.s_addr == 0) + continue; + if ((flags & PFI_AFLAG_PEER) && + !(p->ifa_flags & IFF_POINTOPOINT)) + continue; + if ((flags & PFI_AFLAG_NETWORK) && p->ifindex > 0) + continue; + if (last_if == NULL || strcmp(last_if, p->ifname)) + got4 = got6 = 0; + last_if = p->ifname; + if ((flags & PFI_AFLAG_NOALIAS) && p->af == AF_INET && got4) + continue; + if ((flags & PFI_AFLAG_NOALIAS) && p->af == AF_INET6 && + IN6_IS_ADDR_LINKLOCAL(&p->addr.v.a.addr.v6)) + continue; + if ((flags & PFI_AFLAG_NOALIAS) && p->af == AF_INET6 && got6) + continue; + if (p->af == AF_INET) + got4 = 1; + else + got6 = 1; + n = calloc(1, sizeof(struct node_host)); + if (n == NULL) + err(1, "%s: calloc", __func__); + n->af = p->af; + if (flags & PFI_AFLAG_BROADCAST) + memcpy(&n->addr.v.a.addr, &p->bcast, + sizeof(struct pf_addr)); + else if (flags & PFI_AFLAG_PEER) + memcpy(&n->addr.v.a.addr, &p->peer, + sizeof(struct pf_addr)); + else + memcpy(&n->addr.v.a.addr, &p->addr.v.a.addr, + sizeof(struct pf_addr)); + if (flags & PFI_AFLAG_NETWORK) + set_ipmask(n, unmask(&p->addr.v.a.mask)); + else + set_ipmask(n, -1); + n->ifindex = p->ifindex; + n->ifname = strdup(p->ifname); + + n->next = NULL; + n->tail = n; + if (h == NULL) + h = n; + else { + h->tail->next = n; + h->tail = n; + } + } + return (h); +} + +int +ifa_skip_if(const char *filter, struct node_host *p) +{ + int n; + + if (p->af != AF_INET && p->af != AF_INET6) + return (1); + if (filter == NULL || !*filter) + return (0); + if (!strcmp(p->ifname, filter)) + return (0); /* exact match */ + n = strlen(filter); + if (n < 1 || n >= IFNAMSIZ) + return (1); /* sanity check */ + if (filter[n-1] >= '0' && filter[n-1] <= '9') + return (1); /* only do exact match in that case */ + if (strncmp(p->ifname, filter, n)) + return (1); /* prefix doesn't match */ + return (p->ifname[n] < '0' || p->ifname[n] > '9'); +} + + +struct node_host * +host(const char *s, int opts) +{ + struct node_host *h = NULL; + int mask = -1; + char *p, *ps; + const char *errstr; + + if ((p = strchr(s, '/')) != NULL) { + mask = strtonum(p+1, 0, 128, &errstr); + if (errstr) { + fprintf(stderr, "netmask is %s: %s\n", errstr, p); + goto error; + } + if ((ps = malloc(strlen(s) - strlen(p) + 1)) == NULL) + err(1, "%s: malloc", __func__); + strlcpy(ps, s, strlen(s) - strlen(p) + 1); + } else { + if ((ps = strdup(s)) == NULL) + err(1, "%s: strdup", __func__); + } + + if ((h = host_ip(ps, mask)) == NULL && + (h = host_if(ps, mask)) == NULL && + (h = host_dns(ps, mask, (opts & PF_OPT_NODNS))) == NULL) { + fprintf(stderr, "no IP address found for %s\n", s); + goto error; + } + +error: + free(ps); + return (h); +} + +struct node_host * +host_if(const char *s, int mask) +{ + struct node_host *n, *h = NULL; + char *p, *ps; + int flags = 0; + + if ((ps = strdup(s)) == NULL) + err(1, "host_if: strdup"); + while ((p = strrchr(ps, ':')) != NULL) { + if (!strcmp(p+1, "network")) + flags |= PFI_AFLAG_NETWORK; + else if (!strcmp(p+1, "broadcast")) + flags |= PFI_AFLAG_BROADCAST; + else if (!strcmp(p+1, "peer")) + flags |= PFI_AFLAG_PEER; + else if (!strcmp(p+1, "0")) + flags |= PFI_AFLAG_NOALIAS; + else + goto error; + *p = '\0'; + } + if (flags & (flags - 1) & PFI_AFLAG_MODEMASK) { /* Yep! */ + fprintf(stderr, "illegal combination of interface modifiers\n"); + goto error; + } + if ((flags & (PFI_AFLAG_NETWORK|PFI_AFLAG_BROADCAST)) && mask > -1) { + fprintf(stderr, "network or broadcast lookup, but " + "extra netmask given\n"); + goto error; + } + if (ifa_exists(ps) || !strncmp(ps, "self", IFNAMSIZ)) { + /* interface with this name exists */ + h = ifa_lookup(ps, flags); + if (mask > -1) + for (n = h; n != NULL; n = n->next) + set_ipmask(n, mask); + } + +error: + free(ps); + return (h); +} + +struct node_host * +host_ip(const char *s, int mask) +{ + struct addrinfo hints, *res; + struct node_host *h = NULL; + + h = calloc(1, sizeof(*h)); + if (h == NULL) + err(1, "%s: calloc", __func__); + if (mask != -1) { + /* Try to parse 10/8 */ + h->af = AF_INET; + if (inet_net_pton(AF_INET, s, &h->addr.v.a.addr.v4, + sizeof(h->addr.v.a.addr.v4)) != -1) + goto out; + } + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; /*dummy*/ + hints.ai_flags = AI_NUMERICHOST; + if (getaddrinfo(s, NULL, &hints, &res) == 0) { + h->af = res->ai_family; + copy_satopfaddr(&h->addr.v.a.addr, res->ai_addr); + if (h->af == AF_INET6) + h->ifindex = + ((struct sockaddr_in6 *)res->ai_addr)->sin6_scope_id; + freeaddrinfo(res); + } else { + free(h); + return (NULL); + } +out: + set_ipmask(h, mask); + h->ifname = NULL; + h->next = NULL; + h->tail = h; + + return (h); +} + +struct node_host * +host_dns(const char *s, int mask, int numeric) +{ + struct addrinfo hints, *res0, *res; + struct node_host *n, *h = NULL; + int noalias = 0, got4 = 0, got6 = 0; + char *p, *ps; + + if ((ps = strdup(s)) == NULL) + err(1, "host_dns: strdup"); + if ((p = strrchr(ps, ':')) != NULL && !strcmp(p, ":0")) { + noalias = 1; + *p = '\0'; + } + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; /* DUMMY */ + if (numeric) + hints.ai_flags = AI_NUMERICHOST; + if (getaddrinfo(ps, NULL, &hints, &res0) != 0) + goto error; + + for (res = res0; res; res = res->ai_next) { + if (res->ai_family != AF_INET && + res->ai_family != AF_INET6) + continue; + if (noalias) { + if (res->ai_family == AF_INET) { + if (got4) + continue; + got4 = 1; + } else { + if (got6) + continue; + got6 = 1; + } + } + n = calloc(1, sizeof(struct node_host)); + if (n == NULL) + err(1, "host_dns: calloc"); + n->ifname = NULL; + n->af = res->ai_family; + copy_satopfaddr(&n->addr.v.a.addr, res->ai_addr); + if (res->ai_family == AF_INET6) + n->ifindex = + ((struct sockaddr_in6 *)res->ai_addr)->sin6_scope_id; + set_ipmask(n, mask); + n->next = NULL; + n->tail = n; + if (h == NULL) + h = n; + else { + h->tail->next = n; + h->tail = n; + } + } + freeaddrinfo(res0); +error: + free(ps); + + return (h); +} + +/* + * convert a hostname to a list of addresses and put them in the given buffer. + * test: + * if set to 1, only simple addresses are accepted (no netblock, no "!"). + */ +int +append_addr(struct pfr_buffer *b, char *s, int test, int opts) +{ + char *r; + struct node_host *h, *n; + int rv, not = 0; + + for (r = s; *r == '!'; r++) + not = !not; + if ((n = host(r, opts)) == NULL) { + errno = 0; + return (-1); + } + rv = append_addr_host(b, n, test, not); + do { + h = n; + n = n->next; + free(h); + } while (n != NULL); + return (rv); +} + +/* + * same as previous function, but with a pre-parsed input and the ability + * to "negate" the result. Does not free the node_host list. + * not: + * setting it to 1 is equivalent to adding "!" in front of parameter s. + */ +int +append_addr_host(struct pfr_buffer *b, struct node_host *n, int test, int not) +{ + int bits; + struct pfr_addr addr; + + do { + bzero(&addr, sizeof(addr)); + addr.pfra_not = n->not ^ not; + addr.pfra_af = n->af; + addr.pfra_net = unmask(&n->addr.v.a.mask); + switch (n->af) { + case AF_INET: + addr.pfra_ip4addr.s_addr = n->addr.v.a.addr.addr32[0]; + bits = 32; + break; + case AF_INET6: + memcpy(&addr.pfra_ip6addr, &n->addr.v.a.addr.v6, + sizeof(struct in6_addr)); + bits = 128; + break; + default: + errno = EINVAL; + return (-1); + } + if ((test && (not || addr.pfra_net != bits)) || + addr.pfra_net > bits) { + errno = EINVAL; + return (-1); + } + if (pfr_buf_add(b, &addr)) + return (-1); + } while ((n = n->next) != NULL); + + return (0); +} + +int +pfctl_add_trans(struct pfr_buffer *buf, int rs_num, const char *anchor) +{ + struct pfioc_trans_e trans; + + bzero(&trans, sizeof(trans)); + trans.rs_num = rs_num; + if (strlcpy(trans.anchor, anchor, + sizeof(trans.anchor)) >= sizeof(trans.anchor)) + errx(1, "pfctl_add_trans: strlcpy"); + + return pfr_buf_add(buf, &trans); +} + +u_int32_t +pfctl_get_ticket(struct pfr_buffer *buf, int rs_num, const char *anchor) +{ + struct pfioc_trans_e *p; + + PFRB_FOREACH(p, buf) + if (rs_num == p->rs_num && !strcmp(anchor, p->anchor)) + return (p->ticket); + errx(1, "pfctl_get_ticket: assertion failed"); +} + +int +pfctl_trans(int dev, struct pfr_buffer *buf, u_long cmd, int from) +{ + struct pfioc_trans trans; + + bzero(&trans, sizeof(trans)); + trans.size = buf->pfrb_size - from; + trans.esize = sizeof(struct pfioc_trans_e); + trans.array = ((struct pfioc_trans_e *)buf->pfrb_caddr) + from; + return ioctl(dev, cmd, &trans); +} diff --git a/sbin/pfctl/pfctl_parser.h b/sbin/pfctl/pfctl_parser.h new file mode 100644 index 000000000000..44ddfb45fbe1 --- /dev/null +++ b/sbin/pfctl/pfctl_parser.h @@ -0,0 +1,392 @@ +/* $OpenBSD: pfctl_parser.h,v 1.86 2006/10/31 23:46:25 mcbride Exp $ */ + +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2001 Daniel Hartmeier + * 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. + */ + +#ifndef _PFCTL_PARSER_H_ +#define _PFCTL_PARSER_H_ + +#include <libpfctl.h> + +#include <pfctl.h> + +#define PF_OSFP_FILE "/etc/pf.os" + +#define PF_OPT_DISABLE 0x00001 +#define PF_OPT_ENABLE 0x00002 +#define PF_OPT_VERBOSE 0x00004 +#define PF_OPT_NOACTION 0x00008 +#define PF_OPT_QUIET 0x00010 +#define PF_OPT_CLRRULECTRS 0x00020 +#define PF_OPT_USEDNS 0x00040 +#define PF_OPT_VERBOSE2 0x00080 +#define PF_OPT_DUMMYACTION 0x00100 +#define PF_OPT_DEBUG 0x00200 +#define PF_OPT_SHOWALL 0x00400 +#define PF_OPT_OPTIMIZE 0x00800 +#define PF_OPT_NUMERIC 0x01000 +#define PF_OPT_MERGE 0x02000 +#define PF_OPT_RECURSE 0x04000 +#define PF_OPT_KILLMATCH 0x08000 +#define PF_OPT_NODNS 0x10000 +#define PF_OPT_IGNFAIL 0x20000 +#define PF_OPT_CALLSHOW 0x40000 + +#define PF_NAT_PROXY_PORT_LOW 50001 +#define PF_NAT_PROXY_PORT_HIGH 65535 + +#define PF_OPTIMIZE_BASIC 0x0001 +#define PF_OPTIMIZE_PROFILE 0x0002 + +#define FCNT_NAMES { \ + "searches", \ + "inserts", \ + "removals", \ + NULL \ +} + +struct pfr_buffer; /* forward definition */ + + +struct pfctl { + int dev; + struct pfctl_handle *h; + int opts; + int optimize; + int loadopt; + int asd; /* anchor stack depth */ + int bn; /* brace number */ + int tdirty; /* kernel dirty */ +#define PFCTL_ANCHOR_STACK_DEPTH 64 + struct pfctl_anchor *astack[PFCTL_ANCHOR_STACK_DEPTH]; + struct pfioc_pooladdr paddr; + struct pfioc_altq *paltq; + struct pfioc_queue *pqueue; + struct pfr_buffer *trans; + struct pfctl_anchor *anchor, *alast; + struct pfr_ktablehead pfr_ktlast; + int eth_nr; + struct pfctl_eth_anchor *eanchor, *ealast; + struct pfctl_eth_anchor *eastack[PFCTL_ANCHOR_STACK_DEPTH]; + u_int32_t eth_ticket; + const char *ruleset; + + /* 'set foo' options */ + u_int32_t timeout[PFTM_MAX]; + u_int32_t limit[PF_LIMIT_MAX]; + u_int32_t debug; + u_int32_t hostid; + u_int32_t reassemble; + char *ifname; + bool keep_counters; + u_int8_t syncookies; + u_int8_t syncookieswat[2]; /* lowat, highwat, in % */ + u_int8_t syncookieswat_set; + + u_int8_t timeout_set[PFTM_MAX]; + u_int8_t limit_set[PF_LIMIT_MAX]; + u_int8_t debug_set; + u_int8_t hostid_set; + u_int8_t ifname_set; + u_int8_t reass_set; +}; + +struct node_if { + char ifname[IFNAMSIZ]; + u_int8_t not; + u_int8_t dynamic; /* antispoof */ + u_int ifa_flags; + struct node_if *next; + struct node_if *tail; +}; + +struct node_host { + struct pf_addr_wrap addr; + struct pf_addr bcast; + struct pf_addr peer; + sa_family_t af; + u_int8_t not; + u_int32_t ifindex; /* link-local IPv6 addrs */ + char *ifname; + u_int ifa_flags; + struct node_host *next; + struct node_host *tail; +}; + +void freehostlist(struct node_host *); + +struct node_mac { + u_int8_t mac[ETHER_ADDR_LEN]; + u_int8_t mask[ETHER_ADDR_LEN]; + bool neg; + bool isset; + struct node_mac *next; + struct node_mac *tail; +}; + +struct node_os { + char *os; + pf_osfp_t fingerprint; + struct node_os *next; + struct node_os *tail; +}; + +struct node_queue_bw { + u_int64_t bw_absolute; + u_int16_t bw_percent; +}; + +struct node_hfsc_sc { + struct node_queue_bw m1; /* slope of 1st segment; bps */ + u_int d; /* x-projection of m1; msec */ + struct node_queue_bw m2; /* slope of 2nd segment; bps */ + u_int8_t used; +}; + +struct node_hfsc_opts { + struct node_hfsc_sc realtime; + struct node_hfsc_sc linkshare; + struct node_hfsc_sc upperlimit; + int flags; +}; + +struct node_fairq_sc { + struct node_queue_bw m1; /* slope of 1st segment; bps */ + u_int d; /* x-projection of m1; msec */ + struct node_queue_bw m2; /* slope of 2nd segment; bps */ + u_int8_t used; +}; + +struct node_fairq_opts { + struct node_fairq_sc linkshare; + struct node_queue_bw hogs_bw; + u_int nbuckets; + int flags; +}; + +struct node_queue_opt { + int qtype; + union { + struct cbq_opts cbq_opts; + struct codel_opts codel_opts; + struct priq_opts priq_opts; + struct node_hfsc_opts hfsc_opts; + struct node_fairq_opts fairq_opts; + } data; +}; + +#define QPRI_BITSET_SIZE 256 +__BITSET_DEFINE(qpri_bitset, QPRI_BITSET_SIZE); +LIST_HEAD(gen_sc, segment); + +struct pfctl_altq { + struct pf_altq pa; + struct { + STAILQ_ENTRY(pfctl_altq) link; + u_int64_t bwsum; + struct qpri_bitset qpris; + int children; + int root_classes; + int default_classes; + struct gen_sc lssc; + struct gen_sc rtsc; + } meta; +}; + +struct pfctl_watermarks { + uint32_t hi; + uint32_t lo; +}; + +#ifdef __FreeBSD__ +/* + * XXX + * Absolutely this is not correct location to define this. + * Should we use an another sperate header file? + */ +#define SIMPLEQ_HEAD STAILQ_HEAD +#define SIMPLEQ_HEAD_INITIALIZER STAILQ_HEAD_INITIALIZER +#define SIMPLEQ_ENTRY STAILQ_ENTRY +#define SIMPLEQ_FIRST STAILQ_FIRST +#define SIMPLEQ_END(head) NULL +#define SIMPLEQ_EMPTY STAILQ_EMPTY +#define SIMPLEQ_NEXT STAILQ_NEXT +/*#define SIMPLEQ_FOREACH STAILQ_FOREACH*/ +#define SIMPLEQ_FOREACH(var, head, field) \ + for((var) = SIMPLEQ_FIRST(head); \ + (var) != SIMPLEQ_END(head); \ + (var) = SIMPLEQ_NEXT(var, field)) +#define SIMPLEQ_INIT STAILQ_INIT +#define SIMPLEQ_INSERT_HEAD STAILQ_INSERT_HEAD +#define SIMPLEQ_INSERT_TAIL STAILQ_INSERT_TAIL +#define SIMPLEQ_INSERT_AFTER STAILQ_INSERT_AFTER +#define SIMPLEQ_REMOVE_HEAD STAILQ_REMOVE_HEAD +#endif +SIMPLEQ_HEAD(node_tinithead, node_tinit); +struct node_tinit { /* table initializer */ + SIMPLEQ_ENTRY(node_tinit) entries; + struct node_host *host; + char *file; +}; + + +/* optimizer created tables */ +struct pf_opt_tbl { + char pt_name[PF_TABLE_NAME_SIZE]; + int pt_rulecount; + int pt_generated; + uint32_t pt_refcnt; + struct node_tinithead pt_nodes; + struct pfr_buffer *pt_buf; +}; + +/* optimizer pf_rule container */ +struct pf_opt_rule { + struct pfctl_rule por_rule; + struct pf_opt_tbl *por_src_tbl; + struct pf_opt_tbl *por_dst_tbl; + u_int64_t por_profile_count; + TAILQ_ENTRY(pf_opt_rule) por_entry; + TAILQ_ENTRY(pf_opt_rule) por_skip_entry[PF_SKIP_COUNT]; +}; + +TAILQ_HEAD(pf_opt_queue, pf_opt_rule); + +struct pfr_uktable; + +void copy_satopfaddr(struct pf_addr *, struct sockaddr *); + +int pfctl_rules(int, char *, int, int, char *, struct pfr_buffer *); +int pfctl_optimize_ruleset(struct pfctl *, struct pfctl_ruleset *); + +void pfctl_init_rule(struct pfctl_rule *r); +void pfctl_append_rule(struct pfctl *, struct pfctl_rule *); +int pfctl_append_eth_rule(struct pfctl *, struct pfctl_eth_rule *, + const char *); +int pfctl_add_altq(struct pfctl *, struct pf_altq *); +int pfctl_add_pool(struct pfctl *, struct pfctl_pool *, int); +void pfctl_move_pool(struct pfctl_pool *, struct pfctl_pool *); +void pfctl_clear_pool(struct pfctl_pool *); + +int pfctl_apply_timeout(struct pfctl *, const char *, int, int); +int pfctl_set_reassembly(struct pfctl *, int, int); +int pfctl_set_optimization(struct pfctl *, const char *); +int pfctl_apply_limit(struct pfctl *, const char *, unsigned int); +int pfctl_set_logif(struct pfctl *, char *); +void pfctl_set_hostid(struct pfctl *, u_int32_t); +int pfctl_do_set_debug(struct pfctl *, char *); +int pfctl_set_interface_flags(struct pfctl *, char *, int, int); +int pfctl_cfg_syncookies(struct pfctl *, uint8_t, struct pfctl_watermarks *); + +int parse_config(char *, struct pfctl *); +int parse_flags(char *); +int pfctl_load_anchors(int, struct pfctl *); + +void print_pool(struct pfctl_pool *, u_int16_t, u_int16_t, int); +void print_src_node(struct pfctl_src_node *, int); +void print_eth_rule(struct pfctl_eth_rule *, const char *, int); +void print_rule(struct pfctl_rule *, const char *, int, int); +void print_tabledef(const char *, int, int, struct node_tinithead *); +void print_status(struct pfctl_status *, struct pfctl_syncookies *, int); +void print_running(struct pfctl_status *); + +int eval_pfaltq(struct pfctl *, struct pf_altq *, struct node_queue_bw *, + struct node_queue_opt *); +int eval_pfqueue(struct pfctl *, struct pf_altq *, struct node_queue_bw *, + struct node_queue_opt *); + +void print_altq(const struct pf_altq *, unsigned, struct node_queue_bw *, + struct node_queue_opt *); +void print_queue(const struct pf_altq *, unsigned, struct node_queue_bw *, + int, struct node_queue_opt *); + +int pfctl_define_table(char *, int, int, const char *, struct pfr_buffer *, + u_int32_t, struct pfr_uktable *); + +void pfctl_clear_fingerprints(int, int); +int pfctl_file_fingerprints(int, int, const char *); +pf_osfp_t pfctl_get_fingerprint(const char *); +int pfctl_load_fingerprints(int, int); +char *pfctl_lookup_fingerprint(pf_osfp_t, char *, size_t); +void pfctl_show_fingerprints(int); + + +struct icmptypeent { + const char *name; + u_int8_t type; +}; + +struct icmpcodeent { + const char *name; + u_int8_t type; + u_int8_t code; +}; + +const struct icmptypeent *geticmptypebynumber(u_int8_t, sa_family_t); +const struct icmptypeent *geticmptypebyname(char *, sa_family_t); +const struct icmpcodeent *geticmpcodebynumber(u_int8_t, u_int8_t, sa_family_t); +const struct icmpcodeent *geticmpcodebyname(u_long, char *, sa_family_t); + +struct pf_timeout { + const char *name; + int timeout; +}; + +#define PFCTL_FLAG_FILTER 0x02 +#define PFCTL_FLAG_NAT 0x04 +#define PFCTL_FLAG_OPTION 0x08 +#define PFCTL_FLAG_ALTQ 0x10 +#define PFCTL_FLAG_TABLE 0x20 +#define PFCTL_FLAG_ETH 0x40 + +extern const struct pf_timeout pf_timeouts[]; + +void set_ipmask(struct node_host *, int); +int check_netmask(struct node_host *, sa_family_t); +int unmask(struct pf_addr *); +struct node_host *gen_dynnode(struct node_host *, sa_family_t); +void ifa_load(void); +unsigned int ifa_nametoindex(const char *); +char *ifa_indextoname(unsigned int, char *); +int get_query_socket(void); +struct node_host *ifa_exists(char *); +struct node_host *ifa_grouplookup(char *ifa_name, int flags); +struct node_host *ifa_lookup(char *, int); +struct node_host *host(const char *, int); + +int append_addr(struct pfr_buffer *, char *, int, int); +int append_addr_host(struct pfr_buffer *, + struct node_host *, int, int); +int pfr_ktable_compare(struct pfr_ktable *, + struct pfr_ktable *); +RB_PROTOTYPE(pfr_ktablehead, pfr_ktable, pfrkt_tree, pfr_ktable_compare); + +#endif /* _PFCTL_PARSER_H_ */ diff --git a/sbin/pfctl/pfctl_qstats.c b/sbin/pfctl/pfctl_qstats.c new file mode 100644 index 000000000000..397598b0c114 --- /dev/null +++ b/sbin/pfctl/pfctl_qstats.c @@ -0,0 +1,513 @@ +/* $OpenBSD: pfctl_qstats.c,v 1.30 2004/04/27 21:47:32 kjc Exp $ */ + +/* + * Copyright (c) Henning Brauer <henning@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/cdefs.h> +#define PFIOC_USE_LATEST + +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/socket.h> + +#include <net/if.h> +#include <netinet/in.h> +#include <net/pfvar.h> +#include <arpa/inet.h> + +#include <err.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <net/altq/altq.h> +#include <net/altq/altq_cbq.h> +#include <net/altq/altq_codel.h> +#include <net/altq/altq_priq.h> +#include <net/altq/altq_hfsc.h> +#include <net/altq/altq_fairq.h> + +#include "pfctl.h" +#include "pfctl_parser.h" + +union class_stats { + class_stats_t cbq_stats; + struct priq_classstats priq_stats; + struct hfsc_classstats hfsc_stats; + struct fairq_classstats fairq_stats; + struct codel_ifstats codel_stats; +}; + +#define AVGN_MAX 8 +#define STAT_INTERVAL 5 + +struct queue_stats { + union class_stats data; + int avgn; + double avg_bytes; + double avg_packets; + u_int64_t prev_bytes; + u_int64_t prev_packets; +}; + +struct pf_altq_node { + struct pf_altq altq; + struct pf_altq_node *next; + struct pf_altq_node *children; + struct queue_stats qstats; +}; + +int pfctl_update_qstats(int, struct pf_altq_node **); +void pfctl_insert_altq_node(struct pf_altq_node **, + const struct pf_altq, const struct queue_stats); +struct pf_altq_node *pfctl_find_altq_node(struct pf_altq_node *, + const char *, const char *); +void pfctl_print_altq_node(int, const struct pf_altq_node *, + unsigned, int); +void print_cbqstats(struct queue_stats); +void print_codelstats(struct queue_stats); +void print_priqstats(struct queue_stats); +void print_hfscstats(struct queue_stats); +void print_fairqstats(struct queue_stats); +void pfctl_free_altq_node(struct pf_altq_node *); +void pfctl_print_altq_nodestat(int, + const struct pf_altq_node *); + +void update_avg(struct pf_altq_node *); + +int +pfctl_show_altq(int dev, const char *iface, int opts, int verbose2) +{ + struct pf_altq_node *root = NULL, *node; + int nodes, dotitle = (opts & PF_OPT_SHOWALL); + +#ifdef __FreeBSD__ + if (!altqsupport) + return (-1); +#endif + + if ((nodes = pfctl_update_qstats(dev, &root)) < 0) + return (-1); + + if (nodes == 0) + printf("No queue in use\n"); + for (node = root; node != NULL; node = node->next) { + if (iface != NULL && strcmp(node->altq.ifname, iface)) + continue; + if (dotitle) { + pfctl_print_title("ALTQ:"); + dotitle = 0; + } + pfctl_print_altq_node(dev, node, 0, opts); + } + + while (verbose2 && nodes > 0) { + printf("\n"); + fflush(stdout); + sleep(STAT_INTERVAL); + if ((nodes = pfctl_update_qstats(dev, &root)) == -1) + return (-1); + for (node = root; node != NULL; node = node->next) { + if (iface != NULL && strcmp(node->altq.ifname, iface)) + continue; +#ifdef __FreeBSD__ + if (node->altq.local_flags & PFALTQ_FLAG_IF_REMOVED) + continue; +#endif + pfctl_print_altq_node(dev, node, 0, opts); + } + } + pfctl_free_altq_node(root); + return (0); +} + +int +pfctl_update_qstats(int dev, struct pf_altq_node **root) +{ + struct pf_altq_node *node; + struct pfioc_altq pa; + struct pfioc_qstats pq; + u_int32_t mnr, nr; + struct queue_stats qstats; + static u_int32_t last_ticket; + + memset(&pa, 0, sizeof(pa)); + memset(&pq, 0, sizeof(pq)); + memset(&qstats, 0, sizeof(qstats)); + pa.version = PFIOC_ALTQ_VERSION; + if (ioctl(dev, DIOCGETALTQS, &pa)) { + warn("DIOCGETALTQS"); + return (-1); + } + + /* if a new set is found, start over */ + if (pa.ticket != last_ticket && *root != NULL) { + pfctl_free_altq_node(*root); + *root = NULL; + } + last_ticket = pa.ticket; + + mnr = pa.nr; + for (nr = 0; nr < mnr; ++nr) { + pa.nr = nr; + if (ioctl(dev, DIOCGETALTQ, &pa)) { + warn("DIOCGETALTQ"); + return (-1); + } +#ifdef __FreeBSD__ + if ((pa.altq.qid > 0 || pa.altq.scheduler == ALTQT_CODEL) && + !(pa.altq.local_flags & PFALTQ_FLAG_IF_REMOVED)) { +#else + if (pa.altq.qid > 0) { +#endif + pq.nr = nr; + pq.ticket = pa.ticket; + pq.buf = &qstats.data; + pq.nbytes = sizeof(qstats.data); + pq.version = altq_stats_version(pa.altq.scheduler); + if (ioctl(dev, DIOCGETQSTATS, &pq)) { + warn("DIOCGETQSTATS"); + return (-1); + } + if ((node = pfctl_find_altq_node(*root, pa.altq.qname, + pa.altq.ifname)) != NULL) { + memcpy(&node->qstats.data, &qstats.data, + sizeof(qstats.data)); + update_avg(node); + } else { + pfctl_insert_altq_node(root, pa.altq, qstats); + } + } +#ifdef __FreeBSD__ + else if (pa.altq.local_flags & PFALTQ_FLAG_IF_REMOVED) { + memset(&qstats.data, 0, sizeof(qstats.data)); + if ((node = pfctl_find_altq_node(*root, pa.altq.qname, + pa.altq.ifname)) != NULL) { + memcpy(&node->qstats.data, &qstats.data, + sizeof(qstats.data)); + update_avg(node); + } else { + pfctl_insert_altq_node(root, pa.altq, qstats); + } + } +#endif + } + return (mnr); +} + +void +pfctl_insert_altq_node(struct pf_altq_node **root, + const struct pf_altq altq, const struct queue_stats qstats) +{ + struct pf_altq_node *node; + + node = calloc(1, sizeof(struct pf_altq_node)); + if (node == NULL) + err(1, "pfctl_insert_altq_node: calloc"); + memcpy(&node->altq, &altq, sizeof(struct pf_altq)); + memcpy(&node->qstats, &qstats, sizeof(qstats)); + node->next = node->children = NULL; + + if (*root == NULL) + *root = node; + else if (!altq.parent[0]) { + struct pf_altq_node *prev = *root; + + while (prev->next != NULL) + prev = prev->next; + prev->next = node; + } else { + struct pf_altq_node *parent; + + parent = pfctl_find_altq_node(*root, altq.parent, altq.ifname); + if (parent == NULL) + errx(1, "parent %s not found", altq.parent); + if (parent->children == NULL) + parent->children = node; + else { + struct pf_altq_node *prev = parent->children; + + while (prev->next != NULL) + prev = prev->next; + prev->next = node; + } + } + update_avg(node); +} + +struct pf_altq_node * +pfctl_find_altq_node(struct pf_altq_node *root, const char *qname, + const char *ifname) +{ + struct pf_altq_node *node, *child; + + for (node = root; node != NULL; node = node->next) { + if (!strcmp(node->altq.qname, qname) + && !(strcmp(node->altq.ifname, ifname))) + return (node); + if (node->children != NULL) { + child = pfctl_find_altq_node(node->children, qname, + ifname); + if (child != NULL) + return (child); + } + } + return (NULL); +} + +void +pfctl_print_altq_node(int dev, const struct pf_altq_node *node, + unsigned int level, int opts) +{ + const struct pf_altq_node *child; + + if (node == NULL) + return; + + print_altq(&node->altq, level, NULL, NULL); + + if (node->children != NULL) { + printf("{"); + for (child = node->children; child != NULL; + child = child->next) { + printf("%s", child->altq.qname); + if (child->next != NULL) + printf(", "); + } + printf("}"); + } + printf("\n"); + + if (opts & PF_OPT_VERBOSE) + pfctl_print_altq_nodestat(dev, node); + + if (opts & PF_OPT_DEBUG) + printf(" [ qid=%u ifname=%s ifbandwidth=%s ]\n", + node->altq.qid, node->altq.ifname, + rate2str((double)(node->altq.ifbandwidth))); + + for (child = node->children; child != NULL; + child = child->next) + pfctl_print_altq_node(dev, child, level + 1, opts); +} + +void +pfctl_print_altq_nodestat(int dev, const struct pf_altq_node *a) +{ + if (a->altq.qid == 0 && a->altq.scheduler != ALTQT_CODEL) + return; + +#ifdef __FreeBSD__ + if (a->altq.local_flags & PFALTQ_FLAG_IF_REMOVED) + return; +#endif + switch (a->altq.scheduler) { + case ALTQT_CBQ: + print_cbqstats(a->qstats); + break; + case ALTQT_PRIQ: + print_priqstats(a->qstats); + break; + case ALTQT_HFSC: + print_hfscstats(a->qstats); + break; + case ALTQT_FAIRQ: + print_fairqstats(a->qstats); + break; + case ALTQT_CODEL: + print_codelstats(a->qstats); + break; + } +} + +void +print_cbqstats(struct queue_stats cur) +{ + printf(" [ pkts: %10llu bytes: %10llu " + "dropped pkts: %6llu bytes: %6llu ]\n", + (unsigned long long)cur.data.cbq_stats.xmit_cnt.packets, + (unsigned long long)cur.data.cbq_stats.xmit_cnt.bytes, + (unsigned long long)cur.data.cbq_stats.drop_cnt.packets, + (unsigned long long)cur.data.cbq_stats.drop_cnt.bytes); + printf(" [ qlength: %3d/%3d borrows: %6u suspends: %6u ]\n", + cur.data.cbq_stats.qcnt, cur.data.cbq_stats.qmax, + cur.data.cbq_stats.borrows, cur.data.cbq_stats.delays); + + if (cur.avgn < 2) + return; + + printf(" [ measured: %7.1f packets/s, %s/s ]\n", + cur.avg_packets / STAT_INTERVAL, + rate2str((8 * cur.avg_bytes) / STAT_INTERVAL)); +} + +void +print_codelstats(struct queue_stats cur) +{ + printf(" [ pkts: %10llu bytes: %10llu " + "dropped pkts: %6llu bytes: %6llu ]\n", + (unsigned long long)cur.data.codel_stats.cl_xmitcnt.packets, + (unsigned long long)cur.data.codel_stats.cl_xmitcnt.bytes, + (unsigned long long)cur.data.codel_stats.cl_dropcnt.packets + + cur.data.codel_stats.stats.drop_cnt.packets, + (unsigned long long)cur.data.codel_stats.cl_dropcnt.bytes + + cur.data.codel_stats.stats.drop_cnt.bytes); + printf(" [ qlength: %3d/%3d ]\n", + cur.data.codel_stats.qlength, cur.data.codel_stats.qlimit); + + if (cur.avgn < 2) + return; + + printf(" [ measured: %7.1f packets/s, %s/s ]\n", + cur.avg_packets / STAT_INTERVAL, + rate2str((8 * cur.avg_bytes) / STAT_INTERVAL)); +} + +void +print_priqstats(struct queue_stats cur) +{ + printf(" [ pkts: %10llu bytes: %10llu " + "dropped pkts: %6llu bytes: %6llu ]\n", + (unsigned long long)cur.data.priq_stats.xmitcnt.packets, + (unsigned long long)cur.data.priq_stats.xmitcnt.bytes, + (unsigned long long)cur.data.priq_stats.dropcnt.packets, + (unsigned long long)cur.data.priq_stats.dropcnt.bytes); + printf(" [ qlength: %3d/%3d ]\n", + cur.data.priq_stats.qlength, cur.data.priq_stats.qlimit); + + if (cur.avgn < 2) + return; + + printf(" [ measured: %7.1f packets/s, %s/s ]\n", + cur.avg_packets / STAT_INTERVAL, + rate2str((8 * cur.avg_bytes) / STAT_INTERVAL)); +} + +void +print_hfscstats(struct queue_stats cur) +{ + printf(" [ pkts: %10llu bytes: %10llu " + "dropped pkts: %6llu bytes: %6llu ]\n", + (unsigned long long)cur.data.hfsc_stats.xmit_cnt.packets, + (unsigned long long)cur.data.hfsc_stats.xmit_cnt.bytes, + (unsigned long long)cur.data.hfsc_stats.drop_cnt.packets, + (unsigned long long)cur.data.hfsc_stats.drop_cnt.bytes); + printf(" [ qlength: %3d/%3d ]\n", + cur.data.hfsc_stats.qlength, cur.data.hfsc_stats.qlimit); + + if (cur.avgn < 2) + return; + + printf(" [ measured: %7.1f packets/s, %s/s ]\n", + cur.avg_packets / STAT_INTERVAL, + rate2str((8 * cur.avg_bytes) / STAT_INTERVAL)); +} + +void +print_fairqstats(struct queue_stats cur) +{ + printf(" [ pkts: %10llu bytes: %10llu " + "dropped pkts: %6llu bytes: %6llu ]\n", + (unsigned long long)cur.data.fairq_stats.xmit_cnt.packets, + (unsigned long long)cur.data.fairq_stats.xmit_cnt.bytes, + (unsigned long long)cur.data.fairq_stats.drop_cnt.packets, + (unsigned long long)cur.data.fairq_stats.drop_cnt.bytes); + printf(" [ qlength: %3d/%3d ]\n", + cur.data.fairq_stats.qlength, cur.data.fairq_stats.qlimit); + + if (cur.avgn < 2) + return; + + printf(" [ measured: %7.1f packets/s, %s/s ]\n", + cur.avg_packets / STAT_INTERVAL, + rate2str((8 * cur.avg_bytes) / STAT_INTERVAL)); +} + +void +pfctl_free_altq_node(struct pf_altq_node *node) +{ + while (node != NULL) { + struct pf_altq_node *prev; + + if (node->children != NULL) + pfctl_free_altq_node(node->children); + prev = node; + node = node->next; + free(prev); + } +} + +void +update_avg(struct pf_altq_node *a) +{ + struct queue_stats *qs; + u_int64_t b, p; + int n; + + if (a->altq.qid == 0 && a->altq.scheduler != ALTQT_CODEL) + return; + + qs = &a->qstats; + n = qs->avgn; + + switch (a->altq.scheduler) { + case ALTQT_CBQ: + b = qs->data.cbq_stats.xmit_cnt.bytes; + p = qs->data.cbq_stats.xmit_cnt.packets; + break; + case ALTQT_PRIQ: + b = qs->data.priq_stats.xmitcnt.bytes; + p = qs->data.priq_stats.xmitcnt.packets; + break; + case ALTQT_HFSC: + b = qs->data.hfsc_stats.xmit_cnt.bytes; + p = qs->data.hfsc_stats.xmit_cnt.packets; + break; + case ALTQT_FAIRQ: + b = qs->data.fairq_stats.xmit_cnt.bytes; + p = qs->data.fairq_stats.xmit_cnt.packets; + break; + case ALTQT_CODEL: + b = qs->data.codel_stats.cl_xmitcnt.bytes; + p = qs->data.codel_stats.cl_xmitcnt.packets; + break; + default: + b = 0; + p = 0; + break; + } + + if (n == 0) { + qs->prev_bytes = b; + qs->prev_packets = p; + qs->avgn++; + return; + } + + if (b >= qs->prev_bytes) + qs->avg_bytes = ((qs->avg_bytes * (n - 1)) + + (b - qs->prev_bytes)) / n; + + if (p >= qs->prev_packets) + qs->avg_packets = ((qs->avg_packets * (n - 1)) + + (p - qs->prev_packets)) / n; + + qs->prev_bytes = b; + qs->prev_packets = p; + if (n < AVGN_MAX) + qs->avgn++; +} diff --git a/sbin/pfctl/pfctl_radix.c b/sbin/pfctl/pfctl_radix.c new file mode 100644 index 000000000000..3b7161420e33 --- /dev/null +++ b/sbin/pfctl/pfctl_radix.c @@ -0,0 +1,481 @@ +/* $OpenBSD: pfctl_radix.c,v 1.27 2005/05/21 21:03:58 henning Exp $ */ + +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2002 Cedric Berger + * 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. + * + */ + +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/socket.h> + +#include <net/if.h> +#include <net/pfvar.h> + +#include <errno.h> +#include <string.h> +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <limits.h> +#include <err.h> + +#include "pfctl.h" +#include "pfctl_parser.h" + +#define BUF_SIZE 256 + +extern int dev; + +static int pfr_next_token(char buf[BUF_SIZE], FILE *); + +struct pfr_ktablehead pfr_ktables = { 0 }; +RB_GENERATE(pfr_ktablehead, pfr_ktable, pfrkt_tree, pfr_ktable_compare); + +int +pfr_ktable_compare(struct pfr_ktable *p, struct pfr_ktable *q) +{ + int d; + + if ((d = strncmp(p->pfrkt_name, q->pfrkt_name, PF_TABLE_NAME_SIZE))) + return (d); + return (strcmp(p->pfrkt_anchor, q->pfrkt_anchor)); +} + +static void +pfr_report_error(struct pfr_table *tbl, struct pfioc_table *io, + const char *err) +{ + unsigned long maxcount; + size_t s; + + s = sizeof(maxcount); + if (sysctlbyname("net.pf.request_maxcount", &maxcount, &s, NULL, + 0) == -1) + return; + + if (io->pfrio_size > maxcount || io->pfrio_size2 > maxcount) + fprintf(stderr, "cannot %s %s: too many elements.\n" + "Consider increasing net.pf.request_maxcount.", + err, tbl->pfrt_name); +} + +int +pfr_add_table(struct pfr_table *tbl, int *nadd, int flags) +{ + return (pfctl_add_table(pfh, tbl, nadd, flags)); +} + +int +pfr_del_table(struct pfr_table *tbl, int *ndel, int flags) +{ + return (pfctl_del_table(pfh, tbl, ndel, flags)); +} + +int +pfr_get_tables(struct pfr_table *filter, struct pfr_table *tbl, int *size, + int flags) +{ + struct pfioc_table io; + + if (size == NULL || *size < 0 || (*size && tbl == NULL)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + if (filter != NULL) + io.pfrio_table = *filter; + io.pfrio_buffer = tbl; + io.pfrio_esize = sizeof(*tbl); + io.pfrio_size = *size; + if (ioctl(dev, DIOCRGETTABLES, &io)) { + pfr_report_error(tbl, &io, "get table"); + return (-1); + } + *size = io.pfrio_size; + return (0); +} + +int +pfr_clr_addrs(struct pfr_table *tbl, int *ndel, int flags) +{ + return (pfctl_clear_addrs(pfh, tbl, ndel, flags)); +} + +int +pfr_add_addrs(struct pfr_table *tbl, struct pfr_addr *addr, int size, + int *nadd, int flags) +{ + int ret; + + if (*nadd) + *nadd = 0; + + ret = pfctl_table_add_addrs_h(pfh, tbl, addr, size, nadd, flags); + if (ret) { + errno = ret; + return (-1); + } + return (0); +} + +int +pfr_del_addrs(struct pfr_table *tbl, struct pfr_addr *addr, int size, + int *ndel, int flags) +{ + int ret; + + ret = pfctl_table_del_addrs_h(pfh, tbl, addr, size, ndel, flags); + if (ret) { + errno = ret; + return (-1); + } + return (0); +} + +int +pfr_set_addrs(struct pfr_table *tbl, struct pfr_addr *addr, int size, + int *nadd, int *ndel, int *nchange, int flags) +{ + int ret; + + ret = pfctl_table_set_addrs_h(pfh, tbl, addr, size, nadd, ndel, + nchange, flags); + if (ret) { + errno = ret; + return (-1); + } + return (0); +} + +int +pfr_get_addrs(struct pfr_table *tbl, struct pfr_addr *addr, int *size, + int flags) +{ + int ret; + + ret = pfctl_table_get_addrs(dev, tbl, addr, size, flags); + if (ret) { + errno = ret; + return (-1); + } + return (0); +} + +int +pfr_get_astats(struct pfr_table *tbl, struct pfr_astats *addr, int *size, + int flags) +{ + struct pfioc_table io; + + if (tbl == NULL || size == NULL || *size < 0 || + (*size && addr == NULL)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_table = *tbl; + io.pfrio_buffer = addr; + io.pfrio_esize = sizeof(*addr); + io.pfrio_size = *size; + if (ioctl(dev, DIOCRGETASTATS, &io)) { + pfr_report_error(tbl, &io, "get astats from"); + return (-1); + } + *size = io.pfrio_size; + return (0); +} + +int +pfr_clr_astats(struct pfr_table *tbl, struct pfr_addr *addr, int size, + int *nzero, int flags) +{ + struct pfioc_table io; + + if (size < 0 || !tbl || (size && !addr)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_table = *tbl; + io.pfrio_buffer = addr; + io.pfrio_esize = sizeof(*addr); + io.pfrio_size = size; + if (ioctl(dev, DIOCRCLRASTATS, &io) == -1) + return (-1); + if (nzero) + *nzero = io.pfrio_nzero; + return (0); +} + +int +pfr_tst_addrs(struct pfr_table *tbl, struct pfr_addr *addr, int size, + int *nmatch, int flags) +{ + struct pfioc_table io; + + if (tbl == NULL || size < 0 || (size && addr == NULL)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_table = *tbl; + io.pfrio_buffer = addr; + io.pfrio_esize = sizeof(*addr); + io.pfrio_size = size; + if (ioctl(dev, DIOCRTSTADDRS, &io)) { + pfr_report_error(tbl, &io, "test addresses in"); + return (-1); + } + if (nmatch) + *nmatch = io.pfrio_nmatch; + return (0); +} + +int +pfr_ina_define(struct pfr_table *tbl, struct pfr_addr *addr, int size, + int *nadd, int *naddr, int ticket, int flags) +{ + struct pfioc_table io; + + if (tbl == NULL || size < 0 || (size && addr == NULL)) { + DBGPRINT("%s %p %d %p\n", __func__, tbl, size, addr); + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_table = *tbl; + io.pfrio_buffer = addr; + io.pfrio_esize = sizeof(*addr); + io.pfrio_size = size; + io.pfrio_ticket = ticket; + if (ioctl(dev, DIOCRINADEFINE, &io)) { + pfr_report_error(tbl, &io, "define inactive set table"); + return (-1); + } + if (nadd != NULL) + *nadd = io.pfrio_nadd; + if (naddr != NULL) + *naddr = io.pfrio_naddr; + return (0); +} + +/* interface management code */ + +int +pfi_get_ifaces(const char *filter, struct pfi_kif *buf, int *size) +{ + struct pfioc_iface io; + + if (size == NULL || *size < 0 || (*size && buf == NULL)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + if (filter != NULL) + if (strlcpy(io.pfiio_name, filter, sizeof(io.pfiio_name)) >= + sizeof(io.pfiio_name)) { + errno = EINVAL; + return (-1); + } + io.pfiio_buffer = buf; + io.pfiio_esize = sizeof(*buf); + io.pfiio_size = *size; + if (ioctl(dev, DIOCIGETIFACES, &io)) + return (-1); + *size = io.pfiio_size; + return (0); +} + +/* buffer management code */ + +const size_t buf_esize[PFRB_MAX] = { 0, + sizeof(struct pfr_table), sizeof(struct pfr_tstats), + sizeof(struct pfr_addr), sizeof(struct pfr_astats), + sizeof(struct pfi_kif), sizeof(struct pfioc_trans_e) +}; + +/* + * add one element to the buffer + */ +int +pfr_buf_add(struct pfr_buffer *b, const void *e) +{ + size_t bs; + + if (b == NULL || b->pfrb_type <= 0 || b->pfrb_type >= PFRB_MAX || + e == NULL) { + errno = EINVAL; + return (-1); + } + bs = buf_esize[b->pfrb_type]; + if (b->pfrb_size == b->pfrb_msize) + if (pfr_buf_grow(b, 0)) + return (-1); + memcpy(((caddr_t)b->pfrb_caddr) + bs * b->pfrb_size, e, bs); + b->pfrb_size++; + return (0); +} + +/* + * return next element of the buffer (or first one if prev is NULL) + * see PFRB_FOREACH macro + */ +void * +pfr_buf_next(struct pfr_buffer *b, const void *prev) +{ + size_t bs; + + if (b == NULL || b->pfrb_type <= 0 || b->pfrb_type >= PFRB_MAX) + return (NULL); + if (b->pfrb_size == 0) + return (NULL); + if (prev == NULL) + return (b->pfrb_caddr); + bs = buf_esize[b->pfrb_type]; + if ((((caddr_t)prev)-((caddr_t)b->pfrb_caddr)) / bs >= b->pfrb_size-1) + return (NULL); + return (((caddr_t)prev) + bs); +} + +/* + * minsize: + * 0: make the buffer somewhat bigger + * n: make room for "n" entries in the buffer + */ +int +pfr_buf_grow(struct pfr_buffer *b, int minsize) +{ + caddr_t p; + size_t bs; + + if (b == NULL || b->pfrb_type <= 0 || b->pfrb_type >= PFRB_MAX) { + errno = EINVAL; + return (-1); + } + if (minsize != 0 && minsize <= b->pfrb_msize) + return (0); + bs = buf_esize[b->pfrb_type]; + if (!b->pfrb_msize) { + if (minsize < 64) + minsize = 64; + } + if (minsize == 0) + minsize = b->pfrb_msize * 2; + p = reallocarray(b->pfrb_caddr, minsize, bs); + if (p == NULL) + return (-1); + bzero(p + b->pfrb_msize * bs, (minsize - b->pfrb_msize) * bs); + b->pfrb_caddr = p; + b->pfrb_msize = minsize; + return (0); +} + +/* + * reset buffer and free memory. + */ +void +pfr_buf_clear(struct pfr_buffer *b) +{ + if (b == NULL) + return; + free(b->pfrb_caddr); + b->pfrb_caddr = NULL; + b->pfrb_size = b->pfrb_msize = 0; +} + +int +pfr_buf_load(struct pfr_buffer *b, char *file, int nonetwork, + int (*append_addr)(struct pfr_buffer *, char *, int, int), int opts) +{ + FILE *fp; + char buf[BUF_SIZE]; + int rv; + + if (file == NULL) + return (0); + if (!strcmp(file, "-")) + fp = stdin; + else { + fp = pfctl_fopen(file, "r"); + if (fp == NULL) + return (-1); + } + while ((rv = pfr_next_token(buf, fp)) == 1) + if (append_addr(b, buf, nonetwork, opts)) { + rv = -1; + break; + } + if (fp != stdin) + fclose(fp); + return (rv); +} + +int +pfr_next_token(char buf[BUF_SIZE], FILE *fp) +{ + static char next_ch = ' '; + int i = 0; + + for (;;) { + /* skip spaces */ + while (isspace(next_ch) && !feof(fp)) + next_ch = fgetc(fp); + /* remove from '#' or ';' until end of line */ + if (next_ch == '#' || next_ch == ';') + while (!feof(fp)) { + next_ch = fgetc(fp); + if (next_ch == '\n') + break; + } + else + break; + } + if (feof(fp)) { + next_ch = ' '; + return (0); + } + do { + if (i < BUF_SIZE) + buf[i++] = next_ch; + next_ch = fgetc(fp); + } while (!feof(fp) && !isspace(next_ch)); + if (i >= BUF_SIZE) { + errno = EINVAL; + return (-1); + } + buf[i] = '\0'; + return (1); +} diff --git a/sbin/pfctl/pfctl_table.c b/sbin/pfctl/pfctl_table.c new file mode 100644 index 000000000000..aae347712547 --- /dev/null +++ b/sbin/pfctl/pfctl_table.c @@ -0,0 +1,716 @@ +/* $OpenBSD: pfctl_table.c,v 1.67 2008/06/10 20:55:02 mcbride Exp $ */ + +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2002 Cedric Berger + * 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. + * + */ + +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/socket.h> + +#include <netinet/in.h> +#include <arpa/inet.h> +#include <net/if.h> +#include <net/pfvar.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <netdb.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#include "pfctl_parser.h" +#include "pfctl.h" + +extern void usage(void); +static void print_table(const struct pfr_table *, int, int); +static int print_tstats(const struct pfr_tstats *, int); +static int load_addr(struct pfr_buffer *, int, char *[], char *, int, int); +static void print_addrx(struct pfr_addr *, struct pfr_addr *, int); +static int nonzero_astats(struct pfr_astats *); +static void print_astats(struct pfr_astats *, int); +static void xprintf(int, const char *, ...); +static void print_iface(struct pfi_kif *, int); + +static const char *stats_text[PFR_DIR_MAX][PFR_OP_TABLE_MAX] = { + { "In/Block:", "In/Pass:", "In/XPass:" }, + { "Out/Block:", "Out/Pass:", "Out/XPass:" } +}; + +static const char *istats_text[2][2][2] = { + { { "In4/Pass:", "In4/Block:" }, { "Out4/Pass:", "Out4/Block:" } }, + { { "In6/Pass:", "In6/Block:" }, { "Out6/Pass:", "Out6/Block:" } } +}; + +#define RVTEST(fct) do { \ + if ((!(opts & PF_OPT_NOACTION) || \ + (opts & PF_OPT_DUMMYACTION)) && \ + (fct)) { \ + if ((opts & PF_OPT_RECURSE) == 0) \ + warnx("%s", pf_strerror(errno)); \ + goto _error; \ + } \ + } while (0) + +#define CREATE_TABLE do { \ + warn_duplicate_tables(table.pfrt_name, \ + table.pfrt_anchor); \ + table.pfrt_flags |= PFR_TFLAG_PERSIST; \ + if ((!(opts & PF_OPT_NOACTION) || \ + (opts & PF_OPT_DUMMYACTION)) && \ + (pfr_add_table(&table, &nadd, flags)) && \ + (errno != EPERM)) { \ + warnx("%s", pf_strerror(errno)); \ + goto _error; \ + } \ + if (nadd) { \ + xprintf(opts, "%d table created", nadd); \ + if (opts & PF_OPT_NOACTION) \ + return (0); \ + } \ + table.pfrt_flags &= ~PFR_TFLAG_PERSIST; \ + } while(0) + +int +pfctl_do_clear_tables(const char *anchor, int opts) +{ + int rv; + + if ((rv = pfctl_table(0, NULL, NULL, "-F", NULL, anchor, opts)) == -1) { + if ((opts & PF_OPT_IGNFAIL) == 0) + exit(1); + } + + return (rv); +} + +void +pfctl_show_tables(const char *anchor, int opts) +{ + if (pfctl_table(0, NULL, NULL, "-s", NULL, anchor, opts)) + exit(1); +} + +int +pfctl_table(int argc, char *argv[], char *tname, const char *command, + char *file, const char *anchor, int opts) +{ + struct pfr_table table; + struct pfr_buffer b, b2; + struct pfr_addr *a, *a2; + int nadd = 0, ndel = 0, nchange = 0, nzero = 0; + int rv = 0, flags = 0, nmatch = 0; + void *p; + + if (command == NULL) + usage(); + if (opts & PF_OPT_NOACTION) + flags |= PFR_FLAG_DUMMY; + + bzero(&b, sizeof(b)); + bzero(&b2, sizeof(b2)); + bzero(&table, sizeof(table)); + if (tname != NULL) { + if (strlen(tname) >= PF_TABLE_NAME_SIZE) + usage(); + if (strlcpy(table.pfrt_name, tname, + sizeof(table.pfrt_name)) >= sizeof(table.pfrt_name)) + errx(1, "pfctl_table: strlcpy"); + } + if (strlcpy(table.pfrt_anchor, anchor, + sizeof(table.pfrt_anchor)) >= sizeof(table.pfrt_anchor)) + errx(1, "pfctl_table: strlcpy"); + + if (!strcmp(command, "-F")) { + if (argc || file != NULL) + usage(); + RVTEST(pfctl_clear_tables(pfh, &table, &ndel, flags)); + xprintf(opts, "%d tables deleted", ndel); + } else if (!strcmp(command, "-s")) { + b.pfrb_type = (opts & PF_OPT_VERBOSE2) ? + PFRB_TSTATS : PFRB_TABLES; + if (argc || file != NULL) + usage(); + + if ((opts & PF_OPT_SHOWALL) && b.pfrb_size > 0) + pfctl_print_title("TABLES:"); + + if (opts & PF_OPT_VERBOSE2) { + uintptr_t arg = opts & PF_OPT_DEBUG; + pfctl_get_tstats(pfh, &table, + (pfctl_get_tstats_fn)print_tstats, (void *)arg); + } else { + for (;;) { + pfr_buf_grow(&b, b.pfrb_size); + b.pfrb_size = b.pfrb_msize; + RVTEST(pfr_get_tables(&table, + b.pfrb_caddr, &b.pfrb_size, flags)); + if (b.pfrb_size <= b.pfrb_msize) + break; + } + + if ((opts & PF_OPT_SHOWALL) && b.pfrb_size > 0) + pfctl_print_title("TABLES:"); + + PFRB_FOREACH(p, &b) + print_table(p, opts & PF_OPT_VERBOSE, + opts & PF_OPT_DEBUG); + } + } else if (!strcmp(command, "kill")) { + if (argc || file != NULL) + usage(); + RVTEST(pfr_del_table(&table, &ndel, flags)); + xprintf(opts, "%d table deleted", ndel); + } else if (!strcmp(command, "flush")) { + if (argc || file != NULL) + usage(); + RVTEST(pfr_clr_addrs(&table, &ndel, flags)); + xprintf(opts, "%d addresses deleted", ndel); + } else if (!strcmp(command, "add")) { + b.pfrb_type = PFRB_ADDRS; + if (load_addr(&b, argc, argv, file, 0, opts)) + goto _error; + CREATE_TABLE; + if (opts & PF_OPT_VERBOSE) + flags |= PFR_FLAG_FEEDBACK; + RVTEST(pfr_add_addrs(&table, b.pfrb_caddr, b.pfrb_size, + &nadd, flags)); + xprintf(opts, "%d/%d addresses added", nadd, b.pfrb_size); + if (opts & PF_OPT_VERBOSE) + PFRB_FOREACH(a, &b) + if ((opts & PF_OPT_VERBOSE2) || + a->pfra_fback != PFR_FB_NONE) + print_addrx(a, NULL, + opts & PF_OPT_USEDNS); + } else if (!strcmp(command, "delete")) { + b.pfrb_type = PFRB_ADDRS; + if (load_addr(&b, argc, argv, file, 0, opts)) + goto _error; + if (opts & PF_OPT_VERBOSE) + flags |= PFR_FLAG_FEEDBACK; + RVTEST(pfr_del_addrs(&table, b.pfrb_caddr, b.pfrb_size, + &ndel, flags)); + xprintf(opts, "%d/%d addresses deleted", ndel, b.pfrb_size); + if (opts & PF_OPT_VERBOSE) + PFRB_FOREACH(a, &b) + if ((opts & PF_OPT_VERBOSE2) || + a->pfra_fback != PFR_FB_NONE) + print_addrx(a, NULL, + opts & PF_OPT_USEDNS); + } else if (!strcmp(command, "replace")) { + b.pfrb_type = PFRB_ADDRS; + if (load_addr(&b, argc, argv, file, 0, opts)) + goto _error; + CREATE_TABLE; + if (opts & PF_OPT_VERBOSE) + flags |= PFR_FLAG_FEEDBACK; + RVTEST(pfr_set_addrs(&table, b.pfrb_caddr, b.pfrb_size, + &nadd, &ndel, &nchange, flags)); + if (nadd) + xprintf(opts, "%d addresses added", nadd); + if (ndel) + xprintf(opts, "%d addresses deleted", ndel); + if (nchange) + xprintf(opts, "%d addresses changed", nchange); + if (!nadd && !ndel && !nchange) + xprintf(opts, "no changes"); + if (opts & PF_OPT_VERBOSE) + PFRB_FOREACH(a, &b) + if ((opts & PF_OPT_VERBOSE2) || + a->pfra_fback != PFR_FB_NONE) + print_addrx(a, NULL, + opts & PF_OPT_USEDNS); + } else if (!strcmp(command, "expire")) { + const char *errstr; + u_int lifetime; + + b.pfrb_type = PFRB_ASTATS; + b2.pfrb_type = PFRB_ADDRS; + if (argc != 1 || file != NULL) + usage(); + lifetime = strtonum(*argv, 0, UINT_MAX, &errstr); + if (errstr) + errx(1, "expiry time: %s", errstr); + for (;;) { + pfr_buf_grow(&b, b.pfrb_size); + b.pfrb_size = b.pfrb_msize; + RVTEST(pfr_get_astats(&table, b.pfrb_caddr, + &b.pfrb_size, flags)); + if (b.pfrb_size <= b.pfrb_msize) + break; + } + PFRB_FOREACH(p, &b) { + ((struct pfr_astats *)p)->pfras_a.pfra_fback = PFR_FB_NONE; + if (time(NULL) - ((struct pfr_astats *)p)->pfras_tzero > + lifetime) + if (pfr_buf_add(&b2, + &((struct pfr_astats *)p)->pfras_a)) + err(1, "duplicate buffer"); + } + + if (opts & PF_OPT_VERBOSE) + flags |= PFR_FLAG_FEEDBACK; + RVTEST(pfr_del_addrs(&table, b2.pfrb_caddr, b2.pfrb_size, + &ndel, flags)); + xprintf(opts, "%d/%d addresses expired", ndel, b2.pfrb_size); + if (opts & PF_OPT_VERBOSE) + PFRB_FOREACH(a, &b2) + if ((opts & PF_OPT_VERBOSE2) || + a->pfra_fback != PFR_FB_NONE) + print_addrx(a, NULL, + opts & PF_OPT_USEDNS); + } else if (!strcmp(command, "reset")) { + struct pfr_astats *as; + + b.pfrb_type = PFRB_ASTATS; + b2.pfrb_type = PFRB_ADDRS; + if (argc || file != NULL) + usage(); + do { + pfr_buf_grow(&b, b.pfrb_size); + b.pfrb_size = b.pfrb_msize; + RVTEST(pfr_get_astats(&table, b.pfrb_caddr, + &b.pfrb_size, flags)); + } while (b.pfrb_size > b.pfrb_msize); + PFRB_FOREACH(as, &b) { + as->pfras_a.pfra_fback = 0; + if (nonzero_astats(as)) + if (pfr_buf_add(&b2, &as->pfras_a)) + err(1, "duplicate buffer"); + } + + if (opts & PF_OPT_VERBOSE) + flags |= PFR_FLAG_FEEDBACK; + RVTEST(pfr_clr_astats(&table, b2.pfrb_caddr, b2.pfrb_size, + &nzero, flags)); + xprintf(opts, "%d/%d stats cleared", nzero, b.pfrb_size); + if (opts & PF_OPT_VERBOSE) + PFRB_FOREACH(a, &b2) + if ((opts & PF_OPT_VERBOSE2) || a->pfra_fback) + print_addrx(a, NULL, + opts & PF_OPT_USEDNS); + } else if (!strcmp(command, "show")) { + b.pfrb_type = (opts & PF_OPT_VERBOSE) ? + PFRB_ASTATS : PFRB_ADDRS; + if (argc || file != NULL) + usage(); + for (;;) { + pfr_buf_grow(&b, b.pfrb_size); + b.pfrb_size = b.pfrb_msize; + if (opts & PF_OPT_VERBOSE) + RVTEST(pfr_get_astats(&table, b.pfrb_caddr, + &b.pfrb_size, flags)); + else + RVTEST(pfr_get_addrs(&table, b.pfrb_caddr, + &b.pfrb_size, flags)); + if (b.pfrb_size <= b.pfrb_msize) + break; + } + PFRB_FOREACH(p, &b) + if (opts & PF_OPT_VERBOSE) + print_astats(p, opts & PF_OPT_USEDNS); + else + print_addrx(p, NULL, opts & PF_OPT_USEDNS); + } else if (!strcmp(command, "test")) { + b.pfrb_type = PFRB_ADDRS; + b2.pfrb_type = PFRB_ADDRS; + + if (load_addr(&b, argc, argv, file, 1, opts)) + goto _error; + if (opts & PF_OPT_VERBOSE2) { + flags |= PFR_FLAG_REPLACE; + PFRB_FOREACH(a, &b) + if (pfr_buf_add(&b2, a)) + err(1, "duplicate buffer"); + } + RVTEST(pfr_tst_addrs(&table, b.pfrb_caddr, b.pfrb_size, + &nmatch, flags)); + xprintf(opts, "%d/%d addresses match", nmatch, b.pfrb_size); + if ((opts & PF_OPT_VERBOSE) && !(opts & PF_OPT_VERBOSE2)) + PFRB_FOREACH(a, &b) + if (a->pfra_fback == PFR_FB_MATCH) + print_addrx(a, NULL, + opts & PF_OPT_USEDNS); + if (opts & PF_OPT_VERBOSE2) { + a2 = NULL; + PFRB_FOREACH(a, &b) { + a2 = pfr_buf_next(&b2, a2); + print_addrx(a2, a, opts & PF_OPT_USEDNS); + } + } + if (nmatch < b.pfrb_size) + rv = 2; + } else if (!strcmp(command, "zero") && (argc || file != NULL)) { + b.pfrb_type = PFRB_ADDRS; + if (load_addr(&b, argc, argv, file, 0, opts)) + goto _error; + if (opts & PF_OPT_VERBOSE) + flags |= PFR_FLAG_FEEDBACK; + RVTEST(pfr_clr_astats(&table, b.pfrb_caddr, b.pfrb_size, + &nzero, flags)); + xprintf(opts, "%d/%d addresses cleared", nzero, b.pfrb_size); + if (opts & PF_OPT_VERBOSE) + PFRB_FOREACH(a, &b) + if (opts & PF_OPT_VERBOSE2 || + a->pfra_fback != PFR_FB_NONE) + print_addrx(a, NULL, + opts & PF_OPT_USEDNS); + } else if (!strcmp(command, "zero")) { + flags |= PFR_FLAG_ADDRSTOO; + RVTEST(pfctl_clear_tstats(pfh, &table, &nzero, flags)); + xprintf(opts, "%d table/stats cleared", nzero); + } else + warnx("pfctl_table: unknown command '%s'", command); + goto _cleanup; + +_error: + rv = -1; +_cleanup: + pfr_buf_clear(&b); + pfr_buf_clear(&b2); + return (rv); +} + +void +print_table(const struct pfr_table *ta, int verbose, int debug) +{ + if (!debug && !(ta->pfrt_flags & PFR_TFLAG_ACTIVE)) + return; + if (verbose) + printf("%c%c%c%c%c%c%c\t", + (ta->pfrt_flags & PFR_TFLAG_CONST) ? 'c' : '-', + (ta->pfrt_flags & PFR_TFLAG_PERSIST) ? 'p' : '-', + (ta->pfrt_flags & PFR_TFLAG_ACTIVE) ? 'a' : '-', + (ta->pfrt_flags & PFR_TFLAG_INACTIVE) ? 'i' : '-', + (ta->pfrt_flags & PFR_TFLAG_REFERENCED) ? 'r' : '-', + (ta->pfrt_flags & PFR_TFLAG_REFDANCHOR) ? 'h' : '-', + (ta->pfrt_flags & PFR_TFLAG_COUNTERS) ? 'C' : '-'); + + printf("%s", ta->pfrt_name); + if (ta->pfrt_anchor[0] != '\0') + printf("@%s", ta->pfrt_anchor); + + printf("\n"); +} + +int +print_tstats(const struct pfr_tstats *ts, int debug) +{ + time_t time = ts->pfrts_tzero; + int dir, op; + char *ct; + + if (!debug && !(ts->pfrts_flags & PFR_TFLAG_ACTIVE)) + return (0); + ct = ctime(&time); + print_table(&ts->pfrts_t, 1, debug); + printf("\tAddresses: %d\n", ts->pfrts_cnt); + if (ct) + printf("\tCleared: %s", ct); + else + printf("\tCleared: %lld\n", (long long)time); + printf("\tReferences: [ Anchors: %-18d Rules: %-18d ]\n", + ts->pfrts_refcnt[PFR_REFCNT_ANCHOR], + ts->pfrts_refcnt[PFR_REFCNT_RULE]); + printf("\tEvaluations: [ NoMatch: %-18llu Match: %-18llu ]\n", + (unsigned long long)ts->pfrts_nomatch, + (unsigned long long)ts->pfrts_match); + for (dir = 0; dir < PFR_DIR_MAX; dir++) + for (op = 0; op < PFR_OP_TABLE_MAX; op++) + printf("\t%-12s [ Packets: %-18llu Bytes: %-18llu ]\n", + stats_text[dir][op], + (unsigned long long)ts->pfrts_packets[dir][op], + (unsigned long long)ts->pfrts_bytes[dir][op]); + + return (0); +} + +int +load_addr(struct pfr_buffer *b, int argc, char *argv[], char *file, + int nonetwork, int opts) +{ + while (argc--) + if (append_addr(b, *argv++, nonetwork, opts)) { + if (errno) + warn("cannot decode %s", argv[-1]); + return (-1); + } + if (pfr_buf_load(b, file, nonetwork, append_addr, opts)) { + warn("cannot load %s", file); + return (-1); + } + return (0); +} + +void +print_addrx(struct pfr_addr *ad, struct pfr_addr *rad, int dns) +{ + char ch, buf[256] = "{error}"; + char fb[] = { ' ', 'M', 'A', 'D', 'C', 'Z', 'X', ' ', 'Y', ' ' }; + unsigned int fback, hostnet; + + fback = (rad != NULL) ? rad->pfra_fback : ad->pfra_fback; + ch = (fback < sizeof(fb)/sizeof(*fb)) ? fb[fback] : '?'; + hostnet = (ad->pfra_af == AF_INET6) ? 128 : 32; + inet_ntop(ad->pfra_af, &ad->pfra_u, buf, sizeof(buf)); + printf("%c %c%s", ch, (ad->pfra_not?'!':' '), buf); + if (ad->pfra_net < hostnet) + printf("/%d", ad->pfra_net); + if (rad != NULL && fback != PFR_FB_NONE) { + if (strlcpy(buf, "{error}", sizeof(buf)) >= sizeof(buf)) + errx(1, "print_addrx: strlcpy"); + inet_ntop(rad->pfra_af, &rad->pfra_u, buf, sizeof(buf)); + printf("\t%c%s", (rad->pfra_not?'!':' '), buf); + if (rad->pfra_net < hostnet) + printf("/%d", rad->pfra_net); + } + if (rad != NULL && fback == PFR_FB_NONE) + printf("\t nomatch"); + if (dns && ad->pfra_net == hostnet) { + char host[NI_MAXHOST]; + struct sockaddr_storage ss; + + strlcpy(host, "?", sizeof(host)); + bzero(&ss, sizeof(ss)); + ss.ss_family = ad->pfra_af; + if (ss.ss_family == AF_INET) { + struct sockaddr_in *sin = (struct sockaddr_in *)&ss; + + sin->sin_len = sizeof(*sin); + sin->sin_addr = ad->pfra_ip4addr; + } else { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&ss; + + sin6->sin6_len = sizeof(*sin6); + sin6->sin6_addr = ad->pfra_ip6addr; + } + if (getnameinfo((struct sockaddr *)&ss, ss.ss_len, host, + sizeof(host), NULL, 0, NI_NAMEREQD) == 0) + printf("\t(%s)", host); + } + printf("\n"); +} + +int +nonzero_astats(struct pfr_astats *as) +{ + uint64_t s = 0; + + for (int dir = 0; dir < PFR_DIR_MAX; dir++) + for (int op = 0; op < PFR_OP_ADDR_MAX; op++) + s |= as->pfras_packets[dir][op] | + as->pfras_bytes[dir][op]; + + return (!!s); +} + +void +print_astats(struct pfr_astats *as, int dns) +{ + time_t time = as->pfras_tzero; + int dir, op; + char *ct; + + ct = ctime(&time); + print_addrx(&as->pfras_a, NULL, dns); + if (ct) + printf("\tCleared: %s", ct); + else + printf("\tCleared: %lld\n", (long long)time); + if (as->pfras_a.pfra_fback == PFR_FB_NOCOUNT) + return; + for (dir = 0; dir < PFR_DIR_MAX; dir++) + for (op = 0; op < PFR_OP_ADDR_MAX; op++) + printf("\t%-12s [ Packets: %-18llu Bytes: %-18llu ]\n", + stats_text[dir][op], + (unsigned long long)as->pfras_packets[dir][op], + (unsigned long long)as->pfras_bytes[dir][op]); +} + +int +pfctl_define_table(char *name, int flags, int addrs, const char *anchor, + struct pfr_buffer *ab, u_int32_t ticket, struct pfr_uktable *ukt) +{ + struct pfr_table tbl_buf; + struct pfr_table *tbl; + + if (ukt == NULL) { + bzero(&tbl_buf, sizeof(tbl_buf)); + tbl = &tbl_buf; + } else { + if (ab->pfrb_size != 0) { + /* + * copy IP addresses which come with table from + * temporal buffer to buffer attached to table. + */ + ukt->pfrukt_addrs = *ab; + ab->pfrb_size = 0; + ab->pfrb_msize = 0; + ab->pfrb_caddr = NULL; + } else + memset(&ukt->pfrukt_addrs, 0, + sizeof(struct pfr_buffer)); + + tbl = &ukt->pfrukt_t; + } + + if (strlcpy(tbl->pfrt_name, name, sizeof(tbl->pfrt_name)) >= + sizeof(tbl->pfrt_name) || + strlcpy(tbl->pfrt_anchor, anchor, sizeof(tbl->pfrt_anchor)) >= + sizeof(tbl->pfrt_anchor)) + errx(1, "%s: strlcpy", __func__); + tbl->pfrt_flags = flags; + DBGPRINT("%s %s@%s [%x]\n", __func__, tbl->pfrt_name, tbl->pfrt_anchor, + tbl->pfrt_flags); + + /* + * non-root anchors processed by parse.y are loaded to kernel later. + * Here we load tables, which are either created for root anchor + * or by 'pfctl -t ... -T ...' command. + */ + if (ukt != NULL) + return (0); + + return (pfr_ina_define(tbl, ab->pfrb_caddr, ab->pfrb_size, NULL, NULL, + ticket, addrs ? PFR_FLAG_ADDRSTOO : 0)); +} + +void +warn_duplicate_tables(const char *tablename, const char *anchorname) +{ + struct pfr_buffer b; + struct pfr_table *t; + + bzero(&b, sizeof(b)); + b.pfrb_type = PFRB_TABLES; + for (;;) { + pfr_buf_grow(&b, b.pfrb_size); + b.pfrb_size = b.pfrb_msize; + if (pfr_get_tables(NULL, b.pfrb_caddr, + &b.pfrb_size, PFR_FLAG_ALLRSETS)) + err(1, "pfr_get_tables"); + if (b.pfrb_size <= b.pfrb_msize) + break; + } + PFRB_FOREACH(t, &b) { + if (!(t->pfrt_flags & PFR_TFLAG_ACTIVE)) + continue; + if (!strcmp(anchorname, t->pfrt_anchor)) + continue; + if (!strcmp(tablename, t->pfrt_name)) + warnx("warning: table <%s> already defined" + " in anchor \"%s\"", tablename, + t->pfrt_anchor[0] ? t->pfrt_anchor : "/"); + } + pfr_buf_clear(&b); +} + +void +xprintf(int opts, const char *fmt, ...) +{ + va_list args; + + if (opts & PF_OPT_QUIET) + return; + + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + + if (opts & PF_OPT_DUMMYACTION) + fprintf(stderr, " (dummy).\n"); + else if (opts & PF_OPT_NOACTION) + fprintf(stderr, " (syntax only).\n"); + else + fprintf(stderr, ".\n"); +} + + +/* interface stuff */ + +void +pfctl_show_ifaces(const char *filter, int opts) +{ + struct pfr_buffer b; + struct pfi_kif *p; + + bzero(&b, sizeof(b)); + b.pfrb_type = PFRB_IFACES; + for (;;) { + pfr_buf_grow(&b, b.pfrb_size); + b.pfrb_size = b.pfrb_msize; + if (pfi_get_ifaces(filter, b.pfrb_caddr, &b.pfrb_size)) + errx(1, "%s", pf_strerror(errno)); + if (b.pfrb_size <= b.pfrb_msize) + break; + } + if (opts & PF_OPT_SHOWALL) + pfctl_print_title("INTERFACES:"); + PFRB_FOREACH(p, &b) + print_iface(p, opts); +} + +void +print_iface(struct pfi_kif *p, int opts) +{ + time_t tzero = p->pfik_tzero; + int i, af, dir, act; + char *ct; + + printf("%s", p->pfik_name); + if (opts & PF_OPT_VERBOSE) { + if (p->pfik_flags & PFI_IFLAG_SKIP) + printf(" (skip)"); + } + printf("\n"); + + if (!(opts & PF_OPT_VERBOSE2)) + return; + ct = ctime(&tzero); + if (ct) + printf("\tCleared: %s", ct); + else + printf("\tCleared: %lld\n", (long long)tzero); + printf("\tReferences: %-18d\n", p->pfik_rulerefs); + for (i = 0; i < 8; i++) { + af = (i>>2) & 1; + dir = (i>>1) &1; + act = i & 1; + printf("\t%-12s [ Packets: %-18llu Bytes: %-18llu ]\n", + istats_text[af][dir][act], + (unsigned long long)p->pfik_packets[af][dir][act], + (unsigned long long)p->pfik_bytes[af][dir][act]); + } +} diff --git a/sbin/pfctl/tests/Makefile b/sbin/pfctl/tests/Makefile new file mode 100644 index 000000000000..281cd97dee78 --- /dev/null +++ b/sbin/pfctl/tests/Makefile @@ -0,0 +1,12 @@ +PACKAGE= tests + +ATF_TESTS_C= pfctl_test +ATF_TESTS_SH= macro + +LIBADD+= sbuf +SUBDIR+= files +WARNS=6 + +pfctl_test.o: pfctl_test_list.inc + +.include <bsd.test.mk> diff --git a/sbin/pfctl/tests/Makefile.depend b/sbin/pfctl/tests/Makefile.depend new file mode 100644 index 000000000000..a59dba5bf1aa --- /dev/null +++ b/sbin/pfctl/tests/Makefile.depend @@ -0,0 +1,15 @@ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + lib/${CSU_DIR} \ + lib/atf/libatf-c \ + lib/libc \ + lib/libcompiler_rt \ + lib/libsbuf \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/sbin/pfctl/tests/files/Makefile b/sbin/pfctl/tests/files/Makefile new file mode 100644 index 000000000000..fc52b1db3c30 --- /dev/null +++ b/sbin/pfctl/tests/files/Makefile @@ -0,0 +1,9 @@ +PACKAGE= tests + +TESTSDIR= ${TESTSBASE}/sbin/pfctl/files +BINDIR= ${TESTSDIR} + +# We use ${.CURDIR} as workaround so that the glob patterns work. +FILES!= echo ${.CURDIR}/pf????.in ${.CURDIR}/pf????.include ${.CURDIR}/pf????.ok ${.CURDIR}/pf????.fail + +.include <bsd.progs.mk> diff --git a/sbin/pfctl/tests/files/Makefile.depend b/sbin/pfctl/tests/files/Makefile.depend new file mode 100644 index 000000000000..11aba52f82cf --- /dev/null +++ b/sbin/pfctl/tests/files/Makefile.depend @@ -0,0 +1,10 @@ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/sbin/pfctl/tests/files/pf0001.in b/sbin/pfctl/tests/files/pf0001.in new file mode 100644 index 000000000000..494eee3560fe --- /dev/null +++ b/sbin/pfctl/tests/files/pf0001.in @@ -0,0 +1,8 @@ +pass in all +pass in from any to any no state +pass in proto tcp from any port <= 1024 to any label foo_bar +pass in proto tcp from any to any port = 25 +pass in proto tcp from 10.0.0.0/8 port > 1024 to ! 10.1.2.3 port != 22 +pass in proto igmp from 10.0.0.0/8 to 10.1.1.1 allow-opts +pass in proto tcp from { 1.2.3.4, 1.2.3.5 } to any label \ +"$nr:$proto:$srcaddr:$srcport:$dstaddr:$dstport" diff --git a/sbin/pfctl/tests/files/pf0001.ok b/sbin/pfctl/tests/files/pf0001.ok new file mode 100644 index 000000000000..10fb28bb33dc --- /dev/null +++ b/sbin/pfctl/tests/files/pf0001.ok @@ -0,0 +1,8 @@ +pass in all flags S/SA keep state +pass in all no state +pass in proto tcp from any port <= 1024 to any flags S/SA keep state label "foo_bar" +pass in proto tcp from any to any port = smtp flags S/SA keep state +pass in inet proto tcp from 10.0.0.0/8 port > 1024 to ! 10.1.2.3 port != ssh flags S/SA keep state +pass in inet proto igmp from 10.0.0.0/8 to 10.1.1.1 keep state allow-opts +pass in inet proto tcp from 1.2.3.4 to any flags S/SA keep state label "6:tcp:1.2.3.4::any:" +pass in inet proto tcp from 1.2.3.5 to any flags S/SA keep state label "7:tcp:1.2.3.5::any:" diff --git a/sbin/pfctl/tests/files/pf0002.in b/sbin/pfctl/tests/files/pf0002.in new file mode 100644 index 000000000000..bef5d9b08d1c --- /dev/null +++ b/sbin/pfctl/tests/files/pf0002.in @@ -0,0 +1,34 @@ +# test + +block out log on tun1000000 all +block in log on tun1000000 all + +block return-rst out log on tun1000000 proto tcp all +block return-rst in log on tun1000000 proto tcp all +block return-icmp out log on tun1000000 proto udp all +block return-icmp in log on tun1000000 proto udp all + +block out log quick on tun1000000 from ! 157.161.48.183 to any + +block in quick on tun1000000 from any to 255.255.255.255 + +block in log quick on tun1000000 from 10.0.0.0/8 to any +block in log quick on tun1000000 from 172.16.0.0/12 to any +block in quick log on tun1000000 from 192.168.0.0/16 to any +block in quick log on tun1000000 from 255.255.255.255/32 to any + +block in log quick from no-route to any + +pass out on tun1000000 inet proto icmp all icmp-type 8 code 0 keep state +pass in on tun1000000 inet proto icmp all icmp-type 8 code 0 keep state + +pass out on tun1000000 proto udp all keep state + +pass in on tun1000000 proto udp from any to any port = domain keep state + +pass out on tun1000000 proto tcp all keep state + +pass in on tun1000000 proto tcp from any to any port = ssh keep state +pass in on tun1000000 proto tcp from any to any port = smtp keep state +pass in on tun1000000 proto tcp from any to any port = domain keep state +pass in on tun1000000 proto tcp from any to any port = auth keep state diff --git a/sbin/pfctl/tests/files/pf0002.ok b/sbin/pfctl/tests/files/pf0002.ok new file mode 100644 index 000000000000..02e3099013e5 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0002.ok @@ -0,0 +1,22 @@ +block drop out log on tun1000000 all +block drop in log on tun1000000 all +block return-rst out log on tun1000000 proto tcp all +block return-rst in log on tun1000000 proto tcp all +block return-icmp(port-unr, port-unr) out log on tun1000000 proto udp all +block return-icmp(port-unr, port-unr) in log on tun1000000 proto udp all +block drop out log quick on tun1000000 inet from ! 157.161.48.183 to any +block drop in quick on tun1000000 inet from any to 255.255.255.255 +block drop in log quick on tun1000000 inet from 10.0.0.0/8 to any +block drop in log quick on tun1000000 inet from 172.16.0.0/12 to any +block drop in log quick on tun1000000 inet from 192.168.0.0/16 to any +block drop in log quick on tun1000000 inet from 255.255.255.255 to any +block drop in log quick from no-route to any +pass out on tun1000000 inet proto icmp all icmp-type echoreq code 0 keep state +pass in on tun1000000 inet proto icmp all icmp-type echoreq code 0 keep state +pass out on tun1000000 proto udp all keep state +pass in on tun1000000 proto udp from any to any port = domain keep state +pass out on tun1000000 proto tcp all flags S/SA keep state +pass in on tun1000000 proto tcp from any to any port = ssh flags S/SA keep state +pass in on tun1000000 proto tcp from any to any port = smtp flags S/SA keep state +pass in on tun1000000 proto tcp from any to any port = domain flags S/SA keep state +pass in on tun1000000 proto tcp from any to any port = auth flags S/SA keep state diff --git a/sbin/pfctl/tests/files/pf0003.in b/sbin/pfctl/tests/files/pf0003.in new file mode 100644 index 000000000000..fc82383434b9 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0003.in @@ -0,0 +1,13 @@ +pass in all +pass in from any to any + +block in proto tcp from any to any flags FUPEW/FSRPAUEW +block in proto tcp from any to any flags SF/SFRA +block in proto tcp from any to any flags /SFRAW + +pass in proto { udp, icmp, tcp } from any to any flags S/SA +pass in from any to any flags S/SA no state +pass in from any to any flags any no state +pass in from any to any flags any +pass in from any to any keep state +pass in from any to any diff --git a/sbin/pfctl/tests/files/pf0003.ok b/sbin/pfctl/tests/files/pf0003.ok new file mode 100644 index 000000000000..1d9432f9d6c4 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0003.ok @@ -0,0 +1,13 @@ +pass in all flags S/SA keep state +pass in all flags S/SA keep state +block drop in proto tcp all flags FPUEW/FSRPAUEW +block drop in proto tcp all flags FS/FSRA +block drop in proto tcp all flags /FSRAW +pass in proto udp all keep state +pass in proto icmp all keep state +pass in proto tcp all flags S/SA keep state +pass in all flags S/SA no state +pass in all no state +pass in all flags any keep state +pass in all flags S/SA keep state +pass in all flags S/SA keep state diff --git a/sbin/pfctl/tests/files/pf0004.in b/sbin/pfctl/tests/files/pf0004.in new file mode 100644 index 000000000000..dcd6ee916b37 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0004.in @@ -0,0 +1,16 @@ +block in all +block in proto tcp all +block in proto { tcp, udp } all + +block in from any to any +block in from 10.0.0.0/8 to any +block in from ! 10.0.0.0/8 to any +block in from { 10.0.0.0/8, 172.16.0.0/12 } to any + +block in proto tcp from any port = ssh to any +block in proto tcp from any port { ssh, ftp >< 2048, != 1234, >= www } \ + to any port 1024:2048 + +block in proto { tcp, udp } from { 10.0.0.0/8, 172.16.0.0/12 } port { ssh, ftp } \ + to { 192.168.0.0/16, 12.34.56.78 } port { 6667, 6668, 6669:65535 } + diff --git a/sbin/pfctl/tests/files/pf0004.ok b/sbin/pfctl/tests/files/pf0004.ok new file mode 100644 index 000000000000..87b71cdeff3d --- /dev/null +++ b/sbin/pfctl/tests/files/pf0004.ok @@ -0,0 +1,62 @@ +block drop in all +block drop in proto tcp all +block drop in proto tcp all +block drop in proto udp all +block drop in all +block drop in inet from 10.0.0.0/8 to any +block drop in inet from ! 10.0.0.0/8 to any +block drop in inet from 10.0.0.0/8 to any +block drop in inet from 172.16.0.0/12 to any +block drop in proto tcp from any port = ssh to any +block drop in proto tcp from any port = ssh to any port 1024:2048 +block drop in proto tcp from any port 21 >< 2048 to any port 1024:2048 +block drop in proto tcp from any port != 1234 to any port 1024:2048 +block drop in proto tcp from any port >= 80 to any port 1024:2048 +block drop in inet proto tcp from 10.0.0.0/8 port = ssh to 192.168.0.0/16 port = ircd +block drop in inet proto tcp from 10.0.0.0/8 port = ssh to 192.168.0.0/16 port = 6668 +block drop in inet proto tcp from 10.0.0.0/8 port = ssh to 192.168.0.0/16 port 6669:65535 +block drop in inet proto tcp from 10.0.0.0/8 port = ftp to 192.168.0.0/16 port = ircd +block drop in inet proto tcp from 10.0.0.0/8 port = ftp to 192.168.0.0/16 port = 6668 +block drop in inet proto tcp from 10.0.0.0/8 port = ftp to 192.168.0.0/16 port 6669:65535 +block drop in inet proto tcp from 10.0.0.0/8 port = ssh to 12.34.56.78 port = ircd +block drop in inet proto tcp from 10.0.0.0/8 port = ssh to 12.34.56.78 port = 6668 +block drop in inet proto tcp from 10.0.0.0/8 port = ssh to 12.34.56.78 port 6669:65535 +block drop in inet proto tcp from 10.0.0.0/8 port = ftp to 12.34.56.78 port = ircd +block drop in inet proto tcp from 10.0.0.0/8 port = ftp to 12.34.56.78 port = 6668 +block drop in inet proto tcp from 10.0.0.0/8 port = ftp to 12.34.56.78 port 6669:65535 +block drop in inet proto tcp from 172.16.0.0/12 port = ssh to 192.168.0.0/16 port = ircd +block drop in inet proto tcp from 172.16.0.0/12 port = ssh to 192.168.0.0/16 port = 6668 +block drop in inet proto tcp from 172.16.0.0/12 port = ssh to 192.168.0.0/16 port 6669:65535 +block drop in inet proto tcp from 172.16.0.0/12 port = ftp to 192.168.0.0/16 port = ircd +block drop in inet proto tcp from 172.16.0.0/12 port = ftp to 192.168.0.0/16 port = 6668 +block drop in inet proto tcp from 172.16.0.0/12 port = ftp to 192.168.0.0/16 port 6669:65535 +block drop in inet proto tcp from 172.16.0.0/12 port = ssh to 12.34.56.78 port = ircd +block drop in inet proto tcp from 172.16.0.0/12 port = ssh to 12.34.56.78 port = 6668 +block drop in inet proto tcp from 172.16.0.0/12 port = ssh to 12.34.56.78 port 6669:65535 +block drop in inet proto tcp from 172.16.0.0/12 port = ftp to 12.34.56.78 port = ircd +block drop in inet proto tcp from 172.16.0.0/12 port = ftp to 12.34.56.78 port = 6668 +block drop in inet proto tcp from 172.16.0.0/12 port = ftp to 12.34.56.78 port 6669:65535 +block drop in inet proto udp from 10.0.0.0/8 port = ssh to 192.168.0.0/16 port = 6667 +block drop in inet proto udp from 10.0.0.0/8 port = ssh to 192.168.0.0/16 port = 6668 +block drop in inet proto udp from 10.0.0.0/8 port = ssh to 192.168.0.0/16 port 6669:65535 +block drop in inet proto udp from 10.0.0.0/8 port = ftp to 192.168.0.0/16 port = 6667 +block drop in inet proto udp from 10.0.0.0/8 port = ftp to 192.168.0.0/16 port = 6668 +block drop in inet proto udp from 10.0.0.0/8 port = ftp to 192.168.0.0/16 port 6669:65535 +block drop in inet proto udp from 10.0.0.0/8 port = ssh to 12.34.56.78 port = 6667 +block drop in inet proto udp from 10.0.0.0/8 port = ssh to 12.34.56.78 port = 6668 +block drop in inet proto udp from 10.0.0.0/8 port = ssh to 12.34.56.78 port 6669:65535 +block drop in inet proto udp from 10.0.0.0/8 port = ftp to 12.34.56.78 port = 6667 +block drop in inet proto udp from 10.0.0.0/8 port = ftp to 12.34.56.78 port = 6668 +block drop in inet proto udp from 10.0.0.0/8 port = ftp to 12.34.56.78 port 6669:65535 +block drop in inet proto udp from 172.16.0.0/12 port = ssh to 192.168.0.0/16 port = 6667 +block drop in inet proto udp from 172.16.0.0/12 port = ssh to 192.168.0.0/16 port = 6668 +block drop in inet proto udp from 172.16.0.0/12 port = ssh to 192.168.0.0/16 port 6669:65535 +block drop in inet proto udp from 172.16.0.0/12 port = ftp to 192.168.0.0/16 port = 6667 +block drop in inet proto udp from 172.16.0.0/12 port = ftp to 192.168.0.0/16 port = 6668 +block drop in inet proto udp from 172.16.0.0/12 port = ftp to 192.168.0.0/16 port 6669:65535 +block drop in inet proto udp from 172.16.0.0/12 port = ssh to 12.34.56.78 port = 6667 +block drop in inet proto udp from 172.16.0.0/12 port = ssh to 12.34.56.78 port = 6668 +block drop in inet proto udp from 172.16.0.0/12 port = ssh to 12.34.56.78 port 6669:65535 +block drop in inet proto udp from 172.16.0.0/12 port = ftp to 12.34.56.78 port = 6667 +block drop in inet proto udp from 172.16.0.0/12 port = ftp to 12.34.56.78 port = 6668 +block drop in inet proto udp from 172.16.0.0/12 port = ftp to 12.34.56.78 port 6669:65535 diff --git a/sbin/pfctl/tests/files/pf0005.in b/sbin/pfctl/tests/files/pf0005.in new file mode 100644 index 000000000000..6ad7040c2ed1 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0005.in @@ -0,0 +1,6 @@ +foo = "ssh, ftp" +bar = "other thing" +inside="10.0.0.0/8" + +block in proto udp from $inside port { echo, $foo, ident } \ + to 12.34.56.78 port { 6667, 0x10 } diff --git a/sbin/pfctl/tests/files/pf0005.ok b/sbin/pfctl/tests/files/pf0005.ok new file mode 100644 index 000000000000..6158d6779126 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0005.ok @@ -0,0 +1,11 @@ +foo = "ssh, ftp" +bar = "other thing" +inside = "10.0.0.0/8" +block drop in inet proto udp from 10.0.0.0/8 port = echo to 12.34.56.78 port = 6667 +block drop in inet proto udp from 10.0.0.0/8 port = echo to 12.34.56.78 port = 16 +block drop in inet proto udp from 10.0.0.0/8 port = ssh to 12.34.56.78 port = 6667 +block drop in inet proto udp from 10.0.0.0/8 port = ssh to 12.34.56.78 port = 16 +block drop in inet proto udp from 10.0.0.0/8 port = ftp to 12.34.56.78 port = 6667 +block drop in inet proto udp from 10.0.0.0/8 port = ftp to 12.34.56.78 port = 16 +block drop in inet proto udp from 10.0.0.0/8 port = auth to 12.34.56.78 port = 6667 +block drop in inet proto udp from 10.0.0.0/8 port = auth to 12.34.56.78 port = 16 diff --git a/sbin/pfctl/tests/files/pf0006.in b/sbin/pfctl/tests/files/pf0006.in new file mode 100644 index 000000000000..180d36d85db8 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0006.in @@ -0,0 +1,3 @@ +a=b +c=x +a_b_c=d diff --git a/sbin/pfctl/tests/files/pf0006.ok b/sbin/pfctl/tests/files/pf0006.ok new file mode 100644 index 000000000000..85d1e30aa453 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0006.ok @@ -0,0 +1,3 @@ +a = "b" +c = "x" +a_b_c = "d" diff --git a/sbin/pfctl/tests/files/pf0007.in b/sbin/pfctl/tests/files/pf0007.in new file mode 100644 index 000000000000..02514df9cddb --- /dev/null +++ b/sbin/pfctl/tests/files/pf0007.in @@ -0,0 +1,34 @@ +# test modulate state + +block out log on tun1000000 all +block in log on tun1000000 all + +block return-rst out log on tun1000000 proto tcp all +block return-rst in log on tun1000000 proto tcp all +block return-icmp out log on tun1000000 proto udp all +block return-icmp in log on tun1000000 proto udp all + +block out log quick on tun1000000 from ! 157.161.48.183 to any + +block in quick on tun1000000 from any to 255.255.255.255 + +block in log quick on tun1000000 from 10.0.0.0/8 to any +block in log quick on tun1000000 from 172.16.0.0/12 to any +block in log quick on tun1000000 from 192.168.0.0/16 to any +block in log quick on tun1000000 from 255.255.255.255/32 to any + +pass out on tun1000000 inet proto icmp all icmp-type 8 code 0 keep state +pass in on tun1000000 inet proto icmp all icmp-type 8 code 0 keep state + +pass out on tun1000000 proto udp all keep state + +pass in on tun1000000 proto udp from any to any port = domain keep state + +pass out on tun1000000 proto tcp all modulate state +pass in on tun1000000 proto { tcp udp icmp } all modulate state +pass in on tun1000000 proto { udp tcp icmp } all flags S/SA synproxy state + +pass in on tun1000000 proto tcp from any to any port = ssh modulate state +pass in on tun1000000 proto tcp from any to any port = smtp modulate state +pass in on tun1000000 proto tcp from any to any port = domain modulate state +pass in on tun1000000 proto tcp from any to any port = auth modulate state diff --git a/sbin/pfctl/tests/files/pf0007.ok b/sbin/pfctl/tests/files/pf0007.ok new file mode 100644 index 000000000000..357f3180e307 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0007.ok @@ -0,0 +1,27 @@ +block drop out log on tun1000000 all +block drop in log on tun1000000 all +block return-rst out log on tun1000000 proto tcp all +block return-rst in log on tun1000000 proto tcp all +block return-icmp(port-unr, port-unr) out log on tun1000000 proto udp all +block return-icmp(port-unr, port-unr) in log on tun1000000 proto udp all +block drop out log quick on tun1000000 inet from ! 157.161.48.183 to any +block drop in quick on tun1000000 inet from any to 255.255.255.255 +block drop in log quick on tun1000000 inet from 10.0.0.0/8 to any +block drop in log quick on tun1000000 inet from 172.16.0.0/12 to any +block drop in log quick on tun1000000 inet from 192.168.0.0/16 to any +block drop in log quick on tun1000000 inet from 255.255.255.255 to any +pass out on tun1000000 inet proto icmp all icmp-type echoreq code 0 keep state +pass in on tun1000000 inet proto icmp all icmp-type echoreq code 0 keep state +pass out on tun1000000 proto udp all keep state +pass in on tun1000000 proto udp from any to any port = domain keep state +pass out on tun1000000 proto tcp all flags S/SA modulate state +pass in on tun1000000 proto tcp all flags S/SA modulate state +pass in on tun1000000 proto udp all keep state +pass in on tun1000000 proto icmp all keep state +pass in on tun1000000 proto udp all keep state +pass in on tun1000000 proto tcp all flags S/SA synproxy state +pass in on tun1000000 proto icmp all keep state +pass in on tun1000000 proto tcp from any to any port = ssh flags S/SA modulate state +pass in on tun1000000 proto tcp from any to any port = smtp flags S/SA modulate state +pass in on tun1000000 proto tcp from any to any port = domain flags S/SA modulate state +pass in on tun1000000 proto tcp from any to any port = auth flags S/SA modulate state diff --git a/sbin/pfctl/tests/files/pf0008.in b/sbin/pfctl/tests/files/pf0008.in new file mode 100644 index 000000000000..e092bd955afb --- /dev/null +++ b/sbin/pfctl/tests/files/pf0008.in @@ -0,0 +1,2 @@ +extern = "{ ! 10.0.0.0/8, 10.1.2.3 }" +block out log on tun1000001 from $extern to any diff --git a/sbin/pfctl/tests/files/pf0008.ok b/sbin/pfctl/tests/files/pf0008.ok new file mode 100644 index 000000000000..c8786e384cc7 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0008.ok @@ -0,0 +1,3 @@ +extern = "{ ! 10.0.0.0/8, 10.1.2.3 }" +block drop out log on tun1000001 inet from ! 10.0.0.0/8 to any +block drop out log on tun1000001 inet from 10.1.2.3 to any diff --git a/sbin/pfctl/tests/files/pf0009.in b/sbin/pfctl/tests/files/pf0009.in new file mode 100644 index 000000000000..2e4e724dbb84 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0009.in @@ -0,0 +1,3 @@ +interfaces = "{ enc0, tun1000000 }" + +block in on $interfaces all diff --git a/sbin/pfctl/tests/files/pf0009.ok b/sbin/pfctl/tests/files/pf0009.ok new file mode 100644 index 000000000000..c7e9547a8fd3 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0009.ok @@ -0,0 +1,3 @@ +interfaces = "{ enc0, tun1000000 }" +block drop in on enc0 all +block drop in on tun1000000 all diff --git a/sbin/pfctl/tests/files/pf0010.in b/sbin/pfctl/tests/files/pf0010.in new file mode 100644 index 000000000000..250576b9961f --- /dev/null +++ b/sbin/pfctl/tests/files/pf0010.in @@ -0,0 +1,31 @@ +# return variants +pass in inet proto icmp all +pass in inet6 proto icmp6 all +block in inet proto icmp all +block in inet6 proto icmp6 all +block return-rst in inet proto tcp all +block return-rst in inet6 proto tcp all +block return-rst(ttl 10) in inet proto tcp all +block return-rst(ttl 10) in inet6 proto tcp all +block return-icmp in inet proto icmp all +block return-icmp(0) in inet proto icmp all +block return-icmp(net-unr) in inet proto icmp all +block return-icmp(5) in inet proto icmp all +block return-icmp(srcfail) in inet proto icmp all +block return-icmp(10) in inet proto icmp all +block return-icmp(host-prohib) in inet proto icmp all +block return-icmp(15) in inet proto icmp all +block return-icmp(cutoff-preced) in inet proto icmp all +block return-icmp6 in inet6 proto icmp6 all +block return-icmp6(0) in inet6 proto icmp6 all +block return-icmp6(noroute-unr) in inet6 proto icmp6 all +block return-icmp6(1) in inet6 proto icmp6 all +block return-icmp6(admin-unr) in inet6 proto icmp6 all +block return-icmp6(2) in inet6 proto icmp6 all +block return-icmp6(notnbr-unr) in inet6 proto icmp6 all +block return-icmp6(3) in inet6 proto icmp6 all +block return-icmp6(addr-unr) in inet6 proto icmp6 all +block return-icmp6(4) in inet6 proto icmp6 all +block return-icmp6(port-unr) in inet6 proto icmp6 all +block return-icmp(5, 1) in all +block return-icmp(srcfail, admin-unr) in all diff --git a/sbin/pfctl/tests/files/pf0010.ok b/sbin/pfctl/tests/files/pf0010.ok new file mode 100644 index 000000000000..4003c2306e93 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0010.ok @@ -0,0 +1,30 @@ +pass in inet proto icmp all keep state +pass in inet6 proto ipv6-icmp all keep state +block drop in inet proto icmp all +block drop in inet6 proto ipv6-icmp all +block return-rst in inet proto tcp all +block return-rst in inet6 proto tcp all +block return-rst(ttl 10) in inet proto tcp all +block return-rst(ttl 10) in inet6 proto tcp all +block return-icmp(port-unr) in inet proto icmp all +block return-icmp(net-unr) in inet proto icmp all +block return-icmp(net-unr) in inet proto icmp all +block return-icmp(srcfail) in inet proto icmp all +block return-icmp(srcfail) in inet proto icmp all +block return-icmp(host-prohib) in inet proto icmp all +block return-icmp(host-prohib) in inet proto icmp all +block return-icmp(cutoff-preced) in inet proto icmp all +block return-icmp(cutoff-preced) in inet proto icmp all +block return-icmp6(port-unr) in inet6 proto ipv6-icmp all +block return-icmp6(noroute-unr) in inet6 proto ipv6-icmp all +block return-icmp6(noroute-unr) in inet6 proto ipv6-icmp all +block return-icmp6(admin-unr) in inet6 proto ipv6-icmp all +block return-icmp6(admin-unr) in inet6 proto ipv6-icmp all +block return-icmp6(notnbr-unr) in inet6 proto ipv6-icmp all +block return-icmp6(notnbr-unr) in inet6 proto ipv6-icmp all +block return-icmp6(addr-unr) in inet6 proto ipv6-icmp all +block return-icmp6(addr-unr) in inet6 proto ipv6-icmp all +block return-icmp6(port-unr) in inet6 proto ipv6-icmp all +block return-icmp6(port-unr) in inet6 proto ipv6-icmp all +block return-icmp(srcfail, admin-unr) in all +block return-icmp(srcfail, admin-unr) in all diff --git a/sbin/pfctl/tests/files/pf0011.in b/sbin/pfctl/tests/files/pf0011.in new file mode 100644 index 000000000000..a4dd3d574871 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0011.in @@ -0,0 +1,18 @@ +pass in inet proto icmp all icmp-type 0 +pass in inet proto icmp all icmp-type 0 code 0 +pass in inet proto icmp all icmp-type 1 +pass in inet proto icmp all icmp-type 1 code 1 +pass in inet6 proto ipv6-icmp all icmp6-type 0 +pass in inet6 proto ipv6-icmp all icmp6-type 0 code 0 +pass in inet6 proto ipv6-icmp all icmp6-type 1 +pass in inet6 proto ipv6-icmp all icmp6-type 1 code 1 +block in inet proto icmp all icmp-type 0 +block in inet proto icmp all icmp-type 0 code 0 +block in inet proto icmp all icmp-type 1 +block in inet proto icmp all icmp-type 1 code 1 +block in inet6 proto ipv6-icmp all icmp6-type 0 +block in inet6 proto ipv6-icmp all icmp6-type 0 code 0 +block in inet6 proto ipv6-icmp all icmp6-type 1 +block in inet6 proto ipv6-icmp all icmp6-type 1 code 1 +pass in inet proto icmp all icmp-type unreach code needfrag +pass in inet6 proto ipv6-icmp all icmp6-type timex code reassemb diff --git a/sbin/pfctl/tests/files/pf0011.ok b/sbin/pfctl/tests/files/pf0011.ok new file mode 100644 index 000000000000..1268e772db26 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0011.ok @@ -0,0 +1,18 @@ +pass in inet proto icmp all icmp-type echorep keep state +pass in inet proto icmp all icmp-type echorep code 0 keep state +pass in inet proto icmp all icmp-type 1 keep state +pass in inet proto icmp all icmp-type 1 code 1 keep state +pass in inet6 proto ipv6-icmp all icmp6-type 0 keep state +pass in inet6 proto ipv6-icmp all icmp6-type 0 code 0 keep state +pass in inet6 proto ipv6-icmp all icmp6-type unreach keep state +pass in inet6 proto ipv6-icmp all icmp6-type unreach code admin-unr keep state +block drop in inet proto icmp all icmp-type echorep +block drop in inet proto icmp all icmp-type echorep code 0 +block drop in inet proto icmp all icmp-type 1 +block drop in inet proto icmp all icmp-type 1 code 1 +block drop in inet6 proto ipv6-icmp all icmp6-type 0 +block drop in inet6 proto ipv6-icmp all icmp6-type 0 code 0 +block drop in inet6 proto ipv6-icmp all icmp6-type unreach +block drop in inet6 proto ipv6-icmp all icmp6-type unreach code admin-unr +pass in inet proto icmp all icmp-type unreach code needfrag keep state +pass in inet6 proto ipv6-icmp all icmp6-type timex code reassemb keep state diff --git a/sbin/pfctl/tests/files/pf0012.in b/sbin/pfctl/tests/files/pf0012.in new file mode 100644 index 000000000000..15e4eae6af66 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0012.in @@ -0,0 +1,5 @@ +pass in from 127.0.0.1 to 127.0.0.1/8 no state +pass in from 127.0.0.1/16 to 127.0.0.1/24 no state +pass in from 127.0.0.1/25 to ! 127.0.0.1/26 +pass in inet from ! localhost to localhost/16 +pass in inet from ! lo0 to ! lo0/8 diff --git a/sbin/pfctl/tests/files/pf0012.ok b/sbin/pfctl/tests/files/pf0012.ok new file mode 100644 index 000000000000..4ca6765f377d --- /dev/null +++ b/sbin/pfctl/tests/files/pf0012.ok @@ -0,0 +1,5 @@ +pass in inet from 127.0.0.1 to 127.0.0.0/8 no state +pass in inet from 127.0.0.0/16 to 127.0.0.0/24 no state +pass in inet from 127.0.0.0/25 to ! 127.0.0.0/26 flags S/SA keep state +pass in inet from ! 127.0.0.1 to 127.0.0.0/16 flags S/SA keep state +pass in inet from ! 127.0.0.1 to ! 127.0.0.0/8 flags S/SA keep state diff --git a/sbin/pfctl/tests/files/pf0013.in b/sbin/pfctl/tests/files/pf0013.in new file mode 100644 index 000000000000..a0504019e07d --- /dev/null +++ b/sbin/pfctl/tests/files/pf0013.in @@ -0,0 +1,22 @@ +pass in quick on enc0 from any to any +pass in quick on enc0 inet from any to any +pass in quick on enc0 inet6 from any to any + +#pass out quick on tun1000000 inet from any to any route-to tun1000001 +#pass out quick on tun1000000 from any to 192.168.1.1 route-to tun1000001 +#pass out quick on tun1000000 from any to fec0::1 route-to tun1000001 + +#pass in on tun1000000 proto tcp from any to any port = 21 dup-to (tun1000001 192.168.1.1) +#pass in on tun1000000 proto tcp from any to any port = 21 dup-to (tun1000001 fec0::1) + +#pass in quick on tun1000000 from 192.168.1.1/32 to 10.1.1.1/32 route-to tun1000001 +#pass in quick on tun1000000 from fec0::1/64 to fec1::2/128 route-to tun1000001 + +#pass in on tun1000000 proto tcp from any to any port = 21 reply-to (tun1000001 192.168.1.1) +#pass in on tun1000000 proto tcp from any to any port = 21 reply-to (tun1000001 fec0::1) + +#pass in quick on tun1000000 from 192.168.1.1/32 to 10.1.1.1/32 reply-to tun1000001 +#pass in quick on tun1000000 from fec0::1/64 to fec1::2/128 reply-to tun1000001 + +#pass in quick on tun1000000 from 192.168.1.1/32 to 10.1.1.1/32 dup-to (tun1000001 192.168.1.100) +#pass in quick on tun1000000 from fec0::1/64 to fec1::2/128 dup-to (tun1000001 fec1::2) diff --git a/sbin/pfctl/tests/files/pf0013.ok b/sbin/pfctl/tests/files/pf0013.ok new file mode 100644 index 000000000000..9783e40518b9 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0013.ok @@ -0,0 +1,3 @@ +pass in quick on enc0 all flags S/SA keep state +pass in quick on enc0 inet all flags S/SA keep state +pass in quick on enc0 inet6 all flags S/SA keep state diff --git a/sbin/pfctl/tests/files/pf0014.in b/sbin/pfctl/tests/files/pf0014.in new file mode 100644 index 000000000000..eaca6de0fbfc --- /dev/null +++ b/sbin/pfctl/tests/files/pf0014.in @@ -0,0 +1,6 @@ +pass in quick on lo0 from fe80::1%lo0 to fe80::1%lo0 +pass in quick from fe80::1%lo0 to fe80::1%lo0 +pass in quick from fe80::1%lo0 to any +pass in quick from any to fe80::1%lo0 +pass in quick on lo0 from fe80::1%lo0 to any +pass in quick on lo0 from any to fe80::1%lo0 diff --git a/sbin/pfctl/tests/files/pf0014.ok b/sbin/pfctl/tests/files/pf0014.ok new file mode 100644 index 000000000000..15cc43ff77c4 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0014.ok @@ -0,0 +1,6 @@ +pass in quick on lo0 inet6 from fe80::1 to fe80::1 flags S/SA keep state +pass in quick on lo0 inet6 from fe80::1 to fe80::1 flags S/SA keep state +pass in quick on lo0 inet6 from fe80::1 to any flags S/SA keep state +pass in quick on lo0 inet6 from any to fe80::1 flags S/SA keep state +pass in quick on lo0 inet6 from fe80::1 to any flags S/SA keep state +pass in quick on lo0 inet6 from any to fe80::1 flags S/SA keep state diff --git a/sbin/pfctl/tests/files/pf0016.in b/sbin/pfctl/tests/files/pf0016.in new file mode 100644 index 000000000000..7dbc53aa6a21 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0016.in @@ -0,0 +1,5 @@ +# Test rule order processing: should fail unless nat -> filter +match out on lo0 from 192.168.1.1 to any nat-to 10.0.0.1 +match in on lo0 proto tcp from any to 1.2.3.4/32 port 2222 rdr-to 10.0.0.10 port 22 +match on lo0 from 192.168.1.1 to any binat-to 10.0.0.1 +pass in on lo1000000 from any to any no state diff --git a/sbin/pfctl/tests/files/pf0016.ok b/sbin/pfctl/tests/files/pf0016.ok new file mode 100644 index 000000000000..d65374a16475 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0016.ok @@ -0,0 +1,5 @@ +match out on lo0 inet from 192.168.1.1 to any nat-to 10.0.0.1 +match in on lo0 inet proto tcp from any to 1.2.3.4 port = 2222 rdr-to 10.0.0.10 port 22 +match out on lo0 inet from 192.168.1.1 to any nat-to 10.0.0.1 static-port +match in on lo0 inet from any to 10.0.0.1 rdr-to 192.168.1.1 +pass in on lo1000000 all no state diff --git a/sbin/pfctl/tests/files/pf0018.in b/sbin/pfctl/tests/files/pf0018.in new file mode 100644 index 000000000000..ab3c81f86c5f --- /dev/null +++ b/sbin/pfctl/tests/files/pf0018.in @@ -0,0 +1,19 @@ +# test nat + +TEST_LIST1 = "{ 192.168.1.5, 192.168.1.6, 192.168.1.7 }" +TEST_LIST2 = "{ 172.6.1.1, 172.14.1.2/32, 172.16.2.0/24 }" + +match out on lo0 from 192.168.1.1 to any nat-to 10.0.0.1 +match out on lo0 proto tcp from 192.168.1.2 to any nat-to 10.0.0.2 +match out on lo0 proto udp from 192.168.1.3 to any nat-to 10.0.0.3 +match out on lo0 proto icmp from 192.168.1.4 to any nat-to 10.0.0.4 + +match out on lo0 inet from $TEST_LIST1 to $TEST_LIST2 nat-to lo0 + +match out on lo0 inet from 192.168.0.1/24 to any nat-to (lo0) + +match out on lo0 from 192.168.1.8 to ! 172.17.0.0/16 nat-to 10.0.0.8 + +match out on ! lo0 proto { udp, tcp } from any to any nat-to 10.0.0.8 static-port + +match out on { lo0, tun1000000 } from any to any nat-to 10.0.0.8 diff --git a/sbin/pfctl/tests/files/pf0018.ok b/sbin/pfctl/tests/files/pf0018.ok new file mode 100644 index 000000000000..6ba137ae84f8 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0018.ok @@ -0,0 +1,21 @@ +TEST_LIST1 = "{ 192.168.1.5, 192.168.1.6, 192.168.1.7 }" +TEST_LIST2 = "{ 172.6.1.1, 172.14.1.2/32, 172.16.2.0/24 }" +match out on lo0 inet from 192.168.1.1 to any nat-to 10.0.0.1 +match out on lo0 inet proto tcp from 192.168.1.2 to any nat-to 10.0.0.2 +match out on lo0 inet proto udp from 192.168.1.3 to any nat-to 10.0.0.3 +match out on lo0 inet proto icmp from 192.168.1.4 to any nat-to 10.0.0.4 +match out on lo0 inet from 192.168.1.5 to 172.6.1.1 nat-to 127.0.0.1 +match out on lo0 inet from 192.168.1.5 to 172.14.1.2 nat-to 127.0.0.1 +match out on lo0 inet from 192.168.1.5 to 172.16.2.0/24 nat-to 127.0.0.1 +match out on lo0 inet from 192.168.1.6 to 172.6.1.1 nat-to 127.0.0.1 +match out on lo0 inet from 192.168.1.6 to 172.14.1.2 nat-to 127.0.0.1 +match out on lo0 inet from 192.168.1.6 to 172.16.2.0/24 nat-to 127.0.0.1 +match out on lo0 inet from 192.168.1.7 to 172.6.1.1 nat-to 127.0.0.1 +match out on lo0 inet from 192.168.1.7 to 172.14.1.2 nat-to 127.0.0.1 +match out on lo0 inet from 192.168.1.7 to 172.16.2.0/24 nat-to 127.0.0.1 +match out on lo0 inet from 192.168.0.0/24 to any nat-to (lo0) round-robin +match out on lo0 inet from 192.168.1.8 to ! 172.17.0.0/16 nat-to 10.0.0.8 +match out on ! lo0 inet proto udp all nat-to 10.0.0.8 static-port +match out on ! lo0 inet proto tcp all nat-to 10.0.0.8 static-port +match out on lo0 inet all nat-to 10.0.0.8 +match out on tun1000000 inet all nat-to 10.0.0.8 diff --git a/sbin/pfctl/tests/files/pf0019.in b/sbin/pfctl/tests/files/pf0019.in new file mode 100644 index 000000000000..e2bedbb64bd0 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0019.in @@ -0,0 +1,9 @@ +EVIL = "lo0" +GOOD = "{ lo0, lo1000000 }" +GOOD_NET = "{ 127.0.0.0/24, 10.0.1.0/24 }" +DEST_NET = "{ 1.2.3.4/25, 2.4.6.8/30 }" + +match in on lo0 proto tcp from any to 1.2.3.4/32 port 2222 rdr-to 10.0.0.10 port 22 + +# Test list processing +match in on $GOOD proto tcp from $GOOD_NET to $DEST_NET port 21 rdr-to 127.0.0.1 port 8021 diff --git a/sbin/pfctl/tests/files/pf0019.ok b/sbin/pfctl/tests/files/pf0019.ok new file mode 100644 index 000000000000..a5afc374d19f --- /dev/null +++ b/sbin/pfctl/tests/files/pf0019.ok @@ -0,0 +1,13 @@ +EVIL = "lo0" +GOOD = "{ lo0, lo1000000 }" +GOOD_NET = "{ 127.0.0.0/24, 10.0.1.0/24 }" +DEST_NET = "{ 1.2.3.4/25, 2.4.6.8/30 }" +match in on lo0 inet proto tcp from any to 1.2.3.4 port = 2222 rdr-to 10.0.0.10 port 22 +match in on lo0 inet proto tcp from 127.0.0.0/24 to 1.2.3.0/25 port = ftp rdr-to 127.0.0.1 port 8021 +match in on lo0 inet proto tcp from 127.0.0.0/24 to 2.4.6.8/30 port = ftp rdr-to 127.0.0.1 port 8021 +match in on lo0 inet proto tcp from 10.0.1.0/24 to 1.2.3.0/25 port = ftp rdr-to 127.0.0.1 port 8021 +match in on lo0 inet proto tcp from 10.0.1.0/24 to 2.4.6.8/30 port = ftp rdr-to 127.0.0.1 port 8021 +match in on lo1000000 inet proto tcp from 127.0.0.0/24 to 1.2.3.0/25 port = ftp rdr-to 127.0.0.1 port 8021 +match in on lo1000000 inet proto tcp from 127.0.0.0/24 to 2.4.6.8/30 port = ftp rdr-to 127.0.0.1 port 8021 +match in on lo1000000 inet proto tcp from 10.0.1.0/24 to 1.2.3.0/25 port = ftp rdr-to 127.0.0.1 port 8021 +match in on lo1000000 inet proto tcp from 10.0.1.0/24 to 2.4.6.8/30 port = ftp rdr-to 127.0.0.1 port 8021 diff --git a/sbin/pfctl/tests/files/pf0020.in b/sbin/pfctl/tests/files/pf0020.in new file mode 100644 index 000000000000..c973785bc9c5 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0020.in @@ -0,0 +1,9 @@ +# Test whether list expansion in NAT/RDR works correctly + +EVIL = "lo0" +GOOD = "{ lo0, lo1000000 }" +GOOD_NET = "{ 127.0.0.0/24, 10.0.1.0/24 }" +DEST_NET = "{ 1.2.3.4/25, 2.4.6.8/30 }" + +match out on $EVIL inet from $GOOD_NET to $DEST_NET nat-to $EVIL +match in on $GOOD proto tcp from $GOOD_NET to $DEST_NET port 21 rdr-to 127.0.0.1 port 8021 diff --git a/sbin/pfctl/tests/files/pf0020.ok b/sbin/pfctl/tests/files/pf0020.ok new file mode 100644 index 000000000000..bd2c6cf2055d --- /dev/null +++ b/sbin/pfctl/tests/files/pf0020.ok @@ -0,0 +1,16 @@ +EVIL = "lo0" +GOOD = "{ lo0, lo1000000 }" +GOOD_NET = "{ 127.0.0.0/24, 10.0.1.0/24 }" +DEST_NET = "{ 1.2.3.4/25, 2.4.6.8/30 }" +match out on lo0 inet from 127.0.0.0/24 to 1.2.3.0/25 nat-to 127.0.0.1 +match out on lo0 inet from 127.0.0.0/24 to 2.4.6.8/30 nat-to 127.0.0.1 +match out on lo0 inet from 10.0.1.0/24 to 1.2.3.0/25 nat-to 127.0.0.1 +match out on lo0 inet from 10.0.1.0/24 to 2.4.6.8/30 nat-to 127.0.0.1 +match in on lo0 inet proto tcp from 127.0.0.0/24 to 1.2.3.0/25 port = ftp rdr-to 127.0.0.1 port 8021 +match in on lo0 inet proto tcp from 127.0.0.0/24 to 2.4.6.8/30 port = ftp rdr-to 127.0.0.1 port 8021 +match in on lo0 inet proto tcp from 10.0.1.0/24 to 1.2.3.0/25 port = ftp rdr-to 127.0.0.1 port 8021 +match in on lo0 inet proto tcp from 10.0.1.0/24 to 2.4.6.8/30 port = ftp rdr-to 127.0.0.1 port 8021 +match in on lo1000000 inet proto tcp from 127.0.0.0/24 to 1.2.3.0/25 port = ftp rdr-to 127.0.0.1 port 8021 +match in on lo1000000 inet proto tcp from 127.0.0.0/24 to 2.4.6.8/30 port = ftp rdr-to 127.0.0.1 port 8021 +match in on lo1000000 inet proto tcp from 10.0.1.0/24 to 1.2.3.0/25 port = ftp rdr-to 127.0.0.1 port 8021 +match in on lo1000000 inet proto tcp from 10.0.1.0/24 to 2.4.6.8/30 port = ftp rdr-to 127.0.0.1 port 8021 diff --git a/sbin/pfctl/tests/files/pf0022.in b/sbin/pfctl/tests/files/pf0022.in new file mode 100644 index 000000000000..602a085c59f0 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0022.in @@ -0,0 +1,8 @@ +set optimization aggressive +set timeout { tcp.closing 6, tcp.opening 6 } +set timeout tcp.first 6 +set limit states 500 +set limit {states 1000,frags 1000} +set loginterface lo0 +set loginterface none +set hostid 1 diff --git a/sbin/pfctl/tests/files/pf0022.ok b/sbin/pfctl/tests/files/pf0022.ok new file mode 100644 index 000000000000..76940552aa3a --- /dev/null +++ b/sbin/pfctl/tests/files/pf0022.ok @@ -0,0 +1,10 @@ +set optimization aggressive +set timeout tcp.closing 6 +set timeout tcp.opening 6 +set timeout tcp.first 6 +set limit states 500 +set limit states 1000 +set limit frags 1000 +set loginterface lo0 +set loginterface none +set hostid 0x00000001 diff --git a/sbin/pfctl/tests/files/pf0023.in b/sbin/pfctl/tests/files/pf0023.in new file mode 100644 index 000000000000..2adbe16c4a50 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0023.in @@ -0,0 +1,2 @@ +#test negated interface matching +block in on ! lo0 all diff --git a/sbin/pfctl/tests/files/pf0023.ok b/sbin/pfctl/tests/files/pf0023.ok new file mode 100644 index 000000000000..83a75fe716af --- /dev/null +++ b/sbin/pfctl/tests/files/pf0023.ok @@ -0,0 +1 @@ +block drop in on ! lo0 all diff --git a/sbin/pfctl/tests/files/pf0024.in b/sbin/pfctl/tests/files/pf0024.in new file mode 100644 index 000000000000..73c204933633 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0024.in @@ -0,0 +1,8 @@ +#test variable concat +a="ssh" +b="ftp" +c=$a $b +d=$a $b $a $b +e=$a $b $b "test" $a $b + +pass in proto tcp from any to any port { $c } diff --git a/sbin/pfctl/tests/files/pf0024.ok b/sbin/pfctl/tests/files/pf0024.ok new file mode 100644 index 000000000000..c6ff2f037012 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0024.ok @@ -0,0 +1,7 @@ +a = "ssh" +b = "ftp" +c = "ssh ftp" +d = "ssh ftp ssh ftp" +e = "ssh ftp ftp test ssh ftp" +pass in proto tcp from any to any port = ssh flags S/SA keep state +pass in proto tcp from any to any port = ftp flags S/SA keep state diff --git a/sbin/pfctl/tests/files/pf0025.in b/sbin/pfctl/tests/files/pf0025.in new file mode 100644 index 000000000000..28d1a335ccf8 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0025.in @@ -0,0 +1,4 @@ +antispoof for lo0 +antispoof log quick for lo0 inet +antispoof for (lo0) +antispoof log quick for (lo0) inet diff --git a/sbin/pfctl/tests/files/pf0025.ok b/sbin/pfctl/tests/files/pf0025.ok new file mode 100644 index 000000000000..f4fc7766dc02 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0025.ok @@ -0,0 +1,5 @@ +block drop in on ! lo0 inet6 from ::1 to any +block drop in on ! lo0 inet from 127.0.0.0/8 to any +block drop in log quick on ! lo0 inet from 127.0.0.0/8 to any +block drop in on ! lo0 from (lo0:network) to any +block drop in log quick on ! lo0 inet from (lo0:network) to any diff --git a/sbin/pfctl/tests/files/pf0026.in b/sbin/pfctl/tests/files/pf0026.in new file mode 100644 index 000000000000..5799de5afe9e --- /dev/null +++ b/sbin/pfctl/tests/files/pf0026.in @@ -0,0 +1,2 @@ +block in on lo0 inet from ! (lo0) to any +block out on lo0 inet from any to ! (lo0) diff --git a/sbin/pfctl/tests/files/pf0026.ok b/sbin/pfctl/tests/files/pf0026.ok new file mode 100644 index 000000000000..a9a281244a69 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0026.ok @@ -0,0 +1,2 @@ +block drop in on lo0 inet from ! (lo0) to any +block drop out on lo0 inet from any to ! (lo0) diff --git a/sbin/pfctl/tests/files/pf0028.in b/sbin/pfctl/tests/files/pf0028.in new file mode 100644 index 000000000000..cfcc0b952200 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0028.in @@ -0,0 +1,7 @@ +# test logging keywords, and log quick/quick log order +block in log (all) quick on lo0 all +block in quick log on lo0 all +block in quick log (all) on lo0 all +block in log quick on lo0 all +block in log on lo0 all +block in log (all) on lo0 all diff --git a/sbin/pfctl/tests/files/pf0028.ok b/sbin/pfctl/tests/files/pf0028.ok new file mode 100644 index 000000000000..ff6ca332dff4 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0028.ok @@ -0,0 +1,6 @@ +block drop in log (all) quick on lo0 all +block drop in log quick on lo0 all +block drop in log (all) quick on lo0 all +block drop in log quick on lo0 all +block drop in log on lo0 all +block drop in log (all) on lo0 all diff --git a/sbin/pfctl/tests/files/pf0030.in b/sbin/pfctl/tests/files/pf0030.in new file mode 100644 index 000000000000..8ea257809291 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0030.in @@ -0,0 +1,7 @@ +#test line continuation + +block \ + in \ + on lo0 \ + from any \ + to any diff --git a/sbin/pfctl/tests/files/pf0030.ok b/sbin/pfctl/tests/files/pf0030.ok new file mode 100644 index 000000000000..11fb969bbb91 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0030.ok @@ -0,0 +1 @@ +block drop in on lo0 all diff --git a/sbin/pfctl/tests/files/pf0031.in b/sbin/pfctl/tests/files/pf0031.in new file mode 100644 index 000000000000..c227829f1121 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0031.in @@ -0,0 +1,21 @@ +set block-policy drop +block return in on lo0 all +block return in on lo0 inet all +block return in on lo0 inet6 all +block drop in on lo0 all +block drop in on lo0 inet all +block drop in on lo0 inet6 all +block in on lo0 all +block in on lo0 inet all +block in on lo0 inet6 all +#set block-policy return +block return in on lo0 all +block return in on lo0 inet all +block return in on lo0 inet6 all +block drop in on lo0 all +block drop in on lo0 inet all +block drop in on lo0 inet6 all +block in on lo0 all +block in on lo0 inet all +block in on lo0 inet6 all + diff --git a/sbin/pfctl/tests/files/pf0031.ok b/sbin/pfctl/tests/files/pf0031.ok new file mode 100644 index 000000000000..d19a2797da21 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0031.ok @@ -0,0 +1,19 @@ +set block-policy drop +block return in on lo0 all +block return in on lo0 inet all +block return in on lo0 inet6 all +block drop in on lo0 all +block drop in on lo0 inet all +block drop in on lo0 inet6 all +block drop in on lo0 all +block drop in on lo0 inet all +block drop in on lo0 inet6 all +block return in on lo0 all +block return in on lo0 inet all +block return in on lo0 inet6 all +block drop in on lo0 all +block drop in on lo0 inet all +block drop in on lo0 inet6 all +block drop in on lo0 all +block drop in on lo0 inet all +block drop in on lo0 inet6 all diff --git a/sbin/pfctl/tests/files/pf0032.in b/sbin/pfctl/tests/files/pf0032.in new file mode 100644 index 000000000000..333dafa72dd8 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0032.in @@ -0,0 +1,7 @@ +pass in from 10/8 to any +pass in from 10.1/8 to any +pass in from 192.168.37.29/25 to any +pass in from 192.168.37.29/24 to any +pass in from 192.168.37.29/16 to any +pass in from 192.168.37.29/8 to any + diff --git a/sbin/pfctl/tests/files/pf0032.ok b/sbin/pfctl/tests/files/pf0032.ok new file mode 100644 index 000000000000..826ce61ebcb3 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0032.ok @@ -0,0 +1,6 @@ +pass in inet from 10.0.0.0/8 to any flags S/SA keep state +pass in inet from 10.0.0.0/8 to any flags S/SA keep state +pass in inet from 192.168.37.0/25 to any flags S/SA keep state +pass in inet from 192.168.37.0/24 to any flags S/SA keep state +pass in inet from 192.168.0.0/16 to any flags S/SA keep state +pass in inet from 192.0.0.0/8 to any flags S/SA keep state diff --git a/sbin/pfctl/tests/files/pf0034.in b/sbin/pfctl/tests/files/pf0034.in new file mode 100644 index 000000000000..e3248d281e70 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0034.in @@ -0,0 +1,5 @@ +#mixed af, probability +pass in from any to { 127.0.0.1, 2000::1 } +pass in probability 0.5 +pass in probability 50% +pass in inet6 proto tcp from ::1 probability 0.8% diff --git a/sbin/pfctl/tests/files/pf0034.ok b/sbin/pfctl/tests/files/pf0034.ok new file mode 100644 index 000000000000..a91f1ae50d2e --- /dev/null +++ b/sbin/pfctl/tests/files/pf0034.ok @@ -0,0 +1,5 @@ +pass in inet from any to 127.0.0.1 flags S/SA keep state +pass in inet6 from any to 2000::1 flags S/SA keep state +pass in all flags S/SA keep state probability 50% +pass in all flags S/SA keep state probability 50% +pass in inet6 proto tcp from ::1 to any flags S/SA keep state probability 0.8% diff --git a/sbin/pfctl/tests/files/pf0035.in b/sbin/pfctl/tests/files/pf0035.in new file mode 100644 index 000000000000..3d0ab8963297 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0035.in @@ -0,0 +1,5 @@ +#test matching on tos + +intf = "lo0" +pass out on $intf inet proto tcp from any to any port 22 tos 0x10 +pass out on $intf inet proto tcp from any to any port 22 tos 0x08 diff --git a/sbin/pfctl/tests/files/pf0035.ok b/sbin/pfctl/tests/files/pf0035.ok new file mode 100644 index 000000000000..fb77ae59e523 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0035.ok @@ -0,0 +1,3 @@ +intf = "lo0" +pass out on lo0 inet proto tcp from any to any port = ssh flags S/SA tos 0x10 keep state +pass out on lo0 inet proto tcp from any to any port = ssh flags S/SA tos 0x08 keep state diff --git a/sbin/pfctl/tests/files/pf0038.in b/sbin/pfctl/tests/files/pf0038.in new file mode 100644 index 000000000000..1e63d6e5e268 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0038.in @@ -0,0 +1,5 @@ +# test + +pass in on tun1000000 proto tcp from any to any user bin +pass in on tun1000000 proto tcp from any to any group bin +pass in on tun1000000 proto tcp from any to any group wheel user root user bin diff --git a/sbin/pfctl/tests/files/pf0038.ok b/sbin/pfctl/tests/files/pf0038.ok new file mode 100644 index 000000000000..77e2ee63bf5a --- /dev/null +++ b/sbin/pfctl/tests/files/pf0038.ok @@ -0,0 +1,4 @@ +pass in on tun1000000 proto tcp all user = 3 flags S/SA keep state +pass in on tun1000000 proto tcp all group = 7 flags S/SA keep state +pass in on tun1000000 proto tcp all user = 3 group = 0 flags S/SA keep state +pass in on tun1000000 proto tcp all user = 0 group = 0 flags S/SA keep state diff --git a/sbin/pfctl/tests/files/pf0039.in b/sbin/pfctl/tests/files/pf0039.in new file mode 100644 index 000000000000..739f4efd4297 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0039.in @@ -0,0 +1,25 @@ +#test random ordered opts + +body1="pass in log quick on lo0 inet proto icmp all " +body2="pass in log quick on lo0 inet proto tcp all " +o_user="user root " +o_user2="user bin " +o_group="group wheel " +o_group2="group nobody " +o_flags="flags S/SA " +o_icmpspec="icmp-type 0 code 0 " +o_tos="tos 0x08 " +o_keep="keep state " +o_fragment="fragment " +o_allowopts="allow-opts " +o_label="label blah" +o_prio="set prio 2" + +$body2 $o_fragment $o_keep $o_label $o_tos +$body2 $o_user $o_prio $o_tos $o_keep $o_group $o_label $o_allowopts \ +$o_user2 $o_group2 +$body1 $o_icmpspec $o_keep $o_label $o_prio +$body2 $o_keep +$body2 $o_label $o_keep $o_prio $o_tos +$body1 $o_icmpspec $o_tos +$body2 $o_flags $o_allowopts diff --git a/sbin/pfctl/tests/files/pf0039.ok b/sbin/pfctl/tests/files/pf0039.ok new file mode 100644 index 000000000000..524d9d1d9537 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0039.ok @@ -0,0 +1,24 @@ +body1 = "pass in log quick on lo0 inet proto icmp all " +body2 = "pass in log quick on lo0 inet proto tcp all " +o_user = "user root " +o_user2 = "user bin " +o_group = "group wheel " +o_group2 = "group nobody " +o_flags = "flags S/SA " +o_icmpspec = "icmp-type 0 code 0 " +o_tos = "tos 0x08 " +o_keep = "keep state " +o_fragment = "fragment " +o_allowopts = "allow-opts " +o_label = "label blah" +o_prio = "set prio 2" +pass in log quick on lo0 inet proto tcp all tos 0x08 keep state fragment label "blah" +pass in log quick on lo0 inet proto tcp all user = 3 group = 65534 flags S/SA tos 0x08 set ( prio 2 ) keep state allow-opts label "blah" +pass in log quick on lo0 inet proto tcp all user = 3 group = 0 flags S/SA tos 0x08 set ( prio 2 ) keep state allow-opts label "blah" +pass in log quick on lo0 inet proto tcp all user = 0 group = 65534 flags S/SA tos 0x08 set ( prio 2 ) keep state allow-opts label "blah" +pass in log quick on lo0 inet proto tcp all user = 0 group = 0 flags S/SA tos 0x08 set ( prio 2 ) keep state allow-opts label "blah" +pass in log quick on lo0 inet proto icmp all icmp-type echorep code 0 set ( prio 2 ) keep state label "blah" +pass in log quick on lo0 inet proto tcp all flags S/SA keep state +pass in log quick on lo0 inet proto tcp all flags S/SA tos 0x08 set ( prio 2 ) keep state label "blah" +pass in log quick on lo0 inet proto icmp all icmp-type echorep code 0 tos 0x08 keep state +pass in log quick on lo0 inet proto tcp all flags S/SA keep state allow-opts diff --git a/sbin/pfctl/tests/files/pf0040.in b/sbin/pfctl/tests/files/pf0040.in new file mode 100644 index 000000000000..7d91ad447109 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0040.in @@ -0,0 +1,20 @@ +block +block return +block return-rst proto tcp +pass +pass in no state +pass out no state +pass all no state +block in all +block out all +block from any to any +pass in from any to any +pass out from any to any +block on lo0 +pass on lo0 all +block on lo0 from any to any +pass proto tcp flags S/SA +pass proto udp keep state +pass in proto udp all keep state +pass out proto udp from any to any keep state +pass out on lo0 proto tcp from any to any port 25 keep state diff --git a/sbin/pfctl/tests/files/pf0040.ok b/sbin/pfctl/tests/files/pf0040.ok new file mode 100644 index 000000000000..1a740bb96470 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0040.ok @@ -0,0 +1,20 @@ +block drop all +block return all +block return-rst proto tcp all +pass all flags S/SA keep state +pass in all no state +pass out all no state +pass all no state +block drop in all +block drop out all +block drop all +pass in all flags S/SA keep state +pass out all flags S/SA keep state +block drop on lo0 all +pass on lo0 all flags S/SA keep state +block drop on lo0 all +pass proto tcp all flags S/SA keep state +pass proto udp all keep state +pass in proto udp all keep state +pass out proto udp all keep state +pass out on lo0 proto tcp from any to any port = smtp flags S/SA keep state diff --git a/sbin/pfctl/tests/files/pf0041.in b/sbin/pfctl/tests/files/pf0041.in new file mode 100644 index 000000000000..42987e7f0daa --- /dev/null +++ b/sbin/pfctl/tests/files/pf0041.in @@ -0,0 +1,12 @@ +anchor foo +anchor bar all +anchor bar from any to any +anchor foo inet +anchor foo inet6 +anchor foo inet all +anchor foo proto tcp +anchor foo inet proto tcp from 10.1.2.3 port smtp to 10.2.3.4 port ssh +anchor foobar inet6 proto udp from ::1 port 1 to ::1 port 2 +anchor filteropt out proto tcp to any port 22 user root +anchor filteropt in proto tcp to (self) port 22 group sshd +anchor filteropt out inet proto icmp all icmp-type echoreq diff --git a/sbin/pfctl/tests/files/pf0041.ok b/sbin/pfctl/tests/files/pf0041.ok new file mode 100644 index 000000000000..836c7459365c --- /dev/null +++ b/sbin/pfctl/tests/files/pf0041.ok @@ -0,0 +1,12 @@ +anchor "foo" all +anchor "bar" all +anchor "bar" all +anchor "foo" inet all +anchor "foo" inet6 all +anchor "foo" inet all +anchor "foo" proto tcp all +anchor "foo" inet proto tcp from 10.1.2.3 port = smtp to 10.2.3.4 port = ssh +anchor "foobar" inet6 proto udp from ::1 port = tcpmux to ::1 port = compressnet +anchor "filteropt" out proto tcp from any to any port = ssh user = 0 +anchor "filteropt" in proto tcp from any to (self) port = ssh group = 22 +anchor "filteropt" out inet proto icmp all icmp-type echoreq diff --git a/sbin/pfctl/tests/files/pf0047.in b/sbin/pfctl/tests/files/pf0047.in new file mode 100644 index 000000000000..0fcfa14ebb32 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0047.in @@ -0,0 +1,67 @@ +pass in on lo0 all label "" + +pass in all label "$if" +pass in on lo0 all label "$if" +pass in on lo0 all label "$if$if" + +pass in on lo0 all label "$srcaddr" +pass in on lo0 from 0/0 to any label "$srcaddr" +pass in on lo0 from 127.0.0.1 to any label "$srcaddr" +pass in on lo0 from 127.0.0.1 to any label "$srcaddr$srcaddr" +pass in on lo0 from 127.0.0.1 to any label ":$srcaddr:$srcaddr:" +pass in on lo0 from 127.0.0.1/8 to any label "$srcaddr" +pass in on lo0 from 127.0.0.1/16 to any label "$srcaddr$srcaddr" +pass in on lo0 from 127.0.0.1/31 to any label ":$srcaddr:$srcaddr:" +pass in on lo0 inet6 from fe80::1 to any label "$srcaddr" +pass in on lo0 inet6 from fe80::1 to any label "$srcaddr$srcaddr" +pass in on lo0 inet6 from fe80::1 to any label ":$srcaddr:$srcaddr:" +pass in on lo0 inet6 from lo0/8 to any label "$srcaddr" +pass in on lo0 inet6 from lo0/64 to any label "$srcaddr$srcaddr" +pass in on lo0 inet6 from lo0/127 to any label ":$srcaddr:$srcaddr:" + +pass in on lo0 all label "!$dstaddr!" +pass in on lo0 inet from any to (lo0) label "$dstaddr" +pass in on lo0 inet from any to (lo0) label "$dstaddr$dstaddr" +pass in on lo0 inet from any to (lo0) label " $dstaddr $dstaddr " +pass in on lo0 from any to ! 127.0.0.1/8 label "$dstaddr" +pass in on lo0 from any to ! 127.0.0.1/16 label "$dstaddr$dstaddr" +pass in on lo0 from any to ! 127.0.0.1/31 label " $dstaddr $dstaddr " +pass in on lo0 inet6 from any to ! (lo0) label "$dstaddr" +pass in on lo0 inet6 from any to ! (lo0) label "$dstaddr$dstaddr" +pass in on lo0 inet6 from any to ! (lo0) label " $dstaddr $dstaddr " +pass in on lo0 inet6 from any to ! ::1/8 label "$dstaddr" +pass in on lo0 inet6 from any to ! ::1/64 label "$dstaddr$dstaddr" +pass in on lo0 inet6 from any to ! ::1/127 label " $dstaddr $dstaddr " + +pass in on lo0 all label "x$srcportx" +pass in on lo0 proto tcp from any port = 28 to any label "$srcport" +pass in on lo0 proto tcp from any port 28 >< 29 to any label "$srcport" +pass in on lo0 proto tcp from any port 28 <> 29 to any label "$srcport" +pass in on lo0 proto tcp from any port 28:29 to any label "$srcport" +pass in on lo0 proto tcp from any port != 28 to any label "$srcport" +pass in on lo0 proto tcp from any port < 28 to any label "$srcport" +pass in on lo0 proto tcp from any port <= 28 to any label "$srcport" +pass in on lo0 proto tcp from any port > 28 to any label "$srcport" +pass in on lo0 proto tcp from any port >= 28 to any label "$srcport" +pass in on lo0 proto tcp from any port = 28 to any label "$srcport$srcport" +pass in on lo0 proto tcp from any port = 28 to any label "$$srcport$$srcport$" + +pass in on lo0 all label "$dstport" +pass in on lo0 proto udp from any to any port = 29 label "$dstport" +pass in on lo0 proto udp from any to any port != 29 label "$dstport$dstport" +pass in on lo0 proto udp from any to any port > 29 label "x$dstportx$dstportx" + +pass in on lo0 all label "$proto" +pass in on lo0 proto esp all label "$proto" +pass in on lo0 proto esp all label "$proto$proto" +pass in on lo0 proto esp all label "-$proto-$proto-" +pass in on lo0 proto 166 all label "$proto" +pass in on lo0 proto 166 all label "$proto$proto" +pass in on lo0 proto 166 all label "_$proto_$proto_" + +pass in on lo0 all label "$nr" +pass in on lo0 all label "$nr$nr" +pass in on lo0 all label "%$nr%$nr%" + +pass in on lo0 proto tcp from 127.0.0.1 port = 30 to 127.0.0.2 port = 44 \ + label "if $if proto $proto $srcaddr $srcport $dstaddr $dstport" diff --git a/sbin/pfctl/tests/files/pf0047.ok b/sbin/pfctl/tests/files/pf0047.ok new file mode 100644 index 000000000000..12b93bb14e30 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0047.ok @@ -0,0 +1,61 @@ +pass in on lo0 all flags S/SA keep state +pass in all flags S/SA keep state label "any" +pass in on lo0 all flags S/SA keep state label "lo0" +pass in on lo0 all flags S/SA keep state label "lo0lo0" +pass in on lo0 all flags S/SA keep state label "any" +pass in on lo0 inet all flags S/SA keep state label "any" +pass in on lo0 inet from 127.0.0.1 to any flags S/SA keep state label "127.0.0.1" +pass in on lo0 inet from 127.0.0.1 to any flags S/SA keep state label "127.0.0.1127.0.0.1" +pass in on lo0 inet from 127.0.0.1 to any flags S/SA keep state label ":127.0.0.1:127.0.0.1:" +pass in on lo0 inet from 127.0.0.0/8 to any flags S/SA keep state label "127.0.0.0/8" +pass in on lo0 inet from 127.0.0.0/16 to any flags S/SA keep state label "127.0.0.0/16127.0.0.0/16" +pass in on lo0 inet from 127.0.0.0/31 to any flags S/SA keep state label ":127.0.0.0/31:127.0.0.0/31:" +pass in on lo0 inet6 from fe80::1 to any flags S/SA keep state label "fe80::1" +pass in on lo0 inet6 from fe80::1 to any flags S/SA keep state label "fe80::1fe80::1" +pass in on lo0 inet6 from fe80::1 to any flags S/SA keep state label ":fe80::1:fe80::1:" +pass in on lo0 inet6 from ::/8 to any flags S/SA keep state label "::/8" +pass in on lo0 inet6 from fe00::/8 to any flags S/SA keep state label "fe00::/8" +pass in on lo0 inet6 from ::/64 to any flags S/SA keep state label "::/64::/64" +pass in on lo0 inet6 from fe80::/64 to any flags S/SA keep state label "fe80::/64fe80::/64" +pass in on lo0 inet6 from ::/127 to any flags S/SA keep state label ":::/127:::/127:" +pass in on lo0 inet6 from fe80::/127 to any flags S/SA keep state label ":fe80::/127:fe80::/127:" +pass in on lo0 all flags S/SA keep state label "!any!" +pass in on lo0 inet from any to (lo0) flags S/SA keep state label "(lo0)" +pass in on lo0 inet from any to (lo0) flags S/SA keep state label "(lo0)(lo0)" +pass in on lo0 inet from any to (lo0) flags S/SA keep state label " (lo0) (lo0) " +pass in on lo0 inet from any to ! 127.0.0.0/8 flags S/SA keep state label "! 127.0.0.0/8" +pass in on lo0 inet from any to ! 127.0.0.0/16 flags S/SA keep state label "! 127.0.0.0/16! 127.0.0.0/16" +pass in on lo0 inet from any to ! 127.0.0.0/31 flags S/SA keep state label " ! 127.0.0.0/31 ! 127.0.0.0/31 " +pass in on lo0 inet6 from any to ! (lo0) flags S/SA keep state label "! (lo0)" +pass in on lo0 inet6 from any to ! (lo0) flags S/SA keep state label "! (lo0)! (lo0)" +pass in on lo0 inet6 from any to ! (lo0) flags S/SA keep state label " ! (lo0) ! (lo0) " +pass in on lo0 inet6 from any to ! ::/8 flags S/SA keep state label "! ::/8" +pass in on lo0 inet6 from any to ! ::/64 flags S/SA keep state label "! ::/64! ::/64" +pass in on lo0 inet6 from any to ! ::/127 flags S/SA keep state label " ! ::/127 ! ::/127 " +pass in on lo0 all flags S/SA keep state label "xx" +pass in on lo0 proto tcp from any port = 28 to any flags S/SA keep state label "28" +pass in on lo0 proto tcp from any port 28 >< 29 to any flags S/SA keep state label "28><29" +pass in on lo0 proto tcp from any port 28 <> 29 to any flags S/SA keep state label "28<>29" +pass in on lo0 proto tcp from any port 28:29 to any flags S/SA keep state +pass in on lo0 proto tcp from any port != 28 to any flags S/SA keep state label "!=28" +pass in on lo0 proto tcp from any port < 28 to any flags S/SA keep state label "<28" +pass in on lo0 proto tcp from any port <= 28 to any flags S/SA keep state label "<=28" +pass in on lo0 proto tcp from any port > 28 to any flags S/SA keep state label ">28" +pass in on lo0 proto tcp from any port >= 28 to any flags S/SA keep state label ">=28" +pass in on lo0 proto tcp from any port = 28 to any flags S/SA keep state label "2828" +pass in on lo0 proto tcp from any port = 28 to any flags S/SA keep state label "$28$28$" +pass in on lo0 all flags S/SA keep state +pass in on lo0 proto udp from any to any port = msg-icp keep state label "29" +pass in on lo0 proto udp from any to any port != msg-icp keep state label "!=29!=29" +pass in on lo0 proto udp from any to any port > 29 keep state label "x>29x>29x" +pass in on lo0 all flags S/SA keep state label "ip" +pass in on lo0 proto esp all keep state label "esp" +pass in on lo0 proto esp all keep state label "espesp" +pass in on lo0 proto esp all keep state label "-esp-esp-" +pass in on lo0 proto 166 all keep state label "166" +pass in on lo0 proto 166 all keep state label "166166" +pass in on lo0 proto 166 all keep state label "_166_166_" +pass in on lo0 all flags S/SA keep state label "57" +pass in on lo0 all flags S/SA keep state label "5858" +pass in on lo0 all flags S/SA keep state label "%59%59%" +pass in on lo0 inet proto tcp from 127.0.0.1 port = 30 to 127.0.0.2 port = mpm-flags flags S/SA keep state label "if lo0 proto tcp 127.0.0.1 30 127.0.0.2 44" diff --git a/sbin/pfctl/tests/files/pf0048.in b/sbin/pfctl/tests/files/pf0048.in new file mode 100644 index 000000000000..a0dd143c8dd2 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0048.in @@ -0,0 +1,13 @@ +table < regress > { 1.2.3.4 !5.6.7.8 10/8 lo0 } +table <regress.1> const { ::1 fe80::/64 } +table <regress.a> { 1.2.3.4 !5.6.7.8 } { ::1 ::2 ::3 } file "/dev/null" const { 4.3.2.1 } +match out on lo0 inet from < regress.1> to <regress.2> nat-to lo0:0 +match out on !lo0 inet from !<regress.1 > to <regress.2> nat-to lo0:0 +match in on lo0 inet6 from <regress.1> to <regress.2> rdr-to lo0:0 +match in on !lo0 inet6 from !< regress.1 > to <regress.2> rdr-to lo0:0 +match in from { <regress.1> !<regress.2> } to any +match out from any to { !<regress.1>, <regress.2> } +pass in from <regress> to any +pass out from any to <regress > +pass in from { <regress.1> <regress.2> } to any +pass out from any to { !<regress.1>, !<regress.2> } diff --git a/sbin/pfctl/tests/files/pf0048.ok b/sbin/pfctl/tests/files/pf0048.ok new file mode 100644 index 000000000000..89569fb4f8ba --- /dev/null +++ b/sbin/pfctl/tests/files/pf0048.ok @@ -0,0 +1,17 @@ +table <regress> { 1.2.3.4 !5.6.7.8 10.0.0.0/8 ::1 fe80::1 127.0.0.1 } +table <regress.1> const { ::1 fe80::/64 } +table <regress.a> const { 1.2.3.4 !5.6.7.8 ::1 ::2 ::3 } file "/dev/null" { 4.3.2.1 } +match out on lo0 inet from <regress.1> to <regress.2> nat-to 127.0.0.1 +match out on ! lo0 inet from ! <regress.1> to <regress.2> nat-to 127.0.0.1 +match in on lo0 inet6 from <regress.1> to <regress.2> rdr-to ::1 +match in on ! lo0 inet6 from ! <regress.1> to <regress.2> rdr-to ::1 +match in from <regress.1> to any +match in from ! <regress.2> to any +match out from any to ! <regress.1> +match out from any to <regress.2> +pass in from <regress> to any flags S/SA keep state +pass out from any to <regress> flags S/SA keep state +pass in from <regress.1> to any flags S/SA keep state +pass in from <regress.2> to any flags S/SA keep state +pass out from any to ! <regress.1> flags S/SA keep state +pass out from any to ! <regress.2> flags S/SA keep state diff --git a/sbin/pfctl/tests/files/pf0049.in b/sbin/pfctl/tests/files/pf0049.in new file mode 100644 index 000000000000..91b9712f7b30 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0049.in @@ -0,0 +1,7 @@ +#test :broadcast and :network modifiers +pass in on lo0 from lo0:network to any keep state +pass out on lo0 inet from lo0:network to any +pass in on lo0 inet6 from lo0:network to any keep state + +#broadcast on lo0 doesn't make sense at all! +#block in on lo0 from any to lo0:broadcast diff --git a/sbin/pfctl/tests/files/pf0049.ok b/sbin/pfctl/tests/files/pf0049.ok new file mode 100644 index 000000000000..0349424cee1e --- /dev/null +++ b/sbin/pfctl/tests/files/pf0049.ok @@ -0,0 +1,4 @@ +pass in on lo0 inet6 from ::1 to any flags S/SA keep state +pass in on lo0 inet from 127.0.0.0/8 to any flags S/SA keep state +pass out on lo0 inet from 127.0.0.0/8 to any flags S/SA keep state +pass in on lo0 inet6 from ::1 to any flags S/SA keep state diff --git a/sbin/pfctl/tests/files/pf0050.in b/sbin/pfctl/tests/files/pf0050.in new file mode 100644 index 000000000000..e1ecb5274b1e --- /dev/null +++ b/sbin/pfctl/tests/files/pf0050.in @@ -0,0 +1,4 @@ +# double macro set +extif="wi0" +extif="lo0" +block in on $extif diff --git a/sbin/pfctl/tests/files/pf0050.ok b/sbin/pfctl/tests/files/pf0050.ok new file mode 100644 index 000000000000..e891b238639b --- /dev/null +++ b/sbin/pfctl/tests/files/pf0050.ok @@ -0,0 +1,3 @@ +extif = "wi0" +extif = "lo0" +block drop in on lo0 all diff --git a/sbin/pfctl/tests/files/pf0052.in b/sbin/pfctl/tests/files/pf0052.in new file mode 100644 index 000000000000..262d029841d3 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0052.in @@ -0,0 +1,7 @@ +# test setting all optimizations to avoid future keyword clashes + +set optimization normal +set optimization satellite +set optimization high-latency +set optimization conservative +set optimization aggressive diff --git a/sbin/pfctl/tests/files/pf0052.ok b/sbin/pfctl/tests/files/pf0052.ok new file mode 100644 index 000000000000..f83263b2a267 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0052.ok @@ -0,0 +1,5 @@ +set optimization normal +set optimization satellite +set optimization high-latency +set optimization conservative +set optimization aggressive diff --git a/sbin/pfctl/tests/files/pf0053.in b/sbin/pfctl/tests/files/pf0053.in new file mode 100644 index 000000000000..263f99048f1d --- /dev/null +++ b/sbin/pfctl/tests/files/pf0053.in @@ -0,0 +1,4 @@ +pass in proto tcp from { 1.2.3.4, 1.2.3.5 } to any label \ +"$nr:$if:$proto:$srcaddr:$srcport:$dstaddr:$dstport" +pass in on lo0 proto tcp from { 1.2.3.4, 1.2.3.5 } to any label \ +"$nr:$if:$proto:$srcaddr:$srcport:$dstaddr:$dstport" diff --git a/sbin/pfctl/tests/files/pf0053.ok b/sbin/pfctl/tests/files/pf0053.ok new file mode 100644 index 000000000000..91866b724d31 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0053.ok @@ -0,0 +1,4 @@ +pass in inet proto tcp from 1.2.3.4 to any flags S/SA keep state label "0:any:tcp:1.2.3.4::any:" +pass in inet proto tcp from 1.2.3.5 to any flags S/SA keep state label "1:any:tcp:1.2.3.5::any:" +pass in on lo0 inet proto tcp from 1.2.3.4 to any flags S/SA keep state label "2:lo0:tcp:1.2.3.4::any:" +pass in on lo0 inet proto tcp from 1.2.3.5 to any flags S/SA keep state label "3:lo0:tcp:1.2.3.5::any:" diff --git a/sbin/pfctl/tests/files/pf0055.in b/sbin/pfctl/tests/files/pf0055.in new file mode 100644 index 000000000000..849221e316a7 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0055.in @@ -0,0 +1,18 @@ +set timeout { interval 43, frag 23 } +set timeout { tcp.first 423, tcp.opening 123, tcp.established 43758 } +set timeout { tcp.closing 744, tcp.finwait 25, tcp.closed 38 } +set timeout { udp.first 356, udp.single 73, udp.multiple 34 } +set timeout { icmp.first 464, icmp.error 34 } +set timeout { other.first 455, other.single 54, other.multiple 324 } +set timeout { src.track 3600 } +set limit { states 4522, frags 43556 } +set loginterface none +set loginterface lo0 +set hostid 1 +set optimization normal +set block-policy drop + +set limit states 43254 +set limit frags 34557 +set timeout interval 344 +set timeout frag 213 diff --git a/sbin/pfctl/tests/files/pf0055.ok b/sbin/pfctl/tests/files/pf0055.ok new file mode 100644 index 000000000000..2281ca82abd4 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0055.ok @@ -0,0 +1,28 @@ +set timeout interval 43 +set timeout frag 23 +set timeout tcp.first 423 +set timeout tcp.opening 123 +set timeout tcp.established 43758 +set timeout tcp.closing 744 +set timeout tcp.finwait 25 +set timeout tcp.closed 38 +set timeout udp.first 356 +set timeout udp.single 73 +set timeout udp.multiple 34 +set timeout icmp.first 464 +set timeout icmp.error 34 +set timeout other.first 455 +set timeout other.single 54 +set timeout other.multiple 324 +set timeout src.track 3600 +set limit states 4522 +set limit frags 43556 +set loginterface none +set loginterface lo0 +set hostid 0x00000001 +set optimization normal +set block-policy drop +set limit states 43254 +set limit frags 34557 +set timeout interval 344 +set timeout frag 213 diff --git a/sbin/pfctl/tests/files/pf0056.in b/sbin/pfctl/tests/files/pf0056.in new file mode 100644 index 000000000000..691908925488 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0056.in @@ -0,0 +1,2 @@ +pass in proto tcp from any to any port www keep state (tcp.established 60) +pass in proto tcp from any to any port www keep state (max 10, no-sync, tcp.first 2) diff --git a/sbin/pfctl/tests/files/pf0056.ok b/sbin/pfctl/tests/files/pf0056.ok new file mode 100644 index 000000000000..14bf215a4d7d --- /dev/null +++ b/sbin/pfctl/tests/files/pf0056.ok @@ -0,0 +1,2 @@ +pass in proto tcp from any to any port = http flags S/SA keep state (tcp.established 60) +pass in proto tcp from any to any port = http flags S/SA keep state (max 10, no-sync, tcp.first 2, adaptive.start 6, adaptive.end 12) diff --git a/sbin/pfctl/tests/files/pf0057.in b/sbin/pfctl/tests/files/pf0057.in new file mode 100644 index 000000000000..0eca99d162f0 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0057.in @@ -0,0 +1,4 @@ +a="10.0.0.1" +b="x" +b="y" +pass in from $a diff --git a/sbin/pfctl/tests/files/pf0057.ok b/sbin/pfctl/tests/files/pf0057.ok new file mode 100644 index 000000000000..23299e285181 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0057.ok @@ -0,0 +1,4 @@ +a = "10.0.0.1" +b = "x" +b = "y" +pass in inet from 10.0.0.1 to any flags S/SA keep state diff --git a/sbin/pfctl/tests/files/pf0060.in b/sbin/pfctl/tests/files/pf0060.in new file mode 100644 index 000000000000..2824cfd301b2 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0060.in @@ -0,0 +1,11 @@ +# netmask handling w/ multicast + +pass from 224.4.5.4/32 +pass from 224.4.5.4/16 +pass from 224.4.5.4/26 +pass from 224.4.5.65/26 +pass from 224.4.5.134/26 +pass from 224.4.5.199/26 +pass from 224.4.5.4 + + diff --git a/sbin/pfctl/tests/files/pf0060.ok b/sbin/pfctl/tests/files/pf0060.ok new file mode 100644 index 000000000000..f0cd27039fef --- /dev/null +++ b/sbin/pfctl/tests/files/pf0060.ok @@ -0,0 +1,7 @@ +pass inet from 224.4.5.4 to any flags S/SA keep state +pass inet from 224.4.0.0/16 to any flags S/SA keep state +pass inet from 224.4.5.0/26 to any flags S/SA keep state +pass inet from 224.4.5.64/26 to any flags S/SA keep state +pass inet from 224.4.5.128/26 to any flags S/SA keep state +pass inet from 224.4.5.192/26 to any flags S/SA keep state +pass inet from 224.4.5.4 to any flags S/SA keep state diff --git a/sbin/pfctl/tests/files/pf0061.in b/sbin/pfctl/tests/files/pf0061.in new file mode 100644 index 000000000000..7343a39ee64b --- /dev/null +++ b/sbin/pfctl/tests/files/pf0061.in @@ -0,0 +1,4 @@ +# dynaddr with netmask + +pass inet to (lo0)/24 + diff --git a/sbin/pfctl/tests/files/pf0061.ok b/sbin/pfctl/tests/files/pf0061.ok new file mode 100644 index 000000000000..f28451aa473d --- /dev/null +++ b/sbin/pfctl/tests/files/pf0061.ok @@ -0,0 +1 @@ +pass inet from any to (lo0)/24 flags S/SA keep state diff --git a/sbin/pfctl/tests/files/pf0065.in b/sbin/pfctl/tests/files/pf0065.in new file mode 100644 index 000000000000..617ba5f51e0e --- /dev/null +++ b/sbin/pfctl/tests/files/pf0065.in @@ -0,0 +1,2 @@ +antispoof for lo0 label "antispoof-lo0" +antispoof log quick for lo0 inet label "antispoof-lo0-2" diff --git a/sbin/pfctl/tests/files/pf0065.ok b/sbin/pfctl/tests/files/pf0065.ok new file mode 100644 index 000000000000..eaef6485bcd5 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0065.ok @@ -0,0 +1,3 @@ +block drop in on ! lo0 inet6 from ::1 to any label "antispoof-lo0" +block drop in on ! lo0 inet from 127.0.0.0/8 to any label "antispoof-lo0" +block drop in log quick on ! lo0 inet from 127.0.0.0/8 to any label "antispoof-lo0-2" diff --git a/sbin/pfctl/tests/files/pf0067.in b/sbin/pfctl/tests/files/pf0067.in new file mode 100644 index 000000000000..4594420aff0c --- /dev/null +++ b/sbin/pfctl/tests/files/pf0067.in @@ -0,0 +1,3 @@ +pass in quick on tun1000000 keep state tag regress +pass out quick on lo0 keep state tagged regress + diff --git a/sbin/pfctl/tests/files/pf0067.ok b/sbin/pfctl/tests/files/pf0067.ok new file mode 100644 index 000000000000..4b09611f9a06 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0067.ok @@ -0,0 +1,2 @@ +pass in quick on tun1000000 all flags S/SA keep state tag regress +pass out quick on lo0 all flags S/SA keep state tagged regress diff --git a/sbin/pfctl/tests/files/pf0069.in b/sbin/pfctl/tests/files/pf0069.in new file mode 100644 index 000000000000..85847b9bd6b2 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0069.in @@ -0,0 +1,2 @@ +match out on lo0 inet all tag regress nat-to lo0 +pass out quick on lo0 keep state tagged regress diff --git a/sbin/pfctl/tests/files/pf0069.ok b/sbin/pfctl/tests/files/pf0069.ok new file mode 100644 index 000000000000..2bf34c04baa7 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0069.ok @@ -0,0 +1,2 @@ +match out on lo0 inet all tag regress nat-to 127.0.0.1 +pass out quick on lo0 all flags S/SA keep state tagged regress diff --git a/sbin/pfctl/tests/files/pf0070.in b/sbin/pfctl/tests/files/pf0070.in new file mode 100644 index 000000000000..1ccec9302436 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0070.in @@ -0,0 +1,2 @@ +match out on lo0 from 10.0.0.0/8 to any nat-to lo0 +block out on lo0 tagged regress diff --git a/sbin/pfctl/tests/files/pf0070.ok b/sbin/pfctl/tests/files/pf0070.ok new file mode 100644 index 000000000000..cf79485b40c1 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0070.ok @@ -0,0 +1,2 @@ +match out on lo0 inet from 10.0.0.0/8 to any nat-to 127.0.0.1 +block drop out on lo0 all tagged regress diff --git a/sbin/pfctl/tests/files/pf0071.in b/sbin/pfctl/tests/files/pf0071.in new file mode 100644 index 000000000000..8975a8ebc943 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0071.in @@ -0,0 +1,2 @@ +match in on lo0 proto tcp from 10.0.0.0/8 to port 80 rdr-to lo0 +block out on lo0 tagged regress diff --git a/sbin/pfctl/tests/files/pf0071.ok b/sbin/pfctl/tests/files/pf0071.ok new file mode 100644 index 000000000000..2bae94fc8fac --- /dev/null +++ b/sbin/pfctl/tests/files/pf0071.ok @@ -0,0 +1,2 @@ +match in on lo0 inet proto tcp from 10.0.0.0/8 to any port = http rdr-to 127.0.0.1 +block drop out on lo0 all tagged regress diff --git a/sbin/pfctl/tests/files/pf0072.in b/sbin/pfctl/tests/files/pf0072.in new file mode 100644 index 000000000000..d23843b799d5 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0072.in @@ -0,0 +1,3 @@ +# test binat tagging +match on lo0 from 192.168.1.1 to any tag regress binat-to 10.0.0.1 +block out on lo0 tagged regress diff --git a/sbin/pfctl/tests/files/pf0072.ok b/sbin/pfctl/tests/files/pf0072.ok new file mode 100644 index 000000000000..02e676dadc06 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0072.ok @@ -0,0 +1,3 @@ +match out on lo0 inet from 192.168.1.1 to any tag regress nat-to 10.0.0.1 static-port +match in on lo0 inet from any to 10.0.0.1 tag regress rdr-to 192.168.1.1 +block drop out on lo0 all tagged regress diff --git a/sbin/pfctl/tests/files/pf0074.in b/sbin/pfctl/tests/files/pf0074.in new file mode 100644 index 000000000000..521bdd00c889 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0074.in @@ -0,0 +1 @@ +pass in proto tcp synproxy state diff --git a/sbin/pfctl/tests/files/pf0074.ok b/sbin/pfctl/tests/files/pf0074.ok new file mode 100644 index 000000000000..1f5d99dfe106 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0074.ok @@ -0,0 +1 @@ +pass in proto tcp all flags S/SA synproxy state diff --git a/sbin/pfctl/tests/files/pf0075.in b/sbin/pfctl/tests/files/pf0075.in new file mode 100644 index 000000000000..ee12db7b10cf --- /dev/null +++ b/sbin/pfctl/tests/files/pf0075.in @@ -0,0 +1,3 @@ +block in on lo0 proto tcp from 192.168.0.0/24 to port 22 tag ssh +block in quick on lo0 ! tagged ssh +
\ No newline at end of file diff --git a/sbin/pfctl/tests/files/pf0075.ok b/sbin/pfctl/tests/files/pf0075.ok new file mode 100644 index 000000000000..460715b5dd2d --- /dev/null +++ b/sbin/pfctl/tests/files/pf0075.ok @@ -0,0 +1,2 @@ +block drop in on lo0 inet proto tcp from 192.168.0.0/24 to any port = ssh tag ssh +block drop in quick on lo0 all ! tagged ssh diff --git a/sbin/pfctl/tests/files/pf0077.in b/sbin/pfctl/tests/files/pf0077.in new file mode 100644 index 000000000000..b6e32e15a9e7 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0077.in @@ -0,0 +1,5 @@ +# dynaddr with netmask. I never want to see this again: +# <henning@quigon:1>$ echo "pass inet from (le0)/8" | pfctl -nvf - +# pass inet from (l)/8 to any + +pass inet from (lo0)/8 diff --git a/sbin/pfctl/tests/files/pf0077.ok b/sbin/pfctl/tests/files/pf0077.ok new file mode 100644 index 000000000000..233d434b782b --- /dev/null +++ b/sbin/pfctl/tests/files/pf0077.ok @@ -0,0 +1 @@ +pass inet from (lo0)/8 to any flags S/SA keep state diff --git a/sbin/pfctl/tests/files/pf0078.in b/sbin/pfctl/tests/files/pf0078.in new file mode 100644 index 000000000000..0b2368c72c0e --- /dev/null +++ b/sbin/pfctl/tests/files/pf0078.in @@ -0,0 +1,2 @@ +pass in from 10.0.0.1 to <regress> label "$srcaddr:$dstaddr" + diff --git a/sbin/pfctl/tests/files/pf0078.ok b/sbin/pfctl/tests/files/pf0078.ok new file mode 100644 index 000000000000..fed726e4f671 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0078.ok @@ -0,0 +1 @@ +pass in inet from 10.0.0.1 to <regress> flags S/SA keep state label "10.0.0.1:<regress>" diff --git a/sbin/pfctl/tests/files/pf0079.in b/sbin/pfctl/tests/files/pf0079.in new file mode 100644 index 000000000000..402266be8a72 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0079.in @@ -0,0 +1,2 @@ +pass in from 10.0.0.1 to no-route label "$srcaddr:$dstaddr" + diff --git a/sbin/pfctl/tests/files/pf0079.ok b/sbin/pfctl/tests/files/pf0079.ok new file mode 100644 index 000000000000..a21475d63ec8 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0079.ok @@ -0,0 +1 @@ +pass in inet from 10.0.0.1 to no-route flags S/SA keep state label "10.0.0.1:no-route" diff --git a/sbin/pfctl/tests/files/pf0081.in b/sbin/pfctl/tests/files/pf0081.in new file mode 100644 index 000000000000..ac25c49dc65d --- /dev/null +++ b/sbin/pfctl/tests/files/pf0081.in @@ -0,0 +1,12 @@ +# skip step optimization involving dynaddr, tables, no-route +# optimisation should be done on theses rules + +ip_list="{ ::1 ::2 ::3 0.0.0.1 0.0.0.2 0.0.0.3 }" +table_list="{ <bar1> <bar2> <bar3> }" +pass from (lo0) to $ip_list +pass from <foo> to $table_list +pass from <foo> to $ip_list +pass from <foo> to $table_list +pass from no-route to $table_list +pass from no-route to $ip_list +pass from no-route to $table_list diff --git a/sbin/pfctl/tests/files/pf0081.ok b/sbin/pfctl/tests/files/pf0081.ok new file mode 100644 index 000000000000..2b58a18744d9 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0081.ok @@ -0,0 +1,32 @@ +ip_list = "{ ::1 ::2 ::3 0.0.0.1 0.0.0.2 0.0.0.3 }" +table_list = "{ <bar1> <bar2> <bar3> }" +pass inet6 from (lo0) to ::1 flags S/SA keep state +pass inet6 from (lo0) to ::2 flags S/SA keep state +pass inet6 from (lo0) to ::3 flags S/SA keep state +pass inet from (lo0) to 0.0.0.1 flags S/SA keep state +pass inet from (lo0) to 0.0.0.2 flags S/SA keep state +pass inet from (lo0) to 0.0.0.3 flags S/SA keep state +pass from <foo> to <bar1> flags S/SA keep state +pass from <foo> to <bar2> flags S/SA keep state +pass from <foo> to <bar3> flags S/SA keep state +pass inet6 from <foo> to ::1 flags S/SA keep state +pass inet6 from <foo> to ::2 flags S/SA keep state +pass inet6 from <foo> to ::3 flags S/SA keep state +pass inet from <foo> to 0.0.0.1 flags S/SA keep state +pass inet from <foo> to 0.0.0.2 flags S/SA keep state +pass inet from <foo> to 0.0.0.3 flags S/SA keep state +pass from <foo> to <bar1> flags S/SA keep state +pass from <foo> to <bar2> flags S/SA keep state +pass from <foo> to <bar3> flags S/SA keep state +pass from no-route to <bar1> flags S/SA keep state +pass from no-route to <bar2> flags S/SA keep state +pass from no-route to <bar3> flags S/SA keep state +pass inet6 from no-route to ::1 flags S/SA keep state +pass inet6 from no-route to ::2 flags S/SA keep state +pass inet6 from no-route to ::3 flags S/SA keep state +pass inet from no-route to 0.0.0.1 flags S/SA keep state +pass inet from no-route to 0.0.0.2 flags S/SA keep state +pass inet from no-route to 0.0.0.3 flags S/SA keep state +pass from no-route to <bar1> flags S/SA keep state +pass from no-route to <bar2> flags S/SA keep state +pass from no-route to <bar3> flags S/SA keep state diff --git a/sbin/pfctl/tests/files/pf0082.in b/sbin/pfctl/tests/files/pf0082.in new file mode 100644 index 000000000000..7f1751deb365 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0082.in @@ -0,0 +1,15 @@ +# skip step optimization involving dynaddr, tables, no-route + +pass inet from (lo0) +pass inet from !(lo0) +pass inet from (lo0) +pass inet6 from (lo0) +pass from <foo> +pass from !<foo> +pass from <foo> +pass inet from <bar> +pass from <bar> +pass inet6 from <foo> +pass from <foo> +pass inet from no-route +pass from no-route diff --git a/sbin/pfctl/tests/files/pf0082.ok b/sbin/pfctl/tests/files/pf0082.ok new file mode 100644 index 000000000000..4a2071521a35 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0082.ok @@ -0,0 +1,13 @@ +pass inet from (lo0) to any flags S/SA keep state +pass inet from ! (lo0) to any flags S/SA keep state +pass inet from (lo0) to any flags S/SA keep state +pass inet6 from (lo0) to any flags S/SA keep state +pass from <foo> to any flags S/SA keep state +pass from ! <foo> to any flags S/SA keep state +pass from <foo> to any flags S/SA keep state +pass inet from <bar> to any flags S/SA keep state +pass from <bar> to any flags S/SA keep state +pass inet6 from <foo> to any flags S/SA keep state +pass from <foo> to any flags S/SA keep state +pass inet from no-route to any flags S/SA keep state +pass from no-route to any flags S/SA keep state diff --git a/sbin/pfctl/tests/files/pf0084.in b/sbin/pfctl/tests/files/pf0084.in new file mode 100644 index 000000000000..17140a786d73 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0084.in @@ -0,0 +1,17 @@ +match out on tun1000000 from 10.0.0.0/24 to any \ + nat-to { 10.0.1.1, 10.0.1.2 } round-robin sticky-address +match in on tun1000000 from any to 10.0.1.1 \ + rdr-to { 10.0.0.0/24 } sticky-address random +match in on tun1000000 from any to 10.0.1.2 \ + rdr-to { 10.0.0.1, 10.0.0.2 } sticky-address + +pass in proto tcp from any to any port 22 \ + keep state (source-track) +pass in proto tcp from any to any port 25 \ + keep state (source-track global) +pass in proto tcp from any to any port 80 \ + keep state (source-track rule, max-src-nodes 1000, max-src-states 3) +pass in proto tcp from any to any port 123 \ + keep state (source-track, max-src-nodes 1000) +pass in proto tcp from any to any port 321 \ + keep state (source-track, max-src-states 3) diff --git a/sbin/pfctl/tests/files/pf0084.ok b/sbin/pfctl/tests/files/pf0084.ok new file mode 100644 index 000000000000..1ca89e515a3d --- /dev/null +++ b/sbin/pfctl/tests/files/pf0084.ok @@ -0,0 +1,8 @@ +match out on tun1000000 inet from 10.0.0.0/24 to any nat-to { 10.0.1.1, 10.0.1.2 } round-robin sticky-address +match in on tun1000000 inet from any to 10.0.1.1 rdr-to 10.0.0.0/24 random sticky-address +match in on tun1000000 inet from any to 10.0.1.2 rdr-to { 10.0.0.1, 10.0.0.2 } round-robin sticky-address +pass in proto tcp from any to any port = ssh flags S/SA keep state (source-track global) +pass in proto tcp from any to any port = smtp flags S/SA keep state (source-track global) +pass in proto tcp from any to any port = http flags S/SA keep state (source-track rule, max-src-states 3, max-src-nodes 1000) +pass in proto tcp from any to any port = ntp flags S/SA keep state (source-track rule, max-src-nodes 1000) +pass in proto tcp from any to any port = pip flags S/SA keep state (source-track global, max-src-states 3) diff --git a/sbin/pfctl/tests/files/pf0085.in b/sbin/pfctl/tests/files/pf0085.in new file mode 100644 index 000000000000..43dd0e077658 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0085.in @@ -0,0 +1,3 @@ +# test tag macro expansion +pass from { 127.0.0.1 127.0.0.2 127.0.0.3 } keep state tag "$srcaddr" +pass from { 127.0.0.1 127.0.0.2 127.0.0.3 } keep state tagged "$srcaddr" diff --git a/sbin/pfctl/tests/files/pf0085.ok b/sbin/pfctl/tests/files/pf0085.ok new file mode 100644 index 000000000000..07e71ed5f70d --- /dev/null +++ b/sbin/pfctl/tests/files/pf0085.ok @@ -0,0 +1,6 @@ +pass inet from 127.0.0.1 to any flags S/SA keep state tag 127.0.0.1 +pass inet from 127.0.0.2 to any flags S/SA keep state tag 127.0.0.2 +pass inet from 127.0.0.3 to any flags S/SA keep state tag 127.0.0.3 +pass inet from 127.0.0.1 to any flags S/SA keep state tagged 127.0.0.1 +pass inet from 127.0.0.2 to any flags S/SA keep state tagged 127.0.0.2 +pass inet from 127.0.0.3 to any flags S/SA keep state tagged 127.0.0.3 diff --git a/sbin/pfctl/tests/files/pf0087.in b/sbin/pfctl/tests/files/pf0087.in new file mode 100644 index 000000000000..cd19262b83e4 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0087.in @@ -0,0 +1,24 @@ +# pfctl -o rule reordering + +pass in on lo1000000 proto tcp from any to 10.0.0.2 port 22 keep state +pass in on lo1000001 proto tcp from 10.0.0.1 port 22 to 10.0.0.2 keep state +pass in on lo1000001 proto udp from 10.0.0.5 to 10.0.0.4 port 53 keep state +pass in on lo1000000 proto udp from any to 10.0.0.2 port 53 keep state +pass in proto tcp to 10.0.0.1 port 80 keep state +pass out on lo1000001 proto udp from any to 10.0.0.2 port 53 keep state +pass in proto tcp to 10.0.0.3 port 80 keep state +pass out proto tcp to 10.0.0.1 port 81 keep state +pass in proto udp to 10.0.0.3 port 53 keep state +pass in on lo1000001 proto udp from 10.0.0.2 port 53 to 10.0.0.2 keep state +pass out proto udp to 10.0.0.1 port 53 keep state +pass out on lo1000000 proto udp from any to 10.0.0.2 port 53 keep state +pass out proto udp to 10.0.0.3 port 53 keep state +pass out on lo1000000 proto tcp from any to 10.0.0.2 port 22 keep state +pass in on lo1000001 proto tcp from any to 10.0.0.2 port 22 keep state +pass in on lo1000001 proto udp from any to 10.0.0.2 port 53 keep state +pass in on lo1000001 proto tcp from 10.0.0.1 to 10.0.0.4 keep state +pass out on lo1000001 proto tcp from any to 10.0.0.2 port 22 keep state +pass out proto tcp to 10.0.0.1 port 80 keep state +pass in proto udp to 10.0.0.1 port 53 keep state +pass in on lo1000001 proto tcp from 10.0.0.1 to 10.0.0.6 port 22 keep state +pass in on lo1000001 proto udp from 10.0.0.5 to 10.0.0.2 keep state diff --git a/sbin/pfctl/tests/files/pf0087.ok b/sbin/pfctl/tests/files/pf0087.ok new file mode 100644 index 000000000000..7aa69adefae0 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0087.ok @@ -0,0 +1,22 @@ +pass in on lo1000000 inet proto tcp from any to 10.0.0.2 port = ssh flags S/SA keep state +pass in on lo1000001 inet proto tcp from 10.0.0.1 port = ssh to 10.0.0.2 flags S/SA keep state +pass in on lo1000001 inet proto udp from 10.0.0.5 to 10.0.0.4 port = domain keep state +pass in on lo1000000 inet proto udp from any to 10.0.0.2 port = domain keep state +pass in inet proto tcp from any to 10.0.0.1 port = http flags S/SA keep state +pass out on lo1000001 inet proto udp from any to 10.0.0.2 port = domain keep state +pass in inet proto tcp from any to 10.0.0.3 port = http flags S/SA keep state +pass out inet proto tcp from any to 10.0.0.1 port = 81 flags S/SA keep state +pass in inet proto udp from any to 10.0.0.3 port = domain keep state +pass in on lo1000001 inet proto udp from 10.0.0.2 port = domain to 10.0.0.2 keep state +pass out inet proto udp from any to 10.0.0.1 port = domain keep state +pass out on lo1000000 inet proto udp from any to 10.0.0.2 port = domain keep state +pass out inet proto udp from any to 10.0.0.3 port = domain keep state +pass out on lo1000000 inet proto tcp from any to 10.0.0.2 port = ssh flags S/SA keep state +pass in on lo1000001 inet proto tcp from any to 10.0.0.2 port = ssh flags S/SA keep state +pass in on lo1000001 inet proto udp from any to 10.0.0.2 port = domain keep state +pass in on lo1000001 inet proto tcp from 10.0.0.1 to 10.0.0.4 flags S/SA keep state +pass out on lo1000001 inet proto tcp from any to 10.0.0.2 port = ssh flags S/SA keep state +pass out inet proto tcp from any to 10.0.0.1 port = http flags S/SA keep state +pass in inet proto udp from any to 10.0.0.1 port = domain keep state +pass in on lo1000001 inet proto tcp from 10.0.0.1 to 10.0.0.6 port = ssh flags S/SA keep state +pass in on lo1000001 inet proto udp from 10.0.0.5 to 10.0.0.2 keep state diff --git a/sbin/pfctl/tests/files/pf0088.in b/sbin/pfctl/tests/files/pf0088.in new file mode 100644 index 000000000000..a85aa84a30bb --- /dev/null +++ b/sbin/pfctl/tests/files/pf0088.in @@ -0,0 +1,32 @@ +# pfctl -o duplicate rules + +pass in on lo1000000 from any to 10.0.0.1 +pass in on lo1000000 inet from any to 10.0.0.1 + +pass +pass out +pass out +pass out quick + +pass on lo1000001 to 10.0.0.1 +pass on lo1000000 from any to 10.0.0.1 + +pass to 10.0.0.2 modulate state +pass to 10.0.0.2 keep state +block from 10.0.0.3 to 10.0.0.2 +pass to 10.0.0.2 modulate state +block from 10.0.0.3 to 10.0.0.2 +pass in to 10.0.0.2 synproxy state + + +pass out proto tcp from 10.0.0.4 to 10.0.0.5 keep state +pass out proto tcp from 10.0.0.4 to 10.0.0.5 port 80 keep state + +pass out +pass in + +pass in on lo1000001 from any to any +pass in on lo1000001 from any to any keep state +pass in on lo1000001 from any to any + +block diff --git a/sbin/pfctl/tests/files/pf0088.ok b/sbin/pfctl/tests/files/pf0088.ok new file mode 100644 index 000000000000..801056a4ab46 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0088.ok @@ -0,0 +1,22 @@ +pass in on lo1000000 inet from any to 10.0.0.1 flags S/SA keep state +pass in on lo1000000 inet from any to 10.0.0.1 flags S/SA keep state +pass all flags S/SA keep state +pass out all flags S/SA keep state +pass out all flags S/SA keep state +pass out quick all flags S/SA keep state +pass on lo1000001 inet from any to 10.0.0.1 flags S/SA keep state +pass on lo1000000 inet from any to 10.0.0.1 flags S/SA keep state +pass inet from any to 10.0.0.2 flags S/SA modulate state +pass inet from any to 10.0.0.2 flags S/SA keep state +block drop inet from 10.0.0.3 to 10.0.0.2 +pass inet from any to 10.0.0.2 flags S/SA modulate state +block drop inet from 10.0.0.3 to 10.0.0.2 +pass in inet from any to 10.0.0.2 flags S/SA synproxy state +pass out inet proto tcp from 10.0.0.4 to 10.0.0.5 flags S/SA keep state +pass out inet proto tcp from 10.0.0.4 to 10.0.0.5 port = http flags S/SA keep state +pass out all flags S/SA keep state +pass in all flags S/SA keep state +pass in on lo1000001 all flags S/SA keep state +pass in on lo1000001 all flags S/SA keep state +pass in on lo1000001 all flags S/SA keep state +block drop all diff --git a/sbin/pfctl/tests/files/pf0089.in b/sbin/pfctl/tests/files/pf0089.in new file mode 100644 index 000000000000..1beda48b43b2 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0089.in @@ -0,0 +1,25 @@ +# TCP connection tracking + +table <bad> persist + +block all +block quick from <bad> + +pass out proto tcp flags S/SA keep state +pass out proto { icmp, udp } keep state + +pass in on lo1000001 proto tcp to 10.0.0.1 port 22 flags S/SA \ + keep state (max-src-conn 10, max-src-conn-rate 3/99) + +pass in on lo1000001 proto tcp to 10.0.0.2 port 22 flags S/SA keep state \ + (max-src-conn 10) + +pass in on lo1000001 proto tcp to 10.0.0.3 port 22 flags S/SA keep state \ + (max-src-conn-rate 3/99) + +pass in on lo1000000 proto tcp to 10.0.0.1 port 80 flags S/SA modulate state \ + (max-src-conn 100, max-src-conn-rate 10/5, overload <bad> flush) + +pass in on lo1000000 proto tcp to 10.0.0.1 port 8080 flags S/SA synproxy state \ + (max-src-conn 1000, max-src-conn-rate 1000/5, overload <bad> \ + flush global) diff --git a/sbin/pfctl/tests/files/pf0089.ok b/sbin/pfctl/tests/files/pf0089.ok new file mode 100644 index 000000000000..c2403e775da1 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0089.ok @@ -0,0 +1,11 @@ +table <bad> persist +block drop all +block drop quick from <bad> to any +pass out proto tcp all flags S/SA keep state +pass out proto icmp all keep state +pass out proto udp all keep state +pass in on lo1000001 inet proto tcp from any to 10.0.0.1 port = ssh flags S/SA keep state (source-track rule, max-src-conn 10, max-src-conn-rate 3/99, src.track 99) +pass in on lo1000001 inet proto tcp from any to 10.0.0.2 port = ssh flags S/SA keep state (source-track rule, max-src-conn 10) +pass in on lo1000001 inet proto tcp from any to 10.0.0.3 port = ssh flags S/SA keep state (source-track rule, max-src-conn-rate 3/99, src.track 99) +pass in on lo1000000 inet proto tcp from any to 10.0.0.1 port = http flags S/SA modulate state (source-track rule, max-src-conn 100, max-src-conn-rate 10/5, overload <bad> flush, src.track 5) +pass in on lo1000000 inet proto tcp from any to 10.0.0.1 port = http-alt flags S/SA synproxy state (source-track rule, max-src-conn 1000, max-src-conn-rate 1000/5, overload <bad> flush global, src.track 5) diff --git a/sbin/pfctl/tests/files/pf0090.in b/sbin/pfctl/tests/files/pf0090.in new file mode 100644 index 000000000000..593ddc6a32ee --- /dev/null +++ b/sbin/pfctl/tests/files/pf0090.in @@ -0,0 +1,5 @@ +pass log (user) +pass log (all) +pass log (to pflog7) +block log (all, user, to pflog1) +block log (to pflog1, user) diff --git a/sbin/pfctl/tests/files/pf0090.ok b/sbin/pfctl/tests/files/pf0090.ok new file mode 100644 index 000000000000..4255dc356c43 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0090.ok @@ -0,0 +1,5 @@ +pass log (user) all flags S/SA keep state +pass log (all) all flags S/SA keep state +pass log (to pflog7) all flags S/SA keep state +block drop log (all, user, to pflog1) all +block drop log (user, to pflog1) all diff --git a/sbin/pfctl/tests/files/pf0091.in b/sbin/pfctl/tests/files/pf0091.in new file mode 100644 index 000000000000..b4fc631423e7 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0091.in @@ -0,0 +1,11 @@ +# basic anchor test +anchor on tun1000000 { + anchor foo out { + pass proto tcp to port 1234 + anchor proto tcp to port 2413 user root label "foo" { + block + pass from 127.0.0.1 + } + } + pass in proto tcp to port 1234 +} diff --git a/sbin/pfctl/tests/files/pf0091.ok b/sbin/pfctl/tests/files/pf0091.ok new file mode 100644 index 000000000000..9f69e272d7fd --- /dev/null +++ b/sbin/pfctl/tests/files/pf0091.ok @@ -0,0 +1,10 @@ +anchor on tun1000000 all { + anchor "foo" out all { + pass proto tcp from any to any port = 1234 flags S/SA keep state + anchor proto tcp from any to any port = 2413 user = 0 label "foo" { + block drop all + pass inet from 127.0.0.1 to any flags S/SA keep state + } + } + pass in proto tcp from any to any port = 1234 flags S/SA keep state +} diff --git a/sbin/pfctl/tests/files/pf0092.in b/sbin/pfctl/tests/files/pf0092.in new file mode 100644 index 000000000000..3af6ea6e38d3 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0092.in @@ -0,0 +1,30 @@ +anchor { # testing comments + anchor in { + # comment before rule + pass quick + } + # silly nesting + anchor out { + anchor in { + anchor out { + anchor in { + anchor out { + anchor in { + anchor out { + anchor in { + pass + } + } + } + } + } + } + } + } + pass in on tun1000000 + anchor foo on tun1000000 { + + pass + } +} # comment after closing brace + diff --git a/sbin/pfctl/tests/files/pf0092.ok b/sbin/pfctl/tests/files/pf0092.ok new file mode 100644 index 000000000000..43720c1afa2a --- /dev/null +++ b/sbin/pfctl/tests/files/pf0092.ok @@ -0,0 +1,26 @@ +anchor all { + anchor in all { + pass quick all flags S/SA keep state + } + anchor out all { + anchor in all { + anchor out all { + anchor in all { + anchor out all { + anchor in all { + anchor out all { + anchor in all { + pass all flags S/SA keep state + } + } + } + } + } + } + } + } + pass in on tun1000000 all flags S/SA keep state + anchor "foo" on tun1000000 all { + pass all flags S/SA keep state + } +} diff --git a/sbin/pfctl/tests/files/pf0094.in b/sbin/pfctl/tests/files/pf0094.in new file mode 100644 index 000000000000..b0e3d0feebf8 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0094.in @@ -0,0 +1,4 @@ +pass from 10.1.2.3 - 10.1.2.4 to 10.2.3.4 - 10.3.4.5 +pass from 0.0.0.0 - 255.255.255.255 +pass from 2001:6f8:1098::2 - 2001:6f8:1098::5 to 2001:6f8:1098::3 - 2001:6f8:1098::4 +pass from ::0 - ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff diff --git a/sbin/pfctl/tests/files/pf0094.ok b/sbin/pfctl/tests/files/pf0094.ok new file mode 100644 index 000000000000..5a792644defd --- /dev/null +++ b/sbin/pfctl/tests/files/pf0094.ok @@ -0,0 +1,4 @@ +pass inet from 10.1.2.3 - 10.1.2.4 to 10.2.3.4 - 10.3.4.5 flags S/SA keep state +pass inet from 0.0.0.0 - 255.255.255.255 to any flags S/SA keep state +pass inet6 from 2001:6f8:1098::2 - 2001:6f8:1098::5 to 2001:6f8:1098::3 - 2001:6f8:1098::4 flags S/SA keep state +pass inet6 from :: - ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff to any flags S/SA keep state diff --git a/sbin/pfctl/tests/files/pf0095.in b/sbin/pfctl/tests/files/pf0095.in new file mode 100644 index 000000000000..c43914bc0017 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0095.in @@ -0,0 +1,4 @@ + +include "./pf0095.include" + +block out proto tcp diff --git a/sbin/pfctl/tests/files/pf0095.include b/sbin/pfctl/tests/files/pf0095.include new file mode 100644 index 000000000000..f852a7169cfe --- /dev/null +++ b/sbin/pfctl/tests/files/pf0095.include @@ -0,0 +1,2 @@ + +block in proto udp diff --git a/sbin/pfctl/tests/files/pf0095.ok b/sbin/pfctl/tests/files/pf0095.ok new file mode 100644 index 000000000000..004e1787865d --- /dev/null +++ b/sbin/pfctl/tests/files/pf0095.ok @@ -0,0 +1,2 @@ +block drop in proto udp all +block drop out proto tcp all diff --git a/sbin/pfctl/tests/files/pf0096.in b/sbin/pfctl/tests/files/pf0096.in new file mode 100644 index 000000000000..4d1aed38e5bc --- /dev/null +++ b/sbin/pfctl/tests/files/pf0096.in @@ -0,0 +1,5 @@ +# varset allows concatenated strings as numbers +myports = 5555 6666 +# and also can be used within another macro +moreports = $myports 7777 +pass in proto tcp from any to any port { $moreports } diff --git a/sbin/pfctl/tests/files/pf0096.ok b/sbin/pfctl/tests/files/pf0096.ok new file mode 100644 index 000000000000..df7af0a3a157 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0096.ok @@ -0,0 +1,5 @@ +myports = "5555 6666" +moreports = "5555 6666 7777" +pass in proto tcp from any to any port = personal-agent flags S/SA keep state +pass in proto tcp from any to any port = 6666 flags S/SA keep state +pass in proto tcp from any to any port = 7777 flags S/SA keep state diff --git a/sbin/pfctl/tests/files/pf0097.in b/sbin/pfctl/tests/files/pf0097.in new file mode 100644 index 000000000000..b3fd4939b0a6 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0097.in @@ -0,0 +1,4 @@ +pass in on em0 inet proto tcp from any to any port 220:230 divert-to 127.0.0.1 port 22 +#pass out on em0 inet proto tcp from any to any port 220:230 divert-reply +pass on em0 inet proto tcp from any to any port 80 divert-to 127.0.0.1 port 8080 +pass in on em0 inet proto 103 divert-to 127.0.0.1 port 103 # FIXME diff --git a/sbin/pfctl/tests/files/pf0097.ok b/sbin/pfctl/tests/files/pf0097.ok new file mode 100644 index 000000000000..0a78066a9c25 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0097.ok @@ -0,0 +1,3 @@ +pass in on em0 inet proto tcp from any to any port 220:230 flags S/SA keep state divert-to 22 +pass on em0 inet proto tcp from any to any port = http flags S/SA keep state divert-to 8080 +pass in on em0 inet proto pim all keep state divert-to 103 diff --git a/sbin/pfctl/tests/files/pf0098.in b/sbin/pfctl/tests/files/pf0098.in new file mode 100644 index 000000000000..c26f0fcfe4d3 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0098.in @@ -0,0 +1,3 @@ +# Test rule order processing should pass (require-order no longer required) +pass in on lo1000000 all +match out on lo0 inet6 all nat-to lo0 diff --git a/sbin/pfctl/tests/files/pf0098.ok b/sbin/pfctl/tests/files/pf0098.ok new file mode 100644 index 000000000000..105bb46b4ae5 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0098.ok @@ -0,0 +1,2 @@ +pass in on lo1000000 all flags S/SA keep state +match out on lo0 inet6 all nat-to { ::1, fe80::1 } round-robin diff --git a/sbin/pfctl/tests/files/pf0100.in b/sbin/pfctl/tests/files/pf0100.in new file mode 100644 index 000000000000..287e1c9e4d7c --- /dev/null +++ b/sbin/pfctl/tests/files/pf0100.in @@ -0,0 +1,20 @@ +pass +anchor "a/b" +anchor "1/2/3" # test anchors with multiple path components +anchor "relative" { + pass in on lo0 label TEST1 +} +anchor "camield/*" # empty wildcard anchor + +anchor "relayd/*" + +anchor "foo" in on lo0 { + anchor "bar" in { # nested named inlined anchor + anchor "/1/2/3" # absolute multicomponent path + anchor "/relative" # absolute path + pass in on lo0 label FOO + } + anchor in { # nested unnamed inlined anchor + pass in on lo0 label BAR + } +} diff --git a/sbin/pfctl/tests/files/pf0100.ok b/sbin/pfctl/tests/files/pf0100.ok new file mode 100644 index 000000000000..9f4427379bc7 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0100.ok @@ -0,0 +1,18 @@ +pass all flags S/SA keep state +anchor "a/b" all +anchor "1/2/3" all +anchor "relative" all { + pass in on lo0 all flags S/SA keep state label "TEST1" +} +anchor "camield/*" all +anchor "relayd/*" all +anchor "foo" in on lo0 all { + anchor "bar" in all { + anchor "/1/2/3" all + anchor "/relative" all + pass in on lo0 all flags S/SA keep state label "FOO" + } + anchor in all { + pass in on lo0 all flags S/SA keep state label "BAR" + } +} diff --git a/sbin/pfctl/tests/files/pf0101.in b/sbin/pfctl/tests/files/pf0101.in new file mode 100644 index 000000000000..8bf9dc6cb8da --- /dev/null +++ b/sbin/pfctl/tests/files/pf0101.in @@ -0,0 +1,8 @@ +# test prio + +pass set prio 3 + +pass out on lo1000000 proto tcp from any to any port 22 set prio (5 2) + +pass proto udp from any to { 127.0.0.1 127.0.0.2 } port 53 set prio 4 + diff --git a/sbin/pfctl/tests/files/pf0101.ok b/sbin/pfctl/tests/files/pf0101.ok new file mode 100644 index 000000000000..a46f2699711a --- /dev/null +++ b/sbin/pfctl/tests/files/pf0101.ok @@ -0,0 +1,4 @@ +pass all flags S/SA set ( prio 3 ) keep state +pass out on lo1000000 proto tcp from any to any port = ssh flags S/SA set ( prio(5, 2) ) keep state +pass inet proto udp from any to 127.0.0.1 port = domain set ( prio 4 ) keep state +pass inet proto udp from any to 127.0.0.2 port = domain set ( prio 4 ) keep state diff --git a/sbin/pfctl/tests/files/pf0102.in b/sbin/pfctl/tests/files/pf0102.in new file mode 100644 index 000000000000..d0c3a1110482 --- /dev/null +++ b/sbin/pfctl/tests/files/pf0102.in @@ -0,0 +1,9 @@ +# test rule expansion with mixed af + +pass from {1.1.1.1 2002::} to (self) + +pass from {2002:: 1.1.1.1} to (self) + +pass from {1.1.1.1 2002::} to (self)/40 + +pass from {2002:: 1.1.1.1} to (self)/40 diff --git a/sbin/pfctl/tests/files/pf0102.ok b/sbin/pfctl/tests/files/pf0102.ok new file mode 100644 index 000000000000..1c76ec2725ba --- /dev/null +++ b/sbin/pfctl/tests/files/pf0102.ok @@ -0,0 +1,8 @@ +pass inet from 1.1.1.1 to (self) flags S/SA keep state +pass inet6 from 2002:: to (self) flags S/SA keep state +pass inet6 from 2002:: to (self) flags S/SA keep state +pass inet from 1.1.1.1 to (self) flags S/SA keep state +pass inet from 1.1.1.1 to (self) flags S/SA keep state +pass inet6 from 2002:: to (self)/40 flags S/SA keep state +pass inet6 from 2002:: to (self)/40 flags S/SA keep state +pass inet from 1.1.1.1 to (self) flags S/SA keep state diff --git a/sbin/pfctl/tests/files/pf0104.in b/sbin/pfctl/tests/files/pf0104.in new file mode 100644 index 000000000000..91bd43e3a4bb --- /dev/null +++ b/sbin/pfctl/tests/files/pf0104.in @@ -0,0 +1,10 @@ +# This test assumes that localhost points to 127.0.0.1 first +pass in proto tcp to port 25 divert-to localhost port 8025 +# Test IPv4 addresses +pass in proto tcp to port 25 divert-to 127.0.0.1 port 8025 +pass in inet proto tcp to port 25 divert-to 127.0.0.1 port 8025 +pass in inet proto tcp to port 25 divert-to localhost port 8025 +# Test IPv6 addresses +pass in proto tcp to port 25 divert-to ::1 port 8025 +pass in inet6 proto tcp to port 25 divert-to ::1 port 8025 +pass in inet6 proto tcp to port 25 divert-to localhost port 8025 diff --git a/sbin/pfctl/tests/files/pf0104.ok b/sbin/pfctl/tests/files/pf0104.ok new file mode 100644 index 000000000000..a4260f9ac98e --- /dev/null +++ b/sbin/pfctl/tests/files/pf0104.ok @@ -0,0 +1,7 @@ +pass in proto tcp from any to any port = smtp flags S/SA keep state divert-to 8025 +pass in proto tcp from any to any port = smtp flags S/SA keep state divert-to 8025 +pass in inet proto tcp from any to any port = smtp flags S/SA keep state divert-to 8025 +pass in inet proto tcp from any to any port = smtp flags S/SA keep state divert-to 8025 +pass in proto tcp from any to any port = smtp flags S/SA keep state divert-to 8025 +pass in inet6 proto tcp from any to any port = smtp flags S/SA keep state divert-to 8025 +pass in inet6 proto tcp from any to any port = smtp flags S/SA keep state divert-to 8025 diff --git a/sbin/pfctl/tests/files/pf1001.in b/sbin/pfctl/tests/files/pf1001.in new file mode 100644 index 000000000000..9007d63aeebd --- /dev/null +++ b/sbin/pfctl/tests/files/pf1001.in @@ -0,0 +1,2 @@ +binat on em0 inet6 from fc00::/64 to any -> fc00:0:0:1::/64 +binat on em0 inet6 from any to fc00:0:0:1::/64 -> fc00::/64 diff --git a/sbin/pfctl/tests/files/pf1001.ok b/sbin/pfctl/tests/files/pf1001.ok new file mode 100644 index 000000000000..9007d63aeebd --- /dev/null +++ b/sbin/pfctl/tests/files/pf1001.ok @@ -0,0 +1,2 @@ +binat on em0 inet6 from fc00::/64 to any -> fc00:0:0:1::/64 +binat on em0 inet6 from any to fc00:0:0:1::/64 -> fc00::/64 diff --git a/sbin/pfctl/tests/files/pf1002.in b/sbin/pfctl/tests/files/pf1002.in new file mode 100644 index 000000000000..3fdde81be7de --- /dev/null +++ b/sbin/pfctl/tests/files/pf1002.in @@ -0,0 +1,6 @@ +set timeout interval 10 +set timeout sctp.first 11 +set timeout sctp.opening 12 +set timeout sctp.established 13 +set timeout sctp.closing 14 +set timeout sctp.closed 15 diff --git a/sbin/pfctl/tests/files/pf1002.ok b/sbin/pfctl/tests/files/pf1002.ok new file mode 100644 index 000000000000..3fdde81be7de --- /dev/null +++ b/sbin/pfctl/tests/files/pf1002.ok @@ -0,0 +1,6 @@ +set timeout interval 10 +set timeout sctp.first 11 +set timeout sctp.opening 12 +set timeout sctp.established 13 +set timeout sctp.closing 14 +set timeout sctp.closed 15 diff --git a/sbin/pfctl/tests/files/pf1003.in b/sbin/pfctl/tests/files/pf1003.in new file mode 100644 index 000000000000..298b3df81b52 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1003.in @@ -0,0 +1,3 @@ +altq on em0 cbq(default) bandwidth 100Kb queue qmain +queue qmain priority 4 +pass on em0 queue qmain diff --git a/sbin/pfctl/tests/files/pf1003.ok b/sbin/pfctl/tests/files/pf1003.ok new file mode 100644 index 000000000000..afc9817e3b35 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1003.ok @@ -0,0 +1,3 @@ +altq on em0 cbq( default ) bandwidth 100Kb tbrsize 1500 queue { qmain } +queue qmain priority 4 +pass on em0 all flags S/SA keep state queue qmain diff --git a/sbin/pfctl/tests/files/pf1004.in b/sbin/pfctl/tests/files/pf1004.in new file mode 100644 index 000000000000..e8f26bef9e1a --- /dev/null +++ b/sbin/pfctl/tests/files/pf1004.in @@ -0,0 +1,6 @@ +altq on em0 cbq(default codel) bandwidth 20Mb queue qmain +queue qmain { q1 q2 } +queue q1 priority 1 bandwidth 60% +queue q2 priority 2 bandwidth 40% +pass on em0 queue q1 +block on em0 queue q2 diff --git a/sbin/pfctl/tests/files/pf1004.ok b/sbin/pfctl/tests/files/pf1004.ok new file mode 100644 index 000000000000..b2e033c6e87d --- /dev/null +++ b/sbin/pfctl/tests/files/pf1004.ok @@ -0,0 +1,6 @@ +altq on em0 cbq( codel default ) bandwidth 20Mb tbrsize 12000 queue { qmain } +queue qmain { q1 q2 } +queue q1 bandwidth 60% +queue q2 bandwidth 40% priority 2 +pass on em0 all flags S/SA keep state queue q1 +block drop on em0 all queue q2 diff --git a/sbin/pfctl/tests/files/pf1005.in b/sbin/pfctl/tests/files/pf1005.in new file mode 100644 index 000000000000..72e5c8f2a87d --- /dev/null +++ b/sbin/pfctl/tests/files/pf1005.in @@ -0,0 +1,3 @@ +rdr on em0 proto tcp from any to any -> 1.1.1.1 port 2121 +pass out log quick on lo0 route-to (lo0 localhost) inet from any to any +pass in log quick on lo0 route-to (lo0 localhost) inet6 from any to any diff --git a/sbin/pfctl/tests/files/pf1005.ok b/sbin/pfctl/tests/files/pf1005.ok new file mode 100644 index 000000000000..a1678f61d4ad --- /dev/null +++ b/sbin/pfctl/tests/files/pf1005.ok @@ -0,0 +1,3 @@ +rdr on em0 inet proto tcp all -> 1.1.1.1 port 2121 +pass out log quick on lo0 route-to (lo0 127.0.0.1) inet all flags S/SA keep state +pass in log quick on lo0 route-to (lo0 ::1) inet6 all flags S/SA keep state diff --git a/sbin/pfctl/tests/files/pf1006.in b/sbin/pfctl/tests/files/pf1006.in new file mode 100644 index 000000000000..b50c16994cfc --- /dev/null +++ b/sbin/pfctl/tests/files/pf1006.in @@ -0,0 +1,2 @@ +altq on igb0 fairq bandwidth 1Gb queue { qLink } +queue qLink fairq(default) diff --git a/sbin/pfctl/tests/files/pf1006.ok b/sbin/pfctl/tests/files/pf1006.ok new file mode 100644 index 000000000000..be44b765c2e9 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1006.ok @@ -0,0 +1,2 @@ +altq on igb0 fairq bandwidth 1Gb tbrsize 36000 queue { qLink } +queue qLink fairq( default ) diff --git a/sbin/pfctl/tests/files/pf1007.in b/sbin/pfctl/tests/files/pf1007.in new file mode 100644 index 000000000000..e08b38d7241a --- /dev/null +++ b/sbin/pfctl/tests/files/pf1007.in @@ -0,0 +1 @@ +ether block out on igb0 to ! 00:01:02:03:04:05 diff --git a/sbin/pfctl/tests/files/pf1007.ok b/sbin/pfctl/tests/files/pf1007.ok new file mode 100644 index 000000000000..742b5308ec90 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1007.ok @@ -0,0 +1 @@ +ether block out on igb0 to ! 00:01:02:03:04:05 l3 all diff --git a/sbin/pfctl/tests/files/pf1008.in b/sbin/pfctl/tests/files/pf1008.in new file mode 100644 index 000000000000..a9bd472a5070 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1008.in @@ -0,0 +1 @@ +ether block out on igb0 to 00:01:02:03:04:05/24 diff --git a/sbin/pfctl/tests/files/pf1008.ok b/sbin/pfctl/tests/files/pf1008.ok new file mode 100644 index 000000000000..646ef77c78dd --- /dev/null +++ b/sbin/pfctl/tests/files/pf1008.ok @@ -0,0 +1 @@ +ether block out on igb0 to 00:01:02:03:04:05/24 l3 all diff --git a/sbin/pfctl/tests/files/pf1009.in b/sbin/pfctl/tests/files/pf1009.in new file mode 100644 index 000000000000..833c9099837c --- /dev/null +++ b/sbin/pfctl/tests/files/pf1009.in @@ -0,0 +1 @@ +ether block out on igb0 to 00:01:02:03:04:05&ff:ff:ff:00:00:ff diff --git a/sbin/pfctl/tests/files/pf1009.ok b/sbin/pfctl/tests/files/pf1009.ok new file mode 100644 index 000000000000..3023f1337dd3 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1009.ok @@ -0,0 +1 @@ +ether block out on igb0 to 00:01:02:03:04:05&ff:ff:ff:00:00:ff l3 all diff --git a/sbin/pfctl/tests/files/pf1010.in b/sbin/pfctl/tests/files/pf1010.in new file mode 100644 index 000000000000..2baf4dc360af --- /dev/null +++ b/sbin/pfctl/tests/files/pf1010.in @@ -0,0 +1,2 @@ +pass inet proto icmp icmp-type {unreach} +pass in route-to (if0 127.0.0.1/8) sticky-address inet diff --git a/sbin/pfctl/tests/files/pf1010.ok b/sbin/pfctl/tests/files/pf1010.ok new file mode 100644 index 000000000000..b960dbfc50b8 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1010.ok @@ -0,0 +1,2 @@ +pass inet proto icmp all icmp-type unreach keep state +pass in route-to (if0 127.0.0.0/8) sticky-address inet all flags S/SA keep state diff --git a/sbin/pfctl/tests/files/pf1011.in b/sbin/pfctl/tests/files/pf1011.in new file mode 100644 index 000000000000..84f0e7204e40 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1011.in @@ -0,0 +1 @@ +scrub fragment no reassemble diff --git a/sbin/pfctl/tests/files/pf1011.ok b/sbin/pfctl/tests/files/pf1011.ok new file mode 100644 index 000000000000..48572b371d8d --- /dev/null +++ b/sbin/pfctl/tests/files/pf1011.ok @@ -0,0 +1 @@ +scrub all fragment no reassemble diff --git a/sbin/pfctl/tests/files/pf1012.in b/sbin/pfctl/tests/files/pf1012.in new file mode 100644 index 000000000000..9083d1bf5396 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1012.in @@ -0,0 +1 @@ +scrub diff --git a/sbin/pfctl/tests/files/pf1012.ok b/sbin/pfctl/tests/files/pf1012.ok new file mode 100644 index 000000000000..b7f1f454fb6a --- /dev/null +++ b/sbin/pfctl/tests/files/pf1012.ok @@ -0,0 +1 @@ +scrub all fragment reassemble diff --git a/sbin/pfctl/tests/files/pf1013.in b/sbin/pfctl/tests/files/pf1013.in new file mode 100644 index 000000000000..053804e1a35a --- /dev/null +++ b/sbin/pfctl/tests/files/pf1013.in @@ -0,0 +1 @@ +ether block out on igb0 ridentifier 12345678 diff --git a/sbin/pfctl/tests/files/pf1013.ok b/sbin/pfctl/tests/files/pf1013.ok new file mode 100644 index 000000000000..7395f3fd6311 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1013.ok @@ -0,0 +1 @@ +ether block out on igb0 l3 all ridentifier 12345678 diff --git a/sbin/pfctl/tests/files/pf1014.in b/sbin/pfctl/tests/files/pf1014.in new file mode 100644 index 000000000000..8739034f1bda --- /dev/null +++ b/sbin/pfctl/tests/files/pf1014.in @@ -0,0 +1 @@ +ether block out on igb0 label "test" diff --git a/sbin/pfctl/tests/files/pf1014.ok b/sbin/pfctl/tests/files/pf1014.ok new file mode 100644 index 000000000000..d0086cb25e54 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1014.ok @@ -0,0 +1 @@ +ether block out on igb0 l3 all label "test" diff --git a/sbin/pfctl/tests/files/pf1015.in b/sbin/pfctl/tests/files/pf1015.in new file mode 100644 index 000000000000..11c7a211ae8a --- /dev/null +++ b/sbin/pfctl/tests/files/pf1015.in @@ -0,0 +1 @@ +ether block out on igb0 label "test" label "another label" diff --git a/sbin/pfctl/tests/files/pf1015.ok b/sbin/pfctl/tests/files/pf1015.ok new file mode 100644 index 000000000000..d3ea76f1875b --- /dev/null +++ b/sbin/pfctl/tests/files/pf1015.ok @@ -0,0 +1 @@ +ether block out on igb0 l3 all label "test" label "another label" diff --git a/sbin/pfctl/tests/files/pf1016.in b/sbin/pfctl/tests/files/pf1016.in new file mode 100644 index 000000000000..a7b1f6bc0ca9 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1016.in @@ -0,0 +1 @@ +ether block out on igb0 label "test" ridentifier 12345678 diff --git a/sbin/pfctl/tests/files/pf1016.ok b/sbin/pfctl/tests/files/pf1016.ok new file mode 100644 index 000000000000..f1d59c988730 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1016.ok @@ -0,0 +1 @@ +ether block out on igb0 l3 all label "test" ridentifier 12345678 diff --git a/sbin/pfctl/tests/files/pf1017.in b/sbin/pfctl/tests/files/pf1017.in new file mode 100644 index 000000000000..ad523337bdc5 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1017.in @@ -0,0 +1 @@ +ether block out on igb0 label "test" label "another test" ridentifier 12345678 diff --git a/sbin/pfctl/tests/files/pf1017.ok b/sbin/pfctl/tests/files/pf1017.ok new file mode 100644 index 000000000000..0efdd55e27a0 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1017.ok @@ -0,0 +1 @@ +ether block out on igb0 l3 all label "test" label "another test" ridentifier 12345678 diff --git a/sbin/pfctl/tests/files/pf1018.in b/sbin/pfctl/tests/files/pf1018.in new file mode 100644 index 000000000000..90f0a3a0bab7 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1018.in @@ -0,0 +1 @@ +pass from { 192.0.2.1 2001:db8::1 } to (pppoe0) diff --git a/sbin/pfctl/tests/files/pf1018.ok b/sbin/pfctl/tests/files/pf1018.ok new file mode 100644 index 000000000000..04950f0035b8 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1018.ok @@ -0,0 +1,2 @@ +pass inet from 192.0.2.1 to (pppoe0) flags S/SA keep state +pass inet6 from 2001:db8::1 to (pppoe0) flags S/SA keep state diff --git a/sbin/pfctl/tests/files/pf1019.in b/sbin/pfctl/tests/files/pf1019.in new file mode 100644 index 000000000000..04a770768714 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1019.in @@ -0,0 +1 @@ +pass in keep state (pflow) diff --git a/sbin/pfctl/tests/files/pf1019.ok b/sbin/pfctl/tests/files/pf1019.ok new file mode 100644 index 000000000000..e865d57da16c --- /dev/null +++ b/sbin/pfctl/tests/files/pf1019.ok @@ -0,0 +1 @@ +pass in all flags S/SA keep state (pflow) diff --git a/sbin/pfctl/tests/files/pf1020.in b/sbin/pfctl/tests/files/pf1020.in new file mode 100644 index 000000000000..7f98df69bd04 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1020.in @@ -0,0 +1,3 @@ +table <tabl1> file "./pf1020.include" + +block from <tabl1> diff --git a/sbin/pfctl/tests/files/pf1020.include b/sbin/pfctl/tests/files/pf1020.include new file mode 100644 index 000000000000..3fca07f64bfa --- /dev/null +++ b/sbin/pfctl/tests/files/pf1020.include @@ -0,0 +1,4 @@ +; comment1 +# comment2 +1.0.0.1/32 ; comment1 +2.0.0.2/32 # comment2 diff --git a/sbin/pfctl/tests/files/pf1020.ok b/sbin/pfctl/tests/files/pf1020.ok new file mode 100644 index 000000000000..16073b3d6987 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1020.ok @@ -0,0 +1,2 @@ +table <tabl1> file "./pf1020.include" +block drop from <tabl1> to any diff --git a/sbin/pfctl/tests/files/pf1021.in b/sbin/pfctl/tests/files/pf1021.in new file mode 100644 index 000000000000..841b024157c6 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1021.in @@ -0,0 +1 @@ +nat on vtnet1 inet from ! (vtnet1) to any -> (vtnet1) endpoint-independent diff --git a/sbin/pfctl/tests/files/pf1021.ok b/sbin/pfctl/tests/files/pf1021.ok new file mode 100644 index 000000000000..3b5b84e2e11b --- /dev/null +++ b/sbin/pfctl/tests/files/pf1021.ok @@ -0,0 +1 @@ +nat on vtnet1 inet from ! (vtnet1) to any -> (vtnet1) round-robin endpoint-independent diff --git a/sbin/pfctl/tests/files/pf1022.in b/sbin/pfctl/tests/files/pf1022.in new file mode 100644 index 000000000000..640eb1334100 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1022.in @@ -0,0 +1 @@ +pass out on em0 from 192.0.2.1 to 198.51.100.1 received-on fxp0 diff --git a/sbin/pfctl/tests/files/pf1022.ok b/sbin/pfctl/tests/files/pf1022.ok new file mode 100644 index 000000000000..2f7b4a5bd616 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1022.ok @@ -0,0 +1 @@ +pass out on em0 inet from 192.0.2.1 to 198.51.100.1 received-on fxp0 flags S/SA keep state diff --git a/sbin/pfctl/tests/files/pf1023.in b/sbin/pfctl/tests/files/pf1023.in new file mode 100644 index 000000000000..4855ae0f339e --- /dev/null +++ b/sbin/pfctl/tests/files/pf1023.in @@ -0,0 +1,3 @@ +match log(matches) inet proto tcp +match log(matches) inet from 192.0.2.0/24 +pass diff --git a/sbin/pfctl/tests/files/pf1023.ok b/sbin/pfctl/tests/files/pf1023.ok new file mode 100644 index 000000000000..63fa40113ecf --- /dev/null +++ b/sbin/pfctl/tests/files/pf1023.ok @@ -0,0 +1,3 @@ +match log (matches) inet proto tcp all +match log (matches) inet from 192.0.2.0/24 to any +pass all flags S/SA keep state diff --git a/sbin/pfctl/tests/files/pf1024.in b/sbin/pfctl/tests/files/pf1024.in new file mode 100644 index 000000000000..be518bb3bd53 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1024.in @@ -0,0 +1 @@ +pass in inet af-to inet6 from 2001:db8::1 diff --git a/sbin/pfctl/tests/files/pf1024.ok b/sbin/pfctl/tests/files/pf1024.ok new file mode 100644 index 000000000000..2d4ddb9d0ce7 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1024.ok @@ -0,0 +1 @@ +pass in inet all flags S/SA keep state af-to inet6 from 2001:db8::1 diff --git a/sbin/pfctl/tests/files/pf1025.in b/sbin/pfctl/tests/files/pf1025.in new file mode 100644 index 000000000000..d4ad821a6899 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1025.in @@ -0,0 +1 @@ +pass in from 10.0.0.0/8 af-to inet6 from 2001:db8::1 diff --git a/sbin/pfctl/tests/files/pf1025.ok b/sbin/pfctl/tests/files/pf1025.ok new file mode 100644 index 000000000000..8f48c987c6a0 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1025.ok @@ -0,0 +1 @@ +pass in inet from 10.0.0.0/8 to any flags S/SA keep state af-to inet6 from 2001:db8::1 diff --git a/sbin/pfctl/tests/files/pf1026.in b/sbin/pfctl/tests/files/pf1026.in new file mode 100644 index 000000000000..3691d0947b39 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1026.in @@ -0,0 +1 @@ +pass in on epair2b route-to (epair0a 192.0.2.2) inet6 from any to 64:ff9b::/96 af-to inet from (epair0a) diff --git a/sbin/pfctl/tests/files/pf1026.ok b/sbin/pfctl/tests/files/pf1026.ok new file mode 100644 index 000000000000..323036f2b800 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1026.ok @@ -0,0 +1 @@ +pass in on epair2b route-to (epair0a 192.0.2.2) inet6 from any to 64:ff9b::/96 flags S/SA keep state af-to inet from (epair0a) round-robin diff --git a/sbin/pfctl/tests/files/pf1027.in b/sbin/pfctl/tests/files/pf1027.in new file mode 100644 index 000000000000..3c5c24025e0a --- /dev/null +++ b/sbin/pfctl/tests/files/pf1027.in @@ -0,0 +1 @@ +pass in on epair2b reply-to (epair0a 2001:db8::1) inet6 from any to 64:ff9b::/96 af-to inet from (epair0a) diff --git a/sbin/pfctl/tests/files/pf1027.ok b/sbin/pfctl/tests/files/pf1027.ok new file mode 100644 index 000000000000..b50f1e216837 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1027.ok @@ -0,0 +1 @@ +pass in on epair2b reply-to (epair0a 2001:db8::1) inet6 from any to 64:ff9b::/96 flags S/SA keep state af-to inet from (epair0a) round-robin diff --git a/sbin/pfctl/tests/files/pf1028.in b/sbin/pfctl/tests/files/pf1028.in new file mode 100644 index 000000000000..2386fcb52249 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1028.in @@ -0,0 +1 @@ +rdr on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> 192.0.2.3 diff --git a/sbin/pfctl/tests/files/pf1028.ok b/sbin/pfctl/tests/files/pf1028.ok new file mode 100644 index 000000000000..07be890f4e05 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1028.ok @@ -0,0 +1 @@ +rdr on vtnet0 inet proto tcp from 192.0.2.1 to 192.0.2.2 -> 192.0.2.3 diff --git a/sbin/pfctl/tests/files/pf1029.in b/sbin/pfctl/tests/files/pf1029.in new file mode 100644 index 000000000000..73815839aadd --- /dev/null +++ b/sbin/pfctl/tests/files/pf1029.in @@ -0,0 +1 @@ +rdr on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> 192.0.2.3 port 1002 diff --git a/sbin/pfctl/tests/files/pf1029.ok b/sbin/pfctl/tests/files/pf1029.ok new file mode 100644 index 000000000000..6e9083bf856a --- /dev/null +++ b/sbin/pfctl/tests/files/pf1029.ok @@ -0,0 +1 @@ +rdr on vtnet0 inet proto tcp from 192.0.2.1 to 192.0.2.2 -> 192.0.2.3 port 1002 diff --git a/sbin/pfctl/tests/files/pf1030.in b/sbin/pfctl/tests/files/pf1030.in new file mode 100644 index 000000000000..b6f891998a71 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1030.in @@ -0,0 +1 @@ +rdr on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> 192.0.2.3 port 50001:65535 diff --git a/sbin/pfctl/tests/files/pf1030.ok b/sbin/pfctl/tests/files/pf1030.ok new file mode 100644 index 000000000000..4f6b2eba2f39 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1030.ok @@ -0,0 +1 @@ +rdr on vtnet0 inet proto tcp from 192.0.2.1 to 192.0.2.2 -> 192.0.2.3 port 50001:65535 diff --git a/sbin/pfctl/tests/files/pf1031.in b/sbin/pfctl/tests/files/pf1031.in new file mode 100644 index 000000000000..7cad4ae64000 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1031.in @@ -0,0 +1 @@ +rdr on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 port 1004:2004 -> 192.0.2.3 port 1004 diff --git a/sbin/pfctl/tests/files/pf1031.ok b/sbin/pfctl/tests/files/pf1031.ok new file mode 100644 index 000000000000..8dd7fe027716 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1031.ok @@ -0,0 +1 @@ +rdr on vtnet0 inet proto tcp from 192.0.2.1 to 192.0.2.2 port 1004:2004 -> 192.0.2.3 port 1004 diff --git a/sbin/pfctl/tests/files/pf1032.in b/sbin/pfctl/tests/files/pf1032.in new file mode 100644 index 000000000000..a2eec78da045 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1032.in @@ -0,0 +1 @@ +rdr on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 port 1005:2005 -> 192.0.2.3 port 3004:* diff --git a/sbin/pfctl/tests/files/pf1032.ok b/sbin/pfctl/tests/files/pf1032.ok new file mode 100644 index 000000000000..3b3f124efc33 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1032.ok @@ -0,0 +1 @@ +rdr on vtnet0 inet proto tcp from 192.0.2.1 to 192.0.2.2 port 1005:2005 -> 192.0.2.3 port 3004:4004 diff --git a/sbin/pfctl/tests/files/pf1033.fail b/sbin/pfctl/tests/files/pf1033.fail new file mode 100644 index 000000000000..d9fbfe4296e3 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1033.fail @@ -0,0 +1 @@ +the 'static-port' option is only valid with nat rules diff --git a/sbin/pfctl/tests/files/pf1033.in b/sbin/pfctl/tests/files/pf1033.in new file mode 100644 index 000000000000..76f33e7e8f0e --- /dev/null +++ b/sbin/pfctl/tests/files/pf1033.in @@ -0,0 +1 @@ +rdr on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> 192.0.2.3 static-port diff --git a/sbin/pfctl/tests/files/pf1034.fail b/sbin/pfctl/tests/files/pf1034.fail new file mode 100644 index 000000000000..e407996a8fa3 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1034.fail @@ -0,0 +1 @@ +the 'map-e-portset' option is only valid with nat rules diff --git a/sbin/pfctl/tests/files/pf1034.in b/sbin/pfctl/tests/files/pf1034.in new file mode 100644 index 000000000000..be847a8af241 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1034.in @@ -0,0 +1 @@ +rdr on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> 192.0.2.3 map-e-portset 6/8/0x34 diff --git a/sbin/pfctl/tests/files/pf1035.in b/sbin/pfctl/tests/files/pf1035.in new file mode 100644 index 000000000000..9382ffedc8c9 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1035.in @@ -0,0 +1 @@ +nat on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> 192.0.2.3 diff --git a/sbin/pfctl/tests/files/pf1035.ok b/sbin/pfctl/tests/files/pf1035.ok new file mode 100644 index 000000000000..be573ef460f5 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1035.ok @@ -0,0 +1 @@ +nat on vtnet0 inet proto tcp from 192.0.2.1 to 192.0.2.2 -> 192.0.2.3 diff --git a/sbin/pfctl/tests/files/pf1036.in b/sbin/pfctl/tests/files/pf1036.in new file mode 100644 index 000000000000..81718c908303 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1036.in @@ -0,0 +1 @@ +nat on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> 192.0.2.3 port 50001:65535 diff --git a/sbin/pfctl/tests/files/pf1036.ok b/sbin/pfctl/tests/files/pf1036.ok new file mode 100644 index 000000000000..be573ef460f5 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1036.ok @@ -0,0 +1 @@ +nat on vtnet0 inet proto tcp from 192.0.2.1 to 192.0.2.2 -> 192.0.2.3 diff --git a/sbin/pfctl/tests/files/pf1037.in b/sbin/pfctl/tests/files/pf1037.in new file mode 100644 index 000000000000..a30f6c0e7bbe --- /dev/null +++ b/sbin/pfctl/tests/files/pf1037.in @@ -0,0 +1 @@ +nat on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> 192.0.2.3 port 1003 diff --git a/sbin/pfctl/tests/files/pf1037.ok b/sbin/pfctl/tests/files/pf1037.ok new file mode 100644 index 000000000000..020e2de28dec --- /dev/null +++ b/sbin/pfctl/tests/files/pf1037.ok @@ -0,0 +1 @@ +nat on vtnet0 inet proto tcp from 192.0.2.1 to 192.0.2.2 -> 192.0.2.3 port 1003 diff --git a/sbin/pfctl/tests/files/pf1038.in b/sbin/pfctl/tests/files/pf1038.in new file mode 100644 index 000000000000..532060e56494 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1038.in @@ -0,0 +1 @@ +nat on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> 192.0.2.3 port 1004:2004 diff --git a/sbin/pfctl/tests/files/pf1038.ok b/sbin/pfctl/tests/files/pf1038.ok new file mode 100644 index 000000000000..a4021db7b1b2 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1038.ok @@ -0,0 +1 @@ +nat on vtnet0 inet proto tcp from 192.0.2.1 to 192.0.2.2 -> 192.0.2.3 port 1004:2004 diff --git a/sbin/pfctl/tests/files/pf1039.in b/sbin/pfctl/tests/files/pf1039.in new file mode 100644 index 000000000000..dba14b0625de --- /dev/null +++ b/sbin/pfctl/tests/files/pf1039.in @@ -0,0 +1 @@ +nat on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> 192.0.2.3 static-port diff --git a/sbin/pfctl/tests/files/pf1039.ok b/sbin/pfctl/tests/files/pf1039.ok new file mode 100644 index 000000000000..80cfbe742865 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1039.ok @@ -0,0 +1 @@ +nat on vtnet0 inet proto tcp from 192.0.2.1 to 192.0.2.2 -> 192.0.2.3 static-port diff --git a/sbin/pfctl/tests/files/pf1040.fail b/sbin/pfctl/tests/files/pf1040.fail new file mode 100644 index 000000000000..5b9afc22b441 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1040.fail @@ -0,0 +1 @@ +the 'static-port' option can't be used when specifying a port range diff --git a/sbin/pfctl/tests/files/pf1040.in b/sbin/pfctl/tests/files/pf1040.in new file mode 100644 index 000000000000..38d7292a560a --- /dev/null +++ b/sbin/pfctl/tests/files/pf1040.in @@ -0,0 +1 @@ +nat on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> 192.0.2.3 port 1006 static-port diff --git a/sbin/pfctl/tests/files/pf1040.ok b/sbin/pfctl/tests/files/pf1040.ok new file mode 100644 index 000000000000..ffe2e023f77c --- /dev/null +++ b/sbin/pfctl/tests/files/pf1040.ok @@ -0,0 +1 @@ +nat on vtnet0 inet proto tcp from 192.0.2.1 to 192.0.2.2 -> 192.0.2.3 map-e-portset 6/8/52 diff --git a/sbin/pfctl/tests/files/pf1041.in b/sbin/pfctl/tests/files/pf1041.in new file mode 100644 index 000000000000..4c384ac70e05 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1041.in @@ -0,0 +1 @@ +nat on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> 192.0.2.3 map-e-portset 6/8/0x34 diff --git a/sbin/pfctl/tests/files/pf1041.ok b/sbin/pfctl/tests/files/pf1041.ok new file mode 100644 index 000000000000..ffe2e023f77c --- /dev/null +++ b/sbin/pfctl/tests/files/pf1041.ok @@ -0,0 +1 @@ +nat on vtnet0 inet proto tcp from 192.0.2.1 to 192.0.2.2 -> 192.0.2.3 map-e-portset 6/8/52 diff --git a/sbin/pfctl/tests/files/pf1042.fail b/sbin/pfctl/tests/files/pf1042.fail new file mode 100644 index 000000000000..56e174a5ece5 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1042.fail @@ -0,0 +1 @@ +the 'map-e-portset' option can't be used 'static-port' diff --git a/sbin/pfctl/tests/files/pf1042.in b/sbin/pfctl/tests/files/pf1042.in new file mode 100644 index 000000000000..906f637b6a0a --- /dev/null +++ b/sbin/pfctl/tests/files/pf1042.in @@ -0,0 +1 @@ +nat on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> 192.0.2.3 static-port map-e-portset 6/8/0x34 diff --git a/sbin/pfctl/tests/files/pf1043.fail b/sbin/pfctl/tests/files/pf1043.fail new file mode 100644 index 000000000000..cdfab00916a2 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1043.fail @@ -0,0 +1 @@ +the 'map-e-portset' option can't be used when specifying a port range diff --git a/sbin/pfctl/tests/files/pf1043.in b/sbin/pfctl/tests/files/pf1043.in new file mode 100644 index 000000000000..15428a9e54bc --- /dev/null +++ b/sbin/pfctl/tests/files/pf1043.in @@ -0,0 +1 @@ +nat on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> 192.0.2.3 port 1007 map-e-portset 6/8/0x34 diff --git a/sbin/pfctl/tests/files/pf1044.in b/sbin/pfctl/tests/files/pf1044.in new file mode 100644 index 000000000000..6a927b66b83f --- /dev/null +++ b/sbin/pfctl/tests/files/pf1044.in @@ -0,0 +1 @@ +nat on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> <targets> sticky-address diff --git a/sbin/pfctl/tests/files/pf1044.ok b/sbin/pfctl/tests/files/pf1044.ok new file mode 100644 index 000000000000..a68b1daaa73a --- /dev/null +++ b/sbin/pfctl/tests/files/pf1044.ok @@ -0,0 +1 @@ +nat on vtnet0 inet proto tcp from 192.0.2.1 to 192.0.2.2 -> <targets> round-robin sticky-address diff --git a/sbin/pfctl/tests/files/pf1045.in b/sbin/pfctl/tests/files/pf1045.in new file mode 100644 index 000000000000..38f708ce19b8 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1045.in @@ -0,0 +1 @@ +nat on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> 203.0.113.0/24 bitmask diff --git a/sbin/pfctl/tests/files/pf1045.ok b/sbin/pfctl/tests/files/pf1045.ok new file mode 100644 index 000000000000..5388db7e58a4 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1045.ok @@ -0,0 +1 @@ +nat on vtnet0 inet proto tcp from 192.0.2.1 to 192.0.2.2 -> 203.0.113.0/24 bitmask diff --git a/sbin/pfctl/tests/files/pf1046.fail b/sbin/pfctl/tests/files/pf1046.fail new file mode 100644 index 000000000000..b152f9063241 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1046.fail @@ -0,0 +1 @@ +tables are not supported by pool type diff --git a/sbin/pfctl/tests/files/pf1046.in b/sbin/pfctl/tests/files/pf1046.in new file mode 100644 index 000000000000..e4a9f79efd6f --- /dev/null +++ b/sbin/pfctl/tests/files/pf1046.in @@ -0,0 +1 @@ +nat on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> <targets> bitmask diff --git a/sbin/pfctl/tests/files/pf1047.fail b/sbin/pfctl/tests/files/pf1047.fail new file mode 100644 index 000000000000..239b96b2fed4 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1047.fail @@ -0,0 +1 @@ +interface \(vtnet1\) is not supported by pool type diff --git a/sbin/pfctl/tests/files/pf1047.in b/sbin/pfctl/tests/files/pf1047.in new file mode 100644 index 000000000000..369bfcb0fb26 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1047.in @@ -0,0 +1 @@ +nat on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> (vtnet1) bitmask diff --git a/sbin/pfctl/tests/files/pf1048.in b/sbin/pfctl/tests/files/pf1048.in new file mode 100644 index 000000000000..01232a33b5d8 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1048.in @@ -0,0 +1 @@ +nat on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> 203.0.113.0/24 random diff --git a/sbin/pfctl/tests/files/pf1048.ok b/sbin/pfctl/tests/files/pf1048.ok new file mode 100644 index 000000000000..35e86fc676fc --- /dev/null +++ b/sbin/pfctl/tests/files/pf1048.ok @@ -0,0 +1 @@ +nat on vtnet0 inet proto tcp from 192.0.2.1 to 192.0.2.2 -> 203.0.113.0/24 random diff --git a/sbin/pfctl/tests/files/pf1049.in b/sbin/pfctl/tests/files/pf1049.in new file mode 100644 index 000000000000..3f2e5acf8265 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1049.in @@ -0,0 +1 @@ +nat on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> { 192.0.2.3 } diff --git a/sbin/pfctl/tests/files/pf1049.ok b/sbin/pfctl/tests/files/pf1049.ok new file mode 100644 index 000000000000..be573ef460f5 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1049.ok @@ -0,0 +1 @@ +nat on vtnet0 inet proto tcp from 192.0.2.1 to 192.0.2.2 -> 192.0.2.3 diff --git a/sbin/pfctl/tests/files/pf1050.in b/sbin/pfctl/tests/files/pf1050.in new file mode 100644 index 000000000000..69ccaf445c3b --- /dev/null +++ b/sbin/pfctl/tests/files/pf1050.in @@ -0,0 +1 @@ +nat on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> <targets> diff --git a/sbin/pfctl/tests/files/pf1050.ok b/sbin/pfctl/tests/files/pf1050.ok new file mode 100644 index 000000000000..24ca9b459bb7 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1050.ok @@ -0,0 +1 @@ +nat on vtnet0 inet proto tcp from 192.0.2.1 to 192.0.2.2 -> <targets> round-robin diff --git a/sbin/pfctl/tests/files/pf1051.in b/sbin/pfctl/tests/files/pf1051.in new file mode 100644 index 000000000000..734da64a372c --- /dev/null +++ b/sbin/pfctl/tests/files/pf1051.in @@ -0,0 +1 @@ +nat on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> { 203.0.113.1 203.0.113.2 } diff --git a/sbin/pfctl/tests/files/pf1051.ok b/sbin/pfctl/tests/files/pf1051.ok new file mode 100644 index 000000000000..86f23488be41 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1051.ok @@ -0,0 +1 @@ +nat on vtnet0 inet proto tcp from 192.0.2.1 to 192.0.2.2 -> { 203.0.113.1, 203.0.113.2 } round-robin diff --git a/sbin/pfctl/tests/files/pf1052.in b/sbin/pfctl/tests/files/pf1052.in new file mode 100644 index 000000000000..2ea770f3c06e --- /dev/null +++ b/sbin/pfctl/tests/files/pf1052.in @@ -0,0 +1 @@ +nat on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> { 203.0.113.1 <targets> } diff --git a/sbin/pfctl/tests/files/pf1052.ok b/sbin/pfctl/tests/files/pf1052.ok new file mode 100644 index 000000000000..b71d105eb77a --- /dev/null +++ b/sbin/pfctl/tests/files/pf1052.ok @@ -0,0 +1 @@ +nat on vtnet0 inet proto tcp from 192.0.2.1 to 192.0.2.2 -> { 203.0.113.1, <targets> } round-robin diff --git a/sbin/pfctl/tests/files/pf1053.in b/sbin/pfctl/tests/files/pf1053.in new file mode 100644 index 000000000000..f0cced0b64a2 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1053.in @@ -0,0 +1 @@ +nat on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> 203.0.113.0/24 diff --git a/sbin/pfctl/tests/files/pf1053.ok b/sbin/pfctl/tests/files/pf1053.ok new file mode 100644 index 000000000000..de321b8c738f --- /dev/null +++ b/sbin/pfctl/tests/files/pf1053.ok @@ -0,0 +1 @@ +nat on vtnet0 inet proto tcp from 192.0.2.1 to 192.0.2.2 -> 203.0.113.0/24 diff --git a/sbin/pfctl/tests/files/pf1054.in b/sbin/pfctl/tests/files/pf1054.in new file mode 100644 index 000000000000..9e66bb2a81d6 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1054.in @@ -0,0 +1,3 @@ +# XXX: it causes just the 0th address to be used without cycling +# Probably a bug +nat on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> 203.0.113.0/24 round-robin diff --git a/sbin/pfctl/tests/files/pf1054.ok b/sbin/pfctl/tests/files/pf1054.ok new file mode 100644 index 000000000000..3d7ab7974d87 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1054.ok @@ -0,0 +1 @@ +nat on vtnet0 inet proto tcp from 192.0.2.1 to 192.0.2.2 -> 203.0.113.0/24 round-robin diff --git a/sbin/pfctl/tests/files/pf1055.in b/sbin/pfctl/tests/files/pf1055.in new file mode 100644 index 000000000000..c116ef5fd43e --- /dev/null +++ b/sbin/pfctl/tests/files/pf1055.in @@ -0,0 +1 @@ +nat on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> 203.0.113.0/24 source-hash 0x42424242424242424242424242424242 diff --git a/sbin/pfctl/tests/files/pf1055.ok b/sbin/pfctl/tests/files/pf1055.ok new file mode 100644 index 000000000000..468e47012169 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1055.ok @@ -0,0 +1 @@ +nat on vtnet0 inet proto tcp from 192.0.2.1 to 192.0.2.2 -> 203.0.113.0/24 source-hash 0x42424242424242424242424242424242 diff --git a/sbin/pfctl/tests/files/pf1056.in b/sbin/pfctl/tests/files/pf1056.in new file mode 100644 index 000000000000..bd2af077fc3f --- /dev/null +++ b/sbin/pfctl/tests/files/pf1056.in @@ -0,0 +1 @@ +pass in on vtnet0 inet6 from any to 64:ff9b::/96 af-to inet from 203.0.113.1 to 203.0.113.2 diff --git a/sbin/pfctl/tests/files/pf1056.ok b/sbin/pfctl/tests/files/pf1056.ok new file mode 100644 index 000000000000..0397570dbce0 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1056.ok @@ -0,0 +1 @@ +pass in on vtnet0 inet6 from any to 64:ff9b::/96 flags S/SA keep state af-to inet from 203.0.113.1 to 203.0.113.2 diff --git a/sbin/pfctl/tests/files/pf1057.in b/sbin/pfctl/tests/files/pf1057.in new file mode 100644 index 000000000000..0e26976e5a0d --- /dev/null +++ b/sbin/pfctl/tests/files/pf1057.in @@ -0,0 +1 @@ +nat on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> vlan1057 diff --git a/sbin/pfctl/tests/files/pf1057.ok b/sbin/pfctl/tests/files/pf1057.ok new file mode 100644 index 000000000000..7626951e138c --- /dev/null +++ b/sbin/pfctl/tests/files/pf1057.ok @@ -0,0 +1 @@ +nat on vtnet0 inet proto tcp from 192.0.2.1 to 192.0.2.2 -> 203.0.113.5 diff --git a/sbin/pfctl/tests/files/pf1058.in b/sbin/pfctl/tests/files/pf1058.in new file mode 100644 index 000000000000..27c0ef1d69b3 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1058.in @@ -0,0 +1 @@ +nat on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> { 203.0.113.1 vlan1058 } diff --git a/sbin/pfctl/tests/files/pf1058.ok b/sbin/pfctl/tests/files/pf1058.ok new file mode 100644 index 000000000000..b1d2b07a58b4 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1058.ok @@ -0,0 +1 @@ +nat on vtnet0 inet proto tcp from 192.0.2.1 to 192.0.2.2 -> { 203.0.113.1, 203.0.113.5 } round-robin diff --git a/sbin/pfctl/tests/files/pf1059.in b/sbin/pfctl/tests/files/pf1059.in new file mode 100644 index 000000000000..92ed5c50656b --- /dev/null +++ b/sbin/pfctl/tests/files/pf1059.in @@ -0,0 +1 @@ +nat on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> (vlan1059) diff --git a/sbin/pfctl/tests/files/pf1059.ok b/sbin/pfctl/tests/files/pf1059.ok new file mode 100644 index 000000000000..6b028f18ee60 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1059.ok @@ -0,0 +1 @@ +nat on vtnet0 inet proto tcp from 192.0.2.1 to 192.0.2.2 -> (vlan1059) round-robin diff --git a/sbin/pfctl/tests/files/pf1060.in b/sbin/pfctl/tests/files/pf1060.in new file mode 100644 index 000000000000..85cdd19f2897 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1060.in @@ -0,0 +1 @@ +nat on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> { 203.0.113.0 (vlan1060) } diff --git a/sbin/pfctl/tests/files/pf1060.ok b/sbin/pfctl/tests/files/pf1060.ok new file mode 100644 index 000000000000..3364b3cbdcc5 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1060.ok @@ -0,0 +1 @@ +nat on vtnet0 inet proto tcp from 192.0.2.1 to 192.0.2.2 -> { 203.0.113.0, (vlan1060) } round-robin diff --git a/sbin/pfctl/tests/files/pf1061.in b/sbin/pfctl/tests/files/pf1061.in new file mode 100644 index 000000000000..32eb8272db8b --- /dev/null +++ b/sbin/pfctl/tests/files/pf1061.in @@ -0,0 +1 @@ +nat on vtnet0 proto tcp from 2001:db8::1 to 2001:db8::2 -> vlan1061:0 diff --git a/sbin/pfctl/tests/files/pf1061.ok b/sbin/pfctl/tests/files/pf1061.ok new file mode 100644 index 000000000000..d2e6d969cb11 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1061.ok @@ -0,0 +1 @@ +nat on vtnet0 inet6 proto tcp from 2001:db8::1 to 2001:db8::2 -> 2001:db8::cb00:7105 diff --git a/sbin/pfctl/tests/files/pf1062.in b/sbin/pfctl/tests/files/pf1062.in new file mode 100644 index 000000000000..4d6a0ecc2e92 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1062.in @@ -0,0 +1 @@ +nat on vtnet0 proto tcp from 2001:db8::1 to 2001:db8::2 -> { 2001:db8::3 vlan1062:0 } diff --git a/sbin/pfctl/tests/files/pf1062.ok b/sbin/pfctl/tests/files/pf1062.ok new file mode 100644 index 000000000000..cb5db62ded1d --- /dev/null +++ b/sbin/pfctl/tests/files/pf1062.ok @@ -0,0 +1 @@ +nat on vtnet0 inet6 proto tcp from 2001:db8::1 to 2001:db8::2 -> { 2001:db8::3, 2001:db8::cb00:7105 } round-robin diff --git a/sbin/pfctl/tests/files/pf1063.in b/sbin/pfctl/tests/files/pf1063.in new file mode 100644 index 000000000000..3d164538640d --- /dev/null +++ b/sbin/pfctl/tests/files/pf1063.in @@ -0,0 +1 @@ +nat on vtnet0 proto tcp from 2001:db8::1 to 2001:db8::2 -> (vlan1063) diff --git a/sbin/pfctl/tests/files/pf1063.ok b/sbin/pfctl/tests/files/pf1063.ok new file mode 100644 index 000000000000..13189e00cc8a --- /dev/null +++ b/sbin/pfctl/tests/files/pf1063.ok @@ -0,0 +1 @@ +nat on vtnet0 inet6 proto tcp from 2001:db8::1 to 2001:db8::2 -> (vlan1063) round-robin diff --git a/sbin/pfctl/tests/files/pf1064.in b/sbin/pfctl/tests/files/pf1064.in new file mode 100644 index 000000000000..78d04135154f --- /dev/null +++ b/sbin/pfctl/tests/files/pf1064.in @@ -0,0 +1 @@ +nat on vtnet0 proto tcp from 2001:db8::1 to 2001:db8::2 -> { fe80::2 (vlan1064) } diff --git a/sbin/pfctl/tests/files/pf1064.ok b/sbin/pfctl/tests/files/pf1064.ok new file mode 100644 index 000000000000..ed15d054ab34 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1064.ok @@ -0,0 +1 @@ +nat on vtnet0 inet6 proto tcp from 2001:db8::1 to 2001:db8::2 -> { fe80::2, (vlan1064) } round-robin diff --git a/sbin/pfctl/tests/files/pf1065.in b/sbin/pfctl/tests/files/pf1065.in new file mode 100644 index 000000000000..690045befee6 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1065.in @@ -0,0 +1 @@ +no nat on vtnet0 proto tcp from 2001:db8::1 to 2001:db8::2 diff --git a/sbin/pfctl/tests/files/pf1065.ok b/sbin/pfctl/tests/files/pf1065.ok new file mode 100644 index 000000000000..651a2fa0ae09 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1065.ok @@ -0,0 +1 @@ +no nat on vtnet0 inet6 proto tcp from 2001:db8::1 to 2001:db8::2 diff --git a/sbin/pfctl/tests/files/pf1066.in b/sbin/pfctl/tests/files/pf1066.in new file mode 100644 index 000000000000..e81461c470ab --- /dev/null +++ b/sbin/pfctl/tests/files/pf1066.in @@ -0,0 +1 @@ +no rdr on vtnet0 proto tcp from 2001:db8::1 to 2001:db8::2 diff --git a/sbin/pfctl/tests/files/pf1066.ok b/sbin/pfctl/tests/files/pf1066.ok new file mode 100644 index 000000000000..5ff596fa0158 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1066.ok @@ -0,0 +1 @@ +no rdr on vtnet0 inet6 proto tcp from 2001:db8::1 to 2001:db8::2 diff --git a/sbin/pfctl/tests/files/pf1067.fail b/sbin/pfctl/tests/files/pf1067.fail new file mode 100644 index 000000000000..23ac1daad64f --- /dev/null +++ b/sbin/pfctl/tests/files/pf1067.fail @@ -0,0 +1 @@ +route-to, reply-to and dup-to are not supported on block rules diff --git a/sbin/pfctl/tests/files/pf1067.in b/sbin/pfctl/tests/files/pf1067.in new file mode 100644 index 000000000000..47f3bf6285dd --- /dev/null +++ b/sbin/pfctl/tests/files/pf1067.in @@ -0,0 +1 @@ +block in route-to (if0 127.0.0.1/8) diff --git a/sbin/pfctl/tests/files/pf1068.in b/sbin/pfctl/tests/files/pf1068.in new file mode 100644 index 000000000000..993cfa37f8f9 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1068.in @@ -0,0 +1 @@ +pass in proto icmp max-pkt-rate 100/10 diff --git a/sbin/pfctl/tests/files/pf1068.ok b/sbin/pfctl/tests/files/pf1068.ok new file mode 100644 index 000000000000..bd36043207f9 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1068.ok @@ -0,0 +1 @@ +pass in proto icmp all max-pkt-rate 100/10 keep state diff --git a/sbin/pfctl/tests/files/pf1069.in b/sbin/pfctl/tests/files/pf1069.in new file mode 100644 index 000000000000..3a69158fff7e --- /dev/null +++ b/sbin/pfctl/tests/files/pf1069.in @@ -0,0 +1 @@ +pass in proto icmp max-pkt-size 128 diff --git a/sbin/pfctl/tests/files/pf1069.ok b/sbin/pfctl/tests/files/pf1069.ok new file mode 100644 index 000000000000..b79228266156 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1069.ok @@ -0,0 +1 @@ +pass in proto icmp all max-pkt-size 128 keep state diff --git a/sbin/pfctl/tests/files/pf1070.fail b/sbin/pfctl/tests/files/pf1070.fail new file mode 100644 index 000000000000..60b56d9da2b9 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1070.fail @@ -0,0 +1 @@ +pf1070.include:2: syntax error diff --git a/sbin/pfctl/tests/files/pf1070.in b/sbin/pfctl/tests/files/pf1070.in new file mode 100644 index 000000000000..42b874d4d6f4 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1070.in @@ -0,0 +1,2 @@ +pass in +include pf1070.include diff --git a/sbin/pfctl/tests/files/pf1070.include b/sbin/pfctl/tests/files/pf1070.include new file mode 100644 index 000000000000..09c3755dbe28 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1070.include @@ -0,0 +1,2 @@ +block out +invalidline diff --git a/sbin/pfctl/tests/files/pf1071.in b/sbin/pfctl/tests/files/pf1071.in new file mode 100644 index 000000000000..9e6c2abc0621 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1071.in @@ -0,0 +1 @@ +pass inet from (lo0)/24 diff --git a/sbin/pfctl/tests/files/pf1071.ok b/sbin/pfctl/tests/files/pf1071.ok new file mode 100644 index 000000000000..409b5dc4b068 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1071.ok @@ -0,0 +1 @@ +pass inet from (lo0)/24 to any flags S/SA keep state diff --git a/sbin/pfctl/tests/files/pf1072.fail b/sbin/pfctl/tests/files/pf1072.fail new file mode 100644 index 000000000000..06ef5ae457e5 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1072.fail @@ -0,0 +1 @@ +invalid port range diff --git a/sbin/pfctl/tests/files/pf1072.in b/sbin/pfctl/tests/files/pf1072.in new file mode 100644 index 000000000000..e09e92388ce1 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1072.in @@ -0,0 +1 @@ +pass in proto tcp from any port 500:100 to any diff --git a/sbin/pfctl/tests/files/pf1073.in b/sbin/pfctl/tests/files/pf1073.in new file mode 100644 index 000000000000..477995893ac3 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1073.in @@ -0,0 +1 @@ +pass in on vtnet0 route-to ( vtnet1 2001:db8::1 ) prefer-ipv6-nexthop inet diff --git a/sbin/pfctl/tests/files/pf1073.ok b/sbin/pfctl/tests/files/pf1073.ok new file mode 100644 index 000000000000..f34867508c75 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1073.ok @@ -0,0 +1 @@ +pass in on vtnet0 route-to (vtnet1 2001:db8::1) prefer-ipv6-nexthop inet all flags S/SA keep state diff --git a/sbin/pfctl/tests/files/pf1074.fail b/sbin/pfctl/tests/files/pf1074.fail new file mode 100644 index 000000000000..afe8ee3c458f --- /dev/null +++ b/sbin/pfctl/tests/files/pf1074.fail @@ -0,0 +1 @@ +no routing address with matching address family found. diff --git a/sbin/pfctl/tests/files/pf1074.in b/sbin/pfctl/tests/files/pf1074.in new file mode 100644 index 000000000000..5d285bc5d6e8 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1074.in @@ -0,0 +1 @@ +pass in on vtnet0 route-to ( vtnet1 2001:db8::1 ) inet diff --git a/sbin/pfctl/tests/files/pf1075.in b/sbin/pfctl/tests/files/pf1075.in new file mode 100644 index 000000000000..835a31a25c6a --- /dev/null +++ b/sbin/pfctl/tests/files/pf1075.in @@ -0,0 +1 @@ +pass inet from (lo0)/24 once diff --git a/sbin/pfctl/tests/files/pf1075.ok b/sbin/pfctl/tests/files/pf1075.ok new file mode 100644 index 000000000000..2369c9410cda --- /dev/null +++ b/sbin/pfctl/tests/files/pf1075.ok @@ -0,0 +1 @@ +pass inet from (lo0)/24 to any flags S/SA keep state once diff --git a/sbin/pfctl/tests/macro.sh b/sbin/pfctl/tests/macro.sh new file mode 100755 index 000000000000..071c6cb4f426 --- /dev/null +++ b/sbin/pfctl/tests/macro.sh @@ -0,0 +1,28 @@ + +atf_test_case "space" cleanup +space_head() +{ + atf_set descr "Test macros with spaces" + atf_set require.kmods "pf" +} + +space_body() +{ + echo \"this is\" = \"a variable\" > pf.conf + cat pf.conf + atf_check -o ignore -e ignore -s exit:1 pfctl -nvf pf.conf + + echo this = \"a variable\" > pf.conf + cat pf.conf + atf_check -o ignore -s exit:0 pfctl -nvf pf.conf +} + +space_cleanup() +{ + rm -f pf.conf +} + +atf_init_test_cases() +{ + atf_add_test_case "space" +} diff --git a/sbin/pfctl/tests/pfctl_test.c b/sbin/pfctl/tests/pfctl_test.c new file mode 100644 index 000000000000..5f0aa7826bb4 --- /dev/null +++ b/sbin/pfctl/tests/pfctl_test.c @@ -0,0 +1,341 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright 2020 Alex Richardson <arichardson@FreeBSD.org> + * + * This software was developed by SRI International and the University of + * Cambridge Computer Laboratory (Department of Computer Science and + * Technology) under DARPA contract HR0011-18-C-0016 ("ECATS"), as part of the + * DARPA SSITH research programme. + * + * This work was supported by Innovate UK project 105694, "Digital Security by + * Design (DSbD) Technology Platform Prototype". + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. + */ + +#include <sys/types.h> +#include <sys/param.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <spawn.h> +#include <sys/module.h> +#include <sys/sbuf.h> +#include <sys/stat.h> +#include <sys/wait.h> + +#include <atf-c.h> + +/* + * Tests 0001-0999 are copied from OpenBSD's regress/sbin/pfctl. + * Tests 1001-1999 are ours (FreeBSD's own). + * + * pf: Run pfctl -nv on pfNNNN.in and check that the output matches pfNNNN.ok. + * Copied from OpenBSD. Main differences are some things not working + * in FreeBSD: + * * The action 'match' + * * The command 'set reassemble' + * * The 'from'/'to' options together with 'route-to' + * * The option 'scrub' (it is an action in FreeBSD) + * * Accepting undefined routing tables in actions (??: see pf0093.in) + * * The 'route' option + * * The 'set queue def' option + * selfpf: Feed pfctl output through pfctl again and verify it stays the same. + * Copied from OpenBSD. + */ + +extern char **environ; + +static struct sbuf * +read_fd(int fd, size_t sizehint) +{ + struct sbuf *sb; + ssize_t count; + char buffer[MAXBSIZE]; + + sb = sbuf_new(NULL, NULL, sizehint, SBUF_AUTOEXTEND); + errno = 0; + while ((count = read(fd, buffer, sizeof(buffer) - 1)) > 0) { + sbuf_bcat(sb, buffer, count); + } + ATF_REQUIRE_ERRNO(0, count == 0 && "Should have reached EOF"); + sbuf_finish(sb); /* Ensure NULL-termination */ + return (sb); +} + +static struct sbuf * +read_file(const char *filename) +{ + struct stat s; + struct sbuf *result; + int fd; + + errno = 0; + ATF_REQUIRE_EQ_MSG(stat(filename, &s), 0, "cannot stat %s", filename); + fd = open(filename, O_RDONLY); + ATF_REQUIRE_ERRNO(0, fd > 0); + result = read_fd(fd, s.st_size); + ATF_REQUIRE_ERRNO(0, close(fd) == 0); + return (result); +} + +static void +run_command_pipe(const char *argv[], struct sbuf **output) +{ + posix_spawn_file_actions_t action; + pid_t pid; + int pipefds[2]; + int status; + + ATF_REQUIRE_ERRNO(0, pipe(pipefds) == 0); + + posix_spawn_file_actions_init(&action); + posix_spawn_file_actions_addclose(&action, STDIN_FILENO); + posix_spawn_file_actions_addclose(&action, pipefds[1]); + posix_spawn_file_actions_adddup2(&action, pipefds[0], STDOUT_FILENO); + posix_spawn_file_actions_adddup2(&action, pipefds[0], STDERR_FILENO); + + printf("Running "); + for (int i=0; argv[i] != NULL; i++) + printf("%s ", argv[i]); + printf("\n"); + + status = posix_spawnp( + &pid, argv[0], &action, NULL, __DECONST(char **, argv), environ); + ATF_REQUIRE_EQ_MSG( + status, 0, "posix_spawn failed: %s", strerror(errno)); + posix_spawn_file_actions_destroy(&action); + close(pipefds[0]); + + (*output) = read_fd(pipefds[1], 0); + printf("---\n%s---\n", sbuf_data(*output)); + ATF_REQUIRE_EQ(waitpid(pid, &status, 0), pid); + ATF_REQUIRE_MSG(WIFEXITED(status), + "%s returned non-zero! Output:\n %s", argv[0], sbuf_data(*output)); + close(pipefds[1]); +} + +static void +run_command(const char *argv[]) +{ + posix_spawn_file_actions_t action; + pid_t pid; + int status; + + posix_spawn_file_actions_init(&action); + posix_spawn_file_actions_addopen(&action, STDOUT_FILENO, "/dev/null", O_WRONLY, 0); + posix_spawn_file_actions_addopen(&action, STDERR_FILENO, "/dev/null", O_WRONLY, 0); + posix_spawn_file_actions_addopen(&action, STDIN_FILENO, "/dev/zero", O_RDONLY, 0); + + printf("Running "); + for (int i=0; argv[i] != NULL; i++) + printf("%s ", argv[i]); + printf("\n"); + + status = posix_spawnp( + &pid, argv[0], &action, NULL, __DECONST(char **, argv), environ); + posix_spawn_file_actions_destroy(&action); + waitpid(pid, &status, 0); +} + +static void +run_pfctl_test(const char *input_path, const char *output_path, + const atf_tc_t *tc, bool test_failure) +{ + char input_files_path[PATH_MAX]; + struct sbuf *expected_output; + struct sbuf *real_output; + + /* The test inputs need to be able to use relative includes. */ + snprintf(input_files_path, sizeof(input_files_path), "%s/files", + atf_tc_get_config_var(tc, "srcdir")); + ATF_REQUIRE_ERRNO(0, chdir(input_files_path) == 0); + expected_output = read_file(output_path); + + const char *argv[] = { "pfctl", "-o", "none", "-nvf", input_path, + NULL }; + run_command_pipe(argv, &real_output); + + if (test_failure) { + /* + * Error output contains additional strings like line number + * or "skipping rule due to errors", so use regexp to see + * if the expected error message is there somewhere. + */ + ATF_CHECK_MATCH(sbuf_data(expected_output), sbuf_data(real_output)); + sbuf_delete(expected_output); + } else { + ATF_CHECK_STREQ(sbuf_data(expected_output), sbuf_data(real_output)); + sbuf_delete(expected_output); + } + + sbuf_delete(real_output); +} + +static void +do_pf_test_iface_create(const char *number) +{ + struct sbuf *ifconfig_output; + char ifname[16] = {0}; + + snprintf(ifname, sizeof(ifname), "vlan%s", number); + const char *argv[] = { "ifconfig", ifname, "create", NULL}; + run_command_pipe(argv, &ifconfig_output); + sbuf_delete(ifconfig_output); + + const char *argv_inet[] = { "ifconfig", ifname, "inet", "203.0.113.5/30", NULL}; + run_command_pipe(argv_inet, &ifconfig_output); + sbuf_delete(ifconfig_output); + + const char *argv_inet6[] = { "ifconfig", ifname, "inet6", "2001:db8::203.0.113.5/126", NULL}; + run_command_pipe(argv_inet6, &ifconfig_output); + sbuf_delete(ifconfig_output); + + const char *argv_show[] = { "ifconfig", ifname, NULL}; + run_command_pipe(argv_show, &ifconfig_output); + sbuf_delete(ifconfig_output); +} + +static void +do_pf_test_iface_remove(const char *number) +{ + char ifname[16] = {0}; + + snprintf(ifname, sizeof(ifname), "vlan%s", number); + const char *argv[] = { "ifconfig", ifname, "destroy", NULL}; + run_command(argv); +} + +static void +do_pf_test(const char *number, const atf_tc_t *tc) +{ + char *input_path; + char *expected_path; + asprintf(&input_path, "%s/files/pf%s.in", + atf_tc_get_config_var(tc, "srcdir"), number); + asprintf(&expected_path, "%s/files/pf%s.ok", + atf_tc_get_config_var(tc, "srcdir"), number); + run_pfctl_test(input_path, expected_path, tc, false); + free(input_path); + free(expected_path); +} + +static void +do_pf_test_fail(const char *number, const atf_tc_t *tc) +{ + char *input_path; + char *expected_path; + asprintf(&input_path, "%s/files/pf%s.in", + atf_tc_get_config_var(tc, "srcdir"), number); + asprintf(&expected_path, "%s/files/pf%s.fail", + atf_tc_get_config_var(tc, "srcdir"), number); + run_pfctl_test(input_path, expected_path, tc, true); + free(input_path); + free(expected_path); +} + +static void +do_selfpf_test(const char *number, const atf_tc_t *tc) +{ + char *expected_path; + asprintf(&expected_path, "%s/files/pf%s.ok", + atf_tc_get_config_var(tc, "srcdir"), number); + run_pfctl_test(expected_path, expected_path, tc, false); + free(expected_path); +} + +/* Standard tests perform the normal test and then the selfpf test */ +#define PFCTL_TEST(number, descr) \ + ATF_TC(pf##number); \ + ATF_TC_HEAD(pf##number, tc) \ + { \ + atf_tc_set_md_var(tc, "descr", descr); \ + atf_tc_set_md_var(tc, "require.kmods", "pf"); \ + } \ + ATF_TC_BODY(pf##number, tc) \ + { \ + do_pf_test(#number, tc); \ + } \ + ATF_TC(selfpf##number); \ + ATF_TC_HEAD(selfpf##number, tc) \ + { \ + atf_tc_set_md_var(tc, "descr", "Self " descr); \ + atf_tc_set_md_var(tc, "require.kmods", "pf"); \ + } \ + ATF_TC_BODY(selfpf##number, tc) \ + { \ + do_selfpf_test(#number, tc); \ + } +/* Tests for failure perform only the normal test */ +#define PFCTL_TEST_FAIL(number, descr) \ + ATF_TC(pf##number); \ + ATF_TC_HEAD(pf##number, tc) \ + { \ + atf_tc_set_md_var(tc, "descr", descr); \ + atf_tc_set_md_var(tc, "require.kmods", "pf"); \ + } \ + ATF_TC_BODY(pf##number, tc) \ + { \ + do_pf_test_fail(#number, tc); \ + } +/* Tests with interface perform only the normal test */ +#define PFCTL_TEST_IFACE(number, descr) \ + ATF_TC_WITH_CLEANUP(pf##number); \ + ATF_TC_HEAD(pf##number, tc) \ + { \ + atf_tc_set_md_var(tc, "descr", descr); \ + atf_tc_set_md_var(tc, "execenv", "jail"); \ + atf_tc_set_md_var(tc, "execenv.jail.params", "vnet"); \ + atf_tc_set_md_var(tc, "require.kmods", "pf"); \ + } \ + ATF_TC_BODY(pf##number, tc) \ + { \ + do_pf_test_iface_create(#number); \ + do_pf_test(#number, tc); \ + } \ + ATF_TC_CLEANUP(pf##number, tc) \ + { \ + do_pf_test_iface_remove(#number); \ + } +#include "pfctl_test_list.inc" +#undef PFCTL_TEST +#undef PFCTL_TEST_FAIL +#undef PFCTL_TEST_IFACE + +ATF_TP_ADD_TCS(tp) +{ +#define PFCTL_TEST(number, descr) \ + ATF_TP_ADD_TC(tp, pf##number); \ + ATF_TP_ADD_TC(tp, selfpf##number); +#define PFCTL_TEST_FAIL(number, descr) \ + ATF_TP_ADD_TC(tp, pf##number); +#define PFCTL_TEST_IFACE(number, descr) \ + ATF_TP_ADD_TC(tp, pf##number); +#include "pfctl_test_list.inc" +#undef PFCTL_TEST +#undef PFCTL_TEST_FAIL +#undef PFCTL_TEST_IFACE + + return atf_no_error(); +} diff --git a/sbin/pfctl/tests/pfctl_test_list.inc b/sbin/pfctl/tests/pfctl_test_list.inc new file mode 100644 index 000000000000..9dd4a590ad8f --- /dev/null +++ b/sbin/pfctl/tests/pfctl_test_list.inc @@ -0,0 +1,186 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright 2020 Alex Richardson <arichardson@FreeBSD.org> + * + * This software was developed by SRI International and the University of + * Cambridge Computer Laboratory (Department of Computer Science and + * Technology) under DARPA contract HR0011-18-C-0016 ("ECATS"), as part of the + * DARPA SSITH research programme. + * + * This work was supported by Innovate UK project 105694, "Digital Security by + * Design (DSbD) Technology Platform Prototype". + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. + */ + +/* + * No include guards since this file is included multiple times by pfctl_test + * to avoid duplicating code. + */ +PFCTL_TEST(0001, "Pass with labels") +PFCTL_TEST(0002, "Block/pass") +PFCTL_TEST(0003, "Block/pass with flags") +PFCTL_TEST(0004, "Block") +PFCTL_TEST(0005, "Block with variables") +PFCTL_TEST(0006, "Variables") +PFCTL_TEST(0007, "Block/pass with return") +PFCTL_TEST(0008, "Block with address list") +PFCTL_TEST(0009, "Block with interface list") +PFCTL_TEST(0010, "Block/pass with return") +PFCTL_TEST(0011, "Block/pass ICMP") +PFCTL_TEST(0012, "Pass to subnets") +PFCTL_TEST(0013, "Pass quick") +PFCTL_TEST(0014, "Pass quick IPv6") +PFCTL_TEST(0016, "Pass with no state") +PFCTL_TEST(0018, "Address lists") +PFCTL_TEST(0019, "Lists") +PFCTL_TEST(0020, "Lists") +PFCTL_TEST(0022, "Set options") +PFCTL_TEST(0023, "Block on negated interface") +PFCTL_TEST(0024, "Variable concatenation") +PFCTL_TEST(0025, "Antispoof") +PFCTL_TEST(0026, "Block from negated interface") +PFCTL_TEST(0028, "Block with log and quick") +PFCTL_TEST(0030, "Line continuation") +PFCTL_TEST(0031, "Block policy") +PFCTL_TEST(0032, "Pass to any") +PFCTL_TEST(0034, "Pass with probability") +PFCTL_TEST(0035, "Matching on TOS") +PFCTL_TEST(0038, "Pass with user") +PFCTL_TEST(0039, "Ordered opts") +PFCTL_TEST(0040, "Block/pass") +PFCTL_TEST(0041, "Anchors") +PFCTL_TEST(0047, "Pass with labels") +PFCTL_TEST(0048, "Tables") +PFCTL_TEST(0049, "Broadcast and network modifiers") +PFCTL_TEST(0050, "Double macro set") +PFCTL_TEST(0052, "Set optimization") +PFCTL_TEST(0053, "Pass with labels") +PFCTL_TEST(0055, "Set options") +PFCTL_TEST(0056, "State opts") +PFCTL_TEST(0057, "Variables") +PFCTL_TEST(0060, "Pass from multicast") +PFCTL_TEST(0061, "Dynaddr with netmask") +PFCTL_TEST(0065, "Antispoof with labels") +PFCTL_TEST(0067, "Tags") +PFCTL_TEST(0069, "Tags") +PFCTL_TEST(0070, "Tags") +PFCTL_TEST(0071, "Tags") +PFCTL_TEST(0072, "Tags") +PFCTL_TEST(0074, "Synproxy") +PFCTL_TEST(0075, "Block quick with tags") +PFCTL_TEST(0077, "Dynaddr with netmask") +PFCTL_TEST(0078, "Table with label") +PFCTL_TEST(0079, "No-route with label") +PFCTL_TEST(0081, "Address list and table list with no-route") +PFCTL_TEST(0082, "Pass with interface, table and no-route") +PFCTL_TEST(0084, "Source track") +PFCTL_TEST(0085, "Tag macro expansion") +PFCTL_TEST(0087, "Optimization rule reordering") +PFCTL_TEST(0088, "Optimization duplicate rules handling") +PFCTL_TEST(0089, "TCP connection tracking") +PFCTL_TEST(0090, "Log opts") +PFCTL_TEST(0091, "Nested anchors") +PFCTL_TEST(0092, "Comments") +PFCTL_TEST(0094, "Address ranges") +PFCTL_TEST(0095, "Include") +PFCTL_TEST(0096, "Variables") +PFCTL_TEST(0097, "Divert-to") +PFCTL_TEST(0098, "Pass") +PFCTL_TEST(0100, "Anchor with multiple path components") +PFCTL_TEST(0101, "Prio") +PFCTL_TEST(0102, "Address lists with mixed address family") +PFCTL_TEST(0104, "Divert-to with localhost") +PFCTL_TEST(1001, "Binat") +PFCTL_TEST(1002, "Set timeout interval") +PFCTL_TEST(1003, "ALTQ") +PFCTL_TEST(1004, "ALTQ with Codel") +PFCTL_TEST(1005, "PR 231323") +PFCTL_TEST(1006, "pfctl crashes with certain fairq configurations") +PFCTL_TEST(1007, "Basic ethernet rule") +PFCTL_TEST(1008, "Ethernet rule with mask length") +PFCTL_TEST(1009, "Ethernet rule with mask") +PFCTL_TEST(1010, "POM_STICKYADDRESS test") +PFCTL_TEST(1011, "Test disabling scrub fragment reassemble") +PFCTL_TEST(1012, "Test scrub fragment reassemble is default") +PFCTL_TEST(1013, "Ethernet rule with ridentifier") +PFCTL_TEST(1014, "Ethernet rule with one label") +PFCTL_TEST(1015, "Ethernet rule with several labels") +PFCTL_TEST(1016, "Ethernet rule with ridentifier and one label") +PFCTL_TEST(1017, "Ethernet rule with ridentifier and several labels") +PFCTL_TEST(1018, "Test dynamic address mask") +PFCTL_TEST(1019, "Test pflow option") +PFCTL_TEST(1020, "Test hashmark and semicolon comment") +PFCTL_TEST(1021, "Endpoint-independent") +PFCTL_TEST(1022, "Test received-on") +PFCTL_TEST(1023, "Test match log(matches)") +PFCTL_TEST(1024, "nat64") +PFCTL_TEST(1025, "nat64 with implicit address family") +PFCTL_TEST(1026, "nat64 with route-to") +PFCTL_TEST(1027, "nat64 with reply-to") +PFCTL_TEST(1028, "RDR pool: For RDR rules no port specified means keep port") +PFCTL_TEST(1029, "RDR pool: A single port is shown") +PFCTL_TEST(1030, "RDR pool: The default values are shown for RDR rules") +PFCTL_TEST(1031, "RDR pool: Multiple ports redirected to a single port") +PFCTL_TEST(1032, "RDR pool: Multiple ports redirected to a port range") +PFCTL_TEST_FAIL(1033, "RDR pool: static-port can't be used with RDR rules") +PFCTL_TEST_FAIL(1034, "RDR pool: MAP-E port can't be used with RDR rules") +PFCTL_TEST(1035, "NAT pool: For NAT rules no port specified means default values") +PFCTL_TEST(1036, "NAT pool: Default port numbers are not shown, even if explicitly applied") +PFCTL_TEST(1037, "NAT pool: Single port") +PFCTL_TEST(1038, "NAT pool: Two ports") +PFCTL_TEST(1039, "NAT pool: Static port") +PFCTL_TEST_FAIL(1040, "NAT pool: Static port can't be used with port numbers") +PFCTL_TEST(1041, "NAT pool: MAP-E is displayed using decimal system") +PFCTL_TEST_FAIL(1042, "NAT pool: MAP-E port can't be used with static port") +PFCTL_TEST_FAIL(1043, "NAT pool: MAP-E port can't be used with port numbers") +PFCTL_TEST(1044, "pool: sticky-address is applied on top of round-robin") +PFCTL_TEST(1045, "pool: bitmask is allowed for prefixes") +PFCTL_TEST_FAIL(1046, "pool: bitmask is not allowed for tables") +PFCTL_TEST_FAIL(1047, "pool: bitmask is not allowed for interfaces in brackets") +PFCTL_TEST(1048, "pool: random is allowed for prefixes") +PFCTL_TEST(1049, "pool: round-robin is not set for a single host, even if it looks like a table") +PFCTL_TEST(1050, "pool: round-robin is set automatically for tables") +PFCTL_TEST(1051, "pool: round-robin is set automatically for multiple targets") +PFCTL_TEST(1052, "pool: hosts and table are allowed, round-robin is automatically set") +PFCTL_TEST(1053, "pool: round-robin is not set automatically for prefixes") +PFCTL_TEST(1054, "pool: round-robin is allowed for prefixes") +PFCTL_TEST(1055, "pool: source hash") +PFCTL_TEST(1056, "af-to: from and to") +PFCTL_TEST_IFACE(1057, "Interface translation: IPv4 rule, interface without brackets is translated") +PFCTL_TEST_IFACE(1058, "Interface translation: IPv4 rule, interface without brackets is translated, extra host, round-robin is applied") +PFCTL_TEST_IFACE(1059, "Interface translation: IPv4 rule, interface with brackets is not translated, round-robin is applied") +PFCTL_TEST_IFACE(1060, "Interface translation: IPv4 rule, interface with brackets is not translated, extra host, round-robin is applied") +PFCTL_TEST_IFACE(1061, "Interface translation: IPv6 rule, interface without brackets is translated") +PFCTL_TEST_IFACE(1062, "Interface translation: IPv6 rule, interface without brackets is translated, extra host, round-robin is applied") +PFCTL_TEST_IFACE(1063, "Interface translation: IPv6 rule, interface with brackets is not translated, round-robin is applied") +PFCTL_TEST_IFACE(1064, "Interface translation: IPv6 rule, interface with brackets is not translated, extra host, round robin is applied") +PFCTL_TEST(1065, "no nat") +PFCTL_TEST(1066, "no rdr") +PFCTL_TEST_FAIL(1067, "route-to can't be used on block rules") +PFCTL_TEST(1068, "max-pkt-rate") +PFCTL_TEST(1069, "max-pkt-size") +PFCTL_TEST_FAIL(1070, "include line number") +PFCTL_TEST(1071, "mask length on (lo0)") +PFCTL_TEST_FAIL(1072, "Invalid port range") +PFCTL_TEST(1073, "Filter AF different than route-to AF, with prefer-ipv6-nexthop") +PFCTL_TEST_FAIL(1074, "Filter AF different than route-to AF, without prefer-ipv6-nexthop") +PFCTL_TEST(1075, "One shot rule") |
