Hopefully, you have already read and/or understand these concepts:
Now, it's time to talk about rules; particularly ip rules. Rules are the lifeblood of the Routing Policy DataBase (RPDB). Rules are processed prior to routes. In order to get to a route, a packet must be directed there by a rule. Rules direct traffic to a specific action or routing table. This is why you'll notice rules use the terminology "lookup" and then reference a routing table. This is because the rules effectively do "lookup" or read and execute the contents of the directed routing table name. For example, the default Linux rules allow all traffic and direct both incoming and outgoing traffic and 'lookup' the default routing tables (e.g. localhost, main).
WARNING: Routes and rules are not automatically available during the current session. To activate new rules and routes created with ip rule and ip route, you must flush the cache; either by rebooting the server or forcibly flushing the cache. To activate new iptables rules that are persistent between server reboots, the process is a bit more involved and is explained in Persistence: Making iptables Changes Stick.
New rules are created with the ip rule add command.
IP Rule Syntax
Like almost everything on this site, I'm not going to delve into every possible parameter and switch in ip rule, but I shall go over the most common variants. The syntax of a rule looks like this:
ip rule add {prefix} {selector} {predicate}
Sounds simple enough, eh? But, what is a selector? What is an action?
The rule type add - as its name implies - adds a new rule to the RPDB. Within the context of the ip rule add command there are three components: prefix, selector, and predicate. The prefix determines if the rule applies to incoming or outgoing traffic. The selector is the filter or what conditions are being applied. The predicate is the object of the selector or an action. Their syntax looks like this:
Prefix
from {addr} | to {addr}
Selector
to {addr} | priority {#} | fwmark {fwmark} | iif {name} | oif {name} | tos {value}
Predicate
{ blackhole | prohibited | unreachable } | lookup {table id}
It's helpful to break these down a bit further.
The prefix is a combination of a from and/or to statement, and a single IP address or range of IP addresses, with our without a netmask.
from
An address or range of addresses with the given source IPv4 address or range.*
to
An address or range of addresses with the given destination IPv4 address or range.*
* Note: "all" is an acceptable wildcard, and means "all addresses."
A prefix of from pertains to an incoming filter where the associated IP address is a source address/range, with or without a netmask. A prefix of to pertains to an outgoing filter where the associated IP address or range is a destination address/range. These source and/or destination addresses are used to determine whether or not the current rule up for evaluation should be applied to the current network packet.
After the prefix comes the next step in the filtering process: the selector. Multiple selectors may be used in the same rule. These are filtering screens. The selector is comprised of the following choices:
Selector | Description |
---|---|
fwmark | a means of marking packets; decimal values are converted to hexadecimal |
iif | in-bound interface {name} |
oif | out-bound interface {name} |
priority | rule # you wish assigned; must be unique; lower # = higher priority |
tos | ToS=Type of Service; rarely used by home users; for more info see ToS |
The predicate is a bit easier to follow. There are four possible outcomes:
Predicate | Description |
---|---|
lookup | return route found in the referenced routing table |
blackhole | drop packet silently |
prohibited | reject packet and error, "Communication is administratively prohibited" |
unreachable | drop packet and return error, "network unreachable" |
FYI on FireWall Marks (fwmark): ip rule cannot create or add a mark to a packet. It can detect the presence of an fwmark and apply branching logic (filtering) if an fwmark exists, but it cannot manipulate firewall marks.
IP Rule Examples
Here are some examples to demonstrate the concepts discussed above. An example that points to a new table called custom.
ip rule add to 192.168.1.200 lookup custom
Let's pick apart this example. The "to" prefixing an IP address means it is a destination address. "Lookup custom" means the corresponding action is to lookup or read the routing table named custom. So, an English reading of the corresponding new rule would be something like,
"If the current packet destination address is 192.168.1.200, lookup and execute a route in the table named custom."
Here are more examples of rules you could create:
# match a source network and direct its traffic to a particular table
ip rule add from 192.168.1.0/24 lookup mytable
# prohibit traffic from a source network
ip rule add from 192.168.1.0/24 prohibit
# blackhole any traffic headed for a particular sub-net
ip rule add to 10.10.10.0/24 blackhole
# direct traffic addressed from a specific address to a specific sub-net
ip rule add from 192.168.1.33/32 to 192.168.2.0/24 lookup default
# direct packet from specific IP to specific sub-net to table named test123
ip rule add from 192.168.1.33 to 192.168.1.0/255.255.255.0 lookup test123
# silently drop any traffic attempting to reach between two sub-nets
ip rule add blackhole from 192.168.1.0/24 to 172.16.11.0/24
# direct marked packets to table test123
ip rule add from 192.168.1.0/24 fwmark 1 lookup test123
# set priority and filter specific addr to specific addr
ip rule add priority 22 from 192.168.1.33 to 192.168.1.200 lookup default
Rule Priority
Rule priorities are processed in order from lowest to highest number. Take a look at your current rules.
ip rule show
An unaltered, new ip rule table will look similar to this:
0: from all lookup local
32766: from all lookup main
32767: from all lookup default
Rules are evaluated in order against packets based on the longest match principle. It means the rule matches the current packet and is also the longest character length rule in the RPDB that meets the matching criteria. In the case of a tie, when more than one rule both matches the logical criteria and is the longest matching length rule and is of equal length as another matching rule, only then does priority come into play. At that point the highest priority match wins the battle. Functionally, because it applies to IP addresses, this model normally equates to a model of most specific, because logically that is going to be the longest matching bit pattern. However, that is not always the case as the length of the rule (in bytes within the rule table) influences the outcome. Therefore, if you have more than one potential matching rule for a particular scenario, it's a good idea to place your preferred outcomes at lower rule numbers (higher priorities), just in case.
Important Concept: When adding a new rule via the command line, if the rule number is not specified, the next sequential rule number preceding the last used rule number will be selected. For example, if the previous rule you created has a priority of 100, and you then add another new rule but don't specify a priority number, the number 99 will be assigned to the new rule.
To re-cap, the process works like this:
Rule filter -> Rule Match [Longest + Highest Priority] -> Routing Table -> Most Specific Route
Rules filter the packet first, which identifies the appropriate routing table. Rules are evaluated in order. They are numbered from 0-32767. The routing table is then scanned for a matching route based on the source or destination IP of the packet.
Let's break down how these components interact with one another to derive at a route for each packet. Linux evaluates routing requests in this order:
- Process the rules in order starting with rule #0
- Check each rule in the database to see if it matches the packet
- If there is more than one match, choose the longest matching rule (presumed to be the most specific)
- If no matching rule is found, return a non-reachable error
Take a look at the incumbent rule set after installing Ubuntu. You can see it's very simple and contains just three rules.
0: from all lookup local
32766: from all lookup main
32767: from all lookup default
If you were to create a new rule and not specify a priority for it, such as this rule:
ip rule add from all iif eth0 lookup main
Your RPDB table would then look like this:
0: from all lookup local
32765: from all iif eth0 lookup main
32766: from all lookup main
32767: from all lookup default
Incidentally, the new rule above would instruct the kernel to route all traffic coming from the incoming interface eth0 to the main router table.
Don't forget to pay attention to the order of your rules and the order of when/how each table is called.
If your server doesn't have custom tables yet you may view your current RPDB rule collection using this command:
ip rule show
All RPDB rules are loaded into the kernel’s memory when the server starts up. If you make changes to ip rules or ip routes and wish to utilize them prior to the next system reboot, you must flush the cache. This forces the kernel to reload the rule and routing databases. To do this, run:
ip route flush cache
How Default RPDB Rules Function
While reading the Rule Priority section above, you might have wondered how the default ip rules can possibly be useful. After all, they are simply three "from" rules. Let's review them.
ip rule show
Displays:
0: from all lookup local
32766: from all lookup main
32767: from all lookup default
How is it possible that all packets are moved through the RPDB if there are no corresponding "to" rules? How is it that outgoing packets originating from the server don't get stuck? The answer is that every local packet - whether outgoing or addressed to localhost - still has a "from" or source IP address, regardless of its "to" or destination address. This fact allows all traffic to move through the rules because all traffic is coming "from" some address. The rules are checked in order from 0 - 32767. The kernel looks through the entire list to figure out which route is the most specific for the packet. The default rules simply pass all traffic through each table - local, main, default - in succession until a match is found or the packet fails to be matched to anything in any of these tables.
Routing Marked Packets with fwmark
The FireWall MARK (fwmark) can only be set by iptables, while both iptables and ip rule can read fwmarks (the ip rule system can only read its value and act on it as a selector). Note fwmark converts decimal values to hexadecimal, though a query regarding its value may use either decimal or hexadecimal numbers. If referencing it in hexadecimal form, precede the value notation with an "x." For example, these two statements are identical:
ip rule add fwmark 32 lookup mytable
ip rule add fwmark x20 lookup mytable
Either statement will forward packets with an fwmark value of 32 to the routing table named "mytable."