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_mgr target.ex
Raw

lib/snmpkit/snmp_mgr/target.ex

defmodule SnmpKit.SnmpMgr.Target do
@moduledoc """
Target parsing and validation for SNMP requests.
Handles parsing of various target formats and resolves hostnames to IP addresses.
"""
@default_port 161
@doc """
Parses a target string into a structured format.
## Examples
iex> SnmpKit.SnmpMgr.Target.parse("192.168.1.1:161")
{:ok, %{host: {192, 168, 1, 1}, port: 161}}
iex> SnmpKit.SnmpMgr.Target.parse("device.local")
{:ok, %{host: "device.local", port: 161}}
iex> SnmpKit.SnmpMgr.Target.parse("192.168.1.1")
{:ok, %{host: {192, 168, 1, 1}, port: 161}}
"""
def parse(target) when is_binary(target) do
case String.split(target, ":") do
[host] ->
parse_host_and_port(host, @default_port)
[host, port_str] ->
case Integer.parse(port_str) do
{port, ""} when port > 0 and port <= 65535 ->
parse_host_and_port(host, port)
_ ->
{:error, {:invalid_port, port_str}}
end
_ ->
{:error, {:invalid_target_format, target}}
end
end
def parse(target) when is_tuple(target) and tuple_size(target) == 4 do
# Already an IP tuple
{:ok, %{host: target, port: @default_port}}
end
def parse(%{host: _host, port: _port} = target) do
# Already parsed
{:ok, target}
end
def parse(_target) do
{:error, :invalid_target_format}
end
@doc """
Resolves a target to a parsed struct, with fallback for unparseable targets.
Unlike `parse/1` which returns `{:ok, target}` or `{:error, reason}`,
this function always returns a target struct, using the raw input as
the host with default port 161 if parsing fails.
This is the canonical function for target resolution across all modules.
## Examples
iex> SnmpKit.SnmpMgr.Target.resolve("192.168.1.1:161")
%{host: {192, 168, 1, 1}, port: 161}
iex> SnmpKit.SnmpMgr.Target.resolve("device.local")
%{host: "device.local", port: 161}
iex> SnmpKit.SnmpMgr.Target.resolve(%{host: {127, 0, 0, 1}, port: 162})
%{host: {127, 0, 0, 1}, port: 162}
"""
def resolve(target) when is_binary(target) do
case parse(target) do
{:ok, parsed} -> parsed
{:error, _} -> %{host: target, port: @default_port}
end
end
def resolve(%{host: _, port: _} = target), do: target
def resolve(target) when is_tuple(target) and tuple_size(target) == 4 do
%{host: target, port: @default_port}
end
def resolve(target), do: %{host: target, port: @default_port}
@doc """
Resolves a hostname to an IP address if needed.
If the host is already an IP tuple, returns it unchanged.
"""
def resolve_hostname(%{host: host, port: _port} = target) when is_tuple(host) do
{:ok, target}
end
def resolve_hostname(%{host: hostname, port: port}) when is_binary(hostname) do
case parse_ip_address(hostname) do
{:ok, ip_tuple} ->
{:ok, %{host: ip_tuple, port: port}}
:error ->
case :inet.gethostbyname(String.to_charlist(hostname)) do
{:ok, {:hostent, _name, _aliases, :inet, 4, [ip_tuple | _]}} ->
{:ok, %{host: ip_tuple, port: port}}
{:error, reason} ->
{:error, {:hostname_resolution_failed, hostname, reason}}
end
end
end
@doc """
Validates that a target is reachable (basic connectivity check).
"""
def validate_connectivity(%{host: host, port: port}, timeout \\ 5000) do
case :gen_tcp.connect(host, port, [], timeout) do
{:ok, socket} ->
:gen_tcp.close(socket)
:ok
{:error, :econnrefused} ->
# This is actually good - the port responded (even if it refused)
:ok
{:error, reason} ->
{:error, {:connectivity_check_failed, reason}}
end
end
# Private functions
defp parse_host_and_port(host, port) do
case parse_ip_address(host) do
{:ok, ip_tuple} ->
{:ok, %{host: ip_tuple, port: port}}
:error ->
# Assume it's a hostname
{:ok, %{host: host, port: port}}
end
end
defp parse_ip_address(ip_string) do
case :inet.parse_address(String.to_charlist(ip_string)) do
{:ok, ip_tuple} -> {:ok, ip_tuple}
{:error, _} -> :error
end
end
end