Если там другое значение, то текущая ARP-строка сится к другому интерфейсу не к тому, с которого мы начинали, поэтомунужно вывести информацию о следующем интерфейсе, найденном с помо-щью
Trang 1Когда компьютер получает МАС-адрес, то он сохраняет его в кэше Адреса
в кэше сохраняются в течение определенного времени (по умолчанию
10 минут) Если компьютер в течение 10 минут еще раз обращается по этомуIP-адресу, то начнется отсчет с начала Но такое бывает не во всех системах.Для просмотра ARP-кэша в Windows можно воспользоваться командой ARP
с параметром -g или -а Но мы в этой главе напишем свою собственную большую утилиту, которая будет работать с этим интересным протоколом —ARP Пример будет достаточно сложный, поэтому мы будем его изучать по-степенно
не-Запустите Delphi и создайте форму похожую на ту, что изображена нарис 5.14 В верхней части окна расположена панель с кнопками
В раздел uses нужно добавить уже знакомые вам модули IpRtrMib,IpHlpApi, iptypes и IplfConst Без этих модулей программа не будет компи-лироваться, поэтому их присутствие обязательно
Trang 2Теперь приступим к программированию Для начала напишем код, который
будет выполняться после нажатия кнопки Обновить (листинг 5.5).
//ARP-таблица //строка ARP //Используется для показа заголовка //Таблица адресов
procedure TARPForm.UpdateButtonClick(Sender: TObject);
Trang 3if СигrentIndex <> NetRow.dwlndex then
DisplayMemo.SelAttributes.Style:=
DisplayMemo.SelAttributes.Style+[fsBold];
DisplayMemo.Lines.AddC IP -адрес Физический адрес Тип ');end;
Trang 4Самое интересное находится в самом начале процедуры и спрятано подвызовом функции GetipNetTabie Она возвращает нам в первом параметреARP-таблицу Но когда она вызывается в первый раз, мы указываем nil.Если указать нулевое значение, то функция возвращает размер необходимойпамяти для хранения ARP-таблицы После получения размера ARP-таблицы
мы выделяем память с помощью функции AiiocMem для переменнойNetTable.
После получения ARP-таблицы необходимо узнать IP-адреса, которые надлежат компьютеру Возможно, что на компьютере установлены две сете-вые карты, и тогда мы должны будем отсортировать записи из таблицы ARPстрок по соответствующим сетевым интерфейсам Интерфейс будет опреде-ляться по IP-адресу IP-адреса мы узнаем с помощью функцииGetipAddrTabieWithAiloc, которая выглядит следующим образом:
при-function GetipAddrTabieWithAiloc: PMiblpAddrTable;
про-в кэше ARP:
СигrentIndex : = NetTable A table[0].dwlndex;
DisplayMemo.SelAttributes.Color:=clTeal;
DisplayMemo.SelAttributes.Style:^DisplayMemo.SelAttributes.Style+[fsBold]; DisplayMemo.Lines.Add(Format('Интерфейс: %s на интерфейсе Ox%u',
[IntflndexToIpAddress(IpAddrTable, Currentlndex),
Currentlndex]));
DisplayMemo.SelAttributes.Color:=clTeal;
DisplayMemo.SelAttributes.Style:=DisplayMemo.SelAttributes,Style+[fsBold]; DisplayMemo.Lines.Add(' IP-адрес Физический адрес Тип');
Trang 5После этого запускается цикл, в котором перебираем все записи кэша;for I := 0 to NetTableA.dwNumEntries - 1 do
Внутри цикла первым делом получаем текущую строку ARP-записи:
NetRow := NetTable"4 table [I];
После этого проверяем, изменилось ли значение свойства dwindex текущейстроки по сравнению с предыдущей Если нет, то строка принадлежит к то-
му же интерфейсу Если там другое значение, то текущая ARP-строка сится к другому интерфейсу (не к тому, с которого мы начинали), поэтомунужно вывести информацию о следующем интерфейсе, найденном с помо-щью функции GetlpAddrTableWithAlloc
отно-if CurrentIndex <> NetRow.dwindex then
но уничтожаю переменную, чтобы уж точно быть уверенным в том, что
из-за моей программы не происходит утечка памяти И вам советую освобождатьвсю выделенную память самостоятельно и не надеяться на чужого дядю
5.9 Изменение записей ARP-таблицы
Протокол ARP работает автономно, и все записи в нем появляются тически и без нашего участия Записи, появляющиеся в ARP-таблице, назы-ваются динамическими
автома-Судя по спецификации протокола, у нас есть возможность самим создаватьзаписи в таблице ARP, и такие записи называются статическими Зачем этонужно? Динамические записи хранятся в таблице недолго, и если вы неко-торое время не обращались по определенному адресу, то его запись уничто-жается Это связано с тем, что компьютеры могут иметь динамическиеIP-адреса даже в локальных сетях (выделение адресов по протоколу DHCP) и
в любую минуту у компьютера с определенным МАС-адресом может ниться IP-адрес Чтобы это несоответствие не создавало конфликтов в сети,динамические записи в ARP-таблице хранятся только определенное время
изме-9 Зак изме-978
Trang 6Если в вашей сети используются только постоянные IP-адреса и вы хотите,чтобы ARP-записи, соответствующие этим адресам, хранились все время, томожно добавить в ARP-таблицу статичные записи В этом случае такие за-писи не будут удаляться и при обращении к компьютерам не будет тратить-
ся время на поиск МАС-адреса
5.9.1 Добавление ARP-записей
Давайте добавим в нашу программу возможность добавления таких записей.Для этого сначала создадим новое окно, в котором пользователь должен бу-дет вводить параметры новой записи Внешний вид моего окна вы можетеувидеть на рис 5.15,
01 IP-адрес записи;
О МАС-адрес записи.
Trang 7А теперь посмотрим, как выглядит сама процедура setArpEntry Ее нет
сре-ди API-функций, и мы должны ее написать сами Для этого в разделеprivate добавьте для нее следующее описание:
private
{ Private declarations }
procedure SetArpEntry(const InetAddr, EtherAddr: string);
Теперь нажмите <Ctrl>+<Shift>+<C>, и Delphi создаст для этой процедурызаготовку, в которой нужно написать следующее (листинг 5.6)
procedure TARPForm.SetArpEntry(const InetAddr, EtherAddr: string);
Trang 8по-объявлена принадлежащей типу TMibipNetRow, чтобы в ней случайно неоказалось никакого мусора Для этого использована функция FiilChar.
Во второй строке у структуры Entry заполняется свойство dwAddr, в ром указывается IP-адрес для добавляемой записи Адрес IP у нас хранится
кото-в стрококото-вой переменной inetAddr и его нужно преобразокото-вать кото-в числокото-вой,что и делается с помощью функции stringToipAddr, которая выглядит так:function StringToipAddr(const Addr: string): DWORD;
После преобразования происходит проверка с помощью функции Assert направильность адреса Если Entry.dwAddr не равен INADDR_NONE, TO все нор-мально, иначе генерируется ошибка
Дальше нужно указать физический адрес Сначала указываем длину ского адреса Entry.dwPhysAddrLen, вписывая значение б После этого при-сваиваем свойству bPhysAddr структуры Entry значение физического адреса
физиче-с помощью функции stringToPhysAddr, которая одновременно переводитстроковое представление МАС-адреса в нужный формат У этой функциидва параметра:
О строковое представление МАС-адреса;
CJ переменная, в которую нужно записать приведенный адрес
Саму функцию нужно еще написать Я не стал ее делать частью объекта
ок-на, поэтому где-нибудь выше нашего обработчика напишите код из тинга 5.7
лис-procedure StringToPhysAddr(PhysAddrString: string;
var PhysAddr: TPhysAddrByteArray);
Trang 9ука-Следующим этапом нужно указать интерфейс, для которого мы создаем пись В вашем компьютере может быть несколько сетевых карт, и компью-тер должен знать, для какой из них будет действовать ARP-запись Все этоделается в следующем коде:
В третьей строке мы получаем первый адаптер (IP-адрес) из таблицы с щью функции FirstNetworkAdapter и присваиваем его свойству dwindex струк-туры Entry Функция FirstNetworkAdapter ВЫГЛЯДИТ Следующим образом:
помо-function FirstNetworkAdapter(IpAddrTable: PMiblpAddrTable): Integer;
Trang 10В примере все записи будут всегда создаваться для первого интерфейса изтаблицы, но вы можете улучшить код, чтобы записи можно было создаватьдля любого интерфейса Я даю вам только основу, чтобы вы потом моглисоздать именно то, что вам нужно, а заранее предугадать потребности всехчитателей я не в силах Но если вы захотите добавить возможность выбораинтерфейса, то вам нужно изменить код на такой:
Trang 11дос-димое Эта функция вернет нам результат выполнения команды, которыйпотом преобразовывается в строковое представление с помощьюsysErrorMessage Это строковое представление ошибки добавляется в каче-стве СТрОКИ КОМПОНента DisplayMemo.
ARP Таблица Обновить ; Добавить Уделить Очистить Интерфейс: 192.1Б8.10О.З на интерфейсе 0x2
IP адрес Физический адрес Тип 192.168.100.1 00-04-7Б-90-ВВ-В4 Dynamic 192.168.100.2 FQ-F0-F7-F9-F4-F6 Static
Рис 5.16 Результат работы программы
На рис 5.16 вы можете увидеть результат работы примера В моей лице две записи:
Trang 12Для указания удаляемой записи используется то же окно, что и для ления, только прежде чем его отобразить делаются невидимыми все компо-ненты, которые относятся к МАС-адресу Удаляемую запись мы будем оп-ределять по IP-адресу, поэтому мне достаточно только одного поля вводадля него.
компо-А главное, наша программа не тратит время при загрузке на создание них окон Этот трюк с экономией очень прост и эффективен, поэтому ста-райтесь его использовать почаще, чтобы сэкономить на ресурсах и повыситьскорость своих приложений
лиш-Для удаления ARP-записи мы вызываем процедуру DeieteArpEntry У неедва параметра:
О IP-адрес, запись для которого нужно удалить;
• этот параметр не используется, но вы можете внести в него возможностьвведения номера интерфейса, для которого удаляется запись Это то, что
я опустил в процедуре создания новой ARP-строки
Чтобы создать процедуру DeieteArpEntry, в разделе private добавьте ееописание и нажмите <Ctrl>+<Shift>+<C> Описание должно выглядеть так:procedure DeieteArpEntry(const Host, Intf: string);
В полученной заготовке напишите содержание листинга 5.8
Trang 13V^- 1 V:" ?""4B.""»* 1 '""4*."."> : "" 1 4 «-"""• '•' ""'• "Ч ••""V •"**'-"К-*1 "'*в '""•:: ' ч *&*£*&. :
ji Л истин г 5.8?Удален.ие аапири "итаблице ARP vfiy ^ ' • / '.ъ,-.^ ч& - Щ-Щ
procedure TARPForm.DeleteArpEntry(const Host, Intf: string);
Trang 15На компакт-диске в директории \Примеры\Глава 5\ARP вы можете увидетьпример данной программы.
5.10 Работа с сетевыми ресурсами
В разд 4.7 мы уже познакомились с работой сетевых ресурсов, когда
напи-сали соответствующий сканер В этой части я опишу этот процесс болееподробно Мы напишем программу, которая будет сканировать всю локаль-ную сеть на предмет открытых ресурсов, как это делает Сетевое окружение
В дальнейшем мы добавим в нее возможность подключения сетевых дисков.Итак, наша программа вытаскивает сведения о структуре сети На рис 5.18
Г Всё '•'•'•[
15 Сетевые диски F? Сетевые принтеры
£"• Обновить
Рис 5.18 Главная форма будущей программы
Trang 16На форме расположено три переключателя TRadioButton, которые
управля-ют работой программы В зависимости от их установки будет изменятьсясписок искомых ресурсов Помимо этого есть три компонента TCheckBox,
с помощью которых можно выбрать, что искать: все, сетевые диски илипринтеры Большую часть окна занимает компонент TTreeView, в котором
После этого происходит проверка, какой компонент RadioButton выбран
Если это Глобальные ресурсы, то в переменную ResScope заносим константу
Trang 17мето-Заполнив необходимые переменные начальными значениями, вызываемпроцедуру EnumNet, которая выглядит следующим образом:
procedure TForml.EnumNet(const ParentNode: TTreeNode;
ResScope, ResType: DWORD;
const NetContainerToOpen: PNetResource);
var
hNetEnum: THandle;
begin
hNetEnum := OpenEnum(NetContainerToOpen, ResScope,
ResType, RESOURCEUSAGE_CONNECTABLE or RESOURCEUSAGE_CONTAINER);
if (hNetEnum = 0) then exit;
EnumResources(ParentNode, ResScope, ResType,
RESOURCEUSAGE_CONNECTABLE or RESOURCEUSAGE CONTAINER, hNetEnum) ;
if (NO_ERROR <> WNetCloseEnum(hNetEnum)) then
ShowMessage('WNetCloseEnum Error');
end;
На входе процедура получает следующие параметры:
• ParentNode — родительский объект в дереве Treeview, к которому будутдобавляться имена найденных ресурсов;
• ResScope — указание, где надо будет искать ресурсы (глобальные, ключенные или запомненные);
Trang 18ResScope, ResType: DWORD;
const NetContainerToOpen: PNetResource);
function OpenEnum(const NetContainerToOpen: PNetResource;
ResScope, ResType, ResUsage: DWORD): THandle;
function EnumResources(const ParentNode: TTreeNode;
ResScope, ResType, ResUsage: DWORD;
hNetEnum: THandle): UINT;:
Здесь описано три функции, хотя мы увидели пока только одну Вы тожедолжны объявить все три функции, а их содержимое мы будем писать по-степенно Нажмите <Ctr>+<Shift>+<C>, чтобы Delphi создал заготовку длявсех трех функций Теперь нужно найти функцию openEnum, с которой мыуже столкнулись, и написать в ней следующее:
function TForml.OpenEnum(const NetContainerToOpen: PNetResource;
ResScope, ResType, ResUsage: DWORD): THandle;
О dwScope — какие ресурсы будут включаться в перечисление Возможныкомбинации следующих значений:
Trang 19• о — все ресурсы сети;
• RESOURCEUSAGE_CONNECTABLE — подключаемые;
• RESOURCEUSAGE_CONTAINER — КОНТеЙнерНЫС
О lpNetResource — указатель на структуру NETRESOURCE. Если этот метр равен нулю, то перечисление начнется с самой верхней ступени ие-рархии сетевых ресурсов Нуль ставится для того, чтобы получить самыйпервый ресурс После этого в качестве этого параметра передается указа-тель на уже найденный ресурс Тогда перечисление начнется с найден-ного и продолжится дальше, пока не найдутся все ресурсы
пара-• lphEnum — ЭТО указатель, КОТОрЫЙ Понадобится В функции WnetEnumResource.
Теперь нужно рассмотреть структуру NETRESOURCE. В Delphi есть три видности этой функции: NETRESOURCE, NETRESOURCEA И NETRESOURCEW. Отли-чаются они последней буквой в названии и типом строк
Trang 20После открытия перечисления с помощью функции openEnum вызываетсяфункция EnumResources Ее объявление мы уже написали, и заготовкадолжна быть готова Осталось только написать код, который выглядит сле-дующим образом (листинг 5.10).
function TForml.EnumResources(const ParentNode: TTreeNode;
ResScope, ResType, ResUsage: DWORD;
hNetEnum: THandle}: UINT;
function ShowResource(const ParentNode: TTreeNode;
Res: TNetResource): TTreeNode;
ResourceBuffer: array[1 2000] of TNetResource;
i, ResourceBuf, EntriesToGet: DWORD;
NewMode: TTreeNode;
Trang 21if (NO^ERROR <> WNetEnumResource(hNetEnum, EntriesToGet,
QResourceBuffer, ResourceBuf)) then
NewNode := ShowResource(ParentNode, ResourceBuffer[i]};
if (ResourceBuffer[i].dwUsage and RESOURCEUSAGE_CONTAINER) <> 0 then EnumNet(NewNode, ResScope, ResType, SResourceBuffer[i]);
end;
end;
end;
Самая интересная здесь функция — это wnetEnumResource Она перечисляет ресурсы сетевых объектов В Delphi она описана следующим образом:
Trang 22стесняй-все ресурсы После выполнения функция поместит сюда фактическоечисло найденных ресурсов.
О lpBuffer — указатель на буфер, в который будет помещен результат
О lpBuffersize — размер буфера.
После вызова этой функции найденные ресурсы добавляются с помощью:NewNode := ShowResource(ParentNode, ResourceBuffer[i]);
ФУНКЦИЯ ShowResource как бы ВХОДИТ В СОСТЭВ фуНКЦИИ EnumResources
и выглядит вот так:
function ShowResource(const ParentNode: TTreeNode;
Res: TNetResource): TTreeNode;
ко-Попробуйте скомпилировать программу и запустить ее Посмотрите, как онаищет ресурсы, если выбирать различные параметры поиска На рис 5.19 выможете увидеть результат выполнения моей программы в моей локальнойсети
Если во время выполнения появится окно с ошибкой, то это не значит, чтопрограмма работает неверно Возможно, в перечисление попал компьютер,который требует авторизации и без пароля не пускает В этом случае гене-рируется ошибка перечисления, но программа продолжает искать другиекомпьютеры в локальной сети
На компакт-диске в директории \Примеры\Глава 5\Net Resource 1 вы
може-те увидеть пример этой программы и цветные версии рисунков из этого дела