From 2c9e0f480021c38a648b3b5f764d24428b317f8b Mon Sep 17 00:00:00 2001 From: Ashley Date: Fri, 20 Feb 2026 18:36:35 +0000 Subject: [PATCH] alkali-style onvm app implementation --- tests/experiments_c/basic_monitor.c | 102 ++++++++++++ tests/experiments_c/bridge.c | 41 +++++ tests/experiments_c/firewall.c | 74 +++++++++ tests/experiments_c/forward.c | 52 ++++++ tests/experiments_c/forward_tb.c | 112 +++++++++++++ tests/experiments_c/l2fwd.c | 109 +++++++++++++ tests/experiments_c/l3fwd.c | 194 +++++++++++++++++++++++ tests/experiments_c/load_balancer.c | 153 ++++++++++++++++++ tests/experiments_c/simple_fwd_compare.c | 80 ++++++++++ 9 files changed, 917 insertions(+) create mode 100644 tests/experiments_c/basic_monitor.c create mode 100644 tests/experiments_c/bridge.c create mode 100644 tests/experiments_c/firewall.c create mode 100644 tests/experiments_c/forward.c create mode 100644 tests/experiments_c/forward_tb.c create mode 100644 tests/experiments_c/l2fwd.c create mode 100644 tests/experiments_c/l3fwd.c create mode 100644 tests/experiments_c/load_balancer.c create mode 100644 tests/experiments_c/simple_fwd_compare.c diff --git a/tests/experiments_c/basic_monitor.c b/tests/experiments_c/basic_monitor.c new file mode 100644 index 0000000..aa4d709 --- /dev/null +++ b/tests/experiments_c/basic_monitor.c @@ -0,0 +1,102 @@ +#include "alkali.h" +#include "generic_handlers.h" + +/* ---------------- Upstream ingress metadata ---------------- */ +struct ingress_meta_t { + BITS_FIELD(32, ingress_port); + BITS_FIELD(32, r0); + BITS_FIELD(32, r1); + BITS_FIELD(32, r2); +}; + +/* ---------------- Ethernet header (split 32+16) ---------------- */ +struct eth_header_t { + BITS_FIELD(32, dst_mac_1); + BITS_FIELD(16, dst_mac_2); + BITS_FIELD(32, src_mac_1); + BITS_FIELD(16, src_mac_2); + BITS_FIELD(16, ether_type); +}; + +/* ---------------- Dispatcher metadata ---------------- */ +/* flags: bit0=drop (0 forward), bit1=mac_swapped */ +struct monitor_meta_t { + BITS_FIELD(32, egress_port); + BITS_FIELD(32, flags); + BITS_FIELD(32, ingress_port); + BITS_FIELD(32, r0); +}; + +/* ---------------- Stats tables ---------------- */ +/* key=0 => global counters */ +struct global_stats_t { + BITS_FIELD(32, total_packets); + BITS_FIELD(32, r0); +}; +ak_TABLE(4, BITS(32), struct global_stats_t, ) global_stats; + +/* key=port => per-port counters */ +struct port_stats_t { + BITS_FIELD(32, packets); + BITS_FIELD(32, r0); +}; +ak_TABLE(256, BITS(32), struct port_stats_t, ) port_stats; + +/* Swap src/dst MAC (no branches) */ +static inline void +swap_mac(struct eth_header_t *eth) { + BITS(32) t32; + BITS(16) t16; + + t32 = eth->dst_mac_1; eth->dst_mac_1 = eth->src_mac_1; eth->src_mac_1 = t32; + t16 = eth->dst_mac_2; eth->dst_mac_2 = eth->src_mac_2; eth->src_mac_2 = t16; +} + +void NET_RECV__process_packet(buf_t packet) { + /* 0) Extract ingress port */ + struct ingress_meta_t inmeta; + bufextract(packet, (void*)&inmeta); + BITS(32) ingress_port = inmeta.ingress_port; + + /* 1) Extract Ethernet (for MAC swap) */ + struct eth_header_t eth; + bufextract(packet, (void*)ð); + + /* 2) Update stats (global + per-port) */ + { + BITS(32) k0 = (BITS(32))0; + struct global_stats_t gs; + gs.total_packets = (BITS(32))0; + gs.r0 = (BITS(32))0; + table_lookup(&global_stats, &k0, &gs); + gs.total_packets = gs.total_packets + (BITS(32))1; + table_update(&global_stats, &k0, &gs); + } + + { + struct port_stats_t ps; + ps.packets = (BITS(32))0; + ps.r0 = (BITS(32))0; + table_lookup(&port_stats, &ingress_port, &ps); + ps.packets = ps.packets + (BITS(32))1; + table_update(&port_stats, &ingress_port, &ps); + } + + /* 3) Swap MAC addresses */ + swap_mac(ð); + + /* 4) Emit rewritten Ethernet back (depends on your pipeline's packet model) */ + bufemit(packet, (void*)ð); + + /* 5) Emit dispatcher meta: hairpin to the same port */ + struct monitor_meta_t outm; + outm.egress_port = ingress_port; + outm.flags = (BITS(32))0 | ((BITS(32))1 << 1); /* mac_swapped=1 */ + outm.ingress_port = ingress_port; + outm.r0 = (BITS(32))0; + + bufemit(packet, (void*)&outm); + + /* 6) Terminate */ + EXT__NET_SEND__net_send(packet); +} \ No newline at end of file diff --git a/tests/experiments_c/bridge.c b/tests/experiments_c/bridge.c new file mode 100644 index 0000000..d35f8fb --- /dev/null +++ b/tests/experiments_c/bridge.c @@ -0,0 +1,41 @@ +#include "alkali.h" +#include "generic_handlers.h" + +/* Upstream ingress metadata */ +struct ingress_meta_t { + BITS_FIELD(32, ingress_port); + BITS_FIELD(32, r0); + BITS_FIELD(32, r1); + BITS_FIELD(32, r2); +}; + +/* Meta consumed by the dispatcher */ +struct bridge_meta_t { + BITS_FIELD(32, egress_port); + BITS_FIELD(32, flags); /* bit0=drop (0 forward), other bits reserved */ + BITS_FIELD(32, ingress_port); /* optional: for tracing/debug */ + BITS_FIELD(32, r0); +}; + +void NET_RECV__process_packet(buf_t packet) { + /* 0) Extract upstream ingress metadata */ + struct ingress_meta_t inmeta; + bufextract(packet, (void*)&inmeta); + + BITS(32) in_port = inmeta.ingress_port; + + /* 1) Compute egress port: swap 0 <-> 1 */ + BITS(32) out_port = in_port ^ (BITS(32))1; + + /* 2) Emit dispatcher metadata */ + struct bridge_meta_t outm; + outm.egress_port = out_port; + outm.flags = (BITS(32))0; /* no drop */ + outm.ingress_port = in_port; + outm.r0 = (BITS(32))0; + + bufemit(packet, (void*)&outm); + + /* 3) Terminate stage */ + EXT__NET_SEND__net_send(packet); +} \ No newline at end of file diff --git a/tests/experiments_c/firewall.c b/tests/experiments_c/firewall.c new file mode 100644 index 0000000..24fb127 --- /dev/null +++ b/tests/experiments_c/firewall.c @@ -0,0 +1,74 @@ +#include "alkali.h" +#include "generic_handlers.h" + +/* Ethernet header */ +struct eth_header_t { + BITS_FIELD(48, dst_mac); + BITS_FIELD(48, src_mac); + BITS_FIELD(16, ether_type); +}; + +/* IPv4 header (fixed 20B) */ +struct ip_header_t { + BITS_FIELD(16, misc); + BITS_FIELD(16, length); + BITS_FIELD(16, identification); + BITS_FIELD(16, fragment_offset); + BITS_FIELD(8, ttl); + BITS_FIELD(8, protocol); + BITS_FIELD(16, hdr_checksum); + BITS_FIELD(32, source_ip); + BITS_FIELD(32, dst_ip); +}; + +/* + * Firewall table: + * key = src_ip + * value = rule (0 => allow, non-zero => drop) + * + * IMPORTANT: Use BITS(32) directly to avoid struct value lowering issues. + */ +ak_TABLE(64, BITS(32), BITS(32), ) firewall_ip_table; + +void NET_RECV__process_packet(buf_t packet) { + struct eth_header_t eth; + struct ip_header_t ip; + + /* Always extract in a fixed order */ + bufextract(packet, (void*)ð); + bufextract(packet, (void*)&ip); + + /* Default: drop */ + BITS(32) rule = (BITS(32))1; + BITS(32) allow = (BITS(32))0; + + /* Only apply firewall to IPv4 */ + if (eth.ether_type == (BITS(16))0x0800) { + BITS(32) key = ip.source_ip; + + /* + * On miss, rule should remain default=1. + * So we pass a pre-initialized rule into lookup as the "default". + */ + BITS(32) looked_rule = (BITS(32))1; + table_lookup(&firewall_ip_table, &key, &looked_rule); + + rule = looked_rule; + + if (rule == (BITS(32))0) { + allow = (BITS(32))1; + } else { + allow = (BITS(32))0; + } + } + + /* + * Append meta (rule, allow) WITHOUT using a struct. + * This avoids ep2 struct_update / stackslot init bugs. + */ + bufemit(packet, (void*)&rule); + bufemit(packet, (void*)&allow); + + /* Must terminate by calling the next stage */ + EXT__NET_SEND__net_send(packet); +} \ No newline at end of file diff --git a/tests/experiments_c/forward.c b/tests/experiments_c/forward.c new file mode 100644 index 0000000..8a80ebe --- /dev/null +++ b/tests/experiments_c/forward.c @@ -0,0 +1,52 @@ +#include "alkali.h" +#include "generic_handlers.h" + +// ---- compile-time configuration (replaces CLI arguments) ---- +#define DESTINATION_SVC 3 +#define PRINT_DELAY 1000000 + +// ---- state: use a table to store a global packet counter (fixed key = 0) ---- +struct counter_state_t { + BITS_FIELD(32, cnt); +}; +ak_TABLE(64, BITS(32), struct counter_state_t, ) counter_table; + +// ---- custom forwarding metadata (replaces onvm_pkt_meta.destination) ---- +struct forward_meta_t { + BITS_FIELD(32, destination); // destination service identifier + BITS_FIELD(32, flags); // bit0 = should_report + BITS_FIELD(32, seq); // local packet counter snapshot + BITS_FIELD(32, reserved); +}; + +void NET_RECV__process_packet(buf_t packet) { + // 1) Load and increment the global packet counter + BITS(32) key = 0; + struct counter_state_t st; + table_lookup(&counter_table, &key, &st); + + BITS(32) new_cnt = st.cnt + 1; + + // Compute hit without control-flow: + // hit = 1 if new_cnt >= PRINT_DELAY, else 0 + BITS(32) hit = (new_cnt >= PRINT_DELAY); + + // Reset counter without using branches: + // if hit == 1 -> st.cnt = 0 + // else -> st.cnt = new_cnt + st.cnt = new_cnt * (1 - hit); + + table_update(&counter_table, &key, &st); + + // 2) Emit forwarding metadata + struct forward_meta_t meta; + meta.destination = DESTINATION_SVC; + meta.flags = hit; // downstream stages can act on this flag + meta.seq = new_cnt; // snapshot of the counter before reset + meta.reserved = 0; + + bufemit(packet, &meta); + + // 3) Forward the packet to the next pipeline stage + EXT__NET_SEND__net_send(packet); +} diff --git a/tests/experiments_c/forward_tb.c b/tests/experiments_c/forward_tb.c new file mode 100644 index 0000000..9e46d80 --- /dev/null +++ b/tests/experiments_c/forward_tb.c @@ -0,0 +1,112 @@ +#include "alkali.h" +#include "generic_handlers.h" + +// Compile-time defaults (can be replaced by cfg_table written by control-plane) +#define DESTINATION_SVC 3 + +// -------------------- Packet headers (minimal) -------------------- +struct eth_header_t { + BITS_FIELD(48, dst_mac); + BITS_FIELD(48, src_mac); + BITS_FIELD(16, ether_type); +}; + +struct ip_header_t { + BITS_FIELD(16, misc); + BITS_FIELD(16, length); // IPv4 total length (bytes): header + payload + BITS_FIELD(16, identification); + BITS_FIELD(16, fragment_offset); + BITS_FIELD(16, TTL_transport); + BITS_FIELD(16, checksum); + BITS_FIELD(32, source_ip); + BITS_FIELD(32, dst_ip); + BITS_FIELD(32, options); +}; + +// -------------------- Token bucket state/config -------------------- +// State: current tokens (bytes) +struct tb_state_t { + BITS_FIELD(32, tokens_bytes); +}; + +// Config: bucket depth (bytes). (Rate is not used in dataplane; refill is external.) +struct tb_cfg_t { + BITS_FIELD(32, depth_bytes); +}; + +// key = 0 => single global bucket (you can extend to per-flow later) +ak_TABLE(64, BITS(32), struct tb_state_t, ) tb_state_table; +ak_TABLE(64, BITS(32), struct tb_cfg_t, ) tb_cfg_table; + +// -------------------- Metadata emitted to packet -------------------- +// flags bit0: drop (1 = should drop) +// flags bit1: allow (1 = allowed) +// You can add more bits later. +struct tb_meta_t { + BITS_FIELD(32, destination); + BITS_FIELD(32, flags); + BITS_FIELD(32, pkt_len_bytes); + BITS_FIELD(32, tokens_after); +}; + +void NET_RECV__process_packet(buf_t packet) { + // 0) Parse headers to compute packet length (best-effort) + // NOTE: If your input packets are not guaranteed to be IPv4, you should + // place a classifier stage before this NF. We avoid if/else here. + struct eth_header_t eth; + struct ip_header_t ip; + + bufextract(packet, (void*)ð); + bufextract(packet, (void*)&ip); + + // Approx L2+L3 total length: + // IPv4 total length field does NOT include Ethernet header. + // Add 14 bytes Ethernet header as in your ONVM example. + BITS(32) pkt_len = (BITS(32))ip.length + (BITS(32))14; + + // 1) Load config/state from tables (key = 0) + BITS(32) key = 0; + + struct tb_cfg_t cfg; + table_lookup(&tb_cfg_table, &key, &cfg); + + struct tb_state_t st; + table_lookup(&tb_state_table, &key, &st); + + BITS(32) depth = cfg.depth_bytes; + BITS(32) tokens = st.tokens_bytes; + + // 2) Compute allow/drop decisions WITHOUT branching + // too_big = 1 if pkt_len > depth else 0 + BITS(32) too_big = (pkt_len > depth); + + // enough = 1 if tokens >= pkt_len else 0 + BITS(32) enough = (tokens >= pkt_len); + + // allow = (not too_big) AND enough + // Since values are 0/1, we can use multiplication for AND. + BITS(32) allow = (1 - too_big) * enough; + + // drop = 1 - allow + BITS(32) drop = 1 - allow; + + // 3) Update tokens WITHOUT branching + // If allow==1: tokens_after = tokens - pkt_len + // If allow==0: tokens_after = tokens + BITS(32) tokens_after = tokens - (pkt_len * allow); + + st.tokens_bytes = tokens_after; + table_update(&tb_state_table, &key, &st); + + // 4) Emit metadata for downstream stages + struct tb_meta_t meta; + meta.destination = DESTINATION_SVC; + meta.flags = (drop) | (allow << 1); + meta.pkt_len_bytes = pkt_len; + meta.tokens_after = tokens_after; + + bufemit(packet, &meta); + + // 5) Forward packet (downstream can drop if meta.flags bit0 is set) + EXT__NET_SEND__net_send(packet); +} \ No newline at end of file diff --git a/tests/experiments_c/l2fwd.c b/tests/experiments_c/l2fwd.c new file mode 100644 index 0000000..6f1f767 --- /dev/null +++ b/tests/experiments_c/l2fwd.c @@ -0,0 +1,109 @@ +#include "alkali.h" +#include "generic_handlers.h" + +/* + * Version A contract: + * - Upstream (or host feeder) must prepend ingress_meta_t to provide ingress_port. + * - This stage decides egress_port and emits l2sw_meta_t for a host dispatcher. + * - No send-to-port primitive exists; we only net_send() to the next stage. + */ + +/* Ingress metadata (must exist in packet before this stage) */ +struct ingress_meta_t { + BITS_FIELD(32, ingress_port); + BITS_FIELD(32, reserved0); + BITS_FIELD(32, reserved1); + BITS_FIELD(32, reserved2); +}; + +/* Ethernet header with MAC split into 32+16 (Alkali-friendly) */ +struct eth_header_t { + BITS_FIELD(32, dst_mac_1); + BITS_FIELD(16, dst_mac_2); + BITS_FIELD(32, src_mac_1); + BITS_FIELD(16, src_mac_2); + BITS_FIELD(16, ether_type); +}; + +/* dst_port_table: ingress_port -> egress_port */ +struct port_map_t { + BITS_FIELD(32, egress_port); +}; +ak_TABLE(64, BITS(32), struct port_map_t, ) dst_port_table; + +/* cfg_table[0]: global switch flags */ +struct sw_cfg_t { + BITS_FIELD(32, mac_updating); // 0/1 +}; +ak_TABLE(64, BITS(32), struct sw_cfg_t, ) cfg_table; + +/* stats_table: per-port counters */ +struct port_stats_t { + BITS_FIELD(32, rx); + BITS_FIELD(32, tx); + BITS_FIELD(32, dropped); +}; +ak_TABLE(64, BITS(32), struct port_stats_t, ) stats_table; + +/* Metadata emitted for host dispatcher */ +struct l2sw_meta_t { + BITS_FIELD(32, egress_port); + BITS_FIELD(32, flags); // bit0=drop, bit1=mac_updated + BITS_FIELD(32, ingress_port); + BITS_FIELD(32, reserved); +}; + +void NET_RECV__process_packet(buf_t packet) { + /* 0) Extract ingress meta (provided by upstream/host) */ + struct ingress_meta_t inmeta; + bufextract(packet, (void*)&inmeta); + BITS(32) ingress_port = inmeta.ingress_port; + + /* 1) Lookup egress port */ + struct port_map_t pm; + table_lookup(&dst_port_table, &ingress_port, &pm); + BITS(32) egress_port = pm.egress_port; + + /* 2) Update RX stats on ingress port */ + struct port_stats_t st_in; + table_lookup(&stats_table, &ingress_port, &st_in); + st_in.rx = st_in.rx + 1; + table_update(&stats_table, &ingress_port, &st_in); + + /* 3) Read config (mac_updating: 0/1) */ + BITS(32) cfg_key = 0; + struct sw_cfg_t cfg; + table_lookup(&cfg_table, &cfg_key, &cfg); + + /* m32 is 0/1 in i32 domain */ + BITS(32) m32 = cfg.mac_updating; + BITS(32) inv32 = (BITS(32))1 - m32; + + /* m16 / inv16 are 0/1 in i16 domain (for 16-bit fields) */ + BITS(16) m16 = (BITS(16))m32; + BITS(16) inv16 = (BITS(16))1 - m16; + + struct eth_header_t eth; + bufextract(packet, (void*)ð); + + BITS(32) new_mac_1 = (BITS(32))0x02000000; + BITS(16) new_mac_2 = (BITS(16))egress_port; + + /* 32-bit parts */ + eth.dst_mac_1 = eth.dst_mac_1 * inv32 + new_mac_1 * m32; + eth.src_mac_1 = eth.src_mac_1 * inv32 + new_mac_1 * m32; + + /* 16-bit parts (stay entirely in i16 math) */ + { + BITS(16) dst2 = (BITS(16))eth.dst_mac_2; + BITS(16) src2 = (BITS(16))eth.src_mac_2; + + dst2 = dst2 * inv16 + new_mac_2 * m16; + src2 = src2 * inv16 + new_mac_2 * m16; + + eth.dst_mac_2 = (BITS(16))dst2; + eth.src_mac_2 = (BITS(16))src2; + } + + bufemit(packet, (void*)ð); +} \ No newline at end of file diff --git a/tests/experiments_c/l3fwd.c b/tests/experiments_c/l3fwd.c new file mode 100644 index 0000000..baeac62 --- /dev/null +++ b/tests/experiments_c/l3fwd.c @@ -0,0 +1,194 @@ +#include "alkali.h" +#include "generic_handlers.h" + +/* + * L3 Forwarding (single-stage): Exact Match + LPM-lite + * + * Contract: + * - Upstream prepends ingress_meta_t. + * - This stage extracts ingress meta + Ethernet + IPv4. + * - Performs EM or LPM-lite lookup based on cfg.mode. + * - Optionally rewrites MAC. + * - Emits l3sw_meta_t for dispatcher. + * + * Mode: + * 0 = EM (exact dst_ip) + * 1 = LPM-lite (/32,/24,/16,/8 priority) + * + * Notes: + * - No loops / switch. + * - MAC split into 32+16 bits to avoid width mixing. + */ + +/* ---------------- Ingress metadata ---------------- */ +struct ingress_meta_t { + BITS_FIELD(32, ingress_port); + BITS_FIELD(32, r0); + BITS_FIELD(32, r1); + BITS_FIELD(32, r2); +}; + +/* ---------------- Ethernet + IPv4 ---------------- */ +struct eth_header_t { + BITS_FIELD(32, dst_mac_1); + BITS_FIELD(16, dst_mac_2); + BITS_FIELD(32, src_mac_1); + BITS_FIELD(16, src_mac_2); + BITS_FIELD(16, ether_type); +}; + +struct ip_header_t { + BITS_FIELD(16, misc); + BITS_FIELD(16, length); + BITS_FIELD(16, identification); + BITS_FIELD(16, fragment_offset); + BITS_FIELD(16, TTL_transport); + BITS_FIELD(16, checksum); + BITS_FIELD(32, source_ip); + BITS_FIELD(32, dst_ip); + BITS_FIELD(32, options); +}; + +/* ---------------- Routing tables ---------------- */ +/* value = (egress_port, valid-bit) */ +struct rt_val_t { + BITS_FIELD(32, egress_port); + BITS_FIELD(32, valid); +}; + +ak_TABLE(2048, BITS(32), struct rt_val_t, ) em_route; +ak_TABLE(2048, BITS(32), struct rt_val_t, ) lpm_32; +ak_TABLE(2048, BITS(32), struct rt_val_t, ) lpm_24; +ak_TABLE(2048, BITS(32), struct rt_val_t, ) lpm_16; +ak_TABLE(2048, BITS(32), struct rt_val_t, ) lpm_8; + +/* ---------------- Global config ---------------- */ +struct cfg_t { + BITS_FIELD(32, mode); /* 0=EM, 1=LPM-lite */ + BITS_FIELD(32, mac_rewrite); /* 0/1 */ +}; +ak_TABLE(64, BITS(32), struct cfg_t, ) cfg_table; + +/* ---------------- Per-port stats ---------------- */ +struct port_stats_t { + BITS_FIELD(32, forwarded); + BITS_FIELD(32, dropped); +}; +ak_TABLE(256, BITS(32), struct port_stats_t, ) port_stats; + +/* ---------------- Dispatcher metadata ---------------- */ +/* flags: bit0=drop, bit1=mac_rewritten, bit2=mode */ +struct l3sw_meta_t { + BITS_FIELD(32, egress_port); + BITS_FIELD(32, flags); + BITS_FIELD(32, ingress_port); + BITS_FIELD(32, dst_ip); +}; + +/* Branchless MAC rewrite */ +static inline void +rewrite_mac_branchless(struct eth_header_t *eth, + BITS(32) egress_port, + BITS(32) mac_rewrite) { + BITS(32) m32 = mac_rewrite; + BITS(32) inv32 = (BITS(32))1 - m32; + + BITS(16) m16 = (BITS(16))m32; + BITS(16) inv16 = (BITS(16))1 - m16; + + BITS(32) new_mac_1 = (BITS(32))0x02000000; + BITS(16) new_mac_2 = (BITS(16))egress_port; + + eth->dst_mac_1 = eth->dst_mac_1 * inv32 + new_mac_1 * m32; + eth->src_mac_1 = eth->src_mac_1 * inv32 + new_mac_1 * m32; + + { + BITS(16) dst2 = (BITS(16))eth->dst_mac_2; + BITS(16) src2 = (BITS(16))eth->src_mac_2; + + dst2 = dst2 * inv16 + new_mac_2 * m16; + src2 = src2 * inv16 + new_mac_2 * m16; + + eth->dst_mac_2 = dst2; + eth->src_mac_2 = src2; + } +} + +void NET_RECV__process_packet(buf_t packet) { + + /* 0) Ingress meta */ + struct ingress_meta_t inmeta; + bufextract(packet, (void*)&inmeta); + BITS(32) ingress_port = inmeta.ingress_port; + + /* 1) Parse L2/L3 */ + struct eth_header_t eth; + struct ip_header_t ip; + bufextract(packet, (void*)ð); + bufextract(packet, (void*)&ip); + BITS(32) dst_ip = ip.dst_ip; + + /* 2) Load config */ + BITS(32) key0 = 0; + struct cfg_t cfg; + table_lookup(&cfg_table, &key0, &cfg); + + BITS(32) use_lpm = cfg.mode; + BITS(32) use_em = (BITS(32))1 - use_lpm; + + /* 3A) EM */ + struct rt_val_t emv; + table_lookup(&em_route, &dst_ip, &emv); + BITS(32) em_port = + emv.egress_port * emv.valid + + ingress_port * ((BITS(32))1 - emv.valid); + + /* 3B) LPM-lite */ + BITS(32) k32 = dst_ip; + BITS(32) k24 = dst_ip & (BITS(32))0xFFFFFF00; + BITS(32) k16 = dst_ip & (BITS(32))0xFFFF0000; + BITS(32) k8 = dst_ip & (BITS(32))0xFF000000; + + struct rt_val_t v32, v24, v16, v8; + table_lookup(&lpm_32, &k32, &v32); + table_lookup(&lpm_24, &k24, &v24); + table_lookup(&lpm_16, &k16, &v16); + table_lookup(&lpm_8, &k8, &v8); + + BITS(32) pick32 = v32.valid; + BITS(32) pick24 = ((BITS(32))1 - pick32) * v24.valid; + BITS(32) pick16 = ((BITS(32))1 - pick32 - pick24) * v16.valid; + BITS(32) pick8 = ((BITS(32))1 - pick32 - pick24 - pick16) * v8.valid; + BITS(32) pickfb = (BITS(32))1 - pick32 - pick24 - pick16 - pick8; + + BITS(32) lpm_port = + v32.egress_port * pick32 + + v24.egress_port * pick24 + + v16.egress_port * pick16 + + v8.egress_port * pick8 + + ingress_port * pickfb; + + /* 3C) Mode select */ + BITS(32) egress_port = em_port * use_em + lpm_port * use_lpm; + + /* 4) MAC rewrite */ + rewrite_mac_branchless(ð, egress_port, cfg.mac_rewrite); + bufemit(packet, (void*)ð); + + /* 5) Stats */ + struct port_stats_t ps; + table_lookup(&port_stats, &egress_port, &ps); + ps.forwarded = ps.forwarded + (BITS(32))1; + table_update(&port_stats, &egress_port, &ps); + + /* 6) Emit dispatcher meta */ + struct l3sw_meta_t outm; + outm.egress_port = egress_port; + outm.flags = (cfg.mac_rewrite << 1) | (use_lpm << 2); + outm.ingress_port = ingress_port; + outm.dst_ip = dst_ip; + + bufemit(packet, (void*)&outm); + + EXT__NET_SEND__net_send(packet); +} \ No newline at end of file diff --git a/tests/experiments_c/load_balancer.c b/tests/experiments_c/load_balancer.c new file mode 100644 index 0000000..4cbea8e --- /dev/null +++ b/tests/experiments_c/load_balancer.c @@ -0,0 +1,153 @@ +// Round-robin (TODO) +// Flow timeout / eviction (TODO) + +#include "alkali.h" +#include "generic_handlers.h" + +struct eth_header_t { + BITS_FIELD(48, dst_mac); + BITS_FIELD(48, src_mac); + BITS_FIELD(16, ether_type); +}; + +struct ip_header_t { + // Version (4), IHL (4), DSCP (6), ECN (2) + BITS_FIELD(16, misc); + + BITS_FIELD(16, length); + BITS_FIELD(16, identification); + + // Flags (3), Fragment Offset (13) + BITS_FIELD(16, fragment_offset); + + // TTL (8), Transport Protocol (8) + BITS_FIELD(16, TTL_transport); + + BITS_FIELD(16, checksum); + BITS_FIELD(32, source_ip); + BITS_FIELD(32, dst_ip); + BITS_FIELD(32, options); +}; + +struct tcp_header_t { + BITS_FIELD(16, sport); // Source port + BITS_FIELD(16, dport); // Destination port + + BITS_FIELD(32, seq); // Sequence number + BITS_FIELD(32, ack); // Acknowledgement number + + BITS_FIELD(8, off); // Data offset + BITS_FIELD(8, flags); // Flags + BITS_FIELD(16, win); // Window + + BITS_FIELD(16, sum); // Checksum + BITS_FIELD(16, urp); // Urgent pointer +}; + +struct lb_entry_t { + BITS_FIELD(32, flow_id); + BITS_FIELD(1, if_alloc); + BITS_FIELD(32, backend_ip); + BITS_FIELD(16, backend_port); + //BITS_FIELD(48, backend_mac); +}; + +struct backend_t { + BITS_FIELD(32, ip); + BITS_FIELD(16, port); + //BITS_FIELD(48, mac); +}; + +struct lb_fwd_meta_t { + BITS_FIELD(32, flow_id); + BITS_FIELD(32, backend_ip); + BITS_FIELD(16, backend_port); + //BITS_FIELD(48, backend_mac); +}; + +ak_TABLE(16, BITS(32), struct backend_t) backend_table; +ak_TABLE(1024, BITS(32), struct lb_entry_t) flow_table; + +void NET_RECV__process_packet(buf_t packet) { + struct eth_header_t eth_header; + struct ip_header_t ip_header; + struct tcp_header_t tcp_header; + + bufextract(packet, (void *)ð_header); + bufextract(packet, (void *)&ip_header); + bufextract(packet, (void *)&tcp_header); // Extract headers from packet + + // Backend init (demo values) + BITS(32) base_ip = 134744071; + BITS(16) base_port = 8000; + //BITS(64) base_mac = 0x001122334400; + + struct backend_t be; + BITS(32) idx; + + /* backend 0 */ + idx = 0; + be.ip = base_ip + 0; + be.port = base_port + 0; + //be.mac = base_mac + 0; + //table_update(&backend_table, &idx, &be); + + /* backend 1 */ + idx = 1; + be.ip = base_ip + 1; + be.port = base_port + 1; + //be.mac = base_mac + 1; + //table_update(&backend_table, &idx, &be); + + /* backend 2 */ + idx = 2; + be.ip = base_ip + 2; + be.port = base_port + 2; + //be.mac = base_mac + 2; + //table_update(&backend_table, &idx, &be); + + /* backend 3 */ + idx = 3; + be.ip = base_ip + 3; + be.port = base_port + 3; + //be.mac = base_mac + 3; + //table_update(&backend_table, &idx, &be); + + BITS(32) backend_num = 4; + + // Flow label using XOR of 4-tuple fields (simple hash) + // TODO: confirm Alkali supports 4-tuple as a direct table key if needed. + BITS(32) fid = ip_header.source_ip ^ ip_header.dst_ip ^ tcp_header.sport ^ tcp_header.dport; + + struct lb_entry_t ent; + table_lookup(&flow_table, &fid, &ent); + + // If not allocated yet, assign a backend for this flow + if (ent.if_alloc == 0) { + BITS(32) idx = fid % backend_num; // Same flow goes to the same backend + + table_lookup(&backend_table, &idx, &be); + + ent.if_alloc = 1; + ent.backend_ip = be.ip; + ent.backend_port = be.port; + //ent.backend_mac = be.mac; + + table_update(&flow_table, &fid, &ent); + } + + // Rewrite destination to backend + ip_header.dst_ip = ent.backend_ip; + tcp_header.dport = ent.backend_port; + //eth_header.dst_mac = ent.backend_mac; + + // Emit metadata for downstream stages + struct lb_fwd_meta_t meta; + meta.flow_id = fid; + meta.backend_ip = ent.backend_ip; + meta.backend_port = ent.backend_port; + //meta.backend_mac = ent.backend_mac; + + bufemit(packet, &meta); + EXT__NET_SEND__net_send(packet); +} \ No newline at end of file diff --git a/tests/experiments_c/simple_fwd_compare.c b/tests/experiments_c/simple_fwd_compare.c new file mode 100644 index 0000000..a4d68e6 --- /dev/null +++ b/tests/experiments_c/simple_fwd_compare.c @@ -0,0 +1,80 @@ +#include "alkali.h" +#include "generic_handlers.h" + +// Minimal IPv4 header fields used in this handler +struct ip_header_t { + BITS_FIELD(16, misc); // Version/IHL/DSCP/ECN + BITS_FIELD(16, length); // Total length of the IP packet + BITS_FIELD(16, identification); // Identification field + BITS_FIELD(16, fragment_offset); // Flags + Fragment offset + BITS_FIELD(16, TTL_transport); // TTL + Protocol + BITS_FIELD(16, checksum); // Header checksum + BITS_FIELD(32, source_ip); // Source IP address + BITS_FIELD(32, dst_ip); // Destination IP address + BITS_FIELD(32, options); // Options (if present) +}; + +// Metadata emitted to indicate whether the packet should be dropped +struct drop_it { + BITS_FIELD(16, if_drop); // 1 = drop, 0 = forward +}; + +void NET_RECV__process_packet(buf_t packet) { + + // Extract IP header from incoming packet buffer + struct ip_header_t ip_header; + bufextract(packet, (void *)&ip_header); + + // Maximum allowed packet length (token threshold) + BITS(16) max_token; + max_token = 20000; + + // Read packet length from IP header + BITS(16) length; + length = ip_header.length; + + // Drop condition: + // If packet length exceeds the configured threshold, + // mark it to be dropped. + BITS(16) if_drop; + if_drop = (length > max_token); + + // Emit drop decision as metadata for downstream processing + struct drop_it res; + res.if_drop = if_drop; + bufemit(packet, &res); + + // Forward packet (actual drop handling may occur in later stages) + EXT__NET_SEND__net_send(packet); +} + + +/* +netronome build error: + +/home/zy/Desktop/Alkali/scripts/netronome_compile.sh: line 50: +8610 Floating point exception (core dumped) + +./build/bin/ep2c-opt +-ep2-pipeline-handler="mode=loop target=netronome" +"netronome_out/commonopt.mlir" +-o "netronome_out/cut.mlir" + +Observed during Netronome target compilation. +*/ + + +/* +rtl build error: + +Info: __handler_NET_RECV_process_packet_1 has 0 replicated args +%7 = llvm.zext %6 : i1 to i32 + +ep2c-opt: +.../EmitFPGAHelper.cpp:520: +std::string mlir::ep2::EmitFPGAPass::getValName(mlir::Value): +Assertion `false' failed. + +Likely related to boolean comparison (icmp) and zero-extension handling +in the RTL backend. +*/ \ No newline at end of file