///|
/// Ask a device who it is.
///
/// moon run examples/query
/// moon run examples/query -- 192.0.2.10 public 161
///
/// Reads the MIB-II system group in one round trip and prints it. This
/// is the smallest useful thing an SNMP client does, and it is also the
/// first thing to try when something is wrong: if this works, the
/// address, the community and the version are all right.
///|
fn main {
let cli = @common.Cli::read()
let target = cli.target()
println(
"asking \{target.host}:\{target.port} as \"\{@utf8.decode_lossy(target.community)}\"\n",
)
let outcome = Some(ask(target)) catch {
@session.SnmpError::NoResponse => {
println(
"no reply. The address, the port, or the community is wrong, or the agent is not running.",
)
None
}
e => {
println("the agent answered, but not usefully: \{Repr(e)}")
None
}
}
match outcome {
Some(socket) => socket.close()
None => @common.fail(1)
}
}
///|
/// One GET for the whole system group, then print it.
///
/// Returns the socket so `main` can close it once the work is done. A
/// socket closed while a reply is still in flight would drop that reply,
/// which is a surprising way to lose an answer.
fn ask(target : @common.Target) -> @transport.Transport raise {
// A client wants the OS to choose its local port. The timeout and
// retry count are the two numbers worth thinking about: two seconds
// and one retry is a sane default for a device on the same network.
let socket = @transport.Transport::open(0, 3000)
let session = @session.Session::new(
socket,
target.host,
target.port,
@pdu.V2c,
target.community,
2000, // ms to wait for each reply
1, // extra attempts after a timeout
1, // first request id
)
// One round trip for six values. Asking for them together is both
// faster than six GETs and more consistent, because the answers all
// come from the same instant.
let varbinds = session.get_many([
@mib.sys_descr(),
@mib.sys_object_id(),
@mib.sys_up_time(),
@mib.sys_name(),
@mib.sys_contact(),
@mib.sys_location(),
])
// The library hands back what the agent sent, in the order it was
// asked for. Naming the fields is the caller's job, and the MIB
// constants are what makes it readable instead of a column of dotted
// numbers.
for vb in varbinds {
println(
"\{@common.pad(name_of(vb.name), 12)} \{@mib.format_value(vb.value)}",
)
}
socket
}
///|
/// A short name for the six OIDs this program asks about.
fn name_of(oid : @oid.Oid) -> String {
let known : Array[(@oid.Oid, String)] = [
(@mib.sys_descr(), "sysDescr"),
(@mib.sys_object_id(), "sysObjectID"),
(@mib.sys_up_time(), "sysUpTime"),
(@mib.sys_name(), "sysName"),
(@mib.sys_contact(), "sysContact"),
(@mib.sys_location(), "sysLocation"),
]
for entry in known {
let (candidate, label) = entry
if oid.compare(candidate) == 0 {
return label
}
}
oid.to_dotted()
}