a2s-go is a focused Go SDK for Steam/Source A2S UDP queries.
It is intentionally split into three layers:
a2s: query a known game server withA2S_INFO,A2S_PLAYER, andA2S_RULESmaster: discover server addresses from Valve master server paginationscanner: turn address lists or discovery streams into batched probe results
go get github.com/GoFurry/a2s-go@latestpackage main
import (
"context"
"log"
"time"
"github.com/GoFurry/a2s-go"
)
func main() {
client, err := a2s.NewClient(
"1.2.3.4:27015",
a2s.WithTimeout(3*time.Second),
)
if err != nil {
log.Fatal(err)
}
defer client.Close()
info, err := client.QueryInfo(context.Background())
if err != nil {
log.Fatal(err)
}
log.Printf("name=%s map=%s players=%d/%d", info.Name, info.Map, info.Players, info.MaxPlayers)
}package main
import (
"context"
"log"
"time"
"github.com/GoFurry/a2s-go/master"
)
func main() {
client, err := master.NewClient(
master.WithTimeout(5*time.Second),
)
if err != nil {
log.Fatal(err)
}
defer client.Close()
req := master.Request{
Region: master.RegionAsia,
Filter: "\\secure\\1",
}
page, err := client.Query(context.Background(), req)
if err != nil {
log.Fatal(err)
}
log.Printf("servers=%d done=%v next=%s", len(page.Servers), page.Done, page.NextCursor.String())
}Query fetches one page. Stream keeps turning pages until discovery is done.
package main
import (
"context"
"log"
"github.com/GoFurry/a2s-go/scanner"
)
func main() {
client, err := scanner.NewClient(
scanner.WithConcurrency(32),
)
if err != nil {
log.Fatal(err)
}
servers, err := scanner.ParseAddresses([]string{
"127.0.0.1:27015",
"127.0.0.2", // defaults to 27015
})
if err != nil {
log.Fatal(err)
}
results, err := client.CollectInfo(context.Background(), scanner.Request{
Servers: servers,
})
if err != nil {
log.Fatal(err)
}
for _, result := range results {
if result.Err != nil {
log.Printf("probe error: %v", result.Err)
continue
}
log.Printf("%s -> %s", result.Server.String(), result.Info.Name)
}
}The scanner also supports:
- direct
[]stringaddress input viascanner.Request{Addresses: ...} - explicit address normalization via
scanner.ParseAddress(...)/scanner.ParseAddresses(...) ProbePlayers/CollectPlayersProbeRules/CollectRulesmaster.Streamstyle discovery input
Scanner input rules:
- exactly one of
Addresses,Servers, orDiscoverymust be non-nil Addressesacceptshost:portorhostand defaults missing ports to27015- empty
Addresses/Serverslists are valid and produce zero probe results - scanner currently supports IPv4 targets only
go run ./examples/basicgo run ./examples/live-regression -servers=1.2.3.4:27015,5.6.7.8go run ./examples/mastergo run ./examples/master/fake-mastergo run ./examples/scanner
For release-time live validation, use the manual regression example:
go run ./examples/live-regression -servers=1.2.3.4:27015,5.6.7.8 -mode=all -scanner=trueIt runs single-server probes first, then verifies the same targets through scanner.
See release-checklist.md for the recommended release gate.
Protocol behavior follows Valve documentation first: