ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
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 PC_ENABLE_FLOW_EXPORT
12
13#include <string.h>
14
16
17size_t flow_v5_write_header(uint8_t *buf, size_t cap, const FlowV5Header *h)
18{
19 if (!buf || !h || cap < FLOW_V5_HEADER_SIZE)
20 {
21 return 0;
22 }
23 size_t p = 0;
24 p += pc_wr16be(buf + p, 5); // version
25 p += pc_wr16be(buf + p, h->count);
26 p += pc_wr32be(buf + p, h->sys_uptime);
27 p += pc_wr32be(buf + p, h->unix_secs);
28 p += pc_wr32be(buf + p, h->unix_nsecs);
29 p += pc_wr32be(buf + p, h->flow_sequence);
30 buf[p++] = h->engine_type;
31 buf[p++] = h->engine_id;
32 p += pc_wr16be(buf + p, h->sampling_interval);
33 return p; // 24
34}
35
36size_t flow_v5_write_record(uint8_t *buf, size_t cap, const FlowV5Record *r)
37{
38 if (!buf || !r || cap < FLOW_V5_RECORD_SIZE)
39 {
40 return 0;
41 }
42 size_t p = 0;
43 p += pc_wr32be(buf + p, r->src_addr);
44 p += pc_wr32be(buf + p, r->dst_addr);
45 p += pc_wr32be(buf + p, r->next_hop);
46 p += pc_wr16be(buf + p, r->input);
47 p += pc_wr16be(buf + p, r->output);
48 p += pc_wr32be(buf + p, r->d_pkts);
49 p += pc_wr32be(buf + p, r->d_octets);
50 p += pc_wr32be(buf + p, r->first);
51 p += pc_wr32be(buf + p, r->last);
52 p += pc_wr16be(buf + p, r->src_port);
53 p += pc_wr16be(buf + p, r->dst_port);
54 buf[p++] = 0; // pad1
55 buf[p++] = r->tcp_flags;
56 buf[p++] = r->prot;
57 buf[p++] = r->tos;
58 p += pc_wr16be(buf + p, r->src_as);
59 p += pc_wr16be(buf + p, r->dst_as);
60 buf[p++] = r->src_mask;
61 buf[p++] = r->dst_mask;
62 buf[p++] = 0; // pad2 (2 octets)
63 buf[p++] = 0;
64 return p; // 48
65}
66
67// ---- v9 / IPFIX cursor ----
68
69static void w_u16(FlowWriter *w, uint16_t v)
70{
71 if (w->error)
72 {
73 return;
74 }
75 if (w->pos + 2 > w->cap)
76 {
77 w->error = true;
78 return;
79 }
80 w->pos += pc_wr16be(w->buf + w->pos, v);
81}
82
83static void w_u32(FlowWriter *w, uint32_t v)
84{
85 if (w->error)
86 {
87 return;
88 }
89 if (w->pos + 4 > w->cap)
90 {
91 w->error = true;
92 return;
93 }
94 w->pos += pc_wr32be(w->buf + w->pos, v);
95}
96
97static void w_bytes(FlowWriter *w, const uint8_t *p, size_t n)
98{
99 if (w->error)
100 {
101 return;
102 }
103 if (w->pos + n > w->cap)
104 {
105 w->error = true;
106 return;
107 }
108 memcpy(w->buf + w->pos, p, n);
109 w->pos += n;
110}
111
112// Append @p n zero octets (FlowSet padding).
113static void w_zero(FlowWriter *w, size_t n)
114{
115 if (w->error)
116 {
117 return;
118 }
119 if (w->pos + n > w->cap)
120 {
121 w->error = true;
122 return;
123 }
124 memset(w->buf + w->pos, 0, n);
125 w->pos += n;
126}
127
128static void patch16(FlowWriter *w, size_t off, uint16_t v)
129{
130 pc_wr16be(w->buf + off, v);
131}
132
133bool flow_ipfix_begin(FlowWriter *w, uint8_t *buf, size_t cap, uint32_t export_time, uint32_t seq, uint32_t domain_id)
134{
135 if (!w || !buf)
136 {
137 return false;
138 }
139 w->buf = buf;
140 w->cap = cap;
141 w->pos = 0;
142 w->set_start = 0;
143 w->records = 0;
144 w->version = 10;
145 w->error = false;
146 w_u16(w, 10); // version
147 w_u16(w, 0); // length placeholder (patched on finish)
148 w_u32(w, export_time);
149 w_u32(w, seq);
150 w_u32(w, domain_id);
151 return !w->error;
152}
153
154bool flow_v9_begin(FlowWriter *w, uint8_t *buf, size_t cap, uint32_t sys_uptime, uint32_t unix_secs, uint32_t seq,
155 uint32_t source_id)
156{
157 if (!w || !buf)
158 {
159 return false;
160 }
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 {
181 return false;
182 }
183 if (w->set_start) // a data set was left open
184 {
185 flow_export_data_end(w);
186 }
187 size_t set_off = w->pos;
188 w_u16(w, w->version == 9 ? 0 : 2); // FlowSet ID 0 (v9) / Set ID 2 (IPFIX)
189 w_u16(w, 0); // set length placeholder
190 w_u16(w, template_id);
191 w_u16(w, (uint16_t)field_count);
192 for (size_t i = 0; i < field_count; i++)
193 {
194 w_u16(w, fields[i].type);
195 w_u16(w, fields[i].length);
196 }
197 if (!w->error)
198 {
199 patch16(w, set_off + 2, (uint16_t)(w->pos - set_off));
200 }
201 w->records++; // a template record counts toward the v9 record count
202 return !w->error;
203}
204
205bool flow_export_data_begin(FlowWriter *w, uint16_t template_id)
206{
207 if (!w || template_id < 256) // data sets reference a template id >= 256
208 {
209 return false;
210 }
211 if (w->set_start)
212 {
213 flow_export_data_end(w);
214 }
215 w->set_start = w->pos;
216 w_u16(w, template_id); // Set/FlowSet ID == template id
217 w_u16(w, 0); // length placeholder
218 return !w->error;
219}
220
221bool flow_export_data_record(FlowWriter *w, const uint8_t *record, size_t len)
222{
223 if (!w || !w->set_start || !record || len == 0)
224 {
225 return false;
226 }
227 w_bytes(w, record, len);
228 if (!w->error)
229 {
230 w->records++;
231 }
232 return !w->error;
233}
234
235bool flow_export_data_end(FlowWriter *w)
236{
237 if (!w || !w->set_start)
238 {
239 return false;
240 }
241 if (w->version == 9) // v9 pads each FlowSet so the next starts on a 4-octet boundary
242 {
243 size_t set_len = w->pos - w->set_start;
244 w_zero(w, (4 - (set_len & 3)) & 3);
245 }
246 if (!w->error)
247 {
248 patch16(w, w->set_start + 2, (uint16_t)(w->pos - w->set_start));
249 }
250 w->set_start = 0;
251 return !w->error;
252}
253
254size_t flow_export_finish(FlowWriter *w)
255{
256 if (!w)
257 {
258 return 0;
259 }
260 if (w->set_start)
261 {
262 flow_export_data_end(w);
263 }
264 if (w->error)
265 {
266 return 0;
267 }
268 if (w->version == 9)
269 {
270 patch16(w, 2, w->records); // count = total records
271 }
272 else
273 {
274 if (w->pos > 0xFFFF) // the IPFIX length field is 16-bit; fail closed rather than truncate
275 {
276 return 0;
277 }
278 patch16(w, 2, (uint16_t)w->pos); // IPFIX message length in octets
279 }
280 return w->pos;
281}
282
283#endif // PC_ENABLE_FLOW_EXPORT
Fixed-width integer serializers into a raw uint8_t* buffer - one source of truth.
size_t pc_wr16be(uint8_t *p, uint16_t v)
Write v big-endian at p.
Definition endian.h:85
size_t pc_wr32be(uint8_t *p, uint32_t v)
Write v big-endian at p.
Definition endian.h:93
Flow-record export codec (PC_ENABLE_FLOW_EXPORT) - zero-heap exporter-side builders for NetFlow v5,...