Skip to content
Open
Show file tree
Hide file tree
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
36 changes: 8 additions & 28 deletions initiator.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,8 @@ import (
"bufio"
"context"
"crypto/tls"
"strings"
"sync"
"time"

"golang.org/x/net/proxy"
)

// Initiator initiates connections and processes messages for all sessions.
Expand All @@ -44,21 +41,10 @@ type Initiator struct {
func (i *Initiator) Start() (err error) {
i.stopChan = make(chan interface{})

for sessionID, settings := range i.sessionSettings {
// TODO: move into session factory.
var tlsConfig *tls.Config
if tlsConfig, err = loadTLSConfig(settings); err != nil {
return
}

var dialer proxy.ContextDialer
if dialer, err = loadDialerConfig(settings); err != nil {
return
}

for sessionID := range i.sessionSettings {
i.wg.Add(1)
go func(sessID SessionID) {
i.handleConnection(i.sessions[sessID], tlsConfig, dialer)
i.handleConnection(i.sessions[sessID])
i.wg.Done()
}(sessionID)
}
Expand Down Expand Up @@ -143,7 +129,7 @@ func (i *Initiator) waitForReconnectInterval(reconnectInterval time.Duration) bo
return true
}

func (i *Initiator) handleConnection(session *session, tlsConfig *tls.Config, dialer proxy.ContextDialer) {
func (i *Initiator) handleConnection(session *session) {
var wg sync.WaitGroup
wg.Add(1)
go func() {
Expand Down Expand Up @@ -183,20 +169,14 @@ func (i *Initiator) handleConnection(session *session, tlsConfig *tls.Config, di
address := session.SocketConnectAddress[connectionAttempt%len(session.SocketConnectAddress)]
session.log.OnEventf("Connecting to: %v", address)

netConn, err := dialer.DialContext(ctx, "tcp", address)
netConn, err := session.dialer.DialContext(ctx, "tcp", address)
if err != nil {
session.log.OnEventf("Failed to connect: %v", err)
goto reconnect
} else if tlsConfig != nil {
// Unless InsecureSkipVerify is true, server name config is required for TLS
// to verify the received certificate
if !tlsConfig.InsecureSkipVerify && len(tlsConfig.ServerName) == 0 {
serverName := address
if c := strings.LastIndex(serverName, ":"); c > 0 {
serverName = serverName[:c]
}
tlsConfig.ServerName = serverName
}
} else if session.tlsConfig != nil {
// Derive ServerName from the current address without mutating the shared
// session TLS config, so multi-address reconnects keep the correct name.
tlsConfig := tlsConfigForAddress(session.tlsConfig, address)
tlsConn := tls.Client(netConn, tlsConfig)
if err = tlsConn.Handshake(); err != nil {
session.log.OnEventf("Failed handshake: %v", err)
Expand Down
7 changes: 7 additions & 0 deletions session.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,14 @@ package quickfix

import (
"bytes"
"crypto/tls"
"errors"
"fmt"
"sync"
"time"

"golang.org/x/net/proxy"

"github.com/quickfixgo/quickfix/datadictionary"
"github.com/quickfixgo/quickfix/internal"
)
Expand Down Expand Up @@ -64,6 +67,10 @@ type session struct {

timestampPrecision TimestampPrecision
lastCheckedResetSeqTime time.Time

// Specific to initiators; configured by sessionFactory.
tlsConfig *tls.Config
dialer proxy.ContextDialer
}

func (s *session) logError(err error) {
Expand Down
15 changes: 14 additions & 1 deletion session_factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -537,7 +537,20 @@ func (f sessionFactory) buildInitiatorSettings(session *session, settings *Sessi
}
}

return f.configureSocketConnectAddress(session, settings)
if err := f.configureSocketConnectAddress(session, settings); err != nil {
return err
}

var err error
if session.tlsConfig, err = loadTLSConfig(settings); err != nil {
return err
}

if session.dialer, err = loadDialerConfig(settings); err != nil {
return err
}

return nil
}

func (f sessionFactory) configureSocketConnectAddress(session *session, settings *SessionSettings) (err error) {
Expand Down
36 changes: 36 additions & 0 deletions session_factory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package quickfix

import (
"net"
"testing"
"time"

Expand Down Expand Up @@ -470,6 +471,39 @@ func (s *SessionFactorySuite) TestNewSessionBuildInitiators() {
s.Equal(10*time.Second, session.LogonTimeout)
s.Equal(2*time.Second, session.LogoutTimeout)
s.Equal("127.0.0.1:5000", session.SocketConnectAddress[0])
s.NotNil(session.dialer)
s.Nil(session.tlsConfig)
}

func (s *SessionFactorySuite) TestNewSessionBuildInitiatorsTLSAndDialer() {
s.sessionFactory.BuildInitiators = true
s.SessionSettings.Set(config.HeartBtInt, "34")
s.SessionSettings.Set(config.SocketConnectHost, "127.0.0.1")
s.SessionSettings.Set(config.SocketConnectPort, "5000")
s.SessionSettings.Set(config.SocketUseSSL, "Y")
s.SessionSettings.Set(config.SocketServerName, "fix.example.com")
s.SessionSettings.Set(config.SocketTimeout, "5s")

session, err := s.newSession(s.SessionID, s.MessageStoreFactory, s.SessionSettings, s.LogFactory, s.App)
s.Require().Nil(err)
s.Require().NotNil(session.tlsConfig)
s.Equal("fix.example.com", session.tlsConfig.ServerName)
s.Require().NotNil(session.dialer)

stdDialer, ok := session.dialer.(*net.Dialer)
s.Require().True(ok)
s.Equal(5*time.Second, stdDialer.Timeout)
}

func (s *SessionFactorySuite) TestNewSessionBuildInitiatorsInvalidDialer() {
s.sessionFactory.BuildInitiators = true
s.SessionSettings.Set(config.HeartBtInt, "34")
s.SessionSettings.Set(config.SocketConnectHost, "127.0.0.1")
s.SessionSettings.Set(config.SocketConnectPort, "5000")
s.SessionSettings.Set(config.ProxyType, "totallyinvalidproxytype")

_, err := s.newSession(s.SessionID, s.MessageStoreFactory, s.SessionSettings, s.LogFactory, s.App)
s.NotNil(err)
}

func (s *SessionFactorySuite) TestDuplicateSession() {
Expand Down Expand Up @@ -498,6 +532,8 @@ func (s *SessionFactorySuite) TestNewSessionBuildAcceptors() {
s.False(session.InitiateLogon)
s.Zero(session.HeartBtInt)
s.False(session.HeartBtIntOverride)
s.Nil(session.tlsConfig)
s.Nil(session.dialer)

s.SessionSettings.Set(config.HeartBtIntOverride, "Y")
session, err = s.newSession(s.SessionID, s.MessageStoreFactory, s.SessionSettings, s.LogFactory, s.App)
Expand Down
21 changes: 21 additions & 0 deletions tls.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"crypto/x509"
"errors"
"fmt"
"net"
"os"

"github.com/quickfixgo/quickfix/config"
Expand Down Expand Up @@ -183,3 +184,23 @@ func setMinVersionExplicit(settings *SessionSettings, tlsConfig *tls.Config) {
}
}
}

// tlsConfigForAddress returns a TLS config suitable for dialing address.
// When ServerName is unset and verification is enabled, a clone is returned with
// ServerName derived from address so the shared session config is not mutated.
func tlsConfigForAddress(base *tls.Config, address string) *tls.Config {
if base == nil {
return nil
}
if base.InsecureSkipVerify || base.ServerName != "" {
return base
}

cfg := base.Clone()
host, _, err := net.SplitHostPort(address)
if err != nil {
host = address
}
cfg.ServerName = host
return cfg
}
19 changes: 19 additions & 0 deletions tls_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -331,3 +331,22 @@ func (s *TLSTestSuite) TestServerNameWithCertsFromBytes() {
s.NotNil(tlsConfig)
s.Equal("DummyServerNameWithCerts", tlsConfig.ServerName)
}

func (s *TLSTestSuite) TestTLSConfigForAddress() {
s.Nil(tlsConfigForAddress(nil, "example.com:443"))

configured := &tls.Config{ServerName: "configured.example.com"}
s.Equal(configured, tlsConfigForAddress(configured, "other.example.com:443"))

insecure := &tls.Config{InsecureSkipVerify: true}
s.Equal(insecure, tlsConfigForAddress(insecure, "example.com:443"))

base := &tls.Config{}
derived := tlsConfigForAddress(base, "example.com:443")
s.NotEqual(base, derived)
s.Equal("example.com", derived.ServerName)
s.Empty(base.ServerName)

ipv6 := tlsConfigForAddress(&tls.Config{}, "[2001:db8:a0b:12f0::1]:3001")
s.Equal("2001:db8:a0b:12f0::1", ipv6.ServerName)
}
Loading