Net::Telnet::Netgear - Generate and send Netgear Telnet-enable packets through Net::Telnet
use Net::Telnet::Netgear;
my $telnet = Net::Telnet::Netgear->new (
# Standard Net::Telnet parameters are allowed
host => 'example.com',
packet_mac => 'AA:BB:CC:DD:EE:FF', # or AABBCCDDEEFF
packet_username => 'admin',
packet_password => 'hunter2',
netgear_defaults => 1
);
# The magic is done transparently: the packet has already been sent,
# if necessary, and the standard Net::Telnet API can now be used.
my @lines = $telnet->cmd ('whoami');
use Net::Telnet::Netgear::Packet;
# Manually create a packet.
my $packet = Net::Telnet::Netgear::Packet->new (mac => '...');
say length $packet->get_packet; # or whatever you want
$packet = Net::Telnet::Netgear::Packet->from_base64 ('...');
$packet = Net::Telnet::Netgear::Packet->from_string ('...');
This module allows to programmatically generate and send magic Telnet-enabling packets for Netgear routers with a locked Telnet interface. The packet can either be user-provided or it can be automatically generated given the username, password and MAC address of the router. Also, this module is capable of sending packets using TCP or UDP (the latter is used on new firmwares), and can automatically pick the right protocol to use, making it compatible with old and new firmwares without any additional configuration.
The work on the Telnet protocol is done by Net::Telnet, which is subclassed by this module. In fact, it's possible to use the entire Net::Telnet API and configuration parameters.
Net::Telnet::Netgear inherits all methods from Net::Telnet and implements the following new ones.
my $instance = Net::Telnet::Netgear->new (%options);
Creates a new Net::Telnet::Netgear
instance. Returns undef
on failure.
%options
can contain any of the options valid with the constructor of Net::Telnet,
with the addition of:
-
packet_mac => 'AA:BB:CC:DD:EE:FF'
The MAC address of the router where the packet will be sent to. Each non-hexadecimal character (like colons) will be removed.
-
packet_username => 'admin'
The username that will be put in the packet. Defaults to
Gearguy
for compatibility reasons. With new firmwares, the usernameadmin
should be used.Has no effect if
packet_mac
is not specified. -
packet_password => 'password'
The password that will be put in the packet. Defaults to
Geardog
for compatibility reasons. With new firmwares, the password of the router interface should be used.Has no effect if
packet_mac
is not specified. -
packet_content => 'string'
The content of the packet to be sent, as a string.
Only makes sense if the packet is not defined elsewhere.
-
packet_base64 => 'b64_string'
The content of the packet to be sent, as a Base64 encoded string.
Only makes sense if the packet is not defined elsewhere.
-
packet_instance => ...
A subclass of Net::Telnet::Netgear::Packet to be used as the packet.
Only makes sense if the packet is not defined elsewhere.
NOTE: Packets generated with "new" in Net::Telnet::Netgear::Packet, "from_string" in Net::Telnet::Netgear::Packet and "from_base64" in Net::Telnet::Netgear::Packet can be used too.
-
packet_delay => .50
The amount of time, in seconds, to wait after sending the packet. In pseudo-code:
send_packet(); wait(packet_delay); connect()
Defaults to
.3
seconds, or 300 milliseconds. Can be0
. -
packet_wait_timeout => .75
The amount of time, in seconds, to wait for a response from the server before sending the packet. In pseudo-code:
connect(); if !can_read(in packet_wait_timeout seconds) then send_packet()
Only effective when the packet is sent using TCP. Defaults to
1
second. -
packet_send_mode => 'auto|tcp|udp'
Determines how to send the packet. See "packet_send_mode" below.
Defaults to
auto
. -
netgear_defaults => 0|1
If enabled, the default values defined in the hash
%Net::Telnet::Netgear::NETGEAR_DEFAULTS
are applied once the connection is established. See "DEFAULT VALUES USING %NETGEAR_DEFAULTS".Defaults to
0
. -
exit_on_destroy => 0|1
If enabled, the
exit
shell command is sent before the object is destroyed. This is useful to avoid ghost processes when closing a Telnet connection without killing the shell first.Defaults to
0
.
$instance->apply_netgear_defaults;
$instance->apply_netgear_defaults (
prompt => '/rxp/',
cmd_remove_mode => 0
);
%Net::Telnet::Netgear::NETGEAR_DEFAULTS = (exit_on_destroy => 1);
$instance->apply_netgear_defaults;
Applies the values specified in the hash %Net::Telnet::Netgear::NETGEAR_DEFAULTS
. If any
argument is specified, it is temporarily added to the hash.
See "DEFAULT VALUES USING %NETGEAR_DEFAULTS".
my $current_value = $instance->exit_on_destroy;
# Set exit_on_destroy to 1
my $old_value = $instance->exit_on_destroy (1);
Gets or sets the value of the boolean flag exit_on_destroy
, which causes the module to send
the exit
shell command before being destroyed. This is to avoid ghost processes when closing
a Telnet connection without killing the shell first.
my $current_value = $instance->packet;
# Set the content of the packet to '...'
my $old_value = $instance->packet ('...');
Gets or sets the value of the packet as a string. This is basically equivalent to the
packet_content
constructor parameter.
Note that objects cannot be used - you have to call "get_packet" in Net::Telnet::Netgear::Packet before passing the value to this method.
my $current_value = $instance->packet_delay;
# Set packet_delay to .75 seconds
my $old_value = $instance->packet_delay (.75);
Gets or sets the amount of time, in seconds, to wait after sending the packet.
my $current_value = $instance->packet_send_mode;
# Set packet_send_mode to 'udp'
my $old_value = $instance->packet_send_mode ('udp');
Gets or sets the protocol used to send the packet, between tcp
, udp
and auto
.
If it is auto
, then the module will try to guess the correct protocol to use. More specifically,
if the initial open
performed on the specified host
and port
fails, the packet is sent
using UDP (and then the connection is reopened). Otherwise, if the open
succeeds but it's
impossible to read within the "packet_wait_timeout", the packet is sent using TCP.
If it is tcp
, the packet is sent using TCP.
If it is udp
, the packet is sent using UDP. Note that in this case the packet is always sent
before an open
call.
NOTE: Generally, specifying the protocol instead of using auto
is faster, especially when
the packet has to be sent using UDP (due to the additional connection that has to be made).
my $current_value = $instance->packet_wait_timeout;
# Set packet_wait_timeout to 1.25
my $old_value = $instance->packet_wait_timeout (1.25);
Gets or sets the the amount of time, in seconds, to wait for a response from the server before sending the packet.
Only effective when the packet is sent using TCP.
When you open a connection with Net::Telnet::Netgear (either with the (fh)open
methods
inherited from Net::Telnet or by specifying the host
constructor parameter), the following
actions are performed depending on the value of "packet_send_mode".
NOTE: when fhopen
is used, "socket" refers to the filehandle.
-
"auto"
This is the default. First, Net::Telnet tries to open the socket. If it succeeds, then it's assumed that the server may want a TCP packet. To check if the server actually needs it, a "select" in perlfunc call is performed on the socket to determine if data is available to read. If data is available, then nothing is done. Otherwise, the packet is sent using TCP and then the socket is re-opened.
If the initial
open
didn't succeed, then the server is not listening on the port. It's assumed that the server wants an UDP packet, and it is immediately sent. The socket is re-opened, and if it fails again the error is propagated. -
"tcp"
The actions specified in the first case apply, except that if the initial
open
goes wrong the error is immediately propagated. -
"udp"
The packet is immediately sent before the
open
performed by Net::Telnet. If it fails, the error is immediately propagated.
As an added feature, it's possible to enable a set of options suitable for Netgear routers.
This is possible with the hash %Net::Telnet::Netgear::NETGEAR_DEFAULTS
, which contains a list
of methods to be called on the current instance along with their parameters. This is done by the
method "apply_netgear_defaults".
The current version specifies the following list of default values:
method value
----------------- -----------
cmd_remove_mode 1
exit_on_destroy 1
prompt '/.* # $/'
waitfor '/.* # $/'
It is possible to edit this list either by interacting directly with it:
$Net::Telnet::Netgear::NETGEAR_DEFAULTS{some_option} = 'some_value';
delete $Net::Telnet::Netgear::NETGEAR_DEFAULTS{some_option};
%Net::Telnet::Netgear::NETGEAR_DEFAULTS = (
option1 => 'value1',
option2 => 'value2'
);
Or you can supply additional parameters to "apply_netgear_defaults", which will be temporarily
added to the list. Note that user-specified values have priority over the ones in the hash, and
if you specify the value of an option as undef
, it won't be set at all.
# cmd_remove_mode is set to 0 instead of 1, along with all the other
# default values
$instance->apply_netgear_defaults (cmd_remove_mode => 0);
# do not set cmd_remove_mode at all, but apply every other default
$instance->apply_netgear_defaults (cmd_remove_mode => undef);
# the standard list of default values is applied plus 'some_option'
$instance->apply_netgear_defaults (some_option => 'some_value');
# equivalent to:
{
local %Net::Telnet::Netgear::NETGEAR_DEFAULTS = (
%Net::Telnet::Netgear::NETGEAR_DEFAULTS,
some_option => 'some_value'
);
$instance->apply_netgear_defaults;
}
Net::Telnet::Netgear
uses a timeout to determine if it should send the packet (using TCP).
But what's the magic behind this mysterious decimal number?
Timeouts, under normal conditions, are implemented using the "select" in perlfunc function (which calls the select(2) syscall). This magic function is awesome, and it works beautifully.
It would be great if the story ended here, but happy endings are pretty rare in real life.
select
works basically everywhere when dealing with network sockets, but it doesn't work on
certain systems when dealing with generic filehandles (Win32, I'm looking at you!).
Net::Telnet can make Telnet work on arbitrary filehandles (thanks to "fhopen" in Net::Telnet),
but that means that select
may not be always available. This is a problem, and you can specify
what to do in this case with the boolean variable
$Net::Telnet::Netgear::DIE_ON_SELECT_UNAVAILABLE
.
If this variable is false (the default), then if select
is not available the module will simply
never send packets using TCP and emit a warning. This may not be always desiderable.
If this variable is true, then if select
is unavailable the module will call
Net::Telnet->error
which, when errmode
is the default, stops the execution of the script.
NOTE: If "packet_send_mode" is set to udp
, then select
is never called, thus
$Net::Telnet::Netgear::DIE_ON_SELECT_UNAVAILABLE
won't have any effect even if select
is
unavailable.
An open
call may require serious amounts of time, depending on the "packet_send_mode" and
"packet_wait_timeout".
Particularly, if no packet has to be sent, then tcp
or auto
are the fastest. Otherwise,
udp
is the fastest (because there are no timeouts, and the packet is immediately sent).
auto
is the slowest when the router requires the packet on UDP, because a connection is
attempted on the TCP port, while it has the same speed of tcp
when the packet is expected on
TCP.
Net::Telnet, Net::Telnet::Netgear::Packet, http://wiki.openwrt.org/toh/netgear/telnet.console, https://github.com/Robertof/perl-net-telnet-netgear
Roberto Frenna (robertof AT cpan DOT org)
Thanks to Derreck "insanid" for the precious contribution to the OpenWRT wiki page, and for helping me to discovery the mistery behind the "strange" packets generated with long passwords.
Thanks to the authors of Mojolicious for inspiration about the license and the documentation.
Copyright (C) 2014-2015, Roberto Frenna.
This program is free software, you can redistribute it and/or modify it under the terms of the Artistic License version 2.0.