Как бы не был надёжен протокол TCP, он не имеет никакого понятия о том, что же, собственно, за данные с его помощью передаются. Да и не должен: принцип разделения уровней не позволяет заглядывать «внутрь» передаваемого пакета, и способов наверняка распознать используемый в нём прикладной протокол нет. Прикладной уровень, в отличие от транспортного, предусматривает сколько угодно протоколов передачи данных. Интерпретация данных, в конце концов, дело уже не ядра, а какой-нибудь программы («приложения», как правило, демона). Для того, чтобы можно было предположить, какой протокол используется при передаче данных и чтобы система могла передать эти данные соответствующей программе, ещё на транспортном уровне было введено понятие порт.
Клиент-серверная модель
С точки зрения прикладного уровня, порт — это идентификатор сервиса, предоставляемого системой. В самом деле, практически любой акт передачи данных выглядит, как если бы некий клиент, которому эти данные нужны, запрашивал их у сервера, который может их предоставить.
Обратная ситуация, когда клиент хочет передать что-то серверу, сути дела не меняет: сервер предоставляет услугу клиенту, на этот раз — по приёму данных.
Отношения между программами, которые связываются по сети друг с другом, почти всегда ассимметричны: одной что-то надо, у другой это что-то есть. При установлении соединения и приложение (программа-клиент), и служба (программа-сервер) используют механизм сокетов, описанный в лекции Работа с внешними устройствами, однако ведут себя по-разному.
Служба, запускаясь на сервере, создаёт сетевой сокет и прикрепляет его к определённому порту сервера с помощью системного вызова bind()
. Затем она регистрируется в качестве обработчика запросов (listener), приходящих на этот порт. Служба ждёт запросов, и когда они поступают, предпринимает какие-нибудь действия, например, считывает пришедшие данные и анализирует их в соответствии со своим протоколом, отсылает какие-то данные абоненту, пославшему запрос и т. п.
Приложение, запускаясь на клиенте, также создаёт сокет и присоединяется с его помощью к тому же порту на сервере, где запущена служба, используя системный вызов connect()
. Затем оно, как и служба, посылает и получает данные. Разницы между обменом данными по сетевому сокету и по сокету в файловой системе нет. Очерёдность обмена данными определяется прикладным протоколом.
Как приложение узнаёт, к какому именно порту необходимо подключиться? За большинством прикладных протоколов закреплён постоянный номер порта. Постоянные номера портов и названия соответствущих протоколов хранятся в файле /etc/services
:
[root@localhost root]# wc /etc/services
553 2794 19869 /etc/services
[root@localhost root]# egrep "^(ftp|http|smtp|ssh).*tcp" /etc/services
ftp 21/tcp # File Transfer [Control]
ssh 22/tcp # SSH Remote Login Protocol
smtp 25/tcp mail # Simple Mail Transfer Protocol
http 80/tcp www www-http # World Wide Web HTTP
Пример 6.
Постоянные номера портов для некоторых протоколов Этот файл — не догма, а руководство к действию: каждый может организовать, допустим, сервис HTTP по 25-му порту. Только как об этом узнают другие клиенты, и что подумают почтовые программы, ожидая по этому порту встретить сервис SMTP (пересылка почты)? Вывести список установленных соединений, а также служб-обработчиков можно командой netstat
:
[root@localhost root]# netstat -anA inet
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 0.0.0.0:111 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN
tcp 0 0 192.168.102.125:22 192.168.102.1:33208 ESTABLISHED
udp 0 0 0.0.0.0:111 0.0.0.0:*
Пример 7. Просмотр установленных соединений и служб
Здесь видно, что на компьютере зарегистрировано два TCP-обработчика (на портах 111 и 22), один UDP-обработчик по 11-му порту (понятие Listener, то есть обработчик соединения для UDP не имеет смысла), а также установлено одно соединение с компьютера 192.168.102.1, исходящий порт 33208, к 22-му порту (это порт службы Secure Shell, предоставляющей удалённый терминальный доступ... видимо, Гуревич работает?). В более сложных случаях, когда номер порта заранее неизвестен, а известно только название и версия сервиса, используется служба portmap
, которая раздаёт незанятые порты службам и сообщает приложениям, к какому из них надо обратиться. Порт 111 соответстует именно этой службе.
Обслуживание прикладного уровня в Linux
Самый простой способ проверить, предоставляет ли некий сервер услуги по некоему TCP-порту — это подключиться к нему. Если под рукой нет приложения, работающего по требуемому протоколу, не беда: подойдёт утилита telnet
. В качестве первого параметра следует указать адрес компьютера, к которому нужно подключиться, а в качестве второго (необязательного) — номер порта. Когда-то эта утилита использовалась в качестве клиента к терминальной службе, однако от неё пришлось отказаться: пароль пользователя передавался по сети нешифрованным. Но в качестве клиента других служб, многие из которых используют текстовые протоколы, telnet
используется и поныне. Если даже протокол и не текстовый — не беда: можно выйти в командный режим telnet
, нажав «^[
», и подать команду close
, которая закроет соединение:
[root@localhost root]# telnet 192.168.102.1 112
Trying 192.168.102.1...
telnet: connect to address 192.168.102.1: Connection refused
[root@localhost root]# telnet 192.168.102.1 111
Trying 192.168.102.1...
Connected to 192.168.102.1.
Escape character is '^]'.
^]
telnet> close
Connection closed.
Пример 8. Использование telnet
В сценариях вместо интерактивной утилиты telnet
стоит использовать netcat
, которая работает как cat
в указанный сокет или из него.
Как уже говорилось, интерпретацией прикладных протоколов занимаются разнообразные программы. Прикладной протокол можно представить как обмен сообщениями, часто текстовыми, между клиентом и сервером. Было бы естественно оформлять такие программы в виде фильтров, чтобы пользоваться простейшими функциями ввода-вывода. Однако механизм сокетов предусматривает асинхронную передачу данных, для чего используются другие функции. Программа, желающая обслуживать сетевые соединения по определённому порту, должна удовлетворять четырём требованиям:
- Быть демоном, то есть постоянно находиться в памяти;
- Создавать сокет, прикреплять его к порту;
- Регистрироваться как обработчик по этому сокету и принимать соединения (возможно, придётся обрабатывать несклько соединений одновременно);
- Анализировать прикладной протокол и действовать по результатам анализа.
Нетрудно заметить, что первые три свойства — общие для большинства сервисов. В Linux есть метадемон inetd
, который берёт на себя всю общую сетевую часть работы, а программам прадоставляет разбираться в прикладном протоколе. Сделать свой сетевой сервис с помощью inetd
становится очень просто: пользователь программирует фильтр, задача которого — обмениваться командами прикладного пртокола с помощью стандартного ввода и стандартного вывода. Этот фильтр регистрируется в настройках inetd
с указанием порта, с которого будут приниматься запросы. После чего сам inted
становится обработчиком запросов по всем указанным портам, сам открывает соединение, запуская соответствующий фильтр, а данные из сокета пересылает туда и обратно по двум каналам. При этом фильтр-обработчик даже не должен быть демоном: это обычная программа, которая завершается, когда это предусмотрено прикладным протоколом, или когда закрывается входной поток.
Мефодий минут за пять написал службу, которая в ответ на подключение передаёт календарь на текущий месяц. В его системе используется модернизированная версия inetd — xinted
, обученная чтению конфигурационных файлов по схеме «. d»:
[root@localhost root]# grep quake /etc/services
quake 26000/tcp
quake 26000/udp
[root@localhost root]# cat /etc/xinetd.d/calendar
service quake
{
socket_type = stream
protocol = tcp
wait = no
user = nobody
server = /usr/bin/cal
disable = no
}
Пример 9. Настройка cal
в качестве сетевой службы
Вместо номера порта можно использовать название протокола из /etc/services
. Мефодий воспользовался портом 26000
(чем мог создать некоторые трудности поклонникам одной компьютерной игры). Осталось только перезагрузить xinetd
, чтобы он нашёл новый конфигурационный файл, и подключиться к 26000 порту:
[root@localhost root]# service xinetd restart
Stopping xinetd service: [ DONE ]
Starting xinetd service: [ DONE ]
[root@localhost root]# telnet localhost quake
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
December 2004
Su Mo Tu We Th Fr Sa
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Пример 10. Подключение к самодельной службе «календарь»
Служба доменных имён
В предыдущих примерах Мефодий использовал ключ «-
n» многих сетевых утилит, чтобы избежать путаницы между IP-адресами и доменными именами компьютеров. С другой стороны, доменные имена — несколько слов (часто осмысленных) – запоминать гораздо удобнее, чем адреса (четыре каких-то числа).
Когда-то имена всех компьютеров в сети, соответствующие IP-адресам, хранились в файле /etc/hosts
. Пока абоненты Internet были наперечёт, поддерживать правильность его содержимого не составляло труда. Как только сеть начала расширяться, неувязок стало больше. Трудность была не только в том, что содержимое hosts
быстро менялось, но и в том, что за соответствие имён адресам в различных сетях отвечали разные люди и разные организации. появилась необходимость структурировать глобальную сеть не только топологически (с помощью IP и сетевых масок), но и административно, с указанием, за какие группы адресов кто отвечает.
Проще всего было структурировать сами имена компьютеров. Вся сеть была поделена на домены — зоны ответственности отдельных государств («us», «uk», «ru», «it» и т. п.) или независимые зоны ответственности («com», «org», «net», «edu» и т. п.). Для каждого из таких доменов первого уровня должно присутствовать подразделение, выдающее всем желающим абонентам имена, заканчивающиеся на «.домен
». Подразделение обязано огранизовать и поддерживать службу, заменяющую файл hosts
: любой желающий имеет право узнать, какой IP-адрес соответствует имени компьютера в этом домене или какому доменному имени соответствует определённый IP-адрес.
Такая служба называется DNS (Domain Name Service, служба доменных имён). Она имеет иерархическую структуру. Если за какую-то группу абонентов домена отвечают не хозяева домена, а кто-то другой, ему выделяется поддомен (или домен второго уровня), и он сам распоряжается именами вида «имя_компьютера.поддомен.домен
». Например, за компьютеры в домене «. ru», принадлещие корпорации «Dry Bugs» отвечают сотрудники соответствующего подразделения этой корпорации. Корпорация владеет поддоменом dbugs.ru
. В домене ru
не обязаны знать, какие именно адреса есть в поддомене, но обязаны предоставить информацию о том, как обратиться к серверу имён поддомена dbugs.ru
. Сами сотрудники «Dry Bugs» тоже отвечают только за несколько собственных компьютеров, а всю ответственность за компьютеры в подразделениях перекладывают на сетевых администраторов этих подразделений, выделив им поддомены третьего уровня pr.dbugs.ru
, cook.dbugs.ru
и warehouse.dbugs.ru
. Таким образом получается нечто вроде распределённой сетевой базы данных, хранящей короткие записи о соответствии доменных имён IP-адресам.
- доменное имя
- Имя абонента Internet, имеющее текстовый формат и используемое вместо IP-адреса. Состоит из собственного имени абонента в домене и имени домена, определяющего административную принадлежность абонента. В отличие от IP-адреса, доменное имя не задаётся самим абонентом сети, а устанавливается службой доменных имён.
Все программы, работающие с доменными именами, оказываются клиентами какого-нибудь сервера доменных имён (DNS-сервера). Для этого они обращаются к функциям из библиотеки libresolv
(или им подобным), а те уже определяют, как превратить доменное имя в адрес. Функции используют файл /etc/host.conf
, описывающий, какими способами они должны выполнять преобразование доменных имён в адреса и обратно, а также формат выдачи данных в различных случаях. Обычно сначала проверяется файл /etc/hosts
, а если там соответствий не найдено — /etc/resosv.conf
. В resolv.conf
указан домен по умолчанию (он приписывается в конец имени, если другим способом имя никак не удаётся преобразовать в адрес) и один-два адреса DNS-серверов, к которым и обращаются функции. Такими клиентами выступили утилиты ping
и traceroute
в предыдущих примерах, преобразуя имя www.ru
в адрес 194.87.0.50
. Если утилите traceroute
не указывать ключ «-n
», она подаст несколько DNS-запросов, по одному на каждый маршрутизатор, на обратное преобразование — из IP-адреса в доменное имя.
[root@localhost root]# cat /etc/host.conf
order hosts,bind
multi on
[root@localhost root]# cat /etc/resolv.conf
domain nipponman.ru
nameserver 192.168.102.1
[root@localhost root]# traceroute -q1 www.ru
traceroute to www.ru (194.87.0.50), 30 hops max, 38 byte packets
1 fuji.nipponman.ru (192.168.102.1) 1.378 ms
2 ppp83-237-29-1.pppoe.mtu-net.ru (83.237.29.1) 41.155 ms
3 195.34.53.53 (195.34.53.53) 48.503 ms
4 195.34.53.53 (195.34.53.53) 24.033 ms
5 M9-cr01-A197-cr01.core.mtu.ru (195.34.53.10) 33.414 ms
6 M9-gw2-M9-cr01.core.mtu.ru (195.34.53.81) 26.259 ms
7 s-b3-pos0-0.telia.net (213.248.67.93) 59.791 ms
8 s-bb1-pos5-0-0.telia.net (213.248.66.1) 67.011 ms
9 mow-b1-pos1-0.telia.net (213.248.101.10) 76.138 ms
10 demos-101566-mow-okt-i1.c.telia.net (213.248.78.170) 78.591 ms
11 m9-3-GE4-0-0-vl10.Demos.net (194.87.0.66) 69.813 ms
12 www.ru (194.87.0.50) 70.583 ms
Пример 11. Работа DNS-клиента, встроенного в traceroute
Как видно из примера, обратное преобразование в современной сети работает не всегда. Отсутствие обратной зоны не поощряется сообществом, но и не считается преступлением. Мефодий заметил, что компьютер, не имеющий обратного преобразования адреса, вообще какой-то странный: один раз он передал пакет сам себе (здесь Мефодий не совсем прав: на самом деле этот маршрутизатор отчего-то уменьшает TTL пакета на 2, поэтому-то и на третьем, и на четвёртом шаге именно он возвращает ICMP-сообщение). Кстати сказать, именно по причине того, что DNS-запрос невелик, зато даже один абонент сети может выдать их множество, основным транспортным протоколом для DNS выбран UDP, а не TCP (это не касается протоколов обмена целыми зонами между DNS-серверами, там, коначно, господствует TCP). Если вся задача пользователя — это послать DNS-запрос, то лучше воспользоваться утилитой host
, специально для этого предназначенной:
methody@localhost:~ $ host www.ru
www.ru has address 194.87.0.50
methody@localhost:~ $ host 194.87.0.51
51.0.87.194.in-addr.arpa domain name pointer www.demos-internet.ru.
methody@localhost:~ $ host -t ns www.ru
www.ru name server ns.demos.su.
www.ru name server ns1.demos.net.
methody@localhost:~ $ host -t mx www.ru
www.ru mail is handled by 5 hq.demos.ru.
Пример 12. Утилита host
Довольно необычен формат, в котором хранятся таблицы обратного преобразования адресов. Оказывается, IP-адрес представлен в такой таблице как имя в домене in-addr.arpa
, причём это имя совпадает с адресом, записанным задом наперёд. Такой формат идёт от иерархической структуры DNS. Если некоторая организация получает во владение сеть, допустим, 194.0.0.0/8
, она должна обслуживать DNS-запросы к домену 194.in-addr.arpa
. Если при этом сеть 194.87.0.0/16
передана другой организации, ей же передаётся обязанность обслуживать DNS-запросы к поддомену этого домена — 87.194.in-addr.arpa
, и так вплоть до собственно IP-адресов. Вместо host
можно использовать утилиту dig
, которая выводит больше информации о том, как проходил сам запрос.
Помимо записей типа «адрес» (A
, прямое преобразование) и «указатель ни имя» (PTR
, обратное преобразование) в системе DNS может храниться и другая информация. Таблица (зона) некоторого дамена должна содержать адреса доменных серверов всех его поддоменов (записи типа NS
). Кроме того в домене должно быть определено имя почтового пересыльщика (запись типа MX
). Если почтовый пересыльщик домена не указан, электронная почта направляется почтовому пересыльщику родительского домена.
В зоне DNS есть даже запись типа «просто текст», TXT
: при желании можно самому определить интерпретацию полей этой записи и организовать поверх DNS предназначенную для каких угодно целей базу данных по IP-адресам или доменным именам.
Так поступают, например, при создании «чёрных списков» абонентов сети, от которых почтовый сервер не принимает писем.
В отличие от dig
, которой для запроса к обратной зоне требуется ключ «-x
», утилита host
различает запросы к прямой и обратной зонам по тому, задан ли в качестве параметра адрес или доменное имя. Для указания конкретного типа искомой записи обеим утилитам требуется этот тип передать явно. Тип any
означает поиск всех записей с указанным именем:
methody@localhost:~ $ dig www.us any
; <<>> DiG 9.2.4rc5 <<>> www.us any
;; global options: printcmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 6451
;; flags: qr rd ra; QUERY: 1, ANSWER: 10, AUTHORITY: 0, ADDITIONAL: 4
;; QUESTION SECTION:
;www.us. IN ANY
;; ANSWER SECTION:
www.us. 1766 IN A 209.173.57.26
www.us. 1766 IN A 209.173.53.26
www.us. 1767 IN NS pine.neustar.com.
www.us. 1767 IN NS willow.neustar.com.
www.us. 1767 IN NS cypress.neustar.com.
www.us. 1767 IN NS oak.neustar.com.
www.us. 1771 IN MX 20 pine.neustar.com.
www.us. 1771 IN MX 5 oak.neustar.com.
www.us. 1771 IN MX 5 willow.neustar.com.
www.us. 1771 IN MX 10 cypress.neustar.com.
;; ADDITIONAL SECTION:
pine.neustar.com. 135024 IN A 209.173.57.70
willow.neustar.com. 135024 IN A 209.173.53.84
cypress.neustar.com. 135024 IN A 209.173.57.84
oak.neustar.com. 135024 IN A 209.173.53.70
;; Query time: 932 msec
;; SERVER: 192.168.102.1#53(192.168.102.1)
;; WHEN: Wed Dec 22 22:01:24 2004
;; MSG SIZE rcvd: 281
Пример 13
. Утилита dig