api0

package
v0.0.0-...-8e88d4a Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 22, 2024 License: MIT Imports: 33 Imported by: 0

Documentation

Overview

Package api0 implements the original master server API.

External differences:

  • Proper HTTP response codes are used (this won't break anything since existing code doesn't check them).
  • Caching headers are supported and used where appropriate.
  • Pdiff stuff has been removed (this was never fully implemented to begin with; see docs/PDATA.md).
  • Error messages have been improved. Enum values remain the same for compatibility.
  • Some rate limits (no longer necessary due to increased performance and better caching) have been removed.
  • More HTTP methods and features are supported (e.g., HEAD, OPTIONS, Content-Encoding).
  • Website split into a separate handler (set Handler.NotFound to http.HandlerFunc(web.ServeHTTP) for identical behaviour).
  • /accounts/write_persistence returns a error message for easier debugging.
  • Alive/dead servers can be replaced by a new successful registration from the same ip/port. This eliminates the main cause of the duplicate server error requiring retries, and doesn't add much risk since you need to custom fuckery to start another server when you're already listening on the port.

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrServerListDuplicateAuthAddr = errors.New("already have server with auth addr")
	ErrServerListUpdateServerDead  = errors.New("no server found")
	ErrServerListUpdateWrongIP     = errors.New("wrong server update ip")
	ErrServerListLimitExceeded     = errors.New("would exceed server list limits")
)

Functions

This section is empty.

Types

type Account

type Account struct {
	// UID is the player's Origin UID. It is required and unique.
	UID uint64

	// Username is the player's last known in-game username (their EAID). It is
	// optional and case insensitive.
	Username string

	// AuthIP is the IP used for the current auth session.
	AuthIP netip.Addr

	// AuthToken is the random token generated for the current auth session.
	AuthToken string

	// AuthTokenExpiry is the expiry date of the current auth token.
	AuthTokenExpiry time.Time

	// LastServerID is the ID of the last server the account connected to.
	LastServerID string
}

Account contains information about a registered account.

func (Account) IsOnOwnServer

func (a Account) IsOnOwnServer() bool

type AccountStorage

type AccountStorage interface {
	// GetUIDsByUsername gets all known UIDs matching username. If none match, a
	// nil/zero-length slice is returned. If another error occurs, err is
	// non-nil.
	GetUIDsByUsername(username string) ([]uint64, error)

	// GetAccount gets the player matching uid. If none exists, nil is returned.
	// If another error occurs, err is non-nil.
	GetAccount(uid uint64) (*Account, error)

	// SaveAccount creates or replaces an account by its uid.
	SaveAccount(a *Account) error
}

AccountStorage stores information about registered users. It must be safe for concurrent use.

type ErrorCode

type ErrorCode string

ErrorCode represents a known Northstar error code.

const (
	ErrorCode_NO_GAMESERVER_RESPONSE     ErrorCode = "NO_GAMESERVER_RESPONSE"     // Couldn't reach game server
	ErrorCode_BAD_GAMESERVER_RESPONSE    ErrorCode = "BAD_GAMESERVER_RESPONSE"    // Game server gave an invalid response
	ErrorCode_UNAUTHORIZED_GAMESERVER    ErrorCode = "UNAUTHORIZED_GAMESERVER"    // Game server is not authorized to make that request
	ErrorCode_UNAUTHORIZED_GAME          ErrorCode = "UNAUTHORIZED_GAME"          // Stryder couldn't confirm that this account owns Titanfall 2
	ErrorCode_UNAUTHORIZED_PWD           ErrorCode = "UNAUTHORIZED_PWD"           // Wrong password
	ErrorCode_STRYDER_RESPONSE           ErrorCode = "STRYDER_RESPONSE"           // Got bad response from stryder
	ErrorCode_STRYDER_PARSE              ErrorCode = "STRYDER_PARSE"              // Couldn't parse stryder response
	ErrorCode_PLAYER_NOT_FOUND           ErrorCode = "PLAYER_NOT_FOUND"           // Couldn't find player account
	ErrorCode_GAMESERVER_NOT_FOUND       ErrorCode = "GAMESERVER_NOT_FOUND"       // Couldn't find game server
	ErrorCode_INVALID_MASTERSERVER_TOKEN ErrorCode = "INVALID_MASTERSERVER_TOKEN" // Invalid or expired masterserver token
	ErrorCode_JSON_PARSE_ERROR           ErrorCode = "JSON_PARSE_ERROR"           // Error parsing json response
	ErrorCode_UNSUPPORTED_VERSION        ErrorCode = "UNSUPPORTED_VERSION"        // The version you are using is no longer supported; update Northstar to continue
	ErrorCode_DUPLICATE_SERVER           ErrorCode = "DUPLICATE_SERVER"           // A server with this port already exists for your IP address
	ErrorCode_CONNECTION_REJECTED        ErrorCode = "CONNECTION_REJECTED"        // Connection rejected
)

https://github.com/R2Northstar/NorthstarMasterServer/blob/b45ff0ef267712e8bff6cd718bb5dc1afcdec420/shared/errorcodes.js

const (
	ErrorCode_INTERNAL_SERVER_ERROR ErrorCode = "INTERNAL_SERVER_ERROR"
	ErrorCode_BAD_REQUEST           ErrorCode = "BAD_REQUEST"
)

func (ErrorCode) Message

func (n ErrorCode) Message() string

Message returns the default message for error code n.

func (ErrorCode) MessageObj

func (n ErrorCode) MessageObj() ErrorObj

MessageObj is like Message, but returns an ErrorObj.

func (ErrorCode) MessageObjf

func (n ErrorCode) MessageObjf(format string, a ...interface{}) ErrorObj

MessageObjf is like Messagef, but returns an ErrorObj.

func (ErrorCode) Messagef

func (n ErrorCode) Messagef(format string, a ...interface{}) string

Messagef returns Message() with additional text appended after ": ".

func (ErrorCode) Obj

func (n ErrorCode) Obj() ErrorObj

Obj returns an ErrorObj.

type ErrorObj

type ErrorObj struct {
	Code    ErrorCode `json:"enum"`
	Message string    `json:"msg"` // note: no omitempty
}

ErrorObj contains an error code and a message for API responses.

type Handler

type Handler struct {
	// ServerList stores registered servers.
	ServerList *ServerList

	// AccountStorage stores accounts. It must be non-nil.
	AccountStorage AccountStorage

	// PdataStorage stores player data. It must be non-nil.
	PdataStorage PdataStorage

	// NSPkt handles connectionless packets. It must be non-nil.
	NSPkt *nspkt.Listener

	// UsernameSource configures the source to use for usernames.
	UsernameSource UsernameSource

	// EAXClient makes requests to the EAX API.
	EAXClient *eax.Client

	// CleanBadWords is used to filter bad words from server names and
	// descriptions. If not provided, words will not be filtered.
	CleanBadWords func(s string) string

	// MainMenuPromos gets the main menu promos to return for a request.
	MainMenuPromos func(*http.Request) MainMenuPromos

	// NotFound handles requests not handled by this Handler.
	NotFound http.Handler

	// MaxServers limits the number of registered servers. If -1, no limit is
	// applied. If 0, a reasonable default is used.
	MaxServers int

	// MaxServersPerIP limits the number of registered servers per IP. If -1, no
	// limit is applied. If 0, a reasonable default is used.
	MaxServersPerIP int

	// InsecureDevNoCheckPlayerAuth is an option you shouldn't use since it
	// makes the server trust that clients are who they say they are. Blame
	// @BobTheBob9 for this option even existing in the first place.
	InsecureDevNoCheckPlayerAuth bool

	// MinimumLauncherVersion* restricts authentication and server registration
	// to clients with at least this version, which must be valid semver. +dev
	// versions are always allowed.
	MinimumLauncherVersionClient, MinimumLauncherVersionServer string

	// TokenExpiryTime controls the expiry of player masterserver auth tokens.
	// If zero, a reasonable a default is used.
	TokenExpiryTime time.Duration

	// AllowGameServerIPv6 controls whether to allow game servers to use IPv6.
	AllowGameServerIPv6 bool

	// LookupIP looks up an IP2Location record for an IP. If not provided,
	// server regions and geo metrics are disabled. If it doesn't include latlon
	// info, geo metrics will be disabled too.
	LookupIP func(netip.Addr) (ip2x.Record, error)

	// GetRegion gets the region name from an IP2Location record. If not
	// provided, server regions are disabled.
	//
	// Errors should only be returned for unexpected situations, and a
	// best-effort region should still be returned if applicable (it will still
	// be used on error if non-empty).
	//
	// Note that it is valid to return an
	// empty region and no error if no region is to be assigned.
	GetRegion func(netip.Addr, ip2x.Record) (string, error)
	// contains filtered or unexported fields
}

Handler serves requests for the original master server API.

func (*Handler) CheckLauncherVersion

func (h *Handler) CheckLauncherVersion(r *http.Request, client bool) bool

CheckLauncherVersion checks if the r was made by NorthstarLauncher and if it is at least MinimumLauncherVersion.

func (*Handler) ExtractLauncherVersion

func (h *Handler) ExtractLauncherVersion(r *http.Request) string

ExtractLauncherVersion extracts the launcher version from r, returning an empty string if it's missing or invalid.

func (*Handler) Metrics

func (h *Handler) Metrics() *metrics.Set

func (*Handler) ServeHTTP

func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP routes requests to Handler.

func (*Handler) WritePrometheus

func (h *Handler) WritePrometheus(w io.Writer)

func (*Handler) WritePrometheusGeo

func (h *Handler) WritePrometheusGeo(w io.Writer)
type MainMenuPromos struct {
	NewInfo      MainMenuPromosNew         `json:"newInfo"`
	LargeButton  MainMenuPromosButtonLarge `json:"largeButton"`
	SmallButton1 MainMenuPromosButtonSmall `json:"smallButton1"`
	SmallButton2 MainMenuPromosButtonSmall `json:"smallButton2"`
}
type MainMenuPromosButtonLarge struct {
	Title      string `json:"Title"`
	Text       string `json:"Text"`
	Url        string `json:"Url"`
	ImageIndex int    `json:"ImageIndex"`
}
type MainMenuPromosButtonSmall struct {
	Title      string `json:"Title"`
	Url        string `json:"Url"`
	ImageIndex int    `json:"ImageIndex"`
}
type MainMenuPromosNew struct {
	Title1 string `json:"Title1"`
	Title2 string `json:"Title2"`
	Title3 string `json:"Title3"`
}

type PdataStorage

type PdataStorage interface {
	// GetPdataHash gets the current pdata hash for uid. If there is not any
	// pdata for uid, exists is false. If another error occurs, err is non-nil.
	GetPdataHash(uid uint64) (hash [sha256.Size]byte, exists bool, err error)

	// GetPdataCached gets the pdata for uid. If there is not any pdata for uid,
	// exists is false. If the provided hash is nonzero and the current pdata
	// matches, buf is nil. If another error occurs, err is non-nil.
	GetPdataCached(uid uint64, sha [sha256.Size]byte) (buf []byte, exists bool, err error)

	// SetPdata sets the raw pdata for uid, returning the actual size stored.
	SetPdata(uid uint64, buf []byte) (n int, err error)
}

PdataStorage stores player data for users. It should not make any assumptions on the contents of the stored blobs (including validity). It may compress the stored data. It must be safe for concurrent use.

type Server

type Server struct {
	Order    uint64
	ID       string         // unique, must not be modified after creation
	Addr     netip.AddrPort // unique, must not be modified after creation
	AuthPort uint16         // if zero, reuse game Addr for UDP-based auth, otherwise unique with Addr.Addr(), must not be modified after creation

	LauncherVersion string // for metrics

	Name        string
	Region      string
	Description string
	Password    string // blank for none

	Latitude  float64
	Longitude float64

	VerificationDeadline time.Time // zero once verified
	LastHeartbeat        time.Time

	PlayerCount int
	MaxPlayers  int
	Map         string
	Playlist    string

	ServerAuthToken string // used for authenticating the masterserver to the gameserver authserver

	ModInfo []ServerModInfo
}

func (Server) AuthAddr

func (s Server) AuthAddr() netip.AddrPort

AuthAddr returns the auth address for the server.

type ServerList

type ServerList struct {
	// contains filtered or unexported fields
}

ServerList stores information about registered game servers. It does not do any validation of its own except for ensuring ID and addr/port are unique, and filtering dead/unverified/ghost servers.

func NewServerList

func NewServerList(deadTime, ghostTime, verifyTime time.Duration, cfg ServerListConfig) *ServerList

NewServerList initializes a new server list.

verifyTime is the amount of time a server has to complete verification after it is created.

deadTime is the time since the last heartbeat after which a server is considered dead. A dead server will not be listed on the server list. It must be >= verifyTime if nonzero.

ghostTime is the time since the last heartbeat after which a server cannot be revived by the same ip/port combination. This allows restarted or crashed servers to recover the same server ID. Since a server must be dead to be a ghost, GhostTime must be > DeadTime for it to have any effect.

If both are nonzero, they must be positive, and deadTime must be less than ghostTime. Otherwise, NewServerList will panic.

func (*ServerList) DeleteServerByID

func (s *ServerList) DeleteServerByID(id string) bool

DeleteServerByID deletes a server by its ID, returning true if a live server was deleted.

func (*ServerList) GetLiveServers

func (s *ServerList) GetLiveServers(fn func(*Server) bool)

GetLiveServers loops over live (i.e., not dead/ghost) servers until fn returns false. The order of the servers is non-deterministic.

func (*ServerList) GetMetrics

func (s *ServerList) GetMetrics() []byte

GetMetrics gets Prometheus text format metrics about live servers in the server list. All metrics begin with atlas_api0sl_.

Note: Playlist/map metric labels are limited to known values, or "other".

func (*ServerList) GetServerByID

func (s *ServerList) GetServerByID(id string) *Server

GetServerByID returns a deep copy of the server with id, or nil if it is dead.

func (*ServerList) ReapServers

func (s *ServerList) ReapServers()

ReapServers deletes dead servers from memory.

func (*ServerList) ServerHybridUpdatePut

func (s *ServerList) ServerHybridUpdatePut(u *ServerUpdate, c *Server, l ServerListLimit) (*Server, error)

ServerHybridUpdatePut attempts to update a server by the server ID (if u is non-nil) (reviving it if necessary), and if that fails, then attempts to create/replace a server by the gameserver ip/port instead (if c is non-nil) while following the limits in l. It returns a copy of the resulting Server. If the resulting server's VerificationDeadline is nonzero, the server must be verified.

If the returned error is non-nil, it will either be an unavoidable internal error (e.g, failure to get random data for the server id) or one of the following (use errors.Is):

  • ErrServerListDuplicateAuthAddr - if the auth ip/port of the server to create (if c) or revive (if u and server is a ghost) has already been used by a live server
  • ErrServerListUpdateServerDead - if no server matching the provided id exists (if u) AND c is not provided
  • ErrServerListUpdateWrongIP - if a server matching the provided id exists, but the ip doesn't match (if u and u.ExpectIP)
  • ErrServerListLimitExceeded - if adding the server would exceed server limits (if c and l)

When creating a server using the values from c: c.Order, c.ID, c.ServerAuthToken, c.VerificationDeadline, and c.LastHeartbeat will be generated by this function (any existing value is ignored).

func (*ServerList) VerifyServer

func (s *ServerList) VerifyServer(id string) bool

VerifyServer marks the server with the provided id as verified. If it does not exist, false is returned.

func (*ServerList) WritePrometheus

func (s *ServerList) WritePrometheus(w io.Writer)

WritePrometheus writes metrics for s to w.

func (*ServerList) WritePrometheusGeo

func (s *ServerList) WritePrometheusGeo(w io.Writer)

WritePrometheusGeo writes location metrics for s to w.

type ServerListConfig

type ServerListConfig struct {
	// ExperimentalDeterministicServerIDSecret, if provided, is a secret to
	// combine with the server metadata upon registration to deterministically
	// generate a server ID. The secret is used to prevent brute-forcing server
	// IDs from the ID and known server info.
	//
	// This is NOT to be used for uniquely and/or persistently identifying
	// servers, and may change at any time. It is intended to allow servers to
	// have the same IDs on a best-effort basis when re-registering after
	// masterserver (this will reduce pdata update unauthorized errors after
	// restarting the masterserver) or server restart. Notable, if a server
	// changes their name or description, the ID will not be the same anymore.
	ExperimentalDeterministicServerIDSecret string

	AllowUwuify bool
}

type ServerListLimit

type ServerListLimit struct {
	// MaxServers limits the number of registered servers. If <= 0, no limit is
	// applied.
	MaxServers int

	// MaxServersPerIP limits the number of registered servers per IP. If <= 0,
	// no limit is applied.
	MaxServersPerIP int
}

type ServerModInfo

type ServerModInfo struct {
	Name             string
	Version          string
	RequiredOnClient bool
}

type ServerUpdate

type ServerUpdate struct {
	ID       string     // server to update
	ExpectIP netip.Addr // require the server for ID to have this IP address to successfully update

	Heartbeat   bool
	Name        *string
	Region      *string
	Description *string
	Latitude    *float64
	Longitude   *float64
	PlayerCount *int
	MaxPlayers  *int
	Map         *string
	Playlist    *string
}

type UsernameSource

type UsernameSource string

UsernameSource determines where to get player in-game usernames from.

const (
	// Don't get usernames.
	UsernameSourceNone UsernameSource = ""

	// Get the username from EAX.
	UsernameSourceEAX UsernameSource = "eax"

	// Get the username from Stryder (available since October 2, 2023). Note
	// that this source only returns usernames for valid tokens.
	UsernameSourceStryder UsernameSource = "stryder"

	// Get the username from Stryder, but fall back to EAX on missing/failure.
	UsernameSourceStryderEAX UsernameSource = "stryder-eax"

	// Get the username from Stryder, but also check EAX and warn if it's
	// different.
	UsernameSourceStryderEAXDebug UsernameSource = "stryder-eax-debug"
)

Directories

Path Synopsis
Package api0gameserver interacts with game servers using the original master server api.
Package api0gameserver interacts with game servers using the original master server api.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL