Refactor Interface package and update windows driver (#192)

* script to generate syso files

* test wireguard-windows driver package

* set int log

* add windows test

* add windows test

* verbose bash

* use cd

* move checkout

* exit 0

* removed tty flag

* artifact path

* fix tags and add cache

* fix cache

* fix cache

* test dir

* restore artifacts in the root

* try dll file

* try dll file

* copy dll

* typo in copy dll

* compile test

* checkout first

* updated cicd

* fix add address issue and gen GUID

* psexec typo

* accept eula

* mod tidy before tests

* regular test exec and verbose test with psexec

* test all

* return WGInterface Interface

* use WgIfaceName and timeout after 30 seconds

* different ports and validate connect 2 peers

* Use time.After for timeout and close interface

* Use time.After for testing connect peers

* WG Interface struct

* Update engine and parse address

* refactor Linux create and assignAddress

* NewWGIface and configuration methods

* Update proxy with interface methods

* update up command test

* resolve lint warnings

* remove psexec test

* close copied files

* add goos before build

* run tests on mac,windows and linux

* cache by testing os

* run on push

* fix indentation

* adjust test timeouts

* remove parallel flag

* mod tidy before test

* ignore syso files

* removed functions and renamed vars

* different IPs for connect peers test

* Generate syso with DLL

* Single Close method

* use port from test constant

* test: remove wireguard interfaces after finishing engine test

* use load_wgnt_from_rsrc

Co-authored-by: braginini <bangvalo@gmail.com>
This commit is contained in:
Maycon Santos 2022-01-17 13:01:58 +00:00 committed by GitHub
parent afb302d5e7
commit 64f2d295a8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 687 additions and 445 deletions

View File

@ -1,9 +1,5 @@
on:
push:
branches:
- main
pull_request:
name: Test Build On Platforms
on: push
jobs:
test_build:
strategy:
@ -21,15 +17,15 @@ jobs:
go-version: ${{ matrix.go-version }}
- name: Cache Go modules
uses: actions/cache@v1
uses: actions/cache@v2
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
key: ${{ runner.os }}-go-test-${{ matrix.os }}-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Install modules
run: go mod tidy
run: GOOS=${{ matrix.os }} go mod tidy
- name: run build client
run: GOOS=${{ matrix.os }} go build .

View File

@ -0,0 +1,29 @@
name: Test Code Darwin
on: push
jobs:
test:
strategy:
matrix:
go-version: [1.17.x]
runs-on: macos-latest
steps:
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
- name: Checkout code
uses: actions/checkout@v2
- name: Cache Go modules
uses: actions/cache@v2
with:
path: ~/go/pkg/mod
key: macos-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
macos-go-
- name: Install modules
run: go mod tidy
- name: Test
run: GOBIN=$(which go) && sudo --preserve-env=GOROOT $GOBIN test ./...

View File

@ -1,9 +1,5 @@
on:
push:
branches:
- main
pull_request:
name: Test Code
name: Test Code Linux
on: push
jobs:
test:
strategy:
@ -27,7 +23,20 @@ jobs:
$(whoami) soft nofile 65535
$(whoami) hard nofile 65535
EOF
- name: Cache Go modules
uses: actions/cache@v2
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Checkout code
uses: actions/checkout@v2
- name: Install modules
run: go mod tidy
- name: Test
run: GOBIN=$(which go) && sudo --preserve-env=GOROOT $GOBIN test -p 1 ./...
run: GOBIN=$(which go) && sudo --preserve-env=GOROOT $GOBIN test ./...

View File

@ -0,0 +1,51 @@
name: Test Code Windows
on: push
jobs:
pre:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- run: bash -x wireguard_nt.sh
working-directory: client
- uses: actions/upload-artifact@v2
with:
name: syso
path: client/*.syso
retention-days: 1
test:
needs: pre
strategy:
matrix:
go-version: [1.17.x]
runs-on: windows-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
- uses: actions/cache@v2
with:
path: |
%LocalAppData%\go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- uses: actions/download-artifact@v2
with:
name: syso
path: iface\
- name: Install modules
run: go mod tidy
- name: Test build
run: go test -tags=load_wgnt_from_rsrc ./...

View File

@ -1,9 +1,5 @@
name: golangci-lint
on:
push:
branches:
- main
pull_request:
on: push
jobs:
golangci:
name: lint

View File

@ -14,6 +14,10 @@ jobs:
uses: actions/checkout@v2
with:
fetch-depth: 0 # It is required for GoReleaser to work properly
- name: Generate syso with DLL
run: bash -x wireguard_nt.sh
working-directory: client
-
name: Set up Go
uses: actions/setup-go@v2

1
.gitignore vendored
View File

@ -6,3 +6,4 @@ conf.json
http-cmds.sh
infrastructure_files/management.json
infrastructure_files/docker-compose.yml
*.syso

View File

@ -26,7 +26,7 @@ builds:
- -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.CommitDate}} -X main.builtBy=goreleaser
mod_timestamp: '{{ .CommitTimestamp }}'
tags:
- load_wintun_from_rsrc
- load_wgnt_from_rsrc
- id: wiretrustee-mgmt
dir: management

View File

@ -60,7 +60,7 @@ func TestLogin(t *testing.T) {
}
if actualConf.WgIface != iface.WgInterfaceDefault {
t.Errorf("expected WgIface %s got %s", iface.WgInterfaceDefault, actualConf.WgIface)
t.Errorf("expected WgIfaceName %s got %s", iface.WgInterfaceDefault, actualConf.WgIface)
}
if len(actualConf.PrivateKey) == 0 {

View File

@ -64,7 +64,7 @@ func createEngineConfig(key wgtypes.Key, config *internal.Config, peerConfig *mg
}
engineConf := &internal.EngineConfig{
WgIface: config.WgIface,
WgIfaceName: config.WgIface,
WgAddr: peerConfig.Address,
IFaceBlackList: iFaceBlackList,
WgPrivateKey: key,

View File

@ -36,7 +36,7 @@ func TestUp_Start(t *testing.T) {
func TestUp(t *testing.T) {
defer iface.Close("wt0")
//defer iface.Close("wt0")
tempDir := t.TempDir()
confPath := tempDir + "/config.json"
@ -53,6 +53,8 @@ func TestUp(t *testing.T) {
"A2C8E62B-38F5-4553-B31E-DD66C696CEBB",
"--management-url",
mgmtURL.String(),
"--log-level",
"debug",
"--log-file",
"console",
})
@ -63,20 +65,23 @@ func TestUp(t *testing.T) {
}
}()
exists := false
for start := time.Now(); time.Since(start) < 15*time.Second; {
timeout := 15 * time.Second
timeoutChannel := time.After(timeout)
for {
select {
case <-timeoutChannel:
t.Fatalf("expected wireguard interface %s to be created before %s", iface.WgInterfaceDefault, timeout.String())
default:
}
e, err := iface.Exists(iface.WgInterfaceDefault)
if err != nil {
continue
}
if err != nil {
continue
}
if *e {
exists = true
break
}
}
if !exists {
t.Errorf("expected wireguard interface %s to be created", iface.WgInterfaceDefault)
}
}

View File

@ -21,6 +21,7 @@ import (
// PeerConnectionTimeoutMax is a timeout of an initial connection attempt to a remote peer.
// E.g. this peer will wait PeerConnectionTimeoutMax for the remote peer to respond, if not successful then it will retry the connection attempt.
// Todo pass timeout at EnginConfig
const PeerConnectionTimeoutMax = 45000 //ms
const PeerConnectionTimeoutMin = 30000 //ms
@ -28,8 +29,8 @@ const WgPort = 51820
// EngineConfig is a config for the Engine
type EngineConfig struct {
WgPort int
WgIface string
WgPort int
WgIfaceName string
// WgAddr is a Wireguard local address (Wiretrustee Network IP)
WgAddr string
// WgPrivateKey is a Wireguard private key of our peer (it MUST never leave the machine)
@ -61,6 +62,8 @@ type Engine struct {
cancel context.CancelFunc
ctx context.Context
wgInterface iface.WGIface
}
// Peer is an instance of the Connection Peer
@ -93,11 +96,13 @@ func (e *Engine) Stop() error {
return err
}
log.Debugf("removing Wiretrustee interface %s", e.config.WgIface)
err = iface.Close(e.config.WgIface)
if err != nil {
log.Errorf("failed closing Wiretrustee interface %s %v", e.config.WgIface, err)
return err
log.Debugf("removing Wiretrustee interface %s", e.config.WgIfaceName)
if e.wgInterface.Interface != nil {
err = e.wgInterface.Close()
if err != nil {
log.Errorf("failed closing Wiretrustee interface %s %v", e.config.WgIfaceName, err)
return err
}
}
log.Infof("stopped Wiretrustee Engine")
@ -112,19 +117,26 @@ func (e *Engine) Start() error {
e.syncMsgMux.Lock()
defer e.syncMsgMux.Unlock()
wgIface := e.config.WgIface
wgIfaceName := e.config.WgIfaceName
wgAddr := e.config.WgAddr
myPrivateKey := e.config.WgPrivateKey
var err error
err := iface.Create(wgIface, wgAddr)
e.wgInterface, err = iface.NewWGIface(wgIfaceName, wgAddr, iface.DefaultMTU)
if err != nil {
log.Errorf("failed creating interface %s: [%s]", wgIface, err.Error())
log.Errorf("failed creating wireguard interface instance %s: [%s]", wgIfaceName, err.Error())
return err
}
err = iface.Configure(wgIface, myPrivateKey.String(), e.config.WgPort)
err = e.wgInterface.Create()
if err != nil {
log.Errorf("failed configuring Wireguard interface [%s]: %s", wgIface, err.Error())
log.Errorf("failed creating tunnel interface %s: [%s]", wgIfaceName, err.Error())
return err
}
err = e.wgInterface.Configure(myPrivateKey.String(), e.config.WgPort)
if err != nil {
log.Errorf("failed configuring Wireguard interface [%s]: %s", wgIfaceName, err.Error())
return err
}
@ -399,7 +411,7 @@ func (e Engine) createPeerConn(pubKey string, allowedIPs string) (*peer.Conn, er
proxyConfig := proxy.Config{
RemoteKey: pubKey,
WgListenAddr: fmt.Sprintf("127.0.0.1:%d", e.config.WgPort),
WgInterface: e.config.WgIface,
WgInterface: e.wgInterface,
AllowedIps: allowedIPs,
PreSharedKey: e.config.PreSharedKey,
}

View File

@ -48,7 +48,11 @@ func TestEngine_MultiplePeers(t *testing.T) {
t.Fatal(err)
}
defer func() {
os.Remove(filepath.Join(dir, "store.json")) //nolint
err = os.Remove(filepath.Join(dir, "store.json")) //nolint
if err != nil {
t.Fatal(err)
return
}
}()
ctx, cancel := context.WithCancel(context.Background())
@ -102,25 +106,43 @@ func TestEngine_MultiplePeers(t *testing.T) {
// wait until all have been created and started
wg.Wait()
// check whether all the peer have expected peers connected
expectedConnected := numPeers * (numPeers - 1)
// adjust according to timeouts
timeout := 50 * time.Second
timeoutChan := time.After(timeout)
for {
select {
case <-timeoutChan:
t.Fatalf("waiting for expected connections timeout after %s", timeout.String())
return
default:
}
time.Sleep(time.Second)
totalConnected := 0
for _, engine := range engines {
totalConnected = totalConnected + len(engine.GetConnectedPeers())
}
if totalConnected == expectedConnected {
log.Debugf("total connected=%d", totalConnected)
break
}
log.Infof("total connected=%d", totalConnected)
}
// cleanup test
for _, peerEngine := range engines {
errStop := peerEngine.Stop()
if errStop != nil {
log.Infoln("got error trying to close testing peers engine: ", errStop)
}
}
}
func createEngine(ctx context.Context, cancel context.CancelFunc, setupKey string, i int) (*Engine, error) {
key, err := wgtypes.GenerateKey()
key, err := wgtypes.GeneratePrivateKey()
if err != nil {
return nil, err
}
@ -151,7 +173,7 @@ func createEngine(ctx context.Context, cancel context.CancelFunc, setupKey strin
}
conf := &EngineConfig{
WgIface: ifaceName,
WgIfaceName: ifaceName,
WgAddr: resp.PeerConfig.Address,
WgPrivateKey: key,
WgPort: 33100 + i,

View File

@ -1,6 +1,7 @@
package proxy
import (
"github.com/wiretrustee/wiretrustee/iface"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
"io"
"net"
@ -12,7 +13,7 @@ const DefaultWgKeepAlive = 25 * time.Second
type Config struct {
WgListenAddr string
RemoteKey string
WgInterface string
WgInterface iface.WGIface
AllowedIps string
PreSharedKey *wgtypes.Key
}

View File

@ -3,7 +3,6 @@ package proxy
import (
"context"
log "github.com/sirupsen/logrus"
"github.com/wiretrustee/wiretrustee/iface"
"net"
)
@ -25,9 +24,13 @@ func NewWireguardProxy(config Config) *WireguardProxy {
}
func (p *WireguardProxy) updateEndpoint() error {
udpAddr, err := net.ResolveUDPAddr(p.localConn.LocalAddr().Network(), p.localConn.LocalAddr().String())
if err != nil {
return err
}
// add local proxy connection as a Wireguard peer
err := iface.UpdatePeer(p.config.WgInterface, p.config.RemoteKey, p.config.AllowedIps, DefaultWgKeepAlive,
p.localConn.LocalAddr().String(), p.config.PreSharedKey)
err = p.config.WgInterface.UpdatePeer(p.config.RemoteKey, p.config.AllowedIps, DefaultWgKeepAlive,
udpAddr, p.config.PreSharedKey)
if err != nil {
return err
}
@ -65,7 +68,7 @@ func (p *WireguardProxy) Close() error {
return err
}
}
err := iface.RemovePeer(p.config.WgInterface, p.config.RemoteKey)
err := p.config.WgInterface.RemovePeer(p.config.RemoteKey)
if err != nil {
return err
}

View File

@ -5,5 +5,5 @@
#define STRINGIZE(x) #x
#define EXPAND(x) STRINGIZE(x)
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST manifest.xml
wintun.dll RCDATA wintun.dll
7 ICON ui/wiretrustee.ico
wireguard.dll RCDATA wireguard.dll

Binary file not shown.

27
client/wireguard_nt.sh Normal file
View File

@ -0,0 +1,27 @@
#!/bin/bash
ldir=$PWD
tmp_dir_path=$ldir/.distfiles
winnt=wireguard-nt.zip
download_file_path=$tmp_dir_path/$winnt
download_url=https://download.wireguard.com/wireguard-nt/wireguard-nt-0.10.1.zip
download_sha=772c0b1463d8d2212716f43f06f4594d880dea4f735165bd68e388fc41b81605
function resources_windows(){
cmd=$1
arch=$2
out=$3
docker run -i --rm -v $PWD:$PWD -w $PWD mstorsjo/llvm-mingw:latest $cmd -O coff -c 65001 -I $tmp_dir_path/wireguard-nt/bin/$arch -i resources.rc -o $out
}
mkdir -p $tmp_dir_path
curl -L#o $download_file_path.unverified $download_url
echo "$download_sha $download_file_path.unverified" | sha256sum -c
mv $download_file_path.unverified $download_file_path
mkdir -p .deps
unzip $download_file_path -d $tmp_dir_path
resources_windows i686-w64-mingw32-windres x86 resources_windows_386.syso
resources_windows aarch64-w64-mingw32-windres arm64 resources_windows_arm64.syso
resources_windows x86_64-w64-mingw32-windres amd64 resources_windows_amd64.syso

122
iface/configuration.go Normal file
View File

@ -0,0 +1,122 @@
package iface
import (
log "github.com/sirupsen/logrus"
"golang.zx2c4.com/wireguard/wgctrl"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
"net"
"time"
)
// configureDevice configures the wireguard device
func (w *WGIface) configureDevice(config wgtypes.Config) error {
wg, err := wgctrl.New()
if err != nil {
return err
}
defer wg.Close()
// validate if device with name exists
_, err = wg.Device(w.Name)
if err != nil {
return err
}
log.Debugf("got Wireguard device %s", w.Name)
return wg.ConfigureDevice(w.Name, config)
}
// Configure configures a Wireguard interface
// The interface must exist before calling this method (e.g. call interface.Create() before)
func (w *WGIface) Configure(privateKey string, port int) error {
log.Debugf("configuring Wireguard interface %s", w.Name)
log.Debugf("adding Wireguard private key")
key, err := wgtypes.ParseKey(privateKey)
if err != nil {
return err
}
fwmark := 0
config := wgtypes.Config{
PrivateKey: &key,
ReplacePeers: true,
FirewallMark: &fwmark,
ListenPort: &port,
}
return w.configureDevice(config)
}
// GetListenPort returns the listening port of the Wireguard endpoint
func (w *WGIface) GetListenPort() (*int, error) {
log.Debugf("getting Wireguard listen port of interface %s", w.Name)
//discover Wireguard current configuration
wg, err := wgctrl.New()
if err != nil {
return nil, err
}
defer wg.Close()
d, err := wg.Device(w.Name)
if err != nil {
return nil, err
}
log.Debugf("got Wireguard device listen port %s, %d", w.Name, d.ListenPort)
return &d.ListenPort, nil
}
// UpdatePeer updates existing Wireguard Peer or creates a new one if doesn't exist
// Endpoint is optional
func (w *WGIface) UpdatePeer(peerKey string, allowedIps string, keepAlive time.Duration, endpoint *net.UDPAddr, preSharedKey *wgtypes.Key) error {
log.Debugf("updating interface %s peer %s: endpoint %s ", w.Name, peerKey, endpoint)
//parse allowed ips
_, ipNet, err := net.ParseCIDR(allowedIps)
if err != nil {
return err
}
peerKeyParsed, err := wgtypes.ParseKey(peerKey)
if err != nil {
return err
}
peer := wgtypes.PeerConfig{
PublicKey: peerKeyParsed,
ReplaceAllowedIPs: true,
AllowedIPs: []net.IPNet{*ipNet},
PersistentKeepaliveInterval: &keepAlive,
PresharedKey: preSharedKey,
Endpoint: endpoint,
}
config := wgtypes.Config{
Peers: []wgtypes.PeerConfig{peer},
}
return w.configureDevice(config)
}
// RemovePeer removes a Wireguard Peer from the interface iface
func (w *WGIface) RemovePeer(peerKey string) error {
log.Debugf("Removing peer %s from interface %s ", peerKey, w.Name)
peerKeyParsed, err := wgtypes.ParseKey(peerKey)
if err != nil {
return err
}
peer := wgtypes.PeerConfig{
PublicKey: peerKeyParsed,
Remove: true,
}
config := wgtypes.Config{
Peers: []wgtypes.PeerConfig{peer},
}
return w.configureDevice(config)
}

View File

@ -1,78 +1,51 @@
package iface
import (
log "github.com/sirupsen/logrus"
"golang.zx2c4.com/wireguard/conn"
"golang.zx2c4.com/wireguard/device"
"golang.zx2c4.com/wireguard/tun"
"golang.zx2c4.com/wireguard/wgctrl"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
"net"
"time"
"os"
"runtime"
)
const (
defaultMTU = 1280
DefaultMTU = 1280
)
var (
tunIface tun.Device
)
// CreateWithUserspace Creates a new Wireguard interface, using wireguard-go userspace implementation
func CreateWithUserspace(iface string, address string) error {
var err error
tunIface, err = tun.CreateTUN(iface, defaultMTU)
if err != nil {
return err
}
// We need to create a wireguard-go device and listen to configuration requests
tunDevice := device.NewDevice(tunIface, conn.NewDefaultBind(), device.NewLogger(device.LogLevelSilent, "[wiretrustee] "))
err = tunDevice.Up()
if err != nil {
return err
}
uapi, err := getUAPI(iface)
if err != nil {
return err
}
go func() {
for {
uapiConn, err := uapi.Accept()
if err != nil {
log.Debugln("uapi Accept failed with error: ", err)
continue
}
go tunDevice.IpcHandle(uapiConn)
}
}()
log.Debugln("UAPI listener started")
err = assignAddr(address, iface)
if err != nil {
return err
}
return nil
// WGIface represents a interface instance
type WGIface struct {
Name string
Port int
MTU int
Address WGAddress
Interface NetInterface
}
// configure peer for the wireguard device
func configureDevice(iface string, config wgtypes.Config) error {
wg, err := wgctrl.New()
if err != nil {
return err
}
defer wg.Close()
// WGAddress Wireguard parsed address
type WGAddress struct {
IP net.IP
Network *net.IPNet
}
_, err = wg.Device(iface)
if err != nil {
return err
}
log.Debugf("got Wireguard device %s", iface)
// NetInterface represents a generic network tunnel interface
type NetInterface interface {
Close() error
}
return wg.ConfigureDevice(iface, config)
// NewWGIface Creates a new Wireguard interface instance
func NewWGIface(iface string, address string, mtu int) (WGIface, error) {
wgIface := WGIface{
Name: iface,
MTU: mtu,
}
wgAddress, err := parseAddress(address)
if err != nil {
return wgIface, err
}
wgIface.Address = wgAddress
return wgIface, nil
}
// Exists checks whether specified Wireguard device exists or not
@ -99,140 +72,35 @@ func Exists(iface string) (*bool, error) {
return &exists, nil
}
// Configure configures a Wireguard interface
// The interface must exist before calling this method (e.g. call interface.Create() before)
func Configure(iface string, privateKey string, port int) error {
log.Debugf("configuring Wireguard interface %s", iface)
log.Debugf("adding Wireguard private key")
key, err := wgtypes.ParseKey(privateKey)
// parseAddress parse a string ("1.2.3.4/24") address to WG Address
func parseAddress(address string) (WGAddress, error) {
ip, network, err := net.ParseCIDR(address)
if err != nil {
return err
return WGAddress{}, err
}
fwmark := 0
config := wgtypes.Config{
PrivateKey: &key,
ReplacePeers: false,
FirewallMark: &fwmark,
ListenPort: &port,
}
return configureDevice(iface, config)
return WGAddress{
IP: ip,
Network: network,
}, nil
}
// GetListenPort returns the listening port of the Wireguard endpoint
func GetListenPort(iface string) (*int, error) {
log.Debugf("getting Wireguard listen port of interface %s", iface)
// Closes the tunnel interface
func (w *WGIface) Close() error {
//discover Wireguard current configuration
wg, err := wgctrl.New()
if err != nil {
return nil, err
}
defer wg.Close()
d, err := wg.Device(iface)
if err != nil {
return nil, err
}
log.Debugf("got Wireguard device listen port %s, %d", iface, d.ListenPort)
return &d.ListenPort, nil
}
// UpdatePeer updates existing Wireguard Peer or creates a new one if doesn't exist
// Endpoint is optional
func UpdatePeer(iface string, peerKey string, allowedIps string, keepAlive time.Duration, endpoint string, preSharedKey *wgtypes.Key) error {
log.Debugf("updating interface %s peer %s: endpoint %s ", iface, peerKey, endpoint)
//parse allowed ips
_, ipNet, err := net.ParseCIDR(allowedIps)
err := w.Interface.Close()
if err != nil {
return err
}
peerKeyParsed, err := wgtypes.ParseKey(peerKey)
if err != nil {
return err
}
peer := wgtypes.PeerConfig{
PublicKey: peerKeyParsed,
ReplaceAllowedIPs: true,
AllowedIPs: []net.IPNet{*ipNet},
PersistentKeepaliveInterval: &keepAlive,
PresharedKey: preSharedKey,
}
config := wgtypes.Config{
Peers: []wgtypes.PeerConfig{peer},
}
err = configureDevice(iface, config)
if err != nil {
return err
}
if endpoint != "" {
return UpdatePeerEndpoint(iface, peerKey, endpoint)
if runtime.GOOS == "darwin" {
sockPath := "/var/run/wireguard/" + w.Name + ".sock"
if _, statErr := os.Stat(sockPath); statErr == nil {
statErr = os.Remove(sockPath)
if statErr != nil {
return statErr
}
}
}
return nil
}
// UpdatePeerEndpoint updates a Wireguard interface Peer with the new endpoint
// Used when NAT hole punching was successful and an update of the remote peer endpoint is required
func UpdatePeerEndpoint(iface string, peerKey string, newEndpoint string) error {
log.Debugf("updating peer %s endpoint %s ", peerKey, newEndpoint)
peerAddr, err := net.ResolveUDPAddr("udp4", newEndpoint)
if err != nil {
return err
}
log.Debugf("parsed peer endpoint [%s]", peerAddr.String())
peerKeyParsed, err := wgtypes.ParseKey(peerKey)
if err != nil {
return err
}
peer := wgtypes.PeerConfig{
PublicKey: peerKeyParsed,
ReplaceAllowedIPs: false,
UpdateOnly: true,
Endpoint: peerAddr,
}
config := wgtypes.Config{
Peers: []wgtypes.PeerConfig{peer},
}
return configureDevice(iface, config)
}
// RemovePeer removes a Wireguard Peer from the interface iface
func RemovePeer(iface string, peerKey string) error {
log.Debugf("Removing peer %s from interface %s ", peerKey, iface)
peerKeyParsed, err := wgtypes.ParseKey(peerKey)
if err != nil {
return err
}
peer := wgtypes.PeerConfig{
PublicKey: peerKeyParsed,
Remove: true,
}
config := wgtypes.Config{
Peers: []wgtypes.PeerConfig{peer},
}
return configureDevice(iface, config)
}
// CloseWithUserspace closes the User Space tunnel interface
func CloseWithUserspace() error {
return tunIface.Close()
}

View File

@ -2,62 +2,31 @@ package iface
import (
log "github.com/sirupsen/logrus"
"net"
"os"
"os/exec"
"strings"
)
// Create Creates a new Wireguard interface, sets a given IP and brings it up.
func Create(iface string, address string) error {
return CreateWithUserspace(iface, address)
func (w *WGIface) Create() error {
return w.CreateWithUserspace()
}
// assignAddr Adds IP address to the tunnel interface and network route based on the range provided
func assignAddr(address string, ifaceName string) error {
ip := strings.Split(address, "/")
cmd := exec.Command("ifconfig", ifaceName, "inet", address, ip[0])
func (w *WGIface) assignAddr() error {
//mask,_ := w.Address.Network.Mask.Size()
//
//address := fmt.Sprintf("%s/%d",w.Address.IP.String() , mask)
cmd := exec.Command("ifconfig", w.Name, "inet", w.Address.IP.String(), w.Address.IP.String())
if out, err := cmd.CombinedOutput(); err != nil {
log.Infof("Command: %v failed with output %s and error: ", cmd.String(), out)
log.Infof("adding addreess command \"%v\" failed with output %s and error: ", cmd.String(), out)
return err
}
_, resolvedNet, err := net.ParseCIDR(address)
err = addRoute(ifaceName, resolvedNet)
if err != nil {
log.Infoln("Adding route failed with error:", err)
}
return nil
}
// addRoute Adds network route based on the range provided
func addRoute(iface string, ipNet *net.IPNet) error {
cmd := exec.Command("route", "add", "-net", ipNet.String(), "-interface", iface)
if out, err := cmd.CombinedOutput(); err != nil {
log.Printf("Command: %v failed with output %s and error: ", cmd.String(), out)
return err
}
return nil
}
// Closes the tunnel interface
func Close(iFace string) error {
name, err := tunIface.Name()
if err != nil {
return err
}
sockPath := "/var/run/wireguard/" + name + ".sock"
err = CloseWithUserspace()
if err != nil {
return err
}
if _, err := os.Stat(sockPath); err == nil {
err = os.Remove(sockPath)
if err != nil {
return err
}
routeCmd := exec.Command("route", "add", "-net", w.Address.Network.String(), "-interface", w.Name)
if out, err := routeCmd.CombinedOutput(); err != nil {
log.Printf("adding route command \"%v\" failed with output %s and error: ", routeCmd.String(), out)
return err
}
return nil
}

View File

@ -1,35 +1,36 @@
package iface
import (
"fmt"
log "github.com/sirupsen/logrus"
"github.com/vishvananda/netlink"
"os"
)
type NativeLink struct {
Link *netlink.Link
}
// Create Creates a new Wireguard interface, sets a given IP and brings it up.
// Will reuse an existing one.
func Create(iface string, address string) error {
func (w *WGIface) Create() error {
if WireguardModExists() {
log.Debug("using kernel Wireguard module")
return CreateWithKernel(iface, address)
return w.CreateWithKernel()
} else {
return CreateWithUserspace(iface, address)
return w.CreateWithUserspace()
}
}
// CreateWithKernel Creates a new Wireguard interface using kernel Wireguard module.
// Works for Linux and offers much better network performance
func CreateWithKernel(iface string, address string) error {
attrs := netlink.NewLinkAttrs()
attrs.Name = iface
func (w *WGIface) CreateWithKernel() error {
link := wgLink{
attrs: &attrs,
}
link := newWGLink(w.Name)
// check if interface exists
l, err := netlink.LinkByName(iface)
l, err := netlink.LinkByName(w.Name)
if err != nil {
switch err.(type) {
case netlink.LinkNotFoundError:
@ -41,37 +42,39 @@ func CreateWithKernel(iface string, address string) error {
// remove if interface exists
if l != nil {
err = netlink.LinkDel(&link)
err = netlink.LinkDel(link)
if err != nil {
return err
}
}
log.Debugf("adding device: %s", iface)
err = netlink.LinkAdd(&link)
log.Debugf("adding device: %s", w.Name)
err = netlink.LinkAdd(link)
if os.IsExist(err) {
log.Infof("interface %s already exists. Will reuse.", iface)
log.Infof("interface %s already exists. Will reuse.", w.Name)
} else if err != nil {
return err
}
err = assignAddr(address, iface)
w.Interface = link
err = w.assignAddr()
if err != nil {
return err
}
// todo do a discovery
log.Debugf("setting MTU: %d interface: %s", defaultMTU, iface)
err = netlink.LinkSetMTU(&link, defaultMTU)
log.Debugf("setting MTU: %d interface: %s", w.MTU, w.Name)
err = netlink.LinkSetMTU(link, w.MTU)
if err != nil {
log.Errorf("error setting MTU on interface: %s", iface)
log.Errorf("error setting MTU on interface: %s", w.Name)
return err
}
log.Debugf("bringing up interface: %s", iface)
err = netlink.LinkSetUp(&link)
log.Debugf("bringing up interface: %s", w.Name)
err = netlink.LinkSetUp(link)
if err != nil {
log.Errorf("error bringing up interface: %s", iface)
log.Errorf("error bringing up interface: %s", w.Name)
return err
}
@ -79,39 +82,37 @@ func CreateWithKernel(iface string, address string) error {
}
// assignAddr Adds IP address to the tunnel interface
func assignAddr(address, name string) error {
var err error
attrs := netlink.NewLinkAttrs()
attrs.Name = name
func (w *WGIface) assignAddr() error {
link := wgLink{
attrs: &attrs,
}
mask, _ := w.Address.Network.Mask.Size()
address := fmt.Sprintf("%s/%d", w.Address.IP.String(), mask)
link := newWGLink(w.Name)
//delete existing addresses
list, err := netlink.AddrList(&link, 0)
list, err := netlink.AddrList(link, 0)
if err != nil {
return err
}
if len(list) > 0 {
for _, a := range list {
err = netlink.AddrDel(&link, &a)
err = netlink.AddrDel(link, &a)
if err != nil {
return err
}
}
}
log.Debugf("adding address %s to interface: %s", address, attrs.Name)
log.Debugf("adding address %s to interface: %s", address, w.Name)
addr, _ := netlink.ParseAddr(address)
err = netlink.AddrAdd(&link, addr)
err = netlink.AddrAdd(link, addr)
if os.IsExist(err) {
log.Infof("interface %s already has the address: %s", attrs.Name, address)
log.Infof("interface %s already has the address: %s", w.Name, address)
} else if err != nil {
return err
}
// On linux, the link must be brought up
err = netlink.LinkSetUp(&link)
err = netlink.LinkSetUp(link)
return err
}
@ -119,28 +120,26 @@ type wgLink struct {
attrs *netlink.LinkAttrs
}
func newWGLink(name string) *wgLink {
attrs := netlink.NewLinkAttrs()
attrs.Name = name
return &wgLink{
attrs: &attrs,
}
}
// Attrs returns the Wireguard's default attributes
func (w *wgLink) Attrs() *netlink.LinkAttrs {
return w.attrs
func (l *wgLink) Attrs() *netlink.LinkAttrs {
return l.attrs
}
// Type returns the interface type
func (w *wgLink) Type() string {
func (l *wgLink) Type() string {
return "wireguard"
}
// Close closes the tunnel interface
func Close(iFace string) error {
if tunIface != nil {
return CloseWithUserspace()
} else {
attrs := netlink.NewLinkAttrs()
attrs.Name = iFace
link := wgLink{
attrs: &attrs,
}
return netlink.LinkDel(&link)
}
// Close deletes the link interface
func (l *wgLink) Close() error {
return netlink.LinkDel(l)
}

View File

@ -12,25 +12,36 @@ import (
// keep darwin compability
const (
key = "0PMI6OkB5JmB+Jj/iWWHekuQRx+bipZirWCWKFXexHc="
peerPubKey = "Ok0mC0qlJyXEPKh2UFIpsI2jG0L7LRpC3sLAusSJ5CQ="
WgPort = 51820
WgPort = 51000
)
var (
key string
peerPubKey string
)
func init() {
log.SetLevel(log.DebugLevel)
privateKey, _ := wgtypes.GeneratePrivateKey()
key = privateKey.String()
peerPrivateKey, _ := wgtypes.GeneratePrivateKey()
peerPubKey = peerPrivateKey.PublicKey().String()
}
//
func Test_CreateInterface(t *testing.T) {
ifaceName := "utun999"
wgIP := "10.99.99.1/24"
err := Create(ifaceName, wgIP)
iface, err := NewWGIface(ifaceName, wgIP, DefaultMTU)
if err != nil {
t.Fatal(err)
}
err = iface.Create()
if err != nil {
t.Fatal(err)
}
defer func() {
err = Close(ifaceName)
err = iface.Close()
if err != nil {
t.Error(err)
}
@ -46,21 +57,54 @@ func Test_CreateInterface(t *testing.T) {
}
}()
}
func Test_ConfigureInterface(t *testing.T) {
ifaceName := "utun1000"
wgIP := "10.99.99.10/24"
err := Create(ifaceName, wgIP)
func Test_Close(t *testing.T) {
ifaceName := "utun1004"
wgIP := "10.99.99.50/24"
iface, err := NewWGIface(ifaceName, wgIP, DefaultMTU)
if err != nil {
t.Fatal(err)
}
err = iface.Create()
if err != nil {
t.Fatal(err)
}
wg, err := wgctrl.New()
if err != nil {
t.Fatal(err)
}
defer func() {
err = Close(ifaceName)
err = wg.Close()
if err != nil {
t.Error(err)
}
}()
err = Configure(ifaceName, key, WgPort)
err = iface.Close()
if err != nil {
t.Fatal(err)
}
}
func Test_ConfigureInterface(t *testing.T) {
ifaceName := "utun1000"
wgIP := "10.99.99.10/24"
iface, err := NewWGIface(ifaceName, wgIP, DefaultMTU)
if err != nil {
t.Fatal(err)
}
err = iface.Create()
if err != nil {
t.Fatal(err)
}
defer func() {
err = iface.Close()
if err != nil {
t.Error(err)
}
}()
err = iface.Configure(key, WgPort+1)
if err != nil {
t.Fatal(err)
}
@ -88,28 +132,35 @@ func Test_ConfigureInterface(t *testing.T) {
func Test_UpdatePeer(t *testing.T) {
ifaceName := "utun1001"
wgIP := "10.99.99.20/24"
err := Create(ifaceName, wgIP)
iface, err := NewWGIface(ifaceName, wgIP, DefaultMTU)
if err != nil {
t.Fatal(err)
}
err = iface.Create()
if err != nil {
t.Fatal(err)
}
defer func() {
err = Close(ifaceName)
err = iface.Close()
if err != nil {
t.Error(err)
}
}()
err = Configure(ifaceName, key, WgPort)
err = iface.Configure(key, WgPort+2)
if err != nil {
t.Fatal(err)
}
keepAlive := 15 * time.Second
allowedIP := "10.99.99.2/32"
endpoint := "127.0.0.1:9900"
err = UpdatePeer(ifaceName, peerPubKey, allowedIP, keepAlive, endpoint, nil)
endpoint, err := net.ResolveUDPAddr("udp", "127.0.0.1:9900")
if err != nil {
t.Fatal(err)
}
peer, err := getPeer(ifaceName, t)
err = iface.UpdatePeer(peerPubKey, allowedIP, keepAlive, endpoint, nil)
if err != nil {
t.Fatal(err)
}
peer, err := getPeer(ifaceName, peerPubKey, t)
if err != nil {
t.Fatal(err)
}
@ -117,11 +168,7 @@ func Test_UpdatePeer(t *testing.T) {
t.Fatal("configured peer with mismatched keepalive interval value")
}
resolvedEndpoint, err := net.ResolveUDPAddr("udp", endpoint)
if err != nil {
t.Fatal(err)
}
if peer.Endpoint.String() != resolvedEndpoint.String() {
if peer.Endpoint.String() != endpoint.String() {
t.Fatal("configured peer with mismatched endpoint")
}
@ -137,104 +184,132 @@ func Test_UpdatePeer(t *testing.T) {
}
}
func Test_UpdatePeerEndpoint(t *testing.T) {
ifaceName := "utun1002"
wgIP := "10.99.99.30/24"
err := Create(ifaceName, wgIP)
if err != nil {
t.Fatal(err)
}
defer func() {
err = Close(ifaceName)
if err != nil {
t.Error(err)
}
}()
err = Configure(ifaceName, key, WgPort)
if err != nil {
t.Fatal(err)
}
keepAlive := 15 * time.Second
allowedIP := "10.99.99.2/32"
endpoint := "127.0.0.1:9900"
err = UpdatePeer(ifaceName, peerPubKey, allowedIP, keepAlive, endpoint, nil)
if err != nil {
t.Fatal(err)
}
newEndpoint := "127.0.0.1:9999"
err = UpdatePeerEndpoint(ifaceName, peerPubKey, newEndpoint)
if err != nil {
t.Fatal(err)
}
peer, err := getPeer(ifaceName, t)
if err != nil {
t.Fatal(err)
}
if peer.Endpoint.String() != newEndpoint {
t.Fatal("configured peer with mismatched endpoint")
}
}
func Test_RemovePeer(t *testing.T) {
ifaceName := "utun1003"
wgIP := "10.99.99.40/24"
err := Create(ifaceName, wgIP)
iface, err := NewWGIface(ifaceName, wgIP, DefaultMTU)
if err != nil {
t.Fatal(err)
}
err = iface.Create()
if err != nil {
t.Fatal(err)
}
defer func() {
err = Close(ifaceName)
err = iface.Close()
if err != nil {
t.Error(err)
}
}()
err = Configure(ifaceName, key, WgPort)
err = iface.Configure(key, WgPort+3)
if err != nil {
t.Fatal(err)
}
keepAlive := 15 * time.Second
allowedIP := "10.99.99.2/32"
endpoint := "127.0.0.1:9900"
err = UpdatePeer(ifaceName, peerPubKey, allowedIP, keepAlive, endpoint, nil)
err = iface.UpdatePeer(peerPubKey, allowedIP, keepAlive, nil, nil)
if err != nil {
t.Fatal(err)
}
err = RemovePeer(ifaceName, peerPubKey)
err = iface.RemovePeer(peerPubKey)
if err != nil {
t.Fatal(err)
}
_, err = getPeer(ifaceName, t)
_, err = getPeer(ifaceName, peerPubKey, t)
if err.Error() != "peer not found" {
t.Fatal(err)
}
}
func Test_Close(t *testing.T) {
ifaceName := "utun1004"
wgIP := "10.99.99.50/24"
err := Create(ifaceName, wgIP)
func Test_ConnectPeers(t *testing.T) {
peer1ifaceName := fmt.Sprintf("utun%d", 400)
peer1wgIP := "10.99.99.100/24"
peer1Key, _ := wgtypes.GeneratePrivateKey()
peer1Port := WgPort + 4
peer1endpoint, err := net.ResolveUDPAddr("udp", fmt.Sprintf("127.0.0.1:%d", peer1Port))
if err != nil {
t.Fatal(err)
}
wg, err := wgctrl.New()
peer2ifaceName := fmt.Sprintf("utun%d", 500)
peer2wgIP := "10.99.99.200/24"
peer2Key, _ := wgtypes.GeneratePrivateKey()
peer2Port := WgPort + 5
peer2endpoint, err := net.ResolveUDPAddr("udp", fmt.Sprintf("127.0.0.1:%d", peer2Port))
if err != nil {
t.Fatal(err)
}
keepAlive := 1 * time.Second
iface1, err := NewWGIface(peer1ifaceName, peer1wgIP, DefaultMTU)
if err != nil {
t.Fatal(err)
}
err = iface1.Create()
if err != nil {
t.Fatal(err)
}
iface2, err := NewWGIface(peer2ifaceName, peer2wgIP, DefaultMTU)
if err != nil {
t.Fatal(err)
}
err = iface2.Create()
if err != nil {
t.Fatal(err)
}
defer func() {
err = wg.Close()
err = iface1.Close()
if err != nil {
t.Error(err)
}
err = iface2.Close()
if err != nil {
t.Error(err)
}
}()
err = Close(ifaceName)
err = iface1.Configure(peer1Key.String(), peer1Port)
if err != nil {
t.Fatal(err)
}
err = iface2.Configure(peer2Key.String(), peer2Port)
if err != nil {
t.Fatal(err)
}
err = iface1.UpdatePeer(peer2Key.PublicKey().String(), peer2wgIP, keepAlive, peer2endpoint, nil)
if err != nil {
t.Fatal(err)
}
err = iface2.UpdatePeer(peer1Key.PublicKey().String(), peer1wgIP, keepAlive, peer1endpoint, nil)
if err != nil {
t.Fatal(err)
}
timeout := 10 * time.Second
timeoutChannel := time.After(timeout)
for {
select {
case <-timeoutChannel:
t.Fatalf("waiting for peer handshake timeout after %s", timeout.String())
default:
}
peer, gpErr := getPeer(peer1ifaceName, peer2Key.PublicKey().String(), t)
if gpErr != nil {
t.Fatal(gpErr)
}
if !peer.LastHandshakeTime.IsZero() {
t.Log("peers successfully handshake")
break
}
}
}
func getPeer(ifaceName string, t *testing.T) (wgtypes.Peer, error) {
func getPeer(ifaceName, peerPubKey string, t *testing.T) (wgtypes.Peer, error) {
emptyPeer := wgtypes.Peer{}
wg, err := wgctrl.New()
if err != nil {

View File

@ -1,12 +1,58 @@
//go:build linux || darwin
// +build linux darwin
package iface
import (
log "github.com/sirupsen/logrus"
"golang.zx2c4.com/wireguard/conn"
"golang.zx2c4.com/wireguard/device"
"golang.zx2c4.com/wireguard/ipc"
"golang.zx2c4.com/wireguard/tun"
"net"
)
// CreateWithUserspace Creates a new Wireguard interface, using wireguard-go userspace implementation
func (w *WGIface) CreateWithUserspace() error {
tunIface, err := tun.CreateTUN(w.Name, w.MTU)
if err != nil {
return err
}
w.Interface = tunIface
// We need to create a wireguard-go device and listen to configuration requests
tunDevice := device.NewDevice(tunIface, conn.NewDefaultBind(), device.NewLogger(device.LogLevelSilent, "[wiretrustee] "))
err = tunDevice.Up()
if err != nil {
return err
}
uapi, err := getUAPI(w.Name)
if err != nil {
return err
}
go func() {
for {
uapiConn, uapiErr := uapi.Accept()
if uapiErr != nil {
log.Traceln("uapi Accept failed with error: ", uapiErr)
continue
}
go tunDevice.IpcHandle(uapiConn)
}
}()
log.Debugln("UAPI listener started")
err = w.assignAddr()
if err != nil {
return err
}
return nil
}
// getUAPI returns a Listener
func getUAPI(iface string) (net.Listener, error) {
tunSock, err := ipc.UAPIOpen(iface)

View File

@ -1,46 +1,47 @@
package iface
import (
"fmt"
log "github.com/sirupsen/logrus"
"golang.zx2c4.com/wireguard/ipc"
"golang.zx2c4.com/wireguard/tun"
"golang.org/x/sys/windows"
"golang.zx2c4.com/wireguard/windows/driver"
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
"net"
)
// Create Creates a new Wireguard interface, sets a given IP and brings it up.
func Create(iface string, address string) error {
return CreateWithUserspace(iface, address)
func (w *WGIface) Create() error {
WintunStaticRequestedGUID, _ := windows.GenerateGUID()
adapter, err := driver.CreateAdapter(w.Name, "WireGuard", &WintunStaticRequestedGUID)
if err != nil {
err = fmt.Errorf("error creating adapter: %w", err)
return err
}
w.Interface = adapter
luid := adapter.LUID()
err = adapter.SetLogging(driver.AdapterLogOn)
if err != nil {
err = fmt.Errorf("Error enabling adapter logging: %w", err)
return err
}
err = adapter.SetAdapterState(driver.AdapterStateUp)
if err != nil {
return err
}
state, _ := luid.GUID()
log.Debugln("device guid: ", state.String())
return w.assignAddr(luid)
}
// assignAddr Adds IP address to the tunnel interface and network route based on the range provided
func assignAddr(address string, ifaceName string) error {
func (w *WGIface) assignAddr(luid winipcfg.LUID) error {
nativeTunDevice := tunIface.(*tun.NativeTun)
luid := winipcfg.LUID(nativeTunDevice.LUID())
ip, ipnet, _ := net.ParseCIDR(address)
log.Debugf("adding address %s to interface: %s", address, ifaceName)
err := luid.SetIPAddresses([]net.IPNet{{ip, ipnet.Mask}})
log.Debugf("adding address %s to interface: %s", w.Address.IP, w.Name)
err := luid.SetIPAddresses([]net.IPNet{{w.Address.IP, w.Address.Network.Mask}})
if err != nil {
return err
}
log.Debugf("adding Routes to interface: %s", ifaceName)
err = luid.SetRoutes([]*winipcfg.RouteData{{*ipnet, ipnet.IP, 0}})
if err != nil {
return err
}
return nil
}
// getUAPI returns a Listener
func getUAPI(iface string) (net.Listener, error) {
return ipc.UAPIListen(iface)
}
// Closes the tunnel interface
func Close(iFace string) error {
return CloseWithUserspace()
}

View File

@ -93,6 +93,12 @@ var _ = Describe("Client", func() {
_, err = io.Copy(hashDst, dstFile)
Expect(err).NotTo(HaveOccurred())
err = srcFile.Close()
Expect(err).NotTo(HaveOccurred())
err = dstFile.Close()
Expect(err).NotTo(HaveOccurred())
Expect(hex.EncodeToString(hashSrc.Sum(nil)[:16])).To(BeEquivalentTo(hex.EncodeToString(hashDst.Sum(nil)[:16])))
})
})