From 946bda37a3bb06d1483c1af0c3083844c9602e58 Mon Sep 17 00:00:00 2001 From: Andrey Meshkov Date: Fri, 24 Jul 2020 13:45:46 +0300 Subject: [PATCH] - (ui): improved client access check performance This is still not ideal and if the disallowed clients list is huge enough, the slowdown is considerable. But it's at least x10 or x100 times faster than it was. Closes: #1920 --- Makefile | 4 +- client/src/helpers/constants.js | 44 ---------- client/src/helpers/helpers.js | 140 ++++++++++++++++++++------------ 3 files changed, 91 insertions(+), 97 deletions(-) diff --git a/Makefile b/Makefile index 2a496297..5c546857 100644 --- a/Makefile +++ b/Makefile @@ -134,7 +134,9 @@ lint-go: golangci-lint run test: - @echo Running unit-tests + @echo Running JS unit-tests + npm run test --prefix client + @echo Running Go unit-tests go test -race -v -bench=. -coverprofile=coverage.txt -covermode=atomic ./... ci: dependencies client test diff --git a/client/src/helpers/constants.js b/client/src/helpers/constants.js index aa8d3291..86b2bacb 100644 --- a/client/src/helpers/constants.js +++ b/client/src/helpers/constants.js @@ -288,50 +288,6 @@ export const WHOIS_ICONS = { descr: '', }; -export const DNS_RECORD_TYPES = [ - 'A', - 'AAAA', - 'AFSDB', - 'APL', - 'CAA', - 'CDNSKEY', - 'CDS', - 'CERT', - 'CNAME', - 'CSYNC', - 'DHCID', - 'DLV', - 'DNAME', - 'DNSKEY', - 'DS', - 'HIP', - 'IPSECKEY', - 'KEY', - 'KX', - 'LOC', - 'MX', - 'NAPTR', - 'NS', - 'NSEC', - 'NSEC3', - 'NSEC3PARAM', - 'OPENPGPKEY', - 'PTR', - 'RRSIG', - 'RP', - 'SIG', - 'SMIMEA', - 'SOA', - 'SRV', - 'SSHFP', - 'TA', - 'TKEY', - 'TLSA', - 'TSIG', - 'TXT', - 'URI', -]; - export const DEFAULT_LOGS_FILTER = { search: '', response_status: '', diff --git a/client/src/helpers/helpers.js b/client/src/helpers/helpers.js index b7bfd142..2ea451e4 100644 --- a/client/src/helpers/helpers.js +++ b/client/src/helpers/helpers.js @@ -5,7 +5,6 @@ import subHours from 'date-fns/sub_hours'; import addHours from 'date-fns/add_hours'; import addDays from 'date-fns/add_days'; import subDays from 'date-fns/sub_days'; -import isSameDay from 'date-fns/is_same_day'; import round from 'lodash/round'; import axios from 'axios'; import i18n from 'i18next'; @@ -20,7 +19,6 @@ import { DEFAULT_LANGUAGE, DEFAULT_TIME_FORMAT, DETAILED_DATE_FORMAT_OPTIONS, - DNS_RECORD_TYPES, FILTERED, FILTERED_STATUS, IP_MATCH_LIST_STATUS, @@ -31,6 +29,7 @@ import { /** * @param time {string} The time to format + * @param options {string} * @returns {string} Returns the time in the format HH:mm:ss */ export const formatTime = (time, options = DEFAULT_TIME_FORMAT) => { @@ -60,12 +59,6 @@ export const formatDetailedDateTime = (dateTime) => formatDateTime( dateTime, DETAILED_DATE_FORMAT_OPTIONS, ); -/** - * @param date {string} - * @returns {boolean} - */ -export const isToday = (date) => isSameDay(new Date(date), new Date()); - export const normalizeLogs = (logs) => logs.map((log) => { const { answer, @@ -351,39 +344,6 @@ export const normalizeTopClients = (topClients) => topClients.reduce( }, ); -export const getClientInfo = (clients, ip) => { - const client = clients - .find((item) => item.ip_addrs?.find((clientIp) => clientIp === ip)); - - if (!client) { - return ''; - } - - const { name, whois_info } = client; - const whois = Object.keys(whois_info).length > 0 ? whois_info : ''; - - return { - name, - whois, - }; -}; - -export const getAutoClientInfo = (clients, ip) => { - const client = clients.find((item) => ip === item.ip); - - if (!client) { - return ''; - } - - const { name, whois_info } = client; - const whois = Object.keys(whois_info).length > 0 ? whois_info : ''; - - return { - name, - whois, - }; -}; - export const sortClients = (clients) => { const compare = (a, b) => { const nameA = a.name.toUpperCase(); @@ -443,8 +403,6 @@ export const normalizeWhois = (whois) => { return whois; }; -export const isValidQuestionType = (type) => type && DNS_RECORD_TYPES.includes(type.toUpperCase()); - export const getPathWithQueryString = (path, params) => { const searchParams = new URLSearchParams(params); @@ -542,10 +500,10 @@ export const getMap = (arr, key, value) => arr.reduce((acc, curr) => { /** * @param parsedIp {object} ipaddr.js IPv4 or IPv6 object - * @param cidr {array} ipaddr.js CIDR array + * @param parsedCidr {array} ipaddr.js CIDR array * @returns {boolean} */ -export const isIpMatchCidr = (parsedIp, parsedCidr) => { +const isIpMatchCidr = (parsedIp, parsedCidr) => { try { const cidrIpVersion = parsedCidr[0].kind(); const ipVersion = parsedIp.kind(); @@ -556,6 +514,75 @@ export const isIpMatchCidr = (parsedIp, parsedCidr) => { } }; +/** + * The purpose of this method is to quickly check + * if this IP can possibly be in the specified CIDR range. + * + * @param ip {string} + * @param listItem {string} + * @returns {boolean} + */ +const isIpQuickMatchCIDR = (ip, listItem) => { + const ipv6 = ip.indexOf(':') !== -1; + const cidrIpv6 = listItem.indexOf(':') !== -1; + if (ipv6 !== cidrIpv6) { + // CIDR is for a different IP type + return false; + } + + if (cidrIpv6) { + // We don't do quick check for IPv6 addresses + return true; + } + + const idx = listItem.indexOf('/'); + if (idx === -1) { + // Not a CIDR, return false immediately + return false; + } + + const cidrIp = listItem.substring(0, idx); + const cidrRange = parseInt(listItem.substring(idx + 1), 10); + if (Number.isNaN(cidrRange)) { + // Not a valid CIDR + return false; + } + + const parts = cidrIp.split('.'); + if (parts.length !== 4) { + // Invalid IP, return immediately + return false; + } + + // Now depending on the range we check if the IP can possibly be in that range + if (cidrRange < 8) { + // Use the slow approach + return true; + } + + if (cidrRange < 16) { + // Check the first part + // Example: 0.0.0.0/8 matches 0.*.*.* + return ip.indexOf(`${parts[0]}.`) === 0; + } + + if (cidrRange < 24) { + // Check the first two parts + // Example: 0.0.0.0/16 matches 0.0.*.* + return ip.indexOf(`${parts[0]}.${parts[1]}.`) === 0; + } + + if (cidrRange <= 32) { + // Check the first two parts + // Example: 0.0.0.0/16 matches 0.0.*.* + return ip.indexOf(`${parts[0]}.${parts[1]}.${parts[2]}.`) === 0; + } + + // range for IPv4 CIDR cannot be more than 32 + // no need to check further, this CIDR is invalid + return false; +}; + /** * @param ip {string} * @param list {string} @@ -573,20 +600,29 @@ export const getIpMatchListStatus = (ip, list) => { for (let i = 0; i < listArr.length; i += 1) { const listItem = listArr[i]; - const parsedIp = ipaddr.parse(ip); - const isItemAnIp = ipaddr.isValid(listItem); - const parsedItem = isItemAnIp ? ipaddr.parse(listItem) : ipaddr.parseCIDR(listItem); - - if (isItemAnIp && parsedIp.toString() === parsedItem.toString()) { + if (ip === listItem.trim()) { return IP_MATCH_LIST_STATUS.EXACT; } - if (!isItemAnIp && isIpMatchCidr(parsedIp, parsedItem)) { - return IP_MATCH_LIST_STATUS.CIDR; + // Using ipaddr.js is quite slow so we first do a quick check + // to see if it's possible that this IP may be in the specified CIDR range + if (isIpQuickMatchCIDR(ip, listItem)) { + const parsedIp = ipaddr.parse(ip); + const isItemAnIp = ipaddr.isValid(listItem); + const parsedItem = isItemAnIp ? ipaddr.parse(listItem) : ipaddr.parseCIDR(listItem); + + if (isItemAnIp && parsedIp.toString() === parsedItem.toString()) { + return IP_MATCH_LIST_STATUS.EXACT; + } + + if (!isItemAnIp && isIpMatchCidr(parsedIp, parsedItem)) { + return IP_MATCH_LIST_STATUS.CIDR; + } } } return IP_MATCH_LIST_STATUS.NOT_FOUND; } catch (e) { + console.error(e); return IP_MATCH_LIST_STATUS.NOT_FOUND; } };