conntrackd

conntrackd is a small, efficient conntrack event fanout logger written in
Go. It listens for Linux conntrack/netfilter connection tracking events and
optional enriches them with GEO location information before emitting structured
logs. It's intended for lightweight monitoring, auditing, and integration with
log pipelines.
Features
- Listen for conntrack events (new/updated/destroyed connections)
- Enrich IP addresses with GEO location data
- Fanout to multiple log sinks (stream, syslog,
journald,
Loki)
Getting Started
Prerequisites
- Linux (netlink/conntrack support required)
- Root privileges
- (Optional) MaxMind GeoIP2/GeoLite2 City database
Installation and Usage
Download the latest release from the
releases page.
Start the event listener and logger.
sudo conntrackd run --sink.journal.enable
For further configuration, see the command-line options below.
Filtering
conntrackd logs conntrack events to various sinks.
Protocol Support: Only TCP and UDP events are processed. All other protocols
(ICMP, IGMP, etc.) are automatically ignored and never logged, regardless of filter rules.
You can use filters to control which TCP/UDP events are logged using
CEL (Common Expression Language).
The --filter flag lets you specify filter rules:
sudo conntrackd run \
--filter 'drop destination.address == "8.8.8.8"' \
--filter 'log protocol == "TCP" && is_network(destination.address, "PUBLIC")' \
--filter "drop any" \
--sink.journal.enable
Filter Rules:
- Rules are evaluated in order (first-match wins)
- Events are logged by default when no rule matches
--filter flag can be repeated for multiple rules
- Use
drop any or drop true as a final rule to block all non-matching events from being logged
Important: Filters control which conntrack events are logged,
not network traffic. Traffic always flows normally; filters only affect logging.
Common Filter Examples:
# Don't log events to a specific IP
--filter 'drop destination.address == "8.8.8.8"'
# Log only NEW TCP connections (deny everything else)
--filter 'log event.type == "NEW" && protocol == "TCP"'
--filter "drop any"
# Don't log DNS to specific server
--filter 'drop destination.address == "10.19.80.100" && destination.port == 53'
# Don't log any traffic to private networks
--filter 'drop is_network(destination.address, "PRIVATE")'
# Log only traffic from public IPs using TCP
--filter 'log is_network(source.address, "PUBLIC") && protocol == "TCP"'
--filter "drop any"
See docs/filter.md for complete CEL documentation,
including available variables, functions, operators, and advanced examples.
Configuration
conntrackd can be configured via command-line flags, configuration files,
environment variables, or a combination of these methods.
Configuration Files
By default, conntrackd searches for a configuration file named
conntrackd.(yaml|yml|json|toml) in /etc/conntrackd directory.
You can also specify a custom config file using the --config flag:
sudo conntrackd run --config /path/to/config.yaml
Configuration files support YAML, JSON, and TOML formats.
See contrib/config.yaml for a complete example
configuration file.
Environment Variables
Configuration values can be set via environment variables with the
CONNTRACKD_ prefix:
export CONNTRACKD_LOG_LEVEL=debug
export CONNTRACKD_SINK_STREAM_WRITER=discard
sudo -E conntrackd run
Use underscores (_) to represent nested keys:
sink.stream.writer → CONNTRACKD_SINK_STREAM_WRITER
Priority Order
Configuration values are applied in the following order
(later overrides earlier):
- Default values
- Configuration file
- Environment variables
- Command-line flags
Note: Command-line flags always have the highest priority.
Configuration Flags
| Flag |
Description |
Default |
--config |
Path to configuration file |
|
--filter |
Filter rule in DSL format (repeatable) |
|
--geoip.database |
Path to GeoIP database |
|
--log.level |
Log level (debug, info, warn, error) |
info |
--sink.journal.enable |
Enable journald sink |
|
--sink.syslog.enable |
Enable syslog sink |
|
--sink.loki.enable |
Enable Loki sink |
|
--sink.stream.enable |
Enable stream sink |
|
--sink.syslog.address |
Syslog address |
udp://localhost:514 |
--sink.loki.address |
Loki address |
http://localhost:3100 |
--sink.loki.labels |
Loki labels (comma-separated key=value pairs) |
|
--sink.stream.writer |
Stream writer (stdout, stderr, discard) |
stdout |
--profiler.enable |
Enable continous profiling |
|
--profiler.address |
Pyroscope server address |
http://localhost:4040 |
conntrackd emits structured logs for each conntrack event. A typical log entry
includes:
- type (connection event type)
- flow (connection flow identifier)
- src_addr, dst_addr (IP addresses)
- src_port, dst_port (port numbers)
- prot (transport protocol)
Additionally TCP field:
- state (TCP connection state)
GEO location fields for source and destination if applicable with prefixes
src_ and dst_:
- city (city name)
- country (country name)
- lat (latitude)
- lon (longitude)
Example log entry recorded by sink `syslog`
{
"event": {
"dst_port": 443,
"dst_addr": "2600:1901:0:b3ea::",
"flow": 221193769,
"prot": "TCP",
"src_port": 41348,
"src_addr": "2003:cf:1716:7b64:da80:83ff:fecd:da51",
"tcp_state": "LAST_ACK",
"type": "UPDATE"
},
"level": "INFO",
"logger.name": "samber/slog-syslog",
"logger.version": "v2.5.2",
"message": "UPDATE TCP connection from [2003:cf:1716:7b64:da80:83ff:fecd...",
"timestamp": "2025-11-15T09:55:25.647544937Z"
}
Example log entry recorded by sink `journal`
{
"__CURSOR" : "s=b3c7821dbfce47a59b06797aea9028ca;i=6772d3;b=100da27bd...",
"_CAP_EFFECTIVE" : "1ffffffffff",
"EVENT_SRC_PORT" : "39790",
"_SOURCE_REALTIME_TIMESTAMP" : "1763200187611509",
"_SYSTEMD_CGROUP" : "/user.slice/user-1000.slice/session-1.scope",
"_SYSTEMD_OWNER_UID" : "1000",
"_SYSTEMD_SESSION" : "1",
"_EXE" : "/home/tschaefer/.env/bin/conntrackd",
"_HOSTNAME" : "bullseye",
"_GID" : "0",
"PRIORITY" : "6",
"_SYSTEMD_UNIT" : "session-1.scope",
"EVENT_DST_PORT" : "443",
"SLOG_LOGGER" : "tschaefer/slog-journal:v1.0.0",
"_TRANSPORT" : "journal",
"EVENT_SRC_ADDR" : "2003:cf:1716:7b64:da80:83ff:fecd:da51",
"_COMM" : "conntrackd",
"__MONOTONIC_TIMESTAMP" : "352829248481",
"EVENT_TCP_STATE" : "LAST_ACK",
"_MACHINE_ID" : "75b649379b874beea04d95463e59c3a1",
"_SYSTEMD_SLICE" : "user-1000.slice",
"_SYSTEMD_USER_SLICE" : "-.slice",
"__SEQNUM_ID" : "b3c7821dbfce47a59b06797aea9028ca",
"__REALTIME_TIMESTAMP" : "1763200187611631",
"__SEQNUM" : "6779603",
"_SYSTEMD_INVOCATION_ID" : "021760b3373342b98aaeabf9d12d8d74",
"EVENT_FLOW" : "3478798157",
"_PID" : "3794900",
"_CMDLINE" : "conntrackd run --service.log.level debug --service.log....",
"EVENT_PROT" : "TCP",
"_AUDIT_SESSION" : "1",
"_BOOT_ID" : "100da27bd8b94096b5c80cdac34d6063",
"_RUNTIME_SCOPE" : "system",
"_SELINUX_CONTEXT" : "unconfined\n",
"EVENT_DST_ADDR" : "2600:1901:0:b3ea::",
"_AUDIT_LOGINUID" : "1000",
"_UID" : "0",
"EVENT_TYPE" : "UPDATE",
"MESSAGE" : "UPDATE TCP connection from [2003:cf:1716:7b64:da80:83ff:fe..."
}
Example log entry recorded by sink `loki`
Loki allows maximum 15 labels per log entry. Therefore, location fields are
attached as structured metadata to each log line.
{
"stream": {
"detected_level": "INFO",
"dst_addr": "2a01:4f8:160:5372::2",
"dst_addr_extracted": "2a01:4f8:160:5372::2",
"dst_city": "Falkenstein",
"dst_country": "Germany",
"dst_lat": "50.4777",
"dst_lon": "12.3649",
"dst_port": "443",
"dst_port_extracted": "443",
"flow": "4198226788",
"flow_extracted": "4198226788",
"host": "bullseye.u.coresec.zone",
"level": "INFO",
"prot": "TCP",
"prot_extracted": "TCP",
"service_name": "conntrackd",
"src_addr": "2003:cf:1716:7b64:da80:83ff:fecd:da51",
"src_addr_extracted": "2003:cf:1716:7b64:da80:83ff:fecd:da51",
"src_city": "Garmisch-Partenkirchen",
"src_country": "Germany",
"src_lat": "47.4906",
"src_lon": "11.1026",
"src_port": "56110",
"src_port_extracted": "56110",
"tcp_state": "SYN_SENT",
"tcp_state_extracted": "SYN_SENT",
"type": "NEW",
"type_extracted": "NEW"
},
"values": [
[
"1764163739570953291",
"NEW TCP connection from [2003:cf:1716:7b64:da80:83ff:fecd:da51]:56110..."
]
]
}
Example log entry recorded by sink `stream`
{
"time": "2025-11-25T12:35:11.082791653+01:00",
"level": "INFO",
"msg": "NEW TCP connection from [2003:cf:1716:7b64:da80:83ff:fecd:da51]:4...",
"type": "NEW",
"flow": 4000057915,
"prot": "TCP",
"src_addr": "2003:cf:1716:7b64:da80:83ff:fecd:da51",
"dst_addr": "2a01:4f8:160:5372::2",
"src_port": 41756,
"dst_port": 443,
"tcp_state": "SYN_SENT",
"src_city": "Garmisch-Partenkirchen",
"src_country": "Germany",
"src_lat": 47.4906,
"src_lon": 11.1026,
"dst_city": "Falkenstein",
"dst_country": "Germany",
"dst_lat": 50.4777,
"dst_lon": 12.3649
}
Security Notes
- Observing conntrack/netlink events typically requires elevated privileges.
- Keep GeoIP databases updated.
- Be careful with log storage; connection events may contain sensitive network
metadata.
Contributing
Contributions are welcome! Please fork the repository and submit a pull request.
For major changes, open an issue first to discuss what you would like to change.
Ensure that your code adheres to the existing style and includes appropriate
tests.
License
This project is licensed under the MIT License.