Files
mc-gateway/examples/plugins/mc-auth-proxy/main.go
tursom f508ecc1b9
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
Docker Image / docker (push) Has been cancelled
Go / merge-artifacts (push) Has been cancelled
docs: 补充中文代码注释
2026-06-27 20:15:29 +08:00

223 lines
5.4 KiB
Go

// examples/plugins/mc-auth-proxy/main.go 演示托管插件如何拦截登录流量、发出认证事件并按条件拒绝客户端。
package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net"
"time"
"github.com/tursom/mc-gateway/plugin/api"
"github.com/tursom/mc-gateway/protocol"
)
type PluginImpl struct {
api.AbstractPlugin
config Config
gateway api.Gateway
}
type Config struct {
MatchHost string `json:"match_host"`
FixtureAccept bool `json:"fixture_accept"`
DisconnectMessage string `json:"disconnect_message"`
Backend string `json:"backend"`
}
type loginStart struct {
Username string
}
func Plugin() api.Plugin {
return &PluginImpl{}
}
func (p *PluginImpl) NewConfigObj() any {
return &Config{}
}
func (p *PluginImpl) ReloadConfig(config any) error {
if cfg, ok := config.(*Config); ok {
p.config = *cfg
}
if p.config.DisconnectMessage == "" {
p.config.DisconnectMessage = "Authentication fixture rejected the login"
}
return nil
}
func (p *PluginImpl) Init(gateway api.Gateway) error {
p.gateway = gateway
return api.RegisterHookHandler(
gateway,
api.HookUpstreamConnect,
func(req api.UpstreamConnectRequest) bool {
return p.config.MatchHost == "" || req.Host == p.config.MatchHost
},
func(req api.UpstreamConnectRequest) (net.Conn, error) {
if p.config.MatchHost != "" && req.Host != p.config.MatchHost {
return nil, api.ErrPass
}
gatewayEnd, pluginEnd := net.Pipe()
go p.handleConn(req, pluginEnd)
return gatewayEnd, nil
},
)
}
func (p *PluginImpl) handleConn(req api.UpstreamConnectRequest, conn net.Conn) {
defer conn.Close()
_ = conn.SetDeadline(time.Now().Add(10 * time.Second))
handshakePacket, err := readPacketFromConn(conn)
if err != nil {
return
}
handshake := protocol.ParseHandshake(handshakePacket)
if handshake.ServerHost == "" || handshake.NextState != 2 {
p.emitAuthEvent(req.Context, "auth.failure", "bad_handshake")
_ = writeLoginDisconnect(conn, "Unsupported Minecraft handshake")
return
}
loginPacket, err := readPacketFromConn(conn)
if err != nil {
_ = writeLoginDisconnect(conn, p.config.DisconnectMessage)
return
}
login, err := parseLoginStart(loginPacket)
if err != nil || login.Username == "" {
p.emitAuthEvent(req.Context, "auth.failure", "bad_login_start")
_ = writeLoginDisconnect(conn, p.config.DisconnectMessage)
return
}
if !p.config.FixtureAccept {
p.emitAuthEvent(req.Context, "auth.failure", "fixture_reject")
_ = writeLoginDisconnect(conn, p.config.DisconnectMessage)
return
}
if p.config.Backend == "" {
p.emitAuthEvent(req.Context, "auth.failure", "backend_missing")
_ = writeLoginDisconnect(conn, "Fixture accepted but no backend is configured")
return
}
backend, err := p.gateway.ExternalClient("backend").DialTCP(req.Context, p.config.Backend, 3*time.Second)
if err != nil {
p.emitAuthEvent(req.Context, "auth.failure", "backend_unavailable")
_ = writeLoginDisconnect(conn, "Backend unavailable")
return
}
p.emitAuthEvent(req.Context, "auth.success", "fixture_accept")
defer backend.Close()
_, _ = backend.Write(handshakePacket)
_, _ = backend.Write(loginPacket)
copyBoth(conn, backend)
_ = req
}
func (p *PluginImpl) emitAuthEvent(ctx context.Context, name, result string) {
if p.gateway == nil {
return
}
_ = p.gateway.EmitEvent(ctx, name, map[string]string{
"result": result,
"mode": "fixture",
})
p.gateway.Logger().Info(ctx, name, map[string]string{"result": result})
}
func readPacketFromConn(conn net.Conn) ([]byte, error) {
length, err := readVarIntFromConn(conn)
if err != nil {
return nil, err
}
if length <= 0 || length > 2*1024*1024 {
return nil, fmt.Errorf("invalid packet length %d", length)
}
packet := make([]byte, length)
if _, err := io.ReadFull(conn, packet); err != nil {
return nil, err
}
out := append(encodeVarInt(length), packet...)
return out, nil
}
func parseLoginStart(packet []byte) (loginStart, error) {
payload, _, err := protocol.ReadPacket(packet)
if err != nil {
return loginStart{}, err
}
packetID, n, err := protocol.ReadVarInt(payload)
if err != nil {
return loginStart{}, err
}
if packetID != 0 {
return loginStart{}, errors.New("not a login start packet")
}
username, _, err := protocol.ReadString(payload[n:])
if err != nil {
return loginStart{}, err
}
return loginStart{Username: username}, nil
}
func writeLoginDisconnect(conn net.Conn, message string) error {
payload := []byte{0x00}
text, _ := json.Marshal(map[string]any{"text": message})
payload = append(payload, encodeVarInt(len(text))...)
payload = append(payload, text...)
packet := append(encodeVarInt(len(payload)), payload...)
_, err := conn.Write(packet)
return err
}
func readVarIntFromConn(r io.Reader) (int, error) {
var value int
var one [1]byte
for i := 0; i < 5; i++ {
if _, err := io.ReadFull(r, one[:]); err != nil {
return 0, err
}
b := one[0]
value |= int(b&0x7f) << (7 * i)
if b&0x80 == 0 {
return value, nil
}
}
return 0, errors.New("varint too long")
}
func encodeVarInt(value int) []byte {
var out []byte
for {
b := byte(value & 0x7f)
value >>= 7
if value != 0 {
b |= 0x80
}
out = append(out, b)
if value == 0 {
return out
}
}
}
func copyBoth(a, b net.Conn) {
done := make(chan struct{}, 2)
go func() {
_, _ = io.Copy(a, b)
done <- struct{}{}
}()
go func() {
_, _ = io.Copy(b, a)
done <- struct{}{}
}()
<-done
}