muxmanager.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. package zones
  2. import (
  3. "crypto/sha256"
  4. "encoding/hex"
  5. "fmt"
  6. "io/ioutil"
  7. "log"
  8. "path"
  9. "strings"
  10. "time"
  11. "github.com/miekg/dns"
  12. )
  13. type RegistrationAPI interface {
  14. Add(string, *Zone)
  15. Remove(string)
  16. }
  17. type MuxManager struct {
  18. reg RegistrationAPI
  19. zonelist ZoneList
  20. path string
  21. lastRead map[string]*zoneReadRecord
  22. }
  23. type NilReg struct{}
  24. func (r *NilReg) Add(string, *Zone) {}
  25. func (r *NilReg) Remove(string) {}
  26. // track when each zone was read last
  27. type zoneReadRecord struct {
  28. time time.Time
  29. hash string
  30. }
  31. func NewMuxManager(path string, reg RegistrationAPI) (*MuxManager, error) {
  32. mm := &MuxManager{
  33. reg: reg,
  34. path: path,
  35. zonelist: make(ZoneList),
  36. lastRead: map[string]*zoneReadRecord{},
  37. }
  38. mm.setupRootZone()
  39. mm.setupPgeodnsZone()
  40. err := mm.reload()
  41. return mm, err
  42. }
  43. func (mm *MuxManager) Run() {
  44. for {
  45. err := mm.reload()
  46. if err != nil {
  47. log.Printf("error reading zones: %s", err)
  48. }
  49. time.Sleep(2 * time.Second)
  50. }
  51. }
  52. // Zones returns the list of currently active zones in the mux manager.
  53. func (mm *MuxManager) Zones() ZoneList {
  54. return mm.zonelist
  55. }
  56. func (mm *MuxManager) reload() error {
  57. dir, err := ioutil.ReadDir(mm.path)
  58. if err != nil {
  59. return fmt.Errorf("could not read '%s': %s", mm.path, err)
  60. }
  61. seenZones := map[string]bool{}
  62. var parseErr error
  63. for _, file := range dir {
  64. fileName := file.Name()
  65. if !strings.HasSuffix(strings.ToLower(fileName), ".json") ||
  66. strings.HasPrefix(path.Base(fileName), ".") ||
  67. file.IsDir() {
  68. continue
  69. }
  70. zoneName := fileName[0:strings.LastIndex(fileName, ".")]
  71. seenZones[zoneName] = true
  72. if _, ok := mm.lastRead[zoneName]; !ok || file.ModTime().After(mm.lastRead[zoneName].time) {
  73. modTime := file.ModTime()
  74. if ok {
  75. log.Printf("Reloading %s\n", fileName)
  76. mm.lastRead[zoneName].time = modTime
  77. } else {
  78. log.Printf("Reading new file %s\n", fileName)
  79. mm.lastRead[zoneName] = &zoneReadRecord{time: modTime}
  80. }
  81. filename := path.Join(mm.path, fileName)
  82. // Check the sha256 of the file has not changed. It's worth an explanation of
  83. // why there isn't a TOCTOU race here. Conceivably after checking whether the
  84. // SHA has changed, the contents then change again before we actually load
  85. // the JSON. This can occur in two situations:
  86. //
  87. // 1. The SHA has not changed when we read the file for the SHA, but then
  88. // changes before we process the JSON
  89. //
  90. // 2. The SHA has changed when we read the file for the SHA, but then changes
  91. // again before we process the JSON
  92. //
  93. // In circumstance (1) we won't reread the file the first time, but the subsequent
  94. // change should alter the mtime again, causing us to reread it. This reflects
  95. // the fact there were actually two changes.
  96. //
  97. // In circumstance (2) we have already reread the file once, and then when the
  98. // contents are changed the mtime changes again
  99. //
  100. // Provided files are replaced atomically, this should be OK. If files are not
  101. // replaced atomically we have other problems (e.g. partial reads).
  102. sha256 := sha256File(filename)
  103. if mm.lastRead[zoneName].hash == sha256 {
  104. log.Printf("Skipping new file %s as hash is unchanged\n", filename)
  105. continue
  106. }
  107. zone := NewZone(zoneName)
  108. err := zone.ReadZoneFile(filename)
  109. if zone == nil || err != nil {
  110. parseErr = fmt.Errorf("Error reading zone '%s': %s", zoneName, err)
  111. log.Println(parseErr.Error())
  112. continue
  113. }
  114. (mm.lastRead[zoneName]).hash = sha256
  115. mm.addHandler(zoneName, zone)
  116. }
  117. }
  118. for zoneName, zone := range mm.zonelist {
  119. if zoneName == "pgeodns" {
  120. continue
  121. }
  122. if ok, _ := seenZones[zoneName]; ok {
  123. continue
  124. }
  125. log.Println("Removing zone", zone.Origin)
  126. zone.Close()
  127. mm.removeHandler(zoneName)
  128. }
  129. return parseErr
  130. }
  131. func (mm *MuxManager) addHandler(name string, zone *Zone) {
  132. oldZone := mm.zonelist[name]
  133. zone.SetupMetrics(oldZone)
  134. zone.setupHealthTests()
  135. mm.zonelist[name] = zone
  136. mm.reg.Add(name, zone)
  137. }
  138. func (mm *MuxManager) removeHandler(name string) {
  139. delete(mm.lastRead, name)
  140. delete(mm.zonelist, name)
  141. mm.reg.Remove(name)
  142. }
  143. func (mm *MuxManager) setupPgeodnsZone() {
  144. zoneName := "pgeodns"
  145. zone := NewZone(zoneName)
  146. label := new(Label)
  147. label.Records = make(map[uint16]Records)
  148. label.Weight = make(map[uint16]int)
  149. zone.Labels[""] = label
  150. zone.AddSOA()
  151. mm.addHandler(zoneName, zone)
  152. }
  153. func (mm *MuxManager) setupRootZone() {
  154. dns.HandleFunc(".", func(w dns.ResponseWriter, r *dns.Msg) {
  155. m := new(dns.Msg)
  156. m.SetRcode(r, dns.RcodeRefused)
  157. w.WriteMsg(m)
  158. })
  159. }
  160. func sha256File(fn string) string {
  161. data, err := ioutil.ReadFile(fn)
  162. if err != nil {
  163. return ""
  164. }
  165. hasher := sha256.New()
  166. hasher.Write(data)
  167. return hex.EncodeToString(hasher.Sum(nil))
  168. }