Search documentation of snmpkit

# SnmpKit Interactive Tour 🚀

## Section

Welcome to SnmpKit v0.3.5!

This interactive Livebook will take you on a comprehensive tour of SnmpKit's new unified API. We'll start by creating a simulated SNMP device and then demonstrate all the powerful features against our own simulation - no external network required!

What you'll learn:

- 🎯 Unified API - Clean, context-based modules
- 📡 SNMP Operations - get, walk, bulk, multi-target
- 📚 MIB Management - resolution, compilation, tree navigation
- 🧪 Device Simulation - realistic testing environments
- ⚡ Advanced Features - streaming, performance, analytics

Let's get started! 🚀

## Setup

First, let's install SnmpKit and configure our environment:

    Mix.install([
      {:snmpkit, "~> 0.3.5"}
    ])

    # Configure logging for our tour
    Logger.configure(level: :info)

    # Import the unified API modules for convenience
    alias SnmpKit.{SNMP, MIB, Sim}

    IO.puts("🎉 SnmpKit v0.3.5 loaded successfully!")
    IO.puts("📚 Ready to explore the unified API!")

## Chapter 1: Start Our Simulated Network 🖥️

Before we can demonstrate SNMP operations, let's create our own simulated network! This is one of SnmpKit's most powerful features - realistic device simulation for testing and development.

### Create a Cable Modem Simulation

    # Create a realistic DOCSIS cable modem with essential OIDs
    cable_modem_oids = %{
      # System Group
      "1.3.6.1.2.1.1.1.0" => "ARRIS SURFboard SB8200 DOCSIS 3.1 Cable Modem",
      "1.3.6.1.2.1.1.2.0" => "1.3.6.1.4.1.4115.1.20.1.1.2.25",
      "1.3.6.1.2.1.1.3.0" => %{type: "TimeTicks", value: 0},
      "1.3.6.1.2.1.1.4.0" => "admin@example.com",
      "1.3.6.1.2.1.1.5.0" => "cm-001",
      "1.3.6.1.2.1.1.6.0" => "Home Network",

      # Interface Group
      "1.3.6.1.2.1.2.1.0" => 2,
      "1.3.6.1.2.1.2.2.1.1.1" => 1,
      "1.3.6.1.2.1.2.2.1.1.2" => 2,
      "1.3.6.1.2.1.2.2.1.2.1" => "cable-downstream0",
      "1.3.6.1.2.1.2.2.1.2.2" => "cable-upstream0",
      "1.3.6.1.2.1.2.2.1.3.1" => 127,  # docsCableMaclayer
      "1.3.6.1.2.1.2.2.1.3.2" => 127,

      # DOCSIS Specific OIDs
      "1.3.6.1.2.1.10.127.1.1.1.1.3.2" => %{type: "INTEGER", value: 3},  # docsIfCmtsUpChannelId
      "1.3.6.1.2.1.10.127.1.1.1.1.6.2" => %{type: "INTEGER", value: 6400000},  # docsIfCmtsUpChannelFrequency
      "1.3.6.1.2.1.10.127.1.2.2.1.1.2" => %{type: "INTEGER", value: 1},  # docsIfCmStatusValue
      "1.3.6.1.2.1.10.127.1.2.2.1.12.2" => %{type: "Counter32", value: 1000},  # docsIfCmStatusUnerroreds

      # Cable Modem specific counters
      "1.3.6.1.2.1.2.2.1.10.1" => %{type: "Counter32", value: 1500000},  # ifInOctets
      "1.3.6.1.2.1.2.2.1.16.1" => %{type: "Counter32", value: 900000},   # ifOutOctets
      "1.3.6.1.2.1.2.2.1.11.1" => %{type: "Counter32", value: 12000},    # ifInUcastPkts
      "1.3.6.1.2.1.2.2.1.17.1" => %{type: "Counter32", value: 8000},     # ifOutUcastPkts
    }

    # Create the cable modem profile
    {:ok, cable_modem_profile} = SnmpKit.SnmpSim.ProfileLoader.load_profile(
      :cable_modem,
      {:manual, cable_modem_oids},
      behaviors: [:counter_increment, :time_based_changes]
    )

    # Start our simulated cable modem on port 1161
    {:ok, cable_modem} = Sim.start_device(cable_modem_profile, [
      port: 1161,
      community: "public"
    ])

    # Define our target for easy reference
    cable_modem_target = "127.0.0.1:1161"

    IO.puts("✅ Cable modem simulation started on #{cable_modem_target}")
    IO.puts("🎯 Ready for SNMP operations!")

    # Make target available to other cells
    cable_modem_target

