Skip to content

Commit

Permalink
Merge pull request mmatczuk#11 from mmatczuk/mmt/cli
Browse files Browse the repository at this point in the history
command line tunnel
  • Loading branch information
mmatczuk authored Feb 15, 2017
2 parents c1ffa1a + 3ebeb9e commit efe21af
Show file tree
Hide file tree
Showing 20 changed files with 1,229 additions and 517 deletions.
10 changes: 5 additions & 5 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
Release 1.0

1. cli: cli and file configuration based on ngrok2 https://ngrok.com/docs#config
1. docs: README update
1. docs: new README

Backlog

1. monitoring: client connection state machine
1. monitoring: ping https://godoc.org/github.com/hashicorp/yamux#Session.Ping
1. monitoring: prometheus.io integration
1. proxy: WebSockets
1. docs: demo
1. proxy: UDP
1. proxy: file system
1. proxy: host_header modifier
1. security: certificate signature checks
1. cli: integrated certificate generation
1. monitoring: prometheus.io integration


Notes for README

Expand Down
96 changes: 70 additions & 26 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,8 @@ import (
)

var (
// DefaultDialTimeout specifies how long client should wait for tunnel
// server or local service connection.
DefaultDialTimeout = 10 * time.Second
// DefaultTimeout specifies general purpose timeout.
DefaultTimeout = 10 * time.Second
)

// ClientConfig is configuration of the Client.
Expand Down Expand Up @@ -51,6 +50,7 @@ type Client struct {
conn net.Conn
connMu sync.Mutex
httpServer *http2.Server
serverErr error
logger log.Logger
}

Expand All @@ -63,6 +63,9 @@ func NewClient(config *ClientConfig) *Client {
if config.TLSClientConfig == nil {
panic("Missing TLSClientConfig")
}
if config.Tunnels == nil || len(config.Tunnels) == 0 {
panic("Missing Tunnels")
}
if config.Proxy == nil {
panic("Missing Proxy")
}
Expand All @@ -85,29 +88,53 @@ func NewClient(config *ClientConfig) *Client {
// error, otherwise it spawns a new goroutine with http/2 server handling
// ControlMessages.
func (c *Client) Start() error {
c.connMu.Lock()
defer c.connMu.Unlock()

c.logger.Log(
"level", 1,
"action", "start",
)

for {
conn, err := c.connect()
if err != nil {
return err
}

c.httpServer.ServeConn(conn, &http2.ServeConnOpts{
Handler: http.HandlerFunc(c.serveHTTP),
})

c.logger.Log(
"level", 1,
"action", "disconnected",
)

c.connMu.Lock()
err = c.serverErr
c.conn = nil
c.serverErr = nil
c.connMu.Unlock()

if err != nil {
return fmt.Errorf("server error: %s", err)
}
}
}

func (c *Client) connect() (net.Conn, error) {
c.connMu.Lock()
defer c.connMu.Unlock()

if c.conn != nil {
return fmt.Errorf("already connected")
return nil, fmt.Errorf("already connected")
}

conn, err := c.dial()
if err != nil {
return fmt.Errorf("failed to connect to server: %s", err)
return nil, fmt.Errorf("failed to connect to server: %s", err)
}
c.conn = conn

go c.httpServer.ServeConn(conn, &http2.ServeConnOpts{
Handler: http.HandlerFunc(c.serveHTTP),
})

return nil
return conn, nil
}

func (c *Client) dial() (net.Conn, error) {
Expand All @@ -129,7 +156,7 @@ func (c *Client) dial() (net.Conn, error) {
conn, err = c.config.DialTLS(network, addr, tlsConfig)
} else {
conn, err = tls.DialWithDialer(
&net.Dialer{Timeout: DefaultDialTimeout},
&net.Dialer{Timeout: DefaultTimeout},
network, addr, tlsConfig,
)
}
Expand Down Expand Up @@ -181,7 +208,11 @@ func (c *Client) dial() (net.Conn, error) {

func (c *Client) serveHTTP(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodConnect {
c.handleHandshake(w, r)
if r.Header.Get(proto.ErrorHeader) != "" {
c.handleHandshakeError(w, r)
} else {
c.handleHandshake(w, r)
}
return
}

Expand Down Expand Up @@ -218,6 +249,21 @@ func (c *Client) serveHTTP(w http.ResponseWriter, r *http.Request) {
)
}

func (c *Client) handleHandshakeError(w http.ResponseWriter, r *http.Request) {
err := fmt.Errorf(r.Header.Get(proto.ErrorHeader))

c.logger.Log(
"level", 1,
"action", "handshake error",
"addr", r.RemoteAddr,
"err", err,
)

c.connMu.Lock()
c.serverErr = err
c.connMu.Unlock()
}

func (c *Client) handleHandshake(w http.ResponseWriter, r *http.Request) {
c.logger.Log(
"level", 1,
Expand All @@ -227,18 +273,16 @@ func (c *Client) handleHandshake(w http.ResponseWriter, r *http.Request) {

w.WriteHeader(http.StatusOK)

if c.config.Tunnels != nil {
b, err := json.Marshal(c.config.Tunnels)
if err != nil {
c.logger.Log(
"level", 0,
"msg", "handshake failed",
"err", err,
)
return
}
w.Write(b)
b, err := json.Marshal(c.config.Tunnels)
if err != nil {
c.logger.Log(
"level", 0,
"msg", "handshake failed",
"err", err,
)
return
}
w.Write(b)
}

// Stop closes the connection between client and server. After stopping client
Expand Down
5 changes: 4 additions & 1 deletion client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"github.com/golang/mock/gomock"
"github.com/mmatczuk/tunnel/mock"
"github.com/mmatczuk/tunnel/proto"
)

func TestClient_Dial(t *testing.T) {
Expand All @@ -23,7 +24,8 @@ func TestClient_Dial(t *testing.T) {
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
Proxy: Proxy(ProxyFuncs{}),
Tunnels: map[string]*proto.Tunnel{"test": {}},
Proxy: Proxy(ProxyFuncs{}),
})

conn, err := c.dial()
Expand Down Expand Up @@ -57,6 +59,7 @@ func TestClient_DialBackoff(t *testing.T) {
TLSClientConfig: &tls.Config{},
DialTLS: d,
Backoff: b,
Tunnels: map[string]*proto.Tunnel{"test": {}},
Proxy: Proxy(ProxyFuncs{}),
})

Expand Down
37 changes: 37 additions & 0 deletions cmd/cmd/log.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package cmd

import (
"io"
"os"
"time"

kitlog "github.com/go-kit/kit/log"
"github.com/mmatczuk/tunnel/log"
)

// NewLogger returns logfmt based logger, printing messages up to log level
// logLevel.
func NewLogger(to string, level int) (log.Logger, error) {
var w io.Writer

switch to {
case "none":
return log.NewNopLogger(), nil
case "stdout":
w = os.Stdout
case "stderr":
w = os.Stderr
default:
f, err := os.Create(to)
if err != nil {
return nil, err
}
w = f
}

var logger kitlog.Logger
logger = kitlog.NewJSONLogger(kitlog.NewSyncWriter(w))
logger = kitlog.NewContext(logger).WithPrefix("time", kitlog.Timestamp(time.Now))
logger = log.NewFilterLogger(logger, level)
return logger, nil
}
Loading

0 comments on commit efe21af

Please sign in to comment.