reader.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561
  1. package zones
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "log"
  6. "net"
  7. "os"
  8. "runtime/debug"
  9. "sort"
  10. "strconv"
  11. "strings"
  12. "github.com/abh/geodns/targeting"
  13. "github.com/abh/geodns/typeutil"
  14. "github.com/abh/errorutil"
  15. "github.com/miekg/dns"
  16. )
  17. // ZoneList maps domain names to zone data
  18. type ZoneList map[string]*Zone
  19. func (zone *Zone) ReadZoneFile(fileName string) (zerr error) {
  20. defer func() {
  21. if r := recover(); r != nil {
  22. log.Printf("reading %s failed: %s", zone.Origin, r)
  23. debug.PrintStack()
  24. zerr = fmt.Errorf("reading %s failed: %s", zone.Origin, r)
  25. }
  26. }()
  27. fh, err := os.Open(fileName)
  28. if err != nil {
  29. log.Printf("Could not read '%s': %s", fileName, err)
  30. panic(err)
  31. }
  32. fileInfo, err := fh.Stat()
  33. if err != nil {
  34. log.Printf("Could not stat '%s': %s", fileName, err)
  35. } else {
  36. zone.Options.Serial = int(fileInfo.ModTime().Unix())
  37. }
  38. var objmap map[string]interface{}
  39. decoder := json.NewDecoder(fh)
  40. if err = decoder.Decode(&objmap); err != nil {
  41. extra := ""
  42. if serr, ok := err.(*json.SyntaxError); ok {
  43. if _, serr := fh.Seek(0, os.SEEK_SET); serr != nil {
  44. log.Fatalf("seek error: %v", serr)
  45. }
  46. line, col, highlight := errorutil.HighlightBytePosition(fh, serr.Offset)
  47. extra = fmt.Sprintf(":\nError at line %d, column %d (file offset %d):\n%s",
  48. line, col, serr.Offset, highlight)
  49. }
  50. return fmt.Errorf("error parsing JSON object in config file %s%s\n%v",
  51. fh.Name(), extra, err)
  52. }
  53. //log.Println(objmap)
  54. var data map[string]interface{}
  55. for k, v := range objmap {
  56. //log.Printf("k: %s v: %#v, T: %T\n", k, v, v)
  57. switch k {
  58. case "ttl":
  59. zone.Options.Ttl = typeutil.ToInt(v)
  60. case "serial":
  61. zone.Options.Serial = typeutil.ToInt(v)
  62. case "contact":
  63. zone.Options.Contact = v.(string)
  64. case "max_hosts":
  65. zone.Options.MaxHosts = typeutil.ToInt(v)
  66. case "closest":
  67. zone.Options.Closest = v.(bool)
  68. if zone.Options.Closest {
  69. zone.HasClosest = true
  70. }
  71. case "targeting":
  72. zone.Options.Targeting, err = targeting.ParseTargets(v.(string))
  73. if err != nil {
  74. return fmt.Errorf("parsing targeting '%s': %s", v, err)
  75. }
  76. case "logging":
  77. {
  78. logging := new(ZoneLogging)
  79. for logger, v := range v.(map[string]interface{}) {
  80. switch logger {
  81. case "stathat":
  82. logging.StatHat = typeutil.ToBool(v)
  83. case "stathat_api":
  84. logging.StatHatAPI = typeutil.ToString(v)
  85. logging.StatHat = true
  86. default:
  87. log.Println("Unknown logger option", logger)
  88. }
  89. }
  90. zone.Logging = logging
  91. // log.Printf("logging options: %#v", logging)
  92. }
  93. continue
  94. case "data":
  95. data = v.(map[string]interface{})
  96. }
  97. }
  98. setupZoneData(data, zone)
  99. //log.Printf("ZO T: %T %s\n", Zones["0.us"], Zones["0.us"])
  100. //log.Println("IP", string(Zone.Regions["0.us"].IPv4[0].ip))
  101. if zone.Options.Targeting == 0 && !zone.HasClosest {
  102. // no targeting requested
  103. return nil
  104. }
  105. if targeting.Geo() == nil {
  106. log.Printf("'%s': No geo provider configured", zone.Origin)
  107. return nil
  108. }
  109. switch {
  110. case zone.Options.Targeting >= targeting.TargetRegionGroup || zone.HasClosest:
  111. if ok, err := targeting.Geo().HasLocation(); !ok {
  112. log.Printf("Zone '%s' requested location/city targeting but geo provider isn't available: %s", zone.Origin, err)
  113. }
  114. case zone.Options.Targeting >= targeting.TargetContinent:
  115. if ok, err := targeting.Geo().HasCountry(); !ok {
  116. log.Printf("Zone '%s' requested country targeting but geo provider isn't available: %s", zone.Origin, err)
  117. }
  118. }
  119. if zone.Options.Targeting&targeting.TargetASN > 0 {
  120. if ok, err := targeting.Geo().HasASN(); !ok {
  121. log.Printf("Zone '%s' requested ASN targeting but geo provider isn't available: %s", zone.Origin, err)
  122. }
  123. }
  124. if zone.HasClosest {
  125. zone.SetLocations()
  126. }
  127. return nil
  128. }
  129. func setupZoneData(data map[string]interface{}, zone *Zone) {
  130. recordTypes := map[string]uint16{
  131. "a": dns.TypeA,
  132. "aaaa": dns.TypeAAAA,
  133. "alias": dns.TypeMF,
  134. "cname": dns.TypeCNAME,
  135. "mx": dns.TypeMX,
  136. "ns": dns.TypeNS,
  137. "txt": dns.TypeTXT,
  138. "spf": dns.TypeSPF,
  139. "srv": dns.TypeSRV,
  140. "ptr": dns.TypePTR,
  141. }
  142. for dk, dv_inter := range data {
  143. dv := dv_inter.(map[string]interface{})
  144. //log.Printf("K %s V %s TYPE-V %T\n", dk, dv, dv)
  145. label := zone.AddLabel(dk)
  146. for rType, rdata := range dv {
  147. switch rType {
  148. case "max_hosts":
  149. label.MaxHosts = typeutil.ToInt(rdata)
  150. continue
  151. case "closest":
  152. label.Closest = rdata.(bool)
  153. if label.Closest {
  154. zone.HasClosest = true
  155. }
  156. continue
  157. case "ttl":
  158. label.Ttl = typeutil.ToInt(rdata)
  159. continue
  160. case "health":
  161. zone.addHealthReference(label, rdata)
  162. continue
  163. }
  164. dnsType, ok := recordTypes[rType]
  165. if !ok {
  166. log.Printf("'%s' unsupported record type '%s'\n", zone.Origin, rType)
  167. continue
  168. }
  169. if rdata == nil {
  170. //log.Printf("No %s records for label %s\n", rType, dk)
  171. continue
  172. }
  173. //log.Printf("rdata %s TYPE-R %T\n", rdata, rdata)
  174. records := make(map[string][]interface{})
  175. switch rdata.(type) {
  176. case map[string]interface{}:
  177. // Handle NS map syntax, map[ns2.example.net:<nil> ns1.example.net:<nil>]
  178. tmp := make([]interface{}, 0)
  179. for rdataK, rdataV := range rdata.(map[string]interface{}) {
  180. if rdataV == nil {
  181. rdataV = ""
  182. }
  183. tmp = append(tmp, []string{rdataK, rdataV.(string)})
  184. }
  185. records[rType] = tmp
  186. case string:
  187. // CNAME and alias
  188. tmp := make([]interface{}, 1)
  189. tmp[0] = rdata.(string)
  190. records[rType] = tmp
  191. default:
  192. records[rType] = rdata.([]interface{})
  193. }
  194. //log.Printf("RECORDS %s TYPE-REC %T\n", Records, Records)
  195. label.Records[dnsType] = make(Records, len(records[rType]))
  196. for i := 0; i < len(records[rType]); i++ {
  197. //log.Printf("RT %T %#v\n", records[rType][i], records[rType][i])
  198. record := new(Record)
  199. var h dns.RR_Header
  200. h.Class = dns.ClassINET
  201. h.Rrtype = dnsType
  202. {
  203. // allow for individual health test name overrides
  204. if rec, ok := records[rType][i].(map[string]interface{}); ok {
  205. if h, ok := rec["health"].(string); ok {
  206. record.Test = h
  207. }
  208. }
  209. }
  210. switch len(label.Label) {
  211. case 0:
  212. h.Name = zone.Origin + "."
  213. default:
  214. h.Name = label.Label + "." + zone.Origin + "."
  215. }
  216. switch dnsType {
  217. case dns.TypeA, dns.TypeAAAA, dns.TypePTR:
  218. rec := records[rType][i]
  219. var ip string
  220. switch rec.(type) {
  221. case []interface{}:
  222. str, weight := getStringWeight(records[rType][i].([]interface{}))
  223. ip = str
  224. record.Weight = weight
  225. case map[string]interface{}:
  226. r := rec.(map[string]interface{})
  227. if _, ok := r["ip"]; ok {
  228. ip = r["ip"].(string)
  229. }
  230. if len(ip) == 0 || dnsType == dns.TypePTR {
  231. switch dnsType {
  232. case dns.TypeA:
  233. ip = r["a"].(string)
  234. case dns.TypeAAAA:
  235. ip = r["aaaa"].(string)
  236. case dns.TypePTR:
  237. ip = r["ptr"].(string)
  238. }
  239. }
  240. if w, ok := r["weight"]; ok {
  241. record.Weight = typeutil.ToInt(w)
  242. }
  243. if h, ok := r["health"]; ok {
  244. record.Test = typeutil.ToString(h)
  245. }
  246. }
  247. switch dnsType {
  248. case dns.TypePTR:
  249. record.RR = &dns.PTR{Hdr: h, Ptr: ip}
  250. break
  251. case dns.TypeA:
  252. if x := net.ParseIP(ip); x != nil {
  253. record.RR = &dns.A{Hdr: h, A: x}
  254. break
  255. }
  256. panic(fmt.Errorf("Bad A record %q for %q", ip, dk))
  257. case dns.TypeAAAA:
  258. if x := net.ParseIP(ip); x != nil {
  259. record.RR = &dns.AAAA{Hdr: h, AAAA: x}
  260. break
  261. }
  262. panic(fmt.Errorf("Bad AAAA record %q for %q", ip, dk))
  263. }
  264. case dns.TypeMX:
  265. rec := records[rType][i].(map[string]interface{})
  266. pref := uint16(0)
  267. mx := rec["mx"].(string)
  268. if !strings.HasSuffix(mx, ".") {
  269. mx = mx + "."
  270. }
  271. if rec["weight"] != nil {
  272. record.Weight = typeutil.ToInt(rec["weight"])
  273. }
  274. if rec["preference"] != nil {
  275. pref = uint16(typeutil.ToInt(rec["preference"]))
  276. }
  277. record.RR = &dns.MX{
  278. Hdr: h,
  279. Mx: mx,
  280. Preference: pref}
  281. case dns.TypeSRV:
  282. rec := records[rType][i].(map[string]interface{})
  283. priority := uint16(0)
  284. srv_weight := uint16(0)
  285. port := uint16(0)
  286. target := rec["target"].(string)
  287. if !dns.IsFqdn(target) {
  288. target = target + "." + zone.Origin
  289. }
  290. if rec["srv_weight"] != nil {
  291. srv_weight = uint16(typeutil.ToInt(rec["srv_weight"]))
  292. }
  293. if rec["port"] != nil {
  294. port = uint16(typeutil.ToInt(rec["port"]))
  295. }
  296. if rec["priority"] != nil {
  297. priority = uint16(typeutil.ToInt(rec["priority"]))
  298. }
  299. record.RR = &dns.SRV{
  300. Hdr: h,
  301. Priority: priority,
  302. Weight: srv_weight,
  303. Port: port,
  304. Target: target}
  305. case dns.TypeCNAME:
  306. rec := records[rType][i]
  307. var target string
  308. var weight int
  309. switch rec.(type) {
  310. case string:
  311. target = rec.(string)
  312. case []interface{}:
  313. target, weight = getStringWeight(rec.([]interface{}))
  314. case map[string]interface{}:
  315. r := rec.(map[string]interface{})
  316. if t, ok := r["cname"]; ok {
  317. target = typeutil.ToString(t)
  318. }
  319. if w, ok := r["weight"]; ok {
  320. weight = typeutil.ToInt(w)
  321. }
  322. if h, ok := r["health"]; ok {
  323. record.Test = typeutil.ToString(h)
  324. }
  325. }
  326. if !dns.IsFqdn(target) {
  327. target = target + "." + zone.Origin
  328. }
  329. record.Weight = weight
  330. record.RR = &dns.CNAME{Hdr: h, Target: dns.Fqdn(target)}
  331. case dns.TypeMF:
  332. rec := records[rType][i]
  333. // MF records (how we store aliases) are not FQDNs
  334. record.RR = &dns.MF{Hdr: h, Mf: rec.(string)}
  335. case dns.TypeNS:
  336. rec := records[rType][i]
  337. var ns string
  338. switch rec.(type) {
  339. case string:
  340. ns = rec.(string)
  341. case []string:
  342. recl := rec.([]string)
  343. ns = recl[0]
  344. if len(recl[1]) > 0 {
  345. log.Println("NS records with names syntax not supported")
  346. }
  347. default:
  348. log.Printf("Data: %T %#v\n", rec, rec)
  349. panic("Unrecognized NS format/syntax")
  350. }
  351. rr := &dns.NS{Hdr: h, Ns: dns.Fqdn(ns)}
  352. record.RR = rr
  353. case dns.TypeTXT:
  354. rec := records[rType][i]
  355. var txt string
  356. switch rec.(type) {
  357. case string:
  358. txt = rec.(string)
  359. case map[string]interface{}:
  360. recmap := rec.(map[string]interface{})
  361. if weight, ok := recmap["weight"]; ok {
  362. record.Weight = typeutil.ToInt(weight)
  363. }
  364. if t, ok := recmap["txt"]; ok {
  365. txt = t.(string)
  366. }
  367. }
  368. if len(txt) > 0 {
  369. rr := &dns.TXT{Hdr: h, Txt: []string{txt}}
  370. record.RR = rr
  371. } else {
  372. log.Printf("Zero length txt record for '%s' in '%s'\n", label.Label, zone.Origin)
  373. continue
  374. }
  375. // Initial SPF support added here, cribbed from the TypeTXT case definition - SPF records should be handled identically
  376. case dns.TypeSPF:
  377. rec := records[rType][i]
  378. var spf string
  379. switch rec.(type) {
  380. case string:
  381. spf = rec.(string)
  382. case map[string]interface{}:
  383. recmap := rec.(map[string]interface{})
  384. if weight, ok := recmap["weight"]; ok {
  385. record.Weight = typeutil.ToInt(weight)
  386. }
  387. if t, ok := recmap["spf"]; ok {
  388. spf = t.(string)
  389. }
  390. }
  391. if len(spf) > 0 {
  392. rr := &dns.SPF{Hdr: h, Txt: []string{spf}}
  393. record.RR = rr
  394. } else {
  395. log.Printf("Zero length SPF record for '%s' in '%s'\n", label.Label, zone.Origin)
  396. continue
  397. }
  398. default:
  399. log.Println("type:", rType)
  400. panic("Don't know how to handle this type")
  401. }
  402. if record.RR == nil {
  403. panic("record.RR is nil")
  404. }
  405. label.Weight[dnsType] += record.Weight
  406. label.Records[dnsType][i] = record
  407. }
  408. if label.Weight[dnsType] > 0 {
  409. sort.Sort(RecordsByWeight{label.Records[dnsType]})
  410. }
  411. }
  412. }
  413. // Loop over exisiting labels, create zone records for missing sub-domains
  414. // and set TTLs
  415. for k, l := range zone.Labels {
  416. if strings.Contains(k, ".") {
  417. subLabels := strings.Split(k, ".")
  418. for i := 1; i < len(subLabels); i++ {
  419. subSubLabel := strings.Join(subLabels[i:], ".")
  420. if _, ok := zone.Labels[subSubLabel]; !ok {
  421. zone.AddLabel(subSubLabel)
  422. }
  423. }
  424. }
  425. for qtype, records := range l.Records {
  426. setWeight := false
  427. if _, ok := alwaysWeighted[qtype]; ok && l.Weight[qtype] == 0 {
  428. setWeight = true
  429. }
  430. for _, r := range records {
  431. // We add the TTL as a last pass because we might not have
  432. // processed it yet when we process the record data.
  433. if setWeight {
  434. r.Weight = 1
  435. l.Weight[qtype] += r.Weight
  436. }
  437. var defaultTtl uint32 = 86400
  438. if r.RR.Header().Rrtype != dns.TypeNS {
  439. // NS records have special treatment. If they are not specified, they default to 86400 rather than
  440. // defaulting to the zone ttl option. The label TTL option always works though
  441. defaultTtl = uint32(zone.Options.Ttl)
  442. }
  443. if zone.Labels[k].Ttl > 0 {
  444. defaultTtl = uint32(zone.Labels[k].Ttl)
  445. }
  446. if r.RR.Header().Ttl == 0 {
  447. r.RR.Header().Ttl = defaultTtl
  448. }
  449. }
  450. }
  451. }
  452. zone.addSOA()
  453. }
  454. func getStringWeight(rec []interface{}) (string, int) {
  455. str := rec[0].(string)
  456. var weight int
  457. if len(rec) > 1 {
  458. switch rec[1].(type) {
  459. case string:
  460. var err error
  461. weight, err = strconv.Atoi(rec[1].(string))
  462. if err != nil {
  463. panic("Error converting weight to integer")
  464. }
  465. case float64:
  466. weight = int(rec[1].(float64))
  467. }
  468. }
  469. return str, weight
  470. }