GPRSD = GPRS keepalive daemon for Windows (XP)

GPRS uplink down? Shut down RAS session and redial. Rinse, repeat...
by: Frank Rysanek [rysanek AT fccps DOT cz]

Download

There's a ZIP file for download. It contains all the source code and a pre-built binary.

Install

Unpack GPRSD.EXE into some directory. Add a gprs.ini (rename one of the two sample files and edit). You may need the default simple.inf if that's what you decide to use for your serial modem.

With a modem that uses the Windows "unimodem" driver on top of an opaque serial port, either use the included simple.inf, or provide your own INF file. In such cases, it's better NOT to have a modem "driver" installed. Gprsd will install it for you.

For USB GPRS/3G/4G modems that still have an AT command interface and PPP, note that the COM port is a "side effect", just one of several interfaces presented by the USB device driver. For USB modems, the responsibility is yours, to install the USB modem driver in the usual way (provide a driver to Windows when they ask for one) and gprsd.exe will try to make use of it.
For USB modems, gprsd.exe doesn't even need an INF file. But, you definitely need a custom gprs.ini, where you enter your modem's COM port and a USB device ID string.

In general, just run GPRSD.EXE - it will check what's in the system, possibly install its own modem INF file, and create an RAS connection profile ("phonebook entry").

For repeated deployments, you will want to provide your own gprs.ini. If you don't have one, just run gprsd.exe - it will create a default INI file, which you can modify. Note that there are two sample gprs.ini files included in the distribution ZIP file.

What it does

The classic GPRS modems speak PPP (emulated locally by the modem) to their local DTE = host computer. The PPP is handled by the standard Windows NT+ Remote Access Service (RAS). What RAS cannot do is keep an eye on the GPRS uplink and redial if necessary. RAS alone also isn't very good at inserting a SIM PIN, and generally the Windows modem framework doesn't understand GPRS extensions to the common Hayes-compatible AT command set.
Hence the inspiration for an additional GPRS-aware dialer / connection manager / whatever.

GPRSD does the following:

  1. checks for a COM port
  2. installs a GPRS modem (via an INF file) if missing
  3. patches an AT+CGDCONT=1,"IP","your.apn.name" into the INF file (for serial modems) or straight into the registry (for USB modems)
  4. creates a "phonebook entry" = RAS "connection" if missing
  5. checks for a modem on the COM port and inits the modem (SIM PIN), by direct access to COM port (= before RAS is asked to dial out)
  6. dials the phonebook entry = opens a RAS session
  7. keeps pinging a configured target host. When severeal pings get lost, it hangs up the RAS session and tries to reconnect, starting from the modem init (point 5. above).

See the source code for ugly details.

Configuration - GPRS.INI

On the first run, GPRSD.EXE creates an INI file with some defaults. You can modify the values in this file and copy it to other machines (you need not generate this file on each and every machine.) Configurable options follow:

COM_PORT=string
default: COM1

MODEM_HWID=string
For serial modems, this can be pretty much anything. The "simple.inf" uses the string "Maestro".
default: Maestro
For USB modems, you need to enter you modem's USB ID string. See the sample gprs.ini.usb for an inspiration - you need to find a similar string in the INF file of your modem or in the "device properties" of the USB device ("hardware ID's").

MODEM=string
Modem entry in the Windows device management.
The simple.inf conains just "Maestro".
default: Maestro
USB modems have their own respective names. See the control panel "Modems" for a list of what you have in the system.

INF_FILE=string
Points to the modem "driver" (INF file).
default: simple.inf
gprsd.exe actually doesn't need an INF file for USB modems. (You just need to provide the USB ID string, which happens to be mentioned in a compatible INF file.)

PHONEBOOK_ENTRY=string
default: GPRS

PIN=string
SIM pin, only used if the modem asks for it

APN=string
APN name. Even "public internet access" uses some APN name, such as "internet.t-mobile.cz". Note that this is not (necessarily) a DNS name, it's just an APN name string chosen by the GPRS operator.

AUTH=string
default: ANY
possible values: PAP, CHAP, MSCHAP, MSCHAP2 and more...
Authentication mechanism. Some APN setups need e.g. PAP to work correctly against the remote auth back-end configured at the GGSN. APN auth can be delegated to customer-side radius servers or some such.
Note: it seems that the XP SP3 RAS API is broken, in that it cannot be forced to programmatically configure the set of auth protocols desired. GPRSD.EXE tries to provide the respective set of RASEO_requireXYZ flags OR'ed together in dwfOptions, OR'ed with RASEO_Custom, but the phonebook entry ends up using just the "typical" security settings. It's a bug in RasSetEntryProperties() or what. = if necessary, you need to edit the phonebook entry created by gprsd.exe by hand in the "connection properties" - that way your options will stick.

GPRS_NUM=string
The "phone number" to dial out, in order to get the GPRS service.
default: *99***1#
Note that the "1" before the hash character refers to CGDCONT profile number 1. There can be several AT+CGDCONT profiles (APN names) stored in the modem, and you can refer to them by the trailing digit in the "GPRS phone number"...

GPRS_USERNAME=string
Leave empty if unused

GPRS_PASSWORD=string
Leave empty if unused

PING_HOSTNAME=string
A DNS name or an IP address of the required Ping target machine
default: www.google.com

PING_TIMEOUT=number in seconds
How long to wait for a response to each PING request (ICMP echo request).
default: 5

PING_RETRIES=number
How many failed pings must fail in a row to initiate a redial.
default: 5

PING_OK_PERIOD=number in seconds
When a ping response arrived just fine, how long to sleep before sending another PING request. The default is somewhat long, to avoid incredible volume-based fees to the GPRS operator.
default: 300

PING_RETRY_PERIOD=number in seconds
When a ping timed out, how long to sleep before sending another PING request. It might look like a good idea to check again relatively soon. Beware, you may want to set this longer - to avoid large fees when it's not the local last mile radio segment that went wrong...
default: 5

RAS_REDIAL_PERIOD=number in seconds
When Ping signals connection down, GPRSD tries to redial immediately. If this immediate redial fails, the next attempt (and any further attempts until success) will be preceded by a configurable delay. This delay should give the modem / GSM network / local PPP stack some time to recover from an error - and should also limit the size of an error log file, if you're trying to capture what's going on.
default: 30

SLEEP_ON_MODEM_INIT_FAILURE=number in seconds
When GPRSD fails to init the modem (no response to AT, modem responds ERROR to AT or some such), GPRSD will try again with a configurable delay. This should limit the size of a log file, if you're trying to capture what's going on.
default: 20

Requirements

Windows XP 32bit (not tested on other versions).
Wavecom/Sierra based GPRS modem (or something compatible). Or maybe pretty much any USB WWAN modem. The program was tested against Wavecom-based modems sold by Maestro Wireless / Fargo Telecom (of TW) and against a Quectel MiniPCI-e 4G modem.

Disclaimer

This software is provided as is. I disclaim any responsibility. Use at your own risk.

Beware: GPRSD uses Ping (ICMP echo + echo reply messages) to keep an eye on your GPRS uplink connectivity. This implies data being transferred, which in turn implies more money charged by the GPRS operator. There's no way around this - you need to transfer data to check the status of your GPRS session, and you'll pay for that. How much, that depends on your negotiated fees.
The PING repetition rate is configurable - you'll find yourself trading off monitoring precision (in time) vs. volume-based fees.

Further background info

Written in MinGW for Win32. The source code should be reasonably portable to other compilers.

Known downsides: it's a fairly crude console app. It doesn't have proper GUI behavior, cannot hide in systray, doesn't alone run as a service (you may try running it via NSSM). It sends progress messages to its stdout - to capture that to a file, just use the > sign. Or download some "tee" variant for Win32, if you want to have a logfile AND see the output in the console window at the same time.

The reconnect is a mess. The RAS can fail to reconnect for no obvious reason, once or twice (even when the GSM network is there and antenna is attached) - and the spectrum of errors returned by RasDial() et al. is staggering. Even the modem can sometimes respond "ERROR" to plain "AT" for no apparent reason. It's not quite possible to distinguish fatal vs. temporary errors. It's also difficult to tell if the blame is with Windows or with the modem/network or whatever.
Therefore, GPRSD just keeps on trying to reconnect. You can even remove the SIM from your modem or turn off the modem, and GPRSD just keeps trying (and resumes successfully as soon as you power the modem back on or re-insert its SIM card).

GPRSD is fairly crude/clumsy in how it handles the connection errors and just keeps trying to redial from scratch. This is deliberate. It would certainly be possible to check for more detailed status flags in the modem, but that might make GPRSD sensitive to differences in firmware versions and modem models, which is NOT desired. Therefore, GPRSD is generic at the cost of being somewhat clumsy.

The source code contains practical examples of how to use C++/WinAPI to programmatically install and launch GPRS connectivity via an RS232-attached GPRS modem. It uses the RAS API and the Setup API. It also demonstrates how to code a simple PING-style program.

Apologies for poor coding style, mixing basic C types with Win32-style "Hungarian notation" etc. - the program is based on snippets taken from various sources (see the Credits). It would sure use some janitoring to get more unified / compact / elegant / extensible... I wish I had the time.

GPRS peculiarities

Monitoring GPRS connectivity is not necessarily an obvious task.

PPP (IPCP) does support keepalives, but these are not much practical use. There's no way to ask the Windows RAS to use them, and even if there was a way, there's no guarantee that the GPRS modem relays them to the "PDP context" (GTP) to really check the GPRS "session status". There are hints in Cisco documentation that GTP can do keepalives, but it's difficult to say if these can be relied on...

Another option would be, if you're running some application-level TCP session, to monitor connectivity via TCP "keepalives". A TCP keepalive is a packet with zero payload, sent merely to request an ACK from the opposite end. Such a packet gets generated by calling the socket API's send() function, and providing it with a buffer with zero length = by submitting to TCP a data chunk with length = 0.
This way, you might combine connectivity monitoring with your payload traffic. Unfortunately, it seems that the GPRS endpoints (certainly the GGSN) manipulate the traffic up to the TCP layer in such a way that TCP ACK's to keepalives are sent, even though the GPRS session has actually died (the MT is temporarily out of reach or some such). GPRS simply tries to buffer as much data as possible and hide retransmissions on the radio segment from TCP/IP. The buffering happens even with PING requests and responses (fortunately, ICMP echo responses are NOT locally fudged).

There are some flags in the GPRS modem that can be used to check the status of a GPRS "network registration" and PDP context. Unfortunately, these are A) unreliable and B) cannot be read by the host computer when PPP is up and running. The modem would be able to temporarily break into command mode (using +++) and then resume, but the Windows RAS is certainly uncapable of that.
Actually USB WWAN modems typically do have an extra AT command interface (the DM virtual COM port) that responds to AT commands even while the modem's own virtual COM port is occupied by PPP. This could be used to watch signal strength at gprsd runtime, maybe even display some info about the network (transport network flavour, ARFCN and whatnot. Note that these "engineering mode" commands are vendor-specific.) Such functionality using a second COM port is not implemented at the moment. It would take some additional effort.

In the end, your only chance to monitor the real-world status of a GPRS session is by classic ICMP PING to some outside host that's known to be available. Either to some server of your own, or to some well-known internet site. You could also ping your local GGSN (your default gateway) if your operator allows this - or some IP address further upstream... or ping multiple addresses and try some "quorum consensus", or try a traceroute and infer status from its output. Nevertheless, any more advanced heuristic approach is complex / problematic. Thus, the GPRSD approaches this in a rather crude way: it pings a single outside host and redials if that's not available. This can lead to false detections of "link down", and it cannot distinguish between a local last-mile outage and other problems further upstream (which won't get solved by a redial). Well that's life.

Credits

The whole code is heavily inspired by several articles from the Code Project website. I've noticed bugs in some of their code samples (or sometimes in MS Windows API behavior), but thanks god for that website, and kudos to all the article authors.
The source code contains specific credits where due.