Блог о прокси
open main menu

Dissecting the Internals of REALITY

/ 23 min read

In this guide, we will take a detailed look at setting up VLESS TCP REALITY + FLOW + uTLS, and also examine in detail the mechanisms of their work.

It is necessary to carefully study this guide completely. At each stage, from studying the basics to considering the implementation of various mechanisms and the intricacies of configuration, we will delve into the details of each previous point. This approach will allow you to not only understand the individual aspects of the product but also form a comprehensive and holistic view of its operation and capabilities.

Ultimately, consistent and detailed study of all sections will provide a comprehensive understanding and allow you to effectively use the product in your work.

VLESS

Definition

VLESS is a lightweight, stateless transport protocol that is divided into inbound and outbound parts and can be used as a bridge between a client and an Xray server. The protocol’s authentication method is based on UUID (Universally Unique Identifier).

Request and Response

VLESS has the following request structure:

1 byte16 bytes1 byteM bytes1 byte2 bytes1 byteS bytesX bytes
Protocol VersionCorresponding UUIDLength of additional information MAdditional information in ProtoBufInstructionsPortAddress TypeAddressRequest

VLESS has the following response structure:

1 byte1 byteN bytesY bytes
Protocol version corresponding to the requestLength of additional information NAdditional information in ProtoBufResponse

Transmission Mode

VLESS supports a variety of transmission modes: TCP, gRPC, H2, QUIC, WebSocket, mKCP.

However, due to a combination of factors, which will be discussed a little later, it is recommended to use TCP as the only reliable data transmission mode for REALITY.

VLESS does not provide encryption, so a prerequisite for its use is the presence of a secure channel such as TLS or REALITY.

REALITY

Introduction

REALITY is an improvement over existing TLS technologies, implementing full TLS 1.3 with a specific website SNI for obfuscation while maintaining the appearance of traffic similar to regular TLS 1.3. For ease of further perception, it is important to remember that REALITY is a fork of the TLS package from GOLANG.

Tasks

Key challenges solved during REALITY development:

  1. Resilience to certificate chain attacks
    REALITY improves resilience to certificate chain attacks. In traditional TLS, connection security relies on trust in Certificate Authorities (CAs). Certificate chain attacks are possible if an attacker compromises or forges a certificate issued by a CA. REALITY mitigates this vulnerability by providing additional security mechanisms.

  2. Hiding TLS fingerprints
    TLS fingerprints are unique characteristics that can be used to identify and block certain types of encrypted traffic. In countries with strict Internet censorship, governments can use TLS fingerprints to detect and block traffic. REALITY minimizes these fingerprints, making traffic less recognizable and more resilient to censorship and blocking.

  3. Forward secrecy
    This security principle ensures that even if encryption keys are compromised in the future, attackers will not be able to decrypt past sessions. REALITY supports forward secrecy by ensuring that each session is encrypted with a unique session key.

  4. No domain dependency
    REALITY allows you to specify other people’s websites without having to purchase your own domain or set up a TLS server, which makes it more convenient to use and allows you to present a completely real TLS with a specific SNI to an intermediary.

  5. Compatibility with existing protocols
    REALITY is designed to be compatible with existing protocols, allowing it to be integrated into existing systems without major changes. However, this is not recommended due to the existing and easily recognizable features of TLS.

  6. Transparency for users and servers
    The protocol is designed to operate in a way that is transparent to both clients and servers, making it easy to implement and use.

  7. Support for modern standards
    REALITY supports modern security and encryption standards, providing a high level of data protection.

  8. Flexibility and customization
    The protocol offers various customization options to meet specific security and performance requirements.

REALITY Design Philosophy

Three main principles were followed in the development of REALITY:

  1. Maximum level of security with limited human-controlled factors and minimization of their impact All protocol processes are automated to minimize human intervention, which reduces the risk of errors.
  2. Trust the server, distrust the client, even if the node information stored on the client may be completely compromised Server data and keys are carefully protected, access control is strictly enforced by the server, and critical data is stored only on the server, excluding its compromise.
  3. Server selectivity towards the client For example, the server can refuse to connect outdated versions of Xray-core to prevent vulnerabilities and errors.

Minimum Requirements

Minimum camouflage website requirements:

  1. Foreign website.
  2. TLS 1.3 and H2 support.
  3. An address that is not redirected elsewhere (the domain can be redirected to www).

