diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 82b85cd4..4faeea95 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -5,7 +5,6 @@ import ( "crypto/ed25519" "encoding/hex" "encoding/json" - "flag" "fmt" "net" "os" @@ -18,11 +17,13 @@ import ( gsyslog "github.com/hashicorp/go-syslog" "github.com/hjson/hjson-go/v4" "github.com/kardianos/minwinsvc" + "github.com/spf13/cobra" "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/admin" "github.com/yggdrasil-network/yggdrasil-go/src/config" "github.com/yggdrasil-network/yggdrasil-go/src/ipv6rwc" + monitoring "github.com/yggdrasil-network/yggdrasil-go/src/monitoring" "github.com/yggdrasil-network/yggdrasil-go/src/core" "github.com/yggdrasil-network/yggdrasil-go/src/multicast" @@ -37,32 +38,245 @@ type node struct { admin *admin.AdminSocket } +var ( + cfg *config.NodeConfig + logger *log.Logger + ctx context.Context + cancel context.CancelFunc + + rootCmd = &cobra.Command{ + Use: "yggdrasil", + Short: "Yggdrasil managment", + } + + genconfCmd = &cobra.Command{ + Use: "genconf", + Short: "Print a new config to stdout", + RunE: cmdGenconf, + Annotations: map[string]string{"type": "setup"}, + } + + versionCmd = &cobra.Command{ + Use: "version", + Short: "Prints the version of this build", + Run: cmdVersion, + Annotations: map[string]string{"type": "setup"}, + } + + useconffileCmd = &cobra.Command{ + Use: "useconffile ", + Args: cobra.ExactArgs(1), + Short: "Read HJSON/JSON config from specified file path", + RunE: cmdUseconffile, + Annotations: map[string]string{"type": "setup"}, + } + + addressCmd = &cobra.Command{ + Use: "address ", + Args: cobra.ExactArgs(1), + Short: "Outputs your IPv6 address", + RunE: cmdAddress, + Annotations: map[string]string{"type": "setup"}, + } + + snetCmd = &cobra.Command{ + Use: "subnet ", + Args: cobra.ExactArgs(1), + Short: "Outputs your IPv6 subnet", + RunE: cmdSnet, + Annotations: map[string]string{"type": "setup"}, + } + + pkeyCmd = &cobra.Command{ + Use: "publickey ", + Args: cobra.ExactArgs(1), + Short: "Outputs your public key", + RunE: cmdPkey, + Annotations: map[string]string{"type": "setup"}, + } + + exportKeyCmd = &cobra.Command{ + Use: "exportkey ", + Args: cobra.ExactArgs(1), + Short: "Outputs your private key in PEM format", + RunE: cmdExportKey, + Annotations: map[string]string{"type": "setup"}, + } + + logtoCmd = &cobra.Command{ + Use: "logto ", + Args: cobra.ExactArgs(1), + Short: "File path to log to, \"syslog\" or \"stdout\"", + Run: cmdLogto, + Annotations: map[string]string{"type": "setup"}, + } + + autoconfCmd = &cobra.Command{ + Use: "autoconf", + Short: "Automatic mode (dynamic IP, peer with IPv6 neighbors)", + RunE: cmdAutoconf, + Annotations: map[string]string{"type": "setup"}, + } + + useconfCmd = &cobra.Command{ + Use: "useconf", + Short: "Read HJSON/JSON config from stdin", + RunE: cmdUseconf, + Annotations: map[string]string{"type": "setup"}, + } + + normaliseconfCmd = &cobra.Command{ + Use: "normaliseconf ", + Args: cobra.ExactArgs(1), + Short: "Outputs your configuration normalised", + RunE: cmdNormaliseconf, + Annotations: map[string]string{"type": "setup"}, + } +) + // The main function is responsible for configuring and starting Yggdrasil. +func init() { + cfg = config.GenerateConfig() + genconfCmd.Flags().BoolP("json", "j", false, "print configuration as JSON instead of HJSON") + normaliseconfCmd.Flags().BoolP("json", "j", false, "print configuration as JSON instead of HJSON") + autoconfCmd.AddCommand(logtoCmd) + useconffileCmd.AddCommand(logtoCmd) + useconfCmd.AddCommand(logtoCmd) + rootCmd.AddCommand(genconfCmd) + rootCmd.AddCommand(versionCmd) + rootCmd.AddCommand(useconffileCmd) + rootCmd.AddCommand(addressCmd) + rootCmd.AddCommand(snetCmd) + rootCmd.AddCommand(pkeyCmd) + rootCmd.AddCommand(exportKeyCmd) + rootCmd.AddCommand(autoconfCmd) + rootCmd.AddCommand(useconfCmd) + rootCmd.AddCommand(normaliseconfCmd) +} + func main() { - genconf := flag.Bool("genconf", false, "print a new config to stdout") - useconf := flag.Bool("useconf", false, "read HJSON/JSON config from stdin") - useconffile := flag.String("useconffile", "", "read HJSON/JSON config from specified file path") - normaliseconf := flag.Bool("normaliseconf", false, "use in combination with either -useconf or -useconffile, outputs your configuration normalised") - exportkey := flag.Bool("exportkey", false, "use in combination with either -useconf or -useconffile, outputs your private key in PEM format") - confjson := flag.Bool("json", false, "print configuration from -genconf or -normaliseconf as JSON instead of HJSON") - autoconf := flag.Bool("autoconf", false, "automatic mode (dynamic IP, peer with IPv6 neighbors)") - ver := flag.Bool("version", false, "prints the version of this build") - logto := flag.String("logto", "stdout", "file path to log to, \"syslog\" or \"stdout\"") - getaddr := flag.Bool("address", false, "use in combination with either -useconf or -useconffile, outputs your IPv6 address") - getsnet := flag.Bool("subnet", false, "use in combination with either -useconf or -useconffile, outputs your IPv6 subnet") - getpkey := flag.Bool("publickey", false, "use in combination with either -useconf or -useconffile, outputs your public key") - loglevel := flag.String("loglevel", "info", "loglevel to enable") - flag.Parse() + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} - // Catch interrupts from the operating system to exit gracefully. - ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) +func cmdGenconf(cmd *cobra.Command, args []string) (err error) { + confjson, err := cmd.Flags().GetBool("json") + if err != nil { + return err + } + cfg.AdminListen = "" + var bs []byte + if confjson { + bs, err = json.MarshalIndent(cfg, "", " ") + } else { + bs, err = hjson.Marshal(cfg) + } + if err != nil { + return err + } + fmt.Println(string(bs)) + return nil +} - // Capture the service being stopped on Windows. - minwinsvc.SetOnExit(cancel) +func cmdVersion(cmd *cobra.Command, args []string) { + fmt.Println("Build name:", version.BuildName()) + fmt.Println("Build version:", version.BuildVersion()) +} - // Create a new logger that logs output to stdout. - var logger *log.Logger - switch *logto { +func cmdUseconffile(cmd *cobra.Command, args []string) (err error) { + useconffile := args[0] + err = ReadConfigFile(useconffile) + if err != nil { + return err + } + err = run() + if err != nil { + return err + } + return nil +} + +func cmdAutoconf(cmd *cobra.Command, args []string) (err error) { + err = run() + if err != nil { + return err + } + return nil +} + +func cmdUseconf(cmd *cobra.Command, args []string) (err error) { + if _, err := cfg.ReadFrom(os.Stdin); err != nil { + return err + } + err = run() + if err != nil { + return err + } + return nil +} + +func cmdAddress(cmd *cobra.Command, args []string) (err error) { + useconffile := args[0] + err = ReadConfigFile(useconffile) + if err != nil { + return err + } + privateKey := ed25519.PrivateKey(cfg.PrivateKey) + publicKey := privateKey.Public().(ed25519.PublicKey) + addr := address.AddrForKey(publicKey) + ip := net.IP(addr[:]) + fmt.Println(ip.String()) + return nil +} + +func cmdSnet(cmd *cobra.Command, args []string) (err error) { + useconffile := args[0] + err = ReadConfigFile(useconffile) + if err != nil { + return err + } + privateKey := ed25519.PrivateKey(cfg.PrivateKey) + publicKey := privateKey.Public().(ed25519.PublicKey) + snet := address.SubnetForKey(publicKey) + ipnet := net.IPNet{ + IP: append(snet[:], 0, 0, 0, 0, 0, 0, 0, 0), + Mask: net.CIDRMask(len(snet)*8, 128), + } + fmt.Println(ipnet.String()) + return nil +} + +func cmdPkey(cmd *cobra.Command, args []string) (err error) { + useconffile := args[0] + err = ReadConfigFile(useconffile) + if err != nil { + return err + } + privateKey := ed25519.PrivateKey(cfg.PrivateKey) + publicKey := privateKey.Public().(ed25519.PublicKey) + fmt.Println(hex.EncodeToString(publicKey)) + return nil +} + +func cmdExportKey(cmd *cobra.Command, args []string) (err error) { + useconffile := args[0] + err = ReadConfigFile(useconffile) + if err != nil { + return err + } + pem, err := cfg.MarshalPEMPrivateKey() + if err != nil { + panic(err) + } + fmt.Println(string(pem)) + return nil +} + +func cmdLogto(cmd *cobra.Command, args []string) { + logto := args[0] + switch logto { case "stdout": logger = log.New(os.Stdout, "", log.Flags()) @@ -72,120 +286,49 @@ func main() { } default: - if logfd, err := os.OpenFile(*logto, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644); err == nil { + if logfd, err := os.OpenFile(logto, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644); err == nil { logger = log.New(logfd, "", log.Flags()) } } +} + +func cmdNormaliseconf(cmd *cobra.Command, args []string) (err error) { + confjson, err := cmd.Flags().GetBool("json") + if err != nil { + return err + } + useconffile := args[0] + err = ReadConfigFile(useconffile) + if err != nil { + return err + } + cfg.AdminListen = "" + if cfg.PrivateKeyPath != "" { + cfg.PrivateKey = nil + } + var bs []byte + if confjson { + bs, err = json.MarshalIndent(cfg, "", " ") + } else { + bs, err = hjson.Marshal(cfg) + } + if err != nil { + return err + } + fmt.Println(string(bs)) + return nil +} + +func run() (err error) { + ctx, cancel = signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) + // Capture the service being stopped on Windows. + minwinsvc.SetOnExit(cancel) + if logger == nil { logger = log.New(os.Stdout, "", log.Flags()) logger.Warnln("Logging defaulting to stdout") } - if *normaliseconf { - setLogLevel("error", logger) - } else { - setLogLevel(*loglevel, logger) - } - - cfg := config.GenerateConfig() - var err error - switch { - case *ver: - fmt.Println("Build name:", version.BuildName()) - fmt.Println("Build version:", version.BuildVersion()) - return - - case *autoconf: - // Use an autoconf-generated config, this will give us random keys and - // port numbers, and will use an automatically selected TUN interface. - - case *useconf: - if _, err := cfg.ReadFrom(os.Stdin); err != nil { - panic(err) - } - - case *useconffile != "": - f, err := os.Open(*useconffile) - if err != nil { - panic(err) - } - if _, err := cfg.ReadFrom(f); err != nil { - panic(err) - } - _ = f.Close() - - case *genconf: - cfg.AdminListen = "" - var bs []byte - if *confjson { - bs, err = json.MarshalIndent(cfg, "", " ") - } else { - bs, err = hjson.Marshal(cfg) - } - if err != nil { - panic(err) - } - fmt.Println(string(bs)) - return - - default: - fmt.Println("Usage:") - flag.PrintDefaults() - - if *getaddr || *getsnet { - fmt.Println("\nError: You need to specify some config data using -useconf or -useconffile.") - } - return - } - - privateKey := ed25519.PrivateKey(cfg.PrivateKey) - publicKey := privateKey.Public().(ed25519.PublicKey) - - switch { - case *getaddr: - addr := address.AddrForKey(publicKey) - ip := net.IP(addr[:]) - fmt.Println(ip.String()) - return - - case *getsnet: - snet := address.SubnetForKey(publicKey) - ipnet := net.IPNet{ - IP: append(snet[:], 0, 0, 0, 0, 0, 0, 0, 0), - Mask: net.CIDRMask(len(snet)*8, 128), - } - fmt.Println(ipnet.String()) - return - - case *getpkey: - fmt.Println(hex.EncodeToString(publicKey)) - return - - case *normaliseconf: - cfg.AdminListen = "" - if cfg.PrivateKeyPath != "" { - cfg.PrivateKey = nil - } - var bs []byte - if *confjson { - bs, err = json.MarshalIndent(cfg, "", " ") - } else { - bs, err = hjson.Marshal(cfg) - } - if err != nil { - panic(err) - } - fmt.Println(string(bs)) - return - - case *exportkey: - pem, err := cfg.MarshalPEMPrivateKey() - if err != nil { - panic(err) - } - fmt.Println(string(pem)) - return - } - + setLogLevel("info", logger) n := &node{} // Set up the Yggdrasil node itself. @@ -272,14 +415,18 @@ func main() { } } + m, _ := monitoring.New(n.core, logger) + // Block until we are told to shut down. <-ctx.Done() // Shut down the node. + _ = m.Stop() _ = n.admin.Stop() _ = n.multicast.Stop() _ = n.tun.Stop() n.core.Stop() + return nil } func setLogLevel(loglevel string, logger *log.Logger) { @@ -307,3 +454,15 @@ func setLogLevel(loglevel string, logger *log.Logger) { } } } + +func ReadConfigFile(filepath string) error { + f, err := os.Open(filepath) + if err != nil { + return err + } + if _, err := cfg.ReadFrom(f); err != nil { + return err + } + _ = f.Close() + return nil +} diff --git a/contrib/.DS_Store b/contrib/.DS_Store index 61161474..e49898ef 100644 Binary files a/contrib/.DS_Store and b/contrib/.DS_Store differ diff --git a/go.mod b/go.mod index 3dd8c848..ace121ef 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/hjson/hjson-go/v4 v4.4.0 github.com/kardianos/minwinsvc v1.0.2 github.com/quic-go/quic-go v0.44.0 + github.com/stretchr/testify v1.8.2 github.com/vishvananda/netlink v1.1.0 golang.org/x/crypto v0.23.0 golang.org/x/mobile v0.0.0-20240520174638-fa72addaaa1b @@ -24,24 +25,34 @@ require ( require ( github.com/bits-and-blooms/bitset v1.13.0 // indirect github.com/bits-and-blooms/bloom/v3 v3.7.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect - github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect + github.com/google/pprof v0.0.0-20221103000818-d260c55eee4c // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/kr/pretty v0.3.1 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/onsi/ginkgo/v2 v2.9.5 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect go.uber.org/mock v0.4.0 // indirect golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect golang.org/x/mod v0.17.0 // indirect golang.org/x/sync v0.7.0 // indirect golang.org/x/tools v0.21.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect + google.golang.org/protobuf v1.30.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) require ( + github.com/DATA-DOG/go-sqlmock v1.5.2 github.com/VividCortex/ewma v1.2.0 // indirect github.com/fatih/color v1.15.0 // indirect github.com/mattn/go-isatty v0.0.19 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect + github.com/mattn/go-sqlite3 v1.14.22 github.com/olekukonko/tablewriter v0.0.5 + github.com/spf13/cobra v1.8.0 github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f // indirect ) diff --git a/go.sum b/go.sum index 7ee76861..3b735714 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ github.com/Arceliar/ironwood v0.0.0-20240529054413-b8e59574e2b2 h1:SBdYBKeXYUUFe github.com/Arceliar/ironwood v0.0.0-20240529054413-b8e59574e2b2/go.mod h1:6WP4799FX0OuWdENGQAh+0RXp9FLh0y7NZ7tM9cJyXk= github.com/Arceliar/phony v0.0.0-20220903101357-530938a4b13d h1:UK9fsWbWqwIQkMCz1CP+v5pGbsGoWAw6g4AyvMpm1EM= github.com/Arceliar/phony v0.0.0-20220903101357-530938a4b13d/go.mod h1:BCnxhRf47C/dy/e/D2pmB8NkB3dQVIrkD98b220rx5Q= +github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= +github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= github.com/bits-and-blooms/bitset v1.10.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= @@ -11,9 +13,8 @@ github.com/bits-and-blooms/bloom/v3 v3.7.0 h1:VfknkqV4xI+PsaDIsoHueyxVDZrfvMn56j github.com/bits-and-blooms/bloom/v3 v3.7.0/go.mod h1:VKlUSvp0lFIYqxJjzdnSsZEw4iHb1kOL2tfHTgyJBHg= github.com/cheggaaa/pb/v3 v3.1.4 h1:DN8j4TVVdKu3WxVwcRKu0sG00IIU6FewoABZzXbRQeo= github.com/cheggaaa/pb/v3 v3.1.4/go.mod h1:6wVjILNBaXMs8c21qRiaUM8BR82erfgau1DQ4iUXmSA= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -23,21 +24,29 @@ github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/gologme/log v1.3.0 h1:l781G4dE+pbigClDSDzSaaYKtiueHCILUa/qSDsmHAo= github.com/gologme/log v1.3.0/go.mod h1:yKT+DvIPdDdDoPtqFrFxheooyVmoqi0BAsw+erN3wA4= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20221103000818-d260c55eee4c h1:lvddKcYTQ545ADhBujtIJmqQrZBDsGo7XIMbAQe/sNY= +github.com/google/pprof v0.0.0-20221103000818-d260c55eee4c/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hjson/hjson-go/v4 v4.4.0 h1:D/NPvqOCH6/eisTb5/ztuIS8GUvmpHaLOcNk1Bjr298= github.com/hjson/hjson-go/v4 v4.4.0/go.mod h1:KaYt3bTw3zhBjYqnXkYywcYctk0A2nxeEFTse3rH13E= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/kardianos/minwinsvc v1.0.2 h1:JmZKFJQrmTGa/WiW+vkJXKmfzdjabuEW4Tirj5lLdR0= github.com/kardianos/minwinsvc v1.0.2/go.mod h1:LUZNYhNmxujx2tR7FbdxqYJ9XDDoCd3MQcl1o//FWl4= +github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= @@ -46,21 +55,36 @@ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/quic-go/quic-go v0.44.0 h1:So5wOr7jyO4vzL2sd8/pD9Kesciv91zSk8BoFngItQ0= github.com/quic-go/quic-go v0.44.0/go.mod h1:z4cx/9Ny9UtGITIPzmPTXh1ULfOyWh4qGQlpnPcWmek= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg= github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ= github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0= @@ -99,7 +123,6 @@ golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -135,15 +158,19 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw= golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= golang.zx2c4.com/wireguard v0.0.0-20230223181233-21636207a675 h1:/J/RVnr7ng4fWPRH3xa4WtBJ1Jp+Auu4YNLmGiPv5QU= golang.zx2c4.com/wireguard v0.0.0-20230223181233-21636207a675/go.mod h1:whfbyDBt09xhCYQWtO2+3UVjlaq6/9hDZrjg2ZE6SyA= golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE= golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI= -google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/src/core/api.go b/src/core/api.go index 875d7bf2..20e1b38d 100644 --- a/src/core/api.go +++ b/src/core/api.go @@ -1,8 +1,11 @@ package core import ( + "bytes" "crypto/ed25519" + "encoding/binary" "encoding/json" + "fmt" "net" "net/url" "sync/atomic" @@ -250,3 +253,29 @@ func (c *Core) SetAdmin(a AddHandler) error { } return nil } + +func (peerinfo PeerInfo) GetCoordinates() *[]byte { + var coordsBlob []byte + if peerinfo.Coords != nil { + coordsBlob = make([]byte, len(peerinfo.Coords)*8) + for i, coord := range peerinfo.Coords { + binary.LittleEndian.PutUint64(coordsBlob[i*8:], coord) + } + } + return &coordsBlob +} + +func (peerinfo PeerInfo) SetCoordinates(coords *[]byte) error { + if len(*coords)%8 != 0 { + return fmt.Errorf("length of byte slice must be a multiple of 8") + } + numUint64 := len(*coords) / 8 + buf := bytes.NewReader(*coords) + for i := 0; i < numUint64; i++ { + err := binary.Read(buf, binary.LittleEndian, &peerinfo.Coords[i]) + if err != nil { + return err + } + } + return nil +} diff --git a/src/db/PeerInfoDB/PeerInfoDB.go b/src/db/PeerInfoDB/PeerInfoDB.go new file mode 100644 index 00000000..c45d13a6 --- /dev/null +++ b/src/db/PeerInfoDB/PeerInfoDB.go @@ -0,0 +1,211 @@ +package peerinfodb + +import ( + "crypto/ed25519" + "crypto/x509" + "encoding/binary" + "fmt" + "os" + "path/filepath" + + _ "github.com/mattn/go-sqlite3" + "github.com/yggdrasil-network/yggdrasil-go/src/core" + "github.com/yggdrasil-network/yggdrasil-go/src/db" +) + +type PeerInfoDBConfig struct { + DbConfig *db.DbConfig + name string +} + +var Name = "PeerInfo" + +func New() (*PeerInfoDBConfig, error) { + dir, _ := os.Getwd() + fileName := fmt.Sprintf("%s.db", Name) + filePath := filepath.Join(dir, fileName) + schemas := []string{ + `CREATE TABLE IF NOT EXISTS peer_infos ( + uri TEXT, + up BOOLEAN, + inbound BOOLEAN, + last_error VARCHAR, + last_error_time TIMESTAMP, + key VARCHAR, + root VARCHAR, + coords VARCHAR, + port INT, + priority TINYINT, + Rxbytes BIGINT, + Txbytes BIGINT, + uptime BIGINT, + latency SMALLINT + );`} + dbcfg, err := db.New("sqlite3", &schemas, filePath) + if err != nil { + return nil, err + } + cfg := &PeerInfoDBConfig{ + name: Name, + DbConfig: dbcfg, + } + return cfg, nil +} + +func (cfg *PeerInfoDBConfig) AddPeer(peer core.PeerInfo) (err error) { + var key, root []byte + if peer.Key != nil { + key, err = x509.MarshalPKIXPublicKey(peer.Key) + if err != nil { + return err + } + } + if peer.Root != nil { + root, err = x509.MarshalPKIXPublicKey(peer.Root) + if err != nil { + return err + } + } + var peerErr interface{} + if peer.LastError != nil { + peerErr = peer.LastError.Error() + } else { + peerErr = nil + } + var coordsBlob []byte + if peer.Coords != nil { + coordsBlob = make([]byte, len(peer.Coords)*8) + for i, coord := range peer.Coords { + binary.LittleEndian.PutUint64(coordsBlob[i*8:], coord) + } + } + if !cfg.DbConfig.DBIsOpened() { + return nil + } + _, err = cfg.DbConfig.DB.Exec(` + INSERT OR REPLACE INTO peer_infos + ( + uri, + up, + inbound, + last_error, + last_error_time, + key, + root, + coords, + port, + priority, + Rxbytes, + Txbytes, + uptime, + latency + ) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + peer.URI, peer.Up, peer.Inbound, peerErr, peer.LastErrorTime, key, root, coordsBlob, peer.Port, peer.Priority, peer.RXBytes, peer.TXBytes, peer.Uptime, peer.Latency) + if err != nil { + return err + } + return nil +} + +func (cfg *PeerInfoDBConfig) RemovePeer(peer core.PeerInfo) (err error) { + key, err := x509.MarshalPKIXPublicKey(peer.Key) + if err != nil { + return err + } + root, err := x509.MarshalPKIXPublicKey(peer.Root) + if err != nil { + return err + } + _, err = cfg.DbConfig.DB.Exec("DELETE FROM peer_infos WHERE uri = ? AND key = ? AND root = ?", + peer.URI, key, root) + if err != nil { + return err + } + return nil +} + +func (cfg *PeerInfoDBConfig) GetPeer(peer *core.PeerInfo) (err error) { + key, err := x509.MarshalPKIXPublicKey(peer.Key) + if err != nil { + return err + } + root, err := x509.MarshalPKIXPublicKey(peer.Root) + if err != nil { + return err + } + row := cfg.DbConfig.DB.QueryRow("SELECT * FROM peer_infos WHERE uri = ? AND key = ? AND root = ?", + peer.URI, key, root) + var coord []byte + var peerErr interface{} + err = row.Scan(&peer.URI, &peer.Up, &peer.Inbound, &peerErr, &peer.LastErrorTime, &key, &root, &coord, &peer.Port, &peer.Priority, &peer.RXBytes, &peer.TXBytes, &peer.Uptime, &peer.Latency) + if err != nil { + return err + } + + parsedKey, err := x509.ParsePKCS8PrivateKey(key) + if err != nil { + return err + } + ParsedRoot, err := x509.ParsePKCS8PrivateKey(root) + if err != nil { + return err + } + peer.Key = parsedKey.(ed25519.PublicKey) + peer.Root = ParsedRoot.(ed25519.PublicKey) + return nil +} + +func (cfg *PeerInfoDBConfig) UpdatePeer(peer core.PeerInfo) (err error) { + key, err := x509.MarshalPKIXPublicKey(peer.Key) + if err != nil { + return err + } + root, err := x509.MarshalPKIXPublicKey(peer.Root) + if err != nil { + return err + } + var peerErr interface{} + if peer.LastError != nil { + peerErr = peer.LastError.Error() + } else { + peerErr = nil + } + var coordsBlob []byte + if peer.Coords != nil { + coordsBlob = make([]byte, len(peer.Coords)*8) + for i, coord := range peer.Coords { + binary.LittleEndian.PutUint64(coordsBlob[i*8:], coord) + } + } + _, err = cfg.DbConfig.DB.Exec(`UPDATE peer_infos + SET + up = ?, + inbound = ?, + last_error = ?, + last_error_time = ?, + coords = ?, + port = ?, + priority = ?, + RXBytes = RXBytes + ?, + TXBytes = TXBytes + ?, + uptime = ?, + latency = ? + WHERE + uri = ? AND key = ? AND root = ?`, + peer.Up, peer.Inbound, peerErr, peer.LastErrorTime, coordsBlob, peer.Port, peer.Priority, + peer.RXBytes, peer.TXBytes, peer.Uptime, peer.Latency, peer.URI, key, root) + if err != nil { + return err + } + return nil +} + +func (cfg *PeerInfoDBConfig) Count() (int, error) { + var count int + err := cfg.DbConfig.DB.QueryRow("SELECT COUNT(*) FROM peer_infos").Scan(&count) + if err != nil { + return 0, err + } + return count, nil +} diff --git a/src/db/db.go b/src/db/db.go new file mode 100644 index 00000000..c229f9fc --- /dev/null +++ b/src/db/db.go @@ -0,0 +1,90 @@ +package db + +import ( + "database/sql" + "os" + "path" +) + +type DbConfig struct { + Uri string + DB *sql.DB + Name string + Driver string +} + +var IsOpened = false + +func New(driver string, schemas *[]string, uri string) (*DbConfig, error) { + name := path.Base(uri) + db, err := initDB(driver, schemas, uri) + if err != nil { + return nil, err + } + cfg := &DbConfig{ + DB: db, + Uri: uri, + Name: name, + Driver: driver, + } + return cfg, nil +} + +func initDB(driver string, schemas *[]string, uri string) (*sql.DB, error) { + database, err := sql.Open(driver, uri) + if err != nil { + return nil, err + } + defer database.Close() + tx, err := database.Begin() + if err != nil { + return nil, err + } + for _, schema := range *schemas { + _, err := tx.Exec(schema) + if err != nil { + tx.Rollback() + return nil, err + } + } + tx.Commit() + return database, nil +} + +func (cfg *DbConfig) DeleteDb() error { + err := os.Remove(cfg.Uri) + if err != nil { + return err + } + return nil +} + +func (cfg *DbConfig) OpenDb() error { + db, err := sql.Open(cfg.Driver, cfg.Uri) + if err != nil { + return err + } + cfg.DB = db + IsOpened = true + return nil +} + +func (cfg *DbConfig) CloseDb() error { + if IsOpened { + err := cfg.DB.Close() + if err != nil { + return err + } + IsOpened = false + } + return nil +} + +func (cfg *DbConfig) DBIsOpened() bool { + return IsOpened +} + +func (cfg *DbConfig) DBIsExist() bool { + _, err := os.Stat(cfg.Uri) + return !os.IsNotExist(err) +} diff --git a/src/db/test/PeerInfo.db b/src/db/test/PeerInfo.db new file mode 100644 index 00000000..a770f6ab Binary files /dev/null and b/src/db/test/PeerInfo.db differ diff --git a/src/db/test/db_test.go b/src/db/test/db_test.go new file mode 100644 index 00000000..118c4e28 --- /dev/null +++ b/src/db/test/db_test.go @@ -0,0 +1,396 @@ +package db_test + +import ( + "bytes" + "crypto/ed25519" + "crypto/rand" + "crypto/x509" + "encoding/binary" + "fmt" + "reflect" + "strconv" + "testing" + "time" + + "github.com/DATA-DOG/go-sqlmock" + _ "github.com/mattn/go-sqlite3" + "github.com/stretchr/testify/require" + "github.com/yggdrasil-network/yggdrasil-go/src/core" + peerinfodb "github.com/yggdrasil-network/yggdrasil-go/src/db/PeerInfoDB" +) + +func TestPeerGetCoords(t *testing.T) { + peer := core.PeerInfo{ + Coords: []uint64{1, 2, 3, 4}, + } + target := []byte{1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0} + var coordinates = peer.GetCoordinates() + if reflect.DeepEqual(target, coordinates) { + t.Error(fmt.Errorf("Not equal")) + } +} + +func TestPeerSetCoords(t *testing.T) { + peer := core.PeerInfo{ + Coords: []uint64{1, 2, 3, 4}, + } + target := []byte{4, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0} + var coordinates = peer.SetCoordinates(&target) + if reflect.DeepEqual(target, coordinates) { + t.Error(fmt.Errorf("Not equal")) + } + fmt.Print(peer.Coords) +} + +func TestAddPeer(t *testing.T) { + mockDB, mock, err := sqlmock.New() + require.NoError(t, err) + defer mockDB.Close() + + cfg, err := peerinfodb.New() + require.NoError(t, err) + cfg.DbConfig.DB = mockDB + + pubkey, _, err := ed25519.GenerateKey(rand.Reader) + require.NoError(t, err) + rootPubKey, _, err := ed25519.GenerateKey(rand.Reader) + require.NoError(t, err) + + peer := core.PeerInfo{ + URI: "test.test", + Up: true, + Inbound: true, + LastError: nil, + LastErrorTime: time.Now(), + Key: pubkey, + Root: rootPubKey, + Coords: []uint64{0, 0, 0, 0}, + Port: 8080, + Priority: 1, + RXBytes: 1024, + TXBytes: 2048, + Uptime: 3600, + Latency: 50.0, + } + + pKey, err := x509.MarshalPKIXPublicKey(peer.Key) + require.NoError(t, err) + pKeyRoot, err := x509.MarshalPKIXPublicKey(peer.Root) + require.NoError(t, err) + var coordsBlob []byte + if peer.Coords != nil { + coordsBlob = make([]byte, len(peer.Coords)*8) + for i, coord := range peer.Coords { + binary.LittleEndian.PutUint64(coordsBlob[i*8:], coord) + } + } + mock.ExpectExec("INSERT OR REPLACE INTO peer_infos"). + WithArgs( + peer.URI, + peer.Up, + peer.Inbound, + nil, + peer.LastErrorTime, + pKey, + pKeyRoot, + coordsBlob, + peer.Port, + peer.Priority, + peer.RXBytes, + peer.TXBytes, + peer.Uptime, + peer.Latency, + ). + WillReturnResult(sqlmock.NewResult(1, 1)) + + err = cfg.AddPeer(peer) + require.NoError(t, err) + + err = mock.ExpectationsWereMet() + require.NoError(t, err) +} + +func TestRemovePeer(t *testing.T) { + mockDB, mock, err := sqlmock.New() + require.NoError(t, err) + defer mockDB.Close() + + cfg, err := peerinfodb.New() + require.NoError(t, err) + cfg.DbConfig.DB = mockDB + + pubkey, _, err := ed25519.GenerateKey(rand.Reader) + require.NoError(t, err) + rootPubKey, _, err := ed25519.GenerateKey(rand.Reader) + require.NoError(t, err) + + peer := core.PeerInfo{ + URI: "test.test", + Up: true, + Inbound: true, + LastError: nil, + LastErrorTime: time.Now(), + Key: pubkey, + Root: rootPubKey, + Coords: []uint64{0, 0, 0, 0}, + Port: 8080, + Priority: 1, + RXBytes: 1024, + TXBytes: 2048, + Uptime: 3600, + Latency: 50.0, + } + + pKey, err := x509.MarshalPKIXPublicKey(peer.Key) + require.NoError(t, err) + pKeyRoot, err := x509.MarshalPKIXPublicKey(peer.Root) + require.NoError(t, err) + mock.ExpectExec("DELETE FROM peer_infos WHERE uri = \\? AND key = \\? AND root = \\?"). + WithArgs(peer.URI, pKey, pKeyRoot). + WillReturnResult(sqlmock.NewResult(1, 1)) + + err = cfg.RemovePeer(peer) + require.NoError(t, err) + + err = mock.ExpectationsWereMet() + require.NoError(t, err) +} + +func TestGetPeer(t *testing.T) { + mockDB, mock, err := sqlmock.New() + require.NoError(t, err) + defer mockDB.Close() + + cfg, err := peerinfodb.New() + require.NoError(t, err) + cfg.DbConfig.DB = mockDB + + pubkey, _, err := ed25519.GenerateKey(rand.Reader) + require.NoError(t, err) + rootPubKey, _, err := ed25519.GenerateKey(rand.Reader) + require.NoError(t, err) + + peer := core.PeerInfo{ + URI: "test.test", + Up: true, + Inbound: true, + LastError: nil, + LastErrorTime: time.Now(), + Key: pubkey, + Root: rootPubKey, + Coords: []uint64{0, 0, 0, 0}, + Port: 8080, + Priority: 1, + RXBytes: 1024, + TXBytes: 2048, + Uptime: 3600, + Latency: 50.0, + } + + pKey, err := x509.MarshalPKIXPublicKey(peer.Key) + require.NoError(t, err) + pKeyRoot, err := x509.MarshalPKIXPublicKey(peer.Root) + require.NoError(t, err) + var coords []byte + rows := sqlmock.NewRows([]string{"uri", "up", "Inbound", "LastError", "LastErrorTime", "Key", "Root", "Coords", "Port", "Priority", "Rxbytes", "Txbytes", "Uptime", "Latency"}). + AddRow(peer.URI, peer.Up, peer.Inbound, peer.LastError, peer.LastErrorTime, peer.Key, peer.Root, coords, peer.Port, peer.Priority, peer.RXBytes, peer.TXBytes, peer.Uptime, peer.Latency) + + mock.ExpectQuery("SELECT * FROM peer_infos WHERE uri = ? AND key = ? AND root = ?"). + WithArgs(peer.URI, pKey, pKeyRoot). + WillReturnRows(rows) + + err = cfg.GetPeer(&peer) + require.NoError(t, err) + + err = mock.ExpectationsWereMet() + require.NoError(t, err) +} + +func TestUpdatePeer(t *testing.T) { + mockDB, mock, err := sqlmock.New() + require.NoError(t, err) + defer mockDB.Close() + + cfg, err := peerinfodb.New() + require.NoError(t, err) + cfg.DbConfig.DB = mockDB + + pubkey, _, err := ed25519.GenerateKey(rand.Reader) + require.NoError(t, err) + rootPubKey, _, err := ed25519.GenerateKey(rand.Reader) + require.NoError(t, err) + + peer := core.PeerInfo{ + URI: "test.test", + Up: true, + Inbound: true, + LastError: nil, + LastErrorTime: time.Now(), + Key: pubkey, + Root: rootPubKey, + Coords: []uint64{0, 0, 0, 0}, + Port: 8080, + Priority: 1, + RXBytes: 1024, + TXBytes: 2048, + Uptime: 3600, + Latency: 50.0, + } + + pKey, err := x509.MarshalPKIXPublicKey(peer.Key) + require.NoError(t, err) + pKeyRoot, err := x509.MarshalPKIXPublicKey(peer.Root) + var coordsBlob []byte + if peer.Coords != nil { + coordsBlob = make([]byte, len(peer.Coords)*8) + for i, coord := range peer.Coords { + binary.LittleEndian.PutUint64(coordsBlob[i*8:], coord) + } + } + require.NoError(t, err) + mock.ExpectExec(`UPDATE peer_infos + SET + up = \?, + inbound = \?, + last_error = \?, + last_error_time = \?, + coords = \?, + port = \?, + priority = \?, + RXBytes = RXBytes \+ \?, + TXBytes = TXBytes \+ \?, + uptime = \?, + latency = \? + WHERE + uri = \? AND key = \? AND root = \?`). + WithArgs( + peer.Up, peer.Inbound, peer.LastError, peer.LastErrorTime, coordsBlob, peer.Port, peer.Priority, + peer.RXBytes, peer.TXBytes, peer.Uptime, peer.Latency, peer.URI, pKey, pKeyRoot). + WillReturnResult(sqlmock.NewResult(1, 1)) + + err = cfg.UpdatePeer(peer) + require.NoError(t, err) + + err = mock.ExpectationsWereMet() + require.NoError(t, err) +} + +// One more test here +func TestMain(t *testing.T) { + peerinfodb.Name = fmt.Sprintf( + "%s.%s", + peerinfodb.Name, + strconv.Itoa(int(time.Now().Unix())), + ) + + peerdb, err := peerinfodb.New() + require.NoError(t, err) + + peerdb.DbConfig.OpenDb() + isOpened := peerdb.DbConfig.DBIsOpened() + condition := func() bool { + return isOpened + } + require.Condition(t, condition, "Expected db is opened", isOpened) + + pubkey, _, err := ed25519.GenerateKey(rand.Reader) + require.NoError(t, err) + rootPubKey, _, err := ed25519.GenerateKey(rand.Reader) + require.NoError(t, err) + peer := core.PeerInfo{ + URI: "test.test", + Up: true, + Inbound: true, + LastError: nil, + LastErrorTime: time.Now(), + Key: pubkey, + Root: rootPubKey, + Coords: []uint64{0, 0, 0, 0}, + Port: 8080, + Priority: 1, + RXBytes: 1024, + TXBytes: 2048, + Uptime: 3600, + Latency: 50.0, + } + + root2PubKey, _, err := ed25519.GenerateKey(rand.Reader) + require.NoError(t, err) + peer2 := core.PeerInfo{ + URI: "new.test", + Up: true, + Inbound: true, + LastError: nil, + LastErrorTime: time.Now(), + Key: pubkey, + Root: root2PubKey, + Coords: []uint64{0, 0, 0, 0}, + Port: 8080, + Priority: 1, + RXBytes: 1024, + TXBytes: 2048, + Uptime: 3600, + Latency: 50.0, + } + + peerdb.AddPeer(peer) + peerdb.AddPeer(peer2) + count, err := peerdb.Count() + require.NoError(t, err) + condition = func() bool { + return count == 2 + } + require.Condition(t, condition, "Expected count to be 2", count) + + peerdb.RemovePeer(peer) + count, err = peerdb.Count() + require.NoError(t, err) + condition = func() bool { + return count == 1 + } + require.Condition(t, condition, "Expected count to be 1", count) + + peer2.Latency = 10 + peer2.RXBytes = 1024 + peer2.TXBytes = 1024 + peer2.Port = 80 + peerdb.UpdatePeer(peer2) + peerdb.GetPeer(&peer2) + condition = func() bool { + return peer2.Latency == 10 && + peer2.RXBytes == 2048 && + peer2.TXBytes == 3072 && + peer2.Port == 80 && peer2.URI == "new.test" && bytes.Equal(peer.Key, pubkey) + } + require.Condition(t, condition, "Inner exception") + + peerdb.RemovePeer(peer2) + count, err = peerdb.Count() + require.NoError(t, err) + + condition = func() bool { + return count == 0 + } + + require.Condition(t, condition, "Expected count to be 0", count) + + err = peerdb.DbConfig.CloseDb() + isOpened = peerdb.DbConfig.DBIsOpened() + + condition = func() bool { + return !isOpened + } + + require.Condition(t, condition, "Expected db is not opened", isOpened) + + require.NoError(t, err) + err = peerdb.DbConfig.DeleteDb() + require.NoError(t, err) + isExist := peerdb.DbConfig.DBIsExist() + + condition = func() bool { + return !isExist + } + + require.Condition(t, condition, "Expected db is not exist", isExist) +} diff --git a/src/monitoring/monitoring.go b/src/monitoring/monitoring.go new file mode 100644 index 00000000..7515bfa9 --- /dev/null +++ b/src/monitoring/monitoring.go @@ -0,0 +1,74 @@ +package monitoring + +import ( + "net" + "sync" + + _ "github.com/mattn/go-sqlite3" + "github.com/yggdrasil-network/yggdrasil-go/src/address" + "github.com/yggdrasil-network/yggdrasil-go/src/core" +) + +type Monitoring struct { + core *core.Core + done chan struct{} + log core.Logger + once sync.Once +} + +func New(c *core.Core, log core.Logger) (*Monitoring, error) { + m := &Monitoring{ + core: c, + log: log, + } + m.done = make(chan struct{}) + go m.Monitoring(func(peer core.PeerInfo) { + log.Printf("Peers: %s", peer.URI) + }, func(session core.SessionInfo) { + addr := address.AddrForKey(session.Key) + addrStr := net.IP(addr[:]).String() + log.Printf("Session: %s", addrStr) + }) + return m, nil +} + +func (m *Monitoring) Stop() error { + if m == nil { + return nil + } + m.once.Do(func() { + close(m.done) + }) + return nil +} + +type PeerMonitoring func(peer core.PeerInfo) +type SessionMonitoring func(peer core.SessionInfo) + +func (m *Monitoring) Monitoring(PeerMonitoringCallBack PeerMonitoring, SessionMonitoringCallBack SessionMonitoring) { + peers := make(map[string]struct{}) + sessions := make(map[string]struct{}) + for { + APIpeers := m.core.GetPeers() + for _, peer := range APIpeers { + if _, exist := peers[peer.URI]; !exist { + PeerMonitoringCallBack(peer) + peers[peer.URI] = struct{}{} + } + } + APIsessions := m.core.GetSessions() + for _, session := range APIsessions { + addr := address.AddrForKey(session.Key) + addrStr := net.IP(addr[:]).String() + if _, exist := sessions[addrStr]; !exist { + SessionMonitoringCallBack(session) + sessions[addrStr] = struct{}{} + } + } + select { + case <-m.done: + return + default: + } + } +}