pf: Support "return" statements in passing rules when they fail.

Normally pf rules are expected to do one of two things: pass the traffic or
block it. Blocking can be silent - "drop", or loud - "return", "return-rst",
"return-icmp". Yet there is a 3rd category of traffic passing through pf:
Packets matching a "pass" rule but when applying the rule fails. This happens
when redirection table is empty or when src node or state creation fails. Such
rules always fail silently without notifying the sender.

Allow users to configure this behaviour too, so that pf returns an error packet
in these cases.

PR:		226850
Submitted by:	Kajetan Staszkiewicz <vegeta tuxpowered.net>
MFC after:	1 week
Sponsored by:	InnoGames GmbH
This commit is contained in:
Kristof Provost 2018-06-22 21:59:30 +00:00
parent ba6cce3aea
commit 150182e309
3 changed files with 131 additions and 66 deletions
sbin/pfctl
share/man/man5
sys/netpfil/pf

@ -79,6 +79,7 @@ static u_int16_t returnicmpdefault =
static u_int16_t returnicmp6default =
(ICMP6_DST_UNREACH << 8) | ICMP6_DST_UNREACH_NOPORT;
static int blockpolicy = PFRULE_DROP;
static int failpolicy = PFRULE_DROP;
static int require_order = 1;
static int default_statelock;
@ -455,8 +456,8 @@ int parseport(char *, struct range *r, int);
%token MINTTL ERROR ALLOWOPTS FASTROUTE FILENAME ROUTETO DUPTO REPLYTO NO LABEL
%token NOROUTE URPFFAILED FRAGMENT USER GROUP MAXMSS MAXIMUM TTL TOS DROP TABLE
%token REASSEMBLE FRAGDROP FRAGCROP ANCHOR NATANCHOR RDRANCHOR BINATANCHOR
%token SET OPTIMIZATION TIMEOUT LIMIT LOGINTERFACE BLOCKPOLICY RANDOMID
%token REQUIREORDER SYNPROXY FINGERPRINTS NOSYNC DEBUG SKIP HOSTID
%token SET OPTIMIZATION TIMEOUT LIMIT LOGINTERFACE BLOCKPOLICY FAILPOLICY
%token RANDOMID REQUIREORDER SYNPROXY FINGERPRINTS NOSYNC DEBUG SKIP HOSTID
%token ANTISPOOF FOR INCLUDE
%token BITMASK RANDOM SOURCEHASH ROUNDROBIN STATICPORT PROBABILITY
%token ALTQ CBQ CODEL PRIQ HFSC FAIRQ BANDWIDTH TBRSIZE LINKSHARE REALTIME
@ -640,6 +641,20 @@ option : SET OPTIMIZATION STRING {
YYERROR;
blockpolicy = PFRULE_RETURN;
}
| SET FAILPOLICY DROP {
if (pf->opts & PF_OPT_VERBOSE)
printf("set fail-policy drop\n");
if (check_rulestate(PFCTL_STATE_OPTION))
YYERROR;
failpolicy = PFRULE_DROP;
}
| SET FAILPOLICY RETURN {
if (pf->opts & PF_OPT_VERBOSE)
printf("set fail-policy return\n");
if (check_rulestate(PFCTL_STATE_OPTION))
YYERROR;
failpolicy = PFRULE_RETURN;
}
| SET REQUIREORDER yesno {
if (pf->opts & PF_OPT_VERBOSE)
printf("set require-order %s\n",
@ -2636,7 +2651,12 @@ probability : STRING {
;
action : PASS { $$.b1 = PF_PASS; $$.b2 = $$.w = 0; }
action : PASS {
$$.b1 = PF_PASS;
$$.b2 = failpolicy;
$$.w = returnicmpdefault;
$$.w2 = returnicmp6default;
}
| BLOCK blockspec { $$ = $2; $$.b1 = PF_DROP; }
;
@ -5471,6 +5491,7 @@ lookup(char *s)
{ "drop", DROP},
{ "drop-ovl", FRAGDROP},
{ "dup-to", DUPTO},
{ "fail-policy", FAILPOLICY},
{ "fairq", FAIRQ},
{ "fastroute", FASTROUTE},
{ "file", FILENAME},
@ -5935,6 +5956,7 @@ parse_config(char *filename, struct pfctl *xpf)
returnicmp6default =
(ICMP6_DST_UNREACH << 8) | ICMP6_DST_UNREACH_NOPORT;
blockpolicy = PFRULE_DROP;
failpolicy = PFRULE_DROP;
require_order = 1;
if ((file = pushfile(filename, 0)) == NULL) {

@ -498,6 +498,31 @@ For example:
.Bd -literal -offset indent
set block-policy return
.Ed
.It Ar set fail-policy
The
.Ar fail-policy
option sets the behaviour of rules which should pass a packet but were unable to
do so. This might happen when a nat or route-to rule uses an empty table as list
of targets or if a rule fails to create state or source node.
The following
.Ar block
actions are possible:
.Pp
.Bl -tag -width xxxxxxxx -compact
.It Ar drop
Incoming packet is silently dropped.
.It Ar return
Incoming packet is dropped and TCP RST is returned for TCP packets,
an ICMP UNREACHABLE is returned for UDP packets,
and no response is sent for other packets.
.El
.Pp
For example:
.Bd -literal -offset indent
set fail-policy return
.Ed
.It Ar set state-policy
The
.Ar state-policy

@ -2499,6 +2499,81 @@ pf_send_tcp(struct mbuf *replyto, const struct pf_rule *r, sa_family_t af,
pf_send(pfse);
}
static void
pf_return(struct pf_rule *r, struct pf_rule *nr, struct pf_pdesc *pd,
struct pf_state_key *sk, int off, struct mbuf *m, struct tcphdr *th,
struct pfi_kif *kif, u_int16_t bproto_sum, u_int16_t bip_sum, int hdrlen,
u_short *reason)
{
struct pf_addr * const saddr = pd->src;
struct pf_addr * const daddr = pd->dst;
sa_family_t af = pd->af;
/* undo NAT changes, if they have taken place */
if (nr != NULL) {
PF_ACPY(saddr, &sk->addr[pd->sidx], af);
PF_ACPY(daddr, &sk->addr[pd->didx], af);
if (pd->sport)
*pd->sport = sk->port[pd->sidx];
if (pd->dport)
*pd->dport = sk->port[pd->didx];
if (pd->proto_sum)
*pd->proto_sum = bproto_sum;
if (pd->ip_sum)
*pd->ip_sum = bip_sum;
m_copyback(m, off, hdrlen, pd->hdr.any);
}
if (pd->proto == IPPROTO_TCP &&
((r->rule_flag & PFRULE_RETURNRST) ||
(r->rule_flag & PFRULE_RETURN)) &&
!(th->th_flags & TH_RST)) {
u_int32_t ack = ntohl(th->th_seq) + pd->p_len;
int len = 0;
#ifdef INET
struct ip *h4;
#endif
#ifdef INET6
struct ip6_hdr *h6;
#endif
switch (af) {
#ifdef INET
case AF_INET:
h4 = mtod(m, struct ip *);
len = ntohs(h4->ip_len) - off;
break;
#endif
#ifdef INET6
case AF_INET6:
h6 = mtod(m, struct ip6_hdr *);
len = ntohs(h6->ip6_plen) - (off - sizeof(*h6));
break;
#endif
}
if (pf_check_proto_cksum(m, off, len, IPPROTO_TCP, af))
REASON_SET(reason, PFRES_PROTCKSUM);
else {
if (th->th_flags & TH_SYN)
ack++;
if (th->th_flags & TH_FIN)
ack++;
pf_send_tcp(m, r, af, pd->dst,
pd->src, th->th_dport, th->th_sport,
ntohl(th->th_ack), ack, TH_RST|TH_ACK, 0, 0,
r->return_ttl, 1, 0, kif->pfik_ifp);
}
} else if (pd->proto != IPPROTO_ICMP && af == AF_INET &&
r->return_icmp)
pf_send_icmp(m, r->return_icmp >> 8,
r->return_icmp & 255, af, r);
else if (pd->proto != IPPROTO_ICMPV6 && af == AF_INET6 &&
r->return_icmp6)
pf_send_icmp(m, r->return_icmp6 >> 8,
r->return_icmp6 & 255, af, r);
}
static int
pf_ieee8021q_setpcp(struct mbuf *m, u_int8_t prio)
{
@ -3463,68 +3538,8 @@ pf_test_rule(struct pf_rule **rm, struct pf_state **sm, int direction,
((r->rule_flag & PFRULE_RETURNRST) ||
(r->rule_flag & PFRULE_RETURNICMP) ||
(r->rule_flag & PFRULE_RETURN))) {
/* undo NAT changes, if they have taken place */
if (nr != NULL) {
PF_ACPY(saddr, &sk->addr[pd->sidx], af);
PF_ACPY(daddr, &sk->addr[pd->didx], af);
if (pd->sport)
*pd->sport = sk->port[pd->sidx];
if (pd->dport)
*pd->dport = sk->port[pd->didx];
if (pd->proto_sum)
*pd->proto_sum = bproto_sum;
if (pd->ip_sum)
*pd->ip_sum = bip_sum;
m_copyback(m, off, hdrlen, pd->hdr.any);
}
if (pd->proto == IPPROTO_TCP &&
((r->rule_flag & PFRULE_RETURNRST) ||
(r->rule_flag & PFRULE_RETURN)) &&
!(th->th_flags & TH_RST)) {
u_int32_t ack = ntohl(th->th_seq) + pd->p_len;
int len = 0;
#ifdef INET
struct ip *h4;
#endif
#ifdef INET6
struct ip6_hdr *h6;
#endif
switch (af) {
#ifdef INET
case AF_INET:
h4 = mtod(m, struct ip *);
len = ntohs(h4->ip_len) - off;
break;
#endif
#ifdef INET6
case AF_INET6:
h6 = mtod(m, struct ip6_hdr *);
len = ntohs(h6->ip6_plen) - (off - sizeof(*h6));
break;
#endif
}
if (pf_check_proto_cksum(m, off, len, IPPROTO_TCP, af))
REASON_SET(&reason, PFRES_PROTCKSUM);
else {
if (th->th_flags & TH_SYN)
ack++;
if (th->th_flags & TH_FIN)
ack++;
pf_send_tcp(m, r, af, pd->dst,
pd->src, th->th_dport, th->th_sport,
ntohl(th->th_ack), ack, TH_RST|TH_ACK, 0, 0,
r->return_ttl, 1, 0, kif->pfik_ifp);
}
} else if (pd->proto != IPPROTO_ICMP && af == AF_INET &&
r->return_icmp)
pf_send_icmp(m, r->return_icmp >> 8,
r->return_icmp & 255, af, r);
else if (pd->proto != IPPROTO_ICMPV6 && af == AF_INET6 &&
r->return_icmp6)
pf_send_icmp(m, r->return_icmp6 >> 8,
r->return_icmp6 & 255, af, r);
pf_return(r, nr, pd, sk, off, m, th, kif, bproto_sum,
bip_sum, hdrlen, &reason);
}
if (r->action == PF_DROP)
@ -3543,8 +3558,11 @@ pf_test_rule(struct pf_rule **rm, struct pf_state **sm, int direction,
action = pf_create_state(r, nr, a, pd, nsn, nk, sk, m, off,
sport, dport, &rewrite, kif, sm, tag, bproto_sum, bip_sum,
hdrlen);
if (action != PF_PASS)
if (action != PF_PASS && r->rule_flag & PFRULE_RETURN) {
pf_return(r, nr, pd, sk, off, m, th, kif,
bproto_sum, bip_sum, hdrlen, &reason);
return (action);
}
} else {
if (sk != NULL)
uma_zfree(V_pf_state_key_z, sk);