Skip to content

Commit

Permalink
feat(netcore): implement observable TLS dialing (#6)
Browse files Browse the repository at this point in the history
With this diff, the code structure is ready to emit events.
  • Loading branch information
bassosimone authored Nov 19, 2024
1 parent a6c80f7 commit 6e3519a
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 9 deletions.
5 changes: 3 additions & 2 deletions netcore/dialer.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,15 @@ func (nx *Network) sequentialDial(
return nil, errors.Join(errv...)
}

// dialLog dials and emits structured logs.
func (nx *Network) dialLog(ctx context.Context, network, address string) (net.Conn, error) {
// TODO(bassosimone): do we want to automatically wrap the connection?
// TODO(bassosimone): emit structured logs
return nx.dialNet(ctx, network, address)
}

// dialNet dials using the net package or the configured dialing override.
func (nx *Network) dialNet(ctx context.Context, network, address string) (net.Conn, error) {
// TODO(bassosimone): do we want to automatically wrap the connection?

// if there's an user provided dialer func, use it
if nx.DialContextFunc != nil {
return nx.DialContextFunc(ctx, network, address)
Expand Down
4 changes: 4 additions & 0 deletions netcore/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ type Network struct {
// default [*net.Resolver] from the [net] package.
LookupHostFunc func(ctx context.Context, domain string) ([]string, error)

// NewTLSClientConn is the optional function to create a new TLS client
// connection. If this field is nil, we use the [crypto/tls] package.
NewTLSClientConn func(conn net.Conn, config *tls.Config) TLSConn

// TLSConfig is the TLS client config to use. If this field is nil, we
// will try to create a suitable config based on the network and address
// that are passed to the DialTLSContext method.
Expand Down
10 changes: 10 additions & 0 deletions netcore/tlsconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@ import (
"net"
)

// tlsConfig either returns the (cloned) [*tls.Config] from the [Network] or
// creates a new one by invoking the [newTLSConfig] function.
func (nx *Network) tlsConfig(network, address string) (*tls.Config, error) {
if nx.TLSConfig != nil {
config := nx.TLSConfig.Clone() // make sure we return a cloned config
return config, nil
}
return newTLSConfig(network, address)
}

// newTLSConfig is a best-effort attempt at creating a suitable TLS config
// for TCP and UDP transports using the network and address.
func newTLSConfig(network, address string) (*tls.Config, error) {
Expand Down
61 changes: 54 additions & 7 deletions netcore/tlsdialer.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,69 @@ import (
"net"
)

// TLSConn is the interface implementing [*tls.Conn] as well as
// the conn exported by alternative TLS libraries.
type TLSConn interface {
ConnectionState() tls.ConnectionState
HandshakeContext(ctx context.Context) error
net.Conn
}

// DialTLSContext establishes a new TLS connection.
func (nx *Network) DialTLSContext(ctx context.Context, network, address string) (net.Conn, error) {
// obtain the TLS config to use
config, err := nx.tlsConfig(network, address)
if err != nil {
return nil, err
}

child := &tls.Dialer{Config: config}
// resolve the endpoints to connect to
endpoints, err := nx.maybeLookupEndpoint(ctx, address)
if err != nil {
return nil, err
}

// build a TLS dialer
td := &tlsDialer{config: config, netx: nx}

return child.DialContext(ctx, network, address)
// sequentially attempt with each available endpoint
return nx.sequentialDial(ctx, network, td.dial, endpoints...)
}

type tlsDialer struct {
config *tls.Config
netx *Network
}

func (td *tlsDialer) dial(ctx context.Context, network, address string) (net.Conn, error) {
// dial and log the results of dialing
conn, err := td.netx.dialLog(ctx, network, address)
if err != nil {
return nil, err
}

// create TLS client connection
tconn := td.netx.newTLSClientConn(conn, td.config)

// TODO(bassosimone): emit before handshake event

// perform the TLS handshake
err = tconn.HandshakeContext(ctx)

// TODO(bassosimone): emit after handshake event

// process the results
if err != nil {
conn.Close()
return nil, err
}
return tconn, nil
}

func (nx *Network) tlsConfig(network, address string) (*tls.Config, error) {
if nx.TLSConfig != nil {
config := nx.TLSConfig.Clone() // make sure we return a cloned config
return config, nil
// newTLSClientConn creates a new TLS client connection.
func (nx *Network) newTLSClientConn(conn net.Conn, config *tls.Config) TLSConn {
if nx.NewTLSClientConn != nil {
return nx.NewTLSClientConn(conn, config)
}
return newTLSConfig(network, address)
return tls.Client(conn, config)
}

0 comments on commit 6e3519a

Please sign in to comment.