In regions where the Internet is strictly regulated (e.g. China), direct access to well-known websites such as Google, youtube, Twitter, etc. is not available.

This article introduces a technique to break through network blocks based on the GitHub Actions service, which allows you to watch YouTube 4k videos smoothly, with unlimited traffic, completely free!

GitHub Actions is a free build tool provided by Microsoft for developers. Whenever a task needs to run, GitHub creates a virtual machine (2C4G) in the Azure cloud. This virtual machine has access to the public network, but it doesn’t have a public IP, so it can’t be accessed directly from the outside world. If we could find a way to access it from the outside world, we could use the GitHub Actions virtual machine as a regular VPS, thus implementing a VPN server. This approach is called NAT hole-punching, or NAT pass-through.

Assume the entire network topology is as follows.

1
2
3
                     /- 1.1.1.1               /- 10.0.0.1
      Client <---> C-NAT <------> S-NAT <---> Server
10.1.0.1 -/              2.2.2.2 -/

The Server is the GitHub Actions virtual machine described earlier, and Azure provides address translation for all virtual machines, which we call S-NAT.

Our own device is the Client. The general home or office devices are connected to the Internet through a router, which also provides address translation, which we call C-NAT.

The above is the most typical architecture. If your device has a public IP, it will simplify the difficulty of NAT hole-punching. Let’s start with the typical scenario. Because NAT hole punching is usually oriented towards UDP communication only, the messages described below are UDP data if not specified.

The role of S-NAT is to help the Server access the external network. Specifically, it modifies all IP messages from the Server, changing its source address to the public IP of the S-NAT and changing the source port to a number assigned according to a specific policy.

As an example.

Suppose Server wants to send data to port 80 of 3.3.3.3, the data flow seen from Server side is as follows.

1
10.0.0.1:11111 -> 3.3.3.3:80

Here 11111 is the Server’s local port, which is usually chosen randomly, or can be specified manually. The value is not important, but the data communication requires the quadruplet ``Source Address, Source Port, Destination Address, Destination Port>’'.

Obviously, the source address is private and cannot be routed on the public network. messages from the server are forwarded to the S-NAT, which receives them and finds that they are sent to the outside network, so it changes the source address to 2.2.2.2. it then selects a port, such as 22222, as the source port for the message according to established policy. The data flow then becomes the following.

1
10.0.0.1:11111 -> 2.2.2.2:22222 -> 3.3.3.3:80

3.3.3.3 The data received appears to have been sent from 2.2.2.2:22222. It doesn’t know that the data was sent from the Server. So 3.3.3.3 starts replying to the data.

1
3.3.3.3:80 -> 2.2.2.2:22222

After the S-NAT receives the data, it changes the destination address to 10.0.0.1 and the destination port to 11111, and then sends it to the Server.

However, after the S-NAT receives the message from 3.3.3.3, how does it determine the data to be forwarded to the Server?There may be many devices behind the S-NAT. For this reason, S-NAT maintains a list to temporarily store the mapping between the intranet devices and the local ports. Because of the limited device capacity, the NAT device will regularly clean up the mapping relationships that have been inactive for a long time.

Take the above scenario as an example, the mapping relationships are as follows.

1
2.2.2.2:22222 -> 10.0.0.1:11111

S-NAT records a reverse association mapping for each completed address and port conversion. This way, when a message is received from the external network, the corresponding intranet device can be traced based on the destination address and destination port. Since the data from 2.2.2.2:22222 must be destined for 10.0.0.1:11111, the S-NAT is forwarded to 10.0.0.1:11111.

At this point, we understand how NAT works. If you can get S-NAT to add a mapping relationship to the Server, then it is possible to access the Server from the public network, which is the core principle of NAT hole punching.

Why is this possible? Because NAT devices have a variety of port selection policies when it comes to address translation, some can hole punch and some cannot.

In the case of Azure cloud NAT devices, they have the following features.

  1. Random port
  2. Independent Mapping
  3. Port Dependent Filter

Random port means that the port is randomly selected during the address translation. This means that there is no way to determine which port the NAT will use before sending the data.

Independent Mapping means that the mapping rules have nothing to do with the destination address and destination port, only the source address and source port. That is, if Server sends data to 3.3.3.3:80 and 4.4.4.4:443 respectively, the local port used is 11111.

1
2
10.0.0.1:11111 -> 3.3.3.3:80
10.0.0.1:11111 -> 4.4.4.4:443

Then S-NAT will only save one reverse mapping relationship.

1
2.2.2.2:22222 -> 10.0.0.1:11111

Data from anywhere with a destination of 2.2.2.2:22222 will be forwarded to 10.0.0.1:1111.

This makes NAT pass-through possible.

If the Server wants to give the outside world access to its port 11111, it needs to find a device with a network IP to act as a coordinating server.The Server sends a message to a port on the coordinating server, and the coordinating server receives the public IP of the S-NAT and the mapped port (e.g., 22222) assigned by the S-NAT to the Server’s port 11111. The protocol server needs to return this information to the Server. by now, the first step of NAT hole-punching is complete. the Server can send the received IP and port to others. The receiving device simply sends packets to 2.2.2.2:22222, and these packets are forwarded to the Server’s port 11111.

The protocol server mentioned here is called a STUN server in reality. Its main role is to help the devices behind the NAT to discover their corresponding public IP and port.

So is the NAT hole-punching done? Not really. Because we haven’t even mentioned the second feature of Azure address translation, Port Dependent Filter, which is a firewall rule. This is a firewall rule that requires an intranet device to send data before allowing an extranet device to reply. Let’s take another example.

1
2
3
4
4.4.4.4:33333 -> 2.2.2.2:22222 # Discard

2.2.2.2:22222 -> 4.4.4.4:33333
4.4.4.4:33333 -> 2.2.2.2:22222 # Accepted

If the S-NAT receives a message directly from 4.4.4.4:33333, it considers it to be illegal data and simply discards it. If the Server first sends a piece of data to 4.4.4.4:33333 via 2.2.2.2:2222, then the S-NAT logs the event and allows data from 4.4.4.4:33333 to be received for a certain amount of time. This record is usually only kept for thirty seconds.

So, in order for a message from 4.4.4.4:33333 to be forwarded smoothly through the NAT device to 10.0.0.1:11111, we need to have the Server send a piece of data to 4.4.4.4:33333 first. because the S-NAT access record will expire soon, the Sever needs to send data periodically. This can be done using nping.

1
2
3
4
5
6
7
sudo nping --udp \
           --ttl 4 \
           --count 20 \
           --delay 28s \
           --no-capture \
           --source-port 443 \
           --dest-port 33333 4.4.4.4

What is interesting here is the --ttl 4 parameter. The TTL is subtracted by one after each router the data passes through. This way the data leaves the S-NAT and is discarded soon after because the TTL reaches zero. This way the data is not actually sent to the target device.

Another important point to note is that if the Client wants to connect to the Server, it must determine its IP and port beforehand. Because only then can Sever set up S-NAT to allow data from Client data to pass through.

But because public IPs are scarce, the Client usually accesses the Internet through NAT as well. So C-NAT will do the same job as S-NAT. However, there is a big difference between C-NAT for home use and C-NAT for commercial use in companies.

The port mapping rules for a home NAT device are relatively simple and are generally consistent with the source port of the message. This means that data from 10.1.0.1:1024 will also be mapped to 1.1.1.1:1024 by the C-NAT. This way we only need to determine the public IP of the C-NAT. If the device does not use such a rule, then we can also use UPnP to specify the port mapping manually.

The above is the case when the home device has a public IP. If it does not, there is no difference between home and business equipment.

Now for the technical summary.

  • First of all the client should preferably have a public IP. a port is selected before communication. If you don’t have a public IP, you need to configure the C-NAT device to get a clear public IP and a corresponding mapped port. If you can’t do this step, you don’t need to read further.
  • Then send the client’s public IP and port to the virtual machine via GitHub.
  • The virtual machine selects the listening port and gets the public IP of the S-NAT and its corresponding mapped port via STUN (this port corresponds to the listening port)
  • Then the VM keeps sending data to the corresponding IP and mapped port of the client to ensure that the S-NAT accepts the data from the client.
  • Finally the client sends data to the public IP and mapped port discovered by the VM on the previously selected port. The whole communication process is established.

The whole process is encapsulated by ValdikSS as an open source project. Everyone is free to use it. The usage is as follows.

  • Clone the ValdikSS project and re-upload it to GitHub. you can’t use Fork for this step, because you can’t change the project to a private repository after Fork.
  • Add your own SSH key to authorized_keys
  • Install stun-client to detect the local public IP and port.
  • Run ./run.sh wireguard. Currently only linux is supported, macos requires additional customization.
  • Check the GitHub Actions in the repository to find the corresponding WG:XXXX task, and check the configuration from the Print WireGuard configuration file
  • Terminate the run.sh script and start the WireGuard client
  • Log in to the VM using ssh root@192.168.166.1

Here we get a VPS with a 2C4G configuration and not too slow internet speed. The easiest way to proxy is to use ssh forwarding.

1
ssh -D 1337 -q -C -N root@192.168.166.1

This will start a sock5 proxy at 127.0.0.1:1337. After modifying the browser proxy configuration, you can break the network blocking and swim in the international Internet.

Is there a downside to something so good? Of course there are.

The first disadvantage is that it is free. If it is free, it can be abused, and if it is abused, it can be recalled. We must maintain restraint, careful github account is blocked ⚠️. The second disadvantage is that the client needs to be accessible to the public network. It is best to have a public IP. if not, you should also have a NAT environment where you can make a hole. The third disadvantage is that you can’t use GitHub Actions for more than an hour at a time, because the maximum time you can use GitHub Actions for a single task is 58 minutes. And you can’t use GitHub Actions for more than 33 hours a month.