//    OpenVPN -- An application to securely tunnel IP networks
//               over a single port, with support for SSL/TLS-based
//               session authentication and key exchange,
//               packet encryption, packet authentication, and
//               packet compression.
//
//    Copyright (C) 2012-2018 OpenVPN Inc.
//    Copyright (C) 2018 Antonio Quartulli <antonio@openvpn.net>
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU Affero General Public License Version 3
//    as published by the Free Software Foundation.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU Affero General Public License for more details.
//
//    You should have received a copy of the GNU Affero General Public License
//    along with this program in the COPYING file.
//    If not, see <http://www.gnu.org/licenses/>.

#pragma once

#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>

#include <openvpn/addr/ip.hpp>
#include <openvpn/addr/ipv4.hpp>
#include <openvpn/addr/ipv6.hpp>
#include <openvpn/addr/route.hpp>


#ifdef DEBUG_RTNL
#define OPENVPN_LOG_RTNL(_x) OPENVPN_LOG(_x)
#else
#define OPENVPN_LOG_RTNL(_x)
#endif

namespace openvpn {
  namespace TunNetlink {

#define SNDBUF_SIZE (1024 * 2)
#define RCVBUF_SIZE (1024 * 4)

#define SITNL_ADDATTR(_msg, _max_size, _attr, _data, _size)         \
    {                                                               \
        if (sitnl_addattr(_msg, _max_size, _attr, _data, _size) < 0)\
        {                                                           \
            goto err;                                               \
        }                                                           \
    }

#define NLMSG_TAIL(nmsg) \
    ((struct rtattr *)(((uint8_t *)(nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len)))

    /* this class contains only static members */
    class SITNL
    {
    private:
      /**
       * Link state request message
       */
      struct sitnl_link_req
      {
	struct nlmsghdr n;
	struct ifinfomsg i;
	char buf[256];
      };

      /**
       * Address request message
       */
      struct sitnl_addr_req
      {
	struct nlmsghdr n;
	struct ifaddrmsg i;
	char buf[256];
      };

      /**
       * Route request message
       */
      struct sitnl_route_req
      {
	struct nlmsghdr n;
	struct rtmsg r;
	char buf[256];
      };

      typedef int (*sitnl_parse_reply_cb)(struct nlmsghdr *msg, void *arg);

      /**
       * Helper function used to easily add attributes to a rtnl message
       */
      static int
      sitnl_addattr(struct nlmsghdr *n, int maxlen, int type, const void *data,
		    int alen)
      {
	int len = RTA_LENGTH(alen);
	struct rtattr *rta;

	if ((int)(NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len)) > maxlen)
	{
	  OPENVPN_LOG(__func__ << ": rtnl: message exceeded bound of " << maxlen);
	  return -EMSGSIZE;
	}

	rta = NLMSG_TAIL(n);
	rta->rta_type = type;
	rta->rta_len = len;

	if (!data)
	{
	  memset(RTA_DATA(rta), 0, alen);
	}
	else
	{
	  memcpy(RTA_DATA(rta), data, alen);
	}

	n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len);

	return 0;
      }

      /**
       * Open RTNL socket
       */
      static int
      sitnl_socket(void)
      {
	int sndbuf = SNDBUF_SIZE;
	int rcvbuf = RCVBUF_SIZE;
	int fd;

	fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
	if (fd < 0)
	{
	  OPENVPN_LOG(__func__ << ": cannot open netlink socket");
	  return fd;
	}

	if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf)) < 0)
	{
	  OPENVPN_LOG(__func__ << ": SO_SNDBUF");
	  close(fd);
	  return -1;
	}

	if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf)) < 0)
	{
	  OPENVPN_LOG(__func__ << ": SO_RCVBUF");
	  close(fd);
	  return -1;
	}

	return fd;
      }

      /**
       * Bind socket to Netlink subsystem
       */
      static int
      sitnl_bind(int fd, uint32_t groups)
      {
	socklen_t addr_len;
	struct sockaddr_nl local = { };

	local.nl_family = AF_NETLINK;
	local.nl_groups = groups;

	if (bind(fd, (struct sockaddr *)&local, sizeof(local)) < 0)
	{
	  OPENVPN_LOG(__func__ << ": cannot bind netlink socket");
	  return -errno;
	}

	addr_len = sizeof(local);
	if (getsockname(fd, (struct sockaddr *)&local, &addr_len) < 0)
	{
	  OPENVPN_LOG(__func__ << ": cannot getsockname");
	  return -errno;
	}

	if (addr_len != sizeof(local))
	{
	  OPENVPN_LOG(__func__ << ": wrong address length " << addr_len);
	  return -EINVAL;
	}

	if (local.nl_family != AF_NETLINK)
	{
	  OPENVPN_LOG(__func__ << ": wrong address family " << local.nl_family);
	  return -EINVAL;
	}

	return 0;
      }

      /**
       * Send Netlink message and run callback on reply (if specified)
       */
      static int
      sitnl_send(struct nlmsghdr *payload, pid_t peer, unsigned int groups,
		 sitnl_parse_reply_cb cb, void *arg_cb)
      {
	int len, rem_len, fd, ret, rcv_len;
	struct sockaddr_nl nladdr = { };
	struct nlmsgerr *err;
	struct nlmsghdr *h;
	unsigned int seq;
	char buf[1024 * 16];
	struct iovec iov =
	{
	  .iov_base = payload,
	  .iov_len = payload->nlmsg_len,
	};
	struct msghdr nlmsg =
	{
	  .msg_name = &nladdr,
	  .msg_namelen = sizeof(nladdr),
	  .msg_iov = &iov,
	  .msg_iovlen = 1,
	};

	nladdr.nl_family = AF_NETLINK;
	nladdr.nl_pid = peer;
	nladdr.nl_groups = groups;

	payload->nlmsg_seq = seq = time(NULL);

	/* no need to send reply */
	if (!cb)
	{
	  payload->nlmsg_flags |= NLM_F_ACK;
	}

	fd = sitnl_socket();
	if (fd < 0)
	{
	  OPENVPN_LOG(__func__ << ": can't open rtnl socket");
	  return -errno;
	}

	ret = sitnl_bind(fd, 0);
	if (ret < 0)
	{
	  OPENVPN_LOG(__func__ << ": can't bind rtnl socket");
	  ret = -errno;
	  goto out;
	}

	ret = sendmsg(fd, &nlmsg, 0);
	if (ret < 0)
	{
	  OPENVPN_LOG(__func__ << ": rtnl: error on sendmsg()");
	  ret = -errno;
	  goto out;
	}

	/* prepare buffer to store RTNL replies */
	memset(buf, 0, sizeof(buf));
	iov.iov_base = buf;

	while (1)
	{
	  /*
	   * iov_len is modified by recvmsg(), therefore has to be initialized before
	   * using it again
	   */
	  OPENVPN_LOG_RTNL(__func__ << ": checking for received messages");
	  iov.iov_len = sizeof(buf);
	  rcv_len = recvmsg(fd, &nlmsg, 0);
	  OPENVPN_LOG_RTNL(__func__ << ": rtnl: received " << rcv_len << " bytes");
	  if (rcv_len < 0)
	  {
	    if ((errno == EINTR) || (errno == EAGAIN))
	    {
	      OPENVPN_LOG(__func__ << ": interrupted call");
	      continue;
	    }
	    OPENVPN_LOG(__func__ << ": rtnl: error on recvmsg()");
	    ret = -errno;
	    goto out;
	  }

	  if (rcv_len == 0)
	  {
	    OPENVPN_LOG(__func__ << ": rtnl: socket reached unexpected EOF");
	    ret = -EIO;
	    goto out;
	  }

	  if (nlmsg.msg_namelen != sizeof(nladdr))
	  {
	    OPENVPN_LOG(__func__ << ": sender address length: "
			<< nlmsg.msg_namelen << " (expected " << sizeof(nladdr)
			<< ")");
	    ret = -EIO;
	    goto out;
	  }

	  h = (struct nlmsghdr *)buf;
	  while (rcv_len >= (int)sizeof(*h))
	  {
	    len = h->nlmsg_len;
	    rem_len = len - sizeof(*h);

	    if ((rem_len < 0) || (len > rcv_len))
	    {
	      if (nlmsg.msg_flags & MSG_TRUNC)
	      {
		OPENVPN_LOG(__func__ << ": truncated message");
		ret = -EIO;
		goto out;
	      }
	      OPENVPN_LOG(__func__ << ": malformed message: len=" << len);
	      ret = -EIO;
	      goto out;
	    }

	    if (h->nlmsg_type == NLMSG_DONE)
	    {
	      goto out;
	    }

	    if (h->nlmsg_type == NLMSG_ERROR)
	    {
	      err = (struct nlmsgerr *)NLMSG_DATA(h);
	      if (rem_len < (int)sizeof(struct nlmsgerr))
	      {
		OPENVPN_LOG(__func__ << ": ERROR truncated");
		ret = -EIO;
	      }
	      else
	      {
		if (!err->error)
		{
		  ret = 0;
		  if (cb)
		  {
		    ret = cb(h, arg_cb);
		    if (ret < 0)
		      goto out;
		  }
		}
		else
		{
		  OPENVPN_LOG(__func__ << ": rtnl: generic error: "
			      << strerror(-err->error)
			      << " (" << err->error << ")");
		  ret = err->error;
		}
	      }
	      goto out;
	    }

	    if (cb)
	    {
	      ret = cb(h, arg_cb);
	    }
	    else
	    {
	      OPENVPN_LOG(__func__ << ": RTNL: unexpected reply");
	    }

	    rcv_len -= NLMSG_ALIGN(len);
	    h = (struct nlmsghdr *)((char *)h + NLMSG_ALIGN(len));
	  }

	  if (nlmsg.msg_flags & MSG_TRUNC)
	  {
	    OPENVPN_LOG(__func__ << ": message truncated");
	    continue;
	  }

	  if (rcv_len)
	  {
	    OPENVPN_LOG(__func__ << ": rtnl: " << rcv_len
			<< " not parsed bytes");
	    ret = -1;
	    goto out;
	  }

	  // continue reading multipart message
	  if (!(h->nlmsg_flags & NLM_F_MULTI))
	    goto out;
	}
out:
	close(fd);

	return ret;
      }

      /* store the route entry resulting from the query */
      typedef struct
      {
	sa_family_t family;
	IP::Addr gw;
	std::string iface;
	std::string iface_to_ignore;
	int metric;
	IP::Route dst;
	int prefix_len;
      } route_res_t;

      static int
      sitnl_route_save(struct nlmsghdr *n, void *arg)
      {
	route_res_t *res = (route_res_t *)arg;
	struct rtmsg *r = (struct rtmsg *)NLMSG_DATA(n);
	struct rtattr *rta = RTM_RTA(r);
	int len = n->nlmsg_len - NLMSG_LENGTH(sizeof(*r));
	int ifindex = 0;
	int metric = 0;

	IP::Addr gw;

	IP::Route route;
	switch (res->family)
	{
	  case AF_INET:
	    route = IP::Route("0.0.0.0/0");
	    break;
	  case AF_INET6:
	    route = IP::Route("::/0");
	    break;
	}

	while (RTA_OK(rta, len))
	{
	  switch (rta->rta_type)
	  {
	  case RTA_OIF:
	    /* route interface */
	    ifindex = *(unsigned int *)RTA_DATA(rta);
	    break;
	  case RTA_DST:
	    /* route prefix */
	    {
	      const unsigned char *bytestr = (unsigned char *)RTA_DATA(rta);
	      switch (res->family)
	      {
		case AF_INET:
		  route = IP::Route(IPv4::Addr::from_bytes_net(bytestr).to_string() + "/" + std::to_string(r->rtm_dst_len));
		  break;
		case AF_INET6:
		  route = IP::Route(IPv6::Addr::from_byte_string(bytestr).to_string() + "/" + std::to_string(r->rtm_dst_len));
		  break;
	      }
	    }
	    break;
	  case RTA_PRIORITY:
	    metric = *(unsigned int *)RTA_DATA(rta);
	    break;
	  case RTA_GATEWAY:
	    /* GW for the route */
	    {
	      const unsigned char *bytestr = (unsigned char *)RTA_DATA(rta);
	      switch (res->family)
	      {
	      case AF_INET:
		gw = IP::Addr::from_ipv4(IPv4::Addr::from_bytes_net(bytestr));
		break;
	      case AF_INET6:
		gw = IP::Addr::from_ipv6(IPv6::Addr::from_byte_string(bytestr));
		break;
	      }
	    }
	    break;
	  }

	  rta = RTA_NEXT(rta, len);
	}

	if (!gw.defined() || ifindex <= 0)
	{
	  return 0;
	}
	else
	{
	  OPENVPN_LOG_RTNL(__func__ << ": RTA_GATEWAY " << gw.to_string());
	}

	if (!route.contains(res->dst))
	{
	  OPENVPN_LOG_RTNL(__func__ << ": Ignore gw for unmatched route " << route.to_string());
	  return 0;
	}

	char iface[IFNAMSIZ];
	if (!if_indextoname(ifindex, iface))
	{
	  OPENVPN_LOG(__func__ << ": rtnl: can't get ifname for index "
		      << ifindex);
	  return -1;
	}

	if (res->iface_to_ignore == iface)
	{
	  OPENVPN_LOG_RTNL(__func__ << ": Ignore gw " << gw.to_string() << " on " << iface);
	  return 0;
	}

	// skip if gw's route prefix is shorter
	if (r->rtm_dst_len < res->prefix_len)
	{
	  OPENVPN_LOG_RTNL(__func__ << ": Ignore gw " << gw.to_string() << " with shorter route prefix " << route.to_string());
	  return 0;
	}

	// skip if gw's route metric is higher
	if ((metric > res->metric) && (res->metric != -1))
	{
	  OPENVPN_LOG_RTNL(__func__ << ": Ignore gw " << gw.to_string() << " with higher metrics " << metric);
	  return 0;
	}

	res->iface = iface;
	res->gw = gw;
	res->metric = metric;
	res->prefix_len = res->prefix_len;

	OPENVPN_LOG_RTNL(__func__ << ": Use gw " << gw.to_string() << " route " << route.to_string() << " metric " << metric);

	return 0;
      }

      /**
       * Searches for best gateway for a given route
       * @param iface_to_ignore this allows to exclude certain interface
       * from discovered gateways. Used when we want to exclude VPN interface
       * when there is active VPN connection with redirected default gateway
       * @param route route for which we search gw
       * @param [out] best_gw found gw
       * @param [out] best_iface network interface on which gw was found
       * @return
       */
      static int
      sitnl_route_best_gw(const std::string& iface_to_ignore,
			  const IP::Route& route,
			  IP::Addr& best_gw,
			  std::string& best_iface)
      {
	struct sitnl_route_req req = { };
	req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.r));
	req.n.nlmsg_type = RTM_GETROUTE;
	req.n.nlmsg_flags = NLM_F_REQUEST;

	route_res_t res;
	res.metric = -1;
	res.prefix_len = -1;

	int ret = -EINVAL;

	res.family = req.r.rtm_family = route.addr.family();
	req.r.rtm_dst_len = route.prefix_len;

	if (route.addr.family() == AF_INET)
	{
	  req.n.nlmsg_flags |= NLM_F_DUMP;
	}

	res.iface_to_ignore = iface_to_ignore;
	res.dst = route;

	{
	  unsigned char bytestr[IP::Addr::V6_SIZE / 8];
	  route.addr.to_byte_string_variable(bytestr);

	  SITNL_ADDATTR(&req.n, sizeof(req), RTA_DST, bytestr,
			route.addr.size_bytes());
	}

	ret = sitnl_send(&req.n, 0, 0, sitnl_route_save, &res);
	if (ret >= 0)
	{
	  /* save result in output variables */
	  best_gw = std::move(res.gw);
	  best_iface = std::move(res.iface);

	  OPENVPN_LOG(__func__ << " result: via " << best_gw << " dev " << best_iface);
	}
	else
	{
	  OPENVPN_LOG(__func__ << ": failed to retrieve route, err=" << ret);
	}

err:
	return ret;
      }

      static int
      sitnl_addr_set(const int cmd, const uint32_t flags, const std::string& iface,
		     const IP::Addr& local, const IP::Addr& remote, int prefixlen,
		     const IP::Addr& broadcast)
      {
	struct sitnl_addr_req req = { };
	int ret = -EINVAL;

	if (iface.empty())
	{
	  OPENVPN_LOG(__func__ << ": passed empty interface");
	  return -EINVAL;
	}

	if (local.unspecified())
	{
	  OPENVPN_LOG(__func__ << ": passed zero IP address");
	  return -EINVAL;
	}

	req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.i));
	req.n.nlmsg_type = cmd;
	req.n.nlmsg_flags = NLM_F_REQUEST | flags;

	req.i.ifa_family = local.family();
	req.i.ifa_index = if_nametoindex(iface.c_str());
	if (req.i.ifa_index == 0)
	{
	  OPENVPN_LOG(__func__ << ": cannot get ifindex for " << iface << " "
		      << strerror(errno));
	  return -ENOENT;
	}

	/* if no prefixlen has been specified, assume host address */
	if (prefixlen == 0)
	{
	  prefixlen = local.size();
	}
	req.i.ifa_prefixlen = prefixlen;

	{
	  unsigned char bytestr[IP::Addr::V6_SIZE / 8];

	  local.to_byte_string_variable(bytestr);
	  SITNL_ADDATTR(&req.n, sizeof(req), IFA_LOCAL, bytestr, local.size_bytes());

	  if (remote.specified())
	  {
	    remote.to_byte_string_variable(bytestr);
	    SITNL_ADDATTR(&req.n, sizeof(req), IFA_ADDRESS, bytestr, remote.size_bytes());
	  }

	  if (broadcast.specified())
	  {
	    broadcast.to_byte_string_variable(bytestr);
	    SITNL_ADDATTR(&req.n, sizeof(req), IFA_BROADCAST, bytestr, broadcast.size_bytes());
	  }
	}

	ret = sitnl_send(&req.n, 0, 0, NULL, NULL);
	if ((ret < 0) && (errno == EEXIST))
	{
	  ret = 0;
	}

err:
	return ret;
      }

      static int
      sitnl_addr_ptp_add(const std::string& iface, const IP::Addr& local,
			 const IP::Addr& remote)
      {
	return sitnl_addr_set(RTM_NEWADDR, NLM_F_CREATE | NLM_F_REPLACE, iface,
			      local, remote, 0,
			      IP::Addr::from_zero(local.version()));
      }

      static int
      sitnl_addr_ptp_del(const std::string& iface, const IP::Addr& local)
      {
	return sitnl_addr_set(RTM_DELADDR, 0, iface, local,
			      IP::Addr::from_zero(local.version()),
			      0, IP::Addr::from_zero(local.version()));
      }

      static int
      sitnl_route_set(const int cmd, const uint32_t flags,
		      const std::string& iface, const IP::Route& route,
		      const IP::Addr& gw, const enum rt_class_t table,
		      const int metric, const enum rt_scope_t scope,
		      const int protocol, const int type)
      {
	struct sitnl_route_req req = { };
	int ret = -1;

	req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.r));
	req.n.nlmsg_type = cmd;
	req.n.nlmsg_flags = NLM_F_REQUEST | flags;

	req.r.rtm_family = route.addr.family();
	req.r.rtm_scope = scope;
	req.r.rtm_protocol = protocol;
	req.r.rtm_type = type;
	req.r.rtm_dst_len = route.prefix_len;

	if (table < 256)
	{
	  req.r.rtm_table = table;
	}
	else
	{
	  req.r.rtm_table = RT_TABLE_UNSPEC;
	  SITNL_ADDATTR(&req.n, sizeof(req), RTA_TABLE, &table, 4);
	}

	{
	  unsigned char bytestr[IP::Addr::V6_SIZE / 8];

	  route.addr.to_byte_string_variable(bytestr);
	  SITNL_ADDATTR(&req.n, sizeof(req), RTA_DST, bytestr, route.addr.size_bytes());

	  if (gw.specified())
	  {
	    gw.to_byte_string_variable(bytestr);
	    SITNL_ADDATTR(&req.n, sizeof(req), RTA_GATEWAY, bytestr, gw.size_bytes());
	  }
	}

	if (!iface.empty())
	{
	  int ifindex = if_nametoindex(iface.c_str());
	  if (ifindex == 0)
	  {
	    OPENVPN_LOG(__func__ << ": rtnl: cannot get ifindex for " << iface);
	    return -ENOENT;
	  }

	  SITNL_ADDATTR(&req.n, sizeof(req), RTA_OIF, &ifindex, 4);
	}

	if (metric > 0)
	{
	  SITNL_ADDATTR(&req.n, sizeof(req), RTA_PRIORITY, &metric, 4);
	}

	ret = sitnl_send(&req.n, 0, 0, NULL, NULL);
	if ((ret < 0) && (errno == EEXIST))
	{
	  ret = 0;
	}

