Как работать с сырыми сокетами (SOCK_RAW) | Часть 2


Как работать с сырыми сокетами (SOCK_RAW) | Часть 2

Вторая часть статьи на тему работы с «сырыми сокетами» SOCK RAW

Первая часть —>>

Третья часть —>>

Четвертая часть —>>

0x3. IP_HDRINCL

===============

Одно из самых серьезных решений при написании сетевых программ низкого уровня с
сырые сокеты, если приложение будет создавать вместе с транспортом
заголовок протокола уровня, а также заголовок IP. Было бы лучше сделать несколько
сначала историческое воспоминание, так как многое изменилось с давних времен.
В настоящее время очевидный способ указать IP-уровню не добавлять собственный заголовок
заключается в вызове системного вызова setsockopt (2) и установке IP_HDRINCL (заголовок
В комплекте) вариант. Однако не всегда такой вариант существовал. В выпусках до
Net / 3, не было параметра IP_HDRINCL и единственный способ не иметь ядра
добавить собственный заголовок, чтобы использовать определенные патчи ядра и установить протокол
как IPPROTO_RAW (inetsw [3] - запись с подстановочным знаком). Эти патчи были впервые сделаны
для 4.3BSD и Net / 1 для поддержки Traceroute, который должен был написать свой собственный
полные дейтаграммы IP, так как он перепутал поле TTL. Интересная часть
заключается в том, что с момента появления IP_HDRINCL Linux и FreeBSD выбрали разные пути
чтобы продолжить «традицию». В Linux при настройке протокола как IPPROTO_RAW,
тогда по умолчанию ядро ​​устанавливает параметр IP_HDRINCL и поэтому не добавляет
собственный IP-заголовок. Код ниже доказывает это.

/usr/src/linux-2.6.*/net/ipv4/af_inet.c

static int inet_create (struct net * net, struct socket * sock, протокол int)
{
/ * ... * /
if (SOCK_RAW == sock->type) {
		inet->num = protocol;
		if (IPPROTO_RAW == protocol)
			inet->hdrincl = 1;      /* set IP_HDRINCL */
	}
	/* ... */
}
/ * ... * / 
} 
Однако во FreeBSD ядро ​​никогда не устанавливает IP_HDRINCL по умолчанию, даже если IPPROTO_RAW используется. Это означает, что приложение должно явно установить вариант всякий раз, когда он хочет вручную создать свой собственный заголовок IP. Мы увидим ниже когда мы углубляемся в более подробную информацию о вводе / выводе сырых сокетов, некоторые IP-заголовки поля всегда устанавливались / задавались ядром. Подводя итог, можно сказать, что для создания переносимых приложений сырых сокетов, которые должны создавать свой собственный IP-заголовок, лучше всегда устанавливать IP_HDRINCL, так как это общий способ как для Linux, так и для * BSD.

0x4. сырой ввод

==============

После инициализации сырого сокета в нашем приложении мы должны знать заранее
какие дейтаграммы мы ожидаем получить. Принятый подход к реализации
здесь полностью отличается между Linux и FreeBSD и заслуживает нашего
внимание.

а. Linux

********

Linux выбрал менее ресурсоемкий подход. После обработки уровня IP
новая входящая IP-дейтаграмма, она вызывает функцию ядра ip_local_deliver_finish ()
который отвечает за вызов зарегистрированного обработчика транспортного протокола
проверка поля протокола IP-заголовка (помните из вышеизложенного). тем не мение
перед тем, как доставить дейтаграмму обработчику, он каждый раз проверяет,
приложение создало необработанный сокет с * тем же * номером протокола. Если здесь
одно или несколько таких приложений, оно делает копию дейтаграммы и доставляет
это им тоже.

