Unified VPN SDK for Routers

Introduction

The Router VPN SDK allows developers to manage Virtual Private Network (VPN) functionalities directly from their applications. It simplifies the process of creating, configuring and monitoring VPN connections on routers. This SDK is ideal for developers building network security, privacy, or enterprise network management solutions.

Quick Start

System requirements

Linux based OS with kernel version greater than 4.14, OpenWRT 19.07 and higher versions. The following packages should be installed: libc, kmod-tun (TUN/TAP device driver), ca-bundle, and libnetfilter-conntrack.

SDK Installation

Place libafwrt.so, libwireguard_tools.so, and afwrt.h in the required location.

Minimal working configuration

{
    "tun_ifname" : "write_your_tun_interface_name",
    "wan_ifname" : "write_your_wan_interface_name"
}

SDK Architecture

Figure 1: Architecture of the Router SDK represents a high-level architecture of a Router SDK system, highlighting the interaction between various components in a test or partner application and the underlying operating system (OS).

Key Components of the image:

  • Test / Partner Application: This serves as the environment where the SDK operates and interacts with.

Router SDK:

  • Business Logic: The core logic layer responsible for handling specific business operations within the router.

  • VPN Protocol: This module manages the VPN functionality, allowing the system to handle secure traffic routing.

  • Platform-Specific Module: Adapts the SDK to the platform (e.g., router-specific functionality or configuration), ensuring compatibility with various hardware or operating systems.

  • C API: Exposes a C-language interface for communication between the application and the SDK. The test/partner application interacts with the SDK through this API.

  • Operating System (OS): The diagram shows that the Router SDK interacts with the OS via:

  • Linux Kernel: To manage low-level operations related to networking, devices, and other hardware interfaces.

  • Platform System API: For higher-level interactions, such as controlling system resources or accessing platform-specific features.

VPN features overview

Supported VPN Protocols, platforms, routers

Supported VPN Protocols

  • Hydra

  • WireGuard (version 1.0.20210914)

Supported platforms

Linux-based operating systems (e.g., OpenWRT, Ubuntu, etc.).

Key features

  • Multi-locations refers to the capability of a VPN service to provide multiple server locations across different geographic regions, allowing users to connect to the internet through any of these servers at the same time (see below).

  • Multi-protocols refers to the ability of a VPN service to support multiple tunneling or encryption protocols for establishing secure connections. These protocols define how data is encrypted, transmitted, and secured between a user’s device and the VPN server.

  • Dynamic configuration without SDK restart refers to the ability to modify or update a VPN client’s configuration settings without needing to restart the SDK or the underlying VPN client. This feature allows real-time changes to VPN parameters like server selection, security settings, network routing, or other VPN configurations without requiring a full reset of the VPN service.

  • Transparent tunneling refers to a networking technique where data is transmitted through a VPN tunnel without the end-user or applications being aware of the underlying tunneling process. This method ensures that the tunneling is transparent to the user device and applications, meaning they don't need to be reconfigured or have any knowledge that their data is being encapsulated and transmitted through a tunnel.

Key Components of the image:

  • Connected Devices. Several devices, such as a TV, Laptop, and Mobile Device, are connected to a router (referred to as the Router Gateway (GW)).

  • Router SDK. The router is running the Router SDK, which manages how the traffic from each device is routed. The SDK decides whether traffic should be routed through VPN tunnels or directly to the target server (bypassed).

  • Transparent Tunneling. The SDK handles transparent tunneling for connected devices. Devices do not need to configure VPN settings directly—traffic is routed automatically by the router.

  • Traffic Routing. The traffic from each device is treated differently. TV traffic is routed through a VPN Tunnel to the VPN Server in GB. Laptop traffic is bypassed, meaning it is sent directly to the target server without using a VPN. Mobile Device traffic is routed through a VPN Tunnel to the VPN Server in the US.

  • Target Server and Multilocation. All traffic eventually reaches the Target Server, either directly (bypass) or through VPN tunnels. Multiple VPN Tunnels are established for different locations, in this case, GB and US, to enable geographic flexibility in routing. This setup illustrates selective VPN routing based on network requirements, allowing specific devices to use different VPN locations or bypass the VPN entirely.

