Validate NAT packet boundaries and distinguish skipped packets
This commit is contained in:
1 parent
00b4b92749
commit
5a837f903c
3 files changed
+61
-26
No files matched your search
+51
-21
@@ -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);
|
||||
|
||||
+7
-5
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user