diff --git a/README.md b/README.md index 2c8a8d7..096fab9 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,3 @@ Reseed server for I2P ========================================= - * Author: Matt Drollette - diff --git a/cli.go b/cli.go index ee88567..ca9c1fc 100644 --- a/cli.go +++ b/cli.go @@ -48,11 +48,26 @@ func main() { Value: 300 * time.Second, Usage: "Period to refresh routerInfo lists in time duration format (200ns, 1s, 5m)", }, + cli.BoolFlag{ + Name: "proxy", + Usage: "Trust the IP supplied in the X-Forwarded-For header", + }, + cli.BoolFlag{ + Name: "verbose", + Usage: "Display all access logs", + }, + cli.IntFlag{ + Name: "rateLimit", + Usage: "Maximum number of requests per minute per IP", + }, }, Action: func(c *cli.Context) { server := NewReseeder() server.NetDBDir = c.String("netdb") server.RefreshInterval = c.Duration("refresh") + server.Proxy = c.Bool("proxy") + server.Verbose = c.Bool("verbose") + server.RateLimit = c.Int("rateLimit") server.Start(c.String("addr"), c.String("port"), c.String("cert"), c.String("key")) }, }, @@ -71,7 +86,7 @@ func main() { }, cli.DurationFlag{ Name: "validFor", - Value: 365 * 24 * time.Hour, + Value: 2 * 365 * 24 * time.Hour, Usage: "Duration that certificate is valid for", }, cli.BoolFlag{ diff --git a/reseeder.go b/reseeder.go index 68c4155..5e3e556 100644 --- a/reseeder.go +++ b/reseeder.go @@ -15,7 +15,8 @@ package main // https://geti2p.net/en/docs/spec/updates import ( - "fmt" + "github.com/PuerkitoBio/throttled" + "github.com/PuerkitoBio/throttled/store" "github.com/gorilla/handlers" "github.com/gorilla/mux" "html/template" @@ -28,51 +29,6 @@ import ( "time" ) -type Reseeder struct { - NetDBDir string - nextMap chan []string - RefreshInterval time.Duration -} - -func (rs *Reseeder) Start(addr, port, cert, key string) { - log.Println("Starting reseed server on " + addr + ":" + port) - - r := mux.NewRouter() - s := r.PathPrefix("/netdb").Subrouter() - s.HandleFunc("/", rs.listHandler) - //s.HandleFunc("/i2pseeds.su3", rs.su3Handler) - s.HandleFunc(`/routerInfo-{hash:[A-Za-z0-9+/\-=~]+}.dat`, rs.routerInfoHandler) - - http.Handle("/", handlers.CombinedLoggingHandler(os.Stdout, proxiedHandler(r))) - - go rs.runMap() - rs.Refresh() - - // sample function to update routerInfo map every minute - go func() { - for { - time.Sleep(rs.RefreshInterval) - log.Println("Updating routerInfos") - rs.Refresh() - } - }() - - if _, err := os.Stat(cert); err == nil { - if _, err := os.Stat(key); err == nil { - err := http.ListenAndServeTLS(addr+":"+port, cert, key, nil) - if nil != err { - log.Fatalln(err) - } - return - } - } - - err := http.ListenAndServe(addr+":"+port, nil) - if nil != err { - log.Fatalln(err) - } -} - func proxiedHandler(h http.Handler) http.Handler { return remoteAddrFixup{h} } @@ -88,6 +44,72 @@ func (h remoteAddrFixup) ServeHTTP(w http.ResponseWriter, r *http.Request) { h.h.ServeHTTP(w, r) } +type Reseeder struct { + NetDBDir string + nextMap chan []string + RefreshInterval time.Duration + Proxy bool + Verbose bool + RateLimit int + + listTemplate *template.Template +} + +func (rs *Reseeder) Start(addr, port, cert, key string) { + var err error + + go rs.runMap() + go rs.refresher() + + // parse the template for routerInfo lists + rs.listTemplate, err = template.New("routerinfos").Parse(`NetDB`) + if err != nil { + log.Fatalln("error parsing routerInfo list template", err) + return + } + + r := mux.NewRouter() + s := r.PathPrefix("/netdb").Subrouter() + + s.HandleFunc("/", rs.listHandler) + s.HandleFunc("/i2pseeds.su3", rs.su3Handler) + s.HandleFunc(`/routerInfo-{hash:[A-Za-z0-9+/\-=~]+}.dat`, rs.routerInfoHandler) + + // timeout + muxWithMiddlewares := http.TimeoutHandler(r, time.Second*5, "Timeout!") + + if rs.Proxy { + muxWithMiddlewares = proxiedHandler(muxWithMiddlewares) + } + + th := throttled.RateLimit(throttled.PerMin(rs.RateLimit), &throttled.VaryBy{RemoteAddr: true}, store.NewMemStore(0)) + muxWithMiddlewares = th.Throttle(muxWithMiddlewares) + + if rs.Verbose { + muxWithMiddlewares = handlers.CombinedLoggingHandler(os.Stdout, muxWithMiddlewares) + } + + // try to start tls server + if _, err = os.Stat(cert); err == nil { + if _, err = os.Stat(key); err == nil { + log.Println("Starting TLS reseed server on " + addr + ":" + port) + err := http.ListenAndServeTLS(addr+":"+port, cert, key, muxWithMiddlewares) + if nil != err { + log.Fatalln(err) + } + + return + } + } + + // fall back to regular http server + log.Println("Starting reseed server on " + addr + ":" + port) + err = http.ListenAndServe(addr+":"+port, muxWithMiddlewares) + if nil != err { + log.Fatalln(err) + } +} + func NewReseeder() *Reseeder { return &Reseeder{nextMap: make(chan []string)} } @@ -102,6 +124,14 @@ func (r *Reseeder) runMap() { } } +func (r *Reseeder) refresher() { + for { + log.Println("Updating routerInfos") + r.Refresh() + time.Sleep(r.RefreshInterval) + } +} + func (r *Reseeder) Refresh() { var m []string @@ -133,19 +163,14 @@ func (r *Reseeder) Refresh() { } func (rs *Reseeder) listHandler(w http.ResponseWriter, r *http.Request) { - tmpl, err := template.New("foo").Parse(`NetDB`) + err := rs.listTemplate.Execute(w, <-rs.nextMap) if err != nil { - panic(err) - } - - err = tmpl.Execute(w, <-rs.nextMap) - if err != nil { - panic(err) + log.Fatalln("error rending list template", err) } } func (rs *Reseeder) su3Handler(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, "") + http.NotFound(w, r) } func (rs *Reseeder) routerInfoHandler(w http.ResponseWriter, r *http.Request) { @@ -153,7 +178,8 @@ func (rs *Reseeder) routerInfoHandler(w http.ResponseWriter, r *http.Request) { fileName := "routerInfo-" + vars["hash"] + ".dat" f, err := os.Open(rs.NetDBDir + "/" + fileName) if nil != err { - log.Fatalln("error sending file", err) + http.NotFound(w, r) + log.Println("error sending file", err) return } io.Copy(w, f)