/usr/src/linux-2.6.*/net/ipv4/ip_input.c
static int ip_local_deliver_finish(struct sk_buff *skb)
{
	__skb_pull(skb, ip_hdrlen(skb));

	/* Point into the IP datagram, just past the header. */
	skb_reset_transport_header(skb);

	rcu_read_lock();
	{
		/* Note: See raw.c and net/raw.h, RAWV4_HTABLE_SIZE==MAX_INET_PROTOS */
		int protocol = ip_hdr(skb)->protocol;
		int hash;
		struct sock *raw_sk;
		struct net_protocol *ipprot;

	resubmit:
		hash = protocol & (MAX_INET_PROTOS - 1);
		raw_sk = sk_head(&raw_v4_htable[hash]);

		/* If there maybe a raw socket we must check - if not we
		 * don't care less
		 */
		if (raw_sk && !raw_v4_input(skb, ip_hdr(skb), hash))
			raw_sk = NULL;

	/* ... */
	/* transport protocol handler calling */
	/* ... */

}
Здесь возникает вопрос, какая часть дейтаграммы передается в необработанный разъем? Только полезная нагрузка IP или вся дейтаграмма IP (вместе с IP-заголовком это)? Присмотревшись к приведенному выше коду, мы можем сделать вывод, что весь Дейтаграмма IP передана. Посмотрим, почему: 
Сначала у нас есть: 

__skb_pull (skb, ip_hdrlen (skb)); 

Это существенно сдвигает указатель данных sk_buff, чтобы он указывал чуть ниже заголовка IP (хотя это звучит противоречиво по сравнению с тем, что мы сделанное выше, дождитесь, пока вы прочитаете всю часть, прежде чем поднимать очевидные возражения). Это достигается путем добавления размера ip_hdrlen (skb) len к skb-> элемент данных. Это означает, что skb_data теперь указывает на полезную нагрузку IP - и поскольку пакет перемещается вверх по сетевому стеку, начало IP полезная нагрузка на самом деле является началом транспортного заголовка (скорее всего, TCP заголовок) 
__skb_pull определяется в: 
/usr/src/linux-2.6.*/include/linux/skbuff.h 

static inline unsigned char *__skb_pull(struct sk_buff *skb, unsigned int len)
{
	skb->len -= len;
	BUG_ON(skb->len < skb->data_len);
	return skb->data += len;
}

After __skb_pull we 've got another skb-relevant function being called:

skb_reset_transport_header(skb);
После __skb_pull у нас вызывается еще одна функция, относящаяся к skb: 

skb_reset_transport_header (skb); 

который обновляет член skb-> transport_header структуры skb чтобы указать в новом месте skb-> data, указанные непосредственно перед - угадайте, что - транспортный заголовок. 
skb_reset_transport_header (skb) определяется в: 
/usr/src/linux-2.6.*/include/linux/skbuff.h
static inline void skb_reset_transport_header(struct sk_buff *skb)
{
	skb->transport_header = skb->data;
}
Наша структура sk_buff теперь будет выглядеть так: 

sk_buff {			    buffer
				-------------- 	
.skb_data                       |	     |
	|                       --------------
        |                       | IP header  | <----
        |                       --------------     |
        -----------------> ->	| IP payload |     |
	                   |    |	     |     |
.transport_header ---------|    |	     |     |
	                        --------------	   |	
.network_header ------------------------------------

}
Итак, в какой части мы фактически видим, что * вся * дейтаграмма IP (вместе с IP-заголовок) отправляется пользовательскому процессу, создавшему необработанный сокет? Введите raw_v4_input (). 

Как следует из названия, это основной обработчик сырых сокетов, который вызывается эта строка в ip_local_deliver_finish ():
	if (raw_sk && !raw_v4_input(skb, ip_hdr(skb), hash))
Обратите внимание на второй аргумент ip_hdr (skb) для raw_v4_input. ip_hdr определяется в /usr/src/linux-2.6.*/include/linux/ip.h: 

static inline struct iphdr *ip_hdr(const struct sk_buff *skb)
{
	return (struct iphdr *)skb_network_header(skb);
}
а skb_network_header определяется в 
/usr/src/linux-2.6.*/include/linux/skbuff.h: 

static inline unsigned char *skb_network_header(const struct sk_buff *skb)
{
	return skb->network_header;
}
В основном это передает IP-заголовок текущего пакета в функция raw_v4_input как отдельная структура iphdr. 
Это делается путем литья часть данных skbuff, на которую указывает член skb-> network_header (начало заголовка IP) в структуру iphdr. 
Пришло время для анализа raw_v4_input: 

/usr/src/linux-2.6.*/net/ipv4/raw.c: 

/ * Обработка ввода IP идет сюда для дос
/* IP input processing comes here for RAW socket delivery.
 * Caller owns SKB, so we must make clones.
 *
 * RFC 1122: SHOULD pass TOS value up to the transport layer.
 * -> It does. And not only TOS, but all IP header.
 */
int raw_v4_input(struct sk_buff *skb, struct iphdr *iph, int hash)
{
	struct sock *sk;
	struct hlist_head *head;
	int delivered = 0;

	read_lock(&raw_v4_lock);
	head = &raw_v4_htable[hash];
	if (hlist_empty(head))
		goto out;
	sk = __raw_v4_lookup(__sk_head(head), iph->protocol,
			     iph->saddr, iph->daddr,
			     skb->dev->ifindex);

	while (sk) {
		delivered = 1;
		if (iph->protocol != IPPROTO_ICMP || !icmp_filter(sk, skb)) {
			struct sk_buff *clone = skb_clone(skb, GFP_ATOMIC);

			/* Not releasing hash table! */
			if (clone)
				raw_rcv(sk, clone);
		}
		sk = __raw_v4_lookup(sk_next(sk), iph->protocol,
				     iph->saddr, iph->daddr,
				     skb->dev->ifindex);
	}
out:
	read_unlock(&raw_v4_lock);
	return delivered;
}
Обратите внимание, что функция __raw_v4_lookup (), которая по существу проверяет наличие приложение создало необработанный сокет с определенным номером протокола упомянутый в заголовке IP, учитывает членский протокол iph-> как ожидал. Одна из наиболее важных частей приведенного выше кода - это skb_clone (). функция, которая вызывается столько раз, сколько приложений, необходимо получить копию текущей дейтаграммы. Это реализовано внутри пока цикл. Следующая часть - это вызов raw_rcv (): 

int raw_rcv(struct sock *sk, struct sk_buff *skb)
{
	if (!xfrm4_policy_check(sk, XFRM_POLICY_IN, skb)) {
		kfree_skb(skb);
		return NET_RX_DROP;
	}
	nf_reset(skb);

	skb_push(skb, skb->data - skb_network_header(skb));

	raw_rcv_skb(sk, skb);
	return 0;
}
Эта функция выполняет две задачи: (обязательно, чтобы понимается, поскольку это та часть, которая решает возможные возражения против наших предложение о передаче всей дейтаграммы IP в необработанный сокет) 
1) Вызывает skb_push (), чтобы вернуть skbuff в его старую форму. skb_push () - полная противоположность skb_pull ():
/**
 *	skb_push - add data to the start of a buffer
 *	@skb: buffer to use
 *	@len: amount of data to add
 *
 *	This function extends the used data area of the buffer at the buffer
 *	start. If this would exceed the total buffer headroom the kernel will
 *	panic. A pointer to the first byte of the extra data is returned.
 */
static inline unsigned char *skb_push(struct sk_buff *skb, unsigned int len)
{
	skb->data -= len;
	skb->len  += len;
	if (unlikely(skb->data<skb->head))
		skb_under_panic(skb, len, current_text_addr());
	return skb->data;
}
Так что теперь вспомните его аргументы и то, как выглядел skbuff из диаграмму выше.
	skb_push(skb, skb->data - skb_network_header(skb));
Теперь skbuff будет выглядеть так:
sk_buff {			    buffer
				-------------- 	
.skb_data                       |	     |
	|                       --------------
        |------------------>    | IP header  | <----
                                --------------     |
                           ->	| IP payload |     |
	                   |    |	     |     |
.transport_header ---------|    |	     |     |
	                        --------------	   |	
.network_header ------------------------------------

}
Вам может быть интересно, почему часть skb_pull () сверху, если мы все равно вернусь к этой форме, вызвав skb_push (), чтобы выполнить точный обратная операция? Ответ на этот вопрос заключается в том, что сырые сокеты - это промежуточное место, куда отправляются дейтаграммы. В обычном случае, когда нет сырых сокетов открыты для какого-то протокола IPPROTO_, тогда по мере продвижения пакета вверх по сети stack skb_pull () является естественным, так как пакет медленно лишается заголовки нижнего уровня. Однако этих накладных расходов можно было бы избежать, возможно, вызов skb_pull () после того, как механизм сырых сокетов обслуживается и, таким образом, избегает дополнительный skb_push () внутри raw_rcv (). (Это необходимо проверить) 

2) Вызывает raw_rcv_skb (), который отвечает за вызов общего функция, которая передает sk_buff сокету приложения (а также увеличить еще не реализованный счетчик необработанных отбрасываний в случае сбоя):
static int raw_rcv_skb(struct sock * sk, struct sk_buff * skb)
{
	/* Charge it to the socket. */

	if (sock_queue_rcv_skb(sk, skb) < 0) {
		/* FIXME: increment a raw drops counter here */
		kfree_skb(skb);
		return NET_RX_DROP;
	}

	return NET_RX_SUCCESS;
}
Из всего вышесказанного мы заключаем, что не только полезная нагрузка IP передается в необработанный сокет, но также и заголовок IP.

б. FreeBSD

**********

FreeBSD использует другой подход. Он * никогда * не передает пакеты TCP или UDP в необработанные
Розетки. Такие пакеты необходимо читать непосредственно на уровне канала передачи данных с помощью
библиотеки, такие как libpcap или bpf API. Он также * никогда * не пропускает фрагментированные
дейтаграмма. Каждая дейтаграмма должна быть полностью собрана перед передачей.
к сырому сокету.
FreeBSD переходит к сырому сокету:
а) каждая дейтаграмма IP с полем протокола, не зарегистрированным в
ядро
б) все пакеты IGMP после того, как ядро ​​завершит их обработку
c) все пакеты ICMP (кроме запроса эха, запроса отметки времени и адреса
запрос маски) после того, как ядро ​​завершит их обработку

Мы собираемся изучить первый случай, который представляет больший интерес. Но прежде
это вводный материал о том, как ядро ​​FreeBSD регистрирует
обработчики протоколов, необходимо изучить.

В части 0x2. Creation Мы упомянули несколько вещей о структурах protosw, которые
являются ассоциациями типа SOCK_XXX с протоколом IPPROTO_XXX и находятся в
глобальная таблица inetsw []. Кроме того, у них есть члены, которые
указатели функций на соответствующие обработчики протоколов (так называемые перехватчики).
pr_input () отвечает за обработку входящих данных из протокола более низкого уровня
в то время как pr_output () обрабатывает исходящие данные из протокола более высокого уровня.

/usr/src/sys/sys/protosw.h:

