Журнал Системный Администратор, Февраль 2003

Журнал Системный Администратор

Февраль 2003

Цена: $4.5 US

  Подписаться

Зарегистриванные пользователи, пожалуйста следуйте этой ссылке

Версия для печати Вернуться к оглавлению

Брандмауэр Часть 2

Часть 2

Владимир Мешков

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

Задача программы инициализации и запуска процесса демона – принять исходные данные (правила фильтрации) и запустить на выполнение процесс-демон, передав ему эти правила. В нашем примере правилами фильтрации является IP-адрес хоста, чьи пакеты мы будем блокировать.

Процесс-демон после активизации передает модулю ядра правила фильтрации и в дальнейшем занимается ведением log-файла, в котором фиксируется время запуска/останова демона и попытки доступа с запрещенного адреса.

Теперь давайте детально рассмотрим каждую составляющую.

Программа инициализации и запуска процесса-демона

Нижеприведенный программный код разместим в файле sfc.c. Здесь будет находиться главная функция main().

Рассмотрение программы начнем с определения заголовочных файлов и переменных.

Нам понадобятся следующие header-файлы:

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <fcntl.h>

#include <signal.h>

#include <errno.h>

#include <sys/stat.h>

#include <sys/types.h>

#include "sfc.h"

В файле sfc.h определено имя файла, в котором хранится идентификатор процесса-демона (PID-файл). Файл имеет следующее содержание:

#define PID "daemon.pid"

Идентификатор процесса-демона определим как глобальную переменную:

static pid_t pid;

А теперь распишем главную функцию main().

int main (int argc, char *argv[])

{

    void usage():

Это прототип функции для обработки неправильного ввода параметров. Данная функция имеет следующий вид:

void usage()

{

    fprintf(stderr,"\nUsage: daemon [ start / stop ]\n\n");

    return;

}

 

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

n  start – запустить процесс-демон на выполнение;

n  stop – завершить выполнение процесса-демона.

 

Для работы нам понадобятся переменные:

n  int pid_file – дескриптор файла для хранения идентификатора демона;

n  struct stat s – структура для хранения атрибутов файла.

Проверяем правильность ввода входных параметров:

    if(argc!=2) {

           usage();

           return (-1);

    }

Если входной параметр указан, определяем, какой режим работы задан. Их, как мы уже сказали, два.

Режим запуска процесса-демона на выполнение

if(!(strcmp(argv[1],"start"))) {

Во избежание повторного запуска проверяем наличие в текущем каталоге PID-файла. Если файл присутствует, то демон уже запущен, о чем пользователь получает уведомление:

if(stat(PID,&s)==0) {

fprintf(stderr,"\nDaemon is allready running !\n\n");

           return (-1);

    }

Инициализируем демон:

init_daemon();

Функция инициализации будет определена ниже.

Демон запустим как дочерний процесс.

pid = fork();

    if (pid < 0) {

           perror("fork");

           exit(1);

    }

 

    if (pid==0) {

Отсоединяемся от терминала:

setsid();

Стартуем демон:

start_daemon();

           exit(1);

    }

Родительский процесс создает PID-файл и записывает в него идентификатор процесса-демона:

pid_file=open(PID,O_CREAT|O_TRUNC|O_RDWR,0644);

    if(pid_file < 0) {

           perror(PID);

           return (-1);

    }

if(write(pid_file,(char *)&pid,sizeof(pid_t)) < 0) {

           perror(PID);

           return (-1);

    }

    close(pid_file);

}

Режим остановки выполнения процесса-демона

if(!(strcmp(argv[1],"stop"))) {

Для остановки процесса-демона необходимо получить значение его идентификатора.

Это значение извлекаем из PID-файла:

pid_file=open(PID,O_RDONLY);

    if(pid_file<0) {

           perror(PID);

           return (-1);

    }

 

if(read(pid_file,(char *)&pid,sizeof(pid_t)) < 0) {

           perror(PID);

           return (-1);

    }

 

    close(pid_file);

PID-файл нам больше не нужен, удаляем его:

if(unlink(PID) < 0) {

           perror(PID);

           return (-1);

    }

Теперь останавливаем процесс-демон, послав ему сигнал SIGINT:

kill(pid,SIGINT);

}

На этом функция main() завершается:

    return (0);

}

Процесс-демон

Весь код, отвечающий за запуск, функционирование и остановку процесса-демона, разместим в файле sf_daemon.c. По сути, этот файл будет представлять собой набор функций.

Заголовочные файлы и переменные

Вначале, как всегда, определимся с заголовочными файлами и переменными. Нам понадобятся:

#include <stdio.h>

#include <stdlib.h>

#include <fcntl.h>

#include <time.h>

#include <signal.h>

#include <errno.h>

#include <sys/types.h>

#include <sys/socket.h>

#include <linux/in.h>

#include "sf_daemon.h"

Файл sf_daemon.h имеет следующее содержание:

#include <linux/types.h>

#include <linux/ip.h>

Имя log-файла:

#define LOG "/var/log/daemon"

 

struct data_log {

    __u32 addr;

    int action;

    int ready;

};

 

В этом файле определено имя log-файла и структура data_log, в которой хранятся данные для заполнения log-файла. Назначение полей структуры следующее:

n  __u32 – IP-адрес хоста (в сетевом формате), от которого поступил пакет;

n  int action – выполняемое действие (1 – разрешить прохождение пакета, 0 – отбросить пакет);

n  int ready – флаг готовности данных в устройстве для считывания.

Поскольку наш демон работает с двумя файлами (файл устройства /dev/firewall и log-файл), то необходимо определить две переменные для хранения дескрипторов этих файлов:

int fddev=0;     - дескриптор файла устройства;

int f;           - дескриптор log-файла.

Функции

Первая функция, которую мы рассмотрим, останавливает выполнение процесса-демона. Вот что она из себя представляет:

void stop_daemon()

{

    close(fddev);

    stop_log(f);

    exit(0);

}

Функция закрывает устройство, завершает ведение log-файла и осуществляет выход из программы.

Следующую функцию можно назвать центральной частью процесса-демона. Эта функция осуществляет непосредственный обмен данными с модулем ядра и заполняет log-файл. Главной особенностью данной функции является выполнение в бесконечном цикле, который прерывается только при поступлении сигнала SIGINT.

void packet_loop(void)

{

Структура для информационного обмена с модулем:

struct data_log data;

Размер блока данных, считанного из модуля:

int count;

Запускаем цикл:

for (;;) {

Считываем из модуля данные, в случае ошибки завершаем выполнение:

count=read(fddev,(char *)&data,sizeof(struct data_log));

           if(count<0) stop_daemon();

Если установлен флаг готовности данных для считывания и поступил пакет с запрещенного адреса, фиксируем это событие в log-файле:

if(data.ready==1) {

                 if(data.action==0) {

                        if(fill_log(f,data.action,data.addr) < 0)

                               stop_daemon();

 

                 }

           }

           }

}

Заполнением log-файла ведает функция fill_log, к ней мы еще вернемся.

Теперь подошла очередь функции инициализации. Напомню, что ее задача – передать модулю ядра правила фильтрации (т.е. IP-адрес).

void init_daemon()

{

 

    int err;

    struct iphdr ip_pack;

В структуре ip_pack, в поле saddr (адрес источника), будет находится запрещенный IP-адрес.

Обнулим эту структуру:

memset(&ip_pack,0,sizeof(struct iphdr));

и заполним поле адреса источника:

ip_pack.saddr=inet_addr("192.168.1.10");

Подготовим к работе log-файл. Если log-файл отсутствует, создаем его:

f=open(LOG,O_CREAT|O_APPEND|O_RDWR,0644);

    if(f<0) {

           perror(«open log»);

           exit(0);

    }

Теперь передадим модулю правила фильтрации. Открываем устройство в режиме чтения/записи:

fddev=open("/dev/firewall",O_RDWR);

    if(fddev<0) {

           perror("firewall");

           exit(0);

    }

Записываем в него структуру ip_pack:

err=write(fddev,&ip_pack,sizeof(struct iphdr));

    if(err<0) {

           perror("firewall");

           stop_daemon();

    }

Итак, IP-пакеты, поступившие с хоста с адресом 192.168.1.10, будут заблокированы на входе нашей системы.

Выходим из функции:

    return;

}

Если вам не понравилось, что IP-адрес введен непосредственно в исходный текст, то можете усовершенствовать код, считывая адрес из файла или из командной строки.

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

void start_daemon()

