Skip to content

Commit

Permalink
improved login verification to not allowed new limit of connections e…
Browse files Browse the repository at this point in the history
…very cooldown
  • Loading branch information
realDragonium committed Aug 16, 2021
1 parent 794b513 commit c11882d
Show file tree
Hide file tree
Showing 7 changed files with 210 additions and 132 deletions.
40 changes: 19 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,24 @@
# Ultraviolet - Alpha v0.13
# Ultraviolet

## What is Ultraviolet?
Its a reverse minecraft proxy, capable of serving as a placeholder when the server is offline for status response to clients.
Its a reverse minecraft proxy, capable of serving as a placeholder when the server is offline for status response to clients. It also has some basic anti backend ddos features. So can it ask players to verify themselves when there are to many players trying to join within a given timeframe and it will (by default) cache the status of the server.

one could also say [infrared](https://github.com/haveachin/infrared) but different.

Everything which ultraviolet has or does right now is not final its possible that it will change how it currently works therefore the reason its still in Alpha. If wanna complain that something has been changed and/or it broke something thats your own fault, its in Alpha version after all.

Thinks likely to change:
- Run command
- config file(s) (structure of the files themselves)


## Features
## Extra Features
[x] Proxy Protocol(v2) support
[x] RealIP (v2.4&v2.5)
[x] Rate limiting -> Login verification
[x] Status caching (online status only)
[x] Offline status placeholder
[x] Prometheus Support
[x] API (Reload server config files, more later)
... More coming later?

## Some notes
### Limited connections when running binary
Because linux the default settings for fd is 1024, this means that you can by default Ultraviolet can have 1024 open connections before it starts refusing connections because it cant open anymore fds. Because of some internal queues you should consider increasing the limit if you expect to proxy over 900 open connections at the same time.
### How to run
Ultraviolet will, when no config is specified by the command, use `/etc/ultraviolet` as work dir and create here an `ultraviolet.json` file for you.
```
$ ./ultraviolet run
```

### How to build
Ultraviolet can be ran by using docker or you can also build a binary yourself by running:
Expand All @@ -33,18 +27,22 @@ $ cd cmd/Ultraviolet/
$ go build
```

### How to run
### How to run with docker
Currently there is no docker hub image avaiable which can be used (but there should be one soon). You can run Ultraviolet with the `Dockerfile` in the root folder of the project.

Ultraviolet will, when no config is specified by the command, use `/etc/ultraviolet` as work dir and create here an `ultraviolet.json` file for you.
```
$ ./ultraviolet run
```
### Limited connections when running binary
Because linux the default settings for fd is 1024, this means that you can by default Ultraviolet can have 1024 open connections before it starts refusing connections because it cant open anymore fds. Because of some internal queues you should consider increasing the limit if you expect to proxy over 900 open connections at the same time.

### File examples
In the folder `examples` in the project are a few related file examples which can be used together with Ultraviolet. There is also a basic grafana dashboard layout there which can be used together with the prometheus feature.

### Hotswapping to newer versions
Its not necessary to use this to reload the server configs, there is also an api or an command if you want to reload the server configs.

### Tableflip
This has implemented [tableflip](https://github.com/cloudflare/tableflip) which should make it able to reload/hotswap Ultraviolet without closing existing connections on Linux and macOS. Ultraviolet should still be usable on windows (testing purposes only pls).
Check their [documentation](https://pkg.go.dev/github.com/cloudflare/tableflip) to know what or how.

IMPORTANT: There is a limit of one 'parent' process. So when you reload Ultraviolet once you need to wait until the parent process is closed (all previous connections have been closed) before you can reload it again.
IMPORTANT (when using this feature): There is a limit of one 'parent' process. So when you reload Ultraviolet once you need to wait until the parent process is closed (all previous connections have been closed) before you can reload it again.

## Command-Line
The follows commands can be used with ultraviolet, all flags (if related) should work for every command and be used by every command if you used it for one command.
Expand Down
1 change: 1 addition & 0 deletions cmd/Ultraviolet/.goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,4 @@ changelog:
exclude:
- '^docs:'
- '^test:'
- '^ignore:'
6 changes: 3 additions & 3 deletions config/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,14 @@ func DefaultServerConfig() ServerConfig {
OldRealIP: false,
NewRealIP: false,
SendProxyProtocol: false,
DisconnectMessage: "Server is offline",
DisconnectMessage: "Server is offline",
CacheStatus: true,
CacheUpdateCooldown: "1s",
CacheUpdateCooldown: "1m",
RateLimit: 5,
RateDuration: "1s",
RateBanListCooldown: "5m",
RateDisconMsg: "Please reconnect to verify yourself",
StateUpdateCooldown: "1s",
StateUpdateCooldown: "5s",
}
}

Expand Down
3 changes: 2 additions & 1 deletion server/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ func NewBackendConfig(cfg config.BackendWorkerConfig) BackendConfig {
connCreator = BasicConnCreator(cfg.ProxyTo, dialer)

if cfg.RateLimit > 0 {
rateLimiter = NewBotFilterConnLimiter(cfg.RateLimit, cfg.RateLimitDuration, cfg.RateBanListCooldown, cfg.RateDisconPk)
unverifyCooldown := 10 * cfg.RateLimitDuration
rateLimiter = NewBotFilterConnLimiter(cfg.RateLimit, cfg.RateLimitDuration, unverifyCooldown, cfg.RateBanListCooldown, cfg.RateDisconPk)
} else {
rateLimiter = AlwaysAllowConnection{}
}
Expand Down
66 changes: 40 additions & 26 deletions server/conn_limiter.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ func FilterIpFromAddr(addr net.Addr) string {

type ConnectionLimiter interface {
// The process answer is empty and should be ignored when it does allow the connection to happen
// Returns true if the connection is allowed to happen
Allow(req BackendRequest) (BackendAnswer, bool)
}

Expand Down Expand Up @@ -59,57 +60,70 @@ func (limiter AlwaysAllowConnection) Allow(req BackendRequest) (BackendAnswer, b
return BackendAnswer{}, true
}

func NewBotFilterConnLimiter(ratelimit int, cooldown, clearTime time.Duration, disconnPk mc.Packet) ConnectionLimiter {
func NewBotFilterConnLimiter(ratelimit int, cooldown, clearTime, unverify time.Duration, disconnPk mc.Packet) ConnectionLimiter {

return &botFilterConnLimiter{
rateLimit: ratelimit,
rateCooldown: cooldown,
disconnPacket: disconnPk,
listClearTime: clearTime,
lastTimeAboveLimit: time.Now(),
unverifyCooldown: unverify,
rateLimit: ratelimit,
rateCooldown: cooldown,
disconnPacket: disconnPk,
listClearTime: clearTime,

namesList: make(map[string]string),
blackList: make(map[string]time.Time),
}
}

type botFilterConnLimiter struct {
rateCounter int
rateStartTime time.Time
rateLimit int
rateCooldown time.Duration
disconnPacket mc.Packet
listClearTime time.Duration
limiting bool
unverifyCooldown time.Duration

rateCounter int
rateStartTime time.Time
lastTimeAboveLimit time.Time
rateLimit int
rateCooldown time.Duration
disconnPacket mc.Packet
listClearTime time.Duration

blackList map[string]time.Time
namesList map[string]string
}

func (limiter *botFilterConnLimiter) Allow(req BackendRequest) (BackendAnswer, bool) {
func (l *botFilterConnLimiter) Allow(req BackendRequest) (BackendAnswer, bool) {
if req.Type == mc.Status {
return BackendAnswer{}, true
}
if time.Since(limiter.rateStartTime) >= limiter.rateCooldown {
limiter.rateCounter = 0
limiter.rateStartTime = time.Now()
if time.Since(l.rateStartTime) >= l.rateCooldown {
if l.rateCounter > l.rateLimit {
l.lastTimeAboveLimit = l.rateStartTime
}
if l.limiting && time.Since(l.lastTimeAboveLimit) >= l.unverifyCooldown {
l.limiting = false
}
l.rateCounter = 0
l.rateStartTime = time.Now()
}
limiter.rateCounter++

l.rateCounter++
ip := FilterIpFromAddr(req.Addr)
blockTime, ok := limiter.blackList[ip]
if time.Since(blockTime) >= limiter.listClearTime {
delete(limiter.blackList, ip)
blockTime, ok := l.blackList[ip]
if time.Since(blockTime) >= l.listClearTime {
delete(l.blackList, ip)
} else if ok {
return NewCloseAnswer(), false
}

// TODO: if connections are above rate limit, the next cooldown is probably still is
// Find something to improve it
if limiter.rateCounter > limiter.rateLimit {
username, ok := limiter.namesList[ip]
l.limiting = l.limiting || l.rateCounter > l.rateLimit
if l.limiting {
username, ok := l.namesList[ip]
if !ok {
limiter.namesList[ip] = req.Username
return NewDisconnectAnswer(limiter.disconnPacket), false
l.namesList[ip] = req.Username
return NewDisconnectAnswer(l.disconnPacket), false
}
if username != req.Username {
limiter.blackList[ip] = time.Now()
l.blackList[ip] = time.Now()
return NewCloseAnswer(), false
}
}
Expand Down
Loading

0 comments on commit c11882d

Please sign in to comment.