DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
flow_export.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file flow_export.cpp
6 * @brief NetFlow v5 / v9 + IPFIX exporter codec (pure, host-tested).
7 */
8
10
11#if DETWS_ENABLE_FLOW_EXPORT
12
13#include <string.h>
14
15// ---- bounded big-endian writers into a raw buffer (used by the v5 builders) ----
16
17static size_t put16(uint8_t *p, uint16_t v)
18{
19 p[0] = (uint8_t)(v >> 8);
20 p[1] = (uint8_t)v;
21 return 2;
22}
23
24static size_t put32(uint8_t *p, uint32_t v)
25{
26 p[0] = (uint8_t)(v >> 24);
27 p[1] = (uint8_t)(v >> 16);
28 p[2] = (uint8_t)(v >> 8);
29 p[3] = (uint8_t)v;
30 return 4;
31}
32
33size_t flow_v5_write_header(uint8_t *buf, size_t cap, const FlowV5Header *h)
34{
35 if (!buf || !h || cap < FLOW_V5_HEADER_SIZE)
36 return 0;
37 size_t p = 0;
38 p += put16(buf + p, 5); // version
39 p += put16(buf + p, h->count);
40 p += put32(buf + p, h->sys_uptime);
41 p += put32(buf + p, h->unix_secs);
42 p += put32(buf + p, h->unix_nsecs);
43 p += put32(buf + p, h->flow_sequence);
44 buf[p++] = h->engine_type;
45 buf[p++] = h->engine_id;
46 p += put16(buf + p, h->sampling_interval);
47 return p; // 24
48}
49
50size_t flow_v5_write_record(uint8_t *buf, size_t cap, const FlowV5Record *r)
51{
52 if (!buf || !r || cap < FLOW_V5_RECORD_SIZE)
53 return 0;
54 size_t p = 0;
55 p += put32(buf + p, r->src_addr);
56 p += put32(buf + p, r->dst_addr);
57 p += put32(buf + p, r->next_hop);
58 p += put16(buf + p, r->input);
59 p += put16(buf + p, r->output);
60 p += put32(buf + p, r->d_pkts);
61 p += put32(buf + p, r->d_octets);
62 p += put32(buf + p, r->first);
63 p += put32(buf + p, r->last);
64 p += put16(buf + p, r->src_port);
65 p += put16(buf + p, r->dst_port);
66 buf[p++] = 0; // pad1
67 buf[p++] = r->tcp_flags;
68 buf[p++] = r->prot;
69 buf[p++] = r->tos;
70 p += put16(buf + p, r->src_as);
71 p += put16(buf + p, r->dst_as);
72 buf[p++] = r->src_mask;
73 buf[p++] = r->dst_mask;
74 buf[p++] = 0; // pad2 (2 octets)
75 buf[p++] = 0;
76 return p; // 48
77}
78
79// ---- v9 / IPFIX cursor ----
80
81static void w_u16(FlowWriter *w, uint16_t v)
82{
83 if (w->error)
84 return;
85 if (w->pos + 2 > w->cap)
86 {
87 w->error = true;
88 return;
89 }
90 w->pos += put16(w->buf + w->pos, v);
91}
92
93static void w_u32(FlowWriter *w, uint32_t v)
94{
95 if (w->error)
96 return;
97 if (w->pos + 4 > w->cap)
98 {
99 w->error = true;
100 return;
101 }
102 w->pos += put32(w->buf + w->pos, v);
103}
104
105static void w_bytes(FlowWriter *w, const uint8_t *p, size_t n)
106{
107 if (w->error)
108 return;
109 if (w->pos + n > w->cap)
110 {
111 w->error = true;
112 return;
113 }
114 memcpy(w->buf + w->pos, p, n);
115 w->pos += n;
116}
117
118// Append @p n zero octets (FlowSet padding).
119static void w_zero(FlowWriter *w, size_t n)
120{
121 if (w->error)
122 return;
123 if (w->pos + n > w->cap)
124 {
125 w->error = true;
126 return;
127 }
128 memset(w->buf + w->pos, 0, n);
129 w->pos += n;
130}
131
132static void patch16(FlowWriter *w, size_t off, uint16_t v)
133{
134 put16(w->buf + off, v);
135}
136
137bool flow_ipfix_begin(FlowWriter *w, uint8_t *buf, size_t cap, uint32_t export_time, uint32_t seq, uint32_t domain_id)
138{
139 if (!w || !buf)
140 return false;
141 w->buf = buf;
142 w->cap = cap;
143 w->pos = 0;
144 w->set_start = 0;
145 w->records = 0;
146 w->version = 10;
147 w->error = false;
148 w_u16(w, 10); // version
149 w_u16(w, 0); // length placeholder (patched on finish)
150 w_u32(w, export_time);
151 w_u32(w, seq);
152 w_u32(w, domain_id);
153 return !w->error;
154}
155
156bool flow_v9_begin(FlowWriter *w, uint8_t *buf, size_t cap, uint32_t sys_uptime, uint32_t unix_secs, uint32_t seq,
157 uint32_t source_id)
158{
159 if (!w || !buf)
160 return false;
161 w->buf = buf;
162 w->cap = cap;
163 w->pos = 0;
164 w->set_start = 0;
165 w->records = 0;
166 w->version = 9;
167 w->error = false;
168 w_u16(w, 9); // version
169 w_u16(w, 0); // count placeholder (patched on finish)
170 w_u32(w, sys_uptime);
171 w_u32(w, unix_secs);
172 w_u32(w, seq);
173 w_u32(w, source_id);
174 return !w->error;
175}
176
177bool flow_export_template(FlowWriter *w, uint16_t template_id, const FlowField *fields, size_t field_count)
178{
179 if (!w || !fields || field_count == 0)
180 return false;
181 if (w->set_start) // a data set was left open
182 flow_export_data_end(w);
183 size_t set_off = w->pos;
184 w_u16(w, w->version == 9 ? 0 : 2); // FlowSet ID 0 (v9) / Set ID 2 (IPFIX)
185 w_u16(w, 0); // set length placeholder
186 w_u16(w, template_id);
187 w_u16(w, (uint16_t)field_count);
188 for (size_t i = 0; i < field_count; i++)
189 {
190 w_u16(w, fields[i].type);
191 w_u16(w, fields[i].length);
192 }
193 if (!w->error)
194 patch16(w, set_off + 2, (uint16_t)(w->pos - set_off));
195 w->records++; // a template record counts toward the v9 record count
196 return !w->error;
197}
198
199bool flow_export_data_begin(FlowWriter *w, uint16_t template_id)
200{
201 if (!w || template_id < 256) // data sets reference a template id >= 256
202 return false;
203 if (w->set_start)
204 flow_export_data_end(w);
205 w->set_start = w->pos;
206 w_u16(w, template_id); // Set/FlowSet ID == template id
207 w_u16(w, 0); // length placeholder
208 return !w->error;
209}
210
211bool flow_export_data_record(FlowWriter *w, const uint8_t *record, size_t len)
212{
213 if (!w || !w->set_start || !record || len == 0)
214 return false;
215 w_bytes(w, record, len);
216 if (!w->error)
217 w->records++;
218 return !w->error;
219}
220
221bool flow_export_data_end(FlowWriter *w)
222{
223 if (!w || !w->set_start)
224 return false;
225 if (w->version == 9) // v9 pads each FlowSet so the next starts on a 4-octet boundary
226 {
227 size_t set_len = w->pos - w->set_start;
228 w_zero(w, (4 - (set_len & 3)) & 3);
229 }
230 if (!w->error)
231 patch16(w, w->set_start + 2, (uint16_t)(w->pos - w->set_start));
232 w->set_start = 0;
233 return !w->error;
234}
235
236size_t flow_export_finish(FlowWriter *w)
237{
238 if (!w)
239 return 0;
240 if (w->set_start)
241 flow_export_data_end(w);
242 if (w->error)
243 return 0;
244 if (w->version == 9)
245 patch16(w, 2, w->records); // count = total records
246 else
247 {
248 if (w->pos > 0xFFFF) // the IPFIX length field is 16-bit; fail closed rather than truncate
249 return 0;
250 patch16(w, 2, (uint16_t)w->pos); // IPFIX message length in octets
251 }
252 return w->pos;
253}
254
255#endif // DETWS_ENABLE_FLOW_EXPORT
Flow-record export codec (DETWS_ENABLE_FLOW_EXPORT) - zero-heap exporter-side builders for NetFlow v5...