zones.go 14 KB

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