View Source MNDP (mndp v0.1.1)
An Elixir implementation for the MikroTik Neighbor Discovery Protocol.
Discover devices
> mix mndp.discover
Press any key to end
┌───────┬──────┬────────────────┬─────────┬─────────────┬─────────┬───────────┐
│VERSION│UPTIME│MAC │LAST SEEN│IPV4 │INTERFACE│IDENTITY │
├───────┼──────┼────────────────┼─────────┼─────────────┼─────────┼───────────┤
│0.1.0 │4396 │6:95:72:B0:80:BB│0s ago │172.31.199.73│usb0 │nerves-2a0c│
└───────┴──────┴────────────────┴─────────┴─────────────┴─────────┴───────────┘
The application is automatically started and listening and broadcasting on all available IPv4 network interfaces. You can restrict the interfaces via config. See MNDP.Options
. To use it just add the dependency to your project.
def deps do
[
{:mndp, "~> 0.1.0"}
]
end
To get the last discovered devices you can use MNDP.Listener.list_discovered/0
.
You can decode and encode from and to a binary directly.
Encoding:
iex> MNDP.new!("en0") |> MNDP.encode()
<<...>>
Decoding:
iex> MNDP.decode(binary)
{:ok, %MNDP{}}
Summary
Types
representation of a hardware MAC address
field overrides for the MNDP struct generation
a MNDP packet struct
seconds since start
Functions
Decdes a binary to a MNDP
struct.
Ecnodes an MNDP
stuct to a binary
List all the discovered devices withing configured TTL. See MNDP.Options
for informations about how to modify the default settings
Creates a new MNDP struct from an interface name or struct.
Same as MNDP.new/2
but raises on error
Will print the devices from list_discovered/0
via IO.puts
Subscribe the current process to new MNDP packets from the listener. It will send a message with the type {:mndp, MNDP.t()}
Types
@type mac() :: [byte()]
representation of a hardware MAC address
@type overrides() :: [ identity: String.t(), version: String.t(), platform: String.t(), uptime: (-> uptime()), software_id: String.t(), board: String.t() ]
field overrides for the MNDP struct generation
@type t() :: %MNDP{ board: String.t(), identity: String.t(), interface: String.t(), ip_v4: :inet.ip4_address(), ip_v6: :inet.ip6_address(), last_seen: DateTime.t(), mac: mac(), platform: String.t(), seq_no: non_neg_integer(), software_id: String.t(), ttl: integer(), type: integer(), unpack: :none | nil, uptime: uptime(), version: String.t() }
a MNDP packet struct
@type uptime() :: non_neg_integer()
seconds since start
Functions
Decdes a binary to a MNDP
struct.
The structure of a MNDP packet looks like this
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| TYPE | TTL | SEQ |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| TLV TYPE | TLV LENGTH |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ TLV DATA +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- TYPE (8 bytes)
- TTL (8 bytes)
- SEQ (16 bytes)
- TLV TYPE (16 bytes)
- TLV LENGTH (16 bytes)
- TLV DATA (TLV LENGTH)
Info
The header fields are set like this in RouterOS 6 but changes in RouterOS 7. Currently it is not guaranteed to be correct
Examples
iex> MNDP.decode(<<_>>)
{:ok, %MNDP{}}
iex> MNDP.decode(<<"unknown">>)
{:error, :expected_tlv_format}
Ecnodes an MNDP
stuct to a binary
Examples
iex> MNDP.encode(%MNDP{})
<<...>>
@spec list_discovered() :: [t()]
List all the discovered devices withing configured TTL. See MNDP.Options
for informations about how to modify the default settings
@spec new(MNDP.Interface.t() | String.t(), overrides()) :: t() | {:error, atom()}
Creates a new MNDP struct from an interface name or struct.
Examples
iex> MNDP.new("en0")
{:ok, %MNDP{}}
iex> MNDP.new("unknown interface")
{:error, :interface_not_found}
Same as MNDP.new/2
but raises on error
@spec print_discovered() :: :ok
Will print the devices from list_discovered/0
via IO.puts
@spec subscribe() :: :ok
Subscribe the current process to new MNDP packets from the listener. It will send a message with the type {:mndp, MNDP.t()}
Ususally you do this in a process like a GenServer and the message can be handled like this
def handle_info({:mndp, %MNDP{} = mndp}, state) do
IO.inspect(mndp)
{:noreply, state}
end
If you subscribe from an IEx
shell you can flush the received messages with IEx.Helpers.flush/0
. It is automatically imported and can be called with just flush()