TLS
TLS wraps TCP connections and listeners with OpenSSL-backed TLS state while keeping deadlines and socket lifecycle aligned with the underlying transport. Concrete-address dialing and listening use the same connect and listen entrypoints as the string-address surface. String-address client dialing uses the resolver/policy layer as Reseau.TCP.connect; see Name Resolution for that part of the stack.
- Configuration, Types, and Errors
- Client and Server Construction
- I/O, Lifecycle, Deadlines, and Inspection
- Practical Usage Notes
- Where To Go Next
Configuration, Types, and Errors
The main TLS building blocks are:
Reseau.TLS.Config — Type
Config(; ...)Reusable TLS configuration for client and server sessions.
Keyword arguments:
server_name: hostname or IP literal used for certificate verification. For clients, this also drives SNI when it is a hostname. If omitted,connectwill try to derive it from the dial target.verify_peer: whether to validate the remote certificate chain and peer name.client_auth: server-side client certificate policy.cert_file/key_file: PEM-encoded certificate chain and private key. Servers must provide both; clients may provide both for mutual TLS.ca_file: CA bundle or hashed CA directory used to verify remote servers. When omitted, client verification usesNetworkOptions.ca_roots_path().client_ca_file: CA bundle or hashed CA directory used by servers to verify client certificates. This is required for server configs that verify presented client certs.alpn_protocols: ordered ALPN protocol preference list.handshake_timeout_ns: optional cap, in monotonic nanoseconds, applied only while the handshake is running. Existing transport deadlines still win if they are earlier.min_version/max_version: TLS protocol version bounds.nothingleaves the bound unset.
Returns a reusable immutable Config.
Throws ConfigError if the keyword combination is internally inconsistent.
Reseau.TLS.ConnectionState — Type
ConnectionStateSnapshot of negotiated TLS connection state.
Fields:
handshake_complete: whether the TLS handshake has finished successfully.version: negotiated TLS protocol version string as reported by OpenSSL.alpn_protocol: negotiated ALPN protocol, ornothingif ALPN was not used.
Reseau.TLS.Conn — Type
ConnTLS stream wrapper over one TCP.Conn.
Conn is safe for one concurrent reader and one concurrent writer. Handshake, read, and write all have separate locks so lazy handshakes and shutdown can coordinate without corrupting the OpenSSL state machine. Because Conn <: IO, standard Base stream helpers like read, read!, readbytes!, eof, and write apply directly to decrypted application data.
Reseau.TLS.Listener — Type
ListenerTLS listener wrapper over TCP.Listener.
Accepted connections are wrapped in server-side TLS state but are not handshaken until handshake! or the first read/write call.
Reseau.TLS.ConfigError — Type
ConfigErrorRaised when TLS configuration is invalid.
This is thrown before any network I/O starts, typically while normalizing a Config or constructing an SSL_CTX.
Reseau.TLS.TLSError — Type
TLSErrorRaised when TLS handshake/read/write/close operations fail.
code is usually an OpenSSL SSL_get_error result, though wrapper paths may set it to 0 when the failure is higher-level than one OpenSSL call. cause preserves the underlying Julia-side exception when the failure originated in the transport/poller layer instead of OpenSSL itself.
Reseau.TLS.TLSHandshakeTimeoutError — Type
TLSHandshakeTimeoutErrorRaised when handshake exceeds the configured timeout.
This is kept separate from TLSError because handshake timeouts are often configured and handled distinctly from generic TLS failures.
Reseau.TLS.DeadlineExceededError — Type
DeadlineExceededErrorAlias for the transport deadline timeout type used by TLS.
Catch TLS.DeadlineExceededError for direct listener accept timeouts and when inspecting TLSError.cause. Higher-level TLS operations may wrap deadline expiry in TLSError or TLSHandshakeTimeoutError so callers can keep operation-specific handling.
ClientAuthMode is an enum-like policy surface with the following cases:
TLS.ClientAuthMode.NoClientCertTLS.ClientAuthMode.RequestClientCertTLS.ClientAuthMode.RequireAnyClientCertTLS.ClientAuthMode.VerifyClientCertIfGivenTLS.ClientAuthMode.RequireAndVerifyClientCert
For server-side verified client-certificate auth, provide client_ca_file explicitly in Config.
Client and Server Construction
You can either dial and handshake a client in one step from a concrete TCP.SocketAddr or string address, or wrap an existing TCP.Conn manually:
Reseau.TLS.client — Function
client(tcp, config) -> ConnWrap an established TCP.Conn in client-side TLS state.
The handshake is deferred until handshake! or the first read/write operation.
Throws ConfigError or TLSError if the TLS state cannot be initialized.
Reseau.TLS.server — Function
server(tcp, config) -> ConnWrap an established TCP.Conn in server-side TLS state.
The handshake is deferred until handshake! or the first read/write operation.
Throws ConfigError or TLSError if the TLS state cannot be initialized.
Reseau.TLS.connect — Function
connect(remote_addr; kwargs...) -> Conn
connect(remote_addr, local_addr; kwargs...) -> ConnConnect to a concrete TCP.SocketAddr, negotiate TLS, and return a fully handshaken client connection.
This is the direct-address counterpart to the string-address connect(network, address; ...) overloads. The underlying socket setup is delegated to TCP.connect, after which the returned transport is wrapped in TLS and handshaken immediately.
TLS keyword arguments are forwarded to Config. If server_name is omitted, it is inferred from the concrete remote address when possible so peer verification and SNI can follow the same rules as the string-address client path.
connect(network, address; kwargs...) -> ConnConnect to address, negotiate TLS, and return a fully handshaken client connection.
Network-resolution keyword arguments are:
timeout_nsdeadline_nslocal_addrfallback_delay_nsresolverpolicy
TLS keyword arguments are forwarded to Config:
server_nameverify_peerclient_authcert_filekey_fileca_fileclient_ca_filealpn_protocolshandshake_timeout_nsmin_versionmax_version
If server_name is omitted, it is derived from address when possible so SNI and peer verification use the dial target's host name automatically.
connect(address; kwargs...) -> ConnConvenience shorthand for connect("tcp", address; kwargs...).
Reseau.TLS.listen — Function
listen(network, address, config; backlog=128, reuseaddr=true) -> ListenerCreate a TLS listener by first creating a TCP listener and then associating a server Config with accepted connections.
Accepted Conns are returned in lazy-handshake form.
Throws ConfigError if the server config is invalid and propagates any listener creation errors from TCP.listen.
listen(local_addr, config; backlog=128, reuseaddr=true) -> ListenerCreate a TLS listener from a concrete local TCP.SocketAddr.
This is the direct-address counterpart to listen(network, address, config; ...). The underlying TCP listener is created with TCP.listen, then accepted connections are wrapped in lazy-handshake TLS state.
Reseau.TLS.accept — Function
accept(listener) -> ConnAccept one inbound TCP connection and wrap it in server-side TLS state.
The returned Conn has not handshaken yet. If the listener deadline expires, accept(listener) throws DeadlineExceededError.
Reseau.TLS.handshake! — Function
handshake!(conn)Run the TLS handshake to completion if it has not already finished.
This method is idempotent. Concurrent callers serialize through handshake_lock, so only one task drives OpenSSL while the others observe the completed state afterward.
Returns nothing.
Throws:
TLSHandshakeTimeoutErrorwhenconfig.handshake_timeout_nsexpiresTLSErrorfor OpenSSL or transport failuresEOFErrorif the peer closes cleanly during the handshake
Important behaviors to keep in mind:
connectreturns a fully handshaken client connection for both concrete-address and string-address dialing.listenreturns a TLS listener whose accepted connections are lazy-handshake wrappers.clientandserverare the direct wrapping APIs when you already control the underlyingTCP.Conn.handshake!is idempotent, so eager and lazy handshake styles can coexist safely.
I/O, Lifecycle, Deadlines, and Inspection
TLS connections still behave like Julia streams, but now read and write operate on plaintext application data while OpenSSL handles record framing underneath:
Base.read! — Method
read!(conn, buf) -> bufRead exactly length(buf) decrypted bytes into buf or throw EOFError.
Because Conn <: IO, Base's generic read! implementation already supports mutable byte views like @view bytes[2:5] in addition to plain vectors.
Use readbytes! or readavailable when you want a count-returning read that may stop early.
Base.readbytes! — Method
readbytes!(conn, buf, nb=length(buf); all::Bool=true) -> IntRead up to nb decrypted bytes into buf, returning the byte count.
If all is true (the default), the call keeps reading until nb bytes have been transferred, EOF is reached, or an error occurs. If all is false, at most one underlying TLS read is performed.
Resizable Vector{UInt8} buffers grow when needed, matching Julia's standard readbytes! behavior. Fixed-size contiguous byte views must satisfy nb <= length(buf).
Base.readavailable — Method
readavailable(conn) -> Vector{UInt8}Read and return decrypted plaintext that is ready without requiring a full-buffer exact read.
Base.isopen — Method
isopen(conn) -> BoolReturn true while both the TLS state and underlying TCP transport remain open.
Base.isopen — Method
isopen(listener) -> BoolReturn true while the wrapped TCP listener remains open.
Base.write — Method
write(conn, buf) -> Int
write(conn, buf, nbytes) -> IntWrite plaintext application bytes through the TLS connection.
write(conn, buf) attempts to write the entire buffer. The fixed-size byte-buffer overload allows callers to cap the write at nbytes.
Base.close — Method
close(conn)Close the TLS connection and the underlying TCP transport.
If the handshake completed, this best-effort sends a TLS close_notify alert before closing the socket. The method is idempotent and returns nothing.
Base.close — Method
close(listener)Close the underlying TCP listener. Repeated closes are treated as no-ops.
Base.closewrite — Method
closewrite(conn)Send TLS shutdown on the write side and mark future writes as permanently failed.
This is the TLS analogue of half-closing a TCP socket. It requires the handshake to have completed because the TLS close-notify alert is itself a TLS record.
Returns nothing.
Throws TLSError if the TLS shutdown path fails.
Reseau.TLS.set_deadline! — Function
set_deadline!(conn, deadline_ns)Set both read and write deadlines on the underlying transport.
deadline_ns is interpreted using the same monotonic-clock convention as TCP and IOPoll: 0 clears the deadline, negative values mean the deadline has already expired, and positive values are absolute time_ns() values.
set_deadline!(listener, deadline_ns)Set the accept deadline on the underlying TCP listener.
This affects accept(listener) only. The returned Conn still uses its own connection and handshake deadlines afterward. Expired accept waits throw DeadlineExceededError.
Reseau.TLS.set_read_deadline! — Function
set_read_deadline!(conn, deadline_ns)Set the read deadline on the underlying transport. See set_deadline! for the timestamp convention.
Reseau.TLS.set_write_deadline! — Function
set_write_deadline!(conn, deadline_ns)Set the write deadline on the underlying transport. See set_deadline! for the timestamp convention.
Reseau.TLS.local_addr — Function
local_addr(conn)Return the local TCP.SocketAddr for the wrapped transport.
local_addr(listener)Return the local listening address for the wrapped TCP listener.
This is an alias for addr(listener).
Reseau.TLS.remote_addr — Function
remote_addr(conn)Return the remote TCP.SocketAddr for the wrapped transport.
Reseau.TLS.net_conn — Function
net_conn(conn) -> TCP.ConnExpose the underlying TCP connection.
Callers that need transport-level inspection can reach through the TLS wrapper without reconstructing the socket state.
Reseau.TLS.connection_state — Function
connection_state(conn) -> ConnectionStateReturn a snapshot of the currently negotiated TLS state.
This does not force a handshake; if the handshake has not yet run, the returned ConnectionState reports handshake_complete=false.
Reseau.TLS.addr — Function
addr(listener)Return the local listening address for the wrapped TCP listener.
Two details matter in practice:
read!(conn, buf)fillsbufor throwsEOFError; usereadbytes!orreadavailablewhen you want short-read behavior.- TLS deadlines are delegated to the wrapped TCP transport, so the timeout model matches TCP exactly.
- A timed-out TLS write is treated as a permanent write failure because partial record emission leaves future writes unsafe.
close(conn)andclose(listener)are idempotent, andisopenis available on both TLS connections and listeners.
TLS.DeadlineExceededError is the public alias for the underlying transport timeout type. accept(listener) throws it directly when the listener deadline expires. Higher-level TLS operations may instead raise TLSError with the timeout in cause, or a handshake-specific TLSHandshakeTimeoutError.
Practical Usage Notes
- If
server_nameis omitted,connectderives it from the dial target when possible so SNI and certificate verification use that host name automatically. - If
ca_fileis omitted for outbound verification, Reseau falls back toNetworkOptions.ca_roots_path()when that path is available. connection_statedoes not force the handshake to run; it reports the current negotiated state as-is.set_deadline!also applies toTLS.Listener, where it sets theacceptdeadline only.local_addr(listener)is available as an alias foraddr.
Where To Go Next
- Read TCP for the underlying transport model and socket lifecycle.
- Read Name Resolution for the advanced resolver and address-family policy controls shared by
TCP.connectandTLS.connect. - Read API Reference for the canonical TLS docstrings.