Skip to content

By Administrator in All, Linux administration, Routing protocols

This article illustrates how a scenario, similar to the one from our previous post Simulating VRF with OSPF instances on Cisco, could be implemented using Linux-based routers.

Scenario details and network topology

There are two companies A and B with offices in different towns. The offices are connected to the same service provider (SP). The goal is to establish layer 3 connectivity between each company’s offices in different towns, while maintaining separation of routing information and data traffic among different customers. A standard layer 3 VPN task, however, we try to accomplish it using neither MPLS nor VRF.

Network topology diagram

Network topology diagram

The imaginary physical topology is shown above and for the sake of this experiment virtualization has been used. The environment is similar to the one we used in our LXC – virtualization with Linux containers article.

router1 and router2 will be end customers A and B, while ubuntu and router3 will be acting as Service Provider (SP) devices.

We will be using the BIRD routing daemon for Linux to setup the scenario. By default, the configuration file can be found at /etc/bird.conf. Note that after each change to that file you need to type configure from the BIRD command line client for the changes to take effect. The client is invoked by typing birdc and communicates with the routing daemon via UNIX sockets.

Configuration of router1

Configure router id equal to the loopback interface address.

router id 10.0.0.1;

We also need to add the device protocol, which prepares a list of all available network devices. It also keeps scanning for configuration changes.

protocol device {
        scan time 2;           
}

In order to export the routes from BIRD to the kernel routing table we need the kernel protocol. It is the Linux kernel, which does the actual forwarding. The BIRD daemon works only in the control plane (learning and advertising routes). Worth mentioning is how to write import and export route filters. First, we need to determine the direction of transferring routes. Direction is determined from the perspective of the routing table. When exporting, the routes are taken from the routing table and transferred into the protocol. The Cisco equivalent of “exporting” is the “redistribute” command. “Importing” is moving the routes in the opposite direction – from the routing process to the routing table. Cisco routers, by default, install all routes in the routing table, unless filtered by a “distribute-list” or some other means.

Now, we take all routes from the BIRD routing table and import them into the kernel protocol, e.g. kernel routing table.

protocol kernel {
        export all;
}

Configure an OSPF process named CustEA in area 1 and put all available interfaces in that area. Explicitly configure that all received routes should be accepted and all known routes should be advertised by the routing process.

protocol ospf CustEA {
        rfc1583compat yes;
        tick 2;
        export all;
        import all;
        area 1 {
                interface "eth*", "lo";
        };
}

And this ends customer A configuration. The same is done for customer B (router2). However, the router id is changed to 10.0.0.2 and the OSPF area is changed to 2.

Provider router configuration

We start the configuration of the ubuntu SP router by assigning router id of 10.0.0.3, configuring device and kernel protocols. Next, we configure one OSPF process for customer A, named CustA.

protocol ospf CustA {
        rfc1583compat yes;
        tick 2;
        export all;
        import all;
        area 1 {
                interface "br1", "lo";
        };
                                                             
}

The process runs only on interface br1, which is facing router1 and is assigned to area 1. The loopback interface is also assigned to area 1 (optional).

The same steps are repeated for customer B, but the OSPF process is named CustB, the interface is br2 and the area is 2.

protocol ospf CustB {
        rfc1583compat yes;
        tick 2;
        export all;
        import all;
        area 2 {
                interface "br2";
        };
                                                             
}

Now, we can verify OSPF neighbor adjacency using birdc on the ubuntu router:

bird> show ospf CustA
CustA:
RFC1583 compatibility: enable
RT scheduler tick: 2
Number of areas: 1
Number of LSAs in DB:   3
        Area: 0.0.0.1 (1)
                Stub:   No
                NSSA:   No
                Transit:        No
                Number of interfaces:   2
                Number of neighbors:    1
                Number of adjacent neighbors:   1

bird> show ospf CustB
CustB:
RFC1583 compatibility: enable
RT scheduler tick: 2
Number of areas: 1
Number of LSAs in DB:   10
        Area: 0.0.0.2 (2)
                Stub:   No
                NSSA:   No
                Transit:        No
                Number of interfaces:   1
                Number of neighbors:    1
                Number of adjacent neighbors:   1
bird>

Next, we configure the Provider OSPF process, which will run in area 0 on interface br0:

protocol ospf Provider {
        rfc1583compat yes;
        tick 2;
        export all:
        import all;

        area 0 {
                interface "br0";
        };
}

Configuration of router3

Router3 will be adjacent with router ubuntu over its eth0 interface in area 0. Eth1 and lo will be in area 3. This area will simulate the other office of customer A. In the end, router1 will have connectivity only to networks in area 3.

router id 10.0.0.4;

protocol device {
        scan time 2;      
}

protocol kernel {
        export all;
}

protocol ospf Provider {
        rfc1583compat yes;
        tick 2;

        export all;
        import all;
        area 0 {
                interface "eth0";
        };

        area 3 {
                interface "eth1","lo";
        };

}

We verify the adjacency:

bird> sh ospf Provider
Provider:
RFC1583 compatibility: enable
RT scheduler tick: 2
Number of areas: 1
Number of LSAs in DB:   12
        Area: 0.0.0.0 (0) [BACKBONE]
                Stub:   No
                NSSA:   No
                Transit:        No
                Number of interfaces:   1
                Number of neighbors:    1
                Number of adjacent neighbors:   1
bird>

Now, all routers should have full connectivity and receive all routes. If we check router2‘s routing table, for instance, the output should be similar to the following:

bird> sh route
10.0.0.1/32     via 10.0.2.1 on eth1 [CustEB 11:53] * E2(150/10/10000)[10.0.0.3]
10.0.0.3/32     via 10.0.2.1 on eth1 [CustEB 11:53] * E2(150/10/10000)[10.0.0.3]
10.0.2.0/24     dev eth1 [CustEB 08:41] * I (150/10)[10.0.0.3]
10.0.1.0/24     via 10.0.2.1 on eth1 [CustEB 11:53] * E2(150/10/10000)[10.0.0.3]
10.0.0.2/32     dev lo [CustEB 08:41] * I (150/0) [10.0.0.1]
10.0.0.4/32     via 10.0.2.1 on eth1 [CustEB 11:53] * E2(150/10/10000)[10.0.0.3]
192.168.1.0/24  via 10.0.2.1 on eth1 [CustEB 11:53] * E2(150/10/10000)[10.0.0.3]
192.168.2.0/24  dev eth0 [CustEB 08:52] * I (150/10) [10.0.0.1]
192.168.3.0/24  via 10.0.2.1 on eth1 [CustEB 11:53] * E2(150/10/10000)[10.0.0.3]
10.240.110.0/25 via 10.0.2.1 on eth1 [CustEB 11:53] * E2(150/10/10000)[10.0.0.3]
bird>

And the kernel routing table:

router2:~# route -n
Kernel IP routing table
Destination     Gateway    Genmask         Flags Metric Ref Use Iface
10.0.0.4        10.0.2.1   255.255.255.255 UGH   0      0     0 eth1
10.0.0.3        10.0.2.1   255.255.255.255 UGH   0      0     0 eth1
10.0.0.1        10.0.2.1   255.255.255.255 UGH   0      0     0 eth1
10.240.110.0    10.0.2.1   255.255.255.128 UG    0      0     0 eth1
192.168.3.0     10.0.2.1   255.255.255.0   UG    0      0     0 eth1
10.0.1.0        10.0.2.1   255.255.255.0   UG    0      0     0 eth1
192.168.2.0     0.0.0.0    255.255.255.0   U     0      0     0 eth0
192.168.1.0     10.0.2.1   255.255.255.0   UG    0      0     0 eth1
10.0.2.0        0.0.0.0    255.255.255.0   U     0      0     0 eth1
router2:~#

Customer route filtering and route tagging

Next step is to filter routes between customer A (router1) and customer B (router2) connected to router ubuntu. As in the previous scenario we’ll use tagging to distinguish different customer routes. Customer A routes will be tagged with 10 and customer B with 20. For the CustA ospf process we change the line “export all;” to:

export filter {
                if ospf_tag=10 then accept;
};