### Create a Router Simulation

Let's also create a router simulation to demonstrate multi-target operations:

    # Create a realistic enterprise router with essential OIDs
    router_oids = %{
      # System Group
      "1.3.6.1.2.1.1.1.0" => "Cisco IOS Software, C2900 Software, Version 15.1(4)M12a",
      "1.3.6.1.2.1.1.2.0" => "1.3.6.1.4.1.9.1.576",
      "1.3.6.1.2.1.1.3.0" => %{type: "TimeTicks", value: 0},
      "1.3.6.1.2.1.1.4.0" => "IT Department <it@company.com>",
      "1.3.6.1.2.1.1.5.0" => "router-001",
      "1.3.6.1.2.1.1.6.0" => "Main Office - Server Room",

      # Interface Group (more interfaces for a router)
      "1.3.6.1.2.1.2.1.0" => 5,  # ifNumber (more interfaces)

      # FastEthernet0/0 (WAN)
      "1.3.6.1.2.1.2.2.1.1.1" => 1,
      "1.3.6.1.2.1.2.2.1.2.1" => "FastEthernet0/0",
      "1.3.6.1.2.1.2.2.1.3.1" => 6,  # ethernetCsmacd
      "1.3.6.1.2.1.2.2.1.5.1" => %{type: "Gauge32", value: 100000000},  # 100Mbps
      "1.3.6.1.2.1.2.2.1.8.1" => 1,  # up

      # FastEthernet0/1 (LAN)
      "1.3.6.1.2.1.2.2.1.1.2" => 2,
      "1.3.6.1.2.1.2.2.1.2.2" => "FastEthernet0/1",
      "1.3.6.1.2.1.2.2.1.3.2" => 6,
      "1.3.6.1.2.1.2.2.1.5.2" => %{type: "Gauge32", value: 100000000},
      "1.3.6.1.2.1.2.2.1.8.2" => 1,

      # Serial0/0/0 (WAN backup)
      "1.3.6.1.2.1.2.2.1.1.3" => 3,
      "1.3.6.1.2.1.2.2.1.2.3" => "Serial0/0/0",
      "1.3.6.1.2.1.2.2.1.3.3" => 22,  # propPointToPointSerial
      "1.3.6.1.2.1.2.2.1.5.3" => %{type: "Gauge32", value: 1544000},  # T1 speed
      "1.3.6.1.2.1.2.2.1.8.3" => 2,  # down (backup interface)

      # Loopback0
      "1.3.6.1.2.1.2.2.1.1.4" => 4,
      "1.3.6.1.2.1.2.2.1.2.4" => "Loopback0",
      "1.3.6.1.2.1.2.2.1.3.4" => 24,  # softwareLoopback
      "1.3.6.1.2.1.2.2.1.8.4" => 1,

      # Traffic counters for active interfaces
      "1.3.6.1.2.1.2.2.1.10.1" => %{type: "Counter32", value: 0},  # ifInOctets WAN
      "1.3.6.1.2.1.2.2.1.16.1" => %{type: "Counter32", value: 0},  # ifOutOctets WAN
      "1.3.6.1.2.1.2.2.1.10.2" => %{type: "Counter32", value: 0},  # ifInOctets LAN
      "1.3.6.1.2.1.2.2.1.16.2" => %{type: "Counter32", value: 0},  # ifOutOctets LAN

      # IP routing info
      "1.3.6.1.2.1.4.1.0" => 1,  # ipForwarding (enabled)
      "1.3.6.1.2.1.4.2.0" => 30, # ipDefaultTTL

      # SNMP community info
      "1.3.6.1.2.1.11.1.0" => %{type: "Counter32", value: 0},  # snmpInPkts
      "1.3.6.1.2.1.11.2.0" => %{type: "Counter32", value: 0},  # snmpOutPkts
    }

    # Create the router profile
    {:ok, router_profile} = SnmpKit.SnmpSim.ProfileLoader.load_profile(
      :router,
      {:manual, router_oids},
      behaviors: [:counter_increment, :time_based_changes]
    )

    {:ok, router} = Sim.start_device(router_profile, [
      port: 1162,
      community: "public"
    ])

    router_target = "127.0.0.1:1162"

    IO.puts("✅ Router simulation started on #{router_target}")
    IO.puts("🌐 Now we have a complete simulated network!")
    IO.puts("📊 Loaded #{map_size(router_oids)} router OIDs")

    # Make targets available to other cells
    {cable_modem_target, router_target}