Additional criteria:

  1. The IP address of the camouflage website is close to the IP address of your server. Having an IP address close to your server means that the camouflage website has an IP address that is close to your server’s IP address in terms of network distance or geographic location. This can make the connection more efficient and less suspicious.

  2. Handshake messages after “Server Hello” are encrypted together. Some websites may not encrypt handshake messages after “Server Hello” together, but instead send them separately in clear text. This can provide information about the server and client, such as the cipher suites they support, extensions, and certificates. Attackers can use this to identify the server and client. Therefore, the server and client must exchange encrypted data after the initial TLS handshake, preventing eavesdropping or interference by third parties.

  3. The server supports OCSP. The server supports OCSP stapling, which means that the server can provide proof of the validity of its certificate without requiring the client to contact the Certificate Authority. This can improve the performance and privacy of the connection.

Principle of operation

There is a misconception that REALITY uses the “certificate stealing” method. REALITY implements full TLS 1.3, which does not require or perform certificate spoofing, since in TLS 1.3 certificates are encrypted and not available for viewing by an intermediary.

After Server Hello, all messages are encrypted, so the REALITY server only intercepts the server handshake messages (Server Hello) from the camouflage site, including their length and timing characteristics, replacing key_share.

It follows that the REALITY client receives a temporary trusted certificate issued by a temporary authentication key, and under normal conditions never receives a real camouflage site certificate.

The general interaction scheme is as follows:

  1. Proxy client -> Proxy server: “Client Hello”
  2. Proxy server -> Camouflage site: “Client Hello”
  3. Camouflage site -> Proxy server: “Server Hello”
  4. Proxy server -> Proxy client: “Server Hello”

The client sends a TLS Client-Hello request to the Reality server. The Reality server redirects this request to the address of the camouflage website. The camouflage site responds with TLS Server-Hello, the server receives it and the length characteristics of subsequent handshake messages, and then forwards it back to the client. If the special fields in the client’s TLS Client-Hello request were correct, the Reality server switches to proxy mode. Further, data transmission begins via VLESS, including authorization by UUID and other related processes.

Data flow

In general, our main logic is to use TLS to disguise proxy traffic among the most common Internet traffic. As they say: “Hide a tree in the forest.” For a normal TLS proxy protocol (for example, Trojan), the user’s proxy client establishes a real TLS connection with the proxy server, and through this encrypted tunnel, the application (for example, a browser) establishes another TLS connection with the target server (for example, Google). At this point, the browser and Google’s server provide end-to-end encryption, and no one (including our proxy) can decrypt or spoof the information being sent.

Here is a quick diagram:

Browser ---- Proxy Client ==== Proxy Server ---- Google

And here we understand that when proxy data passes through the censor, it is actually encrypted twice. However, in this process, 99% of the traffic does not actually require additional encryption. Because data encrypted using TLS 1.3 (and we remember that REALITY = TLS 1.3) looks absolutely identical externally, the censor cannot tell them apart.

Thus, the logic of an ideal proxy should be as follows:

  1. Establishing a TLS connection.
  2. Inside the encrypted tunnel, we monitor the communication between the browser and the site, determining whether the current connection is TLS 1.3.
  3. If not, then we use the usual TLS proxy method, continuing to use the encrypted tunnel.
  4. If yes, then we are waiting for the start of the transmission of encrypted data by both parties, and the proxy client inserts the UUID into the first internal encrypted packet, signaling to the proxy server: we are ready for a “bare” connection!
  5. Starting from the second internal encrypted packet, we no longer perform any operations with the data, but simply copy it.

Congratulations! We have invented xtls-rprx-vision.

We can see that:

  1. The external TLS initiated by the proxy is real, so the censor cannot crack it.
  2. “Bare” traffic on the proxy side is simply copied without changes. If the censor tries to intervene, it will get a standard response from the browser and Google’s server, which will be no different from normal access.
  3. The signal for a “bare” connection is a UUID, which is private information of the proxy, in order to avoid vulnerabilities similar to those that were in Go, associated with the use of code characteristics.

One of the important disadvantages of a conventional TLS proxy is “encryption within encryption”. As discussed above, although the appearance of encrypted packets is indistinguishable to a censor, it is inevitable that the header of each packet will increase with each level of encryption. This increase may be insignificant, but for small amounts of data (such as response packets) it can be more noticeable, and some packets may exceed MTU limits at the network layer. Most importantly, each packet is increased by the same length, which creates certain statistical characteristics.

