Hello,
I have been thinking about a method that would allow userland programs
to create dynamic rules. I have come up with the following:
* Create a new rule type called 'chain'. It has the combined syntax of
pass and block. It is parsed and installed as a normal rule and
defaults to block. It must contain a (unique) label.
* Userland programs address the rule using the label, and add their
own 'chain-rules' to the list of chain-rules contained in the rule.
* A chain-rule contains an address/port pair for souce and destination
of the rule. It can also contain fields for any other customizations
that may be needed, including a hit-count and a timeout.
* When a rule with chain action is hit, the list of chain-rules is
traversed and if a match is found, the rule acts as a 'pass' rule.
If there is no match, it acts as a block rule.
* This idea can be extended to NAT/RDR rules by including a chain flag
and labels.
Advantages:
* Admin can completely control the behaviour of the application that
is going to insert the rule.
* Low overhead.
* Application interface is very simple. Labels used in pf.conf are
passed to the application and application performs a single ioctl
with unknown information set to zero. Application needs no further
knowledge of rules or system.
* Chain-rules expire on a hit count or age. Crashed applications do
not leave rules behind.
Disadvantages:
* NAT/RDR does not have labels yet.
* a better name? proxy/extern/dynamic come to mind.
Implementation:
I have attached a draft implementation. It is for tcp and udp only.
chain-rules are one-shot but have expiration timers. I have
also included a test application that adds chain-rules to rule for a
given port.
Comments ?
Can
Index: pf.c
===================================================================
RCS file: /cvs/src/sys/net/pf.c,v
retrieving revision 1.247
diff -u -p -r1.247 pf.c
--- pf.c 2002/10/05 21:17:57 1.247
+++ pf.c 2002/10/06 15:08:12
@@ -142,6 +142,7 @@ int *pftm_timeouts[PFTM_MAX] = { &pftm
struct pool pf_tree_pl, pf_rule_pl, pf_nat_pl, pf_sport_pl;
struct pool pf_rdr_pl, pf_state_pl, pf_binat_pl, pf_addr_pl;
+struct pool pf_chain_pl, pf_chainlist_pl;
void pf_addrcpy(struct pf_addr *, struct pf_addr *,
u_int8_t);
@@ -615,6 +616,7 @@ pf_purge_timeout(void *arg)
s = splsoftnet();
pf_purge_expired_states();
pf_purge_expired_fragments();
+ pf_purge_expired_chains();
splx(s);
timeout_add(to, pftm_interval * hz);
@@ -659,6 +661,33 @@ pf_purge_expired_states(void)
}
}
+void pf_purge_expired_chains(void)
+{
+ struct pf_rule *rule;
+ struct pf_chain *cur, *prev;
+
+ for (rule = TAILQ_FIRST(pf_rules_active);
+ rule != NULL; rule = TAILQ_NEXT(rule, entries)) {
+ if (rule->action != PF_CHAIN)
+ continue;
+ prev = NULL;
+ cur = LIST_FIRST(rule->chainlist);
+ while (cur != NULL) {
+ if (cur->expire > time.tv_sec) {
+ prev = cur;
+ cur = LIST_NEXT(cur, entries);
+ continue;
+ }
+ LIST_REMOVE(cur, entries);
+ pool_put(&pf_chain_pl, cur);
+ if (prev == NULL)
+ cur = LIST_FIRST(rule->chainlist);
+ else
+ cur = LIST_NEXT(prev, entries);
+ }
+ }
+}
+
int
pf_dynaddr_setup(struct pf_addr_wrap *aw, u_int8_t af)
{
@@ -757,6 +786,46 @@ pf_dynaddr_copyout(struct pf_addr_wrap *
}
void
+pf_chainlist_remove(struct pf_chainlist *clist)
+{
+ struct pf_chain *cr;
+
+ if (clist == NULL)
+ return;
+
+ while ((cr = LIST_FIRST(clist)) != NULL) {
+ LIST_REMOVE(cr, entries);
+ pool_put(&pf_chain_pl, cr);
+ }
+
+ pool_put(&pf_chainlist_pl, clist);
+}
+
+/* assumes label contains space for at least PF_RULE_LABEL_SIZE bytes */
+struct pf_rule *
+pf_get_rule_by_label(char *label)
+{
+ struct pf_rule *r;
+ int n;
+
+ if (label == NULL)
+ return NULL;
+
+ r = TAILQ_FIRST(pf_rules_active);
+ while (r != NULL) {
+ for (n = 0; n < PF_RULE_LABEL_SIZE; n++) {
+ if (label[n] != r->label[n])
+ break;
+ if (label[n] == '\0')
+ return (r);
+ }
+ r = TAILQ_NEXT(r, entries);
+ }
+
+ return (NULL);
+}
+
+void
pf_print_host(struct pf_addr *addr, u_int16_t p, u_int8_t af)
{
switch(af) {
@@ -1605,8 +1674,9 @@ pf_test_tcp(struct pf_rule **rm, int dir
uid_t uid;
gid_t gid;
struct pf_rule *r;
+ struct pf_chain *cr;
u_short reason;
- int rewrite = 0, error;
+ int rewrite = 0, error, action;
*rm = NULL;
@@ -1727,14 +1797,49 @@ pf_test_tcp(struct pf_rule **rm, int dir
(*rm)->packets++;
(*rm)->bytes += pd->tot_len;
REASON_SET(&reason, PFRES_MATCH);
+
+ action = (*rm)->action;
+
+ if (action == PF_CHAIN) {
+ cr = LIST_FIRST((*rm)->chainlist);
+
+ action = PF_DROP;
+
+ while (cr != NULL) {
+ if ((cr->sport == 0 ||
+ cr->sport == th->th_sport) &&
+ (cr->dport == 0 ||
+ cr->dport == th->th_dport) &&
+ (PF_AZERO(&cr->src, af) ||
+ PF_AEQ(&cr->src, saddr, af)) &&
+ (PF_AZERO(&cr->dst, af) ||
+ PF_AEQ(&cr->dst, daddr, af)))
+ break;
+ cr = LIST_NEXT(cr, entries);
+ }
+ if (cr != NULL) {
+ int s;
+ if (cr->expire > time.tv_sec)
+ action = PF_PASS;
+ /* one shot for now can add a hit counter
+ * XXX is splsoftnet here correct?
+ */
+ s = splsoftnet();
+ LIST_REMOVE(cr, entries);
+ pool_put(&pf_chain_pl, cr);
+ splx(s);
+ }
+ }
+
if ((*rm)->log) {
if (rewrite)
m_copyback(m, off, sizeof(*th), (caddr_t)th);
+ /* XXX need to pass action to log */
PFLOG_PACKET(ifp, h, m, af, direction, reason, *rm);
}
- if (((*rm)->action == PF_DROP) &&
+ if ((action == PF_DROP) &&
(((*rm)->rule_flag & PFRULE_RETURNRST) ||
(*rm)->return_icmp)) {
/* undo NAT/RST changes, if they have taken place */
@@ -1757,7 +1862,7 @@ pf_test_tcp(struct pf_rule **rm, int dir
(*rm)->return_icmp & 255, af);
}
- if ((*rm)->action == PF_DROP)
+ if (action == PF_DROP)
return (PF_DROP);
}
@@ -1866,8 +1971,9 @@ pf_test_udp(struct pf_rule **rm, int dir
uid_t uid;
gid_t gid;
struct pf_rule *r;
+ struct pf_chain *cr;
u_short reason;
- int rewrite = 0, error;
+ int action, rewrite = 0, error;
*rm = NULL;
@@ -1990,13 +2096,47 @@ pf_test_udp(struct pf_rule **rm, int dir
(*rm)->bytes += pd->tot_len;
REASON_SET(&reason, PFRES_MATCH);
+ action = (*rm)->action;
+
+ if (action == PF_CHAIN) {
+ cr = LIST_FIRST((*rm)->chainlist);
+
+ action = PF_DROP;
+
+ while (cr != NULL) {
+ if ((cr->sport == 0 ||
+ cr->sport == uh->uh_sport) &&
+ (cr->dport == 0 ||
+ cr->dport == uh->uh_dport) &&
+ (PF_AZERO(&cr->src, af) ||
+ PF_AEQ(&cr->src, saddr, af)) &&
+ (PF_AZERO(&cr->dst, af) ||
+ PF_AEQ(&cr->dst, daddr, af)))
+ break;
+ cr = LIST_NEXT(cr, entries);
+ }
+ if (cr != NULL) {
+ int s;
+
+ if (cr->expire > time.tv_sec)
+ action = PF_PASS;
+ /* one shot for now can add a hit counter
+ * XXX is splsoftnet here correct?
+ */
+ s = splsoftnet();
+ LIST_REMOVE(cr, entries);
+ pool_put(&pf_chain_pl, cr);
+ splx(s);
+ }
+ }
+
if ((*rm)->log) {
if (rewrite)
m_copyback(m, off, sizeof(*uh), (caddr_t)uh);
PFLOG_PACKET(ifp, h, m, af, direction, reason, *rm);
}
- if (((*rm)->action == PF_DROP) && (*rm)->return_icmp) {
+ if ((action == PF_DROP) && (*rm)->return_icmp) {
/* undo NAT/RST changes, if they have taken place */
if (nat != NULL ||
(binat != NULL && direction == PF_OUT)) {
@@ -2013,7 +2153,7 @@ pf_test_udp(struct pf_rule **rm, int dir
(*rm)->return_icmp & 255, af);
}
- if ((*rm)->action == PF_DROP)
+ if (action == PF_DROP)
return (PF_DROP);
}
Index: pfvar.h
===================================================================
RCS file: /cvs/src/sys/net/pfvar.h,v
retrieving revision 1.90
diff -u -p -r1.90 pfvar.h
--- pfvar.h 2002/10/05 21:17:57 1.90
+++ pfvar.h 2002/10/06 15:08:22
@@ -38,7 +38,7 @@
#include <sys/tree.h>
enum { PF_IN=0, PF_OUT=1 };
-enum { PF_PASS=0, PF_DROP=1, PF_SCRUB=2 };
+enum { PF_PASS=0, PF_DROP=1, PF_SCRUB=2, PF_CHAIN=3 };
enum { PF_OP_IRG=1, PF_OP_EQ=2, PF_OP_NE=3, PF_OP_LT=4,
PF_OP_LE=5, PF_OP_GT=6, PF_OP_GE=7, PF_OP_XRG=8 };
enum { PF_DEBUG_NONE=0, PF_DEBUG_URGENT=1, PF_DEBUG_MISC=2 };
@@ -216,6 +216,19 @@ struct pf_rule_addr {
u_int8_t noroute;
};
+struct pf_chain {
+ struct pf_addr src;
+ struct pf_addr dst;
+ LIST_ENTRY(pf_chain) entries;
+ u_int32_t expire;
+ u_int16_t sport;
+ u_int16_t dport;
+ u_int8_t type;
+ u_int8_t code;
+};
+
+LIST_HEAD(pf_chainlist, pf_chain);
+
struct pf_rule {
struct pf_rule_addr src;
struct pf_rule_addr dst;
@@ -242,6 +255,7 @@ struct pf_rule {
u_int64_t packets;
u_int64_t bytes;
+ struct pf_chainlist *chainlist;;
struct ifnet *ifp;
struct ifnet *rt_ifp;
@@ -596,6 +610,11 @@ struct pfioc_limit {
unsigned limit;
};
+struct pfioc_chain {
+ char label[PF_RULE_LABEL_SIZE];
+ struct pf_chain chain;
+};
+
/*
* ioctl operations
*/
@@ -641,7 +660,7 @@ struct pfioc_limit {
#define DIOCGETLIMIT _IOWR('D', 39, struct pfioc_limit)
#define DIOCSETLIMIT _IOWR('D', 40, struct pfioc_limit)
#define DIOCKILLSTATES _IOWR('D', 41, struct pfioc_state_kill)
-
+#define DIOCCHAINRULE _IOWR('D', 42, struct pfioc_chain)
#ifdef _KERNEL
RB_HEAD(pf_state_tree, pf_tree_node);
@@ -682,7 +701,7 @@ extern void pf_calc_skip_steps(struct
extern void pf_dynaddr_copyout(struct pf_addr_wrap *);
extern struct pool pf_tree_pl, pf_rule_pl, pf_nat_pl;
extern struct pool pf_rdr_pl, pf_state_pl, pf_binat_pl,
- pf_addr_pl;
+ pf_addr_pl, pf_chainlist_pl, pf_chain_pl;
extern void pf_purge_timeout(void *);
extern int pftm_interval;
extern int pf_compare_rules(struct pf_rule *,
@@ -701,6 +720,8 @@ extern struct ifnet *status_ifp;
extern int *pftm_timeouts[PFTM_MAX];
extern void pf_addrcpy(struct pf_addr *, struct pf_addr *,
u_int8_t);
+extern void pf_chainlist_remove(struct pf_chainlist *);
+extern void pf_purge_expired_chains(void);
#ifdef INET
int pf_test(int, struct ifnet *, struct mbuf **);
@@ -723,6 +744,8 @@ void pf_normalize_init(void);
int pf_normalize_ip(struct mbuf **, int, struct ifnet *, u_short *);
void pf_purge_expired_fragments(void);
int pf_routable(struct pf_addr *addr, int af);
+
+extern struct pf_rule *pf_get_rule_by_label(char *);
extern struct pf_rulequeue *pf_rules_active;
extern struct pf_status pf_status;
Index: pf_ioctl.c
===================================================================
RCS file: /cvs/src/sys/net/pf_ioctl.c,v
retrieving revision 1.8
diff -u -p -r1.8 pf_ioctl.c
--- pf_ioctl.c 2002/08/12 16:41:25 1.8
+++ pf_ioctl.c 2002/10/06 15:08:30
@@ -89,6 +89,10 @@ pfattach(int num)
NULL);
pool_init(&pf_addr_pl, sizeof(struct pf_addr_dyn), 0, 0, 0, "pfaddr",
NULL);
+ pool_init(&pf_chainlist_pl, sizeof(struct pf_chainlist), 0, 0, 0,
+ "pfchlistpl", NULL);
+ pool_init(&pf_chain_pl, sizeof(struct pf_chain), 0, 0, 0, "pfchainpl",
+ NULL);
TAILQ_INIT(&pf_rules[0]);
TAILQ_INIT(&pf_rules[1]);
@@ -217,6 +221,7 @@ pfioctl(dev_t dev, u_long cmd, caddr_t a
TAILQ_REMOVE(pf_rules_inactive, rule, entries);
pf_dynaddr_remove(&rule->src.addr);
pf_dynaddr_remove(&rule->dst.addr);
+ pf_chainlist_remove(rule->chainlist);
pool_put(&pf_rule_pl, rule);
}
*ticket = ++ticket_rules_inactive;
@@ -282,6 +287,19 @@ pfioctl(dev_t dev, u_long cmd, caddr_t a
error = EINVAL;
break;
}
+ if (rule->action == PF_CHAIN) {
+ /* XXX need validation? */
+ rule->chainlist = pool_get(&pf_chainlist_pl,
+ PR_NOWAIT);
+ if (rule->chainlist == NULL) {
+ pf_dynaddr_remove(&rule->src.addr);
+ pf_dynaddr_remove(&rule->dst.addr);
+ pool_put(&pf_rule_pl, rule);
+ error = ENOMEM;
+ break;
+ }
+ LIST_INIT(rule->chainlist);
+ }
rule->evaluations = rule->packets = rule->bytes = 0;
TAILQ_INSERT_TAIL(pf_rules_inactive, rule, entries);
break;
@@ -317,6 +335,7 @@ pfioctl(dev_t dev, u_long cmd, caddr_t a
TAILQ_REMOVE(old_rules, rule, entries);
pf_dynaddr_remove(&rule->src.addr);
pf_dynaddr_remove(&rule->dst.addr);
+ pf_chainlist_remove(rule->chainlist);
pool_put(&pf_rule_pl, rule);
}
break;
@@ -419,6 +438,19 @@ pfioctl(dev_t dev, u_long cmd, caddr_t a
error = EINVAL;
break;
}
+ if (newrule->action == PF_CHAIN) {
+ /* XXX need validation? */
+ newrule->chainlist = pool_get(&pf_chainlist_pl,
+ PR_NOWAIT);
+ if (newrule->chainlist == NULL) {
+ pf_dynaddr_remove(&newrule->src.addr);
+ pf_dynaddr_remove(&newrule->dst.addr);
+ pool_put(&pf_rule_pl, newrule);
+ error = ENOMEM;
+ break;
+ }
+ LIST_INIT(newrule->chainlist);
+ }
newrule->evaluations = newrule->packets = 0;
newrule->bytes = 0;
}
@@ -450,6 +482,7 @@ pfioctl(dev_t dev, u_long cmd, caddr_t a
TAILQ_REMOVE(pf_rules_active, oldrule, entries);
pf_dynaddr_remove(&oldrule->src.addr);
pf_dynaddr_remove(&oldrule->dst.addr);
+ pf_chainlist_remove(oldrule->chainlist);
pool_put(&pf_rule_pl, oldrule);
} else {
if (oldrule == NULL)
@@ -1476,6 +1509,44 @@ pfioctl(dev_t dev, u_long cmd, caddr_t a
TAILQ_FOREACH(rule, pf_rules_active, entries)
rule->evaluations = rule->packets =
rule->bytes = 0;
+ splx(s);
+ break;
+ }
+
+ case DIOCCHAINRULE: {
+ struct pfioc_chain *pc = (struct pfioc_chain *)addr;
+ struct pf_chain *chain;
+ struct pf_rule *rule;
+
+ s = splsoftnet();
+ rule = pf_get_rule_by_label(pc->label);
+ if (rule == NULL) {
+ error = ENOENT;
+ splx(s);
+ break;
+ }
+
+ if (rule->action != PF_CHAIN) {
+ error = EINVAL;
+ splx(s);
+ break;
+ }
+
+ /* XXX check rule and input chain */
+
+ chain = pool_get(&pf_chain_pl, PR_NOWAIT);
+ if (chain == NULL) {
+ error = ENOMEM;
+ splx(s);
+ break;
+ }
+
+ bcopy(&pc->chain, chain, sizeof(struct pf_chain));
+ chain->expire += time.tv_sec;
+
+ /* XXX does 'entries' need to be cleared before use?
+ bzero(&chain->entries, sizeof(chain->entries)); */
+ LIST_INSERT_HEAD(rule->chainlist, chain, entries);
splx(s);
break;
}
? pfctl
? pfctl.cat8
Index: parse.y
===================================================================
RCS file: /cvs/src/sbin/pfctl/parse.y,v
retrieving revision 1.154
diff -u -p -r1.154 parse.y
--- parse.y 2002/09/22 15:30:15 1.154
+++ parse.y 2002/10/06 15:06:18
@@ -243,7 +243,7 @@ typedef struct {
%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 ROUTETO DUPTO NO LABEL
-%token NOROUTE FRAGMENT USER GROUP MAXMSS MAXIMUM TTL
+%token NOROUTE FRAGMENT USER GROUP MAXMSS MAXIMUM TTL CHAIN
%token FRAGNORM FRAGDROP FRAGCROP
%token SET OPTIMIZATION TIMEOUT LIMIT LOGINTERFACE
%token ANTISPOOF FOR
@@ -542,6 +542,7 @@ pfrule : action dir logquick interface
action : PASS { $$.b1 = PF_PASS; $$.b2 = $$.w = 0; }
| BLOCK blockspec { $$ = $2; $$.b1 = PF_DROP; }
+ | CHAIN blockspec { $$ = $2; $$.b1 = PF_CHAIN; }
;
blockspec : /* empty */ { $$.b2 = 0; $$.w = 0; }
@@ -1709,6 +1710,10 @@ rule_consistent(struct pf_rule *r)
yyerror("keep state on block rules doesn't make sense");
problems++;
}
+ if (r->action == PF_CHAIN && *r->label == '\0') {
+ yyerror("chain rules require a label");
+ problems++;
+ }
return (-problems);
}
@@ -2233,6 +2238,7 @@ lookup(char *s)
{ "any", ANY},
{ "binat", BINAT},
{ "block", BLOCK},
+ { "chain", CHAIN},
{ "code", CODE},
{ "crop", FRAGCROP},
{ "drop-ovl", FRAGDROP},
Index: pfctl_parser.c
===================================================================
RCS file: /cvs/src/sbin/pfctl/pfctl_parser.c,v
retrieving revision 1.94
diff -u -p -r1.94 pfctl_parser.c
--- pfctl_parser.c 2002/07/20 18:58:44 1.94
+++ pfctl_parser.c 2002/10/06 15:06:20
@@ -630,8 +630,8 @@ print_rule(struct pf_rule *r)
printf("@%d ", r->nr);
if (r->action == PF_PASS)
printf("pass ");
- else if (r->action == PF_DROP) {
- printf("block ");
+ else if (r->action == PF_DROP || r->action == PF_CHAIN) {
+ printf("%s ", r->action == PF_DROP ? "block":"chain");
if (r->rule_flag & PFRULE_RETURNRST) {
if (!r->return_ttl)
printf("return-rst ");
#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 <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
int
ioctl_pf_chain_add(int fd, char *label, int port, int expire)
{
struct pfioc_chain c;
bzero(&c.chain, sizeof(c.chain));
c.chain.src.addr32[0]=htonl(0);
c.chain.dst.addr32[0]=htonl(0);
c.chain.sport=htons(0);
c.chain.dport=htons(port);
c.chain.expire=expire;
strncpy(c.label, label, PF_RULE_LABEL_SIZE);
printf("chain label %s:\n", label);
if (ioctl(fd, DIOCCHAINRULE, &c)) {
perror("ioctl");
return 1;
}
return 0;
}
void usage(void)
{
printf("Usage: test <label> <port> <timeout>\n");
exit(1);
}
int
main(int argc, char *argv[])
{
int fd, ret;
if (argc != 4) {
usage();
/* NOTREACHED */
}
fd = open("/dev/pf", O_EXCL | O_RDWR, 0);
if (fd < 0) {
perror("open/rw");
return 1;
}
ret = ioctl_pf_chain_add(fd, argv[1], atoi(argv[2]), atoi(argv[3]));
close(fd);
return ret;
}