ping.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. //Package mcping facilitates the pinging of Minecraft servers using the 1.7+ protocol.
  2. package mcping
  3. import (
  4. "bufio"
  5. "bytes"
  6. "encoding/binary"
  7. "encoding/json"
  8. "github.com/jmoiron/jsonq"
  9. "net"
  10. "strconv"
  11. "strings"
  12. "time"
  13. )
  14. const (
  15. //DefaultTimeout stores default ping timeout
  16. DefaultTimeout = time.Second
  17. )
  18. //Ping pings with default timeout
  19. func Ping(addr string) (PingResponse, error) {
  20. return ping(addr, DefaultTimeout)
  21. }
  22. //PingWithTimeout pings with custom timeout
  23. func PingWithTimeout(addr string, timeout time.Duration) (PingResponse, error) {
  24. return ping(addr, timeout)
  25. }
  26. //Deprecated: PingTimeout Pings with custom timeout
  27. func PingTimeout(addr string, timeout int) (PingResponse, error) {
  28. return ping(addr, time.Millisecond * time.Duration(timeout))
  29. }
  30. func ping(addr string, timeout time.Duration) (PingResponse, error) {
  31. var host string
  32. var port uint16
  33. var resp PingResponse
  34. //Start timer
  35. timer := pingTimer{}
  36. timer.Start()
  37. //Connect
  38. conn, err := net.DialTimeout("tcp", addr, timeout)
  39. if err != nil {
  40. return resp, err
  41. }
  42. defer conn.Close()
  43. connReader := bufio.NewReader(conn)
  44. var dataBuf bytes.Buffer
  45. var finBuf bytes.Buffer
  46. dataBuf.Write([]byte("\x00")) //Packet ID
  47. dataBuf.Write([]byte("\x6D")) //1.9 protocol
  48. if addrTokens := strings.Split(addr, ":"); len(addrTokens) == 2 {
  49. host = addrTokens[0]
  50. if intport, err := strconv.Atoi(addrTokens[1]); err == nil {
  51. port = uint16(intport)
  52. } else {
  53. return resp, err
  54. }
  55. } else {
  56. return resp, ErrAddress
  57. }
  58. //Write host string length + host
  59. hostLength := uint8(len(host))
  60. dataBuf.Write([]uint8{hostLength})
  61. dataBuf.Write([]byte(host))
  62. //Write port
  63. b := make([]byte, 2)
  64. binary.BigEndian.PutUint16(b, port)
  65. dataBuf.Write(b)
  66. //Next state ping
  67. dataBuf.Write([]byte("\x01"))
  68. //Prepend packet length with data
  69. packetLength := []byte{uint8(dataBuf.Len())}
  70. finBuf.Write(append(packetLength, dataBuf.Bytes()...))
  71. conn.Write(finBuf.Bytes()) //Sending handshake
  72. conn.Write([]byte("\x01\x00")) //Status ping
  73. //Get situationally useless full byte length
  74. binary.ReadUvarint(connReader)
  75. //Packet type 0 means we're good to receive ping
  76. packetType, _ := connReader.ReadByte()
  77. if bytes.Compare([]byte{packetType}, []byte("\x00")) != 0 {
  78. return resp, ErrPacketType
  79. }
  80. //Get data length via Varint
  81. length, err := binary.ReadUvarint(connReader)
  82. if err != nil {
  83. return resp, err
  84. }
  85. if length < 10 {
  86. return resp, ErrSmallPacket
  87. } else if length > 700000 {
  88. return resp, ErrBigPacket
  89. }
  90. //Recieve json buffer
  91. bytesRecieved := uint64(0)
  92. recBytes := make([]byte, length)
  93. for bytesRecieved < length {
  94. n, _ := connReader.Read(recBytes[bytesRecieved:length])
  95. bytesRecieved = bytesRecieved + uint64(n)
  96. }
  97. //Stop Timer, collect latency
  98. latency := timer.End()
  99. pingString := string(recBytes)
  100. //Convert buffer into jsonq instance
  101. pingData := map[string]interface{}{}
  102. dec := json.NewDecoder(strings.NewReader(pingString))
  103. dec.Decode(&pingData)
  104. jq := jsonq.NewQuery(pingData)
  105. //Assemble PlayerSample
  106. playerSampleMap, err := jq.ArrayOfObjects("players", "sample")
  107. playerSamples := []PlayerSample{}
  108. for k := range playerSampleMap {
  109. sample := PlayerSample{}
  110. sample.UUID = playerSampleMap[k]["id"].(string)
  111. sample.Name = playerSampleMap[k]["name"].(string)
  112. playerSamples = append(playerSamples, sample)
  113. }
  114. //Assemble PingResponse
  115. resp.Latency = uint(latency)
  116. resp.Online, _ = jq.Int("players", "online")
  117. resp.Max, _ = jq.Int("players", "max")
  118. resp.Protocol, _ = jq.Int("version", "protocol")
  119. favicon, _ := jq.String("favicon")
  120. resp.Favicon = []byte(favicon)
  121. resp.Motd, _ = jq.String("description")
  122. versionStr, _ := jq.String("version", "name")
  123. arr := strings.Split(versionStr, " ")
  124. if len(arr) == 0 {
  125. resp.Server = "Unknown"
  126. resp.Version = "Unknown"
  127. } else if len(arr) == 1 {
  128. resp.Server = "Unknown"
  129. resp.Version = arr[0]
  130. } else if len(arr) == 2 {
  131. resp.Server = arr[0]
  132. resp.Version = arr[1]
  133. }
  134. resp.Sample = playerSamples
  135. return resp, nil
  136. }