IPFW IPFW behaving unexpectedly

Hello

Some time ago I set up a small IPFW firewall using built-in kernel NAT that is exposing a jailed web server to an outside network. Today I wanted to add some rules to allow some jails to access the internet, but while doing this I have noticed that according to my interpretation of this firewall it actually should not pass the outgoing packets sent by my web server. I have managed to strip down this firewall to some bare minimum for this server to work and I do not really comprehend how it is working. Here is my configuration after being stripped down to just expose the web server (172.16.0.3: web server, 172.16.0.2: MySQL):

Code:
01400 nat 1 tcp from 172.16.0.3 3010 to any out via em0
01500 skipto 30001 tcp from any to me 3010 in via em0 setup keep-state :default
02100 check-state :default
02900 allow tcp from 172.16.0.3 to 172.16.0.2 3308 out via lo1 setup keep-state :default
30000 deny ip from any to any
30500 nat 1 tcp from any to me 3010 in via em0
30600 allow tcp from any to 172.16.0.3 3010 in via em0
65535 deny ip from any to any

Now, this is how I would expect this firewall to behave:

1. Client sends a packet to my jail host via em0 interface onto 3010 port
2. rule 01500 is matched and saved to temporary table, jump to rule 30001
3. rule 30500 is matched, destination address of the packed is set to 172.16.0.3 (nat 1 is configured to pass all traffic from 3010 to 172.16.0.3:3010)
4. rule 30600 is matched, packed is passed to 172.16.0.3:3010
5. server processes the request, a response is sent
6. rule 1400 is matched, packed is run through NAT
7. rule 1500 is not matched (wrong destination)
8. check-state matches dynamic rule crated in step 2 and executes rule 1500 skipping to 30001
9. rule 30500 is not matched (wrong destination)
10. rule 30600 is not matched (wrong destination)
11. rule 65535 is matched and web server response is dropped.

Now, what I described does not actually happen and I can connect to the web server without any problems (while other packets like ICMP, SSH, outbound server connections etc. are not passed just like expected).
I guess this behavior is not a bug, but me badly understanding how IPFW works. Would anyone be able to point out where I have made a mistake? I use FreeBSD 11.2.
Thanks in advance
 
8. check-state matches dynamic rule crated in step 2 and executes rule 1500 skipping to 30001
This doesn't seem correct. Yes, it checks the state dynamically but why would it also execute the previous rule?
 
It had been set to 1 but after I changed it to 0 everything behaved as expected. After some adjustments I found out that this firewall is sufficient for my web server to function:
Code:
01400 nat 1 tcp from 172.16.0.3 3010 to any out via em0
02900 allow tcp from 172.16.0.3 to 172.16.0.2 3308 out via lo1 setup keep-state :default
30500 nat 1 tcp from any to me 3010 in via em0
31000 check-state :default
65535 deny ip from any to any
So I guess that with net.inet.ip.fw.one_pass set to 1 all packets run through NAT are automatically passed? I cannot find anything about this in IPFW manual pages, FreeBSD handbook or anywhere else really. Shouldn't this be mentioned somewhere?
 
SirDice
check-state run a check against dynamic table and if record is found it execute the rule action that invoke the keep-state. In this case the rule action is skipto.

zbrojny120
net.inet.ip.fw.one_pass control if the packet is returning back to the ipfw checking after it goes out for example for divert,dummynet,nat etc. Then it continue to be checked until action that terminate the search is matched.

It works as following

client PC (203.0.113.2) --- em0 (203.0.113.1) --- lo1 (172.16.0.1) --- web server(172.16.0.3)

NAT table containing your static link specifyed by your nat config and dynamic link created by packets that match rule 01400 or 30500
-----------------
(protocol, local addr: local port, alias addr: alias port, remote addr: remote port)
tcp 203.0.113.1:3010 172.16.0.3:3010 --- ---
tcp 203.0.113.1:3010 172.16.0.3:3010 203.0.113.2:49152

Incoming packet from client pc to em0 203.0.113.1:3010
1. Client PC sends a TCP packet from 203.0.113.2 (upper random port) 49152 to your server in em0 203.0.113.1:3010
2. The packet is received by em0 driver by ether_demux() then checked by ip_input() and passed to ipfw(8) for inspection via ipfw_chk()
3. rule 01500 match (Dynamic rule is created 01500 STATE tcp 203.0.113.1 3010 <-> 203.0.113.2 49152 :default)
4. rule 30500 match (creating a translation in the NAT table) and depending of sysctl net.inet.ip.fw.one_pass is ether passed to the next ipfw rule or is going out to ip_output() like there's allow rule and no other inspections are done.
if net.inet.ip.fw.one_pass=0
5. rule 30600 match and the packet is transmitted to ip_output()

Outgoing packet from web server 172.16.0.3:3010 to client pc
1. web server sends a TCP packet from 172.16.0.3:3010 to 203.0.113.2:49152
2. The packet is received by lo1 then it goes via ip_input() as there's no local route it's forwarded via ip_forward() with reducing it's TTL then it's checked if gateway_enable="YES" (net.inet.ip.forwarding:1) in /etc/rc.conf and transmitted to ip_output() via em0 and then checked by ipfw_chk()
3. rule 01400 match (if net.inet.ip.fw.one_pass is 1 the packed is leaving the interface em0 as 203.0.113.1:3010 and no other inspections are done)
if net.inet.ip.fw.one_pass=0
4. rule 02100 match (dynamic rule 01500 is matched and action skipto 30001 is executed)
5. rule 65535 match
 
I think I get it now. I guess I will have to somehow restructure these rules to make them do what they are actually supposed to do. Thanks for your help!
 
You can use deny_in option. It will look like this. With net.inet.ip.fw.one_pass=1

ipfw nat 1 config if em0 log deny_in same_ports reset redirect_port tcp 172.16.0.3:3010 3010

Code:
00100 allow ip from any to any via lo0
00200 deny ip from any to 127.0.0.0/8
00300 deny ip from 127.0.0.0/8 to any
00400 deny ip from any to ::1
00500 deny ip from ::1 to any
00600 allow ipv6-icmp from :: to ff02::/16
00700 allow ipv6-icmp from fe80::/10 to fe80::/10
00800 allow ipv6-icmp from fe80::/10 to ff02::/16
00900 allow ip6 from any to any proto ipv6-icmp ip6 icmp6types 1
01000 allow ip6 from any to any proto ipv6-icmp ip6 icmp6types 2,135,136
01100 check-state :default
01200 allow tcp from me to any established
01300 allow tcp from me to any setup keep-state :default
01400 allow udp from me to any keep-state :default
01500 allow icmp from me to any keep-state :default
01600 allow ipv6-icmp from me to any keep-state :default
01700 allow udp from 0.0.0.0 68 to 255.255.255.255 67 out
01800 allow udp from any 67 to me 68 in
01900 allow udp from any 67 to 255.255.255.255 68 in
02000 allow udp from fe80::/10 to me 546 in
02100 allow icmp from any to me icmptypes 8
02200 allow ip6 from any to me proto ipv6-icmp ip6 icmp6types 128,129
02300 allow icmp from any to me icmptypes 3,4,11
02400 allow ip6 from any to me proto ipv6-icmp ip6 icmp6types 3
02510 allow tcp from any to me 22
05000 allow ip from any to any via lo1
55000 nat 1 ip from any to any via em0
65535 deny ip from any to any
 
Back
Top