Files
mc-gateway/cmd/gateway/main.go
tursom ae8706f8c8
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 / merge-artifacts (push) Has been cancelled
Docker Image / docker (push) Has been cancelled
feat(plugin): add protocol proxy mode
2026-06-26 10:13:00 +08:00

267 lines
6.2 KiB
Go

package main
import (
"context"
"crypto/rand"
"encoding/hex"
"errors"
"net"
"os"
"sync"
"github.com/rs/zerolog/log"
"github.com/tursom/mc-gateway/internal/upstreamtarget"
"github.com/tursom/mc-gateway/plugin/api"
"github.com/tursom/mc-gateway/protocol"
)
func main() {
if handled, code := runPluginCLI(os.Args[1:]); handled {
os.Exit(code)
}
if err := loadConfig(); err != nil {
panic(err)
}
if err := writePIDFile(); err != nil {
log.Err(err).Msg("Failed to write PID file")
}
defer removePIDFile()
defer closeGatewayRuntime()
go handleLogRotate()
defer exitWaitGroup.Wait()
startEnabledServices()
}
func startEnabledServices() {
startService(runTcpWebPortReuse)
if config.Kcp.Enable {
startService(runKcp)
}
if config.Quic.Enable {
startService(runQuic)
}
if config.WebSocket.Enable && normalizedWebSocketPort() != normalizedTCPPort() {
startService(runWebSocket)
}
}
func startService(run func(wg *sync.WaitGroup)) {
exitWaitGroup.Add(1)
go run(&exitWaitGroup)
}
func handleRequest(conn net.Conn) {
gatewayMetrics.ConnectionStarted()
defer gatewayMetrics.ConnectionFinished()
defer func() {
rec := recover()
if rec == nil {
return
}
if err, ok := rec.(error); ok {
log.Err(err).
Str("client", conn.RemoteAddr().String()).
Msg("Panic on handle request")
} else {
log.Error().Any("err", rec).
Str("client", conn.RemoteAddr().String()).
Msg("Panic on handle request")
}
}()
// 确保连接关闭
defer conn.Close()
client := mapToHost(conn)
if client == nil {
return
}
defer client.Close()
proxyConnections(conn, client)
}
func mapToHost(conn net.Conn) net.Conn {
buf := getProxyBuffer()
defer putProxyBuffer(buf)
n, err := conn.Read(buf)
if err != nil {
log.Err(err).
Str("client", conn.RemoteAddr().String()).
Msg("failed to reading hostname")
return nil
}
if n == 0 {
log.Err(errEmptyBuffer).
Str("client", conn.RemoteAddr().String()).
Msg("buffer is empty")
return nil
}
initialData := append([]byte(nil), buf[:n]...)
handshake := protocol.ParseHandshake(initialData)
if handshake.ServerHost == "" {
log.Err(errEmptyBuffer).
Str("client", conn.RemoteAddr().String()).
Msg("failed to parse mc host from buffer")
return nil
}
host, ok := lookupRoute(handshake.ServerHost)
if host == "" {
gatewayMetrics.RouteMiss()
log.Err(errEmptyBuffer).
Str("client", conn.RemoteAddr().String()).
Str("host", handshake.ServerHost).
Msg("failed to route host")
return nil
}
if ok {
gatewayMetrics.RouteHit(handshake.ServerHost)
}
log.Debug().
Str("client", conn.RemoteAddr().String()).
Str("host", handshake.ServerHost).
Str("mc", host).
Msg("map to host")
var client net.Conn
if pluginsManager != nil {
req := newUpstreamConnectRequest(conn, host, handshake, initialData, ok)
result, err := pluginsManager.ConnectUpstream(context.Background(), req)
if err != nil {
if errors.Is(err, api.ErrBlocked) {
log.Info().
Str("client", conn.RemoteAddr().String()).
Str("host", handshake.ServerHost).
Msg("managed upstream plugin blocked connection")
return nil
}
log.Err(err).
Str("client", conn.RemoteAddr().String()).
Str("host", handshake.ServerHost).
Str("mc", host).
Msg("failed to invoke managed upstream plugin")
return nil
}
if result.Handled {
if result.Proxied {
return nil
}
client = result.Conn
}
}
if client == nil {
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 client == nil {
target := upstreamtarget.Parse(host)
switch target.Protocol {
case upstreamtarget.ProtocolQUIC:
client = upstreamQuic(target.Address)
case upstreamtarget.ProtocolKCP:
client = upstreamKcp(target.Address)
case upstreamtarget.ProtocolHAProxy:
client = haProxyUpstream(conn, target.Address)
default:
client = upstreamTcp(target.Address)
}
}
if client == nil {
return nil
}
if err := writeAll(client, initialData); err != nil {
log.Err(err).
Str("client", conn.RemoteAddr().String()).
Str("host", handshake.ServerHost).
Str("mc", host).
Msg("failed to write initial packet to upstream")
client.Close()
return nil
}
return client
}
func newUpstreamConnectRequest(conn net.Conn, upstream string, handshake protocol.Handshake, initialData []byte, routeHit bool) api.UpstreamConnectRequest {
target := upstreamtarget.Parse(upstream)
transport, serviceName, listenerPort := connectionIngress(conn)
req := api.UpstreamConnectRequest{
Source: conn,
Host: handshake.ServerHost,
Upstream: upstream,
InitialData: append([]byte(nil), initialData...),
Metadata: map[string]string{"route_hit": boolString(routeHit)},
ConnectionID: randomHexID(8),
TraceID: randomHexID(16),
SourceAddr: conn.RemoteAddr().String(),
ServerHost: handshake.ServerHost,
RawServerHost: handshake.RawServerHost,
ProtocolVersion: handshake.ProtocolVersion,
NextState: handshake.NextState,
RouteID: handshake.ServerHost,
RouteTags: []string{},
UpstreamRaw: upstream,
UpstreamProtocol: string(target.Protocol),
UpstreamAddress: target.Address,
Transport: transport,
ServiceName: serviceName,
ListenerPort: listenerPort,
}
return req
}
func connectionIngress(conn net.Conn) (transport string, serviceName string, listenerPort int) {
transport = "tcp"
serviceName = serviceNameTCPAdmin
switch conn.(type) {
case *webSocketConn:
transport = "websocket"
serviceName = serviceNameWebSocket
case quicConn:
transport = "quic"
serviceName = serviceNameQUIC
}
if addr, ok := conn.LocalAddr().(*net.TCPAddr); ok {
listenerPort = addr.Port
}
return transport, serviceName, listenerPort
}
func boolString(value bool) string {
if value {
return "true"
}
return "false"
}
func randomHexID(size int) string {
buf := make([]byte, size)
if _, err := rand.Read(buf); err != nil {
return hex.EncodeToString([]byte("fallback"))
}
return hex.EncodeToString(buf)
}