LINUX.ORG.RU

Девелоперы гуру, нужна помощь по переделке модуля ядра....


0

0

Вот есть у меня модуль для фильтра пакетов ядра, называется ipt_quota.c. В нём вот есть такое содержимое, (оно немного отличается от оригинала, я там немного подправлял):
/*
* netfilter module to enforce network quotas
*
* Sam Johnston <samj@samj.net>
*/
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/spinlock.h>
#include <linux/interrupt.h>

#include <linux/netfilter_ipv4/ip_tables.h>
#include <linux/netfilter_ipv4/ipt_quota.h>

MODULE_LICENSE("GPL");

static spinlock_t quota_lock = SPIN_LOCK_UNLOCKED;

static int
match(const struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
const void *matchinfo,
int offset, const void *hdr, u_int16_t datalen, int *hotdrop)
{
int totlen;
int ret;
struct ipt_quota_info *q = (struct ipt_quota_info *) matchinfo;

spin_lock_bh(&quota_lock);

totlen = ntohs(skb->nh.iph->tot_len);

if (q->quota > totlen) {
/* we can afford this one */
q->quota -= totlen;
spin_unlock_bh(&quota_lock);

#ifdef DEBUG_IPT_QUOTA
printk("IPT Quota OK: %llu datlen %d \n", q->quota, datalen);
#endif
return 1;
}
//////////////////////////////////////////////////////////////////////
///// if (q->quota > 0) { /////
///// Вот тут мне нужно добавить запуск внешней программы /////
///// } /////
//////////////////////////////////////////////////////////////////////
/* so we do not allow even small packets from now on */
q->quota = 0;

#ifdef DEBUG_IPT_QUOTA
printk("IPT Quota Failed: %llu datlen %d \n", q->quota, datalen);
#endif
spin_unlock_bh(&quota_lock);
return 0;
}

static int
checkentry(const char *tablename,
const struct ipt_ip *ip,
void *matchinfo, unsigned int matchsize, unsigned int hook_mask)
{
/* TODO: spinlocks? sanity checks? */
if (matchsize != IPT_ALIGN(sizeof (struct ipt_quota_info)))
return 0;

return 1;
}

static struct ipt_match quota_match
= { {NULL, NULL}, "quota", &match, &checkentry, NULL, THIS_MODULE };

static int __init
init(void)
{
return ipt_register_match(&quota_match);
}

static void __exit
fini(void)
{
ipt_unregister_match(&quota_match);
}

module_init(init);
module_exit(fini);


Вот в выделенном мной месте (выделял я слэшами) нужно как-то добавить запуск другой программы, прям здесь можно путь к ней задать и имя её. Я уже две недели промучился, никак не получается, из за того что это модуль, ни fork, ни exec не работает. Помогите, не поленитесь плиз.

anonymous

RE:

2.4?
call_usermodehelper (linux/kernel/kmod.c).

Murr ★★
()

Вообще-то подход не верный... Правильнее сделать через /dev/mydev,
т.е. твой модуль будет кидать послания на девайс, а демон, который
ты напишешь, будет читать с девайса и делать запуск каких угодно
программ...

Смотри примеры на http://www.tldp.org/LDP/lkmpg/
что то типа этого подойдет http://www.tldp.org/LDP/lkmpg/x571.html#AEN692

McMCC ★★★
()

>2.4? >call_usermodehelper (linux/kernel/kmod.c).

2Murr: Можно конечно и так, но кернел паник легко можно устроить, лучше через /dev, безопаснее будет...

McMCC ★★★
()

RE:

McMCC: упсс... да, что-то я не сообразил, что эта функция в контексте bottom half дергается. Да, надо аккуратно, можно свою нить вроде keventd завести и добавлять ей в очередь, защитив spin irq...

На 99% это будет cut&paste (+надо будет убрать wait_for_completion) ;)

Murr ★★
()

RE:

Единственное - не очень могу понять чего товарищ пытается добиться :/

Murr ★★
()

RE:

MCMCC: или все же таки она в контексте softirq дергается? (я в сети не силен)...

Murr ★★
()

