Map 100's of internal subnets...

Hey all,

Just a quick question. I've got a crazy client. He has (currently) 616 internal /24's. He wants to map each internal /24 to a specific ip address bound on the external interface of which there too are multiple public class C's. Something like this:

Code:
ipfw nat 1 config ip 143.xx.xx.1 deny_in unreg_only       // External ip 1
ipfw nat 2 config ip 143.xx.xx.2 deny_in unreg_only       // External ip 2
ipfw nat 3 config ip 143.xx.xx.3 deny_in unreg_only       // External ip 3
...
...
<snip>
ipfw add 014 nat 1 ip4 from any to 143.xx.xx.1 in via igb0    //  143.xx.xx.1 -> 10.5.0.x
ipfw add 015 nat 2 ip4 from any to 143.xx.xx.2 in via igb0    //  143.xx.xx.2 -> 10.5.1.x
ipfw add 015 nat 3 ip4 from any to 143.xx.xx.3 in via igb0    //  143.xx.xx.3 -> 10.5.2.x
...
...
<snip>
ipfw add 600 nat 1 ip4 from 10.5.0.0/24 to any out via igb0   // 10.5.0.0/24 -> 143.xx.xx.1
ipfw add 601 nat 2 ip4 from 10.5.1.0/24 to any out via igb0   // 10.5.1.0/24 -> 143.xx.xx.2
ipfw add 602 nat 3 ip4 from 10.5.2.0/24 to any out via igb0   // 10.5.2.0/24 -> 143.xx.xx.3
...
...
You could imagine, with 600+ internal /24's and a couple of /24's on the external subnet, I'd be looking at a bare minimum of 1800 lines just to handle the NAT. That wouldn't include any stateful rules that would need to be included or anything else this guy wants.

So a simple question is, is there a better way doing this utilizing ipfw that anyone can think of? I'd much rather use a single nat instance to handle the subnet mess, but I don't for the life of me think this is possible. PF is out of the question as well because recent benchmarks I've ran were horrid.

Any help?

Thanks in Advance.
 
Tell him it's a bad idea. That many rules will probably make the firewall perform quite badly.

Why does he want it?
 
Would it be possible to supernet the internal addresses in the NAT rules? I mean match larger address spaces than /24 in one rule.
 
I would try to write the firewall script in perl. Any other languages are welcome, this is the one I like for such tasks. I would also write a text file containging the mapping between public, private IPs and NAT configs. I would fill two tables with the internal and external ip mappings, using the table argument as a selector for the NAT config. The performance should be visibly improved, since this example would use only two ipfw rules.


The config file:
Code:
1|10.5.0.0/24|143.xx.xx.1
2|10.5.1.0/24|143.xx.xx.2

Then, the (not yet tested) firewall script, which creates the rules for each config line.

Code:
#!/usr/bin/perl -w
my $config_file = "/some/file";
my $cfg_line = "";
my $idx = 0;
my $map_inside = "";
my $map_outside = "";

open CONFIG_FILE, "<", $config_file or die "Can\'t read $config_file!";
while (<CONFIG_FILE>) {
    chomp;
    $cfg_line = split(/\|/, $_);
    $idx = $cfg_line[0];
    $map_inside = $cfg_line[1];
    $map_outside = $cfg_line[2];
    # Create the NAT configs
    qx/\/sbin\/ipfw nat $idx config ip $nat_outside deny_in unreg_only \/\/ Map $idx $map_inside to $map_outside/;
    # Load the table of inside networks
    qx/\/sbin/\/ipfw table 1 add $map_inside[color="Red"] $idx[/color]/;
    # Load the table of outside networks
    qx/\/sbin/\/ipfw table 2 add $map_outside[color="Red"] $idx[/color]/;
}

# create the rules for IPFW
qx/\/some\/static\/ipfw_script/;
qx/\/sbin\/ipfw add 011 nat tablearg ip4 from any to table\(2\) in via igb0/;
qx/\/sbin\/ipfw add 012 nat tablearg ip4 from table\(1\) to any out via igb0/;
# end script

Edit: I've added the most important part of this example: the table arg, now coloured in red. @pprocacci: Let me know if this fits your needs.
 
@SirDice :: I'm not the only one that's told him it's a bad idea. He's got a thick head though. He wants to ensure his clients don't effect the operation of other clients. In otherwords, if one of the public ip's get blacklisted, it wouldn't bring down others ability to send mail...as an example. So, what he does is take a private /24, and divides it up amongst the 40 or so boxes he leases with us. He assigned 1 client to a specific address amongst thouse 40 boxes. His client is bound to that specific subnet regardless of what box he's on. When talking to the public, he want them bound to a specific ip so he knows who's who I guess. I don't really know specific details, but that's it in a nutshell.

@kpa :: I don't know. He specifically asked for /24's to be mapped to individual outbound ip.s


@ecazamir :: I had thought about using tables to accomplish this but I almost didn't because I was thinking about using the tables on a skipto action which had this to say about it in the documentation:

Code:
             possible to use the tablearg keyword with a skipto for a computed
             skipto, but care should be used, as no destination caching is
             possible in this case so the rules are always walked to find it,
             starting from the skipto.

Your usage of table though gets around this it looks like. I'll have to revisit it.

Thanks for the input.
 
What I mean is that if there are consecutive /24s that are mapped to the same external address you could merge those into /23s possibly.

(Looking more closely to you post it looks like this might not be the case...)

For example:

10.5.0.0/24 -> x.y.z.a
10.5.1.0/24 -> x.y.z.a

This would accomplish the same NAT:

10.5.0.0/23 -> x.y.z.a

But if no two consecutive /24s are mapped to the same address then this is all academic.
 
pprocacci said:
He wants to ensure his clients don't effect the operation of other clients. In otherwords, if one of the public ip's get blacklisted, it wouldn't bring down others ability to send mail...as an example.

An example that suggests he's been blocked before. The mapping subnets thing might just be automated whackamole. Hope you can easily kick him off, if necessary, to protect your address space.
 
So each of his clients gets a full 10.x.x.x/24 subnet, but only one external routable one?

He assigns subnets to clients, a client keeps his subnet. Client one get 10.0.0.1, client2 get 10.0.0.2 etc etc ...

He then wants to shift these subnets to specific boxes.

This sounds like a peculiar idea.
 
@kpa :: It's more like this....

Code:
10.5.0.0/24  ->  x.y.x.1
10.5.1.0/24  ->  x.y.z.2
10.5.2.0/24  ->  x.y.z.3

The aren't mapped to the same external ip address ever. I couldn't use a /23 in this case.



@wblock@ :: We have full control over our network, and would never allow anyone to do any harm to us in any fashion. If it ever came down to it, we could turn him (or anyone for that matter) off within a moments notice. He's not doing anything wrong though and there aren't any complaints from any companies that we're aware of. So, thus far, it's a good thing.

@mix_room :: Yes, he assigns a private /24 to each of his clients. Each ip address within the /24 gets spread across, oh I don't know how many....approximately 50 machines. Something like:

Machine a:
Code:
10.x.x.1

Machine b:
Code:
10.x.x.2

Machine c:
Code:
10.x.x.3

Doing this, he can provide redundancy for his client having their stuff work on multiple machines, but also knows which client is depending on the subnet that the traffic is coming from.

Now as you know, 10.x.x.0/24 isn't routable and has to be nat'd. There's where the (for example:

Code:
10.x.x.0/24 -> 123.x.x.1

....comes into play. He doesn't want to "shift these subnets to specific boxes", he wants to have a single "gateway to the internet" in which all subnets map to an external specific ip address already bound to the external nic on the machine. A further example might be:

Gateway has:
Code:
123.x.1.0/24
123.x.2.0/24
123.x.3.0/24

Machine a has:
Code:
10.x.1.1
10.x.2.1
10.x.3.1

Machine b has:
Code:
10.x.1.2
10.x.2.2
10.x.3.2

... etc ... etc.

All machines use `gateway` above for necessary translations:

Code:
10.x.1.0/24 -> 123.x.1.1
10.x.2.0/24 -> 123.x.1.2
10.x.3.0/24 -> 123.x.1.3



My head hurts.
 
I'm thinking that maybe CARP would be a solution.

He could have all the clients boxes listen on the same carp interface.
Then each client would be given a separate carp interface:

Code:
client0:
carp0 vhid 0 pass 0 ... 123.y.y.1

client1:
carp1 vhid 1 pass 0 ... 123.y.y.2
...

Each machine could get its internal ip from the subnetwork, but they would work out the sharing between themselves.
Now perhaps you might need a layer of NAT if you cannot have the external IPs reply directly from the boxes, but it might help you in the right direction.
 
Everyone's getting way off topic. The question isn't "how do I redo my client's entire network setup in order to make things better/worse/whatever". The question is "is there a simpler way to configure ipfw to handle nat for several hundred /24 networks, each nat'd to one public IP".

The short answer is, not really. You need at least 3 lines for each NAT:
  1. ipfw nat 1 config blah blah blah
  2. ipfw add nat 1 incoming packets
  3. ipfw add nat 1 outgoing packets

That's the reality of the situation; the ruleset will be huge.

However, you can do some scripting magic to make the management/creation of the ruleset simpler, by creating those three rules via loops.
 
@mix_room :: We have redundant gateways, and they are using carp, so yes carp I'm familiar with. The boxes on internal network though are windows. Windows eludes me! ;)

@phoenix :: That's what I ended up doing. I created a file which contained lines similar to:

Code:
1|<subnet1>|<public_ip1>
2|<subnet2>|<public_ip2>
...
...
615|<subnet615>|<public_ip615>

Then used the following perl script (initially from the above poster, modified slightly to the following:

Code:
#!/usr/bin/perl -w

use strict;
my $config_file = "/root/etc/ipfw_start.map";

open CONFIG_FILE, "<", $config_file or die "Can\'t read $config_file!";
while (<CONFIG_FILE>) {
    chomp;
    my ($idx, $in, $out) = split /\|/;

    system(sprintf("/sbin/ipfw -q nat %u config ip %s deny_in unreg_only //Map %u %s %s", $idx, $out, $idx, $in, $out));
    system(sprintf("/sbin/ipfw -q table 1 add %s %u", $in, $idx));
    system(sprintf("/sbin/ipfw -q table 2 add %s %u", $out, $idx));
}
close(<CONFIG_FILE>);

system("/sbin/ipfw -q add 011 nat tablearg ip4 from any to table\\(2\\) in via igb0");
system("/sbin/ipfw -q add 012 nat tablearg ip4 from table\\(1\\) to any out via igb0");

@aragon :: I'm admittedly not that familiar with PF. I didn't know such an option existed.

###################################################################################
This'll be placed into the environment come sunday. Hopefully it doesn't puke!
 
pprocacci said:
@aragon :: I'm admittedly not that familiar with PF. I didn't know such an option existed.
Apart from reducing your NAT rules to one line, it's probably faster too... :)
 
PF NAT? I don't think that it's a good idea. Do you remember, for example, that it's only one-thread?
What's about task - it's absolutely normal and widely used by ISPs for home customers (at least in Russia).
 
@ecazamir :: Yep, I total nearly what you had verbatim. We tried putting it into production on Sunday, but we ran into a hard kernel limit... which was fixed here.

http://lists.freebsd.org/pipermail/svn-src-all/2011-April/037900.html

We'll be having another go at it this coming weekend. Wish me luck. ;P


@RusDyr ::
What's about task - it's absolutely normal and widely used by ISPs for home customers (at least in Russia).

Could you provide a link to which you are refering?
 
@ecazamir :: Nah, currently there is a pfSense (pf) box in which the CPU is pinned @ 50pps. I am replacing that with a much much beefier machine using ipfw since that's what I'm most familiar with. Cheers!

@RusDyr :: Thanks for the link. Much appreciated.
 
Back
Top