Big code refactoring #10
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -11,6 +11,9 @@ node_modules/ | ||||
| .DS_Store | ||||
| *.log | ||||
|  | ||||
| # Addon | ||||
| addon/userspace/wg-go.* | ||||
|  | ||||
| # Typescript | ||||
| *.tsbuildinfo | ||||
| src/**/*.d.ts | ||||
|   | ||||
| @@ -1,7 +0,0 @@ | ||||
| #include <string> | ||||
|  | ||||
| namespace WireguardUserspace { | ||||
|   std::string getVersion() { | ||||
|     return ""; | ||||
|   } | ||||
| }; | ||||
| @@ -1,5 +1,4 @@ | ||||
| #include "wg.hh" | ||||
| #include "go/userspace.cpp" | ||||
| #include <napi.h> | ||||
|  | ||||
| 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("userspace", Userspace); | ||||
|   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 | ||||
|   | ||||
| @@ -4,7 +4,6 @@ | ||||
| #include <string> | ||||
| #include <vector> | ||||
| #include <map> | ||||
| #include <iostream> | ||||
|  | ||||
| extern const int wgKeyLength; | ||||
| extern const int Base64WgKeyLength; | ||||
| @@ -196,4 +195,23 @@ class WireguardConfig { | ||||
|   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 | ||||
|   | ||||
							
								
								
									
										10
									
								
								binding.yaml
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								binding.yaml
									
									
									
									
									
								
							| @@ -8,9 +8,17 @@ includes: | ||||
| sources: | ||||
|   - "addon/main.cpp" | ||||
|   - "addon/genKey/wgkeys.cpp" | ||||
|   - "addon/userspace/wginterface.cpp" | ||||
|   # Set undefined functions to non supported system | ||||
|   - "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: | ||||
|   linux: | ||||
|     sources: | ||||
|   | ||||
							
								
								
									
										46
									
								
								src/key.ts
									
									
									
									
									
								
							
							
						
						
									
										46
									
								
								src/key.ts
									
									
									
									
									
								
							| @@ -1,5 +1,7 @@ | ||||
| import crypto from "node:crypto"; | ||||
|  | ||||
| export const KeyLength = 32, Base64Length = 44; | ||||
|  | ||||
| type BinArray = Float64Array|Uint8Array|number[]; | ||||
|  | ||||
| function gf(init?: number[]) { | ||||
| @@ -94,39 +96,31 @@ function clamp(z: BinArray) { | ||||
|   z[0] &= 248; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Generate preshared key blocking loop event | ||||
|  * | ||||
|  * @deprecated - use presharedKey | ||||
|  */ | ||||
| export function presharedKeySync() { | ||||
|   var privateKey = new Uint8Array(32); | ||||
|   crypto.randomFillSync(privateKey); | ||||
|   return keyToBase64(privateKey); | ||||
| function Base64ToKey(keyInput: string): Uint8Array { | ||||
|   if (keyInput.length !== Base64Length) throw new Error("Invalid key length", { cause: { Required: Base64Length, Input: keyInput.length } }); | ||||
|   return new Uint8Array(Buffer.from(keyInput, "base64")); | ||||
| } | ||||
|  | ||||
| function keyToBase64(key: Uint8Array): string { | ||||
|   if (key.length !== KeyLength) throw new Error("Invalid key length", { cause: { Required: KeyLength, Input: key.length } }); | ||||
|   return Buffer.from(key).toString("base64"); | ||||
| } | ||||
|  | ||||
| export function keyToHex(key: string): string { | ||||
|   return Buffer.from(key, "base64").toString("hex"); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Generate preshared key | ||||
|  */ | ||||
| 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) => { | ||||
|     if (err) return reject(err); | ||||
|     done(keyToBase64(privateKey)); | ||||
|   })); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Create private key | ||||
|  * | ||||
|  * @deprecated - Use privateKey | ||||
|  */ | ||||
| export function privateKeySync() { | ||||
|   var privateKey = Base64ToKey(presharedKeySync()); | ||||
|   clamp(privateKey); | ||||
|   return keyToBase64(privateKey); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Create private key | ||||
|  */ | ||||
| @@ -136,14 +130,6 @@ export async function 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 | ||||
|  * | ||||
| @@ -151,7 +137,7 @@ export function Base64ToKey(keyInput: string): Uint8Array { | ||||
|  */ | ||||
| export function publicKey(privKey: string) { | ||||
|   var privateKey: Uint8Array = Base64ToKey(privKey); | ||||
|   var r: number, z = new Uint8Array(32); | ||||
|   var r: number, z = new Uint8Array(KeyLength); | ||||
|   var a = gf([1]), | ||||
|     b = gf([9]), | ||||
|     c = gf(), | ||||
|   | ||||
| @@ -1,5 +1,8 @@ | ||||
| import path from "node:path"; | ||||
| import net from "node:net"; | ||||
| import readline from "node:readline"; | ||||
| import { loadAddon } from "rebory"; | ||||
| import { key } from "./index.js"; | ||||
| const __dirname = import.meta.dirname || path.dirname((await import("node:url")).fileURLToPath(import.meta.url)); | ||||
|  | ||||
| interface Peer { | ||||
| @@ -66,10 +69,10 @@ const addon = (await loadAddon(path.resolve(__dirname, "../binding.yaml"))).wgin | ||||
|   userspace?: { | ||||
|     /** Wireguard-go version */ | ||||
|     driveVersion?: string; | ||||
|     /** Get tunel socket path */ | ||||
|     getTunel: (name: string) => Promise<string>; | ||||
|     /** Get tunel socket path */ | ||||
|     deleteTunel: (name: string) => Promise<string>; | ||||
|     createTunel(wgName: string): Promise<string>; | ||||
|     existTunel(wgName: string): Promise<boolean>; | ||||
|     deleteTunel(wgName: string): Promise<void>; | ||||
|     listTuns(): Promise<string[]>; | ||||
|   }; | ||||
|  | ||||
|   /** Kernel wireguard version */ | ||||
| @@ -100,11 +103,139 @@ export namespace Kernel { | ||||
|  */ | ||||
| export namespace Userspace { | ||||
|   const { userspace } = addon; | ||||
|   export const { driveVersion } = userspace || {}; | ||||
|  | ||||
|   export async function listDevices() {} | ||||
|   export async function deleteInterface() {} | ||||
|  | ||||
|   export async function setConfig() {} | ||||
|   export async function getConfig() {} | ||||
|   export const { | ||||
|     driveVersion, | ||||
|     createTunel, | ||||
|     deleteTunel, | ||||
|     listTuns, | ||||
|     existTunel, | ||||
|   } = 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 { Kernel, SetConfig } from "./wginterface.js"; | ||||
| import { Wireguard } from "./wginterface.js"; | ||||
| import { presharedKey, privateKey, publicKey } from "./key.js"; | ||||
|  | ||||
| await test("Wireguard interface", async t => { | ||||
|   const config: SetConfig = { | ||||
|     name: "wg0", | ||||
|     privateKey: await privateKey(), | ||||
|     replacePeers: true, | ||||
|     address: [ | ||||
|       "10.66.66.1/32", | ||||
|       "fd42:42:42::1/128" | ||||
|     ], | ||||
|     peers: {} | ||||
|   }; | ||||
|   let wgName = "wg23"; | ||||
|   if (process.platform === "darwin") wgName = "utun23"; | ||||
|   const config = new Wireguard; | ||||
|   config.privateKey = await privateKey(); | ||||
|   config.replacePeers = true; | ||||
|   config.address.add("10.66.66.1/32"); | ||||
|   config.address.add("fd42:42:42::1/128"); | ||||
|  | ||||
|   const peer1 = await privateKey(); | ||||
|   config.peers[publicKey(peer1)] = { | ||||
|   config.addPeer(publicKey(peer1), { | ||||
|     keepInterval: 15, | ||||
|     presharedKey: await presharedKey(), | ||||
|     allowedIPs: [ | ||||
|       "10.66.66.2/32" | ||||
|     ] | ||||
|   }; | ||||
|   }); | ||||
|  | ||||
|   const peer2 = await privateKey(); | ||||
|   config.peers[publicKey(peer2)] = { | ||||
|   config.addPeer(publicKey(peer2), { | ||||
|     keepInterval: 0, | ||||
|     allowedIPs: [ | ||||
|       "10.66.66.3/32" | ||||
|     ] | ||||
|   }; | ||||
|   }); | ||||
|  | ||||
|   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 () => { | ||||
|     const fromInterface = await Kernel.getConfig(config.name); | ||||
|     const fromInterface = await config.getConfig(wgName); | ||||
|     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