GitHub - FastNetMon/NatStream: NatStream is user space NAT events exporter in Netflow v9 and IPFIX formats · GitHub
Skip to content

Repository files navigation

NatStream

NatStream is a Linux daemon that exports conntrack NAT events as IPFIX (RFC 7011) or NetFlow v9 (RFC 3954) records over UDP.

It listens to netlink conntrack notifications, extracts NAT-relevant flow fields, and sends them as flow records to a configured collector.

NatStream on a live NAT box: 28.7 Gbps in, 28.8 Gbps out, exporter at 0.1% CPU and 2 MB RSS

A live NAT box carrying ~28.7 Gbps in and ~28.8 Gbps out on bond0, exporting every conntrack event to a collector: natstream sits at 0.1% CPU and 2 MB resident on an 88-thread E5-2699, and the whole machine is at 3%.

Upstream: https://github.com/FastNetMon/NatStream

Install (Debian / Ubuntu)

Every release publishes a .deb per target distribution, with a SHA256SUMS alongside them: https://github.com/FastNetMon/NatStream/releases/latest.

To build one yourself, ./build.sh packages the daemon inside a Docker container based on the target distribution — the same script the release workflow runs, so a locally built package is the packaged one. See DEVELOPMENT.md.

The package

Path
/usr/sbin/natstream the daemon
/lib/systemd/system/natstream.service the service unit
/etc/default/natstream configuration (a dpkg conffile)

Installing does not enable or start the service, because it has no useful default collector and would only produce a restart loop. Configure it first:

sudo dpkg -i natstream_0.1.0-1~deb13_amd64.deb
sudoedit /etc/default/natstream        # set COLLECTOR=ip:port
sudo systemctl enable --now natstream.service
journalctl -u natstream -f

/etc/default/natstream holds the collector endpoint and any extra flags:

COLLECTOR=203.0.113.10:4739
EXPORTER_OPTS=--protocol netflow9 --profile nat-source --counter-width 4

For several collectors, use a comma-separated list — systemd substitutes ${COLLECTOR} as a single word, so this reaches the exporter as one value:

COLLECTOR=203.0.113.10:4739,198.51.100.7:4739

The service

The unit runs the daemon in the foreground under Type=simple and lets journald take its output — systemd supervises it, so the daemon's own --daemon supervisor is not used and would only get in the way.

It does not run as root. The exporter needs CAP_NET_ADMIN to join the conntrack netlink group, force its socket buffer sizes, and set the nf_conntrack sysctls — the kernel's net sysctl handler grants CAP_NET_ADMIN holders owner-level access, so no root and no CAP_DAC_OVERRIDE are required. The unit therefore uses DynamicUser=yes with exactly that one capability, plus the usual sandboxing (ProtectHome, PrivateDevices, ProtectProc, RestrictAddressFamilies=AF_NETLINK AF_INET AF_INET6, a @system-service syscall filter, and no new privileges). systemd-analyze security rates it 2.6.

One directive is deliberately absent: ProtectKernelTunables=yes would remount /proc/sys read-only and break the sysctl setup the exporter does at startup. If you would rather lock that down, set the three sysctls declaratively in /etc/sysctl.d/, add --no-sysctl to EXPORTER_OPTS, and then add ProtectKernelTunables=yes to a unit override.

Requirements

  • Linux (Netlink netfilter API required)
  • CAP_NET_ADMIN (or root) for netlink socket setup and buffer/socket options

On startup the exporter sets three sysctls: nf_conntrack_events=1, without which there are no events to export at all; nf_conntrack_acct=1, without which every counter is zero; and nf_conntrack_timestamp=1, without which a flow's start and end times are unknown. Failing to enable events is fatal; failing to enable accounting or timestamping only warns. Use --no-sysctl to leave all three alone on hosts where they are already configured or /proc/sys is read-only. Note that these settings are net-namespace-wide and are not restored on exit.

