Linux multi-destination router made with Docker – Overview

This article is about setting up a Linux router based on Docker. Unlike many other tutorials on the web the goal is to host and run every component of the router in its own container. Docker’s isolation feature should make it a good fit to for example run a DHCP server separately.

Another interesting feature is that containers can be connected with different types of networks. This allows effective routing between the single components of the router.  That makes it well suited for a router which can route packets to different networks based on their source (source-based routing). Being an expat in Australia, this is interesting to me in order to access certain internet pages as if I was at home to circumvent geo-blocked content.

An usual approach to work around this kind of restriction is to setup a virtual private network software on your devices. However, this comes with a few disadvantages. You need to setup and maintain the vpn software on every device. It does not work well with screen casting solutions like Google’s Chrome Cast or Amazon’s Fire Stick because the device you start the cast from and the displaying device need to be connected to the same vpn at the same time. Also, not every device supports connecting to a vpn after all.

After all, I decided to create my on solution for this problem and since I had a spare Raspberry PI 3 laying around I wanted to use it for the project.

Requirements (directly jumping into the solution space) for the first version of the router:

Functional requirements
  1. The router provides a WLAN Access Point with the Raspberry PI 3 hardware.
  2. The router runs a DHCP server handing out IPs in the range from 10.1.1.100 to 10.1.1.200
  3. For every device on the LAN the user can configure whether its traffic is routed through a vpn or directly to the local internet provider
  4. The user can configure route targets for each device on an intranet web page
  5. The router runs a DNS cache (dnsmasq) specifically for each target network
  6. DNS leakage must not occur. Traffic routed through the vpn shall only run DNS queries through the vpn tunnel and the same for the local internet access
  7. When the VPN is down, traffic of devices configured to be routed through the vpn must not “leak” through the main WAN access but be rejected by proper ICMP packets
  8. When the routing target for a device is changed all connections currently established from that device must be disconnected
  9. This is an experiment so the router is part of an already existing LAN and existing router (which leads to double network address translation)
Qualities in regards to security
  1. The destination configuration page must be only accessible from the LAN
  2. There are no open ports on the WAN interface(s) (for now – usually SSH access from the internet is critical)
Overview

The following drawing shows an overview of the first version. Blue represents containers and green indicates networks – of type bridged or macvlan.

The responsibilities of the individual building blocks are:

  • DHCPD: Runs a DHCP daemon which hands out IP Addresses on the 10.1.1.0/24 network
  • HostAPD: Runs the HostAP daemon which opens a Wifi access point using the Raspberry Pi 3 hardware
  • LAN: Bridges access to the LAN (via USB dongle)
  • Router: Routes traffic between the LAN subnet and the local WAN access or OpenVPN WAN access networks. It offers gateway functionality on 10.1.1.10 which includes forwarding DNS queries coming in on port 53/UDP
  • Local WAN Access: Provides masqueraded access to the local WAN. Runs DNSMasq which answers and caches DNS queries. Has two IPs: One in the 10.2.1.0 subnet and one as a DHCP client from the primary home router.
  • OpenVPN WAN Access: Provides masqueraded access to an OpenVPN connection. Runs DNSMasq which answers and caches DNS queries. Also has two IPs.
  • Primary Home Router: Access to the primary LAN which must provide a correctly configured DHCP server.

In the next part I will discuss the network setup for the router. The project can be found here: https://github.com/ThomasZeman/rpi3-router

Flattr this!

Give your async delegate type the right name

The BCL has well-known delegate types representing synchronous invocation signatures: Action<> and Func<>. But what about asynchronous methods? An asynchronous method with no return value translates to Func<Task>. That looks kind of strange because usually Func means a function with a return value but actually all we are getting is a Promise that the operation will complete without a result. A task amplifies (or let’s say wraps) a result (or void) but instead of having an asynchronous construct which handles the result directly, Task and result are treated as one unit as part of a synchronous call.

The following table shows the synchronous types and how you have to specialize them to get asynchronous versions:

WhatSynchronousAsynchronous w. BCL
no argument and no resultActionFunc<Task>
one argument and no resultAction<T>Func<T, Task>
no argument but resultFunc<TResult>Func<Task<TResult>>
one argument and resultFunc<T,TResult>Func<T,Task<TResult>>

This leads to code like this:

// Sample asynchronous method which takes
// no parameter and returns no result (Func<Task>)
public async Task DoAsync()
{
await Task.Delay(42); // pretend we do something
}

// Method calling asynchronous lambda
public async Task CallAsync(Func<Task> func)
{
await func();
}

public async Task Orchestrate()
{
Func<Task> func = () => DoAsync();
await CallAsync(func);
}

As said at the beginning, I think especially for people who are not so firm in asynchronous programming it becomes confusing that Func<T> actually represents a function with no parameters and no return value. Even for advanced programmers this is something they constantly have to have in their mind when reading code. Therefore, I believe generic asynchronous delegate types are missing in the BCL and should be part of every project with asynchronous code. The following definitions tackle this problem:

Proposed asynchronous typesDefinition of asynchronous types
AsyncActiondelegate Task AsyncAction();
AsyncAction<T>delegate Task AsyncAction<in T>(T arg);
AsyncFunc<TResult>delegate Task<TResult> AsyncFunc<out TResult>();
AsyncFunc<T,TResult>delegate Task<TResult> AsyncFunc<in T, out TResult>(T arg);
// Sample asynchronous method which takes no
// parameter and returns no result (AsyncAction)
public async Task DoAsync()
{
await Task.Delay(42); // pretend we do something
}

// Method calling asynchronous lambda
public async Task CallAsync(AsyncAction action)
{
await action();
}

public async Task Orchestrate()
{
AsyncAction action = () => DoAsync();
await CallAsync(action);
}

Beside being easier to read this also supports better recognition of what is a command and what is a query in your code (referring to CQRS in Wiki or Fowler )

Flattr this!