From 6fa6d6dfc9e5a88e7dff2ed3c148b3b4271f566c Mon Sep 17 00:00:00 2001 From: Jeff Carr Date: Thu, 28 Dec 2023 09:43:45 -0600 Subject: Detect that a VPN is needed IPv6() returns true if it's working display duration a 'DNS Lookup Status' window actual dig results display status and failure counters count lookup failures and successes add TCP dns lookup logic to test if dns is working at all add DNS over HTTPS cloudflare new & update kind of working holy shit, go.wit.com finally works with git mod tidy working, but cloudflare api stuff is broken AAAA '(none)' logic detection is better cloudflare control panel display the working real AAAA addresses Signed-off-by: Jeff Carr --- .gitignore | 2 +- README.md | 1 + cloudflare/api.go | 26 +- cloudflare/cloudflare.go | 182 -------------- cloudflare/durationSlider.go | 2 + cloudflare/http.go | 49 ++-- cloudflare/mainWindow.go | 3 +- cloudflare/oneLiner.go | 42 ++++ cloudflare/rr.go | 157 ++++++++++++ cloudflare/structs.go | 2 +- dns-https.go | 62 +++++ dnsLookupStatus.go | 370 ++++++++++++++++++++++++++++ examples/cloudflare/Makefile | 24 -- examples/cloudflare/argv.go | 30 --- examples/cloudflare/config.go | 71 ------ examples/cloudflare/main.go | 52 ---- examples/control-panel-cloudflare/Makefile | 24 ++ examples/control-panel-cloudflare/argv.go | 30 +++ examples/control-panel-cloudflare/config.go | 71 ++++++ examples/control-panel-cloudflare/main.go | 40 +++ go.mod | 39 +++ go.sum | 88 +++++++ gui.go | 116 +++++++-- hostname.go | 57 ++++- main.go | 27 +- net.go | 6 +- structs.go | 4 + 27 files changed, 1141 insertions(+), 436 deletions(-) delete mode 100644 cloudflare/cloudflare.go create mode 100644 cloudflare/oneLiner.go create mode 100644 cloudflare/rr.go create mode 100644 dns-https.go create mode 100644 dnsLookupStatus.go delete mode 100644 examples/cloudflare/Makefile delete mode 100644 examples/cloudflare/argv.go delete mode 100644 examples/cloudflare/config.go delete mode 100644 examples/cloudflare/main.go create mode 100644 examples/control-panel-cloudflare/Makefile create mode 100644 examples/control-panel-cloudflare/argv.go create mode 100644 examples/control-panel-cloudflare/config.go create mode 100644 examples/control-panel-cloudflare/main.go create mode 100644 go.sum diff --git a/.gitignore b/.gitignore index 148dce4..cd8a456 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,4 @@ control-panel-dns *.swp /plugins/* -examples/cloudflare/cloudflare +examples/control-panel-cloudflare/control-panel-cloudflare diff --git a/README.md b/README.md index e98c2b1..74f747f 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ to update the DNS server. Useful links and other external things which might be useful +* [DNS Resource Record Types](https://en.wikipedia.org/wiki/List_of_DNS_record_types) * [WIT GO projects](http://go.wit.com/) * [GOLANG GUI](https://go.wit.com/gui) * [GO Style Guide](https://google.github.io/styleguide/go/index) diff --git a/cloudflare/api.go b/cloudflare/api.go index 1769f6b..036adc7 100644 --- a/cloudflare/api.go +++ b/cloudflare/api.go @@ -10,11 +10,20 @@ import ( "github.com/davecgh/go-spew/spew" ) +/* + This function should run each time + the user chanegs anything in the GUi + or each time something in general changes + + It returns a RR record which then can be + turned into JSON and sent via http + to cloudflare's API +*/ func DoChange() *RRT { var dnsRow *RRT dnsRow = new(RRT) - log.Println("Look for changes in row", dnsRow.ID) + log.Println("DoChange() START") if (CFdialog.proxyNode.S == "On") { dnsRow.Proxied = true } else { @@ -88,21 +97,6 @@ func SetRow(dnsRow *RRT) { log.Println("http PUT curl =", pretty) CFdialog.curlNode.SetText(pretty) } - - return - log.Println("UPDATE VALUE", CFdialog.NameNode.Name, CFdialog.TypeNode.Name, "to", CFdialog.ValueNode.S) - stuff, result := httpPut(dnsRow) - if (CFdialog.curlNode != nil) { - pretty, _ := FormatJSON(stuff) - log.Println("http PUT curl =", pretty) - CFdialog.curlNode.SetText(pretty) - } - if (CFdialog.resultNode != nil) { - pretty, _ := FormatJSON(result) - log.Println("http PUT result =", pretty) - CFdialog.resultNode.SetText(pretty) - } - // CFdialog.saveNode.Disable() } func GetZonefile(c *ConfigT) *DNSRecords { diff --git a/cloudflare/cloudflare.go b/cloudflare/cloudflare.go deleted file mode 100644 index db98c92..0000000 --- a/cloudflare/cloudflare.go +++ /dev/null @@ -1,182 +0,0 @@ -// This is a simple example -package cloudflare - -import ( - "log" - "os" - - "go.wit.com/gui" -) - -func init() { - Config = make(map[string]*ConfigT) -} - -func CreateRR(myGui *gui.Node, zone string, zoneID string) { - if (CFdialog.cloudflareW != nil) { - // skip this if the window has already been created - log.Println("createRR() the cloudflare window already exists") - CFdialog.cloudflareB.Disable() - return - } - CFdialog.cloudflareW = myGui.NewWindow("cloudflare " + zone + " API") - CFdialog.cloudflareW.Custom = func () { - log.Println("createRR() don't really exit here") - CFdialog.cloudflareW = nil - CFdialog.cloudflareB.Enable() - } - - group := CFdialog.cloudflareW.NewGroup("Create a new DNS Resource Record (rr)") - - // make a grid 2 things wide - grid := group.NewGrid("gridnuts", 2, 3) - - grid.NewLabel("zone") - CFdialog.zoneNode = grid.NewLabel("zone") - CFdialog.zoneNode.SetText(zone) - - grid.NewLabel("zone ID") - CFdialog.zoneIdNode = grid.NewLabel("zoneID") - CFdialog.zoneIdNode.SetText(zoneID) - - grid.NewLabel("shell env $CF_API_EMAIL") - CFdialog.emailNode = grid.NewLabel("type") - CFdialog.emailNode.SetText(os.Getenv("CF_API_EMAIL")) - - grid.NewLabel("shell env $CF_API_KEY") - CFdialog.apiNode = grid.NewLabel("type") - CFdialog.apiNode.SetText(os.Getenv("CF_API_KEY")) - - grid.NewLabel("Resource Record ID") - CFdialog.rrNode = grid.NewLabel("type") - CFdialog.rrNode.SetText(os.Getenv("cloudflare RR id")) - - grid.NewLabel("Record Type") - CFdialog.TypeNode = grid.NewCombobox("type") - CFdialog.TypeNode.AddText("A") - CFdialog.TypeNode.AddText("AAAA") - CFdialog.TypeNode.AddText("CNAME") - CFdialog.TypeNode.AddText("TXT") - CFdialog.TypeNode.AddText("MX") - CFdialog.TypeNode.AddText("NS") - CFdialog.TypeNode.Custom = func () { - DoChange() - } - CFdialog.TypeNode.SetText("AAAA") - - grid.NewLabel("Name (usually the hostname)") - CFdialog.NameNode = grid.NewCombobox("name") - CFdialog.NameNode.AddText("www") - CFdialog.NameNode.AddText("mail") - CFdialog.NameNode.AddText("git") - CFdialog.NameNode.AddText("go") - CFdialog.NameNode.AddText("blog") - CFdialog.NameNode.AddText("ns1") - CFdialog.NameNode.Custom = func () { - DoChange() - } - CFdialog.NameNode.SetText("www") - - grid.NewLabel("Cloudflare Proxy") - CFdialog.proxyNode = grid.NewDropdown("proxy") - CFdialog.proxyNode.AddText("On") - CFdialog.proxyNode.AddText("Off") - CFdialog.proxyNode.Custom = func () { - DoChange() - } - CFdialog.proxyNode.SetText("Off") - - grid.NewLabel("Value") - CFdialog.ValueNode = grid.NewCombobox("value") - CFdialog.ValueNode.AddText("127.0.0.1") - CFdialog.ValueNode.AddText("2001:4860:4860::8888") - CFdialog.ValueNode.AddText("ipv6.wit.com") - CFdialog.ValueNode.Custom = func () { - DoChange() - } - CFdialog.ValueNode.SetText("127.0.0.1") - CFdialog.ValueNode.Expand() - - grid.NewLabel("URL") - CFdialog.urlNode = grid.NewLabel("URL") - - group.NewLabel("curl") - CFdialog.curlNode = group.NewTextbox("curl") - CFdialog.curlNode.Custom = func () { - DoChange() - } - CFdialog.curlNode.SetText("put the curl text here") - - CFdialog.resultNode = group.NewTextbox("result") - CFdialog.resultNode.SetText("API response will show here") - - CFdialog.saveNode = group.NewButton("Save", func () { - dnsRow := DoChange() - result := curlPost(dnsRow) - CFdialog.resultNode.SetText(result) - // CreateCurlRR() - // url, data := CreateCurlRR() - // result := curl(url, data) - // CFdialog.resultNode.SetText(result) - }) - // CFdialog.saveNode.Disable() - - group.Pad() - grid.Pad() - grid.Expand() -} - -/* -func CreateCurlRR() (string, string) { - // enable the Save/Create Button - if (CFdialog.saveNode != nil) { - CFdialog.saveNode.Enable() - } - - if (CFdialog.TypeNode != nil) { - CFdialog.Type = CFdialog.TypeNode.S - } - if (CFdialog.NameNode != nil) { - CFdialog.Name = CFdialog.NameNode.S - } - if (CFdialog.proxyNode != nil) { - if (CFdialog.proxyNode.S == "On") { - CFdialog.ProxyS = "true" - } else { - CFdialog.ProxyS = "false" - } - } - if (CFdialog.ValueNode != nil) { - CFdialog.Content = CFdialog.ValueNode.S - } - CFdialog.Ttl = "3600" - - var url string = "https://api.cloudflare.com/client/v4/zones/" + CFdialog.ID + "/dns_records" - // https://api.cloudflare.com/client/v4/zones/zone_identifier/dns_records \ - // var authKey string = os.Getenv("CF_API_KEY") - // var email string = os.Getenv("CF_API_EMAIL") - - // make a json record to send on port 80 to cloudflare - var tmp string - tmp = `{"content": "` + CFdialog.Content + `", ` - tmp += `"name": "` + CFdialog.Name + `", ` - tmp += `"type": "` + CFdialog.Type + `", ` - tmp += `"ttl": ` + CFdialog.Ttl + `, ` - tmp += `"proxied": ` + CFdialog.ProxyS + `, ` - tmp += `"comment": "WIT DNS Control Panel"` - tmp += `}` - data := []byte(tmp) - - log.Println("http PUT url =", url) - // log.Println("http PUT data =", data) - // spew.Dump(data) - pretty, _ := FormatJSON(string(data)) - log.Println("http URL =", url) - log.Println("http PUT data =", pretty) - if (CFdialog.curlNode != nil) { - CFdialog.curlNode.SetText("URL: " + url + "\n" + pretty) - } - - return url, tmp -} -*/ diff --git a/cloudflare/durationSlider.go b/cloudflare/durationSlider.go index 0d9559b..1ab0e50 100644 --- a/cloudflare/durationSlider.go +++ b/cloudflare/durationSlider.go @@ -9,6 +9,8 @@ import ( "go.wit.com/gui" ) +// TODO: use: https://github.com/robfig/cron/ + // ttl := cloudflare.DurationSlider(g2, "control panel TTL (in tenths of seconds)", 10 * time.Millisecond, 5 * time.Second) // ttl.Set(200 * time.Millisecond) diff --git a/cloudflare/http.go b/cloudflare/http.go index f258f61..1917c8b 100644 --- a/cloudflare/http.go +++ b/cloudflare/http.go @@ -3,7 +3,6 @@ package cloudflare import ( "log" - "fmt" "io/ioutil" "net/http" "bytes" @@ -27,45 +26,43 @@ curl --request POST \ }' */ -func httpPut(dnsRow *RRT) (string, string) { - var url string = cloudflareURL + dnsRow.ZoneID + "/dns_records/" + dnsRow.ID - var authKey string = dnsRow.Auth - var email string = dnsRow.Email - - var tmp string - tmp = makeJSON(dnsRow) - data := []byte(tmp) +func doCurl(method string, rr *RRT) string { + var err error + var req *http.Request - log.Println("http PUT url =", url) - // log.Println("http PUT data =", data) - // spew.Dump(data) - pretty, _ := FormatJSON(string(data)) - log.Println("http PUT data =", pretty) + data := []byte(rr.data) - req, err := http.NewRequest(http.MethodPut, url, bytes.NewBuffer(data)) + if (method == "PUT") { + req, err = http.NewRequest(http.MethodPut, rr.url, bytes.NewBuffer(data)) + } else { + req, err = http.NewRequest(http.MethodPost, rr.url, bytes.NewBuffer(data)) + } // Set headers req.Header.Set("Content-Type", "application/json") - req.Header.Set("X-Auth-Key", authKey) - req.Header.Set("X-Auth-Email", email) + req.Header.Set("X-Auth-Key", rr.Auth) + req.Header.Set("X-Auth-Email", rr.Email) + + log.Println("http PUT url =", rr.url) + log.Println("http PUT Auth =", rr.Auth) + log.Println("http PUT Email =", rr.Email) + log.Println("http PUT data =", rr.data) client := &http.Client{} resp, err := client.Do(req) if err != nil { log.Println(err) - return tmp, fmt.Sprintf("blah err =", err) + return "" } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { log.Println(err) - return tmp, fmt.Sprintf("blah err =", err) + return "" } - // log.Println("http PUT body =", body) - // spew.Dump(body) - return tmp, string(body) + return string(body) } func curlPost(dnsRow *RRT) string { @@ -75,10 +72,10 @@ func curlPost(dnsRow *RRT) string { url := dnsRow.url tmp := dnsRow.data - log.Println("curl() START") - log.Println("curl() authkey = ", authKey) - log.Println("curl() email = ", email) - log.Println("curl() url = ", url) + log.Println("curlPost() START") + log.Println("curlPost() authkey = ", authKey) + log.Println("curlPost() email = ", email) + log.Println("curlPost() url = ", url) data := []byte(tmp) req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(data)) diff --git a/cloudflare/mainWindow.go b/cloudflare/mainWindow.go index dddb0b7..af5b221 100644 --- a/cloudflare/mainWindow.go +++ b/cloudflare/mainWindow.go @@ -9,7 +9,7 @@ import ( ) // This creates a window -func MakeCloudflareWindow(n *gui.Node) { +func MakeCloudflareWindow(n *gui.Node) *gui.Node { CFdialog.rootGui = n var t *gui.Node @@ -54,6 +54,7 @@ func MakeCloudflareWindow(n *gui.Node) { showCloudflareCredentials(more) makeDebugWindow(CFdialog.mainWindow) + return CFdialog.mainWindow } func makeConfigWindow(n *gui.Node) { diff --git a/cloudflare/oneLiner.go b/cloudflare/oneLiner.go new file mode 100644 index 0000000..5fc7a51 --- /dev/null +++ b/cloudflare/oneLiner.go @@ -0,0 +1,42 @@ +// This is a simple example +package cloudflare + +import ( + "log" + + "go.wit.com/gui" +) + +type OneLiner struct { + p *gui.Node // parent widget + l *gui.Node // label widget + v *gui.Node // value widget + + value string + label string + + Custom func() +} + +func (n *OneLiner) Set(value string) { + log.Println("OneLiner.Set() =", value) + n.v.Set(value) + n.value = value +} + +func NewOneLiner(n *gui.Node, name string) *OneLiner { + d := OneLiner { + p: n, + value: "", + } + + // various timeout settings + d.l = n.NewLabel(name) + d.v = n.NewLabel("") + d.v.Custom = func() { + d.value = d.v.S + log.Println("OneLiner.Custom() user changed value to =", d.value) + } + + return &d +} diff --git a/cloudflare/rr.go b/cloudflare/rr.go new file mode 100644 index 0000000..05065b2 --- /dev/null +++ b/cloudflare/rr.go @@ -0,0 +1,157 @@ +/* + This will let you edit a single Resource Record within + a DNS zone file. For example: + google-dns.wit.com. 1 IN A 8.8.8.8 +*/ + +package cloudflare + +import ( + "log" + "os" + + "go.wit.com/gui" +) + +func init() { + Config = make(map[string]*ConfigT) +} + +func CreateRR(myGui *gui.Node, zone string, zoneID string) { + if (CFdialog.cloudflareW != nil) { + // skip this if the window has already been created + log.Println("createRR() the cloudflare window already exists") + CFdialog.cloudflareB.Disable() + return + } + CFdialog.cloudflareW = myGui.NewWindow("cloudflare " + zone + " API") + CFdialog.cloudflareW.Custom = func () { + log.Println("createRR() don't really exit here") + CFdialog.cloudflareW = nil + CFdialog.cloudflareB.Enable() + } + + group := CFdialog.cloudflareW.NewGroup("Create a new DNS Resource Record (rr)") + + // make a grid 2 things wide + grid := group.NewGrid("gridnuts", 2, 3) + + grid.NewLabel("zone") + CFdialog.zoneNode = grid.NewLabel("zone") + CFdialog.zoneNode.SetText(zone) + + grid.NewLabel("zone ID") + CFdialog.zoneIdNode = grid.NewLabel("zoneID") + CFdialog.zoneIdNode.SetText(zoneID) + + grid.NewLabel("shell env $CF_API_EMAIL") + CFdialog.emailNode = grid.NewLabel("type") + CFdialog.emailNode.SetText(os.Getenv("CF_API_EMAIL")) + + grid.NewLabel("shell env $CF_API_KEY") + CFdialog.apiNode = grid.NewLabel("type") + CFdialog.apiNode.SetText(os.Getenv("CF_API_KEY")) + + grid.NewLabel("Resource Record ID") + CFdialog.rrNode = grid.NewLabel("type") + CFdialog.rrNode.SetText(os.Getenv("cloudflare RR id")) + + grid.NewLabel("Record Type") + CFdialog.TypeNode = grid.NewCombobox("type") + CFdialog.TypeNode.AddText("A") + CFdialog.TypeNode.AddText("AAAA") + CFdialog.TypeNode.AddText("CNAME") + CFdialog.TypeNode.AddText("TXT") + CFdialog.TypeNode.AddText("MX") + CFdialog.TypeNode.AddText("NS") + CFdialog.TypeNode.Custom = func () { + DoChange() + } + CFdialog.TypeNode.SetText("AAAA") + + grid.NewLabel("Name (usually the hostname)") + CFdialog.NameNode = grid.NewCombobox("name") + CFdialog.NameNode.AddText("www") + CFdialog.NameNode.AddText("mail") + CFdialog.NameNode.AddText("git") + CFdialog.NameNode.AddText("go") + CFdialog.NameNode.AddText("blog") + CFdialog.NameNode.AddText("ns1") + CFdialog.NameNode.Custom = func () { + DoChange() + } + CFdialog.NameNode.SetText("www") + + grid.NewLabel("Cloudflare Proxy") + CFdialog.proxyNode = grid.NewDropdown("proxy") + CFdialog.proxyNode.AddText("On") + CFdialog.proxyNode.AddText("Off") + CFdialog.proxyNode.Custom = func () { + DoChange() + } + CFdialog.proxyNode.SetText("Off") + + grid.NewLabel("Value") + CFdialog.ValueNode = grid.NewCombobox("value") + CFdialog.ValueNode.AddText("127.0.0.1") + CFdialog.ValueNode.AddText("2001:4860:4860::8888") + CFdialog.ValueNode.AddText("ipv6.wit.com") + CFdialog.ValueNode.Custom = func () { + DoChange() + } + CFdialog.ValueNode.SetText("127.0.0.1") + CFdialog.ValueNode.Expand() + + grid.NewLabel("URL") + CFdialog.urlNode = grid.NewLabel("URL") + + group.NewLabel("curl") + CFdialog.curlNode = group.NewTextbox("curl") + CFdialog.curlNode.Custom = func () { + DoChange() + } + CFdialog.curlNode.SetText("put the curl text here") + + CFdialog.resultNode = group.NewTextbox("result") + CFdialog.resultNode.SetText("API response will show here") + + CFdialog.SaveNode = group.NewButton("Save curlPost()", func () { + dnsRow := DoChange() + result := curlPost(dnsRow) + CFdialog.resultNode.SetText(result) + // CreateCurlRR() + // url, data := CreateCurlRR() + // result := curl(url, data) + // CFdialog.resultNode.SetText(result) + }) + // CFdialog.saveNode.Disable() + group.NewButton("New RR doCurl(PUT)", func () { + rr := DoChange() + + rr.url = "https://api.cloudflare.com/client/v4/zones/" + rr.ZoneID + "/dns_records" + + result := doCurl("POST", rr) + CFdialog.resultNode.SetText(result) + + pretty, _ := FormatJSON(result) + log.Println(pretty) + }) + + group.NewButton("Update RR doCurl(PUT)", func () { + rr := DoChange() + + rr.url = "https://api.cloudflare.com/client/v4/zones/" + rr.ZoneID + "/dns_records/" + rr.ID + + result := doCurl("PUT", rr) + CFdialog.resultNode.SetText(result) + + pretty, _ := FormatJSON(result) + log.Println(pretty) + }) + // CFdialog.saveNode.Disable() + + + group.Pad() + grid.Pad() + grid.Expand() +} diff --git a/cloudflare/structs.go b/cloudflare/structs.go index f0a23d8..9efef62 100644 --- a/cloudflare/structs.go +++ b/cloudflare/structs.go @@ -50,7 +50,7 @@ type dialogT struct { ttlNode *gui.Node // just set to 1 which means automatic to cloudflare curlNode *gui.Node // shows you what you could run via curl resultNode *gui.Node // what the cloudflare API returned - saveNode *gui.Node // button to send it to cloudflare + SaveNode *gui.Node // button to send it to cloudflare zoneNode *gui.Node // "wit.com" zoneIdNode *gui.Node // cloudflare zone ID diff --git a/dns-https.go b/dns-https.go new file mode 100644 index 0000000..00eec82 --- /dev/null +++ b/dns-https.go @@ -0,0 +1,62 @@ +package main + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" +) + +// dnsLookupDoH performs a DNS lookup for AAAA records over HTTPS. +func dnsLookupDoH(domain string) ([]string, error) { + var ipv6Addresses []string + + // Construct the URL for a DNS query with Google's DNS-over-HTTPS API + url := fmt.Sprintf("https://dns.google/resolve?name=%s&type=AAAA", domain) + + // Perform the HTTP GET request + resp, err := http.Get(url) + if err != nil { + return nil, fmt.Errorf("error performing DNS-over-HTTPS request: %w", err) + } + defer resp.Body.Close() + + // Read and unmarshal the response body + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("error reading response: %w", err) + } + + var data struct { + Answer []struct { + Data string `json:"data"` + } `json:"Answer"` + } + + if err := json.Unmarshal(body, &data); err != nil { + return nil, fmt.Errorf("error unmarshaling response: %w", err) + } + + // Extract the IPv6 addresses + for _, answer := range data.Answer { + ipv6Addresses = append(ipv6Addresses, answer.Data) + } + + return ipv6Addresses, nil +} + +/* +func main() { + domain := "google.com" + ipv6Addresses, err := dnsLookupDoH(domain) + if err != nil { + fmt.Println("Error:", err) + return + } + + fmt.Printf("IPv6 Addresses for %s:\n", domain) + for _, addr := range ipv6Addresses { + fmt.Println(addr) + } +} +*/ diff --git a/dnsLookupStatus.go b/dnsLookupStatus.go new file mode 100644 index 0000000..e623cae --- /dev/null +++ b/dnsLookupStatus.go @@ -0,0 +1,370 @@ +/* + 'dig' + + This is essentially doing what the command 'dig' does + It performing DNS queries on TCP and UDP + against localhost, cloudflare & google + + IPv4() and IPv6() return true if they are working + + with the 'gui' package, it can also display the results +*/ + +package main + +import ( + "log" + "fmt" + "time" + "strconv" + + "github.com/miekg/dns" + "go.wit.com/gui" + "go.wit.com/control-panel-dns/cloudflare" + "go.wit.com/shell" +) + +type digStatus struct { + ready bool + statusIPv4 string + statusIPv6 string + + parent *gui.Node + window *gui.Node + group *gui.Node + grid *gui.Node + box *gui.Node + + summary *gui.Node + status *cloudflare.OneLiner + statusAAAA *cloudflare.OneLiner + speed *cloudflare.OneLiner + speedActual *cloudflare.OneLiner + + details *gui.Node + dsLocalhost *dnsStatus + dsLocalNetwork *dnsStatus + dsCloudflare *dnsStatus + dsGoogle *dnsStatus + DnsDigUDP *gui.Node + DnsDigTCP *gui.Node +} + +type dnsStatus struct { + title string + server string // The DNS server. Example: "127.0.0.1:53" or "1.1.1.1:53" + hostname string // the hostname to lookup. Example: "www.google.com" or "go.wit.com" + + parent *gui.Node + group *gui.Node + grid *gui.Node + + // DNS setup options + udpA *gui.Node + tcpA *gui.Node + udpAAAA *gui.Node + tcpAAAA *gui.Node + + // show the display + aFail *gui.Node + aSuccess *gui.Node + aaaaFail *gui.Node + aaaaSuccess *gui.Node + + // interger counters + aFailc int + aSuccessc int + aaaaFailc int + aaaaSuccessc int +} + +func NewDigStatusWindow(p *gui.Node) *digStatus { + var ds *digStatus + ds = new(digStatus) + + ds.ready = false + + ds.window = p.NewWindow("DNS Lookup Status") + ds.box = ds.window.NewBox("hBox", true) + + // summary of the current state of things + ds.summary = ds.box.NewGroup("Summary") + + b := ds.summary.NewBox("hBox", true) + ds.status = cloudflare.NewOneLiner(b, "status") + ds.status.Set("unknown") + + b = ds.summary.NewBox("hBox", true) + ds.statusAAAA = cloudflare.NewOneLiner(b, "IPv6 status") + ds.statusAAAA.Set("unknown") + + b = ds.summary.NewBox("hBox", true) + ds.speed = cloudflare.NewOneLiner(b, "speed") + ds.speed.Set("unknown") + + b = ds.summary.NewBox("hBox", true) + ds.speedActual = cloudflare.NewOneLiner(b, "actual") + ds.speedActual.Set("unknown") + + // make the area to store the raw details + ds.details = ds.box.NewGroup("Details") + ds.dsLocalhost = NewDnsStatus(ds.details, "(localhost)", "127.0.0.1:53", "go.wit.com") + ds.dsLocalNetwork = NewDnsStatus(ds.details, "(Local Network)", "172.22.0.1:53", "go.wit.com") + ds.dsCloudflare = NewDnsStatus(ds.details, "(cloudflare)", "1.1.1.1:53", "go.wit.com") + ds.dsGoogle = NewDnsStatus(ds.details, "(google)", "8.8.8.8:53", "go.wit.com") + ds.makeDnsStatusGrid() + + return ds +} + +func (ds *digStatus) Update() { + duration := timeFunction(func () { ds.updateDnsStatus() }) + s := fmt.Sprint(duration) + ds.speedActual.Set(s) + + if (duration > 500 * time.Millisecond ) { + ds.speed.Set("SLOW") + } else if (duration > 100 * time.Millisecond ) { + ds.speed.Set("OK") + } else { + ds.speed.Set("FAST") + } +} + +// Returns true if the status is valid +func (ds *digStatus) Ready() bool { + return ds.ready +} + +// Returns true if IPv4 is working +func (ds *digStatus) IPv4() bool { + if (ds.statusIPv4 == "OK") { + return true + } + if (ds.statusIPv4 == "GOOD") { + return true + } + return false +} + +// Returns true if IPv6 is working +func (ds *digStatus) IPv6() bool { + if (ds.statusIPv6 == "GOOD") { + return true + } + return false +} + +func (ds *digStatus) setIPv4(s string) { + ds.status.Set(s) + ds.statusIPv4 = s +} + +func (ds *digStatus) setIPv6(s string) { + ds.statusAAAA.Set(s) + ds.statusIPv6 = s +} + +func (ds *digStatus) updateDnsStatus() { + var cmd, out string + var ipv4, ipv6 bool + + ipv4, ipv6 = ds.dsLocalhost.Update() + ipv4, ipv6 = ds.dsLocalNetwork.Update() + ipv4, ipv6 = ds.dsCloudflare.Update() + ipv4, ipv6 = ds.dsGoogle.Update() + + if (ipv4) { + log.Println("updateDnsStatus() IPv4 A lookups working") + ds.setIPv4("OK") + } else { + log.Println("updateDnsStatus() IPv4 A lookups not working. No internet?") + ds.setIPv4("No Internet?") + } + if (ipv6) { + log.Println("updateDnsStatus() IPv6 AAAA lookups working") + ds.setIPv4("GOOD") + ds.setIPv6("GOOD") + } else { + log.Println("updateDnsStatus() IPv6 AAAA lookups are not working") + ds.setIPv6("Need VPN") + } + + cmd = "dig +noall +answer www.wit.com A" + out = shell.Run(cmd) + log.Println("makeDnsStatusGrid() dig", out) + ds.DnsDigUDP.SetText(out) + + cmd = "dig +noall +answer www.wit.com AAAA" + out = shell.Run(cmd) + log.Println("makeDnsStatusGrid() dig", out) + ds.DnsDigTCP.SetText(out) + + ds.ready = true +} + +// Makes a DNS Status Grid +func NewDnsStatus(p *gui.Node, title string, server string, hostname string) *dnsStatus { + var ds *dnsStatus + ds = new(dnsStatus) + ds.parent = p + ds.group = p.NewGroup(server + " " + title + " lookup") + ds.grid = ds.group.NewGrid("LookupStatus", 5, 2) + + ds.server = server + ds.hostname = hostname + + ds.grid.NewLabel("") + ds.grid.NewLabel("UDP") + ds.grid.NewLabel("TCP") + ds.grid.NewLabel("Success") + ds.grid.NewLabel("Fail") + + ds.grid.NewLabel("A") + ds.udpA = ds.grid.NewLabel("?") + ds.tcpA = ds.grid.NewLabel("?") + ds.aSuccess = ds.grid.NewLabel("?") + ds.aFail = ds.grid.NewLabel("?") + + ds.grid.NewLabel("AAAA") + ds.udpAAAA = ds.grid.NewLabel("?") + ds.tcpAAAA = ds.grid.NewLabel("?") + ds.aaaaSuccess = ds.grid.NewLabel("?") + ds.aaaaFail = ds.grid.NewLabel("?") + + ds.group.Margin() + ds.grid.Margin() + ds.group.Pad() + ds.grid.Pad() + + return ds +} + +// special thanks to the Element Hotel wifi in Philidelphia that allowed me to +// easily debug this code since the internet connection here blocks port 53 traffic +func (ds *dnsStatus) Update() (bool, bool) { + var results []string + var a bool = false + var aaaa bool = false + + log.Println("dnsStatus.Update() For server", ds.server, "on", ds.hostname) + results, _ = dnsUdpLookup(ds.server, ds.hostname, dns.TypeA) + log.Println("dnsStatus.Update() UDP type A =", results) + + if (len(results) == 0) { + ds.udpA.SetText("BROKEN") + ds.aFailc += 1 + } else { + ds.udpA.SetText("WORKING") + ds.aSuccessc += 1 + a = true + } + + results, _ = dnsTcpLookup(ds.server, ds.hostname, dns.TypeA) + log.Println("dnsStatus.Update() TCP type A =", results) + + if (len(results) == 0) { + ds.tcpA.SetText("BROKEN") + ds.aFailc += 1 + } else { + ds.tcpA.SetText("WORKING") + ds.aSuccessc += 1 + a = true + } + + ds.aFail.SetText(strconv.Itoa(ds.aFailc)) + ds.aSuccess.SetText(strconv.Itoa(ds.aSuccessc)) + + results, _ = dnsUdpLookup(ds.server, ds.hostname, dns.TypeAAAA) + log.Println("dnsStatus.Update() UDP type AAAA =", results) + + if (len(results) == 0) { + ds.udpAAAA.SetText("BROKEN") + ds.aaaaFailc += 1 + ds.aaaaFail.SetText(strconv.Itoa(ds.aaaaFailc)) + } else { + ds.udpAAAA.SetText("WORKING") + ds.aaaaSuccessc += 1 + aaaa = true + } + + results, _ = dnsTcpLookup(ds.server, ds.hostname, dns.TypeAAAA) + log.Println("dnsStatus.Update() UDP type AAAA =", results) + + if (len(results) == 0) { + ds.tcpAAAA.SetText("BROKEN") + ds.aaaaFailc += 1 + ds.aaaaFail.SetText(strconv.Itoa(ds.aaaaFailc)) + } else { + ds.tcpAAAA.SetText("WORKING") + ds.aaaaSuccessc += 1 + aaaa = true + } + + ds.aaaaFail.SetText(strconv.Itoa(ds.aaaaFailc)) + ds.aaaaSuccess.SetText(strconv.Itoa(ds.aaaaSuccessc)) + + return a, aaaa +} + +func (ds *digStatus) makeDnsStatusGrid() { + var cmd, out string + group := ds.details.NewGroup("dig results") + grid := group.NewGrid("LookupStatus", 2, 2) + + cmd = "dig +noall +answer go.wit.com A" + grid.NewLabel(cmd) + ds.DnsDigUDP = grid.NewLabel("?") + out = shell.Run(cmd) + log.Println("makeDnsStatusGrid() dig", out) + ds.DnsDigUDP.SetText(out) + + cmd = "dig +noall +answer go.wit.com AAAA" + grid.NewLabel(cmd) + ds.DnsDigTCP = grid.NewLabel("?") + out = shell.Run(cmd) + log.Println("makeDnsStatusGrid() dig", out) + ds.DnsDigTCP.SetText(out) + + group.Pad() + grid.Pad() +} + +// dnsLookup performs a DNS lookup for the specified record type (e.g., "TXT", "AAAA") for a given domain. +func dnsUdpLookup(server string, domain string, recordType uint16) ([]string, error) { + var records []string + + c := new(dns.Client) + m := new(dns.Msg) + m.SetQuestion(dns.Fqdn(domain), recordType) + r, _, err := c.Exchange(m, server) // If server = "1.1.1.1:53" then use Cloudflare's DNS server + if err != nil { + return nil, err + } + + for _, ans := range r.Answer { + records = append(records, ans.String()) + } + + return records, nil +} + +func dnsTcpLookup(server string, domain string, recordType uint16) ([]string, error) { + var records []string + + c := new(dns.Client) + c.Net = "tcp" // Specify to use TCP for the query + c.Timeout = time.Second * 5 // Set a 5-second timeout + m := new(dns.Msg) + m.SetQuestion(dns.Fqdn(domain), recordType) + r, _, err := c.Exchange(m, server) // If server = "1.1.1.1:53" then use Cloudflare's DNS server + if err != nil { + return nil, err + } + + for _, ans := range r.Answer { + records = append(records, ans.String()) + } + + return records, nil +} diff --git a/examples/cloudflare/Makefile b/examples/cloudflare/Makefile deleted file mode 100644 index 32f3f55..0000000 --- a/examples/cloudflare/Makefile +++ /dev/null @@ -1,24 +0,0 @@ -run: build - ./cloudflare - -build-release: - go get -v -u -x . - go build - ./cloudflare - -build: - GO111MODULE="off" go get -v -x . - GO111MODULE="off" go build - -update: - GO111MODULE="off" go get -v -u -x . - -log: - reset - tail -f /tmp/witgui.* /tmp/guilogfile - -gocui: build - ./cloudflare -gui gocui >/tmp/witgui.log.stderr 2>&1 - -quiet: - ./cloudflare >/tmp/witgui.log.stderr 2>&1 diff --git a/examples/cloudflare/argv.go b/examples/cloudflare/argv.go deleted file mode 100644 index 49167cd..0000000 --- a/examples/cloudflare/argv.go +++ /dev/null @@ -1,30 +0,0 @@ -// This creates a simple hello world window -package main - -import ( - "fmt" - arg "github.com/alexflint/go-arg" - "go.wit.com/gui" - log "go.wit.com/gui/log" -) - - -var args struct { - Foo string - Bar bool - User string `arg:"env:USER"` - Demo bool `help:"run a demo"` - gui.GuiArgs - log.LogArgs -} - -func init() { - arg.MustParse(&args) - fmt.Println(args.Foo, args.Bar, args.User) - - if (args.Gui != "") { - gui.GuiArg.Gui = args.Gui - } - log.Log(true, "INIT() args.GuiArg.Gui =", gui.GuiArg.Gui) - -} diff --git a/examples/cloudflare/config.go b/examples/cloudflare/config.go deleted file mode 100644 index a65ad25..0000000 --- a/examples/cloudflare/config.go +++ /dev/null @@ -1,71 +0,0 @@ -// This is a simple example -package main - -import ( - "os" - "log" - "bufio" - "strings" - - "go.wit.com/control-panel-dns/cloudflare" -) - -func saveConfig() { - log.Println("TODO") -} - -func readConfig() { - homeDir, err := os.UserHomeDir() - if err != nil { - log.Println("searchPaths() error. exiting here?") - } - filename := homeDir + "/" + configfile - log.Println("filename =", filename) - - readFileLineByLine(filename) - // os.Exit(0) -} - -// readFileLineByLine opens a file and reads through each line. -func readFileLineByLine(filename string) error { - // Open the file. - file, err := os.Open(filename) - if err != nil { - return err - } - defer file.Close() - - log.Println("readFileLineByLine() =", filename) - - // Create a new Scanner for the file. - scanner := bufio.NewScanner(file) - - // Read through each line using scanner. - for scanner.Scan() { - var newc *cloudflare.ConfigT - newc = new(cloudflare.ConfigT) - - line := scanner.Text() - parts := strings.Fields(line) - - if (len(parts) < 4) { - log.Println("readFileLineByLine() SKIP =", parts) - continue - } - - newc.Domain = parts[0] - newc.ZoneID = parts[1] - newc.Auth = parts[2] - newc.Email = parts[3] - - cloudflare.Config[parts[0]] = newc - log.Println("readFileLineByLine() =", newc.Domain, newc.ZoneID, newc.Auth, newc.Email) - } - - // Check for errors during Scan. - if err := scanner.Err(); err != nil { - return err - } - - return nil -} diff --git a/examples/cloudflare/main.go b/examples/cloudflare/main.go deleted file mode 100644 index 2308478..0000000 --- a/examples/cloudflare/main.go +++ /dev/null @@ -1,52 +0,0 @@ -// This is a simple example -package main - -import ( - "go.wit.com/gui" - "go.wit.com/control-panel-dns/cloudflare" -) - -var title string = "Cloudflare DNS Control Panel" -var outfile string = "/tmp/guilogfile" -var configfile string = ".config/wit/cloudflare" - -var myGui *gui.Node - -// var buttonCounter int = 5 -// var gridW int = 5 -// var gridH int = 3 - -// var mainWindow, more, more2 *gui.Node - -// var cloudflareURL string = "https://api.cloudflare.com/client/v4/zones/" - -/* -var zonedrop *gui.Node -var domainWidget *gui.Node -var masterSave *gui.Node - -var zoneWidget *gui.Node -var authWidget *gui.Node -var emailWidget *gui.Node - -var loadButton *gui.Node -var saveButton *gui.Node -*/ - -func main() { - // parse the config file - readConfig() - - // initialize a new GO GUI instance - myGui = gui.New().Default() - - // draw the cloudflare control panel window - cloudflare.MakeCloudflareWindow(myGui) - - // This is just a optional goroutine to watch that things are alive - gui.Watchdog() - gui.StandardExit() - - // update the config file - saveConfig() -} diff --git a/examples/control-panel-cloudflare/Makefile b/examples/control-panel-cloudflare/Makefile new file mode 100644 index 0000000..07d8f77 --- /dev/null +++ b/examples/control-panel-cloudflare/Makefile @@ -0,0 +1,24 @@ +run: build + ./control-panel-cloudflare + +build-release: + go get -v -u -x . + go build + ./control-panel-cloudflare + +build: + GO111MODULE="off" go get -v -x . + GO111MODULE="off" go build + +update: + GO111MODULE="off" go get -v -u -x . + +log: + reset + tail -f /tmp/witgui.* /tmp/guilogfile + +gocui: build + ./control-panel-cloudflare -gui gocui >/tmp/witgui.log.stderr 2>&1 + +quiet: + ./control-panel-cloudflare >/tmp/witgui.log.stderr 2>&1 diff --git a/examples/control-panel-cloudflare/argv.go b/examples/control-panel-cloudflare/argv.go new file mode 100644 index 0000000..49167cd --- /dev/null +++ b/examples/control-panel-cloudflare/argv.go @@ -0,0 +1,30 @@ +// This creates a simple hello world window +package main + +import ( + "fmt" + arg "github.com/alexflint/go-arg" + "go.wit.com/gui" + log "go.wit.com/gui/log" +) + + +var args struct { + Foo string + Bar bool + User string `arg:"env:USER"` + Demo bool `help:"run a demo"` + gui.GuiArgs + log.LogArgs +} + +func init() { + arg.MustParse(&args) + fmt.Println(args.Foo, args.Bar, args.User) + + if (args.Gui != "") { + gui.GuiArg.Gui = args.Gui + } + log.Log(true, "INIT() args.GuiArg.Gui =", gui.GuiArg.Gui) + +} diff --git a/examples/control-panel-cloudflare/config.go b/examples/control-panel-cloudflare/config.go new file mode 100644 index 0000000..a65ad25 --- /dev/null +++ b/examples/control-panel-cloudflare/config.go @@ -0,0 +1,71 @@ +// This is a simple example +package main + +import ( + "os" + "log" + "bufio" + "strings" + + "go.wit.com/control-panel-dns/cloudflare" +) + +func saveConfig() { + log.Println("TODO") +} + +func readConfig() { + homeDir, err := os.UserHomeDir() + if err != nil { + log.Println("searchPaths() error. exiting here?") + } + filename := homeDir + "/" + configfile + log.Println("filename =", filename) + + readFileLineByLine(filename) + // os.Exit(0) +} + +// readFileLineByLine opens a file and reads through each line. +func readFileLineByLine(filename string) error { + // Open the file. + file, err := os.Open(filename) + if err != nil { + return err + } + defer file.Close() + + log.Println("readFileLineByLine() =", filename) + + // Create a new Scanner for the file. + scanner := bufio.NewScanner(file) + + // Read through each line using scanner. + for scanner.Scan() { + var newc *cloudflare.ConfigT + newc = new(cloudflare.ConfigT) + + line := scanner.Text() + parts := strings.Fields(line) + + if (len(parts) < 4) { + log.Println("readFileLineByLine() SKIP =", parts) + continue + } + + newc.Domain = parts[0] + newc.ZoneID = parts[1] + newc.Auth = parts[2] + newc.Email = parts[3] + + cloudflare.Config[parts[0]] = newc + log.Println("readFileLineByLine() =", newc.Domain, newc.ZoneID, newc.Auth, newc.Email) + } + + // Check for errors during Scan. + if err := scanner.Err(); err != nil { + return err + } + + return nil +} diff --git a/examples/control-panel-cloudflare/main.go b/examples/control-panel-cloudflare/main.go new file mode 100644 index 0000000..0661a93 --- /dev/null +++ b/examples/control-panel-cloudflare/main.go @@ -0,0 +1,40 @@ +// This is a simple example +package main + +import ( + "go.wit.com/gui" + "go.wit.com/control-panel-dns/cloudflare" +) + +var title string = "Cloudflare DNS Control Panel" +var outfile string = "/tmp/guilogfile" +var configfile string = ".config/wit/cloudflare" + +var myGui *gui.Node + +// var buttonCounter int = 5 +// var gridW int = 5 +// var gridH int = 3 + +// var mainWindow, more, more2 *gui.Node + +// var cloudflareURL string = "https://api.cloudflare.com/client/v4/zones/" + +func main() { + // parse the config file + readConfig() + + // initialize a new GO GUI instance + myGui = gui.New().Default() + + // draw the cloudflare control panel window + win := cloudflare.MakeCloudflareWindow(myGui) + win.SetText(title) + + // This is just a optional goroutine to watch that things are alive + gui.Watchdog() + gui.StandardExit() + + // update the config file + saveConfig() +} diff --git a/go.mod b/go.mod index f21c131..cde5d25 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,42 @@ module go.wit.com/control-panel-dns go 1.21.4 + +require ( + github.com/Showmax/go-fqdn v1.0.0 + github.com/alexflint/go-arg v1.4.3 + github.com/creack/pty v1.1.21 + github.com/davecgh/go-spew v1.1.1 + github.com/fsnotify/fsnotify v1.7.0 + github.com/golang/protobuf v1.5.3 + github.com/jsimonetti/rtnetlink v1.4.0 + github.com/miekg/dns v1.1.57 + github.com/rs/dnstrace v1.4.1 + go.wit.com/gui v0.9.2 + go.wit.com/shell v0.0.0-20231220210920-25715e30ee82 + golang.org/x/term v0.15.0 +) + +require ( + github.com/alexflint/go-scalar v1.1.0 // indirect + github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/josharian/native v1.1.0 // indirect + github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect + github.com/mdlayher/netlink v1.7.2 // indirect + github.com/mdlayher/socket v0.4.1 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/svent/go-nbreader v0.0.0-20150201200112-7cef48da76dc // indirect + github.com/tmc/scp v0.0.0-20170824174625-f7b48647feef // indirect + github.com/wercker/journalhook v0.0.0-20230927020745-64542ffa4117 // indirect + go.uber.org/atomic v1.7.0 // indirect + go.uber.org/multierr v1.9.0 // indirect + golang.org/x/crypto v0.17.0 // indirect + golang.org/x/mod v0.12.0 // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/sync v0.4.0 // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/tools v0.13.0 // indirect + google.golang.org/protobuf v1.26.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..1162892 --- /dev/null +++ b/go.sum @@ -0,0 +1,88 @@ +github.com/Showmax/go-fqdn v1.0.0 h1:0rG5IbmVliNT5O19Mfuvna9LL7zlHyRfsSvBPZmF9tM= +github.com/Showmax/go-fqdn v1.0.0/go.mod h1:SfrFBzmDCtCGrnHhoDjuvFnKsWjEQX/Q9ARZvOrJAko= +github.com/alexflint/go-arg v1.4.3 h1:9rwwEBpMXfKQKceuZfYcwuc/7YY7tWJbFsgG5cAU/uo= +github.com/alexflint/go-arg v1.4.3/go.mod h1:3PZ/wp/8HuqRZMUUgu7I+e1qcpUbvmS258mRXkFH4IA= +github.com/alexflint/go-scalar v1.1.0 h1:aaAouLLzI9TChcPXotr6gUhq+Scr8rl0P9P4PnltbhM= +github.com/alexflint/go-scalar v1.1.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o= +github.com/cilium/ebpf v0.12.3 h1:8ht6F9MquybnY97at+VDZb3eQQr8ev79RueWeVaEcG4= +github.com/cilium/ebpf v0.12.3/go.mod h1:TctK1ivibvI3znr66ljgi4hqOT8EYQjz1KWBfb1UVgM= +github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU= +github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0= +github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= +github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= +github.com/jsimonetti/rtnetlink v1.4.0 h1:Z1BF0fRgcETPEa0Kt0MRk3yV5+kF1FWTni6KUFKrq2I= +github.com/jsimonetti/rtnetlink v1.4.0/go.mod h1:5W1jDvWdnthFJ7fxYX1GMK07BUpI4oskfOqvPteYS6E= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= +github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= +github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= +github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= +github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM= +github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rs/dnstrace v1.4.1 h1:o6W+8hO9kGcdq9FZAVudpCyw6WXeD9XXamXyYiei/Hs= +github.com/rs/dnstrace v1.4.1/go.mod h1:pFQiHK1kt94r2csi+qAxUsJ58r74QbN2q4JCDuFYTeY= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/svent/go-nbreader v0.0.0-20150201200112-7cef48da76dc h1:usYkrH2/es/TT7ETdC/qLAagcJPW3EEYFKqvibSnFbA= +github.com/svent/go-nbreader v0.0.0-20150201200112-7cef48da76dc/go.mod h1:pPzZl0vMkUhyoxUF8PAGG5bDRGo7PY80oO/PMmpLkkc= +github.com/tmc/scp v0.0.0-20170824174625-f7b48647feef h1:7D6Nm4D6f0ci9yttWaKjM1TMAXrH5Su72dojqYGntFY= +github.com/tmc/scp v0.0.0-20170824174625-f7b48647feef/go.mod h1:WLFStEdnJXpjK8kd4qKLwQKX/1vrDzp5BcDyiZJBHJM= +github.com/wercker/journalhook v0.0.0-20230927020745-64542ffa4117 h1:67A5tweHp3C7osHjrYsy6pQZ00bYkTTttZ7kiOwwHeA= +github.com/wercker/journalhook v0.0.0-20230927020745-64542ffa4117/go.mod h1:XCsSkdKK4gwBMNrOCZWww0pX6AOt+2gYc5Z6jBRrNVg= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= +go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= +go.wit.com/gui v0.9.2 h1:QHMYdwpV6MzKwmFUMGevKUDn2a6GAqHN2Ltx8V3HufI= +go.wit.com/gui v0.9.2/go.mod h1:asRXEYKmdjhtg1yiBi5A8YEY2YG4lWPS0gvNz4NXGDE= +go.wit.com/shell v0.0.0-20231220210920-25715e30ee82 h1:5YVB3Y2PHtH+oE8Y1SzWHIKnJ1SPQa09xNHEw9yxmAI= +go.wit.com/shell v0.0.0-20231220210920-25715e30ee82/go.mod h1:57dOTStlN5aao4EOCZFC+D47rF7In6qDZCjQoobJWcA= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 h1:Jvc7gsqn21cJHCmAWx0LiimpP18LZmUxkT5Mp7EZ1mI= +golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= +golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/gui.go b/gui.go index 1688a9c..df00cf2 100644 --- a/gui.go +++ b/gui.go @@ -14,8 +14,6 @@ import ( "go.wit.com/gui" "go.wit.com/shell" "go.wit.com/control-panel-dns/cloudflare" - - "github.com/davecgh/go-spew/spew" ) // This setups up the dns control panel window @@ -25,9 +23,13 @@ func setupControlPanelWindow() { debug("artificial sleep of:", me.artificialSleep) sleep(me.artificialSleep) + + // setup the main tab dnsTab("DNS") detailsTab("Details") debugTab("Debug") + + me.digStatus = NewDigStatusWindow(me.window) } func detailsTab(title string) { @@ -59,6 +61,9 @@ func detailsTab(title string) { grid.NewLabel("Current IPv6 =") me.IPv6 = grid.NewLabel("?") + grid.NewLabel("Working Real IPv6 =") + me.workingIPv6 = grid.NewLabel("?") + grid.NewLabel("interfaces =") me.Interfaces = grid.NewCombobox("Interfaces") @@ -122,7 +127,6 @@ func debugTab(title string) { g2.NewButton("os.User()", func () { user, _ := user.Current() - spew.Dump(user) log.Println("os.Getuid =", user.Username, os.Getuid()) if (me.uid != nil) { me.uid.SetText(user.Username + " (" + strconv.Itoa(os.Getuid()) + ")") @@ -132,7 +136,6 @@ func debugTab(title string) { g2.NewButton("dig +trace", func () { o := shell.Run("dig +trace +noadditional DS " + me.hostname + " @8.8.8.8") log.Println(o) - // log.Println(o) }) g2.NewButton("Example_listLink()", func () { @@ -186,30 +189,54 @@ func debugTab(title string) { g2.Pad() } +// will return a AAAA value that needs to be deleted +func deleteAAA() string { + var aaaa []string + aaaa = dhcpAAAA() // your AAAA IP addresses right now + for _, s := range aaaa { + debug(LogNow, "DNS AAAA =", s) + if ( me.ipmap[s] == nil) { + return s + } + } + return "" +} + +// will return a AAAA value that needs to be added +func missingAAAA() string { + var aaaa []string + aaaa = dhcpAAAA() // your AAAA IP addresses right now + for _, s := range aaaa { + debug(LogNow, "missing AAAA =", s) + return s + } + return "" +} + // doesn't actually do any network traffic // it just updates the GUI -func displayDNS() int { +func displayDNS() string { var aaaa []string - aaaa = realAAAA() // your AAAA records right now + aaaa = dhcpAAAA() // your AAAA records right now h := me.hostname var all string - var broken int = 0 + var broken string = "unknown" for _, s := range aaaa { debug(LogNow, "host", h, "DNS AAAA =", s, "ipmap[s] =", me.ipmap[s]) all += s + "\n" if ( me.ipmap[s] == nil) { debug(LogError, "THIS IS THE WRONG AAAA DNS ENTRY: host", h, "DNS AAAA =", s) - broken = 2 + broken = "wrong AAAA entry" } else { - if (broken == 0) { - broken = 1 + if (broken == "unknown") { + broken = "needs update" } } } all = sortLines(all) - if (me.DnsAAAA.S != all) { - debug(LogError, "DnsAAAA.SetText() to:", all) - me.DnsAAAA.SetText(all) + if (me.workingIPv6.S != all) { + debug(LogError, "workingIPv6.SetText() to:", all) + me.workingIPv6.SetText(all) } var a []string @@ -296,6 +323,9 @@ func statusGrid(n *gui.Node) { gridP.NewLabel("DNS Status =") me.DnsStatus = gridP.NewLabel("unknown") + me.statusIPv6 = cloudflare.NewOneLiner(gridP, "IPv6 working") + me.statusIPv6.Set("known") + gridP.NewLabel("hostname =") me.hostnameStatus = gridP.NewLabel("invalid") @@ -315,9 +345,6 @@ func statusGrid(n *gui.Node) { ng := n.NewGroup("TODO:") gridP = ng.NewGrid("nut2", 2, 2) - gridP.NewLabel("IPv6 working =") - gridP.NewLabel("unknown") - gridP.NewLabel("ping.wit.com =") gridP.NewLabel("unknown") @@ -337,26 +364,63 @@ func updateDNS() { if (h == "") { h = "test.wit.com" } + + me.digStatus.Update() + // log.Println("digAAAA()") aaaa = digAAAA(h) debug(LogNow, "digAAAA() =", aaaa) + // log.Println(SPEW, me) if (aaaa == nil) { debug(LogError, "There are no DNS AAAA records for hostname: ", h) + me.DnsAAAA.SetText("(none)") + if (cloudflare.CFdialog.TypeNode != nil) { + cloudflare.CFdialog.TypeNode.SetText("AAAA new") + } + + if (cloudflare.CFdialog.NameNode != nil) { + cloudflare.CFdialog.NameNode.SetText(me.hostname) + } + + d := deleteAAA() + if (d != "") { + if (cloudflare.CFdialog.ValueNode != nil) { + cloudflare.CFdialog.ValueNode.SetText(d) + } + } + m := missingAAAA() + if (m != "") { + if (cloudflare.CFdialog.ValueNode != nil) { + cloudflare.CFdialog.ValueNode.SetText(m) + } + /* + rr := &cloudflare.RRT{ + Type: "AAAA", + Name: me.hostname, + Ttl: "Auto", + Proxied: false, + Content: m, + } + cloudflare.Update(rr) + */ + } } - broken := displayDNS() // update the GUI based on dig results - - if (broken == 1) { - me.DnsStatus.SetText("PARTLY WORKING") - } else if (broken == 2) { - me.DnsStatus.SetText("WORKING") - } else { - me.DnsStatus.SetText("BROKEN") - me.fix.Enable() + status := displayDNS() // update the GUI based on dig results + me.DnsStatus.SetText(status) + + if me.digStatus.Ready() { + if me.digStatus.IPv6() { + me.statusIPv6.Set("IPv6 WORKING") + } else { + me.statusIPv6.Set("Need VPN") + } } + + // me.fix.Enable() + user, _ := user.Current() - spew.Dump(user) log.Println("os.Getuid =", user.Username, os.Getuid()) if (me.uid != nil) { me.uid.SetText(user.Username + " (" + strconv.Itoa(os.Getuid()) + ")") diff --git a/hostname.go b/hostname.go index a0da280..7b7132a 100644 --- a/hostname.go +++ b/hostname.go @@ -6,17 +6,22 @@ package main import ( + "log" +// "net" + "strings" + "go.wit.com/shell" "go.wit.com/control-panel-dns/cloudflare" - "go.wit.com/control-panel-dns/dnssecsocket" + + "github.com/miekg/dns" ) // will try to get this hosts FQDN import "github.com/Showmax/go-fqdn" // this is the king of dns libraries -import "github.com/miekg/dns" +// import "github.com/miekg/dns" func getHostname() { @@ -98,6 +103,7 @@ func goodHostname(h string) bool { return false } +/* func digAAAA(s string) []string { var aaaa []string // lookup the IP address from DNS @@ -114,5 +120,52 @@ func digAAAA(s string) []string { me.ipv6s[ipaddr] = rr } debug(true, args.VerboseDNS, "aaaa =", aaaa) + log.Println("digAAAA() returned =", aaaa) + log.Println("digAAAA() me.ipv6s =", me.ipv6s) + os.Exit(0) return aaaa } +*/ + +func digAAAA(hostname string) []string { + var blah, ipv6Addresses []string + // domain := hostname + recordType := dns.TypeAAAA // dns.TypeTXT + + // Cloudflare's DNS server + blah, _ = dnsUdpLookup("1.1.1.1:53", hostname, recordType) + log.Println("digAAAA() has BLAH =", blah) + + if (len(blah) == 0) { + log.Println("digAAAA() RUNNING dnsLookupDoH(domain)") + ipv6Addresses, _ = dnsLookupDoH(hostname) + log.Println("digAAAA() has ipv6Addresses =", strings.Join(ipv6Addresses, " ")) + log.Printf("digAAAA() IPv6 Addresses for %s:\n", hostname) + for _, addr := range ipv6Addresses { + log.Println(addr) + } + return ipv6Addresses + } + + // TODO: check digDoH vs blah, if so, then port 53 TCP and/or UDP is broken or blocked + log.Println("digAAAA() has BLAH =", blah) + + return blah +} + +/* +func dnsHttpsLookup(domain string, recordType uint16) ([]string, error) { + domain := "google.com" + dnsLookupDoH(domain string) ([]string, error) { + ipv6Addresses, err := dnsLookupDoH(domain) + if err != nil { + fmt.Println("Error:", err) + return + } + + fmt.Printf("IPv6 Addresses for %s:\n", domain) + for _, addr := range ipv6Addresses { + fmt.Println(addr) + } +} +*/ diff --git a/main.go b/main.go index 36be8cd..ec859d1 100644 --- a/main.go +++ b/main.go @@ -59,6 +59,27 @@ func main() { /* Poll for changes to the networking settings */ + +/* https://github.com/robfig/cron/blob/master/cron.go + +// Run the cron scheduler, or no-op if already running. +func (c *Cron) Run() { + c.runningMu.Lock() + if c.running { + c.runningMu.Unlock() + return + } + c.running = true + c.runningMu.Unlock() + c.run() +} + +// run the scheduler.. this is private just due to the need to synchronize +// access to the 'running' state variable. +func (c *Cron) run() { + c.logger.Info("start") +*/ + func checkNetworkChanges() { var lastLocal time.Time = time.Now() var lastDNS time.Time = time.Now() @@ -119,12 +140,12 @@ func DNSloop() { } else if (duration > 100 * time.Millisecond ) { newSpeed = "OK" if (me.fixProc != nil) { - me.fixProc.Disable() + // me.fixProc.Disable() } } else { newSpeed = "FAST" if (me.fixProc != nil) { - me.fixProc.Disable() + // me.fixProc.Disable() } } if (newSpeed != me.DnsSpeedLast) { @@ -156,7 +177,7 @@ func linuxLoop() { } var aaaa []string - aaaa = realAAAA() + aaaa = dhcpAAAA() var all string for _, s := range aaaa { debug(LogNet, "my actual AAAA = ",s) diff --git a/net.go b/net.go index 487486e..10b909a 100644 --- a/net.go +++ b/net.go @@ -97,7 +97,11 @@ func checkInterface(i net.Interface) { } } -func realAAAA() []string { +/* + These are the real IP address you have been + given from DHCP +*/ +func dhcpAAAA() []string { var aaaa []string for s, t := range me.ipmap { diff --git a/structs.go b/structs.go index 3d1ba4a..36d84c3 100644 --- a/structs.go +++ b/structs.go @@ -57,6 +57,7 @@ type Host struct { NSrr *gui.Node // NS resource records for the domain name DnsAPI *gui.Node // what DNS API to use? DnsAAAA *gui.Node // the actual DNS AAAA results + workingIPv6 *gui.Node // currently working AAAA DnsA *gui.Node // the actual DNS A results (ignore for status since mostly never happens?) DnsStatus *gui.Node // the current state of DNS DnsSpeed *gui.Node // 'FAST', 'OK', 'SLOW', etc @@ -72,6 +73,9 @@ type Host struct { dbOn *gui.Node // button for setting debugging on dbNet *gui.Node // button for setting network debugging on dbProc *gui.Node // button for setting proc debugging on + + digStatus *digStatus + statusIPv6 *cloudflare.OneLiner } type IPtype struct { -- cgit v1.2.3