Wednesday, December 16, 2015

OpenWRT: one device, multiple routers.

Overview.

Commonly companies has many departments or sections that needs isolated networks, differents types of Internet access and internal routing protocols.
In this way, sometimes each department do its own war whitout a common solution that integrates all company services. The result of this lack of planification is a lot of under-exploited resources: duplicated switches, routers and a lot of wireless access points for each department.
This is, of course, the worst way to build and maintain an company network. A better way is to use the same devices for all services and isolate services in such devices. Isolate a LAN is as easy as to use VLAN in switches, but… What about isolate route tables, NAT or routing protocols? This post will try to give you a resource to do it this using OpenWRT on a single device.

Why OpenWRT.

There are thousands of routers with NAT and routing protocols. But, how many of them can isolate L3 or L4 IP services? not them all, anyway.
Cisco, Juniper, Fortigate, Mikrotik and most of other can do it, but generally they are expensive or don’t collects all requirements. OpenWRT can be a good alternative. Some of its advantages are:
  • built-in package manager that allows you to install packages from a software repository
  • In two years, you may need a new functionality that you have not thought about today. In other devices you must change the software version or buy a new device (some functionalities may be incompatibles).
    OpenWRT repository has thousands of available software packages you can install, try, and discard.
  • Hard development.
  • OpenWRT community is high active. Its software is constantly developing and updating packages.
  • Embedded.
  • OpenWRT needs less than 50Mb to be installed and once it’s running only the access to hard drive for writing configuration changes. It can be installed on a Compact Flash, a SD card or any plugable device such a USB. It doesn't need hard drive maintenance.
  • Customizable (scripting capable).
  • Like all other Linux based systems, almost its processes can be managed by using scripts. You can schedule or automate any job.
  • Runnable on any x86 hardware.

Scenario.

Like I had explain in the overview our scenario will be an enterprise with three departments: Red, Blue and Green. Each department have its own network with different offices in the same building, and each office has a different subnet. Each department has its own Internet provider and all of them use routing protocols for manage its subnets.

Software and hardware.

Obviously, we will use OpenWRT to do this, otherwise the title of this post would be a big mistake. There are more than one package we can use to solve each need, I had choose this ones:

For isolating departments lans: Vlan.

For isolating departments networks: namespaces. All process running within a namespace have their own work environment and are isolated and invisible for the others. Namespaces can contain interfaces. When an interface is linked to a namespace it’s invisible for other namespaces.

For routing protocols: BIRD daemon. BIRD is a powerful routing daemon. I had used Quagga for many years, but in the last few years I had needed to migrate to BIRD because it can do more things in an easier way. Run several instances of Quagga is a bit more difficult than running several instances of BIRD. All you need to run a second instance of BIRD is a second configure file and a second PID file.

OpenWRT can run in a wide range of commercial routers and in lot of X86 based hardware (PC, PCengines, Raspberry Pi, etc) as well. I will use a VirtualBox virtual machine.


Configuration.

It isn't necessary to explain how to download, compile and install OpenWRT, because it is widely explained in its web page and in many other blogs. I want to bore you with configurations and scripts, but not with installations. The only thing you must know is to enable namespaces in the kernel before compile:

CONFIG_KERNEL_NET_NS=yes

Namespaces, vlans, IP address.
Let's start working!
First of all we want to create vlan. If you had managed a Linux system before, you probably know how to do it. We will assign vlan 100 for Red department, vlan 150 for Blue department and vlan 200 for Green department:

vconfig add eth1 100
vconfig add eth1 150
vconfig add eth1 200


Now we want to create the namespaces.

ip netns add red
ip netns add blue
ip netns add green

Link the vlan to its namespace:

ip link set eth1.100 netns red
ip link set eth1.150 netns blue
ip link set eth1.200 netns green

For viewing a list of namespaces running in the system simply run:

ip netns

To execute a command inside the namespace:

ip netns exec red ip addr


And configure IP address for each interface:

ip netns exec red ifconfig eth1.100 192.168.100.1 netmask 255.255.255.0
ip netns exec blue ifconfig eth1.150 192.168.150.1 netmask 255.255.255.0
ip netns exec green ifconfig eth1.200 192.168.200.1 netmask 255.255.255.0

You can try to ping from a namespace to others, but you will see you cannot:

Init script.
To make our life easier, we can use OpenWRT UCI configuration format and a script that loads it to make all configurations.

The configuration file can be something like this:

config namespace 'red_namespace'
        option name 'red’
        option vlan '100'
        option ip '192.168.100.1/24’

config namespace 'blue_namespace'
        option name 'blue’
        option vlan '150'
        option ip '192.168.150.1/24’

config namespace 'green_namespace'
        option name 'green’
        option vlan '200'
        option ip '192.168.100.1/24’

And the script:

config_namespaces(){
        config_get name "$1" name
       config_get ip "$1" ip
        config_get vlan "$1" vlan

        logger "loaded configurtion for namespace "$name

        if [ ! -f /var/run/netns/$name ]; then
                ip netns add $name
                logger "adding namespace "$name
        else
                logger "namespace "$name" alredy exists"
        fi


        logger "new vlan eth0."$vlan."
        vconfig add eth0 $vlan

        logger "link vlan eth0."$vlan " to namespace "$name
        ip link set eth0.$vlan netns $name

       logger "setting IP to vlan eth0."$vlan
        ip netns exec $name ip addr add $ip dev eth0.$vlan
        ip netns exec $name ifconfig eth0.$vlan up


}

start() {
 
        config_load namespaces
        config_foreach config_namespaces namespace
}

Obviously, we must assign execution permissions and enable the script to run at boot time:

chmod ug+x
/etc/init.d/namespaces enable

BIRD4 daemon.

BIRD4 daemon is in OpenWRT repositories, so install it is as simple as use opkg package manager:


opkg install bird4 birdc4

Default install will create a script in /etc/init.d for run the daemon at boot time. If we don't want to use it we must disable

/etc/init.d/bird4 disable

if we want to run three instances of BIRD4 daemon, one per namespace. We will place de configuration files in /etc/bird4.d/namespace.bird4.conf and PID file in /var/run/namespace.bird4.pid. Also BIRD4 client (bird4c) needs a socket file for communicate with the daemon, so we will place this file in /var/run/namespace.bird4.ctl

ip netns exec red bird4 -c /etc/bird4.d/red.bird4.conf -P /var/run/red.bird4.pid -s /var/run/red.bird4.ctl

We can update the init script to run BIRD4 if the configuration file has the line “option bird ‘yes’” adding these lines to function config_namespaces():

        config_get_bool bird "$1" bird


        if [ $bird ]; then
        logger “Running BIRD4 in namespace “$name
                ip netns exec $name bird4 -c /etc/bird4.d/$name.bird4.conf -P /var/run/$name.bird4.pid -s /var/run/$name.bird4.ctl
        fi

if you want to connect bird4 client to a daemon, you must use the CTL socket placed in /var/run. For example:

birdc4 -s /var/run/green.bird4.ctl

IPTables.

Now probably you want to add an external interface, and you may want to use NAT and firewall between external and internal networks. We can use the same method: add a Vlan to external interface, configure external IP and run a script that configure IPTables rules.

As it is the same procedure, I will summarize the steps and show the full script code in the next section

Final config file and script.

The final config file can be someting like this:

config namespace 'red_namespace'
        option name 'red'
        option internalVlan '100'
        option internalIP '192.168.100.1/24'
        option bird 'true'
        option externalVlan '1100'
        option externalIP '10.0.0.2/30'
        option defaultGW '10.0.0.1'
        option iptables 'true'


config namespace 'blue_namespace'
        option name 'blue'
        option internalVlan '150'
        option internalIP '192.168.150.1/24'
        option bird 'true'
        option externalVlan '1150'
        option externalIP '10.20.0.2/30'
        option defaultGW '10.20.0.1'
        option iptables 'true'


config namespace 'green_namespace'
        option name 'green'
        option internalVlan '200'
        option internalIP '192.168.100.1/24'
        option bird 'true'
        option externalVlan '1200'
        option externalIP '10.50.0.2/30'
        option defaultGW '10.50.0.1'
        option iptables 'true'


And the final script:

#!/bin/sh /etc/rc.common


START=70
STOP=15


config_namespaces(){

        config_get name "$1" name
        config_get internalIP "$1" internalIP
        config_get internalVlan "$1" internalVlan
        config_get externalIP "$1" externalIP
        config_get externalVlan "$1" externalVlan
        config_get defaultGW "$1" defaultGW
        config_get_bool bird "$1" bird
        config_get_bool iptables "$1" iptables

        logger -t "Namespaces Configuration" "loaded configurtion for namespace "$name


        #
        # If namespace already exists do not add it.
        #


        if [ ! -f /var/run/netns/$name ]; then
                ip netns add $name
                logger -t "Namespaces Configuration" "adding namespace "$name
        else
                logger -t "Namespaces Configuration" "namespace "$name" alredy exists"
        fi

        #
        # Add Vlan for LAN access (internal Vlan).
        #

        logger -t "Namespaces Configuration" "new vlan eth0."$internalVlan
        vconfig add eth0 $internalVlan

        #
        # Link internal Vlan to namespace
        #

        logger -t "Namespaces Configuration" "link vlan eth0."$internalVlan " to namespace "$name
        ip link set eth0.$internalVlan netns $name

        #
        # Add IP address to internal Vlan. After this, up it.
        #

        logger -t "Namespaces Configuration" "setting IP to vlan eth0."$internalVlan
        ip netns exec $name ip addr add $internalIP dev eth0.$internalVlan
        ip netns exec $name ifconfig eth0.$internalVlan up

        #
        # If option "bird" is enabled in configuration file, run BIRD4.
        #

        if [ $bird ]; then
                logger -t "Namespaces Configuration" "Running BIRD4 in namespace "$name
                ip netns exec $name bird4 -c /etc/bird4.d/$name.bird4.conf -P /var/run/$name.bird4.pid -s /var/run/$name.bird4.ctl
        fi

        #
        # If externalVlan is enabled in configuration file, add WAN vlan (external access).
        # Then link it to namespace and configure its IP address.
        # Then add a default route.
        #

        if [ externalVlan ]; then
                logger -t "Namespaces Configuration" "new vlan eth1."$externalVlan
                vconfig add eth1 $externalVlan

                logger -t "Namespaces Configuration" "link vlan eth1."$externalVlan " to namespace "$name
                ip link set eth1.$externalVlan netns $name

                logger -t "Namespaces Configuration" "setting IP to vlan eth1."$externalVlan
                ip netns exec $name ip addr add $externalIP dev eth1.$externalVlan
                ip netns exec $name ifconfig eth1.$externalVlan up

                ip netns exec $name ip route add default via $defaultGW
        fi

        #
        # If iptables is enabled in configuration file, run a script that has iptables rules.
        #

        if [ $iptables ]; then
                logger -t "Namespaces Configuration" "Running IPTables rules in namespace "$name
                ip netns exec $name /etc/iptables.d/$name.iptables.sh
        fi

}

start() {

        config_load namespaces
        config_foreach config_namespaces namespace
}

I hope you fun with this post as I had fun.
SEE US IN NEXT POST!

No comments:

Post a Comment