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 }