← Blog
0.7.0 · APPS · NETWORKING

DNS Resolver + TLS 1.3 Handshake — Apps Net Stack, 0.7.0 T3

June 22, 2026 · sigil-apps 3f47417 · Sigil-Docs
networking dns tls security browser 0.7.0

sigil-apps 3f47417 closes the Apps-layer network dark lane. dns_resolve.sg constructs a real UDP DNS A-record query packet and parses the A-record response — the resolver that http_fetch.sg and tls_handshake.sg call to turn hostnames into Cap<NetConn> addresses. tls_handshake.sg constructs a complete TLS 1.3 ClientHello (record header + handshake header + cipher suites + all required extensions) and parses a synthetic ServerHello to verify TLS 1.3 negotiation. Both run arm-el0 (real hardware cap ABI), today using loopback stubs for the final wire step; live ECDH, HKDF, and cert verify on the OS crypto lane when the Kernel grants CAP_NET.


dns_resolve.sg — UDP DNS A-record query

dns_resolve.sg (arm-el0, links net.sg) is the DNS resolver that feeds http_fetch.sg. It constructs a real DNS query packet — the packet that would go on the wire to a real DNS server — and parses the A-record response. Today the wire step uses a loopback stub from net.sg (a canned DNS response bytes); when Cap<NetConn> UDP syscall 109-112 lands, the same code path sends to :53 unchanged.

dns_query(id, hostname, out)
Build a DNS query packet for A-record lookup of hostname. Wire format: 12-byte header (ID=id, FLAGS=0x0100 QR/RD, QDCOUNT=1) + QNAME (length-prefixed labels: "sigil"→\x05sigil, "os"→\x02os, terminator \x00) + QTYPE=0x0001 (A) + QCLASS=0x0001 (IN). Output length returned; out receives raw bytes.
udp_hdr(sport, dport, len, out)
Build an 8-byte UDP header (src port, dst port, length = 8 + payload, checksum=0). Used to wrap the DNS query before hand-off to Cap<NetConn> send.
dns_response(loopback)
Loopback stand-in for the raw DNS response bytes. Returns a synthetic 48-byte A-record response matching the DNS wire format: header (ID matches, FLAGS=0x8180 QR/AA/RD/RA, ANCOUNT=1), echo of the question section, RDATA (TYPE=A, CLASS=IN, TTL=300, RDLENGTH=4, IPv4 bytes). This is the shim that live sys-109 UDP recv replaces.
dns_parse_a(resp, len, out_ip)
Extract the IPv4 address from the A-record RDATA. Walks past the header, question section (skipping QNAME by following length bytes), and answer section header (TYPE/CLASS/TTL/RDLENGTH checks), then reads 4 bytes of RDATA into out_ip[0..3]. Returns 0 on success, -1 on bad format.

Resolves sigil.os / sigil-pi.local / github.com in the test path, printing the resolved IPv4 for http_fetch (tcp://<ip>:80) and tls_handshake (:443).

Live wire-in: Cap<NetConn> sys-109 UDP send/recv replaces the dns_response loopback stub. No other code changes — the packet construction and parse path are wire-compatible today.


tls_handshake.sg — TLS 1.3 ClientHello

tls_handshake.sg (arm-el0, standalone, 249 lines) constructs a complete TLS 1.3 ClientHello and parses a synthetic ServerHello to verify TLS 1.3 negotiation. This is the "dark TLS lane" — the Apps-layer exercise of the protocol that the OS tls_client.sg (0ecc2f6) implements kernel-side. The two paths converge when the Kernel grants CAP_NET and ECDH + HKDF + cert verify run on OS crypto.

ClientHello construction

TLS record header
5 bytes — content type 0x16 (handshake), legacy version 0x0301 (TLS 1.0 compatibility), length (2 bytes, filled after body). All TLS 1.3 records use 0x0301 in the record layer; the actual version is negotiated in the supported_versions extension.
Handshake header
4 bytes — type 0x01 (ClientHello), length (3 bytes). Followed by legacy version 0x0303 (TLS 1.2 compatibility, required by RFC 8446 §4.1.2).
client_random
32 bytes of pseudo-random data (fixed test vector for the loopback path; live: CSPRNG via OS sys_random when available).
Cipher suites
2 suites — TLS_AES_128_GCM_SHA256 (0x1301) + TLS_AES_256_GCM_SHA384 (0x1302). Both are TLS 1.3-only; no TLS 1.2 suites offered (sigilOS browser does not support TLS 1.2).
ext: supported_versions
[0x0304] (TLS 1.3 only). This is the extension that actually negotiates TLS 1.3; the record/handshake legacy versions are ignored by TLS 1.3 servers.
ext: server_name (SNI)
Hostname bytes, type host_name (0), length-prefixed. Required for virtual hosting on shared IPs. Value set from the hostname passed to tls_handshake_build.
ext: supported_groups
[x25519] (0x001d). ECDH key exchange curve. Matches the OS tls_client.sg curve preference.
ext: signature_algorithms
[ecdsa_secp256r1_sha256, rsa_pss_rsae_sha256] (0x0403, 0x0804). Covers the cert types expected from modern CAs.
ext: key_share
x25519 stub. 32-byte public key (zero-filled test vector; live: real ECDH keypair from OS crypto). The key_share extension is what makes TLS 1.3 a 1-RTT handshake — server can respond with its key_share in the same flight.

ServerHello parse

tls_parse_server_hello(buf, len)
Parse a synthetic ServerHello. Verifies: record type=0x16, handshake type=0x02, cipher suite matches one of the offered suites, supported_versions extension present with value 0x0304. Returns 0 on success (TLS 1.3 confirmed), -1 on mismatch.

Live path and convergence with OS tls_client.sg

The tls_handshake.sg arm-el0 path validates the packet construction and parse logic independently of the OS crypto layer. When the Kernel grants CAP_NET, the live path activates: ECDH key agreement (x25519 scalar multiply), HKDF key derivation (HKDF-Extract + HKDF-Expand-Label for HS/MS/key material), and cert chain verify all run via OS tls_client.sg sys 117–119. The Apps-layer tls_handshake.sg and the OS tls_client.sg (0ecc2f6) share the same wire format and curve preference — they converge on the same crypto substrate.

dns_resolve.sg
  → dns_query()        # construct UDP DNS A-record packet (wire-compatible)
  → dns_response()     # loopback stub (→ sys-109 UDP recv when CAP_NET lands)
  → dns_parse_a()      # extract IPv4 from A-record RDATA
  → ip passed to http_fetch / tls_handshake

tls_handshake.sg
  → tls_handshake_build(hostname)
      record hdr (0x16 / 0x0301)
      handshake hdr (0x01 / 0x0303)
      client_random [32B]
      cipher_suites [0x1301, 0x1302]
      ext: supported_versions [0x0304]
      ext: server_name (SNI)
      ext: supported_groups [x25519]
      ext: signature_algorithms [0x0403, 0x0804]
      ext: key_share x25519 [32B stub]
  → tls_parse_server_hello()   # verify TLS 1.3 negotiated
  → live: ECDH + HKDF + cert verify via tls_client.sg sys 117-119