Построение сетевого окружения «чужих» сетей

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

Всё намного сложнее в домашних сетях: здесь многое определяется застройкой и часто нет возможности разродится звездообразной конфигурацией. Вот и ходят пользователи к друг другу через всякие сканы, NetView-веры — а что делать. Также часто невозможно всё сделать правильно из-за разнородности материального положения сетей: одна сеть разжилась сервером с DHCP, WINS, обозревателем на самбе, а в другой сети живут вообще без серверов, или, например, нет человека, хоть немного разбирающегося в сетевой «механике»: помогать такой сети никто не собирается — чай не райсобес, но соединиться «по-правильному» хотелось бы. Что делать?

А ответ в данном случае достаточно прост: надо научиться строить добавлять пользователей чужих сетей в сетевое окружение (сетевой обозреватель) и в службу WINS. Классическое построение сети предполагает наличие в таких сетях реплицирующихся WINS-серверов (samba4wins или WINS на Windows) и главного обозревателя сети (PDC) на самбе с включенным режимом enhanced browsing и настроенным remote browse sync. Однако, по описанным выше причинам, классика здесь неприменима 🙂

Однако, сама самба имеет встроенный нереплицируемый WINS и умеет выполнять роль PDC — всё в одном флаконе, удобно. Т.е. дело за малым — научить самбу добавлять новых пользователей из чужих сетей в сетевое окружение и WINS (без WINS-а тупой пользователь будет тыкать мышкой в хост, а IP адреса-то его никто знать не будет 🙂 ).

Вобщем, всё получилось, результат патча на скриншоте. newbrowser

За основу была взята самба 3.0.24-6etch4. Выяснить, имя и группу произвольного хоста в сети можно с помощью команды nmblookup -A <ip>, т.е. взяв за основу исходники nmblookup -A, можно построить нетбиос сканер.
Ф-ция на которой базируется nmblookup -A — node_status_query — находится в файле libsmb/namequery.c. В этой ф-ции надо подправить таймаут ожидания ответа с 2сек на 0.15сек, т.к. если реальный хост будет так «быстро» отвечать, то врядли он может кого-то заинтересовать 🙂

NODE_STATUS_STRUCT *node_status_query(int fd,struct nmb_name *name,
      struct in_addr to_ip, int *num_names,
      struct node_status_extra *extra)
{
 BOOL found=False;
 int retries = 2;
 int retry_time = 2000; // вот тут надо установить 150 млС
 struct timeval tval;
 struct packet_struct p;
 struct packet_struct *p2;
 struct nmb_packet *nmb = &p.packet.nmb;
 NODE_STATUS_STRUCT *ret;

Со сканером всё вроде понятно, теперь надо придумать, как полученные данные вписать сетевое окружение. Частенько можно встретить разные «замечательные» способы с исправлениями browse.dat, который формирует nmbd, а показывает smbd, но постараемя быть выше этого: стоит попробовать заставить nmbd сформировать правильный список сразу.
Списки пользователей nmbd формирует и хранит в памяти: nmbd только записывает файл browse.dat, но никогда не читает, т.е. надо найти точку внедрения в код. Внимание сразу же привлекает файл nmbd_incomingdgrams.c, а в нём ф-ция process_host_announce — это то, что доктор прописал. Из ф-ции видно, что хост добавляется след.образом:

  • 1. вначале в памяти ищется рабочая группа, которой принадлежит хост
    work = find_workgroup_on_subnet(subrec, work_name);
  • 2. если такой группы ещё нет, то создаём её
    // откат, если нулевой указатель
    if((work = create_workgroup_on_subnet(subrec, work_name, ttl))==NULL) goto done;
  • 3. по аналогии с группой, ищем имя хоста и если его нет, создаём в списке обозревателя новую запись
    if((servrec = find_server_in_workgroup( work, announce_name))==NULL)
    {
     create_server_on_workgroup(work, announce_name, 
     servertype|SV_TYPE_LOCAL_LIST_ONLY,ttl, comment);
    }
    else
    {
     servrec->serv.type = servertype|SV_TYPE_LOCAL_LIST_ONLY; // обновление типа хоста
     // обновляем время жизни записи, в своём коде впишем сюда "вечный" TTL
     update_server_ttl( servrec, ttl);
     fstrcpy(servrec->serv.comment,comment); // обновляем комментарий
    }

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

if(!is_myname(announce_name) && (work != NULL) &&
 ((servrec = find_server_in_workgroup( work, announce_name))!=NULL))
{
 remove_server_from_workgroup( work, servrec);
}

Этого всего уже достаточно для того, чтобы самим формировать список обозревателя сети, теперь надо освоить работу со службой WINS: работа с WINS через файл wins.dat представляется несколько ущербной, т.к. этот файл читается только при запуске nmbd, а в процессе работы только записывается. На самом деле записи WINS хранятся в tdb-файлах мини-БД, встроенной в самбу. К тому же придётся написать свою ф-цию ip >-> name, обратную работе WINS для удаления старых записей имён в списке обозревателя. После недолгих «фтыканий» находим ф-цию

add_name_to_subnet(wins_server_subnet,announce_name,nb_type,(uint16)NB_ACTIVE,
 ttl,REGISTER_NAME,ip_nums, ip_list);

Здесь announce_name — имя, регистрируемое в WINS, nb_type — тип нетбиос имени, далее идёт статус записи, время жизни, описание имени (доменное, винс и т.п.), ip_nums — кол-во адресов в массиве ip_list, которые относятся к имени. Удалять имя из WINS — вредно, поэтому на это заморачиваться не будем, ведём только накопительную деятельность 🙂  Регистрировать будем 2 типа записей: nb_type=0x00 и nb_type=0x20. Осталось разобраться с мини-БД самбы для реализации ip >-> name, и можно приступать к написанию патча на самбу.

Каталог TDB достаточно документирован для того, чтобы сообразить с первого подхода, как написать поиск имени по IP, поэтому код поиска по связанному списку приводится без предисловия: он прост.

BOOL find_name_by_ip(uint32 ip,fstring ntb_name)
{
    TDB_DATA            key, data, newkey;
    struct  name_record *namerec=NULL;

    for(key=tdb_firstkey(wins_tdb); // находим первый элемент в списке
         key.dptr; // проверяем, а не хвост ли это? он вообще указвает на что-либо?
         newkey=tdb_nextkey(wins_tdb,key),safe_free(key.dptr),key=newkey)
 // а текущий элемент указывает на следующий элемент
    {
        data=tdb_fetch(wins_tdb,key);
        if (data.dsize==0) continue;
        namerec=wins_record_to_name_record(key,data); // перевод записи в удобоваримый формат
        SAFE_FREE(data.dptr);
        if (!namerec) continue;
        if (namerec->data.num_ips)
        {
            DEBUG(9,("find_name_by_ip: %s ip=%s\n",namerec->name.name,inet_ntoa(*(namerec->data.ip)) ));
            if  (*((uint32*)namerec->data.ip)==ip)
         {
             pull_ascii_nstring(ntb_name,sizeof(fstring),namerec->name.name);
             SAFE_FREE(namerec->data.ip);\n             SAFE_FREE(namerec);
             return True;
         }
        }
        SAFE_FREE(namerec->data.ip);
        SAFE_FREE(namerec);
    }
    return False;
}

Всё, все составляющие для дополнения самбы готовы, процесс сканирования будет жить в отдельном треде и передавать данные в бесконечный рабочий цикл nmbd, т.к. ф-ции, которыми будем пользоваться — нереентерантны. В дополнение к скану снабдим самбу полезным свойством «нерегистрации» левых групп типа FRILAN вместо FREELAN, или S2LANE вместо S2LAN, которые создают тупые пользователи. Для этого немного изменим ф-цию create_workgroup в файле nmbd_workgroupdb.c дополнив её проверкой check_workgroup, которая говорит о полезности или вредности группы в списке.

DEBUG(4,("create_workgroup_on_subnet: creating group %s on subnet %s\\n",
 name, subrec->subnet_name));
if (!check_workgroup((char*)name)) return NULL;
if ((work = create_workgroup(name, ttl)))
{
 add_workgroup(subrec, work);

Также сделаем свой комментарий (адрес хоста) для найденного или анонсировавшего себя хоста.

void create_comment(char* name, uint32 ip, fstring cm_text)
{
    if (name[0]>0x20)
    {
        snprintf(cm_text,MAX_SERVER_STRING_LENGTH,"%s",
        inet_ntoa(*((struct in_addr*)&ip)) );
    }
}

Из текста патча, приведённого ниже, будет видно, что в секции [global] появляются два новых параметра scan net и allow groups. В параметр scan net вписываются все сети, которые надо сканировать  в формате сеть/маска, сети записываются через пробел или запятую, например:

scan net = 192.168.15.0/24 192.168.12.0/255.255.255.0 192.168.14.0/24

Если параметр scan net пуст, то фича сканирования не будет активизирована и самба будет работать, как «нормальная» самба без патча.

Разрешённые группы записываются в параметре allow groups через пробел или запятую, например

allow groups = OPENLAN FREELAN S2LAN LIFESTREAM

Если список пуст или параметр не задан, то доступны все сканированные или анонсировавшие себя группы.

И хотя фича сканирование задумывалась для дополнения «своего» списка самбы хостами из других подсетей, оказалось, что очень удобно сканировать и свою сеть: решаются проблемы «могильных камней» и становятся однотипными комментарии. Крайне не рекомендуется включать scan net  с выключенным своим WINS, надеюсь, что не надо объяснять почему 🙂

Собственно и всё. Скачать патчи для samba-3.0.24 можно здесь, а для samba-3.0.28aздесь.

Вот пример конфигурационного файла /etc/samba/smb.conf

[global]
        workgroup = S2LAN
        dos charset = CP1251
        server string = S2LAN-14 Router

        obey pam restrictions = Yes
        domain logons = No
        lm announce = Yes
        dns proxy = No
        ldap ssl = no

        os level = 124
        domain master = Yes
        local master = Yes
        preferred master = Yes

        wins support = yes

        scan net = 192.168.15.0/24 192.168.12.0/24 192.168.14.0/24
        allow groups = S2LAN VGU

        share modes = Yes
        security = share
        encrypt passwords = yes

        load printers = No
        show add printer wizard = No