From cecc1ea127b1744deb65c58966f80daa2368be66 Mon Sep 17 00:00:00 2001 From: MahnoKropotkinvich Date: Thu, 23 Jan 2025 13:59:24 +0800 Subject: [PATCH] major update --- .gitignore | 8 +- conn.go | 36 ++++++- daemon.go | 1 - ds.go | 22 ++-- fsm.go | 1 + go.mod | 1 + logger.go | 6 ++ protocol/protocol.go | 250 +++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 312 insertions(+), 13 deletions(-) create mode 100644 protocol/protocol.go diff --git a/.gitignore b/.gitignore index 0ce7910..59e7b1b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ bin/ go.sum .vscode/ -test/ -test-bak/ -rebuild-test.sh \ No newline at end of file +test-res/ +test-res-bak/ +rebuild-test.sh +*_test.go +test/ \ No newline at end of file diff --git a/conn.go b/conn.go index 471f237..e639267 100644 --- a/conn.go +++ b/conn.go @@ -2,6 +2,7 @@ package main import ( "io" + "minimcd/protocol" "net" "sync" "time" @@ -31,14 +32,43 @@ 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:"+config.MCPort) if err != nil { @@ -46,6 +76,9 @@ func handleConn(clientOriginal net.Conn) { return } defer server.Close() + if ENABLE_LOGIN_FILTER { + server.Write(firstPacket) + } var wg sync.WaitGroup wg.Add(2) go func() { @@ -66,8 +99,6 @@ func handleConn(clientOriginal net.Conn) { wg.Wait() GetLogger().Infof("Connection from %s closed", clientOriginal.RemoteAddr()) } - queryChan <- STOPPED - st, _ := <-queryChan switch st { case RUNNING: CntChan <- INCREASE @@ -106,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) } } diff --git a/daemon.go b/daemon.go index ef56367..11101e9 100644 --- a/daemon.go +++ b/daemon.go @@ -25,7 +25,6 @@ func Booting() { DaemonChanRX <- struct{}{} GetLogger().Info("enter RUNNING state") go Running() - } func Running() { <-DaemonChanTX diff --git a/ds.go b/ds.go index 3ff2a62..caf01c5 100644 --- a/ds.go +++ b/ds.go @@ -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,7 +113,7 @@ 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{}, 0) @@ -123,9 +124,11 @@ 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 @@ -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{ @@ -194,10 +198,14 @@ func NewDynamicMultiChan[T constraints.Ordered](reply bool, m int) *DynamicMulti // ret.used.Push(id) // // delete(used, id) - ret.modify <- modify_t[T]{DELETE, id - 1, nil} - <-reload[chanId] + 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 - <-reload[chanId] //waiting for you finished + //waiting for you finished // reloadRX <- struct{}{} //I've read all deltas, you can release them continue } @@ -252,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 diff --git a/fsm.go b/fsm.go index 1771cb5..ac08f04 100644 --- a/fsm.go +++ b/fsm.go @@ -60,6 +60,7 @@ func handleWaitingToRunning() { _, ok := <-waitChan if ok { state = RUNNING //happens immediately + ConnSignalChan <- RUNNING } //else it's too late } func handleRunningToWaiting() { diff --git a/go.mod b/go.mod index 170c136..d3720c1 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/logger.go b/logger.go index 8729f6e..dfb57d0 100644 --- a/logger.go +++ b/logger.go @@ -10,8 +10,14 @@ 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 { diff --git a/protocol/protocol.go b/protocol/protocol.go new file mode 100644 index 0000000..9b7e4e2 --- /dev/null +++ b/protocol/protocol.go @@ -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)) +}