я пытаюсь добиться того, чтобы когда квота для доступа в сеть internet закончится, то есть, когда последний пакет правило iptables пропустит, запускалась программа, которая отключит у этого пользователя VPN. То есть запустится прграмма, которая по всем соединениям пройдётся, найдёт у кого квота 0 и убьёт это соединение, можно конечно каждые 5-10 сек/мин проверять, но мне это не подходит, в одном случае (5 сек) ресурсов проца много жрётся (около 100 соеинений проверять надо) в другом случае (5 минут) слишком редко.

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

anonymous
()

Ядро у меня 2.4.21

anonymous
()

RE:

Попробуй вставить

{
char my_path[256] = "/sbin/modprobe";
char * envp[] = {"HOME=/", "TERM=linux", "PATH=/sbin:/usr/sbin:/bin:/usr/bin", 0};
char *argv[] = {my_path, 0};
int ret;

call_usermodehelper(my_path, argv, envp);
}

вместо modprobe что-нибудь свое напиши.

P.S. тут только один момент - если ты не в контексте softirq (а в
 контексте bottom half), то так делать нельзя.

Murr ★★
()

почему это "ни fork, ни exec не работает", а как же тогда процесс init запускается, не работают соответствующие системные вызовы это да, но есть соответствующие функции "для внутреннего пользования" в ядре, пример в файле init/main.c исходников ядра. Cначала в функции start_kernel запускается новый поток ядра с помощью kernel_thread(init,NU.... , в этом потоке выполняется функция init. в этой функций после всяких иницивализаций ядро пытается сделать exec программы init с помощью функции execve. наверно это самый простой способ, однако это не самое разумное решение с точки зрения безопасности, и через демона наверное правильнее.....

Whaler
()

RE:

Whaler: в контексте keventd безопасно. Вот так, как это делал kmod - через request_module - это действительно было небезопасно, поэтому его быстро продрючили (т.н. kmod race).

Murr ★★
()

Murr: Я не знаю в каком я контексте (я писал про знания Си). Но я сделал как ты посоветовал (только добавил в заголовок #include <linux/kmod.h> так как без этой дерективы не компилировалось, писалось что функция call_usermodehelper<дальше какие-то цифры без пробела> не декларирована, когда это добавил всё скомпилилось) и при квоте ноль наступал kernel-panic. Видно всё таки в моём случае так нельзя. Программу я для тестов внешнюю запускал, простую, которая два числа складывает, в ней ошибок нет. Может тогда подскажите как сделать с демоном, то есть записать в файл из ядра, а лучше наверно сразу демону сигнал послать. помогите пожалуйста, действительно надо...

anonymous
()

RE:

Понятно... ну концептуально то схема рабочая... Я её только что прогнал - из top half всё работает.

Если хочешь пойти по принципу наименьшего сопротивления, то можешь завести еще одну нить и глобальный флажок, скажем atomic_t my_flag, и при достижении нулевой квоты делать в bottom half atomic_inc(&my_flag), в нити проверять atomic_value и делать call_usermodehelper если значение ненулевое, после чего atomic_set(&my_flag, 0), вроде:

atomic_t my_flag;
int my_finish = 0;

int my_thread (void * data) {
while (!my_finish) {
if (atomic_value (&my_flag)) {
call_usermodehelper (...);
atomic_dec(&my_flag);
}
schedule ();
}
my_finish = -1; // если ядро невытесняемое, то rmmod может не ждать на этой нитке, а вполне достаточно флага
}

int init () {
...
atomic_set (&my_flag, 0);
kernel_thread (my_thread, 0, CLONE_VFORK|SIGCHLD);
...
}

void fini () {
...
my_finish = 1;
while (my_finish != -1) schedule ();
...
}

P.S. Для красоты надо, конечно, вместо schedule() делать set_task_state(TASK_INTERRUPTIBLE), а потом будить из прерывания, но пусть хотя бы так сначала заработается ;)

Murr ★★
()

Через девайс довольно просто делается:

в начале твоего модуля, перед MODULE_LICENSE
вставляем:
#include <linux/kernel.h>
#include <linux/fs.h>
#include <asm/uaccess.h>

#define DEVICE_NAME "mydev"

static int device_open(struct inode *, struct file *);
static int device_release(struct inode *, struct file *);
static ssize_t device_read(struct file *, char *, size_t, loff_t *);
static ssize_t device_write(struct file *, const char *, size_t, loff_t *);

static struct file_operations fops = {
          .read = device_read,
          .write = device_write,
          .open = device_open,
          .release = device_release
};

static int major;
static char msg[256];
static char *msg_ptr;
u_int64_t count = 0;
.......

Далее, после static spinlock_t quota_lock = SPIN_LOCK_UNLOCKED
вставляем:
static int dev_init(void)
{
  major = register_chrdev(0, DEVICE_NAME, &fops);
  return major;
}

static void dev_exit(void)
{
  int ret = unregister_chrdev(major, DEVICE_NAME);
  if (ret < 0) printk("Error in unregister_chrdev: %d\n", ret);
}


static int device_open(struct inode *inode, struct file *file)
{
  sprintf(msg,"%llu", count);
  msg_ptr = msg;
  return 0;
}

static int device_release(struct inode *inode, struct file *file)
{
  return 0;
}

static ssize_t device_read(struct file *f, char *buf, size_t len, loff_t *off)
{
  int bytes_read = 0;

  if (*msg_ptr == 0) return 0;

  while (len && *msg_ptr)  {
      put_user(*(msg_ptr++), buf++);
      len--;
      bytes_read++;
  }

  return bytes_read;
}

static
ssize_t device_write(struct file *f, const char *buf, size_t len, loff_t *off)
{
  return -EINVAL;
}
.......
Затем за место твоего  if (q->quota > 0) {...}
вставляем одну строчку:
count = q->quota;

и добавляем в 
 static int __init 
init(void) 
{ 
 int num = dev_init();
 if (num < 0) {
     printk ("Registering the character device failed with %d\n", num);
  }
 return ipt_register_match(&quota_match); 
} 
 
static void __exit 
fini(void) 
{ 
 dev_exit();
 ipt_unregister_match(&quota_match); 
} 
 
Теперь создаем сам девайс:
mknod /dev/mydev c 254 0

В результате в /dev/mydev должно скидываться значение
q->quota, которое должно читаться обычным cat'ом, т.е.
cat /dev/mydev должен показывать значение q->quota,
вообщем, читать девайс можно как угодно и чем угодно....

McMCC ★★★
()

McMCC:
Большое спасибо за такой подробный код, сегодня приду попробую. Только есть вопросики:
1. мне достаточно знать только один раз что квота стала нулём у какого нибудь ip (после того как я отключу VPN он все равно уже подключиться не сможет, для этого я пишу if (q->quota > 0) {...}, так как там выше есть if (q->quota > totlen) {... return 1} - по идеи при выполнении этого кода, так как стоит return 1 то до моей проверки не должно дойти - я могу ошибаться так как с Си не знаком - а следовательно когда квота меньше длины пакета, но больше нуля этот момент будет счиаться что наступает ноль, так как дальше квота всегда будет нулевая), может всё таки можно написать:
if (q->quota > 0)
{
count = q->quota
};
или это гразит не высвобождением памяти или ещё чем.
Для меня чем код быстрее выполняется тем лучше (то есть желательно всё что вы для меня написали дополнительно выполнялось только в момент if (q->quota > 0, просто в файле какой нить флажок ставился, что 0 наступил у кого-то), а в другие моменты всё работало как есть), так как на маршрутизации слабенькая машина стоит.
Формат данных, как они будут писаться в /dev/mydev я приду, вставлю ваш код и посмотрю.
2. Смогу ли я после чтения этого устройства, удалить то что там в /dev/mydev есть, хотя в принцепе, приду домой, попробую.

Murr: А вы не могли бы поподробнее написать что, где вставлять, примерно как у McMCC.

Резюме: Ни сколечки не жалею, что переделал маршруизатор под Линукс. Не ожидал я, что смогу получить от людей, любящих линукс, такую квалифицированную помощь, что не пожалеют на мои вопросы, наверное глупые для человека разбирающемся в Си + Linux, времени. Я вам очень благодарен.

Предистория:
У нас в общаге решили мы себе инет завести, но вот я и взялся за маршрутизацию и счёт трафика на добровольных началах, так как я единственный кто в win2000 разбирался хорошо. Месяц назад переделал всё под линукс, настроил, простенький билинг на bash скриптах написал (всякие там проверки при коннекте, складывание в базу и т.д.) и чтоб статистику отдовать использовал php, ни то не другое я не знал, да и щас не могу сказать что знаю, и вот возникла такая проблемка, с наскоку не удалось в Си разобраться и в ядре покавыряться, хотя в том же iptables я ULOG немного переделал, вроде работает. Вот обращаюсь сюда.

Большое вам спасибо!!! Совсем по другому здесь относятся к людям, просящим о помощи.

Андрей

anonymous
()

небольшая поправочка в функции-ните, заменяемой потом Вашем приложением, надо открыть стандарные потоки ввода/вывода, ну это есть в той же функции init.

Whaler
()

RE:

/*
* netfilter module to enforce network quotas
*
* Sam Johnston <samj@samj.net>
*/
#define __KERNEL__
#define MODULE
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kmod.h>
#include <linux/sched.h>
#include <linux/skbuff.h>
#include <linux/spinlock.h>
#include <linux/interrupt.h>

#include <linux/netfilter_ipv4/ip_tables.h>
#include <linux/netfilter_ipv4/ipt_quota.h>

static struct task_struct * my_thread_tsk = 0;
static int my_thread_exit_flag = 0;

static spinlock_t quota_lock = SPIN_LOCK_UNLOCKED;

static int match(const struct sk_buff *skb, const struct net_device *in,
                const struct net_device *out, const void *matchinfo,
                  int offset, const void *hdr, u_int16_t datalen, int *hotdrop) {

    int totlen;
    int ret;
    struct ipt_quota_info *q = (struct ipt_quota_info *) matchinfo;

    spin_lock_bh(&quota_lock);

    totlen = ntohs(skb->nh.iph->tot_len);

    if (q->quota > totlen) {
        /* we can afford this one */
        q->quota -= totlen;
        spin_unlock_bh(&quota_lock);

#ifdef DEBUG_IPT_QUOTA
        printk("IPT Quota OK: %llu datlen %d \n", q->quota, datalen);
#endif
        return 1;
    }
    if (q->quota > 0) {
        if (my_thread_tsk) // AP: yes, it cannot be zero, but still we are wary ...
            wake_up_process (my_thread_tsk);
    }

    /* so we do not allow even small packets from now on */
    q->quota = 0;

#ifdef DEBUG_IPT_QUOTA
    printk("IPT Quota Failed: %llu datlen %d \n", q->quota, datalen);
#endif
    spin_unlock_bh(&quota_lock);
    return 0;
}

static int checkentry(const char *tablename, const struct ipt_ip *ip, void *matchinfo, unsigned int matchsize, unsigned int hook_mask) {
    /* TODO: spinlocks? sanity checks? */
    return (matchsize == IPT_ALIGN(sizeof (struct ipt_quota_info)));
}

static struct ipt_match quota_match = {{NULL, NULL}, "quota", &match, &checkentry, NULL, THIS_MODULE};

static inline int my_thread (void * unused) {

    daemonize ();
    reparent_to_init ();
    strcpy (current->comm, "quotad"); // AP: change the name to whatever you want your ps to show ;)

    while (!my_thread_exit_flag) {
        set_task_state (current, TASK_UNINTERRUPTIBLE); // AP: TASK_UNINTERRUPTIBLE for some stupid not to wake us with a signal
        schedule ();
        /* AP: I assume here we have a small noncritical race with both signal from match() and my_thread_exit_flag
                force exit in that case */
        if (!my_thread_exit_flag) {
            char my_path[256] = "/sbin/modprobe";
            char * envp[] = {"HOME=/", "TERM=linux", "PATH=/sbin:/usr/sbin:/bin:/usr/bin", 0};
            char *argv[] = {my_path, 0};

            call_usermodehelper(my_path, argv, envp);
        }
    }
    my_thread_exit_flag = 0;
    return 0;
}

static inline void my_thread_done () {
    my_thread_exit_flag = 1;
    wake_up_process (my_thread_tsk);
    while (my_thread_exit_flag) // AP: Polling is the best case here, i suppose
        schedule ();
}

static int __init init(void) {
    int rc;
    pid_t m;

    m = kernel_thread (my_thread, 0, SIGCHLD);
    if (m<0)
        return -EAGAIN;

    my_thread_tsk = find_task_by_pid (m);
    if (my_thread_tsk == 0)
        return -EAGAIN;

    rc = ipt_register_match(&quota_match);

    if (rc<0) {
        my_thread_done ();
        return rc;
    }

    return 0;
}

static void __exit fini(void) {
    ipt_unregister_match(&quota_match);
    my_thread_done ();
}

module_init(init);
module_exit(fini);

MODULE_LICENSE("GPL");

Murr ★★
()

Из-за отсуствия соотв. *.h код не проверял, но вроде должен работать ;)

Murr ★★
()

Ещё раз спасибо. Спасибо большое тебе, Murr. Всё прекрасно работает. Только у меня вопрос (для самообразования) я правельно понимаю, что когда инициализирую модуль, то появляется тред, который постоянно висит и ждёт сигнала на запуск моей внешней программы?? спасибо большое и McMCC, только если не жалко немного времени, ответь на мои вопросики написанные выше пожалуйста, я запись в устройство по вашему примеру попробую адаптировать для другой задачки. И Whalerу спасибо, за то что не поленился и вникнул в проблему, просто приятно, что люди рады помоч.

З.Ы. МсМСС и Murr, я с удовольствием вам по баклашке пива куплю, и если вы живёте в Москве, то не поленюсь и доставлю его вам прям на дом ;-)

Ещё раз всем большое спасибо.

Andrey

anonymous
()

Murr

ipt_quota.c:6:1: warning: "__KERNEL__" redefined
<command line>:1:1: warning: this is the location of the previous definition
ipt_quota.c:7:1: warning: "MODULE" redefined
<command line>:1:1: warning: this is the location of the previous definition
ipt_quota.c:101: warning: function declaration isn't a prototype

Это я выписал то, чем отличаются сообщения когда компилируешь без вставки твоего кода и со вставкой, то есть написанное выше добовляется когда компилируешь код, в который вставлен твой код. Посмотри, всё ли в порядке. Просто меня талкнуло это выписать, так как когда я первый раз компилил, там вывелось, что переменная ret описана, но нигде не используется, её описание я убрал и на всякий случай выписал, то, что ещё пишет компилятор.

