Не «зашивайте» IP-адреса и номера портов в код
| | |
У программы есть только два способа получить IP-адрес или номер порта:
Примечание: Строго говоря, 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 нужно учитывать следующие моменты:
Вы можете также выполнить обратную операцию - отобразить адреса хосто на их имена. Для этого служит функция 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 (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 - если ваш компьютер не подсоединен к сети».