mirror of https://github.com/0xERR0R/blocky.git
334 lines
8.3 KiB
Go
334 lines
8.3 KiB
Go
package config
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net"
|
|
"os"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/0xERR0R/blocky/log"
|
|
|
|
"gopkg.in/yaml.v2"
|
|
)
|
|
|
|
const (
|
|
validUpstream = `(?P<Host>(?:\[[^\]]+\])|[^\s/:]+):?(?P<Port>[^\s/:]*)?(?P<Path>/[^\s]*)?`
|
|
// NetUDP UDP protocol (deprecated)
|
|
NetUDP = "udp"
|
|
|
|
// NetTCP TCP protocol (deprecated)
|
|
NetTCP = "tcp"
|
|
|
|
// NetTCPUDP TCP and UDP protocols
|
|
NetTCPUDP = "tcp+udp"
|
|
|
|
// NetTCPTLS TCP-TLS protocol
|
|
NetTCPTLS = "tcp-tls"
|
|
|
|
// NetHTTPS HTTPS protocol
|
|
NetHTTPS = "https"
|
|
)
|
|
|
|
// nolint:gochecknoglobals
|
|
var netDefaultPort = map[string]uint16{
|
|
NetTCPUDP: 53,
|
|
NetTCPTLS: 853,
|
|
NetHTTPS: 443,
|
|
}
|
|
|
|
// Upstream is the definition of external DNS server
|
|
type Upstream struct {
|
|
Net string
|
|
Host string
|
|
Port uint16
|
|
Path string
|
|
}
|
|
|
|
// UnmarshalYAML creates Upstream from YAML
|
|
func (u *Upstream) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|
var s string
|
|
if err := unmarshal(&s); err != nil {
|
|
return err
|
|
}
|
|
|
|
upstream, err := ParseUpstream(s)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
*u = upstream
|
|
|
|
return nil
|
|
}
|
|
|
|
// UnmarshalYAML creates ConditionalUpstreamMapping from YAML
|
|
func (c *ConditionalUpstreamMapping) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|
var input map[string]string
|
|
if err := unmarshal(&input); err != nil {
|
|
return err
|
|
}
|
|
|
|
result := make(map[string][]Upstream)
|
|
|
|
for k, v := range input {
|
|
var upstreams []Upstream
|
|
|
|
for _, part := range strings.Split(v, ",") {
|
|
upstream, err := ParseUpstream(strings.TrimSpace(part))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
upstreams = append(upstreams, upstream)
|
|
}
|
|
|
|
result[k] = upstreams
|
|
}
|
|
|
|
c.Upstreams = result
|
|
|
|
return nil
|
|
}
|
|
|
|
// UnmarshalYAML creates CustomDNSMapping from YAML
|
|
func (c *CustomDNSMapping) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|
var input map[string]string
|
|
if err := unmarshal(&input); err != nil {
|
|
return err
|
|
}
|
|
|
|
result := make(map[string][]net.IP)
|
|
|
|
for k, v := range input {
|
|
var ips []net.IP
|
|
|
|
for _, part := range strings.Split(v, ",") {
|
|
ip := net.ParseIP(strings.TrimSpace(part))
|
|
if ip == nil {
|
|
return fmt.Errorf("invalid IP address '%s'", part)
|
|
}
|
|
|
|
ips = append(ips, ip)
|
|
}
|
|
|
|
result[k] = ips
|
|
}
|
|
|
|
c.HostIPs = result
|
|
|
|
return nil
|
|
}
|
|
|
|
// ParseUpstream creates new Upstream from passed string in format [net]:host[:port][/path]
|
|
func ParseUpstream(upstream string) (result Upstream, err error) {
|
|
if strings.TrimSpace(upstream) == "" {
|
|
return Upstream{}, nil
|
|
}
|
|
|
|
var n string
|
|
|
|
n, upstream = extractNet(upstream)
|
|
|
|
r := regexp.MustCompile(validUpstream)
|
|
|
|
match := r.FindStringSubmatch(upstream)
|
|
|
|
host := match[1]
|
|
|
|
portPart := match[2]
|
|
|
|
path := match[3]
|
|
|
|
var port uint16
|
|
|
|
if len(portPart) > 0 {
|
|
var p uint64
|
|
p, err = strconv.ParseUint(strings.TrimSpace(portPart), 10, 16)
|
|
|
|
if err != nil {
|
|
err = fmt.Errorf("can't convert port to number (1 - 65535) %w", err)
|
|
return
|
|
}
|
|
|
|
port = uint16(p)
|
|
} else {
|
|
port = netDefaultPort[n]
|
|
}
|
|
|
|
host = regexp.MustCompile(`[\[\]]`).ReplaceAllString(host, "")
|
|
|
|
return Upstream{Net: n, Host: host, Port: port, Path: path}, nil
|
|
}
|
|
|
|
func extractNet(upstream string) (string, string) {
|
|
if strings.HasPrefix(upstream, NetTCP+":") {
|
|
log.Log().Warnf("net prefix tcp is deprecated, using tcp+udp as default fallback")
|
|
|
|
return NetTCPUDP, strings.Replace(upstream, NetTCP+":", "", 1)
|
|
}
|
|
|
|
if strings.HasPrefix(upstream, NetUDP+":") {
|
|
log.Log().Warnf("net prefix udp is deprecated, using tcp+udp as default fallback")
|
|
return NetTCPUDP, strings.Replace(upstream, NetUDP+":", "", 1)
|
|
}
|
|
|
|
if strings.HasPrefix(upstream, NetTCPUDP+":") {
|
|
return NetTCPUDP, strings.Replace(upstream, NetTCPUDP+":", "", 1)
|
|
}
|
|
|
|
if strings.HasPrefix(upstream, NetTCPTLS+":") {
|
|
return NetTCPTLS, strings.Replace(upstream, NetTCPTLS+":", "", 1)
|
|
}
|
|
|
|
if strings.HasPrefix(upstream, NetHTTPS+":") {
|
|
return NetHTTPS, strings.Replace(upstream, NetHTTPS+":", "", 1)
|
|
}
|
|
|
|
return NetTCPUDP, upstream
|
|
}
|
|
|
|
const (
|
|
cfgDefaultPort = "53"
|
|
cfgDefaultPrometheusPath = "/metrics"
|
|
)
|
|
|
|
// Config main configuration
|
|
// nolint:maligned
|
|
type Config struct {
|
|
Upstream UpstreamConfig `yaml:"upstream"`
|
|
CustomDNS CustomDNSConfig `yaml:"customDNS"`
|
|
Conditional ConditionalUpstreamConfig `yaml:"conditional"`
|
|
Blocking BlockingConfig `yaml:"blocking"`
|
|
ClientLookup ClientLookupConfig `yaml:"clientLookup"`
|
|
Caching CachingConfig `yaml:"caching"`
|
|
QueryLog QueryLogConfig `yaml:"queryLog"`
|
|
Prometheus PrometheusConfig `yaml:"prometheus"`
|
|
LogLevel string `yaml:"logLevel"`
|
|
LogFormat string `yaml:"logFormat"`
|
|
LogPrivacy bool `yaml:"logPrivacy"`
|
|
LogTimestamp bool `yaml:"logTimestamp"`
|
|
Port string `yaml:"port"`
|
|
HTTPPort string `yaml:"httpPort"`
|
|
HTTPSPort string `yaml:"httpsPort"`
|
|
DisableIPv6 bool `yaml:"disableIPv6"`
|
|
CertFile string `yaml:"httpsCertFile"`
|
|
KeyFile string `yaml:"httpsKeyFile"`
|
|
BootstrapDNS Upstream `yaml:"bootstrapDns"`
|
|
}
|
|
|
|
// PrometheusConfig contains the config values for prometheus
|
|
type PrometheusConfig struct {
|
|
Enable bool `yaml:"enable"`
|
|
Path string `yaml:"path"`
|
|
}
|
|
|
|
// UpstreamConfig upstream server configuration
|
|
type UpstreamConfig struct {
|
|
ExternalResolvers map[string][]Upstream `yaml:",inline"`
|
|
}
|
|
|
|
// CustomDNSConfig custom DNS configuration
|
|
type CustomDNSConfig struct {
|
|
Mapping CustomDNSMapping `yaml:"mapping"`
|
|
}
|
|
|
|
// CustomDNSMapping mapping for the custom DNS configuration
|
|
type CustomDNSMapping struct {
|
|
HostIPs map[string][]net.IP
|
|
}
|
|
|
|
// ConditionalUpstreamConfig conditional upstream configuration
|
|
type ConditionalUpstreamConfig struct {
|
|
Rewrite map[string]string `yaml:"rewrite"`
|
|
Mapping ConditionalUpstreamMapping `yaml:"mapping"`
|
|
}
|
|
|
|
// ConditionalUpstreamMapping mapping for conditional configuration
|
|
type ConditionalUpstreamMapping struct {
|
|
Upstreams map[string][]Upstream
|
|
}
|
|
|
|
// BlockingConfig configuration for query blocking
|
|
type BlockingConfig struct {
|
|
BlackLists map[string][]string `yaml:"blackLists"`
|
|
WhiteLists map[string][]string `yaml:"whiteLists"`
|
|
ClientGroupsBlock map[string][]string `yaml:"clientGroupsBlock"`
|
|
BlockType string `yaml:"blockType"`
|
|
RefreshPeriod int `yaml:"refreshPeriod"`
|
|
}
|
|
|
|
// ClientLookupConfig configuration for the client lookup
|
|
type ClientLookupConfig struct {
|
|
ClientnameIPMapping map[string][]net.IP `yaml:"clients"`
|
|
Upstream Upstream `yaml:"upstream"`
|
|
SingleNameOrder []uint `yaml:"singleNameOrder"`
|
|
}
|
|
|
|
// CachingConfig configuration for domain caching
|
|
type CachingConfig struct {
|
|
MinCachingTime int `yaml:"minTime"`
|
|
MaxCachingTime int `yaml:"maxTime"`
|
|
MaxItemsCount int `yaml:"maxItemsCount"`
|
|
Prefetching bool `yaml:"prefetching"`
|
|
PrefetchExpires int `yaml:"prefetchExpires"`
|
|
PrefetchThreshold int `yaml:"prefetchThreshold"`
|
|
PrefetchMaxItemsCount int `yaml:"prefetchMaxItemsCount"`
|
|
}
|
|
|
|
// QueryLogConfig configuration for the query logging
|
|
type QueryLogConfig struct {
|
|
Dir string `yaml:"dir"`
|
|
PerClient bool `yaml:"perClient"`
|
|
LogRetentionDays uint64 `yaml:"logRetentionDays"`
|
|
}
|
|
|
|
// nolint:gochecknoglobals
|
|
var config = &Config{}
|
|
|
|
// LoadConfig creates new config from YAML file
|
|
func LoadConfig(path string, mandatory bool) {
|
|
cfg := Config{}
|
|
setDefaultValues(&cfg)
|
|
|
|
data, err := ioutil.ReadFile(path)
|
|
|
|
if err != nil {
|
|
if errors.Is(err, os.ErrNotExist) && !mandatory {
|
|
// config file does not exist
|
|
// return config with default values
|
|
config = &cfg
|
|
return
|
|
}
|
|
|
|
log.Log().Fatal("Can't read config file: ", err)
|
|
}
|
|
|
|
err = yaml.UnmarshalStrict(data, &cfg)
|
|
if err != nil {
|
|
log.Log().Fatal("wrong file structure: ", err)
|
|
}
|
|
|
|
if cfg.LogFormat != log.CfgLogFormatText && cfg.LogFormat != log.CfgLogFormatJSON {
|
|
log.Log().Fatal("LogFormat should be 'text' or 'json'")
|
|
}
|
|
|
|
config = &cfg
|
|
}
|
|
|
|
// GetConfig returns the current config
|
|
func GetConfig() *Config {
|
|
return config
|
|
}
|
|
|
|
func setDefaultValues(cfg *Config) {
|
|
cfg.Port = cfgDefaultPort
|
|
cfg.LogLevel = "info"
|
|
cfg.LogFormat = log.CfgLogFormatText
|
|
cfg.LogTimestamp = true
|
|
cfg.Prometheus.Path = cfgDefaultPrometheusPath
|
|
}
|