mirror of
https://github.com/AdguardTeam/AdGuardHome.git
synced 2025-10-26 11:27:18 +00:00
Pull request 2493: ADG-10852-rewrites-enabled
Squashed commit of the following: commit 8ce89d6dab8031dadac7698e71a489edfffe29f8 Merge:7b0052d69b76d10040Author: Stanislav Chzhen <s.chzhen@adguard.com> Date: Thu Oct 16 16:58:28 2025 +0300 Merge branch 'master' into ADG-10852-rewrites-enabled commit7b0052d695Author: Ainar Garipov <A.Garipov@AdGuard.COM> Date: Wed Oct 15 18:09:34 2025 +0300 client: fix i18n commit6ac47b30bbAuthor: Eugene Miroshkin <e.miroshkin@adguard.com> Date: Wed Oct 15 07:56:16 2025 +0300 fix eslint commit5e38412748Author: Eugene Miroshkin <e.miroshkin@adguard.com> Date: Mon Oct 13 18:20:08 2025 +0300 add notify commitb7018efe07Author: Eugene Miroshkin <e.miroshkin@adguard.com> Date: Mon Oct 13 18:06:35 2025 +0300 update ux commit89fa121be1Author: Eugene Miroshkin <e.miroshkin@adguard.com> Date: Mon Oct 13 15:39:49 2025 +0300 update ux for rewrites page commit2ed3a128f2Author: Eugene Miroshkin <e.miroshkin@adguard.com> Date: Fri Oct 10 16:11:06 2025 +0300 update frontend commitbb279f6b2eMerge:8ddc0a7af497441d59Author: Eugene Miroshkin <e.miroshkin@adguard.com> Date: Fri Oct 10 14:01:57 2025 +0300 merge commit8ddc0a7afbAuthor: Eugene Miroshkin <e.miroshkin@adguard.com> Date: Fri Oct 10 14:01:37 2025 +0300 add rewrites toggle commit497441d595Merge:50a76760d2f810068aAuthor: Stanislav Chzhen <s.chzhen@adguard.com> Date: Thu Oct 9 18:44:18 2025 +0300 Merge branch 'master' into ADG-10852-rewrites-enabled commit50a76760dbAuthor: Stanislav Chzhen <s.chzhen@adguard.com> Date: Thu Oct 9 18:25:52 2025 +0300 filtering: fix config write commitf1bf45aa42Author: Stanislav Chzhen <s.chzhen@adguard.com> Date: Fri Oct 3 14:21:53 2025 +0300 all: rewrites enabled
This commit is contained in:
parent
b76d100406
commit
83feced4c8
32
CHANGELOG.md
32
CHANGELOG.md
@ -24,8 +24,40 @@ NOTE: Add new changes BELOW THIS COMMENT.
|
||||
|
||||
### Added
|
||||
|
||||
- New DNS rewrite settings endpoints `GET /control/rewrite/settings` and `PUT /control/rewrite/settings/update`. See `openapi/openapi.yaml` for details.
|
||||
- New fields `"groups"` and `"group_id"` added to the HTTP API (`GET /control/blocked_services/all`). See `openapi/openapi.yaml` for the full description.
|
||||
|
||||
### Changed
|
||||
|
||||
- `POST /control/rewrite/add` and `PUT /control/rewrite/update` now accept the optional field "enabled". See `openapi/openapi.yaml` for details.
|
||||
|
||||
#### Configuration changes
|
||||
|
||||
In this release, the schema version has changed from 30 to 31.
|
||||
|
||||
- Added a new boolean field `filtering.rewrites_enabled` to globally enable/disable DNS rewrites.
|
||||
- Added a new boolean field `enabled` for each entry in `filtering.rewrites` to toggle individual rewrites.
|
||||
|
||||
```yaml
|
||||
# BEFORE:
|
||||
'filtering':
|
||||
'rewrites':
|
||||
- 'domain': test.example
|
||||
'answer': 192.0.2.0
|
||||
# …
|
||||
|
||||
# AFTER:
|
||||
'filtering':
|
||||
'rewrites_enabled': true
|
||||
'rewrites':
|
||||
- 'domain': test.example
|
||||
'answer': 192.0.2.0
|
||||
'enabled': true
|
||||
# …
|
||||
```
|
||||
|
||||
To roll back this change, set `schema_version` back to `30`.
|
||||
|
||||
[go-1.25.3]: https://groups.google.com/g/golang-announce/c/YEyj6FUNbik
|
||||
|
||||
<!--
|
||||
|
||||
@ -514,6 +514,7 @@
|
||||
"rewrite_added": "DNS rewrite for \"{{key}}\" successfully added",
|
||||
"rewrite_deleted": "DNS rewrite for \"{{key}}\" successfully deleted",
|
||||
"rewrite_updated": "DNS rewrite successfully updated",
|
||||
"rewrite_settings_updated": "DNS rewrite settings successfully updated",
|
||||
"rewrite_add": "Add DNS rewrite",
|
||||
"rewrite_edit": "Edit DNS rewrite",
|
||||
"rewrite_not_found": "No DNS rewrites found",
|
||||
@ -522,6 +523,10 @@
|
||||
"rewrite_applied": "Rewrite rule is applied",
|
||||
"rewrite_hosts_applied": "Rewritten by the hosts file rule",
|
||||
"dns_rewrites": "DNS rewrites",
|
||||
"rewrites_enabled_table_header": "Rewrites are enabled",
|
||||
"rewrites_disabled_table_header": "Rewrites are disabled",
|
||||
"enable_rewrites": "Enable rewrite rules",
|
||||
"disable_rewrites": "Disable rewrite rules",
|
||||
"form_domain": "Enter domain name or wildcard",
|
||||
"form_answer": "Enter IP address or domain name",
|
||||
"form_error_domain_format": "Invalid domain format",
|
||||
|
||||
@ -2,6 +2,7 @@ import { createAction } from 'redux-actions';
|
||||
import i18next from 'i18next';
|
||||
import apiClient from '../api/Api';
|
||||
import { addErrorToast, addSuccessToast } from './toasts';
|
||||
import type { RootState } from '../initialState';
|
||||
|
||||
export const toggleRewritesModal = createAction('TOGGLE_REWRITES_MODAL');
|
||||
|
||||
@ -47,12 +48,15 @@ export const updateRewriteSuccess = createAction('UPDATE_REWRITE_SUCCESS');
|
||||
* @param {string} config.target - current DNS rewrite value
|
||||
* @param {string} config.update - updated DNS rewrite value
|
||||
*/
|
||||
export const updateRewrite = (config: any) => async (dispatch: any) => {
|
||||
export const updateRewrite = (config: any) => async (dispatch: any, getState: () => RootState) => {
|
||||
dispatch(updateRewriteRequest());
|
||||
try {
|
||||
await apiClient.updateRewrite(config);
|
||||
dispatch(updateRewriteSuccess());
|
||||
dispatch(toggleRewritesModal());
|
||||
const state = getState();
|
||||
if (state?.rewrites?.isModalOpen) {
|
||||
dispatch(toggleRewritesModal());
|
||||
}
|
||||
dispatch(getRewritesList());
|
||||
dispatch(addSuccessToast(i18next.t('rewrite_updated', { key: config.domain })));
|
||||
} catch (error) {
|
||||
@ -77,3 +81,35 @@ export const deleteRewrite = (config: any) => async (dispatch: any) => {
|
||||
dispatch(deleteRewriteFailure());
|
||||
}
|
||||
};
|
||||
|
||||
export const getRewriteSettingsRequest = createAction('GET_REWRITE_SETTINGS_REQUEST');
|
||||
export const getRewriteSettingsFailure = createAction('GET_REWRITE_SETTINGS_FAILURE');
|
||||
export const getRewriteSettingsSuccess = createAction('GET_REWRITE_SETTINGS_SUCCESS');
|
||||
|
||||
export const getRewriteSettings = () => async (dispatch: any) => {
|
||||
dispatch(getRewriteSettingsRequest());
|
||||
try {
|
||||
const data = await apiClient.getRewriteSettings();
|
||||
dispatch(getRewriteSettingsSuccess(data));
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(getRewriteSettingsFailure());
|
||||
}
|
||||
};
|
||||
|
||||
export const updateRewriteSettingsRequest = createAction('UPDATE_REWRITE_SETTINGS_REQUEST');
|
||||
export const updateRewriteSettingsFailure = createAction('UPDATE_REWRITE_SETTINGS_FAILURE');
|
||||
export const updateRewriteSettingsSuccess = createAction('UPDATE_REWRITE_SETTINGS_SUCCESS');
|
||||
|
||||
export const updateRewriteSettings = (config: any) => async (dispatch: any) => {
|
||||
dispatch(updateRewriteSettingsRequest());
|
||||
try {
|
||||
await apiClient.updateRewriteSettings(config);
|
||||
dispatch(updateRewriteSettingsSuccess(config));
|
||||
dispatch(getRewriteSettings());
|
||||
dispatch(addSuccessToast(i18next.t('rewrite_settings_updated')));
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(updateRewriteSettingsFailure());
|
||||
}
|
||||
};
|
||||
|
||||
@ -489,6 +489,10 @@ class Api {
|
||||
|
||||
REWRITE_DELETE = { path: 'rewrite/delete', method: 'POST' };
|
||||
|
||||
REWRITE_SETTINGS = { path: 'rewrite/settings', method: 'GET' };
|
||||
|
||||
REWRITE_SETTINGS_UPDATE = { path: 'rewrite/settings/update', method: 'PUT' };
|
||||
|
||||
getRewritesList() {
|
||||
const { path, method } = this.REWRITES_LIST;
|
||||
|
||||
@ -511,6 +515,14 @@ class Api {
|
||||
return this.makeRequest(path, method, parameters);
|
||||
}
|
||||
|
||||
updateRewriteSettings(config: any) {
|
||||
const { path, method } = this.REWRITE_SETTINGS_UPDATE;
|
||||
const parameters = {
|
||||
data: config,
|
||||
};
|
||||
return this.makeRequest(path, method, parameters);
|
||||
}
|
||||
|
||||
deleteRewrite(config: any) {
|
||||
const { path, method } = this.REWRITE_DELETE;
|
||||
const parameters = {
|
||||
@ -519,6 +531,12 @@ class Api {
|
||||
return this.makeRequest(path, method, parameters);
|
||||
}
|
||||
|
||||
getRewriteSettings() {
|
||||
const { path, method } = this.REWRITE_SETTINGS;
|
||||
|
||||
return this.makeRequest(path, method);
|
||||
}
|
||||
|
||||
// Blocked services
|
||||
BLOCKED_SERVICES_GET = { path: 'blocked_services/get', method: 'GET' };
|
||||
|
||||
|
||||
@ -15,8 +15,10 @@ interface TableProps {
|
||||
processingAdd: boolean;
|
||||
processingDelete: boolean;
|
||||
processingUpdate: boolean;
|
||||
settings: Record<string, boolean>;
|
||||
handleDelete: (...args: unknown[]) => unknown;
|
||||
toggleRewritesModal: (...args: unknown[]) => unknown;
|
||||
toggleRewrite: (...args: unknown[]) => unknown;
|
||||
}
|
||||
|
||||
class Table extends Component<TableProps> {
|
||||
@ -43,24 +45,35 @@ class Table extends Component<TableProps> {
|
||||
{
|
||||
Header: this.props.t('actions_table_header'),
|
||||
accessor: 'actions',
|
||||
maxWidth: 100,
|
||||
maxWidth: 150,
|
||||
sortable: false,
|
||||
resizable: false,
|
||||
Cell: (value: any) => {
|
||||
const currentRewrite = {
|
||||
answer: value.row.answer,
|
||||
domain: value.row.domain,
|
||||
};
|
||||
Cell: (row: any) => {
|
||||
const { original } = row;
|
||||
const { processing, settings, toggleRewrite } = this.props;
|
||||
const isEnabledSettings = Boolean(settings && settings.enabled);
|
||||
|
||||
return (
|
||||
<div className="logs__row logs__row--center">
|
||||
<label className="checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="checkbox__input"
|
||||
onChange={() => toggleRewrite(original)}
|
||||
checked={original.enabled}
|
||||
disabled={processing || !isEnabledSettings}
|
||||
/>
|
||||
|
||||
<span className="checkbox__label checkbox__label--l" />
|
||||
</label>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-icon btn-outline-primary btn-sm mr-2"
|
||||
onClick={() => {
|
||||
this.props.toggleRewritesModal({
|
||||
type: MODAL_TYPE.EDIT_REWRITE,
|
||||
currentRewrite,
|
||||
original,
|
||||
});
|
||||
}}
|
||||
disabled={this.props.processingUpdate}
|
||||
@ -73,7 +86,7 @@ class Table extends Component<TableProps> {
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-icon btn-outline-secondary btn-sm"
|
||||
onClick={() => this.props.handleDelete(currentRewrite)}
|
||||
onClick={() => this.props.handleDelete(original)}
|
||||
title={this.props.t('delete_table_action')}>
|
||||
<svg className="icons">
|
||||
<use xlinkHref="#delete" />
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { Trans, withTranslation } from 'react-i18next';
|
||||
import cn from 'classnames';
|
||||
|
||||
import Table from './Table';
|
||||
|
||||
@ -18,12 +19,15 @@ interface RewritesProps {
|
||||
addRewrite: (...args: unknown[]) => unknown;
|
||||
deleteRewrite: (...args: unknown[]) => unknown;
|
||||
updateRewrite: (...args: unknown[]) => unknown;
|
||||
updateRewriteSettings: (...args: unknown[]) => unknown;
|
||||
getRewriteSettings: () => (dispatch: any) => void;
|
||||
rewrites: RewritesData;
|
||||
}
|
||||
|
||||
class Rewrites extends Component<RewritesProps> {
|
||||
componentDidMount() {
|
||||
this.props.getRewritesList();
|
||||
this.props.getRewriteSettings();
|
||||
}
|
||||
|
||||
handleDelete = (values: any) => {
|
||||
@ -46,6 +50,21 @@ class Rewrites extends Component<RewritesProps> {
|
||||
}
|
||||
};
|
||||
|
||||
toggleRewrite = (currentRewrite: any) => {
|
||||
const updatedRewrite = { ...currentRewrite, enabled: !currentRewrite.enabled };
|
||||
|
||||
this.props.updateRewrite({
|
||||
target: currentRewrite,
|
||||
update: updatedRewrite,
|
||||
});
|
||||
};
|
||||
|
||||
toggleRewriteSettings = () => {
|
||||
const { enabled } = this.props.rewrites.settings;
|
||||
|
||||
this.props.updateRewriteSettings({ enabled: !enabled });
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
t,
|
||||
@ -64,12 +83,19 @@ class Rewrites extends Component<RewritesProps> {
|
||||
processingUpdate,
|
||||
modalType,
|
||||
currentRewrite,
|
||||
settings
|
||||
} = rewrites;
|
||||
|
||||
const isEnabledSettings = settings.enabled;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<PageTitle title={t('dns_rewrites')} subtitle={t('rewrite_desc')} />
|
||||
|
||||
<div className={cn(isEnabledSettings ? 'text-success' : 'text-warning', 'mb-2')}>
|
||||
{isEnabledSettings ? this.props.t('rewrites_enabled_table_header') : this.props.t('rewrites_disabled_table_header')}
|
||||
</div>
|
||||
|
||||
<Card id="rewrites" bodyType="card-body box-body--settings">
|
||||
<Fragment>
|
||||
<Table
|
||||
@ -80,15 +106,27 @@ class Rewrites extends Component<RewritesProps> {
|
||||
processingUpdate={processingUpdate}
|
||||
handleDelete={this.handleDelete}
|
||||
toggleRewritesModal={toggleRewritesModal}
|
||||
toggleRewrite={this.toggleRewrite}
|
||||
settings={settings}
|
||||
/>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-success btn-standard mt-3"
|
||||
onClick={() => toggleRewritesModal({ type: MODAL_TYPE.ADD_REWRITE })}
|
||||
disabled={processingAdd}>
|
||||
<Trans>rewrite_add</Trans>
|
||||
</button>
|
||||
<div className="card-actions">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-success btn-standard mr-2"
|
||||
onClick={() => toggleRewritesModal({ type: MODAL_TYPE.ADD_REWRITE })}
|
||||
disabled={processingAdd}>
|
||||
<Trans>rewrite_add</Trans>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-primary btn-standard"
|
||||
onClick={() => this.toggleRewriteSettings()}
|
||||
disabled={processingUpdate}>
|
||||
<Trans>{isEnabledSettings ? 'disable_rewrites' : 'enable_rewrites'}</Trans>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<Modal
|
||||
isModalOpen={isModalOpen}
|
||||
|
||||
@ -370,6 +370,10 @@
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.logs__row--center {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.logs__row--icons {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
@ -58,6 +58,13 @@
|
||||
0.3s ease-in-out opacity;
|
||||
}
|
||||
|
||||
.checkbox__label--l {
|
||||
&:before {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.checkbox__label .checkbox__label-text {
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { getRewritesList, addRewrite, deleteRewrite, updateRewrite, toggleRewritesModal } from '../actions/rewrites';
|
||||
import { getRewritesList, addRewrite, deleteRewrite, updateRewrite, toggleRewritesModal, updateRewriteSettings, getRewriteSettings } from '../actions/rewrites';
|
||||
|
||||
import Rewrites from '../components/Filters/Rewrites';
|
||||
import { RootState } from '../initialState';
|
||||
@ -16,6 +16,8 @@ type DispatchProps = {
|
||||
addRewrite: (...args: unknown[]) => unknown;
|
||||
deleteRewrite: (...args: unknown[]) => unknown;
|
||||
updateRewrite: (...args: unknown[]) => unknown;
|
||||
updateRewriteSettings: (...args: unknown[]) => unknown;
|
||||
getRewriteSettings: () => (dispatch: any) => void;
|
||||
}
|
||||
|
||||
const mapDispatchToProps: DispatchProps = {
|
||||
@ -24,6 +26,8 @@ const mapDispatchToProps: DispatchProps = {
|
||||
deleteRewrite,
|
||||
updateRewrite,
|
||||
toggleRewritesModal,
|
||||
updateRewriteSettings,
|
||||
getRewriteSettings,
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Rewrites);
|
||||
|
||||
@ -172,11 +172,16 @@ export type RewritesData = {
|
||||
currentRewrite?: {
|
||||
answer: string;
|
||||
domain: string;
|
||||
enabled: boolean;
|
||||
};
|
||||
list: {
|
||||
answer: string;
|
||||
domain: string;
|
||||
enabled: boolean;
|
||||
}[];
|
||||
settings: {
|
||||
enabled: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
export type NormalizedTopClients = {
|
||||
@ -577,6 +582,7 @@ export const initialState: RootState = {
|
||||
isModalOpen: false,
|
||||
modalType: '',
|
||||
list: [],
|
||||
settings: { enabled: false },
|
||||
},
|
||||
services: {
|
||||
processing: true,
|
||||
|
||||
@ -66,6 +66,39 @@ const rewrites = handleActions(
|
||||
};
|
||||
return newState;
|
||||
},
|
||||
[actions.getRewriteSettingsRequest.toString()]: (state: any) => ({
|
||||
...state,
|
||||
processing: true,
|
||||
}),
|
||||
[actions.getRewriteSettingsFailure.toString()]: (state: any) => ({
|
||||
...state,
|
||||
processing: false,
|
||||
}),
|
||||
[actions.getRewriteSettingsSuccess.toString()]: (state: any, { payload }: any) => {
|
||||
const newState = {
|
||||
...state,
|
||||
settings: payload,
|
||||
processing: false,
|
||||
};
|
||||
return newState;
|
||||
},
|
||||
|
||||
[actions.updateRewriteSettingsRequest.toString()]: (state: any) => ({
|
||||
...state,
|
||||
processingUpdate: true,
|
||||
}),
|
||||
[actions.updateRewriteSettingsFailure.toString()]: (state: any) => ({
|
||||
...state,
|
||||
processingUpdate: false,
|
||||
}),
|
||||
[actions.updateRewriteSettingsSuccess.toString()]: (state: any, { payload }: any) => ({
|
||||
...state,
|
||||
settings: {
|
||||
...state.settings,
|
||||
...payload,
|
||||
},
|
||||
processingUpdate: false,
|
||||
}),
|
||||
|
||||
[actions.toggleRewritesModal.toString()]: (state: any, { payload }: any) => {
|
||||
if (payload) {
|
||||
@ -94,6 +127,7 @@ const rewrites = handleActions(
|
||||
modalType: '',
|
||||
currentRewrite: {},
|
||||
list: [],
|
||||
settings: { enabled: false },
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@ -2,4 +2,4 @@
|
||||
package configmigrate
|
||||
|
||||
// LastSchemaVersion is the most recent schema version.
|
||||
const LastSchemaVersion uint = 30
|
||||
const LastSchemaVersion uint = 31
|
||||
|
||||
@ -141,6 +141,7 @@ func (m *Migrator) upgradeConfigSchema(
|
||||
27: m.migrateTo28,
|
||||
28: m.migrateTo29,
|
||||
29: m.migrateTo30,
|
||||
30: m.migrateTo31,
|
||||
}
|
||||
|
||||
for i, migrate := range upgrades[current:target] {
|
||||
|
||||
@ -199,6 +199,10 @@ func TestMigrateConfig_Migrate(t *testing.T) {
|
||||
yamlEqFunc: require.YAMLEq,
|
||||
name: "v30",
|
||||
targetVersion: 30,
|
||||
}, {
|
||||
yamlEqFunc: require.YAMLEq,
|
||||
name: "v31",
|
||||
targetVersion: 31,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
|
||||
122
internal/configmigrate/testdata/TestMigrateConfig_Migrate/v31/input.yml
vendored
Normal file
122
internal/configmigrate/testdata/TestMigrateConfig_Migrate/v31/input.yml
vendored
Normal file
@ -0,0 +1,122 @@
|
||||
http:
|
||||
address: 127.0.0.1:3000
|
||||
session_ttl: 3h
|
||||
pprof:
|
||||
enabled: true
|
||||
port: 6060
|
||||
users:
|
||||
- name: testuser
|
||||
password: testpassword
|
||||
dns:
|
||||
bind_hosts:
|
||||
- 127.0.0.1
|
||||
port: 53
|
||||
parental_sensitivity: 0
|
||||
upstream_dns:
|
||||
- tls://1.1.1.1
|
||||
- tls://1.0.0.1
|
||||
- quic://8.8.8.8:784
|
||||
bootstrap_dns:
|
||||
- 8.8.8.8:53
|
||||
cache_size: 4194304
|
||||
edns_client_subnet:
|
||||
enabled: true
|
||||
use_custom: false
|
||||
custom_ip: ""
|
||||
filtering:
|
||||
filtering_enabled: true
|
||||
parental_enabled: false
|
||||
safebrowsing_enabled: false
|
||||
rewrites:
|
||||
- domain: test.example
|
||||
answer: 192.0.2.0
|
||||
safe_fs_patterns: []
|
||||
safe_search:
|
||||
enabled: false
|
||||
bing: true
|
||||
duckduckgo: true
|
||||
google: true
|
||||
pixabay: true
|
||||
yandex: true
|
||||
youtube: true
|
||||
protection_enabled: true
|
||||
blocked_services:
|
||||
schedule:
|
||||
time_zone: Local
|
||||
ids:
|
||||
- 500px
|
||||
blocked_response_ttl: 10
|
||||
filters:
|
||||
- url: https://adaway.org/hosts.txt
|
||||
name: AdAway
|
||||
enabled: false
|
||||
- url: FILEPATH
|
||||
name: Local Filter
|
||||
enabled: false
|
||||
clients:
|
||||
persistent:
|
||||
- name: localhost
|
||||
ids:
|
||||
- 127.0.0.1
|
||||
- aa:aa:aa:aa:aa:aa
|
||||
use_global_settings: true
|
||||
use_global_blocked_services: true
|
||||
filtering_enabled: false
|
||||
parental_enabled: false
|
||||
safebrowsing_enabled: false
|
||||
safe_search:
|
||||
enabled: true
|
||||
bing: true
|
||||
duckduckgo: true
|
||||
google: true
|
||||
pixabay: true
|
||||
yandex: true
|
||||
youtube: true
|
||||
blocked_services:
|
||||
schedule:
|
||||
time_zone: Local
|
||||
ids:
|
||||
- 500px
|
||||
runtime_sources:
|
||||
whois: true
|
||||
arp: true
|
||||
rdns: true
|
||||
dhcp: true
|
||||
hosts: true
|
||||
dhcp:
|
||||
enabled: false
|
||||
interface_name: vboxnet0
|
||||
local_domain_name: local
|
||||
dhcpv4:
|
||||
gateway_ip: 192.168.0.1
|
||||
subnet_mask: 255.255.255.0
|
||||
range_start: 192.168.0.10
|
||||
range_end: 192.168.0.250
|
||||
lease_duration: 1234
|
||||
icmp_timeout_msec: 10
|
||||
schema_version: 29
|
||||
user_rules: []
|
||||
querylog:
|
||||
enabled: true
|
||||
file_enabled: true
|
||||
interval: 720h
|
||||
size_memory: 1000
|
||||
ignored:
|
||||
- '|.^'
|
||||
statistics:
|
||||
enabled: true
|
||||
interval: 240h
|
||||
ignored:
|
||||
- '|.^'
|
||||
os:
|
||||
group: ''
|
||||
rlimit_nofile: 123
|
||||
user: ''
|
||||
log:
|
||||
file: ""
|
||||
max_backups: 0
|
||||
max_size: 100
|
||||
max_age: 3
|
||||
compress: true
|
||||
local_time: false
|
||||
verbose: true
|
||||
124
internal/configmigrate/testdata/TestMigrateConfig_Migrate/v31/output.yml
vendored
Normal file
124
internal/configmigrate/testdata/TestMigrateConfig_Migrate/v31/output.yml
vendored
Normal file
@ -0,0 +1,124 @@
|
||||
http:
|
||||
address: 127.0.0.1:3000
|
||||
session_ttl: 3h
|
||||
pprof:
|
||||
enabled: true
|
||||
port: 6060
|
||||
users:
|
||||
- name: testuser
|
||||
password: testpassword
|
||||
dns:
|
||||
bind_hosts:
|
||||
- 127.0.0.1
|
||||
port: 53
|
||||
parental_sensitivity: 0
|
||||
upstream_dns:
|
||||
- tls://1.1.1.1
|
||||
- tls://1.0.0.1
|
||||
- quic://8.8.8.8:784
|
||||
bootstrap_dns:
|
||||
- 8.8.8.8:53
|
||||
cache_enabled: true
|
||||
cache_size: 4194304
|
||||
edns_client_subnet:
|
||||
enabled: true
|
||||
use_custom: false
|
||||
custom_ip: ""
|
||||
filtering:
|
||||
filtering_enabled: true
|
||||
parental_enabled: false
|
||||
safebrowsing_enabled: false
|
||||
rewrites:
|
||||
- domain: test.example
|
||||
answer: 192.0.2.0
|
||||
enabled: true
|
||||
safe_fs_patterns: []
|
||||
safe_search:
|
||||
enabled: false
|
||||
bing: true
|
||||
duckduckgo: true
|
||||
google: true
|
||||
pixabay: true
|
||||
yandex: true
|
||||
youtube: true
|
||||
protection_enabled: true
|
||||
blocked_services:
|
||||
schedule:
|
||||
time_zone: Local
|
||||
ids:
|
||||
- 500px
|
||||
blocked_response_ttl: 10
|
||||
filters:
|
||||
- url: https://adaway.org/hosts.txt
|
||||
name: AdAway
|
||||
enabled: false
|
||||
- url: FILEPATH
|
||||
name: Local Filter
|
||||
enabled: false
|
||||
clients:
|
||||
persistent:
|
||||
- name: localhost
|
||||
ids:
|
||||
- 127.0.0.1
|
||||
- aa:aa:aa:aa:aa:aa
|
||||
use_global_settings: true
|
||||
use_global_blocked_services: true
|
||||
filtering_enabled: false
|
||||
parental_enabled: false
|
||||
safebrowsing_enabled: false
|
||||
safe_search:
|
||||
enabled: true
|
||||
bing: true
|
||||
duckduckgo: true
|
||||
google: true
|
||||
pixabay: true
|
||||
yandex: true
|
||||
youtube: true
|
||||
blocked_services:
|
||||
schedule:
|
||||
time_zone: Local
|
||||
ids:
|
||||
- 500px
|
||||
runtime_sources:
|
||||
whois: true
|
||||
arp: true
|
||||
rdns: true
|
||||
dhcp: true
|
||||
hosts: true
|
||||
dhcp:
|
||||
enabled: false
|
||||
interface_name: vboxnet0
|
||||
local_domain_name: local
|
||||
dhcpv4:
|
||||
gateway_ip: 192.168.0.1
|
||||
subnet_mask: 255.255.255.0
|
||||
range_start: 192.168.0.10
|
||||
range_end: 192.168.0.250
|
||||
lease_duration: 1234
|
||||
icmp_timeout_msec: 10
|
||||
schema_version: 31
|
||||
user_rules: []
|
||||
querylog:
|
||||
enabled: true
|
||||
file_enabled: true
|
||||
interval: 720h
|
||||
size_memory: 1000
|
||||
ignored:
|
||||
- '|.^'
|
||||
statistics:
|
||||
enabled: true
|
||||
interval: 240h
|
||||
ignored:
|
||||
- '|.^'
|
||||
os:
|
||||
group: ''
|
||||
rlimit_nofile: 123
|
||||
user: ''
|
||||
log:
|
||||
file: ""
|
||||
max_backups: 0
|
||||
max_size: 100
|
||||
max_age: 3
|
||||
compress: true
|
||||
local_time: false
|
||||
verbose: true
|
||||
43
internal/configmigrate/v31.go
Normal file
43
internal/configmigrate/v31.go
Normal file
@ -0,0 +1,43 @@
|
||||
package configmigrate
|
||||
|
||||
import "context"
|
||||
|
||||
// migrateTo31 performs the following changes:
|
||||
//
|
||||
// # BEFORE:
|
||||
// 'filtering':
|
||||
// 'rewrites':
|
||||
// - 'domain': test.example
|
||||
// 'answer': 192.0.2.0
|
||||
// # …
|
||||
// # …
|
||||
//
|
||||
// # AFTER:
|
||||
// 'filtering':
|
||||
// 'rewrites':
|
||||
// - 'domain': test.example
|
||||
// 'answer': 192.0.2.0
|
||||
// 'enabled': true
|
||||
// # …
|
||||
// # …
|
||||
func (m *Migrator) migrateTo31(_ context.Context, diskConf yobj) (err error) {
|
||||
diskConf["schema_version"] = 31
|
||||
|
||||
fltConf, ok, err := fieldVal[yobj](diskConf, "filtering")
|
||||
if !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
rewrites, ok, err := fieldVal[yarr](fltConf, "rewrites")
|
||||
if !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := range rewrites {
|
||||
if r, isYobj := rewrites[i].(yobj); isYobj {
|
||||
r["enabled"] = true
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -1294,18 +1294,22 @@ func TestRewrite(t *testing.T) {
|
||||
BlockedServices: emptyFilteringBlockedServices(),
|
||||
BlockingMode: filtering.BlockingModeDefault,
|
||||
Rewrites: []*filtering.LegacyRewrite{{
|
||||
Domain: "test.com",
|
||||
Answer: "1.2.3.4",
|
||||
Type: dns.TypeA,
|
||||
Domain: "test.com",
|
||||
Answer: "1.2.3.4",
|
||||
Type: dns.TypeA,
|
||||
Enabled: true,
|
||||
}, {
|
||||
Domain: "alias.test.com",
|
||||
Answer: "test.com",
|
||||
Type: dns.TypeCNAME,
|
||||
Domain: "alias.test.com",
|
||||
Answer: "test.com",
|
||||
Type: dns.TypeCNAME,
|
||||
Enabled: true,
|
||||
}, {
|
||||
Domain: "my.alias.example.org",
|
||||
Answer: "example.org",
|
||||
Type: dns.TypeCNAME,
|
||||
Domain: "my.alias.example.org",
|
||||
Answer: "example.org",
|
||||
Type: dns.TypeCNAME,
|
||||
Enabled: true,
|
||||
}},
|
||||
RewritesEnabled: true,
|
||||
}
|
||||
f, err := filtering.New(c, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
@ -138,6 +138,7 @@ type Config struct {
|
||||
// to DNS requests blocked by safe-browsing.
|
||||
SafeBrowsingBlockHost string `yaml:"safebrowsing_block_host"`
|
||||
|
||||
// Rewrites is a list of legacy DNS rewrite records.
|
||||
Rewrites []*LegacyRewrite `yaml:"rewrites"`
|
||||
|
||||
// Filters are the blocking filter lists.
|
||||
@ -177,6 +178,9 @@ type Config struct {
|
||||
// FilteringEnabled indicates whether or not use filter lists.
|
||||
FilteringEnabled bool `yaml:"filtering_enabled"`
|
||||
|
||||
// RewritesEnabled indicates whether legacy rewrites are applied.
|
||||
RewritesEnabled bool `yaml:"rewrites_enabled"`
|
||||
|
||||
ParentalEnabled bool `yaml:"parental_enabled"`
|
||||
SafeBrowsingEnabled bool `yaml:"safebrowsing_enabled"`
|
||||
|
||||
@ -542,6 +546,10 @@ func (d *DNSFilter) processRewrites(host string, qtype uint16) (res Result) {
|
||||
|
||||
ctx := context.TODO()
|
||||
|
||||
if !d.conf.RewritesEnabled {
|
||||
return Result{}
|
||||
}
|
||||
|
||||
rewrites, matched := findRewrites(d.conf.Rewrites, host, qtype)
|
||||
if !matched {
|
||||
return Result{}
|
||||
@ -549,8 +557,22 @@ func (d *DNSFilter) processRewrites(host string, qtype uint16) (res Result) {
|
||||
|
||||
res.Reason = Rewritten
|
||||
|
||||
return d.handleRewriteLoop(ctx, host, qtype, rewrites, matched, &res)
|
||||
}
|
||||
|
||||
// handleRewriteLoop performs filtering rewrite processing based on the legacy
|
||||
// rewrite records. res must not be nil.
|
||||
func (d *DNSFilter) handleRewriteLoop(
|
||||
ctx context.Context,
|
||||
host string,
|
||||
qtype uint16,
|
||||
rewrites []*LegacyRewrite,
|
||||
matched bool,
|
||||
res *Result,
|
||||
) (resResult Result) {
|
||||
cnames := container.NewMapSet[string]()
|
||||
origHost := host
|
||||
|
||||
for matched && len(rewrites) > 0 && rewrites[0].Type == dns.TypeCNAME {
|
||||
rw := rewrites[0]
|
||||
rwPat := rw.Domain
|
||||
@ -577,7 +599,7 @@ func (d *DNSFilter) processRewrites(host string, qtype uint16) (res Result) {
|
||||
if cnames.Has(host) {
|
||||
d.logger.InfoContext(ctx, "cname loop", "host", host, "original", origHost)
|
||||
|
||||
return res
|
||||
return *res
|
||||
}
|
||||
|
||||
cnames.Add(host)
|
||||
@ -585,9 +607,9 @@ func (d *DNSFilter) processRewrites(host string, qtype uint16) (res Result) {
|
||||
rewrites, matched = findRewrites(d.conf.Rewrites, host, qtype)
|
||||
}
|
||||
|
||||
d.setRewriteResult(ctx, &res, host, rewrites, qtype)
|
||||
d.setRewriteResult(ctx, res, host, rewrites, qtype)
|
||||
|
||||
return res
|
||||
return *res
|
||||
}
|
||||
|
||||
// matchBlockedServicesRules checks the host against the blocked services rules
|
||||
|
||||
@ -49,7 +49,8 @@ func newForTest(t testing.TB, c *Config, filters []Filter) (f *DNSFilter, setts
|
||||
} else {
|
||||
// It must not be nil.
|
||||
c = &Config{
|
||||
Logger: testLogger,
|
||||
Logger: testLogger,
|
||||
RewritesEnabled: true,
|
||||
}
|
||||
}
|
||||
f, err := New(c, filters)
|
||||
|
||||
@ -730,9 +730,11 @@ func (d *DNSFilter) RegisterFilteringHandlers() {
|
||||
registerHTTP(http.MethodPut, "/control/safesearch/settings", d.handleSafeSearchSettings)
|
||||
|
||||
registerHTTP(http.MethodGet, "/control/rewrite/list", d.handleRewriteList)
|
||||
registerHTTP(http.MethodGet, "/control/rewrite/settings", d.handleRewriteSettings)
|
||||
registerHTTP(http.MethodPost, "/control/rewrite/add", d.handleRewriteAdd)
|
||||
registerHTTP(http.MethodPut, "/control/rewrite/update", d.handleRewriteUpdate)
|
||||
registerHTTP(http.MethodPost, "/control/rewrite/delete", d.handleRewriteDelete)
|
||||
registerHTTP(http.MethodPut, "/control/rewrite/settings/update", d.handleRewriteSettingsUpdate)
|
||||
registerHTTP(http.MethodPut, "/control/rewrite/update", d.handleRewriteUpdate)
|
||||
|
||||
registerHTTP(http.MethodGet, "/control/blocked_services/services", d.handleBlockedServicesIDs)
|
||||
registerHTTP(http.MethodGet, "/control/blocked_services/all", d.handleBlockedServicesAll)
|
||||
|
||||
@ -9,6 +9,8 @@ import (
|
||||
)
|
||||
|
||||
// Item is a single DNS rewrite record.
|
||||
//
|
||||
// TODO(s.chzhen): Add "Enabled" property.
|
||||
type Item struct {
|
||||
// Domain is the domain pattern for which this rewrite should work.
|
||||
Domain string `yaml:"domain"`
|
||||
|
||||
@ -5,13 +5,26 @@ import (
|
||||
"net/http"
|
||||
"slices"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
||||
)
|
||||
|
||||
// rewriteEntryJSON is a single entry of the DNS rewrite.
|
||||
//
|
||||
// TODO(d.kolyshev): Use [rewrite.Item] instead.
|
||||
type rewriteEntryJSON struct {
|
||||
Domain string `json:"domain"`
|
||||
Answer string `json:"answer"`
|
||||
Domain string `json:"domain"`
|
||||
Answer string `json:"answer"`
|
||||
Enabled aghalg.NullBool `json:"enabled"`
|
||||
}
|
||||
|
||||
// rewriteSettings contains DNS rewrite settings.
|
||||
type rewriteSettings struct {
|
||||
// Enabled indicates whether legacy rewrites are applied.
|
||||
//
|
||||
// TODO(s.chzhen): Consider using [aghalg.NullBool] so "{}" won't
|
||||
// accidentally disable rewrites on decode.
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
// handleRewriteList is the handler for the GET /control/rewrite/list HTTP API.
|
||||
@ -24,8 +37,9 @@ func (d *DNSFilter) handleRewriteList(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
for _, ent := range d.conf.Rewrites {
|
||||
jsonEnt := rewriteEntryJSON{
|
||||
Domain: ent.Domain,
|
||||
Answer: ent.Answer,
|
||||
Domain: ent.Domain,
|
||||
Answer: ent.Answer,
|
||||
Enabled: aghalg.BoolToNullBool(ent.Enabled),
|
||||
}
|
||||
arr = append(arr, &jsonEnt)
|
||||
}
|
||||
@ -47,9 +61,15 @@ func (d *DNSFilter) handleRewriteAdd(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
enabled := true
|
||||
if rwJSON.Enabled != aghalg.NBNull {
|
||||
enabled = rwJSON.Enabled == aghalg.NBTrue
|
||||
}
|
||||
|
||||
rw := &LegacyRewrite{
|
||||
Domain: rwJSON.Domain,
|
||||
Answer: rwJSON.Answer,
|
||||
Domain: rwJSON.Domain,
|
||||
Answer: rwJSON.Answer,
|
||||
Enabled: enabled,
|
||||
}
|
||||
|
||||
err = rw.normalize(ctx, l)
|
||||
@ -177,8 +197,52 @@ func (d *DNSFilter) handleRewriteUpdate(w http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
rwDel.Enabled = d.conf.Rewrites[index].Enabled
|
||||
if updateJSON.Update.Enabled == aghalg.NBNull {
|
||||
rwAdd.Enabled = rwDel.Enabled
|
||||
} else {
|
||||
rwAdd.Enabled = updateJSON.Update.Enabled == aghalg.NBTrue
|
||||
}
|
||||
|
||||
d.conf.Rewrites = slices.Replace(d.conf.Rewrites, index, index+1, rwAdd)
|
||||
|
||||
l.DebugContext(ctx, "removed rewrite element", "domain", rwDel.Domain, "answer", rwDel.Answer)
|
||||
l.DebugContext(ctx, "added rewrite element", "domain", rwAdd.Domain, "answer", rwAdd.Answer)
|
||||
l.DebugContext(
|
||||
ctx,
|
||||
"removed rewrite element",
|
||||
"domain", rwDel.Domain,
|
||||
"answer", rwDel.Answer,
|
||||
"enabled", rwDel.Enabled,
|
||||
)
|
||||
l.DebugContext(
|
||||
ctx,
|
||||
"added rewrite element",
|
||||
"domain", rwAdd.Domain,
|
||||
"answer", rwAdd.Answer,
|
||||
"enabled", rwAdd.Enabled,
|
||||
)
|
||||
}
|
||||
|
||||
// handleRewriteSettings is the handler for the GET /control/rewrite/settings
|
||||
// HTTP API.
|
||||
func (d *DNSFilter) handleRewriteSettings(w http.ResponseWriter, r *http.Request) {
|
||||
resp := &rewriteSettings{
|
||||
Enabled: protectedBool(d.confMu, &d.conf.RewritesEnabled),
|
||||
}
|
||||
|
||||
aghhttp.WriteJSONResponseOK(w, r, resp)
|
||||
}
|
||||
|
||||
// handleRewriteSettingsUpdate is the handler for the PUT
|
||||
// /control/rewrite/settings/update HTTP API.
|
||||
func (d *DNSFilter) handleRewriteSettingsUpdate(w http.ResponseWriter, r *http.Request) {
|
||||
req := &rewriteSettings{}
|
||||
err := json.NewDecoder(r.Body).Decode(req)
|
||||
if err != nil {
|
||||
aghhttp.Error(r, w, http.StatusBadRequest, "json.Decode: %s", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
setProtectedBool(d.confMu, &d.conf.RewritesEnabled, req.Enabled)
|
||||
d.conf.ConfModifier.Apply(r.Context())
|
||||
}
|
||||
|
||||
@ -4,11 +4,13 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
@ -18,8 +20,18 @@ import (
|
||||
|
||||
// TODO(d.kolyshev): Use [rewrite.Item] instead.
|
||||
type rewriteJSON struct {
|
||||
Domain string `json:"domain"`
|
||||
Answer string `json:"answer"`
|
||||
Domain string `json:"domain"`
|
||||
Answer string `json:"answer"`
|
||||
Enabled aghalg.NullBool `json:"enabled"`
|
||||
}
|
||||
|
||||
// newRewriteJSON returns a freshly initialized *rewriteJSON.
|
||||
func newRewriteJSON(domain, answer string, enabled aghalg.NullBool) (rw *rewriteJSON) {
|
||||
return &rewriteJSON{
|
||||
Domain: domain,
|
||||
Answer: answer,
|
||||
Enabled: enabled,
|
||||
}
|
||||
}
|
||||
|
||||
type rewriteUpdateJSON struct {
|
||||
@ -33,16 +45,33 @@ const (
|
||||
deleteURL = "/control/rewrite/delete"
|
||||
updateURL = "/control/rewrite/update"
|
||||
|
||||
decodeErrorMsg = "json.Decode: json: cannot unmarshal string into Go value of type" +
|
||||
" filtering.rewriteEntryJSON\n"
|
||||
decodeMsg = "json.Decode: json: cannot unmarshal string into Go value of type"
|
||||
decodeErrorMsg = decodeMsg + " filtering.rewriteEntryJSON\n"
|
||||
decodeUpdateErrorMsg = decodeMsg + " filtering.rewriteUpdateJSON\n"
|
||||
)
|
||||
|
||||
func TestDNSFilter_handleRewriteHTTP(t *testing.T) {
|
||||
confModCh := make(chan struct{})
|
||||
reqCh := make(chan struct{})
|
||||
func TestDNSFilter_HandleRewriteHTTP(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const (
|
||||
exampleDomain = "example.local"
|
||||
exampleAnswer = "example.rewrite"
|
||||
oneDomain = "one.local"
|
||||
oneAnswer = "one.rewrite"
|
||||
disabledDomain = "disabled.local"
|
||||
disabledAnswer = "disabled.rewrite"
|
||||
addDomain = "add.local"
|
||||
addAnswer = "add.rewrite"
|
||||
updDomain = "upd.local"
|
||||
updAnswer = "upd.rewrite"
|
||||
invDomain = "inv.local"
|
||||
invAnswer = "inv.rewrite"
|
||||
)
|
||||
|
||||
testRewrites := []*rewriteJSON{
|
||||
{Domain: "example.local", Answer: "example.rewrite"},
|
||||
{Domain: "one.local", Answer: "one.rewrite"},
|
||||
newRewriteJSON(exampleDomain, exampleAnswer, aghalg.NBTrue),
|
||||
newRewriteJSON(oneDomain, oneAnswer, aghalg.NBTrue),
|
||||
newRewriteJSON(disabledDomain, disabledAnswer, aghalg.NBFalse),
|
||||
}
|
||||
|
||||
testRewritesJSON, mErr := json.Marshal(testRewrites)
|
||||
@ -67,16 +96,48 @@ func TestDNSFilter_handleRewriteHTTP(t *testing.T) {
|
||||
wantBody: string(testRewritesJSON) + "\n",
|
||||
wantList: testRewrites,
|
||||
}, {
|
||||
name: "add",
|
||||
name: "add_enabled_null",
|
||||
url: addURL,
|
||||
method: http.MethodPost,
|
||||
reqData: rewriteJSON{Domain: "add.local", Answer: "add.rewrite"},
|
||||
reqData: rewriteJSON{Domain: addDomain, Answer: addAnswer},
|
||||
wantConfMod: true,
|
||||
wantStatus: http.StatusOK,
|
||||
wantBody: "",
|
||||
wantList: append(
|
||||
testRewrites,
|
||||
&rewriteJSON{Domain: "add.local", Answer: "add.rewrite"},
|
||||
newRewriteJSON(addDomain, addAnswer, aghalg.NBTrue),
|
||||
),
|
||||
}, {
|
||||
name: "add_enabled_false",
|
||||
url: addURL,
|
||||
method: http.MethodPost,
|
||||
reqData: rewriteJSON{
|
||||
Domain: addDomain,
|
||||
Answer: addAnswer,
|
||||
Enabled: aghalg.NBFalse,
|
||||
},
|
||||
wantConfMod: true,
|
||||
wantStatus: http.StatusOK,
|
||||
wantBody: "",
|
||||
wantList: append(
|
||||
testRewrites,
|
||||
newRewriteJSON(addDomain, addAnswer, aghalg.NBFalse),
|
||||
),
|
||||
}, {
|
||||
name: "add_enabled_true",
|
||||
url: addURL,
|
||||
method: http.MethodPost,
|
||||
reqData: rewriteJSON{
|
||||
Domain: addDomain,
|
||||
Answer: addAnswer,
|
||||
Enabled: aghalg.NBTrue,
|
||||
},
|
||||
wantConfMod: true,
|
||||
wantStatus: http.StatusOK,
|
||||
wantBody: "",
|
||||
wantList: append(
|
||||
testRewrites,
|
||||
newRewriteJSON(addDomain, addAnswer, aghalg.NBTrue),
|
||||
),
|
||||
}, {
|
||||
name: "add_error",
|
||||
@ -91,11 +152,14 @@ func TestDNSFilter_handleRewriteHTTP(t *testing.T) {
|
||||
name: "delete",
|
||||
url: deleteURL,
|
||||
method: http.MethodPost,
|
||||
reqData: rewriteJSON{Domain: "one.local", Answer: "one.rewrite"},
|
||||
reqData: rewriteJSON{Domain: oneDomain, Answer: oneAnswer},
|
||||
wantConfMod: true,
|
||||
wantStatus: http.StatusOK,
|
||||
wantBody: "",
|
||||
wantList: []*rewriteJSON{{Domain: "example.local", Answer: "example.rewrite"}},
|
||||
wantList: []*rewriteJSON{
|
||||
newRewriteJSON(exampleDomain, exampleAnswer, aghalg.NBTrue),
|
||||
newRewriteJSON(disabledDomain, disabledAnswer, aghalg.NBFalse),
|
||||
},
|
||||
}, {
|
||||
name: "delete_error",
|
||||
url: deleteURL,
|
||||
@ -106,19 +170,56 @@ func TestDNSFilter_handleRewriteHTTP(t *testing.T) {
|
||||
wantBody: decodeErrorMsg,
|
||||
wantList: testRewrites,
|
||||
}, {
|
||||
name: "update",
|
||||
name: "update_enabled_null",
|
||||
url: updateURL,
|
||||
method: http.MethodPut,
|
||||
reqData: rewriteUpdateJSON{
|
||||
Target: rewriteJSON{Domain: "one.local", Answer: "one.rewrite"},
|
||||
Update: rewriteJSON{Domain: "upd.local", Answer: "upd.rewrite"},
|
||||
Target: rewriteJSON{Domain: oneDomain, Answer: oneAnswer},
|
||||
Update: rewriteJSON{Domain: updDomain, Answer: updAnswer},
|
||||
},
|
||||
wantConfMod: true,
|
||||
wantStatus: http.StatusOK,
|
||||
wantBody: "",
|
||||
wantList: []*rewriteJSON{
|
||||
{Domain: "example.local", Answer: "example.rewrite"},
|
||||
{Domain: "upd.local", Answer: "upd.rewrite"},
|
||||
newRewriteJSON(exampleDomain, exampleAnswer, aghalg.NBTrue),
|
||||
newRewriteJSON(updDomain, updAnswer, aghalg.NBTrue),
|
||||
newRewriteJSON(disabledDomain, disabledAnswer, aghalg.NBFalse),
|
||||
},
|
||||
}, {
|
||||
name: "update_enabled_false",
|
||||
url: updateURL,
|
||||
method: http.MethodPut,
|
||||
reqData: rewriteUpdateJSON{
|
||||
Target: rewriteJSON{Domain: oneDomain, Answer: oneAnswer},
|
||||
Update: rewriteJSON{
|
||||
Domain: updDomain,
|
||||
Answer: updAnswer,
|
||||
Enabled: aghalg.NBFalse,
|
||||
},
|
||||
},
|
||||
wantConfMod: true,
|
||||
wantStatus: http.StatusOK,
|
||||
wantBody: "",
|
||||
wantList: []*rewriteJSON{
|
||||
newRewriteJSON(exampleDomain, exampleAnswer, aghalg.NBTrue),
|
||||
newRewriteJSON(updDomain, updAnswer, aghalg.NBFalse),
|
||||
newRewriteJSON(disabledDomain, disabledAnswer, aghalg.NBFalse),
|
||||
},
|
||||
}, {
|
||||
name: "update_enabled_true",
|
||||
url: updateURL,
|
||||
method: http.MethodPut,
|
||||
reqData: rewriteUpdateJSON{
|
||||
Target: rewriteJSON{Domain: oneDomain, Answer: oneAnswer},
|
||||
Update: rewriteJSON{Domain: updDomain, Answer: updAnswer, Enabled: aghalg.NBTrue},
|
||||
},
|
||||
wantConfMod: true,
|
||||
wantStatus: http.StatusOK,
|
||||
wantBody: "",
|
||||
wantList: []*rewriteJSON{
|
||||
newRewriteJSON(exampleDomain, exampleAnswer, aghalg.NBTrue),
|
||||
newRewriteJSON(updDomain, updAnswer, aghalg.NBTrue),
|
||||
newRewriteJSON(disabledDomain, disabledAnswer, aghalg.NBFalse),
|
||||
},
|
||||
}, {
|
||||
name: "update_error",
|
||||
@ -127,16 +228,15 @@ func TestDNSFilter_handleRewriteHTTP(t *testing.T) {
|
||||
reqData: "invalid_json",
|
||||
wantConfMod: false,
|
||||
wantStatus: http.StatusBadRequest,
|
||||
wantBody: "json.Decode: json: cannot unmarshal string into Go value of type" +
|
||||
" filtering.rewriteUpdateJSON\n",
|
||||
wantList: testRewrites,
|
||||
wantBody: decodeUpdateErrorMsg,
|
||||
wantList: testRewrites,
|
||||
}, {
|
||||
name: "update_error_target",
|
||||
url: updateURL,
|
||||
method: http.MethodPut,
|
||||
reqData: rewriteUpdateJSON{
|
||||
Target: rewriteJSON{Domain: "inv.local", Answer: "inv.rewrite"},
|
||||
Update: rewriteJSON{Domain: "upd.local", Answer: "upd.rewrite"},
|
||||
Target: rewriteJSON{Domain: invDomain, Answer: invAnswer},
|
||||
Update: rewriteJSON{Domain: updDomain, Answer: updAnswer},
|
||||
},
|
||||
wantConfMod: false,
|
||||
wantStatus: http.StatusBadRequest,
|
||||
@ -146,6 +246,11 @@ func TestDNSFilter_handleRewriteHTTP(t *testing.T) {
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
confModCh := make(chan struct{})
|
||||
reqCh := make(chan struct{})
|
||||
|
||||
handlers := make(map[string]http.Handler)
|
||||
confModifier := &aghtest.ConfigModifier{}
|
||||
confModifier.OnApply = func(_ context.Context) {
|
||||
@ -224,10 +329,74 @@ func assertRewritesList(tb testing.TB, handler http.Handler, wantList []*rewrite
|
||||
func rewriteEntriesToLegacyRewrites(entries []*rewriteJSON) (rw []*filtering.LegacyRewrite) {
|
||||
for _, entry := range entries {
|
||||
rw = append(rw, &filtering.LegacyRewrite{
|
||||
Domain: entry.Domain,
|
||||
Answer: entry.Answer,
|
||||
Domain: entry.Domain,
|
||||
Answer: entry.Answer,
|
||||
Enabled: entry.Enabled == aghalg.NBTrue,
|
||||
})
|
||||
}
|
||||
|
||||
return rw
|
||||
}
|
||||
|
||||
func TestDNSFilter_HandleRewriteSettings(t *testing.T) {
|
||||
const (
|
||||
enabled = "enabled"
|
||||
|
||||
path = "/control/rewrite/settings"
|
||||
pathUpdate = path + "/update"
|
||||
)
|
||||
|
||||
var (
|
||||
wantEnabled = fmt.Sprintf("{%q:%s}", enabled, "true")
|
||||
wantDisabled = fmt.Sprintf("{%q:%s}", enabled, "false")
|
||||
)
|
||||
|
||||
confUpdated := false
|
||||
confModifier := &aghtest.ConfigModifier{
|
||||
OnApply: func(_ context.Context) {
|
||||
confUpdated = true
|
||||
},
|
||||
}
|
||||
handlers := make(map[string]http.Handler)
|
||||
|
||||
d, err := filtering.New(&filtering.Config{
|
||||
Logger: testLogger,
|
||||
ConfModifier: confModifier,
|
||||
HTTPRegister: func(_, url string, handler http.HandlerFunc) {
|
||||
handlers[url] = handler
|
||||
},
|
||||
RewritesEnabled: false,
|
||||
}, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Cleanup(d.Close)
|
||||
|
||||
require.True(t, t.Run("register", func(t *testing.T) {
|
||||
d.RegisterFilteringHandlers()
|
||||
require.NotEmpty(t, handlers)
|
||||
require.Contains(t, handlers, path)
|
||||
require.Contains(t, handlers, pathUpdate)
|
||||
|
||||
r := httptest.NewRequest(http.MethodGet, path, nil)
|
||||
w := httptest.NewRecorder()
|
||||
handlers[path].ServeHTTP(w, r)
|
||||
require.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
assert.JSONEq(t, wantDisabled, w.Body.String())
|
||||
}))
|
||||
|
||||
require.True(t, t.Run("update", func(t *testing.T) {
|
||||
r := httptest.NewRequest(http.MethodPut, path, bytes.NewReader([]byte(wantEnabled)))
|
||||
w := httptest.NewRecorder()
|
||||
handlers[pathUpdate].ServeHTTP(w, r)
|
||||
require.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
r = httptest.NewRequest(http.MethodGet, path, nil)
|
||||
w = httptest.NewRecorder()
|
||||
handlers[path].ServeHTTP(w, r)
|
||||
require.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
assert.True(t, confUpdated)
|
||||
assert.JSONEq(t, wantEnabled, w.Body.String())
|
||||
}))
|
||||
}
|
||||
|
||||
@ -17,9 +17,11 @@ import (
|
||||
|
||||
// LegacyRewrite is a single legacy DNS rewrite record.
|
||||
//
|
||||
// Instances of *LegacyRewrite must never be nil.
|
||||
// Instances of *LegacyRewrite must not be nil.
|
||||
//
|
||||
// NOTE: Keep fields in sync with [cloneRewrites].
|
||||
type LegacyRewrite struct {
|
||||
// Domain is the domain pattern for which this rewrite should work.
|
||||
// Domain is the pattern to which this rewrite applies.
|
||||
Domain string `yaml:"domain"`
|
||||
|
||||
// Answer is the IP address, canonical name, or one of the special
|
||||
@ -32,6 +34,9 @@ type LegacyRewrite struct {
|
||||
|
||||
// Type is the DNS record type: A, AAAA, or CNAME.
|
||||
Type uint16 `yaml:"-"`
|
||||
|
||||
// Enabled indicates whether this rewrite is active.
|
||||
Enabled bool `yaml:"enabled"`
|
||||
}
|
||||
|
||||
// equal returns true if the rw is equal to the other.
|
||||
@ -162,6 +167,10 @@ func findRewrites(
|
||||
qtype uint16,
|
||||
) (rewrites []*LegacyRewrite, matched bool) {
|
||||
for _, e := range entries {
|
||||
if !e.Enabled {
|
||||
continue
|
||||
}
|
||||
|
||||
if e.Domain != host && !matchDomainWildcard(host, e.Domain) {
|
||||
continue
|
||||
}
|
||||
@ -176,6 +185,11 @@ func findRewrites(
|
||||
return nil, matched
|
||||
}
|
||||
|
||||
return finalizeRewrites(rewrites), matched
|
||||
}
|
||||
|
||||
// finalizeRewrites sorts rewrites and truncates wildcard ones.
|
||||
func finalizeRewrites(rewrites []*LegacyRewrite) (resRewrites []*LegacyRewrite) {
|
||||
slices.SortFunc(rewrites, (*LegacyRewrite).Compare)
|
||||
|
||||
for i, r := range rewrites {
|
||||
@ -188,7 +202,7 @@ func findRewrites(
|
||||
}
|
||||
}
|
||||
|
||||
return rewrites, matched
|
||||
return rewrites
|
||||
}
|
||||
|
||||
// setRewriteResult sets the Reason or IPList of res if necessary. res must not
|
||||
@ -221,10 +235,11 @@ func cloneRewrites(entries []*LegacyRewrite) (clone []*LegacyRewrite) {
|
||||
clone = make([]*LegacyRewrite, len(entries))
|
||||
for i, rw := range entries {
|
||||
clone[i] = &LegacyRewrite{
|
||||
Domain: rw.Domain,
|
||||
Answer: rw.Answer,
|
||||
IP: rw.IP,
|
||||
Type: rw.Type,
|
||||
Domain: rw.Domain,
|
||||
Answer: rw.Answer,
|
||||
IP: rw.IP,
|
||||
Type: rw.Type,
|
||||
Enabled: rw.Enabled,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -29,64 +29,86 @@ func TestRewrites(t *testing.T) {
|
||||
|
||||
d.conf.Rewrites = []*LegacyRewrite{{
|
||||
// This one and below are about CNAME, A and AAAA.
|
||||
Domain: "somecname",
|
||||
Answer: "somehost.com",
|
||||
Domain: "somecname",
|
||||
Answer: "somehost.com",
|
||||
Enabled: true,
|
||||
}, {
|
||||
Domain: "somehost.com",
|
||||
Answer: netip.IPv4Unspecified().String(),
|
||||
Domain: "somehost.com",
|
||||
Answer: netip.IPv4Unspecified().String(),
|
||||
Enabled: true,
|
||||
}, {
|
||||
Domain: "host.com",
|
||||
Answer: addr1v4.String(),
|
||||
Domain: "host.com",
|
||||
Answer: addr1v4.String(),
|
||||
Enabled: true,
|
||||
}, {
|
||||
Domain: "host.com",
|
||||
Answer: addr2v4.String(),
|
||||
Domain: "host.com",
|
||||
Answer: addr2v4.String(),
|
||||
Enabled: true,
|
||||
}, {
|
||||
Domain: "host.com",
|
||||
Answer: addr1v6.String(),
|
||||
Domain: "host.com",
|
||||
Answer: addr1v6.String(),
|
||||
Enabled: true,
|
||||
}, {
|
||||
Domain: "www.host.com",
|
||||
Answer: "host.com",
|
||||
Domain: "www.host.com",
|
||||
Answer: "host.com",
|
||||
Enabled: true,
|
||||
}, {
|
||||
// This one is a wildcard.
|
||||
Domain: "*.host.com",
|
||||
Answer: addr2v4.String(),
|
||||
Domain: "*.host.com",
|
||||
Answer: addr2v4.String(),
|
||||
Enabled: true,
|
||||
}, {
|
||||
// This one and below are about wildcard overriding.
|
||||
Domain: "a.host.com",
|
||||
Answer: addr1v4.String(),
|
||||
Domain: "a.host.com",
|
||||
Answer: addr1v4.String(),
|
||||
Enabled: true,
|
||||
}, {
|
||||
// This one is about CNAME and wildcard interacting.
|
||||
Domain: "*.host2.com",
|
||||
Answer: "host.com",
|
||||
Domain: "*.host2.com",
|
||||
Answer: "host.com",
|
||||
Enabled: true,
|
||||
}, {
|
||||
// This one and below are about 2 level CNAME.
|
||||
Domain: "b.host.com",
|
||||
Answer: "somecname",
|
||||
Domain: "b.host.com",
|
||||
Answer: "somecname",
|
||||
Enabled: true,
|
||||
}, {
|
||||
// This one and below are about 2 level CNAME and wildcard.
|
||||
Domain: "b.host3.com",
|
||||
Answer: "a.host3.com",
|
||||
Domain: "b.host3.com",
|
||||
Answer: "a.host3.com",
|
||||
Enabled: true,
|
||||
}, {
|
||||
Domain: "a.host3.com",
|
||||
Answer: "x.host.com",
|
||||
Domain: "a.host3.com",
|
||||
Answer: "x.host.com",
|
||||
Enabled: true,
|
||||
}, {
|
||||
Domain: "*.hostboth.com",
|
||||
Answer: addr3v4.String(),
|
||||
Domain: "*.hostboth.com",
|
||||
Answer: addr3v4.String(),
|
||||
Enabled: true,
|
||||
}, {
|
||||
Domain: "*.hostboth.com",
|
||||
Answer: addr2v6.String(),
|
||||
Domain: "*.hostboth.com",
|
||||
Answer: addr2v6.String(),
|
||||
Enabled: true,
|
||||
}, {
|
||||
Domain: "BIGHOST.COM",
|
||||
Answer: addr4v4.String(),
|
||||
Domain: "BIGHOST.COM",
|
||||
Answer: addr4v4.String(),
|
||||
Enabled: true,
|
||||
}, {
|
||||
Domain: "*.issue4016.com",
|
||||
Answer: "sub.issue4016.com",
|
||||
Domain: "*.issue4016.com",
|
||||
Answer: "sub.issue4016.com",
|
||||
Enabled: true,
|
||||
}, {
|
||||
Domain: "*.sub.issue6226.com",
|
||||
Answer: addr2v4.String(),
|
||||
Domain: "*.sub.issue6226.com",
|
||||
Answer: addr2v4.String(),
|
||||
Enabled: true,
|
||||
}, {
|
||||
Domain: "*.issue6226.com",
|
||||
Answer: addr1v4.String(),
|
||||
Domain: "*.issue6226.com",
|
||||
Answer: addr1v4.String(),
|
||||
Enabled: true,
|
||||
}, {
|
||||
Domain: "disabled.rewrite.test",
|
||||
Answer: addr1v4.String(),
|
||||
Enabled: false,
|
||||
}}
|
||||
|
||||
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||
@ -204,6 +226,13 @@ func TestRewrites(t *testing.T) {
|
||||
wantIPs: []netip.Addr{addr2v4},
|
||||
wantReason: Rewritten,
|
||||
dtyp: dns.TypeA,
|
||||
}, {
|
||||
name: "not_filtered_disabled_rewrite",
|
||||
host: "disabled.rewrite.test",
|
||||
wantCName: "",
|
||||
wantIPs: nil,
|
||||
wantReason: NotFilteredNotFound,
|
||||
dtyp: dns.TypeA,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
@ -225,17 +254,20 @@ func TestRewritesLevels(t *testing.T) {
|
||||
t.Cleanup(d.Close)
|
||||
// Exact host, wildcard L2, wildcard L3.
|
||||
d.conf.Rewrites = []*LegacyRewrite{{
|
||||
Domain: "host.com",
|
||||
Answer: "1.1.1.1",
|
||||
Type: dns.TypeA,
|
||||
Domain: "host.com",
|
||||
Answer: "1.1.1.1",
|
||||
Type: dns.TypeA,
|
||||
Enabled: true,
|
||||
}, {
|
||||
Domain: "*.host.com",
|
||||
Answer: "2.2.2.2",
|
||||
Type: dns.TypeA,
|
||||
Domain: "*.host.com",
|
||||
Answer: "2.2.2.2",
|
||||
Type: dns.TypeA,
|
||||
Enabled: true,
|
||||
}, {
|
||||
Domain: "*.sub.host.com",
|
||||
Answer: "3.3.3.3",
|
||||
Type: dns.TypeA,
|
||||
Domain: "*.sub.host.com",
|
||||
Answer: "3.3.3.3",
|
||||
Type: dns.TypeA,
|
||||
Enabled: true,
|
||||
}}
|
||||
|
||||
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||
@ -273,14 +305,17 @@ func TestRewritesExceptionCNAME(t *testing.T) {
|
||||
t.Cleanup(d.Close)
|
||||
// Wildcard and exception for a sub-domain.
|
||||
d.conf.Rewrites = []*LegacyRewrite{{
|
||||
Domain: "*.host.com",
|
||||
Answer: "2.2.2.2",
|
||||
Domain: "*.host.com",
|
||||
Answer: "2.2.2.2",
|
||||
Enabled: true,
|
||||
}, {
|
||||
Domain: "sub.host.com",
|
||||
Answer: "sub.host.com",
|
||||
Domain: "sub.host.com",
|
||||
Answer: "sub.host.com",
|
||||
Enabled: true,
|
||||
}, {
|
||||
Domain: "*.sub.host.com",
|
||||
Answer: "*.sub.host.com",
|
||||
Domain: "*.sub.host.com",
|
||||
Answer: "*.sub.host.com",
|
||||
Enabled: true,
|
||||
}}
|
||||
|
||||
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||
@ -325,25 +360,30 @@ func TestRewritesExceptionIP(t *testing.T) {
|
||||
t.Cleanup(d.Close)
|
||||
// Exception for AAAA record.
|
||||
d.conf.Rewrites = []*LegacyRewrite{{
|
||||
Domain: "host.com",
|
||||
Answer: "1.2.3.4",
|
||||
Type: dns.TypeA,
|
||||
Domain: "host.com",
|
||||
Answer: "1.2.3.4",
|
||||
Type: dns.TypeA,
|
||||
Enabled: true,
|
||||
}, {
|
||||
Domain: "host.com",
|
||||
Answer: "AAAA",
|
||||
Type: dns.TypeAAAA,
|
||||
Domain: "host.com",
|
||||
Answer: "AAAA",
|
||||
Type: dns.TypeAAAA,
|
||||
Enabled: true,
|
||||
}, {
|
||||
Domain: "host2.com",
|
||||
Answer: "::1",
|
||||
Type: dns.TypeAAAA,
|
||||
Domain: "host2.com",
|
||||
Answer: "::1",
|
||||
Type: dns.TypeAAAA,
|
||||
Enabled: true,
|
||||
}, {
|
||||
Domain: "host2.com",
|
||||
Answer: "A",
|
||||
Type: dns.TypeA,
|
||||
Domain: "host2.com",
|
||||
Answer: "A",
|
||||
Type: dns.TypeA,
|
||||
Enabled: true,
|
||||
}, {
|
||||
Domain: "host3.com",
|
||||
Answer: "A",
|
||||
Type: dns.TypeA,
|
||||
Domain: "host3.com",
|
||||
Answer: "A",
|
||||
Type: dns.TypeA,
|
||||
Enabled: true,
|
||||
}}
|
||||
|
||||
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||
|
||||
@ -530,6 +530,8 @@ var config = &configuration{
|
||||
FilteringEnabled: true,
|
||||
FiltersUpdateIntervalHours: 24,
|
||||
|
||||
RewritesEnabled: true,
|
||||
|
||||
ParentalEnabled: false,
|
||||
SafeBrowsingEnabled: false,
|
||||
|
||||
|
||||
@ -2,7 +2,23 @@
|
||||
|
||||
<!-- TODO(a.garipov): Reformat in accordance with the KeepAChangelog spec. -->
|
||||
|
||||
## v0.107.67: API changes
|
||||
## v0.107.68: API changes
|
||||
|
||||
### New HTTP APIs 'GET /control/rewrite/settings' and 'PUT /control/rewrite/settings/update'
|
||||
|
||||
- New HTTP APIs to manage global DNS rewrites.
|
||||
|
||||
```json
|
||||
{
|
||||
"enabled": true
|
||||
}
|
||||
```
|
||||
|
||||
### New `"enabled"` field in 'POST /control/rewrite/add' and 'PUT /control/rewrite/update'
|
||||
|
||||
- New optional field `"enabled"` indicates whether the rewrite is active.
|
||||
|
||||
### The blocked services groups
|
||||
|
||||
- The new field `"groups"` in `GET /control/blocked_services/all` is a list of service group. Groups make it possible to block multiple services with equal `"group_id"` at once.
|
||||
|
||||
|
||||
@ -16,6 +16,8 @@
|
||||
- 'basicAuth': []
|
||||
|
||||
'tags':
|
||||
- 'name': 'blocked_services'
|
||||
'description': 'Blocked services controls'
|
||||
- 'name': 'clients'
|
||||
'description': 'Clients list operations'
|
||||
- 'name': 'dhcp'
|
||||
@ -34,6 +36,8 @@
|
||||
'description': 'Apple .mobileconfig'
|
||||
- 'name': 'parental'
|
||||
'description': 'Blocking adult and explicit materials'
|
||||
- 'name': 'rewrite'
|
||||
'description': 'DNS rewrites'
|
||||
- 'name': 'safebrowsing'
|
||||
'description': 'Blocking malware/phishing sites'
|
||||
- 'name': 'safesearch'
|
||||
@ -1153,6 +1157,30 @@
|
||||
'responses':
|
||||
'200':
|
||||
'description': 'OK.'
|
||||
'/rewrite/settings':
|
||||
'get':
|
||||
'tags':
|
||||
- 'rewrite'
|
||||
'operationId': 'rewriteSettingsGet'
|
||||
'summary': 'Get rewrite settings'
|
||||
'responses':
|
||||
'200':
|
||||
'description': 'OK.'
|
||||
'content':
|
||||
'application/json':
|
||||
'schema':
|
||||
'$ref': '#/components/schemas/RewriteSettings'
|
||||
'/rewrite/settings/update':
|
||||
'put':
|
||||
'tags':
|
||||
- 'rewrite'
|
||||
'operationId': 'rewriteSettingsUpdate'
|
||||
'summary': 'Update rewrite settings'
|
||||
'requestBody':
|
||||
'$ref': '#/components/requestBodies/RewriteSettings'
|
||||
'responses':
|
||||
'200':
|
||||
'description': 'OK.'
|
||||
'/rewrite/update':
|
||||
'put':
|
||||
'tags':
|
||||
@ -1414,6 +1442,12 @@
|
||||
'schema':
|
||||
'$ref': '#/components/schemas/RewriteEntry'
|
||||
'required': true
|
||||
'RewriteSettings':
|
||||
'content':
|
||||
'application/json':
|
||||
'schema':
|
||||
'$ref': '#/components/schemas/RewriteSettings'
|
||||
'required': true
|
||||
'RewriteUpdate':
|
||||
'content':
|
||||
'application/json':
|
||||
@ -3002,6 +3036,23 @@
|
||||
'type': 'string'
|
||||
'description': 'value of A, AAAA or CNAME DNS record'
|
||||
'example': '127.0.0.1'
|
||||
'enabled':
|
||||
'type': 'boolean'
|
||||
'description': >
|
||||
Optional. If omitted on add, defaults to `true`. On update, omitted
|
||||
preserves previous value.
|
||||
'example': true
|
||||
'default': true
|
||||
'RewriteSettings':
|
||||
'type': 'object'
|
||||
'description': 'DNS rewrite settings'
|
||||
'required':
|
||||
- 'enabled'
|
||||
'properties':
|
||||
'enabled':
|
||||
'type': 'boolean'
|
||||
'description': 'indicates whether rewrites are applied'
|
||||
'example': true
|
||||
'BlockedServicesArray':
|
||||
'type': 'array'
|
||||
'items':
|
||||
|
||||
@ -89,7 +89,7 @@ if [ "$(git diff --cached --name-only -- '*.sh' || :)" != '' ]; then
|
||||
make VERBOSE="$verbose" sh-lint
|
||||
fi
|
||||
|
||||
if [ "$(git diff --cached --name-only -- '*.md' '*.txt' '*.yaml' '*.yml' || :)" != '' ]; then
|
||||
if [ "$(git diff --cached --name-only -- '*.md' '*.json' '*.txt' '*.yaml' '*.yml' || :)" != '' ]; then
|
||||
make VERBOSE="$verbose" txt-lint
|
||||
fi
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user