// SPDX-License-Identifier: LGPL-2.1+ // Copyright (C) 2023 Andre Heider #include #include #include #include #include #include #include #include #include #include #include #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 };