Compare commits
7 Commits
Author | SHA1 | Date | |
---|---|---|---|
65b267963c | |||
80b2d2de15 | |||
5fe959e236 | |||
3c1a7b60cd | |||
1e2d97598f | |||
2f742f9929 | |||
86f3f9af52 |
74
README.md
74
README.md
@ -1 +1,73 @@
|
|||||||
# KF2-AntiDDoS
|
# KF2-AntiDDoS
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## 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 will periodically execute the allow script, passing it a set of IPs blocked in the last period.
|
||||||
|
|
||||||
|
## HowTo:
|
||||||
|
```
|
||||||
|
Usage: <kf2_logs_output> | kf2-antiddos [option]... <shell> <deny_script> <allow_script>
|
||||||
|
|
||||||
|
kf2_logs_output KF2 logs to redirect to stdin
|
||||||
|
shell shell to run deny_script and allow_script
|
||||||
|
deny_script firewall deny script (takes IP as argument)
|
||||||
|
allow_script firewall allow script (takes IPs as arguments)
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-j, --jobs N allow N jobs at once
|
||||||
|
-o, --output MODE self|proxy|all|quiet
|
||||||
|
-t, --deny-time TIME minimum ip deny TIME (seconds)
|
||||||
|
-c, --max-connections N Skip N connections before run deny script
|
||||||
|
-v, --version Show version
|
||||||
|
-h, --help Show help
|
||||||
|
```
|
||||||
|
|
||||||
|
- Prepare an IP deny script for your firewall. The script must block the IP received by the first argument
|
||||||
|
- Prepare an IP set allow script for your firewall. The script must unblock the set of IPs given by the arguments
|
||||||
|
- С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
|
||||||
|
|
||||||
|
## Centos example
|
||||||
|
(change paths and values as you need)
|
||||||
|
### systemd service:
|
||||||
|
```
|
||||||
|
[Unit]
|
||||||
|
Description=kf2-antiddos
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
User=root
|
||||||
|
Group=root
|
||||||
|
Type=simple
|
||||||
|
ExecStart=/bin/sh -c '/usr/bin/kf2-srv log tail | /usr/local/bin/kf2-antiddos-linux-amd64 /bin/bash /usr/local/share/kf2-antiddos/deny.sh /usr/local/share/kf2-antiddos/allow.sh'
|
||||||
|
Restart=on-failure
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
```
|
||||||
|
### deny.sh
|
||||||
|
```
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
firewall-cmd --add-rich-rule="rule family=ipv4 source address=$1 port port=7777-7815 protocol=udp reject"
|
||||||
|
firewall-cmd --add-rich-rule="rule family=ipv4 destination address=$1 reject"
|
||||||
|
```
|
||||||
|
|
||||||
|
### allow.sh
|
||||||
|
```
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
for IP in $@
|
||||||
|
do
|
||||||
|
firewall-cmd --remove-rich-rule="rule family=ipv4 source address=$IP port port=7777-7815 protocol=udp reject"
|
||||||
|
firewall-cmd --remove-rich-rule="rule family=ipv4 destination address=$IP reject"
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
It would be great if someone set up, tried it on windows and share their experience
|
||||||
|
|
||||||
|
@ -19,8 +19,9 @@ func printHelp() {
|
|||||||
output.Println("Options:")
|
output.Println("Options:")
|
||||||
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(" -dt, --deny-time TIME minimum ip deny TIME (seconds)")
|
output.Println(" -t, --deny-time TIME minimum ip deny TIME (seconds)")
|
||||||
output.Println(" -mc, --max-connections N Skip N connections before run deny script")
|
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(" -v, --version Show version")
|
output.Println(" -v, --version Show version")
|
||||||
output.Println(" -h, --help Show help")
|
output.Println(" -h, --help Show help")
|
||||||
}
|
}
|
||||||
@ -38,10 +39,13 @@ func parseArgs() config.Config {
|
|||||||
gnuflag.StringVar(&rawCfg.OutputMode, "o", "", "")
|
gnuflag.StringVar(&rawCfg.OutputMode, "o", "", "")
|
||||||
gnuflag.StringVar(&rawCfg.OutputMode, "output", "", "")
|
gnuflag.StringVar(&rawCfg.OutputMode, "output", "", "")
|
||||||
|
|
||||||
gnuflag.UintVar(&rawCfg.DenyTime, "dt", 0, "")
|
gnuflag.UintVar(&rawCfg.DenyTime, "t", 0, "")
|
||||||
gnuflag.UintVar(&rawCfg.DenyTime, "deny-time", 0, "")
|
gnuflag.UintVar(&rawCfg.DenyTime, "deny-time", 0, "")
|
||||||
|
|
||||||
gnuflag.UintVar(&rawCfg.MaxConn, "mc", 0, "")
|
gnuflag.UintVar(&rawCfg.AllowTime, "a", 0, "")
|
||||||
|
gnuflag.UintVar(&rawCfg.AllowTime, "allow-time", 0, "")
|
||||||
|
|
||||||
|
gnuflag.UintVar(&rawCfg.MaxConn, "c", 0, "")
|
||||||
gnuflag.UintVar(&rawCfg.MaxConn, "max-connections", 0, "")
|
gnuflag.UintVar(&rawCfg.MaxConn, "max-connections", 0, "")
|
||||||
|
|
||||||
gnuflag.BoolVar(&rawCfg.ShowVersion, "v", false, "")
|
gnuflag.BoolVar(&rawCfg.ShowVersion, "v", false, "")
|
||||||
|
@ -95,6 +95,7 @@ func main() {
|
|||||||
&banChan,
|
&banChan,
|
||||||
&resetChan,
|
&resetChan,
|
||||||
cfg.MaxConn,
|
cfg.MaxConn,
|
||||||
|
cfg.AllowTime,
|
||||||
))
|
))
|
||||||
|
|
||||||
// Action worker
|
// Action worker
|
||||||
|
@ -9,7 +9,7 @@ allow_script firewall allow script (takes IPs as arguments)
|
|||||||
Options:
|
Options:
|
||||||
-j, --jobs N allow N jobs at once
|
-j, --jobs N allow N jobs at once
|
||||||
-o, --output MODE self|proxy|all|quiet
|
-o, --output MODE self|proxy|all|quiet
|
||||||
-dt, --deny-time TIME minimum ip deny TIME (seconds)
|
-t, --deny-time TIME minimum ip deny TIME (seconds)
|
||||||
-mc, --max-connections N Skip N connections before run deny script
|
-c, --max-connections N Skip N connections before run deny script
|
||||||
-v, --version Show version
|
-v, --version Show version
|
||||||
-h, --help Show help
|
-h, --help Show help
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user