Skip to content
Merged
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
10 changes: 0 additions & 10 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,5 @@
"showLog": false, // Hides some Go Debugger(Delve) log stuff that is not useful for debugging atm
"args": ["--NoSteamCMD"]
},
{
"name": "Debug Go Server noSteamCMD noSvelte overrideAdvertisedIp",
"type": "go",
"request": "launch",
"mode": "debug",
"program": "${workspaceFolder}/server.go",
"console": "integratedTerminal",
"showLog": false, // Hides some Go Debugger(Delve) log stuff that is not useful for debugging atm
"args": ["--NoSteamCMD", "--OverrideAdvertisedIp=127.0.0.1"]
}
]
}
90 changes: 80 additions & 10 deletions src/advertiser/advertiser.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package advertiser
import (
"bytes"
"encoding/json"
"errors"
"net"
"net/http"
"runtime"
"strconv"
Expand All @@ -16,6 +18,9 @@ import (

var StationeersAdvertisementEndpoint = config.GetStationeersServerPingEndpoint()

const maxTransientErrors = 5
const advertiserIntervalSeconds = 30

type ServerAdMessage struct {
SessionId int
Name string
Expand All @@ -33,16 +38,58 @@ type ServerAdResponse struct {
Status string
}

func getIpFromAdvertiserOverride(address string) (string, error) {
// If the address is "auto", we need to check our public IPv4 via ipify
if address == "auto" {
resp, err := http.Get("https://api4.ipify.org")
if err != nil {
return "", err
}
defer resp.Body.Close()
buf := new(bytes.Buffer)
buf.ReadFrom(resp.Body)
return buf.String(), nil
}
// If the address is an IP quad, return it as is
if ip := net.ParseIP(address); ip != nil {
if ip.To4() != nil {
return ip.To4().String(), nil
} else if ip.To16() != nil {
return "", errors.New("IPv6 addresses are not supported for advertiser override")
Comment thread
akirilov marked this conversation as resolved.
}
}
// If the address is a DNS name, resolve it
ips, err := net.LookupIP(address)
if err != nil {
return "", err
}
// Return the first resolved IPv4 address
for _, ip := range ips {
if ip.To4() != nil {
return ip.To4().String(), nil
}
}
// If the address is invalid, return an error
return "", errors.New("unable to resolve IP from advertiser override")
}

func StartAdvertiser() {
if config.GetServerVisible() {
logger.Advertiser.Warn("Server advertisement is enabled. Disable it in the config and restart SSUI to use manual advertisement. Skipping for now...")
return
}
go func() {
sessionId := -1
// Track accumulated transient errors and kill the advertiser if we exceed a threshold
transientErrors := 0
for {
// Only advertise if we are running
if gamemgr.InternalIsServerRunning() {
// If we have exceeded the max transient errors, exit the advertiser
if transientErrors >= maxTransientErrors {
logger.Advertiser.Errorf("ServerAdvertiser exceeded max transient errors (%d). Stopping advertiser...", maxTransientErrors)
return
}
// Get max players
maxplayers, err := strconv.Atoi(config.GetServerMaxPlayers())
if err != nil {
Expand All @@ -60,51 +107,74 @@ func StartAdvertiser() {
case "linux":
platform = 2
}
// Get IP address
ipAddress, err := getIpFromAdvertiserOverride(config.GetAdvertiserOverride())
if err != nil {
logger.Advertiser.Warnf("ServerAdvertiser failed to get IP address from config value '%s': %v", config.GetAdvertiserOverride(), err)
transientErrors++
time.Sleep(advertiserIntervalSeconds * time.Second)
continue
}
adMessage := ServerAdMessage{
SessionId: sessionId,
Name: config.GetServerName(),
Password: config.GetServerPassword() != "",
Version: config.GetExtractedGameVersion(),
Address: config.GetOverrideAdvertisedIp(),
Address: ipAddress,
Port: config.GetGamePort(),
Players: players,
MaxPlayers: maxplayers,
Type: platform,
}
body, err := json.Marshal(adMessage)
if err != nil {
logger.Advertiser.Errorf("ServerAdvertiser failed to Serialize to JSON from native Go struct type: %v", err)
return
logger.Advertiser.Warnf("ServerAdvertiser failed to Serialize to JSON from native Go struct type: %v", err)
transientErrors++
time.Sleep(advertiserIntervalSeconds * time.Second)
continue
}
// Send advertisement
resp, err := http.Post(StationeersAdvertisementEndpoint, "application/json", bytes.NewBuffer(body))
// Check for errors
if err != nil {
logger.Advertiser.Errorf("ServerAdvertiser failed to send request: %v", err)
return
logger.Advertiser.Warnf("ServerAdvertiser failed to send request: %v", err)
transientErrors++
time.Sleep(advertiserIntervalSeconds * time.Second)
continue
}
defer resp.Body.Close()
// Check the status
// Check for non-200 status codes
if resp.StatusCode != 200 {
logger.Advertiser.Warnf("ServerAdvertiser received non-200 status: %d", resp.StatusCode)
transientErrors++
time.Sleep(advertiserIntervalSeconds * time.Second)
resp.Body.Close() // Close the response body
continue
}
// Read the response and update our sessionId if needed
adResponse := ServerAdResponse{}
err = json.NewDecoder(resp.Body).Decode(&adResponse)
resp.Body.Close()
if err != nil {
logger.Advertiser.Errorf("Failed to decode response body: %v", err)
return
logger.Advertiser.Warnf("Failed to decode response body: %v", err)
transientErrors++
time.Sleep(advertiserIntervalSeconds * time.Second)
continue
}
if adResponse.Status != "Success" {
logger.Advertiser.Warnf("ServerAdvertiser received unexpected status: %s", adResponse.Status)
transientErrors++
time.Sleep(advertiserIntervalSeconds * time.Second)
continue
}
// Reset transient errors on success
sessionId = adResponse.SessionId
transientErrors = 0
} else {
// Reset sessionid for the next run
sessionId = -1
}
// Sleep for 30 seconds to follow the standard advertisement timer
time.Sleep(30 * time.Second)
time.Sleep(advertiserIntervalSeconds * time.Second)
}
}()
}
14 changes: 7 additions & 7 deletions src/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,11 @@ type JsonConfig struct {
StartLocation string `json:"StartLocation"`

// Logging and debug settings
Debug *bool `json:"Debug"`
CreateSSUILogFile *bool `json:"CreateSSUILogFile"`
LogLevel int `json:"LogLevel"`
SubsystemFilters []string `json:"subsystemFilters"`
OverrideAdvertisedIp string `json:"OverrideAdvertisedIp"`
Debug *bool `json:"Debug"`
CreateSSUILogFile *bool `json:"CreateSSUILogFile"`
LogLevel int `json:"LogLevel"`
SubsystemFilters []string `json:"subsystemFilters"`
AdvertiserOverride string `json:"AdvertiserOverride"`

// Authentication Settings
Users map[string]string `json:"users"` // Map of username to hashed password
Expand Down Expand Up @@ -297,7 +297,7 @@ func applyConfig(cfg *JsonConfig) {
// use Safebackups folder either way.
ConfiguredSafeBackupDir = filepath.Join("./saves/", SaveName, "Safebackups")

OverrideAdvertisedIp = getString(cfg.OverrideAdvertisedIp, "OVERRIDE_ADVERTISED_IP", "")
AdvertiserOverride = getString(cfg.AdvertiserOverride, "ADVERTISER_OVERRIDE", "")

safeSaveConfig()
}
Expand Down Expand Up @@ -368,7 +368,7 @@ func safeSaveConfig() error {
AutoStartServerOnStartup: &AutoStartServerOnStartup,
SSUIIdentifier: SSUIIdentifier,
SSUIWebPort: SSUIWebPort,
OverrideAdvertisedIp: OverrideAdvertisedIp,
AdvertiserOverride: AdvertiserOverride,
}

file, err := os.Create(ConfigPath)
Expand Down
4 changes: 2 additions & 2 deletions src/config/getters.go
Original file line number Diff line number Diff line change
Expand Up @@ -519,10 +519,10 @@ func GetIsGameServerRunning() bool {
defer ConfigMu.RUnlock()
return IsGameServerRunning
}
func GetOverrideAdvertisedIp() string {
func GetAdvertiserOverride() string {
ConfigMu.RLock()
defer ConfigMu.RUnlock()
return OverrideAdvertisedIp
return AdvertiserOverride
}

func GetStationeersServerPingEndpoint() string {
Expand Down
4 changes: 2 additions & 2 deletions src/config/setters.go
Original file line number Diff line number Diff line change
Expand Up @@ -703,10 +703,10 @@ func SetAllowAutoGameServerUpdates(value bool) error {
return safeSaveConfig()
}

func SetOverrideAdvertisedIp(value string) error {
func SetAdvertiserOverride(value string) error {
ConfigMu.Lock()
defer ConfigMu.Unlock()

OverrideAdvertisedIp = value
AdvertiserOverride = value
return safeSaveConfig()
}
2 changes: 1 addition & 1 deletion src/config/vars.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ var (
LanguageSetting string
AutoStartServerOnStartup bool
SSUIIdentifier string
OverrideAdvertisedIp string
AdvertiserOverride string
)

// Runtime only variables
Expand Down
16 changes: 8 additions & 8 deletions src/core/loader/cmdargs.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ var recoveryPasswordFlag string
var devModeFlag bool
var skipSteamCMDFlag bool
var sanityCheckFlag bool
var overrideAdvertisedIpFlag string
var advertiserOverrideFlag string

// ParseFlags parses command-line arguments ONCE at startup (called from func main)
func ParseFlags() {
Expand All @@ -40,7 +40,7 @@ func ParseFlags() {
flag.BoolVar(&createSSUILogFileFlag, "lf", false, "(Alias) Create log files for SSUI")
flag.BoolVar(&skipSteamCMDFlag, "NoSteamCMD", false, "Skips SteamCMD installation")
flag.BoolVar(&sanityCheckFlag, "NoSanityCheck", false, "Skips the sanity check. Not recommended.")
flag.StringVar(&overrideAdvertisedIpFlag, "OverrideAdvertisedIp", "", "Override the advertised server IP (to allow server advertisement if you are behind a reverse proxy)")
flag.StringVar(&advertiserOverrideFlag, "AdvertiserOverride", "", "Override the advertised server IP. For this, the ServerVisible setting must be set to false. Use \"auto\" for automatic public IP detection, an IPv4 address, or a DNS hostname (to allow server advertisement if you are behind a reverse proxy)")

// Parse command-line flags
flag.Parse()
Expand Down Expand Up @@ -102,15 +102,15 @@ func HandleFlags() {
logger.Main.Info(fmt.Sprintf("Overriding IsDebugMode from command line: Before=%t, Now=true", oldDebug))
}

if overrideAdvertisedIpFlag != "" {
oldOverrideAdvertisedIp := config.GetOverrideAdvertisedIp()
if advertiserOverrideFlag != "" {
oldAdvertiserOverride := config.GetAdvertiserOverride()

if overrideAdvertisedIpFlag == oldOverrideAdvertisedIp {
logger.Advertiser.Info(fmt.Sprintf("Advertised Server IP is already set to %s", overrideAdvertisedIpFlag))
if advertiserOverrideFlag == oldAdvertiserOverride {
logger.Advertiser.Info(fmt.Sprintf("Advertised Server IP is already set to %s", advertiserOverrideFlag))
return
}
config.SetOverrideAdvertisedIp(overrideAdvertisedIpFlag)
logger.Advertiser.Info(fmt.Sprintf("Overriding Advertised Server IP from command line: Before=%s, Now=%s", oldOverrideAdvertisedIp, overrideAdvertisedIpFlag))
config.SetAdvertiserOverride(advertiserOverrideFlag)
logger.Advertiser.Info(fmt.Sprintf("Overriding Advertised Server IP from command line: Before=%s, Now=%s", oldAdvertiserOverride, advertiserOverrideFlag))
}

if createSSUILogFileFlag {
Expand Down
2 changes: 1 addition & 1 deletion src/core/loader/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ func ReloadAppInfoPoller() {
}

func LoadAdvertiser() {
if config.GetOverrideAdvertisedIp() != "" {
if config.GetAdvertiserOverride() != "" {
logger.Advertiser.Info("Starting server advertiser...")
advertiser.StartAdvertiser()
}
Expand Down