Вопрос или проблема
Получение неверного IP-адреса источника для ICMP-пакетов с превышенным временем жизни на Android
Ниже представлен код нативного утилиты ICMP, который я использую в своем Android приложении для целей ‘ping’.
Я решил добавить функциональность трассировки маршрута и пытался использовать тот же код с ttl=1, ttl=2 и так далее. Однако на моем реальном устройстве ответа IP-адрес из этого кода для ttl=1, ttl=2, ttl=3 всегда 123.0.0.0.
Чтобы проверить, так ли это, я взял другое приложение ‘ping’ из Play Market, установил ttl=1, ttl=2, ttl=3, и для каждого из этих значений оно отображает законный ответ IP-адрес, например 192.168.0.1, 100.101.101.2 и т.д.
Знаете ли вы причину? Что не так с этим кодом?
#pragma clang diagnostic push
#pragma ide diagnostic ignored "cppcoreguidelines-avoid-magic-numbers"
#include "icmp_util.h"
#include <linux/icmp.h>
#include <jni.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctime>
#include <unistd.h>
#include <malloc.h>
#include <map>
std::map<int, sockaddr_in> addr_map;
#pragma clang diagnostic push
#pragma ide diagnostic ignored "hicpp-signed-bitwise"
#pragma ide diagnostic ignored "readability-magic-numbers"
u_short checksum(u_short *buf, int len) {
u_long sum = 0;
while (len > 1) {
sum += *buf++;
len -= sizeof(u_short);
}
if (len)
sum += *(u_char *) buf;
sum = (sum >> 16) + (sum & 0xffff);
sum += (sum >> 16);
return (u_short) (~sum);
}
#pragma clang diagnostic pop
int create_icmp_socket(const char *host, int timeout_ms, int ttl) {
struct timeval t_out;
t_out.tv_sec = MS_TO_SEC(timeout_ms);
t_out.tv_usec = MS_TO_USEC(timeout_ms);
struct sockaddr_in addr;
int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP); // NOLINT(android-cloexec-socket)
if (sock < 0) {
return SOCKET_ERROR;
}
if (setsockopt(sock, SOL_IP, IP_TTL, &ttl, sizeof(ttl)) != 0) {
close(sock);
return SOCKET_ERROR;
}
if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char *) &t_out, sizeof(t_out)) != 0) {
close(sock);
return SOCKET_ERROR;
}
bzero(&addr, sizeof(addr));
addr.sin_family = AF_INET;
if (inet_pton(AF_INET, host, &(addr.sin_addr)) < 0) {
close(sock);
return SOCKET_ERROR;
}
addr_map[sock] = addr;
return sock;
}
void close_icmp_socket(int sock) {
shutdown(sock, SHUT_RDWR);
close(sock);
addr_map.erase(sock);
}
#pragma clang diagnostic push
#pragma ide diagnostic ignored "misc-non-private-member-variables-in-classes"
int ping(int sock, u_short sequence, const int size, jbyte *pattern, jsize pattern_len, char *ip_address) {
char packet_data[size + sizeof(icmphdr)];
struct icmphdr *hdr = (struct icmphdr *) &packet_data;
struct timespec time_start, time_end;
struct sockaddr_in addr = addr_map[sock], r_addr;
socklen_t addr_len;
bzero(packet_data, sizeof(packet_data));
hdr->type = ICMP_ECHO;
hdr->un.echo.id = htons(getpid());
hdr->un.echo.sequence = htons(sequence);
if (pattern_len > 0) {
for (int i = 0; i < size; i = i + pattern_len) {
int chunk_size = size - i;
if (chunk_size > pattern_len)
chunk_size = pattern_len;
memcpy(packet_data + i + sizeof(icmphdr), pattern, (size_t) chunk_size);
}
}
hdr->checksum = checksum((u_short *) &packet_data, sizeof(packet_data));
clock_gettime(CLOCK_MONOTONIC, &time_start);
if (sendto(sock, packet_data, sizeof(packet_data), 0, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
return SEND_ERROR;
}
addr_len = sizeof(r_addr);
int resp = recvfrom(sock, packet_data, sizeof(packet_data), 0, (struct sockaddr *) &r_addr, &addr_len);
clock_gettime(CLOCK_MONOTONIC, &time_end);
// Извлечение IP-адреса из r_addr
inet_ntop(AF_INET, &(r_addr.sin_addr), ip_address, INET_ADDRSTRLEN); // здесь ip_address будет содержать 123.0.0.0
if (resp <= 0) {
return SEND_TIMEOUT;
}
double elapsed = ((double) (time_end.tv_nsec - time_start.tv_nsec)) / 1000000.0;
return (int) ((time_end.tv_sec - time_start.tv_sec) * 1000.0 + elapsed);
}
Ответ или решение
Проблема, с которой вы столкнулись, заключается в неправильной интерпретации адреса отправителя ответных ICMP-пакетов при выполнении трассировки маршрута (traceroute) на Android-устройстве. Возвращаемый вами IP-адрес всегда равен 123.0.0.0, что указывает на ошибку в обработке полученных ответов.
Возможные причины проблемы:
-
Неправильная настройка сокета ICMP:
Вы используете UDP сокет для отправки ICMP запросов вместо ICMP сокета. Вам следует использоватьSOCK_RAW
вместоSOCK_DGRAM
при создании сокета ICMP, так как стандартная библиотека для UDP не предназначена для работы с ICMP-пакетами. -
Отсутствие прав на использование сырых сокетов:
Для работы с сырыми сокетами (raw sockets) в Android может потребоваться специальный уровень доступа, такой как root-доступ. Если ваше приложение не имеет необходимых разрешений, вы будете получать некорректные ответы. - Проблемы с обработкой ответов:
Ваш код для получения и обработки ответов ICMP не учитывает структуру ICMP сообщений. При обработке следует проверить, что именно вы получаете. Обратите внимание на правильное заполнение структуры ICMP-ответа и соответствие заголовков.
Рекомендации по исправлению:
-
Измените создание сокета:
Замените строку создания сокета сSOCK_DGRAM
наSOCK_RAW
:int sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
-
Добавьте необходимые разрешения:
Убедитесь, что ваше приложение имеет соответствующие разрешения (INTERNET
, а иногда иACCESS_SUPERUSER
), если это требуется. - Исправьте обработку ICMP-пакетов:
Проверьте, что вы корректно обрабатываете и проверяете заголовки ICMP в полученном пакете. Например, вам может понадобиться явно проверить тип и код ICMP-ответа.
Пример исправленного кода:
Добавьте проверку на тип ICMP и верный возврат адреса отправителя в функции ping
:
// проверка, что ICMP ответ пришел от правильного узла
struct iphdr *ip_header = (struct iphdr *) packet_data;
struct icmphdr *icmp_header = (struct icmphdr *) (packet_data + (ip_header->ihl * 4));
if (icmp_header->type == ICMP_TIME_EXCEEDED) {
inet_ntop(AF_INET, &r_addr.sin_addr, ip_address, INET_ADDRSTRLEN);
} else {
// обработка других типов ICMP
}
Заключение:
Для успешного выполнения функции трассировки, вам нужно удостовериться, что используете правильный тип сокета, имеете необходимые права доступа и корректно обрабатываете входящие ICMP-пакеты. Убедитесь, что ваши изменения протестированы на различных сетях, чтобы исключить влияние внешних факторов.