You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
620 lines
15 KiB
620 lines
15 KiB
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 := ` |
|
<center> |
|
<table border=1> |
|
<tr> |
|
<th>Standard Ports</th> |
|
<th>Non Standard Ports</th> |
|
</tr> |
|
<tr> |
|
<td> |
|
` |
|
t2 := ` {{range .}} |
|
{{.}}<br> |
|
{{end}} |
|
` |
|
t3 := ` |
|
</td> |
|
<td> |
|
` |
|
t4 := ` |
|
</td> |
|
</tr> |
|
</table> |
|
</center> |
|
` |
|
|
|
writeHeader(w, r) |
|
fmt.Fprintf(w, "<b>Currently serving the following DNS records</b>") |
|
fmt.Fprintf(w, "<p><center><b>IPv4</b></center></p>") |
|
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, "<p><center><b>IPv6</b></center></p>") |
|
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 := ` |
|
<center> |
|
<table border=1> |
|
<tr> |
|
<th>Node</th> |
|
<th>Summary</th> |
|
</tr> |
|
{{range .}} |
|
<tr> |
|
<td> |
|
<a href="/node?s={{.Seeder}}&nd={{.Key}}">{{.Key}}</a> |
|
</td> |
|
<td> |
|
{{.Value}} |
|
</td> |
|
</tr> |
|
{{end}} |
|
</table> |
|
</center> |
|
` |
|
|
|
writeHeader(w, r) |
|
|
|
if len(ws) == 0 { |
|
fmt.Fprintf(w, "No Nodes found with this status") |
|
} else { |
|
|
|
switch status { |
|
case statusRG: |
|
fmt.Fprintf(w, "<center><b>Node Status: statusRG - (Reported Good) Have not been able to get addresses yet</b></center>") |
|
case statusCG: |
|
fmt.Fprintf(w, "<center><b>Node Status: statusCG - (Currently Good) Able to connect and get addresses</b></center>") |
|
case statusWG: |
|
fmt.Fprintf(w, "<center><b>Node Status: statusWG - (Was Good) Was Ok but now can not get addresses</b></center>") |
|
case statusNG: |
|
fmt.Fprintf(w, "<center><b>Node Status: statusNG - (No Good) Unable to get addresses</b></center>") |
|
} |
|
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("<b>Fail Count:</b> %v <b>DNS Type:</b> %s", |
|
v.connectFails, |
|
v.dns2str()) |
|
case statusCG: |
|
valueStr = fmt.Sprintf("<b>Remote Version:</b> %v%s <b>Last Block:</b> %v <b>DNS Type:</b> %s", |
|
v.version, |
|
v.strVersion, |
|
v.lastBlock, |
|
v.dns2str()) |
|
|
|
case statusWG: |
|
valueStr = fmt.Sprintf("<b>Last Try:</b> %s ago <b>Last Status:</b> %s\n", |
|
time.Since(v.lastTry).String(), |
|
v.statusStr) |
|
|
|
case statusNG: |
|
valueStr = fmt.Sprintf("<b>Fail Count:</b> %v <b>Last Try:</b> %s ago <b>Last Status:</b> %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 := ` |
|
<center> |
|
<table border=1> |
|
<tr> |
|
<th>Node {{.Key}}</th><th>Details</th> |
|
</tr> |
|
<tr><td>IP Address</td><td>{{.IP}}</td></tr> |
|
<tr><td>Port</td><td>{{.Port}}</td></tr> |
|
<tr><td>DNS Type</td><td>{{.Dnstype}}</td></tr> |
|
<tr><td>Non Standard IP</td><td>{{.Nonstdip}}</td></tr> |
|
<tr><td>Last Connect</td><td>{{.Lastconnect}}<br>{{.Lastconnectago}} ago</td></tr> |
|
<tr><td>Last Connect Status</td><td>{{.Statusstr}}</td></tr> |
|
<tr><td>Last Try</td><td>{{.Lasttry}}<br>{{.Lasttryago}} ago</td></tr> |
|
<tr><td>Crawl Start</td><td>{{.Crawlstart}}<br>{{.Crawlstartago}} ago</td></tr> |
|
<tr><td>Crawl Active</td><td>{{.Crawlactive}}</td></tr> |
|
<tr><td>Connection Fails</td><td>{{.Connectfails}}</td></tr> |
|
<tr><td>Remote Version</td><td>{{.Version}}</td></tr> |
|
<tr><td>Remote SubVersion</td><td>{{.Strversion}}</td></tr> |
|
<tr><td>Remote Services</td><td>{{.Services}}</td></tr> |
|
<tr><td>Remote Last Block</td><td>{{.Lastblock}}</td></tr> |
|
</table> |
|
</center> |
|
` |
|
// 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 := ` |
|
<b>Stats for seeder: {{.Name}}</b> |
|
<center> |
|
<table><tr><td> |
|
Node Stats (count/started)<br> |
|
<table border=1><tr> |
|
<td><a href="/statusRG?s={{.Name}}">RG: {{.RG}}/{{.RGS}}</a></td> |
|
<td><a href="/statusCG?s={{.Name}}">CG: {{.CG}}/{{.CGS}}</a></td> |
|
<td><a href="/statusWG?s={{.Name}}">WG: {{.WG}}/{{.WGS}}</a></td> |
|
<td><a href="/statusNG?s={{.Name}}">NG: {{.NG}}/{{.NGS}}</a></td> |
|
<td>Total: {{.Total}}</td> |
|
<td><a title="Export in format consumed by Bitcoin Core contrib/seeds" href="/seeds.txt?s={{.Name}}">seeds.txt</a></td> |
|
</tr></table> |
|
</td><td> |
|
DNS Requests<br> |
|
<table border=1><tr> |
|
<td>V4 Std: {{.V4Std}}</td> |
|
<td>V4 Non: {{.V4Non}}</td> |
|
<td>V6 Std: {{.V6Std}}</td> |
|
<td>V6 Non: {{.V6Non}}</td> |
|
<td><a href="/dns?s={{.Name}}">Total: {{.DNSTotal}}</a></td> |
|
</tr></table> |
|
</td></tr></table> |
|
</center> |
|
` |
|
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 := ` |
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> |
|
<html><head><title>dnsseeder</title></head><body> |
|
<center> |
|
<a href="/summary">Summary</a> |
|
` |
|
fmt.Fprintf(w, h1) |
|
|
|
// read the seeder name |
|
n := r.FormValue("s") |
|
if n != "" { |
|
s := getSeederByName(n) |
|
if s != nil { |
|
fmt.Fprintf(w, "<br><b>Seeder: %s</b>", html.EscapeString(s.name)) |
|
} |
|
} |
|
fmt.Fprintf(w, "</center><hr><br>") |
|
} |
|
|
|
// 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 := ` |
|
<hr> |
|
<center> |
|
<b>Version:</b> {{.Version}} |
|
<b>Uptime:</b> {{.Uptime}} |
|
<b>Request Time:</b> {{.Rt}} |
|
</center> |
|
</body></html> |
|
` |
|
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) |
|
} |
|
} |
|
|
|
/* |
|
|
|
|
|
*/
|
|
|