Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ruby 3.3.6
66 changes: 63 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ serde = { version = "1.0.219", default-features = false, features = ["alloc", "d
serde_json = { version = "1.0.143", default-features = false, features = ["raw_value", "std"] }
serde_yaml = { version = "0.9.34", default-features = false }
snafu = { version = "0.8.9", default-features = false, features = ["futures", "std"] }
snmp-parser = { version = "0.10.0", default-features = false }
socket2 = { version = "0.5.10", default-features = false }
tempfile = "3.23.0"
tokio = { version = "1.45.1", default-features = false }
Expand All @@ -210,6 +211,7 @@ proptest = { workspace = true, optional = true }
proptest-derive = { workspace = true, optional = true }
semver.workspace = true
snafu.workspace = true
snmp-parser = { workspace = true, optional = true }
uuid.workspace = true
vrl.workspace = true

Expand Down Expand Up @@ -614,6 +616,7 @@ sources-logs = [
"sources-file_descriptor",
"sources-redis",
"sources-socket",
"sources-snmp_trap",
"sources-splunk_hec",
"sources-stdin",
"sources-syslog",
Expand Down Expand Up @@ -688,6 +691,7 @@ sources-prometheus-pushgateway = ["sinks-prometheus", "sources-utils-http", "vec
sources-pulsar = ["dep:apache-avro", "dep:pulsar"]
sources-redis = ["dep:redis"]
sources-socket = ["sources-utils-net", "tokio-util/net"]
sources-snmp_trap = ["sources-utils-net-udp", "dep:snmp-parser"]
sources-splunk_hec = ["dep:roaring"]
sources-statsd = ["sources-utils-net", "tokio-util/net"]
sources-stdin = ["tokio-util/io"]
Expand Down
6 changes: 6 additions & 0 deletions changelog.d/snmp_trap_source.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
A new `snmp_trap` source has been added to receive SNMP v1 and v2c trap messages over UDP (issue #4567)


The source listens for SNMP traps on a configurable UDP port (typically port 162) and converts them into log events. Each trap is parsed and its fields are extracted into structured log data, including community string, version, trap type, enterprise OID, and variable bindings.

authors: bachgarash
61 changes: 61 additions & 0 deletions config/examples/snmp_trap.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# SNMP Trap Example
# ------------------------------------------------------------------------------
# A simple example that demonstrates receiving SNMP traps over UDP.
# This source supports SNMPv1 and SNMPv2c traps.
#
# To test this configuration, you can use the snmptrap command:
# snmptrap -v 2c -c public localhost:1162 '' 1.3.6.1.4.1.8072.2.3.0.1 1.3.6.1.4.1.8072.2.3.2.1 i 123456

data_dir: "/var/lib/vector"

# Receive SNMP traps on UDP port 1162 (using non-privileged port for testing)
# In production, SNMP traps are typically sent to UDP port 162
sources:
snmp_traps:
type: snmp_trap
address: "0.0.0.0:1162"
# Optional: Set receive buffer size
receive_buffer_bytes: 65536

# Process and enrich the trap data
transforms:
process_traps:
type: remap
inputs: ["snmp_traps"]
source: |
# Add a timestamp if not present
if !exists(.timestamp) {
.timestamp = now()
}

# Add a human-readable severity based on trap type
if .snmp_version == "1" {
.severity = if .generic_trap == 0 {
"info"
} else if .generic_trap == 2 || .generic_trap == 3 {
"warning"
} else if .generic_trap == 4 {
"critical"
} else {
"info"
}
} else {
.severity = "info"
}

# Output to console for inspection
sinks:
console:
type: console
inputs: ["process_traps"]
encoding:
codec: json

# Optionally, send to a file for persistent storage
file:
type: file
inputs: ["process_traps"]
path: "/var/log/vector/snmp_traps-%Y-%m-%d.log"
encoding:
codec: json

5 changes: 5 additions & 0 deletions src/internal_events/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ mod sample;
#[cfg(feature = "sinks-sematext")]
mod sematext_metrics;
mod socket;
#[cfg(feature = "sources-snmp_trap")]
mod snmp_trap;
#[cfg(any(feature = "sources-splunk_hec", feature = "sinks-splunk_hec"))]
mod splunk_hec;
#[cfg(feature = "sinks-statsd")]
Expand Down Expand Up @@ -296,3 +298,6 @@ pub use self::{
adaptive_concurrency::*, batch::*, common::*, conditions::*, encoding_transcode::*,
heartbeat::*, http::*, open::*, process::*, socket::*, tcp::*, template::*, udp::*,
};

#[cfg(feature = "sources-snmp_trap")]
pub use self::snmp_trap::*;
27 changes: 27 additions & 0 deletions src/internal_events/snmp_trap.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use metrics::counter;
use vector_lib::internal_event::InternalEvent;
use vector_lib::internal_event::{error_stage, error_type};

#[derive(Debug)]
pub struct SnmpTrapParseError {
pub error: String,
}

impl InternalEvent for SnmpTrapParseError {
fn emit(self) {
error!(
message = "Error parsing SNMP trap.",
error = %self.error,
error_type = error_type::PARSER_FAILED,
stage = error_stage::PROCESSING,
internal_log_rate_limit = true,
);
counter!(
"component_errors_total",
"error_type" => error_type::PARSER_FAILED,
"stage" => error_stage::PROCESSING,
)
.increment(1);
}
}

2 changes: 2 additions & 0 deletions src/sources/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ pub mod pulsar;
pub mod redis;
#[cfg(feature = "sources-socket")]
pub mod socket;
#[cfg(feature = "sources-snmp_trap")]
pub mod snmp_trap;
#[cfg(feature = "sources-splunk_hec")]
pub mod splunk_hec;
#[cfg(feature = "sources-static_metrics")]
Expand Down
Loading
Loading