| // SPDX-License-Identifier: GPL-2.0 |
| /* OpenVPN data channel accelerator |
| * |
| * Copyright (C) 2020-2025 OpenVPN, Inc. |
| * |
| * Author: Antonio Quartulli <antonio@openvpn.net> |
| */ |
| |
| #include <stdio.h> |
| #include <inttypes.h> |
| #include <stdbool.h> |
| #include <string.h> |
| #include <errno.h> |
| #include <unistd.h> |
| #include <arpa/inet.h> |
| #include <net/if.h> |
| #include <netinet/in.h> |
| #include <time.h> |
| |
| #include <linux/ovpn.h> |
| #include <linux/types.h> |
| #include <linux/netlink.h> |
| |
| #include <netlink/socket.h> |
| #include <netlink/netlink.h> |
| #include <netlink/genl/genl.h> |
| #include <netlink/genl/family.h> |
| #include <netlink/genl/ctrl.h> |
| |
| #include <mbedtls/base64.h> |
| #include <mbedtls/error.h> |
| |
| #include <sys/socket.h> |
| |
| /* defines to make checkpatch happy */ |
| #define strscpy strncpy |
| #define __always_unused __attribute__((__unused__)) |
| |
| /* libnl < 3.5.0 does not set the NLA_F_NESTED on its own, therefore we |
| * have to explicitly do it to prevent the kernel from failing upon |
| * parsing of the message |
| */ |
| #define nla_nest_start(_msg, _type) \ |
| nla_nest_start(_msg, (_type) | NLA_F_NESTED) |
| |
| /* libnl < 3.11.0 does not implement nla_get_uint() */ |
| uint64_t ovpn_nla_get_uint(struct nlattr *attr) |
| { |
| if (nla_len(attr) == sizeof(uint32_t)) |
| return nla_get_u32(attr); |
| else |
| return nla_get_u64(attr); |
| } |
| |
| typedef int (*ovpn_nl_cb)(struct nl_msg *msg, void *arg); |
| |
| enum ovpn_key_direction { |
| KEY_DIR_IN = 0, |
| KEY_DIR_OUT, |
| }; |
| |
| #define KEY_LEN (256 / 8) |
| #define NONCE_LEN 8 |
| |
| #define PEER_ID_UNDEF 0x00FFFFFF |
| #define MAX_PEERS 10 |
| |
| struct nl_ctx { |
| struct nl_sock *nl_sock; |
| struct nl_msg *nl_msg; |
| struct nl_cb *nl_cb; |
| |
| int ovpn_dco_id; |
| }; |
| |
| enum ovpn_cmd { |
| CMD_INVALID, |
| CMD_NEW_IFACE, |
| CMD_DEL_IFACE, |
| CMD_LISTEN, |
| CMD_CONNECT, |
| CMD_NEW_PEER, |
| CMD_NEW_MULTI_PEER, |
| CMD_SET_PEER, |
| CMD_DEL_PEER, |
| CMD_GET_PEER, |
| CMD_NEW_KEY, |
| CMD_DEL_KEY, |
| CMD_GET_KEY, |
| CMD_SWAP_KEYS, |
| CMD_LISTEN_MCAST, |
| }; |
| |
| struct ovpn_ctx { |
| enum ovpn_cmd cmd; |
| |
| __u8 key_enc[KEY_LEN]; |
| __u8 key_dec[KEY_LEN]; |
| __u8 nonce[NONCE_LEN]; |
| |
| enum ovpn_cipher_alg cipher; |
| |
| sa_family_t sa_family; |
| |
| unsigned long peer_id; |
| unsigned long lport; |
| |
| union { |
| struct sockaddr_in in4; |
| struct sockaddr_in6 in6; |
| } remote; |
| |
| union { |
| struct sockaddr_in in4; |
| struct sockaddr_in6 in6; |
| } peer_ip; |
| |
| bool peer_ip_set; |
| |
| unsigned int ifindex; |
| char ifname[IFNAMSIZ]; |
| enum ovpn_mode mode; |
| bool mode_set; |
| |
| int socket; |
| int cli_sockets[MAX_PEERS]; |
| |
| __u32 keepalive_interval; |
| __u32 keepalive_timeout; |
| |
| enum ovpn_key_direction key_dir; |
| enum ovpn_key_slot key_slot; |
| int key_id; |
| |
| const char *peers_file; |
| }; |
| |
| static int ovpn_nl_recvmsgs(struct nl_ctx *ctx) |
| { |
| int ret; |
| |
| ret = nl_recvmsgs(ctx->nl_sock, ctx->nl_cb); |
| |
| switch (ret) { |
| case -NLE_INTR: |
| fprintf(stderr, |
| "netlink received interrupt due to signal - ignoring\n"); |
| break; |
| case -NLE_NOMEM: |
| fprintf(stderr, "netlink out of memory error\n"); |
| break; |
| case -NLE_AGAIN: |
| fprintf(stderr, |
| "netlink reports blocking read - aborting wait\n"); |
| break; |
| default: |
| if (ret) |
| fprintf(stderr, "netlink reports error (%d): %s\n", |
| ret, nl_geterror(-ret)); |
| break; |
| } |
| |
| return ret; |
| } |
| |
| static struct nl_ctx *nl_ctx_alloc_flags(struct ovpn_ctx *ovpn, int cmd, |
| int flags) |
| { |
| struct nl_ctx *ctx; |
| int err, ret; |
| |
| ctx = calloc(1, sizeof(*ctx)); |
| if (!ctx) |
| return NULL; |
| |
| ctx->nl_sock = nl_socket_alloc(); |
| if (!ctx->nl_sock) { |
| fprintf(stderr, "cannot allocate netlink socket\n"); |
| goto err_free; |
| } |
| |
| nl_socket_set_buffer_size(ctx->nl_sock, 8192, 8192); |
| |
| ret = genl_connect(ctx->nl_sock); |
| if (ret) { |
| fprintf(stderr, "cannot connect to generic netlink: %s\n", |
| nl_geterror(ret)); |
| goto err_sock; |
| } |
| |
| /* enable Extended ACK for detailed error reporting */ |
| err = 1; |
| setsockopt(nl_socket_get_fd(ctx->nl_sock), SOL_NETLINK, NETLINK_EXT_ACK, |
| &err, sizeof(err)); |
| |
| ctx->ovpn_dco_id = genl_ctrl_resolve(ctx->nl_sock, OVPN_FAMILY_NAME); |
| if (ctx->ovpn_dco_id < 0) { |
| fprintf(stderr, "cannot find ovpn_dco netlink component: %d\n", |
| ctx->ovpn_dco_id); |
| goto err_free; |
| } |
| |
| ctx->nl_msg = nlmsg_alloc(); |
| if (!ctx->nl_msg) { |
| fprintf(stderr, "cannot allocate netlink message\n"); |
| goto err_sock; |
| } |
| |
| ctx->nl_cb = nl_cb_alloc(NL_CB_DEFAULT); |
| if (!ctx->nl_cb) { |
| fprintf(stderr, "failed to allocate netlink callback\n"); |
| goto err_msg; |
| } |
| |
| nl_socket_set_cb(ctx->nl_sock, ctx->nl_cb); |
| |
| genlmsg_put(ctx->nl_msg, 0, 0, ctx->ovpn_dco_id, 0, flags, cmd, 0); |
| |
| if (ovpn->ifindex > 0) |
| NLA_PUT_U32(ctx->nl_msg, OVPN_A_IFINDEX, ovpn->ifindex); |
| |
| return ctx; |
| nla_put_failure: |
| err_msg: |
| nlmsg_free(ctx->nl_msg); |
| err_sock: |
| nl_socket_free(ctx->nl_sock); |
| err_free: |
| free(ctx); |
| return NULL; |
| } |
| |
| static struct nl_ctx *nl_ctx_alloc(struct ovpn_ctx *ovpn, int cmd) |
| { |
| return nl_ctx_alloc_flags(ovpn, cmd, 0); |
| } |
| |
| static void nl_ctx_free(struct nl_ctx *ctx) |
| { |
| if (!ctx) |
| return; |
| |
| nl_socket_free(ctx->nl_sock); |
| nlmsg_free(ctx->nl_msg); |
| nl_cb_put(ctx->nl_cb); |
| free(ctx); |
| } |
| |
| static int ovpn_nl_cb_error(struct sockaddr_nl (*nla)__always_unused, |
| 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 = 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 = (void *)((uint8_t *)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])); |
| fprintf(stderr, "kernel error: %*s\n", len, |
| (char *)nla_data(tb_msg[NLMSGERR_ATTR_MSG])); |
| } |
| |
| if (tb_msg[NLMSGERR_ATTR_MISS_NEST]) { |
| fprintf(stderr, "missing required nesting type %u\n", |
| nla_get_u32(tb_msg[NLMSGERR_ATTR_MISS_NEST])); |
| } |
| |
| if (tb_msg[NLMSGERR_ATTR_MISS_TYPE]) { |
| fprintf(stderr, "missing required attribute type %u\n", |
| nla_get_u32(tb_msg[NLMSGERR_ATTR_MISS_TYPE])); |
| } |
| |
| return NL_STOP; |
| } |
| |
| static int ovpn_nl_cb_finish(struct nl_msg (*msg)__always_unused, |
| void *arg) |
| { |
| int *status = arg; |
| |
| *status = 0; |
| return NL_SKIP; |
| } |
| |
| static int ovpn_nl_cb_ack(struct nl_msg (*msg)__always_unused, |
| void *arg) |
| { |
| int *status = arg; |
| |
| *status = 0; |
| return NL_STOP; |
| } |
| |
| static int ovpn_nl_msg_send(struct nl_ctx *ctx, ovpn_nl_cb cb) |
| { |
| int status = 1; |
| |
| nl_cb_err(ctx->nl_cb, NL_CB_CUSTOM, ovpn_nl_cb_error, &status); |
| nl_cb_set(ctx->nl_cb, NL_CB_FINISH, NL_CB_CUSTOM, ovpn_nl_cb_finish, |
| &status); |
| nl_cb_set(ctx->nl_cb, NL_CB_ACK, NL_CB_CUSTOM, ovpn_nl_cb_ack, &status); |
| |
| if (cb) |
| nl_cb_set(ctx->nl_cb, NL_CB_VALID, NL_CB_CUSTOM, cb, ctx); |
| |
| nl_send_auto_complete(ctx->nl_sock, ctx->nl_msg); |
| |
| while (status == 1) |
| ovpn_nl_recvmsgs(ctx); |
| |
| if (status < 0) |
| fprintf(stderr, "failed to send netlink message: %s (%d)\n", |
| strerror(-status), status); |
| |
| return status; |
| } |
| |
| static int ovpn_parse_key(const char *file, struct ovpn_ctx *ctx) |
| { |
| int idx_enc, idx_dec, ret = -1; |
| unsigned char *ckey = NULL; |
| __u8 *bkey = NULL; |
| size_t olen = 0; |
| long ckey_len; |
| FILE *fp; |
| |
| fp = fopen(file, "r"); |
| if (!fp) { |
| fprintf(stderr, "cannot open: %s\n", file); |
| return -1; |
| } |
| |
| /* get file size */ |
| fseek(fp, 0L, SEEK_END); |
| ckey_len = ftell(fp); |
| rewind(fp); |
| |
| /* if the file is longer, let's just read a portion */ |
| if (ckey_len > 256) |
| ckey_len = 256; |
| |
| ckey = malloc(ckey_len); |
| if (!ckey) |
| goto err; |
| |
| ret = fread(ckey, 1, ckey_len, fp); |
| if (ret != ckey_len) { |
| fprintf(stderr, |
| "couldn't read enough data from key file: %dbytes read\n", |
| ret); |
| goto err; |
| } |
| |
| olen = 0; |
| ret = mbedtls_base64_decode(NULL, 0, &olen, ckey, ckey_len); |
| if (ret != MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL) { |
| char buf[256]; |
| |
| mbedtls_strerror(ret, buf, sizeof(buf)); |
| fprintf(stderr, "unexpected base64 error1: %s (%d)\n", buf, |
| ret); |
| |
| goto err; |
| } |
| |
| bkey = malloc(olen); |
| if (!bkey) { |
| fprintf(stderr, "cannot allocate binary key buffer\n"); |
| goto err; |
| } |
| |
| ret = mbedtls_base64_decode(bkey, olen, &olen, ckey, ckey_len); |
| if (ret) { |
| char buf[256]; |
| |
| mbedtls_strerror(ret, buf, sizeof(buf)); |
| fprintf(stderr, "unexpected base64 error2: %s (%d)\n", buf, |
| ret); |
| |
| goto err; |
| } |
| |
| if (olen < 2 * KEY_LEN + NONCE_LEN) { |
| fprintf(stderr, |
| "not enough data in key file, found %zdB but needs %dB\n", |
| olen, 2 * KEY_LEN + NONCE_LEN); |
| goto err; |
| } |
| |
| switch (ctx->key_dir) { |
| case KEY_DIR_IN: |
| idx_enc = 0; |
| idx_dec = 1; |
| break; |
| case KEY_DIR_OUT: |
| idx_enc = 1; |
| idx_dec = 0; |
| break; |
| default: |
| goto err; |
| } |
| |
| memcpy(ctx->key_enc, bkey + KEY_LEN * idx_enc, KEY_LEN); |
| memcpy(ctx->key_dec, bkey + KEY_LEN * idx_dec, KEY_LEN); |
| memcpy(ctx->nonce, bkey + 2 * KEY_LEN, NONCE_LEN); |
| |
| ret = 0; |
| |
| err: |
| fclose(fp); |
| free(bkey); |
| free(ckey); |
| |
| return ret; |
| } |
| |
| static int ovpn_parse_cipher(const char *cipher, struct ovpn_ctx *ctx) |
| { |
| if (strcmp(cipher, "aes") == 0) |
| ctx->cipher = OVPN_CIPHER_ALG_AES_GCM; |
| else if (strcmp(cipher, "chachapoly") == 0) |
| ctx->cipher = OVPN_CIPHER_ALG_CHACHA20_POLY1305; |
| else if (strcmp(cipher, "none") == 0) |
| ctx->cipher = OVPN_CIPHER_ALG_NONE; |
| else |
| return -ENOTSUP; |
| |
| return 0; |
| } |
| |
| static int ovpn_parse_key_direction(const char *dir, struct ovpn_ctx *ctx) |
| { |
| int in_dir; |
| |
| in_dir = strtoll(dir, NULL, 10); |
| switch (in_dir) { |
| case KEY_DIR_IN: |
| case KEY_DIR_OUT: |
| ctx->key_dir = in_dir; |
| break; |
| default: |
| fprintf(stderr, |
| "invalid key direction provided. Can be 0 or 1 only\n"); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| static int ovpn_socket(struct ovpn_ctx *ctx, sa_family_t family, int proto) |
| { |
| struct sockaddr_storage local_sock = { 0 }; |
| struct sockaddr_in6 *in6; |
| struct sockaddr_in *in; |
| int ret, s, sock_type; |
| size_t sock_len; |
| |
| if (proto == IPPROTO_UDP) |
| sock_type = SOCK_DGRAM; |
| else if (proto == IPPROTO_TCP) |
| sock_type = SOCK_STREAM; |
| else |
| return -EINVAL; |
| |
| s = socket(family, sock_type, 0); |
| if (s < 0) { |
| perror("cannot create socket"); |
| return -1; |
| } |
| |
| switch (family) { |
| case AF_INET: |
| in = (struct sockaddr_in *)&local_sock; |
| in->sin_family = family; |
| in->sin_port = htons(ctx->lport); |
| in->sin_addr.s_addr = htonl(INADDR_ANY); |
| sock_len = sizeof(*in); |
| break; |
| case AF_INET6: |
| in6 = (struct sockaddr_in6 *)&local_sock; |
| in6->sin6_family = family; |
| in6->sin6_port = htons(ctx->lport); |
| in6->sin6_addr = in6addr_any; |
| sock_len = sizeof(*in6); |
| break; |
| default: |
| return -1; |
| } |
| |
| int opt = 1; |
| |
| ret = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); |
| |
| if (ret < 0) { |
| perror("setsockopt for SO_REUSEADDR"); |
| return ret; |
| } |
| |
| ret = setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt)); |
| if (ret < 0) { |
| perror("setsockopt for SO_REUSEPORT"); |
| return ret; |
| } |
| |
| if (family == AF_INET6) { |
| opt = 0; |
| if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &opt, |
| sizeof(opt))) { |
| perror("failed to set IPV6_V6ONLY"); |
| return -1; |
| } |
| } |
| |
| ret = bind(s, (struct sockaddr *)&local_sock, sock_len); |
| if (ret < 0) { |
| perror("cannot bind socket"); |
| goto err_socket; |
| } |
| |
| ctx->socket = s; |
| ctx->sa_family = family; |
| return 0; |
| |
| err_socket: |
| close(s); |
| return -1; |
| } |
| |
| static int ovpn_udp_socket(struct ovpn_ctx *ctx, sa_family_t family) |
| { |
| return ovpn_socket(ctx, family, IPPROTO_UDP); |
| } |
| |
| static int ovpn_listen(struct ovpn_ctx *ctx, sa_family_t family) |
| { |
| int ret; |
| |
| ret = ovpn_socket(ctx, family, IPPROTO_TCP); |
| if (ret < 0) |
| return ret; |
| |
| ret = listen(ctx->socket, 10); |
| if (ret < 0) { |
| perror("listen"); |
| close(ctx->socket); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| static int ovpn_accept(struct ovpn_ctx *ctx) |
| { |
| socklen_t socklen; |
| int ret; |
| |
| socklen = sizeof(ctx->remote); |
| ret = accept(ctx->socket, (struct sockaddr *)&ctx->remote, &socklen); |
| if (ret < 0) { |
| perror("accept"); |
| goto err; |
| } |
| |
| fprintf(stderr, "Connection received!\n"); |
| |
| switch (socklen) { |
| case sizeof(struct sockaddr_in): |
| case sizeof(struct sockaddr_in6): |
| break; |
| default: |
| fprintf(stderr, "error: expecting IPv4 or IPv6 connection\n"); |
| close(ret); |
| ret = -EINVAL; |
| goto err; |
| } |
| |
| return ret; |
| err: |
| close(ctx->socket); |
| return ret; |
| } |
| |
| static int ovpn_connect(struct ovpn_ctx *ovpn) |
| { |
| socklen_t socklen; |
| int s, ret; |
| |
| s = socket(ovpn->remote.in4.sin_family, SOCK_STREAM, 0); |
| if (s < 0) { |
| perror("cannot create socket"); |
| return -1; |
| } |
| |
| switch (ovpn->remote.in4.sin_family) { |
| case AF_INET: |
| socklen = sizeof(struct sockaddr_in); |
| break; |
| case AF_INET6: |
| socklen = sizeof(struct sockaddr_in6); |
| break; |
| default: |
| return -EOPNOTSUPP; |
| } |
| |
| ret = connect(s, (struct sockaddr *)&ovpn->remote, socklen); |
| if (ret < 0) { |
| perror("connect"); |
| goto err; |
| } |
| |
| fprintf(stderr, "connected\n"); |
| |
| ovpn->socket = s; |
| |
| return 0; |
| err: |
| close(s); |
| return ret; |
| } |
| |
| static int ovpn_new_peer(struct ovpn_ctx *ovpn, bool is_tcp) |
| { |
| struct nlattr *attr; |
| struct nl_ctx *ctx; |
| int ret = -1; |
| |
| ctx = nl_ctx_alloc(ovpn, OVPN_CMD_PEER_NEW); |
| if (!ctx) |
| return -ENOMEM; |
| |
| attr = nla_nest_start(ctx->nl_msg, OVPN_A_PEER); |
| NLA_PUT_U32(ctx->nl_msg, OVPN_A_PEER_ID, ovpn->peer_id); |
| NLA_PUT_U32(ctx->nl_msg, OVPN_A_PEER_SOCKET, ovpn->socket); |
| |
| if (!is_tcp) { |
| switch (ovpn->remote.in4.sin_family) { |
| case AF_INET: |
| NLA_PUT_U32(ctx->nl_msg, OVPN_A_PEER_REMOTE_IPV4, |
| ovpn->remote.in4.sin_addr.s_addr); |
| NLA_PUT_U16(ctx->nl_msg, OVPN_A_PEER_REMOTE_PORT, |
| ovpn->remote.in4.sin_port); |
| break; |
| case AF_INET6: |
| NLA_PUT(ctx->nl_msg, OVPN_A_PEER_REMOTE_IPV6, |
| sizeof(ovpn->remote.in6.sin6_addr), |
| &ovpn->remote.in6.sin6_addr); |
| NLA_PUT_U32(ctx->nl_msg, |
| OVPN_A_PEER_REMOTE_IPV6_SCOPE_ID, |
| ovpn->remote.in6.sin6_scope_id); |
| NLA_PUT_U16(ctx->nl_msg, OVPN_A_PEER_REMOTE_PORT, |
| ovpn->remote.in6.sin6_port); |
| break; |
| default: |
| fprintf(stderr, |
| "Invalid family for remote socket address\n"); |
| goto nla_put_failure; |
| } |
| } |
| |
| if (ovpn->peer_ip_set) { |
| switch (ovpn->peer_ip.in4.sin_family) { |
| case AF_INET: |
| NLA_PUT_U32(ctx->nl_msg, OVPN_A_PEER_VPN_IPV4, |
| ovpn->peer_ip.in4.sin_addr.s_addr); |
| break; |
| case AF_INET6: |
| NLA_PUT(ctx->nl_msg, OVPN_A_PEER_VPN_IPV6, |
| sizeof(struct in6_addr), |
| &ovpn->peer_ip.in6.sin6_addr); |
| break; |
| default: |
| fprintf(stderr, "Invalid family for peer address\n"); |
| goto nla_put_failure; |
| } |
| } |
| |
| nla_nest_end(ctx->nl_msg, attr); |
| |
| ret = ovpn_nl_msg_send(ctx, NULL); |
| nla_put_failure: |
| nl_ctx_free(ctx); |
| return ret; |
| } |
| |
| static int ovpn_set_peer(struct ovpn_ctx *ovpn) |
| { |
| struct nlattr *attr; |
| struct nl_ctx *ctx; |
| int ret = -1; |
| |
| ctx = nl_ctx_alloc(ovpn, OVPN_CMD_PEER_SET); |
| if (!ctx) |
| return -ENOMEM; |
| |
| attr = nla_nest_start(ctx->nl_msg, OVPN_A_PEER); |
| NLA_PUT_U32(ctx->nl_msg, OVPN_A_PEER_ID, ovpn->peer_id); |
| NLA_PUT_U32(ctx->nl_msg, OVPN_A_PEER_KEEPALIVE_INTERVAL, |
| ovpn->keepalive_interval); |
| NLA_PUT_U32(ctx->nl_msg, OVPN_A_PEER_KEEPALIVE_TIMEOUT, |
| ovpn->keepalive_timeout); |
| nla_nest_end(ctx->nl_msg, attr); |
| |
| ret = ovpn_nl_msg_send(ctx, NULL); |
| nla_put_failure: |
| nl_ctx_free(ctx); |
| return ret; |
| } |
| |
| static int ovpn_del_peer(struct ovpn_ctx *ovpn) |
| { |
| struct nlattr *attr; |
| struct nl_ctx *ctx; |
| int ret = -1; |
| |
| ctx = nl_ctx_alloc(ovpn, OVPN_CMD_PEER_DEL); |
| if (!ctx) |
| return -ENOMEM; |
| |
| attr = nla_nest_start(ctx->nl_msg, OVPN_A_PEER); |
| NLA_PUT_U32(ctx->nl_msg, OVPN_A_PEER_ID, ovpn->peer_id); |
| nla_nest_end(ctx->nl_msg, attr); |
| |
| ret = ovpn_nl_msg_send(ctx, NULL); |
| nla_put_failure: |
| nl_ctx_free(ctx); |
| return ret; |
| } |
| |
| static int ovpn_handle_peer(struct nl_msg *msg, void (*arg)__always_unused) |
| { |
| struct nlattr *pattrs[OVPN_A_PEER_MAX + 1]; |
| struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); |
| struct nlattr *attrs[OVPN_A_MAX + 1]; |
| __u16 rport = 0, lport = 0; |
| |
| nla_parse(attrs, OVPN_A_MAX, genlmsg_attrdata(gnlh, 0), |
| genlmsg_attrlen(gnlh, 0), NULL); |
| |
| if (!attrs[OVPN_A_PEER]) { |
| fprintf(stderr, "no packet content in netlink message\n"); |
| return NL_SKIP; |
| } |
| |
| nla_parse(pattrs, OVPN_A_PEER_MAX, nla_data(attrs[OVPN_A_PEER]), |
| nla_len(attrs[OVPN_A_PEER]), NULL); |
| |
| if (pattrs[OVPN_A_PEER_ID]) |
| fprintf(stderr, "* Peer %u\n", |
| nla_get_u32(pattrs[OVPN_A_PEER_ID])); |
| |
| if (pattrs[OVPN_A_PEER_SOCKET_NETNSID]) |
| fprintf(stderr, "\tsocket NetNS ID: %d\n", |
| nla_get_s32(pattrs[OVPN_A_PEER_SOCKET_NETNSID])); |
| |
| if (pattrs[OVPN_A_PEER_VPN_IPV4]) { |
| char buf[INET_ADDRSTRLEN]; |
| |
| inet_ntop(AF_INET, nla_data(pattrs[OVPN_A_PEER_VPN_IPV4]), |
| buf, sizeof(buf)); |
| fprintf(stderr, "\tVPN IPv4: %s\n", buf); |
| } |
| |
| if (pattrs[OVPN_A_PEER_VPN_IPV6]) { |
| char buf[INET6_ADDRSTRLEN]; |
| |
| inet_ntop(AF_INET6, nla_data(pattrs[OVPN_A_PEER_VPN_IPV6]), |
| buf, sizeof(buf)); |
| fprintf(stderr, "\tVPN IPv6: %s\n", buf); |
| } |
| |
| if (pattrs[OVPN_A_PEER_LOCAL_PORT]) |
| lport = ntohs(nla_get_u16(pattrs[OVPN_A_PEER_LOCAL_PORT])); |
| |
| if (pattrs[OVPN_A_PEER_REMOTE_PORT]) |
| rport = ntohs(nla_get_u16(pattrs[OVPN_A_PEER_REMOTE_PORT])); |
| |
| if (pattrs[OVPN_A_PEER_REMOTE_IPV6]) { |
| void *ip = pattrs[OVPN_A_PEER_REMOTE_IPV6]; |
| char buf[INET6_ADDRSTRLEN]; |
| int scope_id = -1; |
| |
| if (pattrs[OVPN_A_PEER_REMOTE_IPV6_SCOPE_ID]) { |
| void *p = pattrs[OVPN_A_PEER_REMOTE_IPV6_SCOPE_ID]; |
| |
| scope_id = nla_get_u32(p); |
| } |
| |
| inet_ntop(AF_INET6, nla_data(ip), buf, sizeof(buf)); |
| fprintf(stderr, "\tRemote: %s:%hu (scope-id: %u)\n", buf, rport, |
| scope_id); |
| |
| if (pattrs[OVPN_A_PEER_LOCAL_IPV6]) { |
| void *ip = pattrs[OVPN_A_PEER_LOCAL_IPV6]; |
| |
| inet_ntop(AF_INET6, nla_data(ip), buf, sizeof(buf)); |
| fprintf(stderr, "\tLocal: %s:%hu\n", buf, lport); |
| } |
| } |
| |
| if (pattrs[OVPN_A_PEER_REMOTE_IPV4]) { |
| void *ip = pattrs[OVPN_A_PEER_REMOTE_IPV4]; |
| char buf[INET_ADDRSTRLEN]; |
| |
| inet_ntop(AF_INET, nla_data(ip), buf, sizeof(buf)); |
| fprintf(stderr, "\tRemote: %s:%hu\n", buf, rport); |
| |
| if (pattrs[OVPN_A_PEER_LOCAL_IPV4]) { |
| void *p = pattrs[OVPN_A_PEER_LOCAL_IPV4]; |
| |
| inet_ntop(AF_INET, nla_data(p), buf, sizeof(buf)); |
| fprintf(stderr, "\tLocal: %s:%hu\n", buf, lport); |
| } |
| } |
| |
| if (pattrs[OVPN_A_PEER_KEEPALIVE_INTERVAL]) { |
| void *p = pattrs[OVPN_A_PEER_KEEPALIVE_INTERVAL]; |
| |
| fprintf(stderr, "\tKeepalive interval: %u sec\n", |
| nla_get_u32(p)); |
| } |
| |
| if (pattrs[OVPN_A_PEER_KEEPALIVE_TIMEOUT]) |
| fprintf(stderr, "\tKeepalive timeout: %u sec\n", |
| nla_get_u32(pattrs[OVPN_A_PEER_KEEPALIVE_TIMEOUT])); |
| |
| if (pattrs[OVPN_A_PEER_VPN_RX_BYTES]) |
| fprintf(stderr, "\tVPN RX bytes: %" PRIu64 "\n", |
| ovpn_nla_get_uint(pattrs[OVPN_A_PEER_VPN_RX_BYTES])); |
| |
| if (pattrs[OVPN_A_PEER_VPN_TX_BYTES]) |
| fprintf(stderr, "\tVPN TX bytes: %" PRIu64 "\n", |
| ovpn_nla_get_uint(pattrs[OVPN_A_PEER_VPN_TX_BYTES])); |
| |
| if (pattrs[OVPN_A_PEER_VPN_RX_PACKETS]) |
| fprintf(stderr, "\tVPN RX packets: %" PRIu64 "\n", |
| ovpn_nla_get_uint(pattrs[OVPN_A_PEER_VPN_RX_PACKETS])); |
| |
| if (pattrs[OVPN_A_PEER_VPN_TX_PACKETS]) |
| fprintf(stderr, "\tVPN TX packets: %" PRIu64 "\n", |
| ovpn_nla_get_uint(pattrs[OVPN_A_PEER_VPN_TX_PACKETS])); |
| |
| if (pattrs[OVPN_A_PEER_LINK_RX_BYTES]) |
| fprintf(stderr, "\tLINK RX bytes: %" PRIu64 "\n", |
| ovpn_nla_get_uint(pattrs[OVPN_A_PEER_LINK_RX_BYTES])); |
| |
| if (pattrs[OVPN_A_PEER_LINK_TX_BYTES]) |
| fprintf(stderr, "\tLINK TX bytes: %" PRIu64 "\n", |
| ovpn_nla_get_uint(pattrs[OVPN_A_PEER_LINK_TX_BYTES])); |
| |
| if (pattrs[OVPN_A_PEER_LINK_RX_PACKETS]) |
| fprintf(stderr, "\tLINK RX packets: %" PRIu64 "\n", |
| ovpn_nla_get_uint(pattrs[OVPN_A_PEER_LINK_RX_PACKETS])); |
| |
| if (pattrs[OVPN_A_PEER_LINK_TX_PACKETS]) |
| fprintf(stderr, "\tLINK TX packets: %" PRIu64 "\n", |
| ovpn_nla_get_uint(pattrs[OVPN_A_PEER_LINK_TX_PACKETS])); |
| |
| return NL_SKIP; |
| } |
| |
| static int ovpn_get_peer(struct ovpn_ctx *ovpn) |
| { |
| int flags = 0, ret = -1; |
| struct nlattr *attr; |
| struct nl_ctx *ctx; |
| |
| if (ovpn->peer_id == PEER_ID_UNDEF) |
| flags = NLM_F_DUMP; |
| |
| ctx = nl_ctx_alloc_flags(ovpn, OVPN_CMD_PEER_GET, flags); |
| if (!ctx) |
| return -ENOMEM; |
| |
| if (ovpn->peer_id != PEER_ID_UNDEF) { |
| attr = nla_nest_start(ctx->nl_msg, OVPN_A_PEER); |
| NLA_PUT_U32(ctx->nl_msg, OVPN_A_PEER_ID, ovpn->peer_id); |
| nla_nest_end(ctx->nl_msg, attr); |
| } |
| |
| ret = ovpn_nl_msg_send(ctx, ovpn_handle_peer); |
| nla_put_failure: |
| nl_ctx_free(ctx); |
| return ret; |
| } |
| |
| static int ovpn_new_key(struct ovpn_ctx *ovpn) |
| { |
| struct nlattr *keyconf, *key_dir; |
| struct nl_ctx *ctx; |
| int ret = -1; |
| |
| ctx = nl_ctx_alloc(ovpn, OVPN_CMD_KEY_NEW); |
| if (!ctx) |
| return -ENOMEM; |
| |
| keyconf = nla_nest_start(ctx->nl_msg, OVPN_A_KEYCONF); |
| NLA_PUT_U32(ctx->nl_msg, OVPN_A_KEYCONF_PEER_ID, ovpn->peer_id); |
| NLA_PUT_U32(ctx->nl_msg, OVPN_A_KEYCONF_SLOT, ovpn->key_slot); |
| NLA_PUT_U32(ctx->nl_msg, OVPN_A_KEYCONF_KEY_ID, ovpn->key_id); |
| NLA_PUT_U32(ctx->nl_msg, OVPN_A_KEYCONF_CIPHER_ALG, ovpn->cipher); |
| |
| key_dir = nla_nest_start(ctx->nl_msg, OVPN_A_KEYCONF_ENCRYPT_DIR); |
| NLA_PUT(ctx->nl_msg, OVPN_A_KEYDIR_CIPHER_KEY, KEY_LEN, ovpn->key_enc); |
| NLA_PUT(ctx->nl_msg, OVPN_A_KEYDIR_NONCE_TAIL, NONCE_LEN, ovpn->nonce); |
| nla_nest_end(ctx->nl_msg, key_dir); |
| |
| key_dir = nla_nest_start(ctx->nl_msg, OVPN_A_KEYCONF_DECRYPT_DIR); |
| NLA_PUT(ctx->nl_msg, OVPN_A_KEYDIR_CIPHER_KEY, KEY_LEN, ovpn->key_dec); |
| NLA_PUT(ctx->nl_msg, OVPN_A_KEYDIR_NONCE_TAIL, NONCE_LEN, ovpn->nonce); |
| nla_nest_end(ctx->nl_msg, key_dir); |
| |
| nla_nest_end(ctx->nl_msg, keyconf); |
| |
| ret = ovpn_nl_msg_send(ctx, NULL); |
| nla_put_failure: |
| nl_ctx_free(ctx); |
| return ret; |
| } |
| |
| static int ovpn_del_key(struct ovpn_ctx *ovpn) |
| { |
| struct nlattr *keyconf; |
| struct nl_ctx *ctx; |
| int ret = -1; |
| |
| ctx = nl_ctx_alloc(ovpn, OVPN_CMD_KEY_DEL); |
| if (!ctx) |
| return -ENOMEM; |
| |
| keyconf = nla_nest_start(ctx->nl_msg, OVPN_A_KEYCONF); |
| NLA_PUT_U32(ctx->nl_msg, OVPN_A_KEYCONF_PEER_ID, ovpn->peer_id); |
| NLA_PUT_U32(ctx->nl_msg, OVPN_A_KEYCONF_SLOT, ovpn->key_slot); |
| nla_nest_end(ctx->nl_msg, keyconf); |
| |
| ret = ovpn_nl_msg_send(ctx, NULL); |
| nla_put_failure: |
| nl_ctx_free(ctx); |
| return ret; |
| } |
| |
| static int ovpn_handle_key(struct nl_msg *msg, void (*arg)__always_unused) |
| { |
| struct nlattr *kattrs[OVPN_A_KEYCONF_MAX + 1]; |
| struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); |
| struct nlattr *attrs[OVPN_A_MAX + 1]; |
| |
| nla_parse(attrs, OVPN_A_MAX, genlmsg_attrdata(gnlh, 0), |
| genlmsg_attrlen(gnlh, 0), NULL); |
| |
| if (!attrs[OVPN_A_KEYCONF]) { |
| fprintf(stderr, "no packet content in netlink message\n"); |
| return NL_SKIP; |
| } |
| |
| nla_parse(kattrs, OVPN_A_KEYCONF_MAX, nla_data(attrs[OVPN_A_KEYCONF]), |
| nla_len(attrs[OVPN_A_KEYCONF]), NULL); |
| |
| if (kattrs[OVPN_A_KEYCONF_PEER_ID]) |
| fprintf(stderr, "* Peer %u\n", |
| nla_get_u32(kattrs[OVPN_A_KEYCONF_PEER_ID])); |
| if (kattrs[OVPN_A_KEYCONF_SLOT]) { |
| fprintf(stderr, "\t- Slot: "); |
| switch (nla_get_u32(kattrs[OVPN_A_KEYCONF_SLOT])) { |
| case OVPN_KEY_SLOT_PRIMARY: |
| fprintf(stderr, "primary\n"); |
| break; |
| case OVPN_KEY_SLOT_SECONDARY: |
| fprintf(stderr, "secondary\n"); |
| break; |
| default: |
| fprintf(stderr, "invalid (%u)\n", |
| nla_get_u32(kattrs[OVPN_A_KEYCONF_SLOT])); |
| break; |
| } |
| } |
| if (kattrs[OVPN_A_KEYCONF_KEY_ID]) |
| fprintf(stderr, "\t- Key ID: %u\n", |
| nla_get_u32(kattrs[OVPN_A_KEYCONF_KEY_ID])); |
| if (kattrs[OVPN_A_KEYCONF_CIPHER_ALG]) { |
| fprintf(stderr, "\t- Cipher: "); |
| switch (nla_get_u32(kattrs[OVPN_A_KEYCONF_CIPHER_ALG])) { |
| case OVPN_CIPHER_ALG_NONE: |
| fprintf(stderr, "none\n"); |
| break; |
| case OVPN_CIPHER_ALG_AES_GCM: |
| fprintf(stderr, "aes-gcm\n"); |
| break; |
| case OVPN_CIPHER_ALG_CHACHA20_POLY1305: |
| fprintf(stderr, "chacha20poly1305\n"); |
| break; |
| default: |
| fprintf(stderr, "invalid (%u)\n", |
| nla_get_u32(kattrs[OVPN_A_KEYCONF_CIPHER_ALG])); |
| break; |
| } |
| } |
| |
| return NL_SKIP; |
| } |
| |
| static int ovpn_get_key(struct ovpn_ctx *ovpn) |
| { |
| struct nlattr *keyconf; |
| struct nl_ctx *ctx; |
| int ret = -1; |
| |
| ctx = nl_ctx_alloc(ovpn, OVPN_CMD_KEY_GET); |
| if (!ctx) |
| return -ENOMEM; |
| |
| keyconf = nla_nest_start(ctx->nl_msg, OVPN_A_KEYCONF); |
| NLA_PUT_U32(ctx->nl_msg, OVPN_A_KEYCONF_PEER_ID, ovpn->peer_id); |
| NLA_PUT_U32(ctx->nl_msg, OVPN_A_KEYCONF_SLOT, ovpn->key_slot); |
| nla_nest_end(ctx->nl_msg, keyconf); |
| |
| ret = ovpn_nl_msg_send(ctx, ovpn_handle_key); |
| nla_put_failure: |
| nl_ctx_free(ctx); |
| return ret; |
| } |
| |
| static int ovpn_swap_keys(struct ovpn_ctx *ovpn) |
| { |
| struct nl_ctx *ctx; |
| struct nlattr *kc; |
| int ret = -1; |
| |
| ctx = nl_ctx_alloc(ovpn, OVPN_CMD_KEY_SWAP); |
| if (!ctx) |
| return -ENOMEM; |
| |
| kc = nla_nest_start(ctx->nl_msg, OVPN_A_KEYCONF); |
| NLA_PUT_U32(ctx->nl_msg, OVPN_A_KEYCONF_PEER_ID, ovpn->peer_id); |
| nla_nest_end(ctx->nl_msg, kc); |
| |
| ret = ovpn_nl_msg_send(ctx, NULL); |
| nla_put_failure: |
| nl_ctx_free(ctx); |
| return ret; |
| } |
| |
| /* Helper function used to easily add attributes to a rtnl message */ |
| static int ovpn_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) { |
| fprintf(stderr, "%s: rtnl: message exceeded bound of %d\n", |
| __func__, 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; |
| } |
| |
| static struct rtattr *ovpn_nest_start(struct nlmsghdr *msg, size_t max_size, |
| int attr) |
| { |
| struct rtattr *nest = nlmsg_tail(msg); |
| |
| if (ovpn_addattr(msg, max_size, attr, NULL, 0) < 0) |
| return NULL; |
| |
| return nest; |
| } |
| |
| static void ovpn_nest_end(struct nlmsghdr *msg, struct rtattr *nest) |
| { |
| nest->rta_len = (uint8_t *)nlmsg_tail(msg) - (uint8_t *)nest; |
| } |
| |
| #define RT_SNDBUF_SIZE (1024 * 2) |
| #define RT_RCVBUF_SIZE (1024 * 4) |
| |
| /* Open RTNL socket */ |
| static int ovpn_rt_socket(void) |
| { |
| int sndbuf = RT_SNDBUF_SIZE, rcvbuf = RT_RCVBUF_SIZE, fd; |
| |
| fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); |
| if (fd < 0) { |
| fprintf(stderr, "%s: cannot open netlink socket\n", __func__); |
| return fd; |
| } |
| |
| if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, |
| sizeof(sndbuf)) < 0) { |
| fprintf(stderr, "%s: SO_SNDBUF\n", __func__); |
| close(fd); |
| return -1; |
| } |
| |
| if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, |
| sizeof(rcvbuf)) < 0) { |
| fprintf(stderr, "%s: SO_RCVBUF\n", __func__); |
| close(fd); |
| return -1; |
| } |
| |
| return fd; |
| } |
| |
| /* Bind socket to Netlink subsystem */ |
| static int ovpn_rt_bind(int fd, uint32_t groups) |
| { |
| struct sockaddr_nl local = { 0 }; |
| socklen_t addr_len; |
| |
| local.nl_family = AF_NETLINK; |
| local.nl_groups = groups; |
| |
| if (bind(fd, (struct sockaddr *)&local, sizeof(local)) < 0) { |
| fprintf(stderr, "%s: cannot bind netlink socket: %d\n", |
| __func__, errno); |
| return -errno; |
| } |
| |
| addr_len = sizeof(local); |
| if (getsockname(fd, (struct sockaddr *)&local, &addr_len) < 0) { |
| fprintf(stderr, "%s: cannot getsockname: %d\n", __func__, |
| errno); |
| return -errno; |
| } |
| |
| if (addr_len != sizeof(local)) { |
| fprintf(stderr, "%s: wrong address length %d\n", __func__, |
| addr_len); |
| return -EINVAL; |
| } |
| |
| if (local.nl_family != AF_NETLINK) { |
| fprintf(stderr, "%s: wrong address family %d\n", __func__, |
| local.nl_family); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| typedef int (*ovpn_parse_reply_cb)(struct nlmsghdr *msg, void *arg); |
| |
| /* Send Netlink message and run callback on reply (if specified) */ |
| static int ovpn_rt_send(struct nlmsghdr *payload, pid_t peer, |
| unsigned int groups, ovpn_parse_reply_cb cb, |
| void *arg_cb) |
| { |
| int len, rem_len, fd, ret, rcv_len; |
| struct sockaddr_nl nladdr = { 0 }; |
| struct nlmsgerr *err; |
| struct nlmsghdr *h; |
| 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 = time(NULL); |
| |
| /* no need to send reply */ |
| if (!cb) |
| payload->nlmsg_flags |= NLM_F_ACK; |
| |
| fd = ovpn_rt_socket(); |
| if (fd < 0) { |
| fprintf(stderr, "%s: can't open rtnl socket\n", __func__); |
| return -errno; |
| } |
| |
| ret = ovpn_rt_bind(fd, 0); |
| if (ret < 0) { |
| fprintf(stderr, "%s: can't bind rtnl socket\n", __func__); |
| ret = -errno; |
| goto out; |
| } |
| |
| ret = sendmsg(fd, &nlmsg, 0); |
| if (ret < 0) { |
| fprintf(stderr, "%s: rtnl: error on sendmsg()\n", __func__); |
| 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 |
| */ |
| iov.iov_len = sizeof(buf); |
| rcv_len = recvmsg(fd, &nlmsg, 0); |
| if (rcv_len < 0) { |
| if (errno == EINTR || errno == EAGAIN) { |
| fprintf(stderr, "%s: interrupted call\n", |
| __func__); |
| continue; |
| } |
| fprintf(stderr, "%s: rtnl: error on recvmsg()\n", |
| __func__); |
| ret = -errno; |
| goto out; |
| } |
| |
| if (rcv_len == 0) { |
| fprintf(stderr, |
| "%s: rtnl: socket reached unexpected EOF\n", |
| __func__); |
| ret = -EIO; |
| goto out; |
| } |
| |
| if (nlmsg.msg_namelen != sizeof(nladdr)) { |
| fprintf(stderr, |
| "%s: sender address length: %u (expected %zu)\n", |
| __func__, nlmsg.msg_namelen, 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) { |
| fprintf(stderr, "%s: truncated message\n", |
| __func__); |
| ret = -EIO; |
| goto out; |
| } |
| fprintf(stderr, "%s: malformed message: len=%d\n", |
| __func__, len); |
| ret = -EIO; |
| goto out; |
| } |
| |
| if (h->nlmsg_type == NLMSG_DONE) { |
| ret = 0; |
| goto out; |
| } |
| |
| if (h->nlmsg_type == NLMSG_ERROR) { |
| err = (struct nlmsgerr *)NLMSG_DATA(h); |
| if (rem_len < (int)sizeof(struct nlmsgerr)) { |
| fprintf(stderr, "%s: ERROR truncated\n", |
| __func__); |
| ret = -EIO; |
| goto out; |
| } |
| |
| if (err->error) { |
| fprintf(stderr, "%s: (%d) %s\n", |
| __func__, err->error, |
| strerror(-err->error)); |
| ret = err->error; |
| goto out; |
| } |
| |
| ret = 0; |
| if (cb) { |
| int r = cb(h, arg_cb); |
| |
| if (r <= 0) |
| ret = r; |
| } |
| goto out; |
| } |
| |
| if (cb) { |
| int r = cb(h, arg_cb); |
| |
| if (r <= 0) { |
| ret = r; |
| goto out; |
| } |
| } else { |
| fprintf(stderr, "%s: RTNL: unexpected reply\n", |
| __func__); |
| } |
| |
| rcv_len -= NLMSG_ALIGN(len); |
| h = (struct nlmsghdr *)((uint8_t *)h + |
| NLMSG_ALIGN(len)); |
| } |
| |
| if (nlmsg.msg_flags & MSG_TRUNC) { |
| fprintf(stderr, "%s: message truncated\n", __func__); |
| continue; |
| } |
| |
| if (rcv_len) { |
| fprintf(stderr, "%s: rtnl: %d not parsed bytes\n", |
| __func__, rcv_len); |
| ret = -1; |
| goto out; |
| } |
| } |
| out: |
| close(fd); |
| |
| return ret; |
| } |
| |
| struct ovpn_link_req { |
| struct nlmsghdr n; |
| struct ifinfomsg i; |
| char buf[256]; |
| }; |
| |
| static int ovpn_new_iface(struct ovpn_ctx *ovpn) |
| { |
| struct rtattr *linkinfo, *data; |
| struct ovpn_link_req req = { 0 }; |
| int ret = -1; |
| |
| fprintf(stdout, "Creating interface %s with mode %u\n", ovpn->ifname, |
| ovpn->mode); |
| |
| req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.i)); |
| req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL; |
| req.n.nlmsg_type = RTM_NEWLINK; |
| |
| if (ovpn_addattr(&req.n, sizeof(req), IFLA_IFNAME, ovpn->ifname, |
| strlen(ovpn->ifname) + 1) < 0) |
| goto err; |
| |
| linkinfo = ovpn_nest_start(&req.n, sizeof(req), IFLA_LINKINFO); |
| if (!linkinfo) |
| goto err; |
| |
| if (ovpn_addattr(&req.n, sizeof(req), IFLA_INFO_KIND, OVPN_FAMILY_NAME, |
| strlen(OVPN_FAMILY_NAME) + 1) < 0) |
| goto err; |
| |
| if (ovpn->mode_set) { |
| data = ovpn_nest_start(&req.n, sizeof(req), IFLA_INFO_DATA); |
| if (!data) |
| goto err; |
| |
| if (ovpn_addattr(&req.n, sizeof(req), IFLA_OVPN_MODE, |
| &ovpn->mode, sizeof(uint8_t)) < 0) |
| goto err; |
| |
| ovpn_nest_end(&req.n, data); |
| } |
| |
| ovpn_nest_end(&req.n, linkinfo); |
| |
| req.i.ifi_family = AF_PACKET; |
| |
| ret = ovpn_rt_send(&req.n, 0, 0, NULL, NULL); |
| err: |
| return ret; |
| } |
| |
| static int ovpn_del_iface(struct ovpn_ctx *ovpn) |
| { |
| struct ovpn_link_req req = { 0 }; |
| |
| fprintf(stdout, "Deleting interface %s ifindex %u\n", ovpn->ifname, |
| ovpn->ifindex); |
| |
| req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.i)); |
| req.n.nlmsg_flags = NLM_F_REQUEST; |
| req.n.nlmsg_type = RTM_DELLINK; |
| |
| req.i.ifi_family = AF_PACKET; |
| req.i.ifi_index = ovpn->ifindex; |
| |
| return ovpn_rt_send(&req.n, 0, 0, NULL, NULL); |
| } |
| |
| static int nl_seq_check(struct nl_msg (*msg)__always_unused, |
| void (*arg)__always_unused) |
| { |
| return NL_OK; |
| } |
| |
| struct mcast_handler_args { |
| const char *group; |
| int id; |
| }; |
| |
| static int mcast_family_handler(struct nl_msg *msg, void *arg) |
| { |
| struct mcast_handler_args *grp = arg; |
| struct nlattr *tb[CTRL_ATTR_MAX + 1]; |
| struct genlmsghdr *gnlh = nlmsg_data(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, |
| 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(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; |
| } |
| |
| static int mcast_error_handler(struct sockaddr_nl (*nla)__always_unused, |
| struct nlmsgerr *err, void *arg) |
| { |
| int *ret = arg; |
| |
| *ret = err->error; |
| return NL_STOP; |
| } |
| |
| static int mcast_ack_handler(struct nl_msg (*msg)__always_unused, void *arg) |
| { |
| int *ret = arg; |
| |
| *ret = 0; |
| return NL_STOP; |
| } |
| |
| static int ovpn_handle_msg(struct nl_msg *msg, void *arg) |
| { |
| struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); |
| struct nlattr *attrs[OVPN_A_MAX + 1]; |
| struct nlmsghdr *nlh = nlmsg_hdr(msg); |
| char ifname[IF_NAMESIZE]; |
| int *ret = arg; |
| __u32 ifindex; |
| |
| fprintf(stderr, "received message from ovpn-dco\n"); |
| |
| *ret = -1; |
| |
| if (!genlmsg_valid_hdr(nlh, 0)) { |
| fprintf(stderr, "invalid header\n"); |
| return NL_STOP; |
| } |
| |
| if (nla_parse(attrs, OVPN_A_MAX, genlmsg_attrdata(gnlh, 0), |
| genlmsg_attrlen(gnlh, 0), NULL)) { |
| fprintf(stderr, "received bogus data from ovpn-dco\n"); |
| return NL_STOP; |
| } |
| |
| if (!attrs[OVPN_A_IFINDEX]) { |
| fprintf(stderr, "no ifindex in this message\n"); |
| return NL_STOP; |
| } |
| |
| ifindex = nla_get_u32(attrs[OVPN_A_IFINDEX]); |
| if (!if_indextoname(ifindex, ifname)) { |
| fprintf(stderr, "cannot resolve ifname for ifindex: %u\n", |
| ifindex); |
| return NL_STOP; |
| } |
| |
| switch (gnlh->cmd) { |
| case OVPN_CMD_PEER_DEL_NTF: |
| fprintf(stdout, "received CMD_PEER_DEL_NTF\n"); |
| break; |
| case OVPN_CMD_KEY_SWAP_NTF: |
| fprintf(stdout, "received CMD_KEY_SWAP_NTF\n"); |
| break; |
| default: |
| fprintf(stderr, "received unknown command: %d\n", gnlh->cmd); |
| return NL_STOP; |
| } |
| |
| *ret = 0; |
| return NL_OK; |
| } |
| |
| static int ovpn_get_mcast_id(struct nl_sock *sock, const char *family, |
| const char *group) |
| { |
| struct nl_msg *msg; |
| struct nl_cb *cb; |
| int ret, ctrlid; |
| struct mcast_handler_args grp = { |
| .group = group, |
| .id = -ENOENT, |
| }; |
| |
| msg = nlmsg_alloc(); |
| if (!msg) |
| return -ENOMEM; |
| |
| cb = nl_cb_alloc(NL_CB_DEFAULT); |
| if (!cb) { |
| ret = -ENOMEM; |
| goto out_fail_cb; |
| } |
| |
| ctrlid = genl_ctrl_resolve(sock, "nlctrl"); |
| |
| genlmsg_put(msg, 0, 0, ctrlid, 0, 0, CTRL_CMD_GETFAMILY, 0); |
| |
| ret = -ENOBUFS; |
| NLA_PUT_STRING(msg, CTRL_ATTR_FAMILY_NAME, family); |
| |
| ret = nl_send_auto_complete(sock, msg); |
| if (ret < 0) |
| goto nla_put_failure; |
| |
| ret = 1; |
| |
| nl_cb_err(cb, NL_CB_CUSTOM, mcast_error_handler, &ret); |
| nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, mcast_ack_handler, &ret); |
| nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, mcast_family_handler, &grp); |
| |
| while (ret > 0) |
| nl_recvmsgs(sock, cb); |
| |
| if (ret == 0) |
| ret = grp.id; |
| nla_put_failure: |
| nl_cb_put(cb); |
| out_fail_cb: |
| nlmsg_free(msg); |
| return ret; |
| } |
| |
| static int ovpn_listen_mcast(void) |
| { |
| struct nl_sock *sock; |
| struct nl_cb *cb; |
| int mcid, ret; |
| |
| sock = nl_socket_alloc(); |
| if (!sock) { |
| fprintf(stderr, "cannot allocate netlink socket\n"); |
| goto err_free; |
| } |
| |
| nl_socket_set_buffer_size(sock, 8192, 8192); |
| |
| ret = genl_connect(sock); |
| if (ret < 0) { |
| fprintf(stderr, "cannot connect to generic netlink: %s\n", |
| nl_geterror(ret)); |
| goto err_free; |
| } |
| |
| mcid = ovpn_get_mcast_id(sock, OVPN_FAMILY_NAME, OVPN_MCGRP_PEERS); |
| if (mcid < 0) { |
| fprintf(stderr, "cannot get mcast group: %s\n", |
| nl_geterror(mcid)); |
| goto err_free; |
| } |
| |
| ret = nl_socket_add_membership(sock, mcid); |
| if (ret) { |
| fprintf(stderr, "failed to join mcast group: %d\n", ret); |
| goto err_free; |
| } |
| |
| ret = 1; |
| cb = nl_cb_alloc(NL_CB_DEFAULT); |
| nl_cb_set(cb, NL_CB_SEQ_CHECK, NL_CB_CUSTOM, nl_seq_check, NULL); |
| nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, ovpn_handle_msg, &ret); |
| nl_cb_err(cb, NL_CB_CUSTOM, ovpn_nl_cb_error, &ret); |
| |
| while (ret == 1) { |
| int err = nl_recvmsgs(sock, cb); |
| |
| if (err < 0) { |
| fprintf(stderr, |
| "cannot receive netlink message: (%d) %s\n", |
| err, nl_geterror(-err)); |
| ret = -1; |
| break; |
| } |
| } |
| |
| nl_cb_put(cb); |
| err_free: |
| nl_socket_free(sock); |
| return ret; |
| } |
| |
| static void usage(const char *cmd) |
| { |
| fprintf(stderr, |
| "Usage %s <command> <iface> [arguments..]\n", |
| cmd); |
| fprintf(stderr, "where <command> can be one of the following\n\n"); |
| |
| fprintf(stderr, "* new_iface <iface> [mode]: create new ovpn interface\n"); |
| fprintf(stderr, "\tiface: ovpn interface name\n"); |
| fprintf(stderr, "\tmode:\n"); |
| fprintf(stderr, "\t\t- P2P for peer-to-peer mode (i.e. client)\n"); |
| fprintf(stderr, "\t\t- MP for multi-peer mode (i.e. server)\n"); |
| |
| fprintf(stderr, "* del_iface <iface>: delete ovpn interface\n"); |
| fprintf(stderr, "\tiface: ovpn interface name\n"); |
| |
| fprintf(stderr, |
| "* listen <iface> <lport> <peers_file> [ipv6]: listen for incoming peer TCP connections\n"); |
| fprintf(stderr, "\tiface: ovpn interface name\n"); |
| fprintf(stderr, "\tlport: TCP port to listen to\n"); |
| fprintf(stderr, |
| "\tpeers_file: file containing one peer per line: Line format:\n"); |
| fprintf(stderr, "\t\t<peer_id> <vpnaddr>\n"); |
| fprintf(stderr, |
| "\tipv6: whether the socket should listen to the IPv6 wildcard address\n"); |
| |
| fprintf(stderr, |
| "* connect <iface> <peer_id> <raddr> <rport> [key_file]: start connecting peer of TCP-based VPN session\n"); |
| fprintf(stderr, "\tiface: ovpn interface name\n"); |
| fprintf(stderr, "\tpeer_id: peer ID of the connecting peer\n"); |
| fprintf(stderr, "\traddr: peer IP address to connect to\n"); |
| fprintf(stderr, "\trport: peer TCP port to connect to\n"); |
| fprintf(stderr, |
| "\tkey_file: file containing the symmetric key for encryption\n"); |
| |
| fprintf(stderr, |
| "* new_peer <iface> <peer_id> <lport> <raddr> <rport> [vpnaddr]: add new peer\n"); |
| fprintf(stderr, "\tiface: ovpn interface name\n"); |
| fprintf(stderr, "\tlport: local UDP port to bind to\n"); |
| fprintf(stderr, |
| "\tpeer_id: peer ID to be used in data packets to/from this peer\n"); |
| fprintf(stderr, "\traddr: peer IP address\n"); |
| fprintf(stderr, "\trport: peer UDP port\n"); |
| fprintf(stderr, "\tvpnaddr: peer VPN IP\n"); |
| |
| fprintf(stderr, |
| "* new_multi_peer <iface> <lport> <peers_file>: add multiple peers as listed in the file\n"); |
| fprintf(stderr, "\tiface: ovpn interface name\n"); |
| fprintf(stderr, "\tlport: local UDP port to bind to\n"); |
| fprintf(stderr, |
| "\tpeers_file: text file containing one peer per line. Line format:\n"); |
| fprintf(stderr, "\t\t<peer_id> <raddr> <rport> <vpnaddr>\n"); |
| |
| fprintf(stderr, |
| "* set_peer <iface> <peer_id> <keepalive_interval> <keepalive_timeout>: set peer attributes\n"); |
| fprintf(stderr, "\tiface: ovpn interface name\n"); |
| fprintf(stderr, "\tpeer_id: peer ID of the peer to modify\n"); |
| fprintf(stderr, |
| "\tkeepalive_interval: interval for sending ping messages\n"); |
| fprintf(stderr, |
| "\tkeepalive_timeout: time after which a peer is timed out\n"); |
| |
| fprintf(stderr, "* del_peer <iface> <peer_id>: delete peer\n"); |
| fprintf(stderr, "\tiface: ovpn interface name\n"); |
| fprintf(stderr, "\tpeer_id: peer ID of the peer to delete\n"); |
| |
| fprintf(stderr, "* get_peer <iface> [peer_id]: retrieve peer(s) status\n"); |
| fprintf(stderr, "\tiface: ovpn interface name\n"); |
| fprintf(stderr, |
| "\tpeer_id: peer ID of the peer to query. All peers are returned if omitted\n"); |
| |
| fprintf(stderr, |
| "* new_key <iface> <peer_id> <slot> <key_id> <cipher> <key_dir> <key_file>: set data channel key\n"); |
| fprintf(stderr, "\tiface: ovpn interface name\n"); |
| fprintf(stderr, |
| "\tpeer_id: peer ID of the peer to configure the key for\n"); |
| fprintf(stderr, "\tslot: either 1 (primary) or 2 (secondary)\n"); |
| fprintf(stderr, "\tkey_id: an ID from 0 to 7\n"); |
| fprintf(stderr, |
| "\tcipher: cipher to use, supported: aes (AES-GCM), chachapoly (CHACHA20POLY1305)\n"); |
| fprintf(stderr, |
| "\tkey_dir: key direction, must 0 on one host and 1 on the other\n"); |
| fprintf(stderr, "\tkey_file: file containing the pre-shared key\n"); |
| |
| fprintf(stderr, |
| "* del_key <iface> <peer_id> [slot]: erase existing data channel key\n"); |
| fprintf(stderr, "\tiface: ovpn interface name\n"); |
| fprintf(stderr, "\tpeer_id: peer ID of the peer to modify\n"); |
| fprintf(stderr, "\tslot: slot to erase. PRIMARY if omitted\n"); |
| |
| fprintf(stderr, |
| "* get_key <iface> <peer_id> <slot>: retrieve non sensible key data\n"); |
| fprintf(stderr, "\tiface: ovpn interface name\n"); |
| fprintf(stderr, "\tpeer_id: peer ID of the peer to query\n"); |
| fprintf(stderr, "\tslot: either 1 (primary) or 2 (secondary)\n"); |
| |
| fprintf(stderr, |
| "* swap_keys <iface> <peer_id>: swap content of primary and secondary key slots\n"); |
| fprintf(stderr, "\tiface: ovpn interface name\n"); |
| fprintf(stderr, "\tpeer_id: peer ID of the peer to modify\n"); |
| |
| fprintf(stderr, |
| "* listen_mcast: listen to ovpn netlink multicast messages\n"); |
| } |
| |
| static int ovpn_parse_remote(struct ovpn_ctx *ovpn, const char *host, |
| const char *service, const char *vpnip) |
| { |
| int ret; |
| struct addrinfo *result; |
| struct addrinfo hints = { |
| .ai_family = ovpn->sa_family, |
| .ai_socktype = SOCK_DGRAM, |
| .ai_protocol = IPPROTO_UDP |
| }; |
| |
| if (host) { |
| ret = getaddrinfo(host, service, &hints, &result); |
| if (ret) { |
| fprintf(stderr, "getaddrinfo on remote error: %s\n", |
| gai_strerror(ret)); |
| return -1; |
| } |
| |
| if (!(result->ai_family == AF_INET && |
| result->ai_addrlen == sizeof(struct sockaddr_in)) && |
| !(result->ai_family == AF_INET6 && |
| result->ai_addrlen == sizeof(struct sockaddr_in6))) { |
| ret = -EINVAL; |
| goto out; |
| } |
| |
| memcpy(&ovpn->remote, result->ai_addr, result->ai_addrlen); |
| } |
| |
| if (vpnip) { |
| ret = getaddrinfo(vpnip, NULL, &hints, &result); |
| if (ret) { |
| fprintf(stderr, "getaddrinfo on vpnip error: %s\n", |
| gai_strerror(ret)); |
| return -1; |
| } |
| |
| if (!(result->ai_family == AF_INET && |
| result->ai_addrlen == sizeof(struct sockaddr_in)) && |
| !(result->ai_family == AF_INET6 && |
| result->ai_addrlen == sizeof(struct sockaddr_in6))) { |
| ret = -EINVAL; |
| goto out; |
| } |
| |
| memcpy(&ovpn->peer_ip, result->ai_addr, result->ai_addrlen); |
| ovpn->sa_family = result->ai_family; |
| |
| ovpn->peer_ip_set = true; |
| } |
| |
| ret = 0; |
| out: |
| freeaddrinfo(result); |
| return ret; |
| } |
| |
| static int ovpn_parse_new_peer(struct ovpn_ctx *ovpn, const char *peer_id, |
| const char *raddr, const char *rport, |
| const char *vpnip) |
| { |
| ovpn->peer_id = strtoul(peer_id, NULL, 10); |
| if (errno == ERANGE || ovpn->peer_id > PEER_ID_UNDEF) { |
| fprintf(stderr, "peer ID value out of range\n"); |
| return -1; |
| } |
| |
| return ovpn_parse_remote(ovpn, raddr, rport, vpnip); |
| } |
| |
| static int ovpn_parse_key_slot(const char *arg, struct ovpn_ctx *ovpn) |
| { |
| int slot = strtoul(arg, NULL, 10); |
| |
| if (errno == ERANGE || slot < 1 || slot > 2) { |
| fprintf(stderr, "key slot out of range\n"); |
| return -1; |
| } |
| |
| switch (slot) { |
| case 1: |
| ovpn->key_slot = OVPN_KEY_SLOT_PRIMARY; |
| break; |
| case 2: |
| ovpn->key_slot = OVPN_KEY_SLOT_SECONDARY; |
| break; |
| } |
| |
| return 0; |
| } |
| |
| static int ovpn_send_tcp_data(int socket) |
| { |
| uint16_t len = htons(1000); |
| uint8_t buf[1002]; |
| int ret; |
| |
| memcpy(buf, &len, sizeof(len)); |
| memset(buf + sizeof(len), 0x86, sizeof(buf) - sizeof(len)); |
| |
| ret = send(socket, buf, sizeof(buf), MSG_NOSIGNAL); |
| |
| fprintf(stdout, "Sent %u bytes over TCP socket\n", ret); |
| |
| return ret > 0 ? 0 : ret; |
| } |
| |
| static int ovpn_recv_tcp_data(int socket) |
| { |
| uint8_t buf[1002]; |
| uint16_t len; |
| int ret; |
| |
| ret = recv(socket, buf, sizeof(buf), MSG_NOSIGNAL); |
| |
| if (ret < 2) { |
| fprintf(stderr, ">>>> Error while reading TCP data: %d\n", ret); |
| return ret; |
| } |
| |
| memcpy(&len, buf, sizeof(len)); |
| len = ntohs(len); |
| |
| fprintf(stdout, ">>>> Received %u bytes over TCP socket, header: %u\n", |
| ret, len); |
| |
| return 0; |
| } |
| |
| static enum ovpn_cmd ovpn_parse_cmd(const char *cmd) |
| { |
| if (!strcmp(cmd, "new_iface")) |
| return CMD_NEW_IFACE; |
| |
| if (!strcmp(cmd, "del_iface")) |
| return CMD_DEL_IFACE; |
| |
| if (!strcmp(cmd, "listen")) |
| return CMD_LISTEN; |
| |
| if (!strcmp(cmd, "connect")) |
| return CMD_CONNECT; |
| |
| if (!strcmp(cmd, "new_peer")) |
| return CMD_NEW_PEER; |
| |
| if (!strcmp(cmd, "new_multi_peer")) |
| return CMD_NEW_MULTI_PEER; |
| |
| if (!strcmp(cmd, "set_peer")) |
| return CMD_SET_PEER; |
| |
| if (!strcmp(cmd, "del_peer")) |
| return CMD_DEL_PEER; |
| |
| if (!strcmp(cmd, "get_peer")) |
| return CMD_GET_PEER; |
| |
| if (!strcmp(cmd, "new_key")) |
| return CMD_NEW_KEY; |
| |
| if (!strcmp(cmd, "del_key")) |
| return CMD_DEL_KEY; |
| |
| if (!strcmp(cmd, "get_key")) |
| return CMD_GET_KEY; |
| |
| if (!strcmp(cmd, "swap_keys")) |
| return CMD_SWAP_KEYS; |
| |
| if (!strcmp(cmd, "listen_mcast")) |
| return CMD_LISTEN_MCAST; |
| |
| return CMD_INVALID; |
| } |
| |
| /* Send process to background and waits for signal. |
| * |
| * This helper is called at the end of commands |
| * creating sockets, so that the latter stay alive |
| * along with the process that created them. |
| * |
| * A signal is expected to be delivered in order to |
| * terminate the waiting processes |
| */ |
| static void ovpn_waitbg(void) |
| { |
| daemon(1, 1); |
| pause(); |
| } |
| |
| static int ovpn_run_cmd(struct ovpn_ctx *ovpn) |
| { |
| char peer_id[10], vpnip[INET6_ADDRSTRLEN], laddr[128], lport[10]; |
| char raddr[128], rport[10]; |
| int n, ret; |
| FILE *fp; |
| |
| switch (ovpn->cmd) { |
| case CMD_NEW_IFACE: |
| ret = ovpn_new_iface(ovpn); |
| break; |
| case CMD_DEL_IFACE: |
| ret = ovpn_del_iface(ovpn); |
| break; |
| case CMD_LISTEN: |
| ret = ovpn_listen(ovpn, ovpn->sa_family); |
| if (ret < 0) { |
| fprintf(stderr, "cannot listen on TCP socket\n"); |
| return ret; |
| } |
| |
| fp = fopen(ovpn->peers_file, "r"); |
| if (!fp) { |
| fprintf(stderr, "cannot open file: %s\n", |
| ovpn->peers_file); |
| return -1; |
| } |
| |
| int num_peers = 0; |
| |
| while ((n = fscanf(fp, "%s %s\n", peer_id, vpnip)) == 2) { |
| struct ovpn_ctx peer_ctx = { 0 }; |
| |
| if (num_peers == MAX_PEERS) { |
| fprintf(stderr, "max peers reached!\n"); |
| return -E2BIG; |
| } |
| |
| peer_ctx.ifindex = ovpn->ifindex; |
| peer_ctx.sa_family = ovpn->sa_family; |
| |
| peer_ctx.socket = ovpn_accept(ovpn); |
| if (peer_ctx.socket < 0) { |
| fprintf(stderr, "cannot accept connection!\n"); |
| return -1; |
| } |
| |
| /* store peer sockets to test TCP I/O */ |
| ovpn->cli_sockets[num_peers] = peer_ctx.socket; |
| |
| ret = ovpn_parse_new_peer(&peer_ctx, peer_id, NULL, |
| NULL, vpnip); |
| if (ret < 0) { |
| fprintf(stderr, "error while parsing line\n"); |
| return -1; |
| } |
| |
| ret = ovpn_new_peer(&peer_ctx, true); |
| if (ret < 0) { |
| fprintf(stderr, |
| "cannot add peer to VPN: %s %s\n", |
| peer_id, vpnip); |
| return ret; |
| } |
| num_peers++; |
| } |
| |
| for (int i = 0; i < num_peers; i++) { |
| ret = ovpn_recv_tcp_data(ovpn->cli_sockets[i]); |
| if (ret < 0) |
| break; |
| } |
| ovpn_waitbg(); |
| break; |
| case CMD_CONNECT: |
| ret = ovpn_connect(ovpn); |
| if (ret < 0) { |
| fprintf(stderr, "cannot connect TCP socket\n"); |
| return ret; |
| } |
| |
| ret = ovpn_new_peer(ovpn, true); |
| if (ret < 0) { |
| fprintf(stderr, "cannot add peer to VPN\n"); |
| close(ovpn->socket); |
| return ret; |
| } |
| |
| if (ovpn->cipher != OVPN_CIPHER_ALG_NONE) { |
| ret = ovpn_new_key(ovpn); |
| if (ret < 0) { |
| fprintf(stderr, "cannot set key\n"); |
| return ret; |
| } |
| } |
| |
| ret = ovpn_send_tcp_data(ovpn->socket); |
| ovpn_waitbg(); |
| break; |
| case CMD_NEW_PEER: |
| ret = ovpn_udp_socket(ovpn, AF_INET6); |
| if (ret < 0) |
| return ret; |
| |
| ret = ovpn_new_peer(ovpn, false); |
| ovpn_waitbg(); |
| break; |
| case CMD_NEW_MULTI_PEER: |
| ret = ovpn_udp_socket(ovpn, AF_INET6); |
| if (ret < 0) |
| return ret; |
| |
| fp = fopen(ovpn->peers_file, "r"); |
| if (!fp) { |
| fprintf(stderr, "cannot open file: %s\n", |
| ovpn->peers_file); |
| return -1; |
| } |
| |
| while ((n = fscanf(fp, "%s %s %s %s %s %s\n", peer_id, laddr, |
| lport, raddr, rport, vpnip)) == 6) { |
| struct ovpn_ctx peer_ctx = { 0 }; |
| |
| peer_ctx.ifindex = ovpn->ifindex; |
| peer_ctx.socket = ovpn->socket; |
| peer_ctx.sa_family = AF_UNSPEC; |
| |
| ret = ovpn_parse_new_peer(&peer_ctx, peer_id, raddr, |
| rport, vpnip); |
| if (ret < 0) { |
| fprintf(stderr, "error while parsing line\n"); |
| return -1; |
| } |
| |
| ret = ovpn_new_peer(&peer_ctx, false); |
| if (ret < 0) { |
| fprintf(stderr, |
| "cannot add peer to VPN: %s %s %s %s\n", |
| peer_id, raddr, rport, vpnip); |
| return ret; |
| } |
| } |
| ovpn_waitbg(); |
| break; |
| case CMD_SET_PEER: |
| ret = ovpn_set_peer(ovpn); |
| break; |
| case CMD_DEL_PEER: |
| ret = ovpn_del_peer(ovpn); |
| break; |
| case CMD_GET_PEER: |
| if (ovpn->peer_id == PEER_ID_UNDEF) |
| fprintf(stderr, "List of peers connected to: %s\n", |
| ovpn->ifname); |
| |
| ret = ovpn_get_peer(ovpn); |
| break; |
| case CMD_NEW_KEY: |
| ret = ovpn_new_key(ovpn); |
| break; |
| case CMD_DEL_KEY: |
| ret = ovpn_del_key(ovpn); |
| break; |
| case CMD_GET_KEY: |
| ret = ovpn_get_key(ovpn); |
| break; |
| case CMD_SWAP_KEYS: |
| ret = ovpn_swap_keys(ovpn); |
| break; |
| case CMD_LISTEN_MCAST: |
| ret = ovpn_listen_mcast(); |
| break; |
| case CMD_INVALID: |
| break; |
| } |
| |
| return ret; |
| } |
| |
| static int ovpn_parse_cmd_args(struct ovpn_ctx *ovpn, int argc, char *argv[]) |
| { |
| int ret; |
| |
| /* no args required for LISTEN_MCAST */ |
| if (ovpn->cmd == CMD_LISTEN_MCAST) |
| return 0; |
| |
| /* all commands need an ifname */ |
| if (argc < 3) |
| return -EINVAL; |
| |
| strscpy(ovpn->ifname, argv[2], IFNAMSIZ - 1); |
| ovpn->ifname[IFNAMSIZ - 1] = '\0'; |
| |
| /* all commands, except NEW_IFNAME, needs an ifindex */ |
| if (ovpn->cmd != CMD_NEW_IFACE) { |
| ovpn->ifindex = if_nametoindex(ovpn->ifname); |
| if (!ovpn->ifindex) { |
| fprintf(stderr, "cannot find interface: %s\n", |
| strerror(errno)); |
| return -1; |
| } |
| } |
| |
| switch (ovpn->cmd) { |
| case CMD_NEW_IFACE: |
| if (argc < 4) |
| break; |
| |
| if (!strcmp(argv[3], "P2P")) { |
| ovpn->mode = OVPN_MODE_P2P; |
| } else if (!strcmp(argv[3], "MP")) { |
| ovpn->mode = OVPN_MODE_MP; |
| } else { |
| fprintf(stderr, "Cannot parse iface mode: %s\n", |
| argv[3]); |
| return -1; |
| } |
| ovpn->mode_set = true; |
| break; |
| case CMD_DEL_IFACE: |
| break; |
| case CMD_LISTEN: |
| if (argc < 5) |
| return -EINVAL; |
| |
| ovpn->lport = strtoul(argv[3], NULL, 10); |
| if (errno == ERANGE || ovpn->lport > 65535) { |
| fprintf(stderr, "lport value out of range\n"); |
| return -1; |
| } |
| |
| ovpn->peers_file = argv[4]; |
| |
| ovpn->sa_family = AF_INET; |
| if (argc > 5 && !strcmp(argv[5], "ipv6")) |
| ovpn->sa_family = AF_INET6; |
| break; |
| case CMD_CONNECT: |
| if (argc < 6) |
| return -EINVAL; |
| |
| ovpn->sa_family = AF_INET; |
| |
| ret = ovpn_parse_new_peer(ovpn, argv[3], argv[4], argv[5], |
| NULL); |
| if (ret < 0) { |
| fprintf(stderr, "Cannot parse remote peer data\n"); |
| return -1; |
| } |
| |
| if (argc > 6) { |
| ovpn->key_slot = OVPN_KEY_SLOT_PRIMARY; |
| ovpn->key_id = 0; |
| ovpn->cipher = OVPN_CIPHER_ALG_AES_GCM; |
| ovpn->key_dir = KEY_DIR_OUT; |
| |
| ret = ovpn_parse_key(argv[6], ovpn); |
| if (ret) |
| return -1; |
| } |
| break; |
| case CMD_NEW_PEER: |
| if (argc < 7) |
| return -EINVAL; |
| |
| ovpn->lport = strtoul(argv[4], NULL, 10); |
| if (errno == ERANGE || ovpn->lport > 65535) { |
| fprintf(stderr, "lport value out of range\n"); |
| return -1; |
| } |
| |
| const char *vpnip = (argc > 7) ? argv[7] : NULL; |
| |
| ret = ovpn_parse_new_peer(ovpn, argv[3], argv[5], argv[6], |
| vpnip); |
| if (ret < 0) |
| return -1; |
| break; |
| case CMD_NEW_MULTI_PEER: |
| if (argc < 5) |
| return -EINVAL; |
| |
| ovpn->lport = strtoul(argv[3], NULL, 10); |
| if (errno == ERANGE || ovpn->lport > 65535) { |
| fprintf(stderr, "lport value out of range\n"); |
| return -1; |
| } |
| |
| ovpn->peers_file = argv[4]; |
| break; |
| case CMD_SET_PEER: |
| if (argc < 6) |
| return -EINVAL; |
| |
| ovpn->peer_id = strtoul(argv[3], NULL, 10); |
| if (errno == ERANGE || ovpn->peer_id > PEER_ID_UNDEF) { |
| fprintf(stderr, "peer ID value out of range\n"); |
| return -1; |
| } |
| |
| ovpn->keepalive_interval = strtoul(argv[4], NULL, 10); |
| if (errno == ERANGE) { |
| fprintf(stderr, |
| "keepalive interval value out of range\n"); |
| return -1; |
| } |
| |
| ovpn->keepalive_timeout = strtoul(argv[5], NULL, 10); |
| if (errno == ERANGE) { |
| fprintf(stderr, |
| "keepalive interval value out of range\n"); |
| return -1; |
| } |
| break; |
| case CMD_DEL_PEER: |
| if (argc < 4) |
| return -EINVAL; |
| |
| ovpn->peer_id = strtoul(argv[3], NULL, 10); |
| if (errno == ERANGE || ovpn->peer_id > PEER_ID_UNDEF) { |
| fprintf(stderr, "peer ID value out of range\n"); |
| return -1; |
| } |
| break; |
| case CMD_GET_PEER: |
| ovpn->peer_id = PEER_ID_UNDEF; |
| if (argc > 3) { |
| ovpn->peer_id = strtoul(argv[3], NULL, 10); |
| if (errno == ERANGE || ovpn->peer_id > PEER_ID_UNDEF) { |
| fprintf(stderr, "peer ID value out of range\n"); |
| return -1; |
| } |
| } |
| break; |
| case CMD_NEW_KEY: |
| if (argc < 9) |
| return -EINVAL; |
| |
| ovpn->peer_id = strtoul(argv[3], NULL, 10); |
| if (errno == ERANGE) { |
| fprintf(stderr, "peer ID value out of range\n"); |
| return -1; |
| } |
| |
| ret = ovpn_parse_key_slot(argv[4], ovpn); |
| if (ret) |
| return -1; |
| |
| ovpn->key_id = strtoul(argv[5], NULL, 10); |
| if (errno == ERANGE || ovpn->key_id > 2) { |
| fprintf(stderr, "key ID out of range\n"); |
| return -1; |
| } |
| |
| ret = ovpn_parse_cipher(argv[6], ovpn); |
| if (ret < 0) |
| return -1; |
| |
| ret = ovpn_parse_key_direction(argv[7], ovpn); |
| if (ret < 0) |
| return -1; |
| |
| ret = ovpn_parse_key(argv[8], ovpn); |
| if (ret) |
| return -1; |
| break; |
| case CMD_DEL_KEY: |
| if (argc < 4) |
| return -EINVAL; |
| |
| ovpn->peer_id = strtoul(argv[3], NULL, 10); |
| if (errno == ERANGE) { |
| fprintf(stderr, "peer ID value out of range\n"); |
| return -1; |
| } |
| |
| ret = ovpn_parse_key_slot(argv[4], ovpn); |
| if (ret) |
| return ret; |
| break; |
| case CMD_GET_KEY: |
| if (argc < 5) |
| return -EINVAL; |
| |
| ovpn->peer_id = strtoul(argv[3], NULL, 10); |
| if (errno == ERANGE) { |
| fprintf(stderr, "peer ID value out of range\n"); |
| return -1; |
| } |
| |
| ret = ovpn_parse_key_slot(argv[4], ovpn); |
| if (ret) |
| return ret; |
| break; |
| case CMD_SWAP_KEYS: |
| if (argc < 4) |
| return -EINVAL; |
| |
| ovpn->peer_id = strtoul(argv[3], NULL, 10); |
| if (errno == ERANGE) { |
| fprintf(stderr, "peer ID value out of range\n"); |
| return -1; |
| } |
| break; |
| case CMD_LISTEN_MCAST: |
| break; |
| case CMD_INVALID: |
| break; |
| } |
| |
| return 0; |
| } |
| |
| int main(int argc, char *argv[]) |
| { |
| struct ovpn_ctx ovpn; |
| int ret; |
| |
| if (argc < 2) { |
| usage(argv[0]); |
| return -1; |
| } |
| |
| memset(&ovpn, 0, sizeof(ovpn)); |
| ovpn.sa_family = AF_UNSPEC; |
| ovpn.cipher = OVPN_CIPHER_ALG_NONE; |
| |
| ovpn.cmd = ovpn_parse_cmd(argv[1]); |
| if (ovpn.cmd == CMD_INVALID) { |
| fprintf(stderr, "Error: unknown command.\n\n"); |
| usage(argv[0]); |
| return -1; |
| } |
| |
| ret = ovpn_parse_cmd_args(&ovpn, argc, argv); |
| if (ret < 0) { |
| fprintf(stderr, "Error: invalid arguments.\n\n"); |
| if (ret == -EINVAL) |
| usage(argv[0]); |
| return ret; |
| } |
| |
| ret = ovpn_run_cmd(&ovpn); |
| if (ret) |
| fprintf(stderr, "Cannot execute command: %s (%d)\n", |
| strerror(-ret), ret); |
| |
| return ret; |
| } |