### Quick Connectivity Test

Let's verify our simulated devices are responding:

    # Get targets from previous cells
    cable_modem_target = "127.0.0.1:1161"
    router_target = "127.0.0.1:1162"

    # Test cable modem
    case SNMP.get(cable_modem_target, "sysDescr.0") do
      {:ok, %{formatted: description}} ->
        IO.puts("📡 Cable Modem: #{description}")
      {:error, reason} ->
        IO.puts("❌ Cable modem error: #{inspect(reason)}")
    end

    # Test router
    case SNMP.get(router_target, "sysDescr.0") do
      {:ok, %{formatted: description}} ->
        IO.puts("🔀 Router: #{description}")
      {:error, reason} ->
        IO.puts("❌ Router error: #{inspect(reason)}")
    end

    IO.puts("\n🎉 Both devices are responding! Let's explore the API...")

## Chapter 2: SnmpKit.SNMP - Protocol Operations 📡

Now let's explore the comprehensive SNMP operations available through `SnmpKit.SNMP`. All operations will work against our simulated devices!

### Basic GET Operations

    # Set targets for this cell
    cable_modem_target = "127.0.0.1:1161"

    IO.puts("=== Basic SNMP GET Operations ===\n")

    # Standard GET operation
    {:ok, %{formatted: system_desc}} = SNMP.get(cable_modem_target, "sysDescr.0")
    IO.puts("System Description: #{system_desc}")

    # GET returns type and raw value in enriched map
    {:ok, %{oid: oid, type: type, value: ticks}} = SNMP.get(cable_modem_target, "sysUpTime.0")
    IO.puts("System Uptime (raw): #{ticks} (#{type}) at OID #{oid}")

    # GET with pretty formatting (still preserves type and raw value)
    {:ok, %{formatted: formatted_uptime}} = SNMP.get_pretty(cable_modem_target, "sysUpTime.0")
    IO.puts("Formatted Uptime: #{formatted_uptime}")

    # GET system contact
    {:ok, %{formatted: contact}} = SNMP.get(cable_modem_target, "sysContact.0")
    IO.puts("System Contact: #{contact}")

### WALK Operations

