diff --git a/directives.go b/directives.go index 7af43cf..bad0094 100644 --- a/directives.go +++ b/directives.go @@ -51,6 +51,11 @@ func parseFrontend(srv *Server, d *scfg.Directive) error { } } + protocolDirective := d.Children.Get("protocol") + if protocolDirective != nil { + frontend.Protocols = protocolDirective.Params + } + for _, addr := range d.Params { host, port, err := net.SplitHostPort(addr) if err != nil { diff --git a/server.go b/server.go index fd2d7c8..14409ef 100644 --- a/server.go +++ b/server.go @@ -133,35 +133,68 @@ func (ln *Listener) handle(conn net.Conn) error { defer conn.Close() // TODO: setup timeouts - tlsConn := tls.Server(conn, ln.Server.ACMEConfig.TLSConfig()) + tlsConfig := ln.Server.ACMEConfig.TLSConfig() + getConfigForClient := tlsConfig.GetConfigForClient + tlsConfig.GetConfigForClient = func(hello *tls.ClientHelloInfo) (*tls.Config, error) { + // Call previous GetConfigForClient function, if any + var tlsConfig *tls.Config + if getConfigForClient != nil { + var err error + tlsConfig, err = getConfigForClient(hello) + if err != nil { + return nil, err + } + } else { + tlsConfig = ln.Server.ACMEConfig.TLSConfig() + } + + fe, err := ln.matchFrontend(hello.ServerName) + if err != nil { + return nil, err + } + + tlsConfig.NextProtos = fe.Protocols + return tlsConfig, nil + } + tlsConn := tls.Server(conn, tlsConfig) if err := tlsConn.Handshake(); err != nil { return err } tlsState := tlsConn.ConnectionState() + fe, err := ln.matchFrontend(tlsState.ServerName) + if err != nil { + return err + } - fe, ok := ln.Frontends[tlsState.ServerName] + return fe.handle(tlsConn, &tlsState) +} + +func (ln *Listener) matchFrontend(serverName string) (*Frontend, error) { + fe, ok := ln.Frontends[serverName] if !ok { - // match wildcard certificates, allowing only a single, non-partial wildcard, in the left-most label - i := strings.IndexByte(tlsState.ServerName, '.') - // don't allow wildcards with only a TLD (eg *.com) - if i >= 0 && strings.IndexByte(tlsState.ServerName[i+1:], '.') >= 0 { - fe, ok = ln.Frontends["*"+tlsState.ServerName[i:]] + // Match wildcard certificates, allowing only a single, non-partial + // wildcard, in the left-most label + i := strings.IndexByte(serverName, '.') + // Don't allow wildcards with only a TLD (e.g. *.com) + if i >= 0 && strings.IndexByte(serverName[i+1:], '.') >= 0 { + fe, ok = ln.Frontends["*"+serverName[i:]] } } if !ok { fe, ok = ln.Frontends[""] } if !ok { - return fmt.Errorf("can't find frontend for server name %q", tlsState.ServerName) + return nil, fmt.Errorf("can't find frontend for server name %q", serverName) } - return fe.handle(tlsConn, &tlsState) + return fe, nil } type Frontend struct { - Server *Server - Backend Backend + Server *Server + Backend Backend + Protocols []string } func (fe *Frontend) handle(downstream net.Conn, tlsState *tls.ConnectionState) error { @@ -184,6 +217,9 @@ func (fe *Frontend) handle(downstream net.Conn, tlsState *tls.ConnectionState) e if tlsState.ServerName != "" { tlvs = append(tlvs, authorityTLV(tlsState.ServerName)) } + if tlsState.NegotiatedProtocol != "" { + tlvs = append(tlvs, alpnTLV(tlsState.NegotiatedProtocol)) + } if tlv, err := sslTLV(tlsState); err != nil { return fmt.Errorf("failed to set PROXY protocol header SSL TLV: %v", err) } else { @@ -228,6 +264,13 @@ func authorityTLV(name string) proxyproto.TLV { } } +func alpnTLV(proto string) proxyproto.TLV { + return proxyproto.TLV{ + Type: proxyproto.PP2_TYPE_ALPN, + Value: []byte(proto), + } +} + func sslTLV(state *tls.ConnectionState) (proxyproto.TLV, error) { pp2ssl := tlvparse.PP2SSL{ Client: tlvparse.PP2_BITFIELD_CLIENT_SSL, // all of our connections are TLS diff --git a/tlstunnel.1.scd b/tlstunnel.1.scd index b692ee9..30ee269 100644 --- a/tlstunnel.1.scd +++ b/tlstunnel.1.scd @@ -66,6 +66,21 @@ The following directives are supported: This disables automatic TLS. + *protocol* ... + List of supported application-layer protocols. + + The first protocol which is also supported by the client is negociated. + + The protocols will be advertised via the TLS ALPN extension. See the + IANA registry for a list of protocol names: + https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids + + For instance, for an HTTP server supporting HTTP/1 and HTTP/2: + + ``` + protocol h2 http/1.1 http/1.0 + ``` + *tls* { ... } Customise global TLS configuration.