Skip to content

About

UDP relay server for IncomUdon, a real-time push-to-talk voice communication system.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

35 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

IncomUdon Relay Server

License

The Relay source is licensed under the MIT License. Third-party license texts and distribution notes are in THIRD_PARTY_LICENSES/.

Local run

go run . -port 50000

No-crypto test mode

go run . -port 50000 -no-crypto

Packet logging

go run . -port 50000 -log-packets
go run . -port 50000 -log-packets -log-audio

-log-packets includes codec config details (codec_id, mode, pcm_only) when pktCodecConfig is received. Version 1 Relay traffic is limited to 1200-byte UDP datagrams. Oversize datagrams are discarded before parsing or forwarding; packet logging emits udp_datagram_drop for those drops and udp_size_near_limit for accepted media close to the limit.

Server-managed TX timeout

Set talk timeout by CLI flag:

go run . -port 50000 -talk-max-sec 60

Or by environment variable:

INCOMUDON_TALK_MAX_SEC=60 go run . -port 50000

Notes:

  • 0 disables timeout; non-zero values must be in the 1..65535 range.
  • If both are set, -talk-max-sec takes precedence.
  • On channel join, server sends this value to clients via pktServerCfg so clients can show remaining TX time.
  • TALK_RELEASE carries the Version 1 release reason, including client PTT off, server timeout, membership timeout, client leave, and optional admission or preemption policy outcomes.

Membership lease and keepalive

The Relay advertises an eight-byte SERVER_CONFIG after each successful JOIN. It includes a per-membership snapshot of the normal talk limit, multi-talker policy, membership lease, and idle keepalive interval.

INCOMUDON_MEMBERSHIP_LEASE_SEC=30 \
INCOMUDON_KEEPALIVE_INTERVAL_SEC=10 \
go run . -port 50000

Equivalent flags are -membership-lease-sec and -keepalive-interval-sec. The lease must be 15..300 seconds and keepalive must be 1..floor(lease/3) seconds. Defaults are 30 and 10 seconds. Existing members retain the timing snapshot accepted at JOIN; a later configuration change applies only to a new JOIN.

Only accepted KEEPALIVE, CODEC_CONFIG, PTT control, and authorized active AUDIO/FEC refresh membership. PING/PONG are RTT probes and never extend the membership deadline.

Simultaneous transmit (multi-talk)

The relay remains the authority for PTT admission. Enable simultaneous transmit and set the channel-wide talker limit with CLI flags:

go run . -port 50000 -multi-talk -max-active-talkers 2

Or use environment variables (CLI flags take precedence):

INCOMUDON_MULTI_TALK=true \
INCOMUDON_MAX_ACTIVE_TALKERS=2 \
go run . -port 50000

Notes:

  • Multi-talk is disabled by default; disabled mode always permits one talker.
  • INCOMUDON_MAX_ACTIVE_TALKERS and -max-active-talkers accept 1..16.
  • The relay sends the enabled flag and maximum to each joining client via pktServerCfg.
  • A late-joining client receives the current talkers' cached codec settings before their TALK_GRANT packets, so each stream starts with the correct decoder configuration.

Control Authentication v1

The relay supports the v0.7 Control Authentication v1 handshake and gates AES-GCM v2 media on a verified, per-channel control session. It validates authenticated AUTH_HELLO / AUTH_CHALLENGE / JOIN traffic, performs the 64-counter control replay check across provisional pre-JOIN state and the joined session, caches only verified 19-byte CODEC_CONFIG payloads, and re-signs Relay-generated control packets separately for every recipient. Relay-generated packets use an independent fixed-header sequence space and a non-wrapping Relay control nonce domain. Media remains end-to-end protected and is forwarded byte-for-byte; the receiving client remains responsible for AEAD authentication and media replay detection.

Configure it with these environment variables or their CLI equivalents:

INCOMUDON_CONTROL_AUTH_POLICY=required \
INCOMUDON_CONTROL_KEY_FILE=./control/control-keys.csv \
INCOMUDON_CONTROL_COOKIE_SECRET_FILE=./control/cookie.secret \
go run . -port 50000

INCOMUDON_CONTROL_AUTH_POLICY accepts:

  • off: preserve no-crypto or legacy compatibility control behavior; AES-GCM v2 media is rejected.
  • optional: require authentication only for channels listed in the key CSV.
  • required: require authenticated control traffic and the AES-GCM v2 media profile for every channel. No-crypto and legacy-xor CODEC_CONFIG values are rejected even when their control HMAC is valid.

The key CSV is intentionally not stored in source control:

