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) { | ||||||
| @@ -79,6 +78,66 @@ 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; | ||||||
|   | |||||||
							
								
								
									
										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)); | ||||||
| }); | }); | ||||||
		Reference in New Issue
	
	Block a user