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) ***