diff --git a/src/eim_nat.c b/src/eim_nat.c index 8ccc8a59..865f9a0b 100644 --- a/src/eim_nat.c +++ b/src/eim_nat.c @@ -98,16 +98,51 @@ static uint16_t eim_nat_alloc_port(struct eim_nat_ctx* ctx) { // ==================== Public API ==================== +// Проверить полный IPv4 и границы транспорта до изменения пакета/таблицы. memcpy допускает любой alignment. +static int eim_nat_parse_packet(struct eim_nat_ctx* ctx, const uint8_t* ip, size_t len, uint16_t* header_len) { + if (!ctx || !ctx->initialized || !ctx->table || !ip || len < IP_HDR_MIN_SIZE) { + DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT invalid packet/context: ctx=%p data=%p bytes=%zu", (void*)ctx, (const void*)ip, len); + return EIM_NAT_ERROR; + } + *header_len = (ip[0] & 15) * 4; + uint16_t total, fragment; + memcpy(&total, ip + 2, 2); memcpy(&fragment, ip + 6, 2); + if ((ip[0] >> 4) != 4 || *header_len < 20 || *header_len > len || ntohs(total) != len) { + DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT malformed IPv4: version=%u ihl=%u total=%u bytes=%zu", ip[0] >> 4, *header_len, ntohs(total), len); + return EIM_NAT_ERROR; + } + if (ntohs(fragment) & (IP_FRAG_MF_MASK | IP_FRAG_OFF_MASK)) return EIM_NAT_SKIP; + const uint8_t* transport = ip + *header_len; + size_t remaining = len - *header_len; + int valid; + switch (ip[9]) { + case IPPROTO_TCP_UINT8: + valid = remaining >= 20 && (transport[12] >> 4) >= 5 && (size_t)(transport[12] >> 4) * 4 <= remaining; + break; + case IPPROTO_UDP_UINT8: + if (remaining < 8) { valid = 0; break; } + memcpy(&total, transport + 4, 2); + valid = ntohs(total) >= 8 && ntohs(total) <= remaining; + break; + case IPPROTO_ICMP_UINT8: + if (remaining < 8) { valid = 0; break; } + if (transport[0] != ICMP_ECHO_REQUEST && transport[0] != ICMP_ECHO_REPLY) return EIM_NAT_SKIP; + valid = transport[1] == 0; + break; + default: return EIM_NAT_SKIP; + } + if (!valid) { + DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT malformed transport: proto=%u header=%u bytes=%zu", ip[9], *header_len, len); + return EIM_NAT_ERROR; + } + return EIM_NAT_OK; +} + int eim_nat_egress(struct eim_nat_ctx* ctx, uint8_t* ip_data, size_t ip_len, uint64_t src_node_id, struct ETCP_CONN* src_conn) { - if (ip_len < IP_HDR_MIN_SIZE) return -1; - uint8_t ihl = ip_data[IP_IHL_OFFSET] & 0x0F; - if (ihl < 5) return -1; - uint16_t ip_hdr_len = ihl * 4; - if (ip_len < ip_hdr_len) return -1; - - uint16_t frag_off = ntohs(*(uint16_t*)(ip_data + 6)); - if ((frag_off & (IP_FRAG_MF_MASK | IP_FRAG_OFF_MASK)) != 0) return 0; + uint16_t ip_hdr_len; + int parsed = eim_nat_parse_packet(ctx, ip_data, ip_len, &ip_hdr_len); + if (parsed != EIM_NAT_OK) return parsed; uint8_t proto = ip_data[IP_PROTO_OFFSET]; uint32_t src_ip_net, src_ip_host; @@ -129,7 +164,7 @@ int eim_nat_egress(struct eim_nat_ctx* ctx, uint8_t* ip_data, size_t ip_len, is_icmp_echo = 1; memcpy(&src_port_net, transport + ICMP_ID_OFFSET, 2); } } - if (!is_tcp && !is_udp && !is_icmp_echo) return 0; + if (!is_tcp && !is_udp && !is_icmp_echo) return EIM_NAT_SKIP; struct eim_nat_entry* entry = eim_nat_find_egress(ctx, proto, src_ip_host, src_port_net, src_node_id); uint16_t ext_port_host; @@ -176,19 +211,15 @@ int eim_nat_egress(struct eim_nat_ctx* ctx, uint8_t* ip_data, size_t ip_len, int eim_nat_ingress(struct eim_nat_ctx* ctx, uint8_t* ip_data, size_t ip_len, struct eim_nat_entry** out_entry) { - if (ip_len < IP_HDR_MIN_SIZE) return -1; - uint8_t ihl = ip_data[IP_IHL_OFFSET] & 0x0F; - if (ihl < 5) return -1; - uint16_t ip_hdr_len = ihl * 4; - if (ip_len < ip_hdr_len) return -1; - - uint16_t frag_off = ntohs(*(uint16_t*)(ip_data + 6)); - if ((frag_off & (IP_FRAG_MF_MASK | IP_FRAG_OFF_MASK)) != 0) return 0; + if (out_entry) *out_entry = NULL; + uint16_t ip_hdr_len; + int parsed = eim_nat_parse_packet(ctx, ip_data, ip_len, &ip_hdr_len); + if (parsed != EIM_NAT_OK) return parsed; uint8_t proto = ip_data[IP_PROTO_OFFSET]; uint32_t dst_ip_net; memcpy(&dst_ip_net, ip_data + IP_DST_ADDR_OFFSET, 4); - if (dst_ip_net != htonl(ctx->gateway_ip)) return 0; + if (dst_ip_net != htonl(ctx->gateway_ip)) return EIM_NAT_SKIP; uint8_t* transport = ip_data + ip_hdr_len; size_t tlen = ip_len - ip_hdr_len; @@ -205,12 +236,11 @@ int eim_nat_ingress(struct eim_nat_ctx* ctx, uint8_t* ip_data, size_t ip_len, is_icmp_echo = 1; memcpy(&dst_port_net, transport + ICMP_ID_OFFSET, 2); } } - if (!is_tcp && !is_udp && !is_icmp_echo) return 0; + if (!is_tcp && !is_udp && !is_icmp_echo) return EIM_NAT_SKIP; uint16_t ext_port_host = ntohs(dst_port_net); struct eim_nat_entry* entry = &ctx->table[ext_port_host]; - if (entry->state == EIM_NAT_ENTRY_FREE) return 0; - if (entry->proto != proto) return 0; + if (entry->state == EIM_NAT_ENTRY_FREE || entry->proto != proto) return EIM_NAT_SKIP; uint32_t dst_ip_host = ntohl(dst_ip_net); uint32_t internal_ip_net = htonl(entry->internal_ip); diff --git a/src/eim_nat.h b/src/eim_nat.h index b4da8b4e..a908c1ee 100644 --- a/src/eim_nat.h +++ b/src/eim_nat.h @@ -6,7 +6,7 @@ * Перед обработкой нужен инициализированный ctx; вызовы последовательны в uasync-потоке. * gateway/internal IP — host order, internal_port — network order, внешний порт — host order. * Записи живут до destroy_ctx, автоматического истечения/освобождения портов нет. - * Фрагменты и неподдерживаемые протоколы возвращаются без изменений с кодом 0. + * Фрагменты и неподдерживаемые протоколы возвращаются без изменений с EIM_NAT_SKIP. * src_conn только сохраняется при создании динамической записи, для отправки не используется. */ #ifndef EIM_NAT_H #define EIM_NAT_H @@ -22,6 +22,9 @@ extern "C" { struct ETCP_CONN; #define EIM_NAT_TABLE_SIZE 65536 +#define EIM_NAT_OK 0 // Пакет преобразован. +#define EIM_NAT_SKIP 1 // Фрагмент, неподдерживаемый протокол или отсутствует mapping; пакет не изменён. +#define EIM_NAT_ERROR -1 // Некорректный пакет/контекст или исчерпан пул; пакет не изменён. #define EIM_NAT_ENTRY_FREE 0 #define EIM_NAT_ENTRY_ACTIVE 1 @@ -65,13 +68,12 @@ void eim_nat_destroy_ctx(struct eim_nat_ctx* ctx); * src_node_id — владелец записи; src_conn — необязательная заимствованная метка только для новой ACTIVE-записи. * Контрольные суммы должны быть корректны до вызова: модуль их обновляет, но не проверяет. * ctx создан, ip_data — полный доступный для записи IPv4-пакет; буфер остаётся у вызывающего. - * 0 — преобразован или пропущен без изменений; -1 — короткий заголовок/неверный IHL/исчерпан пул. */ + * Возврат EIM_NAT_OK / SKIP / ERROR. Проверяются версия, IHL, total_length и границы транспортного заголовка. */ int eim_nat_egress(struct eim_nat_ctx* ctx, uint8_t* ip_data, size_t ip_len, uint64_t src_node_id, struct ETCP_CONN* src_conn); /* Для пакета на gateway_ip найти запись по внешнему порту/echo ID и протоколу, восстановить destination. - * ctx/ip_data/checksum — как у egress. out_entry необязателен и изменяется только при найденном mapping: - * заранее установить *out_entry=NULL. Запись заимствована у ctx до destroy_ctx. - * 0 — преобразован или не найден/не поддерживается; -1 — короткий заголовок/неверный IHL. */ + * ctx/ip_data/checksum — как у egress. out_entry необязателен, перед обработкой обнуляется. + * При EIM_NAT_OK получает заимствованную запись; иначе остаётся NULL. Возврат EIM_NAT_OK / SKIP / ERROR. */ int eim_nat_ingress(struct eim_nat_ctx* ctx, uint8_t* ip_data, size_t ip_len, struct eim_nat_entry** out_entry); diff --git a/src/nat_transport.c b/src/nat_transport.c index b6b7dcf9..20e5916d 100644 --- a/src/nat_transport.c +++ b/src/nat_transport.c @@ -121,6 +121,7 @@ static void nat_transport_etcp_recv_cb(struct ETCP_CONN* conn, struct ll_entry* } uint8_t* ip_data = entry->dgram + ROUTER_SVC_PAYLOAD_OFF; size_t ip_len = entry->len - ROUTER_SVC_PAYLOAD_OFF; + if (!ip_len) { queue_dgram_free(entry); queue_entry_free(entry); return; } // Router CLOSE/reset, не IP-пакет. if (tr->nat_via_node_id != 0) { if (tr->nat_tun) { @@ -132,6 +133,8 @@ static void nat_transport_etcp_recv_cb(struct ETCP_CONN* conn, struct ll_entry* int ret = eim_nat_egress(ctx, ip_data, ip_len, src_node_id, conn); if (ret < 0) { DEBUG_WARN(DEBUG_CATEGORY_NAT, "Egress NAT failed"); + } else if (ret == EIM_NAT_SKIP) { + DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "NAT provider: unsupported IP packet dropped bytes=%zu", ip_len); } else if (ret == 0 && tr->nat_tun) { if (tun_write(tr->nat_tun, ip_data, ip_len) == (ssize_t)ip_len) DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "NAT provider: wrote %zu bytes to internet TUN", ip_len);