port forwarding, reverse proxy, and pseudo-VPN

I have already written many articles here on the topic of installing and configuring XRay proxy servers with undetectable protocols Shadowsocks-2022, VLESS (with XTLS), etc. And one of the questions very often raised in the comments is: is it possible to somehow organize port forwarding or gain access to the internals of a corporate network using XRay? You can, and now I’ll tell you how.

XRay supports a mechanism called “reverse proxy”, which, coupled with rich options for customizing routing rules, allows you to create quite a lot of interesting schemes. The mechanism is mentioned in the documentation as “…in the testing phase” and “may have some issues”, but I tried it and everything works.

Traditional neuroimaging for distraction

Traditional neuroimaging for distraction

So what can you do with reverse proxying?

  1. You can access any services on a host behind NAT or a strict firewall, and even moreover, you can access services on other devices on the local network to which this same host behind NAT or a firewall has access.

  2. You can route all (or some, depending on the configured rules) traffic to a host behind NAT or a firewall and release it from there to the Internet.
    For example, you live abroad, you want to pay utility bills for your property in the rest of Russia, but the payment service does not allow you from foreign IPs and does not allow you from the IP addresses of even Russian VPS hosts. Then you can install a pre-configured router or single-board device like Raspberry Pi from one of your friends or relatives in the Russian Federation, which will connect to your proxy server, and you, in turn, through the proxy server will be able to reach this router/plug and access the external Internet through it as an ordinary user located in Russia – and all resources will see the IP address of the Russian home Internet provider.

  3. You can selectively forward ports, for example, all connections to port 80 of the proxy server will be forwarded to port 80 (or any other) of the “isolated” host or somewhere else.

  4. You can even theoretically try to build a pseudo-VPN so that connected clients of the proxy server can communicate with each other.

So, let’s go. The essence of the reverse-proxy mechanism is simple. We have a proxy server on some VPS, accessible to the entire Internet. We need to use it to get to some host that is not accessible from the entire Internet – for example, it is located behind NAT without its own white IP.

The solution is simple: you need to make sure that it is not the server trying to reach the host (this is impossible), but the host connecting to the server first, and then requests will run in the opposite direction over the already established connection. This is reverse proxy.

Next, you need to understand the terminology that is used in the XRay documentation and configs, because everything there is a little unclear and you can get confused. You need to know and understand three words:

“client” – this is a client (logical, yes) who needs to gain access somewhere or access the Internet;
portal” – this is your proxy server on the VPS, the link between the client and the bridge;
bridge” is another xray client, isolated behind NAT and a firewall, which must be accessed.

-

The word “bridge” can be confusing, but most likely the developers decided to use this name, because bridge is the point of contact between the external Internet and resources within the network – that is, the final destination that we want to reach may not be the bridge itself, but some hosts in his local area.

Now let’s figure out how to set it all up. The reverse proxy configuration is described in the XRay documentation: https://xtls.github.io/en/config/reverse.html, but the description is rather scant. There is also a git-repo with examples of setting up a reverse proxy: https://github.com/XTLS/Xray-examples/blob/main/ReverseProxy/README.ENG.md

Any transport protocol supported by XRay can be used as a transport protocol. For simplicity, I will use regular Shadowsocks in the examples, but exactly the same thing can be done with VLESS, and with VLESS with Reality, and with VLESS via websockets and CDN, and in general whatever – see my previous articles.

Let’s start with setting up the server (“portal”)

{
    "reverse": {
      "portals": [
        {
          "tag": "portal",
          "domain": "reverse.hellohabr.com"
        }
      ]
    },
    "inbounds": [
      {
        "port": 5555,
        "tag": "incoming",
        "protocol": "shadowsocks",
        "settings": {
          "method": "2022-blake3-aes-128-gcm",
          "password": "...",
          "network": "tcp,udp"
         }
      }],
     "outbounds": [
        {
            "protocol": "freedom",
            "tag": "direct"
        },
        {
            "protocol": "blackhole",
            "tag": "block"
        }],
     "routing": {
        "rules": [
            {
              "type": "field",
              "inboundTag": ["incoming"],
              "outboundTag": "portal"
            }]
     }
  }

And let’s figure out what it all means. In inbound we have everything as usual – a description of the parameters for incoming connections, simple shadowsocks. But then things get interesting.

At the beginning of the config, in the “reverse” section, we declare one object in the “portals” array, assigning it the “portal” tag and the “reverse.hellohabr.com” virtual domain. This domain is virtual – it may not exist, or more correctly, it should not be one of the really existing domains. It is used only so that XRay understands that this particular incoming request (with such a domain) is not something that needs to be processed as usual and released onto the Internet, but a special request to establish a connection from the bridge in order to raise reverse proxy.

The most important thing here in routing is rules. The routing rule states that all requests that come from clients must be redirected to reverse-proxy (the “portal” tag).

Naturally, if we have other wishes, we can complicate this routing rule a little – for example, redirect only requests to certain IP addresses (IP address ranges) or hostnames to the portal, and send everything else directly to the Internet (outbound “freedom”), or not allowed anywhere (outbound “block”). You can read about routing rules in the XRay documentation: https://xtls.github.io/en/config/routing.html#ruleobjectplus I touched on this topic a little more in my recent FAQ.

Next we configure the “isolated client” (“bridge”)

{
  "reverse": {
    "bridges": [
      {
        "tag": "bridge",
        "domain": "reverse.hellohabr.com"
      }]
    },
    "outbounds": [
    {
      "protocol": "shadowsocks",
      "settings": {
        "servers": [{
          "address": "....",
          "port": 5555,
          "method": "2022-blake3-aes-128-gcm",
          "password": "...",
         }]
      },
      "tag": "outgoing"
    },
    {
      "protocol": "freedom",
      "tag": "direct"
    },
    {
      "protocol": "blackhole",
      "tag": "block"
    }],
    "routing": {
      "rules": [
      {
        "type": "field",
        "inboundTag": ["bridge"],
        "domain": ["full:reverse.hellohabr.com"],
        "outboundTag": "outgoing"
      },
      {
        "type": "field",
        "inboundTag": ["bridge"],
        "outboundTag": "direct"
      }]
    }
  }

In outbounds, everything is also simple for us – connection settings to the server on the VPS, which we configured in the previous step. There is also a “reverse” section, but this time a “bridge” object is declared there with the “bridge” tag and the same virtual service hostname that we set for the portal in the previous step – they must match.

But in routing-rules everything is a little more complicated. The first rule determines exactly how we should connect to our proxy on the VPS (portal) – we say that all connections from the bridge with a virtual service address should go to the proxy via outbound, which we described just above in the same config.

And the second rule determines what we should do with requests that came to us from the proxy server (from the portal). In this case, we immediately release them all outside. That is, if the destination in the request is the IP address of any host on the local network to which the bridge is connected, the connection will be established to this host on the local network. If the destination of the request is any resource on the public Internet, the bridge will establish a connection with it. Etc. Just like in the last paragraph, these rules can be customized for yourself – for example, only send outbound “direct” requests to certain IP addresses or hostnames, and send everything else to block if we do not want people to access the Internet through us , but they could only reach where we allowed.

Ready!

In principle, this completes the setup. When you run Xray on bridge, thanks to the “bridge” object declared in the config, it will initiate and maintain a special service connection to the proxy server.

The proxy server on your VPS, thanks to the “bridge” object in the config, will understand that this connection is not a regular one, but a special service one for reverse proxying, and will process it as it should.

When you connect to a proxy server as your regular client (for example, from a mobile phone or desktop) and try to connect through it to some resource, the proxy (portal), following the rules, forwards this connection request to the bridge, and the bridge to its turn, following its rules, will release this question on its local network or on the Internet.

As I said earlier, you can configure the rules as you need – for example, on your VPS proxy, send only requests to certain IP addresses or domains to the bridge, and release everything else directly to the Internet and block it. You can configure several different inbounds or several different user IDs within one inbound, and apply different rules for different users. On the bridge itself (the host behind NAT), you can also apply different rules – for example, only allow requests to certain IPs and block everything else.

You can read about routing rules in the XRay documentation: https://xtls.github.io/en/config/routing.html#ruleobjectplus I touched on this topic a little more in my recent FAQ.

