WIP: Support for macos #2

Closed
Sirherobrine23 wants to merge 1 commits from wg_apple into main
12 changed files with 414 additions and 12 deletions

@ -60,7 +60,9 @@
"${workspaceFolder}/node_modules/node-addon-api",
"/usr/local/include/node",
"${workspaceFolder}/addons/genKey/**",
"${workspaceFolder}/addons/tools/**"
"${workspaceFolder}/addons/tools/**",
"${workspaceFolder}/build/macos/x86_64/**",
"${workspaceFolder}/build/macos/aarch64/**"
],
"defines": [
"NAPI_DISABLE_CPP_EXCEPTIONS"

@ -142,6 +142,19 @@ static void invert(fe o, const fe i) {
memzero_explicit(c, sizeof(c));
}
std::string wgKeys::keyToHex(const std::string &keyInput) {
static const char hex_digits[] = "0123456789ABCDEF";
wg_key key;
wgKeys::stringToKey(key, keyInput);
std::string output;
output.reserve(sizeof(key) * 2);
for (unsigned char c : key) {
output.push_back(hex_digits[c >> 4]);
output.push_back(hex_digits[c & 15]);
}
return output;
}
void wgKeys::generatePreshared(wg_key preshared_key) {
#if _WIN32 || defined(__CYGWIN__)
HCRYPTPROV hCryptProv;

@ -14,6 +14,9 @@ namespace wgKeys {
/* base64 to wg_key */
void stringToKey(wg_key key, std::string keyBase64);
/* Convert base64 key to hexadecimal key */
std::string keyToHex(const std::string &keyInput);
// bool key_is_zero(wg_key key);
/* Generate preshared key */

@ -0,0 +1,77 @@
#include <napi.h>
#include <iostream>
#include <string>
#include <wginterface.hh>
#include <wgkeys.hh>
#include "libwg-go.h"
unsigned long maxName() {
return 16;
}
std::string versionDrive() {
return wgVersion();
}
void listDevices::Execute() {
auto dev = std::string(listWgDevices());
while (dev.length() > 0) {
if (dev.find(",") == std::string::npos) {
deviceNames[dev] = listInfo();
deviceNames[dev].tunType = "kernel";
break;
}
auto devName = dev.substr(0, dev.find(","));
dev = dev.substr(dev.find(",")+1);
deviceNames[devName] = listInfo();
deviceNames[devName].tunType = "kernel";
}
}
void deleteInterface::Execute() {}
void setConfig::Execute() {
auto tunFd = getTunFile((char*)wgName.c_str());
if (tunFd == -1) {
auto res = std::string(createTun((char*)wgName.c_str()));
if (res.length() != 0) {
SetError(res);
return;
}
tunFd = getTunFile((char*)wgName.c_str());
}
if (tunFd == -1) {
SetError("Cannot get tun fd");
return;
}
std::string interfaceConfig = std::string("private_key=").append(wgKeys::keyToHex(this->privateKey));
if (this->portListen > 0) interfaceConfig = interfaceConfig.append(std::string("\nlisten_port=").append(std::to_string(this->portListen)));
if (this->fwmark > 0) interfaceConfig = interfaceConfig.append(std::string("\nfwmark=").append(std::to_string(this->fwmark)));
if (this->replacePeers) interfaceConfig = interfaceConfig.append("\nreplace_peers=true");
for (auto it = peersVector.begin(); it != peersVector.end(); ++it) {
const std::string peerPubKey = it->first;
auto peerConfig = it->second;
interfaceConfig = interfaceConfig.append(std::string("\npublic_key=")).append(wgKeys::keyToHex(peerPubKey));
if (peerConfig.removeMe) interfaceConfig = interfaceConfig.append(std::string("\nremove_me=true"));
else {
if (peerConfig.presharedKey.length() == B64_WG_KEY_LENGTH) interfaceConfig = interfaceConfig.append("\npreshared_key=").append(wgKeys::keyToHex(peerConfig.presharedKey));
if (peerConfig.endpoint.length() > 0) interfaceConfig = interfaceConfig.append("\nendpoint=").append(peerConfig.endpoint);
if (peerConfig.keepInterval) interfaceConfig = interfaceConfig.append("\npersistent_keepalive_interval=").append(std::to_string(peerConfig.keepInterval));
if (peerConfig.allowedIPs.size() > 0) interfaceConfig = interfaceConfig.append("\nreplace_allowed_ips=true");
for (auto ip : peerConfig.allowedIPs) {
interfaceConfig = interfaceConfig.append("\nallowed_ip=").append(ip);
}
}
}
std::cout << interfaceConfig << std::endl;
auto status = std::string(wgTurnOn((char*)interfaceConfig.append("\n\n").c_str(), tunFd));
if (status.length() > 0 ) SetError(status);
return;
}
void getConfig::Execute() {}

2
addons/wg-go/.gitignore vendored Normal file

@ -0,0 +1,2 @@
*.a
*.h

11
addons/wg-go/go.mod Normal file

@ -0,0 +1,11 @@
module sirherobrine23.org/Wireguard/wireguard-tools.js/wg-tools
go 1.21.6
require (
golang.org/x/crypto v0.13.0 // indirect
golang.org/x/net v0.15.0 // indirect
golang.org/x/sys v0.12.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect
)

10
addons/wg-go/go.sum Normal file

@ -0,0 +1,10 @@
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.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=

243
addons/wg-go/main.go Normal file

@ -0,0 +1,243 @@
/* SPDX-License-Identifier: MIT
*
* Copyright (C) 2018-2019 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
*/
package main
// #include <stdlib.h>
// #include <sys/types.h>
// static void callLogger(void *func, void *ctx, int level, const char *msg)
// {
// ((void(*)(void *, int, const char *))func)(ctx, level, msg);
// }
import "C"
import (
"fmt"
"os"
"os/signal"
"runtime"
"runtime/debug"
"strings"
"time"
"unsafe"
"golang.org/x/sys/unix"
"golang.zx2c4.com/wireguard/conn"
"golang.zx2c4.com/wireguard/device"
"golang.zx2c4.com/wireguard/tun"
)
var loggerFunc unsafe.Pointer
var loggerCtx unsafe.Pointer
type CLogger int
func cstring(s string) *C.char {
b, err := unix.BytePtrFromString(s)
if err != nil {
b := [1]C.char{}
return &b[0]
}
return (*C.char)(unsafe.Pointer(b))
}
func (l CLogger) Printf(format string, args ...interface{}) {
if uintptr(loggerFunc) == 0 {
return
}
C.callLogger(loggerFunc, loggerCtx, C.int(l), cstring(fmt.Sprintf(format, args...)))
}
type tunnelHandle struct {
*device.Device
*device.Logger
}
var tunnelHandles = make(map[int32]tunnelHandle)
var Tuns = make(map[string]tun.Device)
func init() {
signals := make(chan os.Signal)
signal.Notify(signals, unix.SIGUSR2)
go func() {
buf := make([]byte, os.Getpagesize())
for {
select {
case <-signals:
n := runtime.Stack(buf, true)
buf[n] = 0
if uintptr(loggerFunc) != 0 {
C.callLogger(loggerFunc, loggerCtx, 0, (*C.char)(unsafe.Pointer(&buf[0])))
}
}
}
}()
}
//export wgSetLogger
func wgSetLogger(context, loggerFn uintptr) {
loggerCtx = unsafe.Pointer(context)
loggerFunc = unsafe.Pointer(loggerFn)
}
//export listWgDevices
func listWgDevices() *C.char {
wgInterfaceList := make([]byte, 0);
for tunName := range Tuns {
if (len(wgInterfaceList) != 0) { wgInterfaceList = append(wgInterfaceList, []byte(",")...); }
wgInterfaceList = append(wgInterfaceList, []byte(tunName)...)
}
return C.CString(string(wgInterfaceList))
}
//export createTun
func createTun(name *C.char) *C.char {
goName := C.GoString(name)
if Tuns[goName] != nil {
return C.CString("")
}
tun, err := tun.CreateTUN(goName, device.DefaultMTU)
if err != nil {
return C.CString(err.Error())
}
Tuns[goName] = tun
return C.CString("")
}
//export getTunFile
func getTunFile(name *C.char) int {
goName := C.GoString(name)
if Tuns[goName] != nil {
return int(Tuns[goName].File().Fd())
}
return -1
}
//export wgTurnOn
func wgTurnOn(settings *C.char, tunFd int32) *C.char {
logger := &device.Logger{
Verbosef: CLogger(0).Printf,
Errorf: CLogger(1).Printf,
}
dupTunFd, err := unix.Dup(int(tunFd))
if err != nil {
return C.CString(fmt.Sprintf("Unable to dup tun fd: %v", err))
}
err = unix.SetNonblock(dupTunFd, true)
if err != nil {
unix.Close(dupTunFd)
return C.CString(fmt.Sprintf("Unable to set tun fd as non blocking: %v", err))
}
tun, err := tun.CreateTUNFromFile(os.NewFile(uintptr(dupTunFd), "/dev/tun"), 0)
if err != nil {
unix.Close(dupTunFd)
return C.CString(fmt.Sprintf("Unable to create new tun device from fd: %v", err))
}
logger.Verbosef("Attaching to interface")
dev := device.NewDevice(tun, conn.NewStdNetBind(), logger)
err = dev.IpcSet(C.GoString(settings))
if err != nil {
unix.Close(dupTunFd)
return C.CString(fmt.Sprintf("Unable to set IPC settings: %v", err))
}
dev.Up()
logger.Verbosef("Device started")
tunnelHandles[tunFd] = tunnelHandle{dev, logger}
return C.CString("")
}
//export wgTurnOff
func wgTurnOff(tunnelHandle int32) {
dev, ok := tunnelHandles[tunnelHandle]
if !ok {
return
}
delete(tunnelHandles, tunnelHandle)
dev.Close()
}
//export wgSetConfig
func wgSetConfig(tunnelHandle int32, settings *C.char) int64 {
dev, ok := tunnelHandles[tunnelHandle]
if !ok {
return 0
}
err := dev.IpcSet(C.GoString(settings))
if err != nil {
dev.Errorf("Unable to set IPC settings: %v", err)
if ipcErr, ok := err.(*device.IPCError); ok {
return ipcErr.ErrorCode()
}
return -1
}
return 0
}
//export wgGetConfig
func wgGetConfig(tunnelHandle int32) *C.char {
device, ok := tunnelHandles[tunnelHandle]
if !ok {
return nil
}
settings, err := device.IpcGet()
if err != nil {
return nil
}
return C.CString(settings)
}
//export wgBumpSockets
func wgBumpSockets(tunnelHandle int32) {
dev, ok := tunnelHandles[tunnelHandle]
if !ok {
return
}
go func() {
for i := 0; i < 10; i++ {
err := dev.BindUpdate()
if err == nil {
dev.SendKeepalivesToPeersWithCurrentKeypair()
return
}
dev.Errorf("Unable to update bind, try %d: %v", i+1, err)
time.Sleep(time.Second / 2)
}
dev.Errorf("Gave up trying to update bind; tunnel is likely dysfunctional")
}()
}
//export wgDisableSomeRoamingForBrokenMobileSemantics
func wgDisableSomeRoamingForBrokenMobileSemantics(tunnelHandle int32) {
dev, ok := tunnelHandles[tunnelHandle]
if !ok {
return
}
dev.DisableSomeRoamingForBrokenMobileSemantics()
}
//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" {
parts := strings.Split(dep.Version, "-")
if len(parts) == 3 && len(parts[2]) == 12 {
return C.CString(parts[2][:7])
}
return C.CString(dep.Version)
}
}
return C.CString("unknown")
}
func main() {}

@ -40,14 +40,31 @@
flags_cc:
- "-fPIC"
macos:
defines:
- "LISTDEV"
- "GETCONFIG"
- "SETCONFIG"
- "DELIFACE"
cflags_cc:
- "-fexceptions"
cflags:
- "-fexceptions"
sources:
- "!addons/tools/wginterface-dummy.cpp"
- "addons/tools/wginterface-darwin.cpp"
includes:
- ./build/macos/x86_64
- ./build/macos/aarch64
prebuild:
- cwd: ./addons/wg-go
shell: bash
env:
CGO_ENABLED: "1"
run: go build -ldflags=-w -trimpath -v -o ${BUILDDIR}/libwg-go.o -buildmode c-archive .
windows:
sources:
- "addons/tools/wginterface-win.cpp"
- "!addons/tools/wginterface-dummy.cpp"
- "addons/tools/wginterface-win.cpp"
includes:
- "addons/tools/win"
defines:

@ -45,6 +45,6 @@
},
"dependencies": {
"node-addon-api": "^7.1.0",
"rebory": "^0.1.10"
"rebory": "^0.1.11-2"
}
}

@ -23,8 +23,6 @@ const addon = rebory.loadAddon(path.join(__dirname, "../binding.yaml")).wginterf
});
export const { constants } = addon;
console.log(addon);
/** default location to run socket's */
const defaultPath = (process.env.WIRWGUARD_GO_RUN||"").length > 0 ? path.resolve(process.cwd(), process.env.WIRWGUARD_GO_RUN) : process.platform === "win32" ? "\\\\.\\pipe\\WireGuard" : "/var/run/wireguard";
@ -114,13 +112,6 @@ export async function deleteInterface(wgName: string): Promise<void> {
* @param config - Interface config
*/
export async function setConfig(wgName: string, config: WgConfigSet): Promise<void> {
if (process.platform === "darwin") {
if (!(wgName.match(/^tun([0-9]+)$/))) throw new Error("Invalid name, example to valid: tun0");
// Replace to tun name
// const devNames = Object.keys(networkInterfaces()).filter(s => s.toLowerCase().startsWith("tun"));
// for (let i = 0n; i < BigInt(Number.MAX_SAFE_INTEGER); i++) if (devNames.indexOf((wgName = ("tun").concat(i.toString())))) break;
}
if (typeof addon.setConfig === "function") return addon.setConfig(wgName, config);
const client = netConnection(path.join(defaultPath, (wgName).concat(".sock")));
const writel = (...data: any[]) => client.write(data.map(String).join("").concat("\n"));

33
src/wginterface_test.ts Normal file

@ -0,0 +1,33 @@
import { setConfig, deleteInterface, listDevices } from "./wginterface.js";
import { privateKey, publicKey } from "./key.js";
console.log("Creating interface");
const peerPub = publicKey(await privateKey());
await setConfig("utun15", {
privateKey: await privateKey(),
peers: {
[peerPub]: {
allowedIPs: [ "10.0.0.1/32" ]
}
}
});
await setConfig("utun16", {
privateKey: await privateKey(),
peers: {
[peerPub]: {
allowedIPs: [ "10.0.0.1/32" ]
}
}
});
console.log("List interface")
console.log(await listDevices());
console.log("Deleting interface")
await deleteInterface("utun15");
await deleteInterface("utun16");
console.log("List interface 2")
console.log(await listDevices());