// 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