init:
- Working basic wg controls - Working ip_pool allocation - Working basic HTTP API Signed-off-by: HeshamTB <hishaminv@gmail.com>
This commit is contained in:
commit
1a611616bd
2
cmd/hvpn-node/.gitignore
vendored
Normal file
2
cmd/hvpn-node/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
hvpn-node
|
||||
|
257
cmd/hvpn-node/hvpn-node.go
Normal file
257
cmd/hvpn-node/hvpn-node.go
Normal 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
27
go.mod
Normal 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
39
go.sum
Normal 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
137
handlers.go
Normal 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
28
init/isolate.sh
Executable 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
21
init/systemd/hvpn.service
Normal 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
102
ip_pool.go
Normal 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
69
ip_pool_test.go
Normal 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
77
link.go
Normal 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
5
proto/error.go
Normal file
@ -0,0 +1,5 @@
|
||||
package proto
|
||||
|
||||
type ErrJSONResponse struct {
|
||||
Message string `json:"message"`
|
||||
}
|
26
proto/peer.go
Normal file
26
proto/peer.go
Normal 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
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user