Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Оптимизация работы с IP в списке #209

Closed
AltGrF13 opened this issue Nov 8, 2024 · 4 comments
Closed

Оптимизация работы с IP в списке #209

AltGrF13 opened this issue Nov 8, 2024 · 4 comments

Comments

@AltGrF13
Copy link
Contributor

AltGrF13 commented Nov 8, 2024

Можно костылить (как сейчас поступают в чате) внешними решениями, но вся логика работы отлично ложится и в сам КВАС. Делая его более универсальным инструментом, не требующим таких обвязок.

Почему подумать об IP стоит отдельно?

  1. Количество в файле, обычно, гораздо больше; чем доменов. Больше фокус на оптимизации.
  2. Логика работы с ними проще: не нужна сложная фильтрация, не требуется перезапуск DNS (влияют только на ipset и файл списка, не изменяют /opt/etc/dnsmasq.d/kvas.dnsmasq).
  3. Востребованность последнее время.
@AltGrF13
Copy link
Contributor Author

AltGrF13 commented Nov 8, 2024

Сначала очистка. В bin/libs/vpn:cmd_clear_list:

  1. Оптимизирована проверка внутреннего /opt/etc/kvas.list на пустоту.
  2. Добавлен выход по q (при ошибочном вводе он пишет, что такая опция должна работать).
  3. Добавлена поддержка форматов. Если не указано ничего или all, то работает предыдущий код без изменений. Если kvas clear ip, то работает быстрый код для очистки из списка и ipset только IP.

Для чего? Пришли обновлённые списки IP, обращения к которым необходимо защитить. Просто вызываешь kvas clear ip и kvas import.
Без этих изменений пришлось бы перегружать в список и новые IP, и старые домены. При этом "тяжёлыми" ветками кода.

get_regexp_ip_or_range() {
	echo "^(${IP_FILTER}|${IP_FILTER}-${IP_FILTER}|${IP_FILTER}/[0-9]{1,2})$"
}

cmd_clear_list() {
	if ! [ -f "${KVAS_LIST_FILE}" ]; then
		error 'Списочный файл не существует!'
		exit 1
	fi
	#if [ "$(rec_in_file "${KVAS_LIST_FILE}")" = '0' ]; then
	# Предыдущий код закомментирован выше. Сортировать и чистить /opt/etc/kvas.list 
	# (который итак упорядочен и отфильтрован, это же не пользовательские данные) 
	# только для проверки на пустоту — крайне затратная операция при большом размере.
	# Условность для ускорения: если есть IP или доменное имя, то должна быть точка.
	if ! grep -Fq '.' "${KVAS_LIST_FILE}"; then
		error 'Защищённый список уже пуст.'
		exit 1
	fi

	if [ -z "${1}" ]; then
		local clear_format='all'
	else
		local clear_format="${1}"
	fi
	if [ "${clear_format}" = 'all' ]; then
		local submessage='целиком'
	elif [ "${clear_format}" = 'ip' ]; then
		local submessage='от IP'
	else
		error 'Формат очистки неизвестен.'
		exit 3
	fi

	while true; do
		ready "Защищённый список будет очищен ${submessage}. Уверены?" && read -r yn
		case ${yn} in
			[Yy]*)
				print_line
				exit_when_no_internet_or_vpn || exit 2

				submessage="Очищаем защищённый список ${submessage}..."
				if [ "${clear_format}" = 'all' ]; then
					# изначальный код
					ready "${submessage}"

					mv -f "${KVAS_LIST_FILE}" "${KVAS_LIST_FILE_BACKUP}"
					rm -f "${ADGUARD_IPSET_FILE}"
					touch "${KVAS_LIST_FILE}"

					cmd_kvas_init 'update' &> /dev/null
					[ $? = 0 ] && when_ok 'ОЧИЩЕН' || when_bad 'ОШИБКА'

					print_line
					warning "Предыдущий защищённый список был сохранён в файл ${KVAS_LIST_FILE_BACKUP}"
				elif [ "${clear_format}" = 'ip' ]; then
					echo -n "${submessage}"
					local tmp_file='/opt/tmp/without_ip.txt'
					>"${tmp_file}"
					local regexp_ip=$(get_regexp_ip_or_range)
					local readed_count=0
					local deleted_count=0
					local saved_count=0
					local lines_cache=''
					while read line; do
						# скорее всего, лишний блок
						if [ -z "${line}" ]; then
							continue
						fi

						# заменитель шкалы прогресса, каждые 50 записей
						readed_count=$((readed_count + 1))
						if [ "$(( ${readed_count} % 50 ))" -eq 0 ]; then
							echo -n '.'
						fi

						# IP вычищаем из ipset и считаем
						if echo "${line}" | grep -qE -- "${regexp_ip}"; then
							ipset -exist del "${IPSET_TABLE_NAME}" "${line}"
							deleted_count=$((deleted_count + 1))
							continue
						fi

						# неIP в новый файл не построчно, а блоками
						lines_cache="${lines_cache}${line}"$'\n'
						saved_count=$((saved_count + 1))
						if [ "$(( ${saved_count} % 50 ))" -eq 0 ]; then
							echo -n "${lines_cache}" >>"${tmp_file}"
							lines_cache=''
						fi
					done <"${KVAS_LIST_FILE}"
					echo -n 'удалено '
					if [ -n "${lines_cache}" ]; then
						echo -n "${lines_cache}" >>"${tmp_file}"
					fi
					echo "${deleted_count}."

					echo -n 'Предыдущий защищённый список сохранён в '
					mv -f "${KVAS_LIST_FILE}" "${KVAS_LIST_FILE_BACKUP}"
					mv "${tmp_file}" "${KVAS_LIST_FILE}"
					echo "${KVAS_LIST_FILE_BACKUP}"

					# из списка были вычищены лишь IP, не требуются
					# refresh_dnsmasq_ipset_table или refresh_adguard_ipset_table
					# и рестарты
				fi

				break
				;;
			[NnQq]*)
				break
				;;
			*)
				please_repeat
				;;
		esac
	done
}