Example Usage

// NOTE: Functions with client_ prefix should be implemented by library user

#include <afwrt.h>
#include <signal.h>
#include <pthread.h>
#include <stdbool.h>

// global variables
bool afwrt_started = false;
char *access_token = NULL;

static void signal_cb(int signal)
{
	if (signal == SIGINT || signal == SIGTERM) {
		afwrt_stop();
		afwrt_started = false;
	}
}

static void event_cb(const char *event_str)
{
	// parse json event_str, handle events here

	// if event is connected we can start to configure
	const char *event = client_get_event_from_str(event_str);
	if (!strncmp(event, "route_connected", strlen(event)))
		afwrt_started = true;
}

/* thread_func to manage vpn */
static void *thread_func(void *args)
{
	size_t wait = 0U;
	const size_t max_wait = 10U;

	while (!afwrt_started && wait < max_wait) {
		++wait;
		sleep(1U);
	}

	while (afwrt_started) {
	// setup tunnels and rules here

	// Order:

	// get locations by afwrt_get_locations();

	// setup tunnel by afwrt_setup_tunnel();

	// protect by afwrt_protect_iface(),
	// afwrt_protect_ip_addr(), afwrt_protect_mac_addr()

	// unprotect by afwrt_unprotect_iface(),
	// afwrt_unprotect_ip_addr(), afwrt_unprotect_mac_addr()

	// teardown_tunnel by afwrt_teardown_tunnel()

	// afwrt_stop()
	}
}

int main()
{
	int err = -1;
	pthread_t thread;
	// get client config
	const char *cfg_str = client_get_cfg();
	// get client access token
	const char *access_token = client_get_access_token();

	err = afwrt_init(cfg_str, event_cb);
	if (err == -1) {
		// handle error
		return -1;
	}

	if (!access_token) {
		// get client auth method
		const char *auth_method = client_get_auth_method();
		// get client auth token
		const char *auth_token = client_get_auth_token();
		access_token = afwrt_register_device(auth_method, auth_token);
		if (!access_token) {
			// handle error
			afwrt_deinit()
			return -1;
		}
		// save access_token in your storage
	}

	err = afwrt_set_access_token(access_token);
	if (err == -1) {
		// get new access token
	}

	err = pthread_create(&thread, NULL, thread_func, NULL);
	if (err) {
		// handle error
		free(access_token);
		afwrt_deinit();
		return -1;
	}

	signal(SIGINT, signal_cb);
	signal(SIGTERM, signal_cb);

	// blocking call
	err = afwrt_start();
	if (err == -1) {
		// handle error
	}

	err = pthread_join(thread, NULL);
	if (err) {
		// handle error
	}

	err = afwrt_deinit();
	if (err == -1) {
		// handle error
	}

	free(access_token);
	return err;
 }

API Documentation

The library allows developers to manage Virtual Private Network(VPN) functionalities directly from their applications. It simplifies the process of creating, configuring and monitoring VPN connections. All methods are thread safe.

Callback function

afwrt_event_callback_t

Callback function to receive periodic SDK event.

Parameters:

  • event_str: Event string in JSON format, see "Callback events" section

Initialization and Termination

afwrt_init

Initializes the library. Needs to be called once before using the library.

Parameters:

  • params_json: Configuration in JSON format. See "All configuration parameters" section. Cannot be NULL.

  • event_callback: Callback function to receive SDK events. See "Callback events" section. Can be NULL.

Returns: 0 on success, -1 on error.


afwrt_deinit

Deinitializes the library. Needs to be called once after using the library.

Returns: 0 on success, -1 on error.


Main Loop

afwrt_start

Starts the library main loop. This is a blocking call. After calling this, you need to call other methods (setup and teardown tunnel, protect and unprotect IP/MAC/interface) in separate threads.

