package main import ( "bufio" "fmt" "log/slog" "net" "net/http" "net/netip" "net/url" "os" "os/signal" "strings" "time" "github.com/urfave/cli/v2" "golang.zx2c4.com/wireguard/wgctrl/wgtypes" hvpnnode3 "gitea.hbanafa.com/HeshamTB/hvpn-node3" ) /* Can change all therse vars to local func vars IPPool can be internal to wglink or other abstraction. As well as Port, ifname and CIDR and so on. Use Clie.ctx to get the values */ 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) uid := os.Getuid() if uid == -1 { slog.Warn("Running on windows! whatrr u doing?") } else if uid == 0 { slog.Warn("Running as root! avoid running as root by setting CAP_NET_ADMIN") } uuid, err := hvpnnode3.InitNodeUUID() if err != nil { slog.Error(err.Error()) os.Exit(-1) } slog.Info("Node UUID: " + uuid.String()) app := createCliApp() err = app.Run(os.Args) if err != nil { slog.Error(err.Error()) os.Exit(1) } } func run(ctx *cli.Context) { slog.Debug("Starting run()") apiMux := http.NewServeMux() apiMux.HandleFunc("GET /peer/{pubkey}", hvpnnode3.HandleGetPeer(wgLink)) apiMux.HandleFunc("POST /peer", hvpnnode3.HandlePostPeer(wgLink)) apiMux.HandleFunc("DELETE /peer/{pubkey}", hvpnnode3.HandleDeletePeer(wgLink)) apiMux.HandleFunc("GET /peers", hvpnnode3.HandleGetPeers(wgLink)) var handler http.Handler = apiMux handler = hvpnnode3.HttpLogHandler2(handler) port := fmt.Sprintf("%d", httpPort) host := ctx.String("host") if host == "" { slog.Info("Host is not set. Using 0.0.0.0") 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 { lvl := new(slog.LevelVar) err := lvl.UnmarshalText([]byte(s)) if err != nil { slog.Debug("Error on unmarshal log level flag") return err } slog.SetLogLoggerLevel(lvl.Level()) 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) httpListenAddr := cli.StringFlag{ Name: "host", Usage: "IP address to listen on for HTTP API requests", Value: "0.0.0.0", Action: func(ctx *cli.Context, s string) error { _, err := netip.ParseAddr(s) if err != nil { return cli.Exit(fmt.Sprintf("Can not parse %s as a network IP", s), 1) } return nil }, } app.Flags = append(app.Flags, &httpListenAddr) 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(ctx) return nil } return app } func setup() error { slog.Debug("Starting setup()") 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 { slog.Warn("Error while initlizing Wireguard netlink and device!") slog.Warn("Ensure to run as root or with CAP_NET_ADMIN") return cli.Exit(err, 1) } wgLink = wg slog.Info("Wireguard netlink 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(), ), ) ipPool, err := hvpnnode3.NewPool(VPNIPCIDR) if err != nil { slog.Error(fmt.Sprintf("main.IPPool: %s", err)) os.Exit(1) } slog.Debug(fmt.Sprintf("Init ip pool %s", VPNIPCIDR)) testVip, err := ipPool.Allocate() if err != nil { slog.Error("main.testVip: ", err) os.Exit(1) } slog.Debug(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.Debug("main.testVip: Test IP Freed") IPPool = ipPool wgLink.IPPool = ipPool //defer wgLink.Close() cInput := make(chan struct{}) go handleStdin(cInput) c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, os.Kill) go func() { slog.Info("Listening for SIGINT, SIGKILL and user input") select { case <- c: slog.Warn("Recived SIGINT! Closing Wireguard Device") case <- cInput: slog.Warn("Recieved quit! Closing Wireguard Device") } wgLink.Close() os.Exit(0) }() err = testWgPeerAdd(wgLink) if err != nil { return err } return nil } func testWgPeerAdd(wgLink *hvpnnode3.WGLink) error { privateKey, err := wgtypes.GeneratePrivateKey() if err != nil { return err } publicKey := privateKey.PublicKey() urlsafe := url.QueryEscape(publicKey.String()) slog.Debug(urlsafe) ip, err := wgLink.Allocate() if err != nil { return err } peerConfig := wgtypes.PeerConfig{ PublicKey: publicKey, AllowedIPs: []net.IPNet{ { IP: ip, Mask: net.IPv4Mask(255, 255, 255, 255), }, }, } wgConfig := wgtypes.Config{ Peers: []wgtypes.PeerConfig{peerConfig}, } err = wgLink.ConfigureDevice(wgLink.Name, wgConfig) if err != nil { return err } slog.Debug(fmt.Sprintf("Added test peer %v", peerConfig.PublicKey)) wgConfig.ReplacePeers = true wgConfig.Peers = []wgtypes.PeerConfig{} err = wgLink.ConfigureDevice(wgLink.Name, wgConfig) if err != nil { return err } slog.Debug("Removed test peer") wgLink.Free(ip) slog.Debug("Freed test peer ip") return nil } func handleStdin(c chan struct{}) { for { reader := bufio.NewReader(os.Stdin) slog.Info("Enter 'q' or 'exit' to quit") in, _ := reader.ReadString('\n') in = strings.ReplaceAll(in, "\n", "") if in == "q" || in == "exit" { c <- struct{}{} } } }