[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

RFC: dynamic rules



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;
}