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
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);
}