// SPDX-License-Identifier: GPL-2.0 /* OpenVPN data channel accelerator * * Copyright (C) 2020-2025 OpenVPN, Inc. * * Author: Antonio Quartulli */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* 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 [arguments..]\n", cmd); fprintf(stderr, "where can be one of the following\n\n"); fprintf(stderr, "* new_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 : delete ovpn interface\n"); fprintf(stderr, "\tiface: ovpn interface name\n"); fprintf(stderr, "* listen [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 \n"); fprintf(stderr, "\tipv6: whether the socket should listen to the IPv6 wildcard address\n"); fprintf(stderr, "* connect [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 [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 : 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 \n"); fprintf(stderr, "* set_peer : 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 : 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 [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 : 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 [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 : 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 : 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]; 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; }