Table of Contents
Before you can set up your OpenBSD box as any kind of router, you will need to enable IPv4 packet forwarding in the kernel through the sysctl(3) mechanism. For added security, and because you generally will not need this feature anyway, you should also disable ICMP redirects. Issue these commands as root:
# sysctl -w net.inet.ip.forwarding=1 # sysctl -w net.inet.ip.redirect=0
Also, append the lines
net.inet.ip.forwarding=1 net.inet.ip.redirect=0
to the file /etc/sysctl.conf
in order to
make this configuration persistent across reboots.
Next comes the lengthy task of configuring
pf(4), the OpenBSD Packet Filter. The firewall is
configured using the file /etc/pf.conf
, which is
divided into several sections pertaining to macro definitions, address
translation, traffic normalization and queueing, packet filtering, and
so forth.
Configure PF by editing /etc/pf.conf
as
follows:
##################### # Macros and Tables # ##################### ### Interface devices ### if_lo = "lo0" if_wan = "pppoe0" if_lan = "vr1" ### Subnets and address ranges ### # IPv4 loopback net_lo = "127.0.0.0/8" # IPv4 multicast multicast = "224.0.0.0/4" # All the private addresses that fall under our purview net_private = "10.19.0.0/16" # LAN subnet (as opposed to other subnets under our control, such as VPN # subnets) net_lan = "10.19.0.0/17" ### Port-based access control ### # Allowed TCP ports from Internet (ssh treated separately, below) ports_in_tcp = "{ auth 1194 }" # Allowed UDP ports from Internet ports_in_udp = "{ ntp 1194 1195 }" ### Address tables ### # Address groups which shouldn't be in circulation on the Internet table <martians> const { 127.0.0.0/8 192.168.0.0/16 172.16.0.0/12 \ 10.0.0.0/8 169.254.0.0/16 192.0.2.0/24 0.0.0.0/8 } # Table of ddresses that have attempted to brute-force attack our SSH # service from the Internet, for blocking purposes. table <wan_bruteforce> persist ########### # Options # ########### # Strictly associate connection states with network interfaces set state-policy if-bound # Reject disallowed TCP and UDP connections with an RST or ICMP UNREACHABLE # packet, respectively set block-policy return # Log packet- and byte-count statistics for our Internet connection set loginterface $if_wan # You gotta have loopback... set skip on $if_lo ######################### # Traffic Normalization # ######################### # Replace TCP timestamps and IP identification fields with random values to # compensate for less secure values that may be generated by other hosts. # Also, lower the MSS for traversing TCP connections to 1440 because our # PPPoE interface's MTU is lower than that of clients' LAN interfaces. match on $if_wan scrub (reassemble tcp random-id max-mss 1440) ############### # Translation # ############### # NAT match out on $if_wan from $net_private to !(if_wan) nat-to ($if_wan) #################### # Packet Filtering # #################### # Block everything unless we say otherwise block all # Prevent loopback address spoofing antispoof quick for $if_lo # Prevent spoofing where an antispoof rule wouldn't be a robust choice. # However, this is incompatible with the bridged tap device configuration # used for OpenVPN unless we set skip on one of the bridged interfaces. block drop in quick from urpf-failed # FTP proxy anchor anchor "ftp-proxy/*" ### WAN interface ### ## Ingress # Allow SSH access, but block any communications from hosts found trying to # brute-force the SSH server... block drop in quick on $if_wan inet from <wan_bruteforce> pass in quick on $if_wan inet proto tcp from any to ($if_wan) \ port 22 flags S/SFRA modulate state \ (max-src-conn-rate 3/30, overload <wan_bruteforce> flush global) # Necessary IPv4 ICMP types pass in on $if_wan inet proto icmp from any to ($if_wan) \ icmp-type { echoreq unreach redir timex paramprob } keep state # Allow inbound connections on certain TCP and UDP ports pass in on $if_wan inet proto tcp from any to ($if_wan) \ port $ports_in_tcp modulate state flags S/SFRA pass in on $if_wan inet proto udp from any to ($if_wan) \ port $ports_in_udp keep state block drop in on $if_wan inet from <martians> to any ## Egress pass out on $if_wan inet from ($if_wan) to any modulate state \ flags S/SA block return out log on $if_wan inet from any to <martians> ### LAN interface ### ## Ingress # Allow DHCP requests pass in quick on $if_lan inet proto udp from { 0.0.0.0 $net_lan } \ port 68 to { ($if_lan) 255.255.255.255 } port 67 # Just to make sure we don't accidentally lock ourselves out of SSH pass in quick on $if_lan inet proto tcp from any to (self) port ssh \ keep state flags S/SA pass in on $if_lan # Redirect outbound FTP connections through ftp-proxy pass in on $if_lan inet proto tcp to port ftp rdr-to 127.0.0.1 port 8021 ## Egress pass out on $if_lan
Now start the firewall:
# pfctl -f /etc/pf.conf # pfctl -e
Then, so that the packet filter will start automatically at
boot time, add the following line to
/etc/rc.conf.local
.
pf=YES
Due to the goofy way in which the old FTP protocol establishes its data connections, special provisions must be made in firewalls like ours in order to support active-mode FTP connections from LAN clients to servers in the outside world. In OpenBSD, these provisions take the form of the ftp-proxy(8) daemon.
The proxy works by intercepting outbound FTP control
connections, modifying key FTP commands as necessary, and adding
special, temporary firewall rules to allow the inbound active-mode
data connection. This is provided for by the anchor and FTP
redirection rules we've already added to
pf.conf
, as shown above.
Now we need only run the proxy. Run the following command to start (and daemonize) it:
# ftp-proxy
We'll also want ftp-proxy(8) to run
automatically when the system boots, so append the following to
/etc/rc.conf.local
:
ftpproxy_flags=""
OpenBSD 4.8 includes a modified version of the ISC DHCP server, which we will use to provide network configurations to IPv4 hosts on our local network.
First, provide something analogous to the following
configuration in the file /etc/dhcpd.conf
,
starting with the general network configuration:
authoritative; shared-network LOCAL-NET { option domain-name "example.net"; option domain-name-servers 10.19.0.1; option nntp-server 10.19.0.1; option time-servers 10.19.0.1; subnet 10.19.0.0 netmask 255.255.128.0 { option routers 10.19.0.1; option domain-name-servers 10.19.0.1; range 10.19.2.1 10.19.2.254; } }
One of the goals of this project was to set up my own recursive
domain name server to handle DNS requests on behalf of the LAN, so
the DHCP option domain-name-servers
has here been
configured to return the gateway's own private IP address as the
primary name server.
Append to dhcpd.conf
any static DHCP
allocations you would like to make, using the following template –
substituting the actual desired hostnames, IP addresses, and MAC
addresses:
group { host noatun { hardware ethernet XX:XX:XX:XX:XX:XX; fixed-address 10.19.1.1; } host loki { hardware ethernet XX:XX:XX:XX:XX:XX; fixed-address 10.19.1.2; } }
Here we're using the 10.19.1/24 block for static DHCP allocations, and 10.19.2/24 for dynamic DHCP clients.
OpenBSD 4.8 also includes a version of ISC BIND 9, the Internet's de facto standard DNS server. Running a caching BIND server on your gateway can greatly reduce the latency experienced while browsing the Web, by minimizing DNS request round-trip time. This is especially noticeable if your ISP's DNS servers are underpowered and over-utilized.
Also, having your own server protects you from DNS abuse at the hands of your ISP, such as Cox Communications' "Enhanced Error Results Page," which breaks expected DNS behavior so that Cox can make a few extra bucks advertising to typo-prone customers.
BIND's main configuration file is located at
/var/named/etc/named.conf
. Edit this file with
a configuration like the following:
acl clients { ::1; 127.0.0.1; 10.19.0.0/16; }; options { version ""; listen-on { 127.0.0.1; 10.19.0.1; }; allow-recursion { clients; }; }; logging { category lame-servers { null; }; }; // Standard zones // zone "." { type hint; file "etc/root.hint"; }; zone "localhost" { type master; file "standard/localhost"; allow-transfer { localhost; }; }; zone "127.in-addr.arpa" { type master; file "standard/loopback"; allow-transfer { localhost; }; }; zone "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa" { type master; file "standard/loopback6.arpa"; allow-transfer { localhost; }; }; // Master zones // zone "example.net" { type master; file "master/db.net.example"; allow-transfer { localhost; }; allow-update { localhost; }; // Dynamic DNS for example.net. }; zone "19.10.in-addr.arpa" { type master; file "master/db.10.19"; allow-transfer { localhost; }; };
Next create the files in the
/var/named/master
directory which were
referenced above. A detailed discussion of the zone file format is
beyond the scope of this guide; refer to Cricket Liu & Paul
Albitz's DNS and BIND if you
really want to get into the details. However, the following examples
should suffice as templates for most use cases, starting with
db.net.example:
$ORIGIN example.net. $TTL 3h ; 3 hours @ IN SOA midgard.example.net. root.example.net. ( 120 ; serial 3h ; refresh (3 hours) 1h ; retry (1 hour) 1w ; expire (1 week) 1h ; minimum (1 hour) ) NS midgard.example.net. $TTL 5m ; 5 minutes A X.X.X.X ; Replace with your public IPv4 address $TTL 3h ; 3 hours ntp CNAME example.net. midgard A 10.19.0.1 ap A 10.19.0.2 noatun A 10.19.1.1 loki A 10.19.1.2
The $ORIGIN
keyword allows you to specify a base
name for all hostnames in this file, so you can say, e.g.,
noatun
rather than noatun.example.net.
when
specifying that computer's A record.
Next up, reverse DNS for your network's address block, in the
file db.10.19
:
$ORIGIN 19.10.in-addr.arpa. $TTL 3h @ IN SOA midgard.example.net. root.example.net. ( 120 ; serial 3h ; refresh 1h ; retry 1w ; expire 1h ; negative caching TTL ) NS midgard.example.net. 1.0 PTR midgard.example.net. 2.0 PTR ap.example.net. 1.1 PTR noatun.example.net. 2.1 PTR loki.example.net.
The configuration file in Section 7.4.1, “named.conf” specified that dynamic updates should be allowed for the domain. In order for this to work, the name server itself must be allowed to write to the zone master files' directory. Give the name server's system account ownership of this directory:
# chown -R named /var/named/master
Start the BIND domain name server by simply running the command
named as root. Watch for any error messages which
may indicate a syntax error in the zone files or in the BIND
configuration file. If the daemon starts successfully, add the
following line to /etc/rc.conf.local
so that
named(8) will run automatically on subsequent
reboots:
named_flags=""