支持 KCP 协议
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

This commit is contained in:
2025-06-26 14:12:15 +08:00
parent 0e5e0ced30
commit 8530df6cdf
18 changed files with 870 additions and 264 deletions

214
.github/workflows/build.yml vendored Normal file
View File

@@ -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

View File

@@ -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 ./...

1
.gitignore vendored
View File

@@ -1,5 +1,6 @@
.vscode .vscode
config.json config.json
config.toml
/gateway /gateway
/logs/ /logs/

View File

@@ -1,2 +1,76 @@
# mc-gateway # 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"]

123
cmd/gateway/config.go Normal file
View File

@@ -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
}

41
cmd/gateway/kcp.go Normal file
View File

@@ -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)
}
}

70
cmd/gateway/log.go Normal file
View File

@@ -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
}

View File

@@ -1,142 +1,16 @@
package main package main
import ( import (
"encoding/json"
"fmt" "fmt"
"io" "io"
"net" "net"
"os"
"os/signal"
"path/filepath"
"strings"
"sync" "sync"
"syscall"
"time" "time"
"github.com/fsnotify/fsnotify"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log" "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() { func main() {
if err := writePIDFile(); err != nil { if err := writePIDFile(); err != nil {
panic(fmt.Sprintf("Failed to write PID file: %v", err)) panic(fmt.Sprintf("Failed to write PID file: %v", err))
@@ -156,13 +30,18 @@ func main() {
defer wg.Wait() defer wg.Wait()
// 启动QUIC服务 // 启动QUIC服务
if config.Quic { if config.Quic.Enable {
wg.Add(1) wg.Add(1)
go runQuic(&wg) go runQuic(&wg)
} }
if config.Kcp.Enable {
wg.Add(1)
go runKcp(&wg)
}
// 监听TCP端口 // 监听TCP端口
if config.Tcp { if config.Tcp.Enable {
wg.Add(1) wg.Add(1)
go runTcp(&wg) go runTcp(&wg)
} }
@@ -173,15 +52,15 @@ func runTcp(wg *sync.WaitGroup) {
defer wg.Done() 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 { if err != nil {
log.Fatal().Err(err). log.Fatal().Err(err).
Int("port", config.Port). Int("port", config.Tcp.Port).
Msg("Failed to listen on port") Msg("Failed to listen on port")
} }
defer listener.Close() defer listener.Close()
log.Info(). log.Info().
Int("port", config.Port). Int("port", config.Tcp.Port).
Msg("Listening for TCP connections") Msg("Listening for TCP connections")
for { for {
@@ -233,10 +112,10 @@ func handleRequest(conn net.Conn) {
Msg("Error: buffer is empty") Msg("Error: buffer is empty")
return return
} }
mc_host := getMcHost(buf[:n]) mc_host := protocol.GetMcHost(buf[:n])
host, ok := config.Hosts[mc_host] host, ok := config.Hosts[mc_host]
if !ok { if !ok {
host = config.Default host = config.Hosts["default"]
} }
if host == "" { if host == "" {
log.Err(errEmptyBuffer). log.Err(errEmptyBuffer).
@@ -248,6 +127,7 @@ func handleRequest(conn net.Conn) {
log.Info(). log.Info().
Str("client", conn.RemoteAddr().String()). Str("client", conn.RemoteAddr().String()).
Str("host", mc_host).
Str("mc", host). Str("mc", host).
Msg("map to host") Msg("map to host")
@@ -257,6 +137,7 @@ func handleRequest(conn net.Conn) {
return return
} }
defer client.Close() defer client.Close()
setSocketOptions(client)
client.Write(buf[:n]) client.Write(buf[:n])
// 不需要 buf 了,释放掉 // 不需要 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) { func setSocketOptions(conn net.Conn) {
if tcpConn, ok := conn.(*net.TCPConn); ok { if tcpConn, ok := conn.(*net.TCPConn); ok {
tcpConn.SetNoDelay(true) // 禁用 Nagle 算法 tcpConn.SetNoDelay(true) // 禁用 Nagle 算法
@@ -322,35 +183,3 @@ func setSocketOptions(conn net.Conn) {
tcpConn.SetKeepAlivePeriod(30 * time.Second) 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
}

View File

@@ -1,3 +1,6 @@
// pid_unix.go
//go:build unix || plan9
package main package main
import ( import (

View File

@@ -1,3 +1,6 @@
// pid_windows.go
//go:build windows
package main package main
func writePIDFile() error { func writePIDFile() error {

View File

@@ -28,7 +28,7 @@ func runQuic(wg *sync.WaitGroup) {
defer wg.Done() 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 { if err != nil {
log.Panic().Err(err).Msg("Failed to listen UDP") log.Panic().Err(err).Msg("Failed to listen UDP")
} }
@@ -43,7 +43,7 @@ func runQuic(wg *sync.WaitGroup) {
if err != nil { if err != nil {
log.Panic().Err(err).Msg("Failed to listen QUIC") 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 { for {
conn, err := ln.Accept(context.Background()) conn, err := ln.Accept(context.Background())
@@ -52,6 +52,9 @@ func runQuic(wg *sync.WaitGroup) {
continue continue
} }
log.Info().
Str("client", conn.RemoteAddr().String()).
Msg("Accepted QUIC connection")
go handleQuicRequest(conn) go handleQuicRequest(conn)
} }
} }
@@ -115,8 +118,14 @@ func generateTLSConfig() (*tls.Config, error) {
return nil, err return nil, err
} }
nextProtos := config.Quic.ApplicionProtocols
if len(nextProtos) == 0 {
nextProtos = []string{"minecraft", "quic", "raw", "h3"} // 默认协议
}
// 返回 tls.Config // 返回 tls.Config
return &tls.Config{ return &tls.Config{
Certificates: []tls.Certificate{cert}, Certificates: []tls.Certificate{cert},
NextProtos: nextProtos,
}, nil }, nil
} }

123
cmd/kcp/main.go Normal file
View File

@@ -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)
}
}

View File

@@ -1,7 +1,6 @@
package main package main
import ( import (
"bytes"
"context" "context"
"crypto/tls" "crypto/tls"
"errors" "errors"
@@ -9,12 +8,12 @@ import (
"io" "io"
"net" "net"
"os" "os"
"strings"
"sync" "sync"
"time" "time"
"github.com/quic-go/quic-go" "github.com/quic-go/quic-go"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/tursom/mc-gateway/protocol"
) )
var ( var (
@@ -60,8 +59,11 @@ func main() {
func handlerConn(conn net.Conn) { func handlerConn(conn net.Conn) {
defer conn.Close() defer conn.Close()
setSocketOptions(conn)
tlsConf := &tls.Config{ tlsConf := &tls.Config{
InsecureSkipVerify: true, // 跳过证书检查 InsecureSkipVerify: true, // 跳过证书检查
NextProtos: []string{"minecraft", "quic", "raw", "h3"},
} }
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) // 3s handshake timeout ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) // 3s handshake timeout
@@ -100,8 +102,8 @@ func handlerConn(conn net.Conn) {
} }
buf = buf[:n] buf = buf[:n]
new_buf := replaceMcHost(buf, mcHost) buf = protocol.ReplaceMcHost(buf, mcHost)
_, err = stream.Write(new_buf) // 写入数据到 QUIC 流 _, err = stream.Write(buf) // 写入数据到 QUIC 流
if err != nil { if err != nil {
log.Err(err). log.Err(err).
Str("client", conn.RemoteAddr().String()). Str("client", conn.RemoteAddr().String()).
@@ -110,6 +112,8 @@ func handlerConn(conn net.Conn) {
Msg("Error writing to QUIC stream") Msg("Error writing to QUIC stream")
return return
} }
// 释放 buf
buf = nil
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Add(1) 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 { func setSocketOptions(conn net.Conn) {
if len(buf) < 5 { if tcpConn, ok := conn.(*net.TCPConn); ok {
return nil 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()
} }

View File

@@ -1,12 +0,0 @@
{
"log": {
"level": "warn",
"file": "logs/mc1.log"
},
"port": 25565,
"default": "mc1:25565",
"hosts": {
"dev": "mc1:25565",
"dev.lan": "mc1:25568"
}
}

19
config.example.toml Normal file
View File

@@ -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"

11
go.mod
View File

@@ -3,22 +3,31 @@ module github.com/tursom/mc-gateway
go 1.23.1 go 1.23.1
require ( require (
github.com/BurntSushi/toml v1.5.0
github.com/fsnotify/fsnotify v1.7.0 github.com/fsnotify/fsnotify v1.7.0
github.com/quic-go/quic-go v0.52.0 github.com/quic-go/quic-go v0.52.0
github.com/rs/zerolog v1.33.0 github.com/rs/zerolog v1.33.0
github.com/xtaci/kcp-go v5.4.20+incompatible
) )
require ( require (
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // 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-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect github.com/mattn/go-isatty v0.0.19 // indirect
github.com/onsi/ginkgo/v2 v2.9.5 // 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 go.uber.org/mock v0.5.0 // indirect
golang.org/x/crypto v0.26.0 // indirect golang.org/x/crypto v0.26.0 // indirect
golang.org/x/mod v0.18.0 // indirect golang.org/x/mod v0.18.0 // indirect
golang.org/x/net v0.28.0 // indirect golang.org/x/net v0.28.0 // indirect
golang.org/x/sync v0.8.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 golang.org/x/tools v0.22.0 // indirect
) )

91
go.sum
View File

@@ -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/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/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/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/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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= 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= 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 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= 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/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 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 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 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 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 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 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/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 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 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= 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/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 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= 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/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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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 h1:/SlHrCRElyaU6MaEPKqKr9z83sBg2v4FLLvWM+Z47pA=
github.com/quic-go/quic-go v0.52.0/go.mod h1:MFlGGpcpJqRAfmYi6NC2cptDPSxRWTOGNuP4wqrWmzQ= github.com/quic-go/quic-go v0.52.0/go.mod h1:MFlGGpcpJqRAfmYi6NC2cptDPSxRWTOGNuP4wqrWmzQ=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= 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.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 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 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 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= 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 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= 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 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= 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 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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-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.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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.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.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= 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 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= 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 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 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/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.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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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=

60
protocol/mc.go Normal file
View File

@@ -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
}
}