添加 haproxy 协议支持
Some checks failed
Go / build (.exe, 386, windows, windows-386) (push) Has been cancelled
Go / build (.exe, amd64, windows, windows-amd64) (push) Has been cancelled
Go / build (.exe, arm64, windows, windows-arm64) (push) Has been cancelled
Go / build (386, freebsd, freebsd-386) (push) Has been cancelled
Go / build (386, linux, linux-386) (push) Has been cancelled
Go / build (386, netbsd, netbsd-386) (push) Has been cancelled
Go / build (386, openbsd, openbsd-386) (push) Has been cancelled
Go / build (386, plan9, plan9-386) (push) Has been cancelled
Go / build (amd64, darwin, darwin-amd64) (push) Has been cancelled
Go / build (amd64, dragonfly, dragonfly-amd64) (push) Has been cancelled
Go / build (amd64, freebsd, freebsd-amd64) (push) Has been cancelled
Go / build (amd64, illumos, illumos-amd64) (push) Has been cancelled
Go / build (amd64, linux, linux-amd64) (push) Has been cancelled
Go / build (amd64, netbsd, netbsd-amd64) (push) Has been cancelled
Go / build (amd64, openbsd, openbsd-amd64) (push) Has been cancelled
Go / build (amd64, plan9, plan9-amd64) (push) Has been cancelled
Go / build (amd64, solaris, solaris-amd64) (push) Has been cancelled
Go / build (arm, 6, linux, linux-armv6) (push) Has been cancelled
Go / build (arm, 7, linux, linux-armv7) (push) Has been cancelled
Go / build (arm, freebsd, freebsd-arm) (push) Has been cancelled
Go / build (arm, netbsd, netbsd-arm) (push) Has been cancelled
Go / build (arm, openbsd, openbsd-arm) (push) Has been cancelled
Go / build (arm, plan9, plan9-arm) (push) Has been cancelled
Go / build (arm64, darwin, darwin-arm64) (push) Has been cancelled
Go / build (arm64, freebsd, freebsd-arm64) (push) Has been cancelled
Go / build (arm64, linux, linux-arm64) (push) Has been cancelled
Go / build (arm64, netbsd, netbsd-arm64) (push) Has been cancelled
Go / build (arm64, openbsd, openbsd-arm64) (push) Has been cancelled
Go / build (loong64, linux, linux-loong64) (push) Has been cancelled
Go / build (mips, linux, linux-mips) (push) Has been cancelled
Go / build (mips64, linux, linux-mips64) (push) Has been cancelled
Go / build (mips64le, linux, linux-mips64le) (push) Has been cancelled
Go / build (mipsle, linux, linux-mipsle) (push) Has been cancelled
Go / build (ppc64, aix, aix-ppc64) (push) Has been cancelled
Go / build (ppc64, linux, linux-ppc64) (push) Has been cancelled
Go / build (ppc64, openbsd, openbsd-ppc64) (push) Has been cancelled
Go / build (ppc64le, linux, linux-ppc64le) (push) Has been cancelled
Go / build (riscv64, freebsd, freebsd-riscv64) (push) Has been cancelled
Go / build (riscv64, linux, linux-riscv64) (push) Has been cancelled
Go / build (riscv64, openbsd, openbsd-riscv64) (push) Has been cancelled
Go / build (s390x, linux, linux-s390x) (push) Has been cancelled
Go / build (wasm, js, js-wasm) (push) Has been cancelled
Go / build (wasm, wasip1, wasip1-wasm) (push) Has been cancelled
Go / merge-artifacts (push) Has been cancelled

添加插件功能支持
This commit is contained in:
2025-07-10 12:00:31 +08:00
parent 9e80512948
commit ef9cca7440
8 changed files with 438 additions and 43 deletions

View File

@@ -9,6 +9,7 @@ import (
"github.com/BurntSushi/toml"
"github.com/fsnotify/fsnotify"
"github.com/mitchellh/mapstructure"
"github.com/rs/zerolog/log"
)
@@ -22,13 +23,14 @@ var (
type (
Config struct {
Tcp ProtocolConfig `toml:"tcp"`
Quic QuicConfig `toml:"quic"`
Kcp KcpConfig `toml:"kcp"`
WebSocket WebSocketConfig `toml:"websocket"`
Hosts map[string]string `toml:"hosts"`
Log LogConfig `toml:"log"`
PidFile string `toml:"pid_file"`
Tcp ProtocolConfig `toml:"tcp"`
Quic QuicConfig `toml:"quic"`
Kcp KcpConfig `toml:"kcp"`
WebSocket WebSocketConfig `toml:"websocket"`
Hosts map[string]string `toml:"hosts"`
Log LogConfig `toml:"log"`
PidFile string `toml:"pid_file"`
Plugin map[string]map[string]any `toml:"plugin"`
}
ProtocolConfig struct {
@@ -115,7 +117,13 @@ func loadConfig() error {
writePIDFile()
return loadLogger()
if err := loadLogger(); err != nil {
return err
}
loadPlugins()
return nil
}
func watchConfig() *fsnotify.Watcher {
@@ -164,3 +172,23 @@ func watchConfig() *fsnotify.Watcher {
return watcher
}
func loadPluginConfig(cfg map[string]any, pluginCfg any) error {
log.Info().
Any("config", cfg).
Msg("Loading plugin config")
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
Result: pluginCfg,
TagName: "toml",
})
if err != nil {
return err
}
if err := decoder.Decode(cfg); err != nil {
return err
}
return nil
}

52
cmd/gateway/haproxy.go Normal file
View File

@@ -0,0 +1,52 @@
package main
import (
"net"
proxyproto "github.com/pires/go-proxyproto"
"github.com/rs/zerolog/log"
)
func haProxyUpstream(source net.Conn, host string) net.Conn {
target, err := net.ResolveTCPAddr("tcp", host)
if err != nil {
log.Err(err).Msg("failed to resolve TCP address")
return nil
}
conn, err := net.DialTCP("tcp", nil, target)
if err != nil {
log.Err(err).Msg("failed to dial TCP")
return nil
}
sourceAddr, err := net.ResolveTCPAddr(
source.RemoteAddr().Network(),
source.RemoteAddr().String(),
)
if err != nil {
log.Err(err).Msg("failed to resolve TCP address")
return nil
}
TransportProtocol := proxyproto.TCPv4
if sourceAddr.IP.To4() == nil {
TransportProtocol = proxyproto.TCPv6
}
header := &proxyproto.Header{
Version: 1,
Command: proxyproto.PROXY,
TransportProtocol: TransportProtocol,
SourceAddr: sourceAddr,
DestinationAddr: target,
}
// After the connection was created write the proxy headers first
_, err = header.WriteTo(conn)
if err != nil {
log.Err(err).Msg("failed to write proxy header")
return nil
}
return conn
}

View File

@@ -8,6 +8,7 @@ import (
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/tursom/mc-gateway/plugin/api"
"github.com/tursom/mc-gateway/protocol"
)
@@ -26,16 +27,15 @@ func main() {
go handleLogRotate()
var wg sync.WaitGroup
defer wg.Wait()
defer exitWaitGroup.Wait()
for _, service := range services {
if !*service.enable {
continue
}
wg.Add(1)
go service.run(&wg)
exitWaitGroup.Add(1)
go service.run(&exitWaitGroup)
}
}
@@ -121,12 +121,26 @@ func mapToHost(conn net.Conn) net.Conn {
var client net.Conn
if host, ok := strings.CutPrefix(host, "quic://"); ok {
client = upstreamQuic(host)
} else if host, ok := strings.CutPrefix(host, "kcp://"); ok {
client = upstreamKcp(host)
} else {
client = upstreamTcp(host)
ok, err = invokeFirstHookHandler(api.HookUpstream, Handler2[net.Conn, string, bool](conn, host), func(handler func(net.Conn, string) (net.Conn, error)) error {
var err error
client, err = handler(conn, host)
return err
})
if err != nil {
log.Err(err).Msg("Failed to invoke upstream hook")
return nil
}
if !ok {
if host, ok := strings.CutPrefix(host, "quic://"); ok {
client = upstreamQuic(host)
} else if host, ok := strings.CutPrefix(host, "kcp://"); ok {
client = upstreamKcp(host)
} else if host, ok := strings.CutPrefix(host, "haproxy://"); ok {
client = haProxyUpstream(conn, host)
} else {
client = upstreamTcp(host)
}
}
if client == nil {
return nil

175
cmd/gateway/plugin.go Normal file
View File

@@ -0,0 +1,175 @@
package main
import (
"net"
"plugin"
"sync"
"github.com/rs/zerolog/log"
"github.com/tursom/mc-gateway/plugin/api"
)
var (
exitWaitGroup sync.WaitGroup
pluginLock sync.RWMutex
plugins = make(map[string]api.Plugin)
hooks = make(map[string]map[string]any)
)
type (
Gateway struct {
pluginId string
}
)
func loadPlugins() {
pluginLock.Lock()
defer pluginLock.Unlock()
for pluginKey, pluginConfig := range config.Plugin {
if enable, ok := pluginConfig["enable"].(bool); ok && enable {
pluginFile := pluginKey
if file, ok := pluginConfig["file"].(string); ok {
pluginFile = file
}
if _, ok := plugins[pluginKey]; ok {
continue
}
gateway := &Gateway{
pluginId: pluginKey,
}
log.Info().Str("plugin", pluginKey).Msg("Loading plugin")
p, err := plugin.Open(pluginFile + ".so")
if err != nil {
log.Err(err).Str("plugin", pluginKey).Msg("Failed to open plugin")
continue
}
pluginSymbol, err := p.Lookup("Plugin")
if err != nil {
log.Err(err).Str("plugin", pluginKey).Msg("Failed to lookup plugin")
continue
}
pluginFactory, ok := pluginSymbol.(func() api.Plugin)
if !ok {
log.Err(err).Str("plugin", pluginKey).Msg("Invalid plugin factory signature")
continue
}
pluginInstance := pluginFactory()
cfgObj := pluginInstance.NewConfigObj()
if err := loadPluginConfig(pluginConfig, cfgObj); err != nil {
log.Err(err).Str("plugin", pluginKey).Msg("Failed to load plugin config")
continue
}
if err := pluginInstance.ReloadConfig(cfgObj); err != nil {
log.Err(err).Str("plugin", pluginKey).Msg("Failed to reload plugin config")
continue
}
if err := pluginInstance.Init(gateway); err != nil {
log.Err(err).Str("plugin", pluginKey).Msg("Failed to initialize plugin")
continue
}
plugins[pluginKey] = pluginInstance
hooks[pluginKey] = make(map[string]any)
log.Info().Str("plugin", pluginKey).Msg("Plugin loaded successfully")
} else {
if plugin, ok := plugins[pluginKey]; ok {
if err := plugin.Destroy(); err != nil {
log.Err(err).Str("plugin", pluginKey).Msg("Failed to destroy plugin")
}
log.Info().Str("plugin", pluginKey).Msg("Plugin disabled")
}
delete(plugins, pluginKey)
delete(hooks, pluginKey)
}
}
}
// HandleConn implements api.Gateway.
func (g *Gateway) HandleConn(conn net.Conn) {
go handleRequest(conn)
}
// Hook implements api.Gateway.
func (g *Gateway) Hook(hook string, handler any) error {
pluginLock.Lock()
defer pluginLock.Unlock()
hooks[g.pluginId][hook] = handler
return nil
}
// ExitWaitGroup implements api.Gateway.
func (g *Gateway) ExitWaitGroup() *sync.WaitGroup {
return &exitWaitGroup
}
// TestOp implements api.Gateway.
func (g *Gateway) TestOp() {
panic("unimplemented")
}
func Handler1[T1, R any](t1 T1) func(func(T1) R) R {
return func(acceptor func(T1) R) R {
return acceptor(t1)
}
}
func Handler2[T1, T2, R any](t1 T1, t2 T2) func(func(T1, T2) R) R {
return func(acceptor func(T1, T2) R) R {
return acceptor(t1, t2)
}
}
func Handler1R2[T1, R1, R2 any](t1 T1) func(func(T1) (R1, R2)) (R1, R2) {
return func(acceptor func(T1) (R1, R2)) (R1, R2) {
return acceptor(t1)
}
}
func invokeFirstHookHandler[Acceptor, Handler any](
hook api.HookType[Acceptor, Handler],
acceptor func(Acceptor) bool,
handelr func(Handler) error,
) (bool, error) {
pluginLock.RLock()
defer pluginLock.RUnlock()
for _, handlers := range hooks {
if handler, ok := handlers[hook.Key()].(api.HookHandler[Acceptor, Handler]); ok && acceptor(handler.Acceptor()) {
if err := handelr(handler.Handler()); err != nil {
return true, err
}
return true, nil
}
}
return false, nil
}
func invokeAllHookHandler[Acceptor, Handler any](
hook api.HookType[Acceptor, Handler],
acceptor func(Acceptor) bool,
handelr func(Handler) error,
) error {
pluginLock.RLock()
defer pluginLock.RUnlock()
for _, handlers := range hooks {
if handler, ok := handlers[hook.Key()].(api.HookHandler[Acceptor, Handler]); ok && acceptor(handler.Acceptor()) {
if err := handelr(handler.Handler()); err != nil {
return err
}
}
}
return nil
}