Merge: Add WHOIS info for clients
* commit 'a52715e0863af0a9e1b26dbf96fc7cced02ae4f6': + client: add whois info to dashboard and logs + client: add whois info to clients and auto clients table * rDNS: refactor + whois: add WHOIS information for a client
This commit is contained in:
commit
875905ce8a
|
@ -650,6 +650,10 @@ Response:
|
||||||
safesearch_enabled: false
|
safesearch_enabled: false
|
||||||
use_global_blocked_services: true
|
use_global_blocked_services: true
|
||||||
blocked_services: [ "name1", ... ]
|
blocked_services: [ "name1", ... ]
|
||||||
|
whois_info: {
|
||||||
|
key: "value"
|
||||||
|
...
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
auto_clients: [
|
auto_clients: [
|
||||||
|
@ -657,10 +661,16 @@ Response:
|
||||||
name: "host"
|
name: "host"
|
||||||
ip: "..."
|
ip: "..."
|
||||||
source: "etc/hosts" || "rDNS"
|
source: "etc/hosts" || "rDNS"
|
||||||
|
whois_info: {
|
||||||
|
key: "value"
|
||||||
|
...
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Supported keys for `whois_info`: orgname, country, city.
|
||||||
|
|
||||||
|
|
||||||
### Add client
|
### Add client
|
||||||
|
|
||||||
|
|
|
@ -392,5 +392,9 @@
|
||||||
"sign_in": "Sign in",
|
"sign_in": "Sign in",
|
||||||
"sign_out": "Sign out",
|
"sign_out": "Sign out",
|
||||||
"forgot_password": "Forgot password?",
|
"forgot_password": "Forgot password?",
|
||||||
"forgot_password_desc": "Please follow <0>these steps</0> to create a new password for your user account."
|
"forgot_password_desc": "Please follow <0>these steps</0> to create a new password for your user account.",
|
||||||
|
"city": "<0>City:</0> {{value}}",
|
||||||
|
"country": "<0>Country:</0> {{value}}",
|
||||||
|
"orgname": "<0>OrgName:</0> {{value}}",
|
||||||
|
"whois": "Whois"
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,9 @@ import { Trans, withNamespaces } from 'react-i18next';
|
||||||
import Card from '../ui/Card';
|
import Card from '../ui/Card';
|
||||||
import Cell from '../ui/Cell';
|
import Cell from '../ui/Cell';
|
||||||
|
|
||||||
import { getPercent, getClientName } from '../../helpers/helpers';
|
import { getPercent } from '../../helpers/helpers';
|
||||||
import { STATUS_COLORS } from '../../helpers/constants';
|
import { STATUS_COLORS } from '../../helpers/constants';
|
||||||
|
import { formatClientCell } from '../../helpers/formatClientCell';
|
||||||
|
|
||||||
const getClientsPercentColor = (percent) => {
|
const getClientsPercentColor = (percent) => {
|
||||||
if (percent > 50) {
|
if (percent > 50) {
|
||||||
|
@ -18,31 +19,6 @@ const getClientsPercentColor = (percent) => {
|
||||||
return STATUS_COLORS.red;
|
return STATUS_COLORS.red;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ipCell = (clients, autoClients) =>
|
|
||||||
function cell(row) {
|
|
||||||
let client;
|
|
||||||
const { value } = row;
|
|
||||||
const clientName = getClientName(clients, value) || getClientName(autoClients, value);
|
|
||||||
|
|
||||||
if (clientName) {
|
|
||||||
client = (
|
|
||||||
<span>
|
|
||||||
{clientName} <small>({value})</small>
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
client = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="logs__row logs__row--overflow">
|
|
||||||
<span className="logs__text" title={value}>
|
|
||||||
{client}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const countCell = dnsQueries =>
|
const countCell = dnsQueries =>
|
||||||
function cell(row) {
|
function cell(row) {
|
||||||
const { value } = row;
|
const { value } = row;
|
||||||
|
@ -52,6 +28,17 @@ const countCell = dnsQueries =>
|
||||||
return <Cell value={value} percent={percent} color={percentColor} />;
|
return <Cell value={value} percent={percent} color={percentColor} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const clientCell = (clients, autoClients) =>
|
||||||
|
function cell(row) {
|
||||||
|
const { value } = row;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="logs__row logs__row--overflow logs__row--column">
|
||||||
|
{formatClientCell(value, clients, autoClients)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const Clients = ({
|
const Clients = ({
|
||||||
t, refreshButton, topClients, subtitle, clients, autoClients, dnsQueries,
|
t, refreshButton, topClients, subtitle, clients, autoClients, dnsQueries,
|
||||||
}) => (
|
}) => (
|
||||||
|
@ -72,7 +59,7 @@ const Clients = ({
|
||||||
accessor: 'ip',
|
accessor: 'ip',
|
||||||
sortMethod: (a, b) =>
|
sortMethod: (a, b) =>
|
||||||
parseInt(a.replace(/\./g, ''), 10) - parseInt(b.replace(/\./g, ''), 10),
|
parseInt(a.replace(/\./g, ''), 10) - parseInt(b.replace(/\./g, ''), 10),
|
||||||
Cell: ipCell(clients, autoClients),
|
Cell: clientCell(clients, autoClients),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: <Trans>requests_count</Trans>,
|
Header: <Trans>requests_count</Trans>,
|
||||||
|
|
|
@ -10,8 +10,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.logs__row--column {
|
.logs__row--column {
|
||||||
align-items: flex-start;
|
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logs__row--overflow {
|
.logs__row--overflow {
|
||||||
|
@ -40,6 +41,11 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.logs__text--wrap {
|
||||||
|
line-height: 1.4;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
.logs__row .tooltip-custom {
|
.logs__row .tooltip-custom {
|
||||||
top: 0;
|
top: 0;
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
|
|
|
@ -6,9 +6,11 @@ import endsWith from 'lodash/endsWith';
|
||||||
import { Trans, withNamespaces } from 'react-i18next';
|
import { Trans, withNamespaces } from 'react-i18next';
|
||||||
import { HashLink as Link } from 'react-router-hash-link';
|
import { HashLink as Link } from 'react-router-hash-link';
|
||||||
|
|
||||||
import { formatTime, formatDateTime, getClientName } from '../../helpers/helpers';
|
import { formatTime, formatDateTime } from '../../helpers/helpers';
|
||||||
import { SERVICES, FILTERED_STATUS } from '../../helpers/constants';
|
import { SERVICES, FILTERED_STATUS } from '../../helpers/constants';
|
||||||
import { getTrackerData } from '../../helpers/trackers/trackers';
|
import { getTrackerData } from '../../helpers/trackers/trackers';
|
||||||
|
import { formatClientCell } from '../../helpers/formatClientCell';
|
||||||
|
|
||||||
import PageTitle from '../ui/PageTitle';
|
import PageTitle from '../ui/PageTitle';
|
||||||
import Card from '../ui/Card';
|
import Card from '../ui/Card';
|
||||||
import Loading from '../ui/Loading';
|
import Loading from '../ui/Loading';
|
||||||
|
@ -190,24 +192,16 @@ class Logs extends Component {
|
||||||
|
|
||||||
getClientCell = ({ original, value }) => {
|
getClientCell = ({ original, value }) => {
|
||||||
const { dashboard } = this.props;
|
const { dashboard } = this.props;
|
||||||
|
const { clients, autoClients } = dashboard;
|
||||||
const { reason, domain } = original;
|
const { reason, domain } = original;
|
||||||
const isFiltered = this.checkFiltered(reason);
|
const isFiltered = this.checkFiltered(reason);
|
||||||
const isRewrite = this.checkRewrite(reason);
|
const isRewrite = this.checkRewrite(reason);
|
||||||
const clientName =
|
|
||||||
getClientName(dashboard.clients, value) || getClientName(dashboard.autoClients, value);
|
|
||||||
let client = value;
|
|
||||||
|
|
||||||
if (clientName) {
|
|
||||||
client = (
|
|
||||||
<span>
|
|
||||||
{clientName} <small>({value})</small>
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<div className="logs__row">{client}</div>
|
<div className="logs__row logs__row--overflow logs__row--column">
|
||||||
|
{formatClientCell(value, clients, autoClients)}
|
||||||
|
</div>
|
||||||
{isRewrite ? (
|
{isRewrite ? (
|
||||||
<div className="logs__action">
|
<div className="logs__action">
|
||||||
<Link to="/dns#rewrites" className="btn btn-sm btn-outline-primary">
|
<Link to="/dns#rewrites" className="btn btn-sm btn-outline-primary">
|
||||||
|
@ -273,8 +267,8 @@ class Logs extends Component {
|
||||||
{
|
{
|
||||||
Header: t('client_table_header'),
|
Header: t('client_table_header'),
|
||||||
accessor: 'client',
|
accessor: 'client',
|
||||||
maxWidth: 220,
|
maxWidth: 240,
|
||||||
minWidth: 220,
|
minWidth: 240,
|
||||||
Cell: this.getClientCell,
|
Cell: this.getClientCell,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
@ -4,6 +4,8 @@ import { withNamespaces } from 'react-i18next';
|
||||||
import ReactTable from 'react-table';
|
import ReactTable from 'react-table';
|
||||||
|
|
||||||
import Card from '../../ui/Card';
|
import Card from '../../ui/Card';
|
||||||
|
import WhoisCell from './WhoisCell';
|
||||||
|
import WrapCell from './WrapCell';
|
||||||
|
|
||||||
class AutoClients extends Component {
|
class AutoClients extends Component {
|
||||||
getStats = (ip, stats) => {
|
getStats = (ip, stats) => {
|
||||||
|
@ -15,29 +17,26 @@ class AutoClients extends Component {
|
||||||
return '';
|
return '';
|
||||||
};
|
};
|
||||||
|
|
||||||
cellWrap = ({ value }) => (
|
|
||||||
<div className="logs__row logs__row--overflow">
|
|
||||||
<span className="logs__text" title={value}>
|
|
||||||
{value}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
columns = [
|
columns = [
|
||||||
{
|
{
|
||||||
Header: this.props.t('table_client'),
|
Header: this.props.t('table_client'),
|
||||||
accessor: 'ip',
|
accessor: 'ip',
|
||||||
Cell: this.cellWrap,
|
Cell: WrapCell,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: this.props.t('table_name'),
|
Header: this.props.t('table_name'),
|
||||||
accessor: 'name',
|
accessor: 'name',
|
||||||
Cell: this.cellWrap,
|
Cell: WrapCell,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: this.props.t('source_label'),
|
Header: this.props.t('source_label'),
|
||||||
accessor: 'source',
|
accessor: 'source',
|
||||||
Cell: this.cellWrap,
|
Cell: WrapCell,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: this.props.t('whois'),
|
||||||
|
accessor: 'whois_info',
|
||||||
|
Cell: WhoisCell,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: this.props.t('requests_count'),
|
Header: this.props.t('requests_count'),
|
||||||
|
|
|
@ -6,6 +6,8 @@ import ReactTable from 'react-table';
|
||||||
import { MODAL_TYPE, CLIENT_ID } from '../../../helpers/constants';
|
import { MODAL_TYPE, CLIENT_ID } from '../../../helpers/constants';
|
||||||
import Card from '../../ui/Card';
|
import Card from '../../ui/Card';
|
||||||
import Modal from './Modal';
|
import Modal from './Modal';
|
||||||
|
import WrapCell from './WrapCell';
|
||||||
|
import WhoisCell from './WhoisCell';
|
||||||
|
|
||||||
class ClientsTable extends Component {
|
class ClientsTable extends Component {
|
||||||
handleFormAdd = (values) => {
|
handleFormAdd = (values) => {
|
||||||
|
@ -33,14 +35,6 @@ class ClientsTable extends Component {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
cellWrap = ({ value }) => (
|
|
||||||
<div className="logs__row logs__row--overflow">
|
|
||||||
<span className="logs__text" title={value}>
|
|
||||||
{value}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
getClient = (name, clients) => {
|
getClient = (name, clients) => {
|
||||||
const client = clients.find(item => name === item.name);
|
const client = clients.find(item => name === item.name);
|
||||||
|
|
||||||
|
@ -82,6 +76,7 @@ class ClientsTable extends Component {
|
||||||
{
|
{
|
||||||
Header: this.props.t('table_client'),
|
Header: this.props.t('table_client'),
|
||||||
accessor: 'ip',
|
accessor: 'ip',
|
||||||
|
minWidth: 150,
|
||||||
Cell: (row) => {
|
Cell: (row) => {
|
||||||
if (row.original && row.original.mac) {
|
if (row.original && row.original.mac) {
|
||||||
return (
|
return (
|
||||||
|
@ -107,11 +102,13 @@ class ClientsTable extends Component {
|
||||||
{
|
{
|
||||||
Header: this.props.t('table_name'),
|
Header: this.props.t('table_name'),
|
||||||
accessor: 'name',
|
accessor: 'name',
|
||||||
Cell: this.cellWrap,
|
minWidth: 120,
|
||||||
|
Cell: WrapCell,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: this.props.t('settings'),
|
Header: this.props.t('settings'),
|
||||||
accessor: 'use_global_settings',
|
accessor: 'use_global_settings',
|
||||||
|
minWidth: 120,
|
||||||
Cell: ({ value }) => {
|
Cell: ({ value }) => {
|
||||||
const title = value ? (
|
const title = value ? (
|
||||||
<Trans>settings_global</Trans>
|
<Trans>settings_global</Trans>
|
||||||
|
@ -131,6 +128,7 @@ class ClientsTable extends Component {
|
||||||
{
|
{
|
||||||
Header: this.props.t('blocked_services'),
|
Header: this.props.t('blocked_services'),
|
||||||
accessor: 'blocked_services',
|
accessor: 'blocked_services',
|
||||||
|
minWidth: 180,
|
||||||
Cell: (row) => {
|
Cell: (row) => {
|
||||||
const { value, original } = row;
|
const { value, original } = row;
|
||||||
|
|
||||||
|
@ -149,9 +147,16 @@ class ClientsTable extends Component {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Header: this.props.t('whois'),
|
||||||
|
accessor: 'whois_info',
|
||||||
|
minWidth: 200,
|
||||||
|
Cell: WhoisCell,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Header: this.props.t('requests_count'),
|
Header: this.props.t('requests_count'),
|
||||||
accessor: 'statistics',
|
accessor: 'statistics',
|
||||||
|
minWidth: 120,
|
||||||
Cell: (row) => {
|
Cell: (row) => {
|
||||||
const clientIP = row.original.ip;
|
const clientIP = row.original.ip;
|
||||||
const clientStats = clientIP && this.getStats(clientIP, this.props.topClients);
|
const clientStats = clientIP && this.getStats(clientIP, this.props.topClients);
|
||||||
|
@ -172,7 +177,7 @@ class ClientsTable extends Component {
|
||||||
{
|
{
|
||||||
Header: this.props.t('actions_table_header'),
|
Header: this.props.t('actions_table_header'),
|
||||||
accessor: 'actions',
|
accessor: 'actions',
|
||||||
maxWidth: 150,
|
maxWidth: 100,
|
||||||
Cell: (row) => {
|
Cell: (row) => {
|
||||||
const clientName = row.original.name;
|
const clientName = row.original.name;
|
||||||
const {
|
const {
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Trans } from 'react-i18next';
|
||||||
|
|
||||||
|
const getFormattedWhois = (value) => {
|
||||||
|
const keys = Object.keys(value);
|
||||||
|
|
||||||
|
if (keys.length > 0) {
|
||||||
|
return (
|
||||||
|
keys.map(key => (
|
||||||
|
<div key={key} title={value[key]}>
|
||||||
|
<Trans
|
||||||
|
values={{ value: value[key] }}
|
||||||
|
components={[<small key="0">text</small>]}
|
||||||
|
>
|
||||||
|
{key}
|
||||||
|
</Trans>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return '–';
|
||||||
|
};
|
||||||
|
|
||||||
|
const WhoisCell = ({ value }) => (
|
||||||
|
<div className="logs__row logs__row--overflow">
|
||||||
|
<span className="logs__text logs__text--wrap">
|
||||||
|
{getFormattedWhois(value)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
WhoisCell.propTypes = {
|
||||||
|
value: PropTypes.object.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WhoisCell;
|
|
@ -0,0 +1,19 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
const WrapCell = ({ value }) => (
|
||||||
|
<div className="logs__row logs__row--overflow">
|
||||||
|
<span className="logs__text" title={value}>
|
||||||
|
{value || '–'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
WrapCell.propTypes = {
|
||||||
|
value: PropTypes.oneOfType([
|
||||||
|
PropTypes.string,
|
||||||
|
PropTypes.number,
|
||||||
|
]),
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WrapCell;
|
|
@ -0,0 +1,32 @@
|
||||||
|
import React, { Fragment } from 'react';
|
||||||
|
import { getClientInfo } from './helpers';
|
||||||
|
|
||||||
|
export const formatClientCell = (value, clients, autoClients) => {
|
||||||
|
const clientInfo = getClientInfo(clients, value) || getClientInfo(autoClients, value);
|
||||||
|
const { name, whois } = clientInfo;
|
||||||
|
|
||||||
|
if (whois && name) {
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<div className="logs__text logs__text--wrap" title={`${name} (${value})`}>
|
||||||
|
{name} <small className="text-muted-dark">({value})</small>
|
||||||
|
</div>
|
||||||
|
<div className="logs__text logs__text--wrap" title={whois}>
|
||||||
|
<small className="text-muted">{whois}</small>
|
||||||
|
</div>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
} else if (name) {
|
||||||
|
return (
|
||||||
|
<span className="logs__text logs__text--wrap" title={`${name} (${value})`}>
|
||||||
|
{name} <small>({value})</small>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className="logs__text" title={value}>
|
||||||
|
{value}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
|
@ -245,9 +245,33 @@ export const redirectToCurrentProtocol = (values, httpPort = 80) => {
|
||||||
|
|
||||||
export const normalizeTextarea = text => text && text.replace(/[;, ]/g, '\n').split('\n').filter(n => n);
|
export const normalizeTextarea = text => text && text.replace(/[;, ]/g, '\n').split('\n').filter(n => n);
|
||||||
|
|
||||||
export const getClientName = (clients, ip) => {
|
const formatWhois = (whois) => {
|
||||||
|
if (!whois) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const keys = Object.keys(whois);
|
||||||
|
if (keys.length > 0) {
|
||||||
|
return (
|
||||||
|
keys.map(key => whois[key])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getClientInfo = (clients, ip) => {
|
||||||
const client = clients.find(item => ip === item.ip);
|
const client = clients.find(item => ip === item.ip);
|
||||||
return (client && client.name) || '';
|
|
||||||
|
if (!client) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const { name, whois_info } = client;
|
||||||
|
const formattedWhois = formatWhois(whois_info);
|
||||||
|
const whois = formattedWhois && formattedWhois.length > 0 && formattedWhois.join(' | ');
|
||||||
|
|
||||||
|
return { name, whois };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const sortClients = (clients) => {
|
export const sortClients = (clients) => {
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -15,6 +15,7 @@ require (
|
||||||
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1 // indirect
|
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1 // indirect
|
||||||
github.com/kardianos/service v0.0.0-20181115005516-4c239ee84e7b
|
github.com/kardianos/service v0.0.0-20181115005516-4c239ee84e7b
|
||||||
github.com/krolaw/dhcp4 v0.0.0-20180925202202-7cead472c414
|
github.com/krolaw/dhcp4 v0.0.0-20180925202202-7cead472c414
|
||||||
|
github.com/likexian/whois-go v0.0.0-20190627090909-384b3df3fc49
|
||||||
github.com/miekg/dns v1.1.8
|
github.com/miekg/dns v1.1.8
|
||||||
github.com/sparrc/go-ping v0.0.0-20181106165434-ef3ab45e41b0
|
github.com/sparrc/go-ping v0.0.0-20181106165434-ef3ab45e41b0
|
||||||
github.com/stretchr/testify v1.4.0
|
github.com/stretchr/testify v1.4.0
|
||||||
|
|
10
go.sum
10
go.sum
|
@ -53,6 +53,16 @@ github.com/kardianos/service v0.0.0-20181115005516-4c239ee84e7b h1:vfiqKno48aUnd
|
||||||
github.com/kardianos/service v0.0.0-20181115005516-4c239ee84e7b/go.mod h1:10UU/bEkzh2iEN6aYzbevY7J6p03KO5siTxQWXMEerg=
|
github.com/kardianos/service v0.0.0-20181115005516-4c239ee84e7b/go.mod h1:10UU/bEkzh2iEN6aYzbevY7J6p03KO5siTxQWXMEerg=
|
||||||
github.com/krolaw/dhcp4 v0.0.0-20180925202202-7cead472c414 h1:6wnYc2S/lVM7BvR32BM74ph7bPgqMztWopMYKgVyEho=
|
github.com/krolaw/dhcp4 v0.0.0-20180925202202-7cead472c414 h1:6wnYc2S/lVM7BvR32BM74ph7bPgqMztWopMYKgVyEho=
|
||||||
github.com/krolaw/dhcp4 v0.0.0-20180925202202-7cead472c414/go.mod h1:0AqAH3ZogsCrvrtUpvc6EtVKbc3w6xwZhkvGLuqyi3o=
|
github.com/krolaw/dhcp4 v0.0.0-20180925202202-7cead472c414/go.mod h1:0AqAH3ZogsCrvrtUpvc6EtVKbc3w6xwZhkvGLuqyi3o=
|
||||||
|
github.com/likexian/gokit v0.0.0-20190309162924-0a377eecf7aa/go.mod h1:QdfYv6y6qPA9pbBA2qXtoT8BMKha6UyNbxWGWl/9Jfk=
|
||||||
|
github.com/likexian/gokit v0.0.0-20190418170008-ace88ad0983b/go.mod h1:KKqSnk/VVSW8kEyO2vVCXoanzEutKdlBAPohmGXkxCk=
|
||||||
|
github.com/likexian/gokit v0.0.0-20190501133040-e77ea8b19cdc/go.mod h1:3kvONayqCaj+UgrRZGpgfXzHdMYCAO0KAt4/8n0L57Y=
|
||||||
|
github.com/likexian/gokit v0.0.0-20190604165112-68b8a4ba758c h1:KByA4IxKqqYwpqzk/P+w1DBpkPbvy3DArTP/U3LSxTQ=
|
||||||
|
github.com/likexian/gokit v0.0.0-20190604165112-68b8a4ba758c/go.mod h1:kn+nTv3tqh6yhor9BC4Lfiu58SmH8NmQ2PmEl+uM6nU=
|
||||||
|
github.com/likexian/simplejson-go v0.0.0-20190409170913-40473a74d76d/go.mod h1:Typ1BfnATYtZ/+/shXfFYLrovhFyuKvzwrdOnIDHlmg=
|
||||||
|
github.com/likexian/simplejson-go v0.0.0-20190419151922-c1f9f0b4f084/go.mod h1:U4O1vIJvIKwbMZKUJ62lppfdvkCdVd2nfMimHK81eec=
|
||||||
|
github.com/likexian/simplejson-go v0.0.0-20190502021454-d8787b4bfa0b/go.mod h1:3BWwtmKP9cXWwYCr5bkoVDEfLywacOv0s06OBEDpyt8=
|
||||||
|
github.com/likexian/whois-go v0.0.0-20190627090909-384b3df3fc49 h1:xGa+flE6p2UnMgxIS8bm7Q9JSt47HRuYVtwneDVnfLk=
|
||||||
|
github.com/likexian/whois-go v0.0.0-20190627090909-384b3df3fc49/go.mod h1:oR3bJMzrOb55cqTAn14DEzYFLDpSPTXJ3ORe7go9Hc8=
|
||||||
github.com/markbates/oncer v0.0.0-20181014194634-05fccaae8fc4 h1:Mlji5gkcpzkqTROyE4ZxZ8hN7osunMb2RuGVrbvMvCc=
|
github.com/markbates/oncer v0.0.0-20181014194634-05fccaae8fc4 h1:Mlji5gkcpzkqTROyE4ZxZ8hN7osunMb2RuGVrbvMvCc=
|
||||||
github.com/markbates/oncer v0.0.0-20181014194634-05fccaae8fc4/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
|
github.com/markbates/oncer v0.0.0-20181014194634-05fccaae8fc4/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
|
||||||
github.com/miekg/dns v1.1.8 h1:1QYRAKU3lN5cRfLCkPU08hwvLJFhvjP6MqNMmQz6ZVI=
|
github.com/miekg/dns v1.1.8 h1:1QYRAKU3lN5cRfLCkPU08hwvLJFhvjP6MqNMmQz6ZVI=
|
||||||
|
|
|
@ -31,6 +31,7 @@ type Client struct {
|
||||||
SafeSearchEnabled bool
|
SafeSearchEnabled bool
|
||||||
SafeBrowsingEnabled bool
|
SafeBrowsingEnabled bool
|
||||||
ParentalEnabled bool
|
ParentalEnabled bool
|
||||||
|
WhoisInfo [][]string // [[key,value], ...]
|
||||||
|
|
||||||
UseOwnBlockedServices bool // false: use global settings
|
UseOwnBlockedServices bool // false: use global settings
|
||||||
BlockedServices []string
|
BlockedServices []string
|
||||||
|
@ -46,29 +47,34 @@ type clientJSON struct {
|
||||||
SafeSearchEnabled bool `json:"safebrowsing_enabled"`
|
SafeSearchEnabled bool `json:"safebrowsing_enabled"`
|
||||||
SafeBrowsingEnabled bool `json:"safesearch_enabled"`
|
SafeBrowsingEnabled bool `json:"safesearch_enabled"`
|
||||||
|
|
||||||
|
WhoisInfo map[string]interface{} `json:"whois_info"`
|
||||||
|
|
||||||
UseGlobalBlockedServices bool `json:"use_global_blocked_services"`
|
UseGlobalBlockedServices bool `json:"use_global_blocked_services"`
|
||||||
BlockedServices []string `json:"blocked_services"`
|
BlockedServices []string `json:"blocked_services"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type clientSource uint
|
type clientSource uint
|
||||||
|
|
||||||
|
// Client sources
|
||||||
const (
|
const (
|
||||||
// Priority: etc/hosts > DHCP > ARP > rDNS
|
// Priority: etc/hosts > DHCP > ARP > rDNS > WHOIS
|
||||||
ClientSourceRDNS clientSource = 0 // from rDNS
|
ClientSourceWHOIS clientSource = iota // from WHOIS
|
||||||
ClientSourceDHCP clientSource = 1 // from DHCP
|
ClientSourceRDNS // from rDNS
|
||||||
ClientSourceARP clientSource = 2 // from 'arp -a'
|
ClientSourceDHCP // from DHCP
|
||||||
ClientSourceHostsFile clientSource = 3 // from /etc/hosts
|
ClientSourceARP // from 'arp -a'
|
||||||
|
ClientSourceHostsFile // from /etc/hosts
|
||||||
)
|
)
|
||||||
|
|
||||||
// ClientHost information
|
// ClientHost information
|
||||||
type ClientHost struct {
|
type ClientHost struct {
|
||||||
Host string
|
Host string
|
||||||
Source clientSource
|
Source clientSource
|
||||||
|
WhoisInfo [][]string // [[key,value], ...]
|
||||||
}
|
}
|
||||||
|
|
||||||
type clientsContainer struct {
|
type clientsContainer struct {
|
||||||
list map[string]*Client
|
list map[string]*Client // name -> client
|
||||||
ipIndex map[string]*Client
|
ipIndex map[string]*Client // IP -> client
|
||||||
ipHost map[string]ClientHost // IP -> Hostname
|
ipHost map[string]ClientHost // IP -> Hostname
|
||||||
lock sync.Mutex
|
lock sync.Mutex
|
||||||
}
|
}
|
||||||
|
@ -101,7 +107,7 @@ func (clients *clientsContainer) GetList() map[string]*Client {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exists checks if client with this IP already exists
|
// Exists checks if client with this IP already exists
|
||||||
func (clients *clientsContainer) Exists(ip string) bool {
|
func (clients *clientsContainer) Exists(ip string, source clientSource) bool {
|
||||||
clients.lock.Lock()
|
clients.lock.Lock()
|
||||||
defer clients.lock.Unlock()
|
defer clients.lock.Unlock()
|
||||||
|
|
||||||
|
@ -110,8 +116,14 @@ func (clients *clientsContainer) Exists(ip string) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
_, ok = clients.ipHost[ip]
|
ch, ok := clients.ipHost[ip]
|
||||||
return ok
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if source > ch.Source {
|
||||||
|
return false // we're going to overwrite this client's info with a stronger source
|
||||||
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find searches for a client by IP
|
// Find searches for a client by IP
|
||||||
|
@ -266,6 +278,31 @@ func (clients *clientsContainer) Update(name string, c Client) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetWhoisInfo - associate WHOIS information with a client
|
||||||
|
func (clients *clientsContainer) SetWhoisInfo(ip string, info [][]string) {
|
||||||
|
clients.lock.Lock()
|
||||||
|
defer clients.lock.Unlock()
|
||||||
|
|
||||||
|
c, ok := clients.ipIndex[ip]
|
||||||
|
if ok {
|
||||||
|
c.WhoisInfo = info
|
||||||
|
log.Debug("Clients: set WHOIS info for client %s: %v", c.Name, c.WhoisInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
ch, ok := clients.ipHost[ip]
|
||||||
|
if ok {
|
||||||
|
ch.WhoisInfo = info
|
||||||
|
log.Debug("Clients: set WHOIS info for auto-client %s: %v", ch.Host, ch.WhoisInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
ch = ClientHost{
|
||||||
|
Source: ClientSourceWHOIS,
|
||||||
|
}
|
||||||
|
ch.WhoisInfo = info
|
||||||
|
clients.ipHost[ip] = ch
|
||||||
|
log.Debug("Clients: set WHOIS info for auto-client with IP %s: %v", ip, ch.WhoisInfo)
|
||||||
|
}
|
||||||
|
|
||||||
// AddHost adds new IP -> Host pair
|
// AddHost adds new IP -> Host pair
|
||||||
// Use priority of the source (etc/hosts > ARP > rDNS)
|
// Use priority of the source (etc/hosts > ARP > rDNS)
|
||||||
// so we overwrite existing entries with an equal or higher priority
|
// so we overwrite existing entries with an equal or higher priority
|
||||||
|
@ -282,6 +319,7 @@ func (clients *clientsContainer) AddHost(ip, host string, source clientSource) (
|
||||||
clients.ipHost[ip] = ClientHost{
|
clients.ipHost[ip] = ClientHost{
|
||||||
Host: host,
|
Host: host,
|
||||||
Source: source,
|
Source: source,
|
||||||
|
WhoisInfo: c.WhoisInfo,
|
||||||
}
|
}
|
||||||
log.Tracef("'%s' -> '%s' [%d]", ip, host, len(clients.ipHost))
|
log.Tracef("'%s' -> '%s' [%d]", ip, host, len(clients.ipHost))
|
||||||
return true, nil
|
return true, nil
|
||||||
|
@ -386,6 +424,8 @@ type clientHostJSON struct {
|
||||||
IP string `json:"ip"`
|
IP string `json:"ip"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Source string `json:"source"`
|
Source string `json:"source"`
|
||||||
|
|
||||||
|
WhoisInfo map[string]interface{} `json:"whois_info"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type clientListJSON struct {
|
type clientListJSON struct {
|
||||||
|
@ -421,6 +461,11 @@ func handleGetClients(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cj.WhoisInfo = make(map[string]interface{})
|
||||||
|
for _, wi := range c.WhoisInfo {
|
||||||
|
cj.WhoisInfo[wi[0]] = wi[1]
|
||||||
|
}
|
||||||
|
|
||||||
data.Clients = append(data.Clients, cj)
|
data.Clients = append(data.Clients, cj)
|
||||||
}
|
}
|
||||||
for ip, ch := range config.clients.ipHost {
|
for ip, ch := range config.clients.ipHost {
|
||||||
|
@ -428,6 +473,7 @@ func handleGetClients(w http.ResponseWriter, r *http.Request) {
|
||||||
IP: ip,
|
IP: ip,
|
||||||
Name: ch.Host,
|
Name: ch.Host,
|
||||||
}
|
}
|
||||||
|
|
||||||
cj.Source = "etc/hosts"
|
cj.Source = "etc/hosts"
|
||||||
switch ch.Source {
|
switch ch.Source {
|
||||||
case ClientSourceDHCP:
|
case ClientSourceDHCP:
|
||||||
|
@ -436,7 +482,15 @@ func handleGetClients(w http.ResponseWriter, r *http.Request) {
|
||||||
cj.Source = "rDNS"
|
cj.Source = "rDNS"
|
||||||
case ClientSourceARP:
|
case ClientSourceARP:
|
||||||
cj.Source = "ARP"
|
cj.Source = "ARP"
|
||||||
|
case ClientSourceWHOIS:
|
||||||
|
cj.Source = "WHOIS"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cj.WhoisInfo = make(map[string]interface{})
|
||||||
|
for _, wi := range ch.WhoisInfo {
|
||||||
|
cj.WhoisInfo[wi[0]] = wi[1]
|
||||||
|
}
|
||||||
|
|
||||||
data.AutoClients = append(data.AutoClients, cj)
|
data.AutoClients = append(data.AutoClients, cj)
|
||||||
}
|
}
|
||||||
config.clients.lock.Unlock()
|
config.clients.lock.Unlock()
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
package home
|
package home
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
func TestClients(t *testing.T) {
|
func TestClients(t *testing.T) {
|
||||||
var c Client
|
var c Client
|
||||||
|
@ -61,15 +65,9 @@ func TestClients(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// get
|
// get
|
||||||
if clients.Exists("1.2.3.4") {
|
assert.True(t, !clients.Exists("1.2.3.4", ClientSourceHostsFile))
|
||||||
t.Fatalf("Exists")
|
assert.True(t, clients.Exists("1.1.1.1", ClientSourceHostsFile))
|
||||||
}
|
assert.True(t, clients.Exists("2.2.2.2", ClientSourceHostsFile))
|
||||||
if !clients.Exists("1.1.1.1") {
|
|
||||||
t.Fatalf("Exists #1")
|
|
||||||
}
|
|
||||||
if !clients.Exists("2.2.2.2") {
|
|
||||||
t.Fatalf("Exists #2")
|
|
||||||
}
|
|
||||||
|
|
||||||
// failed update - no such name
|
// failed update - no such name
|
||||||
c.IP = "1.2.3.0"
|
c.IP = "1.2.3.0"
|
||||||
|
@ -100,9 +98,7 @@ func TestClients(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// get after update
|
// get after update
|
||||||
if clients.Exists("1.1.1.1") || !clients.Exists("1.1.1.2") {
|
assert.True(t, !(clients.Exists("1.1.1.1", ClientSourceHostsFile) || !clients.Exists("1.1.1.2", ClientSourceHostsFile)))
|
||||||
t.Fatalf("Exists - get after update")
|
|
||||||
}
|
|
||||||
|
|
||||||
// failed remove - no such name
|
// failed remove - no such name
|
||||||
if clients.Del("client3") {
|
if clients.Del("client3") {
|
||||||
|
@ -110,9 +106,7 @@ func TestClients(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove
|
// remove
|
||||||
if !clients.Del("client1") || clients.Exists("1.1.1.2") {
|
assert.True(t, !(!clients.Del("client1") || clients.Exists("1.1.1.2", ClientSourceHostsFile)))
|
||||||
t.Fatalf("Del")
|
|
||||||
}
|
|
||||||
|
|
||||||
// add host client
|
// add host client
|
||||||
b, e = clients.AddHost("1.1.1.1", "host", ClientSourceARP)
|
b, e = clients.AddHost("1.1.1.1", "host", ClientSourceARP)
|
||||||
|
@ -139,7 +133,5 @@ func TestClients(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// get
|
// get
|
||||||
if !clients.Exists("1.1.1.1") {
|
assert.True(t, clients.Exists("1.1.1.1", ClientSourceHostsFile))
|
||||||
t.Fatalf("clientAddHost")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
71
home/dns.go
71
home/dns.go
|
@ -5,26 +5,20 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/dnsfilter"
|
"github.com/AdguardTeam/AdGuardHome/dnsfilter"
|
||||||
"github.com/AdguardTeam/AdGuardHome/dnsforward"
|
"github.com/AdguardTeam/AdGuardHome/dnsforward"
|
||||||
"github.com/AdguardTeam/AdGuardHome/querylog"
|
"github.com/AdguardTeam/AdGuardHome/querylog"
|
||||||
"github.com/AdguardTeam/AdGuardHome/stats"
|
"github.com/AdguardTeam/AdGuardHome/stats"
|
||||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
"github.com/joomcode/errorx"
|
"github.com/joomcode/errorx"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
type dnsContext struct {
|
type dnsContext struct {
|
||||||
rdnsChannel chan string // pass data from DNS request handling thread to rDNS thread
|
rdns *RDNS
|
||||||
// contains IP addresses of clients to be resolved by rDNS
|
whois *Whois
|
||||||
// if IP address couldn't be resolved, it stays here forever to prevent further attempts to resolve the same IP
|
|
||||||
rdnsIP map[string]bool
|
|
||||||
rdnsLock sync.Mutex // synchronize access to rdnsIP
|
|
||||||
upstream upstream.Upstream // Upstream object for our own DNS server
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// initDNSServer creates an instance of the dnsforward.Server
|
// initDNSServer creates an instance of the dnsforward.Server
|
||||||
|
@ -55,7 +49,8 @@ func initDNSServer(baseDir string) {
|
||||||
config.auth = InitAuth(sessFilename, config.Users)
|
config.auth = InitAuth(sessFilename, config.Users)
|
||||||
config.Users = nil
|
config.Users = nil
|
||||||
|
|
||||||
initRDNS()
|
config.dnsctx.rdns = InitRDNS(&config.clients)
|
||||||
|
config.dnsctx.whois = initWhois(&config.clients)
|
||||||
initFiltering()
|
initFiltering()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,6 +58,59 @@ func isRunning() bool {
|
||||||
return config.dnsServer != nil && config.dnsServer.IsRunning()
|
return config.dnsServer != nil && config.dnsServer.IsRunning()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return TRUE if IP is within public Internet IP range
|
||||||
|
func isPublicIP(ip net.IP) bool {
|
||||||
|
ip4 := ip.To4()
|
||||||
|
if ip4 != nil {
|
||||||
|
switch ip4[0] {
|
||||||
|
case 0:
|
||||||
|
return false //software
|
||||||
|
case 10:
|
||||||
|
return false //private network
|
||||||
|
case 127:
|
||||||
|
return false //loopback
|
||||||
|
case 169:
|
||||||
|
if ip4[1] == 254 {
|
||||||
|
return false //link-local
|
||||||
|
}
|
||||||
|
case 172:
|
||||||
|
if ip4[1] >= 16 && ip4[1] <= 31 {
|
||||||
|
return false //private network
|
||||||
|
}
|
||||||
|
case 192:
|
||||||
|
if (ip4[1] == 0 && ip4[2] == 0) || //private network
|
||||||
|
(ip4[1] == 0 && ip4[2] == 2) || //documentation
|
||||||
|
(ip4[1] == 88 && ip4[2] == 99) || //reserved
|
||||||
|
(ip4[1] == 168) { //private network
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case 198:
|
||||||
|
if (ip4[1] == 18 || ip4[2] == 19) || //private network
|
||||||
|
(ip4[1] == 51 || ip4[2] == 100) { //documentation
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case 203:
|
||||||
|
if ip4[1] == 0 && ip4[2] == 113 { //documentation
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case 224:
|
||||||
|
if ip4[1] == 0 && ip4[2] == 0 { //multicast
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case 255:
|
||||||
|
if ip4[1] == 255 && ip4[2] == 255 && ip4[3] == 255 { //subnet
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ip.IsLoopback() || ip.IsLinkLocalMulticast() || ip.IsLinkLocalUnicast() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func onDNSRequest(d *proxy.DNSContext) {
|
func onDNSRequest(d *proxy.DNSContext) {
|
||||||
qType := d.Req.Question[0].Qtype
|
qType := d.Req.Question[0].Qtype
|
||||||
if qType != dns.TypeA && qType != dns.TypeAAAA {
|
if qType != dns.TypeA && qType != dns.TypeAAAA {
|
||||||
|
@ -77,7 +125,10 @@ func onDNSRequest(d *proxy.DNSContext) {
|
||||||
|
|
||||||
ipAddr := net.ParseIP(ip)
|
ipAddr := net.ParseIP(ip)
|
||||||
if !ipAddr.IsLoopback() {
|
if !ipAddr.IsLoopback() {
|
||||||
beginAsyncRDNS(ip)
|
config.dnsctx.rdns.Begin(ip)
|
||||||
|
}
|
||||||
|
if isPublicIP(ipAddr) {
|
||||||
|
config.dnsctx.whois.Begin(ip)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
func TestResolveRDNS(t *testing.T) {
|
func TestResolveRDNS(t *testing.T) {
|
||||||
config.DNS.BindHost = "1.1.1.1"
|
config.DNS.BindHost = "1.1.1.1"
|
||||||
initDNSServer(".")
|
initDNSServer(".")
|
||||||
if r := resolveRDNS("1.1.1.1"); r != "one.one.one.one" {
|
if r := config.dnsctx.rdns.resolve("1.1.1.1"); r != "one.one.one.one" {
|
||||||
t.Errorf("resolveRDNS(): %s", r)
|
t.Errorf("resolveRDNS(): %s", r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
62
home/rdns.go
62
home/rdns.go
|
@ -3,6 +3,7 @@ package home
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||||
|
@ -14,7 +15,21 @@ const (
|
||||||
rdnsTimeout = 3 * time.Second // max time to wait for rDNS response
|
rdnsTimeout = 3 * time.Second // max time to wait for rDNS response
|
||||||
)
|
)
|
||||||
|
|
||||||
func initRDNS() {
|
// RDNS - module context
|
||||||
|
type RDNS struct {
|
||||||
|
clients *clientsContainer
|
||||||
|
ipChannel chan string // pass data from DNS request handling thread to rDNS thread
|
||||||
|
// contains IP addresses of clients to be resolved by rDNS
|
||||||
|
// if IP address couldn't be resolved, it stays here forever to prevent further attempts to resolve the same IP
|
||||||
|
ips map[string]bool
|
||||||
|
lock sync.Mutex // synchronize access to 'ips'
|
||||||
|
upstream upstream.Upstream // Upstream object for our own DNS server
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitRDNS - create module context
|
||||||
|
func InitRDNS(clients *clientsContainer) *RDNS {
|
||||||
|
r := RDNS{}
|
||||||
|
r.clients = clients
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
bindhost := config.DNS.BindHost
|
bindhost := config.DNS.BindHost
|
||||||
|
@ -26,35 +41,36 @@ func initRDNS() {
|
||||||
opts := upstream.Options{
|
opts := upstream.Options{
|
||||||
Timeout: rdnsTimeout,
|
Timeout: rdnsTimeout,
|
||||||
}
|
}
|
||||||
config.dnsctx.upstream, err = upstream.AddressToUpstream(resolverAddress, opts)
|
r.upstream, err = upstream.AddressToUpstream(resolverAddress, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("upstream.AddressToUpstream: %s", err)
|
log.Error("upstream.AddressToUpstream: %s", err)
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
config.dnsctx.rdnsIP = make(map[string]bool)
|
r.ips = make(map[string]bool)
|
||||||
config.dnsctx.rdnsChannel = make(chan string, 256)
|
r.ipChannel = make(chan string, 256)
|
||||||
go asyncRDNSLoop()
|
go r.workerLoop()
|
||||||
|
return &r
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add IP address to the rDNS queue
|
// Begin - add IP address to rDNS queue
|
||||||
func beginAsyncRDNS(ip string) {
|
func (r *RDNS) Begin(ip string) {
|
||||||
if config.clients.Exists(ip) {
|
if r.clients.Exists(ip, ClientSourceRDNS) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// add IP to rdnsIP, if not exists
|
// add IP to ips, if not exists
|
||||||
config.dnsctx.rdnsLock.Lock()
|
r.lock.Lock()
|
||||||
defer config.dnsctx.rdnsLock.Unlock()
|
defer r.lock.Unlock()
|
||||||
_, ok := config.dnsctx.rdnsIP[ip]
|
_, ok := r.ips[ip]
|
||||||
if ok {
|
if ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
config.dnsctx.rdnsIP[ip] = true
|
r.ips[ip] = true
|
||||||
|
|
||||||
log.Tracef("Adding %s for rDNS resolve", ip)
|
log.Tracef("Adding %s for rDNS resolve", ip)
|
||||||
select {
|
select {
|
||||||
case config.dnsctx.rdnsChannel <- ip:
|
case r.ipChannel <- ip:
|
||||||
//
|
//
|
||||||
default:
|
default:
|
||||||
log.Tracef("rDNS queue is full")
|
log.Tracef("rDNS queue is full")
|
||||||
|
@ -62,7 +78,7 @@ func beginAsyncRDNS(ip string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use rDNS to get hostname by IP address
|
// Use rDNS to get hostname by IP address
|
||||||
func resolveRDNS(ip string) string {
|
func (r *RDNS) resolve(ip string) string {
|
||||||
log.Tracef("Resolving host for %s", ip)
|
log.Tracef("Resolving host for %s", ip)
|
||||||
|
|
||||||
req := dns.Msg{}
|
req := dns.Msg{}
|
||||||
|
@ -81,7 +97,7 @@ func resolveRDNS(ip string) string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := config.dnsctx.upstream.Exchange(&req)
|
resp, err := r.upstream.Exchange(&req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug("Error while making an rDNS lookup for %s: %s", ip, err)
|
log.Debug("Error while making an rDNS lookup for %s: %s", ip, err)
|
||||||
return ""
|
return ""
|
||||||
|
@ -106,19 +122,19 @@ func resolveRDNS(ip string) string {
|
||||||
|
|
||||||
// Wait for a signal and then synchronously resolve hostname by IP address
|
// Wait for a signal and then synchronously resolve hostname by IP address
|
||||||
// Add the hostname:IP pair to "Clients" array
|
// Add the hostname:IP pair to "Clients" array
|
||||||
func asyncRDNSLoop() {
|
func (r *RDNS) workerLoop() {
|
||||||
for {
|
for {
|
||||||
var ip string
|
var ip string
|
||||||
ip = <-config.dnsctx.rdnsChannel
|
ip = <-r.ipChannel
|
||||||
|
|
||||||
host := resolveRDNS(ip)
|
host := r.resolve(ip)
|
||||||
if len(host) == 0 {
|
if len(host) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
config.dnsctx.rdnsLock.Lock()
|
r.lock.Lock()
|
||||||
delete(config.dnsctx.rdnsIP, ip)
|
delete(r.ips, ip)
|
||||||
config.dnsctx.rdnsLock.Unlock()
|
r.lock.Unlock()
|
||||||
|
|
||||||
_, _ = config.clients.AddHost(ip, host, ClientSourceRDNS)
|
_, _ = config.clients.AddHost(ip, host, ClientSourceRDNS)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,118 @@
|
||||||
|
package home
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/golibs/log"
|
||||||
|
whois "github.com/likexian/whois-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Whois - module context
|
||||||
|
type Whois struct {
|
||||||
|
clients *clientsContainer
|
||||||
|
ips map[string]bool
|
||||||
|
lock sync.Mutex
|
||||||
|
ipChan chan string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create module context
|
||||||
|
func initWhois(clients *clientsContainer) *Whois {
|
||||||
|
w := Whois{}
|
||||||
|
w.clients = clients
|
||||||
|
w.ips = make(map[string]bool)
|
||||||
|
w.ipChan = make(chan string, 255)
|
||||||
|
go w.workerLoop()
|
||||||
|
return &w
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse plain-text data from the response
|
||||||
|
func whoisParse(data string) map[string]string {
|
||||||
|
m := map[string]string{}
|
||||||
|
lines := strings.Split(data, "\n")
|
||||||
|
for _, ln := range lines {
|
||||||
|
ln = strings.TrimSpace(ln)
|
||||||
|
|
||||||
|
if len(ln) == 0 || ln[0] == '#' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
kv := strings.SplitN(ln, ":", 2)
|
||||||
|
if len(kv) != 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
k := strings.TrimSpace(kv[0])
|
||||||
|
k = strings.ToLower(k)
|
||||||
|
v := strings.TrimSpace(kv[1])
|
||||||
|
|
||||||
|
if k == "orgname" || k == "org-name" {
|
||||||
|
m["orgname"] = v
|
||||||
|
} else if k == "city" {
|
||||||
|
m["city"] = v
|
||||||
|
} else if k == "country" {
|
||||||
|
m["country"] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request WHOIS information
|
||||||
|
func whoisProcess(ip string) [][]string {
|
||||||
|
data := [][]string{}
|
||||||
|
resp, err := whois.Whois(ip)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("Whois: error: %s IP:%s", err, ip)
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug("Whois: IP:%s response: %d bytes", ip, len(resp))
|
||||||
|
|
||||||
|
m := whoisParse(resp)
|
||||||
|
|
||||||
|
keys := []string{"orgname", "country", "city"}
|
||||||
|
for _, k := range keys {
|
||||||
|
v, found := m[k]
|
||||||
|
if !found {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
pair := []string{k, v}
|
||||||
|
data = append(data, pair)
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
// Begin - begin requesting WHOIS info
|
||||||
|
func (w *Whois) Begin(ip string) {
|
||||||
|
w.lock.Lock()
|
||||||
|
_, found := w.ips[ip]
|
||||||
|
if found {
|
||||||
|
w.lock.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.ips[ip] = true
|
||||||
|
w.lock.Unlock()
|
||||||
|
|
||||||
|
log.Debug("Whois: adding %s", ip)
|
||||||
|
select {
|
||||||
|
case w.ipChan <- ip:
|
||||||
|
//
|
||||||
|
default:
|
||||||
|
log.Debug("Whois: queue is full")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get IP address from channel; get WHOIS info; associate info with a client
|
||||||
|
func (w *Whois) workerLoop() {
|
||||||
|
for {
|
||||||
|
var ip string
|
||||||
|
ip = <-w.ipChan
|
||||||
|
|
||||||
|
info := whoisProcess(ip)
|
||||||
|
if len(info) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
w.clients.SetWhoisInfo(ip, info)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
package home
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
whois "github.com/likexian/whois-go"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWhois(t *testing.T) {
|
||||||
|
resp, err := whois.Whois("8.8.8.8")
|
||||||
|
assert.True(t, err == nil)
|
||||||
|
assert.True(t, strings.Index(resp, "OrgName: Google LLC") != -1)
|
||||||
|
assert.True(t, strings.Index(resp, "City: Mountain View") != -1)
|
||||||
|
assert.True(t, strings.Index(resp, "Country: US") != -1)
|
||||||
|
m := whoisParse(resp)
|
||||||
|
assert.True(t, m["orgname"] == "Google LLC")
|
||||||
|
assert.True(t, m["country"] == "US")
|
||||||
|
assert.True(t, m["city"] == "Mountain View")
|
||||||
|
}
|
Loading…
Reference in New Issue