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
+103
View File
@@ -0,0 +1,103 @@
package observer
import (
"context"
"errors"
"io"
"github.com/go-gost/core/logger"
"github.com/go-gost/core/observer"
"github.com/go-gost/plugin/observer/proto"
"github.com/go-gost/x/internal/plugin"
xstats "github.com/go-gost/x/observer/stats"
"github.com/go-gost/x/service"
"google.golang.org/grpc"
)
type grpcPlugin struct {
conn grpc.ClientConnInterface
client proto.ObserverClient
log logger.Logger
}
// NewGRPCPlugin creates an Observer plugin based on gRPC.
func NewGRPCPlugin(name string, addr string, opts ...plugin.Option) observer.Observer {
var options plugin.Options
for _, opt := range opts {
opt(&options)
}
log := logger.Default().WithFields(map[string]any{
"kind": "observer",
"observer": name,
})
conn, err := plugin.NewGRPCConn(addr, &options)
if err != nil {
log.Error(err)
return nil
}
p := &grpcPlugin{
conn: conn,
log: log,
}
if conn != nil {
p.client = proto.NewObserverClient(conn)
}
return p
}
func (p *grpcPlugin) Observe(ctx context.Context, events []observer.Event, opts ...observer.Option) error {
if p.client == nil || len(events) == 0 {
return nil
}
var req proto.ObserveRequest
for _, event := range events {
switch event.Type() {
case observer.EventStatus:
ev := event.(service.ServiceEvent)
req.Events = append(req.Events, &proto.Event{
Kind: ev.Kind,
Service: ev.Service,
Type: string(event.Type()),
Status: &proto.ServiceStatus{
State: string(ev.State),
Msg: ev.Msg,
},
})
case observer.EventStats:
ev := event.(xstats.StatsEvent)
req.Events = append(req.Events, &proto.Event{
Kind: ev.Kind,
Service: ev.Service,
Client: ev.Client,
Type: string(event.Type()),
Stats: &proto.Stats{
TotalConns: ev.TotalConns,
CurrentConns: ev.CurrentConns,
InputBytes: ev.InputBytes,
OutputBytes: ev.OutputBytes,
TotalErrs: ev.TotalErrs,
},
})
}
}
reply, err := p.client.Observe(ctx, &req)
if err != nil {
p.log.Error(err)
return err
}
if reply == nil || !reply.Ok {
return errors.New("observe failed")
}
return nil
}
func (p *grpcPlugin) Close() error {
if closer, ok := p.conn.(io.Closer); ok {
return closer.Close()
}
return nil
}
+147
View File
@@ -0,0 +1,147 @@
package observer
import (
"bytes"
"context"
"encoding/json"
"errors"
"net/http"
"strings"
"github.com/go-gost/core/logger"
"github.com/go-gost/core/observer"
"github.com/go-gost/x/internal/plugin"
xstats "github.com/go-gost/x/observer/stats"
"github.com/go-gost/x/service"
)
type observeRequest struct {
Events []event `json:"events"`
}
type event struct {
Kind string `json:"kind"`
Service string `json:"service"`
Client string `json:"client,omitempty"`
Type observer.EventType `json:"type"`
Stats *statsEvent `json:"stats,omitempty"`
Status *statusEvent `json:"status,omitempty"`
}
type statsEvent struct {
TotalConns uint64 `json:"totalConns"`
CurrentConns uint64 `json:"currentConns"`
InputBytes uint64 `json:"inputBytes"`
OutputBytes uint64 `json:"outputBytes"`
TotalErrs uint64 `json:"totalErrs"`
}
type statusEvent struct {
State string `json:"state"`
Msg string `json:"msg"`
}
type httpPluginResponse struct {
OK bool `json:"ok"`
}
type httpPlugin struct {
url string
client *http.Client
header http.Header
log logger.Logger
}
// NewHTTPPlugin creates an Observer plugin based on HTTP.
func NewHTTPPlugin(name string, url string, opts ...plugin.Option) observer.Observer {
var options plugin.Options
for _, opt := range opts {
opt(&options)
}
if !strings.HasPrefix(url, "http") {
url = "http://" + url
}
return &httpPlugin{
url: url,
client: plugin.NewHTTPClient(&options),
header: options.Header,
log: logger.Default().WithFields(map[string]any{
"kind": "observer",
"observer": name,
}),
}
}
func (p *httpPlugin) Observe(ctx context.Context, events []observer.Event, opts ...observer.Option) error {
if p.client == nil || len(events) == 0 {
return nil
}
var r observeRequest
for _, e := range events {
switch e.Type() {
case observer.EventStatus:
ev := e.(service.ServiceEvent)
r.Events = append(r.Events, event{
Kind: ev.Kind,
Service: ev.Service,
Type: ev.Type(),
Status: &statusEvent{
State: string(ev.State),
Msg: ev.Msg,
},
})
case observer.EventStats:
ev := e.(xstats.StatsEvent)
r.Events = append(r.Events, event{
Kind: ev.Kind,
Service: ev.Service,
Client: ev.Client,
Type: ev.Type(),
Stats: &statsEvent{
TotalConns: ev.TotalConns,
CurrentConns: ev.CurrentConns,
InputBytes: ev.InputBytes,
OutputBytes: ev.OutputBytes,
TotalErrs: ev.TotalErrs,
},
})
}
}
v, err := json.Marshal(r)
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("observe failed")
}
return nil
}
+102
View File
@@ -0,0 +1,102 @@
package stats
import (
"sync/atomic"
"github.com/go-gost/core/observer"
"github.com/go-gost/core/observer/stats"
)
type Stats struct {
updated atomic.Bool
totalConns atomic.Uint64
currentConns atomic.Uint64
inputBytes atomic.Uint64
outputBytes atomic.Uint64
totalErrs atomic.Uint64
resetTraffic bool
}
func NewStats(resetTraffic bool) stats.Stats {
return &Stats{
resetTraffic: resetTraffic,
}
}
func (s *Stats) Add(kind stats.Kind, n int64) {
if s == nil {
return
}
switch kind {
case stats.KindTotalConns:
if n > 0 {
s.totalConns.Add(uint64(n))
}
case stats.KindCurrentConns:
s.currentConns.Add(uint64(n))
case stats.KindInputBytes:
s.inputBytes.Add(uint64(n))
case stats.KindOutputBytes:
s.outputBytes.Add(uint64(n))
case stats.KindTotalErrs:
if n > 0 {
s.totalErrs.Add(uint64(n))
}
}
s.updated.Store(true)
}
func (s *Stats) Get(kind stats.Kind) uint64 {
if s == nil {
return 0
}
switch kind {
case stats.KindTotalConns:
return s.totalConns.Load()
case stats.KindCurrentConns:
return s.currentConns.Load()
case stats.KindInputBytes:
if s.resetTraffic {
return s.inputBytes.Swap(0)
}
return s.inputBytes.Load()
case stats.KindOutputBytes:
if s.resetTraffic {
return s.outputBytes.Swap(0)
}
return s.outputBytes.Load()
case stats.KindTotalErrs:
return s.totalErrs.Load()
}
return 0
}
func (s *Stats) Reset() {
s.updated.Store(false)
s.totalConns.Store(0)
s.currentConns.Store(0)
s.inputBytes.Store(0)
s.outputBytes.Store(0)
s.totalErrs.Store(0)
}
func (s *Stats) IsUpdated() bool {
return s.updated.Swap(false)
}
type StatsEvent struct {
Kind string
Service string
Client string
TotalConns uint64
CurrentConns uint64
InputBytes uint64
OutputBytes uint64
TotalErrs uint64
}
func (StatsEvent) Type() observer.EventType {
return observer.EventStats
}
+260
View File
@@ -0,0 +1,260 @@
package wrapper
import (
"errors"
"io"
"net"
"sync"
"syscall"
"github.com/go-gost/core/metadata"
"github.com/go-gost/core/observer/stats"
xnet "github.com/go-gost/x/internal/net"
"github.com/go-gost/x/internal/net/udp"
)
var (
errUnsupport = errors.New("unsupported operation")
)
type conn struct {
net.Conn
stats stats.Stats
closed chan struct{}
mu sync.Mutex
}
func WrapConn(c net.Conn, pStats stats.Stats) net.Conn {
if c == nil || pStats == nil {
return c
}
pStats.Add(stats.KindTotalConns, 1)
pStats.Add(stats.KindCurrentConns, 1)
return &conn{
Conn: c,
stats: pStats,
closed: make(chan struct{}),
}
}
func (c *conn) Read(b []byte) (n int, err error) {
n, err = c.Conn.Read(b)
c.stats.Add(stats.KindInputBytes, int64(n))
return
}
func (c *conn) Write(b []byte) (n int, err error) {
n, err = c.Conn.Write(b)
c.stats.Add(stats.KindOutputBytes, int64(n))
return
}
func (c *conn) Close() error {
c.mu.Lock()
defer c.mu.Unlock()
select {
case <-c.closed:
return nil
default:
close(c.closed)
}
c.stats.Add(stats.KindCurrentConns, -1)
return c.Conn.Close()
}
func (c *conn) SyscallConn() (rc syscall.RawConn, err error) {
if sc, ok := c.Conn.(syscall.Conn); ok {
rc, err = sc.SyscallConn()
return
}
err = errUnsupport
return
}
func (c *conn) Metadata() metadata.Metadata {
if md, ok := c.Conn.(metadata.Metadatable); ok {
return md.Metadata()
}
return nil
}
type packetConn struct {
net.PacketConn
stats stats.Stats
}
func WrapPacketConn(pc net.PacketConn, stats stats.Stats) net.PacketConn {
if stats == nil {
return pc
}
return &packetConn{
PacketConn: pc,
stats: stats,
}
}
func (c *packetConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
n, addr, err = c.PacketConn.ReadFrom(p)
c.stats.Add(stats.KindInputBytes, int64(n))
return
}
func (c *packetConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
n, err = c.PacketConn.WriteTo(p, addr)
c.stats.Add(stats.KindOutputBytes, int64(n))
return
}
func (c *packetConn) Metadata() metadata.Metadata {
if md, ok := c.PacketConn.(metadata.Metadatable); ok {
return md.Metadata()
}
return nil
}
type udpConn struct {
net.PacketConn
stats stats.Stats
}
func WrapUDPConn(pc net.PacketConn, stats stats.Stats) udp.Conn {
return &udpConn{
PacketConn: pc,
stats: stats,
}
}
func (c *udpConn) RemoteAddr() net.Addr {
if nc, ok := c.PacketConn.(xnet.RemoteAddr); ok {
return nc.RemoteAddr()
}
return nil
}
func (c *udpConn) SetReadBuffer(n int) error {
if nc, ok := c.PacketConn.(xnet.SetBuffer); ok {
return nc.SetReadBuffer(n)
}
return errUnsupport
}
func (c *udpConn) SetWriteBuffer(n int) error {
if nc, ok := c.PacketConn.(xnet.SetBuffer); ok {
return nc.SetWriteBuffer(n)
}
return errUnsupport
}
func (c *udpConn) Read(b []byte) (n int, err error) {
if nc, ok := c.PacketConn.(io.Reader); ok {
n, err = nc.Read(b)
if c.stats != nil {
c.stats.Add(stats.KindInputBytes, int64(n))
}
return
}
err = errUnsupport
return
}
func (c *udpConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
n, addr, err = c.PacketConn.ReadFrom(p)
if c.stats != nil {
c.stats.Add(stats.KindInputBytes, int64(n))
}
return
}
func (c *udpConn) ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error) {
if nc, ok := c.PacketConn.(udp.ReadUDP); ok {
n, addr, err = nc.ReadFromUDP(b)
if c.stats != nil {
c.stats.Add(stats.KindInputBytes, int64(n))
}
return
}
err = errUnsupport
return
}
func (c *udpConn) ReadMsgUDP(b, oob []byte) (n, oobn, flags int, addr *net.UDPAddr, err error) {
if nc, ok := c.PacketConn.(udp.ReadUDP); ok {
n, oobn, flags, addr, err = nc.ReadMsgUDP(b, oob)
if c.stats != nil {
c.stats.Add(stats.KindInputBytes, int64(n))
}
return
}
err = errUnsupport
return
}
func (c *udpConn) Write(b []byte) (n int, err error) {
if nc, ok := c.PacketConn.(io.Writer); ok {
n, err = nc.Write(b)
if c.stats != nil {
c.stats.Add(stats.KindOutputBytes, int64(n))
}
return
}
err = errUnsupport
return
}
func (c *udpConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
n, err = c.PacketConn.WriteTo(p, addr)
if c.stats != nil {
c.stats.Add(stats.KindOutputBytes, int64(n))
}
return
}
func (c *udpConn) WriteToUDP(b []byte, addr *net.UDPAddr) (n int, err error) {
if nc, ok := c.PacketConn.(udp.WriteUDP); ok {
n, err = nc.WriteToUDP(b, addr)
if c.stats != nil {
c.stats.Add(stats.KindOutputBytes, int64(n))
}
return
}
err = errUnsupport
return
}
func (c *udpConn) WriteMsgUDP(b, oob []byte, addr *net.UDPAddr) (n, oobn int, err error) {
if nc, ok := c.PacketConn.(udp.WriteUDP); ok {
n, oobn, err = nc.WriteMsgUDP(b, oob, addr)
if c.stats != nil {
c.stats.Add(stats.KindOutputBytes, int64(n))
}
return
}
err = errUnsupport
return
}
func (c *udpConn) SyscallConn() (rc syscall.RawConn, err error) {
if nc, ok := c.PacketConn.(syscall.Conn); ok {
return nc.SyscallConn()
}
err = errUnsupport
return
}
func (c *udpConn) SetDSCP(n int) error {
if nc, ok := c.PacketConn.(xnet.SetDSCP); ok {
return nc.SetDSCP(n)
}
return nil
}
func (c *udpConn) Metadata() metadata.Metadata {
if md, ok := c.PacketConn.(metadata.Metadatable); ok {
return md.Metadata()
}
return nil
}
+38
View File
@@ -0,0 +1,38 @@
package wrapper
import (
"io"
"github.com/go-gost/core/observer/stats"
)
// readWriter is an io.ReadWriter with Stats.
type readWriter struct {
io.ReadWriter
stats stats.Stats
}
func WrapReadWriter(rw io.ReadWriter, stats stats.Stats) io.ReadWriter {
if rw == nil || stats == nil {
return rw
}
return &readWriter{
ReadWriter: rw,
stats: stats,
}
}
func (p *readWriter) Read(b []byte) (n int, err error) {
n, err = p.ReadWriter.Read(b)
p.stats.Add(stats.KindInputBytes, int64(n))
return
}
func (p *readWriter) Write(b []byte) (n int, err error) {
n, err = p.ReadWriter.Write(b)
p.stats.Add(stats.KindOutputBytes, int64(n))
return
}
@@ -0,0 +1,32 @@
package wrapper
import (
"net"
"github.com/go-gost/core/observer/stats"
)
type listener struct {
stats stats.Stats
net.Listener
}
func WrapListener(ln net.Listener, stats stats.Stats) net.Listener {
if stats == nil {
return ln
}
return &listener{
stats: stats,
Listener: ln,
}
}
func (ln *listener) Accept() (net.Conn, error) {
c, err := ln.Listener.Accept()
if err != nil {
return nil, err
}
return WrapConn(c, ln.stats), nil
}