Эффективное программирование TCP-IP

       

Не «зашивайте» IP-адреса и номера портов в код


| | |

У программы есть только два способа получить IP-адрес или номер порта:

  • из аргументов в командной строке или, если программа имеет графический интерфейс пользователя, с помощью диалогового окна либо аналогичного механизма;
  • с помощью функции разрешения имен, например gethostbyname или getservbyname.
  • Примечание: Строго говоря, getservbyname — это не функция разрешения имени (то есть она не входит в состав DNS-клиента, который отображает имена на IP-адреса и наоборот). Но она рассмотре на вместе с остальными, поскольку выполняет похожие действия.

    Никогда не следует «зашивать» эти параметры в текст программы или помещать их в собственный (не системный) конфигурационный файл. И в UNIX, и в Windows есть стандартные способы получения этой информации, ими и надо пользоваться.

    Теперь IP-адреса все чаще выделяются динамически с помощью протокола DHCP (dynamic host configuration protocol - протокол динамической конфигурации хоста). И это убедительная причина избегать их задания непосредственно в тексте программы. Некоторые считают, что из-за широкой распространенности DHCP и сложности адресов в протоколе IPv6 вообще не нужно передавать приложению числовые адреса, а следует ограничиться только символическими именами хостов, которые приложение должно преобразовать в IP-адреса, обратившись к функции gethostbyname или родственным ей. Даже если протокол DHCP не используется, управлять сетью будет намного проще, если не «зашивать» эту информацию в код и не помещать ее в нестандартные места. Например, если адрес сети изменяется, то все приложения с «зашитыми» адресами просто перестанут работать.

    Всегда возникает искушение встроить адрес или номер порта непосредственно в текст программы, написанной «на скорую руку», и не возиться с функциями типа getXbyY. К сожалению, такие программы начинают жить своей жизнью, а иногда даже становятся коммерческими продуктами. Одно из преимуществ каркасов и библиотечных функций на их основе (совет 4) состоит в том, что код уже написан, так что нет необходимости «срезать углы».


    Рассмотрим некоторые функции разрешения имен и порядок их применения. Вы уже не раз встречались с функцией gethostbyname:
    #include  <netdb.h>        /*  UNIX  */
    #include <winsock2.h> /* Winsock  */
    struct hostent   *gethostbyname(   const  char  *name  );


    Возвращаемое значение: указатель на структуру hostent в случае успеха, h_errno и код ошибки в переменной h_errno - в случае неудачи.
    Функции gethostbyname передается имя хоста, а она возвращает указателя на структуру ho в tent следующего вида:
    struct hostent   {
     char  *h_name; /*  Официальное имя хоста.*/
     char  **h_aliases; /*  Список синонимов.*/
     int h_addrtype; /* Тип адреса хоста.*/
     int h_length; /* Длина адреса.*/
     char  **h_addr_list; /*  Список адресов, полученных от DNS.*/
     #define h_addr h_addr_list[0];   /* Первый адрес.*/
    };
    Поле h_name указывает на «официальное» имя хоста, а поле h_aliases — на список синонимов имени. Поле h_addrtype содержит либо AF_INET, либо AF_INET6 в зависимости от того, составлен ли адрес в соответствии с протоколом IPv4 или IPv6. Аналогично поле h_length равно 4 или 16 в зависимости от типа адреса. Все адреса типа h_addrtype возвращаются в списке, на который указывает поле h_addr_list. Макрос h_addr выступает в роли синонима первого (возможно, единственного) адреса в этом списке. Поскольку gethostbyname возвращает список адресов, приложение может попробовать каждый из них, пока не установит соединение с нужным хостом.
    Работая с функцией gethostbyname нужно учитывать следующие моменты:
  • если хост поддерживает оба протокола IPv4 и IPv6, то возвращается только один тип адреса. В UNIX тип возвращаемого адреса зависит от параметра RES_USE_INET6 системы разрешения имен, который можно явно задать, обратившись к функции res_init или установив переменную среду, а также с помощью опции в конфигурационном файле DNS. В соответствии с Win-sock, всегда возвращается адрес IPv4;

  • структура hostent находится в статической памяти. Это означает, что функция gethostbyname не рентабельна;



  • указатели, хранящиеся в статической структуре hostent, направлены на другую статическую или динамически распределенную память, поэтому при желании скопировать структуру необходимо выполнять глубокое копирова­ние. Это означает, что помимо памяти для самой структуры hostent необ­ходимо выделить память для каждой области, на которую указывают поля структуры, а затем скопировать в нее данные;

  • как говорилось в совете 28, адреса, хранящиеся в списке, на который указывает поле h_addr_list, уже приведены к сетевому порядку байтов, так что применять к ним функцию htonl не надо.

  • Вы можете также выполнить обратную операцию - отобразить адреса хосто на их имена. Для этого служит функция gethostbyaddr.
    #include  <netdb.h> /*   UNIX.   */
    #include  <winsock2.h> /*  Winsock.  */
    struct hostent *gethostbyaddr(const char *addr, int len, int type);
    Возвращаемое значение: указатель на структуру hostent в случае успеха, NULL и код ошибки в переменной h_errno - в случае неудачи.
    Несмотря на то, что параметр addr имеет тип char*, он указывает на структуру in_addr (или in6_addr в случае IPv6). Длина этой структуры задается параметром len, а ее тип (AF_INET или AF_INET6) - параметром type. Предыдущие замечания относительно функции gethostbyname касаются и gethostbyaddr.
    Для хостов, поддерживающих протокол IPv6, функции gethostbyname недо­статочно, так как нельзя задать тип возвращаемого адреса. Для поддержки IPv6 (и других адресных семейств) введена общая функция gethostbyname2, допус­кающая получение адресов указанного типа.
    #include <netdb.h>/*  UNIX  */
    struct hostent *gethostbyname2(const char *name, int af );
    Возвращаемое значение: указатель на структуру hostent в случае успеха, NULL и код ошибки в переменной h_errno - в случае неудачи.
    Параметр af - это адресное семейство. Интерес представляют только возмож­ные значения AF_INET или AF_INET6. Спецификация Winsock не определяет функ­цию gethostbyname2, а использует вместо нее функционально более богатый (и сложный) интерфейс WSALookupServiceNext.


    Примечание: Взаимодействие протоколов IPv4 и IPv6 - это в значительной мере вопрос обработки двух разных типов адресов. И функция gethostbyname2 предлагает один из способов решения этой проблемы. Эта тема подробно обсуждается в книге [Stevens 1998], где также приведена реализация описанной в стандарте POSIX функции getaddrinfo. Эта функция дает удобный, не зависящий от протокола способ работы с обоими типами адресов. Спомощъю getaddrinfo можно написать приложение, которое будет одинаково работать и с IPv4, и с IPv6.
    Раз системе (или службе DNS) разрешено преобразовывать имена хостов в IP-адреса, почему бы ни сделать то же и для номеров портов? В совете 18 рассматри­вался один способ решения этой задачи, теперь остановимся на другом. Так же, как gethostbyname и gethostbyaddr выполняют преобразование имени хоста в адрес и обратно, функции getservbyname и getservbyport преобразуют сим­волическое имя сервиса в номер порта и наоборот. Например, сервис времени дня daytime прослушивает порт 13 в ожидании TCP-соединений или UDP-дата-грамм. Можно обратиться к нему, например, с помощью программы telnet:
    telnet  bsd  13
    Однако необходимо учитывать, что номер порта указанного сервиса равен 13. К счастью, telnet понимает и символические имена портов:
    telnet bsd daytime
    Telnet выполняет отображение символических имен на номера портов, вызывая функцию getservbyname; вы сделаете то же самое. В листинге 2.3 выувидите, что в предложенном каркасе этот вызов уже есть. Функция set_addres сначала оперирует параметром port как представленным в коде ASCII целым числом, то есть пытается преобразовать его в двоичную форму. Если это не получается, то вызывается функция getservbyname, которая ищет в базе данных символическое имя порта и возвращает соответствующее ему число­вое значение.
    Прототип функции getservbyname похож на gethostbyname:
    #include <netdb.h> /*  UNIX  */
    #include <winsock2.h> /* Winsock  */
    struct servant *getservbyname(const char *name, const char *proto );


    Возвращаемое значение: указатель на структуру servent в случае успеха, NULL - в случае неудачи.
    Параметр name - это символическое имя сервиса, например «daytime». Если параметр pro to не равен NULL, то возвращается сервис, соответствующий заданным имени и типу протокола, в противном случае - первый найденный сервис с именем name. Структура servent содержит информацию о найденном сервисе:
    struct servent {
     char *s_name; /*Официальное имя сервиса. */
     char **s_aliases; /*Список синонимов. */
     int s_port; /*Номер порта. */
     char *s_proto; /*Используемый протокол. */
    };
    Поля s_name и s_aliases содержат указатели на официальное имя сервиса и его синонимы. Номер порта сервиса находится в поле s_port. Как обычно, этот номер уже представлен в сетевом порядке байтов. Протокол (TCP или UDP), иcпользуемый сервисом, описывается строкой в поле s_proto.
    Вы можете также выполнить обратную операцию - найти имя сервиса по номеру порта. Для этого служит функция getservbyport:
    #include <netdb.h> /* UNIX.   */
    #include <winsock2.h> /* Winsock.   */
    struct servent *getservbyport( int port, const char *proto);
    Возвращаемое значение: указатель на структуру servent в случае успеха, NULL - в случае неудачи
    Передаваемый в параметре port номер порта должен быть записан в сетевом порядке. Параметр pro to имеет тот же смысл, что и раньше.
    С точки зрения программиста, данный каркас и библиотечные функции решают задачи преобразования имен хостов и сервисов. Они сами вызывают нужные функции, а как это делается, не должно вас волновать. Однако нужно знать, как ввести в систему необходимую информацию.
    Обычно это делается с помощью одного из трех способов:
  • DNS;

  • сетевой информационной системы (NIS) или NIS+;

  • файлов hosts и services.

  • DNS (Domain Name System - служба доменных имен) - это распределенная база данных для преобразования имен хостов в адреса.
    Примечание: DNS используется также для маршрутизации электронной почты. Когда посылается письмо на адрес jsmithesomecompany. com, с помощью DNS ищется обработчик (или обработчики) почтыдля компании somecompany.com. Подробнее это объясняетсяв книге [Albitz and Lin 1998].


    Ответственность за хранение данных распределяется между зонами (грубо говоря, они соответствуют адресным доменам) и подзонами. Например, bigcompany.com может представлять собой одну зону, разбитую на несколько подзон, соответствующих отделам или региональным отделениям. В каждой зоне и подзоне работает один или несколько DNS-серверов, на которых хранится вся информация о хостах в этой зоне или подзоне. Другие DNS-серверы могут запросить информацию у дан­ных серверов для разрешения имен хостов, принадлежащих компании BigCompany.
    Примечание: СистемаDNS -этохороший пример UDP-приложения. Как правило, обмен с DNS-сервером происходит короткими транзакциями. Клиент (обычно одна из функций разрешения имен) посылает UDP-датаграмму, содержащую запрос к DNS-cepeepy.Если в течение некоторого времени ответ не получен, то пробуется другой сервер, если таковой известен. В противном случае повторно посылается запрос первому серверу, но с увеличенным тайм-аутом.
    На сегодняшний день подавляющее большинство преобразований между име­нами хостов и IP-адресами производится с помощью службы DNS. Даже сети, не имеющие выхода вовне, часто пользуются DNS, так как это упрощает администри­рование. При добавлении в сеть нового хоста или изменении адреса существующего нужно обновить только базу данных DNS, а не файлы hosts на каждой машине.
    Система NIS и последовавшая за ней NIS+ предназначены для ведения центра­лизованной базы данных о различных аспектах системы. Помимо имен хостов и IP-адресов, NIS может управлять именами сервисов, паролями, группами и дру­гими данными, которые следует распространять по всей сети. Стандартные функции разрешения имен (о них говорилось выше) могут опрашивать и базы данных NIS. В некоторых системах NIS-сервер при получении запроса на разрешение име­ни хоста, о котором у него нет информации, автоматически посылает запрос DNS-серверу В других системах этим занимается функция разрешения имен.
    Преимущество системы NIS в том, что она централизует хранение всех распространяемых по сети данных, упрощая тем самым администрирование больших сетей. Некоторые эксперты не рекомендуют NIS, так как имеется потенциальная угроза компрометации паролей. В системе NIS+ эта угроза снята, но все равно многие опасаются пользоваться ей. NIS обсуждается в работе [Brown 1994].


    Последнее и самое неудобное из стандартных мест размещения информации об именах и IP-адресах хостов - это файл hosts, обычно находящийся в каталоге /etc на каждой машине. В этом файле хранятся имена, синонимы и IP-адреса хо­стов в сети. Стандартные функции разрешения имен просматривают также и этот файл. Обычно при конфигурации системы можно указать, когда следует просмат­ривать файл hosts - до или после обращения к службе DNS.
    Другой файл - обычно /etc/services - содержит информацию о соответствии имен и портов сервисов. Если NIS не используется, то, как правило, на каждой машине имеется собственная копия этого файла. Поскольку он изменяется редко, с его администрированием не возникает таких проблем, как с файлом hosts. В совете 17 было сказано о формате файла services.
    Основной недостаток файла hosts - это очевидное неудобство его сопровождения. Если в сети более десятка хостов, то проблема быстро становится почти неразрешимой. В результате многие эксперты рекомендуют полностью отказаться от такого метода. Например, в книге [Lehey 1996] советуется следующее: «Есть только одна причина не пользоваться службой DNS - если ваш компьютер не подсоединен к сети».

    Содержание раздела