Bugungi kunda biz sanstun ni taqdim etamiz: bu sans-IO Rust implementatsiyasi WireGuard® holat mashinasi, osonlik, xavfsizlik va samarali ishlash uchun optimallashtirilgan parallel-friendly va idiomatic API bilan.
Sans-IO nima?
Sans-IO — bu tizimni shunday loyihalashtirish gʻoyasi boʻlib, unda IO va yon ta’sirlar holat va biznes-logikadan toza ajratiladi. Sans-IOdagi IO faqatgina fayl yoki tarmoq IO emas, balki vaqt va tasodifiylikni ham oʻz ichiga oladi!
impl PendingEncapsulate {
pub fn encrypt<'a>(self, plaintext: &[u8], dst: &'a mut [u8]) -> Result<DataMessage<'a>, Error> {
// ...
}
}
Yon ta’sirlar yoʻqligi sababli, sans-IO komponentlari yaxshi birlashadi va sinovdan oʻtkazish osonroqdir. Bunday dizayn funksiyalarni ranglashtirish kabi muammolarni oldini oladi va Rustning egalik qoidalariga yaxshi mos keladi.
Bu postdagi kod namunalari va havolalar Rust tilida, lekin sans-IO Rustga xos emas. Uning usullari va gʻoyalari har qanday tilda qoʻllanilishi mumkin. Chunki sans-IO haqidagi postimizni oʻqish mumkin.
Tarix
Biz Firezone ni birinchi marta qurayotganda, boringtun ni WireGuard implementatsiyasi sifatida tanladik. Boringtun bizning talablarimizni qondirdi, lekin vaqt oʻtishi bilan biz uni bug fixes va API qoʻshimchalari bilan doimiy ravishda tahrirlashimiz kerak boʻldi.
Maqsad bu tahrirlashlarni asosiy kod bazasiga qaytarish edi, shuning uchun biz doimiy ravishda mavjud APIni ishlashini taʼminladik. Bizning mahsulot talablari rivojlanib borayotgani sababli, asosiy kod bazasiga 100% API mosligi saqlash qiyinlashdi.
Boringtun ga birinchi katta oʻzgartirishlarimizdan biri vaqtni boshqarish boʻyicha toʻliq sans-IO qilish edi. Bu Instant ni qabul qiladigan _at suffiksli yangi APIlarni qoʻshishni talab qildi.
Sans-IO vaqtni boshqarish butun tizimni aniq simulyatsiya qilish uchun juda muhimdir. Bu haqida koʻproq oʻqish uchun bu blog postini koʻring.
Boshqa bir narsani biz feature-flag qildik — ichki buferlash. Boshida, boringtun sessiya avtomatik ravishda oʻrnatadi, agar sizda hali sessiya yoʻq boʻlsa yoki siz paket yubormoqchi boʻlsangiz. Ichki buferlash, ammo, bir nechta muammolarni keltirib chiqaradi:
- Buferni bo'shatish kerak bo'lgan vaqt keladi. Boringtun bu muammoni hal qilish uchun sizga nol uzunlikdagi datagram bilan decapsulate qilishni talab qiladi. Bu juda aniq emas.
- Ushbu implementatsiyada (boringtun) qoʻshimcha allocatsiyalarni keltirib chiqaradi.
- Bufer oʻlchamlari konfiguratsiya qilinishi / sozlanishi kerak.
Barcha bu muammolar bir xil halni talab qiladi: Kutuphada buferlashni amalga oshirma. Bu interfeys va ikkala tomon uchun implementatsiyani soddalashtiradi.
Tahrirlashlar va to'g'rilashlar ro'yxati juda uzun. Bog'lanishlarni hisoblashdan tashqari, bu 70 dan ortiq PR. Ba'zilarida biz topgan chekka holatlarni to'g'rilash, boshqalari esa yangi APIlarni qo'shish yoki ba'zi jihatlarni konfiguratsiya qilishdir.
Boringtun ni 2 yildan ortiq ishlatish va tahrirlashdan soʻng, bizning userspace WireGuard implementatsiyasining umumiy API nima boʻlishi kerakligi haqida yaxshi tushuncha bor edi. Endi u sanstun sifatida mavjud.
Afsuski sans-IO API
Sans-IO APIning asosiy bloklari endi:
- Kelib chiqadigan paketlarni boshqarish:
- Tunnel::handle_handshake_initiation
- Tunnel::handle_handshake_response
- Tunnel::handle_cookie_reply
- Tunnel::begin_decapsulate
- &Tunnel::commit_decapsulate
- Chiqib ketuvchi paketlarni boshqarish:
- Tunnel::begin_encapsulate
- Taymer yangilanishlarini boshqarish:
- Tunnel::handle_timeout
- &Tunnel::poll_timeout
- Voqealarni boshqarish:
Ichki buferlash yoʻqligi sababli, sizni maʼlum APIlarni doimiy ravishda chaqirish talabi yoʻq, bu sans-IO modeliga ancha yaxshi mos keladi.
Paket yuborish uchun siz Tunnel::begin_encapsulate ni chaqiring. Agar sizda hali sessiya yoʻq boʻlsa, siz paketni buferlashingiz kerak.
data_path/64B/begin_encapsulate
time: [20.785 ns 20.828 ns 20.869 ns]
data_path/64B/encrypt time: [226.15 ns 226.22 ns 226.29 ns]
data_path/64B/begin_decapsulate
time: [37.600 ns 37.675 ns 37.744 ns]
data_path/64B/decrypt time: [205.78 ns 205.83 ns 205.87 ns]
data_path/64B/commit_decapsulate
time: [17.486 ns 17.529 ns 17.575 ns]
data_path/1280B/begin_encapsulate
time: [20.785 ns 20.828 ns 20.869 ns]
data_path/1280B/encrypt time: [933.49 ns 933.54 ns 933.59 ns]
data_path/1280B/begin_decapsulate
time: [37.606 ns 37.681 ns 37.751 ns]
data_path/1280B/decrypt time: [914.81 ns 914.89 ns 914.97 ns]
data_path/1280B/commit_decapsulate
time: [17.657 ns 17.707 ns 17.764 ns]
Yangi sessiya yaratish, yangi handshake yuborishni anglatadi. Siz Tunnel::initiate_handshake ni istalgan vaqtda chaqiring.
Ushbu APIlarning dizayni juda maqsadli va WireGuard protokolini tezda qayta koʻrib chiqish bilan koʻproq ma’nosi beradi.
WireGuard protokoli
WireGuard xabar formati 4 turdagi xabarlarni qoʻllab-quvvatlaydi: handshake init, handshake response, cookie reply va shifrlangan ma'lumot.
Har bir WireGuard sessiyasi Diffie-Hellman kalit almashinuvi bilan boshlanadi, bu bir oz qimmatli operatsiya. WireGuard dizayni terminalni ochish DoS imkoniyatini ochmaydi.
Har qanday kimningdir terminalning ochiq kaliti borligini bilishi, yolgʻon handshake initiatsiyalarini yasalishi va kelib kelayotgan UDP paketining manba IP manzilini yolgʻon qilish oson. Qabul qiluvchi WireGuard implementatsiyasi DH operatsiyalarining yarmini bajarishdan oldin, berilgan handshake maʼlumotlari yolgʻon ekanligini bilishi mumkin emas. Lekin shu vaqtga kelib, CPU resurslari allaqachon sarflangan. Yetiqarli handshake initiatsiyalarini yuborish, qabul qiluvchi tomonning barcha CPU siklini notoʻgʻri handshakelarni boshqarishda sarflashiga olib keladi.
Bu muammoni oldini olish uchun WireGuard manba IP egalligini isbotlashni talab qiladigan cookie mexanizmini belgilaydi, bu esa manba IP boʻyicha handshakelarni cheklash imkonini beradi.
Ushbu cheklovlar butun WireGuard interfeysi uchun, faqat bitta tunnel uchun emas.
Handshake yakunlangandan soʻng, ikkala tomon simmetrik shifr bilan shifrlangan ma'lumot paketlarini almashishi mumkin. Handshake doirasida, tomonlar oʻzlarining mahalliy tanlangan sessiya indekslarini almashadilar. Ushbu indekslar tunnelni demultiplexlash va tunnel ichidagi bir nechta sessiyalarni imkoniyatini beradi.
Protokol invariantlarini API dizayni bilan bog'lash
Agar siz Rust kabi tilda ishlaysiz, siz tiplarni ishlatishni yaxshi koʻrsangiz, bu tiplarni yaxshi ishlatishni anglatadi, bu esa kompilatsiya vaqtida invariantlarni ta’minlashni anglatadi, shuning uchun siz biror xatoni birinchi navbatda joʻnatishingiz mumkin emas. Masalan, xabarga rate limiterni qoʻllamaslik juda yomon boʻladi, chunki bu normal ishlashda koʻrinmaydi. Shuningdek, kelib kelayotgan paketni notoʻgʻri tunnelga yoʻnaltirish faqat ishlab chiqarishda koʻrinadi.
Yaxshi API dizayni buzilgan narsalarni qurishni imkonsiz qiladi va shu bilan birga protokol qanday ishlashini tushunishga yordam beradi.
Bir interfeys, koʻp tunnel
WireGuard interfeysi bir nechta tunnelni boshqarishi mumkin. Har bir tunnel oʻzining xavfsizlik va konfiguratsiya parametrlari bilan ajratiladi. Bu dizayn bir nechta tunnelni boshqarish uchun bir xil interfeysdan foydalanish imkonini beradi.
Sanstun bu dizaynni qoʻllab-quvvatlaydi va bir nechta tunnelni boshqarish uchun bir xil APIni taklif qiladi. Bu foydalanuvchilarga bir nechta tunnelni boshqarish uchun bir xil interfeysdan foydalanish imkonini beradi.
Asl manba: firezone.dev