URLSession and IPv6
During day 60 of Paul Hudson's Hacking With SwiftUI you use URLSession() to download friendface.json from www.hackingwithswift.com.
Here's an example of the code to download the JSON:
func loadData() {
guard let url = URL(string: "https://www.hackingwithswift.com/samples/friendface.json") else {
debugPrint("Invalid URL")
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data {
if let decodedResponse = try? JSONDecoder().decode([User].self, from: data) {
DispatchQueue.main.async {
self.users = decodedResponse
}
return
} }
debugPrint("fetch failed: \(error?.localizedDescription ?? "Unknown error")")
}.resume()
}
It turns out that www.hackingwithswift.com is available via both IPv4 and IPv6:
~ % host www.hackingwithswift.com
www.hackingwithswift.com has address 178.79.182.10
www.hackingwithswift.com has IPv6 address 2a01:7e00::f03c:91ff:fe2e:1654
In addition, I've previously tested that my home network has IPv6 connectivity with whatismyv6.com or test-ipv6.com
Lets capture and analyze a tcpdump of the traffic
% sudo tcpdump port 53 or host 178.79.182.10 or 2a01:7e00::f03c:91ff:fe2e:1654
First we make our DNS requests
14:17:36.948063 IP6 2001:db8:4802:1620:d97b:d0a7:bf39:6d2.49821 > 2001:558:feed::1.53: 13644+ AAAA? www.hackingwithswift.com.
14:17:36.948136 IP6 2001:db8:4802:1620:d97b:d0a7:bf39:6d2.65468 > 2001:558:feed::1.53: 63139+ A? www.hackingwithswift.com.
An AAAA-record maps a hostname to an IPv6 address. An A-record maps a hostname to an IPv4 address. Our Mac immediately requests both the AAAA-record and A-record for www.hackingwithswift.com.
DNS queries can run over IPv4 or IPv6. In our case our Mac chose to run both DNS queries over IPv6 transport, even though the A-record request's purpose was to get an IPv4 address. It is normal (but interesting) that DNS can use IPv6 transport to get information about IPv4 DNS entries (or vice-versa).
We get our first DNS response (which happens to be to our AAAA-request), and immediately open an IPv6 socket
14:17:36.966276 IP6 2001:558:feed::1.53 > 2001:db8:4802:1620:d97b:d0a7:bf39:6d2.49821: 13644 1/0/0 AAAA 2a01:7e00::f03c:91ff:fe2e:1654
14:17:36.980634 IP6 2001:db8:4802:1620:d97b:d0a7:bf39:6d2.49589 > 2a01:7e00::f03c:91ff:fe2e:1654.443: Flags [S], seq 2968916496, win 65535, options [mss 1440,nop,wscale 6,nop,nop,TS val 1458429152 ecr 0,sackOK,eol], length 0
IPv6 tcpdumps can be intimidating and hard to read. Here are the things to look for:
- The first packet's source port is 53 (DNS) and contains an AAAA response including the IPv6 address of www.hackingwithswift.com.
- The second packet is an outbound TCP socket to www.hackingwithswift.com (the same IPv6 address we received in the DNS response). The destination port is 443 (https) and the flag is S for SYN (the first part of the TCP 3-way handshake).
We get our second DNS response (which is our A-request), and open an IPv4 socket after a short delay
14:17:37.019043 IP6 2001:558:feed::1.53 > 2001:db8:4802:1620:d97b:d0a7:bf39:6d2.65468: 63139 1/0/0 A 178.79.182.10
14:17:37.085123 IP 192.168.0.22.49590 > 178.79.182.10.443: Flags [S], seq 1030969591, win 65535, options [mss 1460,nop,wscale 6,nop,nop,TS val 1458429256 ecr 0,sackOK,eol], length 0
Here's what to look for:
- The first packet source port is 53 (DNS) and it includes an A record with www.hackingwithswift.com's IPv4 address (178.79.182.10).
- The second packet is outbound to 178.79.182.10 (www.hackingwithswift.com) destination port 443 (https). It also has the TCP S (SYN) flag, attempting to initiate a TCP socket.
- The second packet is sent 66msec after we received the A-record. This is a longer delay than between the AAAA-record and sending the IPv6 SYN above.
Our IPv6 socket completes the TCP handshake and we start sending data
14:17:37.124348 IP6 2a01:7e00::f03c:91ff:fe2e:1654.443 > 2001:db8:4802:1620:d97b:d0a7:bf39:6d2.49589: Flags [S.], seq 743955647, ack 2968916497, win 28560, options [mss 1440,sackOK,TS val 3277029686 ecr 1458429152,nop,wscale 7], length 0
14:17:37.124398 IP6 2001:db8:4802:1620:d97b:d0a7:bf39:6d2.49589 > 2a01:7e00::f03c:91ff:fe2e:1654.443: Flags [.], ack 1, win 2052, options [nop,nop,TS val 1458429295 ecr 3277029686], length 0
14:17:37.127902 IP6 2001:db8:4802:1620:d97b:d0a7:bf39:6d2.49589 > 2a01:7e00::f03c:91ff:fe2e:1654.443: Flags [P.], seq 1:518, ack 1, win 2052, options [nop,nop,TS val 1458429298 ecr 3277029686], length 517
- Look at the flags: S. (SYN-ACK), . (ACK) P. (Push-ACK) The first two packets complete the 3-way TCP handshake, and then we start sending data.
- Look at the addresses: This TCP socket is over IPv6 transport.
We receive our IPv4 SYN-ACK, but we immediately reset it because we do not need it anymore.
14:17:37.234613 IP 178.79.182.10.443 > 192.168.0.22.49590: Flags [S.], seq 20673728, ack 1030969592, win 28960, options [mss 1460,sackOK,TS val 1850566449 ecr 1458429256,nop,wscale 7], length 0
14:17:37.234651 IP 192.168.0.22.49590 > 178.79.182.10.443: Flags [R], seq 1030969592, win 0, length 0
- The first packet is our IPv4 SYN-ACK. We received it after our IPv6 TCP socket was completed and we started sending data over IPv6.
- Our second packet is an outbound IPv4 Reset (R flag). We sent it less than one 1msec after receiving the SYN-ACK.
Summary
Our URLSession code requested that we download a JSON file from www.hackingwithswift.com, which is a dual-stack IPv4 and IPv6 website. URLSession opened up two TCP sockets: the first over IPv6, and the second (a bit delayed) over IPv4. Once we started receiving data over IPv6, URLSession terminated the (no longer needed) IPv4 socket.
So if you completed the challenge milestone on day 60 of "100 Days of SwiftUI", you are not just a SwiftUI programmer, you are also writing IPv6-enabled client code. The high-level network API's provided by Apple make this automatic.