Configuring FreeBSD as an Internet Gateway

Posted on 01/28/2016 by Brian Carey

I recently ditched our Comcast Business internet connection for Verizon FIOS in search of a more reliable provider.  For well over a year I had seen intermittent high levels of packet loss with Comcast.  One modem/router later and several technicians checking from the pole to the building resulted in everything looking fine in terms of signal strength according to them, but getting them to investigate further down the line seemed impossible.  So since my contract was almost up I decided to give FIOS a try.  Back when I originally switched from Comcast residential to business I had begged and pleaded for them to just allow me to use my own modem with my existing setup of my CentOS 6 server acting as the network gateway instead of their equipment but that was apparently not possible.  With FIOS, thats one added bonus I get back is the ability to just get an ethernet connection straight to the public.  However, since I've now switched my internal infrastructure to FreeBSD it was time to learn how to configure it as my gateway system for the network.  Turns out, it was much simpler than I remember CentOS being.

High Level Planning

Since I had a week from order to install, I did some planning ahead of time to get everything in place.  My existing setup was as follows:

  • Comcast modem/router providing DHCP services to my network
  • Wireless access point/5 port 1GbE switch dropped off of the Comcast router.  This device provides wireless to the entire building plus wired connection to a drop switch in the main office.
  • Primary FreeBSD server also dropped off of the Comcast router on interface igb0.  This system obviously was configured with a static IP address.

Once FIOS is installed I will have a straight ethernet connection to the public internet.  Sure I could have taken their modem. but why I have all I need and it will be much more reliable.  Here's how I figure it should go down.

  • Disconnect my server and existing WAP/switch from the Comcast router.
  • Plug the FIOS ethernet link into the 2nd Network port on my FreeBSD server which is interface em0.
  • Plug the existing interface on the LAN (igb0) into the WAP/switch.
  • Configure the server to provide DHCP/NAT services to the rest of the network and route the traffic as needed

A day before the install, I will stage the new configuration on the server so that all I will need to do is a quick cable swap and reboot to make the changes active.  Sure, I could have just restarted services as needed but it had been a while since my last reboot so I figured that would be easiest.

OS Level Configuration (/etc/rc.conf)

First, I will need to configure my public facing port on the server since it was not previously used.  Since I did not get a static IP with my FIOS it needs to simply be DHCP.  For clarity I provide the configuration for both interfaces even though igb0 doesn't change.  The following are changes required in /etc/rc.conf:

# Public Interface configuration.
ifconfig_em0="dhcp"

# LAN interface configuration 
ifconfig_igb0="inet 192.168.x.x netmask 255.255.255.0"

Next, since I previously had a different router, that was set in my configuration.  This system will now be the default gateway for the rest of the network but its default gateway needs to come from the FIOS DHCP.  I simply comment it out.

# Default gateway.  Not required for FIOS
#defaultrouter="192.168.x.x"

Next, I need to tell the system to forward packets for the rest of the network (NAT).

# Enable ip forward
gateway_enable="YES"

And finally, since I want to provide firewall services now also, I need to enable PF.

# Enable PF
pf_enable="YES"
pflog_enable="YES"
pflog_logfile="/var/log/pflog"
pflogd_enable="YES"
pfsync_enable="YES"

PF Configuration (/etc/pf.conf)

PF is FreeBSD's equivalent to iptables, but completely different.  I do a little reading and configure a very basic ruleset to lock things down until I can rework the configuration as needed once I'm up and running.  By default this is configured in /etc/pf.conf but that file will not exist if you haven't already been running PF.

Note that I have commented the options in the file as needed but better explanations can be found in the awesome FreeBSD documentation.

# $FreeBSD: releng/10.1/share/examples/pf/faq-example1 237681 2012-06-28 03:30:17Z rpaulo $
# $OpenBSD: faq-example1,v 1.5 2006/10/07 04:48:01 mcbride Exp $
#
# Firewall for Home or Small Office
# http://www.openbsd.org/faq/pf/example1.html
#

######################################
# macros
######################################
# Define the external/public interface
ext_if="em0"

# Define the internal LAN interface
int_if="igb0"

# Define a list of services to allow traffic for
tcp_services="{ 80 }"

# Define what types of ICMP to allow
icmp_types="echoreq"

######################################
# options
######################################
set block-policy return
set loginterface $ext_if
set skip on lo
scrub in

######################################
# nat/rdr
######################################
# Handle the NAT forwarding for the LAN
nat on $ext_if inet from !($ext_if) -> ($ext_if:0)

######################################
# filter rules
######################################
# Block all incoming
block in

# Allow outgoing and keep state
pass out keep state

# The antispoof mechanism protects against activity from spoofed or forged IP addresses
antispoof quick for { lo $int_if }

# Allow requests for our ports/services defined in the $tcp_services macro above to the server
pass in on $ext_if inet proto tcp from any to ($ext_if) port $tcp_services

# Allow the defined ICMP types
pass in inet proto icmp all icmp-type $icmp_types

# Allow all internal traffic
pass quick on $int_if no state

Dnsmasq Configuration (DHCP)

There are various options for providing DHCP services to your network.  Dnsmasq is a very nice little utility that provides alot of nice features yet is very simple to configure just what you want to use.  Since I was already using it for DNS services on my network (Comcast's DNS is horrible), its trivial for me to just tell it to provide DHCP services too.  To start with, the main configuration in /usr/local/etc/dnsmasq.conf.  In this case, all I had to add were the last three lines for DHCP.

# Never forward plain names (without a dot or domain part)
domain-needed

# Never forward addresses in the non-routed address spaces.
bogus-priv

# The path to the resolv.conf file for DNSMASQ.  This should NOT be your default /etc/resolv.conf.
resolv-file=/usr/local/etc/resolv.conf

# Only handle requests that are local.  You could also specify specific interfaces to listen on but
# This essentially listens on all local ones.
local-service

# Set the DHCP domain
domain=your.local.domain

# Set the DHCP address range (start, end, lease time)
dhcp-range=192.168.x.x,192.168.x.x,36h

# Set the default router to hand out.  This probably isn't required since DNSMASQ apparently will use
# itself as defaults but I did it for clarity
dhcp-option=option:router,192.168.x.x

Then, for your public DNS resolvers, add them to the file set for resolv-conf.  For example in /usr/local/etc/resolv.conf I have the following:

nameserver 8.8.8.8
nameserver 4.2.2.1

One additional note on Dnsmasq.  By default it reads /etc/hosts on startup and serves the entries out of there for the LAN.  This can be a very helpful feature to provide local addresses for public facing services if desired.

Making the Switch

Now all that was left was to get my connection.  Once the tech activated it I plugged into my server port and rebooted.  Once the system came back up I had my public IP on em0 as expected and could ping out.  Next step was to release/renew my DHCP on my laptop and run the same ping test...SUCCESS!  All told my entire LAN was reconnected in less than 5 minutes like nothing changed.

As always, please contact us with any questions or inquiries about this post.