{

Демон должен реагировать только на один сигнал – SIGINT. При получении этого сигнала демон завершает выполнение. Все остальные сигналы необходимо заблокировать.

Определим переменные:

sigset_t mask;

static struct sigaction act;

Создадим полный набор сигналов, исключив из него SIGINT:

sigfillset(&mask);

sigdelset(&mask,SIGINT);

Блокируем все сигналы:

sigprocmask(SIG_SETMASK,&mask,NULL);

Определяем новый обработчик для SIGINT:

act.sa_handler=stop_daemon;

    sigaction(SIGINT,&act,NULL);

А теперь стартуем:

    start_log(f);

    packet_loop();

    exit(1);

}

LOG-файл

Нам осталось рассмотреть функции для ведения log-файла. Их три:

n  start_log – запись о начале выполнения процесса-демона;

n  fill_log – запись информации о блокировании IP-пакета;

n  stop_log – запись об остановке выполнения процесса-демона.

Каждая из этих функций фиксирует текущее время возникновения того или иного события.

Все три функции разместим в файле sf_log.c.

#include <stdio.h>

#include <sys/types.h>

#include <unistd.h>

#include <fcntl.h>

#include <time.h>

#include <sys/socket.h>

#include <linux/in.h>

#define BSIZE 80

Все функции принимают в качестве аргумента дескриптор log-файла, в который будет осуществляться запись информации. В случае удачного завершения операции все функции возвращают 0, в случае ошибки -1.

Функция start_log

int start_log(int f)

{

    char buf[BSIZE];

    time_t start_t;

Обнулим буфер и получим текущее время:

bzero(buf,BSIZE);

    time(&start_t);

Формируем буфер и записываем его в log-файл:

sprintf(buf,"Daemon started at %s", ctime(&start_t));

    if(write(f,buf,strlen(buf)) < 0) return (-1);

    return (0);

}

Функция stop_log

Функции start_log и stop_log практически не отличаются друг от друга, кроме имен переменных, поэтому привожу код без комментариев:

int stop_log(int f)

{

    char buf[BSIZE];

    time_t stop_t;

 

    bzero(buf,BSIZE);

    time(&stop_t);

    sprintf(buf,»Daemon stoped at %s», ctime(&stop_t));

    if(write(f,buf,strlen(buf)) < 0) return (-1);

    close(f);

    return (0);

}

Функция fill_log

Эта функция, кроме дескриптора log-файла, принимает IP-адрес пакета, который был заблокирован (u_long addr), и идентификатор выполненного действия (int action). Функция очень простая, и необходимости в комментариях я не вижу.

int fill_log(int f, int action, u_long addr)

{

    char buf[BSIZE];

    time_t fill_t;

 

    bzero(buf,BSIZE);

    time(&fill_t);

 

    if(action==0) {

           sprintf(buf,"Packet from %s was rejected at %s",inet_ntoa(addr), ctime(&fill_t));

           if (write(f,buf,strlen(buf)) < 0)

                 return (-1);

           return (0);

    }

}

Makefile

Для сборки исполняемого модуля создадим Makefile следующего содержания:

CC = gcc

name = daemon

DAEMON = sfc.o sf_daemon.o sf_log.o

 

$(name): $(DAEMON)

    $(CC) -g -o $(name) $(DAEMON)

sfc.o: sfc.c

    $(CC) -c sfc.c

sf_daemon.o: sf_daemon.c

    $(CC) -c sf_daemon.c

sf_log.o: sf_log.c

    $(CC) -c sf_log.c

 

clean:

    rm -f *.o

Здесь все должно быть вам знакомо. Ключ «-g» при успешной сборке можно будет заменить на «-s».

Запуск и остановка выполнения процесса-демона

После сборки в текущем каталоге появится исполняемый файл daemon. Для его запуска наберите команду:

./daemon start

Перед запуском процесса-демона необходимо загрузить модуль ядра.

После запуска демона в текущем каталоге появится файл daemon.pid. Не удаляйте этот файл! В нем хранится идентификатор процесса-демона для возможности его корректной остановки. Для остановки выполнения процесса-демона введите команду:

./daemon stop

Файл daemon.pid автоматически удаляется.

Информация о времени запуска и останова процесса-демона, а также о заблокированных пакетах будет зафиксирована в файле /var/log/daemon.

При подготовке статьи были использованы исходные тексты и документация брандмауэра SINUS (http://www.ifi.unizh.ch).




Все права зарезервированы. Этот материал принадлежит или лицензирован компании PLARANA INC. Только для частного использования. Любое распространение запрещено без письменного разрешения PLARANA INC
Версия для печати Вернуться к оглавлению