Hotel hotspot hijinks
Tags: perl
Published: 2024-04-25 15:12:48

---
title: Hotel hotspot hijinks
published: true
date: 2024-03-20 23:00:00 UTC
tags: Perl
canonical_url: https://peateasea.de/hotel-hotspot-hijinks/
cover_image: https://peateasea.de/assets/images/hotel-hotspot-stock-login-page.png
---

*Originally published on [peateasea.de](https://peateasea.de/hotel-hotspot-hijinks/).*

Ever been staying at a hotel and gotten annoyed that you always have to open a browser to log in for wireless access? Yup, me too. A recent instance was particularly frustrating and I had to pull out my favourite [Swiss Army chainsaw](https://www.perl.com/pub/2000/10/begperl1.html/) in order to make my life a bit easier.

## The situation

So, the background story is that I was staying at a hotel in the mountains for a few days. As is the fortunate case these days<sup id="fnref:the-good-old-days" role="doc-noteref"><a href="#fn:the-good-old-days" rel="footnote">1</a></sup>, the hotel had wireless access. The weird part, though, was that each room had a separate username and password. “Fair enough”, I thought and promptly opened my laptop and then Firefox to enter my login data to get the dearly-awaited connectivity. Using Firefox (or any other browser for that matter) was necessary because the login page was accessed via a [captive portal](https://en.wikipedia.org/wiki/Captive_portal). That’s the thing you get directed through when you see a login banner like this pop up in your browser:

![Firefox captive portal login banner](https://peateasea.de/assets/images/firefox-captive-portal-login-banner.png)

That’s fine, I thought, and went merrily on with my day.

## The problem

The problem started the following day. After getting up and waking up my laptop, I wasn’t able to read my email<sup id="fnref:mutt-for-email" role="doc-noteref"><a href="#fn:mutt-for-email" rel="footnote">2</a></sup>, or read chat on [irc](https://libera.chat/)<sup id="fnref:irssi-for-irc" role="doc-noteref"><a href="#fn:irssi-for-irc" rel="footnote">3</a></sup>, see my messages via [Signal](https://signal.org/), or use the internet at all<sup id="fnref:live-on-the-terminal" role="doc-noteref"><a href="#fn:live-on-the-terminal" rel="footnote">4</a></sup>.

Also, `ping` greeted me with `Destination Net Prohibited`:

```
$ ping www.heise.de
PING www.heise.de (193.99.144.85) 56(84) bytes of data.
From logout.hotspot.lan (192.168.168.1) icmp_seq=1 Destination Net Prohibited
From logout.hotspot.lan (192.168.168.1) icmp_seq=2 Destination Net Prohibited
From logout.hotspot.lan (192.168.168.1) icmp_seq=3 Destination Net Prohibited
^C
--- www.heise.de ping statistics ---
3 packets transmitted, 0 received, +3 errors, 100% packet loss, time 2002ms
```

Obviously, I wasn’t online.

That’s when I noticed the Firefox captive portal login banner (see image above) again. Oh, I have to log in again, that’s weird. Upon clicking on the “Open Network Login Page” button, I was logged in automatically. No need to enter the login details again. That’s also weird, I thought, because if the login is automatic, why do I have to visit the login page again at all?

I put my laptop to sleep to go for a walk around the village, get some groceries, and enjoy the mountain air<sup id="fnref:mountain-air" role="doc-noteref"><a href="#fn:mountain-air" rel="footnote">5</a></sup>. Upon my return, I had to log in _again_ to get wireless access. I was slowly starting to get a [bit miffed](https://davywavy.livejournal.com/212580.html). My guess is that the MAC address from the relevant end-user device is removed from the access list fairly quickly, perhaps on the order of an hour or two<sup id="fnref:mac-address-release" role="doc-noteref"><a href="#fn:mac-address-release" rel="footnote">6</a></sup>, and thus network connectivity is cut rather promptly.

One issue that made my situation even worse is that I often have *several* browser windows open at the same time; usually because I have several trains of thought on the go at once and each window contains information relevant to each train of thought. The thing is, only *one* of the browser windows actually shows the (automatically appearing) captive portal login banner. Finding the window with the banner was rather time consuming.

Ok, this was starting to get silly and a bit annoying. Time to automate the annoyance away. [`WWW::Mechanize`](https://metacpan.org/pod/WWW::Mechanize) to the rescue!

![WWW::Mechanize as a comic book super hero; generated by DALL-E](https://peateasea.de/assets/images/www-mechanize-as-superhero.png)
_WWW::Mechanize as a comic book super hero; generated by DALL-E._

## The solution

Why choose `WWW::Mechanize`? Well, I’ve got experience with it (I used to use a similar process to automatically log in to the [ICE train](https://en.wikipedia.org/wiki/Intercity_Express) in Germany when I used to commute to work before [the pandemic](https://en.wikipedia.org/wiki/COVID-19_pandemic)), I know I can use it to [submit data into simple HTML forms](https://metacpan.org/pod/WWW::Mechanize#$mech-%3Esubmit_form(-...-)), and [Perl](https://www.perl.org/) is my go-to language for this kind of automation.

So, how to get started with automating the login process? The simple solution: quit Firefox so that all browser windows are closed, put the computer to sleep and then go for a walk for a couple of hours.

Upon my return, I just needed to use a combination of `perl -de0` to start a [REPL](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop)-like environment to play around in and `perldoc` to read the [extensive `WWW::Mechanize` documentation](https://metacpan.org/pod/WWW::Mechanize).

The first attempt at trying to trigger a connection to the captive portal didn’t go well:

```
└> perl -de0

Loading DB routines from perl5db.pl version 1.55
Editor support available.

Enter h or 'h h' for help, or 'man perldebug' for more help.

main::(-e:1): 0
  DB<1> use WWW::Mechanize;

  DB<2> $mech = WWW::Mechanize->new;

  DB<3> $mech->get('https://google.com');

Error GETing https://google.com: Can't connect to google.com:443 (SSL
connect attempt failed) at (eval
22)[/home/cochrane/perl5/perlbrew/perls/perl-5.30.1/lib/5.30.1/perl5db.pl:738]
line 2.
```

Ok, so we need to use HTTP and avoid HTTPS. Good to know.

Just using HTTP worked much better:

```
  DB<4> x $mech->get('http://google.com')
0 HTTP::Response=HASH(0x55a95f5048c0)
<snip>
lots of details; you really don't want to see this
</snip>
```

That’s what we like to see! We’re at least getting stuff back now. Having a look at the page’s title, we get:

```
  DB<5> x $mech->title();
0 'myadvise hotspot > login'
```

Yup, that’s a login page. Dumping the page’s content with

```
  DB<5> x $mech->content();
<snip>
lots of HTML content
</snip>
```

we get to see what we’ve got to play with. The main things to note about the content (which I’m not showing because it’s too much detail and I want to protect the innocent) are:

- we have a form called `login`

```html
<form name="login" action="http://login.hotspot.lan/login" method="post">
```

- we have a username field with the name `username`

```html
<input style="width: 80px" name="username" type="text" value=""/>
```

- and we have a password field with the name `password`

```html
<input style="width: 80px" name="password" type="password"/>
```

This gives us enough information to be able to submit the form using the relevant login data.

Aside: interestingly enough, the fields are in English even though the site is a German one. I guess standardising the fields on English can be useful when programming.

To submit the form, we use `WWW::Mechanize`’s [`submit_form()`](https://metacpan.org/pod/WWW::Mechanize#$mech-%3Esubmit_form(-...-)) method (the call to which I’ve formatted nicely here to make things easier to read):

```perl
$mech->submit_form(
    form_name => 'login',
    fields => {
        username => 'username-for-room',
        password => 'password-for-room',
    }
);
```

We can check if the form submission was successful by asking the [`HTTP::Response`](https://metacpan.org/pod/HTTP::Response) if things went well:

```
  DB<7> x $mech->res->is_success;
0 1
```

Looking good so far. Let’s see if `ping` works as expected

```
$ ping www.heise.de
PING www.heise.de (193.99.144.85) 56(84) bytes of data.
64 bytes from www.heise.de (193.99.144.85): icmp_seq=1 ttl=247 time=20.6 ms
64 bytes from www.heise.de (193.99.144.85): icmp_seq=2 ttl=247 time=13.9 ms
^C
--- www.heise.de ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1000ms
rtt min/avg/max/mdev = 13.941/17.281/20.621/3.340 ms
```

Yes! In other words: we’re in! [That was easy](https://www.youtube.com/watch?v=kCE0v-SK5_c) :relaxed:.

Putting everything together (and cleaning up the code a bit), I ended up with this:

```perl
use strict;
use warnings;

use WWW::Mechanize;

my $mech = WWW::Mechanize->new;
$mech->get('http://google.com');

# check that we have the login page and not Google or something else
# i.e. we're not logged in
if ($mech->title() =~ 'login') {
    $mech->submit_form(
        form_name => 'login',
        fields => {
            username => '<username-for-room>',
            password => '<password-for-room>'
        }
    );
    if ($mech->res->is_success) {
        print "Login successful\n";
    }
    else {
        print "Login failed\n";
    }
}
else {
    print "Already logged in\n";
}
```

Now I can just run the script every time I wake my laptop back up and I’m back online. Yay!

From a security perspective it’s a bit weird that the username and password are obviously tied to the room number. In other words, I could probably just use the neighbouring room’s account just as easily (I guess: I couldn’t be bothered checking in the end).

## The conclusion

Well, this script certainly saved me some time and hassle when waking up my laptop from the suspend state. Also, it was fun working out what pieces of the puzzle were necessary in order to build a solution. Perl saves the day<sup id="fnref:perl-saves-the-day" role="doc-noteref"><a href="#fn:perl-saves-the-day" rel="footnote">7</a></sup> again!

1. Remember when finding a hotel with _any_ wireless connectivity was a total mission? Sort of like the days when finding a power outlet at an airport to charge a laptop was really difficult and one ended up sitting on the floor next to a utility room where the cleaning staff would normally plug in a vacuum cleaner. Ah, those were the days :wink:. Put another way: humanity has come a looong way. [↩](#fnref:the-good-old-days)

2. I use [mutt](http://www.mutt.org/); it’s fast and one only needs to use text for email. Right? Right? [↩](#fnref:mutt-for-email)

3. I also use [irssi](https://irssi.org/) for IRC. Look, I’ve been around a while, ok? [↩](#fnref:irssi-for-irc)

4. I’m one of those geeks who live on the terminal, hence I tend to use a lot of terminal-based tools to get things done. [↩](#fnref:live-on-the-terminal)

5. I don’t mean this ironically: because of the forests and the distance away from any kind of metropolis, the air is much fresher. Ever notice that the air in European cities is just awful? [↩](#fnref:mountain-air)

6. Later, I’d tried putting my computer to sleep and then waking it up a few minutes later. The connection was still alive, so my best guess is that the timeout to keep connections alive and keep a MAC address registered is on the order of hours, but not more than two or three hours, because even such short periods of inactivity required login again. A later test showed that the timeout was after one hour. [↩](#fnref:mac-address-release)

7. Well, it saved the week really. [↩](#fnref:perl-saves-the-day)