forked from github-mirrorer/quake-kube
Add quake/net package for connection less OOB messages
This commit is contained in:
@ -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
129
internal/quake/net/net.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user