Add quake/net package for connection less OOB messages

This commit is contained in:
chris
2020-08-12 11:28:52 -04:00
committed by Chris Marshall
parent 527087d394
commit bb321e0830
2 changed files with 136 additions and 30 deletions

View File

@ -1,17 +1,16 @@
package client package client
import ( import (
"bytes"
"html/template" "html/template"
"io" "io"
"io/ioutil" "io/ioutil"
"net"
"net/http" "net/http"
"net/url" "net/url"
"time"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware" "github.com/labstack/echo/v4/middleware"
quakenet "github.com/criticalstack/quake-kube/internal/quake/net"
) )
type Config struct { type Config struct {
@ -53,41 +52,19 @@ func NewRouter(cfg *Config) (*echo.Echo, error) {
}) })
}) })
raddr, err := net.ResolveUDPAddr("udp", cfg.ServerAddr)
if err != nil {
return nil, err
}
e.GET("/info", func(c echo.Context) error { e.GET("/info", func(c echo.Context) error {
conn, err := net.ListenPacket("udp", "0.0.0.0:0") m, err := quakenet.GetInfo(cfg.ServerAddr)
if err != nil { if err != nil {
return err return err
} }
defer conn.Close() return c.JSON(http.StatusOK, m)
})
buffer := make([]byte, 1024*1024) e.GET("/status", func(c echo.Context) error {
if err := conn.SetDeadline(time.Now().Add(5 * time.Second)); err != nil { m, err := quakenet.GetStatus(cfg.ServerAddr)
return err
}
n, err := conn.WriteTo([]byte("\xff\xff\xff\xffgetinfo xxx"), raddr)
if err != nil { if err != nil {
return err return err
} }
n, _, err = conn.ReadFrom(buffer)
if err != nil {
return err
}
resp := buffer[:n]
resp = bytes.TrimPrefix(resp, []byte("\xff\xff\xff\xffinfoResponse\n\\"))
resp = bytes.TrimSuffix(resp, []byte("\\xxx"))
parts := bytes.Split(resp, []byte("\\"))
m := make(map[string]string)
for i := 0; i < len(parts)-1; i += 2 {
m[string(parts[i])] = string(parts[i+1])
}
return c.JSON(http.StatusOK, m) return c.JSON(http.StatusOK, m)
}) })

129
internal/quake/net/net.go Normal file
View File

@ -0,0 +1,129 @@
package net
import (
"bytes"
"fmt"
"net"
"strconv"
"time"
"github.com/pkg/errors"
)
const (
OutOfBandHeader = "\xff\xff\xff\xff"
GetInfoCommand = "getinfo"
GetStatusCommand = "getstatus"
)
func SendCommand(addr, cmd string) ([]byte, error) {
raddr, err := net.ResolveUDPAddr("udp4", addr)
if err != nil {
return nil, err
}
conn, err := net.ListenPacket("udp4", "0.0.0.0:0")
if err != nil {
return nil, err
}
defer conn.Close()
buffer := make([]byte, 1024*1024)
if err := conn.SetDeadline(time.Now().Add(5 * time.Second)); err != nil {
return nil, err
}
n, err := conn.WriteTo([]byte(fmt.Sprintf("%s%s", OutOfBandHeader, cmd)), raddr)
if err != nil {
return nil, err
}
n, _, err = conn.ReadFrom(buffer)
if err != nil {
return nil, err
}
return buffer[:n], nil
}
func parseMap(data []byte) map[string]string {
if i := bytes.Index(data, []byte("\n")); i >= 0 {
data = data[i+1:]
}
data = bytes.TrimPrefix(data, []byte("\\"))
data = bytes.TrimSuffix(data, []byte("\n"))
parts := bytes.Split(data, []byte("\\"))
m := make(map[string]string)
for i := 0; i < len(parts)-1; i += 2 {
m[string(parts[i])] = string(parts[i+1])
}
return m
}
type Player struct {
Name string
Ping int
Score int
}
func parsePlayers(data []byte) ([]Player, error) {
players := make([]Player, 0)
for _, player := range bytes.Split(data, []byte("\n")) {
parts := bytes.SplitN(player, []byte(" "), 3)
if len(parts) != 3 {
continue
}
name, err := strconv.Unquote(string(parts[2]))
if err != nil {
return nil, err
}
ping, err := strconv.Atoi(string(parts[1]))
if err != nil {
return nil, err
}
score, err := strconv.Atoi(string(parts[0]))
if err != nil {
return nil, err
}
players = append(players, Player{
Name: name,
Ping: ping,
Score: score,
})
}
return players, nil
}
func GetInfo(addr string) (map[string]string, error) {
resp, err := SendCommand(addr, GetInfoCommand)
if err != nil {
return nil, err
}
return parseMap(resp), nil
}
type StatusResponse struct {
Configuration map[string]string
Players []Player
}
func GetStatus(addr string) (*StatusResponse, error) {
resp, err := SendCommand(addr, GetStatusCommand)
if err != nil {
return nil, err
}
data := bytes.TrimSuffix(resp, []byte("\n"))
parts := bytes.SplitN(data, []byte("\n"), 3)
switch len(parts) {
case 2:
status := &StatusResponse{
Configuration: parseMap(parts[1]),
Players: make([]Player, 0),
}
return status, nil
case 3:
status := &StatusResponse{
Configuration: parseMap(parts[1]),
}
status.Players, _ = parsePlayers(parts[2])
return status, nil
default:
return nil, errors.Errorf("cannot parse response: %q", resp)
}
}