We can say that when TLS 1.3 data is transferred, 99% of our packets have almost ideal traffic characteristics because they are original data not processed by the proxy.

You probably have a question: why do we say that 99% of packets are original data? What is the remaining 1%? We need to investigate what happens in a typical proxy when it encounters TLS 1.3 traffic in the first few packets.

Clearly, after establishing an encrypted channel:

  1. First packet: Proxy client -> Proxy server: “Hello, the target of this proxy session is Google, here is my UUID.”
  2. Second packet: Proxy client <- Proxy server: “Hi, I got your proxy request, start sending data.”
  3. Third packet: Browser -> Proxy client -> Proxy server -> Google: “Hi Google, I’m going to start an encrypted communication with you, here are my supported encryption methods…” (This the packet is also called TLS Client Hello).
  4. Fourth packet: Browser <- Proxy client <- Proxy server <- Google: “Hi user, here is Google’s certificate, we will use TLS_AES_128_GCM_SHA256 encryption, let’s start an encrypted communication!” (This packet is also called TLS Server Hello).
  5. Fifth packet: Browser -> Proxy client -> Proxy server -> Google: “Got it, let’s start an encrypted communication!”

As you know, proxy protocols are varied, but the basics are the same: if a user uses any TLS connection, the above handshake process must be completed. As mentioned earlier, external TLS encryption can be considered absolutely secure, but for a censor, in addition to cracking information, there is an opportunity to use some additional data to identify these five packets.

The most obvious characteristic of these five packs is their length. In particular:

  1. The first packet: very short, the only variable is the target address.
  2. The second package: very short, almost fixed.
  3. The third package: short, with minor changes, almost the only variable is the target SNI.
  4. Fourth package: long, with major changes.
  5. The fifth package: very short, with minor changes.

One can intuitively feel that the length characteristics of these packs are quite obvious. In Vision, the approach to solving this problem is also simple: we increase the length of each short packet to a range from 900 to 1400. Note that this method is different from traditional random padding; we do not just add padding to all packets, but based on our analysis of internal traffic, we purposefully fill in the characteristic TLS handshake packets.

The important point is that when using vision, using mux is not possible.

As we discussed above, using vision means that the proxy does not perform any operations other than 1-to-1 copying from the client connection to the target connection.

Mux means that the proxy uses one connection to carry multiple client connections.

Vision cannot be used within a single mux connection because there is no way to determine where to send data.

REALITY Parameters

Now that we understand the basic mechanisms of our proxy, we can move on to configuring it. Below is a complete description of all Reality options that you can manipulate during setup.

{
  "dest": "example.com:443",
  "serverNames": ["example.com", "www.example.com"],
  "privateKey": "MMX7m0Mj3faUstoEm5NBdegeXkHG6ZB78xzBv2n3ZUA",
  "publicKey": "7xUz-xONEFhoGAb7XdmzhIUYybAlCnRvd1L0Ax_7yk4",
  "shortIds": ["", "0123456789abcdef"],
  "spiderX": "/blog",
  "maxTimeDiff": 0,
  "show": false,
  "minClientVer": "1.8.6",
  "maxClientVer": "1.8.15",
  "fingerprint": "chrome",
  "xver": 0
}

DEST/SNI

Above in the text, we constantly talked about the camouflage website. Setting up these fields will play a key role in your proxy service, so choosing DEST/SNI requires a lot of time.

dest: string

Required field.

Filled on the server.

Example: example.com:443

Example: 228.008.114.881:443

Example: /var/lib/marzban.socket

Example: 8443

"dest" here means “target”, which REALITY imitates in real time, and it (target) interacts with REALITY.

"dest" must meet the minimum requirements.

As we discussed above, by the principle of its operation, at least at the moment, REALITY must receive a handshake packet each time, so it is important to consider how close and stable the target site is, since the choice of this very target site / domain significantly affects on delay, speed and its stability.

Above, we introduced the term that REALITY imitates the target, in connection with which we can reveal a few nuances that people write down as minuses, although this is not so:

  1. If dest is “down”, then our proxy connection will not be established either I consider this nuance to be a big plus, because as we considered above, for the proxy mode the client must receive a temporary trusted certificate signed by a temporary authentication key, in all other cases it will simply be a port forward to dest, respectively, it would be strange if ” lying” dest send the censor into the void and thereby revealing his proxy.
  2. For the same reason, a cache or pre-assembly of data was not made, because during the cache time, the characteristics of dest could change

serverNames: array of strings

Required field.

Filled on the server and on the client.

Example: ["example.com", "www.example.com"]

Example: [" "]

List of allowed SNIs.

Wildcard is not currently supported.

When configuring these options, it is important to remember the following:

In fact, if dest has a valid default certificate, the client can use other SNIs to connect, as the server will respond with characteristics that match the response from dest with the default certificate.

Therefore, the server must limit the range of valid values ​​for serverNames to prevent accidental use of SNI by clients, unless it is done on purpose, and although REALITY supports this possibility, but if the specified dest does not actually have that serverName, this can become a clear sign, so you should not abuse it (as well as an empty serverName).

In total:

  1. If the server names (serverName) in the client and server configurations are the same but different from the names specified in the SSL certificate, the connection may be established.
  2. If the server names (serverName) in the client and server configurations are different and do not match the names specified in the SSL certificate, the connection is not established.

Typically, the servername parameter is populated, if necessary, with the appropriate SANs listed in the SSL certificate for the website at dest.

As mentioned above, there is a nuance in that, to ensure the masking effect, Xray will directly redirect traffic with invalid authentication (illegal reality requests) to the specified dest. If the IP address of the site specified in dest is special (for example, the site uses CDN CloudFlare), this may cause your server to act as a CloudFlare port forward, which may cause traffic leakage after scanning. To prevent this situation, you can consider using Nginx or other methods to filter SNIs that do not meet the requirements.

It is very important not to use large sites as DEST/SNI*

Many large sites have a CDN in the country, so their use is not recommended (this applies, for example, to Google and other major players, although Microsoft disabled CDN in the Russian Federation in 22). After all, if the site has servers in the country, then from the point of view of the observer (censor) under normal conditions, you should access local addresses within the country, and not go outside it.

Also, for example, in the case of the official REALITY repository, dl.google.com is given for illustration purposes only.

No one in their right mind would ever use Google as a dest.

For very few sites, like dl.google.com, Chrome adds extra information to Client Finished, but due to limitations in the uTLS library, we don’t.

So what to do?

If you do not have your own domain, and if you are not in a region where you need to use whitelisted domains, then it is recommended to use an excellent tool https://github.com/XTLS/RealiTLScanner to scan neighboring domains

If you have your own domain, use it.

Private/Public Keys

I would like to dwell on this point in more detail for a better understanding of the internal mechanisms.

As we discussed earlier, REALITY is tls 1.3, so all the mechanisms for it are identical. In this case, the default key exchange algorithm will be ECDHE with the X25519 curve.

You might be wondering why REALITY doesn’t use UUID directly for authentication, but adds a public/private key mechanism?

In fact, if you use symmetric keys, then when you receive the client’s configuration, the censor will be able to conduct a MITM attack, but if you use public and private X25519 keys in combination with the TLSv1.3 key_share, then even if you receive the client’s public key, the censor will not be able to use it to verify the connection as REALITY, not to mention conducting an effective attack.

As we discussed above, REALITY is designed with the understanding that the client configuration can be disclosed, so security is paramount here: protect your REALITY server’s private key and your traffic will be safe.

Of course, regularly changing public and private keys is even better.

Why is this so important and why did we spend so much time with you on this section?

A simple example: if you accidentally leak your SS password or VMess UUID, the censor will be able to decrypt all your past and future encrypted traffic, as well as conduct an ideal MITM attack.

You may ask how can a leak happen? Cloud backup of the phone, keyboard input, clipboard, domestic software and other ways unknown to us.

privateKey: string

Required value

Set only on the server

Example: MMX7m0Mj3faUstoEm5NBdegeXkHG6ZB78xzBv2n3ZUA

You can generate keys by running a command in the console ./xray x25519

For Marzban, the command would be docker exec marzban-marzban-1 xray x25519

The server uses the privateKey from the configuration and the key_share from Client Hello to calculate the shared secret key, then uses HKDF to generate a “temporary authentication key” that is used to decrypt and verify the client’s request, and then generates a temporary trusted Ed25519 certificate whose signature is HMAC “temporary authentication key” for its public key.

publicKey: string

Filled in on the client only.

Example: 7xUz-xONEFhoGAb7XdmzhIUYybAlCnRvd1L0Ax_7yk4

the client uses the private key corresponding to the key_share in Client Hello and the publicKey from the configuration to calculate the shared secret key, then uses HKDF to generate a “temporary authentication key” that is used for AEAD authentication and encryption of the version number, timestamp and Short ID, with the attached data of the entire handshake process, the result is filled in the session ID for verification by the server of the request.

