zone.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  1. package zones
  2. import (
  3. "encoding/json"
  4. "log"
  5. "net"
  6. "strconv"
  7. "strings"
  8. "sync"
  9. "github.com/abh/geodns/applog"
  10. "github.com/abh/geodns/health"
  11. "github.com/abh/geodns/targeting"
  12. "github.com/abh/geodns/targeting/geo"
  13. "github.com/miekg/dns"
  14. )
  15. type ZoneOptions struct {
  16. Serial int
  17. Ttl int
  18. MaxHosts int
  19. Contact string
  20. Targeting targeting.TargetOptions
  21. Closest bool
  22. // temporary, using this to keep the healthtest code
  23. // compiling and vaguely included
  24. healthChecker bool
  25. }
  26. type ZoneLogging struct {
  27. StatHat bool
  28. StatHatAPI string
  29. }
  30. type Record struct {
  31. RR dns.RR
  32. Weight int
  33. Loc *geo.Location
  34. Test string
  35. }
  36. type Records []*Record
  37. func (s Records) Len() int { return len(s) }
  38. func (s Records) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
  39. type RecordsByWeight struct{ Records }
  40. func (s RecordsByWeight) Less(i, j int) bool { return s.Records[i].Weight > s.Records[j].Weight }
  41. type Label struct {
  42. Label string
  43. MaxHosts int
  44. Ttl int
  45. Records map[uint16]Records
  46. Weight map[uint16]int
  47. Closest bool
  48. Test health.HealthTester
  49. }
  50. type LabelMatch struct {
  51. Label *Label
  52. Type uint16
  53. }
  54. type labelmap map[string]*Label
  55. type ZoneMetrics struct {
  56. LabelStats *zoneLabelStats
  57. ClientStats *zoneLabelStats
  58. }
  59. type Zone struct {
  60. Origin string
  61. Labels labelmap
  62. LabelCount int
  63. Options ZoneOptions
  64. Logging *ZoneLogging
  65. Metrics ZoneMetrics
  66. HasClosest bool
  67. HealthStatus health.Status
  68. healthExport bool
  69. sync.RWMutex
  70. }
  71. func NewZone(name string) *Zone {
  72. zone := new(Zone)
  73. zone.Labels = make(labelmap)
  74. zone.Origin = name
  75. zone.LabelCount = dns.CountLabel(zone.Origin)
  76. // defaults
  77. zone.Options.Ttl = 120
  78. zone.Options.MaxHosts = 2
  79. zone.Options.Contact = "hostmaster." + name
  80. zone.Options.Targeting = targeting.TargetGlobal + targeting.TargetCountry + targeting.TargetContinent
  81. return zone
  82. }
  83. func (z *Zone) SetupMetrics(old *Zone) {
  84. z.Lock()
  85. defer z.Unlock()
  86. if old != nil {
  87. z.Metrics = old.Metrics
  88. }
  89. if z.Metrics.LabelStats == nil {
  90. z.Metrics.LabelStats = NewZoneLabelStats(10000)
  91. }
  92. if z.Metrics.ClientStats == nil {
  93. z.Metrics.ClientStats = NewZoneLabelStats(10000)
  94. }
  95. }
  96. func (z *Zone) Close() {
  97. // todo: prune prometheus metrics for the zone ...
  98. if z.Metrics.LabelStats != nil {
  99. z.Metrics.LabelStats.Close()
  100. }
  101. if z.Metrics.ClientStats != nil {
  102. z.Metrics.ClientStats.Close()
  103. }
  104. }
  105. func (l *Label) FirstRR(dnsType uint16) dns.RR {
  106. return l.Records[dnsType][0].RR
  107. }
  108. func (z *Zone) AddLabel(k string) *Label {
  109. k = strings.ToLower(k)
  110. z.Labels[k] = new(Label)
  111. label := z.Labels[k]
  112. label.Label = k
  113. label.Ttl = 0 // replaced later
  114. label.MaxHosts = z.Options.MaxHosts
  115. label.Closest = z.Options.Closest
  116. label.Records = make(map[uint16]Records)
  117. label.Weight = make(map[uint16]int)
  118. return label
  119. }
  120. func (z *Zone) SoaRR() dns.RR {
  121. return z.Labels[""].FirstRR(dns.TypeSOA)
  122. }
  123. func (zone *Zone) AddSOA() {
  124. zone.addSOA()
  125. }
  126. func (zone *Zone) addSOA() {
  127. label := zone.Labels[""]
  128. primaryNs := "ns"
  129. // log.Println("LABEL", label)
  130. if label == nil {
  131. log.Println(zone.Origin, "doesn't have any 'root' records,",
  132. "you should probably add some NS records")
  133. label = zone.AddLabel("")
  134. }
  135. if record, ok := label.Records[dns.TypeNS]; ok {
  136. primaryNs = record[0].RR.(*dns.NS).Ns
  137. }
  138. ttl := zone.Options.Ttl * 10
  139. if ttl > 3600 {
  140. ttl = 3600
  141. }
  142. if ttl == 0 {
  143. ttl = 600
  144. }
  145. s := zone.Origin + ". " + strconv.Itoa(ttl) + " IN SOA " +
  146. primaryNs + " " + zone.Options.Contact + " " +
  147. strconv.Itoa(zone.Options.Serial) +
  148. // refresh, retry, expire, minimum are all
  149. // meaningless with this implementation
  150. " 5400 5400 1209600 3600"
  151. // log.Println("SOA: ", s)
  152. rr, err := dns.NewRR(s)
  153. if err != nil {
  154. log.Println("SOA Error", err)
  155. panic("Could not setup SOA")
  156. }
  157. record := Record{RR: rr}
  158. label.Records[dns.TypeSOA] = make([]*Record, 1)
  159. label.Records[dns.TypeSOA][0] = &record
  160. }
  161. func (z *Zone) findFirstLabel(s string, targets []string, qts []uint16) *LabelMatch {
  162. matches := z.FindLabels(s, targets, qts)
  163. if len(matches) == 0 {
  164. return nil
  165. }
  166. return &matches[0]
  167. }
  168. // Find label "s" in country "cc" falling back to the appropriate
  169. // continent and the global label name as needed. Looks for the
  170. // first available qType at each targeting level. Returns a list of
  171. // LabelMatch for potential labels that might satisfy the query.
  172. // "MF" records are treated as aliases. The API returns all the
  173. // matches the targeting will allow so health check filtering won't
  174. // filter out the "best" results leaving no others.
  175. func (z *Zone) FindLabels(s string, targets []string, qts []uint16) []LabelMatch {
  176. matches := make([]LabelMatch, 0)
  177. for _, target := range targets {
  178. var name string
  179. switch target {
  180. case "@":
  181. name = s
  182. default:
  183. if len(s) > 0 {
  184. name = s + "." + target
  185. } else {
  186. name = target
  187. }
  188. }
  189. if label, ok := z.Labels[name]; ok {
  190. var name string
  191. for _, qtype := range qts {
  192. switch qtype {
  193. case dns.TypeANY:
  194. // short-circuit mostly to avoid subtle bugs later
  195. // to be correct we should run through all the selectors and
  196. // pick types not already picked
  197. matches = append(matches, LabelMatch{z.Labels[s], qtype})
  198. continue
  199. case dns.TypeMF:
  200. if label.Records[dns.TypeMF] != nil {
  201. name = label.FirstRR(dns.TypeMF).(*dns.MF).Mf
  202. // TODO: need to avoid loops here somehow
  203. aliases := z.FindLabels(name, targets, qts)
  204. matches = append(matches, aliases...)
  205. continue
  206. }
  207. default:
  208. // return the label if it has the right record
  209. if label.Records[qtype] != nil && len(label.Records[qtype]) > 0 {
  210. matches = append(matches, LabelMatch{label, qtype})
  211. continue
  212. }
  213. }
  214. }
  215. }
  216. }
  217. if len(matches) == 0 {
  218. // this is to make sure we return 'noerror' instead of 'nxdomain' when
  219. // appropriate.
  220. if label, ok := z.Labels[s]; ok {
  221. matches = append(matches, LabelMatch{label, 0})
  222. }
  223. }
  224. return matches
  225. }
  226. // Find the locations of all the A and AAAA records within a zone. If we were
  227. // being really clever here we could use LOC records too. But for the time
  228. // being we'll just use GeoIP.
  229. func (z *Zone) SetLocations() {
  230. geo := targeting.Geo()
  231. qtypes := []uint16{dns.TypeA, dns.TypeAAAA}
  232. for _, label := range z.Labels {
  233. if label.Closest {
  234. for _, qtype := range qtypes {
  235. if label.Records[qtype] != nil && len(label.Records[qtype]) > 0 {
  236. for i := range label.Records[qtype] {
  237. label.Records[qtype][i].Loc = nil
  238. rr := label.Records[qtype][i].RR
  239. var ip *net.IP
  240. switch rr.(type) {
  241. case *dns.A:
  242. ip = &rr.(*dns.A).A
  243. case *dns.AAAA:
  244. ip = &rr.(*dns.AAAA).AAAA
  245. default:
  246. log.Printf("Can't lookup location of type %T", rr)
  247. }
  248. if ip != nil {
  249. location, err := geo.GetLocation(*ip)
  250. if err != nil {
  251. // log.Printf("Could not get location for '%s': %s", ip.String(), err)
  252. continue
  253. }
  254. label.Records[qtype][i].Loc = location
  255. }
  256. }
  257. }
  258. }
  259. }
  260. }
  261. }
  262. func (z *Zone) addHealthReference(l *Label, data interface{}) {
  263. // First safely get rid of any old test. As label tests
  264. // should never run this should never be executed
  265. // if l.Test != nil {
  266. // l.Test.Stop()
  267. // l.Test = nil
  268. // }
  269. if data == nil {
  270. return
  271. }
  272. if i, ok := data.(map[string]interface{}); ok {
  273. tester, err := health.NewReferenceFromMap(i)
  274. if err != nil {
  275. applog.Printf("Could not setup reference to health check: %s", err)
  276. return
  277. }
  278. l.Test = tester
  279. }
  280. }
  281. func (z *Zone) setupHealthTests() {
  282. for _, label := range z.Labels {
  283. if label.Test == nil {
  284. // log.Printf("label.Test for '%s' == nil", label.Label)
  285. continue
  286. }
  287. // todo: document which record types are processed
  288. // or process all ...
  289. for _, rrs := range label.Records {
  290. for _, rec := range rrs {
  291. if len(rec.Test) > 0 {
  292. continue
  293. }
  294. var t string
  295. switch rrt := rec.RR.(type) {
  296. case *dns.A:
  297. t = rrt.A.String()
  298. case *dns.AAAA:
  299. t = rrt.AAAA.String()
  300. case *dns.MX:
  301. t = rrt.Mx
  302. default:
  303. continue
  304. }
  305. rec.Test = t
  306. }
  307. }
  308. }
  309. }
  310. // func (z *Zone) StartStopHealthTests(start bool, oldZone *Zone) {}
  311. // applog.Printf("Start/stop health checks on zone %s start=%v", z.Origin, start)
  312. // for labelName, label := range z.Labels {
  313. // for _, qtype := range health.Qtypes {
  314. // if label.Records[qtype] != nil && len(label.Records[qtype]) > 0 {
  315. // for i := range label.Records[qtype] {
  316. // rr := label.Records[qtype][i].RR
  317. // var ip net.IP
  318. // switch rrt := rr.(type) {
  319. // case *dns.A:
  320. // ip = rrt.A
  321. // case *dns.AAAA:
  322. // ip = rrt.AAAA
  323. // default:
  324. // continue
  325. // }
  326. // var test *health.HealthTest
  327. // ref := fmt.Sprintf("%s/%s/%d/%d", z.Origin, labelName, qtype, i)
  328. // if start {
  329. // if test = label.Records[qtype][i].Test; test != nil {
  330. // // stop any old test
  331. // health.TestRunner.removeTest(test, ref)
  332. // } else {
  333. // if ltest := label.Test; ltest != nil {
  334. // test = ltest.copy(ip)
  335. // label.Records[qtype][i].Test = test
  336. // }
  337. // }
  338. // if test != nil {
  339. // test.ipAddress = ip
  340. // // if we are given an oldzone, let's see if we can find the old RR and
  341. // // copy over the initial health state, rather than use the initial health
  342. // // state provided from the label. This helps to stop health state bouncing
  343. // // when a zone file is reloaded for a purposes unrelated to the RR
  344. // if oldZone != nil {
  345. // oLabel, ok := oldZone.Labels[labelName]
  346. // if ok {
  347. // if oLabel.Test != nil {
  348. // for i := range oLabel.Records[qtype] {
  349. // oRecord := oLabel.Records[qtype][i]
  350. // var oip net.IP
  351. // switch orrt := oRecord.RR.(type) {
  352. // case *dns.A:
  353. // oip = orrt.A
  354. // case *dns.AAAA:
  355. // oip = orrt.AAAA
  356. // default:
  357. // continue
  358. // }
  359. // if oip.Equal(ip) {
  360. // if oRecord.Test != nil {
  361. // h := oRecord.Test.IsHealthy()
  362. // applog.Printf("Carrying over previous health state for %s: %v", oRecord.Test.ipAddress, h)
  363. // // we know the test is stopped (as we haven't started it) so we can write
  364. // // without the mutex and avoid a misleading log message
  365. // test.healthy = h
  366. // }
  367. // break
  368. // }
  369. // }
  370. // }
  371. // }
  372. // }
  373. // health.TestRunner.addTest(test, ref)
  374. // }
  375. // } else {
  376. // if test = label.Records[qtype][i].Test; test != nil {
  377. // health.TestRunner.removeTest(test, ref)
  378. // }
  379. // }
  380. // }
  381. // }
  382. // }
  383. // }
  384. func (z *Zone) HealthRR(label string, baseLabel string) []dns.RR {
  385. h := dns.RR_Header{Ttl: 1, Class: dns.ClassINET, Rrtype: dns.TypeTXT}
  386. h.Name = label
  387. healthstatus := make(map[string]map[string]bool)
  388. // if l, ok := z.Labels[baseLabel]; ok {
  389. // for qt, records := range l.Records {
  390. // if qts, ok := dns.TypeToString[qt]; ok {
  391. // hmap := make(map[string]bool)
  392. // for _, record := range records {
  393. // if record.Test != nil {
  394. // hmap[(*record.Test).IP().String()] = health.TestRunner.IsHealthy(record.Test)
  395. // }
  396. // }
  397. // healthstatus[qts] = hmap
  398. // }
  399. // }
  400. // }
  401. js, _ := json.Marshal(healthstatus)
  402. return []dns.RR{&dns.TXT{Hdr: h, Txt: []string{string(js)}}}
  403. }