Routing

Overview

The purpose of the RoutingTable singleton is to allow clients to examine and modify routing tables and policy routing rules. Its primary client is the Connection class, whose instances modify routes/rules in order for network traffic to comply with configuration rules such as priority of connected Services and tunnelling of traffic through a VPN.

Background

Routing is the means by which network packets are sent from source nodes to destination nodes. With the IP protocol, a routing decision is made locally at every node that a packet reaches, until the packet either reaches its destination or expires (as determined by a packet's TTL field). Since routing decisions are made locally, the questions to answer at each node are “should this packet be dropped instead of sent forward?” and “if this packet should be sent forward, which interface should it be sent out of and to what node on that LAN?”. Note that sometimes the source and destination nodes are the same, in which case a loopback interface is used to send packets directly to the receiving local process rather than sent out of the machine and back. While different devices may use different methods for making these decisions, the most common way this is done, particularly for the second question, is through the use of routing tables.

A routing table is simply a set of rules matching destination address to egress interface. Since IP addresses are organized hierarchically, broad sets of destination addresses can be represented with a small set of addresses within a routing table through the use of the longest prefix match algorithm at routing time. Entries in a routing table can also have priorities--sometimes called a metric in utilities like ip route--that allow for disambiguation between routes that match equally well. The routes in a routing table can come from a number of sources. There exist a number of routing protocols that allow for routers to populate their own routing tables dynamically. In the case of Chrome OS hosts, however, routing tables are populated by the kernel itself and by Shill.

A single routing table, however, cannot be used to represent all of the routing decisions one could reasonably want their machine to employ. Properties of a packet such as source destination, quality of service, or packet markings left by a firewall are not considered by a standard routing table. While routing tables could be modified to consider all of these cases, an alternative that helps to contain complexity is to use policies (also known as rules) as a means of selecting a routing table based on properties of the packet to send, in a process known as policy-based routing. Policies have their own priorities, and a packet will be compared with policies in order of priority (lowest to highest) until a match is found. If the matched routing table does not have a suitable routing entry or the packet matches with a throw entry in the routing table, the routing process returns to the list of policies in order to find the next matching routing table. On Linux, the list of policies generally ends with a rule sending all packets to the main routing table.

As an aside, note that policy-based routing allows for sophisticated answers to the first of the two questions asked in the beginning of this section: “should the packet be dropped instead of sent forward?”. For example, one could create a routing table that only contains a blackhole route, which simply drops the packet. Policies can then be set to send particular traffic to that routing table to prevent it from being sent. For such firewall-related behavior on Linux, however, tools such as iptables that utilize the kernel's netfilter architecture are generally more popular. Aside from network administrator familiarity with iptables, using the netfilter architecture also allows for packet filtering of ingress and forwarded traffic, applying filtering logic at many stages in the lifetime of a packet, and taking advantages of performance improvements applied to the netfilter architecture. With that said, there is no consistent netfilter API between kernel versions, making it inconvenient to programmatically deal with netfilter. Dynamically modifying routing tables and rules is sufficient for our needs in Shill.

On the kernel side, a routing table is represented as a fib_table, while a policy routing rule is represented as a fib_rule.

Note the use of “FIB” rather than “routing table” in kernel code. A FIB, or Forwarding Information Base, refers specifically to the set of information used to forward a packet (i.e. to send it to another node), which does indeed correspond to our original definition of a routing table. This is in contrast to a RIB, or Routing Information Base, which refers to the set of information a node has about the routes around itself. The distinction, while seemingly unimportant for normal end nodes, is significant for routers or other nodes that use routing protocols to dynamically determine route information. Each routing protocol used by the node (e.g. OSPF, BGP, RIP, etc) maintains its own view of the available routes with a protocol-specific data structure. The information in these data structures are then selected and used to update the RIB, which then serves as the central, protocol-independent representation of routes. Finally, the FIB can be updated to reflect the information in the RIB. Since both RIB and FIB can be called “routing table” in various contexts, the kernel's use of “FIB” helps eliminate any potential ambiguity.

Design

The RoutingTable class is a singleton whose two primary responsibilities are:

  • to send client requests to add or remove routes/rules to the kernel through the rtnetlink interface
  • to maintain an internal representation of routes and policy rules organized on a per-interface basis for use both in internal routing logic and in servicing client requests for information about available routes/rules

When the RoutingTable is Start()ed, it will request all of the routes and rules on the system. After that point, it will keep track of newly-added routes/rules by listening to the RTNL interface (for routes/rules added directly by the kernel) and by updating the internal representation whenever a client request successfully adds or removes a route or rule.

Each Device instance has at most one Connection. When a Device is connecting, an IPConfig instance representing configuration information such as local address, gateway address, DNS servers, etc will be populated and passed to the Connection instance. The relevant information from that IPConfig, along with Service priority information provided by the Manager class, is then used to set up routes and rules appropriately.