Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Evaluating the censorship resistance of Apple's iCloud Private Relay #87

Open
gfw-report opened this issue Sep 24, 2021 · 13 comments
Open
Labels

Comments

@gfw-report
Copy link
Contributor

gfw-report commented Sep 24, 2021

Authors: Anonymous, David Fifield, Amir Houmansadr

Date: Friday, September 24, 2021

中文版:评估苹果的iCloud Private Relay的抗封锁能力

This report first appeared on Net4People. We also maintain an up-to-date copy of the report on GFW Report and ntc.party.


On September 20, 2021, Apple released iCloud Private Relay (archive) as a new service on iOS 15, iPadOS 15, and macOS Monterey.

Although Apple does not introduce Private Relay as a censorship circumvention tool, in this post, we attempt to understand the potential value of iCloud Private Relay for censorship circumvention. We first introduce how private relay works based on Apple's documents and our measurement. We then present our empirical observation on its censorship resilience, supported by our measurements in China. As of September 23, 2021, we haven't found any evidence of censorship against it in China. We also discuss its blocking resistance against common censorship methods, including DNS hijacking, SNI filtering, IP blocking, active probing, as well as self-censorship. Finally, we present some important but unanswered questions about the Private Relay.

We do not intend to make this a comprehensive report. Instead, we hope to start off discussions by presenting our thoughts, observations and measurement methods, encouraging more censorship measurement and circumvention enthusiasts to study it.

Main Findings

  • As of September 23, 2021, we haven't found any evidence of censorship against Private Relay in China.
  • The Private Relay can be easily blocked by common censorship methods, including DNS hijacking, (QUIC) SNI filtering, IP blocking. It may be possible to block by active probing as well.
  • The service has been self-censored in many countries, though it is reportedly still usable with a foreign iCloud account.

Introduction

Below is an introduction based on our measurement and our understanding of Apple's documents. In summary, the Private Relay has a two-hop structure, consisting of an ingress relay and an egress relay:

  ------------
 |DNS resolver|
  ------------
       ^
       |
    A mask.icloud.com?
HTTPS mask.icloud.com?
       |
       0
       |
    ------           -------------           ------------           -------
   |client| <==1==> |ingress relay| <==2==> |egress relay| <==3==> |website|
    ------           -------------           ------------           -------
  • Step 0: The client sends two plaintext queries of type A and HTTPS for mask.icloud.com or mask-api.icloud.com to a DNS resolver, asking for the IP addresses of ingress relays.
  • Step 1: The client then selects one IP address from the answers and initiates an encrypted QUIC connection to its port 443.
  • Step 2: According to the document, "[t]he second relay, which is operated by a third-party content provider, generates a temporary IP address, decrypts the name of the website you requested and connects you to the site".
  • Step 3: The traffic between the egress relays and websites is exactly the same as traffic between clients and websites when no Private Relay is used.

Capture traffic between an iPhone and relays

To capture and analyze the traffic from a mobile device, one intuitive way is to set up a VPN that works at the network layer, tunneling all the traffic at the transport layer and above to a (local) server, where tcpdump or wireshark can be run. However, the iCloud Private Relay feature appears to be disabled when a VPN is used.

As an alternative, we set up a WiFi hotspot from the desktop and let the iPhone connect to it. We then captured and analyzed the traffic from the laptop. Below is the script we used to setup the hotspot, which was borrowed from this tutorial.

#!/bin/bash

set -x
set -e

## Source: https://computingforgeeks.com/create-wi-fi-hotspot-on-ubuntu-debian-fedora-centos-arch/

## Change the IFNAME to your Wi-Fi network interface: `ip link show`
IFNAME="wlp4s0"
CON_NAME="MyHotSpot"
PASSWORD="77fdda98a6feaf6cc9"

nmcli con add type wifi ifname "$IFNAME" con-name "$CON_NAME" autoconnect yes ssid "$CON_NAME"

nmcli con modify "$CON_NAME" 802-11-wireless.mode ap 802-11-wireless.band bg ipv4.method shared

nmcli con modify "$CON_NAME" wifi-sec.key-mgmt wpa-psk
nmcli con modify myhotspot wifi-sec.psk "$PASSWORD"

nmcli connection show "$CON_NAME"

nmcli con up "$CON_NAME"

nmcli connection show "$CON_NAME"

When observing the DNS and initial QUIC traffic, we find the following Wireshark filter helpful:

quic.long.packet_type == 0 or udp.port == 53

Measure current censorship and evaluate potential censorship cost

In this section, we measure current censorship in China, and discuss the cost for a censor to detect and block Private Relay using commonly used censorship methods.

DNS hijacking

As introduced above, the client needs to get an IP address of the ingress relay before initiating QUIC connections to it. Since these DNS queries are (possibly intentionally) sent in plaintext, it is vulnerable to the DNS poisoning attack. Actually, Apple suggests DNS hijacking as "[t]he fastest and most reliable way" to block Private Relay:

The fastest and most reliable way to alert users is to return a negative answer from your network’s DNS resolver, preventing DNS resolution for the following hostnames used by Private Relay traffic. Avoid causing DNS resolution timeouts or silently dropping IP packets sent to the Private Relay server, as this can lead to delays on client devices.

mask.icloud.com

mask-h2.icloud.com

In practice, we observed two ways for the client to get an IP address of the resolvers. The first way is:

  1. The client first sends two DNS queries of type A and HTTPS for mask.icloud.com. The responses include a type CNAME answer mask.apple-dns.net, along with many type A answers.
  2. The client appears to select the first answer in the reponses, which is the CNAME one. The client thus has to send another two DNS queries of type A and HTTPS for mask.apple-dns.net.
  3. The client will then select the first answer in the responses.

The second way is:

  1. The client first sent two DNS queries of type A and HTTPS for mask-api.icloud.com. The responses include a type CNAME answer mask-api.fe.apple-dns.net, along with many type A answers.
  2. The client appears to select the first answer in the reponses, which is the CNAME one. The client thus has to send another two DNS queries of type A and HTTPS for mask-api.fe.apple-dns.net.
  3. The client will then select the first answer in the responses.

Note that we did not observe any query of the documented mask-h2.icloud.com. This observation aligns with the finding in this post.

Measuring current DNS censorship in China

Although it is trivial for the GFW to poison the domains mentioned above, we have not been able to observe any DNS poisoning against these domains yet. Specifically, we tested by sending DNS queries from China to the outside, and also from outside to China. You can also test it yourself from the outside of China, exploiting the bi-directional characterisitc of the GFW. It is worth noting that dig does not support type HTTPS queries yet; be careful that dig will fall back to type A queries without a blocking warning for a query like this: dig @1.1.1.1 mask.icloud.com -t HTTPS +timeout=2.

We thus used the follwing script with Scapy. Since 104.193.82.0 is a Chinese IP address without a DNS service running, we would have received responses injected by the GFW if any of the queries were censored.

#!/usr/bin/env python3

# This script is only reponsible for sending DNS queries, but not for receiving responses.
# To observe DNS responses, use tcpdump or wireshark. eg. :
# sudo tcpdump host 104.193.82.0

from scapy.all import *

# https://www.ietf.org/archive/id/draft-ietf-dnsop-svcb-https-07.html#name-the-svcb-record-type
TYPE_HTTPS=65

CHINESE_IP="104.193.82.0"

for qname in ["mask.icloud.com",
              "mask-api.icloud.com",
              "mask.apple-dns.net",
              "mask-api.fe.apple-dns.net",
              "mask-h2.icloud.com"]:
    for qtype in [TYPE_HTTPS, "A", "AAAA"]:
        send(IP(dst=CHINESE_IP)/UDP(dport=53)/DNS(rd=1, qd=DNSQR(qname=qname, qtype=qtype)))

SNI filtering

As explained in this answer, although clienthello messages are encryped in QUIC, the secrets are derived from a fixed salt and the Destination Connection ID field. The Initial packets can thus be easily decrypted. Actually, a newer version of the Wireshark can automate the decryption process for you.

It is therefore possible for censor to decrypt the Inital packets and check the SNI field against mask.icloud.com.

Measuring current (QUIC-)SNI censorship in China

We tested by capturing the initial packets from client and replaying them on a Chinese server. The server responded with QUIC handshake messages and we observed no disruption.

For example, we first saved the following hex stream as quic.hex:

c80000000108bec8eac6d55fe88a08e87fe5dfa21d247700452c6a2a855275bb191ffe213c2ad1e07467f9ed24956172c4bee69e8446049a94fbae38973cf11ce80cc1379237e4a0f610ae2408ac096635b3978dcf21b4c81d96a2e53d9a9b04dc234341869f7ea85dc99e2ea028827257c4b6993a29ae07e9368c22426d1780abcf8c4b5ab8b2e3ba4de878306ecf4a4e5851c2168b8412f9a55fa5971520914f13c4a86106161e19bfa1eff9c08a9b566e1656ebceeb7184d60a0328203e5e66fc16ad8452343dee5e2ad22ba0ef80b978ba62c64ac75826b79d119c5a7bf9859655d79116e3f4069e87269bab7f9d0373d8e98e4c4891eaf621ce073c61f59eeddd828c96d785ff3155083f5ac93263e5496a6a38a2a2e0bbf64041e76a500bf4748143f2b8705c3732dc12b218f428eeadd02c50e71c5ffaa1ca14c483ac44c75d10e98d38ddaa38f38c0ba7af20108967541586c51bccd2765781b123b4a91fed0f32f0b11bfc4ff5beaccb023f7d977787a0d09942f5159da772b9ca5a7c512a8644bb399858cc6ee2a5d5c099be6780a619cbadc6407db320d34179bf1ff94401ef0e134d0d8ae705a468b5dcd7b9c078e72ddba146e947dca7d4968b3fc892e425ef60bec05df120b20f26f340ed134b064b4fddd194fee666ff49c943b82f812c6f57daaa70ef5aa7b511e8d9501a447783a4e7eee709499161c4055941d516f16bc4ed114d90f6d49c1a297484749fa99f84eb309c2743018eeebb71c6050061e4899b94ecc746fb98174ce383a9d250f61d3aca4db9249122763ca03c41a67b616e722f5171e34a610aeb9cfb6c74f8bdd549d1b0fbd4a766dd66dc355de7f55d55e029d495687c149d9bac0eba89276a0c8048a97545c08597ea836917ceaebe2e334d9376f3c3dc29ee6df84508558d2c77a1139907aba7735945846e3a8c4675845e01c7e2cb1370b31221f95e1bd0c8e5ecc9e86bf5658859379e3c752e34d6bf9e0e9481cf9ed5df79b9c756cb904603eaab2478b6aaa5740b28213f2716b2b4769e21d9c7e2d62e9708de9644a3de048745f079717e0a565475d0684be9cb13c261f55832953c37078cde29894b2176eab5157e4262dbba7919ef2c66d0cf86d7de93059e9f013e2e82544dc803dea878e184d248065454c65c26d8c67b7778e229390de7560815e6cdc53cce1fb11d62d9b0ecea890b4310ffcf7cd544ca8d6a1b9eed7b92a93fd6c00d0a2338f66ad77c9220c69437b3651b18899c68a8e59f12dc2f014d70a6ca5b4aa419516fde01079a1f76c3198db4f6229641e5e89b1b6aa9797c27b55f439e98858f9d3eef1ffff6f5e52e9e94468d21e8ab965abb864836be07016fdbb63a24e954b863a98d590033bd163df6a7740d256a0bbaa910e45a8f40877b6b84fd2d8f57604d236e4351bd228dc707fe3538440b2796dbab58183f306912c6104d13ea96c649fd338994b4a2d5ecbfdd66b69b5245763371cc38c92774723f546a27519db4660f5dc6312f5f56edad2dcb77bd3034c8a4a084ee7e57016fea8a5fcb114ee5ae97d55b177dd8b1ccd0508fb6baee6244ccedf2705ba35a760b944acb4b3e0394b5add44e851d18e0400d99b4910cd4cb63311727f4a98289ce4ee960c506b72243fde14cf5d3185cc4b598f080faf9ebc75847dc7126bb90c47368c5408898e7bdaf9cde4f04299043600dcdf850c306c737d4be37c316eed63718804e9972f6c95d79771ada173293b06037f1282f4e79f8116e3d4c5fed2ec6db335faf2b0481b3aa3a0192f9ff3fea35ae1bafe71bbcd07a301fe11638a180b6b202c29dea331ac6a2527587a82175cd7b96033b165b88580e83df7759ebe6586d68d4efe6028403d5d0d700e967ca4908bbd8e4

We then send it to the ingress relay and received a response:

xxd -r -p quic.hex | nc -u mask.icloud.com 443 -v

Note that the payload in the example will not get a reponse from an ingress relay anymore, approximately two days after it was first generated. One can probably still trigger reponses from the ingress relays with freshly generated Initial packets.

(Quic-)TLS fingerprint filtering

As explained above, the clienthello messages in the Initial packets can be easily decrypted. It is thus possible fro censor to conduct censorship based on the TLS fingerprint.

Our observation of the TLS fingerprint of the Private Relay aligns with the findings in this report:

The connection to the relay uses QUIC to port 443/UDP and TLS 1.3. The clienthello includes the server name extension and the server name "mask.icloud.com." Only 3 cipher suites are offered (TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256). The server ends up selecting the AES128 suite. Application Layer Protocol Negotiation (ALPN) is also used, with unsurprisingly HTTP/3 being the only option.

Note that, apart from the 3 cipher suites, we saw a forth Grease ciphersuit (0x2a2a).

As a side note, we also observed two GREASE extensions
0xAAAA and 0X3A3A. They probably do not have anything to do with authentication (if they do, it's something very non-standard Apple is doing). The GREASE extensions turned out to be not uncommon; web browsers send them as well. As expalined in this doc, they were used to "reserve a set of TLS protocol values that may be advertised to ensure peers correctly handle unknown values". In other words, GREASE is meant to provide automated diversity to protocol fields, to prevent the protocol from "rusting shut" by assuming only certain values may appear.

We are also curious, by any chance, tlsfingerprint.io can tell us how unique these (or any) (QUIC) ClientHello fingerprints are? (@sergeyfrolov, @ewust)

Active probing to ingress relay

We find that the ingress relay will respond to a replay of the Initial packets within approximately two days since the packets were generated. We also tried to use quic-go and curl --http3 to send a typical Quic with SNI=mask.apple.com to the ingress relay; however, the ingress relay did not respond anything in this case. We suspected that has something to do with the ALPN extension included in the clienthello sent by the legitimate clients. It may also be possible that the client traffic contains some other authenticators.

IP blocking to ingress relays

As introduced above, we could still receive responses from ingress relays by sending Initial packets from China. This indicates that, at least for the IP addresses we tested, China has not blocked it yet.

However, it could be fairly easy to block the ingress relay IP in serveral ways:

  1. block all IP addresses to which mask.icloud.com, mask-api.icloud.com and mask-h2.icloud.com resolve.
  2. observe QUIC connections with SNI=mask.apple.com and confirm it is indeed an ingress relay using the active probing approach mentioned above. Then block the corresponding IP addresses.

IP-based discrimination against egress relays

Similar to Tor exit relays, which are available publicly, Apple provides an up-to-date lists of egress IP ranges (archive). This list could be easily used by websites to discriminate against Private Relay users, like what Tor users have been suffering from.

Questions not answered yet

How Apple implements the self censorship

Apart from all the possible external censorship methods discussed above, Apple has been conducting self-censorship to prevent users in heavily censored areas from using the Private Relays. It is thus important to understand how Apple implements the self censorship in order to circumvent it.

Specifically, Apple admitted that:

Private Relay isn't available in all countries and regions. If you travel somewhere Private Relay isn't available, it will automatically turn off and will turn on again when you re-enter a country or region that supports it. Private Relay will notify you when it's unavailable and when it's active again.

According to many news sources, these countries include China, Belarus, Colombia, Egypt, Kazakhstan, Saudi Arabia, South Africa, Turkmenistan, Uganda, the Philippines, and Russia.

It remains an unanswered questions on how and what self-censorship has been implemented by Apple. From our testing, it seems that the ingress server does not refuse service based on the geolocation of client IP addresses. However, it is still unclear to us how Apples determines the location of the user and thus refuse to be activated.

One report claimed (archive) that Apple learned users' geo-location from users' IP addresses connected to its certain servers; proxying traffic to these certain servers will activate the Private Relay service.

Another user report claimed (archive) that it is sufficient to activate Private Relay by changing the iCloud region to ones where Private Relay is not self-censored. However, another user in the post failed to activate the service with the same settings.

We note that it is not uncommon for a Chinese iOS circumventor to have a non-Chinese iCloud account. This is because, due to the heavy censorship against circumvention tools in Chinese app stores, it is almost essential to have a non-Chinese iCloud account to install other circumvention tools.

How does the user authentication work?

Apple claims that:

Private Relay validates that the client connecting is an iPhone, iPad, or Mac, so you can be assured that connections are coming from an Apple device.

All connections that use Private Relay validate that the client is an iPhone, iPad, or Mac and that the customer has a valid iCloud+ subscription. Private Relay enforces several anti-abuse and anti-fraud techniques, such as single-use authentication tokens and rate-limiting.

We are curious how (or if) Apple authenticates Priavet Relay users on the ingress and egress relays.

How does the underlying decryption work?

In the introduction section, we mentioned that the Private Relay "has a two-hop structure". However, we do not have know anything more about the underlying structure. For example, is an onion-routing structure used? Amir Houmansadr expressed concerns on the intransparency of the underlying protocol. Further investigation is required to better understand the underlying encryption/decryption mechanism.

Acknowledgement

We thank a person who prefers to stay anonymous for lending us an iPhone for testing.

Contacts

We encourage you to share your comments publicly or privately. Our private contact information can be found at the footer of GFW Report.

R6CG pushed a commit to R6CG/test-lists that referenced this issue Sep 25, 2021
iCloud Private Relay is an onion routing proxy available in some
versions of iOS, iPadOS, and macOS. Apple's advice to network operators
who want to block access to Private Relay is to configure the DNS server
to respond negatively for queries for the two names added in this
commit, mask.icloud.com and mask-h2.icloud.com.
https://developer.apple.com/support/prepare-your-network-for-icloud-private-relay
Heading "Allow for network traffic audits".

Some caveats to adding these domain names to test-lists:
* We really only care to test a DNS resolution for these names, not
  actually attempt to fetch a web page. A similar situation applied with
  use-application-dns.net (citizenlab#504).
* The documented protocol of Private Relay is anyway UDP+QUIC (HTTP/3),
  not TCP+TLS. I am guessing, purely based on the name, that
  mask-h2.icloud.com may be an HTTP/2-based fallback for
  mask.icloud.com.
* Apple clients reportedly send not only A queries for these names but
  also HTTPS (RR type 65) queries.
  net4people/bbs#87
  https://datatracker.ietf.org/doc/html/draft-ietf-dnsop-svcb-https-07#section-14.2

Being able to resolve mask.icloud.com and mask-h2.icloud.com does not
guarantee that Private Relay will actually work. Apple self-censors the
service in certain geographic regions. I don't know how Apple's own
restrictions are enforced.

Various other domain names may be involved in a Private Relay
connection. This commit includes only the ones that are documented by
Apple to use for blocking access.
* mask.apple-dns.net: mask.icloud.com is a CNAME for this name.
* mask-t.apple-dns.net: mask-h2.icloud.com is a CNAME for this name.
* mask-api.icloud.com: Apple clients were seen to make queries for this
  name in experiments.
* mask-api.fe.apple-dns.net: mask-api.icloud.com is a CNAME for this
  name.
@wkrp
Copy link
Member

wkrp commented Sep 25, 2021

send a typical Quic with SNI=mask.apple.com

Is this a typo for mask.icloud.com? An incorrect SNI may explain why you did not get any responses.

The client first sends two DNS queries of type A and HTTPS

There are posts about Apple clients querying for the HTTPS RR type even in iOS 14. That type of query may just be a part of their HTTPS stack.

https://support.opendns.com/hc/en-us/community/posts/360074584612-iOS-14-issuing-Type-65-RR-HTTPS-requests-which-OpenDNS-does-not-handle-
https://community.cloudflare.com/t/apple-with-ios-14-pokes-a-hole-in-dns-filtering-with-query-type-65/225029

Besides the CNAME aliases you mentioned (mask.icloud.com→mask.apple-dns.net and mask-api.icloud.com→mask-api.fe.apple-dns.net), there is also mask-h2.icloud.com→mask-t.apple-dns.net.

We suspected that has something to do with the ALPN extension included in the clienthello sent by the legitimate clients.

What was the ALPN of legitimate clients? You may be able to get a text dissection of Client Hellos with the following command (works for TLS, not sure about QUIC):

tshark -V -2 -R ssl.handshake.ciphersuites -r input.pcap

A CNET story (archive) of 2021-09-19 contains a tweet (archive) by @letoams of 2021-06-08 that says Private Relay uses MASQUE:

It's really awesome that iCloud Private Relay uses protocols Apple helped develop / spearheaded at the #ietf for standardization - It is using MASQUE (https://datatracker.ietf.org/wg/masque/about/) with Oblivious DoH https://datatracker.ietf.org/doc/html/draft-pauly-dprive-oblivious-doh-06) using QUIC and HTTP/3

The overall purpose of MASQUE is to enable various forms of proxying over HTTP, especially HTTP/3 (which uses QUIC). You may be able to understand more about what is going on by reading the MASQUE specifications. Perhaps someone from ietf-wg-masque can help shed light on how MASQUE is used in Private Relay.

I have not followed MASQUE in the past year, and as I understand it the project's scope has evolved somewhat, but the early MASQUE drafts call for fallback to HTTP/2 when HTTP/3 is not available. My educated guess is that the mask-h2.icloud.com and mask-t.apple-dns.net endpoints are for HTTP/2 fallback. This would explain why mask-h2.icloud.com was not observed in your experiments—as a fallback, it would only be used if the QUIC endpoint were unavailable. More evidence is the fact that TCP port 443 is closed on mask.icloud.com but open on mask-h2.icloud.com:

nmap -Pn -p 443 -sSU --resolve-all --reason mask.icloud.com mask-h2.icloud.com
Starting Nmap 7.70 ( https://nmap.org ) at 2021-09-25 17:33 UTC
Nmap scan report for mask.icloud.com (172.224.77.10)
Host is up, received user-set (0.12s latency).
Other addresses for mask.icloud.com (not scanned): 2a02:26f7:7c:0:ace0:4d05:: 2a02:26f7:7c:0:ace0:4d0f:: 2a02:26f7:7c:0:ace0:4d06:: 2a02:26f7:7c:0:ace0:4d07:: 2a02:26f7:7c:0:ace0:4d0b:: 2a02:26f7:7c:0:ace0:4d0d:: 2a02:26f7:7c:0:ace0:4d08:: 2a02:26f7:7c:0:ace0:4d0e::
rDNS record for 172.224.77.10: a172-224-77-10.source.akaquill.net

PORT    STATE  SERVICE REASON
443/tcp closed https   reset ttl 52
443/udp open   https   udp-response ttl 53

Nmap scan report for mask.icloud.com (172.224.77.9)
Host is up, received user-set (0.11s latency).
Other addresses for mask.icloud.com (not scanned): 2a02:26f7:7c:0:ace0:4d05:: 2a02:26f7:7c:0:ace0:4d0f:: 2a02:26f7:7c:0:ace0:4d06:: 2a02:26f7:7c:0:ace0:4d07:: 2a02:26f7:7c:0:ace0:4d0b:: 2a02:26f7:7c:0:ace0:4d0d:: 2a02:26f7:7c:0:ace0:4d08:: 2a02:26f7:7c:0:ace0:4d0e::
rDNS record for 172.224.77.9: a172-224-77-9.source.akaquill.net

PORT    STATE  SERVICE REASON
443/tcp closed https   reset ttl 52
443/udp open   https   udp-response ttl 53

Nmap scan report for mask.icloud.com (172.224.77.15)
Host is up, received user-set (0.13s latency).
Other addresses for mask.icloud.com (not scanned): 2a02:26f7:7c:0:ace0:4d05:: 2a02:26f7:7c:0:ace0:4d0f:: 2a02:26f7:7c:0:ace0:4d06:: 2a02:26f7:7c:0:ace0:4d07:: 2a02:26f7:7c:0:ace0:4d0b:: 2a02:26f7:7c:0:ace0:4d0d:: 2a02:26f7:7c:0:ace0:4d08:: 2a02:26f7:7c:0:ace0:4d0e::
rDNS record for 172.224.77.15: a172-224-77-15.source.akaquill.net

PORT    STATE  SERVICE REASON
443/tcp closed https   reset ttl 52
443/udp open   https   udp-response ttl 53

Nmap scan report for mask.icloud.com (172.224.77.13)
Host is up, received user-set (0.13s latency).
Other addresses for mask.icloud.com (not scanned): 2a02:26f7:7c:0:ace0:4d05:: 2a02:26f7:7c:0:ace0:4d0f:: 2a02:26f7:7c:0:ace0:4d06:: 2a02:26f7:7c:0:ace0:4d07:: 2a02:26f7:7c:0:ace0:4d0b:: 2a02:26f7:7c:0:ace0:4d0d:: 2a02:26f7:7c:0:ace0:4d08:: 2a02:26f7:7c:0:ace0:4d0e::
rDNS record for 172.224.77.13: a172-224-77-13.source.akaquill.net

PORT    STATE  SERVICE REASON
443/tcp closed https   reset ttl 52
443/udp open   https   udp-response ttl 53

Nmap scan report for mask.icloud.com (172.224.77.8)
Host is up, received user-set (0.11s latency).
Other addresses for mask.icloud.com (not scanned): 2a02:26f7:7c:0:ace0:4d05:: 2a02:26f7:7c:0:ace0:4d0f:: 2a02:26f7:7c:0:ace0:4d06:: 2a02:26f7:7c:0:ace0:4d07:: 2a02:26f7:7c:0:ace0:4d0b:: 2a02:26f7:7c:0:ace0:4d0d:: 2a02:26f7:7c:0:ace0:4d08:: 2a02:26f7:7c:0:ace0:4d0e::
rDNS record for 172.224.77.8: a172-224-77-8.source.akaquill.net

PORT    STATE  SERVICE REASON
443/tcp closed https   reset ttl 52
443/udp open   https   udp-response ttl 53

Nmap scan report for mask.icloud.com (172.224.77.12)
Host is up, received user-set (0.12s latency).
Other addresses for mask.icloud.com (not scanned): 2a02:26f7:7c:0:ace0:4d05:: 2a02:26f7:7c:0:ace0:4d0f:: 2a02:26f7:7c:0:ace0:4d06:: 2a02:26f7:7c:0:ace0:4d07:: 2a02:26f7:7c:0:ace0:4d0b:: 2a02:26f7:7c:0:ace0:4d0d:: 2a02:26f7:7c:0:ace0:4d08:: 2a02:26f7:7c:0:ace0:4d0e::
rDNS record for 172.224.77.12: a172-224-77-12.source.akaquill.net

PORT    STATE  SERVICE REASON
443/tcp closed https   reset ttl 52
443/udp open   https   udp-response ttl 53

Nmap scan report for mask.icloud.com (172.224.77.14)
Host is up, received user-set (0.13s latency).
Other addresses for mask.icloud.com (not scanned): 2a02:26f7:7c:0:ace0:4d05:: 2a02:26f7:7c:0:ace0:4d0f:: 2a02:26f7:7c:0:ace0:4d06:: 2a02:26f7:7c:0:ace0:4d07:: 2a02:26f7:7c:0:ace0:4d0b:: 2a02:26f7:7c:0:ace0:4d0d:: 2a02:26f7:7c:0:ace0:4d08:: 2a02:26f7:7c:0:ace0:4d0e::
rDNS record for 172.224.77.14: a172-224-77-14.source.akaquill.net

PORT    STATE  SERVICE REASON
443/tcp closed https   reset ttl 52
443/udp open   https   udp-response ttl 53

Nmap scan report for mask.icloud.com (172.224.77.11)
Host is up, received user-set (0.12s latency).
Other addresses for mask.icloud.com (not scanned): 2a02:26f7:7c:0:ace0:4d05:: 2a02:26f7:7c:0:ace0:4d0f:: 2a02:26f7:7c:0:ace0:4d06:: 2a02:26f7:7c:0:ace0:4d07:: 2a02:26f7:7c:0:ace0:4d0b:: 2a02:26f7:7c:0:ace0:4d0d:: 2a02:26f7:7c:0:ace0:4d08:: 2a02:26f7:7c:0:ace0:4d0e::
rDNS record for 172.224.77.11: a172-224-77-11.source.akaquill.net

PORT    STATE  SERVICE REASON
443/tcp closed https   reset ttl 52
443/udp open   https   udp-response ttl 53

Nmap scan report for mask-h2.icloud.com (17.248.247.36)
Host is up, received user-set (0.070s latency).
Other addresses for mask-h2.icloud.com (not scanned): 2620:149:a42:700::7 2620:149:a42:700::5 2620:149:a42:700::4 2620:149:a42:701::7 2620:149:a42:701::4 2620:149:a42:701::8 2620:149:a42:701::6 2620:149:a42:700::8

PORT    STATE SERVICE REASON
443/tcp open  https   syn-ack ttl 53
443/udp open  https   udp-response ttl 54

Nmap scan report for mask-h2.icloud.com (17.248.189.7)
Host is up, received user-set (0.069s latency).
Other addresses for mask-h2.icloud.com (not scanned): 2620:149:a42:700::7 2620:149:a42:700::5 2620:149:a42:700::4 2620:149:a42:701::7 2620:149:a42:701::4 2620:149:a42:701::8 2620:149:a42:701::6 2620:149:a42:700::8

PORT    STATE SERVICE REASON
443/tcp open  https   syn-ack ttl 53
443/udp open  https   udp-response ttl 54

Nmap scan report for mask-h2.icloud.com (17.248.189.40)
Host is up, received user-set (0.068s latency).
Other addresses for mask-h2.icloud.com (not scanned): 2620:149:a42:700::7 2620:149:a42:700::5 2620:149:a42:700::4 2620:149:a42:701::7 2620:149:a42:701::4 2620:149:a42:701::8 2620:149:a42:701::6 2620:149:a42:700::8

PORT    STATE SERVICE REASON
443/tcp open  https   syn-ack ttl 53
443/udp open  https   udp-response ttl 54

Nmap scan report for mask-h2.icloud.com (17.248.189.5)
Host is up, received user-set (0.069s latency).
Other addresses for mask-h2.icloud.com (not scanned): 2620:149:a42:700::7 2620:149:a42:700::5 2620:149:a42:700::4 2620:149:a42:701::7 2620:149:a42:701::4 2620:149:a42:701::8 2620:149:a42:701::6 2620:149:a42:700::8

PORT    STATE SERVICE REASON
443/tcp open  https   syn-ack ttl 53
443/udp open  https   udp-response ttl 54

Nmap scan report for mask-h2.icloud.com (17.248.189.36)
Host is up, received user-set (0.070s latency).
Other addresses for mask-h2.icloud.com (not scanned): 2620:149:a42:700::7 2620:149:a42:700::5 2620:149:a42:700::4 2620:149:a42:701::7 2620:149:a42:701::4 2620:149:a42:701::8 2620:149:a42:701::6 2620:149:a42:700::8

PORT    STATE SERVICE REASON
443/tcp open  https   syn-ack ttl 53
443/udp open  https   udp-response ttl 54

Nmap scan report for mask-h2.icloud.com (17.248.247.37)
Host is up, received user-set (0.059s latency).
Other addresses for mask-h2.icloud.com (not scanned): 2620:149:a42:700::7 2620:149:a42:700::5 2620:149:a42:700::4 2620:149:a42:701::7 2620:149:a42:701::4 2620:149:a42:701::8 2620:149:a42:701::6 2620:149:a42:700::8

PORT    STATE SERVICE REASON
443/tcp open  https   syn-ack ttl 53
443/udp open  https   udp-response ttl 54

Nmap scan report for mask-h2.icloud.com (17.248.189.4)
Host is up, received user-set (0.068s latency).
Other addresses for mask-h2.icloud.com (not scanned): 2620:149:a42:700::7 2620:149:a42:700::5 2620:149:a42:700::4 2620:149:a42:701::7 2620:149:a42:701::4 2620:149:a42:701::8 2620:149:a42:701::6 2620:149:a42:700::8

PORT    STATE SERVICE REASON
443/tcp open  https   syn-ack ttl 53
443/udp open  https   udp-response ttl 54

Nmap scan report for mask-h2.icloud.com (17.248.189.39)
Host is up, received user-set (0.068s latency).
Other addresses for mask-h2.icloud.com (not scanned): 2620:149:a42:700::7 2620:149:a42:700::5 2620:149:a42:700::4 2620:149:a42:701::7 2620:149:a42:701::4 2620:149:a42:701::8 2620:149:a42:701::6 2620:149:a42:700::8

PORT    STATE SERVICE REASON
443/tcp open  https   syn-ack ttl 53
443/udp open  https   udp-response ttl 54

Nmap done: 16 IP addresses (16 hosts up) scanned in 1.34 seconds

@wkrp wkrp added the China label Sep 25, 2021
@klzgrad
Copy link

klzgrad commented Sep 30, 2021

although clienthello messages are encryped in QUIC, the secrets are derived from a fixed salt

Interesting, what's the point then? Making life hard for firewalls?

It is using MASQUE

Doesn't a public directory of proxies give them away? MASQUE is supposed to be about probe resistance.

@gfw-report
Copy link
Contributor Author

Is this a typo for mask.icloud.com? An incorrect SNI may explain why you did not get any responses.

Good catch! It is indeed a typo. However, we still couldn't get a response after using the correct SNI mask.icloud.com with curl --http3. Here is how one can reproduce it.

First we used the following script to compile curl that supports --http3. The script was borrowed from this documentation. The compiled curl binary will locate at ./curl/src/curl, relative to the path of the script. Although it may take a while to compile, the process should be smooth.

Script to compile curl that supports http3.
#!/bin/bash

# This script is used to compile curl that supports HTTP3.
# It is a slightly modified version of:
# https://github.com/curl/curl/blob/master/docs/HTTP3.md
# The compiled curl binary will be at: ./curl/src/curl

set -x
set -e

cd "$(dirname "$0")" || exit

ROOT_DIR="$(pwd)"

INSTALL_DIR_OPENSSL=$(readlink -f "./installdir_openssl")
INSTALL_DIR_NGHTTP3=$(readlink -f "./installdir_nghttp3")
INSTALL_DIR_NGTCP2=$(readlink -f "./installdir_ngtcp2")

# For Debian-based:
sudo apt update && sudo apt install -y dh-autoreconf pkg-config perl

# For Fedora:
# sudo dnf install -y dh-autoreconf pkg-config perl-core

# Build OpenSSL
function build_openssl() {
    cd "$ROOT_DIR"
    git clone --depth 1 -b OpenSSL_1_1_1k+quic https://github.com/quictls/openssl
    cd openssl
    ./config enable-tls1_3 --prefix="$INSTALL_DIR_OPENSSL"
    make
    make install_sw
    cd "$ROOT_DIR"
}

# Build nghttp3
function build_nghttp3() {
    cd "$ROOT_DIR"
    git clone https://github.com/ngtcp2/nghttp3
    cd nghttp3
    # had to use -I ./m4
    autoreconf -i -I ./m4
    ./configure --prefix="$INSTALL_DIR_NGHTTP3" --enable-lib-only
    make
    make install
    cd "$ROOT_DIR"
}

# Build ngtcp2
function build_ngtcp2() {
    cd "$ROOT_DIR"
    git clone https://github.com/ngtcp2/ngtcp2
    cd ngtcp2
    autoreconf -i
    ./configure PKG_CONFIG_PATH="${INSTALL_DIR_OPENSSL}/lib/pkgconfig:${INSTALL_DIR_NGHTTP3}/lib/pkgconfig" LDFLAGS="-Wl,-rpath,${INSTALL_DIR_OPENSSL}/lib" --prefix="$INSTALL_DIR_NGTCP2" --enable-lib-only
    make
    make install
    cd "$ROOT_DIR"
}

# Build curl
function build_curl() {
    cd "$ROOT_DIR"
    git clone https://github.com/curl/curl
    cd curl
    autoreconf -fi
    LDFLAGS="-Wl,-rpath,${INSTALL_DIR_OPENSSL}/lib" ./configure --with-openssl="$INSTALL_DIR_OPENSSL" --with-nghttp3="$INSTALL_DIR_NGHTTP3" --with-ngtcp2="$INSTALL_DIR_NGTCP2"
    make
    cd "$ROOT_DIR"
}

build_openssl
build_nghttp3
build_ngtcp2

build_curl

# https://github.com/cloudflare/quiche/issues/319#issuecomment-782152550
sudo ldconfig -v

# show the compiled version
./curl/src/curl -V

Second, check the version of curl and libcurl.

./curl/src/curl -V
curl 7.80.0-DEV (x86_64-pc-linux-gnu) libcurl/7.80.0-DEV OpenSSL/1.1.1k zlib/1.2.11 ngtcp2/0.1.0-DEV nghttp3/0.1.0-DEV
Release-Date: [unreleased]
Protocols: dict file ftp ftps gopher gophers http https imap imaps mqtt pop3 pop3s rtsp smb smbs smtp smtps telnet tftp
Features: alt-svc AsynchDNS HSTS HTTP3 HTTPS-proxy IPv6 Largefile libz NTLM NTLM_WB SSL TLS-SRP UnixSockets

Third, open tcpdump or wireshark to capture the traffic.

Forth, the following command should successfully fetch a normal HTTP3 supported website:

curl --http3 https://nghttp2.org:4433/

Fifth, the ingress Private Relay will not respond anything, though the curl will try sending initial packets several times:

curl --http3 https://mask.icloud.com

What was the ALPN of legitimate clients? You may be able to get a text dissection of Client Hellos with the following command (works for TLS, not sure about QUIC):
tshark -V -2 -R ssl.handshake.ciphersuites -r input.pcap

Thank you for sharing how to get a text dissection of ClinetHellos. It turned out a newer version of tshark can extract the ClientHellos in QUIC with exactly the same command above.

It seems that the ALPN offered by both clients were very similar: one is h3-29; while the other is h3.

The ALPN sent by curl was:

ALPN Protocol
	ALPN string length: 5
	ALPN Next Protocol: h3-29

The ALPN sent by the legitimate client was:

ALPN Protocol
	ALPN string length: 2
	ALPN Next Protocol: h3

I am not sure if this small difference was the reason why the ingress Private Relay did not respond to curl. I have not found a way to control the ALPN value, except crafting the packets manually.

Below are the text dissections of the QUIC payload sent by curl and a legitimate client. The Private Relay responded to the legitimate client, but not to curl.

I compared the extensions and the corresponding values of these two payload. It turns out that there are at least two major differences. I don't know which or any of these differences causes the different reactions from the ingress Private Relay.

First, the Quic versions sent by curl and legitimate client were: Version: draft-29 (0xff00001d) and Version: 1 (0x00000001) respectively.

Second, the key_shared values sent by curl and the legitimate client were: secp256r1 (23) and x25519 (29) respectively. However, if the ingress Private Relay does not support secp256r1, it should have sent a HelloRetryRequest or QUIC's retry features, rather than not responding.

tshark -V -2 -R ssl.handshake.ciphersuites -r curl.pcap
[more content...]
QUIC IETF
    QUIC Connection information
        [Connection Number: 0]
    [Packet Length: 1200]
    1... .... = Header Form: Long Header (1)
    .1.. .... = Fixed Bit: True
    ..00 .... = Packet Type: Initial (0)
    .... 00.. = Reserved: 0
    .... ..00 = Packet Number Length: 1 bytes (0)
    Version: draft-29 (0xff00001d)
    Destination Connection ID Length: 20
    Destination Connection ID: [REDACTED]
    Source Connection ID Length: 20
    Source Connection ID: [REDACTED]
    Token Length: 0
    Length: 1150
    Packet Number: 0
    Payload: 2a870c4366b75bdeae3d4497b508fcf8fb426383c8e5a3319b3e538ed26e3d66be112290…
    TLSv1.3 Record Layer: Handshake Protocol: Client Hello
        Frame Type: CRYPTO (0x0000000000000006)
        Offset: 0
        Length: 306
        Crypto Data
        Handshake Protocol: Client Hello
            Handshake Type: Client Hello (1)
            Length: 302
            Version: TLS 1.2 (0x0303)
            Random: [REDACTED]
            Session ID Length: 0
            Cipher Suites Length: 10
            Cipher Suites (5 suites)
                Cipher Suite: TLS_AES_128_GCM_SHA256 (0x1301)
                Cipher Suite: TLS_AES_256_GCM_SHA384 (0x1302)
                Cipher Suite: TLS_CHACHA20_POLY1305_SHA256 (0x1303)
                Cipher Suite: TLS_AES_128_CCM_SHA256 (0x1304)
                Cipher Suite: TLS_EMPTY_RENEGOTIATION_INFO_SCSV (0x00ff)
            Compression Methods Length: 1
            Compression Methods (1 method)
                Compression Method: null (0)
            Extensions Length: 251
            Extension: server_name (len=20)
                Type: server_name (0)
                Length: 20
                Server Name Indication extension
                    Server Name list length: 18
                    Server Name Type: host_name (0)
                    Server Name length: 15
                    Server Name: mask.icloud.com
            Extension: ec_point_formats (len=4)
                Type: ec_point_formats (11)
                Length: 4
                EC point formats Length: 3
                Elliptic curves point formats (3)
                    EC point format: uncompressed (0)
                    EC point format: ansiX962_compressed_prime (1)
                    EC point format: ansiX962_compressed_char2 (2)
            Extension: supported_groups (len=10)
                Type: supported_groups (10)
                Length: 10
                Supported Groups List Length: 8
                Supported Groups (4 groups)
                    Supported Group: secp256r1 (0x0017)
                    Supported Group: x25519 (0x001d)
                    Supported Group: secp384r1 (0x0018)
                    Supported Group: secp521r1 (0x0019)
            Extension: session_ticket (len=0)
                Type: session_ticket (35)
                Length: 0
                Data (0 bytes)
            Extension: application_layer_protocol_negotiation (len=8)
                Type: application_layer_protocol_negotiation (16)
                Length: 8
                ALPN Extension Length: 6
                ALPN Protocol
                    ALPN string length: 5
                    ALPN Next Protocol: h3-29
            Extension: encrypt_then_mac (len=0)
                Type: encrypt_then_mac (22)
                Length: 0
            Extension: extended_master_secret (len=0)
                Type: extended_master_secret (23)
                Length: 0
            Extension: signature_algorithms (len=30)
                Type: signature_algorithms (13)
                Length: 30
                Signature Hash Algorithms Length: 28
                Signature Hash Algorithms (14 algorithms)
                    Signature Algorithm: ecdsa_secp256r1_sha256 (0x0403)
                        Signature Hash Algorithm Hash: SHA256 (4)
                        Signature Hash Algorithm Signature: ECDSA (3)
                    Signature Algorithm: ecdsa_secp384r1_sha384 (0x0503)
                        Signature Hash Algorithm Hash: SHA384 (5)
                        Signature Hash Algorithm Signature: ECDSA (3)
                    Signature Algorithm: ecdsa_secp521r1_sha512 (0x0603)
                        Signature Hash Algorithm Hash: SHA512 (6)
                        Signature Hash Algorithm Signature: ECDSA (3)
                    Signature Algorithm: ed25519 (0x0807)
                        Signature Hash Algorithm Hash: Unknown (8)
                        Signature Hash Algorithm Signature: Unknown (7)
                    Signature Algorithm: ed448 (0x0808)
                        Signature Hash Algorithm Hash: Unknown (8)
                        Signature Hash Algorithm Signature: Unknown (8)
                    Signature Algorithm: rsa_pss_pss_sha256 (0x0809)
                        Signature Hash Algorithm Hash: Unknown (8)
                        Signature Hash Algorithm Signature: Unknown (9)
                    Signature Algorithm: rsa_pss_pss_sha384 (0x080a)
                        Signature Hash Algorithm Hash: Unknown (8)
                        Signature Hash Algorithm Signature: Unknown (10)
                    Signature Algorithm: rsa_pss_pss_sha512 (0x080b)
                        Signature Hash Algorithm Hash: Unknown (8)
                        Signature Hash Algorithm Signature: Unknown (11)
                    Signature Algorithm: rsa_pss_rsae_sha256 (0x0804)
                        Signature Hash Algorithm Hash: Unknown (8)
                        Signature Hash Algorithm Signature: Unknown (4)
                    Signature Algorithm: rsa_pss_rsae_sha384 (0x0805)
                        Signature Hash Algorithm Hash: Unknown (8)
                        Signature Hash Algorithm Signature: Unknown (5)
                    Signature Algorithm: rsa_pss_rsae_sha512 (0x0806)
                        Signature Hash Algorithm Hash: Unknown (8)
                        Signature Hash Algorithm Signature: Unknown (6)
                    Signature Algorithm: rsa_pkcs1_sha256 (0x0401)
                        Signature Hash Algorithm Hash: SHA256 (4)
                        Signature Hash Algorithm Signature: RSA (1)
                    Signature Algorithm: rsa_pkcs1_sha384 (0x0501)
                        Signature Hash Algorithm Hash: SHA384 (5)
                        Signature Hash Algorithm Signature: RSA (1)
                    Signature Algorithm: rsa_pkcs1_sha512 (0x0601)
                        Signature Hash Algorithm Hash: SHA512 (6)
                        Signature Hash Algorithm Signature: RSA (1)
            Extension: supported_versions (len=3)
                Type: supported_versions (43)
                Length: 3
                Supported Versions length: 2
                Supported Version: TLS 1.3 (0x0304)
            Extension: psk_key_exchange_modes (len=2)
                Type: psk_key_exchange_modes (45)
                Length: 2
                PSK Key Exchange Modes Length: 1
                PSK Key Exchange Mode: PSK with (EC)DHE key establishment (psk_dhe_ke) (1)
            Extension: key_share (len=71)
                Type: key_share (51)
                Length: 71
                Key Share extension
                    Client Key Share Length: 69
                    Key Share Entry: Group: secp256r1, Key Exchange length: 65
                        Group: secp256r1 (23)
                        Key Exchange Length: 65
                        Key Exchange: [REDACTED]
            Extension: quic_transport_parameters (drafts version) (len=55)
                Type: quic_transport_parameters (drafts version) (65445)
                Length: 55
                Parameter: initial_source_connection_id (len=20)
                    Type: initial_source_connection_id (0x0f)
                    Length: 20
                    Value: [REDACTED]
                    Initial Source Connection ID: [REDACTED]
                Parameter: initial_max_stream_data_bidi_local (len=4) 102400
                    Type: initial_max_stream_data_bidi_local (0x05)
                    Length: 4
                    Value: 80019000
                    initial_max_stream_data_bidi_local: 102400
                Parameter: initial_max_stream_data_bidi_remote (len=4) 262144
                    Type: initial_max_stream_data_bidi_remote (0x06)
                    Length: 4
                    Value: 80040000
                    initial_max_stream_data_bidi_remote: 262144
                Parameter: initial_max_stream_data_uni (len=4) 262144
                    Type: initial_max_stream_data_uni (0x07)
                    Length: 4
                    Value: 80040000
                    initial_max_stream_data_uni: 262144
                Parameter: initial_max_data (len=4) 1048576
                    Type: initial_max_data (0x04)
                    Length: 4
                    Value: 80100000
                    initial_max_data: 1048576
                Parameter: initial_max_streams_bidi (len=1) 1
                    Type: initial_max_streams_bidi (0x08)
                    Length: 1
                    Value: 01
                    initial_max_streams_bidi: 1
                Parameter: initial_max_streams_uni (len=1) 3
                    Type: initial_max_streams_uni (0x09)
                    Length: 1
                    Value: 03
                    initial_max_streams_uni: 3
                Parameter: max_idle_timeout (len=1) 0 ms
                    Type: max_idle_timeout (0x01)
                    Length: 1
                    Value: 00
                    max_idle_timeout: 0
    PADDING Length: 823
        Frame Type: PADDING (0x0000000000000000)
        [Padding Length: 823]
tshark -V -2 -R ssl.handshake.ciphersuites -r legit_client.pcap
[more content...]
QUIC IETF
    QUIC Connection information
        [Connection Number: 0]
    [Packet Length: 1350]
    1... .... = Header Form: Long Header (1)
    .1.. .... = Fixed Bit: True
    ..00 .... = Packet Type: Initial (0)
    .... 00.. = Reserved: 0
    .... ..00 = Packet Number Length: 1 bytes (0)
    Version: 1 (0x00000001)
    Destination Connection ID Length: 8
    Destination Connection ID: [REDACTED]
    Source Connection ID Length: 8
    Source Connection ID: [REDACTED]
    Token Length: 0
    Length: 1324
    Packet Number: 0
    Payload: e99522397c948664f5930a0267bdca59d2c1b20465a562d37acd72fae4cc84510fdb60b4…
    TLSv1.3 Record Layer: Handshake Protocol: Client Hello
        Frame Type: CRYPTO (0x0000000000000006)
        Offset: 0
        Length: 291
        Crypto Data
        Handshake Protocol: Client Hello
            Handshake Type: Client Hello (1)
            Length: 287
            Version: TLS 1.2 (0x0303)
            Random: [REDACTED]
            Session ID Length: 0
            Cipher Suites Length: 8
            Cipher Suites (4 suites)
                Cipher Suite: Reserved (GREASE) (0x9a9a)
                Cipher Suite: TLS_AES_128_GCM_SHA256 (0x1301)
                Cipher Suite: TLS_AES_256_GCM_SHA384 (0x1302)
                Cipher Suite: TLS_CHACHA20_POLY1305_SHA256 (0x1303)
            Compression Methods Length: 1
            Compression Methods (1 method)
                Compression Method: null (0)
            Extensions Length: 238
            Extension: Reserved (GREASE) (len=0)
                Type: Reserved (GREASE) (39578)
                Length: 0
                Data: <MISSING>
            Extension: server_name (len=20)
                Type: server_name (0)
                Length: 20
                Server Name Indication extension
                    Server Name list length: 18
                    Server Name Type: host_name (0)
                    Server Name length: 15
                    Server Name: mask.icloud.com
            Extension: supported_groups (len=12)
                Type: supported_groups (10)
                Length: 12
                Supported Groups List Length: 10
                Supported Groups (5 groups)
                    Supported Group: Reserved (GREASE) (0xfafa)
                    Supported Group: x25519 (0x001d)
                    Supported Group: secp256r1 (0x0017)
                    Supported Group: secp384r1 (0x0018)
                    Supported Group: secp521r1 (0x0019)
            Extension: application_layer_protocol_negotiation (len=5)
                Type: application_layer_protocol_negotiation (16)
                Length: 5
                ALPN Extension Length: 3
                ALPN Protocol
                    ALPN string length: 2
                    ALPN Next Protocol: h3
            Extension: status_request (len=5)
                Type: status_request (5)
                Length: 5
                Certificate Status Type: OCSP (1)
                Responder ID list Length: 0
                Request Extensions Length: 0
            Extension: signature_algorithms (len=24)
                Type: signature_algorithms (13)
                Length: 24
                Signature Hash Algorithms Length: 22
                Signature Hash Algorithms (11 algorithms)
                    Signature Algorithm: ecdsa_secp256r1_sha256 (0x0403)
                        Signature Hash Algorithm Hash: SHA256 (4)
                        Signature Hash Algorithm Signature: ECDSA (3)
                    Signature Algorithm: rsa_pss_rsae_sha256 (0x0804)
                        Signature Hash Algorithm Hash: Unknown (8)
                        Signature Hash Algorithm Signature: Unknown (4)
                    Signature Algorithm: rsa_pkcs1_sha256 (0x0401)
                        Signature Hash Algorithm Hash: SHA256 (4)
                        Signature Hash Algorithm Signature: RSA (1)
                    Signature Algorithm: ecdsa_secp384r1_sha384 (0x0503)
                        Signature Hash Algorithm Hash: SHA384 (5)
                        Signature Hash Algorithm Signature: ECDSA (3)
                    Signature Algorithm: ecdsa_sha1 (0x0203)
                        Signature Hash Algorithm Hash: SHA1 (2)
                        Signature Hash Algorithm Signature: ECDSA (3)
                    Signature Algorithm: rsa_pss_rsae_sha384 (0x0805)
                        Signature Hash Algorithm Hash: Unknown (8)
                        Signature Hash Algorithm Signature: Unknown (5)
                    Signature Algorithm: rsa_pss_rsae_sha384 (0x0805)
                        Signature Hash Algorithm Hash: Unknown (8)
                        Signature Hash Algorithm Signature: Unknown (5)
                    Signature Algorithm: rsa_pkcs1_sha384 (0x0501)
                        Signature Hash Algorithm Hash: SHA384 (5)
                        Signature Hash Algorithm Signature: RSA (1)
                    Signature Algorithm: rsa_pss_rsae_sha512 (0x0806)
                        Signature Hash Algorithm Hash: Unknown (8)
                        Signature Hash Algorithm Signature: Unknown (6)
                    Signature Algorithm: rsa_pkcs1_sha512 (0x0601)
                        Signature Hash Algorithm Hash: SHA512 (6)
                        Signature Hash Algorithm Signature: RSA (1)
                    Signature Algorithm: rsa_pkcs1_sha1 (0x0201)
                        Signature Hash Algorithm Hash: SHA1 (2)
                        Signature Hash Algorithm Signature: RSA (1)
            Extension: signed_certificate_timestamp (len=0)
                Type: signed_certificate_timestamp (18)
                Length: 0
            Extension: key_share (len=43)
                Type: key_share (51)
                Length: 43
                Key Share extension
                    Client Key Share Length: 41
                    Key Share Entry: Group: Reserved (GREASE), Key Exchange length: 1
                        Group: Reserved (GREASE) (64250)
                        Key Exchange Length: 1
                        Key Exchange: 00
                    Key Share Entry: Group: x25519, Key Exchange length: 32
                        Group: x25519 (29)
                        Key Exchange Length: 32
                        Key Exchange: [REDACTED]
            Extension: psk_key_exchange_modes (len=2)
                Type: psk_key_exchange_modes (45)
                Length: 2
                PSK Key Exchange Modes Length: 1
                PSK Key Exchange Mode: PSK with (EC)DHE key establishment (psk_dhe_ke) (1)
            Extension: supported_versions (len=5)
                Type: supported_versions (43)
                Length: 5
                Supported Versions length: 4
                Supported Version: Unknown (0x1a1a)
                Supported Version: TLS 1.3 (0x0304)
            Extension: quic_transport_parameters (len=60)
                Type: quic_transport_parameters (57)
                Length: 60
                Parameter: max_idle_timeout (len=4) 30000 ms
                    Type: max_idle_timeout (0x01)
                    Length: 4
                    Value: 80007530
                    max_idle_timeout: 30000
                Parameter: max_udp_payload_size (len=2) 1472
                    Type: max_udp_payload_size (0x03)
                    Length: 2
                    Value: 45c0
                    max_udp_payload_size: 1472
                Parameter: initial_max_data (len=4) 2097152
                    Type: initial_max_data (0x04)
                    Length: 4
                    Value: 80200000
                    initial_max_data: 2097152
                Parameter: initial_max_stream_data_bidi_local (len=4) 131072
                    Type: initial_max_stream_data_bidi_local (0x05)
                    Length: 4
                    Value: 80020000
                    initial_max_stream_data_bidi_local: 131072
                Parameter: initial_max_stream_data_bidi_remote (len=4) 131072
                    Type: initial_max_stream_data_bidi_remote (0x06)
                    Length: 4
                    Value: 80020000
                    initial_max_stream_data_bidi_remote: 131072
                Parameter: initial_max_stream_data_uni (len=4) 131072
                    Type: initial_max_stream_data_uni (0x07)
                    Length: 4
                    Value: 80020000
                    initial_max_stream_data_uni: 131072
                Parameter: initial_max_streams_bidi (len=1) 8
                    Type: initial_max_streams_bidi (0x08)
                    Length: 1
                    Value: 08
                    initial_max_streams_bidi: 8
                Parameter: initial_max_streams_uni (len=1) 8
                    Type: initial_max_streams_uni (0x09)
                    Length: 1
                    Value: 08
                    initial_max_streams_uni: 8
                Parameter: active_connection_id_limit (len=2) 64
                    Type: active_connection_id_limit (0x0e)
                    Length: 2
                    Value: 4040
                    Active Connection ID Limit: 64
                Parameter: initial_source_connection_id (len=8)
                    Type: initial_source_connection_id (0x0f)
                    Length: 8
                    Value: [REDACTED]
                    Initial Source Connection ID: [REDACTED]
                Parameter: max_datagram_frame_size (len=4) 65535
                    Type: max_datagram_frame_size (0x20)
                    Length: 4
                    Value: 8000ffff
                    max_datagram_frame_size: 65535
            Extension: compress_certificate (len=3)
                Type: compress_certificate (27)
                Length: 3
                Algorithms Length: 2
                Algorithm: zlib (1)
            Extension: server_certificate_type (len=2)
                Type: server_certificate_type (20)
                Length: 2
                Certificate Type List Length: 1
                Certificate Type List (1)
                    Certificate Type: Raw Public Key (0x02)
            Extension: Reserved (GREASE) (len=1)
                Type: Reserved (GREASE) (35466)
                Length: 1
                Data: 00
    PADDING Length: 1012
        Frame Type: PADDING (0x0000000000000000)
        [Padding Length: 1012]

My educated guess is that the mask-h2.icloud.com and mask-t.apple-dns.net endpoints are for HTTP/2 fallback. This would explain why mask-h2.icloud.com was not observed in your experiments—as a fallback, it would only be used if the QUIC endpoint were unavailable.

Thank you for your expertise. I will test it out by disrupting the connections to mask.icloud.com and mask-api.icloud.com.

@gfw-report
Copy link
Contributor Author

although clienthello messages are encryped in QUIC, the secrets are derived from a fixed salt

Interesting, what's the point then?

Great question. The doc explains that:

  • All other packets have strong cryptographic protections for confidentiality and integrity, using keys and algorithms negotiated by TLS.

My understanding is that before a shared secret is negotiated by the TLS handshake, there is no way to "completely" encrypted the initial packets.

Making life hard for firewalls?

My imagination is that this seemingly easy to defeat encryption trick will actually raise the cost for censor to analyze and block QUIC traffic.

  • In an initial stage, the censor have to: apply for funding -> implement traffic analysis for QUIC -> testing -> deploying
  • In a later stage, the censor have to: 1) spend extra computation resource to decrypt each QUIC initial packets; 2) update the code everytime a newer QUIC version is used. (eg. the censor may have to track and update the salt value because "future versions of QUIC SHOULD generate a new salt value". )

@wkrp
Copy link
Member

wkrp commented Oct 1, 2021

I have not found a way to control the ALPN value, except crafting the packets manually.

Since you're compiling curl from source, you can probably just change the value there:

https://github.com/curl/curl/blob/e7416cfd2bd58d256b8524f31ef22a43aa23a970/lib/altsvc.c#L53-L59
or
https://github.com/curl/curl/blob/e7416cfd2bd58d256b8524f31ef22a43aa23a970/lib/altsvc.c#L80-L81

It may not actually work beyond the handshake, but you're only trying to get a response anyway. You may be able to change the QUIC version with similar hacking.

You can also try working in the other direction: start with an initial packet that is known to get a response, and change bytes in it until it stops working. But you'd have to undo and redo the initial packet encryption before and after making your changes.

Of course, Apple could conceivably implement whatever requirements they want, such as an exact TLS fingerprint match, or something steganographically encoded into the connection ID. But I'm not seeing any obvious overt authentication in the ClientHello.

The curl packet is padded to 1200 bytes, and the Apple packet is padded to 1350 bytes. It's a long shot, but that packet length is a possibility. It's worth checking if the PADDING frame actually contains all zeroes.

@wkrp
Copy link
Member

wkrp commented Oct 1, 2021

Doesn't a public directory of proxies give them away? MASQUE is supposed to be about probe resistance.

It could be that they only use MASQUE as a convenient and reasonable proxy protocol, not really caring about anti-blocking and indistinguishability features. After all, they publish instructions for how to block Private Relay using DNS.

Anyway, I think the focus of MASQUE has changed over last couple of years. In July 2019, phw summarized:

MASQUE enables circumvention by hiding a circumvention proxy behind a web server, similar to Sergey's httpsproxy. Clients "unlock" the web server's circumvention feature by using the newly-proposed HTTP Transport Authentication Standard.

Crucially, MASQUE defends against active probing by responding with "405 Method Not Allowed" to failed authentication attempts -- the same response one would get for an unexpected CONNECT request. This prevents censors from learning if a web server supports MASQUE.

In July 2020, I asked for comment on a summary of MASQUE for the Turbo Tunnel paper, and David Schinazi replied:

Regarding your description of MASQUE, it reflects my original vision for MASQUE, but that has changed a little bit in the last year of IETF discussions, so I would add some tweaks:

  • Obfuscation is no longer the main goal of MASQUE, though it is a requirement, so I'd rephrase the first sentence to something along the lines of:

    MASQUE is a proposal to colocate proxy servers with existing web servers, over HTTP/2 (TLS/TCP) or HTTP/3 (QUIC/UDP); it allows proxying capability to be indistinguishable from regular Web Traffic.

@gfw-report
Copy link
Contributor Author

Since you're compiling curl from source, you can probably just change the value there:
...
You may be able to change the QUIC version with similar hacking.

Great tips! I tried to change the values of ALPN and QUIC version. It turns out that changing the QUIC version value to 0x00000001 was sufficient to get a response from the ingress Private Relay. In particular, I did:

To quickly find where the value 0xff00001d appears in the source code:

grep --color -ir "0xff00001d"
ngtcp2/lib/includes/ngtcp2/ngtcp2.h:#define NGTCP2_PROTO_VER_DRAFT_MIN 0xff00001du

Replace the value 0xff00001d with 0x000000001:

sed -i 's#NGTCP2_PROTO_VER_DRAFT_MIN 0xff00001du#NGTCP2_PROTO_VER_DRAFT_MIN 0x00000001u#g' ngtcp2/lib/includes/ngtcp2/ngtcp2.h

Note that changing the following two blocks of code from h3-29 to h3 will not influence the ALPN values sent.

https://github.com/curl/curl/blob/e7416cfd2bd58d256b8524f31ef22a43aa23a970/lib/altsvc.c#L53-L59
or
https://github.com/curl/curl/blob/e7416cfd2bd58d256b8524f31ef22a43aa23a970/lib/altsvc.c#L80-L81

There is actually another place in the code the determines the ALPN value. We can change it by:

sed -i 's#\\x5h3-29#\\x2h3#g' ngtcp2/lib/vquic/ngtcp2.c

Although the server responded, the QUIC handshake failed. The decoded QUIC message is:

CONNECTION_CLOSE (Transport) Error code: CRYPTO_ERROR (Missing Extension)
    Frame Type: CONNECTION_CLOSE (Transport) (0x000000000000001c)
    Error code: CRYPTO_ERROR (365)
    TLS Alert Description: Missing Extension (109)
    Frame Type: 0
    Reason phrase Length: 0
    Reason phrase:

What could be the possible "missing extension"(s)? Below are the lists of the extensions included:

$ tshark -V -2 -R ssl.handshake.ciphersuites -Tfields -e tls.handshake.extension.type -r legit_client.pcap
39578,0,10,16,5,13,18,51,45,43,57,27,20,35466

$ tshark -V -2 -R ssl.handshake.ciphersuites -Tfields -e tls.handshake.extension.type -r curl.pcap
0,11,10,35,16,22,23,13,43,45,51,65445

Another dirty but more informative way to compare the lists of extensions:

tshark -V -2 -R ssl.handshake.ciphersuites -r legit_client.pcap | grep "  Type:"
tshark -V -2 -R ssl.handshake.ciphersuites -r curl.pcap | grep "  Type:"

The legitimate clients sent the following four TLS extensions that curl does not send (excluding the GREASE extensions):

Extension: status_request (len=5)
    Type: status_request (5)
    Length: 5
    Certificate Status Type: OCSP (1)
    Responder ID list Length: 0
    Request Extensions Length: 0

Extension: signed_certificate_timestamp (len=0)
    Type: signed_certificate_timestamp (18)
    Length: 0

Extension: compress_certificate (len=3)
    Type: compress_certificate (27)
    Length: 3
    Algorithms Length: 2
    Algorithm: zlib (1)

Extension: server_certificate_type (len=2)
    Type: server_certificate_type (20)
    Length: 2
    Certificate Type List Length: 1
    Certificate Type List (1)
        Certificate Type: Raw Public Key (0x02)

In addition, the legitimate clients sent extension quic_transport_parameters (57) but curl sent extension Type: quic_transport_parameters (drafts version) (65445). I intuitively guessed that the failed handshake might be related to this extension type. However, after replacing all the 0xffa5 (65445) I could find in the source code using grep, curl still sent Type: quic_transport_parameters (drafts version) (65445), rather than (57):

sed -i 's#TLSEXT_TYPE_quic_transport_parameters_draft   0xffa5#TLSEXT_TYPE_quic_transport_parameters_draft   0x0039#g' openssl/include/openssl/tls1.h
sed -i 's#NGTCP2_TLSEXT_QUIC_TRANSPORT_PARAMETERS_DRAFT 0xffa5u#NGTCP2_TLSEXT_QUIC_TRANSPORT_PARAMETERS_DRAFT 0x0039u#g' ngtcp2/lib/includes/ngtcp2/ngtcp2.h

@gfw-report
Copy link
Contributor Author

I intuitively guessed that the failed handshake might be related to this extension type. However, after replacing all the 0xffa5 (65445) I could find in the source code using grep, curl still sent Type: quic_transport_parameters (drafts version) (65445), rather than (57).

After removing all files and recompiling from source, curl does not send any quic_transport_parameters extensions anymore.

Turning off a bit was sufficient to let curl send Type: quic_transport_parameters (57):

sed -i 's#SSL_set_quic_use_legacy_codepoint(qs->ssl, 1)#SSL_set_quic_use_legacy_codepoint(qs->ssl, 0)#g' lib/vquic/ngtcp2.c

However, neither other HTTP3 supported websites, nor the ingress Private Relays would respond.

This is the extension sent by curl:

Extension: quic_transport_parameters (len=55)
    Type: quic_transport_parameters (57)
    Length: 55
    Parameter: initial_source_connection_id (len=20)
    Parameter: initial_max_stream_data_bidi_local (len=4) 102400
    Parameter: initial_max_stream_data_bidi_remote (len=4) 262144
    Parameter: initial_max_stream_data_uni (len=4) 262144
    Parameter: initial_max_data (len=4) 1048576
    Parameter: initial_max_streams_bidi (len=1) 1
    Parameter: initial_max_streams_uni (len=1) 3
    Parameter: max_idle_timeout (len=1) 0 ms

This is the extension sent by legitimate client:

Extension: quic_transport_parameters (len=60)
    Type: quic_transport_parameters (57)
    Length: 60
    Parameter: max_idle_timeout (len=4) 30000 ms
    Parameter: max_udp_payload_size (len=2) 1472
    Parameter: initial_max_data (len=4) 2097152
    Parameter: initial_max_stream_data_bidi_local (len=4) 131072
    Parameter: initial_max_stream_data_bidi_remote (len=4) 131072
    Parameter: initial_max_stream_data_uni (len=4) 131072
    Parameter: initial_max_streams_bidi (len=1) 8
    Parameter: initial_max_streams_uni (len=1) 8
    Parameter: active_connection_id_limit (len=2) 64
    Parameter: initial_source_connection_id (len=8)
    Parameter: max_datagram_frame_size (len=4) 65535

It may not actually work beyond the handshake, but you're only trying to get a response anyway.

Agreed. There isn't much to do even if the handshake succeed.

hellais added a commit to citizenlab/test-lists that referenced this issue Jan 13, 2022
iCloud Private Relay is an onion routing proxy available in some
versions of iOS, iPadOS, and macOS. Apple's advice to network operators
who want to block access to Private Relay is to configure the DNS server
to respond negatively for queries for the two names added in this
commit, mask.icloud.com and mask-h2.icloud.com.
https://developer.apple.com/support/prepare-your-network-for-icloud-private-relay
Heading "Allow for network traffic audits".

Some caveats to adding these domain names to test-lists:
* We really only care to test a DNS resolution for these names, not
  actually attempt to fetch a web page. A similar situation applied with
  use-application-dns.net (#504).
* The documented protocol of Private Relay is anyway UDP+QUIC (HTTP/3),
  not TCP+TLS. I am guessing, purely based on the name, that
  mask-h2.icloud.com may be an HTTP/2-based fallback for
  mask.icloud.com.
* Apple clients reportedly send not only A queries for these names but
  also HTTPS (RR type 65) queries.
  net4people/bbs#87
  https://datatracker.ietf.org/doc/html/draft-ietf-dnsop-svcb-https-07#section-14.2

Being able to resolve mask.icloud.com and mask-h2.icloud.com does not
guarantee that Private Relay will actually work. Apple self-censors the
service in certain geographic regions. I don't know how Apple's own
restrictions are enforced.

Various other domain names may be involved in a Private Relay
connection. This commit includes only the ones that are documented by
Apple to use for blocking access.
* mask.apple-dns.net: mask.icloud.com is a CNAME for this name.
* mask-t.apple-dns.net: mask-h2.icloud.com is a CNAME for this name.
* mask-api.icloud.com: Apple clients were seen to make queries for this
  name in experiments.
* mask-api.fe.apple-dns.net: mask-api.icloud.com is a CNAME for this
  name.

Co-authored-by: David Fifield <david@bamsoftware.com>
Co-authored-by: Arturo Filastò <arturo@openobservatory.org>
@wkrp
Copy link
Member

wkrp commented Jan 16, 2022

In December 2021, Apple published "iCloud Private Relay Overview" that clarifies some details.

https://www.apple.com/privacy/docs/iCloud_Private_Relay_Overview_Dec2021.PDF (archive)

Use of MASQUE:

...Private Relay uses technology being developed by the MASQUE working group at the Internet Engineering Task Force (IETF). Specifically, MASQUE is a way of using HTTP/3 and QUIC as secure proxying technologies.

Private Relay uses both the CONNECT and CONNECT-UDP methods in HTTP/3 to set up connections quickly. For connections to websites that support TLS or QUIC, the initial TLS handshake messages are sent in the same set of data as the proxy request, removing the need to wait for replies from the proxies.

In networks where QUIC traffic is blocked, devices use the CONNECT method of HTTP/2 to communicate with the proxies...

Proxy authentication uses TLS:

To authenticate the proxies, devices validate the raw public key sent in the TLS handshake, and compare it to an expected value shared in an authenticated configuration separately.

Client device authentication is custom:

Private Relay is designed to ensure only valid Apple devices and accounts in good standing are allowed to use the service.

For a device to connect to iCloud Private Relay, it must first be authorized. Authorization is performed by presenting a valid, anonymous token based on RSA blind signatures. These signatures are sent as one-time-use tokens to each proxy when establishing a connection, separating legitimate from illegitimate devices. The proxies can validate the tokens with a public key to validate that the user is legitimate, without actually identifying the user. Tokens and keys are rotated daily to ensure users have authenticated recently. The proxies also perform asynchronous double-spend prevention to stop a token from being shared and used for fraudulent access.

To generate this blind signature, the user’s device connects to an Apple server and is authenticated. To ensure only Apple devices and valid iCloud+ accounts can use Private Relay, the server performs device and account attestation using the Basic Attestation Authority (BAA) server prior to vending out tokens. To mitigate abuse, rate limiting restricts how many tokens a user’s device can retrieve per day.

Use of Oblivious DoH to avoid DNS leaks:

To protect the privacy of DNS name resolution for all queries sent by the device and prevent such tracking, Private Relay uses Oblivious DNS over HTTPS (ODoH).

Egress proxies try to maintain the client's approximate geographic region. The client tells the egress proxy the client's own geohash or country and time zone:

The first internet relay uses a traditional geo-IP lookup to determine which geographic area best represents the user’s original IP address. It then sends this information back to the user’s device in the form of a geohash (truncated to four characters, representing roughly an 800 km2 area).

If the user has selected “Maintain general location,” the user’s device will share the geohash information with the second internet relay. This information allows the second internet relay to select a representative Relay IP address from a pool of addresses assigned to the location.

If “Use country and time zone” is selected, geohash information is not shared and the second internet relay will select a Relay IP address from the much larger region that represents the country and time zone the user is connecting from.

The client's partial IP address is also leaked to the ODoH resolver for EDNS client subnet:

To ensure that DNS answers retrieved over ODoH are correct for the network that the device is on, the device is able to learn its public IP address subnet from the first internet relay and send that value in the encrypted query to the DNS server using the EDNS0 Client Subnet option.

@wkrp
Copy link
Member

wkrp commented Mar 2, 2022

Cloudflare published a blog post stating officially that they are (one of?) the providers of egress nodes for iCloud Private Relay. The ingress nodes are operated by Apple.

https://blog.cloudflare.com/icloud-private-relay/ (archive).

By adding two "relays" (labeled “Ingress Proxy” and “Egress Proxy”), connection metadata is split:

  • The user’s original IP address is visible to the access network (e.g. the coffee shop you’re sitting in, or your home ISP) and the first relay (operated by Apple), but the server or website name is encrypted and not visible to either.
  • Cloudflare-operated relays know only that it is receiving traffic from a Private Relay user, but not specifically who or their client IP address. Cloudflare relays then forward traffic on to the destination server.

The same building blocks that power Cloudflare products were used to build support for Private Relay: our network, 1.1.1.1, Cloudflare Workers, and software like quiche, our open-source QUIC (and now MASQUE) protocol handling library, which now includes proxy support.

Private Relay is designed to preserve IP address to geolocation mapping accuracy, even while preventing tracking and fingerprinting.

At a high-level, here’s how it works:

  • Apple relays geolocate user IP addresses and translate them into a “geohash”. Geohashes are compact representations of latitude and longitude. The system includes protections to ensure geohashes cannot be spoofed by clients, and operates with reduced precision to ensure user privacy is maintained. Apple relays do not send user IP addresses onward.
  • Cloudflare relays maintain a pool of IP addresses for exclusive use by Private Relay. These IP addresses have been registered with geolocation database providers to correspond to specific cities around the world. When a Private Relay user connects and presents the previously determined geohash, the closest matching IP address is selected.
  • Servers see an IP address that corresponds to the original user IP address’s location, without obtaining information that may be used to identify the specific user.

In most parts of the world, Private Relay supports geolocation to the nearest city by default. If users prefer to be located at more coarse location granularity, the option to locate based on country and timezone is available in Private Relay settings.

I may be wrong, but it seems like the fact that Private Relay chooses an egress node located close to the user means that it won't be useful for many forms of circumvention. Any network restrictions that apply to you likely also apply to the egress node, which is located in the same country.

@letoams
Copy link

letoams commented Mar 3, 2022 via email

@gfw-report
Copy link
Contributor Author

but it seems like the fact that Private Relay chooses an egress node located close to the user means that it won't be useful for many forms of circumvention. Any network restrictions that apply to you likely also apply to the egress node, which is located in the same country.

That's a good point. Incidentally, Private Relay can actually help bypass censorship in China. This is probably because Apple and those CDNs do not deploy ingress or egress servers in China, and connections from China thus have to go through some foreign ingress and egress servers.

@gfw-report
Copy link
Contributor Author

To make Private Relay work in China, one needs to:

  1. Login App Store with an Apple ID that belongs to a region where Private Relay is enabled.
  2. Login iCloud with an Apple ID that belongs to a region where Private Relay is enabled.
  3. Make sure that Apple's IP-checking server sees a non-Chinese IP address. This step is necessary because Apple is reportedly to check the IP address of the iPhone every ten minutes; and it only enables Private Relay when the IP address is outside of China.

It appears that the ingress servers do not restrict connections from Chinese IP address. Once the IP-checking server sees a non-Chinese IP address, users are reportedly able to use the Private Relay in the next ten minutes.

Some users (archive) first connected to a proxy to enable Private Relay, then disabled the proxy to only use Private Relay for circumvention. A better way as suggested in this tutorial (archive) is configuring some routing rules to proxy traffic to Apple's IP-checking server.

Going through all the steps above is not easy, but the setup may be useful in some cases. For example, the proxy that connects to Apple's IP-checking servers can have low bandwidth, high latency, and limited traffic volume. But as long as the IP-checking is passed, users can use the fast Private Relay for circumvention.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants