hachem.wtf
aboutsummaryrefslogtreecommitdiff
path: root/internal/server/ping.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/server/ping.go')
-rw-r--r--internal/server/ping.go134
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
+}