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) }