| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
Передача данных через icmp.
Введение
Сам по себе протокол icmp является протоколом сетевого уровня, неотемлемой
частью IP, а именно средством для сообщения о различных ошибках. Он абсолютно
неприспособлен для передачи каких-либо данных. Вместо него вполне можно
было бы использовать и IP c каким-то "своим" кодом верхнего протокола.
Преобразование ненадежного протокола в надежный, скорее напоминает создание
протокола заново. То есть нам приходится превращать сетевой протокол в
транспортый, решающий совсем иные задачи.
Транспортный уровень - сердце всей иерархии протоколов, поскольку именно он
предоставляет приложениям надежные, независящие от нижних уровней сервисы. Он
необходим даже в сетях, с надежной доставкой данных, таких как ATM, поскольку
у пользователей нет средств для постоянного контроля всего сетевого уровня.
Поломка даже одного маршрутизатора, может прервать несколько десятков
соединений, и контроль этих проблем будет возложен на прикладной уровень.
Траспортный уровень отслеживает потерянные пакеты, пришедшие не по порядку,
содержащие ошибки. Также им обеспечивается прозрачность для приложений,
которым становится все равно как они подключены к Сети: через модем или
через спутник. Это достигается за счет использования одного и того же набора
примитивов для работы с сетью, предоставлямого транспортным уровенем.
Примитивы - это набор функций, обеспечивающих одни и те же действия,
для тех, кто их использует. Они могут быть реализованы самыми разными
способами - в виде библиотеки пользовательского уровня, или части ядра ОС.
Чтобы получить представление о них рассмотрим 4 примитива:
- LISTEN - ожидание подключения (пассивное подключение).
- CONNECTING - осуществление активного подключения.
- ESTABLISHED - соединение установлено ( объединение таких примитивов как SEND и RECV).
- CLOSE - закрыть соединение.
Поскольку большинство реализаций одного протокола предоставлет одинаковые или сходные
реализации примитивов, то нет никаких проблем связаться с другим концом Земного шара,
через сотни абсолютно разных сетей, притом связаться с компьютером абсолютно другой
архитектуры (хотя это уже больше проблема представления данных).
Любой примитив можно считать отображением состояния протокола на данный момент, и
поскольку они взаимосвязаны, можно построить граф преходов из одного примитива в другой,
и он будет отражать все возможные состояния транспортного уровня и переходы между ними:
+----<-----+
| |
+-<----IDLE----->+ |
| | +-<-+
| | |
LISTEN CONNECTING |
| | |
| | |
ESTABLISHED |
| |
| |
CLOSING----->---------+
Здесь добавлено еще одно состояние (IDLE), которое характеризуется полным отсутствием
каких либо действий. То есть соединение и не открыто и не закрыто. Это стартовая точка.
Соединение начинается этим состоянием и им заканчивается. Заметьте, что в разных
протоколах набор состояний может быть разным, но минимальный набор примитивов
представлен выше, это, так сказать, хребет транспортной сущности.
Но транспортный протокол совершенно не обязательно должен быть ориентированным на
соединение и делать все выше названное, он может предоставлять и ненадежный сервис,
как это делает например, UDP. Сказать о нем почти нечего, за исключением того, что
он работает быстрее чем TCP (но только в случае когда надо послать одну
датаграмму и одну получить, то есть не передавать массив данных),
и в принципе основная его функция - осуществление мультиплексирования датаграмм
между процессами, и удобный интерфейс.
Ненадежность протоколов более низкого уровня, (это не недостаток, просто они
решают совсем другие задачи) таких как IP, ICMP, еще более существенны. Но как раз они
играют большую роль в компьютерной безопасности. Поскольку разрешив доступ, например
эхо запросу, мы практически открываем злоумышленнику двери в сеть. Ибо ничто ему не
помешает создать туннель использующий для передачи данных протокол ICMP (естественно ему
нужна еще возможность запустить в сети свой серверный компонент туннелирования ;). Вот об
этом мы как раз и поговорим.
Самый нижний уровень.
Основным способом создать свою датаграмму в Linux и *BSD является
использоваиние сокетов, помеченых как RAW_SOCK. Создав его мы получаем
доступ ко всем заголовкам начиная от ip, заканчавая tcp. Правда
прочитать мы сможем только датаграммы ip с неизвестным кодом протокола или
icmp,igmp. Сегменты TCP и UDP не доставляются на "сырые" сокеты. Для
перехвата этих пакетов нужно использовать интерфейс к канальному уровню,
например AF_PACKET в Linux или BPF в *BSD (хотя лучше использовать
библиотеку libpcap). Но нам это и не нужно. В архиве вы найдете реализацию
функции отправляющий пакеты через raw сокеты. Рассмотрев ее вы поймете, как их
использовать. (Заметьте, что мы формируем все заколовки, кроме заголовка ip.
Ядро формирует его самостоятельно. Для того чтобы самим создавать ip заголовок
нужно включить опцию сокета IP_HDRINCL.)
Заголовок ICMP.
Заголовок ICMP хранится в файле netinet/ip_icmp.h. Описан он
в структуре struct icmp. Не будем детально рассмтриваь здесь формат
заголовка, и как мы будем использовать его поля, об этом будет рассказано
позднее.
Потеря пакетов.
Вероятность потери ICMP запросов очень велика, потому что в мире растет
число маршрутизаторов, которые их отфильтровывают. Для защиты от потери
пакетов мы воспользуемся почти тем же средством, что и TCP - подтверждениями
(ack). Каждый пакет будет нести в себе свой порядовый номер, в ответном
пакете будет высылаться такой же номер подтверждения. Отличием от подобной
реализации в TCP является то, что в нем подтверждается каждый байт(поле seq
содержит порядковый номер первого байта в пакете).
До подтверждения все отправленные пакеты будут храниться в специальном
массиве. Для каждого пакета будет запущен таймер переотправки, когда он
сработает пакет будет отправлен заново. Если в течении 5 таймаутов (см. ниже)
не будет получено подтверждение, соединение будет считаться разорванным.
Таймауты.
Для вычисления таймаутов повторной передачи (RTO) мы воспользуемся
cредством опять же взятом из tcp, а именно, будем вычислять сглаженную
оценку RTT(время между отправкой пакета и получением подтверждения на
него). Основная проблема здесь в том, что RTT может значительно меняться
со временем, от нескольких микросекунд до нескольких секунд,
на это влияет расстояние, скорость сети, перегрузки...
Итак нам потребуется получить значение этого самого RTT, вычислить
srtt - сглаженную оценку RTT, и rttvar - сглаженную оценку
среднего отклонения. Из этих двух значений можно получить RTO,
сложив их и умножив на 4.
Вот несколько формул, которыми мы воспользуемся (паписаны в нотации
языка С).
delta = RTT - srtt;
srtt = srtt + g * delta;
rttvar = rttvar + h * (abs(delta) - rttvar);
RTO = srtt + 4 * rttvar;
g - это приращение RTT равное 1/8.
h - это приращение rttvar, равное 1/4.
Если вас интересует математическая сторона этих формул, смотрите
статью Джекобсона (Jacobson) в SIGCOMM'88, приложение А.
Заметьте, что для каждого сегмента будет запущен свой таймер.
Также будут использоваться отложенные подтверждения. Это значит,
что подтверждения будут отправляться не сразу, а ждать пока будет отправлен
ответный пакет с данными, чтобы отправиться с ним. Это уменьшит число
маленьких пакетов в сети. Буфер с отложенными подтверждениями каждые
200-300 мкс будет опустошаться (в отличие от повторной передачи таймер
запускается не для каждого подтверждения, а для всех в буфере).
Данные приходящие не по порядку.
Для решения проблемы решено завести таблицу, в которой будут храниться
сегменты, порядковые номера которых больше тех, что ожидаются.
Потом, при каждом пришедшем сегменте будет просмотрена таблица, и если там
есть следующий(ие) по порядку сегмент(ы) они будут добавлены в
буфер к новым данным.
Перегрузка.
При возникновении перегрузки в сети маршрутизаторы в первую очередь начнут
откидывать ICMP и UDP пакеты, поэтому важно не способствовать их появлению в
сети. Для этого реализован еще один алгоритм из tcp, но не совсем так, как в
нем.
Существует переменная (cwnd), в которой хранится "окно перегрузки", то есть
максимальное число сегментов, которые можно выслать не дожидаясь
подтверждения. С каждой повторной передачей окно перегрузки уменьшается на 1.
Как только придут все подтверждения, которые мы ждем, причем
не будет ни одной повторной передачи, оно увеличится в 2 раза (не совсем
верная реализация алгоритма "начального разгона").
Управление потоком.
В каждом пакете есть указание (структура header, переменная window),
сколько еще байт в состоянии принять отправитель этого пакета. Если для
пришедшего пакета нет места в буфере, то он отбрасывается,
без каких-либо еще действий.
Реализация.
Для обеспечения асинхронности ввода-вывода используется поток,
в котором и происходит управление _установленным_ соединением. В основном
потоке происходит установление/разрыв соединения, чтение и запись.
Для реализации таймеров используестя следующий прием:
при каждом событии (приход пакета, таймаут select()) вызываеся обработчик
таймаута, который проверяет прошло ли еще необходимое количество микросекунд
("точность" таймера - 10000), и если прошло, начинает свое выполнение. Таким
образом таймер срабатывает один раз в 10000 микросекунд.
Структуры данных.
Ниже приведен заголовочный файл с комментариями. По ходу развития
программы он, возможно, будет немного меняться.
#ifndef ICMPWRAP_H
#define ICMPWRAP_H
#define __USE_BSD
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <netdb.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <arpa/inet.h>
#include <pthread.h>
#include "mytable.h"
#include "timer.h"
#include "rtt.h"
#include "buffer.h"
#define MAXDATASIZE 512 //Максимальный размер данных в пакете.
#define MAXSEQN 4294967295UL //2^32 - 1.
#define MAXPACKETSIZE (sizeof(struct icmp)+MAXDATASIZE+sizeof(header))
#define DEFLTIMEOUT (PRECISION+5) /* Чтобы select() не вызывала таймаут
* слишком часто,
* он ведь все равно обработается не чаще
* чем через presicion секунд с предыдущего
* вызова.
* (PRECISION определен в timer.h)
*/
#define MAXSEGMENTN 24 //Максимальное кол-во сегментов в буферах.
#define BUFSIZE MAXSEGMENTN*MAXDATASIZE
#define ACKTBLSIZE MAXSEGMENTN //Максимальное количество отложенных подтверждений.
//Состояния соединения.
typedef enum {LISTEN,CONNECTING,ESTABLISHED,CLOSING,CLOSED,ERROR} cstate;
//Типы запросов.
enum {CONNREQ, CONNACC, DISCONNREQ, DISCONNACC};
//Макросы.
#define min(a,b) ((a<b)?a:b)
#define max(a,b) ((a>b)?a:b)
//Получает общий размер отправляемой датаграммы включая все заголовки.
#define getMessgSize(a) (sizeof(struct icmp)+sizeof(header)+\
ntohs(((header *)(a+sizeof(struct icmp)))->len))
#define incSeq(a) ((a==MAXSEQN)?1:a+1) /* Если увеличенное значение
* становится равно 0 (при переполнении)
* возвращаем 1; Это нужно,
* чтобы увеличенное значение seq
* никогда не стало равно 0
*/
//Заголовок пакета.
typedef struct
{
u_int8_t flag; //Флаги. Используется для передачи опций типа CONNREQ.
u_int32_t seq; //Номер сообщения.
u_int32_t ack; //Номер подвержденного сообщения.
u_int16_t len; //Длина сообщения.
u_int32_t window; //Максимальная длина сегмента, который сможет принять отправитель.
}header;
//Сокет.
typedef struct icmpSock
{
int fd;
u_int16_t magic; //С помощью этих чисел определяем, отправлена ли
//датаграмма именно нам.
u_int16_t peerMagic; //Первое число - устанавливаем мы, второе другая
//сторона.
pthread_t ppid; //Идентификатор нити.
struct sockaddr_in to; //Адрес собеседника.
header local; //Заголовок последнего отправленного сообщения.
header peer; //Заголовок последнего полученного сообщения.
//В них хранятся текущие размеры окна, наачения seq..
//Все данные в этих структурах хранятся с порядком байт хоста.
timers Timers; //Другие таймеры.
struct rttInfo rtt; //Переменные для вычисления RTO.
struct cwndInfo Cwnd; //Окно перегрузки
pthread_mutex_t inProgress; //Если мьютекс заблокирован, значит происходит
//некая операция над сокетом
pthread_cond_t canWrite; //Сигнализирует о возможности записи
//новой порции данных.
pthread_cond_t canRead; //Сигнализирует о возможности чтения
//новой порции данных.
cstate status; //Текущее состояние соединения.
table *unAckedTable; //Таблица неподтвержденных сегментов
table *confusedTable; //Таблица сегментов пришедших в беспорядке.
table *waitedAcks; //Таблица отложенных подтверждений.
buffer *input; //Здесь будут храниться получаемые данные.
buffer *output; //А здесь исходящие данные.
char error;
}icmpSock;
//Здесь должны быть определения ф-ций, но для экономии места
//мы их пропускаем.
#endif
Я не буду давать здесь исходный код функций работающих с буферами,
таблицами и сетью поскольку ничего сложного в них нет, и вы сможете изучить
их в прилагаемом архиве с исходниками.
Получение пакета.
Вот функция которая получает пакет, так сказать входная точка.
#include "icmpwrap.h"
void *
readPth (icmpSock * sock)
{
char inBuffer[IP_MAXPACKET]; //Буфер для приема ip датаграммы.
int rc;
int fds = sock->fd + 1;
fd_set rfd;
struct timeval timeout;
FD_ZERO (&rfd);
header *hdr =
(header *) (inBuffer + sizeof (struct icmp) + sizeof (struct ip));
while ((sock->status!=CLOSED) &&
(sock->status!=ERROR))
{
timeout.tv_sec = 0;
timeout.tv_usec = DEFLTIMEOUT;
FD_SET (sock->fd, &rfd);
rc = select (fds, &rfd, NULL, NULL, &timeout);
if (rc == -1)
{
if (errno == EINTR)
continue;
else
goto err;
}
if (rc == 0) //Таймаут
{
goto timeout;
}
rc =
recv (sock->fd, inBuffer, sizeof (inBuffer), 0);
if (rc == 0)
continue;
if (rc == -1)
{
if (errno == EINTR) //Если получение пакета было прервано сигналом,
//повторим.
continue;
else
goto err;
}
updateHeader (hdr);
/* Проверяем, для нас сообщение или нет.
* Проверяетcя чтобы все размеры были правильными
* Чтобы было верное число magic
*/
if ((rc = testMessage (inBuffer, sock, rc)))
{
continue;
}
//Происходит выбор типа датаграммы и ее обработка
//(данные, подтверждение и так далее..).
choice(sock,hdr);
timeout:
timeoutHandle(sock);
}
err:
//Будим всех, кто ждет событий, чтобы они тут же узнали об ошибке.
pthread_cond_broadcast (&sock->canRead);
pthread_cond_broadcast (&sock->canWrite);
printf ("Connection lost...\n");
return (void *) errno;
}
Определение типа пакета.
Ниже представлена одна из самых важных ф-ций, определяющая и обрабатывающая
пакеты.
#include "icmpwrap.h"
//Если поле seq > sock->peer.seq, пришел пакет с данными,
//если поле ack > 0, пакет c подтверждением,
//если поле window > 0 пакет с (возможно) новым окном.
int
choice (icmpSock * sock, header * hdr)
{
pthread_mutex_lock (&sock->inProgress);
if (hdr->ack != 0)
getAck (hdr, sock);
if (hdr->window != sock->peer.window)
resizePeerWindow (hdr, sock);
if (hdr->seq >= incSeq (sock->peer.seq))
{
if (hdr->seq == incSeq (sock->peer.seq))
{
getNewSegment (hdr, sock); //получен новый сегмент.
}
if (hdr->seq > incSeq (sock->peer.seq))
getConfusedSegment (hdr, sock); //Получаем сегмент,
//пришедший не по порядку.
refreshConfusedTable (sock); //Обновим таблицу беспорядочных сегментов
//Если удаленная сторона запрашивает отсоединения..
if(hdr->flag==DISCONNREQ)
{
passiveDisconnect(sock);
sock->status=CLOSED;
}
}
else if (hdr->seq != 0)
sendAck(sock,hdr->seq); //Старый пакет.
pthread_mutex_unlock (&sock->inProgress);
}
Обработчики пакетов.
Эти функции вызываются из choice(), для обработки различных пакетов.
Работа с окном перегрузки частично размазана между ними. Если что-то не
понятно в его работе, это станет понятно после рассмотрения кода таймера.
#include "icmpwrap.h"
//Устанвлнивает новое значение окна.
size_t setNewLocalWindSize(icmpSock *sock)
{
return (sock->local.window=bufFreeSpace(sock->input));
}
//Отправляет новый сегмент.
int sendSegment(icmpSock *sock,char *data,int len)
{
table *p;
u_int32_t ack=0;
if(addToUnAckTable(sock,data,len,incSeq(sock->local.seq))!=1)
return 0; //Не смогли сохранить неподтвержденное сообщение.
sock->local.seq=incSeq(sock->local.seq);
//Ищем одно из отложенных подтверждений, чтобы отправить его с
//новыми данными.
if((p=testTable(sock->waitedAcks,ACKTBLSIZE)))
{
ack=p->n;
deleteTableEnt(p);
}
//Запускаем ф-цию расчета RTT.
startRTT(sock,sock->local.seq);
return sendData(sock,data,(u_int16_t)len, ack);
}
//Подтверждает сегмент.
void
ackSegment(header * hdr, icmpSock * sock)
{
table *p;
//Сегмент пришел 2ой раз (видимо повторная передача), сразу подтврждаем.
if((p=findElement(sock->waitedAcks,hdr->seq,ACKTBLSIZE)))
{
deleteTableEnt(p);
goto rawAck;
}
if(!(p=findFree(sock->waitedAcks,ACKTBLSIZE)))
{
goto rawAck; //Если не можем "отложить" подтверждение,
//сразу подтвердим.
}
addToTable (p,NULL,0,hdr->seq,0);
return;
rawAck:
setNewLocalWindSize(sock);
sendAck(sock,hdr->seq);
return;
}
//Обрабатывает сегмент-подтверждение.
void
getAck (header * hdr, icmpSock * sock)
{
table *p;
if ((p = findElement (sock->unAckedTable, hdr->ack, MAXSEGMENTN)) == NULL)
return; //Этот сегмент уже продтвержден и удален из таблицы.
//Обновляем значение RTT ( ведь это время между отправкой сегмента и
//получением подтверждения на него)
updateRTT(sock,hdr);
deleteTableEnt (p);
//Здесь начинается работа с cwnd.
updateCwnd(&sock->Cwnd,PACK_ACK);
}
//Изменяет в параметрах сокета размер окна удаленной стороны.
inline void
resizePeerWindow (header * hdr, icmpSock * sock)
{
sock->peer.window = hdr->window;
}
//Отправляет новое окно.
void
resizeLocalWindow (icmpSock * sock)
{
u_int32_t old;
old = sock->local.window;
setNewLocalWindSize(sock);
//Отошлем новое значение окна, только если оно меняется с(на) ноль.
if ((old!=sock->local.window) &&
((old==0) || (sock->local.window==0)))
sendNewWindow (sock);
}
//Пытается найти в таблице с беспорядочными сегментами те,
//которые следуют за последним,который пришел по порядку
//(то есть если пришел сегмент 5, ищет 6,7,8,9...).
void
refreshConfusedTable (icmpSock * sock)
{
int i = 0;
table *p;
sortTable(sock->confusedTable,MAXSEGMENTN);
if((p=findElement(sock->confusedTable, incSeq(sock->peer.seq), MAXSEGMENTN))==NULL)
return;
for(;i<=MAXSEGMENTN && (p->n==incSeq(sock->peer.seq)); i++,p++)
{
if(bufFreeSpace(sock->input) < p->len)
break;
addToBuffer(sock->input,p->data,p->len);
sock->peer.seq=p->n;
deleteTableEnt(p);
}
pthread_cond_signal(&sock->canRead); //Игнорируем ошибку (может быть
//ничего не добавили в буффер)
}
//Принимает новый сегмент (пришел по порядку).
void
getNewSegment (header * hdr, icmpSock * sock)
{
char *buf = (char *) (hdr + 1);
//Не хватает места в буфере, игнорируем пакет.
//При такой реализации мы не можем добавить в буфер "кусок" пакета.
if(bufFreeSpace(sock->input) < hdr->len)
return;
addToBuffer(sock->input, buf, hdr->len);
sock->peer.seq = hdr->seq;
ackSegment(hdr,sock); //Подтвердим приход пакета.
pthread_cond_signal(&sock->canRead);
}
//Получает беспорядочные сегменты, если в таблице нет места пакет игнорируется.
void
getConfusedSegment (header * hdr, icmpSock * sock)
{
table *p;
char *buf = (char *) (hdr + 1);
//Повтор.
if (findElement (sock->confusedTable, hdr->seq,MAXSEGMENTN) != NULL)
{
return;
}
if ((p = findFree (sock->confusedTable,MAXSEGMENTN)) == NULL) //Нет места.
return;
addToTable (p, buf, hdr->len, hdr->seq, 0);
ackSegment (hdr,sock); //Подтвердим приход пакета.
}
//Опустошает буфер с иходящими данными, советуясь с окном перегрузки.
void flushBuffer(icmpSock *sock)
{
size_t bytes; //Максимальное число байт, которое мы можем записать.
size_t out; //Число байтов в выходном буфере.
char buf[MAXDATASIZE];
bytes=min(sock->peer.window,getCwnd(&sock->Cwnd)*MAXDATASIZE);
if(bytes==0)
return;
if((out=inBuffer(sock->output))==0)
return;
bytes=min(bytes,out);
while(bytes)
{
if(findFree(sock->unAckedTable,MAXSEGMENTN)==NULL)
break;
out=getFromBuffer(sock->output,buf,min(bytes,MAXDATASIZE));
if(sendSegment(sock,buf,out)<=0)
break;
bytes-=out;
updateCwnd(&sock->Cwnd,PACK_SEND);
}
pthread_cond_signal(&sock->canWrite);
}
Работа с таймером.
Вот исходник, вначале идет заголовочный файл.
#define WINDPROBETIME 100000000
#define ACKTIMEOUT 200000 //200 мс.
#define PRECISION 10000 //Каждые precision микросекунд будет срабатывать
//обработчик таймера.
#define LIVE 5 // Максимальное число переотправки одного и того же сегмента.
typedef struct
{
time_t windProbe; //Таймаут посылки пробы окна
time_t waitAck; //Таймаут отсылки неподтвержденных сегментов.
struct timeval prev; //Время последнего изменения в микросекундах.
}timers;
//"Вычитает" структуру timeval из переменной t, в которой хранится значение
//в микросекундах.
inline void
updateTimer (time_t * t, struct timeval *i)
{
time_t p = ((i->tv_sec * 1000000) + i->tv_usec);
if (p > (*t))
*t = 0;
else
*t = (*t) - p;
}
//Возвращает 1 если прошло достаточно времени с момента предыдущего вызова.
//В структуре now возвращается прошедшее число секунд/микросекунд с
//момента old.
int
checkTimer (struct timeval *now, struct timeval *old)
{
time_t p;
tvSub (now, old);
p = ((now->tv_sec * 1000000) + now->tv_usec);
if (p>=PRECISION)
return 1;
return 0;
}
//Собственно сам обработчик таймаута.
void
timeoutHandle (icmpSock * sock)
{
struct timeval now; //Текущее время.
struct timeval interval; //Число микросекуд прошедших с предидущего вызова
if (gettimeofday (&now, NULL) == -1)
return;
interval = now;
pthread_mutex_lock (&sock->inProgress);
if (checkTimer (&interval, &sock->Timers.prev) == 0)
goto end;
sock->Timers.prev = now;
waitedAckHandle (sock, &interval);
resendHandle (sock, &interval);
winProbeHandle (sock, &interval);
flushBuffer(sock);
end:
pthread_mutex_unlock (&sock->inProgress);
}
//Отправляет отложенные подтверждения.
void
waitedAckHandle (icmpSock * sock, struct timeval *interval)
{
int i;
table *p = sock->waitedAcks;
updateTimer (&sock->Timers.waitAck, interval);
if (sock->Timers.waitAck == 0)
{
for (i = 0; i <= ACKTBLSIZE; i++, p++)
{
if (p->n != 0)
{
sendAck (sock, p->n);
deleteTableEnt (p); //Удаляем.
}
}
sock->Timers.waitAck = ACKTIMEOUT;
}
}
//Выполняет повторную передачу.
void
resendHandle (icmpSock * sock, struct timeval *interval)
{
int i;
table *p = sock->unAckedTable;
sortTable (p, MAXSEGMENTN);
for (i = 0; i <= MAXSEGMENTN; i++, p++)
{
if (p->n != 0)
{
updateTimer (&p->timeout, interval);
if (p->timeout == 0)
{
createPacket (sock->fd, p->n, 0, sock->local.window,
sock->magic, &sock->to, p->data, p->len);
//Возможно был потерян пакет "которым" мерили RTT
if(rttLost (&sock->rtt, p->n)==-1)
{
sock->status=ERROR; //Если этот пакет терялся слшком много раз.
return;
}
p->timeout = rttTimeout (sock->rtt);
updateCwnd(&sock->Cwnd,PACK_LOST);
}
}
}
}
//Если абонент долго молчит пошлем окно.
//Чтобы он не думал, что оно у нас равно 0.
void
winProbeHandle (icmpSock * sock, struct timeval *interval)
{
updateTimer (&sock->Timers.windProbe, interval);
if (sock->Timers.windProbe == 0)
{
sendNewWindow (sock);
sock->Timers.windProbe = WINDPROBETIME;
}
}
//Обновляет таймаут пересылки.
inline void
updateRTT (icmpSock * sock, header * hdr)
{
rttUpdate (&sock->rtt, hdr->ack);
}
inline void
startRTT (icmpSock * sock, u_int32_t seq)
{
rttStart (&sock->rtt, seq);
}
Расчет RTT и RTO.
Заголовочный файл:
#include <sys/types.h>
#include <sys/time.h>
#include <stdio.h>
#define RTT_RXTMIN 2 //Минимальное значение таймаута для повторной передачи в секундах
#define RTT_RXTMAX 60 //Максимальное значение таймаута для повторной передачи в секундах.
#define RTT_LIVE 5 //Максимальное количество повторных передач для одного сегмента в секундах.
/*
* Этот макрос вычисляет RTO на основе текущих значений:
* RTO = RTT + (4xRTTVAR)
* SRTT - сглаженная величина отклонения в микросекундах.
*/
#define RTOCALC(p) (p->rtt_srtt + (4 * p->rtt_rttvar))
//Возвращает значение таймаута.
#define rttTimeout(p) (time_t)(p.rtt_rto)
struct rttInfo
{
time_t rtt_send; //Время отправки тестового пакета
//в микросекундах с момента rtt_base (см. ниже)
//Если равно 0, то пакет был перепослан и нужно
//отправить еще один тестовый пакет.
time_t rtt_seq; //Номер тестового пакета.
time_t rtt_rtt; //Последнее измеренное значение rtt в микросекундах
time_t rtt_srtt; //Сглаженное значение rtt в микросекундах
time_t rtt_rttvar; //Среднее значение отклонения в микросекундах
time_t rtt_rto; //Текщее значение rto в микросекундах.
time_t rtt_base; //Количество Секунд с начала эпохи на момент запуска
//программы.
int rtt_live;
};
Исходный код:
#include "rtt.h"
//Преобразует текущее время в число микросекунд
//прошедших с момента запуска программы.
time_t
localTime (struct rttInfo *info)
{
struct timeval tv;
if (gettimeofday (&tv, NULL) == -1)
return -1;
return ((tv.tv_sec * 1000000 + tv.tv_usec) - info->rtt_base);
}
//Проверяет, не превысило ли значение RTO установленные
//пределы снизу и сверху.
time_t
rtoMinMax (time_t rto)
{
if (rto < (RTT_RXTMIN * 1000000))
return (RTT_RXTMIN * 1000000);
else if (rto > (RTT_RXTMAX * 1000000))
return (RTT_RXTMAX * 1000000);
return rto;
}
//Инициализирует структуру rttInfo.
int
rttInit (struct rttInfo *info)
{
info->rtt_base = 0;
info->rtt_base = localTime (info);
info->rtt_seq = 0;
info->rtt_rtt = 0;
info->rtt_srtt = 0;
info->rtt_rttvar = 750000;
info->rtt_rto = rtoMinMax (RTOCALC (info)); //Вычисляем первое rto;
info->rtt_live=RTT_LIVE;
return 0;
}
//Эта функция должна выполнятся после отправки пакета.
void
rttStart (struct rttInfo *info, u_int32_t seq)
{
if (info->rtt_seq != 0)
return; //Вычисление уже проводится c другим пакетом.
if ((info->rtt_send = localTime (info)) == -1)
return;
info->rtt_seq = seq;
info->rtt_live=RTT_LIVE;
}
//Вычисляет RTO
void
rttUpdate (struct rttInfo *info, u_int32_t ack)
{
time_t delta;
//Проверяем был ли отправлен тестовый пакет
if (info->rtt_seq == ack) //И тот ли это пакет, который мы ждем.
{
info->rtt_rtt = localTime (info) - info->rtt_send;
delta = info->rtt_rtt - info->rtt_srtt;
info->rtt_srtt += delta / 8; //g=1/8
if (delta < 0)
delta = -delta; //abs(delta)
info->rtt_rttvar += (delta - info->rtt_rttvar) / 4; //р=1/4
info->rtt_rto = rtoMinMax (RTOCALC (info));
info->rtt_seq = 0;
}
}
//Обрабатывает ситуацию, когда потерян тестовый пакет.
//В этом случае новый таймаут равен старому умноженному на 2.
//Если была повторная передача то нельзя расчитать RTT по
//пришедшему подтверждению
//Так как непонятно на какой из двух пакетов оно пришло.
void
rttLost (struct rttInfo *info, u_int32_t ack)
{
if (info->rtt_seq == ack)
{
info->rtt_seq = 0; //Помечаем, что нужно новые вычисление.
info->rtt_rto = rtoMinMax (info->rtt_rto * 2);
info->rtt_live--;
if(!info->rtt_live)
return -1;
}
}
Реализация окна перегрузки.
В TCP все реализуется далеко не так просто как здесь, реализация
в нем хорошо описана у Стивенса в книге "TCP/IP Illustrated"
#define ALL_ACKED 1
#define PACK_LOST 2
#define PACK_SEND 3
#define PACK_ACK 4
#define MAXCWND 256
typedef struct cwndInfo
{
int dyn_cwnd; //Изменяется динамичски, при патере пакета.
int cwnd; //Всего не дожидаясь подтверждения можно отправить.
int maxWrite; //Можно еще отправить, не дожидаясь подтверждения.
int sendet; //Всего одновременно отправлено.
int acked; //Из них подтверждено.
}cwndInfo;
#include "cwnd.h"
int
cwndInit (cwndInfo * info)
{
info->cwnd = 1;
info->dyn_cwnd = 1;
info->maxWrite = 1;
info->sendet = 0;
info->acked = 0;
}
//Возвращает сколько еще сегментов можно отправить не дожидаясь подтверждения.
int
getCwnd (cwndInfo * info)
{
if (info->acked == info->sendet && (info->sendet != 0))//Если все
//отправленные пакеты
//подтверждены, изменим cwnd и
{ //вернем новое значение.
updateCwnd (info, ALL_ACKED);
}
return info->maxWrite;
}
void
updateCwnd (cwndInfo * info, int event)
{
switch (event)
{
case PACK_ACK:
info->acked++;
if (info->acked < info->sendet)
break;
case ALL_ACKED:
if (info->cwnd == info->dyn_cwnd) //Если ни один пакет не потерялся.
info->dyn_cwnd=min((info->dyn_cwnd * 2),MAXCWND);
info->cwnd = info->dyn_cwnd;
info->sendet = 0;
info->acked = 0;
info->maxWrite=info->cwnd;
break;
case PACK_LOST:
if (info->dyn_cwnd > 1)
info->dyn_cwnd--;
break;
case PACK_SEND:
if (info->maxWrite > 0)
info->maxWrite--;
info->sendet++;
break;
}
}
Установка и разрыв соединения.
Вот одни из самых сложных ф-йй. Они устанавливают и разрывают соединение.
Для установки соединения используется тройное рукопожатие, как в tcp.
Установка соедиения:
//Активно пытается запросить соединение.
int
activeConnect (icmpSock * sock)
{
int tryes = 5;
char inBuffer[IP_MAXPACKET];
int timeout = 5;
int rc;
header *hdr =
(header *) (inBuffer + sizeof (struct icmp) + sizeof (struct ip));
struct icmp *Icmp = (struct icmp *) (inBuffer + sizeof (struct ip));
memset(inBuffer,0,sizeof(inBuffer)); //На всякий случай, если придет "левый" пакет.
sock->status=CONNECTING;
while (tryes>0)
{
//Отправляем запрос соединения.
if (sendConnectReq (sock) == -1)
break;
receiving:
//Ф-ция recvAnswer пытестся получить пакет (recv) в течение timeout секунд.
if ((rc = recvAnswer (sock, inBuffer, sizeof(inBuffer), timeout)) == -1)
{
if (errno == EWOULDBLOCK) //Таймаут
{
tryes--;
timeout *= 2;
continue;
}
break;
}
updateHeader (hdr);
sock->peerMagic = ntohs (Icmp->icmp_id);
//Проверяем заголовок в полученном ответе.
if((sock->peerMagic == sock->magic)) //Это наш собственный пакет.
goto receiving;
if ((hdr->ack != sock->local.seq) ||
((hdr->flag != CONNACC)))
continue;
sock->peer = (*hdr);
if (sendAck (sock, sock->peer.seq) == -1)
break;
sock->status = ESTABLISHED;
return 0;
}
errno=ETIMEDOUT;
sock->status = ERROR;
return -1;
}
//Принимает соединение.
int
passiveConnect (icmpSock * sock)
{
char inBuffer[IP_MAXPACKET];
int tryes = 5;
int rc;
int timeout = 5;
header *hdr =
(header *) (inBuffer + sizeof (struct icmp) + sizeof (struct ip));
struct icmp *Icmp = (struct icmp *) (inBuffer + sizeof (struct ip));
memset(inBuffer,0,sizeof(inBuffer));
sock->status=LISTEN;
again:
//Получаем запрос соедиения.
if (recvAnswer (sock, inBuffer,sizeof(inBuffer), 0) == -1)
goto exit;
updateHeader (hdr);
if (hdr->flag != CONNREQ)
goto again;
sock->peer = (*hdr);
sock->peerMagic = ntohs (Icmp->icmp_id);
while (tryes)
{
//Отправляем подтверждение соедиенения.\n");
if (sendConnectAck (sock) == -1)
break;
if ((rc = recvAnswer (sock, inBuffer,sizeof(inBuffer), timeout)) == -1)
{
if (errno == EWOULDBLOCK) //Таймаут
{
tryes--;
timeout *= 2;
continue;
}
break;
}
updateHeader (hdr);
if ((hdr->ack != sock->local.seq) ||
(sock->peerMagic != ntohs (Icmp->icmp_id)))
continue;
sock->status = ESTABLISHED;
return 0;
}
exit:
sock->status = ERROR;
return -1;
}
//Активно разрывает соединение.
int
activeDisconnect (icmpSock * sock)
{
int tryes = 5;
char inBuffer[IP_MAXPACKET];
int timeout = 5;
int rc;
header *hdr =
(header *) (inBuffer + sizeof (struct icmp) + sizeof (struct ip));
struct icmp *Icmp = (struct icmp *) (inBuffer + sizeof (struct ip));
memset(inBuffer,0,sizeof(inBuffer));
sock->status=CLOSING;
sock->local.seq=incSeq(sock->local.seq);
while (tryes>0)
{
if (sendDisconnectReq (sock) == -1)
break;
if ((rc = recvAnswer (sock, inBuffer,sizeof(inBuffer), timeout)) == -1)
{
if (errno == EWOULDBLOCK) //Таймаут
{
tryes--;
timeout *= 2;
continue;
}
break;
}
updateHeader (hdr);
if ((hdr->ack != sock->local.seq) ||
((hdr->flag != DISCONNACC) || (sock->peerMagic != ntohs (Icmp->icmp_id))))
continue;
sock->peer = (*hdr);
if (sendAck (sock, sock->peer.seq) == -1)
break;
sock->status = CLOSED;
break;
}
return 0;
}
int
passiveDisconnect (icmpSock * sock)
{
char inBuffer[IP_MAXPACKET];
int timeout = 5;
sendDisconnectAck (sock);
recvAnswer (sock, inBuffer,sizeof(inBuffer), timeout);
sock->status = CLOSED;
return -1;
}
The End.
Вот на этом вроде бы все. Придумайте как сделать, чтобы makeSocket
возврщала не указатель на icmpSock, а десктриптор (int), с которым можно
было бы работать обычными ф-циями read и write.
//oxid (Птн Мар 26 10:03:54 MSK 2004)
|
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |