Recently I’ve been working on a package named swift-dns, which is:
A Swift DNS library built on top of SwiftNIO; aiming to provide DNS client, resolver and server implementations.
I’ve been thinking about decoupling some IP and domain name types that can be useful outside that package as well, and putting them into a dedicated package.
Most of the code is already ready, living in the swift-dns repository, in Sources/DNSModels/NetworkAddress.
Here’s what I’m thinking right now about how to make these useable as a standalone package.
- There will be a package named like “NetworkAddress“.
- A few products, one dedicated to IPAddress types, one to Domain Name, one for compatibility between IP and Domain Name, and another that just re-exports all.
- Current Domain Name implementation contains IDNA compatibility code (for those unfamiliar, IDNA helps with non-ascii domain names, for example if a domain name is in Persian, or Chinese). I’m thinking of disabling IDNA compatibility by default, and introducing an “IDNA“ trait that reenables it when enabled. The expectation would be that enabling the trait is on end-user, not any libraries that might use this “NetworkAddress“ library.
Right now the main API consists of a few types:
IPv4Address
, IPv6Address
, IPAddress
, DomainName
, CIDR
.
All these types are self-explanatory, and all of them contain optimized implementations for common stuff, such as en/decoding to/from String/ByteBuffer, as well as IP < –- > DomainName conversions.
That also means that for now this package would be relying on SwiftNIO, until we have a better solution for a “bag of bytes“ in the ecosystem, or if I find a reasonable way to make the types independent of ByteBuffer
.
The CIDR
type contains a somewhat simple CIDR
implementation, providing a way to do containment checks and initializations of the CIDR
type through trivial bitwise operations, as well as optimized String en/decoding implementations.
Another thing to mention is that currently a bunch of the implementations require macOS 26 on macOS, due to usage of spans, specially the UTF8Span
of String
. Most if not all of these implementations can be back-deployed if needed, although with worse performance.
There are currently a good amount of tests, as well as benchmarks for these types as well.
The latest results can be found in the benchmark CI runs in the “Summary“ section. Currently the latest CI run is this one.
The current benchmark results are as below:
```
Host 'd20e4aedf073' with 2 'x86_64' processors with 7 GB memory, running:
#71-Ubuntu SMP PREEMPT_DYNAMIC Tue Jul 22 16:52:38 UTC 2025 (Hetzner - Falkenstein)
```
## DomainName
### Equality_Check_CPU_20M
| Metric | p0 | p25 | p50 | p75 | p90 | p99 | p100 | Samples |
|:-----------------------|----------:|----------:|----------:|----------:|----------:|----------:|----------:|----------:|
| Time (user CPU) (ms) * | 260 | 260 | 260 | 260 | 270 | 270 | 270 | 20 |
### Equality_Check_Malloc
| Metric | p0 | p25 | p50 | p75 | p90 | p99 | p100 | Samples |
|:-----------------------|----------:|----------:|----------:|----------:|----------:|----------:|----------:|----------:|
| Malloc (total) * | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 10 |
### app-analytics-services_dot_com_Binary_Parsing_CPU_2M
| Metric | p0 | p25 | p50 | p75 | p90 | p99 | p100 | Samples |
|:-----------------------|----------:|----------:|----------:|----------:|----------:|----------:|----------:|----------:|
| Time (user CPU) (ms) * | 180 | 180 | 180 | 180 | 190 | 190 | 190 | 28 |
### app-analytics-services_dot_com_Binary_Parsing_Malloc
| Metric | p0 | p25 | p50 | p75 | p90 | p99 | p100 | Samples |
|:-----------------------|----------:|----------:|----------:|----------:|----------:|----------:|----------:|----------:|
| Malloc (total) * | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 10 |
### app-analytics-services_dot_com_String_Parsing_CPU_200K
| Metric | p0 | p25 | p50 | p75 | p90 | p99 | p100 | Samples |
|:-----------------------|----------:|----------:|----------:|----------:|----------:|----------:|----------:|----------:|
| Time (user CPU) (ms) * | 80 | 90 | 90 | 90 | 90 | 100 | 100 | 57 |
### app-analytics-services_dot_com_String_Parsing_Malloc
| Metric | p0 | p25 | p50 | p75 | p90 | p99 | p100 | Samples |
|:-----------------------|----------:|----------:|----------:|----------:|----------:|----------:|----------:|----------:|
| Malloc (total) * | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 10 |
### google_dot_com_Binary_Parsing_CPU_2M
| Metric | p0 | p25 | p50 | p75 | p90 | p99 | p100 | Samples |
|:-----------------------|----------:|----------:|----------:|----------:|----------:|----------:|----------:|----------:|
| Time (user CPU) (ms) * | 170 | 170 | 170 | 170 | 170 | 180 | 180 | 30 |
### google_dot_com_Binary_Parsing_Malloc
| Metric | p0 | p25 | p50 | p75 | p90 | p99 | p100 | Samples |
|:-----------------------|----------:|----------:|----------:|----------:|----------:|----------:|----------:|----------:|
| Malloc (total) * | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 10 |
### google_dot_com_String_Parsing_CPU_200K
| Metric | p0 | p25 | p50 | p75 | p90 | p99 | p100 | Samples |
|:-----------------------|----------:|----------:|----------:|----------:|----------:|----------:|----------:|----------:|
| Time (user CPU) (ms) * | 50 | 60 | 60 | 60 | 60 | 70 | 70 | 83 |
### google_dot_com_String_Parsing_Malloc
| Metric | p0 | p25 | p50 | p75 | p90 | p99 | p100 | Samples |
|:-----------------------|----------:|----------:|----------:|----------:|----------:|----------:|----------:|----------:|
| Malloc (total) * | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 10 |
## IPAddress
### 111_Machine_Warmup_Benchmark
| Metric | p0 | p25 | p50 | p75 | p90 | p99 | p100 | Samples |
|:-----------------------|----------:|----------:|----------:|----------:|----------:|----------:|----------:|----------:|
| Time (user CPU) (μs) * | 0 | 0 | 0 | 0 | 0 | 10000 | 10000 | 30916 |
### IPv4_CIDR_Create_Then_Check_Is_Loopback_100M
| Metric | p0 | p25 | p50 | p75 | p90 | p99 | p100 | Samples |
|:-----------------------|----------:|----------:|----------:|----------:|----------:|----------:|----------:|----------:|
| Time (user CPU) (ms) * | 100 | 110 | 110 | 110 | 120 | 120 | 120 | 45 |
### IPv4_CIDR_Create_Then_Check_Is_Multicast_100M
| Metric | p0 | p25 | p50 | p75 | p90 | p99 | p100 | Samples |
|:-----------------------|----------:|----------:|----------:|----------:|----------:|----------:|----------:|----------:|
| Time (user CPU) (ms) * | 110 | 110 | 110 | 110 | 120 | 120 | 120 | 45 |
### IPv4_CIDR_Create_Then_Check_Is_Multicast_Malloc
| Metric | p0 | p25 | p50 | p75 | p90 | p99 | p100 | Samples |
|:-----------------------|----------:|----------:|----------:|----------:|----------:|----------:|----------:|----------:|
| Malloc (total) * | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 10 |
### IPv4_String_Decoding_Local_Broadcast_10M
| Metric | p0 | p25 | p50 | p75 | p90 | p99 | p100 | Samples |
|:-----------------------|----------:|----------:|----------:|----------:|----------:|----------:|----------:|----------:|
| Time (user CPU) (ms) * | 180 | 190 | 190 | 190 | 190 | 200 | 200 | 27 |
### IPv4_String_Decoding_Local_Broadcast_Malloc
| Metric | p0 | p25 | p50 | p75 | p90 | p99 | p100 | Samples |
|:-----------------------|----------:|----------:|----------:|----------:|----------:|----------:|----------:|----------:|
| Malloc (total) * | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 10 |
### IPv4_String_Decoding_Localhost_10M
| Metric | p0 | p25 | p50 | p75 | p90 | p99 | p100 | Samples |
|:-----------------------|----------:|----------:|----------:|----------:|----------:|----------:|----------:|----------:|
| Time (user CPU) (ms) * | 150 | 160 | 160 | 160 | 170 | 170 | 170 | 31 |
### IPv4_String_Decoding_Zero_10M
| Metric | p0 | p25 | p50 | p75 | p90 | p99 | p100 | Samples |
|:-----------------------|----------:|----------:|----------:|----------:|----------:|----------:|----------:|----------:|
| Time (user CPU) (ms) * | 150 | 150 | 150 | 160 | 160 | 160 | 160 | 33 |
### IPv4_String_Encoding_Local_Broadcast_15M
| Metric | p0 | p25 | p50 | p75 | p90 | p99 | p100 | Samples |
|:-----------------------|----------:|----------:|----------:|----------:|----------:|----------:|----------:|----------:|
| Time (user CPU) (ms) * | 180 | 190 | 190 | 190 | 190 | 200 | 200 | 27 |
### IPv4_String_Encoding_Localhost_15M
| Metric | p0 | p25 | p50 | p75 | p90 | p99 | p100 | Samples |
|:-----------------------|----------:|----------:|----------:|----------:|----------:|----------:|----------:|----------:|
| Time (user CPU) (ms) * | 170 | 170 | 170 | 180 | 180 | 180 | 180 | 29 |
### IPv4_String_Encoding_Mixed_15M
| Metric | p0 | p25 | p50 | p75 | p90 | p99 | p100 | Samples |
|:-----------------------|----------:|----------:|----------:|----------:|----------:|----------:|----------:|----------:|
| Time (user CPU) (ms) * | 170 | 170 | 180 | 180 | 180 | 180 | 180 | 29 |
### IPv4_String_Encoding_Mixed_Malloc
| Metric | p0 | p25 | p50 | p75 | p90 | p99 | p100 | Samples |
|:-----------------------|----------:|----------:|----------:|----------:|----------:|----------:|----------:|----------:|
| Malloc (total) * | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 10 |
### IPv4_String_Encoding_Zero_15M
| Metric | p0 | p25 | p50 | p75 | p90 | p99 | p100 | Samples |
|:-----------------------|----------:|----------:|----------:|----------:|----------:|----------:|----------:|----------:|
| Time (user CPU) (ms) * | 170 | 170 | 170 | 180 | 180 | 180 | 180 | 29 |
### IPv6_CIDR_Create_Then_Check_Is_Loopback_100M
| Metric | p0 | p25 | p50 | p75 | p90 | p99 | p100 | Samples |
|:-----------------------|----------:|----------:|----------:|----------:|----------:|----------:|----------:|----------:|
| Time (user CPU) (ms) * | 110 | 110 | 110 | 110 | 120 | 120 | 120 | 45 |
### IPv6_CIDR_Create_Then_Check_Is_Multicast_100M
| Metric | p0 | p25 | p50 | p75 | p90 | p99 | p100 | Samples |
|:-----------------------|----------:|----------:|----------:|----------:|----------:|----------:|----------:|----------:|
| Time (user CPU) (ms) * | 110 | 110 | 110 | 110 | 120 | 120 | 120 | 45 |
### IPv6_CIDR_Create_Then_Check_Is_Multicast_Malloc
| Metric | p0 | p25 | p50 | p75 | p90 | p99 | p100 | Samples |
|:-----------------------|----------:|----------:|----------:|----------:|----------:|----------:|----------:|----------:|
| Malloc (total) * | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 10 |
### IPv6_String_Decoding_2_Groups_Compressed_At_The_Begining_2M
| Metric | p0 | p25 | p50 | p75 | p90 | p99 | p100 | Samples |
|:-----------------------|----------:|----------:|----------:|----------:|----------:|----------:|----------:|----------:|
| Time (user CPU) (ms) * | 100 | 100 | 110 | 110 | 110 | 110 | 110 | 47 |
### IPv6_String_Decoding_2_Groups_Compressed_At_The_End_2M
| Metric | p0 | p25 | p50 | p75 | p90 | p99 | p100 | Samples |
|:-----------------------|----------:|----------:|----------:|----------:|----------:|----------:|----------:|----------:|
| Time (user CPU) (ms) * | 90 | 90 | 90 | 100 | 100 | 100 | 100 | 54 |
### IPv6_String_Decoding_2_Groups_Compressed_In_The_Middle_2M
| Metric | p0 | p25 | p50 | p75 | p90 | p99 | p100 | Samples |
|:-----------------------|----------:|----------:|----------:|----------:|----------:|----------:|----------:|----------:|
| Time (user CPU) (ms) * | 100 | 100 | 100 | 100 | 110 | 110 | 110 | 49 |
### IPv6_String_Decoding_2_Groups_Compressed_In_The_Middle_Malloc
| Metric | p0 | p25 | p50 | p75 | p90 | p99 | p100 | Samples |
|:-----------------------|----------:|----------:|----------:|----------:|----------:|----------:|----------:|----------:|
| Malloc (total) * | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 10 |
### IPv6_String_Decoding_Localhost_Compressed_10M
| Metric | p0 | p25 | p50 | p75 | p90 | p99 | p100 | Samples |
|:-----------------------|----------:|----------:|----------:|----------:|----------:|----------:|----------:|----------:|
| Time (user CPU) (ms) * | 90 | 100 | 100 | 100 | 100 | 110 | 110 | 51 |
### IPv6_String_Decoding_Uncompressed_2M
| Metric | p0 | p25 | p50 | p75 | p90 | p99 | p100 | Samples |
|:-----------------------|----------:|----------:|----------:|----------:|----------:|----------:|----------:|----------:|
| Time (user CPU) (ms) * | 120 | 120 | 130 | 130 | 130 | 130 | 130 | 40 |
### IPv6_String_Decoding_Zero_Compressed_10M
| Metric | p0 | p25 | p50 | p75 | p90 | p99 | p100 | Samples |
|:-----------------------|----------:|----------:|----------:|----------:|----------:|----------:|----------:|----------:|
| Time (user CPU) (ms) * | 80 | 80 | 80 | 90 | 90 | 90 | 90 | 60 |
### IPv6_String_Decoding_Zero_Uncompressed_2M
| Metric | p0 | p25 | p50 | p75 | p90 | p99 | p100 | Samples |
|:-----------------------|----------:|----------:|----------:|----------:|----------:|----------:|----------:|----------:|
| Time (user CPU) (ms) * | 120 | 120 | 130 | 130 | 130 | 130 | 130 | 40 |
### IPv6_String_Encoding_Localhost_10M
| Metric | p0 | p25 | p50 | p75 | p90 | p99 | p100 | Samples |
|:-----------------------|----------:|----------:|----------:|----------:|----------:|----------:|----------:|----------:|
| Time (user CPU) (ms) * | 150 | 150 | 150 | 160 | 160 | 160 | 160 | 33 |
### IPv6_String_Encoding_Max_4M
| Metric | p0 | p25 | p50 | p75 | p90 | p99 | p100 | Samples |
|:-----------------------|----------:|----------:|----------:|----------:|----------:|----------:|----------:|----------:|
| Time (user CPU) (ms) * | 200 | 210 | 210 | 210 | 210 | 220 | 220 | 24 |
### IPv6_String_Encoding_Mixed_4M
| Metric | p0 | p25 | p50 | p75 | p90 | p99 | p100 | Samples |
|:-----------------------|----------:|----------:|----------:|----------:|----------:|----------:|----------:|----------:|
| Time (user CPU) (ms) * | 180 | 180 | 190 | 190 | 190 | 190 | 190 | 27 |
### IPv6_String_Encoding_Mixed_Malloc
| Metric | p0 | p25 | p50 | p75 | p90 | p99 | p100 | Samples |
|:-----------------------|----------:|----------:|----------:|----------:|----------:|----------:|----------:|----------:|
| Malloc (total) * | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 10 |
### IPv6_String_Encoding_Zero_10M
| Metric | p0 | p25 | p50 | p75 | p90 | p99 | p100 | Samples |
|:-----------------------|----------:|----------:|----------:|----------:|----------:|----------:|----------:|----------:|
| Time (user CPU) (ms) * | 170 | 170 | 180 | 180 | 180 | 180 | 180 | 29 |
Some examples from the results above:
IPv6_String_Decoding_2_Groups_Compressed_In_The_Middle_2M
([2001:0db8:85a3::8a2e:0370:7334]
) takes 110ms, meaning ~18 million + rounds in a second.
IPv4_String_Decoding_Local_Broadcast_10M
(255.255.255.255
) takes 200ms, meaning ~50 million + rounds in a second.
Right now the only thing that is not decently-optimized is the IDNA implementation, which is only used when a domain name is not in simple ASCII.
The implementation passes the whole Unicode 17’s IDNA test suite with 6400 tests, in an extensive way, but I haven’t yet gone for making sure the implementation is optimized.
I don’t expect a package like this to have a massive impact. I’m mostly just hoping for some refined APIs in different packages. For example if a library is taking an argument for an address to another host, It should be able to use these types to make sure an incorrect value is harder to pass to the library, while having better performance in some situations.
So … what do you-all think? Would such a package benefit the community? Would you do this in another way?