📜 ⬆️ ⬇️

Private dynamic IP - come, see, hide

image
I did not have to communicate with DynDNS services from the very first day of our acquaintance. The rake came across at every step: registering, downloading and launching a client, setting up a client or a router — everywhere there were some minor nuances, misunderstandings, shortcomings, or just bugs, which led to the inoperability of the service. In the appendage to everything, after a while, “these guys” suddenly cease to be white, fluffy and free - they begin to send spam, to demand to solve a captcha once a month, or to make them perform some other movements to prove that you are still alive. All this led to a general dislike for all services of this kind. And so the idea arose to create something of their own, and so that the “white and fluffy” is necessary.

A lot of time passed from idea to implementation. Mainly due to the misunderstanding of “what do I really need?”. I read articles at my leisure, I thought about it, and gradually a list of basic requirements for a bicycle appeared in my head.

The main provisions.


Purpose : find the IP address of the remote computer (for example, home computer).
Level of paranoia : above average! (i.e., only authorized representatives should know the IP address). This is the main difference from similar services - I don’t want anybody to get the address of my computer just by typing something like “ping supercomp.dyndns.org” on the command line.
Mandatory conditions of "fluffiness" :
  1. Free (do not forget that time is also money).
  2. Stability.
  3. Simplicity of the ready decision for the end user.

Based on the clarification to the first condition, it was decided to use technology only those that I personally know more or less - Windows, c #, ASP.NET.
Influenced by the article “ Your Simple DynDNS Server ”, an attempt was made to write a small proxy site. But, looking at the surprisingly stable instability of free ASP.NET hosting, it was decided to abandon this idea and use free mail services and cloud storages as intermediaries. By the way, it was from this article that the sensible idea with the “ability to store the IP addresses of all client interfaces” was taken.
Somehow it happened that this should be a common Windows B-Sharpe application.

The choice of "storage"


Under the repository means a place where our information will lie. This place must be protected from prying eyes, be easily accessible from any point and must comply with the three "fluffy" requirements.
In order not to strain too much, it was decided to focus on the following options:

On the third paragraph we will stop, and we will consider possible options.
A preliminary survey of friends and acquaintances showed that most have nothing against Yandex-Disk and Sky-Drive. Therefore, they were initially considered as the main applicants. But after spending half a day in “active search”, it turned out that not every cloud service provides a sane means of interaction. For example, the Sky-Drive API cannot be used in desktop applications for some time, the Google-Drive API is not possible to understand without a bottle, and I haven’t found the Windows SDK at all in DropBox. The use of unofficial or outdated “APIs” was not even considered, as there is no guarantee that they will work tomorrow. Maybe I was looking badly, or not there, or looking for something else - I don’t know if someone has examples, I will be happy to help. The last nail in the problem of choosing a cloud service was the fact that no third-party libraries are needed at all to work with the Yandex disk from c #.
I didn’t stop at one of these three types of storage / transfer. It was decided to make support for all three, and what specifically to use - leave the choice of the user. For situations are different - someone has closed ports and mail does not work, someone can not put the program of cloud clients, etc.
')

The overall algorithm of the application.


The general algorithm of work is simple as two kopeks:
  1. Periodically save text messages with all the necessary information in the "repository"
  2. Periodically we read messages, and we show in a convenient form.

Let us turn to the implementation of ideas in the program code.

Getting an external address.

It's simple. In the "Internet" is full of all sorts of services that show your external address. If there are few existing ones, then creating a couple of dozen will not be difficult. Sample code for such a page on ASP.NET:
protected void Page_Load(object sender, EventArgs e) { LabelIp.Text = HttpContext.Current.Request.UserHostAddress; } 

Let's return to our application. Using the System.Net.WebClient class, we download a page with this address into a string, parse it with a regular expression, and get the information we need:
 WebClient webClient = new WebClient(); string strExternalIp = webClient.DownloadString("http://checkip.dyndns.org/"); strExternalIp = (new Regex(@"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}")).Matches(strExternalIp)[0].ToString(); 

Getting the properties of network interfaces.

The System.Net.NetworkInformation.NetworkInterface class, and its static method GetAllNetworkInterfaces (), which returns an array of elements of its own NetworkInterface [] type, will help us with this. After going through this array, we can get all the information we need from the IPInterfaceProperties object - IP addresses, masks, gateways, dns servers, etc .:
 NetworkInterface[] adapters = NetworkInterface.GetAllNetworkInterfaces(); //     foreach (NetworkInterface nic in adapters) { string strInterfaceName = nic.Name; //   string strPhysicalAddress = nic.GetPhysicalAddress().ToString(); // -  string strAddr = string.Empty; //  IP  IPInterfaceProperties properties = nic.GetIPProperties(); foreach (UnicastIPAddressInformation unicast in properties.UnicastAddresses) { strAddr = unicast.Address.ToString() + " / " + unicast.IPv4Mask; } //  - foreach (IPAddress dnsAddress in properties.DnsAddresses) { strAddr = dnsAddress.ToString(); } //   foreach (GatewayIPAddressInformation gatewayIpAddressInformation in properties.GatewayAddresses) { strAddr = gatewayIpAddressInformation.Address.ToString(); } } 

