Current section
Files
Jump to
Current section
Files
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