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:
commit
1d09ff0562
@ -24,6 +24,9 @@ Contents:
|
||||
* "Enable DHCP" command
|
||||
* Static IP check/set
|
||||
* Add a static lease
|
||||
* DNS access settings
|
||||
* List access settings
|
||||
* Set access settings
|
||||
|
||||
|
||||
## First startup
|
||||
@ -626,3 +629,47 @@ Response:
|
||||
Error response (Client not found):
|
||||
|
||||
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
|
||||
|
@ -298,5 +298,14 @@
|
||||
"clients_not_found": "No clients found",
|
||||
"client_confirm_delete": "Are you sure you want to delete client \"{{key}}\"?",
|
||||
"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"
|
||||
}
|
||||
|
45
client/src/actions/access.js
Normal file
45
client/src/actions/access.js
Normal 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());
|
||||
}
|
||||
};
|
@ -460,4 +460,22 @@ export default class Api {
|
||||
};
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
80
client/src/components/Settings/Access/Form.js
Normal file
80
client/src/components/Settings/Access/Form.js
Normal 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);
|
43
client/src/components/Settings/Access/index.js
Normal file
43
client/src/components/Settings/Access/index.js
Normal 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);
|
@ -63,6 +63,10 @@
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.form__label--with-desc {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.form__status {
|
||||
margin-top: 10px;
|
||||
font-size: 14px;
|
||||
|
@ -62,7 +62,7 @@ let Form = (props) => {
|
||||
</div>
|
||||
<div className="col-12">
|
||||
<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>
|
||||
</label>
|
||||
<div className="form__desc form__desc--top">
|
||||
|
@ -7,6 +7,7 @@ import Dhcp from './Dhcp';
|
||||
import Encryption from './Encryption';
|
||||
import Clients from './Clients';
|
||||
import AutoClients from './Clients/AutoClients';
|
||||
import Access from './Access';
|
||||
import Checkbox from '../ui/Checkbox';
|
||||
import Loading from '../ui/Loading';
|
||||
import PageTitle from '../ui/PageTitle';
|
||||
@ -43,6 +44,7 @@ class Settings extends Component {
|
||||
this.props.getDhcpStatus();
|
||||
this.props.getDhcpInterfaces();
|
||||
this.props.getTlsStatus();
|
||||
this.props.getAccessList();
|
||||
}
|
||||
|
||||
renderSettings = (settings) => {
|
||||
@ -68,7 +70,7 @@ class Settings extends Component {
|
||||
|
||||
render() {
|
||||
const {
|
||||
settings, dashboard, clients, t,
|
||||
settings, dashboard, clients, access, t,
|
||||
} = this.props;
|
||||
return (
|
||||
<Fragment>
|
||||
@ -117,6 +119,7 @@ class Settings extends Component {
|
||||
/>
|
||||
</Fragment>
|
||||
)}
|
||||
<Access access={access} setAccessList={this.props.setAccessList} />
|
||||
<Encryption
|
||||
encryption={this.props.encryption}
|
||||
setTlsConfig={this.props.setTlsConfig}
|
||||
|
@ -26,6 +26,10 @@ import {
|
||||
deleteClient,
|
||||
toggleClientModal,
|
||||
} from '../actions/clients';
|
||||
import {
|
||||
getAccessList,
|
||||
setAccessList,
|
||||
} from '../actions/access';
|
||||
import Settings from '../components/Settings';
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
@ -35,6 +39,7 @@ const mapStateToProps = (state) => {
|
||||
dhcp,
|
||||
encryption,
|
||||
clients,
|
||||
access,
|
||||
} = state;
|
||||
const props = {
|
||||
settings,
|
||||
@ -42,6 +47,7 @@ const mapStateToProps = (state) => {
|
||||
dhcp,
|
||||
encryption,
|
||||
clients,
|
||||
access,
|
||||
};
|
||||
return props;
|
||||
};
|
||||
@ -68,6 +74,8 @@ const mapDispatchToProps = {
|
||||
addStaticLease,
|
||||
removeStaticLease,
|
||||
toggleLeaseModal,
|
||||
getAccessList,
|
||||
setAccessList,
|
||||
};
|
||||
|
||||
export default connect(
|
||||
|
43
client/src/reducers/access.js
Normal file
43
client/src/reducers/access.js
Normal 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;
|
@ -8,6 +8,7 @@ import * as actions from '../actions';
|
||||
import toasts from './toasts';
|
||||
import encryption from './encryption';
|
||||
import clients from './clients';
|
||||
import access from './access';
|
||||
|
||||
const settings = handleActions({
|
||||
[actions.initSettingsRequest]: state => ({ ...state, processing: true }),
|
||||
@ -418,6 +419,7 @@ export default combineReducers({
|
||||
dhcp,
|
||||
encryption,
|
||||
clients,
|
||||
access,
|
||||
loadingBar: loadingBarReducer,
|
||||
form: formReducer,
|
||||
});
|
||||
|
@ -996,6 +996,9 @@ func registerControlHandlers() {
|
||||
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/access/list", postInstall(optionalAuth(ensureGET(handleAccessList))))
|
||||
http.HandleFunc("/control/access/set", postInstall(optionalAuth(ensurePOST(handleAccessSet))))
|
||||
|
||||
RegisterTLSHandlers()
|
||||
RegisterClientsHandlers()
|
||||
|
||||
|
87
control_access.go
Normal file
87
control_access.go
Normal 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)
|
||||
}
|
@ -43,6 +43,12 @@ type Server struct {
|
||||
stats *stats // General server statistics
|
||||
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
|
||||
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)
|
||||
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"`
|
||||
}
|
||||
|
||||
@ -121,6 +131,34 @@ func (s *Server) Start(config *ServerConfig) error {
|
||||
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
|
||||
func (s *Server) startInternal(config *ServerConfig) error {
|
||||
if config != nil {
|
||||
@ -167,10 +205,23 @@ func (s *Server) startInternal(config *ServerConfig) error {
|
||||
CacheEnabled: true,
|
||||
Upstreams: s.conf.Upstreams,
|
||||
DomainsReservedUpstreams: s.conf.DomainsReservedUpstreams,
|
||||
Handler: s.handleDNSRequest,
|
||||
BeforeRequestHandler: s.beforeRequestHandler,
|
||||
RequestHandler: s.handleDNSRequest,
|
||||
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 != "" {
|
||||
proxyConfig.TLSListenAddr = s.conf.TLSListenAddr
|
||||
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 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
|
||||
func (s *Server) handleDNSRequest(p *proxy.Proxy, d *proxy.DNSContext) error {
|
||||
start := time.Now()
|
||||
|
@ -622,3 +622,72 @@ func publicKey(priv interface{}) interface{} {
|
||||
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
8
go.mod
@ -3,7 +3,7 @@ module github.com/AdguardTeam/AdGuardHome
|
||||
go 1.12
|
||||
|
||||
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/urlfilter v0.3.0
|
||||
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/service v0.0.0-20181115005516-4c239ee84e7b
|
||||
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/stretchr/testify v1.3.0
|
||||
golang.org/x/net v0.0.0-20190313220215-9f648a60d977
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a
|
||||
golang.org/x/net v0.0.0-20190424112056-4829fb13d2c6
|
||||
golang.org/x/sys v0.0.0-20190424160641-4347357a82bc
|
||||
gopkg.in/asaskevich/govalidator.v4 v4.0.0-20160518190739-766470278477
|
||||
gopkg.in/yaml.v2 v2.2.1
|
||||
)
|
||||
|
34
go.sum
34
go.sum
@ -1,5 +1,5 @@
|
||||
github.com/AdguardTeam/dnsproxy v0.12.0 h1:BPgv2PlH2u4xakFcaW4EqU3Visk1BNidrqGSgxe5Qzg=
|
||||
github.com/AdguardTeam/dnsproxy v0.12.0/go.mod h1:lcZM2QPwcWGEL3pz8RYy06nQdbjj4pr+94H45jnVSHg=
|
||||
github.com/AdguardTeam/dnsproxy v0.14.0 h1:ubB5031Oc8TfOWxRpYYDx0Lt181jiNGOfiOgEN5VJys=
|
||||
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.3 h1:hmapdTtMtIk3T8eQDwTOLdqZLGDKNKk9325uC8z12xg=
|
||||
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/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyYYufQuIbVqRawmubW3OFGqK1ekw=
|
||||
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.6/go.mod h1:ZvT9LaNaJfDNXKIbkYFf24HUgHuQR6MNT6nwVvN4jMQ=
|
||||
github.com/ameshkov/dnscrypt v1.0.7 h1:7LS9wiC/6c00H3ZdZOlwQSYGTJvs12g5ui9D1VSZ2aQ=
|
||||
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/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A=
|
||||
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/markbates/oncer v0.0.0-20181014194634-05fccaae8fc4 h1:Mlji5gkcpzkqTROyE4ZxZ8hN7osunMb2RuGVrbvMvCc=
|
||||
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.1/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/miekg/dns v1.1.8 h1:1QYRAKU3lN5cRfLCkPU08hwvLJFhvjP6MqNMmQz6ZVI=
|
||||
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/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||
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/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-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/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-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-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-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/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/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-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
|
||||
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/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/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/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/go.mod h1:QDV1vrFSrowdoOba0UM8VJPUZONT7dnfdLsM+GG53Z8=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
|
@ -144,7 +144,7 @@ paths:
|
||||
- 'application/json'
|
||||
responses:
|
||||
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:
|
||||
$ref: "#/definitions/VersionInfo"
|
||||
500:
|
||||
|
Loading…
Reference in New Issue
Block a user