Compare commits

..

2 Commits

Author SHA1 Message Date
cecc1ea127 major update 2025-01-23 13:59:24 +08:00
e33d213789 fix a lot of bugs, adding detailed log output 2025-01-03 22:00:10 +08:00
9 changed files with 407 additions and 72 deletions

8
.gitignore vendored
View File

@ -1,6 +1,8 @@
bin/
go.sum
.vscode/
test/
test-bak/
rebuild-test.sh
test-res/
test-res-bak/
rebuild-test.sh
*_test.go
test/

View File

@ -1,8 +1,9 @@
package main
import (
"gopkg.in/yaml.v2"
"os"
"gopkg.in/yaml.v2"
)
type Config struct {
@ -10,10 +11,20 @@ type Config struct {
StartCommand string `yaml:"start_command"`
Port string `yaml:"port"`
ConnectTimeout int `yaml:"connect_timeout"` //seconds
MCPort string `yaml:"mc_port"`
}
var defaultConfig = Config{
MCPort: "25565",
ConnectTimeout: 10,
Port: "4567",
Timeout: 1,
}
// this is a global constant since it's shared
var config Config
var config = Config{}
const DEFAULT_VALUE_FMT_STR = "%s not found, default to %v"
func LoadConfig(filePath string) error {
data, err := os.ReadFile(filePath)
@ -26,5 +37,21 @@ func LoadConfig(filePath string) error {
return err
}
GetLogger().Info("Configuration loaded successfully")
if config.MCPort == "" {
GetLogger().Warnf(DEFAULT_VALUE_FMT_STR, "MCPort", defaultConfig.MCPort)
config.MCPort = defaultConfig.MCPort
}
if config.ConnectTimeout == 0 {
GetLogger().Warnf(DEFAULT_VALUE_FMT_STR, "ConnectTimeout", defaultConfig.ConnectTimeout)
config.ConnectTimeout = defaultConfig.ConnectTimeout
}
if config.Port == "" {
GetLogger().Warnf(DEFAULT_VALUE_FMT_STR, "Port", defaultConfig.Port)
config.Port = defaultConfig.Port
}
if config.Timeout == 0 {
GetLogger().Warnf(DEFAULT_VALUE_FMT_STR, "Timeout", defaultConfig.Timeout)
config.Timeout = defaultConfig.Timeout
}
return nil
}

48
conn.go
View File

@ -2,6 +2,7 @@ package main
import (
"io"
"minimcd/protocol"
"net"
"sync"
"time"
@ -31,21 +32,53 @@ func bridge() {
for {
msg, _ := <-ConnSignalChan
clientSignalChan.TX <- msg
clientSignalChan = NewDynamicMultiChan[MCState](false, 2)
}
}
const ENABLE_LOGIN_FILTER = true
func handleConn(clientOriginal net.Conn) {
client := timeoutConn{clientOriginal}
defer clientOriginal.Close()
const DENIED_FMT_STR = "%s refused, it's not login connection"
check := func() protocol.Packet {
l := protocol.StreamedFromVarNoException[int](clientOriginal)
if l == 0 {
return nil
}
pid := protocol.StreamedFromVarNoException[int](clientOriginal)
if pid != 0x0 {
return nil
}
ver, addr, port, nxtst := protocol.StreamedFromHandshakePacketDataNoException(clientOriginal)
if nxtst != 2 {
return nil
}
return protocol.ToHandshakePacket(ver, addr, port, nxtst)
}
var firstPacket protocol.Packet
if ENABLE_LOGIN_FILTER {
if firstPacket = check(); firstPacket == nil {
GetLogger().Infof(DENIED_FMT_STR, clientOriginal.RemoteAddr())
return
}
}
queryChan := make(QueryChan)
defer close(queryChan)
QueryChanChan <- queryChan
queryChan <- STOPPED
st, _ := <-queryChan
proceed := func() {
server, err := net.Dial("tcp", "127.0.0.1:25565")
server, err := net.Dial("tcp", "127.0.0.1:"+config.MCPort)
if err != nil {
GetLogger().Errorf("Failed to connect to MC server: %v", err)
return
}
defer server.Close()
if ENABLE_LOGIN_FILTER {
server.Write(firstPacket)
}
var wg sync.WaitGroup
wg.Add(2)
go func() {
@ -66,21 +99,21 @@ func handleConn(clientOriginal net.Conn) {
wg.Wait()
GetLogger().Infof("Connection from %s closed", clientOriginal.RemoteAddr())
}
CntChan <- INCREASE
defer func() { CntChan <- DECREASE }()
queryChan <- STOPPED
st, _ := <-queryChan
switch st {
case RUNNING:
CntChan <- INCREASE
defer func() { CntChan <- DECREASE }()
proceed()
case STOPPED, WAITING:
// client.Write([]byte("Server not ready!"))
GetLogger().Infof("Connection queued, server currently at %s state", stateToStr[state])
CntChan <- INCREASE
defer func() { CntChan <- DECREASE }()
curChan := make(chan MCState)
defer close(curChan)
clientSignalChan.Add(curChan)
state, _ := <-curChan
if state == RUNNING {
st, _ := <-curChan
if st == RUNNING {
proceed()
} else {
client.Write([]byte("You are too late, server dying!"))
@ -104,6 +137,7 @@ func Listen() {
}
//conn.SetReadDeadline(time.Now().Add(time.Duration(config.ConnectTimeout) * time.Second))
GetLogger().Infof("New connection from %s", conn.RemoteAddr())
conn.SetDeadline(time.Now().Add(time.Duration(config.ConnectTimeout) * time.Second))
go handleConn(conn)
}
}

View File

@ -20,11 +20,11 @@ func Booting() {
proc = exec.Command(arg[0], arg[1:]...)
proc.Stdout = os.Stdout
proc.Stderr = os.Stderr
proc.Stdin = os.Stdin
proc.Start()
DaemonChanRX <- struct{}{}
GetLogger().Info("enter RUNNING state")
go Running()
}
func Running() {
<-DaemonChanTX

115
ds.go
View File

@ -3,6 +3,7 @@ package main
import (
"reflect"
inf "github.com/Code-Hex/go-infinity-channel"
"golang.org/x/exp/constraints"
)
@ -103,7 +104,7 @@ type DynamicMultiChan[T constraints.Ordered] struct {
TX chan T
RX chan T
reply bool
modify chan modify_t[T]
modify *inf.Channel[modify_t[T]]
}
func NewDynamicMultiChan[T constraints.Ordered](reply bool, m int) *DynamicMultiChan[T] {
@ -112,10 +113,10 @@ func NewDynamicMultiChan[T constraints.Ordered](reply bool, m int) *DynamicMulti
RX: make(chan T),
reply: reply,
modify: make(chan modify_t[T]),
modify: inf.NewChannel[modify_t[T]](),
}
mode := m
reload := make(chan struct{})
reload := make([]chan struct{}, 0)
used := make(map[int]bool)
unused := make(map[int]bool)
// delta := -1
@ -123,14 +124,16 @@ func NewDynamicMultiChan[T constraints.Ordered](reply bool, m int) *DynamicMulti
selectCases := make([]reflect.SelectCase, 1)
viewCnt := 0
addQue := NewQueue[int]()
del := make(map[int]bool)
go func() {
for {
modification, _ := <-ret.modify
modification, _ := <-ret.modify.Out()
op := modification.op
id := modification.id
ch := modification.ch
for range viewCnt {
reload <- struct{}{}
for _, v := range reload {
v <- struct{}{}
}
switch op {
case ADD:
@ -158,6 +161,7 @@ func NewDynamicMultiChan[T constraints.Ordered](reply bool, m int) *DynamicMulti
}
case DELETE:
delete(used, id)
delete(del, id+1)
unused[id] = true
list[id] = nil
selectCases[id+1] = reflect.SelectCase{
@ -167,64 +171,71 @@ func NewDynamicMultiChan[T constraints.Ordered](reply bool, m int) *DynamicMulti
}
//deleted <- struct{}{} // <-reloadTX //extra communication
}
for range viewCnt {
reload <- struct{}{}
for _, v := range reload {
v <- struct{}{}
}
}
}()
go func() {
{
chanId := viewCnt
viewCnt++
reload = append(reload, make(chan struct{}))
selectCases[0] = reflect.SelectCase{
Dir: reflect.SelectRecv,
Chan: reflect.ValueOf(reload),
Chan: reflect.ValueOf(reload[chanId]),
}
prevId := -1
for {
id, recv, ok := reflect.Select(selectCases)
if prevId != -1 {
selectCases[prevId].Dir = reflect.SelectRecv
selectCases[prevId].Send = reflect.Value{}
prevId = -1
}
if !ok {
// ret.used.Push(id)
//
// delete(used, id)
ret.modify <- modify_t[T]{DELETE, id - 1, nil}
<-reload
// reloadRX <- struct{}{} //I'm currently not dealing with other chan
<-reload //waiting for you finished
// reloadRX <- struct{}{} //I've read all deltas, you can release them
continue
}
if mode == 1 && id != 0 {
ret.RX <- To[T](recv) // possible for below to send to Chan?
if ret.reply {
msg, _ := <-ret.TX
selectCases[id].Dir = reflect.SelectSend
selectCases[id].Send = reflect.ValueOf(msg)
prevId = id
}
} else if id == 0 {
// reloadRX <- struct{}{} //I'm currently not dealing with other chan
<-reload //waiting for you finished
// reloadRX <- struct{}{} //I've read all deltas, you can release them
}
}
}()
if mode == 2 {
go func() {
viewCnt++
msgList := make([]T, 0)
for {
id, recv, ok := reflect.Select(selectCases)
if prevId != -1 {
selectCases[prevId].Dir = reflect.SelectRecv
selectCases[prevId].Send = reflect.Value{}
prevId = -1
}
if !ok {
// ret.used.Push(id)
//
// delete(used, id)
if _, Ok := del[id]; !Ok {
del[id] = true
ret.modify.In() <- modify_t[T]{DELETE, id - 1, nil}
<-reload[chanId]
<-reload[chanId]
}
// reloadRX <- struct{}{} //I'm currently not dealing with other chan
//waiting for you finished
// reloadRX <- struct{}{} //I've read all deltas, you can release them
continue
}
if mode == 1 && id != 0 {
ret.RX <- To[T](recv) // possible for below to send to Chan?
if ret.reply {
msg, _ := <-ret.TX
selectCases[id].Dir = reflect.SelectSend
selectCases[id].Send = reflect.ValueOf(msg)
prevId = id
}
} else if id == 0 {
// reloadRX <- struct{}{} //I'm currently not dealing with other chan
<-reload[chanId] //waiting for you finished
// reloadRX <- struct{}{} //I've read all deltas, you can release them
}
}
}()
}
if mode == 2 {
chanId := viewCnt
viewCnt++
reload = append(reload, make(chan struct{}))
msgList := make([]T, 0)
go func() {
for {
select {
case <-reload:
case <-reload[chanId]:
// reloadRX <- struct{}{} //I'm currently not dealing with other chan
<-reload //waiting for you finished
if addQue.Length() == 0 {
continue
}
<-reload[chanId] //waiting for you finished
for !addQue.IsEmpty() {
delta := addQue.Pop()
for _, x := range msgList {
@ -249,7 +260,7 @@ func (self DynamicMultiChan[T]) IsReply() bool {
return self.reply
}
func (self *DynamicMultiChan[T]) Add(ch chan T) {
self.modify <- modify_t[T]{ADD, -1, ch}
self.modify.In() <- modify_t[T]{ADD, -1, ch}
}
// TODO: we need to wait for 2 msg and send 4 msg in chan

14
fsm.go
View File

@ -12,6 +12,7 @@ const (
INCOMING globalConnEvent = iota
EMPTY
)
const SERVER_STATE_INFO_FMTSTR = "Server is currently at %s state"
// luckily I use int for them all
var (
@ -54,15 +55,16 @@ var waitChan chan struct{}
// no we don't need cmdChan, we just make it work immediately
func handleWaitingToRunning() {
GetLogger().Infof("Server is currently at %s state", stateToStr[RUNNING])
GetLogger().Infof(SERVER_STATE_INFO_FMTSTR, stateToStr[RUNNING])
waitChan <- struct{}{}
_, ok := <-waitChan
if ok {
state = RUNNING //happens immediately
ConnSignalChan <- RUNNING
} //else it's too late
}
func handleRunningToWaiting() {
GetLogger().Infof("Server is currently at %s state", stateToStr[WAITING])
GetLogger().Infof(SERVER_STATE_INFO_FMTSTR, stateToStr[WAITING])
waitChan = make(chan struct{})
go waitingThread()
state = WAITING
@ -70,7 +72,7 @@ func handleRunningToWaiting() {
// TODO: work with daemon
func handleWaitingToStopping() {
GetLogger().Infof("Server is currently at %s state", stateToStr[STOPPING])
GetLogger().Infof(SERVER_STATE_INFO_FMTSTR, stateToStr[STOPPING])
go stoppingThread()
state = STOPPING
}
@ -82,7 +84,7 @@ func stoppingThread() {
handleStoppingToStopped()
}
func handleStoppingToStopped() {
GetLogger().Infof("Server is currently at %s state", stateToStr[STOPPED])
GetLogger().Infof(SERVER_STATE_INFO_FMTSTR, stateToStr[STOPPED])
state = STOPPED
}
func bootingThread() {
@ -95,12 +97,12 @@ func bootingThread() {
var runningChan chan struct{}
func handleBootingToRunning() {
GetLogger().Infof("Server is currently at %s state", stateToStr[RUNNING])
GetLogger().Infof(SERVER_STATE_INFO_FMTSTR, stateToStr[RUNNING])
state = RUNNING
ConnSignalChan <- RUNNING
}
func handleStoppedToBooting() {
GetLogger().Infof("Server is currently at %s state", stateToStr[BOOTING])
GetLogger().Infof(SERVER_STATE_INFO_FMTSTR, stateToStr[BOOTING])
state = BOOTING
bootingThread()
}

1
go.mod
View File

@ -9,6 +9,7 @@ require (
)
require (
github.com/Code-Hex/go-infinity-channel v1.0.0
github.com/alecthomas/repr v0.4.0 // indirect
github.com/hexops/gotextdiff v1.0.3 // indirect
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67

View File

@ -1,21 +1,29 @@
package main
import (
"github.com/sirupsen/logrus"
"os"
"github.com/sirupsen/logrus"
)
var logger *logrus.Logger
func InitLogger() {
logger = logrus.New()
disableLogColor := os.Getenv("DISABLE_LOG_COLOR")
logColor := true
if disableLogColor == "true" {
logColor = false
}
logger.SetFormatter(&logrus.TextFormatter{
FullTimestamp: true,
ForceColors: logColor,
})
logLevel := os.Getenv("LOG_LEVEL")
switch logLevel {
case "debug":
logger.SetLevel(logrus.DebugLevel)
logger.SetReportCaller(true)
case "info":
logger.SetLevel(logrus.InfoLevel)
case "warn", "warning":

250
protocol/protocol.go Normal file
View File

@ -0,0 +1,250 @@
package protocol
import (
"bytes"
"encoding/binary"
"encoding/json"
"io"
"golang.org/x/exp/constraints"
)
type intType interface {
int | int32 | int64
}
type Var[T intType] []byte
type String []byte
const SEGMENT_BITS = 0x7F
const CONTINUE_BIT = 0x80
func ToVar[T intType](value T) Var[T] {
ret := make(Var[T], 0)
for {
if (value & ^SEGMENT_BITS) == 0 {
ret = append(ret, byte(value))
return ret
}
ret = append(ret, byte((value&SEGMENT_BITS)|CONTINUE_BIT))
value = T(uint(value) >> 7)
}
}
func FromVar[T intType](x Var[T]) T {
value, position := T(0), 0
for i := 0; ; i++ {
cur := x[i]
value |= T((cur & SEGMENT_BITS) << byte(position))
if (cur & CONTINUE_BIT) == 0 {
break
}
position += 7
}
return value
}
func StreamedFromVarNoException[T intType](reader io.Reader) T {
value, position := T(0), 0
buf := make(Var[T], 1)
for i := 0; ; i++ {
reader.Read(buf)
cur := buf[0]
value |= (T(cur) & SEGMENT_BITS) << position
if (cur & CONTINUE_BIT) == 0 {
break
}
position += 7
}
return value
}
func ToString(str string) String {
ret := make(String, 0)
ret = append(ret, ToVar[int](len(str))...)
ret = append(ret, []byte(str)...)
return ret
}
func FromString(x String) string {
l := FromVar(Var[int](x))
ll := Length(Var[int](x))
return string(x[ll : ll+l])
}
func StreamedFromStringNoException(reader io.Reader) string {
l := StreamedFromVarNoException[int](reader)
buf := make([]byte, l)
reader.Read(buf)
return string(buf)
}
type PrefixedArray[T intType | byte] []byte
func ToPrefixedArray[T intType | byte](arr []T) PrefixedArray[T] {
ret := make(PrefixedArray[T], 0)
ret = append(ret, ToVar[int](len(arr))...)
switch any(*new(T)).(type) {
case byte:
for _, v := range arr {
ret = append(ret, byte(v))
}
default:
panic("unimplemented!")
}
return ret
}
func StreamedFromPrefixedArrayNoException(reader io.Reader) []byte { //I don't know how to write parser for other types, left for further change
l := StreamedFromVarNoException[int](reader)
ret := make([]byte, l)
reader.Read(ret)
return ret
}
type VarLengthTypes interface {
Var[int] | Var[int32] | Var[int64] | String
}
// TODO: rewrite as method
func Length[T VarLengthTypes](x T) int {
switch any(x).(type) {
case Var[int], Var[int32], Var[int64]:
i := 0
for ; ; i++ {
cur := x[i]
if (cur & CONTINUE_BIT) == 0 {
break
}
}
return i + 1
case String:
return Length[Var[int]](Var[int](x)) + len(FromString(String(x)))
default:
panic("unreachable")
}
}
type Boolean [4]byte
func ToBoolean(x bool) (ret Boolean) {
if x {
ret[3] = 1
} else {
ret[3] = 0
}
return
}
func StreamedFromBooleanNoException(reader io.Reader) bool {
buf := Boolean{}
reader.Read(buf[:])
if buf[3] == 1 {
return true
} else {
return false
}
}
type BigEndian[T constraints.Integer] []byte
func ToBigEndian[T constraints.Integer](x T) BigEndian[T] {
buf := new(bytes.Buffer)
binary.Write(buf, binary.BigEndian, x)
return buf.Bytes()
}
func FromBigEndian[T constraints.Integer](x BigEndian[T]) (ret T) {
binary.Read(bytes.NewReader(x), binary.BigEndian, &ret)
return
}
func StreamedFromBigEndianNoException[T constraints.Integer](reader io.Reader) (ret T) {
buf := make(BigEndian[T], binary.Size(ret))
reader.Read(buf)
binary.Read(bytes.NewReader(buf), binary.BigEndian, &ret)
return
}
type UUID [16]byte // I don't think that I should parse the UUID sent from server, which means the endianess will be kept the same
type Data []byte
func ToHandshakePacketData(ver int, addr string, port uint16, nxtst int) Data {
ret := make(Data, 0)
ret = append(ret, ToVar(ver)...)
ret = append(ret, ToString(addr)...)
ret = append(ret, ToBigEndian(port)...)
ret = append(ret, ToVar(nxtst)...)
return ret
}
func FromHandshakePacketData(x Data) (ver int, addr string, port uint16, nxtst int) {
ver = FromVar(Var[int](x))
x = x[Length(Var[int](x)):]
addr = FromString(String(x))
x = x[Length(String(x)):]
port = FromBigEndian(BigEndian[uint16](x[:2]))
x = x[2:]
nxtst = FromVar(Var[int](x))
return ver, addr, port, nxtst
}
func StreamedFromHandshakePacketDataNoException(reader io.Reader) (ver int, addr string, port uint16, nxtst int) {
ver = StreamedFromVarNoException[int](reader)
//x = x[Length(Var[int](x)):]
addr = StreamedFromStringNoException(reader)
// x = x[Length(String(x)):]
port = StreamedFromBigEndianNoException[uint16](reader)
// x = x[2:]
nxtst = StreamedFromVarNoException[int](reader)
return ver, addr, port, nxtst
}
func ToLoginStartPacketData(name string, uuid UUID) Data {
ret := Data(ToString(name))
ret = append(ret, uuid[:]...)
return ret
}
func StreamedFromLoginStartPacketDataNoException(reader io.Reader) (name string, uuid UUID) {
name = StreamedFromStringNoException(reader)
reader.Read(uuid[:])
return
}
func ToEncryptionRequestPacketData(sid string, pubkey []byte, token []byte, auth bool) Data {
ret := make(Data, 0)
ret = append(ret, ToString(sid)...)
ret = append(ret, ToPrefixedArray(pubkey)...)
ret = append(ret, ToPrefixedArray(token)...)
authByte := ToBoolean(auth)
ret = append(ret, authByte[:]...)
return ret
}
func StreamedFromEncryptionRequestPacketData(reader io.Reader) (sid string, pubkey []byte, token []byte, auth bool) {
sid = StreamedFromStringNoException(reader)
pubkey = StreamedFromPrefixedArrayNoException(reader)
token = StreamedFromPrefixedArrayNoException(reader)
auth = StreamedFromBooleanNoException(reader)
return
}
type disconnectJsonFields struct {
Type string `json:"type"`
Text string `json:"text"`
}
func ToDisconnectPacketData(text string) Data {
ret, _ := json.Marshal(disconnectJsonFields{"text", text})
return ret
}
type Packet []byte
func ToPacket(p int, data Data) Packet {
ret := make(Packet, 0)
pid := ToVar[int](p)
ret = append(ret, ToVar[int](Length(pid)+len(data))...)
ret = append(ret, pid...)
ret = append(ret, data...)
return ret
}
func StreamedFromPacketNoException(reader io.Reader) (int, Data) {
l := StreamedFromVarNoException[int](reader)
// ll := Length(Var[int](x))
//pid := FromVar(Var[int](x[ll:]))
pid := StreamedFromVarNoException[int](reader)
ret := make(Data, l-Length(ToVar(pid)))
reader.Read(ret)
return pid, ret
}
func ToHandshakePacket(ver int, addr string, port uint16, nxtst int) Packet {
return ToPacket(0x00, ToHandshakePacketData(ver, addr, port, nxtst))
}