Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions Magma.sln
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Magma.Performance", "benchm
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Magma.NetMap.TcpHost", "sample\Magma.NetMap.TcpHost\Magma.NetMap.TcpHost.csproj", "{358CD7A8-E78B-4DCD-8640-EA62442755D7}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Magma.NetMap.UdpEchoServer", "sample\Magma.NetMap.UdpEchoServer\Magma.NetMap.UdpEchoServer.csproj", "{F8B5E3A1-2D4C-4F8A-9E5B-1C3D4E5F6A7B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Magma.Internet.Ip.Facts", "test\Magma.Internet.Ip.Facts\Magma.Internet.Ip.Facts.csproj", "{47D45F49-E8E0-4267-9FDA-8635DA6F5348}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Magma.PCap", "src\Magma.PCap\Magma.PCap.csproj", "{F91B0866-9DF5-488D-AD3E-1E471509C60F}"
Expand Down Expand Up @@ -235,6 +237,18 @@ Global
{358CD7A8-E78B-4DCD-8640-EA62442755D7}.Release|x64.Build.0 = Release|Any CPU
{358CD7A8-E78B-4DCD-8640-EA62442755D7}.Release|x86.ActiveCfg = Release|Any CPU
{358CD7A8-E78B-4DCD-8640-EA62442755D7}.Release|x86.Build.0 = Release|Any CPU
{F8B5E3A1-2D4C-4F8A-9E5B-1C3D4E5F6A7B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F8B5E3A1-2D4C-4F8A-9E5B-1C3D4E5F6A7B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F8B5E3A1-2D4C-4F8A-9E5B-1C3D4E5F6A7B}.Debug|x64.ActiveCfg = Debug|Any CPU
{F8B5E3A1-2D4C-4F8A-9E5B-1C3D4E5F6A7B}.Debug|x64.Build.0 = Debug|Any CPU
{F8B5E3A1-2D4C-4F8A-9E5B-1C3D4E5F6A7B}.Debug|x86.ActiveCfg = Debug|Any CPU
{F8B5E3A1-2D4C-4F8A-9E5B-1C3D4E5F6A7B}.Debug|x86.Build.0 = Debug|Any CPU
{F8B5E3A1-2D4C-4F8A-9E5B-1C3D4E5F6A7B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F8B5E3A1-2D4C-4F8A-9E5B-1C3D4E5F6A7B}.Release|Any CPU.Build.0 = Release|Any CPU
{F8B5E3A1-2D4C-4F8A-9E5B-1C3D4E5F6A7B}.Release|x64.ActiveCfg = Release|Any CPU
{F8B5E3A1-2D4C-4F8A-9E5B-1C3D4E5F6A7B}.Release|x64.Build.0 = Release|Any CPU
{F8B5E3A1-2D4C-4F8A-9E5B-1C3D4E5F6A7B}.Release|x86.ActiveCfg = Release|Any CPU
{F8B5E3A1-2D4C-4F8A-9E5B-1C3D4E5F6A7B}.Release|x86.Build.0 = Release|Any CPU
{47D45F49-E8E0-4267-9FDA-8635DA6F5348}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{47D45F49-E8E0-4267-9FDA-8635DA6F5348}.Debug|Any CPU.Build.0 = Debug|Any CPU
{47D45F49-E8E0-4267-9FDA-8635DA6F5348}.Debug|x64.ActiveCfg = Debug|Any CPU
Expand Down Expand Up @@ -326,6 +340,7 @@ Global
{D8E37D7D-E535-4790-B93C-1E60AD624C70} = {B1BA53C8-CCCF-46D5-BA9F-6031811F2E19}
{C2C87D71-4BB3-4DD1-BE4D-35DEBF927290} = {0581267E-B53B-4BBE-BB90-80C3CA0C4ACD}
{358CD7A8-E78B-4DCD-8640-EA62442755D7} = {23E375E0-8A4A-4D6A-8C96-9F2046CE9EB0}
{F8B5E3A1-2D4C-4F8A-9E5B-1C3D4E5F6A7B} = {23E375E0-8A4A-4D6A-8C96-9F2046CE9EB0}
{47D45F49-E8E0-4267-9FDA-8635DA6F5348} = {B1BA53C8-CCCF-46D5-BA9F-6031811F2E19}
{F91B0866-9DF5-488D-AD3E-1E471509C60F} = {34A1DC50-486C-4BB9-9929-1805CA0B0DD0}
{76D4F5E7-2C04-420D-A43A-B721D2F32904} = {23E375E0-8A4A-4D6A-8C96-9F2046CE9EB0}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<LangVersion>latest</LangVersion>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Magma.Common\Magma.Common.csproj" />
<ProjectReference Include="..\..\src\Magma.Internet.Ip\Magma.Internet.Ip.csproj" />
<ProjectReference Include="..\..\src\Magma.Link\Magma.Link.csproj" />
<ProjectReference Include="..\..\src\Magma.NetMap\Magma.NetMap.csproj" />
<ProjectReference Include="..\..\src\Magma.Transport.Udp\Magma.Transport.Udp.csproj" />
</ItemGroup>

</Project>
130 changes: 130 additions & 0 deletions sample/Magma.NetMap.UdpEchoServer/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Magma.Internet.Ip;
using Magma.Network.Header;

namespace Magma.NetMap.UdpEchoServer
{
class Program
{
static void Main(string[] args)
{
var listenPort = (ushort)7777;

if (args.Length >= 1)
{
if (ushort.TryParse(args[0], out var port))
{
listenPort = port;
}
}

Console.WriteLine("=== Magma UDP Echo Server Sample ===");
Console.WriteLine($"Listen Port: {listenPort}");
Console.WriteLine($"Ethernet Header: {Unsafe.SizeOf<Ethernet>()} bytes");
Console.WriteLine($"IPv4 Header: {Unsafe.SizeOf<IPv4>()} bytes");
Console.WriteLine($"UDP Header: {Unsafe.SizeOf<Udp>()} bytes");
Console.WriteLine();
Console.WriteLine("This sample demonstrates:");
Console.WriteLine(" - UDP header parsing using Magma.Transport.Udp");
Console.WriteLine(" - End-to-end packet processing (Ethernet -> IPv4 -> UDP)");
Console.WriteLine(" - Zero-copy packet manipulation with Span<byte>");
Console.WriteLine();
Console.WriteLine("To run this sample with NetMap:");
Console.WriteLine(" 1. Ensure NetMap kernel module is loaded");
Console.WriteLine(" 2. Run with sudo or appropriate capabilities");
Console.WriteLine(" 3. Specify network interface: dotnet run -- eth0");
Console.WriteLine();
Console.WriteLine("Example UDP packet processing:");
Console.WriteLine();

DemonstrateUdpParsing(listenPort);

Console.WriteLine();
Console.WriteLine("Sample completed. See README.md for integration with NetMap.");
}

static unsafe void DemonstrateUdpParsing(ushort listenPort)
{
var packet = CreateSampleUdpPacket(listenPort);

Console.WriteLine($"Sample packet ({packet.Length} bytes):");
Console.WriteLine(BitConverter.ToString(packet));
Console.WriteLine();

if (Ethernet.TryConsume(packet, out var ethernet, out var etherData))
{
Console.WriteLine(ethernet.ToString());

if (ethernet.Ethertype == EtherType.IPv4)
{
if (IPv4.TryConsume(etherData, out var ipv4, out var ipData))
{
Console.WriteLine(ipv4.ToString());

if (ipv4.Protocol == ProtocolNumber.Udp)
{
if (Udp.TryConsume(ipData, out var udp, out var udpData))
{
Console.WriteLine(udp.ToString());
Console.WriteLine($"+- UDP Data -------------------------------------------------------------------+");
Console.WriteLine($"| Length: {udpData.Length} bytes".PadRight(87) + "|");
if (udpData.Length > 0)
{
var preview = udpData.Length > 60 ? udpData.Slice(0, 60) : udpData;
Console.WriteLine($"| {BitConverter.ToString(preview.ToArray())}".PadRight(87) + "|");
}
Console.WriteLine($"+------------------------------------------------------------------------------+");
Console.WriteLine();
Console.WriteLine($"✓ Successfully parsed UDP packet to port {udp.DestinationPort}");
}
}
}
}
}
}

static byte[] CreateSampleUdpPacket(ushort destPort)
{
var packet = new byte[64];
var span = packet.AsSpan();

ref byte current = ref MemoryMarshal.GetReference(span);

ref var ethernet = ref Unsafe.As<byte, Ethernet>(ref current);
ethernet.Destination = new MacAddress(0x00, 0x11, 0x22, 0x33, 0x44, 0x55);
ethernet.Source = new MacAddress(0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF);
ethernet.Ethertype = EtherType.IPv4;

current = ref Unsafe.Add(ref current, Unsafe.SizeOf<Ethernet>());

ref var ipv4 = ref Unsafe.As<byte, IPv4>(ref current);
ipv4.VersionAndHeaderLength = 0x45;
ipv4.TypeOfService = 0;
ipv4.TotalLength = (ushort)(20 + 8 + 10);
ipv4.Identification = 12345;
ipv4.FlagsAndFragmentOffset = 0;
ipv4.TimeToLive = 64;
ipv4.Protocol = ProtocolNumber.Udp;
ipv4.SourceAddress = new IPv4Address(192, 168, 1, 100);
ipv4.DestinationAddress = new IPv4Address(192, 168, 1, 1);
ipv4.HeaderChecksum = 0;

current = ref Unsafe.Add(ref current, Unsafe.SizeOf<IPv4>());

ref var udp = ref Unsafe.As<byte, Udp>(ref current);
udp.SourcePort = 12345;
udp.DestinationPort = destPort;
udp.Length = (ushort)(8 + 10);
udp.Checksum = 0;

current = ref Unsafe.Add(ref current, Unsafe.SizeOf<Udp>());

var data = "Hello UDP!"u8.ToArray();
data.AsSpan().CopyTo(MemoryMarshal.CreateSpan(ref current, data.Length));

return packet;
}
}
}
154 changes: 154 additions & 0 deletions sample/Magma.NetMap.UdpEchoServer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
# Magma UDP Echo Server Sample

A simple UDP echo server demonstrating end-to-end usage of the Magma network stack with UDP transport.

## Overview

This sample application shows how to:
- Parse incoming UDP packets using the Magma stack (Ethernet → IPv4 → UDP)
- Process UDP datagrams at the transport layer
- Construct and transmit UDP response packets
- Use NetMap for high-performance packet I/O on Linux

The server listens on a specified UDP port and echoes back any received data to the sender.

## What It Demonstrates

- **UDP Header Parsing**: Uses `Udp.TryConsume()` to parse UDP headers from raw packet data
- **Packet Construction**: Builds outgoing UDP packets from scratch, including:
- Ethernet frame with swapped MAC addresses
- IPv4 header with swapped IP addresses and checksum calculation
- UDP header with swapped ports
- **Zero-Copy Processing**: Direct manipulation of packet buffers using `Span<byte>` and unsafe pointers
- **NetMap Integration**: High-performance packet I/O using Linux NetMap interface

## Building

From the repository root:

```bash
dotnet build sample/Magma.NetMap.UdpEchoServer
```

## Running

### Prerequisites

- Linux with NetMap kernel module loaded
- Network interface available for NetMap (typically a dedicated interface)
- Root privileges or appropriate capabilities

### Start the Server

```bash
# Run with default settings (eth0, port 7777)
sudo dotnet run --project sample/Magma.NetMap.UdpEchoServer

# Specify interface and port
sudo dotnet run --project sample/Magma.NetMap.UdpEchoServer -- eth1 8888
```

### Test the Server

From another machine on the same network:

```bash
# Using netcat (nc)
echo "Hello Magma!" | nc -u <server-ip> 7777

# Using Python
python3 -c "import socket; s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM); s.sendto(b'Hello', ('<server-ip>', 7777)); print(s.recvfrom(1024))"

# Using socat
echo "Test message" | socat - UDP4:<server-ip>:7777
```

## Architecture

```
┌─────────────────────────────────────────────────────────┐
│ Application │
│ (UdpEchoReceiver) │
└────────────────────┬────────────────────────────────────┘
│ IPacketReceiver.TryConsume()
┌────────────────────▼────────────────────────────────────┐
│ NetMap Transport │
│ (NetMapPort<UdpEchoReceiver>) │
└────────────────────┬────────────────────────────────────┘
│ Direct packet I/O
┌────────────────────▼────────────────────────────────────┐
│ Network Interface │
│ (NetMap ring) │
└─────────────────────────────────────────────────────────┘
```

## Code Flow

### Receive Path

1. NetMap delivers raw packet buffer to `UdpEchoReceiver.TryConsume()`
2. Parse Ethernet frame: `Ethernet.TryConsume()`
3. Check for IPv4: `etherIn.Ethertype == EtherType.IPv4`
4. Parse IPv4 header: `IPv4.TryConsume()`
5. Check for UDP: `ipIn.Protocol == ProtocolNumber.Udp`
6. Parse UDP header: `Udp.TryConsume()`
7. Check destination port matches listen port

### Transmit Path

1. Get transmit buffer: `_transmitter.TryGetNextBuffer()`
2. Build Ethernet header (swap source/dest MACs)
3. Build IPv4 header (swap source/dest IPs, calculate checksum)
4. Build UDP header (swap source/dest ports)
5. Copy original data payload
6. Send packet: `_transmitter.SendBuffer()` and `ForceFlush()`

## Technical Details

### UDP Header Structure

The `Udp` struct in `Magma.Transport.Udp` represents the 8-byte UDP header:

```
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Port | Destination Port |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Length | Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
```

- **Source/Destination Port**: 16-bit port numbers
- **Length**: Total length of UDP header + data (minimum 8 bytes)
- **Checksum**: Optional error checking (set to 0 in this sample)

### Performance Characteristics

- **Zero-copy**: Direct buffer manipulation using `Unsafe` and `Span<byte>`
- **Minimal allocations**: Packet buffers are pooled by NetMap
- **Kernel bypass**: NetMap bypasses the Linux network stack for lower latency

## Limitations

- **NetMap only**: Requires Linux with NetMap kernel module
- **No checksum validation**: UDP checksum is not validated on receive
- **No fragmentation**: Only handles packets that fit in a single Ethernet frame
- **Simplified error handling**: Production code would need more robust error checking

## Related Components

- `src/Magma.Transport.Udp` - UDP protocol implementation
- `src/Magma.Internet.Ip` - IPv4/IPv6 headers
- `src/Magma.Link` - Ethernet frame handling
- `src/Magma.NetMap` - NetMap transport integration

## See Also

- [RFC 768 - User Datagram Protocol](https://www.rfc-editor.org/rfc/rfc768)
- [NetMap - Fast packet I/O framework](http://info.iet.unipi.it/~luigi/netmap/)
- Other samples: `sample/Magma.NetMap.TcpHost`, `samples/Magma.NetMap.Host`
Loading