Revert "Update and lock latest go-http-tunnel dependency"

This reverts commit 3c8c8fed77.
This commit is contained in:
Michal Jan Matczuk 2020-02-12 17:00:37 +01:00 committed by GitHub
parent 3c8c8fed77
commit cc79e7406f
26 changed files with 72 additions and 1694 deletions

16
Gopkg.lock generated
View file

@ -34,16 +34,7 @@
version = "v1.2.0"
[[projects]]
branch = "master"
digest = "1:e2d808cdb7eda4933f1b22f6747bd0b8dbbf79744c272500ce0c49ef58d31c96"
name = "github.com/inconshreveable/go-vhost"
packages = ["."]
pruneopts = ""
revision = "06d84117953b22058c096b49a429ebd4f3d3d97b"
[[projects]]
branch = "master"
digest = "1:a5b782c76280477562569308f47c8146626370d4a4657b78fc16b92c17afd8a6"
digest = "1:3a86b4526cc15ca4caf041c7f6943b59b3eb3711a9bc832de27b0801b826e019"
name = "github.com/mmatczuk/go-http-tunnel"
packages = [
".",
@ -53,8 +44,8 @@
"tunnelmock",
]
pruneopts = ""
revision = "f48a09d2ab15c6f4fcbeec67864d39a584f22b30"
source = "github.com/mmatczuk/go-http-tunnel"
revision = "75a30abccc8db9d1149845e9985e80cca57758a2"
version = "2.1"
[[projects]]
branch = "master"
@ -108,7 +99,6 @@
"github.com/cenkalti/backoff",
"github.com/felixge/tcpkeepalive",
"github.com/golang/mock/gomock",
"github.com/inconshreveable/go-vhost",
"github.com/mmatczuk/go-http-tunnel",
"github.com/mmatczuk/go-http-tunnel/id",
"github.com/mmatczuk/go-http-tunnel/log",

View file

@ -33,11 +33,6 @@
name = "github.com/golang/mock"
version = "1.2.0"
[[constraint]]
name = "github.com/mmatczuk/go-http-tunnel"
branch = "master"
source = "github.com/mmatczuk/go-http-tunnel"
[[constraint]]
branch = "master"
name = "golang.org/x/net"

View file

@ -1,13 +0,0 @@
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.

View file

@ -1,110 +0,0 @@
# go-vhost
go-vhost is a simple library that lets you implement virtual hosting functionality for different protocols (HTTP and TLS so far). go-vhost has a high-level and a low-level interface. The high-level interface lets you wrap existing net.Listeners with "muxer" objects. You can then Listen() on a muxer for a particular virtual host name of interest which will return to you a net.Listener for just connections with the virtual hostname of interest.
The lower-level go-vhost interface are just functions which extract the name/routing information for the given protocol and return an object implementing net.Conn which works as if no bytes had been consumed.
### [API Documentation](https://godoc.org/github.com/inconshreveable/go-vhost)
### Usage
```go
l, _ := net.Listen("tcp", *listen)
// start multiplexing on it
mux, _ := vhost.NewHTTPMuxer(l, muxTimeout)
// listen for connections to different domains
for _, v := range virtualHosts {
vhost := v
// vhost.Name is a virtual hostname like "foo.example.com"
muxListener, _ := mux.Listen(vhost.Name())
go func(vh virtualHost, ml net.Listener) {
for {
conn, _ := ml.Accept()
go vh.Handle(conn)
}
}(vhost, muxListener)
}
for {
conn, err := mux.NextError()
switch err.(type) {
case vhost.BadRequest:
log.Printf("got a bad request!")
conn.Write([]byte("bad request"))
case vhost.NotFound:
log.Printf("got a connection for an unknown vhost")
conn.Write([]byte("vhost not found"))
case vhost.Closed:
log.Printf("closed conn: %s", err)
default:
if conn != nil {
conn.Write([]byte("server error"))
}
}
if conn != nil {
conn.Close()
}
}
```
### Low-level API usage
```go
// accept a new connection
conn, _ := listener.Accept()
// parse out the HTTP request and the Host header
if vhostConn, err = vhost.HTTP(conn); err != nil {
panic("Not a valid http connection!")
}
fmt.Printf("Target Host: ", vhostConn.Host())
// Target Host: example.com
// vhostConn contains the entire request as if no bytes had been consumed
bytes, _ := ioutil.ReadAll(vhostConn)
fmt.Printf("%s", bytes)
// GET / HTTP/1.1
// Host: example.com
// User-Agent: ...
// ...
```
### Advanced introspection
The entire HTTP request headers are available for inspection in case you want to mux on something besides the Host header:
```go
// parse out the HTTP request and the Host header
if vhostConn, err = vhost.HTTP(conn); err != nil {
panic("Not a valid http connection!")
}
httpVersion := vhost.Request.MinorVersion
customRouting := vhost.Request.Header["X-Custom-Routing-Header"]
```
Likewise for TLS, you can look at detailed information about the ClientHello message:
```go
if vhostConn, err = vhost.TLS(conn); err != nil {
panic("Not a valid TLS connection!")
}
cipherSuites := vhost.ClientHelloMsg.CipherSuites
sessionId := vhost.ClientHelloMsg.SessionId
```
##### Memory reduction with Free
After you're done muxing, you probably don't need to inspect the header data anymore, so you can make it available for garbage collection:
```go
// look up the upstream host
upstreamHost := hostMapping[vhostConn.Host()]
// free up the muxing data
vhostConn.Free()
// vhostConn.Host() == ""
// vhostConn.Request == nil (HTTP)
// vhostConn.ClientHelloMsg == nil (TLS)
```

View file

@ -1,42 +0,0 @@
package vhost
import (
"bufio"
"net"
"net/http"
)
type HTTPConn struct {
*sharedConn
Request *http.Request
}
// HTTP parses the head of the first HTTP request on conn and returns
// a new, unread connection with metadata for virtual host muxing
func HTTP(conn net.Conn) (httpConn *HTTPConn, err error) {
c, rd := newShared(conn)
httpConn = &HTTPConn{sharedConn: c}
if httpConn.Request, err = http.ReadRequest(bufio.NewReader(rd)); err != nil {
return
}
// You probably don't need access to the request body and this makes the API
// simpler by allowing you to call Free() optionally
httpConn.Request.Body.Close()
return
}
// Free sets Request to nil so that it can be garbage collected
func (c *HTTPConn) Free() {
c.Request = nil
}
func (c *HTTPConn) Host() string {
if c.Request == nil {
return ""
}
return c.Request.Host
}

View file

@ -1,45 +0,0 @@
package vhost
import (
"net"
"net/http"
"testing"
)
func TestHTTPHost(t *testing.T) {
var testHostname string = "foo.example.com"
l, err := net.Listen("tcp", "127.0.0.1:12345")
if err != nil {
panic(err)
}
defer l.Close()
go func() {
conn, err := net.Dial("tcp", "127.0.0.1:12345")
if err != nil {
panic(err)
}
defer conn.Close()
req, err := http.NewRequest("GET", "http://"+testHostname+"/bar", nil)
if err != nil {
panic(err)
}
if err = req.Write(conn); err != nil {
panic(err)
}
}()
conn, err := l.Accept()
if err != nil {
panic(err)
}
c, err := HTTP(conn)
if err != nil {
panic(err)
}
if c.Host() != testHostname {
t.Errorf("Connection Host() is %s, expected %s", c.Host(), testHostname)
}
}

