dns.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575
  1. package controller
  2. import (
  3. "context"
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. "net/http"
  8. "strings"
  9. "time"
  10. "github.com/google/uuid"
  11. "github.com/gorilla/mux"
  12. "github.com/gravitl/netmaker/database"
  13. "github.com/gravitl/netmaker/db"
  14. "github.com/gravitl/netmaker/logger"
  15. "github.com/gravitl/netmaker/logic"
  16. "github.com/gravitl/netmaker/models"
  17. "github.com/gravitl/netmaker/mq"
  18. "github.com/gravitl/netmaker/schema"
  19. "github.com/gravitl/netmaker/servercfg"
  20. "gorm.io/datatypes"
  21. )
  22. func dnsHandlers(r *mux.Router) {
  23. r.HandleFunc("/api/dns", logic.SecurityCheck(true, http.HandlerFunc(getAllDNS))).
  24. Methods(http.MethodGet)
  25. r.HandleFunc("/api/dns/adm/{network}/nodes", logic.SecurityCheck(true, http.HandlerFunc(getNodeDNS))).
  26. Methods(http.MethodGet)
  27. r.HandleFunc("/api/dns/adm/{network}/custom", logic.SecurityCheck(true, http.HandlerFunc(getCustomDNS))).
  28. Methods(http.MethodGet)
  29. r.HandleFunc("/api/dns/adm/{network}", logic.SecurityCheck(true, http.HandlerFunc(getDNS))).
  30. Methods(http.MethodGet)
  31. r.HandleFunc("/api/dns/adm/{network}/sync", logic.SecurityCheck(true, http.HandlerFunc(syncDNS))).
  32. Methods(http.MethodPost)
  33. r.HandleFunc("/api/dns/{network}", logic.SecurityCheck(true, http.HandlerFunc(createDNS))).
  34. Methods(http.MethodPost)
  35. r.HandleFunc("/api/dns/adm/pushdns", logic.SecurityCheck(true, http.HandlerFunc(pushDNS))).
  36. Methods(http.MethodPost)
  37. r.HandleFunc("/api/dns/{network}/{domain}", logic.SecurityCheck(true, http.HandlerFunc(deleteDNS))).
  38. Methods(http.MethodDelete)
  39. r.HandleFunc("/api/v1/nameserver", logic.SecurityCheck(true, http.HandlerFunc(createNs))).Methods(http.MethodPost)
  40. r.HandleFunc("/api/v1/nameserver", logic.SecurityCheck(true, http.HandlerFunc(listNs))).Methods(http.MethodGet)
  41. r.HandleFunc("/api/v1/nameserver", logic.SecurityCheck(true, http.HandlerFunc(updateNs))).Methods(http.MethodPut)
  42. r.HandleFunc("/api/v1/nameserver", logic.SecurityCheck(true, http.HandlerFunc(deleteNs))).Methods(http.MethodDelete)
  43. r.HandleFunc("/api/v1/nameserver/global", logic.SecurityCheck(true, http.HandlerFunc(getGlobalNs))).Methods(http.MethodGet)
  44. }
  45. // @Summary List Global Nameservers
  46. // @Router /api/v1/nameserver/global [get]
  47. // @Tags Auth
  48. // @Accept json
  49. // @Param query network string
  50. // @Success 200 {object} models.SuccessResponse
  51. // @Failure 400 {object} models.ErrorResponse
  52. // @Failure 401 {object} models.ErrorResponse
  53. // @Failure 500 {object} models.ErrorResponse
  54. func getGlobalNs(w http.ResponseWriter, r *http.Request) {
  55. logic.ReturnSuccessResponseWithJson(w, r, logic.GlobalNsList, "fetched nameservers")
  56. }
  57. // @Summary Create Nameserver
  58. // @Router /api/v1/nameserver [post]
  59. // @Tags DNS
  60. // @Accept json
  61. // @Param body body models.NameserverReq
  62. // @Success 200 {object} models.SuccessResponse
  63. // @Failure 400 {object} models.ErrorResponse
  64. // @Failure 401 {object} models.ErrorResponse
  65. // @Failure 500 {object} models.ErrorResponse
  66. func createNs(w http.ResponseWriter, r *http.Request) {
  67. var req schema.Nameserver
  68. err := json.NewDecoder(r.Body).Decode(&req)
  69. if err != nil {
  70. logger.Log(0, "error decoding request body: ",
  71. err.Error())
  72. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
  73. return
  74. }
  75. if err := logic.ValidateNameserverReq(req); err != nil {
  76. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
  77. return
  78. }
  79. if req.Tags == nil {
  80. req.Tags = make(datatypes.JSONMap)
  81. }
  82. if gNs, ok := logic.GlobalNsList[req.Name]; ok {
  83. req.Servers = gNs.IPs
  84. }
  85. ns := schema.Nameserver{
  86. ID: uuid.New().String(),
  87. Name: req.Name,
  88. NetworkID: req.NetworkID,
  89. Description: req.Description,
  90. MatchDomain: req.MatchDomain,
  91. Servers: req.Servers,
  92. Tags: req.Tags,
  93. Status: true,
  94. CreatedBy: r.Header.Get("user"),
  95. CreatedAt: time.Now().UTC(),
  96. }
  97. err = ns.Create(db.WithContext(r.Context()))
  98. if err != nil {
  99. logic.ReturnErrorResponse(
  100. w,
  101. r,
  102. logic.FormatError(errors.New("error creating nameserver "+err.Error()), logic.Internal),
  103. )
  104. return
  105. }
  106. logic.LogEvent(&models.Event{
  107. Action: models.Create,
  108. Source: models.Subject{
  109. ID: r.Header.Get("user"),
  110. Name: r.Header.Get("user"),
  111. Type: models.UserSub,
  112. },
  113. TriggeredBy: r.Header.Get("user"),
  114. Target: models.Subject{
  115. ID: ns.ID,
  116. Name: ns.Name,
  117. Type: models.NameserverSub,
  118. },
  119. NetworkID: models.NetworkID(ns.NetworkID),
  120. Origin: models.Dashboard,
  121. })
  122. go mq.PublishPeerUpdate(false)
  123. logic.ReturnSuccessResponseWithJson(w, r, ns, "created nameserver")
  124. }
  125. // @Summary List Nameservers
  126. // @Router /api/v1/nameserver [get]
  127. // @Tags Auth
  128. // @Accept json
  129. // @Param query network string
  130. // @Success 200 {object} models.SuccessResponse
  131. // @Failure 400 {object} models.ErrorResponse
  132. // @Failure 401 {object} models.ErrorResponse
  133. // @Failure 500 {object} models.ErrorResponse
  134. func listNs(w http.ResponseWriter, r *http.Request) {
  135. network := r.URL.Query().Get("network")
  136. if network == "" {
  137. logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("network is required"), "badrequest"))
  138. return
  139. }
  140. ns := schema.Nameserver{NetworkID: network}
  141. list, err := ns.ListByNetwork(db.WithContext(r.Context()))
  142. if err != nil {
  143. logic.ReturnErrorResponse(
  144. w,
  145. r,
  146. logic.FormatError(errors.New("error listing nameservers "+err.Error()), "internal"),
  147. )
  148. return
  149. }
  150. logic.ReturnSuccessResponseWithJson(w, r, list, "fetched nameservers")
  151. }
  152. // @Summary Update Nameserver
  153. // @Router /api/v1/nameserver [put]
  154. // @Tags Auth
  155. // @Accept json
  156. // @Param body body models.NameserverReq
  157. // @Success 200 {object} models.SuccessResponse
  158. // @Failure 400 {object} models.ErrorResponse
  159. // @Failure 401 {object} models.ErrorResponse
  160. // @Failure 500 {object} models.ErrorResponse
  161. func updateNs(w http.ResponseWriter, r *http.Request) {
  162. var updateNs schema.Nameserver
  163. err := json.NewDecoder(r.Body).Decode(&updateNs)
  164. if err != nil {
  165. logger.Log(0, "error decoding request body: ",
  166. err.Error())
  167. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
  168. return
  169. }
  170. if err := logic.ValidateUpdateNameserverReq(updateNs); err != nil {
  171. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
  172. return
  173. }
  174. if updateNs.Tags == nil {
  175. updateNs.Tags = make(datatypes.JSONMap)
  176. }
  177. ns := schema.Nameserver{ID: updateNs.ID}
  178. err = ns.Get(db.WithContext(r.Context()))
  179. if err != nil {
  180. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
  181. return
  182. }
  183. var updateStatus bool
  184. if updateNs.Status != ns.Status {
  185. updateStatus = true
  186. }
  187. event := &models.Event{
  188. Action: models.Update,
  189. Source: models.Subject{
  190. ID: r.Header.Get("user"),
  191. Name: r.Header.Get("user"),
  192. Type: models.UserSub,
  193. },
  194. TriggeredBy: r.Header.Get("user"),
  195. Target: models.Subject{
  196. ID: ns.ID,
  197. Name: updateNs.Name,
  198. Type: models.NameserverSub,
  199. },
  200. Diff: models.Diff{
  201. Old: ns,
  202. New: updateNs,
  203. },
  204. NetworkID: models.NetworkID(ns.NetworkID),
  205. Origin: models.Dashboard,
  206. }
  207. ns.Servers = updateNs.Servers
  208. ns.Tags = updateNs.Tags
  209. ns.Description = updateNs.Description
  210. ns.Name = updateNs.Name
  211. ns.Status = updateNs.Status
  212. ns.UpdatedAt = time.Now().UTC()
  213. err = ns.Update(db.WithContext(context.TODO()))
  214. if err != nil {
  215. logic.ReturnErrorResponse(
  216. w,
  217. r,
  218. logic.FormatError(errors.New("error creating egress resource"+err.Error()), "internal"),
  219. )
  220. return
  221. }
  222. if updateStatus {
  223. ns.UpdateStatus(db.WithContext(context.TODO()))
  224. }
  225. logic.LogEvent(event)
  226. go mq.PublishPeerUpdate(false)
  227. logic.ReturnSuccessResponseWithJson(w, r, ns, "updated nameserver")
  228. }
  229. // @Summary Delete Nameserver Resource
  230. // @Router /api/v1/nameserver [delete]
  231. // @Tags Auth
  232. // @Accept json
  233. // @Param body body models.Egress
  234. // @Success 200 {object} models.SuccessResponse
  235. // @Failure 400 {object} models.ErrorResponse
  236. // @Failure 401 {object} models.ErrorResponse
  237. // @Failure 500 {object} models.ErrorResponse
  238. func deleteNs(w http.ResponseWriter, r *http.Request) {
  239. id := r.URL.Query().Get("id")
  240. if id == "" {
  241. logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("id is required"), "badrequest"))
  242. return
  243. }
  244. ns := schema.Nameserver{ID: id}
  245. err := ns.Get(db.WithContext(r.Context()))
  246. if err != nil {
  247. logic.ReturnErrorResponse(w, r, logic.FormatError(err, logic.BadReq))
  248. return
  249. }
  250. err = ns.Delete(db.WithContext(r.Context()))
  251. if err != nil {
  252. logic.ReturnErrorResponse(w, r, logic.FormatError(err, logic.Internal))
  253. return
  254. }
  255. logic.LogEvent(&models.Event{
  256. Action: models.Delete,
  257. Source: models.Subject{
  258. ID: r.Header.Get("user"),
  259. Name: r.Header.Get("user"),
  260. Type: models.UserSub,
  261. },
  262. TriggeredBy: r.Header.Get("user"),
  263. Target: models.Subject{
  264. ID: ns.ID,
  265. Name: ns.Name,
  266. Type: models.NameserverSub,
  267. },
  268. NetworkID: models.NetworkID(ns.NetworkID),
  269. Origin: models.Dashboard,
  270. })
  271. go mq.PublishPeerUpdate(false)
  272. logic.ReturnSuccessResponseWithJson(w, r, nil, "deleted nameserver resource")
  273. }
  274. // @Summary Gets node DNS entries associated with a network
  275. // @Router /api/dns/{network} [get]
  276. // @Tags DNS
  277. // @Accept json
  278. // @Param network path string true "Network identifier"
  279. // @Success 200 {array} models.DNSEntry
  280. // @Failure 500 {object} models.ErrorResponse
  281. func getNodeDNS(w http.ResponseWriter, r *http.Request) {
  282. w.Header().Set("Content-Type", "application/json")
  283. var dns []models.DNSEntry
  284. var params = mux.Vars(r)
  285. network := params["network"]
  286. dns, err := logic.GetNodeDNS(network)
  287. if err != nil {
  288. logger.Log(0, r.Header.Get("user"),
  289. fmt.Sprintf("failed to get node DNS entries for network [%s]: %v", network, err))
  290. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
  291. return
  292. }
  293. w.WriteHeader(http.StatusOK)
  294. json.NewEncoder(w).Encode(dns)
  295. }
  296. // @Summary Get all DNS entries
  297. // @Router /api/dns [get]
  298. // @Tags DNS
  299. // @Accept json
  300. // @Success 200 {array} models.DNSEntry
  301. // @Failure 500 {object} models.ErrorResponse
  302. func getAllDNS(w http.ResponseWriter, r *http.Request) {
  303. w.Header().Set("Content-Type", "application/json")
  304. dns, err := logic.GetAllDNS()
  305. if err != nil {
  306. logger.Log(0, r.Header.Get("user"), "failed to get all DNS entries: ", err.Error())
  307. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
  308. return
  309. }
  310. logic.SortDNSEntrys(dns[:])
  311. w.WriteHeader(http.StatusOK)
  312. json.NewEncoder(w).Encode(dns)
  313. }
  314. // @Summary Gets custom DNS entries associated with a network
  315. // @Router /api/dns/adm/{network}/custom [get]
  316. // @Tags DNS
  317. // @Accept json
  318. // @Param network path string true "Network identifier"
  319. // @Success 200 {array} models.DNSEntry
  320. // @Failure 500 {object} models.ErrorResponse
  321. func getCustomDNS(w http.ResponseWriter, r *http.Request) {
  322. w.Header().Set("Content-Type", "application/json")
  323. var dns []models.DNSEntry
  324. var params = mux.Vars(r)
  325. network := params["network"]
  326. dns, err := logic.GetCustomDNS(network)
  327. if err != nil {
  328. logger.Log(
  329. 0,
  330. r.Header.Get("user"),
  331. fmt.Sprintf(
  332. "failed to get custom DNS entries for network [%s]: %v",
  333. network,
  334. err.Error(),
  335. ),
  336. )
  337. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
  338. return
  339. }
  340. w.WriteHeader(http.StatusOK)
  341. json.NewEncoder(w).Encode(dns)
  342. }
  343. // @Summary Get all DNS entries associated with the network
  344. // @Router /api/dns/adm/{network} [get]
  345. // @Tags DNS
  346. // @Accept json
  347. // @Param network path string true "Network identifier"
  348. // @Success 200 {array} models.DNSEntry
  349. // @Failure 500 {object} models.ErrorResponse
  350. func getDNS(w http.ResponseWriter, r *http.Request) {
  351. w.Header().Set("Content-Type", "application/json")
  352. var dns []models.DNSEntry
  353. var params = mux.Vars(r)
  354. network := params["network"]
  355. dns, err := logic.GetDNS(network)
  356. if err != nil {
  357. logger.Log(0, r.Header.Get("user"),
  358. fmt.Sprintf("failed to get all DNS entries for network [%s]: %v", network, err.Error()))
  359. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
  360. return
  361. }
  362. w.WriteHeader(http.StatusOK)
  363. json.NewEncoder(w).Encode(dns)
  364. }
  365. // @Summary Create a new DNS entry
  366. // @Router /api/dns/adm/{network} [post]
  367. // @Tags DNS
  368. // @Accept json
  369. // @Param network path string true "Network identifier"
  370. // @Param body body models.DNSEntry true "DNS entry details"
  371. // @Success 200 {object} models.DNSEntry
  372. // @Failure 400 {object} models.ErrorResponse
  373. // @Failure 500 {object} models.ErrorResponse
  374. func createDNS(w http.ResponseWriter, r *http.Request) {
  375. w.Header().Set("Content-Type", "application/json")
  376. var entry models.DNSEntry
  377. var params = mux.Vars(r)
  378. netID := params["network"]
  379. _ = json.NewDecoder(r.Body).Decode(&entry)
  380. entry.Network = params["network"]
  381. err := logic.ValidateDNSCreate(entry)
  382. if err != nil {
  383. logger.Log(0, r.Header.Get("user"),
  384. fmt.Sprintf("invalid DNS entry %+v: %v", entry, err))
  385. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
  386. return
  387. }
  388. // check if default domain is appended if not append
  389. if logic.GetDefaultDomain() != "" &&
  390. !strings.HasSuffix(entry.Name, logic.GetDefaultDomain()) {
  391. entry.Name += "." + logic.GetDefaultDomain()
  392. }
  393. entry, err = logic.CreateDNS(entry)
  394. if err != nil {
  395. logger.Log(0, r.Header.Get("user"),
  396. fmt.Sprintf("Failed to create DNS entry %+v: %v", entry, err))
  397. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
  398. return
  399. }
  400. if servercfg.IsDNSMode() {
  401. err = logic.SetDNS()
  402. if err != nil {
  403. logger.Log(0, r.Header.Get("user"),
  404. fmt.Sprintf("Failed to set DNS entries on file: %v", err))
  405. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
  406. return
  407. }
  408. }
  409. if logic.GetManageDNS() {
  410. mq.SendDNSSyncByNetwork(netID)
  411. }
  412. logger.Log(1, "new DNS record added:", entry.Name)
  413. logger.Log(2, r.Header.Get("user"),
  414. fmt.Sprintf("DNS entry is set: %+v", entry))
  415. w.WriteHeader(http.StatusOK)
  416. json.NewEncoder(w).Encode(entry)
  417. }
  418. // @Summary Delete a DNS entry
  419. // @Router /api/dns/{network}/{domain} [delete]
  420. // @Tags DNS
  421. // @Accept json
  422. // @Param network path string true "Network identifier"
  423. // @Param domain path string true "Domain Name"
  424. // @Success 200 {array} models.DNSEntry
  425. // @Failure 500 {object} models.ErrorResponse
  426. func deleteDNS(w http.ResponseWriter, r *http.Request) {
  427. // Set header
  428. w.Header().Set("Content-Type", "application/json")
  429. // get params
  430. var params = mux.Vars(r)
  431. netID := params["network"]
  432. entrytext := params["domain"] + "." + params["network"]
  433. err := logic.DeleteDNS(params["domain"], params["network"])
  434. if err != nil {
  435. logger.Log(0, "failed to delete dns entry: ", entrytext)
  436. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
  437. return
  438. }
  439. logger.Log(1, "deleted dns entry: ", entrytext)
  440. if servercfg.IsDNSMode() {
  441. err = logic.SetDNS()
  442. if err != nil {
  443. logger.Log(0, r.Header.Get("user"),
  444. fmt.Sprintf("Failed to set DNS entries on file: %v", err))
  445. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
  446. return
  447. }
  448. }
  449. if logic.GetManageDNS() {
  450. mq.SendDNSSyncByNetwork(netID)
  451. }
  452. json.NewEncoder(w).Encode(entrytext + " deleted.")
  453. }
  454. // GetDNSEntry - gets a DNS entry
  455. func GetDNSEntry(domain string, network string) (models.DNSEntry, error) {
  456. var entry models.DNSEntry
  457. key, err := logic.GetRecordKey(domain, network)
  458. if err != nil {
  459. return entry, err
  460. }
  461. record, err := database.FetchRecord(database.DNS_TABLE_NAME, key)
  462. if err != nil {
  463. return entry, err
  464. }
  465. err = json.Unmarshal([]byte(record), &entry)
  466. return entry, err
  467. }
  468. // @Summary Push DNS entries to nameserver
  469. // @Router /api/dns/adm/pushdns [post]
  470. // @Tags DNS
  471. // @Accept json
  472. // @Success 200 {string} string "DNS Pushed to CoreDNS"
  473. // @Failure 400 {object} models.ErrorResponse
  474. // @Failure 500 {object} models.ErrorResponse
  475. func pushDNS(w http.ResponseWriter, r *http.Request) {
  476. // Set header
  477. w.Header().Set("Content-Type", "application/json")
  478. if !servercfg.IsDNSMode() {
  479. logic.ReturnErrorResponse(
  480. w,
  481. r,
  482. logic.FormatError(errors.New("DNS Mode is set to off"), "badrequest"),
  483. )
  484. return
  485. }
  486. err := logic.SetDNS()
  487. if err != nil {
  488. logger.Log(0, r.Header.Get("user"),
  489. fmt.Sprintf("Failed to set DNS entries on file: %v", err))
  490. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
  491. return
  492. }
  493. logger.Log(1, r.Header.Get("user"), "pushed DNS updates to nameserver")
  494. json.NewEncoder(w).Encode("DNS Pushed to CoreDNS")
  495. }
  496. // @Summary Sync DNS entries for a given network
  497. // @Router /api/dns/adm/{network}/sync [post]
  498. // @Tags DNS
  499. // @Accept json
  500. // @Success 200 {string} string "DNS Sync completed successfully"
  501. // @Failure 400 {object} models.ErrorResponse
  502. // @Failure 500 {object} models.ErrorResponse
  503. func syncDNS(w http.ResponseWriter, r *http.Request) {
  504. // Set header
  505. w.Header().Set("Content-Type", "application/json")
  506. if !logic.GetManageDNS() {
  507. logic.ReturnErrorResponse(
  508. w,
  509. r,
  510. logic.FormatError(errors.New("manage DNS is set to false"), "badrequest"),
  511. )
  512. return
  513. }
  514. var params = mux.Vars(r)
  515. netID := params["network"]
  516. k, err := logic.GetDNS(netID)
  517. if err == nil && len(k) > 0 {
  518. err = mq.PushSyncDNS(k)
  519. }
  520. if err != nil {
  521. logger.Log(0, r.Header.Get("user"),
  522. fmt.Sprintf("Failed to Sync DNS entries to network %s: %v", netID, err))
  523. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
  524. return
  525. }
  526. logger.Log(1, r.Header.Get("user"), "DNS Sync complelted successfully")
  527. json.NewEncoder(w).Encode("DNS Sync completed successfully")
  528. }