This document defines version 1 of the Nowhere proxy protocol and the Portal configuration required by the reference implementation.
Nowhere carries authenticated TCP and UDP proxy traffic over TLS/TCP or QUIC.
TCP uses a dedicated TLS/TCP connection or a QUIC bidirectional stream. UDP
uses QUIC DATAGRAM frames or a length-prefixed UDP-over-TCP (UoT) flow. A shared
key authenticates each transport connection, while a deterministic spec
value selects the authentication shape, padding, and field layout.
The key words MUST, MUST NOT, REQUIRED, SHOULD, SHOULD NOT, and MAY are to be interpreted as normative requirements.
- All integer fields are unsigned and use network byte order unless stated otherwise.
u8,u16, andu64denote 1-, 2-, and 8-byte unsigned integers.||denotes byte concatenation with no separator.- Text converted to bytes uses UTF-8.
- Lengths are measured in bytes after URL percent decoding.
SHA-256(x)denotes the 32-byte SHA-256 digest ofx.HMAC-SHA256(k, x)denotes HMAC-SHA256 with keykand messagex.HKDF-Extract(salt, ikm)andHKDF-Expand(prk, info, length)use SHA-256 as specified by HKDF. Labels shown in quotation marks are their literal ASCII bytes and do not include a terminating zero byte.
The Portal is configured with one URL:
portal://<shared-key>@<listen-host>:<listen-port>?log=<level>&tls=<mode>&crt=<path>&key=<path>&net=<mode>&spec=<spec>&alpn=<alpn>&rate=<mbps>&etar=<mbps>&dial=<ip-or-auto>&socks=<proxy>
Minimal configuration:
portal://secret@:2077
The URL username is the shared key. A password component is not supported and MUST be rejected. The listen port and a non-empty shared key are required. Unknown query parameters are ignored.
The shared key, spec, and alpn are percent-decoded as UTF-8. A literal +
in spec or alpn remains +; it is not converted to a space. If a query key
occurs more than once, the first occurrence is used. Duplicate socks
parameters MUST be rejected.
| Input | Requirement | Decoded UTF-8 byte length |
|---|---|---|
| Shared key | Required and non-empty | 1..255 |
spec |
Optional; empty is treated as omitted | 1..255 when non-empty |
alpn |
Optional; empty is treated as omitted | 1..255 when non-empty |
| SOCKS username | Required when SOCKS authentication is configured | 1..255 |
| SOCKS password | Required when SOCKS authentication is configured | 1..255 |
| Parameter | Default | Semantics |
|---|---|---|
log |
info |
none, debug, info, warn, error, or event. An unknown value selects info. |
tls |
1 |
1 creates a self-signed certificate. 2 loads PEM files from crt and key. Other values are invalid. |
crt |
Empty | PEM certificate chain used by tls=2. |
key |
Empty | PEM private key used by tls=2. |
net |
mix |
Selects ingress transports. tcp enables TLS/TCP, udp enables QUIC/UDP, and mix enables both. Missing and empty values select mix; other values are invalid. |
spec |
auto |
Deterministic seed for v1 authentication, padding, and frame layouts. |
alpn |
now/1 |
QUIC/TLS ALPN override. It does not alter any other protocol field. |
rate |
0 |
Client-to-target rate limit in Mbps. |
etar |
0 |
Target-to-client rate limit in Mbps. |
dial |
auto |
Local IP literal for outbound TCP and UDP sockets. Empty, invalid, hostname, and auto values select the operating-system default. |
socks |
none |
SOCKS5 proxy as host:port or user:pass@host:port. Missing, empty, and none disable proxying. |
rate and etar accept positive decimal integers. Zero, a negative value, an
invalid value, or omission disables the corresponding limit. The conversion is:
bytes_per_second = mbps * 125000
The rate limiter is shared by all sessions handled by one Portal process. It has independent client-to-target and target-to-client buckets.
net does not select the proxied traffic type. TLS/TCP supports ordinary TCP
relay and UoT. QUIC supports TCP relay on bidirectional streams and UDP relay
in DATAGRAM frames.
When socks is configured, every target MUST use that proxy. TCP uses SOCKS5
CONNECT. Each UDP flow uses a separate UDP ASSOCIATE and retains its control
connection for the flow lifetime. Target domain names are encoded in SOCKS5
requests and resolved by the proxy. Proxy failure MUST NOT fall back to direct
target access. dial binds connections and relay sockets toward the proxy.
An empty listen host binds separate IPv4 and IPv6 wildcard sockets on the same
port for each transport selected by net. 0.0.0.0 binds only IPv4 and [::]
binds only IPv6. An IP literal binds that address. A hostname is resolved and
the first resolved address is used. All selected sockets MUST bind before the
Portal begins accepting traffic.
Both peers MUST build the same effective protocol spec before establishing a session.
effective_spec = decoded first `spec` value when non-empty, otherwise "auto"
effective_alpn = decoded first `alpn` value when non-empty, otherwise "now/1"
effective_spec and effective_alpn have independent roles. Changing spec
does not change ALPN. Changing alpn does not change authentication, padding,
or frame layout.
The shared key also does not alter the spec-derived shape. It is used only to derive the authentication key.
spec_bytes = UTF8(effective_spec)
spec_salt = SHA-256(spec_bytes)
spec_prk = HKDF-Extract(spec_salt, spec_bytes)
derive(label, length) = HKDF-Expand(spec_prk, UTF8(label), length)
The following outputs are defined by v1:
spec_id = base64url-no-pad(derive("spec id", 8))
auth_magic = derive("auth magic", 8)
auth_info = derive("auth hmac info", 32)
auth_context = derive("auth context", 32)
auth_layout_seed = derive("auth frame layout", 8)
proxy_layout_seed = derive("proxy frame layout", 8)
auth_padding_len_seed = derive("auth padding length", 2)
auth_padding_key = derive("auth padding key", 32)
tcp_padding_len_seed = derive("tcp request padding length", 1)
tcp_padding_key = derive("tcp request padding key", 32)
spec_id is a diagnostic and conformance identifier. It is not transmitted in
a v1 protocol frame.
v1 uses a deterministic Fisher-Yates shuffle. Given an array A, a seed, and
an initial seed offset:
for i from len(A) - 1 down to 1:
k = initial_offset + (len(A) - 1 - i)
j = seed[k] mod (i + 1)
swap A[i] and A[j]
The three layouts are derived as follows:
| Layout | Initial array | Seed offset |
|---|---|---|
| Authentication | [magic, nonce, padding, tag] |
0 in auth_layout_seed |
| TCP request | [version, target, padding] |
0 in proxy_layout_seed |
| UDP header | [version, type, flow_id, target] |
1 in proxy_layout_seed |
If the authentication shuffle produces its initial array unchanged, the result
MUST be rotated left once to [nonce, padding, tag, magic]. TCP and UDP layouts
do not apply this rotation.
All peers MUST implement these algorithms exactly. Derivation MUST NOT depend on the shared key, wall-clock time, randomness, locale, platform integer width, map iteration order, or transport-library defaults.
Nowhere v1 supports TLS 1.3 over TCP and QUIC over UDP. Both transports use the
same certificate and advertise exactly one ALPN value, effective_alpn.
For QUIC, the Portal:
- advertises exactly one ALPN value:
effective_alpn; - enables QUIC DATAGRAM;
- requires address validation with QUIC Retry before accepting a connection;
- uses BBR congestion control;
- initially advertises one bidirectional stream and 64 KiB connection-level receive credit;
- after authentication, raises the bidirectional-stream limit to
NOW_QUIC_MAX_STREAMSand the connection-level receive credit to 32 MiB; - advertises 16 MiB per-stream receive credit;
- permits up to 32 MiB of unacknowledged stream data per connection;
- does not accept unidirectional streams;
- uses
NOW_UDP_IDLE_TIMEOUTas the QUIC idle timeout; and - does not send transport-level keepalive packets.
The Portal explicitly disables TLS 1.3 early data and half-RTT server data for both QUIC and TLS/TCP. Application data is therefore never accepted as 0-RTT.
A QUIC client MUST offer exactly one ALPN value equal to effective_alpn,
enable QUIC DATAGRAM, and support bidirectional streams. A TCP client MUST
negotiate TLS 1.3 with the same ALPN. An ALPN mismatch is a
connection-establishment failure. Plaintext TCP is not supported.
tls=1 generates a new self-signed certificate for localhost when the Portal
starts. Client trust for this mode is an application decision and MUST be
explicit.
tls=2 loads a PEM certificate chain from crt and a PEM private key from
key. Both files must be valid when the Portal starts. On a ClientHello, the
Portal reloads them if NOW_RELOAD_INTERVAL has elapsed. A successful reload
replaces the cached certificate. A failed reload is logged and the previous
certificate remains active.
Clients using tls=2 SHOULD apply normal platform certificate and server-name
verification. TLS trust policy and SNI do not change the v1 wire format.
After the QUIC handshake:
- The client opens the first bidirectional stream as the authentication stream.
- The client writes exactly one v1 authentication frame and finishes its send side.
- The authentication frame binds the connection to a client-generated 128-bit logical session ID.
- After successful authentication, each additional bidirectional stream may carry one TCP relay or one half of an asymmetric flow.
- UDP relay traffic may use QUIC DATAGRAM frames for the lifetime of the authenticated connection.
The Portal samples one absolute authentication deadline after the QUIC or TLS
handshake completes. The deadline is NOW_HANDSHAKE_TIMEOUT multiplied by a
system-random factor in [0.8, 1.2]; if randomness is unavailable, the
configured timeout is used unchanged. For QUIC, waiting for the authentication
stream and reading its complete frame share this single deadline. No target
traffic is forwarded before authentication succeeds.
QUIC DATAGRAM frames that arrive before authentication are actively drained. The Portal retains at most 64 KiB in aggregate and delivers those retained frames to the session after successful authentication. Excess frames and all retained frames from a failed authentication are discarded.
For TLS/TCP, each proxy flow uses a new connection:
- The client completes TLS 1.3.
- The client sends exactly one v1 authentication frame.
- The client sends one v1 TCP request frame immediately or after keeping the authenticated connection idle in a client-side warm pool.
- A normal target selects TCP relay and all remaining bytes are raw
client-to-target TCP payload. The reserved target
uot.nowhere.invalid:0selects UoT and the client continues with the setup and packet frames defined in Section 9.2.
There is no authentication response, post-relay connection reuse, or multiplexing on the TCP transport. A pooled connection is consumed by its first request and carries exactly one TCP relay or one UoT flow. Connections sharing an authenticated session ID may form one asymmetric flow. The Portal never associates them by source IP, so TCP and QUIC may arrive through different interfaces or NAT mappings. An authenticated connection that sends no request frame is closed after 40 seconds.
For a tcp/tcp client with TLS preconnection enabled, the reference client
starts one warm connection on a cold pool. Consuming a warm connection starts up to two
replacements without allowing idle plus in-progress connections to exceed the
configured pool limit. Each unconsumed slot expires 30 seconds after it is
created; expiration does not trigger a replacement. A client pool value of 0
disables this warm pool and opens TLS/TCP lanes on demand. Any carrier matrix
containing UDP disables the warm pool; both QUIC and TLS/TCP halves are then
created lazily.
The credential key is:
auth_key = SHA-256(shared_key_bytes)
The client supplies a 32-byte nonce and a 16-byte random session_id. It SHOULD
generate a fresh, cryptographically random nonce for each connection and one
session ID for each transport bundle; every QUIC session and TLS/TCP lane in
that bundle carries the same session ID.
auth_padding_len = 1 + (u16be(auth_padding_len_seed) mod 255)
auth_padding = HKDF-Expand(
auth_padding_key,
UTF8("auth padding bytes") || nonce || u8(auth_padding_len),
auth_padding_len
)
auth_tag = HMAC-SHA256(
auth_key,
auth_info || auth_context || nonce ||
u8(auth_padding_len) || auth_padding || session_id
)
auth_padding_len is in the range 1..255.
The authentication frame contains four elements in the derived authentication order followed by the fixed session ID:
| Element | Encoding |
|---|---|
magic |
auth_magic (8 bytes) |
nonce |
32 bytes |
padding |
`auth_padding_len_u8 |
tag |
auth_tag (32 bytes) |
session_id |
16 bytes, appended after the shuffled elements |
The complete frame length is 90..344 bytes. The receiver MUST verify the
frame length, magic, declared padding length, deterministic padding bytes, and
HMAC tag. A QUIC authentication stream MUST end immediately after the frame. A
TLS/TCP lane reads exactly the authentication frame length and treats following
bytes as its request frame. Tag and padding comparisons SHOULD be constant-time.
Correct authentication proceeds immediately. A missing stream, truncated
frame, EOF, missing FIN, trailing bytes, or invalid field, padding, or HMAC is
held until the sampled absolute deadline. The reference Portal then closes
QUIC with application code 0x01 and reason access denied, or closes TCP
without an application response. Network closure is initiated before detailed
failure information is written to the Portal's local log. Service shutdown
cancels the delay.
If a newly authenticated QUIC connection presents the session_id of an
older active QUIC connection, the Portal replaces the older carrier. This
permits recovery from a client-side path failure while the stale server-side
connection is still waiting for its idle timeout. Existing flows on the older
carrier are not migrated.
The Portal applies a process-wide pre-authentication admission limit shared by
TCP and QUIC: at most 256 connections in total and 32 per IPv4 /32 or IPv6
/64. A validated QUIC attempt above either limit is silently ignored; an
accepted TCP connection above either limit is immediately closed. A slot is
released as soon as authentication succeeds or fails.
Each TCP relay uses either one new bidirectional QUIC stream or one dedicated TLS/TCP connection. In both cases the client writes the same request frame, followed immediately by raw client-to-target TCP bytes.
tcp_padding_len = tcp_padding_len_seed[0] mod 64
tcp_padding = HKDF-Expand(
tcp_padding_key,
UTF8("tcp request padding bytes") || target_utf8 ||
u8(tcp_padding_len),
tcp_padding_len
)
tcp_padding_len is in the range 0..63. Its bytes are deterministic for the
pair (effective_spec, target).
The request frame contains these three elements in the derived TCP order:
| Element | Encoding |
|---|---|
version |
u8(1) |
target |
`target_len_u16 |
padding |
`tcp_padding_len_u8 |
The receiver MUST reject a version other than 1, an invalid target, an
incorrect padding length, or incorrect padding bytes. The request padding is
not forwarded to the target.
After parsing the request, the Portal either resolves and connects directly to
the target or sends the target unchanged in a configured SOCKS5 CONNECT
request. dial binds the direct target socket or the connection to the SOCKS5
server. The Portal then relays bytes in both directions. When one direction
reaches EOF, the other direction may continue for at most
NOW_TCP_READ_TIMEOUT.
An asymmetric flow prepends this fixed envelope to the ordinary request frame on both client-created halves:
magic_f1 || version_u8(1) || role_u8 || flow_id_u64 ||
kind_u8 || uplink_u8 || downlink_u8
role is 1 for FLOW_OPEN and 2 for FLOW_ATTACH; kind is 1 for TCP
and 2 for UDP; carriers use 1 for TLS/TCP and 2 for QUIC/UDP. The Portal
keys pending halves by (session_id, flow_id), requires identical metadata and
target frames, and dials the target only after both halves arrive. Symmetric
flows omit this envelope and retain their direct fast path.
UDP relay has two transport-specific forms:
- QUIC DATAGRAM multiplexes flows by
(flow_id, target)on one authenticated QUIC connection. - UoT carries one target flow as length-prefixed packets on one authenticated TLS/TCP connection.
The forms share target validation, outbound UDP dialing, rate limits, idle timeouts, and UDP counters. Their wire frames are otherwise independent.
The upgraded client uses a fixed compact data plane:
| Type | Value | Body |
|---|---|---|
OPEN_DATA |
0x11 |
`flow_id_u64 |
OPEN_ACK |
0x12 |
flow_id_u64 |
DATA |
0x13 |
`flow_id_u64 |
CLOSE |
0x14 |
flow_id_u64 |
Every frame begins with version_u8(1) || type_u8. Before receiving
OPEN_ACK, every client payload uses OPEN_DATA; after the ACK, the client
uses target-free DATA. The Portal treats repeated open metadata idempotently
and forwards the first payload without an additional RTT. Target-to-client
DATAGRAMs always use compact DATA. If the Portal receives target-free DATA
for an unknown or expired flow, it returns CLOSE; the client then clears its
ACK state and uses OPEN_DATA for the next payload, or recreates an asymmetric
flow, to restore the target metadata.
The derived-order header below remains accepted for the existing symmetric implementation path but is not emitted by the upgraded reference client.
Each QUIC DATAGRAM contains a derived-order header followed by an opaque payload.
| Element | Encoding |
|---|---|
version |
u8(1) |
type |
One of the values below |
flow_id |
u64 scoped to the authenticated QUIC connection |
target |
`target_len_u16 |
The header elements appear in the derived UDP order. The payload always follows the complete header and is never shuffled.
| Type | Value | Direction and meaning |
|---|---|---|
| Request | 1 |
Client to Portal; open or reuse the flow and forward the payload to the target. |
| Response | 2 |
Portal to client; payload received from the target. |
| Close | 3 |
Client to Portal; close the matching flow. Any payload is ignored. |
A UDP flow is identified by (flow_id, target) within one QUIC connection.
Different targets with the same flow ID are distinct flows. The response uses
the request's flow ID and target. The Portal closes an inactive flow after
NOW_UDP_IDLE_TIMEOUT.
The reference Portal dispatches each flow independently. Target dialing, rate-limit waits, and target socket I/O for one flow do not block DATAGRAM dispatch to other flows. Each flow queues at most 64 client datagrams. New requests are dropped when that queue, the per-connection queued-byte budget, or the per-connection flow limit is full; already accepted requests remain FIFO.
Malformed datagrams, unsupported versions, unknown types, and response frames received by the Portal are not forwarded. A close frame for an unknown flow has no effect.
UoT is available only on an authenticated TLS/TCP connection. It is selected by sending the ordinary spec-derived TCP request frame from Section 8 with this reserved target:
uot.nowhere.invalid:0
The reserved target is a protocol switch and MUST NOT be treated as a TCP destination. Its request frame uses the same derived field order and deterministic padding as every other TCP request.
Immediately after that request, the client MUST send exactly one setup frame:
target_len_u16 || target_utf8
target_len_u16 MUST be from 1 through 512, and target_utf8 MUST satisfy
Section 10. The Portal bounds reading the complete setup target by
NOW_HANDSHAKE_TIMEOUT. It then resolves the target and opens one connected UDP
socket, optionally binding its source address according to dial. With SOCKS5
enabled, it instead creates a per-flow UDP ASSOCIATE, keeps the associated TCP
control connection open, and sends the target in each SOCKS5 UDP packet.
After setup, both directions consist only of packet frames:
payload_len_u16 || payload
payload_len_u16 is from 0 through 65535. Each frame represents exactly one UDP
packet, so implementations MUST preserve frame and packet boundaries. UoT has
no flow ID, message type, or in-band close frame. One TLS/TCP connection carries
one target flow; clients use separate connections for different concurrent
targets.
Traffic in either direction refreshes NOW_UDP_IDLE_TIMEOUT. Clean TCP EOF,
truncated or invalid framing, a target socket error, the idle timeout, or
service shutdown closes the UoT flow. Payload bytes are charged to rate and
etar and recorded in the UDP counters. The two-byte packet lengths, setup
frame, authentication frame, and request frame are not counted as UDP payload.
TCP requests, QUIC DATAGRAM headers, and UoT setup frames use the same target
representation. target_utf8 MUST:
- be valid UTF-8;
- have a byte length in
1..512; - contain a non-empty port component; and
- use brackets around an IPv6 literal, for example
[2001:db8::1]:443.
An unbracketed target may contain only the single colon that separates host and port. v1 codec validation does not require the host component to be non-empty and does not parse the port as an integer; target resolution and dialing may still fail after the frame is accepted.
The Portal applies one process-wide limiter with two independent directions:
rate: client-to-target TCP bytes and QUIC DATAGRAM/UoT UDP payload bytes;etar: target-to-client TCP bytes and QUIC DATAGRAM/UoT UDP payload bytes.
Rate limits do not select or modify QUIC congestion control. BBR remains fixed.
At startup and then every NOW_REPORT_INTERVAL, the Portal emits this event
record when the active log level includes events:
CHECK_POINT|MODE=0|PING=0ms|POOL=<n>|TCPS=<n>|UDPS=<n>|TCPRX=<bytes>|TCPTX=<bytes>|UDPRX=<bytes>|UDPTX=<bytes>
| Field | Meaning |
|---|---|
TCPS |
Active TCP relay streams. |
UDPS |
Active QUIC DATAGRAM and UoT flows. |
TCPRX |
Client-to-target TCP bytes. |
TCPTX |
Target-to-client TCP bytes. |
UDPRX |
Client-to-target UDP payload bytes. |
UDPTX |
Target-to-client UDP payload bytes. |
POOL is the number of authenticated TLS/TCP connections waiting for their
first request frame. TLS handshakes, authentication in progress, active TCP or
UoT relays, and QUIC connections are not included. MODE is 0 for net=mix,
1 for net=tcp, and 2 for net=udp. PING remains fixed to the value
shown in v1.
NOW_REPORT_INTERVAL controls only this local telemetry schedule. It does not
control QUIC keepalive traffic.
The Portal also emits carrier-level absolute counters without changing
CHECK_POINT:
LINK_STATUS|TCP=<lanes>|UDP=<sessions>|PAIRS=<sessions>|UPTCP=<payload-bytes>|UPUDP=<payload-bytes>|DOWNTCP=<payload-bytes>|DOWNUDP=<payload-bytes>
These environment variables control the reference Portal. They do not alter the v1 derivation or frame formats.
| Variable | Default | Purpose |
|---|---|---|
NOW_QUIC_MAX_STREAMS |
1024 |
Maximum concurrent QUIC bidirectional streams. |
NOW_QUIC_MAX_UDP_FLOWS |
256 |
Maximum QUIC DATAGRAM UDP flows per authenticated connection. |
NOW_QUIC_UDP_QUEUE_BYTES |
4194304 |
Maximum queued QUIC DATAGRAM bytes per authenticated connection. |
NOW_MAX_PENDING_FLOW_PAIRS |
1024 |
Maximum unpaired asymmetric flow records per session. |
NOW_FLOW_PAIR_TIMEOUT |
5s |
Lifetime of an unpaired flow half. |
NOW_TCP_DATA_BUF_SIZE |
32768 |
Buffer size for each TCP relay direction. |
NOW_UDP_DATA_BUF_SIZE |
65536 |
UDP target-socket receive buffer size. |
NOW_TCP_DIAL_TIMEOUT |
15s |
TCP target connection timeout. |
NOW_UDP_DIAL_TIMEOUT |
15s |
UDP target connection timeout. |
NOW_TCP_READ_TIMEOUT |
30s |
Grace period after one TCP direction finishes. |
NOW_UDP_IDLE_TIMEOUT |
120s |
QUIC idle timeout and QUIC DATAGRAM/UoT flow idle timeout. |
NOW_HANDSHAKE_TIMEOUT |
5s |
Base for the single jittered authentication deadline. |
NOW_REPORT_INTERVAL |
5s |
Local event interval. |
NOW_SHUTDOWN_TIMEOUT |
5s |
Endpoint idle wait during shutdown. |
NOW_RELOAD_INTERVAL |
3600s |
Minimum interval between PEM reload attempts. |
Duration values accept human-readable forms supported by the Portal, such as
500ms, 15s, or 2m. Invalid values use the listed defaults.
NOW_QUIC_MAX_UDP_FLOWS and NOW_QUIC_UDP_QUEUE_BYTES must be positive; zero
or invalid values use their defaults and emit a warning. Other integer values
must be non-negative; invalid or negative values use the listed defaults.
Two peers interoperate only when they use the same:
- shared key;
effective_specand v1 derivation rules;effective_alpn; and- frame version
1.
An ALPN mismatch fails during TLS or QUIC negotiation. With equal ALPN but a different shared key or effective spec, authentication fails before proxy traffic is processed.
A conforming implementation MUST reject malformed or truncated frames, invalid field lengths, unsupported versions, incorrect deterministic padding, invalid authentication tags, trailing authentication-stream bytes, and invalid UoT setup or packet frames. It MUST bound all allocations using the limits in this document.
The v1 derivation, labels, field-order algorithm, integer encodings, and frame type values are protocol constants. Changing any of them is not compatible with Nowhere v1.
An implementation should verify at least the following cases:
- Omitted, empty, and explicit
spec=autoproduce identical effective specs. - Omitted and empty
alpnselectnow/1. - An explicit ALPN changes only
effective_alpn. - Inputs at 255 decoded bytes are accepted and inputs at 256 bytes are rejected.
- Different shared keys with the same spec produce the same derived layout but different authentication tags.
- Different specs produce their own authentication constants, padding, and layouts.
- Authentication, TCP request, UDP datagram, UoT setup, and UoT packet encoders round-trip through their decoders.
- Wrong versions, target lengths, padding lengths, padding bytes, frame types, and tags are rejected.
- The authentication stream is rejected if any byte follows the valid frame.
- A TLS/TCP request for
uot.nowhere.invalid:0switches to UoT, preserves UDP packet boundaries in both directions, and records the flow as UDP.
For the following fixed inputs:
shared key = "secret"
spec = "auto"
ALPN = "now/1"
nonce = 32 bytes, each equal to 0x07
TCP target = "example.com:443"
the hexadecimal encoding of the authentication frame is:
33e07eceb833c31f41bea81b0c57a48d0745d1fc22df836733e99316d7ead83e
d065c573fe8427ef058b0eb2d90a070707070707070707070707070707070707
0707070707070707070707070707
and the hexadecimal encoding of the TCP request frame is:
000f6578616d706c652e636f6d3a343433013c1526b9b947228779cfc539fe46
81bcb5d1e20efa2bcb9f89eda5b473625c3c6b7fb12499fd33edfefb1934c9a
e0bfc0e849f4c94814f4f2f9ae782e8
Whitespace and line breaks in these hexadecimal displays are not part of the frames.