58 changes: 48 additions & 10 deletions share/man/man4/pf.4
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.\" $OpenBSD: pf.4,v 1.40 2003/10/04 17:18:56 mcbride Exp $
.\" $OpenBSD: pf.4,v 1.41 2003/12/15 00:02:03 mcbride Exp $
.\"
.\" Copyright (C) 2001, Kjell Wooding. All rights reserved.
.\"
Expand Down Expand Up @@ -246,15 +246,17 @@ Specifies the interface for which statistics are accumulated.
.It Dv DIOCGETSTATUS Fa "struct pf_status"
.Bd -literal
struct pf_status {
u_int64_t counters[PFRES_MAX];
u_int64_t fcounters[FCNT_MAX];
u_int64_t pcounters[2][2][3];
u_int64_t bcounters[2][2];
u_int32_t running;
u_int32_t states;
u_int32_t since;
u_int32_t debug;
char ifname[IFNAMSIZ];
u_int64_t counters[PFRES_MAX];
u_int64_t fcounters[FCNT_MAX];
u_int64_t scounters[SCNT_MAX];
u_int64_t pcounters[2][2][3];
u_int64_t bcounters[2][2];
u_int32_t running;
u_int32_t states;
u_int32_t src_nodes;
u_int32_t since;
u_int32_t debug;
char ifname[IFNAMSIZ];
};
.Ed
.Pp
Expand Down Expand Up @@ -638,6 +640,42 @@ The rest of the structure members will come back filled.
Get the whole list by repeatedly incrementing the
.Va fp_getnum
number until the ioctl returns EBUSY.
.It Dv DIOCGETSRCNODES Fa "struct pfioc_src_nodes"
.Bd -literal
struct pfioc_src_nodes {
int psn_len;
union {
caddr_t psu_buf;
struct pf_src_node *psu_src_nodes;
} psn_u;
#define psn_buf psn_u.psu_buf
#define psn_src_nodes psn_u.psu_src_nodes
};
.Ed
.Pp
Get the list of source nodes kept by the
.Ar sticky-address
and
.Ar source-track
options.
The ioctl must be called once with
.Va psn_len
set to 0,
If the ioctl returns without error,
.Va psn_len
will be set to the size of the buffer required to hold all the
.Va pf_src_node
structures held in the table.
A buffer of this size should then be allocated, and a pointer to this buffer
placed in
.Va psn_buf .
The ioctl must then be called again to fill this buffer with the actual
source node data.
After the ioctl call
.Va psn_len
will be set to the length of the buffer actually used.
.It Dv DIOCCLRSRCNODES Fa "struct pfioc_table"
Clear the tree of source tracking nodes.
.El
.Sh EXAMPLES
The following example demonstrates how to use the DIOCNATLOOK command
Expand Down
51 changes: 44 additions & 7 deletions share/man/man5/pf.conf.5
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.\" $OpenBSD: pf.conf.5,v 1.284 2003/11/29 10:05:55 dhartmei Exp $
.\" $OpenBSD: pf.conf.5,v 1.285 2003/12/15 00:02:03 mcbride Exp $
.\"
.\" Copyright (c) 2002, Daniel Hartmeier
.\" All rights reserved.
Expand Down Expand Up @@ -234,6 +234,9 @@ command.
Interval between purging expired states and fragments.
.It Ar frag
Seconds before an unassembled fragment is expired.
.It Ar src.track
Length of time to retain a source-tracking entry after the last state
expires.
.El
.Pp
When a packet matches a stateful connection, the seconds to live for the
Expand Down Expand Up @@ -1560,6 +1563,24 @@ option prevents
.Xr pf 4
from modifying the source port on TCP and UDP packets.
.El
.Pp
Additionally, the
.Ar sticky-address
option can be specified to help ensure that multiple connections from the
same source are mapped to the same redirection address. This option can be
used with the
.Ar random
and
.Ar round-robin
pool options.
Note that by default these associations are destroyed as soon as there are
no longer states which refer to them; in order to make the mappings last
beyond the lifetime of the states, increase the global options with
.Ar set timeout source-track
See
.Sx STATEFUL TRACKING OPTIONS
for more ways to control the source tracking.

