Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 86 additions & 3 deletions foreign/go/client/tcp/tcp_core.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,13 @@
package tcp

import (
"crypto/tls"
"crypto/x509"
"encoding/binary"
"errors"
"fmt"
"net"
"os"
"strings"
"sync"
"time"

Expand Down Expand Up @@ -141,6 +144,35 @@ func WithServerAddress(address string) Option {
}
}

// WithTLS enables or disables TLS for the TCP client.
func WithTLS(enabled bool) Option {
return func(opts *Options) {
opts.config.tlsEnabled = enabled
}
}

// WithTLSDomain sets the TLS domain for server name indication (SNI).
// If empty, the domain will be automatically extracted from the server address.
func WithTLSDomain(domain string) Option {
return func(opts *Options) {
opts.config.tlsDomain = domain
}
}

// WithTLSCAFile sets the path to the CA certificate file for TLS verification.
func WithTLSCAFile(path string) Option {
return func(opts *Options) {
opts.config.tlsCAFile = path
}
}

// WithTLSValidateCertificate enables or disables TLS certificate validation.
func WithTLSValidateCertificate(validate bool) Option {
return func(opts *Options) {
opts.config.tlsValidateCertificate = validate
}
}

// NewIggyTcpClient creates a new Iggy TCP client with the given options.
// warning: don't use this function directly, use iggycli.NewIggyClient with iggycli.WithTcp instead.
func NewIggyTcpClient(options ...Option) (*IggyTcpClient, error) {
Expand Down Expand Up @@ -325,8 +357,20 @@ func (c *IggyTcpClient) connect() error {
return nil
}

// TODO TLS logic
return errors.New("TLS connection is not implemented yet")
// TLS logic
tlsConfig, err := c.createTLSConfig()
if err != nil {
return err
}

tlsConn := tls.Client(connection, tlsConfig)
if err := tlsConn.Handshake(); err != nil {
connection.Close()
return fmt.Errorf("TLS handshake failed: %w", err)
}

conn = tlsConn
return nil
},
retry.Attempts(uint(c.config.reconnection.maxRetries)),
retry.Delay(c.config.reconnection.interval),
Expand All @@ -342,6 +386,45 @@ func (c *IggyTcpClient) connect() error {
return nil
}

func (c *IggyTcpClient) createTLSConfig() (*tls.Config, error) {
tlsConfig := &tls.Config{
InsecureSkipVerify: !c.config.tlsValidateCertificate,
}

// Set server name for SNI
serverName := c.config.tlsDomain
if serverName == "" {
// Extract hostname from server address (format: "host:port")
host := c.currentServerAddress
if colonIdx := strings.LastIndex(host, ":"); colonIdx != -1 {
host = host[:colonIdx]
}
serverName = host
}

if serverName == "" {
return nil, ierror.ErrInvalidTlsDomain
}
tlsConfig.ServerName = serverName

// Load CA certificate if provided
if c.config.tlsCAFile != "" {
caCert, err := os.ReadFile(c.config.tlsCAFile)
if err != nil {
return nil, ierror.ErrInvalidTlsCertificatePath
}

caCertPool := x509.NewCertPool()
if !caCertPool.AppendCertsFromPEM(caCert) {
return nil, ierror.ErrInvalidTlsCertificate
}

tlsConfig.RootCAs = caCertPool
}

return tlsConfig, nil
}

func (c *IggyTcpClient) disconnect() error {
c.mtx.Lock()
defer c.mtx.Unlock()
Expand Down