Newer
Older
XinYang_IOS / Pods / OpenVPNAdapter / Sources / OpenVPN3 / openvpn / tun / linux / client / genl.hpp
@zhangfeng zhangfeng on 7 Dec 2023 20 KB 1.8.0
//    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-2020 OpenVPN Inc.
//    Copyright (C) 2020-2020 Lev Stipakov <lev@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 <openvpn/buffer/buffer.hpp>
#include <openvpn/buffer/bufstr.hpp>
#include <openvpn/common/exception.hpp>
#include <openvpn/dco/key.hpp>

#include <linux/ovpn_dco.h>
#include <netlink/genl/ctrl.h>
#include <netlink/genl/family.h>
#include <netlink/genl/genl.h>
#include <netlink/netlink.h>
#include <netlink/socket.h>

#include <memory>

namespace openvpn {

#define nla_nest_start(_msg, _type) nla_nest_start(_msg, (_type) | NLA_F_NESTED)

typedef int (*ovpn_nl_cb)(struct nl_msg *msg, void *arg);

/**
 * Implements asynchronous communication with ovpn-dco kernel module
 * via generic netlink protocol.
 *
 * Before using this class, caller should create ovpn-dco network device.
 *
 * @tparam ReadHandler class which implements
 * <tt>tun_read_handler(BufferAllocated &buf)</tt> method. \n
 *
 * buf has following layout:
 *  \li first byte - command type ( \p OVPN_CMD_PACKET, \p OVPN_CMD_DEL_PEER or
 * -1 for error)
 * \li following bytes - command-specific payload
 */
template <typename ReadHandler> class GeNL : public RC<thread_unsafe_refcount> {
  OPENVPN_EXCEPTION(netlink_error);

  typedef std::unique_ptr<nl_msg, decltype(&nlmsg_free)> NlMsgPtr;
  typedef std::unique_ptr<nl_sock, decltype(&nl_socket_free)> NlSockPtr;
  typedef std::unique_ptr<nl_cb, decltype(&nl_cb_put)> NlCbPtr;

public:
  typedef RCPtr<GeNL> Ptr;

  /**
   * Construct a new GeNL object
   *
   * @param io_context reference to io_context
   * @param ifindex_arg index of ovpn-dco network device
   * @param read_handler_arg instance of \p ReadHandler
   * @throws netlink_error thrown if error occurs during initialization
   */
  explicit GeNL(openvpn_io::io_context &io_context, unsigned int ifindex_arg,
                ReadHandler read_handler_arg)
      : sock_ptr(nl_socket_alloc(), nl_socket_free),
        cb_ptr(nl_cb_alloc(NL_CB_DEFAULT), nl_cb_put), sock(sock_ptr.get()),
        cb(cb_ptr.get()), ifindex(ifindex_arg), read_handler(read_handler_arg),
        halt(false) {
    nl_socket_set_buffer_size(sock, 8192, 8192);

    int ret = genl_connect(sock);
    if (ret != 0)
      OPENVPN_THROW(netlink_error,
                    " cannot connect to generic netlink: " << nl_geterror(ret));

    int mcast_id = get_mcast_id();
    if (mcast_id < 0)
      OPENVPN_THROW(netlink_error,
                    " cannot get multicast group: " << nl_geterror(mcast_id));

    ret = nl_socket_add_membership(sock, mcast_id);
    if (ret)
      OPENVPN_THROW(netlink_error, "failed to join mcast group: " << ret);

    ovpn_dco_id = genl_ctrl_resolve(sock, OVPN_NL_NAME);
    if (ovpn_dco_id < 0)
      OPENVPN_THROW(netlink_error,
                    " cannot find ovpn_dco netlink component: " << ovpn_dco_id);

    // set callback to handle control channel messages
    nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, message_received, this);

    nl_cb_set(
        cb, NL_CB_SEQ_CHECK, NL_CB_CUSTOM,
        [](struct nl_msg *, void *) { return (int)NL_OK; }, NULL);
    nl_socket_set_cb(sock, cb);

    register_packet();

    // wrap netlink socket into ASIO primitive for async read
    stream.reset(new openvpn_io::posix::stream_descriptor(
        io_context, nl_socket_get_fd(sock)));

    nl_socket_set_nonblocking(sock);

    queue_genl_read();
  }

  /**
   * Start VPN connection
   *
   * At the moment, only client mode and ipv4/udp transport is supported.
   *
   * Note: this and many other methods have \p nla_put_failure label, which
   * must be defined when using NLA_PUT_XXX macros.
   *
   * @param socket file descriptor of transport socket, created by client
   * @throws netlink_error thrown if error occurs during sending netlink message
   */
  void start_vpn(int socket) {
    auto msg_ptr = create_msg(OVPN_CMD_START_VPN);
    auto* msg = msg_ptr.get();

    NLA_PUT_U32(msg, OVPN_ATTR_SOCKET, socket);
    NLA_PUT_U8(msg, OVPN_ATTR_PROTO, OVPN_PROTO_UDP4);
    NLA_PUT_U8(msg, OVPN_ATTR_MODE, OVPN_MODE_CLIENT);

    send_netlink_message(msg);
    return;

  nla_put_failure:
    OPENVPN_THROW(netlink_error, " start_vpn() nla_put_failure");
  }

  /**
   * Add peer information to kernel module
   *
   * @tparam T ASIO's transport socket endpoint type
   * @param local_endpoint local endpoint
   * @param remote_endpoint remote endpoiont
   * @throws netlink_error thrown if error occurs during sending netlink message
   */
  template <typename T> void new_peer(T local_endpoint, T remote_endpoint) {
    auto msg_ptr = create_msg(OVPN_CMD_NEW_PEER);
    auto* msg = msg_ptr.get();

    struct in_addr laddr;
    std::memcpy(&laddr.s_addr,
                local_endpoint.address().to_v4().to_bytes().data(), 4);
    struct in_addr raddr;
    std::memcpy(&raddr.s_addr,
                remote_endpoint.address().to_v4().to_bytes().data(), 4);

    struct nlattr *addr = nla_nest_start(msg, OVPN_ATTR_SOCKADDR_REMOTE);
    NLA_PUT(msg, OVPN_SOCKADDR_ATTR_ADDRESS, 4, &raddr);
    NLA_PUT_U16(msg, OVPN_SOCKADDR_ATTR_PORT, remote_endpoint.port());
    nla_nest_end(msg, addr);

    addr = nla_nest_start(msg, OVPN_ATTR_SOCKADDR_LOCAL);
    NLA_PUT(msg, OVPN_SOCKADDR_ATTR_ADDRESS, 4, &laddr);
    NLA_PUT_U16(msg, OVPN_SOCKADDR_ATTR_PORT, local_endpoint.port());
    nla_nest_end(msg, addr);

    send_netlink_message(msg);
    return;

  nla_put_failure:
    OPENVPN_THROW(netlink_error, " new_peer() nla_put_failure");
  }

  /**
   * Send data to kernel module, which is then sent to remote.
   * Used for sending control channel packets.
   *
   * @param data binary blob
   * @param len length of binary blob
   * @throws netlink_error thrown if error occurs during sending netlink message
   */
  void send_data(const void *data, size_t len) {
    auto msg_ptr = create_msg(OVPN_CMD_PACKET);
    auto* msg = msg_ptr.get();

    NLA_PUT(msg, OVPN_ATTR_PACKET, len, data);

    send_netlink_message(msg);
    return;

  nla_put_failure:
    OPENVPN_THROW(netlink_error, " send_data() nla_put_failure");
  }

  /**
   * Inject new key into kernel module
   *
   * @param key_slot OVPN_KEY_SLOT_PRIMARY or OVPN_KEY_SLOT_SECONDARY
   * @param kc pointer to KeyConfig struct which contains key data
   * @throws netlink_error thrown if error occurs during sending netlink message
   */
  void new_key(unsigned int key_slot, const KoRekey::KeyConfig *kc) {
    auto msg_ptr = create_msg(OVPN_CMD_NEW_KEY);
    auto* msg = msg_ptr.get();

    const int NONCE_LEN = 12;

    struct nlattr *key_dir;

    NLA_PUT_U32(msg, OVPN_ATTR_REMOTE_PEER_ID, kc->remote_peer_id);
    NLA_PUT_U8(msg, OVPN_ATTR_KEY_SLOT, key_slot);
    NLA_PUT_U16(msg, OVPN_ATTR_KEY_ID, kc->key_id);
    NLA_PUT_U16(msg, OVPN_ATTR_CIPHER_ALG, kc->cipher_alg);
    if ((kc->cipher_alg == OVPN_CIPHER_ALG_AES_CBC) ||
        (kc->cipher_alg == OVPN_CIPHER_ALG_NONE)) {
      NLA_PUT_U16(msg, OVPN_ATTR_HMAC_ALG, kc->hmac_alg);
    }

    key_dir = nla_nest_start(msg, OVPN_ATTR_ENCRYPT_KEY);
    NLA_PUT(msg, OVPN_KEY_DIR_ATTR_CIPHER_KEY, kc->encrypt.cipher_key_size,
            kc->encrypt.cipher_key);
    if (kc->cipher_alg == OVPN_CIPHER_ALG_AES_GCM) {
      NLA_PUT(msg, OVPN_KEY_DIR_ATTR_NONCE_TAIL, NONCE_LEN,
              kc->encrypt.nonce_tail);
    } else {
      NLA_PUT(msg, OVPN_KEY_DIR_ATTR_HMAC_KEY, kc->encrypt.hmac_key_size,
              kc->encrypt.hmac_key);
    }
    nla_nest_end(msg, key_dir);

    key_dir = nla_nest_start(msg, OVPN_ATTR_DECRYPT_KEY);
    NLA_PUT(msg, OVPN_KEY_DIR_ATTR_CIPHER_KEY, kc->decrypt.cipher_key_size,
            kc->decrypt.cipher_key);
    if (kc->cipher_alg == OVPN_CIPHER_ALG_AES_GCM) {
      NLA_PUT(msg, OVPN_KEY_DIR_ATTR_NONCE_TAIL, NONCE_LEN,
              kc->decrypt.nonce_tail);
    } else {
      NLA_PUT(msg, OVPN_KEY_DIR_ATTR_HMAC_KEY, kc->decrypt.hmac_key_size,
              kc->decrypt.hmac_key);
    }
    nla_nest_end(msg, key_dir);

    send_netlink_message(msg);
    return;

  nla_put_failure:
    OPENVPN_THROW(netlink_error, " set_keys() nla_put_failure");
  }

  /**
   * Swap keys between primary and secondary slots. Called
   * by client as part of rekeying logic to promote and demote keys.
   *
   * @throws netlink_error thrown if error occurs during sending netlink message
   */
  void swap_keys() {
    auto msg_ptr = create_msg(OVPN_CMD_SWAP_KEYS);
    auto* msg = msg_ptr.get();

    send_netlink_message(msg);
  }

  /**
   * Remove key from key slot.
   *
   * @param key_slot OVPN_KEY_SLOT_PRIMARY or OVPN_KEY_SLOT_SECONDARY
   * @throws netlink_error thrown if error occurs during sending netlink message
   */
  void del_key(unsigned int key_slot) {
    auto msg_ptr = create_msg(OVPN_CMD_DEL_KEY);
    auto* msg = msg_ptr.get();

    NLA_PUT_U8(msg, OVPN_ATTR_KEY_SLOT, key_slot);

    send_netlink_message(msg);
    return;

  nla_put_failure:
    OPENVPN_THROW(netlink_error, " del_key() nla_put_failure");
  }

  /**
   * Set peer properties. Currently used for keepalive settings.
   *
   * @param keepalive_interval how often to send ping packet in absence of
   * traffic
   * @param keepalive_timeout when to trigger keepalive_timeout in absence of
   * traffic
   * @throws netlink_error thrown if error occurs during sending netlink message
   */
  void set_peer(unsigned int keepalive_interval,
                unsigned int keepalive_timeout) {
    auto msg_ptr = create_msg(OVPN_CMD_SET_PEER);
    auto* msg = msg_ptr.get();

    NLA_PUT_U32(msg, OVPN_ATTR_KEEPALIVE_INTERVAL, keepalive_interval);
    NLA_PUT_U32(msg, OVPN_ATTR_KEEPALIVE_TIMEOUT, keepalive_timeout);

    send_netlink_message(msg);
    return;

  nla_put_failure:
    OPENVPN_THROW(netlink_error, " set_peer() nla_put_failure");
  }

  void stop() {
    if (!halt) {
      halt = true;

      try {
        stream->cancel();
        stream->close();
      } catch (...) {
        // ASIO might throw transport exceptions which I found is safe to ignore
      }

      // contrary to what ASIO doc says, stream->close() doesn't
      // cancel async read on netlink socket, explicitly close it here
      sock_ptr.reset();
      cb_ptr.reset();
    }
  }

private:
  void register_packet() {
    auto msg_ptr = create_msg(OVPN_CMD_REGISTER_PACKET);
    auto* msg = msg_ptr.get();

    send_netlink_message(msg);
  }

  struct mcast_handler_args {
    const char *group;
    int id;
  };

  /**
   * This callback is called by libnl. Here we enumerate netlink
   * multicast groups and find id of the one which name matches
   * ovpn-dco multicast group.
   *
   * @param msg netlink message to be processed
   * @param arg arguments passed by nl_cb_set() call
   * @return int id of ovpn-dco multicast group
   */
  static int mcast_family_handler(struct nl_msg *msg, void *arg) {
    struct mcast_handler_args *grp = static_cast<mcast_handler_args *>(arg);
    struct nlattr *tb[CTRL_ATTR_MAX + 1];
    struct genlmsghdr *gnlh = static_cast<genlmsghdr *>(
        nlmsg_data(reinterpret_cast<const nlmsghdr *>(nlmsg_hdr(msg))));
    struct nlattr *mcgrp;
    int rem_mcgrp;

    nla_parse(tb, CTRL_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
              genlmsg_attrlen(gnlh, 0), NULL);

    if (!tb[CTRL_ATTR_MCAST_GROUPS])
      return NL_SKIP;

    nla_for_each_nested(mcgrp, tb[CTRL_ATTR_MCAST_GROUPS], rem_mcgrp) {
      struct nlattr *tb_mcgrp[CTRL_ATTR_MCAST_GRP_MAX + 1];

      nla_parse(tb_mcgrp, CTRL_ATTR_MCAST_GRP_MAX,
                static_cast<nlattr *>(nla_data(mcgrp)), nla_len(mcgrp), NULL);

      if (!tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME] ||
          !tb_mcgrp[CTRL_ATTR_MCAST_GRP_ID])
        continue;
      if (strncmp((const char *)nla_data(tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME]),
                  grp->group, nla_len(tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME])))
        continue;
      grp->id = nla_get_u32(tb_mcgrp[CTRL_ATTR_MCAST_GRP_ID]);
      break;
    }

    return NL_SKIP;
  }

  /**
   * Return id of multicast group which ovpn-dco uses to
   * broadcast OVPN_CMD_DEL_PEER message
   *
   * @return int multicast group id
   */
  int get_mcast_id() {
    int ret = 1;
    struct mcast_handler_args grp = {
        .group = OVPN_NL_MULTICAST_GROUP_PEERS,
        .id = -ENOENT,
    };
    NlMsgPtr msg_ptr(nlmsg_alloc(), nlmsg_free);
    auto* msg = msg_ptr.get();

    NlCbPtr mcast_cb_ptr(nl_cb_alloc(NL_CB_DEFAULT), nl_cb_put);
    auto* mcast_cb = mcast_cb_ptr.get();

    int ctrlid = genl_ctrl_resolve(sock, "nlctrl");

    genlmsg_put(msg, 0, 0, ctrlid, 0, 0, CTRL_CMD_GETFAMILY, 0);
    NLA_PUT_STRING(msg, CTRL_ATTR_FAMILY_NAME, OVPN_NL_NAME);

    send_netlink_message(msg);

    nl_cb_err(
        mcast_cb, NL_CB_CUSTOM,
        [](struct sockaddr_nl *nla, struct nlmsgerr *err, void *arg) {
          int *ret = static_cast<int *>(arg);
          *ret = err->error;
          return (int)NL_STOP;
        },
        &ret);
    nl_cb_set(
        mcast_cb, NL_CB_ACK, NL_CB_CUSTOM,
        [](struct nl_msg *msg, void *arg) {
          int *ret = static_cast<int *>(arg);
          *ret = 0;
          return (int)NL_STOP;
        },
        &ret);

    nl_cb_set(mcast_cb, NL_CB_VALID, NL_CB_CUSTOM, mcast_family_handler, &grp);

    while (ret > 0)
      nl_recvmsgs(sock, mcast_cb);

    if (ret == 0)
      ret = grp.id;

    return ret;

  nla_put_failure:
    OPENVPN_THROW(netlink_error, "get_mcast_id() nla_put_failure");
  }

  void handle_read(const openvpn_io::error_code &error) {
    if (halt)
      return;

    std::ostringstream os;
    if (error) {
      os << "error reading netlink message: " << error.message() << ", "
         << error;
      reset_buffer();
      int8_t cmd = -1;
      buf.write(&cmd, sizeof(cmd));
      buf_write_string(buf, os.str());
      read_handler->tun_read_handler(buf);
    }

    try {
      read_netlink_message();
      queue_genl_read();
    } catch (const netlink_error &e) {
      reset_buffer();
      int8_t cmd = -1;
      buf.write(&cmd, sizeof(cmd));
      buf_write_string(buf, e.what());
      read_handler->tun_read_handler(buf);
    }
  }

  void queue_genl_read() {
    stream->async_wait(openvpn_io::posix::stream_descriptor::wait_read,
                       [self = Ptr(this)](const openvpn_io::error_code &error) {
                         self->handle_read(error);
                       });
  }

  NlMsgPtr create_msg(enum ovpn_nl_commands cmd) {
    NlMsgPtr msg_ptr(nlmsg_alloc(), nlmsg_free);
    genlmsg_put(msg_ptr.get(), 0, 0, ovpn_dco_id, 0, 0, cmd, 0);
    NLA_PUT_U32(msg_ptr.get(), OVPN_ATTR_IFINDEX, ifindex);
    return msg_ptr;

  nla_put_failure:
    OPENVPN_THROW(netlink_error, " start_vpn() nla_put_failure");
  }

  void read_netlink_message() {
    // this is standard error code returned from kernel
    // and assigned inside ovpn_nl_cb_error()
    int ovpn_dco_err = 0;
    nl_cb_err(cb, NL_CB_CUSTOM, ovpn_nl_cb_error, &ovpn_dco_err);

    // this triggers reading callback, GeNL::message_received(),
    // and, if neccessary, ovpn_nl_cb_error() and returns netlink error code
    int netlink_err = nl_recvmsgs(sock, cb);

    if (ovpn_dco_err != 0)
      OPENVPN_THROW(netlink_error, "ovpn-dco error on receiving message: "
                                       << strerror(-ovpn_dco_err) << ", "
                                       << ovpn_dco_err);

    if (netlink_err < 0)
      OPENVPN_THROW(netlink_error, "netlink error on receiving message: "
                                       << nl_geterror(netlink_err) << ", "
                                       << netlink_err);
  }

  /**
   * This is called inside libnl's \c nl_recvmsgs() call
   * to process incoming netlink message.
   *
   * @param msg netlink message to be processed
   * @param arg argument passed by \c nl_cb_set()
   * @return int callback action
   */
  static int message_received(struct nl_msg *msg, void *arg) {
    GeNL *self = static_cast<GeNL *>(arg);

    struct genlmsghdr *gnlh = static_cast<genlmsghdr *>(
        nlmsg_data(reinterpret_cast<const nlmsghdr *>(nlmsg_hdr(msg))));
    struct nlattr *attrs[OVPN_ATTR_MAX + 1];

    nla_parse(attrs, OVPN_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
              genlmsg_attrlen(gnlh, 0), NULL);

    switch (gnlh->cmd) {
    case OVPN_CMD_PACKET:
      if (!attrs[OVPN_ATTR_PACKET])
        OPENVPN_THROW(
            netlink_error,
            "missing OVPN_ATTR_PACKET attribute in OVPN_CMD_PACKET command");

      {
        self->reset_buffer();
        self->buf.write(&gnlh->cmd, sizeof(gnlh->cmd));
        self->buf.write(nla_data(attrs[OVPN_ATTR_PACKET]),
                        nla_len(attrs[OVPN_ATTR_PACKET]));
        // pass control channel message to upper layer
        self->read_handler->tun_read_handler(self->buf);
      }
      break;

    case OVPN_CMD_DEL_PEER:
      if (!attrs[OVPN_ATTR_DEL_PEER_REASON])
        OPENVPN_THROW(netlink_error, "missing OVPN_ATTR_DEL_PEER_REASON "
                                     "attribute in OVPN_CMD_DEL_PEER command");

      {
        self->reset_buffer();
        self->buf.write(&gnlh->cmd, sizeof(gnlh->cmd));
        uint8_t reason = nla_get_u8(attrs[OVPN_ATTR_DEL_PEER_REASON]);
        self->buf.write(&reason, sizeof(reason));
        self->read_handler->tun_read_handler(self->buf);
      }
      break;

    default:
      OPENVPN_LOG(__func__ << " unknown netlink command: " << (int)gnlh->cmd);
    }

    return NL_SKIP;
  }

  void reset_buffer() {
    // good enough values to handle control packets
    buf.reset(512, 3072,
              BufferAllocated::GROW | BufferAllocated::CONSTRUCT_ZERO |
                  BufferAllocated::DESTRUCT_ZERO);
  }

  /**
   * This is an error callback called by netlink for
   * error message processing customization.
   *
   * @param nla netlink address of the peer (value not needed here)
   * @param err netlink error message being processed
   * @param arg argument passed by \c nl_cb_err()
   * @return int callback action
   */
  static int ovpn_nl_cb_error(struct sockaddr_nl* /*nla*/,
                              struct nlmsgerr *err, void *arg) {
    struct nlmsghdr *nlh = (struct nlmsghdr *)err - 1;
    struct nlattr *tb_msg[NLMSGERR_ATTR_MAX + 1];
    int len = nlh->nlmsg_len;
    struct nlattr *attrs;
    int *ret = static_cast<int *>(arg);
    int ack_len = sizeof(*nlh) + sizeof(int) + sizeof(*nlh);

    *ret = err->error;

    if (!(nlh->nlmsg_flags & NLM_F_ACK_TLVS))
      return NL_STOP;

    if (!(nlh->nlmsg_flags & NLM_F_CAPPED))
      ack_len += err->msg.nlmsg_len - sizeof(*nlh);

    if (len <= ack_len)
      return NL_STOP;

    attrs = reinterpret_cast<nlattr *>((unsigned char *)nlh + ack_len);
    len -= ack_len;

    nla_parse(tb_msg, NLMSGERR_ATTR_MAX, attrs, len, NULL);
    if (tb_msg[NLMSGERR_ATTR_MSG]) {
      len = strnlen((char *)nla_data(tb_msg[NLMSGERR_ATTR_MSG]),
                    nla_len(tb_msg[NLMSGERR_ATTR_MSG]));
      OPENVPN_LOG(__func__ << " kernel error "
                           << (char *)nla_data(tb_msg[NLMSGERR_ATTR_MSG]));
    }

    return NL_STOP;
  }

  void send_netlink_message(struct nl_msg *msg) {
    int netlink_err = nl_send_auto(sock, msg);

    if (netlink_err < 0)
      OPENVPN_THROW(netlink_error, "netlink error on sending message: "
                                       << nl_geterror(netlink_err) << ", "
                                       << netlink_err);
  }

  NlSockPtr sock_ptr;
  NlCbPtr cb_ptr;

  struct nl_sock *sock;
  struct nl_cb *cb;

  int ovpn_dco_id;
  unsigned int ifindex;

  ReadHandler read_handler;

  bool halt;
  BufferAllocated buf;

  std::unique_ptr<openvpn_io::posix::stream_descriptor> stream;
};
} // namespace openvpn