Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add armed Listener with TLS handshake timeout #637

Merged
merged 4 commits into from
Jan 18, 2024
Merged

Conversation

Choraden
Copy link
Contributor

@Choraden Choraden commented Jan 5, 2024

This patch adds server side TLS handshake timeout. It will protect forwarders listener and additionally a handshake in mitm.

Due to the lack of a convenient solution upstream connect is given a constant 1m timeout to complete.

http_proxy.go Outdated
@@ -615,7 +618,9 @@ func (hp *HTTPProxy) listen() (net.Listener, error) {
case HTTPScheme:
return listener, nil
case HTTPSScheme, HTTP2Scheme:
return tls.NewListener(listener, hp.tlsConfig), nil
l := NewTLSListener(listener, hp.tlsConfig)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about we passed TLSServerConfig to NewTLSListener?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I decided to leave it as it is, to have logging in the same place where we load certs.

func (hp *HTTPProxy) configureHTTPS() error {
	if hp.config.CertFile == "" && hp.config.KeyFile == "" {
		hp.log.Infof("no TLS certificate provided, using self-signed certificate")
	} else {
		hp.log.Debugf("loading TLS certificate from %s and %s", hp.config.CertFile, hp.config.KeyFile)
	}

	hp.tlsConfig = httpsTLSConfigTemplate()

	return hp.config.ConfigureTLSConfig(hp.tlsConfig)
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eventually, I changed it.

@mmatczuk
Copy link
Contributor

mmatczuk commented Jan 5, 2024

It feels like tlshandshake could be inlined given that we have the TLSListener

@mmatczuk
Copy link
Contributor

mmatczuk commented Jan 5, 2024

Some more context it the commit messages would be helpful

@Choraden
Copy link
Contributor Author

Choraden commented Jan 5, 2024

It feels like tlshandshake could be inlined given that we have the TLSListener

Listener is one thing. The other is handling handshake in mitm or SC.
It can be later extended with proper error handling.

@Choraden Choraden changed the title Add server TLS handshake timeout Add TLS handshake timeout Jan 9, 2024
@Choraden Choraden requested a review from mmatczuk January 9, 2024 08:32
@mmatczuk
Copy link
Contributor

mmatczuk commented Jan 9, 2024

Now we can inline handshake in the listener.

@Choraden
Copy link
Contributor Author

I've came to a conclusion adding handshake in the listener might not be the best option.

The logic in the martian Proxy.Serve would cause Proxy to exit on a failed handshake.

conn, err := l.Accept()
nosigpipe.IgnoreSIGPIPE(conn)
if err != nil {
	var nerr net.Error
	if ok := errors.As(err, &nerr); ok && nerr.Temporary() {
		if delay == 0 {
			delay = 5 * time.Millisecond
		} else {
			delay *= 2
		}
		if max := time.Second; delay > max {
			delay = max
		}

		log.Debugf(context.TODO(), "temporary error on accept: %v", err)
		time.Sleep(delay)
		continue
	}

	if errors.Is(err, net.ErrClosed) {
		log.Debugf(context.TODO(), "listener closed, returning")
		return err
	}

	log.Errorf(context.TODO(), "failed to accept: %v", err)
	return err
}

@Choraden Choraden requested a review from a team as a code owner January 15, 2024 11:52
@Choraden Choraden changed the title Add TLS handshake timeout Add armed Listener with TLS handshake timeout Jan 15, 2024
net.go Outdated
@@ -78,3 +81,85 @@ func Listen(network, address string) (net.Listener, error) {
// I asked about it here: https://groups.google.com/g/golang-nuts/c/Q1I7Viz9AJc
return defaultListenConfig().Listen(context.Background(), network, address)
}

type Listener struct {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use exported variables to configure the listener like we do with http servers for instance.

net.go Outdated
handshakeTimeout time.Duration

// OnAccept is called when a new connection is successfully accepted.
OnAccept func(net.Conn)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An interface with the callback would be better if we are planning to have more of them.

http_proxy.go Outdated
@@ -251,6 +251,7 @@ func (hp *HTTPProxy) configureProxy() error {
hp.proxy.ReadTimeout = hp.config.ReadTimeout
hp.proxy.ReadHeaderTimeout = hp.config.ReadHeaderTimeout
hp.proxy.WriteTimeout = hp.config.WriteTimeout
hp.proxy.MITMTLSHandshakeTimeout = hp.config.TLSServerConfig.HandshakeTimeout
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move it to mitm setup

@@ -155,6 +155,10 @@ type Proxy struct {
// A zero or negative value means there will be no timeout.
WriteTimeout time.Duration

// MITMTLSHandshakeTimeout specifies the maximum amount of time to wait for a TLS handshake for a MITMed connection.
// Zero means no timeout.
MITMTLSHandshakeTimeout time.Duration
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move it adjanct to MITMFilter

res, conn, err = d.DialContextR(req.Context(), "tcp", req.URL.Host)

ctx, cancel := context.WithTimeout(req.Context(), 60*time.Second)
res, conn, err = d.DialContextR(ctx, "tcp", req.URL.Host)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's have ConnectTimeout field.

Let's open new issue to add context to socks.

@Choraden Choraden force-pushed the hg/tls_handshake branch 3 times, most recently from 28cd997 to 62e72b4 Compare January 16, 2024 17:19
@Choraden
Copy link
Contributor Author

@mmatczuk I've addressed your comments

net.go Outdated
}

func (l *Listener) withTLS(c net.Conn) (net.Conn, error) {
tc := tls.Server(c, l.TLSConfig)
Copy link
Contributor

@mmatczuk mmatczuk Jan 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use Martian/std naming tconn

net.go Outdated
Address string
Log log.Logger
TLSConfig *tls.Config
HandshakeTimeout time.Duration
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TLSHandshakeTimeout

net_test.go Outdated
t.Fatal(err)
}

donec := make(chan struct{})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use done as it's more popular variant.

net.go Outdated
}
}

func (l *Listener) withTLS(c net.Conn) (net.Conn, error) {
Copy link
Contributor

@mmatczuk mmatczuk Jan 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rename c to conn and return tls.Conn ptr

net_test.go Outdated
donec := make(chan struct{})

l := Listener{
Address: "[::]:0",
Copy link
Contributor

@mmatczuk mmatczuk Jan 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why bind to IPv6?

Tests should run on localhost as much as possible.
This way you can avoid the macOS banner to allow listening from the test process.
Please one an issue to fix that across the board i.e. in Martian tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why bind to IPv6?

I guess, I took it from martian tests.

Comment on lines 519 to 527
var hctx context.Context
if p.MITMTLSHandshakeTimeout > 0 {
var hcancel context.CancelFunc
hctx, hcancel = context.WithTimeout(req.Context(), p.MITMTLSHandshakeTimeout)
defer hcancel()
} else {
hctx = req.Context()
}
if err := tlsconn.HandshakeContext(hctx); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we have var error - less ugly code.

@@ -121,6 +121,9 @@ type Proxy struct {
// Implementations can return ErrConnectFallback to indicate that the CONNECT request should be handled by martian.
ConnectFunc ConnectFunc

// ConnectTimeout specifies the maximum amount of time to wait for a dial to complete.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not dial it's connect with upstream.

@Choraden
Copy link
Contributor Author

@mmatczuk I've addressed your comments and opened two issues.

The goal of this patch is to move listening related tasks to custom Listener type.
The Listener supports:
- tls connections with handshake timeout
- per connection read and write limits
- callbacks to easily attach metrics gatherer
The timeout is 60s by default.
Copy link
Contributor

@mmatczuk mmatczuk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@mmatczuk mmatczuk merged commit fb9e70a into main Jan 18, 2024
4 checks passed
@mmatczuk mmatczuk deleted the hg/tls_handshake branch January 18, 2024 10:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants