Получение неверного IP-адреса источника для ICMP-пакетов с превышенным временем жизни на Android

Вопрос или проблема

Получение неверного 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, что указывает на ошибку в обработке полученных ответов.

Возможные причины проблемы:

  1. Неправильная настройка сокета ICMP:
    Вы используете UDP сокет для отправки ICMP запросов вместо ICMP сокета. Вам следует использовать SOCK_RAW вместо SOCK_DGRAM при создании сокета ICMP, так как стандартная библиотека для UDP не предназначена для работы с ICMP-пакетами.

  2. Отсутствие прав на использование сырых сокетов:
    Для работы с сырыми сокетами (raw sockets) в Android может потребоваться специальный уровень доступа, такой как root-доступ. Если ваше приложение не имеет необходимых разрешений, вы будете получать некорректные ответы.

  3. Проблемы с обработкой ответов:
    Ваш код для получения и обработки ответов ICMP не учитывает структуру ICMP сообщений. При обработке следует проверить, что именно вы получаете. Обратите внимание на правильное заполнение структуры ICMP-ответа и соответствие заголовков.

Рекомендации по исправлению:

  1. Измените создание сокета:
    Замените строку создания сокета с SOCK_DGRAM на SOCK_RAW:

    int sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
  2. Добавьте необходимые разрешения:
    Убедитесь, что ваше приложение имеет соответствующие разрешения (INTERNET, а иногда и ACCESS_SUPERUSER), если это требуется.

  3. Исправьте обработку 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-пакеты. Убедитесь, что ваши изменения протестированы на различных сетях, чтобы исключить влияние внешних факторов.

Оцените материал
Добавить комментарий

Капча загружается...