ddosFilter/main.go

241 lines
5.8 KiB
Go

package main
import (
"encoding/json"
"flag"
//"fmt"
"git.openprivacy.ca/openprivacy/log"
"io/ioutil"
"net/http"
"os"
"strconv"
"strings"
"sync"
"time"
)
const MaxLength = 8192
const SameCookieTimeLimitMins = 10
const blocklistFile = "blocklist.json"
type Ip2LastSeen map[string]time.Time
var cookiesToIps map[string]Ip2LastSeen // map [ cookie string] Ip2LastSeen
var cookiesLock sync.Mutex
var blocklistedIps map[string]bool
var blocklistLock sync.Mutex
var counter *Counter
func main() {
var listenPort = flag.Int("listenPort", 5999, "port to listen on for incoming HTTP connections")
var proxyPort = flag.Int("proxyPort", 6000, "port to forward connections to that pass the filter")
var logLevel = flag.String("logLevel", "warn", "debug, info, warn, or err")
flag.Parse()
if *logLevel == "debug" {
log.SetLevel(log.LevelDebug)
} else if *logLevel == "info" {
log.SetLevel(log.LevelInfo)
} else if *logLevel == "warn" {
log.SetLevel(log.LevelWarn)
} else if *logLevel == "err" {
log.SetLevel(log.LevelError)
}
log.Infof("Starting ddosFilter on %v -> %v...\n", *listenPort, *proxyPort)
cookiesToIps = map[string]Ip2LastSeen{}
blocklistedIps = map[string]bool{}
load()
counter = NewCounter()
go logger()
go save()
listen(*listenPort, *proxyPort)
}
func load() {
_, err := os.Stat(blocklistFile)
if os.IsNotExist(err) {
log.Warnf("No blocklistFile: %V\n", err)
return
}
bytes, err := ioutil.ReadFile(blocklistFile)
if err != nil {
log.Errorf("Couldn't read blocklist file: %v\n", err)
return
}
err = json.Unmarshal(bytes, &blocklistedIps)
if err != nil {
log.Errorf("Couldn't unmarshal blocklisted IPs: %v\n", err)
}
return
}
func save() {
for {
time.Sleep(1*time.Minute)
blocklistLock.Lock()
bytes, err := json.Marshal(blocklistedIps)
blocklistLock.Unlock()
if err != nil {
log.Error("Could not marshal blockedIPlist: %v\n", err)
continue
}
ioutil.WriteFile(blocklistFile, bytes, 0600)
}
}
func logger() {
for {
time.Sleep(5*time.Second)
log.Infof("Thread count: %v\n", counter.String())
}
}
func listen(listenPort, proxyPort int) {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { counter.Add(); filter(w, r, listenPort, proxyPort); counter.Sub()})
addr := "127.0.0.1:" + strconv.Itoa(listenPort)
err := http.ListenAndServe(addr, nil)
if err != nil {
log.Errorf("ListenAndServe on %v failed: %v\n", addr, err)
return
}
}
func filter(res http.ResponseWriter, req *http.Request, listenPort, proxyPort int) {
ip := req.RemoteAddr
// requires
// proxy_set_header X-Real-IP $remote_addr;
// in nginx server config
if realIp := req.Header.Get("X-Real-IP"); realIp != "" {
ip = realIp
}
blocklistLock.Lock()
blocked, ok := blocklistedIps[ip]
blocklistLock.Unlock()
if ok && blocked {
log.Debugln("blocked ip, 404ing")
http.Redirect(res, req, "http://gitopcybr57ris5iuivfz62gdwe2qk5pinnt2wplpwzicaybw73stjqd.onion", http.StatusSeeOther)
//res.WriteHeader(http.StatusNotFound)
//fmt.Fprint(res, "404 - suspected botnet")
return
}
cookieObj, err := req.Cookie("i_like_gogits")
log.Debugf("ip: %v cookie: %v\n", ip, cookieObj)
if err != nil {
pass(res, req, listenPort, proxyPort)
return
}
cookie := cookieObj.Value
var mostRecent string = ""
var ipList = []string{}
cookiesLock.Lock() /**** Inside cookies Lock *****/
ipsForCookie, foundIpsForCookie := cookiesToIps[cookie]
if !foundIpsForCookie {
ips := Ip2LastSeen{ip: time.Now()}
cookiesToIps[cookie] = ips
} else {
for ip, lastSeen := range ipsForCookie {
ipList = append(ipList, ip)
if mostRecent == "" || lastSeen.After(ipsForCookie[mostRecent]) {
mostRecent = ip
}
}
ipsForCookie[ip] = time.Now()
cookiesToIps[cookie] = ipsForCookie
}
cookiesLock.Unlock() /***** End Cookies Lock *****/
if !foundIpsForCookie {
pass(res, req, listenPort, proxyPort)
return
}
if mostRecent == "" || mostRecent == ip {
pass(res, req, listenPort, proxyPort)
return
}
timeDiff := time.Now().Sub(ipsForCookie[mostRecent])
if timeDiff.Minutes() > SameCookieTimeLimitMins {
pass(res, req, listenPort, proxyPort)
return
}
log.Infof("different IP in the last %v minutes, 404ing\n", SameCookieTimeLimitMins)
blocklistLock.Lock()
for _, ip := range ipList {
blocklistedIps[ip] = true
}
blocklistLock.Unlock()
http.Redirect(res, req, "http://gitopcybr57ris5iuivfz62gdwe2qk5pinnt2wplpwzicaybw73stjqd.onion", http.StatusSeeOther)
// res.WriteHeader(http.StatusNotFound)
//fmt.Fprint(res, "404 - suspected botnet")
}
// https://medium.com/@mlowicki/http-s-proxy-in-golang-in-less-than-100-lines-of-code-6a51c2f2c38c
/*func copyHeader(dst, src *http.Header) {
for k, vv := range src {
for _, v := range vv {
dst.Add(k, v)
}
}
}*/
//https://stackoverflow.com/questions/23164547/golang-reverseproxy-not-working
func copyHeader(source http.Header, dest *http.Header){
for n, v := range source {
for _, vv := range v {
dest.Add(n, vv)
}
}
}
func pass(res http.ResponseWriter, req *http.Request, listenPort, proxyPort int) {
//log.Debugf("Request pass to proxy")
//log.Infof("orig: %v\n", req.Host)
req.Host = strings.Replace(req.Host, strconv.Itoa(listenPort), strconv.Itoa(proxyPort), 1)
//req.Host = "git.danballard.com"
req.URL.Host = req.Host //"git.danballard.com"
req.URL.Scheme = "http"
//log.Infof("req: %v\n", req)
var transport http.Transport
resp, err := transport.RoundTrip(req)
if err != nil {
log.Errorf("Error fetching: %v\n", err.Error())
http.Error(res, err.Error(), http.StatusServiceUnavailable)
return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Error(err)
return
}
dH := res.Header()
copyHeader(resp.Header, &dH)
dH.Add("Requested-Host", req.Host)
res.WriteHeader(resp.StatusCode)
/*n, err := */ res.Write(body)
//log.Infof("res.write n: %v err: %v\n", n, err)
}