Шаг 190 - Снифер на C#

В C# сокеты реализованы с помощью класса Socket. Давайте попробуем с помощью этого класса написать простенький снифер. Для начала я просто написал создал абстрактный класс BaseSocket, опишу только функции которые заслуживают внимания.

CreateAndBindSocket(string ip) - создаёт сокет, а затем биндит его на определённый IP

Shutdown() - прекращает все операции по приёму и посылке данных, после закрывает сокет.

SetSockoption() - устанавливает опцию включать IP заголовок в данные.

socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.HeaderIncluded, 1);

и переводит сокет в режим приёма всех пакетов. socket.IOControl(SIO_RCVALL, IN, OUT); IOControl - это аналог API функции WSAIoctl.

SIO_RCVALL равен 0x98000001 он указывает принимать все пакеты, на самом деле, дело обстоит иначе, если к примеру мы имеем на одной сетевой два ip из одной подсетки типа 10.100.101.130 и 10.100.101.160, то пакеты будут приниматься на эти два ip. Тем более учтите это всё работает только на (на милениуме не пробовал). И если бы будете посылать пинги на свою машину типа ping 10.100.101.130(это ваш ip) или просматривать страницу по этому адресу и т.д. Пакеты не будет отсылаться и следовательно не будут ловится. Идём далее, IN и OUT это входные параметры типа byte[], по идее в Platform SDK это должны быть как булевы переменные BOOL, поэтому я определил их длины равную 4(значение должно быть установлено в TRUE) поэтому я передаю в массиве 1.

Далее всё просто ловлю пакет через функцию BeginReceive Это функция асинхронного приёма Имеет следующий вид

public IAsyncResult BeginReceive
(
	byte[] buffer,
	int offset,
	int size,
	SocketFlags socketFlags,
	AsyncCallback callback,
	object state
);

Первый параметр массив байтов куда принимать данные, затем смещение от начала этого массива(может мы захотим принимать не в начало), затем размер, параметр socketFlags флаги для приёма, типа не роутить пакеты при посылке и всё такое прочие я установил его в None, callback это функция которая вызывается по идее когда начинается приём дынных, а затем state - в этот параметр мы можем предать всё что угодно, он передаётся в качестве единственного параметра функции callback. AsyncCallback делегат имеет следующий вид

 
public delegate void AsyncCallback(IAsyncResult ar);

Судя по тому, что написано в SDK мы должны были бы вызывать из этой функции EndReceive, чтобы завершить операцию чтения данных, но так как эта функция имеет следующий вид

public int EndReceive(IAsyncResult asyncResult);

И параметр asyncResult, является результатом который мы получаем после вызова BeginReceive, то нормально вызвать из callback, мы эту функцию не можем. Конечно, могли бы, но это чревато последствиями. Скажем если функция callback, выполнялась потом начался выполняться другой процесс, после во второй вызывается наше приложение callback ожидает, а мы что-то сделали с результатом вызова BeginReceive, в итоге мы не получим, то что хотели. Далее я использую следующую структуру

[StructLayout(LayoutKind.Explicit)]
public struct IpHeader
{
	[FieldOffset(0)] public byte ip_verlen; 		//версия ip и длина заголовка по 4 бита
	[FieldOffset(1)] public byte ip_tos; 		//тип сервиса
	[FieldOffset(2)] public ushort ip_totallength; 	//длина общая длина
	[FieldOffset(4)] public ushort ip_id; 		//уникальный идентификатор
	[FieldOffset(6)] public ushort ip_offset; 		//сперва 3 бита флаги затем 13 бит смещение
	[FieldOffset(8)] public byte ip_ttl; 		//ttl
	[FieldOffset(9)] public byte ip_protocol; 		//тип протокола tcp, udp, icmp и т.п.
	[FieldOffset(10)] public ushort ip_checksum;		//контрольная сумма
	[FieldOffset(12)] public long ip_srcaddr; 		//адрес от кого
	[FieldOffset(16)] public long ip_destaddr;		//адрес кому
}

Это заголовок IP пакета, не буду подробно описывать структуру заголовка, можете если хотите почитать RFC791 или если туго с английским страничку по адресу:

http://www.citforum.ru/internet/tifamily/ipspec.shtml

А про атрибут StructLayout я уже писал. Далее приняв пакет мне нужно вытащить IP заголовок, я бы мог это сделать с помощью класса Marshal, но там муторно слишком получается, или создать класс типа:

[StructLayout(LayoutKind.Explicit)]
class Rec
{
	[FieldOffset(0)] public IpHeader header;
	[FieldOffset(0)] public byte []receive_bytes;
}

Тоже не захотелось. Я решил это сделать с помощью unsafe code. Для начала я зафиксировал принятые данные, чтобы GC не переместил их в памяти. Мало ли что ему в голову стукнет.

 fixed(byte *fixed_buf = buf)

я в принципе хотел сразу же сделать

fixed(IpHeader*header = buf)

Но оказалось, что так делать нельзя, нужно сперва получить указатель на byte *. Далее всё просто вытащил длину, заголовка IP заголовка, тип протокола и т.д. Учтите кроме IP заголовка есть ещё UDP, TCP, ICMP и т.д. заголовки поэтому у меня длина данных без IP заголовка, это длина данных + длина заголовка конкретного заголовка. Пример длина всего пакета 40 байт, это пакет TCP примерно 20 байт из них заголовок IP пакета, 8 длина заголовка TCP и уже остальное данные. В своей программе я вытаскивал только порты для TCP и UDP. ICMP и IGMP пакеты я не разбирал, лень.


Загрузить проект | Предыдущий Шаг | Следующий Шаг | Оглавление
Автор Leonid Molochniy.