Solved IPNat and Selective Source NATing

Hey folks -

I'm in the process of converting a lot of my infrastructure at home to FreeBSD. Next on the list is my router, which has been running Linux for years. It's a simple Atom-based system with 3 Ethernet interfaces on it:
  1. Connected to the Verizon FIOS ONT
  2. Connected to public VLAN
  3. Connected to private VLAN
I have a business class FIOS connection with a static "block" of IPs, so unfortunately it means I have to make a bridge out of the first two Ethernet ints so that my public-facing servers can get to their default route (on VZ's network, not mine. Grrr!)

Anyway, I think I can easily create the bridge interface, and write the appropriate ipfw rules to match what I'm currently doing with my Linux router. My question centers around selective source NATing. Specifically: the third Ethernet interface services my private VLAN. I want to make sure that any packet FROM that LAN to my public VLAN isn't NATed. I also want to make sure the public VLAN can route natively to the private VLAN without being DNATed. The only time I want packets NATed is if they originate from the private VLAN and exit the Ethernet interface facing the ONT.

The rule I use with Linux's IPTables to accomplish this is:

Code:
-t nat -A POSTROUTING -m iprange -s [private VLAN IP block] ! --dst-range [IP Range clipped] -o br0 -j SNAT --to [IP clipped]

The important bits:
  • The public IP range - VZ is dumb in that they don't use CIDR blocks but just a range (of almost-CIDR-blocks... idiots). So I can't do real CIDR notation there. That's fine because ipfw supports ranges. I assume ipnat does as well?
  • The NAT IP - I specifically NAT all of the outbound traffic to a certain IP, not to my router's public IP. Is that doable with ipnat?
  • ! - This tells iptables to not NAT for anything in that --dst-range. I looked around for examples of ipnat, and I didn't see anything about a negating operator. Might there be some other way to accomplish this?
 
This is how I would do it with PF (mainly because it would be easiest with PF). The extif would be the interface facing the VZ router, not the bridge(4) interface. The <publicaddresses> table would be all of the addresses in your public "block" of addresses. The bridge changes the scenario from the normal NAT slightly and the first line covers traffic going to both side of the bridge because PF is not aware of layer 2 things like a bridge. You would filter only on the member interfaces, not on the bridge(4) interface.

Code:
privatevlan = 192.168.1.0/24 # Examples, change to your setup.
table <publicaddresses> const { 1.2.3.7, 1.2.3.8/30}

no nat on $extif from $privatevlan to <publicaddresses>
nat on $extif from any to any -> $extif

One important thing here is that hosts on the public VLAN must be configured to use the VZ router as their default gateway because of the bridge, using the FreeBSD system as their default gateway won't work. The hosts on the private VLAN however would use the FreeBSD system as their default gateway.
 
The bridge changes the scenario from the normal NAT slightly and the first line covers traffic going to both side of the bridge because PF is not aware of layer 2 things like a bridge.

So that's kind of a challenge. Linux's IPTables can handle L3 filtering on an L2 bridge (the network guy in my shivers at the thought, but it works). In order for me to do this using your suggestion, it'd have to be able to run ipfw and pf at the same time. Is that possible?

Thanks!
 
So that's kind of a challenge. Linux's IPTables can handle L3 filtering on an L2 bridge (the network guy in my shivers at the thought, but it works). In order for me to do this using your suggestion, it'd have to be able to run ipfw and pf at the same time. Is that possible?

Thanks!

It should be possible, at least pfSense is using IPFW with PF to implement the so called captive portal that can block/allow access based on MAC addresses.

Do note that PF is still perfectly capable of filtering on the bridge, you just decide if you do the filtering on the individual member interfaces or the bridge interface itself. Take a look at the net.link.bridge.pfil_member and net.link.bridge.pfil_bridge sysctl(8)s in the bridge(4) manual page. I've found the first case to be easier. You won't need IPFW unless you must have L2 filtering of some sort.
 
I wanted to necro this thread specifically because, after long last, I converted my router to FreeBSD and went to work trying to get pf to do the things I asked about in the OP. After some thrashing around, I think I finally have it.

The first thing tripping me up (because I didn't read the previous post carefully enough) was the filtering on the individual L2 interfaces in bridge0. Well, the aforementioned post had the answer: stop it with a sysctl net.link.bridge.pfil_member=0 and carry on. That was killing me until I realized that the individual interfaces were blocking the packets when the bridge interface was supposed to be allowing it.

The NAT rules as suggested in the previous posts were nearly right on the money. I covered all bases with this, and I'll explain them in a moment:
Code:
no nat on $br from $external_ipv4_lan to any
no nat on $br from $local_ipv4_lan to $external_ipv4_lan
nat on $br from $local_ipv4_lan to any -> lo100

The macros should be easy to figure out, save the $br which is the bridge0 interface. Now, why the -> lo100? I couldn't figure out the syntax within pf to say, "Hey, NAT using this source IP". It only takes a source interface, and I didn't want pf using the bridge0 IP address. I tried with an alias on bridge0, but the outgoing packets would seemingly randomly be sourced from either IP.

So I tried an experiment and set up interface loopback100 with the IP I want pf to use.
Code:
# lo100 just to source the NAT from
ifconfig_lo100="inet <NAT IP>/32"

And it works. Any time I check an external "what's my IP" website via something that gets NAT'd, it comes back with loopback100's IP.
 
Back
Top