Hi,

# let me correct the previous mail, it has some typos.

Currently SO_BINDANY is usable without any divert or divert-reply
rule.

pf reserves its associated PCB to its state when the packet is going
out.  This time, the pf rule is not required to have "divert" or
"divert-reply" option.  When receiving reverse direction packets,
those packets are going to "ours" since they has the associated PCB.

But when dropping the connection, the PCB is deleted but the state
will not removed.  Currently pf removes the state only if it is
created by a rule with "divert-reply" or "divert" option.  Otherwise
the state is kept.

As the result, following incoming packets for the connection will be
forwarded by the state.  They should not be forwarded since they were
going to "ours".

I think the state should be deleted even if it's created by a rule
without "divert" or "divert-reply" option. The following diff will
change this behavior.  Also I attached a test procedure after the
diff.


ok? comments?

Don't keep a state when associated PCB is delete regardless it's
created without a "divert-to" or "divert-reply" rule.  It might be
created by SO_BINDANY.

Index: sys/net/pf.c
===================================================================
RCS file: /cvs/src/sys/net/pf.c,v
retrieving revision 1.1094
diff -u -p -r1.1094 pf.c
--- sys/net/pf.c        24 Jul 2020 18:17:15 -0000      1.1094
+++ sys/net/pf.c        25 Jul 2020 07:39:19 -0000
@@ -1410,9 +1410,7 @@ pf_remove_divert_state(struct pf_state_k
        struct pf_state_item    *si;
 
        TAILQ_FOREACH(si, &sk->states, entry) {
-               if (sk == si->s->key[PF_SK_STACK] && si->s->rule.ptr &&
-                   (si->s->rule.ptr->divert.type == PF_DIVERT_TO ||
-                   si->s->rule.ptr->divert.type == PF_DIVERT_REPLY)) {
+               if (sk == si->s->key[PF_SK_STACK]) {
                        pf_remove_state(si->s);
                        break;
                }



network configuration:

  192.168.0.101 -- 192.168.0.1 [OBJ] 10.0.0.1 --> 10.0.0.10

setup:

  ifconfig pair100 rdomain 10
  ifconfig pair100 inet 192.168.0.1
  ifconfig pair101 rdomain 11 patch pair100
  ifconfig pair101 inet 192.168.0.101
  ifconfig pair102 rdomain 10
  ifconfig pair102 inet 10.0.0.1/24
  ifconfig pair103 rdomain 12 patch pair102
  ifconfig pair103 inet 10.0.0.101/24
  route -T11 add default 192.168.0.1

/etc/pf.conf:

  pass on {pair100 pair101 pair102 pair103}
  match out on pair102 nat-to (pair102:0)
  block in on pair103 proto tcp to port 443

procedure:

1. run a server by scapy on 443/tcp on rdomain 12

   $ doas route -T12 exec python test.py

2. connect to the server from OBJ (rdomain 10)

   $ doas route -T10 exec nc -vs 192.168.0.101 10.0.0.101 443
   Connection to 10.0.0.101 443 port [tcp/https] succeeded!
   Ctrl-D
   $

   close the connection by Ctrl-D immediately

3. see the packet capture on pair103

   - You can see packets like below
     19:28:51.822879 10.0.0.101.443 > 10.0.0.1.60956: . ack 1 win 8192
     19:28:51.823559 192.168.0.101.22083 > 10.0.0.101.443: R 0:0(0)
       ack 1 win 0 (DF) [tos 0x10]
     
   - Since the pf state is kept, the packet "10.0.0.101.443 >
     10.0.0.1.60956" is converted into "10.0.0.101.443 >
     192.168.0.101.22083" by the state's NAT
   - but since the PCB doesn't exist, the packet is forwarded.
   - but the packet is blocked by default "block return" rule
   - "192.168.0.101.22083 > 10.0.0.101.443" is the result of "block
     return"
   
   -> 192.168.0.101 is NATed address.  It should not appear on
      10.0.0.0/24 network.

teardown:

  ifconfig pair100 destroy
  ifconfig pair101 destroy
  ifconfig pair102 destroy
  ifconfig pair103 destroy

test.py
***
import time
from scapy.all import *

a=sniff(iface="pair102", count=1, filter="tcp and port 443")

ip_src = a[0][IP].src
ip_dst = a[0][IP].dst
sport =  a[0][TCP].sport
dport =  a[0][TCP].dport
seq_nr = 50000
ack_nr = a[0][TCP].seq + 1

a=sr1(IP(src=ip_dst, dst=ip_src)/
      TCP(sport=dport, dport=sport, flags="SA", seq=seq_nr, ack=ack_nr,
      options=[('MSS', 1460)]))
#ack_nr = a[0][TCP].seq + 1

# Send FIN and receive FIN+ACK
seq_nr = seq_nr + 1
a=sr1(IP(src=ip_dst, dst=ip_src)/
  TCP(sport=dport, dport=sport, flags="FA", seq=seq_nr, ack=ack_nr))
ack_nr = a[0][TCP].seq + 1

time.sleep(2)

# Send ACK of FIN
lastack = (IP(src=ip_dst, dst=ip_src)/
      TCP(sport=dport, dport=sport, flags="A", seq=seq_nr, ack=ack_nr))
send(lastack)

# Resend in 100 times
for _ in range(100):
    time.sleep(2)
    send(lastack)
***

Reply via email to