Enabling nf_conntrack_acct or nf_conntrack_timestamp does not fix up the entries that already exist. Both counters and timestamps live in extension areas that are attached when a conntrack entry is created, so every flow already tracked when the sysctl was flipped stays without them for the rest of its life: it reports zero counters, and its flow times fall back to when the exporter saw the event. New entries get both immediately, so the effect fades as the table churns; a long-lived session can keep exporting zeros for hours. conntrack -F clears it at once, at the cost of breaking every established NAT session on the box.

Run

sudo natstream --collector <IP>:<port>

Examples:

# Basic foreground mode
sudo natstream --collector 203.0.113.10:4739

# Override buffers and domain id
sudo natstream \
  --collector 203.0.113.10:4739 \
  --recv-buf 8388608 \
  --send-buf 8388608 \
  --domain-id 100

# The same records to two collectors, as a repeated flag or a comma-separated list
sudo natstream \
  --collector 203.0.113.10:4739 \
  --collector 198.51.100.7:4739
sudo natstream --collector 203.0.113.10:4739,198.51.100.7:4739

# NetFlow v9 to a collector that only decodes the base field registry
sudo natstream \
  --collector 203.0.113.10:2055 \
  --protocol netflow9 \
  --profile flow-only \
  --counter-width 4

# Run with self-supervision, a log file and verbose logs
sudo natstream --collector 203.0.113.10:4739 \
  --daemon --log-file /var/log/natstream.log -v

Under systemd, prefer Type=simple without --daemon and let journald capture stderr, rather than the built-in supervisor.

Command-line options

  • -c, --collector <ip:port> (required): flow collector endpoint. Repeat the flag, or separate endpoints with commas, to export the same records to several collectors. IPv4 and IPv6 endpoints can be mixed.
  • --protocol <ipfix|netflow9>: export protocol (default: ipfix).
  • --profile <full|nat-source|flow-only>: which field set to export (default: full).
  • --counter-width <4|8>: byte and packet counter width (default: 8).
  • --template-id <u16>: template ID to advertise (default: 256, minimum 256).
  • --template-interval <secs>: seconds between template retransmissions (default: 5).
  • --domain-id <u32>: observation domain ID / NetFlow v9 source ID (default: 0).
  • --recv-buf <bytes>: netlink receive buffer size (default: 4194304).
  • --send-buf <bytes>: UDP send buffer size (default: 4194304).
  • -v, --verbose: enable debug logging.
  • --daemon: run as a background supervisor/worker pair with restart on worker crash.
  • --log-file <path>: in --daemon mode, append log output here instead of discarding it.
  • --no-sysctl: do not touch the nf_conntrack sysctls.

The effective configuration is logged at startup, including the record size and how many records fit in a message.

Protocol and profiles

The NAT elements this exporter relies on — natEvent and the four postNAT* elements — are IPFIX registry entries and are not in RFC 3954's NetFlow v9 field table. They are a Cisco NAT Event Logging convention there. Support varies by collector and by collector version, which is what the profiles are for: pick the largest field set your collector actually decodes.

Profile Fields IPFIX record (8B / 4B counters) v9 record (8B / 4B) Carries
full 16 74 B / 58 B 66 B / 50 B Both NAT directions and both counter directions
nat-source 14 68 B / 52 B 60 B / 44 B Source translation only; drops postNATDestination*
flow-only 11 61 B / 45 B 53 B / 37 B No NAT information at all — pre-NAT five-tuple, counters and timestamps only

The two protocols differ in size only because of the flow timestamps: 8 bytes each under IPFIX, 4 each under NetFlow v9.

Rough starting points, worth confirming against your own version: pmacct (nfacctd) and nfdump 1.7+ handle full; ntopng and NEL-aware commercial collectors generally do too. Collectors limited to their base registry need flow-only, which is a real loss of information — it exports the flow but not the translation. Use tshark -d udp.port==<port>,cflow -V to see exactly what a dissector makes of your export before blaming a collector.

--counter-width 4 exists because NetFlow v9's IN_BYTES/OUT_BYTES default to four bytes and some collectors expect exactly that. Conntrack counters are 64-bit, so a value too large for a four-byte field is clamped, not truncated, and the clamp count is reported in the periodic stats line.

Record layout

Both protocols carry the same values in the same order. They differ in how the elements are identified, and — for the two timestamps alone — in what the value actually measures.

# Field IPFIX IE NetFlow v9 type Bytes Profiles
1 natEvent 230 230 1 full, nat-source
2 protocolIdentifier 4 4 PROTOCOL 1 all
3 sourceIPv4Address 8 8 IPV4_SRC_ADDR 4 all
4 destinationIPv4Address 12 12 IPV4_DST_ADDR 4 all
5 sourceTransportPort 7 7 L4_SRC_PORT 2 all
6 destinationTransportPort 11 11 L4_DST_PORT 2 all
7 postNATSourceIPv4Address 225 225 4 full, nat-source
8 postNAPTSourceTransportPort 227 227 2 full, nat-source
9 postNATDestinationIPv4Address 226 226 4 full
10 postNAPTDestinationTransportPort 228 228 2 full
11 octetDeltaCount 1 1 IN_BYTES 4 or 8 all
12 packetDeltaCount 2 2 IN_PKTS 4 or 8 all
13 reply octets 1 + PEN 29305 23 OUT_BYTES 4 or 8 all
14 reply packets 2 + PEN 29305 24 OUT_PKTS 4 or 8 all
15 flow start 152 flowStartMilliseconds 22 FIRST_SWITCHED 8 / 4 all
16 flow end 153 flowEndMilliseconds 21 LAST_SWITCHED 8 / 4 all

Fields 7–10 come from the conntrack reply tuple, so SNAT, DNAT and combined translations all report the address and port they actually rewrote; a direction that was not translated simply repeats the original value.

Fields 13 and 14 report the flow's reply direction. Under IPFIX that is an RFC 5103 reverse Information Element, so the collector must handle enterprise-specific field specifiers. NetFlow v9 has no enterprise mechanism, so it uses the dedicated OUT_BYTES/OUT_PKTS types instead. Note these are not interchangeable: IEs 23/24 under IPFIX mean postOctetDeltaCount/postPacketDeltaCount, the forward direction as modified by a middlebox, which is a different quantity.

Counters come from the conntrack accounting counters, so they are zero on CREATE events and hold the flow's totals on DELETE.

Fields 15 and 16 come from the conntrack timestamp extension (nf_conntrack_timestamp), which records when the kernel first saw the flow and when it tore it down. The two protocols carry different quantities here:

  • IPFIX carries absolute milliseconds since the Unix epoch, which is what flowStartMilliseconds/flowEndMilliseconds are defined as. Nothing else is needed to interpret them.
  • NetFlow v9 has no absolute-time element in its field table. FIRST_SWITCHED and LAST_SWITCHED are milliseconds counted from the same zero as the header's sysUpTime, so a collector has to combine them with sysUpTime and the header's unix_secs to get a wall-clock time. Two consequences follow: the 32-bit field wraps after 49.7 days of exporter uptime, and a flow that started before the exporter did — every session that outlives a restart — has no representation at all, so it reports 0. Prefer IPFIX if flow times matter.

A CREATE event has not ended, so it reports the instant it began as both start and end. On a DELETE both are the kernel's own timestamps.

Without nf_conntrack_timestamp the kernel attaches no timestamp to a conntrack, and both fields fall back to when the exporter observed the event. That is later than the truth by however long the flow had been running, but it is a real instant rather than 1970, so a collector can still place the record.

FAQ

All my counters are zero. Conntrack accounting is off. The exporter tries to set nf_conntrack_acct=1 at startup but only warns if it cannot — look for conntrack accounting unavailable, counters will be zero in the log. The usual causes are --no-sysctl in EXPORTER_OPTS, ProtectKernelTunables=yes in a unit override, or a read-only /proc/sys. If accounting was enabled only recently, see the note in Requirements: entries created before that never get counters.

My flow timestamps are all "now", or every FIRST_SWITCHED is 0. Conntrack timestamping is off, so the records fall back to the observation time — look for conntrack timestamping unavailable in the log. Same causes as zero counters above, and the same catch: entries created before nf_conntrack_timestamp=1 took effect never get a timestamp. Under NetFlow v9 a FIRST_SWITCHED of 0 has a second, unrelated cause: the flow genuinely started before the exporter did, and v9 cannot express that. IPFIX reports the true start in both cases.

Half my flows are zero and the other half are not. That is expected. The exporter subscribes to both the conntrack CREATE and DELETE event groups. A CREATE record is emitted the instant the entry appears, when nothing has been counted yet, so its counters are zero by definition; the flow's totals arrive on the matching DELETE record.

No flows at all. The exporter refuses to start when it cannot enable nf_conntrack_events, so a running daemon has event delivery. Check that nf_conntrack is actually tracking (conntrack -E should print events), that the collector address in COLLECTOR= is reachable over UDP, and the periodic log line for send failures and netlink drops.

My collector shows the forward counters but not the reply ones. Under IPFIX the reply direction is an RFC 5103 reverse Information Element with PEN 29305, and collectors that ignore enterprise-specific field specifiers drop it. Either use a collector that handles the full profile, or export NetFlow v9, where the reply direction is carried as plain OUT_BYTES/OUT_PKTS. See Protocol and profiles.

The post-NAT destination is identical to the destination — can I drop it? On a box that only does source NAT, yes: that is what --profile nat-source is for. It drops fields 9 and 10 and leaves the rest, 68 B per IPFIX record instead of 74. Those two fields come from the conntrack reply tuple's source, and nothing rewrites the destination under plain SNAT or masquerade, so they repeat fields 4 and 6 exactly. They stop repeating as soon as anything translates the destination — a DNAT port forward, a REDIRECT to a transparent proxy, hairpin NAT, a load-balancing rule — and then they are the only place the collector learns which backend the flow reached. Check with nft list ruleset | grep -Ei 'dnat|redirect' before deciding, and note that the template is fixed at startup: add a port forward later and the translation silently stops being exported. Do not solve the duplication the other way round by keeping only the post-NAT pair — fields 3–6 are the base five-tuple that every collector decodes without any NAT support at all.

Can one instance feed two collectors? Yes — pass --collector more than once, or one comma-separated list, and every message goes to all of them. The records are encoded once and only the send() repeats, so a second collector costs one more syscall per message, not a second copy of the work. Each collector gets its own socket, so a mixed list of IPv4 and IPv6 endpoints is fine and an address that is unreachable at startup fails startup rather than being discovered later in the logs. What one instance cannot do is speak two protocols: the protocol, profile and counter width are fixed for the whole process, so IPFIX to one collector and NetFlow v9 to another still means two instances — and then each is an independent netlink multicast subscriber, so the kernel copies every conntrack event once per instance.

Notes

  • Uses only a small set of dependencies and manual encoding/parsing for hot-path efficiency.
  • Messages are capped to an MTU-safe size of 1472 bytes. NetFlow v9 FlowSets are padded to a 4-byte boundary as RFC 3954 asks.
  • One process speaks one protocol, profile and counter width, to as many collectors as --collector names. Exporting in two different protocols means two instances, and each is a separate netlink multicast subscriber, so the kernel copies every conntrack event once per instance.
  • A send failure is counted per collector, and the periodic stats line names the collectors that failed, since one unreachable destination does not stop the others.
  • Every 10 seconds, any netlink drops, UDP send failures, truncated datagrams and messages from a non-kernel sender are logged.
  • The daemon runs as root throughout; it does not yet drop privileges after opening its sockets.

Development

Building from source, packaging, benchmarks and the release process are in DEVELOPMENT.md.

License

Copyright 2026 FastNetMon LTD.

Licensed under the Apache License, Version 2.0. See LICENSE for the full text and NOTICE for the copyright notice, or https://www.apache.org/licenses/LICENSE-2.0.

About

NatStream is user space NAT events exporter in Netflow v9 and IPFIX formats

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages