Compare commits
10 Commits
Author | SHA1 | Date | |
---|---|---|---|
4f8f4e8d7b | |||
b03a4afb0e | |||
96bc7ed385 | |||
d8fb266319 | |||
0a1f34d8be | |||
e31f52211d | |||
d042ab23d0 | |||
29a498d0aa | |||
f068cb6282 | |||
65b267963c |
43
README.md
43
README.md
@ -1,8 +1,41 @@
|
|||||||
# KF2-AntiDDoS
|
# KF2-AntiDDoS
|
||||||
|
**DDoS protection of the kf2 server from one of the attacks faced by community**
|
||||||
|
|
||||||
Compiled versions for windows and linux are available on the [releases page](https://github.com/GenZmeY/KF2-AntiDDoS/releases).
|
Compiled versions for windows and linux are available on the [releases page](https://github.com/GenZmeY/KF2-AntiDDoS/releases).
|
||||||
But you can build it yourself, for this there is a Makefile.
|
But you can build it yourself, for this there is a Makefile.
|
||||||
|
|
||||||
|
## ⚠️ Note ⚠️
|
||||||
|
### UPDATE 10.04.2023:
|
||||||
|
This tool has served well, but since its inception, the community has moved forward in protecting KF2 servers from DDoS.
|
||||||
|
|
||||||
|
I highly recommend paying attention to the solution from [baztheallmighty](https://forums.tripwireinteractive.com/index.php?members/baztheallmighty.110378/):
|
||||||
|
https://forums.tripwireinteractive.com/index.php?threads/kf2-or-any-unreal-engine-3-server-on-redhat-centos-rocky-alma-linux-ddos-defense-with-the-help-of-firewalld.2337631/post-2355522
|
||||||
|
This method limits the number of connections from each IP so junk traffic is dropped before it even reaches the kf2 server. **It is much more efficient than this tool.**
|
||||||
|
***
|
||||||
|
If you want to continue using this tool for any reason, it will be useful to reduce the `ConnectionTimeout` so that fake connections are closed faster and do not overload the server:
|
||||||
|
**PCServer-KFEngine.ini / LinuxServer-KFEngine.ini**
|
||||||
|
```ini
|
||||||
|
[IpDrv.TcpNetDriver]
|
||||||
|
...
|
||||||
|
ConnectionTimeout=20.0
|
||||||
|
```
|
||||||
|
Thanks to [o2xVc3UuXp0NyBihrUnu](https://forums.tripwireinteractive.com/index.php?members/o2xvc3uuxp0nybihrunu.95080/) for [finding and sharing this setting](https://forums.tripwireinteractive.com/index.php?threads/kf2-or-any-unreal-engine-3-server-on-redhat-centos-rocky-alma-linux-ddos-defense-with-the-help-of-firewalld.2337631/page-5#post-2355506).
|
||||||
|
***
|
||||||
|
The main discussion of the DDoS issue is here:
|
||||||
|
[forums.tripwireinteractive.com/KF2 Sever DDos Defence](https://forums.tripwireinteractive.com/index.php?threads/kf2-or-any-unreal-engine-3-server-on-redhat-centos-rocky-alma-linux-ddos-defense-with-the-help-of-firewalld.2337631/)
|
||||||
|
You might find it helpful to follow this thread.
|
||||||
|
### UPDATE 06.09.2023:
|
||||||
|
[o2xVc3UuXp0NyBihrUnu](https://forums.tripwireinteractive.com/index.php?members/o2xvc3uuxp0nybihrunu.95080/) adapted the [baztheallmighty](https://forums.tripwireinteractive.com/index.php?members/baztheallmighty.110378/) idea for firewall-cmd, which is quite handy:
|
||||||
|
```
|
||||||
|
firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 0 -p udp --dport 7777:7797 -m connlimit --connlimit-above 5 --connlimit-mask 20 -j DROP
|
||||||
|
```
|
||||||
|
**Source:** https://forums.tripwireinteractive.com/index.php?threads/kf2-or-any-unreal-engine-3-server-on-redhat-centos-rocky-alma-linux-ddos-defense-with-the-help-of-firewalld.2337631/post-2358698
|
||||||
|
|
||||||
|
### Update 04.10.2023
|
||||||
|
It looks like the author of the original ddos thread on the forum can no longer keep it up to date, so he moved all the information here:
|
||||||
|
https://www.zsdr.org/index.php/2023/10/03/killing-floor-2-or-any-unreal-engine-3-dedicated-server-on-redhat-centos-rocky-alma-linux-ddos-defense-with-the-help-of-iptables-firewalld/
|
||||||
|
It makes sense to follow this post
|
||||||
|
|
||||||
## How it works
|
## How it works
|
||||||
The program parses the output of the KF2 server(s) and counts the number of connections. If the number of connections from one IP exceeds the threshold and it is still not known that this is a player, the program will execute a deny script passing it the IP as an argument.
|
The program parses the output of the KF2 server(s) and counts the number of connections. If the number of connections from one IP exceeds the threshold and it is still not known that this is a player, the program will execute a deny script passing it the IP as an argument.
|
||||||
The program will periodically execute the allow script, passing it a set of IPs blocked in the last period.
|
The program will periodically execute the allow script, passing it a set of IPs blocked in the last period.
|
||||||
@ -30,6 +63,11 @@ Options:
|
|||||||
- Сreate a redirection of the output of all KF2 servers to the stdin of the program
|
- Сreate a redirection of the output of all KF2 servers to the stdin of the program
|
||||||
- In the parameters specify the scripts that you prepared and the shell that will execute them
|
- In the parameters specify the scripts that you prepared and the shell that will execute them
|
||||||
|
|
||||||
|
## Raw example
|
||||||
|
```
|
||||||
|
tail -f ./KFGame/Logs/Launch.log | ./kf2-antiddos-linux-amd64 /bin/bash ./deny.sh ./allow.sh
|
||||||
|
```
|
||||||
|
|
||||||
## Centos example
|
## Centos example
|
||||||
(change paths and values as you need)
|
(change paths and values as you need)
|
||||||
### systemd service:
|
### systemd service:
|
||||||
@ -49,6 +87,11 @@ Restart=on-failure
|
|||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
```
|
```
|
||||||
|
|
||||||
|
pay attention to this part:
|
||||||
|
`/usr/bin/kf2-srv log tail`
|
||||||
|
I use a self-written system to manage the kf2 servers - the command specified here combines the output of all kf2 server logs into one stdout stream. If you want to protect several servers with antiddos, you also need to combine their logs into one stream. Replace this command with yours.
|
||||||
|
|
||||||
### deny.sh
|
### deny.sh
|
||||||
```
|
```
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
@ -20,6 +20,7 @@ func printHelp() {
|
|||||||
output.Println(" -j, --jobs N allow N jobs at once")
|
output.Println(" -j, --jobs N allow N jobs at once")
|
||||||
output.Println(" -o, --output MODE self|proxy|all|quiet")
|
output.Println(" -o, --output MODE self|proxy|all|quiet")
|
||||||
output.Println(" -t, --deny-time TIME minimum ip deny TIME (seconds)")
|
output.Println(" -t, --deny-time TIME minimum ip deny TIME (seconds)")
|
||||||
|
output.Println(" -a, --allow-time TIME ip whitelist time after disconnect (seconds)")
|
||||||
output.Println(" -c, --max-connections N Skip N connections before run deny script")
|
output.Println(" -c, --max-connections N Skip N connections before run deny script")
|
||||||
output.Println(" -v, --version Show version")
|
output.Println(" -v, --version Show version")
|
||||||
output.Println(" -h, --help Show help")
|
output.Println(" -h, --help Show help")
|
||||||
@ -41,6 +42,9 @@ func parseArgs() config.Config {
|
|||||||
gnuflag.UintVar(&rawCfg.DenyTime, "t", 0, "")
|
gnuflag.UintVar(&rawCfg.DenyTime, "t", 0, "")
|
||||||
gnuflag.UintVar(&rawCfg.DenyTime, "deny-time", 0, "")
|
gnuflag.UintVar(&rawCfg.DenyTime, "deny-time", 0, "")
|
||||||
|
|
||||||
|
gnuflag.UintVar(&rawCfg.AllowTime, "a", 0, "")
|
||||||
|
gnuflag.UintVar(&rawCfg.AllowTime, "allow-time", 0, "")
|
||||||
|
|
||||||
gnuflag.UintVar(&rawCfg.MaxConn, "c", 0, "")
|
gnuflag.UintVar(&rawCfg.MaxConn, "c", 0, "")
|
||||||
gnuflag.UintVar(&rawCfg.MaxConn, "max-connections", 0, "")
|
gnuflag.UintVar(&rawCfg.MaxConn, "max-connections", 0, "")
|
||||||
|
|
||||||
|
@ -95,6 +95,7 @@ func main() {
|
|||||||
&banChan,
|
&banChan,
|
||||||
&resetChan,
|
&resetChan,
|
||||||
cfg.MaxConn,
|
cfg.MaxConn,
|
||||||
|
cfg.AllowTime,
|
||||||
))
|
))
|
||||||
|
|
||||||
// Action worker
|
// Action worker
|
||||||
|
@ -60,7 +60,7 @@ func (a *Action) allow(unbanAll bool) {
|
|||||||
unban := make([]string, 0)
|
unban := make([]string, 0)
|
||||||
|
|
||||||
for ip := range a.ips {
|
for ip := range a.ips {
|
||||||
if unbanAll || bool(a.ips[ip]) { // aka if readyToUnban
|
if unbanAll || a.ips[ip] { // aka if readyToUnban
|
||||||
unban = append(unban, ip)
|
unban = append(unban, ip)
|
||||||
} else {
|
} else {
|
||||||
a.ips[ip] = true // mark readyToUnban next time
|
a.ips[ip] = true // mark readyToUnban next time
|
||||||
|
@ -22,6 +22,7 @@ type Config struct {
|
|||||||
Jobs uint
|
Jobs uint
|
||||||
OutputMode string
|
OutputMode string
|
||||||
DenyTime uint
|
DenyTime uint
|
||||||
|
AllowTime uint
|
||||||
MaxConn uint
|
MaxConn uint
|
||||||
|
|
||||||
ShowVersion bool
|
ShowVersion bool
|
||||||
@ -79,4 +80,7 @@ func (cfg *Config) SetEmptyArgs() {
|
|||||||
if cfg.DenyTime == 0 {
|
if cfg.DenyTime == 0 {
|
||||||
cfg.DenyTime = 20 * 60
|
cfg.DenyTime = 20 * 60
|
||||||
}
|
}
|
||||||
|
if cfg.AllowTime == 0 {
|
||||||
|
cfg.AllowTime = 20 * 60
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,11 @@ package history
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"kf2-antiddos/internal/common"
|
"kf2-antiddos/internal/common"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type History struct {
|
type History struct {
|
||||||
|
ticker *time.Ticker
|
||||||
quit chan struct{}
|
quit chan struct{}
|
||||||
eventChan *chan common.Event
|
eventChan *chan common.Event
|
||||||
banChan *chan string
|
banChan *chan string
|
||||||
@ -12,18 +14,19 @@ type History struct {
|
|||||||
head byte
|
head byte
|
||||||
history map[byte]common.Event
|
history map[byte]common.Event
|
||||||
ips map[string]uint // map[ip]conn_count
|
ips map[string]uint // map[ip]conn_count
|
||||||
whitelist map[string]struct{}
|
whitelist map[string]bool
|
||||||
banned map[string]struct{}
|
banned map[string]struct{}
|
||||||
maxConn uint
|
maxConn uint
|
||||||
workerID uint
|
workerID uint
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(workerID uint, eventChan *chan common.Event, banChan *chan string, resetChan *chan string, maxConn uint) *History {
|
func New(workerID uint, eventChan *chan common.Event, banChan *chan string, resetChan *chan string, maxConn uint, allowTime uint) *History {
|
||||||
return &History{
|
return &History{
|
||||||
|
ticker: time.NewTicker(time.Duration(allowTime) * time.Second),
|
||||||
quit: make(chan struct{}),
|
quit: make(chan struct{}),
|
||||||
ips: make(map[string]uint, 0),
|
ips: make(map[string]uint, 0),
|
||||||
history: make(map[byte]common.Event, 0),
|
history: make(map[byte]common.Event, 0),
|
||||||
whitelist: make(map[string]struct{}, 0),
|
whitelist: make(map[string]bool, 0),
|
||||||
banned: make(map[string]struct{}, 0),
|
banned: make(map[string]struct{}, 0),
|
||||||
eventChan: eventChan,
|
eventChan: eventChan,
|
||||||
banChan: banChan,
|
banChan: banChan,
|
||||||
@ -42,7 +45,10 @@ func (h *History) Do() {
|
|||||||
h.registerEvent(event)
|
h.registerEvent(event)
|
||||||
case ip := <-*h.resetChan:
|
case ip := <-*h.resetChan:
|
||||||
h.resetIp(ip)
|
h.resetIp(ip)
|
||||||
|
case <-h.ticker.C:
|
||||||
|
h.unWhiteList()
|
||||||
case <-h.quit:
|
case <-h.quit:
|
||||||
|
h.ticker.Stop()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -88,16 +94,29 @@ func (h *History) registerConnect(ip string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *History) registerNewPlayer(ip string) {
|
func (h *History) registerNewPlayer(ip string) {
|
||||||
h.whitelist[ip] = struct{}{}
|
h.whitelist[ip] = false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *History) registerEndPlayer(ip string) {
|
func (h *History) registerEndPlayer(ip string) {
|
||||||
delete(h.whitelist, ip)
|
h.whitelist[ip] = true
|
||||||
delete(h.ips, ip)
|
|
||||||
delete(h.banned, ip)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *History) resetIp(ip string) {
|
func (h *History) resetIp(ip string) {
|
||||||
delete(h.ips, ip)
|
delete(h.ips, ip)
|
||||||
delete(h.banned, ip)
|
delete(h.banned, ip)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *History) unWhiteList() {
|
||||||
|
toRemove := make([]string, 0)
|
||||||
|
for ip := range h.whitelist {
|
||||||
|
if h.whitelist[ip] {
|
||||||
|
toRemove = append(toRemove, ip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ip := range toRemove {
|
||||||
|
delete(h.whitelist, ip)
|
||||||
|
delete(h.ips, ip)
|
||||||
|
delete(h.banned, ip)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user