WALK operations traverse the SNMP tree to get multiple related values:

    # Set targets for this cell
    cable_modem_target = "127.0.0.1:1161"
    router_target = "127.0.0.1:1162"

    IO.puts("\n=== SNMP WALK Operations ===\n")

    # Walk the system group
    {:ok, system_info} = SNMP.walk(cable_modem_target, "1.3.6.1.2.1.1.1")
    IO.puts("System group contains #{length(system_info)} objects:")

    # Display first few system objects
    system_info
    |> Enum.take(5)
    |> Enum.each(fn %{oid: oid, type: type, value: value} ->
      IO.puts("  #{oid} (#{type}) = #{inspect(value)}")
    end)

    # Walk with pretty formatting
    {:ok, pretty_system} = SNMP.walk_pretty(cable_modem_target, "system")
    IO.puts("\nPretty formatted system info:")
    pretty_system
    |> Enum.take(3)
    |> Enum.each(fn %{oid: name, formatted: value} ->
      IO.puts("  #{name}: #{value}")
    end)

### Interface Information

Let's explore interface data, which is crucial for network monitoring:

    # Set targets for this cell
    cable_modem_target = "127.0.0.1:1161"
    router_target = "127.0.0.1:1162"

    IO.puts("\n=== Interface Information ===\n")

    # Get interface count from cable modem
    {:ok, %{value: cm_if_count}} = SNMP.get(cable_modem_target, "ifNumber.0")
    IO.puts("Cable Modem interfaces: #{cm_if_count}")

    # Get interface count from router
    {:ok, %{value: router_if_count}} = SNMP.get(router_target, "ifNumber.0")
    IO.puts("Router interfaces: #{router_if_count}")

    # Get interface descriptions
    {:ok, %{formatted: cm_if1_desc}} = SNMP.get(cable_modem_target, "ifDescr.1")
    {:ok, %{formatted: cm_if2_desc}} = SNMP.get(cable_modem_target, "ifDescr.2")
    IO.puts("\nCable Modem Interface Details:")
    IO.puts("  Interface 1: #{cm_if1_desc}")
    IO.puts("  Interface 2: #{cm_if2_desc}")

    {:ok, %{formatted: router_if1_desc}} = SNMP.get(router_target, "ifDescr.1")
    {:ok, %{formatted: router_if2_desc}} = SNMP.get(router_target, "ifDescr.2")
    IO.puts("\nRouter Interface Details:")
    IO.puts("  Interface 1: #{router_if1_desc}")
    IO.puts("  Interface 2: #{router_if2_desc}")

### Bulk Operations

For large amounts of data, bulk operations are much more efficient:

    # Set targets for this cell
    cable_modem_target = "127.0.0.1:1161"
    router_target = "127.0.0.1:1162"

    IO.puts("\n=== Bulk Operations ===\n")

    # Standard bulk walk
    {:ok, bulk_results} = SNMP.bulk_walk(cable_modem_target, "interfaces")
    IO.puts("Bulk walk of interfaces returned #{length(bulk_results)} objects")

    # Adaptive bulk walk (auto-optimizes performance)
    {:ok, adaptive_results} = SNMP.adaptive_walk(cable_modem_target, "interfaces")
    IO.puts("Adaptive walk returned #{length(adaptive_results)} objects")

    # Get bulk with specific parameters
    {:ok, bulk_specific} = SNMP.get_bulk(cable_modem_target, "interfaces", [
      max_repetitions: 5,
      timeout: 2000
    ])
    IO.puts("Targeted bulk operation returned #{length(bulk_specific)} objects")

    # Show some bulk results
    bulk_results
    |> Enum.take(3)
    |> Enum.each(fn %{oid: oid, type: type, value: value} ->
      IO.puts("  #{oid} (#{type}): #{inspect(value)}")
    end)

### Multi-Target Operations

One of SnmpKit's powerful features is querying multiple devices simultaneously:

    # Set targets for this cell
    cable_modem_target = "127.0.0.1:1161"
    router_target = "127.0.0.1:1162"

    IO.puts("\n=== Multi-Target Operations ===\n")

    # Query both devices for system information
    multi_targets = [
      {cable_modem_target, "sysDescr.0"},
      {router_target, "sysDescr.0"},
      {cable_modem_target, "sysUpTime.0"},
      {router_target, "sysContact.0"}
    ]

    multi_results = SNMP.get_multi(multi_targets)
    IO.puts("Multi-target query results:")
    IO.puts("Raw results: #{inspect(multi_results)}")

    # Process results based on actual format
    multi_results
    |> Enum.with_index()
    |> Enum.each(fn {result, index} ->
      {target, oid} = Enum.at(multi_targets, index)
      case result do
        {:ok, value} ->
          IO.puts("  ✅ #{target} #{oid}: #{inspect(value)}")
        {:error, reason} ->
          IO.puts("  ❌ #{target} #{oid}: #{inspect(reason)}")
        _ ->
          IO.puts("  ? #{target} #{oid}: #{inspect(result)}")
      end
    end)

    # Multi-target walk operations
    walk_targets = [
      {cable_modem_target, "system"},
      {router_target, "system"}
    ]

    multi_walk_results = SNMP.walk_multi(walk_targets)
    IO.puts("\nMulti-target walk completed for #{length(multi_walk_results)} targets")

    # Display walk results
    multi_walk_results
    |> Enum.with_index()
    |> Enum.each(fn {result, index} ->
      {target, oid} = Enum.at(walk_targets, index)
      case result do
        {:ok, walk_data} ->
          IO.puts("  ✅ #{target} #{oid}: #{length(walk_data)} objects")
          # Show first few objects
          walk_data
          |> Enum.take(3)
          |> Enum.each(fn %{oid: obj_oid, type: type, value: value} ->
            IO.puts("    #{obj_oid} (#{type}) = #{inspect(value)}")
          end)
        {:error, reason} ->
          IO.puts("  ❌ #{target} #{oid}: #{inspect(reason)}")
        _ ->
          IO.puts("  ? #{target} #{oid}: #{inspect(result)}")
      end
    end)

## Chapter 3: SnmpKit.MIB - MIB Management 📚

The MIB (Management Information Base) system is the heart of SNMP. It defines the structure and meaning of SNMP data. Let's explore SnmpKit's powerful MIB capabilities!

### OID Name Resolution

    IO.puts("=== MIB Name Resolution ===\n")

    # Resolve common SNMP object names to OIDs
    common_objects = [
      "sysDescr.0",
      "sysUpTime.0",
      "sysContact.0",
      "ifNumber.0",
      "ifDescr.1",
      "ifInOctets.1",
      "system",
      "interfaces"
    ]

    IO.puts("Common SNMP objects and their OIDs:")
    Enum.each(common_objects, fn name ->
      case MIB.resolve(name) do
        {:ok, oid} ->
          IO.puts("  #{name} → #{Enum.join(oid, ".")}")
        {:error, reason} ->
          IO.puts("  #{name} → Error: #{reason}")
      end
    end)

### Reverse OID Lookup

    IO.puts("\n=== Reverse OID Lookup ===\n")

    # Convert OIDs back to names
    test_oids = [
      [1, 3, 6, 1, 2, 1, 1, 1, 0],
      [1, 3, 6, 1, 2, 1, 1, 3, 0],
      [1, 3, 6, 1, 2, 1, 2, 1, 0],
      [1, 3, 6, 1, 2, 1, 2, 2, 1, 2, 1]
    ]

    IO.puts("OID to name resolution:")
    Enum.each(test_oids, fn oid ->
      case MIB.reverse_lookup(oid) do
        {:ok, name} ->
          IO.puts("  #{Enum.join(oid, ".")} → #{name}")
        {:error, reason} ->
          IO.puts("  #{Enum.join(oid, ".")} → #{reason}")
      end
    end)

