Спасение утопающих … и далее по тексту

Однажды, просматривая статистику посещений блога, я заметил, что его не читают вовсе. То есть, абсолютно никого — ни посетителей, ни пауков — тишина, ни одного запроса. Поиск причины опускаю (хотя он и не был довольно долгим). Причина была в том, что мой модем не зарегистрировался на сервисе DynDNS.

Итак, вводная.

Несколько лет назад я заключил договор на услугу доступа в интернет по технологии ADSL. После этого у меня на холодильнике появился мой домашний сервер. Но согласно договора у меня не было постоянного (фиксированного) IP-адреса. Чтобы в этом случае мой сайт был виден из интернета, я зарегистрировался на сервере DynDNS. Описание сервиса DynDNS не является целью данной заметки — нагуглить все ответы об этом любой желающий сможет буквально за пару секунд.

Клиент DynDNS присутствовал в программном обеспечении моего ADSL-модема. По этому, задача ежесуточной перерегистрации на DynDNS после смены IP адреса была возложена именно на него.

Но вернемся к моему сбою. Не могу сказать, чтобы я был недоволен моим модемом, моим провайдером или сервисом DynDNS. Но это был уже третий сбой, и я решил, что что-то все-таки нужно сделать! И не важно, что это был третий раз … за три года.

Первым делом нужно было решить — что делать в таком случае. Проверка показала, что если заходить в консоль модема и давать ему команду перезагрузки, то после этого регистрация на сервисе проходит успешно всегда. Таким образом, ответ на вопрос «Что делать?» был найден. Пришёл черед вопроса «Как делать?«…

1. Как сбрасывать модем

Итак, для программного сброса модема достаточно всего лишь войти в его консоль и ввести команду сброса. Одно лишь «но» — имя пользователя ввести — «ручками», пароль ввести — «ручками», команду сброса — опять же «ручками»… Осталось лишь решить, чьи же «ручки» будут все это вводить, когда система скрипт запустит? Уж точно не мои!

Можно долго биться головой об стену. А можно просто спросить совета у кого-то знающего. Так и сделал — спросил на форуме техподдержки моего модема (а кто его еще может знать лучше?). В итоге появилась на указанном форуме  вот такая тема: «Сброс W422G консольной командой«. Совет был краток — «man expect». Ввел — получил ответ, что не знает мой Linux «ничего такого». Раз не знает, значит скорее всего не установлено! Ввел следующую команду от имени root-а:

urpmi expect

Установился пакет. Снова спросил «man expect» — получил полторы тысячи строк мануала на английском. Читать было лень, спросил у Гугла. Нашел вот такой документ — «Интерактивные программы в скриптах UNIX (shell expect perl tcl python)«. Пример про использование expect из указанного документа был более чем нагляден, его я и начал переиначивать под собственные нужды… В итоге, за пару минут появился на свет вот такой скрипт modem_reboot.sh:

#!/usr/bin/expect

            spawn telnet 192.168.1.1
            expect ogin {send root\r}
            expect assword {send my_password\r}
            expect ommands {send reboot\r}
            expect eof

Быстренько пройдемся по его содержимому.

  • Первая строка (#!/usr/bin/expect) задает обработчик — вместо bash будет использован expect.
  • Строка «spawn telnet 192.168.1.1» указывает, что будет запущено исполнение (spawn) програмы telnet, которая должна подключиться к устройству с адресом 192.168.1.1. В данной строке «telnet» можно преспокойно поменять на «ssh» (в том случае, если к Вашему модему доступа по telnet нет, но есть по ssh). И также не забудьте при необходимости сменить IP-адрес модема.
  • Строка «expect ogin {send root\r}» указывает, что необходимо дождаться от модема сообщения, содержащего «ogin» (первая буква опущена, чтобы не угадывать, как именно модем спросит — с заглавной буквы или прописной — Login или login), и в ответ на запрос передать в модем имя пользователя, пытающегося подключиться к модему, и вдогонку — команду перевода каретки (\r — равносильно нажатию кнопки Entrer на клавиатуре). Пусть не смущает имя пользователя — в консоль модема входить нужно root-ом, даже если в веб-интерфейсе модема Вы вводите admin.
  • Следующая строка «expect assword {send my_password\r}» по аналогии указывает, что теперь необходимо дождаться от модема сообщения, содержащего «assword» (первая буква «шаблонного» слова Password или password также опущена), в ответ на него отослать в модем пароль (не забудьте вместо «my_password» подставить Ваш пароль), и опять же команду перевода каретки.
  • Последняя «значимая» строка (expect ommands {send reboot\r}) отсылает модему собственно команду перезагрузки (reboot). Изначально я вводил в скрипт просто строку send "reboot\r". Но потом вынужден был изменить ее до того вида, который собственно и представлен выше. Причина простая — после успешного логина пользователя shell модема стартовал с некоторой задержкой и выводил приглашение (показанное ниже). А команда перезагрузки отсылалась скриптом сразу же и как следствие — модемом «проглатывалась» безо всякой на нее реакции. В итоге, скрипт теперь команду перезагрузки отсылает только после того, как дождется своего «шаблона». В моем случае — это слово «commands«, которое, как это видно из приведенного ниже примера, является последним словом в приветствии модема:
        ADSL ROUTER ACORP W422G LAN 4-PORT/WiFi        *
*********************************************************

BusyBox v0.61.Acorp (2008.04.30-06:17+0000) Built-in shell (ash)
Enter 'help' for a list of built-in commands.

#

Итогом работы описанного скрипта является факт перезагрузки модема, чего собственно я и добивался. Таким образом, ответ на вопрос «Как?…» я нашел. С другой стороны, может возникнуть вопрос — а не перебор ли — перезагружать весь модем? Тем у кого модем такой же как у меня, можно почитать другую тему на уже упоминавшемся форуме — «Dynamic DNS client, проблемы с обновлением«. Там как раз есть команда перезапуска самой службы поддержки DynDNS. Кстати, как оказалось, в прошивках DD-WRT тоже используется таже программа — updatedd. Так что, желающие могут обойтись и без перезагрузки…

Ну а в моей «эпопее» пришел черед следующего вопроса — «Когда?».

2. Когда сбрасывать модем

Точнее сказать — вопрос был не в том, что «когда?». Вопрос заключался в том, как определить, что » уже пора»…

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

Тем не менее, первая модификация скрипта исправляла не его суть, а скажем так, надежность. Заключалась модернизация в том, что после нее пинговался не один лишь Гугл, а уже несколько различных серверов. В итоге команда на перезапуск модема давалась только в том случае, если были недоступны сразу все опрашиваемые сайты. То есть, был снижен риск срабатывания скрипта по причине того, что не работал опрашиваемый сервер (а с моим в это время было все хорошо). Но все же, этот скрипт по прежнему не в проверял — а виден ли мой сайт из интернета.

Следующая версия скрипта наконец-то делала именно то, что нужно — она проверяла — а виден ли мой сайт из интернета. Для этого было использовано такое явление интернета как «бесплатные прокси сервера». Итоговый скрип запрашивает страницу моего сайта через один из таких прокси-серверов. Если страница не доступна, попытка повторяется с использованием данных другого прокси-сервера, потом — третьего и т. д. Неудачей считается ситуация, когда страница недоступна при запросе с пяти различных прокси серверов. Именно после этого скрипт отдает команду на перезагрузку модема.

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

В итоге, у меня в компьютере добавилось два файла

Первый из них /root/proxylist.txt — это просто список прокси серверов, используемых при проверке. В нем идет простое построчное перечисление прокси серверов вида «123.123.123.123:8080» по одной записи в каждой строке. Где взять адреса прокси? Один сайт для примерауказан в скрипте (ниже), вот, например, еще один — http://www.bestproxylists.com/. Число строк в файле — 5. Именно на такое число рассчитан приведенный скрипт. Строк, на мой взгляд, чем больше, тем лучше, так как это снижает риск оказаться в ситуации, когда скрипт сработает не потому, что были проблемы с видимостью моего сайта, а потому что проблемы были у всех тех серверов, с которых я пытался опрашивать свой сайт.

Второй файл — /root/inet_check.sh собственно сам скрипт. Вот он:

#! /bin/bash

# Сайт, доступность которого будем проверять
URL=http://yoursite.dyndns.org/index.html
# Файл протокола
LOGFILE=/var/log/internet_test.log

# PROXYLIST file must be in format:
# ip:port
# EOLs must be LF but not CR or CRLF!
# Example:
# -- begin of example --
# 100.100.100.100:3128
# 100.100.100.101:3128
# -- end of example --
# Free proxies can be found at proxy4free.com
PROXYLIST="/root/proxylist.txt"

# Временный файл для ротации списка прокси
TEMPFILE="/root/newfile"

# Переменная, равная текущей дате и времени. Далее используется в записях файла протокола
TIMESTAMP=`date +%d-%m-%Y_%H:%M`

# Процедура ротации списка прокси, чтобы не дергать
# все время один и тот же сервер.
# Считываем первую строку списка прокси в
# (используемую по умолчанию) переменную REPLY
read < $PROXYLIST
# Последние четыре строки из файла списка прокси копируем в временный файл
# Если в файле списка прокси иное число строк (не 5), то нужно изменить
# число 4 на число, меньшее на единицу числа строк в списке прокси.
tail -4 $PROXYLIST > $TEMPFILE
# Переименовываем временный файл в файл списка прокси
mv -f $TEMPFILE $PROXYLIST
# Дописываем содержимое переменной в конец файла списка прокси
echo $REPLY >> $PROXYLIST

# Считываем построчно из файла списка прокси данные о прокси серверах...
for PROXY in $(cat $PROXYLIST)
do
# ...затем через прокси сервер запрашиваем проверяемый сайт и по результатам....
if http_proxy=$PROXY wget --connect-timeout=15 --no-dns-cache --no-cache --spider "$URL" &> /dev/null
# Если попытка удалась, пишем в лог ОК и выходим из скрипта
then echo "$TIMESTAMP - Site visible, All is OK! Proxy - $PROXY" >> $LOGFILE ; exit
# Если попытка неудачна, пишем в лог сообщение об этом и идем брать данные
# следующего прокси сервера
else echo "$TIMESTAMP - Proxy $PROXY - Site checking ERROR!"  >> $LOGFILE
fi
done
# Сюда мы добираемся, если все попытки оказались неудачными,
# а значит сайт действительно недоступен.
# Назначаем повторную проверку через 10 минут
at -f /root/inet_check.sh now +10 minutes
# Вызываем скрипт перезагрузки модема
/root/modem_reboot.sh
# Пишем в протокол сообщение о необходимости перезагрузки модема
echo "$TIMESTAMP - All proxy report - Site checking ERROR! Rebooting modem..."  >> $LOGFILE

Скрипт я постарался прокомментировать «по максимуму», так что, думаю вопросов по поводу него не возникнет…

Скрипт проверки вызывается автоматически через cron один раз в час. Если проверка оказалась неудачной и модем был перезагружен, то скрипт сам себе назначает следующую «внеплановую» проверку через 10 минут. И еще одно — скрипт осуществляет «ротацию» списка прокси серверов. Чтобы не долбить все время в один и тот же прокси сервер, при каждом запуске проверки адрес прокси сервера, указанный в файла списка первым перемещается на последнюю строку.

P.S. Немного изменил скрипт. Добавил проверку того, что скрипт не выполняется повторно. Также сделал определение строк в файле списка прокси.  Если предыдущий скрипт был написан так, что в файле списка прокси ОБЯЗАТЕЛЬНО должно было быть РОВНО 5 значений, то в новой редакции это число может варьироваться. Единственное — их (строк с указанием адресов прокси) число должно быть не более 9.

#! /bin/bash

#  Имя м расположение файла протокола
LOGFILE=/var/log/internet_test.log
# Имя и расположение файла-"семафора"
LOCKFILE=/tmp/inet_check.lock
# Сайт, доступность которого будем проверять
URL=http://yoursite.dyndns.org/index.html

# Проверка того, что скрипт не запущен повторно
if lockfile -r2 $LOCKFILE  &> /dev/null
then echo "OK" # любая команда, нужна для синтаксиса
else echo "Already running" >> $LOGFILE ; exit
fi

# Файл списка прокси должен содержать не более 9 строк вида:
# ip:port
# В конце строки должен быть LF а не CR или CRLF!
# Например:
# -- begin of example --
# 100.100.100.100:3128
# 100.100.100.101:3128
# -- end of example --
# Список бесплатных прокси можно найти на сайте proxy4free.com
PROXYLIST="/root/proxylist.txt"

# Временный файл, используемый для ротации списка прокси
TEMPFILE="/root/newfile"

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

# Сначала определяем число строк в фыайде со списком прокси
LINES_COUNT=`wc --lines $PROXYLIST | grep -o [0-999]`
# Уменьшаем полученное значение переменной на 1
let LINES_COUNT=($LINES_COUNT-1)

# Считываем первую строку списка прокси в
# (используемую по умолчанию) переменную REPLY
read < $PROXYLIST # Копируем во временный файл все последние строки, кромепервой. tail -$LINES_COUNT $PROXYLIST > $TEMPFILE
# Переименовываем временный файл в файл списка прокси
mv -f $TEMPFILE $PROXYLIST
# Дописываем содержимое переменной в конец файла списка прокси
echo $REPLY >> $PROXYLIST

# Считываем построчно из файла списка прокси данные о прокси серверах...
for PROXY in $(cat $PROXYLIST)
do
# ...затем через прокси сервер запрашиваем проверяемый сайт и по результатам....
if http_proxy=$PROXY wget --connect-timeout=15 --no-dns-cache --no-cache --spider "$URL" &> /dev/null
# Если попытка удалась, пишем в лог ОК ..
then echo "$(date +%d-%m-%Y_%H:%M) - Site visible, All is OK! Proxy - $PROXY" >> $LOGFILE
# Удаляем файл-семафор
rm -f $LOCKFILE
# и выходим
exit
# Если попытка неудачна, пишем в лог сообщение об этом и идем брать данные
# следующего прокси сервера
else echo "$(date +%d-%m-%Y_%H:%M) - Proxy $PROXY - Site checking ERROR!"  >> $LOGFILE
fi
done
# Если все попытки оказались неудачными, значит сайт действительно недоступен
# Первым делом назначаем повторную проверку через 10 минут
at -f /root/inet_check.sh now +10 minutes
# Выводим в лог сообщение об ошибке проверки со всех прокси и о перезегрузке модема
echo "$(date +%d-%m-%Y_%H:%M) - All proxy report - Site checking ERROR! Modem will be rebooted now..."  >> $LOGFILE
# Удаляем файл-семафор
rm -f $LOCKFILE
# Вызываем скрипт перезагрузки модема
/root/modem_reboot.sh

Еще подкорректировал скрипт в части строки запуска команды wget. Теперь она у меня такая:

if http_proxy=$PROXY wget --connect-timeout=15 --tries=5 --inet4-only --no-dns-cache --no-cache --spider "$URL" &> /dev/null

Как видите, добавилось два ключа:
--tries=5 — ограничивает число попыток
--inet4-only — запрещает использование протокола IP-v6