diff options
author | Himbeer <himbeer@disroot.org> | 2024-10-13 22:39:14 +0200 |
---|---|---|
committer | Himbeer <himbeer@disroot.org> | 2024-10-13 22:44:46 +0200 |
commit | e3048097f2e618061d8f3d7b2c91b0f5783133a4 (patch) | |
tree | f53c0661c3108eb10adc3817340410955ef69288 | |
parent | 542955410600d337d595fc3b63910d8716e6a19f (diff) |
Overhaul fallback logic
Fallback is now set up by configuring individual servers to build up a
fallback chain. Each server now only accepts a single fallback server
name and global fallback server definitions have been removed entirely.
Fallback is attempted if there is a kick or if the RUDP connection is
lost. If following the fallback chain is unsuccessful for any reason,
the client is disconnected with the original kick sent by the original
server or an error message about the loss of connection. If fallback
cannot be initiated at any part of the chain (e.g. due to the client not
being connected to a server in the first place) the client is
disconnected with an error message.
This commit fixes #150.
-rw-r--r-- | client_conn.go | 3 | ||||
-rw-r--r-- | config.go | 30 | ||||
-rw-r--r-- | connect.go | 11 | ||||
-rw-r--r-- | doc/config.md | 23 | ||||
-rw-r--r-- | doc/server_groups.md | 2 | ||||
-rw-r--r-- | fallback.go | 61 | ||||
-rw-r--r-- | hop.go | 23 | ||||
-rw-r--r-- | process.go | 12 | ||||
-rw-r--r-- | run.go | 16 | ||||
-rw-r--r-- | server_conn.go | 44 |
10 files changed, 116 insertions, 109 deletions
diff --git a/client_conn.go b/client_conn.go index 951fa95..9753147 100644 --- a/client_conn.go +++ b/client_conn.go @@ -40,6 +40,9 @@ type ClientConn struct { salt, srpA, srpB, srpM, srpK []byte } + fallbackFrom string + whyKicked *mt.ToCltKick + lang string major, minor, patch uint8 @@ -29,7 +29,7 @@ type Server struct { Addr string MediaPool string Groups []string - Fallbacks []string + Fallback string dynamic bool poolAdded time.Time @@ -53,7 +53,6 @@ type Config struct { Servers map[string]Server ForceDefaultSrv bool KickOnNewPool bool - FallbackServers []string CSMRF struct { NoCSMs bool ChatMsgs bool @@ -165,9 +164,6 @@ func (cnf Config) clone() Config { newConfig.Servers = copyMap(cnf.Servers) - newConfig.FallbackServers = make([]string, len(cnf.FallbackServers)) - copy(newConfig.FallbackServers, cnf.FallbackServers) - newConfig.Groups = copyMapSlice(cnf.Groups) newConfig.UserGroups = copyMap(cnf.UserGroups) @@ -286,29 +282,6 @@ func (cnf Config) RandomGroupServer(search string) (string, bool) { 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 { - conf := Conf() - - srv, ok := conf.Servers[server] - if !ok { - return nil - } - - fallbacks := srv.Fallbacks - fallbacks = append(fallbacks, conf.FallbackServers...) - - final := make([]string, 0, len(fallbacks)) - for _, srvName := range fallbacks { - if srvName != server { - final = append(final, srvName) - } - } - - return final -} - // LoadConfig attempts to parse the configuration file. // It leaves the config unchanged if there is an error // and returns the error. @@ -325,7 +298,6 @@ func LoadConfig() error { config.TelnetAddr = defaultTelnetAddr config.BindAddr = defaultBindAddr config.Servers = make(map[string]Server) - config.FallbackServers = make([]string, 0) config.Groups = make(map[string][]string) config.UserGroups = make(map[string]string) config.List.Interval = defaultListInterval @@ -4,6 +4,7 @@ import ( "fmt" "log" "net" + "time" "github.com/HimbeerserverDE/mt" ) @@ -42,8 +43,18 @@ func connect(conn net.Conn, name string, cc *ClientConn) *ServerConn { cc.mu.Lock() cc.srv = sc + cc.fallbackFrom = sc.name cc.mu.Unlock() + // Mark fallback as done after a connection has persisted for some time. + go func() { + time.Sleep(10 * time.Second) + + if cc.srv == sc { + cc.whyKicked = nil + } + }() + go handleSrv(sc) return sc } diff --git a/doc/config.md b/doc/config.md index 18a4006..e9e4e01 100644 --- a/doc/config.md +++ b/doc/config.md @@ -140,15 +140,13 @@ See [server_groups.md](https://github.com/HimbeerserverDE/mt-multiserver-proxy/b for more information. ``` -> `Server.Fallbacks` +> `Server.Fallback` ``` -Type: []string -Default: []string{} -Description: The names of the servers a client should fall back to -if this server shuts down or crashes gracefully. Connection attempts -are made in the order in which the servers are given. As soon as -a connection is successful the other fallback servers in this list -will be ignored. +Type: string +Default: "" +Description: The name of the server a client should fall back to if this server +shuts down or crashes gracefully. Connection attempts are made in the order in +which the servers are given. ``` > `ForceDefaultSrv` @@ -224,15 +222,6 @@ Default: 0 Description: The maximum distance from which CSMs can read the map. ``` -> `FallbackServers` -``` -Type: []string -Default: []string{} -Description: Names of general fallback servers to connect to -if a connection attempt fails or an existing connection -to a game server is lost. -``` - > `DropCSMRF` ``` Type: bool diff --git a/doc/server_groups.md b/doc/server_groups.md index 4a0140a..14b9b86 100644 --- a/doc/server_groups.md +++ b/doc/server_groups.md @@ -5,7 +5,7 @@ in the `Groups` subfield of the server definition in the config. Configuration options that support server groups will randomly choose from their member servers every time they are applied to a client. -Neither local nor global fallback servers can be server groups. +Fallback servers cannot be server groups. If there is a server group with the same name as a regular server, the regular server is preferred, rendering the group inaccessible. diff --git a/fallback.go b/fallback.go new file mode 100644 index 0000000..729a4e4 --- /dev/null +++ b/fallback.go @@ -0,0 +1,61 @@ +package proxy + +import "github.com/HimbeerserverDE/mt" + +func (cc *ClientConn) fallback() bool { + if cc.fallbackFrom == "" { + return false + } + + fallback := config.Servers[cc.fallbackFrom].Fallback + if fallback == "" { + ack, _ := cc.SendCmd(cc.whyKicked) + + select { + case <-cc.Closed(): + case <-ack: + cc.Close() + + cc.mu.Lock() + cc.srv = nil + cc.mu.Unlock() + + if srv := cc.server(); srv != nil { + srv.mu.Lock() + srv.clt = nil + srv.mu.Unlock() + } + } + + return true + } + + // Use HopRaw so the fallback server doesn't get saved + // as the last server. + if err := cc.HopRaw(fallback); err != nil { + cc.Log("<-", "fallback fail:", err) + + ack, _ := cc.SendCmd(&mt.ToCltKick{ + Reason: mt.Custom, + Custom: "Fallback failed.", + }) + + select { + case <-cc.Closed(): + case <-ack: + cc.Close() + + cc.mu.Lock() + cc.srv = nil + cc.mu.Unlock() + + if srv := cc.server(); srv != nil { + srv.mu.Lock() + srv.clt = nil + srv.mu.Unlock() + } + } + } + + return true +} @@ -28,27 +28,8 @@ func (cc *ClientConn) Hop(serverName string) (err error) { } }() - if err = cc.HopRaw(serverName); err != nil { - if errors.Is(err, ErrNoSuchServer) || errors.Is(err, ErrNewMediaPool) { - return err - } - - cc.Log("<-", err) - cc.SendChatMsg("Could not switch servers, triggering fallback. Error:", err.Error()) - - for _, srvName := range FallbackServers(serverName) { - if err = cc.HopRaw(srvName); err != nil { - cc.Log("<-", err) - cc.SendChatMsg("Could not connect, continuing fallback. Error:", err.Error()) - } - - return nil - } - - return err - } - - return nil + err = cc.HopRaw(serverName) + return } // HopGroup connects the ClientConn to the specified server group @@ -568,16 +568,12 @@ func (sc *ServerConn) process(pkt mt.Pkt) { sc.Log("<-", "deny access", cmd) if cmd.Reason == mt.Shutdown || cmd.Reason == mt.Crash || cmd.Reason == mt.SrvErr || cmd.Reason == mt.TooManyClts || cmd.Reason == mt.UnsupportedVer { - clt.SendChatMsg("A kick occured, triggering fallback. Reason:", cmd.String()) + clt.SendChatMsg("A kick occured, switching to fallback server. Reason:", cmd) - for _, srvName := range FallbackServers(sc.name) { - if err := clt.HopRaw(srvName); err != nil { - clt.Log("<-", err) - clt.SendChatMsg("Could not connect to "+srvName+", continuing fallback. Error:", err.Error()) - } + clt.whyKicked = cmd - return - } + clt.fallback() + return } ack, _ := clt.SendCmd(cmd) @@ -156,18 +156,24 @@ func runFunc() { if err := doConnect(srvName, srv); err != nil { cc.Log("<-", err) - cc.SendChatMsg("Could not connect, triggering fallback. Error:", err.Error()) + cc.SendChatMsg("Could not connect, trying fallback server. Error:", err) - for _, fbName := range FallbackServers(srvName) { - fb, ok := conf.Servers[fbName] + fbName := srv.Fallback + for fbName != "" { + var ok bool + srv, ok = conf.Servers[fbName] if !ok { cc.Log("<-", "invalid fallback") continue } - if err := doConnect(fbName, fb); err != nil { + if err := doConnect(fbName, srv); err != nil { + fbName = srv.Fallback + cc.Log("<-", err) - cc.SendChatMsg("Could not connect, continuing fallback. Error:", err.Error()) + cc.SendChatMsg("Could not connect, trying next fallback server. Error:", err.Error()) + } else { + break } } } diff --git a/server_conn.go b/server_conn.go index ec3164f..f451cd5 100644 --- a/server_conn.go +++ b/server_conn.go @@ -104,7 +104,6 @@ func handleSrv(sc *ServerConn) { } }() -RecvLoop: for { pkt, err := sc.Recv() if err != nil { @@ -117,38 +116,27 @@ RecvLoop: if sc.client() != nil { if errors.Is(sc.WhyClosed(), rudp.ErrTimedOut) { - sc.client().SendChatMsg("Server connection timed out, triggering fallback.") - } else { - sc.client().SendChatMsg("Server connection lost, triggering fallback.") - } + sc.client().SendChatMsg("Server connection timed out, switching to fallback server.") - for _, srvName := range FallbackServers(sc.name) { - if err := sc.client().HopRaw(srvName); err != nil { - sc.client().Log("<-", err) - sc.client().SendChatMsg("Could not connect to "+srvName+", continuing fallback. Error:", err.Error()) + if sc.client().whyKicked == nil { + sc.client().whyKicked = &mt.ToCltKick{ + Reason: mt.Custom, + Custom: "Server connection timed out.", + } } + } else { + sc.client().SendChatMsg("Server connection lost, switching to fallback server.") - break RecvLoop + if sc.client().whyKicked == nil { + sc.client().whyKicked = &mt.ToCltKick{ + Reason: mt.Custom, + Custom: "Server connection lost.", + } + } } - ack, _ := sc.client().SendCmd(&mt.ToCltKick{ - Reason: mt.Custom, - Custom: "Server connection closed unexpectedly.", - }) - - select { - case <-sc.client().Closed(): - case <-ack: - sc.client().Close() - - sc.client().mu.Lock() - sc.client().srv = nil - sc.client().mu.Unlock() - - sc.mu.Lock() - sc.clt = nil - sc.mu.Unlock() - } + sc.client().fallback() + break } break |