channel_id,key_id,control_key_base64
100,1,BASE64_ENCODED_32_BYTE_CONTROL_KEY

Each row contains a non-zero key_id and an exact 32-byte Base64-decoded Control Authentication key. The value must be the channel's derived control_key defined by the protocol; do not place the human channel password in this file. The Relay-only cookie secret is at least 32 random bytes, either raw or Base64 encoded. For example:

openssl rand -base64 32 > control/cookie.secret

For Docker Compose, place the files under INCOMUDON_CONTROL_DATA_DIR (default ./control) and use container paths in .env:

INCOMUDON_CONTROL_AUTH_POLICY=required
INCOMUDON_CONTROL_DATA_DIR=./control
INCOMUDON_CONTROL_KEY_FILE=/run/incomudon-control/control-keys.csv
INCOMUDON_CONTROL_COOKIE_SECRET_FILE=/run/incomudon-control/cookie.secret

-no-crypto cannot be combined with required policy. Endpoint-originated packets with sender_id = 0 are rejected before they can create membership, talk, or authentication state; zero remains reserved for Relay/System packets.

Secret file handling

Relay credential directories in compose.yaml and compose.management.yaml are bind-mounted read-only. The Private Control Link state directory and the bundled Management Service command-store directory are intentional separate writable mounts: the former holds Relay durable deny rules and idempotency acknowledgements, and the latter holds Management-to-Relay revocation retries. The Relay also checks the private inputs it reads: Control Authentication keys and cookie secret, Directory channel-key CSV, Management Plane signing/TLS private keys, and the Private Control Link TLS private key when the mTLS TCP profile is selected.

INCOMUDON_SECRET_FILE_PERMISSIONS (or -secret-file-permissions) controls the startup check:

  • required (the Compose default): on Unix, the path must be a regular file, owner-readable, and have no group or other permissions. Use 0400 or 0600.
  • warn: log a validation failure and continue. Use only for local development when the platform cannot expose POSIX permissions.
  • off: disable the check; intended only for tests or explicitly accepted legacy environments.

The container runs as UID/GID 10001. On Linux hosts, make the secret directories private and give this account ownership before starting Compose:

install -d -m 0700 -o 10001 -g 10001 control directory-v3 management private-control private-control-state
install -d -m 0700 -o 10002 -g 10002 management-state
chown 10001:10001 control/control-keys.csv control/cookie.secret \
  directory-v3/directory-keys.csv management/signing-key.csv management/server.key
chmod 0600 control/control-keys.csv control/cookie.secret \
  directory-v3/directory-keys.csv management/signing-key.csv management/server.key

For the optional PCL mTLS TCP profile, protect private-control/server.key with the same owner and 0600 mode. The bundled UDS profile has no PCL private key; its socket volume is initialized separately by Compose.

Docker Desktop bind mounts on Windows do not reliably preserve Unix mode bits. For local Windows development, set INCOMUDON_SECRET_FILE_PERMISSIONS=warn. Production deployments should run on a Linux host or use a secret-delivery mechanism that preserves the equivalent ownership and access restrictions.

Identity Admission and Floor Interrupt

Identity Admission v1 verifies compact Ed25519-signed admission tickets from the configured Access Service. Every IDENTITY_* packet uses the existing Control Authentication session; a successful proof binds the ticket to the client UDP endpoint, its sender ID, and its Ed25519 public key. required mode denies JOIN, PTT, and media forwarding until the endpoint has a current admission. optional mode preserves legacy JOIN behavior, but enforces the permissions of a ticket once a client presents one.

INCOMUDON_CONTROL_AUTH_POLICY=required \
INCOMUDON_IDENTITY_ADMISSION_MODE=required \
INCOMUDON_IDENTITY_ISSUER=https://access.example.example \
INCOMUDON_IDENTITY_AUDIENCE=incomudon-relay-production \
INCOMUDON_IDENTITY_SIGNING_KEY_FILE=./identity/signing-keys.csv \
go run . -port 50000

The signing-key CSV is Relay-local configuration and supports overlapping key rotation:

kid,ed25519_public_key_base64
access-ed25519-2026-01,BASE64_ENCODED_32_BYTE_ED25519_PUBLIC_KEY

Floor Interrupt v1 is disabled by default. Enable it only together with Identity Admission required and Control Authentication required:

INCOMUDON_FLOOR_INTERRUPT_ENABLED=true go run . -port 50000

An authenticated PTT_REQUEST is granted only when the current ticket has listen, talk, and interrupt permissions with a non-zero admission-derived priority. On a full channel, the Relay preempts exactly one lowest-priority talker (lower sender_id breaks equal-priority ties), broadcasts an authenticated TALK_RELEASE(PREEMPTED), and then grants the requester. The request packet never carries a caller-selected priority.