err:
	return ret;
      }

      static int
      sitnl_addr_add(const std::string& iface, const IP::Addr& addr,
		     int prefixlen, const IP::Addr& broadcast)
      {
	return sitnl_addr_set(RTM_NEWADDR, NLM_F_CREATE | NLM_F_REPLACE, iface,
			      addr, IP::Addr::from_zero(addr.version()),
			      prefixlen, broadcast);
      }

      static int
      sitnl_addr_del(const std::string& iface, const IP::Addr& addr, int prefixlen)
      {
	return sitnl_addr_set(RTM_DELADDR, 0, iface, addr,
			      IP::Addr::from_zero(addr.version()), prefixlen,
			      IP::Addr::from_zero(addr.version()));
      }

      static int
      sitnl_route_add(const IP::Route& route, const IP::Addr& gw,
		      const std::string& iface, const uint32_t table,
		      const int metric)
      {
	return sitnl_route_set(RTM_NEWROUTE, NLM_F_CREATE, iface,
			       route, gw,
			       (enum rt_class_t)(!table ? RT_TABLE_MAIN : table),
			       metric, RT_SCOPE_UNIVERSE, RTPROT_BOOT, RTN_UNICAST);
      }

      static int
      sitnl_route_del(const IP::Route& route, const IP::Addr& gw,
		      const std::string& iface, const uint32_t table,
		      const int metric)
      {
	return sitnl_route_set(RTM_DELROUTE, 0, iface, route, gw,
			       (enum rt_class_t)(!table ? RT_TABLE_MAIN : table),
			       metric, RT_SCOPE_NOWHERE,
			       0, 0);
      }

    public:

      static int
      net_route_best_gw(const IP::Route6& route, IPv6::Addr& best_gw6,
			std::string& best_iface, const std::string& iface_to_ignore = "")
      {
	IP::Addr best_gw;
	int ret;

	OPENVPN_LOG(__func__ << " query IPv6: " << route);

	ret = sitnl_route_best_gw(iface_to_ignore, IP::Route(IP::Addr::from_ipv6(route.addr), route.prefix_len),
				  best_gw, best_iface);
	if (ret >= 0)
	{
	  best_gw6 = best_gw.to_ipv6();
	}

	return ret;
      }

      static int
      net_route_best_gw(const IP::Route4& route, IPv4::Addr &best_gw4,
			std::string& best_iface, const std::string& iface_to_ignore = "")
      {
	IP::Addr best_gw;
	int ret;

	OPENVPN_LOG(__func__ << " query IPv4: " << route);

	ret = sitnl_route_best_gw(iface_to_ignore, IP::Route(IP::Addr::from_ipv4(route.addr), route.prefix_len),
				  best_gw, best_iface);
	if (ret >= 0)
	{
	  best_gw4 = best_gw.to_ipv4();
	}

	return ret;
      }

      static int
      net_iface_up(std::string& iface, bool up)
      {
	struct sitnl_link_req req = { };
	int ifindex;

	if (iface.empty())
	{
	  OPENVPN_LOG(__func__ << ": passed empty interface");
	  return -EINVAL;
	}

	ifindex = if_nametoindex(iface.c_str());
	if (ifindex == 0)
	{
	  OPENVPN_LOG(__func__ << ": rtnl: cannot get ifindex for " << iface
		      << ": " << strerror(errno));
	  return -ENOENT;
	}

	req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.i));
	req.n.nlmsg_flags = NLM_F_REQUEST;
	req.n.nlmsg_type = RTM_NEWLINK;

	req.i.ifi_family = AF_PACKET;
	req.i.ifi_index = ifindex;
	req.i.ifi_change |= IFF_UP;
	if (up)
	{
	  req.i.ifi_flags |= IFF_UP;
	}
	else
	{
	  req.i.ifi_flags &= ~IFF_UP;
	}

	OPENVPN_LOG(__func__ << ": set " << iface << " " << (up ? "up" : "down"));

	return sitnl_send(&req.n, 0, 0, NULL, NULL);
      }

      static int
      net_iface_mtu_set(std::string& iface, uint32_t mtu)
      {
	struct sitnl_link_req req = { };
	int ifindex;

	if (iface.empty())
	{
	  OPENVPN_LOG(__func__ << ": passed empty interface");
	  return -EINVAL;
	}

	ifindex = if_nametoindex(iface.c_str());
	if (ifindex == 0)
	{
	  OPENVPN_LOG(__func__ << ": rtnl: cannot get ifindex for " << iface);
	  return -1;
	}

	req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.i));
	req.n.nlmsg_flags = NLM_F_REQUEST;
	req.n.nlmsg_type = RTM_NEWLINK;

	req.i.ifi_family = AF_PACKET;
	req.i.ifi_index = ifindex;

	SITNL_ADDATTR(&req.n, sizeof(req), IFLA_MTU, &mtu, 4);

	OPENVPN_LOG(__func__ << ": mtu " << mtu << " for " << iface);

err:
	return sitnl_send(&req.n, 0, 0, NULL, NULL);
      }

      static int
      net_addr_add(const std::string& iface, const IPv4::Addr& addr,
		   const int prefixlen, const IPv4::Addr& broadcast)
      {
	OPENVPN_LOG(__func__ << ": " << addr << "/" << prefixlen << " brd "
		    << broadcast << " dev " << iface);

	return sitnl_addr_add(iface, IP::Addr::from_ipv4(addr), prefixlen,
			      IP::Addr::from_ipv4(broadcast));
      }

      static int
      net_addr_add(const std::string& iface, const IPv6::Addr& addr,
		   const int prefixlen)
      {
	OPENVPN_LOG(__func__ << ": " << addr << "/" << prefixlen << " dev " << iface);

	return sitnl_addr_add(iface, IP::Addr::from_ipv6(addr), prefixlen,
			      IP::Addr::from_zero(IP::Addr::V6));
      }

      static int
      net_addr_del(const std::string& iface, const IPv4::Addr& addr,
		   const int prefixlen)
      {
	OPENVPN_LOG(__func__ << ": " << addr << "/" << prefixlen << " dev " << iface);

	return sitnl_addr_del(iface, IP::Addr::from_ipv4(addr), prefixlen);
      }

      static int
      net_addr_del(const std::string& iface, const IPv6::Addr& addr,
		   const int prefixlen)
      {
	OPENVPN_LOG(__func__ << ": " << addr << "/" << prefixlen << " dev " << iface);

	return sitnl_addr_del(iface, IP::Addr::from_ipv6(addr), prefixlen);
      }

      static int
      net_addr_ptp_add(const std::string& iface, const IPv4::Addr& local,
		       const IPv4::Addr& remote)
      {
	OPENVPN_LOG(__func__ << ": " << local << " peer " << remote << " dev " << iface);

	return sitnl_addr_ptp_add(iface, IP::Addr::from_ipv4(local),
				  IP::Addr::from_ipv4(remote));
      }

      static int
      net_addr_ptp_del(const std::string& iface, const IPv4::Addr& local,
		       const IPv4::Addr& remote)
      {
	OPENVPN_LOG(__func__ << ": " << local << " dev " << iface);

	return sitnl_addr_ptp_del(iface, IP::Addr::from_ipv4(local));
      }

      static int
      net_route_add(const IP::Route4& route, const IPv4::Addr& gw,
		    const std::string& iface, const uint32_t table,
		    const int metric)
      {
	OPENVPN_LOG(__func__ << ": " << route << " via " << gw << " dev " << iface
		    << " table " << table << " metric " << metric);

	return sitnl_route_add(IP::Route(IP::Addr::from_ipv4(route.addr), route.prefix_len),
			       IP::Addr::from_ipv4(gw), iface, table, metric);
      }

      static int
      net_route_add(const IP::Route6& route, const IPv6::Addr& gw,
		    const std::string& iface, const uint32_t table,
		    const int metric)
      {
	OPENVPN_LOG(__func__ << ": " << route << " via " << gw << " dev " << iface
		    << " table " << table << " metric " << metric);

	return sitnl_route_add(IP::Route(IP::Addr::from_ipv6(route.addr), route.prefix_len),
			       IP::Addr::from_ipv6(gw), iface, table, metric);
      }

      static int
      net_route_del(const IP::Route4& route, const IPv4::Addr& gw,
		    const std::string& iface, const uint32_t table,
		    const int metric)
      {
	OPENVPN_LOG(__func__ << ": " << route << " via " << gw << " dev " << iface
		    << " table " << table << " metric " << metric);

	return sitnl_route_del(IP::Route(IP::Addr::from_ipv4(route.addr), route.prefix_len),
			       IP::Addr::from_ipv4(gw), iface, table, metric);
      }

      static int
      net_route_del(const IP::Route6& route, const IPv6::Addr& gw,
		    const std::string& iface, const uint32_t table,
		    const int metric)
      {
	OPENVPN_LOG(__func__ << ": " << route << " via " << gw << " dev " << iface
		    << " table " << table << " metric " << metric);

	return sitnl_route_del(IP::Route(IP::Addr::from_ipv6(route.addr), route.prefix_len),
			       IP::Addr::from_ipv6(gw), iface, table, metric);
      }
    };
  }
}
