package net import ( "bytes" "encoding/binary" "errors" "fmt" "math/rand" "net" ) const MaxRCONPackageSize = 4096 // DialRCON connect to a RCON server and return the connection after login. // We promise the returned RCONClientConn is an RCONConn, so you can convert // them by type assertions if you need call the ReadPacket() or WritePacket() methods. func DialRCON(addr string, password string) (client RCONClientConn, err error) { c := &RCONConn{ReqID: rand.Int31()} client = c c.Conn, err = net.Dial("tcp", addr) if err != nil { err = fmt.Errorf("connect fail: %v", err) return } //Login err = c.WritePacket(c.ReqID, 3, password) if err != nil { err = fmt.Errorf("login fail: %v", err) return } //Login resp r, _, _, err := c.ReadPacket() if err != nil { err = fmt.Errorf("read login resp fail: %v", err) return } if r == c.ReqID { err = nil } else if r == -1 { err = errors.New("login fail") } else { err = errors.New("req id not match") } return } type RCONConn struct { net.Conn ReqID int32 } func (r *RCONConn) ReadPacket() (RequestID, Type int32, Payload string, err error) { //read packet length var Length int32 err = binary.Read(r, binary.LittleEndian, &Length) if err != nil { err = fmt.Errorf("read packet length fail: %v", err) return } //check length if Length < 4+4+0+2 { err = errors.New("packet too short") return } if Length > MaxRCONPackageSize { err = errors.New("packet too large") return } //read packet data buf := make([]byte, Length) err = binary.Read(r, binary.LittleEndian, &buf) if err != nil { err = fmt.Errorf("read packet body fail: %v", err) return } RequestID = int32(binary.LittleEndian.Uint32(buf[:4])) Type = int32(binary.LittleEndian.Uint32(buf[4:8])) Payload = string(buf[8 : Length-2]) return } func (r *RCONConn) WritePacket(RequestID, Type int32, Payload string) error { buf := new(bytes.Buffer) for _, v := range []interface{}{ int32(4 + 4 + len(Payload) + 2), //Length RequestID, //Request ID Type, //Type []byte(Payload), //Payload []byte{0, 0}, //pad } { err := binary.Write(buf, binary.LittleEndian, v) if err != nil { return err } } _, err := r.Write(buf.Bytes()) return err } func (r *RCONConn) Cmd(cmd string) error { err := r.WritePacket(r.ReqID, 2, cmd) return err } func (r *RCONConn) Resp() (resp string, err error) { var ReqID, Type int32 ReqID, Type, resp, err = r.ReadPacket() if err != nil { return } if ReqID != r.ReqID { err = errors.New("req ID not match") } else if Type != 0 { err = fmt.Errorf("packet type wrong: %d", Type) } return } func (r *RCONConn) AcceptLogin(password string) error { R, T, P, err := r.ReadPacket() if err != nil { return err } r.ReqID = R //Check packet type if T != 3 { return fmt.Errorf("not a login packet: %d", T) } if P != password { err = r.WritePacket(-1, 2, "") if err != nil { return err } return errors.New("password wrong") } err = r.WritePacket(R, 2, "") if err != nil { return err } return nil } func (r *RCONConn) AcceptCmd() (string, error) { R, T, P, err := r.ReadPacket() if err != nil { return P, err } r.ReqID = R //Check packet type if T != 2 { return P, fmt.Errorf("not a command packet: %d", T) } return P, nil } func (r *RCONConn) RespCmd(resp string) error { return r.WritePacket(r.ReqID, 0, resp) } type RCONClientConn interface { Cmd(cmd string) error Resp() (resp string, err error) Close() error } // RCONServerConn is the connection in the server side. type RCONServerConn interface { AcceptLogin(password string) error AcceptCmd() (cmd string, err error) RespCmd(resp string) error Close() error } // ListenRCON announces on the local network address, accepting RCON clients. func ListenRCON(addr string) (*RCONListener, error) { l, err := net.Listen("tcp", addr) if err != nil { return nil, err } return &RCONListener{Listener: l}, nil } type RCONListener struct{ net.Listener } // Accept RCON connection for client. // We promise the returned RCONServerConn is an RCONConn, so you can convert // them by type assertions if you need call the ReadPacket() or WritePacket() methods. func (r *RCONListener) Accept() (RCONServerConn, error) { conn, err := r.Listener.Accept() if err != nil { return nil, err } return &RCONConn{Conn: conn}, nil }