This commit is contained in:
qaq
2025-06-17 12:17:33 +08:00
parent 4a6b2c8e0f
commit 9fa968d07c
648 changed files with 64968 additions and 1621 deletions
+55
View File
@@ -0,0 +1,55 @@
package recorder
import (
"context"
"io"
"github.com/go-gost/core/recorder"
)
type fileRecorderOptions struct {
sep string
}
type FileRecorderOption func(opts *fileRecorderOptions)
func SepRecorderOption(sep string) FileRecorderOption {
return func(opts *fileRecorderOptions) {
opts.sep = sep
}
}
type fileRecorder struct {
out io.WriteCloser
sep string
}
// FileRecorder records data to file.
func FileRecorder(out io.WriteCloser, opts ...FileRecorderOption) recorder.Recorder {
var options fileRecorderOptions
for _, opt := range opts {
opt(&options)
}
return &fileRecorder{
out: out,
sep: options.sep,
}
}
func (r *fileRecorder) Record(ctx context.Context, b []byte, opts ...recorder.RecordOption) error {
if _, err := r.out.Write(b); err != nil {
return err
}
if r.sep != "" {
_, err := io.WriteString(r.out, r.sep)
return err
}
return nil
}
func (r *fileRecorder) Close() error {
return r.out.Close()
}
+87
View File
@@ -0,0 +1,87 @@
package recorder
import (
"bytes"
"context"
"errors"
"net/http"
"strings"
"time"
"github.com/go-gost/core/recorder"
)
type httpRecorderOptions struct {
timeout time.Duration
header http.Header
}
type HTTPRecorderOption func(opts *httpRecorderOptions)
func TimeoutHTTPRecorderOption(timeout time.Duration) HTTPRecorderOption {
return func(opts *httpRecorderOptions) {
opts.timeout = timeout
}
}
func HeaderHTTPRecorderOption(header http.Header) HTTPRecorderOption {
return func(opts *httpRecorderOptions) {
opts.header = header
}
}
type httpRecorder struct {
url string
httpClient *http.Client
header http.Header
}
// HTTPRecorder records data to HTTP service.
func HTTPRecorder(url string, opts ...HTTPRecorderOption) recorder.Recorder {
var options httpRecorderOptions
for _, opt := range opts {
opt(&options)
}
if url == "" {
return nil
}
if !strings.HasPrefix(url, "http") {
url = "http://" + url
}
return &httpRecorder{
url: url,
httpClient: &http.Client{
Timeout: options.timeout,
},
header: options.header,
}
}
func (r *httpRecorder) Record(ctx context.Context, b []byte, opts ...recorder.RecordOption) error {
req, err := http.NewRequest(http.MethodPost, r.url, bytes.NewReader(b))
if err != nil {
return err
}
if r.header != nil {
req.Header = r.header
}
if req.Header.Get("Content-Type") == "" {
req.Header.Set("Content-Type", "application/json")
}
resp, err := r.httpClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode >= http.StatusBadRequest {
return errors.New(resp.Status)
}
return nil
}
+84
View File
@@ -0,0 +1,84 @@
package recorder
import (
"context"
"encoding/json"
"errors"
"io"
"github.com/go-gost/core/logger"
"github.com/go-gost/core/recorder"
"github.com/go-gost/plugin/recorder/proto"
"github.com/go-gost/x/internal/plugin"
"google.golang.org/grpc"
)
type grpcPlugin struct {
conn grpc.ClientConnInterface
client proto.RecorderClient
log logger.Logger
}
// NewGRPCPlugin creates a Recorder plugin based on gRPC.
func NewGRPCPlugin(name string, addr string, opts ...plugin.Option) recorder.Recorder {
var options plugin.Options
for _, opt := range opts {
opt(&options)
}
log := logger.Default().WithFields(map[string]any{
"kind": "recorder",
"recorder": name,
})
conn, err := plugin.NewGRPCConn(addr, &options)
if err != nil {
log.Error(err)
}
p := &grpcPlugin{
conn: conn,
log: log,
}
if conn != nil {
p.client = proto.NewRecorderClient(conn)
}
return p
}
func (p *grpcPlugin) Record(ctx context.Context, b []byte, opts ...recorder.RecordOption) error {
if p.client == nil {
return nil
}
var options recorder.RecordOptions
for _, opt := range opts {
opt(&options)
}
md, _ := json.Marshal(options.Metadata)
reply, err := p.client.Record(ctx,
&proto.RecordRequest{
Data: b,
Metadata: md,
})
if err != nil {
p.log.Error(err)
return err
}
if reply == nil || !reply.Ok {
return errors.New("record failed")
}
return nil
}
func (p *grpcPlugin) Close() error {
if p.conn == nil {
return nil
}
if closer, ok := p.conn.(io.Closer); ok {
return closer.Close()
}
return nil
}
+98
View File
@@ -0,0 +1,98 @@
package recorder
import (
"bytes"
"context"
"encoding/json"
"errors"
"net/http"
"github.com/go-gost/core/logger"
"github.com/go-gost/core/recorder"
"github.com/go-gost/x/internal/plugin"
)
type httpPluginRequest struct {
Data []byte `json:"data"`
Metadata []byte `json:"metadata"`
}
type httpPluginResponse struct {
OK bool `json:"ok"`
}
type httpPlugin struct {
url string
client *http.Client
header http.Header
log logger.Logger
}
// NewHTTPPlugin creates an Recorder plugin based on HTTP.
func NewHTTPPlugin(name string, url string, opts ...plugin.Option) recorder.Recorder {
var options plugin.Options
for _, opt := range opts {
opt(&options)
}
return &httpPlugin{
url: url,
client: plugin.NewHTTPClient(&options),
header: options.Header,
log: logger.Default().WithFields(map[string]any{
"kind": "recorder",
"recorder": name,
}),
}
}
func (p *httpPlugin) Record(ctx context.Context, b []byte, opts ...recorder.RecordOption) error {
if len(b) == 0 || p.client == nil {
return nil
}
var options recorder.RecordOptions
for _, opt := range opts {
opt(&options)
}
md, _ := json.Marshal(options.Metadata)
rb := httpPluginRequest{
Data: b,
Metadata: md,
}
v, err := json.Marshal(&rb)
if err != nil {
return err
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, p.url, bytes.NewReader(v))
if err != nil {
return err
}
if p.header != nil {
req.Header = p.header.Clone()
}
req.Header.Set("Content-Type", "application/json")
resp, err := p.client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return errors.New(resp.Status)
}
res := httpPluginResponse{}
if err := json.NewDecoder(resp.Body).Decode(&res); err != nil {
return err
}
if !res.OK {
return errors.New("record failed")
}
return nil
}
+110
View File
@@ -0,0 +1,110 @@
package recorder
import (
"context"
"encoding/json"
"net/http"
"time"
"github.com/go-gost/core/recorder"
)
const (
RecorderServiceHandler = "recorder.service.handler"
RecorderServiceHandlerSerial = "recorder.service.handler.serial"
RecorderServiceHandlerTunnel = "recorder.service.handler.tunnel"
)
type HTTPRequestRecorderObject struct {
ContentLength int64 `json:"contentLength"`
Header http.Header `json:"header"`
Body []byte `json:"body"`
}
type HTTPResponseRecorderObject struct {
ContentLength int64 `json:"contentLength"`
Header http.Header `json:"header"`
Body []byte `json:"body"`
}
type HTTPRecorderObject struct {
Host string `json:"host"`
Method string `json:"method"`
Proto string `json:"proto"`
Scheme string `json:"scheme"`
URI string `json:"uri"`
StatusCode int `json:"statusCode"`
Request HTTPRequestRecorderObject `json:"request"`
Response HTTPResponseRecorderObject `json:"response"`
}
type WebsocketRecorderObject struct {
From string `json:"from"`
Fin bool `json:"fin"`
Rsv1 bool `json:"rsv1"`
Rsv2 bool `json:"rsv2"`
Rsv3 bool `json:"rsv3"`
OpCode int `json:"opcode"`
Masked bool `json:"masked"`
MaskKey uint32 `json:"maskKey"`
Length int64 `json:"length"`
Payload []byte `json:"payload"`
}
type TLSRecorderObject struct {
ServerName string `json:"serverName"`
CipherSuite string `json:"cipherSuite"`
CompressionMethod uint8 `json:"compressionMethod"`
Proto string `json:"proto"`
Version string `json:"version"`
ClientHello string `json:"clientHello"`
ServerHello string `json:"serverHello"`
}
type DNSRecorderObject struct {
ID int `json:"id"`
Name string `json:"name"`
Class string `json:"class"`
Type string `json:"type"`
Question string `json:"question"`
Answer string `json:"answer"`
Cached bool `json:"cached"`
}
type HandlerRecorderObject struct {
Node string `json:"node,omitempty"`
Service string `json:"service"`
Network string `json:"network"`
RemoteAddr string `json:"remote"`
LocalAddr string `json:"local"`
Host string `json:"host"`
Dst string `json:"dst"`
Proto string `json:"proto,omitempty"`
ClientIP string `json:"clientIP"`
ClientID string `json:"clientID,omitempty"`
HTTP *HTTPRecorderObject `json:"http,omitempty"`
Websocket *WebsocketRecorderObject `json:"websocket,omitempty"`
TLS *TLSRecorderObject `json:"tls,omitempty"`
DNS *DNSRecorderObject `json:"dns,omitempty"`
Route string `json:"route,omitempty"`
InputBytes uint64 `json:"inputBytes"`
OutputBytes uint64 `json:"outputBytes"`
Redirect string `json:"redirect,omitempty"`
Err string `json:"err,omitempty"`
SID string `json:"sid"`
Duration time.Duration `json:"duration"`
Time time.Time `json:"time"`
}
func (p *HandlerRecorderObject) Record(ctx context.Context, r recorder.Recorder) error {
if p == nil || r == nil || p.Time.IsZero() {
return nil
}
data, err := json.Marshal(p)
if err != nil {
return err
}
return r.Record(ctx, data)
}
+144
View File
@@ -0,0 +1,144 @@
package recorder
import (
"context"
"github.com/go-gost/core/recorder"
"github.com/go-redis/redis/v8"
)
type redisRecorderOptions struct {
db int
username string
password string
key string
}
type RedisRecorderOption func(opts *redisRecorderOptions)
func DBRedisRecorderOption(db int) RedisRecorderOption {
return func(opts *redisRecorderOptions) {
opts.db = db
}
}
func UsernameRedisRecorderOption(username string) RedisRecorderOption {
return func(opts *redisRecorderOptions) {
opts.username = username
}
}
func PasswordRedisRecorderOption(password string) RedisRecorderOption {
return func(opts *redisRecorderOptions) {
opts.password = password
}
}
func KeyRedisRecorderOption(key string) RedisRecorderOption {
return func(opts *redisRecorderOptions) {
opts.key = key
}
}
type redisSetRecorder struct {
client *redis.Client
key string
}
// RedisSetRecorder records data to a redis set.
func RedisSetRecorder(addr string, opts ...RedisRecorderOption) recorder.Recorder {
var options redisRecorderOptions
for _, opt := range opts {
opt(&options)
}
return &redisSetRecorder{
client: redis.NewClient(&redis.Options{
Addr: addr,
Password: options.password,
DB: options.db,
}),
key: options.key,
}
}
func (r *redisSetRecorder) Record(ctx context.Context, b []byte, opts ...recorder.RecordOption) error {
if r.key == "" {
return nil
}
return r.client.SAdd(ctx, r.key, b).Err()
}
func (r *redisSetRecorder) Close() error {
return r.client.Close()
}
type redisListRecorder struct {
client *redis.Client
key string
}
// RedisListRecorder records data to a redis list.
func RedisListRecorder(addr string, opts ...RedisRecorderOption) recorder.Recorder {
var options redisRecorderOptions
for _, opt := range opts {
opt(&options)
}
return &redisListRecorder{
client: redis.NewClient(&redis.Options{
Addr: addr,
Password: options.password,
DB: options.db,
}),
key: options.key,
}
}
func (r *redisListRecorder) Record(ctx context.Context, b []byte, opts ...recorder.RecordOption) error {
if r.key == "" {
return nil
}
return r.client.LPush(ctx, r.key, b).Err()
}
func (r *redisListRecorder) Close() error {
return r.client.Close()
}
type redisSortedSetRecorder struct {
client *redis.Client
key string
}
// RedisSortedSetRecorder records data to a redis sorted set.
func RedisSortedSetRecorder(addr string, opts ...RedisRecorderOption) recorder.Recorder {
var options redisRecorderOptions
for _, opt := range opts {
opt(&options)
}
return &redisSortedSetRecorder{
client: redis.NewClient(&redis.Options{
Addr: addr,
Password: options.password,
DB: options.db,
}),
key: options.key,
}
}
func (r *redisSortedSetRecorder) Record(ctx context.Context, b []byte, opts ...recorder.RecordOption) error {
if r.key == "" {
return nil
}
return r.client.ZIncr(ctx, r.key, &redis.Z{
Score: 1,
Member: b,
}).Err()
}
func (r *redisSortedSetRecorder) Close() error {
return r.client.Close()
}
+62
View File
@@ -0,0 +1,62 @@
package recorder
import (
"context"
"net"
"time"
"github.com/go-gost/core/logger"
"github.com/go-gost/core/recorder"
)
type tcpRecorderOptions struct {
timeout time.Duration
log logger.Logger
}
type TCPRecorderOption func(opts *tcpRecorderOptions)
func TimeoutTCPRecorderOption(timeout time.Duration) TCPRecorderOption {
return func(opts *tcpRecorderOptions) {
opts.timeout = timeout
}
}
func LogTCPRecorderOption(log logger.Logger) TCPRecorderOption {
return func(opts *tcpRecorderOptions) {
opts.log = log
}
}
type tcpRecorder struct {
addr string
dialer *net.Dialer
log logger.Logger
}
// 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,
},
log: options.log,
}
}
func (r *tcpRecorder) Record(ctx context.Context, b []byte, opts ...recorder.RecordOption) error {
c, err := r.dialer.DialContext(ctx, "tcp", r.addr)
if err != nil {
return err
}
defer c.Close()
_, err = c.Write(b)
return err
}