Hello,

so there is actually bug. I was able to reproduce it with very simple
rules on my router:

    set skip on em1
    block return all
    pass out on em0 from 192.168.2.0/24 to any nat-to(em0)

em1 is interface, facing to LAN
em0 is interface to internet where NAT happens.

I did use a scapy to generate ICMP host unreachable:
    d = '77.75.77.222'
    s = '192.168.2.5'
    sp = 1234
    dp = 12345
    pkt = Ether()/IP(dst=d)/ICMP(type=3, code=1, )/\
            IP(src=d', dst=s)/UDP(sport=sp, dport=dp)
    sendp(pkt, iface='iwn0')

the packet has been sent from my notebook via LAN towards router.  to my
surprise router forwards packet untranslated without creating a state.

the packet indeed matches the 'pass out on em0 rule...'
however 'keep state' and 'nat-to' actions are ignored. To understand
why we must look here at pf_test_rule():

4475         if (pd->virtual_proto != PF_VPROTO_FRAGMENT
4476             && !ctx.state_icmp && r->keep_state) {
4477
....
4491                 action = pf_create_state(pd, r, a, ctx.nr, &skw, &sks,
4492                     &rewrite, sm, ctx.tag, &ctx.rules, &ctx.act, ctx.sns);
4493
4494                 if (action != PF_PASS)
4495                         goto cleanup;
4496                 if (sks != skw) {
4497                         struct pf_state_key     *sk;
4498
4499                         if (pd->dir == PF_IN)
4500                                 sk = sks;
4501                         else
4502                                 sk = skw;
4503                         rewrite += pf_translate(pd,
4504                             &sk->addr[pd->af == pd->naf ? pd->sidx : 
pd->didx],
4505                             sk->port[pd->af == pd->naf ? pd->sidx : 
pd->didx],
4506                             &sk->addr[pd->af == pd->naf ? pd->didx : 
pd->sidx],
4507                             sk->port[pd->af == pd->naf ? pd->didx : 
pd->sidx],
4508                             virtual_type, ctx.icmp_dir);
4509                 }
4510
4511 #ifdef INET6
4512                 if (rewrite && skw->af != sks->af)
4513                         action = PF_AFRT;
4514 #endif /* INET6 */
4515
4516         } else {
4517                 while ((ctx.ri = SLIST_FIRST(&ctx.rules))) {
4518                         SLIST_REMOVE_HEAD(&ctx.rules, entry);
4519                         pool_put(&pf_rule_item_pl, ctx.ri);
4520                 }
4521         }

note '!ctx.state_icmp' at line 4476. This variable is being set
by pf_icmp_mapping() function very early in pf_test_rule() function.

4354         switch (pd->virtual_proto) {
4355         case IPPROTO_ICMP:
4356                 ctx.icmptype = pd->hdr.icmp.icmp_type;
4357                 ctx.icmpcode = pd->hdr.icmp.icmp_code;
4358                 ctx.state_icmp = pf_icmp_mapping(pd, ctx.icmptype,
4359                     &ctx.icmp_dir, &virtual_id, &virtual_type);
4360                 if (ctx.icmp_dir == PF_IN) {
4361                         pd->osport = pd->nsport = virtual_id;

for ICMP error messages the ctx.state_icmp value is set to one.
It happens right here in pf_icmp_mapping():

2582                 /* These ICMP types map to other connections */
2583                 case ICMP_UNREACH:
2584                 case ICMP_SOURCEQUENCH:
2585                 case ICMP_REDIRECT:
2586                 case ICMP_TIMXCEED:
2587                 case ICMP_PARAMPROB:
2588                         /* These will not be used, but set them anyway */
2589                         *icmp_dir = PF_IN;
2590                         *virtual_type = htons(type);
2591                         *virtual_id = 0;
2592                         return (1);  /* These types match to another state 
*/


this explains why pf ignores 'keep state' and 'nat-to' action for ICMP errors.

It's tempting to fix things up in pf_test_rule() in else branch lines 4517-4520.
However I'm afraid this approach may create some undesired side-effect.
in my opinion is to fix pf_match_rule() function, so ICMP error message
will no longer match 'keep state' rule. Diff below is for IPv4. I still
need to think of more about IPv6. My gut feeling is it will be very similar.

thanks and
regards
sashan

--------8<---------------8<---------------8<------------------8<--------
diff --git a/sys/net/pf.c b/sys/net/pf.c
index 4f0fc3f91a9..0993aed85fb 100644
--- a/sys/net/pf.c
+++ b/sys/net/pf.c
@@ -4148,6 +4148,9 @@ enter_ruleset:
                            (r->rule_flag & PFRULE_STATESLOPPY) == 0 &&
                            ctx->icmp_dir != PF_IN),
                                TAILQ_NEXT(r, entries));
+                       /* icmp packet must match existing state */
+                       PF_TEST_ATTRIB(r->keep_state && ctx->state_icmp,
+                               TAILQ_NEXT(r, entries);
                        break;
 
                case IPPROTO_ICMPV6:

Reply via email to