From what else may be useful: in the outbound settings with the “freedom” protocol, you can specify a specific network interface or IP address for outgoing connections, and even a routing mark in Linux to manage access to the local area as you need, see the Xray documentation for details.

What if you need port forwarding?

Based on the configs described above, you can build different schemes – for example, forward ports.

Let’s say we need all connections to port 80 of the portal to be redirected to port 80 of the bridge.

Let’s add a special inbound and a rule for it to the portal:

"inbounds":
[
  ....,
  {
    "tag": "web_server_forward",
    "port": 80,
    "protocol": "dokodemo-door",
    "settings": {
      "address": "127.0.0.1",
      "port": 80,
      "network": "tcp"
    }
  }
]
....
"routing": {
  "rules": [
    ....,
  
    {
      "type": "field",
      "inboundTag": ["web_server_forward"],
      "outboundTag": "portal"
    }
  ]
}

and add the corresponding directives to the bridge:

"outbounds":
[
  ...,
  {
    "tag": "web_server_local",
    "protocol": "freedom",
    "settings": {
      "redirect": "127.0.0.1:80"
    }
  }
]
...
"routing": {
  "rules": [
    ...,
    {
      "type": "field",
      "inboundTag": ["bridge"],
      "outboundTag": "web_server_local"
    }
  ]
}

After this, all requests coming to port 80 of the portal will be redirected to port 80 of the bridge via a reverse proxy connection.

If nothing works

If you are trying to access the local network via reverse proxy, but nothing works, and even on the server there are no requests in the logs, check the client settings. In many clients, by default, requests for “geoip:private” (that is, at least class A, B, and C local IP addresses) are sent to block.

From what I encountered: after setting it up on the bridge, XRay started, but for some reason it did not connect to the portal, and there was silence in the logs.
On the portal, when trying to go somewhere via the bridge, the logs contained a message

v2ray.com/core/app/reverse: failed to process reverse connection > v2ray.com/core/app/reverse: empty worker list

which meant that the portal didn’t mind going somewhere via a bridge, but not a single suitable bridge was connected to it. I still didn’t understand what was the matter, at some point I simply deleted the config and rewrote it again more carefully and everything worked.

If configured correctly, when you try to connect, on the portal side in the logs there will be something like this:


xray[1856216]: 11.11.11.11:10457 accepted 22.22.22.22:443 [incoming -> portal]
xray[1856216]: proxy/shadowsocks_2022: tunnelling request to tcp:reverse.hellohabr.com:0
xray[1856216]: app/dispatcher: taking detour [portal] for [tcp:reverse.hellohabr.com:0]
xray[1856216]: common/mux: dispatching request to udp:reverse.internal.v2fly.org:0
xray[1856216]: [33.33.33.33]:63786 accepted reverse.hellohabr.com:0 [incoming -> portal]

and on bridge:

xray[648852]: [Info] app/dispatcher: taking detour [outgoing] for [tcp:reverse.hellohabr.com:0]
xray[648852]: [Info] proxy/shadowsocks_2022: tunneling request to tcp:reverse.hellohabr.com:0 via 11.11.11.11:5555
xray[648852]: [Info] transport/internet/tcp: dialing TCP to tcp:[11.11.11.11]:5555
xray[648852]: [Info] common/mux: received request for udp:reverse.internal.v2fly.org:0

Pseudo-VPN?

A savvy reader will probably guess that in theory, you can even make some kind of VPN in this way. If on everyone client, configure and outbound to a proxy and bridge, and on the proxy, create a portal and corresponding rules for each client (for example, by inventing a set of virtual IP addresses and routing rules for them, such as sending connections to 10.0.0.1 to this portal, and to 10.0. 0.2 to another), then you can build a scheme where clients can connect to each other through a proxy. I’ll be honest – I haven’t tried it. It might work, or it might not. If someone tries it and it works, please tell me. If it doesn’t work out, tell me too. Of the obvious disadvantages: the config will be terrible, and debugging the whole thing will not be easy. Plus, on mobile clients (and even in simple desktop ones), the bridge functionality cannot be easily configured.

Therefore, if this is necessary, I would still suggest using classic VPN protocols, for example, AmneziaWG, or OpenVPN wrapped in Cloak, or something from TLS VPN, which I will talk about in the next article.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *