Статья участвует в Конкурсе статей по настройке оборудования Mikrotik с операционной системой RouterOS

Каждый системный администратор, который когда-либо сталкивался с P2P радио-линками на оборудовании MikroTik, рано или поздно задумывался о том, что бы автоматизировать процесс настройки рабочей частоты радио. Принцип работы программы должен быть до элементарного прост — сменили рабочую частоту и проверили пропускную способностью. Однако, рабочих вариантов в сети Интернет лично мне не попадалось.

Многие пользователи продолжают использовать встроенные в MikroTik средства для анализа эфира и уже на основании этих данных пытаться строить линки. Но это не дает желаемого эффекта по причине того, что многие помехи не укладываются в разрешающую способность спектр-анализатора MikroTik, а следовательно, и увидеть их нельзя.

Что же, давайте попробуем решить данный вопрос на уровне автоматики. Нам потребуется дистрибутив Linux (я использую RedHat Fedora 14 — это мой рабочий ПК), базовый набор утилит, таких как grep,awk,timeout, а также sshpass.

В обязательном порядке нам потребуется запущенный локальный MySQL сервер без пароля для root пользователя, который будет сборщиком данных и будет служить для формирования отчета. И так, начнем.

Определим для начала переменные, которые будут использоваться:

#!/bin/sh

# Логин и пароль для входа на точку

LOGIN="admin"

PASSWORD="admin"

# IP адреса настраиваемых точек, ближней и дальней

LOCAL_IP="1.1.1.2"
REMOTE_IP="1.1.1.3"

# Таймаут ожидания соединения WiFi в секундах

WIFI_CONNECT_INTERVAL=60

# Требуемая емкость канала

DUPLEX_MBITS_REQUIRED=20

# Длительность тестирования

DUPLEX_TEST_DURATION=60

# Диапазон частот и шаг по частоте

FREQUENCY_START=4920

FREQUENCY_STOP=6070

FREQUENCY_STEP=5

# Количество частот в скан-листе

MAXIMUM_NEW_FREQUENCIES=10

Определим версию SSH, на котором будем подключаться:

get_mikrotik_ssh_version () {
local IP="$1"
local RV
/usr/bin/timeout -sHUP 10s /usr/bin/sshpass -p$PASSWORD /usr/bin/ssh -1 -o ConnectTimeout=10 -o "StrictHostKeyChecking no" $LOGIN@$IP "/system resource print" > /dev/null 2>&1
RV="$?"
if [ $RV -eq 0 ] ; then
SSH_VERSION="-1"
else
SSH_VERSION="-2"
fi
}

Определим функция для посылки данных:

send_command_to_mikrotik () {
local IP="$1"
local COMMAND="$2"
get_mikrotik_ssh_version $IP
RETVAL=`/usr/bin/timeout -sHUP 10s /usr/bin/sshpass -p$PASSWORD /usr/bin/ssh $SSH_VERSION -o ConnectTimeout=10 -o "StrictHostKeyChecking no" $LOGIN@$IP "$COMMAND" 2>/dev/null | /bin/sed 's/\x0D//'`
}

Определим функцию проверки устройства, что оно живо:

check_is_mikrotik_connected () {
local IP="$1"
local IS_CONNECTED
send_command_to_mikrotik $IP "/system resource print"
IS_CONNECTED=`echo "$RETVAL" | /bin/grep -c version`
if [ $IS_CONNECTED -eq 0 ] ; then
RETVAL=0
else
RETVAL=1
fi
}

Функция для проверки работоспособности обоих устройств:

interactive_check_is_both_devices_connected () {
local LOCAL_CONNECTED
local REMOTE_CONNECTED
echo -n "Проверяем доступность обоих устройств ... "
check_is_mikrotik_connected $LOCAL_IP
LOCAL_CONNECTED=$RETVAL
check_is_mikrotik_connected $REMOTE_IP
REMOTE_CONNECTED=$RETVAL
if [ $LOCAL_CONNECTED -eq 1 ] && [ $REMOTE_CONNECTED -eq 1 ] ; then
echo -e '[ \E[0;32m'"\033[1mOK\033[0m ]"
else
if [ $LOCAL_CONNECTED -eq 0 ] ; then
echo -e '[ \E[0;31m'"\033[1mНЕ ДОСТУПНО БЛИЖНЕЕ УСТРОЙСТВО\033[0m ]"
exit
fi
if [ $REMOTE_CONNECTED -eq 0 ] ; then
echo -e '[ \E[0;31m'"\033[1mНЕ ДОСТУПНО ДАЛЬНЕЕ УСТРОЙСТВО\033[0m ]"
exit
fi
fi
}

Небольшое отступление: в данной функции у нас фигурирует префикс interactive. Это будет означать, что функция будет выводить на экран данные и принимать решение о том, требуется ли дальнейшая работа.

Функция определения текущей рабочей частоты:

interactive_get_current_working_frequency () {
echo -n "Текущая рабочая частота ... "
send_command_to_mikrotik $LOCAL_IP ":put [interface wireless get 0 value-name=frequency]"
if [ $RETVAL -ge 100 2>/dev/null ] ; then
CURRENT_WORKING_FREQUENCY=$RETVAL
echo -e '[ \E[0;32m'"\033[1m$CURRENT_WORKING_FREQUENCY\033[0m ]"
else
echo -e '[ \E[0;31m'"\033[1mНЕ УДАЛОСЬ ПОЛУЧИТЬ ДАННЫЕ\033[0m ]"
exit
fi
}

Функция определения уровня сигнала на ближней стороне:

interactive_get_local_signal_level () {
local SIGNAL
echo -n "Уровень сигнала на ближней стороне ... "
send_command_to_mikrotik $LOCAL_IP ":put [/interface wireless registration-table get 0 value-name=signal-strength ]"
SIGNAL=`echo $RETVAL | /bin/sed 's/dBm/\ /g' | /bin/awk '{print($1)}' | /bin/sed 's/-//g'`
if [ $SIGNAL -ge 1 2>/dev/null ] ; then
CURRENT_LOCAL_SIGNAL=$SIGNAL
echo -e '[ \E[0;32m'"\033[1m-""$CURRENT_LOCAL_SIGNAL""dBm\033[0m ]"
else
echo -e '[ \E[0;31m'"\033[1mНЕ УДАЛОСЬ ПОЛУЧИТЬ ДАННЫЕ\033[0m ]"
CURRENT_LOCAL_SIGNAL=0
#interactive_fallback_p2p_link
#exit
fi
}

Функция определения уровня сигнала на дальней стороне:

interactive_get_remote_signal_level () {
local SIGNAL
echo -n "Уровень сигнала на дальней стороне ... "
send_command_to_mikrotik $REMOTE_IP ":put [/interface wireless registration-table get 0 value-name=signal-strength]"
SIGNAL=`echo $RETVAL | /bin/sed 's/dBm/\ /g' | /bin/awk '{print($1)}' | /bin/sed 's/-//g'`
if [ $SIGNAL -ge 1 2>/dev/null ] ; then
CURRENT_REMOTE_SIGNAL=$SIGNAL
echo -e '[ \E[0;32m'"\033[1m-""$CURRENT_REMOTE_SIGNAL""dBm\033[0m ]"
else
echo -e '[ \E[0;31m'"\033[1mНЕ УДАЛОСЬ ПОЛУЧИТЬ ДАННЫЕ\033[0m ]"
CURRENT_REMOTE_SIGNAL=0
#interactive_fallback_p2p_link
#exit
fi
}

Функция определения SNR (signal-to-noise ratio) на ближней стороне:

interactive_get_local_snr () {
local SNR
echo -n "Уровень SNR на ближней стороне ... "
send_command_to_mikrotik $LOCAL_IP ":put [/interface wireless registration-table get 0 value-name=signal-to-noise ]"
SNR=`echo $RETVAL | /bin/sed 's/dBm/\ /g' | /bin/awk '{print($1)}' | /bin/sed 's/-//g'`
if [ $SNR -ge 1 2>/dev/null ] ; then
CURRENT_LOCAL_SNR=$SNR
echo -e '[ \E[0;32m'"\033[1m$CURRENT_LOCAL_SNR""dB\033[0m ]"
else
echo -e '[ \E[0;31m'"\033[1mНЕ УДАЛОСЬ ПОЛУЧИТЬ ДАННЫЕ\033[0m ]"
CURRENT_LOCAL_SNR=0
#interactive_fallback_p2p_link
#exit
fi
}

