This commit is contained in:
jasinco 2025-06-29 23:23:15 +08:00
commit b8c995ab9a
16 changed files with 2318 additions and 0 deletions

6
.gitignore vendored Normal file
View file

@ -0,0 +1,6 @@
perf.data
build
.cache
subprojects/oatpp-1.3.1
subprojects/packagecache

7
Dockerfile Normal file
View file

@ -0,0 +1,7 @@
FROM alpine:3.22
RUN apk update
RUN apk add nftables wireguard-tools-wg
RUN mkdir "/app"
WORKDIR /app
ENTRYPOINT ash

4
justfile Normal file
View file

@ -0,0 +1,4 @@
buildimage:
podman build --dns 8.8.8.8 -t wgcldev .
runapp:
podman run --volume ./:/app -i -t wgcldev /bin/ash

23
meson.build Normal file
View file

@ -0,0 +1,23 @@
project(
'wgcl',
['cpp','c'],
version : '0.1',
meson_version : '>= 1.3.0',
default_options : ['warning_level=3', 'cpp_std=c++23'],
)
oatpp = subproject('oatpp').get_variable('oatpp_dep')
dependencies = [
oatpp
]
exe = executable(
'wgcl',
['wgcl.cpp', './wireguard/wireguard.c', './server/server.cpp', './wireguard/wgcpp.cpp'],
install : true,
dependencies : dependencies,
include_directories : [],
)
test_wg = executable('test_wg', ['./wireguard/wgcpp.cpp', './wireguard/wireguard.c', './tests/wg_device.cpp'])
test('wg', test_wg)

View file

22
server/dto/DTOs.hpp Normal file
View file

@ -0,0 +1,22 @@
#pragma once
#include "oatpp/core/data/mapping/type/Object.hpp"
#include "oatpp/core/macro/codegen.hpp"
/* Begin DTO code-generation */
#include OATPP_CODEGEN_BEGIN(DTO)
/**
* Message Data-Transfer-Object
*/
class MessageDto : public oatpp::DTO {
DTO_INIT(MessageDto, DTO /* Extends */)
DTO_FIELD(Int32, statusCode); // Status code field
DTO_FIELD(String, message); // Message field
};
/* TODO - Add more DTOs here */
/* End DTO code-generation */
#include OATPP_CODEGEN_END(DTO)

54
server/server.cpp Normal file
View file

@ -0,0 +1,54 @@
#include "./server_components.hpp"
#include "./server.hpp"
#include "oatpp/network/Server.hpp"
#include "./dto/DTOs.hpp"
/**
* Custom Request Handler
*/
class Handler : public oatpp::web::server::HttpRequestHandler {
private:
/* Inject object mapper component */
OATPP_COMPONENT(std::shared_ptr<oatpp::data::mapping::ObjectMapper>, m_objectMapper);
public:
/**
* Handle incoming request and return outgoing response.
*/
std::shared_ptr<OutgoingResponse> handle(const std::shared_ptr<IncomingRequest>& request) override {
auto message = MessageDto::createShared();
message->statusCode = 1024;
message->message = "Hello DTO!";
return ResponseFactory::createResponse(Status::CODE_200, message, m_objectMapper);
}
};
void Server::run() {
/* Register Components in scope of run() method */
AppComponent components;
/* Get router component */
OATPP_COMPONENT(std::shared_ptr<oatpp::web::server::HttpRouter>, router);
/* Route GET - "/hello" requests to Handler */
router->route("GET", "/hello", std::make_shared<Handler>());
/* Get connection handler component */
OATPP_COMPONENT(std::shared_ptr<oatpp::network::ConnectionHandler>, connectionHandler);
/* Get connection provider component */
OATPP_COMPONENT(std::shared_ptr<oatpp::network::ServerConnectionProvider>, connectionProvider);
/* Create server which takes provided TCP connections and passes them to HTTP connection handler */
oatpp::network::Server server(connectionProvider, connectionHandler);
/* Priny info about server port */
OATPP_LOGI("MyApp", "Server running on port %s", connectionProvider->getProperty("port").getData());
/* Run server */
server.run();
}

4
server/server.hpp Normal file
View file

@ -0,0 +1,4 @@
#pragma once
namespace Server{
void run();
}

View file

@ -0,0 +1,49 @@
#pragma once
#include "oatpp/parser/json/mapping/ObjectMapper.hpp"
#include "oatpp/web/server/HttpConnectionHandler.hpp"
#include "oatpp/network/tcp/server/ConnectionProvider.hpp"
#include "oatpp/core/macro/component.hpp"
/**
* Class which creates and holds Application components and registers components in oatpp::base::Environment
* Order of components initialization is from top to bottom
*/
class AppComponent {
public:
/**
* Create ConnectionProvider component which listens on the port
*/
OATPP_CREATE_COMPONENT(std::shared_ptr<oatpp::network::ServerConnectionProvider>, serverConnectionProvider)([] {
return oatpp::network::tcp::server::ConnectionProvider::createShared({"localhost", 8000, oatpp::network::Address::IP_4});
}());
/**
* Create Router component
*/
OATPP_CREATE_COMPONENT(std::shared_ptr<oatpp::web::server::HttpRouter>, httpRouter)([] {
return oatpp::web::server::HttpRouter::createShared();
}());
/**
* Create ConnectionHandler component which uses Router component to route requests
*/
OATPP_CREATE_COMPONENT(std::shared_ptr<oatpp::network::ConnectionHandler>, serverConnectionHandler)([] {
OATPP_COMPONENT(std::shared_ptr<oatpp::web::server::HttpRouter>, router); // get Router component
return oatpp::web::server::HttpConnectionHandler::createShared(router);
}());
/**
* Create ObjectMapper component to serialize/deserialize DTOs in Contoller's API
*/
OATPP_CREATE_COMPONENT(std::shared_ptr<oatpp::data::mapping::ObjectMapper>, apiObjectMapper)([] {
return oatpp::parser::json::mapping::ObjectMapper::createShared();
}());
};

14
subprojects/oatpp.wrap Normal file
View file

@ -0,0 +1,14 @@
[wrap-file]
directory = oatpp-1.3.1
source_url = https://github.com/oatpp/oatpp/archive/refs/tags/1.3.1.tar.gz
source_filename = oatpp-1.3.1.tar.gz
source_hash = 9dd31f005ab0b3e8895a478d750d7dbce99e42750a147a3c42a9daecbddedd64
patch_filename = oatpp_1.3.1-2_patch.zip
patch_url = https://wrapdb.mesonbuild.com/v2/oatpp_1.3.1-2/get_patch
patch_hash = 55b1ca10e8ace40d6a95ef6c7e91a2de852e3aee74900ad7765697d7de014128
source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/oatpp_1.3.1-2/oatpp-1.3.1.tar.gz
wrapdb_version = 1.3.1-2
[provide]
oatpp = oatpp_dep
oatpp-test = oatpp_test_dep

45
tests/wg_device.cpp Normal file
View file

@ -0,0 +1,45 @@
#include "wireguard/wgcpp.hpp"
#include <cstdio>
int main(void) {
Users users;
WG wg(users, "wgdev1", 51820, 0xc0a80000,24);
User &user = wg.add_cfg("Test");
if (user.ipv4 != 0xc0a80002) {
return 1;
}
wg.merge();
for (auto stat : *wg.peer_state()) {
printf("%d %x %s\n", stat.active, stat.ipv4, stat.pubkey);
}
printf("user2,4 append\n");
User &user2 = wg.add_cfg("Test2");
if (user2.ipv4 != 0xc0a80003) {
return 1;
}
User &user4 = wg.add_cfg("Test4", 0xc0a80005);
if (user4.ipv4 != 0xc0a80005) {
return 1;
}
wg.merge();
for (auto stat : *wg.peer_state()) {
printf("%d %x %s\n", stat.active, stat.ipv4, stat.pubkey);
}
printf("user 3 prepend\n");
User &user3 = wg.add_cfg("Test3", 0xc0a80004);
if (user3.ipv4 != 0xc0a80004) {
return 1;
}
wg.merge();
for (auto stat : *wg.peer_state()) {
printf("%d %x %s\n", stat.active, stat.ipv4, stat.pubkey);
}
return 0;
}

16
wgcl.cpp Normal file
View file

@ -0,0 +1,16 @@
#include "./server/server.hpp"
#include "oatpp/network/Server.hpp"
#include "wireguard/wgcpp.hpp"
#include <iostream>
int main(int argc, char **argv) {
oatpp::base::Environment::init();
/* Run App */
Server::run();
/* Destroy oatpp Environment */
oatpp::base::Environment::destroy();
return 0;
}

143
wireguard/wgcpp.cpp Normal file
View file

@ -0,0 +1,143 @@
#include "./wgcpp.hpp"
#include "wireguard/wireguard.h"
#include <algorithm>
#include <cstring>
#include <netinet/in.h>
#include <sys/socket.h>
#include <vector>
std::map<std::string_view, std::vector<User>> Users::acl_aggr() {
std::map<std::string_view, std::vector<User>> acl_list;
for (const User &usr : this->users) {
for (const std::string_view member : usr.acl_member) {
auto x = acl_list.find(member);
if (x != acl_list.end()) {
acl_list.at(member).push_back(usr);
} else {
std::vector<User> acl_members;
acl_members.push_back(usr);
acl_list[member] = acl_members;
}
}
}
return acl_list;
}
void Users::sort_with_ip() {
std::sort(this->users.begin(), this->users.end(),
[](User &a, User &b) { return a.ipv4 < b.ipv4; });
}
WG::WG(Users &wg_users, const char name[16], std::uint16_t port,
uint32_t network, uint8_t cidr)
: users(wg_users), network_ipv4(network), cidr(cidr) {
wg_generate_private_key(this->wg_dev.private_key);
wg_key_to_base64(this->b64_private_key, this->wg_dev.private_key);
wg_generate_public_key(this->wg_dev.public_key, this->wg_dev.private_key);
wg_key_to_base64(this->b64_public_key, this->wg_dev.public_key);
this->wg_dev.first_peer = nullptr;
this->wg_dev.last_peer = nullptr;
std::strncpy(this->wg_dev.name, name, 16);
this->wg_dev.listen_port = port;
this->wg_dev.flags = (enum wg_device_flags)(WGDEVICE_HAS_LISTEN_PORT |
WGDEVICE_HAS_PRIVATE_KEY |
WGDEVICE_HAS_PUBLIC_KEY);
uint32_t wildcard = 0xffffffff;
if (this->cidr < 32) {
wildcard = ((1 << (32 - this->cidr)) - 1);
}
for (uint32_t i = 2; i < wildcard; i++) {
if ((i ^ 255) != 0) {
this->ipv4_pool.insert(this->network_ipv4 + i);
}
}
for (const auto &user : wg_users.users) {
this->ipv4_pool.erase(user.ipv4);
}
}
WG::WG(Users &wg_users, const char name[16], std::uint16_t port,
uint32_t network, uint8_t cidr, std::string_view srv_priv_key,
std::string_view srv_pub_key)
: users(wg_users), network_ipv4(network), cidr(cidr) {
std::strncpy(this->b64_private_key, srv_priv_key.data(), 45);
std::strncpy(this->b64_public_key, srv_pub_key.data(), 45);
wg_key_from_base64(this->wg_dev.private_key, this->b64_private_key);
wg_key_from_base64(this->wg_dev.public_key, this->b64_public_key);
this->wg_dev.first_peer = nullptr;
this->wg_dev.last_peer = nullptr;
std::strncpy(this->wg_dev.name, name, 16);
this->wg_dev.listen_port = port;
this->wg_dev.flags = (enum wg_device_flags)(WGDEVICE_HAS_LISTEN_PORT |
WGDEVICE_HAS_PRIVATE_KEY |
WGDEVICE_HAS_PUBLIC_KEY);
uint32_t wildcard = 0xffffffff;
if (this->cidr < 32) {
wildcard = ((1 << (32 - this->cidr)) - 1);
}
for (uint32_t i = 2; i < wildcard; i++) {
if ((i ^ 255) != 0) {
this->ipv4_pool.insert(this->network_ipv4 + i);
}
}
for (const auto &user : wg_users.users) {
this->ipv4_pool.erase(user.ipv4);
}
}
void WG::merge() {
User *previous = nullptr;
for (User &user : this->users.users) {
if (previous) {
previous->node.next_peer = &user.node;
} else {
previous = &user;
}
}
}
User &WG::add_cfg(strv name) {
uint32_t ipv4 = this->ipv4_alloc();
return this->add_cfg(name, ipv4);
}
User &WG::add_cfg(strv name, uint32_t ipv4) {
wg_key pub, priv;
wg_generate_private_key(priv);
wg_generate_public_key(pub, priv);
User new_user(name, ipv4, pub, priv);
this->users.users.push_back(new_user);
return this->users.users.back();
}
std::vector<WGPeerStat> *WG::peer_state() {
std::vector<WGPeerStat> *x = new std::vector<WGPeerStat>();
for (User &user : this->users.users) {
WGPeerStat stat;
stat.active = !(user.node.flags & WGPEER_REMOVE_ME);
stat.ipv4 = user.node.first_allowedip->ip4.s_addr;
wg_key_to_base64(stat.pubkey, user.node.public_key);
x->push_back(stat);
}
return x;
}
User::User(strv name, uint32_t ipv4, wg_key pubkey, wg_key privkey)
: username(name), ipv4(ipv4) {
memset(&this->node, 0, sizeof(wg_peer));
this->node.flags = (enum wg_peer_flags)(WGPEER_HAS_PUBLIC_KEY | WGPEER_REPLACE_ALLOWEDIPS);
wg_allowedip *allowed_ip = new wg_allowedip;
allowed_ip->cidr = 32;
allowed_ip->family = AF_INET;
allowed_ip->ip4.s_addr = htonl(ipv4);
this->node.first_allowedip = allowed_ip;
this->node.last_allowedip = allowed_ip;
memcpy(this->node.public_key, pubkey, sizeof(uint8_t) * 32);
memcpy(this->privkey, privkey, sizeof(uint8_t) * 32);
}

62
wireguard/wgcpp.hpp Normal file
View file

@ -0,0 +1,62 @@
#pragma once
#include <cstdint>
#include <map>
#include <set>
#include <string_view>
#include <vector>
extern "C" {
#include "./wireguard.h"
}
typedef std::string_view strv;
class User {
public:
std::string_view username;
std::uint32_t ipv4;
wg_peer node;
std::set<std::string_view> acl_member;
wg_key privkey;
User(strv name, uint32_t ipv4, wg_key pubkey, wg_key privkey);
};
class Users {
public:
std::vector<User> users;
std::map<std::string_view, std::vector<User>> acl_aggr();
void sort_with_ip();
};
struct WGPeerStat {
bool active;
wg_key_b64_string pubkey;
u_int32_t ipv4;
};
class WG {
private:
wg_device wg_dev;
std::set<uint32_t> ipv4_pool;
uint32_t ipv4_alloc() {
uint32_t pick = *this->ipv4_pool.begin();
this->ipv4_pool.erase(pick);
return pick;
}
wg_key_b64_string b64_private_key, b64_public_key;
public:
Users &users;
uint32_t network_ipv4;
uint8_t cidr;
uint32_t persistent_time = 0;
WG(Users &wg_users, const char name[16], std::uint16_t port, uint32_t network,
uint8_t cidr);
WG(Users &wg_users, const char name[16], std::uint16_t port, uint32_t network,
uint8_t cidr, std::string_view srv_priv_key, std::string_view srv_pub_key);
User &add_cfg(strv name);
User &add_cfg(strv name, uint32_t ipv4);
void merge();
std::vector<WGPeerStat> *peer_state();
};

1756
wireguard/wireguard.c Normal file

File diff suppressed because it is too large Load diff

113
wireguard/wireguard.h Normal file
View file

@ -0,0 +1,113 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
/*
* Copyright (C) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights
* Reserved.
*/
#ifndef WIREGUARD_H
#define WIREGUARD_H
#include <net/if.h>
#include <netinet/in.h>
#include <stdbool.h>
#include <stdint.h>
#include <sys/socket.h>
#include <time.h>
typedef uint8_t wg_key[32];
typedef char wg_key_b64_string[((sizeof(wg_key) + 2) / 3) * 4 + 1];
/* Cross platform __kernel_timespec */
struct timespec64 {
int64_t tv_sec;
int64_t tv_nsec;
};
typedef struct wg_allowedip {
uint16_t family;
union {
struct in_addr ip4;
struct in6_addr ip6;
};
uint8_t cidr;
struct wg_allowedip *next_allowedip;
} wg_allowedip;
enum wg_peer_flags {
WGPEER_REMOVE_ME = 1U << 0,
WGPEER_REPLACE_ALLOWEDIPS = 1U << 1,
WGPEER_HAS_PUBLIC_KEY = 1U << 2,
WGPEER_HAS_PRESHARED_KEY = 1U << 3,
WGPEER_HAS_PERSISTENT_KEEPALIVE_INTERVAL = 1U << 4
};
typedef union wg_endpoint {
struct sockaddr addr;
struct sockaddr_in addr4;
struct sockaddr_in6 addr6;
} wg_endpoint;
typedef struct wg_peer {
enum wg_peer_flags flags;
wg_key public_key;
wg_key preshared_key;
wg_endpoint endpoint;
struct timespec64 last_handshake_time;
uint64_t rx_bytes, tx_bytes;
uint16_t persistent_keepalive_interval;
struct wg_allowedip *first_allowedip, *last_allowedip;
struct wg_peer *next_peer;
} wg_peer;
enum wg_device_flags {
WGDEVICE_REPLACE_PEERS = 1U << 0,
WGDEVICE_HAS_PRIVATE_KEY = 1U << 1,
WGDEVICE_HAS_PUBLIC_KEY = 1U << 2,
WGDEVICE_HAS_LISTEN_PORT = 1U << 3,
WGDEVICE_HAS_FWMARK = 1U << 4
};
typedef struct wg_device {
char name[IFNAMSIZ];
uint32_t ifindex;
enum wg_device_flags flags;
wg_key public_key;
wg_key private_key;
uint32_t fwmark;
uint16_t listen_port;
struct wg_peer *first_peer, *last_peer;
} wg_device;
#define wg_for_each_device_name(__names, __name, __len) \
for ((__name) = (__names), (__len) = 0; ((__len) = strlen(__name)); \
(__name) += (__len) + 1)
#define wg_for_each_peer(__dev, __peer) \
for ((__peer) = (__dev)->first_peer; (__peer); (__peer) = (__peer)->next_peer)
#define wg_for_each_allowedip(__peer, __allowedip) \
for ((__allowedip) = (__peer)->first_allowedip; (__allowedip); \
(__allowedip) = (__allowedip)->next_allowedip)
int wg_set_device(wg_device *dev);
int wg_get_device(wg_device **dev, const char *device_name);
int wg_add_device(const char *device_name);
int wg_del_device(const char *device_name);
void wg_free_device(wg_device *dev);
char *wg_list_device_names(void); /* first\0second\0third\0forth\0last\0\0 */
void wg_key_to_base64(wg_key_b64_string base64, const wg_key key);
int wg_key_from_base64(wg_key key, const wg_key_b64_string base64);
bool wg_key_is_zero(const wg_key key);
void wg_generate_public_key(wg_key public_key, const wg_key private_key);
void wg_generate_private_key(wg_key private_key);
void wg_generate_preshared_key(wg_key preshared_key);
#endif