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/docker/Dockerfile.dpdk b/docker/Dockerfile.dpdk new file mode 100644 index 0000000..91ae3e1 --- /dev/null +++ b/docker/Dockerfile.dpdk @@ -0,0 +1,63 @@ +# 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 \ + meson \ + ninja-build \ + pkg-config \ + libnuma-dev \ + libpcap-dev \ + linux-headers-generic \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +# 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..3623adb --- /dev/null +++ b/docker/README.DPDK.md @@ -0,0 +1,184 @@ +# DPDK Docker Environment + +This directory contains Docker configurations for building and running Magma with DPDK support. + +## Prerequisites + +- Docker 20.10+ with Compose plugin (v2) +- 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 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..b1e2558 --- /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 = "rte_eal"; + + /// + /// 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..ce4a6f8 --- /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 = "rte_ethdev"; + + /// + /// 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..a933cbd --- /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 = "rte_mbuf"; + + /// + /// 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..790d6ae --- /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 (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) + +## 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.