diff --git a/cmd/server/server.go b/cmd/server/server.go index 73b4e41..c56f72b 100644 --- a/cmd/server/server.go +++ b/cmd/server/server.go @@ -3,16 +3,17 @@ package server import ( "fmt" "github.com/gin-gonic/gin" + "github.com/kfsoftware/getout/proxy" "github.com/kfsoftware/go-vhost" - "io" + "github.com/pkg/errors" "net" "strings" "sync" "time" "github.com/hashicorp/yamux" + "github.com/kfsoftware/getout/log" "github.com/kfsoftware/getout/pkg/messages" - "github.com/rs/zerolog/log" "github.com/spf13/cobra" ) @@ -29,7 +30,7 @@ func (c *serverCmd) validate() error { func (c *serverCmd) returnResponse(initialConn net.Conn, status messages.TunnelStatus) error { tunnelResponse := &messages.TunnelResponse{Status: status} - log.Debug().Msgf("Returning response to client: %s", status) + log.Debugf("Returning response to client: %s", status) err := messages.WriteMsg(initialConn, tunnelResponse) if err != nil { return err @@ -58,7 +59,7 @@ func (r *SessionRegistry) store(sni string, s *Session) error { defer r.Unlock() _, ok := r.sessions[sni] if ok { - log.Warn().Msgf("Session already exists for %s", sni) + log.Warnf("Session already exists for %s", sni) return fmt.Errorf("session already exists for %s", sni) } r.sessions[sni] = s @@ -74,7 +75,14 @@ func (r *SessionRegistry) delete(sni string) { delete(r.sessions, sni) } } - +func (r *SessionRegistry) cleanupDeadSessions() { + for sni, session := range r.sessions { + if session.Sess.IsClosed() { + log.Debugf("Session closed for %s", sni) + r.delete(sni) + } + } +} func (r *SessionRegistry) find(sni string) *Session { r.RLock() defer r.RUnlock() @@ -85,20 +93,20 @@ func (r *SessionRegistry) find(sni string) *Session { return s } func (c *serverCmd) handleTunnelRequest(mux *vhost.TLSMuxer, conn net.Conn) error { - log.Trace().Msgf("client %s connected", conn.RemoteAddr().String()) + log.Debugf("client %s connected", conn.RemoteAddr().String()) config := yamux.DefaultConfig() // setup session sess, err := yamux.Server(conn, config) if err != nil { - log.Err(err).Msg("failed to create yamux session") + log.Errorf("failed to create yamux session") return err } // accept connection initialConn, err := sess.Accept() if err != nil { defer sess.Close() - log.Trace().Msgf("client %s disconnected", conn.RemoteAddr().String()) - log.Error().Msgf("multiplex conn accept failed %v", err) + log.Debugf("client %s disconnected", conn.RemoteAddr().String()) + log.Errorf("multiplex conn accept failed %v", err) return err } defer initialConn.Close() @@ -112,17 +120,17 @@ func (c *serverCmd) handleTunnelRequest(mux *vhost.TLSMuxer, conn net.Conn) erro if s != nil { defer conn.Close() defer sess.Close() - log.Trace().Msgf("Session already exists in the registry for %s", sni) + log.Debugf("Session already exists in the registry for %s", sni) err = c.returnResponse(initialConn, messages.TunnelStatus_ALREADY_EXISTS) if err != nil { - log.Warn().Msgf("Failed to send response: %v", err) + log.Warnf("Failed to send response: %v", err) } return err } muxListener, err := c.startMuxListener(mux, initialConn, sni) if err != nil { if msg != nil { - log.Err(err).Msgf("failed to listen on %s", msg.GetTls().GetSni()) + log.Errorf("failed to listen on %s", msg.GetTls().GetSni()) } if muxListener != nil { muxListener.Close() @@ -132,7 +140,7 @@ func (c *serverCmd) handleTunnelRequest(mux *vhost.TLSMuxer, conn net.Conn) erro if strings.Contains(strings.ToLower(err.Error()), "already bound") { err = c.returnResponse(initialConn, messages.TunnelStatus_ALREADY_EXISTS) if err != nil { - log.Warn().Msgf("Failed to send response: %v", err) + log.Warnf("Failed to send response: %v", err) } return err } @@ -146,7 +154,7 @@ func (c *serverCmd) handleTunnelRequest(mux *vhost.TLSMuxer, conn net.Conn) erro } err = c.sessionRegistry.store(sni, session) if err != nil { - log.Err(err).Msgf("failed to store session for %s", sni) + log.Errorf("failed to store session for %s", sni) session.cleanup() return err } @@ -154,72 +162,26 @@ func (c *serverCmd) handleTunnelRequest(mux *vhost.TLSMuxer, conn net.Conn) erro if err != nil { err = c.returnResponse(initialConn, messages.TunnelStatus_ERROR) if err != nil { - log.Warn().Msgf("Failed to send response: %v", err) + log.Warnf("Failed to send response: %v", err) } c.sessionRegistry.delete(sni) return err } - go func(ml net.Listener) { - defer func() { - c.sessionRegistry.delete(sni) - if r := recover(); r != nil { - log.Info().Msgf("Recovered in request dispatcher %v", r) - } - }() - for { - conn, err := ml.Accept() - if err != nil { - log.Err(err).Msg("Error accepting connection") - c.sessionRegistry.delete(sni) - if strings.Contains(strings.ToLower(err.Error()), "listener closed") { - log.Info().Msg("listener closed") - return - } - continue - } - destConn, err := sess.Open() - if err != nil { - _ = conn.Close() - log.Warn().Msgf("Connection closed") - continue - } - transfer := func(side string, dst, src net.Conn) { - log.Trace().Msgf("proxing %s -> %s", src.RemoteAddr(), dst.RemoteAddr()) - tStart := time.Now() - - n, err := io.Copy(dst, src) - if err != nil { - log.Error().Msgf("%s: copy error: %s", side, err) - } - - if err := src.Close(); err != nil { - log.Trace().Msgf("%s: close error: %s", side, err) - } - - // not for yamux streams, but for client to local server connections - if d, ok := dst.(*net.TCPConn); ok { - if err := d.CloseWrite(); err != nil { - log.Trace().Msgf("%s: closeWrite error: %s", side, err) - } - if err := d.CloseRead(); err != nil { - log.Trace().Msgf("%s: closeRead error: %s", side, err) - } - } - log.Trace().Msgf("done proxing %s -> %s: %d bytes in %s", src.RemoteAddr(), dst.RemoteAddr(), n, time.Since(tStart)) - } - go transfer("remote to local", conn, destConn) - go transfer("local to remote", destConn, conn) + defer func() { + c.sessionRegistry.delete(sni) + if r := recover(); r != nil { + log.Infof("Recovered in request dispatcher %v", r) } - }(muxListener) + }() go func() { - log.Debug().Msgf("Checking if session %s is alive", sni) + log.Debugf("Checking if session %s is alive", sni) defer func() { c.sessionRegistry.delete(sni) }() for { _, err = sess.Ping() if err != nil { - log.Info().Msgf("Session %s inactive, removing it: %v", sni, err) + log.Infof("Session %s inactive, removing it: %v", sni, err) c.sessionRegistry.delete(sni) break } @@ -227,22 +189,46 @@ func (c *serverCmd) handleTunnelRequest(mux *vhost.TLSMuxer, conn net.Conn) erro continue } }() + for { + conn, err := muxListener.Accept() + if err != nil { + log.Errorf("Error accepting connection", err) + c.sessionRegistry.delete(sni) + if strings.Contains(strings.ToLower(err.Error()), "listener closed") { + log.Info("listener closed") + return errors.New("listener closed") + } + continue + } + destConn, err := sess.Open() + if err != nil { + _ = conn.Close() + log.Warnf("Connection closed") + continue + } + p := proxy.New( + conn, + destConn, + ) + p.Start() + } + return nil } func (c *serverCmd) startMuxListener(mux *vhost.TLSMuxer, initialConn net.Conn, sni string) (net.Listener, error) { - log.Debug().Msgf("Received request for %v", sni) + log.Debugf("Received request for %v", sni) muxListener, err := mux.Listen(sni) if err != nil { - log.Err(err).Msgf("failed to listen on %s", sni) + log.Errorf("failed to listen on %s", sni) if strings.Contains(strings.ToLower(err.Error()), "already bound") { err = mux.Del(sni) if err != nil { - log.Err(err).Msgf("failed to delete mux %s", sni) + log.Errorf("failed to delete mux %s", sni) } respErr := c.returnResponse(initialConn, messages.TunnelStatus_ALREADY_EXISTS) if respErr != nil { - log.Warn().Msgf("Failed to send response: %v", err) + log.Warnf("Failed to send response: %v", err) return muxListener, respErr } return muxListener, err @@ -253,7 +239,7 @@ func (c *serverCmd) startMuxListener(mux *vhost.TLSMuxer, initialConn net.Conn, if err != nil { err = c.returnResponse(initialConn, messages.TunnelStatus_ERROR) if err != nil { - log.Warn().Msgf("Failed to send response: %v", err) + log.Warnf("Failed to send response: %v", err) } c.sessionRegistry.delete(sni) return nil, err @@ -270,37 +256,39 @@ func (c *serverCmd) run() error { // start multiplexing on it mux, err := vhost.NewTLSMuxer(l, muxTimeout) if err != nil { - log.Err(err).Msg("failed to create muxer") + log.Errorf("failed to create muxer") } - log.Debug().Msgf("Starting server %s", c.addr) + log.Debugf("Starting server %s", c.addr) muxServer, err := net.Listen("tcp", c.tunnelAddr) if err != nil { panic(fmt.Errorf("error listening on %s: %w", c.tunnelAddr, err)) } defer func(muxServer net.Listener) { - log.Warn().Msgf("Closing mux server %s", muxServer.Addr()) + log.Warnf("Closing mux server %s", muxServer.Addr()) _ = muxServer.Close() }(muxServer) go func() { - log.Info().Msgf("tunnel listening on %s", c.tunnelAddr) + log.Infof("tunnel listening on %s", c.tunnelAddr) defer func() { if r := recover(); r != nil { - log.Info().Msgf("tunnel listener closed %v", r) + log.Infof("tunnel listener closed %v", r) } }() for { conn, err := muxServer.Accept() if err != nil { - log.Warn().Msgf("Connection closed") + log.Warnf("Connection closed") return } - log.Trace().Msgf("Accepted connection from %s", conn.RemoteAddr()) - err = c.handleTunnelRequest(mux, conn) - if err != nil { - log.Warn().Msgf("Failed to handle tunnel request: %v", err) - } + log.Debugf("Accepted connection from %s", conn.RemoteAddr()) + go func() { + err = c.handleTunnelRequest(mux, conn) + if err != nil { + log.Warnf("Failed to handle tunnel request: %v", err) + } + }() } - log.Info().Msg("tunnel server closed") + log.Info("tunnel server closed") }() go func() { r := gin.Default() @@ -311,10 +299,10 @@ func (c *serverCmd) run() error { c.sessionRegistry.delete(c1.Param("sni")) c1.JSON(200, c.sessionRegistry.sessions) }) - log.Info().Msgf("admin server listening on %s", c.adminAddr) + log.Infof("admin server listening on %s", c.adminAddr) err := r.Run(c.adminAddr) if err != nil { - log.Error().Msgf("failed to listen on address: %s %v", c.adminAddr, err) + log.Errorf("failed to listen on address: %s %v", c.adminAddr, err) } }() go func() { @@ -322,13 +310,13 @@ func (c *serverCmd) run() error { conn, err := mux.NextError() switch err.(type) { case vhost.BadRequest: - log.Trace().Msgf("got a bad request!") + log.Debugf("got a bad request!") case vhost.NotFound: - log.Trace().Msgf("got a connection for an unknown vhost") + log.Debugf("got a connection for an unknown vhost") case vhost.Closed: - log.Trace().Msgf("closed conn: %s", err) + log.Debugf("closed conn: %s", err) default: - log.Trace().Msgf("Server error") + log.Debugf("Server error") } if conn != nil { diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..81bce82 --- /dev/null +++ b/config/config.go @@ -0,0 +1,60 @@ +package config + +import ( + "time" + + "github.com/spf13/viper" +) + +// Provider defines a set of read-only methods for accessing the application +// configuration params as defined in one of the config files. +type Provider interface { + ConfigFileUsed() string + Get(key string) interface{} + GetBool(key string) bool + GetDuration(key string) time.Duration + GetFloat64(key string) float64 + GetInt(key string) int + GetInt64(key string) int64 + GetSizeInBytes(key string) uint + GetString(key string) string + GetStringMap(key string) map[string]interface{} + GetStringMapString(key string) map[string]string + GetStringMapStringSlice(key string) map[string][]string + GetStringSlice(key string) []string + GetTime(key string) time.Time + InConfig(key string) bool + IsSet(key string) bool +} + +var defaultConfig *viper.Viper + +// Config returns a default config providers +func Config() Provider { + return defaultConfig +} + +// LoadConfigProvider returns a configured viper instance +func LoadConfigProvider(appName string, configFile string) Provider { + return readViperConfig(appName, configFile) +} + +func init() { + defaultConfig = readViperConfig("FABRIC-CHAINCODE", "") +} + +func readViperConfig(appName string, configFile string) *viper.Viper { + v := viper.New() + v.SetEnvPrefix(appName) + v.AutomaticEnv() + if configFile != "" { + v.AddConfigPath(configFile) + } + + // global defaults + + v.SetDefault("json_logs", false) + v.SetDefault("loglevel", "info") + + return v +} diff --git a/go.mod b/go.mod index 3a0b2d7..33812ee 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,8 @@ require ( github.com/kfsoftware/go-vhost v0.0.1 github.com/pkg/errors v0.9.1 github.com/rs/zerolog v1.26.1 + github.com/sirupsen/logrus v1.2.0 github.com/spf13/cobra v1.1.3 + github.com/spf13/viper v1.7.0 google.golang.org/protobuf v1.26.0 ) diff --git a/go.sum b/go.sum index 50e1f30..209eb9f 100644 --- a/go.sum +++ b/go.sum @@ -38,6 +38,7 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= @@ -104,6 +105,7 @@ github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= @@ -128,6 +130,7 @@ github.com/kfsoftware/go-vhost v0.0.1 h1:i8fOOUgs++kBS//71DdcFsIjiIYWO6FW5Y5QIYd github.com/kfsoftware/go-vhost v0.0.1/go.mod h1:4YXBbTHlkGUxJw3EmGjZjkdUo7yblUelN26ChdHGCIM= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= @@ -137,6 +140,7 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= @@ -151,6 +155,7 @@ github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eI github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= @@ -161,6 +166,7 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -187,19 +193,24 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M= github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= +github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -207,6 +218,7 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= @@ -292,11 +304,13 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e h1:WUoyKPm6nCo1BnNUvPGnFG3T5DUVem42yDJZZ4CNxMA= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -353,6 +367,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= diff --git a/log/log.go b/log/log.go new file mode 100644 index 0000000..bbc5666 --- /dev/null +++ b/log/log.go @@ -0,0 +1,213 @@ +package log + +import ( + "github.com/kfsoftware/getout/config" + "os" + + "github.com/sirupsen/logrus" +) + +// Logger defines a set of methods for writing application logs. Derived from and +// inspired by logrus.Entry. +type Logger interface { + Debug(args ...interface{}) + Debugf(format string, args ...interface{}) + Debugln(args ...interface{}) + Error(args ...interface{}) + Errorf(format string, args ...interface{}) + Errorln(args ...interface{}) + Fatal(args ...interface{}) + Fatalf(format string, args ...interface{}) + Fatalln(args ...interface{}) + Info(args ...interface{}) + Infof(format string, args ...interface{}) + Infoln(args ...interface{}) + Panic(args ...interface{}) + Panicf(format string, args ...interface{}) + Panicln(args ...interface{}) + Print(args ...interface{}) + Printf(format string, args ...interface{}) + Println(args ...interface{}) + Warn(args ...interface{}) + Warnf(format string, args ...interface{}) + Warning(args ...interface{}) + Warningf(format string, args ...interface{}) + Warningln(args ...interface{}) + Warnln(args ...interface{}) +} + +var defaultLogger *logrus.Logger + +func init() { + defaultLogger = newLogrusLogger(config.Config()) +} + +// NewLogger returns a configured logrus instance +func NewLogger(cfg config.Provider) *logrus.Logger { + return newLogrusLogger(cfg) +} + +func newLogrusLogger(cfg config.Provider) *logrus.Logger { + + l := logrus.New() + + if cfg.GetBool("json_logs") { + l.Formatter = new(logrus.JSONFormatter) + } + l.Out = os.Stderr + + switch cfg.GetString("loglevel") { + case "debug": + l.Level = logrus.DebugLevel + case "warning": + l.Level = logrus.WarnLevel + case "info": + l.Level = logrus.InfoLevel + default: + l.Level = logrus.DebugLevel + } + + return l +} + +// Fields is a map string interface to define fields in the structured log +type Fields map[string]interface{} + +// With allow us to define fields in out structured logs +func (f Fields) With(k string, v interface{}) Fields { + f[k] = v + return f +} + +// WithFields allow us to define fields in out structured logs +func (f Fields) WithFields(f2 Fields) Fields { + for k, v := range f2 { + f[k] = v + } + return f +} + +// WithFields allow us to define fields in out structured logs +func WithFields(fields Fields) Logger { + return defaultLogger.WithFields(logrus.Fields(fields)) +} + +// Debug package-level convenience method. +func Debug(args ...interface{}) { + defaultLogger.Debug(args...) +} + +// Debugf package-level convenience method. +func Debugf(format string, args ...interface{}) { + defaultLogger.Debugf(format, args...) +} + +// Debugln package-level convenience method. +func Debugln(args ...interface{}) { + defaultLogger.Debugln(args...) +} + +// Error package-level convenience method. +func Error(args ...interface{}) { + defaultLogger.Error(args...) +} + +// Errorf package-level convenience method. +func Errorf(format string, args ...interface{}) { + defaultLogger.Errorf(format, args...) +} + +// Errorln package-level convenience method. +func Errorln(args ...interface{}) { + defaultLogger.Errorln(args...) +} + +// Fatal package-level convenience method. +func Fatal(args ...interface{}) { + defaultLogger.Fatal(args...) +} + +// Fatalf package-level convenience method. +func Fatalf(format string, args ...interface{}) { + defaultLogger.Fatalf(format, args...) +} + +// Fatalln package-level convenience method. +func Fatalln(args ...interface{}) { + defaultLogger.Fatalln(args...) +} + +// Info package-level convenience method. +func Info(args ...interface{}) { + defaultLogger.Info(args...) +} + +// Infof package-level convenience method. +func Infof(format string, args ...interface{}) { + defaultLogger.Infof(format, args...) +} + +// Infoln package-level convenience method. +func Infoln(args ...interface{}) { + defaultLogger.Infoln(args...) +} + +// Panic package-level convenience method. +func Panic(args ...interface{}) { + defaultLogger.Panic(args...) +} + +// Panicf package-level convenience method. +func Panicf(format string, args ...interface{}) { + defaultLogger.Panicf(format, args...) +} + +// Panicln package-level convenience method. +func Panicln(args ...interface{}) { + defaultLogger.Panicln(args...) +} + +// Print package-level convenience method. +func Print(args ...interface{}) { + defaultLogger.Print(args...) +} + +// Printf package-level convenience method. +func Printf(format string, args ...interface{}) { + defaultLogger.Printf(format, args...) +} + +// Println package-level convenience method. +func Println(args ...interface{}) { + defaultLogger.Println(args...) +} + +// Warn package-level convenience method. +func Warn(args ...interface{}) { + defaultLogger.Warn(args...) +} + +// Warnf package-level convenience method. +func Warnf(format string, args ...interface{}) { + defaultLogger.Warnf(format, args...) +} + +// Warning package-level convenience method. +func Warning(args ...interface{}) { + defaultLogger.Warning(args...) +} + +// Warningf package-level convenience method. +func Warningf(format string, args ...interface{}) { + defaultLogger.Warningf(format, args...) +} + +// Warningln package-level convenience method. +func Warningln(args ...interface{}) { + defaultLogger.Warningln(args...) +} + +// Warnln package-level convenience method. +func Warnln(args ...interface{}) { + defaultLogger.Warnln(args...) +} diff --git a/pkg/tunnel/tunnel.go b/pkg/tunnel/tunnel.go index 761131f..2312521 100644 --- a/pkg/tunnel/tunnel.go +++ b/pkg/tunnel/tunnel.go @@ -1,12 +1,11 @@ package tunnel import ( - "fmt" "github.com/hashicorp/yamux" "github.com/kfsoftware/getout/pkg/messages" + "github.com/kfsoftware/getout/proxy" "github.com/pkg/errors" "github.com/rs/zerolog/log" - "io" "net" "time" ) @@ -97,41 +96,10 @@ func (c tunnelClient) startSNIProxy(session *yamux.Session, remoteAddress string } continue } - log.Debug().Msgf("client %s connected", conn.RemoteAddr().String()) - copyConn := func(writer, reader net.Conn) { - defer writer.Close() - defer reader.Close() - _, err := io.Copy(writer, reader) - if err != nil { - fmt.Printf("io.Copy error: %s", err) - } - log.Info().Msgf("Connection finished") - } - _ = copyConn - - transfer := func(side string, dst, src net.Conn) { - log.Debug().Msgf("proxing %s -> %s", src.RemoteAddr(), dst.RemoteAddr()) - - n, err := io.Copy(dst, src) - if err != nil { - log.Error().Msgf("%s: copy error: %s", side, err) - } - - if err := src.Close(); err != nil { - log.Debug().Msgf("%s: close error: %s", side, err) - } - - // not for yamux streams, but for client to local server connections - if d, ok := dst.(*net.TCPConn); ok { - if err := d.CloseWrite(); err != nil { - log.Debug().Msgf("%s: closeWrite error: %s", side, err) - } - - } - log.Debug().Msgf("done proxing %s -> %s: %d bytes", src.RemoteAddr(), dst.RemoteAddr(), n) - } - - go transfer("remote to local", conn, destConn) - go transfer("local to remote", destConn, conn) + p := proxy.New( + conn, + destConn, + ) + p.Start() } } diff --git a/proxy/proxy.go b/proxy/proxy.go new file mode 100644 index 0000000..04870aa --- /dev/null +++ b/proxy/proxy.go @@ -0,0 +1,147 @@ +package proxy + +import ( + "github.com/kfsoftware/getout/log" + + "io" + "net" +) + +// Proxy - Manages a Proxy connection, piping data between local and remote. +type Proxy struct { + sentBytes uint64 + receivedBytes uint64 + //laddr, raddr *net.TCPAddr + lconn, rconn io.ReadWriteCloser + erred bool + errsig chan bool + tlsUnwrapp bool + tlsAddress string + + Matcher func([]byte) + Replacer func([]byte) []byte + + // Settings + Nagles bool + OutputHex bool +} + +// New - Create a new Proxy instance. Takes over local connection passed in, +// and closes it when finished. +func New(lconn net.Conn, rconn net.Conn) *Proxy { + return &Proxy{ + lconn: lconn, + //laddr: laddr, + //raddr: raddr, + rconn: rconn, + erred: false, + errsig: make(chan bool), + } +} + +// NewTLSUnwrapped - Create a new Proxy instance with a remote TLS server for +// which we want to unwrap the TLS to be able to connect without encryption +// locally +func NewTLSUnwrapped(lconn, rconn *net.TCPConn, laddr, raddr *net.TCPAddr, addr string) *Proxy { + p := New(lconn, rconn) + p.tlsUnwrapp = true + p.tlsAddress = addr + return p +} + +type setNoDelayer interface { + SetNoDelay(bool) error +} + +// Start - open connection to remote and start proxying data. +func (p *Proxy) Start() { + defer p.lconn.Close() + defer p.rconn.Close() + + //nagles? + if p.Nagles { + if conn, ok := p.lconn.(setNoDelayer); ok { + conn.SetNoDelay(true) + } + if conn, ok := p.rconn.(setNoDelayer); ok { + conn.SetNoDelay(true) + } + } + + //display both ends + //log.Infof("Opened %s >>> %s", p.lconn.String(), p.raddr.String()) + + //bidirectional copy + go p.pipe(p.lconn, p.rconn) + go p.pipe(p.rconn, p.lconn) + + //wait for close... + <-p.errsig + log.Infof("Closed (%d bytes sent, %d bytes recieved)", p.sentBytes, p.receivedBytes) +} + +func (p *Proxy) err(s string, err error) { + if p.erred { + return + } + if err != io.EOF { + log.Warn(s, err) + } + p.errsig <- true + p.erred = true +} + +func (p *Proxy) pipe(src, dst io.ReadWriter) { + islocal := src == p.lconn + + var dataDirection string + if islocal { + dataDirection = ">>> %d bytes sent%s" + } else { + dataDirection = "<<< %d bytes recieved%s" + } + + var byteFormat string + if p.OutputHex { + byteFormat = "%x" + } else { + byteFormat = "%s" + } + + //directional copy (64k buffer) + buff := make([]byte, 0xffff) + for { + n, err := src.Read(buff) + if err != nil { + p.err("Read failed '%s'\n", err) + return + } + b := buff[:n] + + //execute match + if p.Matcher != nil { + p.Matcher(b) + } + + //execute replace + if p.Replacer != nil { + b = p.Replacer(b) + } + + //show output + log.Debug(dataDirection, n, "") + log.Debug(byteFormat, b) + + //write out result + n, err = dst.Write(b) + if err != nil { + p.err("Write failed '%s'\n", err) + return + } + if islocal { + p.sentBytes += uint64(n) + } else { + p.receivedBytes += uint64(n) + } + } +}