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


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

Продолжение (3-я часть) статьи на тему «сырых сокетов» на языке С++

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

Вторая часть —>>

0x5. необработанный вывод

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

Проанализировав, как обрабатываются входящие сырые дейтаграммы, мы готовы к переходу
подробнее о механизме необработанного вывода. Что будем проверять
вот какие значения IP-заголовка записываются по умолчанию каждым ядром и
за какие из них мы обязаны сами заполнить. Это конечно
применяется только в том случае, если включена опция IP_HDRINCL (установленная нами или
ядро по умолчанию [Linux - IPPROTO_RAW]).

а. Linux

********

Основным обработчиком вывода необработанного сокета является raw_sendmsg (), который определен в
/usr/src/linux.2.6*/net/ipv4/raw.c:

static int raw_sendmsg (struct kiocb * iocb, struct sock * sk, struct msghdr * msg,
size_t len)
{
struct inet_sock * inet = inet_sk (sk);
struct ipcm_cookie ipc;
struct rtable * rt = NULL;
/ * ... * /
если (inet-> hdrincl)
err = raw_send_hdrinc (sk, msg-> msg_iov, len,
rt, msg-> msg_flags);

/ * ... * /

}

Если установлен IP_HDRINCL, то вызывается raw_send_hdrinc () (определено в
тот же файл):

static int raw_send_hdrinc (struct sock * sk, void * from, size_t length,
структура rtable * rt,
беззнаковые целочисленные флаги)
{
/ * ... * /
if (length> rt-> u.dst.dev-> mtu) {
ip_local_error (sk, EMSGSIZE, rt-> rt_dst, inet-> dport,
rt-> u.dst.dev-> mtu);
возврат -EMSGSIZE;
}

/ * Мы не изменяем недопустимый заголовок * /
iphlen = iph-> ihl * 4;
if (iphlen> = sizeof (* iph) && iphlen <= length) {
если (! iph-> saddr)
iph-> saddr = rt-> rt_src;
iph-> проверка = 0;
iph-> tot_len = htons (длина);
если (! iph-> id)
ip_select_ident (iph, & rt-> u.dst, NULL);

iph-> check = ip_fast_csum ((unsigned char *) iph, iph-> ihl);
}
если (iph-> протокол == IPPROTO_ICMP)
icmp_out_count (((struct icmphdr *)
skb_transport_header (skb)) -> тип);

err = NF_HOOK (PF_INET, NF_IP_LOCAL_OUT, skb, NULL, rt-> u.dst.dev,
dst_output);

/ * ... * /
}

Код здесь довольно очевиден. Если приложение не определило IP
адрес источника, затем он заполняется. То же самое касается IP-идентификации.
номер. Всегда заполняются два поля: контрольная сумма IP.
(надеюсь для нас - это избавит нас от хлопот) и общая длина,
iph-> tot_len дейтаграммы (здесь нет коррупции). Вышесказанное подтверждено
от man 7 raw:

+ ------------------------------------------------- - +
| Поля заголовка IP, измененные при отправке IP_HDRINCL |
+ ---------------------- + -------------------------- - +
| Контрольная сумма IP | Всегда заполняется.
+ ---------------------- + -------------------------- - +
| Исходный адрес | Заполняется, когда ноль. |
+ ---------------------- + -------------------------- - +
| Идентификатор пакета | Заполняется, когда ноль. |
+ ---------------------- + -------------------------- - +
| Общая длина | Всегда заполняется. |
+ ---------------------- + -------------------------- - +

Обратите внимание, что в конце вызывается функция dst_output () и уровень IP
вообще не участвует. В результате это имеет один отрицательный эффект (хотя в
производительность лучше): при необходимости не будет фрагментации IP. Этот
означает, что необработанный пакет больше, чем MTU интерфейса, вероятно,
быть отброшенным. Вместо этого ip_local_error (), который выполняет общую очистку sk_buff,
вызывается и возвращается ошибка EMSGSIZE. С другой стороны, нормальный сырой
фрагментация сокета происходит, когда мы не включаем собственный IP-заголовок.

б. FreeBSD

**********

Необработанный вывод во FreeBSD обрабатывается rip_output (). Цепочка функций ядра
вызываются до того, как мы дойдем до rip_output ().

Первый sosend_generic (), который определен в /usr/src/sys/kern/uipc_socket.c
обращается к структуре pr_usrreqs (пользовательский запрос) и вызывает такой вызов:

* so-> so_proto-> pr_usrreqs-> pru_send (...)

где so - тип struct socket * so.

so_proto - указатель на struct protosw * и указывает на протокол
разъем. pr_usrreqs - это структура, члены которой являются указателями на функции
которые используются протоколом для обслуживания запросов, поступающих из сокета
слой (обычно выдается системными вызовами на уровне приложения).

Для протокола RAW pr_usrreqs называется rip_usrreqs и определяется в
/usr/src/sys/kern/raw_ip.c непосредственно перед концом файла:

struct pr_usrreqs rip_usrreqs = {
.pru_abort = rip_abort,
.pru_attach = rip_attach,
.pru_bind = rip_bind,
.pru_connect = rip_connect,
.pru_control = in_control,
.pru_detach = rip_detach,
.pru_disconnect = rip_disconnect,
.pru_peeraddr = in_getpeeraddr,
.pru_send = rip_send,
.pru_shutdown = rip_shutdown,
.pru_sockaddr = in_getsockaddr,
.pru_sosetlabel = in_pcbsosetlabel,
};

Это означает, что вызов * so-> so_proto-> pr_usrreqs-> pru_send (...) на необработанном
socket, вызовет rip_send (), который определен в raw_ip.c, и вызовет
rip_output (), которым мы сейчас займемся.

статический int
rip_send (struct socket * so, int flags, struct mbuf * m, struct sockaddr * nam,
struct mbuf * control, struct thread * td)
{
struct inpcb * inp;
u_long dst;

/ * ... * /
return rip_output (m, so, dst);
}

rip_output () либо частично создаст заголовок IP перед его передачей
на уровень IP, или, если мы установили опцию IP_HDRINCL, отметьте пакет
как не подлежащие изменению путем установки флага IP_RAWOUTPUT. В конце концов, он звонит
ip_output () и покоится с миром (рип) на данный момент.

/ *
* Сгенерировать IP-заголовок и передать пакет в ip_output.
* Пользователь может настроить параметры при помощи контрольного вызова.
* /
int
rip_output (struct mbuf * m, struct socket * so, u_long dst)
{
struct ip * ip;
int error;
struct inpcb * inp = sotoinpcb (так);
int flags = ((so-> so_options & SO_DONTROUTE)? IP_ROUTETOIF: 0) |
IP_ALLOWBROADCAST;

/ *
* Если пользователь передал нам полный IP-пакет, используйте его.
* В противном случае выделите mbuf для заголовка и заполните его.
* /
if ((inp-> inp_flags & INP_HDRINCL) == 0) {
if (m-> m_pkthdr.len + sizeof (struct ip)> IP_MAXPACKET) {
m_freem (м);
возврат (EMSGSIZE);
}
M_PREPEND (m, sizeof (struct ip), M_DONTWAIT);
если (m == NULL)
возврат (ENOBUFS);

INP_LOCK (дюйм);
ip = mtod (m, struct ip *);
ip-> ip_tos = inp-> inp_ip_tos;
если (inp-> inp_flags & INP_DONTFRAG)
ip-> ip_off = IP_DF;
еще
ip-> ip_off = 0;
ip-> ip_p = inp-> inp_ip_p; / * записываем указанное значение протокола XXX
в сокете (AF_INET, SOCK_RAW, XXX) * /
ip-> ip_len = m-> m_pkthdr.len;
если (заключен в тюрьму (inp-> inp_socket-> so_cred))
ip-> ip_src.s_addr =
htonl (тюрьма_getip (inp-> inp_socket-> so_cred));
еще
ip-> ip_src = inp-> inp_laddr;
ip-> ip_dst.s_addr = dst;
ip-> ip_ttl = inp-> inp_ip_ttl;
} еще {
/ * проверки * /
/ * ... * /

/ * не разрешать и параметры, указанные пользователем, и параметры setsockopt,
и не позволяйте размерам пакетов, которые приведут к сбою * /
если (((ip-> ip_hl! = (sizeof (* ip) >> 2))
&& inp-> inp_options)
|| (ip-> ip_len> m-> m_pkthdr.len)
|| (ip-> ip_len <(ip-> ip_hl << 2))) {
INP_UNLOCK (дюйм);
m_freem (м);
вернуть EINVAL;
}
если (ip-> ip_id == 0)
ip-> ip_id = ip_newid ();
/ * XXX запрещает ip_output перезаписывать поля заголовка * /
флаги | = IP_RAWOUTPUT;
ipstat.ips_rawout ++;
}

/ * ... * /

error = ip_output (m, inp-> inp_options, NULL, flags,
inp-> inp_moptions, inp);
INP_UNLOCK (дюйм);
ошибка возврата;
}

Что касается случая, когда ядро ​​добавляет свой собственный заголовок, это стоит наших
время упомянуть, что полю протокола IP-заголовка присвоено значение
упоминается в сокете (AF_INET, SOCK_RAW, XXX) и * не * IPPROTO_RAW, даже если
запись default_RAW фактически является его обработчиком.
Нас интересует случай, показанный под условием else (case
где пользователь передал полный заголовок IP-пакета). Ну в былые времена
пакет большой длины может потенциально привести к сбою некоторых вещей, так как
проверок ip_len (который является общей длиной дейтаграммы) не было.
Еще одна интересная вещь здесь заключается в том, что ядро ​​будет использовать случайное значение для
установите ip_id (идентификационный номер - используется при фрагментации), если он был
слева со значением 0. Следующее - флаг IP_RAWOUTPUT, о котором мы говорили.
чуть раньше. Посмотрим, как это повлияет на ip_output ()
который определен в одноименном файле .c в /usr/src/sys/netinet/ip_output.c:

int
ip_output (struct mbuf * m, struct mbuf * opt, struct route * ro, int flags,
struct ip_moptions * imo, struct inpcb * inp)
{

/ * ... * /

if ((flags & (IP_FORWARDING | IP_RAWOUTPUT)) == 0) {
ip-> ip_v = IPVERSION;
ip-> ip_hl = hlen >> 2;
ip-> ip_id = ip_newid ();
ipstat.ips_localout ++;
} еще {
hlen = ip-> ip_hl << 2;
}

/ * ... * /

/ *
* Если достаточно мало для интерфейса, или интерфейс примет
* забота о фрагментации для нас, мы можем просто отправить напрямую.
* /

/ * ... * /
ip-> ip_sum = 0;
если (sw_csum & CSUM_DELAY_IP)
ip-> ip_sum = in_cksum (м, хлен);

/ * ... * /
/ *
* Если адрес источника еще не указан, используйте адрес
* выходного интерфейса.
* /
если (ip-> ip_src.s_addr == INADDR_ANY) {
/ * Интерфейс может не иметь адресов. * /
if (ia! = NULL) {
ip-> ip_src = IA_SIN (ia) -> sin_addr;
}
}
/ * ... * /
}

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



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

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

Вторая часть —>>

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


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

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

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