Привет, Хабр! Решил представить тут небольшое и дешёвое устройство, которое на данный момент умеет:
1. Пинговать заданный IP
2. Смотреть пакеты с помощью tcpdump
3. Сканировать сеть через ARP

А главное, всё это работает полностью автономно на Raspberry PI 2 без GUI и потребляет всего 50 Мб ОЗУ!
В статье будет: описание самого устройства и идеи; описание различных приколов и багов в процессе разработки; инструкция как сделать такое самому; и вопрос знатокам, нужно ли это вообще кому-нибудь :)

Как я к этому пришел

В процессе разработки коммутатора на работе я столкнулся с тем, что было бы прикольно иметь небольшое настольное устройство для, например, генерации пакетов с заданными QoS метками; пакетов с таким-то VLAN'ом, или просто для просмотра "идут пакеты или нет".

Постепенно в голове начали формироваться следующие требования к устройству:
1. Дешевизна (забегая вперед, это устройство получилось в районе 20$)
2. Наличие собственного экрана (3.5 дюйма SPI экран с резистивным тач-скрином)
3. Наличие линукса на борту
4. И один или несколько портов Ethernet.

Я долго искал одноплатники с двумя портами Ethernet, но находил только дорогие, заточенные под создание собственного маршрутизатора. В итоге остановился на Raspberry pi 2B, потому что во-первых, она была в наличии, а во-вторых, не возникнет проблем с подключением экрана (думал он). Плюс, при желании добавить один порт можно просто купить дешевый переходник USB - Ethernet, открывая кучу возможностей.

Описание возможностей

Итак, что имеем сейчас в получившемся устройстве (подчеркиваю, это только proof-of-concept):

  • Можно пингануть другое устройство в сети.

  • Можно посмотреть пакеты, приходящие на интерфейс.

  • Можно узнать какие девайсы есть в сети. При этом устройство посылает arp-запросы на маску /24 (да, она захардкожена. PoC же), и слушает ответы, генерируя список соответствия IP-адреса MAC-адресу.

  • И можно посмотреть информацию о своём интерфейсе (на данный момент это просто вывод "ip address show dev eth0".

Гифки (~10 мб)

Как это было сделано

А теперь, к вопросу как это все сделать без GUI.

Компоненты

Их всего два. Raspberry pi 2b можно взять на авито примерно за 10 долларов; экранчик брал как в спойлере, с учетом доставки и прочего получаются те же 10$.

Фото
Экран
Экран
Малинка
Малинка

Образ ОС

В качестве ОС я выбрал Raspbian lite (32х битную), без десктопного окружения, потому что зачем, когда у нас есть терминал :) Да и вторая малинка с трудом тянет полноценную графическую систему.

Подключение экрана

Итак, экран начал подключать по инструкции здесь. Там всё просто, клонируем репу, запускаем ./LCD35-show, наслаждаемся. Но не тут-то было, т.к. далее стоял вопрос как откалибровать его. Не помню как именно, но тернистыми путями вышел на библиотеку, в которой было всё что нужно для калибровки.

Написание собственно программы

Существует шикарная библиотека для быстрого написания интерфейсов на плюсах Dear ImGui. Однако существует менее известная библиотека ImTui, которая позволяет использовать ImGui для написания приложений в терминале с использованием ncurses.

Про автора:

Автор ImTui -- ggerganov -- какой-то монстр: он сделал мегапопулярную библиотеку для LLM llama.cpp, порт Whisper на С++, ImTui... А о его активности можно слагать легенды:

Склонировав репу ImTui и попробовав запустить, я столкнулся со следующим: не регистрирует нажатия. Потыкавшись с линуксовыми инпутами, я решил проблему просто: линкуем себе в приложение libts, лезем в исходник imtui и добавляем обработку:

// imtui-impl-ncurses.cpp
#include "tslib.h"
...
static struct tsdev* ts;
...
ImTui::TScreen * ImTui_ImplNcurses_Init() {
    ...
    ts = ts_setup(nullptr, 1);
}

bool ImTui_ImplNcurses_NewFrame() {
    struct ts_sample samp;
    int ret = ts_read(ts, &samp, 1);
    if (samp.pressure > 0) { // Если давление на экран больше ноля
        // и если данные от экрана "более-менее" норм
        if (samp.x > 0 && samp.x < 1000 && samp.y > 0 && samp.y < 1000) {
            // масштабируем и выдаем координаты нажатия
            mx = (double)samp.x * 30 / 240;
            my = (double)samp.y * 20 / 160;
            lbut = 1;
            hasInput = true;
        } else {
            lbut = 0;
        }
    } else {
        lbut = 0;
    }
    ...
}

Весь код лежит на гитхабе.
Был также момент с масштабированием. По умолчанию текст получался очень мелкий, было сложно что-то разглядеть. Однако драйвер к экрану не поддерживает масштабирование. Поэтому я просто задал разрешение экрана в 1.5 раза меньше, и текст увеличился. Это неидеальное решение, он получился немного размытым, надо бы больше поковырять этот момент.

Приколы в коде и баги

Для пинга и просмотра пакетов используются команды ping и tcpdump, вывод которых перенаправляется в pipe и отображается. При этом у tcpdump есть флаг -U, который, согласно ману, означает "вывод будет записан в stdout когда закончится очередной пакет". Но это не работало! Пакеты выводились пачкой раз в секунд 10. Зато опция -l "вывод записывается в stdout по окончанию очередной строки" работала! Хотя даже в man tcpdump говорится что опции идентичны.

Залез в исходники. Флаг -l вызывает setvbuf(stdout, NULL, _IOLBF, 0) где _IOLBF и означает строковую буферизацию. Флаг -U вызывает в конце каждого пакета функцию pcap_dump_flush, которая является частью API libpcap. Лезем в код libpcap. Находим:

int
pcap_dump_flush(pcap_dumper_t *p)
{
	if (fflush((FILE *)p) == EOF)
		return (-1);
	else
		return (0);
}

Fflush чистит все user-space буферы, но не чистит kernel-space. При этом размер pipe обычно 4 кибибайта, а средний пакет в выводе tcpdump ~ 100 байт. Вот и получалось что он печатал пачками по ~40 пакетов. Причина найдена, поэтому идем дальше.

Был прикол с тем, что если в .bashrc прописать что-то типа

if [[ $(tty) == /dev/tty1 ]]; then
    /root/TcpDumper/build/bin/TcpDumper
fi

То при открытии приложения будет чёрный экран. Я не знаю сколько бы я с этим бодался, но подсказала ChatGPT: для ncurses приложения необходима переменная среды TERM! Добавив в код выше export TERM=xterm-256color, все заработало.

С файлом .profile был ещё один мем. Когда я первый раз добавил запуск приложения сразу после запуска ОС, я по-глупости написал это вот так:

# файл /root/.profile
...
exec /root/TcpDumper/build/bin/TcpDumper

И сохранил. Решил проверить. Перезахожу по ssh. Меня встречает приложение, работает, всё хорошо. Жму Ctrl+C чтобы его закрыть и... сессия ssh закрывается. О нет. Ведь exec замещает процесс баша приложением в его аргументе, и назад в баш вернуться не было возможности. От пользователя pi пароль я забыл, команда вида ssh root@192.168.0.114 'echo privet > /root/.profile' завершается ошибкой...

Начал думать что делать. Точно, нужно просто достать sd-карточку и поменять на ней этот файл! Но есть проблема. Файловая система у малинок отформатирована в ext4, а винда такой формат не поддерживает. Другого устройства на линуксе дома нет.
Нашел под винду программу просмотра файлов с карточек ext2fsd, но она умеет их только читать, а не писать. Но у меня же есть WSL, подумал я, нашёл программу для проброса usb флешек в WSL -- usb-ipd. Но она тоже отказалась монтировать флешку. Погуглив, нашел информацию что нужно пересобрать ядро WSL с поддержкой нужных дров, но решил остановиться и... поспать. На завтра занёс её на работу, где проблема решилась за 10 секунд :) Можно ещё было залить на флешку образ линукса и загрузиться в live cd на ноуте, но тогда я почему-то отверг этот вариант (может просто флешки не было еще одной).

Ещё был баг, если зайти на малинку через ssh, и открыть сразу две копии приложения через openvt, то все начинает глючить. Не знаю зачем вам эта информация :)

Инструкция по программированию

Если вы не собираетесь повторять девайс, этот раздел можно пропустить, тут не будет ничего интересного.

Установка ОС

  1. Перейдите на https://www.raspberrypi.com/software, скачайте raspberry pi imager под вашу ОС

  2. В Imager: выберите вашу модель Raspberry, ОС - Raspberry pi OS lite, в настройках включите удаленный доступ (ssh).

  3. Установите выбранную ОС на sd-карточку.

Установка драйверов для дисплея и калибровка

  1. Установите необходимые пакеты: apt install libts-dev cmake

  2. Склонируйте репозиторий с драйвером дисплея: git clone https://github.com/goodtft/LCD-show.git

  3. cd LCD-show; ./LCD35-show

  4. Поменяйте (уменьшите) разрешение дисплея. Для этого в файле /boot/config.txt добавьте (если их нет) в конец следующие строки:
    framebuffer_width=240
    framebuffer_height=160

  5. reboot

  6. Далее, необходимо выставить следующие переменные среды для работы libts: export TSLIB_FBDEVICE=/dev/fb0;
    export TSLIB_TSDEVICE=/dev/input/event0

  7. Откалибруйте дисплей: ts_calibrate

Сборка программы

  1. Склонируйте репозиторий:
    cd ~;
    git clone https://github.com/Dima-Makarov/TcpDumper.git --recurse-submodules

  2. Можно собирать и запускать:
    cd TcpDumper;
    cmake -S . -B build;
    cmake --build build;
    build/bin/TcpDumper

  3. Для того, чтобы оно автоматически запускалось при старте малинки, добавьте следующие строки в ~/.profile:
    export TSLIB_FBDEVICE=/dev/fb0
    export TSLIB_TSDEVICE=/dev/input/event0
    if [[ $(tty) == /dev/tty1 ]]; then
    export TERM=xterm-256color
    openvt -f -c 1 /root/TcpDumper/build/bin/TcpDumper
    fi

  4. Также нужно сделать так, чтобы экран запускался с пользователем root. Для этого:
    nano /etc/systemd/system/getty@tty1.service.d/autologin.conf
    В строке после слова autologin измените слово pi на root
    Выйдите из редактора:
    reboot

  5. Готово!

Если появятся какие-то проблемы - смело пишите в лс, обещаю оперативно помочь.

Инструкция по сборке

Простите
Соедините детали как показано на видео
Соедините детали как показано на видео

Описание улучшений которые можно было бы реализовать

  1. Не зря я упомянул про адаптер USB-Ethernet в начале, ведь с ним можно сделать следующее: втыкаем его в usb, соединяем два сетевых интерфейса в bridge, и вуаля, получаем сниффер, который можем включить в разрыв между двумя устройствами и смотреть через tcpdump что они там шлют друг другу.

  2. На поздних "малинках" есть wi-fi, и с ним можно сделать много чего интересного, и всё из того же TUI интерфейса. Как минимум - переходник "медь-воздух", wi-fi сканер, you name it.

  3. Можно реализовать сценарии разных атак на сеть - arp spoof, mac spoof, проверить включен ли port-security на коммутаторе, попытавшись загадить ему mac-таблицу :), и прочее и прочее.

  4. Также, через wi-fi можно реализовать управление этим устройством со смартфона, и там уже воротить все что вздумается.

Вопросы знатокам

Мои вопросы: заинтересован ли кто-нибудь в подобном устройстве? Какие бы фичи вам были бы полезны? Удобный ли форм-фактор, на ваш взгляд? Добро пожаловать в комменты!

Скрытый текст

Стоп он реально не оставил ссылки на свой тг канал???

Комментарии (14)


  1. iliasam
    09.02.2025 12:27

    Интересно, можно ли сделать что-то аналогичное на связке Android-смартфон + USB-Ethernet адаптер ?


    1. Duke_nukeum Автор
      09.02.2025 12:27

      Поизучал вопрос, скорее всего можно, но не с каждым адаптером, не с каждой версией Android, да и нужен root.


    1. NutsUnderline
      09.02.2025 12:27

      можно, утилит то полно. но драйвера USB-Ethernet могут включить в прошивку, а могут и не включить


  1. jhoag
    09.02.2025 12:27

    Распберри с Wi-Fi-модулем в этом случае выглядят любопытно, если модуль поддерживает режим мониторинга. Но непонятно, есть ли такие.


  1. victor_1212
    09.02.2025 12:27

    вуаля получаем сниффер

    sniffer - типа если promiscuous mode нормально работает, хотя бы до 20-30% utilization, контроллер LAN9514 вероятно его поддерживает, но для Broadcom + драйвер надо проверять


  1. IndyCar
    09.02.2025 12:27

    производительность вызывает сомнения, и кстати, на рынке много нормальных x86-64 устройств в той же ценовой категории (если не дешевле) с производительностью в разы выше

    и да, 300-400Mbit/s там запросто


    1. Duke_nukeum Автор
      09.02.2025 12:27

      Тут производительность упирается не в cpu устройства (хватает с большим запасом), а в скорость обновления экрана по SPI. Это никак не исправить, только перейти на экран с hdmi, и соответственно сильно увеличить стоимость


    1. Duke_nukeum Автор
      09.02.2025 12:27

      Можете привести примеры таких одноплатников?


      1. IndyCar
        09.02.2025 12:27

        UP-CHT01 первое что пришло в голову (UpBoard) уже старенький по современным меркам. Можно и винду запустить, накопитель сразу eMMC

        Еще вспоминается проект Udoo x86, мне понравился, можно нормальные SSD цеплять, WiFi на PCIe


  1. IndyCar
    09.02.2025 12:27

    -


  1. sdy
    09.02.2025 12:27

    Малина наверное неплохо, но с двумя гигабитными портами и с MIPI/DSI - минипк в районе 3тр-5тр, в зависимости от корпуса, тот же Nano PI R3s

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

    Можно еще поискать, вопрос только какой бюджет устройства


  1. DraugerVan
    09.02.2025 12:27

    Странно, что не нашли дешевый двухпортовик: Orange pi R1 Plus lts. Есть еще Nanopi R2, R3, R4, но они подороже.


  1. marshallab
    09.02.2025 12:27

    Тоже не хватает банальной пинговалки с экраном! По случайности пылится рпи с таким экранчиком. Обязательно запилю такой девайс. Из хотелок: подключатся со смартфона через ssh по wifi ble... посажу на power bank для автономного питания. Для наворотов можно поставить marauder rpi - там все есть.


  1. NutsUnderline
    09.02.2025 12:27

    красивое. в текущем виде в общем то хватило бы esp32 с выеденным ethernet и экраном, но готовая сборка такая если и есть то недешевая