The UDP Firewall Punching Technique Explored

I recently read an article on The H entitled The Hole Trick: How Skype & Co. Get Round Firewalls. This article does a great job of explaining the technique of punching holes into firewalls — with an emphasis on how Skype does it.

The need for “firewall punching” arises when two clients that are behind a “typical firewall” wish to connect directly to each other. In this case, I am defining a “typical firewall” to be one that permits outbound traffic but blocks inbound traffic. Neither client can send the initial packet because it will be immediately dropped by the other’s firewall. The solution is to use a mediating server to establish a direct connection between the two clients.

The process is as follows:

  1. Each client establishes a connection to a known server.
  2. Each client binds to a UDP port but does not send anything. UDP is used due to its connectionless nature.
  3. Each client sends the assigned port of its UDP connection to the server.
  4. The server passes each client’s UDP port on to the other client.
  5. Client 1 sends a UDP packet to Client 2 on the specified port, which is dropped by Client 2’s firewall. Client 1’s firewall has now been punched.
  6. Client 2 sends a UDP packet to Client 1 on the specified port, which is perceived by Client 1’s firewall as a response to the dropped packet. Client 2’s firewall has now been punched.
  7. A direct connection between the clients has now been established. The server may exit and the clients can continue communicating.

I have written a proof-of-concept Java client and server to illustrate this concept. I have tried to keep it as simple as possible to showcase only the firewall punching technique. The client’s purpose is to simply exchange messages with the other client over UDP. The server implements the port-switching technique described above and on the first page of the article.

The most interesting class of this project is probably the ClientConnection class, which manages a thread that runs on the server. The ClientConnection class uses a Socket to communicate with the remote client and maintains a reference to the client’s partner (which is also an instance of ClientConnection).

The run() method of ClientConnection:

  public void run() {
    sendClientNum();
    sendMessage("Partner found: "
        + partner
        + "\nRequesting UDP info from partner...");
    partner.requestUDPPort();

    // We want both threads to rendezvous here, since they have now acquired
    // their client's addresses.
    hasPortSemaphore.release();
    partner.waitForPort(); // Calls hasPortSemaphore.acquire() on the partner

    sendPartnerAddress();
    close();
    System.out.println("Client thread "
        + partner + " exiting.");
  }

Sending the client an assigned number (sendClientNum()) may at first seem unnecessary. However, it is important that each client is distinguished from the other because it is only necessary for one client to perform the firewall punching (i.e., sending the packet that is discarded by the firewall).

Concurrency plays an integral part of any network program including this one. The run() method highlights the most complicated concurrency involved in this program. The two-thread rendezvous is implemented using simple semaphores. Each ClientConnection has a semaphore representing the acquisition of their client’s UDP port. These are both initialized to zero. When this information is received, signal is called (release() in Java) on hasPortSemaphore, meaning we now have our client’s UDP port. We now must wait on the partner’s port by calling wait (acquire() in Java) on their hasPortSemaphore. In a nutshell, this block of code prevents each client from being sent the address and port of the partner until the partner responds with their data. For more information on the thread rendezvous, see the Rendezvous section in the excellent free book The Little Book of Semaphores by Allen Downey.

I should note that this application may not necessarily work on every network. I have only implemented the most common way of firewall punching, to which firewalls may or may not be vulnerable. As stated in the article, Skype goes to great lengths to establish a direct client connection. If this is not possible, it will route client-to-client through its servers as necessary. My purpose here was to illustrate only the most common technique for the task.

The firewall punch project and instructions on its use are hosted on SourceForge. It is licensed under the terms of the MIT/X11 License. The code is stored in a git repository and there are executable JAR files available for download as well.