From 7149d6349629603b38703d87ed2c33a56b1394b9 Mon Sep 17 00:00:00 2001 From: Dan Ballard Date: Sat, 26 Sep 2020 13:48:49 -0700 Subject: [PATCH] initial filter and proxy work --- go.mod | 5 ++ go.sum | 2 + main.go | 162 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 169 insertions(+) create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..41300f6 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module git.openprivacy.ca/openprivacy/ddosFilter + +go 1.14 + +require git.openprivacy.ca/openprivacy/log v1.0.1 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..0a84823 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +git.openprivacy.ca/openprivacy/log v1.0.1 h1:NWV5oBTatvlSzUE6wtB+UQCulgyMOtm4BXGd34evMys= +git.openprivacy.ca/openprivacy/log v1.0.1/go.mod h1:gGYK8xHtndRLDymFtmjkG26GaMQNgyhioNS82m812Iw= diff --git a/main.go b/main.go new file mode 100644 index 0000000..941f6f8 --- /dev/null +++ b/main.go @@ -0,0 +1,162 @@ +package main + +import ( + "flag" + "fmt" + "git.openprivacy.ca/openprivacy/log" + "io/ioutil" + "net/http" + "strconv" + "sync" + "time" +) + +const MaxLength = 8192 +const SameCookieTimeLimitMins = 10 + +type Ip2LastSeen map[string]time.Time + +var cookiesToIps sync.Map // map [ cookie string] Ip2LastSeen + +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") + flag.Parse() + log.SetLevel(log.LevelInfo) + log.Infof("Starting ddosFilter on %v -> %v...\n", *listenPort, *proxyPort) + + listen(*listenPort, *proxyPort) +} + +func listen(listenPort, proxyPort int) { + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { filter(w, r, listenPort, proxyPort)}) + 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 + log.Infof("%v: Request %v %v\n", ip, req.Host, req.URL) + cookie, err := req.Cookie("i_like_gogits") + if err != nil { + pass(res, req, listenPort, proxyPort) + return + } + + ips, ok := cookiesToIps.Load(cookie) + + if !ok { + ips := Ip2LastSeen{ip: time.Now()} + cookiesToIps.Store(cookie, ips) + pass(res, req, listenPort, proxyPort) + return + } + + ipsMap := ips.(Ip2LastSeen) + var mostRecent string = "" + + for ip, lastSeen := range ipsMap { + if mostRecent == "" || lastSeen.After(ipsMap[mostRecent]) { + mostRecent = ip + } + } + + ipsMap[ip] = time.Now() + cookiesToIps.Store(cookie, ipsMap) + + if mostRecent == "" || mostRecent == ip { + pass(res, req, listenPort, proxyPort) + return + } + + timeDiff := time.Now().Sub(ipsMap[mostRecent]) + if timeDiff.Minutes() > SameCookieTimeLimitMins { + pass(res, req, listenPort, proxyPort) + return + } + + log.Infof("different IP in the last %v minutes, 404ing\n", SameCookieTimeLimitMins) + 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.Infoln("Request pass to proxy") + + + log.Infof("orig: %v\n", req.Host) + //req.Host = "http://" + strings.Replace(req.Host, strconv.Itoa(listenPort), strconv.Itoa(proxyPort), 1)i + req.Host = "git.danballard.com" + + req.URL.Host = "git.danballard.com" + req.URL.Scheme = "https" + + + if req.Method == "POST" { + body, _ := ioutil.ReadAll(req.Body) + fmt.Printf("POST Body: %v\n", string(body)); + } + + /* Works but loses header and cookies */ + /*rr, err := http.NewRequest(req.Method, req.Host + req.URL.Path, req.Body) + for _, cookie := range req.Cookies() { + rr.AddCookie(cookie) + log.Infof("copy cookie: %v\n", cookie.String()) + }*/ + + log.Infof("req: %v\n", req) + //log.Infof("rr: %v\n", rr) + /*if rr.Method == "POST" { + body, _ := ioutil.ReadAll(rr.Body) + fmt.Printf("POST Body: %v\n", string(body)) + }*/ + //log.Infof("rr body: %v\n", rr.Body.) + var transport http.Transport + resp, err := transport.RoundTrip(req) //rr) + + + 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) + //log.Infof("read BODY: %v\n", string(body)) + if err != nil { + log.Error(err) + return + } + + dH := res.Header() + copyHeader(resp.Header, &dH) + dH.Add("Requested-Host", rr.Host) + res.WriteHeader(resp.StatusCode) + + n, err := res.Write(body) + log.Infof("res.write n: %v err: %v\n", n, err) + +}