struct protosw {
короткий pr_type; / * тип сокета, используемый для * /
struct domain * pr_domain; / * протокол домена является членом * /
короткий pr_protocol; / * номер протокола * /
короткие pr_flags; /* см. ниже */
/ * ловушки протокол-протокол * /
pr_input_t * pr_input; / * ввод в протокол (снизу) * /
pr_output_t * pr_output; / * вывод в протокол (сверху) * /
pr_ctlinput_t * pr_ctlinput; / * управляющий ввод (снизу) * /
pr_ctloutput_t * pr_ctloutput; / * управляющий вывод (сверху) * /
/ * ловушка пользовательского протокола * /
pr_usrreq_t * pr_ousrreq;
/ * служебные перехватчики * /
pr_init_t * pr_init;
pr_fasttimo_t * pr_fasttimo; / * быстрый тайм-аут (200 мс) * /
pr_slowtimo_t * pr_slowtimo; / * медленный тайм-аут (500 мс) * /
pr_drain_t * pr_drain; / * очистить все возможное лишнее пространство * /

struct pr_usrreqs * pr_usrreqs; / * заменяет pr_usrreq () * /
};

Таким образом, уровень IP обычно вызывает соответствующий обработчик протокола.
pr_input () через эту таблицу при получении пакета. Чтобы узнать, какой протокол
к какому обработчику переходит фаза инициализации. Во-первых, все протосв
структуры инициализируются в /usr/src/sys/netinet/in_proto.c

Это похоже на то, что мы видели ранее в Linux на
/usr/src/linux-2.6.*/net/ipv4/af_inet.c

/usr/src/sys/netinet/in_proto.c:

