diff --git a/client/src/components/Settings/Encryption/index.js b/client/src/components/Settings/Encryption/index.js
index 7c2cccc8..f7ca52e0 100644
--- a/client/src/components/Settings/Encryption/index.js
+++ b/client/src/components/Settings/Encryption/index.js
@@ -66,6 +66,7 @@ class Encryption extends Component {
force_https,
port_https,
port_dns_over_tls,
+ port_dns_over_quic,
certificate_chain,
private_key,
certificate_path,
@@ -78,6 +79,7 @@ class Encryption extends Component {
force_https,
port_https,
port_dns_over_tls,
+ port_dns_over_quic,
certificate_chain,
private_key,
certificate_path,
diff --git a/client/src/components/Settings/Settings.css b/client/src/components/Settings/Settings.css
index 3bf1a121..4efb0868 100644
--- a/client/src/components/Settings/Settings.css
+++ b/client/src/components/Settings/Settings.css
@@ -54,7 +54,7 @@
}
.form__message--error {
- color: var(--red);
+ color: #cd201f;
}
.form__message--left-pad {
diff --git a/client/src/components/ui/Card.css b/client/src/components/ui/Card.css
index 5930d881..eb8a7963 100644
--- a/client/src/components/ui/Card.css
+++ b/client/src/components/ui/Card.css
@@ -19,7 +19,7 @@
max-height: 17.5rem;
}
-.card-table-overflow--limited.clients__table {
+.dashboard .card-table-overflow--limited {
max-height: 18rem;
}
@@ -122,6 +122,12 @@
}
}
+@media (min-width: 992px) {
+ .dashboard .card:not(.card--full) {
+ height: 22rem;
+ }
+}
+
.card .logs__cell--red {
background-color: #fff4f2;
}
diff --git a/client/src/helpers/constants.js b/client/src/helpers/constants.js
index 90d06a6f..2ab176ea 100644
--- a/client/src/helpers/constants.js
+++ b/client/src/helpers/constants.js
@@ -52,6 +52,7 @@ export const REPOSITORY = {
export const PRIVACY_POLICY_LINK = 'https://adguard.com/privacy/home.html';
export const PORT_53_FAQ_LINK = 'https://github.com/AdguardTeam/AdGuardHome/wiki/FAQ#bindinuse';
+export const UPSTREAM_CONFIGURATION_WIKI_LINK = 'https://github.com/AdguardTeam/AdGuardHome/wiki/Configuration#upstreams';
export const GETTING_STARTED_LINK = 'https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started#update';
@@ -71,6 +72,7 @@ export const STANDARD_DNS_PORT = 53;
export const STANDARD_WEB_PORT = 80;
export const STANDARD_HTTPS_PORT = 443;
export const DNS_OVER_TLS_PORT = 853;
+export const DNS_OVER_QUIC_PORT = 784;
export const MAX_PORT = 65535;
export const EMPTY_DATE = '0001-01-01T00:00:00Z';
@@ -292,8 +294,11 @@ export const QUERY_LOG_INTERVALS_DAYS = [1, 7, 30, 90];
export const FILTERS_INTERVALS_HOURS = [0, 1, 12, 24, 72, 168];
+// Note that translation strings contain these modes (blocking_mode_CONSTANT)
+// i.e. blocking_mode_default, blocking_mode_null_ip
export const BLOCKING_MODES = {
default: 'default',
+ refused: 'refused',
nxdomain: 'nxdomain',
null_ip: 'null_ip',
custom_ip: 'custom_ip',
@@ -501,9 +506,12 @@ export const FORM_NAME = {
export const SMALL_SCREEN_SIZE = 767;
export const MEDIUM_SCREEN_SIZE = 1023;
-export const SECONDS_IN_HOUR = 60 * 60;
+export const SECONDS_IN_DAY = 60 * 60 * 24;
-export const SECONDS_IN_DAY = SECONDS_IN_HOUR * 24;
+export const UINT32_RANGE = {
+ MIN: 0,
+ MAX: 4294967295,
+};
export const DHCP_VALUES_PLACEHOLDERS = {
ipv4: {
@@ -548,3 +556,15 @@ export const TOAST_TIMEOUTS = {
[TOAST_TYPES.ERROR]: FAILURE_TOAST_TIMEOUT,
[TOAST_TYPES.NOTICE]: FAILURE_TOAST_TIMEOUT,
};
+
+export const ADDRESS_TYPES = {
+ IP: 'IP',
+ CIDR: 'CIDR',
+ UNKNOWN: 'UNKNOWN',
+};
+
+export const CACHE_CONFIG_FIELDS = {
+ cache_size: 'cache_size',
+ cache_ttl_min: 'cache_ttl_min',
+ cache_ttl_max: 'cache_ttl_max',
+};
diff --git a/client/src/helpers/helpers.js b/client/src/helpers/helpers.js
index bafd230e..f509ef44 100644
--- a/client/src/helpers/helpers.js
+++ b/client/src/helpers/helpers.js
@@ -14,6 +14,7 @@ import queryString from 'query-string';
import { getTrackerData } from './trackers/trackers';
import {
+ ADDRESS_TYPES,
CHECK_TIMEOUT,
CUSTOM_FILTERING_RULES_ID,
DEFAULT_DATE_FORMAT_OPTIONS,
@@ -509,6 +510,18 @@ const isIpMatchCidr = (parsedIp, parsedCidr) => {
}
};
+export const isIpInCidr = (ip, cidr) => {
+ try {
+ const parsedIp = ipaddr.parse(ip);
+ const parsedCidr = ipaddr.parseCIDR(cidr);
+
+ return isIpMatchCidr(parsedIp, parsedCidr);
+ } catch (e) {
+ console.error(e);
+ return false;
+ }
+};
+
/**
* The purpose of this method is to quickly check
* if this IP can possibly be in the specified CIDR range.
@@ -578,6 +591,29 @@ const isIpQuickMatchCIDR = (ip, listItem) => {
return false;
};
+/**
+ *
+ * @param ipOrCidr
+ * @returns {'IP' | 'CIDR' | 'UNKNOWN'}
+ *
+ */
+export const findAddressType = (address) => {
+ try {
+ const cidrMaybe = address.includes('/');
+
+ if (!cidrMaybe && ipaddr.isValid(address)) {
+ return ADDRESS_TYPES.IP;
+ }
+ if (cidrMaybe && ipaddr.parseCIDR(address)) {
+ return ADDRESS_TYPES.CIDR;
+ }
+
+ return ADDRESS_TYPES.UNKNOWN;
+ } catch (e) {
+ return ADDRESS_TYPES.UNKNOWN;
+ }
+};
+
/**
* @param ip {string}
* @param list {string}
@@ -622,6 +658,42 @@ export const getIpMatchListStatus = (ip, list) => {
}
};
+/**
+ * @param ids {string[]}
+ * @returns {Object}
+ */
+export const separateIpsAndCidrs = (ids) => ids.reduce((acc, curr) => {
+ const addressType = findAddressType(curr);
+
+ if (addressType === ADDRESS_TYPES.IP) {
+ acc.ips.push(curr);
+ }
+ if (addressType === ADDRESS_TYPES.CIDR) {
+ acc.cidrs.push(curr);
+ }
+ return acc;
+}, { ips: [], cidrs: [] });
+
+export const countClientsStatistics = (ids, autoClients) => {
+ const { ips, cidrs } = separateIpsAndCidrs(ids);
+
+ const ipsCount = ips.reduce((acc, curr) => {
+ const count = autoClients[curr] || 0;
+ return acc + count;
+ }, 0);
+
+ const cidrsCount = Object.entries(autoClients)
+ .reduce((acc, curr) => {
+ const [id, count] = curr;
+ if (cidrs.some((cidr) => isIpInCidr(id, cidr))) {
+ // eslint-disable-next-line no-param-reassign
+ acc += count;
+ }
+ return acc;
+ }, 0);
+
+ return ipsCount + cidrsCount;
+};
/**
* @param {string} elapsedMs
diff --git a/client/src/helpers/validators.js b/client/src/helpers/validators.js
index 45055154..9b853120 100644
--- a/client/src/helpers/validators.js
+++ b/client/src/helpers/validators.js
@@ -1,4 +1,3 @@
-import i18next from 'i18next';
import {
MAX_PORT,
R_CIDR,
@@ -28,17 +27,6 @@ export const validateRequiredValue = (value) => {
return 'form_error_required';
};
-/**
- * @param maximum {number}
- * @returns {(value:number) => undefined|string}
- */
-export const getMaxValueValidator = (maximum) => (value) => {
- if (value && value > maximum) {
- return i18next.t('value_not_larger_than', { maximum });
- }
- return undefined;
-};
-
/**
* @param value {string}
* @returns {undefined|string}
@@ -122,17 +110,6 @@ export const validateMac = (value) => {
return undefined;
};
-/**
- * @param value {number}
- * @returns {undefined|string}
- */
-export const validateIsPositiveValue = (value) => {
- if ((value || value === 0) && value <= 0) {
- return 'form_error_positive';
- }
- return undefined;
-};
-
/**
* @param value {number}
* @returns {boolean|*}
@@ -180,6 +157,12 @@ export const validatePortTLS = (value) => {
return undefined;
};
+/**
+ * @param value {number}
+ * @returns {undefined|string}
+ */
+export const validatePortQuic = validatePortTLS;
+
/**
* @param value {number}
* @returns {undefined|string}
diff --git a/client/src/reducers/dnsConfig.js b/client/src/reducers/dnsConfig.js
index 91e95818..bbe4ad2f 100644
--- a/client/src/reducers/dnsConfig.js
+++ b/client/src/reducers/dnsConfig.js
@@ -48,6 +48,7 @@ const dnsConfig = handleActions(
edns_cs_enabled: false,
disable_ipv6: false,
dnssec_enabled: false,
+ upstream_dns_file: '',
},
);
diff --git a/dnsforward/config.go b/dnsforward/config.go
index 69af11eb..8d27d1ff 100644
--- a/dnsforward/config.go
+++ b/dnsforward/config.go
@@ -5,16 +5,17 @@ import (
"crypto/x509"
"errors"
"fmt"
+ "io/ioutil"
"net"
"net/http"
"sort"
- "github.com/AdguardTeam/golibs/log"
- "github.com/joomcode/errorx"
-
"github.com/AdguardTeam/AdGuardHome/dnsfilter"
+ "github.com/AdguardTeam/AdGuardHome/util"
"github.com/AdguardTeam/dnsproxy/proxy"
"github.com/AdguardTeam/dnsproxy/upstream"
+ "github.com/AdguardTeam/golibs/log"
+ "github.com/joomcode/errorx"
)
// FilteringConfig represents the DNS filtering configuration of AdGuard Home
@@ -55,10 +56,11 @@ type FilteringConfig struct {
// Upstream DNS servers configuration
// --
- UpstreamDNS []string `yaml:"upstream_dns"`
- BootstrapDNS []string `yaml:"bootstrap_dns"` // a list of bootstrap DNS for DoH and DoT (plain DNS only)
- AllServers bool `yaml:"all_servers"` // if true, parallel queries to all configured upstream servers are enabled
- FastestAddr bool `yaml:"fastest_addr"` // use Fastest Address algorithm
+ UpstreamDNS []string `yaml:"upstream_dns"`
+ UpstreamDNSFileName string `yaml:"upstream_dns_file"`
+ BootstrapDNS []string `yaml:"bootstrap_dns"` // a list of bootstrap DNS for DoH and DoT (plain DNS only)
+ AllServers bool `yaml:"all_servers"` // if true, parallel queries to all configured upstream servers are enabled
+ FastestAddr bool `yaml:"fastest_addr"` // use Fastest Address algorithm
// Access settings
// --
@@ -92,6 +94,7 @@ type FilteringConfig struct {
// TLSConfig is the TLS configuration for HTTPS, DNS-over-HTTPS, and DNS-over-TLS
type TLSConfig struct {
TLSListenAddr *net.TCPAddr `yaml:"-" json:"-"`
+ QUICListenAddr *net.UDPAddr `yaml:"-" json:"-"`
StrictSNICheck bool `yaml:"strict_sni_check" json:"-"` // Reject connection if the client uses server name (in SNI) that doesn't match the certificate
CertificateChain string `yaml:"certificate_chain" json:"certificate_chain"` // PEM-encoded certificates chain
@@ -184,7 +187,7 @@ func (s *Server) createProxyConfig() (proxy.Config, error) {
// Validate proxy config
if proxyConfig.UpstreamConfig == nil || len(proxyConfig.UpstreamConfig.Upstreams) == 0 {
- return proxyConfig, errors.New("no upstream servers configured")
+ return proxyConfig, errors.New("no default upstream servers configured")
}
return proxyConfig, nil
@@ -211,14 +214,55 @@ func (s *Server) initDefaultSettings() {
if s.conf.TCPListenAddr == nil {
s.conf.TCPListenAddr = defaultValues.TCPListenAddr
}
+ if len(s.conf.BlockedHosts) == 0 {
+ s.conf.BlockedHosts = defaultBlockedHosts
+ }
}
// prepareUpstreamSettings - prepares upstream DNS server settings
func (s *Server) prepareUpstreamSettings() error {
- upstreamConfig, err := proxy.ParseUpstreamsConfig(s.conf.UpstreamDNS, s.conf.BootstrapDNS, DefaultTimeout)
+ // We're setting a customized set of RootCAs
+ // The reason is that Go default mechanism of loading TLS roots
+ // does not always work properly on some routers so we're
+ // loading roots manually and pass it here.
+ // See "util.LoadSystemRootCAs"
+ upstream.RootCAs = s.conf.TLSv12Roots
+
+ // See util.InitTLSCiphers -- removed unsafe ciphers
+ if len(s.conf.TLSCiphers) > 0 {
+ upstream.CipherSuites = s.conf.TLSCiphers
+ }
+
+ // Load upstreams either from the file, or from the settings
+ var upstreams []string
+ if s.conf.UpstreamDNSFileName != "" {
+ data, err := ioutil.ReadFile(s.conf.UpstreamDNSFileName)
+ if err != nil {
+ return err
+ }
+ d := string(data)
+ for len(d) != 0 {
+ s := util.SplitNext(&d, '\n')
+ upstreams = append(upstreams, s)
+ }
+ log.Debug("DNS: using %d upstream servers from file %s", len(upstreams), s.conf.UpstreamDNSFileName)
+ } else {
+ upstreams = s.conf.UpstreamDNS
+ }
+ upstreamConfig, err := proxy.ParseUpstreamsConfig(upstreams, s.conf.BootstrapDNS, DefaultTimeout)
if err != nil {
return fmt.Errorf("DNS: proxy.ParseUpstreamsConfig: %s", err)
}
+
+ if len(upstreamConfig.Upstreams) == 0 {
+ log.Info("Warning: no default upstream servers specified, using %v", defaultDNS)
+ uc, err := proxy.ParseUpstreamsConfig(defaultDNS, s.conf.BootstrapDNS, DefaultTimeout)
+ if err != nil {
+ return fmt.Errorf("DNS: failed to parse default upstreams: %v", err)
+ }
+ upstreamConfig.Upstreams = uc.Upstreams
+ }
+
s.conf.UpstreamConfig = &upstreamConfig
return nil
}
@@ -235,36 +279,49 @@ func (s *Server) prepareIntlProxy() {
// prepareTLS - prepares TLS configuration for the DNS proxy
func (s *Server) prepareTLS(proxyConfig *proxy.Config) error {
- if s.conf.TLSListenAddr != nil && len(s.conf.CertificateChainData) != 0 && len(s.conf.PrivateKeyData) != 0 {
+ if len(s.conf.CertificateChainData) == 0 || len(s.conf.PrivateKeyData) == 0 {
+ return nil
+ }
+
+ if s.conf.TLSListenAddr == nil &&
+ s.conf.QUICListenAddr == nil {
+ return nil
+ }
+
+ if s.conf.TLSListenAddr != nil {
proxyConfig.TLSListenAddr = []*net.TCPAddr{s.conf.TLSListenAddr}
- var err error
- s.conf.cert, err = tls.X509KeyPair(s.conf.CertificateChainData, s.conf.PrivateKeyData)
+ }
+
+ if s.conf.QUICListenAddr != nil {
+ proxyConfig.QUICListenAddr = []*net.UDPAddr{s.conf.QUICListenAddr}
+ }
+
+ var err error
+ s.conf.cert, err = tls.X509KeyPair(s.conf.CertificateChainData, s.conf.PrivateKeyData)
+ if err != nil {
+ return errorx.Decorate(err, "Failed to parse TLS keypair")
+ }
+
+ if s.conf.StrictSNICheck {
+ x, err := x509.ParseCertificate(s.conf.cert.Certificate[0])
if err != nil {
- return errorx.Decorate(err, "Failed to parse TLS keypair")
+ return errorx.Decorate(err, "x509.ParseCertificate(): %s", err)
}
-
- if s.conf.StrictSNICheck {
- x, err := x509.ParseCertificate(s.conf.cert.Certificate[0])
- if err != nil {
- return errorx.Decorate(err, "x509.ParseCertificate(): %s", err)
- }
- if len(x.DNSNames) != 0 {
- s.conf.dnsNames = x.DNSNames
- log.Debug("DNS: using DNS names from certificate's SAN: %v", x.DNSNames)
- sort.Strings(s.conf.dnsNames)
- } else {
- s.conf.dnsNames = append(s.conf.dnsNames, x.Subject.CommonName)
- log.Debug("DNS: using DNS name from certificate's CN: %s", x.Subject.CommonName)
- }
- }
-
- proxyConfig.TLSConfig = &tls.Config{
- GetCertificate: s.onGetCertificate,
- MinVersion: tls.VersionTLS12,
+ if len(x.DNSNames) != 0 {
+ s.conf.dnsNames = x.DNSNames
+ log.Debug("DNS: using DNS names from certificate's SAN: %v", x.DNSNames)
+ sort.Strings(s.conf.dnsNames)
+ } else {
+ s.conf.dnsNames = append(s.conf.dnsNames, x.Subject.CommonName)
+ log.Debug("DNS: using DNS name from certificate's CN: %s", x.Subject.CommonName)
}
}
- upstream.RootCAs = s.conf.TLSv12Roots
- upstream.CipherSuites = s.conf.TLSCiphers
+
+ proxyConfig.TLSConfig = &tls.Config{
+ GetCertificate: s.onGetCertificate,
+ MinVersion: tls.VersionTLS12,
+ }
+
return nil
}
diff --git a/dnsforward/dnsforward.go b/dnsforward/dnsforward.go
index 6815a7cd..1d2c3d7b 100644
--- a/dnsforward/dnsforward.go
+++ b/dnsforward/dnsforward.go
@@ -31,6 +31,9 @@ var defaultDNS = []string{
}
var defaultBootstrap = []string{"9.9.9.10", "149.112.112.10", "2620:fe::10", "2620:fe::fe:10"}
+// Often requested by all kinds of DNS probes
+var defaultBlockedHosts = []string{"version.bind", "id.server", "hostname.bind"}
+
var webRegistered bool
// Server is the main way to start a DNS server.
diff --git a/dnsforward/dnsforward_http.go b/dnsforward/dnsforward_http.go
index 63e82d95..42c738f2 100644
--- a/dnsforward/dnsforward_http.go
+++ b/dnsforward/dnsforward_http.go
@@ -22,8 +22,9 @@ func httpError(r *http.Request, w http.ResponseWriter, code int, format string,
}
type dnsConfigJSON struct {
- Upstreams []string `json:"upstream_dns"`
- Bootstraps []string `json:"bootstrap_dns"`
+ Upstreams []string `json:"upstream_dns"`
+ UpstreamsFile string `json:"upstream_dns_file"`
+ Bootstraps []string `json:"bootstrap_dns"`
ProtectionEnabled bool `json:"protection_enabled"`
RateLimit uint32 `json:"ratelimit"`
@@ -43,6 +44,7 @@ func (s *Server) handleGetConfig(w http.ResponseWriter, r *http.Request) {
resp := dnsConfigJSON{}
s.RLock()
resp.Upstreams = stringArrayDup(s.conf.UpstreamDNS)
+ resp.UpstreamsFile = s.conf.UpstreamDNSFileName
resp.Bootstraps = stringArrayDup(s.conf.BootstrapDNS)
resp.ProtectionEnabled = s.conf.ProtectionEnabled
@@ -74,7 +76,7 @@ func (s *Server) handleGetConfig(w http.ResponseWriter, r *http.Request) {
func checkBlockingMode(req dnsConfigJSON) bool {
bm := req.BlockingMode
- if !(bm == "default" || bm == "nxdomain" || bm == "null_ip" || bm == "custom_ip") {
+ if !(bm == "default" || bm == "refused" || bm == "nxdomain" || bm == "null_ip" || bm == "custom_ip") {
return false
}
@@ -157,6 +159,11 @@ func (s *Server) handleSetConfig(w http.ResponseWriter, r *http.Request) {
restart = true
}
+ if js.Exists("upstream_dns_file") {
+ s.conf.UpstreamDNSFileName = req.UpstreamsFile
+ restart = true
+ }
+
if js.Exists("bootstrap_dns") {
s.conf.BootstrapDNS = req.Bootstraps
restart = true
@@ -270,7 +277,7 @@ func ValidateUpstreams(upstreams []string) error {
return nil
}
-var protocols = []string{"tls://", "https://", "tcp://", "sdns://"}
+var protocols = []string{"tls://", "https://", "tcp://", "sdns://", "quic://"}
func validateUpstream(u string) (bool, error) {
// Check if user tries to specify upstream for domain
diff --git a/dnsforward/dnsforward_test.go b/dnsforward/dnsforward_test.go
index 605b52a9..57540331 100644
--- a/dnsforward/dnsforward_test.go
+++ b/dnsforward/dnsforward_test.go
@@ -8,13 +8,18 @@ import (
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
+ "fmt"
+ "io/ioutil"
"math/big"
"net"
+ "os"
"sort"
"sync"
"testing"
"time"
+ "github.com/AdguardTeam/AdGuardHome/util"
+
"github.com/AdguardTeam/AdGuardHome/dhcpd"
"github.com/AdguardTeam/AdGuardHome/dnsfilter"
"github.com/AdguardTeam/dnsproxy/proxy"
@@ -128,6 +133,41 @@ func TestDotServer(t *testing.T) {
}
}
+func TestDoqServer(t *testing.T) {
+ // Prepare the proxy server
+ _, certPem, keyPem := createServerTLSConfig(t)
+ s := createTestServer(t)
+
+ s.conf.TLSConfig = TLSConfig{
+ QUICListenAddr: &net.UDPAddr{Port: 0},
+ CertificateChainData: certPem,
+ PrivateKeyData: keyPem,
+ }
+
+ _ = s.Prepare(nil)
+ // Starting the server
+ err := s.Start()
+ assert.Nil(t, err)
+
+ // Create a DNS-over-QUIC upstream
+ addr := s.dnsProxy.Addr(proxy.ProtoQUIC)
+ opts := upstream.Options{InsecureSkipVerify: true}
+ u, err := upstream.AddressToUpstream(fmt.Sprintf("quic://%s", addr), opts)
+ assert.Nil(t, err)
+
+ // Send the test message
+ req := createGoogleATestMessage()
+ res, err := u.Exchange(req)
+ assert.Nil(t, err)
+ assertGoogleAResponse(t, res)
+
+ // Stop the proxy
+ err = s.Stop()
+ if err != nil {
+ t.Fatalf("DNS server failed to stop: %s", err)
+ }
+}
+
func TestServerRace(t *testing.T) {
s := createTestServer(t)
err := s.Start()
@@ -227,7 +267,7 @@ func TestBlockedRequest(t *testing.T) {
addr := s.dnsProxy.Addr(proxy.ProtoUDP)
//
- // NXDomain blocking
+ // Default blocking - REFUSED
//
req := dns.Msg{}
req.Id = dns.Id()
@@ -240,9 +280,7 @@ func TestBlockedRequest(t *testing.T) {
if err != nil {
t.Fatalf("Couldn't talk to server %s: %s", addr, err)
}
- if reply.Rcode != dns.RcodeNameError {
- t.Fatalf("Wrong response: %s", reply.String())
- }
+ assert.Equal(t, dns.RcodeRefused, reply.Rcode)
err = s.Stop()
if err != nil {
@@ -404,7 +442,7 @@ func TestBlockCNAME(t *testing.T) {
req := createTestMessage("badhost.")
reply, err := dns.Exchange(req, addr.String())
assert.Nil(t, err, nil)
- assert.Equal(t, dns.RcodeNameError, reply.Rcode)
+ assert.Equal(t, dns.RcodeRefused, reply.Rcode)
// 'whitelist.example.org' has a canonical name 'null.example.org' which is blocked by filters
// but 'whitelist.example.org' is in a whitelist:
@@ -419,7 +457,7 @@ func TestBlockCNAME(t *testing.T) {
req = createTestMessage("example.org.")
reply, err = dns.Exchange(req, addr.String())
assert.Nil(t, err)
- assert.Equal(t, dns.RcodeNameError, reply.Rcode)
+ assert.Equal(t, dns.RcodeRefused, reply.Rcode)
_ = s.Stop()
}
@@ -630,17 +668,17 @@ func TestBlockedBySafeBrowsing(t *testing.T) {
func TestRewrite(t *testing.T) {
c := dnsfilter.Config{}
c.Rewrites = []dnsfilter.RewriteEntry{
- dnsfilter.RewriteEntry{
+ {
Domain: "test.com",
Answer: "1.2.3.4",
Type: dns.TypeA,
},
- dnsfilter.RewriteEntry{
+ {
Domain: "alias.test.com",
Answer: "test.com",
Type: dns.TypeCNAME,
},
- dnsfilter.RewriteEntry{
+ {
Domain: "my.alias.example.org",
Answer: "example.org",
Type: dns.TypeCNAME,
@@ -988,7 +1026,7 @@ func (d *testDHCP) SetOnLeaseChanged(onLeaseChanged dhcpd.OnLeaseChangedT) {
return
}
-func TestPTRResponse(t *testing.T) {
+func TestPTRResponseFromDHCPLeases(t *testing.T) {
dhcp := &testDHCP{}
c := dnsfilter.Config{}
@@ -1016,3 +1054,45 @@ func TestPTRResponse(t *testing.T) {
s.Close()
}
+
+func TestPTRResponseFromHosts(t *testing.T) {
+ c := dnsfilter.Config{
+ AutoHosts: &util.AutoHosts{},
+ }
+
+ // Prepare test hosts file
+ hf, _ := ioutil.TempFile("", "")
+ defer func() { _ = os.Remove(hf.Name()) }()
+ defer hf.Close()
+
+ _, _ = hf.WriteString(" 127.0.0.1 host # comment \n")
+ _, _ = hf.WriteString(" ::1 localhost#comment \n")
+
+ // Init auto hosts
+ c.AutoHosts.Init(hf.Name())
+ defer c.AutoHosts.Close()
+
+ f := dnsfilter.New(&c, nil)
+ s := NewServer(DNSCreateParams{DNSFilter: f})
+ s.conf.UDPListenAddr = &net.UDPAddr{Port: 0}
+ s.conf.TCPListenAddr = &net.TCPAddr{Port: 0}
+ s.conf.UpstreamDNS = []string{"127.0.0.1:53"}
+ s.conf.FilteringConfig.ProtectionEnabled = true
+ err := s.Prepare(nil)
+ assert.True(t, err == nil)
+ assert.Nil(t, s.Start())
+
+ addr := s.dnsProxy.Addr(proxy.ProtoUDP)
+ req := createTestMessage("1.0.0.127.in-addr.arpa.")
+ req.Question[0].Qtype = dns.TypePTR
+
+ resp, err := dns.Exchange(req, addr.String())
+ assert.Nil(t, err)
+ assert.Equal(t, 1, len(resp.Answer))
+ assert.Equal(t, dns.TypePTR, resp.Answer[0].Header().Rrtype)
+ assert.Equal(t, "1.0.0.127.in-addr.arpa.", resp.Answer[0].Header().Name)
+ ptr := resp.Answer[0].(*dns.PTR)
+ assert.Equal(t, "host.", ptr.Ptr)
+
+ s.Close()
+}
diff --git a/dnsforward/filter.go b/dnsforward/filter.go
index e4900acb..d2e5535a 100644
--- a/dnsforward/filter.go
+++ b/dnsforward/filter.go
@@ -52,7 +52,7 @@ func (s *Server) filterDNSRequest(ctx *dnsContext) (*dnsfilter.Result, error) {
return nil, errorx.Decorate(err, "dnsfilter failed to check host '%s'", host)
} else if res.IsFiltered {
- // log.Tracef("Host %s is filtered, reason - '%s', matched rule: '%s'", host, res.Reason, res.Rule)
+ log.Tracef("Host %s is filtered, reason - '%s', matched rule: '%s'", host, res.Reason, res.Rule)
d.Res = s.genDNSFilterMessage(d, &res)
} else if res.Reason == dnsfilter.ReasonRewrite && len(res.CanonName) != 0 && len(res.IPList) == 0 {
@@ -60,6 +60,19 @@ func (s *Server) filterDNSRequest(ctx *dnsContext) (*dnsfilter.Result, error) {
// resolve canonical name, not the original host name
d.Req.Question[0].Name = dns.Fqdn(res.CanonName)
+ } else if res.Reason == dnsfilter.RewriteEtcHosts && len(res.ReverseHost) != 0 {
+
+ resp := s.makeResponse(req)
+ ptr := &dns.PTR{}
+ ptr.Hdr = dns.RR_Header{
+ Name: req.Question[0].Name,
+ Rrtype: dns.TypePTR,
+ Ttl: s.conf.BlockedResponseTTL,
+ Class: dns.ClassINET,
+ }
+ ptr.Ptr = res.ReverseHost
+ resp.Answer = append(resp.Answer, ptr)
+ d.Res = resp
} else if res.Reason == dnsfilter.ReasonRewrite || res.Reason == dnsfilter.RewriteEtcHosts {
resp := s.makeResponse(req)
@@ -82,20 +95,6 @@ func (s *Server) filterDNSRequest(ctx *dnsContext) (*dnsfilter.Result, error) {
}
d.Res = resp
-
- } else if res.Reason == dnsfilter.RewriteEtcHosts && len(res.ReverseHost) != 0 {
-
- resp := s.makeResponse(req)
- ptr := &dns.PTR{}
- ptr.Hdr = dns.RR_Header{
- Name: req.Question[0].Name,
- Rrtype: dns.TypePTR,
- Ttl: s.conf.BlockedResponseTTL,
- Class: dns.ClassINET,
- }
- ptr.Ptr = res.ReverseHost
- resp.Answer = append(resp.Answer, ptr)
- d.Res = resp
}
return &res, err
diff --git a/dnsforward/handle_dns.go b/dnsforward/handle_dns.go
index 3f0f7911..f864865b 100644
--- a/dnsforward/handle_dns.go
+++ b/dnsforward/handle_dns.go
@@ -88,7 +88,7 @@ func processInitial(ctx *dnsContext) int {
// disable Mozilla DoH
if (d.Req.Question[0].Qtype == dns.TypeA || d.Req.Question[0].Qtype == dns.TypeAAAA) &&
d.Req.Question[0].Name == "use-application-dns.net." {
- d.Res = s.genNXDomain(d.Req)
+ d.Res = s.makeResponseREFUSED(d.Req)
return resultFinish
}
diff --git a/dnsforward/msg.go b/dnsforward/msg.go
index a1078539..fc4ecb51 100644
--- a/dnsforward/msg.go
+++ b/dnsforward/msg.go
@@ -24,7 +24,7 @@ func (s *Server) genDNSFilterMessage(d *proxy.DNSContext, result *dnsfilter.Resu
m := d.Req
if m.Question[0].Qtype != dns.TypeA && m.Question[0].Qtype != dns.TypeAAAA {
- return s.genNXDomain(m)
+ return s.makeResponseREFUSED(m)
}
switch result.Reason {
@@ -64,15 +64,20 @@ func (s *Server) genDNSFilterMessage(d *proxy.DNSContext, result *dnsfilter.Resu
// means that we should return NXDOMAIN for any blocked request
return s.genNXDomain(m)
+
+ } else if s.conf.BlockingMode == "refused" {
+ // means that we should return NXDOMAIN for any blocked request
+
+ return s.makeResponseREFUSED(m)
}
// Default blocking mode
// If there's an IP specified in the rule, return it
- // If there is no IP, return NXDOMAIN
+ // If there is no IP, return REFUSED
if result.IP != nil {
return s.genResponseWithIP(m, result.IP)
}
- return s.genNXDomain(m)
+ return s.makeResponseREFUSED(m)
}
}
@@ -182,6 +187,14 @@ func (s *Server) genCNAMEAnswer(req *dns.Msg, cname string) *dns.CNAME {
return answer
}
+// Create REFUSED DNS response
+func (s *Server) makeResponseREFUSED(request *dns.Msg) *dns.Msg {
+ resp := dns.Msg{}
+ resp.SetRcode(request, dns.RcodeRefused)
+ resp.RecursionAvailable = true
+ return &resp
+}
+
func (s *Server) genNXDomain(request *dns.Msg) *dns.Msg {
resp := dns.Msg{}
resp.SetRcode(request, dns.RcodeNameError)
diff --git a/go.mod b/go.mod
index 1a81eba8..68944567 100644
--- a/go.mod
+++ b/go.mod
@@ -3,13 +3,12 @@ module github.com/AdguardTeam/AdGuardHome
go 1.14
require (
- github.com/AdguardTeam/dnsproxy v0.31.1
+ github.com/AdguardTeam/dnsproxy v0.32.5
github.com/AdguardTeam/golibs v0.4.2
github.com/AdguardTeam/urlfilter v0.12.2
github.com/NYTimes/gziphandler v1.1.1
github.com/fsnotify/fsnotify v1.4.9
github.com/gobuffalo/packr v1.30.1
- github.com/google/go-cmp v0.4.0 // indirect
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714
github.com/insomniacslk/dhcp v0.0.0-20200621044212-d74cd86ad5b8
github.com/joomcode/errorx v1.0.1
@@ -24,7 +23,7 @@ require (
github.com/u-root/u-root v6.0.0+incompatible
go.etcd.io/bbolt v1.3.4
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
- golang.org/x/net v0.0.0-20200625001655-4c5254603344
+ golang.org/x/net v0.0.0-20200707034311-ab3426394381
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299
gopkg.in/natefinch/lumberjack.v2 v2.0.0
gopkg.in/yaml.v2 v2.3.0
diff --git a/go.sum b/go.sum
index 794cd232..aed6cc4b 100644
--- a/go.sum
+++ b/go.sum
@@ -1,5 +1,14 @@
-github.com/AdguardTeam/dnsproxy v0.31.1 h1:kYnlLGM20LjPlEH+fqwCy08gMP5EVdp1FRaJ7uzyIJ0=
-github.com/AdguardTeam/dnsproxy v0.31.1/go.mod h1:hOYFV9TW+pd5XKYz7KZf2FFD8SvSPqjyGTxUae86s58=
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=
+dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
+dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
+dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
+dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
+git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
+github.com/AdguardTeam/dnsproxy v0.32.5 h1:UiExd/uHt2UOL4tYg1+WfXXUlkxmlpnMnQiTs63PekQ=
+github.com/AdguardTeam/dnsproxy v0.32.5/go.mod h1:ZLDrKIypYxBDz2N9FQHgeehuHrwTbuhZXdGwNySshbw=
github.com/AdguardTeam/golibs v0.4.0/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
github.com/AdguardTeam/golibs v0.4.2 h1:7M28oTZFoFwNmp8eGPb3ImmYbxGaJLyQXeIFVHjME0o=
github.com/AdguardTeam/golibs v0.4.2/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
@@ -20,21 +29,35 @@ github.com/ameshkov/dnscrypt v1.1.0 h1:2vAt5dD6ZmqlAxEAfzRcLBnkvdf8NI46Kn9InSwQb
github.com/ameshkov/dnscrypt v1.1.0/go.mod h1:ikduAxNLCTEfd1AaCgpIA5TgroIVQ8JY3Vb095fiFJg=
github.com/ameshkov/dnsstamps v1.0.1 h1:LhGvgWDzhNJh+kBQd/AfUlq1vfVe109huiXw4JhnPug=
github.com/ameshkov/dnsstamps v1.0.1/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A=
+github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/beefsack/go-rate v0.0.0-20180408011153-efa7637bb9b6 h1:KXlsf+qt/X5ttPGEjR0tPH1xaWWoKBEg9Q1THAj2h3I=
github.com/beefsack/go-rate v0.0.0-20180408011153-efa7637bb9b6/go.mod h1:6YNgTHLutezwnBvyneBbwvB8C82y3dcoOj5EQJIdGXA=
+github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
+github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
+github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
+github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
+github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
+github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
+github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
+github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
github.com/go-test/deep v1.0.5 h1:AKODKU3pDH1RzZzm6YZu77YWtEAq6uh1rLIAQlay2qc=
@@ -49,12 +72,43 @@ github.com/gobuffalo/packr v1.30.1 h1:hu1fuVR3fXEZR7rXNW3h8rqSML8EVAf6KNm0NKO/wK
github.com/gobuffalo/packr v1.30.1/go.mod h1:ljMyFO2EcrnzsHsN99cvbq055Y9OhRrIaviy289eRuk=
github.com/gobuffalo/packr/v2 v2.5.1 h1:TFOeY2VoGamPjQLiNDT3mn//ytzk236VMO2j7iHxJR4=
github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw=
+github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
+github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
+github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
+github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
+github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
+github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
+github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
+github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 h1:/jC7qQFrv8CrSJVmaolDVOxTfS9kc36uB6H40kdbQq8=
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
@@ -62,16 +116,20 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/insomniacslk/dhcp v0.0.0-20200621044212-d74cd86ad5b8 h1:u+vle+5E78+cT/CSMD5/Y3NUpMgA83Yu2KhG+Zbco/k=
github.com/insomniacslk/dhcp v0.0.0-20200621044212-d74cd86ad5b8/go.mod h1:CfMdguCK66I5DAUJgGKyNz8aB6vO5dZzkm9Xep6WGvw=
+github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/joomcode/errorx v1.0.1 h1:CalpDWz14ZHd68fIqluJasJosAewpz2TFaJALrUxjrk=
github.com/joomcode/errorx v1.0.1/go.mod h1:kgco15ekB6cs+4Xjzo7SPeXzx38PbJzBwbnu9qfVNHQ=
+github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/kardianos/service v1.1.0 h1:QV2SiEeWK42P0aEmGcsAgjApw/lRxkwopvT+Gu6t1/0=
github.com/kardianos/service v1.1.0/go.mod h1:RrJI2xn5vve/r32U5suTbeaSGoMU6GbNPoj36CVYcHc=
github.com/karrick/godirwalk v1.10.12 h1:BqUm+LuJcXjGv1d2mj3gBiQyrQ57a0rYoAmhvJQ7RDU=
github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@@ -79,38 +137,94 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJ
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/lucas-clemente/quic-go v0.18.0 h1:JhQDdqxdwdmGdKsKgXi1+coHRoGhvU6z0rNzOJqZ/4o=
+github.com/lucas-clemente/quic-go v0.18.0/go.mod h1:yXttHsSNxQi8AWijC/vLP+OJczXqzHSOcJrM5ITUlCg=
+github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
+github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/marten-seemann/qpack v0.2.0/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc=
+github.com/marten-seemann/qtls v0.10.0 h1:ECsuYUKalRL240rRD4Ri33ISb7kAQ3qGDlrrl55b2pc=
+github.com/marten-seemann/qtls v0.10.0/go.mod h1:UvMd1oaYDACI99/oZUYLzMCkBXQVT0aGm99sJhbT8hs=
+github.com/marten-seemann/qtls-go1-15 v0.1.0 h1:i/YPXVxz8q9umso/5y474CNcHmTpA+5DH+mFPjx6PZg=
+github.com/marten-seemann/qtls-go1-15 v0.1.0/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I=
+github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7 h1:lez6TS6aAau+8wXUP3G9I3TGlmPFEq2CTxBaRqY6AGE=
github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7/go.mod h1:U6ZQobyTjI/tJyq2HG+i/dfSoFUt8/aZCM+GKtmFk/Y=
github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065 h1:aFkJ6lx4FPip+S+Uw4aTegFMct9shDvP+79PsSxpm3w=
github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
+github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
github.com/miekg/dns v1.1.29 h1:xHBEhR+t5RzcFJjBLJlax2daXOrTYtr9z4WdKEfWFzg=
github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
+github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
+github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
+github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
+github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
+github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA=
+github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
+github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
+github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
+github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
+github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
+github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
+github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
+github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shirou/gopsutil v2.20.3+incompatible h1:0JVooMPsT7A7HqEYdydp/OfjSOYSjhXV7w1hkKj/NPQ=
github.com/shirou/gopsutil v2.20.3+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
+github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
+github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
+github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
+github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
+github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
+github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw=
+github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI=
+github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=
+github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=
+github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg=
+github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw=
+github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y=
+github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
+github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=
+github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ=
+github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I=
+github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0=
+github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
+github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=
+github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
+github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
+github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
+github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
+github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
github.com/sparrc/go-ping v0.0.0-20190613174326-4e5b6552494c h1:gqEdF4VwBu3lTKGHS9rXE9x1/pEaSwCXRLOZRF6qtlw=
github.com/sparrc/go-ping v0.0.0-20190613174326-4e5b6552494c/go.mod h1:eMyUVp6f/5jnzM+3zahzl7q6UXLbgSc3MKg/+ow9QW0=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
@@ -126,25 +240,47 @@ github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
github.com/u-root/u-root v6.0.0+incompatible h1:YqPGmRoRyYmeg17KIWFRSyVq6LX5T6GSzawyA6wG6EE=
github.com/u-root/u-root v6.0.0+incompatible/go.mod h1:RYkpo8pTHrNjW08opNd/U6p/RJE7K0D8fXO0d47+3YY=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
+github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
+github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg=
go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
+go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
+go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
+golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
+golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20200403201458-baeed622b8d8 h1:fpnn/HnJONpIu6hkXi1u/7rR0NzilgWr4T0JmWkEitk=
-golang.org/x/crypto v0.0.0-20200403201458-baeed622b8d8/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190419010253-1f3472d942ba/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
@@ -153,46 +289,114 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4=
-golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
+golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190418153312-f0ce4c0180be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606122018-79a91cf218c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d h1:nc5K6ox/4lTFbMVSL9WRR81ixkcwXThoiF6yf+R9scA=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425 h1:VvQyQJN0tSuecqgcIxMWnnfG5kSmgy9KZR9sW3W5QeA=
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
+google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
+google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
+google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
+google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
+google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
+gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
+honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
+rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
+sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
+sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
diff --git a/home/auth.go b/home/auth.go
index 36e56d05..afb7f0ed 100644
--- a/home/auth.go
+++ b/home/auth.go
@@ -276,7 +276,11 @@ type loginJSON struct {
}
func getSession(u *User) []byte {
- d := []byte(fmt.Sprintf("%d%s%s", rand.Uint32(), u.Name, u.PasswordHash))
+ // the developers don't currently believe that using a
+ // non-cryptographic RNG for the session hash salt is
+ // insecure
+ salt := rand.Uint32() //nolint:gosec
+ d := []byte(fmt.Sprintf("%d%s%s", salt, u.Name, u.PasswordHash))
hash := sha256.Sum256(d)
return hash[:]
}
diff --git a/home/config.go b/home/config.go
index d0fc6396..348e72fa 100644
--- a/home/config.go
+++ b/home/config.go
@@ -92,11 +92,12 @@ type dnsConfig struct {
}
type tlsConfigSettings struct {
- Enabled bool `yaml:"enabled" json:"enabled"` // Enabled is the encryption (DOT/DOH/HTTPS) status
- ServerName string `yaml:"server_name" json:"server_name,omitempty"` // ServerName is the hostname of your HTTPS/TLS server
- ForceHTTPS bool `yaml:"force_https" json:"force_https,omitempty"` // ForceHTTPS: if true, forces HTTP->HTTPS redirect
- PortHTTPS int `yaml:"port_https" json:"port_https,omitempty"` // HTTPS port. If 0, HTTPS will be disabled
- PortDNSOverTLS int `yaml:"port_dns_over_tls" json:"port_dns_over_tls,omitempty"` // DNS-over-TLS port. If 0, DOT will be disabled
+ Enabled bool `yaml:"enabled" json:"enabled"` // Enabled is the encryption (DOT/DOH/HTTPS) status
+ ServerName string `yaml:"server_name" json:"server_name,omitempty"` // ServerName is the hostname of your HTTPS/TLS server
+ ForceHTTPS bool `yaml:"force_https" json:"force_https,omitempty"` // ForceHTTPS: if true, forces HTTP->HTTPS redirect
+ PortHTTPS int `yaml:"port_https" json:"port_https,omitempty"` // HTTPS port. If 0, HTTPS will be disabled
+ PortDNSOverTLS int `yaml:"port_dns_over_tls" json:"port_dns_over_tls,omitempty"` // DNS-over-TLS port. If 0, DOT will be disabled
+ PortDNSOverQUIC uint16 `yaml:"port_dns_over_quic" json:"port_dns_over_quic,omitempty"` // DNS-over-QUIC port. If 0, DoQ will be disabled
// Allow DOH queries via unencrypted HTTP (e.g. for reverse proxying)
AllowUnencryptedDOH bool `yaml:"allow_unencrypted_doh" json:"allow_unencrypted_doh"`
@@ -124,8 +125,9 @@ var config = configuration{
FiltersUpdateIntervalHours: 24,
},
TLS: tlsConfigSettings{
- PortHTTPS: 443,
- PortDNSOverTLS: 853, // needs to be passed through to dnsproxy
+ PortHTTPS: 443,
+ PortDNSOverTLS: 853, // needs to be passed through to dnsproxy
+ PortDNSOverQUIC: 784,
},
logSettings: logSettings{
LogCompress: false,
diff --git a/home/control_update.go b/home/control_update.go
index fb160900..b8f6bcbe 100644
--- a/home/control_update.go
+++ b/home/control_update.go
@@ -99,7 +99,9 @@ func getVersionResp(info update.VersionInfo) []byte {
Context.tls.WriteDiskConfig(&tlsConf)
if runtime.GOOS != "windows" &&
- ((tlsConf.Enabled && (tlsConf.PortHTTPS < 1024 || tlsConf.PortDNSOverTLS < 1024)) ||
+ ((tlsConf.Enabled && (tlsConf.PortHTTPS < 1024 ||
+ tlsConf.PortDNSOverTLS < 1024 ||
+ tlsConf.PortDNSOverQUIC < 1024)) ||
config.BindPort < 1024 ||
config.DNS.Port < 1024) {
// On UNIX, if we're running under a regular user,
diff --git a/home/dns.go b/home/dns.go
index c28ebd1f..bd70ce38 100644
--- a/home/dns.go
+++ b/home/dns.go
@@ -173,12 +173,20 @@ func generateServerConfig() dnsforward.ServerConfig {
Context.tls.WriteDiskConfig(&tlsConf)
if tlsConf.Enabled {
newconfig.TLSConfig = tlsConf.TLSConfig
+
if tlsConf.PortDNSOverTLS != 0 {
newconfig.TLSListenAddr = &net.TCPAddr{
IP: net.ParseIP(config.DNS.BindHost),
Port: tlsConf.PortDNSOverTLS,
}
}
+
+ if tlsConf.PortDNSOverQUIC != 0 {
+ newconfig.QUICListenAddr = &net.UDPAddr{
+ IP: net.ParseIP(config.DNS.BindHost),
+ Port: int(tlsConf.PortDNSOverQUIC),
+ }
+ }
}
newconfig.TLSv12Roots = Context.tlsRoots
newconfig.TLSCiphers = Context.tlsCiphers
@@ -226,6 +234,11 @@ func getDNSAddresses() []string {
addr := fmt.Sprintf("tls://%s:%d", tlsConf.ServerName, tlsConf.PortDNSOverTLS)
dnsAddresses = append(dnsAddresses, addr)
}
+
+ if tlsConf.PortDNSOverQUIC != 0 {
+ addr := fmt.Sprintf("quic://%s:%d", tlsConf.ServerName, tlsConf.PortDNSOverQUIC)
+ dnsAddresses = append(dnsAddresses, addr)
+ }
}
return dnsAddresses
diff --git a/home/home.go b/home/home.go
index b9d7a760..3c2dce58 100644
--- a/home/home.go
+++ b/home/home.go
@@ -126,7 +126,7 @@ func Main(version string, channel string, armVer string) {
}()
if args.serviceControlAction != "" {
- handleServiceControlAction(args.serviceControlAction)
+ handleServiceControlAction(args)
return
}
@@ -161,6 +161,9 @@ func run(args options) {
// configure log level and output
configureLogger(args)
+ // Go memory hacks
+ memoryUsage(args)
+
// print the first message after logger is configured
log.Println(version())
log.Debug("Current working directory is %s", Context.workDir)
@@ -526,108 +529,25 @@ func cleanupAlways() {
log.Info("Stopped")
}
-// command-line arguments
-type options struct {
- verbose bool // is verbose logging enabled
- configFilename string // path to the config file
- workDir string // path to the working directory where we will store the filters data and the querylog
- bindHost string // host address to bind HTTP server on
- bindPort int // port to serve HTTP pages on
- logFile string // Path to the log file. If empty, write to stdout. If "syslog", writes to syslog
- pidFile string // File name to save PID to
- checkConfig bool // Check configuration and exit
- disableUpdate bool // If set, don't check for updates
-
- // service control action (see service.ControlAction array + "status" command)
- serviceControlAction string
-
- // runningAsService flag is set to true when options are passed from the service runner
- runningAsService bool
-
- glinetMode bool // Activate GL-Inet mode
+func exitWithError() {
+ os.Exit(64)
}
// loadOptions reads command line arguments and initializes configuration
func loadOptions() options {
- o := options{}
+ o, f, err := parse(os.Args[0], os.Args[1:])
- var printHelp func()
- var opts = []struct {
- longName string
- shortName string
- description string
- callbackWithValue func(value string)
- callbackNoValue func()
- }{
- {"config", "c", "Path to the config file", func(value string) { o.configFilename = value }, nil},
- {"work-dir", "w", "Path to the working directory", func(value string) { o.workDir = value }, nil},
- {"host", "h", "Host address to bind HTTP server on", func(value string) { o.bindHost = value }, nil},
- {"port", "p", "Port to serve HTTP pages on", func(value string) {
- v, err := strconv.Atoi(value)
- if err != nil {
- panic("Got port that is not a number")
- }
- o.bindPort = v
- }, nil},
- {"service", "s", "Service control action: status, install, uninstall, start, stop, restart, reload (configuration)", func(value string) {
- o.serviceControlAction = value
- }, nil},
- {"logfile", "l", "Path to log file. If empty: write to stdout; if 'syslog': write to system log", func(value string) {
- o.logFile = value
- }, nil},
- {"pidfile", "", "Path to a file where PID is stored", func(value string) { o.pidFile = value }, nil},
- {"check-config", "", "Check configuration and exit", nil, func() { o.checkConfig = true }},
- {"no-check-update", "", "Don't check for updates", nil, func() { o.disableUpdate = true }},
- {"verbose", "v", "Enable verbose output", nil, func() { o.verbose = true }},
- {"glinet", "", "Run in GL-Inet compatibility mode", nil, func() { o.glinetMode = true }},
- {"version", "", "Show the version and exit", nil, func() {
- fmt.Println(version())
+ if err != nil {
+ log.Error(err.Error())
+ _ = printHelp(os.Args[0])
+ exitWithError()
+ } else if f != nil {
+ err = f()
+ if err != nil {
+ log.Error(err.Error())
+ exitWithError()
+ } else {
os.Exit(0)
- }},
- {"help", "", "Print this help", nil, func() {
- printHelp()
- os.Exit(64)
- }},
- }
- printHelp = func() {
- fmt.Printf("Usage:\n\n")
- fmt.Printf("%s [options]\n\n", os.Args[0])
- fmt.Printf("Options:\n")
- for _, opt := range opts {
- val := ""
- if opt.callbackWithValue != nil {
- val = " VALUE"
- }
- if opt.shortName != "" {
- fmt.Printf(" -%s, %-30s %s\n", opt.shortName, "--"+opt.longName+val, opt.description)
- } else {
- fmt.Printf(" %-34s %s\n", "--"+opt.longName+val, opt.description)
- }
- }
- }
- for i := 1; i < len(os.Args); i++ {
- v := os.Args[i]
- knownParam := false
- for _, opt := range opts {
- if v == "--"+opt.longName || (opt.shortName != "" && v == "-"+opt.shortName) {
- if opt.callbackWithValue != nil {
- if i+1 >= len(os.Args) {
- log.Error("Got %s without argument\n", v)
- os.Exit(64)
- }
- i++
- opt.callbackWithValue(os.Args[i])
- } else if opt.callbackNoValue != nil {
- opt.callbackNoValue()
- }
- knownParam = true
- break
- }
- }
- if !knownParam {
- log.Error("unknown option %v\n", v)
- printHelp()
- os.Exit(64)
}
}
diff --git a/home/home_test.go b/home/home_test.go
index 0868cb71..c78aee52 100644
--- a/home/home_test.go
+++ b/home/home_test.go
@@ -1,3 +1,5 @@
+// +build !race
+
package home
import (
diff --git a/home/memory.go b/home/memory.go
new file mode 100644
index 00000000..5ca1c12f
--- /dev/null
+++ b/home/memory.go
@@ -0,0 +1,44 @@
+package home
+
+import (
+ "os"
+ "runtime/debug"
+ "time"
+
+ "github.com/AdguardTeam/golibs/log"
+)
+
+// memoryUsage implements a couple of not really beautiful hacks which purpose is to
+// make OS reclaim the memory freed by AdGuard Home as soon as possible.
+// See this for the details on the performance hits & gains:
+// https://github.com/AdguardTeam/AdGuardHome/issues/2044#issuecomment-687042211
+func memoryUsage(args options) {
+ if args.disableMemoryOptimization {
+ log.Info("Memory optimization is disabled")
+ return
+ }
+
+ // Makes Go allocate heap at a slower pace
+ // By default we keep it at 50%
+ debug.SetGCPercent(50)
+
+ // madvdontneed: setting madvdontneed=1 will use MADV_DONTNEED
+ // instead of MADV_FREE on Linux when returning memory to the
+ // kernel. This is less efficient, but causes RSS numbers to drop
+ // more quickly.
+ _ = os.Setenv("GODEBUG", "madvdontneed=1")
+
+ // periodically call "debug.FreeOSMemory" so
+ // that the OS could reclaim the free memory
+ go func() {
+ ticker := time.NewTicker(5 * time.Minute)
+ for {
+ select {
+ case t := <-ticker.C:
+ t.Second()
+ log.Debug("Free OS memory")
+ debug.FreeOSMemory()
+ }
+ }
+ }()
+}
diff --git a/home/options.go b/home/options.go
new file mode 100644
index 00000000..1d8a8a4e
--- /dev/null
+++ b/home/options.go
@@ -0,0 +1,325 @@
+package home
+
+import (
+ "fmt"
+ "os"
+ "strconv"
+)
+
+// options passed from command-line arguments
+type options struct {
+ verbose bool // is verbose logging enabled
+ configFilename string // path to the config file
+ workDir string // path to the working directory where we will store the filters data and the querylog
+ bindHost string // host address to bind HTTP server on
+ bindPort int // port to serve HTTP pages on
+ logFile string // Path to the log file. If empty, write to stdout. If "syslog", writes to syslog
+ pidFile string // File name to save PID to
+ checkConfig bool // Check configuration and exit
+ disableUpdate bool // If set, don't check for updates
+
+ // service control action (see service.ControlAction array + "status" command)
+ serviceControlAction string
+
+ // runningAsService flag is set to true when options are passed from the service runner
+ runningAsService bool
+
+ // disableMemoryOptimization - disables memory optimization hacks
+ // see memoryUsage() function for the details
+ disableMemoryOptimization bool
+
+ glinetMode bool // Activate GL-Inet compatibility mode
+}
+
+// functions used for their side-effects
+type effect func() error
+
+type arg struct {
+ description string // a short, English description of the argument
+ longName string // the name of the argument used after '--'
+ shortName string // the name of the argument used after '-'
+
+ // only one of updateWithValue, updateNoValue, and effect should be present
+
+ updateWithValue func(o options, v string) (options, error) // the mutator for arguments with parameters
+ updateNoValue func(o options) (options, error) // the mutator for arguments without parameters
+ effect func(o options, exec string) (f effect, err error) // the side-effect closure generator
+
+ serialize func(o options) []string // the re-serialization function back to arguments (return nil for omit)
+}
+
+// {type}SliceOrNil functions check their parameter of type {type}
+// against its zero value and return nil if the parameter value is
+// zero otherwise they return a string slice of the parameter
+
+func stringSliceOrNil(s string) []string {
+ if s == "" {
+ return nil
+ }
+ return []string{s}
+}
+
+func intSliceOrNil(i int) []string {
+ if i == 0 {
+ return nil
+ }
+ return []string{strconv.Itoa(i)}
+}
+
+func boolSliceOrNil(b bool) []string {
+ if b {
+ return []string{}
+ }
+ return nil
+}
+
+var args []arg
+
+var configArg = arg{
+ "Path to the config file",
+ "config", "c",
+ func(o options, v string) (options, error) { o.configFilename = v; return o, nil },
+ nil,
+ nil,
+ func(o options) []string { return stringSliceOrNil(o.configFilename) },
+}
+
+var workDirArg = arg{
+ "Path to the working directory",
+ "work-dir", "w",
+ func(o options, v string) (options, error) { o.workDir = v; return o, nil }, nil, nil,
+ func(o options) []string { return stringSliceOrNil(o.workDir) },
+}
+
+var hostArg = arg{
+ "Host address to bind HTTP server on",
+ "host", "h",
+ func(o options, v string) (options, error) { o.bindHost = v; return o, nil }, nil, nil,
+ func(o options) []string { return stringSliceOrNil(o.bindHost) },
+}
+
+var portArg = arg{
+ "Port to serve HTTP pages on",
+ "port", "p",
+ func(o options, v string) (options, error) {
+ var err error
+ var p int
+ minPort, maxPort := 0, 1<<16-1
+ if p, err = strconv.Atoi(v); err != nil {
+ err = fmt.Errorf("port '%s' is not a number", v)
+ } else if p < minPort || p > maxPort {
+ err = fmt.Errorf("port %d not in range %d - %d", p, minPort, maxPort)
+ } else {
+ o.bindPort = p
+ }
+ return o, err
+ }, nil, nil,
+ func(o options) []string { return intSliceOrNil(o.bindPort) },
+}
+
+var serviceArg = arg{
+ "Service control action: status, install, uninstall, start, stop, restart, reload (configuration)",
+ "service", "s",
+ func(o options, v string) (options, error) {
+ o.serviceControlAction = v
+ return o, nil
+ }, nil, nil,
+ func(o options) []string { return stringSliceOrNil(o.serviceControlAction) },
+}
+
+var logfileArg = arg{
+ "Path to log file. If empty: write to stdout; if 'syslog': write to system log",
+ "logfile", "l",
+ func(o options, v string) (options, error) { o.logFile = v; return o, nil }, nil, nil,
+ func(o options) []string { return stringSliceOrNil(o.logFile) },
+}
+
+var pidfileArg = arg{
+ "Path to a file where PID is stored",
+ "pidfile", "",
+ func(o options, v string) (options, error) { o.pidFile = v; return o, nil }, nil, nil,
+ func(o options) []string { return stringSliceOrNil(o.pidFile) },
+}
+
+var checkConfigArg = arg{
+ "Check configuration and exit",
+ "check-config", "",
+ nil, func(o options) (options, error) { o.checkConfig = true; return o, nil }, nil,
+ func(o options) []string { return boolSliceOrNil(o.checkConfig) },
+}
+
+var noCheckUpdateArg = arg{
+ "Don't check for updates",
+ "no-check-update", "",
+ nil, func(o options) (options, error) { o.disableUpdate = true; return o, nil }, nil,
+ func(o options) []string { return boolSliceOrNil(o.disableUpdate) },
+}
+
+var disableMemoryOptimizationArg = arg{
+ "Disable memory optimization",
+ "no-mem-optimization", "",
+ nil, func(o options) (options, error) { o.disableMemoryOptimization = true; return o, nil }, nil,
+ func(o options) []string { return boolSliceOrNil(o.disableMemoryOptimization) },
+}
+
+var verboseArg = arg{
+ "Enable verbose output",
+ "verbose", "v",
+ nil, func(o options) (options, error) { o.verbose = true; return o, nil }, nil,
+ func(o options) []string { return boolSliceOrNil(o.verbose) },
+}
+
+var glinetArg = arg{
+ "Run in GL-Inet compatibility mode",
+ "glinet", "",
+ nil, func(o options) (options, error) { o.glinetMode = true; return o, nil }, nil,
+ func(o options) []string { return boolSliceOrNil(o.glinetMode) },
+}
+
+var versionArg = arg{
+ "Show the version and exit",
+ "version", "",
+ nil, nil, func(o options, exec string) (effect, error) {
+ return func() error { fmt.Println(version()); os.Exit(0); return nil }, nil
+ },
+ func(o options) []string { return nil },
+}
+
+var helpArg = arg{
+ "Print this help",
+ "help", "",
+ nil, nil, func(o options, exec string) (effect, error) {
+ return func() error { _ = printHelp(exec); os.Exit(64); return nil }, nil
+ },
+ func(o options) []string { return nil },
+}
+
+func init() {
+ args = []arg{
+ configArg,
+ workDirArg,
+ hostArg,
+ portArg,
+ serviceArg,
+ logfileArg,
+ pidfileArg,
+ checkConfigArg,
+ noCheckUpdateArg,
+ disableMemoryOptimizationArg,
+ verboseArg,
+ glinetArg,
+ versionArg,
+ helpArg,
+ }
+}
+
+func getUsageLines(exec string, args []arg) []string {
+ usage := []string{
+ "Usage:",
+ "",
+ fmt.Sprintf("%s [options]", exec),
+ "",
+ "Options:",
+ }
+ for _, arg := range args {
+ val := ""
+ if arg.updateWithValue != nil {
+ val = " VALUE"
+ }
+ if arg.shortName != "" {
+ usage = append(usage, fmt.Sprintf(" -%s, %-30s %s",
+ arg.shortName,
+ "--"+arg.longName+val,
+ arg.description))
+ } else {
+ usage = append(usage, fmt.Sprintf(" %-34s %s",
+ "--"+arg.longName+val,
+ arg.description))
+ }
+ }
+ return usage
+}
+
+func printHelp(exec string) error {
+ for _, line := range getUsageLines(exec, args) {
+ _, err := fmt.Println(line)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func argMatches(a arg, v string) bool {
+ return v == "--"+a.longName || (a.shortName != "" && v == "-"+a.shortName)
+}
+
+func parse(exec string, ss []string) (o options, f effect, err error) {
+ for i := 0; i < len(ss); i++ {
+ v := ss[i]
+ knownParam := false
+ for _, arg := range args {
+ if argMatches(arg, v) {
+ if arg.updateWithValue != nil {
+ if i+1 >= len(ss) {
+ return o, f, fmt.Errorf("got %s without argument", v)
+ }
+ i++
+ o, err = arg.updateWithValue(o, ss[i])
+ if err != nil {
+ return
+ }
+ } else if arg.updateNoValue != nil {
+ o, err = arg.updateNoValue(o)
+ if err != nil {
+ return
+ }
+ } else if arg.effect != nil {
+ var eff effect
+ eff, err = arg.effect(o, exec)
+ if err != nil {
+ return
+ }
+ if eff != nil {
+ prevf := f
+ f = func() error {
+ var err error
+ if prevf != nil {
+ err = prevf()
+ }
+ if err == nil {
+ err = eff()
+ }
+ return err
+ }
+ }
+ }
+ knownParam = true
+ break
+ }
+ }
+ if !knownParam {
+ return o, f, fmt.Errorf("unknown option %v", v)
+ }
+ }
+
+ return
+}
+
+func shortestFlag(a arg) string {
+ if a.shortName != "" {
+ return "-" + a.shortName
+ }
+ return "--" + a.longName
+}
+
+func serialize(o options) []string {
+ ss := []string{}
+ for _, arg := range args {
+ s := arg.serialize(o)
+ if s != nil {
+ ss = append(ss, append([]string{shortestFlag(arg)}, s...)...)
+ }
+ }
+ return ss
+}
diff --git a/home/options_test.go b/home/options_test.go
new file mode 100644
index 00000000..afaa873f
--- /dev/null
+++ b/home/options_test.go
@@ -0,0 +1,251 @@
+package home
+
+import (
+ "fmt"
+ "testing"
+)
+
+func testParseOk(t *testing.T, ss ...string) options {
+ o, _, err := parse("", ss)
+ if err != nil {
+ t.Fatal(err.Error())
+ }
+ return o
+}
+
+func testParseErr(t *testing.T, descr string, ss ...string) {
+ _, _, err := parse("", ss)
+ if err == nil {
+ t.Fatalf("expected an error because %s but no error returned", descr)
+ }
+}
+
+func testParseParamMissing(t *testing.T, param string) {
+ testParseErr(t, fmt.Sprintf("%s parameter missing", param), param)
+}
+
+func TestParseVerbose(t *testing.T) {
+ if testParseOk(t).verbose {
+ t.Fatal("empty is not verbose")
+ }
+ if !testParseOk(t, "-v").verbose {
+ t.Fatal("-v is verbose")
+ }
+ if !testParseOk(t, "--verbose").verbose {
+ t.Fatal("--verbose is verbose")
+ }
+}
+
+func TestParseConfigFilename(t *testing.T) {
+ if testParseOk(t).configFilename != "" {
+ t.Fatal("empty is no config filename")
+ }
+ if testParseOk(t, "-c", "path").configFilename != "path" {
+ t.Fatal("-c is config filename")
+ }
+ testParseParamMissing(t, "-c")
+ if testParseOk(t, "--config", "path").configFilename != "path" {
+ t.Fatal("--configFilename is config filename")
+ }
+ testParseParamMissing(t, "--config")
+}
+
+func TestParseWorkDir(t *testing.T) {
+ if testParseOk(t).workDir != "" {
+ t.Fatal("empty is no work dir")
+ }
+ if testParseOk(t, "-w", "path").workDir != "path" {
+ t.Fatal("-w is work dir")
+ }
+ testParseParamMissing(t, "-w")
+ if testParseOk(t, "--work-dir", "path").workDir != "path" {
+ t.Fatal("--work-dir is work dir")
+ }
+ testParseParamMissing(t, "--work-dir")
+}
+
+func TestParseBindHost(t *testing.T) {
+ if testParseOk(t).bindHost != "" {
+ t.Fatal("empty is no host")
+ }
+ if testParseOk(t, "-h", "addr").bindHost != "addr" {
+ t.Fatal("-h is host")
+ }
+ testParseParamMissing(t, "-h")
+ if testParseOk(t, "--host", "addr").bindHost != "addr" {
+ t.Fatal("--host is host")
+ }
+ testParseParamMissing(t, "--host")
+}
+
+func TestParseBindPort(t *testing.T) {
+ if testParseOk(t).bindPort != 0 {
+ t.Fatal("empty is port 0")
+ }
+ if testParseOk(t, "-p", "65535").bindPort != 65535 {
+ t.Fatal("-p is port")
+ }
+ testParseParamMissing(t, "-p")
+ if testParseOk(t, "--port", "65535").bindPort != 65535 {
+ t.Fatal("--port is port")
+ }
+ testParseParamMissing(t, "--port")
+}
+
+func TestParseBindPortBad(t *testing.T) {
+ testParseErr(t, "not an int", "-p", "x")
+ testParseErr(t, "hex not supported", "-p", "0x100")
+ testParseErr(t, "port negative", "-p", "-1")
+ testParseErr(t, "port too high", "-p", "65536")
+ testParseErr(t, "port too high", "-p", "4294967297") // 2^32 + 1
+ testParseErr(t, "port too high", "-p", "18446744073709551617") // 2^64 + 1
+}
+
+func TestParseLogfile(t *testing.T) {
+ if testParseOk(t).logFile != "" {
+ t.Fatal("empty is no log file")
+ }
+ if testParseOk(t, "-l", "path").logFile != "path" {
+ t.Fatal("-l is log file")
+ }
+ if testParseOk(t, "--logfile", "path").logFile != "path" {
+ t.Fatal("--logfile is log file")
+ }
+}
+
+func TestParsePidfile(t *testing.T) {
+ if testParseOk(t).pidFile != "" {
+ t.Fatal("empty is no pid file")
+ }
+ if testParseOk(t, "--pidfile", "path").pidFile != "path" {
+ t.Fatal("--pidfile is pid file")
+ }
+}
+
+func TestParseCheckConfig(t *testing.T) {
+ if testParseOk(t).checkConfig {
+ t.Fatal("empty is not check config")
+ }
+ if !testParseOk(t, "--check-config").checkConfig {
+ t.Fatal("--check-config is check config")
+ }
+}
+
+func TestParseDisableUpdate(t *testing.T) {
+ if testParseOk(t).disableUpdate {
+ t.Fatal("empty is not disable update")
+ }
+ if !testParseOk(t, "--no-check-update").disableUpdate {
+ t.Fatal("--no-check-update is disable update")
+ }
+}
+
+func TestParseDisableMemoryOptimization(t *testing.T) {
+ if testParseOk(t).disableMemoryOptimization {
+ t.Fatal("empty is not disable update")
+ }
+ if !testParseOk(t, "--no-mem-optimization").disableMemoryOptimization {
+ t.Fatal("--no-mem-optimization is disable update")
+ }
+}
+
+func TestParseService(t *testing.T) {
+ if testParseOk(t).serviceControlAction != "" {
+ t.Fatal("empty is no service command")
+ }
+ if testParseOk(t, "-s", "command").serviceControlAction != "command" {
+ t.Fatal("-s is service command")
+ }
+ if testParseOk(t, "--service", "command").serviceControlAction != "command" {
+ t.Fatal("--service is service command")
+ }
+}
+
+func TestParseGLInet(t *testing.T) {
+ if testParseOk(t).glinetMode {
+ t.Fatal("empty is not GL-Inet mode")
+ }
+ if !testParseOk(t, "--glinet").glinetMode {
+ t.Fatal("--glinet is GL-Inet mode")
+ }
+}
+
+func TestParseUnknown(t *testing.T) {
+ testParseErr(t, "unknown word", "x")
+ testParseErr(t, "unknown short", "-x")
+ testParseErr(t, "unknown long", "--x")
+ testParseErr(t, "unknown triple", "---x")
+ testParseErr(t, "unknown plus", "+x")
+ testParseErr(t, "unknown dash", "-")
+}
+
+func testSerialize(t *testing.T, o options, ss ...string) {
+ result := serialize(o)
+ if len(result) != len(ss) {
+ t.Fatalf("expected %s but got %s", ss, result)
+ }
+ for i, r := range result {
+ if r != ss[i] {
+ t.Fatalf("expected %s but got %s", ss, result)
+ }
+ }
+}
+
+func TestSerializeEmpty(t *testing.T) {
+ testSerialize(t, options{})
+}
+
+func TestSerializeConfigFilename(t *testing.T) {
+ testSerialize(t, options{configFilename: "path"}, "-c", "path")
+}
+
+func TestSerializeWorkDir(t *testing.T) {
+ testSerialize(t, options{workDir: "path"}, "-w", "path")
+}
+
+func TestSerializeBindHost(t *testing.T) {
+ testSerialize(t, options{bindHost: "addr"}, "-h", "addr")
+}
+
+func TestSerializeBindPort(t *testing.T) {
+ testSerialize(t, options{bindPort: 666}, "-p", "666")
+}
+
+func TestSerializeLogfile(t *testing.T) {
+ testSerialize(t, options{logFile: "path"}, "-l", "path")
+}
+
+func TestSerializePidfile(t *testing.T) {
+ testSerialize(t, options{pidFile: "path"}, "--pidfile", "path")
+}
+
+func TestSerializeCheckConfig(t *testing.T) {
+ testSerialize(t, options{checkConfig: true}, "--check-config")
+}
+
+func TestSerializeDisableUpdate(t *testing.T) {
+ testSerialize(t, options{disableUpdate: true}, "--no-check-update")
+}
+
+func TestSerializeService(t *testing.T) {
+ testSerialize(t, options{serviceControlAction: "run"}, "-s", "run")
+}
+
+func TestSerializeGLInet(t *testing.T) {
+ testSerialize(t, options{glinetMode: true}, "--glinet")
+}
+
+func TestSerializeDisableMemoryOptimization(t *testing.T) {
+ testSerialize(t, options{disableMemoryOptimization: true}, "--no-mem-optimization")
+}
+
+func TestSerializeMultiple(t *testing.T) {
+ testSerialize(t, options{
+ serviceControlAction: "run",
+ configFilename: "config",
+ workDir: "work",
+ pidFile: "pid",
+ disableUpdate: true,
+ disableMemoryOptimization: true,
+ }, "-c", "config", "-w", "work", "-s", "run", "--pidfile", "pid", "--no-check-update", "--no-mem-optimization")
+}
diff --git a/home/service.go b/home/service.go
index 21c9cfc6..b48c743b 100644
--- a/home/service.go
+++ b/home/service.go
@@ -24,12 +24,14 @@ const (
// Represents the program that will be launched by a service or daemon
type program struct {
+ opts options
}
// Start should quickly start the program
func (p *program) Start(s service.Service) error {
// Start should not block. Do the actual work async.
- args := options{runningAsService: true}
+ args := p.opts
+ args.runningAsService = true
go run(args)
return nil
}
@@ -125,7 +127,8 @@ func sendSigReload() {
// run - this is a special command that is not supposed to be used directly
// it is specified when we register a service, and it indicates to the app
// that it is being run as a service/daemon.
-func handleServiceControlAction(action string) {
+func handleServiceControlAction(opts options) {
+ action := opts.serviceControlAction
log.Printf("Service control action: %s", action)
if action == "reload" {
@@ -137,15 +140,17 @@ func handleServiceControlAction(action string) {
if err != nil {
log.Fatal("Unable to find the path to the current directory")
}
+ runOpts := opts
+ runOpts.serviceControlAction = "run"
svcConfig := &service.Config{
Name: serviceName,
DisplayName: serviceDisplayName,
Description: serviceDescription,
WorkingDirectory: pwd,
- Arguments: []string{"-s", "run"},
+ Arguments: serialize(runOpts),
}
configureService(svcConfig)
- prg := &program{}
+ prg := &program{runOpts}
s, err := service.New(prg, svcConfig)
if err != nil {
log.Fatal(err)
diff --git a/home/tls.go b/home/tls.go
index 157cda2a..0b9e8f90 100644
--- a/home/tls.go
+++ b/home/tls.go
@@ -45,6 +45,7 @@ func tlsCreate(conf tlsConfigSettings) *TLSMod {
ServerName: conf.ServerName,
PortHTTPS: conf.PortHTTPS,
PortDNSOverTLS: conf.PortDNSOverTLS,
+ PortDNSOverQUIC: conf.PortDNSOverQUIC,
AllowUnencryptedDOH: conf.AllowUnencryptedDOH,
}}
}
@@ -267,6 +268,7 @@ func (t *TLSMod) handleTLSConfigure(w http.ResponseWriter, r *http.Request) {
t.conf.ForceHTTPS = data.ForceHTTPS
t.conf.PortHTTPS = data.PortHTTPS
t.conf.PortDNSOverTLS = data.PortDNSOverTLS
+ t.conf.PortDNSOverQUIC = data.PortDNSOverQUIC
t.conf.CertificateChain = data.CertificateChain
t.conf.CertificatePath = data.CertificatePath
t.conf.CertificateChainData = data.CertificateChainData
diff --git a/main.go b/main.go
index eebcd5c4..f712d6c0 100644
--- a/main.go
+++ b/main.go
@@ -4,10 +4,6 @@
package main
import (
- "os"
- "runtime/debug"
- "time"
-
"github.com/AdguardTeam/AdGuardHome/home"
)
@@ -21,32 +17,5 @@ var channel = "release"
var goarm = ""
func main() {
- memoryUsage()
-
home.Main(version, channel, goarm)
}
-
-// memoryUsage implements a couple of not really beautiful hacks which purpose is to
-// make OS reclaim the memory freed by AdGuard Home as soon as possible.
-func memoryUsage() {
- debug.SetGCPercent(10)
-
- // madvdontneed: setting madvdontneed=1 will use MADV_DONTNEED
- // instead of MADV_FREE on Linux when returning memory to the
- // kernel. This is less efficient, but causes RSS numbers to drop
- // more quickly.
- _ = os.Setenv("GODEBUG", "madvdontneed=1")
-
- // periodically call "debug.FreeOSMemory" so
- // that the OS could reclaim the free memory
- go func() {
- ticker := time.NewTicker(15 * time.Second)
- for {
- select {
- case t := <-ticker.C:
- t.Second()
- debug.FreeOSMemory()
- }
- }
- }()
-}
diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml
index 52791130..c50d9485 100644
--- a/openapi/openapi.yaml
+++ b/openapi/openapi.yaml
@@ -997,6 +997,8 @@ components:
example:
- tls://1.1.1.1
- tls://1.0.0.1
+ upstream_dns_file:
+ type: string
protection_enabled:
type: boolean
dhcp_available:
@@ -1007,6 +1009,7 @@ components:
type: string
enum:
- default
+ - refused
- nxdomain
- null_ip
- custom_ip
@@ -1563,6 +1566,11 @@ components:
format: int32
example: 853
description: DNS-over-TLS port. If 0, DOT will be disabled.
+ port_dns_over_quic:
+ type: integer
+ format: int32
+ example: 784
+ description: DNS-over-QUIC port. If 0, DOQ will be disabled.
certificate_chain:
type: string
description: Base64 string with PEM-encoded certificates chain
diff --git a/util/auto_hosts.go b/util/auto_hosts.go
index b12acd81..1cec9c1a 100644
--- a/util/auto_hosts.go
+++ b/util/auto_hosts.go
@@ -10,9 +10,10 @@ import (
"strings"
"sync"
+ "github.com/miekg/dns"
+
"github.com/AdguardTeam/golibs/log"
"github.com/fsnotify/fsnotify"
- "github.com/miekg/dns"
)
type onChangedT func()
@@ -62,6 +63,9 @@ func (a *AutoHosts) Init(hostsFn string) {
a.hostsDirs = append(a.hostsDirs, "/tmp/hosts") // OpenWRT: "/tmp/hosts/dhcp.cfg01411c"
}
+ // Load hosts initially
+ a.updateHosts()
+
var err error
a.watcher, err = fsnotify.NewWatcher()
if err != nil {
@@ -102,6 +106,62 @@ func (a *AutoHosts) Close() {
}
}
+// Process - get the list of IP addresses for the hostname
+// Return nil if not found
+func (a *AutoHosts) Process(host string, qtype uint16) []net.IP {
+ if qtype == dns.TypePTR {
+ return nil
+ }
+
+ var ipsCopy []net.IP
+ a.lock.Lock()
+ ips, _ := a.table[host]
+ if len(ips) != 0 {
+ ipsCopy = make([]net.IP, len(ips))
+ copy(ipsCopy, ips)
+ }
+ a.lock.Unlock()
+
+ log.Debug("AutoHosts: answer: %s -> %v", host, ipsCopy)
+ return ipsCopy
+}
+
+// ProcessReverse - process PTR request
+// Return "" if not found or an error occurred
+func (a *AutoHosts) ProcessReverse(addr string, qtype uint16) string {
+ if qtype != dns.TypePTR {
+ return ""
+ }
+
+ ipReal := DNSUnreverseAddr(addr)
+ if ipReal == nil {
+ return "" // invalid IP in question
+ }
+ ipStr := ipReal.String()
+
+ a.lock.Lock()
+ host := a.tableReverse[ipStr]
+ a.lock.Unlock()
+
+ if len(host) == 0 {
+ return "" // not found
+ }
+
+ log.Debug("AutoHosts: reverse-lookup: %s -> %s", addr, host)
+ return host
+}
+
+// List - get "IP -> hostname" table. Thread-safe.
+func (a *AutoHosts) List() map[string]string {
+ table := make(map[string]string)
+ a.lock.Lock()
+ for k, v := range a.tableReverse {
+ table[k] = v
+ }
+ a.lock.Unlock()
+ return table
+}
+
// update table
func (a *AutoHosts) updateTable(table map[string][]net.IP, host string, ipAddr net.IP) {
ips, ok := table[host]
@@ -275,59 +335,3 @@ func (a *AutoHosts) updateHosts() {
a.notify()
}
-
-// Process - get the list of IP addresses for the hostname
-// Return nil if not found
-func (a *AutoHosts) Process(host string, qtype uint16) []net.IP {
- if qtype == dns.TypePTR {
- return nil
- }
-
- var ipsCopy []net.IP
- a.lock.Lock()
- ips, _ := a.table[host]
- if len(ips) != 0 {
- ipsCopy = make([]net.IP, len(ips))
- copy(ipsCopy, ips)
- }
- a.lock.Unlock()
-
- log.Debug("AutoHosts: answer: %s -> %v", host, ipsCopy)
- return ipsCopy
-}
-
-// ProcessReverse - process PTR request
-// Return "" if not found or an error occurred
-func (a *AutoHosts) ProcessReverse(addr string, qtype uint16) string {
- if qtype != dns.TypePTR {
- return ""
- }
-
- ipReal := DNSUnreverseAddr(addr)
- if ipReal == nil {
- return "" // invalid IP in question
- }
- ipStr := ipReal.String()
-
- a.lock.Lock()
- host := a.tableReverse[ipStr]
- a.lock.Unlock()
-
- if len(host) == 0 {
- return "" // not found
- }
-
- log.Debug("AutoHosts: reverse-lookup: %s -> %s", addr, host)
- return host
-}
-
-// List - get "IP -> hostname" table. Thread-safe.
-func (a *AutoHosts) List() map[string]string {
- table := make(map[string]string)
- a.lock.Lock()
- for k, v := range a.tableReverse {
- table[k] = v
- }
- a.lock.Unlock()
- return table
-}
diff --git a/util/auto_hosts_test.go b/util/auto_hosts_test.go
index ea2e43ad..efd94b99 100644
--- a/util/auto_hosts_test.go
+++ b/util/auto_hosts_test.go
@@ -34,9 +34,6 @@ func TestAutoHostsResolution(t *testing.T) {
ah.Init(f.Name())
- // Update from the hosts file
- ah.updateHosts()
-
// Existing host
ips := ah.Process("localhost", dns.TypeA)
assert.NotNil(t, ips)
@@ -79,7 +76,6 @@ func TestAutoHostsFSNotify(t *testing.T) {
// Init
_, _ = f.WriteString(" 127.0.0.1 host localhost \n")
ah.Init(f.Name())
- ah.updateHosts()
// Unknown host
ips := ah.Process("newhost", dns.TypeA)