2
0

zones.go 14 KB

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