From a743862f2304672ae14e07c14c3400a5a76fc889 Mon Sep 17 00:00:00 2001 From: ginuerzh Date: Mon, 18 Sep 2023 21:13:11 +0800 Subject: [PATCH] add recorder for serial handler --- config/config.go | 17 +++++++++-- config/parsing/parse.go | 12 ++++++++ config/parsing/service.go | 6 ++++ go.mod | 2 +- go.sum | 4 +-- handler/serial/conn.go | 47 ++++++++++++++++++++--------- handler/serial/handler.go | 12 ++++---- recorder/http.go | 62 +++++++++++++++++++++++++++++++++++++++ recorder/tcp.go | 52 ++++++++++++++++++++++++++++++++ 9 files changed, 188 insertions(+), 26 deletions(-) create mode 100644 recorder/http.go create mode 100644 recorder/tcp.go diff --git a/config/config.go b/config/config.go index 9290a5b..d7d2f86 100644 --- a/config/config.go +++ b/config/config.go @@ -229,6 +229,8 @@ type IngressConfig struct { type RecorderConfig struct { Name string `json:"name"` File *FileRecorder `yaml:",omitempty" json:"file,omitempty"` + TCP *TCPRecorder `yaml:"tcp,omitempty" json:"tcp,omitempty"` + HTTP *HTTPRecorder `yaml:"http,omitempty" json:"http,omitempty"` Redis *RedisRecorder `yaml:",omitempty" json:"redis,omitempty"` Plugin *PluginConfig `yaml:",omitempty" json:"plugin,omitempty"` } @@ -238,6 +240,16 @@ type FileRecorder struct { Sep string `yaml:",omitempty" json:"sep,omitempty"` } +type TCPRecorder struct { + Addr string `json:"addr"` + Timeout time.Duration `json:"timeout"` +} + +type HTTPRecorder struct { + URL string `json:"url" yaml:"url"` + Timeout time.Duration `json:"timeout"` +} + type RedisRecorder struct { Addr string `json:"addr"` DB int `yaml:",omitempty" json:"db,omitempty"` @@ -247,8 +259,9 @@ type RedisRecorder struct { } type RecorderObject struct { - Name string `json:"name"` - Record string `json:"record"` + Name string `json:"name"` + Record string `json:"record"` + Metadata map[string]any } type LimiterConfig struct { diff --git a/config/parsing/parse.go b/config/parsing/parse.go index 11b834c..22fcd2a 100644 --- a/config/parsing/parse.go +++ b/config/parsing/parse.go @@ -49,6 +49,10 @@ const ( mdKeyPostUp = "postUp" mdKeyPostDown = "postDown" mdKeyIgnoreChain = "ignoreChain" + + mdKeyRecorderDirection = "direction" + mdKeyRecorderTimestampFormat = "timeStampFormat" + mdKeyRecorderHexdump = "hexdump" ) func ParseAuther(cfg *config.AutherConfig) auth.Authenticator { @@ -492,6 +496,14 @@ func ParseRecorder(cfg *config.RecorderConfig) (r recorder.Recorder) { ) } + if cfg.TCP != nil && cfg.TCP.Addr != "" { + return xrecorder.TCPRecorder(cfg.TCP.Addr, xrecorder.TimeoutTCPRecorderOption(cfg.TCP.Timeout)) + } + + if cfg.HTTP != nil && cfg.HTTP.URL != "" { + return xrecorder.HTTPRecorder(cfg.HTTP.URL, xrecorder.TimeoutHTTPRecorderOption(cfg.HTTP.Timeout)) + } + if cfg.Redis != nil && cfg.Redis.Addr != "" && cfg.Redis.Key != "" { diff --git a/config/parsing/service.go b/config/parsing/service.go index 3d8cb91..18e9ced 100644 --- a/config/parsing/service.go +++ b/config/parsing/service.go @@ -167,9 +167,15 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) { var recorders []recorder.RecorderObject for _, r := range cfg.Recorders { + md := metadata.NewMetadata(r.Metadata) recorders = append(recorders, recorder.RecorderObject{ Recorder: registry.RecorderRegistry().Get(r.Name), Record: r.Record, + Options: &recorder.Options{ + Direction: mdutil.GetBool(md, mdKeyRecorderDirection), + TimestampFormat: mdutil.GetString(md, mdKeyRecorderTimestampFormat), + Hexdump: mdutil.GetBool(md, mdKeyRecorderHexdump), + }, }) } diff --git a/go.mod b/go.mod index 639c397..41baba4 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d github.com/gin-contrib/cors v1.3.1 github.com/gin-gonic/gin v1.9.1 - github.com/go-gost/core v0.0.0-20230916134612-801f835e9ac1 + github.com/go-gost/core v0.0.0-20230918131208-c258a114c40b github.com/go-gost/gosocks4 v0.0.1 github.com/go-gost/gosocks5 v0.3.1-0.20211109033403-d894d75b7f09 github.com/go-gost/plugin v0.0.0-20230418123101-d221a4ec9a98 diff --git a/go.sum b/go.sum index 07d1c7c..990b82b 100644 --- a/go.sum +++ b/go.sum @@ -99,8 +99,8 @@ github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SU github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gost/core v0.0.0-20230916134612-801f835e9ac1 h1:2B5ir4WZUv5EI+5sKsvSP6GDbrvx/b2+/BnQDPFV1Kg= -github.com/go-gost/core v0.0.0-20230916134612-801f835e9ac1/go.mod h1:db6lBY+DkC3ct4OJfclsKnQwQmcv1B9NnMnpI2MNUwY= +github.com/go-gost/core v0.0.0-20230918131208-c258a114c40b h1:kqALaNXbbYyKFlcLj3ODsuvzplRxypnJOhMINSiM8sk= +github.com/go-gost/core v0.0.0-20230918131208-c258a114c40b/go.mod h1:db6lBY+DkC3ct4OJfclsKnQwQmcv1B9NnMnpI2MNUwY= github.com/go-gost/gosocks4 v0.0.1 h1:+k1sec8HlELuQV7rWftIkmy8UijzUt2I6t+iMPlGB2s= github.com/go-gost/gosocks4 v0.0.1/go.mod h1:3B6L47HbU/qugDg4JnoFPHgJXE43Inz8Bah1QaN9qCc= github.com/go-gost/gosocks5 v0.3.1-0.20211109033403-d894d75b7f09 h1:A95M6UWcfZgOuJkQ7QLfG0Hs5peWIUSysCDNz4pfe04= diff --git a/handler/serial/conn.go b/handler/serial/conn.go index 128e99f..2a58d77 100644 --- a/handler/serial/conn.go +++ b/handler/serial/conn.go @@ -12,33 +12,52 @@ import ( type recorderConn struct { net.Conn - recorder recorder.Recorder + recorder recorder.RecorderObject } func (c *recorderConn) Read(b []byte) (n int, err error) { n, err = c.Conn.Read(b) - if n > 0 && c.recorder != nil { + if n > 0 && c.recorder.Recorder != nil { var buf bytes.Buffer - buf.WriteByte('>') - buf.WriteString(time.Now().Format("2006-01-02 15:04:05.000")) - buf.WriteByte('\n') - buf.WriteString(hex.Dump(b[:n])) - c.recorder.Record(context.Background(), buf.Bytes()) + if c.recorder.Options != nil && c.recorder.Options.Direction { + buf.WriteByte('>') + } + if c.recorder.Options != nil && c.recorder.Options.TimestampFormat != "" { + buf.WriteString(time.Now().Format(c.recorder.Options.TimestampFormat)) + } + if buf.Len() > 0 { + buf.WriteByte('\n') + } + if c.recorder.Options != nil && c.recorder.Options.Hexdump { + buf.WriteString(hex.Dump(b[:n])) + } else { + buf.Write(b[:n]) + } + c.recorder.Recorder.Record(context.Background(), buf.Bytes()) } return } func (c *recorderConn) Write(b []byte) (int, error) { - if c.recorder != nil { + if c.recorder.Recorder != nil { var buf bytes.Buffer - buf.WriteByte('<') - buf.WriteString(time.Now().Format("2006-01-02 15:04:05.000")) - buf.WriteByte('\n') - buf.WriteString(hex.Dump(b)) - c.recorder.Record(context.Background(), buf.Bytes()) + if c.recorder.Options != nil && c.recorder.Options.Direction { + buf.WriteByte('<') + } + if c.recorder.Options != nil && c.recorder.Options.TimestampFormat != "" { + buf.WriteString(time.Now().Format(c.recorder.Options.TimestampFormat)) + } + if buf.Len() > 0 { + buf.WriteByte('\n') + } + if c.recorder.Options != nil && c.recorder.Options.Hexdump { + buf.WriteString(hex.Dump(b)) + } else { + buf.Write(b) + } + c.recorder.Recorder.Record(context.Background(), buf.Bytes()) } return c.Conn.Write(b) - } diff --git a/handler/serial/handler.go b/handler/serial/handler.go index 3ad09e7..3850199 100644 --- a/handler/serial/handler.go +++ b/handler/serial/handler.go @@ -28,7 +28,7 @@ type serialHandler struct { router *chain.Router md metadata options handler.Options - recorder recorder.Recorder + recorder recorder.RecorderObject } func NewHandler(opts ...handler.Option) handler.Handler { @@ -54,7 +54,7 @@ func (h *serialHandler) Init(md md.Metadata) (err error) { if opts := h.router.Options(); opts != nil { for _, ro := range opts.Recorders { if ro.Record == xrecorder.RecorderServiceHandlerSerial { - h.recorder = ro.Recorder + h.recorder = ro break } } @@ -96,11 +96,9 @@ func (h *serialHandler) Handle(ctx context.Context, conn net.Conn, opts ...handl log.Debugf("%s >> %s", conn.LocalAddr(), target.Addr) - if h.recorder != nil { - conn = &recorderConn{ - Conn: conn, - recorder: h.recorder, - } + conn = &recorderConn{ + Conn: conn, + recorder: h.recorder, } // serial port diff --git a/recorder/http.go b/recorder/http.go new file mode 100644 index 0000000..efea5c3 --- /dev/null +++ b/recorder/http.go @@ -0,0 +1,62 @@ +package recorder + +import ( + "bytes" + "context" + "fmt" + "net/http" + "time" + + "github.com/go-gost/core/recorder" +) + +type httpRecorderOptions struct { + timeout time.Duration +} + +type HTTPRecorderOption func(opts *httpRecorderOptions) + +func TimeoutHTTPRecorderOption(timeout time.Duration) HTTPRecorderOption { + return func(opts *httpRecorderOptions) { + opts.timeout = timeout + } +} + +type httpRecorder struct { + url string + httpClient *http.Client +} + +// HTTPRecorder records data to HTTP service. +func HTTPRecorder(url string, opts ...HTTPRecorderOption) recorder.Recorder { + var options httpRecorderOptions + for _, opt := range opts { + opt(&options) + } + + return &httpRecorder{ + url: url, + httpClient: &http.Client{ + Timeout: options.timeout, + }, + } +} + +func (r *httpRecorder) Record(ctx context.Context, b []byte) error { + req, err := http.NewRequest(http.MethodPost, r.url, bytes.NewReader(b)) + if err != nil { + return err + } + + resp, err := r.httpClient.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("%d %s", resp.StatusCode, resp.Status) + } + + return nil +} diff --git a/recorder/tcp.go b/recorder/tcp.go new file mode 100644 index 0000000..ea4b124 --- /dev/null +++ b/recorder/tcp.go @@ -0,0 +1,52 @@ +package recorder + +import ( + "context" + "net" + "time" + + "github.com/go-gost/core/recorder" +) + +type tcpRecorderOptions struct { + timeout time.Duration +} + +type TCPRecorderOption func(opts *tcpRecorderOptions) + +func TimeoutTCPRecorderOption(timeout time.Duration) TCPRecorderOption { + return func(opts *tcpRecorderOptions) { + opts.timeout = timeout + } +} + +type tcpRecorder struct { + addr string + dialer *net.Dialer +} + +// TCPRecorder records data to TCP service. +func TCPRecorder(addr string, opts ...TCPRecorderOption) recorder.Recorder { + var options tcpRecorderOptions + for _, opt := range opts { + opt(&options) + } + + return &tcpRecorder{ + addr: addr, + dialer: &net.Dialer{ + Timeout: options.timeout, + }, + } +} + +func (r *tcpRecorder) Record(ctx context.Context, b []byte) error { + c, err := r.dialer.DialContext(ctx, "tcp", r.addr) + if err != nil { + return err + } + defer c.Close() + + _, err = c.Write(b) + return err +}