From e480091388f9acc5ea347065cd2a1b0c6bb46735 Mon Sep 17 00:00:00 2001 From: Darien Raymond Date: Tue, 21 Feb 2017 23:14:07 +0100 Subject: [PATCH] smart error propagation --- app/proxyman/inbound/worker.go | 15 +++++++++-- app/proxyman/outbound/handler.go | 7 ++++- common/errors/errors.go | 46 +++++++++++++++++++++++++------- common/errors/errors_test.go | 26 ++++++++++++++++++ proxy/shadowsocks/client.go | 2 +- proxy/vmess/outbound/outbound.go | 2 +- transport/internet/tcp_hub.go | 13 +++++++-- 7 files changed, 95 insertions(+), 16 deletions(-) create mode 100644 common/errors/errors_test.go diff --git a/app/proxyman/inbound/worker.go b/app/proxyman/inbound/worker.go index 110f0187..33149e76 100644 --- a/app/proxyman/inbound/worker.go +++ b/app/proxyman/inbound/worker.go @@ -11,6 +11,7 @@ import ( "v2ray.com/core/app/dispatcher" "v2ray.com/core/app/log" "v2ray.com/core/common/buf" + "v2ray.com/core/common/errors" v2net "v2ray.com/core/common/net" "v2ray.com/core/proxy" "v2ray.com/core/transport/internet" @@ -53,7 +54,12 @@ func (w *tcpWorker) callback(conn internet.Connection) { ctx = proxy.ContextWithInboundEntryPoint(ctx, v2net.TCPDestination(w.address, w.port)) ctx = proxy.ContextWithSource(ctx, v2net.DestinationFromAddr(conn.RemoteAddr())) if err := w.proxy.Process(ctx, v2net.Network_TCP, conn, w.dispatcher); err != nil { - log.Info("Proxyman|TCPWorker: Connection ends with ", err) + err := errors.Base(err).Message("Proxyman|TCPWorker: Connection ends.") + if errors.IsActionRequired(err) { + log.Warning(err) + } else { + log.Info(err) + } } cancel() conn.Close() @@ -213,7 +219,12 @@ func (w *udpWorker) callback(b *buf.Buffer, source v2net.Destination, originalDe ctx = proxy.ContextWithSource(ctx, source) ctx = proxy.ContextWithInboundEntryPoint(ctx, v2net.UDPDestination(w.address, w.port)) if err := w.proxy.Process(ctx, v2net.Network_UDP, conn, w.dispatcher); err != nil { - log.Info("Proxyman|UDPWorker: Connection ends with ", err) + err = errors.Base(err).Message("Proxyman|UDPWorker: Connection ends.") + if errors.IsActionRequired(err) { + log.Warning(err) + } else { + log.Info(err) + } } w.removeConn(source) cancel() diff --git a/app/proxyman/outbound/handler.go b/app/proxyman/outbound/handler.go index a1a10a90..69b69376 100644 --- a/app/proxyman/outbound/handler.go +++ b/app/proxyman/outbound/handler.go @@ -67,7 +67,12 @@ func (h *Handler) Dispatch(ctx context.Context, outboundRay ray.OutboundRay) { err := h.proxy.Process(ctx, outboundRay, h) // Ensure outbound ray is properly closed. if err != nil && errors.Cause(err) != io.EOF { - log.Info("Proxyman|OutboundHandler: Failed to process outbound traffic: ", err) + err = errors.Base(err).Message("Proxyman|OutboundHandler: Failed to process outbound traffic.") + if errors.IsActionRequired(err) { + log.Warning(err) + } else { + log.Info(err) + } outboundRay.OutboundOutput().CloseError() } else { outboundRay.OutboundOutput().Close() diff --git a/common/errors/errors.go b/common/errors/errors.go index e5ff29b3..37e05c3c 100644 --- a/common/errors/errors.go +++ b/common/errors/errors.go @@ -12,10 +12,15 @@ type hasInnerError interface { Inner() error } +type actionRequired interface { + ActionRequired() bool +} + // Error is an error object with underlying error. type Error struct { - message string - inner error + message string + inner error + actionRequired bool } // Error implements error.Error(). @@ -31,6 +36,10 @@ func (v *Error) Inner() error { return v.inner } +func (v *Error) ActionRequired() bool { + return v.actionRequired +} + // New returns a new error object with message formed from given arguments. func New(msg ...interface{}) error { return &Error{ @@ -56,19 +65,36 @@ func Cause(err error) error { } for { inner, ok := err.(hasInnerError) - if !ok { + if !ok || inner.Inner() == nil { break } - if inner.Inner() == nil { + err = inner.Inner() + } + return err +} + +func IsActionRequired(err error) bool { + for err != nil { + if ar, ok := err.(actionRequired); ok && ar.ActionRequired() { + return true + } + inner, ok := err.(hasInnerError) + if !ok || inner.Inner() == nil { break } err = inner.Inner() } - return err + return false } type ErrorBuilder struct { error + actionRequired bool +} + +func (v ErrorBuilder) RequireUserAction() ErrorBuilder { + v.actionRequired = true + return v } // Message returns an error object with given message and base error. @@ -78,8 +104,9 @@ func (v ErrorBuilder) Message(msg ...interface{}) error { } return &Error{ - message: serial.Concat(msg...) + " > " + v.error.Error(), - inner: v.error, + message: serial.Concat(msg...) + " > " + v.error.Error(), + inner: v.error, + actionRequired: v.actionRequired, } } @@ -89,7 +116,8 @@ func (v ErrorBuilder) Format(format string, values ...interface{}) error { return nil } return &Error{ - message: fmt.Sprintf(format, values...) + " > " + v.error.Error(), - inner: v.error, + message: fmt.Sprintf(format, values...) + " > " + v.error.Error(), + inner: v.error, + actionRequired: v.actionRequired, } } diff --git a/common/errors/errors_test.go b/common/errors/errors_test.go new file mode 100644 index 00000000..302f5d1e --- /dev/null +++ b/common/errors/errors_test.go @@ -0,0 +1,26 @@ +package errors_test + +import ( + "io" + "testing" + + . "v2ray.com/core/common/errors" + "v2ray.com/core/testing/assert" +) + +func TestActionRequired(t *testing.T) { + assert := assert.On(t) + + err := New("TestError") + assert.Bool(IsActionRequired(err)).IsFalse() + + err = Base(io.EOF).Message("TestError2") + assert.Bool(IsActionRequired(err)).IsFalse() + + err = Base(io.EOF).RequireUserAction().Message("TestError3") + assert.Bool(IsActionRequired(err)).IsTrue() + + err = Base(io.EOF).RequireUserAction().Message("TestError4") + err = Base(err).Message("TestError5") + assert.Bool(IsActionRequired(err)).IsTrue() +} diff --git a/proxy/shadowsocks/client.go b/proxy/shadowsocks/client.go index d936f78e..24407050 100644 --- a/proxy/shadowsocks/client.go +++ b/proxy/shadowsocks/client.go @@ -60,7 +60,7 @@ func (v *Client) Process(ctx context.Context, outboundRay ray.OutboundRay, diale return nil }) if err != nil { - return errors.Base(err).Message("Shadowsocks|Client: Failed to find an available destination.") + return errors.Base(err).RequireUserAction().Message("Shadowsocks|Client: Failed to find an available destination.") } log.Info("Shadowsocks|Client: Tunneling request to ", destination, " via ", server.Destination()) diff --git a/proxy/vmess/outbound/outbound.go b/proxy/vmess/outbound/outbound.go index c7d24131..31ba99eb 100644 --- a/proxy/vmess/outbound/outbound.go +++ b/proxy/vmess/outbound/outbound.go @@ -61,7 +61,7 @@ func (v *Handler) Process(ctx context.Context, outboundRay ray.OutboundRay, dial return nil }) if err != nil { - return errors.Base(err).Message("VMess|Outbound: Failed to find an available destination.") + return errors.Base(err).RequireUserAction().Message("VMess|Outbound: Failed to find an available destination.") } defer conn.Close() diff --git a/transport/internet/tcp_hub.go b/transport/internet/tcp_hub.go index d20cd5d1..775b494a 100644 --- a/transport/internet/tcp_hub.go +++ b/transport/internet/tcp_hub.go @@ -3,6 +3,7 @@ package internet import ( "net" + "v2ray.com/core/app/log" "v2ray.com/core/common/errors" v2net "v2ray.com/core/common/net" "v2ray.com/core/common/retry" @@ -85,13 +86,21 @@ func (v *TCPHub) start() { default: conn, err := v.listener.Accept() if err != nil { - return errors.Base(err).Message("Internet|Listener: Failed to accept new TCP connection.") + return errors.Base(err).RequireUserAction().Message("Internet|Listener: Failed to accept new TCP connection.") } newConn = conn return nil } }) - if err == nil && newConn != nil { + if err != nil { + if errors.IsActionRequired(err) { + log.Warning(err) + } else { + log.Info(err) + } + continue + } + if newConn != nil { go v.connCallback(newConn) } }