246 lines
6.4 KiB
C
246 lines
6.4 KiB
C
// SPDX-License-Identifier: LGPL-2.1+
|
|
// Copyright (C) 2023 Andre Heider <a.heider@gmail.com>
|
|
|
|
#include <arpa/inet.h>
|
|
#include <netinet/in.h>
|
|
#include <sys/socket.h>
|
|
#include <net/if.h>
|
|
#include <netdb.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include <libubox/blobmsg.h>
|
|
#include <libubox/blobmsg_json.h>
|
|
|
|
#include <libubus.h>
|
|
|
|
#include <rpcd/plugin.h>
|
|
|
|
#include "wireguard.h"
|
|
|
|
static struct blob_buf buf;
|
|
|
|
enum {
|
|
RPC_PK_DEVICE,
|
|
__RPC_PK_MAX,
|
|
};
|
|
|
|
static const struct blobmsg_policy rpc_privatekey_policy[__RPC_PK_MAX] = {
|
|
[RPC_PK_DEVICE] = { .name = "private", .type = BLOBMSG_TYPE_STRING },
|
|
};
|
|
|
|
static void rpc_wireguard_add_endpoint(const wg_endpoint *endpoint)
|
|
{
|
|
char host[1025]; // NI_MAXHOST
|
|
char serv[32]; // NI_MAXSERV
|
|
char res[sizeof(host) + sizeof(serv) + 4];
|
|
socklen_t addr_len;
|
|
|
|
memset(res, 0, sizeof(res));
|
|
if (endpoint->addr.sa_family == AF_INET)
|
|
addr_len = sizeof(struct sockaddr_in);
|
|
else if (endpoint->addr.sa_family == AF_INET6)
|
|
addr_len = sizeof(struct sockaddr_in6);
|
|
else
|
|
return;
|
|
|
|
if (getnameinfo(&endpoint->addr, addr_len, host, sizeof(host), serv, sizeof(serv),
|
|
NI_DGRAM | NI_NUMERICHOST | NI_NUMERICSERV))
|
|
return;
|
|
|
|
if (endpoint->addr.sa_family == AF_INET6 && strchr(host, ':'))
|
|
snprintf(res, sizeof(res), "[%s]:%s", host, serv);
|
|
else
|
|
snprintf(res, sizeof(res), "%s:%s", host, serv);
|
|
res[sizeof(res) - 1] = 0;
|
|
|
|
blobmsg_add_string(&buf, "endpoint", res);
|
|
}
|
|
|
|
static void rpc_wireguard_add_allowedip(const wg_allowedip *allowedip)
|
|
{
|
|
char res[INET6_ADDRSTRLEN + 4 + 1];
|
|
|
|
memset(res, 0, sizeof(res));
|
|
if (allowedip->family == AF_INET)
|
|
inet_ntop(AF_INET, &allowedip->ip4, res, INET6_ADDRSTRLEN);
|
|
else if (allowedip->family == AF_INET6)
|
|
inet_ntop(AF_INET6, &allowedip->ip6, res, INET6_ADDRSTRLEN);
|
|
else
|
|
return;
|
|
|
|
if (!res[0])
|
|
return;
|
|
|
|
sprintf(res + strlen(res), "/%u", allowedip->cidr);
|
|
res[sizeof(res) - 1] = 0;
|
|
|
|
blobmsg_add_string(&buf, NULL, res);
|
|
}
|
|
|
|
static void rpc_wireguard_add_peer(const wg_peer *peer)
|
|
{
|
|
void *c;
|
|
struct wg_allowedip *allowedip;
|
|
|
|
rpc_wireguard_add_endpoint(&peer->endpoint);
|
|
|
|
c = blobmsg_open_array(&buf, "allowed_ips");
|
|
wg_for_each_allowedip(peer, allowedip)
|
|
rpc_wireguard_add_allowedip(allowedip);
|
|
blobmsg_close_array(&buf, c);
|
|
|
|
blobmsg_add_u64(&buf, "last_handshake", peer->last_handshake_time.tv_sec);
|
|
blobmsg_add_u64(&buf, "rx_bytes", peer->rx_bytes);
|
|
blobmsg_add_u64(&buf, "tx_bytes", peer->tx_bytes);
|
|
if (peer->persistent_keepalive_interval)
|
|
blobmsg_add_u16(&buf, "persistent_keepalive_interval", peer->persistent_keepalive_interval);
|
|
}
|
|
|
|
static void rpc_wireguard_add_device(const wg_device *device)
|
|
{
|
|
void *c, *d;
|
|
wg_peer *peer;
|
|
wg_key_b64_string key;
|
|
|
|
blobmsg_add_u32(&buf, "ifindex", device->ifindex);
|
|
|
|
if (device->flags & WGDEVICE_HAS_PUBLIC_KEY) {
|
|
wg_key_to_base64(key, device->public_key);
|
|
blobmsg_add_string(&buf, "public_key", key);
|
|
}
|
|
|
|
if (device->listen_port)
|
|
blobmsg_add_u16(&buf, "listen_port", device->listen_port);
|
|
|
|
if (device->fwmark)
|
|
blobmsg_add_u32(&buf, "fwmark", device->fwmark);
|
|
|
|
c = blobmsg_open_table(&buf, "peers");
|
|
wg_for_each_peer(device, peer) {
|
|
wg_key_to_base64(key, peer->public_key);
|
|
d = blobmsg_open_table(&buf, key);
|
|
rpc_wireguard_add_peer(peer);
|
|
blobmsg_close_table(&buf, d);
|
|
}
|
|
blobmsg_close_table(&buf, c);
|
|
}
|
|
|
|
static int rpc_wireguard_status(struct ubus_context *ctx, struct ubus_object *obj,
|
|
struct ubus_request_data *req, const char *method, struct blob_attr *msg)
|
|
{
|
|
void *c;
|
|
char *device_names, *device_name;
|
|
size_t len;
|
|
|
|
device_names = wg_list_device_names();
|
|
if (!device_names)
|
|
return UBUS_STATUS_NOT_FOUND;
|
|
|
|
blob_buf_init(&buf, 0);
|
|
|
|
wg_for_each_device_name(device_names, device_name, len) {
|
|
wg_device *device;
|
|
|
|
if (wg_get_device(&device, device_name) < 0)
|
|
continue;
|
|
|
|
c = blobmsg_open_table(&buf, device_name);
|
|
rpc_wireguard_add_device(device);
|
|
blobmsg_close_table(&buf, c);
|
|
|
|
wg_free_device(device);
|
|
}
|
|
|
|
free(device_names);
|
|
|
|
ubus_send_reply(ctx, req, buf.head);
|
|
|
|
return UBUS_STATUS_OK;
|
|
}
|
|
|
|
static int rpc_wireguard_genkey(struct ubus_context *ctx, struct ubus_object *obj,
|
|
struct ubus_request_data *req, const char *method, struct blob_attr *msg)
|
|
{
|
|
wg_key private_key, public_key;
|
|
wg_key_b64_string key;
|
|
|
|
wg_generate_private_key(private_key);
|
|
wg_generate_public_key(public_key, private_key);
|
|
|
|
blob_buf_init(&buf, 0);
|
|
wg_key_to_base64(key, private_key);
|
|
blobmsg_add_string(&buf, "private", key);
|
|
wg_key_to_base64(key, public_key);
|
|
blobmsg_add_string(&buf, "public", key);
|
|
ubus_send_reply(ctx, req, buf.head);
|
|
|
|
return UBUS_STATUS_OK;
|
|
}
|
|
|
|
static int rpc_wireguard_genpsk(struct ubus_context *ctx, struct ubus_object *obj,
|
|
struct ubus_request_data *req, const char *method, struct blob_attr *msg)
|
|
{
|
|
wg_key preshared_key;
|
|
wg_key_b64_string key;
|
|
|
|
wg_generate_preshared_key(preshared_key);
|
|
|
|
blob_buf_init(&buf, 0);
|
|
wg_key_to_base64(key, preshared_key);
|
|
blobmsg_add_string(&buf, "preshared", key);
|
|
ubus_send_reply(ctx, req, buf.head);
|
|
|
|
return UBUS_STATUS_OK;
|
|
}
|
|
|
|
static int rpc_wireguard_pubkey(struct ubus_context *ctx, struct ubus_object *obj,
|
|
struct ubus_request_data *req, const char *method, struct blob_attr *msg)
|
|
{
|
|
static struct blob_attr *tb[__RPC_PK_MAX];
|
|
wg_key_b64_string key;
|
|
wg_key private_key, public_key;
|
|
|
|
blobmsg_parse(rpc_privatekey_policy, __RPC_PK_MAX, tb, blob_data(msg), blob_len(msg));
|
|
|
|
if (!tb[RPC_PK_DEVICE])
|
|
return UBUS_STATUS_INVALID_ARGUMENT;
|
|
|
|
if (wg_key_from_base64(private_key, blobmsg_get_string(tb[RPC_PK_DEVICE])))
|
|
return UBUS_STATUS_INVALID_ARGUMENT;
|
|
|
|
wg_generate_public_key(public_key, private_key);
|
|
blob_buf_init(&buf, 0);
|
|
wg_key_to_base64(key, public_key);
|
|
blobmsg_add_string(&buf, "public", key);
|
|
ubus_send_reply(ctx, req, buf.head);
|
|
|
|
return UBUS_STATUS_OK;
|
|
}
|
|
|
|
static int rpc_wireguard_api_init(const struct rpc_daemon_ops *ops, struct ubus_context *ctx)
|
|
{
|
|
static const struct ubus_method wireguard_methods[] = {
|
|
UBUS_METHOD_NOARG("status", rpc_wireguard_status),
|
|
UBUS_METHOD_NOARG("genkey", rpc_wireguard_genkey),
|
|
UBUS_METHOD_NOARG("genpsk", rpc_wireguard_genpsk),
|
|
UBUS_METHOD("pubkey", rpc_wireguard_pubkey, rpc_privatekey_policy),
|
|
};
|
|
|
|
static struct ubus_object_type wireguard_type =
|
|
UBUS_OBJECT_TYPE("rpcd-plugin-wireguard", wireguard_methods);
|
|
|
|
static struct ubus_object obj = {
|
|
.name = "wireguard",
|
|
.type = &wireguard_type,
|
|
.methods = wireguard_methods,
|
|
.n_methods = ARRAY_SIZE(wireguard_methods),
|
|
};
|
|
|
|
return ubus_add_object(ctx, &obj);
|
|
}
|
|
|
|
struct rpc_plugin rpc_plugin = {
|
|
.init = rpc_wireguard_api_init
|
|
};
|