anonymous
()

RE:

ipt_quota.c:6:1: warning: "__KERNEL__" redefined
<command line>:1:1: warning: this is the location of the previous definition
ipt_quota.c:7:1: warning: "MODULE" redefined
<command line>:1:1: warning: this is the location of the previous definition
ipt_quota.c:101: warning: function declaration isn't a prototype

Не надо при компиляции модуля передавать -DMODULE -D__KERNEL__
#define должны быть определены в исходном коде, а не опциях компиляции.

Для меня это золотое правило. Если Вам не нравится - передавайте -D в командной строке. Это исключительно дело вкуса.

В чем претензии то?

Murr ★★
()
Ответ на: комментарий от anonymous

RE:

>Только у меня вопрос (для самообразования) я правельно понимаю, что когда инициализирую модуль, то появляется тред, который постоянно висит и ждёт сигнала на запуск моей внешней программы??

есть т.н. утилиты modutils (modprobe и insmod). insmod осуществляет загрузку модуля. Для этого он одним системным вызовом выделяет в ядре память (create_module->vmalloc), выполняет relocation и загружает туда модуль (init_module), init_module вызывает функцию init модуля (т.е. вся эта байда происходит в контексте insmod).

в init я создаю нить, которая спит, но иногда мы её будим и она запускает нужный нам процесс (мы не можем запускать процесс их match, поскольку match работает в контексте bottom half, в котором нельзя ждать завершения call_usermodehelper).