Returns: 0 on success, -1 on error.


afwrt_stop

Stops the library main loop. Should be called once to stop the VPN client.

Returns: Always returns 0.


IP Address Protection

afwrt_protect_ip_addr

Adds a rule to route traffic from the specified IP address via a specified VPN location. Can be called multiple times to change the location. This should be used when the library is initialized, started, and the tunnel for this location is set up. Doesn't work with wireguard, see "Wireguard traffic routing" section

Parameters:

  • ip_addr: IP address to protect.

  • location: VPN location to route traffic.

Returns: 0 on success, -1 on error.


afwrt_unprotect_ip_addr

Removes the rule to route traffic from the specified IP address via the VPN. Should be used for existing rules. Doesn't work with wireguard, see "Wireguard traffic routing" section

Parameters:

  • ip_addr: IP address to unprotect.

Returns: 0 on success, -1 on error.


MAC Address Protection

afwrt_protect_mac_addr

Adds a rule to route traffic from the specified MAC address via a specified VPN location. Can be called multiple times to change the location. This should be used when the library is initialized, started, and the tunnel for this location is set up. Doesn't work with wireguard, see "Wireguard traffic routing" section

Parameters:

  • mac_addr: MAC address to protect.

  • location: VPN location to route traffic.

Returns: 0 on success, -1 on error.


afwrt_unprotect_mac_addr

Removes the rule to route traffic from the specified MAC address via the VPN. Should be used for existing rules. Doesn't work with wireguard, see "Wireguard traffic routing" section

Parameters:

  • mac_addr: MAC address to unprotect.

Returns: 0 on success, -1 on error.


Interface Protection

afwrt_protect_iface

Adds a rule to route traffic from the specified interface via a specified VPN location. Can be called multiple times to change the location. This should be used when the library is initialized, started, and the tunnel for this location is set up. Doesn't work with wireguard, see "Wireguard traffic routing" section

Parameters:

  • iface: Interface to protect.

  • location: VPN location to route traffic.

Returns: 0 on success, -1 on error.


afwrt_unprotect_iface

Removes the rule to route traffic from the specified interface via the VPN. Should be used for existing rules. Doesn't work with wireguard, see "Wireguard traffic routing" section

Parameters:

  • iface: Interface to unprotect.

Returns: 0 on success, -1 on error.


Access Token Management

afwrt_register_device

(Deprecated) Authenticates a device using any supported by Pango auth provider. Saving the received token value is considered good practice. This function is deprecated, use afwrt_auth_device instead of afwrt_register_device.

Parameters:

  • auth_method: Supported auth provider or anonymous. Exact strings are provided by Pango for each project. See "All configuration parameters" section

  • auth_token: Auth token suitable for the specified auth method. See "All configuration parameters" section

Returns: Access token, which can be used in the afwrt_set_access_token() call.


afwrt_auth_device

Authenticates a device using any supported by Pango auth provider. Saving a received token value would be considered a good practice.

Parameters:

  • auth_method: Supported auth provider or anonymous. Exact strings are provided by Pango for each project. See "All configuration parameters" section

  • auth_token: Auth token suitable for the specified auth method. See "All configuration parameters" section

Returns: Access token, which can be used in the afwrt_set_access_token() call.


afwrt_set_access_token

Adds an access token value to the current configuration.

Parameters:

  • access_token: Platform API access token. The token may consist only of Latin letters, numbers, and ., _, -, ~. "URL encode" needs to apply for other characters.

Returns: 0 on success, -1 when an invalid access token is provided.


Version Management

afwrt_version

Retrieves the SDK version number in MAJOR.MINOR.PATCH format (e.g., 1.0.0). This can be used at any time.

Returns: A statically allocated version string. It does not need to be freed.


Tunnel Management

afwrt_setup_tunnel

Initiates a tunnel connection to a specific virtual location. Should be used when the library is initialized, started, and list of locations have been retrieved.

Hydra-specific behavior:

  1. If the VPN protocol is hydra-tcp, iface_name is ignored.

  2. If the location connection is interrupted, this function should be used to initiate re-connection.

  3. No actual connection is made until the first network packet is sent.

Wireguard-specific behavior:

  1. Connection is initiated immediately.

  2. route_failed or route_connected events are expected soon after calling this function.

Parameters:

  • location: Virtual location name, as provided by the afwrt_get_locations() call.

  • iface_name: System interface name.

Returns: 0 on success, -1 on error with errno set to:

  • EINVAL: Null location or interface for Wireguard, or afwrt_init() was not called.

  • ENOENT: Unknown location ID.

  • EBUSY: Interface already in use.

  • EEXIST: Tunnel to the specified location is already set.


afwrt_teardown_tunnel

Terminates the tunnel connection to a specific virtual location. Should be used for an existing tunnel.

Parameters:

  • location: Virtual location name, as provided to afwrt_setup_tunnel().

Returns: 0 on success, -1 on error with errno set to:

  • EINVAL: Null location or afwrt_init() was not called.

  • ENOENT: No known connection to the provided location.

  • EBUSY: At least one active rule for this location exists (Hydra only).


Location Management

afwrt_get_locations

Retrieves a JSON response containing VPN locations, private groups, DNS servers, features, and profiles. This should be used when the library is initialized and started. The caller is responsible for freeing the returned string. The JSON response looks like:

{
  "result": "OK",
  "locations": [
    {
      "name": "us-east",
      "description": "US-East",
      "status": "OK",
      "labels": {
        "country": "US",
        "subdivision": "NY",
        "city": "New York"
      }
    }
  ],
  "private_groups": [
    {
      "name": "string"
    }
  ],
  "dns_servers": [
    {
      "name": "string",
      "label": "string"
    }
  ],
  "features": [
    {
      "name": "string"
    }
  ],
  "profiles": [
    {
      "name": "string",
      "labels": {}
    }
  ]
}

Returns: JSON string with a list of locations, NULL on failure.

All configuration parameters

"tun_ifname"

Name of virtual TUN interface will be created by SDK.

Example:

{ "tun_ifname": "wrt0" }

"hydra_tun_fd"

Hydra-specific option. Tells SDK not to create interface but reuse existing one instead. Option value is file descriptor of interface to use.

Example:

{ "hydra_tun_fd": 42 }

"wan_ifname"

Name of WAN interface.

Example:

{ "wan_ifname": "eth1" }

"vl_default"

Default virtual location. By default, all traffic routed to the SDK TUN interface goes through this location. If not specified or empty, SDK will try to get an optimal location that can be configured on the backend side. Default: "" (empty).

Example:

{ "vl_default": "us" }

"backend"

Backend server address. Caller should not rely on default value and should specify it explicitly. The specify address will be provided by Pango.

"type"

VPN protocol to use. Possible values: "hydra-tcp", "wireguard". This parameter is deprecated, use "protocol" instead of "type". Default: "hydra-tcp"

"protocol"

VPN protocol to use. Possible values: "hydra-tcp", "wireguard". The "protocol" has higher priority than "type". If "protocol" is empty string than SDK uses "type" as "protocol". If "protocol" is set than "type" will be ignored. Default: ""

"auth_method"

Authentication method, used by client. The value is used to register the device in the Pango infrastructure. The authentication method will be provided by Pango if need.

"auth_token"

Authentication token. The value is used to register the device in the Pango infrastructure.

"data_report_interval"

Event report interval in milliseconds. The time interval between bandwidth events(see "Callback events" section). Default: 30000 (30 seconds).

"default_gateway"

Redirect all traffic including router to SDK TUN interface. Do not create any specific iptables rule for protected IP, MAC, interface, but add a global traffic redirection rule. Default: 0

"ipv6_output_only"

Use only servers which have assigned IPv6 exit pools. Set to 1 to enable. Default: 0

"hydra_default_vl_reconnect_interval"

