123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167 |
- //Package mcping facilitates the pinging of Minecraft servers using the 1.7+ protocol.
- package mcping
- import (
- "bufio"
- "bytes"
- "encoding/binary"
- "encoding/json"
- "github.com/jmoiron/jsonq"
- "net"
- "strconv"
- "strings"
- "time"
- )
- const (
- //DefaultTimeout stores default ping timeout
- DefaultTimeout = time.Second
- )
- //Ping pings with default timeout
- func Ping(addr string) (PingResponse, error) {
- return ping(addr, DefaultTimeout)
- }
- //PingWithTimeout pings with custom timeout
- func PingWithTimeout(addr string, timeout time.Duration) (PingResponse, error) {
- return ping(addr, timeout)
- }
- //Deprecated: PingTimeout Pings with custom timeout
- func PingTimeout(addr string, timeout int) (PingResponse, error) {
- return ping(addr, time.Millisecond * time.Duration(timeout))
- }
- func ping(addr string, timeout time.Duration) (PingResponse, error) {
- var host string
- var port uint16
- var resp PingResponse
- //Start timer
- timer := pingTimer{}
- timer.Start()
- //Connect
- conn, err := net.DialTimeout("tcp", addr, timeout)
- if err != nil {
- return resp, err
- }
- defer conn.Close()
- connReader := bufio.NewReader(conn)
- var dataBuf bytes.Buffer
- var finBuf bytes.Buffer
- dataBuf.Write([]byte("\x00")) //Packet ID
- dataBuf.Write([]byte("\x6D")) //1.9 protocol
- if addrTokens := strings.Split(addr, ":"); len(addrTokens) == 2 {
- host = addrTokens[0]
- if intport, err := strconv.Atoi(addrTokens[1]); err == nil {
- port = uint16(intport)
- } else {
- return resp, err
- }
- } else {
- return resp, ErrAddress
- }
- //Write host string length + host
- hostLength := uint8(len(host))
- dataBuf.Write([]uint8{hostLength})
- dataBuf.Write([]byte(host))
- //Write port
- b := make([]byte, 2)
- binary.BigEndian.PutUint16(b, port)
- dataBuf.Write(b)
- //Next state ping
- dataBuf.Write([]byte("\x01"))
- //Prepend packet length with data
- packetLength := []byte{uint8(dataBuf.Len())}
- finBuf.Write(append(packetLength, dataBuf.Bytes()...))
- conn.Write(finBuf.Bytes()) //Sending handshake
- conn.Write([]byte("\x01\x00")) //Status ping
- //Get situationally useless full byte length
- binary.ReadUvarint(connReader)
- //Packet type 0 means we're good to receive ping
- packetType, _ := connReader.ReadByte()
- if bytes.Compare([]byte{packetType}, []byte("\x00")) != 0 {
- return resp, ErrPacketType
- }
- //Get data length via Varint
- length, err := binary.ReadUvarint(connReader)
- if err != nil {
- return resp, err
- }
- if length < 10 {
- return resp, ErrSmallPacket
- } else if length > 700000 {
- return resp, ErrBigPacket
- }
- //Recieve json buffer
- bytesRecieved := uint64(0)
- recBytes := make([]byte, length)
- for bytesRecieved < length {
- n, _ := connReader.Read(recBytes[bytesRecieved:length])
- bytesRecieved = bytesRecieved + uint64(n)
- }
- //Stop Timer, collect latency
- latency := timer.End()
- pingString := string(recBytes)
- //Convert buffer into jsonq instance
- pingData := map[string]interface{}{}
- dec := json.NewDecoder(strings.NewReader(pingString))
- dec.Decode(&pingData)
- jq := jsonq.NewQuery(pingData)
- //Assemble PlayerSample
- playerSampleMap, err := jq.ArrayOfObjects("players", "sample")
- playerSamples := []PlayerSample{}
- for k := range playerSampleMap {
- sample := PlayerSample{}
- sample.UUID = playerSampleMap[k]["id"].(string)
- sample.Name = playerSampleMap[k]["name"].(string)
- playerSamples = append(playerSamples, sample)
- }
- //Assemble PingResponse
- resp.Latency = uint(latency)
- resp.Online, _ = jq.Int("players", "online")
- resp.Max, _ = jq.Int("players", "max")
- resp.Protocol, _ = jq.Int("version", "protocol")
- favicon, _ := jq.String("favicon")
- resp.Favicon = []byte(favicon)
-
- resp.Motd, _ = jq.String("description")
- versionStr, _ := jq.String("version", "name")
- arr := strings.Split(versionStr, " ")
- if len(arr) == 0 {
- resp.Server = "Unknown"
- resp.Version = "Unknown"
- } else if len(arr) == 1 {
- resp.Server = "Unknown"
- resp.Version = arr[0]
- } else if len(arr) == 2 {
- resp.Server = arr[0]
- resp.Version = arr[1]
- }
- resp.Sample = playerSamples
- return resp, nil
- }
|