If you plan on using bridge(4) for software-based switching, create the interfaces.# echo dhcp > /etc/hostname.em0 # if you have a static IP, use that instead # echo up > /etc/hostname.em1 # echo up > /etc/hostname.em2 # echo up > /etc/hostname.em3
And add the following lines:# echo 'inet 192.168.1.1 255.255.255.0 192.168.1.255' > /etc/hostname.vether0 # vi /etc/hostname.bridge0
If you plan on using a physical switch, give your secondary interface the network information instead.add vether0 add em1 add em2 add em3 blocknonip vether0 blocknonip em1 blocknonip em2 blocknonip em3 up
In either case, IP forwarding must be enabled to allow passing packets between interfaces.# echo 'inet 192.168.1.1 255.255.255.0 192.168.1.255' > /etc/hostname.em1
This change will take effect upon the next reboot.# echo 'net.inet.ip.forwarding=1' >> /etc/sysctl.conf
Take this example and adjust to fit your needs:# rcctl enable dhcpd # rcctl set dhcpd flags vether0 # replace vether0 with your secondary interface if not bridging # vi /etc/dhcpd.conf
Use the MAC addresses of certain clients if they require static IPs. You can specify another RFC 1918 address space here if you prefer, or even a public IP block if you have one. Using this example, clients will query a local DNS server, detailed in a later section. If you don't plan on using a local DNS server, replace 192.168.1.1 in the domain-name-servers line with the address of your preferred upstream DNS server (8.8.8.8, for example).option domain-name-servers 192.168.1.1; subnet 192.168.1.0 netmask 255.255.255.0 { option routers 192.168.1.1; range 192.168.1.4 192.168.1.254; host myserver { fixed-address 192.168.1.2; hardware ethernet 00:00:00:00:00:00; } host mylaptop { fixed-address 192.168.1.3; hardware ethernet 11:11:11:11:11:11; } }
A configuration for a gateway system might look like this:# vi /etc/pf.conf
Now we'll break this ruleset down and explain what each line does.int_if="{ vether0 em1 em2 em3 }" table <martians> { 0.0.0.0/8 10.0.0.0/8 127.0.0.0/8 169.254.0.0/16 \ 172.16.0.0/12 192.0.0.0/24 192.0.2.0/24 224.0.0.0/3 \ 192.168.0.0/16 198.18.0.0/15 198.51.100.0/24 \ 203.0.113.0/24 } set block-policy drop set loginterface egress set skip on lo0 match in all scrub (no-df random-id max-mss 1440) match out on egress inet from !(egress:network) to any nat-to (egress:0) block in quick on egress from <martians> to any block return out quick on egress from any to <martians> block all pass out quick inet pass in on $int_if inet pass in on egress inet proto tcp from any to (egress) port 22 pass in on egress inet proto tcp from any to (egress) port { 80 443 } rdr-to 192.168.1.2
This is a macro, used to make overall maintenance easier. Macros can be referenced throughout the ruleset after being defined. The int_if macro is a list of internal interfaces, including the virtual ethernet interface. If you're using an external switch instead of bridge(4), replace { vether0 em1 em2 em3 } with your secondary interface name.int_if="{ vether0 em1 em2 em3 }"
This is a table of non-routable private addresses that will be used later.table <martians> { 0.0.0.0/8 10.0.0.0/8 127.0.0.0/8 169.254.0.0/16 \ 172.16.0.0/12 192.0.0.0/24 192.0.2.0/24 224.0.0.0/3 \ 192.168.0.0/16 198.18.0.0/15 198.51.100.0/24 \ 203.0.113.0/24 }
PF allows certain options to be set at runtime. The block-policy decides whether rejected packets should return a TCP RST or be silently dropped. The loginterface is exactly what it sounds like: which interface should have packet and byte statistics collection enabled. These statistics can be viewed with the pfctl -si command. In this case, we're using the egress group rather than a specific interface. The egress keyword automatically chooses the interface that holds the default route, or the em0 WAN interface in our example. Finally, skip allows you to completely omit a given interface from packet processing. The firewall will ignore loopback traffic on the lo(4) interface.set block-policy drop set loginterface egress set skip on lo0
The match rules used here accomplish two things: normalizing incoming packets and performing network address translation, with the egress interface between the LAN and the public internet. For a more detailed explanation of match rules and their different options, refer to the pf.conf(5) manual.match in all scrub (no-df random-id max-mss 1440) match out on egress inet from !(egress:network) to any nat-to (egress:0)
Packets coming in on the egress interface should be dropped if they appear to be from the list of unroutable addresses we defined. Such packets were likely sent due to misconfiguration, or possibly as part of a spoofing attack. Similarly, our clients should not attempt to connect to such addresses. We'll specify the "return" action to prevent annoying timeouts for users.block in quick on egress from <martians> to any block return out quick on egress from any to <martians>
The firewall will set a "default deny" policy for all traffic. This means we will only allow incoming and outgoing connections that we explicitly put in our ruleset.block all
With the previous rule in mind, there are obviously some types of traffic we want to pass in our setup. This line allows outgoing IPv4 traffic from both the gateway itself and the LAN clients.pass out quick inet
Allow all internal LAN traffic to pass.pass in on $int_if inet
Allow incoming connections to the router itself (on TCP port 22, for SSH) and forward incoming connections (on TCP ports 80 and 443, for a web server) to our machine at 192.168.1.2. This is merely an example of port forwarding.pass in on egress inet proto tcp from any to (egress) port 22 pass in on egress inet proto tcp from any to (egress) port { 80 443 } rdr-to 192.168.1.2
Setting up local DNS caching is very simple with unbound(8). Outgoing lookups can optionally be encrypted with dnscrypt-proxy, installed from the package system. When clients issue a DNS query, they'll first hit the cache. If unbound doesn't have the answer, it goes out (via an encrypted channel, if using dnscrypt-proxy) to the upstream resolver that you've configured. Results are then fed to the client and cached for a period of time, making future lookups of the same address much quicker. If you choose to use DNSCrypt, be aware that you're still trusting whichever upstream resolver is used to provide accurate responses.
Something like this should work for most setups:# rcctl enable unbound # vi /var/unbound/etc/unbound.conf
Further configuration options can be found in unbound.conf(5). Now we'll set up dnscrypt-proxy. It's not part of the base system, so you need to install it from ports or packages. If you'd like to use a normal authoritative DNS server instead, replace 127.0.0.1@40 in the forward-addr line with the address of the server you want to use (8.8.8.8@53, for example). Otherwise, it's just a matter of installing the tool and choosing a resolver.server: interface: 192.168.1.1 interface: 127.0.0.1 access-control: 192.168.1.0/24 allow do-not-query-localhost: no hide-identity: yes hide-version: yes forward-zone: name: "." forward-addr: 127.0.0.1@40
We'll set it to listen for connections on localhost, port 40. Note that the dnscrypt-proxy utility won't use any server (and thus won't work) by default; you need to specify one with the -R flag.# export PKG_PATH=http://ftp.openbsd.org/pub/OpenBSD/$(uname -r)/packages/$(uname -m) # pkg_add dnscrypt-proxy # rcctl enable dnscrypt_proxy
Replace someserver with an upstream resolver of your choice. The package includes a list of servers in the /usr/local/share/dnscrypt-proxy/dnscrypt-resolvers.csv file.# rcctl set dnscrypt_proxy flags -E -m 1 -R someserver -a 127.0.0.1:40
If you're using DNSCrypt to prevent information leakage, the following pf.conf(5) rule may be used as an additional safety belt:
As configured in a previous section, our DHCP server will give users a default DNS server to query. Unless they explicitly define another public DNS server, all the outgoing lookups should be encrypted. This PF rule will block any outgoing connections on port 53 if they aren't destined for our DNS cache. The log keyword is used so that the admin can easily investigate such connections with pflogd(8).block return in log on $int_if inet proto { tcp udp } from any to ! 192.168.1.1 port 53
If you want the gateway to use the caching resolver for lookups too, don't forget to change its /etc/resolv.conf file to point to 127.0.0.1. Since the majority of routers won't be doing many DNS queries, this is likely not needed. Also note that it will need to be changed back to an actual resolver when using bsd.rd for network-based upgrades if you do so.