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:
- Provide translation from the external interface to internal addresses using NAT so that PCs with private addressing maintain traditional Internet access
- Route traffic sourced from my public subnet out through the GRE tunnel
- 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