Browse Source
- sock_match: единая классификация сокетов (DIRECT/NAT/LOCAL) и адресов (PUB/PRIV/UNREACH) - eim приравнен к direct; на приватный адрес — только с приватного интерфейса; оба EIM за одним NAT (одинаковый публичный IP) — только через LAN - topo_node: LOCAL-сокеты рекламируются (INTERFACE), private скрыт, NAT-адрес не рекламируется для strict и при оффлайне - ntp: флаг reachable по циклам + пере-анонс nodeinfo при смене доступности - cm_direct_add_links / ncd_create_links / conn_match_candidate_sockets на sock_match - test_sock_match: unit-тесты классификации и сопоставленияv2
13 changed files with 349 additions and 107 deletions
@ -0,0 +1,82 @@ |
|||||||
|
/* sock_match.c — классификация сокетов и сопоставление адресов при подключении */ |
||||||
|
#include "sock_match.h" |
||||||
|
|
||||||
|
#include <string.h> |
||||||
|
#include "../lib/platform_compat.h" |
||||||
|
#include "../lib/debug_config.h" |
||||||
|
#include "utun_instance.h" |
||||||
|
#include "etcp_connections.h" |
||||||
|
#include "config_parser.h" |
||||||
|
#include "topo_node.h" |
||||||
|
#include "ntp_time.h" |
||||||
|
|
||||||
|
/* Нормализация NAT-типа: сырые (NAT_TYPE_*) или проверенные (NAT_VERIFIED_*) значения. */ |
||||||
|
static int nat_is_eim(uint8_t n) { return n == NAT_TYPE_EIM || n == NAT_VERIFIED_EIM; } |
||||||
|
static int nat_is_direct(uint8_t n) { return n == NAT_TYPE_DIRECT || n == NAT_VERIFIED_DIRECT; } |
||||||
|
|
||||||
|
uint8_t sock_role_of(uint8_t type, uint8_t nat_type, int ntp_reachable) { |
||||||
|
uint8_t role; |
||||||
|
if (type == CFG_SERVER_TYPE_LOCAL) |
||||||
|
role = SOCK_ROLE_LOCAL; /* без интернета (из конфига) */ |
||||||
|
else if (type == CFG_SERVER_TYPE_PUBLIC || type == CFG_SERVER_TYPE_UNKNOWN) |
||||||
|
role = SOCK_ROLE_DIRECT; |
||||||
|
else if (type == CFG_SERVER_TYPE_NAT) |
||||||
|
role = (nat_is_eim(nat_type) || nat_is_direct(nat_type)) ? SOCK_ROLE_DIRECT : SOCK_ROLE_NAT; |
||||||
|
else |
||||||
|
role = SOCK_ROLE_NAT; /* PRIVATE — скрытый, только исходящие */ |
||||||
|
if (!ntp_reachable && role != SOCK_ROLE_LOCAL) |
||||||
|
role = SOCK_ROLE_LOCAL; /* NTP недоступен — интернета нет */ |
||||||
|
return role; |
||||||
|
} |
||||||
|
|
||||||
|
uint8_t sock_target_class(uint8_t a_type, uint8_t tcfg, uint8_t tnat) { |
||||||
|
if (tcfg == CFG_SERVER_TYPE_PUBLIC || tcfg == CFG_SERVER_TYPE_UNKNOWN) return SOCK_TC_PUB; |
||||||
|
if (tcfg == CFG_SERVER_TYPE_PRIVATE || tcfg == CFG_SERVER_TYPE_LOCAL) return SOCK_TC_PRIV; |
||||||
|
/* CFG_SERVER_TYPE_NAT */ |
||||||
|
if (nat_is_eim(tnat) || nat_is_direct(tnat)) |
||||||
|
return (a_type == TOPO_ADDR_INTERFACE) ? SOCK_TC_PRIV : SOCK_TC_PUB; /* EIM=direct: NAT-адрес публичный, INTERFACE — приватный */ |
||||||
|
return (a_type == TOPO_ADDR_INTERFACE) ? SOCK_TC_PRIV : SOCK_TC_UNREACH; /* strict/unknown: публичный адрес не пробиваем */ |
||||||
|
} |
||||||
|
|
||||||
|
int sock_match(const struct sock_view* v, uint8_t a_type, uint8_t tcfg, uint8_t tnat, uint32_t target_ip4) { |
||||||
|
uint8_t tc = sock_target_class(a_type, tcfg, tnat); |
||||||
|
if (tc == SOCK_TC_UNREACH) return 0; /* strict/unknown NAT-адрес — напрямую не пробьём */ |
||||||
|
if (tc == SOCK_TC_PUB) { |
||||||
|
if (v->role == SOCK_ROLE_LOCAL) return 0; /* у нашего сокета нет интернета */ |
||||||
|
if (a_type == TOPO_ADDR_NAT && v->is_eim && nat_is_eim(tnat) |
||||||
|
&& v->public_ip4 && v->public_ip4 == target_ip4) return 0; /* оба EIM за одним NAT — hairpin */ |
||||||
|
return 1; |
||||||
|
} |
||||||
|
return v->has_priv; /* приватный адрес — только с нашего приватного интерфейса */ |
||||||
|
} |
||||||
|
|
||||||
|
int sock_collect_views(struct UTUN_INSTANCE* inst, struct sock_view* out, int max) { |
||||||
|
if (!inst || !out || max <= 0) return 0; |
||||||
|
int ntp_reachable = inst->ntp.reachable; |
||||||
|
int n = 0; |
||||||
|
struct ETCP_SOCKET* s = inst->etcp_sockets; |
||||||
|
while (s && n < max) { |
||||||
|
if (s->local_addr.ss_family != AF_INET) { s = s->next; continue; } |
||||||
|
struct sock_view* v = &out[n++]; |
||||||
|
v->sock = s; |
||||||
|
v->role = sock_role_of(s->type, s->nat_type, ntp_reachable); |
||||||
|
v->has_inet = (uint8_t)(v->role != SOCK_ROLE_LOCAL); |
||||||
|
v->has_priv = (uint8_t)(s->type != CFG_SERVER_TYPE_PUBLIC && s->type != CFG_SERVER_TYPE_UNKNOWN); |
||||||
|
v->is_eim = (uint8_t)(s->type == CFG_SERVER_TYPE_NAT && nat_is_eim(s->nat_type)); |
||||||
|
v->family = AF_INET; |
||||||
|
v->is_tcp = s->is_tcp; |
||||||
|
v->local_ip4 = 0; v->public_ip4 = 0; |
||||||
|
{ const struct sockaddr_storage* ia = s->interface_addr.ss_family ? &s->interface_addr : &s->local_addr; |
||||||
|
if (ia && ia->ss_family == AF_INET) v->local_ip4 = ((const struct sockaddr_in*)ia)->sin_addr.s_addr; } |
||||||
|
{ const struct sockaddr_in* na = (const struct sockaddr_in*)&s->nat_addr; |
||||||
|
if (s->nat_addr.ss_family == AF_INET && na->sin_addr.s_addr != 0) |
||||||
|
v->public_ip4 = na->sin_addr.s_addr; |
||||||
|
else if (s->type == CFG_SERVER_TYPE_PUBLIC || s->type == CFG_SERVER_TYPE_UNKNOWN || nat_is_direct(s->nat_type)) |
||||||
|
v->public_ip4 = v->local_ip4; } |
||||||
|
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "sock_view[%d] sock=%s type=%d nat=%d role=%d inet=%d priv=%d eim=%d tcp=%d local=%08x public=%08x", |
||||||
|
n - 1, s->name, s->type, s->nat_type, v->role, v->has_inet, v->has_priv, v->is_eim, v->is_tcp, |
||||||
|
v->local_ip4, v->public_ip4); |
||||||
|
s = s->next; |
||||||
|
} |
||||||
|
return n; |
||||||
|
} |
||||||
@ -0,0 +1,97 @@ |
|||||||
|
/**
|
||||||
|
* @file sock_match.h |
||||||
|
* @brief Классификация сокетов и сопоставление адресов при подключении. |
||||||
|
* |
||||||
|
* ─── Общая идея ──────────────────────────────────────────────────────────── |
||||||
|
* Узел имеет несколько сокетов с разной природой сети (публичный IP, NAT, |
||||||
|
* скрытый/приватный, без интернета). Чтобы при подключении не создавать |
||||||
|
* заведомо мёртвые линки, каждый наш сокет сводится к одной из трёх РОЛЕЙ, |
||||||
|
* а каждый адрес пира — к одному из трёх КЛАССОВ. Соединение пробуем только |
||||||
|
* для совместимых пар «наш сокет → адрес пира». Для удобства формируем |
||||||
|
* список наших сокетов в плоском виде (struct sock_view): роль, локальный IP |
||||||
|
* (interface), публичный IP (NAT) и флаги достижимости. |
||||||
|
* |
||||||
|
* ─── Роли нашего сокета (что он умеет как ИСТОЧНИК) ─────────────────────── |
||||||
|
* DIRECT — публичный IP / EIM NAT / verified-direct: интернет в обе стороны. |
||||||
|
* NAT — только исходящие (strict/unknown NAT или скрытый private): |
||||||
|
* интернет есть, но входящих соединений нет. |
||||||
|
* LOCAL — без интернета: type=local из конфига, либо любой сокет при |
||||||
|
* недоступном NTP (узел оффлайн). Работает только в LAN. |
||||||
|
* |
||||||
|
* EIM-сокет приравнивается к DIRECT (endpoint-independent mapping пробивается |
||||||
|
* снаружи, поэтому его публичный адрес достижим как у public). |
||||||
|
* |
||||||
|
* ─── Реклама адресов (что узел публикует в nodeinfo) ────────────────────── |
||||||
|
* PUBLIC → свой публичный IP (INTERFACE). |
||||||
|
* NAT + EIM → приватный IP (INTERFACE) + публичный IP (NAT). |
||||||
|
* NAT + STRICT → только приватный IP (INTERFACE), публичный НЕ светим. |
||||||
|
* LOCAL → только приватный IP (INTERFACE) — для связи двух клиентов |
||||||
|
* в одной локалке. |
||||||
|
* PRIVATE → скрыт, не рекламируется вовсе (но usable как источник). |
||||||
|
* |
||||||
|
* ─── Сопоставление (наш сокет → адрес пира) ─────────────────────────────── |
||||||
|
* Адрес пира классифицируется: PUB (публичный, из интернета), PRIV |
||||||
|
* (приватный/LAN), UNREACH (напрямую недостижим). Правила: |
||||||
|
* UNREACH → не пробуем (strict/unknown NAT-адрес); |
||||||
|
* PUB → нужен интернет у нашего сокета (role != LOCAL), плюс |
||||||
|
* hairpin-фильтр: если оба узла EIM за одним NAT (одинаковый |
||||||
|
* публичный IP) — по публичному адресу не ходим, остаётся LAN; |
||||||
|
* PRIV → только с нашего сокета, имеющего приватный интерфейс |
||||||
|
* (has_priv) — приватный адрес достижим лишь из той же сети. |
||||||
|
*/ |
||||||
|
#ifndef SOCK_MATCH_H |
||||||
|
#define SOCK_MATCH_H |
||||||
|
|
||||||
|
#ifdef __cplusplus |
||||||
|
extern "C" { |
||||||
|
#endif |
||||||
|
|
||||||
|
#include <stdint.h> |
||||||
|
|
||||||
|
struct UTUN_INSTANCE; |
||||||
|
struct ETCP_SOCKET; |
||||||
|
|
||||||
|
/* Роли нашего сокета (как источника соединения) */ |
||||||
|
#define SOCK_ROLE_DIRECT 0 /* публичный / EIM / verified-direct — интернет в обе стороны */ |
||||||
|
#define SOCK_ROLE_NAT 1 /* только исходящие (strict/unknown NAT, скрытый private) */ |
||||||
|
#define SOCK_ROLE_LOCAL 2 /* без интернета (type=local или NTP недоступен) */ |
||||||
|
|
||||||
|
/* Классы целевого адреса пира */ |
||||||
|
#define SOCK_TC_PUB 0 /* публичный — достижим из интернета */ |
||||||
|
#define SOCK_TC_PRIV 1 /* приватный / LAN */ |
||||||
|
#define SOCK_TC_UNREACH 2 /* напрямую недостижим (strict/unknown NAT) */ |
||||||
|
|
||||||
|
/**
|
||||||
|
* Плоское представление нашего сокета для сопоставления. |
||||||
|
* "ip сокета" — local_ip4 (interface), "публичный" — public_ip4 (NAT/прямой), |
||||||
|
* "тип" — role + флаги has_inet/has_priv/is_eim. |
||||||
|
*/ |
||||||
|
struct sock_view { |
||||||
|
struct ETCP_SOCKET* sock; |
||||||
|
uint8_t role; /* SOCK_ROLE_* */ |
||||||
|
uint8_t has_inet; /* 1 — есть интернет (role != LOCAL) */ |
||||||
|
uint8_t has_priv; /* 1 — есть приватный интерфейс (type != PUBLIC/UNKNOWN) */ |
||||||
|
uint8_t is_eim; /* 1 — NAT + EIM (для hairpin) */ |
||||||
|
uint8_t family; /* AF_INET / AF_INET6 */ |
||||||
|
uint8_t is_tcp; |
||||||
|
uint32_t local_ip4; /* interface_addr, network order (0 если нет) */ |
||||||
|
uint32_t public_ip4; /* nat_addr / interface если direct, network order (0 если нет) */ |
||||||
|
}; |
||||||
|
|
||||||
|
/* Роль нашего сокета по конфиг-типу, NAT-типу и доступности интернета. */ |
||||||
|
uint8_t sock_role_of(uint8_t type, uint8_t nat_type, int ntp_reachable); |
||||||
|
|
||||||
|
/* Класс адреса пира по типу адреса (TOPO_ADDR_*), конфиг-типу и NAT-типу сокета. */ |
||||||
|
uint8_t sock_target_class(uint8_t a_type, uint8_t tcfg, uint8_t tnat); |
||||||
|
|
||||||
|
/* 1 — наш сокет (view) подходит для подключения к целевому адресу (target_ip4, network order). */ |
||||||
|
int sock_match(const struct sock_view* v, uint8_t a_type, uint8_t tcfg, uint8_t tnat, uint32_t target_ip4); |
||||||
|
|
||||||
|
/* Собирает список наших сокетов в плоском виде (v4). Возвращает число элементов. */ |
||||||
|
int sock_collect_views(struct UTUN_INSTANCE* inst, struct sock_view* out, int max); |
||||||
|
|
||||||
|
#ifdef __cplusplus |
||||||
|
} |
||||||
|
#endif |
||||||
|
|
||||||
|
#endif /* SOCK_MATCH_H */ |
||||||
@ -0,0 +1,87 @@ |
|||||||
|
/**
|
||||||
|
* @file test_sock_match.c |
||||||
|
* @brief Unit-тесты классификации сокетов и сопоставления адресов (sock_match). |
||||||
|
* |
||||||
|
* Тестирует только чистую логику sock_role_of / sock_target_class / sock_match |
||||||
|
* без сети и без UTUN_INSTANCE — с синтетическими struct sock_view и адресами. |
||||||
|
*/ |
||||||
|
#include <stdio.h> |
||||||
|
#include <string.h> |
||||||
|
#include "../src/routing_layer/sock_match.h" |
||||||
|
#include "../src/config_parser.h" |
||||||
|
#include "../src/transport_layer/etcp_connections.h" |
||||||
|
#include "../src/routing_layer/topo_node.h" |
||||||
|
|
||||||
|
static int fails = 0; |
||||||
|
static int checks = 0; |
||||||
|
|
||||||
|
#define CHECK(cond, msg) do { checks++; if (!(cond)) { fails++; printf("FAIL: %s\n", msg); } } while (0) |
||||||
|
|
||||||
|
static struct sock_view mk_view(uint8_t role, uint8_t has_inet, uint8_t has_priv, uint8_t is_eim, uint32_t pub4) { |
||||||
|
struct sock_view v; memset(&v, 0, sizeof(v)); |
||||||
|
v.role = role; v.has_inet = has_inet; v.has_priv = has_priv; v.is_eim = is_eim; v.public_ip4 = pub4; |
||||||
|
return v; |
||||||
|
} |
||||||
|
|
||||||
|
static void test_role_of(void) { |
||||||
|
/* config -> role, при ntp_reachable=1 */ |
||||||
|
CHECK(sock_role_of(CFG_SERVER_TYPE_PUBLIC, NAT_TYPE_UNKNOWN, 1) == SOCK_ROLE_DIRECT, "public -> DIRECT"); |
||||||
|
CHECK(sock_role_of(CFG_SERVER_TYPE_UNKNOWN, NAT_TYPE_UNKNOWN, 1) == SOCK_ROLE_DIRECT, "unknown -> DIRECT"); |
||||||
|
CHECK(sock_role_of(CFG_SERVER_TYPE_NAT, NAT_VERIFIED_EIM, 1) == SOCK_ROLE_DIRECT, "nat+EIM -> DIRECT"); |
||||||
|
CHECK(sock_role_of(CFG_SERVER_TYPE_NAT, NAT_VERIFIED_DIRECT,1) == SOCK_ROLE_DIRECT, "nat+DIRECT -> DIRECT"); |
||||||
|
CHECK(sock_role_of(CFG_SERVER_TYPE_NAT, NAT_VERIFIED_STRICT,1) == SOCK_ROLE_NAT, "nat+STRICT -> NAT"); |
||||||
|
CHECK(sock_role_of(CFG_SERVER_TYPE_NAT, NAT_TYPE_UNKNOWN, 1) == SOCK_ROLE_NAT, "nat+unknown -> NAT"); |
||||||
|
CHECK(sock_role_of(CFG_SERVER_TYPE_PRIVATE, NAT_TYPE_UNKNOWN, 1) == SOCK_ROLE_NAT, "private -> NAT"); |
||||||
|
CHECK(sock_role_of(CFG_SERVER_TYPE_LOCAL, NAT_TYPE_UNKNOWN, 1) == SOCK_ROLE_LOCAL, "local -> LOCAL"); |
||||||
|
/* NTP offline: все кроме LOCAL демотируются */ |
||||||
|
CHECK(sock_role_of(CFG_SERVER_TYPE_PUBLIC, NAT_TYPE_UNKNOWN, 0) == SOCK_ROLE_LOCAL, "offline public -> LOCAL"); |
||||||
|
CHECK(sock_role_of(CFG_SERVER_TYPE_NAT, NAT_VERIFIED_EIM, 0) == SOCK_ROLE_LOCAL, "offline nat+EIM -> LOCAL"); |
||||||
|
CHECK(sock_role_of(CFG_SERVER_TYPE_PRIVATE, NAT_TYPE_UNKNOWN, 0) == SOCK_ROLE_LOCAL, "offline private -> LOCAL"); |
||||||
|
CHECK(sock_role_of(CFG_SERVER_TYPE_LOCAL, NAT_TYPE_UNKNOWN, 0) == SOCK_ROLE_LOCAL, "offline local -> LOCAL"); |
||||||
|
} |
||||||
|
|
||||||
|
static void test_target_class(void) { |
||||||
|
CHECK(sock_target_class(TOPO_ADDR_INTERFACE, CFG_SERVER_TYPE_PUBLIC, NAT_TYPE_UNKNOWN) == SOCK_TC_PUB, "public INTERFACE -> PUB"); |
||||||
|
CHECK(sock_target_class(TOPO_ADDR_INTERFACE, CFG_SERVER_TYPE_UNKNOWN, NAT_TYPE_UNKNOWN) == SOCK_TC_PUB, "unknown -> PUB"); |
||||||
|
CHECK(sock_target_class(TOPO_ADDR_INTERFACE, CFG_SERVER_TYPE_LOCAL, NAT_TYPE_UNKNOWN) == SOCK_TC_PRIV, "local -> PRIV"); |
||||||
|
CHECK(sock_target_class(TOPO_ADDR_INTERFACE, CFG_SERVER_TYPE_PRIVATE, NAT_TYPE_UNKNOWN) == SOCK_TC_PRIV, "private -> PRIV"); |
||||||
|
CHECK(sock_target_class(TOPO_ADDR_NAT, CFG_SERVER_TYPE_NAT, NAT_VERIFIED_EIM) == SOCK_TC_PUB, "nat+EIM NAT-addr -> PUB"); |
||||||
|
CHECK(sock_target_class(TOPO_ADDR_INTERFACE, CFG_SERVER_TYPE_NAT, NAT_VERIFIED_EIM) == SOCK_TC_PRIV, "nat+EIM INTERFACE -> PRIV"); |
||||||
|
CHECK(sock_target_class(TOPO_ADDR_NAT, CFG_SERVER_TYPE_NAT, NAT_VERIFIED_DIRECT) == SOCK_TC_PUB, "nat+DIRECT NAT-addr -> PUB"); |
||||||
|
CHECK(sock_target_class(TOPO_ADDR_NAT, CFG_SERVER_TYPE_NAT, NAT_VERIFIED_STRICT) == SOCK_TC_UNREACH, "nat+STRICT NAT-addr -> UNREACH"); |
||||||
|
CHECK(sock_target_class(TOPO_ADDR_INTERFACE, CFG_SERVER_TYPE_NAT, NAT_VERIFIED_STRICT) == SOCK_TC_PRIV, "nat+STRICT INTERFACE -> PRIV"); |
||||||
|
CHECK(sock_target_class(TOPO_ADDR_NAT, CFG_SERVER_TYPE_NAT, NAT_TYPE_UNKNOWN) == SOCK_TC_UNREACH, "nat+unknown NAT-addr -> UNREACH"); |
||||||
|
CHECK(sock_target_class(TOPO_ADDR_INTERFACE, CFG_SERVER_TYPE_NAT, NAT_TYPE_UNKNOWN) == SOCK_TC_PRIV, "nat+unknown INTERFACE -> PRIV"); |
||||||
|
} |
||||||
|
|
||||||
|
static void test_match(void) { |
||||||
|
uint32_t pub = 0x01020304u; /* произвольный "публичный" IP */ |
||||||
|
struct sock_view direct = mk_view(SOCK_ROLE_DIRECT, 1, 0, 0, pub); |
||||||
|
struct sock_view natout = mk_view(SOCK_ROLE_NAT, 1, 1, 0, 0); |
||||||
|
struct sock_view local = mk_view(SOCK_ROLE_LOCAL, 0, 1, 0, 0); |
||||||
|
|
||||||
|
/* наш DIRECT (публичный) */ |
||||||
|
CHECK(sock_match(&direct, TOPO_ADDR_NAT, CFG_SERVER_TYPE_PUBLIC, NAT_TYPE_UNKNOWN, pub) == 1, "DIRECT->PUB ok"); |
||||||
|
CHECK(sock_match(&direct, TOPO_ADDR_INTERFACE, CFG_SERVER_TYPE_LOCAL, NAT_TYPE_UNKNOWN, 0) == 0, "DIRECT->PRIV нет приватного интерфейса"); |
||||||
|
/* наш NAT (outbound-only) */ |
||||||
|
CHECK(sock_match(&natout, TOPO_ADDR_NAT, CFG_SERVER_TYPE_PUBLIC, NAT_TYPE_UNKNOWN, pub) == 1, "NAT->PUB ok"); |
||||||
|
CHECK(sock_match(&natout, TOPO_ADDR_INTERFACE, CFG_SERVER_TYPE_NAT, NAT_VERIFIED_STRICT, 0) == 1, "NAT->PRIV (есть приватный)"); |
||||||
|
/* наш LOCAL (без интернета) */ |
||||||
|
CHECK(sock_match(&local, TOPO_ADDR_NAT, CFG_SERVER_TYPE_PUBLIC, NAT_TYPE_UNKNOWN, pub) == 0, "LOCAL->PUB нет интернета"); |
||||||
|
CHECK(sock_match(&local, TOPO_ADDR_INTERFACE, CFG_SERVER_TYPE_LOCAL, NAT_TYPE_UNKNOWN, 0) == 1, "LOCAL->PRIV ок (LAN)"); |
||||||
|
/* UNREACH цель */ |
||||||
|
CHECK(sock_match(&direct, TOPO_ADDR_NAT, CFG_SERVER_TYPE_NAT, NAT_VERIFIED_STRICT, pub) == 0, "строгий NAT-адрес недостижим"); |
||||||
|
/* hairpin: оба EIM, одинаковый публичный IP */ |
||||||
|
struct sock_view eim = mk_view(SOCK_ROLE_DIRECT, 1, 1, 1, pub); |
||||||
|
CHECK(sock_match(&eim, TOPO_ADDR_NAT, CFG_SERVER_TYPE_NAT, NAT_VERIFIED_EIM, pub) == 0, "hairpin (одинаковый публичный IP)"); |
||||||
|
CHECK(sock_match(&eim, TOPO_ADDR_NAT, CFG_SERVER_TYPE_NAT, NAT_VERIFIED_EIM, 0x05060708u) == 1, "EIM разные публичные IP -> ok"); |
||||||
|
} |
||||||
|
|
||||||
|
int main(void) { |
||||||
|
test_role_of(); |
||||||
|
test_target_class(); |
||||||
|
test_match(); |
||||||
|
if (fails) { printf("RESULT: %d/%d failed\n", fails, checks); return 1; } |
||||||
|
printf("RESULT: all %d checks passed\n", checks); |
||||||
|
return 0; |
||||||
|
} |
||||||
Loading…
Reference in new issue