|
Also in FreeBSD Basics: File Integrity and Anti-DDoS Utilities |
In the last
article, I had successfully blocked all IP packets from entering or leaving
my FreeBSD computer by installing ipfw with a default policy to
deny all packets. This week, I want to create a ruleset to be read by
ipfw that will allow the IP packets I wish to leave and enter my
computer.
Since there is no "correct" way to create a ruleset, and I can't possibly
demonstrate how to add rules that will cover every possible scenario, I'll
instead demonstrate the logic one goes through when creating a ruleset. I'll
also assume that you are familiar enough with the syntax used by
ipfw that you can follow along as I create my rules.
If you're new to this series, you might want to first skim through the past articles, starting with TCP Protocol Layers Explained as we'll come across IP behaviour as we try to troubleshoot the ruleset.
When creating your own ruleset, keep in mind that rules are read in numbered
order, and as soon as a packet matches a rule, ipfw stops reading
the ruleset. This means that if you create two rules, say number 400 and number
800, that could apply to a specific IP packet, rule 400 would always be used and
rule 800 would not be read. It's always a good idea to look at your current
rules before adding another one to make sure an older rule won't override your
new rule.
Also, rules apply to every interface on your computer, that is, anything you
can see in the output of ifconfig -a. This isn't a problem if you
only use one interface as I'm doing in my example, but can make a difference if
you're using multiple interfaces. For example, if one interface is connected to
the Internet and another interface is connected to your internal LAN, you'll
probably want to apply different security restrictions to each interface and can
do so by specifying the interface name in your ipfw rules.
Let's return to my firewall setup. This is a stand-alone computer running FreeBSD 4.2-Release that has one interface cabled to the Internet. Since this is my home computer, I've decided not to place any restrictions on the types of packets I send out to the Internet; however, I only want my computer to accept IP packets that are a valid response to the packets I've sent out.
A good way to accomplish this task is to take advantage of the "dynamic" or
"stateful" feature of ipfw. If you're unfamiliar with this term,
there is a good
explanation here.
If I use "dynamic" rules, when I send out a packet to the Internet,
ipfw will add an entry to its "state table." This entry will
include the IP address of the computer I sent the packet to, and what port
number I made a connection to on that computer. When packets come back from the
Internet, they will be discarded if they do not come back from that IP address
using that port number. However, dynamic rules only work with TCP packets, as
TCP creates a connection that is used for the length of the data transfer. Since
UDP doesn't create a virtual connection, it is called a "state-less" protocol
and can't use the "state table."
The Examples section of the manpage for ipfw gives the three
rules that are used to create this "dynamic" packet filter. Since I've decided
to create my ruleset in a separate file that I've called
/etc/ipfw.rules, I'll become the superuser, and create that file
now with the following lines:
#from man 8 ipfw: allow only outbound TCP connections I've
created
add 00300 check-state
add 00301 deny tcp from any to any in
established
add 00302 allow tcp from any to any out setup
keep-state
Notice that I've decided to start numbering my rules at 300 since rules 100
and 200 are pre-created in the file /etc/rc.firewall. I like to
number related rules together, so I've numbered these rules 300, 301, and 302.
When I create more, unrelated rules, I'll jump up to 400. Remember, you can
number your rules any way you wish as long as the number isn't already in use
and an earlier numbered rule won't prevent your new rule from being read.
You'll notice that these three rules contain some key words that are
described in man ipfw, which I've quoted here:
check-state: Checks the packet against the dynamic ruleset.
If a match is found then the search terminates, otherwise we move to the next
rule.
keep-state: Upon a match, the firewall will create a dynamic
rule, whose default behaviour is to matching bidirectional traffic between
source and destination IP/port using the same protocol. The rule has a limited
lifetime (controlled by a set of sysctl(8) variables), and the lifetime is
refreshed every time a matching packet is found.
established: TCP packets only. Match packets that have the
RST or ACK bits set.
setup: TCP packets only. Match packets that have the SYN bit
set but no ACK bit.In other words, when a packet arrives at one of my interfaces,
ipfw will first check to see if it is in the state table; if it is,
the packet is allowed. (Rule 300 does check-state.) If it's not in
the state table and the RST or ACK bits are set, it will deny the packet because
it's not a valid response to a connection I've created. (Rule 301 checks for
established.) If the ACK flag is not set (meaning it wants to
initiate a TCP connection), it is allowed, but only if the packet is outbound;
if a packet meets this rule, it will also be added to the state table. (Rule 303
does setup and keep-state.)
Let's see what happens by adding these rules. Once I've double-checked my changes for typos and saved the file, I'll type:
killall initpress Enter, then type:exit
and watch my boot messages to make sure my rules load without any syntax
errors. If you instead receive an error message, your security level may be set
to 3 or higher and you'll have to first change this line in
/etc/rc.conf to a smaller value:
kern_securelevel="3"
then repeat the killall init command.
Once I'm logged back in, I'll see if I can send any IP packets out to the Internet and receive some replies back:
ping www.freebsd.org
ping: cannot resolve www.freebsd.org: Host name lookup failure
lynx www.freebsd.org
Alert!. Unable to access document.
Hmmmmm. Looks like I still don't have DNS name resolution. Let's try that again, using an IP address instead:
lynx 216.136.204.21
This time, I find myself at the home page of www.freebsd.org. Let's try pinging that IP address:
ping 216.136.204.21
PING 216.136.204.21 (216.136.204.21): 56 data bytes
ping: sendto: Permission denied
ping: sendto: Permission denied
ping: sendto: Permission denied
^C
--- 216.136.204.21 ping statistics ---
3 packets transmitted, 0 packets received, 100% packet loss
Now, let's try to understand this odd behaviour, as obviously some packets are going in and out of my computer and some are not. Let's start by picking apart which protocols I used in each of the examples above.
Name resolution is failing, as I was only able to access www.freebsd.org by using its IP address. When I use DNS, I send a name lookup request to my service provider's DNS server, which should send the response back to me. This seems to match our rules, as I make the request on port 53 and should receive a request back on port 53. I better double-check that I am aware of which DNS servers to send a request to:
more /etc/resolv.conf
search kico1.on.home.com
nameserver 24.226.1.90
nameserver 24.226.1.20
nameserver 24.2.9.34
That doesn't seem to be the problem, so it's time to look a bit deeper at how name resolution works. Let's see if we can glean any information from the online manual pages:
apropos resolve
dnsquery(1) - query domain name servers using
resolver
res_query(3), res_search(3), res_mkquery(3), res_send(3),
res_init(3), dn_comp(3), dn_expand(3) - resolver routines
resolver(5) -
resolver configuration file
I'll then try
man resolver
but will end up at man 3 resolver. Being the curious type, I
read it anyway and am intrigued by these lines:
RES_USEVC Use TCP connections for queries instead of
UDP datagrams.
RES_STAYOPEN Used with RES_USEVC to keep the TCP
connection open between queries. This is useful only in programs that regularly
do many queries. UDP should be the normal mode used.
I may be onto something here; if DNS is using UDP instead of TCP, my name
lookup will fail, as I've made rules only to allow TCP responses to my TCP
connections. Now I'll try that manpage for dnsquery:
man dnsquery
<snip to just show intriguing part>
-s Use a stream rather than a packet. This uses a TCP stream
connection with the nameserver rather than a UDP datagram. This sets the
RES_USEVC bit of the resolver's options field. (Default: UDP
datagram.)
Now, there's a switch I want to try:
dnsquery -s www.freebsd.org
;; ->>HEADER<<- opcode:
QUERY, status: NOERROR, id: 39772
;; flags: qr rd ra; QUERY: 1, ANSWER: 1,
AUTHORITY: 5, ADDITIONAL: 5
;; www.freebsd.org, type = ANY, class =
IN
www.freebsd.org. 49m21s IN CNAME freefall.freebsd.org.
freebsd.org.
22m43s IN NS ns1.iafrica.com.
freebsd.org. 22m43s IN NS
ns2.iafrica.com.
freebsd.org. 22m43s IN NS ns.gnome.co.uk.
freebsd.org.
22m43s IN NS ns0.freebsd.org.
freebsd.org. 22m43s IN NS
ns1.root.com.
ns1.iafrica.com. 1h1m3s IN A 196.7.0.139
ns2.iafrica.com.
1h1m3s IN A 196.7.142.133
ns.gnome.co.uk. 12m37s IN A
193.243.228.142
ns0.freebsd.org. 11h9m9s IN A
216.136.204.126
ns1.root.com. 1h8m12s IN A 209.102.106.178
Looks like name resolution works nicely when I make a DNS request using TCP.
Let's try it one more time without that s switch to see if it works
with UDP:
dnsquery www.freebsd.org
Query failed (h_errno=2) : Host name lookup failure
There we go; DNS is using UDP packets, and since I haven't allowed for UDP packets in my ruleset, I'm not getting DNS name resolution.
Now that we've solved that one, let's see why ping isn't
working, even with an IP address. If you've been following along in the series,
you'll remember from Examining
ICMP Packets that the ping utility uses ICMP, not TCP in its
packets. Again, since I've only allowed my own TCP connections in my ruleset,
I'm not going to have any luck if I try to send out ICMP packets.
Before adding any new rules to my ruleset, I'll become the superuser and see
what the output of ipfw show looks like:
su
Password:
ipfw show
00100 0 0 allow ip from any to any via
lo0
00200 0 0 deny ip from any to 127.0.0.0/8
00300 0 0
check-state
00301 0 0 deny tcp from any to any in established
00302 21
15144 allow tcp from any to any out keep-state setup
65535 142 10531 deny ip
from any to any
## Dynamic rules:
00302 19 15040 (T 0, # 147) ty 0 tcp,
24.141.119.162 2932 <-> 216.136.204.21 80
Note the Dynamic rules section; this is the state table. When I ran the
command lynx 216.136.204.21 to connect to the http port (port 80)
at www.freebsd.org, Rule 00302 allowed my setup packet out and added an entry to
the state table. Now, any packets that are addressed to or from 216.136.204.21
on port 80 will be allowed to enter or leave my computer.
You'll also note that rules 00302 and 65535 have numbers next to them that represent the number of packets followed by the number of bytes that met each rule. The packets that were denied by rule 65535 were the failed UDP and ICMP packets, as both of these protocols are part of an "ip" packet.
Before I add any more rules to my ruleset, I'll use the ipfw
zero command to reset these counters. This way, when I test my new rules,
I'll be able to see which rules have new packet statistics next to them.
I'll now add some rules to allow for DNS name resolution. Since DNS is using
UDP, and UDP doesn't make a connection, I can't specify to only allow in valid
responses to my connections. However, I can limit packets by the port number
used by DNS (port 53), and I can choose to only accept these packets from the IP
addresses of my provider's DNS servers. I discovered those IP addresses when I
ran the more /etc.resolv.conf command. I'll add the following lines
to my /etc/ipfw.rules file:
#allow DNS
add 00400 allow udp from 24.226.1.90 53 to any in recv
ed0
add 00401 allow udp from 24.226.1.20 53 to any in recv ed0
add 00402
allow udp from 24.2.9.34 53 to any in recv ed0
I'll then reload my rules using killall init and see if name
resolution now works:
lynx www.freebsd.org
Alert!. Unable to access document.
Wait a minute, how come I'm still not getting name resolution when I've
explicitly allowed in these UDP packets? Let's do an ipfw show to
see which rule has a packet count next to it:
su
Password:
ipfw show
00100 0 0 allow ip from any to any via
lo0
00200 0 0 deny ip from any to 127.0.0.0/8
00300 0 0
check-state
00301 0 0 deny tcp from any to any in established
00302 0 0
allow tcp from any to any keep-state setup
00400 0 0 allow udp from
24.226.1.90 53 to any in recv ed0
00401 0 0 allow udp from 24.226.1.20 53 to
any in recv ed0
00402 0 0 allow udp from 24.2.9.34 53 to any in recv
ed0
65535 30 2196 deny ip from any to any
## Dynamic rules:
The only rule that has any packet statistics associated with it is that last
deny rule; note that none of my allow udp rules were used. Then it
dawns on me, I've never allowed "out" any udp packets; no wonder there aren't
any udp replies anxious to come back in. Let's try adding one more line to that
ruleset:
add 00403 allow udp from any to any out
Here I've specified that I'm willing to allow out my own udp packets. I'll
clear those statistics with ipfw zero, repeat the killall
init command, and try one more time:
lynx www.freebsd.org
The main page of FreeBSD's website never looked so good. If I become the
superuser, I should have a more satisfactory ipfw show output:
ipfw show
00100 0 0 allow ip from any to any via lo0
00200 0 0
deny ip from any to 127.0.0.0/8
00300 0 0 check-state
00301 0 0 deny tcp
from any to any in established
00302 20 15061 allow tcp from any to any
keep-state setup
00400 10 1882 allow udp from 24.226.1.90 53 to any in recv
ed0
00401 0 0 allow udp from 24.226.1.20 53 to any in recv ed0
00402 0 0
allow udp from 24.2.9.34 53 to any in recv ed0
00403 10 591 allow udp from
any to any out
65535 31 2577 deny ip from any to any
## Dynamic
rules:
00302 19 15017 (T 0, # 236) ty 0 tcp, 24.141.119.162 4363 <->
216.136.204.21 80
Note that rule 00403 let out my DNS request, rule 00400 let in the DNS reply, rule 00302 set up the HTTP connection, and I now have an entry in the state table for my HTTP connection to 216.136.204.21.
I now have a working network connection, but there is still lots of room for improvement to this ruleset. In next week's article, we'll take a look at the additional rules which should be added to the ruleset, then we'll take a look at logging and console messages.
Dru Lavigne is a networking instructor at a private technical college in Kingston, ON, where she teaches the fundamentals of TCP/IP networking, routing, and security.
Read more FreeBSD Basics columns.
Return to the BSD DevCenter.
oreillynet.com Copyright © 2003 O'Reilly & Associates, Inc.