mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-03 15:33:56 +08:00
240 lines
9.9 KiB
C
240 lines
9.9 KiB
C
/* SPDX-License-Identifier: Apache-2.0 */
|
|
#include <assert.h>
|
|
#define STATE_FILE "/tmp/luci-client-traffic.test.state"
|
|
#include "../src/traffic.c"
|
|
|
|
void rpc_luci_traffic_discover(void (*add)(int, const void *, const unsigned char *)) {}
|
|
|
|
static void put(struct nlmsghdr *msg, int type, const void *data, size_t len)
|
|
{
|
|
struct nlattr *a = (void *)((char *)msg + NLMSG_ALIGN(msg->nlmsg_len));
|
|
a->nla_type = type;
|
|
a->nla_len = NLA_HDRLEN + len;
|
|
memcpy((char *)a + NLA_HDRLEN, data, len);
|
|
msg->nlmsg_len = NLMSG_ALIGN(msg->nlmsg_len) + NLA_ALIGN(a->nla_len);
|
|
}
|
|
|
|
static struct nlattr *begin(struct nlmsghdr *msg, int type)
|
|
{
|
|
struct nlattr *a = (void *)((char *)msg + NLMSG_ALIGN(msg->nlmsg_len));
|
|
a->nla_type = type | NLA_F_NESTED;
|
|
a->nla_len = NLA_HDRLEN;
|
|
msg->nlmsg_len += NLA_HDRLEN;
|
|
return a;
|
|
}
|
|
|
|
static void end(struct nlmsghdr *msg, struct nlattr *a)
|
|
{
|
|
a->nla_len = (char *)msg + msg->nlmsg_len - (char *)a;
|
|
}
|
|
|
|
static void tuple(struct nlmsghdr *msg, int type, const char *src, const char *dst)
|
|
{
|
|
struct address a;
|
|
struct nlattr *t = begin(msg, type), *ip = begin(msg, CTA_TUPLE_IP);
|
|
parse_address(src, &a);
|
|
put(msg, a.family == AF_INET ? CTA_IP_V4_SRC : CTA_IP_V6_SRC, a.bytes, a.family == AF_INET ? 4 : 16);
|
|
parse_address(dst, &a);
|
|
put(msg, a.family == AF_INET ? CTA_IP_V4_DST : CTA_IP_V6_DST, a.bytes, a.family == AF_INET ? 4 : 16);
|
|
end(msg, ip);
|
|
struct nlattr *proto = begin(msg, CTA_TUPLE_PROTO);
|
|
uint8_t num = 6;
|
|
uint16_t port = htons(1234);
|
|
put(msg, CTA_PROTO_NUM, &num, 1);
|
|
put(msg, CTA_PROTO_SRC_PORT, &port, 2);
|
|
put(msg, CTA_PROTO_DST_PORT, &port, 2);
|
|
end(msg, proto);
|
|
end(msg, t);
|
|
}
|
|
|
|
static void counters(struct nlmsghdr *msg, int type, uint64_t bytes)
|
|
{
|
|
unsigned char b[8];
|
|
for (int i = 7; i >= 0; i--) { b[i] = bytes & 255; bytes >>= 8; }
|
|
struct nlattr *a = begin(msg, type);
|
|
put(msg, CTA_COUNTERS_BYTES, b, 8);
|
|
end(msg, a);
|
|
}
|
|
|
|
static void feed(int family, const char *src, const char *dst, const char *reply_src,
|
|
uint32_t id, uint64_t up, uint64_t down, bool dead)
|
|
{
|
|
char buffer[2048] = {0};
|
|
struct nlmsghdr *msg = (void *)buffer;
|
|
struct nfgenmsg *nf = NLMSG_DATA(msg);
|
|
msg->nlmsg_len = NLMSG_LENGTH(sizeof(*nf));
|
|
msg->nlmsg_type = (NFNL_SUBSYS_CTNETLINK << 8) | (dead ? IPCTNL_MSG_CT_DELETE : IPCTNL_MSG_CT_NEW);
|
|
nf->nfgen_family = family;
|
|
tuple(msg, CTA_TUPLE_ORIG, src, dst);
|
|
tuple(msg, CTA_TUPLE_REPLY, reply_src, src);
|
|
put(msg, CTA_ID, &id, 4);
|
|
counters(msg, CTA_COUNTERS_ORIG, up);
|
|
counters(msg, CTA_COUNTERS_REPLY, down);
|
|
assert(process_flow(msg));
|
|
}
|
|
|
|
static void count_record_state(int family, const char *src, const char *dst, const char *reply_src, int state)
|
|
{
|
|
char buffer[2048] = {0};
|
|
struct nlmsghdr *msg = (void *)buffer;
|
|
struct nfgenmsg *nf = NLMSG_DATA(msg);
|
|
msg->nlmsg_len = NLMSG_LENGTH(sizeof(*nf));
|
|
nf->nfgen_family = family;
|
|
tuple(msg, CTA_TUPLE_ORIG, src, dst);
|
|
tuple(msg, CTA_TUPLE_REPLY, reply_src, src);
|
|
if (state >= 0) {
|
|
struct nlattr *info = begin(msg, CTA_PROTOINFO), *tcp = begin(msg, CTA_PROTOINFO_TCP);
|
|
uint8_t value = state;
|
|
put(msg, CTA_PROTOINFO_TCP_STATE, &value, sizeof(value));
|
|
end(msg, tcp); end(msg, info);
|
|
}
|
|
/* Missing byte counters must not prevent connection counting. */
|
|
count_connection(msg);
|
|
}
|
|
|
|
static void count_record(int family, const char *src, const char *dst, const char *reply_src)
|
|
{
|
|
count_record_state(family, src, dst, reply_src, -1);
|
|
}
|
|
|
|
static struct client *client(const char *ip)
|
|
{
|
|
struct client *c = calloc(1, sizeof(*c));
|
|
assert(parse_address(ip, &c->address));
|
|
c->mac[0] = 2;
|
|
c->mac[5] = c->address.bytes[c->address.family == AF_INET ? 3 : 15];
|
|
get_host(c->mac, true);
|
|
c->avl.key = &c->address;
|
|
avl_insert(&clients, &c->avl);
|
|
return c;
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
avl_init(&clients, address_cmp, false, NULL);
|
|
avl_init(&flows, flow_cmp, false, NULL);
|
|
avl_init(&hosts, mac_cmp, false, NULL);
|
|
started = now_ms();
|
|
struct client *a = client("192.168.0.100"), *b = client("192.168.0.101"), *v6 = client("fd00::2");
|
|
unsigned char v6mac[6];
|
|
memcpy(v6mac, v6->mac, 6);
|
|
memcpy(v6->mac, a->mac, 6);
|
|
count_record(AF_INET, "192.168.0.100", "8.8.8.8", "8.8.8.8");
|
|
count_record(AF_INET6, "fd00::2", "2001:db8::1", "2001:db8::1");
|
|
count_record(AF_INET, "8.8.8.8", "203.0.113.1", "192.168.0.101");
|
|
count_record(AF_INET, "192.168.0.100", "203.0.113.1", "192.168.0.100");
|
|
assert(get_host(a->mac, false)->pending_connections == 3);
|
|
assert(get_host(b->mac, false)->pending_connections == 1);
|
|
assert(get_host(a->mac, false)->connections == 0);
|
|
assert(get_host(a->mac, false)->pending_routed_connections == 3);
|
|
assert(get_host(b->mac, false)->pending_routed_connections == 0);
|
|
struct sockaddr_in local4 = { .sin_family = AF_INET };
|
|
struct sockaddr_in6 local6 = { .sin6_family = AF_INET6 };
|
|
inet_pton(AF_INET, "192.168.0.1", &local4.sin_addr);
|
|
inet_pton(AF_INET6, "fd00::1", &local6.sin6_addr);
|
|
struct ifaddrs if6 = { .ifa_addr = (void *)&local6 };
|
|
struct ifaddrs if4 = { .ifa_addr = (void *)&local4, .ifa_next = &if6 };
|
|
router_addresses = &if4;
|
|
count_record(AF_INET, "192.168.0.100", "192.168.0.1", "192.168.0.1");
|
|
count_record(AF_INET6, "fd00::2", "fd00::1", "fd00::1");
|
|
count_record(AF_INET, "192.168.0.100", "8.8.8.8", "192.168.0.1"); /* Redirect to router. */
|
|
count_record(AF_INET6, "fd00::2", "ff02::fb", "ff02::fb");
|
|
count_record_state(AF_INET, "192.168.0.100", "8.8.8.8", "8.8.8.8", TCP_CONNTRACK_TIME_WAIT);
|
|
count_record_state(AF_INET, "192.168.0.100", "8.8.8.8", "8.8.8.8", TCP_CONNTRACK_CLOSE);
|
|
assert(get_host(a->mac, false)->pending_routed_connections == 3);
|
|
count_record(AF_INET, "192.168.0.100", "203.0.113.1", "192.168.0.101"); /* Hairpin forwards. */
|
|
count_record_state(AF_INET, "192.168.0.100", "8.8.8.8", "8.8.8.8", TCP_CONNTRACK_ESTABLISHED);
|
|
assert(get_host(a->mac, false)->pending_routed_connections == 5);
|
|
router_addresses = NULL;
|
|
get_host(a->mac, false)->pending_routed_connections = 0;
|
|
get_host(a->mac, false)->pending_connections = 0;
|
|
get_host(b->mac, false)->pending_connections = 0;
|
|
memcpy(v6->mac, v6mac, 6);
|
|
feed(AF_INET, "192.168.0.100", "8.8.8.8", "8.8.8.8", 1, 1000, 2000, false);
|
|
assert(a->bytes[0] == 0 && a->bytes[1] == 0); /* First sample is a baseline. */
|
|
finish_dump();
|
|
assert(a->warm && !a->ready);
|
|
assert(get_host(a->mac, false)->total[0] == 0);
|
|
generation++;
|
|
feed(AF_INET, "192.168.0.100", "8.8.8.8", "8.8.8.8", 1, 3000, 7000, false);
|
|
assert(a->bytes[0] == 2000 && a->bytes[1] == 5000);
|
|
/* DNAT: original packets are downloads to reply.src, replies are uploads. */
|
|
feed(AF_INET, "8.8.4.4", "203.0.113.1", "192.168.0.101", 2, 10000, 100, false);
|
|
assert(b->bytes[0] == 100 && b->bytes[1] == 10000);
|
|
feed(AF_INET6, "fd00::2", "2001:db8::1", "2001:db8::1", 3, 500, 1000, false);
|
|
assert(v6->bytes[0] == 500 && v6->bytes[1] == 1000);
|
|
/* Destroy notifications include the final bytes of short-lived flows. */
|
|
feed(AF_INET, "192.168.0.100", "1.1.1.1", "1.1.1.1", 4, 80, 200, true);
|
|
assert(a->bytes[0] == 2080 && a->bytes[1] == 5200);
|
|
feed(AF_INET, "192.168.0.100", "1.1.1.1", "1.1.1.1", 4, 80, 200, false);
|
|
assert(a->bytes[0] == 2080 && a->bytes[1] == 5200); /* Late dump after destroy. */
|
|
feed(AF_INET, "192.168.0.100", "8.8.8.8", "8.8.8.8", 1, 20, 30, false);
|
|
assert(a->bytes[0] == 2080 && a->bytes[1] == 5200); /* Reset cannot underflow. */
|
|
sampled = now_ms() - 2000;
|
|
finish_dump();
|
|
assert(a->ready && a->rate[0] >= 1035 && a->rate[0] <= 1040);
|
|
assert(a->rate[1] >= 2590 && a->rate[1] <= 2600);
|
|
/* A burst must stay in the rolling window, even across two quiet samples. */
|
|
struct client rolling = { .ready = true, .bytes = {6000, 12000} };
|
|
update_rate(&rolling, 2000);
|
|
assert(rolling.rate[0] == 3000 && rolling.rate[1] == 6000);
|
|
update_rate(&rolling, 2000);
|
|
update_rate(&rolling, 2000);
|
|
assert(rolling.rate[0] == 1000 && rolling.rate[1] == 2000);
|
|
update_rate(&rolling, 2000);
|
|
assert(rolling.rate[0] == 0 && rolling.rate[1] == 0);
|
|
/* Unequal intervals use sum(bytes) / sum(time), not mean(rate). */
|
|
rolling.bytes[0] = 12000;
|
|
update_rate(&rolling, 4000);
|
|
assert(rolling.rate[0] == 1500);
|
|
rolling.ready = false;
|
|
update_rate(&rolling, 2000);
|
|
assert(rolling.rate[0] == 0 && rolling.duration[0] == 0);
|
|
rolling.ready = true;
|
|
rolling.bytes[0] = 2000;
|
|
update_rate(&rolling, 2000);
|
|
assert(rolling.rate[0] == 1000);
|
|
|
|
/* Total bytes use exact deltas, never the rolling average. */
|
|
struct host *ha = get_host(a->mac, false);
|
|
assert(ha->total[0] == 2080 && ha->total[1] == 5200);
|
|
uint64_t before = ha->total[0] + ha->total[1];
|
|
save_state();
|
|
struct client *c, *cn;
|
|
struct host *h, *hn;
|
|
clear_flows();
|
|
avl_for_each_element_safe(&clients, c, avl, cn) { avl_delete(&clients, &c->avl); free(c); }
|
|
avl_for_each_element_safe(&hosts, h, avl, hn) { avl_delete(&hosts, &h->avl); free(h); }
|
|
load_state();
|
|
struct address address;
|
|
parse_address("192.168.0.100", &address);
|
|
a = avl_find_element(&clients, &address, a, avl);
|
|
assert(a && !a->warm);
|
|
ha = get_host(a->mac, false);
|
|
assert(ha->total[0] + ha->total[1] == before);
|
|
/* Restored baselines only count the increment across an rpcd reload. */
|
|
feed(AF_INET, "192.168.0.100", "8.8.8.8", "8.8.8.8", 1, 120, 230, false);
|
|
assert(ha->total[0] + ha->total[1] == before + 300);
|
|
/* An IP reassignment must not transfer the old connection to a new MAC. */
|
|
unsigned char newmac[6] = {2, 1, 2, 3, 4, 5};
|
|
bind_client(AF_INET, address.bytes, newmac);
|
|
feed(AF_INET, "192.168.0.100", "8.8.8.8", "8.8.8.8", 1, 220, 330, false);
|
|
assert(ha->total[0] + ha->total[1] == before + 500);
|
|
assert(get_host(newmac, false)->total[0] == 0);
|
|
/* Recovery preserves totals and the flow baselines, preventing replay. */
|
|
reset_sampler("test_overrun");
|
|
assert(!a->ready && !a->warm && flows.count > 0);
|
|
feed(AF_INET, "192.168.0.100", "8.8.8.8", "8.8.8.8", 1, 220, 330, false);
|
|
assert(ha->total[0] + ha->total[1] == before + 500);
|
|
/* Collection is scheduled even when no browser has ever requested data. */
|
|
uloop_init();
|
|
sample_timer(&timer);
|
|
assert(timer.pending);
|
|
uloop_timeout_cancel(&timer);
|
|
reset_sampler("test_end");
|
|
uloop_done();
|
|
unlink(STATE_FILE);
|
|
puts("traffic: NAT, IPv6, rolling rates, exact totals, restart recovery, IP reassignment and continuous sampling passed");
|
|
return 0;
|
|
}
|