diff --git a/client/cmd/down.go b/client/cmd/down.go new file mode 100644 index 000000000..31f4c5e77 --- /dev/null +++ b/client/cmd/down.go @@ -0,0 +1,37 @@ +package cmd + +import ( + "context" + "time" + + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + + "github.com/wiretrustee/wiretrustee/client/proto" +) + +var downCmd = &cobra.Command{ + Use: "down", + Short: "down wiretrustee connections", + RunE: func(cmd *cobra.Command, args []string) error { + SetFlagsFromEnvVars() + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) + defer cancel() + + conn, err := DialClientGRPCServer(ctx, daemonAddr) + if err != nil { + log.Errorf("failed to connect to service CLI interface %v", err) + return err + } + defer conn.Close() + + daemonClient := proto.NewDaemonServiceClient(conn) + + if _, err := daemonClient.Down(ctx, &proto.DownRequest{}); err != nil { + log.Errorf("call service down method: %v", err) + return err + } + return nil + }, +} diff --git a/client/cmd/login.go b/client/cmd/login.go index c763b05ca..dbbc16d14 100644 --- a/client/cmd/login.go +++ b/client/cmd/login.go @@ -1,175 +1,66 @@ package cmd import ( - "bufio" "context" "fmt" - "os" - "time" - "github.com/cenkalti/backoff/v4" - "github.com/google/uuid" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" + "github.com/wiretrustee/wiretrustee/client/internal" - "github.com/wiretrustee/wiretrustee/client/system" - mgm "github.com/wiretrustee/wiretrustee/management/client" - mgmProto "github.com/wiretrustee/wiretrustee/management/proto" - "github.com/wiretrustee/wiretrustee/util" - "golang.zx2c4.com/wireguard/wgctrl/wgtypes" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" + "github.com/wiretrustee/wiretrustee/client/proto" ) -var ( - loginCmd = &cobra.Command{ - Use: "login", - Short: "login to the Wiretrustee Management Service (first run)", - RunE: func(cmd *cobra.Command, args []string) error { - SetFlagsFromEnvVars() +var loginCmd = &cobra.Command{ + Use: "login", + Short: "login to the Wiretrustee Management Service (first run)", + RunE: func(cmd *cobra.Command, args []string) error { + SetFlagsFromEnvVars() + ctx := internal.CtxInitState(context.Background()) - var backOff = &backoff.ExponentialBackOff{ - InitialInterval: time.Second, - RandomizationFactor: backoff.DefaultRandomizationFactor, - Multiplier: backoff.DefaultMultiplier, - MaxInterval: 2 * time.Second, - MaxElapsedTime: time.Second * 10, - Stop: backoff.Stop, - Clock: backoff.SystemClock, - } - - loginOp := func() error { - - err := util.InitLog(logLevel, logFile) - if err != nil { - log.Errorf("failed initializing log %v", err) - return err - } - - config, err := internal.GetConfig(managementURL, configPath, preSharedKey) - if err != nil { - log.Errorf("failed getting config %s %v", configPath, err) - return err - } - - //validate our peer's Wireguard PRIVATE key - myPrivateKey, err := wgtypes.ParseKey(config.PrivateKey) - if err != nil { - log.Errorf("failed parsing Wireguard key %s: [%s]", config.PrivateKey, err.Error()) - return err - } - - ctx := context.Background() - - mgmTlsEnabled := false - if config.ManagementURL.Scheme == "https" { - mgmTlsEnabled = true - } - - log.Debugf("connecting to Management Service %s", config.ManagementURL.String()) - mgmClient, err := mgm.NewClient(ctx, config.ManagementURL.Host, myPrivateKey, mgmTlsEnabled) - if err != nil { - log.Errorf("failed connecting to Management Service %s %v", config.ManagementURL.String(), err) - return err - } - log.Debugf("connected to management Service %s", config.ManagementURL.String()) - - serverKey, err := mgmClient.GetServerPublicKey() - if err != nil { - log.Errorf("failed while getting Management Service public key: %v", err) - return err - } - - _, err = loginPeer(*serverKey, mgmClient, setupKey) - if err != nil { - log.Errorf("failed logging-in peer on Management Service : %v", err) - return err - } - - err = mgmClient.Close() - if err != nil { - log.Errorf("failed closing Management Service client: %v", err) - return err - } - - return nil - } - - err := backoff.RetryNotify(loginOp, backOff, func(err error, duration time.Duration) { - log.Warnf("retrying Login to the Management service in %v due to error %v", duration, err) - }) + // workaround to run without service + if logFile == "console" { + config, err := internal.GetConfig(managementURL, configPath, preSharedKey) if err != nil { - log.Errorf("exiting login retry loop due to unrecoverable error: %v", err) + log.Errorf("get config file: %v", err) return err } - - return nil - }, - } -) - -// loginPeer attempts to login to Management Service. If peer wasn't registered, tries the registration flow. -func loginPeer(serverPublicKey wgtypes.Key, client *mgm.GrpcClient, setupKey string) (*mgmProto.LoginResponse, error) { - - loginResp, err := client.Login(serverPublicKey) - if err != nil { - if s, ok := status.FromError(err); ok && s.Code() == codes.PermissionDenied { - log.Debugf("peer registration required") - return registerPeer(serverPublicKey, client, setupKey) - } else { - return nil, err + err = WithBackOff(func() error { + return internal.Login(ctx, config, setupKey) + }) + if err != nil { + log.Errorf("backoff cycle failed: %v", err) + } + return err } - } - log.Info("peer has successfully logged-in to Management Service") + if setupKey == "" { + log.Error("setup key can't be empty") + return fmt.Errorf("empty setup key") + } - return loginResp, nil -} - -// registerPeer checks whether setupKey was provided via cmd line and if not then it prompts user to enter a key. -// Otherwise tries to register with the provided setupKey via command line. -func registerPeer(serverPublicKey wgtypes.Key, client *mgm.GrpcClient, setupKey string) (*mgmProto.LoginResponse, error) { - - var err error - if setupKey == "" { - setupKey, err = promptPeerSetupKey() + conn, err := DialClientGRPCServer(ctx, daemonAddr) if err != nil { - log.Errorf("failed getting setup key from user: %s", err) - return nil, err + log.Errorf("failed to connect to service CLI interface %v", err) + return err } - } + defer conn.Close() - validSetupKey, err := uuid.Parse(setupKey) - if err != nil { - return nil, err - } - - log.Debugf("sending peer registration request to Management Service") - info := system.GetInfo() - loginResp, err := client.Register(serverPublicKey, validSetupKey.String(), info) - if err != nil { - log.Errorf("failed registering peer %v", err) - return nil, err - } - - log.Infof("peer has been successfully registered on Management Service") - - return loginResp, nil -} - -// promptPeerSetupKey prompts user to enter Setup Key -func promptPeerSetupKey() (string, error) { - fmt.Print("Enter setup key: ") - - s := bufio.NewScanner(os.Stdin) - for s.Scan() { - input := s.Text() - if input != "" { - return input, nil + request := proto.LoginRequest{ + SetupKey: setupKey, + PresharedKey: preSharedKey, + ManagementUrl: managementURL, } - fmt.Println("Specified key is empty, try again:") - - } - - return "", s.Err() + client := proto.NewDaemonServiceClient(conn) + err = WithBackOff(func() error { + if _, err := client.Login(ctx, &request); err != nil { + log.Errorf("try login: %v", err) + } + return err + }) + if err != nil { + log.Errorf("backoff cycle failed: %v", err) + } + return err + }, } diff --git a/client/cmd/login_test.go b/client/cmd/login_test.go index 0016f568c..8e945f00a 100644 --- a/client/cmd/login_test.go +++ b/client/cmd/login_test.go @@ -2,13 +2,14 @@ package cmd import ( "fmt" + "path/filepath" + "strings" + "testing" + "github.com/wiretrustee/wiretrustee/client/internal" "github.com/wiretrustee/wiretrustee/iface" mgmt "github.com/wiretrustee/wiretrustee/management/server" "github.com/wiretrustee/wiretrustee/util" - "path/filepath" - "strings" - "testing" ) var mgmAddr string @@ -25,12 +26,11 @@ func TestLogin_Start(t *testing.T) { if err != nil { t.Fatal(err) } - _, listener := startManagement(config, t) + _, listener := startManagement(t, config) mgmAddr = listener.Addr().String() } func TestLogin(t *testing.T) { - tempDir := t.TempDir() confPath := tempDir + "/config.json" mgmtURL := fmt.Sprintf("http://%s", mgmAddr) @@ -38,6 +38,8 @@ func TestLogin(t *testing.T) { "login", "--config", confPath, + "--log-file", + "console", "--setup-key", strings.ToUpper("a2c8e62b-38f5-4553-b31e-dd66c696cebb"), "--management-url", diff --git a/client/cmd/root.go b/client/cmd/root.go index dc5a87a8c..b4fdb2ce6 100644 --- a/client/cmd/root.go +++ b/client/cmd/root.go @@ -1,16 +1,23 @@ package cmd import ( + "context" "fmt" - log "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - "github.com/spf13/pflag" - "github.com/wiretrustee/wiretrustee/client/internal" "os" "os/signal" "runtime" "strings" "syscall" + "time" + + "github.com/cenkalti/backoff/v4" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + + "github.com/wiretrustee/wiretrustee/client/internal" ) var ( @@ -19,6 +26,7 @@ var ( logLevel string defaultLogFile string logFile string + daemonAddr string managementURL string setupKey string preSharedKey string @@ -37,8 +45,8 @@ var ( func Execute() error { return rootCmd.Execute() } -func init() { +func init() { stopCh = make(chan int) cleanupCh = make(chan struct{}) @@ -49,6 +57,11 @@ func init() { defaultLogFile = os.Getenv("PROGRAMDATA") + "\\Wiretrustee\\" + "client.log" } + defaultDaemonAddr := "unix:///var/run/wiretrustee.sock" + if runtime.GOOS == "windows" { + defaultDaemonAddr = "tcp://127.0.0.1:41731" + } + rootCmd.PersistentFlags().StringVar(&daemonAddr, "daemon-addr", defaultDaemonAddr, "Daemon service address to serve CLI requests [unix|tcp]://[path|host:port]") rootCmd.PersistentFlags().StringVar(&managementURL, "management-url", "", fmt.Sprintf("Management Service URL [http|https]://[host]:[port] (default \"%s\")", internal.ManagementURLDefault().String())) rootCmd.PersistentFlags().StringVar(&configPath, "config", defaultConfigPath, "Wiretrustee config file location") rootCmd.PersistentFlags().StringVar(&logLevel, "log-level", "info", "sets Wiretrustee log level") @@ -57,6 +70,8 @@ func init() { rootCmd.PersistentFlags().StringVar(&preSharedKey, "preshared-key", "", "Sets Wireguard PreSharedKey property. If set, then only peers that have the same key can communicate.") rootCmd.AddCommand(serviceCmd) rootCmd.AddCommand(upCmd) + rootCmd.AddCommand(downCmd) + rootCmd.AddCommand(statusCmd) rootCmd.AddCommand(loginCmd) rootCmd.AddCommand(versionCmd) serviceCmd.AddCommand(runCmd, startCmd, stopCmd, restartCmd) // service control commands are subcommands of service @@ -79,7 +94,6 @@ func SetupCloseHandler() { func SetFlagsFromEnvVars() { flags := rootCmd.PersistentFlags() flags.VisitAll(func(f *pflag.Flag) { - envVar := FlagNameToEnvVar(f.Name) if value, present := os.LookupEnv(envVar); present { @@ -99,3 +113,34 @@ func FlagNameToEnvVar(f string) string { upper := strings.ToUpper(parsed) return prefix + upper } + +// DialClientGRPCServer returns client connection to the dameno server. +func DialClientGRPCServer(ctx context.Context, addr string) (*grpc.ClientConn, error) { + ctx, cancel := context.WithTimeout(ctx, time.Second*3) + defer cancel() + + return grpc.DialContext( + ctx, + strings.TrimPrefix(addr, "tcp://"), + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithBlock(), + ) +} + +// WithBackOff execute function in backoff cycle. +func WithBackOff(bf func() error) error { + return backoff.RetryNotify(bf, CLIBackOffSettings, func(err error, duration time.Duration) { + log.Warnf("retrying Login to the Management service in %v due to error %v", duration, err) + }) +} + +// CLIBackOffSettings is default backoff settings for CLI commands. +var CLIBackOffSettings = &backoff.ExponentialBackOff{ + InitialInterval: time.Second, + RandomizationFactor: backoff.DefaultRandomizationFactor, + Multiplier: backoff.DefaultMultiplier, + MaxInterval: 10 * time.Second, + MaxElapsedTime: 30 * time.Second, + Stop: backoff.Stop, + Clock: backoff.SystemClock, +} diff --git a/client/cmd/service.go b/client/cmd/service.go index 0ec4199ed..f5ef50298 100644 --- a/client/cmd/service.go +++ b/client/cmd/service.go @@ -1,14 +1,30 @@ package cmd import ( + "context" + "github.com/kardianos/service" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" + "google.golang.org/grpc" + + "github.com/wiretrustee/wiretrustee/client/internal" ) type program struct { - cmd *cobra.Command - args []string + ctx context.Context + cmd *cobra.Command + args []string + serv *grpc.Server +} + +func newProgram(cmd *cobra.Command, args []string) *program { + ctx := internal.CtxInitState(cmd.Context()) + return &program{ + ctx: ctx, + cmd: cmd, + args: args, + } } func newSVCConfig() *service.Config { @@ -28,9 +44,8 @@ func newSVC(prg *program, conf *service.Config) (service.Service, error) { return s, nil } -var ( - serviceCmd = &cobra.Command{ - Use: "service", - Short: "manages wiretrustee service", - } -) +var serviceCmd = &cobra.Command{ + Use: "service", + Short: "manages wiretrustee service", +} + diff --git a/client/cmd/service_controller.go b/client/cmd/service_controller.go index b64da710c..aaea6fa9b 100644 --- a/client/cmd/service_controller.go +++ b/client/cmd/service_controller.go @@ -1,23 +1,60 @@ package cmd import ( + "net" + "os" + "strings" + "time" + "github.com/kardianos/service" log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "github.com/wiretrustee/wiretrustee/client/proto" + "github.com/wiretrustee/wiretrustee/client/server" "github.com/wiretrustee/wiretrustee/util" - "time" + "google.golang.org/grpc" ) -func (p *program) Start(service.Service) error { - +func (p *program) Start(svc service.Service) error { // Start should not block. Do the actual work async. log.Info("starting service") //nolint go func() { - err := runClient() - if err != nil { - log.Errorf("stopped Wiretrustee client app due to error: %v", err) + // in any case, even if configuration does not exists we run daemon to serve CLI gRPC API. + p.serv = grpc.NewServer() + + split := strings.Split(daemonAddr, "://") + switch split[0] { + case "unix": + // cleanup failed close + stat, err := os.Stat(split[1]) + if err == nil && !stat.IsDir() { + if err := os.Remove(split[1]); err != nil { + log.Debugf("remove socket file: %v", err) + } + } + case "tcp": + default: + log.Errorf("unsupported daemon address protocol: %v", split[0]) return } + + listen, err := net.Listen(split[0], split[1]) + if err != nil { + log.Fatalf("failed to listen daemon interface: %v", err) + } + defer listen.Close() + + serverInstance := server.New(p.ctx, managementURL, configPath, stopCh, cleanupCh) + if err := serverInstance.Start(); err != nil { + log.Fatalf("failed start daemon: %v", err) + } + proto.RegisterDaemonServiceServer(p.serv, serverInstance) + + log.Printf("started daemon server: %v", split[1]) + if err := p.serv.Serve(listen); err != nil { + log.Errorf("failed to serve daemon requests: %v", err) + } }() return nil } @@ -27,6 +64,11 @@ func (p *program) Stop(service.Service) error { stopCh <- 1 }() + // stop CLI daemon service + if p.serv != nil { + p.serv.GracefulStop() + } + select { case <-cleanupCh: case <-time.After(time.Second * 10): @@ -36,117 +78,104 @@ func (p *program) Stop(service.Service) error { return nil } -var ( - runCmd = &cobra.Command{ - Use: "run", - Short: "runs wiretrustee as service", - Run: func(cmd *cobra.Command, args []string) { - SetFlagsFromEnvVars() +var runCmd = &cobra.Command{ + Use: "run", + Short: "runs wiretrustee as service", + Run: func(cmd *cobra.Command, args []string) { + SetFlagsFromEnvVars() - err := util.InitLog(logLevel, logFile) - if err != nil { - log.Errorf("failed initializing log %v", err) - return - } + err := util.InitLog(logLevel, logFile) + if err != nil { + log.Errorf("failed initializing log %v", err) + return + } - SetupCloseHandler() + SetupCloseHandler() - prg := &program{ - cmd: cmd, - args: args, - } + s, err := newSVC(newProgram(cmd, args), newSVCConfig()) + if err != nil { + cmd.PrintErrln(err) + return + } + err = s.Run() + if err != nil { + cmd.PrintErrln(err) + return + } + cmd.Printf("Wiretrustee service is running") + }, +} - s, err := newSVC(prg, newSVCConfig()) - if err != nil { - cmd.PrintErrln(err) - return - } - err = s.Run() - if err != nil { - cmd.PrintErrln(err) - return - } - cmd.Printf("Wiretrustee service is running") - }, - } -) +var startCmd = &cobra.Command{ + Use: "start", + Short: "starts wiretrustee service", + RunE: func(cmd *cobra.Command, args []string) error { + SetFlagsFromEnvVars() -var ( - startCmd = &cobra.Command{ - Use: "start", - Short: "starts wiretrustee service", - RunE: func(cmd *cobra.Command, args []string) error { - SetFlagsFromEnvVars() + err := util.InitLog(logLevel, logFile) + if err != nil { + log.Errorf("failed initializing log %v", err) + return err + } + s, err := newSVC(newProgram(cmd, args), newSVCConfig()) + if err != nil { + cmd.PrintErrln(err) + return err + } + err = s.Start() + if err != nil { + cmd.PrintErrln(err) + return err + } + cmd.Println("Wiretrustee service has been started") + return nil + }, +} - err := util.InitLog(logLevel, logFile) - if err != nil { - log.Errorf("failed initializing log %v", err) - return err - } - s, err := newSVC(&program{}, newSVCConfig()) - if err != nil { - cmd.PrintErrln(err) - return err - } - err = s.Start() - if err != nil { - cmd.PrintErrln(err) - return err - } - cmd.Println("Wiretrustee service has been started") - return nil - }, - } -) +var stopCmd = &cobra.Command{ + Use: "stop", + Short: "stops wiretrustee service", + Run: func(cmd *cobra.Command, args []string) { + SetFlagsFromEnvVars() -var ( - stopCmd = &cobra.Command{ - Use: "stop", - Short: "stops wiretrustee service", - Run: func(cmd *cobra.Command, args []string) { - SetFlagsFromEnvVars() + err := util.InitLog(logLevel, logFile) + if err != nil { + log.Errorf("failed initializing log %v", err) + } + s, err := newSVC(newProgram(cmd, args), newSVCConfig()) + if err != nil { + cmd.PrintErrln(err) + return + } + err = s.Stop() + if err != nil { + cmd.PrintErrln(err) + return + } + cmd.Println("Wiretrustee service has been stopped") + }, +} - err := util.InitLog(logLevel, logFile) - if err != nil { - log.Errorf("failed initializing log %v", err) - } - s, err := newSVC(&program{}, newSVCConfig()) - if err != nil { - cmd.PrintErrln(err) - return - } - err = s.Stop() - if err != nil { - cmd.PrintErrln(err) - return - } - cmd.Println("Wiretrustee service has been stopped") - }, - } -) +var restartCmd = &cobra.Command{ + Use: "restart", + Short: "restarts wiretrustee service", + Run: func(cmd *cobra.Command, args []string) { + SetFlagsFromEnvVars() -var ( - restartCmd = &cobra.Command{ - Use: "restart", - Short: "restarts wiretrustee service", - Run: func(cmd *cobra.Command, args []string) { - SetFlagsFromEnvVars() - - err := util.InitLog(logLevel, logFile) - if err != nil { - log.Errorf("failed initializing log %v", err) - } - s, err := newSVC(&program{}, newSVCConfig()) - if err != nil { - cmd.PrintErrln(err) - return - } - err = s.Restart() - if err != nil { - cmd.PrintErrln(err) - return - } - cmd.Println("Wiretrustee service has been restarted") - }, - } -) + err := util.InitLog(logLevel, logFile) + if err != nil { + log.Errorf("failed initializing log %v", err) + } + s, err := newSVC(newProgram(cmd, args), newSVCConfig()) + if err != nil { + cmd.PrintErrln(err) + return + } + err = s.Restart() + if err != nil { + cmd.PrintErrln(err) + return + } + cmd.Println("Wiretrustee service has been restarted") + }, +} diff --git a/client/cmd/service_installer.go b/client/cmd/service_installer.go index f5939a7d8..6dcd47a9a 100644 --- a/client/cmd/service_installer.go +++ b/client/cmd/service_installer.go @@ -1,69 +1,67 @@ package cmd import ( - "github.com/spf13/cobra" "runtime" + + "github.com/spf13/cobra" ) -var ( - installCmd = &cobra.Command{ - Use: "install", - Short: "installs wiretrustee service", - RunE: func(cmd *cobra.Command, args []string) error { - SetFlagsFromEnvVars() +var installCmd = &cobra.Command{ + Use: "install", + Short: "installs wiretrustee service", + RunE: func(cmd *cobra.Command, args []string) error { + SetFlagsFromEnvVars() - svcConfig := newSVCConfig() + svcConfig := newSVCConfig() - svcConfig.Arguments = []string{ - "service", - "run", - "--config", - configPath, - "--log-level", - logLevel, - } + svcConfig.Arguments = []string{ + "service", + "run", + "--config", + configPath, + "--log-level", + logLevel, + } - if runtime.GOOS == "linux" { - // Respected only by systemd systems - svcConfig.Dependencies = []string{"After=network.target syslog.target"} - } + if runtime.GOOS == "linux" { + // Respected only by systemd systems + svcConfig.Dependencies = []string{"After=network.target syslog.target"} + } - s, err := newSVC(&program{}, svcConfig) - if err != nil { - cmd.PrintErrln(err) - return err - } + s, err := newSVC(newProgram(cmd, args), svcConfig) + if err != nil { + cmd.PrintErrln(err) + return err + } - err = s.Install() - if err != nil { - cmd.PrintErrln(err) - return err - } - cmd.Println("Wiretrustee service has been installed") - return nil - }, - } -) + err = s.Install() + if err != nil { + cmd.PrintErrln(err) + return err + } + cmd.Println("Wiretrustee service has been installed") + return nil + }, +} -var ( - uninstallCmd = &cobra.Command{ - Use: "uninstall", - Short: "uninstalls wiretrustee service from system", - Run: func(cmd *cobra.Command, args []string) { - SetFlagsFromEnvVars() +var uninstallCmd = &cobra.Command{ + Use: "uninstall", + Short: "uninstalls wiretrustee service from system", + Run: func(cmd *cobra.Command, args []string) { + SetFlagsFromEnvVars() - s, err := newSVC(&program{}, newSVCConfig()) - if err != nil { - cmd.PrintErrln(err) - return - } + s, err := newSVC(newProgram(cmd, args), newSVCConfig()) + if err != nil { + cmd.PrintErrln(err) + return + } + + err = s.Uninstall() + if err != nil { + cmd.PrintErrln(err) + return + } + cmd.Println("Wiretrustee has been uninstalled") + }, +} - err = s.Uninstall() - if err != nil { - cmd.PrintErrln(err) - return - } - cmd.Println("Wiretrustee has been uninstalled") - }, - } -) diff --git a/client/cmd/status.go b/client/cmd/status.go new file mode 100644 index 000000000..44daf5e90 --- /dev/null +++ b/client/cmd/status.go @@ -0,0 +1,37 @@ +package cmd + +import ( + "context" + + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "google.golang.org/grpc/status" + + "github.com/wiretrustee/wiretrustee/client/internal" + "github.com/wiretrustee/wiretrustee/client/proto" +) + +var statusCmd = &cobra.Command{ + Use: "status", + Short: "status of the Wiretrustee Service", + RunE: func(cmd *cobra.Command, args []string) error { + SetFlagsFromEnvVars() + ctx := internal.CtxInitState(context.Background()) + + conn, err := DialClientGRPCServer(ctx, daemonAddr) + if err != nil { + log.Errorf("failed to connect to service CLI interface %v", err) + return err + } + defer conn.Close() + + resp, err := proto.NewDaemonServiceClient(conn).Status(cmd.Context(), &proto.StatusRequest{}) + if err != nil { + log.Errorf("status failed: %v", status.Convert(err).Message()) + return nil + } + + log.Infof("status: %v", resp.Status) + return nil + }, +} diff --git a/client/cmd/testutil.go b/client/cmd/testutil.go index 99c9325df..03db8fec5 100644 --- a/client/cmd/testutil.go +++ b/client/cmd/testutil.go @@ -1,13 +1,18 @@ package cmd import ( + "context" + "net" + "testing" + "time" + + clientProto "github.com/wiretrustee/wiretrustee/client/proto" + client "github.com/wiretrustee/wiretrustee/client/server" mgmtProto "github.com/wiretrustee/wiretrustee/management/proto" mgmt "github.com/wiretrustee/wiretrustee/management/server" sigProto "github.com/wiretrustee/wiretrustee/signal/proto" sig "github.com/wiretrustee/wiretrustee/signal/server" "google.golang.org/grpc" - "net" - "testing" ) func startSignal(t *testing.T) (*grpc.Server, net.Listener) { @@ -26,7 +31,7 @@ func startSignal(t *testing.T) (*grpc.Server, net.Listener) { return s, lis } -func startManagement(config *mgmt.Config, t *testing.T) (*grpc.Server, net.Listener) { +func startManagement(t *testing.T, config *mgmt.Config) (*grpc.Server, net.Listener) { lis, err := net.Listen("tcp", ":0") if err != nil { t.Fatal(err) @@ -48,9 +53,40 @@ func startManagement(config *mgmt.Config, t *testing.T) (*grpc.Server, net.Liste go func() { if err := s.Serve(lis); err != nil { t.Error(err) - return } }() return s, lis } + +func startClientDaemon( + t *testing.T, ctx context.Context, managementURL, configPath string, + stopCh chan int, cleanupCh chan<- struct{}, +) (*grpc.Server, net.Listener) { + lis, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatal(err) + } + s := grpc.NewServer() + + server := client.New( + ctx, + managementURL, + configPath, + stopCh, + cleanupCh, + ) + if err := server.Start(); err != nil { + t.Fatal(err) + } + clientProto.RegisterDaemonServiceServer(s, server) + go func() { + if err := s.Serve(lis); err != nil { + t.Error(err) + } + }() + + time.Sleep(time.Second) + + return s, lis +} diff --git a/client/cmd/up.go b/client/cmd/up.go index 5f7b049eb..518c469cb 100644 --- a/client/cmd/up.go +++ b/client/cmd/up.go @@ -1,239 +1,77 @@ package cmd import ( - "context" - "github.com/wiretrustee/wiretrustee/iface" - "time" - - "github.com/cenkalti/backoff/v4" - "github.com/kardianos/service" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" + "github.com/wiretrustee/wiretrustee/client/internal" - mgm "github.com/wiretrustee/wiretrustee/management/client" - mgmProto "github.com/wiretrustee/wiretrustee/management/proto" - signal "github.com/wiretrustee/wiretrustee/signal/client" - "golang.zx2c4.com/wireguard/wgctrl/wgtypes" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" + "github.com/wiretrustee/wiretrustee/client/proto" ) -var ( - upCmd = &cobra.Command{ - Use: "up", - Short: "install, login and start wiretrustee client", - RunE: func(cmd *cobra.Command, args []string) error { - SetFlagsFromEnvVars() +var upCmd = &cobra.Command{ + Use: "up", + Short: "install, login and start wiretrustee client", + RunE: func(cmd *cobra.Command, args []string) error { + SetFlagsFromEnvVars() + ctx := internal.CtxInitState(cmd.Context()) - err := loginCmd.RunE(cmd, args) + // workaround to run without service + if logFile == "console" { + config, err := internal.GetConfig(managementURL, configPath, preSharedKey) if err != nil { + log.Errorf("get config file: %v", err) return err } - if logFile == "console" { - SetupCloseHandler() - return runClient() - } - - s, err := newSVC(&program{}, newSVCConfig()) + err = WithBackOff(func() error { + return internal.Login(ctx, config, setupKey) + }) if err != nil { - cmd.PrintErrln(err) + log.Errorf("backoff cycle failed: %v", err) return err } - srvStatus, err := s.Status() - if err != nil { - if err == service.ErrNotInstalled { - log.Infof("%s. Installing it now", err.Error()) - e := installCmd.RunE(cmd, args) - if e != nil { - return e - } - } else { - log.Warnf("failed retrieving service status: %v", err) - } - } - if srvStatus == service.StatusRunning { - stopCmd.Run(cmd, args) - } - return startCmd.RunE(cmd, args) - }, - } -) - -// createEngineConfig converts configuration received from Management Service to EngineConfig -func createEngineConfig(key wgtypes.Key, config *internal.Config, peerConfig *mgmProto.PeerConfig) (*internal.EngineConfig, error) { - iFaceBlackList := make(map[string]struct{}) - for i := 0; i < len(config.IFaceBlackList); i += 2 { - iFaceBlackList[config.IFaceBlackList[i]] = struct{}{} - } - - engineConf := &internal.EngineConfig{ - WgIfaceName: config.WgIface, - WgAddr: peerConfig.Address, - IFaceBlackList: iFaceBlackList, - WgPrivateKey: key, - WgPort: iface.DefaultWgPort, - } - - if config.PreSharedKey != "" { - preSharedKey, err := wgtypes.ParseKey(config.PreSharedKey) - if err != nil { - return nil, err + SetupCloseHandler() + return internal.RunClient(ctx, config, stopCh, cleanupCh) } - engineConf.PreSharedKey = &preSharedKey - } - return engineConf, nil -} - -// connectToSignal creates Signal Service client and established a connection -func connectToSignal(ctx context.Context, wtConfig *mgmProto.WiretrusteeConfig, ourPrivateKey wgtypes.Key) (*signal.GrpcClient, error) { - var sigTLSEnabled bool - if wtConfig.Signal.Protocol == mgmProto.HostConfig_HTTPS { - sigTLSEnabled = true - } else { - sigTLSEnabled = false - } - - signalClient, err := signal.NewClient(ctx, wtConfig.Signal.Uri, ourPrivateKey, sigTLSEnabled) - if err != nil { - log.Errorf("error while connecting to the Signal Exchange Service %s: %s", wtConfig.Signal.Uri, err) - return nil, status.Errorf(codes.FailedPrecondition, "failed connecting to Signal Service : %s", err) - } - - return signalClient, nil -} - -// connectToManagement creates Management Services client, establishes a connection, logs-in and gets a global Wiretrustee config (signal, turn, stun hosts, etc) -func connectToManagement(ctx context.Context, managementAddr string, ourPrivateKey wgtypes.Key, tlsEnabled bool) (*mgm.GrpcClient, *mgmProto.LoginResponse, error) { - log.Debugf("connecting to management server %s", managementAddr) - client, err := mgm.NewClient(ctx, managementAddr, ourPrivateKey, tlsEnabled) - if err != nil { - return nil, nil, status.Errorf(codes.FailedPrecondition, "failed connecting to Management Service : %s", err) - } - log.Debugf("connected to management server %s", managementAddr) - - serverPublicKey, err := client.GetServerPublicKey() - if err != nil { - return nil, nil, status.Errorf(codes.FailedPrecondition, "failed while getting Management Service public key: %s", err) - } - - loginResp, err := client.Login(*serverPublicKey) - if err != nil { - if s, ok := status.FromError(err); ok && s.Code() == codes.PermissionDenied { - log.Error("peer registration required. Please run wiretrustee login command first") - return nil, nil, err - } else { - return nil, nil, err - } - } - - log.Debugf("peer logged in to Management Service %s", managementAddr) - - return client, loginResp, nil -} - -func runClient() error { - var backOff = &backoff.ExponentialBackOff{ - InitialInterval: time.Second, - RandomizationFactor: backoff.DefaultRandomizationFactor, - Multiplier: backoff.DefaultMultiplier, - MaxInterval: 10 * time.Second, - MaxElapsedTime: 24 * 3 * time.Hour, //stop the client after 3 days trying (must be a huge problem, e.g permission denied) - Stop: backoff.Stop, - Clock: backoff.SystemClock, - } - - operation := func() error { - - config, err := internal.ReadConfig(managementURL, configPath) - if err != nil { - log.Errorf("failed reading config %s %v", configPath, err) - return err - } - - //validate our peer's Wireguard PRIVATE key - myPrivateKey, err := wgtypes.ParseKey(config.PrivateKey) - if err != nil { - log.Errorf("failed parsing Wireguard key %s: [%s]", config.PrivateKey, err.Error()) - return err - } - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - mgmTlsEnabled := false - if config.ManagementURL.Scheme == "https" { - mgmTlsEnabled = true - } - - // connect (just a connection, no stream yet) and login to Management Service to get an initial global Wiretrustee config - mgmClient, loginResp, err := connectToManagement(ctx, config.ManagementURL.Host, myPrivateKey, mgmTlsEnabled) - if err != nil { - log.Warn(err) - return err - } - - // with the global Wiretrustee config in hand connect (just a connection, no stream yet) Signal - signalClient, err := connectToSignal(ctx, loginResp.GetWiretrusteeConfig(), myPrivateKey) - if err != nil { - log.Error(err) - return err - } - - peerConfig := loginResp.GetPeerConfig() - - engineConfig, err := createEngineConfig(myPrivateKey, config, peerConfig) - if err != nil { - log.Error(err) - return err - } - - engine := internal.NewEngine(signalClient, mgmClient, engineConfig, cancel, ctx) - err = engine.Start() - if err != nil { - log.Errorf("error while starting Wiretrustee Connection Engine: %s", err) - return err - } - - log.Print("Wiretrustee engine started, my IP is: ", peerConfig.Address) - - select { - case <-stopCh: - case <-ctx.Done(): - } - - backOff.Reset() - - err = mgmClient.Close() - if err != nil { - log.Errorf("failed closing Management Service client %v", err) - return err - } - err = signalClient.Close() - if err != nil { - log.Errorf("failed closing Signal Service client %v", err) - return err - } - - err = engine.Stop() - if err != nil { - log.Errorf("failed stopping engine %v", err) - return err - } - - go func() { - cleanupCh <- struct{}{} - }() - - log.Info("stopped Wiretrustee client") - - return ctx.Err() - } - - err := backoff.Retry(operation, backOff) - if err != nil { - log.Errorf("exiting client retry loop due to unrecoverable error: %s", err) - return err - } - return nil + conn, err := DialClientGRPCServer(ctx, daemonAddr) + if err != nil { + log.Errorf("failed to connect to service CLI interface %v", err) + return err + } + defer conn.Close() + + daemonClient := proto.NewDaemonServiceClient(conn) + + loginRequest := proto.LoginRequest{ + SetupKey: setupKey, + PresharedKey: preSharedKey, + } + err = WithBackOff(func() error { + _, err := daemonClient.Login(ctx, &loginRequest) + return err + }) + if err != nil { + log.Errorf("backoff cycle failed: %v", err) + return err + } + + status, err := daemonClient.Status(ctx, &proto.StatusRequest{}) + if err != nil { + log.Errorf("get status: %v", err) + return err + } + + if status.Status != string(internal.StatusIdle) { + log.Warnf("already connected") + return nil + } + + if _, err := daemonClient.Up(ctx, &proto.UpRequest{}); err != nil { + log.Errorf("call service up method: %v", err) + return err + } + + return nil + }, } diff --git a/client/cmd/up_daemon_test.go b/client/cmd/up_daemon_test.go new file mode 100644 index 000000000..f597660ed --- /dev/null +++ b/client/cmd/up_daemon_test.go @@ -0,0 +1,98 @@ +package cmd + +import ( + "context" + "path/filepath" + "testing" + "time" + + "github.com/wiretrustee/wiretrustee/client/internal" + mgmt "github.com/wiretrustee/wiretrustee/management/server" + "github.com/wiretrustee/wiretrustee/util" +) + +func TestUpDaemon_Start(t *testing.T) { + config := &mgmt.Config{} + _, err := util.ReadJson("../testdata/management.json", config) + if err != nil { + t.Fatal(err) + } + testDir := t.TempDir() + config.Datadir = testDir + err = util.CopyFileContents("../testdata/store.json", filepath.Join(testDir, "store.json")) + if err != nil { + t.Fatal(err) + } + + _, signalLis := startSignal(t) + signalAddr = signalLis.Addr().String() + config.Signal.URI = signalAddr + + _, mgmLis := startManagement(t, config) + mgmAddr = mgmLis.Addr().String() +} + +func TestUpDaemon(t *testing.T) { + tempDir := t.TempDir() + confPath := tempDir + "/config.json" + + stopCh = make(chan int, 1) + cleanupCh = make(chan struct{}, 1) + + ctx := internal.CtxInitState(context.Background()) + state := internal.CtxGetState(ctx) + + _, cliLis := startClientDaemon(t, ctx, "http://"+mgmAddr, confPath, stopCh, cleanupCh) + + cliAddr = cliLis.Addr().String() + + daemonAddr = "tcp://" + cliAddr + rootCmd.SetArgs([]string{ + "login", + "--daemon-addr", "tcp://" + cliAddr, + "--setup-key", "A2C8E62B-38F5-4553-B31E-DD66C696CEBB", + "--log-file", "", + }) + if err := rootCmd.Execute(); err != nil { + t.Errorf("expected no error while running up command, got %v", err) + return + } + time.Sleep(time.Second * 3) + if status, err := state.Status(); err != nil && status != internal.StatusIdle { + t.Errorf("wrong status after login: %s, %v", internal.StatusIdle, err) + return + } + + rootCmd.SetArgs([]string{ + "up", + "--daemon-addr", "tcp://" + cliAddr, + "--log-file", "", + }) + if err := rootCmd.Execute(); err != nil { + t.Errorf("expected no error while running up command, got %v", err) + return + } + time.Sleep(time.Second * 3) + if status, err := state.Status(); err != nil && status != internal.StatusConnected { + t.Errorf("wrong status after connect: %s, %v", status, err) + return + } + + rootCmd.SetArgs([]string{ + "status", + "--daemon-addr", "tcp://" + cliAddr, + }) + if err := rootCmd.Execute(); err != nil { + t.Errorf("expected no error while running up command, got %v", err) + return + } + time.Sleep(time.Second * 3) + + rootCmd.SetErr(nil) + rootCmd.SetArgs([]string{"down", "--daemon-addr", "tcp://" + cliAddr}) + if err := rootCmd.Execute(); err != nil { + t.Errorf("expected no error while running up command, got %v", err) + return + } + // we can't check status here, because context already canceled +} diff --git a/client/cmd/up_test.go b/client/cmd/up_test.go index 97543599d..9148b7996 100644 --- a/client/cmd/up_test.go +++ b/client/cmd/up_test.go @@ -1,16 +1,20 @@ package cmd import ( - "github.com/wiretrustee/wiretrustee/iface" - mgmt "github.com/wiretrustee/wiretrustee/management/server" - "github.com/wiretrustee/wiretrustee/util" "net/url" "path/filepath" "testing" "time" + + "github.com/wiretrustee/wiretrustee/iface" + mgmt "github.com/wiretrustee/wiretrustee/management/server" + "github.com/wiretrustee/wiretrustee/util" ) -var signalAddr string +var ( + signalAddr string + cliAddr string +) func TestUp_Start(t *testing.T) { config := &mgmt.Config{} @@ -29,15 +33,11 @@ func TestUp_Start(t *testing.T) { signalAddr = signalLis.Addr().String() config.Signal.URI = signalAddr - _, mgmLis := startManagement(config, t) + _, mgmLis := startManagement(t, config) mgmAddr = mgmLis.Addr().String() - } func TestUp(t *testing.T) { - - //defer iface.Close("wt0") - tempDir := t.TempDir() confPath := tempDir + "/config.json" mgmtURL, err := url.Parse("http://" + mgmAddr) @@ -58,12 +58,13 @@ func TestUp(t *testing.T) { "--log-file", "console", }) + go func() { - err = rootCmd.Execute() - if err != nil { + if err := rootCmd.Execute(); err != nil { t.Errorf("expected no error while running up command, got %v", err) } }() + time.Sleep(time.Second * 2) timeout := 15 * time.Second timeoutChannel := time.After(timeout) diff --git a/client/internal/connect.go b/client/internal/connect.go new file mode 100644 index 000000000..6c0a1902f --- /dev/null +++ b/client/internal/connect.go @@ -0,0 +1,210 @@ +package internal + +import ( + "context" + "time" + + log "github.com/sirupsen/logrus" + "github.com/wiretrustee/wiretrustee/iface" + mgm "github.com/wiretrustee/wiretrustee/management/client" + mgmProto "github.com/wiretrustee/wiretrustee/management/proto" + signal "github.com/wiretrustee/wiretrustee/signal/client" + + "github.com/cenkalti/backoff/v4" + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +// RunClient with main logic. +func RunClient( + ctx context.Context, config *Config, stopCh <-chan int, cleanupCh chan<- struct{}, +) error { + backOff := &backoff.ExponentialBackOff{ + InitialInterval: time.Second, + RandomizationFactor: backoff.DefaultRandomizationFactor, + Multiplier: backoff.DefaultMultiplier, + MaxInterval: 10 * time.Second, + MaxElapsedTime: 24 * 3 * time.Hour, // stop the client after 3 days trying (must be a huge problem, e.g permission denied) + Stop: backoff.Stop, + Clock: backoff.SystemClock, + } + + state := CtxGetState(ctx) + defer state.Set(StatusIdle) + + wrapErr := state.Wrap + operation := func() error { + // if context cancelled we not start new backoff cycle + select { + case <-ctx.Done(): + return nil + default: + } + + state.Set(StatusConnecting) + // validate our peer's Wireguard PRIVATE key + myPrivateKey, err := wgtypes.ParseKey(config.PrivateKey) + if err != nil { + log.Errorf("failed parsing Wireguard key %s: [%s]", config.PrivateKey, err.Error()) + return wrapErr(err) + } + + var mgmTlsEnabled bool + if config.ManagementURL.Scheme == "https" { + mgmTlsEnabled = true + } + + // connect (just a connection, no stream yet) and login to Management Service to get an initial global Wiretrustee config + mgmClient, loginResp, err := connectToManagement(ctx, config.ManagementURL.Host, myPrivateKey, mgmTlsEnabled) + if err != nil { + log.Warn(err) + return wrapErr(err) + } + + // with the global Wiretrustee config in hand connect (just a connection, no stream yet) Signal + signalClient, err := connectToSignal(ctx, loginResp.GetWiretrusteeConfig(), myPrivateKey) + if err != nil { + log.Error(err) + return wrapErr(err) + } + + peerConfig := loginResp.GetPeerConfig() + + engineConfig, err := createEngineConfig(myPrivateKey, config, peerConfig) + if err != nil { + log.Error(err) + return wrapErr(err) + } + + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + engine := NewEngine(ctx, cancel, signalClient, mgmClient, engineConfig) + err = engine.Start() + if err != nil { + log.Errorf("error while starting Wiretrustee Connection Engine: %s", err) + return wrapErr(err) + } + + log.Print("Wiretrustee engine started, my IP is: ", peerConfig.Address) + state.Set(StatusConnected) + + select { + case <-stopCh: + case <-ctx.Done(): + } + + backOff.Reset() + + err = mgmClient.Close() + if err != nil { + log.Errorf("failed closing Management Service client %v", err) + return wrapErr(err) + } + err = signalClient.Close() + if err != nil { + log.Errorf("failed closing Signal Service client %v", err) + return wrapErr(err) + } + + err = engine.Stop() + if err != nil { + log.Errorf("failed stopping engine %v", err) + return wrapErr(err) + } + + go func() { + cleanupCh <- struct{}{} + }() + + log.Info("stopped Wiretrustee client") + + if _, err := state.Status(); err == ErrResetConnection { + return err + } + + return nil + } + + err := backoff.Retry(operation, backOff) + if err != nil { + log.Errorf("exiting client retry loop due to unrecoverable error: %s", err) + return err + } + return nil +} + +// createEngineConfig converts configuration received from Management Service to EngineConfig +func createEngineConfig(key wgtypes.Key, config *Config, peerConfig *mgmProto.PeerConfig) (*EngineConfig, error) { + iFaceBlackList := make(map[string]struct{}) + for i := 0; i < len(config.IFaceBlackList); i += 2 { + iFaceBlackList[config.IFaceBlackList[i]] = struct{}{} + } + + engineConf := &EngineConfig{ + WgIfaceName: config.WgIface, + WgAddr: peerConfig.Address, + IFaceBlackList: iFaceBlackList, + WgPrivateKey: key, + WgPort: iface.DefaultWgPort, + } + + if config.PreSharedKey != "" { + preSharedKey, err := wgtypes.ParseKey(config.PreSharedKey) + if err != nil { + return nil, err + } + engineConf.PreSharedKey = &preSharedKey + } + + return engineConf, nil +} + +// connectToSignal creates Signal Service client and established a connection +func connectToSignal(ctx context.Context, wtConfig *mgmProto.WiretrusteeConfig, ourPrivateKey wgtypes.Key) (*signal.GrpcClient, error) { + var sigTLSEnabled bool + if wtConfig.Signal.Protocol == mgmProto.HostConfig_HTTPS { + sigTLSEnabled = true + } else { + sigTLSEnabled = false + } + + signalClient, err := signal.NewClient(ctx, wtConfig.Signal.Uri, ourPrivateKey, sigTLSEnabled) + if err != nil { + log.Errorf("error while connecting to the Signal Exchange Service %s: %s", wtConfig.Signal.Uri, err) + return nil, status.Errorf(codes.FailedPrecondition, "failed connecting to Signal Service : %s", err) + } + + return signalClient, nil +} + +// connectToManagement creates Management Services client, establishes a connection, logs-in and gets a global Wiretrustee config (signal, turn, stun hosts, etc) +func connectToManagement(ctx context.Context, managementAddr string, ourPrivateKey wgtypes.Key, tlsEnabled bool) (*mgm.GrpcClient, *mgmProto.LoginResponse, error) { + log.Debugf("connecting to management server %s", managementAddr) + client, err := mgm.NewClient(ctx, managementAddr, ourPrivateKey, tlsEnabled) + if err != nil { + return nil, nil, status.Errorf(codes.FailedPrecondition, "failed connecting to Management Service : %s", err) + } + log.Debugf("connected to management server %s", managementAddr) + + serverPublicKey, err := client.GetServerPublicKey() + if err != nil { + return nil, nil, status.Errorf(codes.FailedPrecondition, "failed while getting Management Service public key: %s", err) + } + + loginResp, err := client.Login(*serverPublicKey) + if err != nil { + if s, ok := status.FromError(err); ok && s.Code() == codes.PermissionDenied { + log.Error("peer registration required. Please run wiretrustee login command first") + return nil, nil, err + } else { + return nil, nil, err + } + } + + log.Debugf("peer logged in to Management Service %s", managementAddr) + + return client, loginResp, nil +} + diff --git a/client/internal/engine.go b/client/internal/engine.go index 19f79b8fc..6acd834e3 100644 --- a/client/internal/engine.go +++ b/client/internal/engine.go @@ -31,6 +31,8 @@ const ( PeerConnectionTimeoutMin = 30000 // ms ) +var ErrResetConnection = fmt.Errorf("reset connection") + // EngineConfig is a config for the Engine type EngineConfig struct { WgPort int @@ -95,10 +97,12 @@ type Peer struct { // NewEngine creates a new Connection Engine func NewEngine( + ctx context.Context, cancel context.CancelFunc, signalClient signal.Client, mgmClient mgm.Client, config *EngineConfig, - cancel context.CancelFunc, ctx context.Context, ) *Engine { return &Engine{ + ctx: ctx, + cancel: cancel, signal: signalClient, mgmClient: mgmClient, peerConns: map[string]*peer.Conn{}, @@ -106,8 +110,6 @@ func NewEngine( config: config, STUNs: []*ice.URL{}, TURNs: []*ice.URL{}, - cancel: cancel, - ctx: ctx, networkSerial: 0, } } @@ -380,6 +382,7 @@ func (e *Engine) receiveManagementEvents() { if err != nil { // happens if management is unavailable for a long time. // We want to cancel the operation of the whole client + _ = CtxGetState(e.ctx).Wrap(ErrResetConnection) e.cancel() return } @@ -615,6 +618,7 @@ func (e *Engine) receiveSignalEvents() { if err != nil { // happens if signal is unavailable for a long time. // We want to cancel the operation of the whole client + _ = CtxGetState(e.ctx).Wrap(ErrResetConnection) e.cancel() return } diff --git a/client/internal/engine_test.go b/client/internal/engine_test.go index 3605f9cec..a941e0125 100644 --- a/client/internal/engine_test.go +++ b/client/internal/engine_test.go @@ -40,7 +40,6 @@ var ( ) func TestEngine_UpdateNetworkMap(t *testing.T) { - // test setup key, err := wgtypes.GeneratePrivateKey() if err != nil { @@ -51,12 +50,12 @@ func TestEngine_UpdateNetworkMap(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - engine := NewEngine(&signal.MockClient{}, &mgmt.MockClient{}, &EngineConfig{ + engine := NewEngine(ctx, cancel, &signal.MockClient{}, &mgmt.MockClient{}, &EngineConfig{ WgIfaceName: "utun100", WgAddr: "100.64.0.1/24", WgPrivateKey: key, WgPort: 33100, - }, cancel, ctx) + }) type testCase struct { name string @@ -157,7 +156,6 @@ func TestEngine_UpdateNetworkMap(t *testing.T) { } for _, c := range []testCase{case1, case2, case3, case4, case5} { - t.Run(c.name, func(t *testing.T) { err = engine.updateNetworkMap(c.networkMap) if err != nil { @@ -179,13 +177,10 @@ func TestEngine_UpdateNetworkMap(t *testing.T) { } } }) - } - } func TestEngine_Sync(t *testing.T) { - key, err := wgtypes.GeneratePrivateKey() if err != nil { t.Fatal(err) @@ -199,7 +194,6 @@ func TestEngine_Sync(t *testing.T) { updates := make(chan *mgmtProto.SyncResponse) defer close(updates) syncFunc := func(msgHandler func(msg *mgmtProto.SyncResponse) error) error { - for msg := range updates { err := msgHandler(msg) if err != nil { @@ -209,12 +203,12 @@ func TestEngine_Sync(t *testing.T) { return nil } - engine := NewEngine(&signal.MockClient{}, &mgmt.MockClient{SyncFunc: syncFunc}, &EngineConfig{ + engine := NewEngine(ctx, cancel, &signal.MockClient{}, &mgmt.MockClient{SyncFunc: syncFunc}, &EngineConfig{ WgIfaceName: "utun100", WgAddr: "100.64.0.1/24", WgPrivateKey: key, WgPort: 33100, - }, cancel, ctx) + }) defer func() { err := engine.Stop() @@ -264,12 +258,10 @@ func TestEngine_Sync(t *testing.T) { break } } - } func TestEngine_MultiplePeers(t *testing.T) { - - //log.SetLevel(log.DebugLevel) + // log.SetLevel(log.DebugLevel) dir := t.TempDir() @@ -285,8 +277,9 @@ func TestEngine_MultiplePeers(t *testing.T) { } }() - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(CtxInitState(context.Background())) defer cancel() + sport := 10010 sigServer, err := startSignal(sport) if err != nil { @@ -366,7 +359,6 @@ func TestEngine_MultiplePeers(t *testing.T) { } func createEngine(ctx context.Context, cancel context.CancelFunc, setupKey string, i int, mport int, sport int) (*Engine, error) { - key, err := wgtypes.GeneratePrivateKey() if err != nil { return nil, err @@ -406,7 +398,7 @@ func createEngine(ctx context.Context, cancel context.CancelFunc, setupKey strin WgPort: wgPort, } - return NewEngine(signalClient, mgmtClient, conf, cancel, ctx), nil + return NewEngine(ctx, cancel, signalClient, mgmtClient, conf), nil } func startSignal(port int) (*grpc.Server, error) { @@ -429,7 +421,6 @@ func startSignal(port int) (*grpc.Server, error) { } func startManagement(port int, dataDir string) (*grpc.Server, error) { - config := &server.Config{ Stuns: []*server.Host{}, TURNConfig: &server.TURNConfig{}, diff --git a/client/internal/login.go b/client/internal/login.go new file mode 100644 index 000000000..31253a828 --- /dev/null +++ b/client/internal/login.go @@ -0,0 +1,118 @@ +package internal + +import ( + "context" + "time" + + "github.com/cenkalti/backoff/v4" + "github.com/google/uuid" + log "github.com/sirupsen/logrus" + "github.com/wiretrustee/wiretrustee/client/system" + mgm "github.com/wiretrustee/wiretrustee/management/client" + mgmProto "github.com/wiretrustee/wiretrustee/management/proto" + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +func Login(ctx context.Context, config *Config, setupKey string) error { + backOff := &backoff.ExponentialBackOff{ + InitialInterval: time.Second, + RandomizationFactor: backoff.DefaultRandomizationFactor, + Multiplier: backoff.DefaultMultiplier, + MaxInterval: 2 * time.Second, + MaxElapsedTime: time.Second * 10, + Stop: backoff.Stop, + Clock: backoff.SystemClock, + } + + // validate our peer's Wireguard PRIVATE key + myPrivateKey, err := wgtypes.ParseKey(config.PrivateKey) + if err != nil { + log.Errorf("failed parsing Wireguard key %s: [%s]", config.PrivateKey, err.Error()) + return err + } + + var mgmTlsEnabled bool + if config.ManagementURL.Scheme == "https" { + mgmTlsEnabled = true + } + + loginOp := func() error { + log.Debugf("connecting to Management Service %s", config.ManagementURL.String()) + mgmClient, err := mgm.NewClient(ctx, config.ManagementURL.Host, myPrivateKey, mgmTlsEnabled) + if err != nil { + log.Errorf("failed connecting to Management Service %s %v", config.ManagementURL.String(), err) + return err + } + log.Debugf("connected to management Service %s", config.ManagementURL.String()) + + serverKey, err := mgmClient.GetServerPublicKey() + if err != nil { + log.Errorf("failed while getting Management Service public key: %v", err) + return err + } + + _, err = loginPeer(*serverKey, mgmClient, setupKey) + if err != nil { + log.Errorf("failed logging-in peer on Management Service : %v", err) + return err + } + + err = mgmClient.Close() + if err != nil { + log.Errorf("failed closing Management Service client: %v", err) + return err + } + + return nil + } + + err = backoff.RetryNotify(loginOp, backOff, func(err error, duration time.Duration) { + log.Warnf("retrying Login to the Management service in %v due to error %v", duration, err) + }) + if err != nil { + log.Errorf("exiting login retry loop due to unrecoverable error: %v", err) + return err + } + + return nil +} + +// loginPeer attempts to login to Management Service. If peer wasn't registered, tries the registration flow. +func loginPeer(serverPublicKey wgtypes.Key, client *mgm.GrpcClient, setupKey string) (*mgmProto.LoginResponse, error) { + loginResp, err := client.Login(serverPublicKey) + if err != nil { + if s, ok := status.FromError(err); ok && s.Code() == codes.PermissionDenied { + log.Debugf("peer registration required") + return registerPeer(serverPublicKey, client, setupKey) + } else { + return nil, err + } + } + + log.Info("peer has successfully logged-in to Management Service") + + return loginResp, nil +} + +// registerPeer checks whether setupKey was provided via cmd line and if not then it prompts user to enter a key. +// Otherwise tries to register with the provided setupKey via command line. +func registerPeer(serverPublicKey wgtypes.Key, client *mgm.GrpcClient, setupKey string) (*mgmProto.LoginResponse, error) { + validSetupKey, err := uuid.Parse(setupKey) + if err != nil { + return nil, err + } + + log.Debugf("sending peer registration request to Management Service") + info := system.GetInfo() + loginResp, err := client.Register(serverPublicKey, validSetupKey.String(), info) + if err != nil { + log.Errorf("failed registering peer %v", err) + return nil, err + } + + log.Infof("peer has been successfully registered on Management Service") + + return loginResp, nil +} diff --git a/client/internal/state.go b/client/internal/state.go new file mode 100644 index 000000000..5f327079c --- /dev/null +++ b/client/internal/state.go @@ -0,0 +1,67 @@ +package internal + +import ( + "context" + "sync" +) + +type StatusType string + +const ( + StatusIdle StatusType = "Idle" + + StatusConnecting StatusType = "Connecting" + StatusConnected StatusType = "Connected" +) + +// CtxInitState setup context state into the context tree. +// +// This function should be used to initialize context before +// CtxGetState will be executed. +func CtxInitState(ctx context.Context) context.Context { + return context.WithValue(ctx, stateCtx, &contextState{ + status: StatusIdle, + }) +} + +// CtxGetState object to get/update state/errors of process. +func CtxGetState(ctx context.Context) *contextState { + return ctx.Value(stateCtx).(*contextState) +} + +type contextState struct { + err error + status StatusType + mutex sync.Mutex +} + +func (c *contextState) Set(update StatusType) { + c.mutex.Lock() + defer c.mutex.Unlock() + + c.status = update + c.err = nil +} + +func (c *contextState) Status() (StatusType, error) { + c.mutex.Lock() + defer c.mutex.Unlock() + + if c.err != nil { + return "", c.err + } + + return c.status, nil +} + +func (c *contextState) Wrap(err error) error { + c.mutex.Lock() + defer c.mutex.Unlock() + + c.err = err + return err +} + +type stateKey int + +var stateCtx stateKey diff --git a/client/proto/daemon.pb.go b/client/proto/daemon.pb.go new file mode 100644 index 000000000..8c603bebf --- /dev/null +++ b/client/proto/daemon.pb.go @@ -0,0 +1,566 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.26.0 +// protoc v3.17.3 +// source: daemon.proto + +package proto + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + _ "google.golang.org/protobuf/types/descriptorpb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type LoginRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // setupKey wiretrustee setup key. + SetupKey string `protobuf:"bytes,1,opt,name=setupKey,proto3" json:"setupKey,omitempty"` + // presharedKey for wireguard setup. + PresharedKey string `protobuf:"bytes,2,opt,name=presharedKey,proto3" json:"presharedKey,omitempty"` + // managementUrl to authenticate. + ManagementUrl string `protobuf:"bytes,3,opt,name=managementUrl,proto3" json:"managementUrl,omitempty"` +} + +func (x *LoginRequest) Reset() { + *x = LoginRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_daemon_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *LoginRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LoginRequest) ProtoMessage() {} + +func (x *LoginRequest) ProtoReflect() protoreflect.Message { + mi := &file_daemon_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LoginRequest.ProtoReflect.Descriptor instead. +func (*LoginRequest) Descriptor() ([]byte, []int) { + return file_daemon_proto_rawDescGZIP(), []int{0} +} + +func (x *LoginRequest) GetSetupKey() string { + if x != nil { + return x.SetupKey + } + return "" +} + +func (x *LoginRequest) GetPresharedKey() string { + if x != nil { + return x.PresharedKey + } + return "" +} + +func (x *LoginRequest) GetManagementUrl() string { + if x != nil { + return x.ManagementUrl + } + return "" +} + +type LoginResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *LoginResponse) Reset() { + *x = LoginResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_daemon_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *LoginResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LoginResponse) ProtoMessage() {} + +func (x *LoginResponse) ProtoReflect() protoreflect.Message { + mi := &file_daemon_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LoginResponse.ProtoReflect.Descriptor instead. +func (*LoginResponse) Descriptor() ([]byte, []int) { + return file_daemon_proto_rawDescGZIP(), []int{1} +} + +type UpRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *UpRequest) Reset() { + *x = UpRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_daemon_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpRequest) ProtoMessage() {} + +func (x *UpRequest) ProtoReflect() protoreflect.Message { + mi := &file_daemon_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpRequest.ProtoReflect.Descriptor instead. +func (*UpRequest) Descriptor() ([]byte, []int) { + return file_daemon_proto_rawDescGZIP(), []int{2} +} + +type UpResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *UpResponse) Reset() { + *x = UpResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_daemon_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpResponse) ProtoMessage() {} + +func (x *UpResponse) ProtoReflect() protoreflect.Message { + mi := &file_daemon_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpResponse.ProtoReflect.Descriptor instead. +func (*UpResponse) Descriptor() ([]byte, []int) { + return file_daemon_proto_rawDescGZIP(), []int{3} +} + +type StatusRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *StatusRequest) Reset() { + *x = StatusRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_daemon_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StatusRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StatusRequest) ProtoMessage() {} + +func (x *StatusRequest) ProtoReflect() protoreflect.Message { + mi := &file_daemon_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StatusRequest.ProtoReflect.Descriptor instead. +func (*StatusRequest) Descriptor() ([]byte, []int) { + return file_daemon_proto_rawDescGZIP(), []int{4} +} + +type StatusResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // status of the server. + Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` +} + +func (x *StatusResponse) Reset() { + *x = StatusResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_daemon_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StatusResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StatusResponse) ProtoMessage() {} + +func (x *StatusResponse) ProtoReflect() protoreflect.Message { + mi := &file_daemon_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StatusResponse.ProtoReflect.Descriptor instead. +func (*StatusResponse) Descriptor() ([]byte, []int) { + return file_daemon_proto_rawDescGZIP(), []int{5} +} + +func (x *StatusResponse) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +type DownRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *DownRequest) Reset() { + *x = DownRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_daemon_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DownRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DownRequest) ProtoMessage() {} + +func (x *DownRequest) ProtoReflect() protoreflect.Message { + mi := &file_daemon_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DownRequest.ProtoReflect.Descriptor instead. +func (*DownRequest) Descriptor() ([]byte, []int) { + return file_daemon_proto_rawDescGZIP(), []int{6} +} + +type DownResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *DownResponse) Reset() { + *x = DownResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_daemon_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DownResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DownResponse) ProtoMessage() {} + +func (x *DownResponse) ProtoReflect() protoreflect.Message { + mi := &file_daemon_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DownResponse.ProtoReflect.Descriptor instead. +func (*DownResponse) Descriptor() ([]byte, []int) { + return file_daemon_proto_rawDescGZIP(), []int{7} +} + +var File_daemon_proto protoreflect.FileDescriptor + +var file_daemon_proto_rawDesc = []byte{ + 0x0a, 0x0c, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, + 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x1a, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x74, 0x0a, 0x0c, 0x4c, 0x6f, 0x67, 0x69, + 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65, 0x74, 0x75, + 0x70, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x65, 0x74, 0x75, + 0x70, 0x4b, 0x65, 0x79, 0x12, 0x22, 0x0a, 0x0c, 0x70, 0x72, 0x65, 0x73, 0x68, 0x61, 0x72, 0x65, + 0x64, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x65, 0x73, + 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x12, 0x24, 0x0a, 0x0d, 0x6d, 0x61, 0x6e, 0x61, + 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x55, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x55, 0x72, 0x6c, 0x22, 0x0f, + 0x0a, 0x0d, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x0b, 0x0a, 0x09, 0x55, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x0c, 0x0a, 0x0a, + 0x55, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x0f, 0x0a, 0x0d, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x28, 0x0a, 0x0e, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, + 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x0d, 0x0a, 0x0b, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x22, 0x0e, 0x0a, 0x0c, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xe6, 0x01, 0x0a, 0x0d, 0x44, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x53, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x36, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, + 0x14, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, + 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x2d, + 0x0a, 0x02, 0x55, 0x70, 0x12, 0x11, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x55, 0x70, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, + 0x2e, 0x55, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x39, 0x0a, + 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x15, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, + 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, + 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x04, 0x44, 0x6f, 0x77, 0x6e, + 0x12, 0x13, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, + 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x08, 0x5a, + 0x06, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_daemon_proto_rawDescOnce sync.Once + file_daemon_proto_rawDescData = file_daemon_proto_rawDesc +) + +func file_daemon_proto_rawDescGZIP() []byte { + file_daemon_proto_rawDescOnce.Do(func() { + file_daemon_proto_rawDescData = protoimpl.X.CompressGZIP(file_daemon_proto_rawDescData) + }) + return file_daemon_proto_rawDescData +} + +var file_daemon_proto_msgTypes = make([]protoimpl.MessageInfo, 8) +var file_daemon_proto_goTypes = []interface{}{ + (*LoginRequest)(nil), // 0: daemon.LoginRequest + (*LoginResponse)(nil), // 1: daemon.LoginResponse + (*UpRequest)(nil), // 2: daemon.UpRequest + (*UpResponse)(nil), // 3: daemon.UpResponse + (*StatusRequest)(nil), // 4: daemon.StatusRequest + (*StatusResponse)(nil), // 5: daemon.StatusResponse + (*DownRequest)(nil), // 6: daemon.DownRequest + (*DownResponse)(nil), // 7: daemon.DownResponse +} +var file_daemon_proto_depIdxs = []int32{ + 0, // 0: daemon.DaemonService.Login:input_type -> daemon.LoginRequest + 2, // 1: daemon.DaemonService.Up:input_type -> daemon.UpRequest + 4, // 2: daemon.DaemonService.Status:input_type -> daemon.StatusRequest + 6, // 3: daemon.DaemonService.Down:input_type -> daemon.DownRequest + 1, // 4: daemon.DaemonService.Login:output_type -> daemon.LoginResponse + 3, // 5: daemon.DaemonService.Up:output_type -> daemon.UpResponse + 5, // 6: daemon.DaemonService.Status:output_type -> daemon.StatusResponse + 7, // 7: daemon.DaemonService.Down:output_type -> daemon.DownResponse + 4, // [4:8] is the sub-list for method output_type + 0, // [0:4] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_daemon_proto_init() } +func file_daemon_proto_init() { + if File_daemon_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_daemon_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*LoginRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_daemon_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*LoginResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_daemon_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_daemon_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_daemon_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StatusRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_daemon_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StatusResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_daemon_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DownRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_daemon_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DownResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_daemon_proto_rawDesc, + NumEnums: 0, + NumMessages: 8, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_daemon_proto_goTypes, + DependencyIndexes: file_daemon_proto_depIdxs, + MessageInfos: file_daemon_proto_msgTypes, + }.Build() + File_daemon_proto = out.File + file_daemon_proto_rawDesc = nil + file_daemon_proto_goTypes = nil + file_daemon_proto_depIdxs = nil +} diff --git a/client/proto/daemon.proto b/client/proto/daemon.proto new file mode 100644 index 000000000..53b74f1fa --- /dev/null +++ b/client/proto/daemon.proto @@ -0,0 +1,49 @@ +syntax = "proto3"; + +import "google/protobuf/descriptor.proto"; + +option go_package = "/proto"; + +package daemon; + +service DaemonService { + // Login uses setup key to prepare configuration for the daemon. + rpc Login(LoginRequest) returns (LoginResponse) {} + + // Up starts engine work in the daemon. + rpc Up(UpRequest) returns (UpResponse) {} + + // Status of the service. + rpc Status(StatusRequest) returns (StatusResponse) {} + + // Down engine work in the daemon. + rpc Down(DownRequest) returns (DownResponse) {} +}; + +message LoginRequest { + // setupKey wiretrustee setup key. + string setupKey = 1; + + // presharedKey for wireguard setup. + string presharedKey = 2; + + // managementUrl to authenticate. + string managementUrl = 3; +} + +message LoginResponse {} + +message UpRequest {} + +message UpResponse {} + +message StatusRequest{} + +message StatusResponse{ + // status of the server. + string status = 1; +} + +message DownRequest {} + +message DownResponse {} diff --git a/client/proto/daemon_grpc.pb.go b/client/proto/daemon_grpc.pb.go new file mode 100644 index 000000000..8ef4c0e8d --- /dev/null +++ b/client/proto/daemon_grpc.pb.go @@ -0,0 +1,217 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. + +package proto + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// DaemonServiceClient is the client API for DaemonService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type DaemonServiceClient interface { + // Login uses setup key to prepare configuration for the daemon. + Login(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*LoginResponse, error) + // Up starts engine work in the daemon. + Up(ctx context.Context, in *UpRequest, opts ...grpc.CallOption) (*UpResponse, error) + // Status of the service. + Status(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*StatusResponse, error) + // Down engine work in the daemon. + Down(ctx context.Context, in *DownRequest, opts ...grpc.CallOption) (*DownResponse, error) +} + +type daemonServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewDaemonServiceClient(cc grpc.ClientConnInterface) DaemonServiceClient { + return &daemonServiceClient{cc} +} + +func (c *daemonServiceClient) Login(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*LoginResponse, error) { + out := new(LoginResponse) + err := c.cc.Invoke(ctx, "/daemon.DaemonService/Login", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *daemonServiceClient) Up(ctx context.Context, in *UpRequest, opts ...grpc.CallOption) (*UpResponse, error) { + out := new(UpResponse) + err := c.cc.Invoke(ctx, "/daemon.DaemonService/Up", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *daemonServiceClient) Status(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*StatusResponse, error) { + out := new(StatusResponse) + err := c.cc.Invoke(ctx, "/daemon.DaemonService/Status", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *daemonServiceClient) Down(ctx context.Context, in *DownRequest, opts ...grpc.CallOption) (*DownResponse, error) { + out := new(DownResponse) + err := c.cc.Invoke(ctx, "/daemon.DaemonService/Down", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// DaemonServiceServer is the server API for DaemonService service. +// All implementations must embed UnimplementedDaemonServiceServer +// for forward compatibility +type DaemonServiceServer interface { + // Login uses setup key to prepare configuration for the daemon. + Login(context.Context, *LoginRequest) (*LoginResponse, error) + // Up starts engine work in the daemon. + Up(context.Context, *UpRequest) (*UpResponse, error) + // Status of the service. + Status(context.Context, *StatusRequest) (*StatusResponse, error) + // Down engine work in the daemon. + Down(context.Context, *DownRequest) (*DownResponse, error) + mustEmbedUnimplementedDaemonServiceServer() +} + +// UnimplementedDaemonServiceServer must be embedded to have forward compatible implementations. +type UnimplementedDaemonServiceServer struct { +} + +func (UnimplementedDaemonServiceServer) Login(context.Context, *LoginRequest) (*LoginResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Login not implemented") +} +func (UnimplementedDaemonServiceServer) Up(context.Context, *UpRequest) (*UpResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Up not implemented") +} +func (UnimplementedDaemonServiceServer) Status(context.Context, *StatusRequest) (*StatusResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Status not implemented") +} +func (UnimplementedDaemonServiceServer) Down(context.Context, *DownRequest) (*DownResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Down not implemented") +} +func (UnimplementedDaemonServiceServer) mustEmbedUnimplementedDaemonServiceServer() {} + +// UnsafeDaemonServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to DaemonServiceServer will +// result in compilation errors. +type UnsafeDaemonServiceServer interface { + mustEmbedUnimplementedDaemonServiceServer() +} + +func RegisterDaemonServiceServer(s grpc.ServiceRegistrar, srv DaemonServiceServer) { + s.RegisterService(&DaemonService_ServiceDesc, srv) +} + +func _DaemonService_Login_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(LoginRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DaemonServiceServer).Login(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/daemon.DaemonService/Login", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DaemonServiceServer).Login(ctx, req.(*LoginRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _DaemonService_Up_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DaemonServiceServer).Up(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/daemon.DaemonService/Up", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DaemonServiceServer).Up(ctx, req.(*UpRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _DaemonService_Status_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(StatusRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DaemonServiceServer).Status(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/daemon.DaemonService/Status", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DaemonServiceServer).Status(ctx, req.(*StatusRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _DaemonService_Down_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DownRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DaemonServiceServer).Down(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/daemon.DaemonService/Down", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DaemonServiceServer).Down(ctx, req.(*DownRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// DaemonService_ServiceDesc is the grpc.ServiceDesc for DaemonService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var DaemonService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "daemon.DaemonService", + HandlerType: (*DaemonServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Login", + Handler: _DaemonService_Login_Handler, + }, + { + MethodName: "Up", + Handler: _DaemonService_Up_Handler, + }, + { + MethodName: "Status", + Handler: _DaemonService_Status_Handler, + }, + { + MethodName: "Down", + Handler: _DaemonService_Down_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "daemon.proto", +} diff --git a/client/proto/generate.sh b/client/proto/generate.sh new file mode 100755 index 000000000..fb8eb74bc --- /dev/null +++ b/client/proto/generate.sh @@ -0,0 +1,4 @@ +#!/bin/bash +go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.26 +go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1 +protoc -I proto/ proto/daemon.proto --go_out=. --go-grpc_out=. diff --git a/client/server/server.go b/client/server/server.go new file mode 100644 index 000000000..ca60f8755 --- /dev/null +++ b/client/server/server.go @@ -0,0 +1,166 @@ +package server + +import ( + "context" + "fmt" + "sync" + + log "github.com/sirupsen/logrus" + + "github.com/wiretrustee/wiretrustee/client/internal" + "github.com/wiretrustee/wiretrustee/client/proto" +) + +// Server for service control. +type Server struct { + rootCtx context.Context + actCancel context.CancelFunc + + managementURL string + configPath string + stopCh chan int + cleanupCh chan<- struct{} + + mutex sync.Mutex + config *internal.Config + proto.UnimplementedDaemonServiceServer +} + +// New server instance constructor. +func New( + ctx context.Context, managementURL, configPath string, + stopCh chan int, cleanupCh chan<- struct{}, +) *Server { + return &Server{ + rootCtx: ctx, + managementURL: managementURL, + configPath: configPath, + stopCh: stopCh, + cleanupCh: cleanupCh, + } +} + +func (s *Server) Start() error { + state := internal.CtxGetState(s.rootCtx) + + // if current state contains any error, return it + // in all other cases we can continue execution only if status is idle and up command was + // not in the progress or already successfully estabilished connection. + status, err := state.Status() + if err != nil { + return err + } + + if status != internal.StatusIdle { + return nil + } + + ctx, cancel := context.WithCancel(s.rootCtx) + s.actCancel = cancel + + // if configuration exists, we just start connections. + config, err := internal.ReadConfig(s.managementURL, s.configPath) + if err != nil { + log.Warnf("no config file, skip connection stage: %v", err) + return nil + } + + go func() { + if err := internal.RunClient(ctx, config, s.stopCh, s.cleanupCh); err != nil { + log.Errorf("init connections: %v", err) + } + }() + + return nil +} + +// Login uses setup key to prepare configuration for the daemon. +func (s *Server) Login(_ context.Context, msg *proto.LoginRequest) (*proto.LoginResponse, error) { + s.mutex.Lock() + defer s.mutex.Unlock() + + managementURL := s.managementURL + if msg.ManagementUrl != "" { + managementURL = msg.ManagementUrl + } + + config, err := internal.GetConfig(managementURL, s.configPath, msg.PresharedKey) + if err != nil { + return nil, err + } + s.config = config + + // login operation uses backoff scheme to connect to management API + // we don't wait for result and return response immediately. + if err := internal.Login(s.rootCtx, s.config, msg.SetupKey); err != nil { + log.Errorf("failed login: %v", err) + return nil, err + } + + return &proto.LoginResponse{}, nil +} + +// Up starts engine work in the daemon. +func (s *Server) Up(_ context.Context, msg *proto.UpRequest) (*proto.UpResponse, error) { + s.mutex.Lock() + defer s.mutex.Unlock() + + state := internal.CtxGetState(s.rootCtx) + + // if current state contains any error, return it + // in all other cases we can continue execution only if status is idle and up command was + // not in the progress or already successfully estabilished connection. + status, err := state.Status() + if err != nil { + return nil, err + } + if status != internal.StatusIdle { + return nil, fmt.Errorf("up already in progress: current status %s", status) + } + + // it should be nill here, but . + if s.actCancel != nil { + s.actCancel() + } + ctx, cancel := context.WithCancel(s.rootCtx) + s.actCancel = cancel + + if s.config == nil { + return nil, fmt.Errorf("config is not defined, please call login command first") + } + + go func() { + if err := internal.RunClient(ctx, s.config, s.stopCh, s.cleanupCh); err != nil { + log.Errorf("run client connection: %v", state.Wrap(err)) + return + } + }() + + return &proto.UpResponse{}, nil +} + +// Down dengine work in the daemon. +func (s *Server) Down(ctx context.Context, msg *proto.DownRequest) (*proto.DownResponse, error) { + s.mutex.Lock() + defer s.mutex.Unlock() + + if s.actCancel == nil { + return nil, fmt.Errorf("service is not up") + } + s.actCancel() + + return &proto.DownResponse{}, nil +} + +// Status starts engine work in the daemon. +func (s *Server) Status(ctx context.Context, msg *proto.StatusRequest) (*proto.StatusResponse, error) { + s.mutex.Lock() + defer s.mutex.Unlock() + + status, err := internal.CtxGetState(s.rootCtx).Status() + if err != nil { + return nil, err + } + + return &proto.StatusResponse{Status: string(status)}, nil +} diff --git a/management/client/grpc.go b/management/client/grpc.go index 7c28928c9..c13ebafa0 100644 --- a/management/client/grpc.go +++ b/management/client/grpc.go @@ -29,7 +29,6 @@ type GrpcClient struct { // NewClient creates a new client to Management service func NewClient(ctx context.Context, addr string, ourPrivateKey wgtypes.Key, tlsEnabled bool) (*GrpcClient, error) { - transportOption := grpc.WithTransportCredentials(insecure.NewCredentials()) if tlsEnabled { @@ -47,7 +46,6 @@ func NewClient(ctx context.Context, addr string, ourPrivateKey wgtypes.Key, tlsE Time: 15 * time.Second, Timeout: 10 * time.Second, })) - if err != nil { log.Errorf("failed creating connection to Management Service %v", err) return nil, err @@ -68,14 +66,14 @@ func (c *GrpcClient) Close() error { return c.conn.Close() } -//defaultBackoff is a basic backoff mechanism for general issues +// defaultBackoff is a basic backoff mechanism for general issues func defaultBackoff(ctx context.Context) backoff.BackOff { return backoff.WithContext(&backoff.ExponentialBackOff{ InitialInterval: 800 * time.Millisecond, RandomizationFactor: backoff.DefaultRandomizationFactor, Multiplier: backoff.DefaultMultiplier, MaxInterval: 10 * time.Second, - MaxElapsedTime: 12 * time.Hour, //stop after 12 hours of trying, the error will be propagated to the general retry of the client + MaxElapsedTime: 12 * time.Hour, // stop after 12 hours of trying, the error will be propagated to the general retry of the client Stop: backoff.Stop, Clock: backoff.SystemClock, }, ctx) @@ -90,11 +88,9 @@ func (c *GrpcClient) ready() bool { // Sync wraps the real client's Sync endpoint call and takes care of retries and encryption/decryption of messages // Blocking request. The result will be sent via msgHandler callback function func (c *GrpcClient) Sync(msgHandler func(msg *proto.SyncResponse) error) error { - - var backOff = defaultBackoff(c.ctx) + backOff := defaultBackoff(c.ctx) operation := func() error { - log.Debugf("management connection state %v", c.conn.GetState()) if !c.ready() { @@ -215,7 +211,6 @@ func (c *GrpcClient) login(serverKey wgtypes.Key, req *proto.LoginRequest) (*pro WgPubKey: c.key.PublicKey().String(), Body: loginReq, }) - if err != nil { return nil, err }