python

Одновременный speedtest на нескольких LTE-модемах

  • четверг, 9 июля 2020 г. в 00:26:44
https://habr.com/ru/company/ruvds/blog/510046/
  • Блог компании RUVDS.com
  • Настройка Linux
  • Python
  • *nix
  • Разработка под Linux


На карантине мне предложили поучаствовать в разработке устройства измерения скорости LTE-модемов для нескольких операторов сотовой связи.



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

Сразу скажу, что задача не самая простая и наукоемкая, расскажу, какие проблемы мне встретились и как я их решал. Итак, поехали.

Примечание


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

Изначально заказчик хотел просто гонять курьера с телефонами операторов, проводить измерения прямо на телефоне и далее в тетрадку записывать результаты измерения скорости. Мое решение измерения скорости сетей lte, хоть не идеально, но решает поставленную задачу.

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

Техническое задание


Как сказано в статье Без ТЗ: почему клиент не хочет его: Не работайте без ТЗ! Никогда, нигде!

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

На базе одноплатного компьютера vim2 сделать тестер скорости lte-соединения через модемы Huawei e3372h — 153 нескольких операторов связи (от одного до n). Так же необходимо получать координаты с GPS-приемника, подключенного по UART. Замеры скорости производить с помощью сервиса www.speedtest.net и сводить их в таблицу вида:



Таблица в формате csv. После чего отсылать на е-майл каждые 6 часов данную табличку. В случае возникновения ошибок мигать светодиодом, который подключен к GPIO.

ТЗ я описал в вольной форме, после множества согласований. Но смысл задачи уже виден. Срок на все про все был дан неделя. Но в реальности он растянулся на три недели. Это с учетом того, что я делал это только после основной работы и по выходным.

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

Архитектура и разработка


Схема проста и очевидна. Поэтому оставлю ее без особых комментариев.



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

Также в процессе открыл, что python имеет две ходовые версии 2 и 3, в результате остановился на третьей.

Аппаратные узлы


Одноплатник vim2


В качестве основной машины мне был дан одноплатник vim2



Отличный, мощный медиакомбайн для умного дома и SMART-TV, но на редкость неподходящий для данной задачи, или скажем так, слабо подходящий. Например, его главная ОС — это Android, а Linux — это попутная ОС, и соответственно никто не гарантирует качественной работы всех узлов и драйверов под Linux. И я предполагаю, что часть проблем была связана с драйверами USB данной платформы, поэтому модемы работали на данной плате не так как ожидал. Так же у него очень плохая и разрозненная документация, поэтому каждая операция занимала много времени копания в доках. Даже рядовая работа с GPIO попила много крови. Например, чтобы настроить работу со светодиодом, мне понадобилось несколько часов. Но, если быть объективным, то принципиально не было важно, что за одноплатник, главное, чтобы работал и были USB-порты.

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

Есть два варианта установить линукс: на внешнюю SD-карту, либо на внутреннюю MMC. С картой я побился вечерок, так и не вкурил как же заставить работать, поэтому решил устанавливать на MMC, хотя без сомнения с внешней картой много проще было бы работать.

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

  • Tool Pin GND: <—> Pin17 of VIMs’s GPIO
  • Tool Pin TXD: <—> Pin18 of VIMs’s GPIO (Linux_Rx)
  • Tool Pin RXD: <—> Pin19 of VIMs’s GPIO (Linux_Tx)
  • Tool Pin VCC: <—> Pin20 of VIMs’s GPIO



После чего, я скачал прошивку отсюда. Конкретная версия прошивки VIM1_Ubuntu-server-bionic_Linux-4.9_arm64_EMMC_V20191231.

Для того, чтобы залить данную прошивку, мне необходимы нужны утилиты. Более подробно об этом рассказано тут. Под Windows не пробовал прошивать, а вот о прошивке под Linux надо пару слов рассказать. Для начала установлю утилиты, согласно инструкции.

git clone https://github.com/khadas/utils
cd /path/to/utils
sudo ./INSTALL

Иии… Ничего не работает. Потратил пару часов занимаясь правками установочных скриптов, чтобы все корректно установилось у меня. Что там делал не помню, но тоже еще тот цирк с конями. Так что будьте осторожны. Но без этих утилит дальше мучить vim2 смысла нет. Лучше с ним вообще не связываться!

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



При загрузке VIM2 в терминале UART нажимаю какую-либо клавишу, например пробел, чтобы остановить загрузку. После того, как появится строка

