- Публичный IP-адрес. Ну, или если оборудование находится за чьим-то NAT-ом, то публичный IP и «проброшенный» порт.
- Туннель (PPTP/OpenVPN/L2TP+IPSec и т.д.) до центрального узла, через который была бы доступна.
Поэтому «мой велосипед» потребуется Вам, когда стандартные методы Вам не подходят, например:
- Оборудование находится за NAT-ом и кроме обычного http (80-го порта) — все закрыто. Вполне нормальная ситуация для крупных федеральных корпоративных сетей. Прописать порты — могут, но не сразу, не быстро и не Вам.
- Нестабильный и/или «узкий» канал связи. Маленькая скорость, постоянные потери. Боль и разочарование при попытке организовать туннель.
- Дорогой канал связи, где буквально каждый мегабайт на счету. Например спутниковая связь. Плюс большие задержки и «узкая» полоса.
- Ситуация, когда Вам надо «жонглировать» большим количеством маленьких роутеров, на которых с одной стороны установлена OpenWrt/Lede для расширения возможностей, а с другой стороны ресурсов (памяти) роутера хватает далеко не на все.
Чаще всего требования к стоимости решения в целом, но иногда ключевую роль играет и форм-фактор. Например, на объекте стоит TP-Link ML3020, его единственный USB-порт используется под 2G/3G модем, все это завернуто в какой-нить небольшой пластиковый корпус и размещено где-то высоко-высоко (на мачте), далеко-далеко (в поле, в 30 км. от ближайшей базовой станции мобильного оператора). Да, можно воткнуть USB-hub и расширить число портов, но опыт показывает что это громоздко и ненадежно.
Итак, я постарался описать Вам мою типовую ситуацию: «где-то далеко-далеко, стоит очень важный, одинокий и маленький роутер под управлением Linux. Важно знать хотя бы раз в день, что он „жив“ и при необходимости отсылась ему команды, например „солнышко, перезагрузись!“
Перейдем к реализации:
1) На стороне роутера по cron-у каждые 5/10/1440 минут, или когда угодно необходимо отсылать http-запрос на сервер с помощью wget, результат запроса сохранять в файл, файл делать исполняемым, и исполнять его.
У меня строчка в cron-е выглядит примерно так:
Файл /etc/crontabs/root:
*/5 * * * * wget "http://xn--80abgfbdwanb2akugdrd3a2e5gsbj.xn--p1ai/a.php?u=user&p=password" -O /tmp/wa.sh && chmod 777 /tmp/wa.sh && /tmp/wa.sh
, где:
xn--80abgfbdwanb2akugdrd3a2e5gsbj.xn--p1ai — домен моего сервера. Сразу замечу: да, можно указать и конкретный ip-адрес сервера, мы так раньше делали, пока наше государство, в праведном порыве борьбы нескажусчемнезнаю — не закрыло доступ к львиной доле „облаков“ DigitalOcean и Amazon. В случае использования символьного домена, при возникновении подобного казуса, вы спокойно сможете поднять резервное облако, перенаправить на него домен и восстановить мониторинг устройств.
a.php — имя скрипта на стороне сервера. Да, я знаю, что это неправильно, называть переменные и имена файлов одной буквой… предлагаю считать, что так мы экономим несколько байт при отправке запроса :)
u — имя пользователя, логин железки
p — пароль
„-O /tmp/wa.sh“ — файл на удаленном роутере, куда будет сохранятся ответ сервера, например команда reboot.
2) На стороне сервера (у меня это Ubuntu) мы будем использовать Zabbix. Почему: хочу чтобы было красиво (с графиками) и удобно (отправлять команды через контекстное меню). У Заббикса есть такая прелестная вещь, как zabbix-агент. Через агента мы будем вызывать php-скрипт на сервере, который будет возвращать информацию о том, регистрировался ли наш роутер в требуемый период времени. Для хранения информации о времени регистрации, командах для устройств, я использую MySQL, отдельную таблицу users примерно с такими полями:
CREATE TABLE `users` (
`id` varchar(25) NOT NULL,
`passwd` varchar(25) NOT NULL,
`description` varchar(150) NOT NULL,
`category` varchar(30) NOT NULL,
`status` varchar(10) NOT NULL,
`last_time` varchar(20) NOT NULL, // время последнего соединения
`last_ip` varchar(20) NOT NULL, // IP последнего соединения
`last_port` int(11) NOT NULL, // порт последнего соединения
`task` text NOT NULL, // задача которую получает роутер
`reg_task` varchar(150) NOT NULL, // "регулярная" задача, если мы захотим чтобы задача выполнялась всегда при регистрации
`last_task` text NOT NULL, // лог задач
`response` text NOT NULL, // сюда пишется ответ устройства
`seq` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Все исходники можно забрать с Git-репозитория, по адресу: https://github.com/BazDen/iotnet.online.git
Теперь PHP-скрипты, размещаемые на стороне сервера(для удобства их можно положить в папку /usr/share/zabbix/):
Файл a.php:
<?php
// Получаем входные параметры: имя пользователя, пароль и сообщение от удаленного роутера
// Зачем нужен message ? Это способ ответа роутера, например если вы захотите посмотреть содержимое файла роутера
$user=$_REQUEST['u'];
$password=$_REQUEST['p'];
$message=$_REQUEST['m'];
// Подключаемся к нашей базе данных (MySQL)
$conn=new mysqli("localhost","db_login","db_password","DB_name");
if (mysqli_connect_errno()) {
exit();
}
$conn->set_charset("utf8");
// здесь ищем наш роутер в таблице базы данных
$sql_users=$conn->prepare("SELECT task, reg_task, response, last_time FROM users WHERE id=? AND passwd=? AND status='active';");
$sql_users->bind_param('ss', $user, $password);
$sql_users->bind_result($task, $reg_task, $response, $last_time);
$sql_users->execute();
$sql_users->store_result();
if (($sql_users->num_rows)==1){
$sql_users->fetch();
// здесь мы роутеру отправляем его задачи
echo $task;
echo "\n";
echo $reg_task;
// вот здесь мы пишем время ответа и сам ответ роутера
$response_history="[".date("Y-m-d H:i")."] ".$message;
// задачу отправили, теперь надо ее удалить,а после удаления отметить в логах, что такая-то задача выполнена
$last_ip=$_SERVER["REMOTE_ADDR"];
$last_port=$_SERVER["REMOTE_PORT"];
$ts_last_conn_time=$last_time;
$sql_users=$conn->prepare("UPDATE users SET task='', seq=1 WHERE (id=?);");
$sql_users->bind_param('s', $user);
$sql_users->execute();
if (strlen($message)>1){
$sql_users=$conn->prepare("UPDATE users SET response=?, seq=1 WHERE (id=?);");
$sql_users->bind_param('ss', $response_history, $user);
$sql_users->execute();
}
// теперь надо сохранить время регистрации пользователя, его айпи и сообщение от него. Пока только сообщение
$ts_now=time();
$sql_users=$conn->prepare("UPDATE users SET last_time=?, last_ip=?, last_port=? WHERE (id=?);");
$sql_users->bind_param('ssss', $ts_now, $last_ip, $last_port, $user);
$sql_users->execute();
}
// если мы не нашли роутер в нашей базе данных, или его статус "неактивный", то ему ... будет отправлена команда reboot....
// Почему так жестоко ? Потому что роутеры иногда пропадают, а это маленький способ проучить "новых владельцев".
else
{
echo "reboot";
}
$sql_users->close();
?>
Файл agent.php (это скрипт вызываемого zabbix-агента):
<?php
// файл агента Zabbix. Данный скрипт обращается к таблице users и получает "1" если устройство регистрировалось с момента последнего обращения
// user и password - учетные данные оборудования
$user = $argv[1];
$password = $argv[2];
// подключаемся к нашей базе данных
$conn=new mysqli("localhost","db_user","db_password","db_name");
if (mysqli_connect_errno()) {
exit();
}
$conn->set_charset("utf8");
$sql_users=$conn->prepare("SELECT seq FROM users WHERE id=? AND passwd=? AND status='active';");
$sql_users->bind_param('ss', $user, $password);
$sql_users->bind_result($seq);
$sql_users->execute();
$sql_users->store_result();
// обмен данными происходит через поле seq. При регистрации железка ставит данное поле в "1"
if (($sql_users->num_rows)==1){
$sql_users->fetch();
echo $seq;
}
// обнуляем $seq.
$sql_users=$conn->prepare("UPDATE users SET seq=0 WHERE id=? AND passwd=? AND status='active';");
$sql_users->bind_param('ss', $user, $password);
$sql_users->execute();
$sql_users->close();
?>
Ну и заключительный этап: прописание агента и добавление графиков.
Если у Вас еще не установлен zabbix-агент, то:
apt-get install zabbix-agent
Редактируем файл /etc/zabbix/zabbix_agentd.conf.
Добавляем строку:
UserParameter=test,php /usr/share/zabbix/agent.php user password
, где:
test — имя нашего агента
„php /usr/share/zabbix/agent.php user password“ — вызываемый скрипт с указанием регистрационных данных устройства.
Добавление графиков: открываем web-интерфейс zabbix, в меню выбираем:
Настройка -> Узлы сети -> Создать узел сети. Здесь достаточно указать имя узла сети, его группу, интерфейс агента по умолчанию:
Теперь нам для данного узла сети надо добавить элемент данных. Обратите внимание на два поля: „ключ“ — это как раз тот параметр, что мы прописывали в файле /etc/zabbix/zabbix_agentd.conf (в нашем случае это test), и „интервал обновления“ — я ставлю 5 минут, потому как и оборудование регистрируется на сервере тоже один раз в пять минут.
Ну и добавляем график. Рекомендую в качестве стиля отрисовки выбрать „Заполнение“.
На выходе получается нечто очень лаконичное, например вот так:
На резонный вопрос: „и это того стоило?“, отвечу: ну конечно, смотрите „причины создания велосипеда“ в начале статьи.
Если мой первый графоманский опыт вызовет интерес читателей, то в следующих статьях я хочу описать как отправлять команды на удаленное оборудование. Также удалось реализовать всю схему и для устройств на базе RouterOS (Mikrotik-ов).
Комментарии (14)
acyp
27.03.2019 17:18Мне кажется некоторый логический изъян. Допустим модем не зарегистрировался, но это означает. что он не выполнил wget, а значит и не скачал скрипт. Т.е. ребутнуть его уже возможности нет… или я что-то упускаю?
nochkin
27.03.2019 17:45Видимо, «reboot» это просто как пример. Понятное дело, что если роутер завис полностью, то и команду выполнить он не сможет.
acyp
27.03.2019 17:56Есть менее оригинальные способы узнать есть ли у нас роутер на связи… Я не к тому, что статья некорректная. Сам отпахал 4 года на Крайнем Севере и знаю на сколько важно понимать «загнулось» ли оборудование и в какой стадии. И статью в закладки добавил.
Но вдруг я что-то упускаю, и это решение? Вопрос то к автору был…bazden Автор
28.03.2019 07:16По поводу невозможности модема зарегистрироваться — это больше вопрос методологии. Поясню: когда планируется развертывание сети подобных устройств: подбирается одна модель роутера, одна модель модема и одна стабильная версия прошивки OpenWRT/Lede/ЧтоТоСвое. Все это тестируется, причем не только по софту но и хардкорно, например «а что будет если выдернуть модем на рабочем роутере и вставить его обратно без перезагрузки?». Если позволяет время и клиент, то 1-2 тестовых образца ставятся уже непосредственно на местности и за ними продолжается наблюдение 1-2 недели. Конкретно по модему: Во-первых делаю «обвязку» bash-скриптов на случай, если в течение 10 минут модем «не вышел на связь», например «подними-ка еще разочек pptp интерфейс, а ?», во-вторых в прошивке предусматривается строки инициализации для всех моделей модемов.
acyp
28.03.2019 07:31Ладно. Получается я понял правильно. Основная идея как оставить оборудование на связи в том, что в самом роутере запилить крон-задачу, которая тем или иным способом проверяет состояние модема и ребутит его (при необходимости). (часть пропущена за своей простотой).
На стороне сервера просто проверяется скачивался ли скрипт и во сколько.
Теперь правильно?bazden Автор
28.03.2019 07:44Верно. И если пойти дальше то все взаимодействие между удаленным устройством и сервером должно быть сведено к следующему диалогу:
Устройство: «Привет, я железка такая-то. Поручения будут ?»
Сервер: «Принято, записал что ты „живой“. Поручений нет».
Ну а по поводу команд — да, семь бед — один ответ/reboot. Но не только. Может быть и вот такой ответ сервера:
Сервер: «Принято записал что ты живо. Поручение: „попингай вот это->вывод команды в файл->содержимое файла пришли мне“
alammer
28.03.2019 07:26Хотелось бы в тему спросить — а кто-нибудь игрался на openwrt c пакетом usbreset? В ситуации с отваливанием того же модема он срабатывает как аналог физического переподключения модема в порт? А то в свое время руки не дошли, а сейчас уже роутеры далеко и эксперементировать с ними удалённо не хотелось бы.
Sly_tom_cat
28.03.2019 11:27На сколько я понял эта фича не во всех роутерах работает — там там одним из GPIO рулится шина питания на USB разъеме дополнительным ключем/транзистором.
У приведенного для примера TP-LINK ML3020 такой ключик стоит, но мне попадались роутеры где питание внешнего USB заведено на шину +5 напрямую. Хотя может быть это очень старые роутеры были…nochkin
28.03.2019 18:24Обычно питание можно дёрнуть USB контроллером. Даже банальной записью 0/1 через /sys/bus/usb/devices/…
Если заведено питание напрямую, то им и reboot особо не поможет.
osipov_dv
Логин и пароль открытым текстом в URL? Месье знает толк в извращениях… Тут даже https не спасет. Почему бы не сделать через POST\GET за https?
Andrusha
Это статья из песочницы, и, подозреваю, что, как и многие предыдущие, она там слишком долго пролежала. Но ответ на ваш вопрос содержится в блоке «Примечание номер два».
nochkin
Вопрос был про POST. wget умеет POST без увеличения размера.
bazden Автор
Вы говорите про параметр --post-data для wget? В составе прошивок OpenWrt/Lede для мелких роутеров с памятью 4МБ wget идет без этой опции. Ну точнее на момент сбора прошивки вы вольны выбирать wget с какими опциями включать, но «нэ лезет». И это не совсем извращение — мы порой простой не знаем, с какими роутерами и с какой прошивкой придется работать, поэтому делаем «универсальный велосипед». А bash и wget (хоть и ужатый) — есть практически везде.