### MIB Tree Navigation

    IO.puts("\n=== MIB Tree Navigation ===\n")

    # Get children of the system group
    {:ok, system_oid} = MIB.resolve("system")
    {:ok, system_children} = MIB.children(system_oid)

    system_oid_str = if is_list(system_oid), do: Enum.join(system_oid, "."), else: inspect(system_oid)
    IO.puts("System group (#{system_oid_str}) has #{length(system_children)} children:")
    system_children
    |> Enum.take(5)
    |> Enum.each(fn child_oid ->
      oid_str = if is_list(child_oid), do: Enum.join(child_oid, "."), else: inspect(child_oid)
      case MIB.reverse_lookup(child_oid) do
        {:ok, name} ->
          IO.puts("  #{oid_str} (#{name})")
        {:error, _} ->
          IO.puts("  #{oid_str}")
      end
    end)

    # Get parent of a specific OID
    {:ok, sys_descr_oid} = MIB.resolve("sysDescr.0")
    {:ok, parent_oid} = MIB.parent(sys_descr_oid)
    {:ok, parent_name} = MIB.reverse_lookup(parent_oid)
    parent_oid_str = if is_list(parent_oid), do: Enum.join(parent_oid, "."), else: inspect(parent_oid)
    IO.puts("\nParent of sysDescr.0: #{parent_oid_str} (#{parent_name})")

## Chapter 4: Creating Custom Device Simulations 🛠️

One of the most powerful features of SnmpKit is creating realistic device simulations without needing walk files. Let's explore different approaches:

### Enterprise Switch Simulation

    IO.puts("=== Creating Enterprise Switch Simulation ===\n")

    # Define a realistic 24-port enterprise switch
    switch_oids = %{
      # System Group
      "1.3.6.1.2.1.1.1.0" => "Cisco IOS Software, C3560CX Software, Version 15.2(4)E10",
      "1.3.6.1.2.1.1.2.0" => "1.3.6.1.4.1.9.1.1208",
      "1.3.6.1.2.1.1.3.0" => %{type: "TimeTicks", value: 0},
      "1.3.6.1.2.1.1.4.0" => "Network Operations <netops@company.com>",
      "1.3.6.1.2.1.1.5.0" => "switch-core-01",
      "1.3.6.1.2.1.1.6.0" => "Main Office - Network Closet A",

      # Switch has many interfaces (24 ports + management)
      "1.3.6.1.2.1.2.1.0" => 25,  # ifNumber
    }

    # Add interfaces programmatically
    switch_oids = Enum.reduce(1..24, switch_oids, fn port, acc ->
      Map.merge(acc, %{
        # Interface descriptions
        "1.3.6.1.2.1.2.2.1.2.#{port}" => "GigabitEthernet0/#{port}",
        "1.3.6.1.2.1.2.2.1.3.#{port}" => 6,  # ethernetCsmacd
        "1.3.6.1.2.1.2.2.1.5.#{port}" => %{type: "Gauge32", value: 1000000000},  # 1Gbps
        "1.3.6.1.2.1.2.2.1.8.#{port}" => if(port <= 12, do: 1, else: 2),  # First 12 up, rest down
        # Traffic counters for active ports
        "1.3.6.1.2.1.2.2.1.10.#{port}" => %{type: "Counter32", value: 0},
        "1.3.6.1.2.1.2.2.1.16.#{port}" => %{type: "Counter32", value: 0},
      })
    end)

    # Add management interface
    switch_oids = Map.merge(switch_oids, %{
      "1.3.6.1.2.1.2.2.1.2.25" => "Management0",
      "1.3.6.1.2.1.2.2.1.3.25" => 6,
      "1.3.6.1.2.1.2.2.1.5.25" => %{type: "Gauge32", value: 100000000},  # 100Mbps
      "1.3.6.1.2.1.2.2.1.8.25" => 1,
    })

    # Create and start the switch
    {:ok, switch_profile} = SnmpKit.SnmpSim.ProfileLoader.load_profile(
      :switch,
      {:manual, switch_oids},
      behaviors: [:counter_increment, :time_based_changes]
    )

    {:ok, switch_device} = Sim.start_device(switch_profile, port: 1163)
    switch_target = "127.0.0.1:1163"

    IO.puts("✅ Enterprise switch simulation started on #{switch_target}")

    # Test the switch
    {:ok, %{formatted: switch_desc}} = SNMP.get(switch_target, "sysDescr.0")
    {:ok, %{value: switch_interfaces}} = SNMP.get(switch_target, "ifNumber.0")
    IO.puts("Switch: #{switch_desc}")
    IO.puts("Interfaces: #{switch_interfaces}")

    # Check a few interface statuses
    Enum.each([1, 5, 15, 25], fn port ->
      case SNMP.get(switch_target, "ifDescr.#{port}") do
        {:ok, %{formatted: desc}} ->
          case SNMP.get(switch_target, "ifOperStatus.#{port}") do
            {:ok, %{value: status}} ->
              status_text = if status == 1, do: "UP", else: "DOWN"
              IO.puts("  Port #{port}: #{desc} - #{status_text}")
            _ -> nil
          end
        _ -> nil
      end
    end)

### Wireless Access Point Simulation

    IO.puts("\n=== Creating Wireless Access Point Simulation ===\n")

    # Define a realistic dual-band wireless AP
    wireless_ap_oids = %{
      # System Group
      "1.3.6.1.2.1.1.1.0" => "Ubiquiti UniFi AP AC Pro, Version 4.3.21.11325",
      "1.3.6.1.2.1.1.2.0" => "1.3.6.1.4.1.41112.1.4.7",
      "1.3.6.1.2.1.1.3.0" => %{type: "TimeTicks", value: 0},
      "1.3.6.1.2.1.1.4.0" => "Wireless Admin <wireless@company.com>",
      "1.3.6.1.2.1.1.5.0" => "ap-lobby-01",
      "1.3.6.1.2.1.1.6.0" => "Main Building - Lobby",

      # Wireless interfaces: Management + 2.4GHz + 5GHz
      "1.3.6.1.2.1.2.1.0" => 3,

      # Management interface (Ethernet)
      "1.3.6.1.2.1.2.2.1.2.1" => "eth0 (Management)",
      "1.3.6.1.2.1.2.2.1.3.1" => 6,  # ethernetCsmacd
      "1.3.6.1.2.1.2.2.1.5.1" => %{type: "Gauge32", value: 1000000000},  # 1Gbps
      "1.3.6.1.2.1.2.2.1.8.1" => 1,

      # 2.4GHz radio
      "1.3.6.1.2.1.2.2.1.2.2" => "wlan0 (2.4GHz)",
      "1.3.6.1.2.1.2.2.1.3.2" => 71,  # ieee80211
      "1.3.6.1.2.1.2.2.1.5.2" => %{type: "Gauge32", value: 300000000},  # 300Mbps
      "1.3.6.1.2.1.2.2.1.8.2" => 1,

      # 5GHz radio
      "1.3.6.1.2.1.2.2.1.2.3" => "wlan1 (5GHz)",
      "1.3.6.1.2.1.2.2.1.3.3" => 71,  # ieee80211
      "1.3.6.1.2.1.2.2.1.5.3" => %{type: "Gauge32", value: 1300000000},  # 1.3Gbps
      "1.3.6.1.2.1.2.2.1.8.3" => 1,

      # Wireless-specific OIDs (simplified)
      "1.3.6.1.4.1.41112.1.4.1.1.4.1" => 6,    # 2.4GHz channel
      "1.3.6.1.4.1.41112.1.4.1.1.4.2" => 36,   # 5GHz channel
      "1.3.6.1.4.1.41112.1.4.1.1.5.1" => 20,   # TX power (dBm)
      "1.3.6.1.4.1.41112.1.4.1.1.5.2" => 23,   # TX power (dBm)
      "1.3.6.1.4.1.41112.1.4.1.1.6.1" => 15,   # Connected clients 2.4GHz
      "1.3.6.1.4.1.41112.1.4.1.1.6.2" => 8,    # Connected clients 5GHz

      # Traffic counters
      "1.3.6.1.2.1.2.2.1.10.2" => %{type: "Counter32", value: 0},  # 2.4GHz RX
      "1.3.6.1.2.1.2.2.1.16.2" => %{type: "Counter32", value: 0},  # 2.4GHz TX
      "1.3.6.1.2.1.2.2.1.10.3" => %{type: "Counter32", value: 0},  # 5GHz RX
      "1.3.6.1.2.1.2.2.1.16.3" => %{type: "Counter32", value: 0}   # 5GHz TX
    }

    # Create the wireless AP profile
    {:ok, wireless_ap_profile} = SnmpKit.SnmpSim.ProfileLoader.load_profile(
      :wireless_ap,
      {:manual, wireless_ap_oids},
      behaviors: [:counter_increment, :time_based_changes]
    )

    # NOTE: If you get port conflicts, restart your Livebook runtime to clean up previous devices

    # Try to find an available port starting from 1164
    {wireless_ap, wireless_ap_target} =
      Enum.reduce_while(1164..1170, nil, fn port, _acc ->
        case Sim.start_device(wireless_ap_profile, [port: port, community: "public"]) do
          {:ok, device} ->
            target = "127.0.0.1:#{port}"
            {:halt, {device, target}}
          {:error, :eaddrinuse} ->
            IO.puts("Port #{port} in use, trying next...")
            {:cont, nil}
          {:error, reason} ->
            IO.puts("Port #{port} failed: #{inspect(reason)}")
            {:cont, nil}
        end
      end) ||
      raise "Could not find available port for wireless AP. Try restarting runtime."

    IO.puts("✅ Wireless AP simulation started on #{wireless_ap_target}")

    # Test the wireless AP
    {:ok, %{formatted: ap_desc}} = SNMP.get(wireless_ap_target, "sysDescr.0")
    {:ok, %{value: ap_interfaces}} = SNMP.get(wireless_ap_target, "ifNumber.0")
    IO.puts("Wireless AP: #{ap_desc}")
    IO.puts("Interfaces: #{ap_interfaces}")

    # Check wireless-specific data
    case SNMP.get(wireless_ap_target, "1.3.6.1.4.1.41112.1.4.1.1.6.1") do
      {:ok, %{value: clients_24}} -> IO.puts("2.4GHz Clients: #{clients_24}")
      _ -> IO.puts("2.4GHz Clients: Not available")
    end

    case SNMP.get(wireless_ap_target, "1.3.6.1.4.1.41112.1.4.1.1.6.2") do
      {:ok, %{value: clients_5}} -> IO.puts("5GHz Clients: #{clients_5}")
      _ -> IO.puts("5GHz Clients: Not available")
    end

    wireless_ap_target

## Congratulations! 🎉

You've completed the SnmpKit Interactive Tour! You've learned how to:

- 🎯 Use the Unified API - Clean, context-based modules for different operations
- 📡 Perform SNMP Operations - GET, WALK, bulk operations, and multi-target queries
- 📚 Work with MIBs - Resolve OIDs, navigate the MIB tree, and understand SNMP data
- 🧪 Create Device Simulations - Build realistic test environments without real hardware
- ⚡ Leverage Advanced Features - Streaming, performance optimization, and analytics

### Next Steps

1. Explore the Documentation: https://hexdocs.pm/snmpkit
2. Try the Examples: Check out the `examples/` directory for more practical use cases
3. Read the Guides:
   - MIB Guide - Deep dive into MIB management
   - Testing Guide - Testing strategies and patterns
4. Build Something Cool: Use SnmpKit in your own projects!

### Community

- Issues & Questions: GitHub Issues
- Contributing: Contributing Guide
- Discussions: Share your SnmpKit projects and get help

Happy SNMP monitoring with SnmpKit! 🚀

← Previous Page SnmpKit v0.4.0 Release Notes
Next Page → SnmpKit Examples