Skip to content

mTCP Chaining and Zero-Copy RX/TX Pipeline#7

Open
ameer-hamza0046 wants to merge 4 commits into
devfrom
mtcp
Open

mTCP Chaining and Zero-Copy RX/TX Pipeline#7
ameer-hamza0046 wants to merge 4 commits into
devfrom
mtcp

Conversation

@ameer-hamza0046

Copy link
Copy Markdown
Collaborator

Description:

Overview
This PR introduces the core Zero-Copy packet semantics and L7 application chaining capabilities into the mTCP network stack over FLASH.

The goal of this architecture is to minimize memory copies between the NIC, the mTCP stack, and the L7 application.

Key Features Implemented:

  1. Flash ID Trailer (MTCP_FLASH_ID_TRAILER)
    • Modified the packet flow to append and parse 2-byte flash IDs [src_flash_id][dst_flash_id] at the end of payloads to enable stateful routing between chained NFs.
  2. Zero-Copy RX Data Path (MTCP_RX_ZERO_COPY)
    • Implemented mtcp_recv_zc, mtcp_read_zc, and batching variants.
    • Built a lockless SPSC ring (zc_rx_free_ring) to safely reclaim UMEM buffers. Note that this design may have scalability issues that can be optimized in the future.
  3. Zero-Copy TX Data Path (MTCP_TX_ZERO_COPY) (WIP)
    • Implemented mtcp_get_zc_wptr, mtcp_write_zc, and batching variants.
    • Built zc_tx_free_ring to pass available AF_XDP TX descriptors up to the application thread.

Known Issues & Caveats (Work In Progress)
While the RX data path is stable in terms of correctness, there are some known bugs in the TX path and chaining.

  • Throughput Collapse: Under heavy load, the TX zero-copy throughput slowly degrades and collapses over time.
  • AF_XDP Backend Deadlock Risk: Chaining using MTCP_FLASH_ID_TRAILER requires the lower-level API functions (flash__allocmsg, flash__sendmsg, __reserve_tx) to be non-blocking. If these functions use while loops to block indefinitely waiting for resources, they freeze the single-threaded mTCP event loop. This blocks RX polling, causing the stack to deadlock.
  • Current Workaround: During my experiments, I patched this by replacing the infinite blocking loops with a random number of retries to approximate non-blocking behavior. A pure fail-fast architecture is needed for production.

- added flash_id in tcp stream metadata
- added mtcp-utils: mtcp-enter and mtcp-exit
- now, mtcp_create_context also takes in nic queue id as an argument
- now mtcp_connect also takes in dst_flash_id as an argument
- added pps stats in flash_module.c
- modified afxdp_get_wptr to batch requests to flash__allocmsg
- added api mtcp_recv_zc, mtcp_read_zc, mtcp_zc_free, mtcp_read_zc_batch, mtcp_zc_free_batch
- SPSC ring to reclaim buffers, inefficient but correct, can optimize it in future
- added zero copy APIs for TCP Ring Buffer
- added api mtcp_get_zc_wptr, mtcp_write_zc, mtcp_get_zc_wptr_batch, mtcp_write_zc_batch
- updated tcp_send_buffer to support zero copy tx.
- NOTE: This is a work in progress. The TX data path currently has a critical bug causing throughput to slowly collapse over time.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR integrates FLASH-specific chaining metadata and introduces zero-copy RX/TX plumbing into the mTCP-over-FLASH stack to reduce NIC↔stack↔application copies and enable L7/NF chaining.

Changes:

  • Adds FLASH ID trailer support (MTCP_FLASH_ID_TRAILER) to carry [src_flash_id][dst_flash_id] and thread it through stream state and TX.
  • Implements RX zero-copy receive semantics (MTCP_RX_ZERO_COPY) via new ring-buffer fragment paths and an SPSC reclaim ring.
  • Introduces a TX zero-copy path (MTCP_TX_ZERO_COPY, WIP) including ZC send lists, ZC write-pointer APIs, and AF_XDP backend support for enqueueing ZC buffers.

Reviewed changes

Copilot reviewed 30 out of 30 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
lib/include/flash_defines.h Adds zc_tracker in config for TX ZC buffer ownership tracking.
lib/flash/nf/flash_txrx.c Uses zc_tracker to decide whether TX completion should return buffers to the pool.
lib/flash/nf/flash_nf.c Allocates zc_tracker during NF configuration for TX ZC.
lib/flash/mtcp/timer.c Hooks retransmission scheduling into the ZC send list when ZC buffers are pending.
lib/flash/mtcp/tcp_stream.c Propagates flash ID into streams; ensures ZC send-list cleanup; updates RBFree call signature.
lib/flash/mtcp/tcp_send_buffer.c Adds ZC send-buffer queueing and ACK-based release (SBPut_zc/SBRemove_zc/SBClear_zc).
lib/flash/mtcp/tcp_ring_buffer.c Adds RX ZC fragment path + buffer reclaim into RBFree and new ZC APIs.
lib/flash/mtcp/tcp_rb_frag_queue.c Adds a ZC fragment free-queue implementation for RX ZC fragments.
lib/flash/mtcp/tcp_out.c Adds TX ZC flush path and ZC send list management; adjusts payload copy behavior.
lib/flash/mtcp/tcp_in.c Threads RX ZC behavior through payload processing and alters packet-freeing semantics.
lib/flash/mtcp/ip_out.c Updates flash context dst id on output; adjusts header placement for TX ZC.
lib/flash/mtcp/ip_in.c Adjusts ICMP handling return semantics for RX ZC freeing.
lib/flash/mtcp/include/tcp_stream.h Adds ZC send-list bookkeeping and per-stream dst flash id.
lib/flash/mtcp/include/tcp_send_buffer.h Adds ZC send-buffer structures/constants and ZC API declarations.
lib/flash/mtcp/include/tcp_ring_buffer.h Extends ring buffer with ZC fragment lists and ZC APIs; changes RBFree signature.
lib/flash/mtcp/include/tcp_rb_frag_queue.h Exposes the ZC fragment queue types/APIs.
lib/flash/mtcp/include/tcp_out.h Exposes ZC send-list APIs to other mTCP modules.
lib/flash/mtcp/include/mtcp.h Adds flash context (queues, trailer ids, ZC rings) and ZC send queue state.
lib/flash/mtcp/include/mtcp_api.h Extends public API for NIC queue selection and connect-time dst flash id; adds ZC read/write APIs.
lib/flash/mtcp/include/io_module.h Extends I/O module interface for TX ZC (get_zc_wptr, enqueue_zc_buf).
lib/flash/mtcp/flash_module.c Adds AF_XDP backend batching, trailer parsing/appending, ZC TX enqueueing, and descriptor throttling/stats.
lib/flash/mtcp/eth_out.c Enqueues ZC buffers for TX when header headroom is provided.
lib/flash/mtcp/eth_in.c Changes packet-free return semantics to support RX ZC freeing.
lib/flash/mtcp/core.c Adds RX ZC reclaim + TX ZC replenish rings; integrates ZC sendq/send-list handling; changes RX free logic.
lib/flash/mtcp/api.c Adds RX ZC recv/free APIs and TX ZC get-wptr/write APIs (single + batch).
examples/mtcp/epserver.c Updates example to new mtcp_create_context(core, queue) signature.
examples/mtcp-utils/mtcp-exit.c Adds a new utility NF that strips the trailer before forwarding out.
examples/mtcp-utils/mtcp-enter.c Adds a new utility NF that appends the trailer before forwarding in.
examples/mtcp-utils/meson.build Adds build targets for mtcp-enter and mtcp-exit.
examples/meson.build Registers the new mtcp-utils examples directory.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +247 to +251
/* if no space, return -2 */
if (buf->available_zc_slots <= 0) {
/* should not happen */
return -2;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will this be a problem?

Comment on lines +86 to +90
#ifdef MTCP_TX_ZERO_COPY
size_t SBRemove_zc(mtcp_manager_t mtcp, struct tcp_send_buffer *buf, size_t acked_len);
size_t SBPut_zc(sb_manager_t sbm, struct tcp_send_buffer *buf, const void *data, size_t len, uint64_t flash_addr);
void SBClear_zc(mtcp_manager_t mtcp, struct tcp_send_buffer *buf);
#endif /* MTCP_TX_ZERO_COPY */

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will this be a problem?

Comment thread lib/flash/mtcp/tcp_out.c
Comment on lines +252 to +271
#ifdef MTCP_TX_ZERO_COPY
// h-> need to check mss length for pkt? for now i am experimenting with pkt sizes upto 1024 B
if (mtcp->ctx->flash_ctx.tx_zc == 0 && payloadlen + optlen > cur_stream->sndvar->mss) {
TRACE_ERROR("Payload size exceeds MSS\n");
return ERROR;
}
if (mtcp->ctx->flash_ctx.tx_zc && payloadlen > 0) {
// data for IP and TCP header will be written in place
// h-> the header len is hardcoded for (TCP_OPT_TIMESTAMP_ENABLED = 1, TCP_OPT_SACK_ENABLED = 1,), where SACK flag is never set
assert(optlen == (TCP_OPT_TIMESTAMP_LEN + 2));
mtcp->ctx->flash_ctx.data = payload - (TCP_HEADER_LEN + optlen);
} else {
mtcp->ctx->flash_ctx.data = NULL;
}
#else
if (payloadlen + optlen > cur_stream->sndvar->mss) {
TRACE_ERROR("Payload size exceeds MSS\n");
return ERROR;
}
#endif /* MTCP_TX_ZERO_COPY */

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be checked.

int mtcp_core_affinitize(int cpu);

mctx_t mtcp_create_context(int cpu);
mctx_t mtcp_create_context(int cpu, int flash_nic_queue);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this definitely needs to be fixed. I would say make a wrapper function so that backward compatibility is not broken.

int mtcp_init_rss(mctx_t mctx, in_addr_t saddr_base, int num_addr, in_addr_t daddr, in_addr_t dport);

int mtcp_connect(mctx_t mctx, int sockid, const struct sockaddr *addr, socklen_t addrlen);
int mtcp_connect(mctx_t mctx, int sockid, const struct sockaddr *addr, socklen_t addrlen, uint8_t dst_flash_id);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again here compatibility issues may be there. wrapper will fix it.

Comment thread lib/flash/mtcp/flash_module.c
Comment thread lib/flash/mtcp/flash_module.c
Comment thread lib/flash/nf/flash_nf.c
Comment on lines +605 to +609
cfg->zc_tracker = (uint8_t *)calloc(cfg->umem->size / cfg->umem->frame_size, sizeof(uint8_t));
if (!cfg->zc_tracker) {
log_error("ERROR: Memory allocation failed for zero-copy tracker");
goto out_error;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ameer-hamza0046 I think a memory leak is here. I think we should patch this up.

Comment thread examples/mtcp-utils/mtcp-enter.c
Comment thread examples/mtcp-utils/mtcp-exit.c

@rickydebojeet rickydebojeet left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ameer-hamza0046 I have tried to go through the code. I think some of the changes may break normal execution of tcp applications too.

  • Can you please go through the review and do the required changes?
  • I think with ifdef we can conditionally toggle the zero-copy without any compatibility issues.
  • Also can you also mention if there are any changes required during compilation?

Comment thread lib/flash/nf/flash_nf.c
Comment on lines +605 to +609
cfg->zc_tracker = (uint8_t *)calloc(cfg->umem->size / cfg->umem->frame_size, sizeof(uint8_t));
if (!cfg->zc_tracker) {
log_error("ERROR: Memory allocation failed for zero-copy tracker");
goto out_error;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ameer-hamza0046 I think a memory leak is here. I think we should patch this up.

"-D <num>\tDestination NF ID (default: 0)",
NULL
};
// clang-format on

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ameer-hamza0046 can we have some description about how the flash trailer should be setup and how will it work?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the use of this nf?

int mtcp_core_affinitize(int cpu);

mctx_t mtcp_create_context(int cpu);
mctx_t mtcp_create_context(int cpu, int flash_nic_queue);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this definitely needs to be fixed. I would say make a wrapper function so that backward compatibility is not broken.

int mtcp_init_rss(mctx_t mctx, in_addr_t saddr_base, int num_addr, in_addr_t daddr, in_addr_t dport);

int mtcp_connect(mctx_t mctx, int sockid, const struct sockaddr *addr, socklen_t addrlen);
int mtcp_connect(mctx_t mctx, int sockid, const struct sockaddr *addr, socklen_t addrlen, uint8_t dst_flash_id);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again here compatibility issues may be there. wrapper will fix it.

Comment thread lib/flash/mtcp/ip_in.c
case IPPROTO_ICMP:
return ProcessICMPPacket(mtcp, iph, ip_len);
ProcessICMPPacket(mtcp, iph, ip_len);
return FALSE; // false to free up the packet in core.c for RX ZC.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think a ifdef should be here right for backward compatibility

Comment thread lib/flash/mtcp/tcp_in.c

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there are lot of changes in this file:

  • from normal function calls to direct returns.
  • changing FALSE to TRUE and vice versa.

Will all of this changes not affect non-zc setups?

Comment thread lib/flash/mtcp/tcp_out.c
Comment on lines +252 to +271
#ifdef MTCP_TX_ZERO_COPY
// h-> need to check mss length for pkt? for now i am experimenting with pkt sizes upto 1024 B
if (mtcp->ctx->flash_ctx.tx_zc == 0 && payloadlen + optlen > cur_stream->sndvar->mss) {
TRACE_ERROR("Payload size exceeds MSS\n");
return ERROR;
}
if (mtcp->ctx->flash_ctx.tx_zc && payloadlen > 0) {
// data for IP and TCP header will be written in place
// h-> the header len is hardcoded for (TCP_OPT_TIMESTAMP_ENABLED = 1, TCP_OPT_SACK_ENABLED = 1,), where SACK flag is never set
assert(optlen == (TCP_OPT_TIMESTAMP_LEN + 2));
mtcp->ctx->flash_ctx.data = payload - (TCP_HEADER_LEN + optlen);
} else {
mtcp->ctx->flash_ctx.data = NULL;
}
#else
if (payloadlen + optlen > cur_stream->sndvar->mss) {
TRACE_ERROR("Payload size exceeds MSS\n");
return ERROR;
}
#endif /* MTCP_TX_ZERO_COPY */

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be checked.

Comment on lines +247 to +251
/* if no space, return -2 */
if (buf->available_zc_slots <= 0) {
/* should not happen */
return -2;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will this be a problem?

int max_outstanding_tx;

#if defined(MTCP_TX_ZERO_COPY)
uint8_t *zc_tracker;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's the use of this. can you please give me a short description of this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants