General Purpose P2P

The IT industry has made an interesting turn of development in recent decades. At the dawn of the IT revolution, when computers were expensive, their resources, although modest by today's standards, had to be used effectively. This led to the emergence of multi-user OS, such as the UNIX family, the creation of computer networks, and the emergence of remote access protocols.

The rapid development of chip performance and the reduction in hardware prices led to the revolution of personal computers. However, the outstripping growth of computing needs launched the development into a new circle. Now there are few companies and organizations that manage exclusively with personal computers and services in the local network. Once exotic IaaS, PaaS, SaaS, distributed computing technologies have firmly entered life. Access to remote systems is a common need not only for administrators and programmers. Everyone knows about the protocols ssh, rdp, vnc, many use TeamViewer, AnyDesk, Remmina, X2Go, etc. Due to the fact that IPv4 networks and the NAT generated by them are still more than relevant, not each of the listed tools allows you to connect to a machine located behind NAT if you do not have the ability to forward ports.

Working remotely, gradually acquiring devices, from time to time I began to feel the need to connect to my own devices remotely via the Internet. I rented a VDS at a minimum and raised a virtual network. From a technical point of view, the solution is excessive for my modest and infrequent needs. It would seem that such a simple need is to connect to the service of a remote machine, and NAT gets in the way. In the P2P world, various torrents, cryptocurrencies, VoIP, WebRTC, some messengers have long been coping with NAT using protocols STUN/TURN/ICE.

Let me remind you that these protocols allow you to explore NAT and its properties, “open” it for incoming connections to an internal resource, and if this is impossible, then use relay solutions. However, the implementations of these protocols are built into the final products, and in OpenSource I was unable to find solutions that could simply be taken and used “out of the box” at my own discretion.

An idea was born to fill this gap and write a tool that would allow remote connection via NAT to any service. This is how the project appeared plexusand later a couple more auxiliary ones. The tool does not yet fully implement everything needed, but in most cases it allows you to connect applications running on machines located behind NAT. The further narrative assumes that the reader understands the principles of NAT, TCP/UDP protocols and has read something about the technique of overcoming NAT using the protocol STUN called UDP hole punching.

NAT and its properties

Much has been written about NAT, so we will briefly and simply outline its characteristics. When NAT sees the first outgoing packet from some internal address, it allocates a port on the public interface, replaces the source address/port of the outgoing packet with the allocated pair, makes an entry in the table where it binds the address/port of the internal resource, the address/port of the external resource and the allocated public address/port, and then sends the packet to its destination. In other outgoing packets with the same parameters, it replaces the source address/port and forwards them further. For incoming packets to the allocated pair, NAT replaces the destination address/port with the address/port of the internal resource and forwards the packet to the internal network. Not every incoming packet can be forwarded to the internal network. This is the responsibility of the policy filtering:

  • Full Cone NAT — packets from any source are allowed through.

  • Port Restricted Cone NAT — only packets whose source port matches the destination port of the first outgoing packet are allowed through.

  • Address Restricted Cone NAT — only packets whose source address matches the destination address of the first outgoing packet are allowed through.

  • Symmetric NAT — only packets from the source to which the initial request was made are allowed through.

Property hairpin determines whether packets received on the external interface from the internal network are allowed to be transmitted to the internal network.

Another important policy is mapping. It is responsible for how the internal source is mapped to the external interface, whether a new mapping will be created if the source of the next outgoing packet remains the same, but the destination has changed. In theory, the same options are possible here as with filtering.

Property preserve port Determines whether NAT will attempt to preserve the port number when mapping.

UDP hole punching

Under certain conditions, connecting to a service hidden behind NAT is possible. To do this, the service must somehow learn about the client's desire to connect and its external address/port, be able to create the necessary mapping to the external NAT interface, learn the address/port of this mapping, and communicate it to the client. The client's address/port must comply with the NAT filtering policy.

To solve these problems, first of all it is necessary STUN. This is a UDP protocol for exchanging messages with a special service that has two public addresses and two ports, through which it is available on each of them. It allows you to find out NAT policies using several requests, contacting different addresses and ports of the server and requesting responses from different addresses and ports, and also to get the address/port of the created mapping to the public NAT interface. To exchange messages between the client and the service, a signaling service is used, the so-called rendezvous. It can be implemented in different ways. For example, via a file shared on the Internet, via email or SMS, via DHT or IPFS networks, etc. The scheme for overcoming NAT is shown below.

A client wishing to connect to a service hidden behind a NAT first uses STUN to map itself to the external interface of its own NAT and discover its policies (1).

For the client, only politics matters mappingsince later, when sending packets to the public address of a remote service hidden behind NAT, it is necessary that its external display, obtained after communicating with the STUN server, does not change. If everything is OK, then through the signaling service the client sends a message to the service, specifying its own public address/port (2).

The service, upon receiving the message, also maps itself to the external NAT interface using STUN and finds out the policies of its own NAT (3).

Politics matters for service filteringsince it implements a server application, i.e. listens to connections. As a rule, NAT providers implement symmetric filtering, which is not very good for p2p, but not hopeless. In general, symmetric filtering is normal, since sending packets from anywhere to the internal network is unsafe. In this case, the policy comes to the rescue mapping. Here, providers usually allow the mapping to be saved when the destination address/port of outgoing packets changes. And this is also normal, since the new mapping is initiated legally by the internal host. Now our service must forward the NAT-piercing packet to the address/port received from the client, thereby creating a new entry in the NAT table, and the presence of symmetric filtering will not interfere in any way (4).

Then, via the signaling service, the service communicates its external display (5) to the client.

From this point on, the client and the service can establish a connection in the usual way, NAT will no longer interfere (6). This is the essence of the technique UDP hole punching.

Why UDP and not TCP? Because UDP is multicast and datagram, has no states, the sides of the connection do not have formal roles, unlike TCP, where all this greatly interferes with software implementation and overcoming NAT. UDP mapping to NAT is removed by a timer, which is very convenient due to predictability. Implementation for TCP is also possible, but it will work poorly. For TCP, you need to break through NAT syn-packets, but the client will also connect using synand the setting firewall can only consider as acceptable syn|ack. Also, TCP is a stateful protocol, and NAT knows about it, and race conditions when establishing a connection through a “broken” NAT can provoke reset answers or icmp errors to which NAT will respond by resetting the entry in the table.

Practical experiments have shown that NAT overcoming does not work everywhere for TCP and is extremely unstable. In addition, manipulation of individual TCP packets requires elevated privileges. However, most network applications work via TCP. The problem can be solved by forwarding the TCP stream through a UDP tunnel, which will be shown later.

Plexus

The above algorithm is implemented in the utility plexus with a small addition. During the exchange of messages, the parties additionally form a verification key, and at the last stage, before transferring control to the main application, they identify each other with counter packets using the key. The service can be used as a signaling service email (smtp And imap) or DHT network. In github– in the repositories you can find instructions for assembling the utility or download ready-made packages.

To use the utility, you need to create an application repository, place in it the directories of all participants, according to the scheme apprepo/owner@mailer.com/pinWhere

  • apprepo — application directory;

  • owner@mailer.com — identifier of the owner of the remote or local endpoint;

  • pin — endpoint identifier, unique to the owner.

Each participant must generate a private key and an X509 certificate and exchange certificates with the other participants. In the local host directory, place its certificate, named cert.crt, and its private key, named private.key. In the remote host directory, place its certificate, cert.crt. Do the same on other machines.

IN plexus the parties identify each other with a public key in the case of using DHT as a signaling service, and messages are encrypted. The sender is verified using a digital signature. The owner identifier can be any string, it is not used in the DHT network. However, if you plan to use email as a signaling service, the identifiers must be real email addresses through which messages will be exchanged. The endpoint identifiers must be consistent, as they are used to refine the recipient and filter messages. Here, message encryption is optional. If you fully trust email service, then simply do not place keys and certificates in the participants' directories.

Example

The following example demonstrates connecting to a remote host behind NAT via ssh. As I mentioned above, for TCP applications, you need to create a UDP tunnel through the broken NAT. An auxiliary utility was written for this purpose wormhole. With its help, you can map a remote service to a local interface port via a UPD tunnel. Approximately the same as port forwarding via the same ssh. I will tell you more about this in the next article. Download/build and install packages plexus And wormhole on the remote and local machines. Create and fill the repositories on the server and client machines:

Configuration for server machine, file ssh-export.conf:

accept=1
app-id=ssh-service
app-repo=/home/server/plexus
host-info=server-owner@mailer.com/ssh-server
peer-info=client-owner@mailer.com/ssh-client
dht-bootstrap=bootstrap.jami.net:4222
stun-server=stun.ekiga.net
exec-command=wormhole
exec-args=–purpose=export –service=127.0.0.1:22 –gateway=%innerip%:%innerport% –faraway=%peerip%:%peerport% –log-file=export%p. log

Let's launch…

$ cd ~/plexus
$ plexus –config=ssh-export.conf

Configuration for the client machine, file ssh-import.conf:

accept=0
app-id=ssh-service
app-repo=/home/client/plexus
host-info=client-owner@mailer.com/ssh-client
peer-info=server-owner@mailer.com/ssh-server
dht-bootstrap=bootstrap.jami.net:4222
stun-server=stun.ekiga.net
exec-command=wormhole
exec-args=–purpose=import –service=127.0.0.1:2222 –gateway=%innerip%:%innerport% –faraway=%peerip%:%peerport% –log-file=import%p. log

Let's launch…

$ cd ~/plexus
$ plexus –config=ssh-import.conf

After plexus NAT breakthrough utility wormhole will create a UDP tunnel and remote ssh– the server will be “mapped” locally to the address 127.0.0.1:2222. If everything went normally, the logs will display a record with the PID of the running command. You can connect to ssh…

$ ssh -p 2222 127.0.0.1

Arguments in plexus can be transferred both by a configuration file and by keys. Since one repository can be used for different applications, the key –app-id you must specify the target application. As you might guess, the keys –host-info And –peer-info identify the participants and are relative paths to the participants' directories in the repository. Key –accept indicates that this side will operate in the mode of waiting for connections. If you want to accept connections from several hosts, then simply do not specify the key –peer-infoconnections from all hosts described in the repository will be accepted. The argument –exec-command you can pass any command or script at your discretion. To pass parameters to your application, you need to use the key –exec-args with the following wildcard patterns:

  • %innerip% — local binding address of the application, can be specified by a key –stun-client

  • %innerport% — local port of application binding, can be specified by a key –stun-client

  • %outerip% — mapping address to the public NAT interface

  • %outerport% — port mapping to the public NAT interface

  • %peerip% — the address of the public interface of the remote host, obtained from the signaling service

  • %peerport% — the port of the public interface of the remote host, obtained from the signaling service

  • %hostmail% — local host owner identifier, argument prefix –host-info

  • %peermail% — remote host owner identifier, argument prefix –peer-info

  • %hostpin% — local host identifier, argument suffix –host-info

  • %peerpin% — remote host identifier, argument suffix –peer-info

  • %secret% — a 64-bit session key generated by the parties

I encountered a case when a packet “punching through” to the client (step 4), having reached the client NAT, led to a reset of the server display. At this point, such a packet does not yet pass the filtering policy. This was probably the reason for such a reaction of the NAT. To stop such problems, you need to use the key –punch-hopsdefault 7. This argument is for setting ttl “punching” the packet so that it could go beyond the server NAT, creating the necessary mapping on it, but not reach the client. To use email As a signaling service, define keys –email-smtps, –email-imaps, –email-login And –email-password. Run the utility with the key –helpto see all possible arguments.

In what cases plexus will not help

  • In case the NAT mapping policy implements a dependency on the destination address/port, i.e. a new mapping is created when redirecting packets to a new resource even if the source address/port is preserved.

  • In case both machines are behind the same NAT and it does not support the function hairpin.

  • Detection and connection of applications in local networks is not yet implemented, as is the possibility of using relay solutions.

The project is developing and solutions have already been outlined for these cases.

Library

Utility plexus also implemented as a library. API is described in the header file plexus.his identical in functionality to that described above. A C++ programmer can easily figure it out. Additionally, it is possible to create UDP streams, including those with SSL encryption. If there is interest, I will also tell you about this in a separate article.

Copyright © 2024 Novemus

Author of the article: Sergey Devyatkin


UFO arrived and left a promo code here for our blog readers:
-15% on any VDS order (except for the Warming up tariff) — HABRFIRSTVDS

Similar Posts

Leave a Reply

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