random seeder
This commit is contained in:
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
/i2p-reseeder
|
||||
/cert.pem
|
||||
/key.pem
|
||||
/netdb
|
||||
99
cert.go
Normal file
99
cert.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/big"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func GenerateCert(host string, validFrom string, validFor time.Duration, isCA bool, rsaBits int) {
|
||||
if len(host) == 0 {
|
||||
log.Fatalf("Missing required -host parameter")
|
||||
}
|
||||
|
||||
priv, err := rsa.GenerateKey(rand.Reader, rsaBits)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to generate private key: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
var notBefore time.Time
|
||||
if len(validFrom) == 0 {
|
||||
notBefore = time.Now()
|
||||
} else {
|
||||
notBefore, err = time.Parse("Jan 2 15:04:05 2006", validFrom)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to parse creation date: %s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
notAfter := notBefore.Add(validFor)
|
||||
|
||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to generate serial number: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
template := x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
Subject: pkix.Name{
|
||||
Organization: []string{"I2P"},
|
||||
},
|
||||
NotBefore: notBefore,
|
||||
NotAfter: notAfter,
|
||||
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
}
|
||||
|
||||
hosts := strings.Split(host, ",")
|
||||
for _, h := range hosts {
|
||||
if ip := net.ParseIP(h); ip != nil {
|
||||
template.IPAddresses = append(template.IPAddresses, ip)
|
||||
} else {
|
||||
template.DNSNames = append(template.DNSNames, h)
|
||||
}
|
||||
}
|
||||
|
||||
if isCA {
|
||||
template.IsCA = true
|
||||
template.KeyUsage |= x509.KeyUsageCertSign
|
||||
}
|
||||
|
||||
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create certificate: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
certOut, err := os.Create("cert.pem")
|
||||
if err != nil {
|
||||
log.Fatalf("failed to open cert.pem for writing: %s", err)
|
||||
return
|
||||
}
|
||||
pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
|
||||
certOut.Close()
|
||||
log.Print("written cert.pem\n")
|
||||
|
||||
keyOut, err := os.OpenFile("key.pem", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||
if err != nil {
|
||||
log.Print("failed to open key.pem for writing:", err)
|
||||
return
|
||||
}
|
||||
pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})
|
||||
keyOut.Close()
|
||||
log.Print("written key.pem\n")
|
||||
}
|
||||
94
cli.go
Normal file
94
cli.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/codegangsta/cli"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := cli.NewApp()
|
||||
app.Name = "reseeder"
|
||||
app.Version = "1.0.0"
|
||||
app.Usage = "I2P reseed server"
|
||||
|
||||
app.Commands = []cli.Command{
|
||||
{
|
||||
Name: "serve",
|
||||
ShortName: "s",
|
||||
Usage: "Start an http server for SU3 files",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "addr",
|
||||
Value: "",
|
||||
Usage: "Address to bind to",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "port",
|
||||
Value: "8080",
|
||||
Usage: "Port to listen on",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "cert",
|
||||
Value: "cert.pem",
|
||||
Usage: "Certificate for TLS",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "key",
|
||||
Value: "key.pem",
|
||||
Usage: "Key for TLS certificate",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "netdb",
|
||||
Value: "./netdb",
|
||||
Usage: "Path to NetDB directory containing routerInfo files",
|
||||
},
|
||||
cli.DurationFlag{
|
||||
Name: "refresh",
|
||||
Value: 300 * time.Second,
|
||||
Usage: "Period to refresh routerInfo lists in time duration format (200ns, 1s, 5m)",
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) {
|
||||
server := NewReseeder()
|
||||
server.NetDBDir = c.String("netdb")
|
||||
server.RefreshInterval = c.Duration("refresh")
|
||||
server.Start(c.String("addr"), c.String("port"), c.String("cert"), c.String("key"))
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "generate",
|
||||
ShortName: "g",
|
||||
Usage: "Generate a celf-signed certificate",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "host",
|
||||
Usage: "Comma-separated hostnames and IPs to generate a certificate for",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "validFrom",
|
||||
Usage: "Creation date formatted as Jan 1 15:04:05 2011",
|
||||
},
|
||||
cli.DurationFlag{
|
||||
Name: "validFor",
|
||||
Value: 365 * 24 * time.Hour,
|
||||
Usage: "Duration that certificate is valid for",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "ca",
|
||||
Usage: "Whether this cert should be its own Certificate Authority",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "rsaBits",
|
||||
Value: 2048,
|
||||
Usage: "Size of RSA key to generate",
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) {
|
||||
GenerateCert(c.String("host"), c.String("validFrom"), c.Duration("validFor"), c.Bool("isCA"), c.Int("rsaBits"))
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
app.Run(os.Args)
|
||||
}
|
||||
231
reseeder.go
231
reseeder.go
@@ -1,142 +1,78 @@
|
||||
package main
|
||||
|
||||
// read in all files from netdb dir into a slice of routerinfos
|
||||
|
||||
// for every unique requesting IP
|
||||
// look up that IP in the db
|
||||
// - if it exists, check the creation time
|
||||
// - if the creation time is within the threshold, serve up the routerinfos
|
||||
// - if the creation time is outside the threshold, or if it does not exist generate a new slice of routerinfos from the current master set
|
||||
|
||||
// at some regular interval, update the master slice with fresh netdb routerinfos
|
||||
|
||||
// can serve up html/ul of routerinfos
|
||||
// can serve up su3 signed file
|
||||
// https://geti2p.net/en/docs/spec/updates
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/braintree/manners"
|
||||
"github.com/gorilla/handlers"
|
||||
"github.com/gorilla/mux"
|
||||
"html/template"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var netdbDir = flag.String("dir", "./netdb", "Location of your netdb directory")
|
||||
var bindIp = flag.String("ip", "", "Interface to bind to")
|
||||
var bindPort = flag.String("port", "3000", "Port to bind to")
|
||||
type Reseeder struct {
|
||||
NetDBDir string
|
||||
nextMap chan []string
|
||||
RefreshInterval time.Duration
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
func (rs *Reseeder) Start(addr, port, cert, key string) {
|
||||
log.Println("Starting reseed server on " + addr + ":" + port)
|
||||
|
||||
netdb := NewNetDb(*netdbDir)
|
||||
reseeder := &Reseeder{netdb, make([]*Peer, 100)}
|
||||
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)
|
||||
|
||||
log.Printf("Starting server on %s:%s serving netdb from %s", *bindIp, *bindPort, *netdbDir)
|
||||
http.Handle("/", handlers.CombinedLoggingHandler(os.Stdout, proxiedHandler(r)))
|
||||
|
||||
server := manners.NewServer()
|
||||
go rs.runMap()
|
||||
rs.Refresh()
|
||||
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, os.Interrupt)
|
||||
// sample function to update routerInfo map every minute
|
||||
go func() {
|
||||
<-c
|
||||
log.Println("waiting for connections to close and exiting...")
|
||||
server.Shutdown <- true
|
||||
go time.AfterFunc(time.Duration(5)*time.Second, func() {
|
||||
log.Println("Killing idle connections...")
|
||||
os.Exit(0)
|
||||
})
|
||||
for {
|
||||
time.Sleep(rs.RefreshInterval)
|
||||
log.Println("Updating routerInfos")
|
||||
rs.Refresh()
|
||||
}
|
||||
}()
|
||||
|
||||
err := server.ListenAndServe(*bindIp+":"+*bindPort, handlers.CombinedLoggingHandler(os.Stdout, reseeder))
|
||||
if err != nil {
|
||||
log.Fatal("ListenAndServe: ", err)
|
||||
}
|
||||
}
|
||||
|
||||
type Reseeder struct {
|
||||
NetDb *NetDb
|
||||
Peers []*Peer
|
||||
}
|
||||
|
||||
func (rs *Reseeder) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
infos := make(chan *RouterInfo)
|
||||
|
||||
peer := &Peer{req.RemoteAddr, time.Now(), time.Now()}
|
||||
|
||||
go rs.GetForPeer(peer, infos)
|
||||
|
||||
for info := range infos {
|
||||
fmt.Fprintf(w, "%s\n", info.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func (rs *Reseeder) GetForPeer(peer *Peer, infos chan *RouterInfo) {
|
||||
rs.NetDb.lock.RLock()
|
||||
defer rs.NetDb.lock.RUnlock()
|
||||
|
||||
for _, info := range rs.NetDb.RouterInfos {
|
||||
infos <- info
|
||||
}
|
||||
close(infos)
|
||||
}
|
||||
|
||||
func NewNetDb(dir string) *NetDb {
|
||||
if _, err := os.Stat(dir); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
log.Fatalf("netdb directory is not readable: %s", dir)
|
||||
} else {
|
||||
// other error
|
||||
}
|
||||
}
|
||||
netdb := &NetDb{Dir: dir, RouterInfos: make(map[string]*RouterInfo, 1000)}
|
||||
netdb.Refresh()
|
||||
|
||||
return netdb
|
||||
}
|
||||
|
||||
type NetDb struct {
|
||||
lock sync.RWMutex
|
||||
RouterInfos map[string]*RouterInfo
|
||||
Dir string
|
||||
}
|
||||
|
||||
func (db *NetDb) Refresh() {
|
||||
files, err := ioutil.ReadDir(db.Dir)
|
||||
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.Fatalf("unable to read %s", db.Dir)
|
||||
log.Fatalln(err)
|
||||
}
|
||||
for _, file := range files {
|
||||
db.Set(file.Name(), NewRouterInfo(db.Dir+file.Name()))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (db *NetDb) Get(key string) (*RouterInfo, bool) {
|
||||
db.lock.RLock()
|
||||
defer db.lock.RUnlock()
|
||||
d, ok := db.RouterInfos[key]
|
||||
return d, ok
|
||||
err := http.ListenAndServe(addr+":"+port, nil)
|
||||
if nil != err {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (db *NetDb) Set(key string, d *RouterInfo) {
|
||||
db.lock.Lock()
|
||||
defer db.lock.Unlock()
|
||||
db.RouterInfos[key] = d
|
||||
}
|
||||
|
||||
func (db *NetDb) UnSet(key string) {
|
||||
db.lock.Lock()
|
||||
defer db.lock.Unlock()
|
||||
delete(db.RouterInfos, key)
|
||||
}
|
||||
|
||||
func NewRouterInfo(file string) *RouterInfo {
|
||||
return &RouterInfo{file}
|
||||
}
|
||||
|
||||
type RouterInfo struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
type Peer struct {
|
||||
Ip string
|
||||
Seen time.Time
|
||||
Created time.Time
|
||||
}
|
||||
|
||||
//// stuff for reverse proxy handling
|
||||
func proxiedHandler(h http.Handler) http.Handler {
|
||||
return remoteAddrFixup{h}
|
||||
}
|
||||
@@ -151,3 +87,74 @@ func (h remoteAddrFixup) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
h.h.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func NewReseeder() *Reseeder {
|
||||
return &Reseeder{nextMap: make(chan []string)}
|
||||
}
|
||||
|
||||
func (r *Reseeder) runMap() {
|
||||
var m []string
|
||||
for {
|
||||
select {
|
||||
case m = <-r.nextMap:
|
||||
case r.nextMap <- m:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Reseeder) Refresh() {
|
||||
var m []string
|
||||
|
||||
src, err := ioutil.ReadDir(r.NetDBDir)
|
||||
if nil != err {
|
||||
log.Fatalln("error reading netdb dir", err)
|
||||
return
|
||||
}
|
||||
|
||||
// randomize the file order
|
||||
files := make([]os.FileInfo, len(src))
|
||||
perm := rand.Perm(len(src))
|
||||
for i, v := range perm {
|
||||
files[v] = src[i]
|
||||
}
|
||||
|
||||
added := 0
|
||||
for _, file := range files {
|
||||
if !file.IsDir() && file.Name() != "." && file.Name() != ".." {
|
||||
m = append(m, file.Name())
|
||||
added++
|
||||
}
|
||||
if added >= 50 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
r.nextMap <- m
|
||||
}
|
||||
|
||||
func (rs *Reseeder) listHandler(w http.ResponseWriter, r *http.Request) {
|
||||
tmpl, err := template.New("foo").Parse(`<html><head><title>NetDB</title></head><body><ul>{{ range . }}<li><a href="{{ . }}">{{ . }}</a></li>{{ end }}</ul></body></html>`)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = tmpl.Execute(w, <-rs.nextMap)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (rs *Reseeder) su3Handler(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "")
|
||||
}
|
||||
|
||||
func (rs *Reseeder) routerInfoHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
fileName := "routerInfo-" + vars["hash"] + ".dat"
|
||||
f, err := os.Open(rs.NetDBDir + "/" + fileName)
|
||||
if nil != err {
|
||||
log.Fatalln("error sending file", err)
|
||||
return
|
||||
}
|
||||
io.Copy(w, f)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user