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

Нет нужного VPN/WireGuard #183

Closed
AltGrF13 opened this issue Oct 19, 2024 · 4 comments
Closed

Нет нужного VPN/WireGuard #183

AltGrF13 opened this issue Oct 19, 2024 · 4 comments

Comments

@AltGrF13
Copy link
Contributor

AltGrF13 commented Oct 19, 2024

Из-за проблем с WebRTC у ss-redir решил перейти на WireGuard. И не смог на первом же шаге — нужной сети в списке выбора нет (встречал схожие истории и в Телеграмме). У другого рабочего VPN в списке нет описания. Вызов kvas vpn rescan в 1 случае из 7 может лишь добавить описание/имя подключения. Переустановка с нуля вопрос тоже не решила.

Чтобы сэкономить время, сам поисследовал вопрос. Выдернул из кода update_interface_name_list и все зависимые функции. Но его выполнение шло как в чёрной коробочке, никаких выводов/прогрессбаров в процессе. Поэтому докинул вывод о каждом шаге долгом или важном: о выключении сетевого подключения; включении; какое описание/имя пытаемся добавить; плюс перед sed добавил проверку, а есть ли вообще интерфейс в /opt/etc/inface_equals.

#!/bin/sh

INFACE_NAMES_FILE=/opt/tmp/test_interface

RED="\033[1;31m";
GREEN="\033[1;32m";
NOCL="\033[m";
length=$(stty size 2>/dev/null | cut -d' ' -f2)
[ -n "${length}" ] && [ "${length}" -gt 80 ] && LENGTH=$((length*2/3)) || LENGTH=68
LOCALHOST_IP=127.0.0.1
SSR_ENTWARE_TEMPL=ezcfg
INFACE_REQUEST="${LOCALHOST_IP}:79/rci/show/interface"
INFACE_PART_REQUEST="${LOCALHOST_IP}:79/rci/interface"

diff_len() {
	len=${2:-${LENGTH}}			# максим длинна строки
	charlen=$(echo "${1}" | sed -r "s/[\]033\[([0-9]{1,3}(;[0-9]{1,2})?)?[mGK]//g")
	charlen=${#charlen}
	echo $(( len - charlen ))
}

warning() {
	_error="${GREEN}${1}${NOCL}"
	size=$(diff_len "${_error}")
	printf "%b%-${size}s%b\n" "${_error}"
}

error() {
	_error="${RED}${1}${NOCL}"
	size=$(diff_len "${_error}")
	printf "%b%-${size}s%b\n" "${_error}" 2>&1

	log_error "${1}"
}

print_line() {
	len=$((LENGTH))
	printf "%${len}s\n" | tr " " "-"
}

get_firmware_version(){
	curl -s localhost:79/rci/show/version | jq '.title' | sed 's/["a-zA-Z]//g'
}

get_hook_dir(){
	get_firmware_version | grep -q '4.' && echo "iflayerchanged.d" ||  echo "ifstatechanged.d"
}

ip4() (/opt/sbin/ip -4 "$@")

get_ssr_entware_interface()(ip4 a | grep "${SSR_ENTWARE_TEMPL}" | head -1 | cut -d': ' -f2 | tr -d ' ')

get_defaultgw_id() {
	curl -s "${INFACE_REQUEST}" | jq -r '.[] | select(.defaultgw==true and .global==true) | .id'
}

reset_ISP_connection() {
	# получаем текущее состояние инф-са для возвращения его к исходному состоянию
	id=$(get_defaultgw_id)

	echo -n ', отключение ISP'
	curl -s -d '{"down":"true"}' "${INFACE_PART_REQUEST}/${id}" &> /dev/null
	sleep 3

	echo -n ', включение ISP'
	curl -s -d '{"up":"true"}' "${INFACE_PART_REQUEST}/${id}" &> /dev/null
}

reset_connection() {
	inface_cli=${1}
	time_delay=${2}

	# получаем текущее состояние инф-са для возвращения его к исходному состоянию
	isp_state=$(curl -s "${INFACE_REQUEST}" | jq -r '.[] | select(.defaultgw==true and .global==true) | .state')
	if echo "${isp_state}" | grep -q down ; then
		reset_ISP_connection || {
			error "Проверьте соединение с провайдером и/или настройки DNS."
			return 1
		}
		sleep 1
	fi

	# в зависимости от состояния - включаем и выключаем или выключаем и включаем интерфейс
	# для того, чтобы сработал наш хук в файле /opt/etc/ndm/ifstatechanged.d/100-save-inface_entware
	state=$(curl -s "${INFACE_REQUEST}" | jq -r '.[] | select(.id=="'"${inface_cli}"'") | .state')
	if [ "${state}" = 'up' ]; then
		echo -n ', отключение'
		curl -s -d '{"down":"true"}' "${INFACE_PART_REQUEST}/${inface_cli}" &> /dev/null
	else
		error_mess=$(curl -s -d '{"up":"true"}' "${INFACE_PART_REQUEST}/${inface_cli}" \
		 |  jq '.[] | .status | .[] | select(.status == "error") | .message' | tr '\"' '"')
		echo "${error_mess}" | grep -q '[a-zA-Z]' && {
			error "Обнаружена ошибка при переключении '${inface_cli}' сети. " nl
			error "Прежде чем продолжить - устраните ее." nl
			print_line
			error "${error_mess}" nl
			print_line
			return 1
		}
	fi
	sleep "${time_delay}"

	echo -n ', включение'
	curl -s -d "{\"${state}\":\"true\"}" "${INFACE_PART_REQUEST}/${inface_cli}" &> /dev/null
	sleep 1
	return 0
}

get_value_interface_field() {
	id_cli_inface=${1}
	field_name=${2}
	sleep 1
	curl -s "${INFACE_REQUEST}" | jq -r '.[] | select(.id=="'"${id_cli_inface}"'") | .'"${field_name}"
}

update_interface_name_list() {
	warning "Производим сканирование интерфейсов."
	warning "Сохраняйте терпение и спокойствие!"
	print_line

	rm -f "${INFACE_NAMES_FILE}"
	touch "${INFACE_NAMES_FILE}"

	HOOK_INFACE_FILE=/opt/etc/ndm/$(get_hook_dir)/100-save-inface_entware
	# если нет файла по отлавливанию хука
	if ! [ -f "${HOOK_INFACE_FILE}" ]; then
		# создаем файл
		cat <<EOF >"${HOOK_INFACE_FILE}"
#!/bin/sh
if [ -z "\$(grep "\${id}" "${INFACE_NAMES_FILE}")" ]; then
	echo "\${id}|\${system_name}" >> "${INFACE_NAMES_FILE}"
fi
exit 0
EOF
		chmod +x "${HOOK_INFACE_FILE}"
	fi

	# очищаем файл с именами интерфейсов
	echo "shadowsocks|$(get_ssr_entware_interface)|shadowsocks" > "${INFACE_NAMES_FILE}"

	# обозначаем список типов обрабатываемых VPN интерфейсов
	types_inface='"OpenVPN","Wireguard","IKE","SSTP","PPPOE","L2TP","PPTP","Proxy"'
	# получаем список ID интерфейсов в наличии на роутере через пробел
	inface_list=$(
		curl -s "${INFACE_REQUEST}" \
			| jq -r '.[] | select([.type]| inside(['"${types_inface}"'])) | select(.defaultgw!=true) | .id' \
			| tr '\n' ' ' | sed 's/[ ]$//g' \
	)
	# проходимся по каждому интерфейсу
	# ВНИМАНИЕ! inface_list - все маленькими буквами!!!
	delay=3
	for inface_cli in ${inface_list}; do
		# переподключаем текущее соединение
		echo -n "${inface_cli}"
		reset_connection "${inface_cli}" "${delay}"

		echo -n ', в файле '
		if grep -q "${inface_cli}" "${INFACE_NAMES_FILE}"; then
			echo -n 'есть'
		else
			echo -n 'нет'
		fi

		# получаем описание интерфейса
		description=$(get_value_interface_field "${inface_cli}" description | sed 's|\/|\\/|g')
		if [ -n "${description}" ]; then
			echo -n ", имя ${description}"

			# вставляем описание в файл /opt/etc/inface_equals
			sed -i 's/\('"${inface_cli}"'.*\)/\1|'"${description}"'/' "${INFACE_NAMES_FILE}"
		fi

		echo ''
	done

	rm -f "${HOOK_INFACE_FILE}"
}

update_interface_name_list

Соответственно, вопрос 1 — А можно ли добавить какой-то такой же механизм в релиз (вывод на картинке ниже, он пополняется в процессе)? Во-первых, это избавит от необходимости такого жёсткого предупреждения, пользователь будет видеть прогресс. Во-вторых, будет понятно, что происходит, и если какое падение (или невхождение, как у меня), то меньше будет сообщений «не работает, но не понятно что». При этом я понимаю, что некоторые функции переиспользуются и вывод там не нужен, надо будет заводить дублёры типа reset_connection_silent или reset_connection_with_stdout.
kvas_rescan
Дык вот, запуск этого файла полностью повторяет проблему kvas vpn rescan на моих KN-1011 и Netac U116. Последней сети нет, предпоследняя чаще всего без имени. В цикле проверяются всегда 3 правильные сети. Если в коде цикл запускать дважды, отдельно для перезапуска коннектов, отдельно для добавления описания/имени, то проблема отсутствующего имени у предпоследнего уходит:

	delay=3
	for inface_cli in ${inface_list}; do
		# переподключаем текущее соединение
		echo -n "${inface_cli}"
		reset_connection "${inface_cli}" "${delay}"
		echo ''
	done
	for inface_cli in ${inface_list}; do
		# получаем описание интерфейса
		description=$(get_value_interface_field "${inface_cli}" description | sed 's|\/|\\/|g')

		echo -n "${inface_cli} в файле "
		if grep -q "${inface_cli}" "${INFACE_NAMES_FILE}"; then
			#TODO: выводить зелёным, ведь хук достал название сети
			echo -n 'есть'
		else
			#TODO: выводить красным, таймаута не хватило
			echo -n 'нет'
		fi

		if [ -n "${description}" ]; then
			echo -n ", имя ${description}"

			# вставляем описание в файл /opt/etc/inface_equals
			sed -i 's/\('"${inface_cli}"'.*\)/\1|'"${description}"'/' "${INFACE_NAMES_FILE}"
		fi

		echo ''
	done

По поводу отсутствия сети WireGuard, какие-то проблемы у роутера с многопоточностью/конкурентностью. Сейчас в коде КВАСа после отключения сетевого интерфейса sleep 3, после включения sleep 1. Увеличение последнего sleep до 4 почти всегда решает проблему, подключение Ваергварда успевает попадать в /opt/etc/inface_equals. Но зачем нам увеличивать на 3–4 секунды для каждого подключения, если достаточно подождать отработки хука лишь после последнего? Тем более, на прошлом шаге цикл разделили на 2; добавим sleep между, код становится:

	delay=3
	for inface_cli in ${inface_list}; do
		# переподключаем текущее соединение
		echo -n "${inface_cli}"
		reset_connection "${inface_cli}" "${delay}"
		echo ''
	done

	echo -n 'Ожидание отработки последнего хука...'
	sleep 5
	echo ' завершено'

	for inface_cli in ${inface_list}; do
		# получаем описание интерфейса
		description=$(get_value_interface_field "${inface_cli}" description | sed 's|\/|\\/|g')

		echo -n "${inface_cli} в файле "
		if grep -q "${inface_cli}" "${INFACE_NAMES_FILE}"; then
			#TODO: выводить зелёным, ведь хук достал название сети
			echo -n 'есть'
		else
			#TODO: выводить красным, таймаута не хватило
			echo -n 'нет'
		fi

		if [ -n "${description}" ]; then
			echo -n ", имя ${description}"

			# вставляем описание в файл /opt/etc/inface_equals
			sed -i 's/\('"${inface_cli}"'.*\)/\1|'"${description}"'/' "${INFACE_NAMES_FILE}"
		fi

		echo ''
	done

kvas_rescan3
Итого, вопрос 2: можете добавить такое же изменение в код, чтобы у меня заработало? Фактически, в функции update_interface_name_list в opt/bin/libs запускать цикл for inface_cli in ${inface_list}; do дважды: один раз для reset_connection "${inface_cli}" "${delay}", второй для добавления имени/описания. Со слипом 5 между ними (при 4 не успевает).

UPD Во втором цикле перед sed крайне желательно делать grep для проверки, что хук этот интерфейс успел добавить. У меня в коде только вывод в консоль, но в идеале при неудаче имеет смысл писать в лог сообщение типа «Получить имя сетевого интерфейса для Wireguard0 не удалось.».

UPD2 Ещё подумалось, что sleep 5 между циклами кому-то может быть много, а кому-то мало. Правильнее там организовать цикл с проверкой grep каждые 2 секунды. Если нашли, цикл прерывается и идёт дальше к sed. Если нет за 5–8 шагов (10–16 секунд), то выводим об этом сообщение и записываем в лог ошибок. Чтобы пользователя не пугал такой зависон, можно выводить по точечке в каждой итерации, станет что-то в духе Ожидание отработки последнего хука......завершено.

@AltGrF13 AltGrF13 changed the title Переключение на VPN/WireGuard Нет нужного VPN/WireGuard Oct 22, 2024
@qzeleza
Copy link
Owner

qzeleza commented Oct 23, 2024

нет, подобного рода информацию добавлять не буду, так как не всем это нужно в принципе.
Что касается интервалов задержки, то у всех они разные - все зависит от скорости процессора и используемой памяти, потому универсального средства пока не придумал.

То, что предлагаете Вы не универсальный выход, только под конкретно Ваш случай - это только затянет время сканирования, при этом большая часть пользователей с этим проблем не испытывает.

Как вариант это пересканирование несколько раз до победного результата, либо предложите более стабильный и универсальный вариант кода.

@AltGrF13
Copy link
Contributor Author

AltGrF13 commented Oct 24, 2024

предложите более стабильный и универсальный вариант кода

Согласен, мой код можно было написать гораздо лучше:

	delay=3
	for inface_cli in ${inface_list}; do
		# переподключаем текущее соединение
		reset_connection "${inface_cli}" "${delay}"
	done
	for inface_cli in ${inface_list}; do
		# максимальное количество попыток (секунд) для ожидания отработки хука
		# если при ежесекундной проверке интерфейс в файле появится, то ожидание прервётся
		# если он уже на месте, задержки не будет
		attempt=10
		while [[ $attempt -gt 0 ]] && ! $(grep -Fq "${inface_cli}" "${INFACE_NAMES_FILE}"); do
			sleep 1
			attempt=$(( $attempt - 1 ))
		done

		if [[ $attempt -gt 0 ]]; then
			# получаем описание интерфейса
			description=$(get_value_interface_field "${inface_cli}" description | sed 's|\/|\\/|g')
			# вставляем описание в файл /opt/etc/inface_equals
			[ -n "${description}" ] && \
				sed -i 's/\('"${inface_cli}"'.*\)/\1|'"${description}"'/' "${INFACE_NAMES_FILE}"
		else
			log_error "Сетевой интерфейс для ${inface_cli} получить не удалось."
		fi
	done

Пока отлаживал, пособирал статистику, сколько требовалось дополнительного ожидания. Первые две сети не заходили в цикл (не требовали лишних секунд; что не удивительно, ведь отработке их хуков достаётся больше времени), для Amnezia WG: 2 секунды, 7, 3, 2, 4, 1. Ну т.е. ситуация реально случайная, и такому адаптивному варианту там самое место.

@qzeleza
Copy link
Owner

qzeleza commented Oct 24, 2024

Спасибо, внес изменения по Вашему варианту кода в 1.1.9b6

@qzeleza qzeleza closed this as completed Oct 24, 2024
@AltGrF13
Copy link
Contributor Author

Обновился до beta6, сейчас все сети находит и даёт выбрать, спасибо!

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