Skip to content

Commit

Permalink
add config validation & warns
Browse files Browse the repository at this point in the history
  • Loading branch information
robinbraemer committed Aug 5, 2020
1 parent 35df672 commit 9e453d6
Show file tree
Hide file tree
Showing 9 changed files with 132 additions and 30 deletions.
28 changes: 15 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

# The Minecraft Proxy _(alpha)_

![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/minekube/gate?sort=semver)
[![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/minekube/gate?sort=semver)](https://github.com/minekube/gate/releases)
[![Doc](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go)](https://pkg.go.dev/go.minekube.com/gate)
![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/minekube/gate?logo=go)
[![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/minekube/gate?logo=go)](https://golang.org/doc/devel/release.html)
[![Go Report Card](https://goreportcard.com/badge/go.minekube.com/gate)](https://goreportcard.com/report/go.minekube.com/gate)
[![test](https://github.com/minekube/gate/workflows/test/badge.svg)](https://github.com/minekube/gate/actions?query=workflow%3Atest)
[![Discord](https://img.shields.io/discord/633708750032863232?logo=discord)](https://discord.gg/6vMDqWE)
Expand All @@ -21,12 +21,14 @@
### Features

- [**Fast**](#benchmarks)
- High performant parallelism (see benchmarks)
- Excellent server version support
- Newest version down to 1.7 (+forge support)
- Bungeecord plugins compatible (plugin messages)
- Velocity's player forwarding mode
- [Quick installation](#quick-sample-setup)
- simply pick a download from the releases
- support windows/macOS/linux/...
- single executable binary
- (No Java runtime needed)
- simply pick a download from the [releases](https://github.com/minekube/gate/releases)
- (No Java runtime needed for Gate itself)
- A simple API to [extend Gate](#extending-gate-with-custom-code)
- Benefits from Go's awesome language features
- simple, reliable, efficient
- [and much more](https://golang.org/)
Expand Down Expand Up @@ -65,16 +67,16 @@ custom plugins/code can react to.
This is a simple setup of a Minecraft network using Gate proxy,
a Paper 1.16.1 (server1) & Paper 1.8.8 (server2).

**You will only need a JRE (Java Runtime Environment, 8 or higher).**
_You will need Java Runtime 8 or higer for running the Paper servers._

1. `git clone https://github.com/minekube/gate.git`
2. Go to `docs/sample`
3. Open a terminal in this directory
and run the pre-compiled executable
- linux: `./gate`
- windows: `gate.exe`
- Or build an executable yourself. ;)
4. Open two terminals, one for each `server1` & `server2`.
and download a [release](https://github.com/minekube/gate/releases).
- Or compile it yourself. ;)
4. Run Gate within `docs/sample`
- (windows: `gate.exe` / linux: `./gate`)
5. Open two more terminals, one for each `server1` & `server2`.
- Run `java -jar <the server jar>` to run both servers.

Now you can connect to the network on `localhost:25565`
Expand Down
115 changes: 108 additions & 7 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@ package config

import (
"errors"
"fmt"
"github.com/spf13/viper"
"go.uber.org/zap"
"net"
"regexp"
)

// Config is the configuration of the proxy.
type Config struct {
File
*File
}

// File is for reading a config file into this struct.
Expand Down Expand Up @@ -60,8 +64,7 @@ func init() {
viper.SetDefault("status.maxplayers", 1000)
viper.SetDefault("status.faviconfile", "server-icon.png")
viper.SetDefault("status.showPingRequests", false)
//viper.SetDefault("compression.threshold", -1)
viper.SetDefault("compression.threshold", 256) // TODO de/compression doesn't work yet
viper.SetDefault("compression.threshold", 256)
viper.SetDefault("compression.level", 1)
viper.SetDefault("query.enabled", false)
viper.SetDefault("query.port", 25577)
Expand All @@ -79,17 +82,115 @@ func NewValid(f *File) (c *Config, err error) {
return nil, errors.New("file must not be nil-pointer")
}

// TODO validate input config
// validate input config
warns, errs := validate(f)
if len(errs) != 0 {
for _, err = range errs {
zap.S().Errorf("Config error: %s", err)
}

a, s := "are", "s"
if len(errs) == 1 {
a, s = "is", ""
}
return nil, fmt.Errorf("there %s %d config validation error%s", a, len(errs), s)
}
for _, err = range warns {
zap.L().Warn(err.Error())
}

return &Config{
File: *f,
File: f,
}, nil
}

func (c *Config) AttemptConnectionOrder() []string {
return c.Try
func validate(f *File) (warns []error, errs []error) {
e := func(m string, args ...interface{}) { errs = append(errs, fmt.Errorf(m, args...)) }
w := func(m string, args ...interface{}) { warns = append(warns, fmt.Errorf(m, args...)) }

if len(f.Bind) == 0 {
e("bind is empty")
} else {
if err := ValidHostPort(f.Bind); err != nil {
e("invalid bind %q: %v", f.Bind, err)
}
}

if !f.OnlineMode {
w("Proxy is running in offline mode!")
}

switch f.Forwarding.Mode {
case NoneForwardingMode:
w("Player forwarding is disabled! Backend servers will have players with " +
"offline-mode UUIDs and the same IP as the proxy.")
case LegacyForwardingMode, VelocityForwardingMode:
default:
e("Unknown forwarding mode %q, must be one of none,legacy,velocity", f.Forwarding.Mode)
}

if len(f.Servers) == 0 {
w("No backend servers configured.")
}
for name, addr := range f.Servers {
if !ValidServerName(name) {
e("Invalid server name format %q: %s and length be 1-%d", name,
qualifiedNameErrMsg, qualifiedNameMaxLength)
}
if err := ValidHostPort(addr); err != nil {
e("Invalid address %q for server %q: %w", addr, name, err)
}
}

for _, name := range f.Try {
if _, ok := f.Servers[name]; !ok {
e("Fallback/try server %q must be registered under servers", name)
}
}

for host, servers := range f.ForcedHosts {
for _, name := range servers {
e("Forced host %q server %q must be registered under servers", host, name)
}
}

if f.Compression.Level < -1 || f.Compression.Level > 9 {
e("Unsupported compression level %d: must be -1..9", f.Compression.Level)
} else if f.Compression.Level == 0 {
w("All packets going through the proxy will are uncompressed, this increases bandwidth usage.")
}

if f.Compression.Threshold < -1 {
e("Invalid compression threshold %d: must be >= -1", f.Compression.Threshold)
} else if f.Compression.Threshold == 0 {
w("All packets going through the proxy will be compressed, this lowers bandwidth, " +
"but has lower throughput and increases CPU usage.")
}

return
}

func ValidHostPort(hostAndPort string) error {
_, _, err := net.SplitHostPort(hostAndPort)
return err
}

// Constants obtained from https://github.com/kubernetes/apimachinery/blob/master/pkg/util/validation/validation.go
const (
qnameCharFmt = "[A-Za-z0-9]"
qnameExtCharFmt = "[-A-Za-z0-9_.]"
qualifiedNameFmt = "(" + qnameCharFmt + qnameExtCharFmt + "*)?" + qnameCharFmt
qualifiedNameErrMsg = "must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character"
qualifiedNameMaxLength = 63
)

var qualifiedNameRegexp = regexp.MustCompile("^" + qualifiedNameFmt + "$")

func ValidServerName(str string) bool {
return str != "" && len(str) <= qualifiedNameMaxLength && qualifiedNameRegexp.MatchString(str)
}

// ForwardingMode is a player info forwarding mode.
type ForwardingMode string

const (
Expand Down
2 changes: 1 addition & 1 deletion pkg/proto/codec/decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ func (d *Decoder) decodePayload(p []byte) (ctx *proto.PacketContext, err error)
// Payload buffer should now be empty.
if payload.Len() != 0 {
// packet decoder did not read all of the packet's data!
zap.L().Warn("Decoder did not read all of packet's data", append([]zap.Field{
zap.L().Debug("Decoder did not read all of packet's data", append([]zap.Field{
zap.Stringer("type", reflect.TypeOf(ctx.Packet)),
zap.Stringer("packetId", ctx.PacketId),
zap.Stringer("protocol", ctx.Protocol),
Expand Down
2 changes: 0 additions & 2 deletions pkg/proto/packet/joingame.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ type JoinGame struct {
DimensionRegistry *DimensionRegistry // 1.16+
DimensionInfo *DimensionInfo // 1.16+
PreviousGamemode int16 // 1.16+

// TODO add retained []byte field with util/io.RecordReader so we don't encode again when forwarding
}

func (j *JoinGame) Encode(c *proto.PacketContext, wr io.Writer) error {
Expand Down
2 changes: 1 addition & 1 deletion pkg/proxy/connect.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ func (c *Connect) listen(address string) error {
func (c *Connect) handleRawConn(rawConn net.Conn) {
// Create client connection
conn := newMinecraftConn(rawConn, c.proxy, true, func() []zap.Field {
return []zap.Field{zap.Bool("isPlayer", true)}
return []zap.Field{zap.Bool("player", true)}
})
conn.setSessionHandler0(newHandshakeSessionHandler(conn))
// Read packets in loop
Expand Down
5 changes: 2 additions & 3 deletions pkg/proxy/connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,8 @@ type sessionHandler interface {
// minecraftConn is a Minecraft connection from the
// client -> proxy or proxy -> server (backend).
type minecraftConn struct {
proxy *Proxy // convenient backreference

c net.Conn // Underlying connection
proxy *Proxy // convenient backreference
c net.Conn // Underlying connection

// readLoop owns these fields
readBuf *bufio.Reader
Expand Down
2 changes: 1 addition & 1 deletion pkg/proxy/player.go
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ func (p *connectedPlayer) nextServerToTry(current RegisteredServer) RegisteredSe
}

if len(p.serversToTry) == 0 {
p.serversToTry = p.proxy.Config().AttemptConnectionOrder()
p.serversToTry = p.proxy.Config().Try
}

sameName := func(rs RegisteredServer, name string) bool {
Expand Down
3 changes: 2 additions & 1 deletion pkg/proxy/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ type RegisteredServer interface {
//
//

// Players is a list of players safe for concurrent use.
type Players interface {
Len() int // Returns the size of the player list.
Range(func(p Player) bool) // Loops through the players, breaks if func returns false.
Expand Down Expand Up @@ -256,7 +257,7 @@ func (s *serverConnection) connect(ctx context.Context, resultFn internalConnect
// Wrap server connection
serverMc := newMinecraftConn(conn, s.player.proxy, false, func() []zap.Field {
return []zap.Field{
zap.Bool("isBackendServerConnection", true),
zap.Bool("server", true),
zap.String("serverName", s.Server().ServerInfo().Name()),
zap.Stringer("serverAddr", s.Server().ServerInfo().Addr()),
zap.Stringer("forPlayer", s.player),
Expand Down
3 changes: 2 additions & 1 deletion pkg/proxy/session_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ type statusSessionHandler struct {
}

func (h *statusSessionHandler) activated() {
if h.conn.proxy.Config().Status.ShowPingRequests {
cfg := h.conn.proxy.Config()
if cfg.Status.ShowPingRequests || cfg.Debug {
zap.S().Infof("%s with version %s", h.inbound, h.conn.protocol)
}
}
Expand Down

0 comments on commit 9e453d6

Please sign in to comment.