zones.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700
  1. package main
  2. import (
  3. "crypto/sha256"
  4. "encoding/hex"
  5. "encoding/json"
  6. "fmt"
  7. "io/ioutil"
  8. "log"
  9. "net"
  10. "os"
  11. "path"
  12. "runtime/debug"
  13. "sort"
  14. "strconv"
  15. "strings"
  16. "time"
  17. "github.com/abh/geodns/Godeps/_workspace/src/github.com/abh/errorutil"
  18. "github.com/abh/geodns/Godeps/_workspace/src/github.com/miekg/dns"
  19. )
  20. // Zones maps domain names to zone data
  21. type Zones map[string]*Zone
  22. type ZoneReadRecord struct {
  23. time time.Time
  24. hash string
  25. }
  26. var lastRead = map[string]*ZoneReadRecord{}
  27. func zonesReader(dirName string, zones Zones) {
  28. for {
  29. zonesReadDir(dirName, zones)
  30. time.Sleep(5 * time.Second)
  31. }
  32. }
  33. func addHandler(zones Zones, name string, config *Zone) {
  34. oldZone := zones[name]
  35. config.SetupMetrics(oldZone)
  36. zones[name] = config
  37. dns.HandleFunc(name, setupServerFunc(config))
  38. }
  39. func zonesReadDir(dirName string, zones Zones) error {
  40. dir, err := ioutil.ReadDir(dirName)
  41. if err != nil {
  42. log.Println("Could not read", dirName, ":", err)
  43. return err
  44. }
  45. seenZones := map[string]bool{}
  46. var parseErr error
  47. for _, file := range dir {
  48. fileName := file.Name()
  49. if !strings.HasSuffix(strings.ToLower(fileName), ".json") {
  50. continue
  51. }
  52. zoneName := zoneNameFromFile(fileName)
  53. seenZones[zoneName] = true
  54. if _, ok := lastRead[zoneName]; !ok || file.ModTime().After(lastRead[zoneName].time) {
  55. modTime := file.ModTime()
  56. if ok {
  57. logPrintf("Reloading %s\n", fileName)
  58. lastRead[zoneName].time = modTime
  59. } else {
  60. logPrintf("Reading new file %s\n", fileName)
  61. lastRead[zoneName] = &ZoneReadRecord{time: modTime}
  62. }
  63. filename := path.Join(dirName, fileName)
  64. // Check the sha256 of the file has not changed. It's worth an explanation of
  65. // why there isn't a TOCTOU race here. Conceivably after checking whether the
  66. // SHA has changed, the contents then change again before we actually load
  67. // the JSON. This can occur in two situations:
  68. //
  69. // 1. The SHA has not changed when we read the file for the SHA, but then
  70. // changes before we process the JSON
  71. //
  72. // 2. The SHA has changed when we read the file for the SHA, but then changes
  73. // again before we process the JSON
  74. //
  75. // In circumstance (1) we won't reread the file the first time, but the subsequent
  76. // change should alter the mtime again, causing us to reread it. This reflects
  77. // the fact there were actually two changes.
  78. //
  79. // In circumstance (2) we have already reread the file once, and then when the
  80. // contents are changed the mtime changes again
  81. //
  82. // Provided files are replaced atomically, this should be OK. If files are not
  83. // replaced atomically we have other problems (e.g. partial reads).
  84. sha256 := sha256File(filename)
  85. if lastRead[zoneName].hash == sha256 {
  86. logPrintf("Skipping new file %s as hash is unchanged\n", filename)
  87. continue
  88. }
  89. config, err := readZoneFile(zoneName, filename)
  90. if config == nil || err != nil {
  91. parseErr = fmt.Errorf("Error reading zone '%s': %s", zoneName, err)
  92. log.Println(parseErr.Error())
  93. continue
  94. }
  95. (lastRead[zoneName]).hash = sha256
  96. addHandler(zones, zoneName, config)
  97. }
  98. }
  99. for zoneName, zone := range zones {
  100. if zoneName == "pgeodns" {
  101. continue
  102. }
  103. if ok, _ := seenZones[zoneName]; ok {
  104. continue
  105. }
  106. log.Println("Removing zone", zone.Origin)
  107. delete(lastRead, zoneName)
  108. zone.Close()
  109. dns.HandleRemove(zoneName)
  110. delete(zones, zoneName)
  111. }
  112. return parseErr
  113. }
  114. func setupPgeodnsZone(zones Zones) {
  115. zoneName := "pgeodns"
  116. Zone := NewZone(zoneName)
  117. label := new(Label)
  118. label.Records = make(map[uint16]Records)
  119. label.Weight = make(map[uint16]int)
  120. Zone.Labels[""] = label
  121. setupSOA(Zone)
  122. addHandler(zones, zoneName, Zone)
  123. }
  124. func setupRootZone() {
  125. dns.HandleFunc(".", func(w dns.ResponseWriter, r *dns.Msg) {
  126. m := new(dns.Msg)
  127. m.SetRcode(r, dns.RcodeRefused)
  128. w.WriteMsg(m)
  129. })
  130. }
  131. func readZoneFile(zoneName, fileName string) (zone *Zone, zerr error) {
  132. defer func() {
  133. if r := recover(); r != nil {
  134. log.Printf("reading %s failed: %s", zoneName, r)
  135. debug.PrintStack()
  136. zerr = fmt.Errorf("reading %s failed: %s", zoneName, r)
  137. }
  138. }()
  139. fh, err := os.Open(fileName)
  140. if err != nil {
  141. log.Printf("Could not read '%s': %s", fileName, err)
  142. panic(err)
  143. }
  144. zone = NewZone(zoneName)
  145. fileInfo, err := fh.Stat()
  146. if err != nil {
  147. log.Printf("Could not stat '%s': %s", fileName, err)
  148. } else {
  149. zone.Options.Serial = int(fileInfo.ModTime().Unix())
  150. }
  151. var objmap map[string]interface{}
  152. decoder := json.NewDecoder(fh)
  153. if err = decoder.Decode(&objmap); err != nil {
  154. extra := ""
  155. if serr, ok := err.(*json.SyntaxError); ok {
  156. if _, serr := fh.Seek(0, os.SEEK_SET); serr != nil {
  157. log.Fatalf("seek error: %v", serr)
  158. }
  159. line, col, highlight := errorutil.HighlightBytePosition(fh, serr.Offset)
  160. extra = fmt.Sprintf(":\nError at line %d, column %d (file offset %d):\n%s",
  161. line, col, serr.Offset, highlight)
  162. }
  163. return nil, fmt.Errorf("error parsing JSON object in config file %s%s\n%v",
  164. fh.Name(), extra, err)
  165. }
  166. if err != nil {
  167. panic(err)
  168. }
  169. //log.Println(objmap)
  170. var data map[string]interface{}
  171. for k, v := range objmap {
  172. //log.Printf("k: %s v: %#v, T: %T\n", k, v, v)
  173. switch k {
  174. case "ttl":
  175. zone.Options.Ttl = valueToInt(v)
  176. case "serial":
  177. zone.Options.Serial = valueToInt(v)
  178. case "contact":
  179. zone.Options.Contact = v.(string)
  180. case "max_hosts":
  181. zone.Options.MaxHosts = valueToInt(v)
  182. case "targeting":
  183. zone.Options.Targeting, err = parseTargets(v.(string))
  184. if err != nil {
  185. log.Printf("Could not parse targeting '%s': %s", v, err)
  186. return nil, err
  187. }
  188. case "logging":
  189. {
  190. logging := new(ZoneLogging)
  191. for logger, v := range v.(map[string]interface{}) {
  192. switch logger {
  193. case "stathat":
  194. logging.StatHat = valueToBool(v)
  195. case "stathat_api":
  196. logging.StatHatAPI = valueToString(v)
  197. logging.StatHat = true
  198. default:
  199. log.Println("Unknown logger option", logger)
  200. }
  201. }
  202. zone.Logging = logging
  203. // log.Printf("logging options: %#v", logging)
  204. }
  205. continue
  206. case "data":
  207. data = v.(map[string]interface{})
  208. }
  209. }
  210. setupZoneData(data, zone)
  211. //log.Printf("ZO T: %T %s\n", Zones["0.us"], Zones["0.us"])
  212. //log.Println("IP", string(Zone.Regions["0.us"].IPv4[0].ip))
  213. switch {
  214. case zone.Options.Targeting >= TargetRegionGroup:
  215. geoIP.setupGeoIPCity()
  216. case zone.Options.Targeting >= TargetContinent:
  217. geoIP.setupGeoIPCountry()
  218. }
  219. if zone.Options.Targeting&TargetASN > 0 {
  220. geoIP.setupGeoIPASN()
  221. }
  222. return zone, nil
  223. }
  224. func setupZoneData(data map[string]interface{}, Zone *Zone) {
  225. recordTypes := map[string]uint16{
  226. "a": dns.TypeA,
  227. "aaaa": dns.TypeAAAA,
  228. "alias": dns.TypeMF,
  229. "cname": dns.TypeCNAME,
  230. "mx": dns.TypeMX,
  231. "ns": dns.TypeNS,
  232. "txt": dns.TypeTXT,
  233. "spf": dns.TypeSPF,
  234. "srv": dns.TypeSRV,
  235. }
  236. for dk, dv_inter := range data {
  237. dv := dv_inter.(map[string]interface{})
  238. //log.Printf("K %s V %s TYPE-V %T\n", dk, dv, dv)
  239. label := Zone.AddLabel(dk)
  240. for rType, rdata := range dv {
  241. switch rType {
  242. case "max_hosts":
  243. label.MaxHosts = valueToInt(rdata)
  244. continue
  245. case "ttl":
  246. label.Ttl = valueToInt(rdata)
  247. continue
  248. }
  249. dnsType, ok := recordTypes[rType]
  250. if !ok {
  251. log.Printf("Unsupported record type '%s'\n", rType)
  252. continue
  253. }
  254. if rdata == nil {
  255. //log.Printf("No %s records for label %s\n", rType, dk)
  256. continue
  257. }
  258. //log.Printf("rdata %s TYPE-R %T\n", rdata, rdata)
  259. records := make(map[string][]interface{})
  260. switch rdata.(type) {
  261. case map[string]interface{}:
  262. // Handle NS map syntax, map[ns2.example.net:<nil> ns1.example.net:<nil>]
  263. tmp := make([]interface{}, 0)
  264. for rdataK, rdataV := range rdata.(map[string]interface{}) {
  265. if rdataV == nil {
  266. rdataV = ""
  267. }
  268. tmp = append(tmp, []string{rdataK, rdataV.(string)})
  269. }
  270. records[rType] = tmp
  271. case string:
  272. // CNAME and alias
  273. tmp := make([]interface{}, 1)
  274. tmp[0] = rdata.(string)
  275. records[rType] = tmp
  276. default:
  277. records[rType] = rdata.([]interface{})
  278. }
  279. //log.Printf("RECORDS %s TYPE-REC %T\n", Records, Records)
  280. label.Records[dnsType] = make(Records, len(records[rType]))
  281. for i := 0; i < len(records[rType]); i++ {
  282. //log.Printf("RT %T %#v\n", records[rType][i], records[rType][i])
  283. record := new(Record)
  284. var h dns.RR_Header
  285. h.Class = dns.ClassINET
  286. h.Rrtype = dnsType
  287. // We add the TTL as a last pass because we might not have
  288. // processed it yet when we process the record data.
  289. switch len(label.Label) {
  290. case 0:
  291. h.Name = Zone.Origin + "."
  292. default:
  293. h.Name = label.Label + "." + Zone.Origin + "."
  294. }
  295. switch dnsType {
  296. case dns.TypeA, dns.TypeAAAA:
  297. str, weight := getStringWeight(records[rType][i].([]interface{}))
  298. ip := str
  299. record.Weight = weight
  300. switch dnsType {
  301. case dns.TypeA:
  302. if x := net.ParseIP(ip); x != nil {
  303. record.RR = &dns.A{Hdr: h, A: x}
  304. break
  305. }
  306. panic(fmt.Errorf("Bad A record %s for %s", ip, dk))
  307. case dns.TypeAAAA:
  308. if x := net.ParseIP(ip); x != nil {
  309. record.RR = &dns.AAAA{Hdr: h, AAAA: x}
  310. break
  311. }
  312. panic(fmt.Errorf("Bad AAAA record %s for %s", ip, dk))
  313. }
  314. case dns.TypeMX:
  315. rec := records[rType][i].(map[string]interface{})
  316. pref := uint16(0)
  317. mx := rec["mx"].(string)
  318. if !strings.HasSuffix(mx, ".") {
  319. mx = mx + "."
  320. }
  321. if rec["weight"] != nil {
  322. record.Weight = valueToInt(rec["weight"])
  323. }
  324. if rec["preference"] != nil {
  325. pref = uint16(valueToInt(rec["preference"]))
  326. }
  327. record.RR = &dns.MX{
  328. Hdr: h,
  329. Mx: mx,
  330. Preference: pref}
  331. case dns.TypeSRV:
  332. rec := records[rType][i].(map[string]interface{})
  333. priority := uint16(0)
  334. srv_weight := uint16(0)
  335. port := uint16(0)
  336. target := rec["target"].(string)
  337. if !dns.IsFqdn(target) {
  338. target = target + "." + Zone.Origin
  339. }
  340. if rec["srv_weight"] != nil {
  341. srv_weight = uint16(valueToInt(rec["srv_weight"]))
  342. }
  343. if rec["port"] != nil {
  344. port = uint16(valueToInt(rec["port"]))
  345. }
  346. if rec["priority"] != nil {
  347. priority = uint16(valueToInt(rec["priority"]))
  348. }
  349. record.RR = &dns.SRV{
  350. Hdr: h,
  351. Priority: priority,
  352. Weight: srv_weight,
  353. Port: port,
  354. Target: target}
  355. case dns.TypeCNAME:
  356. rec := records[rType][i]
  357. var target string
  358. var weight int
  359. switch rec.(type) {
  360. case string:
  361. target = rec.(string)
  362. case []interface{}:
  363. target, weight = getStringWeight(rec.([]interface{}))
  364. }
  365. if !dns.IsFqdn(target) {
  366. target = target + "." + Zone.Origin
  367. }
  368. record.Weight = weight
  369. record.RR = &dns.CNAME{Hdr: h, Target: dns.Fqdn(target)}
  370. case dns.TypeMF:
  371. rec := records[rType][i]
  372. // MF records (how we store aliases) are not FQDNs
  373. record.RR = &dns.MF{Hdr: h, Mf: rec.(string)}
  374. case dns.TypeNS:
  375. rec := records[rType][i]
  376. if h.Ttl < 86400 {
  377. h.Ttl = 86400
  378. }
  379. var ns string
  380. switch rec.(type) {
  381. case string:
  382. ns = rec.(string)
  383. case []string:
  384. recl := rec.([]string)
  385. ns = recl[0]
  386. if len(recl[1]) > 0 {
  387. log.Println("NS records with names syntax not supported")
  388. }
  389. default:
  390. log.Printf("Data: %T %#v\n", rec, rec)
  391. panic("Unrecognized NS format/syntax")
  392. }
  393. rr := &dns.NS{Hdr: h, Ns: dns.Fqdn(ns)}
  394. record.RR = rr
  395. case dns.TypeTXT:
  396. rec := records[rType][i]
  397. var txt string
  398. switch rec.(type) {
  399. case string:
  400. txt = rec.(string)
  401. case map[string]interface{}:
  402. recmap := rec.(map[string]interface{})
  403. if weight, ok := recmap["weight"]; ok {
  404. record.Weight = valueToInt(weight)
  405. }
  406. if t, ok := recmap["txt"]; ok {
  407. txt = t.(string)
  408. }
  409. }
  410. if len(txt) > 0 {
  411. rr := &dns.TXT{Hdr: h, Txt: []string{txt}}
  412. record.RR = rr
  413. } else {
  414. log.Printf("Zero length txt record for '%s' in '%s'\n", label.Label, Zone.Origin)
  415. continue
  416. }
  417. // Initial SPF support added here, cribbed from the TypeTXT case definition - SPF records should be handled identically
  418. case dns.TypeSPF:
  419. rec := records[rType][i]
  420. var spf string
  421. switch rec.(type) {
  422. case string:
  423. spf = rec.(string)
  424. case map[string]interface{}:
  425. recmap := rec.(map[string]interface{})
  426. if weight, ok := recmap["weight"]; ok {
  427. record.Weight = valueToInt(weight)
  428. }
  429. if t, ok := recmap["spf"]; ok {
  430. spf = t.(string)
  431. }
  432. }
  433. if len(spf) > 0 {
  434. rr := &dns.SPF{Hdr: h, Txt: []string{spf}}
  435. record.RR = rr
  436. } else {
  437. log.Printf("Zero length SPF record for '%s' in '%s'\n", label.Label, Zone.Origin)
  438. continue
  439. }
  440. default:
  441. log.Println("type:", rType)
  442. panic("Don't know how to handle this type")
  443. }
  444. if record.RR == nil {
  445. panic("record.RR is nil")
  446. }
  447. label.Weight[dnsType] += record.Weight
  448. label.Records[dnsType][i] = *record
  449. }
  450. if label.Weight[dnsType] > 0 {
  451. sort.Sort(RecordsByWeight{label.Records[dnsType]})
  452. }
  453. }
  454. }
  455. // loop over exisiting labels, create zone records for missing sub-domains
  456. // and set TTLs
  457. for k := range Zone.Labels {
  458. if strings.Contains(k, ".") {
  459. subLabels := strings.Split(k, ".")
  460. for i := 1; i < len(subLabels); i++ {
  461. subSubLabel := strings.Join(subLabels[i:], ".")
  462. if _, ok := Zone.Labels[subSubLabel]; !ok {
  463. Zone.AddLabel(subSubLabel)
  464. }
  465. }
  466. }
  467. if Zone.Labels[k].Ttl > 0 {
  468. for _, records := range Zone.Labels[k].Records {
  469. for _, r := range records {
  470. r.RR.Header().Ttl = uint32(Zone.Labels[k].Ttl)
  471. }
  472. }
  473. }
  474. }
  475. setupSOA(Zone)
  476. //log.Println(Zones[k])
  477. }
  478. func getStringWeight(rec []interface{}) (string, int) {
  479. str := rec[0].(string)
  480. var weight int
  481. if len(rec) > 1 {
  482. switch rec[1].(type) {
  483. case string:
  484. var err error
  485. weight, err = strconv.Atoi(rec[1].(string))
  486. if err != nil {
  487. panic("Error converting weight to integer")
  488. }
  489. case float64:
  490. weight = int(rec[1].(float64))
  491. }
  492. }
  493. return str, weight
  494. }
  495. func setupSOA(Zone *Zone) {
  496. label := Zone.Labels[""]
  497. primaryNs := "ns"
  498. // log.Println("LABEL", label)
  499. if label == nil {
  500. log.Println(Zone.Origin, "doesn't have any 'root' records,",
  501. "you should probably add some NS records")
  502. label = Zone.AddLabel("")
  503. }
  504. if record, ok := label.Records[dns.TypeNS]; ok {
  505. primaryNs = record[0].RR.(*dns.NS).Ns
  506. }
  507. ttl := Zone.Options.Ttl * 10
  508. if ttl > 3600 {
  509. ttl = 3600
  510. }
  511. if ttl == 0 {
  512. ttl = 600
  513. }
  514. s := Zone.Origin + ". " + strconv.Itoa(ttl) + " IN SOA " +
  515. primaryNs + " " + Zone.Options.Contact + " " +
  516. strconv.Itoa(Zone.Options.Serial) +
  517. // refresh, retry, expire, minimum are all
  518. // meaningless with this implementation
  519. " 5400 5400 1209600 3600"
  520. // log.Println("SOA: ", s)
  521. rr, err := dns.NewRR(s)
  522. if err != nil {
  523. log.Println("SOA Error", err)
  524. panic("Could not setup SOA")
  525. }
  526. record := Record{RR: rr}
  527. label.Records[dns.TypeSOA] = make([]Record, 1)
  528. label.Records[dns.TypeSOA][0] = record
  529. }
  530. func valueToBool(v interface{}) (rv bool) {
  531. switch v.(type) {
  532. case bool:
  533. rv = v.(bool)
  534. case string:
  535. str := v.(string)
  536. switch str {
  537. case "true":
  538. rv = true
  539. case "1":
  540. rv = true
  541. }
  542. case float64:
  543. if v.(float64) > 0 {
  544. rv = true
  545. }
  546. default:
  547. log.Println("Can't convert", v, "to bool")
  548. panic("Can't convert value")
  549. }
  550. return rv
  551. }
  552. func valueToString(v interface{}) (rv string) {
  553. switch v.(type) {
  554. case string:
  555. rv = v.(string)
  556. case float64:
  557. rv = strconv.FormatFloat(v.(float64), 'f', -1, 64)
  558. default:
  559. log.Println("Can't convert", v, "to string")
  560. panic("Can't convert value")
  561. }
  562. return rv
  563. }
  564. func valueToInt(v interface{}) (rv int) {
  565. switch v.(type) {
  566. case string:
  567. i, err := strconv.Atoi(v.(string))
  568. if err != nil {
  569. panic("Error converting weight to integer")
  570. }
  571. rv = i
  572. case float64:
  573. rv = int(v.(float64))
  574. default:
  575. log.Println("Can't convert", v, "to integer")
  576. panic("Can't convert value")
  577. }
  578. return rv
  579. }
  580. func zoneNameFromFile(fileName string) string {
  581. return fileName[0:strings.LastIndex(fileName, ".")]
  582. }
  583. func sha256File(fn string) string {
  584. if data, err := ioutil.ReadFile(fn); err != nil {
  585. return ""
  586. } else {
  587. hasher := sha256.New()
  588. hasher.Write(data)
  589. return hex.EncodeToString(hasher.Sum(nil))
  590. }
  591. }