The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

Net::Telnet::Netgear - Generate and send Netgear Telnet-enable packets through Net::Telnet

SYNOPSIS

    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 ('...');

DESCRIPTION

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.

METHODS

Net::Telnet::Netgear inherits all methods from Net::Telnet and implements the following new ones.

new

    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 username admin 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 be 0.

  • 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.

apply_netgear_defaults

    $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".

exit_on_destroy

    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.

packet

    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.

packet_delay

    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.

packet_send_mode

    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).

packet_wait_timeout

    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.

IMPLEMENTATION DETAILS

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.

DEFAULT VALUES USING %NETGEAR_DEFAULTS

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;
    }

THE MAGIC BEHIND TIMEOUTS

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.

CAVEATS

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.

SEE ALSO

Net::Telnet, Net::Telnet::Netgear::Packet, http://wiki.openwrt.org/toh/netgear/telnet.console, https://github.com/Robertof/perl-net-telnet-netgear

AUTHOR

Roberto Frenna (robertof AT cpan DOT org)

THANKS

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.

LICENSE

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.