Skip to content

Commit

Permalink
Replaced NetSNMP.Agent with Pathname; refactored error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
Jonathan D. Storm committed Aug 7, 2015
1 parent a905e12 commit 1d80021
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 138 deletions.
29 changes: 10 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ A thin layer of Elixir poured atop net-snmp utilities. To be used with [snmp-mib
iex> credential = NetSNMP.credential :v2c, "public"
[version: "2c", community: "public"]
iex> agent = NetSNMP.agent "192.0.2.2"
%NetSNMP.Agent{host: "192.0.2.2", ip_proto: :udp, port: 161}
iex> agent = Pathname.new "192.0.2.2"
%NetSNMP.Agent{host: "192.0.2.2", ip_proto: nil, nil}
iex> sysname_object = SNMPMIB.object ".1.3.6.1.2.1.1.5", :string, ""
%SNMPMIB.Object{oid: [1, 3, 6, 1, 2, 1, 1, 5], type: 4, value: ""}
Expand All @@ -24,22 +24,14 @@ iex> ip_net_to_media_table_object = SNMPMIB.object "1.3.6.1.2.1.4.22", :any, nil
[ok: %SNMPMIB.Object{oid: [1, 3, 6, 1, 2, 1, 4, 22], type: 0, value: nil}]
iex> NetSNMP.walk ip_net_to_media_table_object, agent, credential
[ok: %SNMPMIB.Object{oid: [1, 3, 6, 1, 2, 1, 4, 22, 1, 1, 2, 192, 0, 2, 1],
type: 2, value: 2},
ok: %SNMPMIB.Object{oid: [1, 3, 6, 1, 2, 1, 4, 22, 1, 1, 2, 192, 0, 2, 2],
type: 2, value: 2},
ok: %SNMPMIB.Object{oid: [1, 3, 6, 1, 2, 1, 4, 22, 1, 2, 2, 192, 0, 2, 1],
type: 4, value: "66:fb:30:37:c3:81"},
ok: %SNMPMIB.Object{oid: [1, 3, 6, 1, 2, 1, 4, 22, 1, 2, 2, 192, 0, 2, 2],
type: 4, value: "c2:1:39:f3:0:0"},
ok: %SNMPMIB.Object{oid: [1, 3, 6, 1, 2, 1, 4, 22, 1, 3, 2, 192, 0, 2, 1],
type: 4, value: "192.0.2.1"},
ok: %SNMPMIB.Object{oid: [1, 3, 6, 1, 2, 1, 4, 22, 1, 3, 2, 192, 0, 2, 2],
type: 4, value: "192.0.2.2"},
ok: %SNMPMIB.Object{oid: [1, 3, 6, 1, 2, 1, 4, 22, 1, 4, 2, 192, 0, 2, 1],
type: 2, value: 3},
ok: %SNMPMIB.Object{oid: [1, 3, 6, 1, 2, 1, 4, 22, 1, 4, 2, 192, 0, 2, 2],
type: 2, value: 4}]
[ok: %SNMPMIB.Object{oid: [1,3,6,1,2,1,4,22,1,1,2,192,0,2,1], type: 2, value: 2},
ok: %SNMPMIB.Object{oid: [1,3,6,1,2,1,4,22,1,1,2,192,0,2,2], type: 2, value: 2},
ok: %SNMPMIB.Object{oid: [1,3,6,1,2,1,4,22,1,2,2,192,0,2,1], type: 4, value: "66:fb:30:37:c3:81"},
ok: %SNMPMIB.Object{oid: [1,3,6,1,2,1,4,22,1,2,2,192,0,2,2], type: 4, value: "c2:1:39:f3:0:0"},
ok: %SNMPMIB.Object{oid: [1,3,6,1,2,1,4,22,1,3,2,192,0,2,1], type: 4, value: "192.0.2.1"},
ok: %SNMPMIB.Object{oid: [1,3,6,1,2,1,4,22,1,3,2,192,0,2,2], type: 4, value: "192.0.2.2"},
ok: %SNMPMIB.Object{oid: [1,3,6,1,2,1,4,22,1,4,2,192,0,2,1], type: 2, value: 3},
ok: %SNMPMIB.Object{oid: [1,3,6,1,2,1,4,22,1,4,2,192,0,2,2], type: 2, value: 4}]
iex> ip_cidr_route_table_object = SNMPMIB.object "1.3.6.1.2.1.4.24.4", :any, nil
Expand All @@ -54,7 +46,6 @@ iex> NetSNMP.table ip_cidr_route_table_object, agent, credential
mask: "255.255.255.255", metric1: "0", metric2: "-1", metric3: "-1",
metric4: "-1", metric5: "-1", nexthop: "0.0.0.0", nexthopas: "0", proto: "2",
status: "1", tos: "0", type: "3"}]
```

For now, this assumes you (1) have net-snmp utilities installed and (2) snmpget, snmpset, etc. are in your path.
Expand Down
209 changes: 103 additions & 106 deletions lib/net_snmp.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,41 +4,6 @@
# as published by Sam Hocevar. See the COPYING.WTFPL file for more details.

defmodule NetSNMP do
@type ip_protocol :: :udp | :tcp
@type port_number :: 0..65535

defmodule Agent do
defstruct host: nil, ip_proto: nil, port: nil

@type t :: %Agent{
host: String.t,
ip_proto: NetSNMP.ip_protocol,
port: NetSNMP.port_number
}

def host(agent) do
agent.host
end

def ip_protocol(agent) do
agent.ip_proto
end

def port(agent) do
agent.port
end
end

@spec agent(String.t, ip_protocol, port_number) :: Agent.t
def agent(host, ip_protocol, port)
when ip_protocol in [:tcp, :udp] and port in 0..65535 do
%Agent{host: host, ip_proto: ip_protocol, port: port}
end
@spec agent(String.t) :: Agent.t
def agent(host) do
%Agent{host: host, ip_proto: :udp, port: 161}
end

@spec credential(:v1, String.t) :: Keyword.t
def credential(:v1, community) do
[
Expand All @@ -61,7 +26,7 @@ defmodule NetSNMP do
sec_name: sec_name
]
end
@spec credential(:v3, :auth_no_priv, String.t, :md5 | :sha, String.t) :: Keyword.t
@spec credential(:v3, :auth_no_priv, String.t, :md5|:sha, String.t) :: Keyword.t
def credential(:v3, :auth_no_priv, sec_name, auth_proto, auth_pass)
when auth_proto in [:md5, :sha] do
[
Expand All @@ -72,7 +37,7 @@ defmodule NetSNMP do
auth_pass: auth_pass
]
end
@spec credential(:v3, :auth_priv, String.t, :md5 | :sha, String.t, :des | :aes, String.t) :: Keyword.t
@spec credential(:v3, :auth_priv, String.t, :md5|:sha, String.t, :des|:aes, String.t) :: Keyword.t
def credential(:v3, :auth_priv, sec_name, auth_proto, auth_pass, priv_proto, priv_pass)
when auth_proto in [:md5, :sha] and priv_proto in [:des, :aes] do
[
Expand All @@ -87,34 +52,34 @@ defmodule NetSNMP do
end

defp _credential_to_snmpcmd_args([], acc) do
Enum.join(acc, " ")
Enum.join acc, " "
end
defp _credential_to_snmpcmd_args([{:version, version}|tail], acc) do
_credential_to_snmpcmd_args(tail, ["-v#{version}"|acc])
_credential_to_snmpcmd_args tail, ["-v#{version}"|acc]
end
defp _credential_to_snmpcmd_args([{:community, community}|tail], acc) do
_credential_to_snmpcmd_args(tail, acc ++ ["-c #{community}"])
_credential_to_snmpcmd_args tail, acc ++ ["-c #{community}"]
end
defp _credential_to_snmpcmd_args([{:sec_level, sec_level}|tail], acc) do
_credential_to_snmpcmd_args(tail, acc ++ ["-l#{sec_level}"])
_credential_to_snmpcmd_args tail, acc ++ ["-l#{sec_level}"]
end
defp _credential_to_snmpcmd_args([{:sec_name, sec_name}|tail], acc) do
_credential_to_snmpcmd_args(tail, acc ++ ["-u #{sec_name}"])
_credential_to_snmpcmd_args tail, acc ++ ["-u #{sec_name}"]
end
defp _credential_to_snmpcmd_args([{:auth_proto, auth_proto}|tail], acc) do
_credential_to_snmpcmd_args(tail, acc ++ ["-a #{auth_proto}"])
_credential_to_snmpcmd_args tail, acc ++ ["-a #{auth_proto}"]
end
defp _credential_to_snmpcmd_args([{:auth_pass, auth_pass}|tail], acc) do
_credential_to_snmpcmd_args(tail, acc ++ ["-A #{auth_pass}"])
_credential_to_snmpcmd_args tail, acc ++ ["-A #{auth_pass}"]
end
defp _credential_to_snmpcmd_args([{:priv_proto, priv_proto}|tail], acc) do
_credential_to_snmpcmd_args(tail, acc ++ ["-x #{priv_proto}"])
_credential_to_snmpcmd_args tail, acc ++ ["-x #{priv_proto}"]
end
defp _credential_to_snmpcmd_args([{:priv_pass, priv_pass}|tail], acc) do
_credential_to_snmpcmd_args(tail, acc ++ ["-X #{priv_pass}"])
_credential_to_snmpcmd_args tail, acc ++ ["-X #{priv_pass}"]
end
def credential_to_snmpcmd_args(credential) do
_credential_to_snmpcmd_args(credential, [])
_credential_to_snmpcmd_args credential, []
end

defp output_type_string_to_type(type_string) do
Expand Down Expand Up @@ -198,26 +163,35 @@ defmodule NetSNMP do
end

defp output_error_message_to_cause(error_message) do
error_message
[error|_] = String.split error_message, "("

error
|> String.lstrip(?=)
|> String.strip
end

defp parse_snmp_error(output) do
[line|_] = String.split output, "\n"
[_|error_words] = String.split(line)

cause = error_words
|> Enum.join(" ")
|> output_error_message_to_cause
|> get_snmpcmd_error

{:error, cause}
end

defp parse_snmp_output_line(line) do
try do
[oid, _, type_string, value] = String.split(line)

type = output_type_string_to_type(type_string)

{:ok, SNMPMIB.object(oid, type, value)}
rescue
_ ->
[_|error_words] = String.split(line)
cause = error_words
|> Enum.join(" ")
|> output_error_message_to_cause
|> get_snmpcmd_error

{:error, cause}
parse_snmp_error(line)
end
end

Expand All @@ -232,62 +206,78 @@ defmodule NetSNMP do
for pair <- Enum.zip(columns, values), into: %{}, do: pair
end

def parse_snmp_table_output(output) do
[headers | rows] = output
|> String.strip
|> String.split("\n")
|> Enum.drop(1)
|> Enum.filter(fn "" -> false; _ -> true end)

columns = headers
defp parse_column_headers(headers) do
headers
|> String.split("||")
|> Enum.map(fn header ->
header |> String.downcase |> String.to_atom
header
|> String.downcase
|> String.to_atom
end)
end

rows
|> Stream.map(fn row -> String.split(row, "||") end)
|> Enum.map(fn values ->
columns_and_values_to_data_model(columns, values)
end)
def parse_snmp_table_output(output) do
try do
[headers | rows] = output
|> String.strip
|> String.split("\n")
|> Enum.drop(1)
|> Enum.filter(fn "" -> false; _ -> true end)

rows
|> Stream.map(fn row -> String.split(row, "||") end)
|> Enum.map(fn values ->
headers
|> parse_column_headers
|> columns_and_values_to_data_model(values)
end)
rescue
_ ->
parse_snmp_error(output)
end
end

defp objects_to_oids(snmp_objects) do
snmp_objects
|> Enum.map(fn object ->
SNMPMIB.Object.oid(object) |> SNMPMIB.list_oid_to_string
Enum.map(snmp_objects, fn object ->
object
|> SNMPMIB.Object.oid
|> SNMPMIB.list_oid_to_string
end)
end

defp gen_snmpcmd(:get, snmp_objects, agent, credential)
defp gen_snmpcmd(:get, snmp_objects, pathname, credential)
when is_list(snmp_objects) do
[
"snmpget -Le -mALL -One",
credential_to_snmpcmd_args(credential),
to_string(agent) | objects_to_oids(snmp_objects)
] |> Enum.join(" ")
to_string(pathname) | objects_to_oids(snmp_objects)
]
|> Enum.join(" ")
end
defp gen_snmpcmd(:set, snmp_objects, agent, credential)
defp gen_snmpcmd(:set, snmp_objects, pathname, credential)
when is_list(snmp_objects) do
[
"snmpset -Le -mALL -One",
credential_to_snmpcmd_args(credential),
to_string(agent) | (for o <- snmp_objects, do: to_string o)
] |> Enum.join(" ")
to_string(pathname) | (for o <- snmp_objects, do: to_string o)
]
|> Enum.join(" ")
end
defp gen_snmpcmd(:table, snmp_object, agent, credential) do
defp gen_snmpcmd(:table, snmp_object, pathname, credential) do
[
"snmptable -Le -mALL -Clbf '||' -Oe",
credential_to_snmpcmd_args(credential),
to_string(agent) | objects_to_oids([snmp_object])
] |> Enum.join(" ")
to_string(pathname) | objects_to_oids([snmp_object])
]
|> Enum.join(" ")
end
defp gen_snmpcmd(:walk, snmp_object, agent, credential) do
defp gen_snmpcmd(:walk, snmp_object, pathname, credential) do
[
"snmpwalk -Le -mALL -One",
credential_to_snmpcmd_args(credential),
to_string(agent) | objects_to_oids([snmp_object])
] |> Enum.join(" ")
to_string(pathname) | objects_to_oids([snmp_object])
]
|> Enum.join(" ")
end

defp shell_cmd(command) do
Expand All @@ -297,55 +287,62 @@ defmodule NetSNMP do
|> :binary.list_to_bin
end

def get(snmp_objects, agent, credential) when is_list(snmp_objects) do
gen_snmpcmd(:get, snmp_objects, agent, credential)
def get(snmp_objects, pathname, credential) when is_list(snmp_objects) do
gen_snmpcmd(:get, snmp_objects, pathname, credential)
|> shell_cmd
|> parse_snmp_output
end
def get(snmp_object, agent, credential) do
get([snmp_object], agent, credential)
def get(snmp_object, pathname, credential) do
get [snmp_object], pathname, credential
end

def set(snmp_objects, agent, credential) when is_list(snmp_objects) do
gen_snmpcmd(:set, snmp_objects, agent, credential)
def set(snmp_objects, pathname, credential) when is_list(snmp_objects) do
gen_snmpcmd(:set, snmp_objects, pathname, credential)
|> shell_cmd
|> parse_snmp_output
end
def set(snmp_object, agent, credential) do
set([snmp_object], agent, credential)
def set(snmp_object, pathname, credential) do
set [snmp_object], pathname, credential
end

def table(snmp_objects, agent, credential) when is_list(snmp_objects) do
def table(snmp_objects, pathname, credential) when is_list(snmp_objects) do
snmp_objects
|> Enum.map(fn object -> table(object, agent, credential) end)
|> Enum.map(fn object -> table(object, pathname, credential) end)
|> List.flatten
end
def table(snmp_object, agent, credential) do
gen_snmpcmd(:table, snmp_object, agent, credential)
def table(snmp_object, pathname, credential) do
gen_snmpcmd(:table, snmp_object, pathname, credential)
|> shell_cmd
|> parse_snmp_table_output
end

def walk(snmp_objects, agent, credential) when is_list(snmp_objects) do
def walk(snmp_objects, pathname, credential) when is_list(snmp_objects) do
snmp_objects
|> Enum.map(fn object -> walk(object, agent, credential) end)
|> Enum.map(fn object -> walk(object, pathname, credential) end)
|> List.flatten
end
def walk(snmp_object, agent, credential) do
gen_snmpcmd(:walk, snmp_object, agent, credential)
def walk(snmp_object, pathname, credential) do
gen_snmpcmd(:walk, snmp_object, pathname, credential)
|> shell_cmd
|> parse_snmp_output
end
end

defimpl String.Chars, for: NetSNMP.Agent do
defimpl String.Chars, for: Pathname do
import Kernel, except: [to_string: 1]

def to_string(agent) do
transport_spec = agent |> NetSNMP.Agent.ip_protocol |> Kernel.to_string
transport_addr = NetSNMP.Agent.host(agent)
transport_port = agent |> NetSNMP.Agent.port |> Kernel.to_string
def to_string(pathname) do
transport_spec = pathname
|> Pathname.protocol
|> Kernel.to_string

transport_addr = Pathname.address(pathname)

transport_port = Pathname.protocol_params(pathname)[:port]
|> Kernel.to_string

[transport_spec, transport_addr, transport_port] |> Enum.join(":")
[transport_spec, transport_addr, transport_port]
|> Enum.join(":")
|> String.strip(?:)
end
end
Loading

0 comments on commit 1d80021

Please sign in to comment.