Document exported code (#49)

master
HimbeerserverDE 2021-09-10 12:47:19 +02:00
parent 04d33fd100
commit 7e72caa092
No known key found for this signature in database
GPG Key ID: 1A651504791E6A8B
17 changed files with 95 additions and 37 deletions

View File

@ -5,7 +5,7 @@ import (
"time" "time"
) )
var authIface AuthBackend var authIface authBackend
var ErrAuthBackendExists = errors.New("auth backend already set") var ErrAuthBackendExists = errors.New("auth backend already set")
type user struct { type user struct {
@ -15,7 +15,7 @@ type user struct {
timestamp time.Time timestamp time.Time
} }
type AuthBackend interface { type authBackend interface {
Exists(name string) bool Exists(name string) bool
Passwd(name string) (salt, verifier []byte, err error) Passwd(name string) (salt, verifier []byte, err error)
SetPasswd(name string, salt, verifier []byte) error SetPasswd(name string, salt, verifier []byte) error
@ -24,7 +24,7 @@ type AuthBackend interface {
Export() ([]user, error) Export() ([]user, error)
} }
func SetAuthBackend(ab AuthBackend) error { func setAuthBackend(ab authBackend) error {
if authIface != nil { if authIface != nil {
return ErrAuthBackendExists return ErrAuthBackendExists
} }

View File

@ -9,11 +9,12 @@ import (
_ "github.com/mattn/go-sqlite3" _ "github.com/mattn/go-sqlite3"
) )
type AuthSQLite3 struct { type authSQLite3 struct {
db *sql.DB 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 { if err := a.init(); err != nil {
return false return false
} }
@ -24,7 +25,8 @@ func (a AuthSQLite3) Exists(name string) bool {
return err == nil 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 { if err = a.init(); err != nil {
return return
} }
@ -35,7 +37,9 @@ func (a AuthSQLite3) Passwd(name string) (salt, verifier []byte, err error) {
return 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 { if err := a.init(); err != nil {
return err return err
} }
@ -50,7 +54,9 @@ func (a AuthSQLite3) SetPasswd(name string, salt, verifier []byte) error {
return nil 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 { if err := a.init(); err != nil {
return time.Time{}, err 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) 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 { if err := a.init(); err != nil {
return 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 { if err := a.init(); err != nil {
return nil, err return nil, err
} }
@ -119,11 +129,11 @@ func (a AuthSQLite3) Export() ([]user, error) {
return out, nil 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) 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() executable, err := os.Executable()
if err != nil { if err != nil {
return err return err
@ -148,6 +158,6 @@ func (a *AuthSQLite3) init() error {
return nil return nil
} }
func (a AuthSQLite3) close() error { func (a authSQLite3) close() error {
return a.db.Close() return a.db.Close()
} }

View File

@ -8,6 +8,7 @@ import (
"github.com/anon55555/mt" "github.com/anon55555/mt"
) )
// SendChatMsg sends a chat message to the ClientConn.
func (cc *ClientConn) SendChatMsg(msg ...string) { func (cc *ClientConn) SendChatMsg(msg ...string) {
cc.SendCmd(&mt.ToCltChatMsg{ cc.SendCmd(&mt.ToCltChatMsg{
Type: mt.SysMsg, 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 { func Colorize(text, color string) string {
return string(0x1b) + "(c@" + color + ")" + text + string(0x1b) + "(c@#FFF)" return string(0x1b) + "(c@" + color + ")" + text + string(0x1b) + "(c@#FFF)"
} }

View File

@ -23,6 +23,7 @@ const (
csSudo csSudo
) )
// A ClientConn is a connection to a minetest client.
type ClientConn struct { type ClientConn struct {
mt.Peer mt.Peer
srv *ServerConn srv *ServerConn
@ -60,6 +61,7 @@ type ClientConn struct {
modChs map[string]struct{} modChs map[string]struct{}
} }
// Name returns the player name of the ClientConn.
func (cc *ClientConn) Name() string { return cc.name } func (cc *ClientConn) Name() string { return cc.name }
func (cc *ClientConn) server() *ServerConn { func (cc *ClientConn) server() *ServerConn {
@ -69,6 +71,8 @@ func (cc *ClientConn) server() *ServerConn {
return cc.srv 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 { func (cc *ClientConn) ServerName() string {
srv := cc.server() srv := cc.server()
if srv != nil { if srv != nil {
@ -92,8 +96,12 @@ func (cc *ClientConn) setState(state clientState) {
cc.cstate = state 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 } 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{}) { func (cc *ClientConn) Log(dir string, v ...interface{}) {
if cc.Name() != "" { if cc.Name() != "" {
format := "{%s, %s} %s {←|⇶}" format := "{%s, %s} %s {←|⇶}"

View File

@ -1,3 +1,6 @@
/*
mt-multiserver-proxy starts the reverse proxy.
*/
package main package main
import "github.com/HimbeerserverDE/mt-multiserver-proxy" import "github.com/HimbeerserverDE/mt-multiserver-proxy"

View File

@ -8,12 +8,6 @@ import (
"sync" "sync"
) )
const latestSerializeVer = 28
const latestProtoVer = 39
const maxPlayerNameLen = 20
const playerNameChars = "^[a-zA-Z0-9-_]+$"
const bytesPerMediaBunch = 5000
const defaultCmdPrefix = ">" const defaultCmdPrefix = ">"
const defaultSendInterval = 0.09 const defaultSendInterval = 0.09
const defaultUserLimit = 10 const defaultUserLimit = 10
@ -23,6 +17,8 @@ const defaultBindAddr = ":40000"
var config Config var config Config
var configMu sync.RWMutex var configMu sync.RWMutex
// A Config contains information from the configuration file
// that affects the way the proxy works.
type Config struct { type Config struct {
NoPlugins bool NoPlugins bool
CmdPrefix string CmdPrefix string
@ -48,6 +44,8 @@ type Config struct {
UserGroups map[string]string 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 { func Conf() Config {
configMu.RLock() configMu.RLock()
defer configMu.RUnlock() defer configMu.RUnlock()
@ -55,6 +53,9 @@ func Conf() Config {
return 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 { func LoadConfig() error {
configMu.Lock() configMu.Lock()
defer configMu.Unlock() defer configMu.Unlock()

View File

@ -6,7 +6,7 @@ import (
"github.com/anon55555/mt" "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() cc.mu.RLock()
if cc.srv != nil { if cc.srv != nil {
cc.Log("<->", "already connected to server") cc.Log("<->", "already connected to server")

View File

@ -1,7 +1,7 @@
//go:build ignore //go:build ignore
// This program generates default_textures.go. It can be invoked // gen_textures generates default_textures.go. It can be invoked
// by running go generate // by running go generate.
package main package main
import ( import (
@ -23,6 +23,7 @@ func main() {
} }
defer f.Close() defer f.Close()
f.WriteString("// Code generated by gen_textures.go. DO NOT EDIT.\n")
f.WriteString("package proxy\n") f.WriteString("package proxy\n")
f.WriteString("\n") f.WriteString("\n")
f.WriteString("var defaultTextures = []mediaFile{\n") f.WriteString("var defaultTextures = []mediaFile{\n")

7
hop.go
View File

@ -8,6 +8,9 @@ import (
"github.com/anon55555/mt" "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 { func (cc *ClientConn) Hop(serverName string) error {
cc.hopMu.Lock() cc.hopMu.Lock()
defer cc.hopMu.Unlock() defer cc.hopMu.Unlock()
@ -32,7 +35,7 @@ func (cc *ClientConn) Hop(serverName string) error {
return fmt.Errorf("inexistent server") 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 // so the clientConn isn't closed by the packet handler
cc.server().mu.Lock() cc.server().mu.Lock()
cc.server().clt = nil cc.server().clt = nil
@ -144,7 +147,7 @@ func (cc *ClientConn) Hop(serverName string) error {
return err return err
} }
Connect(conn, serverName, cc) connect(conn, serverName, cc)
for ch := range cc.modChs { for ch := range cc.modChs {
cc.server().SendCmd(&mt.ToSrvJoinModChan{Channel: ch}) cc.server().SendCmd(&mt.ToSrvJoinModChan{Channel: ch})

View File

@ -32,7 +32,7 @@ type listener struct {
clts map[*ClientConn]struct{} clts map[*ClientConn]struct{}
} }
func Listen(pc net.PacketConn) *listener { func listen(pc net.PacketConn) *listener {
l := &listener{ l := &listener{
Listener: mt.Listen(pc), Listener: mt.Listen(pc),
clts: make(map[*ClientConn]struct{}), clts: make(map[*ClientConn]struct{}),
@ -49,7 +49,7 @@ func Listen(pc net.PacketConn) *listener {
return l return l
} }
func (l *listener) Clts() map[*ClientConn]struct{} { func (l *listener) clients() map[*ClientConn]struct{} {
clts := make(map[*ClientConn]struct{}) clts := make(map[*ClientConn]struct{})
l.mu.RLock() l.mu.RLock()
@ -62,7 +62,7 @@ func (l *listener) Clts() map[*ClientConn]struct{} {
return clts return clts
} }
func (l *listener) Accept() (*ClientConn, error) { func (l *listener) accept() (*ClientConn, error) {
p, err := l.Listener.Accept() p, err := l.Listener.Accept()
if err != nil { if err != nil {
return nil, err return nil, err

2
log.go
View File

@ -10,6 +10,8 @@ type LogWriter struct {
f *os.File 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) { func (lw *LogWriter) Write(p []byte) (n int, err error) {
n, err = os.Stderr.Write(p) n, err = os.Stderr.Write(p)
if err != nil { if err != nil {

View File

@ -1,7 +1,6 @@
package proxy package proxy
import () // Perms returns the permissions of the ClientConn.
func (cc *ClientConn) Perms() []string { func (cc *ClientConn) Perms() []string {
if cc.Name() == "" { if cc.Name() == "" {
return []string{} return []string{}
@ -19,6 +18,8 @@ func (cc *ClientConn) Perms() []string {
return []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 { func (cc *ClientConn) HasPerms(want ...string) bool {
has := make(map[string]struct{}) has := make(map[string]struct{})
for _, perm := range cc.Perms() { for _, perm := range cc.Perms() {

View File

@ -5,6 +5,8 @@ import "sync"
var players = make(map[string]struct{}) var players = make(map[string]struct{})
var playersMu sync.RWMutex var playersMu sync.RWMutex
// Players returns the names of all players
// that are currently connected to the proxy.
func Players() map[string]struct{} { func Players() map[string]struct{} {
playersMu.RLock() playersMu.RLock()
defer playersMu.RUnlock() defer playersMu.RUnlock()
@ -17,11 +19,12 @@ func Players() map[string]struct{} {
return p return p
} }
// Clts returns all ClientConns currently connected to the proxy.
func Clts() map[*ClientConn]struct{} { func Clts() map[*ClientConn]struct{} {
clts := make(map[*ClientConn]struct{}) clts := make(map[*ClientConn]struct{})
lm := allListeners() lm := allListeners()
for l := range lm { for l := range lm {
for clt := range l.Clts() { for clt := range l.clients() {
clts[clt] = struct{}{} clts[clt] = struct{}{}
} }
} }
@ -29,6 +32,8 @@ func Clts() map[*ClientConn]struct{} {
return clts 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 { func Find(name string) *ClientConn {
for clt := range Clts() { for clt := range Clts() {
if clt.Name() == name { if clt.Name() == name {

View File

@ -2,6 +2,7 @@ package proxy
import "sync" import "sync"
// A ChatCmd holds information on how to handle a chat command.
type ChatCmd struct { type ChatCmd struct {
Name string Name string
Perm string Perm string
@ -14,6 +15,7 @@ var chatCmds map[string]ChatCmd
var chatCmdsMu sync.RWMutex var chatCmdsMu sync.RWMutex
var chatCmdsOnce sync.Once var chatCmdsOnce sync.Once
// ChatCmds returns a map of all ChatCmds indexed by their names.
func ChatCmds() map[string]ChatCmd { func ChatCmds() map[string]ChatCmd {
initChatCmds() initChatCmds()
@ -28,12 +30,14 @@ func ChatCmds() map[string]ChatCmd {
return cmds return cmds
} }
// ChatCmdExists reports if a ChatCmd exists.
func ChatCmdExists(name string) bool { func ChatCmdExists(name string) bool {
cmds := ChatCmds() _, ok := ChatCmds()[name]
_, ok := cmds[name]
return ok 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 { func RegisterChatCmd(cmd ChatCmd) bool {
initChatCmds() initChatCmds()

11
proxy.go Normal file
View File

@ -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

12
run.go
View File

@ -12,6 +12,8 @@ import (
"github.com/anon55555/mt" "github.com/anon55555/mt"
) )
// Run initializes the proxy andstarts the main listener loop.
// It blocks forever.
func Run() { func Run() {
if err := LoadConfig(); err != nil { if err := LoadConfig(); err != nil {
log.Fatal("{←|⇶} ", err) log.Fatal("{←|⇶} ", err)
@ -24,7 +26,7 @@ func Run() {
var err error var err error
switch Conf().AuthBackend { switch Conf().AuthBackend {
case "sqlite3": case "sqlite3":
SetAuthBackend(AuthSQLite3{}) setAuthBackend(authSQLite3{})
default: default:
log.Fatal("{←|⇶} invalid auth backend") log.Fatal("{←|⇶} invalid auth backend")
} }
@ -39,7 +41,7 @@ func Run() {
log.Fatal("{←|⇶} ", err) log.Fatal("{←|⇶} ", err)
} }
l := Listen(pc) l := listen(pc)
defer l.Close() defer l.Close()
log.Print("{←|⇶} listen ", l.Addr()) log.Print("{←|⇶} listen ", l.Addr())
@ -49,7 +51,7 @@ func Run() {
signal.Notify(sig, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP) signal.Notify(sig, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP)
<-sig <-sig
clts := l.Clts() clts := l.clients()
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Add(len(clts)) wg.Add(len(clts))
@ -75,7 +77,7 @@ func Run() {
}() }()
for { for {
cc, err := l.Accept() cc, err := l.accept()
if err != nil { if err != nil {
if errors.Is(err, net.ErrClosed) { if errors.Is(err, net.ErrClosed) {
log.Print("{←|⇶} stop listening") log.Print("{←|⇶} stop listening")
@ -139,7 +141,7 @@ func Run() {
return return
} }
Connect(conn, Conf().Servers[0].Name, cc) connect(conn, Conf().Servers[0].Name, cc)
}() }()
} }

View File

@ -14,6 +14,7 @@ import (
"github.com/anon55555/mt/rudp" "github.com/anon55555/mt/rudp"
) )
// A ServerConn is a connection to a minetest server.
type ServerConn struct { type ServerConn struct {
mt.Peer mt.Peer
clt *ClientConn clt *ClientConn
@ -63,8 +64,12 @@ func (sc *ServerConn) setState(state clientState) {
sc.cstate = state 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 } 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{}) { func (sc *ServerConn) Log(dir string, v ...interface{}) {
if sc.client() != nil { if sc.client() != nil {
format := "%s {%s}" format := "%s {%s}"