Централизованная схема управления сетью с использованием OpenLDAP

Автор: (С) Прокопьев Евгений - 10.07.2004

Оригинал: ftp.atmsk.ru и linux-os.ru

Документ распространяется в соответствии с условиями лицензии GNU Free Documentation License. Ни автор, ни распространители, ни любой другой контрибьютор данного документа, не несет никакой ответственности за физический, финансовый, моральный или любой другой ущерб, понесенный при выполнении рекомендаций данного документа.


Содержание:

1. Постановка задачи
1.1. Список задач
1.2. Способы решения задач
2. Реализация
2.1. Подсистема управления настройками
2.1.1. Первоначальная настройка OpenLDAP
2.1.2. Настройка связки DHCP+LDAP
2.1.3. Настройка DDNS
2.1.4. Настройка Squid и IPTables
2.1.5. Настройка авторизации в OpenLDAP
2.1.6. Хранение в OpenLDAP произвольной справочной информации
2.1.7. Результат сведения настроек в единое хранилище
2.2. Подсистема учета трафика
2.2.1. Создание базы данных
2.2.2. Учет прокси-трафика
2.2.3. Учет NAT-трафика
2.3. Графический интерфейс системы администрирования
2.3.1. Описание графического интерфейса с точки зрения пользователя
2.3.2. Реализация графического интерфейса
3. Итоги
Приложение А. Подключение репозитория nm для пользователей ALT Linux
Приложение Б. Ссылки


1. Постановка задачи

1.1. Список задач

Для небольшой локальной сети необходимо обеспечить:
1.2. Способы решения задач

Традиционно такого рода задачи решаются стандартными средствами открытых UNIX-систем. Для решения каждой подзадачи конфигурируется один из стандарных сервисов:
Основной недостаток такого решения - сложность администрирования. Например, чтобы включить в сеть еще один компьютер, необходимо проделать следующее:
Т.е. человеку, обслуживающего такую систему, приходится выполнять слишком много рутинной работы. Гораздо правильнее будет переложить большую часть работы на плечи компьютера.

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

Написание скриптов для генерации конфигов может быть довольно трудоемкой задачей. Ситуацию упрощает тот факт, что многие сервисы имеют возможность хранить свои конфигурационные данные не в текстовых файлах, а в некотором внешнем хранилище, в качестве которого чаще всего выступает LDAP. Некоторые особо продвинутые сервисы даже имеют развитые средства для написания собственных backend-ов (примеры - bind и courier-imap), однако LDAP все равно остается самым распространенным внешним хранилищем конфигурационных даных.

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

2. Реализация

Для построения сервера был использован Linux, а точнее ALT Linux Master 2.2. В целом система выглядит так:

Схема системы

Реализация каждого компонента системы далее рассмотрена подробно.

2.1. Подсистема управления настройками

2.1.1. Первоначальная настройка OpenLDAP


После установки OpenLDAP (в ALT Linux это можно сделать выполнив команду apt-get install openldap openldap-servers openldap-clients openldap-guide) необходимо отредактировать его главный конфигурационный файл /etc/openldap/slapd.conf следующим образом:

# Включение необходимых схем. В каждой схеме описан набор классов и их атрибутов.
# В дереве LDAP могут присутствовать только такие записи, которые являются экземлярами
# классов, описанных в схемах, и удовлетворяют ограничениям этих классов
include /etc/openldap/schema/core.schema
include /etc/openldap/schema/cosine.schema
include /etc/openldap/schema/inetorgperson.schema
include /etc/openldap/schema/misc.schema
include /etc/openldap/schema/nis.schema
include /etc/openldap/schema/openldap.schema

# pid-файл и файл с аргументами запуска
pidfile         /var/run/slapd.pid
argsfile        /var/run/slapd.args

# Права доступа к дереву LDAP
access to attr=userPassword
  by users write
  by anonymous auth
  by * none
access to *
  by users write
  by anonymous read
  by * none

# Используемый backend для хранения дерева
database        ldbm

# Суффикс дерева
suffix          "dc=myserver,dc=myprovider,dc=ru"

# Администратор дерева
rootdn          "cn=manager,dc=myserver,dc=myprovider,dc=ru"

# Пароль администратора. Желательно хранить его не в открытом виде,
# а генерировать с помощью slappasswd
rootpw  secret

# Каталог для хранения дерева
directory       /var/lib/ldap/bases

# Уровень отладки
loglevel 0


# Индексы для ускорения поиска в дереве
index  objectClass         eq


В конфигурационном файле определены только самые необходимые настройки. Об остальных можно прочесть в комментариях к оригинальному конфигурационному файлу и в документации к OpenLDAP.

Теперь можно запустить OpenLDAP (в ALT Linux это можно сделать выполнив команду service ldap start).

2.1.2. Настройка связки DHCP+LDAP

Для хранения конфигурации ISC DHCP-сервера в LDAP его необходимо пересобрать с патчем dhcp-3.0.1rc13-ldap-patch (его последнюю версию можно найти на http://home.ntelos.net/~masneyb/). Пользователи ALT Linux вместо ручной пересборки могут подключить репозиторий nm и выполнить команду apt-get install dhcp. После установки DHCP-сервера нужно скопировать файл dhcp.schema (который также можно найти в составе пакета dhcp) в каталог /etc/openldap/schema и модифицировать файл /etc/openldap/slapd.conf (добавленные строки выделены):

# Включение необходимых схем. В каждой схеме описан набор классов и их атрибутов.
# В дереве LDAP могут присутствовать только такие записи, которые являются экземлярами
# классов, описанных в схемах, и удовлетворяют ограничениям этих классов
include /etc/openldap/schema/core.schema
include /etc/openldap/schema/cosine.schema
include /etc/openldap/schema/inetorgperson.schema
include /etc/openldap/schema/misc.schema
include /etc/openldap/schema/nis.schema
include /etc/openldap/schema/openldap.schema

# Включение схемы для хранения настроек DHCP в LDAP
include /etc/openldap/schema/dhcp.schema

# pid-файл и файл с аргументами запуска
pidfile         /var/run/slapd.pid
argsfile        /var/run/slapd.args

...

# Индексы для ускорения поиска в дереве
index  objectClass         eq

#Индексы для DHCP
index dhcpHWAddress        eq

index dhcpClassData        eq

После перезапуска OpenLDAP (service ldap restart в ALT Linux) необходимо создать файл dhcpd.ldif со следующим содержимым (или взять его из пакета dhcp):

dn: dc=myserver, dc=myprovider, dc=ru
objectClass: top
objectClass: dhcpServer
cn: myserver.myprovider.ru
dhcpServiceDN: cn=Network, dc=myserver, dc=myprovider, dc=ru

dn: cn=Network, dc=myserver, dc=myprovider, dc=ru
cn: Network
objectClass: top
objectClass: dhcpService
objectClass: dhcpOptions
dhcpPrimaryDN: cn=myserver.myprovider.ru, dc=myserver, dc=myprovider, dc=ru
dhcpOption: domain-name "myserver.myprovider.ru"
dhcpOption: domain-name-servers 192.168.1.1
dhcpOption: time-offset -5
dhcpOption: ntp-servers 192.168.1.1
dhcpStatements: default-lease-time 21600
dhcpStatements: max-lease-time 43200
dhcpStatements: ddns-update-style ad-hoc

dn: cn=192.168.1.0, cn=Network, dc=myserver, dc=myprovider, dc=ru
cn: 192.168.1.0
objectClass: top
objectClass: dhcpSubnet
objectClass: dhcpOptions
dhcpNetMask: 24
dhcpOption: routers 192.168.1.1
dhcpOption: subnet-mask 255.255.255.0

dn: cn=host1, cn=192.168.1.0, cn=Network, dc=myserver, dc=myprovider, dc=ru
cn: host1
objectClass: top
objectClass: dhcpHost
dhcpHWAddress: ethernet 00:c0:26:31:6a:13
dhcpStatements: fixed-address 192.168.1.11

dn: cn=host2, cn=192.168.1.0, cn=Network, dc=myserver, dc=myprovider, dc=ru
cn: host2
objectClass: top
objectClass: dhcpHost
dhcpHWAddress: ethernet 00:c0:26:31:6a:14
dhcpStatements: fixed-address 192.168.1.12


Содержимое файла необходимо добавить в дерево LDAP  командой:

ldapadd -h 127.0.0.1 -x -D "cn=manager,dc=myserver,dc=myprovider,dc=ru" -w "secret" -f dhcpd.ldif

и проверить, добавлены  ли записи, командой:

ldapsearch -h 127.0.0.1 -LLL -b "dc=myserver,dc=myprovider,dc=ru" -D "cn=manager,dc=myserver,dc=myprovider,dc=ru" -w "secret"

После этого нужно создать конфигурационный файл DHCP-сервера /etc/dhcpd.conf (или взять его из пакета dhcp):

ldap-server "localhost";
ldap-port 389;
ldap-username "cn=manager,dc=myserver,dc=myprovider,dc=ru";
ldap-password "secret";
ldap-base-dn "dc=myserver,dc=myprovider,dc=ru";
ldap-method static;


После запуска DHCP-сервера (service dhcpd start в ALT Linux) в качестве источника настроек он будет использовать дерево LDAP.

2.1.3. Настройка DDNS

Первоначально для хранения конфигурации DNS в LDAP планировалось использовать соответствующий backend bind. Но затем нашлось более простое решение: DDNS - динамический DNS, автоматически редактирующий записи о хостах в файлах прямой и обратной зон по запросу DHCP-сервера. Для его настройки необходимо отредактировать настройки DHCP-сервера непосредственно в дереве LDAP, используя файл dhcp-ddns-add.ldif:

dn: cn=Network, dc=myserver, dc=myprovider, dc=ru
changetype: modify
replace: dhcpStatements
dhcpStatements: default-lease-time 21600
dhcpStatements: max-lease-time 43200
dhcpStatements: ddns-update-style interim
dhcpStatements: ddns-updates on
dhcpStatements: ddns-domainname "myserver.myprovider.ru"
dhcpStatements: ddns-rev-domainname "in-addr.arpa"
dhcpStatements: update-static-leases on
dhcpStatements: key DHCP_UPDATE { algorithm HMAC-MD5.SIG-ALG.REG.INT; secret "WOfB3kj8IhJK4OZ5s3zHeQ=="
dhcpStatements: } zone myserver.myprovider.ru. { primary 192.168.1.1; key DHCP_UPDATE
dhcpStatements: } zone 1.168.192.in-addr.arpa. { primary 192.168.1.1; key DHCP_UPDATE
dhcpStatements: } #


Содержимое файла необходимо внести в дерево LDAP следующим образом:

ldapmodify -h 127.0.0.1 -x -D "cn=manager,dc=myserver,dc=myprovider,dc=ru" -w "secret" -f dhcpd-ddns-add.ldif

После этого желательно проверить, правильно ли отредактирована главная запись DHCP-сервера:

$ ldapsearch -h 127.0.0.1 -LLL -b "dc=myserver,dc=myprovider,dc=ru" -D "cn=manager,dc=myserver,dc=myprovider,dc=ru" -w "secret" "objectClass=dhcpService"

dn: cn=Network, dc=myserver, dc=myprovider, dc=ru
cn: Network
objectClass: top
objectClass: dhcpService
objectClass: dhcpOptions
dhcpPrimaryDN: cn=myserver.myprovider.ru, dc=myserver, dc=myprovider, dc=ru
dhcpOption: domain-name "myserver.myprovider.ru"
dhcpOption: domain-name-servers 192.168.1.1
dhcpOption: time-offset -5
dhcpOption: ntp-servers 192.168.1.1
dhcpStatements: default-lease-time 21600
dhcpStatements: max-lease-time 43200
dhcpStatements: ddns-update-style interim
dhcpStatements: ddns-updates on
dhcpStatements: ddns-domainname "myserver.myprovider.ru"
dhcpStatements: ddns-rev-domainname "in-addr.arpa"
dhcpStatements: update-static-leases on
dhcpStatements: key DHCP_UPDATE { algorithm HMAC-MD5.SIG-ALG.REG.INT; secret "
 WOfB3kj8IhJK4OZ5s3zHeQ=="
dhcpStatements: } zone myserver.myprovider.ru. { primary 192.168.1.1; key DHCP
 _UPDATE
dhcpStatements: } zone 1.168.192.in-addr.arpa. { primary 192.168.1.1; key DHCP
 _UPDATE
dhcpStatements: } #

Затем необходимо установить bind (apt-get install bind в ALT Linux) и включить в нем поддержку DDNS. Содержимое конфигурационных файлов и права доступа к ним приведены ниже (предполагаем, что bind выполняется в chroot - в ALT Linux так оно и есть):

# ls -l /var/lib/bind
total 2
drwx--x---    2 root     named          48 Feb 12  2003 dev
drwx--x---    2 root     named         328 Jul 11 14:11 etc
drwx--x---    2 root     named          48 Feb 12  2003 var
drwx--x---    2 root     named         344 Jul 11 14:23 zone
# ls -l /var/lib/bind/etc/
total 36
-rw-r-----    1 root     named         100 Apr 29 14:31 dhcp.key
-rw-r-----    1 root     named         182 Jul 11 14:00 local.conf
-rw-r-----    1 root     named         298 Jul 11 14:07 myserver.myprovider.conf
-rw-r-----    1 root     named         293 Apr 29 14:33 named.conf
-rw-r-----    1 root     named         447 Feb 10  2003 options.conf
-rw-r-----    1 root     named         564 Feb  9  2003 rfc1912.conf
-rw-r-----    1 root     named        1515 Feb  9  2003 rfc1918.conf
-rw-r-----    1 root     named         108 Feb  9  2003 rndc.conf
-rw-r-----    1 root     named          97 Jul 11 14:04 rndc.key

# cat /var/lib/bind/etc/named.conf
// This is the primary configuration file for the BIND DNS server named.
//
// If you are just adding zones, please do that in /var/lib/bind/etc/local.conf

include "/etc/options.conf";
include "/etc/rndc.conf";
include "/etc/dhcp.key";
include "/etc/rfc1912.conf";
include "/etc/local.conf";

# cat /var/lib/bind/etc/options.conf

options {
        version "unknown";
        directory "/zone";
        pid-file "";
        dump-file "/var/run/named_dump.db";
        statistics-file "/var/run/named.stats";

        /*
         * Oftenly used directives are listed below.
        */

        /*
         * For localhost configuration, uncomment the listen-on directive below.
        */
        //      listen-on { 127.0.0.1; };

        /*
         * If forward directive set to "only", the server will only query
         * the forwarders.
        */
        //      forward only;
        //      forwarders { };
};

# cat /var/lib/bind/etc/dhcp.key

key DHCP_UPDATE {
    algorithm HMAC-MD5.SIG-ALG.REG.INT;
    secret "WOfB3kj8IhJK4OZ5s3zHeQ==";
};

# cat /var/lib/bind/etc/local.conf

// Consider adding the 1918 zones here, if they are not used in your organization.
//      include "/etc/rfc1918.conf";

// Add other zones here

include "/etc/myserver.myprovider.conf";

# cat /var/lib/bind/etc/myserver.myprovider.conf
zone "myserver.myprovider.ru" {
    type master;
    file "myserver.myprovider.ru";
    forwarders {};
    allow-update { key DHCP_UPDATE; };
};

zone "1.168.192.in-addr.arpa" {
    type master;
    file "1.168.192.in-addr.arpa";
    forwarders {};
    allow-update { key DHCP_UPDATE; };
};

# ls -l /var/lib/bind/zone/
total 32
-rw-r-----    1 root    named         433 Jul 11 14:49 1.168.192.in-addr.arpa
-rw-r-----    1 root    named         212 Feb  9  2003 127.in-addr.arpa
-rw-r-----    1 root    named         309 Feb  9  2003 empty
-rw-r-----    1 root    named         208 Feb 12  2003 localdomain
-rw-r-----    1 root    named         178 Jan 13  2003 localhost
-rw-r-----    1 root    named         561 Jul 11 14:49 myserver.myprovider.ru

# cat /var/lib/bind/zone/1.168.192.in-addr.arpa

$TTL            1D
@               IN      SOA     myserver.myprovider.ru. root.myserver.myprovider.ru. (
                                 2003050603      ; serial
                                 12H             ; refresh
                                 1H              ; retry
                                 1W              ; expire
                                 1H              ; ncache
                         )
                IN      NS      myserver.myprovider.ru.
1               IN      PTR     myserver.myprovider.ru.

# cat /var/lib/bind/zone/myserver.myprovider.ru

$TTL            1D
@               IN      SOA     ns.myserver.myprovider.ru. root.myserver.myprovider.ru. (
                                 2003102202      ; serial
                                 12H             ; refresh
                                 1H              ; retry
                                 1W              ; expire
                                 1H              ; ncache
                         )
                IN              NS      ns.myserver.myprovider.ru.
                IN              MX      10 mail.myserver.myprovider.ru.
@               IN              A       192.168.1.1
ns              IN              A       192.168.1.1
mail            IN              A       192.168.1.1


WOfB3kj8IhJK4OZ5s3zHeQ== - это ключ, сгенерированный следующим образом:

dnssec-keygen -a HMAC-MD5 -b 128 -n USER DHCP_UPDATE

После выполнения этой команды создаются файлы Kdhcp_update.+157+56116.key и Kdhcp_update.+157+56116.private, из любого можно взять строку ключа.

Перед первым запуском bind необходимо выставить на каталог /var/lib/bind/zone права rwxrwx---, чтобы bind мог стать владельцем файлов зон и создать файлы журналов. После того, как первый хост будет зарегистирирован в DNS, права можно вернуть обратно.

Если все сконфигурировано верно, при регистрации первого хоста в логах можно увидеть следующее:

Jul 11 14:23:53 myserver named[3515]: client 192.168.1.1#32770: updating zone 'myserver.myprovider.ru/IN': adding an RR
Jul 11 14:23:53 myserver named[3515]: client 192.168.1.1#32770: updating zone 'myserver.myprovider.ru/IN': adding an RR
Jul 11 14:23:53 myserver named[3515]: journal file myserver.myprovider.ru.jnl does not exist, creating it
Jul 11 14:23:53 myserver dhcpd: Added new forward map from host1.myserver.myprovider.ru to 192.168.1.11
Jul 11 14:23:53 myserver named[3515]: client 192.168.1.1#32770: updating zone '1.168.192.in-addr.arpa/IN': deleting an rrset
Jul 11 14:23:53 myserver named[3515]: client 192.168.1.1#32770: updating zone '1.168.192.in-addr.arpa/IN': adding an RR
Jul 11 14:23:53 myserver named[3515]: journal file 1.168.192.in-addr.arpa.jnl does not exist, creating it
Jul 11 14:23:53 myserver dhcpd: added reverse map from 11.1.168.192.in-addr.arpa to host1.myserver.myprovider.ru
Jul 11 14:23:53 myserver dhcpd: DHCPREQUEST for 192.168.1.11 from 00:c0:26:31:6a:13 via eth0
Jul 11 14:23:53 myserver dhcpd: DHCPACK on 192.168.1.11 to 00:c0:26:31:6a:13 via eth0


2.1.4. Настройка Squid и IPTables


Ни Squid,  ни IPTables не поддерживают хранение собственных настроек в LDAP. Для Squid существует модуль авторизации пользователей из LDAP, но нам требуется разграничение доступа не по пользователям, а по рабочим станциям. Настройки IPTables - это стартовый скрипт и/или файл дампа, что тоже не совсем подходит.

Самый простой выход в данной ситуации - создание собственной схемы internet-access.schema со следующим содержимым:

attributetype ( 1.1.2.1.1.1 NAME 'allowNat'
        DESC 'allow use NAT'
        EQUALITY booleanMatch
        SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE )

attributetype ( 1.1.2.1.1.2 NAME 'allowProxy'
        DESC 'allow use proxy'
        EQUALITY booleanMatch
        SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE )

attributetype ( 1.1.2.1.1.3 NAME 'forceProxy'
        DESC 'force use proxy'
        EQUALITY booleanMatch
        SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE )

objectclass ( 1.1.2.2.1.1 NAME 'internetAccess' SUP top STRUCTURAL
        DESC 'internet access rules'
        MUST ( allowNat $ allowProxy $ forceProxy ))


В схеме определяется класс internetAccess и его атрибуты: allowNat (разрешить использование NAT), allowProxy (разрешить использование прокси-сервера) или forceProxy (использовать прокси-сервер в прозрачном режиме). Cозданную схему необходимо скопировать в каталог /etc/openldap/schema и модифицировать файл /etc/openldap/slapd.conf (добавленные строки выделены):

...

# Включение схемы для хранения настроек DHCP в LDAP
include /etc/openldap/schema/dhcp.schema

# Включение схемы для хранения настроек Squid и IPTables
include /etc/openldap/schema/internet-access.schema


# pid-файл и файл с аргументами запуска
pidfile         /var/run/slapd.pid
argsfile        /var/run/slapd.args

...


После перезапуска OpenLDAP (service ldap restart в ALT Linux) необходимо внести данные, соответствующие схеме, из файла internet-access.ldif:

dn: cn=host1, cn=192.168.1.0, cn=Network, dc=myserver, dc=myprovider, dc=ru
changetype: modify
add: objectClass
objectClass: internetAccess
-
replace: allowNat
allowNat: TRUE
-
replace: allowProxy
allowProxy: TRUE
-
replace: forceProxy
forceProxy: TRUE

dn: cn=host2, cn=192.168.1.0, cn=Network, dc=myserver, dc=myprovider, dc=ru
changetype: modify
add: objectClass
objectClass: internetAccess
-
replace: allowNat
allowNat: TRUE
-
replace: allowProxy
allowProxy: FALSE
-
replace: forceProxy
forceProxy: FALSE

Добавить их можно командой:

ldapmodify -h 127.0.0.1 -x -D "cn=manager,dc=myserver,dc=myprovider,dc=ru" -w "secret" -f internet-access.ldif

Для извлечения списка хостов, у которых одно из свойств allowNat, allowProxy или forceProxy установлено в TRUE, можно использовать такой простой скрипт (/opt/scripts/make_ldap_filter.sh):

#!/bin/bash

ldapsearch -h 127.0.0.1 -LLL -b "dc=myserver,dc=myprovider,dc=ru" -D "cn=manager,dc=myserver,dc=myprovider,dc=ru" -w "secret"
"(&(objectClass=internetAccess) ("$1"=TRUE))" | grep fixed-address | awk '{print $3}'

Пример вызова скрипта:

# /opt/scripts/make_ldap_filter.sh allowProxy
192.168.1.11

Eго можно использовать в скрипте, генерирующем правила для IPTables (/opt/scripts/make_iptables.sh):

#!/bin/sh

################################################################################
EXTERNAL_DEVICE="ppp0"
INTERNAL_DEVICE="eth0"
INTERNAL_ADDRESS="192.168.1.1"
IPTABLES="/sbin/iptables"
################################################################################

# Обнуляем таблицы
$IPTABLES -F
$IPTABLES -X
$IPTABLES -t nat -F
$IPTABLES -t nat -X

# Устанавливаем правила по умолчанию для тех пакетов,
# про которые мы случайно забудем при написании правил ;)
$IPTABLES -P INPUT DROP
$IPTABLES -P OUTPUT ACCEPT
$IPTABLES -P FORWARD DROP

# Создаем конечные цепочки для регистрации пакетов
# подробнее об этом в разделе 2.2.3
$IPTABLES -N allowed
$IPTABLES -A allowed -j ULOG --ulog-nlgroup 1 --ulog-qthreshold 50 --ulog-prefix allow
$IPTABLES -A allowed -j ACCEPT

$IPTABLES -N rejected
$IPTABLES -A rejected -j ULOG --ulog-nlgroup 1 --ulog-qthreshold 50 --ulog-prefix drop
$IPTABLES -A rejected -j DROP

# Выпускаем все пакеты наружу без ограничений
$IPTABLES -A OUTPUT -j allowed

# Впускаем все пакеты, которые пришли не с внешнего интерфейса
$IPTABLES -A INPUT ! -i $EXTERNAL_DEVICE -j allowed
# Не впускаем TCP-пакеты, пришедшие с внешнего интерфейса,
# для которых статус установлен в NEW, но которые на самом деле таковыми не являются
$IPTABLES -A INPUT -i $EXTERNAL_DEVICE -p TCP ! --syn -m state --state NEW -j rejected
# Впускаем TCP-пакеты, пришедшие с внешнего интерфейса на порт 22
# и пытающиеся установить новое соединение (для ssh)
$IPTABLES -A INPUT -i $EXTERNAL_DEVICE -p TCP --syn --dport 22 -j allowed
# Впускаем все TCP-пакеты, пришедшие с внешнего интерфейса, для которых соединение уже установлено
$IPTABLES -A INPUT -i $EXTERNAL_DEVICE -p TCP -m state --state ESTABLISHED,RELATED -j allowed
# Впускаем все UDP-пакеты, пришедшие с внешнего интерфейса, необходимые для DNS
$IPTABLES -A INPUT -i $EXTERNAL_DEVICE -p UDP --dport 53 -j allowed
$IPTABLES -A INPUT -i $EXTERNAL_DEVICE -p UDP --sport 53 -j allowed
# Впускаем все UDP-пакеты, пришедшие с внешнего интерфейса, необходимые для NTP
$IPTABLES -A INPUT -i $EXTERNAL_DEVICE -p UDP --dport 123 -j allowed
$IPTABLES -A INPUT -i $EXTERNAL_DEVICE -p UDP --sport 123 -j allowed
# Впускаем все ICMP-пакеты, пришедшие с внешнего интерфейса
$IPTABLES -A INPUT -i $EXTERNAL_DEVICE -p ICMP -j allowed
# Не впускаем все прочие пакеты, пришедшие с внешнего интерфейса
$IPTABLES -A INPUT -i $EXTERNAL_DEVICE -j rejected

# Добавляем правила для всех хостов, которым разрешено использовать NAT
for i in `/opt/scripts/make_ldap_filter.sh allowNat`
do
    # Заменяем адреса пакетов
    $IPTABLES -t nat -A POSTROUTING -o $EXTERNAL_DEVICE -s $i -j MASQUERADE
    # Выпускаем все пакеты наружу без ограничений
    $IPTABLES -A FORWARD -i $INTERNAL_DEVICE -o $EXTERNAL_DEVICE -s $i -j allowed
    # Не впускаем TCP-пакеты, пришедшие с внешнего интерфейса, пытающиеся установить новое соединение
    $IPTABLES -A FORWARD -o $INTERNAL_DEVICE -i $EXTERNAL_DEVICE -p TCP --syn -d $i -j rejected
    # Не впускаем ICMP-пакеты с типом 8 (request), пришедшие с внешнего интерфейса
    $IPTABLES -A FORWARD -o $INTERNAL_DEVICE -i $EXTERNAL_DEVICE -p ICMP --icmp-type 8 -d $i -j rejected
    # Впускаем все прочие пакеты
    $IPTABLES -A FORWARD -o $INTERNAL_DEVICE -i $EXTERNAL_DEVICE -d $i -j allowed
done

# Добавляем правила для всех хостов, которым разрешено использовать прокси-сервер в прозрачном режиме
for i in `/opt/scripts/make_ldap_filter.sh forceProxy`
do
    # Заворачиваем HTTP-запросы на прокси-сервер
    $IPTABLES -t nat -A PREROUTING -s $i -p TCP --dport 80 -j REDIRECT --to-port 3128
done

Аналогичный скрипт для Squid (/opt/scripts/make_squid.sh) выглядит так:

#!/bin/sh

/opt/scripts/make_ldap_filter.sh allowProxy > /etc/squid/allowed_hosts

При этом в конфигурационном файл Squid (/etc/squid/squid.conf) должно быть написано примерно следующее:

hierarchy_stoplist cgi-bin ?
acl QUERY urlpath_regex cgi-bin \?
no_cache deny QUERY
auth_param basic children 5
auth_param basic realm Squid proxy-caching web server
auth_param basic credentialsttl 2 hours
refresh_pattern ^ftp:           1440    20%     10080
refresh_pattern ^gopher:        1440    0%      1440
refresh_pattern .               0       20%     4320
acl all src 0.0.0.0/0.0.0.0
acl manager proto cache_object
acl localhost src 127.0.0.1/255.255.255.255
acl to_localhost dst 127.0.0.0/8
acl SSL_ports port 443 563
acl Jabber_ports port 5222
acl Safe_ports port 80          # http
acl Safe_ports port 21          # ftp
acl Safe_ports port 443 563     # https, snews
acl Safe_ports port 70          # gopher
acl Safe_ports port 210         # wais
acl Safe_ports port 1025-65535  # unregistered ports
acl Safe_ports port 280         # http-mgmt
acl Safe_ports port 488         # gss-http
acl Safe_ports port 591         # filemaker
acl Safe_ports port 777         # multiling http
acl CONNECT method CONNECT
http_access allow manager localhost
http_access deny manager
http_access deny !Safe_ports
http_access deny CONNECT !SSL_ports !Jabber_ports
acl allowed_hosts src "/etc/squid/allowed_hosts"
http_access allow allowed_hosts
http_access allow localhost
http_access deny all
http_reply_access allow all
icp_access allow all
httpd_accel_host virtual
httpd_accel_with_proxy on
httpd_accel_uses_host_header on
error_directory /usr/share/squid/errors/Russian-koi8-r
coredump_dir /var/spool/squid
visible_hostname myserver.myprovider.ru


2.1.5. Настройка авторизации в OpenLDAP


Если все основные настройки системы хранятся в LDAP, то вполне естественно хранить там же и учетные записи пользователей. Для этого желательно модифицировать файл /etc/openldap/slapd.conf (добавленные строки выделены):

...

# Индексы для ускорения поиска в дереве
index  objectClass         eq

#Индексы для DHCP
index dhcpHWAddress        eq
index dhcpClassData        eq

# Индексы для авторизации
index cn, uid, uidNumber, gidNumber eq


Затем необходимо создать файл users.ldif со следующим содержимым:

dn: cn=Users, dc=myserver, dc=myprovider, dc=ru
objectClass: top

dn: cn=ldapuser1, cn=Users, dc=myserver, dc=myprovider, dc=ru
objectClass: posixAccount
cn: LDAP User 1
uid: ldapuser1
uidNumber: 1001
gidNumber: 10
homeDirectory: /home/ldapuser1
loginShell: /bin/bash

dn: cn=ldapuser2, cn=Users, dc=myserver, dc=myprovider, dc=ru
objectClass: posixAccount
cn: LDAP User 2
uid: ldapuser2
uidNumber: 1002
gidNumber: 10
homeDirectory: /home/ldapuser2
loginShell: /bin/bash

Содержимое файла необходимо добавить в дерево LDAP  командой:

ldapadd -h 127.0.0.1 -x -D "cn=manager,dc=myserver,dc=myprovider,dc=ru" -w "secret" -f users.ldif

После этого нужно установить pam_ldap и nss_ldap (apt-get install pam_ldap nss_ldap в ALT Linux) и отредактировать следующие файлы по образцу:

/etc/nsswitch.conf:

passwd:     files ldap nisplus nis
shadow:     tcb files ldap nisplus nis
group:      files ldap nisplus nis
hosts:      files nisplus nis dns
ethers:     files
netmasks:   files
networks:   files
protocols:  files
rpc:        files
services:   files
bootparams: nisplus [NOTFOUND=return] files
netgroup:   nisplus
publickey:  nisplus
automount:  files nisplus
aliases:    files nisplus


/etc/ldap.conf:

host 127.0.0.1
base dc=myserver,dc=myprovider,dc=ru
rootbinddn cn=manager,dc=myserver,dc=myprovider,dc=ru


/etc/ldap.secret:

secret

/etc/pam.d/system-auth:

auth            sufficient      /lib/security/pam_ldap.so
auth            required        /lib/security/pam_tcb.so shadow fork prefix=a$ count=8 nullok use_first_pass
account         sufficient      /lib/security/pam_ldap.so
account         required        /lib/security/pam_tcb.so shadow fork
password        sufficient      /lib/security/pam_ldap.so
password        required        /lib/security/pam_passwdqc.so min=disabled,24,12,8,7 max=40 passphrase=3 match=4 similar=deny random=42 enforce=users retry=3
password        required        /lib/security/pam_tcb.so use_authtok shadow fork prefix=a$ count=8 write_to=tcb
session         required        /lib/security/pam_tcb.so
session         required        /lib/security/pam_limits.so
session         required        /lib/security/pam_mkhomedir.so skel=/etc/skel/ umask=0077


/etc/pam.d/system-auth-use_first_pass:

auth            sufficient      /lib/security/pam_ldap.so use_first_pass
auth            required        /lib/security/pam_tcb.so shadow fork prefix=a$ count=8 nullok use_first_pass
password        sufficient      /lib/security/pam_ldap.so
password        required        /lib/security/pam_tcb.so use_authtok shadow fork prefix=a$ count=8 write_to=tcb


Теперь созданные пользователи LDAP являются также системными пользователями:

# id ldapuser1
uid=1001(ldapuser1) gid=10(wheel) groups=10(wheel)

# passwd ldapuser1
passwd: updating all authentication tokens for user ldapuser1.
New password:
Re-enter new password:
LDAP password information changed for ldapuser1
passwd: all authentication tokens updated successfully.
$ su - ldapuser1
Password:
Creating directory '/home/ldapuser1'.
Creating directory '/home/ldapuser1/tmp'.
Creating directory '/home/ldapuser1/Documents'.
Creating directory '/home/ldapuser1/.mutt'.
Creating directory '/home/ldapuser1/.mutt/color.default'.
Creating directory '/home/ldapuser1/.xsession.d'.

$ ssh ldapuser1@localhost
ldapuser1@localhost's password:
Last login: Tue Apr 20 10:38:21 2004 from localhost.localdomain
$
2.1.6. Хранение в OpenLDAP произвольной справочной информации

Все что в настоящее хранится в LDAP (как информация о хостах, так и о пользователях), тем или иным способом используется различными сервисами. Кроме этого, иногда бывает полезно хранить дополнительную информацию о хостах, которая будет представлять интерес только для системного администратора. Хранение такой дополнительной информации поддерживает схема info.schema:

attributetype ( 1.1.2.1.2.1 NAME 'comment'
        DESC 'Host comment'
        EQUALITY caseIgnoreMatch
        SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )

objectclass ( 1.1.2.2.1.2 NAME 'hostsInfo' SUP top STRUCTURAL
        DESC 'Hosts additional info'
        MAY ( comment ))


Необходимо скопровать схему в каталог /etc/openldap/schema и модифицировать файл /etc/openldap/slapd.conf (добавленные строки выделены):

# В дереве LDAP могут присутствовать только такие записи, которые являются экземлярами
# классов, описанных в схемах, и удовлетворяют ограничениям этих классов
include /etc/openldap/schema/core.schema
include /etc/openldap/schema/cosine.schema
include /etc/openldap/schema/inetorgperson.schema
include /etc/openldap/schema/misc.schema
include /etc/openldap/schema/nis.schema
include /etc/openldap/schema/openldap.schema

# Включение схемы для хранения настроек DHCP в LDAP
include /etc/openldap/schema/dhcp.schema

# Включение схемы для хранения настроек Squid и IPTables
include /etc/openldap/schema/internet-access.schema

# Включение схемы для хранения справочной информации о хостах
include /etc/openldap/schema/info.schema

# pid-файл и файл с аргументами запуска
pidfile         /var/run/slapd.pid
argsfile        /var/run/slapd.args

...

После перезапуска OpenLDAP (service ldap restart в ALT Linux) необходимо создать файл info.ldif со следующим содержимым:

dn: cn=host1, cn=192.168.1.0, cn=Network, dc=myserver, dc=myprovider, dc=ru
changetype: modify
add: objectClass
objectClass: hostsInfo

dn: cn=host2, cn=192.168.1.0, cn=Network, dc=myserver, dc=myprovider, dc=ru
changetype: modify
add: objectClass
objectClass: hostsInfo


Содержимое файла необходимо внести в дерево LDAP командой:

ldapmodify -h 127.0.0.1 -x -D "cn=manager,dc=myserver,dc=myprovider,dc=ru" -w "secret" -f info.ldif

2.1.7. Результат сведения настроек в единое хранилище

После всех модификаций в окончательном варианте дерево LDAP выглядит так:

$ ldapsearch -h 127.0.0.1 -LLL -b "dc=myserver,dc=myprovider,dc=ru" -D "cn=manager,dc=myserver,dc=myprovider,dc=ru" -w "secret"

dn: dc=myserver, dc=myprovider, dc=ru
objectClass: top
objectClass: dhcpServer
cn: myserver.myprovider.ru
dhcpServiceDN: cn=Network, dc=myserver, dc=myprovider, dc=ru

dn: cn=Network, dc=myserver, dc=myprovider, dc=ru
cn: Network
objectClass: top
objectClass: dhcpService
objectClass: dhcpOptions
dhcpPrimaryDN: cn=myserver.myprovider.ru, dc=myserver, dc=myprovider, dc=ru
dhcpOption: domain-name "myserver.myprovider.ru"
dhcpOption: domain-name-servers 192.168.1.1
dhcpOption: time-offset -5
dhcpOption: ntp-servers 192.168.1.1
dhcpStatements: default-lease-time 21600
dhcpStatements: max-lease-time 43200
dhcpStatements: ddns-update-style interim
dhcpStatements: ddns-updates on
dhcpStatements: ddns-domainname "myserver.myprovider.ru"
dhcpStatements: ddns-rev-domainname "in-addr.arpa"
dhcpStatements: update-static-leases on
dhcpStatements: key DHCP_UPDATE { algorithm HMAC-MD5.SIG-ALG.REG.INT; secret "
 WOfB3kj8IhJK4OZ5s3zHeQ=="
dhcpStatements: } zone myserver.myprovider.ru. { primary 192.168.1.1; key DHCP
 _UPDATE
dhcpStatements: } zone 1.168.192.in-addr.arpa. { primary 192.168.1.1; key DHCP
 _UPDATE
dhcpStatements: } #

dn: cn=192.168.1.0, cn=Network, dc=myserver, dc=myprovider, dc=ru
cn: 192.168.1.0
objectClass: top
objectClass: dhcpSubnet
objectClass: dhcpOptions
dhcpNetMask: 24
dhcpOption: routers 192.168.1.1
dhcpOption: subnet-mask 255.255.255.0

dn: cn=host1, cn=192.168.1.0, cn=Network, dc=myserver, dc=myprovider, dc=ru
cn: host1
objectClass: top
objectClass: dhcpHost
objectClass: internetAccess
objectClass: hostsInfo
dhcpHWAddress: ethernet 00:c0:26:31:6a:13
dhcpStatements: fixed-address 192.168.1.11
allowNat: TRUE
allowProxy: TRUE
forceProxy: TRUE

dn: cn=host2, cn=192.168.1.0, cn=Network, dc=myserver, dc=myprovider, dc=ru
cn: host2
objectClass: top
objectClass: dhcpHost
objectClass: internetAccess
objectClass: hostsInfo
dhcpHWAddress: ethernet 00:c0:26:31:6a:14
dhcpStatements: fixed-address 192.168.1.12
allowNat: TRUE
allowProxy: FALSE
forceProxy: FALSE

dn: cn=Users, dc=myserver, dc=myprovider, dc=ru
objectClass: top

dn: cn=ldapuser1, cn=Users, dc=myserver, dc=myprovider, dc=ru
objectClass: posixAccount
cn: LDAP User 1
uid: ldapuser1
uidNumber: 1001
gidNumber: 10
homeDirectory: /home/ldapuser1
loginShell: /bin/bash
userPassword:: MQ==

dn: cn=ldapuser2, cn=Users, dc=myserver, dc=myprovider, dc=ru
objectClass: posixAccount
cn: LDAP User 2
uid: ldapuser2
uidNumber: 1002
gidNumber: 10
homeDirectory: /home/ldapuser2
loginShell: /bin/bash

Главный скрипт, который заставляет все необходимые сервисы перечитать конфигурацию (/opt/scripts/make_all.sh), выглядит следующим образом:

#!/bin/bash

echo "Apply DHCP settings ..."
service dhcpd restart

echo "Apply Firewall settings ..."
/opt/scripts/make_iptables.sh
service iptables save

echo "Apply Proxy settings ..."
/opt/scripts/make_squid.sh
service squid restart


Таким образом, первые две задачи - централизованное управление настройками рабочих станций и централизованное управление доступом в интернет - решены. Для внесения изменений в конфигурацию рабочей станции достаточно изменить запись о ней в дереве LDAP и выполнить скрипт /opt/scripts/make_all.sh

2.2. Подсистема учета трафика

В качестве СУБД для подсистемы учета трафика был выбран Firebird Classic Server 1.5. Основная причина такого выбора заключалась в том, что сервер, на котором конфигурировалась подсистема управления настройками, имел очень скромную аппаратную конфигурацию. В то же время рядом стоял уже настроенный сервер БД, поэтому и было принято решение использовать его.

Скрипты для обработки данных о трафике написаны на Perl, поэтому для их работы необходимо наличие в системе самого Perl'а и DBI-драйвера для Firebird, последняя версия которого доступна с http://dbi-interbase.sourceforge.net. Пользователи ALT Linux вместо ручной установки драйвера могут подключить репозиторий nm и выполнить команду apt-get install perl-DBD-InterBase. Кроме того, необходимо наличие как минимум клиентской части Firebird. Существует 2 неправильных способа установить клиентскую часть Firebird: установить Firebird целиком (его последняя версия доступна на http://firebird.sourceforge.net) или скопировать с машины, на которую он установлен, файл libfbclient.so.1.5.0 и создать ссылку libfbclient.so.1 -> libfbclient.so.1.5.0. Правильным способом была бы пересборка Firebird c учетом специфики ALT Linux (включая разбиение его на пакеты firebird-server-cs, firebird-server-ss, firebird-client, firebird-devel, firebird-doc), однако эту задачу я отложил до лучших времен.

2.2.1. Создание базы данных

Для создания базы данных на сервере Firebird необходимо создать следующий файл metadata.sql:

SET SQL DIALECT 3;

CREATE DATABASE 'localhost:billing' USER 'sysdba' PASSWORD 'masterkey';

/* Таблица для хранения данных о трафике, прошедшем через прокси-сервер */
CREATE TABLE PROXY_LOG (LOG_DATE DATE, /* дата */
CLIENT_ADDRESS VARCHAR(256), /* адрес рабочей станции */
HOST VARCHAR(255), /* адрес сервера, на который отправлен HTTP-запрос */
REQUEST_METHOD VARCHAR(10), /* тип HTTP-запроса */
TOTAL_COUNT BIGINT, /* общее количество запросов */
TOTAL_BYTES BIGINT, /* общее количество запрошенных байт */
CACHE_COUNT BIGINT, /* количество запросов, для ответа на которые был использован кэш прокси-сервера */
CACHE_BYTES BIGINT); /* количество байт, взятых из кэша */

/* Таблица для хранения данных о трафике, прошедшем через NAT */
CREATE TABLE TRANSFER_LOG (LOG_DATE DATE, /* дата */
PROTOCOL SMALLINT, /* номер протокола (из /etc/protocols) */
SOURCE VARCHAR(15), /* отправитель пакета */
SOURCE_PORT INTEGER, /* порт отправителя */
DESTINATION VARCHAR(15), /* получатель пакета */
DESTINATION_PORT INTEGER, /* порт получателя */
INCOMING VARCHAR(4), /* входящий интерфейс */
OUTGOING VARCHAR(4), /* исходящий интерфейс */
PREFIX VARCHAR(25), /* префикс из ULOG */
PACKETS BIGINT, /* количество пакетов */
BYTES BIGINT); /* количество байт */

COMMIT WORK;

CREATE INDEX PROXY_LOG_DATE ON PROXY_LOG(LOG_DATE);
CREATE INDEX TRANSFER_LOG_DATE ON TRANSFER_LOG(LOG_DATE);
CREATE INDEX TRANSFER_LOG_DESTINATION ON TRANSFER_LOG(DESTINATION);
CREATE INDEX TRANSFER_LOG_DESTINATION_PORT ON TRANSFER_LOG(DESTINATION_PORT);
CREATE INDEX TRANSFER_LOG_INCOMING ON TRANSFER_LOG(INCOMING);
CREATE INDEX TRANSFER_LOG_OUTGOING ON TRANSFER_LOG(OUTGOING);
CREATE INDEX TRANSFER_LOG_PREFIX ON TRANSFER_LOG(PREFIX);
CREATE INDEX TRANSFER_LOG_SOURCE ON TRANSFER_LOG(SOURCE);
CREATE INDEX TRANSFER_LOG_SOURCE_PORT ON TRANSFER_LOG(SOURCE_PORT);
COMMIT WORK;

GRANT SELECT ON PROXY_LOG TO USER OPERATOR;
GRANT SELECT ON TRANSFER_LOG TO USER OPERATOR;
COMMIT WORK;

В файл /opt/firebird/aliases.conf на сервере Firebird необходимо добавить строку:

billing = /var/db/firebird/billing.fdb

Затем на сервере Firebird нужно выполнить команду:

/opt/firebird/bin/isql -i metadata.sql

2.2.2. Учет прокси-трафика

Источником данных о трафике, проходящем через прокси-сервер Squid, является файл /var/log/squid/access.log. Формат файла описан в документации Squid. Существует множество готовых программ и скриптов для анализа содержимого access.log, доступных по какой-либо открытой лицензии. На основе одного из них (скрипта из пакета loganalyzer из ALT Linux) с минимальными изменениями был написан скрипт /opt/scripts/translate_squid.pl:

#!/usr/bin/perl
#
# Based on loganalyzer (Sergey N. Yatskevich <syatskevich@altlinux.ru>)
#
# Обработка лога Squid'а и запись результатов в таблицу proxy_log
#
# При обработке лога извлекается следующая информация :
# 1) время (с детализацией по дням) обращения
# 2) машина, с которой произошло обращение
# 3) хост, к которому произошло обращение (только доменное имя)
# 4) метод обращения (GET или POST)
# 5) Взят-ли объект из кэша или произошла его загрузка из сети
# 6) объем загруженной информации
#
# Интересует "объем информации"/"количество обращений" в разрезе
# 1, 2, 3, 4
#

use URI;
use DBI;

$dsn = "dbi:InterBase:dbname=billing;host=firebird;ib_dialect=3";
$user = "sysdba";
$passwd = "system";

sub parseLogLine
{
($time, $duration, $client_address, $result_codes, $bytes, $request_method, $url, $rfc931, $hierarhy_code, $type) = split (/\s+/);

# Пропускаем сообщения об ошибках выдаваемых Squid пользователям
# и запросы к самому Squid'у
if ($result_codes =~ /^NONE/ || $url =~ /^cache_object:/)
{
return ("SKIP_LINE", "", "", "", "", 0, 0);
}

# Squid не проставляет при типе соединения CONNECT (по крайней
# мере для ICQ) используемый протокол, что вызывает ошибку
# в пакете URI. Поэтому добавляем протокол сами.
if ($request_method eq "CONNECT" && $url !~ /^https:/i)
{
$url = "https://$url";
}

$uri = URI->new ($url);
$uri = $uri->canonical;
$host = $uri->host;

($day, $month, $year) = (localtime ($time)) [3, 4, 5];

$month++;
$year += 1900;

$hit = ($result_codes =~ /_HIT/) ? 1 : 0;

return ("INSERT", "$year/$month/$day", $client_address, lc $host, $request_method, $hit, $bytes);
}

while (<STDIN>)
{
($state, $date, $client_address, $host, $request_method, $hit, $bytes) = parseLogLine ($_);

if ($state eq "INSERT")
{
$total_count{"$date $client_address $host $request_method"} ++;
$total_bytes{"$date $client_address $host $request_method"} += $bytes;

if ($hit == 1)
{
$hit_count{"$date $client_address $host $request_method"} ++;
$hit_bytes{"$date $client_address $host $request_method"} += $bytes;
}
}
}

$dbh = DBI->connect ($dsn, $user, $passwd, {AutoCommit => 0});

$sth = $dbh->prepare ("INSERT INTO proxy_log VALUES (?, ?, ?, ?, ?, ?, ?, ?)");

foreach $key (keys %total_count)
{
if ($total_bytes{$key} > 0)
{
($date, $client_address, $host, $request_method) = split (/ /, $key);

($hcount, $hbytes) = (exists $hit_count{$key}) ? ($hit_count{$key}, $hit_bytes{$key}) : (0, 0);

$sth->execute ($date, $client_address, $host, $request_method, $total_count{$key}, $total_bytes{$key}, $hcount, $hbytes);
if ($sth->state)
{
$dbh->rollback;
$dbh->disconnect;

die "Ошибка вставки записи, откат в начальное состояние\n";
}
}
}

$dbh->commit;
$dbh->disconnect;
Для регулярной очистки файла access.log и загрузки его содежимого в базу данных можно использовать мехнизм logrotate. В этом случае файл /etc/logrotate.d/squid необходимо отредактировать следующим образом:

/var/log/squid/access.log {
    daily
    rotate 7
    copytruncate
    compress
    notifempty
    missingok
    prerotate
        cd /opt/scripts
        ./translate_squid.pl < /var/log/squid/access.log
    endscript
}
/var/log/squid/cache.log {
    daily
    rotate 7
    copytruncate
    compress
    notifempty
    missingok
}

/var/log/squid/store.log {
    daily
    rotate 7
    copytruncate
    compress
    notifempty
    missingok
# This script asks squid to rotate its logs on its own.
# Restarting squid is a long process and it is not worth
# doing it just to rotate logs
    postrotate
      /usr/sbin/squid -k rotate
    endscript
}


2.2.3. Учет NAT-трафика

Для учета NAT-трафика используется механизм ULOG. Работает он следующим образом: средствами IPTables для части пакетов может быть указана цель ULOG. Это означает, что копии пакетов (или только заголовки) из ядра отправляются в userspace, где их ждет специальный демон, умеющий их обрабатывать. В настоящее время существует 2 таких демона: ulogd и ulog-acctd. Ulog-acctd проще, компактнее и умеет группировать пакеты, поэтому он и был выбран как средство учета NAT-трафика. Последнюю версию ulog-acctd можно загрузить с http://alioth.debian.org/projects/pkg-ulog-acctd. Пользователи ALT Linux вместо ручной сборки могут подключить репозиторий nm и выполнить команду apt-get install ulog-acctd.

После установки ulog-acctd необходимо отредактировать его конфигурационный файл /etc/ulog-acctd.conf:

multicast groups=1
accounting file=/var/log/ulog-acctd/account.log
dump file=/var/log/ulog-acctd/dump
debug file=/var/log/ulog-acctd/debug.log
pid file=/var/run/ulog-acctd.pid
debug = syscall, misc, statistics, error, error-packet
accounting format="%t\t%p\t%s\t%S\t%d\t%D\t%P\t%b\t%i\t%o\t%f\n"
empty interface="none"
empty prefix="none"
flush=30
fdelay=0


Затем нужно запустить ulog-acctd (service ulog-acctd start в ALT Linux).

Источником данных о трафике, проходящем через ulog-acctd, является файл /var/log/ulog-acctd/account.log. Для обработки данных о трафике на основе скрипта из пакета loganalyzer из ALT Linux был написан скрипт /opt/scripts/translate_ulog.pl:

#!/usr/bin/perl
#
# Based on loganalyzer (Sergey N. Yatskevich <syatskevich@altlinux.ru>)
#
# Обработка лога ulog-acctd и запись результатов в таблицу transfer_log
#
# При обработке лога извлекается следующая информация :
# 1) время (с детализацией по дням) обращения
# 2) протокол (1 - ICMP, 6 - TCP, 17 - UDP)
# 3) отправитель пакета
# 4) порт отправителя
# 5) адресат пакета
# 6) порт адресата
# 7) входящий интерфейс
# 8) исходящий интерфейс
# 9) префикс ulog-acctd
# 10) количество и размер пакетов

# Интересует "размер пакетов"/"количество пакетов" в разрезе
# 1, 2, 3, 4, 5, 6, 7, 8, 9
#

use DBI;

$dsn = "dbi:InterBase:dbname=billing;host=firebird;ib_dialect=3";
$user = "sysdba";
$passwd = "system";

sub parseLogLine
{
($time, $protocol, $source, $source_port, $destination, $destination_port, $packets, $bytes, $incoming, $outgoing, $prefix) = split (/\s+/);

($day, $month, $year) = (localtime ($time)) [3, 4, 5];

$month++;
$year += 1900;

return ("INSERT", "$year/$month/$day", $protocol, $source, $source_port, $destination, $destination_port, $packets, $bytes, $incoming, $outgoing, $prefix);
}

while (<STDIN>)
{
($state, $date, $protocol, $source, $source_port, $destination, $destination_port, $packets, $bytes, $incoming, $outgoing, $prefix) = parseLogLine ($_);

if ($state eq "INSERT")
{
$total_packets{"$date $protocol $source $source_port $destination $destination_port $incoming $outgoing $prefix"} ++;
$total_bytes{"$date $protocol $source $source_port $destination $destination_port $incoming $outgoing $prefix"} += $bytes;
}
}

$dbh = DBI->connect ($dsn, $user, $passwd, {AutoCommit => 0});

$sth = $dbh->prepare ("INSERT INTO transfer_log VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");

foreach $key (keys %total_packets)
{
if ($total_bytes{$key} > 0)
{
($date, $protocol, $source, $source_port, $destination, $destination_port, $incoming, $outgoing, $prefix) = split (/ /, $key);

$sth->execute ($date, $protocol, $source, $source_port, $destination, $destination_port, $incoming, $outgoing, $prefix, $total_packets{$key}, $total_bytes{$key});
if ($sth->state)
{
$dbh->rollback;
$dbh->disconnect;

die "Ошибка вставки записи, откат в начальное состояние\n";
}
}
}

$dbh->commit;
$dbh->disconnect;
Для регулярной очистки файла account.log и загрузки его содежимого в базу данных можно использовать мехнизм logrotate. В этом случае файл /etc/logrotate.d/ulog-acctd необходимо отредактировать следующим образом:

/var/log/ulog-acctd/account.log /var/log/ulog-acctd/debug.log {
weekly
rotate 5
copytruncate
compress
notifempty
missingok
sharedscripts
prerotate
if [ -e /var/run/ulog-acctd.pid ]; then kill -STOP `cat /var/run/ulog-acctd.pid`; fi
endscript
postrotate
if [ -e /var/run/ulog-acctd.pid ]; then kill -CONT `cat /var/run/ulog-acctd.pid`; fi
endscript
}

2.3. Графический интерфейс системы администрирования

Для адинистрирования системы можно использовать стандартные средства LDAP (от утилит, работающих в режиме командной строки, до графических клиентов: GQ - http://biot.com/gq/, JXplorer - http://pegacat.com/jxplorer/) и Firebird (от стандартного isql до IBExpert - http://ibexpert.com/). Однако все они обладают слишком широкой функциональностью, поэтому для управления созданной структурой была создана Network Console. Бинарный дистрибутив (для Linux и Windows) лежит здесь, а исходные коды - здесь. Network Console написана на Java, поэтому для исполнения она требует JRE, а для компиляции - JDK. И JDK, и JRE (как отдельно, так и в составе JDK) доступны на http://java.sun.com/j2se/1.4.2/download.html.

2.3.1. Описание графического интерфейса с точки зрения пользователя

В составе бинарного дистрибутива есть 3 стартовых скрипта:

Скрипт
Назначение
networkconsole-linux-gtk2.sh
Скрипт для запуска Network Console в Linux с использованием GTK2
networkconsole-linux-motif.sh
Скрипт для запуска Network Console в Linux с использованием OpenMotif
networkconsole-win32.bat
Скрипт для запуска Network Console в Windows

При запуске Network Console необходимо указать параметры подключения к OpenLDAP и Firebird:

Подключение к OpenLDAP
Подключение к Firebird

Кроме того, существуют еще 2 параметра подключения к OpenLDAP, которые можно изменить только в конфигурационном файле networkconsole.properties:

connection.ldap.context.hosts=cn\=192.168.1.0,cn\=Network,dc\=myserver,dc\=myprovider,dc\=ru
connection.ldap.context.users=cn\=Users,dc\=myserver,dc\=myprovider,dc\=ru


Главное окно запущенной Network Console состоит из 3-х вкладок:

1. Управление пользователями OpenLDAP (теми, кто может модифицировать дерево LDAP, в том числе и с помощью Network Console) - версия для GTK2:

Пользователи

2. Управление настройками хостов - версия для OpenMotif:

Хосты

3. Выполнение запросов к базе данных, в которой хранится информация о трафике - версия для Win32:

Трафик

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

Редактирование

Для всех вкладок возможен экспорт списка в XML с XSLT-трансформацией:

Экспорт

Для вкладки "Трафик" возможно использование готовых запросов, реализованных в виде хранимых процедур. Чтобы использовать эту возможность, необходимо на сервере Firebird создать файл procedures.sql со следующим содержимым:

SET ECHO ON;
SET SQL DIALECT 3;
SET NAMES WIN1251;

CONNECT 'localhost:billing' USER 'sysdba' PASSWORD 'masterkey';

SET TERM ^ ;

CREATE PROCEDURE FULL_TRAFFIC (PERIOD_BEGIN DATE,
PERIOD_END DATE)
RETURNS (INCOMING_ALLOWED BIGINT,
INCOMING_REJECTED BIGINT,
OUTGOING BIGINT)
AS

begin
select sum(bytes) from transfer_log
where outgoing<>'ppp0' and incoming='ppp0' and prefix='allow'
and log_date>=:period_begin and log_date<=:period_end
into :incoming_allowed;
select sum(bytes) from transfer_log
where outgoing<>'ppp0' and incoming='ppp0' and prefix='drop'
and log_date>=:period_begin and log_date<=:period_end
into :incoming_rejected;
select sum(bytes) from transfer_log
where outgoing='ppp0' and incoming<>'ppp0'
and log_date>=:period_begin and log_date<=:period_end
into :outgoing;
suspend;
end ^

CREATE PROCEDURE NAT_TRAFFIC_BY_HOSTS (PERIOD_BEGIN DATE,
PERIOD_END DATE)
RETURNS (HOST VARCHAR(15) CHARACTER SET NONE,
INCOMING_ALLOWED BIGINT,
INCOMING_REJECTED BIGINT,
OUTGOING BIGINT)
AS

begin
for select destination, sum(bytes) from transfer_log
where outgoing='eth0' and incoming='ppp0' and prefix='allow'
and log_date>=:period_begin and log_date<=:period_end
group by destination
into :host, :incoming_allowed
do
begin
select sum(bytes) from transfer_log
where outgoing='eth0' and incoming='ppp0' and prefix='drop'
and log_date>=:period_begin and log_date<=:period_end and destination=:host
into :incoming_rejected;
select sum(bytes) from transfer_log
where outgoing='ppp0' and incoming='eth0'
and log_date>=:period_begin and log_date<=:period_end and source=:host
into :outgoing;
suspend;
end
end ^

CREATE PROCEDURE PROXY_TRAFFIC_BY_HOSTS (PERIOD_BEGIN DATE,
PERIOD_END DATE)
RETURNS (HOST VARCHAR(15) CHARACTER SET NONE,
TOTAL BIGINT,
CACHED BIGINT)
AS

begin
for select client_address, sum(total_bytes), sum(cache_bytes) from proxy_log
where log_date>=:period_begin and log_date<=:period_end
group by client_address
into :host, :total, :cached
do
begin
suspend;
end
end ^

CREATE PROCEDURE REJECTED_INNER_TRAFFIC (PERIOD_BEGIN DATE,
PERIOD_END DATE)
RETURNS (PROTOCOL SMALLINT,
SOURCE VARCHAR(15) CHARACTER SET NONE,
SOURCE_PORT INTEGER,
DESTINATION VARCHAR(15) CHARACTER SET NONE,
DESTINATION_PORT INTEGER,
BYTES BIGINT)
AS

begin
for select protocol, source, source_port, destination, destination_port,
sum(bytes) from transfer_log
where outgoing='eth0' and incoming='ppp0' and prefix='drop'
group by protocol, source, source_port, destination, destination_port
order by 6 desc
into :protocol, :source, :source_port, :destination, :destination_port, :bytes
do
begin
suspend;
end
end ^

CREATE PROCEDURE REJECTED_OUTER_TRAFFIC (PERIOD_BEGIN DATE,
PERIOD_END DATE)
RETURNS (PROTOCOL SMALLINT,
SOURCE VARCHAR(15) CHARACTER SET NONE,
SOURCE_PORT INTEGER,
DESTINATION VARCHAR(15) CHARACTER SET NONE,
DESTINATION_PORT INTEGER,
BYTES BIGINT)
AS

begin
for select first 25 protocol, source, source_port, destination, destination_port,
sum(bytes) from transfer_log
where outgoing<>'ppp0' and incoming='ppp0' and prefix='drop'
group by protocol, source, source_port, destination, destination_port
order by 6 desc
into :protocol, :source, :source_port, :destination, :destination_port, :bytes
do
begin
suspend;
end
end ^

SET TERM ; ^

COMMIT WORK ;

GRANT SELECT ON PROXY_LOG TO USER OPERATOR;
GRANT SELECT ON TRANSFER_LOG TO USER OPERATOR;
GRANT EXECUTE ON PROCEDURE FULL_TRAFFIC TO USER OPERATOR;
GRANT EXECUTE ON PROCEDURE NAT_TRAFFIC_BY_HOSTS TO USER OPERATOR;
GRANT EXECUTE ON PROCEDURE PROXY_TRAFFIC_BY_HOSTS TO USER OPERATOR;
GRANT EXECUTE ON PROCEDURE REJECTED_INNER_TRAFFIC TO USER OPERATOR;
GRANT EXECUTE ON PROCEDURE REJECTED_OUTER_TRAFFIC TO USER OPERATOR;

COMMIT WORK ;

CREATE TABLE TRANSLATION (LANGUAGE VARCHAR(10),
OBJECT_TYPE VARCHAR(25),
SYSTEM_NAME VARCHAR(256),
TRANSLATED_NAME VARCHAR(256) CHARACTER SET WIN1251);

INSERT INTO TRANSLATION (LANGUAGE, OBJECT_TYPE, SYSTEM_NAME, TRANSLATED_NAME) VALUES ('RU', 'PROCEDURE', 'FULL_TRAFFIC', 'Общее потребление трафика');
INSERT INTO TRANSLATION (LANGUAGE, OBJECT_TYPE, SYSTEM_NAME, TRANSLATED_NAME) VALUES ('RU', 'COLUMN', 'INCOMING_ALLOWED', 'Входящий разрешенный');
INSERT INTO TRANSLATION (LANGUAGE, OBJECT_TYPE, SYSTEM_NAME, TRANSLATED_NAME) VALUES ('RU', 'COLUMN', 'INCOMING_REJECTED', 'Входящий запрещенный');
INSERT INTO TRANSLATION (LANGUAGE, OBJECT_TYPE, SYSTEM_NAME, TRANSLATED_NAME) VALUES ('RU', 'COLUMN', 'OUTGOING', 'Исходящий');
INSERT INTO TRANSLATION (LANGUAGE, OBJECT_TYPE, SYSTEM_NAME, TRANSLATED_NAME) VALUES ('EN', 'PROCEDURE', 'FULL_TRAFFIC', 'General traffic consumption');
INSERT INTO TRANSLATION (LANGUAGE, OBJECT_TYPE, SYSTEM_NAME, TRANSLATED_NAME) VALUES ('RU', 'COLUMN', 'SYSTEM_NAME', 'Системное имя');
INSERT INTO TRANSLATION (LANGUAGE, OBJECT_TYPE, SYSTEM_NAME, TRANSLATED_NAME) VALUES ('EN', 'COLUMN', 'INCOMING_REJECTED', 'Incoming rejected');
INSERT INTO TRANSLATION (LANGUAGE, OBJECT_TYPE, SYSTEM_NAME, TRANSLATED_NAME) VALUES ('EN', 'COLUMN', 'OUTGOING', 'Outgoing');
INSERT INTO TRANSLATION (LANGUAGE, OBJECT_TYPE, SYSTEM_NAME, TRANSLATED_NAME) VALUES ('RU', 'COLUMN', 'TRANSLATED_NAME', 'Описание');
INSERT INTO TRANSLATION (LANGUAGE, OBJECT_TYPE, SYSTEM_NAME, TRANSLATED_NAME) VALUES ('EN', 'COLUMN', 'SYSTEM_NAME', 'System name');
INSERT INTO TRANSLATION (LANGUAGE, OBJECT_TYPE, SYSTEM_NAME, TRANSLATED_NAME) VALUES ('EN', 'COLUMN', 'TRANSLATED_NAME', 'Description');
INSERT INTO TRANSLATION (LANGUAGE, OBJECT_TYPE, SYSTEM_NAME, TRANSLATED_NAME) VALUES ('EN', 'COLUMN', 'INCOMING_ALLOWED', 'Incomong allowed');
INSERT INTO TRANSLATION (LANGUAGE, OBJECT_TYPE, SYSTEM_NAME, TRANSLATED_NAME) VALUES ('EN', 'COLUMN', 'HOST', 'Host');
INSERT INTO TRANSLATION (LANGUAGE, OBJECT_TYPE, SYSTEM_NAME, TRANSLATED_NAME) VALUES ('RU', 'PROCEDURE', 'NAT_TRAFFIC_BY_HOSTS', 'Потребление NAT-трафика по хостам');
INSERT INTO TRANSLATION (LANGUAGE, OBJECT_TYPE, SYSTEM_NAME, TRANSLATED_NAME) VALUES ('EN', 'PROCEDURE', 'NAT_TRAFFIC_BY_HOSTS', 'NAT traffic consumption by hosts');
INSERT INTO TRANSLATION (LANGUAGE, OBJECT_TYPE, SYSTEM_NAME, TRANSLATED_NAME) VALUES ('RU', 'COLUMN', 'HOST', 'Хост');
INSERT INTO TRANSLATION (LANGUAGE, OBJECT_TYPE, SYSTEM_NAME, TRANSLATED_NAME) VALUES ('EN', 'PROCEDURE', 'PROXY_TRAFFIC_BY_HOSTS', 'Proxy traffic consumption by hosts');
INSERT INTO TRANSLATION (LANGUAGE, OBJECT_TYPE, SYSTEM_NAME, TRANSLATED_NAME) VALUES ('RU', 'PROCEDURE', 'PROXY_TRAFFIC_BY_HOSTS', 'Потребление proxy-трафика по хостам');
INSERT INTO TRANSLATION (LANGUAGE, OBJECT_TYPE, SYSTEM_NAME, TRANSLATED_NAME) VALUES ('EN', 'COLUMN', 'TOTAL', 'Total');
INSERT INTO TRANSLATION (LANGUAGE, OBJECT_TYPE, SYSTEM_NAME, TRANSLATED_NAME) VALUES ('EN', 'COLUMN', 'CACHED', 'Cached');
INSERT INTO TRANSLATION (LANGUAGE, OBJECT_TYPE, SYSTEM_NAME, TRANSLATED_NAME) VALUES ('RU', 'COLUMN', 'TOTAL', 'Общий');
INSERT INTO TRANSLATION (LANGUAGE, OBJECT_TYPE, SYSTEM_NAME, TRANSLATED_NAME) VALUES ('RU', 'COLUMN', 'CACHED', 'Из кэша');
INSERT INTO TRANSLATION (LANGUAGE, OBJECT_TYPE, SYSTEM_NAME, TRANSLATED_NAME) VALUES ('EN', 'PROCEDURE', 'REJECTED_OUTER_TRAFFIC', 'Rejected outer traffic');
INSERT INTO TRANSLATION (LANGUAGE, OBJECT_TYPE, SYSTEM_NAME, TRANSLATED_NAME) VALUES ('RU', 'PROCEDURE', 'REJECTED_OUTER_TRAFFIC', 'Запрещенный внешний трафик');
INSERT INTO TRANSLATION (LANGUAGE, OBJECT_TYPE, SYSTEM_NAME, TRANSLATED_NAME) VALUES ('EN', 'COLUMN', 'PROTOCOL', 'Protocol');
INSERT INTO TRANSLATION (LANGUAGE, OBJECT_TYPE, SYSTEM_NAME, TRANSLATED_NAME) VALUES ('EN', 'COLUMN', 'SOURCE', 'Source');
INSERT INTO TRANSLATION (LANGUAGE, OBJECT_TYPE, SYSTEM_NAME, TRANSLATED_NAME) VALUES ('EN', 'COLUMN', 'SOURCE_PORT', 'Source port');
INSERT INTO TRANSLATION (LANGUAGE, OBJECT_TYPE, SYSTEM_NAME, TRANSLATED_NAME) VALUES ('EN', 'COLUMN', 'DESTINATION', 'Destination');
INSERT INTO TRANSLATION (LANGUAGE, OBJECT_TYPE, SYSTEM_NAME, TRANSLATED_NAME) VALUES ('EN', 'COLUMN', 'DESTINATION_PORT', 'Destination port');
INSERT INTO TRANSLATION (LANGUAGE, OBJECT_TYPE, SYSTEM_NAME, TRANSLATED_NAME) VALUES ('EN', 'COLUMN', 'BYTES', 'Bytes');
INSERT INTO TRANSLATION (LANGUAGE, OBJECT_TYPE, SYSTEM_NAME, TRANSLATED_NAME) VALUES ('RU', 'COLUMN', 'PROTOCOL', 'Протокол');
INSERT INTO TRANSLATION (LANGUAGE, OBJECT_TYPE, SYSTEM_NAME, TRANSLATED_NAME) VALUES ('RU', 'COLUMN', 'SOURCE', 'Источник');
INSERT INTO TRANSLATION (LANGUAGE, OBJECT_TYPE, SYSTEM_NAME, TRANSLATED_NAME) VALUES ('RU', 'COLUMN', 'SOURCE_PORT', 'Порт источника');
INSERT INTO TRANSLATION (LANGUAGE, OBJECT_TYPE, SYSTEM_NAME, TRANSLATED_NAME) VALUES ('RU', 'COLUMN', 'DESTINATION', 'Назначение');
INSERT INTO TRANSLATION (LANGUAGE, OBJECT_TYPE, SYSTEM_NAME, TRANSLATED_NAME) VALUES ('RU', 'COLUMN', 'DESTINATION_PORT', 'Порт назначения');
INSERT INTO TRANSLATION (LANGUAGE, OBJECT_TYPE, SYSTEM_NAME, TRANSLATED_NAME) VALUES ('RU', 'COLUMN', 'BYTES', 'Количество байт');
INSERT INTO TRANSLATION (LANGUAGE, OBJECT_TYPE, SYSTEM_NAME, TRANSLATED_NAME) VALUES ('EN', 'PROCEDURE', 'REJECTED_INNER_TRAFFIC', 'Rejected inner traffic');
INSERT INTO TRANSLATION (LANGUAGE, OBJECT_TYPE, SYSTEM_NAME, TRANSLATED_NAME) VALUES ('RU', 'PROCEDURE', 'REJECTED_INNER_TRAFFIC', 'Запрещенный внутренний трафик');

COMMIT WORK;

GRANT SELECT ON TRANSLATION TO USER OPERATOR;

COMMIT WORK ;

Затем на сервере Firebird нужно выполнить команду:

/opt/firebird/bin/isql -i procedures.sql

После этого в файле networkconsole.properties исправить ui.view.traffic.translate=false на ui.view.traffic.translate=true и перезапустить Network Console.
На вкладке "Трафик" на панели инструментов нажать кнопку "Выбрать" и указать требуемый отчет:

Отчеты

Теперь в поле редактирования sql-запроса появится:

-- Потребление proxy-трафика по хостам
select * from proxy_traffic_by_hosts('11.04.2004','11.07.2004')


Этот запрос можно выполнить обычным образом, нажав кнопку "Обновить".

Аналогичным образом можно создавать в базе данных любые отчеты в виде хранимых процедур, принимающих в качестве параметров даты начала и окончания перода, и их описаний в таблице TRANSLATION. Вносить изменения в Network Console для поддержки этих отчетов не требуется.

2.3.2. Реализация
графического интерфейса

В состав архива с исходными кодами Network Console включены все необходимы библиотеки, поэтому ничего, кроме стандартного JDK, для сборки не потребуется. Для сборки необходимо отредактировать файл build.sh (или build.bat) и выполнить его с параметром distrib-crossplatform.

Для отрисовки графического интерфейса Network Console использует SWT/JFace вместо стандартной для Java библиотеки Swing. Основное отличие SWT/JFace от Swing - использование стандартных графических библиотек той операционной системы, под управлением которой исполняется приложение. Например, под Windows Network Console использует Win23 API, а под Linux - GTK2 или OpenMotif. Помимо увеличения производительности такой подход позволяет Network Console выглядеть стандартным образом в различных средах и использовать настройки внешнего вида той среды, в которой она работает. Отрицательная стророна такого подхода - использование механизма JNI для подключения графических библиотек и, как следствие, худшая переносимость по сравнению со Swing, который не использует никаких посторонних библиотек, а рисует виджеты собственными средствами.

Приводить здесь листинг всех исходных кодов Network Console не имеет смысла, лучше ограничиться общим описанием пакетов и классов.

Основным пакетом является networkconsole.datamodel. Диаграмма, на которой изображены классы и интерфейсы пакета и связи между ними, выглядит так:

Диаграмма

В пакете существуют 2 иерархии: потомки абстрактных классов Entry (предствляет запись, которая может храниться как в LDAP, так и в JDBC) и EntryList (представляет набор таких записей). Потомки LdapEntry имеет фиксированное число полей (для каждого поля определены методы set/get/describe), а количество полей JdbcEntry заранее не известно. Значения полей потомков LdapEntry можно модифицировать. Потомки EntryList умеют оповещать о своих изменениях с помощью интерфейса IEntryListAdapter.

Примеры использования классов пакета networkconsole.datamodel можно найти в пакете networkconsole.tests.

Следующие пакеты используют классы networkconsole.datamodel для решения своих задач:

Пакет
Назначение
networkconsole.datamodel.persistance
Сохранение/извлечение потомков LdapEntry в/из LDAP посредством фабрик JNDI
networkconsole.datamodel.sax
Представление потомков LdapEntryList и JdbcEntryList в виде потока событий SAX для использования в JAXP
networkconsole.datamodel.swt.dialogs
Предоставление диалоговых окон для экспорта и редактирования потомков LdapEntryList и JdbcEntryList
networkconsole.datamodel.swt.providers
Предоставление провайдеров JFace отображения потомков LdapEntryList и JdbcEntryList в TableViewer

Два последних пакета ориентированы на использование networkconsole.datamodel в десктопном приложении на основе SWT/JFace, но ничто не мешает создать аналогичные пакеты для использования классов networkconsole.datamodel в приложении с web-интерфейсом или интерфейсом на основе Swing.

Пакет networkconsole.swt.extensions расширяет возможности SWT/JFace, добавляя поддержку еще одного провайдера - ITableColumnProvider - для управления колонками TableViewer.

Наконец, в пакете networkconsole.ui находится основной код Network Console - диалоговое окно для ввода параметров подключения, главное окно приложения, вкладки, окно выбора готовых отчетов об использовании трафика.

Такое разделение на пакеты/классы позволяет локализовать внесение изменений, не затрагивая те модули, для которых эти изменения не принципиальны. Например, чтобы добавить поддержку еще одного параметра для хоста, нужно исправить только класс LdapHostEntry и 2 фабрики из networkconsole.datamodel.persistance: LdapHostObjectFactory и LdapHostStateFactory, при этом основной код Network Console править не нужно.

3. Итоги


Главное - не конкретные решения, а технология. Предложенные в статье решения применимы без изменений для довольно узкого круга задач. Но описанная технология в любом случае позволяет упростить администрирование небольших локальных сетей. Та же технология с некоторыми изменениями (фактически они сведутся к увеличению настроек, хранимых в LDAP, поддержке новых сервисов и, как следствие, к модификации Network Console) может быть использована для управления более крупными сетями.

Приложение А. Подключение репозитория nm для пользователей ALT Linux

Для подключения репозитория nm необходимо загрузить файл nm-repositiry.tar.bz2 и извлечь его содержимое в любой каталог (например, /home/public/distrib/nm-repository), а затем вписать следующие строки в файл /etc/apt/sources.list:

rpm file:/home/public/distrib/nm-repository i586 nm
rpm-src file:/home/
public/distrib/nm-repository i586 nm

и выполнить команду apt-get update.

Приложение Б. Ссылки

1. http://ldapzone.spb.ru - единственный в рунете сайт, целиком посвященный использованию LDAP
2. http://www.altlinux.ru, http://www.atmsk.ru, http://www.linux-os.ru - сайты, на которых можно найти описание специфики дистрибутивов ALT Linux
3. http://www.eclipse.org - официальный сайт Java-платформы Eclipse, составной частью которой является SWT/JFace