<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Formats · XB Field Notes</title><link>https://www.xbcatm.com/tags/formats/</link><description>Illustrated field notes on HTTP, DNS, TLS, Caddy, SSH, releases, and recovery.</description><generator>Hugo</generator><language>en-us</language><atom:link href="https://www.xbcatm.com/tags/formats/index.xml" rel="self" type="application/rss+xml"/><item><title>IPv6 text normalization preserves addresses, but endpoint context still matters</title><link>https://www.xbcatm.com/posts/ipv6-text-form-is-condensed/</link><pubDate>Mon, 30 Nov 2026 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/posts/ipv6-text-form-is-condensed/</guid><description>Compare equivalent spellings, reject malformed addresses, preserve a single zero field, and separate URL ports and scope identifiers.</description><content:encoded>&lt;p&gt;An IPv6 address contains 128 bits, usually written as eight hexadecimal groups. Text may omit leading zeros and replace one consecutive run of zero groups with &lt;code&gt;::&lt;/code&gt;. These rules preserve the address while shortening its spelling. RFC 4291 defines valid forms; RFC 5952 recommends a consistent representation for display and exchange. Valid syntax and the preferred spelling are related but different questions.&lt;/p&gt;
&lt;h2 id="when-it-breaks"&gt;When it breaks&lt;/h2&gt;
&lt;p&gt;Raw string equality can treat one address as several identities. Uppercase letters, leading zeros, and expanded zero groups can all change the text without changing the 128 bits. That can split inventory entries or defeat an application&amp;rsquo;s intended address comparison. Parse first with a library appropriate to the input, then compare address values instead of assuming lowercasing alone performs full normalization.&lt;/p&gt;
&lt;p&gt;Compression also has constraints. &lt;code&gt;::&lt;/code&gt; can appear only once, because two omitted runs would leave their lengths ambiguous. For the recommended representation, compress the longest zero run, choosing the first when lengths tie, and do not compress a single zero field. General IPv6 syntax permits some spellings that these display rules deliberately avoid. A normalizer should not confuse an accepted nonpreferred form with malformed input.&lt;/p&gt;
&lt;p&gt;An address is not always a complete endpoint. A URL needs brackets around an IPv6 literal to separate its colons from the port delimiter. A link-local destination may also need a zone identifier to select the intended interface context. Stripping a zone or flattening an IPv4-mapped address without an explicit comparison policy can discard information the caller needs to make the right connection.&lt;/p&gt;
&lt;h2 id="check-it-quickly"&gt;Check it quickly&lt;/h2&gt;
&lt;p&gt;Save this as &lt;code&gt;check-ipv6-text.py&lt;/code&gt; and run &lt;code&gt;python3 check-ipv6-text.py&lt;/code&gt; with Docker. The pinned Python 3.11.16 image runs standard-library parsing without network traffic. It compares valid variants, rejects malformed inputs, and inspects endpoint components. The test does not assign the example addresses, send IPv6 packets, or establish that any route to them exists:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="nn"&gt;subprocess&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="nn"&gt;uuid&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;python@sha256:528257d48c1da0dcecc2e725d1ae34498d60c965f1241e39cd6a85a8859bdf84&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;program&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;from ipaddress import IPv6Address
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;from urllib.parse import urlsplit
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;import json
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;variants=[&amp;#39;2001:0DB8:0000:0000:0000:ff00:0042:8329&amp;#39;,&amp;#39;2001:db8:0:0:0:ff00:42:8329&amp;#39;,&amp;#39;2001:db8::ff00:42:8329&amp;#39;]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;parsed=[IPv6Address(v) for v in variants];assert len(set(parsed))==1 and len(set(variants))==3
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;tie=IPv6Address(&amp;#39;2001:0:0:1:0:0:1:1&amp;#39;).compressed;assert tie==&amp;#39;2001::1:0:0:1:1&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;single=IPv6Address(&amp;#39;2001:db8::1:2:3:4:5&amp;#39;).compressed;assert single==&amp;#39;2001:db8:0:1:2:3:4:5&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;bad=[]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;for text in [&amp;#39;2001::db8::1&amp;#39;,&amp;#39;2001:db8:1:2:3:4:5:6:7&amp;#39;,&amp;#39;2001:db8::gg&amp;#39;,&amp;#39;2001:db8::1/64&amp;#39;]:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; try:IPv6Address(text);raise AssertionError(&amp;#39;invalid address accepted&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; except ValueError:bad.append(text)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;url=urlsplit(&amp;#39;https://[2001:db8::1]:8443/health&amp;#39;);assert IPv6Address(url.hostname)==IPv6Address(&amp;#39;2001:db8::1&amp;#39;) and url.port==8443
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;try:urlsplit(&amp;#39;https://2001:db8::1:8443/health&amp;#39;).port;raise AssertionError(&amp;#39;unbracketed endpoint accepted&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;except ValueError:bad_url=True
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;mapped=IPv6Address(&amp;#39;::ffff:192.0.2.1&amp;#39;);assert str(mapped.ipv4_mapped)==&amp;#39;192.0.2.1&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;zone_a=IPv6Address(&amp;#39;fe80::1&lt;/span&gt;&lt;span class="si"&gt;%e&lt;/span&gt;&lt;span class="s2"&gt;th0&amp;#39;);zone_b=IPv6Address(&amp;#39;fe80::1&lt;/span&gt;&lt;span class="si"&gt;%e&lt;/span&gt;&lt;span class="s2"&gt;th1&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;assert zone_a!=zone_b and zone_a.packed==zone_b.packed
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;print(json.dumps({&amp;#39;variants&amp;#39;:variants,&amp;#39;same_128_bits&amp;#39;:len(set(parsed))==1,&amp;#39;normalized&amp;#39;:str(parsed[0]),
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; &amp;#39;leftmost_tie&amp;#39;:tie,&amp;#39;single_zero_retained&amp;#39;:single,&amp;#39;rejected&amp;#39;:bad,&amp;#39;url_host&amp;#39;:url.hostname,&amp;#39;url_port&amp;#39;:url.port,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; &amp;#39;unbracketed_url_port_rejected&amp;#39;:bad_url,&amp;#39;mapped_python_text&amp;#39;:mapped.compressed,&amp;#39;mapped_ipv4&amp;#39;:str(mapped.ipv4_mapped),
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; &amp;#39;zone_scope_ids&amp;#39;:[zone_a.scope_id,zone_b.scope_id],&amp;#39;same_bits_different_scopes&amp;#39;:True}))
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;xbcatm-socket-&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uuid4&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hex&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;docker&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;run&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--rm&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;-i&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--network&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;none&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;--memory&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;128m&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--pids-limit&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;64&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;python3&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;-&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;program&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;capture_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;returncode&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="n"&gt;ensure_ascii&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;sort_keys&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;docker&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;rm&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;-f&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="n"&gt;capture_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Three strings represent the same address, including &lt;code&gt;2001:0DB8:0000:0000:0000:ff00:0042:8329&lt;/code&gt; and &lt;code&gt;2001:db8::ff00:42:8329&lt;/code&gt;. Their string set contains three entries, while their parsed-address set contains one. The normalized output is &lt;code&gt;2001:db8::ff00:42:8329&lt;/code&gt;. This directly exposes why textual spelling is an unreliable key when the application&amp;rsquo;s identity is meant to be the underlying address.&lt;/p&gt;
&lt;p&gt;For &lt;code&gt;2001:0:0:1:0:0:1:1&lt;/code&gt;, the first of two equal zero runs is shortened, producing &lt;code&gt;2001::1:0:0:1:1&lt;/code&gt;. The valid spelling &lt;code&gt;2001:db8::1:2:3:4:5&lt;/code&gt; contains only one omitted zero group, so its normalized output restores that group as &lt;code&gt;2001:db8:0:1:2:3:4:5&lt;/code&gt;. The exercise explicitly verifies the single-zero rule rather than recommending compression wherever it makes a string shorter.&lt;/p&gt;
&lt;p&gt;Inputs with two &lt;code&gt;::&lt;/code&gt; markers, too many groups, or nonhexadecimal digits fail address parsing. A string ending in &lt;code&gt;/64&lt;/code&gt; also fails this address-only constructor because it describes a different kind of input. Select an interface or network parser when a prefix is part of the contract; do not silently discard the suffix merely to make an address parser accept it.&lt;/p&gt;
&lt;p&gt;The bracketed URL &lt;code&gt;https://[2001:db8::1]:8443/health&lt;/code&gt; separates hostname and port correctly. Accessing the port of the intentionally unbracketed counterpart raises ValueError. The fixture also recognizes the IPv4 component of &lt;code&gt;::ffff:192.0.2.1&lt;/code&gt;; this pinned runtime prints that mapped address in mixed notation. Those results describe the tested parser, not a claim that every runtime formats every special address identically.&lt;/p&gt;
&lt;p&gt;Two scoped values, &lt;code&gt;fe80::1%eth0&lt;/code&gt; and &lt;code&gt;fe80::1%eth1&lt;/code&gt;, have identical packed address bytes but compare differently with their scopes retained. The zone is additional context rather than part of the 128-bit address. No such interfaces are contacted here. The test checks that the representation can preserve the distinction, not that either scoped destination is configured or reachable.&lt;/p&gt;
&lt;h2 id="choose-the-comparison-contract-before-storing-text"&gt;Choose the comparison contract before storing text&lt;/h2&gt;
&lt;p&gt;Decide whether a field stores an address, network prefix, interface assignment, URL, or socket endpoint. Use that type&amp;rsquo;s parser and preserve its required components. Keeping raw input separately can help explain a configuration mistake, while a normalized representation supports consistent display and indexing. Neither normalization nor parsing authenticates who controls the address or whether the requested service is safe.&lt;/p&gt;
&lt;p&gt;Document treatment of mapped IPv4 and scope identifiers when correlating logs or applying address rules. Test equivalent valid forms, malformed inputs, and context-bearing cases against that policy. The goal is a stable interpretation of the intended endpoint, not merely the shortest possible string. Address formatting becomes dependable when the binary value and its surrounding connection context remain distinct and explicit.&lt;/p&gt;
&lt;h2 id="sources"&gt;Sources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc4291.html"&gt;RFC 4291 IPv6 addressing&lt;/a&gt;, valid IPv6 syntax, compressed forms and embedded IPv4, checked 2026-09-12.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc5952.html"&gt;RFC 5952 IPv6 text representation&lt;/a&gt;, recommended zero compression, tie handling and lowercase, checked 2026-09-12.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc3986.html"&gt;RFC 3986 URI syntax&lt;/a&gt;, bracketed IP literals and separation from a port, checked 2026-09-12.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.python.org/3.11/library/ipaddress.html"&gt;Python 3.11 ipaddress&lt;/a&gt;, parsed address equality, mapped addresses and scope IDs, checked 2026-09-12.&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item></channel></rss>