ipfilter (now pf) for dummies

IPFW, PF, IPF (but not limited) related discussion

ipfilter (now pf) for dummies

Postby tomh009 » 18 Jan 2009, 22:58

Our mail setup uses Postini to handle spam and viruses. It costs money but does a decent job, with no admin required (anyway, changing that isn't an option at the moment). The trouble is with the spammers who ignore MX and scan for open SMTP ports, and thus bypass Postini.

Having just upgraded our mail server to 7.0-RELEASE (yeah, started too early for 7.1), I want to address this issue, too. I spent most of the day fighting with the mailfromd milter, but I think it's not the right tool in the end.

So I'm looking for a dead-simple ipfilter configuration:
  • allow all traffic not on port 25
  • allow port 25 from 64.18.0.0/20
  • allow port 25 from 207.126.144/20
  • disallow other port 25


So trying to figure out what I need ...
Code: Select all
pass out on bge0 from any to any
pass in on bge0 from any to any port < 25
pass in on bge0 from any to any port > 25
pass in on bge0 from 64.18.0.0/20 to any port = 25
pass in on bge0 from 207.126.144/20 to any port = 25
pass in on bge0 from 127.0.0.1 to any port = 25
block in on bge0 from any to any port = 25


Does this make sense? I'm always hesitant about firewall rules lest I block myself out! :\
User avatar
tomh009
Junior Member
 
Posts: 38
Joined: 21 Nov 2008, 22:52
Location: Great White North

Postby anomie » 19 Jan 2009, 00:48

tomh009 wrote:I'm always hesitant about firewall rules lest I block myself out!


I can't evaluate your ipfilter ruleset (I've used only pf and ipfw), but your concern is definitely legit.

I recommend using an at(1) job for insurance against this. I wrote up a brief example here: http://daemonforums.org/showthread.php?t=887

I do the same thing on both FreeBSD (pf) and RHEL (iptables) boxes to be sure I don't get locked out.
User avatar
anomie
Member
 
Posts: 783
Joined: 17 Nov 2008, 04:37
Location: Texas

Postby tomh009 » 19 Jan 2009, 03:05

anomie wrote:I can't evaluate your ipfilter ruleset (I've used only pf and ipfw), but your concern is definitely legit.


Should I use ipfw instead of ipf? It wasn't clear to me which would be the most appropriate tool for this.

Thanks for the suggestion on at(1).
User avatar
tomh009
Junior Member
 
Posts: 38
Joined: 21 Nov 2008, 22:52
Location: Great White North

Postby anomie » 19 Jan 2009, 05:35

Well, what I can tell you is that pf and ipfw (in that order) certainly seem to be more popular. i.e. You'll likely get better peer/community support using one of those two.
User avatar
anomie
Member
 
Posts: 783
Joined: 17 Nov 2008, 04:37
Location: Texas

Postby tomh009 » 19 Jan 2009, 18:38

All right ... that's the trouble with so many tools, never know which one is the best! So trying to adapt to the pf.conf syntax (not so much different), does this look reasonable?
Code: Select all
pass out from any to any
pass in proto tcp from any to any port < 25
pass in proto tcp from any to any port > 25
pass in proto tcp from 64.18.0.0/20 to any port = 25 label "postini-1"
pass in proto tcp from 207.126.144/20 to any port = 25 label "postini-2"
pass in proto tcp from 127.0.0.1 to any port = 25 label "local"
block in proto tcp from any to any port = 25 label "spam"
pass in from any to any
User avatar
tomh009
Junior Member
 
Posts: 38
Joined: 21 Nov 2008, 22:52
Location: Great White North

Postby SirDice » 19 Jan 2009, 20:03

Your last rule annihilates everything else and lets everything in.
PF and IPFILTER don't stop matching rules when one hits ;)

I'd change it slightly to this:
Code: Select all
ext_if="bge0"
block in all
# Allow traffic to/from localhost
pass in quick on lo0 all
pass out quick on lo0 all

# We trust our own host :}
pass out on $ext_if from any to any keep state

# SMTP in
pass in quick on $ext_if proto tcp from 64.18.0.0/20 to any port = 25 label "postini-1" keep state
pass in quick on $ext_if proto tcp from 207.126.144/20 to any port = 25 label "postini-2" keep state


You can "short-circuit" rules with the quick keyword. The "keep state" keeps track of tcp/ip sessions, so you don't have to worry about the returning answer.
Not sure what you want to do with the labels though. They're only used in the logs. I have these:
Code: Select all
block in log on $ext_if inet proto udp all label "BlockIn_ExtIF_UDP"
block in log on $ext_if inet proto icmp all label "BlockIn_ExtIF_ICMP"
block return-rst in log on $ext_if inet proto tcp all label "BlockIn_ExtIF_TCP"


Edit: Oh.. remote editing a firewall can be slightly dangerous indeed :e
Code: Select all
# check syntax:
# pfctl -nf /etc/pf.conf
# test: run rules for 60 sec. then disable firewall
# pfctl -f /etc/pf.conf && sleep 60 && pfctl -d
# green light :)
# pfctl -f /etc/pf.conf
User avatar
SirDice
Old Fart
 
Posts: 16185
Joined: 17 Nov 2008, 16:50
Location: Rotterdam, Netherlands

Postby anomie » 19 Jan 2009, 23:21

tomh009 wrote:So trying to adapt to the pf.conf syntax (not so much different)


Just a couple references, if you haven't already located them on your own, to get you up to speed quickly on pf:

The first is a thorough pf tutorial and reference. The second contains some important FreeBSD-specific pf notes.

To elaborate on SirDice's point, there is one key gotcha when using pf for packet filtering: the last matching rule wins (with some exceptions, but no need to split hairs yet). This is very different than e.g. ipfw, iptables (Linux), Cisco FWSM, etc.
User avatar
anomie
Member
 
Posts: 783
Joined: 17 Nov 2008, 04:37
Location: Texas

Postby tomh009 » 20 Jan 2009, 17:53

SirDice wrote:Your last rule annihilates everything else and lets everything in.
PF and IPFILTER don't stop matching rules when one hits ;)


Ah -- last-match rather than first-match. Good to know! :)

So using that principle, and opening things up some more, I'm thinking that this might possibly work -- what do you think?

Code: Select all
ext_if="bge0"
block in all
# Allow traffic to/from localhost
pass in quick on lo0 all
pass out quick on lo0 all

# Default to allow all, unless matched later
pass in on $ext_if from any to any keep state
pass out on $ext_if from any to any keep state

# Block SMTP by default
block in log on $ext_if inet proto tcp from any to any port = 25 label "Block SMTP"

# Allow SMTP from Postini
pass in quick on $ext_if proto tcp from 64.18.0.0/20 to any port = 25 label "postini-1" keep state
pass in quick on $ext_if proto tcp from 207.126.144/20 to any port = 25 label "postini-2" keep state


And thanks for the tip on testing!
User avatar
tomh009
Junior Member
 
Posts: 38
Joined: 21 Nov 2008, 22:52
Location: Great White North

Postby SirDice » 20 Jan 2009, 18:58

tomh009 wrote:Ah -- last-match rather than first-match. Good to know! :)

Yes. The quick keyword short-circuits this, in case of lo0 traffic (our ruleset :e ) it'll stop matching any of the rules following it.

So using that principle, and opening things up some more, I'm thinking that this might possibly work -- what do you think?


If you're going to allow all traffic anyway you could loose that block in all at the beginning. And the quick keywords in the last 2 rules are a bit pointless. But it should work fine as is though :)
User avatar
SirDice
Old Fart
 
Posts: 16185
Joined: 17 Nov 2008, 16:50
Location: Rotterdam, Netherlands

Postby tomh009 » 21 Jan 2009, 00:18

After figuring out that the pf and pflog modules are not loaded by default (used kldload for now, and added to loader.conf for the future), I got it running. The script worked great, no trouble there. And within a few minutes ...
Code: Select all
montecarlo 95 # tcpdump -n -r /var/log/pflog     
reading from file /var/log/pflog, link-type PFLOG (OpenBSD pflog file)
19:10:47.559784 IP 58.60.97.164.2056 > *.*.98.4.25: S 3141617944:3141617944(0) win 65535 <mss 1440,nop,nop,sackOK>
19:10:50.477568 IP 58.60.97.164.2056 > *.*.98.4.25: S 3141617944:3141617944(0) win 65535 <mss 1440,nop,nop,sackOK>
19:10:56.487070 IP 58.60.97.164.2056 > *.*.98.4.25: S 3141617944:3141617944(0) win 65535 <mss 1440,nop,nop,sackOK>


Ah-ha! So who is that?

Code: Select all
montecarlo 97 # whois -A 58.60.97.164
% [whois.apnic.net node-1]
% Whois data copyright terms    http://www.apnic.net/db/dbcopyright.html

inetnum:      58.60.0.0 - 58.63.255.255
netname:      CHINANET-GD
descr:        CHINANET Guangdong province network
descr:        China Telecom
(...)


The usual suspects at work -- but foiled by pf! :e

Thanks very much for the help, guys!
User avatar
tomh009
Junior Member
 
Posts: 38
Joined: 21 Nov 2008, 22:52
Location: Great White North

Postby J65nko » 21 Jan 2009, 03:04

To prevent problems with TCP window scaling please add flags S/SA to those 'keep states' rules.
Code: Select all
pass in quick on $ext_if proto tcp from 64.18.0.0/20 to any port = 25 label "postini-1" [color=blue] flags S/SA[/color] keep state
pass in quick on $ext_if proto tcp from 207.126.144/20 to any port = 25 label "postini-2" [color=blue] flags S/SA[/color] keep state

From http://undeadly.org/cgi?action=article&sid=20060928081238
Create TCP states on the initial SYN packet

Ideally, TCP state entries are created when the first packet of the connection, the initial SYN is seen. You can enforce this by following a simple principle:

Use 'flags S/SA' on all 'pass proto tcp keep state' rules!

All initial SYN packets (and only those packets) have flag SYN set but flag ACK not set. When all your 'keep state' rules that can apply to TCP packets are restricted these packet, only initial SYN packets can create states. Therefore, any TCP state created is created based on an initial SYN packet.

The reason for creating state only on initial SYN packets is a TCP extention called 'window scaling' defined in RFC 1323.....
J65nko
Member
 
Posts: 422
Joined: 17 Nov 2008, 00:06
Location: Budel, Netherlands

Postby SirDice » 21 Jan 2009, 08:44

J65nko wrote:To prevent problems with TCP window scaling please add flags S/SA to those 'keep states' rules.


Doesn't scrub in all take care of that?
User avatar
SirDice
Old Fart
 
Posts: 16185
Joined: 17 Nov 2008, 16:50
Location: Rotterdam, Netherlands

Postby DutchDaemon » 21 Jan 2009, 10:12

Scrub handles various things like reassembling/normalisation of fragments, protecting/hardening of sequence numbers and/or timestamps, sequence number wrapping (paws), but does not handle window scaling. One could call it 'window cleaning' at best ;)
User avatar
DutchDaemon
Old Fart
 
Posts: 10464
Joined: 16 Nov 2008, 20:17
Location: The Netherlands


Return to Firewalls

Who is online

Users browsing this forum: No registered users and 0 guests