Делаем собственный сервис по определению WHOIS любого домена



    Сервис WHOIS – это один из основных инструментов для людей, которые постоянно работают с доменными именами. Он нужен как любому человеку, желающему подобрать себе красивое доменное имя, так и хостинг-провайдеру, который помимо прочих услуг может предоставлять возможность регистрации домена. И те, и другие ищут автоматизации своей работы.

    Итак, давайте разберёмся как это работает.

    У каждой доменной зоны, будь то RU, COM или HOST есть как минимум один центр (whois-сервер), который обладает информацией о находящемся в её зоне домене. Для зоны RU, например, это whois.ripn.net и whois.tcinet.ru

    Все whois-сервера всех доменных зон предоставляют информацию по строго унифицированному протоколу, слушающем подключения и запросы на 43-м порту.

    Сам же запрос к whois-серверу – это просто отправка интересующего доменного имени на данный порт, после чего мы просто читаем ответ.

    Для разработки системы автоматизированного получения WHOIS информации по доменам в первую очередь необходимо заполучить список серверов whois для всех существующих доменных зон. Правильным запросом Google в первой же строчке выдаёт ссылку на проект в GitHub, где выложен полноценный XML, содержащий всю требуемую нам информацию:
    https://raw.githubusercontent.com/whois-server-list/whois-server-list/master/whois-server-list.xml
    

    Сохраняем этот файл себе для последующего открытия в нашем приложении.

    Если кому-то покажется, что постоянно обновлять XML файл и парсить его дело не очень удобное, то можно воспользоваться более простым способом – сторонним online-сервисом whois-servers.net. Просто склейте имя корневой зоны с хвостом «.whois-servers.net» и получите готовый адрес для отправки запроса на данные WHOIS (например, для зоны COM получится адрес «com.whois-servers.net»). Этот сервис к WHOIS не имеет никакого отношения, просто своими доменами третьего уровня ссылается на корректные адреса рабочих серверов WHOIS.

    Пример разработан на C# в обычном WinForms: всего 2 текстовых поля и 1 кнопка.

    Для получения списка серверов WHOIS по доменной зоне из загруженного файла XML была написана следующая функция:

    public static List<string> GetWhoisServers(string domainZone){
        if (_serverList == null){
            _serverList = new XmlDocument();
            //загружаем XML если ранее он не был загружен
            _serverList.Load("whois-server-list.xml");
        }
        List<string> result = new List<string>();
        //определяем функцию для рекурсивной обработки XML
        Action<XmlNodeList> find = null;
        find = new Action<XmlNodeList>((nodes) =>{
            foreach (XmlNode node in nodes)
                if (node.Name == "domain"){
                    //находим в XML документе интересующую нас зону
                    if (node.Attributes["name"] != null && node.Attributes["name"].Value.ToLower() == domainZone){
                        foreach (XmlNode n in node.ChildNodes)
                            //забираем все адреса серверов, по которым можно получить данные о домене в требуемой зоне
                            if (n.Name == "whoisServer"){
                                XmlAttribute host = n.Attributes["host"];
                                if (host != null && host.Value.Length > 0 && !result.Contains(host.Value))
                                    result.Add(host.Value);
                            }
                    }
                    find(node.ChildNodes);
                }
        });
        find(_serverList["domainList"].ChildNodes);
        return result;
    }
    

    Функция для получения WHOIS информации с уже известного сервера выглядит так:

    public static string Lookup(string whoisServer, string domainName){
        try{
            if (string.IsNullOrEmpty(whoisServer) || string.IsNullOrEmpty(domainName))
                return null;
    
            //Punycode-конвертер (если требуется)
            Func<string, string> formatDomainName = delegate(string name){
                return name.ToLower()
                    //если в названии домена есть нелатинские буквы и это не цифры и не точка и не тире,
                    //например, "россия.рф" то сконвертировать имя в XN--H1ALFFA9F.XN--P1AI
                    .Any(v => !"abcdefghijklmnopqrstuvdxyz0123456789.-".Contains(v)) ?
                        new IdnMapping().GetAscii(name) ://вернуть в Punycode
                        name;//вернуть исходный вариант
            };
    
            StringBuilder result = new StringBuilder();
            result.AppendLine("По данным " + whoisServer + ": ------------------------------------------");
            using (TcpClient tcpClient = new TcpClient()){
                //открываем соединение с сервером WHOIS
                tcpClient.Connect(whoisServer.Trim(), 43);
                byte[] domainQueryBytes = Encoding.ASCII.GetBytes(formatDomainName(domainName) + "\r\n");
                using (Stream stream = tcpClient.GetStream()){
                    //отправляем запрос на сервер WHOIS
                    stream.Write(domainQueryBytes, 0, domainQueryBytes.Length);
                    //читаем ответ в формате UTF8, так как некоторые национальные домены содержат информацию на местном языке
                    using (StreamReader sr = new StreamReader(tcpClient.GetStream(), Encoding.UTF8)){
                        string row;
                        while ((row = sr.ReadLine()) != null)
                            result.AppendLine(row);
                    }
                }
            }
            result.AppendLine("---------------------------------------------------------------------\r\n");
            return result.ToString();
        }catch{}
        return "Не удалось получить данные с сервера " + whoisServer;
    }
    

    Функция умеет автоматически конвертировать домены на кириллице (или любом другом языке), благодаря чему запрос отлично работает как с доменами в классических зонах на латинице, так и с любыми национальными. Особенно приятно, что в .NET эта конвертация реализуется одной строчкой кода с использованием класса System.Globalization.IdnMapping

    Эти 2 созданные функции дают нам всё, что требуется и остаётся только обработать нажатие кнопки «Получить данные» на форме.

    Имея доменное имя на входе для проверки WHOIS сначала нам необходимо вычленить зону, в которой он находится. Ввиду того, то домен может быть в зоне какого угодно уровня (вовсе не обязательно, что всегда во второй!), я написал простой цикл, который для каждого уровня, начиная с наивысшего, проверит наличие серверов WHOIS.

    private void get_BTN_Click(object sender, EventArgs e){
        List<string> whoisServers = null;
        //разбиваем домен на уровни
        string[] domainLevels = domainName_TB.Text.Trim().Split('.');
        //по шагам пытаемся найти WHOIS-сервер для доменной зоны различного уровня от большей к меньшей
        for (int a = 1; a < domainLevels.Length; a++){
            /*
                * Если требуется информация по домену test.some-name.ru.com,
                * то сначала попытаемся найти WHOIS-сервера для some-name.ru.com,
                * после для ru.com и если всё ещё не найдём, то для com
            */
            string zone = string.Join(".", domainLevels, a, domainLevels.Length - a);
            whoisServers = WhoisService.GetWhoisServers(zone);
            //если нашли WHOIS-сервер, то поиск прекращаем
            if (whoisServers.Count > 0)
                break;
        }
    
        if (whoisServers == null || whoisServers.Count == 0)
            result_TB.Text = domainName_TB.Text + "\r\n----------------\r\nНеизвестная доменная зона";
        else{
            result_TB.Text = "";
            foreach (string whoisServer in whoisServers)
                result_TB.Text += WhoisService.Lookup(whoisServer, domainName_TB.Text);
        }
    }
    

    Далее спрашиваем информацию по домену у каждого из найденных серверов и записываем её в окно вывода результата.



    Чтобы не собирать по частям из кода в статье, проект в готовом виде загрузил сюда.

    Similar posts

    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 21

      +3
      Проблема не в том, чтобы получить эти данные, а распарсить :)
        +1
        Парсинг — это не проблема, просто немного муторно. Распарсить текст сможет программист любого уровня и для этого не нужно ничего искать в сети.
        Здесь не стал уж вставлять простыни кода парсинга… Каждому нужно что-то получить своё в данных whois
        Мне лично были интересны ns-сервера.
        Для парсинга их написал
        такую функцию ->
        public static List<string> ParseNameServers(string whoisData){
            if (string.IsNullOrEmpty(whoisData)) return null;
            List<string> result = new List<string>();
            whoisData = whoisData.ToLower().Replace("\r\n", "\n").Replace("\r", "\n");
            foreach (string line in whoisData.Split('\n'))
                if (line.Length > 0){
                    int sep = line.IndexOf(":");
                    if (sep > 0){
                        string param = line.Substring(0, sep).ToLower();
                        if (param.Contains("n") && param.Contains("server")){
                            string nameServer = line.Substring(sep + 1, line.Length - sep - 1).Trim().ToLower();
                            while (nameServer.Length > 0 && nameServer.Substring(nameServer.Length - 1, 1) == ".")
                                nameServer = nameServer.Substring(0, nameServer.Length - 1);
                            if (nameServer.Length > 0 && nameServer.Contains(".") && !result.Contains(nameServer))
                                result.Add(nameServer);
                        }
                    }
                }
            if (result.Count == 0 && whoisData.Contains("ns server")) {
                bool startFound = false;
                foreach (string line in whoisData.Split('\n'))
                    if (line.Contains("ns server")) startFound = true;
                    else if (startFound){
                        string nameServer = FormatDomainName(line);
                        if (!ValidateDomainName(nameServer, false)) break;
                        if (nameServer.Length > 0 && nameServer.Contains(".") && !result.Contains(nameServer))
                            result.Add(nameServer);
                    }
            }
            return result;
        }
        
        public static string FormatDomainName(string value){
            if (string.IsNullOrEmpty(value)) return value;
            value = value.Trim().ToLower().Replace(",", ".");
            while (value.Contains("..")) value = value.Replace("..", ".");
            while (value.Length > 0 && value.Substring(0, 1) == ".") value = value.Substring(1, value.Length - 1).Trim();
            while (value.Length > 0 && value.Substring(value.Length - 1,1) == ".") value = value.Substring(0, value.Length - 1).Trim();
            return value;
        }
        
        private static string _symbols = "abcdefghijklmnopqrstuvwxyz0123456789-.";
        public static bool ValidateDomainName(string value, bool onlySecondLevelDomains){
            if (string.IsNullOrEmpty(value)) return false;
            value = value.Trim().ToLower();
            int dotCount = 0;
            for (var a = 0; a < value.Length; a++){
                if (value.Substring(a, 1) == ".")
                    dotCount++;
                if (!_symbols.Contains(value.Substring(a, 1)))
                    return false;
            }
            return onlySecondLevelDomains ? dotCount == 1 : dotCount > 0;
        }
        

          +3
          Распарсить-то не проблема. Проблема в том, что у почти у каждого whois-сервера — свой формат :)

          Чуть подробнее по теме: https://whoisrb.org/manual/parser/
            0
            nslookup -q=ns…? И не имеет ограничений на число запросов.
              0
              Это же другое совершенно…
                0
                NS сервера в ответе от whois сервера и через запрос DNS — одно и то же. Нужно не использовать общедоступные рекурсивные, например самый известный 8.8.8.8, а спрашивать сначала корневые (*.root-servers.net), а они ответят списком DNS серверов конкретной зоны.
                Просто, когда подбираете свободные имена, то лучше сначала воспользоваться именно опросом DNS, т.к. его легко распараллелить, фильтруя, что в некоторых зонах на любое доменное имя будет корректный ответ (например, раньше так в .tk было, набрав любой свободный домен попадали на страницу регистрации, если правильно помню).
                Поэтому, как вы пишите, что если вам интересны только ns-сервера, то это — из пушки по воробьям; из whois куда интереснее дата, когда домен теоретически освободится.
          0
          А как получить другой вид информации?
          Например историю по домену?
          Вот такую: http://who.ru/simplesearch-rc?domainsimple=habrahabr.ru
            0
            К сожалнию, не нашёт такой информации.
            Но, боюсь предположить, что она не нахидтся в открытом доступе на серверах WHOIS, т.к. такие как reg.ru эту историю продают и даеко не для всех зон (или вообще только ru, su, рф).
              0

              Очевидно, что у них есть бот, который все время проверяет WHOIS по списку доменов.

              0
              Что скажете по поводу этого средства?
                0
                Не слышал о таком
                +1
                Настраивал я как-то мониторинг регистрации доменов по информации из whois на баше. Дак вот, самая главная проблема в том, что для некоторых зон информацию просто так не предоставляют, например для ES, либо при частых запросах начинают банить по IP. Да и формат ответа хоть и имеет определённую структуру, но далёк от удобоваримого.
                  +1
                  Не все так просто с whois как хотелось бы. Если в данной программе запросить данные по google.com, вываливается довольно большой список «левых» доменов. Некоторые whois-сервера позволяют писать уточняющий запрос "=google.com"
                  Использовать кем то собраный xml, тоже наверно не лучший вариант. Почему не взять данные как положено из www.iana.org/whois?
                    0
                    За информацию спасибо!
                      0
                      www.iana.org/whois, так же доступен как whois-сервис: whois.iana.org на 43 порту.
                      +1
                      Все что вы описали — это даже не самая большая проблемы :)

                      Когда вы начинаете использовать ваш whois на production'e и более-менее средних нагрузках, то сразу вылазит проблема «как проверить 20 доменов за секунду».

                      Разные whois сервера позволяют параллельно принимать только 1-N запросов. Например, ua только 3 в один момент времени (свыше — баниться на несколько минут), и т.д.
                        0
                        Какое решение кроме использования пула различных IP-адресов на сетевом адаптере видите? )
                          0
                          Можно использовать прокси-сервера.
                            0
                            Регистраторы используют протокол EPP для проверки занятости/срока делегирования и обновления данных домена а так же регистрации. Иногда регистраторы предоставляют свой whois сервис, который внутри использует тот же EPP, потому как у команды whois в разных дистрибутивах и whois серверов разных доменных зон форматы вывода и названия полей могут сильно отличаться, и парсить одновременно несколько сотен запросов к whois сайту любого регистратора с разными форматами вывода данных я бы сказал — идея плохая от слова совсем.
                              0
                              Не соглашусь с этим моментом. По EPP можно получить всю информацию только по своим доменам. Информации по доменам другого регистратора регистратор не видит, только статус «свободен/занят». Регистраторы так же используют Whois, только доступ к нему не ограничивается (до разумных объемов).

                              Поэтому единственный способ получать информацию по любым доменам — Whois. Но автор статьи действительно даже не проверил google.com и не увидел, что многие серверы требуют разный формат запроса: google.com, «google.com», =google.com, domain google.com и тд
                          0
                          Для тех кто юзает Unix-like.
                          Я делаю так, что-бы получить краткий вывод.

                          function _whois_this(){
                          whois "$1" | grep «Organization:\|Address:\|Country:\|StateProv:»
                          }

                          Only users with full accounts can post comments. Log in, please.