Merge: + dnsforward: add access settings for blocking DNS requests

Close #728

* commit 'e4532a27cd2a6f92aaf724fddbffa00fcecb064c':
  - openapi: correct format
  + client: handle access settings
  * go.mod: update dnsproxy
  + control: /access/list, /access/set handlers
  + dnsforward: add access settings for blocking DNS requests
This commit is contained in:
Simon Zolin 2019-06-03 15:04:52 +03:00
commit 1d09ff0562
19 changed files with 607 additions and 20 deletions

View File

@ -24,6 +24,9 @@ Contents:
* "Enable DHCP" command * "Enable DHCP" command
* Static IP check/set * Static IP check/set
* Add a static lease * Add a static lease
* DNS access settings
* List access settings
* Set access settings
## First startup ## First startup
@ -626,3 +629,47 @@ Response:
Error response (Client not found): Error response (Client not found):
400 400
## DNS access settings
There are low-level settings that can block undesired DNS requests. "Blocking" means not responding to request.
There are 3 types of access settings:
* allowed_clients: Only these clients are allowed to make DNS requests.
* disallowed_clients: These clients are not allowed to make DNS requests.
* blocked_hosts: These hosts are not allowed to be resolved by a DNS request.
### List access settings
Request:
GET /control/access/list
Response:
200 OK
{
allowed_clients: ["127.0.0.1", ...]
disallowed_clients: ["127.0.0.1", ...]
blocked_hosts: ["host.com", ...]
}
### Set access settings
Request:
POST /control/access/set
{
allowed_clients: ["127.0.0.1", ...]
disallowed_clients: ["127.0.0.1", ...]
blocked_hosts: ["host.com", ...]
}
Response:
200 OK

View File

@ -298,5 +298,14 @@
"clients_not_found": "No clients found", "clients_not_found": "No clients found",
"client_confirm_delete": "Are you sure you want to delete client \"{{key}}\"?", "client_confirm_delete": "Are you sure you want to delete client \"{{key}}\"?",
"auto_clients_title": "Clients (runtime)", "auto_clients_title": "Clients (runtime)",
"auto_clients_desc": "Data on the clients that use AdGuard Home, but not stored in the configuration" "auto_clients_desc": "Data on the clients that use AdGuard Home, but not stored in the configuration",
"access_title": "Access settings",
"access_desc": "Here you can configure access rules for the AdGuard Home DNS server.",
"access_allowed_title": "Allowed clients",
"access_allowed_desc": "A list of CIDR or IP addresses. If configured, AdGuard Home will accept requests from these IP addresses only.",
"access_disallowed_title": "Disallowed clients",
"access_disallowed_desc": "A list of CIDR or IP addresses. If configured, AdGuard Home will drop requests from these IP addresses.",
"access_blocked_title": "Blocked domains",
"access_blocked_desc": "Don't confuse this with filters. AdGuard Home will drop DNS queries with these domains in query's question.",
"access_settings_saved": "Access settings successfully saved"
} }

View File

@ -0,0 +1,45 @@
import { createAction } from 'redux-actions';
import Api from '../api/Api';
import { addErrorToast, addSuccessToast } from './index';
import { normalizeTextarea } from '../helpers/helpers';
const apiClient = new Api();
export const getAccessListRequest = createAction('GET_ACCESS_LIST_REQUEST');
export const getAccessListFailure = createAction('GET_ACCESS_LIST_FAILURE');
export const getAccessListSuccess = createAction('GET_ACCESS_LIST_SUCCESS');
export const getAccessList = () => async (dispatch) => {
dispatch(getAccessListRequest());
try {
const data = await apiClient.getAccessList();
dispatch(getAccessListSuccess(data));
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(getAccessListFailure());
}
};
export const setAccessListRequest = createAction('SET_ACCESS_LIST_REQUEST');
export const setAccessListFailure = createAction('SET_ACCESS_LIST_FAILURE');
export const setAccessListSuccess = createAction('SET_ACCESS_LIST_SUCCESS');
export const setAccessList = config => async (dispatch) => {
dispatch(setAccessListRequest());
try {
const { allowed_clients, disallowed_clients, blocked_hosts } = config;
const values = {
allowed_clients: (allowed_clients && normalizeTextarea(allowed_clients)) || [],
disallowed_clients: (disallowed_clients && normalizeTextarea(disallowed_clients)) || [],
blocked_hosts: (blocked_hosts && normalizeTextarea(blocked_hosts)) || [],
};
await apiClient.setAccessList(values);
dispatch(setAccessListSuccess());
dispatch(addSuccessToast('access_settings_saved'));
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(setAccessListFailure());
}
};

View File

@ -460,4 +460,22 @@ export default class Api {
}; };
return this.makeRequest(path, method, parameters); return this.makeRequest(path, method, parameters);
} }
// DNS access settings
ACCESS_LIST = { path: 'access/list', method: 'GET' };
ACCESS_SET = { path: 'access/set', method: 'POST' };
getAccessList() {
const { path, method } = this.ACCESS_LIST;
return this.makeRequest(path, method);
}
setAccessList(config) {
const { path, method } = this.ACCESS_SET;
const parameters = {
data: config,
headers: { 'Content-Type': 'application/json' },
};
return this.makeRequest(path, method, parameters);
}
} }

View File

@ -0,0 +1,80 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Field, reduxForm } from 'redux-form';
import { Trans, withNamespaces } from 'react-i18next';
import flow from 'lodash/flow';
const Form = (props) => {
const { handleSubmit, submitting, invalid } = props;
return (
<form onSubmit={handleSubmit}>
<div className="form__group mb-5">
<label className="form__label form__label--with-desc" htmlFor="allowed_clients">
<Trans>access_allowed_title</Trans>
</label>
<div className="form__desc form__desc--top">
<Trans>access_allowed_desc</Trans>
</div>
<Field
id="allowed_clients"
name="allowed_clients"
component="textarea"
type="text"
className="form-control form-control--textarea"
/>
</div>
<div className="form__group mb-5">
<label className="form__label form__label--with-desc" htmlFor="disallowed_clients">
<Trans>access_disallowed_title</Trans>
</label>
<div className="form__desc form__desc--top">
<Trans>access_disallowed_desc</Trans>
</div>
<Field
id="disallowed_clients"
name="disallowed_clients"
component="textarea"
type="text"
className="form-control form-control--textarea"
/>
</div>
<div className="form__group mb-5">
<label className="form__label form__label--with-desc" htmlFor="blocked_hosts">
<Trans>access_blocked_title</Trans>
</label>
<div className="form__desc form__desc--top">
<Trans>access_blocked_desc</Trans>
</div>
<Field
id="blocked_hosts"
name="blocked_hosts"
component="textarea"
type="text"
className="form-control form-control--textarea"
/>
</div>
<div className="card-actions">
<div className="btn-list">
<button
type="submit"
className="btn btn-success btn-standard"
disabled={submitting || invalid}
>
<Trans>save_config</Trans>
</button>
</div>
</div>
</form>
);
};
Form.propTypes = {
handleSubmit: PropTypes.func,
submitting: PropTypes.bool,
invalid: PropTypes.bool,
initialValues: PropTypes.object,
t: PropTypes.func,
};
export default flow([withNamespaces(), reduxForm({ form: 'accessForm' })])(Form);

View File

@ -0,0 +1,43 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { withNamespaces } from 'react-i18next';
import Form from './Form';
import Card from '../../ui/Card';
class Access extends Component {
handleFormSubmit = (values) => {
this.props.setAccessList(values);
};
render() {
const { t, access } = this.props;
const {
processing,
processingSet,
...values
} = access;
return (
<Card
title={t('access_title')}
subtitle={t('access_desc')}
bodyType="card-body box-body--settings"
>
<Form
initialValues={values}
onSubmit={this.handleFormSubmit}
/>
</Card>
);
}
}
Access.propTypes = {
access: PropTypes.object.isRequired,
setAccessList: PropTypes.func.isRequired,
t: PropTypes.func.isRequired,
};
export default withNamespaces()(Access);

View File

