Документация по ОС FreeBSD Воскресенье, 28.04.2024, 16:59
Приветствую Вас Гость | RSS
Меню сайта

Категории каталога
Apache [58]
DNS [25]
FTP [27]
Mail [74]
Samba [24]
Squid [46]
SSH [23]
VPN [35]
РРР [20]
Net [173]

Главная » Статьи » Сеть » Mail

Postfix изнутри
Лично я практически никогда и нигде не видел эталонных программ для учащихся и это очень странно. Казалось бы, для того чтобы научить программированию, надо хотя бы один раз показать что это такое и как это делать хорошо. Является ли алгоритм сортировки пузырьком показателем "качественного" программирования? Нет, не является, потому что это частное решение некоторой задачи, практически никогда не возникающей перед человеком обособленно. Нет, этот алгоритм используется, конечно, и в его реализациях иногда делают ошибки, но он никогда не будет самоцелью для программиста.

Выполняемые же задания бывают иногда хороши, но только как упражнения для фиксирования некоторого материала: те же сортировки, поиск, нахождение оптимального решения... любой из этих алгоритмов всегда будет средством достижения результата, а не конечным результатом. Даже курсовые работы и те отличаются малым объемом за очень редким исключением. Что остается? Диплом? Слишком поздно для того, чтобы на его основе можно было бы чего-нибудь изучить.

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

Так вот, этих эталонных программ не существует. Максимум на что может надеяться студент, так это на написанные мелом на доске, чрезвычайно корявым почерком, надписи, изображающие некоторую программу. Обычно она не работает, а половина студентов не смогла правильно записать весь поток сознания от преподавателя и поэтому ничего не сможет разобрать уже в своих собственных каракулях.

Требования к эталонным программам
Программа, претендующая на звание "эталонной", должна выполнять следующие условия:

ее исходный текст должен быть свободно доступен;
это должна быть настоящая программа, решающая какую-то реальную задачу;
задача не должна требовать много специальных знаний, например, сложного протокола передачи данных или специфических машинных кодов некоторого процессора

она должна быть не очень большой, но и не маленькой, примерно 100 тысяч строк;
она должна быть популярной и массово используемой;
она должна быть поддерживаемой, то есть должен существовать программист или группа программистов, продолжающий работать над ее улучшением;
она должна быть хорошо оформлена, хорошо написана, производительна, переносима и т.д. --- в общем, все то, что преподаватель считает "хорошей" программой
в ней должны быть недостатки; впрочем, в любой программе есть недостатки.
Кстати сказать, пример подобной "эталонной" программы можно найти в книге Э. Таненбаума "Современные операционные системы", где он предлагал в качестве примера свою ОС под названием Minix. Но она была вытеснена Linux и перестала использоваться, соответсвенно сейчас она выглядит достаточно загадочно. А когда книга только появлялась, Minix, пожалуй, была очень хорошим примером "эталонной" программы, которую можно изучать или критиковать. Опять же, Linux возник именно потому, что Торвальдс изучал книгу Таненбаума, но ему не хватало Minix для своих нужд.

Мне хочется взять на себя смелость и предложить подобную эталонную программу для изучения ее внутренностей. Выбрать исходники FreeBSD? Нет, они слишком большие и, зачастую, специфические. Можно было бы взять Apache, но тут уже я недостаточно хорошо знаком с его исходными текстами.

Осмелюсь предложить postfix --- это популярный MTA (Mail Transfer Agent), позволяющий эффективно передавать письма адресатам. Азы протокола SMTP, которые надо знать, не особенно сложны для изучения и их можно освоить за время изучения самого postfix. Никто не против?

Mail Transfer Agent
MTA это программа, которая лежит в основе передачи электронной почты. Когда вы посылаете письмо своему знакомому с адресом vasya@pupkin.ru, то вы делаете это посредством своего SMTP-клиента (например, Outlook, The Bat!, mutt), который передает письмо MTA. Он может делать это по протоколу SMTP или еще каким-нибудь способом, в любом случае он не выполняет доставку письма самостоятельно. MTA получает письмо и проверяет по своей конфигурации, что он должен с ним сделать: послать адресату напрямую (то есть, послать письмо MTA, который установлен на хосте, куда указывает MX-запись соответствующего домена).

MTA должен сделать примерно следующее:

Обрабатывать входные соединения для приема почты по протоколу SMTP.
Сохранять почту на диске в очереди.
Отправлять почту адресату из очереди (это может быть локальный почтовый ящик, другой MTA или еще какой-то иной способ доставки почты).
Гарантировать доставку почты получателю или нотификацию отправителю о невозможности доставки.
Этот список --- только самое необходимое. Хороший MTA должен, кроме того, быть надежным, высокопроизводительным, гибко настраиваемым, поддерживать встройку фильтров (например, для борьбы со спамом) и т.д.

Долгое время фактически единственным MTA для Unix'ов был sendmail, отличающийся диким форматом конфигурации и, вообще, неудобством использования. Сейчас ситуация иная --- есть еще несколько свободных почтовых систем, таких как QMail и postfix, которые обладают целым рядом преимуществ по сравнению с sendmail. Впрочем, sendmail все равно есть и, наверное, останется самым популярным MTA, так как традиционно входит в состав огромного количества дистрибутивов Unix. Кроме того, опять же, интерфейс с установленным MTA все равно закрепился как вызов программ из установки sendmail и все остальные MTA поддерживают его.

Общая организация postfix: модульность, управляющий процесс master
Первое, на что стоит я хотел бы обратить внимание: postfix не является монолитной программой. Он весь состоит из модулей, которые передают почтовые сообщения или иную информацию между собой.

Интересно, что модули постфикса, или сервисы, устроены в виде отдельных программ, каждая из которых запускается из управляющего сервиса master (порождается от master). Почему выбрана такая модель (то есть, множество однопоточных процессов) а не, к примеру, популярные потоки?

Давайте я сразу оговорюсь --- многопоточные сервисы вполне могут существовать и создаваться, это мы все рассмотрим чуть позднее. Но все сервисы постфикса основаны либо на select, либо на pre-fork моделях. Связано это, как я думаю, с отлаженностью этих механизмов на различных операционных системах. Постфикс компилируется и успешно работает практически на всем зоопарке более-менее популярных Unix'ов, это было бы попросту невозможно в том случае, если бы использовались потоки, которые везде разные. А pre-fork модель или select вполне отлажена в течение десятилетий использования.

Я отмечу сразу же --- то что я даю свои ответы на поставленные мною же вопросы, еще не значит что эти ответы верны. Я не автор постфикса и его мотивация могла быть совершенно иной, я же лишь предполагаю или, точнее, меряю на себя возникающие вопросы. Поэтому все причины, которые я пытаюсь разъяснить, следует понимать следующим образом: "Я бы тоже сделал бы так если бы стоял перед этой проблемой по следующей причине". Сделав эту ремарку, я надеюсь что читатель ее запомнит и не будет так уж безоговорочно верить в мои ответы --- может быть я и не прав. Если у читателя появились свои ответы (а в качестве postfix'а мы, напомню, не сомневаемся, эта программа является эталоном), то это значит что своей цели я добился.

Для функционирования всей системы в целом необходимо запустить только процесс master. Он читает конфигурационный файл следующего вида:

# ==========================================================================
# service type private unpriv chroot wakeup maxproc command + args
# (yes) (yes) (yes) (never) (50)
# ==========================================================================
smtp inet n - n - - smtpd
#628 inet n - n - - qmqpd
pickup fifo n - n 60 1 pickup
cleanup unix n - n - 0 cleanup
qmgr fifo n - n 300 1 qmgr
#qmgr fifo n - n 300 1 nqmgr
rewrite unix - - n - - trivial-rewrite
bounce unix - - n - 0 bounce
defer unix - - n - 0 bounce
flush unix n - n 1000? 0 flush
smtp unix - - n - - smtp
showq unix n - n - - showq
error unix - - n - - error
local unix - n n - - local
virtual unix - n n - - virtual
lmtp unix - - n - - lmtp

Это список всех доступных сервисов. Вкратце, о том что значат все эти надписи, по столбцам:

service --- название сервиса в случае использования сокетов unix-domain и прочих средств IPC (interprocess communication), порт и интерфейс в случае использования интернет-сокетов (smtp, понятно, аналог *:25)
type --- тип сокета, который слушается сервисом. Возможные значения, по-моему, ясны.
private --- признак того, что сервис может доступен снаружи MTA. Все сервисы с типом inet не могут быть private по понятным причинам --- никак нельзя ограничить доступ процессов к интернет-сокету. В зависимости от этого флага отличается каталог и права доступа на файл, который связан с создаваемым сокетом. Доступные снаружи сервисы нужны по разным причинам, например сервис showq нужен для получения содержимого очереди сообщений для команды mailq.
unpriv --- признак того, что сервис запускается от пользователя postfix, а не root. Надо сказать, что смена пользователя целиком и полностью на совести сервиса, а не master'а (единственное, чем отличается запуск непривилегированного процесса от запуска привелигированного --- так это наличием ключа -u). Это можно объяснить тем, что сервисы (а это программы в каталоге /usr/libexec/postfix) просто так не появляются и они должны поддерживать такой интерфейс. Этот флаг нужен для того, чтобы обезопасить сервер от сбоев в работе постфикса: допустим, в постфиксе найдена жуткая ошибка в каком-либо из сервисов, позволяющая злоумышленнику выполнить любой код на вашем сервере; если бы сервисы работали от root'а то этот человек получил бы полный доступ к компьютеру, а если они работают от пользователя postfix, который даже не имеет возможности логина, то под ударом только ваша почта. Естественно, что основной процесс, master, работает от root'а, это значит что master должен быть максимально простым для того, чтобы гарантировать отсутствие в нем грубых ошибок.
chroot --- аналогично unpriv, флаг заставляет сервисы выполнить вызов chroot на /var/spool/postfix. Тем самым для сервисов меняется положение каталога '/' и они не могут получить доступ к другим файлам, кроме очереди сообщений. Необходимость этого флага обусловлена теми же причинами, что и для unpriv.
wakeup --- нотификация сервиса каждые n секунд. Это позволяет заставить сервисы, допустим, перечитать очередь. На самом деле, нотификация об изменениях в очереди может быть доставлена от других сервисов, но это все равно полезно: вдруг где-то что-то сломалось, или постфикс перезапустился, все равно очередь должна быть проверена.
maxproc --- максимальное количество процессов сервиса, которые могут быть запущены. Это число очень полезно для настройки особенно тяжеловесных сервисов, запуск которых может привести к большой загрузке сервера.
command --- это просто название программы и аргументы, которые должны быть ей переданы. Здесь никаких тонкостей нет.
Значения по-умолчанию (например, maxproc) настраиваются через другой конфигурационный файл --- main.cf.

master: запуск сервисов, интерфейс с уже запущенными сервисами
Теперь рассмотрим, как появляются новые процессы, выполняющие работу сервисов.

При запуске master считывает master.cf, по которому он определяет какие сервисы понадобятся. Он создает все указанные сокеты и готовиться их "слушать", инициализирует внутри себя структуры, описывающие состояние каждого из сервисов (количество запущенных процессов и т.п.), после чего master загоняет все дескрипторы сокетов в select и ждет какого-нибудь из событий на каждый из них.

Теперь, допустим, на наш сервер должна прийти почта. Сейчас интересно не то, как будет обрабатываться почта, а как будут запускаться сервисы.

Что значит --- пришла почта? Это значит, что некий почтовый релей установил соединение на 25-й порт нашего сервера. В этом случае, дескриптор в master'е, который связан с этим портом, будет "готов к чтению" и, тем самым, select завершится.

Сам по себе master не умеет обрабатывать smtp-сессию, зато это умеет делать сервис smtpd, который и будет запущен при помощи fork. Естественно, что перед запуском производится некоторое количество действий, которые пока что не особенно интересны, важно то, что сразу же после запуска smtpd выполняет accept на этом дескрипторе и приступает к обработке smtp-сессии и пересылке письма дальше по сервисам до менеджера очереди.

Перед запуском master увеличивает счетчик процессов сервиса smtpd и запоминает его pid. Этот счетчик будет уменьшен тогда, когда master получит сигнал SIGCHLD, то есть smtpd завершится. Тем самым, master может контролировать количество запущенных процессов.

Теперь самый интересный вопрос --- пока smtp-сессия обрабатывается, master может опять реагировать на изменения состояния дескриптора, связанного с 25-м портом, а что делать когда эта сессия закончится? Глупо завершать smtpd сразу же после обработки одного письма если через секунду, возможно, придет еще одно письмо: тогда будет слишком много затрат связанных с fork. Тем самым, сервисы должны уметь обрабатывать новые соединения и при этом им не должен мешаться master. Кроме того, master все равно должен следить за сервисами и если кто-то из них захотел "умереть", то это не должно сказаться на работоспобности MTA в целом.

Опять же, технология в этом случае совершенно простая, грубо говоря это и есть pre-fork модель построения сетевых серверов, единственное отличие от, скажем так, классической реализации заключается в том, что обычно сервер выполняет только одну операцию и, тем самым, можно сделать так что "главный" сервер (который и выполняет fork) сам по себе тоже способен обрабатывать соединения. В случае с master, который запускает любые сервисы, реализовать такой подход попросту нереально.

Обычно, каждый свободный экземпляр сервера (то есть, отдельный процесс) выполняет accept (или сначала select, а потом accept) на нужный дескриптор. При этом реально accept будет выполнен только у одного экземпляра сервера (заранее неизвестно какого именно), остальные получат ошибку и могут опять запустить accept или select. Сейчас же, master должен уметь отличать ситуации когда свободных экземпляров сервиса нет (тогда он должен слушать сокет сам и выполнить fork когда придет новое соединение) и когда кто-то свободен (и тогда ему не надо запускать select на этот сокет, поскольку этот "кто-то" сам следит за своим сокетом).

Естественно, что это делается опять же через IPC: между потомком (то есть, экземпляром сервиса) и master'ом есть канал, через который потомок уведомляет master о своей занятости или свободности. Когда потомок изменяет свое состояние, он передает master'у два значения: свой pid и флаг "занят" или "свободен".

Реализация многопроцессного однопотокового сервиса в этом случае простая: сервис все время делает accept, по успеху последнего он оповещает master о занятости, затем начинает обрабатывать соединение, после чего сообщает master'у о своей свободности и опять делает accept. Если в какой-то момент он решит закончить свое выполнение, он может это сделать без оповещений --- master получит сигнал от операционной системы и выполнит все необходимые действия.

Реализация многопотокого сервиса еще проще --- сервис никогда не оповещает master о своей занятости, а обрабатывает соединения во внутреннем select или еще каким-нибудь образом.

Теперь небольшая ремарка о необходимости завершения работы процесса. Это общепринятая практика, когда сервер после выполнения определенного числа операций или простоя, прекращает свое выполнение. Такая логика очень полезно, потому что уменьшает зависимость от ошибок с неудалением выделенной ранее памяти (попросту, "разбухания" процесса), поэтому обычно любые сервера имеют какое-то ограничение на количество обработанных запросов и время простоя. В postfix каждый сервис имеет подобные ограничения, кроме, разумеется, master, так как последний некому перезапустить.

Когда некий сервис требует для своей работы другой сервис (например, для того чтобы передать почтовое сообщение от smtpd в cleanup), он всего-навсего обращается по нужному сетевому адресу (в сети интернет или файловой системе), все остальное за него будет сделано master'ом или работающим целевым сервисом.

Автор: Андрей Калинин
Категория: Mail | Добавил: oleg (23.11.2007)
Просмотров: 1280 | Рейтинг: 5.0/1 |
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]
Форма входа

Beastie

Друзья сайта

Статистика

Онлайн всего: 1
Гостей: 1
Пользователей: 0
links

Copyright MyCorp © 2024