FreeBSD ipfw Rules
I have setup some simplified ipfw rules on my FreeBSD 8 wireless access point/firewall.
These scripts and my explanation of them assume you have some understanding of network interfaces, ipfw, ipnat, DHCP, cron, and basic 'nix operations.
There are three networks - the internet, the WEP WLAN, and the WPA WLAN (setup as shown here).
The WEP WLAN is 10.0.0.0/28 (10.0.0.1 to 10.0.0.15) and the WPA WLAN is 10.0.1.0/28 (10.0.1.1 to 10.0.1.15). Systems on the WEP WLAN must use my proxy (squid) on the firewall to surf the 'net. On the WPA WLAN, however, all outgoing traffic is permitted.
I know that allowing all outgoing traffic is dirty evil, but things like Belkin Skype wireless phones are happier that way.
Obviously you must edit these scripts to match your network interfaces and IP space, etc.
Also, I run a webserver and a mailserver, and allow incoming SSH connections, so incoming ports 22 (SSH), 25 (SMTP), 80 (HTTP), and 443 (HTTPS) are part of my ipnat and ipfw rules (see below).
The firewall scripts are started during boot by placing the firewall.sh script as /usr/local/etc/rc.d/firewall.sh.
All other scripts go in either /etc/fire or /etc/fire/rc.d as indicated (you must create these directories).
/etc/fire/dhcp_check.sh is also called via cron every few minutes to see if the ISP has decided to change my IP address. The /etc/crontab entry appears as...
*/2 * * * * root (/etc/fire/dhcp_check.sh >/dev/null 2>&1)
These rules are subject to change -- current ruleset dated Thu Sep 30 22:58:45 AST 2010.
You'd best not cut and paste these scripts, but download them via the links provided. Cut and paste will break some of the regular expressions. You'll also have to review all these scripts to ensure that they match your needs and environment.
Someday soon I'll consolidate the variables in each script into a common .conf file and make installation and configuration easier... but for now, here are the scripts as I run them...
/usr/local/etc/rc.d/firewall.sh
#!/bin/sh
case $1 in
start)
# startup firewall rules.
/etc/fire/ipfw.sh start
# try to connect to ISP.
/etc/fire/dhcp_check.sh &
;;
stop) ipfw -q flush
ipnat -FC
;;
esac
# EOF
/etc/fire/ipfw.sh
#!/bin/sh
WD=$(dirname `realpath ${0}`)
NULL=/dev/null
OUT=alc0 # 'net interface
OUT_LOG="log logamount 0"
#OUT_LOG=""
WLAN0="10.0.0.0/28" # LIMITED
WLAN0_INTF=wlan0 # private WLAN
WLAN0_LOG="log logamount 0"
WLAN1="10.0.1.0/28" # UNLIMITED
WLAN1_INTF=wlan1 # dirty WLAN
WLAN1_LOG="log logamount 0"
#WLAN1_LOG=""
DENY_LOG="log logamount 0"
#DENY_LOG=""
#
###############
#
start_fire() \
{
### ipfw
#
#
{
ipfw -q flush
# nothing in from RFC1918 subnets
ipfw add 1000 deny ip from 10.0.0.0/8 to any in via ${OUT}
ipfw add 1010 deny ip from 172.16.0.0/12 to any in via ${OUT}
ipfw add 1020 deny ip from 192.168.0.0/16 to any in via ${OUT}
# inter VLAN spoofs...
ipfw add 1300 deny ip from not ${WLAN0} to any in via ${WLAN0_INTF}
# talk to myself
ipfw add 1500 allow ip from me to me via lo0
ipfw add 1510 deny ip from me to me
# ICMP
ipfw add 1600 allow icmp from me to any keep-state
ipfw add 1610 allow icmp from any to me icmptypes 11
# check-state - if a dynamic rule exists, allow the traffic (typically for reverse-path traffic)
ipfw add 2000 check-state
### WLAN0
# allow WLAN0 services to the firewall
ipfw add 3000 allow udp from ${WLAN0} to me 53 via ${WLAN0_INTF} keep-state # DNS
ipfw add 3010 allow tcp from ${WLAN0} to me 80 via ${WLAN0_INTF} keep-state # auto-configure/website
ipfw add 3020 allow udp from ${WLAN0} to me 123 via ${WLAN0_INTF} keep-state # NTP
ipfw add 3030 allow tcp from ${WLAN0} to me 443 via ${WLAN0_INTF} keep-state # webmail
ipfw add 3100 allow tcp from ${WLAN0} to me 3128 via ${WLAN0_INTF} # squid
ipfw add 3110 allow tcp from me 3128 to ${WLAN0} via ${WLAN0_INTF} # squid
ipfw add 3200 allow tcp from ${WLAN0} to me 22 via ${WLAN0_INTF} # SSH
ipfw add 3210 allow tcp from me 22 to ${WLAN0} via ${WLAN0_INTF} # SSH
# total end of WLAN0
ipfw add 9990 deny ${WLAN0_LOG} ip from any to any via ${WLAN0_INTF}
### WLAN1
# allow WLAN1 services to the firewall
ipfw add 10000 allow udp from ${WLAN1} to me 53 via ${WLAN1_INTF} keep-state # DNS
ipfw add 10010 allow tcp from ${WLAN1} to me 80 via ${WLAN1_INTF} keep-state # auto-configure/website
ipfw add 10020 allow udp from ${WLAN1} to me 123 via ${WLAN1_INTF} keep-state # NTP
#ipfw add 10030 allow tcp from ${WLAN1} to me 443 via ${WLAN1_INTF} keep-state # webmail
#ipfw add 10100 allow tcp from ${WLAN1} to me 3128 via ${WLAN1_INTF} # squid
#ipfw add 10110 allow tcp from me 3128 to ${WLAN1} via ${WLAN1_INTF} # squid
#ipfw add 10200 allow tcp from ${WLAN1} to me 22 via ${WLAN1_INTF} # SSH
#ipfw add 10210 allow tcp from me 22 to ${WLAN1} via ${WLAN1_INTF} # SSH
ipfw add 11000 deny ip from ${WLAN1} to me via ${WLAN1_INTF} # no more to me from WLAN1
ipfw add 19000 allow ip from any to any via ${WLAN1_INTF}
ipfw add 19990 deny ip from any to any via ${WLAN1_INTF}
### OUT
# services offered to the 'net
ipfw add 20000 allow tcp from any to me 22 via ${OUT} keep-state
ipfw add 20010 allow tcp from any to me 25 via ${OUT} keep-state
ipfw add 20020 allow tcp from any to me 80 via ${OUT} keep-state
ipfw add 20030 allow tcp from any to me 443 via ${OUT} keep-state
# no more incoming TCP SYNs
ipfw add 29000 deny ${OUT_LOG} tcp from any to any in via ${OUT} setup
# now allow it ALL!
#ipfw add 29990 allow ip from me to any via ${OUT} keep-state # BROKEN.
ipfw add 29990 allow ip from any to any via ${OUT} keep-state
# drop it all
ipfw add 61000 deny ${DENY_LOG} ip from any to any
} >$NULL 2>&1
#
#
###
}
#
###############
#
case $1 in
start) start_fire;;
stop) ipfw -q flush;;
esac
#
#
### EOF
/etc/fire/dhcp_check.sh
#!/bin/sh
export PATH=/usr/bin:/usr/sbin:/bin:/sbin:/usr/local/bin:/usr/local/sbin
PING_ATTEMPTS=5
PING_TIMEOUT=5
PID=/var/run/dhcp_check.pid
OUT=alc0
TMPFILE=/etc/fire/.ipaddress
NULL=/dev/null
if [ -f $PID ]; then
PIDN=`cat $PID`
ps -p $PIDN >$NULL 2>&1
if [ $? = 0 ]; then
# already running
logger -t firewall "already running ${0}"
exit 0
else
rm -f ${PID}
fi
fi
echo $$ > $PID
start_firewall_and_exit() \
{
logger -t firewall "start - ${*}"
#PIDS=`ps ax | grep dhclient | grep -v grep | awk '{ print $1 }'`
#[ X"${PIDS}" != X"" ] && kill $PIDS && sleep 1
{
# does it run?
ps ax | grep -v grep | grep dhclient >$NULL 2>&1
if [ $? != 0 ]; then
logger -t firewall "starting dhclient ${OUT}"
> /var/db/dhclient.leases.${OUT}
dhclient ${OUT}
fi
/etc/fire/fire.sh start
} 2>&1 | logger -t firewall
exit 0
}
ifconfig $OUT | grep "status: no carrier" >$NULL 2>&1
if [ $? = 0 ]; then
logger -t firewall "wireless modem is not connected"
exit 0
fi
# MWW. Apr 28 2010 06:00.
# does taking the last IP make more sense?
# ifconfig shows proper IP first, then 0.0.0.0 for DHCP waiting renewals...
IP=`ifconfig $OUT | grep -iE "^[ ]*inet " | tail -1 | awk '{ print $2 }'`
# do we have an IP?
if [ X"${IP}" = X"" -o X"${IP}" = X"0.0.0.0" ]; then
# no IP?
start_firewall_and_exit "no IP address"
fi
# get the old IP
if [ -f $TMPFILE ]; then
OIP=`cat $TMPFILE`
else
OIP="unknown"
fi
# did the IP change?
if [ X"${IP}" != X"${OIP}" ]; then
# save the new IP
echo "$IP" > $TMPFILE
# ... so we have a new IP, rewrite firewall rules.
start_firewall_and_exit "IP changed from $OIP to $IP"
fi
# can we ping our router?
RIP=`netstat -nrf inet | grep -iE "^default" | awk '{ print $2 }'`
if [ X"${RIP}" != X"" -a X"${RIP}" != X"0.0.0.0" ]; then
C=0; while [ $C -lt $PING_ATTEMPTS ]; do
ping -t $PING_TIMEOUT -c 1 $RIP >$NULL 2>&1 && break
C=$(($C+1))
done
if [ $C -ge $PING_ATTEMPTS ]; then # nope, start firewall...
logger -t firewall "restarting dhclient - no default router ping response from $RIP"
### ... restart dhclient?
pkill dhclient
sleep 5
while [ $? = 0 ]; do
ifconfig $OUT delete
done
> /var/db/dhclient.leases.${OUT}
# dhclinet will automagically start with start_firewall_and_exit
start_firewall_and_exit "there was no default router ping response from $RIP... so I have restarted dhclient as well"
exit 0
fi
else
start_firewall_and_exit "no default router IP"
fi
### do we have IPNAT rules?
ipnat -l | grep -E "^map" >$NULL 2>&1
if [ $? != 0 ]; then
start_firewall_and_exit "there was a response from the default router, but no ipnat rules existed. starting firewall..."
fi
logger -t firewall "looks good..."
#
#
### EOF
/etc/fire/fire.sh
#!/bin/sh
WD=$(dirname `realpath ${0}`)
OUT=alc0 # 'net interface
WLAN0_INTF=wlan0 # private WLAN
WLAN1_INTF=wlan1 # dirty WLAN
WLAN0="10.0.0.0/28"
WLAN1="10.0.1.0/28"
# get IP address...
#ifconfig ${OUT} | grep -E "^[ ]*inet " | head -1 | awk '{ print $2 }'
GLOBAL=`ifconfig ${OUT} | grep -E "^[ ]*inet " | head -1 | awk '{ print $2 }'`
[ X"${GLOBAL}" = X"" -o X"${GLOBAL}" = X"0.0.0.0" ] && exit 69
#
###############
#
start_fire() \
{
### pre-exec
#
#
${WD}/rc.d/sysctl.sh
#
#
###
### ipfw rules
#
#
${WD}/ipfw.sh start
#
#
###
### ipnat rules
#
#
ipnat -CF
ipnat -f - << EOF
map $OUT $WLAN1 -> $GLOBAL proxy port ftp ftp/tcp
map $OUT $WLAN1 -> $GLOBAL portmap tcp 49152:65535
map $OUT $WLAN1 -> $GLOBAL portmap udp 49152:65535
map $OUT $WLAN1 -> $GLOBAL
rdr $OUT $GLOBAL port 22 -> 10.0.0.2 port 22 tcp
rdr $OUT $GLOBAL port 25 -> 10.0.0.2 port 25 tcp
rdr $OUT $GLOBAL port 80 -> 10.0.0.2 port 80 tcp
rdr $OUT $GLOBAL port 443 -> 10.0.0.2 port 443 tcp
EOF
#
#
###
### post-exec
#
# These are my own custom scripts to kick my DNS server and restart my NTP daemon.
#
${WD}/rc.d/named.sh
${WD}/rc.d/ntpd.sh
#
#
###
}
#
###############
#
case $1 in
start) start_fire;;
stop) ${WD}/ipfw.sh stop
ipnat -FC
for XI in $OUT $WLAN0_INTF $WLAN1_INTF; do
ifconfig $XI down
while [ $? = 0 ]; do # this depends on the above being successful.
ifconfig $XI delete # loop continues while there's IPs to delete.
done
done
;;
esac
#
#
### EOF
/etc/fire/rc.d/sysctl.sh
#!/bin/sh
# tweak kernel values
# ala net.inet.ip.forwarding=1
[ X"${WD}" = X"" ] && WD=`dirname $(realpath ${0})`
if [ -f ${WD}/sysctl.conf ]; then
cat ${WD}/sysctl.conf | sed 's/#.*//g' | grep -v "^$" | while read SYSCTL_LN; do
O_LN=`sysctl -w ${SYSCTL_LN}`
set - $O_LN
[ X"${2}" != X"${4}" ] && echo "${O_LN}"
done
fi
# EOF
/etc/fire/rc.d/sysctl.conf
net.inet.ip.forwarding=1
net.inet.ip.redirect=0
net.inet.ip.sourceroute=0
net.inet.ip.accept_sourceroute=0
net.inet.ip.fw.enable=1
net.inet.ip.fw.verbose=1
net.inet.ip.fw.verbose_limit=0
net.inet.tcp.log_in_vain=2
net.inet.udp.log_in_vain=2
/etc/fire/rc.d/named.sh
#!/bin/sh
if [ -f /var/run/named.pid ]; then
echo "HUP'ing named"
kill -1 `cat /var/tmp/named.pid`
fi
# EOF
/etc/fire/rc.d/ntpd.sh
#!/bin/sh
PIDDIR=/var/run
PIDFILE=ntpd.pid
NTPCONF=/etc/ntp.conf
# cycle thru killing ntpd, running ntpdate, and restarting ntpd...
if [ -f $PIDDIR/$PIDFILE ]; then
kill `cat $PIDDIR/$PIDFILE`
rm -f $PIDDIR/$PIDFILE
fi
NTPDATE_SERVER=`cat $NTPCONF | grep -v "^#" | sed 's/#.*//g' | grep -v "^$" | grep prefer | awk '{ print $2 }'`
if [ X"${NTPDATE_SERVER}" != X"" ]; then
echo "NTPdate Server: $NTPDATE_SERVER"
ntpdate -b $NTPDATE_SERVER
fi
/usr/sbin/ntpd -A -p $PIDDIR/$PIDFILE
# EOF
Return to tips+howtos.