zones.go 15 KB

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