@AltGrF13
Copy link
Contributor Author

AltGrF13 commented Nov 8, 2024

При добавлении делаем проверку, не чистый ли это файл с IP (сама проверка пролетает мгновенно даже на файле с 1000 записей). И если это так, импорт идёт по упрощенному коду. Сам ваш код импорта не изменялся. Только сообщения об ошибках в начале сделаны в едином стиле.

cmd_import_hosts() {
	user_list="${1}"
	if [ -z "${user_list}" ]; then
		error 'Не задан файл для импорта!'
		error 'Укажите его вторым аргументом при запуске.'
		exit 1
	fi
	if ! [ -f "${user_list}" ]; then
		error "Не найден файл ${user_list}!"
		error 'Проверьте верность написания пути и его имени.'
		exit 1
	fi
	# Условность для ускорения: если есть IP или доменное имя, то должна быть и точка
	if ! grep -Fq '.' "${user_list}"; then
		error "Не найдены IP или имена доменов в файле ${user_list}!"
		error 'Заполните его данными.'
		exit 1
	fi

	# Выходим из функции при отсутствии интернет-соединения или VPN
	exit_when_no_internet_or_vpn || exit 2

	regexp_ip=$(get_regexp_ip_or_range)
	if ! grep -vEq -- "${regexp_ip}" "${user_list}"; then
		# альтернативный оптимизированный код для списка IP
		# благодаря верхней проверке, мы знаем, что комментариев и пустых строк уже нет
		# проверку на уникальность сделаем на лету, сортировку в финале

		#sort -o не поддерживается, поэтому через временный файл
		local tmp_file='/opt/tmp/sorted_list.txt'
		cp -f "${KVAS_LIST_FILE}" "${tmp_file}"

		echo -n 'Импортируем IP в список защиты..'
		local readed_count=0
		local added_count=0
		local lines_cache=''
		while read line; do
			# заменитель шкалы прогресса, каждые 50 записей
			readed_count=$((readed_count + 1))
			if [ "$(( ${readed_count} % 50 ))" -eq 0 ]; then
				echo -n '.'
			fi

			# уже добавлен, уникализация
			if grep -Fq -- "${line}" "${tmp_file}"; then
				continue
			fi

			# IP не умеют самовосстанавливаться, ttl бесконечность
			ipset -exist add "${IPSET_TABLE_NAME}" "${line}" timeout 0

			lines_cache="${lines_cache}${line}"$'\n'
			added_count=$((added_count + 1))
			if [ "$(( ${added_count} % 50 ))" -eq 0 ]; then
				# пишем в файл не построчно, а блоками
				echo -n "${lines_cache}" >>"${tmp_file}"
				lines_cache=''
			fi
		done <"${user_list}"
		echo -n 'добавлено '
		if [ -n "${lines_cache}" ]; then
			echo -n "${lines_cache}" >>"${tmp_file}"
		fi
		echo "${added_count}."

		echo -n 'Предыдущий защищённый список сохранён в '
		mv -f "${KVAS_LIST_FILE}" "${KVAS_LIST_FILE_BACKUP}"
		echo "${KVAS_LIST_FILE_BACKUP}"

		echo -n 'Сортируем список защиты...'
		sort "${tmp_file}" >"${KVAS_LIST_FILE}"
		rm -f "${tmp_file}"
		echo 'сделано.'

		# в список были добавлены лишь IP, не требуются
		# refresh_dnsmasq_ipset_table или refresh_adguard_ipset_table
		# и рестарты
		return
	fi

	# изначальный код без изменений
	ready 'Импортируем хосты в список защищаемых доменов...'
	clear_file_content "${user_list}"
	#clear_file_content "${KVAS_LIST_FILE}"

	hosts_to_add_ubl=''
	hosts_repeated_ubl=''
	hosts_not_added=''
	hosts_errors=''
	while read -r line || [ -n "${line}" ]; do
		# удаляем из строки комментарии - все что встречается после символа # и сам символ
		host=$(echo "${line}" | sed 's/#.*$//g' | tr -s ' ')
		#  пропускаем пустые строки и строки с комментариями
		[ -z "${host}" ] && continue
		#  пропускаем строки с комментариями
		[ "${host:0:1}" = "#" ] && continue

		case "$(cmd_add_one_host "${host}" "no" "import")" in
		0) hosts_not_added="${hosts_not_added}${host}\n" ;;
		1) hosts_repeated_bl="${hosts_repeated_bl}${host}\n" ;;
		+) hosts_to_add_ubl="${hosts_to_add_ubl}${host}\n" ;;
		2) hosts_errors="${hosts_errors}${host}\n";;
		*) ;;
		esac
	done < "${user_list}"

	# добавляем хосты в файл списочный
	_hosts_to_add_ubl=$(echo -e "${hosts_to_add_ubl}" | sed '/^$/d; /^[-+.]/!s/\(.*\)/\1/')
	echo -e "${_hosts_to_add_ubl}" >> "${KVAS_LIST_FILE}"

	# если включен блокировщик рекламы
	if cmd_ads_status | grep -q ВКЛЮЧЕН ; then
		ads_list_hosts_update &> /dev/null
	fi

	cmd_kvas_init  "update" &> /dev/null
	[ $? = 0 ] && when_ok "УСПЕШНО" || when_bad "ОШИБКА"

	num_not_added=$(rec_in_var "${hosts_not_added}")
	num_repeated_ubl=$(rec_in_var "${hosts_repeated_ubl}")
	num_to_add_ubl=$(rec_in_var "${hosts_to_add_ubl}")
	num_errors=$(rec_in_var "${hosts_errors}")

	num_errors=$((num_not_added + num_repeated_ubl + num_errors))
	tab="    "
	# print_line
	if [ "${num_to_add_ubl}" -gt 0 ]; then
		# :
		#warning "Новых записей добавлено не было!"
	# else
		warning "В защищенный список было добавлено ${YELLOW}${num_to_add_ubl}${GREEN} новых домена/ов:"
		print_line
		if [ "${num_to_add_ubl}" -gt 0 ]; then
			warning "${tab}Список добавленных доменов:"
			print_line
			echo -e "${hosts_to_add_ubl}" | sed '/^$/d; /^[-]/!s/\(.*\)/'"${tab}${tab}"'\1/g'
		fi
	fi
	if [ "${num_errors}" -gt 0 ]; then
		echo
		error "В ходе импорта возникли ошибки ${YELLOW}${num_errors}${RED} шт.:"
		print_line
		if [ "${num_not_added}" -gt 0 ]; then
			error "${tab}Проблем в написании или недоступности доменов выявлено ${YELLOW}${num_not_added}${RED} шт."
			print_line
			echo -e "${hosts_not_added}" | sed 's/^$/'"$(print_line)"'/; /^[-]/!s/\(.*\)/'"${tab}${tab}"'\1/g'
		fi
		if [ "${num_repeated_ubl}" -gt 0 ]; then
			error "${tab}Домены ниже уже присутствуют в защищенном списке [${YELLOW}${num_repeated_ubl}${RED} шт.]"
			print_line
			echo -e "${hosts_repeated_ubl}" | sed 's/^$/'"$(print_line)"'/; /^[-]/!s/\(.*\)/'"${tab}${tab}"'\1/g'
		fi
	fi
}

@qzeleza
Copy link
Owner

qzeleza commented Nov 8, 2024

Благодарю, добавил в бету 7

@AltGrF13
Copy link
Contributor Author

AltGrF13 commented Nov 8, 2024

Тест

Данные: 40 доменов, 640 IP, медленная флешка.

Загрузка 640 IP к имеющимся 40 доменам: 14 секунд против 9:53 общим методом.
Очистка списка: 640 IP 15 секунд, 640 IP + 40 доменов 7 секунд. Общий метод очистки быстрее нового, но после него нужно загружать 40 доменов обратно, это ещё 49 секунд.

Итого операция «мне пришёл новый список IP» занимает оптимизированными методами 15+14=29 секунд, обычными общими же 7+49+9:53=10:49. В 20 раз быстрее на конкретной (узкой) операции.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants