Skip to content

Commit

Permalink
loads configuration file dynamically (#228)
Browse files Browse the repository at this point in the history
* refactor the ip helper

* refactor the ip helper

* refactor the ip helper

* refactor the ip helper

* refactor the dns manager

* add file watcher

* reset urls & index

* set config path

* rename

* refactor the monitor

* reload the manager
  • Loading branch information
TimothyYe authored Feb 4, 2024
1 parent 7767024 commit 80feebf
Show file tree
Hide file tree
Showing 7 changed files with 182 additions and 38 deletions.
23 changes: 11 additions & 12 deletions cmd/godns/godns.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"os"
"os/signal"
"syscall"
"time"

"github.com/TimothyYe/godns/internal/manager"
"github.com/TimothyYe/godns/internal/settings"
Expand All @@ -20,9 +21,9 @@ const (
)

var (
configuration settings.Settings
optConf = flag.String("c", "./config.json", "Specify a config file")
optHelp = flag.Bool("h", false, "Show help")
config settings.Settings
optConf = flag.String("c", "./config.json", "Specify a config file")
optHelp = flag.Bool("h", false, "Show help")

// Version is current version of GoDNS.
Version = "0.1"
Expand All @@ -33,7 +34,6 @@ func init() {
}

func main() {

flag.Parse()
if *optHelp {
color.Cyan(utils.Logo, Version)
Expand All @@ -49,26 +49,23 @@ func main() {
configPath = os.Getenv(configEnv)
}

// Load settings from configurations file
if err := settings.LoadSettings(configPath, &configuration); err != nil {
// Load settings from configs file
if err := settings.LoadSettings(configPath, &config); err != nil {
log.Fatal(err)
}

if configuration.DebugInfo {
if config.DebugInfo {
log.SetLevel(log.DebugLevel)
} else {
log.SetLevel(log.InfoLevel)
}

if err := utils.CheckSettings(&configuration); err != nil {
if err := utils.CheckSettings(&config); err != nil {
log.Fatal("Invalid settings: ", err.Error())
}

// Create DNS manager
dnsManager := &manager.DNSManager{}
if err := dnsManager.SetConfiguration(&configuration).Build(); err != nil {
log.Fatal(err)
}
dnsManager := manager.GetDNSManager(configPath, &config)

// Run DNS manager
log.Info("GoDNS started, starting the DNS manager...")
Expand All @@ -82,5 +79,7 @@ func main() {
<-c
log.Info("GoDNS is terminated, stopping the DNS manager...")
dnsManager.Stop()
// wait for the goroutines to exit
time.Sleep(200 * time.Millisecond)
log.Info("GoDNS is stopped, bye!")
}
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ require (
gopkg.in/yaml.v3 v3.0.1
)

require github.com/fsnotify/fsnotify v1.7.0

require (
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
github.com/go-resty/resty/v2 v2.7.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY=
github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
Expand Down
11 changes: 10 additions & 1 deletion internal/handler/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,26 @@ var (
)

type Handler struct {
ctx context.Context
Configuration *settings.Settings
dnsProvider provider.IDNSProvider
notificationManager notification.INotificationManager
ipManager *lib.IPHelper
cachedIP string
}

func (handler *Handler) SetContext(ctx context.Context) {
handler.ctx = ctx
}

func (handler *Handler) SetConfiguration(conf *settings.Settings) {
handler.Configuration = conf
handler.notificationManager = notification.GetNotificationManager(handler.Configuration)
handler.ipManager = lib.NewIPHelper(handler.Configuration)
handler.ipManager = lib.GetIPHelperInstance(handler.Configuration)
}

func (handler *Handler) Init() {
handler.ipManager.UpdateConfiguration(handler.Configuration)
}

func (handler *Handler) SetProvider(provider provider.IDNSProvider) {
Expand Down
122 changes: 116 additions & 6 deletions internal/manager/dns_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,15 @@ package manager
import (
"context"
"os"
"path/filepath"
"sync"
"time"

"github.com/TimothyYe/godns/internal/handler"
"github.com/TimothyYe/godns/internal/provider"
"github.com/TimothyYe/godns/internal/settings"
"github.com/TimothyYe/godns/internal/utils"
"github.com/fsnotify/fsnotify"
log "github.com/sirupsen/logrus"
)

Expand All @@ -16,28 +21,116 @@ type DNSManager struct {
provider provider.IDNSProvider
ctx context.Context
cancel context.CancelFunc
watcher *fsnotify.Watcher
configPath string
}

func (manager *DNSManager) SetConfiguration(conf *settings.Settings) *DNSManager {
var (
managerInstance *DNSManager
managerOnce sync.Once
)

func getFileName(configPath string) string {
// get the file name from the path
// e.g. /etc/godns/config.json -> config.json
return filepath.Base(configPath)
}

func (manager *DNSManager) setConfig(conf *settings.Settings) {
manager.configuration = conf
return manager
}

func (manager *DNSManager) Build() error {
func GetDNSManager(cfgPath string, conf *settings.Settings) *DNSManager {
managerOnce.Do(func() {
managerInstance = &DNSManager{}
managerInstance.configPath = cfgPath
managerInstance.configuration = conf
if err := managerInstance.initManager(); err != nil {
log.Fatalf("Error during DNS manager initialization: %s", err)
}
})

return managerInstance
}

func (manager *DNSManager) startMonitor(ctx context.Context) {
// Start listening for events.
go func() {
for {
select {
case <-ctx.Done():
log.Debug("Shutting down the old file watcher...")
return
case event, ok := <-manager.watcher.Events:
if !ok {
return
}
if event.Has(fsnotify.Write) {
log.Debug("modified file:", event.Name)
log.Debug("Reloading configuration...")
// reload the configuration
// read the file and update the configuration
configFile := getFileName(manager.configPath)
if event.Name == configFile {
// Load settings from configs file
newConfig := &settings.Settings{}
if err := settings.LoadSettings(manager.configPath, newConfig); err != nil {
log.Errorf("Failed to reload configuration: %s", err)
continue
}

// validate the new configuration
if err := utils.CheckSettings(newConfig); err != nil {
log.Errorf("Failed to validate the new configuration: %s", err)
continue
}

manager.setConfig(newConfig)
manager.Restart()
}
}
case err, ok := <-manager.watcher.Errors:
if !ok {
return
}
log.Println("error:", err)
}
}
}()

// Add a path.
if err := manager.watcher.Add(manager.configPath); err != nil {
log.Fatal(err)
}
}

func (manager *DNSManager) initManager() error {
log.Infof("Creating DNS handler with provider: %s", manager.configuration.Provider)
dnsProvider, err := provider.GetProvider(manager.configuration)
if err != nil {
return err
}

ctx, cancel := context.WithCancel(context.Background())
manager.ctx = ctx
manager.cancel = cancel

manager.provider = dnsProvider
manager.handler = &handler.Handler{}
manager.handler.SetContext(manager.ctx)
manager.handler.SetConfiguration(manager.configuration)
manager.handler.SetProvider(manager.provider)
manager.handler.Init()

ctx, cancel := context.WithCancel(context.Background())
manager.ctx = ctx
manager.cancel = cancel
// create a new file watcher
log.Debug("Creating the new file watcher...")
managerInstance.watcher, err = fsnotify.NewWatcher()
if err != nil {
log.Fatal(err)
}

// monitor the configuration file changes
managerInstance.startMonitor(ctx)
return nil
}

Expand All @@ -64,4 +157,21 @@ func (manager *DNSManager) Run() {

func (manager *DNSManager) Stop() {
manager.cancel()
manager.watcher.Close()
}

func (manager *DNSManager) Restart() {
log.Info("Restarting DNS manager...")
manager.Stop()

// wait for the goroutines to exit
time.Sleep(200 * time.Millisecond)

// re-init the manager
if err := manager.initManager(); err != nil {
log.Fatalf("Error during DNS manager restarting: %s", err)
}

manager.Run()
log.Info("DNS manager restarted successfully")
}
58 changes: 40 additions & 18 deletions pkg/lib/ip_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,44 +27,63 @@ type IPHelper struct {
idx int64
}

func NewIPHelper(conf *settings.Settings) *IPHelper {
manager := &IPHelper{
configuration: conf,
idx: -1,
}
var (
helperInstance *IPHelper
helperOnce sync.Once
)

func (helper *IPHelper) UpdateConfiguration(conf *settings.Settings) {
helper.mutex.Lock()
defer helper.mutex.Unlock()

// clear urls
helper.reqURLs = helper.reqURLs[:0]
// reset the index
helper.idx = -1

if conf.IPType == "" || strings.ToUpper(conf.IPType) == utils.IPV4 {
// filter empty urls
for _, url := range conf.IPUrls {
if url != "" {
manager.reqURLs = append(manager.reqURLs, url)
helper.reqURLs = append(helper.reqURLs, url)
}
}

if conf.IPUrl != "" {
manager.reqURLs = append(manager.reqURLs, conf.IPUrl)
helper.reqURLs = append(helper.reqURLs, conf.IPUrl)
}
} else {
// filter empty urls
for _, url := range conf.IPV6Urls {
if url != "" {
manager.reqURLs = append(manager.reqURLs, url)
helper.reqURLs = append(helper.reqURLs, url)
}
}

if conf.IPV6Url != "" {
manager.reqURLs = append(manager.reqURLs, conf.IPV6Url)
helper.reqURLs = append(helper.reqURLs, conf.IPV6Url)
}
}

SafeGo(func() {
for {
manager.getCurrentIP()
time.Sleep(time.Second * time.Duration(conf.Interval))
log.Debugf("Update ip helper configuration, urls: %v", helper.reqURLs)
}

func GetIPHelperInstance(conf *settings.Settings) *IPHelper {
helperOnce.Do(func() {
helperInstance = &IPHelper{
configuration: conf,
idx: -1,
}

SafeGo(func() {
for {
helperInstance.getCurrentIP()
time.Sleep(time.Second * time.Duration(conf.Interval))
}
})
})

return manager
return helperInstance
}

func (helper *IPHelper) GetCurrentIP() string {
Expand All @@ -88,6 +107,9 @@ func (helper *IPHelper) setCurrentIP(ip string) {

func (helper *IPHelper) getNext() string {
newIdx := atomic.AddInt64(&helper.idx, 1)

helper.mutex.RLock()
defer helper.mutex.RUnlock()
newIdx %= int64(len(helper.reqURLs))
next := helper.reqURLs[newIdx]
return next
Expand All @@ -97,13 +119,13 @@ func (helper *IPHelper) getNext() string {
func (helper *IPHelper) getIPFromInterface() (string, error) {
ifaces, err := net.InterfaceByName(helper.configuration.IPInterface)
if err != nil {
log.Error("can't get network device "+helper.configuration.IPInterface+":", err)
log.Error("Can't get network device "+helper.configuration.IPInterface+":", err)
return "", err
}

addrs, err := ifaces.Addrs()
if err != nil {
log.Error("can't get address from "+helper.configuration.IPInterface+":", err)
log.Error("Can't get address from "+helper.configuration.IPInterface+":", err)
return "", err
}

Expand Down Expand Up @@ -134,7 +156,7 @@ func (helper *IPHelper) getIPFromInterface() (string, error) {
}

if ip.String() != "" {
log.Debugf("get ip success from network intereface by: %s, IP: %s", helper.configuration.IPInterface, ip.String())
log.Debugf("Get ip success from network intereface by: %s, IP: %s", helper.configuration.IPInterface, ip.String())
return ip.String(), nil
}
}
Expand Down Expand Up @@ -206,7 +228,7 @@ func (helper *IPHelper) getIPOnline() string {
log.Error(fmt.Sprintf("request:%v failed to get online IP", reqURL))
continue
}
log.Debugf("get ip success by: %s, online IP: %s", reqURL, onlineIP)
log.Debugf("Get ip success by: %s, online IP: %s", reqURL, onlineIP)

err = response.Body.Close()
if err != nil {
Expand Down
Loading

0 comments on commit 80feebf

Please sign in to comment.