- Working basic wg controls
    - Working ip_pool allocation
    - Working basic HTTP API

Signed-off-by: HeshamTB <hishaminv@gmail.com>
This commit is contained in:
HeshamTB 2024-03-11 17:34:06 +03:00
commit 1a611616bd
Signed by: Hesham
GPG Key ID: 74876157D199B09E
12 changed files with 790 additions and 0 deletions

2
cmd/hvpn-node/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
hvpn-node

257
cmd/hvpn-node/hvpn-node.go Normal file
View File

@ -0,0 +1,257 @@
package main
import (
"errors"
"fmt"
"log/slog"
"net/http"
"os"
"os/signal"
"time"
"github.com/urfave/cli/v2"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
hvpnnode3 "gitea.hbanafa.com/HeshamTB/hvpn-node3"
)
var IPPool *hvpnnode3.IPPool
var VPNIPCIDR string
var PrivateKeyPath cli.Path
var InterfaceName string
var WgPort int
var wgLink *hvpnnode3.WGLink
var httpPort int
func main() {
// TODO: Define error exit codes
slog.SetLogLoggerLevel(slog.LevelDebug)
app := createCliApp()
err := app.Run(os.Args)
if err != nil {
slog.Error(err.Error())
os.Exit(1)
}
}
func run() {
IPPool, err := hvpnnode3.NewPool(VPNIPCIDR)
if err != nil {
slog.Error(fmt.Sprintf("main.IPPool: %s", err))
os.Exit(1)
}
slog.Info(fmt.Sprintf("Init ip pool %s", VPNIPCIDR))
testVip, err := IPPool.Allocate()
if err != nil {
slog.Error("main.testVip: ", err)
os.Exit(1)
}
slog.Info(fmt.Sprintf("main.testVip: IP Pool Test IP: %s", testVip.String()))
err = IPPool.Free(testVip)
if err != nil {
slog.Error("main.testVip: Could not free test Vip from IPPool!", err)
os.Exit(1)
}
slog.Info("main.testVip: Test IP Freed")
apiMux := http.NewServeMux()
apiMux.HandleFunc("GET /peer", hvpnnode3.HandleGetPeer(wgLink))
apiMux.HandleFunc("POST /peer", hvpnnode3.HandlePostPeer(IPPool, wgLink))
apiMux.HandleFunc("GET /peers", hvpnnode3.HandleGetPeers(wgLink))
var handler http.Handler = apiMux
handler = hvpnnode3.HttpLogHandler2(handler)
port := fmt.Sprintf("%d", httpPort)
host := "0.0.0.0"
hostPort := fmt.Sprintf("%s:%s", host, port)
slog.Info(fmt.Sprintf("Starting HVPN node on %s", hostPort))
srv := &http.Server{
ReadTimeout: 120 * time.Second,
WriteTimeout: 120 * time.Second,
IdleTimeout: 120 * time.Second,
Handler: handler,
Addr: hostPort,
}
defer wgLink.Close()
slog.Warn(srv.ListenAndServe().Error())
}
func createCliApp() *cli.App {
app := cli.NewApp()
app.Name = os.Args[0]
app.HideHelpCommand = true
app.Usage = "HVPN node API server"
app.Args = true
author1 := cli.Author{
Name: "Hesham T. Banafa",
Email: "hishaminv@gmail.com"}
app.Authors = append(app.Authors, &author1)
logLevel := cli.StringFlag{
Name: "log-level",
Value: "INFO",
EnvVars: []string{"LOG_LEVEL"},
Action: func(ctx *cli.Context, s string) error {
switch s {
case "INFO":
slog.SetLogLoggerLevel(slog.LevelInfo)
case "DEBUG":
slog.SetLogLoggerLevel(slog.LevelDebug)
case "WARN":
slog.SetLogLoggerLevel(slog.LevelWarn)
case "ERROR":
slog.SetLogLoggerLevel(slog.LevelError)
default:
return cli.Exit(fmt.Sprintf("Undefined log level: %s", s), 1)
}
return nil
},
}
app.Flags = append(app.Flags, &logLevel)
privateKeyFileFlag := cli.PathFlag{
Name: "private-key",
Required: true,
Usage: "Path to file with private key",
Destination: &PrivateKeyPath,
}
app.Flags = append(app.Flags, &privateKeyFileFlag)
vpnIpCIDR := cli.StringFlag{
Name: "cidr",
Usage: "The network subnet used for the internal IP Pool",
Value: "10.42.0.0/16",
Aliases: []string{"n"},
Destination: &VPNIPCIDR,
}
app.Flags = append(app.Flags, &vpnIpCIDR)
wgInterfaceName := cli.StringFlag{
Name: "interface",
Usage: "Name of the Wireguard interface to be created and managed",
Value: "hvpn0",
Aliases: []string{"i"},
Destination: &InterfaceName,
}
app.Flags = append(app.Flags, &wgInterfaceName)
wgPort := cli.IntFlag{
Name: "port",
Usage: "UDP Port for wireguard device",
Value: 6416,
Aliases: []string{"p"},
Destination: &WgPort,
}
app.Flags = append(app.Flags, &wgPort)
httpPort := cli.IntFlag{
Name: "http-port",
Usage: "TCP Port for HTTP API",
Value: 8080,
Destination: &httpPort,
}
app.Flags = append(app.Flags, &httpPort)
app.Action = func(ctx *cli.Context) error {
err := setup()
if err != nil {
return err
}
run()
return nil
}
return app
}
func setup() error {
privKeyFile, err := os.Open(PrivateKeyPath)
if err != nil {
return cli.Exit(err, 1)
}
defer privKeyFile.Close()
slog.Debug("Keyfile opened for reading")
privateKeyStr := make([]byte, 45)
n, err := privKeyFile.Read(privateKeyStr)
if err != nil {
return cli.Exit(err, 1)
}
if n != 45 {
slog.Warn("Private key length did not math the expected 45!")
}
slog.Debug(fmt.Sprintf("Read %d bytes from keyfile", n))
privateKey, err := wgtypes.ParseKey(string(privateKeyStr))
if err != nil {
return cli.Exit(err, 1)
}
slog.Debug("Private key parsed and is correct")
wg, err := hvpnnode3.InitWGLink(
InterfaceName,
&privateKey,
WgPort,
)
if err != nil {
return cli.Exit(err, 1)
}
wgLink = wg
// this is done to recover from the next call to wgLink.Device call
// idk a better way to recover or prevent the panic when running user
// does not have root or CAP_NET_ADMIN.
defer func() {
if r := recover(); r != nil {
slog.Error(fmt.Sprint(r))
slog.Error("Recovered from panic. Ensure to run as root or with CAP_NET_ADMIN")
cli.Exit(errors.New("Recovered from panic. Ensure to run as root or with CAP_NET_ADMIN"),1)
os.Exit(-1)
}
}()
slog.Info("Wireguard device is setup and running")
dev, err := wgLink.Device(InterfaceName)
if err != nil {
return cli.Exit(err, 1)
}
slog.Info("Initialized Wireguard device using wgctrl")
slog.Info(
fmt.Sprintf(
"Device name: %s, UDP port: %d, Type: %s",
dev.Name, dev.ListenPort, dev.Type.String(),
),
)
//defer wgLink.Close()
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, os.Kill)
go func() {
slog.Debug("Listening for SIGINT, SIGKILL")
<- c
slog.Warn("Recived SIGINT! Closing Wireguard Device")
wgLink.Close()
os.Exit(0)
}()
return nil
}

27
go.mod Normal file
View File

@ -0,0 +1,27 @@
module gitea.hbanafa.com/HeshamTB/hvpn-node3
go 1.22.0
require (
github.com/felixge/httpsnoop v1.0.4
github.com/urfave/cli/v2 v2.27.1
github.com/vishvananda/netlink v1.1.0
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
)
require (
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/josharian/native v1.1.0 // indirect
github.com/mdlayher/genetlink v1.3.2 // indirect
github.com/mdlayher/netlink v1.7.2 // indirect
github.com/mdlayher/socket v0.4.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
golang.org/x/crypto v0.8.0 // indirect
golang.org/x/net v0.9.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.7.0 // indirect
golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b // indirect
)

39
go.sum Normal file
View File

@ -0,0 +1,39 @@
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw=
github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o=
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws=
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho=
github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0=
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df h1:OviZH7qLw/7ZovXvuNyL3XQl8UFofeikI1NW1Gypu7k=
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b h1:J1CaxgLerRR5lgx3wnr6L04cJFbWoceSK9JWBdglINo=
golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b/go.mod h1:tqur9LnfstdR9ep2LaJT4lFUl0EjlHtge+gAjmsHUG4=
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE=
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6/go.mod h1:3rxYc4HtVcSG9gVaTs2GEBdehh+sYPOwKtyUWEOTb80=

137
handlers.go Normal file
View File

@ -0,0 +1,137 @@
package hvpnnode3
import (
"encoding/json"
"fmt"
"log/slog"
"net"
"net/http"
"github.com/felixge/httpsnoop"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
"gitea.hbanafa.com/HeshamTB/hvpn-node3/proto"
)
func HandleGetPeer(wgLink *WGLink) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotImplemented)
slog.Info("GET Peer is not implemented")
}
}
func HandleGetPeers(wgLink *WGLink) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
dev, err := wgLink.Device(wgLink.Name)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
slog.Error(err.Error())
return
}
var peers []proto.Peer
for _, peer := range dev.Peers {
p := proto.Peer{
Address: peer.AllowedIPs[0].IP,
PublicKey: peer.PublicKey.String(),
PersistentKeepalive: peer.PersistentKeepaliveInterval,
TX: peer.TransmitBytes,
RX: peer.ReceiveBytes,
}
peers = append(peers, p)
}
err = json.NewEncoder(w).Encode(peers)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
}
return
}
}
func HandlePostPeer(IPPool IPPool, wgLink *WGLink) http.HandlerFunc {
return func(w http.ResponseWriter, r* http.Request) {
peerRequest := proto.CreatePeerRequest{}
err := json.NewDecoder(r.Body).Decode(&peerRequest)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(
&proto.ErrJSONResponse{Message: "Invalid request JSON"},
)
return
}
// TODO: Check if pubkey is already registered
dev, err := wgLink.Device(wgLink.Name)
if err != nil {
slog.Error(err.Error())
w.WriteHeader(http.StatusInternalServerError)
return
}
for _, peer := range dev.Peers {
if peer.PublicKey.String() == peerRequest.PublicKey {
w.WriteHeader(http.StatusAccepted)
return
}
}
newIP, err := IPPool.Allocate()
if err != nil {
slog.Error(err.Error())
w.WriteHeader(http.StatusInternalServerError)
return
}
// TODO: Add peer to wg device
// TODO: Declare subnet mask for the service
pkey, _ := wgtypes.GeneratePrivateKey()
newPeer := wgtypes.PeerConfig{
PublicKey: pkey.PublicKey(),
ReplaceAllowedIPs: true,
AllowedIPs: []net.IPNet{
{
IP: newIP,
Mask: net.IPv4Mask(255, 255, 255, 255),
},
},
}
err = wgLink.ConfigureDevice(
wgLink.Name,
wgtypes.Config{
Peers: []wgtypes.PeerConfig{
newPeer,
},
ReplacePeers: false,
},
)
if err != nil {
slog.Error(err.Error())
w.WriteHeader(http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(
&proto.Peer{
Address: newIP,
MTU: 1384,
PublicKey: peerRequest.PublicKey,
Endpoint: "vpn.test.com:8487",
AllowedIPs: net.ParseIP("0.0.0.0/0"),
PersistentKeepalive: 25,
},
)
}
}
func HttpLogHandler2(h http.Handler) http.Handler {
// https://blog.kowalczyk.info/article/e00e89c3841e4f8c8c769a78b8a90b47/logging-http-requests-in-go.html
fn := func(w http.ResponseWriter, r* http.Request) {
m := httpsnoop.CaptureMetrics(h, w, r)
msg := fmt.Sprintf(
"%s %s %s %d %s", r.RemoteAddr, r.Method,
r.URL.String(),m.Code, m.Duration)
slog.Info(msg)
}
return http.HandlerFunc(fn)
}

28
init/isolate.sh Executable file
View File

@ -0,0 +1,28 @@
#!/bin/env bash
# A POSIX variable
OPTIND=1 # Reset in case getopts has been used previously in the shell.
# Initialize our own variables:
wg_interface=""
while getopts "h?i:" opt; do
case "$opt" in
h|\?)
#show_help
echo "Usage: isolate.sh -i <wg_interface>"
exit 0
;;
i) wg_interface=$OPTARG
;;
esac
done
shift $((OPTIND-1))
[ "${1:-}" = "--" ] && shift
set -x
iptables -I FORWARD -i $wg_interface -o $wg_interface -j DROP
set +x

21
init/systemd/hvpn.service Normal file
View File

@ -0,0 +1,21 @@
[Unit]
Description=HVPN node service
[Service]
Type=exec
User=hvpnnode
Group=hvpn
ExecStart=/opt/hvpn-node/hvpn-node
AmbientCapabilities=CAP_NET_ADMIN
RemainAfterExit=true
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
PrivateDevices=true
ProtectKernelTunables=true
#Nice=19
#IOSchedulingClass=idle
#IOSchedulingPriority=7
#PrivateDevices=true
#PrivateNetwork=true

102
ip_pool.go Normal file
View File

@ -0,0 +1,102 @@
package hvpnnode3
import (
"encoding/binary"
"errors"
"net"
"sync"
)
// IPPool knows how to allocate IPs and free previously allocated ones.
type IPPool interface {
Allocate() (net.IP, error)
Free(net.IP) error
Remove(...net.IP) error
}
// Pool is a pool of available IP numbers for allocation.
type Pool struct {
network *net.IPNet
available []net.IP
allocMu sync.Mutex
}
// NewPool creates a new pool with a CIDR.
func NewPool(cidr string) (*Pool, error) {
networkip, network, err := net.ParseCIDR(cidr)
if err != nil {
return nil, err
}
mask, size := network.Mask.Size()
maskbits := binary.BigEndian.Uint32(network.Mask)
max := uint32(1 << uint(size-mask))
// Remove unusable host ranges
max -= 2
available := make([]net.IP, max)
networkipbits := maskbits & binary.BigEndian.Uint32(networkip.To4())
for ; max > 0; max-- {
ip := make([]byte, 4)
binary.BigEndian.PutUint32(ip, uint32(networkipbits|max))
available[max-1] = net.IP(ip)
}
return &Pool{
network: network,
available: available,
}, nil
}
// Remove selected IPs from the available pool.
func (p *Pool) Remove(ips ...net.IP) error {
p.allocMu.Lock()
defer p.allocMu.Unlock()
all := p.available
for _, ip := range ips {
if !p.network.Contains(ip) {
return errors.New("IP is not part of this pool")
}
for n, a := range p.available {
if a.Equal(ip) {
all = append(all[:n], all[n+1:]...)
}
}
}
p.available = all
return nil
}
// Allocate assigns a new IP to the pool for use.
func (p *Pool) Allocate() (ip net.IP, err error) {
p.allocMu.Lock()
defer p.allocMu.Unlock()
if len(p.available) > 0 {
ip = p.available[0]
p.available = p.available[1:]
} else {
err = errors.New("No more IPs currently available")
}
return
}
// Free returns the IP to the pool to be used by other allocations.
func (p *Pool) Free(ip net.IP) error {
if !p.network.Contains(ip) {
return errors.New("IP is not part of this pool")
}
p.allocMu.Lock()
defer p.allocMu.Unlock()
p.available = append([]net.IP{ip}, p.available...)
return nil
}
// ip4To6 will prefix IPv4 with the IPv6 network to create an IPv6 address.
func ip4To6(ip4 net.IP, ip6prefix *net.IPNet) (ip6 net.IP) {
b6 := ip6prefix.IP.To16()
return append(b6[:12], ip4.To4()...)
}

69
ip_pool_test.go Normal file
View File

@ -0,0 +1,69 @@
package hvpnnode3
import (
"net"
"testing"
)
func TestPoolIP4(t *testing.T) {
pool, err := NewPool("10.2.0.0/16")
if err != nil {
t.Fatal(err)
}
if l := len(pool.available); l != 65534 {
t.Error("Expected 65534 available addresses, got", l)
}
ip, err := pool.Allocate()
if err != nil {
t.Fatal(err)
}
if l := len(pool.available); l != 65533 {
t.Error("Expected 65533 available addresses, got", l)
}
if err := pool.Free(ip); err != nil {
t.Fatal(err)
}
if l := len(pool.available); l != 65534 {
t.Error("Expected 65534 available addresses, got", l)
}
if ip.String() != "10.2.0.1" {
t.Error("Wrong ip", ip)
}
if err = pool.Free(net.ParseIP("1.2.3.4")); err == nil {
t.Error("expected error trying to free invalid IP")
}
if l := len(pool.available); l != 65534 {
t.Error("Expected 65534 available addresses, got", l)
}
if err := pool.Remove(net.ParseIP("10.2.0.100"), net.ParseIP("10.2.0.200")); err != nil {
t.Fatal(err)
}
if l := len(pool.available); l != 65532 {
t.Error("invalid number of available addresses, ", l)
}
for _, ip := range pool.available {
if ip.Equal(net.ParseIP("10.2.0.100")) {
t.Error("IP was not removed")
break
}
}
}
func TestIP4To6(t *testing.T) {
_, prefix, err := net.ParseCIDR("fdad:b10c:a::/48")
if err != nil {
t.Fatal(err)
}
ip6 := ip4To6(net.IP{10, 1, 2, 3}, prefix)
if s := net.IP(ip6).String(); s != "fdad:b10c:a::a01:203" {
t.Log(s)
t.Error("invalid IP")
}
}

77
link.go Normal file
View File

@ -0,0 +1,77 @@
package hvpnnode3
import (
"errors"
"github.com/vishvananda/netlink"
"golang.zx2c4.com/wireguard/wgctrl"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
)
type WGLink struct {
*netlink.LinkAttrs
*wgctrl.Client
}
// Retruns an existing or create a WGLink
func InitWGLink(ifName string, privateKey *wgtypes.Key, port int) (*WGLink, error){
attrs := netlink.NewLinkAttrs()
attrs.Name = ifName
wg := WGLink{LinkAttrs: &attrs}
link, err := netlink.LinkByName(ifName)
if err != nil {
switch err.(type) {
case netlink.LinkNotFoundError:
if err := netlink.LinkAdd(&wg); err != nil {
return nil, err
}
default:
return nil, err
}
} else {
wg.LinkAttrs = link.Attrs()
}
err = wg.initClient()
if err != nil {
return nil, err
}
err = wg.ConfigureDevice(
ifName,
wgtypes.Config{
PrivateKey: privateKey,
ListenPort: &port,
},
)
if err != nil {
return nil, err
}
return &wg, netlink.LinkSetUp(&wg)
}
func (WGLink) Type() string {
return "wireguard"
}
func (wg *WGLink) Attrs() *netlink.LinkAttrs {
return wg.LinkAttrs
}
func (wg *WGLink) Close() error {
return netlink.LinkDel(wg)
}
func (wg *WGLink) initClient() error {
client, err := wgctrl.New()
if client == nil {
return errors.New("Could not initialize new Wireguard Client")
}
wg.Client = client
return err
}

5
proto/error.go Normal file
View File

@ -0,0 +1,5 @@
package proto
type ErrJSONResponse struct {
Message string `json:"message"`
}

26
proto/peer.go Normal file
View File

@ -0,0 +1,26 @@
package proto
import (
"net"
"time"
)
type CreatePeerRequest struct {
UUID string `json:"uuid"`
PublicKey string `json:"public_key"`
}
/* JSON returned for user to use. This means that the peer can connect with
these parameters as a wireguard peer */
type Peer struct {
Address net.IP `json:"address"`
MTU uint16 `json:"mtu"`
PublicKey string `json:"public_key"`
Endpoint string `json:"endpoint"`
AllowedIPs net.IP `json:"allowed_ips"`
PersistentKeepalive time.Duration `json:"presistent_keepalive"`
TX int64
RX int64
}