Вже пару місяців, як на робочому ноуті Lenovo ThinkPad T14 Gen 5 з Arch Linux виникла проблема з відкриттям нових сайтів – перші 10-15 секунд сайт завантажується “шматочками”, наприклад:
Але потім “розчехляється”, і все починає працювати чудово:
І проблема виявилась дуже цікавою. Причину шукав довго, і перепровірив купу різних налаштувань – від IPv6 і DNS до драйверу мережової карти.
Головне, що проблема не те щоб була критичною – в цілому інет працював, а тому я іноді починав шукати причину, потім закидував, потім знов повертався.
The issue: “communications error to 192.168.0.1#53: timed out”
Що цікаво, що проблема спостерігалась тільки на Ethernet-підключені – на WiFi все працювало чудово.
А на Ethernet репродьюсилось на різних кабелях і через різні роутери.
Значить – що? Значить – або Сєня щось наковиряв руками у своєму Linux, або десь колись прилетів “кривий” апдейт чи до ядра, чи до драйвера, чи до якось бібліотеки.
Вже не пам’ятаю чому, але спершу грішив на DNS, бо ми ж знаємо, що:
І такі да – під час спроб зарепродьюсити це вдалось саме з DNS, під час тестів з dig – тому довго копав в цю сторону.
Виглядала проблема так: робимо dig, 10-15 запитів проходять нормально, а потім прилітає “communications error to 192.168.0.1#53: timed out“:
$ time dig google.com +short @192.168.0.1
;; communications error to 192.168.0.1#53: timed out
...
real 0m5.018s
user 0m0.004s
sys 0m0.008s
Ну і це виглядало, як дійсно причина того, що сайти тупили з загрузкою контенту: якщо DNS періодично відвалюється, а сайти мають купу додаткових скриптів і картинок, які підвантажуються з інших ресурсів – то поки всі хости разрезолвляться, поки отримаємо всі адреси, поки почнеться завантаження – як раз маємо цю затримку в кількадесят секунд.
Логічно? Так.
Тому і всі подальше тести я робив вже в циклі з dig:
$ for i in {1..50}; do { time dig +nocookie +noedns +tries=1 +time=2 google.com >/dev/null; } 2>&1; done
...
real 0m0.016s
...
real 0m2.015s
...
real 0m0.013s
...
real 0m1.392s
І такий результат був постійно – пачка запитів проходить нормально – “real 0m0.016s“, а потім на якомусь одному – таймаут і “real 0m2.015s” (бо +time=2 – чекати 2 секунди, а не дефолтні 5).
Ця ж проблема була видна з tcpdump: в 09:57:47 запит відправлений, але відповіді не отримано, через 2 секунди, в 09:57:49 – новий запит, і на нього вже відповідь прийшла:
...
09:57:47.717951 IP setevoy-work.40923 > _gateway.domain: 13058+ [1au] A? google.com. (51)
09:57:49.729589 IP setevoy-work.45441 > _gateway.domain: 63641+ [1au] A? google.com. (51)
09:57:49.730249 IP _gateway.domain > setevoy-work.45441: 63641 6/4/4 A 142.250.109.101, A 142.250.109.100, A 142.250.109.139, A 142.250.109.138, A 142.250.109.102, A 142.250.109.113 (260)
...
Тут в 0.002788 відкритий сокет для відправки запиту, а через 5 секунд (5.005754) – бо зараз dig запускався без +time=2 – відкривається новий сокет для нового запиту, бо на попередній відповіді не було.
В пошуках Немо проблеми
Тут опишу що взагалі перевіряв – квест вийшов той ще.
Хоча записав не все, робив більше, але основне зберіг – вже давно є звичка закидувати в чорнетку поста на RTFM під час дебагу проблем.
Перевірка DNS в Linux
Перше – що з DNS в системі?
В /etc/resolv.conf заданий роутер:
# Generated by NetworkManager
nameserver 192.168.0.1
Міняємо на 1.1.1.1 чи на 8.8.8.8 – проблема залишається.
Окей… Може, в системі ще якийсь активний резолвер, і починається “DNS-гонка в ядрі” – запит “блукає” між ними?
Перевіряємо systemd-resolved – ні, не запущений:
$ systemctl status systemd-resolved
○ systemd-resolved.service - Network Name Resolution
Loaded: loaded (/usr/lib/systemd/system/systemd-resolved.service; disabled; preset: enabled)
Active: inactive (dead)
...
Може, dnsmasq?
Теж виключений:
$ systemctl status dnsmasq
○ dnsmasq.service - dnsmasq - A lightweight DHCP and caching DNS server
Loaded: loaded (/usr/lib/systemd/system/dnsmasq.service; disabled; preset: disabled)
Active: inactive (dead)
...
Значить, DNS-запити йдуть напряму до роутера, і… Що? Тупить роутер з відповідями? До нього не доходять запити – іноді губляться?
Що це може бути?
локальний firewall на Linux чи роутері?
ні – вимикав, проблема залишалась
race між кількома локальними DNS-сервісами?
виключили вище
power management мережевої карти – вона уходить в sleep?
маловірогідно, але далі перевіряв і це
баг драйвера мережевої карти?
можливо, бо проблема з’явилась не так давно, до цього на цьому ноуті і цій системі все працювало без проблем
якісь проблеми конкретно з UDP?
теж ніж – робив dig +tcp google.com, проблема залишалась
відповідь на DNS-запит повертається з іншого IP?
екзотична ідея, але як варіант – на роутері кілька мережевих інтерфейсів, об’єднаних в bridge, і – теоретично – роутер може відправити відповідь з іншої
але це прям щось дуже неординарне, та і проблема виникала однаково на різних роутерах, і раніше її не було
IPv6 та DNS
Не пам’ятаю чому, але десь на початку грішив на IPv6 під час виконання DNS.
/etc/gai.conf керує алгоритмом вибору адрес у glibc (GAI = getaddrinfo()), і визначає яку адресу (IPv4 чи IPv6) програма, яка робила DNS-запит вибере першою у випадку, якщо DNS повернув і A, і AAAA записи.
Можна включити IPv4 first – розкоментувати строку:
...
precedence ::ffff:0:0/96 100
...
Перевіряємо, що повернеться першим – адреса IPv4, чи IPv6:
Тут здавалось, що проблема знайдена – бо перший раз все пройшло без проблем, але ні – потім знов таймаути.
NIC Offloading
NIC Offloading – це коли частина операцій виконується на самому мережевому інтерфейсі, тобто offload деяких задач з CPU ноутбука на контролер карти.
Перевіряємо активні з ethtool -k:
$ sudo ethtool -k enp0s31f6 | grep on
rx-checksumming: on
tx-checksumming: on
tx-checksum-ip-generic: on
scatter-gather: on
tx-scatter-gather: on
tcp-segmentation-offload: on
...
generic-segmentation-offload: on
generic-receive-offload: on
rx-vlan-offload: on
tx-vlan-offload: on
receive-hashing: on
...
Самі цікаві тут:
TSO (TCP Segmentation Offloading): процесор віддає карті один великий шматок даних (наприклад, 64 КБ), а карта сама “нарізає” його на маленькі TCP-пакети по 1500 байт
GSO (Generic Segmentation Offloading): те саме, що й TSO, але більш універсальне (працює не лише для TCP)
GRO (Generic Receive Offloading): зворотний процес – карта отримує багато дрібних пакетів, “склеює” їх в один великий і лише тоді віддає процесору, що економить ресурси CPU
RX та TX Checksum Offloading: карта сама перевіряє контрольні суми (CRC) вхідних пакетів – якщо пакет “битий”, карта його просто викидає, навіть не повідомляючи операційну систему
По черзі вимикаємо їх, і перевіряємо:
sudo ethtool -K enp0s31f6 gro off: не допомогло
sudo ethtool -K enp0s31f6 gso off: не допомогло
sudo ethtool -K enp0s31f6 tso off: не допомогло
sudo ethtool -K enp0s31f6 rx off: не допомогло, і стало навіть гірше
Насправді те, що після відключення RX Checksum Offloading стало гірше – вже було підказкою: якщо до цього мережева карта сама фільтрувала помилки, то тепер вони всі повалили до ядра, що створило додаткове навантаження і хаос у черзі пакетів, тому корисні DNS-відповіді стали губитися ще частіше.
IR-PCI-MSI-0000:00:1f.6 – драйвер використовує MSI (Message Signaled Interrupts), яка начебто на Linux може давати drops для UDP на деяких картах Intel.
rx_crc_errors каже, що проблема з цілісністю пакетів, і – якщо з роутерам і кабелем все в порядку (а проблема спостерігалась на різних роутерах і з різними кабелями) – то скоріш за все проблема в самому RJ-45 на ноутбуці, хоча контакти виглядають нормально.
Спробував примусово зменшити швидкість на інтерфейсі з гігабіта до 100 Mbps:
$ sudo ethtool -s enp0s31f6 speed 100 duplex full autoneg on
І чудо! Все працює!
Повертаємо знов 1000:
$ sudo ethtool -s enp0s31f6 speed 1000 duplex full autoneg on
І проблема знову з’являється.
Можна було б просто залишити 100 Mbps – але ж я не для того підключений по кабелю і плачу за гігабітний GPON?
Благо, вдома є кілька USB-адаптерів з Ethernet, перемкнув кабель на нього:
$ ip a s enp0s13f0u2u3
2: enp0s13f0u2u3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether c8:4d:44:29:27:6b brd ff:ff:ff:ff:ff:ff
altname enxc84d4429276b
inet 192.168.0.198/24 brd 192.168.0.255 scope global dynamic noprefixroute enp0s13f0u2u3
...
Є гігабіт і Full Duplex:
$ sudo ethtool enp0s13f0u2u3
Settings for enp0s13f0u2u3:
Supported ports: [ TP MII ]
Supported link modes: 10baseT/Half 10baseT/Full
100baseT/Half 100baseT/Full
1000baseT/Half 1000baseT/Full
...
Speed: 1000Mb/s
Duplex: Full
...
drv probe link timer ifdown ifup rx_err tx_err tx_queued intr tx_done rx_status pktdata hw wol
Link detected: yes
Власне, по налаштуванню NAS вже зроблено майже все – з VPN є доступи з різних мереж, є різні шари, трохи затюнили безпеку.
Залишилось дві основні речі: моніторинг і бекапи, бо мати ZFS mirror на двох дисках з регулярними ZFS snapshots це, звісно, класно, але все одно недостатньо, а тому хочеться додатково робити бекапи десь в клауд.
Особливо я відчув необхідність мати доступ до бекапів в клаудах на початку війни, коли не ясно було де я опинюсь через годину, і чи буде в мене можливість забрати із собою бодай якесь залізо.
Сьогодні подумаємо і заплануємо як робити бекапи з Linux-хостів на NFS share та як робити бекапи даних NFS і Samba на FreeBSD. При чому бекапи хочеться зробити у два незалежних сховища – AWS S3 та Google Disk: S3 буде основним, а Гугл – резервною копією (резервної копії).
Всі частини серії по налаштуванню домашнього NAS на FreeBSD:
Дані в restic організовані у репозиторіях: кожен репозиторій – це окремий каталог, який містить конфігурацію репозиторію, індекси та зашифровані дані.
Під час створення бекапу restic формує власні логічні snapshots. Дані розбиваються на незалежні блоки (blobs), які і є базовими одиницями зберігання в репозиторії.
При наступних бекапах restic перевіряє, які саме блоки були змінені, і копіює тільки їх, а на блоки даних, які не змінились – створює посилання з нового снапшоту, таким чином оптимізуючи зайнятий дисковий простір.
Тобто тут процес схожий із ZFS snapshots – тільки у ZFS посилання створюється на блоки самої файлової системи і оригінальні дані, а в restic – на власні блоки даних в каталозі репозиторію.
При цьому restic зберігає дані у власному форматі, а тому ми не залежимо від файлової системи – створюємо бекап з ext4, копіюємо на ZFS, зберігаємо в S3, і відновлюємо на упасі боже Windows з NTFS. Єдине, що нам буде треба – це клієнт restic.
Створюємо тестовий репозиторій:
$ restic init --repo test-repo
enter password for new repository:
enter password again:
created restic repository 50ef450308 at test-repo
Перевіряємо зміст:
$ ll test-repo/
total 24
-r-------- 1 setevoy setevoy 155 Jan 1 16:47 config
drwx------ 258 setevoy setevoy 4096 Jan 1 16:47 data
drwx------ 2 setevoy setevoy 4096 Jan 1 16:47 index
drwx------ 2 setevoy setevoy 4096 Jan 1 16:47 keys
drwx------ 2 setevoy setevoy 4096 Jan 1 16:47 locks
drwx------ 2 setevoy setevoy 4096 Jan 1 16:47 snapshots
Кожен репозиторій має ключ, який використовується для доступу до зашифрованих даних:
$ restic key list --repo test-repo
enter password for repository:
repository 50ef4503 opened (version 2, compression level auto)
ID User Host Created
-----------------------------------------------------
*0ca1c659 setevoy setevoy-work 2026-01-01 16:49:49
-----------------------------------------------------
Шифрування даних виконується з master key, який зберігається в репозиторії:
$ restic -r test-repo cat masterkey
enter password for repository:
repository 50ef4503 opened (version 2, compression level auto)
{
"mac": {
"k": "v1PIB3bB1VW46oWWBtKQYA==",
"r": "Tia4a7HGs7PmN1EzWoWh4g=="
},
"encrypt": "0c3l/00P3dTgdbAqPZApAYn7E/MiloqOXyQsYr6AGOA="
}
Для отримання доступу до якого використовуються дані user keys:
scrypt: KDF (Key Derivation Function), яка використовується для отримання криптографічного ключа з пароля (див. Scrypt Key Derivation Function)
salt: сіль з рандомним значенням
data: зашифрований master key – саме він використовується для шифрування даних
Коли restic потрібно отримати доступ до даних у репозиторії – він бере введений пароль і salt, передає їх у KDF і формує ключ, який використовується для розшифрування master key репозиторію.
Master key, у свою чергу, застосовується для шифрування та розшифрування ключів, якими вже безпосередньо шифруються дані та метадані в репозиторії.
При цьому можна мати кілька різних user keys (або access keys), які будуть використовуватись для отримання master key.
При потребі пароль можна змінити:
$ restic key passwd --repo test-repo/
enter password for repository:
repository 50ef4503 opened (version 2, compression level auto)
created new cache in /home/setevoy/.cache/restic
enter new password:
enter password again:
saved new key as <Key of setevoy@setevoy-work, created on 2026-01-01 16:49:49.010471345 +0200 EET m=+13.105722833>
При налаштуванні автоматизації бекапів – пароль можна передати зі змінної оточення RESTIC_PASSWORD (див. Environment Variables) або з файлу через --password-file.
Наприклад, для використання паролю з файлу – створимо директорію:
$ restic key add --repo test-repo
enter password for repository:
repository 50ef4503 opened (version 2, compression level auto)
enter new password:
enter password again:
saved new key with ID c08c993b87363c17526e98fd46aeaf14767fa051e3b0d87a32c0cecc50e361d4
Перевіряємо ключі тепер:
[setevoy@setevoy-work ~/Projects/Restic] $ restic key list --repo test-repo
enter password for repository:
repository 50ef4503 opened (version 2, compression level auto)
ID User Host Created
-----------------------------------------------------
*0ca1c659 setevoy setevoy-work 2026-01-01 16:49:49
c08c993b setevoy setevoy-work 2026-01-01 17:02:10
-----------------------------------------------------
В *0ca1c659 зірочка показує, що зараз репозиторій відкритий з цим ключем.
Пробуємо відкрити з новим ключем – паролем з файла:
$ restic stats --repo test-repo --password-file ~/.config/restic-test/test-repo-password
repository 50ef4503 opened (version 2, compression level auto)
[0:00] 0 index files loaded
scanning...
Stats in restore-size mode:
Snapshots processed: 0
Total Size: 0 B
Для створення бекапів використовуємо команду restic backup, а для відновлення, власне, restic restore.
Бекапимо файл /tmp/restic-test.txt в наш репозиторій:
$ restic backup /tmp/restic-test.txt --repo test-repo
repository 50ef4503 opened (version 2, compression level auto)
no parent snapshot found, will read all files
[0:00] 0 index files loaded
Files: 1 new, 0 changed, 0 unmodified
Dirs: 1 new, 0 changed, 0 unmodified
Added to the repository: 755 B (687 B stored)
processed 1 files, 13 B in 0:01
snapshot bf8def5f saved
Під час кожного виклику restic backup в репозиторії створюється новий snapshot, навіть якщо дані в source не змінювались.
Але, як писав вище – якщо не змінюються дані, то і розмір репозиторію не росте, бо restic просто створить посилання з нового снапшоту на старі блоки даних.
При зміні частини даних – відповідно будуть створені нові блоки тільки для нових даних, на які буде замаплений цей снапшот, а на незмінні дані – в новому снапшоті залишаться старі посилання.
Перевіряємо наявні снапшоти:
$ restic snapshots --repo test-repo
repository 50ef4503 opened (version 2, compression level auto)
ID Time Host Tags Paths Size
-----------------------------------------------------------------------------------
bf8def5f 2026-01-01 17:07:53 setevoy-work /tmp/restic-test.txt 13 B
-----------------------------------------------------------------------------------
1 snapshots
Основні корисні команди при роботі з репозиторіями та снапшотами:
restic stats: статистика по репозиторію або снапшоту
restic check: перевірка цілісності репозиторію
restic ls: подивитись зміст снапшоту
restic diff: порівняти дані у двох снапшотах
restic copy: скопіювати зміст одного репозиторію в інший
І окремо варто згадати --dry-run – перевірити що саме буде виконуватись, і яких даних торкнеться операція.
Для відновлення даних з бекапу використовуємо restic restore і вказуємо ID снапшоту та куди його відновити.
Якщо в destination каталогу нема – restic його створить, а в ньому відновить ієрархію каталогів та файлів зі снапшоту:
$ restic restore --repo test-repo bf8def5f --target /tmp/test-restic-restore
...
restoring snapshot bf8def5f of [/tmp/restic-test.txt] at 2026-01-01 17:07:53.016664301 +0200 EET by setevoy@setevoy-work to /tmp/test-restic-restore
Summary: Restored 2 files/dirs (13 B) in 0:00
Перевіряємо:
$ tree /tmp/test-restic-restore
/tmp/test-restic-restore
└── tmp
└── restic-test.txt
Include та exclude для backup та restore
При створенні бекапу з restic backup ми вказуємо шлях, який бекапиться, а тому окремої опції --include нема.
Але є --exclude, з яким можна вказати які дані не треба включати в снапшот.
Наприклад, маємо каталог:
$ tree /tmp/restic-dir-test
/tmp/restic-dir-test
├── a.txt
└── sub
└── b.txt
Бекапимо весь цей каталог, але пропускаємо файл a.txt:
--keep-daily 7: залишаємо снапшоти за останні 7 днів
--keep-weekly 4: залишаємо снапшоти за останні 4 тижні (по одному snapshot на тиждень)
--keep-monthly 6: залишаємо снапшоти за останні 6 місяців (по одному snapshot на місяць)
застосовуємо тільки для снапшотів з тегом daily, і відразу видаляємо дані з диску
Restic mount – репозиторій як директорія
Можна змонтувати репозиторій як звичайну папку, монтується тільки в режимі read-only:
$ mkdir /tmp/restic-mounted-test-repo
$ restic mount -r test-repo /tmp/restic-mounted-test-repo
...
Now serving the repository at /tmp/restic-mounted-test-repo
...
$ restic snapshots -r new-test-repo
repository fc8a407c opened (version 2, compression level auto)
ID Time Host Tags Paths Size
-----------------------------------------------------------------------------------------------
7335e7bf 2026-01-04 15:08:41 setevoy-work daily,2026-01-04-15-08 /tmp/restic-dir-test 18 B
-----------------------------------------------------------------------------------------------
Restic та репозиторій в AWS S3
З S3 все більш-менш аналогічно до роботи з локальними репозиторіями, але є деякі нюанси.
Для аутентифікації restic використовує звичайний механізм – пошук змінних оточення AWS_ACCESS_KEY_ID та AWS_SECRET_ACCESS_KEY, або пошук у файлах ~/.aws/config та ~/.aws/credentials.
Важливі нюанси, які треба мати на увазі при роботі з S3:
видаляти дані з репозиторію restic в AWS S3 можна тільки через restic forget та restic prune
використання S3 Lifecycle rules для restic не рекомендується – навіть для зміни storage class
Каталоги (index/, snapshots/, keys/) активно використовуються restic; якщо перенести, наприклад, keys/ у Glacier або Deep Archive – restic може зависати або падати по таймауту, очікуючи доступ до ключів.
Теоретично lifecycle transitions можна застосувати лише до каталогу data/, де зберігаються pack-файли з даними, але якщо потім запустити restic prune – то restic буде потрібен доступ до старих pack-файлів в data/, і, якщо вони знаходяться в Glacier або Deep Archive, операція стане або дуже повільною, або взагалі неможливою
Тому краще просто робити періодичний restic forget і restic prune, та залишити S3 Standart class даних в бакеті.
Restic та Google Drive через rclone
В мене rclone для Google Drive вже налаштований, допишу про нього окремо, вже є в чернетках, бо теж дуже цікава система.
Що ми можемо зробити – це використати rclone як storage backend для роботи з типами storage, яких нема в самому restic.
Але працює ця схема ну дуже повільно (принаймні з Google Drive) – тому її краще використовувати як one time copy, а не для регулярних бекапів.
Запускаємо restic copy, але тепер для copy вказуємо тільки --from-repo – бо destination у нас вже заданий через $RESTIC_REPOSITORY:
$ restic copy --from-repo s3:s3.amazonaws.com/test-restic-repo-bucket/test-restic-repository
enter password for source repository:
repository 58303a9c opened (version 2, compression level auto)
created new cache in /home/setevoy/.cache/restic
enter password for repository:
repository e1a8edae opened (version 2, compression level auto)
created new cache in /home/setevoy/.cache/restic
[0:00] 0 index files loaded
[0:00] 0 index files loaded
Перевіряємо в Google Drive:
$ restic snapshots
enter password for repository:
repository e1a8edae opened (version 2, compression level auto)
ID Time Host Tags Paths Size
-----------------------------------------------------------------------------------------------
...
bb02e44b 2026-01-04 15:08:41 setevoy-work daily,2026-01-04-15-08 /tmp/restic-dir-test 18 B
-----------------------------------------------------------------------------------------------
Що треба мати на увазі при роботі restic через clone:
не використовувати rclone mount
не виконувати запис одночасно з двох restic клієнтів
не використовувати одночасно два rclone serve restic для одного репозиторію
Власне, на цьому все.
Залишилось додати автоматизацію запуску бекапів на Linux та FreeBSD, але це вже опишу окремим постом.
Там часто всякі боти запускаються, нічого незвичного.
Далі, вирішив з nmap глянути що за сервіси є на тому атакуючому хості:
# nmap -sS 46.101.201.123
...
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
443/tcp open https
...
Хм, думаю – дивно, що за бот такий, що має 80 і 443 порти?
Відкриваю https://46.101.201.123 в браузері, і… попадаю на власний блог 🙂
Шта?…
Перевіряю, які IP в мене в DigitalOcean, і:
Тобто – да, 46.101.201.123 – це Droplet IP сервера, на якому хоститься RTFM.
Хоча взагалі на DNS в IN A для rtfm.co.ua використовується DigitalOcean Reserved IP, який можна переключати між дроплетами:
Тобто:
NS для rtfm.co.ua – Cloudflare
на них IN A 67.207.75.157
Droplet IP 46.101.201.123 не вказаний ніде
але запити йдуть на нього
Окей…
Тут ще буде окреме питання – чому в CloudFlare показувались запити від 46.101.201.123 – але про це в кінці.
SYN flood та підключення в SYN_RECV
Пішов глянути що взагалі на сервері в нетворкінгу, які активні конекти?
А там…
Купа підключень в статусі SYN_RECV – класичний SYN flood: клієнт нам відправляє TCP-пакет з флагом SYN, ми йому відповіли з SYN-ACK, і чекаємо на ACK від нього – але він не приходить, а ресурси CPU/RAM сервера на очікування зайняті (див. TCP handshake, нещодавно писав).
Mitigating the issue
Так як підключення йдуть не через CloudFalre – то і його Security Rules нам не допоможуть.
А Network Firewall в Digital Ocean, як і Security Groups в AWS вміють тільки в Allow правила – але не в Deny (в AWS можна зробити Deny через правила у VPC NACL – Network Access Control List).
А друге – сама причина 46.101.201.123 в логах Clodflare: “GET /wp-json/pvc/v1/increase/” – це запит до WordPress-плагіна Page View Count, який не так давно включив. А “https://rtfm.co.ua/en/freebsd-home-nas-part-1-configuring-zfs-mirror-raid1″ – звідки запит був зроблений.
Тобто, плагін на сторінці поста робить запит, аби збільшити лічильник переглядів – при цьому підставляючи Referrer у вигляді тої сторінки, звідки він запит робить.
Cloudflare жеж бачить, що запит йде від Origin – і використовує в логах Droplet IP.
Це при тому, що зазвичай переглядів кілька десятків, ну максимум 100-200.
І перевірка в гуглі вже показала причину такого напливу:
А трапилось те, що я сьогодні вранці перший раз запостив лінк на https://lobste.rs, звідки його перепостили на Hacker News, і я отримав “Hacker News hug of death” – див. Surviving the Hug of Death, де у людини була схожа ситуація.
Після відключення плагіну Page View Count – Droplet IP 46.101.201.123 в Cloudflare зник.
Наступний крок в процесі налаштування домашнього NAS на FreeBSD – додати NFS share.
Samba зробили в попередній частині – тепер до неї додамо шари з NFS: моя ідея в тому, щоб Samba share використовувалась для всяких медіа-ресурсів, до яких потрібен доступ з телефонів та TV, а NFS буде виключно для Linux-хостів – двох ноутбуків в різних мережах (дім і офіс), які на цей розділ будуть робити свої бекапи з rsync, rclone або restic.
Всі частини серії по налаштуванню домашнього NAS на FreeBSD:
В 192.168.0.2:/backups каталог /backups вказуємо від корня, який задали в /etc/exports: тобто корінь у нас “/nas/nfs” – значить на клієнтах він буде “/“, і, відповідно, внутрішні датасети монтуємо від цього корня як /backups.
А по-дефолту NFS виконує root_squash, і всі операції від клієнтів на сервері виконує від локального юзера nobody.
Є кілька варіантів вирішення:
на сервері створити групу типу nfsusers, дати їй права на запис в каталог (775), і додати локального юзера setevoy в цю групу
самий кошерний і безпечний варіант
або можна задати опцію -maproot=root – тоді root на клієнті буде == root на сервері (UID в обох 0)
але це тільки про доступ до файлів, і тільки в межах NFS root – /nas/nfs
ОК варіант для домашнього NAS
трохи безпечніший варіант – вказати -maproot=setevoy і на сервері змінити власника /nas/nfs/backups/ – тоді операції від root на клієнті на сервері будуть виконуватись від UID/GID юзера setevoy на сервері
або взагалі зробити -mapall=root – і тоді всі юзери на клієнті будуть виконувати операції як локальний root
аналогічно до -maproot=root, але і самий небезпечний варіант
Так як ця шара для бекапів, які на клієнтах будуть виконуватись від root – то можна використати maproot=root:
root@setevoy-nas:/home/setevoy # zfs set sharenfs="-network 192.168.0.0/24 -maproot=root" nas/nfs/backups
root@setevoy-nas:/home/setevoy # service nfsd restart
Тепер на клієнті від звичайного юзера у нас доступу все ще нема:
Допомогли на форумі FreeBSD, див. NFSv4 and share for multiply networks (взагалі, FreeBSD community дуже відкрите, і набагато менш токсичне, аніж ті ж форуми Arch Linux).
І, звісно, хочеться доступ до NFS мати захищеним на рівні мереж (хоча в моєму випадку цілком можна було обійтись без цього, але краще відразу робити правильно).
А проблема полягає в том, що у ZFS ver 2.2.7, яка використовується у FreeBSD 14.3 зараз, нема можливості вказати кілька мереж в sharenfs property.
Тобто, не можна використати щось типу:
# zfs set sharenfs="-network 192.168.0.0/24 -network 192.168.100.0/24 -network 10.8.0.0/24" nas/nfs/backups
Але у FreeBSD 15.0 і ZFS 2.4.0 начебто розширили синтаксис, і там вже можна передати список, розділений “;“:
[setevoy@setevoy-home ~]$ sudo wg show
interface: wg0
...
peer: xLWA/FgF3LBswHD5Z1uZZMOiCbtSvDaUOOFjH4IF6W8=
endpoint: 178.***.***.184:51830
allowed ips: 10.8.0.1/32, 192.168.0.0/24
З адресою 10.8.0.3:
[setevoy@setevoy-home ~]$ ip a s wg0
44: wg0: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1420 qdisc noqueue state UNKNOWN group default qlen 1000
link/none
inet 10.8.0.3/24
...
Встановлюємо nfs-utils, інакше буде помилка “NFS: mount program didn’t pass remote address“:
Дуже рідко бувають проблеми з апгрейдами на Arch Linux, а така ситуація, як сьогодні, в мене за майже 10 років користування системою вперше.
Отже, що трапилось:
встановив апгрейди з pacman -Syu
після установки sudo reboot зависав
ребутнутись вдалось тільки з sudo reboot -f (force)
після ребуту – X.Org запустився, SDDM теж, Plasma теж – але на робочому столі все зависало і не реагувало на клаву і мишку
Дичина якась 🙂
Пофіксити вдалось, хоча так і не зрозумів що саме і чому впало.
Проблема #1: sudo reboot та “Failed to connect to system scope bus via local transport”
Це насправді було першим дзвіночком.
В логах при виконанні sudo reboot з’являлось повідомлення про:
...
Call to Reboot failed: Connection timed out
Failed to connect to system scope bus via local transport: Connection refused
...
Явно проблема з D-Bus/systemd, і саме тому допомогло sudo reboot -f (чи з sudo shutdown -r now, щось з них двох спрацювало), бо в такому випадку systemd не намагається координувати shutdown через system bus і напряму викликає примусовий перезапуск.
Окей – ребутнулись, і тут виникла друга – сама цікава – проблема.
KDE Plasma “висить” після апгрейду і ребуту
Беру в лапки “Plasma висить”, бо проблема все ж була не в ній – але проявлялось саме там.
Отже, ребунувся, залогінився, робочий стіл і сервіси запустились:
Але на цьому – все.
Ніякої реакції на клаву і мишку.
Перша підозра – проблема з KDE Wallet або GNOME Keyring – бо все виглядало так, що зависання виникає після їх старту.
При цьому SSH працював, і я зміг підключатись з іншого ноутбука аби подебажити і пофіксити.
Хоча xorg-server і xorg-xinit вже були, бо я на X11, а не глючному Wayland.
Далі по SSH прибив поточну сесію:
$ sudo systemctl stop sddm
На ноутбуці через Alt + F2 зайшов в консоль (до речі… дивно, що це спрацювало, але ок), і запустив startx.
X11 та xterm завантажились – і тут жеж все знов перестало реагувати на клаву-мишку.
…
Краса…)
А отже, проблема виникає не на рівні Plasma чи конкретного desktop environment, а нижче – на рівні ядра, systemd, input stack та їх ініціалізації.
Fix attempt #3: pacman.log та “Failed to connect to system scope bus via local transport: Connection refused”
Окей – пішов дивитись логи pacman, і ось тут було дещо цікаве:
[2025-12-29T09:33:25+0200] [PACMAN] Running 'pacman -Syu --noconfirm'
[2025-12-29T09:33:25+0200] [PACMAN] synchronizing package lists
[2025-12-29T09:33:28+0200] [PACMAN] starting full system upgrade
...
[2025-12-29T09:33:49+0200] [ALPM] upgraded systemd (258.3-1 -> 259-1)
[2025-12-29T09:33:49+0200] [ALPM-SCRIPTLET] Creating group 'empower' with GID 946.
[2025-12-29T09:35:20+0200] [ALPM-SCRIPTLET] Failed to connect to system scope bus via local transport: Connection refused
[2025-12-29T09:35:20+0200] [ALPM-SCRIPTLET] Failed to connect to system scope bus via local transport: Connection refused
[2025-12-29T09:35:20+0200] [ALPM-SCRIPTLET] :: This is a systemd feature update. You may want to have a look at
[2025-12-29T09:35:20+0200] [ALPM-SCRIPTLET] NEWS for what changed, or if you observe unexpected behavior:
[2025-12-29T09:35:20+0200] [ALPM-SCRIPTLET] /usr/share/doc/systemd/NEWS
[2025-12-29T09:35:20+0200] [ALPM] upgraded polkit (126-2 -> 127-2)
[2025-12-29T09:35:20+0200] [ALPM-SCRIPTLET] Failed to connect to system scope bus via local transport: Connection refused
...
[2025-12-29T09:35:26+0200] [ALPM] running '30-systemd-catalog.hook'...
[2025-12-29T09:35:26+0200] [ALPM] running '30-systemd-daemon-reload-system.hook'...
[2025-12-29T09:35:26+0200] [ALPM-SCRIPTLET] Failed to connect to system scope bus via local transport: Connection refused
[2025-12-29T09:35:26+0200] [ALPM] running '30-systemd-daemon-reload-user.hook'...
[2025-12-29T09:35:27+0200] [ALPM-SCRIPTLET] Failed to connect to system scope bus via local transport: Connection refused
[2025-12-29T09:35:27+0200] [ALPM] running '30-systemd-hwdb.hook'...
[2025-12-29T09:35:27+0200] [ALPM] running '30-systemd-restart-marked.hook'...
[2025-12-29T09:35:27+0200] [ALPM-SCRIPTLET] Failed to connect to system scope bus via local transport: Connection refused
....
[2025-12-29T09:35:30+0200] [ALPM-SCRIPTLET] ==> Building image from preset: /etc/mkinitcpio.d/linux.preset: 'default'
[2025-12-29T09:35:30+0200] [ALPM-SCRIPTLET] ==> Using default configuration file: '/etc/mkinitcpio.conf'
[2025-12-29T09:35:30+0200] [ALPM-SCRIPTLET] -> -k /boot/vmlinuz-linux -g /boot/initramfs-linux.img
...
[2025-12-29T09:35:33+0200] [ALPM-SCRIPTLET] ==> Creating zstd-compressed initcpio image: '/boot/initramfs-linux.img'
[2025-12-29T09:35:33+0200] [ALPM-SCRIPTLET] -> Early uncompressed CPIO image generation successful
[2025-12-29T09:35:33+0200] [ALPM-SCRIPTLET] ==> Initcpio image generation successful
[2025-12-29T09:35:33+0200] [ALPM] running 'dbus-reload.hook'...
[2025-12-29T09:35:33+0200] [ALPM-SCRIPTLET] Failed to connect to system scope bus via local transport: Connection refused
...
І згадуємо першу проблему – що нормальний reboot не працював з тою самою помилкою підключення до системної шини (через /run/dbus/system_bus_socket).
І система не змогла виконати всі hooks, і в результаті після рестарту частина системних сервісів зависала.
Найімовірніше, це торкнулось компонентів, які залежать від udev, logind та input stack. В результаті X.Org і display manager стартували, але пристрої вводу (keyboard/mouse) опинились у неконсистентному стані, без коректної обробки подій.
Final fix: установка ядра linux-lts (але діло не в LTS)
Тут в мене з’явилась підозра, що проблема в ядрі і Intel-дайверах, і вирішив спробувати встановити LTS-ядро:
$ pacman -S linux-lts linux-lts-header
Правда, після встановлення забув зробити grub-mkconfig, а тому в меню GRUB “Advanced options for Arch Linux” нове ядро не з’явилось, і я просто загрузився зі старим.
Але… Саме після цього все запрацювало.
Чому – бо під час встановлення LTS заново запустились всі хуки, бо перший апдейт відбувався в момент оновлення systemd і D-Bus, а другий – у вже стабільному systemd-оточенні:
Коротше – класичний приклад неочевидної “магії” systemd, D-Bus та системних hooks під час апгрейду.
Lessons learned
якщо під час pacman -Syu з’являються помилки systemd/dbus, їх не варто ігнорувати – а я звик, що апгрейди проходять без проблем, і не подивився на pacman output в консолі, а відразу почав ребутати машину
зависання GUI не завжди означає проблему в Destop Environment або Wayland/X11
повторний запуск системних hooks у стабільному середовищі може повністю виправити систему
Прийшов час трохи привести в порядок SSH на самому FreeBSD та на клієнтах – ноутбуках з Arch Linux, бо на домашніх машинках досі використовую парольну аутентифікацію.
Власне, описані нижче налаштування не специфічні ні для FreeBSD, ні для Linux, бо SSH server один і той самий на всіх системах (OpenSSH_9.9p2 на FreeBSD 14.3 і OpenSSH_10.2p1 на Arch Linux).
хост з FreeBSD/NAS: доступний тільки в локальній мережі і VPN, тому SSH brute force не очікується (але при параної або на публічно доступних серверах можна додати Fail2Ban чи SSHGuard)
sshd: друга лінія захисту, базові налаштування доступу
SSH та аутентифікація по ключам
Перше і основне – це налаштувати доступ по ключам замість парольної аутентифікації.
Зробимо це, потім доступ по паролям відключимо взагалі.
На клієнті, ноутбуці з Linux, створюємо ключі:
[setevoy@setevoy-work ~] $ ssh-keygen -t ed25519 -C "setevoy@setevoy"
Generating public/private ed25519 key pair.
Enter file in which to save the key (/home/setevoy/.ssh/id_ed25519): /home/setevoy/.ssh/freebsd-nas
Enter passphrase for "/home/setevoy/.ssh/freebsd-nas" (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/setevoy/.ssh/freebsd-nas
Your public key has been saved in /home/setevoy/.ssh/freebsd-nas.pub
Копіюємо на хост з FreeBSD:
[setevoy@setevoy-work ~] $ ssh-copy-id -i /home/setevoy/.ssh/freebsd-nas [email protected]
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/setevoy/.ssh/freebsd-nas.pub"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
([email protected]) Password for setevoy@setevoy-nas:
Number of key(s) added: 1
Now try logging into the machine, with: "ssh -i /home/setevoy/.ssh/freebsd-nas '[email protected]'"
and check to make sure that only the key(s) you wanted were added.
Перевіряємо файл ~/.ssh/authorized_keys на FreeBSD у юзера setevoy:
root@setevoy-nas:/home/setevoy # op account add
Enter your sign-in address (example.1password.com): my.1password.com
Enter the email address for your account on my.1password.com: <ACCOUNT_EMAIL>
Enter the Secret Key for [email protected] on my.1password.com: <SECRET_KEY>
Enter the password for [email protected] at my.1password.com:
Enter your six-digit authentication code: <OTP_CODE>
Now run 'eval $(op signin)' to sign in.
Перевіряємо акаунти:
root@setevoy-nas:/home/setevoy # op account list
SHORTHAND URL EMAIL USER ID
my https://my.1password.com [email protected] 7BS***KMM
Логінимось:
root@setevoy-nas:/home/setevoy # eval $(op signin)
Enter the password for [email protected] at my.1password.com:
І маємо доступ до сікретів.
Наприклад, отримати ключ, який додамо далі:
root@setevoy-nas:/home/setevoy # op item get "FreeBSD NAS SSH"
ID: ulz***4ce
Title: FreeBSD NAS SSH
Vault: Personal (wb7***guq)
Created: 1 week ago
Updated: 1 week ago by Arseny
Favorite: false
Tags: FreeBSD,SSH
Version: 1
Category: LOGIN
Fields:
password: [use 'op item get ulz***4ce --reveal' to reveal]
username: setevoy
Виконуємо ssh nas.setevoy, 1Password запросить підтвердження – пароль самого 1Password:
І тепер все працює через його SSH Agent:
[setevoy@setevoy-work ~] $ ssh nas.setevoy
...
Last login: Sat Dec 27 09:03:15 2025 from 192.168.0.4
FreeBSD 14.3-RELEASE (GENERIC) releng/14.3-n271432-8c9ce319fef7
Welcome to FreeBSD!
...
[setevoy@setevoy-nas ~]$
Як працює SSH Agent
При підключені SSH-клієнт:
читає конфігурацію (~/.ssh/config, опції CLI)
підключається до SSH-агента (у нашому випадку 1Password) і отримує список доступних публічних ключів
по черзі пропонує серверу публічні ключі, які в нього є (з агента і, за відсутності обмежень, з ~/.ssh/)
sshd на сервері дивиться в ~/.ssh/authorized_keys конкретного юзера і перевіряє, чи є там запропонований публічний ключ
коли сервер знаходить збіг, він приймає цей публічний ключ і надсилає клієнту дані для підпису (випадковий набір чисел)
клієнт передає ці дані SSH-агенту, який володіє відповідним приватним ключем
агент створює криптографічний підпис приватним ключем і повертає його клієнту
сервер перевіряє підпис публічним ключем і завершує аутентифікацію
Глянути це можемо з ssh -v user@host:
[setevoy@setevoy-work ~] $ ssh -v [email protected]
debug1: OpenSSH_10.2p1, OpenSSL 3.6.0 1 Oct 2025
debug1: Reading configuration data /home/setevoy/.ssh/config
...
debug1: Connecting to nas.setevoy [192.168.0.2] port 22.
debug1: Connection established.
...
debug1: Authenticating to nas.setevoy:22 as 'setevoy'
...
debug1: Will attempt key: RTFM RSA SHA256:nedb3Qgpkxgu57MRP7/eXShHgw6N6b7SjZ3S1rNyFb4 agent
debug1: Will attempt key: FreeBSD-NAS ED25519 SHA256:ZuJ77z6BNMwra41BiTKDrJSSQrDJ0/u+4wZRCvGJMpA agent
debug1: Will attempt key: /home/setevoy/.ssh/id_rsa
debug1: Will attempt key: /home/setevoy/.ssh/id_ecdsa
debug1: Will attempt key: /home/setevoy/.ssh/id_ecdsa_sk
debug1: Will attempt key: /home/setevoy/.ssh/id_ed25519 ED25519 SHA256:X8L1lCBQz8Bk7K5rMGqiE+tlSthCbgaqK7ryLZ6gVWU
debug1: Will attempt key: /home/setevoy/.ssh/id_ed25519_sk
debug1: Offering public key: RTFM RSA SHA256:nedb3Qgpkxgu57MRP7/eXShHgw6N6b7SjZ3S1rNyFb4 agent
debug1: Authentications that can continue: publickey
debug1: Offering public key: FreeBSD-NAS ED25519 SHA256:ZuJ77z6BNMwra41BiTKDrJSSQrDJ0/u+4wZRCvGJMpA agent
debug1: Server accepts key: FreeBSD-NAS ED25519 SHA256:ZuJ77z6BNMwra41BiTKDrJSSQrDJ0/u+4wZRCvGJMpA agent
Authenticated to nas.setevoy ([192.168.0.2]:22) using "publickey".
...
Last login: Sun Dec 28 13:44:51 2025 from 192.168.0.4
FreeBSD 14.3-RELEASE (GENERIC) releng/14.3-n271432-8c9ce319fef7
Welcome to FreeBSD!
...
[setevoy@setevoy-nas ~]$
І для ключів “Will attempt key: RTFM RSA [...] та FreeBSD-NAS [...] як раз бачимо, що вони були отримані від agent.
SSH Agent та помилка “Too many authentication failures”
Вище бачили, що передається ціла пачка ключів, і якщо їх багато – то сервер може відкинути подальшу аутентифікацію.
Кількість спроб задається на сервері параметром MaxAuthTries (дефолт “6”) в /etc/ssh/sshd_config. Тобто, якщо маємо 7 ключів, і перші 6 не підійшли – то підключитись не зможемо.
Аби вказати SSH клієнту який конкретно приватний ключ з агента використовувати – вказуємо публічний ключ та задаємо IdentitiesOnly:
root@setevoy-nas:/home/setevoy # sshd -T
port 22
addressfamily any
listenaddress 0.0.0.0:22
...
Наприклад, чи дозволений логін root:
root@setevoy-nas:/home/setevoy # sshd -T | grep root
permitrootlogin no
Редагуємо конфіг /etc/ssh/sshd_config, задаємо мінімальні параметри, і нехай буде PermitRootLogin no вказаний тут явно.
Плюс явно вказуємо дозвіл на PubkeyAuthentication та PasswordAuthentication – парольну аутентифікацію відключимо пізніше, коли впевнимось, що все працює:
PermitRootLogin no
PubkeyAuthentication yes
PasswordAuthentication yes
Перевіряємо синтаксис з sshd -t, якщо все добре – то команда просто нічого не виведе:
...
# allow SSH from Office LAN (192.168.0.0/24) to FreeBSD host
pass in log on em0 proto tcp from 192.168.0.0/24 to (em0) port 22 keep state
# allow SSH from Home network (192.168.100.0/24) to FreeBSD host
pass in log on em0 proto tcp from 192.168.100.0/24 to (em0) port 22 keep state
# allow SSH from VPN clients to FreeBSD host
pass in on wg0 proto tcp from 10.8.0.0/24 to (wg0) port 22 keep state
...
Можна мережі вказати як { 192.168.0.0/24, 192.168.100.0/24, 10.8.0.0/24 }, але для SSH вирішив залишити в такому вигляді, аби було явно видно.
Далі можна додати ще трохи параметрів:
PermitRootLogin no: заборона підключення root, це вже є
PubkeyAuthentication yes: аутентифікація по ключам, теж вже є
PasswordAuthentication yes: пароль аутентифікація, поки залишаємо включеною
ChallengeResponseAuthentication no: вимикаємо keyboard-interactive аутентифікацію через PAM, яка не потрібна без 2FA
тут ще нюанс в тому, що навіть якщо відключити PasswordAuthentication, але ChallengeResponseAuthentication буде “yes” і при UsePAM = “yes” – то система все одно може запросити парольну аутентифікацію
AuthorizedKeysFile .ssh/authorized_keys: це дефолтне значення, але задамо явно
Перевіряємо ще раз з sshd -t, виконуємо reload, перевіряємо підключення з ключем з клієнта.
Якщо все ОК – то додаємо ще трохи тюнингу:
PasswordAuthentication no: тепер відключаємо парольну аутентифікацію
AuthenticationMethods publickey: явно заборонити всі механізми окрім ключів
MaxSessions 2: максимальна кількість активних сесій на одне TCP-підключення
LoginGraceTime 30: кількість секунд, за яку клієнт має пройти аутентифікацію
Для ще більшої безпеки – можна налаштувати інший порт замість 22 (наприклад, задати Port 2222), обмежити IP, на яких слухає sshd (параметр ListenAddress 192.168.0.2), і навіть налаштувати Two-factor Authentication For SSH – але для домашнього сервера з доступом тільки з локальних мереж це вже overkill.
Продовжуємо налаштовувати домашній NAS на FreeBSD.
Власне, NAS – Network System, і хочеться мати до нього доступ з інших девайсів – з Linux та Windows хостів, з телефонів, телевізорів.
Тут у нас на вибір дві основні опції – Samba та NFS. Можна, звісно, згадати і sshfs – але це рішення точно не для домашньої мережі (хоча простіше).
Я для себе вирішив для Windows (на ігровому ПК), Android телефонів та Android TV зробити доступ з Samba share – а NFS буде чисто для Linux систем для бекапів.
В цьому пості налаштуємо Samba на FreeBSD, а в наступному – додамо NFS.
Всі частини серії по налаштуванню домашнього NAS на FreeBSD:
без можливості логіна в систему – /usr/sbin/nologin
але якщо планується виконувати локальні операції від юзера – то створюємо з -s /bin/sh, а SSH все одно буде заблокований в sshd config (про SSH буде окремим постом)
і з -g smbshare вказуємо primary group
Перевіряємо юзера:
root@setevoy-nas:/home/setevoy # id smbshare
uid=1004(smbshare) gid=1004(smbshare) groups=1004(smbshare)
І права доступу до каталога тепер – можна глянути розширену інформацію з getfacl, аби отримати POSIX/NFSv4 ACL, які ZFS використовує замість класичних Unix-бітів:
Samba буде встановлювати власні права доступу, тому в параметрах ZFS ACL треба переключити режими.
Samba керує правами доступу самостійно, тому для ZFS ACL потрібно увімкнути режими passthrough, щоб ZFS не змінював ACL.
Перевіряємо параметри датасету зараз:
root@setevoy-nas:/home/setevoy # zfs get acltype,aclmode,aclinherit nas/share
NAME PROPERTY VALUE SOURCE
nas/share acltype nfsv4 default
nas/share aclmode discard default
nas/share aclinherit restricted default
Задаємо aclmode та aclinherit в значення passthrough – тоді ZFS буде використовувати ті права, які задає Samba:
root@setevoy-nas:/home/setevoy # zfs set aclmode=passthrough nas/share
root@setevoy-nas:/home/setevoy # zfs set aclinherit=passthrough nas/share
Перевіряємо тепер:
root@setevoy-nas:/home/setevoy # zfs get acltype,aclmode,aclinherit nas/share
NAME PROPERTY VALUE SOURCE
nas/share acltype nfsv4 default
nas/share aclmode passthrough local
nas/share aclinherit passthrough local
На відміну від acltype=off, який повністю вимикає ACL і залишає лише POSIX-права, режим passthrough дозволяє Samba керувати ACL без втручання ZFS.
Надалі зміни в директорії /nas/share краще виконувати від імені smbshare або root, а не звичайного користувача, щоб не порушувати модель прав доступу Samba.
Або більше детально, просто явно вказуємо деякі дефолтні опції:
[global]
workgroup = WORKGROUP
security = user
[shared]
path = /nas/share
read only = no
browseable = yes
valid users = @smbshare
create mask = 0660
directory mask = 2770
Перевіряємо синтаксис:
root@setevoy-nas:/home/setevoy # testparm
Load smb config files from /usr/local/etc/smb4.conf
Loaded services file OK.
Weak crypto is allowed
Server role: ROLE_STANDALONE
Press enter to see a dump of your service definitions
# Global parameters
[global]
security = USER
idmap config * : backend = tdb
[shared]
create mask = 0660
directory mask = 02770
path = /nas/share
read only = No
valid users = @smbshare
Додаємо Samba-юзера:
root@setevoy-nas:/home/setevoy # smbpasswd -a smbshare
New SMB password:
Retype new SMB password:
Added user smbshare.
smbpasswd збереже його пароль в /var/db/samba4/private/passdb.tdb.
Запускаємо сервіс:
root@setevoy-nas:~ # service samba_server start
Performing sanity check on Samba configuration: OK
Starting nmbd.
Starting smbd.
root@setevoy-nas:/home/setevoy # smbclient //localhost/shared -U smbshare
Password for [WORKGROUP\smbshare]:
Try "help" to get a list of possible commands.
smb: \>
Робимо list files – поки тут пусто:
smb: \> ls
. D 0 Fri Dec 26 15:46:55 2025
.. D 0 Fri Dec 26 15:46:55 2025
3771191492 blocks of size 1024. 3771191396 blocks available
Копіюємо щось із системи:
smb: \> put /etc/hosts hosts.test
putting file /etc/hosts as \hosts.test (252.7 kb/s) (average 252.7 kb/s)
smb: \> ls hosts.test
hosts.test A 1035 Fri Dec 26 16:14:29 2025
3771191480 blocks of size 1024. 3771191380 blocks available
І тепер маємо файл в /nas/share:
root@setevoy-nas:/home/setevoy # ls -l /nas/share
total 5
-rw-rw---- 1 smbshare smbshare 1035 Dec 26 16:14 hosts.test
cifs-utils: утиліти для роботи з Samba share через CIFS:
mount -t cifs
запису в /etc/fstab
systemd automount
Перевіряємо підключення з Linux до Samba на FreeBSD:
[setevoy@setevoy-work ~] $ smbclient //192.168.0.2/shared -U smbshare
Can't load /etc/samba/smb.conf - run testparm to debug it
Password for [WORKGROUP\smbshare]:
Try "help" to get a list of possible commands.
smb: \>
Ще раз list – все на місці:
smb: \> ls hosts.test
hosts.test A 1035 Fri Dec 26 16:14:29 2025
3771191480 blocks of size 1024. 3771191380 blocks available
Налаштування /etc/fstab та systemd-automount
Якщо хочемо, аби шара підключалась автоматично – налаштуємо /etc/fstab та systemd-automount.
На клієнті створюємо каталог, в який буде підключатись /nas/share з серверу:
Через файл mnt-nas\x2dshared.automount systemd відстежує доступ до /mnt/nas-shared, і як тільки ми виконаємо якусь дію (наприклад, cd /mnt/nas-shared) – systemd виконає mnt-nas\x2dshared.mount, який власне підключить розділ.
57.2 MB/s, тобто 457.6 Mbit/s. При тому, що ноут зараз на WiFi – цілком нормальна швидкість.
Підключення Samba клієнтів
І приклади того, як до Samba підключитись з різних девайсів.
Samba та KDE Dolphin
Взагалі, ми вже зробили automount через systemd, але можна відкрити напряму з KDE Dolphin file manager – вказуємо адресу smb://192.168.0.2, вводимо логін-пароль:
root@setevoy-nas:~ # camcontrol devlist
<Samsung SSD 870 EVO 4TB SVT03B6Q> at scbus0 target 0 lun 0 (pass0,ada0)
<Samsung SSD 870 EVO 4TB SVT03B6Q> at scbus1 target 0 lun 0 (pass1,ada1)
<CT500P310SSD8 VACR001> at scbus7 target 0 lun 1 (pass3,nda0)
Тут в мене ada1 та ada2 – це SATA-диски для самого NAS storage, а nda0 – NVMe, на якому встановлена система (там зараз UFS, але потім, скоріш за все, перевстановлю із ZFS теж).
Імена пристроїв на кшталт /dev/ada0, /dev/ada1 або /dev/nda0 можуть змінюватися залежно від порядку підключення дисків, а тому напряму використовувати їх у ZFS не рекомендується – далі створимо власні GPT labels.
Створення GPT tables
Про всяк випадок – видаляємо існуючі таблиці розділів.
ВАЖЛИВО: таблиця розділів буде знищена. Дані залишаються на носії, але стануть недоступними.
В моєму випадку – диски нові, тому бачимо помилку “Invalid argument“:
Для подальшої роботи з дисками створимо постійні GPT lables – іменовані ідентифікатори розділів, які зберігаються в GPT-таблиці розділів і читаються при старті системи.
Вони не змінюються після reboot, не залежать від порядку SATA портів, не залежать від того, як ядро виявило диск.
root@setevoy-nas:~ # glabel status
Name Status Components
gpt/zfs_disk1 N/A ada0p1
gptid/67ebfac9-dcce-11f0-98bf-00d861f3bff0 N/A ada0p1
diskid/DISK-S758NX0Y701757D N/A ada0
gpt/zfs_disk2 N/A ada1p1
gptid/6a9c3ee5-dcce-11f0-98bf-00d861f3bff0 N/A ada1p1
diskid/DISK-S758NX0Y701756A N/A ada1
Диски готові – переходимо до ZFS, нам треба:
налаштувати ZFS pool з mirror
створити datasets
подивитись на шифрування даних
перевірити, як працювати зі snapshots
І в кінці окремо поговоримо про моніторинг дисків та ZFS pool.
Створення ZFS mirror pool
Використовуємо такі параметри:
ashift=12: розмір фізичного сектора, який ZFS використовує для I/O
розмір сектора визначається як 2^ashift байт, тобто 2¹² = 4096 байт
замінити значення ashift після створення Pool не можна
atime=off: вимикаємо оновлення часу доступу до файлів, зменшує кількість зайвих записів на диск
compression=lz4: швидка компресія даних на диску з мінімальним CPU-оверхедом
xattr: налаштування зберігання атрибутів файлі:
xattr=on: старий і default варіант, extended attributes зберігаються як окремі приховані файли
xattr=sa: xattr зберігаються безпосередньо в dnode файлу (аналог inode в UFS/ext4), без створення окремих прихованих файлів – менше звернень до диска і краща продуктивність
mirror: використовуємо ZFS mirror (аналог RAID1) – дані синхронно записуються на обидва диски (див. також vdev)
root@setevoy-nas:~ # zpool status
pool: nas
state: ONLINE
config:
NAME STATE READ WRITE CKSUM
nas ONLINE 0 0 0
mirror-0 ONLINE 0 0 0
gpt/zfs_disk1 ONLINE 0 0 0
gpt/zfs_disk2 ONLINE 0 0 0
errors: No known data errors
Додаємо підключення при ребутах:
root@setevoy-nas:/home/setevoy # sysrc zfs_enable=YES
zfs_enable: NO -> YES
Створення ZFS datasets
ZFS dataset – це окрема файлова система всередині ZFS pool, яка має власні властивості (compression, quota, mountpoint тощо) і керується незалежно від інших datasets.
Зараз у нас один, корневий dataset:
root@setevoy-nas:~ # zfs list
NAME USED AVAIL REFER MOUNTPOINT
nas 420K 3.51T 96K /nas
Корінь pool – це технічний корінь, а не місце для даних, тому створимо кілька окремих.
Не факт, що в мене датасети залишаться такими і надалі, але просто як ідея того, як можна розділити простір на дисках:
nas/data: основний dataset для збереження всяких даних типу музики-фільмів
nas/backups: сюди можна буде копіювати якісь periodic бекапи з робочого і домашнього ноутбуків
nas/private: зашифрований розділ для приватних даних та/або баз даних типу KeePass або бекапів 1Password
nas/shared: загальнодоступний dataset для доступу з телефонів і ноутбуків через Samba share (про налаштування Samba – окремим постом, вже є в чернетках)
Створюємо новий датасет з іменем nas/data:
root@setevoy-nas:~ # zfs create nas/data
Перевіряємо список ще раз:
root@setevoy-nas:~ # zfs list
NAME USED AVAIL REFER MOUNTPOINT
nas 540K 3.51T 96K /nas
nas/data 96K 3.51T 96K /nas/data
І каталог цього датасету:
root@setevoy-nas:~ # ls -la /nas/data/
total 1
drwxr-xr-x 2 root wheel 2 Dec 19 13:41 .
drwxr-xr-x 3 root wheel 3 Dec 19 13:41 ..
Він жеж має власну точку підключення:
root@setevoy-nas:/home/setevoy # mount | grep data
nas/data on /nas/data (zfs, local, noatime, nfsv4acls)
Яка задана у властивостях датасету:
root@setevoy-nas:/home/setevoy # zfs get mountpoint nas/data
NAME PROPERTY VALUE SOURCE
nas/data mountpoint /nas/data default
Додамо ще один:
root@setevoy-nas:~ # zfs create nas/backups
Ще раз список:
root@setevoy-nas:~ # zfs list
NAME USED AVAIL REFER MOUNTPOINT
nas 696K 3.51T 96K /nas
nas/backups 96K 3.51T 96K /nas/backups
nas/data 96K 3.51T 96K /nas/data
Шифрування dataset
Хочеться мати окремий dataset для чутливих даних, тому глянемо, як це зроблено у ZFS.
включити шифрування можна тільки створенні dataset – але можна відключити після
якщо батьківський dataset не зашифрований – дочірній можна зашифрувати
якщо батьківський зашифрований – дочірні успадковують його шифрування
Створимо новий dataset, вказуємо, що шифрується з паролем:
root@setevoy-nas:~ # zfs create -o encryption=on -o keyformat=passphrase -o keylocation=prompt nas/private
Enter new passphrase:
Re-enter new passphrase:
Перевіряємо цей датасет:
root@setevoy-nas:~ # zfs get encryption,keyformat,keylocation nas/private
NAME PROPERTY VALUE SOURCE
nas/private encryption aes-256-gcm -
nas/private keyformat passphrase -
nas/private keylocation prompt local
Як це буде виглядати після reboot:
pool nas імпортується
nas/private буде заблокований, і mountpoint не зʼявиться, доки вручну не ввести пароль і не розблокувати його
Для розблокування потім використовуємо:
root@setevoy-nas:~ # zfs load-key nas/private
root@setevoy-nas:~ # zfs mount nas/private
На відміну від, наприклад, шифрування розділів з LUKS – ZFS дозволяє змінювати пароль для зашифрованого dataset без перешифрування даних. Фактично змінюється лише ключ, який захищає основний ключ шифрування.
Якщо хочемо змінити пароль – виконуємо zfs change-key:
root@setevoy-nas:~ # zfs change-key nas/private
Enter new passphrase for 'nas/private':
Замість використання паролю можемо створити файл-ключ, який буде використаний про ребутах для підключення датасету.
root@setevoy-nas:/home/setevoy # zfs get encryption,keyformat,keylocation nas/private
NAME PROPERTY VALUE SOURCE
nas/private encryption aes-256-gcm -
nas/private keyformat raw -
nas/private keylocation file:///root/nas-private-pass.key local
Ребутаємо машину:
root@setevoy-nas:/home/setevoy # shutdown -r now
Shutdown NOW!
shutdown: [pid 13519]
І потім перевіряємо розділи:
root@setevoy-nas:/home/setevoy # mount | grep nas
nas on /nas (zfs, local, noatime, nfsv4acls)
nas/backups on /nas/backups (zfs, local, noatime, nfsv4acls)
nas/data on /nas/data (zfs, local, noatime, nfsv4acls)
Та з zfs list:
root@setevoy-nas:/home/setevoy # zfs list
NAME USED AVAIL REFER MOUNTPOINT
nas 200G 3.32T 112K /nas
nas/backups 200K 500G 104K /nas/backups
nas/data 96K 3.51T 96K /nas/data
nas/private 200K 3.32T 200K /nas/private
Налаштування dataset quotas
ZFS datasets підтримують задання квот на розмір датасетів – тобто, максимальний розмір, який він може займати.
Зручно, наприклад, аби якийсь backups/ випадково не забив весь диск.
Є ще dataset reservation, але про це трохи далі.
Задамо ліміт в 500 гігабайт на nas/backups:
root@setevoy-nas:~ # zfs set quota=500G nas/backups
Перевіряємо:
root@setevoy-nas:~ # zfs get quota,used,available nas/backups
NAME PROPERTY VALUE SOURCE
nas/backups quota 500G local
nas/backups used 96K -
nas/backups available 500G -
Налаштування dataset reservation
ZFS дозволяє зарезервувати місце для датасету, гарантуючи доступний простір незалежно від заповненості пулу.
Важливо, що ZFS Reservation резервує місце незалежно від того, використовується воно чи ні.
Задамо мінімально доступний розмір основного датасету nas/data:
root@setevoy-nas:~ # zfs set reservation=200G nas/data
Перевіряємо:
root@setevoy-nas:~ # zfs get reservation,used,available nas/data
NAME PROPERTY VALUE SOURCE
nas/data reservation 200G local
nas/data used 96K -
nas/data available 3.51T -
Аби змінити reservation чи видалити – просто раз виконуємо zfs set з новим значенням:
root@setevoy-nas:~ # zfs set reservation=100G nas/data
root@setevoy-nas:~ # zfs set reservation=none nas/data
Використання ZFS snapshots
ZFS Snapshots – це миттєві read-only знімки стану dataset, які дозволяють швидко відкотитись до попереднього стану.
ZFS працює за принципом COW (Copy On Write), тобто при зміні в блоках даних – зміни робляться в новому блоку, а старі блоки не перезаписуються, поки на них є активні посилання
при створенні снапшоту ZFS не копіює дані, а створює таке посилання на цей блок
далі, коли ми робимо зміни в даних датасета, для якого є снапшот – то зміни на диску робляться в нових блоках даних, а доступ до старих зберігається через снапшот
root@setevoy-nas:/home/setevoy # zfs list -t snapshot nas/data@test-snap
NAME USED AVAIL REFER MOUNTPOINT
nas/data@test-snap 0B - 104K -
Його атрибути:
root@setevoy-nas:/home/setevoy # zfs get creation,used,referenced nas/data@test-snap
NAME PROPERTY VALUE SOURCE
nas/data@test-snap creation Sat Dec 20 15:46 2025 -
nas/data@test-snap used 0B -
nas/data@test-snap referenced 104K -
Відновлення зі snapshot
Снапшоти зберігаються в каталозі .zfs датасета – /nas/data/.zfs/snapshot/:
root@setevoy-nas:/home/setevoy # ll /nas/data/.zfs/snapshot/test-snap/
total 1
-rw-r--r-- 1 root wheel 10 Dec 20 15:45 test-snap.txt
І звідси ми можемо отримати доступ і до нашого тестового файлу:
root@setevoy-nas:/home/setevoy # rm /nas/data/test-snap.txt
root@setevoy-nas:/home/setevoy # file /nas/data/test-snap.txt
/nas/data/test-snap.txt: cannot open `/nas/data/test-snap.txt' (No such file or directory)
Аби відновити зі снапшоту – можна або просто скопіювати з каталога /nas/data/.zfs/snapshot/test-snap/ з cp, або, якщо треба відкотити весь датасет, то використати zfs rollback – але в такому разі всі зміни, які були зроблені після створення снапшоту будуть втрачені:
Ну і ZFS Boot Environments працюють через ті самі снапшоти – під час виконання freebsd-update install автоматично створюється копія даних, на яку можна відкотитись в разі проблем.
Взагалі ZFS Boot Environments дуже цікава штука, може, окремо про неї напишу.
Замість повного rollback – аби не перезаписувати дані на поточному датасеті – можна зробити клонування снапшоту в новий датасет:
root@setevoy-nas:/home/setevoy # cat /usr/local/etc/periodic/daily//402.zfSnap
#!/bin/sh
# If there is a global system configuration file, suck it in.
#
if [ -r /etc/defaults/periodic.conf ]; then
. /etc/defaults/periodic.conf
source_periodic_confs
fi
...
Включити запуск скриптів можна в файлі /etc/periodic.conf (або, краще, /etc/periodic.conf.local):
Запустити виконання всіх daily задач, для яких в /etc/defaults/periodic.conf задано “YES” – з командою periodic:
root@setevoy-nas:/home/setevoy # periodic daily
І тепер нас є новий снапшот:
root@setevoy-nas:/home/setevoy # zfs list -t snapshot
NAME USED AVAIL REFER MOUNTPOINT
...
nas/data@daily-2025-12-21_16.41.03--1w 0B - 104K -
Моніторинг ZFS
Для моніторингу у нас є цілий набір утиліт – як дефолтні від самої файлової системи, так і додаткові, які можна встановити окремо.
Є класний документ Monitoring ZFS, хоча і 2017 року, але все ще актуальний.
З основного, чим можемо користуватись і що бажано моніторити:
SMART: перевірка самих дисків
zpool status: перевірка, що нема проблем на самих ZFS pools
zfs scrub: не зовсім про моніторинг, але може показати проблеми
zpool events: події пулів
arcstats: корисно перевіряти ефективність роботи кешу ZFS
Перевірка S.M.A.R.T. для SSD
Диски зовсім нові, але just in case і на майбутнє – налаштуємо S.M.A.R.T. (Self-Monitoring, Analysis, and Reporting Technology).
Встановлюємо пакет:
root@setevoy-nas:~ # pkg install smartmontools
І перевіряємо стан дисків:
root@setevoy-nas:~ # smartctl -a /dev/ada0
root@setevoy-nas:~ # smartctl -a /dev/ada1
Нам цікаві в основному такі показники:
SMART overall-health self-assessment test result: PASSED: перевірку пройдено (але зазвичай FAILED тут з’являється, коли все зовсім погано)
помилки:
Reallocated_Sector_Ct:
кількість битих секторів, які диск знайшов і замінив резервними
0 – ідеально, зростання цього значення – поганий сигнал
Runtime_Bad_Block:
кількість некоректних блоків, знайдених під час нормальної роботи або деградацію лінка (наприклад, падіння швидкості SATA)
0 – ідеально, зростання цього значення – поганий сигнал
Uncorrectable_Error_Cnt:
кількість помилок читання/запису, які неможливо було виправити
0 – обов’язково має залишатися, зростання – вже серйозна проблема
Wear/Used reserve:
Wear_Leveling_Count:
показує знос елементів памʼяті SSD
0 означає, що диск практично новий або знос мінімальний
Used_Rsvd_Blk_Cnt_Tot:
скільки резервних блоків уже використано для заміни зношених
0 – ідеальний стан
Power_On_Hours:
кількість годин, протягом яких диск був увімкнений
83 години – диски тільки нещодавно купив і підключив
CRC_Error_Count:
кількість помилок передачі даних між диском і контролером (кабель, порт)
0 – норма, зростання часто означає проблеми з кабелем, а не з самим диском
Total_LBAs_Written / Host_Writes / NAND_Writes:
скільки даних реально записано на диск
порівнюємо зі TBW (Total Bytes Written) від виробника
в моєму випадку Total_LBAs_Written = 77982, де LBA – це Logical Block Address, який зазвичай SMART рахує по 512 байт, тобто записано на диск ~40 мегабайт – при заявлених Samsung 2400 TB
Temperature / Temperature_Celsius / Airflow_Temperature_Cel
температура дисків, в мене зараз 28 градусів
SMART Periodic
Для запуска SMART є власні скрипти в /usr/local/etc/periodic.
Аби включити перевірку і репорти – додаємо запуск smartd в автостарт:
І налаштовуємо periodic в /etc/periodic.conf.local.
Аби включити перевірку – треба явно задати диски, по яким цю перевірку робити:
daily_status_smart_devices="/dev/ada0 /dev/ada1"
Результат в репорті:
ZFS Pool Status
zpool status вже запускали вище, тепер додамо запуск по крону і відправку повідомлень.
Скрипти для ZFS /etc/periodic/daily/, наприклад /etc/periodic/daily/404.status-zfs.
Додаємо до /etc/periodic.conf.local:
daily_status_zfs_enable="YES"
Запускаємо скрипти:
root@setevoy-nas:/home/setevoy # periodic daily
Отримуємо листа:
root@setevoy-nas:/home/setevoy # mail -u root
Mail version 8.1 6/6/93. Type ? for help.
"/var/mail/root": 12 messages 4 new 12 unread
...
N 12 root@setevoy-nas Sun Dec 21 17:20 82/3368 "setevoy-nas daily run output"
&
Під час scrub ZFS порівнює checksum кожного блоку зі збереженим значенням і у разі виявлення помилки система фіксує її в логах та, за наявності mirror, автоматично відновлює дані з другої копії.
Оскільки для цього виконується багато I/O операцій, тому не варто запускати scrubbing часто – раз на місяць буде достатньо.
Запускаємо вручну:
root@setevoy-nas:/home/setevoy # zpool scrub nas
І перевіряємо в zpool status:
root@setevoy-nas:/home/setevoy # zpool status
pool: nas
state: ONLINE
scan: scrub repaired 0B in 00:00:00 with 0 errors on Fri Dec 19 16:54:04 2025
config:
NAME STATE READ WRITE CKSUM
nas ONLINE 0 0 0
mirror-0 ONLINE 0 0 0
gpt/zfs_disk1 ONLINE 0 0 0
gpt/zfs_disk2 ONLINE 0 0 0
errors: No known data errors
Тепер в scan є “scrub repaired 0B” – все добре.
Скрипт – /etc/periodic/daily/800.scrub-zfs, в якому перевіряється значення daily_scrub_zfs_default_threshold, і, якщо пройшло більше днів, ніж задано в threshold – то запускається zpool scrub.
З daily_scrub_zfs_pools можна вказати, які саме пули перевіряти.
З zpool events можна перевірити всі останні події в пулі:
root@setevoy-nas:/home/setevoy # zpool events
TIME CLASS
Dec 20 2025 16:46:39.702350180 sysevent.fs.zfs.history_event
Dec 20 2025 16:46:39.711350328 ereport.fs.zfs.config_cache_write
Dec 20 2025 16:46:39.711350328 sysevent.fs.zfs.config_sync
Dec 20 2025 16:46:39.711350328 sysevent.fs.zfs.pool_import
Dec 20 2025 16:46:39.712349914 sysevent.fs.zfs.history_event
Dec 20 2025 16:46:39.720349727 sysevent.fs.zfs.config_sync
Dec 20 2025 14:46:44.749348450 sysevent.fs.zfs.config_sync
...
Dec 21 2025 17:26:30.905209986 sysevent.fs.zfs.history_event
Аби побачити деталі – додаємо -z:
root@setevoy-nas:/home/setevoy # zpool events -v
TIME CLASS
Dec 20 2025 16:46:39.702350180 sysevent.fs.zfs.history_event
version = 0x0
class = "sysevent.fs.zfs.history_event"
pool = "nas"
pool_guid = 0x2f9ad6b17a5e8426
pool_state = 0x0
pool_context = 0x0
history_hostname = ""
history_internal_str = "pool version 5000; software version zfs-2.2.7-0-ge269af1b3; uts 14.3-RELEASE 1403000 amd64"
history_internal_name = "open"
history_txg = 0x2d65
history_time = 0x6946b6cf
time = 0x6946b6cf 0x29dd0364
eid = 0x1
...
А з zpool history можна подивитись всі команди, які запускались:
root@setevoy-nas:/home/setevoy # zfs-stats -E
------------------------------------------------------------------------
ZFS Subsystem Report Sun Dec 21 18:07:59 2025
------------------------------------------------------------------------
ARC Efficiency: 104.72 k
Cache Hit Ratio: 99.87% 104.58 k
Cache Miss Ratio: 0.13% 137
Actual Hit Ratio: 99.87% 104.58 k
Data Demand Efficiency: 100.00% 0
CACHE HITS BY CACHE LIST:
Most Recently Used: 23.21% 24.28 k
Most Frequently Used: 76.79% 80.30 k
Most Recently Used Ghost: 0.00% 0
Most Frequently Used Ghost: 0.00% 0
CACHE HITS BY DATA TYPE:
Demand Data: 0.00% 0
Prefetch Data: 0.00% 0
Demand Metadata: 99.96% 104.54 k
Prefetch Metadata: 0.04% 39
CACHE MISSES BY DATA TYPE:
Demand Data: 0.00% 0
Prefetch Data: 0.00% 0
Demand Metadata: 91.24% 125
Prefetch Metadata: 8.76% 12
Або використати zfs-mon:
Ну і по моніторингу ZFS цього, мабуть, вистачить.
Ще окремо будемо говорити вже про повноцінний моніторинг системи – планую з VictoriaMetrics, і тоді можна буде додати якийсь експортер для ZFS, наприклад zfs_exporter.
Але зараз, аби підключитись до якогось хосту в мережах треба вказувати IP-адресу.
Можна, звісно, прописувати все в файлах /etc/hosts, але це і не дуже зручно, і будуть клієнти типу Android-телефонів, і взагалі – хочеться все красівоє.
Тому зробимо локальний DNS, на якому буде централізований DNS для всієї інфраструктури:
власну локальну DNS-зону .setevoy
DNS-відповіді для .setevoy, що залежать від мережі, з якої приходить запит (office/home/VPN)
резолвінг зовнішніх доменів через forward-DNS (Cloudflare/Google)
Всі частини серії по налаштуванню домашнього NAS на FreeBSD:
Я колись мав BIND, який обслуговував навіть зону самого rtfm.co.ua – але для маленької домашньої мережі це зовсім overkill.
Мав справу з dnsmasq – простий, швидкий, вміє в DHCP, але трохи обмежений в можливостях.
І Unbound – рідний для FreeBSD, теж простий і швидкий, вміє в DNSSEC validation прям з коробки, вміє в views/access-control – те, що треба.
Routers та DHCP зі Static IP
Взагалі, можна було б заморочитись і автоматизувати навіть видачу IP для хостів в мережах, але, по-перше, ну камон – в мене 3 машинки у двох мережах 🙂
По-друге – робочий ноут і ThinkCenter знаходяться в офісі, а домашній ноутбук – вдома. Тобто, робити DHCP на хості з FreeBSD – така собі затєя, бо домашній ноутбук може бути не підключеним до VPN.
Тому – прибиваємо все статично в налаштуваннях роутера (чекаю, чекаю, коли до мене доїде MikroTik hAP ax3!):
Тепер для робочого ноута, коли він підключений по Ethernet, адреса завжди буде 192.168.0.3.
root@setevoy-nas:/home/setevoy # service unbound start
Перевіряємо локально, з хоста з FreeBSD з drill замість dig:
root@setevoy-nas:/home/setevoy # drill nas.setevoy @127.0.0.1
...
;; ANSWER SECTION:
nas.setevoy. 3600 IN A 192.168.0.2
...
І якийсь зовнішній домен:
root@setevoy-nas:/home/setevoy # drill google.com @127.0.0.1
...
;; ANSWER SECTION:
google.com. 300 IN A 142.251.98.101
google.com. 300 IN A 142.251.98.139
...
Тепер при підключенні VPN буде задаватись 10.8.0.1 як основний, і адреса домашнього роутера 192.168.100.1 як fallback опція.
Ще можна буде налаштувати split-DNS, задавши Domains = ~setevoy, аби до 10.8.0.1 робити запити тільки для зони .setevoy, див. systemd-resolved та systemd-networkd.
Налаштування forward-only DNS
З поточним конфігом наш unbound виконує повний рекурсивний пошук.
Тобто, коли ми з клієнта робимо запит google.com – то:
unbound звертається до root-серверів DNS
там шукає Name Servers для зони .com
звертається до цих Name Servers, запитуючи інформацію про Name Servers для google.com
і тільки після цього йде на Name Servers google.com, звідки бере інформацію по IP, яку потім повертає клієнту
Натомість можемо налаштувати forward-only, і тоді Unbound одним запитом сходить на 1.1.1.1 або 8.8.8.8, і відразу поверне відповідь клієнту.
root@setevoy-nas:/home/setevoy # unbound-checkconf && service unbound reload
Тепер Unbound в порядку пріорітету спочатку перевірить свою локальну зону, якщо google.com в нього на обслуговуванні нема – то він переадресує запит до Cloudflare або Google.
Налаштування логів
Робив для того, аби подивись як впливає налаштування forward-zone – тому теж нехай тут буде.
Тепер відключаємо forward-zone, з робочого ноутбука робимо запит про google.com до нашого DNS:
[setevoy@setevoy-work ~] $ time dig +short google.com @192.168.0.2
...
real 0m0.109s
Час виконання – 0.109s.
Дивимось лог Unbound:
...
info: 192.168.0.3 google.com. A IN
...
info: priming . IN NS
info: sending query: . NS IN
info: reply from <.> 199.7.83.42#53
info: priming successful for . NS IN
...
info: sending query: com. A IN
info: reply from <.> 202.12.27.33#53
info: query response was REFERRAL
...
info: sending query: google.com. A IN
info: reply from <google.com.> 216.239.38.10#53
info: query response was ANSWER
...
info: 192.168.0.3 google.com. A IN NOERROR 0.101574
...
Тут добре видно, як Unbound працює як рекурсивний резолвер :
спочатку виконує запит до root-зони “.“, де отримує Name Servers для зони .com
далі звертається до Name Servers зони .com, де отримує адреси Name Servers зони google.com
і лише після цього звертається вже до Name Servers домену google.com, отримуючи фінальну A-відповідь з IP-адресами серверів Google
А тепер повертаємо forward-zone, але залишаємо відключеним кешування (cache-min/max-ttl) і робимо запит з клієнта ще раз:
[setevoy@setevoy-work ~] $ time dig +short google.com @192.168.0.2
...
real 0m0.016s
Тепер час на отримання відповіді – 0.030s, а без forward-zone він був 0.109s (або з логу – google.com. A IN NOERROR 0.101574). Майже в 3 раз швидше. Іноді буває і 0.01s.
І логи Unbound тепер набагато менші:
...
debug: forwarding request
...
debug: ip4 1.1.1.1 port 53
debug: ip4 8.8.8.8 port 53
info: sending query: google.com. A IN
debug: sending to target: <.> 8.8.8.8#53
...
info: 192.168.0.3 google.com. A IN NOERROR 0.039006
...
Все, що він зробив – це передав запит до 8.8.8.8 і повернув результат, отриманий від нього.
Unbound DNS views для різних мереж
Зараз у нас з дому, який за VPN, при запиті nas.setevoy повернеться адреса 192.168.0.1:
...
local-data: "nas.setevoy. A 192.168.0.2"
...
Але якщо хочемо ходити чисто через нетворк VPN – можемо розділити зони з views і для кожної мережі створити власні зони:
server:
interface: 0.0.0.0
do-daemonize: yes
do-ip6: no
hide-identity: yes
hide-version: yes
# map client networks to views
access-control-view: 127.0.0.0/8 office
access-control-view: 192.168.0.0/24 office
access-control-view: 10.8.0.0/24 vpn
#######################
### Hosts list note ###
#######################
# -Office:
# - setevoy-work: Arch Linux laptop
# - setevoy-pc: Arch Linux/Windows gaming PC
# - setevoy-nas: FreeBSD ThinkCentre
# - TP-Link router
# - Home:
# - setevoy-home: Arch Linux laptop
# - TP-Link router
################
# OFFICE VIEW #
################
view:
name: "office"
local-zone: "setevoy." static
local-data: "nas.setevoy. A 192.168.0.2"
local-data: "work.setevoy. A 192.168.0.3"
local-data: "pc.setevoy. A 192.168.0.4"
#############
# VPN VIEW #
#############
view:
name: "vpn"
local-zone: "setevoy." static
local-data: "nas.setevoy. A 10.8.0.1"
# not VPN-connected, but Home has routing to the 192.168.0.0/24
local-data: "work.setevoy. A 192.168.0.3"
local-data: "pc.setevoy. A 192.168.0.4"
#################
# FORWARD ONLY #
#################
forward-zone:
name: "."
forward-addr: 1.1.1.1
forward-addr: 8.8.8.8
Тут в access-control-view: 10.8.0.0/24 vpn прив’язує клієнтів з VPN-мережі 10.8.0.0/24 до view з ім’ям vpn, для якого далі окремо описується DNS-логіка.
Конкретно в моєму сетапі домашній ноутбук все одно має роутинг в мережу 192.168.0.0/24 через 10.8.0.1, і можна повертати адреси з неї – але в цілому штука корисна.
Перевіряємо, застосовуємо:
root@setevoy-nas:/home/setevoy # unbound-checkconf && service unbound reload
unbound-checkconf: no errors in /usr/local/etc/unbound/unbound.conf
Перевіряємо з дому – отримуємо адресу з мережі VPN:
Основна ідея – поєднати (нарешті!) мій “офіс” і квартиру, а пізніше, можливо, ще і підключити сервер, на якому зараз працює rtfm.co.ua – аби бекапи блогу і баз даних зберігати відразу на ZFS пулі з RAID домашнього сервера.
WireGuard vs OpenVPN
Коли діло дійшло до вибору який конкретно VPN сервер вибрати, то я спочатку думав взяти OpenVPN – бо працюю з ним не один рік, і на RTFM про нього навіть є якісь матеріали.
Але, трохи подумавши вирішив, що для домашнього VPN рішення типу OpenVPN або Pritunl будуть трохи оверхедом, і можна спробувати WireGuard.
Системи дуже різні, але якщо коротко, то:
WireGuard набагато менший по коду – наприклад, Linux-реалізація це близько 4000 строк в ядрі, тоді як в OpenVPN це близько 100,000 строк в user space
WireGuard працює як модуль ядра – обробка пакетів і криптографія виконуються безпосередньо в kernel space, а OpenVPN є user space сервісом і працює через TCP або UDP socket та взаємодіє з ядром через стандартний мережевий стек ядра
туди ж – шифрування, бо WireGuard має вбудовану криптографію, яка є частиною самого протоколу і працює в kernel space, а OpenVPN використовує стандартний SSL/TLS стек (OpenSSL, LibreSSL тощо) в user space, що додає складність і накладні витрати CPU/RAM
модель роботи WireGuard – peer-to-peer, тобто протокол не має вбудованих ролей “сервер” чи “клієнт”, є лише Peers з ключами і дозволеними IP, тоді як OpenVPN побудований навколо класичної клієнт-серверної архітектури
В результаті WireGuard можна сприймати не як окремий сервіс, а як зашифрований мережевий інтерфейс, тоді як OpenVPN залишається класичним прикладним VPN-сервісом.
“офіс”: окрема локальна мережа 192.168.0.0/24, на вході – роутер TP-LINK Archer AX12
в цій мережі є робочий ноутбук з Arch Linux і Lenovo ThinkCentre з FreeBSD
на FreeBSD буде NAS, NFS і, власне, саме WireGuard
хоча Archer AX12 має власні вбудовані OpenVPN і WireGuard – але хочеться зробити самому, руками, плюс все ж більше контролю
дома: там мережа 192.168.100.0/24, на вході точно такий же роутер Archer AX12
там єдиний клієнт – це домашній ноутбук з Arch Linux
І що я хочу зробити:
на FreeBSD буде WireGuard в ролі VPN-сервера
на роутері Archer AX12 буде NAT port-forwarding для підключення до WireGuard на FreeBSD
мережа VPN – 10.8.0.1/24
на FreeBSD – Paket Filter firewall для контролю трафіка
обидва ноутбуки повинні мати доступ один до одного і до майбутнього NAS на FreeBSD
Як це в результаті виглядає схематично:
Запуск WireGuard на FreeBSD
У FreeBSD (власне, як і в Linux) WireGuard – це kernel module + userspace tools: основна “робоча” частина завантажується як модуль ядра, а для роботи з ним встановлюється окремий пакет.
роутити інтернет через VPN не потрібно – тільки трафік між мережами дома і офісу
Конфіг pf зараз – мінімалістичний, з попереднього поста:
allowed_tcp_ports = "{ 22 }"
allowed_clients = "{ 192.168.0.0/24, 192.168.1.0/24 }"
set skip on lo
block all
# allow ssh only from specific hosts
pass in proto tcp from $allowed_clients to any port $allowed_tcp_ports keep state
# allow all outgoing traffic
pass out all keep state
Що до нього треба додати:
дозволити вхідні UDP-з’єднання на порт WireGuard (51820) для handshake
дозволити трафік з VPN-мережі 10.8.0.0/24 до самого FreeBSD-хоста (ping, SSH)
дозволити транзитний трафік з VPN-мережі 10.8.0.0/24 до локальних мереж офісу і дому (192.168.0.0/24 та 192.168.100.0/24)
дозволити ICMP і SSH з VPN-мережі та домашньої мережі до FreeBSD-хоста
дозволити вихідний трафік з FreeBSD
Я додав макроси в конфіг, але поки пишу і тестую – вказую всі порти та адреси явно прямо в конфігу, простіше читати.
Тепер /etc/pf.conf буде виглядати так:
##################
### Interfaces ###
##################
# lan_if = "em0"
# wg_if = "wg0"
################
### Networks ###
################
# lan_net = "192.168.0.0/24"
# home_net = "192.168.100.0/24"
# wg_net = "10.8.0.0/24"
# vpn_nets = "{ 10.8.0.0/24, 192.168.100.0/24 }"
################
### Services ###
################
# ssh_ports = "{ 22 }"
# wg_port = "51820"
######################
### Basic settings ###
######################
# do not filter loopback traffic
set skip on lo
######################
### Default policy ###
######################
# block everything by default
block all
#######################
### Inbound traffic ###
#######################
### SSH
# allow SSH from Office LAN (192.168.0.0/24) to FreeBSD host
pass in log on em0 proto tcp from 192.168.0.0/24 to (em0) port 22 keep state
# allow SSH from Home network (192.168.100.0/24) to FreeBSD host
pass in log on em0 proto tcp from 192.168.100.0/24 to (em0) port 22 keep state
# allow SSH from VPN clients to FreeBSD host
pass in on wg0 proto tcp from 10.8.0.0/24 to (wg0) port 22 keep state
### VPN
# allow WireGuard handshake (UDP/51820) on LAN interface
pass in on em0 proto udp to (em0) port 51820 keep state
# allow VPN clients (10.8.0.0/24) to access FreeBSD host itself
# this allows ping, ssh, etc. to the wg0 address
pass in on wg0 from 10.8.0.0/24 to (wg0) keep state
# allow VPN clients to access Office LAN (192.168.0.0/24)
pass in on wg0 from 10.8.0.0/24 to 192.168.0.0/24 keep state
# allow VPN clients to access Home network (192.168.100.0/24)
pass in on wg0 from 10.8.0.0/24 to 192.168.100.0/24 keep state
# allow ICMP (ping) from VPN clients to FreeBSD host
pass in on wg0 proto icmp from 10.8.0.0/24 to (wg0) keep state
# allow ICMP (ping) from Home network to FreeBSD host
pass in on em0 proto icmp from 192.168.100.0/24 to (em0) keep state
############################
### outbound traffic ###
############################
# allow all outbound traffic from FreeBSD
pass out keep state
Перевіряємо синтаксис:
root@setevoy-nas:/home/setevoy # pfctl -vnf /etc/pf.conf
set skip on { lo }
block drop all
pass in log on em0 inet proto tcp from 192.168.0.0/24 to (em0) port = ssh flags S/SA keep state
pass in log on em0 inet proto tcp from 192.168.100.0/24 to (em0) port = ssh flags S/SA keep state
pass in on wg0 inet from 10.8.0.0/24 to (wg0) flags S/SA keep state
pass in on wg0 inet proto icmp from 10.8.0.0/24 to (wg0) keep state
pass in on wg0 inet from 10.8.0.0/24 to 192.168.0.0/24 flags S/SA keep state
pass in on wg0 inet from 10.8.0.0/24 to 192.168.100.0/24 flags S/SA keep state
pass in on em0 inet proto icmp from 192.168.100.0/24 to (em0) keep state
pass in on em0 proto udp from any to (em0) port = 51820 keep state
pass out all flags S/SA keep state
Виконуємо reload:
root@setevoy-nas:/home/setevoy # service pf reload
Reloading pf rules.
Тепер можемо підготувати запуск WireGuard.
Конфігурація WireGuard
Тут все дуже просто – створити ключі, написати конфіг-файл.
Створення ключів
Комунікація і криптографія у WireGuard побудована на стандартній схемі асиметричних ключів:
на “сервері” зберігається приватний ключі
на клієнті вказується публічний
під час handshake клієнт переконується, що підключився саме до того сервера, публічний ключ якого він знає
а далі, з цими ключами, відбувається і шифрування даних
Слово “сервер” все ж беру в лапки, бо, як було зазначено вище – WireGuard це P2P, а не client-server.
Після установки wireguard-tools створюється каталог /usr/local/etc/wireguard – переходимо туди, і з wg genkey створюємо приватний та публічний ключ:
root@setevoy-nas:/home/setevoy # cd /usr/local/etc/wireguard
root@setevoy-nas:/usr/local/etc/wireguard # wg genkey | tee server.key | wg pubkey > server.pub
root@setevoy-nas:/usr/local/etc/wireguard # chmod 600 server.key
root@setevoy-nas:/usr/local/etc/wireguard # ll
total 12
-rw------- 1 root wheel 45 Dec 17 15:58 server.key
-rw-r--r-- 1 root wheel 45 Dec 17 15:58 server.pub
Базовий конфіг для WireGuard
Можна створити кілька різних конфігурацій в /usr/local/etc/wireguard/, кожен на власному порті та/або IP та з власним ключем і мати кілька різних VPN-підключень, а керувати ними використовуючи ім’я файлу – wg0, wg1, etc.
Блок Interface визначає параметри WireGuard-інтерфейсу wg0 – його IP-адресу, UDP-порт і приватний ключ, який використовується для шифрування трафіку.
Тут жеж можна вказати які DNS використовувати, чи робити апдейт в таблицях маршрутизації клієнтів (default – true) і запуск скриптів з PreUp, PostUp, PreDown, PostDown.
Запускаємо сам WireGuard:
root@setevoy-nas:/home/setevoy # wg-quick up wg0
[#] ifconfig wg create name wg0
[#] wg setconf wg0 /dev/stdin
[#] ifconfig wg0 inet 10.8.0.1/24 alias
[#] ifconfig wg0 mtu 1420
[#] ifconfig wg0 up
[+] Backgrounding route monitor
Перевіряємо інтерфейс:
root@setevoy-nas:/home/setevoy # ifconfig wg0
wg0: flags=10080c1<UP,RUNNING,NOARP,MULTICAST,LOWER_UP> metric 0 mtu 1420
options=80000<LINKSTATE>
inet 10.8.0.1 netmask 0xffffff00
groups: wg
nd6 options=109<PERFORMNUD,IFDISABLED,NO_DAD>
Та статус WireGuard:
root@setevoy-nas:/home/setevoy # wg show
interface: wg0
public key: xLWA/FgF3LBswHD5Z1uZZMOiCbtSvDaUOOFjH4IF6W8=
private key: (hidden)
listening port: 51820
Поки у нас нема ніяких клієнтів – переходимо до них.
TP-Link Dynamic DNS та NAT port-forwarding
Аби з дому підключатись до хосту з FreeBSD і WireGuard – на роутері в офісі додаємо форвард портів:
protocol: UDP
зовнішній порт на роутері: 51830 (трохи замаскувати від ботів)
куди форвардити: 192.168.0.2 (хост з FreeBSD)
на який порт форвардити: 51830 (WireGuard на em0 на FreeBSD)
На TP-Link Archer AX12 це виглядає так:
Якщо Internet IP в офісі динамічний – в Archer AX12 є можливість налаштування Dynamic DNS:
Хоча в мене він статичний, але DDNS заради інтересу налаштував з https://www.noip.com.
Запуск WireGuard на Arch Linux
В Linux все аналогічно – в ядрі є модулі, нам треба тільки встановити пакет з утилітами.
Тут в AllowedIPs вказуємо мережі в які, по-перше, буде доступ взагалі, по-друге – вони будуть додані в таблиці маршрутизації (“Acts as a routing table and access control list“).
Запускаємо на клієнті:
[root@setevoy-wg-test setevoy]# wg-quick up wg0
[#] ip link add dev wg0 type wireguard
[#] wg setconf wg0 /dev/fd/63
[#] ip -4 address add 10.8.0.3/24 dev wg0
[#] ip link set mtu 1420 up dev wg0
[#] ip -4 route add 10.8.0.3/32 dev wg0
[#] ip -4 route add 192.168.0.0/24 dev wg0
Тут:
ip -4 address add: Interface - Address заданий для wg0
ip -4 route add 10.8.0.3/32 та 192.168.0.0/24: додані нові роути через інтерфейс wg0 для мереж VPN та офісної локалки
Перевіряємо:
root@setevoy-home:/etc/wireguard # ip r s 10.8.0.0/24
10.8.0.0/24 dev wg0 proto kernel scope link src 10.8.0.3
root@setevoy-home:/etc/wireguard # ip r s 192.168.0.0/24
192.168.0.0/24 dev wg0 scope link
Додаємо Peer на сервері, файл /usr/local/etc/wireguard/wg0.conf тепер буде таким:
В принципі, на цьому вже майже все готове – доступ є, все працює.
Але чого хочеться ще – це мати прямий доступ з домашнього ноута на робочий і з робочого на домашній, бо на робочому ноуті VPN нема – він там і не потрібен, бо FreeBSD/NAS в тій самій локальній мережі.
Конфігурація cross-LAN доступу
Тож що треба зробити – це налаштувати прямий доступ між ноутами в домашній мережі 192.168.100.0/24 і офісній 192.168.0.0/24, бо зараз з робочого ноутбука на ноутбук вдома і навпаки доступ не працює.
Картина зараз така:
IP ноута в офісі: 192.168.0.165
IP ноута вдома: 192.168.100.205
на робочому ноуті WireGuard нема
з офісу на домашній ноут конекта нема
з дому на робочий ноут конекта нема
з дому на FreeBSD конект є
Налаштування Routing tables
Поки робимо – закоментуємо block all в /etc/pf.conf, потім до нього повернемось.
В результаті того, що зараз будемо робити – вийде ось така схема: тут головне – це маршрути, спеціально робив схемою, аби те, що буде описано далі було простіше зрозуміти:
Перевіряємо роути з домашнього ноута на FreeBSD:
root@setevoy-home:/etc/wireguard # ip route get 192.168.0.2
192.168.0.2 dev wg0 src 10.8.0.3 uid 0
І на робочий ноут:
root@setevoy-home:/etc/wireguard # ip route get 192.168.0.165
192.168.0.165 dev wg0 src 10.8.0.3 uid 0
Трафік йде через wg0, і Source Address для пакета задається як 10.8.0.3.
А на робочому ноуті роут на домашній ноут йде через 192.168.0.1:
[setevoy@setevoy-work ~] $ ip route get 192.168.100.205
192.168.100.205 via 192.168.0.1 dev wlan0 src 192.168.0.165 uid 1000
Тут 192.168.0.1 – дефолтний гейтвей, роутер в офісі, який нічого не знає про домашню мережу 192.168.100.0/24.
Тому перше – додаємо роут в домашню мережу через хост з FreeBSD:
[setevoy@setevoy-work ~] $ sudo ip route add 192.168.100.0/24 via 192.168.0.2
Перевіряємо ще раз:
[setevoy@setevoy-work ~] $ ip route get 192.168.100.205
192.168.100.205 via 192.168.0.2 dev wlan0 src 192.168.0.165 uid 1000
Тепер є контакт з офісу додому:
[setevoy@setevoy-work ~] $ ping 192.168.100.205 -c 1
PING 192.168.100.205 (192.168.100.205) 56(84) bytes of data.
64 bytes from 192.168.100.205: icmp_seq=1 ttl=63 time=62.0 ms
--- 192.168.100.205 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
Але з домашнього все ще нема, бо з дому ми шлемо:
з домашнього ноута з IP 192.168.100.205
через FreeBSD з IP 192.168.0.2
на робочий ноутбук з IP 192.168.0.165
Але у нас з домашнього ноутбука задається Source IP як 10.8.0.3:
root@setevoy-home:/etc/wireguard # ip route get 192.168.0.165
192.168.0.165 dev wg0 src 10.8.0.3 uid 0
Бо маршрут в 192.168.0.0/24 заданий через VPN інтерфейс wg0:
root@setevoy-home:/etc/wireguard # ip r s 192.168.0.0/24
192.168.0.0/24 dev wg0 scope link
А у wg0 заданий IP 10.8.0.3:
root@setevoy-home:/etc/wireguard # ip a s wg0
20: wg0: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1420 qdisc noqueue state UNKNOWN group default qlen 1000
link/none
inet 10.8.0.3/24 scope global wg0
А робочий ноут нічого про мережу 10.8.0.0/24 не знає і не може повернути відповідь.
Тому на робочому ноуті додаємо ще один маршрут:
[setevoy@setevoy-work ~] $ sudo ip route add 10.8.0.0/24 via 192.168.0.2 dev wlan0
Перевіряємо:
[setevoy@setevoy-work ~] $ ip r s 10.8.0.0/24
10.8.0.0/24 via 192.168.0.2 dev wlan0
І тепер з домашнього ноута на робочий доступ теж є:
root@setevoy-home:/etc/wireguard # ping -c1 192.168.0.165
PING 192.168.0.165 (192.168.0.165) 56(84) bytes of data.
64 bytes from 192.168.0.165: icmp_seq=1 ttl=63 time=6.19 ms
--- 192.168.0.165 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
Аби ці роути додати постійно – можна зробити з NetworkManager CLI.
Видаляємо те, що додали вручну:
[setevoy@setevoy-work ~] $ sudo ip route del 10.8.0.0/24 via 192.168.0.2
[setevoy@setevoy-work ~] $ sudo ip route del 192.168.100.0/24 via 192.168.0.2
Знаходимо ім’я підключення:
[setevoy@setevoy-work ~] $ nmcli connection show
NAME UUID TYPE DEVICE
setevoy-tp-link-21-5 3a12a60d-7b37-4c20-b573-d27c47a94ae5 wifi wlan0
...
[setevoy@setevoy-work ~] $ nmcli connection show setevoy-tp-link-21-5 | grep ipv4.routes
ipv4.routes: { ip = 10.8.0.0/24, nh = 192.168.0.2 }; { ip = 192.168.100.0/24, nh = 192.168.0.2 }
Перезапускаємо підключення:
[setevoy@setevoy-work ~] $ sudo nmcli connection down setevoy-tp-link-21-5 && sudo nmcli connection up setevoy-tp-link-21-5
Connection 'setevoy-tp-link-21-5' successfully deactivated (D-Bus active path: /org/freedesktop/NetworkManager/ActiveConnection/15)
Connection successfully activated (D-Bus active path: /org/freedesktop/NetworkManager/ActiveConnection/16)
Перевіряємо роути тепер:
[setevoy@setevoy-work ~] $ ip route get 10.8.0.3
10.8.0.3 via 192.168.0.2 dev wlan0 src 192.168.0.165 uid 1000
[setevoy@setevoy-work ~] $ ip route get 192.168.100.205
192.168.100.205 via 192.168.0.2 dev wlan0 src 192.168.0.165 uid 1000
Тепер у нас є ping з офісного ноутбука на домашній:
[setevoy@setevoy-work ~] $ ping -c1 192.168.100.205
PING 192.168.100.205 (192.168.100.205) 56(84) bytes of data.
64 bytes from 192.168.100.205: icmp_seq=1 ttl=63 time=5.95 ms
--- 192.168.100.205 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
І з домашнього на робочий:
root@setevoy-home:/etc/wireguard # ping -c1 192.168.0.165
PING 192.168.0.165 (192.168.0.165) 56(84) bytes of data.
64 bytes from 192.168.0.165: icmp_seq=1 ttl=63 time=5.67 ms
--- 192.168.0.165 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
Налаштування Packet Filter
Але якщо ми включимо block all в pf, то підключення з офісу на домашній ноут зламається, бо у нас зараз правила тільки для FreeBSD host:
...
# allow SSH from Office LAN (192.168.0.0/24) to FreeBSD host
pass in log on em0 proto tcp from 192.168.0.0/24 to (em0) port 22 keep state
...
# allow ICMP (ping) from Home network to FreeBSD host
pass in on em0 proto icmp from 192.168.100.0/24 to (em0) keep state
...
Тут:
перше правило – дозволяє SSH з офісної мережі на IP інтерфейсу em0 хоста з FreeBSD
друге – дозволяє ping з домашньої мережі на IP інтерфейсу em0 хоста з FreeBSD
Тому додаємо ще два правила – з SSH і ping з офісу додому:
...
# allow SSH from Office network to Home network
pass in on em0 proto tcp from 192.168.0.0/24 to 192.168.100.0/24 port 22 keep state
...
# allow ICMP from Home network to Office network
pass in on em0 proto icmp from 192.168.0.0/24 to 192.168.100.0/24 keep state
...
Перевіряємо, перечитуємо конфіг pf:
root@setevoy-nas:/usr/local/etc/wireguard # pfctl -vnf /etc/pf.conf && service pf reload
set skip on { lo }
block drop log all
pass in log on em0 inet proto tcp from 192.168.0.0/24 to (em0) port = ssh flags S/SA keep state
pass in log on em0 inet proto tcp from 192.168.100.0/24 to (em0) port = ssh flags S/SA keep state
pass in on wg0 inet from 10.8.0.0/24 to (wg0) flags S/SA keep state
pass in on wg0 inet proto icmp from 10.8.0.0/24 to (wg0) keep state
pass in on wg0 inet from 10.8.0.0/24 to 192.168.0.0/24 flags S/SA keep state
pass in on wg0 inet from 10.8.0.0/24 to 192.168.100.0/24 flags S/SA keep state
pass in on em0 inet proto tcp from 192.168.0.0/24 to 192.168.100.0/24 port = ssh flags S/SA keep state
pass in on em0 inet proto icmp from 192.168.0.0/24 to 192.168.100.0/24 keep state
pass in on em0 proto udp from any to (em0) port = 51820 keep state
pass out all flags S/SA keep state
Reloading pf rules.
І тепер у нас є пінг з дому в офіс:
root@setevoy-home:/etc/wireguard # ping -c1 192.168.0.165
PING 192.168.0.165 (192.168.0.165) 56(84) bytes of data.
64 bytes from 192.168.0.165: icmp_seq=1 ttl=63 time=8.09 ms
--- 192.168.0.165 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
##################
### Interfaces ###
##################
# lan_if = "em0"
# wg_if = "wg0"
################
### Networks ###
################
# lan_net = "192.168.0.0/24"
# home_net = "192.168.100.0/24"
# wg_net = "10.8.0.0/24"
# vpn_nets = "{ 10.8.0.0/24, 192.168.100.0/24 }"
################
### Services ###
################
# ssh_ports = "{ 22 }"
# wg_port = "51820"
######################
### Basic settings ###
######################
# do not filter loopback traffic
set skip on lo
######################
### Default policy ###
######################
# block everything by default
block log all
#######################
### Inbound traffic ###
#######################
### SSH
# allow SSH from Office LAN (192.168.0.0/24) to FreeBSD host
pass in log on em0 proto tcp from 192.168.0.0/24 to (em0) port 22 keep state
# allow SSH from Home network (192.168.100.0/24) to FreeBSD host
pass in log on em0 proto tcp from 192.168.100.0/24 to (em0) port 22 keep state
# allow SSH from VPN clients to FreeBSD host
pass in on wg0 proto tcp from 10.8.0.0/24 to (wg0) port 22 keep state
### NEW
# allow SSH from Office netwrok to Home network
pass in on em0 proto tcp from 192.168.0.0/24 to 192.168.100.0/24 port 22 keep state
### TEST
# allow Office LAN to reach Home LAN via WireGuard
#pass in on em0 from 192.168.0.0/24 to 192.168.100.0/24 keep state
#pass out on wg0 from 192.168.0.0/24 to 192.168.100.0/24 keep state
# allow Home LAN to reach Office LAN via WireGuard
#pass in on wg0 from 192.168.100.0/24 to 192.168.0.0/24 keep state
#pass out on em0 from 192.168.100.0/24 to 192.168.0.0/24 keep state
### VPN
# allow WireGuard handshake (UDP/51820) on LAN interface
pass in on em0 proto udp to (em0) port 51820 keep state
# allow VPN clients (10.8.0.0/24) to access FreeBSD host itself
# this allows ping, ssh, etc. to the wg0 address
pass in on wg0 from 10.8.0.0/24 to (wg0) keep state
# allow VPN clients to access Office LAN (192.168.0.0/24)
pass in on wg0 from 10.8.0.0/24 to 192.168.0.0/24 keep state
# allow VPN clients to access Home network (192.168.100.0/24)
pass in on wg0 from 10.8.0.0/24 to 192.168.100.0/24 keep state
#
#pass in on em0 from 192.168.0.0/24 to 192.168.100.0/24 keep state
#pass in on wg0 from 192.168.100.0/24 to 192.168.0.0/24 keep state
### ICMP
# allow ICMP from VPN clients to FreeBSD host
pass in on wg0 proto icmp from 10.8.0.0/24 to (wg0) keep state
# allow ICMP from Home network to FreeBSD host
#pass in on em0 proto icmp from 192.168.100.0/24 to (em0) keep state
# allow ICMP from Home network to Office network
pass in on em0 proto icmp from 192.168.0.0/24 to 192.168.100.0/24 keep state
############################
### outbound traffic ###
############################
# allow all outbound traffic from FreeBSD
pass out keep state
Активні підключення в pftop:
Тут:
In 192.168.0.165:50286 => 192.168.0.2:22: SSH робочий ноут на FreeBSD
In 178.***.***.236:56432 => 192.168.0.2:51820: підключення з дому через NAT Port-forwarding на офісному роутері до VPN на FreeBSD
In 10.8.0.3:39442 => 192.168.0.165:22: SSH з дому на робочий ноут
Out 10.8.0.1:50589 => 10.8.0.3:22: SSH з FreeBSD на домашній
P.S. Який це дикий кайф – оцей во “traditional networking” а не ці всі AWS VPC і його subnets…