I've been using ipfw for a while to create a router with NAT and packet filtering, but have never combined it with stateful filtering, instead using things like "established" to accept incoming TCP packets which are part of a conversation initiated from the "inside". I'd like to move to using keep-state/check-state to get tighter filtering and also to allow outgoing UDP and the replies, which currently I block. But I just can't get my head around how to do this. On the way out, should the dynamic rules be created to match the pre-NAT or post-NAT packets? The man pages are good at explaining both NAT and dynamic rules but not both in combination. Jim Hatfield
On Wed, Jun 11, 2003 at 11:05:00AM +0100, Subscriber wrote:> I've been using ipfw for a while to create a router with NAT > and packet filtering, but have never combined it with > stateful filtering, instead using things like "established" to > accept incoming TCP packets which are part of a conversation > initiated from the "inside". > > I'd like to move to using keep-state/check-state to get tighter > filtering and also to allow outgoing UDP and the replies, which > currently I block. > > But I just can't get my head around how to do this. On the way > out, should the dynamic rules be created to match the pre-NAT > or post-NAT packets? > > The man pages are good at explaining both NAT and dynamic > rules but not both in combination. >Jim, Attached is the conversation I had with Luigi Rizzo exactly three years ago on this topic. Maybe it is still helpful. Cheers, -- Ruslan Ermilov Sysadmin and DBA, ru@sunbay.com Sunbay Software Ltd, ru@FreeBSD.org FreeBSD committer -------------- next part -------------- An embedded message was scrubbed... From: Ruslan Ermilov <ru@FreeBSD.org> Subject: [IPFW] keep-state/check-state with divert Date: Thu, 8 Jun 2000 23:20:52 +0300 Size: 3682 Url: http://lists.freebsd.org/pipermail/freebsd-security/attachments/20030611/8d8c5778/attachment.eml -------------- next part -------------- An embedded message was scrubbed... From: Luigi Rizzo <luigi@info.iet.unipi.it> Subject: Re: [IPFW] keep-state/check-state with divert Date: Fri, 9 Jun 2000 07:25:34 +0200 (CEST) Size: 5386 Url: http://lists.freebsd.org/pipermail/freebsd-security/attachments/20030611/8d8c5778/attachment-0001.eml -------------- next part -------------- An embedded message was scrubbed... From: Ruslan Ermilov <ru@FreeBSD.org> Subject: Re: [IPFW] keep-state/check-state with divert Date: Wed, 14 Jun 2000 10:19:53 +0300 Size: 2570 Url: http://lists.freebsd.org/pipermail/freebsd-security/attachments/20030611/8d8c5778/attachment-0002.eml -------------- next part -------------- A non-text attachment was scrubbed... Name: not available Type: application/pgp-signature Size: 187 bytes Desc: not available Url : http://lists.freebsd.org/pipermail/freebsd-security/attachments/20030611/8d8c5778/attachment.bin
Subscriber wrote:> > I've been using ipfw for a while to create a router with NAT > and packet filtering, but have never combined it with > stateful filtering, instead using things like "established" to > accept incoming TCP packets which are part of a conversation > initiated from the "inside". > > I'd like to move to using keep-state/check-state to get tighter > filtering and also to allow outgoing UDP and the replies, which > currently I block. > > But I just can't get my head around how to do this. On the way > out, should the dynamic rules be created to match the pre-NAT > or post-NAT packets? > > The man pages are good at explaining both NAT and dynamic > rules but not both in combination. >## Example ## fxp0 = external nic xl0 = internal nic internal network = 10.10.10.0/24 internal traffic NAT'd to 1.2.3.4 ## handle nat traffic 100 divert 8668 ip from 10.10.10.0/24 to any out via fxp0 200 divert 8668 ip from any to 1.2.3.4 in via fxp0 300 check-state ## dynamic rules for internal clients access to everything ## needed so un-nat'd return traffic can flow out the ## internal nic to the internal clients 400 allow tcp from 10.10.10.0/24 to any keep-state via xl0 500 allow udp from 10.10.10.0/24 to any keep-state via xl0 ## dynamic rules allow natd alias address access to ## external resources 600 allow tcp from 1.2.3.4 to any keep-state out via fxp0 700 allow udp from 1.2.3.4 to any keep-state out via fxp0 You should also run natd with the "-deny_incoming" flag as an extra defense against bogus packets. good luck, greg
> -----Original Message----- > From: Greg Panula [mailto:greg.panula@dolaninformation.com] > Sent: 11 June 2003 13:21 > To: Subscriber > Cc: freebsd-security@freebsd.org > Subject: Re: IPFW: combining "divert natd" with "keep-state" > > ## Example ## > fxp0 = external nic > xl0 = internal nic > internal network = 10.10.10.0/24 > internal traffic NAT'd to 1.2.3.4 > > ## handle nat traffic > 100 divert 8668 ip from 10.10.10.0/24 to any out via fxp0 > 200 divert 8668 ip from any to 1.2.3.4 in via fxp0 > > 300 check-state > > ## dynamic rules for internal clients access to everything > ## needed so un-nat'd return traffic can flow out the > ## internal nic to the internal clients > 400 allow tcp from 10.10.10.0/24 to any keep-state via xl0 > 500 allow udp from 10.10.10.0/24 to any keep-state via xl0Thanks, for some reason I was fixated on putting all the rules on the external interface and having pass all from any to any via xl0 as the first rule in the list. I'll give this a go. Jim
On Wed, 11 Jun 2003 13:21:07 +0100, in local.freebsd.security you wrote:>## Example ## >fxp0 = external nic >xl0 = internal nic >internal network = 10.10.10.0/24 >internal traffic NAT'd to 1.2.3.4 > >## handle nat traffic >100 divert 8668 ip from 10.10.10.0/24 to any out via fxp0 >200 divert 8668 ip from any to 1.2.3.4 in via fxp0 > >300 check-state > >## dynamic rules for internal clients access to everything >## needed so un-nat'd return traffic can flow out the >## internal nic to the internal clients >400 allow tcp from 10.10.10.0/24 to any keep-state via xl0 >500 allow udp from 10.10.10.0/24 to any keep-state via xl0 > >## dynamic rules allow natd alias address access to >## external resources >600 allow tcp from 1.2.3.4 to any keep-state out via fxp0 >700 allow udp from 1.2.3.4 to any keep-state out via fxp0This appears to work but I am at a loss to understand how! If I follow one TCP packet all the way out to the Internet and its reply back to the internal net, there are four ipfw trips: A - request packet incoming on xl0 B - request packet outgoing on fxp0 C - reply packet incoming on fxp0 D - reply packet outgoing on xl0 Trip A matches rule 400 and is accepted, creating a dynamic rule which will match trip D. Trip B first matches rule 100, gets rewritten by natd then matches rule 600 and is sent, creating a dynamic rule matching a reply to 1.2.3.4. Trip C is the problem. It matches rule 200 so gets rewritten, and now does not match the dynamic rule created by trip B since that matches packets with 1.2.3.4 as destination address, which this packet no longer has. None of the other rules match either, so it is dropped. So how can it work????? This is the problem I have always been struggling with, ie should the dynamic rules match the incoming packets before or after they have been rewritten by natd to have their final destination address. I have always had the equivalent of "pass all from any to any via xl0", which replaces the dynamic rule created by trip A and used by trip D, but this doesn't alter the problem.
Well, I *did* figure it out.>>## Example ## >>fxp0 = external nic >>xl0 = internal nic >>internal network = 10.10.10.0/24 >>internal traffic NAT'd to 1.2.3.4 >> >>## handle nat traffic >>100 divert 8668 ip from 10.10.10.0/24 to any out via fxp0 >>200 divert 8668 ip from any to 1.2.3.4 in via fxp0 >> >>300 check-state >> >>## dynamic rules for internal clients access to everything >>## needed so un-nat'd return traffic can flow out the >>## internal nic to the internal clients >>400 allow tcp from 10.10.10.0/24 to any keep-state via xl0 >>500 allow udp from 10.10.10.0/24 to any keep-state via xl0 >> >>## dynamic rules allow natd alias address access to >>## external resources >>600 allow tcp from 1.2.3.4 to any keep-state out via fxp0 >>700 allow udp from 1.2.3.4 to any keep-state out via fxp0 > >This appears to work but I am at a loss to understand how! > >If I follow one TCP packet all the way out to the Internet and >its reply back to the internal net, there are four ipfw trips: > >A - request packet incoming on xl0 >B - request packet outgoing on fxp0 >C - reply packet incoming on fxp0 >D - reply packet outgoing on xl0 > >Trip A matches rule 400 and is accepted, creating a dynamic >rule which will match trip D. > >Trip B first matches rule 100, gets rewritten by natd then >matches rule 600 and is sent, creating a dynamic rule >matching a reply to 1.2.3.4. > >Trip C is the problem. It matches rule 200 so gets rewritten, >and now does not match the dynamic rule created by trip B >since that matches packets with 1.2.3.4 as destination >address, which this packet no longer has. None of the other >rules match either, so it is dropped. > >So how can it work?????It works because I wrongly assumed that dynamic rules check the interface if the rule which created them had a "via" clause. But reading the manual reveals that this is not so. So in my example above, the rule created by trip A is used during both trip C and trip D since it doesn't check the interface. The rule created by trip B is wasted - it's never used to match anything. The only use of the keep-state on rule 600 seems to be for conversations initiated by the router. I don't know why but I don't really like the lack of symmetry here. Plus there is a small problem in that if I telnet into the router then leave the session open for a long time, the rule is removed and next time I try to use the session it dies. I guess I can fix that by increasing the timeout from 5 minutes to 24 hours, or by adding another static rule which allows packets to go out on the internal network from the router itself. Jim
On Wed, 11 Jun 2003 12:20:20 +0100, in local.freebsd.security you wrote:>: ipfw -f flush >: ipfw add 100 divert natd ip from any to any via rl0 in >: ipfw add 200 check-state >: ipfw add 300 deny ip from 192.168.0.0/16 to any in via rl0 >: ipfw add 300 deny ip from any to 192.168.0.0/16 in via rl0 >: ipfw add 400 skipto 500 ip from any to any out via rl0 keep-state >: ipfw add 500 divert natd ip from any to any out via rl0 >: ipfw add 600 deny ip from 192.168.0.0/16 to any out via rl0 >: ipfw add 600 deny ip from any to 192.168.0.0/16 out via rl0 >: ipfw add 65000 allow ip from any to anyTricky indeed. I've been playing with the rules suggested by Greg Panula, but I don't really like them for a couple of reasons: - I prefer to keep the internal interface open. I often telnet into the router and keep the session open and inactive for hours, and the dynamic rules time out and kill it. - a rule is created which is never used, ie the outgoing packet starting a conversation creates two rules, only one of which is used in the check-state to match incoming. So I will try out your set. But one question first: do you ever get hits on the second rule 300? I would have thought it very difficult for anyone to route a packet to you with a non-routable destination address. Surely only your ISP could do that? Jim
On Wed, 11 Jun 2003 12:20:20 +0100, in local.freebsd.security you wrote:> >Attached is the conversation I had with Luigi Rizzo exactly >three years ago on this topic. Maybe it is still helpful.Well it was indeed. The use of skipto was the clue. I didn't go with any of the setups suggested but rolled my own using that idea. Here it is, in use so far for four days with no problems:>#!/bin/sh ># ># rc.firewall for NAT'ing firewall router - dynamic rules version. ># ># JPH -- 20th Jun 2003 Created. ># >fw="/sbin/ipfw -q" ># ># Interface and address definitions ># >eint=rl0 # External interface >iint=sis0 # Internal interface >inet="192.168.100.0/24" # Internal net ># ># Clear existing ruleset ># >$fw flush ># ># Transparent proxy: TCP packets to port 80 forwarded to Squid proxy ># >$fw add fwd 127.0.0.1,3128 tcp from $inet to any 80 in via $iint ># ># Internal interface and loopback interface are open ># >$fw add allow ip from any to any via $iint >$fw add allow ip from any to any via lo0 ># ># Packets still being processed are traversing the external interface ># De-NAT incoming packets to get back true destination address and port ># >$fw add divert natd ip from any to any in ># ># Dynamic rules: all outgoing packets create dynamic rules which are matched ># by both outgoing and incoming. Matching packets skip to rule 10000 ># >$fw add check-state >$fw add skipto 10000 ip from any to any out keep-state ># ># Here we handle unsolicited incoming packets. Allow selected ones in ># and block the rest. Our first reply will create a dynamic rule. ># >$fw add allow tcp from any to any 25 in setup >$fw add allow icmp from any to any in icmptype 0,3,4,11 >$fw add allow udp from any 67 to 255.255.255.255 68 in >$fw add deny log ip from any to any ># ># Packets matched by dynamic rules are tested here. ># Since they have matched a rule they can be passed. ># Outgoing packets still need to be NAT'ed first. ># >$fw add 10000 divert natd ip from $inet to any out >$fw add allow ip from any to anyI have a few extras in there that a "pure" router wouldn't need, ie the forwarding of http to a Squid cache and the acceptance of incoming SMTP, plus I have a Linksys DSL modem/bridge which broadcasts DHCPACK packets once a minute so I let them in to avoid polluting the logs. The driver behind this is that I wanted to be able to pass UDP safely so I could then move on to get linuxigd working, so I can use Windows Messenger to have free voice conversations with a friend a few thousand miles away. What a shame that when I finally get round to looking at linuxigd I realise that it is written to use ipf and not ipfw :-((