Bug 29294

Summary: IPFW dynamic rules and NATD interaction has logical design flaw
Product: Base System Reporter: mikescott <mikescott>
Component: kernAssignee: freebsd-bugs (Nobody) <bugs>
Status: Closed FIXED    
Severity: Affects Only Me CC: mikescott
Priority: Normal    
Version: 4.3-RELEASE   
Hardware: Any   
OS: Any   

Description mikescott 2001-07-29 09:40:01 UTC
	There seems to be a logical error in the way natd is handled in
	conjunction with the ipfw firewall rules.  I've asked on the
	questions and hackers lists about this, but there's been little
	response - one person said my config file was wrong, another vaguely
	remembered a problem of this sort.
	(1) ipfw rules are handled top down, stop on first match.
	(2) therefore the NAT diversion must be the first item in the list,
	or it may never be reached.
	(3) keep-state and check-state must therefore both follow the NAT
	diversion. (So no trickery allowed having before-nat and after-nat
	state checks)
	(4) keep-state and check-state must work on the same set of addresses,
	either both internal, or both external.
	(5) the list of firewall rules is traversed in the same order for
	incoming and outgoing packets
	(6) therefore all rules for incoming packets are applied to *local*
	addresses, all rules for outgoing are applied to *external* addresses.
	(7) keep-state and check-state are normally applied to packets flowing
	in opposite directions.
	(8) Therefore, they are applied inconsistently to incoming and
	outgoing addresses (keep-state may save an internal address, but
	check-state will be applied to an external address, and vice versa)

Fix: 

Assuming I'm right, the "call" to natd doesn't belong in the fw rules.
	It should always occur just after packets are read in, just before
	they're written out to the network.
	
	Workaround is not to use dynamic rules.
How-To-Repeat: 	I assume that any ipfw config of the form
	$fwcmd add divert natd all from any to any via tun0
	...
	$fwcmd add check-state
	$fwcmd add deny log tcp from any to any established
	$fwcmd add allow log tcp from any to any out via tun0 keep-state

	will exhibit the wrong behaviour.  'ipfw show' will show the
	dynamic rule(s) with the wrong addresses in
Comment 1 david 2001-08-06 16:14:36 UTC
For this to work, you need to split your firewall rules between incoming and
outgoing packets and divert them to natd at different times.

i.e.

add 1 skipto 30000 ip from any to any out
# All packets at this point are now inbound
# Map incoming external IPs to internal
add 100 divert natd ip from any to any via tun0
# Allow any packets that are part of an ongoing connection
add 200 check-state
add 300 deny log ip from any to any

# Outgoing packets are processed here
# Add in dynamic rule using non-NAT addresses
add 30000 skipto 30100 ip from any to any via tun0 keep-state
# Do NAT
add 30100 divert natd ip from any to any via tun0
add 30200 allow ip from any to any

From this, both keep-state and check-state will work on internal (i.e.
before-NAT) addresses.

Hope this helps,

David
--
Dr David Hedley, R&D Director,
Intelligent Network Technology Ltd, Bristol, UK
http://www.inty.net/


--
Information in this electronic mail message is confidential
and may be legally privileged. It is intended solely for
the addressee. Access to this message by anyone else is
unauthorised. If you are not the intended recipient any 
use, disclosure, copying or distribution of this message is
prohibited and may be unlawful. When addressed to our
customers, any information contained in this message is
subject to INT Ltd Terms & Conditions.
--

This email has been virus scanned using Sophos Anti-Virus by intY (www.inty.net)
Comment 2 mikescott 2001-08-07 16:20:38 UTC
On 6 Aug 2001, at 16:14, David Hedley wrote:
> For this to work, you need to split your firewall rules between incoming and
> outgoing packets and divert them to natd at different times.
> 
> i.e.
> 
> add 1 skipto 30000 ip from any to any out
> # All packets at this point are now inbound
> # Map incoming external IPs to internal
> add 100 divert natd ip from any to any via tun0
> # Allow any packets that are part of an ongoing connection
> add 200 check-state
> add 300 deny log ip from any to any
> 
> # Outgoing packets are processed here
> # Add in dynamic rule using non-NAT addresses
> add 30000 skipto 30100 ip from any to any via tun0 keep-state
> # Do NAT
> add 30100 divert natd ip from any to any via tun0
> add 30200 allow ip from any to any
> 
> >From this, both keep-state and check-state will work on internal (i.e.
> before-NAT) addresses.
> 
> Hope this helps,
> 
> David

Ah, I see. Thanks for that.  Amazing how you can't see the wood sometimes.

However, even though there is a workable solution, I'd still contend 
the basic design is logically flawed -- it's the embedding of the nat 
diversion into the firewall rules that requires that the rules be so 
split: I'm no OS design expert, but I'm still prepared to stick my 
neck out and say the nat should occur immediately data is 
transferred to/from the device, in which case the firewall rules deal 
only with one set of addresses and are much simpler.  I *think* this 
is what happens with ipf, to which (in desperation :-) ) I've already 
switched -- certainly the equivalent rules for ipf are much simpler 
and seem to work as expected. (As an aside, I wonder why fbsd 
offers two different firewalls?  ipf seems to offer a superset of ipfw's 
facilities, claims to be portable, and if my experience is anything to 
go by, is easier to set up: why keep ipfw?)

Anyway, as this would seem to be a "feature" rather than a "fault", 
may I suggest to anyone involved in the documentation that a 
working example might be useful in future please!

Thanks again, anyway!

--
various incoming sites blocked because of spam:
see www.mikescott.clara.net for a list
mikescott@clara.net           Mike Scott 
aka mikeascott@ntlworld.com   Harlow Essex England
Comment 3 Crist J. Clark 2001-08-16 17:47:57 UTC
On Tue, Aug 07, 2001 at 08:30:02AM -0700, mikescott@clara.net wrote:
>  On 6 Aug 2001, at 16:14, David Hedley wrote:
>  > For this to work, you need to split your firewall rules between incoming and
>  > outgoing packets and divert them to natd at different times.

No, there is a much easier way.

  $fwcmd add divert natd all from any to any via ${natd_interface}
  $fwcmd add check-state
  $fwcmd add pass ip from ${oip} to any out via ${oif} keep-state
  $fwcmd add pass ip from ${net} to any in  via ${iif} keep-state

Just create dynamic rules on packets going _in_ the internal
interface(s). We now get two dynamic rules for each "connection," one
using the translated source one using the private net address.

>  However, even though there is a workable solution, I'd still contend 
>  the basic design is logically flawed -- it's the embedding of the nat 
>  diversion into the firewall rules that requires that the rules be so 
>  split: I'm no OS design expert, but I'm still prepared to stick my 
>  neck out and say the nat should occur immediately data is 
>  transferred to/from the device, in which case the firewall rules deal 
>  only with one set of addresses and are much simpler.

This would require NAT to take place in the kernel which is against
one of the fundamental design assumptions of natd(8). natd(8) works
the way it does for good reasons. However, it is not perfect, there
are both pros and cons to the design.

>  I *think* this 
>  is what happens with ipf, to which (in desperation :-) )

Yes. ipf(8) and ipnat(8) live in the kernel. This design also has pros
and cons.

>  I've already 
>  switched -- certainly the equivalent rules for ipf are much simpler 
>  and seem to work as expected. (As an aside, I wonder why fbsd 
>  offers two different firewalls?  ipf seems to offer a superset of ipfw's 
>  facilities, claims to be portable, and if my experience is anything to 
>  go by, is easier to set up: why keep ipfw?)

ipfw(8) is the native firewall. ipf(8) is externally maintained.

I think the only real bug that might be here is that the default
rc.firewall does not actually work with natd(8) and that is not what
the original PR is about. Unless someone objects, I am going to close
this PR later today. If someone feels the need to, a PR about
rc.firewall can be submitted separately.
-- 
Crist J. Clark                           cjclark@alum.mit.edu
Comment 4 Crist J. Clark freebsd_committer freebsd_triage 2001-08-17 21:35:09 UTC
State Changed
From-To: open->closed

The issue can be solved by writing your firewall rules in the correct 
fashion. Thie "problem" is a design choice, not a bug.