mirror of
https://github.com/amnezia-vpn/OpenVPNAdapter.git
synced 2026-06-27 12:37:03 +03:00
642 lines
20 KiB
C++
642 lines
20 KiB
C++
// 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
|