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; } };