View file

@ -1,11 +0,0 @@
package vhost
import (
"net"
)
type Conn interface {
net.Conn
Host() string
Free()
}

View file

@ -1,337 +0,0 @@
package vhost
import (
"fmt"
"net"
"strings"
"sync"
"time"
)
var (
normalize = strings.ToLower
isClosed = func(err error) bool {
netErr, ok := err.(net.Error)
if ok {
return netErr.Temporary()
}
return false
}
)
// NotFound is returned when a vhost is not found
type NotFound struct {
error
}
// BadRequest is returned when extraction of the vhost name fails
type BadRequest struct {
error
}
// Closed is returned when the underlying connection is closed
type Closed struct {
error
}
type (
// this is the function you apply to a net.Conn to get
// a new virtual-host multiplexed connection
muxFn func(net.Conn) (Conn, error)
// an error encountered when multiplexing a connection
muxErr struct {
err error
conn net.Conn
}
)
type VhostMuxer struct {
listener net.Listener // listener on which we mux connections
muxTimeout time.Duration // a connection fails if it doesn't send enough data to mux after this timeout
vhostFn muxFn // new connections are multiplexed by applying this function
muxErrors chan muxErr // all muxing errors are sent over this channel
registry map[string]*Listener // registry of name -> listener
sync.RWMutex // protects the registry
}
func NewVhostMuxer(listener net.Listener, vhostFn muxFn, muxTimeout time.Duration) (*VhostMuxer, error) {
mux := &VhostMuxer{
listener: listener,
muxTimeout: muxTimeout,
vhostFn: vhostFn,
muxErrors: make(chan muxErr),
registry: make(map[string]*Listener),
}
go mux.run()
return mux, nil
}
// Listen begins multiplexing the underlying connection to send new
// connections for the given name over the returned listener.
func (m *VhostMuxer) Listen(name string) (net.Listener, error) {
name = normalize(name)
vhost := &Listener{
name: name,
mux: m,
accept: make(chan Conn),
}
if err := m.set(name, vhost); err != nil {
return nil, err
}
return vhost, nil
}
// NextError returns the next error encountered while mux'ing a connection.
// The net.Conn may be nil if the wrapped listener returned an error from Accept()
func (m *VhostMuxer) NextError() (net.Conn, error) {
muxErr := <-m.muxErrors
return muxErr.conn, muxErr.err
}
// Close closes the underlying listener
func (m *VhostMuxer) Close() {
m.listener.Close()
}
// run is the VhostMuxer's main loop for accepting new connections from the wrapped listener
func (m *VhostMuxer) run() {
for {
conn, err := m.listener.Accept()
if err != nil {
if isClosed(err) {
m.sendError(nil, Closed{err})
return
} else {
m.sendError(nil, err)
continue
}
}
go m.handle(conn)
}
}
// handle muxes a connection accepted from the listener
func (m *VhostMuxer) handle(conn net.Conn) {
defer func() {
// recover from failures
if r := recover(); r != nil {
m.sendError(conn, fmt.Errorf("NameMux.handle failed with error %v", r))
}
}()
// Make sure we detect dead connections while we decide how to multiplex
if err := conn.SetDeadline(time.Now().Add(m.muxTimeout)); err != nil {
m.sendError(conn, fmt.Errorf("Failed to set deadline: %v", err))
return
}
// extract the name
vconn, err := m.vhostFn(conn)
if err != nil {
m.sendError(conn, BadRequest{fmt.Errorf("Failed to extract vhost name: %v", err)})
return
}
// normalize the name
host := normalize(vconn.Host())
// look up the correct listener
l, ok := m.get(host)
if !ok {
m.sendError(vconn, NotFound{fmt.Errorf("Host not found: %v", host)})
return
}
if err = vconn.SetDeadline(time.Time{}); err != nil {
m.sendError(vconn, fmt.Errorf("Failed unset connection deadline: %v", err))
return
}
l.accept <- vconn
}
func (m *VhostMuxer) sendError(conn net.Conn, err error) {
m.muxErrors <- muxErr{conn: conn, err: err}
}
func (m *VhostMuxer) get(name string) (l *Listener, ok bool) {
m.RLock()
defer m.RUnlock()
l, ok = m.registry[name]
if !ok {
// look for a matching wildcard
parts := strings.Split(name, ".")
for i := 0; i < len(parts)-1; i++ {
parts[i] = "*"
name = strings.Join(parts[i:], ".")
l, ok = m.registry[name]
if ok {
break
}
}
}
return
}
func (m *VhostMuxer) set(name string, l *Listener) error {
m.Lock()
defer m.Unlock()
if _, exists := m.registry[name]; exists {
return fmt.Errorf("name %s is already bound", name)
}
m.registry[name] = l
return nil
}
func (m *VhostMuxer) del(name string) {
m.Lock()
defer m.Unlock()
delete(m.registry, name)
}
const (
serverError = `HTTP/1.0 500 Internal Server Error
Content-Length: 22
Internal Server Error
`
notFound = `HTTP/1.0 404 Not Found
Content-Length: 14
404 not found
`
badRequest = `HTTP/1.0 400 Bad Request
Content-Length: 12
Bad Request
`
)
type HTTPMuxer struct {
*VhostMuxer
}
// HandleErrors handles muxing errors by calling .NextError(). You must
// invoke this function if you do not want to handle the errors yourself.
func (m *HTTPMuxer) HandleErrors() {
for {
m.HandleError(m.NextError())
}
}
func (m *HTTPMuxer) HandleError(conn net.Conn, err error) {
switch err.(type) {
case Closed:
return
case NotFound:
conn.Write([]byte(notFound))
case BadRequest:
conn.Write([]byte(badRequest))
default:
if conn != nil {
conn.Write([]byte(serverError))
}
}
if conn != nil {
conn.Close()
}
}
// NewHTTPMuxer begins muxing HTTP connections on the given listener by inspecting
// the HTTP Host header in new connections.
func NewHTTPMuxer(listener net.Listener, muxTimeout time.Duration) (*HTTPMuxer, error) {
fn := func(c net.Conn) (Conn, error) { return HTTP(c) }
mux, err := NewVhostMuxer(listener, fn, muxTimeout)
return &HTTPMuxer{mux}, err
}
type TLSMuxer struct {
*VhostMuxer
}
// HandleErrors is the default error handler for TLS muxers. At the moment, it simply
// closes connections which are invalid or destined for virtual host names that it is
// not listening for.
// You must invoke this function if you do not want to handle the errors yourself.
func (m *TLSMuxer) HandleErrors() {
for {
conn, err := m.NextError()
if conn == nil {
if _, ok := err.(Closed); ok {
return
} else {
continue
}
} else {
// XXX: respond with valid TLS close messages
conn.Close()
}
}
}
func (m *TLSMuxer) Listen(name string) (net.Listener, error) {
// TLS SNI never includes the port
host, _, err := net.SplitHostPort(name)
if err != nil {
host = name
}
return m.VhostMuxer.Listen(host)
}
// NewTLSMuxer begins muxing TLS connections by inspecting the SNI extension.
func NewTLSMuxer(listener net.Listener, muxTimeout time.Duration) (*TLSMuxer, error) {
fn := func(c net.Conn) (Conn, error) { return TLS(c) }
mux, err := NewVhostMuxer(listener, fn, muxTimeout)
return &TLSMuxer{mux}, err
}
// Listener is returned by a call to Listen() on a muxer. A Listener
// only receives connections that were made to the name passed into the muxer's
// Listen call.
//
// Listener implements the net.Listener interface, so you can Accept() new
// connections and Close() it when finished. When you Close() a Listener,
// the parent muxer will stop listening for connections to the Listener's name.
type Listener struct {
name string
mux *VhostMuxer
accept chan Conn
}
// Accept returns the next mux'd connection for this listener and blocks
// until one is available.
func (l *Listener) Accept() (net.Conn, error) {
conn, ok := <-l.accept
if !ok {
return nil, fmt.Errorf("Listener closed")
}
return conn, nil
}
// Close stops the parent muxer from listening for connections to the mux'd
// virtual host name.
func (l *Listener) Close() error {
l.mux.del(l.name)
close(l.accept)
return nil
}
// Addr returns the address of the bound listener used by the parent muxer.
func (l *Listener) Addr() net.Addr {
// XXX: include name in address?
return l.mux.listener.Addr()
}
// Name returns the name of the virtual host this listener receives connections on.
func (l *Listener) Name() string {
return l.name
}

