Packages

A comprehensive SNMP toolkit for Elixir featuring a unified API, pure Elixir implementation, and powerful device simulation. Perfect for network monitoring, testing, and development with support for SNMP operations, MIB management, and realistic device simulation.

Current section

Files

Jump to
snmpkit lib snmpkit snmp_sim behavior_config.ex
Raw

lib/snmpkit/snmp_sim/behavior_config.ex

defmodule SnmpKit.SnmpSim.BehaviorConfig do
@moduledoc """
Configuration system for enhancing walk files with realistic behaviors.
Provides easy-to-use behavior presets and customization options.
"""
@doc """
Apply behavior configurations to a loaded profile.
## Examples
# Apply basic realistic behaviors
enhanced_profile = SnmpKit.SnmpSim.BehaviorConfig.apply_behaviors(profile, [
:realistic_counters,
:daily_patterns,
{:custom_utilization, peak_hours: {9, 17}}
])
"""
def apply_behaviors(profile, behavior_configs) do
# Normalize behavior configurations first
normalized_configs = Enum.map(behavior_configs, &normalize_behavior_spec/1)
Enum.reduce(normalized_configs, profile, fn config, acc_profile ->
apply_single_behavior(acc_profile, config)
end)
end
@doc """
Get predefined behavior configuration sets.
## Examples
# Cable modem realistic simulation
behaviors = SnmpKit.SnmpSim.BehaviorConfig.get_preset(:cable_modem_realistic)
# High traffic simulation
behaviors = SnmpKit.SnmpSim.BehaviorConfig.get_preset(:high_traffic_simulation)
"""
def get_preset(preset_name) do
case preset_name do
:cable_modem_realistic ->
cable_modem_realistic_preset()
:cmts_realistic ->
cmts_realistic_preset()
:switch_realistic ->
switch_realistic_preset()
:high_traffic_simulation ->
high_traffic_simulation_preset()
:low_signal_quality ->
low_signal_quality_preset()
:network_congestion ->
network_congestion_preset()
:maintenance_mode ->
maintenance_mode_preset()
:development_testing ->
development_testing_preset()
_ ->
{:error, :unknown_preset}
end
end
@doc """
Create custom behavior configuration.
## Examples
config = SnmpKit.SnmpSim.BehaviorConfig.create_custom([
{:traffic_counters, %{
rate_multiplier: 1.5,
daily_pattern: true,
burst_probability: 0.2
}},
{:signal_quality, %{
base_snr: 25,
weather_impact: true,
degradation_rate: 0.1
}}
])
"""
def create_custom(behavior_specs) do
behavior_specs
|> Enum.map(&normalize_behavior_spec/1)
|> validate_behavior_config()
end
@doc """
Get available behavior types and their configurations.
"""
def list_available_behaviors do
%{
counter_behaviors: [
:realistic_counters,
:high_traffic_counters,
:bursty_counters,
:steady_counters
],
gauge_behaviors: [
:realistic_gauges,
:stable_gauges,
:volatile_gauges,
:degrading_gauges
],
time_patterns: [
:daily_patterns,
:weekly_patterns,
:seasonal_patterns,
:custom_schedule
],
signal_quality: [
:docsis_signal_simulation,
:weather_correlation,
:distance_based_degradation
],
error_simulation: [
:realistic_errors,
:burst_errors,
:correlated_errors
]
}
end
# Preset Definitions
defp cable_modem_realistic_preset do
[
# Traffic counters with realistic patterns
{:increment_counters,
%{
oid_patterns: ["ifInOctets", "ifOutOctets"],
rate_range: {1_000, 50_000_000},
daily_variation: true,
weekend_factor: 0.7,
burst_probability: 0.1
}},
# Packet counters correlated with traffic
{:increment_counters,
%{
oid_patterns: ["ifInUcastPkts", "ifOutUcastPkts"],
rate_range: {10, 500_000},
correlation_with: "octets_counters",
packet_size_avg: 1200
}},
# Error counters - very low but realistic
{:increment_counters,
%{
oid_patterns: ["ifInErrors", "ifOutErrors"],
rate_range: {0, 10},
correlation_with: "utilization",
error_burst_probability: 0.02
}},
# DOCSIS signal quality gauges
{:vary_gauges,
%{
oid_patterns: ["docsIfSigQSignalNoise"],
range: {20, 35},
pattern: :inverse_utilization,
weather_correlation: true,
noise_factor: 1.0
}},
# Power level gauges
{:vary_gauges,
%{
oid_patterns: ["docsIfDownChannelPower"],
range: {-10, 10},
pattern: :environmental,
seasonal_variation: true
}},
# System uptime
{:increment_uptime,
%{
# Very rare reboots
reset_probability: 0.0001,
# Standard TimeTicks
increment_rate: 100
}},
# Interface utilization calculation
{:calculate_utilization,
%{
based_on: ["ifInOctets", "ifOutOctets"],
interface_speed: "ifSpeed",
smoothing_factor: 0.9
}}
]
end
defp cmts_realistic_preset do
[
# High-capacity traffic handling
{:increment_counters,
%{
oid_patterns: ["ifInOctets", "ifOutOctets"],
rate_range: {100_000, 1_000_000_000},
# Aggregates many modems
aggregation_factor: 100,
daily_variation: true
}},
# Modem management counters
{:increment_counters,
%{
oid_patterns: ["docsIfCmtsServiceNewCmStatusTxFails"],
rate_range: {0, 50},
pattern: :sporadic
}},
# System resource gauges
{:vary_gauges,
%{
oid_patterns: ["hrProcessorLoad"],
range: {10, 80},
pattern: :correlated_with_traffic,
spike_probability: 0.05
}},
# Temperature monitoring
{:vary_gauges,
%{
oid_patterns: ["entSensorValue"],
range: {30, 75},
pattern: :temperature,
load_correlation: true
}}
]
end
defp switch_realistic_preset do
[
# Per-port traffic counters
{:increment_counters,
%{
oid_patterns: ["ifInOctets", "ifOutOctets"],
rate_range: {100, 125_000_000},
per_interface: true,
trunk_multiplier: 10
}},
# Spanning tree and forwarding
{:increment_counters,
%{
oid_patterns: ["dot1dTpLearnedEntryDiscards"],
rate_range: {0, 100},
pattern: :learning_phase
}},
# VLAN statistics
{:increment_counters,
%{
oid_patterns: ["dot1qVlanStatisticsInPkts"],
rate_range: {10, 100_000},
per_vlan: true
}},
# CPU and memory utilization
{:vary_gauges,
%{
oid_patterns: ["cpmCPUTotalPhysicalIndex"],
range: {5, 60},
pattern: :processing_load
}}
]
end
defp high_traffic_simulation_preset do
[
{:increment_counters,
%{
rate_multiplier: 5.0,
burst_probability: 0.3,
sustained_high_load: true
}},
{:vary_gauges,
%{
utilization_bias: 0.8,
volatility: 0.3
}},
{:realistic_errors,
%{
error_rate_multiplier: 2.0,
congestion_errors: true
}}
]
end
defp low_signal_quality_preset do
[
{:vary_gauges,
%{
oid_patterns: ["docsIfSigQSignalNoise"],
# Poor SNR range
range: {8, 20},
degradation_trend: true,
weather_impact_multiplier: 2.0
}},
{:increment_counters,
%{
oid_patterns: ["ifInErrors", "ifOutErrors"],
rate_multiplier: 5.0,
correlation_with: "signal_quality"
}},
{:vary_gauges,
%{
oid_patterns: ["docsIfDownChannelPower"],
# Weak signal power
range: {-15, -5},
instability: true
}}
]
end
defp network_congestion_preset do
[
{:increment_counters,
%{
rate_multiplier: 1.5,
sustained_load: 0.9,
burst_frequency: 0.4
}},
{:increment_counters,
%{
oid_patterns: ["ifInDiscards", "ifOutDiscards"],
rate_range: {10, 1000},
congestion_correlation: true
}},
{:vary_gauges,
%{
oid_patterns: ["ifOutQLen"],
range: {10, 100},
pattern: :queue_buildup
}}
]
end
defp maintenance_mode_preset do
[
{:static_values,
%{
oid_patterns: ["ifAdminStatus"],
# Interface down
value: 2
}},
{:increment_counters,
%{
# Very low traffic
rate_multiplier: 0.1,
maintenance_pattern: true
}},
{:vary_gauges,
%{
# Very stable during maintenance
stability_factor: 0.95,
reduced_variation: true
}}
]
end
defp development_testing_preset do
[
{:predictable_patterns,
%{
counter_increment: 1000,
gauge_pattern: :sine_wave,
# 5 minutes
cycle_duration: 300
}},
{:test_scenarios,
%{
error_injection: :scheduled,
value_validation: true,
debugging_enabled: true
}}
]
end
# Behavior Application Functions
defp apply_single_behavior(profile, behavior_config) do
case behavior_config do
:realistic_counters ->
apply_realistic_counters(profile)
{:realistic_counters, _config} ->
apply_realistic_counters(profile)
:daily_patterns ->
apply_daily_patterns(profile)
{:daily_patterns, _config} ->
apply_daily_patterns(profile)
:weekly_patterns ->
apply_weekly_patterns(profile)
{:weekly_patterns, _config} ->
apply_weekly_patterns(profile)
{:increment_counters, config} ->
apply_increment_counters(profile, config)
{:vary_gauges, config} ->
apply_vary_gauges(profile, config)
{:realistic_errors, config} ->
apply_realistic_errors(profile, config)
{:custom_utilization, config} ->
apply_custom_utilization(profile, config)
_ ->
# Unknown behavior config, return profile unchanged
profile
end
end
defp apply_realistic_counters(profile) do
# Enhance counter OIDs with realistic increment behaviors
enhanced_oids =
profile.oid_map
|> Enum.map(fn {oid, value_info} ->
if is_counter_type?(value_info.type) do
enhanced_info =
Map.put(value_info, :behavior, determine_counter_behavior(oid, value_info))
{oid, enhanced_info}
else
{oid, value_info}
end
end)
|> Map.new()
%{profile | oid_map: enhanced_oids}
end
defp apply_daily_patterns(profile) do
# Add daily pattern behavior to all appropriate OIDs
enhanced_oids =
profile.oid_map
|> Enum.map(fn {oid, value_info} ->
if supports_time_patterns?(value_info.type) do
current_behavior = Map.get(value_info, :behavior, {:static_value, %{}})
enhanced_behavior = add_daily_pattern(current_behavior)
enhanced_info = Map.put(value_info, :behavior, enhanced_behavior)
{oid, enhanced_info}
else
{oid, value_info}
end
end)
|> Map.new()
%{profile | oid_map: enhanced_oids}
end
defp apply_weekly_patterns(profile) do
# Similar to daily patterns but for weekly cycles
enhanced_oids =
profile.oid_map
|> Enum.map(fn {oid, value_info} ->
if supports_time_patterns?(value_info.type) do
current_behavior = Map.get(value_info, :behavior, {:static_value, %{}})
enhanced_behavior = add_weekly_pattern(current_behavior)
enhanced_info = Map.put(value_info, :behavior, enhanced_behavior)
{oid, enhanced_info}
else
{oid, value_info}
end
end)
|> Map.new()
%{profile | oid_map: enhanced_oids}
end
defp apply_increment_counters(profile, config) do
oid_patterns = Map.get(config, :oid_patterns, [])
enhanced_oids =
profile.oid_map
|> Enum.map(fn {oid, value_info} ->
if matches_oid_patterns?(oid, oid_patterns) and is_counter_type?(value_info.type) do
behavior_config = create_counter_behavior_config(config)
enhanced_info = Map.put(value_info, :behavior, {:traffic_counter, behavior_config})
{oid, enhanced_info}
else
{oid, value_info}
end
end)
|> Map.new()
%{profile | oid_map: enhanced_oids}
end
defp apply_vary_gauges(profile, config) do
oid_patterns = Map.get(config, :oid_patterns, [])
enhanced_oids =
profile.oid_map
|> Enum.map(fn {oid, value_info} ->
if matches_oid_patterns?(oid, oid_patterns) and is_gauge_type?(value_info.type) do
behavior_config = create_gauge_behavior_config(config)
enhanced_info = Map.put(value_info, :behavior, {:utilization_gauge, behavior_config})
{oid, enhanced_info}
else
{oid, value_info}
end
end)
|> Map.new()
%{profile | oid_map: enhanced_oids}
end
defp apply_realistic_errors(profile, config) do
# Apply error behaviors to error counter OIDs
enhanced_oids =
profile.oid_map
|> Enum.map(fn {oid, value_info} ->
if is_error_counter?(oid, value_info) do
behavior_config = create_error_behavior_config(config)
enhanced_info = Map.put(value_info, :behavior, {:error_counter, behavior_config})
{oid, enhanced_info}
else
{oid, value_info}
end
end)
|> Map.new()
%{profile | oid_map: enhanced_oids}
end
defp apply_custom_utilization(profile, config) do
# Apply custom utilization patterns
enhanced_oids =
profile.oid_map
|> Enum.map(fn {oid, value_info} ->
if is_utilization_related?(oid, value_info) do
behavior_config = Map.merge(%{behavior_type: :custom_utilization}, config)
enhanced_info = Map.put(value_info, :behavior, {:utilization_gauge, behavior_config})
{oid, enhanced_info}
else
{oid, value_info}
end
end)
|> Map.new()
%{profile | oid_map: enhanced_oids}
end
# Helper Functions
defp normalize_behavior_spec({behavior_type, config}) when is_map(config) do
{behavior_type, config}
end
defp normalize_behavior_spec({behavior_type, config}) when is_list(config) do
# Convert keyword list to map
{behavior_type, Map.new(config)}
end
defp normalize_behavior_spec(behavior_atom) when is_atom(behavior_atom) do
{behavior_atom, %{}}
end
defp normalize_behavior_spec(invalid_spec) do
# Return the invalid spec as-is so it can be caught by validation
invalid_spec
end
defp validate_behavior_config(behavior_specs) do
# Validate that all behavior specifications are valid
case Enum.find(behavior_specs, &(!valid_behavior_spec?(&1))) do
nil -> {:ok, behavior_specs}
invalid_spec -> {:error, {:invalid_behavior_spec, invalid_spec}}
end
end
defp valid_behavior_spec?({behavior_type, config})
when is_atom(behavior_type) and is_map(config) do
behavior_type in [
:increment_counters,
:vary_gauges,
:realistic_errors,
:custom_utilization,
:daily_patterns,
:weekly_patterns,
:seasonal_patterns,
:traffic_counters,
:realistic_counters,
:signal_quality
]
end
defp valid_behavior_spec?(_), do: false
defp is_counter_type?(type) do
String.downcase(type) in ["counter32", "counter64"]
end
defp is_gauge_type?(type) do
String.downcase(type) in ["gauge32", "gauge"]
end
defp is_error_counter?(oid, _value_info) do
# First check the OID string directly
oid_lower = String.downcase(oid)
if String.contains?(oid_lower, "error") or
String.contains?(oid_lower, "discard") or
String.contains?(oid_lower, "drop") do
true
else
# Also check the OID name mapping
oid_name = get_oid_name(oid)
oid_name &&
(String.contains?(String.downcase(oid_name), "error") or
String.contains?(String.downcase(oid_name), "discard") or
String.contains?(String.downcase(oid_name), "drop"))
end
end
defp is_utilization_related?(oid, _value_info) do
oid_lower = String.downcase(oid)
String.contains?(oid_lower, "util") or
String.contains?(oid_lower, "load") or
String.contains?(oid_lower, "cpu")
end
defp supports_time_patterns?(type) do
is_counter_type?(type) or is_gauge_type?(type)
end
defp matches_oid_patterns?(oid, patterns) do
Enum.any?(patterns, fn pattern ->
oid_matches_pattern?(oid, pattern)
end)
end
# Check if an OID matches a pattern (either by containing the pattern string or by OID name mapping)
defp oid_matches_pattern?(oid, pattern) do
# First try direct string matching (for cases where OID contains the pattern)
if String.contains?(String.downcase(oid), String.downcase(pattern)) do
true
else
# Try OID name mapping for common SNMP objects
oid_name = get_oid_name(oid)
oid_name && String.contains?(String.downcase(oid_name), String.downcase(pattern))
end
end
# Basic mapping of common SNMP interface OIDs to their names
defp get_oid_name(oid) do
cond do
# Interface statistics (IF-MIB)
String.starts_with?(oid, "1.3.6.1.2.1.2.2.1.10.") -> "ifInOctets"
String.starts_with?(oid, "1.3.6.1.2.1.2.2.1.16.") -> "ifOutOctets"
String.starts_with?(oid, "1.3.6.1.2.1.2.2.1.11.") -> "ifInUcastPkts"
String.starts_with?(oid, "1.3.6.1.2.1.2.2.1.17.") -> "ifOutUcastPkts"
String.starts_with?(oid, "1.3.6.1.2.1.2.2.1.14.") -> "ifInErrors"
String.starts_with?(oid, "1.3.6.1.2.1.2.2.1.20.") -> "ifOutErrors"
String.starts_with?(oid, "1.3.6.1.2.1.2.2.1.13.") -> "ifInDiscards"
String.starts_with?(oid, "1.3.6.1.2.1.2.2.1.19.") -> "ifOutDiscards"
String.starts_with?(oid, "1.3.6.1.2.1.2.2.1.5.") -> "ifSpeed"
String.starts_with?(oid, "1.3.6.1.2.1.2.2.1.8.") -> "ifOperStatus"
String.starts_with?(oid, "1.3.6.1.2.1.2.2.1.7.") -> "ifAdminStatus"
# System group (SNMPv2-MIB)
String.starts_with?(oid, "1.3.6.1.2.1.1.1.") -> "sysDescr"
String.starts_with?(oid, "1.3.6.1.2.1.1.3.") -> "sysUpTime"
String.starts_with?(oid, "1.3.6.1.2.1.1.4.") -> "sysContact"
String.starts_with?(oid, "1.3.6.1.2.1.1.5.") -> "sysName"
String.starts_with?(oid, "1.3.6.1.2.1.1.6.") -> "sysLocation"
# DOCSIS-specific OIDs (commonly used in cable modems)
String.starts_with?(oid, "1.3.6.1.2.1.10.127.1.1.1.1.6.") -> "docsIfSigQSignalNoise"
String.starts_with?(oid, "1.3.6.1.2.1.10.127.1.1.1.1.2.") -> "docsIfDownChannelPower"
# CPU and memory (HOST-RESOURCES-MIB)
String.starts_with?(oid, "1.3.6.1.2.1.25.3.3.1.2.") -> "hrProcessorLoad"
String.starts_with?(oid, "1.3.6.1.2.1.25.2.2.1.") -> "hrStorageUsed"
true -> nil
end
end
defp determine_counter_behavior(oid, _value_info) do
cond do
String.contains?(String.downcase(oid), "octets") ->
{:traffic_counter, %{rate_range: {1_000, 50_000_000}}}
String.contains?(String.downcase(oid), "packets") ->
{:packet_counter, %{rate_range: {10, 500_000}}}
String.contains?(String.downcase(oid), "error") ->
{:error_counter, %{rate_range: {0, 10}}}
true ->
{:generic_counter, %{rate_range: {100, 10_000}}}
end
end
defp add_daily_pattern({behavior_type, config}) do
enhanced_config = Map.put(config, :daily_pattern, true)
{behavior_type, enhanced_config}
end
defp add_weekly_pattern({behavior_type, config}) do
enhanced_config = Map.put(config, :weekly_pattern, true)
{behavior_type, enhanced_config}
end
defp create_counter_behavior_config(config) do
%{
rate_range: Map.get(config, :rate_range, {1_000, 10_000_000}),
daily_variation: Map.get(config, :daily_variation, true),
burst_probability: Map.get(config, :burst_probability, 0.1),
correlation_with: Map.get(config, :correlation_with),
rate_multiplier: Map.get(config, :rate_multiplier, 1.0)
}
end
defp create_gauge_behavior_config(config) do
%{
range: Map.get(config, :range, {0, 100}),
pattern: Map.get(config, :pattern, :daily_variation),
weather_correlation: Map.get(config, :weather_correlation, false),
utilization_bias: Map.get(config, :utilization_bias, 0.5),
volatility: Map.get(config, :volatility, 0.1)
}
end
defp create_error_behavior_config(config) do
%{
rate_range: Map.get(config, :rate_range, {0, 10}),
error_rate_multiplier: Map.get(config, :error_rate_multiplier, 1.0),
burst_probability: Map.get(config, :burst_probability, 0.02),
correlation_with: Map.get(config, :correlation_with, "utilization")
}
end
end