kvim2# 

Ввожу команду:

kvim2# run update

На хосте, откуда загружаем, выполняю:

burn-tool -v aml -b VIM2 -i  VIM2_Ubuntu-server-bionic_Linux-4.9_arm64_EMMC_V20191231.img

Все, фух. Прошил, на плате есть Linux. Логин/пароль khadas:khadas.

После этого небольшие первичные настройки. Для дальнейшей работы отключаю пароль у sudo (да, не безопасно, но удобно).

sudo visudo

Редактирую строку до вида и сохраняем

# Allow members of group sudo to execute any command
%sudo ALL=(ALL:ALL) NOPASSWD: ALL

После чего меняю текущую локаль, чтобы время было по Москве, иначе будет по Гринвичу.

sudo timedatectl set-timezone Europe/Moscow

либо

ln -s /usr/share/zoneinfo/Europe/Moscow /etc/localtime

Если вам показалось сложно, то не пользуйтесь данной платой, лучше Raspberry Pi. Честно.

Модем Huawei e3372h — 153


Данный модем у меня попил крови знатно, и, по сути, он и стал самым узким местом всего проекта. Вообще, название “модем” для данных устройств совершенно не отражает суть работы: это мощнейший комбайн, эта железяка имеет составное устройство, которое прикидывается CD-ROM для того, чтобы установить драйвера, а потом переходит в режим сетевой карты.

Архитектурно, с точки зрения пользователя Linux после всех настроек, выглядит так: после подключения модема, у меня появляется сетевой интерфейс eth*, который по dhcp получает ip адрес 192.168.8.100, и шлюз по умолчанию 192.168.8.1.

И самый главный момент! Данная модель модема, не умеет работать в режиме именно модема, который управляется АТ-командами. Все было бы сильно проще, создать ppp-соединения на каждый модем и дальше уже оперировать с ними. Но в моем случае «сам» (точнее дайвера Linux согласно правилам udev), создает eth-интерфейс и по dhcp назначают ему ip-адрес.

Чтобы дальше не путаться, предлагаю забыть слово «модем» и говорить сетевая карта и шлюз, ибо по сути, это как подключение новой сетевой карты со шлюзом.
Когда один модем, это не вызывает особых проблем, но когда их больше одного, а именно n-штук, то возникает следующая картина сети.



То есть n сетевых карт, с одним IP-адресом, у каждого один и тот же шлюз по умолчанию. Но по факту, каждый из них подключен к своему оператору.

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

Поэтому я выбрал путь менять «вручную» ip-адреса модемов и дальше гонять трафик с помощью настроек маршрутизации.



На этом у меня проблемы с модемами не закончились: в случае проблем с питанием, они отваливались, требовалось хорошее стабильное питание USB-хаба. Эту проблему решил жестко припаяв питание прямо к хабу. Другая проблема, с которой я столкнулся и которая погубила весь проект: после перезагрузки или холодного старта устройства определялись не все модемы и не всегда, и почему это происходило и по какому алгоритму мне установить не удалось. Но обо всем по порядку.

Для корректной работы модема, я установил пакет usb-modeswitch.

sudo apt update
sudo apt install -y usb-modeswitch

После чего, модем после подключения будет корректно определяться и конфигурироваться подсистемой udev. Проверяю, просто подключив модем и убедившись, что сеть появилась.
Еще одна проблема, которую я не смог решить: это как из этого модема получить имя оператора, с которым мы работаем? Имя оператора содержится в веб-интерфейсе модема по адресу 192.168.8.1. Это динамическая веб-страница, которая получает данные посредством ajax-запросов, поэтому просто wget-тнуть страницу и спарсить имя не получится. Поэтому начал смотреть, как отработать web-страницу и т.п., и понял, что занимаюсь какой-то ерундой. В результате плюнул, и оператора начал получать с помощью API самого Speedtest.

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

GPS


GPS-приемник, который мне выдали, имел интерфейс UART и питание. Это было не самое лучшее решение, но тем не менее рабочее и простое. Приемник был примерно такого вида.



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

Для начала включаю uart_AO_B (UART_RX_AO_B, UART_TX_AO_B) для подключения GPS.

khadas@Khadas:~$ sudo fdtput -t s /dtb.img /serial@c81004e0 status okay

После проверяю успешность операции.

khadas@Khadas:~$ fdtget /dtb.img /serial@c81004e0 status
okay

Данная команда, судя по всему, на лету редактирует devtree, что весьма удобно.

После успеха этой операции перезагружаемся и устанавливаем gps-демон.

khadas@Khadas:~$ sudo reboot

Установка gps-демона. Устанавливаю все и отрубаю его сразу для дальнейшей конфигурации.

sudo apt install gpsd gpsd-clients -y
sudo killall gpsd
 
/* GPS daemon stop/disable */
sudo systemctl stop gpsd.socket
sudo systemctl disable gpsd.socket

Редактирую файл настроек.

sudo vim /etc/default/gpsd

Устанавливаю UART, на котором будет висеть GPS.

DEVICES="/dev/ttyS4"

И после все включаем и стартуем.

/* GPS daemon enable/start */
sudo systemctl enable gpsd.socket
sudo systemctl start gpsd.socket

После чего, подключаю GPS.



В руках провод GPS, под пальцами видны провода UART отладчика.

Перезагружаюсь, и проверяю работу GPS с помощью программы gpsmon.



На этом скриншоте спутников не видать, но видно общение с GPS-приемником, и это говорит, что все хорошо.

На python опробовал много вариантов работы с данным демоном, но я остановился на том, который корректно работал с python 3.

Устанавливаю необходимую библиотеку.

sudo -H pip3 install gps3 

И ваяю код работы.

from gps3.agps3threaded import AGPS3mechanism
...

def getPositionData(agps_thread):
	counter = 0;
	while True:
		longitude = agps_thread.data_stream.lon
		latitude = agps_thread.data_stream.lat
		if latitude != 'n/a' and longitude != 'n/a':
			return '{}' .format(longitude), '{}' .format(latitude)
		counter = counter + 1
		print ("Wait gps counter = %d" % counter)
		if counter == 10:
			ErrorMessage("Ошибка GPS приемника!!!")
			return "NA", "NA"
		time.sleep(1.0)
...
f __name__ == '__main__':
...
	#gps
	agps_thread = AGPS3mechanism()  # Instantiate AGPS3 Mechanisms
	agps_thread.stream_data()  # From localhost (), or other hosts, by example, (host='gps.ddns.net')
	agps_thread.run_thread()  # Throttle time to sleep after an empty lookup, default '()' 0.2 two tenths of a second

Если мне нужно получить координаты, то делается это следующим вызовом:

longitude, latitude = getPositionData(agps_thread)

И в течении 1-10 секунд я либо получу координату, либо нет. Да, у меня попыток получить координаты было десять. Не оптимально, криво и косо, но работает. Я решил так сделать, потому что GPS может ловить плохо и не всегда получать данные. Если ждать получения данных, то в случае работы в глухом помещении, программа зависнет в это месте. Поэтому реализовал такой не элегантный вариант.

В принципе, было бы больше времени, можно было бы напрямую по UART получать данные с GPS, парсить их в отдельном потоке и работать с ними. Но времени не было совсем, отсюда лютый некрасивый код. И да, мне не стыдно.

Светодиод


С подключением светодиода было все просто и сложно одновременно. Главная сложность в том, что номер пина в системе не соответствует номеру пина на плате и потому что документация написана левой пяткой. Чтобы сопоставить номер аппаратного пина и номер пина в ОС, надо выполнить команду:

gpio readall

Будет выведена таблица соответствия пина в системе, и на плате. После чего я уже могу оперировать пином в самой ОС. В моем случае светодиод подключен к GPIOH_5.



Перевожу пин GPIO в режим вывода.

gpio -g mode 421 out

Записываю нуль.

gpio -g write 421 0

Записываю единицу.

gpio -g write 421 1


Все горит, после записи «1»

#gpio subsistem
def gpio_init():
	os.system("gpio -g mode 421 out")
	os.system("gpio -g write 421 1")

def gpio_set(val):
	os.system("gpio -g write 421 %d" % val)
	
def error_blink():
	gpio_set(0)
	time.sleep(0.1)
	gpio_set(1)
	time.sleep(0.1)
	gpio_set(0)
	time.sleep(0.1)
	gpio_set(1)
	time.sleep(0.1)
	gpio_set(0)
	time.sleep(1.0)
	gpio_set(1)

def good_blink():
	gpio_set(1)

Теперь, в случае ошибок я вызываю error_blink() и светодиод нам красиво помигает.

Программные узлы


Speedtest API


Большая радость, что у сервиса speedtest.net есть свой собственный python-API, посмотреть можно на Github.