.Sh STATEFUL INSPECTION
.Xr pf 4
is a stateful packet filter, which means it can track the state of
Expand Down Expand Up @@ -1763,17 +1784,31 @@ Prevent state changes for states created by this rule from appearing on the
interface.
.It Ar <timeout> <seconds>
Changes the timeout values used for states created by this rule.
.Pp
When the
.Ar source-tracking
keyword is specified, the number of states per source ip is tracked.
The following limits can be set:
.Pp
.Bl -tag -width xxxx -compact
.It Ar max-src-nodes
Limits the maximum number of source addresses which can simultaneously
have state table entries.
.It Ar max-src-states
Limits the maximum number of simultaneous state entries that a single
source address can greate with this rule.
.El
For a list of all valid timeout names, see
.Sx OPTIONS
above.
.Pp
Multiple options can be specified, separated by commas:
.Bd -literal
pass in proto tcp from any to any \e
pass in proto tcp from any to any
port www flags S/SA keep state \e
(max 100, tcp.established 60, tcp.closing 5)
(max 100, source-track rule, max-src-nodes 75, \e
max-src-states 3, tcp.established 60, tcp.closing 5)
.Ed
.El
.Sh OPERATING SYSTEM FINGERPRINTING
Passive OS Fingerprinting is a mechanism to inspect nuances of a TCP
connection's initial SYN packet and guess at the host's operating system.
Expand Down Expand Up @@ -2446,7 +2481,9 @@ tos = "tos" ( "lowdelay" | "throughput" | "reliability" |
[ "0x" ] number )

state-opts = state-opt [ [ "," ] state-opts ]
state-opt = ( "max" number | "no-sync" | timeout )
state-opt = ( "max" number | "no-sync" | timeout |
"source-track" [ ( "rule" | "global" ) ] |
"max-src-nodes" number | "max-src-states" number)

fragmentation = [ "fragment reassemble" | "fragment crop" |
"fragment drop-ovl" ]
Expand All @@ -2457,15 +2494,15 @@ timeout = ( "tcp.first" | "tcp.opening" | "tcp.established" |
"udp.first" | "udp.single" | "udp.multiple" |
"icmp.first" | "icmp.error" |
"other.first" | "other.single" | "other.multiple" |
"frag" | "interval" |
"frag" | "interval" | "src.track" |
"adaptive.start" | "adaptive.end" ) number

limit-list = limit-item [ [ "," ] limit-list ]
limit-item = ( "states" | "frags" ) number

pooltype = ( "bitmask" | "random" |
"source-hash" [ ( hex-key | string-key ) ] |
"round-robin" )
"round-robin" ) [ sticky-address ]

subqueue = string | "{" queue-list "}"
queue-list = string [ [ "," ] string ]
Expand Down
661 changes: 503 additions & 158 deletions sys/net/pf.c

Large diffs are not rendered by default.

97 changes: 89 additions & 8 deletions sys/net/pf_ioctl.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* $OpenBSD: pf_ioctl.c,v 1.88 2003/12/12 20:05:45 cedric Exp $ */
/* $OpenBSD: pf_ioctl.c,v 1.89 2003/12/15 00:02:04 mcbride Exp $ */

/*
* Copyright (c) 2001 Daniel Hartmeier
Expand Down Expand Up @@ -106,6 +106,8 @@ pfattach(int num)
&pool_allocator_nointr);
pool_init(&pf_addr_pl, sizeof(struct pf_addr_dyn), 0, 0, 0, "pfaddrpl",
&pool_allocator_nointr);
pool_init(&pf_src_tree_pl, sizeof(struct pf_src_node), 0, 0, 0,
"pfsrctrpl", NULL);
pool_init(&pf_state_pl, sizeof(struct pf_state), 0, 0, 0, "pfstatepl",
NULL);
pool_init(&pf_altq_pl, sizeof(struct pf_altq), 0, 0, 0, "pfaltqpl",
Expand All @@ -120,6 +122,7 @@ pfattach(int num)

RB_INIT(&tree_lan_ext);
RB_INIT(&tree_ext_gwy);
RB_INIT(&tree_src_tracking);
TAILQ_INIT(&pf_anchors);
pf_init_ruleset(&pf_main_ruleset);
TAILQ_INIT(&pf_altqs[0]);
Expand Down Expand Up @@ -150,11 +153,13 @@ pfattach(int num)
timeout[PFTM_OTHER_MULTIPLE] = 60; /* Bidirectional */
timeout[PFTM_FRAG] = 30; /* Fragment expire */
timeout[PFTM_INTERVAL] = 10; /* Expire interval */
timeout[PFTM_SRC_NODE] = 0; /* Source tracking */

timeout_set(&pf_expire_to, pf_purge_timeout, &pf_expire_to);
timeout_add(&pf_expire_to, timeout[PFTM_INTERVAL] * hz);

pf_normalize_init();
bzero(&pf_status, sizeof(pf_status));
pf_status.debug = PF_DEBUG_URGENT;
}

Expand Down Expand Up @@ -414,7 +419,9 @@ pf_rm_rule(struct pf_rulequeue *rulequeue, struct pf_rule *rule)
rule->entries.tqe_prev = NULL;
rule->nr = -1;
}
if (rule->states > 0 || rule->entries.tqe_prev != NULL)

if (rule->states > 0 || rule->src_nodes > 0 ||
rule->entries.tqe_prev != NULL)
return;
pf_tag_unref(rule->tag);
pf_tag_unref(rule->match_tag);
Expand Down Expand Up @@ -732,6 +739,8 @@ pfioctl(dev_t dev, u_long cmd, caddr_t addr, int flags, struct proc *p)
case DIOCRCLRASTATS:
case DIOCRTSTADDRS:
case DIOCOSFPGET:
case DIOCGETSRCNODES:
case DIOCCLRSRCNODES:
break;
default:
return (EPERM);
Expand Down Expand Up @@ -761,6 +770,7 @@ pfioctl(dev_t dev, u_long cmd, caddr_t addr, int flags, struct proc *p)
case DIOCRGETASTATS:
case DIOCRTSTADDRS:
case DIOCOSFPGET:
case DIOCGETSRCNODES:
break;
default:
return (EACCES);
Expand All @@ -774,10 +784,12 @@ pfioctl(dev_t dev, u_long cmd, caddr_t addr, int flags, struct proc *p)
else {
u_int32_t states = pf_status.states;
u_int32_t debug = pf_status.debug;
u_int32_t src_nodes = pf_status.src_nodes;
bzero(&pf_status, sizeof(struct pf_status));
pf_status.running = 1;
pf_status.states = states;
pf_status.debug = debug;
pf_status.states = src_nodes;
pf_status.since = time.tv_sec;
if (status_ifp != NULL)
strlcpy(pf_status.ifname,
Expand Down Expand Up @@ -847,6 +859,7 @@ pfioctl(dev_t dev, u_long cmd, caddr_t addr, int flags, struct proc *p)
TAILQ_INIT(&rule->rpool.list);
/* initialize refcounting */
rule->states = 0;
rule->src_nodes = 0;
rule->entries.tqe_prev = NULL;
#ifndef INET
if (rule->af == AF_INET) {
Expand Down Expand Up @@ -1363,12 +1376,14 @@ pfioctl(dev_t dev, u_long cmd, caddr_t addr, int flags, struct proc *p)
case DIOCCLRSTATUS: {
u_int32_t running = pf_status.running;
u_int32_t states = pf_status.states;
u_int32_t src_nodes = pf_status.src_nodes;
u_int32_t since = pf_status.since;
u_int32_t debug = pf_status.debug;

bzero(&pf_status, sizeof(struct pf_status));
pf_status.running = running;
pf_status.states = states;
pf_status.src_nodes = src_nodes;
pf_status.since = since;
pf_status.debug = debug;
if (status_ifp != NULL)
Expand Down Expand Up @@ -1488,6 +1503,8 @@ pfioctl(dev_t dev, u_long cmd, caddr_t addr, int flags, struct proc *p)
}
old_limit = pf_pool_limits[pl->index].limit;
pf_pool_limits[pl->index].limit = pl->limit;
if (pl->index == PF_LIMIT_SRC_NODES)
pf_default_rule.max_src_nodes = pl->limit;
pl->limit = old_limit;
break;
}
Expand Down Expand Up @@ -2204,12 +2221,6 @@ pfioctl(dev_t dev, u_long cmd, caddr_t addr, int flags, struct proc *p)
break;
}

case DIOCOSFPFLUSH:
s = splsoftnet();
pf_osfp_flush();
splx(s);
break;

case DIOCOSFPADD: {
struct pf_osfp_ioctl *io = (struct pf_osfp_ioctl *)addr;
s = splsoftnet();
Expand Down Expand Up @@ -2411,6 +2422,76 @@ pfioctl(dev_t dev, u_long cmd, caddr_t addr, int flags, struct proc *p)
break;
}

case DIOCGETSRCNODES: {
struct pfioc_src_nodes *psn = (struct pfioc_src_nodes *)addr;
struct pf_src_node *n;
struct pf_src_node *p, pstore;
u_int32_t nr = 0;
int space = psn->psn_len;

if (space == 0) {
s = splsoftnet();
RB_FOREACH(n, pf_src_tree, &tree_src_tracking)
nr++;
splx(s);
psn->psn_len = sizeof(struct pf_src_node) * nr;
return (0);
}

s = splsoftnet();
p = psn->psn_src_nodes;
RB_FOREACH(n, pf_src_tree, &tree_src_tracking) {
int secs = time.tv_sec;

if ((nr + 1) * sizeof(*p) > (unsigned)psn->psn_len)
break;

bcopy(n, &pstore, sizeof(pstore));
if (n->rule.ptr != NULL)
pstore.rule.nr = n->rule.ptr->nr;
pstore.creation = secs - pstore.creation;
if (pstore.expire > secs)
pstore.expire -= secs;
else
pstore.expire = 0;
error = copyout(&pstore, p, sizeof(*p));
if (error) {
splx(s);
goto fail;
}
p++;
nr++;
}
psn->psn_len = sizeof(struct pf_src_node) * nr;
splx(s);
break;
}

case DIOCCLRSRCNODES: {
struct pf_src_node *n;
struct pf_state *state;

s = splsoftnet();
RB_FOREACH(state, pf_state_tree_lan_ext, &tree_lan_ext) {
state->src_node = NULL;
state->nat_src_node = NULL;
}
RB_FOREACH(n, pf_src_tree, &tree_src_tracking) {
n->expire = 1;
n->states = 0;
}
pf_purge_expired_src_nodes();
pf_status.src_nodes = 0;
splx(s);
break;
}

case DIOCOSFPFLUSH:
s = splsoftnet();
pf_osfp_flush();
splx(s);
break;

default:
error = ENODEV;
break;
Expand Down
62 changes: 56 additions & 6 deletions sys/net/pfvar.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* $OpenBSD: pfvar.h,v 1.176 2003/12/12 20:05:45 cedric Exp $ */
/* $OpenBSD: pfvar.h,v 1.177 2003/12/15 00:02:04 mcbride Exp $ */

/*
* Copyright (c) 2001 Daniel Hartmeier
Expand Down Expand Up @@ -68,16 +68,17 @@ enum { PFTM_TCP_FIRST_PACKET, PFTM_TCP_OPENING, PFTM_TCP_ESTABLISHED,
PFTM_ICMP_FIRST_PACKET, PFTM_ICMP_ERROR_REPLY,
PFTM_OTHER_FIRST_PACKET, PFTM_OTHER_SINGLE,
PFTM_OTHER_MULTIPLE, PFTM_FRAG, PFTM_INTERVAL,
PFTM_ADAPTIVE_START, PFTM_ADAPTIVE_END, PFTM_MAX,
PFTM_PURGE, PFTM_UNTIL_PACKET };
PFTM_ADAPTIVE_START, PFTM_ADAPTIVE_END, PFTM_SRC_NODE,
PFTM_MAX, PFTM_PURGE, PFTM_UNTIL_PACKET };
enum { PF_NOPFROUTE, PF_FASTROUTE, PF_ROUTETO, PF_DUPTO, PF_REPLYTO };
enum { PF_LIMIT_STATES, PF_LIMIT_FRAGS, PF_LIMIT_MAX };
enum { PF_LIMIT_STATES, PF_LIMIT_SRC_NODES, PF_LIMIT_FRAGS, PF_LIMIT_MAX };
#define PF_POOL_IDMASK 0x0f
enum { PF_POOL_NONE, PF_POOL_BITMASK, PF_POOL_RANDOM,
PF_POOL_SRCHASH, PF_POOL_ROUNDROBIN };
enum { PF_ADDR_ADDRMASK, PF_ADDR_NOROUTE, PF_ADDR_DYNIFTL,
PF_ADDR_TABLE };
#define PF_POOL_TYPEMASK 0x0f
#define PF_POOL_STICKYADDR 0x20
#define PF_WSCALE_FLAG 0x80
#define PF_WSCALE_MASK 0x0f

Expand Down Expand Up @@ -447,7 +448,6 @@ struct pf_rule {
union pf_rule_ptr skip[PF_SKIP_COUNT];
#define PF_RULE_LABEL_SIZE 64
char label[PF_RULE_LABEL_SIZE];
u_int32_t timeout[PFTM_MAX];
#define PF_QNAME_SIZE 16
char ifname[IFNAMSIZ];
char qname[PF_QNAME_SIZE];
Expand All @@ -469,8 +469,13 @@ struct pf_rule {
struct pf_anchor *anchor;

pf_osfp_t os_fingerprint;

u_int32_t timeout[PFTM_MAX];
u_int32_t states;
u_int32_t max_states;
u_int32_t src_nodes;
u_int32_t max_src_nodes;
u_int32_t max_src_states;
u_int32_t qid;
u_int32_t pqid;
u_int32_t rt_listid;
Expand Down Expand Up @@ -518,6 +523,8 @@ struct pf_rule {
#define PFRULE_RETURNICMP 0x0004
#define PFRULE_RETURN 0x0008
#define PFRULE_NOSYNC 0x0010
#define PFRULE_SRCTRACK 0x0020 /* track source states */
#define PFRULE_RULESRCTRACK 0x0040 /* per rule */

/* scrub flags */
#define PFRULE_NODF 0x0100
Expand All @@ -528,6 +535,20 @@ struct pf_rule {

#define PFSTATE_HIWAT 10000 /* default state table size */

struct pf_src_node {
RB_ENTRY(pf_src_node) entry;
struct pf_addr addr;
struct pf_addr raddr;
union pf_rule_ptr rule;
struct ifnet *ifp;
u_int32_t bytes;
u_int32_t packets;
u_int32_t states;
u_int32_t creation;
u_int32_t expire;
sa_family_t af;
u_int8_t ruletype;
};

struct pf_state_scrub {
u_int16_t pfss_flags;
Expand Down Expand Up @@ -567,6 +588,8 @@ struct pf_state {
union pf_rule_ptr nat_rule;
struct pf_addr rt_addr;
struct ifnet *rt_ifp;
struct pf_src_node *src_node;
struct pf_src_node *nat_src_node;
u_int32_t creation;
u_int32_t expire;
u_int32_t packets[2];
Expand Down Expand Up @@ -796,6 +819,10 @@ struct pf_pdesc {
#define FCNT_STATE_REMOVALS 2
#define FCNT_MAX 3

#define SCNT_SRC_NODE_SEARCH 0
#define SCNT_SRC_NODE_INSERT 1
#define SCNT_SRC_NODE_REMOVALS 2
#define SCNT_MAX 3

#define ACTION_SET(a, x) \
do { \
Expand All @@ -814,10 +841,12 @@ struct pf_pdesc {
struct pf_status {
u_int64_t counters[PFRES_MAX];
u_int64_t fcounters[FCNT_MAX];
u_int64_t scounters[SCNT_MAX];
u_int64_t pcounters[2][2][3];
u_int64_t bcounters[2][2];
u_int32_t running;
u_int32_t states;
u_int32_t src_nodes;
u_int32_t since;
u_int32_t debug;
char ifname[IFNAMSIZ];
Expand Down Expand Up @@ -963,6 +992,16 @@ struct pfioc_states {
#define ps_states ps_u.psu_states
};

struct pfioc_src_nodes {
int psn_len;
union {
caddr_t psu_buf;
struct pf_src_node *psu_src_nodes;
} psn_u;
#define psn_buf psn_u.psu_buf
#define psn_src_nodes psn_u.psu_src_nodes
};

struct pfioc_if {
char ifname[IFNAMSIZ];
};
Expand Down Expand Up @@ -1116,9 +1155,15 @@ struct pfioc_table {
#define DIOCXBEGIN _IOWR('D', 81, struct pfioc_trans)
#define DIOCXCOMMIT _IOWR('D', 82, struct pfioc_trans)
#define DIOCXROLLBACK _IOWR('D', 83, struct pfioc_trans)
#define DIOCGETSRCNODES _IOWR('D', 84, struct pfioc_src_nodes)
#define DIOCCLRSRCNODES _IO('D', 85)


#ifdef _KERNEL
RB_HEAD(pf_src_tree, pf_src_node);
RB_PROTOTYPE(pf_src_tree, pf_src_node, entry, pf_src_compare);
extern struct pf_src_tree tree_src_tracking;

RB_HEAD(pf_state_tree_lan_ext, pf_state);
RB_PROTOTYPE(pf_state_tree_lan_ext, pf_state,
entry_lan_ext, pf_state_compare_lan_ext);
Expand Down Expand Up @@ -1154,12 +1199,17 @@ extern void pf_calc_skip_steps(struct pf_rulequeue *);
extern void pf_rule_set_qid(struct pf_rulequeue *);
extern u_int32_t pf_qname_to_qid(char *);
extern void pf_update_anchor_rules(void);
extern struct pool pf_rule_pl, pf_addr_pl;
extern struct pool pf_src_tree_pl, pf_rule_pl, pf_addr_pl;
extern struct pool pf_state_pl, pf_altq_pl, pf_pooladdr_pl;
extern struct pool pf_state_scrub_pl;
extern void pf_purge_timeout(void *);
extern void pf_purge_expired_src_nodes(void);
extern void pf_purge_expired_states(void);
extern int pf_insert_state(struct pf_state *);
extern int pf_insert_src_node(struct pf_src_node **,
struct pf_rule *, struct pf_addr *,
sa_family_t);
void pf_src_tree_remove_state(struct pf_state *);
extern struct pf_state *pf_find_state(struct pf_state *, u_int8_t);
extern struct pf_anchor *pf_find_anchor(const char *);
extern struct pf_ruleset *pf_find_ruleset(char *, char *);
Expand Down