@ -63,6 +63,10 @@
font-weight: 700; font-weight: 700;
} }
.form__label--with-desc {
margin-bottom: 0;
}
.form__status { .form__status {
margin-top: 10px; margin-top: 10px;
font-size: 14px; font-size: 14px;

View File

@ -62,7 +62,7 @@ let Form = (props) => {
</div> </div>
<div className="col-12"> <div className="col-12">
<div className="form__group"> <div className="form__group">
<label className="form__label" htmlFor="bootstrap_dns"> <label className="form__label form__label--with-desc" htmlFor="bootstrap_dns">
<Trans>bootstrap_dns</Trans> <Trans>bootstrap_dns</Trans>
</label> </label>
<div className="form__desc form__desc--top"> <div className="form__desc form__desc--top">

View File

@ -7,6 +7,7 @@ import Dhcp from './Dhcp';
import Encryption from './Encryption'; import Encryption from './Encryption';
import Clients from './Clients'; import Clients from './Clients';
import AutoClients from './Clients/AutoClients'; import AutoClients from './Clients/AutoClients';
import Access from './Access';
import Checkbox from '../ui/Checkbox'; import Checkbox from '../ui/Checkbox';
import Loading from '../ui/Loading'; import Loading from '../ui/Loading';
import PageTitle from '../ui/PageTitle'; import PageTitle from '../ui/PageTitle';
@ -43,6 +44,7 @@ class Settings extends Component {
this.props.getDhcpStatus(); this.props.getDhcpStatus();
this.props.getDhcpInterfaces(); this.props.getDhcpInterfaces();
this.props.getTlsStatus(); this.props.getTlsStatus();
this.props.getAccessList();
} }
renderSettings = (settings) => { renderSettings = (settings) => {
@ -68,7 +70,7 @@ class Settings extends Component {
render() { render() {
const { const {
settings, dashboard, clients, t, settings, dashboard, clients, access, t,
} = this.props; } = this.props;
return ( return (
<Fragment> <Fragment>
@ -117,6 +119,7 @@ class Settings extends Component {
/> />
</Fragment> </Fragment>
)} )}
<Access access={access} setAccessList={this.props.setAccessList} />
<Encryption <Encryption
encryption={this.props.encryption} encryption={this.props.encryption}
setTlsConfig={this.props.setTlsConfig} setTlsConfig={this.props.setTlsConfig}

View File

@ -26,6 +26,10 @@ import {
deleteClient, deleteClient,
toggleClientModal, toggleClientModal,
} from '../actions/clients'; } from '../actions/clients';
import {
getAccessList,
setAccessList,
} from '../actions/access';
import Settings from '../components/Settings'; import Settings from '../components/Settings';
const mapStateToProps = (state) => { const mapStateToProps = (state) => {
@ -35,6 +39,7 @@ const mapStateToProps = (state) => {
dhcp, dhcp,
encryption, encryption,
clients, clients,
access,
} = state; } = state;
const props = { const props = {
settings, settings,
@ -42,6 +47,7 @@ const mapStateToProps = (state) => {
dhcp, dhcp,
encryption, encryption,
clients, clients,
access,
}; };
return props; return props;
}; };
@ -68,6 +74,8 @@ const mapDispatchToProps = {
addStaticLease, addStaticLease,
removeStaticLease, removeStaticLease,
toggleLeaseModal, toggleLeaseModal,
getAccessList,
setAccessList,
}; };
export default connect( export default connect(

View File

@ -0,0 +1,43 @@
import { handleActions } from 'redux-actions';
import * as actions from '../actions/access';
const access = handleActions(
{
[actions.getAccessListRequest]: state => ({ ...state, processing: true }),
[actions.getAccessListFailure]: state => ({ ...state, processing: false }),
[actions.getAccessListSuccess]: (state, { payload }) => {
const {
allowed_clients,
disallowed_clients,
blocked_hosts,
} = payload;
const newState = {
...state,
allowed_clients: allowed_clients.join('\n'),
disallowed_clients: disallowed_clients.join('\n'),
blocked_hosts: blocked_hosts.join('\n'),
};
return newState;
},
[actions.setAccessListRequest]: state => ({ ...state, processingSet: true }),
[actions.setAccessListFailure]: state => ({ ...state, processingSet: false }),
[actions.setAccessListSuccess]: (state) => {
const newState = {
...state,
processingSet: false,
};
return newState;
},
},
{
processing: true,
processingSet: false,
allowed_clients: null,
disallowed_clients: null,
blocked_hosts: null,
},
);
export default access;

View File

@ -8,6 +8,7 @@ import * as actions from '../actions';
import toasts from './toasts'; import toasts from './toasts';
import encryption from './encryption'; import encryption from './encryption';
import clients from './clients'; import clients from './clients';
import access from './access';
const settings = handleActions({ const settings = handleActions({
[actions.initSettingsRequest]: state => ({ ...state, processing: true }), [actions.initSettingsRequest]: state => ({ ...state, processing: true }),
@ -418,6 +419,7 @@ export default combineReducers({
dhcp, dhcp,
encryption, encryption,
clients, clients,
access,
loadingBar: loadingBarReducer, loadingBar: loadingBarReducer,
form: formReducer, form: formReducer,
}); });

View File

@ -996,6 +996,9 @@ func registerControlHandlers() {
http.HandleFunc("/control/dhcp/add_static_lease", postInstall(optionalAuth(ensurePOST(handleDHCPAddStaticLease)))) http.HandleFunc("/control/dhcp/add_static_lease", postInstall(optionalAuth(ensurePOST(handleDHCPAddStaticLease))))
http.HandleFunc("/control/dhcp/remove_static_lease", postInstall(optionalAuth(ensurePOST(handleDHCPRemoveStaticLease)))) http.HandleFunc("/control/dhcp/remove_static_lease", postInstall(optionalAuth(ensurePOST(handleDHCPRemoveStaticLease))))
http.HandleFunc("/control/access/list", postInstall(optionalAuth(ensureGET(handleAccessList))))
http.HandleFunc("/control/access/set", postInstall(optionalAuth(ensurePOST(handleAccessSet))))
RegisterTLSHandlers() RegisterTLSHandlers()
RegisterClientsHandlers() RegisterClientsHandlers()

87
control_access.go Normal file
View File

@ -0,0 +1,87 @@
package main
import (
"encoding/json"
"net"
"net/http"
"github.com/AdguardTeam/golibs/log"
)
type accessListJSON struct {
AllowedClients []string `json:"allowed_clients"`
DisallowedClients []string `json:"disallowed_clients"`
BlockedHosts []string `json:"blocked_hosts"`
}
func handleAccessList(w http.ResponseWriter, r *http.Request) {
log.Tracef("%s %v", r.Method, r.URL)
controlLock.Lock()
j := accessListJSON{
AllowedClients: config.DNS.AllowedClients,
DisallowedClients: config.DNS.DisallowedClients,
BlockedHosts: config.DNS.BlockedHosts,
}
controlLock.Unlock()
w.Header().Set("Content-Type", "application/json")
err := json.NewEncoder(w).Encode(j)
if err != nil {
httpError(w, http.StatusInternalServerError, "json.Encode: %s", err)
return
}
}
func checkIPCIDRArray(src []string) error {
for _, s := range src {
ip := net.ParseIP(s)
if ip != nil {
continue
}
_, _, err := net.ParseCIDR(s)
if err != nil {
return err
}
}
return nil
}
func handleAccessSet(w http.ResponseWriter, r *http.Request) {
log.Tracef("%s %v", r.Method, r.URL)
j := accessListJSON{}
err := json.NewDecoder(r.Body).Decode(&j)
if err != nil {
httpError(w, http.StatusBadRequest, "json.Decode: %s", err)
return
}
err = checkIPCIDRArray(j.AllowedClients)
if err == nil {
err = checkIPCIDRArray(j.DisallowedClients)
}
if err != nil {
httpError(w, http.StatusBadRequest, "%s", err)
return
}
config.Lock()
config.DNS.AllowedClients = j.AllowedClients
config.DNS.DisallowedClients = j.DisallowedClients
config.DNS.BlockedHosts = j.BlockedHosts
config.Unlock()
log.Tracef("Update access lists: %d, %d, %d",
len(j.AllowedClients), len(j.DisallowedClients), len(j.BlockedHosts))
err = writeAllConfigsAndReloadDNS()
if err != nil {
httpError(w, http.StatusBadRequest, "%s", err)
return
}
returnOK(w)
}

View File

@ -43,6 +43,12 @@ type Server struct {
stats *stats // General server statistics stats *stats // General server statistics
once sync.Once once sync.Once
AllowedClients map[string]bool // IP addresses of whitelist clients
DisallowedClients map[string]bool // IP addresses of clients that should be blocked
AllowedClientsIPNet []net.IPNet // CIDRs of whitelist clients
DisallowedClientsIPNet []net.IPNet // CIDRs of clients that should be blocked
BlockedHosts map[string]bool // hosts that should be blocked
sync.RWMutex sync.RWMutex
conf ServerConfig conf ServerConfig
} }
@ -70,6 +76,10 @@ type FilteringConfig struct {
BootstrapDNS []string `yaml:"bootstrap_dns"` // a list of bootstrap DNS for DoH and DoT (plain DNS only) 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 AllServers bool `yaml:"all_servers"` // if true, parallel queries to all configured upstream servers are enabled
AllowedClients []string `yaml:"allowed_clients"` // IP addresses of whitelist clients
DisallowedClients []string `yaml:"disallowed_clients"` // IP addresses of clients that should be blocked
BlockedHosts []string `yaml:"blocked_hosts"` // hosts that should be blocked
dnsfilter.Config `yaml:",inline"` dnsfilter.Config `yaml:",inline"`
} }
@ -121,6 +131,34 @@ func (s *Server) Start(config *ServerConfig) error {
return s.startInternal(config) return s.startInternal(config)
} }
func convertArrayToMap(dst *map[string]bool, src []string) {
*dst = make(map[string]bool)
for _, s := range src {
(*dst)[s] = true
}
}
// Split array of IP or CIDR into 2 containers for fast search
func processIPCIDRArray(dst *map[string]bool, dstIPNet *[]net.IPNet, src []string) error {
*dst = make(map[string]bool)
for _, s := range src {
ip := net.ParseIP(s)
if ip != nil {
(*dst)[s] = true
continue
}
_, ipnet, err := net.ParseCIDR(s)
if err != nil {
return err
}
*dstIPNet = append(*dstIPNet, *ipnet)
}
return nil
}
// startInternal starts without locking // startInternal starts without locking
func (s *Server) startInternal(config *ServerConfig) error { func (s *Server) startInternal(config *ServerConfig) error {
if config != nil { if config != nil {
@ -167,10 +205,23 @@ func (s *Server) startInternal(config *ServerConfig) error {
CacheEnabled: true, CacheEnabled: true,
Upstreams: s.conf.Upstreams, Upstreams: s.conf.Upstreams,
DomainsReservedUpstreams: s.conf.DomainsReservedUpstreams, DomainsReservedUpstreams: s.conf.DomainsReservedUpstreams,
Handler: s.handleDNSRequest, BeforeRequestHandler: s.beforeRequestHandler,
RequestHandler: s.handleDNSRequest,
AllServers: s.conf.AllServers, AllServers: s.conf.AllServers,
} }
err = processIPCIDRArray(&s.AllowedClients, &s.AllowedClientsIPNet, s.conf.AllowedClients)
if err != nil {
return err
}
err = processIPCIDRArray(&s.DisallowedClients, &s.DisallowedClientsIPNet, s.conf.DisallowedClients)
if err != nil {
return err
}
convertArrayToMap(&s.BlockedHosts, s.conf.BlockedHosts)
if s.conf.TLSListenAddr != nil && s.conf.CertificateChain != "" && s.conf.PrivateKey != "" { if s.conf.TLSListenAddr != nil && s.conf.CertificateChain != "" && s.conf.PrivateKey != "" {
proxyConfig.TLSListenAddr = s.conf.TLSListenAddr proxyConfig.TLSListenAddr = s.conf.TLSListenAddr
keypair, err := tls.X509KeyPair([]byte(s.conf.CertificateChain), []byte(s.conf.PrivateKey)) keypair, err := tls.X509KeyPair([]byte(s.conf.CertificateChain), []byte(s.conf.PrivateKey))
@ -321,6 +372,67 @@ func (s *Server) GetStatsHistory(timeUnit time.Duration, startTime time.Time, en
return s.stats.getStatsHistory(timeUnit, startTime, endTime) return s.stats.getStatsHistory(timeUnit, startTime, endTime)
} }
// Return TRUE if this client should be blocked
func (s *Server) isBlockedIP(ip string) bool {
if len(s.AllowedClients) != 0 || len(s.AllowedClientsIPNet) != 0 {
_, ok := s.AllowedClients[ip]
if ok {
return false
}
if len(s.AllowedClientsIPNet) != 0 {
ipAddr := net.ParseIP(ip)
for _, ipnet := range s.AllowedClientsIPNet {
if ipnet.Contains(ipAddr) {
return false
}
}
}
return true
}
_, ok := s.DisallowedClients[ip]
if ok {
return true
}
if len(s.DisallowedClientsIPNet) != 0 {
ipAddr := net.ParseIP(ip)
for _, ipnet := range s.DisallowedClientsIPNet {
if ipnet.Contains(ipAddr) {
return true
}
}
}
return false
}
// Return TRUE if this domain should be blocked
func (s *Server) isBlockedDomain(host string) bool {
_, ok := s.BlockedHosts[host]
return ok
}
func (s *Server) beforeRequestHandler(p *proxy.Proxy, d *proxy.DNSContext) (bool, error) {
ip, _, _ := net.SplitHostPort(d.Addr.String())
if s.isBlockedIP(ip) {
log.Tracef("Client IP %s is blocked by settings", ip)
return false, nil
}
if len(d.Req.Question) == 1 {
host := strings.TrimSuffix(d.Req.Question[0].Name, ".")
if s.isBlockedDomain(host) {
log.Tracef("Domain %s is blocked by settings", host)
return false, nil
}
}
return true, nil
}
// handleDNSRequest filters the incoming DNS requests and writes them to the query log // handleDNSRequest filters the incoming DNS requests and writes them to the query log
func (s *Server) handleDNSRequest(p *proxy.Proxy, d *proxy.DNSContext) error { func (s *Server) handleDNSRequest(p *proxy.Proxy, d *proxy.DNSContext) error {
start := time.Now() start := time.Now()

View File

@ -622,3 +622,72 @@ func publicKey(priv interface{}) interface{} {
return nil return nil
} }
} }
func TestIsBlockedIPAllowed(t *testing.T) {
s := createTestServer(t)
s.conf.AllowedClients = []string{"1.1.1.1", "2.2.0.0/16"}
err := s.Start(nil)
defer removeDataDir(t)
if err != nil {
t.Fatalf("Failed to start server: %s", err)
}
if s.isBlockedIP("1.1.1.1") {
t.Fatalf("isBlockedIP")
}
if !s.isBlockedIP("1.1.1.2") {
t.Fatalf("isBlockedIP")
}
if s.isBlockedIP("2.2.1.1") {
t.Fatalf("isBlockedIP")
}
if !s.isBlockedIP("2.3.1.1") {
t.Fatalf("isBlockedIP")
}
}
func TestIsBlockedIPDisallowed(t *testing.T) {
s := createTestServer(t)
s.conf.DisallowedClients = []string{"1.1.1.1", "2.2.0.0/16"}
err := s.Start(nil)
defer removeDataDir(t)
if err != nil {
t.Fatalf("Failed to start server: %s", err)
}
if !s.isBlockedIP("1.1.1.1") {
t.Fatalf("isBlockedIP")
}
if s.isBlockedIP("1.1.1.2") {
t.Fatalf("isBlockedIP")
}
if !s.isBlockedIP("2.2.1.1") {
t.Fatalf("isBlockedIP")
}
if s.isBlockedIP("2.3.1.1") {
t.Fatalf("isBlockedIP")
}
}
func TestIsBlockedIPBlockedDomain(t *testing.T) {
s := createTestServer(t)
s.conf.BlockedHosts = []string{"host1", "host2"}
err := s.Start(nil)
defer removeDataDir(t)
if err != nil {
t.Fatalf("Failed to start server: %s", err)
}
if !s.isBlockedDomain("host1") {
t.Fatalf("isBlockedDomain")
}
if !s.isBlockedDomain("host2") {
t.Fatalf("isBlockedDomain")
}
if s.isBlockedDomain("host3") {
t.Fatalf("isBlockedDomain")
}
}

8
go.mod
View File

@ -3,7 +3,7 @@ module github.com/AdguardTeam/AdGuardHome
go 1.12 go 1.12
require ( require (
github.com/AdguardTeam/dnsproxy v0.12.0 github.com/AdguardTeam/dnsproxy v0.14.0
github.com/AdguardTeam/golibs v0.1.3 github.com/AdguardTeam/golibs v0.1.3
github.com/AdguardTeam/urlfilter v0.3.0 github.com/AdguardTeam/urlfilter v0.3.0
github.com/NYTimes/gziphandler v1.1.1 github.com/NYTimes/gziphandler v1.1.1
@ -14,11 +14,11 @@ require (
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1 // indirect github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1 // indirect
github.com/kardianos/service v0.0.0-20181115005516-4c239ee84e7b github.com/kardianos/service v0.0.0-20181115005516-4c239ee84e7b
github.com/krolaw/dhcp4 v0.0.0-20180925202202-7cead472c414 github.com/krolaw/dhcp4 v0.0.0-20180925202202-7cead472c414
github.com/miekg/dns v1.1.1 github.com/miekg/dns v1.1.8
github.com/sparrc/go-ping v0.0.0-20181106165434-ef3ab45e41b0 github.com/sparrc/go-ping v0.0.0-20181106165434-ef3ab45e41b0
github.com/stretchr/testify v1.3.0 github.com/stretchr/testify v1.3.0
golang.org/x/net v0.0.0-20190313220215-9f648a60d977 golang.org/x/net v0.0.0-20190424112056-4829fb13d2c6
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a golang.org/x/sys v0.0.0-20190424160641-4347357a82bc
gopkg.in/asaskevich/govalidator.v4 v4.0.0-20160518190739-766470278477 gopkg.in/asaskevich/govalidator.v4 v4.0.0-20160518190739-766470278477
gopkg.in/yaml.v2 v2.2.1 gopkg.in/yaml.v2 v2.2.1
) )

34
go.sum
View File

@ -1,5 +1,5 @@
github.com/AdguardTeam/dnsproxy v0.12.0 h1:BPgv2PlH2u4xakFcaW4EqU3Visk1BNidrqGSgxe5Qzg= github.com/AdguardTeam/dnsproxy v0.14.0 h1:ubB5031Oc8TfOWxRpYYDx0Lt181jiNGOfiOgEN5VJys=
github.com/AdguardTeam/dnsproxy v0.12.0/go.mod h1:lcZM2QPwcWGEL3pz8RYy06nQdbjj4pr+94H45jnVSHg= github.com/AdguardTeam/dnsproxy v0.14.0/go.mod h1:50//JYIOMRnQnq0GQhvg516seqb5vjjyMIk+Z3RYy/s=
github.com/AdguardTeam/golibs v0.1.2/go.mod h1:b0XkhgIcn2TxwX6C5AQMtpIFAgjPehNgxJErWkwA3ko= github.com/AdguardTeam/golibs v0.1.2/go.mod h1:b0XkhgIcn2TxwX6C5AQMtpIFAgjPehNgxJErWkwA3ko=
github.com/AdguardTeam/golibs v0.1.3 h1:hmapdTtMtIk3T8eQDwTOLdqZLGDKNKk9325uC8z12xg= github.com/AdguardTeam/golibs v0.1.3 h1:hmapdTtMtIk3T8eQDwTOLdqZLGDKNKk9325uC8z12xg=
github.com/AdguardTeam/golibs v0.1.3/go.mod h1:b0XkhgIcn2TxwX6C5AQMtpIFAgjPehNgxJErWkwA3ko= github.com/AdguardTeam/golibs v0.1.3/go.mod h1:b0XkhgIcn2TxwX6C5AQMtpIFAgjPehNgxJErWkwA3ko=
@ -13,8 +13,8 @@ github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmH
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyYYufQuIbVqRawmubW3OFGqK1ekw= github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyYYufQuIbVqRawmubW3OFGqK1ekw=
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635/go.mod h1:lmLxL+FV291OopO93Bwf9fQLQeLyt33VJRUg5VJ30us= github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635/go.mod h1:lmLxL+FV291OopO93Bwf9fQLQeLyt33VJRUg5VJ30us=
github.com/ameshkov/dnscrypt v1.0.6 h1:55wfnNF8c4E3JXDNlwPl2Pbs7UPPIh+kI6KK3THqYS0= github.com/ameshkov/dnscrypt v1.0.7 h1:7LS9wiC/6c00H3ZdZOlwQSYGTJvs12g5ui9D1VSZ2aQ=
github.com/ameshkov/dnscrypt v1.0.6/go.mod h1:ZvT9LaNaJfDNXKIbkYFf24HUgHuQR6MNT6nwVvN4jMQ= github.com/ameshkov/dnscrypt v1.0.7/go.mod h1:rA74ASZ0j4JqPWaiN64hN97QXJ/zu5Kb2xgn295VzWQ=
github.com/ameshkov/dnsstamps v1.0.1 h1:LhGvgWDzhNJh+kBQd/AfUlq1vfVe109huiXw4JhnPug= 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/ameshkov/dnsstamps v1.0.1/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A=
github.com/ameshkov/goproxy v0.0.0-20190328085534-e9f6fabc24d4/go.mod h1:tKA6C/1BQYejT7L6ZX0klDrqloYenYETv3BCk8xCbrQ= github.com/ameshkov/goproxy v0.0.0-20190328085534-e9f6fabc24d4/go.mod h1:tKA6C/1BQYejT7L6ZX0klDrqloYenYETv3BCk8xCbrQ=
@ -55,8 +55,8 @@ github.com/krolaw/dhcp4 v0.0.0-20180925202202-7cead472c414 h1:6wnYc2S/lVM7BvR32B
github.com/krolaw/dhcp4 v0.0.0-20180925202202-7cead472c414/go.mod h1:0AqAH3ZogsCrvrtUpvc6EtVKbc3w6xwZhkvGLuqyi3o= github.com/krolaw/dhcp4 v0.0.0-20180925202202-7cead472c414/go.mod h1:0AqAH3ZogsCrvrtUpvc6EtVKbc3w6xwZhkvGLuqyi3o=
github.com/markbates/oncer v0.0.0-20181014194634-05fccaae8fc4 h1:Mlji5gkcpzkqTROyE4ZxZ8hN7osunMb2RuGVrbvMvCc= github.com/markbates/oncer v0.0.0-20181014194634-05fccaae8fc4 h1:Mlji5gkcpzkqTROyE4ZxZ8hN7osunMb2RuGVrbvMvCc=
github.com/markbates/oncer v0.0.0-20181014194634-05fccaae8fc4/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= github.com/markbates/oncer v0.0.0-20181014194634-05fccaae8fc4/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
github.com/miekg/dns v1.1.1 h1:DVkblRdiScEnEr0LR9nTnEQqHYycjkXW9bOjd+2EL2o= github.com/miekg/dns v1.1.8 h1:1QYRAKU3lN5cRfLCkPU08hwvLJFhvjP6MqNMmQz6ZVI=
github.com/miekg/dns v1.1.1/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.8/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= 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/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
@ -82,27 +82,41 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
golang.org/x/arch v0.0.0-20190312162104-788fe5ffcd8c/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4= golang.org/x/arch v0.0.0-20190312162104-788fe5ffcd8c/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190122013713-64072686203f h1:u1CmMhe3a44hy8VIgpInORnI01UVaUYheqR7x9BxT3c=
golang.org/x/crypto v0.0.0-20190122013713-64072686203f/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190422183909-d864b10871cd h1:sMHc2rZHuzQmrbVoSpt9HgerkXPyIeCSO6k0zUMGfFk=
golang.org/x/crypto v0.0.0-20190422183909-d864b10871cd/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190516052701-61b8692d9a5c/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/mobile v0.0.0-20190509164839-32b2708ab171/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/net v0.0.0-20181102091132-c10e9556a7bc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181102091132-c10e9556a7bc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190119204137-ed066c81e75e h1:MDa3fSUp6MdYHouVmCCNz/zaH2a6CRcxY3VhT/K3C5Q= golang.org/x/net v0.0.0-20190119204137-ed066c81e75e h1:MDa3fSUp6MdYHouVmCCNz/zaH2a6CRcxY3VhT/K3C5Q=
golang.org/x/net v0.0.0-20190119204137-ed066c81e75e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190119204137-ed066c81e75e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190313220215-9f648a60d977 h1:actzWV6iWn3GLqN8dZjzsB+CLt+gaV2+wsxroxiQI8I= golang.org/x/net v0.0.0-20190313220215-9f648a60d977 h1:actzWV6iWn3GLqN8dZjzsB+CLt+gaV2+wsxroxiQI8I=
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/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-20190424112056-4829fb13d2c6 h1:FP8hkuE6yUEaJnK7O2eTuejKWwW+Rhfj80dQ2JcKxCU=
golang.org/x/net v0.0.0-20190424112056-4829fb13d2c6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/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 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw= golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190122071731-054c452bb702 h1:Lk4tbZFnlyPgV+sLgTw5yGfzrlOn9kx4vSombi2FFlY= golang.org/x/sys v0.0.0-20190122071731-054c452bb702 h1:Lk4tbZFnlyPgV+sLgTw5yGfzrlOn9kx4vSombi2FFlY=
golang.org/x/sys v0.0.0-20190122071731-054c452bb702/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190122071731-054c452bb702/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190424160641-4347357a82bc h1:ULV59IIHLrmESQT7EqC104GKra36T4CqHvPeEqR6v8M=
golang.org/x/sys v0.0.0-20190424160641-4347357a82bc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/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 h1:nsUiJHvm6yOoRozW9Tz0siNk9sHieLzR+w814Ihse3A=
golang.org/x/text v0.3.1/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/asaskevich/govalidator.v4 v4.0.0-20160518190739-766470278477 h1:5xUJw+lg4zao9W4HIDzlFbMYgSgtvNVHh00MEHvbGpQ= gopkg.in/asaskevich/govalidator.v4 v4.0.0-20160518190739-766470278477 h1:5xUJw+lg4zao9W4HIDzlFbMYgSgtvNVHh00MEHvbGpQ=
gopkg.in/asaskevich/govalidator.v4 v4.0.0-20160518190739-766470278477/go.mod h1:QDV1vrFSrowdoOba0UM8VJPUZONT7dnfdLsM+GG53Z8= gopkg.in/asaskevich/govalidator.v4 v4.0.0-20160518190739-766470278477/go.mod h1:QDV1vrFSrowdoOba0UM8VJPUZONT7dnfdLsM+GG53Z8=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=

View File

@ -144,7 +144,7 @@ paths:
- 'application/json' - 'application/json'
responses: responses:
200: 200:
description: 'Version info. If response message is empty, UI doesn't show a version update message.' description: 'Version info. If response message is empty, UI does not show a version update message.'
schema: schema:
$ref: "#/definitions/VersionInfo" $ref: "#/definitions/VersionInfo"
500: 500: