






















Средний
16 мин
1.1K
Недавно я наткнулся на статью о том, что в ICMP-пакеты можно вставлять произвольные данные. Сразу возникла мысль: а почему бы не попробовать загнать весь трафик через ICMP (да, о существовании ICMP-туннеля я тоже ничего не знал). Так появился проект — ICMP-туннель на уровне ядра, который:
перехватывает исходящие TCP/UDP-пакеты;
инкапсулирует их в ICMP эхо-запросы (тип ICMP_ECHO);
на приёмной стороне извлекает оригинальные пакеты и передаёт их дальше.
Цель проекта — углубить знания в:
разработке модулей ядра (работа с sk_buff, хуки Netfilter, тасклеты). На данный момент я работал с Linux только из user space;
сетевом стеке (IPv4, Ethernet, ICMP, TCP, UDP) и, в частности, его реализации в Linux. До этого моя работа с сетевыми протоколами ограничивалась сокетами;
механизмах перехвата и модификации пакетов.
Хук NF_INET_POST_ROUTING - для пакетов, которые покинули пространство user space и готовы отправиться по сети на другую машину:
фильтрует TCP/UDP-трафик;
преобразует пакет в ICMP. В результате преобразования должен получиться пакет следующего вида:
Пакет данныхХук NF_INET_LOCAL_IN - для пакетов, которые прошли все сетевые фильтры и готовы отправиться в user space:
перехватывает входящие ICMP-пакеты;
извлекает оригинальный TCP/UDP-пакет;
направляет его в loopback-интерфейс (lo) для дальнейшей обработки системой.
Тасклет для отправки пакета в сетевой интерфейс.
inputHook.hook = input_hook;
inputHook.hooknum = NF_INET_LOCAL_IN;
inputHook.pf = PF_INET;
inputHook.priority = NF_IP_PRI_FIRST;
nf_register_net_hook(&init_net, &inputHook);
outputHook.hook = output_hook;
outputHook.hooknum = NF_INET_POST_ROUTING;
outputHook.pf = PF_INET;
outputHook.priority = NF_IP_PRI_FIRST;
nf_register_net_hook(&init_net, &outputHook);Пояснения:
NF_INET_POST_ROUTING — хук для исходящего трафика;
NF_INET_LOCAL_IN — хук для входящего трафика;
приоритет NF_IP_PRI_FIRST гарантирует, что наш обработчик сработает первым.
struct task_data
{
struct tasklet_struct tasklet;
struct sk_buff *skb;
};
void send_func (unsigned long d)
{
struct task_data *data = (struct task_data *)d;
if (dev_queue_xmit(data->skb) != 0)
{
pr_err("dev_queue_xmit failed\n");
kfree_skb(data->skb);
}
kfree (data);
}
Пояснения:
В качестве механизма отложенного выполнения задачи я выбрал тасклеты. Подробнее про них можно прочитать в этой статье (https://habr.com/ru/companies/embox/articles/244071/).
sk_buff - это основная сетевая структура в ядре Linux.
dev_queue_xmit - функция ставит в очередь буфер для передачи на сетевое устройство.
В случае неудачи добавления буфера в сетевой интерфейс уничтожаем sk_buff посредством вызова kfree_skb(data->skb);
Освобождаем память, выделенную под задачу kfree (data)
static unsigned int output_hook (void *priv, struct sk_buff *skb, const struct nf_hook_state *state)
{
struct sk_buff *skb_out = create_packet_output (skb);
if (!skb_out)
return NF_ACCEPT;
struct task_data *data = kmalloc (sizeof(struct task_data), GFP_ATOMIC);
if (!data)
{
pr_err("kmalloc\n");
kfree_skb(skb_out);
return NF_ACCEPT;
}
data->skb = skb_out;
tasklet_init (&data->tasklet, send_func, (unsigned long)data);
tasklet_schedule (&data->tasklet);
return NF_STOLEN;
}
Пояснения:
Функция create_packet_output;
фильтрует TCP/UDP пакеты;
преобразует TCP/UDP пакет в ICMP;
формирует sk_buff для отправки в сетевой интерфейс.
Если пакет не прошёл фильтрацию или не удалось создать sk_buff , то отправляем текущий пакет на следующий этап. Для этого возвращаем значение NF_ACCEPT;
Создаём структуру для тасклета.
Регистрируем отложенную задачу: tasklet_schedule (&data->tasklet);
NF_STOLEN - сообщаем ядру, что пакет мы забрали и дальше его обрабатывать не надо. Важно использовать именно его, а не NF_DROP.
static struct sk_buff* create_packet_output(struct sk_buff* in_packet)
{
struct iphdr* ip_in = ip_hdr(in_packet);
uint8_t mac_out[ETH_ALEN];
uint8_t protocol_type;
uint16_t header_len;
void* transport_hdr;
uint16_t data_len;
if (ip_in->protocol != IPPROTO_UDP && ip_in->protocol != IPPROTO_TCP)
{
return NULL;
}
if (find_mac_addr(mac_out, ip_in->daddr, in_packet->dev) < 0)
{
pr_info("Not found mac\n");
return NULL;
}
if (skb_linearize(in_packet))
{
pr_info("Failed to linearize skb\n");
return NULL;
}
if (ip_in->protocol == IPPROTO_UDP)
{
struct udphdr* in_udp = udp_hdr(in_packet);
protocol_type = 0;
header_len = sizeof(struct udphdr);
transport_hdr = in_udp;
data_len = ntohs(in_udp->len) - sizeof(struct udphdr);
}
else
{
struct tcphdr* in_tcp = tcp_hdr(in_packet);
protocol_type = 1;
header_len = tcp_hdrlen(in_packet);
transport_hdr = in_tcp;
data_len = ntohs(ip_in->tot_len) - (ip_in->ihl * 4) - header_len;
}
int packet_size = sizeof(struct ethhdr)
+ sizeof(struct iphdr)
+ sizeof(struct icmphdr)
+ header_len
+ data_len;
int hh_len = LL_RESERVED_SPACE(in_packet->dev);
int tlen = in_packet->dev->needed_tailroom;
struct sk_buff* skb = netdev_alloc_skb(in_packet->dev, hh_len + tlen + packet_size);
if (unlikely(!skb))
{
pr_err("netdev_alloc_skb failed\n");
return NULL;
}
skb_reserve(skb, hh_len);
skb->dev = in_packet->dev;
skb->protocol = htons(ETH_P_IP);
skb_put(skb, packet_size);
skb_reset_network_header(skb);
skb_set_transport_header(skb, sizeof(struct iphdr));
struct iphdr* ip_out = ip_hdr(skb);
ip_out->version = 4;
ip_out->ihl = 5;
ip_out->tos = 0;
ip_out->tot_len = htons(packet_size - sizeof(struct ethhdr));
ip_out->id = 0;
ip_out->frag_off = htons(0x4000);
ip_out->ttl = 64;
ip_out->protocol = IPPROTO_ICMP;
ip_out->saddr = ip_in->saddr;
ip_out->daddr = ip_in->daddr;
ip_out->check = 0;
ip_out->check = ip_fast_csum((u8 *)ip_out, ip_out->ihl);
struct transfer_header header;
static uint8_t id = 0;
header.id = id++;
header.last = 1;
header.type = protocol_type;
struct icmphdr* icmp = icmp_hdr(skb);
icmp->type = ICMP_ECHO;
icmp->code = 0;
icmp->checksum = 0;
icmp->un.echo.id = htons(*(uint16_t*)&header);
icmp->un.echo.sequence = 1;
uint8_t* data_out = (uint8_t*)(icmp + 1);
memcpy(data_out, transport_hdr, header_len);
data_out += header_len;
uint8_t* data_in = (uint8_t*)transport_hdr + header_len;
memcpy(data_out, data_in, data_len);
icmp->checksum = ip_compute_csum(icmp, sizeof(struct icmphdr) + header_len + data_len);
skb_push(skb, sizeof(struct ethhdr));
skb_reset_mac_header(skb);
struct ethhdr *eth_out = eth_hdr(skb);
memset(eth_out, 0, sizeof(struct ethhdr));
memcpy(eth_out->h_source, skb->dev->dev_addr, ETH_ALEN);
memcpy(eth_out->h_dest, mac_out, ETH_ALEN);
eth_out->h_proto = htons(0x0800);
return skb;
}
struct iphdr* ip_in = ip_hdr(in_packet);
if (ip_in->protocol != IPPROTO_UDP && ip_in->protocol != IPPROTO_TCP)
{
return NULL;
}
Пояснения:
Анализируем заголовок сетевого уровня. Для облегчения себе жизни в рамках данной работы я решил ограничиться IPv4. Для этого вызываем функцию ip_hdr;
За тип протокола в IPv4 отвечает поле protocol. Для TCP оно равно 6, для UDP — 17, для ICMP — 1.
if (ip_in->protocol == IPPROTO_UDP)
{
struct udphdr* in_udp = udp_hdr(in_packet);
protocol_type = 0;
header_len = sizeof(struct udphdr);
transport_hdr = in_udp;
data_len = ntohs(in_udp->len) - sizeof(struct udphdr);
}
else
{
struct tcphdr* in_tcp = tcp_hdr(in_packet);
protocol_type = 1;
header_len = tcp_hdrlen(in_packet);
transport_hdr = in_tcp;
data_len = ntohs(ip_in->tot_len) - (ip_in->ihl * 4) - header_len;
}
int packet_size = sizeof(struct ethhdr)
+ sizeof(struct iphdr)
+ sizeof(struct icmphdr)
+ header_len
+ data_len;
int hh_len = LL_RESERVED_SPACE(in_packet->dev);
int tlen = in_packet->dev->needed_tailroom;
struct sk_buff* skb = netdev_alloc_skb(in_packet->dev, hh_len + tlen + packet_size);Пояснения:
Рассчитываем размер заголовка транспортного уровня для старого пакета: header_len = sizeof(struct udphdr); / header_len = tcp_hdrlen(in_packet);;
Рассчитываем размер полезных данных: data_len = ntohs(in_udp->len) - sizeof(struct udphdr); /data_len = ntohs(ip_in->tot_len) - (ip_in->ihl * 4) - header_len; ;
Резервируем место для заголовка канального уровня: sizeof(struct ethhdr) ;
Резервируем место для заголовка сетевого уровня: sizeof(struct iphdr);
Резервируем место для заголовка транспортного уровня:sizeof(struct icmphdr);
Резервируем место под полезные данные: header_len + data_len;
Резервируем дополнительное место под данные для сетевого интерфейса LL_RESERVED_SPACE(in_packet->dev), in_packet->dev->needed_tailroom
static int find_mac_addr (uint8_t* mac, uint32_t ip, struct net_device *dev)
{
struct neighbour *neigh = __ipv4_neigh_lookup(dev, ip);
if (neigh && (neigh->nud_state & NUD_VALID))
{
memcpy(mac, neigh->ha, ETH_ALEN);
neigh_release(neigh);
return 0;
}
return -1;
}
skb_push(skb, sizeof(struct ethhdr));
skb_reset_mac_header(skb);
struct ethhdr *eth_out = eth_hdr(skb);
memset(eth_out, 0, sizeof(struct ethhdr));
memcpy(eth_out->h_source, skb->dev->dev_addr, ETH_ALEN);
memcpy(eth_out->h_dest, mac_out, ETH_ALEN);
eth_out->h_proto = htons(0x0800);Пояснения:
На момент отправки пакета из user space у нас ещё нет протокола канального уровня, и приходится создавать его самостоятельно.
skb->dev->dev_addr - наш MAC-адрес.
find_mac_addr - поиск MAC-адреса получателя по его IP-адресу.
skb_reset_network_header(skb);
skb_set_transport_header(skb, sizeof(struct iphdr));
struct iphdr* ip_out = ip_hdr(skb);
ip_out->version = 4;
ip_out->ihl = 5;
ip_out->tos = 0;
ip_out->tot_len = htons(packet_size - sizeof(struct ethhdr));
ip_out->id = 0;
ip_out->frag_off = htons(0x4000);
ip_out->ttl = 64;
ip_out->protocol = IPPROTO_ICMP;
ip_out->saddr = ip_in->saddr;
ip_out->daddr = ip_in->daddr;
ip_out->check = 0;
ip_out->check = ip_fast_csum((u8 *)ip_out, ip_out->ihl);Пояснения:
В качестве протокола транспортного уровня указываем ICMP.
ip_out->version = 4 - Версия протокола
ip_out->ihl = 5 - Длинна заголовка измеряемая в 32-битных словах
ip_out->ttl = 64 - Время жизни пакте
struct icmphdr* icmp = icmp_hdr(skb);
icmp->type = ICMP_ECHO;
icmp->code = 0;
icmp->checksum = 0;
struct transfer_header
{
uint8_t id;
uint8_t last : 1;
uint8_t type : 3;
uint8_t reserv : 4;
};
static uint8_t id = 0;
header.id = id++;
header.last = 1;
header.type = protocol_type;
icmp->un.echo.id = htons(*(uint16_t*)&header);
icmp->un.echo.sequence = 1;
memcpy(data_out, transport_hdr, header_len);
data_out += header_len;
uint8_t* data_in = (uint8_t*)transport_hdr + header_len;
memcpy(data_out, data_in, data_len);
icmp->checksum = ip_compute_csum(icmp, sizeof(struct icmphdr) + header_len + data_len);
Пояснения:
Формируем ECHO-запрос: icmp->type = ICMP_ECHO;
В пакетах echo-запрос и echo-ответ добавляются два 16-битных слова. В поле sequence храним номер фрейма, а в поле id храним заголовок нашего псевдопакета.
Контрольная сумма для ICMP рассчитывается с учётом полезных данных.
static unsigned int input_hook (void *priv, struct sk_buff *skb, const struct nf_hook_state *state)
{
struct iphdr *iph = ip_hdr(skb);
if (iph->protocol != IPPROTO_ICMP)
return NF_ACCEPT;
struct icmphdr *icmph = icmp_hdr(skb);
if (icmph->type != ICMP_ECHO)
return NF_ACCEPT;
struct sk_buff *skb_out = create_packet_input (skb);
if (!skb_out)
return NF_ACCEPT;
struct task_data *data = kmalloc(sizeof(struct task_data), GFP_ATOMIC);
if (!data)
{
pr_err("kmalloc\n");
kfree_skb(skb_out);
return NF_ACCEPT;
}
data->skb = skb_out;
tasklet_init(&data->tasklet, send_func, (unsigned long)data);
tasklet_schedule(&data->tasklet);
return NF_STOLEN;
}Пояснения:
Фильтруем все пакеты, кроме ICMP.
Формируем sk_buff для отправки в lo.
struct net_device *dev = dev_get_by_name(&init_net, "lo");Полный код create_packet_input static struct sk_buff* create_packet_input(struct sk_buff* in_packet)
{
struct iphdr* ip_in = ip_hdr(in_packet);
struct icmphdr* icmp_in = icmp_hdr(in_packet);
uint16_t id = ntohs(icmp_in->un.echo.id);
struct transfer_header* header = (struct transfer_header*)&id;
if (header->type != 0 && header->type != 1)
{
pr_err("Unknown protocol type: %d\n", header->type);
return NULL;
}
struct net_device *dev = dev_get_by_name(&init_net, "lo");
if (!dev)
{
pr_err("Cannot get loopback device\n");
return NULL;
}
uint8_t mac_in[ETH_ALEN];
if (find_mac_addr(mac_in, ip_in->saddr, in_packet->dev) < 0)
{
pr_info("MAC address not found for %pI4\n", &ip_in->saddr);
return NULL;
}
uint8_t* data_in = (uint8_t*)(icmp_in + 1);
void* transport_in;
uint32_t transport_header_len;
uint16_t data_len;
uint8_t protocol;
if (header->type == 0)
{
struct udphdr* udp_in = (struct udphdr*)data_in;
transport_in = udp_in;
transport_header_len = sizeof(struct udphdr);
data_len = (uint8_t*)skb_tail_pointer(in_packet)
- (uint8_t*)icmp_in
- sizeof(struct icmphdr)
- sizeof(struct udphdr);
protocol = IPPROTO_UDP;
}
else
{
struct tcphdr* tcp_in = (struct tcphdr*)data_in;
transport_in = tcp_in;
transport_header_len = __tcp_hdrlen(tcp_in);
data_len = (uint8_t*)skb_tail_pointer(in_packet)
- (uint8_t*)icmp_in
- sizeof(struct icmphdr)
- transport_header_len;
protocol = IPPROTO_TCP;
}
int packet_size = sizeof(struct ethhdr)
+ sizeof(struct iphdr)
+ transport_header_len
+ data_len;
int hh_len = LL_RESERVED_SPACE(dev);
int tlen = dev->needed_tailroom;
struct sk_buff* skb = netdev_alloc_skb(dev, hh_len + tlen + packet_size);
if (unlikely(!skb)) {
pr_err("netdev_alloc_skb failed\n");
return NULL;
}
skb_reserve(skb, hh_len);
skb->dev = dev;
skb->protocol = htons(ETH_P_IP);
skb_put(skb, packet_size);
skb_reset_network_header(skb);
skb_set_transport_header(skb, sizeof(struct iphdr));
struct iphdr* ip_out = ip_hdr(skb);
ip_out->version = 4;
ip_out->ihl = 5;
ip_out->tos = 0;
ip_out->tot_len = htons(packet_size - sizeof(struct ethhdr));
ip_out->id = 0;
ip_out->frag_off = htons(0x4000);
ip_out->ttl = 64;
ip_out->protocol = protocol;
ip_out->saddr = ip_in->saddr;
ip_out->daddr = ip_in->daddr;
ip_out->check = 0;
ip_out->check = ip_fast_csum((u8 *)ip_out, ip_out->ihl);
if (protocol == IPPROTO_UDP)
{
struct udphdr* udph = udp_hdr(skb);
struct udphdr* udp_in = (struct udphdr*)transport_in;
udph->source = udp_in->source;
udph->dest = udp_in->dest;
udph->len = udp_in->len;
udph->check = 0;
uint8_t* data_out = (uint8_t*)(udph + 1);
memcpy(data_out, data_in + sizeof(struct udphdr), data_len);
}
else
{
struct tcphdr* tcph = tcp_hdr(skb);
struct tcphdr* tcp_in = (struct tcphdr*)transport_in;
memcpy(tcph, tcp_in, transport_header_len);
tcph->check = 0;
uint8_t* data_out = (uint8_t*)tcph + transport_header_len;
memcpy(data_out, data_in + transport_header_len, data_len);
int tcplen = transport_header_len + data_len;
tcph->check = tcp_v4_check(tcplen,
ip_out->saddr,
ip_out->daddr,
csum_partial((char *)tcph, tcplen, 0));
skb->ip_summed = CHECKSUM_NONE;
}
skb_push(skb, sizeof(struct ethhdr));
skb_reset_mac_header(skb);
struct ethhdr *eth_out = eth_hdr(skb);
memset(eth_out, 0, sizeof(struct ethhdr));
memcpy(eth_out->h_source, mac_in, ETH_ALEN);
memcpy(eth_out->h_dest, dev->dev_addr, ETH_ALEN);
eth_out->h_proto = htons(ETH_P_IP);
return skb;
}
Создадим 2 виртуальные машины и объединим их в общую сеть. Для этих целей я использовал VirtualBox.
Соберём модуль под нашу платформу.
Загрузим его с помощью команды ismod на обеих виртуальных машинах.
На приёмной стороне выполняем следующие команды:
nc -ul 4020 # Принимаем UDP-пакеты на 4020 порт
tcpdump -I enp0s3 – w captureEnp.pcap # Запись интернет-трафика на интерфейсе enp0s3 (интерфейс, через который связаны наши виртуальные машины)
tcpdump -I lo – w captureLo.pcap На второй виртуальной машине вводим следующую команду и вводим текст в консоли:
nc – u ip 4020 Получаем сообщение на приёмной стороне:

Открываем файл captureEnp.pcap с помощью Wireshark и видим наше сообщение с текстом Hello, завёрнутое в ICMP-пакет:
ICMP-ПакетыОткрываем файл captureLo.pcap и видим уже UDP-пакеты, которые идут к нам:
UDP-пакетыИтак, нам удалось отправить UDP и TCP-пакеты через ICMP-туннель. Но в данный момент длина ICMP-пакета ограничена длиной исходного пакета, что не очень хорошо. Добавим фрагментацию для пакетов.
static int max_size = 10;
module_param(max_size, int, 0644);
MODULE_PARM_DESC(my_int, "Max size out packet");Расчёт полной длины полезных данных для ICMP-пакета (заголовок + данные):
if (ip_in->protocol == IPPROTO_UDP)
{
struct udphdr* in_udp = udp_hdr(in_packet);
protocol_type = 0;
data_in = (uint8_t*)in_udp;
data_len = ntohs(in_udp->len);
}
else
{
struct tcphdr* in_tcp = tcp_hdr(in_packet);
protocol_type = 1;
data_in = (uint8_t*)in_tcp;
data_len = ntohs(ip_in->tot_len) - (ip_in->ihl * 4);
}Нарезка пакета на подпакеты:
int packet_len = (data_len>max_size)?max_size:data_len;
data_len -= packet_len;
int packet_size = sizeof(struct ethhdr)
+ sizeof(struct iphdr)
+ sizeof(struct icmphdr)
+ packet_len;
struct sk_buff* skb = netdev_alloc_skb(in_packet->dev, hh_len + tlen + packet_size);Связывание sk_buff друг с другом для последовательной отправки:
if (!skb_out)
{
skb_out = skb;
}
if (skb_current)
{
skb_current->next = skb;
skb->prev = skb_current;
}Формирование заголовка пакета. id для всего пакета одинаковое. В поле last указываем признак последнего пакета. В icmp->un.echo.sequence — номер подпакета.
header.id = id;
header.last = (data_len == 0)?1:0;
header.type = protocol_type;
header.reserv = 0;
icmp->un.echo.sequence = htons(frag++);Формирование данных в пакете
uint8_t* data_out = (uint8_t*)(icmp + 1);
memcpy(data_out, data_in, packet_len);
icmp->checksum = ip_compute_csum(icmp, sizeof(struct icmphdr) + packet_len);
data_in += packet_len;static struct sk_buff* create_packet_output(struct sk_buff* in_packet)
{
struct iphdr* ip_in = ip_hdr(in_packet);
uint8_t mac_out[ETH_ALEN];
uint8_t protocol_type;
uint16_t data_len;
uint8_t* data_in;
if (ip_in->protocol != IPPROTO_UDP && ip_in->protocol != IPPROTO_TCP)
{
return NULL;
}
if (find_mac_addr(mac_out, ip_in->daddr, in_packet->dev) < 0)
{
pr_info("Not found mac\n");
return NULL;
}
if (skb_linearize(in_packet))
{
pr_info("Failed to linearize skb\n");
return NULL;
}
if (ip_in->protocol == IPPROTO_UDP)
{
struct udphdr* in_udp = udp_hdr(in_packet);
protocol_type = 0;
data_in = (uint8_t*)in_udp;
data_len = ntohs(in_udp->len);
}
else
{
struct tcphdr* in_tcp = tcp_hdr(in_packet);
protocol_type = 1;
data_in = (uint8_t*)in_tcp;
data_len = ntohs(ip_in->tot_len) - (ip_in->ihl * 4);
}
static uint8_t id = 0;
id++;
int hh_len = LL_RESERVED_SPACE(in_packet->dev);
int tlen = in_packet->dev->needed_tailroom;
struct sk_buff* skb_out = NULL;
struct sk_buff* skb_current = NULL;
uint16_t frag = 0;
while(true)
{
int packet_len = (data_len>max_size)?max_size:data_len;
data_len -= packet_len;
int packet_size = sizeof(struct ethhdr)
+ sizeof(struct iphdr)
+ sizeof(struct icmphdr)
+ packet_len;
struct sk_buff* skb = netdev_alloc_skb(in_packet->dev, hh_len + tlen + packet_size);
if (!skb)
{
while (skb_out)
{
skb = skb_out->next;
kfree_skb(skb_out);
skb_out = skb;
}
pr_err("netdev_alloc_skb failed\n");
return NULL;
}
if (!skb_out)
{
skb_out = skb;
}
if (skb_current)
{
skb_current->next = skb;
skb->prev = skb_current;
}
skb_reserve(skb, hh_len);
skb->dev = in_packet->dev;
skb->protocol = htons(ETH_P_IP);
skb_put(skb, packet_size);
skb_reset_network_header(skb);
skb_set_transport_header(skb, sizeof(struct iphdr));
struct iphdr* ip_out = ip_hdr(skb);
ip_out->version = 4;
ip_out->ihl = 5;
ip_out->tos = 0;
ip_out->tot_len = htons(packet_size - sizeof(struct ethhdr));
ip_out->id = 0;
ip_out->frag_off = htons(0x4000);
ip_out->ttl = 64;
ip_out->protocol = IPPROTO_ICMP;
ip_out->saddr = ip_in->saddr;
ip_out->daddr = ip_in->daddr;
ip_out->check = 0;
ip_out->check = ip_fast_csum((u8 *)ip_out, ip_out->ihl);
struct transfer_header header;
header.id = id;
header.last = (data_len == 0)?1:0;
header.type = protocol_type;
header.reserv = 0;
struct icmphdr* icmp = icmp_hdr(skb);
icmp->type = ICMP_ECHOREPLY;
icmp->code = 0;
icmp->checksum = 0;
icmp->un.echo.id = htons(*(uint16_t*)&header);
icmp->un.echo.sequence = htons(frag++);
uint8_t* data_out = (uint8_t*)(icmp + 1);
memcpy(data_out, data_in, packet_len);
icmp->checksum = ip_compute_csum(icmp, sizeof(struct icmphdr) + packet_len);
skb_push(skb, sizeof(struct ethhdr));
skb_reset_mac_header(skb);
struct ethhdr *eth_out = eth_hdr(skb);
memset(eth_out, 0, sizeof(struct ethhdr));
memcpy(eth_out->h_source, skb->dev->dev_addr, ETH_ALEN);
memcpy(eth_out->h_dest, mac_out, ETH_ALEN);
eth_out->h_proto = htons(0x0800);
skb_current = skb;
data_in += packet_len;
if (data_len == 0)
break;
}
skb_current = skb_out;
return skb_out;
}
struct list_data
{
uint32_t size;
uint8_t* data;
void* prev;
void* next;
};
static int flag_error = 0;
static int id_packet = 0;
static int current_frag = 0;
static struct list_data* end = NULL;
static int total_size = 0;
uint16_t id = ntohs(icmph->un.echo.id);
struct transfer_header* header = (struct transfer_header*)&id;
if (ntohs(icmph->un.echo.sequence) == 0)
{
id_packet = header->id;
flag_error = 0;
clear_list (&end);
current_frag = ntohs(icmph->un.echo.sequence);
total_size = 0;
}
if (flag_error == 1)
{
return NF_STOLEN;
}
if (current_frag == ntohs(icmph->un.echo.sequence)
&& id_packet == header->id)
{
current_frag++;
if (end)
{
end->next = kmalloc(sizeof(struct list_data), GFP_ATOMIC);
if (!end->next)
{
flag_error = 1;
return NF_STOLEN;
}
struct list_data* cur = end;
end = end->next;
end->prev = cur;
}
else
{
end = kmalloc(sizeof(struct list_data), GFP_ATOMIC);
if (!end)
{
flag_error = 1;
return NF_STOLEN;
}
end->prev = NULL;
end->next = NULL;
}
end->size = (uint8_t*)skb_tail_pointer(skb) - (uint8_t*)icmph - sizeof(struct icmphdr);
end->data = kmalloc(end->size, GFP_ATOMIC);
if (!end->data)
{
flag_error = 1;
return NF_STOLEN;
}
memcpy(end->data, (uint8_t*)(icmph + 1), end->size);
total_size += end->size;
}
else
{
flag_error = 1;
clear_list (&end);
}
if (header->last == 1 && flag_error == 0)
{
pr_info ("get packet %d %d\n",header->type, total_size);
struct sk_buff* skb_out = create_packet_input (skb, end, total_size);
if (!skb_out)
{
flag_error = 1;
clear_list (&end);
pr_info ("clear_list %u\n",end);
return NF_STOLEN;
}
struct task_data *data = kmalloc(sizeof(struct task_data), GFP_ATOMIC);
if (!data)
{
pr_err("kmalloc\n");
kfree_skb(skb_out);
return NF_ACCEPT;
}
data->skb = skb_out;
tasklet_init(&data->tasklet, send_func, (unsigned long)data);
tasklet_schedule(&data->tasklet);
}
Пояснения:
if (ntohs(icmph->un.echo.sequence) == 0) - проверяем фрагмент на признак первого сообщения в пакете.
if (current_frag == ntohs(icmph->un.echo.sequence) && id_packet == header->id) - проверяем, что пришёл тот фрагмент, который мы ожидали.
end->next = kmalloc(sizeof(struct list_data), GFP_ATOMIC); / end = kmalloc(sizeof(struct list_data), GFP_ATOMIC); - добавляем фрагмент в список.
end->data = kmalloc(end->size, GFP_ATOMIC); / memcpy(end->data, (uint8_t*)(icmph + 1), end->size); - сохраняем данные фрагмента.
if (header->last == 1 && flag_error == 0) - если приняли весь пакет, то склеиваем его.
if (header->type == 0)
{
addr_transport_header = (uint8_t*)udp_hdr(skb);
}
else
{
addr_transport_header = (uint8_t*)tcp_hdr(skb);
}
struct list_data* cur = end;
int cp_size = total_size;
while (cur)
{
cp_size -= cur->size;
memcpy (addr_transport_header + cp_size, cur->data, cur->size);
cur = cur->prev;
}
if (header->type == 0)
{
transport_header_len = sizeof(struct udphdr);
struct udphdr* udph = udp_hdr(skb);
udph->check = 0;
}
else
{
struct tcphdr* tcph = tcp_hdr(skb);
struct tcphdr* tcp_in = (struct tcphdr*)data_in;
transport_header_len = __tcp_hdrlen(tcp_in);
tcph->check = 0;
tcph->check = tcp_v4_check(size,
ip_out->saddr,
ip_out->daddr,
csum_partial((char *)tcph, size, 0));
skb->ip_summed = CHECKSUM_NONE;
}
Пояснения:
addr_transport_header = (uint8_t*)udp_hdr(skb); / addr_transport_header = (uint8_t*)tcp_hdr(skb); - находим адрес заголовка транспортного уровня.
memcpy (addr_transport_header + cp_size, cur->data, cur->size); - копируем фреймы с конца в новый пакет.
Выполняем все те же команды, что и в шаге 5.
Убеждаемся, что сообщения передаются:
messeger1Открываем файл captureEnp.pcap и видим там несколько подряд идущих ICMP-пакетов:
ICMP-пакетыОткрываем файл captureLo.pcap и видим уже UDP, но в одном экземпляре:
UDP-пактыПолный код проекта можно найти на GitHub (https://github.com/kormilicinkostia/icmptunel).
В итоге удалось реализовать задуманный механизм. Но в рамках сетевого стека Linux осталось больше вопросов, чем ответов. Разработанный модуль имеет огрехи и как минимум требует улучшения в следующих аспектах:
Алгоритм склейки пакетов работает в однопоточном режиме. Если у нас появятся несколько интерфейсов или клиентов на одном, он просто сломается.
Наверняка можно передать пакет напрямую в user space, а не пересылать его через lo-интерфейс.
Но в рамках первого опыта и знакомства с ядром Linux результатом я доволен.
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。