The Relay source is licensed under the MIT License. Third-party
license texts and distribution notes are in
THIRD_PARTY_LICENSES/.
go run . -port 50000go run . -port 50000 -no-cryptogo 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.
Set talk timeout by CLI flag:
go run . -port 50000 -talk-max-sec 60Or by environment variable:
INCOMUDON_TALK_MAX_SEC=60 go run . -port 50000Notes:
0disables timeout; non-zero values must be in the1..65535range.- If both are set,
-talk-max-sectakes precedence. - On channel join, server sends this value to clients via
pktServerCfgso clients can show remaining TX time. TALK_RELEASEcarries the Version 1 release reason, including client PTT off, server timeout, membership timeout, client leave, and optional admission or preemption policy outcomes.
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 50000Equivalent 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.
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 2Or use environment variables (CLI flags take precedence):
INCOMUDON_MULTI_TALK=true \
INCOMUDON_MAX_ACTIVE_TALKERS=2 \
go run . -port 50000Notes:
- Multi-talk is disabled by default; disabled mode always permits one talker.
INCOMUDON_MAX_ACTIVE_TALKERSand-max-active-talkersaccept1..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_GRANTpackets, so each stream starts with the correct decoder configuration.
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 50000INCOMUDON_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-xorCODEC_CONFIGvalues 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_KEYEach 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.secretFor 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.
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. Use0400or0600.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.keyFor 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 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 50000The 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_KEYFloor 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 50000An 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 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 50000kid,ed25519_public_key_base64
management-ed25519-2026-01,BASE64_ENCODED_32_BYTE_ED25519_PUBLIC_KEYThe 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,truemanagement-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,truemanagement-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 50000The 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.
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,trueThe 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 50000For 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,truecertificate_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 50000compose.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 -dThe 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.
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 50000For 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.
Run these commands from the server/ directory:
docker compose up --build -d
docker compose logs -f relayStop the relay with:
docker compose downcompose.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 -ddocker build -t incomudon-relay . --no-cache
docker run --rm -p 50000:50000/udp incomudon-relaydocker run --rm -p 50000:50000/udp incomudon-relay -no-crypto