книги хакеры / журнал хакер / 153_Optimized
.pdf
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|||
|
|
X |
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
|
NOW! |
o |
|||
|
|
|
|
|
|
|
||||
|
|
|
|
|
BUY |
|
|
|||
|
|
|
|
to |
|
|
|
|
|
|
w Click |
|
|
|
|
|
m |
||||
|
|
|
|
|
|
|||||
w |
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-xcha |
|
|
|
|
IN PDEVICE_OBJECT DeviceObject,
IN OUT PIRP Irp
)
{
Irp->CurrentLocation--;
irpSp = IoGetNextIrpStackLocation( Irp );
Irp->Tail.Overlay.CurrentStackLocation = irpSp;
irpSp->DeviceObject = DeviceObject;
driverObject = DeviceObject->DriverObject;
status = driverObject->MajorFunction[irpSp->MajorFunction](
DeviceObject, Irp );
return status;
}
Кстати,IoCallDriver—этопсевдонимвстречаемоговлитературе названияфункцииIofCallDriver,которая,всвоюочередь,всеголишь являетсязаглушкойдлявызоваIopfCallDriver.Можносразуотметить, чтоеслифильтроватьданныевядревышеописаннымобразом,то всё,чембудетсебявыдаватьтакаяфильтрация,—этоодинперехват IoCallDriver.Ивсё!Небудетсуществоватькаких-топодозритель- ныхустройств,прицепленныхвстек,небудетперехватовmajor- обработчиковвсистеме.Всё,чтоможнобудетнайти,—этоизменен- ныйадресфункцииIoCallDriver,авотужеопределить,чтоименно фильтруетсяикакиеданныепроходятчерезнутротвоегонепростого драйвера(еслион,конечно,существуетотдельнымфайлом,ночто мешаетзаразитьдругойдрайвер?),будеточеньиоченьсложно.
ОПРЕДЕЛЯЕМ,ОТКУДАИКУДА
Теперьсамоеглавноеисамоесложное.ПростойперехватIRP-пакета, основанныйнаединственномхукеIoCallDriver,внашейситуации
непоможет,ивотпочему.Скажем,MJ-кодIRP_MJ_CREATEможет посылатьсяприсозданиифайланадискеилиприсозданиисетевого соединения.Чтобысделатьчто-тополезное,получивконтрольнад IRP-пакетомстакимкодом,твойдрайвердолженобладатьопреде- леннойлогикой,позволяющейопределить,кудаименноискакой цельюпосылаетсяэтотIRP-пакет,авотэтоужедостаточногеморрно, новсёжеосуществимо.Всёделовтом,чтоприперехватеIoCallDriver внаширукипопадаетиуказательнаDeviceObject,распарсивкоторый,
можнодостаточноточноопределить,комупредназначаетсяIRP.Возьмем конкретныйпример.Причтениисканкодаклавиатурыпостекуподклю- ченных«устройствклавиатуры»прогоняетсяIRP-пакетскодомIRP_MJ_ READ.МыперехватываемIoCallDriver,получаемуказательнаIRP-пакет, MajorFunctionкоторогоравняетсяIRP_MJ_READ.Нокакопределить,что этотпакетименноотклавиатуры?Дляэтого,какяужеговорил,намнужнопроанализироватьтакжеперехваченныйуказательнаDeviceObject.В случаесклавиатуройэтоможносделатьследующимобразом.
Таккакустройстввстекеможетбытьнесколько,можно(каквариант)простопросмотретьназваниетехдрайверов,которыесоздалиэти устройства:
BOOLEAN IsKeybordDevice( DEVICE_OBJECT * topDevice )
{
UNICODE_STRING driverName = {0};
DEVICE_OBJECT * device = 0;
RtlInitUnicodeString( & driverName, L"\\Driver\\Kbdclass");
for (device = TopDevice;
device;
device = device->DeviceObjectExtension->AttachedTo)
{
if ( !RtlCompareUnicodeString(
&device->DriverObject->DriverName, &driverName, TRUE))
return TRUE;
}
return FALSE;
}
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|||
|
|
X |
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
|
NOW! |
o |
|||
|
|
|
|
|
|
|
||||
|
|
|
|
|
BUY |
|
|
|||
|
|
|
|
to |
|
|
|
|
|
|
Универсальныйфильтрw Click |
|
|
|
|
|
m |
||||
|
|
|
|
|
|
|||||
w |
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-x cha |
|
|
|
|
КлючевыммоментомдляфильтрацииявляетсяпарсингструктурыDeviceObject.Получивуказательнанее,мылегкополучимуказа- тельнапринадлежащийструктуреописательдрайвера—структуру DRIVER_OBJECT.Можнотакжепроанализироватьстек,гдебылпойманзахвостIRP,дляэтого,еслипомнишь,нужновызыватьIO_STACK_ LOCATION *stack = IoGetCurrentIrpStackLocation(pIrp).Далеепо указателюнастектыужесможешьпроанализироватьуказательна «объект-файл»—stack->FileObject.Тоестьтакимивотнехитрыми действиямитысможешьопределить,кудаикомупредназначается IRP-пакет,нуачтоснимделать—делосугуботвое.Так,например, можнозапретитьчтениекаких-либофайловcдиска.Ненужнописать сложныефильтрыфайловойсистемыдляэтого!
ДостаточноперехватитьIoCallDriver,организоватьфильтрацию IRP-пакетовскодомIRP_MJ_CREATE,поуказателюstack->FileObject получитьимяфайла:
OBJECT_NAME_INFORMATION *fileNameInformation = 0;
status = ObQueryNameString( stack->FileObject,
fileNameInformation, 1024, &retSize);
wcscat(fileNameInformation->Name.Buffer,
stack->FileObject->FileName.Buffer);
DbgPrint("file name now is: %ws \n", fileNameInformation->Name.Buffer);
...послечегозавернутьэтотIRP-пакет,еслион,допустим,предназна- чендляоткрытиятогофайла,которыйтебенужнозащитить.
ULONG CreateDisposition =
(stack->Parameters.Create.Options>> 24)& 0x000000ff;
if((CreateDisposition==FILE_CREATE)||
(CreateDisposition==FILE_OPEN_IF )||
(CreateDisposition==FILE_OVERWRITE_IF))
{
Irp->IoStatus.Status = STATUS_ACCESS_DENIED;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
ExFreePool(fileNameInformation);
return STATUS_ACCESS_DENIED;
}
СЛОЖНОСТИ
ВспособефильтрацииIRP-данных,основанномнаперехвате IoCallDriver,естьоднасерьезнаясложность.Деловтом,чтоIoCallDriver являетсячрезвычайноиспользуемойфункциейвядреWindows’а.Это связаносочевиднымфактом—функциятолькоиделает,чтоотправля- етIRP-пакетынаправо-налево,авзаимодействиемеждудрайверами- устройствамивядреоснованоименнонапосылкеIRP-пакетов.Правда, приобслуживаниифайловойсистемытакжеиногдаиспользуется механизмFastIO,нообэтомпозже.Еслипредставить,чтовсяэтакуча драйверовначинаетслатьдругдругуIRP-пакетыиихобрабатывать,то можнолегкопредставить,какуюнагрузкуиспытываетIoCallDriver— речьможетидтионесколькихсотняхвызовахвсекунду.Поэтомууста- новка/снятиеперехватанаIoCallDriver—делотонкоеилегкорушащее системуввидеBSOD’aскодомDRIVER_UNLOADED_WITHOUT_CANCELLING_ PENDING_OPERATIONS.Этопроисходитобычно,еслиначинаешьпри перехватенеоченьаккуратнообрабатыватьтеданные,которыенесутс собойIRP-пакеты.Спешупредостеречь:необходимымусловиемтвоего драйвера,которыйбудетперехватыватьIoCallDriver,должнабытьгра- мотнаяобработкаизавершениевсехпросмотренныхIRP-пакетов.
ЗАКЛЮЧЕНИЕ
Какоказывается,сделатьуниверсальныйинестандартныйфильтр довольнолегко.Аеслидостаточноопытакодингадрайверов,тосозданныйфильтрбудетобладатьещеитоликойнадежности.Возможностейпримененияуэтойнехитройтехникиоченьмного,азатратына реализациюминимальные—этохорошийстимулдляразвитиятемыв домашнихусловиях.Удачногокомпилирования,идапребудетстобой Сила! z
ХАКЕР 10/153/2011 |
099 |
|
|
|
|
hang |
e |
|
|
|
|
||
|
|
|
C |
|
E |
|
|
||||
|
|
X |
|
|
|
|
|
|
|||
|
- |
|
|
|
|
|
|
d |
|||
|
F |
|
|
|
|
|
|
|
t |
||
|
D |
|
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
|
NOW! |
o |
||||
|
|
|
|
|
|
|
|||||
|
|
|
|
|
BUY |
|
|
||||
|
|
|
|
to |
КОДИНГm |
||||||
w Click |
|
||||||||||
|
|
||||||||||
w |
|
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
|
|
.c |
|||
|
|
p |
|
|
|
|
|
g |
|
|
|
|
|
|
df |
-xcha |
n |
e |
|
|
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|||
|
|
|
X |
|
|
|
|
|
|||
|
|
- |
|
|
|
|
|
d |
|
||
|
|
F |
|
|
|
|
|
|
t |
|
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
|
r |
||
|
P |
|
|
|
|
|
NOW! |
o |
|||
|
|
|
|
|
|
|
|
||||
|
|
|
|
|
|
BUY |
|
|
|||
|
|
|
|
|
to |
|
|
|
|
|
|
Всеволод Захаров (seva@vingrad.ru)w Click |
|
|
|
|
|
m |
|||||
|
|
|
|
|
|
||||||
|
w |
|
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
|
-x cha |
|
|
|
|
||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Ядромпо Макинтошу
ИЗУЧАЕМKERNEL-КОДИНГПОДMACOSX
РазработкаприкладныхпрограммподMacOSX иiOSотносительнонеплохоосвещенавлитературеиинтернете.Акакнасчеттемысоздания кода,которыйдолженвыполнятьсяврежиме ядра?Сэтим—глухо.Вэтойстатьемыпопробу- емданнуюнесправедливостьликвидировать.
Сеть
BSD |
Файловые |
системы |
NKE
I/OKit
Mach
Драйверы
XNUвнутри
ЯДРОMACOSX
Дляначала—немноготеории,котораяпоможеттебеприсозданиирас- ширенийядра.MacOSXосновананаядреXNU(XNUisnotUNIX).XNU—это гибридноеядро,котороесостоитизнесколькихкомпонентов:подсисте- мыMach,BSDиобъектно-ориентированногофреймворкадлясоздания драйверовIOKit.
Mach—этомикроядро,созданноеврезультатеисследованийв университетеКарнегивсередине80-хгодов.Микроядернаяархитекту- ра,какизвестно,неплохосмотритсявпрезентациях,но,ксожалению, приводитксерьезнымпроблемамспроизводительностьюиотладкой. Видимо,поэтомупроектGNU/HURD,вкоторомсистемаGNUпостроенане намонолитномLinux,анамикроядре,всеещедалекотвыпускастабиль- нойверсии.Нет-нет,неволнуйся,проMacOSXмыподобноенескажем,и вовсенепотому,чтобоимсяместияблочныхфанатов:).Деловтом,чтов MacOSXподсистемаMachиспользуетсявнесколькоизмененномвиде: микроядроивсекомпоненты,которыеонодолжнообслуживать,собраны вединомадресномпространствемонолитногоядра.Такойход,содной стороны,позволяетиметьболееструктурированноеядро,асдругой— избавляетотпроблемсотладкойипроизводительностью.
Mach-кодвXNUотвечаетзабольшинствонизкоуровневыхфункций:
•вытесняющаямногозадачность;
•виртуальнаяпамять;
•межпроцессноевзаимодействие;
100 |
ХАКЕР 10/153/2011 |
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|||
|
|
X |
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
|
NOW! |
o |
|||
|
|
|
|
|
|
|
||||
|
|
|
|
|
BUY |
|
|
|||
|
|
|
|
to |
|
|
|
|
|
|
w Click |
|
|
|
|
|
m |
||||
|
|
|
|
|
|
|||||
w |
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-xcha |
|
|
|
|
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|||
|
|
X |
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
|
NOW! |
o |
|||
|
|
|
|
|
|
|
||||
|
|
|
|
|
BUY |
|
|
|||
|
|
|
|
to |
|
|
|
|
|
|
ЯдромпоМакинтошуw Click |
|
|
|
|
|
m |
||||
|
|
|
|
|
|
|||||
w |
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-x cha |
|
|
|
|
kextудачнособрался
•прерывания;
•консольныйввод-вывод.
ПодсистемаBSDядраMacOSXосновананакодеизFreeBSDиотвечаетзавыполнениеследующихзадач:
•идентификациюпользователяибазовуюмодельбезопасности;
•POSIXAPI,системныевызовыBSD;
•TCP/IPBSD-сокеты;
•файловыесистемы;
•различныемеханизмысинхронизации;
•поддержкуреальноговремени.
ДлявзаимодействиясоборудованиемвядреMacOSXприменяется объектно-ориентированныйфреймворкIOKit.Ониспользуетограни- ченноеподмножествоC++вкачествеязыкапрограммирования.Внем, например,отсутствуюттакиеэлементы,какисключения,множественноенаследование,шаблоныиRTTI. Каждыйтипсервисаядраили устройствапредставленвIOKitввидеклассаC++,акаждыйконкретный сервисилиустройство—ввидеобъектаэтогокласса. Корочеговоря, драйверIOKit—этообъект,которыйуправляетустройствомилишиной, представляяабстрактныйинтерфейскнимдлядругихчастейсистемы.
МОДУЛИЯДРА
MacOSXпредоставляетвозможностьрасширятьядроспомощью динамическойзагрузкимодулейядра,которыеназываютkext'ами(от kernelextension).ЕслитынесобираешьсяправитьисходникиXNU, тоединственнаявозможностьвыполнитькодврежимеядра—это написаниеизагрузкарасширения.Так,драйвераIOKitреализованы именноввидеkext’ов.Kext’ы—этобандлы,какиобычныепользова- тельскиеприложенияMacOSX,ионисодержат:
•plist-файл,вкоторомописаносодержимоебандла,установкииза- висимостимодуля;
•бинарныефайлымодуляядра—файлывMach-O-формате,вкоторых содержитсякод,подгружаемыйвадресноепространствоядра;
•ресурсы—иконки,данныедлялокализацииит.п.
Когдаkextсоздан,загрузитьеговядроможноспомощьюкоманды kextloadизтерминала.Приэтомнеобходимоиметьправаадминистратора.Отлаживатьkext'ысложнеепользовательскихприложений.В первуюочередь,нужноразрешитьотладкуядра.Дляотладкиядра,
какводится,необходимоиспользоватьдвемашины:наоднойработа- етядро,анадругой—отладчик.ДляотладкииспользуетсяGDB.Когда kextотлажен,егоможноустанавливатьвсистему.Тогдазазагрузку расширенияпризапускесистемыбудетотвечатьkext-manager.Для этогобандлkext'анужнопоместитьв/System/Library/Extensions.
Присозданииkext'ов(какиостальногософта)вMacOSXиспользуется XCode.ПопробуемсоздатьпростоерасширениеядраспомощьюэтойIDE.
Дляэтогонамнужно:
1.ЗапуститьXCode:).
2.СоздатьновыйпроектисредишаблоноввыбратьGenericKernel Extension,посколькумыбудемсоздаватьпростоерасширениенаC, анеIOKit-драйвернаC++.
3.ВкачествеименипроектауказатьSampleKext(да,янастаиваю:)).
Еслитыправильнореализовалописанныйвышенехитрый алгоритм,товкачествеегологичногозавершениясистемасоздаст занасфайлSampleKext.c.Внембудутсодержатьсядвефункции: SampleKext_startиSampleKext_stop.Кактыужедогадался,этифункциивызываютсяпризагрузке/выгрузкерасширения.Вшаблонеэти функцииничегополезногонеделают,авреальномkext'еониотвечают
Выбираемшаблондляпроекта
ХАКЕР 10/153/2011 |
101 |
|
|
|
|
hang |
e |
|
|
|
|
||
|
|
|
C |
|
E |
|
|
||||
|
|
X |
|
|
|
|
|
|
|||
|
- |
|
|
|
|
|
|
d |
|||
|
F |
|
|
|
|
|
|
|
t |
||
|
D |
|
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
|
NOW! |
o |
||||
|
|
|
|
|
|
|
|||||
|
|
|
|
|
BUY |
|
|
||||
|
|
|
|
to |
КОДИНГm |
||||||
w Click |
|
||||||||||
|
|
||||||||||
w |
|
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
|
|
.c |
|||
|
|
p |
|
|
|
|
|
g |
|
|
|
|
|
|
df |
-xcha |
n |
e |
|
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|||
|
|
X |
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
|
NOW! |
o |
|||
|
|
|
|
|
|
|
||||
|
|
|
|
|
BUY |
|
|
|||
|
|
|
|
to |
|
|
|
|
|
|
w Click |
|
|
|
|
|
m |
||||
|
|
|
|
|
|
|||||
w |
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-x cha |
|
|
|
|
Подгружаемkextдлятестирования
зарегистрациюcallback'овдляразличныхсобытийядраидругие действияпоинициализации/деинициализациирасширения.Например,вSampleKext_startмымоглибызарегистрироватьсокет,через которыйприкладныепрограммымоглибыобщатьсяснашимkext'ом. Нодляначаламывэтихфункцияхтольковыведемтестовыесообщения,которыеможнобудетувидетьпризагрузке/выгрузкерасширения.
SampleKext.c
#include <sys/systm.h>
#include <mach/mach_types.h>
kern_return_t MyKext_start (kmod_info_t * ki, void * d)
{
printf("Kext loaded.\n");
return KERN_SUCCESS;
}
kern_return_t MyKext_stop (kmod_info_t * ki, void * d)
{
printf("Kext unloaded.\n");
return KERN_SUCCESS;
}
Сборкапроектапослеэтогопройдетбезпроблем,ноеслитыпопробуешьзагрузитьSampleKext.kext,тоничегохорошегонеполучится, посколькумыещенепозаботилисьосодержимомplist-файла.Каждый kextобязательнодолженсодержатьInfo.plistвXML-формате.Посмотрим важныедляправильнойзагрузкирасширенияключивэтомфайле:
•CFBundleIdentifier—идентификаторkext'а.Например,com.apple. driver.AppleUSBMergeNub.
•CFBundleExecutable—бинарныйфайлkext'а.Егоможетинебытьв случаеIOKit-драйверов.
ДЛЯКОРРЕКТНОЙЗАГРУЗКИ KEXT'АДОСТАТОЧНО БУДЕТПОПРАВИТЬ OSBUNDLELIBRARIES
•CFBundleVersion—версиябандла.
•OSBundleLibraries—либы,откоторыхзависитkext.• IOKitPersonalities—объектыIOKit,длякоторыхнужноподгружать этотkext.Еслиэтодрайвер,конечно.
Посколькумысоздаемнедрайвер,аобычноерасширение ядра,длякорректнойзагрузкиkext'адостаточнобудетпоправить
OSBundleLibraries.Длятогочтобыавтоматомполучитьсписоквсехкомпонентов,откоторыхзависитрасширение,можноиспользоватьутилиту kextlibs.Запустивеесключом-xml,тысразуполучишьXML-код,который нужнодобавитьвplist-файл:
kextlibs -xml MyKext.kext
<key>OSBundleLibraries</key>
<dict>
<key>com.apple.kpi.libkern</key>
<string>9.2.2</string>
</dict>
Нашkextзависитвсегоотодногоэлемента.Скопируемэтоткодв Info.plist.Длятогочтобынашkextзаработал,осталсяодинважныйшаг: бандлkext'адолженпринадлежатьадминистратору.Длятестирования скопируемегов/tmpсправамирута:
sudo cp -R SampleKext.kext /tmp
Воттеперьможнозагружатьнашмодуль:
sudo kextload /tmp/SampleKext.kext
Послеэтогов/var/log/system.logтыможешьувидетьвыводнашего kext'a.Чтобывыгрузитьkext,используемkextunload:sudokextunload/ tmp/SampleKext.kext.
OUTRO
МысоздалипростейшиймодульядрадляMacOSX.Все,чтоонумеет насегодня,—этокорректнымобразомзагружатьсяивыгружаться.В идеалежемодулиядрамогутиспользоватьсянетолькодлясоздания драйверов,нои,например,длявынесениячастоиспользуемоймногимиприкладнымипрограммамифункциональностивсервисядра. Удачногоkernel-кодинга!z
102 |
ХАКЕР 10/153/2011 |
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|||
|
|
X |
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
|
NOW! |
o |
|||
|
|
|
|
|
|
|
||||
|
|
|
|
|
BUY |
|
|
|||
|
|
|
|
to |
|
|
|
|
|
|
w Click |
|
|
|
|
|
m |
||||
|
|
|
|
|
|
|||||
w |
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-xcha |
|
|
|
|
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|||
|
|
X |
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
|
NOW! |
o |
|||
|
|
|
|
|
|
|
||||
|
|
|
|
|
BUY |
|
|
|||
|
|
|
|
to |
|
|
|
|
|
|
w Click |
|
|
|
|
|
m |
||||
|
|
|
|
|
|
|||||
w |
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-x cha |
|
|
|
|
|
|
|
|
hang |
e |
|
|
|
|
||
|
|
|
C |
|
E |
|
|
||||
|
|
X |
|
|
|
|
|
|
|||
|
- |
|
|
|
|
|
|
d |
|||
|
F |
|
|
|
|
|
|
|
t |
||
|
D |
|
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
|
NOW! |
o |
||||
|
|
|
|
|
|
|
|||||
|
|
|
|
|
BUY |
|
|
||||
|
|
|
|
to |
КОДИНГm |
||||||
w Click |
|
||||||||||
|
|
||||||||||
w |
|
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
|
|
.c |
|||
|
|
p |
|
|
|
|
|
g |
|
|
|
|
|
|
df |
-xcha |
n |
e |
|
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|||
|
|
X |
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
|
NOW! |
o |
|||
|
|
|
|
|
|
|
||||
|
|
|
|
|
BUY |
|
|
|||
|
|
|
|
to |
|
|
|
|
|
|
СергейКононенко(kononencheg@gmail.com)w Click |
|
|
|
|
|
m |
||||
|
|
|
|
|
|
|||||
w |
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-x cha |
|
|
|
|
JavaScript
длясервера
ПИШЕМСЕРВЕР МГНОВЕННЫХ СООБЩЕНИЙ НАNODE.JS
Всезнают,чтотакое JavaScript.Номалокто можетпредставитьего обрабатывающимнетекст
вдивахиспанах,атысячи
итысячивходящихзапросов.Мысегоднянетолько
представим,ноиразработаемнаJSсвойсервер!
LINKS |
|
INFO |
|
nodejs.org—сайт |
Масштабируемость— |
||
проекта,здесьже |
этобольнойвопрос |
||
документацияи |
дляNode.js. |
||
примеры. |
Основнаяпроблема— |
||
|
|
взаимодействие |
|
|
|
междупроцессами. |
|
|
|
Ноестьизящное |
|
|
|
решениеспомощью |
|
|
|
publish/subscribe- |
|
|
|
взаимодействия, |
|
|
|
использующегобазу |
|
|
|
данныхRedis.Воз- |
|
|
|
можно,мыобэтомеще |
|
|
|
напишем. |
104 |
ХАКЕР 10/153/2011 |
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|||
|
|
X |
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
|
NOW! |
o |
|||
|
|
|
|
|
|
|
||||
|
|
|
|
|
BUY |
|
|
|||
|
|
|
|
to |
|
|
|
|
|
|
w Click |
|
|
|
|
|
m |
||||
|
|
|
|
|
|
|||||
w |
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-xcha |
|
|
|
|
ЧТОТАКОЕNODE.JS
Раньшедлясозданиякастомногосерверанеобходимобылоприложить немалоусилий,аразработкаасинхронноймоделиуправлениявводом ивыводомдоставляланемалопроблем.ДажеPythonсTwistedспасал слабо.ОднакоспоявлениемNode.jsзадачаневероятноупростилась.
Еслиговоритьвикипедийно,тоNode.js—этособытийно- ориентированнаяI/OсервернаясредадляJavaScript.Агрубоговоря, специальныйJavaScript-фреймворк,позволяющийделатьвещи,в общем,дляяваскриптанехарактерные,вродеработысфайловой системой,процессамиилисозданияHTTP-серверовбезовсякихбрау- зеровивлюбомокружении.
Чтобыбылопонятнее,поcмотринакод,которыйсоздастwebсервер,здоровающийсясомной:
var http = require('http'); http.createServer(function(request, response) {
response.writeHead(200, {'Content-Type': 'text/plain'});
response.end('Привет, Кононенка!'); }).listen(8080, '127.0.0.1');
console.log('Сервер запущен http://127.0.0.1:8080/');
Авоттакмыегозапустим:
%node имя_файла_скрипта.js
Сервер запущен http://127.0.0.1:8080/
Понятноедело,чтовместоприветствияможновыдавать<html> <head><title>...итакдалее.;)Кромебыстройотдачизабавныхприветствийистатичнойверсткиможнореализоватьпрактическичтоугодно! «Какойбыстрой?Яваскрипт—этожемедленно!»—вспомнишь
тыибудешьотчастиправ.Ноитутямогутебяпорадовать!Вкачестве JavaScript-движкавNodeиспользуетсянепревзойденныйгугловскийV8, амодельввода/выводавовнутренностяхисключительноасинхронная. Вместеэтодаетблестящуюскорость,достойнуювысоконагруженного сервиса.Доказательствомэтогоявляетсято,чтоименнонаNodeуже успешнореализовансерверобменамгновеннымисообщениямина «ВКонтакте»,аужгде-где,атамнагрузкаого-го.Кслову,немногопохо- жийсервермыинапишем,новначаленемногомануальнойтеории.
НОВОВВЕДЕНИЯВNODE
Первое,чтомоглоброситьсявглазавприведенномвышескрипте,— этоrequire.Нодавсеинтересненькоеиготовенькое(тоестьпочтивсе) держитвспециальныхмодулях.Именноихиподгружаетглобальныйэтот метод.ЧастьмодулейнаписанынаС++слибамиV8,частьнепосредствен- нонаJavaScript’е.СамостоятельноJS-модульсделатьоченьпросто:
Наш модуль circle.js
exports.area = function (r) {
Взглядсосторонысервера
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|||
|
|
X |
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
|
NOW! |
o |
|||
|
|
|
|
|
|
|
||||
|
|
|
|
|
BUY |
|
|
|||
|
|
|
|
to |
|
|
|
|
|
|
JavaScriptдлясервераw Click |
|
|
|
|
|
m |
||||
|
|
|
|
|
|
|||||
w |
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-x cha |
|
|
|
|
||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Взглядсостороныклиента
return Math.PI • r • r; };
exports.circumference = function (r) {
return 2 • Math.PI • r;
};
Здесьвстречаетсявторойглобальныйобъект—exports.Внутри своегомодулямыопределяемвнемлюбыесвоиметоды,которые послебудутдоступныизвне(areaиcircumference—впримере).Вос- пользуемсяэтиммодулем:
var circle = require('./circle.js');
console.log( 'Площадь круга с радиусом 4 равна %d', circle.area(4));
Чтобывывестиплощадькруганаэкран,мыпользуемсятретьим глобальнымобъектом—console,предназначеннымдлявыводав stdoutиstderr.Методlog()—этоprintf-подобныйметод(Классический форматированныйвыводвJavaScript—этосчастье.—Прим.ред.) длявыводавstdout,методerror()—тожесамоедляstderr.Последний интересныйглобальныйобъект—этоprocess,дающийнамполный контрольнадисполнениемтекущегопроцесса.Воттольконекоторые изегометодовиобъектов,которыесовершенноточнопригодятсяпри болееилименеесерьезномпрограммированиисервера:
•process.stdout,process.stderrиprocess.stdin—потокистан- дартноговвода/вывода.Дляпервыхдвухметодwriteвруки,и получитсяconcole.logиconsole.error,авотметодstdin—этоуже интереснее.Node—платформасобытийно-ориентированная, асинхронная.Поэтомудляполучениястандартноговводаопределяетсяпростаяфункцияксобытию:
process.stdin.on('data', function (chunk) {
// печатаем полученный символ
process.stdout.write('data: ' + chunk);
});
Функции,которыеуказываютсявкачествеаргумента,—это callback-функции.Онисрабатываютпринекоторомсобытии.В этом,кстати,ивсясутьсобытийнойориентированности,ноэтоты всепрекраснознаешь.
•process.argv—массивсаргументамикоманднойстроки,заданны- мипризапуске.
// печатаем аргументы
process.argv.forEach(function (val, index, array) {
console.log(index + ': ' + val);
});
ХАКЕР 10/153/2011 |
105 |
|
|
|
|
hang |
e |
|
|
|
|
||
|
|
|
C |
|
E |
|
|
||||
|
|
X |
|
|
|
|
|
|
|||
|
- |
|
|
|
|
|
|
d |
|||
|
F |
|
|
|
|
|
|
|
t |
||
|
D |
|
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
|
NOW! |
o |
||||
|
|
|
|
|
|
|
|||||
|
|
|
|
|
BUY |
|
|
||||
|
|
|
|
to |
КОДИНГm |
||||||
w Click |
|
||||||||||
|
|
||||||||||
w |
|
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
|
|
.c |
|||
|
|
p |
|
|
|
|
|
g |
|
|
|
|
|
|
df |
-xcha |
n |
e |
|
•process.cwd()—рабочаядиректорияпроцесса.
•process.pid,process.getgid()process.getuid()—текущийidпроцес- са,атакжеgidиuid.
•process.kill(pid,signal='SIGTERM')—убьетзаданныйпроцесс.
•process.exit(num)—завершиттекущийпроцесс.Кстати,процесс завершенияможноотследить,зарегистрировавсоответствующее событие:
process.on('exit', function () { console.log('Я таю... таю...'); });
Прочиеглобальныеобъектыиихметодыуженетакиеинтересные, заточтоинтересно,такэтомодули,которыеможноприкрутитькNode.
Кэтомумоментутыдолженсовершенноточнопонять,чтоNode.js
—этонереальнокруто.Я,например,вспомнил,чтовсюжизньтолько имечтал,чтонаписатьсвойвеб-сервер!Ибезкаких-нибудьтам унылыхCGI-скриптов,ассокетами,пустьдаже«веб»!Теперьэтолегко реализуемо.
БИБЛИОТЕКИДЛЯЭМУЛЯЦИИ СОКЕТНОГОСОЕДИНЕНИЯ
Мгновенностьсообщенийнашегосерверабудетзаключатьсявтом, чтоданныемеждупользователямибудутпередаватьсябеззадержек встилеreal-timeweb.Обеспечиватьсяэтобудетспомощьюэмуляции постоянногосоединениясбраузером.Самаясовершеннаятехнология, постоянноедвухстороннеесоединениесбраузером,ксожалению, поддерживаетсятольконовымибраузерами.Анашбудущийсервер долженсуметьдатьнастоящийреалтайм-веблюбомуклиенту!Для созданияреалтайм-веб-приложенийсуществуетмногоразличных решений,нодлянашегослучаяихвсегоничего:
•ОченьпопулярнаябиблиотекаSoket.IOпорадуеткрасивымдизайномглавнойстраницыпроектаhttp://socket.io/иприличным количествомплюшекисвистелок.Онаобещаеточеньмногоесделатьзавас.Иона,конечно,делает.Поддерживаеткучуспособов соединения:отновыхвеб-сокетовдовечныхайфреймов.Новая версияпозволитгенерироватьсерверныесобытия,сопровождаемыеданными.ВСетилегкогуглитсякучатуториаловипримеров, описывающихто,какэтабиблиотекаудобнаикрута.
•Гораздоменеепопулярна(азря!)библиотекаBeseda,которая делаетпримернотожесамое,однакоимееттолькорепозитарийна гитхабе(goo.gl/9SoJR).ОнаподдерживаетгибкийпротоколBayeux итриспособасоединенияссервером.
Достоинствомобеихбиблиотекявляетсято,чтоихдействительно легкоиспользовать.Правда,Soket.IOплохоработаетсовсемиспосо- бамиподдержкисоединения,кромевеб-сокетов.Такжеслучаются утечкипамятиинеобъяснимыепадениясервераприотносительно небольшомколичествеклиентов(более5000).АвотBesedaбезвеб- сокетовнеплохосправляетсяис20000клиентов.Даисвеб-сокетами онаработаетпошустрее.
НАПИШЕМЛУЧШЕ!
Чтобылучшевъехатьвновуютехнологию,всегдаполезноизобрести велосипед.Аособеннохорошо,есливелосипедбудетбыстрый.
КакпоказалмойопытработысNode.jsиJavaScript,чемпроще— тембыстрее.Поэтомудажедляреальногоприменениясвоябиблиотекаможеточеньдажесгодиться.Состоятьнашабиблиотекабудет изодногофайлаio.js.Адляпроверкииотладкиещедва—сценарий сервераserver.jsистраницаклиентаindex.html.
Самымуниверсальнымспособомподдерживатьпостоянноесоеди- нениессерверомявляетсяlong-polling-соединение,поэтомудляна- чалареализуемего.Намважно,чтобынашсервермогподдерживать кучусоединений,нетекинетормозил.
После долгих и упорных ковыряний в исходниках, отладки и профилирования Soket.IO причина падений и тормозов, о которых я говорил выше, была найдена! Это тайм-ауты, обрабатывающие
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|||
|
|
X |
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
|
NOW! |
o |
|||
|
|
|
|
|
|
|
||||
|
|
|
|
|
BUY |
|
|
|||
|
|
|
|
to |
|
|
|
|
|
|
w Click |
|
|
|
|
|
m |
||||
|
|
|
|
|
|
|||||
w |
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-x cha |
|
|
|
|
||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
СТАНДАРТНЫЕМОДУЛИNODE
абормодулейNode.jsудовлетворяетпотребностипринаписа-
Ннииоченьбольшогоколичествасофта. Смотрисам:
•fs—модульдляработысфайловойсистемой.Естьметодыдлялюбых операций,отчтенияизаписидосозданиясимволическихссылок.
fs.readFile('/etc/passwd', function (err, data) { if (err) throw err;
console.log(data);
});
•crypto—криптографическиеметоды:хэшированиелюбымиал- горитмами(sha1,md5,sha256,sha512ит.п.),конвертацияданных и,собственно,любоешифрование.
•net—модульдляасинхроннойработысTCP.
var net = require('net');
var server = net.createServer(function (c) { c.write('hello\r\n');
c.pipe(c);
});
server.listen(8124, 'localhost');
•dgram—дляасинхроннойработысUDP-идругимидатаграмны- мипротоколами.
•events—дляреализациисвоейсобытийнойсистемы.
•util—наборполезныхметодов:форматированиестроки,при- нудительноенаследование.
•tls—имплементацияOpenSSL,тоестьудобныеSSL-соединения длятвоихпрограмм.
•vm—виртуальнаяJavaScript-машина,т.е.модульдлязапускаи компиляции(evalэтогонеумеет)js-скриптоввнутрисвоейпро- граммы.
var localVar = 123, usingscript, evaled, vm = require('vm');
usingscript = vm.runInThisContext('localVar = 1;', 'myfile.vm');
console.log('localVar: ' + localVar + ', usingscript: ' + usingscript);
//localVar: 123, usingscript: 1
•dns—резолвинглюбыхзаписей(MXит.п.).
•http,https—добротныеибыстрыереализацииweb-серверов.
•child_process—работасдочернимипроцессами,форк,exec, spawnит.п.
•os—все,чтонужноотоперационнойсистемы.
Естьещекучаистандартныхмодулей,инестандартных,написан-
ныхстороннимиразработчиками:отмодулейдляработыMySQLдо полноценныхвеб-фреймворков.
ВКАЧЕСТВЕJS-ДВИЖКА
ВNODEИСПОЛЬЗУЕТСЯ НЕПРЕВЗОЙДЕННЫЙ ГУГЛОВСКИЙV8
106 |
ХАКЕР 10/153/2011 |
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|||
|
|
X |
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
|
NOW! |
o |
|||
|
|
|
|
|
|
|
||||
|
|
|
|
|
BUY |
|
|
|||
|
|
|
|
to |
|
|
|
|
|
|
w Click |
|
|
|
|
|
m |
||||
|
|
|
|
|
|
|||||
w |
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-xcha |
|
|
|
|
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|||
|
|
X |
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
|
NOW! |
o |
|||
|
|
|
|
|
|
|
||||
|
|
|
|
|
BUY |
|
|
|||
|
|
|
|
to |
|
|
|
|
|
|
JavaScriptдлясервераw Click |
|
|
|
|
|
m |
||||
|
|
|
|
|
|
|||||
w |
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-x cha |
|
|
|
|
Процессразработки
каждоесоединениесклиентом.Каждыйасинхронныйцикл,под- держивающийлогикуобработкиlong-polling-соединения,естмно- горесурсов.Поэтомунадопостаратьсяужатьвсеводинцикл.Для этогокаждоесоединениепредставим«плоским»наборомданных, которыйбудетсодержатьинформациюотом,вкакоймоментего необходимообнулить,отправивсодержимоеклиенту.Дляэтогов файлеio.jsнапишемследующийкод:
//Интервал проверки наличия новых данных var CHECK_INTERVAL = 1000;
//Максимальньное количество итераций перед
//обязательным сбросом
var MAX_LOOP_COUNT = 10;
//Таблица соединений var connections = {};
//Данные соединения
var LongPollingData = function() { this.loopCount = MAX_LOOP_COUNT;
this.dataQueue = [];
this.response = null; };
// Итерация основного цикла
function mainLoopIteration() {
var pollingData;
for (var id in connections) {
pollingData = connections[id];
if (pollingData.response) { // Если клиент подключен
pollingData.loopCount--;
// ...и имеет данные либо долго висит без дела
if (pollingData.dataQueue.length
|| pollingData.loopCount === 0)
flush(pollingData); // ...сбрасываем его
}
}
}
function flush(pollingData) {
pollingData.response.end(pollingData.dataQueue.join('|'));
pollingData.dataQueue = [];
pollingData.response = null;
}
setInterval(mainLoopIteration, CHECK_INTERVAL);
Каждуюитерациювсесоединенияпроверяютсянаналичиеданныхилинапростой.Вслучае,есливсоединениепришлиданныелибо, наоборот,давноничегонеприходило,текущийзапрособрываетсяс отправкойданных.Послечегоклиентдолженпослатьновыйзапрос. Практическибиблиотекаготова!Теперьнеобходимодатьвозможностьклиентуполучатьсвойидентификаториобрабатывать«долгий»
запросданных,атакжезаписыватьданныевсоединение.
var lastID = 0; // Идентификатор последнего соединения // Регистрация клиента и возвращение его идентификатора var init = exports.init = function(request, response) {
var id = 'connection_' + ++lastID;
connections[id] = new LongPollingData();
response.end(id);
};
ХАКЕР 10/153/2011 |
107 |