Compare commits
73 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9eb6da05ad | ||
|
|
3f796a5d05 | ||
|
|
0a1d7fd707 | ||
|
|
26db906e54 | ||
|
|
3a5f9a7ad3 | ||
|
|
bcbfa43ea2 | ||
|
|
2520a62e24 | ||
|
|
fce551dcaf | ||
|
|
cf4616cbee | ||
|
|
f0c9ffcbeb | ||
|
|
71c1157ef5 | ||
|
|
2fe9819150 | ||
|
|
4af635e58a | ||
|
|
b0cfd7228e | ||
|
|
e03efbcdd1 | ||
|
|
2897bb983f | ||
|
|
387783cf91 | ||
|
|
d4bd53a824 | ||
|
|
f1a6912092 | ||
|
|
531ee20988 | ||
|
|
5c7c9964b8 | ||
|
|
425f3c87d0 | ||
|
|
ad7c5cb9dc | ||
|
|
124d73bd32 | ||
|
|
1445940473 | ||
|
|
df30248870 | ||
|
|
b419a1e3d8 | ||
|
|
98ff11e1c7 | ||
|
|
134d9275bb | ||
|
|
e2675e9a3b | ||
|
|
dc43ad9910 | ||
|
|
ceac4cbdd5 | ||
|
|
131aa4c93c | ||
|
|
5abf0b5a53 | ||
|
|
5cddde53c3 | ||
|
|
1c9abd6107 | ||
|
|
8e3f05e538 | ||
|
|
64f66cfb5d | ||
|
|
e616d843bf | ||
|
|
6406202888 | ||
|
|
b3c2b3a21b | ||
|
|
b45e8e80fb | ||
|
|
885b660808 | ||
|
|
bdc9a0b906 | ||
|
|
c631a6832f | ||
|
|
db7efc24d3 | ||
|
|
b4b11406cf | ||
|
|
eb8c531ae1 | ||
|
|
d1987e711d | ||
|
|
e50b4fd185 | ||
|
|
d2258cb66d | ||
|
|
42b76ada9d | ||
|
|
25da23497a | ||
|
|
efaaeb58eb | ||
|
|
0b3ba82242 | ||
|
|
eff23f3b62 | ||
|
|
0e9df33a40 | ||
|
|
6a1edc45be | ||
|
|
5d60bb05ab | ||
|
|
2307f55715 | ||
|
|
f1e6a30931 | ||
|
|
4ddae72faf | ||
|
|
082354204b | ||
|
|
6187871e3b | ||
|
|
dc682763ff | ||
|
|
9fe34818e3 | ||
|
|
9a77bb3a0a | ||
|
|
86890a8609 | ||
|
|
1fd0f78612 | ||
|
|
1fcb69d3a9 | ||
|
|
07db927246 | ||
|
|
f9807e4011 | ||
|
|
5647bc1fc9 |
@@ -257,7 +257,11 @@ Server can only auto-update if the current version is equal or higher than `self
|
||||
|
||||
Request:
|
||||
|
||||
GET /control/version.json
|
||||
POST /control/version.json
|
||||
|
||||
{
|
||||
"recheck_now": true | false // if false, server will check for a new version data only once in several hours
|
||||
}
|
||||
|
||||
Response:
|
||||
|
||||
@@ -526,7 +530,7 @@ Notes:
|
||||
|
||||
* If `use_global_settings` is true, then DNS responses for this client are processed and filtered using global settings.
|
||||
|
||||
* If `use_global_settings` is false, then the client-specific settings are used to override (disable) global settings. For example, if global setting `parental_enabled` is true, then per-client setting `parental_enabled:false` can disable Parental Control for this specific client.
|
||||
* If `use_global_settings` is false, then the client-specific settings are used to override (enable or disable) global settings.
|
||||
|
||||
|
||||
### Get list of clients
|
||||
|
||||
4
Makefile
4
Makefile
@@ -20,10 +20,10 @@ client/node_modules: client/package.json client/package-lock.json
|
||||
$(STATIC): $(JSFILES) client/node_modules
|
||||
npm --prefix client run build-prod
|
||||
|
||||
$(TARGET): $(STATIC) *.go dhcpd/*.go dnsfilter/*.go dnsforward/*.go
|
||||
$(TARGET): $(STATIC) *.go home/*.go dhcpd/*.go dnsfilter/*.go dnsforward/*.go
|
||||
GOOS=$(NATIVE_GOOS) GOARCH=$(NATIVE_GOARCH) GO111MODULE=off go get -v github.com/gobuffalo/packr/...
|
||||
PATH=$(GOPATH)/bin:$(PATH) packr -z
|
||||
CGO_ENABLED=0 go build -ldflags="-s -w -X main.VersionString=$(GIT_VERSION) -X main.updateChannel=$(CHANNEL)" -asmflags="-trimpath=$(PWD)" -gcflags="-trimpath=$(PWD)"
|
||||
CGO_ENABLED=0 go build -ldflags="-s -w -X main.version=$(GIT_VERSION) -X main.channel=$(CHANNEL)" -asmflags="-trimpath=$(PWD)" -gcflags="-trimpath=$(PWD)"
|
||||
PATH=$(GOPATH)/bin:$(PATH) packr clean
|
||||
|
||||
clean:
|
||||
|
||||
@@ -69,6 +69,11 @@ Alternatively, you can use our [official Docker image](https://hub.docker.com/r/
|
||||
* [How to install and run AdGuard Home on Raspberry Pi](https://github.com/AdguardTeam/AdGuardHome/wiki/Raspberry-Pi)
|
||||
* [How to install and run AdGuard Home on a Virtual Private Server](https://github.com/AdguardTeam/AdGuardHome/wiki/VPS)
|
||||
|
||||
### API
|
||||
|
||||
If you want to integrate with AdGuard Home, you can use our [REST API](https://github.com/AdguardTeam/AdGuardHome/tree/master/openapi).
|
||||
Alternatively, you can use this [python client](https://pypi.org/project/adguardhome/), which is used to build the [AdGuard Home Hass.io Add-on](https://community.home-assistant.io/t/community-hass-io-add-on-adguard-home).
|
||||
|
||||
<a id="comparison"></a>
|
||||
## Comparing AdGuard Home to other solutions
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"client_settings": "Client settings",
|
||||
"example_upstream_reserved": "you can specify DNS upstream <0>for a specific domain(s)<\/0>",
|
||||
"upstream_parallel": "Use parallel queries to speed up resolving by simultaneously querying all upstream servers",
|
||||
"bootstrap_dns": "Bootstrap DNS servers",
|
||||
@@ -52,7 +53,7 @@
|
||||
"filters": "Filters",
|
||||
"query_log": "Query Log",
|
||||
"faq": "FAQ",
|
||||
"version": "version",
|
||||
"version": "Version",
|
||||
"address": "address",
|
||||
"on": "ON",
|
||||
"off": "OFF",
|
||||
@@ -99,7 +100,6 @@
|
||||
"dns_settings": "DNS settings",
|
||||
"encryption_settings": "Encryption settings",
|
||||
"dhcp_settings": "DHCP settings",
|
||||
"client_settings": "Client settings",
|
||||
"upstream_dns": "Upstream DNS servers",
|
||||
"upstream_dns_hint": "If you keep this field empty, AdGuard Home will use <a href='https:\/\/1.1.1.1\/' target='_blank'>Cloudflare DNS<\/a> as an upstream.",
|
||||
"test_upstream_btn": "Test upstreams",
|
||||
@@ -139,7 +139,7 @@
|
||||
"example_comment": "! Here goes a comment",
|
||||
"example_comment_meaning": "just a comment",
|
||||
"example_comment_hash": "# Also a comment",
|
||||
"example_regex_meaning": "block access to the domains matching the specified regular expression",
|
||||
"example_regex_meaning": "block access to the domains matching the <0>specified regular expression</0>",
|
||||
"example_upstream_regular": "regular DNS (over UDP)",
|
||||
"example_upstream_dot": "encrypted <0>DNS-over-TLS<\/0>",
|
||||
"example_upstream_doh": "encrypted <0>DNS-over-HTTPS<\/0>",
|
||||
@@ -201,12 +201,12 @@
|
||||
"install_auth_password_enter": "Enter password",
|
||||
"install_step": "Step",
|
||||
"install_devices_title": "Configure your devices",
|
||||
"install_devices_desc": "In order for AdGuard Home to start working, you need to configure your devices to use it.",
|
||||
"install_devices_desc": "To start using AdGuard Home, you need to configure your devices to use it.",
|
||||
"install_submit_title": "Congratulations!",
|
||||
"install_submit_desc": "The setup procedure is finished and you are ready to start using AdGuard Home.",
|
||||
"install_devices_router": "Router",
|
||||
"install_devices_router_desc": "This setup will automatically cover all the devices connected to your home router and you will not need to configure each of them manually.",
|
||||
"install_devices_address": "AdGuard Home DNS server is listening to the following addresses",
|
||||
"install_devices_address": "AdGuard Home DNS server is listening on the following addresses",
|
||||
"install_devices_router_list_1": "Open the preferences for your router. Usually, you can access it from your browser via a URL (like http:\/\/192.168.0.1\/ or http:\/\/192.168.1.1\/). You may be asked to enter the password. If you don't remember it, you can often reset the password by pressing a button on the router itself. Some routers require a specific application, which in that case should be already installed on your computer\/phone.",
|
||||
"install_devices_router_list_2": "Find the DHCP\/DNS settings. Look for the DNS letters next to a field which allows two or three sets of numbers, each broken into four groups of one to three digits.",
|
||||
"install_devices_router_list_3": "Enter your AdGuard Home server addresses there.",
|
||||
@@ -312,5 +312,24 @@
|
||||
"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"
|
||||
"access_settings_saved": "Access settings successfully saved",
|
||||
"updates_checked": "Updates successfully checked",
|
||||
"updates_version_equal": "AdGuard Home is up-to-date",
|
||||
"check_updates_now": "Check for updates now",
|
||||
"dns_privacy": "DNS Privacy",
|
||||
"setup_dns_privacy_1": "<0>DNS-over-TLS:</0> Use <1>{{address}}</1> string.",
|
||||
"setup_dns_privacy_2": "<0>DNS-over-HTTPS:</0> Use <1>{{address}}</1> string.",
|
||||
"setup_dns_privacy_3": "<0>Please note that encrypted DNS protocols are supported only on Android 9. So you need to install additional software for other operating systems.</0><0>Here's a list of software you can use.</0>",
|
||||
"setup_dns_privacy_android_1": "Android 9 supports DNS-over-TLS natively. To configure it, go to Settings → Network & internet → Advanced → Private DNS and enter your domain name there.",
|
||||
"setup_dns_privacy_android_2": "<0>AdGuard for Android</0> supports <1>DNS-over-HTTPS</1> and <1>DNS-over-TLS</1>.",
|
||||
"setup_dns_privacy_android_3": "<0>Intra</0> adds <1>DNS-over-HTTPS</1> support to Android.",
|
||||
"setup_dns_privacy_ios_1": "<0>DNSCloak</0> supports <1>DNS-over-HTTPS</1>, but in order to configure it to use your own server, you'll need to generate a <2>DNS Stamp</2> for it.",
|
||||
"setup_dns_privacy_ios_2": "<0>AdGuard for iOS</0> supports <1>DNS-over-HTTPS</1> and <1>DNS-over-TLS</1> setup.",
|
||||
"setup_dns_privacy_other_title": "Other implementations",
|
||||
"setup_dns_privacy_other_1": "AdGuard Home itself can be a secure DNS client on any platform.",
|
||||
"setup_dns_privacy_other_2": "<0>dnsproxy</0> supports all known secure DNS protocols.",
|
||||
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> supports <1>DNS-over-HTTPS</1>.",
|
||||
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> supports <1>DNS-over-HTTPS</1>.",
|
||||
"setup_dns_privacy_other_5": "You will find more implementations <0>here</0> and <1>here</1>.",
|
||||
"setup_dns_notice": "In order to use <1>DNS-over-HTTPS</1> or <1>DNS-over-TLS</1>, you need to <0>configure Encryption</0> in AdGuard Home settings."
|
||||
}
|
||||
@@ -4,8 +4,10 @@ import { t } from 'i18next';
|
||||
import { showLoading, hideLoading } from 'react-redux-loading-bar';
|
||||
import axios from 'axios';
|
||||
|
||||
import versionCompare from '../helpers/versionCompare';
|
||||
import { normalizeHistory, normalizeFilteringStatus, normalizeLogs, normalizeTextarea, sortClients } from '../helpers/helpers';
|
||||
import { SETTINGS_NAMES, CHECK_TIMEOUT } from '../helpers/constants';
|
||||
import { getTlsStatus } from './encryption';
|
||||
import Api from '../api/Api';
|
||||
|
||||
const apiClient = new Api();
|
||||
@@ -145,11 +147,22 @@ export const getVersionRequest = createAction('GET_VERSION_REQUEST');
|
||||
export const getVersionFailure = createAction('GET_VERSION_FAILURE');
|
||||
export const getVersionSuccess = createAction('GET_VERSION_SUCCESS');
|
||||
|
||||
export const getVersion = () => async (dispatch) => {
|
||||
export const getVersion = (recheck = false) => async (dispatch, getState) => {
|
||||
dispatch(getVersionRequest());
|
||||
try {
|
||||
const newVersion = await apiClient.getGlobalVersion();
|
||||
dispatch(getVersionSuccess(newVersion));
|
||||
const data = await apiClient.getGlobalVersion({ recheck_now: recheck });
|
||||
dispatch(getVersionSuccess(data));
|
||||
|
||||
if (recheck) {
|
||||
const { dnsVersion } = getState().dashboard;
|
||||
const currentVersion = dnsVersion === 'undefined' ? 0 : dnsVersion;
|
||||
|
||||
if (data && versionCompare(currentVersion, data.new_version) === -1) {
|
||||
dispatch(addSuccessToast('updates_checked'));
|
||||
} else {
|
||||
dispatch(addSuccessToast('updates_version_equal'));
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(getVersionFailure());
|
||||
@@ -264,8 +277,7 @@ export const getDnsStatus = () => async (dispatch) => {
|
||||
const dnsStatus = await apiClient.getGlobalStatus();
|
||||
dispatch(dnsStatusSuccess(dnsStatus));
|
||||
dispatch(getVersion());
|
||||
dispatch(getClients());
|
||||
dispatch(getTopStats());
|
||||
dispatch(getTlsStatus());
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(initSettingsFailure());
|
||||
|
||||
@@ -36,7 +36,7 @@ export default class Api {
|
||||
GLOBAL_QUERY_LOG_DISABLE = { path: 'querylog_disable', method: 'POST' };
|
||||
GLOBAL_SET_UPSTREAM_DNS = { path: 'set_upstreams_config', method: 'POST' };
|
||||
GLOBAL_TEST_UPSTREAM_DNS = { path: 'test_upstream_dns', method: 'POST' };
|
||||
GLOBAL_VERSION = { path: 'version.json', method: 'GET' };
|
||||
GLOBAL_VERSION = { path: 'version.json', method: 'POST' };
|
||||
GLOBAL_ENABLE_PROTECTION = { path: 'enable_protection', method: 'POST' };
|
||||
GLOBAL_DISABLE_PROTECTION = { path: 'disable_protection', method: 'POST' };
|
||||
GLOBAL_UPDATE = { path: 'update', method: 'POST' };
|
||||
@@ -125,9 +125,13 @@ export default class Api {
|
||||
return this.makeRequest(path, method, config);
|
||||
}
|
||||
|
||||
getGlobalVersion() {
|
||||
getGlobalVersion(data) {
|
||||
const { path, method } = this.GLOBAL_VERSION;
|
||||
return this.makeRequest(path, method);
|
||||
const config = {
|
||||
data,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
return this.makeRequest(path, method, config);
|
||||
}
|
||||
|
||||
enableGlobalProtection() {
|
||||
|
||||
@@ -65,8 +65,7 @@ class App extends Component {
|
||||
|
||||
render() {
|
||||
const { dashboard, encryption } = this.props;
|
||||
const updateAvailable =
|
||||
!dashboard.processingVersions && dashboard.isCoreRunning && dashboard.isUpdateAvailable;
|
||||
const updateAvailable = dashboard.isCoreRunning && dashboard.isUpdateAvailable;
|
||||
|
||||
return (
|
||||
<HashRouter hashType="noslash">
|
||||
|
||||
@@ -50,8 +50,26 @@ class Dashboard extends Component {
|
||||
dashboard.processingClients ||
|
||||
dashboard.processingTopStats;
|
||||
|
||||
const refreshFullButton = <button type="button" className="btn btn-outline-primary btn-sm" onClick={() => this.getAllStats()}><Trans>refresh_statics</Trans></button>;
|
||||
const refreshButton = <button type="button" className="btn btn-outline-primary btn-sm card-refresh" onClick={() => this.getAllStats()} />;
|
||||
const refreshFullButton = (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-outline-primary btn-sm"
|
||||
onClick={() => this.getAllStats()}
|
||||
>
|
||||
<Trans>refresh_statics</Trans>
|
||||
</button>
|
||||
);
|
||||
const refreshButton = (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-icon btn-outline-primary btn-sm"
|
||||
onClick={() => this.getAllStats()}
|
||||
>
|
||||
<svg className="icons">
|
||||
<use xlinkHref="#refresh" />
|
||||
</svg>
|
||||
</button>
|
||||
);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
|
||||
@@ -4,7 +4,7 @@ import ReactModal from 'react-modal';
|
||||
import classnames from 'classnames';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
import { R_URL_REQUIRES_PROTOCOL } from '../../helpers/constants';
|
||||
import './Modal.css';
|
||||
import '../ui/Modal.css';
|
||||
|
||||
ReactModal.setAppElement('#root');
|
||||
|
||||
@@ -17,10 +17,7 @@ const initialState = {
|
||||
class Modal extends Component {
|
||||
state = initialState;
|
||||
|
||||
// eslint-disable-next-line
|
||||
isUrlValid = url => {
|
||||
return R_URL_REQUIRES_PROTOCOL.test(url);
|
||||
};
|
||||
isUrlValid = url => R_URL_REQUIRES_PROTOCOL.test(url);
|
||||
|
||||
handleUrlChange = async (e) => {
|
||||
const { value: url } = e.currentTarget;
|
||||
@@ -17,12 +17,13 @@ class UserRules extends Component {
|
||||
render() {
|
||||
const { t } = this.props;
|
||||
return (
|
||||
<Card
|
||||
title={ t('custom_filter_rules') }
|
||||
subtitle={ t('custom_filter_rules_hint') }
|
||||
>
|
||||
<Card title={t('custom_filter_rules')} subtitle={t('custom_filter_rules_hint')}>
|
||||
<form onSubmit={this.handleSubmit}>
|
||||
<textarea className="form-control form-control--textarea-large" value={this.props.userRules} onChange={this.handleChange} />
|
||||
<textarea
|
||||
className="form-control form-control--textarea-large"
|
||||
value={this.props.userRules}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
<div className="card-actions">
|
||||
<button
|
||||
className="btn btn-success btn-standard"
|
||||
@@ -33,27 +34,42 @@ class UserRules extends Component {
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<hr/>
|
||||
<hr />
|
||||
<div className="list leading-loose">
|
||||
<Trans>examples_title</Trans>:
|
||||
<ol className="leading-loose">
|
||||
<li>
|
||||
<code>||example.org^</code> - { t('example_meaning_filter_block') }
|
||||
<code>||example.org^</code> – {t('example_meaning_filter_block')}
|
||||
</li>
|
||||
<li>
|
||||
<code> @@||example.org^</code> - { t('example_meaning_filter_whitelist') }
|
||||
<code> @@||example.org^</code> – {t('example_meaning_filter_whitelist')}
|
||||
</li>
|
||||
<li>
|
||||
<code>127.0.0.1 example.org</code> - { t('example_meaning_host_block') }
|
||||
<code>127.0.0.1 example.org</code> – {t('example_meaning_host_block')}
|
||||
</li>
|
||||
<li>
|
||||
<code>{ t('example_comment') }</code> - { t('example_comment_meaning') }
|
||||
<code>{t('example_comment')}</code> – {t('example_comment_meaning')}
|
||||
</li>
|
||||
<li>
|
||||
<code>{ t('example_comment_hash') }</code> - { t('example_comment_meaning') }
|
||||
<code>{t('example_comment_hash')}</code> –
|
||||
{t('example_comment_meaning')}
|
||||
</li>
|
||||
<li>
|
||||
<code>/REGEX/</code> - { t('example_regex_meaning') }
|
||||
<code>/REGEX/</code> –
|
||||
<Trans
|
||||
components={[
|
||||
<a
|
||||
href="https://kb.adguard.com/general/dns-filtering-syntax"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
key="0"
|
||||
>
|
||||
link
|
||||
</a>,
|
||||
]}
|
||||
>
|
||||
example_regex_meaning
|
||||
</Trans>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { Component } from 'react';
|
||||
import ReactTable from 'react-table';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
import Modal from '../ui/Modal';
|
||||
import Modal from './Modal';
|
||||
import PageTitle from '../ui/PageTitle';
|
||||
import Card from '../ui/Card';
|
||||
import UserRules from './UserRules';
|
||||
|
||||
@@ -75,7 +75,18 @@
|
||||
}
|
||||
|
||||
.nav-version__value {
|
||||
max-width: 110px;
|
||||
font-weight: 600;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 992px) {
|
||||
.nav-version__value {
|
||||
max-width: 100%;
|
||||
overflow: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-version__link {
|
||||
@@ -85,6 +96,12 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.nav-version__text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.header-brand-img {
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
@@ -2,14 +2,26 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
|
||||
import { getDnsAddress } from '../../helpers/helpers';
|
||||
const Version = (props) => {
|
||||
const {
|
||||
dnsVersion, dnsAddresses, processingVersion, t,
|
||||
} = props;
|
||||
|
||||
function Version(props) {
|
||||
const { dnsVersion, dnsAddresses, dnsPort } = props;
|
||||
return (
|
||||
<div className="nav-version">
|
||||
<div className="nav-version__text">
|
||||
<Trans>version</Trans>: <span className="nav-version__value">{dnsVersion}</span>
|
||||
<Trans>version</Trans>: <span className="nav-version__value" title={dnsVersion}>{dnsVersion}</span>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-icon btn-icon-sm btn-outline-primary btn-sm ml-2"
|
||||
onClick={() => props.getVersion(true)}
|
||||
disabled={processingVersion}
|
||||
title={t('check_updates_now')}
|
||||
>
|
||||
<svg className="icons">
|
||||
<use xlinkHref="#refresh" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div className="nav-version__link">
|
||||
<div className="popover__trigger popover__trigger--address">
|
||||
@@ -17,20 +29,21 @@ function Version(props) {
|
||||
</div>
|
||||
<div className="popover__body popover__body--address">
|
||||
<div className="popover__list">
|
||||
{dnsAddresses
|
||||
.map(ip => <li key={ip}>{getDnsAddress(ip, dnsPort)}</li>)
|
||||
}
|
||||
{dnsAddresses.map(ip => <li key={ip}>{ip}</li>)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
Version.propTypes = {
|
||||
dnsVersion: PropTypes.string.isRequired,
|
||||
dnsAddresses: PropTypes.array.isRequired,
|
||||
dnsPort: PropTypes.number.isRequired,
|
||||
getVersion: PropTypes.func.isRequired,
|
||||
processingVersion: PropTypes.bool.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default withNamespaces()(Version);
|
||||
|
||||
@@ -23,7 +23,7 @@ class Header extends Component {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { dashboard } = this.props;
|
||||
const { dashboard, getVersion, location } = this.props;
|
||||
const { isMenuOpen } = this.state;
|
||||
const badgeClass = classnames({
|
||||
'badge dns-status': true,
|
||||
@@ -51,7 +51,7 @@ class Header extends Component {
|
||||
</div>
|
||||
</div>
|
||||
<Menu
|
||||
location={this.props.location}
|
||||
location={location}
|
||||
isMenuOpen={isMenuOpen}
|
||||
toggleMenuOpen={this.toggleMenuOpen}
|
||||
closeMenu={this.closeMenu}
|
||||
@@ -59,7 +59,8 @@ class Header extends Component {
|
||||
{!dashboard.processing &&
|
||||
<div className="col col-sm-6 col-lg-3">
|
||||
<Version
|
||||
{ ...this.props.dashboard }
|
||||
{ ...dashboard }
|
||||
getVersion={getVersion}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
@@ -71,8 +72,9 @@ class Header extends Component {
|
||||
}
|
||||
|
||||
Header.propTypes = {
|
||||
dashboard: PropTypes.object,
|
||||
location: PropTypes.object,
|
||||
dashboard: PropTypes.object.isRequired,
|
||||
location: PropTypes.object.isRequired,
|
||||
getVersion: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default withNamespaces()(Header);
|
||||
|
||||
@@ -21,6 +21,7 @@ class Logs extends Component {
|
||||
componentDidMount() {
|
||||
this.getLogs();
|
||||
this.props.getFilteringStatus();
|
||||
this.props.getClients();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
@@ -356,6 +357,7 @@ Logs.propTypes = {
|
||||
processingRules: PropTypes.bool,
|
||||
logStatusProcessing: PropTypes.bool,
|
||||
t: PropTypes.func,
|
||||
getClients: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default withNamespaces()(Logs);
|
||||
|
||||
@@ -80,7 +80,7 @@ let Form = (props) => {
|
||||
<div className="form__desc">
|
||||
<Trans
|
||||
components={[
|
||||
<a href="#settings_dhcp" key="0">
|
||||
<a href="#dhcp" key="0">
|
||||
link
|
||||
</a>,
|
||||
]}
|
||||
|
||||
@@ -10,6 +10,7 @@ import Loading from '../../ui/Loading';
|
||||
class Clients extends Component {
|
||||
componentDidMount() {
|
||||
this.props.getClients();
|
||||
this.props.getTopStats();
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -63,6 +64,7 @@ Clients.propTypes = {
|
||||
addClient: PropTypes.func.isRequired,
|
||||
updateClient: PropTypes.func.isRequired,
|
||||
getClients: PropTypes.func.isRequired,
|
||||
getTopStats: PropTypes.func.isRequired,
|
||||
topStats: PropTypes.object,
|
||||
};
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Trans, withNamespaces } from 'react-i18next';
|
||||
import flow from 'lodash/flow';
|
||||
import format from 'date-fns/format';
|
||||
|
||||
import { renderField, renderSelectField, toNumber, port, isSafePort } from '../../../helpers/form';
|
||||
import { renderField, renderSelectField, toNumber, port, portTLS, isSafePort } from '../../../helpers/form';
|
||||
import { EMPTY_DATE } from '../../../helpers/constants';
|
||||
import i18n from '../../../i18n';
|
||||
|
||||
@@ -167,7 +167,7 @@ let Form = (props) => {
|
||||
type="number"
|
||||
className="form-control"
|
||||
placeholder={t('encryption_dot')}
|
||||
validate={[port]}
|
||||
validate={[portTLS]}
|
||||
normalize={toNumber}
|
||||
onChange={handleChange}
|
||||
disabled={!isEnabled}
|
||||
|
||||
@@ -11,9 +11,7 @@ import Loading from '../../ui/Loading';
|
||||
|
||||
class Encryption extends Component {
|
||||
componentDidMount() {
|
||||
const { getTlsStatus, validateTlsConfig, encryption } = this.props;
|
||||
|
||||
getTlsStatus();
|
||||
const { validateTlsConfig, encryption } = this.props;
|
||||
|
||||
if (encryption.enabled) {
|
||||
validateTlsConfig(encryption);
|
||||
|
||||
@@ -88,3 +88,10 @@
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.btn-icon-sm {
|
||||
width: 23px;
|
||||
height: 23px;
|
||||
min-width: 23px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
|
||||
import { getDnsAddress } from '../../helpers/helpers';
|
||||
|
||||
import Guide from '../ui/Guide';
|
||||
import Card from '../ui/Card';
|
||||
import PageTitle from '../ui/PageTitle';
|
||||
@@ -13,7 +11,6 @@ const SetupGuide = ({
|
||||
t,
|
||||
dashboard: {
|
||||
dnsAddresses,
|
||||
dnsPort,
|
||||
},
|
||||
}) => (
|
||||
<div className="guide">
|
||||
@@ -28,12 +25,10 @@ const SetupGuide = ({
|
||||
<Trans>install_devices_address</Trans>:
|
||||
</div>
|
||||
<div className="mt-2 font-weight-bold">
|
||||
{dnsAddresses
|
||||
.map(ip => <li key={ip}>{getDnsAddress(ip, dnsPort)}</li>)
|
||||
}
|
||||
{dnsAddresses.map(ip => <li key={ip}>{ip}</li>)}
|
||||
</div>
|
||||
</div>
|
||||
<Guide />
|
||||
<Guide dnsAddresses={dnsAddresses} />
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -33,21 +33,6 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.card-refresh {
|
||||
height: 26px;
|
||||
width: 26px;
|
||||
background-size: 14px;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-image: url("data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiBoZWlnaHQ9IjI0IiBzdHJva2U9IiM0NjdmY2YiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIyIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSIyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJtMjMgNHY2aC02Ii8+PHBhdGggZD0ibTEgMjB2LTZoNiIvPjxwYXRoIGQ9Im0zLjUxIDlhOSA5IDAgMCAxIDE0Ljg1LTMuMzZsNC42NCA0LjM2bS0yMiA0IDQuNjQgNC4zNmE5IDkgMCAwIDAgMTQuODUtMy4zNiIvPjwvc3ZnPg==");
|
||||
}
|
||||
|
||||
.card-refresh:hover,
|
||||
.card-refresh:not(:disabled):not(.disabled):active,
|
||||
.card-refresh:focus:active {
|
||||
background-image: url("data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiBoZWlnaHQ9IjI0IiBzdHJva2U9IiNmZmYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIyIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSIyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJtMjMgNHY2aC02Ii8+PHBhdGggZD0ibTEgMjB2LTZoNiIvPjxwYXRoIGQ9Im0zLjUxIDlhOSA5IDAgMCAxIDE0Ljg1LTMuMzZsNC42NCA0LjM2bS0yMiA0IDQuNjQgNC4zNmE5IDkgMCAwIDAgMTQuODUtMy4zNiIvPjwvc3ZnPg==");
|
||||
}
|
||||
|
||||
.card-title-stats {
|
||||
font-size: 13px;
|
||||
color: #9aa0ac;
|
||||
|
||||
@@ -18,7 +18,7 @@ const EncryptionTopline = (props) => {
|
||||
if (isExpired) {
|
||||
return (
|
||||
<Topline type="danger">
|
||||
<Trans components={[<a href="#settings" key="0">link</a>]}>
|
||||
<Trans components={[<a href="#encryption" key="0">link</a>]}>
|
||||
topline_expired_certificate
|
||||
</Trans>
|
||||
</Topline>
|
||||
@@ -26,7 +26,7 @@ const EncryptionTopline = (props) => {
|
||||
} else if (isAboutExpire) {
|
||||
return (
|
||||
<Topline type="warning">
|
||||
<Trans components={[<a href="#settings" key="0">link</a>]}>
|
||||
<Trans components={[<a href="#encryption" key="0">link</a>]}>
|
||||
topline_expiring_certificate
|
||||
</Trans>
|
||||
</Topline>
|
||||
|
||||
@@ -1,83 +1,373 @@
|
||||
import React from 'react';
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
|
||||
import Tabs from '../ui/Tabs';
|
||||
import Icons from '../ui/Icons';
|
||||
|
||||
const Guide = () => (
|
||||
<div>
|
||||
<Icons />
|
||||
<Tabs>
|
||||
<div label="Router">
|
||||
<div className="tab__title">
|
||||
<Trans>install_devices_router</Trans>
|
||||
const Guide = (props) => {
|
||||
const { dnsAddresses } = props;
|
||||
const tlsAddress = (dnsAddresses && dnsAddresses.filter(item => item.includes('tls://'))) || '';
|
||||
const httpsAddress =
|
||||
(dnsAddresses && dnsAddresses.filter(item => item.includes('https://'))) || '';
|
||||
const showDnsPrivacyNotice = httpsAddress.length < 1 && tlsAddress.length < 1;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Icons />
|
||||
<Tabs>
|
||||
<div label="Router">
|
||||
<div className="tab__title">
|
||||
<Trans>install_devices_router</Trans>
|
||||
</div>
|
||||
<div className="tab__text">
|
||||
<p>
|
||||
<Trans>install_devices_router_desc</Trans>
|
||||
</p>
|
||||
<ol>
|
||||
<li>
|
||||
<Trans>install_devices_router_list_1</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans>install_devices_router_list_2</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans>install_devices_router_list_3</Trans>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
<div className="tab__text">
|
||||
<p><Trans>install_devices_router_desc</Trans></p>
|
||||
<ol>
|
||||
<li><Trans>install_devices_router_list_1</Trans></li>
|
||||
<li><Trans>install_devices_router_list_2</Trans></li>
|
||||
<li><Trans>install_devices_router_list_3</Trans></li>
|
||||
</ol>
|
||||
<div label="Windows">
|
||||
<div className="tab__title">Windows</div>
|
||||
<div className="tab__text">
|
||||
<ol>
|
||||
<li>
|
||||
<Trans>install_devices_windows_list_1</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans>install_devices_windows_list_2</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans>install_devices_windows_list_3</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans>install_devices_windows_list_4</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans>install_devices_windows_list_5</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans>install_devices_windows_list_6</Trans>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div label="Windows">
|
||||
<div className="tab__title">
|
||||
Windows
|
||||
<div label="macOS">
|
||||
<div className="tab__title">macOS</div>
|
||||
<div className="tab__text">
|
||||
<ol>
|
||||
<li>
|
||||
<Trans>install_devices_macos_list_1</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans>install_devices_macos_list_2</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans>install_devices_macos_list_3</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans>install_devices_macos_list_4</Trans>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
<div className="tab__text">
|
||||
<ol>
|
||||
<li><Trans>install_devices_windows_list_1</Trans></li>
|
||||
<li><Trans>install_devices_windows_list_2</Trans></li>
|
||||
<li><Trans>install_devices_windows_list_3</Trans></li>
|
||||
<li><Trans>install_devices_windows_list_4</Trans></li>
|
||||
<li><Trans>install_devices_windows_list_5</Trans></li>
|
||||
<li><Trans>install_devices_windows_list_6</Trans></li>
|
||||
</ol>
|
||||
<div label="Android">
|
||||
<div className="tab__title">Android</div>
|
||||
<div className="tab__text">
|
||||
<ol>
|
||||
<li>
|
||||
<Trans>install_devices_android_list_1</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans>install_devices_android_list_2</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans>install_devices_android_list_3</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans>install_devices_android_list_4</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans>install_devices_android_list_5</Trans>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div label="macOS">
|
||||
<div className="tab__title">
|
||||
macOS
|
||||
<div label="iOS">
|
||||
<div className="tab__title">iOS</div>
|
||||
<div className="tab__text">
|
||||
<ol>
|
||||
<li>
|
||||
<Trans>install_devices_ios_list_1</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans>install_devices_ios_list_2</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans>install_devices_ios_list_3</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans>install_devices_ios_list_4</Trans>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
<div className="tab__text">
|
||||
<ol>
|
||||
<li><Trans>install_devices_macos_list_1</Trans></li>
|
||||
<li><Trans>install_devices_macos_list_2</Trans></li>
|
||||
<li><Trans>install_devices_macos_list_3</Trans></li>
|
||||
<li><Trans>install_devices_macos_list_4</Trans></li>
|
||||
</ol>
|
||||
<div label="dns_privacy" title={props.t('dns_privacy')}>
|
||||
<div className="tab__title">
|
||||
<Trans>dns_privacy</Trans>
|
||||
</div>
|
||||
<div className="tab__text">
|
||||
{tlsAddress && tlsAddress.length > 0 && (
|
||||
<div className="tab__paragraph">
|
||||
<Trans
|
||||
values={{ address: tlsAddress[0] }}
|
||||
components={[
|
||||
<strong key="0">text</strong>,
|
||||
<code key="1">text</code>,
|
||||
]}
|
||||
>
|
||||
setup_dns_privacy_1
|
||||
</Trans>
|
||||
</div>
|
||||
)}
|
||||
{httpsAddress && httpsAddress.length > 0 && (
|
||||
<div className="tab__paragraph">
|
||||
<Trans
|
||||
values={{ address: httpsAddress[0] }}
|
||||
components={[
|
||||
<strong key="0">text</strong>,
|
||||
<code key="1">text</code>,
|
||||
]}
|
||||
>
|
||||
setup_dns_privacy_2
|
||||
</Trans>
|
||||
</div>
|
||||
)}
|
||||
{showDnsPrivacyNotice && (
|
||||
<div className="tab__paragraph">
|
||||
<Trans
|
||||
components={[
|
||||
<a
|
||||
href="https://github.com/AdguardTeam/AdguardHome/wiki/Encryption"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
key="0"
|
||||
>
|
||||
link
|
||||
</a>,
|
||||
<code key="1">text</code>,
|
||||
]}
|
||||
>
|
||||
setup_dns_notice
|
||||
</Trans>
|
||||
</div>
|
||||
)}
|
||||
{!showDnsPrivacyNotice && (
|
||||
<Fragment>
|
||||
<div className="tab__paragraph">
|
||||
<Trans components={[<p key="0">text</p>]}>
|
||||
setup_dns_privacy_3
|
||||
</Trans>
|
||||
</div>
|
||||
<div className="tab__paragraph">
|
||||
<strong>Android</strong>
|
||||
<ul>
|
||||
<li>
|
||||
<Trans>setup_dns_privacy_android_1</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans
|
||||
components={[
|
||||
<a
|
||||
href="https://adguard.com/adguard-android/overview.html"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
key="0"
|
||||
>
|
||||
link
|
||||
</a>,
|
||||
<code key="1">text</code>,
|
||||
]}
|
||||
>
|
||||
setup_dns_privacy_android_2
|
||||
</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans
|
||||
components={[
|
||||
<a
|
||||
href="https://getintra.org/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
key="0"
|
||||
>
|
||||
link
|
||||
</a>,
|
||||
<code key="1">text</code>,
|
||||
]}
|
||||
>
|
||||
setup_dns_privacy_android_3
|
||||
</Trans>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="tab__paragraph">
|
||||
<strong>iOS</strong>
|
||||
<ul>
|
||||
<li>
|
||||
<Trans
|
||||
components={[
|
||||
<a
|
||||
href="https://itunes.apple.com/app/id1452162351"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
key="0"
|
||||
>
|
||||
link
|
||||
</a>,
|
||||
<code key="1">text</code>,
|
||||
<a
|
||||
href="https://dnscrypt.info/stamps"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
key="2"
|
||||
>
|
||||
link
|
||||
</a>,
|
||||
]}
|
||||
>
|
||||
setup_dns_privacy_ios_1
|
||||
</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans
|
||||
components={[
|
||||
<a
|
||||
href="https://adguard.com/adguard-ios/overview.html"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
key="0"
|
||||
>
|
||||
link
|
||||
</a>,
|
||||
<code key="1">text</code>,
|
||||
]}
|
||||
>
|
||||
setup_dns_privacy_ios_2
|
||||
</Trans>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="tab__paragraph">
|
||||
<strong>
|
||||
<Trans>setup_dns_privacy_other_title</Trans>
|
||||
</strong>
|
||||
<ul>
|
||||
<li>
|
||||
<Trans>setup_dns_privacy_other_1</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans
|
||||
components={[
|
||||
<a
|
||||
href="https://github.com/AdguardTeam/dnsproxy"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
key="0"
|
||||
>
|
||||
link
|
||||
</a>,
|
||||
]}
|
||||
>
|
||||
setup_dns_privacy_other_2
|
||||
</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans
|
||||
components={[
|
||||
<a
|
||||
href="https://github.com/jedisct1/dnscrypt-proxy"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
key="0"
|
||||
>
|
||||
link
|
||||
</a>,
|
||||
<code key="1">text</code>,
|
||||
]}
|
||||
>
|
||||
setup_dns_privacy_other_3
|
||||
</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans
|
||||
components={[
|
||||
<a
|
||||
href="https://www.mozilla.org/firefox/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
key="0"
|
||||
>
|
||||
link
|
||||
</a>,
|
||||
<code key="1">text</code>,
|
||||
]}
|
||||
>
|
||||
setup_dns_privacy_other_4
|
||||
</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans
|
||||
components={[
|
||||
<a
|
||||
href="https://dnscrypt.info/implementations"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
key="0"
|
||||
>
|
||||
link
|
||||
</a>,
|
||||
<a
|
||||
href="https://dnsprivacy.org/wiki/display/DP/DNS+Privacy+Clients"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
key="1"
|
||||
>
|
||||
link
|
||||
</a>,
|
||||
]}
|
||||
>
|
||||
setup_dns_privacy_other_5
|
||||
</Trans>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</Fragment>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div label="Android">
|
||||
<div className="tab__title">
|
||||
Android
|
||||
</div>
|
||||
<div className="tab__text">
|
||||
<ol>
|
||||
<li><Trans>install_devices_android_list_1</Trans></li>
|
||||
<li><Trans>install_devices_android_list_2</Trans></li>
|
||||
<li><Trans>install_devices_android_list_3</Trans></li>
|
||||
<li><Trans>install_devices_android_list_4</Trans></li>
|
||||
<li><Trans>install_devices_android_list_5</Trans></li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
<div label="iOS">
|
||||
<div className="tab__title">
|
||||
iOS
|
||||
</div>
|
||||
<div className="tab__text">
|
||||
<ol>
|
||||
<li><Trans>install_devices_ios_list_1</Trans></li>
|
||||
<li><Trans>install_devices_ios_list_2</Trans></li>
|
||||
<li><Trans>install_devices_ios_list_3</Trans></li>
|
||||
<li><Trans>install_devices_ios_list_4</Trans></li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Guide.defaultProps = {
|
||||
dnsAddresses: [],
|
||||
};
|
||||
|
||||
Guide.propTypes = {
|
||||
dnsAddresses: PropTypes.array,
|
||||
t: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default withNamespaces()(Guide);
|
||||
|
||||
@@ -55,6 +55,14 @@ const Icons = () => (
|
||||
<symbol id="settings" viewBox="0 0 24 24" stroke="currentColor" fill="none" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2">
|
||||
<circle cx="12" cy="12" r="3"/><path d="m19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1 -2.83 0l-.06-.06a1.65 1.65 0 0 0 -1.82-.33 1.65 1.65 0 0 0 -1 1.51v.17a2 2 0 0 1 -2 2 2 2 0 0 1 -2-2v-.09a1.65 1.65 0 0 0 -1.08-1.51 1.65 1.65 0 0 0 -1.82.33l-.06.06a2 2 0 0 1 -2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0 -1.51-1h-.17a2 2 0 0 1 -2-2 2 2 0 0 1 2-2h.09a1.65 1.65 0 0 0 1.51-1.08 1.65 1.65 0 0 0 -.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33h.08a1.65 1.65 0 0 0 1-1.51v-.17a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0 -.33 1.82v.08a1.65 1.65 0 0 0 1.51 1h.17a2 2 0 0 1 2 2 2 2 0 0 1 -2 2h-.09a1.65 1.65 0 0 0 -1.51 1z"/>
|
||||
</symbol>
|
||||
|
||||
<symbol id="refresh" viewBox="0 0 24 24" stroke="currentColor" fill="none" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2">
|
||||
<path d="M23 4v6h-6M1 20v-6h6"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/>
|
||||
</symbol>
|
||||
|
||||
<symbol id="dns_privacy" viewBox="0 0 30 30" stroke="none" fill="currentColor" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M15 3C10.57 3 6.701 5.419 4.623 9h2.39a10.063 10.063 0 0 1 4.05-3.19c-.524.89-.961 1.973-1.3 3.19h2.108c.79-2.459 1.998-4 3.129-4s2.339 1.541 3.129 4h2.107c-.338-1.217-.774-2.3-1.299-3.19A10.062 10.062 0 0 1 22.989 9h2.389C23.298 5.419 19.43 3 15 3zm7.035 9.129c-1.372 0-2.264.73-2.264 1.842 0 .896.538 1.463 1.579 1.66l.75.15c.65.13.898.3.898.615 0 .375-.37.635-.91.635-.6 0-1.014-.265-1.049-.68h-1.38c.023 1.097.93 1.776 2.37 1.776 1.491 0 2.399-.717 2.399-1.904 0-.903-.504-1.412-1.63-1.63l-.734-.142c-.6-.118-.851-.3-.851-.611 0-.378.336-.62.844-.62.509 0 .891.28.923.682h1.336c-.024-1.053-.948-1.773-2.28-1.773zm-16.185.148v5.696h2.39c1.712 0 2.662-1.033 2.662-2.903 0-1.779-.966-2.793-2.662-2.793H5.85zm6.933.004v5.692h1.373v-3.235h.076l2.377 3.235h1.149V12.28h-1.373v3.203h-.076l-2.372-3.203h-1.154zm-5.486 1.16h.682c.912 0 1.449.596 1.449 1.657 0 1.128-.51 1.713-1.45 1.713h-.681v-3.37zM4.623 21C6.701 24.581 10.57 27 15 27c4.43 0 8.299-2.419 10.377-6h-2.389a10.063 10.063 0 0 1-4.049 3.19c.524-.89.96-1.973 1.297-3.19H18.13c-.79 2.459-1.996 4-3.127 4-1.131 0-2.339-1.541-3.129-4h-2.11c.339 1.217.776 2.3 1.3 3.19A10.056 10.056 0 0 1 7.013 21h-2.39z"></path>
|
||||
</symbol>
|
||||
</svg>
|
||||
);
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ class Tab extends Component {
|
||||
const {
|
||||
activeTab,
|
||||
label,
|
||||
title,
|
||||
} = this.props;
|
||||
|
||||
const tabClass = classnames({
|
||||
@@ -26,7 +27,7 @@ class Tab extends Component {
|
||||
<svg className="tab__icon">
|
||||
<use xlinkHref={`#${label.toLowerCase()}`} />
|
||||
</svg>
|
||||
{label}
|
||||
{title || label}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -36,6 +37,7 @@ Tab.propTypes = {
|
||||
activeTab: PropTypes.string.isRequired,
|
||||
label: PropTypes.string.isRequired,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
title: PropTypes.string,
|
||||
};
|
||||
|
||||
export default Tab;
|
||||
|
||||
@@ -49,3 +49,12 @@
|
||||
.tab__text p {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.tab__text ul,
|
||||
.tab__text ol {
|
||||
padding-left: 25px;
|
||||
}
|
||||
|
||||
.tab__paragraph {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
@@ -27,12 +27,13 @@ class Tabs extends Component {
|
||||
<div className="tabs">
|
||||
<div className="tabs__controls">
|
||||
{children.map((child) => {
|
||||
const { label } = child.props;
|
||||
const { label, title } = child.props;
|
||||
|
||||
return (
|
||||
<Tab
|
||||
key={label}
|
||||
label={label}
|
||||
title={title}
|
||||
activeTab={activeTab}
|
||||
onClick={this.onClickTabControl}
|
||||
/>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { getClients } from '../actions';
|
||||
import { getClients, getTopStats } from '../actions';
|
||||
import { addClient, updateClient, deleteClient, toggleClientModal } from '../actions/clients';
|
||||
import Clients from '../components/Settings/Clients';
|
||||
|
||||
@@ -14,6 +14,7 @@ const mapStateToProps = (state) => {
|
||||
|
||||
const mapDispatchToProps = {
|
||||
getClients,
|
||||
getTopStats,
|
||||
addClient,
|
||||
updateClient,
|
||||
deleteClient,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { getLogs, toggleLogStatus, downloadQueryLog, getFilteringStatus, setRules, addSuccessToast } from '../actions';
|
||||
import { getLogs, toggleLogStatus, downloadQueryLog, getFilteringStatus, setRules, addSuccessToast, getClients } from '../actions';
|
||||
import Logs from '../components/Logs';
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
@@ -15,6 +15,7 @@ const mapDispatchToProps = {
|
||||
getFilteringStatus,
|
||||
setRules,
|
||||
addSuccessToast,
|
||||
getClients,
|
||||
};
|
||||
|
||||
export default connect(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export const R_URL_REQUIRES_PROTOCOL = /^https?:\/\/\w[\w_\-.]*\.[a-z]{2,8}[^\s]*$/;
|
||||
export const R_URL_REQUIRES_PROTOCOL = /^https?:\/\/[^/\s]+(\/.*)?$/;
|
||||
export const R_IPV4 = /^(?:(?:^|\.)(?:2(?:5[0-5]|[0-4]\d)|1?\d?\d)){4}$/g;
|
||||
export const R_MAC = /^((([a-fA-F0-9][a-fA-F0-9]+[-]){5}|([a-fA-F0-9][a-fA-F0-9]+[:]){5})([a-fA-F0-9][a-fA-F0-9])$)|(^([a-fA-F0-9][a-fA-F0-9][a-fA-F0-9][a-fA-F0-9]+[.]){2}([a-fA-F0-9][a-fA-F0-9][a-fA-F0-9][a-fA-F0-9]))$/g;
|
||||
|
||||
@@ -41,7 +41,7 @@ export const LANGUAGES = [
|
||||
},
|
||||
{
|
||||
key: 'pt-br',
|
||||
name: 'Português (BR)',
|
||||
name: 'Portuguese (BR)',
|
||||
},
|
||||
{
|
||||
key: 'sv',
|
||||
|
||||
@@ -76,6 +76,15 @@ export const port = (value) => {
|
||||
return false;
|
||||
};
|
||||
|
||||
export const portTLS = (value) => {
|
||||
if (value === 0) {
|
||||
return false;
|
||||
} else if (value && (value < 80 || value > 65535)) {
|
||||
return <Trans>form_error_port_range</Trans>;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export const isSafePort = (value) => {
|
||||
if (UNSAFE_PORTS.includes(value)) {
|
||||
return <Trans>form_error_port_unsafe</Trans>;
|
||||
|
||||
@@ -137,11 +137,15 @@ const dashboard = handleActions({
|
||||
newVersion,
|
||||
canAutoUpdate,
|
||||
isUpdateAvailable: true,
|
||||
processingVersion: false,
|
||||
};
|
||||
return newState;
|
||||
}
|
||||
|
||||
return state;
|
||||
return {
|
||||
...state,
|
||||
processingVersion: false,
|
||||
};
|
||||
},
|
||||
|
||||
[actions.getUpdateRequest]: state => ({ ...state, processingUpdate: true }),
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDoUpdate(t *testing.T) {
|
||||
config.DNS.Port = 0
|
||||
config.ourWorkingDir = "."
|
||||
u := updateInfo{
|
||||
pkgURL: "https://github.com/AdguardTeam/AdGuardHome/releases/download/v0.95/AdGuardHome_v0.95_linux_amd64.tar.gz",
|
||||
pkgName: "./AdGuardHome_v0.95_linux_amd64.tar.gz",
|
||||
newVer: "v0.95",
|
||||
updateDir: "./agh-update-v0.95",
|
||||
backupDir: "./agh-backup-v0.94",
|
||||
configName: "./AdGuardHome.yaml",
|
||||
updateConfigName: "./agh-update-v0.95/AdGuardHome/AdGuardHome.yaml",
|
||||
curBinName: "./AdGuardHome",
|
||||
bkpBinName: "./agh-backup-v0.94/AdGuardHome",
|
||||
newBinName: "./agh-update-v0.95/AdGuardHome/AdGuardHome",
|
||||
}
|
||||
e := doUpdate(&u)
|
||||
if e != nil {
|
||||
t.Fatalf("FAILED: %s", e)
|
||||
}
|
||||
os.RemoveAll(u.backupDir)
|
||||
}
|
||||
|
||||
func TestTargzFileUnpack(t *testing.T) {
|
||||
fn := "./dist/AdGuardHome_v0.95_linux_amd64.tar.gz"
|
||||
outdir := "./test-unpack"
|
||||
_ = os.Mkdir(outdir, 0755)
|
||||
files, e := targzFileUnpack(fn, outdir)
|
||||
if e != nil {
|
||||
t.Fatalf("FAILED: %s", e)
|
||||
}
|
||||
t.Logf("%v", files)
|
||||
os.RemoveAll(outdir)
|
||||
}
|
||||
|
||||
func TestZipFileUnpack(t *testing.T) {
|
||||
fn := "./dist/AdGuardHome_v0.95_Windows_amd64.zip"
|
||||
outdir := "./test-unpack"
|
||||
_ = os.Mkdir(outdir, 0755)
|
||||
files, e := zipFileUnpack(fn, outdir)
|
||||
if e != nil {
|
||||
t.Fatalf("FAILED: %s", e)
|
||||
}
|
||||
t.Logf("%v", files)
|
||||
os.RemoveAll(outdir)
|
||||
}
|
||||
@@ -207,8 +207,6 @@ func (s *Server) Stop() error {
|
||||
s.cond.Wait()
|
||||
}
|
||||
s.mutex.Unlock()
|
||||
|
||||
s.dbStore()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -235,6 +233,10 @@ func (s *Server) reserveLease(p dhcp4.Packet) (*Lease, error) {
|
||||
lease := &Lease{HWAddr: hwaddr, Hostname: string(hostname)}
|
||||
|
||||
log.Tracef("Lease not found for %s: creating new one", hwaddr)
|
||||
|
||||
s.leasesLock.Lock()
|
||||
defer s.leasesLock.Unlock()
|
||||
|
||||
ip, err := s.findFreeIP(hwaddr)
|
||||
if err != nil {
|
||||
i := s.findExpiredLease()
|
||||
@@ -245,9 +247,8 @@ func (s *Server) reserveLease(p dhcp4.Packet) (*Lease, error) {
|
||||
log.Tracef("Assigning IP address %s to %s (lease for %s expired at %s)",
|
||||
s.leases[i].IP, hwaddr, s.leases[i].HWAddr, s.leases[i].Expiry)
|
||||
lease.IP = s.leases[i].IP
|
||||
s.leasesLock.Lock()
|
||||
s.leases[i] = lease
|
||||
s.leasesLock.Unlock()
|
||||
s.dbStore()
|
||||
|
||||
s.reserveIP(lease.IP, hwaddr)
|
||||
return lease, nil
|
||||
@@ -255,9 +256,8 @@ func (s *Server) reserveLease(p dhcp4.Packet) (*Lease, error) {
|
||||
|
||||
log.Tracef("Assigning to %s IP address %s", hwaddr, ip.String())
|
||||
lease.IP = ip
|
||||
s.leasesLock.Lock()
|
||||
s.leases = append(s.leases, lease)
|
||||
s.leasesLock.Unlock()
|
||||
s.dbStore()
|
||||
return lease, nil
|
||||
}
|
||||
|
||||
@@ -405,11 +405,12 @@ func (s *Server) addrAvailable(target net.IP) bool {
|
||||
// Add the specified IP to the black list for a time period
|
||||
func (s *Server) blacklistLease(lease *Lease) {
|
||||
hw := make(net.HardwareAddr, 6)
|
||||
s.reserveIP(lease.IP, hw)
|
||||
s.leasesLock.Lock()
|
||||
s.reserveIP(lease.IP, hw)
|
||||
lease.HWAddr = hw
|
||||
lease.Hostname = ""
|
||||
lease.Expiry = time.Now().Add(s.leaseTime)
|
||||
s.dbStore()
|
||||
s.leasesLock.Unlock()
|
||||
}
|
||||
|
||||
@@ -608,19 +609,18 @@ func (s *Server) Leases() []Lease {
|
||||
// StaticLeases returns the list of statically-configured DHCP leases (thread-safe)
|
||||
func (s *Server) StaticLeases() []Lease {
|
||||
s.leasesLock.Lock()
|
||||
defer s.leasesLock.Unlock()
|
||||
|
||||
if s.IPpool == nil {
|
||||
s.dbLoad()
|
||||
}
|
||||
s.leasesLock.Unlock()
|
||||
|
||||
var result []Lease
|
||||
s.leasesLock.RLock()
|
||||
for _, lease := range s.leases {
|
||||
if lease.Expiry.Unix() == 1 {
|
||||
result = append(result, *lease)
|
||||
}
|
||||
}
|
||||
s.leasesLock.RUnlock()
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -650,6 +650,6 @@ func (s *Server) FindIPbyMAC(mac net.HardwareAddr) net.IP {
|
||||
func (s *Server) reset() {
|
||||
s.leasesLock.Lock()
|
||||
s.leases = nil
|
||||
s.leasesLock.Unlock()
|
||||
s.IPpool = make(map[[4]byte]net.HardwareAddr)
|
||||
s.leasesLock.Unlock()
|
||||
}
|
||||
|
||||
@@ -38,12 +38,19 @@ func TestDHCP(t *testing.T) {
|
||||
|
||||
p = make(dhcp4.Packet, 241)
|
||||
|
||||
// Reserve an IP
|
||||
// Discover and reserve an IP
|
||||
hw = []byte{3, 2, 3, 4, 5, 6}
|
||||
p.SetCHAddr(hw)
|
||||
lease, _ = s.reserveLease(p)
|
||||
check(t, bytes.Equal(lease.HWAddr, hw), "lease.HWAddr")
|
||||
check(t, bytes.Equal(lease.IP, []byte{1, 1, 1, 1}), "lease.IP")
|
||||
p.SetCIAddr([]byte{0, 0, 0, 0})
|
||||
opt = make(dhcp4.Options, 10)
|
||||
p2 = s.handleDiscover(p, opt)
|
||||
opt = p2.ParseOptions()
|
||||
check(t, bytes.Equal(opt[dhcp4.OptionDHCPMessageType], []byte{byte(dhcp4.Offer)}), "dhcp4.Offer")
|
||||
check(t, bytes.Equal(p2.YIAddr(), []byte{1, 1, 1, 1}), "p2.YIAddr")
|
||||
check(t, bytes.Equal(p2.CHAddr(), hw), "p2.CHAddr")
|
||||
check(t, bytes.Equal(opt[dhcp4.OptionIPAddressLeaseTime], dhcp4.OptionsLeaseTime(5*time.Second)), "OptionIPAddressLeaseTime")
|
||||
check(t, bytes.Equal(opt[dhcp4.OptionServerIdentifier], s.ipnet.IP), "OptionServerIdentifier")
|
||||
|
||||
lease = s.findLease(p)
|
||||
check(t, bytes.Equal(lease.HWAddr, hw), "lease.HWAddr")
|
||||
check(t, bytes.Equal(lease.IP, []byte{1, 1, 1, 1}), "lease.IP")
|
||||
@@ -88,6 +95,8 @@ func TestDHCP(t *testing.T) {
|
||||
check(t, bytes.Equal(opt[dhcp4.OptionIPAddressLeaseTime], dhcp4.OptionsLeaseTime(5*time.Second)), "OptionIPAddressLeaseTime")
|
||||
check(t, bytes.Equal(opt[dhcp4.OptionServerIdentifier], s.ipnet.IP), "OptionServerIdentifier")
|
||||
|
||||
check(t, bytes.Equal(s.FindIPbyMAC(hw), []byte{1, 1, 1, 1}), "FindIPbyMAC")
|
||||
|
||||
// Commit the previously reserved lease #2
|
||||
hw = []byte{2, 2, 3, 4, 5, 6}
|
||||
p.SetCHAddr(hw)
|
||||
@@ -103,10 +112,28 @@ func TestDHCP(t *testing.T) {
|
||||
lease, _ = s.reserveLease(p)
|
||||
check(t, lease == nil, "lease == nil")
|
||||
|
||||
s.reset()
|
||||
testStaticLeases(t, &s)
|
||||
|
||||
s.reset()
|
||||
misc(t, &s)
|
||||
}
|
||||
|
||||
func testStaticLeases(t *testing.T, s *Server) {
|
||||
var err error
|
||||
var l Lease
|
||||
l.IP = []byte{1, 1, 1, 1}
|
||||
l.HWAddr = []byte{2, 2, 3, 4, 5, 6}
|
||||
err = s.AddStaticLease(l)
|
||||
check(t, err == nil, "AddStaticLease")
|
||||
|
||||
ll := s.StaticLeases()
|
||||
check(t, len(ll) != 0 && bytes.Equal(ll[0].IP, []byte{1, 1, 1, 1}), "StaticLeases")
|
||||
|
||||
err = s.RemoveStaticLease(l)
|
||||
check(t, err == nil, "RemoveStaticLease")
|
||||
}
|
||||
|
||||
// Small tests that don't require a static server's state
|
||||
func misc(t *testing.T, s *Server) {
|
||||
var p, p2 dhcp4.Packet
|
||||
|
||||
@@ -11,10 +11,13 @@ import (
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/joomcode/errorx"
|
||||
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/urlfilter"
|
||||
@@ -33,7 +36,8 @@ const defaultSafebrowsingServer = "sb.adtidy.org"
|
||||
const defaultSafebrowsingURL = "%s://%s/safebrowsing-lookup-hash.html?prefixes=%s"
|
||||
const defaultParentalServer = "pctrl.adguard.com"
|
||||
const defaultParentalURL = "%s://%s/check-parental-control-hash?prefixes=%s&sensitivity=%d"
|
||||
const maxDialCacheSize = 2 // the number of host names for safebrowsing and parental control
|
||||
const defaultParentalSensitivity = 13 // use "TEEN" by default
|
||||
const maxDialCacheSize = 2 // the number of host names for safebrowsing and parental control
|
||||
|
||||
// Custom filtering settings
|
||||
type RequestFilteringSettings struct {
|
||||
@@ -45,13 +49,12 @@ type RequestFilteringSettings struct {
|
||||
|
||||
// Config allows you to configure DNS filtering with New() or just change variables directly.
|
||||
type Config struct {
|
||||
FilteringTempFilename string `yaml:"filtering_temp_filename"` // temporary file for storing unused filtering rules
|
||||
ParentalSensitivity int `yaml:"parental_sensitivity"` // must be either 3, 10, 13 or 17
|
||||
ParentalEnabled bool `yaml:"parental_enabled"`
|
||||
UsePlainHTTP bool `yaml:"-"` // use plain HTTP for requests to parental and safe browsing servers
|
||||
SafeSearchEnabled bool `yaml:"safesearch_enabled"`
|
||||
SafeBrowsingEnabled bool `yaml:"safebrowsing_enabled"`
|
||||
ResolverAddress string // DNS server address
|
||||
ParentalSensitivity int `yaml:"parental_sensitivity"` // must be either 3, 10, 13 or 17
|
||||
ParentalEnabled bool `yaml:"parental_enabled"`
|
||||
UsePlainHTTP bool `yaml:"-"` // use plain HTTP for requests to parental and safe browsing servers
|
||||
SafeSearchEnabled bool `yaml:"safesearch_enabled"`
|
||||
SafeBrowsingEnabled bool `yaml:"safebrowsing_enabled"`
|
||||
ResolverAddress string // DNS server address
|
||||
|
||||
// Filtering callback function
|
||||
FilterHandler func(clientAddr string, settings *RequestFilteringSettings) `yaml:"-"`
|
||||
@@ -79,7 +82,7 @@ type Stats struct {
|
||||
|
||||
// Dnsfilter holds added rules and performs hostname matches against the rules
|
||||
type Dnsfilter struct {
|
||||
rulesStorage *urlfilter.RulesStorage
|
||||
rulesStorage *urlfilter.RuleStorage
|
||||
filteringEngine *urlfilter.DNSEngine
|
||||
|
||||
// HTTP lookups for safebrowsing and parental
|
||||
@@ -92,8 +95,9 @@ type Dnsfilter struct {
|
||||
|
||||
// Filter represents a filter list
|
||||
type Filter struct {
|
||||
ID int64 `json:"id"` // auto-assigned when filter is added (see nextFilterID), json by default keeps ID uppercase but we need lowercase
|
||||
Data []byte `json:"-" yaml:"-"` // List of rules divided by '\n'
|
||||
ID int64 `json:"id"` // auto-assigned when filter is added (see nextFilterID), json by default keeps ID uppercase but we need lowercase
|
||||
Data []byte `json:"-" yaml:"-"` // List of rules divided by '\n'
|
||||
FilePath string `json:"-" yaml:"-"` // Path to a filtering rules file
|
||||
}
|
||||
|
||||
//go:generate stringer -type=Reason
|
||||
@@ -125,14 +129,15 @@ const (
|
||||
FilteredSafeSearch
|
||||
)
|
||||
|
||||
// these variables need to survive coredns reload
|
||||
var (
|
||||
type dnsFilterContext struct {
|
||||
stats Stats
|
||||
dialCache gcache.Cache // "host" -> "IP" cache for safebrowsing and parental control servers
|
||||
safebrowsingCache gcache.Cache
|
||||
parentalCache gcache.Cache
|
||||
safeSearchCache gcache.Cache
|
||||
)
|
||||
}
|
||||
|
||||
var gctx dnsFilterContext // global dnsfilter context
|
||||
|
||||
// Result holds state of hostname check
|
||||
type Result struct {
|
||||
@@ -295,14 +300,10 @@ func (d *Dnsfilter) checkSafeSearch(host string) (Result, error) {
|
||||
defer timer.LogElapsed("SafeSearch HTTP lookup for %s", host)
|
||||
}
|
||||
|
||||
if safeSearchCache == nil {
|
||||
safeSearchCache = gcache.New(defaultCacheSize).LRU().Expiration(defaultCacheTime).Build()
|
||||
}
|
||||
|
||||
// Check cache. Return cached result if it was found
|
||||
cachedValue, isFound, err := getCachedReason(safeSearchCache, host)
|
||||
cachedValue, isFound, err := getCachedReason(gctx.safeSearchCache, host)
|
||||
if isFound {
|
||||
atomic.AddUint64(&stats.Safesearch.CacheHits, 1)
|
||||
atomic.AddUint64(&gctx.stats.Safesearch.CacheHits, 1)
|
||||
log.Tracef("%s: found in SafeSearch cache", host)
|
||||
return cachedValue, nil
|
||||
}
|
||||
@@ -319,7 +320,7 @@ func (d *Dnsfilter) checkSafeSearch(host string) (Result, error) {
|
||||
res := Result{IsFiltered: true, Reason: FilteredSafeSearch}
|
||||
if ip := net.ParseIP(safeHost); ip != nil {
|
||||
res.IP = ip
|
||||
err = safeSearchCache.Set(host, res)
|
||||
err = gctx.safeSearchCache.Set(host, res)
|
||||
if err != nil {
|
||||
return Result{}, nil
|
||||
}
|
||||
@@ -346,7 +347,7 @@ func (d *Dnsfilter) checkSafeSearch(host string) (Result, error) {
|
||||
}
|
||||
|
||||
// Cache result
|
||||
err = safeSearchCache.Set(host, res)
|
||||
err = gctx.safeSearchCache.Set(host, res)
|
||||
if err != nil {
|
||||
return Result{}, nil
|
||||
}
|
||||
@@ -392,10 +393,7 @@ func (d *Dnsfilter) checkSafeBrowsing(host string) (Result, error) {
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
if safebrowsingCache == nil {
|
||||
safebrowsingCache = gcache.New(defaultCacheSize).LRU().Expiration(defaultCacheTime).Build()
|
||||
}
|
||||
result, err := d.lookupCommon(host, &stats.Safebrowsing, safebrowsingCache, true, format, handleBody)
|
||||
result, err := d.lookupCommon(host, &gctx.stats.Safebrowsing, gctx.safebrowsingCache, true, format, handleBody)
|
||||
return result, err
|
||||
}
|
||||
|
||||
@@ -410,7 +408,11 @@ func (d *Dnsfilter) checkParental(host string) (Result, error) {
|
||||
if d.UsePlainHTTP {
|
||||
schema = "http"
|
||||
}
|
||||
url := fmt.Sprintf(defaultParentalURL, schema, d.parentalServer, hashparam, d.ParentalSensitivity)
|
||||
sensitivity := d.ParentalSensitivity
|
||||
if sensitivity == 0 {
|
||||
sensitivity = defaultParentalSensitivity
|
||||
}
|
||||
url := fmt.Sprintf(defaultParentalURL, schema, d.parentalServer, hashparam, sensitivity)
|
||||
return url
|
||||
}
|
||||
handleBody := func(body []byte, hashes map[string]bool) (Result, error) {
|
||||
@@ -443,10 +445,7 @@ func (d *Dnsfilter) checkParental(host string) (Result, error) {
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
if parentalCache == nil {
|
||||
parentalCache = gcache.New(defaultCacheSize).LRU().Expiration(defaultCacheTime).Build()
|
||||
}
|
||||
result, err := d.lookupCommon(host, &stats.Parental, parentalCache, false, format, handleBody)
|
||||
result, err := d.lookupCommon(host, &gctx.stats.Parental, gctx.parentalCache, false, format, handleBody)
|
||||
return result, err
|
||||
}
|
||||
|
||||
@@ -527,15 +526,50 @@ func (d *Dnsfilter) lookupCommon(host string, lookupstats *LookupStats, cache gc
|
||||
// Adding rule and matching against the rules
|
||||
//
|
||||
|
||||
// Return TRUE if file exists
|
||||
func fileExists(fn string) bool {
|
||||
_, err := os.Stat(fn)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Initialize urlfilter objects
|
||||
func (d *Dnsfilter) initFiltering(filters map[int]string) error {
|
||||
var err error
|
||||
d.rulesStorage, err = urlfilter.NewRuleStorage(d.FilteringTempFilename)
|
||||
if err != nil {
|
||||
return err
|
||||
listArray := []urlfilter.RuleList{}
|
||||
for id, dataOrFilePath := range filters {
|
||||
var list urlfilter.RuleList
|
||||
|
||||
if id == 0 {
|
||||
list = &urlfilter.StringRuleList{
|
||||
ID: 0,
|
||||
RulesText: dataOrFilePath,
|
||||
IgnoreCosmetic: false,
|
||||
}
|
||||
|
||||
} else if !fileExists(dataOrFilePath) {
|
||||
list = &urlfilter.StringRuleList{
|
||||
ID: id,
|
||||
IgnoreCosmetic: false,
|
||||
}
|
||||
|
||||
} else {
|
||||
var err error
|
||||
list, err = urlfilter.NewFileRuleList(id, dataOrFilePath, false)
|
||||
if err != nil {
|
||||
return fmt.Errorf("urlfilter.NewFileRuleList(): %s: %s", dataOrFilePath, err)
|
||||
}
|
||||
}
|
||||
listArray = append(listArray, list)
|
||||
}
|
||||
|
||||
d.filteringEngine = urlfilter.NewDNSEngine(filters, d.rulesStorage)
|
||||
var err error
|
||||
d.rulesStorage, err = urlfilter.NewRuleStorage(listArray)
|
||||
if err != nil {
|
||||
return fmt.Errorf("urlfilter.NewRuleStorage(): %s", err)
|
||||
}
|
||||
d.filteringEngine = urlfilter.NewDNSEngine(d.rulesStorage)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -613,7 +647,7 @@ func (d *Dnsfilter) shouldBeInDialCache(host string) bool {
|
||||
|
||||
// Search for an IP address by host name
|
||||
func searchInDialCache(host string) string {
|
||||
rawValue, err := dialCache.Get(host)
|
||||
rawValue, err := gctx.dialCache.Get(host)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
@@ -625,7 +659,7 @@ func searchInDialCache(host string) string {
|
||||
|
||||
// Add "hostname" -> "IP address" entry to cache
|
||||
func addToDialCache(host, ip string) {
|
||||
err := dialCache.Set(host, ip)
|
||||
err := gctx.dialCache.Set(host, ip)
|
||||
if err != nil {
|
||||
log.Debug("dialCache.Set: %s", err)
|
||||
}
|
||||
@@ -669,15 +703,16 @@ func (d *Dnsfilter) createCustomDialContext(resolverAddr string) dialFunctionTyp
|
||||
return nil, e
|
||||
}
|
||||
|
||||
var firstErr error
|
||||
firstErr = nil
|
||||
if len(addrs) == 0 {
|
||||
return nil, fmt.Errorf("couldn't lookup host: %s", host)
|
||||
}
|
||||
|
||||
var dialErrs []error
|
||||
for _, a := range addrs {
|
||||
addr = fmt.Sprintf("%s:%s", a.String(), port)
|
||||
con, err := dialer.DialContext(ctx, network, addr)
|
||||
if err != nil {
|
||||
if firstErr == nil {
|
||||
firstErr = err
|
||||
}
|
||||
dialErrs = append(dialErrs, err)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -687,12 +722,29 @@ func (d *Dnsfilter) createCustomDialContext(resolverAddr string) dialFunctionTyp
|
||||
|
||||
return con, err
|
||||
}
|
||||
return nil, firstErr
|
||||
return nil, errorx.DecorateMany(fmt.Sprintf("couldn't dial to %s", addr), dialErrs...)
|
||||
}
|
||||
}
|
||||
|
||||
// New creates properly initialized DNS Filter that is ready to be used
|
||||
func New(c *Config, filters map[int]string) *Dnsfilter {
|
||||
|
||||
if c != nil {
|
||||
// initialize objects only once
|
||||
if c.SafeBrowsingEnabled && gctx.safebrowsingCache == nil {
|
||||
gctx.safebrowsingCache = gcache.New(defaultCacheSize).LRU().Expiration(defaultCacheTime).Build()
|
||||
}
|
||||
if c.SafeSearchEnabled && gctx.safeSearchCache == nil {
|
||||
gctx.safeSearchCache = gcache.New(defaultCacheSize).LRU().Expiration(defaultCacheTime).Build()
|
||||
}
|
||||
if c.ParentalEnabled && gctx.parentalCache == nil {
|
||||
gctx.parentalCache = gcache.New(defaultCacheSize).LRU().Expiration(defaultCacheTime).Build()
|
||||
}
|
||||
if len(c.ResolverAddress) != 0 && gctx.dialCache == nil {
|
||||
gctx.dialCache = gcache.New(maxDialCacheSize).LRU().Expiration(defaultCacheTime).Build()
|
||||
}
|
||||
}
|
||||
|
||||
d := new(Dnsfilter)
|
||||
|
||||
// Customize the Transport to have larger connection pool,
|
||||
@@ -706,7 +758,6 @@ func New(c *Config, filters map[int]string) *Dnsfilter {
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
}
|
||||
if c != nil && len(c.ResolverAddress) != 0 {
|
||||
dialCache = gcache.New(maxDialCacheSize).LRU().Expiration(defaultCacheTime).Build()
|
||||
d.transport.DialContext = d.createCustomDialContext(c.ResolverAddress)
|
||||
}
|
||||
d.client = http.Client{
|
||||
@@ -782,5 +833,5 @@ func (d *Dnsfilter) SafeSearchDomain(host string) (string, bool) {
|
||||
|
||||
// GetStats return dns filtering stats since startup
|
||||
func (d *Dnsfilter) GetStats() Stats {
|
||||
return stats
|
||||
return gctx.stats
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/bluele/gcache"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
@@ -24,11 +25,14 @@ import (
|
||||
// HELPERS
|
||||
|
||||
func purgeCaches() {
|
||||
if safebrowsingCache != nil {
|
||||
safebrowsingCache.Purge()
|
||||
if gctx.safebrowsingCache != nil {
|
||||
gctx.safebrowsingCache.Purge()
|
||||
}
|
||||
if parentalCache != nil {
|
||||
parentalCache.Purge()
|
||||
if gctx.parentalCache != nil {
|
||||
gctx.parentalCache.Purge()
|
||||
}
|
||||
if gctx.safeSearchCache != nil {
|
||||
gctx.safeSearchCache.Purge()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,14 +43,8 @@ func _Func() string {
|
||||
return path.Base(f.Name())
|
||||
}
|
||||
|
||||
func NewForTest() *Dnsfilter {
|
||||
d := New(nil, nil)
|
||||
purgeCaches()
|
||||
return d
|
||||
}
|
||||
|
||||
func NewForTestFilters(filters map[int]string) *Dnsfilter {
|
||||
d := New(nil, filters)
|
||||
func NewForTest(c *Config, filters map[int]string) *Dnsfilter {
|
||||
d := New(c, filters)
|
||||
purgeCaches()
|
||||
return d
|
||||
}
|
||||
@@ -94,7 +92,7 @@ func TestEtcHostsMatching(t *testing.T) {
|
||||
addr, addr6)
|
||||
filters := make(map[int]string)
|
||||
filters[0] = text
|
||||
d := NewForTestFilters(filters)
|
||||
d := NewForTest(nil, filters)
|
||||
defer d.Destroy()
|
||||
|
||||
d.checkMatchIP(t, "google.com", addr, dns.TypeA)
|
||||
@@ -119,37 +117,35 @@ func TestSafeBrowsing(t *testing.T) {
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(fmt.Sprintf("%s in %s", tc, _Func()), func(t *testing.T) {
|
||||
d := NewForTest()
|
||||
d := NewForTest(&Config{SafeBrowsingEnabled: true}, nil)
|
||||
defer d.Destroy()
|
||||
d.SafeBrowsingEnabled = true
|
||||
stats.Safebrowsing.Requests = 0
|
||||
gctx.stats.Safebrowsing.Requests = 0
|
||||
d.checkMatch(t, "wmconvirus.narod.ru")
|
||||
d.checkMatch(t, "wmconvirus.narod.ru")
|
||||
if stats.Safebrowsing.Requests != 1 {
|
||||
t.Errorf("Safebrowsing lookup positive cache is not working: %v", stats.Safebrowsing.Requests)
|
||||
if gctx.stats.Safebrowsing.Requests != 1 {
|
||||
t.Errorf("Safebrowsing lookup positive cache is not working: %v", gctx.stats.Safebrowsing.Requests)
|
||||
}
|
||||
d.checkMatch(t, "WMconvirus.narod.ru")
|
||||
if stats.Safebrowsing.Requests != 1 {
|
||||
t.Errorf("Safebrowsing lookup positive cache is not working: %v", stats.Safebrowsing.Requests)
|
||||
if gctx.stats.Safebrowsing.Requests != 1 {
|
||||
t.Errorf("Safebrowsing lookup positive cache is not working: %v", gctx.stats.Safebrowsing.Requests)
|
||||
}
|
||||
d.checkMatch(t, "wmconvirus.narod.ru.")
|
||||
d.checkMatch(t, "test.wmconvirus.narod.ru")
|
||||
d.checkMatch(t, "test.wmconvirus.narod.ru.")
|
||||
d.checkMatchEmpty(t, "yandex.ru")
|
||||
d.checkMatchEmpty(t, "pornhub.com")
|
||||
l := stats.Safebrowsing.Requests
|
||||
l := gctx.stats.Safebrowsing.Requests
|
||||
d.checkMatchEmpty(t, "pornhub.com")
|
||||
if stats.Safebrowsing.Requests != l {
|
||||
t.Errorf("Safebrowsing lookup negative cache is not working: %v", stats.Safebrowsing.Requests)
|
||||
if gctx.stats.Safebrowsing.Requests != l {
|
||||
t.Errorf("Safebrowsing lookup negative cache is not working: %v", gctx.stats.Safebrowsing.Requests)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParallelSB(t *testing.T) {
|
||||
d := NewForTest()
|
||||
d := NewForTest(&Config{SafeBrowsingEnabled: true}, nil)
|
||||
defer d.Destroy()
|
||||
d.SafeBrowsingEnabled = true
|
||||
t.Run("group", func(t *testing.T) {
|
||||
for i := 0; i < 100; i++ {
|
||||
t.Run(fmt.Sprintf("aaa%d", i), func(t *testing.T) {
|
||||
@@ -167,7 +163,7 @@ func TestParallelSB(t *testing.T) {
|
||||
|
||||
// the only way to verify that custom server option is working is to point it at a server that does serve safebrowsing
|
||||
func TestSafeBrowsingCustomServerFail(t *testing.T) {
|
||||
d := NewForTest()
|
||||
d := NewForTest(&Config{SafeBrowsingEnabled: true}, nil)
|
||||
defer d.Destroy()
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// w.Write("Hello, client")
|
||||
@@ -176,7 +172,6 @@ func TestSafeBrowsingCustomServerFail(t *testing.T) {
|
||||
defer ts.Close()
|
||||
address := ts.Listener.Addr().String()
|
||||
|
||||
d.SafeBrowsingEnabled = true
|
||||
d.SetHTTPTimeout(time.Second * 5)
|
||||
d.SetSafeBrowsingServer(address) // this will ensure that test fails
|
||||
d.checkMatchEmpty(t, "wmconvirus.narod.ru")
|
||||
@@ -185,13 +180,15 @@ func TestSafeBrowsingCustomServerFail(t *testing.T) {
|
||||
// SAFE SEARCH
|
||||
|
||||
func TestSafeSearch(t *testing.T) {
|
||||
d := NewForTest()
|
||||
d := NewForTest(nil, nil)
|
||||
defer d.Destroy()
|
||||
_, ok := d.SafeSearchDomain("www.google.com")
|
||||
if ok {
|
||||
t.Errorf("Expected safesearch to error when disabled")
|
||||
}
|
||||
d.SafeSearchEnabled = true
|
||||
|
||||
d = NewForTest(&Config{SafeSearchEnabled: true}, nil)
|
||||
defer d.Destroy()
|
||||
val, ok := d.SafeSearchDomain("www.google.com")
|
||||
if !ok {
|
||||
t.Errorf("Expected safesearch to find result for www.google.com")
|
||||
@@ -202,12 +199,9 @@ func TestSafeSearch(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCheckHostSafeSearchYandex(t *testing.T) {
|
||||
d := NewForTest()
|
||||
d := NewForTest(&Config{SafeSearchEnabled: true}, nil)
|
||||
defer d.Destroy()
|
||||
|
||||
// Enable safesearch
|
||||
d.SafeSearchEnabled = true
|
||||
|
||||
// Slice of yandex domains
|
||||
yandex := []string{"yAndeX.ru", "YANdex.COM", "yandex.ua", "yandex.by", "yandex.kz", "www.yandex.com"}
|
||||
|
||||
@@ -225,12 +219,9 @@ func TestCheckHostSafeSearchYandex(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCheckHostSafeSearchGoogle(t *testing.T) {
|
||||
d := NewForTest()
|
||||
d := NewForTest(&Config{SafeSearchEnabled: true}, nil)
|
||||
defer d.Destroy()
|
||||
|
||||
// Enable safesearch
|
||||
d.SafeSearchEnabled = true
|
||||
|
||||
// Slice of google domains
|
||||
googleDomains := []string{"www.google.com", "www.google.im", "www.google.co.in", "www.google.iq", "www.google.is", "www.google.it", "www.google.je"}
|
||||
|
||||
@@ -248,7 +239,7 @@ func TestCheckHostSafeSearchGoogle(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSafeSearchCacheYandex(t *testing.T) {
|
||||
d := NewForTest()
|
||||
d := NewForTest(nil, nil)
|
||||
defer d.Destroy()
|
||||
domain := "yandex.ru"
|
||||
|
||||
@@ -264,8 +255,9 @@ func TestSafeSearchCacheYandex(t *testing.T) {
|
||||
t.Fatalf("SafeSearch is not enabled but there is an answer for `%s` !", domain)
|
||||
}
|
||||
|
||||
// Enable safesearch
|
||||
d.SafeSearchEnabled = true
|
||||
d = NewForTest(&Config{SafeSearchEnabled: true}, nil)
|
||||
defer d.Destroy()
|
||||
|
||||
result, err = d.CheckHost(domain, dns.TypeA, "")
|
||||
if err != nil {
|
||||
t.Fatalf("CheckHost for safesearh domain %s failed cause %s", domain, err)
|
||||
@@ -277,7 +269,7 @@ func TestSafeSearchCacheYandex(t *testing.T) {
|
||||
}
|
||||
|
||||
// Check cache
|
||||
cachedValue, isFound, err := getCachedReason(safeSearchCache, domain)
|
||||
cachedValue, isFound, err := getCachedReason(gctx.safeSearchCache, domain)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("An error occured during cache search for %s: %s", domain, err)
|
||||
@@ -293,7 +285,7 @@ func TestSafeSearchCacheYandex(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSafeSearchCacheGoogle(t *testing.T) {
|
||||
d := NewForTest()
|
||||
d := NewForTest(nil, nil)
|
||||
defer d.Destroy()
|
||||
domain := "www.google.ru"
|
||||
result, err := d.CheckHost(domain, dns.TypeA, "")
|
||||
@@ -304,8 +296,8 @@ func TestSafeSearchCacheGoogle(t *testing.T) {
|
||||
t.Fatalf("SafeSearch is not enabled but there is an answer!")
|
||||
}
|
||||
|
||||
// Enable safesearch and check host
|
||||
d.SafeSearchEnabled = true
|
||||
d = NewForTest(&Config{SafeSearchEnabled: true}, nil)
|
||||
defer d.Destroy()
|
||||
|
||||
// Let's lookup for safesearch domain
|
||||
safeDomain, ok := d.SafeSearchDomain(domain)
|
||||
@@ -338,7 +330,7 @@ func TestSafeSearchCacheGoogle(t *testing.T) {
|
||||
}
|
||||
|
||||
// Check cache
|
||||
cachedValue, isFound, err := getCachedReason(safeSearchCache, domain)
|
||||
cachedValue, isFound, err := getCachedReason(gctx.safeSearchCache, domain)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("An error occured during cache search for %s: %s", domain, err)
|
||||
@@ -356,17 +348,16 @@ func TestSafeSearchCacheGoogle(t *testing.T) {
|
||||
// PARENTAL
|
||||
|
||||
func TestParentalControl(t *testing.T) {
|
||||
d := NewForTest()
|
||||
d := NewForTest(&Config{ParentalEnabled: true}, nil)
|
||||
defer d.Destroy()
|
||||
d.ParentalEnabled = true
|
||||
d.ParentalSensitivity = 3
|
||||
d.checkMatch(t, "pornhub.com")
|
||||
d.checkMatch(t, "pornhub.com")
|
||||
if stats.Parental.Requests != 1 {
|
||||
if gctx.stats.Parental.Requests != 1 {
|
||||
t.Errorf("Parental lookup positive cache is not working")
|
||||
}
|
||||
d.checkMatch(t, "PORNhub.com")
|
||||
if stats.Parental.Requests != 1 {
|
||||
if gctx.stats.Parental.Requests != 1 {
|
||||
t.Errorf("Parental lookup positive cache is not working")
|
||||
}
|
||||
d.checkMatch(t, "www.pornhub.com")
|
||||
@@ -374,9 +365,9 @@ func TestParentalControl(t *testing.T) {
|
||||
d.checkMatch(t, "www.pornhub.com.")
|
||||
d.checkMatchEmpty(t, "www.yandex.ru")
|
||||
d.checkMatchEmpty(t, "yandex.ru")
|
||||
l := stats.Parental.Requests
|
||||
l := gctx.stats.Parental.Requests
|
||||
d.checkMatchEmpty(t, "yandex.ru")
|
||||
if stats.Parental.Requests != l {
|
||||
if gctx.stats.Parental.Requests != l {
|
||||
t.Errorf("Parental lookup negative cache is not working")
|
||||
}
|
||||
|
||||
@@ -442,7 +433,7 @@ func TestMatching(t *testing.T) {
|
||||
t.Run(fmt.Sprintf("%s-%s", test.testname, test.hostname), func(t *testing.T) {
|
||||
filters := make(map[int]string)
|
||||
filters[0] = test.rules
|
||||
d := NewForTestFilters(filters)
|
||||
d := NewForTest(nil, filters)
|
||||
defer d.Destroy()
|
||||
|
||||
ret, err := d.CheckHost(test.hostname, dns.TypeA, "")
|
||||
@@ -470,9 +461,8 @@ func TestClientSettings(t *testing.T) {
|
||||
var r Result
|
||||
filters := make(map[int]string)
|
||||
filters[0] = "||example.org^\n"
|
||||
d := NewForTestFilters(filters)
|
||||
d := NewForTest(&Config{ParentalEnabled: true}, filters)
|
||||
defer d.Destroy()
|
||||
d.ParentalEnabled = true
|
||||
d.ParentalSensitivity = 3
|
||||
|
||||
// no client settings:
|
||||
@@ -508,9 +498,8 @@ func TestClientSettings(t *testing.T) {
|
||||
// BENCHMARKS
|
||||
|
||||
func BenchmarkSafeBrowsing(b *testing.B) {
|
||||
d := NewForTest()
|
||||
d := NewForTest(&Config{SafeBrowsingEnabled: true}, nil)
|
||||
defer d.Destroy()
|
||||
d.SafeBrowsingEnabled = true
|
||||
for n := 0; n < b.N; n++ {
|
||||
hostname := "wmconvirus.narod.ru"
|
||||
ret, err := d.CheckHost(hostname, dns.TypeA, "")
|
||||
@@ -524,9 +513,8 @@ func BenchmarkSafeBrowsing(b *testing.B) {
|
||||
}
|
||||
|
||||
func BenchmarkSafeBrowsingParallel(b *testing.B) {
|
||||
d := NewForTest()
|
||||
d := NewForTest(&Config{SafeBrowsingEnabled: true}, nil)
|
||||
defer d.Destroy()
|
||||
d.SafeBrowsingEnabled = true
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
hostname := "wmconvirus.narod.ru"
|
||||
@@ -542,9 +530,8 @@ func BenchmarkSafeBrowsingParallel(b *testing.B) {
|
||||
}
|
||||
|
||||
func BenchmarkSafeSearch(b *testing.B) {
|
||||
d := NewForTest()
|
||||
d := NewForTest(&Config{SafeSearchEnabled: true}, nil)
|
||||
defer d.Destroy()
|
||||
d.SafeSearchEnabled = true
|
||||
for n := 0; n < b.N; n++ {
|
||||
val, ok := d.SafeSearchDomain("www.google.com")
|
||||
if !ok {
|
||||
@@ -557,9 +544,8 @@ func BenchmarkSafeSearch(b *testing.B) {
|
||||
}
|
||||
|
||||
func BenchmarkSafeSearchParallel(b *testing.B) {
|
||||
d := NewForTest()
|
||||
d := NewForTest(&Config{SafeSearchEnabled: true}, nil)
|
||||
defer d.Destroy()
|
||||
d.SafeSearchEnabled = true
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
val, ok := d.SafeSearchDomain("www.google.com")
|
||||
@@ -572,3 +558,17 @@ func BenchmarkSafeSearchParallel(b *testing.B) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestDnsfilterDialCache(t *testing.T) {
|
||||
d := Dnsfilter{}
|
||||
gctx.dialCache = gcache.New(1).LRU().Expiration(30 * time.Minute).Build()
|
||||
|
||||
d.shouldBeInDialCache("hostname")
|
||||
if searchInDialCache("hostname") != "" {
|
||||
t.Errorf("searchInDialCache")
|
||||
}
|
||||
addToDialCache("hostname", "1.1.1.1")
|
||||
if searchInDialCache("hostname") != "1.1.1.1" {
|
||||
t.Errorf("searchInDialCache")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,7 +260,11 @@ func (s *Server) initDNSFilter() error {
|
||||
if s.conf.FilteringEnabled {
|
||||
filters = make(map[int]string)
|
||||
for _, f := range s.conf.Filters {
|
||||
filters[int(f.ID)] = string(f.Data)
|
||||
if f.ID == 0 {
|
||||
filters[int(f.ID)] = string(f.Data)
|
||||
} else {
|
||||
filters[int(f.ID)] = f.FilePath
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -459,7 +459,7 @@ func createTestServer(t *testing.T) *Server {
|
||||
s.conf.Filters = make([]dnsfilter.Filter, 0)
|
||||
|
||||
rules := "||nxdomain.example.org^\n||null.example.org^\n127.0.0.1 host.example.org\n"
|
||||
filter := dnsfilter.Filter{ID: 1, Data: []byte(rules)}
|
||||
filter := dnsfilter.Filter{ID: 0, Data: []byte(rules)}
|
||||
s.conf.Filters = append(s.conf.Filters, filter)
|
||||
return s
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ func (l *queryLog) logRequest(question *dns.Msg, answer *dns.Msg, result *dnsfil
|
||||
if needFlush {
|
||||
// write to file
|
||||
// do it in separate goroutine -- we are stalling DNS response this whole time
|
||||
go l.flushLogBuffer(false)
|
||||
go l.flushLogBuffer(false) // nolint
|
||||
}
|
||||
|
||||
return &entry
|
||||
|
||||
6
go.mod
6
go.mod
@@ -3,14 +3,14 @@ module github.com/AdguardTeam/AdGuardHome
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/AdguardTeam/dnsproxy v0.14.0
|
||||
github.com/AdguardTeam/dnsproxy v0.15.0
|
||||
github.com/AdguardTeam/golibs v0.1.3
|
||||
github.com/AdguardTeam/urlfilter v0.3.0
|
||||
github.com/AdguardTeam/urlfilter v0.4.0
|
||||
github.com/NYTimes/gziphandler v1.1.1
|
||||
github.com/bluele/gcache v0.0.0-20190203144525-2016d595ccb0
|
||||
github.com/go-test/deep v1.0.1
|
||||
github.com/gobuffalo/packr v1.19.0
|
||||
github.com/joomcode/errorx v0.1.0
|
||||
github.com/joomcode/errorx v0.8.0
|
||||
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
|
||||
|
||||
13
go.sum
13
go.sum
@@ -1,10 +1,12 @@
|
||||
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/dnsproxy v0.15.0 h1:aK287RkLZmxWmwOZ8XqdhHYzP1I04gDQyzgXw6/nAZQ=
|
||||
github.com/AdguardTeam/dnsproxy v0.15.0/go.mod h1:HYc+jgo3GRr82WSYPcbcNn6yJwnXuoMnbsb0Sz0ERLs=
|
||||
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=
|
||||
github.com/AdguardTeam/urlfilter v0.3.0 h1:WNd3uZEYWwxylUuA8QS6V5DqHNsVFw3ZD/E2rd5HGpo=
|
||||
github.com/AdguardTeam/urlfilter v0.3.0/go.mod h1:9xfZ6R2vB8LlT8G9LxtbNhDsbr/xybUOSwmJvpXhl/c=
|
||||
github.com/AdguardTeam/urlfilter v0.4.0 h1:s4EFwI4+gzBdnATNKbuOY53wS2PCHgFZBv1Ixxva6tg=
|
||||
github.com/AdguardTeam/urlfilter v0.4.0/go.mod h1:6YehXZ8e0Hx2MvqeQWLFom6IkPinm04tNhO1CkwAxmg=
|
||||
github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
|
||||
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
|
||||
github.com/StackExchange/wmi v0.0.0-20181212234831-e0a55b97c705 h1:UUppSQnhf4Yc6xGxSkoQpPhb7RVzuv5Nb1mwJ5VId9s=
|
||||
@@ -47,6 +49,8 @@ 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 v0.1.0 h1:QmJMiI1DE1UFje2aI1ZWO/VMT5a32qBoXUclGOt8vsc=
|
||||
github.com/joomcode/errorx v0.1.0/go.mod h1:kgco15ekB6cs+4Xjzo7SPeXzx38PbJzBwbnu9qfVNHQ=
|
||||
github.com/joomcode/errorx v0.8.0 h1:GhAqPtcYuo1O7TOIbtzEIDzPGQ3SrKJ3tdjXNmUtDNo=
|
||||
github.com/joomcode/errorx v0.8.0/go.mod h1:kgco15ekB6cs+4Xjzo7SPeXzx38PbJzBwbnu9qfVNHQ=
|
||||
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1 h1:PJPDf8OUfOK1bb/NeTKd4f1QXZItOX389VN3B6qC8ro=
|
||||
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
|
||||
github.com/kardianos/service v0.0.0-20181115005516-4c239ee84e7b h1:vfiqKno48aUndBMjTeWFpCExNnTf2Xnd6d228L4EfTQ=
|
||||
@@ -86,10 +90,6 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90Pveol
|
||||
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=
|
||||
@@ -108,7 +108,6 @@ golang.org/x/sys v0.0.0-20190122071731-054c452bb702 h1:Lk4tbZFnlyPgV+sLgTw5yGfzr
|
||||
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=
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package home
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
@@ -7,11 +7,18 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
clientsUpdatePeriod = 1 * time.Hour
|
||||
)
|
||||
|
||||
// Client information
|
||||
@@ -40,8 +47,10 @@ type clientJSON struct {
|
||||
type clientSource uint
|
||||
|
||||
const (
|
||||
ClientSourceHostsFile clientSource = 0 // from /etc/hosts
|
||||
ClientSourceRDNS clientSource = 1 // from rDNS
|
||||
// Priority: etc/hosts > ARP > rDNS
|
||||
ClientSourceRDNS clientSource = 0 // from rDNS
|
||||
ClientSourceARP clientSource = 1 // from 'arp -a'
|
||||
ClientSourceHostsFile clientSource = 2 // from /etc/hosts
|
||||
)
|
||||
|
||||
// ClientHost information
|
||||
@@ -68,7 +77,15 @@ func clientsInit() {
|
||||
clients.ipIndex = make(map[string]*Client)
|
||||
clients.ipHost = make(map[string]ClientHost)
|
||||
|
||||
clientsAddFromHostsFile()
|
||||
go periodicClientsUpdate()
|
||||
}
|
||||
|
||||
func periodicClientsUpdate() {
|
||||
for {
|
||||
clientsAddFromHostsFile()
|
||||
clientsAddFromSystemARP()
|
||||
time.Sleep(clientsUpdatePeriod)
|
||||
}
|
||||
}
|
||||
|
||||
func clientsGetList() map[string]*Client {
|
||||
@@ -240,13 +257,16 @@ func clientUpdate(name string, c Client) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add new IP -> Host pair
|
||||
// Use priority of the source (etc/hosts > ARP > rDNS)
|
||||
// so we overwrite existing entries with an equal or higher priority
|
||||
func clientAddHost(ip, host string, source clientSource) (bool, error) {
|
||||
clients.lock.Lock()
|
||||
defer clients.lock.Unlock()
|
||||
|
||||
// check index
|
||||
_, ok := clients.ipHost[ip]
|
||||
if ok {
|
||||
c, ok := clients.ipHost[ip]
|
||||
if ok && c.Source > source {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
@@ -254,7 +274,7 @@ func clientAddHost(ip, host string, source clientSource) (bool, error) {
|
||||
Host: host,
|
||||
Source: source,
|
||||
}
|
||||
log.Tracef("'%s': '%s' -> [%d]", host, ip, len(clients.ipHost))
|
||||
log.Tracef("'%s' -> '%s' [%d]", ip, host, len(clients.ipHost))
|
||||
return true, nil
|
||||
}
|
||||
|
||||
@@ -296,6 +316,52 @@ func clientsAddFromHostsFile() {
|
||||
log.Info("Added %d client aliases from %s", n, hostsFn)
|
||||
}
|
||||
|
||||
// Add IP -> Host pairs from the system's `arp -a` command output
|
||||
// The command's output is:
|
||||
// HOST (IP) at MAC on IFACE
|
||||
func clientsAddFromSystemARP() {
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
return
|
||||
}
|
||||
|
||||
cmd := exec.Command("arp", "-a")
|
||||
log.Tracef("executing %s %v", cmd.Path, cmd.Args)
|
||||
data, err := cmd.Output()
|
||||
if err != nil || cmd.ProcessState.ExitCode() != 0 {
|
||||
log.Debug("command %s has failed: %v code:%d",
|
||||
cmd.Path, err, cmd.ProcessState.ExitCode())
|
||||
return
|
||||
}
|
||||
|
||||
n := 0
|
||||
lines := strings.Split(string(data), "\n")
|
||||
for _, ln := range lines {
|
||||
|
||||
open := strings.Index(ln, " (")
|
||||
close := strings.Index(ln, ") ")
|
||||
if open == -1 || close == -1 || open >= close {
|
||||
continue
|
||||
}
|
||||
|
||||
host := ln[:open]
|
||||
ip := ln[open+2 : close]
|
||||
if utils.IsValidHostname(host) != nil || net.ParseIP(ip) == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
ok, e := clientAddHost(ip, host, ClientSourceARP)
|
||||
if e != nil {
|
||||
log.Tracef("%s", e)
|
||||
}
|
||||
if ok {
|
||||
n++
|
||||
}
|
||||
}
|
||||
|
||||
log.Info("Added %d client aliases from 'arp -a' command output", n)
|
||||
}
|
||||
|
||||
type clientHostJSON struct {
|
||||
IP string `json:"ip"`
|
||||
Name string `json:"name"`
|
||||
@@ -342,8 +408,11 @@ func handleGetClients(w http.ResponseWriter, r *http.Request) {
|
||||
Name: ch.Host,
|
||||
}
|
||||
cj.Source = "etc/hosts"
|
||||
if ch.Source == ClientSourceRDNS {
|
||||
switch ch.Source {
|
||||
case ClientSourceRDNS:
|
||||
cj.Source = "rDNS"
|
||||
case ClientSourceARP:
|
||||
cj.Source = "ARP"
|
||||
}
|
||||
data.AutoClients = append(data.AutoClients, cj)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package home
|
||||
|
||||
import "testing"
|
||||
|
||||
@@ -29,6 +29,16 @@ func TestClients(t *testing.T) {
|
||||
t.Fatalf("clientAdd #2")
|
||||
}
|
||||
|
||||
c, b = clientFind("1.1.1.1")
|
||||
if !b || c.Name != "client1" {
|
||||
t.Fatalf("clientFind #1")
|
||||
}
|
||||
|
||||
c, b = clientFind("2.2.2.2")
|
||||
if !b || c.Name != "client2" {
|
||||
t.Fatalf("clientFind #2")
|
||||
}
|
||||
|
||||
// failed add - name in use
|
||||
c = Client{
|
||||
IP: "1.2.3.5",
|
||||
@@ -104,17 +114,29 @@ func TestClients(t *testing.T) {
|
||||
}
|
||||
|
||||
// add host client
|
||||
b, e = clientAddHost("1.1.1.1", "host", ClientSourceHostsFile)
|
||||
b, e = clientAddHost("1.1.1.1", "host", ClientSourceARP)
|
||||
if !b || e != nil {
|
||||
t.Fatalf("clientAddHost")
|
||||
}
|
||||
|
||||
// failed add - ip exists
|
||||
b, e = clientAddHost("1.1.1.1", "host", ClientSourceHostsFile)
|
||||
b, e = clientAddHost("1.1.1.1", "host1", ClientSourceRDNS)
|
||||
if b || e != nil {
|
||||
t.Fatalf("clientAddHost - ip exists")
|
||||
}
|
||||
|
||||
// overwrite with new data
|
||||
b, e = clientAddHost("1.1.1.1", "host2", ClientSourceARP)
|
||||
if !b || e != nil {
|
||||
t.Fatalf("clientAddHost - overwrite with new data")
|
||||
}
|
||||
|
||||
// overwrite with new data (higher priority)
|
||||
b, e = clientAddHost("1.1.1.1", "host3", ClientSourceHostsFile)
|
||||
if !b || e != nil {
|
||||
t.Fatalf("clientAddHost - overwrite with new data (higher priority)")
|
||||
}
|
||||
|
||||
// get
|
||||
if !clientExists("1.1.1.1") {
|
||||
t.Fatalf("clientAddHost")
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package home
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
@@ -51,6 +51,7 @@ type configuration struct {
|
||||
// runningAsService flag is set to true when options are passed from the service runner
|
||||
runningAsService bool
|
||||
disableUpdate bool // If set, don't check for updates
|
||||
appSignalChannel chan os.Signal
|
||||
|
||||
BindHost string `yaml:"bind_host"` // BindHost is the IP address of the HTTP server to bind to
|
||||
BindPort int `yaml:"bind_port"` // BindPort is the port the HTTP server
|
||||
@@ -126,6 +127,7 @@ type tlsConfig struct {
|
||||
}
|
||||
|
||||
// initialize to default values, will be changed later when reading config or parsing command line
|
||||
// TODO: Get rid of global variables
|
||||
var config = configuration{
|
||||
ourConfigFilename: "AdGuardHome.yaml",
|
||||
BindPort: 3000,
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package home
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -80,27 +80,60 @@ func httpUpdateConfigReloadDNSReturnOK(w http.ResponseWriter, r *http.Request) {
|
||||
returnOK(w)
|
||||
}
|
||||
|
||||
func handleStatus(w http.ResponseWriter, r *http.Request) {
|
||||
log.Tracef("%s %v", r.Method, r.URL)
|
||||
func addDNSAddress(dnsAddresses *[]string, addr string) {
|
||||
if config.DNS.Port != 53 {
|
||||
addr = fmt.Sprintf("%s:%d", addr, config.DNS.Port)
|
||||
}
|
||||
*dnsAddresses = append(*dnsAddresses, addr)
|
||||
}
|
||||
|
||||
// Get the list of DNS addresses the server is listening on
|
||||
func getDNSAddresses() []string {
|
||||
dnsAddresses := []string{}
|
||||
|
||||
if config.DNS.BindHost == "0.0.0.0" {
|
||||
|
||||
ifaces, e := getValidNetInterfacesForWeb()
|
||||
if e != nil {
|
||||
log.Error("Couldn't get network interfaces: %v", e)
|
||||
return []string{}
|
||||
}
|
||||
|
||||
for _, iface := range ifaces {
|
||||
for _, addr := range iface.Addresses {
|
||||
dnsAddresses = append(dnsAddresses, addr)
|
||||
addDNSAddress(&dnsAddresses, addr)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(dnsAddresses) == 0 {
|
||||
dnsAddresses = append(dnsAddresses, config.DNS.BindHost)
|
||||
|
||||
} else {
|
||||
addDNSAddress(&dnsAddresses, config.DNS.BindHost)
|
||||
}
|
||||
|
||||
if config.TLS.Enabled && len(config.TLS.ServerName) != 0 {
|
||||
|
||||
if config.TLS.PortHTTPS != 0 {
|
||||
addr := config.TLS.ServerName
|
||||
if config.TLS.PortHTTPS != 443 {
|
||||
addr = fmt.Sprintf("%s:%d", addr, config.TLS.PortHTTPS)
|
||||
}
|
||||
addr = fmt.Sprintf("https://%s/dns-query", addr)
|
||||
dnsAddresses = append(dnsAddresses, addr)
|
||||
}
|
||||
|
||||
if config.TLS.PortDNSOverTLS != 0 {
|
||||
addr := fmt.Sprintf("tls://%s:%d", config.TLS.ServerName, config.TLS.PortDNSOverTLS)
|
||||
dnsAddresses = append(dnsAddresses, addr)
|
||||
}
|
||||
}
|
||||
|
||||
return dnsAddresses
|
||||
}
|
||||
|
||||
func handleStatus(w http.ResponseWriter, r *http.Request) {
|
||||
log.Tracef("%s %v", r.Method, r.URL)
|
||||
|
||||
data := map[string]interface{}{
|
||||
"dns_addresses": dnsAddresses,
|
||||
"dns_addresses": getDNSAddresses(),
|
||||
"http_port": config.BindPort,
|
||||
"dns_port": config.DNS.Port,
|
||||
"protection_enabled": config.DNS.ProtectionEnabled,
|
||||
@@ -109,7 +142,7 @@ func handleStatus(w http.ResponseWriter, r *http.Request) {
|
||||
"bootstrap_dns": config.DNS.BootstrapDNS,
|
||||
"upstream_dns": config.DNS.UpstreamDNS,
|
||||
"all_servers": config.DNS.AllServers,
|
||||
"version": VersionString,
|
||||
"version": versionString,
|
||||
"language": config.Language,
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package home
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package home
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package home
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@@ -1,6 +1,6 @@
|
||||
// Control: TLS configuring handlers
|
||||
|
||||
package main
|
||||
package home
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package home
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
@@ -43,7 +43,7 @@ func getVersionResp(data []byte) []byte {
|
||||
}
|
||||
|
||||
_, ok := versionJSON[fmt.Sprintf("download_%s_%s", runtime.GOOS, runtime.GOARCH)]
|
||||
if ok && ret["new_version"] != VersionString && VersionString >= selfUpdateMinVersion {
|
||||
if ok && ret["new_version"] != versionString && versionString >= selfUpdateMinVersion {
|
||||
ret["can_autoupdate"] = true
|
||||
}
|
||||
|
||||
@@ -51,6 +51,10 @@ func getVersionResp(data []byte) []byte {
|
||||
return d
|
||||
}
|
||||
|
||||
type getVersionJSONRequest struct {
|
||||
RecheckNow bool `json:"recheck_now"`
|
||||
}
|
||||
|
||||
// Get the latest available version from the Internet
|
||||
func handleGetVersionJSON(w http.ResponseWriter, r *http.Request) {
|
||||
log.Tracef("%s %v", r.Method, r.URL)
|
||||
@@ -60,19 +64,29 @@ func handleGetVersionJSON(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
controlLock.Lock()
|
||||
cached := now.Sub(versionCheckLastTime) <= versionCheckPeriod && len(versionCheckJSON) != 0
|
||||
data := versionCheckJSON
|
||||
controlLock.Unlock()
|
||||
|
||||
if cached {
|
||||
// return cached copy
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write(getVersionResp(data))
|
||||
req := getVersionJSONRequest{}
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil {
|
||||
httpError(w, http.StatusBadRequest, "JSON parse: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
if !req.RecheckNow {
|
||||
controlLock.Lock()
|
||||
cached := now.Sub(versionCheckLastTime) <= versionCheckPeriod && len(versionCheckJSON) != 0
|
||||
data := versionCheckJSON
|
||||
controlLock.Unlock()
|
||||
|
||||
if cached {
|
||||
log.Tracef("Returning cached data")
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write(getVersionResp(data))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
log.Tracef("Downloading data from %s", versionCheckURL)
|
||||
resp, err := client.Get(versionCheckURL)
|
||||
if err != nil {
|
||||
httpError(w, http.StatusBadGateway, "Couldn't get version check json from %s: %T %s\n", versionCheckURL, err, err)
|
||||
@@ -145,12 +159,12 @@ func getUpdateInfo(jsonData []byte) (*updateInfo, error) {
|
||||
return nil, fmt.Errorf("Invalid JSON")
|
||||
}
|
||||
|
||||
if u.newVer == VersionString {
|
||||
if u.newVer == versionString {
|
||||
return nil, fmt.Errorf("No need to update")
|
||||
}
|
||||
|
||||
u.updateDir = filepath.Join(workDir, fmt.Sprintf("agh-update-%s", u.newVer))
|
||||
u.backupDir = filepath.Join(workDir, fmt.Sprintf("agh-backup-%s", VersionString))
|
||||
u.backupDir = filepath.Join(workDir, "agh-backup")
|
||||
|
||||
_, pkgFileName := filepath.Split(u.pkgURL)
|
||||
if len(pkgFileName) == 0 {
|
||||
@@ -360,7 +374,7 @@ func getPackageFile(u *updateInfo) error {
|
||||
// Perform an update procedure
|
||||
func doUpdate(u *updateInfo) error {
|
||||
log.Info("Updating from %s to %s. URL:%s Package:%s",
|
||||
VersionString, u.newVer, u.pkgURL, u.pkgName)
|
||||
versionString, u.newVer, u.pkgURL, u.pkgName)
|
||||
|
||||
_ = os.Mkdir(u.updateDir, 0755)
|
||||
|
||||
91
home/control_update_test.go
Normal file
91
home/control_update_test.go
Normal file
@@ -0,0 +1,91 @@
|
||||
// +build ignore
|
||||
|
||||
package home
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDoUpdate(t *testing.T) {
|
||||
|
||||
config.DNS.Port = 0
|
||||
config.ourWorkingDir = "..." // set absolute path
|
||||
newver := "v0.96"
|
||||
|
||||
data := `{
|
||||
"version": "v0.96",
|
||||
"announcement": "AdGuard Home v0.96 is now available!",
|
||||
"announcement_url": "",
|
||||
"download_windows_amd64": "",
|
||||
"download_windows_386": "",
|
||||
"download_darwin_amd64": "",
|
||||
"download_linux_amd64": "https://github.com/AdguardTeam/AdGuardHome/releases/download/v0.96/AdGuardHome_linux_amd64.tar.gz",
|
||||
"download_linux_386": "",
|
||||
"download_linux_arm": "",
|
||||
"download_linux_arm64": "",
|
||||
"download_linux_mips": "",
|
||||
"download_linux_mipsle": "",
|
||||
"selfupdate_min_version": "v0.0"
|
||||
}`
|
||||
uu, err := getUpdateInfo([]byte(data))
|
||||
if err != nil {
|
||||
t.Fatalf("getUpdateInfo: %s", err)
|
||||
}
|
||||
|
||||
u := updateInfo{
|
||||
pkgURL: "https://github.com/AdguardTeam/AdGuardHome/releases/download/" + newver + "/AdGuardHome_linux_amd64.tar.gz",
|
||||
pkgName: config.ourWorkingDir + "/agh-update-" + newver + "/AdGuardHome_linux_amd64.tar.gz",
|
||||
newVer: newver,
|
||||
updateDir: config.ourWorkingDir + "/agh-update-" + newver,
|
||||
backupDir: config.ourWorkingDir + "/agh-backup",
|
||||
configName: config.ourWorkingDir + "/AdGuardHome.yaml",
|
||||
updateConfigName: config.ourWorkingDir + "/agh-update-" + newver + "/AdGuardHome/AdGuardHome.yaml",
|
||||
curBinName: config.ourWorkingDir + "/AdGuardHome",
|
||||
bkpBinName: config.ourWorkingDir + "/agh-backup/AdGuardHome",
|
||||
newBinName: config.ourWorkingDir + "/agh-update-" + newver + "/AdGuardHome/AdGuardHome",
|
||||
}
|
||||
|
||||
if uu.pkgURL != u.pkgURL ||
|
||||
uu.pkgName != u.pkgName ||
|
||||
uu.newVer != u.newVer ||
|
||||
uu.updateDir != u.updateDir ||
|
||||
uu.backupDir != u.backupDir ||
|
||||
uu.configName != u.configName ||
|
||||
uu.updateConfigName != u.updateConfigName ||
|
||||
uu.curBinName != u.curBinName ||
|
||||
uu.bkpBinName != u.bkpBinName ||
|
||||
uu.newBinName != u.newBinName {
|
||||
t.Fatalf("getUpdateInfo: %v != %v", uu, u)
|
||||
}
|
||||
|
||||
e := doUpdate(&u)
|
||||
if e != nil {
|
||||
t.Fatalf("FAILED: %s", e)
|
||||
}
|
||||
os.RemoveAll(u.backupDir)
|
||||
}
|
||||
|
||||
func TestTargzFileUnpack(t *testing.T) {
|
||||
fn := "./dist/AdGuardHome_v0.95_linux_amd64.tar.gz"
|
||||
outdir := "./test-unpack"
|
||||
_ = os.Mkdir(outdir, 0755)
|
||||
files, e := targzFileUnpack(fn, outdir)
|
||||
if e != nil {
|
||||
t.Fatalf("FAILED: %s", e)
|
||||
}
|
||||
t.Logf("%v", files)
|
||||
os.RemoveAll(outdir)
|
||||
}
|
||||
|
||||
func TestZipFileUnpack(t *testing.T) {
|
||||
fn := "./dist/AdGuardHome_v0.95_Windows_amd64.zip"
|
||||
outdir := "./test-unpack"
|
||||
_ = os.Mkdir(outdir, 0755)
|
||||
files, e := zipFileUnpack(fn, outdir)
|
||||
if e != nil {
|
||||
t.Fatalf("FAILED: %s", e)
|
||||
}
|
||||
t.Logf("%v", files)
|
||||
os.RemoveAll(outdir)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package home
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package home
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -165,10 +165,13 @@ func onDNSRequest(d *proxy.DNSContext) {
|
||||
return
|
||||
}
|
||||
|
||||
beginAsyncRDNS(ip)
|
||||
ipAddr := net.ParseIP(ip)
|
||||
if !ipAddr.IsLoopback() {
|
||||
beginAsyncRDNS(ip)
|
||||
}
|
||||
}
|
||||
|
||||
func generateServerConfig() dnsforward.ServerConfig {
|
||||
func generateServerConfig() (dnsforward.ServerConfig, error) {
|
||||
filters := []dnsfilter.Filter{}
|
||||
userFilter := userFilter()
|
||||
filters = append(filters, dnsfilter.Filter{
|
||||
@@ -177,8 +180,8 @@ func generateServerConfig() dnsforward.ServerConfig {
|
||||
})
|
||||
for _, filter := range config.Filters {
|
||||
filters = append(filters, dnsfilter.Filter{
|
||||
ID: filter.ID,
|
||||
Data: filter.Data,
|
||||
ID: filter.ID,
|
||||
FilePath: filter.Path(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -203,14 +206,14 @@ func generateServerConfig() dnsforward.ServerConfig {
|
||||
|
||||
upstreamConfig, err := proxy.ParseUpstreamsConfig(config.DNS.UpstreamDNS, config.DNS.BootstrapDNS, dnsforward.DefaultTimeout)
|
||||
if err != nil {
|
||||
log.Error("Couldn't get upstreams configuration cause: %s", err)
|
||||
return newconfig, fmt.Errorf("Couldn't get upstreams configuration cause: %s", err)
|
||||
}
|
||||
newconfig.Upstreams = upstreamConfig.Upstreams
|
||||
newconfig.DomainsReservedUpstreams = upstreamConfig.DomainReservedUpstreams
|
||||
newconfig.AllServers = config.DNS.AllServers
|
||||
newconfig.FilterHandler = applyClientSettings
|
||||
newconfig.OnDNSRequest = onDNSRequest
|
||||
return newconfig
|
||||
return newconfig, nil
|
||||
}
|
||||
|
||||
// If a client has his own settings, apply them
|
||||
@@ -221,18 +224,10 @@ func applyClientSettings(clientAddr string, setts *dnsfilter.RequestFilteringSet
|
||||
}
|
||||
|
||||
log.Debug("Using settings for client with IP %s", clientAddr)
|
||||
if !c.FilteringEnabled {
|
||||
setts.FilteringEnabled = false
|
||||
}
|
||||
if !c.SafeSearchEnabled {
|
||||
setts.SafeSearchEnabled = false
|
||||
}
|
||||
if !c.SafeBrowsingEnabled {
|
||||
setts.SafeBrowsingEnabled = false
|
||||
}
|
||||
if !c.ParentalEnabled {
|
||||
setts.ParentalEnabled = false
|
||||
}
|
||||
setts.FilteringEnabled = c.FilteringEnabled
|
||||
setts.SafeSearchEnabled = c.SafeSearchEnabled
|
||||
setts.SafeBrowsingEnabled = c.SafeBrowsingEnabled
|
||||
setts.ParentalEnabled = c.ParentalEnabled
|
||||
}
|
||||
|
||||
func startDNSServer() error {
|
||||
@@ -240,8 +235,11 @@ func startDNSServer() error {
|
||||
return fmt.Errorf("unable to start forwarding DNS server: Already running")
|
||||
}
|
||||
|
||||
newconfig := generateServerConfig()
|
||||
err := dnsServer.Start(&newconfig)
|
||||
newconfig, err := generateServerConfig()
|
||||
if err != nil {
|
||||
return errorx.Decorate(err, "Couldn't start forwarding DNS server")
|
||||
}
|
||||
err = dnsServer.Start(&newconfig)
|
||||
if err != nil {
|
||||
return errorx.Decorate(err, "Couldn't start forwarding DNS server")
|
||||
}
|
||||
@@ -259,8 +257,11 @@ func reconfigureDNSServer() error {
|
||||
return fmt.Errorf("Refusing to reconfigure forwarding DNS server: not running")
|
||||
}
|
||||
|
||||
config := generateServerConfig()
|
||||
err := dnsServer.Reconfigure(&config)
|
||||
config, err := generateServerConfig()
|
||||
if err != nil {
|
||||
return errorx.Decorate(err, "Couldn't start forwarding DNS server")
|
||||
}
|
||||
err = dnsServer.Reconfigure(&config)
|
||||
if err != nil {
|
||||
return errorx.Decorate(err, "Couldn't start forwarding DNS server")
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package home
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package home
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -241,7 +241,7 @@ func refreshFiltersIfNecessary(force bool) int {
|
||||
log.Info("Updated filter #%d. Rules: %d -> %d",
|
||||
f.ID, f.RulesCount, uf.RulesCount)
|
||||
f.Name = uf.Name
|
||||
f.Data = uf.Data
|
||||
f.Data = nil
|
||||
f.RulesCount = uf.RulesCount
|
||||
f.checksum = uf.checksum
|
||||
updateCount++
|
||||
@@ -339,6 +339,9 @@ func (filter *filter) update() (bool, error) {
|
||||
}
|
||||
|
||||
// saves filter contents to the file in dataDir
|
||||
// This method is safe to call during filters update,
|
||||
// because it creates a new file and then renames it,
|
||||
// so the currently opened file descriptors to the old filter file remain valid.
|
||||
func (filter *filter) save() error {
|
||||
filterFilePath := filter.Path()
|
||||
log.Printf("Saving filter %d contents to: %s", filter.ID, filterFilePath)
|
||||
@@ -369,7 +372,7 @@ func (filter *filter) load() error {
|
||||
rulesCount, _ := parseFilterContents(filterFileContents)
|
||||
|
||||
filter.RulesCount = rulesCount
|
||||
filter.Data = filterFileContents
|
||||
filter.Data = nil
|
||||
filter.checksum = crc32.ChecksumIEEE(filterFileContents)
|
||||
filter.LastUpdated = filter.LastTimeUpdated()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package home
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
@@ -338,20 +338,21 @@ func customDialContext(ctx context.Context, network, addr string) (net.Conn, err
|
||||
return nil, e
|
||||
}
|
||||
|
||||
var firstErr error
|
||||
firstErr = nil
|
||||
if len(addrs) == 0 {
|
||||
return nil, fmt.Errorf("couldn't lookup host: %s", host)
|
||||
}
|
||||
|
||||
var dialErrs []error
|
||||
for _, a := range addrs {
|
||||
addr = net.JoinHostPort(a.String(), port)
|
||||
con, err := dialer.DialContext(ctx, network, addr)
|
||||
if err != nil {
|
||||
if firstErr == nil {
|
||||
firstErr = err
|
||||
}
|
||||
dialErrs = append(dialErrs, err)
|
||||
continue
|
||||
}
|
||||
return con, err
|
||||
}
|
||||
return nil, firstErr
|
||||
return nil, errorx.DecorateMany(fmt.Sprintf("couldn't dial to %s", addr), dialErrs...)
|
||||
}
|
||||
|
||||
// check if error is "address already in use"
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package home
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package home
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
@@ -25,15 +25,6 @@ import (
|
||||
"github.com/gobuffalo/packr"
|
||||
)
|
||||
|
||||
// VersionString will be set through ldflags, contains current version
|
||||
var VersionString = "undefined"
|
||||
|
||||
// updateChannel can be set via ldflags
|
||||
var updateChannel = "release"
|
||||
var versionCheckURL = "https://static.adguard.com/adguardhome/" + updateChannel + "/version.json"
|
||||
|
||||
const versionCheckPeriod = time.Hour * 8
|
||||
|
||||
var httpServer *http.Server
|
||||
var httpsServer struct {
|
||||
server *http.Server
|
||||
@@ -48,8 +39,22 @@ const (
|
||||
configSyslog = "syslog"
|
||||
)
|
||||
|
||||
// Update-related variables
|
||||
var (
|
||||
versionString string
|
||||
updateChannel string
|
||||
versionCheckURL string
|
||||
)
|
||||
|
||||
const versionCheckPeriod = time.Hour * 8
|
||||
|
||||
// main is the entry point
|
||||
func main() {
|
||||
func Main(version string, channel string) {
|
||||
// Init update-related global variables
|
||||
versionString = version
|
||||
updateChannel = channel
|
||||
versionCheckURL = "https://static.adguard.com/adguardhome/" + updateChannel + "/version.json"
|
||||
|
||||
// config can be specified, which reads options from there, but other command line flags have to override config values
|
||||
// therefore, we must do it manually instead of using a lib
|
||||
args := loadOptions()
|
||||
@@ -81,7 +86,7 @@ func run(args options) {
|
||||
enableTLS13()
|
||||
|
||||
// print the first message after logger is configured
|
||||
log.Printf("AdGuard Home, version %s, channel %s\n", VersionString, updateChannel)
|
||||
log.Printf("AdGuard Home, version %s, channel %s\n", versionString, updateChannel)
|
||||
log.Debug("Current working directory is %s", config.ourWorkingDir)
|
||||
if args.runningAsService {
|
||||
log.Info("AdGuard Home is running as a service")
|
||||
@@ -94,10 +99,10 @@ func run(args options) {
|
||||
requireAdminRights()
|
||||
}
|
||||
|
||||
signalChannel := make(chan os.Signal)
|
||||
signal.Notify(signalChannel, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT)
|
||||
config.appSignalChannel = make(chan os.Signal)
|
||||
signal.Notify(config.appSignalChannel, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT)
|
||||
go func() {
|
||||
<-signalChannel
|
||||
<-config.appSignalChannel
|
||||
cleanup()
|
||||
cleanupAlways()
|
||||
os.Exit(0)
|
||||
@@ -174,7 +179,7 @@ func run(args options) {
|
||||
go periodicallyRefreshFilters()
|
||||
|
||||
// Initialize and run the admin Web interface
|
||||
box := packr.NewBox("build/static")
|
||||
box := packr.NewBox("../build/static")
|
||||
|
||||
// if not configured, redirect / to /install.html, otherwise redirect /install.html to /
|
||||
http.Handle("/", postInstallHandler(optionalAuthHandler(gziphandler.GzipHandler(http.FileServer(box)))))
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package home
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
27
home/os_freebsd.go
Normal file
27
home/os_freebsd.go
Normal file
@@ -0,0 +1,27 @@
|
||||
// +build freebsd
|
||||
|
||||
package home
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
)
|
||||
|
||||
// Set user-specified limit of how many fd's we can use
|
||||
// https://github.com/AdguardTeam/AdGuardHome/issues/659
|
||||
func setRlimit(val uint) {
|
||||
var rlim syscall.Rlimit
|
||||
rlim.Max = int64(val)
|
||||
rlim.Cur = int64(val)
|
||||
err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rlim)
|
||||
if err != nil {
|
||||
log.Error("Setrlimit() failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the current user has root (administrator) rights
|
||||
func haveAdminRights() (bool, error) {
|
||||
return os.Getuid() == 0, nil
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris
|
||||
// +build aix darwin dragonfly linux netbsd openbsd solaris
|
||||
|
||||
package main
|
||||
package home
|
||||
|
||||
import (
|
||||
"os"
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package home
|
||||
|
||||
import "golang.org/x/sys/windows"
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package main
|
||||
package home
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
"syscall"
|
||||
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/kardianos/service"
|
||||
@@ -31,9 +32,10 @@ func (p *program) Start(s service.Service) error {
|
||||
// Stop stops the program
|
||||
func (p *program) Stop(s service.Service) error {
|
||||
// Stop should not block. Return with a few seconds.
|
||||
cleanup()
|
||||
cleanupAlways()
|
||||
os.Exit(0)
|
||||
if config.appSignalChannel == nil {
|
||||
os.Exit(0)
|
||||
}
|
||||
config.appSignalChannel <- syscall.SIGINT
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// +build !windows,!nacl,!plan9
|
||||
|
||||
package main
|
||||
package home
|
||||
|
||||
import (
|
||||
"log"
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package home
|
||||
|
||||
import (
|
||||
"log"
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package home
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package home
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
15
main.go
Normal file
15
main.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/AdguardTeam/AdGuardHome/home"
|
||||
)
|
||||
|
||||
// version will be set through ldflags, contains current version
|
||||
var version = "undefined"
|
||||
|
||||
// channel can be set via ldflags
|
||||
var channel = "release"
|
||||
|
||||
func main() {
|
||||
home.Main(version, channel)
|
||||
}
|
||||
@@ -2,7 +2,7 @@ swagger: '2.0'
|
||||
info:
|
||||
title: 'AdGuard Home'
|
||||
description: 'AdGuard Home REST API. Admin web interface is built on top of this REST API.'
|
||||
version: 0.96.0
|
||||
version: 0.97.0
|
||||
schemes:
|
||||
- http
|
||||
basePath: /control
|
||||
@@ -135,11 +135,19 @@ paths:
|
||||
"192.168.1.104:53535": "Couldn't communicate with DNS server"
|
||||
|
||||
/version.json:
|
||||
get:
|
||||
post:
|
||||
tags:
|
||||
- global
|
||||
operationId: getVersionJson
|
||||
summary: 'Gets information about the latest available version of AdGuard'
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- in: "body"
|
||||
name: "body"
|
||||
required: true
|
||||
schema:
|
||||
$ref: "#/definitions/GetVersionRequest"
|
||||
produces:
|
||||
- 'application/json'
|
||||
responses:
|
||||
@@ -994,6 +1002,13 @@ definitions:
|
||||
example:
|
||||
- '||example.org^'
|
||||
- '||example.com^'
|
||||
GetVersionRequest:
|
||||
type: "object"
|
||||
description: "/version.json request data"
|
||||
properties:
|
||||
recheck_now:
|
||||
description: "If false, server will check for a new version data only once in several hours"
|
||||
type: "boolean"
|
||||
VersionInfo:
|
||||
type: "object"
|
||||
description: "Information about the latest available version of AdGuard Home"
|
||||
|
||||
@@ -43,6 +43,7 @@ CHANNEL=$channel GOOS=windows GOARCH=amd64 f
|
||||
CHANNEL=$channel GOOS=windows GOARCH=386 f
|
||||
CHANNEL=$channel GOOS=linux GOARCH=mipsle GOMIPS=softfloat f
|
||||
CHANNEL=$channel GOOS=linux GOARCH=mips GOMIPS=softfloat f
|
||||
CHANNEL=$channel GOOS=freebsd GOARCH=amd64 f
|
||||
|
||||
# Variables for CI
|
||||
echo "version=$version" > $dst/version.txt
|
||||
@@ -61,5 +62,6 @@ echo " \"download_linux_arm\": \"$baseUrl/AdGuardHome_linux_arm.tar.gz\"," >> $
|
||||
echo " \"download_linux_arm64\": \"$baseUrl/AdGuardHome_linux_arm64.tar.gz\"," >> $dst/version.json
|
||||
echo " \"download_linux_mips\": \"$baseUrl/AdGuardHome_linux_mips.tar.gz\"," >> $dst/version.json
|
||||
echo " \"download_linux_mipsle\": \"$baseUrl/AdGuardHome_linux_mipsle.tar.gz\"," >> $dst/version.json
|
||||
echo " \"download_freebsd_amd64\": \"$baseUrl/AdGuardHome_freebsd_amd64.tar.gz\"," >> $dst/version.json
|
||||
echo " \"selfupdate_min_version\": \"v0.0\"" >> $dst/version.json
|
||||
echo "}" >> $dst/version.json
|
||||
|
||||
Reference in New Issue
Block a user