View file

@ -1,195 +0,0 @@
package vhost
import (
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"strconv"
"strings"
"testing"
"time"
)
// TestErrors ensures that error types for this package are implemented properly
func TestErrors(t *testing.T) {
// test case for https://github.com/inconshreveable/go-vhost/pull/2
// create local err vars of error interface type
var notFoundErr error
var badRequestErr error
var closedErr error
// stuff local error types in to interface values
notFoundErr = NotFound{fmt.Errorf("test NotFound")}
badRequestErr = BadRequest{fmt.Errorf("test BadRequest")}
closedErr = Closed{fmt.Errorf("test Closed")}
// assert the types
switch errType := notFoundErr.(type) {
case NotFound:
default:
t.Fatalf("expected NotFound, got: %s", errType)
}
switch errType := badRequestErr.(type) {
case BadRequest:
default:
t.Fatalf("expected BadRequest, got: %s", errType)
}
switch errType := closedErr.(type) {
case Closed:
default:
t.Fatalf("expected Closed, got: %s", errType)
}
}
func localListener(t *testing.T) (net.Listener, string) {
l, err := net.Listen("tcp", "localhost:0")
if err != nil {
t.Fatalf("failed to listen: %v", err)
}
return l, strconv.Itoa(l.Addr().(*net.TCPAddr).Port)
}
func TestHTTPMux(t *testing.T) {
l, port := localListener(t)
mux, err := NewHTTPMuxer(l, time.Second)
if err != nil {
t.Fatalf("failed to start muxer: %v", err)
}
go mux.HandleErrors()
muxed, err := mux.Listen("example.com")
if err != nil {
t.Fatalf("failed to listen on muxer: %v", muxed)
}
go http.Serve(muxed, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
io.Copy(w, r.Body)
}))
msg := "test"
url := "http://localhost:" + port
resp, err := http.Post(url, "text/plain", strings.NewReader(msg))
if err != nil {
t.Fatalf("failed to post: %v", err)
}
if resp.StatusCode != 404 {
t.Fatalf("sent incorrect host header, expected 404 but got %d", resp.StatusCode)
}
req, err := http.NewRequest("POST", url, strings.NewReader(msg))
if err != nil {
t.Fatalf("failed to construct HTTP request: %v", err)
}
req.Host = "example.com"
req.Header.Set("Content-Type", "text/plain")
resp, err = new(http.Client).Do(req)
if err != nil {
t.Fatalf("failed to make HTTP request", err)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatalf("failed to read: %v", err)
}
got := string(body)
if got != msg {
t.Fatalf("unexpected resposne. got: %v, expected: %v", got, msg)
}
}
func testMux(t *testing.T, listen, dial string) {
muxFn := func(c net.Conn) (Conn, error) {
return fakeConn{c, dial}, nil
}
fakel := make(fakeListener, 1)
mux, err := NewVhostMuxer(fakel, muxFn, time.Second)
if err != nil {
t.Fatalf("failed to start vhost muxer: %v", err)
}
l, err := mux.Listen(listen)
if err != nil {
t.Fatalf("failed to listen for %s", err)
}
done := make(chan struct{})
go func() {
conn, err := l.Accept()
if err != nil {
t.Fatalf("failed to accept connection: %v", err)
return
}
got := conn.(Conn).Host()
expected := dial
if got != expected {
t.Fatalf("got connection with unexpected host. got: %s, expected: %s", got, expected)
return
}
close(done)
}()
go func() {
_, err := mux.NextError()
if err != nil {
t.Fatalf("muxing error: %v", err)
}
}()
fakel <- struct{}{}
select {
case <-done:
case <-time.After(time.Second):
t.Fatalf("test timed out: dial: %s listen: %s", dial, listen)
}
}
func TestMuxingPatterns(t *testing.T) {
var tests = []struct {
listen string
dial string
}{
{"example.com", "example.com"},
{"sub.example.com", "sub.example.com"},
{"*.example.com", "sub.example.com"},
{"*.example.com", "nested.sub.example.com"},
}
for _, test := range tests {
testMux(t, test.listen, test.dial)
}
}
type fakeConn struct {
net.Conn
host string
}
func (c fakeConn) SetDeadline(d time.Time) error { return nil }
func (c fakeConn) Host() string { return c.host }
func (c fakeConn) Free() {}
type fakeNetConn struct {
net.Conn
}
func (fakeNetConn) SetDeadline(time.Time) error { return nil }
type fakeListener chan struct{}
func (l fakeListener) Accept() (net.Conn, error) {
for _ = range l {
return fakeNetConn{nil}, nil
}
select {}
}
func (fakeListener) Addr() net.Addr { return nil }
func (fakeListener) Close() error { return nil }

View file

@ -1,52 +0,0 @@
package vhost
import (
"bytes"
"io"
"net"
"sync"
)
const (
initVhostBufSize = 1024 // allocate 1 KB up front to try to avoid resizing
)
type sharedConn struct {
sync.Mutex
net.Conn // the raw connection
vhostBuf *bytes.Buffer // all of the initial data that has to be read in order to vhost a connection is saved here
}
func newShared(conn net.Conn) (*sharedConn, io.Reader) {
c := &sharedConn{
Conn: conn,
vhostBuf: bytes.NewBuffer(make([]byte, 0, initVhostBufSize)),
}
return c, io.TeeReader(conn, c.vhostBuf)
}
func (c *sharedConn) Read(p []byte) (n int, err error) {
c.Lock()
if c.vhostBuf == nil {
c.Unlock()
return c.Conn.Read(p)
}
n, err = c.vhostBuf.Read(p)
// end of the request buffer
if err == io.EOF {
// let the request buffer get garbage collected
// and make sure we don't read from it again
c.vhostBuf = nil
// continue reading from the connection
var n2 int
n2, err = c.Conn.Read(p[n:])
// update total read
n += n2
}
c.Unlock()
return
}

View file

@ -1,64 +0,0 @@
package vhost
import (
"bytes"
"io"
"net"
"reflect"
"testing"
)
func TestHeaderPreserved(t *testing.T) {
var msg string = "TestHeaderPreserved message! Hello world!"
var headerLen int = 15
l, err := net.Listen("tcp", "127.0.0.1:12345")
if err != nil {
panic(err)
}
defer l.Close()
go func() {
conn, err := net.Dial("tcp", "127.0.0.1:12345")
if err != nil {
panic(err)
}
if _, err := conn.Write([]byte(msg)); err != nil {
panic(err)
}
if err = conn.Close(); err != nil {
panic(err)
}
}()
conn, err := l.Accept()
if err != nil {
panic(err)
}
// create a shared connection object
c, rd := newShared(conn)
// read out a "header"
p := make([]byte, headerLen)
_, err = io.ReadFull(rd, p)
if err != nil {
panic(err)
}
// make sure we got the header
expectedHeader := []byte(msg[:headerLen])
if !reflect.DeepEqual(p, expectedHeader) {
t.Errorf("Read header bytes %s, expected %s", p, expectedHeader)
return
}
// read out the entire connection. make sure it includes the header
buf := bytes.NewBuffer([]byte{})
io.Copy(buf, c)
expected := []byte(msg)
if !reflect.DeepEqual(buf.Bytes(), expected) {
t.Errorf("Read full connection bytes %s, expected %s", buf.Bytes(), expected)
}
}

View file

@ -1,434 +0,0 @@
// Portions of the TLS code are:
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// TLS virtual hosting
package vhost
import (
"bytes"
"errors"
"io"
"net"
"strconv"
)
const (
maxPlaintext = 16384 // maximum plaintext payload length
maxCiphertext = 16384 + 2048 // maximum ciphertext payload length
recordHeaderLen = 5 // record header length
maxHandshake = 65536 // maximum handshake we support (protocol max is 16 MB)
)
type alert uint8
const (
alertUnexpectedMessage alert = 10
alertRecordOverflow alert = 22
alertInternalError alert = 80
)
var alertText = map[alert]string{
alertUnexpectedMessage: "unexpected message",
alertRecordOverflow: "record overflow",
alertInternalError: "internal error",
}
func (e alert) String() string {
s, ok := alertText[e]
if ok {
return s
}
return "alert(" + strconv.Itoa(int(e)) + ")"
}
func (e alert) Error() string {
return e.String()
}
// TLS record types.
type recordType uint8
const (
recordTypeHandshake recordType = 22
)
// TLS handshake message types.
const (
typeClientHello uint8 = 1
)
// TLS extension numbers
var (
extensionServerName uint16 = 0
extensionStatusRequest uint16 = 5
extensionSupportedCurves uint16 = 10
extensionSupportedPoints uint16 = 11
extensionSessionTicket uint16 = 35
extensionNextProtoNeg uint16 = 13172 // not IANA assigned
)
// TLS CertificateStatusType (RFC 3546)
const (
statusTypeOCSP uint8 = 1
)
// A Conn represents a secured connection.
// It implements the net.Conn interface.
type TLSConn struct {
*sharedConn
ClientHelloMsg *ClientHelloMsg
}
// TLS parses the ClientHello message on conn and returns
// a new, unread connection with metadata for virtual host muxing
func TLS(conn net.Conn) (tlsConn *TLSConn, err error) {
c, rd := newShared(conn)
tlsConn = &TLSConn{sharedConn: c}
if tlsConn.ClientHelloMsg, err = readClientHello(rd); err != nil {
return
}
return
}
func (c *TLSConn) Host() string {
if c.ClientHelloMsg == nil {
return ""
}
return c.ClientHelloMsg.ServerName
}
func (c *TLSConn) Free() {
c.ClientHelloMsg = nil
}
// A block is a simple data buffer.
type block struct {
data []byte
off int // index for Read
}
// resize resizes block to be n bytes, growing if necessary.
func (b *block) resize(n int) {
if n > cap(b.data) {
b.reserve(n)
}
b.data = b.data[0:n]
}
// reserve makes sure that block contains a capacity of at least n bytes.
func (b *block) reserve(n int) {
if cap(b.data) >= n {
return
}
m := cap(b.data)
if m == 0 {
m = 1024
}
for m < n {
m *= 2
}
data := make([]byte, len(b.data), m)
copy(data, b.data)
b.data = data
}
// readFromUntil reads from r into b until b contains at least n bytes
// or else returns an error.
func (b *block) readFromUntil(r io.Reader, n int) error {
// quick case
if len(b.data) >= n {
return nil
}
// read until have enough.
b.reserve(n)
for {
m, err := r.Read(b.data[len(b.data):cap(b.data)])
b.data = b.data[0 : len(b.data)+m]
if len(b.data) >= n {
break
}
if err != nil {
return err
}
}
return nil
}
func (b *block) Read(p []byte) (n int, err error) {
n = copy(p, b.data[b.off:])
b.off += n
return
}
// newBlock allocates a new block
func newBlock() *block {
return new(block)
}
// splitBlock splits a block after the first n bytes,
// returning a block with those n bytes and a
// block with the remainder. the latter may be nil.
func splitBlock(b *block, n int) (*block, *block) {
if len(b.data) <= n {
return b, nil
}
bb := newBlock()
bb.resize(len(b.data) - n)
copy(bb.data, b.data[n:])
b.data = b.data[0:n]
return b, bb
}
// readHandshake reads the next handshake message from
// the record layer.
func readClientHello(rd io.Reader) (*ClientHelloMsg, error) {
var nextBlock *block // raw input, right off the wire
var hand bytes.Buffer // handshake data waiting to be read
// readRecord reads the next TLS record from the connection
// and updates the record layer state.
readRecord := func() error {
// Caller must be in sync with connection:
// handshake data if handshake not yet completed,
// else application data. (We don't support renegotiation.)
if nextBlock == nil {
nextBlock = newBlock()
}
b := nextBlock
// Read header, payload.
if err := b.readFromUntil(rd, recordHeaderLen); err != nil {
return err
}
typ := recordType(b.data[0])
// No valid TLS record has a type of 0x80, however SSLv2 handshakes
// start with a uint16 length where the MSB is set and the first record
// is always < 256 bytes long. Therefore typ == 0x80 strongly suggests
// an SSLv2 client.
if typ == 0x80 {
return errors.New("tls: unsupported SSLv2 handshake received")
}
vers := uint16(b.data[1])<<8 | uint16(b.data[2])
n := int(b.data[3])<<8 | int(b.data[4])
if n > maxCiphertext {
return alertRecordOverflow
}
// First message, be extra suspicious:
// this might not be a TLS client.
// Bail out before reading a full 'body', if possible.
// The current max version is 3.1.
// If the version is >= 16.0, it's probably not real.
// Similarly, a clientHello message encodes in
// well under a kilobyte. If the length is >= 12 kB,
// it's probably not real.
if (typ != recordTypeHandshake) || vers >= 0x1000 || n >= 0x3000 {
return alertUnexpectedMessage
}
if err := b.readFromUntil(rd, recordHeaderLen+n); err != nil {
return err
}
// Process message.
b, nextBlock = splitBlock(b, recordHeaderLen+n)
b.off = recordHeaderLen
data := b.data[b.off:]
if len(data) > maxPlaintext {
return alertRecordOverflow
}
hand.Write(data)
return nil
}
if err := readRecord(); err != nil {
return nil, err
}
data := hand.Bytes()
n := int(data[1])<<16 | int(data[2])<<8 | int(data[3])
if n > maxHandshake {
return nil, alertInternalError
}
for hand.Len() < 4+n {
if err := readRecord(); err != nil {
return nil, err
}
}
data = hand.Next(4 + n)
if data[0] != typeClientHello {
return nil, alertUnexpectedMessage
}
msg := new(ClientHelloMsg)
if !msg.unmarshal(data) {
return nil, alertUnexpectedMessage
}
return msg, nil
}
type ClientHelloMsg struct {
Raw []byte
Vers uint16
Random []byte
SessionId []byte
CipherSuites []uint16
CompressionMethods []uint8
NextProtoNeg bool
ServerName string
OcspStapling bool
SupportedCurves []uint16
SupportedPoints []uint8
TicketSupported bool
SessionTicket []uint8
}
func (m *ClientHelloMsg) unmarshal(data []byte) bool {
if len(data) < 42 {
return false
}
m.Raw = data
m.Vers = uint16(data[4])<<8 | uint16(data[5])
m.Random = data[6:38]
sessionIdLen := int(data[38])
if sessionIdLen > 32 || len(data) < 39+sessionIdLen {
return false
}
m.SessionId = data[39 : 39+sessionIdLen]
data = data[39+sessionIdLen:]
if len(data) < 2 {
return false
}
// cipherSuiteLen is the number of bytes of cipher suite numbers. Since
// they are uint16s, the number must be even.
cipherSuiteLen := int(data[0])<<8 | int(data[1])
if cipherSuiteLen%2 == 1 || len(data) < 2+cipherSuiteLen {
return false
}
numCipherSuites := cipherSuiteLen / 2
m.CipherSuites = make([]uint16, numCipherSuites)
for i := 0; i < numCipherSuites; i++ {
m.CipherSuites[i] = uint16(data[2+2*i])<<8 | uint16(data[3+2*i])
}
data = data[2+cipherSuiteLen:]
if len(data) < 1 {
return false
}
compressionMethodsLen := int(data[0])
if len(data) < 1+compressionMethodsLen {
return false
}
m.CompressionMethods = data[1 : 1+compressionMethodsLen]
data = data[1+compressionMethodsLen:]
m.NextProtoNeg = false
m.ServerName = ""
m.OcspStapling = false
m.TicketSupported = false
m.SessionTicket = nil
if len(data) == 0 {
// ClientHello is optionally followed by extension data
return true
}
if len(data) < 2 {
return false
}
extensionsLength := int(data[0])<<8 | int(data[1])
data = data[2:]
if extensionsLength != len(data) {
return false
}
for len(data) != 0 {
if len(data) < 4 {
return false
}
extension := uint16(data[0])<<8 | uint16(data[1])
length := int(data[2])<<8 | int(data[3])
data = data[4:]
if len(data) < length {
return false
}
switch extension {
case extensionServerName:
if length < 2 {
return false
}
numNames := int(data[0])<<8 | int(data[1])
d := data[2:]
for i := 0; i < numNames; i++ {
if len(d) < 3 {
return false
}
nameType := d[0]
nameLen := int(d[1])<<8 | int(d[2])
d = d[3:]
if len(d) < nameLen {
return false
}
if nameType == 0 {
m.ServerName = string(d[0:nameLen])
break
}
d = d[nameLen:]
}
case extensionNextProtoNeg:
if length > 0 {
return false
}
m.NextProtoNeg = true
case extensionStatusRequest:
m.OcspStapling = length > 0 && data[0] == statusTypeOCSP
case extensionSupportedCurves:
// http://tools.ietf.org/html/rfc4492#section-5.5.1
if length < 2 {
return false
}
l := int(data[0])<<8 | int(data[1])
if l%2 == 1 || length != l+2 {
return false
}
numCurves := l / 2
m.SupportedCurves = make([]uint16, numCurves)
d := data[2:]
for i := 0; i < numCurves; i++ {
m.SupportedCurves[i] = uint16(d[0])<<8 | uint16(d[1])
d = d[2:]
}
case extensionSupportedPoints:
// http://tools.ietf.org/html/rfc4492#section-5.5.2
if length < 1 {
return false
}
l := int(data[0])
if length != l+1 {
return false
}
m.SupportedPoints = make([]uint8, l)
copy(m.SupportedPoints, data[1:])
case extensionSessionTicket:
// http://tools.ietf.org/html/rfc5077#section-3.2
m.TicketSupported = true
m.SessionTicket = data[:length]
}
data = data[length:]
}
return true
}

View file

@ -1,39 +0,0 @@
package vhost
import (
"crypto/tls"
"net"
"testing"
)
func TestSNI(t *testing.T) {
var testHostname string = "foo.example.com"
l, err := net.Listen("tcp", "127.0.0.1:12345")
if err != nil {
panic(err)
}
defer l.Close()
go func() {
conf := &tls.Config{ServerName: testHostname}
conn, err := tls.Dial("tcp", "127.0.0.1:12345", conf)
if err != nil {
panic(err)
}
conn.Close()
}()
conn, err := l.Accept()
if err != nil {
panic(err)
}
c, err := TLS(conn)
if err != nil {
panic(err)
}
if c.Host() != testHostname {
t.Errorf("Connection Host() is %s, expected %s", c.Host(), testHostname)
}
}

View file

@ -22,6 +22,3 @@ Session.vim
*~
# auto-generated tag files
tags
### Mac
.DS_Store

View file

@ -2,110 +2,50 @@
[[projects]]
digest = "1:d7c5f0bac5337c5098b18d000ee24a9a58d10a6b98824cd1821af74d7219ebea"
name = "github.com/calmh/luhn"
packages = ["."]
pruneopts = ""
revision = "5b2abb343e70180dbf456397c5fd93f14471b08e"
version = "v2.0.0"
[[projects]]
digest = "1:59df7ab87ddca233bb586ff32c6feba12bf787260f6694dc45ceecea9d3e1a7e"
name = "github.com/cenkalti/backoff"
packages = ["."]
pruneopts = ""
revision = "1e4cf3da559842a91afcb6ea6141451e6c30c618"
version = "v2.1.1"
revision = "61153c768f31ee5f130071d08fc82b85208528de"
version = "v1.1.0"
[[projects]]
branch = "master"
digest = "1:50cf302cc52eb618cec25d4daf167ed5593145d43b447b906a9f0d24e271d48b"
name = "github.com/felixge/tcpkeepalive"
packages = ["."]
pruneopts = ""
revision = "5bb0b2dea91e0de550022159b9571aafc72c08ba"
[[projects]]
digest = "1:530233672f656641b365f8efb38ed9fba80e420baff2ce87633813ab3755ed6d"
name = "github.com/golang/mock"
packages = ["gomock"]
pruneopts = ""
revision = "51421b967af1f557f93a59e0057aaf15ca02e29c"
version = "v1.2.0"
[[projects]]
digest = "1:3a86b4526cc15ca4caf041c7f6943b59b3eb3711a9bc832de27b0801b826e019"
name = "github.com/mmatczuk/go-http-tunnel"
packages = [
".",
"id",
"log",
"proto",
"tunnelmock",
]
pruneopts = ""
revision = "75a30abccc8db9d1149845e9985e80cca57758a2"
version = "2.1"
revision = "13f360950a79f5864a972c786a10a50e44b69541"
version = "v1.0.0"
[[projects]]
branch = "master"
digest = "1:1285570ced192ee4703ca44573a4eaf19b5a0895af56f9db2210b171eefd2a41"
name = "golang.org/x/net"
packages = [
"http2",
"http2/hpack",
"idna",
"lex/httplex",
]
pruneopts = ""
packages = ["context","http2","http2/hpack","idna","lex/httplex"]
revision = "c7086645de248775cbf2373cf5ca4d2fa664b8c1"
[[projects]]
branch = "master"
digest = "1:39a1a71adca4b837a25dc6b5fcdd9d175e4597c6d3440f107dfeda31230218e4"
name = "golang.org/x/text"
packages = [
"collate",
"collate/build",
"internal/colltab",
"internal/gen",
"internal/tag",
"internal/triegen",
"internal/ucd",
"language",
"secure/bidirule",
"transform",
"unicode/bidi",
"unicode/cldr",
"unicode/norm",
"unicode/rangetable",
]
pruneopts = ""
packages = ["collate","collate/build","internal/colltab","internal/gen","internal/tag","internal/triegen","internal/ucd","language","secure/bidirule","transform","unicode/bidi","unicode/cldr","unicode/norm","unicode/rangetable"]
revision = "88f656faf3f37f690df1a32515b479415e1a6769"
[[projects]]
digest = "1:cedccf16b71e86db87a24f8d4c70b0a855872eb967cb906a66b95de56aefbd0d"
branch = "v2"
name = "gopkg.in/yaml.v2"
packages = ["."]
pruneopts = ""
revision = "51d6538a90f86fe93ac480b35f37b2be17fef232"
version = "v2.2.2"
revision = "287cf08546ab5e7e37d55a84f7ed3fd1db036de5"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
input-imports = [
"github.com/calmh/luhn",
"github.com/cenkalti/backoff",
"github.com/felixge/tcpkeepalive",
"github.com/golang/mock/gomock",
"github.com/mmatczuk/go-http-tunnel",
"github.com/mmatczuk/go-http-tunnel/id",
"github.com/mmatczuk/go-http-tunnel/log",
"github.com/mmatczuk/go-http-tunnel/proto",
"github.com/mmatczuk/go-http-tunnel/tunnelmock",
"golang.org/x/net/http2",
"gopkg.in/yaml.v2",
]
inputs-digest = "654fea302b8b5a71ce4f36f4097d0c0caa8150e46109cfbdd462adcd054a6768"
solver-name = "gps-cdcl"
solver-version = 1

View file

@ -27,24 +27,24 @@
[[constraint]]
name = "github.com/cenkalti/backoff"
version = "2.1.0"
version = "1.1.0"
[[constraint]]
name = "github.com/golang/mock"
version = "1.2.0"
version = "1.0.0"
[[constraint]]
name = "github.com/google/gops"
version = "0.3.2"
[[constraint]]
branch = "master"
name = "golang.org/x/net"
[[constraint]]
branch = "v2"
name = "gopkg.in/yaml.v2"
version = "2.2.2"
[[constraint]]
branch = "master"
name = "github.com/felixge/tcpkeepalive"
[[constraint]]
branch = "master"
name = "github.com/inconshreveable/go-vhost"

View file

@ -1,10 +1,3 @@
GO_FILES := $(shell \
find . '(' -path '*/.*' -o -path './vendor' ')' -prune \
-o -name '*.go' -print | cut -b3-)
LINT_IGNORE := "/id/\|/tunnelmock/\|/vendor/"
all: clean check test
.PHONY: clean
@ -16,14 +9,11 @@ fmt:
@go fmt ./...
.PHONY: check
check: .check-fmt .check-vet .check-lint .check-ineffassign .check-static .check-misspell .check-vendor
check: .check-fmt .check-vet .check-lint .check-ineffassign .check-mega .check-misspell .check-vendor
.PHONY: .check-fmt
.check-fmt:
$(eval FMT_LOG := $(shell mktemp -t gofmt.XXXXX))
@cat /dev/null > $(FMT_LOG)
@gofmt -e -s -l -d $(GO_FILES) > $(FMT_LOG) || true
@[ ! -s "$(FMT_LOG)" ] || (echo "$@ failed:" | cat - $(FMT_LOG) && false)
@go fmt ./... | tee /dev/stderr | ifne false
.PHONY: .check-vet
.check-vet:
@ -31,11 +21,10 @@ check: .check-fmt .check-vet .check-lint .check-ineffassign .check-static .check
.PHONY: .check-lint
.check-lint:
$(eval LINT_LOG := $(shell mktemp -t golint.XXXXX))
@cat /dev/null > $(LINT_LOG)
@$(foreach pkg, $(GO_FILES), golint $(pkg | grep -v $LINT_IGNORE) >> $(LINT_LOG) || true;)
@[ ! -s "$(LINT_LOG)" ] || (echo "$@ failed:" | cat - $(LINT_LOG) && false)
@golint `go list ./...` \
| grep -v /id/ \
| grep -v /tunnelmock/ \
| tee /dev/stderr | ifne false
.PHONY: .check-ineffassign
.check-ineffassign:
@ -46,8 +35,8 @@ check: .check-fmt .check-vet .check-lint .check-ineffassign .check-static .check
@misspell ./...
.PHONY: .check-mega
.check-static:
@staticcheck -checks ['SA1006','ST1005'] ./...
.check-mega:
@megacheck ./...
.PHONY: .check-vendor
.check-vendor:
@ -67,37 +56,37 @@ get-deps:
get-tools:
@echo "==> Installing tools..."
@go get -u github.com/golang/dep/cmd/dep
@go get -u golang.org/x/lint/golint
@go get -u github.com/golang/lint/golint
@go get -u github.com/golang/mock/gomock
@go get -u github.com/client9/misspell/cmd/misspell
@go get -u github.com/gordonklaus/ineffassign
@go get -u github.com/mitchellh/gox
@go get -u github.com/tcnksm/ghr
@go get -u honnef.co/go/tools/cmd/staticcheck
@go get -u honnef.co/go/tools/cmd/megacheck
OUTPUT_DIR = build
OS = "darwin freebsd linux windows"
ARCH = "386 amd64 arm"
OSARCH = "!darwin/386 !darwin/arm !windows/arm"
GIT_COMMIT = $(shell git describe --always)
.PHONY: release
release: check test clean build package
.PHONY: build
build:
mkdir ${OUTPUT_DIR}
CGO_ENABLED=0 GOARM=5 gox -ldflags "-w -X main.version=$(GIT_COMMIT)" \
-os=${OS} -arch=${ARCH} -osarch=${OSARCH} -output "${OUTPUT_DIR}/pkg/{{.OS}}_{{.Arch}}/{{.Dir}}" \
./cmd/tunnel ./cmd/tunneld
.PHONY: package
package:
mkdir ${OUTPUT_DIR}/dist
cd ${OUTPUT_DIR}/pkg/; for osarch in *; do (cd $$osarch; tar zcvf ../../dist/tunnel_$$osarch.tar.gz ./*); done;
cd ${OUTPUT_DIR}/dist; sha256sum * > ./SHA256SUMS
.PHONY: publish
publish:
ghr -recreate -u mmatczuk -t ${GITHUB_TOKEN} -r go-http-tunnel pre-release ${OUTPUT_DIR}/dist
#OUTPUT_DIR = build
#OS = "darwin freebsd linux windows"
#ARCH = "386 amd64 arm"
#OSARCH = "!darwin/386 !darwin/arm !windows/arm"
#GIT_COMMIT = $(shell git describe --always)
#
#.PHONY: release
#release: check test clean build package
#
#.PHONY: build
#build:
# mkdir ${OUTPUT_DIR}
# CGO_ENABLED=0 GOARM=5 gox -ldflags "-w -X main.version=$(GIT_COMMIT)" \
# -os=${OS} -arch=${ARCH} -osarch=${OSARCH} -output "${OUTPUT_DIR}/pkg/{{.OS}}_{{.Arch}}/{{.Dir}}" \
# ./cmd/tunnel ./cmd/tunneld
#
#.PHONY: package
#package:
# mkdir ${OUTPUT_DIR}/dist
# cd ${OUTPUT_DIR}/pkg/; for osarch in *; do (cd $$osarch; tar zcvf ../../dist/tunnel_$$osarch.tar.gz ./*); done;
# cd ${OUTPUT_DIR}/dist; sha256sum * > ./SHA256SUMS
#
#.PHONY: publish
#publish:
# ghr -recreate -u mmatczuk -t ${GITHUB_TOKEN} -r go-http-tunnel pre-release ${OUTPUT_DIR}/dist

View file

@ -6,7 +6,6 @@ Features:
* HTTP proxy with [basic authentication](https://en.wikipedia.org/wiki/Basic_access_authentication)
* TCP proxy
* [SNI](https://en.wikipedia.org/wiki/Server_Name_Indication) vhost proxy
* Client auto reconnect
* Client management and eviction
* Easy to use CLI
@ -68,65 +67,6 @@ $ tunneld -tlsCrt .tunneld/server.crt -tlsKey .tunneld/server.key
This will run HTTP server on port `80` and HTTPS (HTTP/2) server on port `443`. If you want to use HTTPS it's recommended to get a properly signed certificate to avoid security warnings.
### Run Server as a Service on Ubuntu using Systemd:
* After completing the steps above successfully, create a new file for your service (you can name it whatever you want, just replace the name below with your chosen name).
``` bash
$ vim tunneld.service
```
* Add the following configuration to the file
```
[Unit]
Description=Go-Http-Tunnel Service
After=network.target
After=network-online.target
[Service]
ExecStart=/path/to/your/tunneld -tlsCrt /path/to/your/folder/.tunneld/server.crt -tlsKey /path/to/your/folder/.tunneld/server.key
TimeoutSec=30
Restart=on-failure
RestartSec=30
[Install]
WantedBy=multi-user.target
```
* Save and exit this file.
* Move this new file to /etc/systemd/system/
```bash
$ sudo mv tunneld.service /etc/systemd/system/
```
* Change the file permission to allow it to run.
```bash
$ sudo chmod u+x /etc/systemd/system/tunneld.service
```
* Start the new service and make sure you don't get any errors, and that your client is able to connect.
```bash
$ sudo systemctl start tunneld.service
```
* You can stop the service with:
```bash
$ sudo systemctl stop tunneld.service
```
* Finally, if you want the service to start automatically when the server is rebooted, you need to enable it.
```bash
$ sudo systemctl enable tunneld.service
```
There are many more options for systemd services, and this is by not means an exhaustive configuration file.
## Configuration
The tunnel client `tunnel` requires configuration file, by default it will try reading `tunnel.yml` in your current working directory. If you want to specify other file use `-config` flag.
@ -150,10 +90,6 @@ looks like this
proto: tcp
addr: 192.168.0.5:22
remote_addr: 0.0.0.0:22
tls:
proto: sni
addr: localhost:443
host: tls.my-tunnel-host.com
```
Configuration options:
@ -163,10 +99,10 @@ Configuration options:
* `tls_key`: path to client TLS certificate key, *default:* `client.key` *in the config file directory*
* `root_ca`: path to trusted root certificate authority pool file, if empty any server certificate is accepted
* `tunnels / [name]`
* `proto`: tunnel protocol, `http`, `tcp` or `sni`
* `proto`: tunnel protocol, `http` or `tcp`
* `addr`: forward traffic to this local port number or network address, for `proto=http` this can be full URL i.e. `https://machine/sub/path/?plus=params`, supports URL schemes `http` and `https`
* `auth`: (`proto=http`) (optional) basic authentication credentials to enforce on tunneled requests, format `user:password`
* `host`: (`proto=http`, `proto=sni`) hostname to request (requires reserved name and DNS CNAME)
* `host`: (`proto=http`) hostname to request (requires reserved name and DNS CNAME)
* `remote_addr`: (`proto=tcp`) bind the remote TCP address
* `backoff`
* `interval`: how long client would wait before redialing the server if connection was lost, exponential backoff initial interval, *default:* `500ms`
@ -175,10 +111,9 @@ Configuration options:
* `max_time`: maximal time client would try to reconnect to the server if connection was lost, set `0` to never stop trying, *default:* `15m`
## How it works
Client opens a TLS connection to a server. Server accepts connections from known clients only, client is recognised by it's TLS certificate ID. The server is publicly available and proxies incoming connections to the client. Then the connection is further proxied in the client's network.
A client opens TLS connection to a server. The server accepts connections from known clients only. The client is recognized by its TLS certificate ID. The server is publicly available and proxies incoming connections to the client. Then the connection is further proxied in the client's network.
The tunnel is based HTTP/2 for speed and security. There is a single TCP connection between client and server and all the proxied connections are multiplexed using HTTP/2.
Tunnel is based HTTP/2 for speed and security. There is a single TCP connection between client and server and all the proxied connections are multiplexed using HTTP/2.
## Donation

View file

@ -88,10 +88,6 @@ func loadClientConfigFromFile(file string) (*ClientConfig, error) {
if err := validateTCP(t); err != nil {
return nil, fmt.Errorf("%s %s", name, err)
}
case proto.SNI:
if err := validateSNI(t); err != nil {
return nil, fmt.Errorf("%s %s", name, err)
}
default:
return nil, fmt.Errorf("%s invalid protocol %q", name, t.Protocol)
}
@ -144,27 +140,3 @@ func validateTCP(t *Tunnel) error {
return nil
}
func validateSNI(t *Tunnel) error {
var err error
if t.Host == "" {
return fmt.Errorf("host: missing")
}
if t.Addr == "" {
return fmt.Errorf("addr: missing")
}
if t.Addr, err = normalizeAddress(t.Addr); err != nil {
return fmt.Errorf("addr: %s", err)
}
// unexpected
if t.RemoteAddr != "" {
return fmt.Errorf("remote_addr: unexpected")
}
if t.Auth != "" {
return fmt.Errorf("auth: unexpected")
}
return nil
}

View file

@ -38,10 +38,6 @@ config.yaml:
proto: tcp
addr: 192.168.0.5:22
remote_addr: 0.0.0.0:22
tls:
proto: sni
addr: localhost:443
host: tls.my-tunnel-host.com
Author:
Written by M. Matczuk (mmatczuk@gmail.com)

View file

@ -182,8 +182,6 @@ func proxy(m map[string]*Tunnel, logger log.Logger) tunnel.ProxyFunc {
httpURL[t.Host] = u
case proto.TCP, proto.TCP4, proto.TCP6:
tcpAddr[t.RemoteAddr] = t.Addr
case proto.SNI:
tcpAddr[t.Host] = t.Addr
}
}

View file

@ -16,10 +16,9 @@ options:
const usage2 string = `
Example:
tunneld
tunneld -clients YMBKT3V-ESUTZ2Z-7MRILIJ-T35FHGO-D2DHO7D-FXMGSSR-V4LBSZX-BNDONQ4
tunneld -httpAddr :8080 -httpsAddr ""
tunneld -httpsAddr "" -sniAddr ":443" -rootCA client_root.crt -tlsCrt server.crt -tlsKey server.key
tuneld
tuneld -clients YMBKT3V-ESUTZ2Z-7MRILIJ-T35FHGO-D2DHO7D-FXMGSSR-V4LBSZX-BNDONQ4
tuneld -httpAddr :8080 -httpsAddr ""
Author:
Written by M. Matczuk (mmatczuk@gmail.com)
@ -41,7 +40,6 @@ type options struct {
httpAddr string
httpsAddr string
tunnelAddr string
sniAddr string
tlsCrt string
tlsKey string
rootCA string
@ -54,7 +52,6 @@ func parseArgs() *options {
httpAddr := flag.String("httpAddr", ":80", "Public address for HTTP connections, empty string to disable")
httpsAddr := flag.String("httpsAddr", ":443", "Public address listening for HTTPS connections, emptry string to disable")
tunnelAddr := flag.String("tunnelAddr", ":5223", "Public address listening for tunnel client")
sniAddr := flag.String("sniAddr", "", "Public address listening for TLS SNI connections, empty string to disable")
tlsCrt := flag.String("tlsCrt", "server.crt", "Path to a TLS certificate file")
tlsKey := flag.String("tlsKey", "server.key", "Path to a TLS key file")
rootCA := flag.String("rootCA", "", "Path to the trusted certificate chian used for client certificate authentication, if empty any client certificate is accepted")
@ -67,7 +64,6 @@ func parseArgs() *options {
httpAddr: *httpAddr,
httpsAddr: *httpsAddr,
tunnelAddr: *tunnelAddr,
sniAddr: *sniAddr,
tlsCrt: *tlsCrt,
tlsKey: *tlsKey,
rootCA: *rootCA,

View file

@ -42,7 +42,6 @@ func main() {
// setup server
server, err := tunnel.NewServer(&tunnel.ServerConfig{
Addr: opts.tunnelAddr,
SNIAddr: opts.sniAddr,
AutoSubscribe: autoSubscribe,
TLSConfig: tlsconf,
Logger: logger,
@ -123,14 +122,12 @@ func tlsConfig(opts *options) (*tls.Config, error) {
}
return &tls.Config{
Certificates: []tls.Certificate{cert},
ClientAuth: clientAuth,
ClientCAs: roots,
SessionTicketsDisabled: true,
MinVersion: tls.VersionTLS12,
CipherSuites: []uint16{
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
Certificates: []tls.Certificate{cert},
ClientAuth: clientAuth,
ClientCAs: roots,
SessionTicketsDisabled: true,
MinVersion: tls.VersionTLS12,
CipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
PreferServerCipherSuites: true,
NextProtos: []string{"h2"},
}, nil

View file

@ -32,7 +32,6 @@ const (
TCP4 = "tcp4"
TCP6 = "tcp6"
UNIX = "unix"
SNI = "sni"
)
// ControlMessage is sent from server to client before streaming data. It's

View file

@ -18,7 +18,6 @@ import (
"golang.org/x/net/http2"
"github.com/inconshreveable/go-vhost"
"github.com/mmatczuk/go-http-tunnel/id"
"github.com/mmatczuk/go-http-tunnel/log"
"github.com/mmatczuk/go-http-tunnel/proto"
@ -39,8 +38,6 @@ type ServerConfig struct {
Listener net.Listener
// Logger is optional logger. If nil logging is disabled.
Logger log.Logger
// Addr is TCP address to listen for TLS SNI connections
SNIAddr string
}
// Server is responsible for proxying public connections to the client over a
@ -53,7 +50,6 @@ type Server struct {
connPool *connPool
httpClient *http.Client
logger log.Logger
vhostMuxer *vhost.TLSMuxer
}
// NewServer creates a new Server.
@ -86,54 +82,6 @@ func NewServer(config *ServerConfig) (*Server, error) {
},
}
if config.SNIAddr != "" {
l, err := net.Listen("tcp", config.SNIAddr)
if err != nil {
return nil, err
}
mux, err := vhost.NewTLSMuxer(l, DefaultTimeout)
if err != nil {
return nil, fmt.Errorf("SNI Muxer creation failed: %s", err)
}
s.vhostMuxer = mux
go func() {
for {
conn, err := mux.NextError()
vhostName := ""
tlsConn, ok := conn.(*vhost.TLSConn)
if ok {
vhostName = tlsConn.Host()
}
switch err.(type) {
case vhost.BadRequest:
logger.Log(
"level", 0,
"action", "got a bad request!",
"addr", conn.RemoteAddr(),
)
case vhost.NotFound:
logger.Log(
"level", 0,
"action", "got a connection for an unknown vhost",
"addr", vhostName,
)
case vhost.Closed:
logger.Log(
"level", 0,
"action", "closed conn",
"addr", vhostName,
)
}
if conn != nil {
conn.Close()
}
}
}()
}
return s, nil
}
@ -438,25 +386,6 @@ func (s *Server) addTunnels(tunnels map[string]*proto.Tunnel, identifier id.ID)
"addr", l.Addr(),
)
i.Listeners = append(i.Listeners, l)
case proto.SNI:
if s.vhostMuxer == nil {
err = fmt.Errorf("unable to configure SNI for tunnel %s: %s", name, t.Protocol)
goto rollback
}
var l net.Listener
l, err = s.vhostMuxer.Listen(t.Host)
if err != nil {
goto rollback
}
s.logger.Log(
"level", 2,
"action", "add SNI vhost",
"identifier", identifier,
"host", t.Host,
)
i.Listeners = append(i.Listeners, l)
default:
err = fmt.Errorf("unsupported protocol for tunnel %s: %s", name, t.Protocol)
@ -501,8 +430,7 @@ func (s *Server) listen(l net.Listener, identifier id.ID) {
for {
conn, err := l.Accept()
if err != nil {
if strings.Contains(err.Error(), "use of closed network connection") ||
strings.Contains(err.Error(), "Listener closed") {
if strings.Contains(err.Error(), "use of closed network connection") {
s.logger.Log(
"level", 2,
"action", "listener closed",
@ -524,20 +452,11 @@ func (s *Server) listen(l net.Listener, identifier id.ID) {
msg := &proto.ControlMessage{
Action: proto.ActionProxy,
ForwardedHost: l.Addr().String(),
ForwardedProto: l.Addr().Network(),
}
tlsConn, ok := conn.(*vhost.TLSConn)
if ok {
msg.ForwardedHost = tlsConn.Host()
err = keepAlive(tlsConn.Conn)
} else {
msg.ForwardedHost = l.Addr().String()
err = keepAlive(conn)
}
if err != nil {
if err := keepAlive(conn); err != nil {
s.logger.Log(
"level", 1,
"msg", "TCP keepalive for tunneled connection failed",
@ -684,10 +603,7 @@ func (s *Server) proxyConn(identifier id.ID, conn net.Conn, msg *proto.ControlMe
"src", identifier,
))
select {
case <-done:
case <-time.After(DefaultTimeout):
}
<-done
s.logger.Log(
"level", 2,

View file

@ -57,7 +57,7 @@ func NewMultiTCPProxy(localAddrMap map[string]string, logger log.Logger) *TCPPro
// Proxy is a ProxyFunc.
func (p *TCPProxy) Proxy(w io.Writer, r io.ReadCloser, msg *proto.ControlMessage) {
switch msg.ForwardedProto {
case proto.TCP, proto.TCP4, proto.TCP6, proto.UNIX, proto.SNI:
case proto.TCP, proto.TCP4, proto.TCP6, proto.UNIX:
// ok
default:
p.logger.Log(