ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
flow_export.h
Go to the documentation of this file.
1// ProtoCore v1.0.16 - Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file flow_export.h
6 * @brief The Exporting Process (PROTOCORE_ENABLE_FLOW_EXPORT): builds IPFIX Messages (RFC 7011),
7 * NetFlow Version 9 Export Packets (RFC 3954), and vendor NetFlow Version 5 packets.
8 *
9 * RFC 7011 sec 3 "IPFIX Message Format": a Message is a Message Header (sec 3.1) followed by one
10 * or more Sets (sec 3.3). RFC 3954 sec 5 "Export Packet Format" is the same shape one revision
11 * earlier: a Header (sec 5.1) followed by FlowSets. Every field is network byte order.
12 *
13 * Template-then-data. A Template Record (RFC 7011 sec 3.4.1, RFC 3954 sec 5.2) lists the Field
14 * Specifiers a record carries and is given a Template ID; Data Records (RFC 7011 sec 3.4.3,
15 * RFC 3954 sec 5.3) then travel in a Set whose Set ID is that Template ID. RFC 7011 sec 3.3.2:
16 * "A value of 2 is reserved for Template Sets... Values 256 and above are used for Data Sets."
17 * RFC 3954 sec 5.2 uses FlowSet ID 0 for the Template FlowSet and reserves IDs 0-255.
18 *
19 * A Field Specifier (RFC 7011 sec 3.2) is an Information Element identifier plus a Field Length.
20 * The identifier is the elementId of RFC 7012 sec 2.1, from the IANA "IPFIX Information Elements"
21 * registry (RFC 7012 sec 7.1). The E bit stays zero here, so no Enterprise Number follows.
22 *
23 * NetFlow Version 5 has no IETF specification. It is a vendor-defined fixed export format
24 * (Cisco Systems NetFlow Version 5); RFC 3954 specifies Version 9 only and does not describe
25 * Version 5. The layout built here is that vendor format: a 24-octet header then N 48-octet
26 * records.
27 *
28 * One message is under construction at a time: ipfix_begin or v9_begin, then template_set,
29 * data_set_begin, data_record, data_set_end, then message_finish, which patches the IPFIX
30 * Message Length (RFC 7011 sec 3.1) or the v9 Count (RFC 3954 sec 5.1) and reports the octets.
31 * This is the wire codec only; the flow cache is the app's and the datagram send is
32 * `Udp.client->sendto`.
33 *
34 * @author Douglas Quigg (dstroy0)
35 * @date 2026
36 */
37
38#ifndef PROTOCORE_FLOW_EXPORT_H
39#define PROTOCORE_FLOW_EXPORT_H
40
41#include "protocore_config.h" // the entry point: protocore_types.h for the widths
42
43#if PROTOCORE_ENABLE_FLOW_EXPORT
44
46
47#define FLOW_V5_HEADER_SIZE 24 ///< octets in the vendor Version 5 packet header
48#define FLOW_V5_RECORD_SIZE 48 ///< octets in one vendor Version 5 flow record
49
50/** @brief Vendor NetFlow Version 5 packet header. The builder writes Version 5 itself. */
51typedef struct
52{
53 uint16_t count; ///< number of records that follow
54 uint32_t sys_uptime; ///< ms since the device booted
55 uint32_t unix_secs; ///< seconds since the epoch
56 uint32_t unix_nsecs; ///< residual nanoseconds
57 uint32_t flow_sequence; ///< running count of exported flows
58 uint8_t engine_type; ///< flow-switching engine type
59 uint8_t engine_id; ///< flow-switching engine id
60 uint16_t sampling_interval; ///< sampling mode in the top 2 bits, interval in the rest
61} FlowV5Header;
62
63/** @brief One vendor NetFlow Version 5 flow record. The builder zero-fills both pad spans. */
64typedef struct
65{
66 uint32_t src_addr; ///< source IPv4, host order, written big-endian
67 uint32_t dst_addr; ///< destination IPv4
68 uint32_t next_hop; ///< next-hop router IPv4
69 uint16_t input; ///< ingress interface SNMP index
70 uint16_t output; ///< egress interface SNMP index
71 uint32_t d_pkts; ///< packets in the flow
72 uint32_t d_octets; ///< octets in the flow
73 uint32_t first; ///< sys_uptime at flow start
74 uint32_t last; ///< sys_uptime at the last packet
75 uint16_t src_port; ///< source transport port
76 uint16_t dst_port; ///< destination transport port
77 uint8_t tcp_flags; ///< cumulative OR of the flow's TCP flags
78 uint8_t prot; ///< IP protocol number
79 uint8_t tos; ///< IP type of service
80 uint16_t src_as; ///< source autonomous system
81 uint16_t dst_as; ///< destination autonomous system
82 uint8_t src_mask; ///< source prefix length
83 uint8_t dst_mask; ///< destination prefix length
84} FlowV5Record;
85
86/**
87 * @brief RFC 7011 sec 3.2 Field Specifier: one Information Element and its on-wire length.
88 * RFC 3954 sec 5.2 calls the same pair Field Type and Field Length.
89 */
90typedef struct
91{
92 uint16_t information_element_id; ///< RFC 7012 sec 2.1 elementId, E bit zero
93 uint16_t field_length; ///< RFC 7011 sec 3.2 Field Length, in octets
94} FlowFieldSpecifier;
95
96/** @brief Where a builder writes and how far it may go. */
97typedef struct
98{
99 uint8_t *buf; ///< the octets a build fills
100 size_t cap; ///< how many octets it may use
101} FlowOutArgs;
102
103/** @brief The vendor Version 5 structures a fixed-format write reads. */
104typedef struct
105{
106 const FlowV5Header *header; ///< the 24-octet packet header a write emits
107 const FlowV5Record *record; ///< the 48-octet flow record a write emits
108} FlowV5Args;
109
110/** @brief The Message Header fields a begin writes: RFC 7011 sec 3.1, RFC 3954 sec 5.1. */
111typedef struct
112{
113 uint32_t sys_uptime; ///< RFC 3954 sec 5.1 sysUpTime, ms since the device booted
114 uint32_t unix_secs; ///< RFC 3954 sec 5.1 UNIX Secs, seconds since the epoch
115 uint32_t export_time; ///< RFC 7011 sec 3.1 Export Time, seconds since the epoch
116 uint32_t sequence_number; ///< RFC 3954 sec 5.1 counts Export Packets, RFC 7011 sec 3.1 counts Data Records
117 uint32_t observation_domain_id; ///< RFC 7011 sec 3.1 Observation Domain ID, RFC 3954 sec 5.1 Source ID
118} FlowMessageArgs;
119
120/** @brief RFC 7011 sec 3.4.1 / RFC 3954 sec 5.2: what one Template Record lists. */
121typedef struct
122{
123 const FlowFieldSpecifier *fields; ///< the Field Specifiers, in wire order
124 size_t field_count; ///< RFC 7011 sec 3.4.1 / RFC 3954 sec 5.2 Field Count
125} FlowTemplateArgs;
126
127/** @brief RFC 7011 sec 3.4.3 / RFC 3954 sec 5.3: one already-encoded Data Record. */
128typedef struct
129{
130 const uint8_t *record; ///< its Field Values in Template order, big-endian
131 size_t len; ///< its octet length
132} FlowDataArgs;
133
134/**
135 * @brief The flow-record Exporting Process: RFC 7011 IPFIX, RFC 3954 NetFlow v9, vendor v5.
136 *
137 * A caller sets the members a call takes, invokes it through ::FlowExport, and reads the outcome
138 * off the same handle.
139 *
140 * @var FlowExportNs::template_id RFC 7011 sec 3.4.1 / RFC 3954 sec 5.2 Template ID a Set names
141 * @var FlowExportNs::out where a builder writes and how far it may go
142 * @var FlowExportNs::v5 the vendor Version 5 header and record a fixed write emits
143 * @var FlowExportNs::message the Message Header fields a begin writes
144 * @var FlowExportNs::tmpl the Field Specifiers a Template Record lists
145 * @var FlowExportNs::data one encoded Data Record and its length
146 * @var FlowExportNs::ok a call's true/false outcome
147 * @var FlowExportNs::n octets a v5 write emitted, or the finished message length
148 * @var FlowExportNs::v5_header write the 24-octet vendor Version 5 packet header
149 * @var FlowExportNs::v5_record write one 48-octet vendor Version 5 flow record
150 * @var FlowExportNs::ipfix_begin write the IPFIX Message Header (RFC 7011 sec 3.1)
151 * @var FlowExportNs::v9_begin write the NetFlow v9 packet Header (RFC 3954 sec 5.1)
152 * @var FlowExportNs::template_set emit a Template Set (RFC 7011 sec 3.3.2 Set ID 2) or a Template
153 * FlowSet (RFC 3954 sec 5.2 FlowSet ID 0)
154 * @var FlowExportNs::data_set_begin open a Data Set for template_id (RFC 7011 sec 3.3.2,
155 * RFC 3954 sec 5.3)
156 * @var FlowExportNs::data_record append one Data Record to the open Set
157 * @var FlowExportNs::data_set_end patch the Set Length and, for v9, pad to a 4-octet boundary
158 * @var FlowExportNs::message_finish close any open Set, patch the IPFIX Length or the v9 Count
159 */
160typedef struct
161{
162 uint16_t template_id; ///< the Template ID a Template Set or a Data Set names
163 FlowOutArgs out; ///< where a builder writes
164 FlowV5Args v5; ///< the vendor Version 5 structures a fixed write emits
165 FlowMessageArgs message; ///< the Message Header fields a begin writes
166 FlowTemplateArgs tmpl; ///< the Field Specifiers a Template Record lists
167 FlowDataArgs data; ///< one encoded Data Record
168 proto_bool ok;
169 size_t n;
170} FlowExportVars;
171
172/** @brief The operands and the outcome. */
173extern FlowExportVars FlowExportV;
174
175/** @brief The entries. */
176typedef struct
177{
178 void (*const v5_header)(uint8_t *work);
179 void (*const v5_record)(uint8_t *work);
180 void (*const ipfix_begin)(uint8_t *work);
181 void (*const v9_begin)(uint8_t *work);
182 void (*const template_set)(uint8_t *work);
183 void (*const data_set_begin)(uint8_t *work);
184 void (*const data_record)(uint8_t *work);
185 void (*const data_set_end)(uint8_t *work);
186 void (*const message_finish)(uint8_t *work);
187} FlowExportNs;
188
189// What the table binds, defined once in the .c and taking one parameter each: everything
190// else an entry needs is an operand in FlowExportV or a region of the borrow at a fixed offset.
191void protocore_flow_export_v5_header(uint8_t *work);
192void protocore_flow_export_v5_record(uint8_t *work);
193void protocore_flow_export_ipfix_begin(uint8_t *work);
194void protocore_flow_export_v9_begin(uint8_t *work);
195void protocore_flow_export_template_set(uint8_t *work);
196void protocore_flow_export_data_set_begin(uint8_t *work);
197void protocore_flow_export_data_record(uint8_t *work);
198void protocore_flow_export_data_set_end(uint8_t *work);
199void protocore_flow_export_message_finish(uint8_t *work);
200
201// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
202// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
203// `FlowExport.v5_header(work)` resolves to a named function and becomes a DIRECT call. An extern table
204// leaves the call indirect and the symbol live at every level, -O2 -flto included.
205static const FlowExportNs FlowExport __attribute__((unused)) = {
206 .v5_header = protocore_flow_export_v5_header,
207 .v5_record = protocore_flow_export_v5_record,
208 .ipfix_begin = protocore_flow_export_ipfix_begin,
209 .v9_begin = protocore_flow_export_v9_begin,
210 .template_set = protocore_flow_export_template_set,
211 .data_set_begin = protocore_flow_export_data_set_begin,
212 .data_record = protocore_flow_export_data_record,
213 .data_set_end = protocore_flow_export_data_set_end,
214 .message_finish = protocore_flow_export_message_finish,
215};
216
217/**
218 * @brief The PROTOCORE_FLOW_EXPORT_BORROW bytes this module's state lives in.
219 *
220 * Stated beside the namespace rather than on it: an entry takes a borrow, and this is where
221 * that borrow comes from. Taken once from the end of the pool, which no mark and no release
222 * walks, so the state lasts the life of the program.
223 *
224 * @return the span.
225 */
226uint8_t *protocore_flow_export_span(void);
227
229
230#endif // PROTOCORE_ENABLE_FLOW_EXPORT
231
232#endif // PROTOCORE_FLOW_EXPORT_H
#define PROTOCORE_BEGIN_DECLS
Give a header's declarations C linkage, so their symbol names carry no parameter types.
Definition types.h:96
_Bool proto_bool
The truth value.
Definition types.h:64
#define PROTOCORE_END_DECLS
Definition types.h:97