Sending a text message in the "repository".

After collecting all the necessary information, we send it to the “repository” as a plain text file (in the case of mail, just a message).
With ordinary files, everything is simple:
 System.IO.File.WriteAllText("MyInterfaces.txt", strInterfaces); 

With mail, too, everything is solved by a couple of lines of code (the method is easily on the Internet). One of the possible variations:
 MailMessage mail = new MailMessage { From = new MailAddress(strMailAddress), //   Subject = strSubject, //   Body = strBody, //   IsBodyHtml = false }; mail.To.Add(new MailAddress(Settings.Default.strMailTo)); //  SmtpClient client = new SmtpClient { Host = strSmtpServer, //  SMTP  Port = nSmtpServerPort, //  SMTP  EnableSsl = isSmtpSsl, //    SSL Credentials = new NetworkCredential(strEmailUserName, strMailPassword), //   DeliveryMethod = SmtpDeliveryMethod.Network }; client.Send(mail); //  mail.Dispose(); 

But with the clouds a little more difficult, the general sense is to create the correct web request in which to cram the transmitted text:
 // strFilePath -        HttpWebRequest web = (HttpWebRequest)WebRequest.Create("https://webdav.yandex.ru/" + strFilePath); //     (!!!   ) web.Credentials = new NetworkCredential("mail@yandex.ru", "password"); web.Headers.Add("Authorization: Basic " + Convert.ToBase64String(Encoding.Unicode.GetBytes("mail@yandex.ru" + ":" + "password"))); web.Accept = "*/*"; web.Method = "PUT"; web.ContentType = "application/binary"; web.ContentLength = buffer.Length; using (Stream myReqStream = web.GetRequestStream()) { // strContent -    byte[] buffer = Encoding.UTF8.GetBytes(strContent); myReqStream.Write(buffer, 0, buffer.Length); myReqStream.Flush(); } HttpWebResponse resp = (HttpWebResponse)web.GetResponse(); 

Here I had to dance a little with encodings, but it was found by the “scientific tyke” method that everything works fine with UTF8.

Reading messages from the "repository"

Regular files from a regular file system are read in one line. But we don’t just need one file, and its name may not be known in advance, so we look through the entire contents of the folder, look for files using the specified mask and process them in turn:
 //          var files = Directory.EnumerateFiles("  ", "*.txt"); strFileNames = files as string[] ?? files.ToArray(); foreach (string strFileName in strFileNames) { string message = File.ReadAllText(strFileName); //    // -    } 

With reading the mail had to tinker. The code was sharpened for Google-mail, so it may be incorrect work on other mailers. It was Google Mail that led to the use of the IMAP server (at present, hotmail does not support this protocol). Many advised to use a pseudo-free library (I will not give the name), which periodically instead of the body of the letter returned its advertisement. But this directly violates the "second fluffy requirement" - stability, and if you pay, the "first fluffy" - free of charge. Therefore, I chose a completely free and fully working library in which there is work with IMAP servers - “ MailSystem.NET ”. Examples of use can be found on the project page, but here I will give a small piece of code to receive a letter:
 Imap4Client imap = new Imap4Client(); imap.ConnectSsl("imap.gmail.com", 993); //  imap.Login("mail@google.com", "password");//  Mailbox inbox = imap.SelectMailbox("inbox");//    int[] nIdsUnread = inbox.Search("UNSEEN"); //    int nUnreadCount = nIdsUnread.Length; //    for (int i = 0; i < nUnreadCount; i++) { int idx = nIdsUnread[i]; //       //    Message message = inbox.Fetch.MessageObject(idx); // message.Subject -    // message.BodyText.Text -    //    } 

This is how you can read the letter - only ten lines of code, but they pull five libraries (DLL) into the program folder, and then you have to carry them everywhere.

Reading files from cloud storage is even easier than sending them there:
 // strFilePath -        HttpWebRequest web = (HttpWebRequest)WebRequest.Create("https://webdav.yandex.ru/" + strFilePath); //     web.Credentials = new NetworkCredential("mail@yandex.ru", "password"); web.Headers.Add("Authorization: Basic " + Convert.ToBase64String(Encoding.Unicode.GetBytes("mail@yandex.ru" + ":" + "password"))); web.Accept = "*/*"; web.Method = "GET"; HttpWebResponse resp = (HttpWebResponse)web.GetResponse(); using (StreamReader sr = new StreamReader(resp.GetResponseStream())) { string text = sr.ReadToEnd(); // text -        } 

But this example will read only one file, and we need to read all the files from the specified directory. This task is solved by preliminary request of the file list. The server will return the XML file to us, and going over the contents of the <d: displayname> tags we will get a list of files:
 // strPath -      HttpWebRequest web = (HttpWebRequest)WebRequest.Create("https://webdav.yandex.ru/" + strPath); //     web.Credentials = new NetworkCredential("mail@yandex.ru", "password"); web.Headers.Add("Authorization: Basic " + Convert.ToBase64String(Encoding.Unicode.GetBytes("mail@yandex.ru" + ":" + "password"))); web.Accept = "*/*"; web.Headers.Add("Depth: 1"); web.Method = "PROPFIND"; List<string> retValue = new List<string>(); //          HttpWebResponse resp = (HttpWebResponse)web.GetResponse(); using (StreamReader sr = new StreamReader(resp.GetResponseStream())) { //   XML .   : XmlDocument xmlDoc = new XmlDocument(); xmlDoc.LoadXml(sr.ReadToEnd()); XmlNodeList displaynames = xmlDoc.GetElementsByTagName("d:displayname"); int nCount = displaynames.Count; for (int i = 1; i < nCount; i++) { retValue.Add(displaynames[i].InnerText); } } 

DNS

After receiving all the information from the repository, the question arises - what next? And there are not many options:
  1. Show the user everything received in a convenient way.
  2. To organize the ability to access remote computers by name

With the first one everything is simple - a regular list and a couple of columns. But the second is more difficult. You can implement in two ways:
  1. Editing the% windir% \ system32 \ drivers \ etc \ hosts file
  2. Create your local DNS server

The first is implemented quite simply, the hosts file is a plain text file that is easily read / changed / saved, the main thing is just to have rights to it. And the usual user doesn’t have them, so we increase the “Windows Account Management Level” for our application by setting the value for requestedExecutionLevel = requireAdministrator in the app.manifest file. Read more about this here . We work with the hosts file like this:
 //   string strHosts = File.ReadAllText(Environment.SystemDirectory + "\\drivers\\etc\\hosts"); string[] linesHostsOld = Regex.Split(strHosts, "\r\n|\r|\n"); //    StringBuilder sbHostsNew = new StringBuilder(); //    foreach (string lineHosts in linesHostsOld) { sbHostsNew.AppendLine(lineHosts); } //       sbHostsNew.AppendLine("127.0.0.1 hello.world.com"); //    File.WriteAllText(Environment.SystemDirectory + "\\drivers\\etc\\hosts", sbHostsNew.ToString()); 

The second option I did not manage to test well, since so far everyone is fully satisfied with the performance of the first method. DNS server is implemented using a third-party library " ARSoft.Tools.Net ". Strongly not wise, and these examples make our functions, something like this:
 DnsServer _server = new DnsServer(IPAddress.Any, 10, 10, ProcessQuery); _server.Start(); //   //    DNS  private static DnsMessageBase ProcessQuery( DnsMessageBase message, IPAddress clientAddress, ProtocolType protocol) { message.IsQuery = false; DnsMessage query = message as DnsMessage; if (query != null) { if (query.Questions.Count == 1) { if (query.Questions[0].RecordType == RecordType.A) { if (query.Questions[0].Name.Equals("hello.world.com", StringComparison.InvariantCultureIgnoreCase)) { IPAddress ip; if (IPAddress.TryParse("127.0.0.1", out ip)) { query.ReturnCode = ReturnCode.NoError; DnsRecordBase rec = new ARecord(strHostName, 0, ip); query.AnswerRecords.Add(rec); return message; } } } } } message.ReturnCode = ReturnCode.ServerFailure; return message; } 

Ready application.


By bringing together all the above, and adding a call to the necessary procedures on a timer, you get some semblance of a conceived program. It remains only to modify all the file, bring in the divine form and can be shown to people.
All the settings (and they turned out quite a few) the application stores in the file% PROGRAM_NAME% .exe.config and the file itself lies somewhere in the area:% USERPROFILE% \ AppData \ Local \% PROGRAM_NAME% \ ***. This is implemented using the standard features of Properties.Settings.Default . Passwords are stored there, but in encrypted form. Encryption is done using DPAPI (there is an article and a question on a hard disk on this topic).
I will not describe in detail the work with the settings of the form, with encryption, with timers, with parallel processes and everything else that does not concern the original topic. Who is very interesting - you can always see the source code.

The appearance of the resulting bike:


Four screenshots of the program





When you first start you will need to carefully configure all the necessary parameters.
In the minimum version: on the first computer (with a dynamic address) you will need to configure the "interfaces", and on the second computer (on which we need to know the dynamic address) you will need to carefully configure the "hosts".

Development plans.




The source code of the project and the program itself has yet been uploaded to Yandex.Disk.
Sources can be downloaded here: http://yadi.sk/d/iZNy9wA28E0-E
Binary files are here: http://yadi.sk/d/kYpZIqdn8E-ui

That's all. Thanks for attention.

Source: https://habr.com/ru/post/191172/


All Articles