diff --git a/config.go b/config.go index cf3c229..d270f77 100644 --- a/config.go +++ b/config.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "log" + "math/rand" "os" "sync" "time" @@ -27,6 +28,7 @@ var loadConfigOnce sync.Once type Server struct { Addr string MediaPool string + Groups []string Fallbacks []string dynamic bool @@ -236,6 +238,32 @@ func (cnf Config) Pools() map[string]map[string]Server { return pools } +// RandomGroupServer returns the name of a random member of a server group +// or the input string if it is a valid, existent server name. +// It also returns a boolean indicating success. +// The returned string is blank if there is a failure, +// i.e. if the input string is neither a server nor a group. +func (cnf Config) RandomGroupServer(search string) (string, bool) { + candidates := make([]string, 0) + for name, srv := range cnf.Servers { + if name == search { + return name, true + } + + for _, grp := range srv.Groups { + if grp == search { + candidates = append(candidates, name) + } + } + } + + if len(candidates) == 0 { + return "", false + } + + return candidates[rand.Intn(len(candidates))], true +} + // FallbackServers returns a slice of server names that // a server can fall back to. func FallbackServers(server string) []string { diff --git a/doc/config.md b/doc/config.md index 747c604..c114d42 100644 --- a/doc/config.md +++ b/doc/config.md @@ -117,6 +117,17 @@ See [media_pools.md](https://github.com/HimbeerserverDE/mt-multiserver-proxy/blo for more information. ``` +> `Server.Groups` +``` +Type: []string +Default: []string{} +Description: The server groups this server is in. Group hopping and default server +selection accept server groups on top of regular server names, +randomly choosing one of its servers. If a server name and a group name +are in conflict, the server name is preferred. +This feature can be used to implement simple load balancing. +``` + > `Server.Fallbacks` ``` Type: []string diff --git a/hop.go b/hop.go index a9f5c7b..a84b583 100644 --- a/hop.go +++ b/hop.go @@ -15,7 +15,8 @@ var ( ) // Hop connects the ClientConn to the specified upstream server -// or the first working fallback server, saving the player's last server. +// or the first working fallback server, saving the player's last server +// unless `ForceDefaultSrv` is enabled. // If all attempts fail the client is kicked. // At the moment the ClientConn is NOT fixed if an error occurs // so the player may have to reconnect. @@ -49,6 +50,23 @@ func (cc *ClientConn) Hop(serverName string) (err error) { return nil } +// HopGroup connects the ClientConn to the specified server group +// or the first working fallback server, saving the player's last server +// unless `ForceDefaultSrv` is enabled. +// See the documentation on `Server.Groups` in `doc/config.md` +// for details on how a specific game server is selected from the group name. +// If all attempts fail the client is kicked. +// At the moment the ClientConn is NOT fixed if an error occurs +// so the player may have to reconnect. +func (cc *ClientConn) HopGroup(groupName string) error { + choice, ok := Conf().RandomGroupServer(groupName) + if !ok { + return ErrNoSuchServer + } + + return cc.Hop(choice) +} + // HopRaw connects the ClientConn to the specified upstream server. // At the moment the ClientConn is NOT fixed if an error occurs // so the player may have to reconnect. diff --git a/run.go b/run.go index 8bed44a..b017b67 100644 --- a/run.go +++ b/run.go @@ -120,15 +120,14 @@ func runFunc() { srvName, srv := conf.DefaultServerInfo() lastSrv, err := authIface.LastSrv(cc.Name()) - if err == nil && !Conf().ForceDefaultSrv && lastSrv != srvName { - for name, s := range conf.Servers { - if name == lastSrv { - srvName = name - srv = s - - break - } + if err == nil && !conf.ForceDefaultSrv && lastSrv != srvName { + choice, ok := conf.RandomGroupServer(lastSrv) + if !ok { + cc.Log("<-", "inexistent previous server") } + + srvName = choice + srv, _ = conf.Servers[choice] // Existence already checked. } addr, err := net.ResolveUDPAddr("udp", srv.Addr)