DatagramTunneler
July 17, 2026 · View on GitHub
DatagramTunneler tunnels UDP multicast datagrams over a TCP connection. A client joins a multicast group, forwards received datagrams to a server, and the server republishes them on its own subnet.
Use it either from the command line or through the local browser dashboard. It supports current Linux, macOS, and Windows 10/11 releases, plus reusable named tunnel definitions.
CLI

Local dashboard

What it is for
Use DatagramTunneler when a multicast-dependent application needs to cross a routed, site-to-site, or test-network boundary. Typical uses include:
- relaying industrial, IoT, or monitoring telemetry to another network;
- feeding simulation, robotics, or hardware-in-the-loop test environments;
- testing multicast applications from a remote development or CI environment;
- sending one-way LAN-discovery announcements to a second subnet; and
- relaying a multicast feed to remote diagnostic, capture, or observability tooling.
DatagramTunneler and generic tunnels
DatagramTunneler solves a different problem from generic public-endpoint tools such as ngrok. It relays UDP multicast that the client can already receive; it does not expose a local service to the public Internet, perform NAT traversal, or provide a low-latency UDP transport. TCP's ordering and retransmission behaviour can make it unsuitable for latency-sensitive real-time game traffic.
It is a better fit for multicast-dependent workflows such as LAN discovery, telemetry, and cross-subnet testing.
The current tunnel is one-way from client to server and carries one client connection per process. Discovery protocols that require replies need a separate return path. The TCP connection is not encrypted or authenticated, so run it on a trusted network or inside a secure overlay such as WireGuard or SSH.
See the practical tutorials for multicast relaying, telemetry, and remote discovery-testing examples.
Install
Debian and Ubuntu
Check your Debian architecture:
dpkg --print-architecture
The latest published release is v2.1.0. It currently provides a Debian
package for AMD64 (amd64/x86_64):
curl -fLO https://github.com/MarkoPaul0/DatagramTunneler/releases/download/v2.1.0/dgramtunneler_2.1.0_amd64.deb
sudo apt install ./dgramtunneler_2.1.0_amd64.deb
dgramtunneler --version
| Debian architecture | CPU name | Package |
|---|---|---|
amd64 | Intel/AMD 64-bit (x86_64) | Available |
arm64, armhf, i386 | ARM or 32-bit Intel/AMD | Not currently published |
For an architecture without a .deb, use the source build.
Do not install the amd64 package on a different architecture. The
latest release
also contains portable Linux archives for the architectures listed there.
macOS and Linux: Homebrew
Install from the official tap:
brew tap MarkoPaul0/dgramtunneler
brew install dgramtunneler
Windows 10 and 11
The latest release currently provides a Windows AMD64 ZIP. Download and verify it from PowerShell:
$version = "2.1.0"
Invoke-WebRequest `
-Uri "https://github.com/MarkoPaul0/DatagramTunneler/releases/download/v$version/dgramtunneler-$version-Windows-AMD64.zip" `
-OutFile "dgramtunneler-$version-Windows-AMD64.zip"
Expand-Archive "dgramtunneler-$version-Windows-AMD64.zip" -DestinationPath dgramtunneler
& ".\dgramtunneler\dgramtunneler-$version-Windows-AMD64\bin\dgramtunneler.exe" --version
Add that bin directory to PATH for normal use. Windows ARM64, Winget,
Chocolatey, and Scoop packages are not published yet.
Build from source
Requirements: a C++20-capable compiler and CMake 3.16 or newer.
cmake -S . -B build-cmake
cmake --build build-cmake
cmake --install build-cmake --prefix ~/.local
CMake is the supported build and installation path.
First tunnel
Create and edit the configuration at the platform-default path:
dgramtunneler config init
dgramtunneler config edit
dgramtunneler tunnel list
Run the server first, then the client:
dgramtunneler tunnel run office-server
dgramtunneler tunnel run office-client
In a third terminal, generate five test datagrams for the client tunnel:
dgramtunneler producer office-client --count 5
Use dgramtunneler tunnel show <alias> to inspect the definition and dgramtunneler tunnel validate [alias] to check it before running.
producer <client-alias> sends Dummy datagram #1, Dummy datagram #2, and
so on to that client tunnel's configured multicast group. It defaults to one
datagram per second until interrupted. Use --count, --interval-ms, and
--payload-prefix to control a test run.
Named tunnels
Named definitions are stored in a versioned TOML configuration file. The default path is:
| Platform | Configuration path |
|---|---|
| macOS | ~/Library/Application Support/DatagramTunneler/config.toml |
| Linux | $XDG_CONFIG_HOME/dgramtunneler/config.toml, or ~/.config/dgramtunneler/config.toml |
| Windows | %APPDATA%\DatagramTunneler\config.toml |
Use --config <path> with any configuration or tunnel command to select another file.
udp_interface accepts either a local IPv4 address (for example,
192.168.1.20) or a local interface name such as en0, eth0, or Ethernet.
The name must resolve to an IPv4 address on the host running that tunnel.
version = 1
[tunnels.office-client]
mode = "client"
udp_interface = "192.168.1.20"
udp_group = "239.1.2.3:5000"
tcp_server = "192.168.1.10:14052"
[tunnels.office-server]
mode = "server"
udp_interface = "192.168.1.10"
tcp_listen_port = 14052
udp_destination = "replicate_client"
config edit creates the starter file if needed, then opens it using $VISUAL, $EDITOR, or TextEdit, vi, or Notepad. The parser rejects unknown fields, duplicate aliases, invalid IPv4 addresses, and invalid ports. tunnel run runs in the foreground.
Local control dashboard
Start the localhost-only control service to use the supported browser dashboard:
dgramtunneler control serve --port 8765
open http://127.0.0.1:8765
The dashboard provides tunnel lifecycle controls, a dummy-producer form, per-tunnel throughput and latency statistics, the ten most recent datagram observations, live events, and a TOML editor. See the dashboard guide.
It binds only to 127.0.0.1 and has no authentication, so do not expose it
through a proxy or port forward.
Local control API
The same localhost-only service exposes a versioned HTTP API and a WebSocket event stream for local automation and integrations. Start it with the command above, then use the OpenAPI definition to generate a client or import it into an API tool. The control API reference covers the event stream, error format, and compatibility policy.
Live terminal output
On an interactive terminal, tunnels and the built-in producer use the compact
live display by default: one statistics line and the five most recent events.
Use --verbose for conventional line-by-line diagnostics, including heartbeat
messages:
dgramtunneler tunnel run office-client --verbose
dgramtunneler producer office-client --verbose
The statistics show forwarded datagram count, average datagram size, and
average throughput since the tunnel started. On a v2 server, they also show a
rolling latency average, p50, p99, and maximum for the most recent 1,024
datagrams. The compact display activates only on an interactive terminal; when
output is redirected, normal line-oriented logs are kept so they remain easy to
capture and process. Its statistics line identifies the active client, server,
or producer route; event lines use short actions such as forwarded,
published, and sent rather than repeating the route for every datagram.
Latency telemetry
Each DTEP v2 datagram carries the client wall-clock timestamp in microseconds. The server prints its per-datagram tunnel latency and, every five seconds, reports interval average, p50, p99, and maximum latency. These figures require the client and server clocks to be reasonably synchronized (for example, with NTP); a client clock ahead of the server is reported as unavailable rather than as a misleading negative latency.
For server tunnels, udp_destination = "replicate_client" republishes to the
same multicast group and port joined by the client. Omitting udp_destination
retains the same legacy behaviour. Set it to an explicit IPv4:port endpoint
when the destination network should use a different group. Client-group
replication is for different source and destination subnets: using it on the
same subnet can create a multicast feedback loop. The server disables its own
local multicast loopback in replication mode, but it cannot prevent a separate
client host on the same subnet from receiving and re-forwarding the packet.
Direct command-line use
Start the server before the client.
dgramtunneler --server -i <udp_interface> -t <tcp_listen_port> [-u <udp_dst_ip>:<port>]
dgramtunneler --client -i <udp_interface> -t <tcp_srv_ip>:<tcp_srv_port> -u <udp_dst_ip>:<port>
The server's -u destination is optional: when absent, it republishes each datagram to the multicast group encoded by the client.
Create release packages
The root VERSION file is the release version. To create installable artifacts for the current platform:
cmake -S . -B build-cmake -DCMAKE_BUILD_TYPE=Release
cmake --build build-cmake
cpack --config build-cmake/CPackConfig.cmake -B release-artifacts
This produces .tar.gz and .zip archives named dgramtunneler-<version>-<system>-<architecture>. Linux also produces a Debian .deb package.
For Homebrew updates, use the tap release procedure.
Publish a release
Pushing a semantic-version tag automatically validates the matching VERSION file, builds and tests release packages on macOS, Ubuntu, and Windows, creates the GitHub Release, uploads the artifacts, and attaches a SHA256SUMS file.
When the Homebrew tap automation is configured, publishing the release also opens a reviewed formula-update pull request in the tap. See the tap release procedure for the one-time credential setup.
After merging the version bump to master:
git tag -a v<version> -m "DatagramTunneler <version>"
git push origin v<version>
The tag must exactly match VERSION: for example, VERSION 1.1.0 requires tag v1.1.0.
Tests
The test suite covers protocol framing, command and configuration parsing, named-tunnel commands, and a loopback multicast round-trip test when Python 3 is available.
cmake -S . -B build-cmake -DBUILD_TESTING=ON
cmake --build build-cmake
ctest --test-dir build-cmake --output-on-failure
How it works
DatagramTunneler transfers multicast traffic from a subnet where a multicast group is available to one where it is not. It has a client and a server:

Client
Run the client where the multicast group is joinable. It connects to the server over TCP, joins the UDP multicast group, and forwards each received datagram using DTEP.
Server
Run the server on the destination subnet. It accepts one client connection, then republishes each forwarded datagram to the configured UDP destination—or, when none is configured, to the group encoded in the DTEP packet. When either endpoint disconnects, both sides exit.
DTEP
DTEP (Datagram Tunneler Encapsulation Protocol) is the binary framing used over the TCP connection. The current protocol is version 2. Every frame starts with a one-byte packet type and a one-byte protocol version; a peer using a different version is rejected immediately with a clear error.
0x00 — Heartbeat
The client sends a two-byte type/version heartbeat to confirm that the connection remains alive.
0x01 — Datagram
This packet encapsulates a UDP datagram observed by the client. Its fixed 18-byte header contains the following fields before the original payload:
| Field | Size | Purpose |
|---|---|---|
| Type | 1 byte | 0x01 (datagram) |
| Protocol version | 1 byte | 0x02 |
| UDP group address | 4 bytes | IPv4 multicast group joined by the client |
| UDP group port | 2 bytes | Multicast port |
| Datagram length | 2 bytes | Original payload length |
| Client timestamp | 8 bytes | Unix epoch timestamp in microseconds |
- Datagram length: payload length, excluding the DTEP header.
- UDP channel address and port: the multicast group where the client received the datagram.
- Client timestamp: used by the server to calculate tunnel latency when both system clocks are synchronized.
- Encapsulated UDP datagram: the original UDP payload.
Licensing
See LICENSE.