Big code refactoring #10
3
.gitignore
vendored
3
.gitignore
vendored
@ -11,6 +11,9 @@ node_modules/
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
*.log
|
*.log
|
||||||
|
|
||||||
|
# Addon
|
||||||
|
addon/userspace/wg-go.*
|
||||||
|
|
||||||
# Typescript
|
# Typescript
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
src/**/*.d.ts
|
src/**/*.d.ts
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
#include <string>
|
|
||||||
|
|
||||||
namespace WireguardUserspace {
|
|
||||||
std::string getVersion() {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,5 +1,4 @@
|
|||||||
#include "wg.hh"
|
#include "wg.hh"
|
||||||
#include "go/userspace.cpp"
|
|
||||||
#include <napi.h>
|
#include <napi.h>
|
||||||
|
|
||||||
Napi::Object StartAddon(const Napi::Env env, const Napi::Object exports) {
|
Napi::Object StartAddon(const Napi::Env env, const Napi::Object exports) {
|
||||||
@ -9,7 +8,7 @@ Napi::Object StartAddon(const Napi::Env env, const Napi::Object exports) {
|
|||||||
if (!(exports.Get(keysOnLoadsNames[keyIndex]).IsString())) continue;
|
if (!(exports.Get(keysOnLoadsNames[keyIndex]).IsString())) continue;
|
||||||
LoadConfig[keysOnLoadsNames[keyIndex].ToString().Utf8Value()] = exports.Get(keysOnLoadsNames[keyIndex]).ToString().Utf8Value();
|
LoadConfig[keysOnLoadsNames[keyIndex].ToString().Utf8Value()] = exports.Get(keysOnLoadsNames[keyIndex]).ToString().Utf8Value();
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
std::string statusLoading = driveLoad(LoadConfig);
|
std::string statusLoading = driveLoad(LoadConfig);
|
||||||
if (!!(statusLoading.length())) {
|
if (!!(statusLoading.length())) {
|
||||||
@ -79,8 +78,68 @@ Napi::Object StartAddon(const Napi::Env env, const Napi::Object exports) {
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
Userspace.Set("createTunel", Napi::Function::New(env, [](const Napi::CallbackInfo &info) -> Napi::Value {
|
||||||
|
const Napi::Env env = info.Env();
|
||||||
|
if (!(info[0].IsString())) {
|
||||||
|
Napi::Error::New(env, "Set interface name").ThrowAsJavaScriptException();
|
||||||
|
return env.Undefined();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
auto *worker = new WgUserspace::CreateWgTunnel(env, info[0].ToString());
|
||||||
|
worker->Queue();
|
||||||
|
return worker->NodePromise.Promise();
|
||||||
|
} catch (std::string &err) {
|
||||||
|
Napi::Error::New(env, err).ThrowAsJavaScriptException();
|
||||||
|
return env.Undefined();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
Userspace.Set("deleteTunel", Napi::Function::New(env, [](const Napi::CallbackInfo &info) -> Napi::Value {
|
||||||
|
const Napi::Env env = info.Env();
|
||||||
|
if (!(info[0].IsString())) {
|
||||||
|
Napi::Error::New(env, "Set interface name").ThrowAsJavaScriptException();
|
||||||
|
return env.Undefined();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
auto *worker = new WgUserspace::DeleteWgTunnel(env, info[0].ToString());
|
||||||
|
worker->Queue();
|
||||||
|
return worker->NodePromise.Promise();
|
||||||
|
} catch (std::string &err) {
|
||||||
|
Napi::Error::New(env, err).ThrowAsJavaScriptException();
|
||||||
|
return env.Undefined();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
Userspace.Set("checkTunel", Napi::Function::New(env, [](const Napi::CallbackInfo &info) -> Napi::Value {
|
||||||
|
const Napi::Env env = info.Env();
|
||||||
|
if (!(info[0].IsString())) {
|
||||||
|
Napi::Error::New(env, "Set interface name").ThrowAsJavaScriptException();
|
||||||
|
return env.Undefined();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
auto *worker = new WgUserspace::CheckWgTunnel(env, info[0].ToString());
|
||||||
|
worker->Queue();
|
||||||
|
return worker->NodePromise.Promise();
|
||||||
|
} catch (std::string &err) {
|
||||||
|
Napi::Error::New(env, err).ThrowAsJavaScriptException();
|
||||||
|
return env.Undefined();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
Userspace.Set("listTunels", Napi::Function::New(env, [](const Napi::CallbackInfo &info) -> Napi::Value {
|
||||||
|
const Napi::Env env = info.Env();
|
||||||
|
try {
|
||||||
|
auto *worker = new WgUserspace::ListWgTunnels(env);
|
||||||
|
worker->Queue();
|
||||||
|
return worker->NodePromise.Promise();
|
||||||
|
} catch (std::string &err) {
|
||||||
|
Napi::Error::New(env, err).ThrowAsJavaScriptException();
|
||||||
|
return env.Undefined();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
exports.Set("constants", Constants);
|
exports.Set("constants", Constants);
|
||||||
exports.Set("userspace", Userspace);
|
exports.Set("userspace", Userspace);
|
||||||
return exports;
|
return exports;
|
||||||
}
|
}
|
||||||
NODE_API_MODULE(addon, StartAddon);
|
NODE_API_MODULE(addon, StartAddon);
|
||||||
|
14
addon/userspace/go/go.mod
Normal file
14
addon/userspace/go/go.mod
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
module sirherobrine23.org/Wireguard/wireguard-tools.js/wg-tun
|
||||||
|
|
||||||
|
go 1.21.6
|
||||||
|
|
||||||
|
require (
|
||||||
|
golang.org/x/sys v0.12.0
|
||||||
|
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
golang.org/x/crypto v0.13.0 // indirect
|
||||||
|
golang.org/x/net v0.15.0 // indirect
|
||||||
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||||
|
)
|
16
addon/userspace/go/go.sum
Normal file
16
addon/userspace/go/go.sum
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
|
||||||
|
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
|
||||||
|
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
|
||||||
|
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||||
|
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
|
||||||
|
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||||
|
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
||||||
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44=
|
||||||
|
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
||||||
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||||
|
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
|
||||||
|
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
|
||||||
|
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 h1:TbRPT0HtzFP3Cno1zZo7yPzEEnfu8EjLfl6IU9VfqkQ=
|
||||||
|
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259/go.mod h1:AVgIgHMwK63XvmAzWG9vLQ41YnVHN0du0tEC46fI7yY=
|
161
addon/userspace/go/main.go
Normal file
161
addon/userspace/go/main.go
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"runtime/debug"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.zx2c4.com/wireguard/conn"
|
||||||
|
"golang.zx2c4.com/wireguard/device"
|
||||||
|
"golang.zx2c4.com/wireguard/ipc"
|
||||||
|
"golang.zx2c4.com/wireguard/tun"
|
||||||
|
|
||||||
|
_ "unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {}
|
||||||
|
|
||||||
|
//export wgVersion
|
||||||
|
func wgVersion() *C.char {
|
||||||
|
info, ok := debug.ReadBuildInfo()
|
||||||
|
if !ok {
|
||||||
|
return C.CString("unknown")
|
||||||
|
}
|
||||||
|
for _, dep := range info.Deps {
|
||||||
|
if dep.Path == "golang.zx2c4.com/wireguard" {
|
||||||
|
return C.CString(dep.Version)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return C.CString("unknown")
|
||||||
|
}
|
||||||
|
|
||||||
|
//export wgSocketDirectory
|
||||||
|
func wgSocketDirectory() *C.char {
|
||||||
|
return C.CString(socketDirectory)
|
||||||
|
}
|
||||||
|
|
||||||
|
//export deleteTun
|
||||||
|
func deleteTun(_tunName *C.char) bool {
|
||||||
|
tunName := C.GoString(_tunName)
|
||||||
|
if !existTun(_tunName) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
Files, err := os.ReadDir(socketDirectory)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, file := range Files {
|
||||||
|
if file.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
splits := strings.Split(file.Name(), "/")
|
||||||
|
splits[len(splits)-1] = strings.TrimSuffix(splits[len(splits)-1], ".sock")
|
||||||
|
if splits[len(splits)-1] == tunName {
|
||||||
|
os.Remove(strings.Join(([]string{socketDirectory, file.Name()}), "/"))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
//export existTun
|
||||||
|
func existTun(_tunName *C.char) bool {
|
||||||
|
tunName := C.GoString(_tunName)
|
||||||
|
Files, err := os.ReadDir(socketDirectory)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, file := range Files {
|
||||||
|
if file.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
splits := strings.Split(file.Name(), "/")
|
||||||
|
splits[len(splits)-1] = strings.TrimSuffix(splits[len(splits)-1], ".sock")
|
||||||
|
if splits[len(splits)-1] == tunName {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
//export getSocketPath
|
||||||
|
func getSocketPath(_tunName *C.char) *C.char {
|
||||||
|
tunName := C.GoString(_tunName)
|
||||||
|
uapi, err := ipc.UAPIOpen(tunName)
|
||||||
|
if err != nil {
|
||||||
|
return C.CString(fmt.Sprintf("Failed to get socket path: %v", err))
|
||||||
|
}
|
||||||
|
return C.CString(strings.Join(([]string{"/", uapi.Name()}), ""))
|
||||||
|
}
|
||||||
|
|
||||||
|
//export createTun
|
||||||
|
func createTun(_tunName *C.char) *C.char {
|
||||||
|
interfaceName := C.GoString(_tunName)
|
||||||
|
if existTun(_tunName) {
|
||||||
|
return C.CString("Tun already exists")
|
||||||
|
}
|
||||||
|
logger := device.NewLogger(device.LogLevelSilent, fmt.Sprintf("(%s) ", interfaceName))
|
||||||
|
|
||||||
|
// open TUN device (or use supplied fd)
|
||||||
|
tdev, err := tun.CreateTUN(interfaceName, device.DefaultMTU)
|
||||||
|
if err == nil {
|
||||||
|
realInterfaceName, err2 := tdev.Name()
|
||||||
|
if err2 == nil {
|
||||||
|
interfaceName = realInterfaceName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return C.CString(fmt.Sprintf("Failed to create TUN device: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// open UAPI file (or use supplied fd)
|
||||||
|
fileUAPI, err := ipc.UAPIOpen(interfaceName)
|
||||||
|
if err != nil {
|
||||||
|
tdev.Close()
|
||||||
|
return C.CString(fmt.Sprintf("Failed to open UAPI file: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// create device
|
||||||
|
dev := device.NewDevice(tdev, conn.NewDefaultBind(), logger)
|
||||||
|
|
||||||
|
uapi, err := ipc.UAPIListen(interfaceName, fileUAPI)
|
||||||
|
if err != nil {
|
||||||
|
dev.Close()
|
||||||
|
tdev.Close()
|
||||||
|
return C.CString(fmt.Sprintf("Failed to listen on UAPI file: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UAPI Listener
|
||||||
|
uapiListened := uapi.Addr().String()
|
||||||
|
|
||||||
|
clean := func() {
|
||||||
|
uapi.Close()
|
||||||
|
dev.Close()
|
||||||
|
tdev.Close()
|
||||||
|
if uapiListened[:1] == "/" {
|
||||||
|
os.Remove(uapiListened)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
conn, err := uapi.Accept()
|
||||||
|
if err != nil {
|
||||||
|
clean()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
go dev.IpcHandle(conn)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
<-dev.Wait()
|
||||||
|
clean()
|
||||||
|
}()
|
||||||
|
|
||||||
|
return C.CString(strings.Join(([]string{"/", uapiListened}), ""))
|
||||||
|
}
|
17
addon/userspace/go/main_unix.go
Normal file
17
addon/userspace/go/main_unix.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
//go:build linux || darwin || freebsd || openbsd
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
_ "unsafe"
|
||||||
|
|
||||||
|
_ "golang.zx2c4.com/wireguard/ipc"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:linkname socketDirectory golang.xz2c4.com/wireguard/ipc.socketDirectory
|
||||||
|
var socketDirectory = "/var/run/wireguard"
|
||||||
|
|
||||||
|
//go:linkname sockPath golang.xz2c4.com/wireguard/ipc.sockPath
|
||||||
|
func sockPath(iface string) string {
|
||||||
|
return fmt.Sprintf("%s/%s.sock", socketDirectory, iface)
|
||||||
|
}
|
46
addon/userspace/wginterface.cpp
Normal file
46
addon/userspace/wginterface.cpp
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
#include "wginterface.hh"
|
||||||
|
#include "userspace/wg-go.h"
|
||||||
|
#include <filesystem>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
// Get Wireguard-go version
|
||||||
|
std::string WireguardUserspace::getVersion() {
|
||||||
|
return wgVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create tunel and return path to tunel
|
||||||
|
std::string WireguardUserspace::createTunel(std::string wgName) {
|
||||||
|
std::string PathError = createTun((char*)wgName.c_str());
|
||||||
|
if (PathError.rfind("/", 0) != 0) throw PathError;
|
||||||
|
return PathError.substr(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete tunel by name and return true if success
|
||||||
|
bool WireguardUserspace::deleteTunel(std::string wgName) {
|
||||||
|
return !!deleteTun((char*)wgName.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if tunel exist
|
||||||
|
bool WireguardUserspace::checkTunel(std::string wgName) {
|
||||||
|
return !!existTun((char*)wgName.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get uapi folder
|
||||||
|
std::string WireguardUserspace::getUapiDirectory() {
|
||||||
|
return wgSocketDirectory();
|
||||||
|
}
|
||||||
|
|
||||||
|
// List all tunels
|
||||||
|
std::vector<std::string> WireguardUserspace::listTunels() {
|
||||||
|
std::vector<std::string> tunels;
|
||||||
|
std::filesystem::path p = wgSocketDirectory();
|
||||||
|
if (std::filesystem::exists(p)) {
|
||||||
|
for (const auto & entry : std::filesystem::directory_iterator(p)) {
|
||||||
|
// Absolute path to tunel
|
||||||
|
tunels.push_back(entry.path().string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tunels;
|
||||||
|
}
|
66
addon/wg.hh
66
addon/wg.hh
@ -172,4 +172,70 @@ class GetConfig : public WireguardConfig, public Promised {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
namespace WgUserspace {
|
||||||
|
class CreateWgTunnel : public Promised {
|
||||||
|
std::string tunName;
|
||||||
|
public:
|
||||||
|
CreateWgTunnel(const Napi::Env &env, const Napi::String &name): Promised(env), tunName{name.Utf8Value()} {}
|
||||||
|
|
||||||
|
void Execute() override {
|
||||||
|
try {
|
||||||
|
std::string status = WireguardUserspace::createTunel(tunName);
|
||||||
|
if (status.rfind("/", 0) != 0) SetError(status);
|
||||||
|
} catch (std::string &err) { SetError(err); }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class DeleteWgTunnel : public Promised {
|
||||||
|
std::string tunName;
|
||||||
|
public:
|
||||||
|
DeleteWgTunnel(const Napi::Env &env, const Napi::String &name): Promised(env), tunName{name.Utf8Value()} {}
|
||||||
|
|
||||||
|
void Execute() override {
|
||||||
|
try {
|
||||||
|
WireguardUserspace::deleteTunel(tunName);
|
||||||
|
} catch (std::string &err) { SetError(err); }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class CheckWgTunnel : public Promised {
|
||||||
|
std::string tunName;
|
||||||
|
bool status;
|
||||||
|
public:
|
||||||
|
CheckWgTunnel(const Napi::Env &env, const Napi::String &name): Promised(env), tunName{name.Utf8Value()} {}
|
||||||
|
|
||||||
|
void Execute() override {
|
||||||
|
try {
|
||||||
|
status = WireguardUserspace::checkTunel(tunName);
|
||||||
|
} catch (std::string &err) { SetError(err); }
|
||||||
|
}
|
||||||
|
|
||||||
|
void runOk(std::function<void(Napi::Value)> callback) override {
|
||||||
|
Napi::HandleScope scope(Env());
|
||||||
|
callback(Napi::Boolean::New(Env(), status));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class ListWgTunnels : public Promised {
|
||||||
|
public:
|
||||||
|
ListWgTunnels(const Napi::Env &env): Promised(env) {}
|
||||||
|
std::vector<std::string> tuns;
|
||||||
|
|
||||||
|
void Execute() override {
|
||||||
|
try {
|
||||||
|
tuns = WireguardUserspace::listTunels();
|
||||||
|
if (tuns.size() == 0) SetError("No interfaces found");
|
||||||
|
} catch (std::string &err) { SetError(err); }
|
||||||
|
}
|
||||||
|
|
||||||
|
void runOk(std::function<void(Napi::Value)> callback) override {
|
||||||
|
Napi::HandleScope scope(Env());
|
||||||
|
const Napi::Env env = Env();
|
||||||
|
const Napi::Array interf = Napi::Array::New(env);
|
||||||
|
for (auto &ip : tuns) interf.Set(interf.Length(), ip);
|
||||||
|
callback(interf);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
extern const int wgKeyLength;
|
extern const int wgKeyLength;
|
||||||
extern const int Base64WgKeyLength;
|
extern const int Base64WgKeyLength;
|
||||||
@ -196,4 +195,23 @@ class WireguardConfig {
|
|||||||
void getWireguardConfig();
|
void getWireguardConfig();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
namespace WireguardUserspace {
|
||||||
|
// Get Wireguard-go version
|
||||||
|
std::string getVersion();
|
||||||
|
|
||||||
|
// Create tunel and return path to tunel
|
||||||
|
std::string createTunel(std::string wgName);
|
||||||
|
|
||||||
|
// Delete tunel by name and return true if success
|
||||||
|
bool deleteTunel(std::string wgName);
|
||||||
|
|
||||||
|
// Check if tunel exist
|
||||||
|
bool checkTunel(std::string wgName);
|
||||||
|
|
||||||
|
// Get uapi folder
|
||||||
|
std::string getUapiDirectory();
|
||||||
|
|
||||||
|
// List all tunels
|
||||||
|
std::vector<std::string> listTunels();
|
||||||
|
};
|
||||||
#endif
|
#endif
|
||||||
|
10
binding.yaml
10
binding.yaml
@ -8,9 +8,17 @@ includes:
|
|||||||
sources:
|
sources:
|
||||||
- "addon/main.cpp"
|
- "addon/main.cpp"
|
||||||
- "addon/genKey/wgkeys.cpp"
|
- "addon/genKey/wgkeys.cpp"
|
||||||
|
- "addon/userspace/wginterface.cpp"
|
||||||
# Set undefined functions to non supported system
|
# Set undefined functions to non supported system
|
||||||
- "addon/dummy/wginterface.cpp"
|
- "addon/dummy/wginterface.cpp"
|
||||||
|
prebuild:
|
||||||
|
- shell: bash
|
||||||
|
cwd: ./addon/userspace/go
|
||||||
|
env:
|
||||||
|
CGO_ENABLED: "1"
|
||||||
|
run: |
|
||||||
|
go build -ldflags=-w -trimpath -v -o ../wg-go.o -buildmode c-archive .
|
||||||
|
mv -vf ../wg-go.o ${BUILDDIR}/wg-go.o
|
||||||
target:
|
target:
|
||||||
linux:
|
linux:
|
||||||
sources:
|
sources:
|
||||||
|
46
src/key.ts
46
src/key.ts
@ -1,5 +1,7 @@
|
|||||||
import crypto from "node:crypto";
|
import crypto from "node:crypto";
|
||||||
|
|
||||||
|
export const KeyLength = 32, Base64Length = 44;
|
||||||
|
|
||||||
type BinArray = Float64Array|Uint8Array|number[];
|
type BinArray = Float64Array|Uint8Array|number[];
|
||||||
|
|
||||||
function gf(init?: number[]) {
|
function gf(init?: number[]) {
|
||||||
@ -94,39 +96,31 @@ function clamp(z: BinArray) {
|
|||||||
z[0] &= 248;
|
z[0] &= 248;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function Base64ToKey(keyInput: string): Uint8Array {
|
||||||
* Generate preshared key blocking loop event
|
if (keyInput.length !== Base64Length) throw new Error("Invalid key length", { cause: { Required: Base64Length, Input: keyInput.length } });
|
||||||
*
|
return new Uint8Array(Buffer.from(keyInput, "base64"));
|
||||||
* @deprecated - use presharedKey
|
}
|
||||||
*/
|
|
||||||
export function presharedKeySync() {
|
function keyToBase64(key: Uint8Array): string {
|
||||||
var privateKey = new Uint8Array(32);
|
if (key.length !== KeyLength) throw new Error("Invalid key length", { cause: { Required: KeyLength, Input: key.length } });
|
||||||
crypto.randomFillSync(privateKey);
|
return Buffer.from(key).toString("base64");
|
||||||
return keyToBase64(privateKey);
|
}
|
||||||
|
|
||||||
|
export function keyToHex(key: string): string {
|
||||||
|
return Buffer.from(key, "base64").toString("hex");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate preshared key
|
* Generate preshared key
|
||||||
*/
|
*/
|
||||||
export async function presharedKey(): Promise<string> {
|
export async function presharedKey(): Promise<string> {
|
||||||
var privateKey = new Uint8Array(32);
|
var privateKey = new Uint8Array(KeyLength);
|
||||||
return new Promise((done, reject) => crypto.randomFill(privateKey, (err) => {
|
return new Promise((done, reject) => crypto.randomFill(privateKey, (err) => {
|
||||||
if (err) return reject(err);
|
if (err) return reject(err);
|
||||||
done(keyToBase64(privateKey));
|
done(keyToBase64(privateKey));
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create private key
|
|
||||||
*
|
|
||||||
* @deprecated - Use privateKey
|
|
||||||
*/
|
|
||||||
export function privateKeySync() {
|
|
||||||
var privateKey = Base64ToKey(presharedKeySync());
|
|
||||||
clamp(privateKey);
|
|
||||||
return keyToBase64(privateKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create private key
|
* Create private key
|
||||||
*/
|
*/
|
||||||
@ -136,14 +130,6 @@ export async function privateKey() {
|
|||||||
return keyToBase64(privateKey);
|
return keyToBase64(privateKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function keyToBase64(key: Uint8Array): string {
|
|
||||||
return Buffer.from(key).toString("base64");
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Base64ToKey(keyInput: string): Uint8Array {
|
|
||||||
return new Uint8Array(Buffer.from(keyInput, "base64"));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get Public key from Private key
|
* Get Public key from Private key
|
||||||
*
|
*
|
||||||
@ -151,7 +137,7 @@ export function Base64ToKey(keyInput: string): Uint8Array {
|
|||||||
*/
|
*/
|
||||||
export function publicKey(privKey: string) {
|
export function publicKey(privKey: string) {
|
||||||
var privateKey: Uint8Array = Base64ToKey(privKey);
|
var privateKey: Uint8Array = Base64ToKey(privKey);
|
||||||
var r: number, z = new Uint8Array(32);
|
var r: number, z = new Uint8Array(KeyLength);
|
||||||
var a = gf([1]),
|
var a = gf([1]),
|
||||||
b = gf([9]),
|
b = gf([9]),
|
||||||
c = gf(),
|
c = gf(),
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
import net from "node:net";
|
||||||
|
import readline from "node:readline";
|
||||||
import { loadAddon } from "rebory";
|
import { loadAddon } from "rebory";
|
||||||
|
import { key } from "./index.js";
|
||||||
const __dirname = import.meta.dirname || path.dirname((await import("node:url")).fileURLToPath(import.meta.url));
|
const __dirname = import.meta.dirname || path.dirname((await import("node:url")).fileURLToPath(import.meta.url));
|
||||||
|
|
||||||
interface Peer {
|
interface Peer {
|
||||||
@ -66,10 +69,10 @@ const addon = (await loadAddon(path.resolve(__dirname, "../binding.yaml"))).wgin
|
|||||||
userspace?: {
|
userspace?: {
|
||||||
/** Wireguard-go version */
|
/** Wireguard-go version */
|
||||||
driveVersion?: string;
|
driveVersion?: string;
|
||||||
/** Get tunel socket path */
|
createTunel(wgName: string): Promise<string>;
|
||||||
getTunel: (name: string) => Promise<string>;
|
existTunel(wgName: string): Promise<boolean>;
|
||||||
/** Get tunel socket path */
|
deleteTunel(wgName: string): Promise<void>;
|
||||||
deleteTunel: (name: string) => Promise<string>;
|
listTuns(): Promise<string[]>;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Kernel wireguard version */
|
/** Kernel wireguard version */
|
||||||
@ -100,11 +103,139 @@ export namespace Kernel {
|
|||||||
*/
|
*/
|
||||||
export namespace Userspace {
|
export namespace Userspace {
|
||||||
const { userspace } = addon;
|
const { userspace } = addon;
|
||||||
export const { driveVersion } = userspace || {};
|
export const {
|
||||||
|
driveVersion,
|
||||||
export async function listDevices() {}
|
createTunel,
|
||||||
export async function deleteInterface() {}
|
deleteTunel,
|
||||||
|
listTuns,
|
||||||
export async function setConfig() {}
|
existTunel,
|
||||||
export async function getConfig() {}
|
} = userspace || {};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export class Wireguard {
|
||||||
|
address = new Set<string>;
|
||||||
|
|
||||||
|
#_fwmark: number = 0;
|
||||||
|
|
||||||
|
#_portListen: number = 0;
|
||||||
|
|
||||||
|
#_privateKey: string;
|
||||||
|
set privateKey(value: string) {
|
||||||
|
this.#_privateKey = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
get privateKey() {
|
||||||
|
return this.#_privateKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
set publicKey(_key: string) {}
|
||||||
|
get publicKey() {
|
||||||
|
return key.publicKey(this.#_privateKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
#_replacePeers?: boolean;
|
||||||
|
set replacePeers(value: boolean) {
|
||||||
|
this.#_replacePeers = !!value;
|
||||||
|
}
|
||||||
|
get replacePeers() {
|
||||||
|
return this.#_replacePeers;
|
||||||
|
}
|
||||||
|
|
||||||
|
#_peers = new Map<string, SetPeer>;
|
||||||
|
addPeer(publicKey: string, value: SetPeer) {
|
||||||
|
this.#_peers.set(publicKey, value);
|
||||||
|
}
|
||||||
|
removePeer(publicKey: string) {
|
||||||
|
this.#_peers.delete(publicKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON() {
|
||||||
|
const config: Omit<SetConfig, "name"> = Object.create({});
|
||||||
|
config.privateKey = this.#_privateKey;
|
||||||
|
config.publicKey = this.publicKey;
|
||||||
|
config.portListen = this.#_portListen;
|
||||||
|
config.fwmark = this.#_fwmark;
|
||||||
|
config.address = Array.from(this.address);
|
||||||
|
|
||||||
|
config.peers = Object.create({});
|
||||||
|
for (const [key, value] of this.#_peers) config.peers[key] = value;
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
async setConfig(name: string): Promise<void> {
|
||||||
|
if (!!Kernel.driveVersion) {
|
||||||
|
return Kernel.setConfig({ name, ...this.toJSON() });
|
||||||
|
}
|
||||||
|
if (!(await Userspace.existTunel(name))) await Userspace.createTunel(name);
|
||||||
|
const sockPath = (await Userspace.listTuns()).find((tun) => { let tt = tun.split("/").pop(); return (tt.endsWith(".sock") ? tt.slice(0, -5) : tt) === name; });
|
||||||
|
if (!sockPath) throw new Error("Wireguard interface not found");
|
||||||
|
const sock = net.connect(sockPath);
|
||||||
|
await new Promise<void>((resolve, reject) => sock.once("connect", resolve).once("error", reject));
|
||||||
|
const rl = readline.createInterface({ input: sock, output: sock });
|
||||||
|
let stop = [];
|
||||||
|
rl.on("line", (line): any => {
|
||||||
|
if (stop.length) {
|
||||||
|
stop.push(line);
|
||||||
|
if (line === "") sock.end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (line.startsWith("errno=")) {
|
||||||
|
if (parseInt(line.slice(6)) !== 0) stop.push("");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
rl.once("close", () => {
|
||||||
|
if (stop.length) throw new Error(stop.join("\n").trim());
|
||||||
|
});
|
||||||
|
rl.write(`set=1\n`);
|
||||||
|
if (this.#_privateKey.length == key.Base64Length) rl.write(`\nprivate_key=${key.keyToHex(this.#_privateKey)}\npublic_key=${key.keyToHex(this.publicKey)}`);
|
||||||
|
if (this.#_portListen >= 0) rl.write(`\nlisten_port=${this.#_portListen}`);
|
||||||
|
if (this.#_fwmark >= 0) rl.write(`\nfwmark=${this.#_fwmark}`);
|
||||||
|
if (this.address.size > 0) rl.write(`\naddress=${Array.from(this.address).join(",")}`);
|
||||||
|
if (this.#_replacePeers) rl.write(`\nreplace_peers=1`);
|
||||||
|
for (const [key, value] of this.#_peers) {
|
||||||
|
rl.write(`\npublic_key=${key}`);
|
||||||
|
if (value.removeMe) rl.write(`\nremove=${key}`);
|
||||||
|
else {
|
||||||
|
if (value.presharedKey) rl.write(`\npreshared_key=${value.presharedKey}`);
|
||||||
|
if (value.keepInterval) rl.write(`\npersistent_keepalive_interval=${value.keepInterval}`);
|
||||||
|
if (value.endpoint) rl.write(`\nendpoint=${value.endpoint}`);
|
||||||
|
if (value.allowedIPs) rl.write(`\nallowed_ips=${value.allowedIPs.join(",")}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rl.write("\n\n");
|
||||||
|
return new Promise<void>((resolve, reject) => rl.once("close", () => resolve()).once("error", reject));
|
||||||
|
}
|
||||||
|
|
||||||
|
async getConfig(name: string) {
|
||||||
|
if (!!Kernel.driveVersion) return Kernel.getConfig(name);
|
||||||
|
const sockPath = (await Userspace.listTuns()).find((tun) => { let tt = tun.split("/").pop(); return (tt.endsWith(".sock") ? tt.slice(0, -5) : tt) === name; });
|
||||||
|
if (!sockPath) throw new Error("Wireguard interface not found");
|
||||||
|
const sock = net.connect(sockPath);
|
||||||
|
await new Promise<void>((resolve, reject) => sock.once("connect", resolve).once("error", reject));
|
||||||
|
const rl = readline.createInterface({ input: sock, output: sock });
|
||||||
|
let stop = [];
|
||||||
|
rl.on("line", (line): any => {
|
||||||
|
if (stop.length) {
|
||||||
|
stop.push(line);
|
||||||
|
if (line === "") sock.end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (line.startsWith("errno=")) {
|
||||||
|
if (parseInt(line.slice(6)) !== 0) stop.push("");
|
||||||
|
}
|
||||||
|
console.log(line);
|
||||||
|
});
|
||||||
|
rl.once("close", () => {
|
||||||
|
if (stop.length) throw new Error(stop.join("\n").trim());
|
||||||
|
});
|
||||||
|
rl.write("get=1\n\n");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteInterface(name: string): Promise<void> {
|
||||||
|
if (!!Kernel.driveVersion) return Kernel.deleteInterface(name);
|
||||||
|
if (await Userspace.existTunel(name)) return Userspace.deleteTunel(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default Wireguard;
|
@ -1,42 +1,39 @@
|
|||||||
import test from "node:test";
|
import test from "node:test";
|
||||||
import { Kernel, SetConfig } from "./wginterface.js";
|
import { Wireguard } from "./wginterface.js";
|
||||||
import { presharedKey, privateKey, publicKey } from "./key.js";
|
import { presharedKey, privateKey, publicKey } from "./key.js";
|
||||||
|
|
||||||
await test("Wireguard interface", async t => {
|
await test("Wireguard interface", async t => {
|
||||||
const config: SetConfig = {
|
let wgName = "wg23";
|
||||||
name: "wg0",
|
if (process.platform === "darwin") wgName = "utun23";
|
||||||
privateKey: await privateKey(),
|
const config = new Wireguard;
|
||||||
replacePeers: true,
|
config.privateKey = await privateKey();
|
||||||
address: [
|
config.replacePeers = true;
|
||||||
"10.66.66.1/32",
|
config.address.add("10.66.66.1/32");
|
||||||
"fd42:42:42::1/128"
|
config.address.add("fd42:42:42::1/128");
|
||||||
],
|
|
||||||
peers: {}
|
|
||||||
};
|
|
||||||
|
|
||||||
const peer1 = await privateKey();
|
const peer1 = await privateKey();
|
||||||
config.peers[publicKey(peer1)] = {
|
config.addPeer(publicKey(peer1), {
|
||||||
keepInterval: 15,
|
keepInterval: 15,
|
||||||
presharedKey: await presharedKey(),
|
presharedKey: await presharedKey(),
|
||||||
allowedIPs: [
|
allowedIPs: [
|
||||||
"10.66.66.2/32"
|
"10.66.66.2/32"
|
||||||
]
|
]
|
||||||
};
|
});
|
||||||
|
|
||||||
const peer2 = await privateKey();
|
const peer2 = await privateKey();
|
||||||
config.peers[publicKey(peer2)] = {
|
config.addPeer(publicKey(peer2), {
|
||||||
keepInterval: 0,
|
keepInterval: 0,
|
||||||
allowedIPs: [
|
allowedIPs: [
|
||||||
"10.66.66.3/32"
|
"10.66.66.3/32"
|
||||||
]
|
]
|
||||||
};
|
});
|
||||||
|
|
||||||
let skip: string;
|
let skip: string;
|
||||||
await t.test("Create and Set config in interface", async () => Kernel.setConfig(config).catch(err => { skip = "Cannot set wireguard config"; return Promise.reject(err); }));
|
await t.test("Create and Set config in interface", async () => config.setConfig(wgName).catch(err => { skip = "Cannot set wireguard config"; return Promise.reject(err); }));
|
||||||
await t.test("Get config from interface", { skip }, async () => {
|
await t.test("Get config from interface", { skip }, async () => {
|
||||||
const fromInterface = await Kernel.getConfig(config.name);
|
const fromInterface = await config.getConfig(wgName);
|
||||||
console.dir(fromInterface, { depth: null });
|
console.dir(fromInterface, { depth: null });
|
||||||
});
|
});
|
||||||
|
|
||||||
// await t.test("Delete interface if exists", { skip }, async () => Kernel.deleteInterface(config.name));
|
await t.test("Delete interface if exists", { skip }, async () => config.deleteInterface(wgName));
|
||||||
});
|
});
|
Loading…
x
Reference in New Issue
Block a user