RDMA implementation review using AI (Claude 4.7) #69
Reference in New Issue
Block a user
No description provided.
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Аудит реализации RDMA/RDMACM в Vitastor
Дата проведения: 2026-05-16
Версия кода: ветка
master, последний коммитe136a1dfОбласть аудита:
src/client/msgr_rdma.{h,cpp}— основной RDMA-мессенджер (Verbs)src/client/msgr_rdmacm.cpp— RDMA-CM (Connection Manager) реализацияsrc/client/messenger.{h,cpp}— общий мессенджер, инициализацияsrc/client/msgr_receive.cpp,msgr_send.cpp,msgr_stop.cpp— путь данныхsrc/nfs/nfs_proxy_rdma.cpp— NFS-RDMA сервер (RFC 5666 / 8166)src/nfs/rdma_alloc.{h,cpp}— простой RDMA-aware аллокаторsrc/osd/osd.cpp,src/osd/osd_secondary.cpp,src/osd/osd_cluster.cpp— интеграция в OSDМетодология: Файлы прочитаны целиком (не grep-cherry-pick). Анализ выполнен пятью параллельными агентами по областям: жизненный цикл и cleanup, обработка ошибок, корректность протокола, безопасность/границы, конкурентность/конфигурация. Все находки независимо проверены чтением исходников.
Итог: Найдено >50 проблем. Реализация RDMA в Vitastor функционально работает в «хорошую погоду» (RoCEv2, x86_64, надёжная сеть, доверенные пиры), но имеет множественные критические дефекты, ломающие надёжность, безопасность и производительность в реальной эксплуатации. Особое внимание разработчика заслуживают: систематическое использование
exit(1)на любых временных ошибках RDMA; use-after-free в NFS RDMA-сервере; целочисленные переполнения с потенциально эксплуатируемыми последствиями в NFS-RPC-RDMA; систематически некорректные порядки уничтожения объектов RDMA.1. КРИТИЧЕСКИЕ ПРОБЛЕМЫ (Critical) — требуют немедленного исправления
1.1. Системное использование
exit(1)при любой ошибке RDMA — один битый клиент роняет весь процессФайлы и места:
src/client/msgr_rdma.cpp:566—try_send_rdma_wrпослеibv_post_sendsrc/client/msgr_rdma.cpp:603—try_send_rdmaприibv_reg_mrsrc/client/msgr_rdma.cpp:657—try_recv_rdma_wrпослеibv_post_recvsrc/client/msgr_rdma.cpp:670—init_recv_rdmaприibv_reg_mrsrc/client/msgr_rdma.cpp:697—handle_rdma_eventsприibv_req_notify_cqsrc/client/msgr_rdmacm.cpp:84,:99—handle_rdmacm_eventsпри ошибкахrdma_get_cm_event/rdma_ack_cm_eventsrc/nfs/nfs_proxy_rdma.cpp:474,:500,:759,:783,:1048— все аналогичные места в NFS-RDMAsrc/nfs/rdma_alloc.cpp:118—rdma_malloc_allocприibv_reg_mrОписание: В коде около 13 точек, где временная или ожидаемая ошибка вызовов libibverbs/librdmacm приводит к
exit(1)всего процесса. В частности:ibv_post_send/ibv_post_recvвозвращают ошибку, если QP перешла в состояниеIBV_QPS_ERR(а это происходит автоматически при любой ошибке транспортного уровня — RNR retry exceeded, transport timeout, length error и т.п.). Один сломанный клиент → весь OSD падает.ibv_reg_mrпадает при превышенииulimit -lmemlock (часто 64 МБ на дефолтных дистрибутивах) или при достиженииdev_attr.max_mrHCA. Достаточно открыть много соединений, чтобы убить OSD.ibv_req_notify_cqфактически возвращает ошибку, только если CQ невалидна — что возможно, например, при асинхронномIBV_EVENT_DEVICE_FATAL.Серьёзность: Critical — практически любой производственный сценарий приводит к каскадному падению OSD.
Сценарии срабатывания:
max_mr) при росте числа подключений — узел падает.Рекомендация:
exit(1)в RDMA/RDMACM-путях на:stop_client(client_id);init_recv_rdmaтак, чтобы возвращаемоеboolдействительно использовалось; сейчас функция всегда возвращаетtrue, но при ошибке убивает процесс.rdma_malloc_allocдолжен возвращатьNULL, а не падать.1.2. Use-after-free в NFS RDMA
handle_ioпри ошибке WCФайл:
src/nfs/nfs_proxy_rdma.cpp:792-815Описание: При получении WC со статусом ошибки вызывается
conn->client->stop(). Внутриnfs_client_t::stop()(см.nfs_proxy.cpp) после установкиstopped=trueприrefs <= 0происходит немедленноеdelete this.delete clientвызываетclient->destroy_rdma_conn()→delete rdma_conn→~nfs_rdma_conn_t()→ уничтожает самconn, по которому мы сейчас итерируем вhandle_io().Код после
stop()продолжает обращаться кconn->client->stopped,conn->recv_buffers,conn->outbox— это чтение из освобождённой памяти.Серьёзность: Critical — гарантированный краш или повреждение памяти при любом сетевом сбое NFS-RDMA-клиента.
Рекомендация: Использовать паттерн отложенного удаления, как сделано в OSD (
destroy_clientчерезset_immediate_ops/ refs-механику). Послеclient->stop()—breakиз цикла обработки WC и не трогатьconn.1.3. Ассерты в
~nfs_rdma_conn_t()приводят кabort()при разрыве клиента в момент работыФайл:
src/nfs/nfs_proxy_rdma.cpp:413-417Описание: Деструктор требует, чтобы все три очереди были пусты. Однако стандартный сценарий разрыва:
post_chunk_reads(rop, true)→chunk_inbox.push_back(rop)(или поместил вoutbox/chunk_read_postponed).RDMA_CM_EVENT_DISCONNECTED.handle_rdmacm_events→stop_clients.push_back(client)→client->stop()→delete rdma_conn→ assert срабатывает →abort().Серьёзность: Critical — любой разрыв клиента в момент незавершённой RDMA-операции крашит NFS-прокси целиком.
Рекомендация:
nfs_client_t::stopдоdelete rdma_conn) очищать все три очереди, корректно освобождаяrop->bufferчерезrdma_malloc_freeи самropчерезfree/xdr_free.delete rdma_conn.1.4.
rdma_malloc_destroyвызываетabort()при оставшихся живых фрагментахФайл:
src/nfs/rdma_alloc.cpp:73-77Описание: При уничтожении
nfs_rdma_dev_state_tзовётсяrdma_malloc_destroy. Если в аллокаторе остались живые фрагменты (а они там почти всегда есть при аварийном разрыве —rop->buffer, read-chunk-буферы), вызываетсяabort().Серьёзность: Critical — крах NFS-прокси при разрыве клиента в момент работы.
Связано с: 1.3, 1.5.
Рекомендация: Снять
abort()— заменить на лог + утечку (лучше), либо предварительно гарантированно очистить все фрагменты в вышестоящем коде. Самый чистый вариант: владение алокатором и QP должны быть в одних руках; перед уничтожением одного — чистить второе.1.5. Неверный порядок уничтожения в
~nfs_rdma_conn_t(): CQ удаляется ДО QPФайл:
src/nfs/nfs_proxy_rdma.cpp:418-438Описание:
delete conn_devвызываетibv_destroy_cq(cq). По стандарту libibverbs,ibv_destroy_cqвозвращаетEBUSY, пока на CQ ссылается живой QP. Здесь QP уничтожается уже после CQ — порядок неверен.Серьёзность: Critical — приводит к утечке CQ/PD/channel + UB в зависимости от драйвера. На Mellanox обычно «работает», но даёт «висящие» CQ.
Рекомендация: Поменять порядок:
Параллельно см. 1.3, 1.4.
1.6. Use-after-free в
handle_rdmacm_eventsпри множественных событиях для одногоcmidФайлы:
src/client/msgr_rdmacm.cpp:73-167src/nfs/nfs_proxy_rdma.cpp:245-310Описание: Чтобы обойти зависание
rdma_destroy_id, код копирует события вevents_copyи акает их сразу. Это решает первоначальную проблему, но создаёт новую: если за одинpoll-цикл придёт несколько событий для одногоcmid(напримерRDMA_CM_EVENT_DISCONNECTED+RDMA_CM_EVENT_TIMEWAIT_EXIT), первое событие приведёт кstop_client(...)→delete rdma_conn→rdma_destroy_id(cmid). Второе событие вevents_copyссылается на уже уничтоженныйcmid→ use-after-free приrdmacm_connections.find(ev->id)(как ключе в hashmapе), а также при последующихfprintf(... "...event for client %ju", cli_it->second->client_id)и т.д.Дополнительно: события, пришедшие в канал после дренажа в текущем цикле, но до реального вызова
rdma_destroy_id, всё ещё могут привести к зависаниюrdma_destroy_id— обход неполон.Серьёзность: Critical — реальный риск падения при flapping-соединениях.
Рекомендация:
events_copyсохранять не самrdma_cm_event, а минимальные нужные поля (event,id,status). Перед использованиемidпроверять валидность черезrdmacm_connections.find(...)иrdmacm_connecting.find(...).rdma_destroy_idещё раз дренировать канал на наличие событий для этогоid.rdma_destroy_idдо начала следующего вызоваhandle_rdmacm_eventsчерез специальную «корзину для уничтожения».1.7. Потенциальный stack buffer overflow в NFS RPC-RDMA через переполнение
wr_countФайл:
src/nfs/nfs_proxy_rdma.cpp:588-655Описание:
reply_chunk->target.target_len— этоu_int(unsigned int), пришедший с провода от клиента. Если, например,target_len = 0xFFFFFFFE:reply_chunk_wr_count = 0xFFFFFFFE(int, но интерпретируется в uint32);wr_count = 1 + 1 + 0xFFFFFFFE = 0(переполнение uint32);wr_count > ctx->max_send_wrпройдёт (0 < 1024);sges[0]/wrs[0](или 1-2) на стеке;target_len = 0xFFFFFFFEитераций пишет вsges[wr_pos++]далеко за пределами VLA → переполнение стека.Аналогично —
xdr_xdr_read_list/xdr_xdr_write_listдекодеры (src/nfs/proto/rpc_rdma_xdr.cpp:34-42, 54-62) рекурсивно, без верхнего лимита, спускаются по связному списку — при длинном входном списке вызывают stack overflow при декодировании (классический DoS через malformed packet).Серьёзность: Critical — потенциально удалённо эксплуатируемое условие. CVE-уровень.
Рекомендация:
target_lenсразу после XDR-декодирования (например, ≤ 64).__builtin_add_overflowдля расчётаwr_count.std::vectorили фиксированный размер вместо VLA.xdr_xdr_read_list/xdr_xdr_write_listитеративно с лимитом длины.1.8. Целочисленное переполнение в
rdma_malloc_allocведёт к heap-overflowФайл:
src/nfs/rdma_alloc.cpp:104-145, в связке сsrc/nfs/nfs_proxy_rdma.cpp:988-1054Описание: В
nfs_rdma_conn_t::post_chunk_readsread_chunk_sizeнакапливается из недоверенных значенийcur->entry.target.length(uint32_t). С учётом существующей проверкиread_chunk_count > max_rdma_reads(≤ 255), теоретически возможна сумма около255 * 4 ГБ ≈ 1 ТБ. На 64-битной системе это не превышаетSIZE_MAX, но при округлении вверх:если
size ≈ SIZE_MAX - 100, тоsize + rdma_alloc_size - 1переполняется, давая маленькое значение →malloc_or_die(маленький размер)+ibv_reg_mrна «настоящий размер» (но MR регистрируется на тот жеr->len = alloc_size_HUGE, что и вmalloc-параметре). Далееpos += cur->entry.target.lengthпишет байты RDMA-Read'ом за пределами выделенного буфера → heap overflow.Серьёзность: Critical (если достижимо через малых-чанков-много, либо одного большого) — потенциальный RCE через heap-corruption.
Рекомендация:
__builtin_add_overflow(size, rdma_alloc_size - 1, &intermediate)перед делением/умножением.read_chunk_size(напримерmax_send_wr * 256 КБ = 256 МБ).lengthидёт в malloc.1.9. Использование
lrand48()для PSN безsrand48()— предсказуемые PSNФайл:
src/client/msgr_rdma.cpp:443Описание:
srand48()нигде в OSD/RDMA-коде не вызывается, поэтомуlrand48()инициализирована константным сидом1(POSIX), и PSN-последовательность одинакова между всеми OSD и сохраняется между перезапусками.PSN (Packet Sequence Number) в RC-QP — единственная не-инвариантная по адресу защита от стейл/спуф-пакетов. На предсказуемых PSN атакующий с доступом к сети способен инжектить «правильные» пакеты в установленное RDMA-соединение.
Серьёзность: Critical (модель угроз "atomic untrusted network"). Для типичного дата-центра, где RDMA-сеть считается доверенной, проблема меньше — но и в этом случае стейл-пакеты предсказуемы и могут запутывать диагностику.
Рекомендация: Перед первым
lrand48()инициализироватьsrand48(...)отclock_gettime(CLOCK_MONOTONIC)+pidили, лучше, использоватьgetrandom()напрямую.1.10. Неверный порядок в
~msgr_rdma_buf_t():free(buf)доibv_dereg_mr(mr)Файл:
src/client/msgr_rdma.cpp:68-80Описание: По спецификации libibverbs,
ibv_dereg_mrдолжно вызываться до освобождения подложенной памяти. Послеfree(buf), hardware ещё может иметь outstanding DMA, использующих MR, который ссылается на освобождённую память → UB / повреждение данных / crash на некоторых драйверах. На практике, к моменту попадания сюда QP уже уничтожен, и DMA должно быть завершено, но порядок остаётся UB и небезопасным паттерном.Серьёзность: High–Critical (зависит от драйвера). На Mellanox обычно «работает», на iWARP может крашить.
Рекомендация: Поменять порядок: сначала
ibv_dereg_mr(mr), затемfree(buf). Это однострочное исправление.1.11. Use-after-free на
ibv_contextв~osd_messenger_t()для is_cm-контекстовФайлы:
src/client/messenger.cpp:264-299, в связке сsrc/client/msgr_rdma.cpp:56-66,src/client/msgr_rdmacm.cpp:169-203Описание: При уничтожении мессенджера:
stop_client(..., force_delete=true). Каждый клиент с RDMA-CM-соединением через~msgr_rdma_connection_t()вызываетrdma_destroy_qp(cmid)+rdma_destroy_id(cmid).rdma_destroy_idпотенциально освобождаетibv_context, на который ссылается общийmsgr_rdma_context_t(is_cm=true).rdma_contexts, который вызываетdelete rdma_context→~msgr_rdma_context_t()→ibv_destroy_cq,ibv_destroy_comp_channel,ibv_dealloc_pdповерх уже потенциально мёртвогоibv_context.В теории
ibv_contextрефкаунтится внутренне (CQ/PD держат ссылку), и пока их не уничтожить, контекст жив. Однакоcm_refsв коде помечен какFIXME now unused(см.msgr_rdma.h:41,msgr_rdmacm.cpp:482) — поле инкрементируется и декрементируется, но никогда не проверяется. То есть в~osd_messenger_t()нет проверки, что для CM-контекста все QP действительно уничтожены.Серьёзность: Critical (если рассинхрон порядка деструкции произойдёт).
Рекомендация:
cm_refs: уничтожатьis_cmmsgr_rdma_context_tтолько когдаcm_refs == 0И ни одинrdmacm_connecting_tего не держит.cm_refsтакжеrdmacm_connecting_t(+1вrdmacm_address_resolved,-1вrdmacm_on_connect_peer_error/rdmacm_established).1.12. Утечка
rdmacm_connecting_tпри разрушении мессенджераФайл:
src/client/messenger.cpp:264-299(деструктор)Описание:
~osd_messenger_t()останавливает клиентов (stop_client), уничтожает контексты, уничтожаетrdmacm_evch, но никогда не итерирует поrdmacm_connecting. Незавершённые соединения остаются — этоcmid+ активные таймеры. Когда таймер сработает позже, лямбда захватываетthis(мёртвыйosd_messenger_t) → segfault.Серьёзность: Critical (при тестах/рестартах).
Рекомендация: Перед
rdma_destroy_event_channel(rdmacm_evch)пройтись поrdmacm_connecting, очистить таймеры, уничтожитьcmid, удалить структуры.1.13. Race в
rdmacm_set_conn_timeout: callback может ссылаться на удалённыйcmidФайл:
src/client/msgr_rdmacm.cpp:301-317Описание: Лямбда захватывает raw-pointer
cmid. Если до срабатывания таймера соединение перешло в установленное состояние или было удалено черезrdmacm_on_connect_peer_error,rdmacm_connecting.at(cmid)броситout_of_range— необработанное исключение →std::terminate→abort.tfd->clear_timer(conn->timeout_id)пытается отменить таймер, но если callback уже встал в очередь обработки иclear_timerсрабатывает в окне между запуском и фактическим execution — поведение зависит от реализацииtimerfd_manager_t.Серьёзность: Critical (зависит от семантики
clear_timer).Рекомендация:
find, неat. Если не нашли —return.cmidиспользовать generation-counter или захватыватьweak_ptrнаconn.2. ВЫСОКОПРИОРИТЕТНЫЕ ПРОБЛЕМЫ (High)
2.1. Отсутствие обработки асинхронных событий устройств —
ibv_get_async_eventне используется НИГДЕФайлы: весь RDMA-код
Описание: В Vitastor нет обращений к
ibv_get_async_event/ibv_ack_async_event. Это значит, что игнорируются критические события устройства:IBV_EVENT_DEVICE_FATAL— фатальная ошибка HCAIBV_EVENT_PORT_ERR/IBV_EVENT_PORT_ACTIVE— состояние порта (вышел/восстановился)IBV_EVENT_LID_CHANGE,IBV_EVENT_GID_CHANGE— переконфигурация subnetIBV_EVENT_CLIENT_REREGISTER— переподпись MR требуетсяIBV_EVENT_QP_FATAL,IBV_EVENT_QP_REQ_ERR,IBV_EVENT_QP_ACCESS_ERR— фатальные ошибки QPIBV_EVENT_CQ_ERR,IBV_EVENT_SRQ_ERR— фатальные ошибки CQ/SRQСценарии:
Серьёзность: High — критично для production. Долгоживущие OSD не способны корректно реагировать на изменения состояния сети/устройства.
Рекомендация: Открыть
ibv_context->async_fd(он уже non-blocking), повесить epoll-handler, в нём вызыватьibv_get_async_event+ibv_ack_async_event, и обрабатывать события:2.2.
min_rnr_timer = 1(10 микросекунд) — лавины RNR-retriesФайл:
src/client/msgr_rdma.cpp:487Описание: По IBA spec §11.4.1.1.5,
min_rnr_timer— это прямой индекс таблицы, где1 = 0.01 мс (10 мкс),12 = 0.64 мс,16 = 2.56 мс,0 = 655.36 мс. Комментарий в коде «4.096us*2^(timeout+1)» относится к ack-timeout, не кmin_rnr_timer— кодом ошибочно объединили формулы.С
min_rnr_timer = 1(10 мкс), при возникновении RNR-NAK (Receiver Not Ready — приёмник не успел запостить RECV WR), отправитель ретраит через 10 мкс. Если приёмник под нагрузкой и не успевает за 10 мкс, лавина изrnr_retry = 7попыток истощится за миллисекунды, после чего QP уйдёт в ERR. ОтсюдаIBV_WC_RNR_RETRY_EXC_ERR→stop_client(или, до исправления 1.1 —exit(1)).Серьёзность: High — реальные сетевые/нагрузочные сценарии вызывают преждевременные разрывы.
Рекомендация: Установить
min_rnr_timer = 12(0.64 мс) минимально, лучше16(2.56 мс). Сделать настраиваемым черезrdma_min_rnr_timer.Дополнительно:
rnr_retry = 7— особое значение «бесконечные ретраи». Это значит, что при «висящем» приёмнике отправитель никогда не сдастся — IO залипает навсегда. Лучше использовать6(6 попыток) и полагаться на верхнеуровневые таймауты.2.3. RDMA
NOMSGне поддерживается — нарушение RFC 8166Файл:
src/nfs/nfs_proxy_rdma.cpp:895-916Описание: Принимается только
RDMA_MSG.RDMA_NOMSG(RFC 8166 §3.7, тип сообщения, в котором payload передаётся целиком в read/reply-чанках, а SEND содержит только заголовок) не реализован. Linux NFS-клиент используетRDMA_NOMSGдля крупных запросов с inline-частью большеinline_threshold.Серьёзность: High — нарушение совместимости с RFC. Часть сценариев NFS-RDMA не работает с Vitastor.
Рекомендация: Добавить обработку
RDMA_NOMSG: после получения SEND с типомNOMSGсделатьRDMA_READна reply chunk, и далее декодировать XDR.2.4. NFS RDMA: утечка
used_buffersпри остановке клиентаФайл:
src/nfs/nfs_proxy_rdma.cpp:481-484(post_recvс проверкойstopped),:418-423(деструктор поrecv_buffers)Описание: В
post_recvестьif (client->stopped) return;. В~nfs_rdma_conn_t()освобождаются буферы изrecv_buffers. Но между ними буфер живёт вstd::map<void*, nfs_rdma_buf_t> used_buffers(туда заносится вpost_initial_receives/reuse_buffer). Приstopбуфер удалён изrecv_buffers(см.handle_io:803), но вused_buffersостаётся. Деструкторused_buffersне освобождает.Серьёзность: High — утечка
ibv_mrиmalloc-памяти при каждом disconnect.Рекомендация: В деструкторе освобождать буферы из
used_buffers, не изrecv_buffers.2.5. Целочисленное переполнение в
send_out_size = max_msg * rdma_max_send(int)Файл:
src/client/msgr_rdma.h:79,src/client/msgr_rdma.cpp:599-601Описание:
max_msg—uint64_t(до 128 МБ),rdma_max_send—uint64_t. Произведение хранится вint. Приmax_msg = 128 МБ, rdma_max_send = 17произведение≈ 2.18 ГБпревышаетINT_MAX→ UB, отрицательное значение.Затем
malloc_or_die((size_t)send_out_size)после неявной конвертацииint → size_tдля отрицательного значения превращается в огромное беззнаковое число → илиmalloc_or_dieпадает сabort, или (на некоторых системах) выделяется огромный буфер с регистрациейibv_reg_mr.Серьёзность: High — конфигурируется администратором, реалистично при попытке настройки большой производительности.
Рекомендация: Изменить тип
send_out_size,send_out_pos,send_done_posнаuint64_tилиsize_t. Проверять__builtin_mul_overflow.2.6. Отсутствие верхних границ в
handle_op_hdr/handle_reply_hdr— DoS и потенциально heap-corruptionФайлы:
src/client/msgr_receive.cpp:264-346(handle_op_hdr),:401-413(handle_reply_hdr)Описание: Эти пути выполняются и для TCP, и для RDMA. От недоверенного или сломанного пира приходят произвольные значения
len/retval._or_dieфункции приlen > доступной памятивызываютabort()— DoS. Но особенно опасно:cur_op->req.show_conf.json_len + 1приjson_len = UINT64_MAX→0→malloc(0)(может вернуть валидный указатель!) → запись нуля в[UINT64_MAX]за пределами буфера.Для
SEC_LIST:sizeof(obj_ver_id) * retvalприretval ≈ 2^60переполняетsize_t(на 64-биткеsizeof(obj_ver_id) = 24,24 * 2^60 = overflow) → выделение маленького буфера, запись больших данных → heap corruption.Серьёзность: High (DoS) — Critical (heap-corruption через SEC_LIST overflow при злонамеренном пире).
Рекомендация: Установить разумные верхние лимиты (например,
< 1 ГБна запрос/конфиг). Использовать__builtin_add_overflow/__builtin_mul_overflow.2.7. Хардкодный
rnr_retry = 7— бесконечные ретраи (зависание IO)Файлы:
src/client/msgr_rdma.cpp:490(OSD RC-QP RTS)src/client/msgr_rdmacm.cpp:279, 450(rdma_accept / rdma_connect conn_params)src/nfs/nfs_proxy_rdma.cpp:364(NFS rdma_accept)Описание: Значение
7вrnr_retry(для QP) иrnr_retry_count(для conn_params в RDMA-CM) по спецификации IBA означает «infinite retries». При флапе приёмника отправитель будет ретраить вечно, IO залипают, верхнеуровневые таймауты NFS могут быть на минуты.Серьёзность: High — затрудняет отказоустойчивость и диагностику.
Рекомендация: Использовать
6(6 попыток ≈ десятки миллисекунд) и полагаться наosd_ping_timeout.2.8.
cm_refsпометка «FIXME now unused» — счётчик не работаетФайлы:
src/client/msgr_rdma.h:41,src/client/msgr_rdmacm.cpp:482,src/client/msgr_rdma.cpp:90Описание:
cm_refsинкрементируется вrdmacm_established, декрементируется в~msgr_rdma_connection_t(). Но никогда не проверяется ни в одном месте. Это означает:cm_refs > 0, будет use-after-free (см. 1.11).rdmacm_connecting_t(rdmacm_create_qpуже создал QP в контексте) не учитываются вовсе — комментарий в коде сам это признаёт.Серьёзность: High.
Рекомендация: Либо довести
cm_refsдо полноценного refcount'а, либо удалить как мёртвый код, явно документировав, что время жизни CM-контекста привязано к мессенджеру (что неверно, т.к. сейчас не привязано).2.9. Хардкодный
rdma_max_send = 8— недостаточная глубина SEND для современных каналовФайл:
src/client/messenger.cpp:323-325Описание: При линковой скорости 100/200 Гбит/с и
max_msg = 129 КБмаксимум в полёте= 8 × 129 КБ ≈ 1 МБ. При RTT ≈ 5 мкс это даёт лимит около200 МБ/сна пир — в 100 раз меньше возможностей канала.Серьёзность: High (производительность). Полностью лишает кластер преимуществ RDMA на современном железе.
Рекомендация: Поднять дефолт до
32-64. Аналогичноrdma_max_recvдо32-64.2.10. Большой объём pinned-памяти на пир (recv_buf ≈ 2 МБ)
Файл:
src/client/msgr_rdma.cpp:666-668Описание: На каждый пир выделяется
max_msg × max_recv = 129 КБ × 16 ≈ 2 МБpinned memory + такая же для send (max_msg × rdma_max_send = 129 КБ × 8 ≈ 1 МБ). При кластере с 500 OSD-пирами это~1.5 ГБpinned-памяти на узел. Возможен OOM/memlock-bust.OSD вызывает
MCL_FUTURE | MCL_CURRENT(см.osd.cpp:34-46), поэтому это давит наulimit -l.Серьёзность: High на больших кластерах. На малых — OK.
Рекомендация:
rdma_alloc.cppдля NFS).ulimit -lв зависимости от размера кластера.2.11. RDMA-CM listener: bound_port разделяется между bind_address без согласования
Файл:
src/osd/osd.cpp:408-424Описание: После первого вызова
rdmacm_portобновляется реально выбранным портом и передаётся как in/out для второго bind. Если первый порт был auto-pick'ом (0), второй bind использует выбранное значение — но на другом интерфейсе порт может быть занят, и второй bind упадёт. Тогда iWARP-CM перестанет работать на одном из адресов.Серьёзность: Medium-High.
Рекомендация: Явно использовать первое выбранное значение для всех или ошибаться при невозможности.
2.12. Двойное соответствие
RDMA_CM_EVENT_REJECTEDвhandle_rdmacm_events— дублирование и потенциальная путаницаФайл:
src/client/msgr_rdmacm.cpp:109-152Описание: Второе сравнение
REJECTEDнедостижимо. Это явный знак, что код проходил несколько правок без чистки. Хотя сегодня логика корректна, в будущем правки могут добавить специфическое поведение во вторую ветку, не зная про первую.Серьёзность: Medium (как индикатор беспорядка) — High (как уязвимость к будущим правкам).
Рекомендация: Удалить лишний
REJECTEDиз второй ветки, добавить комментарий, что REJECTED обрабатывается выше.2.13.
init_recv_rdmaзапускается ПОСЛЕrdma_acceptиrdmacm_connections[cmid] = clФайлы:
src/client/msgr_rdmacm.cpp:266-298, 507-516Описание: Сначала
rdma_accept(ev->id, &conn_params), потом установка обработчика, потом, в обработчикеRDMA_CM_EVENT_ESTABLISHED—init_recv_rdma(cl). Между событиямиaccept(сервер OK'ает соединение) иESTABLISHED(соединение фактически установлено) клиент может уже начать слать данные. На стороне сервера ни одного RECV WR не запостено → RNR-NAK. RNR-retry компенсирует, но это лишняя задержка и нагрузка на retry-счётчики.Серьёзность: Medium (производительность).
Рекомендация: Запостить хотя бы один RECV WR до
rdma_accept.3. ПРОБЛЕМЫ СРЕДНЕГО ПРИОРИТЕТА (Medium)
3.1. Endianness magic для NFS-RDMA-CM private_data ломает big-endian-хосты
Файл:
src/nfs/nfs_proxy_rdma.cpp:23-32, 342-348, 385-396На LE: байты на проводе
f6 ab 0e 18→uint32_t = 0x180eabf6, совпадает. На BE:f6 ab 0e 18→uint32_t = 0xf6ab0e18, не совпадает. На BE-хосте магия не работает, дефолтные параметры не согласуются (в т.ч.remote_invalidate=true, который может быть некорректным).Рекомендация: Использовать
be32toh()/htobe32()явно, имя без_LE.3.2. NFS NFS-RDMA:
remote_invalidateпо умолчаниюtrue— несовместимость с не-Linux клиентамиФайл:
src/nfs/nfs_proxy_rdma.cpp:137-138При недополучении private_data (или старом клиенте), дефолтный
remote_invalidate = trueприводит к отправкеIBV_WR_SEND_WITH_INV. Если клиент не поддерживает remote invalidation, его rkey'и не освобождаются автоматически → утечка регистраций / зависание.Рекомендация: Дефолт
false, включать только при явной успешной негоциации.3.3.
max_send_sge = 1в NFS заставляет копировать reply черезmemcpyФайл:
src/nfs/nfs_proxy_rdma.cpp:327-329, 670-680QP создаётся с
max_send_sge = 1. Все исходящие данные собираются в один буферrop->bufferчерезmemcpy. Это лишний цикл копирования для каждого READ-reply размером до 256 КБ. Влияние на throughput значительно.Рекомендация: Использовать
max_send_sge ≈ 16-32, собирать SEND из iovec'ов напрямую (как вrdma_allocдля готовых iovec — без копирования). Это уменьшит memory bandwidth в 2 раза для крупных READ.3.4.
num_sge = 1для OSD RDMA-SEND — лишнийmemcpyв send_outФайл:
src/client/msgr_rdma.cpp:629-634В OSD-протоколе каждое RDMA-SEND имеет один SGE. Данные из
cl->send_list(iovec) сначала копируются в линейныйsend_out-буфер, потом отправляются. Это удваивает memory bandwidth на каждое сообщение.Рекомендация: Использовать pre-registered memory pool (либо общий, либо per-context), отправлять SEND напрямую из iovec'ов в виде SGE list.
3.5.
IBV_WR_SEND_WITH_INVбез проверки device_cap — упадёт на HCA без MR_EXTENSIONSФайл:
src/nfs/nfs_proxy_rdma.cpp:741-744Используется
IBV_WR_SEND_WITH_INVприremote_invalidate=true, но никакой проверкиdev_attr.device_cap_flags & IBV_DEVICE_MEM_MGT_EXTENSIONSнет. На устройствах без этой возможностиibv_post_sendупадёт →exit(1)(см. 1.1).Рекомендация: Проверять capability, при отсутствии fallback на обычный SEND и отключение
remote_invalidate.3.6.
ibv_resize_cq— нет верхнего лимита и потенциально небезопасный fallbackФайл:
src/client/msgr_rdma.cpp:383-403Алгоритм удваивает
max_cqeбез верхнего лимита (dev_attr.max_cqe). На некоторых драйверахibv_resize_cqможет вернуть успех, фактически не изменив размер — потенциальное переполнение CQ (fatal).Рекомендация: Кэппить по
attrx.orig_attr.max_cqe. После resize вызыватьibv_query_cqдля верификации.3.7.
max_send_wr/max_recv_wrне проверяются противdev_attr.max_qp_wrФайлы:
msgr_rdma.cpp:425-428,msgr_rdmacm.cpp:249-252,nfs_proxy_rdma.cpp:325-328Если admin задаст экстремальные значения,
ibv_create_qp/rdma_create_qpупадёт с ENOMEM/EINVAL — но без диагностической подсказки.Рекомендация: Clamp по
dev_attr.max_qp_wr, логировать предупреждение.3.8.
IBV_SEND_INLINEне используется — лишняя латентность на small messagesФайлы:
msgr_rdma.cpp:552-569,nfs_proxy_rdma.cpp(всеpost_send)max_inline_dataне устанавливается (остаётся 0). Для маленьких сообщений (заголовки OSD ≈ 128 байт, ping-операции) inline-SEND экономит DMA-операцию (~0.5-1 мкс).Рекомендация: Установить
max_inline_data = 256или512, использоватьIBV_SEND_INLINEflag для маленьких payload.3.9. iWARP несовместимости
Файлы:
msgr_rdma.cpp:601(access=0при reg_mr),nfs_proxy_rdma.cpp:692-707(RDMA_WRITE длины 0)ibv_reg_mr(... access=0)для send-буфера может вернуть EINVAL — iWARP требует хотя быIBV_ACCESS_LOCAL_WRITE.IBV_WR_RDMA_WRITEнулевой длины (см. RFC 5041 §5.3). Если reply chunk обрезается до 0 — сбой.Рекомендация: Добавить
IBV_ACCESS_LOCAL_WRITEдля send_out MR. Приlength == 0пропускать RDMA_WRITE WR.3.10. Хардкодные
responder_resources=1, initiator_depth=1в RDMA-CM accept/connectФайлы:
src/client/msgr_rdmacm.cpp:275-280, 446-451Жёсткий хардкод без проверки
dev_attr.max_qp_rd_atom/max_qp_init_rd_atom. На SoftRoCE/симуляторах, гдеmax_qp_rd_atom = 0,rdma_accept/rdma_connectупадёт с EINVAL. Также блокирует производительность RDMA_READ для NFS на ConnectX-4 (16) и новых HCA.3.11. Хардкодный backlog
rdma_listen(..., 128)Файлы:
src/client/msgr_rdmacm.cpp:46,src/nfs/nfs_proxy_rdma.cpp:208На больших кластерах при массовом переподключении (например, после рестарта OSD) backlog может переполниться → отказ части соединений.
Рекомендация: Сделать настраиваемым, дефолт
1024.3.12. Утечка
xdr_freeвfree_rdma_rpc_opФайл:
src/nfs/nfs_proxy_rdma.cpp:852-869При создании
rpc_op_tчерезmalloc_or_die,in_rdma_msg = rmsgкопирует pointersrdma_reads,rdma_writes,rdma_replyна XDR-allocated linked-list узлы. Вfree_rdma_rpc_opнетxdr_free((xdrproc_t)xdr_rdma_msg, &rop->in_rdma_msg)→ утечка XDR-аллокированных узлов на каждый RPC.3.13.
wr_id = 4для chunk-write RDMA_WRITE не обрабатывается вhandle_ioФайл:
src/nfs/nfs_proxy_rdma.cpp:798-848При ошибке QP unsignaled RDMA_WRITE'ы получают FLUSH-completion. Они приходят с
wr_id = 4, для которого нет ветки, и логируется «BUG: unknown wr_id = 4». Спам в логах при каждом сбое.3.14. Бесконечный цикл
goto chunk_errorпри маленькихmax_send_sizeот пираФайл:
src/nfs/nfs_proxy_rdma.cpp:594-655При злонамеренном клиенте, передающем
max_recv_size = 0(т.е. 1 КБ) в private_data, заголовок ERR_CHUNK может превысить 1 КБ, и проверкаmsg_size+hdr_size > max_send_size && !reply_chunkснова отправит вchunk_error→ бесконечная рекурсия goto, OOM/CPU 100%.Рекомендация: В
chunk_errorпропускать дополнительные проверки. Гарантировать минимальный header-fit. Не повторятьchunk_errorрекурсивно.3.15. ADDR_ERROR/ROUTE_ERROR — диагностика статуса неверна при положительном
ev->statusФайл:
src/client/msgr_rdmacm.cpp:407-409, 440-442Если ядро возвращает положительное значение статуса (что бывает на некоторых драйверах), сообщение всегда «unknown error». Тратится время на диагностику.
Рекомендация:
strerror(abs(ev->status)).3.16. Кредитный flow-control в NFS: рассинхрон
cur_creditс реальным числом RECV WRФайл:
src/nfs/nfs_proxy_rdma.cpp:139, 379, 456-462, 509, 804-815, 867cur_creditдекрементируется при получении SEND-completion отрицательного направления (recv), а инкрементируется приfree_rdma_rpc_op. Однако фактический счётчик RECV WR в очереди QP не отслеживается; теоретически возможна ситуация, когдаcur_credit > 0, а в QP нет RECV WR → RNR-NAK от клиента.Рекомендация: Перепостить RECV сразу при
handle_recv, до начала обработки.3.17. Конфигурация
rdma_max_send/recv = 0обрабатывается дефолтами, ноrdma_max_msg < OSD_PACKET_SIZEне проверяетсяФайл:
src/client/messenger.cpp:320-331Минимальные/максимальные границы не валидируются.
rdma_max_send = 1даст депрессивную глубину;rdma_max_msg < 128 байтприведёт к assertion-фейлам в путях обработки заголовков.3.18. NFS-RDMA: doesn't support multi-segment write chunks
Файл:
src/nfs/nfs_proxy_rdma.cpp:962-976Только 1 chunk с 1 segment в write chunk. Клиенты (не-Linux), пересылающие multi-segment, получают ERR_CHUNK.
3.19. NFS-RDMA: модифицирует входящее сообщение
rop->in_rdma_msgпри partial chunk writeФайл:
src/nfs/nfs_proxy_rdma.cpp:641-646rdma_writes->entry.target.target_val[0].length = chunk_iov->iov_lenмодифицирует структуру, которая должна быть immutable. Не баг в текущем коде, но потенциальная мина при будущих правках.3.20.
out_of_rangeexception вcl->send_list.at(send_pos)не обрабатываетсяФайл:
src/client/msgr_rdma.cpp:756-766Использование
.atпри рассинхроне приводит к необработанному exception →std::terminate.Рекомендация: Или использовать
[](UB при OOB, но не throw) + assert, или ловить exception иstop_client.4. ПРОБЛЕМЫ НИЗКОГО ПРИОРИТЕТА (Low) и стилистические
4.1.
is_globalопределяется по факту наличия GID, а не по link_layerФайл:
src/client/msgr_rdma.cpp:481Логика «GID не пустой → is_global=1» работает для RoCE (GID всегда не пустой), но для нативной IB с локальным LID-роутингом теоретически неверна. Лучше:
is_global = (link_layer == IBV_LINK_LAYER_ETHERNET || subnet_prefix != 0).4.2.
hop_limit = 64хардкод + комментарий «FIXME»Файл:
src/client/msgr_rdma.cpp:476Не настраивается. Для multi-DC RDMA может не хватить. Дефолт OK.
4.3.
sl = 0(service level) — отсутствует QoSФайл:
src/client/msgr_rdma.cpp:479Без поддержки конфигурации SL нельзя интегрировать с PFC/DCQCN/lossless-классами трафика на современных RoCE-фабриках.
4.4.
pkey_index = 0— фиксированноФайл:
src/client/msgr_rdma.cpp:448Для мультипартиционных IB-кластеров может не подойти.
4.5.
timeout = 14(~134 мс) — избыточно для локального DCСлишком долгое ожидание detection unreachable peer (~32 секунды до полного отказа QP).
4.6. Парсинг адреса в
msgr_rdma_address_t::from_string:%xбез шириныФайл:
src/client/msgr_rdma.cpp:48-49sscanfбез явной ширины дляqpn,psn— недостаточная валидация. Не security-проблема (значения позже проверяются вibv_modify_qp), но стилистически плохо.4.7. GID-сериализация без разделителя между половинами
Файл:
src/client/msgr_rdma.cpp:39-42Читабельность.
4.8.
rdma_max_msg = 129*1024— недокументированное магическое числоФайл:
src/client/messenger.cpp:331Без комментария «128 КБ payload + 1 КБ overhead».
4.9.
RDMA_MSGP(deprecated в RFC 8166) отвергается без явной диагностики4.10.
chunk_iov->iov_len == 0для READ переписывает write chunk length=0 → проблема на iWARP4.11.
ibv_get_cq_eventнеэффективен:read()syscall на каждое событиеПоток-event-channel + epoll → 1 syscall per notify. Альтернатива: hybrid polling (busy-spin + fallback на channel).
4.12.
fcntlбез проверки результатаФайлы: многие места
fcntl(fd, F_SETFL, ... | O_NONBLOCK)Возвращаемое значение не проверяется. В крайне редких случаях fcntl может вернуть
-1.4.13. RDMA-CM listener backlog не настраивается
4.14. NFS
next_recv_buf— мёртвая переменная (всегда 0)Файл:
src/nfs/nfs_proxy_rdma.cpp:144, 802-803Никогда не инкрементируется, всегда указывает на нулевой элемент
recv_buffers. Читателю мисслидинг.4.15. Отсутствие документации требований к сети
В коде нет документации, что RDMA-сеть должна быть доверенной (нет аутентификации/шифрования RDMA).
4.16. WITH_RDMACM без WITH_RDMA — нерабочая компиляция
Файлы:
messenger.h:47, 170, 257, 261, 309,msgr_rdma.h:5-8WITH_RDMACM требует WITH_RDMA (типы из
msgr_rdma.h), но#error-защиты нет.4.17.
send_out_size = 0в конце деструктора~msgr_rdma_connection_t()Файл:
src/client/msgr_rdma.cpp:99Мёртвый код — присвоение полю в уже удаляемом объекте.
4.18.
accept_connectionsдублирует строкуcl->peer_addr = addr;Файл:
src/client/messenger.cpp:785-786(это TCP, но рядом с RDMA логикой)4.19.
init_recv_rdmaобъявлен какbool, но всегда возвращаетtrue(или умирает)Файл:
src/client/msgr_rdma.cpp:662-680Возвращаемое значение бесполезно. См. 1.1.
4.20.
nfs_rdma_buf_t::len,mrполя без копи-семантики/move-семантикиСейчас не вызывает проблем, но при копировании
nfs_rdma_buf_tMR будет двойно зарегистрирован.5. РЕКОМЕНДАЦИИ В ПРИОРИТЕТНОМ ПОРЯДКЕ
Приоритет 1 — немедленно (Critical)
exit(1)в RDMA-путях — заменить наstop_client/stop_session. Без этого OSD/NFS-прокси нельзя считать production-ready. (§1.1)~nfs_rdma_conn_t()— сначала QP, потом CQ. (§1.5)handle_ioпослеclient->stop()— отложенное удаление. (§1.2)~nfs_rdma_conn_t()/rdma_malloc_destroy— убратьassert/abort(). (§1.3, §1.4)target_len,read_chunk_size,wr_count. (§1.7, §1.8)dereg_mr↔freeв~msgr_rdma_buf_t(). (§1.10)handle_rdmacm_events: проверять валидностьcmidперед использованием. (§1.6)lrand48()для PSN или использоватьgetrandom(). (§1.9)rdmacm_set_conn_timeoutcallback**: использоватьfind, неat`. (§1.13)rdmacm_connecting_tпри разрушении мессенджера. (§1.12)cm_refsили его удаление (либо защита на уровне порядка деструкции). (§1.11, §2.8)Приоритет 2 — следующая итерация (High)
ibv_get_async_event. (§2.1)min_rnr_timerдо 12-16. (§2.2)NOMSGдля совместимости с RFC 8166. (§2.3)used_buffersв NFS. (§2.4)send_out_sizeи проверки__builtin_mul_overflow. (§2.5)handle_op_hdr/handle_reply_hdr. (§2.6)rnr_retry = 6вместо бесконечного7. (§2.7)rdma_max_senddefault = 32-64, документировать формулу. (§2.9)recv_bufчерез общий MR-pool, header-first. (§2.10)handle_rdmacm_events. (§2.12)rdma_accept. (§2.13)Приоритет 3 — рефакторинг (Medium)
remote_invalidate=falseпо умолчанию. (§3.1, §3.2)max_send_sge > 1+ scatter-gather из iovec безmemcpy. (§3.3, §3.4)IBV_WR_SEND_WITH_INV. (§3.5)ibv_resize_cqс верхним лимитом. (§3.6)max_send_wr/max_recv_wrпо device caps. (§3.7)IBV_SEND_INLINEдля маленьких сообщений. (§3.8)LOCAL_WRITEдля send MR, skip zero-byte RDMA_WRITE. (§3.9)responder_resources/initiator_depth = 1. (§3.10)rdma_listenbacklog. (§3.11)xdr_freeвfree_rdma_rpc_op. (§3.12)wr_id = 4. (§3.13)chunk_error. (§3.14)Приоритет 4 — улучшения (Low)
36-50. Стилистика, мёртвый код, документация (см. §4).
6. ВЫВОД
Реализация RDMA в Vitastor содержит существенные дефекты надёжности и безопасности, которые делают её непригодной для production-эксплуатации в нынешнем виде на узлах, обслуживающих недоверенный или большой объём трафика. В то же время архитектура (отдельный RDMA-мессенджер с фолбэком на TCP, поддержка RDMA-CM, отдельный путь для NFS-RDMA) выглядит разумной.
Главные системные проблемы:
exit(1)на временных RDMA-сбоях — самый яркий дефект, влияющий на все ситуации.cm_refsи связанные потенциальные use-after-free на ibv_context.rdma_max_send=8,max_send_sge=1(NFS), отсутствия inline-SEND, лишнихmemcpy-копий.После закрытия Critical-проблем (§1) можно говорить о приемлемой работоспособности; после High (§2) — о приемлемой производительности и наблюдаемости. Medium/Low — задел для дальнейшего созревания.
RDMA implementation review using AI (Claude)to RDMA implementation review using AI (Claude 4.7)Переделаю с помощью Opus 4.8 + ultracode