struct protosw inetsw [] = {
{
.pr_type = 0,
.pr_domain = & inetdomain,
.pr_protocol = IPPROTO_IP,
.pr_init = ip_init,
.pr_slowtimo = ip_slowtimo,
.pr_drain = ip_drain,
.pr_usrreqs = & nousrreqs
},
{
.pr_type = SOCK_DGRAM,
.pr_domain = & inetdomain,
.pr_protocol = IPPROTO_UDP,
.pr_flags = PR_ATOMIC | PR_ADDR,
.pr_input = udp_input,
.pr_ctlinput = udp_ctlinput,
.pr_ctloutput = ip_ctloutput,
.pr_init = udp_init,
.pr_usrreqs = & udp_usrreqs
},
{
.pr_type = SOCK_STREAM,
.pr_domain = & inetdomain,
.pr_protocol = IPPROTO_TCP,
.pr_flags = PR_CONNREQUIRED | PR_IMPLOPCL | PR_WANTRCVD,
.pr_input = tcp_input,
.pr_ctlinput = tcp_ctlinput,
.pr_ctloutput = tcp_ctloutput,
.pr_init = tcp_init,
.pr_slowtimo = tcp_slowtimo,
.pr_drain = tcp_drain,
.pr_usrreqs = & tcp_usrreqs
},
/ * #ifdef SCTP * /
/ * ... * /
/ * #endif * /
{
.pr_type = SOCK_RAW,
.pr_domain = & inetdomain,
.pr_protocol = IPPROTO_RAW,
.pr_flags = PR_ATOMIC | PR_ADDR,
.pr_input = rip_input,
.pr_ctlinput = rip_ctlinput,
.pr_ctloutput = rip_ctloutput,
.pr_usrreqs = & rip_usrreqs
},
{
.pr_type = SOCK_RAW,
.pr_domain = & inetdomain,
.pr_protocol = IPPROTO_ICMP,
.pr_flags = PR_ATOMIC | PR_ADDR | PR_LASTHDR,
.pr_input = icmp_input,
.pr_ctloutput = rip_ctloutput,
.pr_usrreqs = & rip_usrreqs
},
{
.pr_type = SOCK_RAW,
.pr_domain = & inetdomain,
.pr_protocol = IPPROTO_IGMP,
.pr_flags = PR_ATOMIC | PR_ADDR | PR_LASTHDR,
.pr_input = igmp_input,
.pr_ctloutput = rip_ctloutput,
.pr_init = igmp_init,
.pr_fasttimo = igmp_fasttimo,
.pr_slowtimo = igmp_slowtimo,
.pr_usrreqs = & rip_usrreqs
},
{
.pr_type = SOCK_RAW,
.pr_domain = & inetdomain,
.pr_protocol = IPPROTO_RSVP,
.pr_flags = PR_ATOMIC | PR_ADDR | PR_LASTHDR,
.pr_input = rsvp_input,
.pr_ctloutput = rip_ctloutput,
.pr_usrreqs = & rip_usrreqs
},

Вопрос в том, как уровень IP понимает, к какому обработчику протокола он
следует передать пакет? Это делается путем проверки поля «Протокол» в
Заголовок IP (мы упоминали об этом выше), а затем поиск в таблице ip_protox []
который инициализируется функцией ip_init ().

/usr/src/sys/netinet/ip_input.c:

пустота
ip_init (недействительно)
{
struct protosw * pr;
int i;

TAILQ_INIT (& in_ifaddrhead);
in_ifaddrhashtbl = hashinit (INADDR_NHASH, M_IFADDR, & in_ifaddrhmask);
пр = pffindproto (PF_INET, IPPROTO_RAW, SOCK_RAW);
если (pr == NULL)
паника («ip_init: PF_INET не найден»);

/ * Инициализируем весь массив ip_protox [] как IPPROTO_RAW. * /
для (я = 0; я <IPPROTO_MAX; я ++)
ip_protox [i] = pr - inetsw;
/ *
* Прокрутите IP-протоколы и поместите их в соответствующее место
* в ip_protox [].
* /
для (pr = inetdomain.dom_protosw;
пр <inetdomain.dom_protoswNPROTOSW; пр ++)
если (pr-> pr_domain-> dom_family == PF_INET &&
pr-> pr_protocol && pr-> pr_protocol! = IPPROTO_RAW) {
/ * Будьте осторожны, индексируйте только действительные IP-протоколы. * /
если (пр-> пр_протокол <IPPROTO_MAX)
ip_protox [pr-> pr_protocol] = pr - inetsw;
}
/ * ... * /

}

Массив ip_protox [] - это не что иное, как простой ассоциативный массив, который
используется IP для демультиплексирования входящих дейтаграмм на основе * реального * транспорта
номер протокола уровня. Что мы подразумеваем под настоящим? Фактическое количество проживающих
в заголовке IP в поле Протокол - стандартизованный номер по всем RFC.
Число определено в /usr/src/linux-2.6.*/include/linux/in.h для Linux и
/usr/src/sys/netinet/in.h для FreeBSD. IPPROTO_XXX. Большинство протоколов имеют такие
глобальный общий номер.

Таблица inetsw [] не имеет структур протокола protosw, организованных этим
номер. Например, TCP, который определен как протокол номер 6, находится в
inetsw [2]. Итак, ip_protox [6] должен указывать на inetsw [2]. Довольно просто. Тоже самое
логика работает для всех протоколов, которым IP может потребоваться передавать дейтаграммы.
Другая диаграмма:

ip_protox [] inetsw []

--------- ---------
0 | 3 | | IP |
--------- ---------
1 | 4 | ------------------------- | | UDP |
--------- | ---------
2 | 5 | ------ | | -------------------> | TCP |
--------- | | | ---------
3 | | | | | | IP (необработанный) | (По умолчанию)
--------- | | | ---------
4 | | | | | ---------> | ICMP |
--------- | | ---------
5 | | | -----------------------------> | IGMP |
--------- | ---------
6 | 3 | ---------------- | | ... |
--------- ---------
7 | | | ... |
--------- ---------
... | ... | | IP (необработанный) | (подстановочный знак)
--------- ---------

(Не представлены выше, чтобы избежать загромождения, это указатели, исходящие от
незарегистрированные протоколы и переход к записи IP (raw) по умолчанию.

Где в этой части задействованы SOCK_RAW и IPPROTO_RAW?
Проще говоря, если их protosw не существует, ядро ​​паникует! Почему
что? Поскольку каждая запись в ip_protox [] должна быть инициализирована, чтобы указать
в (первую) IP (необработанную) структуру protosw. И по уважительной причине:
если ядро ​​не знает, как обрабатывать протокол (нет другого
зарегистрированный обработчик для него) он передает его обработчику записи по умолчанию, который
SOCK_RAW - IPPROTO_RAW (по умолчанию_RAW).
Такова важность его наличия во всех современных ядрах.
Конечно, все записи ip_protox [], которые * действительно * имеют обработчик для протокола
Указанные числа определены, чтобы указывать на фактическую запись inetsw [], таким образом
перезапись указателя на запись по умолчанию. Но все, у кого нет,
основывают свое (не официальное) существование на IP (необработанном).

Зная эту информацию, ip_input после выполнения всех проверок, которые должны быть
сделано (например, чтобы проверить правильность контрольной суммы дейтаграммы) можно
наконец, вызовите обработчик протокола более высокого уровня.
Примечание: протокол более высокого уровня не обязательно должен быть транспортным,
поскольку IP может передавать дейтаграммы в ICMP, IGMP, которые * не * являются транспортными протоколами
но считаются частью сетевого уровня.

/usr/src/sys/netinet/ip_input.c:

/ *
* Процедура ввода IP. Заголовок подкачки контрольной суммы и байтов. Если фрагментирован
* попробуй собрать заново. Варианты процесса. Перейти на следующий уровень.
* /
пустота
ip_input (структура mbuf * m)
{
struct ip * ip = NULL;
struct in_ifaddr * ia = NULL;
struct ifaddr * ifa;
int checkif, hlen = 0;
u_короткая сумма;
int dchg = 0; / * пункт назначения изменен после fw * /
struct in_addr odst; / * исходный адрес dst * /

/ * ... * /

/ *
* Переключитесь на процедуру ввода протокола.
* /
ipstat.ips_delivered ++;

(* inetsw [ip_protox [ip-> ip_p]]. pr_input) (m, hlen);
возвращаться;
Плохо:
m_freem (м);
}

Предположим теперь, что ip-> ip_p (поле Protocol в заголовке ip) что-то
этот * не * имеет зарегистрированного обработчика в ядре.
Что будет вызывать следующая строка из ip_input ()?

(* inetsw [ip_protox [ip-> ip_p]]. pr_input) (m, hlen);

Вы угадали. pr_input inetsw [default_RAW] (SOCK_RAW - IPPROTO_RAW).
Пришло время проанализировать rip_input ().

/usr/src/sys/netinet/raw_ip.c:

/ *
* Настройка общих адресов и структур протокола
* для подпрограммы raw_input, затем передайте их вместе с
* цепочка mbuf.
* /
пустота
rip_input (struct mbuf * m, int off)
{
struct ip * ip = mtod (m, struct ip *);
int proto = ip-> ip_p;
struct inpcb * inp, * last;

INP_INFO_RLOCK (& ripcbinfo);
ripsrc.sin_addr = ip-> ip_src;
последний = NULL;
LIST_FOREACH (inp, & ripcb, inp_list) {
INP_LOCK (дюйм);
if (inp-> inp_ip_p && inp-> inp_ip_p! = proto) {
docontinue:
INP_UNLOCK (дюйм);
Продолжать;
}
#ifdef INET6
если ((inp-> inp_vflag & INP_IPV4) == 0)
goto docontinue;
#endif
если (inp-> inp_laddr.s_addr &&
inp-> inp_laddr.s_addr! = ip-> ip_dst.s_addr)
goto docontinue;
если (inp-> inp_faddr.s_addr &&
inp-> inp_faddr.s_addr! = ip-> ip_src.s_addr)
goto docontinue;
если (заключен в тюрьму (inp-> inp_socket-> so_cred))
если (htonl (тюрьма_getip (inp-> inp_socket-> so_cred))! =
ip-> ip_dst.s_addr)
goto docontinue;
if (last) {
struct mbuf * n;

n = m_copy (m, 0, (int) M_COPYALL);
если (n! = NULL)
(void) raw_append (последний, ip, n);
/ * XXX количество отброшенных пакетов * /
INP_UNLOCK (последний);
}
last = inp;
}
if (last! = NULL) {
если (raw_append (последний, ip, m)! = 0)
ipstat.ips_delivered--;
INP_UNLOCK (последний);
} еще {
m_freem (м);
ipstat.ips_noproto ++;
ipstat.ips_delivered--;
}
INP_INFO_RUNLOCK (& ripcbinfo);
}

Прочитав приведенный выше исходный код, мы можем выделить следующие моменты:

1) Если необработанный сокет (тип SOCK_RAW) создается со значением протокола 0, тогда:

а) если процесс вызвал bind () и указанный адрес совпадает
адрес назначения в дейтаграмме, затем * ВСЕ * такие дейтаграммы, которые
ядро не знает, как обрабатывать, передаются в его необработанный сокет

