pax_global_header 0000666 0000000 0000000 00000000064 12304477321 0014515 g ustar 00root root 0000000 0000000 52 comment=e0613552d6b2ab4753a7d44f82975bdda62cd4ff
slt-0.0.git20140301/ 0000775 0000000 0000000 00000000000 12304477321 0013553 5 ustar 00root root 0000000 0000000 slt-0.0.git20140301/LICENSE 0000664 0000000 0000000 00000001047 12304477321 0014562 0 ustar 00root root 0000000 0000000 Copyright 2014 Alan Shreve
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
slt-0.0.git20140301/README.md 0000664 0000000 0000000 00000003455 12304477321 0015041 0 ustar 00root root 0000000 0000000 slt is a dead-simple TLS reverse-proxy with SNI multiplexing (TLS virtual hosts).
That means you can send TLS/SSL connections for multiple different applications to the same port and forward
them all to the appropriate backend hosts depending on the intended destination.
# Features
### SNI Multiplexing
slt multiplexes connections to a single TLS port by inspecting the name in the SNI extension field of each connection.
### Simple YAML Configuration
You configure slt with a simple YAML configuration file:
bind_addr: ":443"
frontends:
v1.example.com:
backends:
-
addr: ":4443"
v2.example.com:
backends:
-
addr: "192.168.0.2:443"
-
addr: "192.168.0.1:443"
### Optional TLS Termination
Sometimes, you don't actually want to terminate the TLS traffic, you just want to forward it elsewhere. slt only
terminates the TLS traffic if you specify a private key and certificate file like so:
frontends:
v1.example.com:
tls_key: /path/to/v1.example.com.key
tls_crt: /path/to/v1.example.com.crt
### Round robin load balancing among arbitrary backends
slt performs simple round-robin load balancing when more than one backend is available (other strategies will be available in the future):
frontends:
v1.example.com:
backends:
-
addr: ":8080"
-
addr: ":8081"
# Running it
Running slt is also simple. It takes a single argument, the path to the configuration file:
./slt /path/to/config.yml
# Building it
Just cd into the directory and "go build". It requires Go 1.1+.
# Testing it
Just cd into the directory and "go test".
# Stability
I run slt in production handling hundreds of thousands of connections daily.
# License
Apache
slt-0.0.git20140301/TODO 0000664 0000000 0000000 00000000152 12304477321 0014241 0 ustar 00root root 0000000 0000000 Other backend balancing strategies (weighted round robin, random)
Switch to more robust logging framework
slt-0.0.git20140301/example.yml 0000664 0000000 0000000 00000001254 12304477321 0015733 0 ustar 00root root 0000000 0000000 # bind to this address to accept new TLS connections
bind_addr: ":443"
frontends:
# connections with SNI of "v1.example.com" are handled by this block,
# they are proxied to port 4443 on localhost
v1.example.com:
backends:
-
addr: ":4443"
# 1000 ms timeout connecting to this backend
connect_timeout: 1000
v2.example.com:
# round robin between two backends
backends:
-
addr: "192.168.0.2:443"
-
addr: "192.168.0.1:443"
example.org:
# terminate TLS traffic for example.org
tls_crt: /etc/certs/example.org.pem
tls_key: /etc/certs/example.org.key
backends:
-
addr: ":4444"
slt-0.0.git20140301/man/ 0000775 0000000 0000000 00000000000 12304477321 0014326 5 ustar 00root root 0000000 0000000 slt-0.0.git20140301/man/slt.8 0000664 0000000 0000000 00000005175 12304477321 0015231 0 ustar 00root root 0000000 0000000 .\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "SLT" "8" "March 2014" "" ""
.
.SH "NAME"
\fBslt\fR \- multiplex a port for multiple TLS applications with SNI
.
.SH "SYNOPOSIS"
\fBslt\fR \fIconfig\-file\fR
.
.SH "DESCRIPTION"
\fBslt\fR is a TLS reverse\-proxy which allows an administrator to run multiple TLS applications on a single port\. \fBslt\fR multiplexes incoming connections by inspecting the Server Name Indication (\fBSNI\fR) extension data and appropriately forwarding the connection to the appropriate upstream server\.
.
.SH "CONFIGURATION FILE"
Configure \fBslt\fR with a simple YAML file\. Specify a \fBbind_addr\fR to instuct \fBslt\fR where it should listen for incoming connections\. \fBslt\fR may listen for any number of \fBfrontends\fR\. Each frontend is identified by the name to match in the SNI data\. Each frontend forwards to any number of \fBbackends\fR\. You may specify each backend with a hash of values\. The only required attribute is \fBaddr\fR\. When more than one backend is enumerated, \fBslt\fR performs simple round\-robin load balancing among them\.
.
.P
An example configuration follows for listening on port 443 of all local interfaces multiplexing traffic for two applications, \fIv1\.example\.com\fR and \fIv2\.example\.com\fR\. \fIv1\.example\.com\fR forwards to a single upstream server on port 1234\. \fIv2\.example\.com\fR forwards to two upstream hosts on different addresses:
.
.IP "" 4
.
.nf
bind_addr: ":443"
frontends:
v1\.example\.com:
backends:
\- addr: ":1234"
v2\.example\.com:
backends:
\- addr: "192\.168\.0\.2:443"
\- addr: "192\.168\.0\.1:443"
.
.fi
.
.IP "" 0
.
.P
By default, \fBslt\fR does not terminate any TLS traffic\. \fBslt\fR only inspects connections for their SNI data before being forwarded upstream\. \fBslt\fR may terminate TLS traffic for any \fBfrontend\fR by providing paths to the TLS public certificate and private key files, like so:
.
.IP "" 4
.
.nf
frontends:
v1\.example\.com:
tls_key: /path/to/v1\.example\.com\.key
tls_crt: /path/to/v1\.example\.com\.crt
.
.fi
.
.IP "" 0
.
.P
Designate one \fBfrontend\fR to be the \fBdefault\fR in the case that no SNI data is present in the connection like so:
.
.IP "" 4
.
.nf
frontends:
v1\.example\.com:
default: true
.
.fi
.
.IP "" 0
.
.SH "EXIT STATUS"
Exit status is 0 on success, non\-zero on failure\.
.
.SH "LINKS"
.
.TP
\fBSource code and documentation\fR
https://github\.com/inconshreveable/slt \fI\fR
.
.TP
\fBServer Name Indication\fR
http://www\.ietf\.org/rfc/rfc3546\.txt \fI\fR
.
.SH "AUTHOR"
Alan Shreve (@inconshreveable)
.
.SH "SEE ALSO"
ssl(3) stunnel(8)
slt-0.0.git20140301/man/slt.8.html 0000664 0000000 0000000 00000013762 12304477321 0016175 0 ustar 00root root 0000000 0000000
slt(8) - multiplex a port for multiple TLS applications with SNI
- slt(8)
- slt(8)
NAME
slt
- multiplex a port for multiple TLS applications with SNI
SYNOPOSIS
slt
config-file
DESCRIPTION
slt
is a TLS reverse-proxy which allows an administrator to run
multiple TLS applications on a single port. slt
multiplexes incoming
connections by inspecting the Server Name Indication (SNI
) extension
data and appropriately forwarding the connection to the appropriate
upstream server.
CONFIGURATION FILE
Configure slt
with a simple YAML file. Specify a bind_addr
to instuct slt
where it should listen for incoming connections. slt
may listen for any number
of frontends
. Each frontend is identified by the name to match in the SNI
data. Each frontend forwards to any number of backends
. You may specify each
backend with a hash of values. The only required attribute is addr
. When
more than one backend is enumerated, slt
performs simple round-robin load
balancing among them.
An example configuration follows for listening on port 443 of all local
interfaces multiplexing traffic for two applications, v1.example.com
and v2.example.com. v1.example.com forwards to a single upstream
server on port 1234. v2.example.com forwards to two upstream hosts
on different addresses:
bind_addr: ":443"
frontends:
v1.example.com:
backends:
- addr: ":1234"
v2.example.com:
backends:
- addr: "192.168.0.2:443"
- addr: "192.168.0.1:443"
By default, slt
does not terminate any TLS traffic. slt
only inspects
connections for their SNI data before being forwarded upstream. slt
may
terminate TLS traffic for any frontend
by providing paths to the TLS
public certificate and private key files, like so:
frontends:
v1.example.com:
tls_key: /path/to/v1.example.com.key
tls_crt: /path/to/v1.example.com.crt
Designate one frontend
to be the default
in the case that no
SNI data is present in the connection like so:
frontends:
v1.example.com:
default: true
EXIT STATUS
Exit status is 0 on success, non-zero on failure.
LINKS
Source code and documentation
https://github.com/inconshreveable/slt
Server Name Indication
http://www.ietf.org/rfc/rfc3546.txt
AUTHOR
Alan Shreve (@inconshreveable)
SEE ALSO
ssl(3) stunnel(8)
slt-0.0.git20140301/man/slt.8.ronn 0000664 0000000 0000000 00000004531 12304477321 0016177 0 ustar 00root root 0000000 0000000 slt(8) - multiplex a port for multiple TLS applications with SNI
================================================================
## SYNOPOSIS
`slt`
## DESCRIPTION
`slt` is a TLS reverse-proxy which allows an administrator to run
multiple TLS applications on a single port. `slt` multiplexes incoming
connections by inspecting the Server Name Indication (`SNI`) extension
data and appropriately forwarding the connection to the appropriate
upstream server.
## CONFIGURATION FILE
Configure `slt` with a simple YAML file. Specify a `bind_addr` to instuct `slt`
where it should listen for incoming connections. `slt` may listen for any number
of `frontends`. Each frontend is identified by the name to match in the SNI
data. Each frontend forwards to any number of `backends`. You may specify each
backend with a hash of values. The only required attribute is `addr`. When
more than one backend is enumerated, `slt` performs simple round-robin load
balancing among them.
An example configuration follows for listening on port 443 of all local
interfaces multiplexing traffic for two applications, _v1.example.com_
and _v2.example.com_. _v1.example.com_ forwards to a single upstream
server on port 1234. _v2.example.com_ forwards to two upstream hosts
on different addresses:
bind_addr: ":443"
frontends:
v1.example.com:
backends:
- addr: ":1234"
v2.example.com:
backends:
- addr: "192.168.0.2:443"
- addr: "192.168.0.1:443"
By default, `slt` does not terminate any TLS traffic. `slt` only inspects
connections for their SNI data before being forwarded upstream. `slt` may
terminate TLS traffic for any `frontend` by providing paths to the TLS
public certificate and private key files, like so:
frontends:
v1.example.com:
tls_key: /path/to/v1.example.com.key
tls_crt: /path/to/v1.example.com.crt
Designate one `frontend` to be the `default` in the case that no
SNI data is present in the connection like so:
frontends:
v1.example.com:
default: true
## EXIT STATUS
Exit status is 0 on success, non-zero on failure.
## LINKS
* `Source code and documentation`:
[https://github.com/inconshreveable/slt]()
* `Server Name Indication`:
[http://www.ietf.org/rfc/rfc3546.txt]()
## AUTHOR
Alan Shreve (@inconshreveable)
## SEE ALSO
ssl(3) stunnel(8)
slt-0.0.git20140301/server.go 0000664 0000000 0000000 00000017006 12304477321 0015414 0 ustar 00root root 0000000 0000000 package main
import (
"crypto/tls"
"flag"
"fmt"
vhost "github.com/inconshreveable/go-vhost"
"io"
"io/ioutil"
"launchpad.net/goyaml"
"log"
"net"
"os"
"sync"
"time"
)
const (
muxTimeout = 10 * time.Second
defaultConnectTimeout = 10000 // milliseconds
)
type loadTLSConfigFn func(crtPath, keyPath string) (*tls.Config, error)
type Options struct {
configPath string
}
type Backend struct {
Addr string `"yaml:addr"`
ConnectTimeout int `yaml:connect_timeout"`
}
type Frontend struct {
Backends []Backend `yaml:"backends"`
Strategy string `yaml:"strategy"`
TLSCrt string `yaml:"tls_crt"`
mux *vhost.TLSMuxer
TLSKey string `yaml:"tls_key"`
Default bool `yaml:"default"`
strategy BackendStrategy `yaml:"-"`
tlsConfig *tls.Config `yaml:"-"`
}
type Configuration struct {
BindAddr string `yaml:"bind_addr"`
Frontends map[string]*Frontend `yaml:"frontends"`
defaultFrontend *Frontend
}
type Server struct {
*log.Logger
*Configuration
wait sync.WaitGroup
// these are for easier testing
mux *vhost.TLSMuxer
ready chan int
}
func (s *Server) Run() error {
// bind a port to handle TLS connections
l, err := net.Listen("tcp", s.Configuration.BindAddr)
if err != nil {
return err
}
s.Printf("Serving connections on %v", l.Addr())
// start muxing on it
s.mux, err = vhost.NewTLSMuxer(l, muxTimeout)
if err != nil {
return err
}
// wait for all frontends to finish
s.wait.Add(len(s.Frontends))
// setup muxing for each frontend
for name, front := range s.Frontends {
fl, err := s.mux.Listen(name)
if err != nil {
return err
}
go s.runFrontend(name, front, fl)
}
// custom error handler so we can log errors
go func() {
for {
conn, err := s.mux.NextError()
if conn == nil {
s.Printf("Failed to mux next connection, error: %v", err)
if _, ok := err.(vhost.Closed); ok {
return
} else {
continue
}
} else {
if _, ok := err.(vhost.NotFound); ok && s.defaultFrontend != nil {
go s.proxyConnection(conn, s.defaultFrontend)
} else {
s.Printf("Failed to mux connection from %v, error: %v", conn.RemoteAddr(), err)
// XXX: respond with valid TLS close messages
conn.Close()
}
}
}
}()
// we're ready, signal it for testing
if s.ready != nil {
close(s.ready)
}
s.wait.Wait()
return nil
}
func (s *Server) runFrontend(name string, front *Frontend, l net.Listener) {
// mark finished when done so Run() can return
defer s.wait.Done()
// always round-robin strategy for now
front.strategy = &RoundRobinStrategy{backends: front.Backends}
s.Printf("Handling connections to %v", name)
for {
// accept next connection to this frontend
conn, err := l.Accept()
if err != nil {
s.Printf("Failed to accept new connection for '%v': %v", conn.RemoteAddr())
if e, ok := err.(net.Error); ok {
if e.Temporary() {
continue
}
}
return
}
s.Printf("Accepted new connection for %v from %v", name, conn.RemoteAddr())
// proxy the connection to an backend
go s.proxyConnection(conn, front)
}
}
func (s *Server) proxyConnection(c net.Conn, front *Frontend) (err error) {
// unwrap if tls cert/key was specified
if front.tlsConfig != nil {
c = tls.Server(c, front.tlsConfig)
}
// pick the backend
backend := front.strategy.NextBackend()
// dial the backend
upConn, err := net.DialTimeout("tcp", backend.Addr, time.Duration(backend.ConnectTimeout)*time.Millisecond)
if err != nil {
s.Printf("Failed to dial backend connection %v: %v", backend.Addr, err)
c.Close()
return
}
s.Printf("Initiated new connection to backend: %v %v", upConn.LocalAddr(), upConn.RemoteAddr())
// join the connections
s.joinConnections(c, upConn)
return
}
func (s *Server) joinConnections(c1 net.Conn, c2 net.Conn) {
var wg sync.WaitGroup
halfJoin := func(dst net.Conn, src net.Conn) {
defer wg.Done()
defer dst.Close()
defer src.Close()
n, err := io.Copy(dst, src)
s.Printf("Copy from %v to %v failed after %d bytes with error %v", src.RemoteAddr(), dst.RemoteAddr(), n, err)
}
s.Printf("Joining connections: %v %v", c1.RemoteAddr(), c2.RemoteAddr())
wg.Add(2)
go halfJoin(c1, c2)
go halfJoin(c2, c1)
wg.Wait()
}
type BackendStrategy interface {
NextBackend() Backend
}
type RoundRobinStrategy struct {
backends []Backend
idx int
}
func (s *RoundRobinStrategy) NextBackend() Backend {
n := len(s.backends)
if n == 1 {
return s.backends[0]
} else {
s.idx = (s.idx + 1) % n
return s.backends[s.idx]
}
}
func parseArgs() (*Options, error) {
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage: %s \n\n", os.Args[0])
fmt.Fprintf(os.Stderr, "%s is a simple TLS reverse proxy that can multiplex TLS connections\n"+
"by inspecting the SNI extension on each incoming connection. This\n"+
"allows you to accept connections to many different backend TLS\n"+
"applications on a single port.\n\n"+
"%s takes a single argument: the path to a YAML configuration file.\n\n", os.Args[0], os.Args[0])
}
flag.Parse()
if len(flag.Args()) != 1 {
return nil, fmt.Errorf("You must specify a single argument, the path to the configuration file.")
}
return &Options{
configPath: flag.Arg(0),
}, nil
}
func parseConfig(configBuf []byte, loadTLS loadTLSConfigFn) (config *Configuration, err error) {
// deserialize/parse the config
config = new(Configuration)
if err = goyaml.Unmarshal(configBuf, &config); err != nil {
err = fmt.Errorf("Error parsing configuration file: %v", err)
return
}
// configuration validation / normalization
if config.BindAddr == "" {
err = fmt.Errorf("You must specify a bind_addr")
return
}
if len(config.Frontends) == 0 {
err = fmt.Errorf("You must specify at least one frontend")
return
}
for name, front := range config.Frontends {
if len(front.Backends) == 0 {
err = fmt.Errorf("You must specify at least one backend for frontend '%v'", name)
return
}
if front.Default {
if config.defaultFrontend != nil {
err = fmt.Errorf("Only one frontend may be the default")
return
}
config.defaultFrontend = front
}
for _, back := range front.Backends {
if back.ConnectTimeout == 0 {
back.ConnectTimeout = defaultConnectTimeout
}
if back.Addr == "" {
err = fmt.Errorf("You must specify an addr for each backend on frontend '%v'", name)
return
}
}
if front.TLSCrt != "" || front.TLSKey != "" {
if front.tlsConfig, err = loadTLS(front.TLSCrt, front.TLSKey); err != nil {
err = fmt.Errorf("Failed to load TLS configuration for frontend '%v': %v", name, err)
return
}
}
}
return
}
func loadTLSConfig(crtPath, keyPath string) (*tls.Config, error) {
cert, err := tls.LoadX509KeyPair(crtPath, keyPath)
if err != nil {
return nil, err
}
return &tls.Config{
Certificates: []tls.Certificate{cert},
}, nil
}
func main() {
// parse command line options
opts, err := parseArgs()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
// read configuration file
configBuf, err := ioutil.ReadFile(opts.configPath)
if err != nil {
fmt.Printf("Failed to read configuration file %s: %v\n", opts.configPath, err)
os.Exit(1)
}
// parse configuration file
config, err := parseConfig(configBuf, loadTLSConfig)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
// run server
s := &Server{
Configuration: config,
Logger: log.New(os.Stdout, "slt ", log.LstdFlags|log.Lshortfile),
}
// this blocks unless there's a startup error
err = s.Run()
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to start slt: %v\n", err)
os.Exit(1)
}
}
slt-0.0.git20140301/server_test.go 0000664 0000000 0000000 00000017243 12304477321 0016456 0 ustar 00root root 0000000 0000000 package main
import (
"crypto/tls"
"fmt"
"io/ioutil"
"log"
"net"
"reflect"
"testing"
)
var snakeoilCert = `-----BEGIN CERTIFICATE-----
MIICGTCCAYICCQCww5WxTI3a5jANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJB
VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0
cyBQdHkgTHRkMB4XDTEzMTIxOTExMDMzNloXDTQxMDUwNjExMDMzNlowXTELMAkG
A1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExITAfBgNVBAoMGEludGVybmV0
IFdpZGdpdHMgUHR5IEx0ZDEWMBQGA1UEAwwNKi5leGFtcGxlLmNvbTCBnzANBgkq
hkiG9w0BAQEFAAOBjQAwgYkCgYEArmBi147MNv5v+97eznwD2OTyCOToKV/IIOBM
qrSNu3iKASb817CoiPV9x9NmxdoLeVvVWHgGC9cBDo+j5fTPEdxQCE4Xm6KOUy0S
4/rJzxNniWFWusVgT4VbwWeNdEg22PM8uGKM9nrQ42UXdNsrXRWQdAxR966ZBCoG
xcwx4ZcCAwEAATANBgkqhkiG9w0BAQUFAAOBgQBd4bS8qYe7vld2rgIOsNM5sqBk
mMcVCZPqUDX9axYQGGHkxF1qXv2ohnNvdmlVQtreuKF82HNL0P5uuU5jIms8fXPv
20TxAD7CbdR4dFn38mRHovprt9No3vtL8PmxhDOs7EOKtNyXplbVtmjf1N27UbQ3
K+MApaOowXqkoBSx9Q==
-----END CERTIFICATE-----`
var snakeoilKey = `-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQCuYGLXjsw2/m/73t7OfAPY5PII5OgpX8gg4EyqtI27eIoBJvzX
sKiI9X3H02bF2gt5W9VYeAYL1wEOj6Pl9M8R3FAITheboo5TLRLj+snPE2eJYVa6
xWBPhVvBZ410SDbY8zy4Yoz2etDjZRd02ytdFZB0DFH3rpkEKgbFzDHhlwIDAQAB
AoGAWw7sLqJcE8+0TLOqZ+ss2yNbHLfkYE6rJDfc8TuN07rzXfytBjkzGSoQ/7tu
LJ1bZolFFIjAp4gj/iWWMewwAMfkoG3nT25z3Q8v+EPwO97kT5rgMW/sI9yamRhb
LQpENsaxF1UFW4ADxl32go2sPbYv/5hnMLB7bfR0vgZaFHkCQQDaAUgmKogKj0qb
BeuIftzLJWJ+uYYtUGpICF53LAbd/lUygnUx4fapcVQDTyHcpb1lRRRXuGfZn1x2
jn9KRC87AkEAzMSIpdZXXCigvEMWYi0laNV/AJjKKafBcq/l8VQcAq0FUhgeRCoB
FjSVJrngMwzu1cQC1Xwtp6Dh6+V4T51pVQJBALPQatpQKnXLSxYjA+tJ+IP3Cg7M
p8eolIFlpcVWIzPoHA3VXSUP5IxOVaWFF8EPU/C70dOo3r+5mmKPlp6DLxECQAxM
QWi0VsrSJdUosk9zJqwFJnuCsaGO0a9xoP29b3E5svgbOrYdT7NltQ9+Wli2jiGI
hCMOMi+/GdJxFaiya4ECQCabLUAE0YEZL0M4mrcALa4T0C2sKCW8Xo2wvbwDGc1Y
+GQErfiGNv0xDOWLYrqe40x71R8z4kZv4EKLH/7zjTE=
-----END RSA PRIVATE KEY-----`
func loadSnakeoilConfig(crtPath, keyPath string) (*tls.Config, error) {
cert, err := tls.X509KeyPair([]byte(snakeoilCert), []byte(snakeoilKey))
if err != nil {
return nil, err
}
return &tls.Config{Certificates: []tls.Certificate{cert}}, nil
}
func backendOrFail(t *testing.T) (net.Listener, string) {
cfg, err := loadSnakeoilConfig("", "")
if err != nil {
t.Fatalf("Failed to make snakeoil certificate: %v", err)
}
l, err := tls.Listen("tcp", "127.0.0.1:0", cfg)
if err != nil {
t.Fatalf("Failed to listen: %v", err)
}
return l, fmt.Sprintf("127.0.0.1:%d", l.Addr().(*net.TCPAddr).Port)
}
func mkServer(t *testing.T, cfgString string) *Server {
config, err := parseConfig([]byte(cfgString), loadSnakeoilConfig)
if err != nil {
t.Fatalf("Failed to parse config: %v", err)
}
return &Server{
Configuration: config,
Logger: log.New(ioutil.Discard, "", 0),
ready: make(chan int),
}
}
func TestSimple(t *testing.T) {
l, addr := backendOrFail(t)
s := mkServer(t, fmt.Sprintf(`
bind_addr: "127.0.0.1:55111"
frontends:
test.example.com:
backends:
-
addr: %s
`, addr))
go s.Run()
// wait for the listener to bind
<-s.ready
defer s.mux.Close()
expected := []byte("Hello World")
go func() {
out, err := tls.Dial("tcp", "127.0.0.1:55111", &tls.Config{ServerName: "test.example.com", InsecureSkipVerify: true})
if err != nil {
t.Fatalf("Failed to dial: %v", err)
}
out.Write(expected)
out.Close()
}()
in, err := l.Accept()
if err != nil {
t.Fatalf("Failed to accept new connection: %v", err)
}
got, err := ioutil.ReadAll(in)
if err != nil {
t.Fatalf("Error reading data from connection: %v", err)
}
if !reflect.DeepEqual(got, expected) {
t.Errorf("Wrong data read from connection. Got %v, expected %v", got, expected)
}
}
func TestMany(t *testing.T) {
l1, addr1 := backendOrFail(t)
l2, addr2 := backendOrFail(t)
s := mkServer(t, fmt.Sprintf(`
bind_addr: "127.0.0.1:55111"
frontends:
test1.example.com:
backends:
-
addr: %s
test2.example.com:
backends:
-
addr: %s
`, addr1, addr2))
go s.Run()
// wait for the listener to bind
<-s.ready
defer s.mux.Close()
sendData := func(payload, name string) {
out, err := tls.Dial("tcp", "127.0.0.1:55111", &tls.Config{ServerName: name, InsecureSkipVerify: true})
if err != nil {
t.Fatalf("Failed to dial: %v", err)
}
out.Write([]byte(payload))
out.Close()
}
check := func(l net.Listener, expected string) {
in, err := l.Accept()
if err != nil {
t.Errorf("Failed to accept new connection: %v", err)
return
}
got, err := ioutil.ReadAll(in)
if err != nil {
t.Errorf("Error reading data from connection: %v", err)
return
}
if !reflect.DeepEqual(got, []byte(expected)) {
t.Errorf("Wrong data read from connection. Got %v, expected %v", got, []byte(expected))
}
}
go sendData("Hello 1", "test1.example.com")
check(l1, "Hello 1")
go sendData("Hello 2", "test2.example.com")
check(l2, "Hello 2")
}
func TestHostNotFound(t *testing.T) {
_, addr := backendOrFail(t)
s := mkServer(t, fmt.Sprintf(`
bind_addr: "127.0.0.1:55111"
frontends:
test.example.com:
backends:
-
addr: %s
`, addr))
go s.Run()
<-s.ready
defer s.mux.Close()
_, err := tls.Dial("tcp", "127.0.0.1:55111", &tls.Config{ServerName: "foo.example.com", InsecureSkipVerify: true})
if err == nil {
t.Fatalf("Expected error when dialing wrong name, got nil")
}
}
func TestRoundRobin(t *testing.T) {
l1, addr1 := backendOrFail(t)
l2, addr2 := backendOrFail(t)
s := mkServer(t, fmt.Sprintf(`
bind_addr: "127.0.0.1:55111"
frontends:
test.example.com:
backends:
-
addr: %s
-
addr: %s
`, addr1, addr2))
go s.Run()
// wait for the listener to bind
<-s.ready
defer s.mux.Close()
payload := "Hello world!"
go func() {
for i := 0; i < 20; i++ {
out, err := tls.Dial("tcp", "127.0.0.1:55111", &tls.Config{ServerName: "test.example.com", InsecureSkipVerify: true})
if err != nil {
t.Fatalf("Failed to dial: %v", err)
}
out.Write([]byte(payload))
out.Close()
}
}()
var count1, count2 int
var l net.Listener = l1
for i := 0; i < 20; i++ {
// conections should switch off between backends
if l == l1 {
l = l2
} else {
l = l1
}
in, err := l.Accept()
if err != nil {
t.Errorf("Failed to accept new connection: %v", err)
return
}
got, err := ioutil.ReadAll(in)
if err != nil {
t.Errorf("Error reading data from connection: %v", err)
return
}
if !reflect.DeepEqual(got, []byte(payload)) {
t.Errorf("Wrong data read from connection. Got %v, expected %v", got, []byte(payload))
return
}
if l == l1 {
count1 += 1
} else {
count2 += 1
}
}
if count1 != 10 || count2 != 10 {
t.Fatalf("Expected 10 connections to each backend, got: %v %v", count1, count2)
}
}
// Check that we un
func TestTerminateTLS(t *testing.T) {
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("Failed to listen: %v", err)
}
addr := fmt.Sprintf("127.0.0.1:%d", l.Addr().(*net.TCPAddr).Port)
s := mkServer(t, fmt.Sprintf(`
bind_addr: "127.0.0.1:55111"
frontends:
test.example.com:
tls_crt: /snakeoil.crt
tls_key: /snakeoil.key
backends:
-
addr: %s
`, addr))
go s.Run()
// wait for the listener to bind
<-s.ready
defer s.mux.Close()
expected := []byte("Hello World")
go func() {
out, err := tls.Dial("tcp", "127.0.0.1:55111", &tls.Config{ServerName: "test.example.com", InsecureSkipVerify: true})
if err != nil {
t.Fatalf("Failed to dial: %v", err)
}
out.Write(expected)
out.Close()
}()
in, err := l.Accept()
if err != nil {
t.Fatalf("Failed to accept new connection: %v", err)
}
got, err := ioutil.ReadAll(in)
if err != nil {
t.Fatalf("Error reading data from connection: %v", err)
}
if !reflect.DeepEqual(got, expected) {
t.Errorf("Wrong data read from connection. Got %v, expected %v", got, expected)
}
}