From 7e72caa092194306ba6b5c6b58e6262fdd1fd72c Mon Sep 17 00:00:00 2001 From: HimbeerserverDE Date: Fri, 10 Sep 2021 12:47:19 +0200 Subject: [PATCH] Document exported code (#49) --- auth.go | 6 +++--- auth_sqlite3.go | 30 ++++++++++++++++++++---------- chat.go | 2 ++ client_conn.go | 8 ++++++++ cmd/mt-multiserver-proxy/proxy.go | 3 +++ config.go | 13 +++++++------ connect.go | 2 +- gen_textures.go | 5 +++-- hop.go | 7 +++++-- listen.go | 6 +++--- log.go | 2 ++ perms.go | 5 +++-- players.go | 7 ++++++- plugin_chatcmd.go | 8 ++++++-- proxy.go | 11 +++++++++++ run.go | 12 +++++++----- server_conn.go | 5 +++++ 17 files changed, 95 insertions(+), 37 deletions(-) create mode 100644 proxy.go diff --git a/auth.go b/auth.go index 3334ebf..4feec77 100644 --- a/auth.go +++ b/auth.go @@ -5,7 +5,7 @@ import ( "time" ) -var authIface AuthBackend +var authIface authBackend var ErrAuthBackendExists = errors.New("auth backend already set") type user struct { @@ -15,7 +15,7 @@ type user struct { timestamp time.Time } -type AuthBackend interface { +type authBackend interface { Exists(name string) bool Passwd(name string) (salt, verifier []byte, err error) SetPasswd(name string, salt, verifier []byte) error @@ -24,7 +24,7 @@ type AuthBackend interface { Export() ([]user, error) } -func SetAuthBackend(ab AuthBackend) error { +func setAuthBackend(ab authBackend) error { if authIface != nil { return ErrAuthBackendExists } diff --git a/auth_sqlite3.go b/auth_sqlite3.go index 8574768..4ad9724 100644 --- a/auth_sqlite3.go +++ b/auth_sqlite3.go @@ -9,11 +9,12 @@ import ( _ "github.com/mattn/go-sqlite3" ) -type AuthSQLite3 struct { +type authSQLite3 struct { db *sql.DB } -func (a AuthSQLite3) Exists(name string) bool { +// Exists reports whether a user is registered. +func (a authSQLite3) Exists(name string) bool { if err := a.init(); err != nil { return false } @@ -24,7 +25,8 @@ func (a AuthSQLite3) Exists(name string) bool { return err == nil } -func (a AuthSQLite3) Passwd(name string) (salt, verifier []byte, err error) { +// Passwd returns the SRP salt and verifier of a user or an error. +func (a authSQLite3) Passwd(name string) (salt, verifier []byte, err error) { if err = a.init(); err != nil { return } @@ -35,7 +37,9 @@ func (a AuthSQLite3) Passwd(name string) (salt, verifier []byte, err error) { return } -func (a AuthSQLite3) SetPasswd(name string, salt, verifier []byte) error { +// SetPasswd creates a password entry if necessary +// and sets the password of a user. +func (a authSQLite3) SetPasswd(name string, salt, verifier []byte) error { if err := a.init(); err != nil { return err } @@ -50,7 +54,9 @@ func (a AuthSQLite3) SetPasswd(name string, salt, verifier []byte) error { return nil } -func (a AuthSQLite3) Timestamp(name string) (time.Time, error) { +// Timestamp returns the last time an authentication entry was accessed +// or an error. +func (a authSQLite3) Timestamp(name string) (time.Time, error) { if err := a.init(); err != nil { return time.Time{}, err } @@ -65,7 +71,9 @@ func (a AuthSQLite3) Timestamp(name string) (time.Time, error) { return time.Parse("2006-01-02 15:04:05", tstr) } -func (a AuthSQLite3) Import(in []user) { +// Import clears the database and and refills it with the passed +// users. +func (a authSQLite3) Import(in []user) { if err := a.init(); err != nil { return } @@ -78,7 +86,9 @@ func (a AuthSQLite3) Import(in []user) { } } -func (a AuthSQLite3) Export() ([]user, error) { +// Export returns data that can be processed by Import +// or an error. +func (a authSQLite3) Export() ([]user, error) { if err := a.init(); err != nil { return nil, err } @@ -119,11 +129,11 @@ func (a AuthSQLite3) Export() ([]user, error) { return out, nil } -func (a AuthSQLite3) updateTimestamp(name string) { +func (a authSQLite3) updateTimestamp(name string) { a.db.Exec(`UPDATE user SET timestamp = datetime("now") WHERE name = ?;`, name) } -func (a *AuthSQLite3) init() error { +func (a *authSQLite3) init() error { executable, err := os.Executable() if err != nil { return err @@ -148,6 +158,6 @@ func (a *AuthSQLite3) init() error { return nil } -func (a AuthSQLite3) close() error { +func (a authSQLite3) close() error { return a.db.Close() } diff --git a/chat.go b/chat.go index e355b2a..cf75594 100644 --- a/chat.go +++ b/chat.go @@ -8,6 +8,7 @@ import ( "github.com/anon55555/mt" ) +// SendChatMsg sends a chat message to the ClientConn. func (cc *ClientConn) SendChatMsg(msg ...string) { cc.SendCmd(&mt.ToCltChatMsg{ Type: mt.SysMsg, @@ -16,6 +17,7 @@ func (cc *ClientConn) SendChatMsg(msg ...string) { }) } +// Colorize returns the minetest-colorized version of the input. func Colorize(text, color string) string { return string(0x1b) + "(c@" + color + ")" + text + string(0x1b) + "(c@#FFF)" } diff --git a/client_conn.go b/client_conn.go index f21d48c..343b323 100644 --- a/client_conn.go +++ b/client_conn.go @@ -23,6 +23,7 @@ const ( csSudo ) +// A ClientConn is a connection to a minetest client. type ClientConn struct { mt.Peer srv *ServerConn @@ -60,6 +61,7 @@ type ClientConn struct { modChs map[string]struct{} } +// Name returns the player name of the ClientConn. func (cc *ClientConn) Name() string { return cc.name } func (cc *ClientConn) server() *ServerConn { @@ -69,6 +71,8 @@ func (cc *ClientConn) server() *ServerConn { return cc.srv } +// ServerName returns the name of the current upstream server +// of the ClientConn. It is empty if there is no upstream connection. func (cc *ClientConn) ServerName() string { srv := cc.server() if srv != nil { @@ -92,8 +96,12 @@ func (cc *ClientConn) setState(state clientState) { cc.cstate = state } +// Init returns a channel that is closed +// when the ClientConn enters the csActive state. func (cc *ClientConn) Init() <-chan struct{} { return cc.initCh } +// Log logs an interaction with the ClientConn. +// dir indicates the direction of the interaction. func (cc *ClientConn) Log(dir string, v ...interface{}) { if cc.Name() != "" { format := "{%s, %s} %s {←|⇶}" diff --git a/cmd/mt-multiserver-proxy/proxy.go b/cmd/mt-multiserver-proxy/proxy.go index 0d735be..f706643 100644 --- a/cmd/mt-multiserver-proxy/proxy.go +++ b/cmd/mt-multiserver-proxy/proxy.go @@ -1,3 +1,6 @@ +/* +mt-multiserver-proxy starts the reverse proxy. +*/ package main import "github.com/HimbeerserverDE/mt-multiserver-proxy" diff --git a/config.go b/config.go index 3e5ab11..4fc8874 100644 --- a/config.go +++ b/config.go @@ -8,12 +8,6 @@ import ( "sync" ) -const latestSerializeVer = 28 -const latestProtoVer = 39 -const maxPlayerNameLen = 20 -const playerNameChars = "^[a-zA-Z0-9-_]+$" -const bytesPerMediaBunch = 5000 - const defaultCmdPrefix = ">" const defaultSendInterval = 0.09 const defaultUserLimit = 10 @@ -23,6 +17,8 @@ const defaultBindAddr = ":40000" var config Config var configMu sync.RWMutex +// A Config contains information from the configuration file +// that affects the way the proxy works. type Config struct { NoPlugins bool CmdPrefix string @@ -48,6 +44,8 @@ type Config struct { UserGroups map[string]string } +// Conf returns a copy of the Config used by the proxy. +// Any modifications will not affect the original Config. func Conf() Config { configMu.RLock() defer configMu.RUnlock() @@ -55,6 +53,9 @@ func Conf() Config { return config } +// LoadConfig attempts to parse the configuration file. +// It leaves the config unchanged if there is an error +// and returns the error. func LoadConfig() error { configMu.Lock() defer configMu.Unlock() diff --git a/connect.go b/connect.go index 4aa7a5d..7dfe277 100644 --- a/connect.go +++ b/connect.go @@ -6,7 +6,7 @@ import ( "github.com/anon55555/mt" ) -func Connect(conn net.Conn, name string, cc *ClientConn) *ServerConn { +func connect(conn net.Conn, name string, cc *ClientConn) *ServerConn { cc.mu.RLock() if cc.srv != nil { cc.Log("<->", "already connected to server") diff --git a/gen_textures.go b/gen_textures.go index f2a9517..9838d10 100644 --- a/gen_textures.go +++ b/gen_textures.go @@ -1,7 +1,7 @@ //go:build ignore -// This program generates default_textures.go. It can be invoked -// by running go generate +// gen_textures generates default_textures.go. It can be invoked +// by running go generate. package main import ( @@ -23,6 +23,7 @@ func main() { } defer f.Close() + f.WriteString("// Code generated by gen_textures.go. DO NOT EDIT.\n") f.WriteString("package proxy\n") f.WriteString("\n") f.WriteString("var defaultTextures = []mediaFile{\n") diff --git a/hop.go b/hop.go index b54277e..c065e16 100644 --- a/hop.go +++ b/hop.go @@ -8,6 +8,9 @@ import ( "github.com/anon55555/mt" ) +// Hop 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. func (cc *ClientConn) Hop(serverName string) error { cc.hopMu.Lock() defer cc.hopMu.Unlock() @@ -32,7 +35,7 @@ func (cc *ClientConn) Hop(serverName string) error { return fmt.Errorf("inexistent server") } - // This needs to be done before the serverConn is closed + // This needs to be done before the ServerConn is closed // so the clientConn isn't closed by the packet handler cc.server().mu.Lock() cc.server().clt = nil @@ -144,7 +147,7 @@ func (cc *ClientConn) Hop(serverName string) error { return err } - Connect(conn, serverName, cc) + connect(conn, serverName, cc) for ch := range cc.modChs { cc.server().SendCmd(&mt.ToSrvJoinModChan{Channel: ch}) diff --git a/listen.go b/listen.go index 72fa838..a1349d8 100644 --- a/listen.go +++ b/listen.go @@ -32,7 +32,7 @@ type listener struct { clts map[*ClientConn]struct{} } -func Listen(pc net.PacketConn) *listener { +func listen(pc net.PacketConn) *listener { l := &listener{ Listener: mt.Listen(pc), clts: make(map[*ClientConn]struct{}), @@ -49,7 +49,7 @@ func Listen(pc net.PacketConn) *listener { return l } -func (l *listener) Clts() map[*ClientConn]struct{} { +func (l *listener) clients() map[*ClientConn]struct{} { clts := make(map[*ClientConn]struct{}) l.mu.RLock() @@ -62,7 +62,7 @@ func (l *listener) Clts() map[*ClientConn]struct{} { return clts } -func (l *listener) Accept() (*ClientConn, error) { +func (l *listener) accept() (*ClientConn, error) { p, err := l.Listener.Accept() if err != nil { return nil, err diff --git a/log.go b/log.go index 2e16a73..bf8d68a 100644 --- a/log.go +++ b/log.go @@ -10,6 +10,8 @@ type LogWriter struct { f *os.File } +// Write writes the input data to os.Stderr and the log file. +// It returns the number of bytes written and an error. func (lw *LogWriter) Write(p []byte) (n int, err error) { n, err = os.Stderr.Write(p) if err != nil { diff --git a/perms.go b/perms.go index 9190376..85091ae 100644 --- a/perms.go +++ b/perms.go @@ -1,7 +1,6 @@ package proxy -import () - +// Perms returns the permissions of the ClientConn. func (cc *ClientConn) Perms() []string { if cc.Name() == "" { return []string{} @@ -19,6 +18,8 @@ func (cc *ClientConn) Perms() []string { return []string{} } +// HasPerms returns true if the ClientConn has all +// of the specified permissions. Otherwise it returns false. func (cc *ClientConn) HasPerms(want ...string) bool { has := make(map[string]struct{}) for _, perm := range cc.Perms() { diff --git a/players.go b/players.go index 16db02a..18c3049 100644 --- a/players.go +++ b/players.go @@ -5,6 +5,8 @@ import "sync" var players = make(map[string]struct{}) var playersMu sync.RWMutex +// Players returns the names of all players +// that are currently connected to the proxy. func Players() map[string]struct{} { playersMu.RLock() defer playersMu.RUnlock() @@ -17,11 +19,12 @@ func Players() map[string]struct{} { return p } +// Clts returns all ClientConns currently connected to the proxy. func Clts() map[*ClientConn]struct{} { clts := make(map[*ClientConn]struct{}) lm := allListeners() for l := range lm { - for clt := range l.Clts() { + for clt := range l.clients() { clts[clt] = struct{}{} } } @@ -29,6 +32,8 @@ func Clts() map[*ClientConn]struct{} { return clts } +// Find returns the ClientConn that has the specified player name. +// If no ClientConn is found, nil is returned. func Find(name string) *ClientConn { for clt := range Clts() { if clt.Name() == name { diff --git a/plugin_chatcmd.go b/plugin_chatcmd.go index 7df2365..85f2e11 100644 --- a/plugin_chatcmd.go +++ b/plugin_chatcmd.go @@ -2,6 +2,7 @@ package proxy import "sync" +// A ChatCmd holds information on how to handle a chat command. type ChatCmd struct { Name string Perm string @@ -14,6 +15,7 @@ var chatCmds map[string]ChatCmd var chatCmdsMu sync.RWMutex var chatCmdsOnce sync.Once +// ChatCmds returns a map of all ChatCmds indexed by their names. func ChatCmds() map[string]ChatCmd { initChatCmds() @@ -28,12 +30,14 @@ func ChatCmds() map[string]ChatCmd { return cmds } +// ChatCmdExists reports if a ChatCmd exists. func ChatCmdExists(name string) bool { - cmds := ChatCmds() - _, ok := cmds[name] + _, ok := ChatCmds()[name] return ok } +// RegisterChatCmd adds a new ChatCmd. It returns true on success +// and false if a command with the same name already exists. func RegisterChatCmd(cmd ChatCmd) bool { initChatCmds() diff --git a/proxy.go b/proxy.go new file mode 100644 index 0000000..d7a03cc --- /dev/null +++ b/proxy.go @@ -0,0 +1,11 @@ +/* +Package proxy is a minetest reverse proxy for multiple servers. +It also provides an API for plugins. +*/ +package proxy + +const latestSerializeVer = 28 +const latestProtoVer = 39 +const maxPlayerNameLen = 20 +const playerNameChars = "^[a-zA-Z0-9-_]+$" +const bytesPerMediaBunch = 5000 diff --git a/run.go b/run.go index cecfd44..19ab895 100644 --- a/run.go +++ b/run.go @@ -12,6 +12,8 @@ import ( "github.com/anon55555/mt" ) +// Run initializes the proxy andstarts the main listener loop. +// It blocks forever. func Run() { if err := LoadConfig(); err != nil { log.Fatal("{←|⇶} ", err) @@ -24,7 +26,7 @@ func Run() { var err error switch Conf().AuthBackend { case "sqlite3": - SetAuthBackend(AuthSQLite3{}) + setAuthBackend(authSQLite3{}) default: log.Fatal("{←|⇶} invalid auth backend") } @@ -39,7 +41,7 @@ func Run() { log.Fatal("{←|⇶} ", err) } - l := Listen(pc) + l := listen(pc) defer l.Close() log.Print("{←|⇶} listen ", l.Addr()) @@ -49,7 +51,7 @@ func Run() { signal.Notify(sig, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP) <-sig - clts := l.Clts() + clts := l.clients() var wg sync.WaitGroup wg.Add(len(clts)) @@ -75,7 +77,7 @@ func Run() { }() for { - cc, err := l.Accept() + cc, err := l.accept() if err != nil { if errors.Is(err, net.ErrClosed) { log.Print("{←|⇶} stop listening") @@ -139,7 +141,7 @@ func Run() { return } - Connect(conn, Conf().Servers[0].Name, cc) + connect(conn, Conf().Servers[0].Name, cc) }() } diff --git a/server_conn.go b/server_conn.go index c527dd6..5c9fa61 100644 --- a/server_conn.go +++ b/server_conn.go @@ -14,6 +14,7 @@ import ( "github.com/anon55555/mt/rudp" ) +// A ServerConn is a connection to a minetest server. type ServerConn struct { mt.Peer clt *ClientConn @@ -63,8 +64,12 @@ func (sc *ServerConn) setState(state clientState) { sc.cstate = state } +// Init returns a channel that is closed +// when the ServerConn enters the csActive state. func (sc *ServerConn) Init() <-chan struct{} { return sc.initCh } +// Log logs an interaction with the ServerConn. +// dir indicates the direction of the interaction. func (sc *ServerConn) Log(dir string, v ...interface{}) { if sc.client() != nil { format := "%s {%s}"