Thus, publicKey and privateKey in the configuration are used to establish a shared secret between the client and the server, and to generate temporary authentication keys and temporary trusted certificates that are required for secure and authenticated data exchange within REALITY.

Many mistakenly believe that keys consisting of 256 bits are worse than 2048-bit keys, without taking into account the fact that RSA and DHE require large keys to provide similar security compared to elliptic curve algorithms. For ease of understanding, for example in RSA, there is a decomposition of a number into prime factors. Previously, this was a difficult task, but it is becoming easier with the growth of computer power, and in order to protect against them (hacking with their help), you need to use large keys, 2048 or 3072 bits or even more. At the same time, x25519 is based on a more complex mathematical problem that is more difficult to solve even with super-powerful computers, and due to the complexity, the decomposition of the logarithm on elliptic curves provides high security with smaller key sizes.

As we discussed above, a 256-bit key for X25519 provides a level of security equivalent to a 3072-bit key for RSA.

ShortID

shortId: array of strings

Required field.

The list of shortIds available to the client is used to distinguish between different clients.

The range is from 0 to f, the length must be a multiple of 2 and not exceed 16 characters.

Can be generated by running the openssl rand -hex 8 command in the console

If empty values ​​are included, the client’s shortId may be empty.

Example: [" ", "e13c3f07bcffd6e9"]

Example: [" "]

Example: ["c28e3p08uiqqh6e5", "e13c3f07bcffd6e9"]

In the section above, we discussed with you where and when this parameter is involved.

SpiderX

spiderX: string

Optional field.

Filled in on the client.

Example: /

Example: /blog

It is recommended that the crawler’s initial path and parameters be different for each client.

In order to understand what a “spider” is, or scan mode, we need to dive a little more into REALITY.

Previously, we have always considered only one option, when REALITY received a temporary trusted certificate and established a proxy connection, but in fact, REALITY can distinguish between several types of certificates:

  1. temporary trusted
  2. real
  3. invalid

when receiving different types of certificates, the reality client performs the corresponding actions:

  1. Upon receipt of a temporary trusted certificate, a proxy connection is established, and everything goes according to the usual scheme.
  2. When receiving a real certificate, it goes into scan mode (crawler).
  3. When an invalid certificate is received, a TLS alert is sent and the connection is dropped.

Accordingly, for each of the cases when the reality client receives a real certificate, namely:

  1. The REALITY server rejects the client’s Client Hello and traffic is redirected to the target website.
  2. The client’s Client Hello is redirected to the target website by an intermediary.
  3. MITM attacks, possibly involving the target website, or a certificate chain attack.

Then he (the client) goes into scan mode (crawler): Launching

  1. We start one HTTP/2 connection with dest, and GET requests are sent in parallel over the same connection to different URLs on the target server (target/dest), according to the Target site + path scheme, where the initial path is determined by the SpiderX parameter.

  2. upon receipt of responses to our requests, they are analyzed, and all found links are added to the list of paths (path) and this continues.

In fact, the mechanism is a little more complicated (with the installation of UA, Referrer, randomization of requests, timeout, and so on, but we will omit this)

Accordingly, with this mechanism, we (the reality client) imitate the behavior of a real user in the above emergency cases.

MaxTimeDiff

MaxTimeDiff: number

Optional field.

Filled on the server.

The maximum allowed time difference in milliseconds.

Example: 0

Example: 60000

MaxTimeDiff is a safeguard to prevent possible malicious manipulations by an intermediary.

In the Client Hello of the REALITY protocol, all elements are used as much as possible, and the additional AEAD data includes the entire Client Hello.

The man-in-the-middle cannot change a single bit, and all that remains for him is to resend, and although it is useless (since the man-in-the-middle does not have a temporary private key corresponding to the key_share, he will not be able to decrypt the server’s message), nevertheless, this may still result in unnecessary server responses.

What if there is a lot of data, and he accidentally has the corresponding private key?

Therefore, REALITY has an encrypted timestamp.

In fact, the timestamp sent by the client is accurate only to the second. The unit of measure for maxTimeDiff is milliseconds.

If you need to set maxTimeDiff, then it is recommended to start with a value of 60000+, if you do not want to synchronize, you can even start with one day.

SHOW

show: boolean

Output debug information

Filled on the server.

Example: true

minClientVer

minClientVer: string

Optional value.

Filled on the server.

The minimum allowed client version from which the connection will be accepted

Example: 1.8.6

maxClientVer

maxClientVer: string

Optional value.

Filled on the server.

The maximum allowed client version from which the connection will be accepted

Example: 1.8.6

fingerprint

fingerprint: string

Required field.

Filled in on the client.

Example: iOS

Specifies the fingerprint of the TLS Client Hello message.

If not specified, then fingerprint spoofing will not be enabled.

When enabled, Xray will simulate the TLS fingerprint via the uTLS library or generate it randomly.

Three types of options are supported:

  1. Simulate TLS fingerprints of recent versions of popular browsers, including:
  • "chrome"
  • "firefox"
  • "safari"
  • "ios"
  • "android"
  • "edge"
  • "360"
  • "qq"
  1. Generate fingerprint automatically at xray startup
  • "random": randomly select one of the current browsers
  • "randomized": generate a completely random and unique fingerprint (100% compatible with TLS 1.3 using X25519)
  1. Use your own uTLS fingerprint variables such as "HelloRandomizedNoALPN" "HelloChrome_106_Shuffle".

This function only simulates the fingerprint of the TLS Client Hello message, leaving other behaviors the same as the original Go TLS.

xver

xver: string

Optional value

Filled on the server.

Example: 0

The PROXY protocol is used to transmit the real source IP address and request port.

The version can be set to 1 or 2, with a default value of 0, which means that the PROXY protocol is not used. It is recommended to use version 1 if necessary.

Versions 1 and 2 currently have the same functionality, but a different structure: version 1 is a text format and version 2 is binary.

Server setup

Above, we have analyzed the main parameters available for configuration. Now let’s configure the xray-core server to work with REALITY.

The minimum configuration of your server will look like this. If you wish, you can customize it based on the knowledge you have gained earlier.

{
  "log": {
    "loglevel": "warning"
  },
  "inbounds": [
    {
      "listen": "0.0.0.0",
      "port": 443,
      "protocol": "vless",
      "settings": {
        "clients": [
          {
            "id": "dfccaec7-6bed-499b-af77-0275d55d573c",
            "flow": "xtls-rprx-vision"
          }
        ],
        "decryption": "none"
      },
      "streamSettings": {
        "network": "tcp",
        "security": "reality",
        "realitySettings": {
          "dest": "pupalupa.com",
          "serverNames": [
            "www.pupalupa.com",
            "pupalupa.com"
          ],
          "privateKey": "2KZ4uouMKgI8nR-LDJNP1_MHisCJOmKGj9jUjZLncVU",
          "shortIds": [
            "6ba85179e30d4fc2"
          ]
        }
      },
      "sniffing": {
        "enabled": true,
        "destOverride": [
          "http",
          "tls",
          "quic"
        ]
      }
    }
  ],
  "outbounds": [
    {
      "protocol": "freedom",
      "tag": "direct"
    },
    {
      "protocol": "blackhole",
      "tag": "block"
    }
  ]
}

Client setup

The minimum configuration of your client will look like this. If you wish, you can customize it based on the knowledge you have gained earlier.


{
  "log": {
    "loglevel": "warning"
  },
  "routing": {
    "rules": [
      {
        "ip": [
          "geoip:private"
        ],
        "outboundTag": "direct"
      }
    ]
  },
  "inbounds": [
    {
      "listen": "127.0.0.1",
      "port": 10808,
      "protocol": "socks"
    },
    {
      "listen": "127.0.0.1",
      "port": 10809,
      "protocol": "http"
    }
  ],
  "outbounds": [
    {
      "protocol": "vless",
      "settings": {
        "vnext": [
          {
            "address": "",
            "port": 443,
            "users": [
              {
                "id": "dfccaec7-6bed-499b-af77-0275d55d573c",
                "encryption": "none",
                "flow": "xtls-rprx-vision"
              }
            ]
          }
        ]
      },
      "streamSettings": {
        "network": "tcp",
        "security": "reality",
        "realitySettings": {
          "fingerprint": "chrome",
          "serverName": "www.pupalupa.com",
          "publicKey": "Z84J2IelR9ch3k8VtlVhhs5ycBUlXA7wHBWcBrjqnAw",
          "shortId": "6ba85179e30d4fc2"
        }
      },
      "tag": "proxy"
    },
    {
      "protocol": "freedom",
      "tag": "direct"
    }
  ]
}