diff --git a/signal/loadtest/README.md b/signal/loadtest/README.md index 11a26bee2..8c11356de 100644 --- a/signal/loadtest/README.md +++ b/signal/loadtest/README.md @@ -8,6 +8,7 @@ Load testing tool for the NetBird signal server. - **Two exchange modes**: - **Single message**: Each pair exchanges one message for validation - **Continuous exchange**: Pairs continuously exchange messages for a specified duration (e.g., 30 seconds, 10 minutes) +- **TLS/HTTPS support**: Connect to TLS-enabled signal servers with optional certificate verification - **Configurable message interval**: Control message send rate in continuous mode - **Message exchange validation**: Validates encrypted body size > 0 - **Comprehensive metrics**: Tracks throughput, success/failure rates, and latency statistics @@ -53,18 +54,34 @@ go build -o signal-loadtest -test-duration 15m \ -log-level debug +# TLS server with valid certificate +./signal-loadtest \ + -server https://signal.example.com:443 \ + -pairs-per-sec 10 \ + -total-pairs 50 \ + -message-size 100 + +# TLS server with self-signed certificate +./signal-loadtest \ + -server https://localhost:443 \ + -pairs-per-sec 5 \ + -total-pairs 10 \ + -insecure-skip-verify \ + -log-level debug + # Show help ./signal-loadtest -h ``` **Available Flags:** -- `-server`: Signal server URL (default: `http://localhost:10000`) +- `-server`: Signal server URL (http:// or https://) (default: `http://localhost:10000`) - `-pairs-per-sec`: Peer pairs created per second (default: 10) - `-total-pairs`: Total number of peer pairs (default: 100) - `-message-size`: Message size in bytes (default: 100) - `-test-duration`: Maximum test duration, 0 = unlimited (default: 0) - `-exchange-duration`: Continuous exchange duration per pair, 0 = single message (default: 0) - `-message-interval`: Interval between messages in continuous mode (default: 100ms) +- `-insecure-skip-verify`: Skip TLS certificate verification for self-signed certificates (default: false) - `-log-level`: Log level: trace, debug, info, warn, error (default: info) ### Running Tests @@ -156,6 +173,7 @@ func main() { - **TestDuration**: Maximum test duration (optional, 0 = no limit) - **ExchangeDuration**: Duration for continuous message exchange per pair (0 = single message) - **MessageInterval**: Interval between messages in continuous mode (default: 100ms) +- **InsecureSkipVerify**: Skip TLS certificate verification (for self-signed certificates) - **RampUpDuration**: Gradual ramp-up period (not yet implemented) ## Metrics diff --git a/signal/loadtest/client.go b/signal/loadtest/client.go index a912c6977..34b82b2bf 100644 --- a/signal/loadtest/client.go +++ b/signal/loadtest/client.go @@ -2,11 +2,13 @@ package loadtest import ( "context" + "crypto/tls" "fmt" "strings" "time" "google.golang.org/grpc" + "google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/metadata" @@ -24,9 +26,23 @@ type Client struct { msgChannel chan *proto.EncryptedMessage } +// ClientConfig holds optional configuration for the client +type ClientConfig struct { + InsecureSkipVerify bool +} + // NewClient creates a new signal client for load testing func NewClient(serverURL, peerID string) (*Client, error) { - addr, opts, err := parseServerURL(serverURL) + return NewClientWithConfig(serverURL, peerID, nil) +} + +// NewClientWithConfig creates a new signal client with custom TLS configuration +func NewClientWithConfig(serverURL, peerID string, config *ClientConfig) (*Client, error) { + if config == nil { + config = &ClientConfig{} + } + + addr, opts, err := parseServerURL(serverURL, config.InsecureSkipVerify) if err != nil { return nil, fmt.Errorf("parse server URL: %w", err) } @@ -124,7 +140,7 @@ func (c *Client) receiveMessages() { } } -func parseServerURL(serverURL string) (string, []grpc.DialOption, error) { +func parseServerURL(serverURL string, insecureSkipVerify bool) (string, []grpc.DialOption, error) { serverURL = strings.TrimSpace(serverURL) if serverURL == "" { return "", nil, fmt.Errorf("server URL is empty") @@ -135,7 +151,11 @@ func parseServerURL(serverURL string) (string, []grpc.DialOption, error) { if strings.HasPrefix(serverURL, "https://") { addr = strings.TrimPrefix(serverURL, "https://") - return "", nil, fmt.Errorf("TLS support not yet implemented") + tlsConfig := &tls.Config{ + MinVersion: tls.VersionTLS12, + InsecureSkipVerify: insecureSkipVerify, + } + opts = append(opts, grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig))) } else if strings.HasPrefix(serverURL, "http://") { addr = strings.TrimPrefix(serverURL, "http://") opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials())) diff --git a/signal/loadtest/cmd/signal-loadtest/main.go b/signal/loadtest/cmd/signal-loadtest/main.go index 537584b61..4afb44972 100644 --- a/signal/loadtest/cmd/signal-loadtest/main.go +++ b/signal/loadtest/cmd/signal-loadtest/main.go @@ -12,24 +12,26 @@ import ( ) var ( - serverURL string - pairsPerSecond int - totalPairs int - messageSize int - testDuration time.Duration - exchangeDuration time.Duration - messageInterval time.Duration - logLevel string + serverURL string + pairsPerSecond int + totalPairs int + messageSize int + testDuration time.Duration + exchangeDuration time.Duration + messageInterval time.Duration + insecureSkipVerify bool + logLevel string ) func init() { - flag.StringVar(&serverURL, "server", "http://localhost:10000", "Signal server URL") + flag.StringVar(&serverURL, "server", "http://localhost:10000", "Signal server URL (http:// or https://)") flag.IntVar(&pairsPerSecond, "pairs-per-sec", 10, "Number of peer pairs to create per second") flag.IntVar(&totalPairs, "total-pairs", 100, "Total number of peer pairs to create") flag.IntVar(&messageSize, "message-size", 100, "Size of test message in bytes") flag.DurationVar(&testDuration, "test-duration", 0, "Maximum test duration (0 = unlimited)") flag.DurationVar(&exchangeDuration, "exchange-duration", 0, "Duration for continuous message exchange per pair (0 = single message)") flag.DurationVar(&messageInterval, "message-interval", 100*time.Millisecond, "Interval between messages in continuous mode") + flag.BoolVar(&insecureSkipVerify, "insecure-skip-verify", false, "Skip TLS certificate verification (use with self-signed certificates)") flag.StringVar(&logLevel, "log-level", "info", "Log level (trace, debug, info, warn, error)") } @@ -44,13 +46,14 @@ func main() { log.SetLevel(level) config := loadtest.LoadTestConfig{ - ServerURL: serverURL, - PairsPerSecond: pairsPerSecond, - TotalPairs: totalPairs, - MessageSize: messageSize, - TestDuration: testDuration, - ExchangeDuration: exchangeDuration, - MessageInterval: messageInterval, + ServerURL: serverURL, + PairsPerSecond: pairsPerSecond, + TotalPairs: totalPairs, + MessageSize: messageSize, + TestDuration: testDuration, + ExchangeDuration: exchangeDuration, + MessageInterval: messageInterval, + InsecureSkipVerify: insecureSkipVerify, } if err := validateConfig(config); err != nil { @@ -64,6 +67,9 @@ func main() { log.Infof(" Pairs per second: %d", config.PairsPerSecond) log.Infof(" Total pairs: %d", config.TotalPairs) log.Infof(" Message size: %d bytes", config.MessageSize) + if config.InsecureSkipVerify { + log.Warnf(" TLS certificate verification: DISABLED (insecure)") + } if config.TestDuration > 0 { log.Infof(" Test duration: %v", config.TestDuration) } diff --git a/signal/loadtest/rate_loadtest.go b/signal/loadtest/rate_loadtest.go index ee24cfe11..be21ca5d4 100644 --- a/signal/loadtest/rate_loadtest.go +++ b/signal/loadtest/rate_loadtest.go @@ -12,14 +12,15 @@ import ( // LoadTestConfig configuration for the load test type LoadTestConfig struct { - ServerURL string - PairsPerSecond int - TotalPairs int - MessageSize int - TestDuration time.Duration - ExchangeDuration time.Duration - MessageInterval time.Duration - RampUpDuration time.Duration + ServerURL string + PairsPerSecond int + TotalPairs int + MessageSize int + TestDuration time.Duration + ExchangeDuration time.Duration + MessageInterval time.Duration + RampUpDuration time.Duration + InsecureSkipVerify bool } // LoadTestMetrics metrics collected during the load test @@ -138,13 +139,17 @@ func (lt *LoadTest) executePairExchange(pairID int) error { senderID := fmt.Sprintf("sender-%d", pairID) receiverID := fmt.Sprintf("receiver-%d", pairID) - sender, err := NewClient(lt.config.ServerURL, senderID) + clientConfig := &ClientConfig{ + InsecureSkipVerify: lt.config.InsecureSkipVerify, + } + + sender, err := NewClientWithConfig(lt.config.ServerURL, senderID, clientConfig) if err != nil { return fmt.Errorf("create sender: %w", err) } defer sender.Close() - receiver, err := NewClient(lt.config.ServerURL, receiverID) + receiver, err := NewClientWithConfig(lt.config.ServerURL, receiverID, clientConfig) if err != nil { return fmt.Errorf("create receiver: %w", err) }