Чем хорошо, что есть исходные коды, которые тоже можно посмотреть. Как работать с данным API (простейшие примеры) можно посмотреть в соответствующем разделе.

Устанавливаю python-библиотеку следующей командой.

sudo -H pip3 install speedtest-cli

Для примера вы можете вообще поставить спидтестер в Ubuntu прямо из реп. Это тоже самое python-приложение, которое потом можно запустить прямо из консоли.

sudo apt install speedtest-cli -y

И произвести замеры скорости вашего интернета.

speedtest-cli
Retrieving speedtest.net configuration...
Testing from B***** (*.*.*.*)...
Retrieving speedtest.net server list...
Selecting best server based on ping...
Hosted by MTS (Moscow) [0.12 km]: 11.8 ms
Testing download speed................................................................................
Download: 7.10 Mbit/s
Testing upload speed......................................................................................................
Upload: 3.86 Mbit/s


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

import speedtest
from datetime import datetime
...
#Указываем конкретный сервер для теста
#6053) MaximaTelecom (Moscow, Russian Federation)
servers = ["6053"]
# If you want to use a single threaded test
threads = None
s = speedtest.Speedtest()
#получаем имя оператора сотовой связи
opos = '%(isp)s' % s.config['client']
s.get_servers(servers)
#получаем текстовую строку с параметрами сервера
testserver = '%(sponsor)s (%(name)s) [%(d)0.2f km]: %(latency)s ms' % s.results.server
#тест загрузки
s.download(threads=threads)
#тест выгрузки
s.upload(threads=threads)
#получаем результаты
s.results.share()

#После чего формируется строка для записи в csv-файл.
#получаем позицию GPS
longitude, latitude = getPositionData(agps_thread)
#время и дата
curdata = datetime.now().strftime('%d.%m.%Y')
curtime = datetime.now().strftime('%H:%M:%S')
delimiter = ';'
result_string = opos + delimiter + str(curpos) + delimiter + \
	curdata + delimiter + curtime + delimiter + longitude + ', ' + latitude + delimiter + \
	str(s.results.download/1000.0/1000.0) + delimiter + str(s.results.upload / 1000.0 / 1000.0) + \
	delimiter + str(s.results.ping) + delimiter + testserver + "\n"
#тут идет запись в файл логов

Здесь тоже оказалось не так все просто, хотя, казалось бы, куда проще. Изначально параметр servers у меня был равен [], мол выбери лучший сервер. В результате у меня были случайные сервера, и как нетрудно догадаться, плавающая скорость. Это достаточно сложная тема, использовать фиксированный сервер, если да, то статический или динамический, требует исследования. Но вот пример графиков замеров скорости оператора Билайна при динамическом выборе тестового сервера и статически зафиксированного.


Результат измерения скорости при выборе динамического сервера.


Результат тестирования скорости, при одном строго выбранном одном сервере.

«Шерсть» при тестировании есть и там и там, и ее нужно убирать математическими методами. Но при фиксированном сервере ее немного меньше и амплитуда стабильнее.
Вообще это место больших исследований. И я бы проводил замеры скорости к своему серверу, по средством утилиты iperf. Но мы придерживаемся от ТЗ.

Отправка почты и ошибок


Для отправки почты попробовал несколько десятков различных вариантов, но в результате остановился на следующем. Зарегистрировал почтовый ящик на yandex и далее взял данный пример отправки почты. Проверил его и внедрил в программу. В этом примере разбираются различные варианты, в том числе отправка с gmail и т.п. Возится с поднятием своего почтового сервера мне не хотелось и не было времени на это, но как потом оказалось тоже напрасно.

Отправка логов производил по планировщику, при наличии связи, каждые 6 часов: в 00 часов, 06 утра, 12 дня и 18 вечера. Отправлял следующим образом.

from send_email import *
...
message_log = "Логи тестирования платы №1"
EmailForSend = ["dlinyj@trololo.ru", "pupkin@trololo.ru"]
files = ["/home/khadas/modems_speedtest/csv"]
...
def sendLogs():
	global EmailForSend
	curdata = datetime.now().strftime('%d.%m.%Y')
	сurtime = datetime.now().strftime('%H:%M:%S')
	try:
		for addr_to in EmailForSend:
			send_email(addr_to, message_log, "Логи за " + curdata + " " + сurtime, files)
	except:
		print("Network problem for send mail")
		return False
	return True

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

Сервер обратной связи


Для того, чтобы иметь доступ к удаленной железке и иметь возможность ее донастроить и переконфигурировать мне понадобился внешний сервер. Вообще, справедливости говоря, правильно было бы все данные отправлять на сервер и в веб-интерфейсе строить все красивые графики. Но не все сразу.

В качестве VPS я выбрал ruvds.com. Можно было бы взять самый простой сервер. И в целом для моих бы целей этого хватило бы за глаза. Но поскольку платил за сервер не из своего кармана, решил взять с небольшим запасом, чтобы хватило, если будем разворачивать web-интерфейс, свой SMTP-сервер, vpn и т.д. Плюс иметь возможность настроить Telegram-бота и не иметь проблем с его блокировками. Поэтому выбрал Amsterdam и следующие параметры.



В качестве способа связи с железкой vim2 выбрал обратное ssh соединение и как показала практика — не самое лучшее. При разрыве соединения, сервер удерживает порт и по нему невозможно подключиться некоторое время. Поэтому, все же лучше использовать другие способы связи, например vpn. В будущем хотел перейти на vpn, но не успел.

Не буду вдаваться в подробности настройки файрвола, ограничения прав, отключения ssh соединения root и прочие прописные истины настройки VPS. Хочется верить, что вы и так все знаете. Для удаленного соединения, создаю нового пользователя на сервере.

adduser vimssh

На нашей железке генерирую ключи ssh соединения.

ssh-keygen

И копирую их на наш сервер.

ssh-copy-id vimssh@host.com

На нашей железке создаю автоматическое подключение обратного ssh при каждой загрузке.

[Unit]
Description=Auto Reverse SSH
Requires=systemd-networkd-wait-online.service
After=systemd-networkd-wait-online.service
[Service]
User=khadas
ExecStart=/usr/bin/ssh -NT -o ExitOnForwardFailure=yes -o ServerAliveInterval=60 -CD 8080 -R 8083:localhost:22 vimssh@host.com
RestartSec=5
Restart=always
[Install]
WantedBy=multi-user.target


Обратите внимание на порт 8083: он и определяет по какому порту у меня будет осуществляется подключение через обратный ssh. Добавляем в автозагрузку и стартуем.

sudo systemctl enable autossh.service
sudo systemctl start autossh.service

Можно даже посмотреть статус:

sudo systemctl status autossh.service

Теперь, на нашем VPS-сервере, если выполнить:

ssh -p 8083 khadas@localhost

То я попадаю на мою тестовую железку. И с железки могу так же отправлять логи и любые данные по ssh на мой сервер, что весьма удобно.

Собираем все воедино



Включение, приступаем к разработке и отладке

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

Важный момент с кодом: Данный проект вот так вот «влоб» может не запуститься, так как затачивался на определенную задачу, определенной архитектуры. Хоть я и даю исходники, но все же самое ценное разберу вот тут, прямо в тексте, иначе совершенно непонятно.

В начале у меня идет инициализация gps, gpio и запуск отдельного потока планировщика.

#запуск потока планировщика
pShedulerThread = threading.Thread(target=ShedulerThread, args=(1,))
pShedulerThread.start()

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

#sheduler
def ShedulerThread(name):
	global ready_to_send
	while True:
		d = datetime.today()
		time_x = d.strftime('%H:%M')
		if time_x in time_send_csv:
			ready_to_send = True
		if error_status:
			error_blink()
		else:
			good_blink()
		time.sleep(1)

Самый сложный момент в данном проекте — это сохранять обратное ssh-соединение при каждом тесте. В каждом тесте идет заново настройка шлюза по умолчанию и dns-сервера. Поскольку все равно никто не читает, то знайте, что поезд не катается по деревянным рельсам. Кто найдет пасхалку, тому конфетка.

Для этого я создаю отдельную таблица маршрутизации --set-mark 0x2 и правило для перенаправления трафика.

def InitRouteForSSH():
	cmd_run("sudo iptables -t mangle -A OUTPUT -p tcp -m tcp --dport 22 -j MARK --set-mark 0x2")
	cmd_run("sudo ip rule add fwmark 0x2/0x2 lookup 102")

Подробнее о том, как это работает можно прочитать в этой статье.

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

network_list = getNetworklist()

Получение списка сетевых интерфейсов достаточно простое.

def getNetworklist():
	full_networklist = os.listdir('/sys/class/net/')
	network_list = [x for x in full_networklist if "eth" in x and x != "eth0"]
	return network_list

После получения списка, задаю IP-адреса всем интерфейсам, как я приводил на картинке в главе про модем.

SetIpAllNetwork(network_list)

def SetIpAllNetwork(network_list):
	for iface in network_list:
		lastip = "%d" % (3 + network_list.index(iface))
		cmd_run ("sudo ifconfig " + iface + " 192.168.8." + lastip +" up")

Далее просто в цикле иду по каждому интерфейсу. И конфигурирую каждый интерфейс.

	for iface in network_list:
		ConfigNetwork(iface)

def ConfigNetwork(iface):
#сбрасываем все настройки
		cmd_run("sudo ip route flush all")
#Назначаем шлюз по умолчанию
		cmd_run("sudo route add default gw 192.168.8.1 " + iface)
#задаем dns-сервер (это нужно для работы speedtest)
		cmd_run ("sudo bash -c 'echo nameserver 8.8.8.8 > /etc/resolv.conf'")

Проверяю интерфейс на работоспособность, если сети нет, то формирую ошибки. Если сеть есть, то время действовать!

Здесь я настраиваю ssh маршрутизацию на данный интерфейс (если не было сделано), отправляю ошибки на сервер, если время пришло, отправляю логи и в конце концов провожу speedtest и сохраняем логи в csv-файл.

if not NetworkAvalible():
....
#Здесь мы формируем ошибки
....
else: #Есть сеть, ура, работаем!
#Если у нас проблемный интерфейс, на котором ssh, то меняем его
  if (sshint == lastbanint or sshint =="free"):
    print("********** Setup SSH ********************")
    if sshint !="free":
      сmd_run("sudo ip route del default via 192.168.8.1 dev " + sshint +" table 102")
    SetupReverseSSH(iface)
    sshint = iface
#раз сетка работает, то давай срочно все отправим!!!
    if ready_to_send:
      print ("**** Ready to send!!!")
        if sendLogs():
          ready_to_send = False
        if error_status:
          SendErrors()
#и далее тестируем скорость и сохраняем логи. 

Разве что стоит сказать о функции настройки обратного ssh.

def SetupReverseSSH(iface):
	cmd_run("sudo systemctl stop autossh.service")
	cmd_run("sudo ip route add default via 192.168.8.1 dev " + iface +" table 102")
	cmd_run("sudo systemctl start autossh.service")

Ну и конечно же, необходимо всю эту красоту добавить в автозагрузку. Для этого создаю файл:

sudo vim /etc/systemd/system/modems_speedtest.service

И записываю в него:

[Unit]
Description=Modem Speed Test
Requires=systemd-networkd-wait-online.service
After=systemd-networkd-wait-online.service
[Service]
User=khadas
ExecStart=/usr/bin/python3.6 /home/khadas/modems_speedtest/networks.py
RestartSec=5
Restart=always
[Install]
WantedBy=multi-user.target


Включаю автозагрузку и стартую!

sudo systemctl enable modems_speedtest.service
sudo systemctl start modems_speedtest.service

Теперь я могу смотреть логи того, что происходит с помощью команды:

journalctl -u modems_speedtest.service --no-pager -f

Результаты


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

#! /usr/bin/gnuplot -persist
set terminal postscript eps enhanced color solid
set output "Rostelecom.ps"
 
#set terminal png size 1024, 768
#set output "Rostelecom.png"
 
set datafile separator ';'
set grid xtics ytics
set xdata time
set ylabel "Speed Mb/s"
set xlabel 'Time'
set timefmt '%d.%m.%Y;%H:%M:%S'
set title "Rostelecom Speed"

plot "Rostelecom.csv" using 3:6 with lines title "Download", '' using 3:7 with lines title "Upload"
 
set title "Rostelecom 2 Ping"
set ylabel "Ping ms"
plot "Rostelecom.csv" using 3:8 with lines title "Ping"

Первый опыт был оператора Tele2, который я проводил в течении нескольких дней.



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

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









Как видно тема очень обширная для исследований и обработки этих данных, и явно не тянет на пару недель работы. Но…

Итог работы


Работа была резко завершена по независящим от меня обстоятельствам. Одной из слабых сторон данного проекта, на мой субъективный взгляд, был модем, который не очень хотел работать одновременно с другими модемами, и при каждой загрузке выделывал такие фортеля. Для данных целей существует громадное количество других моделей модемов, обычно они уже имеют формат Mini PCI-e и ставятся внутрь устройства и их сильно проще конфигурировать. Но это уже совсем другая история. Проект был интересный и был очень рад, что удалось в нем поучаствовать.