package config import ( "flag" "fmt" "github.com/BurntSushi/toml" ) type Config struct { Server ServerConfig `toml:"server"` Cache CacheConfig `toml:"cache"` Resolver ResolverConfig `toml:"resolver"` Blocklist BlocklistConfig `toml:"blocklist"` Log LogConfig `toml:"log"` } type ServerConfig struct { ListenUDP string `toml:"listen_udp"` ListenTCP string `toml:"listen_tcp"` ListenDOH string `toml:"listen_doh"` } type CacheConfig struct { MaxEntries int `toml:"max_entries"` DBPath string `toml:"db_path"` } type ResolverConfig struct { Timeout string `toml:"timeout"` MaxDelegations int `toml:"max_delegations"` } type BlocklistConfig struct { Response string `toml:"response"` Files []string `toml:"files"` URLs []string `toml:"urls"` } type LogConfig struct { Level string `toml:"level"` } type CLIFlags struct { Config string LogLevel string ListenUDP string ListenTCP string ListenDOH string } func ParseFlags() CLIFlags { var f CLIFlags flag.StringVar(&f.Config, "config", "linum.toml", "path to config file") flag.StringVar(&f.LogLevel, "loglevel", "", "log level (debug|info|warn|error)") flag.StringVar(&f.ListenUDP, "udp", "", "UDP listen address") flag.StringVar(&f.ListenTCP, "tcp", "", "TCP listen address") flag.StringVar(&f.ListenDOH, "doh", "", "DoH listen address") flag.Parse() return f } func Default() Config{ return Config{ Server: ServerConfig{ ListenUDP: ":5353", ListenTCP: ":5353", ListenDOH: ":8443", }, Cache: CacheConfig{ MaxEntries: 100000, }, Resolver: ResolverConfig{ Timeout: "2s", MaxDelegations: 30, }, Blocklist: BlocklistConfig{ Response: "zero_ip", }, Log: LogConfig{ Level: "info", }, } } func LoadFile(path string) (Config, error) { var cfg Config _, err := toml.DecodeFile(path, &cfg) return cfg, err } func Merge(dst, src Config) Config { if src.Server.ListenUDP != "" { dst.Server.ListenUDP = src.Server.ListenUDP } if src.Server.ListenTCP != "" { dst.Server.ListenTCP = src.Server.ListenTCP } if src.Server.ListenDOH != "" { dst.Server.ListenDOH = src.Server.ListenDOH } if src.Cache.MaxEntries > 0 { dst.Cache.MaxEntries = src.Cache.MaxEntries } if src.Cache.DBPath != "" { dst.Cache.DBPath = src.Cache.DBPath } if src.Resolver.Timeout != "" { dst.Resolver.Timeout = src.Resolver.Timeout } if src.Resolver.MaxDelegations > 0 { dst.Resolver.MaxDelegations = src.Resolver.MaxDelegations } if src.Blocklist.Response != "" { dst.Blocklist.Response = src.Blocklist.Response } if len(src.Blocklist.Files) > 0 { dst.Blocklist.Files = src.Blocklist.Files } if len(src.Blocklist.URLs) > 0 { dst.Blocklist.URLs = src.Blocklist.URLs } if src.Log.Level != "" { dst.Log.Level = src.Log.Level } return dst } func (f CLIFlags) Apply(cfg Config) Config { if f.ListenUDP != "" { cfg.Server.ListenUDP = f.ListenUDP } if f.ListenTCP != "" { cfg.Server.ListenTCP = f.ListenTCP } if f.ListenDOH != "" { cfg.Server.ListenDOH = f.ListenDOH } if f.LogLevel != "" { cfg.Log.Level = f.LogLevel } return cfg } func (c Config) Validate() error { switch c.Blocklist.Response { case "zero_ip", "nxdomain", "": default: return fmt.Errorf("invalid blocklist response %q (want zero_ip or nxdomain)", c.Blocklist.Response) } return nil }