Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: SQP performance reporting #16

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,40 @@ func main() {

CLI
-------------
A cli is available in https://github.com/multiplay/go-svrquery/tree/master/cmd/cli
A cli is available in github releases and also at https://github.com/multiplay/go-svrquery/tree/master/cmd/cli

This enables you make queries to servers using the specified protocol, and returns the response in pretty json.

### Client

```
./go-svrquery -addr localhost:12121 -proto sqp
{
"version": 1,
"address": "localhost:12121",
"server_info": {
"current_players": 1,
"max_players": 2,
"server_name": "Name",
"game_type": "Game Type",
"build_id": "",
"map": "Map",
"port": 1000
}
}
```

### Example Server

This tool also provides the ability to start a very basic sample server using a given protocol.

Currently, only `sqp` is supported

```
./go-svrquery -server :12121 -proto sqp
Starting sample server using protocol sqp on :12121
```

Documentation
-------------
- [GoDoc API Reference](http://godoc.org/github.com/multiplay/go-svrquery).
Expand Down
93 changes: 83 additions & 10 deletions cmd/cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,45 @@ import (
"flag"
"fmt"
"log"
"net"
"os"
"time"

"github.com/multiplay/go-svrquery/lib/svrquery"
"github.com/multiplay/go-svrquery/lib/svrsample"
"github.com/multiplay/go-svrquery/lib/svrsample/common"
)

func main() {
address := flag.String("addr", "", "Address e.g. 127.0.0.1:12345")
clientAddr := flag.String("addr", "", "Address to connect to e.g. 127.0.0.1:12345")
proto := flag.String("proto", "", "Protocol e.g. sqp, tf2e, tf2e-v7, tf2e-v8")
serverAddr := flag.String("server", "", "Address to start server e.g. 127.0.0.1:12121, :23232")
flag.Parse()

l := log.New(os.Stderr, "", 0)

if *address == "" {
l.Println("No address provided")
flag.PrintDefaults()
os.Exit(1)
if *serverAddr != "" && *clientAddr != "" {
bail(l, "Cannot run both a server and a client. Specify either -addr OR -server flags")
}

if *proto == "" {
l.Println("No protocol provided")
flag.PrintDefaults()
os.Exit(1)
switch {
case *serverAddr != "":
if *proto == "" {
bail(l, "No protocol provided in client mode")
}
serverMode(l, *proto, *serverAddr)
case *clientAddr != "":
if *proto == "" {
bail(l, "Protocol required in server mode")
}
queryMode(l, *proto, *clientAddr)
default:
bail(l, "Please supply some options")
}
}

if err := query(*proto, *address); err != nil {
func queryMode(l *log.Logger, proto, address string) {
if err := query(proto, address); err != nil {
l.Fatal(err)
}
}
Expand All @@ -53,3 +67,62 @@ func query(proto, address string) error {
fmt.Printf("%s\n", b)
return nil
}

func serverMode(l *log.Logger, proto, serverAddr string) {
if err := server(l, proto, serverAddr); err != nil {
l.Fatal(err)
}
}

func server(l *log.Logger, proto, address string) error {
l.Printf("Starting sample server using protocol %s on %s", proto, address)
responder, err := svrsample.GetResponder(proto, common.QueryState{
CurrentPlayers: 1,
MaxPlayers: 2,
ServerName: "Name",
GameType: "Game Type",
Map: "Map",
Port: 1000,
})

addr, err := net.ResolveUDPAddr("udp4", address)
if err != nil {
return err
}

conn, err := net.ListenUDP("udp4", addr)
if err != nil {
return err
}

for {
buf := make([]byte, 16)
_, to, err := conn.ReadFromUDP(buf)
if err != nil {
l.Println("read from udp", err)
continue
}

resp, err := responder.Respond(to.String(), buf)
if err != nil {
l.Println("error responding to query", err)
continue
}

if err = conn.SetWriteDeadline(time.Now().Add(1 * time.Second)); err != nil {
l.Println("error setting write deadline")
continue
}

if _, err = conn.WriteTo(resp, to); err != nil {
l.Println("error writing response")
}
}

}

func bail(l *log.Logger, msg string) {
l.Println(msg)
flag.PrintDefaults()
os.Exit(1)
}
3 changes: 0 additions & 3 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand All @@ -10,14 +9,12 @@ github.com/netdata/go-orchestrator v0.0.0-20190905093727-c793edba0e8f h1:jSzujNr
github.com/netdata/go-orchestrator v0.0.0-20190905093727-c793edba0e8f/go.mod h1:ECF8anFVCt/TfTIWVPgPrNaYJXtAtpAOF62ugDbw41A=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Expand Down
3 changes: 0 additions & 3 deletions lib/svrquery/protocol/sqp/consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,4 @@ const (
// TODO(steve): remove this?
// DefaultMaxPacketSize is the default maximum size of a packet (MTU 1500 - UDP+IP header size)
DefaultMaxPacketSize = 1472

// Version is the query protocol version this client uses.
Version = uint16(1)
)
1 change: 1 addition & 0 deletions lib/svrquery/protocol/sqp/enums.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,5 @@ const (
ServerRules
PlayerInfo
TeamInfo
PerformanceInfo
)
106 changes: 101 additions & 5 deletions lib/svrquery/protocol/sqp/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import (
"bufio"
"bytes"
"encoding/binary"
"fmt"
"io"
"io/ioutil"
"math"

"github.com/multiplay/go-svrquery/lib/svrquery/protocol"
)
Expand All @@ -16,18 +18,24 @@ type queryer struct {
reader *packetReader
challengeID uint32
requestedChunks byte
version uint16
}

func newCreator(c protocol.Client) protocol.Queryer {
return newQueryer(ServerInfo, DefaultMaxPacketSize, c)
return newQueryer(ServerInfo, DefaultMaxPacketSize, 1, c)
}

func newQueryer(requestedChunks byte, maxPktSize int, c protocol.Client) *queryer {
func newCreatorV2(c protocol.Client) protocol.Queryer {
return newQueryer(ServerInfo|PerformanceInfo, DefaultMaxPacketSize, 2, c)
}

func newQueryer(requestedChunks byte, maxPktSize int, version uint16, c protocol.Client) *queryer {
return &queryer{
c: c,
maxPktSize: maxPktSize,
requestedChunks: requestedChunks,
reader: newPacketReader(bufio.NewReaderSize(c, maxPktSize)),
version: version,
}
}

Expand All @@ -37,6 +45,8 @@ func (q *queryer) Query() (protocol.Responser, error) {
return nil, err
}

fmt.Println("query 1")

return q.readQuery(q.requestedChunks)
}

Expand All @@ -55,25 +65,31 @@ func (q *queryer) sendQuery(requestedChunks byte) error {
return err
}

if err := binary.Write(pkt, binary.BigEndian, Version); err != nil {
if err := binary.Write(pkt, binary.BigEndian, q.version); err != nil {
return err
}

if err := pkt.WriteByte(requestedChunks); err != nil {
fmt.Printf("Requested Chunks: %b %d\n", requestedChunks, requestedChunks)
//if err := pkt.WriteByte(requestedChunks); err != nil {
// return err
//}

if err := pkt.WriteByte(ServerInfo); err != nil {
return err
}

_, err := q.c.Write(pkt.Bytes())
return err
}

func (q *queryer) readQueryHeader() (uint16, byte, byte, uint16, error) {
fmt.Println("readQuery header 1")
pktType, err := q.reader.ReadByte()
if err != nil {
return 0, 0, 0, 0, err
} else if pktType != QueryResponseType {
return 0, 0, 0, 0, NewErrMalformedPacketf("was expecting 0x%02x for response type, got 0x%02x", QueryResponseType, pktType)
}
fmt.Println("readQuery header 2")

if err = q.validateChallenge(); err != nil {
return 0, 0, 0, 0, err
Expand Down Expand Up @@ -113,6 +129,8 @@ func (q *queryer) readQuery(requestedChunks byte) (*QueryResponse, error) {
return nil, err
}

fmt.Println("readQuery 1")

if lastPkt == 0 && curPkt == 0 {
// If the header says the body is empty, we should just return now
if pktLen == 0 {
Expand All @@ -128,6 +146,9 @@ func (q *queryer) readQuery(requestedChunks byte) (*QueryResponse, error) {
func (q *queryer) readQuerySinglePacket(r *packetReader, version uint16, requestedChunks byte, pktLen uint32) (*QueryResponse, error) {
qr := &QueryResponse{Version: version, Address: q.c.Address()}

fmt.Println("SP 1")
fmt.Println("SP 1a: ", requestedChunks&ServerInfo, requestedChunks&PerformanceInfo)

l := pktLen
if requestedChunks&ServerInfo > 0 {
if err := q.readQueryServerInfo(qr, r); err != nil {
Expand Down Expand Up @@ -157,6 +178,13 @@ func (q *queryer) readQuerySinglePacket(r *packetReader, version uint16, request
l -= qr.TeamInfo.ChunkLength + uint32(Uint32.Size())
}

if requestedChunks&PerformanceInfo > 0 && version == 2 {
if err := q.readQueryPerformanceInfo(qr, r); err != nil {
return nil, err
}
l -= qr.PerformanceInfo.ChunkLength + uint32(Uint32.Size())
}

if l > 0 {
// If we have extra bytes remaining, we assume they are new fields from a future
// query version and discard them.
Expand All @@ -171,6 +199,8 @@ func (q *queryer) readQuerySinglePacket(r *packetReader, version uint16, request
func (q *queryer) readQueryServerInfo(qr *QueryResponse, r *packetReader) (err error) {
qr.ServerInfo = &ServerInfoChunk{}

fmt.Println("SI 1")

if qr.ServerInfo.ChunkLength, err = r.ReadUint32(); err != nil {
return err
}
Expand Down Expand Up @@ -420,6 +450,72 @@ func (q *queryer) readQueryTeamInfo(qr *QueryResponse, r *packetReader) (err err
return nil
}

// FlagsNum byte
// Flags uint32
// GaugesNum byte
// Gauges []float32

func (q *queryer) readQueryPerformanceInfo(qr *QueryResponse, r *packetReader) (err error) {
qr.PerformanceInfo = &PerformanceInfoChunk{
Gauges: []float32{},
}

fmt.Println("p1")

if qr.PerformanceInfo.ChunkLength, err = r.ReadUint32(); err != nil {
return err
}
l := int64(qr.PerformanceInfo.ChunkLength)

fmt.Println("p2")
if qr.PerformanceInfo.NumFlags, err = r.ReadByte(); err != nil {
return err
}
l -= int64(Byte.Size())

//fmt.Printf("num: %d, mask: %b\n", flagsNum, mask)

if qr.PerformanceInfo.Flags, err = r.ReadUint32(); err != nil {
return err
}
l -= int64(Uint32.Size())

fmt.Println("p2")
var gaugesNum byte
if gaugesNum, err = r.ReadByte(); err != nil {
return err
}
l -= int64(Byte.Size())

fmt.Println("p3 ", gaugesNum)

for i := byte(0); i < gaugesNum; i++ {
fmt.Println("p4")
u, err := r.ReadUint32()
if err != nil {
return err
}
l -= int64(Uint32.Size())
qr.PerformanceInfo.Gauges = append(qr.PerformanceInfo.Gauges, math.Float32frombits(u))
}

fmt.Println("p5")

switch {
case l < 0:
// If we have read more bytes than expected, the packet is malformed
return NewErrMalformedPacketf("expected chunk length of %v, but have %v bytes remaining", qr.PerformanceInfo.ChunkLength, l)
case l > 0:
// If we have extra bytes remaining, we assume they are new fields from a future
// query version and discard them
if _, err := io.CopyN(ioutil.Discard, r, l); err != nil {
return err
}
}

return nil
}

func (q *queryer) readQueryMultiPacket(version uint16, curPkt, lastPkt, requestedChunks byte, pktLen uint16) (*QueryResponse, error) {
// Setup our array of packet bodies
expectedPkts := lastPkt + 1
Expand Down
1 change: 1 addition & 0 deletions lib/svrquery/protocol/sqp/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ import (

func init() {
protocol.MustRegister("sqp", newCreator)
protocol.MustRegister("sqp-v2", newCreatorV2)
}
Loading