Skip to content

RobarePruyn/UDPCaster

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

UDPCaster

A small, fast Linux service that receives unicast UDP on one interface and re-emits the payloads as multicast UDP out one or more different NICs. Built for broadcast plant / stadium / AV-over-IP work where a contribution feed lands on one network and needs to fan out onto isolated multicast distribution VLANs without dragging in a full media server.

Features

  • One worker thread per stream, blocking sockets, zero per-packet allocation. The async runtime (tokio) is only used for the web control plane — it never touches the hot path.
  • Multi-NIC fan-out — each stream can egress on one or many NICs simultaneously. You can even loop multicast back out the ingress interface.
  • Egress NIC pinned by interface name via IP_MULTICAST_IF. No reliance on routing-table tricks; the packets leave the NIC you named or the worker fails to start.
  • Tunable kernel socket buffers (SO_RCVBUF / SO_SNDBUF) for burst tolerance.
  • TOML config (hand-editable) plus a web UI at :8080 for add / edit / delete / toggle and a 1 Hz live stats view.
  • Multicast loopback is disabled on egress sockets so the listener never hears itself.

Quick Start

# Build (requires Rust 1.75+)
cargo build --release

# Run (creates config.toml on first launch)
./target/release/udpcaster

# Open the web UI
open http://localhost:8080

Build

Requires Rust 1.75+ (rustup default stable).

cargo build --release
# Binary: target/release/udpcaster

Docker

docker build -t udpcaster .
docker run --rm --net=host -v ./config.toml:/app/config.toml udpcaster

--net=host is required so the container sees the host's NICs and multicast works correctly.

Install (systemd)

# 1. Create the service user (no shell, no home).
sudo useradd --system --no-create-home --shell /usr/sbin/nologin udpcaster

# 2. Lay out the install directories.
sudo mkdir -p /opt/udpcaster /etc/udpcaster
sudo cp target/release/udpcaster /opt/udpcaster/
sudo cp -r static                /opt/udpcaster/
sudo chown -R udpcaster:udpcaster /opt/udpcaster /etc/udpcaster

# 3. Drop in the unit file and start.
sudo cp udpcaster.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now udpcaster
sudo systemctl status udpcaster

The web UI is now at http://<server>:8080. The config file lives at /etc/udpcaster/config.toml and is created on first run if missing.

Command-Line Options

Usage: udpcaster [OPTIONS]

Options:
  -c, --config <PATH>      Path to TOML config file [default: config.toml]
      --web-bind <ADDR>     Bind address for the web UI [default: 0.0.0.0:8080]
  -h, --help                Print help
  -V, --version             Print version

Kernel Tuning (important for high-throughput streams)

The rcvbuf_bytes / sndbuf_bytes values in each stream are silently capped by the kernel limits. Bump them once, system-wide:

# /etc/sysctl.d/99-udpcaster.conf
net.core.rmem_max     = 67108864
net.core.wmem_max     = 67108864
net.core.rmem_default = 33554432
net.core.wmem_default = 33554432
net.core.netdev_max_backlog = 5000
sudo sysctl --system

If you intend to forward multicast off the local subnet, also enable multicast routing on the egress NIC (smcrouted, pimd, etc.) — this relay only sends; it does not route.

Configuring a Stream

Either click + New Stream in the web UI, or hand-edit the config file:

[[streams]]
id = "cam1"
name = "Field Camera 1 -> Video Wall"
enabled = true

# Where the unicast feed arrives.
listen_addr = "10.20.0.5"     # IP of the ingress NIC, or 0.0.0.0
listen_port = 5004

# Where to fan it out.
multicast_group     = "239.10.20.30"
multicast_port      = 5004
egress_interfaces   = ["eth1", "eth2"]   # one or more NIC names
multicast_ttl       = 1                  # 1 = stay on the local subnet

# Burst headroom (capped by kernel sysctl).
rcvbuf_bytes = 8388608   # 8 MiB
sndbuf_bytes = 8388608

After a hand-edit, restart the service (sudo systemctl restart udpcaster). Edits made through the web UI are applied live with no restart.

Verifying

On the relay host, watch the counters in the UI, or:

# See the listener bound:
ss -uap | grep udpcaster

# Confirm packets land on the egress NIC:
sudo tcpdump -i eth1 -n host 239.10.20.30

On a downstream multicast receiver:

# Quick smoke test with socat:
socat -u UDP4-RECV:5004,ip-add-membership=239.10.20.30:eth0 -

Operational Notes

  • One ingress port per stream. Two streams cannot share the same (listen_addr, listen_port) pair — the second worker will fail to bind.
  • Disable a stream before changing its ports to avoid a brief bind-conflict during reconcile.
  • Stats are best-effort counters (Relaxed atomics). They are exact enough for health monitoring, not for billing.
  • MTU: this is a pure passthrough — whatever datagram size arrives is what goes out. Make sure both NICs and any switches in between agree on jumbo frames if you use them.
  • Security: the web UI has no auth. Put it behind a reverse proxy with basic auth, or bind it to 127.0.0.1 and SSH-tunnel, before exposing it on anything that isn't a trusted management VLAN.

Troubleshooting

Symptom Likely cause
Worker fails to start: unknown egress interface NIC name typo; check ip -br link.
Worker starts, counters increment, no packets on the wire Egress NIC has no IPv4 address — multicast needs a source IP. Assign one even if it's just a /31.
RX packets climb but TX errors climb too sndbuf capped by wmem_max; raise the sysctl.
Receivers see nothing despite tcpdump showing the packets IGMP snooping on the switch with no querier — enable an IGMP querier on that VLAN.

License

MIT

About

Application to consume unicast UDP sources and rebroadcast them.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors