This post is hello world into vpn technologies (which i am not an expert in), technologies like openVPN and WireGuard and other good stuff.

Back story Link to heading

Over the years, I mentained my own openVPN server on VPS machine and that setup was working for me. There were several advantages, well, it was cheap (actually free!) considering I was hosting my blog on that machine. But now that i am too old for sh!t and gave up that machine, and After soe research, I got myself Nordvpn. So naturally, I wanted to check if they use openVPN or WireGuard. Let’s see.

WireGuard Link to heading

A quick look at ippadd output show it’s a tun network device called nordlynx which is an interesting name.

6: nordlynx: <POINTOPOINT,UP,LOWER_UP> mtu 1420 qdisc noqueue state UNKNOWN group default qlen 1000

Anyway, nordvpn supports says the following, I guess it WireGuard after all.

NordLynx is NordVPN’s revolutionary technology built around the WireGuard® VPN protocol. It helps you connect to NordVPN servers faster and improves your VPN connection speeds without compromising security or privacy.

from WireGuard website:

WireGuard® is an extremely simple yet fast and modern VPN that utilizes state-of-the-art cryptography. It aims to be faster, simpler, leaner, and more useful than IPsec, while avoiding the massive headache.

IPSEC as defined in RFC4301 has been part of the kernel for long time but over the years, people complained about how bad it was. That’s why things like openVPN projects started. At some point, they came up with WireGuard which started as off-tree kernel module and eventually got up-streamed into linux kernel.

The protocol sounds simple enough

Example image

WireGuard Tools Link to heading

The low level WireGuard configuration can be done through wg. The subcommands for wg are interesting as it can be used to configure the interface parameters.

Available subcommands:
  show: Shows the current configuration and device information
  showconf: Shows the current configuration of a given WireGuard interface, for use with `setconf'
  set: Change the current configuration, add peers, remove peers, or change peers
  setconf: Applies a configuration file to a WireGuard interface
  addconf: Appends a configuration file to a WireGuard interface
  syncconf: Synchronizes a configuration file to a WireGuard interface
  genkey: Generates a new private key and writes it to stdout
  genpsk: Generates a new preshared key and writes it to stdout
  pubkey: Reads a private key from stdin and writes a public key to stdout

I tried wg while i am connected, and i can see that Nordvpn already create the wireguard interface for us with the right parameters. For example, We can see the public and private(hidden though) keys. There is also endpoint(remote IP address for VPN server).

$ sudo wg show
interface: nordlynx
  public key: ........
  private key: (hidden)
  listening port: ....
  fwmark: ....

peer: .... 
  endpoint: .....
  allowed ips: .....

Linux kernel Link to heading

As I mentioned, wireguard is part of the linux mainstream. So, Starting with module entry point where wg_genetlink_init


static int __init wg_mod_init(void)
{
	int ret;

	wg_noise_init();

	ret = wg_peer_init();
	if (ret < 0)
		goto err_peer;

	ret = wg_device_init();
	if (ret < 0)
		goto err_device;

	ret = wg_genetlink_init();
	if (ret < 0)
		goto err_netlink;

	pr_info("WireGuard " WIREGUARD_VERSION " loaded. See www.wireguard.com for information.\n");
	pr_info("Copyright (C) 2015-2019 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.\n");

wg_gentlink_init is important because it registers the netlink interface with genl_family. genl_family is part of linux netlink interface.

static const struct genl_ops genl_ops[] = {
	{
		.cmd = WG_CMD_GET_DEVICE,
		.start = wg_get_device_start,
		.dumpit = wg_get_device_dump,
		.done = wg_get_device_done,
		.flags = GENL_UNS_ADMIN_PERM
	}, {
		.cmd = WG_CMD_SET_DEVICE,
		.doit = wg_set_device,
		.flags = GENL_UNS_ADMIN_PERM
	}
};

static struct genl_family genl_family __ro_after_init = {
	.ops = genl_ops,
	.n_ops = ARRAY_SIZE(genl_ops),
	.resv_start_op = WG_CMD_SET_DEVICE + 1,
	.name = WG_GENL_NAME,
	.version = WG_GENL_VERSION,
	.maxattr = WGDEVICE_A_MAX,
	.module = THIS_MODULE,
	.policy = device_policy,
	.netnsok = true
};

int __init wg_genetlink_init(void)
{
	return genl_register_family(&genl_family);
}

Let’s look at wg_get_device_start which calls lookup_interface

static int wg_get_device_start(struct netlink_callback *cb)
{
	struct wg_device *wg;

	wg = lookup_interface(genl_dumpit_info(cb)->attrs, cb->skb);
	if (IS_ERR(wg))
		return PTR_ERR(wg);
	DUMP_CTX(cb)->wg = wg;
	return 0;
}

loop_interface search and finds the device and returns the priv structure out of dev.

static struct wg_device *lookup_interface(struct nlattr **attrs,
					  struct sk_buff *skb)
{
	struct net_device *dev = NULL;

	if (!attrs[WGDEVICE_A_IFINDEX] == !attrs[WGDEVICE_A_IFNAME])
		return ERR_PTR(-EBADR);
	if (attrs[WGDEVICE_A_IFINDEX])
		dev = dev_get_by_index(sock_net(skb->sk),
				       nla_get_u32(attrs[WGDEVICE_A_IFINDEX]));
	else if (attrs[WGDEVICE_A_IFNAME])
		dev = dev_get_by_name(sock_net(skb->sk),
				      nla_data(attrs[WGDEVICE_A_IFNAME]));
	if (!dev)
		return ERR_PTR(-ENODEV);
	if (!dev->rtnl_link_ops || !dev->rtnl_link_ops->kind ||
	    strcmp(dev->rtnl_link_ops->kind, KBUILD_MODNAME)) {
		dev_put(dev);
		return ERR_PTR(-EOPNOTSUPP);
	}
	return netdev_priv(dev);
}