Функция определения SNR (signal-to-noise ratio) на дальней стороне:

interactive_get_remote_snr () {
local SNR
echo -n "Уровень SNR на дальней стороне ... "
send_command_to_mikrotik $REMOTE_IP ":put [/interface wireless registration-table get 0 value-name=signal-to-noise ]"
SNR=`echo $RETVAL | /bin/sed 's/dBm/\ /g' | /bin/awk '{print($1)}' | /bin/sed 's/-//g'`
if [ $SNR -ge 1 2>/dev/null ] ; then
CURRENT_REMOTE_SNR=$SNR
echo -e '[ \E[0;32m'"\033[1m$CURRENT_REMOTE_SNR""dB\033[0m ]"
else
echo -e '[ \E[0;31m'"\033[1mНЕ УДАЛОСЬ ПОЛУЧИТЬ ДАННЫЕ\033[0m ]"
CURRENT_REMOTE_SNR=0
#interactive_fallback_p2p_link
#exit
fi
}

Функция получения текущего Скан-Листа:

interactive_get_current_scanlist () {
echo -n "Текущий сканлист ... "
send_command_to_mikrotik $REMOTE_IP ":put [interface wireless get 0 value-name=scan-list]"
SCANLIST_CHECK_EVAL=`echo $RETVAL | /bin/sed 's/;/+/g'`
SCANLIST_CHECK_SUM=`echo $SCANLIST_CHECK_EVAL | /usr/bin/bc`
if [ $SCANLIST_CHECK_SUM -ge 100 2>/dev/null ] ; then
CURRENT_WORKING_SCANLIST=`echo $RETVAL | /bin/sed 's/;/,/g'`
echo -e '[ \E[0;32m'"\033[1m$CURRENT_WORKING_SCANLIST\033[0m ]"
else
echo -e '[ \E[0;31m'"\033[1mНЕ УДАЛОСЬ ПОЛУЧИТЬ ДАННЫЕ\033[0m ]"
exit
fi
}

Функция смены Скан-Листа:

interactive_change_remote_scanlist () {
local ADDFREQ="$1"
local NEWSCANLIST=$CURRENT_WORKING_FREQUENCY","$ADDFREQ
local CMDTOSEND="/interface wireless set 0 scan-list="$NEWSCANLIST
echo -n "Меняем скан-лист на дальней стороне ($NEWSCANLIST) ... "
send_command_to_mikrotik $REMOTE_IP "$CMDTOSEND"
sleep $WIFI_CONNECT_INTERVAL
send_command_to_mikrotik $REMOTE_IP ":put [interface wireless get 0 value-name=scan-list]"
WORKINGSCANLIST=`echo $RETVAL | /bin/sed 's/;/,/g'`
if [ "$WORKINGSCANLIST" != "$NEWSCANLIST" ] ; then
echo -e '[ \E[0;31m'"\033[1mНЕ СМЕНЕНО\033[0m ]"
#interactive_fallback_p2p_link
#exit
else
echo -e '[ \E[0;32m'"\033[1mOK\033[0m ]"
fi
}

Примечание: следующая функция также меняет сканлист, но сделана она больше для того, что бы закончить цикл и финализировать работу скрипта. Понимаю, что код избыточный, но мне по душе лучше сделать отдельную функцию для такой работы.

Функция смены Скан-Листа и смены частоты:

interactive_change_remote_scanlist_final () {
local ADDFREQ="$2"
local NEWFREQ="$1"
local NEWSCANLIST="$ADDFREQ"
local CMDTOSEND="/interface wireless set 0 scan-list="$NEWSCANLIST
echo -n "Финально меняем скан-лист на дальней стороне и частоту на ближней ... "
send_command_to_mikrotik $REMOTE_IP "$CMDTOSEND"
send_command_to_mikrotik $LOCAL_IP "/interface wireless set 0 frequency=$NEWFREQ"
sleep $WIFI_CONNECT_INTERVAL
send_command_to_mikrotik $REMOTE_IP ":put [interface wireless get 0 value-name=scan-list]"
WORKINGSCANLIST=`echo $RETVAL | /bin/sed 's/;/,/g'`
if [ "$WORKINGSCANLIST" != "$NEWSCANLIST" ] ; then
echo -e '[ \E[0;31m'"\033[1mНЕ СМЕНЕНО\033[0m ]"
interactive_fallback_p2p_link
exit
else
send_command_to_mikrotik $LOCAL_IP "/interface wireless set 0 comment=\"scan-list=$NEWSCANLIST\""
echo -e '[ \E[0;32m'"\033[1mOK\033[0m ]"
fi
}

Функция включения режима superchannel:

interactive_update_superchannel () {
echo -n "Устанавливаем режим superchannel для обоих точек ... "
send_command_to_mikrotik $REMOTE_IP "/interface wireless set 0 frequency-mode=superchannel"
sleep $WIFI_CONNECT_INTERVAL
send_command_to_mikrotik $LOCAL_IP "/interface wireless set 0 frequency-mode=superchannel"
sleep $WIFI_CONNECT_INTERVAL
echo -e '[ \E[0;32m'"\033[1mOK\033[0m ]"
}

Функция обновления таблицы регистрации (ROS любит, что бы перед запросом на получение данных давали команду print):

interactive_update_registration_table () {
send_command_to_mikrotik $LOCAL_IP "/interface wireless registration-table print"
send_command_to_mikrotik $REMOTE_IP "/interface wireless registration-table print"
}

Функция смены рабочей частоты на ближней точке:

interactive_change_local_frequency () {
local NEWFREQ="$1"
echo -n "Меняем частоту на ближней стороне ($NEWFREQ) ... "
send_command_to_mikrotik $LOCAL_IP "/interface wireless set 0 frequency=$NEWFREQ"
send_command_to_mikrotik $LOCAL_IP ":put [interface wireless get 0 value-name=frequency]"
if [ "$RETVAL" != "$NEWFREQ" ] ; then
echo -e '[ \E[0;31m'"\033[1mНЕ СМЕНЕНО\033[0m ]"
interactive_fallback_p2p_link
exit
else
echo -e '[ \E[0;32m'"\033[1mOK\033[0m ]"
fi
}

Функция конвертации скоростей, полученный от MikroTik:

convert_mikrotik_speed_to_decimal () {
local SPEED="$1"
local IS_MEGA
local IS_KILO
local RESULT
IS_MEGA=`echo $SPEED | /bin/grep -c -i M`
IS_KILO=`echo $SPEED | /bin/grep -c -i K`
RAW_NUMBER=`echo $SPEED | /bin/sed 's/[a-zA-Z]//g'`
RESULT=`printf "%.0f" $(echo "scale=0; $RAW_NUMBER" | /usr/bin/bc | /bin/sed 's/\./,/g' )`
if [ $IS_MEGA -eq 1 ] ; then
RESULT=`printf "%.0f" $(echo "scale=0; $RAW_NUMBER*1000000" | /usr/bin/bc | /bin/sed 's/\./,/g' )`
fi
if [ $IS_KILO -eq 1 ] ; then
RESULT=`printf "%.0f" $(echo "scale=0; $RAW_NUMBER*1000" | /usr/bin/bc | /bin/sed 's/\./,/g' )`
fi
RETVAL=$RESULT
}

Функция измерения скоростей:

interactive_bandwidth_test () {
local IP=$LOCAL_IP
local BTESTINFO
local TX_TOTAL_AVERAGE_RAW
local RX_TOTAL_AVERAGE_RAW
local MAXIMUM_SSH_TIMEOUT
let "MAXIMUM_SSH_TIMEOUT=$DUPLEX_TEST_DURATION*2"
echo -n "Проводим замер скоростей ... "
get_mikrotik_ssh_version $IP
BTESTINFO=`/usr/bin/timeout -sHUP $MAXIMUM_SSH_TIMEOUT""s /usr/bin/sshpass -p$PASSWORD /usr/bin/ssh -t $SSH_VERSION -o ConnectTimeout=10 -o "StrictHostKeyChecking no" $LOGIN@$IP "/tool bandwidth-test $REMOTE_IP user=$LOGIN password=$PASSWORD direction=both local-tx-speed="$DUPLEX_MBITS_REQUIRED"M remote-tx-speed="$DUPLEX_MBITS_REQUIRED"M duration="$DUPLEX_TEST_DURATION"s ; quit" 2>/dev/null | /bin/sed 's/\x0D//'`
IS_BANDWIDTH_OK=`echo "$BTESTINFO" | /bin/grep -c tx-total-average`
if [ $IS_BANDWIDTH_OK -eq 0 ] ; then
TX_TOTAL_AVERAGE=0
RX_TOTAL_AVERAGE=0
echo -e '[ \E[0;31m'"\033[1mОШИБКА\033[0m ]"
else
TX_TOTAL_AVERAGE_RAW=`echo "$BTESTINFO" | /bin/sed 's/\0X1B//g' | /bin/grep tx-total-average | /bin/sed -r "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]//g" | /usr/bin/tail -n1 | /bin/awk '{print($2)}'`
RX_TOTAL_AVERAGE_RAW=`echo "$BTESTINFO" | /bin/sed 's/\0X1B//g' | /bin/grep rx-total-average | /bin/sed -r "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]//g" | /usr/bin/tail -n1 | /bin/awk '{print($2)}'`
convert_mikrotik_speed_to_decimal $TX_TOTAL_AVERAGE_RAW
TX_TOTAL_AVERAGE=$RETVAL
convert_mikrotik_speed_to_decimal $RX_TOTAL_AVERAGE_RAW
RX_TOTAL_AVERAGE=$RETVAL
echo -e '[ \E[0;32m'"\033[1mTX/RX=$TX_TOTAL_AVERAGE_RAW/$RX_TOTAL_AVERAGE_RAW\033[0m ]"
fi
}

Функция отката настроек на прежние:

interactive_fallback_p2p_link () {
local CMDTOSEND
local WORKINGSCANLIST
echo -n "Откатываем параметры p2p линка к исходным ... "
local CMDTOSEND="/interface wireless set 0 frequency="$CURRENT_WORKING_FREQUENCY
send_command_to_mikrotik $LOCAL_IP "$CMDTOSEND"
sleep $WIFI_CONNECT_INTERVAL
CMDTOSEND="/interface wireless set 0 scan-list="$CURRENT_WORKING_SCANLIST
send_command_to_mikrotik $REMOTE_IP "$CMDTOSEND"
send_command_to_mikrotik $REMOTE_IP ":put [interface wireless get 0 value-name=scan-list]"
WORKINGSCANLIST=`echo $RETVAL | /bin/sed 's/;/,/g'`
if [ "$WORKINGSCANLIST" != "$CURRENT_WORKING_SCANLIST" ] ; then
echo -e '[ \E[0;31m'"\033[1mНЕ УДАЛОСЬ\033[0m ]"
else
echo -e '[ \E[0;32m'"\033[1mOK\033[0m ]"
fi
}

Функция установки рабочей частоты на начальную:

interactive_set_original_frequency () {
local CMDTOSEND
local WORKINGSCANLIST
echo -n "Откатываем частоту нашей стороны на старую ... "
local CMDTOSEND="/interface wireless set 0 frequency="$CURRENT_WORKING_FREQUENCY
send_command_to_mikrotik $LOCAL_IP "$CMDTOSEND"
echo -e '[ \E[0;32m'"\033[1mOK\033[0m ]"
}

Инициализируем БД MySQL:

mysql_init () {
/usr/bin/mysql -N -e "create database mikrotik_tuner" > /dev/null 2>&1
/usr/bin/mysql -N -e "drop table link_parameters" mikrotik_tuner > /dev/null 2>&1
/usr/bin/mysql -N -e "create table link_parameters (id int primary key AUTO_INCREMENT,frequency int,tx_speed int,rx_speed int,local_signal int,remote_signal int,local_snr int,remote_snr int)" mikrotik_tuner > /dev/null 2>&1
}

Функция занесения данных в БД:

