pf: Add pfsync protocol for FreeBSD 15

A new version of pfsync packet is introduced: 1500. This version solves
the issues with data alignment introduced in version 1400 and adds syncing
of information needed to sync states created by rules with af-to (original
interface, af and proto separate for wire and stack keys), of rt_af
needed for prefer-ipv6-nexthop, and of tag names.

Reviewed by:	kp
Sponsored by:	InnoGames GmbH
Differential Revision:	https://reviews.freebsd.org/D52176
This commit is contained in:
Kajetan Staszkiewicz 2025-08-22 12:44:20 +02:00
parent 42441d3420
commit 99475087d6
8 changed files with 883 additions and 103 deletions

View file

@ -102,6 +102,7 @@ struct pfsync_actions {
static void pfsync_print_clr(netdissect_options *, const void *);
static void pfsync_print_state_1301(netdissect_options *, const void *);
static void pfsync_print_state_1400(netdissect_options *, const void *);
static void pfsync_print_state_1500(netdissect_options *, const void *);
static void pfsync_print_ins_ack(netdissect_options *, const void *);
static void pfsync_print_upd_c(netdissect_options *, const void *);
static void pfsync_print_upd_req(netdissect_options *, const void *);
@ -131,6 +132,8 @@ struct pfsync_actions actions[] = {
{ "eof", 0, NULL },
{ "insert", sizeof(struct pfsync_state_1400), pfsync_print_state_1400 },
{ "update", sizeof(struct pfsync_state_1400), pfsync_print_state_1400 },
{ "insert", sizeof(struct pfsync_state_1500), pfsync_print_state_1500 },
{ "update", sizeof(struct pfsync_state_1500), pfsync_print_state_1500 },
};
static void
@ -228,12 +231,21 @@ pfsync_print_state_1301(netdissect_options *ndo, const void *bp)
static void
pfsync_print_state_1400(netdissect_options *ndo, const void *bp)
{
struct pfsync_state_1301 *st = (struct pfsync_state_1301 *)bp;
struct pfsync_state_1400 *st = (struct pfsync_state_1400 *)bp;
fn_print_char(ndo, '\n');
print_state(ndo, (union pfsync_state_union *)st, PFSYNC_MSG_VERSION_1400);
}
static void
pfsync_print_state_1500(netdissect_options *ndo, const void *bp)
{
struct pfsync_state_1500 *st = (struct pfsync_state_1500 *)bp;
fn_print_char(ndo, '\n');
print_state(ndo, (union pfsync_state_union *)st, PFSYNC_MSG_VERSION_1500);
}
static void
pfsync_print_ins_ack(netdissect_options *ndo, const void *bp)
{

View file

@ -162,6 +162,8 @@ FreeBSD releases 13.2 and older.
Compatibility with FreeBSD 13.1 has been verified.
.It Cm 1400
FreeBSD release 14.0.
.It Cm 1500
FreeBSD release 15.0.
.El
.Sh SYSCTL VARIABLES
The following variables can be entered at the

View file

@ -62,9 +62,10 @@ enum pfsync_msg_versions {
PFSYNC_MSG_VERSION_UNSPECIFIED = 0,
PFSYNC_MSG_VERSION_1301 = 1301,
PFSYNC_MSG_VERSION_1400 = 1400,
PFSYNC_MSG_VERSION_1500 = 1500,
};
#define PFSYNC_MSG_VERSION_DEFAULT PFSYNC_MSG_VERSION_1400
#define PFSYNC_MSG_VERSION_DEFAULT PFSYNC_MSG_VERSION_1500
#define PFSYNC_ACT_CLR 0 /* clear all states */
#define PFSYNC_ACT_INS_1301 1 /* insert state */
@ -81,7 +82,9 @@ enum pfsync_msg_versions {
#define PFSYNC_ACT_EOF 12 /* end of frame */
#define PFSYNC_ACT_INS_1400 13 /* insert state */
#define PFSYNC_ACT_UPD_1400 14 /* update state */
#define PFSYNC_ACT_MAX 15
#define PFSYNC_ACT_INS_1500 15 /* insert state */
#define PFSYNC_ACT_UPD_1500 16 /* update state */
#define PFSYNC_ACT_MAX 17
/*
* A pfsync frame is built from a header followed by several sections which

View file

@ -452,6 +452,16 @@ VNET_DECLARE(struct rmlock, pf_rules_lock);
#define PF_RULES_RASSERT() rm_assert(&V_pf_rules_lock, RA_RLOCKED)
#define PF_RULES_WASSERT() rm_assert(&V_pf_rules_lock, RA_WLOCKED)
VNET_DECLARE(struct rmlock, pf_tags_lock);
#define V_pf_tags_lock VNET(pf_tags_lock)
#define PF_TAGS_RLOCK_TRACKER struct rm_priotracker _pf_tags_tracker
#define PF_TAGS_RLOCK() rm_rlock(&V_pf_tags_lock, &_pf_tags_tracker)
#define PF_TAGS_RUNLOCK() rm_runlock(&V_pf_tags_lock, &_pf_tags_tracker)
#define PF_TAGS_WLOCK() rm_wlock(&V_pf_tags_lock)
#define PF_TAGS_WUNLOCK() rm_wunlock(&V_pf_tags_lock)
#define PF_TAGS_WASSERT() rm_assert(&V_pf_tags_lock, RA_WLOCKED)
extern struct mtx_padalign pf_table_stats_lock;
#define PF_TABLE_STATS_LOCK() mtx_lock(&pf_table_stats_lock)
#define PF_TABLE_STATS_UNLOCK() mtx_unlock(&pf_table_stats_lock)
@ -1209,11 +1219,11 @@ struct pfsync_state_1301 {
u_int8_t state_flags;
u_int8_t timeout;
u_int8_t sync_flags;
u_int8_t updates;
u_int8_t updates; /* unused */
} __packed;
struct pfsync_state_1400 {
/* The beginning of the struct is compatible with previous versions */
/* The beginning of the struct is compatible with pfsync_state_1301 */
u_int64_t id;
char ifname[IFNAMSIZ];
struct pfsync_state_key key[2];
@ -1236,7 +1246,7 @@ struct pfsync_state_1400 {
u_int8_t __spare;
u_int8_t timeout;
u_int8_t sync_flags;
u_int8_t updates;
u_int8_t updates; /* unused */
/* The rest is not */
u_int16_t qid;
u_int16_t pqid;
@ -1249,12 +1259,54 @@ struct pfsync_state_1400 {
u_int8_t set_prio[2];
u_int8_t rt;
char rt_ifname[IFNAMSIZ];
} __packed;
struct pfsync_state_1500 {
/* The beginning of the struct is compatible with pfsync_state_1301 */
u_int64_t id;
char ifname[IFNAMSIZ];
struct pfsync_state_key key[2];
struct pf_state_peer_export src;
struct pf_state_peer_export dst;
struct pf_addr rt_addr;
u_int32_t rule;
u_int32_t anchor;
u_int32_t nat_rule;
u_int32_t creation;
u_int32_t expire;
u_int32_t packets[2][2];
u_int32_t bytes[2][2];
u_int32_t creatorid;
/* The rest is not, use the opportunity to fix alignment */
char tagname[PF_TAG_NAME_SIZE];
char rt_ifname[IFNAMSIZ];
char orig_ifname[IFNAMSIZ];
int32_t rtableid;
u_int16_t state_flags;
u_int16_t qid;
u_int16_t pqid;
u_int16_t dnpipe;
u_int16_t dnrpipe;
u_int16_t max_mss;
sa_family_t wire_af;
sa_family_t stack_af;
sa_family_t rt_af;
u_int8_t wire_proto;
u_int8_t stack_proto;
u_int8_t log;
u_int8_t timeout;
u_int8_t direction;
u_int8_t rt;
u_int8_t min_ttl;
u_int8_t set_tos;
u_int8_t set_prio[2];
u_int8_t spare[3]; /* Improve struct alignment */
} __packed;
union pfsync_state_union {
struct pfsync_state_1301 pfs_1301;
struct pfsync_state_1400 pfs_1400;
struct pfsync_state_1500 pfs_1500;
} __packed;
#ifdef _KERNEL
@ -2462,6 +2514,10 @@ int pf_translate(struct pf_pdesc *, struct pf_addr *, u_int16_t,
struct pf_addr *, u_int16_t, u_int16_t, int);
int pf_translate_af(struct pf_pdesc *);
bool pf_init_threshold(struct pf_kthreshold *, uint32_t, uint32_t);
uint16_t pf_tagname2tag(const char *);
#ifdef ALTQ
uint16_t pf_qname2qid(const char *, bool);
#endif /* ALTQ */
void pfr_initialize(void);
void pfr_cleanup(void);

View file

@ -153,6 +153,8 @@ static int (*pfsync_acts[])(struct mbuf *, int, int, int, int) = {
pfsync_in_eof, /* PFSYNC_ACT_EOF */
pfsync_in_ins, /* PFSYNC_ACT_INS_1400 */
pfsync_in_upd, /* PFSYNC_ACT_UPD_1400 */
pfsync_in_ins, /* PFSYNC_ACT_INS_1500 */
pfsync_in_upd, /* PFSYNC_ACT_UPD_1500 */
};
struct pfsync_q {
@ -165,9 +167,11 @@ struct pfsync_q {
enum pfsync_q_id {
PFSYNC_Q_INS_1301,
PFSYNC_Q_INS_1400,
PFSYNC_Q_INS_1500,
PFSYNC_Q_IACK,
PFSYNC_Q_UPD_1301,
PFSYNC_Q_UPD_1400,
PFSYNC_Q_UPD_1500,
PFSYNC_Q_UPD_C,
PFSYNC_Q_DEL_C,
PFSYNC_Q_COUNT,
@ -176,6 +180,7 @@ enum pfsync_q_id {
/* Functions for building messages for given queue */
static void pfsync_out_state_1301(struct pf_kstate *, void *);
static void pfsync_out_state_1400(struct pf_kstate *, void *);
static void pfsync_out_state_1500(struct pf_kstate *, void *);
static void pfsync_out_iack(struct pf_kstate *, void *);
static void pfsync_out_upd_c(struct pf_kstate *, void *);
static void pfsync_out_del_c(struct pf_kstate *, void *);
@ -184,9 +189,11 @@ static void pfsync_out_del_c(struct pf_kstate *, void *);
static struct pfsync_q pfsync_qs[] = {
{ pfsync_out_state_1301, sizeof(struct pfsync_state_1301), PFSYNC_ACT_INS_1301 },
{ pfsync_out_state_1400, sizeof(struct pfsync_state_1400), PFSYNC_ACT_INS_1400 },
{ pfsync_out_state_1500, sizeof(struct pfsync_state_1500), PFSYNC_ACT_INS_1500 },
{ pfsync_out_iack, sizeof(struct pfsync_ins_ack), PFSYNC_ACT_INS_ACK },
{ pfsync_out_state_1301, sizeof(struct pfsync_state_1301), PFSYNC_ACT_UPD_1301 },
{ pfsync_out_state_1400, sizeof(struct pfsync_state_1400), PFSYNC_ACT_UPD_1400 },
{ pfsync_out_state_1500, sizeof(struct pfsync_state_1500), PFSYNC_ACT_UPD_1500 },
{ pfsync_out_upd_c, sizeof(struct pfsync_upd_c), PFSYNC_ACT_UPD_C },
{ pfsync_out_del_c, sizeof(struct pfsync_del_c), PFSYNC_ACT_DEL_C }
};
@ -195,9 +202,11 @@ static struct pfsync_q pfsync_qs[] = {
static u_int8_t pfsync_qid_sstate[] = {
PFSYNC_S_INS, /* PFSYNC_Q_INS_1301 */
PFSYNC_S_INS, /* PFSYNC_Q_INS_1400 */
PFSYNC_S_INS, /* PFSYNC_Q_INS_1500 */
PFSYNC_S_IACK, /* PFSYNC_Q_IACK */
PFSYNC_S_UPD, /* PFSYNC_Q_UPD_1301 */
PFSYNC_S_UPD, /* PFSYNC_Q_UPD_1400 */
PFSYNC_S_UPD, /* PFSYNC_Q_UPD_1500 */
PFSYNC_S_UPD_C, /* PFSYNC_Q_UPD_C */
PFSYNC_S_DEL_C, /* PFSYNC_Q_DEL_C */
};
@ -525,13 +534,15 @@ pfsync_state_import(union pfsync_state_union *sp, int flags, int msg_version)
struct pf_kstate *st = NULL;
struct pf_state_key *skw = NULL, *sks = NULL;
struct pf_krule *r = NULL;
struct pfi_kkif *kif;
struct pfi_kkif *kif, *orig_kif;
struct pfi_kkif *rt_kif = NULL;
struct pf_kpooladdr *rpool_first;
int error;
int n = 0;
sa_family_t rt_af = 0;
uint8_t rt = 0;
int n = 0;
sa_family_t wire_af, stack_af;
u_int8_t wire_proto, stack_proto;
PF_RULES_RASSERT();
@ -542,7 +553,11 @@ pfsync_state_import(union pfsync_state_union *sp, int flags, int msg_version)
return (EINVAL);
}
if ((kif = pfi_kkif_find(sp->pfs_1301.ifname)) == NULL) {
/*
* Check interfaces early on. Do it before allocating memory etc.
* Because there is a high chance there will be a lot more such states.
*/
if ((kif = orig_kif = pfi_kkif_find(sp->pfs_1301.ifname)) == NULL) {
if (V_pf_status.debug >= PF_DEBUG_MISC)
printf("%s: unknown interface: %s\n", __func__,
sp->pfs_1301.ifname);
@ -551,6 +566,30 @@ pfsync_state_import(union pfsync_state_union *sp, int flags, int msg_version)
return (0); /* skip this state */
}
/*
* States created with floating interface policy can be synchronized to
* hosts with different interfaces, because they are bound to V_pfi_all.
* But s->orig_kif still points to a real interface. Don't abort
* importing the state if orig_kif does not exists on the importing host
* but the state is not interface-bound.
*/
if (msg_version == PFSYNC_MSG_VERSION_1500) {
orig_kif = pfi_kkif_find(sp->pfs_1500.orig_ifname);
if (orig_kif == NULL) {
if (kif == V_pfi_all) {
orig_kif = kif;
} else {
if (V_pf_status.debug >= PF_DEBUG_MISC)
printf("%s: unknown original interface:"
" %s\n", __func__,
sp->pfs_1500.orig_ifname);
if (flags & PFSYNC_SI_IOCTL)
return (EINVAL);
return (0); /* skip this state */
}
}
}
/*
* If the ruleset checksums match or the state is coming from the ioctl,
* it's safe to associate the state with the rule of that number.
@ -565,10 +604,6 @@ pfsync_state_import(union pfsync_state_union *sp, int flags, int msg_version)
} else
r = &V_pf_default_rule;
/*
* Check routing interface early on. Do it before allocating memory etc.
* because there is a high chance there will be a lot more such states.
*/
switch (msg_version) {
case PFSYNC_MSG_VERSION_1301:
/*
@ -619,10 +654,12 @@ pfsync_state_import(union pfsync_state_union *sp, int flags, int msg_version)
"because of different ruleset", __func__);
return ((flags & PFSYNC_SI_IOCTL) ? EINVAL : 0);
}
wire_af = stack_af = sp->pfs_1301.af;
wire_proto = stack_proto = sp->pfs_1301.proto;
break;
case PFSYNC_MSG_VERSION_1400:
/*
* On FreeBSD 14 and above we're not taking any chances.
* On FreeBSD 14 we're not taking any chances.
* We use the information synced to us.
*/
if (sp->pfs_1400.rt) {
@ -641,6 +678,29 @@ pfsync_state_import(union pfsync_state_union *sp, int flags, int msg_version)
*/
rt_af = sp->pfs_1400.af;
}
wire_af = stack_af = sp->pfs_1400.af;
wire_proto = stack_proto = sp->pfs_1400.proto;
break;
case PFSYNC_MSG_VERSION_1500:
/*
* On FreeBSD 15 and above we're not taking any chances.
* We use the information synced to us.
*/
if (sp->pfs_1500.rt) {
rt_kif = pfi_kkif_find(sp->pfs_1500.rt_ifname);
if (rt_kif == NULL) {
DPFPRINTF(PF_DEBUG_MISC,
"%s: unknown route interface: %s",
__func__, sp->pfs_1500.rt_ifname);
return ((flags & PFSYNC_SI_IOCTL) ? EINVAL : 0);
}
rt = sp->pfs_1500.rt;
rt_af = sp->pfs_1500.rt_af;
}
wire_af = sp->pfs_1500.wire_af;
stack_af = sp->pfs_1500.stack_af;
wire_proto = sp->pfs_1500.wire_proto;
stack_proto = sp->pfs_1500.stack_proto;
break;
}
@ -667,8 +727,9 @@ pfsync_state_import(union pfsync_state_union *sp, int flags, int msg_version)
ks = &sp->pfs_1301.key[PF_SK_STACK];
#endif
if (PF_ANEQ(&kw->addr[0], &ks->addr[0], sp->pfs_1301.af) ||
PF_ANEQ(&kw->addr[1], &ks->addr[1], sp->pfs_1301.af) ||
if (wire_af != stack_af ||
PF_ANEQ(&kw->addr[0], &ks->addr[0], wire_af) ||
PF_ANEQ(&kw->addr[1], &ks->addr[1], wire_af) ||
kw->port[0] != ks->port[0] ||
kw->port[1] != ks->port[1]) {
sks = uma_zalloc(V_pf_state_key_z, M_NOWAIT);
@ -687,36 +748,19 @@ pfsync_state_import(union pfsync_state_union *sp, int flags, int msg_version)
skw->addr[1] = kw->addr[1];
skw->port[0] = kw->port[0];
skw->port[1] = kw->port[1];
skw->proto = sp->pfs_1301.proto;
skw->af = sp->pfs_1301.af;
skw->proto = wire_proto;
skw->af = wire_af;
if (sks != skw) {
sks->addr[0] = ks->addr[0];
sks->addr[1] = ks->addr[1];
sks->port[0] = ks->port[0];
sks->port[1] = ks->port[1];
sks->proto = sp->pfs_1301.proto;
sks->af = sp->pfs_1301.af;
sks->proto = stack_proto;
sks->af = stack_af;
}
/* copy to state */
bcopy(&sp->pfs_1301.rt_addr, &st->act.rt_addr, sizeof(st->act.rt_addr));
st->creation = (time_uptime - ntohl(sp->pfs_1301.creation)) * 1000;
st->expire = pf_get_uptime();
if (sp->pfs_1301.expire) {
uint32_t timeout;
timeout = r->timeout[sp->pfs_1301.timeout];
if (!timeout)
timeout = V_pf_default_rule.timeout[sp->pfs_1301.timeout];
/* sp->expire may have been adaptively scaled by export. */
st->expire -= (timeout - ntohl(sp->pfs_1301.expire)) * 1000;
}
st->direction = sp->pfs_1301.direction;
st->act.log = sp->pfs_1301.log;
st->timeout = sp->pfs_1301.timeout;
st->act.rt = rt;
st->act.rt_kif = rt_kif;
st->act.rt_af = rt_af;
@ -724,6 +768,12 @@ pfsync_state_import(union pfsync_state_union *sp, int flags, int msg_version)
switch (msg_version) {
case PFSYNC_MSG_VERSION_1301:
st->state_flags = sp->pfs_1301.state_flags;
st->direction = sp->pfs_1301.direction;
st->act.log = sp->pfs_1301.log;
st->timeout = sp->pfs_1301.timeout;
if (rt)
bcopy(&sp->pfs_1301.rt_addr, &st->act.rt_addr,
sizeof(st->act.rt_addr));
/*
* In FreeBSD 13 pfsync lacks many attributes. Copy them
* from the rule if possible. If rule can't be matched
@ -762,6 +812,9 @@ pfsync_state_import(union pfsync_state_union *sp, int flags, int msg_version)
break;
case PFSYNC_MSG_VERSION_1400:
st->state_flags = ntohs(sp->pfs_1400.state_flags);
st->direction = sp->pfs_1400.direction;
st->act.log = sp->pfs_1400.log;
st->timeout = sp->pfs_1400.timeout;
st->act.qid = ntohs(sp->pfs_1400.qid);
st->act.pqid = ntohs(sp->pfs_1400.pqid);
st->act.dnpipe = ntohs(sp->pfs_1400.dnpipe);
@ -772,12 +825,47 @@ pfsync_state_import(union pfsync_state_union *sp, int flags, int msg_version)
st->act.max_mss = ntohs(sp->pfs_1400.max_mss);
st->act.set_prio[0] = sp->pfs_1400.set_prio[0];
st->act.set_prio[1] = sp->pfs_1400.set_prio[1];
if (rt)
bcopy(&sp->pfs_1400.rt_addr, &st->act.rt_addr,
sizeof(st->act.rt_addr));
break;
case PFSYNC_MSG_VERSION_1500:
st->state_flags = ntohs(sp->pfs_1500.state_flags);
st->direction = sp->pfs_1500.direction;
st->act.log = sp->pfs_1500.log;
st->timeout = sp->pfs_1500.timeout;
st->act.qid = ntohs(sp->pfs_1500.qid);
st->act.pqid = ntohs(sp->pfs_1500.pqid);
st->act.dnpipe = ntohs(sp->pfs_1500.dnpipe);
st->act.dnrpipe = ntohs(sp->pfs_1500.dnrpipe);
st->act.rtableid = ntohl(sp->pfs_1500.rtableid);
st->act.min_ttl = sp->pfs_1500.min_ttl;
st->act.set_tos = sp->pfs_1500.set_tos;
st->act.max_mss = ntohs(sp->pfs_1500.max_mss);
st->act.set_prio[0] = sp->pfs_1500.set_prio[0];
st->act.set_prio[1] = sp->pfs_1500.set_prio[1];
if (rt)
bcopy(&sp->pfs_1500.rt_addr, &st->act.rt_addr,
sizeof(st->act.rt_addr));
if (sp->pfs_1500.tagname[0] != 0)
st->tag = pf_tagname2tag(sp->pfs_1500.tagname);
break;
default:
panic("%s: Unsupported pfsync_msg_version %d",
__func__, msg_version);
}
st->expire = pf_get_uptime();
if (sp->pfs_1301.expire) {
uint32_t timeout;
timeout = r->timeout[st->timeout];
if (!timeout)
timeout = V_pf_default_rule.timeout[st->timeout];
/* sp->expire may have been adaptively scaled by export. */
st->expire -= (timeout - ntohl(sp->pfs_1301.expire)) * 1000;
}
if (! (st->act.rtableid == -1 ||
(st->act.rtableid >= 0 && st->act.rtableid < rt_numfibs)))
goto cleanup;
@ -797,7 +885,7 @@ pfsync_state_import(union pfsync_state_union *sp, int flags, int msg_version)
if (!(flags & PFSYNC_SI_IOCTL))
st->state_flags |= PFSTATE_NOSYNC;
if ((error = pf_state_insert(kif, kif, skw, sks, st)) != 0)
if ((error = pf_state_insert(kif, orig_kif, skw, sks, st)) != 0)
goto cleanup_state;
/* XXX when we have nat_rule/anchors, use STATE_INC_COUNTERS */
@ -1089,23 +1177,29 @@ pfsync_in_ins(struct mbuf *m, int offset, int count, int flags, int action)
struct mbuf *mp;
union pfsync_state_union *sa, *sp;
int i, offp, total_len, msg_version, msg_len;
u_int8_t timeout, direction;
sa_family_t af;
switch (action) {
case PFSYNC_ACT_INS_1301:
msg_len = sizeof(struct pfsync_state_1301);
total_len = msg_len * count;
msg_version = PFSYNC_MSG_VERSION_1301;
break;
case PFSYNC_ACT_INS_1400:
msg_len = sizeof(struct pfsync_state_1400);
total_len = msg_len * count;
msg_version = PFSYNC_MSG_VERSION_1400;
break;
case PFSYNC_ACT_INS_1500:
msg_len = sizeof(struct pfsync_state_1500);
msg_version = PFSYNC_MSG_VERSION_1500;
break;
default:
V_pfsyncstats.pfsyncs_badver++;
return (-1);
}
total_len = msg_len * count;
mp = m_pulldown(m, offset, total_len, &offp);
if (mp == NULL) {
V_pfsyncstats.pfsyncs_badlen++;
@ -1116,13 +1210,26 @@ pfsync_in_ins(struct mbuf *m, int offset, int count, int flags, int action)
for (i = 0; i < count; i++) {
sp = (union pfsync_state_union *)((char *)sa + msg_len * i);
switch (msg_version) {
case PFSYNC_MSG_VERSION_1301:
case PFSYNC_MSG_VERSION_1400:
af = sp->pfs_1301.af;
timeout = sp->pfs_1301.timeout;
direction = sp->pfs_1301.direction;
break;
case PFSYNC_MSG_VERSION_1500:
af = sp->pfs_1500.wire_af;
timeout = sp->pfs_1500.timeout;
direction = sp->pfs_1500.direction;
break;
}
/* Check for invalid values. */
if (sp->pfs_1301.timeout >= PFTM_MAX ||
if (timeout >= PFTM_MAX ||
sp->pfs_1301.src.state > PF_TCPS_PROXY_DST ||
sp->pfs_1301.dst.state > PF_TCPS_PROXY_DST ||
sp->pfs_1301.direction > PF_OUT ||
(sp->pfs_1301.af != AF_INET &&
sp->pfs_1301.af != AF_INET6)) {
direction > PF_OUT ||
(af != AF_INET && af != AF_INET6)) {
if (V_pf_status.debug >= PF_DEBUG_MISC)
printf("%s: invalid value\n", __func__);
V_pfsyncstats.pfsyncs_badval++;
@ -1215,23 +1322,28 @@ pfsync_in_upd(struct mbuf *m, int offset, int count, int flags, int action)
struct pf_kstate *st;
struct mbuf *mp;
int sync, offp, i, total_len, msg_len, msg_version;
u_int8_t timeout;
switch (action) {
case PFSYNC_ACT_UPD_1301:
msg_len = sizeof(struct pfsync_state_1301);
total_len = msg_len * count;
msg_version = PFSYNC_MSG_VERSION_1301;
break;
case PFSYNC_ACT_UPD_1400:
msg_len = sizeof(struct pfsync_state_1400);
total_len = msg_len * count;
msg_version = PFSYNC_MSG_VERSION_1400;
break;
case PFSYNC_ACT_UPD_1500:
msg_len = sizeof(struct pfsync_state_1500);
msg_version = PFSYNC_MSG_VERSION_1500;
break;
default:
V_pfsyncstats.pfsyncs_badact++;
return (-1);
}
total_len = msg_len * count;
mp = m_pulldown(m, offset, total_len, &offp);
if (mp == NULL) {
V_pfsyncstats.pfsyncs_badlen++;
@ -1242,8 +1354,18 @@ pfsync_in_upd(struct mbuf *m, int offset, int count, int flags, int action)
for (i = 0; i < count; i++) {
sp = (union pfsync_state_union *)((char *)sa + msg_len * i);
switch (msg_version) {
case PFSYNC_MSG_VERSION_1301:
case PFSYNC_MSG_VERSION_1400:
timeout = sp->pfs_1301.timeout;
break;
case PFSYNC_MSG_VERSION_1500:
timeout = sp->pfs_1500.timeout;
break;
}
/* check for invalid values */
if (sp->pfs_1301.timeout >= PFTM_MAX ||
if (timeout >= PFTM_MAX ||
sp->pfs_1301.src.state > PF_TCPS_PROXY_DST ||
sp->pfs_1301.dst.state > PF_TCPS_PROXY_DST) {
if (V_pf_status.debug >= PF_DEBUG_MISC) {
@ -1288,7 +1410,7 @@ pfsync_in_upd(struct mbuf *m, int offset, int count, int flags, int action)
pfsync_alloc_scrub_memory(&sp->pfs_1301.dst, &st->dst);
pf_state_peer_ntoh(&sp->pfs_1301.dst, &st->dst);
st->expire = pf_get_uptime();
st->timeout = sp->pfs_1301.timeout;
st->timeout = timeout;
}
st->pfsync_time = time_uptime;
@ -1788,6 +1910,14 @@ pfsync_out_state_1400(struct pf_kstate *st, void *buf)
pfsync_state_export(sp, st, PFSYNC_MSG_VERSION_1400);
}
static void
pfsync_out_state_1500(struct pf_kstate *st, void *buf)
{
union pfsync_state_union *sp = buf;
pfsync_state_export(sp, st, PFSYNC_MSG_VERSION_1500);
}
static void
pfsync_out_iack(struct pf_kstate *st, void *buf)
{
@ -2455,6 +2585,8 @@ pfsync_sstate_to_qid(u_int8_t sync_state)
return PFSYNC_Q_INS_1301;
case PFSYNC_MSG_VERSION_1400:
return PFSYNC_Q_INS_1400;
case PFSYNC_MSG_VERSION_1500:
return PFSYNC_Q_INS_1500;
}
break;
case PFSYNC_S_IACK:
@ -2465,6 +2597,8 @@ pfsync_sstate_to_qid(u_int8_t sync_state)
return PFSYNC_Q_UPD_1301;
case PFSYNC_MSG_VERSION_1400:
return PFSYNC_Q_UPD_1400;
case PFSYNC_MSG_VERSION_1500:
return PFSYNC_Q_UPD_1500;
}
break;
case PFSYNC_S_UPD_C:
@ -3021,6 +3155,7 @@ pfsync_kstatus_to_softc(struct pfsync_kstatus *status, struct pfsync_softc *sc)
break;
case PFSYNC_MSG_VERSION_1301:
case PFSYNC_MSG_VERSION_1400:
case PFSYNC_MSG_VERSION_1500:
sc->sc_version = status->version;
break;
default:

View file

@ -116,7 +116,6 @@ static int pf_rollback_altq(u_int32_t);
static int pf_commit_altq(u_int32_t);
static int pf_enable_altq(struct pf_altq *);
static int pf_disable_altq(struct pf_altq *);
static uint16_t pf_qname2qid(const char *);
static void pf_qid_unref(uint16_t);
#endif /* ALTQ */
static int pf_begin_rules(u_int32_t *, int, const char *);
@ -214,8 +213,7 @@ static void pf_init_tagset(struct pf_tagset *, unsigned int *,
static void pf_cleanup_tagset(struct pf_tagset *);
static uint16_t tagname2hashindex(const struct pf_tagset *, const char *);
static uint16_t tag2hashindex(const struct pf_tagset *, uint16_t);
static u_int16_t tagname2tag(struct pf_tagset *, const char *);
static u_int16_t pf_tagname2tag(const char *);
static u_int16_t tagname2tag(struct pf_tagset *, const char *, bool);
static void tag_unref(struct pf_tagset *, u_int16_t);
struct cdev *pf_dev;
@ -286,6 +284,7 @@ int pf_end_threads;
struct proc *pf_purge_proc;
VNET_DEFINE(struct rmlock, pf_rules_lock);
VNET_DEFINE(struct rmlock, pf_tags_lock);
VNET_DEFINE_STATIC(struct sx, pf_ioctl_lock);
#define V_pf_ioctl_lock VNET(pf_ioctl_lock)
struct sx pf_end_lock;
@ -687,19 +686,50 @@ tag2hashindex(const struct pf_tagset *ts, uint16_t tag)
}
static u_int16_t
tagname2tag(struct pf_tagset *ts, const char *tagname)
tagname2tag(struct pf_tagset *ts, const char *tagname, bool add_new)
{
struct pf_tagname *tag;
u_int32_t index;
u_int16_t new_tagid;
PF_RULES_WASSERT();
PF_TAGS_RLOCK_TRACKER;
PF_TAGS_RLOCK();
index = tagname2hashindex(ts, tagname);
TAILQ_FOREACH(tag, &ts->namehash[index], namehash_entries)
if (strcmp(tagname, tag->name) == 0) {
tag->ref++;
return (tag->tag);
new_tagid = tag->tag;
PF_TAGS_RUNLOCK();
return (new_tagid);
}
/*
* When used for pfsync with queues we must not create new entries.
* Pf tags can be created just fine by this function, but queues
* require additional configuration. If they are missing on the target
* system we just ignore them
*/
if (add_new == false) {
printf("%s: Not creating a new tag\n", __func__);
PF_TAGS_RUNLOCK();
return (0);
}
/*
* If a new entry must be created do it under a write lock.
* But first search again, somebody could have created the tag
* between unlocking the read lock and locking the write lock.
*/
PF_TAGS_RUNLOCK();
PF_TAGS_WLOCK();
TAILQ_FOREACH(tag, &ts->namehash[index], namehash_entries)
if (strcmp(tagname, tag->name) == 0) {
tag->ref++;
new_tagid = tag->tag;
PF_TAGS_WUNLOCK();
return (new_tagid);
}
/*
@ -716,16 +746,20 @@ tagname2tag(struct pf_tagset *ts, const char *tagname)
* to rounding of the number of bits in the vector up to a multiple
* of the vector word size at declaration/allocation time.
*/
if ((new_tagid == 0) || (new_tagid > TAGID_MAX))
if ((new_tagid == 0) || (new_tagid > TAGID_MAX)) {
PF_TAGS_WUNLOCK();
return (0);
}
/* Mark the tag as in use. Bits are 0-based for BIT_CLR() */
BIT_CLR(TAGID_MAX, new_tagid - 1, &ts->avail);
/* allocate and fill new struct pf_tagname */
tag = uma_zalloc(V_pf_tag_z, M_NOWAIT);
if (tag == NULL)
if (tag == NULL) {
PF_TAGS_WUNLOCK();
return (0);
}
strlcpy(tag->name, tagname, sizeof(tag->name));
tag->tag = new_tagid;
tag->ref = 1;
@ -737,7 +771,29 @@ tagname2tag(struct pf_tagset *ts, const char *tagname)
index = tag2hashindex(ts, new_tagid);
TAILQ_INSERT_TAIL(&ts->taghash[index], tag, taghash_entries);
return (tag->tag);
PF_TAGS_WUNLOCK();
return (new_tagid);
}
static char *
tag2tagname(struct pf_tagset *ts, u_int16_t tag)
{
struct pf_tagname *t;
uint16_t index;
PF_TAGS_RLOCK_TRACKER;
PF_TAGS_RLOCK();
index = tag2hashindex(ts, tag);
TAILQ_FOREACH(t, &ts->taghash[index], taghash_entries)
if (tag == t->tag) {
PF_TAGS_RUNLOCK();
return (t->name);
}
PF_TAGS_RUNLOCK();
return (NULL);
}
static void
@ -746,7 +802,7 @@ tag_unref(struct pf_tagset *ts, u_int16_t tag)
struct pf_tagname *t;
uint16_t index;
PF_RULES_WASSERT();
PF_TAGS_WLOCK();
index = tag2hashindex(ts, tag);
TAILQ_FOREACH(t, &ts->taghash[index], taghash_entries)
@ -763,12 +819,20 @@ tag_unref(struct pf_tagset *ts, u_int16_t tag)
}
break;
}
PF_TAGS_WUNLOCK();
}
static uint16_t
uint16_t
pf_tagname2tag(const char *tagname)
{
return (tagname2tag(&V_pf_tags, tagname));
return (tagname2tag(&V_pf_tags, tagname, true));
}
static const char *
pf_tag2tagname(uint16_t tag)
{
return (tag2tagname(&V_pf_tags, tag));
}
static int
@ -899,10 +963,16 @@ pf_commit_eth(uint32_t ticket, const char *anchor)
}
#ifdef ALTQ
static uint16_t
pf_qname2qid(const char *qname)
uint16_t
pf_qname2qid(const char *qname, bool add_new)
{
return (tagname2tag(&V_pf_qids, qname));
return (tagname2tag(&V_pf_qids, qname, add_new));
}
static const char *
pf_qid2qname(uint16_t qid)
{
return (tag2tagname(&V_pf_qids, qid));
}
static void
@ -1151,7 +1221,7 @@ pf_altq_ifnet_event(struct ifnet *ifp, int remove)
}
bcopy(a1, a2, sizeof(struct pf_altq));
if ((a2->qid = pf_qname2qid(a2->qname)) == 0) {
if ((a2->qid = pf_qname2qid(a2->qname, true)) == 0) {
error = EBUSY;
free(a2, M_PFALTQ);
break;
@ -1606,7 +1676,7 @@ pf_export_kaltq(struct pf_altq *q, struct pfioc_altq_v1 *pa, size_t ioc_size)
#define ASSIGN_OPT(x) exported_q->pq_u.hfsc_opts.x = q->pq_u.hfsc_opts.x
#define ASSIGN_OPT_SATU32(x) exported_q->pq_u.hfsc_opts.x = \
SATU32(q->pq_u.hfsc_opts.x)
ASSIGN_OPT_SATU32(rtsc_m1);
ASSIGN_OPT(rtsc_d);
ASSIGN_OPT_SATU32(rtsc_m2);
@ -1620,7 +1690,7 @@ pf_export_kaltq(struct pf_altq *q, struct pfioc_altq_v1 *pa, size_t ioc_size)
ASSIGN_OPT_SATU32(ulsc_m2);
ASSIGN_OPT(flags);
#undef ASSIGN_OPT
#undef ASSIGN_OPT_SATU32
} else
@ -1728,7 +1798,7 @@ pf_import_kaltq(struct pfioc_altq_v1 *pa, struct pf_altq *q, size_t ioc_size)
ASSIGN_OPT(ulsc_m2);
ASSIGN_OPT(flags);
#undef ASSIGN_OPT
} else
COPY(pq_u);
@ -1760,7 +1830,7 @@ pf_import_kaltq(struct pfioc_altq_v1 *pa, struct pf_altq *q, size_t ioc_size)
ASSIGN(qid);
break;
}
default:
default:
panic("%s: unhandled struct pfioc_altq version", __func__);
break;
}
@ -2191,11 +2261,11 @@ pf_ioctl_addrule(struct pf_krule *rule, uint32_t ticket,
#ifdef ALTQ
/* set queue IDs */
if (rule->qname[0] != 0) {
if ((rule->qid = pf_qname2qid(rule->qname)) == 0)
if ((rule->qid = pf_qname2qid(rule->qname, true)) == 0)
ERROUT(EBUSY);
else if (rule->pqname[0] != 0) {
if ((rule->pqid =
pf_qname2qid(rule->pqname)) == 0)
pf_qname2qid(rule->pqname, true)) == 0)
ERROUT(EBUSY);
} else
rule->pqid = rule->qid;
@ -3314,7 +3384,7 @@ DIOCGETETHRULE_error:
#ifdef ALTQ
/* set queue IDs */
if (rule->qname[0] != 0) {
if ((rule->qid = pf_qname2qid(rule->qname)) == 0)
if ((rule->qid = pf_qname2qid(rule->qname, true)) == 0)
error = EBUSY;
else
rule->qid = rule->qid;
@ -3865,11 +3935,11 @@ DIOCGETRULENV_error:
/* set queue IDs */
if (newrule->qname[0] != 0) {
if ((newrule->qid =
pf_qname2qid(newrule->qname)) == 0)
pf_qname2qid(newrule->qname, true)) == 0)
error = EBUSY;
else if (newrule->pqname[0] != 0) {
if ((newrule->pqid =
pf_qname2qid(newrule->pqname)) == 0)
pf_qname2qid(newrule->pqname, true)) == 0)
error = EBUSY;
} else
newrule->pqid = newrule->qid;
@ -4400,7 +4470,7 @@ DIOCGETSTATESV2_full:
* copy the necessary fields
*/
if (altq->qname[0] != 0) {
if ((altq->qid = pf_qname2qid(altq->qname)) == 0) {
if ((altq->qid = pf_qname2qid(altq->qname, true)) == 0) {
PF_RULES_WUNLOCK();
error = EBUSY;
free(altq, M_PFALTQ);
@ -5723,6 +5793,7 @@ fail:
void
pfsync_state_export(union pfsync_state_union *sp, struct pf_kstate *st, int msg_version)
{
const char *tagname;
bzero(sp, sizeof(union pfsync_state_union));
/* copy from state key */
@ -5734,8 +5805,6 @@ pfsync_state_export(union pfsync_state_union *sp, struct pf_kstate *st, int msg_
sp->pfs_1301.key[PF_SK_STACK].addr[1] = st->key[PF_SK_STACK]->addr[1];
sp->pfs_1301.key[PF_SK_STACK].port[0] = st->key[PF_SK_STACK]->port[0];
sp->pfs_1301.key[PF_SK_STACK].port[1] = st->key[PF_SK_STACK]->port[1];
sp->pfs_1301.proto = st->key[PF_SK_WIRE]->proto;
sp->pfs_1301.af = st->key[PF_SK_WIRE]->af;
/* copy from state */
strlcpy(sp->pfs_1301.ifname, st->kif->pfik_name, sizeof(sp->pfs_1301.ifname));
@ -5747,16 +5816,31 @@ pfsync_state_export(union pfsync_state_union *sp, struct pf_kstate *st, int msg_
else
sp->pfs_1301.expire = htonl(sp->pfs_1301.expire - time_uptime);
sp->pfs_1301.direction = st->direction;
sp->pfs_1301.log = st->act.log;
sp->pfs_1301.timeout = st->timeout;
switch (msg_version) {
case PFSYNC_MSG_VERSION_1301:
sp->pfs_1301.state_flags = st->state_flags;
sp->pfs_1301.direction = st->direction;
sp->pfs_1301.log = st->act.log;
sp->pfs_1301.timeout = st->timeout;
sp->pfs_1301.proto = st->key[PF_SK_WIRE]->proto;
sp->pfs_1301.af = st->key[PF_SK_WIRE]->af;
/*
* XXX Why do we bother pfsyncing source node information if source
* nodes are not synced? Showing users that there is source tracking
* when there is none seems useless.
*/
if (st->sns[PF_SN_LIMIT] != NULL)
sp->pfs_1301.sync_flags |= PFSYNC_FLAG_SRCNODE;
if (st->sns[PF_SN_NAT] != NULL || st->sns[PF_SN_ROUTE])
sp->pfs_1301.sync_flags |= PFSYNC_FLAG_NATSRCNODE;
break;
case PFSYNC_MSG_VERSION_1400:
sp->pfs_1400.state_flags = htons(st->state_flags);
sp->pfs_1400.direction = st->direction;
sp->pfs_1400.log = st->act.log;
sp->pfs_1400.timeout = st->timeout;
sp->pfs_1400.proto = st->key[PF_SK_WIRE]->proto;
sp->pfs_1400.af = st->key[PF_SK_WIRE]->af;
sp->pfs_1400.qid = htons(st->act.qid);
sp->pfs_1400.pqid = htons(st->act.pqid);
sp->pfs_1400.dnpipe = htons(st->act.dnpipe);
@ -5772,22 +5856,53 @@ pfsync_state_export(union pfsync_state_union *sp, struct pf_kstate *st, int msg_
strlcpy(sp->pfs_1400.rt_ifname,
st->act.rt_kif->pfik_name,
sizeof(sp->pfs_1400.rt_ifname));
/*
* XXX Why do we bother pfsyncing source node information if source
* nodes are not synced? Showing users that there is source tracking
* when there is none seems useless.
*/
if (st->sns[PF_SN_LIMIT] != NULL)
sp->pfs_1400.sync_flags |= PFSYNC_FLAG_SRCNODE;
if (st->sns[PF_SN_NAT] != NULL || st->sns[PF_SN_ROUTE])
sp->pfs_1400.sync_flags |= PFSYNC_FLAG_NATSRCNODE;
break;
case PFSYNC_MSG_VERSION_1500:
sp->pfs_1500.state_flags = htons(st->state_flags);
sp->pfs_1500.direction = st->direction;
sp->pfs_1500.log = st->act.log;
sp->pfs_1500.timeout = st->timeout;
sp->pfs_1500.wire_proto = st->key[PF_SK_WIRE]->proto;
sp->pfs_1500.wire_af = st->key[PF_SK_WIRE]->af;
sp->pfs_1500.stack_proto = st->key[PF_SK_STACK]->proto;
sp->pfs_1500.stack_af = st->key[PF_SK_STACK]->af;
sp->pfs_1500.qid = htons(st->act.qid);
sp->pfs_1500.pqid = htons(st->act.pqid);
sp->pfs_1500.dnpipe = htons(st->act.dnpipe);
sp->pfs_1500.dnrpipe = htons(st->act.dnrpipe);
sp->pfs_1500.rtableid = htonl(st->act.rtableid);
sp->pfs_1500.min_ttl = st->act.min_ttl;
sp->pfs_1500.set_tos = st->act.set_tos;
sp->pfs_1500.max_mss = htons(st->act.max_mss);
sp->pfs_1500.set_prio[0] = st->act.set_prio[0];
sp->pfs_1500.set_prio[1] = st->act.set_prio[1];
sp->pfs_1500.rt = st->act.rt;
sp->pfs_1500.rt_af = st->act.rt_af;
if (st->act.rt_kif)
strlcpy(sp->pfs_1500.rt_ifname,
st->act.rt_kif->pfik_name,
sizeof(sp->pfs_1500.rt_ifname));
strlcpy(sp->pfs_1500.orig_ifname,
st->orig_kif->pfik_name,
sizeof(sp->pfs_1500.orig_ifname));
if ((tagname = pf_tag2tagname(st->tag)) != NULL)
strlcpy(sp->pfs_1500.tagname, tagname,
sizeof(sp->pfs_1500.tagname));
break;
default:
panic("%s: Unsupported pfsync_msg_version %d",
__func__, msg_version);
}
/*
* XXX Why do we bother pfsyncing source node information if source
* nodes are not synced? Showing users that there is source tracking
* when there is none seems useless.
*/
if (st->sns[PF_SN_LIMIT] != NULL)
sp->pfs_1301.sync_flags |= PFSYNC_FLAG_SRCNODE;
if (st->sns[PF_SN_NAT] != NULL || st->sns[PF_SN_ROUTE])
sp->pfs_1301.sync_flags |= PFSYNC_FLAG_NATSRCNODE;
sp->pfs_1301.id = st->id;
sp->pfs_1301.creatorid = st->creatorid;
pf_state_peer_hton(&st->src, &sp->pfs_1301.src);
@ -6842,6 +6957,7 @@ pf_load_vnet(void)
NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, 0);
rm_init_flags(&V_pf_rules_lock, "pf rulesets", RM_RECURSE);
rm_init_flags(&V_pf_tags_lock, "pf tags and queues", RM_RECURSE);
sx_init(&V_pf_ioctl_lock, "pf ioctl");
pf_init_tagset(&V_pf_tags, &pf_rule_tag_hashsize,
@ -6993,7 +7109,7 @@ vnet_pf_init(void *unused __unused)
pf_load_vnet();
}
VNET_SYSINIT(vnet_pf_init, SI_SUB_PROTO_FIREWALL, SI_ORDER_THIRD,
VNET_SYSINIT(vnet_pf_init, SI_SUB_PROTO_FIREWALL, SI_ORDER_THIRD,
vnet_pf_init, NULL);
static void
@ -7001,7 +7117,7 @@ vnet_pf_uninit(const void *unused __unused)
{
pf_unload_vnet();
}
}
SYSUNINIT(pf_unload, SI_SUB_PROTO_FIREWALL, SI_ORDER_SECOND, pf_unload, NULL);
VNET_SYSUNINIT(vnet_pf_uninit, SI_SUB_PROTO_FIREWALL, SI_ORDER_THIRD,
vnet_pf_uninit, NULL);

View file

@ -921,6 +921,8 @@ rtable_cleanup()
route_to_common_head()
{
# TODO: Extend setup_router_server_nat64 to create a 2nd router
pfsync_version=$1
shift
@ -937,11 +939,16 @@ route_to_common_head()
# pfsync interface
jexec one ifconfig ${epair_sync}a 192.0.2.1/24 up
jexec one ifconfig ${epair_one}a 198.51.100.1/24 up
jexec one ifconfig ${epair_one}a 198.51.100.1/28 up
jexec one ifconfig ${epair_one}a inet6 2001:db8:4211::1/64 no_dad
jexec one ifconfig ${epair_one}a name inif
jexec one ifconfig ${epair_out_one}a 203.0.113.1/24 up
jexec one ifconfig ${epair_out_one}a inet6 2001:db8:4200::1/64 no_dad
jexec one ifconfig ${epair_out_one}a name outif
jexec one sysctl net.inet.ip.forwarding=1
jexec one arp -s 203.0.113.254 00:01:02:03:04:05
jexec one sysctl net.inet6.ip6.forwarding=1
jexec one arp -s 203.0.113.254 00:01:02:00:00:04
jexec one ndp -s 2001:db8:4200::fe 00:01:02:00:00:06
jexec one ifconfig pfsync0 \
syncdev ${epair_sync}a \
maxupd 1 \
@ -949,20 +956,30 @@ route_to_common_head()
up
jexec two ifconfig ${epair_sync}b 192.0.2.2/24 up
jexec two ifconfig ${epair_two}a 198.51.100.2/24 up
jexec two ifconfig ${epair_out_two}a 203.0.113.2/24 up
jexec two ifconfig ${epair_two}a 198.51.100.17/28 up
jexec two ifconfig ${epair_two}a inet6 2001:db8:4212::1/64 no_dad
jexec two ifconfig ${epair_two}a name inif
jexec two ifconfig ${epair_out_two}a 203.0.113.1/24 up
jexec two ifconfig ${epair_out_two}a inet6 2001:db8:4200::2/64 no_dad
jexec two ifconfig ${epair_out_two}a name outif
jexec two sysctl net.inet.ip.forwarding=1
jexec two arp -s 203.0.113.254 00:01:02:03:04:05
jexec two sysctl net.inet6.ip6.forwarding=1
jexec two arp -s 203.0.113.254 00:01:02:00:00:04
jexec two ndp -s 2001:db8:4200::fe 00:01:02:00:00:06
jexec two ifconfig pfsync0 \
syncdev ${epair_sync}b \
maxupd 1 \
version $pfsync_version \
up
ifconfig ${epair_one}b 198.51.100.254/24 up
ifconfig ${epair_two}b 198.51.100.253/24 up
ifconfig ${epair_one}b 198.51.100.2/28 up
ifconfig ${epair_one}b inet6 2001:db8:4211::2/64 no_dad
ifconfig ${epair_two}b 198.51.100.18/28 up
ifconfig ${epair_two}b inet6 2001:db8:4212::2/64 no_dad
# Target is behind router "one"
route add -net 203.0.113.0/24 198.51.100.1
route add -inet6 -net 64:ff9b::/96 2001:db8:4211::1
ifconfig ${epair_two}b up
ifconfig ${epair_out_one}b up
ifconfig ${epair_out_two}b up
@ -1206,6 +1223,435 @@ route_to_1400_bad_ifname_cleanup()
pfsynct_cleanup
}
atf_test_case "af_to_in_floating" "cleanup"
af_to_in_floating_head()
{
atf_set descr 'Test syncing of states created by inbound af-to rules with floating states'
atf_set require.user root
atf_set require.progs python3 scapy
}
af_to_in_floating_body()
{
route_to_common_head 1500
jexec one pfctl -e
pft_set_rules one \
"set state-policy floating" \
"set skip on ${epair_sync}a" \
"block" \
"pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv } keep state (no-sync)" \
"pass in on inif to 64:ff9b::/96 af-to inet from (outif) keep state"
jexec two pfctl -e
pft_set_rules two \
"set skip on ${epair_sync}b" \
"block" \
"pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv } keep state (no-sync)"
# ptf_ping can't deal with nat64, this test will fail but generate states
atf_check -s exit:1 env PYTHONPATH=${common_dir} \
${common_dir}/pft_ping.py \
--sendif ${epair_one}b \
--fromaddr 2001:db8:4201::fe \
--to 64:ff9b::203.0.113.254 \
--recvif ${epair_out_one}b
# Allow time for sync
sleep 2
states_one=$(mktemp)
states_two=$(mktemp)
jexec one pfctl -qvvss | normalize_pfctl_s > $states_one
jexec two pfctl -qvvss | normalize_pfctl_s > $states_two
# Sanity check
grep -qE 'all ipv6-icmp 203.0.113.1 \(2001:db8:4201::fe\) -> 203.0.113.254:8 \(64:ff9b::cb00:71fe) .* rule 3 .* origif: inif' $states_one ||
atf_fail "State missing on router one"
grep -qE 'all ipv6-icmp 203.0.113.1 \(2001:db8:4201::fe\) -> 203.0.113.254:8 \(64:ff9b::cb00:71fe) .* origif: inif' $states_two ||
atf_fail "State missing on router two"
}
af_to_in_floating_cleanup()
{
pfsynct_cleanup
}
atf_test_case "af_to_in_if_bound" "cleanup"
af_to_in_if_bound_head()
{
atf_set descr 'Test syncing of states created by inbound af-to rules with if-bound states'
atf_set require.user root
atf_set require.progs python3 scapy
}
af_to_in_if_bound_body()
{
route_to_common_head 1500
jexec one pfctl -e
pft_set_rules one \
"set state-policy if-bound" \
"set skip on ${epair_sync}a" \
"block" \
"pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv } keep state (no-sync)" \
"pass in on inif to 64:ff9b::/96 af-to inet from (outif) keep state"
jexec two pfctl -e
pft_set_rules two \
"set skip on ${epair_sync}b" \
"block" \
"pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv } keep state (no-sync)"
# ptf_ping can't deal with nat64, this test will fail but generate states
atf_check -s exit:1 env PYTHONPATH=${common_dir} \
${common_dir}/pft_ping.py \
--sendif ${epair_one}b \
--fromaddr 2001:db8:4201::fe \
--to 64:ff9b::203.0.113.254 \
--recvif ${epair_out_one}b
# Allow time for sync
sleep 2
states_one=$(mktemp)
states_two=$(mktemp)
jexec one pfctl -qvvss | normalize_pfctl_s > $states_one
jexec two pfctl -qvvss | normalize_pfctl_s > $states_two
# Sanity check
grep -qE 'outif ipv6-icmp 203.0.113.1 \(2001:db8:4201::fe\) -> 203.0.113.254:8 \(64:ff9b::cb00:71fe) .* rule 3 .* origif: inif' $states_one ||
atf_fail "State missing on router one"
grep -qE 'outif ipv6-icmp 203.0.113.1 \(2001:db8:4201::fe\) -> 203.0.113.254:8 \(64:ff9b::cb00:71fe) .* origif: inif' $states_two ||
atf_fail "State missing on router two"
}
af_to_in_if_bound_cleanup()
{
pfsynct_cleanup
}
atf_test_case "af_to_out_if_bound" "cleanup"
af_to_out_if_bound_head()
{
atf_set descr 'Test syncing of states created by outbound af-to rules with if-bound states'
atf_set require.user root
atf_set require.progs python3 scapy
}
af_to_out_if_bound_body()
{
route_to_common_head 1500
jexec one route add -inet6 -net 64:ff9b::/96 -iface outif
jexec one sysctl net.inet6.ip6.forwarding=1
jexec one pfctl -e
pft_set_rules one \
"set state-policy if-bound" \
"set skip on ${epair_sync}a" \
"block" \
"pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv } keep state (no-sync)" \
"pass in on inif to 64:ff9b::/96 keep state" \
"pass out on outif to 64:ff9b::/96 af-to inet from (outif) keep state"
jexec two pfctl -e
pft_set_rules two \
"set skip on ${epair_sync}b" \
"block" \
"pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv } keep state (no-sync)"
# ptf_ping can't deal with nat64, this test will fail but generate states
atf_check -s exit:1 env PYTHONPATH=${common_dir} \
${common_dir}/pft_ping.py \
--sendif ${epair_one}b \
--fromaddr 2001:db8:4201::fe \
--to 64:ff9b::203.0.113.254 \
--recvif ${epair_out_one}b
# Allow time for sync
sleep 2
states_one=$(mktemp)
states_two=$(mktemp)
jexec one pfctl -qvvss | normalize_pfctl_s > $states_one
jexec two pfctl -qvvss | normalize_pfctl_s > $states_two
# Sanity check
# st->orig_kif is the same as st->kif, so st->orig_kif is not printed.
for state_regexp in \
"inif ipv6-icmp 64:ff9b::cb00:71fe\[128\] <- 2001:db8:4201::fe .* rule 3 .* creatorid: [0-9a-f]+" \
"outif icmp 203.0.113.1 \(64:ff9b::cb00:71fe\[8\]\) -> 203.0.113.254:8 \(2001:db8:4201::fe\) .* rule 4 .* creatorid: [0-9a-f]+" \
; do
grep -qE "${state_regexp}" $states_one || atf_fail "State not found for '${state_regexp}'"
done
for state_regexp in \
"inif ipv6-icmp 64:ff9b::cb00:71fe\[128\] <- 2001:db8:4201::fe .* creatorid: [0-9a-f]+" \
"outif icmp 203.0.113.1 \(64:ff9b::cb00:71fe\[8\]\) -> 203.0.113.254:8 \(2001:db8:4201::fe\) .* creatorid: [0-9a-f]+" \
; do
grep -qE "${state_regexp}" $states_two || atf_fail "State not found for '${state_regexp}'"
done
}
af_to_out_if_bound_cleanup()
{
pfsynct_cleanup
}
atf_test_case "tag" "cleanup"
tag_head()
{
atf_set descr 'Test if the pf tag is synced'
atf_set require.user root
atf_set require.progs python3 scapy
}
tag_body()
{
route_to_common_head 1500
jexec one pfctl -e
pft_set_rules one \
"set skip on ${epair_sync}a" \
"block" \
"pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv } keep state (no-sync)" \
"pass in on inif inet proto udp tag sometag keep state" \
"pass out on outif tagged sometag keep state (no-sync)"
jexec two pfctl -e
pft_set_rules two \
"set debug loud" \
"set skip on ${epair_sync}b" \
"block" \
"pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv } keep state (no-sync)" \
"block tagged othertag" \
"pass out on outif tagged sometag keep state (no-sync)"
atf_check -s exit:0 env PYTHONPATH=${common_dir} \
${common_dir}/pft_ping.py \
--ping-type=udp \
--sendif ${epair_one}b \
--fromaddr 198.51.100.254 \
--to 203.0.113.254 \
--recvif ${epair_out_one}b
# Allow time for sync
sleep 2
# Force the next request to go through the 2nd router
route change -net 203.0.113.0/24 198.51.100.17
atf_check -s exit:0 env PYTHONPATH=${common_dir} \
${common_dir}/pft_ping.py \
--ping-type=udp \
--sendif ${epair_two}b \
--fromaddr 198.51.100.254 \
--to 203.0.113.254 \
--recvif ${epair_out_two}b
}
tag_cleanup()
{
pfsynct_cleanup
}
atf_test_case "altq_queues" "cleanup"
altq_queues_head()
{
atf_set descr 'Test if the altq queues are synced'
atf_set require.user root
atf_set require.progs python3 scapy
}
altq_queues_body()
{
route_to_common_head 1500
altq_init
is_altq_supported hfsc
jexec one pfctl -e
pft_set_rules one \
"set skip on ${epair_sync}a" \
"altq on outif bandwidth 30000b hfsc queue { default other1 other2 }" \
"queue default hfsc(linkshare 10000b default)" \
"queue other1 hfsc(linkshare 10000b)" \
"queue other2 hfsc(linkshare 10000b)" \
"block" \
"pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv } keep state (no-sync)" \
"pass in on inif inet proto udp queue other1 keep state" \
"pass out on outif inet proto udp keep state"
jexec two pfctl -e
pft_set_rules two \
"set debug loud" \
"set skip on ${epair_sync}b" \
"altq on outif bandwidth 30000b hfsc queue { default other2 other1 }" \
"queue default hfsc(linkshare 10000b default)" \
"queue other2 hfsc(linkshare 10000b)" \
"queue other1 hfsc(linkshare 10000b)" \
"block" \
"pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv } keep state (no-sync)" \
"pass out on outif inet proto udp keep state"
atf_check -s exit:0 env PYTHONPATH=${common_dir} \
${common_dir}/pft_ping.py \
--ping-type=udp \
--sendif ${epair_one}b \
--fromaddr 198.51.100.254 \
--to 203.0.113.254 \
--recvif ${epair_out_one}b
queues_one=$(mktemp)
jexec one pfctl -qvsq | normalize_pfctl_s > $queues_one
echo " === queues one === "
cat $queues_one
grep -qE 'queue other1 on outif .* pkts: 1 ' $queues_one || atf_fail 'Packets not sent through queue "other1"'
# Allow time for sync
sleep 2
# Force the next request to go through the 2nd router
route change -net 203.0.113.0/24 198.51.100.17
# Send a packet through router "two". It lacks the inbound rule
# but the inbound state should have been pfsynced from router "one"
# including altq queuing information. However the queues are created
# on router "two" in different order and we only sync queue index,
# so the packet ends up in a different queue. One must have identical
# queue set on both routers!
atf_check -s exit:0 env PYTHONPATH=${common_dir} \
${common_dir}/pft_ping.py \
--ping-type=udp \
--sendif ${epair_two}b \
--fromaddr 198.51.100.254 \
--to 203.0.113.254 \
--recvif ${epair_out_two}b
queues_two=$(mktemp)
jexec two pfctl -qvsq | normalize_pfctl_s > $queues_two
echo " === queues two === "
cat $queues_two
grep -qE 'queue other2 on outif .* pkts: 1 ' $queues_two || atf_fail 'Packets not sent through queue "other2"'
}
altq_queues_cleanup()
{
# Interface detaching seems badly broken in altq. If interfaces are
# destroyed when shutting down the vnet and then pf is unloaded, it will
# cause a kernel crash. Work around the issue by first flushing the
# pf rulesets
jexec one pfctl -F all
jexec two pfctl -F all
pfsynct_cleanup
}
atf_test_case "rt_af" "cleanup"
rt_af_head()
{
atf_set descr 'Test if the rt_af is synced'
atf_set require.user root
atf_set require.progs python3 scapy
}
rt_af_body()
{
route_to_common_head 1500
jexec one pfctl -e
pft_set_rules one \
"set skip on ${epair_sync}a" \
"block" \
"pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv } keep state (no-sync)" \
"pass in on inif \
route-to (outif 203.0.113.254) prefer-ipv6-nexthop \
inet proto udp \
to 203.0.113.241 \
keep state" \
"pass in on inif \
route-to (outif 2001:db8:4200::fe) prefer-ipv6-nexthop \
inet proto udp \
to 203.0.113.242 \
keep state" \
"pass in on inif \
route-to (outif 2001:db8:4200::fe) prefer-ipv6-nexthop \
inet6 proto udp \
to 2001:db8:4200::f3 \
keep state" \
"pass out on outif inet proto udp keep state (no-sync)" \
"pass out on outif inet6 proto udp keep state (no-sync)"
jexec two pfctl -e
pft_set_rules two \
"set debug loud" \
"set skip on ${epair_sync}b" \
"block" \
"pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv } keep state (no-sync)" \
# IPv4 packet over IPv4 gateway
atf_check -s exit:0 env PYTHONPATH=${common_dir} \
${common_dir}/pft_ping.py \
--ping-type=udp \
--sendif ${epair_one}b \
--fromaddr 198.51.100.254 \
--to 203.0.113.241 \
--recvif ${epair_out_one}b
# FIXME: Routing IPv4 packets over IPv6 gateways with gateway added
# with `ndp -s` causes the static NDP entry to become expired.
# Pfsync tests don't use "servers" which can reply to ARP and NDP,
# but such static entry for gateway and only check if a stateless
# ICMP or UDP packet is forward through.
#
# IPv4 packert over IPv6 gateway
#atf_check -s exit:0 env PYTHONPATH=${common_dir} \
# ${common_dir}/pft_ping.py \
# --ping-type=udp \
# --sendif ${epair_one}b \
# --fromaddr 198.51.100.254 \
# --to 203.0.113.242 \
# --recvif ${epair_out_one}b
# IPv6 packet over IPv6 gateway
atf_check -s exit:0 env PYTHONPATH=${common_dir} \
${common_dir}/pft_ping.py \
--ping-type=udp \
--sendif ${epair_one}b \
--fromaddr 2001:db8:4211::fe \
--to 2001:db8:4200::f3 \
--recvif ${epair_out_one}b
sleep 5 # Wait for pfsync
states_one=$(mktemp)
states_two=$(mktemp)
jexec one pfctl -qvvss | normalize_pfctl_s > $states_one
jexec two pfctl -qvvss | normalize_pfctl_s > $states_two
echo " === states one === "
cat $states_one
echo " === states two === "
cat $states_two
for state_regexp in \
"all udp 203.0.113.241:9 <- 198.51.100.254 .* route-to: 203.0.113.254@outif origif: inif" \
"all udp 2001:db8:4200::f3\[9\] <- 2001:db8:4211::fe .* route-to: 2001:db8:4200::fe@outif origif: inif" \
; do
grep -qE "${state_regexp}" $states_two || atf_fail "State not found for '${state_regexp}' on router two"
done
}
rt_af_cleanup()
{
jexec one pfctl -qvvsr
jexec one pfctl -qvvss
jexec one arp -an
jexec one ndp -an
pfsynct_cleanup
}
atf_init_test_cases()
{
atf_add_test_case "basic"
@ -1224,4 +1670,10 @@ atf_init_test_cases()
atf_add_test_case "route_to_1301_bad_rpool"
atf_add_test_case "route_to_1400_bad_ruleset"
atf_add_test_case "route_to_1400_bad_ifname"
atf_add_test_case "af_to_in_floating"
atf_add_test_case "af_to_in_if_bound"
atf_add_test_case "af_to_out_if_bound"
atf_add_test_case "tag"
atf_add_test_case "altq_queues"
atf_add_test_case "rt_af"
}

View file

@ -84,8 +84,10 @@ static const char* pfsyncacts[] = {
/* PFSYNC_ACT_BUS */ "bulk update mark",
/* PFSYNC_ACT_TDB */ "TDB replay counter update",
/* PFSYNC_ACT_EOF */ "end of frame mark",
/* PFSYNC_ACT_INS_1400 */ "state insert",
/* PFSYNC_ACT_UPD_1400 */ "state update",
/* PFSYNC_ACT_INS_1400 */ "14.0 state insert",
/* PFSYNC_ACT_UPD_1400 */ "14.0 state update",
/* PFSYNC_ACT_INS_1500 */ "state insert",
/* PFSYNC_ACT_UPD_1500 */ "state update",
};
static const char* pfsyncacts_name[] = {
@ -102,8 +104,10 @@ static const char* pfsyncacts_name[] = {
/* PFSYNC_ACT_BUS */ "bulk-update-mark",
/* PFSYNC_ACT_TDB */ "TDB-replay-counter-update",
/* PFSYNC_ACT_EOF */ "end-of-frame-mark",
/* PFSYNC_ACT_INS_1400 */ "state-insert",
/* PFSYNC_ACT_UPD_1400 */ "state-update",
/* PFSYNC_ACT_INS_1400 */ "state-insert-1400",
/* PFSYNC_ACT_UPD_1400 */ "state-update-1400",
/* PFSYNC_ACT_INS_1500 */ "state-insert",
/* PFSYNC_ACT_UPD_1500 */ "state-update",
};
static void