a recursive dns resolver
at main 4.0 kB view raw
1package config 2 3import ( 4 "fmt" 5 "os" 6 "slices" 7 "time" 8 9 "github.com/BurntSushi/toml" 10) 11 12type ServerConfig struct { 13 Address string `toml:"address"` 14 Port int `toml:"port"` 15 RootHintsFile string `toml:"root_hints_file"` 16 UDPSize int `toml:"udp_payload_size"` 17} 18 19type LoggingConfig struct { 20 Output string `toml:"output"` 21 FilePath string `toml:"file_path"` 22 Level string `toml:"level"` 23} 24 25type RatelimitConfig struct { 26 Rate int `toml:"rate"` 27 Burst int `toml:"burst"` 28 Window duration `toml:"window_seconds"` 29 ExpirationTime duration `toml:"cleanup_seconds"` 30} 31 32type MetricsConfig struct { 33 DSN string `toml:"dsn"` 34 BatchSize int `toml:"batch_size"` 35 FlushInterval duration `toml:"flush_interval"` 36 RetentionPeriod duration `toml:"retention_period"` 37} 38 39type AdvancedConfig struct { 40 QueryTimeout duration `toml:"query_timeout"` 41 ReadTimeout duration `toml:"read_timeout"` 42 WriteTimeout duration `toml:"write_timeout"` 43} 44 45type Config struct { 46 Server ServerConfig `toml:"server"` 47 Logging LoggingConfig `toml:"logging"` 48 Ratelimit RatelimitConfig `toml:"ratelimit"` 49 Metrics MetricsConfig `toml:"metrics"` 50 Advanced AdvancedConfig `toml:"advanced"` 51} 52 53type duration struct { 54 time.Duration 55} 56 57func (d *duration) UnmarshalText(text []byte) error { 58 var err error 59 d.Duration, err = time.ParseDuration(string(text)) 60 if err != nil { 61 var seconds int64 62 seconds, err = parseInt64(string(text)) 63 if err == nil { 64 d.Duration = time.Duration(seconds) * time.Second 65 return nil 66 } 67 return fmt.Errorf("invalid duration format: %w", err) 68 } 69 return nil 70} 71 72func parseInt64(s string) (int64, error) { 73 var i int64 74 _, err := fmt.Sscan(s, &i) 75 return i, err 76} 77 78func LoadConfig(path string) (Config, error) { 79 cfg := Config{} 80 cfg.Server.Address = "127.0.0.1" 81 cfg.Server.Port = 53 82 cfg.Server.RootHintsFile = "/etc/alky/root.hints" 83 cfg.Server.UDPSize = 512 84 85 cfg.Logging.Output = "stdout" 86 cfg.Logging.Level = "info" 87 88 cfg.Ratelimit.Rate = 100 89 cfg.Ratelimit.Burst = 200 90 cfg.Ratelimit.Window.Duration = 1 * time.Second 91 cfg.Ratelimit.ExpirationTime.Duration = 1 * time.Minute 92 93 cfg.Metrics.DSN = "clickhouse://localhost:9000/default" 94 cfg.Metrics.BatchSize = 1000 95 cfg.Metrics.FlushInterval.Duration = 10 * time.Second 96 cfg.Metrics.RetentionPeriod.Duration = 30 * 24 * time.Hour 97 98 cfg.Advanced.QueryTimeout.Duration = 5 * time.Second 99 cfg.Advanced.ReadTimeout.Duration = 2 * time.Second 100 cfg.Advanced.WriteTimeout.Duration = 2 * time.Second 101 102 md, err := toml.DecodeFile(path, &cfg) 103 if err != nil { 104 if os.IsNotExist(err) { 105 fmt.Printf("Warning: Config file '%s' not found, using default settings.\n", path) 106 } else { 107 return cfg, fmt.Errorf("error decoding config file '%s': %w", path, err) 108 } 109 } 110 111 if len(md.Undecoded()) > 0 { 112 return cfg, fmt.Errorf("unknown configuration keys found: %v", md.Undecoded()) 113 } 114 115 if cfg.Logging.Output == "file" && cfg.Logging.FilePath == "" { 116 return cfg, fmt.Errorf("logging output is 'file' but 'file_path' is not set") 117 } 118 119 validLevels := []string{"debug", "info", "warn", "error"} 120 if !slices.Contains(validLevels, cfg.Logging.Level) { 121 return cfg, fmt.Errorf("invalid logging level '%s', must be one of: %v", cfg.Logging.Level, validLevels) 122 } 123 124 if cfg.Advanced.QueryTimeout.Duration <= 0 { 125 cfg.Advanced.QueryTimeout.Duration = 5 * time.Second 126 } 127 128 if cfg.Advanced.ReadTimeout.Duration <= 0 { 129 cfg.Advanced.ReadTimeout.Duration = 2 * time.Second 130 } 131 132 if cfg.Advanced.WriteTimeout.Duration <= 0 { 133 cfg.Advanced.WriteTimeout.Duration = 2 * time.Second 134 } 135 136 if cfg.Server.UDPSize <= 0 || cfg.Server.UDPSize > 4096 { 137 cfg.Server.UDPSize = 512 138 } 139 140 if cfg.Ratelimit.Rate > 0 { 141 if cfg.Ratelimit.Window.Duration <= 0 { 142 cfg.Ratelimit.Window.Duration = 1 * time.Second 143 } 144 if cfg.Ratelimit.ExpirationTime.Duration <= 0 { 145 cfg.Ratelimit.ExpirationTime.Duration = 1 * time.Minute 146 } 147 } 148 149 return cfg, nil 150}