From 87f1baede2ad912880231a13ecb97cf464d02327 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 11 Feb 2026 04:49:48 +0000
Subject: [PATCH 1/5] Initial plan
From 1e563aa7032ab2d902167399f5c5406860cb6686 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 11 Feb 2026 05:07:42 +0000
Subject: [PATCH 2/5] Add Magma.DPDK project with P/Invoke bindings and helpers
Co-authored-by: benaadams <1142958+benaadams@users.noreply.github.com>
---
Magma.sln | 15 ++
src/Magma.DPDK/EalArgumentBuilder.cs | 249 +++++++++++++++++++++++++++
src/Magma.DPDK/HugePageHelper.cs | 181 +++++++++++++++++++
src/Magma.DPDK/Interop/EAL.cs | 79 +++++++++
src/Magma.DPDK/Interop/EthDev.cs | 222 ++++++++++++++++++++++++
src/Magma.DPDK/Interop/Mbuf.cs | 153 ++++++++++++++++
src/Magma.DPDK/Magma.DPDK.csproj | 31 ++++
src/Magma.DPDK/README.md | 194 +++++++++++++++++++++
8 files changed, 1124 insertions(+)
create mode 100644 src/Magma.DPDK/EalArgumentBuilder.cs
create mode 100644 src/Magma.DPDK/HugePageHelper.cs
create mode 100644 src/Magma.DPDK/Interop/EAL.cs
create mode 100644 src/Magma.DPDK/Interop/EthDev.cs
create mode 100644 src/Magma.DPDK/Interop/Mbuf.cs
create mode 100644 src/Magma.DPDK/Magma.DPDK.csproj
create mode 100644 src/Magma.DPDK/README.md
diff --git a/Magma.sln b/Magma.sln
index de00e04..2e15cf0 100644
--- a/Magma.sln
+++ b/Magma.sln
@@ -57,6 +57,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Magma.WinTun.TcpHost", "sam
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Magma.AF_XDP", "src\Magma.AF_XDP\Magma.AF_XDP.csproj", "{5922914B-D7E6-4A23-9A26-5589FD72727B}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Magma.DPDK", "src\Magma.DPDK\Magma.DPDK.csproj", "{864623FD-5021-4147-984F-2F5128CE4CB3}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -307,6 +309,18 @@ Global
{5922914B-D7E6-4A23-9A26-5589FD72727B}.Release|x64.Build.0 = Release|Any CPU
{5922914B-D7E6-4A23-9A26-5589FD72727B}.Release|x86.ActiveCfg = Release|Any CPU
{5922914B-D7E6-4A23-9A26-5589FD72727B}.Release|x86.Build.0 = Release|Any CPU
+ {864623FD-5021-4147-984F-2F5128CE4CB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {864623FD-5021-4147-984F-2F5128CE4CB3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {864623FD-5021-4147-984F-2F5128CE4CB3}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {864623FD-5021-4147-984F-2F5128CE4CB3}.Debug|x64.Build.0 = Debug|Any CPU
+ {864623FD-5021-4147-984F-2F5128CE4CB3}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {864623FD-5021-4147-984F-2F5128CE4CB3}.Debug|x86.Build.0 = Debug|Any CPU
+ {864623FD-5021-4147-984F-2F5128CE4CB3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {864623FD-5021-4147-984F-2F5128CE4CB3}.Release|Any CPU.Build.0 = Release|Any CPU
+ {864623FD-5021-4147-984F-2F5128CE4CB3}.Release|x64.ActiveCfg = Release|Any CPU
+ {864623FD-5021-4147-984F-2F5128CE4CB3}.Release|x64.Build.0 = Release|Any CPU
+ {864623FD-5021-4147-984F-2F5128CE4CB3}.Release|x86.ActiveCfg = Release|Any CPU
+ {864623FD-5021-4147-984F-2F5128CE4CB3}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -332,6 +346,7 @@ Global
{503140F9-24F6-41B5-89F2-BB0FB24CCB2E} = {34A1DC50-486C-4BB9-9929-1805CA0B0DD0}
{845A5A0B-E59B-46AF-BF95-4FC50D660967} = {23E375E0-8A4A-4D6A-8C96-9F2046CE9EB0}
{5922914B-D7E6-4A23-9A26-5589FD72727B} = {34A1DC50-486C-4BB9-9929-1805CA0B0DD0}
+ {864623FD-5021-4147-984F-2F5128CE4CB3} = {34A1DC50-486C-4BB9-9929-1805CA0B0DD0}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {99D656D2-FC86-462A-BB4C-610D644ADC62}
diff --git a/src/Magma.DPDK/EalArgumentBuilder.cs b/src/Magma.DPDK/EalArgumentBuilder.cs
new file mode 100644
index 0000000..3059560
--- /dev/null
+++ b/src/Magma.DPDK/EalArgumentBuilder.cs
@@ -0,0 +1,249 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Magma.DPDK
+{
+ ///
+ /// Builder for constructing DPDK EAL (Environment Abstraction Layer) arguments.
+ ///
+ public class EalArgumentBuilder
+ {
+ private readonly List _arguments = new List();
+ private string _programName = "magma-dpdk";
+
+ ///
+ /// Creates a new EAL argument builder with default settings.
+ ///
+ public EalArgumentBuilder()
+ {
+ }
+
+ ///
+ /// Sets the program name (argv[0]).
+ ///
+ /// Program name
+ /// This builder for method chaining
+ public EalArgumentBuilder WithProgramName(string name)
+ {
+ _programName = name ?? throw new ArgumentNullException(nameof(name));
+ return this;
+ }
+
+ ///
+ /// Sets the core mask for lcores to run on.
+ ///
+ /// Hexadecimal bitmask of cores (e.g., "0xF" for cores 0-3)
+ /// This builder for method chaining
+ public EalArgumentBuilder WithCoreMask(string coreMask)
+ {
+ if (string.IsNullOrWhiteSpace(coreMask))
+ throw new ArgumentException("Core mask cannot be null or whitespace", nameof(coreMask));
+
+ _arguments.Add("-c");
+ _arguments.Add(coreMask);
+ return this;
+ }
+
+ ///
+ /// Sets the list of cores to run on.
+ ///
+ /// Comma-separated list of cores (e.g., "0,2,4" or "0-3,8")
+ /// This builder for method chaining
+ public EalArgumentBuilder WithCoreList(string coreList)
+ {
+ if (string.IsNullOrWhiteSpace(coreList))
+ throw new ArgumentException("Core list cannot be null or whitespace", nameof(coreList));
+
+ _arguments.Add("-l");
+ _arguments.Add(coreList);
+ return this;
+ }
+
+ ///
+ /// Sets the number of memory channels.
+ ///
+ /// Number of memory channels (typically 2, 4, or 8)
+ /// This builder for method chaining
+ public EalArgumentBuilder WithMemoryChannels(int channels)
+ {
+ if (channels <= 0)
+ throw new ArgumentOutOfRangeException(nameof(channels), "Memory channels must be positive");
+
+ _arguments.Add("-n");
+ _arguments.Add(channels.ToString());
+ return this;
+ }
+
+ ///
+ /// Sets the amount of memory in megabytes.
+ ///
+ /// Amount of memory in MB
+ /// This builder for method chaining
+ public EalArgumentBuilder WithMemory(int megabytes)
+ {
+ if (megabytes <= 0)
+ throw new ArgumentOutOfRangeException(nameof(megabytes), "Memory must be positive");
+
+ _arguments.Add("-m");
+ _arguments.Add(megabytes.ToString());
+ return this;
+ }
+
+ ///
+ /// Adds a PCI device to the allowlist (whitelist).
+ ///
+ /// PCI address (e.g., "0000:01:00.0")
+ /// This builder for method chaining
+ public EalArgumentBuilder WithPciDevice(string pciAddress)
+ {
+ if (string.IsNullOrWhiteSpace(pciAddress))
+ throw new ArgumentException("PCI address cannot be null or whitespace", nameof(pciAddress));
+
+ _arguments.Add("-a");
+ _arguments.Add(pciAddress);
+ return this;
+ }
+
+ ///
+ /// Adds a PCI device to the blocklist (blacklist).
+ ///
+ /// PCI address (e.g., "0000:01:00.0")
+ /// This builder for method chaining
+ public EalArgumentBuilder WithBlockedPciDevice(string pciAddress)
+ {
+ if (string.IsNullOrWhiteSpace(pciAddress))
+ throw new ArgumentException("PCI address cannot be null or whitespace", nameof(pciAddress));
+
+ _arguments.Add("-b");
+ _arguments.Add(pciAddress);
+ return this;
+ }
+
+ ///
+ /// Enables the main lcore to be a service core.
+ ///
+ /// This builder for method chaining
+ public EalArgumentBuilder WithMainLcoreAsServiceCore()
+ {
+ _arguments.Add("--main-lcore");
+ _arguments.Add("0");
+ return this;
+ }
+
+ ///
+ /// Sets the huge page file prefix.
+ ///
+ /// Huge page file prefix
+ /// This builder for method chaining
+ public EalArgumentBuilder WithHugePagePrefix(string prefix)
+ {
+ if (string.IsNullOrWhiteSpace(prefix))
+ throw new ArgumentException("Huge page prefix cannot be null or whitespace", nameof(prefix));
+
+ _arguments.Add("--file-prefix");
+ _arguments.Add(prefix);
+ return this;
+ }
+
+ ///
+ /// Runs in memory mode (no huge pages needed).
+ ///
+ /// This builder for method chaining
+ public EalArgumentBuilder WithInMemoryMode()
+ {
+ _arguments.Add("--in-memory");
+ return this;
+ }
+
+ ///
+ /// Runs with no huge pages.
+ ///
+ /// This builder for method chaining
+ public EalArgumentBuilder WithNoHugePages()
+ {
+ _arguments.Add("--no-huge");
+ return this;
+ }
+
+ ///
+ /// Sets the socket memory allocation per NUMA node.
+ ///
+ /// Comma-separated list of memory in MB per socket (e.g., "1024,1024")
+ /// This builder for method chaining
+ public EalArgumentBuilder WithSocketMemory(string socketMemory)
+ {
+ if (string.IsNullOrWhiteSpace(socketMemory))
+ throw new ArgumentException("Socket memory cannot be null or whitespace", nameof(socketMemory));
+
+ _arguments.Add("--socket-mem");
+ _arguments.Add(socketMemory);
+ return this;
+ }
+
+ ///
+ /// Enables process type (primary/secondary/auto).
+ ///
+ /// Process type ("primary", "secondary", or "auto")
+ /// This builder for method chaining
+ public EalArgumentBuilder WithProcessType(string processType)
+ {
+ if (string.IsNullOrWhiteSpace(processType))
+ throw new ArgumentException("Process type cannot be null or whitespace", nameof(processType));
+
+ _arguments.Add("--proc-type");
+ _arguments.Add(processType);
+ return this;
+ }
+
+ ///
+ /// Adds a custom EAL argument.
+ ///
+ /// Custom argument
+ /// This builder for method chaining
+ public EalArgumentBuilder WithCustomArgument(string argument)
+ {
+ if (string.IsNullOrWhiteSpace(argument))
+ throw new ArgumentException("Argument cannot be null or whitespace", nameof(argument));
+
+ _arguments.Add(argument);
+ return this;
+ }
+
+ ///
+ /// Builds the argument array suitable for rte_eal_init().
+ ///
+ /// Array of arguments including program name
+ public string[] Build()
+ {
+ var result = new List { _programName };
+ result.AddRange(_arguments);
+ return result.ToArray();
+ }
+
+ ///
+ /// Gets the argument count for rte_eal_init().
+ ///
+ /// Number of arguments including program name
+ public int GetArgumentCount()
+ {
+ return _arguments.Count + 1;
+ }
+
+ ///
+ /// Returns a string representation of the arguments.
+ ///
+ /// Space-separated argument string
+ public override string ToString()
+ {
+ var sb = new StringBuilder();
+ sb.Append(_programName);
+ foreach (var arg in _arguments)
+ {
+ sb.Append(' ');
+ sb.Append(arg);
+ }
+ return sb.ToString();
+ }
+ }
+}
diff --git a/src/Magma.DPDK/HugePageHelper.cs b/src/Magma.DPDK/HugePageHelper.cs
new file mode 100644
index 0000000..0a9e8b0
--- /dev/null
+++ b/src/Magma.DPDK/HugePageHelper.cs
@@ -0,0 +1,181 @@
+using System;
+using System.IO;
+using System.Linq;
+
+namespace Magma.DPDK
+{
+ ///
+ /// Helper class for detecting and configuring huge pages on Linux.
+ ///
+ public static class HugePageHelper
+ {
+ private const string HugePagesDir = "/sys/kernel/mm/hugepages";
+ private const string HugePagesMountDir = "/proc/mounts";
+
+ ///
+ /// Information about huge page configuration.
+ ///
+ public class HugePageInfo
+ {
+ public long TotalPages { get; set; }
+ public long FreePages { get; set; }
+ public long PageSizeKb { get; set; }
+ public bool IsAvailable { get; set; }
+ public string MountPoint { get; set; }
+
+ public long TotalSizeMb => (TotalPages * PageSizeKb) / 1024;
+ public long FreeSizeMb => (FreePages * PageSizeKb) / 1024;
+ }
+
+ ///
+ /// Detects if huge pages are available on the system.
+ ///
+ /// True if huge pages are available, false otherwise
+ public static bool IsHugePagesAvailable()
+ {
+ return Directory.Exists(HugePagesDir);
+ }
+
+ ///
+ /// Gets information about the default huge page configuration (typically 2MB pages).
+ ///
+ /// Huge page information, or null if not available
+ public static HugePageInfo GetDefaultHugePageInfo()
+ {
+ return GetHugePageInfo("hugepages-2048kB");
+ }
+
+ ///
+ /// Gets information about a specific huge page size.
+ ///
+ /// Huge page directory name (e.g., "hugepages-2048kB" or "hugepages-1048576kB")
+ /// Huge page information, or null if not available
+ public static HugePageInfo GetHugePageInfo(string hugepageDir)
+ {
+ if (!IsHugePagesAvailable())
+ return null;
+
+ var hugepagePath = Path.Combine(HugePagesDir, hugepageDir);
+ if (!Directory.Exists(hugepagePath))
+ return null;
+
+ try
+ {
+ var info = new HugePageInfo
+ {
+ IsAvailable = true
+ };
+
+ var nrHugepagesFile = Path.Combine(hugepagePath, "nr_hugepages");
+ if (File.Exists(nrHugepagesFile))
+ {
+ var content = File.ReadAllText(nrHugepagesFile).Trim();
+ if (long.TryParse(content, out var totalPages))
+ info.TotalPages = totalPages;
+ }
+
+ var freeHugepagesFile = Path.Combine(hugepagePath, "free_hugepages");
+ if (File.Exists(freeHugepagesFile))
+ {
+ var content = File.ReadAllText(freeHugepagesFile).Trim();
+ if (long.TryParse(content, out var freePages))
+ info.FreePages = freePages;
+ }
+
+ var pageSizeKb = ExtractPageSizeFromDirName(hugepageDir);
+ if (pageSizeKb.HasValue)
+ info.PageSizeKb = pageSizeKb.Value;
+
+ info.MountPoint = GetHugePagesMountPoint();
+
+ return info;
+ }
+ catch (Exception)
+ {
+ return null;
+ }
+ }
+
+ ///
+ /// Gets the mount point for hugetlbfs.
+ ///
+ /// Mount point, or null if not found
+ public static string GetHugePagesMountPoint()
+ {
+ if (!File.Exists(HugePagesMountDir))
+ return null;
+
+ try
+ {
+ var lines = File.ReadAllLines(HugePagesMountDir);
+ var hugetlbfsLine = lines.FirstOrDefault(line => line.Contains("hugetlbfs"));
+ if (hugetlbfsLine is not null)
+ {
+ var parts = hugetlbfsLine.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
+ if (parts.Length >= 2)
+ return parts[1];
+ }
+ }
+ catch (Exception)
+ {
+ }
+
+ return "/dev/hugepages";
+ }
+
+ ///
+ /// Checks if the system has sufficient huge pages configured.
+ ///
+ /// Required size in megabytes
+ /// True if sufficient huge pages are available
+ public static bool HasSufficientHugePages(long requiredSizeMb)
+ {
+ var info = GetDefaultHugePageInfo();
+ if (info is null || !info.IsAvailable)
+ return false;
+
+ return info.FreeSizeMb >= requiredSizeMb;
+ }
+
+ ///
+ /// Gets a recommended huge page configuration message.
+ ///
+ /// Required size in megabytes
+ /// Configuration message
+ public static string GetConfigurationMessage(long requiredSizeMb)
+ {
+ var info = GetDefaultHugePageInfo();
+ if (info is null || !info.IsAvailable)
+ {
+ return "Huge pages are not available on this system. " +
+ "Enable huge pages in the kernel or use --no-huge or --in-memory options.";
+ }
+
+ if (info.FreeSizeMb < requiredSizeMb)
+ {
+ var requiredPages = (requiredSizeMb * 1024) / info.PageSizeKb;
+ return $"Insufficient huge pages. Required: {requiredSizeMb}MB ({requiredPages} pages), " +
+ $"Available: {info.FreeSizeMb}MB ({info.FreePages} pages). " +
+ $"Configure more huge pages with: echo {requiredPages} > /sys/kernel/mm/hugepages/hugepages-{info.PageSizeKb}kB/nr_hugepages";
+ }
+
+ return $"Huge pages are properly configured. Available: {info.FreeSizeMb}MB ({info.FreePages} pages).";
+ }
+
+ private static long? ExtractPageSizeFromDirName(string dirName)
+ {
+ if (string.IsNullOrEmpty(dirName))
+ return null;
+
+ var parts = dirName.Split('-');
+ if (parts.Length != 2)
+ return null;
+
+ var sizeStr = parts[1].Replace("kB", "").Replace("KB", "");
+ if (long.TryParse(sizeStr, out var size))
+ return size;
+
+ return null;
+ }
+ }
+}
diff --git a/src/Magma.DPDK/Interop/EAL.cs b/src/Magma.DPDK/Interop/EAL.cs
new file mode 100644
index 0000000..781de27
--- /dev/null
+++ b/src/Magma.DPDK/Interop/EAL.cs
@@ -0,0 +1,79 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Magma.DPDK.Interop
+{
+ ///
+ /// P/Invoke declarations for DPDK Environment Abstraction Layer (EAL)
+ ///
+ public static class EAL
+ {
+ private const string LibDpdk = "librte_eal.so";
+
+ ///
+ /// Initialize the Environment Abstraction Layer (EAL).
+ /// This function must be called before any other DPDK function.
+ ///
+ /// Number of arguments in argv array
+ /// Array of arguments (program name + EAL arguments)
+ ///
+ /// The number of arguments parsed on success, or negative error code on failure.
+ /// The parsed arguments are consumed and removed from argv.
+ ///
+ [DllImport(LibDpdk, CallingConvention = CallingConvention.Cdecl)]
+ public static extern int rte_eal_init(int argc, [In] string[] argv);
+
+ ///
+ /// Clean up the Environment Abstraction Layer (EAL).
+ /// This function must be called to release any resources allocated by rte_eal_init().
+ ///
+ /// 0 on success, negative error code on failure
+ [DllImport(LibDpdk, CallingConvention = CallingConvention.Cdecl)]
+ public static extern int rte_eal_cleanup();
+
+ ///
+ /// Get the current lcore ID.
+ ///
+ /// The current lcore ID
+ [DllImport(LibDpdk, CallingConvention = CallingConvention.Cdecl)]
+ public static extern uint rte_lcore_id();
+
+ ///
+ /// Get the total number of enabled lcores.
+ ///
+ /// The number of enabled lcores
+ [DllImport(LibDpdk, CallingConvention = CallingConvention.Cdecl)]
+ public static extern uint rte_lcore_count();
+
+ ///
+ /// Get the ID of the main lcore.
+ ///
+ /// The ID of the main lcore
+ [DllImport(LibDpdk, CallingConvention = CallingConvention.Cdecl)]
+ public static extern uint rte_get_main_lcore();
+
+ ///
+ /// Check if the specified lcore is enabled.
+ ///
+ /// The lcore ID to check
+ /// 1 if the lcore is enabled, 0 otherwise
+ [DllImport(LibDpdk, CallingConvention = CallingConvention.Cdecl)]
+ public static extern int rte_lcore_is_enabled(uint lcore_id);
+
+ ///
+ /// Get the socket ID (NUMA node) of the specified lcore.
+ ///
+ /// The lcore ID
+ /// The socket ID, or -1 on error
+ [DllImport(LibDpdk, CallingConvention = CallingConvention.Cdecl)]
+ public static extern int rte_lcore_to_socket_id(uint lcore_id);
+
+ ///
+ /// Return the Application thread ID of the execution unit.
+ ///
+ /// The lcore ID
+ /// The pthread ID, or -1 on error
+ [DllImport(LibDpdk, CallingConvention = CallingConvention.Cdecl)]
+ public static extern int rte_lcore_to_cpu_id(uint lcore_id);
+ }
+}
diff --git a/src/Magma.DPDK/Interop/EthDev.cs b/src/Magma.DPDK/Interop/EthDev.cs
new file mode 100644
index 0000000..269a0ce
--- /dev/null
+++ b/src/Magma.DPDK/Interop/EthDev.cs
@@ -0,0 +1,222 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Magma.DPDK.Interop
+{
+ ///
+ /// P/Invoke declarations for DPDK Ethernet device management
+ ///
+ public static class EthDev
+ {
+ private const string LibDpdk = "librte_ethdev.so";
+
+ ///
+ /// Ethernet device configuration structure.
+ ///
+ [StructLayout(LayoutKind.Sequential)]
+ public struct rte_eth_conf
+ {
+ public uint link_speeds;
+ public rte_eth_rxmode rxmode;
+ public rte_eth_txmode txmode;
+ public uint lpbk_mode;
+ public rte_eth_dcb_rx_conf rx_adv_conf_dcb;
+ public rte_eth_vmdq_dcb_conf vmdq_dcb_conf;
+ public rte_eth_dcb_tx_conf dcb_tx_conf;
+ public rte_eth_vmdq_tx_conf vmdq_tx_conf;
+ }
+
+ ///
+ /// Ethernet RX mode configuration.
+ ///
+ [StructLayout(LayoutKind.Sequential)]
+ public struct rte_eth_rxmode
+ {
+ public uint mq_mode;
+ public uint mtu;
+ public uint max_lro_pkt_size;
+ public ushort split_hdr_size;
+ public ulong offloads;
+ public uint reserved_64s0;
+ public uint reserved_64s1;
+ public uint reserved_ptrs0;
+ public uint reserved_ptrs1;
+ }
+
+ ///
+ /// Ethernet TX mode configuration.
+ ///
+ [StructLayout(LayoutKind.Sequential)]
+ public struct rte_eth_txmode
+ {
+ public uint mq_mode;
+ public ulong offloads;
+ public ushort pvid;
+ public byte hw_vlan_reject_tagged;
+ public byte hw_vlan_reject_untagged;
+ public byte hw_vlan_insert_pvid;
+ public uint reserved_64s;
+ public uint reserved_ptrs;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct rte_eth_dcb_rx_conf
+ {
+ public uint nb_tcs;
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
+ public byte[] dcb_tc;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct rte_eth_vmdq_dcb_conf
+ {
+ public uint nb_queue_pools;
+ public uint enable_default_pool;
+ public byte default_pool;
+ public byte nb_pool_maps;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct rte_eth_dcb_tx_conf
+ {
+ public uint nb_tcs;
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
+ public byte[] dcb_tc;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct rte_eth_vmdq_tx_conf
+ {
+ public uint nb_queue_pools;
+ }
+
+ ///
+ /// RX queue configuration structure.
+ ///
+ [StructLayout(LayoutKind.Sequential)]
+ public struct rte_eth_rxconf
+ {
+ public rte_eth_thresh rx_thresh;
+ public ushort rx_free_thresh;
+ public byte rx_drop_en;
+ public byte rx_deferred_start;
+ public ulong offloads;
+ }
+
+ ///
+ /// TX queue configuration structure.
+ ///
+ [StructLayout(LayoutKind.Sequential)]
+ public struct rte_eth_txconf
+ {
+ public rte_eth_thresh tx_thresh;
+ public ushort tx_rs_thresh;
+ public ushort tx_free_thresh;
+ public byte tx_deferred_start;
+ public ulong offloads;
+ }
+
+ ///
+ /// Threshold values for RX/TX queues.
+ ///
+ [StructLayout(LayoutKind.Sequential)]
+ public struct rte_eth_thresh
+ {
+ public byte pthresh;
+ public byte hthresh;
+ public byte wthresh;
+ }
+
+ ///
+ /// Get the number of available Ethernet devices.
+ ///
+ /// The number of available Ethernet devices
+ [DllImport(LibDpdk, CallingConvention = CallingConvention.Cdecl)]
+ public static extern ushort rte_eth_dev_count_avail();
+
+ ///
+ /// Configure an Ethernet device.
+ ///
+ /// The port identifier
+ /// Number of RX queues
+ /// Number of TX queues
+ /// Device configuration
+ /// 0 on success, negative error code on failure
+ [DllImport(LibDpdk, CallingConvention = CallingConvention.Cdecl)]
+ public static extern int rte_eth_dev_configure(ushort port_id, ushort nb_rx_queue, ushort nb_tx_queue, ref rte_eth_conf eth_conf);
+
+ ///
+ /// Allocate and set up a receive queue for an Ethernet device.
+ ///
+ /// The port identifier
+ /// The RX queue index (must be in range [0, nb_rx_queue-1])
+ /// Number of receive descriptors
+ /// NUMA socket ID for memory allocation
+ /// RX queue configuration, or NULL for default
+ /// Memory pool from which to allocate mbufs
+ /// 0 on success, negative error code on failure
+ [DllImport(LibDpdk, CallingConvention = CallingConvention.Cdecl)]
+ public static extern int rte_eth_rx_queue_setup(ushort port_id, ushort rx_queue_id, ushort nb_rx_desc, uint socket_id, ref rte_eth_rxconf rx_conf, nint mb_pool);
+
+ ///
+ /// Allocate and set up a transmit queue for an Ethernet device.
+ ///
+ /// The port identifier
+ /// The TX queue index (must be in range [0, nb_tx_queue-1])
+ /// Number of transmit descriptors
+ /// NUMA socket ID for memory allocation
+ /// TX queue configuration, or NULL for default
+ /// 0 on success, negative error code on failure
+ [DllImport(LibDpdk, CallingConvention = CallingConvention.Cdecl)]
+ public static extern int rte_eth_tx_queue_setup(ushort port_id, ushort tx_queue_id, ushort nb_tx_desc, uint socket_id, ref rte_eth_txconf tx_conf);
+
+ ///
+ /// Start an Ethernet device.
+ ///
+ /// The port identifier
+ /// 0 on success, negative error code on failure
+ [DllImport(LibDpdk, CallingConvention = CallingConvention.Cdecl)]
+ public static extern int rte_eth_dev_start(ushort port_id);
+
+ ///
+ /// Stop an Ethernet device.
+ ///
+ /// The port identifier
+ /// 0 on success, negative error code on failure
+ [DllImport(LibDpdk, CallingConvention = CallingConvention.Cdecl)]
+ public static extern int rte_eth_dev_stop(ushort port_id);
+
+ ///
+ /// Close a stopped Ethernet device.
+ ///
+ /// The port identifier
+ /// 0 on success, negative error code on failure
+ [DllImport(LibDpdk, CallingConvention = CallingConvention.Cdecl)]
+ public static extern int rte_eth_dev_close(ushort port_id);
+
+ ///
+ /// Enable receipt in promiscuous mode for an Ethernet device.
+ ///
+ /// The port identifier
+ /// 0 on success, negative error code on failure
+ [DllImport(LibDpdk, CallingConvention = CallingConvention.Cdecl)]
+ public static extern int rte_eth_promiscuous_enable(ushort port_id);
+
+ ///
+ /// Disable receipt in promiscuous mode for an Ethernet device.
+ ///
+ /// The port identifier
+ /// 0 on success, negative error code on failure
+ [DllImport(LibDpdk, CallingConvention = CallingConvention.Cdecl)]
+ public static extern int rte_eth_promiscuous_disable(ushort port_id);
+
+ ///
+ /// Retrieve the Ethernet address of an Ethernet device.
+ ///
+ /// The port identifier
+ /// Pointer to buffer to store MAC address
+ /// 0 on success, negative error code on failure
+ [DllImport(LibDpdk, CallingConvention = CallingConvention.Cdecl)]
+ public static extern int rte_eth_macaddr_get(ushort port_id, nint mac_addr);
+ }
+}
diff --git a/src/Magma.DPDK/Interop/Mbuf.cs b/src/Magma.DPDK/Interop/Mbuf.cs
new file mode 100644
index 0000000..a870a49
--- /dev/null
+++ b/src/Magma.DPDK/Interop/Mbuf.cs
@@ -0,0 +1,153 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Magma.DPDK.Interop
+{
+ ///
+ /// P/Invoke declarations for DPDK memory pool (mbuf) management
+ ///
+ public static class Mbuf
+ {
+ private const string LibDpdk = "librte_mbuf.so";
+
+ ///
+ /// Mbuf structure (simplified view for P/Invoke).
+ /// The actual structure is more complex, but we only need the pointer.
+ ///
+ [StructLayout(LayoutKind.Sequential)]
+ public struct rte_mbuf
+ {
+ public nint buf_addr;
+ public ulong buf_iova;
+ public ushort data_off;
+ public ushort refcnt;
+ public ushort nb_segs;
+ public ushort port;
+ public ulong ol_flags;
+ public uint packet_type;
+ public uint pkt_len;
+ public ushort data_len;
+ public ushort vlan_tci;
+ }
+
+ ///
+ /// Create a packet mbuf pool.
+ ///
+ /// Name of the mbuf pool
+ /// Number of elements in the pool
+ /// Size of per-lcore cache
+ /// Size of application private data
+ /// Size of data buffer in each mbuf
+ /// NUMA socket to allocate memory from
+ /// Pointer to mbuf pool structure, or NULL on error
+ [DllImport(LibDpdk, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
+ public static extern nint rte_pktmbuf_pool_create(
+ string name,
+ uint n,
+ uint cache_size,
+ ushort priv_size,
+ ushort data_room_size,
+ int socket_id);
+
+ ///
+ /// Free a packet mbuf pool.
+ ///
+ /// Pointer to mbuf pool
+ [DllImport(LibDpdk, CallingConvention = CallingConvention.Cdecl)]
+ public static extern void rte_mempool_free(nint mp);
+
+ ///
+ /// Allocate a new mbuf from a mempool.
+ ///
+ /// Pointer to mbuf pool
+ /// Pointer to mbuf, or NULL on error
+ [DllImport(LibDpdk, CallingConvention = CallingConvention.Cdecl)]
+ public static extern nint rte_pktmbuf_alloc(nint mp);
+
+ ///
+ /// Free a packet mbuf back to its mempool.
+ ///
+ /// Pointer to mbuf
+ [DllImport(LibDpdk, CallingConvention = CallingConvention.Cdecl)]
+ public static extern void rte_pktmbuf_free(nint m);
+
+ ///
+ /// Get the data pointer from an mbuf.
+ ///
+ /// Pointer to mbuf
+ /// Pointer to data buffer
+ public static unsafe nint rte_pktmbuf_mtod(nint m)
+ {
+ if (m == nint.Zero)
+ return nint.Zero;
+
+ var mbuf = Marshal.PtrToStructure(m);
+ return mbuf.buf_addr + mbuf.data_off;
+ }
+
+ ///
+ /// Prepend data to the beginning of an mbuf.
+ ///
+ /// Pointer to mbuf
+ /// Amount of data to prepend
+ /// Pointer to prepended data, or NULL on error
+ [DllImport(LibDpdk, CallingConvention = CallingConvention.Cdecl)]
+ public static extern nint rte_pktmbuf_prepend(nint m, ushort len);
+
+ ///
+ /// Append data to the end of an mbuf.
+ ///
+ /// Pointer to mbuf
+ /// Amount of data to append
+ /// Pointer to appended data, or NULL on error
+ [DllImport(LibDpdk, CallingConvention = CallingConvention.Cdecl)]
+ public static extern nint rte_pktmbuf_append(nint m, ushort len);
+
+ ///
+ /// Remove data at the beginning of an mbuf.
+ ///
+ /// Pointer to mbuf
+ /// Amount of data to remove
+ /// Pointer to new data start, or NULL on error
+ [DllImport(LibDpdk, CallingConvention = CallingConvention.Cdecl)]
+ public static extern nint rte_pktmbuf_adj(nint m, ushort len);
+
+ ///
+ /// Remove data at the end of an mbuf.
+ ///
+ /// Pointer to mbuf
+ /// Amount of data to remove
+ /// 0 on success, negative on error
+ [DllImport(LibDpdk, CallingConvention = CallingConvention.Cdecl)]
+ public static extern int rte_pktmbuf_trim(nint m, ushort len);
+
+ ///
+ /// Reset the fields of a packet mbuf to their default values.
+ ///
+ /// Pointer to mbuf
+ [DllImport(LibDpdk, CallingConvention = CallingConvention.Cdecl)]
+ public static extern void rte_pktmbuf_reset(nint m);
+
+ ///
+ /// Get the headroom in a packet mbuf.
+ ///
+ /// Pointer to mbuf
+ /// Amount of headroom in bytes
+ public static ushort rte_pktmbuf_headroom(nint m)
+ {
+ if (m == nint.Zero)
+ return 0;
+
+ var mbuf = Marshal.PtrToStructure(m);
+ return mbuf.data_off;
+ }
+
+ ///
+ /// Common mbuf sizes.
+ ///
+ public const ushort RTE_MBUF_DEFAULT_BUF_SIZE = 2048 + 128;
+ public const ushort RTE_MBUF_DEFAULT_DATAROOM = 2048;
+ public const int RTE_MEMPOOL_CACHE_MAX_SIZE = 512;
+ public const int SOCKET_ID_ANY = -1;
+ }
+}
diff --git a/src/Magma.DPDK/Magma.DPDK.csproj b/src/Magma.DPDK/Magma.DPDK.csproj
new file mode 100644
index 0000000..50cfc88
--- /dev/null
+++ b/src/Magma.DPDK/Magma.DPDK.csproj
@@ -0,0 +1,31 @@
+
+
+
+ net10.0
+ true
+ latest
+ DPDK (Data Plane Development Kit) integration for maximum performance packet I/O
+
+
+
+ false
+
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Magma.DPDK/README.md b/src/Magma.DPDK/README.md
new file mode 100644
index 0000000..f75e639
--- /dev/null
+++ b/src/Magma.DPDK/README.md
@@ -0,0 +1,194 @@
+# Magma.DPDK
+
+DPDK (Data Plane Development Kit) integration for Magma, providing maximum performance packet I/O through kernel bypass.
+
+## Overview
+
+This library provides P/Invoke bindings to DPDK native libraries, enabling .NET applications to achieve extreme network performance by bypassing the kernel networking stack.
+
+## Features
+
+- **Environment Abstraction Layer (EAL)**: Core initialization and resource management
+- **Ethernet Device Management**: Device configuration, start/stop, and control
+- **Memory Pool (mbuf)**: Packet buffer allocation and management
+- **EAL Argument Builder**: Fluent API for configuring DPDK initialization
+- **Huge Page Helpers**: Detection and configuration of Linux huge pages
+
+## Requirements
+
+- Linux operating system (DPDK is Linux-only)
+- DPDK 23.11 or later installed on the system
+- Huge pages configured (or use `--no-huge` option for testing)
+- Root privileges or appropriate capabilities (CAP_NET_ADMIN, CAP_IPC_LOCK)
+
+## Installation
+
+### DPDK Installation
+
+On Ubuntu/Debian:
+```bash
+sudo apt-get update
+sudo apt-get install dpdk dpdk-dev
+```
+
+On RHEL/CentOS:
+```bash
+sudo yum install dpdk dpdk-devel
+```
+
+### Huge Page Configuration
+
+DPDK requires huge pages for optimal performance. Configure 2MB huge pages:
+
+```bash
+# Allocate 1024 huge pages (2GB total)
+echo 1024 | sudo tee /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
+
+# Mount hugetlbfs (if not already mounted)
+sudo mkdir -p /dev/hugepages
+sudo mount -t hugetlbfs nodev /dev/hugepages
+```
+
+To make this persistent across reboots, add to `/etc/sysctl.conf`:
+```
+vm.nr_hugepages = 1024
+```
+
+## Usage
+
+### Basic Initialization
+
+```csharp
+using Magma.DPDK;
+using Magma.DPDK.Interop;
+
+// Build EAL arguments
+var args = new EalArgumentBuilder()
+ .WithCoreList("0-3") // Use cores 0-3
+ .WithMemoryChannels(4) // 4 memory channels
+ .WithMemory(2048) // 2GB of memory
+ .WithPciDevice("0000:01:00.0") // Specify NIC
+ .Build();
+
+// Initialize DPDK EAL
+var ret = EAL.rte_eal_init(args.Length, args);
+if (ret < 0)
+{
+ throw new Exception($"Failed to initialize DPDK EAL: {ret}");
+}
+
+// Your DPDK application logic here...
+
+// Cleanup
+EAL.rte_eal_cleanup();
+```
+
+### Check Huge Pages
+
+```csharp
+using Magma.DPDK;
+
+// Check if huge pages are available
+if (HugePageHelper.IsHugePagesAvailable())
+{
+ var info = HugePageHelper.GetDefaultHugePageInfo();
+ Console.WriteLine($"Total huge pages: {info.TotalPages}");
+ Console.WriteLine($"Free huge pages: {info.FreePages}");
+ Console.WriteLine($"Total size: {info.TotalSizeMb}MB");
+}
+
+// Check if sufficient huge pages are available
+if (!HugePageHelper.HasSufficientHugePages(2048))
+{
+ Console.WriteLine(HugePageHelper.GetConfigurationMessage(2048));
+}
+```
+
+### Memory Pool Creation
+
+```csharp
+using Magma.DPDK.Interop;
+
+// Create a packet mbuf pool
+var mbufPool = Mbuf.rte_pktmbuf_pool_create(
+ name: "mbuf_pool",
+ n: 8192, // 8K buffers
+ cache_size: 256, // 256 per-lcore cache
+ priv_size: 0, // No private data
+ data_room_size: Mbuf.RTE_MBUF_DEFAULT_DATAROOM,
+ socket_id: Mbuf.SOCKET_ID_ANY
+);
+
+if (mbufPool == nint.Zero)
+{
+ throw new Exception("Failed to create mbuf pool");
+}
+```
+
+### Ethernet Device Configuration
+
+```csharp
+using Magma.DPDK.Interop;
+
+// Get number of available devices
+var numPorts = EthDev.rte_eth_dev_count_avail();
+Console.WriteLine($"Found {numPorts} DPDK ports");
+
+// Configure device
+var portId = (ushort)0;
+var ethConf = new EthDev.rte_eth_conf();
+var ret = EthDev.rte_eth_dev_configure(portId, 1, 1, ref ethConf);
+if (ret < 0)
+{
+ throw new Exception($"Failed to configure device: {ret}");
+}
+
+// Start device
+ret = EthDev.rte_eth_dev_start(portId);
+if (ret < 0)
+{
+ throw new Exception($"Failed to start device: {ret}");
+}
+```
+
+## Platform Considerations
+
+### Linux Only
+
+This library can **only** be built and used on Linux. Attempting to build on Windows or macOS will result in a build error.
+
+### Root Privileges
+
+DPDK typically requires root privileges or specific capabilities:
+
+```bash
+# Run with sudo
+sudo dotnet run
+
+# Or grant capabilities (more secure)
+sudo setcap cap_net_admin,cap_ipc_lock=ep /path/to/your/app
+```
+
+## Limitations
+
+- This is a foundational implementation providing P/Invoke bindings and basic helpers
+- Packet RX/TX functionality will be added in future phases
+- No managed abstractions or high-level APIs yet
+- Direct P/Invoke requires understanding of DPDK memory model and lifecycle
+
+## Next Steps
+
+- Implement single-queue RX/TX operations
+- Add multi-queue support
+- Integrate with `IPacketReceiver` and `IPacketTransmitter` interfaces
+- Add sample applications demonstrating DPDK usage
+
+## Resources
+
+- [DPDK Documentation](https://doc.dpdk.org/)
+- [DPDK Getting Started Guide](https://doc.dpdk.org/guides/linux_gsg/)
+- [DPDK Programming Guide](https://doc.dpdk.org/guides/prog_guide/)
+
+## License
+
+This project is licensed under the same license as the main Magma project.
From bf12d12be127986c63eb7089601185ccafaab9b9 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 11 Feb 2026 05:08:40 +0000
Subject: [PATCH 3/5] Add Docker configuration for DPDK builds
Co-authored-by: benaadams <1142958+benaadams@users.noreply.github.com>
---
docker/Dockerfile.dpdk | 65 ++++++++++++
docker/README.DPDK.md | 185 +++++++++++++++++++++++++++++++++
docker/docker-compose.dpdk.yml | 22 ++++
3 files changed, 272 insertions(+)
create mode 100644 docker/Dockerfile.dpdk
create mode 100644 docker/README.DPDK.md
create mode 100644 docker/docker-compose.dpdk.yml
diff --git a/docker/Dockerfile.dpdk b/docker/Dockerfile.dpdk
new file mode 100644
index 0000000..9fe09ec
--- /dev/null
+++ b/docker/Dockerfile.dpdk
@@ -0,0 +1,65 @@
+# Dockerfile for building Magma with DPDK support
+# Based on Ubuntu 24.04 with DPDK 23.11+
+
+FROM ubuntu:24.04
+
+# Set environment variables
+ENV DEBIAN_FRONTEND=noninteractive
+ENV DPDK_VERSION=23.11.2
+
+# Install build dependencies
+RUN apt-get update && apt-get install -y \
+ build-essential \
+ wget \
+ curl \
+ git \
+ python3 \
+ python3-pip \
+ python3-pyelftools \
+ ninja-build \
+ pkg-config \
+ libnuma-dev \
+ libpcap-dev \
+ linux-headers-generic \
+ ca-certificates \
+ && rm -rf /var/lib/apt/lists/*
+
+# Install meson (required for DPDK build)
+RUN pip3 install --break-system-packages meson
+
+# Download and build DPDK
+WORKDIR /tmp
+RUN wget https://fast.dpdk.org/rel/dpdk-${DPDK_VERSION}.tar.xz && \
+ tar xf dpdk-${DPDK_VERSION}.tar.xz && \
+ cd dpdk-${DPDK_VERSION} && \
+ meson setup build && \
+ cd build && \
+ ninja && \
+ ninja install && \
+ ldconfig && \
+ cd /tmp && \
+ rm -rf dpdk-${DPDK_VERSION} dpdk-${DPDK_VERSION}.tar.xz
+
+# Install .NET SDK 10.0
+RUN wget https://dot.net/v1/dotnet-install.sh -O dotnet-install.sh && \
+ chmod +x dotnet-install.sh && \
+ ./dotnet-install.sh --channel 10.0 --install-dir /usr/share/dotnet && \
+ ln -s /usr/share/dotnet/dotnet /usr/bin/dotnet && \
+ rm dotnet-install.sh
+
+# Set up huge pages (2GB)
+RUN mkdir -p /dev/hugepages && \
+ echo "vm.nr_hugepages = 1024" >> /etc/sysctl.conf
+
+# Create working directory
+WORKDIR /app
+
+# Copy the source code
+COPY . .
+
+# Restore and build
+RUN dotnet restore && \
+ dotnet build -c Release
+
+# Set entrypoint
+CMD ["/bin/bash"]
diff --git a/docker/README.DPDK.md b/docker/README.DPDK.md
new file mode 100644
index 0000000..eb0217b
--- /dev/null
+++ b/docker/README.DPDK.md
@@ -0,0 +1,185 @@
+# DPDK Docker Environment
+
+This directory contains Docker configurations for building and running Magma with DPDK support.
+
+## Prerequisites
+
+- Docker 20.10+
+- Docker Compose 1.29+
+- Linux host with huge pages configured (see below)
+- DPDK-compatible network interface (optional, for actual packet processing)
+
+## Huge Pages Setup (Host)
+
+Before running the Docker container, configure huge pages on the host:
+
+```bash
+# Allocate 1024 huge pages (2GB total)
+echo 1024 | sudo tee /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
+
+# Create huge pages mount point
+sudo mkdir -p /dev/hugepages
+sudo mount -t hugetlbfs nodev /dev/hugepages
+```
+
+To make this persistent across reboots, add to `/etc/sysctl.conf`:
+```
+vm.nr_hugepages = 1024
+```
+
+## Building the Image
+
+### Using Docker
+
+```bash
+cd docker
+docker build -f Dockerfile.dpdk -t magma-dpdk:latest ..
+```
+
+### Using Docker Compose
+
+```bash
+cd docker
+docker-compose -f docker-compose.dpdk.yml build
+```
+
+## Running the Container
+
+### Using Docker
+
+```bash
+docker run -it --privileged \
+ --cap-add=IPC_LOCK \
+ --cap-add=NET_ADMIN \
+ --network=host \
+ -v /dev/hugepages:/dev/hugepages \
+ magma-dpdk:latest
+```
+
+### Using Docker Compose
+
+```bash
+cd docker
+docker-compose -f docker-compose.dpdk.yml up -d
+docker-compose -f docker-compose.dpdk.yml exec magma-dpdk /bin/bash
+```
+
+## Verifying DPDK Installation
+
+Inside the container, verify DPDK is installed:
+
+```bash
+# Check DPDK version
+dpdk-devbind --version
+
+# List available network devices
+dpdk-devbind --status
+
+# Check huge pages
+cat /proc/meminfo | grep Huge
+```
+
+## Building Magma with DPDK
+
+Inside the container:
+
+```bash
+# Build all projects including Magma.DPDK
+dotnet build
+
+# Build only DPDK project
+dotnet build src/Magma.DPDK/Magma.DPDK.csproj
+
+# Run tests (if available)
+dotnet test
+```
+
+## Using DPDK with Magma
+
+```bash
+# Example: Initialize DPDK EAL (requires huge pages)
+cd /app
+dotnet run --project samples/YourDpdkSample
+
+# For testing without hardware (no huge pages required)
+dotnet run --project samples/YourDpdkSample -- --no-huge
+```
+
+## Binding Network Interfaces to DPDK
+
+To use DPDK with actual network hardware, bind interfaces to DPDK-compatible drivers:
+
+```bash
+# Inside container
+
+# Check current driver
+dpdk-devbind --status
+
+# Unbind from kernel driver and bind to DPDK driver
+dpdk-devbind --bind=vfio-pci 0000:01:00.0
+
+# Verify binding
+dpdk-devbind --status
+```
+
+## Troubleshooting
+
+### Permission Denied
+
+If you get permission errors:
+- Ensure the container runs with `--privileged` flag
+- Check that `IPC_LOCK` and `NET_ADMIN` capabilities are granted
+
+### Huge Pages Not Available
+
+```bash
+# Check huge pages inside container
+cat /proc/meminfo | grep Huge
+
+# If zero, check host configuration
+# Exit container and run on host:
+echo 1024 | sudo tee /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
+```
+
+### DPDK Initialization Fails
+
+Try running with `--no-huge` option for testing:
+```bash
+dotnet run -- --no-huge --in-memory
+```
+
+### No Network Devices Found
+
+- Ensure network devices are bound to DPDK driver (see "Binding Network Interfaces" above)
+- Use `dpdk-devbind --status` to check device status
+- Some devices may require specific DPDK drivers
+
+## CI/CD Integration
+
+For CI builds, use the Dockerfile to create a consistent build environment:
+
+```yaml
+# Example GitHub Actions workflow
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ container:
+ image: magma-dpdk:latest
+ steps:
+ - uses: actions/checkout@v3
+ - name: Build with DPDK
+ run: dotnet build
+```
+
+## Notes
+
+- The container requires privileged mode for direct hardware access
+- Host network mode is recommended for DPDK applications
+- Huge pages must be configured on the host, not just in the container
+- DPDK 23.11+ uses meson/ninja build system (not the old make-based system)
+
+## Resources
+
+- [DPDK Documentation](https://doc.dpdk.org/)
+- [DPDK Container Guide](https://doc.dpdk.org/guides/howto/docker.html)
+- [Huge Pages Documentation](https://www.kernel.org/doc/Documentation/vm/hugetlbpage.txt)
diff --git a/docker/docker-compose.dpdk.yml b/docker/docker-compose.dpdk.yml
new file mode 100644
index 0000000..9d9eec6
--- /dev/null
+++ b/docker/docker-compose.dpdk.yml
@@ -0,0 +1,22 @@
+version: '3.8'
+
+services:
+ magma-dpdk:
+ build:
+ context: ..
+ dockerfile: docker/Dockerfile.dpdk
+ image: magma-dpdk:latest
+ container_name: magma-dpdk
+ privileged: true # Required for DPDK to access hardware
+ volumes:
+ - /dev/hugepages:/dev/hugepages # Share huge pages with host
+ - ..:/app # Mount source code
+ devices:
+ - /dev/vfio/vfio # VFIO device for DPDK (if using VFIO driver)
+ cap_add:
+ - IPC_LOCK # Required for huge pages
+ - NET_ADMIN # Required for network configuration
+ network_mode: host # Use host network for direct hardware access
+ command: /bin/bash
+ tty: true
+ stdin_open: true
From 7b26353f1b0d9269fa86790b93d371c761e2d270 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 11 Feb 2026 05:09:58 +0000
Subject: [PATCH 4/5] Fix library names and Docker image configuration per code
review
Co-authored-by: benaadams <1142958+benaadams@users.noreply.github.com>
---
docker/Dockerfile.dpdk | 4 +---
src/Magma.DPDK/Interop/EAL.cs | 2 +-
src/Magma.DPDK/Interop/EthDev.cs | 2 +-
src/Magma.DPDK/Interop/Mbuf.cs | 2 +-
4 files changed, 4 insertions(+), 6 deletions(-)
diff --git a/docker/Dockerfile.dpdk b/docker/Dockerfile.dpdk
index 9fe09ec..91ae3e1 100644
--- a/docker/Dockerfile.dpdk
+++ b/docker/Dockerfile.dpdk
@@ -16,6 +16,7 @@ RUN apt-get update && apt-get install -y \
python3 \
python3-pip \
python3-pyelftools \
+ meson \
ninja-build \
pkg-config \
libnuma-dev \
@@ -24,9 +25,6 @@ RUN apt-get update && apt-get install -y \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
-# Install meson (required for DPDK build)
-RUN pip3 install --break-system-packages meson
-
# Download and build DPDK
WORKDIR /tmp
RUN wget https://fast.dpdk.org/rel/dpdk-${DPDK_VERSION}.tar.xz && \
diff --git a/src/Magma.DPDK/Interop/EAL.cs b/src/Magma.DPDK/Interop/EAL.cs
index 781de27..b1e2558 100644
--- a/src/Magma.DPDK/Interop/EAL.cs
+++ b/src/Magma.DPDK/Interop/EAL.cs
@@ -8,7 +8,7 @@ namespace Magma.DPDK.Interop
///
public static class EAL
{
- private const string LibDpdk = "librte_eal.so";
+ private const string LibDpdk = "rte_eal";
///
/// Initialize the Environment Abstraction Layer (EAL).
diff --git a/src/Magma.DPDK/Interop/EthDev.cs b/src/Magma.DPDK/Interop/EthDev.cs
index 269a0ce..ce4a6f8 100644
--- a/src/Magma.DPDK/Interop/EthDev.cs
+++ b/src/Magma.DPDK/Interop/EthDev.cs
@@ -8,7 +8,7 @@ namespace Magma.DPDK.Interop
///
public static class EthDev
{
- private const string LibDpdk = "librte_ethdev.so";
+ private const string LibDpdk = "rte_ethdev";
///
/// Ethernet device configuration structure.
diff --git a/src/Magma.DPDK/Interop/Mbuf.cs b/src/Magma.DPDK/Interop/Mbuf.cs
index a870a49..a933cbd 100644
--- a/src/Magma.DPDK/Interop/Mbuf.cs
+++ b/src/Magma.DPDK/Interop/Mbuf.cs
@@ -8,7 +8,7 @@ namespace Magma.DPDK.Interop
///
public static class Mbuf
{
- private const string LibDpdk = "librte_mbuf.so";
+ private const string LibDpdk = "rte_mbuf";
///
/// Mbuf structure (simplified view for P/Invoke).
From 68b86848fd9ec6257b2b61a59ebada3793a37ed8 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 11 Feb 2026 05:11:14 +0000
Subject: [PATCH 5/5] Update documentation to clarify DPDK version and Docker
Compose requirements
Co-authored-by: benaadams <1142958+benaadams@users.noreply.github.com>
---
docker/README.DPDK.md | 3 +--
src/Magma.DPDK/README.md | 2 +-
2 files changed, 2 insertions(+), 3 deletions(-)
diff --git a/docker/README.DPDK.md b/docker/README.DPDK.md
index eb0217b..3623adb 100644
--- a/docker/README.DPDK.md
+++ b/docker/README.DPDK.md
@@ -4,8 +4,7 @@ This directory contains Docker configurations for building and running Magma wit
## Prerequisites
-- Docker 20.10+
-- Docker Compose 1.29+
+- Docker 20.10+ with Compose plugin (v2)
- Linux host with huge pages configured (see below)
- DPDK-compatible network interface (optional, for actual packet processing)
diff --git a/src/Magma.DPDK/README.md b/src/Magma.DPDK/README.md
index f75e639..790d6ae 100644
--- a/src/Magma.DPDK/README.md
+++ b/src/Magma.DPDK/README.md
@@ -17,7 +17,7 @@ This library provides P/Invoke bindings to DPDK native libraries, enabling .NET
## Requirements
- Linux operating system (DPDK is Linux-only)
-- DPDK 23.11 or later installed on the system
+- DPDK 23.11 or later installed on the system (tested with 23.11.2)
- Huge pages configured (or use `--no-huge` option for testing)
- Root privileges or appropriate capabilities (CAP_NET_ADMIN, CAP_IPC_LOCK)