Banana Pi 64 — это одноплатный компьютер по типу Raspberry Pi, но с несколькими портами Ethernet, что дает возможность сделать из него маршрутизатор на базе дистрибутива Linuх общего назначения.



Да, уже есть Openwrt, но у него свои заморочки свой GUI и CLI; есть Mikrotik, но у него опять же свой GUI/CLI, да и Wireguard из коробки не работает… В общем хочется маршрутизатор с гибкими настройками, при этом оставшись в рамках стандартного линукса, с которым работаешь каждый день.


В статье под названиями BPI, R64, одноплатник я буду подразумевать одно и тоже — собственно сам одноплатник Banana Pi R64.


Выбор образа. Загрузка по eMMC


Самый первый навык, который нужно приобрести при работе с SBC вообще, и с R64 в частности, это научиться грузить в него операционку и иметь возможность взаимодействовать с ним, ведь у R64 нет порта для монитора (HDMI, например). Когда все отвалилось — перестал работать Wifi, сеть Ethernet, Bluetooth, USB и прочее есть UART, через интерфейс которого всегда можно посмотреть, что пошло не так, а также запустить пару команд из консоли, по необходимости.


Алгоритм подключения к R64 по USB-UART:


  • бежим в магазин радиодеталей за кабелем USB-UART (PL2303, Serial-to-USB)
  • подключаем один USB-конец к компьютеру, а другой, UART,- к R64, тремя проводами из четырех, как на картинке ниже
  • в консоли компьютера запускаем sudo minicom

После этого в большинстве случаев появится консоль одноплатника = успех.
Подробнее можно посмотреть здесь.


USB-UART


Далее, проще всего загрузить операционную систему с SD-карты: скачиваем по ссылке образ и заливаем его:


unzip -p 2019-08-23-ubuntu-16.04-lite-preview-bpi-r64-sd-emmc.img.zip | pv | sudo dd of=/dev/mmcblk0 bs=10M status=noxfer

вставляем карту в SD-слот R64, включаем, наблюдаем по подключенной консоли загрузку сначала uboot, затем стандартную загрузку линукса.


Альтернативный вариант загрузки — с помощью уже вшитой в R64 карты размером 8Gb, называемой eMMC. По инструкции в вики переписываем образ на устройство
/dev/mmcblk0 в BPI, перегружаемся, вытаскиваем SD-карту, включаем BPI снова… и не работает. Как туда-сюда Boot select ни дергай.


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


root@bpi-r64:~# ./mmc extcsd read /dev/mmcblk1 | grep 'PARTITION_CONFIG'
Boot configuration bytes [PARTITION_CONFIG: 0x00]
root@bpi-r64:~# ./mmc bootpart enable 1 1 /dev/mmcblk1
root@bpi-r64:~# ./mmc extcsd read /dev/mmcblk1 | grep 'PARTITION_CONFIG'
Boot configuration bytes [PARTITION_CONFIG: 0x48]

Далее, в специальный boot-вый раздел надо записать preloader


root@bpi-r64:~# echo 0 > /sys/block/mmcblk0boot0/force_ro 
root@bpi-r64:~# dd if=preloader_evb7622_64_foremmc.bin of=/dev/mmcblk0boot0

Производитель R64 (Китай) выложил этот бинарь здесь. Что он делает неизвестно (исходников нет), но без него тоже не заработает.


В общем-то после этого образы начинают грузиться и с eMMC. Если же захотеть разобраться и создавать образы с нуля, то для обоих случаев (SD/eMMC) нужно записывать еще несколько файлов (preloader для SD-карты, ATF, u-boot), только чтоб добраться до загрузки ядра. Тема эта до сих пор развивается, но для нас главное, что работает и ладно.


Сейчас я загрузку по eMMC, честно говоря, не использую, SD карты достаточно, но я довольно много времени потратил на то, чтоб оно заработало, поэтому пусть будет в статье.


Выбор операционки. Armbian


Первая прикладная задача — запустить VPN, естественно Wireguard. Сразу же обнаружилось, что со стороны ядра он не собран, и заголовков нет. Пересобрал ядро и, по привычке с x86, собрал модуль ядра с помощью DKMS. Однако скорость сборки на arm64 даже небольших утилит меня неприятно удивила. А дальше еще один модуль ядра потребовался, и т.д. В общем, выходит, что все, связанное с ядром, лучше собирать на теплом-ламповом ноутбуке x86, затем простым копированием переносить на R64, перегружаться и тестить.


Другое дело — юзейспейсная часть. В моем случае выбора Debian, все для архитектуры arm64 уже есть на packages.debian.org и ничего пересобирать не приходится.


Чтобы не плодить очередной велосипед, я портировал Armbian на BPI R64.
Вернее так: userspace-ная часть — Armbian, а ядро берется из репозитория Frank-а. Самый свежий образ можно скачать здесь.


Вся активность по разработке софтовой части R64 ведется на форуме. Вообще говоря, сам производитель стремится популяризировать роутер под Openwrt, но благодаря активности разработчика Frank-а из Германии все фичи быстро оказываются в ядре для Debian-а. Удивительно, но Frank активен в каждой ветке форума.


Организация рабочего пространства: провода


Отдельно хочу рассказать, как во время разработки/тестирования разместить SBC (не только BPI) на столе так, чтоб не вести Ethernet-кабель к нему от источника интернета через всю комнату/офис. Дело в том, что с одной стороны нужно обеспечить железке интернет, а с другой стороны в этой самой железке может все ломаться, и в первую очередь Wifi.


Сначала я решил приобрести дешевый USB-Wifi "свисток", воткнуть его в единственный порт на BPI и забыть о проводах. Для этого приобрел недорогой TP-LINK TL-WN725N USB 2.0, но очень скоро стало понятно, что не взлетит: для работы свистка нужен драйвер ядра, которого там, естественно, не было (позже я собрал нужный драйвер RTL8XXXU, но все равно это непрактично). И Ethernet-кабель портил вид комнаты некоторое время.


В итоге от кабеля мне удалось избавиться с помощь Tenda MW3 (Wifi mesh-система): просто расположил один кубик под столом и метровым Ethernet-кабелем подключил BPI к LAN-порту последнего. Успех.


Wireguard, РКН, Bird


Одна из хотелок, для чего я использую Banana PI — иметь свободный доступ на сайты, заблокированные РКН, в частности, чтоб работал Telegram и звонки в Slack-е. На эту тему уже были предложены статьи на Хабре: раз, два, три.


Развертывание именно такого решения я реализовал с помощью Ansible: ссылка.


Предполагается, что VPS работает под Ubuntu 18.04. Работоспособность проверил на двух хостерах в Европе: Amazon и Digital Ocean.


Итак, мы установили вышеуказанный Armbian на R64, он доступен по ssh под именем hm-bananapi-1 и имеет доступ в интернет. Развертываем последовательно ansible, скрипты автоматизации и запускаем установку собственно на R64:


# зависимости для Debian-based дистрибутивов
$ sudo apt install --no-install-recommends python3-pip python3-setuptools python3-wheel git
$ which pip3
/usr/bin/pip3

# ansible с pybook, скриптование на Python
$ pip3 install https://github.com/muravjov/ansible/archive/ansible-2.10.0.dev0-pybook2019.tar.gz

$ export PATH=~/.local/bin:$PATH
$ which ansible-playbook
/home/sa/.local/bin/ansible-playbook

$ git clone https://github.com/muravjov/ansible-bpi-r64.git
$ cd ansible-bpi-r64

$ git submodule update --init

# убеждаемся в доступности hm-bananapi-1
$ ssh hm-bananapi-1 which python3
/usr/bin/python3

# собственно установка
$ ansible-playbook ./router.py -l hm-bananapi-1

Далее нужно аналогичным образом задеплоить на VPS наш VPN:


ansible-playbook ./router.py -l current-vpn

Здесь аргумент всегда current-vpn, а собственно имя VPS настраивается в переменной (в данном случае это paris-vpn-aws-t2-micro-1):


$ grep current_vpn group_vars/all 
current_vpn: paris-vpn-aws-t2-micro-1
#current_vpn: frankfurt-vpn-d0-starter-1

Ах да, перед всеми этими операциями нужно сгенерировать секреты (в частности ключи Wireguard) в папку ./secrets, директория должна выглядеть так.


Автоматизация Ansible на Python


Можно заметить, что вместо формата YAML команды Ansible закодированы в скриптах Python. Для сравнения, как обычным способом включить демон bird:


- name: start bird
  systemd:
    name: bird
    state: started
    enabled: yes

и как то же самое через Python:


with mapping:
    append("name", "start bird")
    with mapping("systemd"):
        append("name",  "bird")
        append("state", "started")
        append("enabled", "yes")

Запись команд Ansible кодом на Python позволяет повторно использовать код, да и вообще открыты все возможности языка общего назначения. Например, установка bird на R64 и VPS:


install_bird("router/bird.conf.j2")
install_bird("vpn/bird.conf.j2")

посмотреть код функции install_bird().


Данная фича под названием pybook реализована здесь. Документации по pybook пока нет, потом исправлю этот недочет.


Что думает upstream по этому поводу.


Мониторинг. Prometheus


Итого: телеграмчик работает, linkedin и pornhub тоже, в общем user experience — ок. Но все может ломаться, и китайские железки тоже.


Обновления ядра тоже бывают интересными: например, я захотел обновить ядро 5.4 => 5.6, нуачо, там же Wireguard изкоробки, не надо патчить… Сказано-сделано: кропотливо переносил патчи с 5.4 на 5.6, ядро завелось, туннель до VPS пингуется, но bird не может соединиться с ошибкой "BGP Error"… "В ужасе откатился назад" (с) на 5.4; переезд на 5.6 отложил в TODO.


Поэтому в дополнение к инсталяции роутера и VPS добавил мониторинг (на x86 Ubuntu 18.04), который ставится на отдельный хост со следующими компонентами:


  • prometheus, alertmanager, blackbox_exporter — все в докере
  • алерты направляются в телеграм-канал с помощью бота metalmatze/alertmanager-bot — тоже в докере
  • tor для бота, чтобы бот мог алертить ситуации, когда интернет есть, но телеграм все равно не работает, и сам бот не может соединиться
  • прикладные алерты: NodeVPNTroubles (нет ping-а до VPS), BirdVPNTroubles (нет сессии Bird), AntifilterDownloadTroubles (ошибка загрузки заблокированных IP-адресов), SiteTroubles (недоступен злополучный телеграм)
  • системные алерты, например, HostGrowingDiskReadLatency (дешевая SD-карта перестает читаться)

Пример установки мониторинга:


ansible-playbook ./monitoring.py -l monitoring-preprod

Auto Discovery для прометея настроено на папку /etc/prometheus/auto_http, пример добавления хоста в мониторинг (по умолчанию хосты не мониторятся):


bash << 'EOF'
HOSTNAME=hm-bananapi-1
IP_ADDRESS=`ssh -G $HOSTNAME | awk '/^hostname / { print $2 }'`

ssh monitoring-preprod sudo sponge /etc/prometheus/auto_http/$HOSTNAME.json << EOF2
[
  {
    "targets": ["$IP_ADDRESS:9100"],
    "labels": {
      "env": "prod",
      "hostname": "$HOSTNAME"
    }
  }
]
EOF2
EOF

TODO: 2 провайдера, 2 BPI, anycast failover


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


Наиболее продвинутый user experience на тему multi-wan описан здесь для системы Mwan3 под Openwrt. У данного решения богатый функционал, но настройка и эксплуатация вообще для multi-wan довольно хлопотна. Один только пример: если приходить на некоторые сайты сразу с двух IP-адресов, то им это может не понравится, они перестанут работать => "интернет не работает".


Учитывая данный опыт решил, что multihoming пока не в приоритете, только failover. Хотя, кажется, в последних версиях linux все должно работать одной командой вида:


ip route add default     nexthop via 192.168.1.1 weight 10     nexthop via 192.168.2.1 weight 5

Итак, чтобы не было единой точки отказа, берем 2 BPI, каждый подключаем к одному провайдеру, соединяем их между собой и связь друг c другом сделаем динамической маршрутизацией через bird/OSPF.


Далее, на каждом анонсируем одинаковый IP-адрес в случае, если сервис доступен (интернет, DNS). То есть проставлять маршрут по умолчанию будем не сами, а посредством bird. Решение подсмотрел здесь .


Данный функционал пока не реализовал, коварный коронавирус и тут подгадил (не все приехало c алиэкспресс; еще один интернет-магазин, Layta, обещал доставить за неделю, а уже больше месяца прошло; второй провайдер не успел кабель протянуть до карантина, успел только дырку в стене просверлить для кабеля).


Как заказать R64


Сама плата в официальном магазине SinoVoip.
Также лучше сразу заказать:


  • питание + сообщить стандарт штекера ЕС или США
  • теплоотвод: радиаторы/вентиляторы; потому что и CPU греется, и чип switch-а
  • антенну для wifi, например

Есть нюанс — цена доставки с какого-то времени стала неадекватно высокой в официальном магазине. Менеджер Judy Huang убеждала меня, что ошибки нет, и можно выбрать ePacket за \$5, но я видел, что для России есть только EMS за >33\$. Неприятно, но не критично. Причем, если выбрать любую другую страну для доставки (перебрал все континенты), доставка будет за ~5\$. Русофобы?.. Но потом я нашел, что для Франции цена доставки тоже ~30\$, и успокоился.


В итоге Judy предложила сделать заказ, но не оплачивать (hint: положить на карту меньше, чтоб автоматом оплата не прошла); написать ей, и она снизит цену доставки до нормальной. Успех.


Issues


Не все пока работает идеально.


Производительность


Медленно выполняются команды Ansible=Python, даже холостые, по 20-30 секунд; на порядок дольше, чем на ноуте x86. Причем сначала выполняются достачно быстро, ~3 секунды, затем резко замедляются. Возможно это происходит из-за нагревающегося CPU (throttling). Код на Go тоже долго работает:


# запрос метрик для прометея из node_exporter на Go
$ time curl -s http://172.30.1.1:9100/metrics > /dev/null

real    0m6,118s
user    0m0,005s
sys     0m0,009s

# однако температура 51 градус, не так и много
sa@bananapir64:~$ cat /sys/devices/virtual/thermal/thermal_zone0/temp
51700

Wifi


Wifi работает, но на Armbian где-то через сутки перестает, пишет:


sa@bananapir64:~$ dmesg | grep -E 'mt7622_wmac.*timeout'
[470303.802539] mt7622_wmac 18000000.wmac: Message 38 (seq 3) timeout
[470314.042508] mt7622_wmac 18000000.wmac: Message 50 (seq 4) timeout
...

Помогает лишь рестарт. Надо дальше разбираться.


Ethernet


Ethernet работает, но через ~сутки пакеты (DHCP) от R64 перестают приходить.
Помогает рестарт интерфейса:


ifdown br0; sleep 30; ifup br0

Драйвер новый, в ядро еще не приняли, надеюсь китаец Landen Chao допилит.