Skip to content
Open
41 changes: 39 additions & 2 deletions cmd/mc-router/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ type Config struct {

SimplifySRV bool `default:"false" usage:"Simplify fully qualified SRV records for mapping"`

FakeOnline bool `default:"false" usage:"Enable fake online MOTD when backend is offline and auto-scale-up is enabled"`
FakeOnlineMOTD string `default:"Server is sleeping\nJoin to wake it up" usage:"Custom MOTD to show when backend is offline and auto-scale-up is enabled"`

CacheStatus bool `default:"false" usage:"Cache status response for backends"`
CacheStatusInterval string `default:"30s" usage:"Interval to update the status cache"`

Webhook WebhookConfig `usage:"Webhook configuration"`
}

Expand Down Expand Up @@ -138,7 +144,6 @@ func main() {
// Only one instance should be created
server.DownScaler = server.NewDownScaler(ctx, downScalerEnabled, downScalerDelay)


c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)

Expand Down Expand Up @@ -167,7 +172,21 @@ func main() {
trustedIpNets = append(trustedIpNets, ipNet)
}

connector := server.NewConnector(metricsBuilder.BuildConnectorMetrics(), config.UseProxyProtocol, config.ReceiveProxyProtocol, trustedIpNets, config.RecordLogins, autoScaleAllowDenyConfig)
fakeOnlineEnabled := config.FakeOnline && config.AutoScale.Up && (config.InKubeCluster || config.KubeConfig != "")

connectorConfig := server.ConnectorConfig{
SendProxyProto: config.UseProxyProtocol,
ReceiveProxyProto: config.ReceiveProxyProtocol,
TrustedProxyNets: trustedIpNets,
RecordLogins: config.RecordLogins,
AutoScaleUpAllowDenyConfig: autoScaleAllowDenyConfig,
AutoScaleUp: config.AutoScale.Up,
FakeOnline: fakeOnlineEnabled,
FakeOnlineMOTD: config.FakeOnlineMOTD,
CacheStatus: config.CacheStatus,
}

connector := server.NewConnector(metricsBuilder.BuildConnectorMetrics(), connectorConfig)

clientFilter, err := server.NewClientFilter(config.ClientsToAllow, config.ClientsToDeny)
if err != nil {
Expand All @@ -184,6 +203,15 @@ func main() {
server.NewWebhookNotifier(config.Webhook.Url, config.Webhook.RequireUser))
}

var cacheInterval time.Duration
if config.CacheStatus {
cacheInterval, err = time.ParseDuration(config.CacheStatusInterval)
if err != nil {
logrus.WithError(err).Fatal("Unable to parse cache status interval")
}
logrus.WithField("interval", config.CacheStatusInterval).Info("Using cache status interval")
}

if config.NgrokToken != "" {
connector.UseNgrok(config.NgrokToken)
}
Expand Down Expand Up @@ -240,6 +268,15 @@ func main() {
logrus.WithError(err).Fatal("Unable to start metrics reporter")
}

if config.CacheStatus {
logrus.Info("Starting status cache updater")
connector.StatusCache.StartUpdater(connector, cacheInterval, func() map[string]string {
mappings := server.Routes.GetMappings()
logrus.WithField("mappings", mappings).Debug("Status cache updater")
return mappings
})
}

// wait for process-stop signal
<-c
logrus.Info("Stopping. Waiting for connections to complete...")
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ require (
)

require (
github.com/Raqbit/mc-pinger v0.2.4 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/containerd/log v0.1.0 // indirect
Expand Down Expand Up @@ -50,6 +51,7 @@ require (
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.62.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/sethvargo/go-retry v0.3.0 // indirect
github.com/x448/float16 v0.8.4 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 // indirect
Expand Down
57 changes: 57 additions & 0 deletions go.sum

Large diffs are not rendered by default.

27 changes: 27 additions & 0 deletions mcproto/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,30 @@ type ByteReader interface {
const (
PacketLengthFieldBytes = 1
)

type StatusResponse struct {
Version StatusVersion `json:"version"`
Players StatusPlayers `json:"players"`
Description StatusText `json:"description"`
Favicon string `json:"favicon,omitempty"`
}

type StatusVersion struct {
Name string `json:"name"`
Protocol int `json:"protocol"`
}

type StatusPlayers struct {
Max int `json:"max"`
Online int `json:"online"`
Sample []PlayerEntry `json:"sample,omitempty"`
}

type PlayerEntry struct {
Name string `json:"name"`
ID string `json:"id"`
}

type StatusText struct {
Text string `json:"text"`
}
41 changes: 41 additions & 0 deletions mcproto/write.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package mcproto

import (
"encoding/json"
"io"
)

func WriteStatusResponse(w io.Writer, status *StatusResponse) error {
data, err := json.Marshal(status)
if err != nil {
return err
}

jsonLen := encodeVarInt(len(data))
payload := append(jsonLen, data...)
return WritePacket(w, 0x00, payload)
}

func WritePacket(w io.Writer, packetID int, data []byte) error {
packet := append(encodeVarInt(packetID), data...)
length := encodeVarInt(len(packet))
_, err := w.Write(append(length, packet...))
return err
}

// encodeVarInt encodes an int as a Minecraft VarInt.
func encodeVarInt(value int) []byte {
var buf []byte
for {
temp := byte(value & 0x7F)
value >>= 7
if value != 0 {
temp |= 0x80
}
buf = append(buf, temp)
if value == 0 {
break
}
}
return buf
}
6 changes: 3 additions & 3 deletions server/allow_deny_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ import (

type AllowDenyLists struct {
Allowlist []PlayerInfo
Denylist []PlayerInfo
Denylist []PlayerInfo
}

type AllowDenyConfig struct {
Global AllowDenyLists
Global AllowDenyLists
Servers map[string]AllowDenyLists
}

Expand All @@ -35,7 +35,7 @@ func entryMatchesPlayer(entry *PlayerInfo, userInfo *PlayerInfo) bool {
if entry.Name == "" && entry.Uuid == uuid.Nil {
return false
}

if entry.Name != "" && entry.Uuid != uuid.Nil {
return *entry == *userInfo
}
Expand Down
30 changes: 15 additions & 15 deletions server/allow_deny_list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
func Test_allowDenyConfig_ServerAllowsPlayer(t *testing.T) {
type args struct {
serverAddress string
userInfo *PlayerInfo
userInfo *PlayerInfo
}
validUserInfo := &PlayerInfo{
Name: "player_name",
Expand All @@ -27,20 +27,20 @@ func Test_allowDenyConfig_ServerAllowsPlayer(t *testing.T) {
want bool
}{
{
name: "nil config",
name: "nil config",
allowDenyConfig: nil,
args: args{
serverAddress: "server.my.domain",
userInfo: validUserInfo,
userInfo: validUserInfo,
},
want: true,
},
{
name: "empty config",
name: "empty config",
allowDenyConfig: &AllowDenyConfig{},
args: args{
serverAddress: "server.my.domain",
userInfo: validUserInfo,
userInfo: validUserInfo,
},
want: true,
},
Expand All @@ -58,7 +58,7 @@ func Test_allowDenyConfig_ServerAllowsPlayer(t *testing.T) {
},
args: args{
serverAddress: "server.my.domain",
userInfo: validUserInfo,
userInfo: validUserInfo,
},
want: false,
},
Expand All @@ -73,7 +73,7 @@ func Test_allowDenyConfig_ServerAllowsPlayer(t *testing.T) {
},
args: args{
serverAddress: "server.my.domain",
userInfo: validUserInfo,
userInfo: validUserInfo,
},
want: true,
},
Expand All @@ -88,7 +88,7 @@ func Test_allowDenyConfig_ServerAllowsPlayer(t *testing.T) {
},
args: args{
serverAddress: "server.my.domain",
userInfo: validUserInfo,
userInfo: validUserInfo,
},
want: false,
},
Expand All @@ -103,7 +103,7 @@ func Test_allowDenyConfig_ServerAllowsPlayer(t *testing.T) {
},
args: args{
serverAddress: "server.my.domain",
userInfo: validUserInfo,
userInfo: validUserInfo,
},
want: false,
},
Expand All @@ -121,7 +121,7 @@ func Test_allowDenyConfig_ServerAllowsPlayer(t *testing.T) {
},
args: args{
serverAddress: "server.my.domain",
userInfo: validUserInfo,
userInfo: validUserInfo,
},
want: true,
},
Expand All @@ -138,7 +138,7 @@ func Test_allowDenyConfig_ServerAllowsPlayer(t *testing.T) {
},
args: args{
serverAddress: "server.my.domain",
userInfo: validUserInfo,
userInfo: validUserInfo,
},
want: true,
},
Expand All @@ -155,7 +155,7 @@ func Test_allowDenyConfig_ServerAllowsPlayer(t *testing.T) {
},
args: args{
serverAddress: "server.my.domain",
userInfo: validUserInfo,
userInfo: validUserInfo,
},
want: false,
},
Expand All @@ -172,7 +172,7 @@ func Test_allowDenyConfig_ServerAllowsPlayer(t *testing.T) {
},
args: args{
serverAddress: "server.my.domain",
userInfo: validUserInfo,
userInfo: validUserInfo,
},
want: false,
},
Expand All @@ -194,7 +194,7 @@ func Test_allowDenyConfig_ServerAllowsPlayer(t *testing.T) {
},
args: args{
serverAddress: "server.my.domain",
userInfo: validUserInfo,
userInfo: validUserInfo,
},
want: true,
},
Expand All @@ -216,7 +216,7 @@ func Test_allowDenyConfig_ServerAllowsPlayer(t *testing.T) {
},
args: args{
serverAddress: "server.my.domain",
userInfo: validUserInfo,
userInfo: validUserInfo,
},
want: true,
},
Expand Down
Loading