Because no routes are tagged, router1 will receive no OSPF routes:

bird> sh route
10.0.0.1/32       dev lo [CustEA 08:50] * I (150/0) [10.0.0.2]
10.0.1.0/24       dev eth1 [CustEA 08:51] * I (150/10) [10.0.0.3]
192.168.1.0/24    dev eth0 [CustEA 08:51] * I (150/10) [10.0.0.2]
bird>

Router1 knows about connected networks only.

Next, we tag customer routes. Customer routes are tagged when imported into the Provider OSPF process. Because routes are moved between different OSPF processes, they are imported as external, type 2 (E2) routes. As mentioned, we tag customer A network 10.0.1.0/24 with a tag of 10. The 192.168.1.0/24 network is tagged with a tag of 11. This is due to the fact that if different customers have overlapping subnets they must be tagged with different labels. That labels will be imported into both customers’ routing processes.

Under the Provider OSPF process we change the line “export all;” to:

export filter {
          if net ~ [10.0.1.0/24] then ospf_tag=10;
          if net ~ [192.168.1.0/24] then ospf_tag=11;
          accept;
};

Let’s verify the routing updates received on router3 (SP):

bird> sh route for 192.168.1.0 all
192.168.1.0/24
via 10.240.110.27 on eth0 [Provider 12:26] * E2 (150/10/10000) [b] [10.0.0.3]
        Type: OSPF-E2 unicast univ
        OSPF.metric1: 10
        OSPF.metric2: 10000
        OSPF.tag: 0x0000000b
        OSPF.router_id: 10.0.0.3
bird>
bird> sh route for 10.0.1.0 all
10.0.1.0/24
via 10.240.110.27 on eth0 [Provider 11:51] * E2 (150/10/10000) [a] [10.0.0.3]
        Type: OSPF-E2 unicast univ
        OSPF.metric1: 10
        OSPF.metric2: 10000
        OSPF.tag: 0x0000000a
        OSPF.router_id: 10.0.0.3

The route to 10.0.1.0/24 has a tag of 10 (hex “a”) and the route to 192.168.1.0/24 has a tag of 11 (hex “b”).

Now, on router3, we want to accept only routes that have a tag of 10 or 11 and filter the rest. Again, mind the direction, which is “import” into the routing table, i.e. import filter. We change the line “import all;” to:

import filter {
         if source =RTS_OSPF then accept; #accept locally generated routes
         if ospf_tag=11 || ospf_tag=10 then accept;
};

Let’s verify router3‘s routing table:

bird> sh route
10.0.1.0/24      via 10.240.110.27
on eth0 [Provider 11:51] * E2(150/10/10000) [a] [10.0.0.3]

10.0.0.4/32      dev lo [Provider 11:48] * I (150/0) [10.0.0.4]

192.168.1.0/24   via 10.240.110.27
on eth0 [Provider 12:26] * E2(150/10/10000) [b] [10.0.0.3]

192.168.3.0/24   dev eth1 [Provider 11:48] * I (150/10) [10.0.0.4]

10.240.110.0/25  dev eth0 [Provider 11:48] * I (150/10) [10.0.0.3]
bird>

Only local and tagged routes are present. Even router1‘s loopback address 10.0.0.1 has disappeared (it is not tagged). It is possible to filter routes further, if needed. If for some reason you want to allow only routes with tag=10, then on router3, under the “kernel” protocol, you may change the line “export all;” to the following:

export filter {
             if ospf_tag=10 then accept;
};

This will filter the route to 192.168.1.0/24 (tag=11). To check the results:

router3:~# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
10.240.110.0    0.0.0.0         255.255.255.128 U     0      0        0 eth0
192.168.3.0     0.0.0.0         255.255.255.0   U     0      0        0 eth1
10.0.1.0        10.240.110.27   255.255.255.0   UG    0      0        0 eth0
0.0.0.0         10.240.110.126  0.0.0.0         UG    0      0        0 eth0
router3:~#

Indeed, the route is not present in the kernel routing table.

Full router configs so far

Router ubuntu configuration:

router id 10.0.0.3;

protocol kernel {
        export all;
}

protocol device {
        scan time 2;           
}

protocol ospf Provider {
        rfc1583compat yes;
        tick 2;
        export filter {
          if net ~ [10.0.1.0/24] then ospf_tag=10;
          if net ~ [192.168.1.0/24] then ospf_tag=11;
                accept;
        };

        import all;

        area 0 {
                interface "br0";
        };
}

protocol ospf CustA {
        rfc1583compat yes;
        tick 2;
        export filter {
                if ospf_tag=10 then accept;

        };

        import all;

        area 1 {
                interface "br1" , "lo";
        };
}

protocol ospf CustB {
        rfc1583compat yes;
        tick 2;
        export all;
        import all;
        area 2 {
                interface "br2";
        };
                                                           
}

Router router1 configuration:

router id 10.0.0.1;

protocol device {
        scan time 2;   

protocol kernel {
        export all;
}

protocol ospf CustEA {
        rfc1583compat yes;
        tick 2;
        export all;
        import all;
        area 1 {
                interface "eth*", "lo";
        };
}

Router router2 configuration:

router id 10.0.0.2;


protocol device {
        scan time 2;         
}

protocol kernel {
        export all;
}

protocol ospf CustEB {
        rfc1583compat yes;
        tick 2;
        export all;
        import all;
        area 2 {
                interface "eth*", "lo";
        };
}

Router router3 configuration:

router id 10.0.0.4;

protocol device {
        scan time 2;          
}

protocol kernel {
        export filter {
                if ospf_tag=10 then accept;
        };
}

protocol ospf Provider {
        rfc1583compat yes;
        tick 2;

        export all;
        import filter {
                if source =RTS_OSPF then accept; #allow local routes
                if ospf_tag=11 || ospf_tag=10 then accept;
        };

        area 0 {
                interface "eth0";
        };

        area 3 {
                interface "eth1","lo";
        };

}

The problem of overlapping subnets and the NAT solution

Here comes the old problem of overlapping customer subnets. We will use the old solution and perform NAT translation on the customer routers. All customer networks will be translated to the loopback addresses. The loopback addresses must be unique and known throughout the SP network. We begin with SP configuration on router ubuntu. For customers to receive loopback routes, they must be tagged and imported.

Under the Provider OSPF process, the export filter is changed from “if net ~ [10.0.1.0/24] then ospf_tag=10;” to:

if net ~ [10.0.1.0/24] || net ~ [10.0.0.0/24+] then ospf_tag=10;

Check the results on router3:

bird> show route
10.0.0.2/32
via 10.240.110.27 on eth0 [Provider 07:41] * E2 (150/10/10000) [a] [10.0.0.3]
10.0.0.3/32
via 10.240.110.27 on eth0 [Provider 07:41] * E2 (150/10/10000) [a] [10.0.0.3]
10.0.1.0/24
via 10.240.110.27 on eth0 [Provider 07:27] ! E2 (150/10/10000) [a] [10.0.0.3]
10.0.0.1/32
via 10.240.110.27 on eth0 [Provider 07:41] * E2 (150/10/10000) [a] [10.0.0.3]
10.0.0.4/32        dev lo [Provider 07:14] * I (150/0) [10.0.0.4]
192.168.1.0/24
via 10.240.110.27 on eth0 [Provider 07:40] * E2 (150/10/10000) [b] [10.0.0.3]
192.168.3.0/24     dev eth1 [Provider 07:14] * I (150/10) [10.0.0.4]
10.240.110.0/25    dev eth0 [Provider 07:14] * I (150/10) [10.0.0.4]
bird>

Since we are filtering all routes from CustA OSPF process (the ubuntu router) we have to allow routes coming from router3. We cannot use tagging on router3 as we did on ubuntu. OSPF inter-area routes have no tag field. That’s why, on the ubuntu router, under CustA process, we will directly allow or deny customer subnets. The export filter line is changed to the following:

export filter {
      if net ~ [10.0.0.0/24+] || net ~ [192.168.3.0/24] then accept;
};

This will allow loopback routes and router3‘s subnet in area 3. Now, we should be able to ping from router1 to router3‘s directly connected networks:

router1:~# ping 192.168.3.1
PING 192.168.3.1 (192.168.3.1) 56(84) bytes of data.
64 bytes from 192.168.3.1: icmp_seq=1 ttl=63 time=6.70 ms
64 bytes from 192.168.3.1: icmp_seq=2 ttl=63 time=0.218 ms
^C
--- 192.168.3.1 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1014ms
rtt min/avg/max/mdev = 0.218/3.462/6.706/3.244 ms
router1:~# ping 10.0.0.4
PING 10.0.0.4 (10.0.0.4) 56(84) bytes of data.
64 bytes from 10.0.0.4: icmp_seq=1 ttl=63 time=0.288 ms
64 bytes from 10.0.0.4: icmp_seq=2 ttl=63 time=0.191 ms
^C
--- 10.0.0.4 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 999ms
rtt min/avg/max/mdev = 0.191/0.239/0.288/0.050 ms
router1:~#

And the same check from router3:

router3:~# ping 192.168.1.1 -I 10.0.0.4
PING 192.168.1.1 (192.168.1.1) from 10.0.0.4 : 56(84) bytes of data.
64 bytes from 192.168.1.1: icmp_seq=1 ttl=63 time=0.350 ms
64 bytes from 192.168.1.1: icmp_seq=2 ttl=63 time=0.173 ms
^C
--- 192.168.1.1 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 999ms
rtt min/avg/max/mdev = 0.173/0.261/0.350/0.089 ms
router3:~# ping 192.168.1.1 -I 192.168.3.1
PING 192.168.1.1 (192.168.1.1) from 192.168.3.1 : 56(84) bytes of data.
64 bytes from 192.168.1.1: icmp_seq=1 ttl=63 time=0.374 ms
64 bytes from 192.168.1.1: icmp_seq=2 ttl=63 time=0.164 ms
^C
--- 192.168.1.1 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 999ms
rtt min/avg/max/mdev = 0.164/0.269/0.374/0.105 ms
router3:~#

We cannot use the default source interface eth0, because router1 has no route to the 10.240.110.0/25 network.

For the sake of this experiment, we will now make the customer networks overlapping. We change the eth0 IP address on router2 to 192.168.1.10/24.

When we check the BIRD routing table on router ubuntu the result is:

bird> sh route for 192.168.1.0
192.168.1.0/24    via 10.0.1.2 on br1 [CustA 23:30] * I (150/20) [10.0.0.2]
                  via 10.0.2.2 on br2 [CustB 00:35] I (150/20) [10.0.0.1]
bird>

The SP has two routes for the network but only one is used and exported to the kernel (marked with “*”). So, connectivity to the CustA or CustB network is lost, depending on which route has arrived first on ubuntu (in the example there is no connectivity to 192.168.1.10 on router2). To resolve the problem we use NAT to separate the control plane traffic from the data plane traffic.

The 192.168.0.0/16 subnet will be translated to the loopback address of 10.0.0.4. As connectivity is already establish for existing networks, we’ll add another subnet, not allowed into the CustA OSPF process. It will be used to verify that NAT is working.

Configure an alias for the eth1 interface:

router3:~# ip addr add 192.168.100.1/24 brd + dev eth1 label eth1:100

Try to ping router1:

router3:~# ping 192.168.1.1 -I 192.168.100.1
PING 192.168.1.1 (192.168.1.1) from 192.168.100.1 : 56(84) bytes of data.
^C
--- 192.168.1.1 ping statistics ---
3 packets transmitted, 0 received, 100% packet loss, time 2017ms

As expected, the ping is unsuccessful. Let’s now add the following NAT translation rule:

router3:~# iptables -t nat -A POSTROUTING -o eth0 -s 192.168.0.0/16 \
> -j SNAT --to-source 10.0.0.4

This time, due to the NAT rule, the ping is successful. Optionally, we may add a similar NAT translation rule on router1.

Tags: , , , , , , , , ,

Comment Feed

No Responses (yet)



Some HTML is OK

or, reply to this post via trackback.