feat(traffic-flow): add NAT fields to flow processing and analytics
Docker images / prepare-release (push) Successful in 10s
Docker images / backend-test (push) Successful in 2m22s
Docker images / frontend-image (push) Successful in 3m30s
Docker images / updater-image (push) Successful in 45s
Docker images / backend-image (push) Successful in 2m51s
Docker images / notify-webhook (push) Skipped
Docker images / publish-release (push) Successful in 9s

- Introduced new fields for NAT source and destination IPs, as well as their respective ports, in the flow data model.
- Updated database schema and migration scripts to accommodate the new NAT fields in the `flow_buckets` table.
- Enhanced flow analytics and processing functions to utilize the new NAT fields, improving accuracy in traffic flow analysis.
- Added tests to validate the handling of NAT data in various scenarios, ensuring robustness in flow processing.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Denozordec
2026-09-11 23:01:48 +07:00
co-authored by Cursor
parent 5f774ce26e
commit fc29dcede7
22 changed files with 578 additions and 85 deletions
+14 -1
View File
@@ -9,6 +9,8 @@ if (!(await withPgOrSkip())) {
process.exit(0)
}
await applySqlMigrations(pool)
{
const { rows } = await dbQuery<{ n: string }>(`SELECT COUNT(*)::text AS n FROM servers`)
assert.ok(rows[0])
@@ -114,13 +116,17 @@ if (!(await withPgOrSkip())) {
SELECT column_name, udt_name
FROM information_schema.columns
WHERE table_schema = 'public' AND table_name = 'flow_buckets'
AND column_name IN ('src', 'dst', 'next_hop', 'proto')
AND column_name IN ('src', 'dst', 'next_hop', 'proto', 'nat_src', 'nat_dst', 'nat_src_port', 'nat_dst_port')
`)
const by = Object.fromEntries(rows.map((r) => [r.column_name, r.udt_name]))
assert.equal(by.src, "inet")
assert.equal(by.dst, "inet")
assert.equal(by.next_hop, "inet")
assert.equal(by.proto, "int2")
assert.equal(by.nat_src, "inet")
assert.equal(by.nat_dst, "inet")
assert.equal(by.nat_src_port, "int4")
assert.equal(by.nat_dst_port, "int4")
}
{
@@ -179,6 +185,13 @@ if (!(await withPgOrSkip())) {
assert.equal(by.section, "text")
}
{
const mig = await dbQuery<{ id: string }>(
`SELECT id FROM schema_migrations WHERE id = '0007_flow_buckets_nat'`,
)
assert.equal(mig.rows.length, 1, "0007 применена")
}
{
const marker = await dbQuery<{ sqlite_imported_at: string | null }>(
`SELECT sqlite_imported_at FROM data_migration WHERE id = 1`,
+4
View File
@@ -266,6 +266,10 @@ export const flowBuckets = pgTable("flow_buckets", {
nextHop: inet("next_hop"),
flowStartMs: bigint("flow_start_ms", { mode: "number" }).notNull().default(0),
flowEndMs: bigint("flow_end_ms", { mode: "number" }).notNull().default(0),
natSrc: inet("nat_src"),
natDst: inet("nat_dst"),
natSrcPort: integer("nat_src_port").notNull().default(0),
natDstPort: integer("nat_dst_port").notNull().default(0),
}, (t) => [
primaryKey({
name: "flow_buckets_pkey",
+1
View File
@@ -125,6 +125,7 @@ const TABLES: TableCopy[] = [
["src_port", "int"], ["dst_port", "int"], ["bytes", "int"], ["packets", "int"],
["in_iface", "text"], ["out_iface", "text"], ["next_hop", "inet"],
["flow_start_ms", "int"], ["flow_end_ms", "int"],
["nat_src", "inet"], ["nat_dst", "inet"], ["nat_src_port", "int"], ["nat_dst_port", "int"],
]},
{ table: "flow_minute_stats", timeCol: "bucket_at", retentionDays: 3, columns: [
["server_id", "int"], ["bucket_at", "ts"], ["bytes", "int"], ["packets", "int"],