From 3c959bb178d585b171c5341cedab9ff26dc9de83 Mon Sep 17 00:00:00 2001
From: Mikhail Bragin <bangvalo@gmail.com>
Date: Sun, 6 Feb 2022 18:56:00 +0100
Subject: [PATCH] Login exits on a single attempt to connect to management
 (#220)

* fix: login exits on a single attempt to connect to management

* chore: add log verbosity for Login operation
---
 client/cmd/login.go       | 113 +++++++++++++++++++++++---------------
 management/client/grpc.go |   6 +-
 signal/client/grpc.go     |   4 +-
 3 files changed, 74 insertions(+), 49 deletions(-)

diff --git a/client/cmd/login.go b/client/cmd/login.go
index e439b8e8f..5e16848dc 100644
--- a/client/cmd/login.go
+++ b/client/cmd/login.go
@@ -4,6 +4,7 @@ import (
 	"bufio"
 	"context"
 	"fmt"
+	"github.com/cenkalti/backoff/v4"
 	"github.com/google/uuid"
 	log "github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
@@ -15,6 +16,7 @@ import (
 	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/status"
 	"os"
+	"time"
 )
 
 var (
@@ -24,55 +26,78 @@ var (
 		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
+			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,
 			}
 
-			config, err := internal.GetConfig(managementURL, configPath, preSharedKey)
-			if err != nil {
-				log.Errorf("failed getting config %s %v", configPath, err)
-				return err
+			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
 			}
 
-			//validate our peer's Wireguard PRIVATE key
-			myPrivateKey, err := wgtypes.ParseKey(config.PrivateKey)
+			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("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 anagement 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)
+				log.Errorf("exiting login retry loop due to unrecoverable error: %v", err)
 				return err
 			}
 
diff --git a/management/client/grpc.go b/management/client/grpc.go
index 369f0dde3..1f915b65c 100644
--- a/management/client/grpc.go
+++ b/management/client/grpc.go
@@ -36,7 +36,7 @@ func NewClient(ctx context.Context, addr string, ourPrivateKey wgtypes.Key, tlsE
 		transportOption = grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{}))
 	}
 
-	mgmCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
+	mgmCtx, cancel := context.WithTimeout(ctx, time.Second*3)
 	defer cancel()
 	conn, err := grpc.DialContext(
 		mgmCtx,
@@ -185,7 +185,7 @@ func (c *GrpcClient) GetServerPublicKey() (*wgtypes.Key, error) {
 		return nil, fmt.Errorf("no connection to management")
 	}
 
-	mgmCtx, cancel := context.WithTimeout(c.ctx, 5*time.Second) //todo make a general setting
+	mgmCtx, cancel := context.WithTimeout(c.ctx, time.Second*2)
 	defer cancel()
 	resp, err := c.realClient.GetServerKey(mgmCtx, &proto.Empty{})
 	if err != nil {
@@ -209,7 +209,7 @@ func (c *GrpcClient) login(serverKey wgtypes.Key, req *proto.LoginRequest) (*pro
 		log.Errorf("failed to encrypt message: %s", err)
 		return nil, err
 	}
-	mgmCtx, cancel := context.WithTimeout(c.ctx, 5*time.Second) //todo make a general setting
+	mgmCtx, cancel := context.WithTimeout(c.ctx, time.Second*2)
 	defer cancel()
 	resp, err := c.realClient.Login(mgmCtx, &proto.EncryptedMessage{
 		WgPubKey: c.key.PublicKey().String(),
diff --git a/signal/client/grpc.go b/signal/client/grpc.go
index dc7b8cb52..0406bdc06 100644
--- a/signal/client/grpc.go
+++ b/signal/client/grpc.go
@@ -58,7 +58,7 @@ func NewClient(ctx context.Context, addr string, key wgtypes.Key, tlsEnabled boo
 		transportOption = grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{}))
 	}
 
-	sigCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
+	sigCtx, cancel := context.WithTimeout(ctx, time.Second*3)
 	defer cancel()
 	conn, err := grpc.DialContext(
 		sigCtx,
@@ -291,7 +291,7 @@ func (c *GrpcClient) Send(msg *proto.Message) error {
 		return err
 	}
 
-	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+	ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
 	defer cancel()
 	_, err = c.realClient.Send(ctx, encryptedMessage)
 	if err != nil {