Hydra reconnect interval when default route fails to connect. Default: 10

"tun_dev_path"

Path to OS tun device. Default: "/dev/net/tun"

"tun_addr"

IPv4 address of SDK TUN interface. Default: "100.73.0.73"

"tun_mtu"

MTU value of SDK TUN interface. If set to 0 - interface MTU value is not updated. Default: 0

"no_iptables"

Do not configure iptables rules from router SDK. 0 - The SDK setups iptables rules. 1 - The SDK client setups iptables rules. Default: 0

"no_socket_setup"

Do not configure tun interface IP addresses. 0 - The SDK setups tun interface. 1 - The SDK client setups interface. Default: 0

"protected_ip_addrs"

Array of protected IP addresses with not empty "ip_addr" and "vl" fields. Default: "" (empty, no IP addresses to protect).

Example:

"protected_ip_addrs": [{ "ip_addr": "192.168.50.159", "vl": "us-new-york" }, { "ip_addr": "192.168.50.139", "vl": "de-berlin" } ]

"protected_mac_addrs"

An array of protected MAC addresses with no empty "mac_addr" and "vl" fields. Default: "" (empty, no MAC addresses to protect).

Example:

"protected_mac_addrs": [ { "mac_addr": "08:00:27:AA:D7:17", "vl": "us-new-york" }, { "mac_addr": "09:01:28:AB:D8:18", "vl": "de-berlin" } ]

"protected_ifaces"

An array of interfaces with not empty "iface" and "vl" fields. Default: "" (empty, no interface to protect).

Example:

"protected_ifaces": [ { "iface": "eth1", "vl": "us-new-york" }, { "iface": "br0", "vl": "de-berlin" } ]

Callback events

"ready"

Wireguard-specific event. Emitted when SDK is fully initialized and ready to receive control commands. This event added since wireguard can be started without default location and no "route_connected" event is emitted in this case.

Example:

{"event":"ready"}

"route_connected"

The event is sent when a link to one of the virtual locations became active.

Example:

{ "event": "route_connected", "route_name": "us" }

"route_disconnected"

The event is sent when an active link to one of the virtual locations became inactive due to SDK stop, network condition, or for another external reason.

Example:

{ "event": "route_disconnected", "route_name": "us" }

"route_failed"

The event is sent when a link to one of the virtual locations can not be established.

Example:

{ "event": "route_failed", "route_name": "fr", "reason": 201, "reason_details": "connection failed" }

The possible values for the reason field in Hydra are as follows:

  • 201: Internal Hydra error

  • 202: Recovery timeout expired for connection restoration

  • 203: Route search exhausted

  • 204: No network available

  • 205: Unable to send data; buffer is full

  • 206: Client is blocked

The possible values for the reason field in Wireguard are as follows:

  • 201: Internal SDK error

  • 202: Indicates an issue related to the location functionality

  • 203: Indicates an issue related to the node(server) functionality

"bandwidth_info"

The event is sent periodically to the client application.

Example:

{ "event": "bandwidth_info", "bandwidth_info": [ { "data_interval_ms": 100, "n_points": 100, "points_downstream": 0, "points_upstream": 0, "route_name": "us" }, { "data_interval_ms": 100, "n_points": 100, "points_downstream": 0, "points_upstream": 0, "route_name": "de" } ] }

"credentials_expire"

The event occurs when internal VPN credentials were expired(usually in 24 hours) and SDK failed to update them automatically. In this case application should stop or restart SDK.

Example:

{"event": "credentials_expire"}

"auth_success"

The event happens when SDK successfully receives access_token while registering device using provided auth_method and auth_token. Event also returns access_token, it should be stored in persistent storage and used during next SDK start.

Example:

{"event":"auth_success", "access_token":"keeMeroohohfiefai8AhXaesh9eetheeX8neilu5ichaichuzu"}

"invalid_auth"

The event happens when SDK does device registration using provided auth_method and auth_token and receives reject from backend side. Application then should stop or restart SDK with new auth_method and auth_token.

