diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..8b48cc6 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,214 @@ +# This workflow will build a golang project +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go + +name: Go + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +jobs: + + build: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + # Linux builds + - goos: linux + goarch: amd64 + name: linux-amd64 + - goos: linux + goarch: arm64 + name: linux-arm64 + - goos: linux + goarch: arm + goarm: 7 + name: linux-armv7 + - goos: linux + goarch: arm + goarm: 6 + name: linux-armv6 + - goos: linux + goarch: 386 + name: linux-386 + - goos: linux + goarch: loong64 + name: linux-loong64 + - goos: linux + goarch: mips + name: linux-mips + - goos: linux + goarch: mips64 + name: linux-mips64 + - goos: linux + goarch: mips64le + name: linux-mips64le + - goos: linux + goarch: mipsle + name: linux-mipsle + - goos: linux + goarch: ppc64 + name: linux-ppc64 + - goos: linux + goarch: ppc64le + name: linux-ppc64le + - goos: linux + goarch: riscv64 + name: linux-riscv64 + - goos: linux + goarch: s390x + name: linux-s390x + + # Windows builds + - goos: windows + goarch: amd64 + name: windows-amd64 + ext: .exe + - goos: windows + goarch: arm64 + name: windows-arm64 + ext: .exe + - goos: windows + goarch: 386 + name: windows-386 + ext: .exe + + # macOS builds + - goos: darwin + goarch: amd64 + name: darwin-amd64 + - goos: darwin + goarch: arm64 + name: darwin-arm64 + + # FreeBSD builds + - goos: freebsd + goarch: amd64 + name: freebsd-amd64 + - goos: freebsd + goarch: arm64 + name: freebsd-arm64 + - goos: freebsd + goarch: 386 + name: freebsd-386 + - goos: freebsd + goarch: arm + name: freebsd-arm + - goos: freebsd + goarch: riscv64 + name: freebsd-riscv64 + + - goos: aix + goarch: ppc64 + name: aix-ppc64 + - goos: dragonfly + goarch: amd64 + name: dragonfly-amd64 + - goos: illumos + goarch: amd64 + name: illumos-amd64 + - goos: netbsd + goarch: 386 + name: netbsd-386 + - goos: netbsd + goarch: amd64 + name: netbsd-amd64 + - goos: netbsd + goarch: arm + name: netbsd-arm + - goos: netbsd + goarch: arm64 + name: netbsd-arm64 + - goos: openbsd + goarch: 386 + name: openbsd-386 + - goos: openbsd + goarch: amd64 + name: openbsd-amd64 + - goos: openbsd + goarch: arm + name: openbsd-arm + - goos: openbsd + goarch: arm64 + name: openbsd-arm64 + - goos: openbsd + goarch: ppc64 + name: openbsd-ppc64 + - goos: openbsd + goarch: riscv64 + name: openbsd-riscv64 + - goos: plan9 + goarch: 386 + name: plan9-386 + - goos: plan9 + goarch: amd64 + name: plan9-amd64 + - goos: plan9 + goarch: arm + name: plan9-arm + - goos: solaris + goarch: amd64 + name: solaris-amd64 + + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.23.1' + + - name: Build Gateway + run: go build -ldflags="-s -w" -o gateway-${{ matrix.name }}${{ matrix.ext }} ./cmd/gateway + env: + GOOS: ${{ matrix.goos }} + GOARCH: ${{ matrix.goarch }} + GOARM: ${{ matrix.goarm }} + + - name: Build KCP + run: go build -ldflags="-s -w" -o kcp-${{ matrix.name }}${{ matrix.ext }} ./cmd/kcp + env: + GOOS: ${{ matrix.goos }} + GOARCH: ${{ matrix.goarch }} + GOARM: ${{ matrix.goarm }} + + - name: Build QUIC + run: go build -ldflags="-s -w" -o quic-${{ matrix.name }}${{ matrix.ext }} ./cmd/quic + env: + GOOS: ${{ matrix.goos }} + GOARCH: ${{ matrix.goarch }} + GOARM: ${{ matrix.goarm }} + + - name: Upload artifacts artifacts + uses: actions/upload-artifact@v4 + with: + name: binaries-${{ matrix.name }} + path: | + gateway-${{ matrix.name }}${{ matrix.ext }} + kcp-${{ matrix.name }}${{ matrix.ext }} + quic-${{ matrix.name }}${{ matrix.ext }} + + # 合并所有构建产物 + merge-artifacts: + needs: build + runs-on: ubuntu-latest + steps: + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: ./artifacts + merge-multiple: true # 合并所有 artifacts + + - name: Display structure + run: ls -la ./artifacts + + - name: Upload merged artifacts + uses: actions/upload-artifact@v4 + with: + name: mc-gateway-all-platforms + path: ./artifacts/* + retention-days: 30 \ No newline at end of file diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml deleted file mode 100644 index b01a093..0000000 --- a/.github/workflows/go.yml +++ /dev/null @@ -1,28 +0,0 @@ -# This workflow will build a golang project -# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go - -name: Go - -on: - push: - branches: [ "master" ] - pull_request: - branches: [ "master" ] - -jobs: - - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Set up Go - uses: actions/setup-go@v4 - with: - go-version: '1.23.1' - - - name: Build - run: go build -ldflags="-s -w" -v ./... - - - name: Test - run: go test -v ./... diff --git a/.gitignore b/.gitignore index db565b5..4437391 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .vscode config.json +config.toml /gateway /logs/ diff --git a/README.md b/README.md index 53e56d3..8c97f80 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,76 @@ # mc-gateway -This project is a Minecraft game server gateway. It allows you to access multiple game servers through different hostnames, using the same port. + +一个简易的 Minecraft 网关,通过 host 将客户端的流量转发到对应的后端 Minecraft 服务器。 + +## 配置 + +目前 mc-gateway 只支持读取当前目录的 `config.toml` 作为配置。在 `config.toml` 被修改时,可以自动加载并更新部分配置,以达到不停机修改配置的效果。支持热加载的配置有: + +- hosts +- log +- KCP 的 data_shards 和 parity_Shards +- QUIC 的 application_protocols + +### hosts + +hosts 使用期望的 host 做 key,转发的目的地址为 value。参考`config.example.toml`。 + +### log + +| 配置 | 类型 | 备注 | +| ----- | ------ | -------- | +| level | Level | 日志等级 | +| file | string | 日志文件 | + +> 日志适配 logrotate,可以使用 logrotate 进行日志分片、压缩等日常运维操作,参考配置: + +```logrotate +/var/log/mc-gateway.log { + copytruncate + daily + missingok + rotate 14 + compress + compresscmd /usr/bin/zstd + compressext .zst + compressoptions -T0 --long + uncompresscmd /usr/bin/unzstd + notifempty + delaycompress + dateext + postrotate + # 向程序发送 SIGHUP 信号 + # mc-gateway 默认会将当前进程的 pid 写入 /var/run/mc-gateway.pid + if [ -f /var/run/mc-gateway.pid ]; then + kill -SIGHUP $(cat /var/run/mc-gateway.pid) + fi + endscript +} +``` + +### TCP + +| 配置 | 类型 | 备注 | +| ------ | ---- | -------- | +| enable | bool | 是否启用 | +| port | int | 端口 | + +### KCP + +| 配置 | 类型 | 备注 | +| ------------- | ---- | -------- | +| enable | bool | 是否启用 | +| port | int | 端口 | +| data_shards | int | 数据分片 | +| parity_Shards | int | 校验分片 | + +### QUIC + +| 配置 | 类型 | 备注 | +| --------------------- | -------- | ------------ | +| enable | bool | 是否启用 | +| port | int | 端口 | +| application_protocols | []string | 应用协议列表 | + +> application_protocols 只要客户端与服务端有一个能够对应上就可以成功连接 +> 默认值为 ["minecraft", "quic", "raw", "h3"] diff --git a/cmd/gateway/config.go b/cmd/gateway/config.go new file mode 100644 index 0000000..af7d6ec --- /dev/null +++ b/cmd/gateway/config.go @@ -0,0 +1,123 @@ +package main + +import ( + "io" + "os" + "strings" + "sync" + "time" + + "github.com/BurntSushi/toml" + "github.com/fsnotify/fsnotify" + "github.com/rs/zerolog/log" +) + +var ( + configFile = "config.toml" + + config Config + currentLogFile string + configLoadLock sync.Mutex +) + +type ( + Config struct { + Tcp ProtocolConfig `toml:"tcp"` + Quic QuicConfig `toml:"quic"` + Kcp KcpConfig `toml:"kcp"` + Hosts map[string]string `toml:"hosts"` + Log LogConfig `toml:"log"` + } + + ProtocolConfig struct { + Enable bool `toml:"enable"` + Port int `toml:"port"` + } + + KcpConfig struct { + Enable bool `toml:"enable"` + Port int `toml:"port"` + DataShards int `toml:"data_shards"` + ParityShards int `toml:"parity_Shards"` + } + + QuicConfig struct { + Enable bool `toml:"enable"` + Port int `toml:"port"` + ApplicionProtocols []string `toml:"application_protocols"` + } + + LogConfig struct { + Level string `toml:"level"` + File string `toml:"file"` + } +) + +func loadConfig() error { + configLoadLock.Lock() + defer configLoadLock.Unlock() + + file, err := os.Open(configFile) + if err != nil { + return err + } + defer file.Close() + + byteValue, err := io.ReadAll(file) + if err != nil { + return err + } + + if err := toml.Unmarshal(byteValue, &config); err != nil { + return err + } + + return loadLogger() +} + +func watchConfig() *fsnotify.Watcher { + go func() { + for { + time.Sleep(time.Minute) + if err := loadConfig(); err != nil { + log.Error().Err(err).Msg("Failed to reload config") + } + } + }() + + watcher, err := fsnotify.NewWatcher() + if err != nil { + log.Fatal().Err(err).Msg("Failed to create config watcher") + } + go func() { + for { + select { + case event, ok := <-watcher.Events: + if !ok { + log.Error().Msg("watcher.Events channel closed") + return + } + if !strings.HasSuffix(event.Name, configFile) { + continue + } + case err, ok := <-watcher.Errors: + if !ok { + log.Error().Msg("watcher.Errors channel closed") + return + } + log.Error().Err(err).Msg("watcher error") + continue + } + + log.Info().Msg("reload config") + if err := loadConfig(); err != nil { + log.Error().Err(err).Msg("Failed to reload config") + } + } + }() + if err = watcher.Add("."); err != nil { + log.Fatal().Err(err).Msg("Failed to watch config file") + } + + return watcher +} diff --git a/cmd/gateway/kcp.go b/cmd/gateway/kcp.go new file mode 100644 index 0000000..8b76bd6 --- /dev/null +++ b/cmd/gateway/kcp.go @@ -0,0 +1,41 @@ +package main + +import ( + "fmt" + "sync" + + "github.com/rs/zerolog/log" + "github.com/xtaci/kcp-go" +) + +func runKcp(wg *sync.WaitGroup) { + if wg != nil { + defer wg.Done() + } + + listener, err := kcp.ListenWithOptions(fmt.Sprintf(":%d", config.Kcp.Port), nil, config.Kcp.DataShards, config.Kcp.ParityShards) + if err != nil { + log.Fatal().Err(err). + Int("port", config.Kcp.Port). + Msg("Failed to listen on KCP port") + } + defer listener.Close() + + log.Info().Int("port", config.Kcp.Port).Msg("KCP server is listening") + + for { + conn, err := listener.AcceptKCP() + if err != nil { + log.Err(err). + Msg("Failed to accept KCP connection") + continue + } + log.Info(). + Str("remote_addr", conn.RemoteAddr().String()). + Msg("Accepted KCP connection") + + conn.SetACKNoDelay(true) + + go handleRequest(conn) + } +} diff --git a/cmd/gateway/log.go b/cmd/gateway/log.go new file mode 100644 index 0000000..490fd10 --- /dev/null +++ b/cmd/gateway/log.go @@ -0,0 +1,70 @@ +package main + +import ( + "io" + "os" + "os/signal" + "path/filepath" + "syscall" + + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" +) + +func loadLogger() error { + level, err := zerolog.ParseLevel(config.Log.Level) + if err != nil { + return err + } + + log.Logger = log.Logger.Level(level) + + if config.Log.File != "" && currentLogFile != config.Log.File { + if err := os.MkdirAll(filepath.Dir(config.Log.File), 0755); err != nil { + return err + } + + logFile, err := os.OpenFile(config.Log.File, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) + if err != nil { + return err + } + + log.Logger = log.Logger.Output(io.MultiWriter(os.Stdout, logFile)) + + currentLogFile = config.Log.File + } + + return nil +} + +func handleLogRotate() { + signalChan := make(chan os.Signal, 1) + signal.Notify(signalChan, syscall.SIGHUP) + + for range signalChan { + log.Info().Msg("Received SIGHUP, reopening log file") + if err := reopenLogFile(); err != nil { + log.Error().Err(err).Msg("Failed to reopen log file") + } + } +} + +func reopenLogFile() error { + configLoadLock.Lock() + defer configLoadLock.Unlock() + + if config.Log.File == "" { + return nil + } + + // 重新打开日志文件 + logFile, err := os.OpenFile(config.Log.File, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) + if err != nil { + return err + } + + log.Logger = log.Logger.Output(io.MultiWriter(os.Stdout, logFile)) + currentLogFile = config.Log.File + + return nil +} diff --git a/cmd/gateway/main.go b/cmd/gateway/main.go index ecc3f35..8adb330 100644 --- a/cmd/gateway/main.go +++ b/cmd/gateway/main.go @@ -1,142 +1,16 @@ package main import ( - "encoding/json" "fmt" "io" "net" - "os" - "os/signal" - "path/filepath" - "strings" "sync" - "syscall" "time" - "github.com/fsnotify/fsnotify" - "github.com/rs/zerolog" "github.com/rs/zerolog/log" + "github.com/tursom/mc-gateway/protocol" ) -type ( - Config struct { - Port int `json:"port"` - Tcp bool `json:"tcp"` - Quic bool `json:"quic"` - Hosts map[string]string `json:"hosts"` - Default string `json:"default"` - Log LogConfig `json:"log"` - } - - LogConfig struct { - Level string `json:"level"` - File string `json:"file"` - } -) - -var ( - configFile = "config.json" - - config Config - currentLogFile string - configLoadLock sync.Mutex -) - -func loadConfig() error { - configLoadLock.Lock() - defer configLoadLock.Unlock() - - file, err := os.Open(configFile) - if err != nil { - return err - } - defer file.Close() - - byteValue, err := io.ReadAll(file) - if err != nil { - return err - } - - if err := json.Unmarshal(byteValue, &config); err != nil { - return err - } - - return loadLogger() -} - -func loadLogger() error { - level, err := zerolog.ParseLevel(config.Log.Level) - if err != nil { - return err - } - - log.Logger = log.Logger.Level(level) - - if config.Log.File != "" && currentLogFile != config.Log.File { - if err := os.MkdirAll(filepath.Dir(config.Log.File), 0755); err != nil { - return err - } - - logFile, err := os.OpenFile(config.Log.File, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) - if err != nil { - return err - } - - log.Logger = log.Logger.Output(io.MultiWriter(os.Stdout, logFile)) - - currentLogFile = config.Log.File - } - - return nil -} - -func watchConfig() *fsnotify.Watcher { - go func() { - for { - time.Sleep(time.Minute) - if err := loadConfig(); err != nil { - log.Error().Err(err).Msg("Failed to reload config") - } - } - }() - - watcher, err := fsnotify.NewWatcher() - if err != nil { - log.Fatal().Err(err).Msg("Failed to create config watcher") - } - go func() { - for { - select { - case event, ok := <-watcher.Events: - if !ok { - log.Error().Msg("watcher.Events channel closed") - return - } - if !strings.HasSuffix(event.Name, configFile) { - continue - } - case err, ok := <-watcher.Errors: - if !ok { - log.Error().Msg("watcher.Errors channel closed") - return - } - log.Error().Err(err).Msg("watcher error") - continue - } - - log.Info().Msg("reload config") - if err := loadConfig(); err != nil { - log.Error().Err(err).Msg("Failed to reload config") - } - } - }() - if err = watcher.Add("."); err != nil { - log.Fatal().Err(err).Msg("Failed to watch config file") - } - - return watcher -} - func main() { if err := writePIDFile(); err != nil { panic(fmt.Sprintf("Failed to write PID file: %v", err)) @@ -156,13 +30,18 @@ func main() { defer wg.Wait() // 启动QUIC服务 - if config.Quic { + if config.Quic.Enable { wg.Add(1) go runQuic(&wg) } + if config.Kcp.Enable { + wg.Add(1) + go runKcp(&wg) + } + // 监听TCP端口 - if config.Tcp { + if config.Tcp.Enable { wg.Add(1) go runTcp(&wg) } @@ -173,15 +52,15 @@ func runTcp(wg *sync.WaitGroup) { defer wg.Done() } - listener, err := net.Listen("tcp", fmt.Sprintf(":%d", config.Port)) + listener, err := net.Listen("tcp", fmt.Sprintf(":%d", config.Tcp.Port)) if err != nil { log.Fatal().Err(err). - Int("port", config.Port). + Int("port", config.Tcp.Port). Msg("Failed to listen on port") } defer listener.Close() log.Info(). - Int("port", config.Port). + Int("port", config.Tcp.Port). Msg("Listening for TCP connections") for { @@ -233,10 +112,10 @@ func handleRequest(conn net.Conn) { Msg("Error: buffer is empty") return } - mc_host := getMcHost(buf[:n]) + mc_host := protocol.GetMcHost(buf[:n]) host, ok := config.Hosts[mc_host] if !ok { - host = config.Default + host = config.Hosts["default"] } if host == "" { log.Err(errEmptyBuffer). @@ -248,6 +127,7 @@ func handleRequest(conn net.Conn) { log.Info(). Str("client", conn.RemoteAddr().String()). + Str("host", mc_host). Str("mc", host). Msg("map to host") @@ -257,6 +137,7 @@ func handleRequest(conn net.Conn) { return } defer client.Close() + setSocketOptions(client) client.Write(buf[:n]) // 不需要 buf 了,释放掉 @@ -295,26 +176,6 @@ func handleWrite(srv, cli net.Conn, wg *sync.WaitGroup) { } } -func getMcHost(buf []byte) string { - if len(buf) < 5 { - return "" - } - - buf = buf[4:] - host_len := buf[0] - if len(buf) < int(host_len)+1 { - return "" - } - - host := string(buf[1 : host_len+1]) - - if spliterIndex := strings.IndexRune(host, 0); spliterIndex != -1 { - return host[0:spliterIndex] - } else { - return host - } -} - func setSocketOptions(conn net.Conn) { if tcpConn, ok := conn.(*net.TCPConn); ok { tcpConn.SetNoDelay(true) // 禁用 Nagle 算法 @@ -322,35 +183,3 @@ func setSocketOptions(conn net.Conn) { tcpConn.SetKeepAlivePeriod(30 * time.Second) } } - -func handleLogRotate() { - signalChan := make(chan os.Signal, 1) - signal.Notify(signalChan, syscall.SIGHUP) - - for range signalChan { - log.Info().Msg("Received SIGHUP, reopening log file") - if err := reopenLogFile(); err != nil { - log.Error().Err(err).Msg("Failed to reopen log file") - } - } -} - -func reopenLogFile() error { - configLoadLock.Lock() - defer configLoadLock.Unlock() - - if config.Log.File == "" { - return nil - } - - // 重新打开日志文件 - logFile, err := os.OpenFile(config.Log.File, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) - if err != nil { - return err - } - - log.Logger = log.Logger.Output(io.MultiWriter(os.Stdout, logFile)) - currentLogFile = config.Log.File - - return nil -} diff --git a/cmd/gateway/pid_linux.go b/cmd/gateway/pid_unix.go similarity index 85% rename from cmd/gateway/pid_linux.go rename to cmd/gateway/pid_unix.go index ac34643..8cb8da7 100644 --- a/cmd/gateway/pid_linux.go +++ b/cmd/gateway/pid_unix.go @@ -1,3 +1,6 @@ +// pid_unix.go +//go:build unix || plan9 + package main import ( diff --git a/cmd/gateway/pid_windows.go b/cmd/gateway/pid_windows.go index 072d2c0..bc4f83e 100644 --- a/cmd/gateway/pid_windows.go +++ b/cmd/gateway/pid_windows.go @@ -1,3 +1,6 @@ +// pid_windows.go +//go:build windows + package main func writePIDFile() error { diff --git a/cmd/gateway/quic.go b/cmd/gateway/quic.go index 366beef..774e579 100644 --- a/cmd/gateway/quic.go +++ b/cmd/gateway/quic.go @@ -28,7 +28,7 @@ func runQuic(wg *sync.WaitGroup) { defer wg.Done() } - udpConn, err := net.ListenUDP("udp4", &net.UDPAddr{Port: config.Port}) + udpConn, err := net.ListenUDP("udp4", &net.UDPAddr{Port: config.Quic.Port}) if err != nil { log.Panic().Err(err).Msg("Failed to listen UDP") } @@ -43,7 +43,7 @@ func runQuic(wg *sync.WaitGroup) { if err != nil { log.Panic().Err(err).Msg("Failed to listen QUIC") } - log.Info().Int("port", config.Port).Msg("Listening for QUIC connections") + log.Info().Int("port", config.Quic.Port).Msg("Listening for QUIC connections") for { conn, err := ln.Accept(context.Background()) @@ -52,6 +52,9 @@ func runQuic(wg *sync.WaitGroup) { continue } + log.Info(). + Str("client", conn.RemoteAddr().String()). + Msg("Accepted QUIC connection") go handleQuicRequest(conn) } } @@ -115,8 +118,14 @@ func generateTLSConfig() (*tls.Config, error) { return nil, err } + nextProtos := config.Quic.ApplicionProtocols + if len(nextProtos) == 0 { + nextProtos = []string{"minecraft", "quic", "raw", "h3"} // 默认协议 + } + // 返回 tls.Config return &tls.Config{ Certificates: []tls.Certificate{cert}, + NextProtos: nextProtos, }, nil } diff --git a/cmd/kcp/main.go b/cmd/kcp/main.go new file mode 100644 index 0000000..eb89189 --- /dev/null +++ b/cmd/kcp/main.go @@ -0,0 +1,123 @@ +package main + +import ( + "errors" + "fmt" + "io" + "net" + "os" + "sync" + "time" + + "github.com/rs/zerolog/log" + "github.com/tursom/mc-gateway/protocol" + "github.com/xtaci/kcp-go" +) + +var ( + localPort = 25565 + + mcHost = "bh.mc.tursom.cn" + mcPort = 25566 +) + +func main() { + if len(os.Args) > 1 { + mcHost = os.Args[1] + } + + // 监听TCP端口 + listener, err := net.Listen("tcp", fmt.Sprintf(":%d", localPort)) + if err != nil { + log.Fatal().Err(err). + Int("port", localPort). + Msg("Failed to listen on port") + } + defer listener.Close() + log.Info(). + Int("port", localPort). + Msg("Listening for TCP connections") + + for { + // 接受传入的连接 + conn, err := listener.Accept() + if err != nil { + log.Err(err).Msg("Error accepting") + continue + } + log.Info(). + Str("client", conn.RemoteAddr().String()). + Int("port", localPort). + Msg("Accepted connection") + // 处理连接 + go handlerConn(conn) + } +} + +func handlerConn(client net.Conn) { + defer client.Close() + setSocketOptions(client) + + conn, err := kcp.DialWithOptions(fmt.Sprintf("%s:%d", mcHost, mcPort), nil, 10, 5) + if err != nil { + log.Error().Err(err). + Msg("Failed to dial KCP server") + } + defer conn.Close() + + conn.SetACKNoDelay(true) + + buf := make([]byte, 1024) + n, err := client.Read(buf) + if err != nil { + log.Err(err). + Str("client", client.RemoteAddr().String()). + Msg("Error reading hostname") + return + } + if n == 0 { + log.Err(errors.New("empty buffer")). + Str("client", client.RemoteAddr().String()). + Msg("Error: buffer is empty") + return + } + buf = buf[:n] + + buf = protocol.ReplaceMcHost(buf, mcHost) + _, err = conn.Write(buf) // 写入数据到 QUIC 流 + if err != nil { + log.Err(err). + Str("client", client.RemoteAddr().String()). + Str("host", mcHost). + Int("port", mcPort). + Msg("Error writing to QUIC stream") + return + } + // 释放 buf + buf = nil + + var wg sync.WaitGroup + wg.Add(1) + go copyData(conn, client, &wg) + copyData(client, conn, nil) + wg.Wait() +} + +func copyData(src io.Reader, dst io.Writer, wg *sync.WaitGroup) { + if wg != nil { + defer wg.Done() + } + + _, err := io.Copy(dst, src) + if err != nil && err != io.EOF { + log.Err(err).Msg("Error copying data") + } +} + +func setSocketOptions(conn net.Conn) { + if tcpConn, ok := conn.(*net.TCPConn); ok { + tcpConn.SetNoDelay(true) // 禁用 Nagle 算法 + tcpConn.SetKeepAlive(true) + tcpConn.SetKeepAlivePeriod(30 * time.Second) + } +} diff --git a/cmd/client/main.go b/cmd/quic/main.go similarity index 74% rename from cmd/client/main.go rename to cmd/quic/main.go index 2f71be5..e814d95 100644 --- a/cmd/client/main.go +++ b/cmd/quic/main.go @@ -1,7 +1,6 @@ package main import ( - "bytes" "context" "crypto/tls" "errors" @@ -9,12 +8,12 @@ import ( "io" "net" "os" - "strings" "sync" "time" "github.com/quic-go/quic-go" "github.com/rs/zerolog/log" + "github.com/tursom/mc-gateway/protocol" ) var ( @@ -60,8 +59,11 @@ func main() { func handlerConn(conn net.Conn) { defer conn.Close() + setSocketOptions(conn) + tlsConf := &tls.Config{ InsecureSkipVerify: true, // 跳过证书检查 + NextProtos: []string{"minecraft", "quic", "raw", "h3"}, } ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) // 3s handshake timeout @@ -100,8 +102,8 @@ func handlerConn(conn net.Conn) { } buf = buf[:n] - new_buf := replaceMcHost(buf, mcHost) - _, err = stream.Write(new_buf) // 写入数据到 QUIC 流 + buf = protocol.ReplaceMcHost(buf, mcHost) + _, err = stream.Write(buf) // 写入数据到 QUIC 流 if err != nil { log.Err(err). Str("client", conn.RemoteAddr().String()). @@ -110,6 +112,8 @@ func handlerConn(conn net.Conn) { Msg("Error writing to QUIC stream") return } + // 释放 buf + buf = nil var wg sync.WaitGroup wg.Add(1) @@ -129,33 +133,10 @@ func copyData(src io.Reader, dst io.Writer, wg *sync.WaitGroup) { } } -func replaceMcHost(buf []byte, host string) []byte { - if len(buf) < 5 { - return nil +func setSocketOptions(conn net.Conn) { + if tcpConn, ok := conn.(*net.TCPConn); ok { + tcpConn.SetNoDelay(true) // 禁用 Nagle 算法 + tcpConn.SetKeepAlive(true) + tcpConn.SetKeepAlivePeriod(30 * time.Second) } - - var out bytes.Buffer - head := buf[:4] - - buf = buf[4:] - host_len := buf[0] - if len(buf) < int(host_len)+1 { - return nil - } - - raw_host := string(buf[1 : host_len+1]) - if spliterIndex := strings.IndexRune(raw_host, 0); spliterIndex != -1 { - host = host + raw_host[spliterIndex:] - } - - // 修改标识数据包长度的字节 - head[0] += byte(len(host) - len(raw_host)) - - out.Write(head) // 保留前四个字节 - out.WriteByte(byte(len(host))) // 写入主机名长度 - out.Write([]byte(host)) // 写入主机名 - - out.Write(buf[host_len+1:]) // 写入剩余数据 - - return out.Bytes() } diff --git a/config.example.json b/config.example.json deleted file mode 100644 index 81c5800..0000000 --- a/config.example.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "log": { - "level": "warn", - "file": "logs/mc1.log" - }, - "port": 25565, - "default": "mc1:25565", - "hosts": { - "dev": "mc1:25565", - "dev.lan": "mc1:25568" - } -} \ No newline at end of file diff --git a/config.example.toml b/config.example.toml new file mode 100644 index 0000000..32d3678 --- /dev/null +++ b/config.example.toml @@ -0,0 +1,19 @@ +[log] +level = "warn" +file = "logs/mc1.log" + +[tcp] +enable = true +port = 25565 + +[quic] +enable = true +port = 25565 + +[kcp] +enable = true +port = 25566 + +[hosts] +dev = "mc1:25565" +"dev.lan" = "mc1:25568" diff --git a/go.mod b/go.mod index 6b2a517..f12c152 100644 --- a/go.mod +++ b/go.mod @@ -3,22 +3,31 @@ module github.com/tursom/mc-gateway go 1.23.1 require ( + github.com/BurntSushi/toml v1.5.0 github.com/fsnotify/fsnotify v1.7.0 github.com/quic-go/quic-go v0.52.0 github.com/rs/zerolog v1.33.0 + github.com/xtaci/kcp-go v5.4.20+incompatible ) require ( github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect + github.com/klauspost/cpuid/v2 v2.2.8 // indirect + github.com/klauspost/reedsolomon v1.12.4 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.19 // indirect github.com/onsi/ginkgo/v2 v2.9.5 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect + github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b // indirect + github.com/tjfoc/gmsm v1.4.1 // indirect + github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37 // indirect go.uber.org/mock v0.5.0 // indirect golang.org/x/crypto v0.26.0 // indirect golang.org/x/mod v0.18.0 // indirect golang.org/x/net v0.28.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.23.0 // indirect + golang.org/x/sys v0.24.0 // indirect golang.org/x/tools v0.22.0 // indirect ) diff --git a/go.sum b/go.sum index e5fe4b1..27926c0 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,20 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= +github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= @@ -12,13 +22,32 @@ github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbV github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= +github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/klauspost/reedsolomon v1.12.4 h1:5aDr3ZGoJbgu/8+j45KtUJxzYm8k08JGtB9Wx1VQ4OA= +github.com/klauspost/reedsolomon v1.12.4/go.mod h1:d3CzOMOt0JXGIFZm1StgkyF14EYr3xneR2rNWo7NcMU= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= @@ -28,9 +57,11 @@ github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/quic-go/quic-go v0.52.0 h1:/SlHrCRElyaU6MaEPKqKr9z83sBg2v4FLLvWM+Z47pA= github.com/quic-go/quic-go v0.52.0/go.mod h1:MFlGGpcpJqRAfmYi6NC2cptDPSxRWTOGNuP4wqrWmzQ= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= @@ -40,29 +71,85 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 h1:89CEmDvlq/F7SJEOqkIdNDGJXrQIhuIx9D2DBXjavSU= +github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU= +github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b h1:fj5tQ8acgNUr6O8LEplsxDhUIe2573iLkJc+PqnzZTI= +github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4= +github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= +github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= +github.com/xtaci/kcp-go v5.4.20+incompatible h1:TN1uey3Raw0sTz0Fg8GkfM0uH3YwzhnZWQ1bABv5xAg= +github.com/xtaci/kcp-go v5.4.20+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE= +github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37 h1:EWU6Pktpas0n8lLQwDsRyZfmkPeRbdgPtW609es+/9E= +github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37/go.mod h1:HpMP7DB2CyokmAh4lp0EQnnWhmycP/TvwBGzvuie+H0= go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= -golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/protocol/mc.go b/protocol/mc.go new file mode 100644 index 0000000..efdb095 --- /dev/null +++ b/protocol/mc.go @@ -0,0 +1,60 @@ +package protocol + +import ( + "bytes" + "strings" +) + +// ReplaceMcHost 替换 Minecraft 主机名 +// 必须是连接的第一个数据包 +func ReplaceMcHost(buf []byte, host string) []byte { + if len(buf) < 5 { + return nil + } + + var out bytes.Buffer + head := buf[:4] + + buf = buf[4:] + host_len := buf[0] + if len(buf) < int(host_len)+1 { + return nil + } + + raw_host := string(buf[1 : host_len+1]) + if spliterIndex := strings.IndexRune(raw_host, 0); spliterIndex != -1 { + host = host + raw_host[spliterIndex:] + } + + // 修改标识数据包长度的字节 + head[0] += byte(len(host) - len(raw_host)) + + out.Write(head) // 保留前四个字节 + out.WriteByte(byte(len(host))) // 写入主机名长度 + out.Write([]byte(host)) // 写入主机名 + + out.Write(buf[host_len+1:]) // 写入剩余数据 + + return out.Bytes() +} + +// GetMcHost 通过第一个数据包获取 Minecraft 主机名 +func GetMcHost(buf []byte) string { + if len(buf) < 5 { + return "" + } + + buf = buf[4:] + host_len := buf[0] + if len(buf) < int(host_len)+1 { + return "" + } + + host := string(buf[1 : host_len+1]) + + if spliterIndex := strings.IndexRune(host, 0); spliterIndex != -1 { + return host[0:spliterIndex] + } else { + return host + } +}