б) если процесс вызвал connect () и указанный адрес
соответствует внешнему адресу в дейтаграмме, тогда * ВСЕ * такие дейтаграммы
которые ядро ​​не умеет обрабатывать, передаются его необработанному
разъем.

c) если процесс не вызвал bind () и connect (), то * ALL *
дейтаграммы, которые ядро ​​не умеет обрабатывать, передаются
к его сырому сокету.

г) любая комбинация из a или b, не прошедшая проверку, означает, что
дейтаграмма не передается в приложение.

2) Если необработанный сокет (тип SOCK_RAW) создается со значением протокола не-
ноль, то имеем следующие случаи:

а) если указанное значение протокола имеет зарегистрированный обработчик протокола
в ядре приложение * не * получает дейтаграммы
(исключение составляют ICMP, IGMP, но следует другой механизм, который не обсуждается
здесь).

б) если указанное значение протокола не имеет зарегистрированного протокола
обработчик в ядре, приложение получает * только * дейтаграммы
которые имеют это значение протокола, указанное в заголовке IP.

Вышесказанное можно вывести из следующего фрагмента кода:

if (inp-> inp_ip_p && inp-> inp_ip_p! = proto) {
docontinue:
INP_UNLOCK (дюйм);
Продолжать;
}
если (inp-> inp_laddr.s_addr &&
inp-> inp_laddr.s_addr! = ip-> ip_dst.s_addr)
goto docontinue;
если (inp-> inp_faddr.s_addr &&
inp-> inp_faddr.s_addr! = ip-> ip_src.s_addr)
goto docontinue;

Используя обратную логику, первая проверка игнорирует любую дейтаграмму, которая имеет ненулевое значение.
значение протокола и проверяемый необработанный сокет не имеет этого протокола
указано. Это означает, что любой необработанный сокет, в котором указано значение протокола
из 0 пройдут эту проверку и не будут проигнорированы. То же самое и с любым сырым
сокет, который имеет ненулевое значение протокола, но имеет то же значение протокола, что и
тот, что в дейтаграмме, проверен.
То же самое относится к двум проверкам, которые следуют за первой, которые проверяют локальную
и внешние адреса (в случае вызова bind () или / и connect ()).
Конечно, случай, когда приложение не получает дейтаграммы, хотя
он создал необработанный сокет (но с зарегистрированным протоколом) приходит
из-за того, что rip_input () в этом случае вообще не будет вызываться.

