#!/bin/bash ############################################################################## # # rc.firewall # # version 1.68 (2008/09/09) by John Wiegley # # This script takes a series of arguments describing the current network # interfaces and the networks behind them. Basic usage is: # # rc.firewall [OPTIONS] [INTERFACES...] # # The list of INTERFACES specifies which interfaces and networks you know # about, and their respective level of trust. # # NOTE: Interfaces/networks which are not mentioned are completely shut out # and *not logged*. This means that if no interfaces are given, the effect is # to shut out networking entirely, except for DHCP and certain types of ICMP. # This is not a bad idea as a default after startup, until you know what your # networking environment looks like. # # An INTERFACE may be just an interface name, in which case traffic is not # filtered by a netmask. This is important for interfaces that will access # addresses outside of their own range, such as those wishing to reach the # Internet. If a network base address and mask is given after a colon, it # specifies legal address ranges for that interface: this is used to check for # spoofing and illegal addresses. If two colons are used, the network is # considered "trusted" and additional traffic, such as Rendezvous, is allowed. # # INTERFACE EXAMPLES # # en1{0,0} en1 (Airport) accesses the Internet # and uses priority packet queueing, # but without rate limiting # en0:192.168.0.0/24 Local network on en0: 192.168.0.x # en0::192.168.0.0/16 Trusted network on en0: 192.168.x.x # en0::192.168.0.0:255.255.0.0 Same as previous # en0+mac::192.168.0.0/16 Trusted Mac network on en0 # en0+win::192.168.0.0/16 Trusted Windows network on en0 # en0+mac+win::192.168.0.0/16 Trusted mixed network on en0 # # The same interface can appear multiple times, if you have several networks # connected to it (such as having your router's local network, and the # Internet, both visible over en1). NOTE: *In that case, always list the most # specific interfaces/networks first*. This means that if you access both # 192.168.0.0/24 and the Internet over en1, use this: # # rc.firewall en1::192.168.0.0/24 en1\{0,0\} # # The string {0,0} (escaped for the shell) specifies inbound and outbound # bandwidth limits for public interfaces. A pair of zeros means bandwith # isn't limited, but it *is* shaped -- under the assumption that resources # probably *are* limited, you just don't want to specify an artifical number # right now. If the "{IN,OUT}" isn't specified for an interface, it means # neither limiting nor shaping is performed. # # NOTE: A deficiency of this script is that I limit/shape based on the # interface, not the network; to compensate, traffic bound for internal # networks is never limited or shaped. # RECIPES # # 1. You're going to be gaming on a mixed local LAN tonight over Ethernet, but # your Internet access will be via Wireless: # # rc.firewall --gaming en0\{0,0\}+mac+win::192.168.x.0/24 en1 # # 2. You're at a local coffee shop, using wireless, and you'd like to be a # good citizen (i.e., not saturate their network with your downloads), so # you limit voluntarily rate-limit yourself to 512 Kbits down and 128 Kbits # up: # # rc.firewall --stealth --blackhole en1\{512Kbits/s,128Kbits/s\} # # 3. You're at home, where you connect by wireless and have a Mac network, # plus you leave your SSH port open: # # rc.firewall --trusted-tcp 22 en1+mac::192.168.x.0/24 en1\{0,0\} # # 4. You're running your secure little Mac at the cafe, same as #2, but you # also have a Windows box running in VMware that you'd like to trust, and # you have a VPN connection to your Windows-based office. Lastly, you # sometimes route *all* traffic through the VPN, though not always: # # rc.firewall --stealth --blackhole \ # en1\{512Kbits/s,128Kbits/s\} \ # vmnet8+win::192.168.36.0/24 \ # tap0\{500Kbits/s,118Kbits/s\}+win::10.0.0.0/8 \ # tap0\{500Kbits/s,118Kbits/s\} # OPTIONS # # --debug # Show what commands would have been executed to setup the firewall. No # system changes are made. # # --verbose # Log suspicious packets. Note that on some network, this can result in # rather large log files. # # --log-all # Log every rejected packet, not just the suspicious ones. Note: If you're # restarting the firewall, you'll initially reject a lot of established # packets from connections made before the restart. # # --stealth # Try to be as stealthy as possible. Ordinarily, this script responds to # failed connection attempts in the following manner: # # port 113 TCP RESET # broadcasts ICMP host-prohib # connections ICMP filter-prohib # old established connections TCP RESET # NetBIOS/Rendezvous traffic silently drop packet # # Also, while all "normal" outbound traffic is silently allowed, anything # out of the ordinary is logged (if --verbose is used). # # When --stealth mode is on, this script drops all inbound packets silently, # and also all suspicious outbound packets. It tries to make your machine # appear as far under the radar as possible on an open network. # # However, do realize that being entirely stealthy is not possible. Anyone # on the same local network as you will be able to see your ARP packets # flying around, and will know you are there and what your MAC address is. # Also, by attempting to scan you, they will know you're trying to be # stealthy. In fact, --stealth mode is really only effective against the # most casual of observers. # # --blackhole # If used, a tcp/udp blackhole is configured to help block stealth scanning. # Note however that this has caused known slowdowns in services like Samba # (smbfs). # # --gaming # Indicates that you intend to use the machine primarily for gaming, where # latency is more important than throughput. Kernel variables are tuned # accordingly. You should also use priority queuing (suffix {0,0} to the # interface) so that small ACK packets get out ASAP. # # --router INTF1,INTF2:NET # This machine acts as a router between INTF1 and INTF2 for network NET. # INTF1 specifies the target interface, so if you wanted to route traffic # from en1 (local clients connected to your Airport network) over en0 (cable # modem connection to the Internet), you would use: # # rc.firewall --router en0,en1:192.168.10.0/24 # # This might read as "route traffic over Ethernet (en0) coming from the # Wireless (en1) subnetwork (192.168.10.0/24)". # # --tcp PORT[,PORT...] # --udp PORT[,PORT...] # Make the given inbound PORTs accessible on any configured interface. # # --local-tcp PORT[,PORT...] # --local-udp PORT[,PORT...] # Make the given inbound PORTs accessible to local networks (i.e., not the # Internet). # # --trusted-tcp PORT[,PORT...] # --trusted-udp PORT[,PORT...] # Make the given inbound PORTs accessible to trusted networks only. # NOTES # # A word about a few of the things this script cannot do, owing to # deficiencies in ipfw on Mac OS X 10.4 and 10.5: # # * You cannot filter incoming packets based on the MAC address of the source. # This is because the necessary support is not compiled into the OS X # kernel, not because ipfw doesn't support it. # # * You cannot take action based on counters, like shutting off ECHO REQUEST # packets from a certain host once they exceed a certain number within a # given set of time. Tools like IPNetSentryX can do this. # # * Although you can shape traffic by directing it along specific pipes or # queues, you cannot manipulate traffic as it passes down the chain. Doing # this requires divert'ing the traffic to a user-space daemon, which # modifies the packets and passes them on (google for 'throttled' as an # excellent example). iptables on Linux can do this sort of thing very # easily, it's a shame ipfw cannot. # # * You can't match a packet based on its IP fragment offset, only on the # existence of fragmentation or not. Also, ipfw always drops TCP packets # with a fragment offset of 1. # ############################################################################## ############################################################################## # # Process command-line options # ############################################################################## IPFW="/sbin/ipfw -q" debug=false gaming=false blackhole=false router=false this="me" logall="" stealth=false verbose=false tcp_reset="reset" unreach_host="unreach host" # aka, reject unreach_host_prohib="unreach host-prohib" unreach_filter_prohib="unreach filter-prohib" trusted_tcp_ports="" trusted_udp_ports="" local_tcp_ports="" local_udp_ports="" public_tcp_ports="" public_udp_ports="" while [[ -n "$1" ]] && (echo $1 | grep -q -e ^--); do case "$1" in --debug) shift 1 debug=true IPFW="echo ipfw" ;; --gaming) shift 1 gaming=true ;; --verbose) shift 1 verbose=true ;; --log-all) shift 1 echo Logging all denied packets logall="log" ;; --stealth) shift 1 echo Enabling stealth mode to avoid detection and leakage tcp_reset="drop" unreach_host="drop" # aka, reject unreach_host_prohib="drop" unreach_filter_prohib="drop" stealth="true" ;; --blackhole) shift 1 echo Enabling blackhole to avoid stealth port scans blackhole="true" ;; --router) shift 1 router=true external_intf=$(echo $1 | sed 's/,.*//') client_intf=$(echo $1 | sed 's/.*,//') client_net=$(echo $client_intf | sed 's/.*://') client_intf=$(echo $client_intf | sed 's/:.*//') echo Enabling routing $client_intf \($client_net\) -\> $external_intf shift 1;; --trusted-tcp) shift 1 if [[ -n "$trusted_tcp_ports" ]]; then trusted_tcp_ports="$trusted_tcp_ports,$1" else trusted_tcp_ports="$1" fi echo Opening trusted TCP ports: $trusted_tcp_ports shift 1 ;; --local-tcp) shift 1 if [[ -n "$local_tcp_ports" ]]; then local_tcp_ports="$local_tcp_ports,$1" else local_tcp_ports="$1" fi echo Opening local TCP ports: $local_tcp_ports shift 1 ;; --tcp) shift 1 if [[ -n "$public_tcp_ports" ]]; then public_tcp_ports="$public_tcp_ports,$1" else public_tcp_ports="$1" fi echo Opening public TCP ports: $public_tcp_ports shift 1 ;; --trusted-udp) shift 1 if [[ -n "$trusted_udp_ports" ]]; then trusted_udp_ports="$trusted_udp_ports,$1" else trusted_udp_ports="$1" fi echo Opening trusted UDP ports: $trusted_udp_ports shift 1 ;; --local-udp) shift 1 if [[ -n "$local_udp_ports" ]]; then local_udp_ports="$local_udp_ports,$1" else local_udp_ports="$1" fi echo Opening local UDP ports: $local_udp_ports shift 1 ;; --udp) shift 1 if [[ -n "$public_udp_ports" ]]; then public_udp_ports="$public_udp_ports,$1" else public_udp_ports="$1" fi echo Opening public UDP ports: $public_udp_ports shift 1 ;; *) echo Unrecognized option $1 exit 1 esac done ############################################################################## # # If interfaces were given, setup the global variables used by all the # rules. # ############################################################################## args="$@" declare -a interfaces declare -a networks declare -a trust declare -a nettype declare -a inbw declare -a outbw declare -a all_intf intf_count=0 function add_interface() { intf=$1 netw=$2 trusted=$3 ntype=$4 intf_inbw=$5 intf_outbw=$6 interfaces[$intf_count]=$intf networks[$intf_count]=$netw trust[$intf_count]=$trusted nettype[$intf_count]=$ntype inbw[$intf_count]=$intf_inbw outbw[$intf_count]=$intf_outbw echo Configuring interface $intf_count: $intf $netw \ trusted? $trusted type $ntype \ \(in $intf_inbw out $intf_outbw\) # Configure bandwith shaping in/out pipes for each interface that # has a bandwith defined if [[ -n "$intf_inbw" ]]; then $IPFW pipe $((100 + intf_count)) config bw 0 $IPFW pipe $((200 + intf_count)) config bw 0 fi # If the interface is not yet in the `all_intf' array, add it if echo ${all_intf[@]} | grep -qv "\\<$intf\\>"; then all_intf=(${all_intf[@]} $intf) fi intf_count=$((intf_count + 1)) } while [[ -n "$1" ]]; do intf="" netw=any trusted=false ntype=unknown intf=$(echo "$1" | sed 's/[+:].*//') netw=$(echo "$1" | sed 's/^[^:]*:*//') if [[ -z "$netw" ]]; then netw=any fi if echo "$1" | grep -q "::"; then trusted=true fi if echo "$1" | grep -q "\\+mac"; then ntype=mac fi if echo "$1" | grep -q "\\+win"; then if [[ $ntype == mac ]]; then ntype=both else ntype=win fi fi if echo "$intf" | grep -q "{"; then intf_inbw=$(echo "$intf" | sed 's/.*{//' | sed 's/,.*//' | sed 's/}//') intf_outbw=$(echo "$intf" | sed 's/.*{//' | sed 's/.*,//' | sed 's/}//') intf=$(echo "$intf" | sed 's/{.*//') else intf_inbw="" intf_outbw="" fi add_interface $intf $netw $trusted $ntype $intf_inbw $intf_outbw shift 1 done via_all="" if [[ $intf_count > 0 ]]; then for intf in ${all_intf[@]}; do if [[ -z "$via_all" ]]; then via_all="{" else via_all="$via_all or" fi via_all="$via_all via $intf" done if [[ -n "$via_all" ]]; then via_all="$via_all }" fi fi ############################################################################## # # Function for rate limiting/traffic shaping an interface # ############################################################################## declare -a limited function limit_intf() { intf=$1 intf_index="" for (( i=0; i < intf_count; i++ )); do if [[ ${interfaces[$i]} == $intf && \ -z "${limited[$i]}" && -n "${inbw[$i]}" ]]; then limited[$i]=true # Put a pipe in place for all remaining inbound/outbound traffic, # which can be throttled using the option --setrate or the # separate script "setrate". This makes it easy to be kind to # other people's networks, no matter what kind of traffic it is. inpipe="pipe $((100 + i))" outpipe="pipe $((200 + i))" $IPFW $inpipe config bw ${inbw[$i]} $IPFW $outpipe config bw ${outbw[$i]} private="192.168.0.0/16,172.16.0.0/12,10.0.0.0/8" $IPFW add 450 set 0 skipto 900 all from any to $private out via $intf $IPFW add 460 set 0 skipto 900 all from $private to any in via $intf $IPFW add 500 set 0 $inpipe tcp from any to any in via $intf tcpflags !syn $IPFW add 510 set 0 $inpipe udp from any to any in via $intf # Shape the outbound pipe so that some protocols get priority going # out $IPFW queue $((100 + i)) config $outpipe weight 7 # high-priority $IPFW queue $((200 + i)) config $outpipe weight 5 # medium-priority $IPFW queue $((300 + i)) config $outpipe weight 1 # low-priority # Assign outgoing empty/small ACK packets to the high-priority queue $IPFW add 600 set 0 queue $((100 + i)) \ tcp from any to any out via $intf tcpflags ack iplen 0-80 # Assign outgoing UDP (DNS) and SSH traffic to the medium-priority queue $IPFW add 700 set 0 queue $((200 + i)) tcp from any to any 22,80,443,5900 \ out via $intf \{ tcpflags \!ack or iplen 81-65535 \} $IPFW add 710 set 0 queue $((200 + i)) udp from any to any 53,1194 out via $intf # Assign all other outgoing traffic to the low-priority queue: $IPFW add 800 set 0 queue $((300 + i)) tcp from any to any not 22,80,443,5900 \ out via $intf \{ tcpflags \!ack or iplen 81-65535 \} $IPFW add 810 set 0 queue $((300 + i)) udp from any to any not 53,1194 out via $intf fi done } ############################################################################## # # Initialize and tune the firewalling environment # ############################################################################## # Remove any rules previously defined and flush the dynamic tables $IPFW -f flush $IPFW -f pipe flush # Log all rejected packets, which are always "suspect" if [[ $debug == false ]]; then if [[ $verbose == true ]]; then sysctl -w net.inet.ip.fw.verbose=1 else sysctl -w net.inet.ip.fw.verbose=0 fi # Make sure packets get reinjected sysctl -w net.inet.ip.fw.one_pass=0 # Check that packets are appropriate to their interface sysctl -w net.inet.ip.check_interface=1 # Turn on RFC1323 TCP high speed optimization. # NOTE: This can be a security risk (DoS attack) sysctl -w net.inet.tcp.rfc1323=1 # ICMP limit sysctl -w net.inet.icmp.icmplim=1024 # Stop redirects sysctl -w net.inet.icmp.drop_redirect=1 sysctl -w net.inet.icmp.log_redirect=1 sysctl -w net.inet.ip.redirect=0 # Stop source routing sysctl -w net.inet.ip.sourceroute=0 sysctl -w net.inet.ip.accept_sourceroute=0 # Stop broadcast ECHO response sysctl -w net.inet.icmp.bmcastecho=0 # Stop other broadcast probes sysctl -w net.inet.icmp.maskrepl=0 # Turn on strong TCP sequencing sysctl -w net.inet.tcp.strict_rfc1948=1 # Socket queue defense against SYN attacks sysctl -w kern.ipc.somaxconn=1024 # IPC max buffering sysctl -w kern.ipc.maxsockbuf=523288 # ARP cleanup sysctl -w net.link.ether.inet.max_age=1200 # Increase buffers for faster downloads when you have very # high-speed, latency-free bandwidth (ie, _not_ DSL which dies badly # as it saturates) if [[ $gaming == true ]]; then sysctl -w net.inet.tcp.sendspace=16383 sysctl -w net.inet.tcp.recvspace=16383 sysctl -w net.inet.udp.recvspace=42080 sysctl -w net.inet.raw.recvspace=8192 sysctl -w net.local.dgram.maxdgram=4196 # Other network buffering sysctl -w net.local.stream.recvspace=16383 sysctl -w net.local.stream.sendspace=16383 sysctl -w net.local.dgram.recvspace=8192 # TCP delayed ack off, better latency sysctl -w net.inet.tcp.delayed_ack=0 else sysctl -w net.inet.tcp.sendspace=32767 sysctl -w net.inet.tcp.recvspace=32767 sysctl -w net.inet.udp.recvspace=65535 sysctl -w net.inet.raw.recvspace=16384 sysctl -w net.local.dgram.maxdgram=8192 # Other network buffering sysctl -w net.local.stream.recvspace=32767 sysctl -w net.local.stream.sendspace=32767 sysctl -w net.local.dgram.recvspace=16384 # TCP delayed ack on, better throughput sysctl -w net.inet.tcp.delayed_ack=1 fi # Create a blackhole to avoid stealth port scans if [[ $blackhole == true ]]; then sysctl -w net.inet.tcp.blackhole=2 sysctl -w net.inet.udp.blackhole=1 else sysctl -w net.inet.tcp.blackhole=0 sysctl -w net.inet.udp.blackhole=0 fi # If we're acting as a router, enable packet forwarding if [[ $router == true ]]; then sysctl -w net.inet.ip.forwarding=1 else sysctl -w net.inet.ip.forwarding=0 fi fi ############################################################################## # # Set 0: Allow loopback, divert NAT packets, shape ICMP/TCP-SYN # ############################################################################## # Allow all loopback traffic, don't bother with anything else $IPFW add 100 set 0 allow all from any to any via 'lo*' if [[ $router == true ]]; then # Divert traffic from the external interface to the nat daemon # (note: using "in" here, as some firewalls do, causes OpenVPN to # stop working) $IPFW add 200 set 0 divert natd all from any to any via $external_intf fi # Rate limit TCP traffic used to establish connections, to protect # against SYN flooding $IPFW pipe 300 config bw 64Kbit/s queue 5 $IPFW add 300 set 0 pipe 300 tcp from any to any in setup # Delay TCP RESET packets. From the IPNetSentryX docs: "Some # firewalls can send TCP RESET segments when denying access. If the # interface running such a firewall is set to promiscuous mode, the # firewall may send TCP RESET segments in response to connection # requests that were not originally addressed to that host. The # symptom is frequent “Connection refused” responses when trying to # access remote servers. By delaying such TCP RESET segments # (approximately 0.5 seconds), we allow the actual target of the # connection request (if any) to respond first completing the # connection process. When the RESET arrives, it will be safely # ignored as out of order if the target host has already responded." $IPFW pipe 350 config delay 500 $IPFW add 350 set 0 pipe 350 tcp from any to any in tcpflags rst # Rate limit ICMP traffic to avoid line clogging by Smurf attacks $IPFW pipe 400 config bw 16Kbit/s queue 1 $IPFW pipe 410 config bw 16Kbit/s queue 5 $IPFW add 410 set 0 pipe 400 icmp from any to any in $IPFW add 415 set 0 pipe 410 icmp from any to any out # Rules 500-899 are defined by limit_intf() above $IPFW add 901 set 0 deny log all from any to any ipoptions rr in $via_all $IPFW add 911 set 0 deny log all from any to any ipoptions ts in $via_all $IPFW add 921 set 0 deny log all from any to any ipoptions lsrr in $via_all $IPFW add 931 set 0 deny log all from any to any ipoptions ssrr in $via_all $IPFW add 941 set 0 deny log tcp from any to any tcpflags syn,fin $IPFW add 951 set 0 deny log tcp from any to any tcpflags syn,rst $IPFW add 961 set 0 deny log tcp from any 0 to any $IPFW add 971 set 0 deny log tcp from any to any 0 $IPFW add 981 set 0 deny log udp from any 0 to any $IPFW add 991 set 0 deny log udp from any to any 0 ############################################################################## # # Set 1: Allow routed traffic # # If this machine is acting as a router, allow traffic to pass through # between the two connected interfaces. # ############################################################################## if [[ $router == true ]]; then # Allow traffic coming in from the "client" network, and traffic # coming in from the Internet bound for the client network $IPFW add 1000 set 1 allow all from $client_net to any in recv $client_intf $IPFW add 1010 set 1 allow all from any to $client_net in recv $external_intf # And allow this traffic out through the respective target interface $IPFW add 1100 set 1 allow all \ from $client_net to any out recv $client_intf xmit $external_intf $IPFW add 1110 set 1 allow all \ from any to $client_net out recv $external_intf xmit $client_intf # Also allow the client network to see the router $IPFW add 1200 set 1 allow all from $client_net to me out recv $client_intf $IPFW add 1210 set 1 allow all from me to $client_net out xmit $client_intf fi ############################################################################## # # Set 2: Allow traffic relating to ongoing conversations # # But deny fragments and established connections not so related. # ############################################################################## # Allow traffic if it matches the dynamic table $IPFW add 2000 set 2 check-state # Drop fragments and reset established connections not matched by the # dynamic table $IPFW add 2100 set 2 deny $logall all from any to any frag $IPFW add 2110 set 2 $tcp_reset $logall tcp from any to any established ############################################################################## # # Set 3: Allow Rendezvous/Bonjour and AFP for Apple networks # ############################################################################## # TCP 548 Apple AFP # 5009 Apple Airport Express Admin # 5900 Apple VNC (screen sharing) tcp_ports=548,5009,5900 # UDP 192 Apple UPnP (network discovery) udp_ports=192 allowed=false for (( index=0; index < intf_count; index++ )); do if [[ ${nettype[$index]} == mac || ${nettype[$index]} == both ]]; then netw=${networks[$index]} intf=${interfaces[$index]} # Allow outbound and inbound TCP connections to tcp_ports $IPFW add $((3100 + index)) set 3 allow $logall \ tcp from me to $netw $tcp_ports out via $intf setup keep-state $IPFW add $((3110 + index)) set 3 allow $logall \ tcp from $netw to me $tcp_ports in via $intf setup keep-state # Allow outbound and inbound UDP connections to udp_ports $IPFW add $((3200 + index)) set 3 allow $logall \ udp from me to $netw $udp_ports out via $intf keep-state $IPFW add $((3210 + index)) set 3 allow $logall \ udp from $netw to me $udp_ports in via $intf keep-state # Allow Rendezvous/Bonjour (Zeroconf) traffic $IPFW add $((3300 + index)) set 3 allow $logall \ udp from $netw to any 5353 via $intf keep-state # Allow three types of broadcast traffic typically found in # Windows and Apple enviroments $IPFW add $((3400 + index)) set 3 allow $logall \ ip from $netw to 224.0.0.0/3 via $intf keep-state $IPFW add $((3410 + index)) set 3 allow $logall \ udp from $netw to 239.255.255.253 via $intf keep-state $IPFW add $((3420 + index)) set 3 allow $logall \ udp from $netw to 255.255.255.255 via $intf keep-state allowed=true fi done # Load or unload the Apple Multicast daemon based on whether we're # using Rendezvous at all; no need to broadcast if we're not, since # the packets won't even be let out if [[ $debug == false ]]; then launchctl unload /System/Library/LaunchDaemons/com.apple.mDNSResponder.plist 2> /dev/null if [[ $allowed == true ]]; then launchctl load -w /System/Library/LaunchDaemons/com.apple.mDNSResponder.plist fi fi ############################################################################## # # Set 4: Allow NetBIOS and SMB for Windows networks # ############################################################################## # TCP 135-139 Windows File sharing (http://support.microsoft.com/kb/298804) # 445 Windows Direct-hosted SMB traffic (same URL) # 5000 Windows UPnP tcp_ports=135-139,445,5000 # UDP 135-139 Windows File sharing (see above) # 427 Windows SLP (Service Location Protocol) # 445 Windows Direct-hosted SMB traffic # 1900 Windows UPnP udp_ports=135-139,427,445,1900 for (( index=0; index < intf_count; index++ )); do if [[ ${nettype[$index]} == win || ${nettype[$index]} == both ]]; then netw=${networks[$index]} intf=${interfaces[$index]} # Allow outbound and inbound TCP connections to tcp_ports $IPFW add $((4100 + index)) set 4 allow $logall \ tcp from me to $netw $tcp_ports out via $intf setup keep-state $IPFW add $((4110 + index)) set 4 allow $logall \ tcp from $netw to me $tcp_ports in via $intf setup keep-state # Allow outbound and inbound UDP connections to udp_ports $IPFW add $((4200 + index)) set 4 allow $logall \ udp from me to $netw $udp_ports out via $intf keep-state $IPFW add $((4210 + index)) set 4 allow $logall \ udp from $netw to me $udp_ports in via $intf keep-state # Allow three types of broadcast traffic typically found in # Windows and Apple enviroments $IPFW add $((4300 + index)) set 4 allow $logall \ ip from $netw to 224.0.0.0/3 via $intf keep-state $IPFW add $((4320 + index)) set 4 allow $logall \ udp from $netw to 239.255.255.253 via $intf keep-state $IPFW add $((4340 + index)) set 4 allow $logall \ udp from $netw to 255.255.255.255 via $intf keep-state fi done ############################################################################## # # Set 5: Allow certain kinds of traffic for trusted networks # ############################################################################## for (( index=0; index < intf_count; index++ )); do if [[ ${trust[$index]} == true ]]; then netw=${networks[$index]} intf=${interfaces[$index]} # Allow all ICMP traffic within trusted networks (aka ping) $IPFW add $((5100 + index)) set 5 allow $logall \ icmp from me to $netw out via $intf $IPFW add $((5110 + index)) set 5 allow $logall \ icmp from $netw to me in via $intf fi done # Reject broadcast traffic (possibly related to Rendezvous/NetBIOS) $IPFW add 5200 set 5 $unreach_host_prohib $logall ip from any to 224.0.0.0/3 not 67-68 $IPFW add 5210 set 5 $unreach_host_prohib $logall ip from any to 239.255.255.253 not 67-68 $IPFW add 5220 set 5 $unreach_host_prohib $logall ip from any to 255.255.255.255 not 67-68 # Deny Windows and Apple network related UDP traffic reaching this # point; reject TCP with host-prohib $IPFW add 5300 set 5 deny $logall udp from any to any $udp_ports $IPFW add 5310 set 5 $unreach_host_prohib $logall tcp from any to any $tcp_ports ############################################################################## # # Set 6: Allow in/out packets related to DHCP and some ICMP # # This rule set allows our network interfaces to be configured. It can # be skipped if DHCP is not being used; it could also be disabled once # an address is assigned, although only for as long as the lease will # last. # ############################################################################## # Allow DHCP packets in and out, including broadcast. Since we don't have # an address yet, we can't use "me" as a target here. $IPFW add 6000 set 6 allow $logall udp \ from any 67-68 to any 67-68 keep-state # Allow certain types of ICMP packets on known interfaces, which might be # necessary for proper operation $IPFW add 6100 set 6 allow $logall icmp from any to any \ icmptypes 0,3,4,11,12,13,14 keep-state ############################################################################## # # Set 7: Filter inbound traffic # # Stateful firewalling is used to allow in packets related to # established outbound connections. Filtering is used to remove # possible spoof packets. The remainder might be consider "legitimate # inbound requests", and are passed to the subsequent rule sets. # ############################################################################## # Verify reverse path to help avoid spoofed packets. This means any # packet coming from a particular interface must have an address # matching the netmask for that interface. The `via_all' variable is # used to avoid logging packets on interfaces we're not interested in. $IPFW add 7000 set 7 deny $logall all from any to any not verrevpath in $via_all # Reject anything received that was not directly intended for this # matchine with a "host prohibited" response. This mostly means # broadcast. We omit DHCP from this because until a DHCP lease is # given, we have no idea what our address is/will be. $IPFW add 7100 set 7 $unreach_host_prohib log all from any to not me not 67-68 in $via_all $IPFW add 7110 set 7 $unreach_host_prohib log all from not me to any out $via_all # Filter packets inbound on known interfaces via_check="" for (( index=0; index < intf_count; index++ )); do netw=${networks[$index]} intf=${interfaces[$index]} if [[ $netw == any ]]; then if [[ -z "$via_check" ]]; then via_check="{" else via_check="$via_check or" fi via_check="$via_check via $intf" fi done if [[ -n "$via_check" ]]; then via_check="$via_check }" fi for (( index=0; index < intf_count; index++ )); do netw=${networks[$index]} intf=${interfaces[$index]} if [[ $netw != any ]]; then $IPFW add $((7200 + index)) set 7 skipto 7500 \ all from $netw to me in via $intf fi done if [[ -n "$via_check" ]]; then # Deny all inbound traffic from RFC1918 address spaces (spoof!) $IPFW add $((7300 + index)) set 7 deny log \ all from 192.168.0.0/16 to any in $via_check $IPFW add $((7320 + index)) set 7 deny log \ all from 172.16.0.0/12 to any in $via_check $IPFW add $((7340 + index)) set 7 deny log \ all from 10.0.0.0/8 to any in $via_check # Deny all inbound traffic from a loopback address (spoof!) $IPFW add $((7400 + index)) set 7 deny log \ all from 127.0.0.0/8 to any in $via_check fi # Reject broadcasts not related to DHCP $IPFW add 7500 set 7 $unreach_host_prohib $logall all from 0.0.0.0/8 to any not 67-68 in # Reject DHCP auto-config, and public class D & E multicast $IPFW add 7600 set 7 $unreach_host_prohib $logall all from 169.254.0.0/16 to any in $IPFW add 7610 set 7 $unreach_host_prohib $logall all from 224.0.0.0/3 to any in # Send TCP RST on attempted auth connections; IRC and SMTP might try # to connect back to us legitimately $IPFW add 7700 set 7 $tcp_reset $logall tcp from any to me 113 in setup ############################################################################## # # Set 10: Shape outbound traffic # # Outbound traffic is shaped for best response while concurrent # transfers might be occurring in both directions. The actual shaping # is done at a much earlier step, even though we set it up at this # point in the script. # ############################################################################## $IPFW set disable 10 for (( index=0; index < intf_count; index++ )); do limit_intf ${interfaces[$index]} ${inbw[$index]} ${outbw[$index]} done if [[ $intf_count > 0 ]]; then $IPFW set enable 10 fi ############################################################################## # # Set 11: Allow all outbound traffic # # If this ruleset is enabled, all other outbound traffic will be allowed # here. I use the application Little Snitch to allow/deny outbound # packets on a per-Application basis. # # Note that all "known" outbound traffic is silently allowed, whereas # unknown traffic is logged. If the --stealth option is used, unknown # traffic is logged and denied. # ############################################################################## $IPFW set disable 11 tcp_out_ports="80,443" # Web tcp_out_ports="$tcp_out_ports,21" # FTP tcp_out_ports="$tcp_out_ports,22" # OpenSSH tcp_out_ports="$tcp_out_ports,5900" # VNC tcp_out_ports="$tcp_out_ports,5222,5190,5050,1863" # IM tcp_out_ports="$tcp_out_ports,6667" # IRC tcp_out_ports="$tcp_out_ports,25,26" # SMTP tcp_out_ports="$tcp_out_ports,110" # POP3 tcp_out_ports="$tcp_out_ports,995" # POP3S tcp_out_ports="$tcp_out_ports,143" # IMAP4 tcp_out_ports="$tcp_out_ports,993" # IMAP4S udp_out_ports="1194" # OpenVPN udp_out_ports="$udp_out_ports,53" # DNS udp_out_ports="$udp_out_ports,123" # NTP for (( index=0; index < intf_count; index++ )); do netw=${networks[$index]} intf=${interfaces[$index]} $IPFW add 11000 set 11 allow $logall \ tcp from me to $netw $tcp_out_ports out via $intf setup keep-state $IPFW add 11100 set 11 allow $logall udp \ from me to $netw $udp_out_ports out via $intf keep-state $IPFW add 11200 set 11 allow $logall tcp \ from me to $netw out via $intf setup keep-state $IPFW add 11210 set 11 allow $logall ip \ from me to $netw out via $intf keep-state if [[ $intf_count > 0 ]]; then $IPFW set enable 11 fi done ############################################################################## # # Set 20: Allowing specified inbound traffic # # Ports can be opened for inbound traffic using the following options # when invoking this script: # # --trusted-tcp PORT[,PORT...] # --local-tcp PORT[,PORT...] # --tcp PORT[,PORT...] # # --trusted-udp PORT[,PORT...] # --local-udp PORT[,PORT...] # --udp PORT[,PORT...] # # These options can specified a group of ports separated by commas, such # as "--tcp 80,443", or you can give the same option multiple times, # such as "--tcp 80 --tcp 443". # # The default, if no options are given, is to allow nothing. # ############################################################################## $IPFW set disable 20 for (( index=0; index < intf_count; index++ )); do netw=${networks[$index]} intf=${interfaces[$index]} if [[ ${trust[$index]} != false ]]; then if [[ -n "$trusted_tcp_ports" ]]; then $IPFW add $((20000 + index)) set 20 allow tcp $logall \ from $netw to me $trusted_tcp_ports \ in via $intf setup keep-state fi if [[ -n "$trusted_udp_ports" ]]; then $IPFW add $((20020 + index)) set 20 allow udp $logall \ from $netw to me $trusted_udp_ports \ in via $intf keep-state fi fi if [[ $netw != any || ${trust[$index]} != false ]]; then if [[ -n "$local_tcp_ports" ]]; then $IPFW add $((20100 + index)) set 20 allow tcp $logall \ from $netw to me $local_tcp_ports \ in via $intf setup keep-state fi if [[ -n "$local_udp_ports" ]]; then $IPFW add $((20120 + index)) set 20 allow udp $logall \ from $netw to me $local_udp_ports \ in via $intf keep-state fi fi if [[ -n "$public_tcp_ports" ]]; then $IPFW add $((20200 + index)) set 20 allow tcp $logall \ from $netw to me $public_tcp_ports \ in via $intf setup keep-state fi if [[ -n "$public_udp_ports" ]]; then $IPFW add $((20220 + index)) set 20 allow udp $logall \ from $netw to me $public_udp_ports \ in via $intf keep-state fi done if [[ $intf_count > 0 ]]; then $IPFW set enable 20 fi ############################################################################## # # Set 30: Reject ALL remaining packets, in or out # # Disabling this rule set will cause the default behavior to be to allow # all packets not rejected by the above rules. # ############################################################################## # Reject certain packets that seem to fly around with no purpose $IPFW add 30000 set 30 $unreach_host_prohib $logall udp from any 53 to any in $via_all $IPFW add 30010 set 30 $unreach_host_prohib $logall udp from any to any 53 in $via_all # Deny all traffic in or out, but divide the log between packets # inbound on known interfaces and all other packets $IPFW add 30100 set 30 $unreach_filter_prohib $logall udp from any to any in $via_all $IPFW add 30110 set 30 $unreach_filter_prohib log tcp from any to any in $via_all $IPFW add 30120 set 30 $unreach_filter_prohib $logall all from any to any in $via_all # Deny all outbound traffic reaching this point, and log it so we know # what the machine was trying to do $IPFW add 30200 set 30 $unreach_filter_prohib log all from any to any out $via_all # Lastly, we simply drop it all. $IPFW add 30300 set 30 $unreach_filter_prohib $logall all from any to any ############################################################################## # # END: Inform the system that the firewall is now installed # ############################################################################## logger -i -p daemon.notice -t firewall "firewall installed: $args" # rc.firewall ends here