Browse Source

Make closest option work again

Ask Bjørn Hansen 7 years ago
parent
commit
d10d3b22d8
3 changed files with 143 additions and 27 deletions
  1. 26 8
      dns/test.example.com.json
  2. 32 19
      zones/zone.go
  3. 85 0
      zones/zones_closest_test.go

+ 26 - 8
dns/test.example.com.json

@@ -6,14 +6,6 @@
   "targeting": "country continent @ regiongroup region ip asn",
   "targeting": "country continent @ regiongroup region ip asn",
   "contact": "support.bitnames.com",
   "contact": "support.bitnames.com",
   "data": {
   "data": {
-    "0": {
-      "a": [
-        [
-          "192.168.0.1",
-          10
-        ]
-      ]
-    },
     "": {
     "": {
       "ns": {
       "ns": {
         "ns1.example.net.": null,
         "ns1.example.net.": null,
@@ -162,6 +154,14 @@
         ]
         ]
       ]
       ]
     },
     },
+    "0": {
+      "a": [
+        [
+          "192.168.0.1",
+          10
+        ]
+      ]
+    },
     "0-alias": {
     "0-alias": {
       "alias": "0"
       "alias": "0"
     },
     },
@@ -199,6 +199,24 @@
     },
     },
     "cname-internal-referal": {
     "cname-internal-referal": {
       "cname": "bar"
       "cname": "bar"
+    },
+    "closest": {
+      "a": [
+        [
+          "194.106.223.155",
+          100
+        ],
+        [
+          "207.171.7.49",
+          100
+        ],
+        [
+          "207.171.7.59",
+          100
+        ]
+      ],
+      "max_hosts": "1",
+      "closest": true
     }
     }
   }
   }
 }
 }

+ 32 - 19
zones/zone.go

@@ -3,6 +3,7 @@ package zones
 import (
 import (
 	"encoding/json"
 	"encoding/json"
 	"log"
 	"log"
+	"net"
 	"strconv"
 	"strconv"
 	"strings"
 	"strings"
 	"sync"
 	"sync"
@@ -290,25 +291,37 @@ func (z *Zone) FindLabels(s string, targets []string, qts []uint16) []LabelMatch
 // Find the locations of all the A records within a zone. If we were being really clever
 // Find the locations of all the A records within a zone. If we were being really clever
 // here we could use LOC records too. But for the time being we'll just use GeoIP
 // here we could use LOC records too. But for the time being we'll just use GeoIP
 func (z *Zone) SetLocations() {
 func (z *Zone) SetLocations() {
-	log.Println("todo: SetLocations()")
-	// qtypes := []uint16{dns.TypeA}
-	// for _, label := range z.Labels {
-	// 	if label.Closest {
-	// 		for _, qtype := range qtypes {
-	// 			if label.Records[qtype] != nil && len(label.Records[qtype]) > 0 {
-	// 				for i := range label.Records[qtype] {
-	// 					label.Records[qtype][i].Loc = nil
-	// 					rr := label.Records[qtype][i].RR
-	// 					if a, ok := rr.(*dns.A); ok {
-	// 						ip := a.A
-	// 						_, _, _, _, _, location := targeting.GeoIP().GetCountryRegion(ip)
-	// 						label.Records[qtype][i].Loc = location
-	// 					}
-	// 				}
-	// 			}
-	// 		}
-	// 	}
-	// }
+	geo := targeting.Geo()
+	qtypes := []uint16{dns.TypeA}
+	for _, label := range z.Labels {
+		if label.Closest {
+			for _, qtype := range qtypes {
+				if label.Records[qtype] != nil && len(label.Records[qtype]) > 0 {
+					for i := range label.Records[qtype] {
+						label.Records[qtype][i].Loc = nil
+						rr := label.Records[qtype][i].RR
+						var ip *net.IP
+						switch rr.(type) {
+						case *dns.A:
+							ip = &rr.(*dns.A).A
+						case *dns.AAAA:
+							ip = &rr.(*dns.AAAA).AAAA
+						default:
+							log.Printf("Can't lookup location of type %T", rr)
+						}
+						if ip != nil {
+							location, err := geo.GetLocation(*ip)
+							if err != nil {
+								// log.Printf("Could not get location for '%s': %s", ip.String(), err)
+								continue
+							}
+							label.Records[qtype][i].Loc = location
+						}
+					}
+				}
+			}
+		}
+	}
 }
 }
 
 
 func (z *Zone) addHealthReference(l *Label, data interface{}) {
 func (z *Zone) addHealthReference(l *Label, data interface{}) {

+ 85 - 0
zones/zones_closest_test.go

@@ -0,0 +1,85 @@
+package zones
+
+import (
+	"net"
+	"reflect"
+	"sort"
+	"testing"
+
+	"github.com/miekg/dns"
+)
+
+func TestClosest(t *testing.T) {
+	muxm := loadZones(t)
+	t.Log("test closests")
+
+	tests := []struct {
+		Label     string
+		ClientIP  string
+		ExpectedA []string
+		MaxHosts  int
+	}{
+		{"closest", "212.237.144.84", []string{"194.106.223.155"}, 1},
+		{"closest", "208.113.157.108", []string{"207.171.7.49", "207.171.7.59"}, 2},
+		// {"closest", "208.113.157.108", []string{"207.171.7.59"}, 1},
+	}
+
+	for _, x := range tests {
+
+		ip := net.ParseIP(x.ClientIP)
+		if ip == nil {
+			t.Fatalf("Invalid ClientIP: %s", x.ClientIP)
+		}
+
+		tz := muxm.zonelist["test.example.com"]
+		targets, netmask, location := tz.Options.Targeting.GetTargets(ip, true)
+
+		t.Logf("targets: %q, netmask: %d, location: %+v", targets, netmask, location)
+
+		// This is a weird API, but it's what serve() uses now. Fixing it
+		// isn't super straight forward. Moving some of the exceptions from serve()
+		// into configuration and making the "find the best answer" code have
+		// a better API should be possible though. Some day.
+		labelMatches := tz.FindLabels(x.Label, targets, []uint16{dns.TypeMF, dns.TypeCNAME, dns.TypeA})
+
+		if len(labelMatches) == 0 {
+			t.Fatalf("no labelmatches")
+		}
+
+		for _, match := range labelMatches {
+			label := match.Label
+			labelQtype := match.Type
+
+			records := tz.Picker(label, labelQtype, x.MaxHosts, location)
+			if records == nil {
+				t.Fatalf("didn't get closest records")
+			}
+
+			if len(x.ExpectedA) == 0 {
+				if len(records) > 0 {
+					t.Logf("Expected 0 records but got %d", len(records))
+					t.Fail()
+				}
+			}
+			ips := []string{}
+
+			for _, r := range records {
+
+				switch rr := r.RR.(type) {
+				case *dns.A:
+					ips = append(ips, rr.A.String())
+				default:
+					t.Fatalf("unexpected RR type: %s", rr.Header().String())
+				}
+			}
+			sort.Strings(ips)
+			sort.Strings(x.ExpectedA)
+
+			if !reflect.DeepEqual(ips, x.ExpectedA) {
+				t.Logf("Got '%+v', expected '%+v'", ips, x.ExpectedA)
+				t.Fail()
+			}
+
+		}
+	}
+}