package main import ( "fmt" "html" "log" "net/http" "text/template" "time" ) // startHTTP runs in a goroutine and provides the web interface // to the dnsseeder func startHTTP(port string) { http.HandleFunc("/dns", dnsWebHandler) http.HandleFunc("/node", nodeHandler) http.HandleFunc("/statusRG", statusRGHandler) http.HandleFunc("/statusCG", statusCGHandler) http.HandleFunc("/statusWG", statusWGHandler) http.HandleFunc("/statusNG", statusNGHandler) http.HandleFunc("/summary", summaryHandler) http.HandleFunc("/seeds.txt", txtHandler) http.HandleFunc("/", emptyHandler) // listen only on localhost err := http.ListenAndServe("127.0.0.1:"+port, nil) if err != nil { log.Fatal("ListenAndServe: ", err) } } // dnsWebHandler processes all requests and returns output in the requested format func dnsWebHandler(w http.ResponseWriter, r *http.Request) { st := time.Now() // skip the s= from the raw query n := r.FormValue("s") s := getSeederByName(n) if s == nil { writeHeader(w, r) fmt.Fprintf(w, "No seeder found: %s", html.EscapeString(n)) writeFooter(w, r, st) return } // FIXME - This is ugly code and needs to be cleaned up a lot config.dnsmtx.RLock() // if the dns map does not have a key for the request it will return an empty slice v4std := config.dns[s.dnsHost+".A"] v4non := config.dns["nonstd."+s.dnsHost+".A"] v6std := config.dns[s.dnsHost+".AAAA"] v6non := config.dns["nonstd."+s.dnsHost+".AAAA"] config.dnsmtx.RUnlock() var v4stdstr, v4nonstr []string var v6stdstr, v6nonstr []string if x := len(v4std); x > 0 { v4stdstr = make([]string, x) for k, v := range v4std { v4stdstr[k] = v.String() } } else { v4stdstr = []string{"No records Available"} } if x := len(v4non); x > 0 { v4nonstr = make([]string, x) for k, v := range v4non { v4nonstr[k] = v.String() } } else { v4nonstr = []string{"No records Available"} } // ipv6 if x := len(v6std); x > 0 { v6stdstr = make([]string, x) for k, v := range v6std { v6stdstr[k] = v.String() } } else { v6stdstr = []string{"No records Available"} } if x := len(v6non); x > 0 { v6nonstr = make([]string, x) for k, v := range v6non { v6nonstr[k] = v.String() } } else { v6nonstr = []string{"No records Available"} } t1 := `
Standard Ports Non Standard Ports
` t2 := ` {{range .}} {{.}}
{{end}} ` t3 := `
` t4 := `
` writeHeader(w, r) fmt.Fprintf(w, "Currently serving the following DNS records") fmt.Fprintf(w, "

IPv4

") fmt.Fprintf(w, t1) t := template.New("v4 template") t, err := t.Parse(t2) if err != nil { log.Printf("error parsing template v4 %v\n", err) } err = t.Execute(w, v4stdstr) if err != nil { log.Printf("error executing template v4 %v\n", err) } fmt.Fprintf(w, t3) err = t.Execute(w, v4nonstr) if err != nil { log.Printf("error executing template v4 non %v\n", err) } fmt.Fprintf(w, t4) // ipv6 records fmt.Fprintf(w, "

IPv6

") fmt.Fprintf(w, t1) err = t.Execute(w, v6stdstr) if err != nil { log.Printf("error executing template v6 %v\n", err) } fmt.Fprintf(w, t3) err = t.Execute(w, v6nonstr) if err != nil { log.Printf("error executing template v6 non %v\n", err) } fmt.Fprintf(w, t4) writeFooter(w, r, st) } // emptyHandler processes all requests for non-existant urls func emptyHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/plain") fmt.Fprintf(w, "Nothing to see here. Move along please\n") } func statusRGHandler(w http.ResponseWriter, r *http.Request) { statusHandler(w, r, statusRG) } func statusCGHandler(w http.ResponseWriter, r *http.Request) { statusHandler(w, r, statusCG) } func statusWGHandler(w http.ResponseWriter, r *http.Request) { statusHandler(w, r, statusWG) } func statusNGHandler(w http.ResponseWriter, r *http.Request) { statusHandler(w, r, statusNG) } type webstatus struct { Key string Value string Seeder string } func statusHandler(w http.ResponseWriter, r *http.Request, status uint32) { startT := time.Now() // read the seeder name n := r.FormValue("s") s := getSeederByName(n) if s == nil { writeHeader(w, r) fmt.Fprintf(w, "No seeder found called %s", html.EscapeString(n)) writeFooter(w, r, startT) return } // gather all the info before writing anything to the remote browser ws := generateWebStatus(s, status) st := `
{{range .}} {{end}}
Node Summary
{{.Key}} {{.Value}}
` writeHeader(w, r) if len(ws) == 0 { fmt.Fprintf(w, "No Nodes found with this status") } else { switch status { case statusRG: fmt.Fprintf(w, "
Node Status: statusRG - (Reported Good) Have not been able to get addresses yet
") case statusCG: fmt.Fprintf(w, "
Node Status: statusCG - (Currently Good) Able to connect and get addresses
") case statusWG: fmt.Fprintf(w, "
Node Status: statusWG - (Was Good) Was Ok but now can not get addresses
") case statusNG: fmt.Fprintf(w, "
Node Status: statusNG - (No Good) Unable to get addresses
") } t := template.New("Status template") t, err := t.Parse(st) if err != nil { log.Printf("error parsing status template %v\n", err) } err = t.Execute(w, ws) if err != nil { log.Printf("error executing status template %v\n", err) } } writeFooter(w, r, startT) } // generateWebStatus is given a node status and returns a slice of webstatus structures // ready to be ranged over by an html/template func generateWebStatus(s *dnsseeder, status uint32) (ws []webstatus) { s.mtx.RLock() defer s.mtx.RUnlock() var valueStr string for k, v := range s.theList { if v.status != status { continue } switch status { case statusRG: valueStr = fmt.Sprintf("Fail Count: %v DNS Type: %s", v.connectFails, v.dns2str()) case statusCG: valueStr = fmt.Sprintf("Remote Version: %v%s Last Block: %v DNS Type: %s", v.version, v.strVersion, v.lastBlock, v.dns2str()) case statusWG: valueStr = fmt.Sprintf("Last Try: %s ago Last Status: %s\n", time.Since(v.lastTry).String(), v.statusStr) case statusNG: valueStr = fmt.Sprintf("Fail Count: %v Last Try: %s ago Last Status: %s\n", v.connectFails, time.Since(v.lastTry).String(), v.statusStr) default: valueStr = "" } ows := webstatus{ Key: k, Value: valueStr, Seeder: s.name, } ws = append(ws, ows) } return ws } // copy Node details into a template friendly struct type webtemplate struct { Key string IP string Port uint16 Statusstr string Rating string Dnstype string Lastconnect string Lastconnectago string Lasttry string Lasttryago string Crawlstart string Crawlstartago string Crawlactive bool Connectfails uint32 Version int32 Strversion string Services string Lastblock int32 Nonstdip string } // nodeHandler displays details about one node func nodeHandler(w http.ResponseWriter, r *http.Request) { st := time.Now() ndt := `
Node {{.Key}}Details
IP Address{{.IP}}
Port{{.Port}}
DNS Type{{.Dnstype}}
Non Standard IP{{.Nonstdip}}
Last Connect{{.Lastconnect}}
{{.Lastconnectago}} ago
Last Connect Status{{.Statusstr}}
Last Try{{.Lasttry}}
{{.Lasttryago}} ago
Crawl Start{{.Crawlstart}}
{{.Crawlstartago}} ago
Crawl Active{{.Crawlactive}}
Connection Fails{{.Connectfails}}
Remote Version{{.Version}}
Remote SubVersion{{.Strversion}}
Remote Services{{.Services}}
Remote Last Block{{.Lastblock}}
` // read the seeder name n := r.FormValue("s") s := getSeederByName(n) if s == nil { writeHeader(w, r) fmt.Fprintf(w, "No seeder found called %s", html.EscapeString(n)) writeFooter(w, r, st) return } s.mtx.RLock() defer s.mtx.RUnlock() k := r.FormValue("nd") writeHeader(w, r) if _, ok := s.theList[k]; ok == false { fmt.Fprintf(w, "Sorry there is no Node with those details\n") } else { nd := s.theList[k] wt := webtemplate{ IP: nd.na.IP.String(), Port: nd.na.Port, Dnstype: nd.dns2str(), Nonstdip: nd.nonstdIP.String(), Statusstr: nd.statusStr, Lastconnect: nd.lastConnect.String(), Lastconnectago: time.Since(nd.lastConnect).String(), Lasttry: nd.lastTry.String(), Lasttryago: time.Since(nd.lastTry).String(), Crawlstart: nd.crawlStart.String(), Crawlstartago: time.Since(nd.crawlStart).String(), Connectfails: nd.connectFails, Crawlactive: nd.crawlActive, Version: nd.version, Strversion: nd.strVersion, Services: nd.services.String(), Lastblock: nd.lastBlock, } // display details for the Node t := template.New("Node template") t, err := t.Parse(ndt) if err != nil { log.Printf("error parsing Node template %v\n", err) } err = t.Execute(w, wt) if err != nil { log.Printf("error executing Node template %v\n", err) } } writeFooter(w, r, st) } // summaryHandler displays details about one node func summaryHandler(w http.ResponseWriter, r *http.Request) { st := time.Now() var hc struct { Name string RG uint32 RGS uint32 CG uint32 CGS uint32 WG uint32 WGS uint32 NG uint32 NGS uint32 Total uint32 V4Std uint32 V4Non uint32 V6Std uint32 V6Non uint32 DNSTotal uint32 } writeHeader(w, r) // loop through each of the seeder name from a slice so they are always returned in // the same order then get a pointer to the seeder struct for _, n := range config.order { s := config.seeders[n] hc.Name = s.name // fill the structs so they can be displayed via the template s.counts.mtx.RLock() hc.RG = s.counts.NdStatus[statusRG] hc.RGS = s.counts.NdStarts[statusRG] hc.CG = s.counts.NdStatus[statusCG] hc.CGS = s.counts.NdStarts[statusCG] hc.WG = s.counts.NdStatus[statusWG] hc.WGS = s.counts.NdStarts[statusWG] hc.NG = s.counts.NdStatus[statusNG] hc.NGS = s.counts.NdStarts[statusNG] hc.Total = hc.RG + hc.CG + hc.WG + hc.NG hc.V4Std = s.counts.DNSCounts[dnsV4Std] hc.V4Non = s.counts.DNSCounts[dnsV4Non] hc.V6Std = s.counts.DNSCounts[dnsV6Std] hc.V6Non = s.counts.DNSCounts[dnsV6Non] hc.DNSTotal = hc.V4Std + hc.V4Non + hc.V6Std + hc.V6Non s.counts.mtx.RUnlock() // we are using basic and simple html here. No fancy graphics or css sp := ` Stats for seeder: {{.Name}}
Node Stats (count/started)
RG: {{.RG}}/{{.RGS}} CG: {{.CG}}/{{.CGS}} WG: {{.WG}}/{{.WGS}} NG: {{.NG}}/{{.NGS}} Total: {{.Total}} seeds.txt
DNS Requests
V4 Std: {{.V4Std}} V4 Non: {{.V4Non}} V6 Std: {{.V6Std}} V6 Non: {{.V6Non}} Total: {{.DNSTotal}}
` t := template.New("Header template") t, err := t.Parse(sp) if err != nil { log.Printf("error parsing summary template %v\n", err) } err = t.Execute(w, hc) if err != nil { log.Printf("error executing summary template %v\n", err) } } writeFooter(w, r, st) } // txtHandler outputs the node list in the format expected by Bitcoin Core's // contrib/seeds script. func txtHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/plain") // read the seeder name n := r.FormValue("s") s := getSeederByName(n) if s == nil { fmt.Fprintf(w, "No seeder found called %s\n", n) return } fmt.Fprintf(w, "# address good lastSuccess %%(2h) %%(8h) %%(1d) %%(7d) %%(30d) blocks svcs version\n") // gather all the info before writing anything to the remote browser s.mtx.RLock() defer s.mtx.RUnlock() for k, v := range s.theList { address := k var good int if v.status == statusCG { good = 1 } else { good = 0 } lastSuccess := v.lastConnect // Alas we don't actually measure this, so fake it. uptime := (100.0 - float32(v.rating)) / 2.0 + 50.0 blocks := v.lastBlock services := v.services version := v.version userAgent := v.strVersion fmt.Fprintf(w, "%s %d %d %.2f%% %.2f%% %.2f%% %.2f%% %.2f%% %d %08x %d %q\n", address, good, lastSuccess.Unix(), uptime, uptime, uptime, uptime, uptime, blocks, int32(services), version, userAgent) } } // writeHeader will output the standard header func writeHeader(w http.ResponseWriter, r *http.Request) { // we are using basic and simple html here. No fancy graphics or css h1 := ` dnsseeder
Summary ` fmt.Fprintf(w, h1) // read the seeder name n := r.FormValue("s") if n != "" { s := getSeederByName(n) if s != nil { fmt.Fprintf(w, "
Seeder: %s", html.EscapeString(s.name)) } } fmt.Fprintf(w, "


") } // writeFooter will output the standard footer func writeFooter(w http.ResponseWriter, r *http.Request, st time.Time) { // Footer needs to be exported for template processing to work var Footer struct { Uptime string Version string Rt string } f := `
Version: {{.Version}} Uptime: {{.Uptime}} Request Time: {{.Rt}}
` Footer.Uptime = time.Since(config.uptime).String() Footer.Version = config.version Footer.Rt = time.Since(st).String() t := template.New("Footer template") t, err := t.Parse(f) if err != nil { log.Printf("error parsing template %v\n", err) } err = t.Execute(w, Footer) if err != nil { log.Printf("error executing template %v\n", err) } if config.verbose { log.Printf("status - processed web request: %s %s\n", r.RemoteAddr, r.RequestURI) } } /* */