Managed Service Admission and Management Plane

Managed Service Admission v1 is disabled by default. It lets a recorder, observer, or automation service complete the normal authenticated JOIN flow with a short-lived, Management Service-issued Ed25519 grant and a proof-of-possession signature. It never replaces the channel credential, Control Authentication, media authentication, membership lease, or ordinary floor policy.

Enable the Relay-side verifier with a Control Authentication key and a public signing-key CSV:

INCOMUDON_CONTROL_AUTH_POLICY=required \
INCOMUDON_SERVICE_ADMISSION_MODE=enabled \
INCOMUDON_SERVICE_ADMISSION_ISSUER=https://management.example.example \
INCOMUDON_SERVICE_ADMISSION_AUDIENCE=incomudon-relay-production \
INCOMUDON_SERVICE_ADMISSION_SIGNING_KEY_FILE=./service-admission/verification-keys.csv \
go run . -port 50000
kid,ed25519_public_key_base64
management-ed25519-2026-01,BASE64_ENCODED_32_BYTE_ED25519_PUBLIC_KEY

The optional Management Plane is a separate mTLS HTTPS listener for the canonical /v1 management API. Bind it only to an administration network, VPN, or private interface; do not expose it alongside the public UDP relay port. The Compose file intentionally does not publish a management TCP port. Publish one only through a private network or reverse proxy with the same mTLS boundary.

Direct single-process Management Plane mode requires the Relay service admission verifier to trust the public half of the configured Management signing key. It loads the canonical Management service and channel ACL CSVs:

management-services.csv:

service_id,certificate_sha256,api_role,enabled
recorder-east,LOWERCASE_SHA256_OF_DER_CLIENT_CERTIFICATE,recorder,true

management-channel-acl.csv:

service_id,channel_id,sender_id,admission_role,allow_listen,allow_talk,allow_interrupt,interrupt_priority,enabled
recorder-east,111,9001,recorder,true,false,false,0,true

management-global-permissions.csv is an implementation-specific optional file for explicit non-channel permissions. Its strict format is service_id,permission,enabled; this Relay recognizes only health.read and service_admission.revoke. The private signing-key file is likewise Relay-local and has the strict header kid,ed25519_private_key_base64; it contains one standard Base64 Ed25519 seed (32 bytes) or private key (64 bytes), and must be protected as a secret.

INCOMUDON_MANAGEMENT_ENABLED=true \
INCOMUDON_MANAGEMENT_LISTEN=127.0.0.1:8443 \
INCOMUDON_MANAGEMENT_CERT_FILE=./management/server.crt \
INCOMUDON_MANAGEMENT_KEY_FILE=./management/server.key \
INCOMUDON_MANAGEMENT_CLIENT_CA_FILE=./management/client-ca.crt \
INCOMUDON_MANAGEMENT_SERVICES_CSV=./management/management-services.csv \
INCOMUDON_MANAGEMENT_CHANNEL_ACL_CSV=./management/management-channel-acl.csv \
INCOMUDON_MANAGEMENT_SIGNING_KEY_FILE=./management/signing-key.csv \
go run . -port 50000

The embedded Relay listener advertises event_delivery: "live" by default and audit_retrieval: false. It sends redacted SSE events only to currently connected authorized subscribers, does not retain replay history, ignores a standard SSE Last-Event-ID during reconnect, rejects an explicit since query parameter, and returns 404 for /v1/audit-records. Set INCOMUDON_MANAGEMENT_EVENT_DELIVERY=disabled to omit SSE entirely. A deployment that needs replay-capable SSE, Audit Retrieval, recording-job control, or durable revocation integration must use an external Management Service behind the private management boundary. The embedded listener does not expose /v1/recording-jobs: it never creates a Recorder Worker or represents a local in-memory job as an active recording.

Private Control Link Event Export

The optional Private Control Link is a dedicated authenticated Relay-control listener for a Management Service. It is distinct from the Management Plane HTTPS API. This Relay implements the Linux UDS peer-credential and mTLS TCP profiles of Private Control Link v1; choose exactly one at startup. UDS is the recommended same-host profile, while mTLS TCP must use a separate private listener address for cross-host deployments.

A Management Service starts a private-control-link-v1 session with hello. The Relay independently accepts requested lifecycle-event, audit-input, and diagnostics capabilities, then uses bounded, best-effort queues for redacted outbound notifications. Diagnostics negotiation is mandatory in the hello wire shape. When requested, this Relay accepts get_relay_diagnostics and returns a process-local, redacted Floor Interrupt counter snapshot; diagnostic reads are limited to one accepted request per session every ten seconds. It neither retains nor replays notifications or diagnostic snapshots, and never assigns external SSE cursor IDs. A full queue drops the affected notification rather than delaying media forwarding; the Management Service owns any durable SSE, audit, or metric storage.

The Relay exports participant_joined, participant_left, talk_started, talk_ended, service_admission_issued, service_admission_revoked, and an initial relay_health_changed event when lifecycle events are accepted. When audit inputs are accepted, admission-identified Floor Interrupt requests also emit redacted relay_audit_input messages.

The authenticated Management Service may send a channel-scoped revoke_service_admission command with an absolute UTC deny_until timestamp. The Relay installs its bounded deny rule before acknowledging it, removes only memberships matching every supplied target field, and releases matching active talkers with SERVICE_ADMISSION_REVOKED. An already elapsed deadline returns already_expired; a deadline more than 90 minutes ahead is rejected. Deny rules and duplicate-command acknowledgements are persisted before an applied acknowledgement is released, so the original acknowledgement and deadline are restored after a Relay restart. Reuse of a message ID with a different body closes the link.

When Managed Service Admission is enabled, INCOMUDON_PRIVATE_CONTROL_STATE_FILE is required. Its parent directory must be writable only by the Relay and must not be the read-only credential directory. An event-export-only PCL deployment without Managed Service Admission does not require a state file.

On Unix, the default required secret-file policy also requires the state directory to be Relay-owned and mode 0700, and an existing state file to be Relay-owned, regular, and mode 0600. The Relay synchronizes the state file and its directory before acknowledging a revocation.

Stateful Service Admission revocation therefore requires a non-Windows deployment and a filesystem that supports directory synchronization. On Windows, the Relay cannot provide portable directory synchronization and refuses to start PCL when Managed Service Admission is enabled. Event-export- only PCL remains available without a state file.

Set INCOMUDON_PRIVATE_CONTROL_TRANSPORT to exactly one profile. The Relay does not fall back between profiles.

For same-host deployments, uds is the recommended profile. The Relay derives the Management Service identity from Linux SO_PEERCRED; the hello management_service_id is only a consistency check. Map each non-root Management Service UID with a separate strict CSV policy file:

management_service_id,uid,enabled
management-main,10002,true

The socket directory must be Relay-owned, non-symlinked, and neither group- nor world-writable. The Relay creates relay.sock with mode 0660 and the configured shared group. The Management Service needs group write access to the socket, but must not be able to modify its parent directory. The UDS profile is available only on Linux; deployments without reliable peer credentials must use the mTLS TCP profile instead.

INCOMUDON_PRIVATE_CONTROL_ENABLED=true \
INCOMUDON_PRIVATE_CONTROL_TRANSPORT=uds \
INCOMUDON_PRIVATE_CONTROL_UDS_SOCKET_PATH=/run/incomudon-pcl/relay.sock \
INCOMUDON_PRIVATE_CONTROL_UDS_SOCKET_GROUP=10003 \
INCOMUDON_PRIVATE_CONTROL_UDS_SERVICES_CSV=./private-control/uds-services.csv \
INCOMUDON_PRIVATE_CONTROL_RELAY_ID=relay-production-east-1 \
INCOMUDON_PRIVATE_CONTROL_STATE_FILE=./private-control-state/state.json \
go run . -port 50000

For a Management Service on another host, use mtls-tcp and authorize client certificates with a separate strict CSV policy file:

management_service_id,certificate_sha256,enabled
management-main,LOWERCASE_SHA256_OF_DER_CLIENT_CERTIFICATE,true

certificate_sha256 is the lowercase SHA-256 digest of the complete DER client certificate. The management_service_id in the authenticated hello must match this certificate mapping. An example is available at private-control/services.csv.example.

INCOMUDON_PRIVATE_CONTROL_ENABLED=true \
INCOMUDON_PRIVATE_CONTROL_TRANSPORT=mtls-tcp \
INCOMUDON_PRIVATE_CONTROL_LISTEN=127.0.0.1:9443 \
INCOMUDON_PRIVATE_CONTROL_CERT_FILE=./private-control/server.crt \
INCOMUDON_PRIVATE_CONTROL_KEY_FILE=./private-control/server.key \
INCOMUDON_PRIVATE_CONTROL_CLIENT_CA_FILE=./private-control/client-ca.crt \
INCOMUDON_PRIVATE_CONTROL_SERVICES_CSV=./private-control/services.csv \
INCOMUDON_PRIVATE_CONTROL_RELAY_ID=relay-production-east-1 \
INCOMUDON_PRIVATE_CONTROL_STATE_FILE=./private-control-state/state.json \
go run . -port 50000

Bundled Management Service Container

compose.management.yaml starts the separately built Management Service image alongside the Relay, while keeping the source repositories and container privileges separate. It uses the UDS profile by default: the PCL has no TCP port, and the Relay's UDP media port remains the only port published by the base Compose file.

The bundled Management Service provides live-only SSE, fresh Relay state from PCL snapshots, and Managed Service Admission grant and revocation handling. It does not currently provide replay SSE, Audit Retrieval, or recording orchestration.

Prepare the UDS service policy and the separate writable Relay and Management state directories before starting the overlay:

private-control/                 # read-only in the Relay
  uds-services.csv

private-control-state/           # writable only by the Relay (UID/GID 10001)
  state.json                     # created automatically

management-state/                # writable only by Management (UID/GID 10002)
  pcl-revocations.json            # created automatically; durable retry outbox

The overlay's one-shot private-control-socket-init service initializes the shared volume as 10001:10003 with mode 0750. Relay runs as UID 10001 and creates the socket for group 10003; the Management Service runs as UID 10002 with that supplementary group. Keep these IDs and the uds-services.csv mapping aligned. The shared socket volume is read-write so the Management Service can connect, but it contains no credentials and its directory is not writable by that service. The separate management-state/ mount is required: it stores pending PCL revocation commands before they are sent and removes them only after the Relay acknowledgement is durably handled.

After copying the required configuration from .env.example, start both containers together:

docker compose -f compose.yaml -f compose.management.yaml up -d

The default image is the Management repository's main image. Set INCOMUDON_MANAGEMENT_IMAGE to a released tag before production deployment. The Management Service reconnects with backoff, so Compose start order is not used as a readiness guarantee.

Directory UDP

This Relay implements optional Directory UDP v3. It is disabled by default and supports only the current v3 wire format; the removed v1 shared-PSK and v2 formats are neither accepted nor transmitted.

When enabled, the default media-port transport uses the Relay UDP port and the authenticated IDP3 || 0x01 || JSON carrier. Directory processing has bounded worker, request, and response budgets, so it is best effort and cannot delay media or ordinary control packets. No extra Docker port is required for this default transport.

Each configured channel requires static metadata and a Relay-local directory_channel_key:

directory-v3/directory-keys.csv
channel_id,directory_channel_key_base64url
111,<canonical-unpadded-base64url-32-byte-key>

The key is the 32-byte directory_channel_key derived from the channel's password_key using HKDF-SHA-256 with info incomudon-directory-channel-v3. Provision this derived key through an offline credential-management workflow. The Relay deliberately does not accept raw channel credentials or password_key values for Directory configuration. Use directory-v3/channels.csv.example, directory-v3/speakers.csv.example, and directory-v3/directory-keys.csv.example as format references; keep the real key file outside version control.

INCOMUDON_DIRECTORY_ENABLED=true \
INCOMUDON_DIRECTORY_KEY_FILE=./directory-v3/directory-keys.csv \
INCOMUDON_DIRECTORY_CHANNELS_CSV=./directory-v3/channels.csv \
INCOMUDON_DIRECTORY_SPEAKERS_CSV=./directory-v3/speakers.csv \
go run . -port 50000

For operationally isolated Directory traffic, select INCOMUDON_DIRECTORY_TRANSPORT=dedicated-udp and set INCOMUDON_DIRECTORY_DEDICATED_LISTEN, for example :51000. The dedicated listener carries raw v3 JSON and must be exposed explicitly by the deployment; the default compose.yaml intentionally does not publish that optional port.

Docker Compose

Run these commands from the server/ directory:

docker compose up --build -d
docker compose logs -f relay

Stop the relay with:

docker compose down

compose.yaml publishes UDP port 50000 by default and passes the relay's server configuration through environment variables. Create a .env file next to compose.yaml when persistent configuration is required; the available keys and defaults are documented in .env.example. It does not publish the optional Management Plane HTTPS port.

For example, to allow two simultaneous transmitters and set a 60-second TX timeout:

INCOMUDON_MULTI_TALK=true \
INCOMUDON_MAX_ACTIVE_TALKERS=2 \
INCOMUDON_TALK_MAX_SEC=60 \
docker compose up --build -d

Docker (without Compose)

docker build -t incomudon-relay . --no-cache
docker run --rm -p 50000:50000/udp incomudon-relay
docker run --rm -p 50000:50000/udp incomudon-relay -no-crypto

About

UDP relay server for IncomUdon, a real-time push-to-talk voice communication system.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages