>> PROJECT HOMEPAGE
Using the Firewall in an ISP Application
By Scott Bartlett April, 2003
Some of the most complicated firewall related questions have arisen surrounding the implementation of rc.firewall in an ISP application. More specifically, questions centered around how to insert a firewall between an existing network which uses a publicly routable address pool, and the border router of that network. Questions on this subject have appeared numerous times on the firewall discussion board. As a result of a few mind numbing discussions, a couple of solutions have emerged as leading candidates, though none were without drawbacks. The disorganized nature of the data presented through the discussion board and the introduction of a new solution with firewall release version 2.0rc9 has warranted the compilation of this paper.
The reader is assumed to have a working knowledge of networking, addressing, subnets, and routing and be familiar with the projectfiles.com Linux Firewall, aka rc.firewall.
The goal--
Convert this:
[ border router]-----------(unprotected internal network)
into this:
[border router]------[Linux firewall]------(protected internal network)
...In the simplest and most efficient manner possible.
Let us pretend that 172.16.75.0-172.16.75.255 is our IANA assigned publicly routable class C address pool, which of course is not really possible since 172.16.0.0/16 is reserved for private use, but for the sake of this example suspend your knowledge of this. Without a firewall in place, the internal interface of the border router is configured as 172.16.75.1 with netmask 255.255.255.0. The hosts on the internal network are configured with publicly routable addresses and have the default gateway pointing to the border router at 172.16.75.1.
Problem:
The firewall now must act as a router. However both the internal and
external interfaces are on the same network, 172.16.75.0/24!
Solution:
One of the first solutions proposed to this problem was to subnet the class C network into two equally sized networks. Attach one network, 172.16.75.0/25, to the external interface of the Linux firewall and the other, 172.16.75.128/25 to the internal interface. Configure the border router with an address 172.16.75.1/25 and configure the external interface of the Linux router as 172.16.75.2/25. Configure the default gateway of the Linux router to be the border router's address. Configure the internal interface of the Linux router with an address of 172.16.75.129/25. Have all hosts on the internal network set their default gateway to the address on the internal interface of the Linux router. Add a static route in the border router to route traffic destined for 172.16.75.128/25 through 172.16.75.2.
Problem:
This solution wastes a vast number of public addresses. The maximum number of hosts on the internal network is now 125, (128 minus broadcast address, network address, and default gateway) as opposed to the 253 addresses available before the firewall was implemented. While this solution may be workable for some applications it is certainly not efficient enough to consider a universal solution. Additionally, if configured on a preexisting network, internal hosts would probably need to be reconfigured so that their addresses would all be in the upper half of the original class C address pool. This could prove to be quite an extensive undertaking, especially when dealing with services such as DNS and web which can be a pain to transition to new addresses.
Solution:
A variation on this solution to waste less public addresses was to use a smaller network on the external interface, e.g. 172.16.75.0/26 and then have two (or more) internal networks, e.g. 172.16.75.64/26 and 172.16.75.128/25. This would allow for a small reduction in the wasted address space, but formulates a rather kludgey solution in that the more efficient the use of the public address space, the greater the required number of separate internal networks. Obviously a better solution must exist.
A Better Solution:
The revised version of this solution, and probably the most popular implementation in ISP applications prior to this paper has been to take advantage of a universal property of routing tables; routes to smaller networks always take precedence over routes to larger networks. Using this idea we can go ahead and overlap the internal and external networks, making the internal network the entire class C and the external network a minimally sized subnet, as demonstrated in the following example configuration:
Border router: 172.16.75.249/30
Linux firewall external interface: 172.16.75.250/30
Linux firewall internal (DMZ) interface: 172.16.75.1/24
Internal network: 172.16.75.0/24 except for 172.16.75.248-172.16.75.251
Now add a static route in your border router to route traffic destined for 172.16.75.0/24 through 172.16.75.250.
Before getting into the benefits of this solution over the last, one might ask, "Why not use a /31 to form a network between the Linux router and border router since we only need two addresses?" While you might indeed be able to get away with that, it is proper practice to leave a broadcast and network address for each subnet you create. A host configured on what another system considers to be a broadcast address has been demonstrated to cause connectivity problems. As a network administrator it is your decision to choose whether to follow this standard or not. Also note that the subnet was not chosen to be the last four (or first four) addresses to avoid clobbering the broadcast (or network) address of the class C.
So, what makes this solution better? Now there are 249 publicly routable addresses available for DMZ hosts, as opposed to the 125 available with the previous solution. An even more critical component in the success of this solution is that internal hosts do not require reconfiguration, provided you can find a 4 address subnet in the class C that is not already in use.
Problem #1:
What if we don't have access to our border router?
Solution:
In theory it should be possible to set up the Linux firewall as an ethernet bridge and perform firewall functions on passing packets, however this type of solution has not yet been implemented... yet. So far our only choice is to convert the entire internal network to a private address space and add an entry in STATIC_INSIDE_OUTSIDE for each server that needs to be accessed from the Internet. This solution requires configuring IP aliases on the external interface of the Linux router for each and every STATIC_INSIDE_OUTSIDE entry.
Problem:
First of all, any reconfiguration of an entire network is difficult to pull off transparently. In a conversion where servers need to communicate with each other frequently and downtime must be kept to a minimum, this might be a insurmountable obstacle. Another consideration might be the change in how the servers communicate with each other. Either they can be reconfigured to talk to each other on their private addresses, or else all INTERNAL as well as EXTERNAL communication will have to pass through through the Linux firewall. This may be either desirable or undesirable, desirable because it would grant firewall level control over what internal services can access each other but undesirable because all internal communication must end up at the Linux router. For high traffic communication, especially if all segments of the network are not on high speed LAN this would likely be undesirable. A final barrier to implementation is simply how time consuming it is to configure a large network in this solution. Adding say, 200 entries to STATIC_INSIDE_OUTSIDE would mean an entry to the directive of over 4000 characters, not to mention the 200 IP aliases to configure on the external interface. Impossible, no, but is there no easier way?
Solution:
Yes. As of 2.0rc9 a slightly easier solution is available to handle the situation where you do not have access to your border router. The firewall can now be configured to take a STATIC_INSIDE_OUTSIDE entry like 192.168.1.1/24:207.198.61.0/24 and the firewall will map the entire class C, address for address, onto the internal network. The firewall may still take a while to set up the massive iptables rule set required for this undertaking, and the external interfaces still need IP aliases for each address, but at least the firewall configuration has been made a good deal easier. Unfortunately, I suspect that without access to the border router, this is as good as it is going to get as far as a solution goes. While some future release of rc.firewall could theoretically configure the IP aliases automatically as required, it was decided long ago that the scope of a firewall should be limited to the configuration of firewall related rules. This way the user knows that the firewall will never change their routing table or interface configuration.
Backtracking a little, we find that there are still other problems with the solution involving using a four address subnet on the external interface of the Linux firewall.
Problem #2:
We cannot access the external address of the Linux firewall or the inside address of the border router without manually adding a static route pointing to the Linux box for the four host subnet we created on each internal host which needs to access those addresses. In some cases this may not be a big deal at all, but it is a caveat none the less. If only there was another way....
Solution?
Wait! Why can't we just put some arbitrary private network (like some 10.x.x.x) between the Linux firewall and the border router and put a static route for the ENTIRE class C to the Linux router's private external address? This way we don't even need to subnet the class C!
Unfortunately this solution neglects an issue vital to the operation of the firewall as a host itself. If the default gateway of the firewall is a 10.x.x.x address then traffic generated by the firewall itself will be sent out with with a source address of 10.x.x.x and will be dropped by the first Internet router it reaches. This effectively cuts off the firewall from accessing any host on the Internet. If only there was some way to perform network address translation on traffic from the Linux firewall itself....
Enter FIREWALL_IP, a configuration directive which does just that. This directive takes two addresses separated by a colon. The first address is the private address used on the Linux firewall on the interface connected to the border router. The second address is a publicly routable address which must be configured on a DMZ or internal interface. Confused? Here's what the network configuration should look like now:
Border router: 10.0.0.1/30
Linux firewall external interface: 10.0.0.2/30
Linux firewall internal (DMZ) interface: 172.16.75.1/24
Internal network: 172.16.75.0/24
Add a static route in your border router to route traffic destined for 172.16.75.0/24 to 10.0.0.2.
The FIREWALL_IP entry should look like the following:
FIREWALL_IP="10.0.0.2:172.16.75.1"
Packets bound for the Internet from the Linux firewall now have network address translation performed on their source address, converting it to 172.16.75.1. This is not a problem for receiving the returning data as the border router routes traffic bound for 172.16.75.0/24 to the Linux firewall at 10.0.0.2. Once the Linux firewall receives the packet it says, hey I'm 172.16.75.1! and accepts the data. This directive also effects the behavior of port forwarding as well as NAT performed on outbound traffic from an internal network that would normally have been sourced to the address of the outbound interface.
Any questions?
|