Murr ★★
()

RE:

Это одно из отличий Linux и "эталонного" Solaris. В Solaris под прерывание "подкладывается" нить из пула системных с высоким приоритетом, поэтому в прерывании всегда можно спать. В Linux, BSD, WinNT это не так - у прерывания нет контекста, поэтому в нём нельзя ждать. Чтобы выполнить некую операцию, которая будет спать, её обычно помещают в очередь, которую разгребают системные нити (будь то softirq/keventd в Linux или system worker threads в NT, etc).

Murr ★★
()

Murr <В чем претензии то?>
нет у меня никаких притензий, да и откудова они могут быть у человека, который линукс в глаза видит всего месяц, к вам. Я вообще удивляюсь, как вы так смогли написать сразу правильный код без ошибок, не пробуя его в действии и не исправляя его в ходе компиляции. А выписал я данный текст на всякий случай, влруг вы что нить увидите (вы же не компилировали сами, вдруг вы где ошиблись и по сообщениям увидите это, а для меня весь текст предупреждений ничего не значит). Компилировал модуль я не отдельно, а таким способом:
make modules для ядра, а потом в ручную нужный мне модуль копировал в /lib/..., поэтому в параметрах компиляции я ничего не писал.(как крмпилировать модули по отдельности я не знаю).

Вопросы:
1. если я компилирую таким способом (make modules), мне значит надо убрать:
#define __KERNEL__
#define MODULE
2. Что значит:
ipt_quota.c:101: warning: function declaration isn't a prototype


Ещё раз большое вам спасибо, у меня всё работает не смотря на эти предупреждения.

anonymous
()
Ответ на: комментарий от anonymous

RE:

>нет у меня никаких притензий, да и откудова они могут быть у человека, который линукс в глаза видит всего месяц, к вам. 
Извини, просто нервы... ))

> Компилировал модуль я не отдельно, а таким способом:
>make modules для ядра, а потом в ручную нужный мне модуль копировал в >/lib/..., поэтому в параметрах компиляции я ничего не писал.(как >крмпилировать модули по отдельности я не знаю).

>Вопросы:
>1. если я компилирую таким способом (make modules), мне значит надо >убрать:
>#define __KERNEL__
>#define MODULE 
То есть ты модуль собираешь с ядрёным Makefile и Rules? А как ты Makefile правил? Нужно же ручками связать объектные файлы с config опциями ядра...

Я бы отдельно от ядра собирал модуль. Для инсталляции сделал бы что-то вроде маленького Makefile. Очень сильно упрощая:
all: ipt_quota.o

ipt_quota.o: ipt_quota.c 
     gcc -c ipt_quota.c -I/lib/modules/`uname -r`/build/include

install: ipt_quota.o
     cp ipt_quota.o /lib/modules/`uname -r`/kernel/net
     depmod

По поводу -D - да, это почти эквивалентно #define в начале исходного файла, так что делай как удобнее, просто в ядре есть неопределенность что собирать модулем, а что в ядро, поэтому -D имеет смысл. Для того, что будет определенно модулем и собирается отдельно от ядра, нагляднее задавать #define __KERNEL__ #define MODULE, IMHO.

Murr ★★
()

Makefile я думаю за меня правил патч patch-o-matic-20030107.tar.bz2, который я ставлю для iptables c http://www.netfilter.org/downloads.html так как при перекомпиляции модулей проблем нет, и то что вы написали для ipt_quota.c работает. Это патч для того, чтоб можно было использовать дополнительные правила в iptables. В нём и содержится модуль для ядра ipt_quota.c, только он, модуль, эту квоту считал не так как мне надо, и я его немного поправил его, а потом возникла вот такая идея с запуском внешней программы для отключения vpn при quota -> 0 и я обратился сюда. Большое спасибо.

anonymous
()

> 2. Что значит: > ipt_quota.c:101: warning: function declaration isn't a prototype

Я думаю если работает, то на это обращать не надо.

anonymous
()

Чисто из любопытства - а какой код в этой строчке?

Murr ★★
()

static inline void my_thread_done () { //это 101 строчка my_thread_exit_flag = 1; wake_up_process (my_thread_tsk); while (my_thread_exit_flag) // AP: Polling is the best case here, i suppose schedule (); } ничего страшного???

anonymous
()

static inline void my_thread_done () {// это 101 строчка
my_thread_exit_flag = 1;
wake_up_process (my_thread_tsk);
while (my_thread_exit_flag) // AP: Polling is the best case here, i suppose
schedule ();
}

Ничего страшного???

anonymous
()

Попробуй заменить static inline void my_thread_done () на static inline void my_thread_done(void). Кстати, а у my_thread надо inline убить ;).

Murr ★★
()

Могу в меру своего понимания объяснить как и что. UIN 97916607.

Murr ★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.