aboutsummaryrefslogtreecommitdiff
path: root/check_speedtest_servers
blob: 62c279bb3c24685eb0f2037d3248db0140cbfade (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
#!/usr/bin/env perl

#
# Check an Ookla Speedtest server with a specified URL and/or host is present
# on the list of servers.
#
# Author: Tom Ryder <tom@sanctum.geek.nz>
# Copyright: 2018 Tom Ryder
#
package main;

# Force me to write this properly
use strict;
use warnings;
use utf8;

# Require at least this Perl version
# Should work even on very old Perls
use 5.006;

# Import required modules
use English '-no_match_vars';
use LWP::UserAgent ();
use Monitoring::Plugin qw(%ERRORS);
use XML::LibXML;

# Decree package version
our $VERSION = '0.04';

# Add description and license package variables
our $DESCRIPTION = <<'EOF';
This plugin retrieves the list of speedtest servers from speedtest.net and
checks for the presence of at least one server with the given host and/or URL.
EOF
our $LICENSE = <<'EOF';
This plugin is distributed under an MIT license. See LICENSE, or visit
<https://opensource.org/licenses/MIT>. Thanks to Inspire Net Ltd for allowing
this open-source fork.
EOF

# Define custom options
our @OPTS = (
    {
        spec  => 'host|h=s',
        label => 'HOSTNAME:PORT',
        help  => 'Hostname:port pair to find in list, usually *:8080',
    },
    {
        spec  => 'url|u=s',
        label => 'URL',
        help  => 'URL to find in list, usually ends in /upload.php',
    },
);

# URL from which the server list should be retrieved
our $SERVERS_LIST_URL = 'https://www.speedtest.net/speedtest-servers.php';

# Build Monitoring::Plugin object
my $mp = Monitoring::Plugin->new(
    usage   => 'Usage: %s --host|-H HOSTNAME:PORT --url|-u URL',
    version => $VERSION,
    blurb   => $DESCRIPTION,
    license => $LICENSE,
) or die "Failed plugin construct\n";

# Anything that dies in here will raise ->plugin_die()
eval {

    # Define and parse custom options
    for my $opt (@OPTS) {
        $mp->add_arg( %{$opt} );
    }
    $mp->getopts();

    # At least one of --host and --url must be specified
    length $mp->opts->host
      or length $mp->opts->url
      or die "One or both --host or --url must be specified\n";

    # Build a user agent that accepts only XML
    my $ua = LWP::UserAgent->new();

    # Attempt to retrieve the server list
    my $headers = HTTP::Headers->new(
        'Accept'          => 'application/xml;text/xml',
        'Accept-Encoding' => scalar HTTP::Message::decodable(),
    );
    my $request = HTTP::Request->new( 'GET', $SERVERS_LIST_URL, $headers );
    my $response = $ua->request($request);
    $response->is_success
      or die "$response->status_line\n";

    # Parse the server list as an XML document
    my $lxml = XML::LibXML->new();
    my $doc = $lxml->load_xml( string => $response->decoded_content )
      or die "Failed to parse response XML\n";

    # Build an XPath query object
    my $xpc = XML::LibXML::XPathContext->new($doc)
      or die "Failed to build XPath query object on response XML\n";

    # Build a query depending on which options we were provided
    ## no critic (RequireInterpolationOfMetachars)
    my $query = '/settings/servers/server';
    if ( $mp->opts->url ) {
        $query .= sprintf q{[@url='%s']}, $mp->opts->url;
    }
    if ( $mp->opts->host ) {
        $query .= sprintf q{[@host='%s']}, $mp->opts->host;
    }

    # Check that we have at least one matching server
    my @servers = $xpc->findnodes($query);
    my $code    = $mp->check_threshold(
        check    => scalar @servers,
        critical => '1:',
    );
    my $message = sprintf '%u matching servers', scalar @servers;
    $mp->add_message( $code, $message );

    # Add OK-level messages showing the conditions we applied
    # If we find an actual problem, "OK" will get replaced by ->check_messages()
    if ( $mp->opts->host ) {
        $mp->add_message( $ERRORS{OK}, sprintf 'host=%s', $mp->opts->host );
    }
    if ( $mp->opts->url ) {
        $mp->add_message( $ERRORS{OK}, sprintf 'url=%s', $mp->opts->url );
    }

    # Exit with determined code and messages
    $mp->plugin_exit(
        $mp->check_messages(
            join     => q{, },
            join_all => q{, },
        ),
    );

} or $mp->plugin_die($EVAL_ERROR);