Небольшая скобка здесь, чтобы прокомментировать кое-что из справочных страниц * BSD.
В частности, man 4 ip упоминает, что:
Сокеты Raw IP
"Если proto равен 0, для исходящих пакетов используется протокол по умолчанию IPPROTO_RAW,
и принимаются только входящие, предназначенные для этого протокола ".

Это частично сбивает с толку, так как похоже на создание сырого сокета с
значение протокола 0 приведет только к пакетам, предназначенным для IPPROTO_RAW
(как в 255) получил и больше ничего. Однако это неправда, и мужчина
Автор страниц хочет сказать, что каждая дейтаграмма IP, имеющая значение протокола,
* приводит к демультиплексированию IP, выполненному ip_protox [], чтобы указать на IPPROTO_RAW
запись * (помните, что все неизвестные протоколы указывают на эту запись по умолчанию), затем
сокет получит эти дейтаграммы. По этому определению это правильно.

Забавно то, что указание протокола IPPROTO_RAW при создании
необработанный сокет * обычно * приводит к тому, что сокет не получает * никаких * дейтаграмм
на основе приведенной выше логики. Обычно, поскольку дейтаграммы с таким протоколом
номер do / shound не появляется на проводе.
Конечно, это не означает, что процесс не может подделать пакет с таким
номер протокола и отправьте его. Еще один случай принципиальной разницы
между не должно и не может. Вдобавок к этому не вся подделка пакетов
у программ есть желание всегда общаться ... (как при двустороннем общении)
В Linux произойдет то же самое, поскольку raw_v4_input () вызывается каждый раз и
проверит, существует ли необработанный сокет с номером протокола IPPROTO_RAW в
пользовательское пространство.

Следующие две небольшие программы демонстрируют вышеуказанную функциональность
IPPROTO_RAW в Linux. Помните из части 0x3. IP_HDRINCL, что Linux
ядро устанавливает IP_HDRINCL по умолчанию при создании сокета с IPPROTO_RAW
поэтому нам придется создать свой собственный заголовок, чтобы не отправлять мусор на
провод. В демонстрации используется устройство обратной петли, но будут те же эффекты.
на любом реальном устройстве Ethernet.
Обе программы работают и на FreeBSD / OpenBSD, хотя и создают собственный IP-заголовок.
не требуется (тогда netinet / in.h следует использовать вместо netinet / ip.h)

/ *** Приемник IPPROTO_RAW *** /
# включить <sys / socket.h>
#include <sys / types.h>
#include <netinet / ip.h>
#include <arpa / inet.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

int main (пусто)
{
int s;
struct sockaddr_in saddr;
пакет char [50];

if ((s = socket (AF_INET, SOCK_RAW, IPPROTO_RAW)) <0) {
perror ("ошибка:");
выход (EXIT_FAILURE);
}

memset (пакет, 0, sizeof (пакет));
socklen_t * len = (socklen_t *) sizeof (saddr);
int fromlen = sizeof (saddr);

в то время как (1) {
если (recvfrom (s, (char *) & packet, sizeof (пакет), 0,
(struct sockaddr *) & saddr, & fromlen) <0)
perror ("ошибка получения пакета:");

int я = sizeof (структура iphdr); / * распечатываем полезную нагрузку * /
while (i <sizeof (пакет)) {
fprintf (stderr, "% c", пакет [i]);
i ++;
}
printf ("\ п");
}
выход (EXIT_SUCCESS);
}

/ *** IPPROTO_RAW отправитель *** /
# включить <sys / socket.h>
#include <sys / types.h>
#include <netinet / ip.h>
#include <arpa / inet.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

#define DEST "127.0.0.1"

int main (пусто)
{

int s;
struct sockaddr_in daddr;
пакет char [50];
/ * указываем iphdr на начало пакета * /
struct iphdr * ip = (struct iphdr *) пакет;

if ((s = socket (AF_INET, SOCK_RAW, IPPROTO_RAW)) <0) {
perror ("ошибка:");
выход (EXIT_FAILURE);
}

daddr.sin_family = AF_INET;
daddr.sin_port = 0; / * не требуется в SOCK_RAW * /
inet_pton (AF_INET, DEST, (struct in_addr *) & daddr.sin_addr.s_addr);
memset (daddr.sin_zero, 0, sizeof (daddr.sin_zero));
memset (пакет, 'A', sizeof (пакет)); / * вся полезная нагрузка будет как * /

ip-> ihl = 5;
ip-> версия = 4;
ip-> tos = 0;
ip-> tot_len = htons (40); / * 16-байтовое значение * /
ip-> frag_off = 0; / * без фрагмента * /
ip-> ttl = 64; /* значение по умолчанию */
ip-> протокол = IPPROTO_RAW; / * протокол на L4 * /
ip-> check = 0; / * не требуется в iphdr * /
ip-> saddr = daddr.sin_addr.s_addr;
ip-> daddr = daddr.sin_addr.s_addr;

в то время как (1) {
сон (1);
если (sendto (s, (char *) пакет, sizeof (пакет), 0,
(struct sockaddr *) & daddr, (socklen_t) sizeof (daddr)) <0)
perror ("ошибка отправки пакета:");
}
выход (EXIT_SUCCESS);
}

Их запуск приведет к их идеальному действительному общению, поскольку никто
запрещает им использовать IPPROTO_RAW (255) в качестве протокола L4.

#./Отправить

#tcpdump -i lo -X -vv

19: 49: 27.048431 IP (tos 0x0, ttl 64, id 16705, смещение 0, флаги [нет],
proto unknown (255), длина 50) localhost.localdomain> localhost.localdomain:
ip-proto-255 30
0x0000: 4500 0032 4141 0000 40ff 3a8a 7f00 0001 E..2AA .. @.: .....
0x0010: 7f00 0001 4141 4141 4141 4141 4141 4141 .... AAAAAAAAAAAA
0x0020: 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA
0x0030: 4141 AA
19: 49: 28.048466 IP (tos 0x0, ttl 64, id 16705, смещение 0, флаги [нет],
proto unknown (255), длина 50) localhost.localdomain> localhost.localdomain:
ip-proto-255 30
0x0000: 4500 0032 4141 0000 40ff 3a8a 7f00 0001 E..2AA .. @.: .....
0x0010: 7f00 0001 4141 4141 4141 4141 4141 4141 .... AAAAAAAAAAAA
0x0020: 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA
0x0030: 4141

#. / recv
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Далее с анализом rip_input ():

Подобно тому, как Linux использует skb_clone для создания копии дейтаграммы, FreeBSD использует m_copy.
(mbuf вместо структуры sk_buff). Конечно, копия делается только тогда, когда считается
необходимо - это соответствующий сырой сокет (который не игнорируется для одного из
причины, указанные выше) обнаруживается во время поиска цикла for. m_copy () вполне
дорогостоящая операция, так как она включает в себя фактическое копирование памяти, а не просто перемещение
указатели вокруг. По этой причине FreeBSD использует разумный способ избежать этого.
операция, если дейтаграмма нужна только одному приложению. Это выполнено
с использованием последней переменной, которая указывает на последний найденный сокет
дейтаграмма. Для полноты картины обсудим логику:

последний первый указывает на NULL. Если сокет считается жизнеспособным, чтобы иметь текущий
датаграмма, что означает, что она прошла все проверки игнорирования, скажем, на итерации i,
то последний будет указывать на него, но условие if не будет выполнено (в первый раз). На
итерация i + N, где N - это сокет после сокета i, который также считался
может получить дейтаграмму, m_copy () будет выполняться, так как if
состояние теперь будет истинным. Затем будет вызвана функция raw_append (), которая будет
отвечает за передачу дейтаграммы в приложение, вызывая
sbappendaddr_locked () (см. код raw_append ()). Этот механизм будет и дальше
происходит до тех пор, пока не будет пройден весь необработанный список сокетов. Условие if, которое
находится вне цикла LIST_FOREACH, необходим для передачи копии дейтаграммы в
последний необработанный сокет, который в нем нуждается. Если он не нужен ни одному сокету, то копирование не производится.

последний = NULL;
LIST_FOREACH (inp, & ripcb, inp_list) {

/ * логика каких-сокетов-игнорировать * /
/ * ... * /

if (last) {
struct mbuf * n;

n = m_copy (m, 0, (int) M_COPYALL);
если (n! = NULL)
(void) raw_append (последний, ip, n);
/ * XXX количество отброшенных пакетов * /
INP_UNLOCK (последний);
}
last = inp;
}

if (last! = NULL) {
если (raw_append (последний, ip, m)! = 0)
ipstat.ips_delivered--;
INP_UNLOCK (последний);
} еще {
m_freem (м);
ipstat.ips_noproto ++;
ipstat.ips_delivered--;
}
INP_INFO_RUNLOCK (& ripcbinfo);

Обратите внимание, что Linux всегда должен делать хотя бы одну копию, даже если только один необработанный
сокету нужна дейтаграмма. Это происходит потому, что, как мы уже говорили, Linux использует
необработанный механизм сокета как человек посередине, что означает, что он может иметь
передать дейтаграмму в обычное приложение вместе с raw-sockets-using
один.
FreeBSD передает всю дейтаграмму IP (вместе с заголовком IP) в
приложение вроде Linux.


 

Продолжение следует…

Первая часть —>>

Третья часть —>>

Четвертая часть —>>

Источник https://sock-raw.org/papers/sock_raw (автоперевод)


Поделиться в соц сетях

Подписаться
Уведомить о
0 комментариев
Старые
Новые
Межтекстовые Отзывы
Посмотреть все комментарии

Есть идеи, замечания, предложения? Воспользуйтесь формой Обратная связь или отправьте сообщение по адресу replay@sciencestory.ru
© 2017 Истории науки. Информация на сайте опубликована в ознакомительных целях может иметь ограничение 18+