Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions handler/forward/local/conn.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package local

import (
"bytes"
"context"
"encoding/hex"
"net"
"time"

"github.com/go-gost/core/recorder"
)

type recorderConn struct {
net.Conn
recorder recorder.RecorderObject
}

func (c *recorderConn) record(ctx context.Context, direction byte, b []byte) {
if len(b) == 0 || c.recorder.Recorder == nil {
return
}

var buf bytes.Buffer
if c.recorder.Options != nil && c.recorder.Options.Direction {
buf.WriteByte(direction)
}
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(ctx, buf.Bytes())
}

func (c *recorderConn) Read(b []byte) (n int, err error) {
n, err = c.Conn.Read(b)
c.record(context.Background(), '>', b[:n])
return
}

func (c *recorderConn) Write(b []byte) (int, error) {
c.record(context.Background(), '<', b)
return c.Conn.Write(b)
}
81 changes: 81 additions & 0 deletions handler/forward/local/conn_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package local

import (
"context"
"errors"
"net"
"testing"

"github.com/go-gost/core/recorder"
)

type captureRecorder struct {
records [][]byte
err error
}

func (r *captureRecorder) Record(_ context.Context, b []byte, _ ...recorder.RecordOption) error {
r.records = append(r.records, append([]byte(nil), b...))
return r.err
}

func TestRecorderConnRecordsPayload(t *testing.T) {
client, upstream := net.Pipe()
defer client.Close()
defer upstream.Close()

rec := &captureRecorder{}
conn := &recorderConn{
Conn: client,
recorder: recorder.RecorderObject{
Recorder: rec,
Options: &recorder.Options{Direction: true},
},
}

go func() {
buf := make([]byte, 7)
_, _ = upstream.Read(buf)
_, _ = upstream.Write([]byte("reply"))
}()

if _, err := conn.Write([]byte("payload")); err != nil {
t.Fatal(err)
}
buf := make([]byte, 5)
if _, err := conn.Read(buf); err != nil {
t.Fatal(err)
}

if got := string(rec.records[0]); got != "<\npayload" {
t.Fatalf("write record = %q, want %q", got, "<\npayload")
}
if got := string(rec.records[1]); got != ">\nreply" {
t.Fatalf("read record = %q, want %q", got, ">\nreply")
}
}

func TestRecorderConnRecordFailureDoesNotStopWrite(t *testing.T) {
client, upstream := net.Pipe()
defer client.Close()
defer upstream.Close()

conn := &recorderConn{
Conn: client,
recorder: recorder.RecorderObject{
Recorder: &captureRecorder{err: errors.New("sink down")},
},
}

done := make(chan struct{})
go func() {
defer close(done)
buf := make([]byte, 7)
_, _ = upstream.Read(buf)
}()

if _, err := conn.Write([]byte("payload")); err != nil {
t.Fatal(err)
}
<-done
}
24 changes: 17 additions & 7 deletions handler/forward/local/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,12 @@ func init() {
}

type forwardHandler struct {
hop hop.Hop
md metadata
options handler.Options
recorder recorder.RecorderObject
certPool tls_util.CertPool
hop hop.Hop
md metadata
options handler.Options
recorder recorder.RecorderObject
rawRecorder recorder.RecorderObject
certPool tls_util.CertPool
}

func NewHandler(opts ...handler.Option) handler.Handler {
Expand All @@ -59,9 +60,11 @@ func (h *forwardHandler) Init(md md.Metadata) (err error) {
}

for _, ro := range h.options.Recorders {
if ro.Record == xrecorder.RecorderServiceHandler {
switch ro.Record {
case xrecorder.RecorderServiceHandler:
h.recorder = ro
break
case xrecorder.RecorderServiceHandlerRaw:
Comment on lines 62 to +66

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve first matching handler recorder selection

This loop no longer stops after finding recorder.service.handler, so the last matching entry now overwrites earlier ones. Other handlers in this repo keep first-match behavior (break on first match), so configs with multiple handler recorder entries will silently change which sink receives service metadata after this commit. That regression is hard to detect and can redirect records to the wrong backend.

Useful? React with 👍 / 👎.

h.rawRecorder = ro
}
}

Expand Down Expand Up @@ -110,6 +113,13 @@ func (h *forwardHandler) Handle(ctx context.Context, conn net.Conn, opts ...hand
})
log.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr())

if network == "tcp" && h.rawRecorder.Recorder != nil {
conn = &recorderConn{
Conn: conn,
recorder: h.rawRecorder,
}
}

pStats := xstats.Stats{}
conn = stats_wrapper.WrapConn(conn, &pStats)

Expand Down
1 change: 1 addition & 0 deletions recorder/recorder.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const (
RecorderServiceHandler = "recorder.service.handler"
RecorderServiceHandlerSerial = "recorder.service.handler.serial"
RecorderServiceHandlerTunnel = "recorder.service.handler.tunnel"
RecorderServiceHandlerRaw = "recorder.service.handler.raw"
)

type HTTPRequestRecorderObject struct {
Expand Down