diff options
Diffstat (limited to 'internal/server/ping.go')
| -rw-r--r-- | internal/server/ping.go | 134 |
1 files changed, 134 insertions, 0 deletions
diff --git a/internal/server/ping.go b/internal/server/ping.go new file mode 100644 index 0000000..4783ec7 --- /dev/null +++ b/internal/server/ping.go @@ -0,0 +1,134 @@ +package server + +import ( + "bufio" + "bytes" + "encoding/binary" + "encoding/json" + "errors" + "fmt" + "io" + "net" + "time" +) + +type pingResponse struct { + Version struct { + Name string `json:"name"` + } `json:"version"` + Players struct { + Online int `json:"online"` + Max int `json:"max"` + } `json:"players"` + Description json.RawMessage `json:"description"` +} + +func (p pingResponse) motdText() string { + var text string + if json.Unmarshal(p.Description, &text) == nil { + return text + } + + var obj struct { + Text string `json:"text"` + } + if json.Unmarshal(p.Description, &obj) == nil { + return obj.Text + } + return "" +} + +func pingServer(host string, port int, timeout time.Duration) (*pingResponse, error) { + conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", host, port), timeout) + if err != nil { + return nil, err + } + defer conn.Close() + conn.SetDeadline(time.Now().Add(timeout)) + + var handshake bytes.Buffer + writeVarInt(&handshake, 0x00) + writeVarInt(&handshake, -1) + writeString(&handshake, host) + binary.Write(&handshake, binary.BigEndian, uint16(port)) + writeVarInt(&handshake, 1) + if err := writePacket(conn, handshake.Bytes()); err != nil { + return nil, err + } + + if err := writePacket(conn, []byte{0x00}); err != nil { + return nil, err + } + + reader := bufio.NewReader(conn) + if _, err := readVarInt(reader); err != nil { + return nil, err + } + packetID, err := readVarInt(reader) + if err != nil { + return nil, err + } + if packetID != 0x00 { + return nil, fmt.Errorf("unexpected packet id %d", packetID) + } + + jsonLen, err := readVarInt(reader) + if err != nil { + return nil, err + } + payload := make([]byte, jsonLen) + if _, err := io.ReadFull(reader, payload); err != nil { + return nil, err + } + + var resp pingResponse + if err := json.Unmarshal(payload, &resp); err != nil { + return nil, err + } + return &resp, nil +} + +func writePacket(conn net.Conn, data []byte) error { + var buf bytes.Buffer + writeVarInt(&buf, len(data)) + buf.Write(data) + _, err := conn.Write(buf.Bytes()) + return err +} + +func writeString(buf *bytes.Buffer, s string) { + writeVarInt(buf, len(s)) + buf.WriteString(s) +} + +func writeVarInt(buf *bytes.Buffer, value int) { + uv := uint32(value) + for { + if uv&^uint32(0x7F) == 0 { + buf.WriteByte(byte(uv)) + return + } + buf.WriteByte(byte(uv&0x7F | 0x80)) + uv >>= 7 + } +} + +func readVarInt(r io.ByteReader) (int, error) { + var result uint32 + var shift uint + for { + b, err := r.ReadByte() + if err != nil { + return 0, err + } + result |= uint32(b&0x7F) << shift + if b&0x80 == 0 { + break + } + shift += 7 + if shift >= 32 { + return 0, errors.New("varint too long") + } + } + return int(result), nil +} |
