Eric Radman : a Journal

Breaking the Tyranny of Private IP

Starved for Numbers

We're still waiting for IPv6, and based on progress to this point we'll be waiting for many years to come. Until then I'm suffering under the rule of 32-bit IP addresses run through nasty TCP and UDP port address translation mechanisms that preclude the implementation of other protocols.

One of the reasons that the world loves restricted address space is because service providers can then charge for addresses. Internet address blocks cannot be bought, but they can be sold!

If you're lucky enough to own addresses somewhere, you can potentially have them anywhere. Here's how I did it.

Configuring Site A

GRE is often used to bridge private networks together, but it can be used to bring public addressing into a network that is only serviced by a single public IP.

Since I run an Internet service, I have access to real blocks of addresses. The router handling the subnets I'm writing about is a Cisco 3660. First I used the invaluable ipcalc to carve out link addresses and the block to route to my office.

72.20.216    .64                                  /29  252
             .65   Tunnel2 on tei-sc-3600a
             .66   gre0 on ia100-ob0
             .67

Next I allocated a range to assign to network devices and computers:

72.20.216    .56                                  /28  248
             .57   fxp0 on ia100-ob0
             .58
             .59
             .60
             .61
             .62   ss75--nb0.eradman.com
             .63

Now in Cisco-land build a tunnel like this:

interface Tunnel2
 description Public net-block pushed over to TEI office
 ip address 72.20.216.57 255.255.255.252
 tunnel source FastEthernet0/0.1
 tunnel destination 69.168.230.114

tunnel source is the physical or logical interface that the other end of the tunnel will talk to. If you're not using VLAN routing it won't be a sub-interface such as 0/0.2 it will be 0/0 or 0/1.

Configuring Site B

At the office I'm running an OpenBSD box on a P100. First make sure the appropriate kernel options are set in sysctl.conf:

net.inet.gre.allow=1
net.inet.ip.forwarding=1

These are my hostname.* files:

hostname.ep0

192.168.1.1 netmask 255.255.255.0 broadcast 192.168.1.255

hostname.fxp0

192.168.168.36 netmask 255.255.255.0 broadcast 192.168.168.255
alias 72.20.216.57 netmask 255.255.255.248

hostname.xl0

dhcp

xl0 happens to be the interface connected to our service provider's cable modem. Unlike DSL with PPPoE it's address doesn't change when re-connected. If it ever does I have to reset the tunnel endpoints.

I copied the commands to create the GRE tunnel in rc.local like this:

ifconfig gre0 create
ifconfig gre0 72.20.216.66 72.20.216.65 netmask 255.255.255.252 up
ifconfig gre0 tunnel 69.168.230.114 209.183.177.66

The second line is to assign the endpoint addresses to the tunnel. A gre interface requires two parameters because it's a point-to-point interface. You can route traffic to the either endpoint address because either way the traffic is wrapped and shoved down the pipe.

Integrating NAT and Outbound Routing

Next I built a set of packet filter rules and set pf=YES in rc.conf.

There are three operations I need to accomplish with PF:

  1. Provide translation from the external interface to internal addresses using NAT so that PCs with private addressing maintain traditional Internet access
  2. Route traffic sourced from my public subnet out through the GRE tunnel
  3. Provide a mechanism for publicly addressed devices to talk to other devices on the inside
# /etc/pf.conf

ext_if = "xl0"
int_if = "fxp0"
untrusted_if = "ep0"
tunnel_if = "gre0"

tcp_services = "{ 22, 23, 80, 515 }"
udp_services = "{ 500 }"
icmp_types = "echoreq"

# options
set block-policy return

# scrub
scrub in all

# nat/rdr
nat on $ext_if from 192.168.168.0/24 to any -> ($ext_if)
nat on $ext_if from 192.168.1.0/24 to any -> ($ext_if)

# filter rules
#
pass in quick on $int_if route-to ($int_if 192.168.168.37) \
    inet from 72.20.216.62 to 192.168.168.0/24
pass in quick on $int_if route-to ($tunnel_if 72.20.216.65) \
    inet from 72.20.216.62 to any
pass on { lo, $int_if, $tunnel_if }
pass in on $ext_if inet proto tcp from any to any port $tcp_services \
    flags S/SA keep state
pass in on $ext_if inet proto udp from any to any port $udp_services \
    keep state
pass in inet proto icmp all icmp-type $icmp_types keep state

block in on $untrusted_if from any to 192.168.168.0/24

In this file 72.20.216.62 is the first host with public addressing on the inside of my network. The first filter rule uses a route-to statement to redirect traffic to another router (in this case Windows 2000 Server with Routing and Remote Access turned up) if the destination is a known local network. The second filter rule bypasses NAT, the local routing table, and other rules to ship traffic through the tunnel, but only if the source is my public host.

All other traffic not destined for a locally-connected subnet is reshaped by the nat redirection rules.

If you're paying attention you may notice that I'm explicitly allowing particular TCP, UDP, and ICMP traffic on the outside interface without a rule to block any other traffic. I do this just out of habit--if I decide to apply some stateful functions I'll already know what kinds of traffic I want to permit. It also enables me to see what rules are being evaluated while processing packets:

$ sudo pfctl -s rules -v 
...
pass on gre0 all
  [ Evaluations: 2326      Packets: 524       Bytes: 34433       States: 0     ]
...

While testing flush rules with pfctl -F rules and load new ones with pfctl -f /etc/pf.conf.

My First Public Host on the Inside

On my NetBSD workstation I set up ifconfig.le1 with a static address:

inet 72.20.216.62 netmask 255.255.255.248

And then set the rout to every other host in the world in rc.conf:

defaultroute=72.20.216.57

If routing is working is done right I can use public or inside name servers in resolv.conf, but now this host can't communicate with my systems on the 192.168.0.0/16 range, but the router can forward packets between subnets.

$ netstat -rn -f inet
Routing tables

Internet:
Destination        Gateway            Flags     Refs     Use    Mtu  Interface
default            72.20.216.57       UGS         1     3885      -  le1
72.20.216.56/29    link#2             UC          1        0      -  le1
72.20.216.57       00:a0:c9:6c:51:90  UHLc        1        0      -  le1
127/8              127.0.0.1          UGRS        0        0  33196  lo0
127.0.0.1          127.0.0.1          UH          1       40  33196  lo0

$ ping -n -c 4 192.168.168.37
PING 192.168.168.37 (192.168.168.37): 56 data bytes
64 bytes from 192.168.168.37: icmp_seq=0 ttl=127 time=2.713 ms
64 bytes from 192.168.168.37: icmp_seq=1 ttl=127 time=2.600 ms
64 bytes from 192.168.168.37: icmp_seq=2 ttl=127 time=2.587 ms
64 bytes from 192.168.168.37: icmp_seq=3 ttl=127 time=2.693 ms

----192.168.168.37 PING Statistics----
4 packets transmitted, 4 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 2.587/2.648/2.713/0.064 ms