summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLubomir Rintel <lkundrak@v3.sk>2018-06-09 12:06:32 +0200
committerLubomir Rintel <lkundrak@v3.sk>2018-06-11 13:48:30 +0200
commitc4d800c09c308fd8ccc697f154cf10dbe9ed4374 (patch)
tree90233ece8bdec609d39d25a4a913b01d19e86d8f
parent5f94476b2664b1f3cfbfae929e24746282bd63fb (diff)
downloadNetworkManager-lr/modemu.tar.gz
contrib: add a serial modem emulatorlr/modemu
Useful for quickly testing the ModemManager integration.
-rwxr-xr-xcontrib/test/modemu.pl268
1 files changed, 268 insertions, 0 deletions
diff --git a/contrib/test/modemu.pl b/contrib/test/modemu.pl
new file mode 100755
index 0000000000..26182ffdc1
--- /dev/null
+++ b/contrib/test/modemu.pl
@@ -0,0 +1,268 @@
+#!/usr/bin/env perl
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Copyright 2018 Red Hat, Inc.
+
+# $ perldoc modemu.pl for eye-pleasing view of the manual:
+
+=head1 NAME
+
+modemu.pl - emulate a serial modem
+
+=head1 SYNOPSIS
+
+modemu.pl [<name>] [-- <pppd> ...]
+
+=head1 DESCRIPTION
+
+B<modemu.pl> opens a PTY, links the slave side to F</dev> and announces a
+fake kobject via netlink as if it were a real serial device, so that
+ModemManager picks it up.
+
+Then it answers to a very basic subset of AT commands, sufficient making
+ModemManager recognize it as a 3GPP capable modem registered to a network.
+
+Upon receiving the dial (ATD) command, it spawns C<pppd> so that
+NetworkManager can establish a connection.
+
+B<modemu.pl> needs superuser privileges to be able to announce a kobject
+and create a F</dev> node.
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<< <name> >>
+
+Create a modem of given name. Links it to F<< /dev/<name> >>.
+
+Defaults to I<modemu>.
+
+=item B<< <pppd> >>
+
+Specifies extra arguments to be prepended before C<pppd> to the default
+set of I<nodetach notty local logfd 2 nopersist>.
+
+Defaults to I<pppd dump debug 172.31.82.1:172.31.82.2>.
+
+=back
+
+=cut
+
+use strict;
+use warnings;
+
+use Errno;
+use Socket;
+use IO::Pty;
+use IO::Handle;
+
+use constant AF_NETLINK => 16;
+use constant NETLINK_KOBJECT_UEVENT => 15;
+
+# This allows us to use buffered read for lines from ModemManager
+# despite not ending with \n
+IO::Handle->input_record_separator ("\r");
+
+# Parse command line arguments
+my $name;
+my @pppd = qw/pppd dump debug 172.31.82.1:172.31.82.2/;
+while (@ARGV) {
+ $_ = shift @ARGV;
+ if ($_ eq '--') {
+ @pppd = @ARGV;
+ last;
+ } else {
+ die "Extra argument: '$_'" if $name;
+ $name = $_;
+ }
+};
+$name ||= 'modemu';
+
+socket my $fd, AF_NETLINK, SOCK_RAW, NETLINK_KOBJECT_UEVENT
+ or die "Can't create a netlink socket: $!";
+
+sub send_netlink
+{
+ my %props = @_;
+ my $props = join '', map { $_, '=', $props{$_}, "\0" } keys %props;
+
+ my $head = pack 'a8NLLLLLLL',
+ # signature + magic
+ 'libudev',
+ 0xfeedcafe,
+
+ # 40 octets is the length of this header
+ 40, 40, 40 + length ($props),
+
+ # SUBSYS=tty hash. Precomputed somehow.
+ 0xc890fa8a,
+ 0x00000000,
+ 0x00040002,
+ 0x00008010;
+
+ $! = undef;
+ send $fd, "$head$props", 0, pack 'SSLL', AF_NETLINK, 0, 0, 0x0002;
+ # RHEL 7 kernel responds ECONNREFUSED even thoguh the sendto succeeded. Weird.
+ die "Can't send a netlink message: $!" if $! and not $!{ECONNREFUSED};
+}
+
+my %props = (
+ DEVPATH => "/devices/pci0000:00/0000:00:00.0/$name",
+ SUBSYSTEM => 'tty',
+ DEVNAME => "/dev/$name",
+
+ # Whitelisting that works for both ModemManager 1.6 and 1.8
+ ID_MM_CANDIDATE => '1',
+ ID_MM_DEVICE_PROCESS => '1',
+);
+
+sub cleanup
+{
+ unlink "/dev/$name";
+ send_netlink (ACTION => 'remove', %props) if $fd;
+}
+
+# Ensure we clean up before and after.
+END { cleanup };
+$SIG{INT} = sub { cleanup; die };
+cleanup;
+
+my $pty = new IO::Pty;
+my $ptyname = ttyname $pty;
+symlink $ptyname, "/dev/$name" or die "Can't create /dev/$name: $!";
+send_netlink (ACTION => 'add', %props);
+
+while (<$pty>) {
+ chomp;
+
+ if (/^AT$/ or /^ATE0$/ or /^ATV1$/ or /^AT\+CMEE=1$/ or /^ATX4$/ or /^AT&C1$/ or /^ATZ$/) {
+ # Standard Hayes commands that are basically used to
+ # ensure the modem is in a known state. Accept them all.
+ print $pty "\r\n";
+ print $pty "OK\r\n";
+
+ } elsif (/^AT\+CPIN\?$/) {
+ # PIN unlocked. Required.
+ print $pty "\r\n";
+ print $pty "+CPIN:READY\r\n";
+ print $pty "\r\n";
+ print $pty "OK\r\n";
+
+ } elsif (/^AT\+COPS=0$/) {
+ # Select access technology (we just accept 0=automatic)
+ print $pty "\r\n";
+ print $pty "OK\r\n";
+
+ } elsif (/^AT\+CGREG\?$/) {
+ # 3GPP Registration status.
+ print $pty "\r\n";
+ print $pty "+CGREG: 0,1\r\n";
+ print $pty "\r\n";
+ print $pty "OK\r\n";
+
+ } elsif (/^AT\+CGDCONT=\?$/) {
+ # Get supported PDP contexts
+ print $pty "\r\n";
+ print $pty "+CGDCONT: (1-10),(\"IP\"),,,(0-1),(0-1)\r\n";
+ print $pty "+CGDCONT: (1-10),(\"IPV6\"),,,(0-1),(0-1)\r\n";
+ print $pty "OK\r\n";
+
+ } elsif (/^AT\+CGACT=0,1$/) {
+ # Activate a PDP context
+ print $pty "\r\n";
+ print $pty "OK\r\n";
+
+ } elsif (/^AT\+CGDCONT=1,"(.*)","(.*)"$/) {
+ # Set PDP context. We accept any.
+ print $pty "\r\n";
+ print $pty "OK\r\n";
+
+ } elsif (/^ATD/) {
+ print $pty "\r\n";
+ print $pty "CONNECT 28800000\r\n";
+
+ my $ppp = fork;
+ die "Can't fork: $!" unless defined $ppp;
+ if ($ppp == 0) {
+ close STDIN;
+ close STDOUT;
+ open STDIN, '<&', $pty or die "Can't dup pty to a pppd stdin: $!";
+ open STDOUT, '>&', $pty or die "Can't dup pty to a pppd stdout: $!";
+ close $pty;
+ exec @pppd, qw/nodetach notty local logfd 2 nopersist/;
+ die "Can't exec pppd: $!";
+ }
+ waitpid $ppp, 0;
+ } else {
+ print $pty "\r\n";
+ print $pty "ERROR\r\n";
+ }
+}
+
+=head1 EXAMPLES
+
+=over
+
+=item B<modemu.pl>
+
+Just create a modem named I<modemu>, with the default PPP arguments.
+
+=item B<modemu.pl ttyS666>
+
+Same as above, just name the modem I<ttyS666>.
+
+=item B<modemu.pl -- unshare --net pppd 172.31.82.1:172.31.82.2>
+
+Avoid polluting the namespace with the modem end of PPP connection.
+
+=item B<modemu.pl -- pppd 10.0.0.1:10.0.0.2>
+
+Override the C<pppd> parameters: no debug logging and different set of
+addresses.
+
+=item B<modemu.pl mymodem -- pppd 10.0.0.1:10.0.0.2>
+
+Same as above, with a modem name different from default.
+
+=back
+
+=head1 BUGS
+
+Only works on machines with a PCI bus. ModemManager is picky about platform
+devices and accepts PCI and USB busses easily. Which is why pretent to have
+our tty on the PCI root device.
+
+Terminates after a single PPP session. C<pppd> seems to hang up the PTY.
+
+=head1 SEE ALSO
+
+L<ModemManager(8)>, L<pppd(8)>
+
+=head1 COPYRIGHT
+
+Copyright 2018 Lubomir Rintel
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+=head1 AUTHOR
+
+Lubomir Rintel C<lkundrak@v3.sk>
+
+=cut