OS level routing

To protect traffic, SDK creates a TUN device and routes all packets or packets with specific source IP, MAC, or interface to it. To achieve this, SDK creates a set of routing and/or firewall (iptables) rules.

Simple scenario

"default_gateway" configuration parameter enabled. Redirect just all traffic including router to TUN device. To be smarter, SDK not changing the default route of the router "main" table, but adding two big subnets with high metrics. 0.0.0.0/1 and 128.0.0.0/1 for IPv4.

Example:

# ip route show table main
0.0.0.0/1 dev wrt0 scope link  metric 1000
128.0.0.0/1 dev wrt0 scope link  metric 1000

Advanced scenario

"default_gateway" configuration parameter disabled. Selective routing by IP, MAC, or interface name.

First, SDK creates routing table 47 with TUN device default gateway.

Example:

# ip route show table 47
default dev wrt0

Second, SDK creates a rule to forward all packets with 0x8 mark to table 47.

Example:

# ip rule
10:	from all fwmark 0x8 lookup 47

Third, SDK creates a custom iptables chain that will contain all specific IP, MAC, and interface rules.

Example:

# iptables -L -t mangle
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination
afwrt_chain  all  --  anywhere             anywhere

Such rules can be loaded from config on the initialization phase or changed dynamically using the protection methods above. Each rule marks specified IP, MAC, or interface with 0x8 mark.

Example:

# iptables -L -t mangle -v
Chain afwrt_chain (1 references)
 pkts bytes target     prot opt in     out     source               destination
    0     0 MARK       all  --  eth2   any     anywhere             anywhere             MARK set 0x8
    0     0 MARK       all  --  any    any     anywhere             anywhere             MAC 08:00:27:AA:D7:17 MARK set 0x8
    1    76 MARK       all  --  any    any     192.168.50.139       anywhere             MARK set 0x8

It doesn't matter in which order rules will appear. All routing priority logic is done by SDK internally (see VPN Core level routing section).

VPN Core level routing

Regardless of the OS-level routing mechanism, the core is always ready to do selective routing based on IP, MAC, and interface rules if any. If no rules are specified, traffic will go to "vl_default" route.

Routing rules priority

MAC address rule has higher priority than IP address rule. IP address rule has higher priority than Interface rule.

For example, there are the following rules:

route traffic from a device with a MAC address "08:00:27:AA:D7:17" via "us"

route traffic from a device with an IP address "192.168.50.139" via "de"

If both addresses (MAC and IP) belongs to the same device, traffic will be routed via "us" VPN location.

MAC and Interface to IP resolution

To be able to distinguish device traffic on the L3 network layer SDK needs its IP address. SDK maintains a mapping from MAC or Interface to IP addresses internally.

It subscribes to changes in neighbors table using a netlink, asking for ones with the following states: NUD_REACHABLE, NUD_DELAY, NUD_PROBE, NUD_PERMANENT, NUD_STALE.

Wireguard Traffic Routing

The SDK library does not handle traffic routing for the Wireguard protocol. It is the user's responsibility to set up all required routing rules, routes, and netfilter rules. Below is a typical configuration of the networking stack. These examples assume that the tun_ifname value is set to "wrt0".

Configure Netfilter

  • For IPv4 Protocol:

    iptables -t nat -A POSTROUTING -o wrt0 -j MASQUERADE
  • For IPv6 Protocol:

    ip6tables -t nat -A POSTROUTING -o wrt0 -j MASQUERADE

Create Routing Tables with Default Routes

  • For IPv4 Protocol:

    ip ro add table 142 default dev wrt0
  • For IPv6 Protocol:

    ip -6 ro add table 142 default dev wrt0

Add Source-Based Rules to Forward Specific Devices

  • For IPv4 Protocol:

    ip ru add from 192.168.56.101/32 lookup 142
  • For IPv6 Protocol:

    ip -6 ru add from fc00::101/128 lookup 142

Last updated