add_p2p_parameters_to_array () {
local FREQ="$1"
/usr/bin/mysql -N -e "insert into link_parameters (frequency,tx_speed,rx_speed,local_signal,remote_signal,local_snr,remote_snr) values ($FREQ,$TX_TOTAL_AVERAGE,$RX_TOTAL_AVERAGE,$CURRENT_LOCAL_SIGNAL,$CURRENT_REMOTE_SIGNAL,$
CURRENT_LOCAL_SNR,$CURRENT_REMOTE_SNR)" mikrotik_tuner > /dev/null 2>&1

}

Приступаем к написанию тела скрипта:

mysql_init
interactive_check_is_both_devices_connected
interactive_get_current_working_frequency
interactive_get_current_scanlist
interactive_update_superchannel
FREQUENCY=$FREQUENCY_START
OLD_FREQUENCY=$FREQUENCY_START
while [ $FREQUENCY -le $FREQUENCY_STOP ] ; do
if [ "$FREQUENCY" != "$OLD_FREQUENCY" ] ; then
ACTUAL_SCANLIST=$FREQUENCY","$OLD_FREQUENCY
else
ACTUAL_SCANLIST=$FREQUENCY
fi
interactive_change_remote_scanlist $ACTUAL_SCANLIST
interactive_change_local_frequency $FREQUENCY
echo -n "Ожидаем подключения ... "
sleep $WIFI_CONNECT_INTERVAL
/bin/ping -c 3 -q $REMOTE_IP > /dev/null 2>&1
RETVAL=$?

if [ $RETVAL -eq 0 ] ; then
interactive_update_registration_table
echo -e '[ \E[0;32m'"\033[1mOK\033[0m ]"
interactive_get_local_signal_level
interactive_get_remote_signal_level
interactive_get_local_snr
interactive_get_remote_snr
interactive_bandwidth_test
add_p2p_parameters_to_array $FREQUENCY
OLD_FREQUENCY=$FREQUENCY
else
echo -e '[ \E[0;31m'"\033[1mНЕ ПОДКЛЮЧЕНО\033[0m ]"
interactive_set_original_frequency
echo -n "Ожидаем подключения ... "
sleep $WIFI_CONNECT_INTERVAL
echo -e '[ \E[0;32m'"\033[1mOK\033[0m ]"
fi
let "FREQUENCY=$FREQUENCY+$FREQUENCY_STEP"
done

IM_SCAN_LIST_RAW=`/usr/bin/mysql -N -e "select frequency from link_parameters where local_snr>40 and remote_snr>40 order by abs(local_signal-remote_signal) asc,rx_speed desc,tx_speed desc limit $MAXIMUM_NEW_FREQUENCIES" mikrotik_tuner`
IM_SCAN_LIST=`echo $IM_SCAN_LIST_RAW | /bin/sed 's/\ /,/g'`
IM_NEW_FREQUENCY=`/usr/bin/mysql -N -e "select frequency from link_parameters where local_snr>40 and remote_snr>40 order by abs(local_signal-remote_signal) asc,rx_speed desc,tx_speed desc limit 1" mikrotik_tuner`
if [ "$IM_SCAN_LIST" != "" ] ; then
interactive_change_remote_scanlist_final $IM_NEW_FREQUENCY "$IM_SCAN_LIST"
else
interactive_fallback_p2p_link
fi

echo
echo "Отчет:"
echo "Новый скан-лист : "$IM_SCAN_LIST
echo "Новая рабочая частота : "$IM_NEW_FREQUENCY
echo "Старый рабочий скан-лист: "$CURRENT_WORKING_SCANLIST
echo "Старая рабочая частота  : "$CURRENT_WORKING_FREQUENCY

Заключение

Как мы видим, скрипт был написан буквально «на коленке», но отлично зарекомендовал себя при настройке P2P линков. Одним из главных параметров при настройке можно считать WIFI_CONNECT_INTERVAL, его на первых порах я ставил равным 15 с, однако на некоторых версиях ROS более 6 получал долгий таймаут по соединению WiFi и был вынужден поставить 60 с, поэтому рекомендую вам в конкретных условиях протестировать скрипт с меньшим значением этого параметра. Надеюсь, что все остальное не вызвало у вас никаких вопросов. А изящество получения данных за счет MySQL лично у меня, да и надеюсь, у многих вызовет восхищение.

Участник конкурса Александр

Просьба всем кто читает статьи, участвующие в конкурсе, ставить оценки.