hvpn-node3/cmd/hvpn-node/hvpn-node.go

258 lines
6.4 KiB
Go
Raw Normal View History

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
}