pax_global_header00006660000000000000000000000064123044773210014515gustar00rootroot0000000000000052 comment=e0613552d6b2ab4753a7d44f82975bdda62cd4ff slt-0.0.git20140301/000077500000000000000000000000001230447732100135535ustar00rootroot00000000000000slt-0.0.git20140301/LICENSE000066400000000000000000000010471230447732100145620ustar00rootroot00000000000000Copyright 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.md000066400000000000000000000034551230447732100150410ustar00rootroot00000000000000slt 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/TODO000066400000000000000000000001521230447732100142410ustar00rootroot00000000000000Other backend balancing strategies (weighted round robin, random) Switch to more robust logging framework slt-0.0.git20140301/example.yml000066400000000000000000000012541230447732100157330ustar00rootroot00000000000000# 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/000077500000000000000000000000001230447732100143265ustar00rootroot00000000000000slt-0.0.git20140301/man/slt.8000066400000000000000000000051751230447732100152310ustar00rootroot00000000000000.\" 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.html000066400000000000000000000137621230447732100161750ustar00rootroot00000000000000 slt(8) - multiplex a port for multiple TLS applications with SNI
  1. slt(8)
  2. 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.

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)

  1. March 2014
  2. slt(8)
slt-0.0.git20140301/man/slt.8.ronn000066400000000000000000000045311230447732100161770ustar00rootroot00000000000000slt(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.go000066400000000000000000000170061230447732100154140ustar00rootroot00000000000000package 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.go000066400000000000000000000172431230447732100164560ustar00rootroot00000000000000package 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) } }