Inward fax-to-email gateway with Asterisk + IAXmodem/SpanDSP + HylaFAX

Posted in Tips by Alex Balashov on the November 23rd, 2007

I recently deployed Asterisk/IAXmodem/HylaFAX as a fairly high-volume fax-to-email gateway solution. In the process of so doing, I learned that there is a lot of good information out there — in bits and pieces — but not really a good, insightful tutorial to sum it all up conceptually to any degree of satisfying completeness, let alone up-front discussion of some theoretical and methodological questions, caveats, drawbacks and pitfalls that are useful in attempting a real-world deployment in a serious business environment.

It is my intent to provide something reaching in that direction here. I mean that in the most humble way possible; the open-source focused VoIP community could benefit from an involved synthesis of some of the relevant issues. By no means do I command the most comprehensive insights — this is a new project for me, and that’s why I’m eager to share what I’ve learned. Corrections, additions, questions and comments are absolutely welcome - please post comments or drop me a line!

This article does presume some familiarity with Asterisk and IP telephony on a technical level.

By the way, I have seen that packaged, integrated Asterisk GUI solutions such as Trixbox and FreePBX can take care of a lot of this for you automatically. If you like that, by all means, stick with it. Personally, I am one of those Asterisk enthusiasts that prefers the level of control and flexibility that comes with rolling one’s own installation from scratch, writing the configuration manually, and so on. Ironically, I’ve found that it keeps things pretty simple. I’ve seen the immensely complicated and metadata-rich configs that FreePBX & co. put out; colour me several shades of not impressed. Too hard to figure out, modify, customise, or otherwise access the inner workings. But if you don’t really want to roll your own solution, this article is probably not for you.

The Business Case / About Fax-to-Email

If you already know everything about your wants and needs in this area and are just looking for a tutorial on the technical nitty gritty, skip this section.

Believe it or not, much that goes in business these days still relies on analog facsmile technology, especially where signed paperwork, contracts, and financial documents bearing some measure of legally certifiable authenticity are required. If you are in business, chances are that you have a fax machine.

Fax-to-email functionality entails setting up an electronic interface that mimics the behaviour of a conventional fax machine in order to send and receive faxes from conventional fax machines. (If you are primarily receiving documents from electronic / digital sources, chances are you should be using e-mail or FTP for this.) The fax transmission is then converted to an electronic format (such as Adobe PDF) and automatically e-mailed as an attachment to the correct recipient.

This type of interface technology — often known and branded as “electronic fax” or “e-fax” — is about moving forward and embracing the migration to computer-based telephony1 while looking backward in acknowledgment that analog fax is likely to be around for some time yet. Until we come up with better, universal, integrated and legally/regulatorily viable ways of digitally “signing” documents at the very least, and possibly beyond that simply for reasons of lopsided technological development and acceptance, as part of the all-around division of labour in the so-called global economy.

If you are a provider of retail or wholesale end-subscriber telecommunications services, unified messaging, or play in the PSTN origination and termination space, etc. the reasons for why you may want to consider this option are fairly obvious, and in all likelyhood you already provide this service to your clients in some fashion.

For a consumer of these services, the question is almost entirely economic. You may already be a customer of one of the numerous services that pioneered this type of product some years ago, such as uReach or quite a few others. It is also a big hit with enhanced service providers participating in the “unified messaging” space (e-mail + voicemail + fax + anything else delivered to one messaging portal). If you have some type of “e-fax” service and are happy with it, the rule is the same as with other mission-critical services on which your business depends that are outside your core competency and competitive advantage: stick with it. It works well, does the job, and frees you from all the financial and technical headaches of figuring out and maintaining it yourself.

For my client, volume and ROI was the central element of this polemic. The company had a large number of employees with fax numbers, but only a small percentage of them routinely received faxes. However, the posture of the business model dictated that pretty much all of them needed to have fax numbers in principle. Also, the bulk of the employees were very low-ROI, specifically as far as that portion of their productivity that benefits from fax usage is concerned. Within the context of fax expenditures as such, marginal product of labour was incredibly, vastly lower than marginal cost. On top of that, the client had already deployed an Asterisk-based IP telephony system internally and had a large amount of PRI circuits with plenty of spare channel capacity that could be leveraged to bring fax services in-house and save money. The cost justification was clearly there.

Of course, you might benefit from this solution whether or not you share my client’s particular cost structure and ROI dilemma. Surely you’ve seen what happens in, for example, a busy sales organisation when large crowds of employees assemble around a shared fax machine to wait for and collect their transmissions, whose sources are queuing behind a perenially “busy” fax line? You could be losing more than an hour a day of potential employee productivity on this waiting game, not to mention countless additional opportunities. With the right combination of technical capital, business need and fax volume, you may be better off not outsourcing your fax services, but even if you are, definitely invest in individual fax-to-email services one way or another! Keep your employees at their desks, your paper and toner costs low, and your faxes coming in quickly, expediently and simultaneously.

If fax is used in the course of your daily business operations to any significant degree whatsoever, you don’t have time or money for the opportunity costs of doing things the old way.

The Concepts

In order to swing your own fax interface, you need more than just an analog telephony interface. You need something that can emulate a fax machine’s DSP (Digital Signal Processor), which is essentially the same sort of DSP as in a modem. Some fax interface devices accomplish this in hardware DSP chips, the most obvious being a conventional fax machine. But it is also possible to emulate this functionality purely in software. That’s the IAXmodem part of this equation. IAXmodem uses an open-source library called SpanDSP to perform its magic.

IAXmodem interfaces with Asterisk using IAX (Inter-Asterisk eXchange), a VoIP signaling and trunking protocol developed entirely in connection with Asterisk and primarily for the purpose of getting calls from one Asterisk PBX to another. It could have used SIP, or whatever — it just happens to use IAX, and thus, by that virtue, also happens to require Asterisk to front it when interfacing with (decidedly non-IAX) PSTN call endpoints. I suppose if you have IAX trunking to a PSTN origination provider (which some offer) it is theoretically possible to use IAXmodem without Asterisk, but I wouldn’t recommend it because IAXmodem by itself is pretty dumb, and does one thing. Plus, it has no measures to compensate for QoS issues such as jitter buffers for unevenly spaced media streams. From a call control standpoint, it is much wiser to pass calls through Asterisk.

IAXmodem itself is just a dumb DSP. To process and convert received fax data from whatever DSP source, you need actual fax software; that’s HylaFAX.

A Quick Note about FoIP (Fax-over-IP)

You may have picked up on the fact that IAXmodem uses VoIP to provide the link between Asterisk and the actual modem DSP emulation component.

Much has been said and much has been made of the perils of running analog fax traffic encapsulated in a packetised form over an IP network, and not without reason. It generally doesn’t work well. Fax terminals are extremely sensitive to the effects of delay and jitter on the encoded analog data; even slight packet loss or out-of-sequence errors in the IP leg of the transport can produce protocol errors that will cause a fax transmission to fail, or show up tragically garbled. The overall impact of the fundamental unreliability of IP — especially over the Internet — is much greater on fax traffic than it is on subjective perceptions of voice quality.

In order to get analog pass-thru on fax data to work, you need to use a clear-channel PCM codec whose accommodation of the analog spectrum equals the full extent of the 3.1 KHz analog bandwidth on standard analog voice lines. This means ITU-T G.711 with either μ-law (North American) or A-law (European) companding. But even then, it is generally impossible to run this traffic reliability over the Internet; too much data simply gets lost, or arrives in a manner inconsistent with the tight jitter requirements for successful pass-thru. This is the problem T.38 is intended to solve; it packetises fax data in ways more consistent with the way Internet applications usually transmit data, rather than attempting to graft high-throughput real-time voice data with extremely low packetisation durations onto an infrastructure whose QoS is not inherently designed to handle it well at present. Then, two T.30 (traditional analog fax) terminals can interface with each other over a more reliable data transport conduit that is entirely transparent to them.

Fortunately, moving fax data in an analog pass-thru format over a LAN — not the greater Internet — is generally reliable. It does depend on how your LAN is designed and what kind of contention exists on it. If you’re moving a whole, whole lot of data across your LAN without any VoIP-oriented QoS precedence measures in place, it may still be problematic. But in general, it is possible. The IAXmodem FAQ has the following to offer:

Q1: IAX is VoIP. Isn’t it bad to run fax over VoIP?

A1: Yes, generally it is. The conditions of jitter, packet loss,
and other latency issues cause problems for reassembling VoIP
audio streams on the receiver exactly as they were disassembled
on the sender. However, these latency conditions can be
controlled. For example, these conditions should not be present
when communicating over the localhost address (127.0.0.1) in the
case where the Asterisk server and the system running IAXmodem
are the same. Other possible arrangements could be to use a
dedicated network connection (e.g. a crossover cable) between
the Asterisk server and the system running IAXmodem. IAXmodem
should also be fine to run over a local area network (LAN) that
is well-designed and controlled.

The basic point is this: if you can eliminate the mitigating factors
of jitter and packet loss, then you can reliably use VoIP channels
for faxing.

As this relates to the setup being discussed here, you do not have to move the fax data over an IP network at all if you don’t want to. You can set up HylaFAX, IAXmodem and Asterisk on a server that has analog lines (PRI or POTS) running into it, and thus take the fax traffic in unmitigated analog form directly at the source.

The setup I deployed does decompose this assembly and frame it over an IP data plane: The client had PRIs running into Asterisk boxes with Sangoma T1 cards. These media gateways then forward the call via SIP (using G.711μ-law for the RTP) to an internal fax server on the LAN that runs Asterisk, IAXmodem and HylaFAX. Asterisk receives those calls and forwards them onto IAXmodem using IAX. This has not given us any problems. Just make sure that the you are using G.711; if you use G.729 or some other high-compression codec, the acoustic characteristics of the voice signal are mangled in ways that humans do not necessarily find objectionable, but faxes do, thus breaking the fax transmission entirely — as it reconstructs digital data from analog sound data over a larger bandwidth and sample complexity than is necessary for subjectively acceptable human speech.

Also, do note that there is an analog interface to the PSTN somewhere on our LAN here. If you are getting your PSTN origination entirely via SIP handoff, your success may vary. If it is over the Internet, chances are it won’t work at all. If you are colocated with a telco or have a dedicated point-to-point IP link to your provider, perhaps it will be okay; it just depends.

I do recommend that you run the Asterisk instance that terminates the fax calls and funnels them into IAXmodem on the same machine. But it is not strictly necessary; in fact, the reason we forward the calls via SIP internally is more for unrelated engineering reasons. We could have just as well set up IAXmodem on one machine, and had the modems register as IAX peers right on the media gateways, and I am confident it would have worked fine, especially with the jitter buffer enabled. If you have experiences to the contrary, feel free to share.

Note on DIDs

This type of setup is, much like VoIP products in general, very telephone number-intensive, but especially so because the direct fax number dialed is pretty much your only destination criterion for routing calls to particular users. Unless you plan to route faxes to particular users based on caller ID or some other common call data addressable from within HylaFAX, you’re pretty much going to have order direct fax numbers for every user. Luckily, despite heavy numbering space utilisation, getting a block of DIDs for this purpose from your carrier is not particularly expensive.

The more important point is that you need to have a way to get faxes coming through the door to particular users’ mailboxes, and the direct fax number dialed is the best way to do it. This ability to transmit the number dialed by the far end into the application is known in telco terminology as DNIS (Dialed Number Information Service), which has its roots in legacy terminology that covers making DID (Direct Inward Dial) work with business PBXs by transmitting the number dialed from the telco switch as a separate item of signaling data. (Think about it — how else does a telephone endpoint receiving a call inherently “know” what number a user dialed to reach it? Traditional telephone switching was purely a matter of cross-connecting DS0 signals.)

Setting up Asterisk

I cannot give an exhaustive tutorial on every aspect of setting up Asterisk for this task. If you do not know how to use Asterisk, learn to use it first, and this article will serve you better. There are plenty of resources online and offline to get you started, plus a resourceful, helpful and abundant user community to answer your questions.

You need to get your fax-bound calls into an Asterisk instance using one of the methods mentioned above, or some other method. This will be our departure point. In my case, this is really simple; on our media gateways, we have a dial plan that forwards DID ranges associated we’ve chosen to allocate for fax to the internal fax server via SIP:


;;; Context for calls coming in from PSTN.

[from-pstn]

exten => 8885551212,1,Goto(from-pstn-fax,fax,1)

...

[from-pstn-fax]

; Fax routing.

[from-pstn-fax]

exten => fax,1,Dial(SIP/${DNID}@Fax,15,r)
exten => fax,n,Busy()
exten => fax,n,Hangup()

Notice that we supply the ${DNID} (Dialed Number ID) as the extension to reach at the fax server SIP trunk (’Fax’). This is because we have a dedicated dial plan ruleset for fax, with an extension called ‘fax’, so if we sent ${EXTEN}, its value would be ‘fax’, which is not particularly useful to the fax server as it provides no DNIS. But we might as well have just done:

exten => 8885551212,1,Dial(SIP/${EXTEN}@Fax,15,r)

Purely a matter of style.

Because our fax server only accepts fax calls, we just accept any number inward in the dial plan there:

[from-sip-external]

exten => _.,1,Dial(IAX2/FaxDSP0/${EXTEN},15,r)
exten => _.,2,Dial(IAX2/FaxDSP1/${EXTEN},15,r)
exten => _.,3,Dial(IAX2/FaxDSP2/${EXTEN},15,r)
exten => _.,4,Dial(IAX2/FaxDSP3/${EXTEN},15,r)
exten => _.,5,Dial(IAX2/FaxDSP4/${EXTEN},15,r)
exten => _.,6,Dial(IAX2/FaxDSP5/${EXTEN},15,r)
exten => _.,7,Dial(IAX2/FaxDSP6/${EXTEN},15,r)
exten => _.,8,Dial(IAX2/FaxDSP7/${EXTEN},15,r)
exten => _.,9,Dial(IAX2/FaxDSP8/${EXTEN},15,r)
exten => _.,10,Dial(IAX2/FaxDSP9/${EXTEN},15,r)
exten => _.,11,Dial(IAX2/FaxDSP10/${EXTEN},15,r)
exten => _.,12,Dial(IAX2/FaxDSP11/${EXTEN},15,r)
exten => _.,13,Dial(IAX2/FaxDSP12/${EXTEN},15,r)
exten => _.,14,Dial(IAX2/FaxDSP13/${EXTEN},15,r)
exten => _.,15,Dial(IAX2/FaxDSP14/${EXTEN},15,r)
exten => _.,16,Dial(IAX2/FaxDSP15/${EXTEN},15,r)
exten => _.,17,Dial(IAX2/FaxDSP16/${EXTEN},15,r)
exten => _.,18,Dial(IAX2/FaxDSP17/${EXTEN},15,r)
exten => _.,19,Dial(IAX2/FaxDSP18/${EXTEN},15,r)
exten => _.,20,Dial(IAX2/FaxDSP19/${EXTEN},15,r)
exten => _.,n,Congestion()
exten => _.,n,Hangup

The ‘FaxDSPX[X]’ destinations are registered IAX peers provided by IAXmodem. We have twenty inward fax channels (distinct IAXmodem entities) configured separately. As you can see above, we just cycle through them like a rollover hunt group; if the first fax DSP is busy, we go on the next, and so on. Remember that the Dial() application returns the executin scope from the context if the call is successful and does not continue to execute subsequent priorities.

Setting up IAXmodem

Download IAXmodem and compile it, or install a package if your distribution supplies one. IAXmodem comes with the SpanDSP library.

Next, you need to decide how many “modems” you want to configure. We configured 20 in our case. Despite the high-volume fax environment, the need for more than five or so is not in evidence, but your oversubscription ratios may vary significantly. The considerations relevant to computing power and system dimensioning notwithstanding, I know of no theoretical limit to the amount of virtual IAXmodems one can configure, so if you expect to need three PRIs worth of capacity, have at it.

IAXmodems are defined in /etc/iaxmodem/ttyIAXy, where ‘y’ is the number of the serial device. They can start at 0 and are numbered upward, e.g. ttyIAX0 through ttyIAX20. The file is plain text and contains a stanza like so:

device /dev/ttyIAX0
owner uucp:uucp
mode 660
port 4570
refresh 60
server 66.1.1.1
peername FaxDSP0
secret FaxDSP0
cidname Evariste Systems
cidnumber 6789540670
codec ulaw

Each IAXmodem you configure creates a self-contained serial device (used by HylaFAX the same way a real serial port would be, by way of the same low-level serial I/O APIs) and a distinct registered IAX peer. This means that if you plan to have more than one IAXmodem, the IAX peers need to be registered from unique UDP ports in order to be uniquely reachable. So, if you define more than one ttyIAX, make sure to give them unique port numbers. I just started numbering mine one above the standard IAX port (4569) at 4570, 4571, 4572, and so on.

Also, the ‘cidnumber’ and others above are for outbound purposes and should not particularly concern you.

Now you need to create dedicated IAXmodem ‘listeners’ on each of these serial devices; this is what provides for something actually picking up when it gets a call. The listener daemons are started by running:

iaxmodem ttyIAXnn

The easiest way to ensure a listener is always running on each IAXmodem “port” is to put them in ‘/etc/inittab’ as parts of a runlevel:

i0:2345:respawn:/usr/sbin/iaxmodem ttyIAX0
i1:2345:respawn:/usr/sbin/iaxmodem ttyIAX1
i2:2345:respawn:/usr/sbin/iaxmodem ttyIAX2
i3:2345:respawn:/usr/sbin/iaxmodem ttyIAX3
i4:2345:respawn:/usr/sbin/iaxmodem ttyIAX4
i5:2345:respawn:/usr/sbin/iaxmodem ttyIAX5
i6:2345:respawn:/usr/sbin/iaxmodem ttyIAX6
i7:2345:respawn:/usr/sbin/iaxmodem ttyIAX7
i8:2345:respawn:/usr/sbin/iaxmodem ttyIAX8
i9:2345:respawn:/usr/sbin/iaxmodem ttyIAX9
i10:2345:respawn:/usr/sbin/iaxmodem ttyIAX10
i11:2345:respawn:/usr/sbin/iaxmodem ttyIAX11
i12:2345:respawn:/usr/sbin/iaxmodem ttyIAX12
i13:2345:respawn:/usr/sbin/iaxmodem ttyIAX13
i14:2345:respawn:/usr/sbin/iaxmodem ttyIAX14
i15:2345:respawn:/usr/sbin/iaxmodem ttyIAX15
i16:2345:respawn:/usr/sbin/iaxmodem ttyIAX16
i17:2345:respawn:/usr/sbin/iaxmodem ttyIAX17
i18:2345:respawn:/usr/sbin/iaxmodem ttyIAX18
i19:2345:respawn:/usr/sbin/iaxmodem ttyIAX19

NOTE: Resist any temptation to give the process IDs names longer than three characters. When I had then named iax0, iax1, … instead of ia0, ia1, … not all of the iaxmodem processes would start. I am not sure why. Something with the way init(1) works, I suppose.

Restart init:

init q

The IAXmodem end of the setup is now complete. Remember that each IAXmodem listener is an IAX peer, and will try to register with Asterisk on the standard IAX port (4569). You will need to provide IAX peer definitions for each unique IAXmodem in ‘/etc/asterisk/iax.conf’:

;;;
;;; IAX2 config for fax trunking to SpanDSP/IAXmodem/HylaFAX.
;;;

[general]
bindport = 4569           ; Port to bind to (IAX is 4569)
bindaddr = 0.0.0.0    ; Address to bind to (all addresses on machine)
disallow=all
allow=ulaw
allow=alaw
mailboxdetail=yes
iaxcompat=yes
jitterbuffer=yes

[FaxDSP0]

type=friend
username=FaxDSP0
secret=FaxDSP0
host=127.0.0.1
port=4570
disallow=all
allow=ulaw
qualify=yes  

[FaxDSP1]

type=friend
username=FaxDSP1
secret=FaxDSP1
host=127.0.0.1
port=4571
disallow=all
allow=ulaw
qualify=yes

[ ... and so on ...]

You should now be able to dial into a fax DID and hear something resembling a modem pick up on the far end. If something seems amiss, verify that your dial plan is working correctly by running the Asterisk CLI and turning verbosity to high:

asterisk -r
set verbose 60

Then try dial a fax number, and see what transpires:

   -- Executing Dial("SIP/66.1.1.1-b77103a0", "IAX2/FaxDSP0/h|15|r") in new stack

    -- Called FaxDSP0/h

    -- Call accepted by 127.0.0.1 (format ulaw)

    -- Format for call is ulaw

    -- IAX2/FaxDSP0-9 is ringing
    -- IAX2/FaxDSP0-9 answered SIP/10.2.2.68-b77103a0

If IAXmodem is not working correctly, you may see something like:

    -- Executing Dial("SIP/66.1.1.1-b77057f0", "IAX2/FaxDSP0/8885551212|15|r") in new stack
    -- Called FaxDSP0/8885551212

    -- IAX2/FaxDSP0-62 is circuit-busy
    -- Hungup 'IAX2/FaxDSP0-62'
  == Everyone is busy/congested at this time (1:0/1/0)

You can debug by disabling the ‘iaxmodem’ spawning for the current runlevel in ‘/etc/inittab’ temporarily and run ‘iaxmodem’ on the terminal manually against a particular IAXmodem port (the first one in your dial plan hunting scheme):

iaxmodem ttyIAX0

This should give you some debugging output.

If you are not sure whether the IAX registration against Asterisk is completing successfully, you can connect to the Asterisk CLI:

asterisk -r

And issue the command ‘iax2 show peers’:

fax*CLI> iax2 show peers
Name/Username    Host                 Mask             Port          Status
FaxDSP19/FaxDSP  127.0.0.1       (S)  255.255.255.255  4589          OK (3 ms)
FaxDSP18/FaxDSP  127.0.0.1       (S)  255.255.255.255  4588          OK (3 ms)
FaxDSP17/FaxDSP  127.0.0.1       (S)  255.255.255.255  4587          OK (1 ms)
FaxDSP16/FaxDSP  127.0.0.1       (S)  255.255.255.255  4586          OK (3 ms)
FaxDSP15/FaxDSP  127.0.0.1       (S)  255.255.255.255  4585          OK (2 ms)
FaxDSP14/FaxDSP  127.0.0.1       (S)  255.255.255.255  4584          OK (1 ms)
FaxDSP13/FaxDSP  127.0.0.1       (S)  255.255.255.255  4583          OK (3 ms)
FaxDSP12/FaxDSP  127.0.0.1       (S)  255.255.255.255  4582          OK (3 ms)
FaxDSP11/FaxDSP  127.0.0.1       (S)  255.255.255.255  4581          OK (3 ms)
FaxDSP10/FaxDSP  127.0.0.1       (S)  255.255.255.255  4580          OK (3 ms)

If you’re really stuck, try turning on IAX debugging:

iax2 debug

This can give some hints:

Rx-Frame Retry[ No] -- OSeqno: 000 ISeqno: 000 Type: IAX     Subclass: REGREQ 

   Timestamp: 00003ms  SCall: 06029  DCall: 00000 [10.2.2.63:4570]

   USERNAME        : FaxDSP0

   REFRESH         : 60

If you place a call in, you can also verify that an IAXmodem DSP picked up by doing:

fax*CLI> show channels
Channel              Location             State   Application(Data)             

IAX2/FaxDSP0-3       (None)               Up      Bridged Call(SIP/66.1.1.1-b77
SIP/10.2.2.68-b77057 8885551212@from-sip- Up      Dial(IAX2/FaxDSP0/8885551212|1
2 active channels
1 active call

Setting up HylaFAX

HylaFAX is a complicated software package that can do many different things entirely outside the scope of this tutorial. However, if you’re just wanting to push out faxes from it via e-mail, it should be relatively simple, as the stock package comes with decent pre-built configurations for dialing rules and so on.

Install HylaFAX, either from source or from a package. Start the services to make sure they all work out of the box:

[root@fax ~]# /etc/init.d/hylafax start
Starting HylaFAX queue manager (faxq):                     [  OK  ]
Starting HylaFAX server (hfaxd):                           [  OK  ]
Restarting HylaFAX modem manager (faxgetty):               [  OK  ]

Next, you will need to put up HylaFAX’s ‘faxgetty’ listeners on the IAXmodems. Back to ‘/etc/inittab/’:

m0:2345:respawn:/usr/sbin/faxgetty ttyIAX0
m1:2345:respawn:/usr/sbin/faxgetty ttyIAX1
m2:2345:respawn:/usr/sbin/faxgetty ttyIAX2
m3:2345:respawn:/usr/sbin/faxgetty ttyIAX3
m4:2345:respawn:/usr/sbin/faxgetty ttyIAX4
m5:2345:respawn:/usr/sbin/faxgetty ttyIAX5
m6:2345:respawn:/usr/sbin/faxgetty ttyIAX6
m7:2345:respawn:/usr/sbin/faxgetty ttyIAX7
m8:2345:respawn:/usr/sbin/faxgetty ttyIAX8
m0:2345:respawn:/usr/sbin/faxgetty ttyIAX9
m10:2345:respawn:/usr/sbin/faxgetty ttyIAX10
m11:2345:respawn:/usr/sbin/faxgetty ttyIAX11
m12:2345:respawn:/usr/sbin/faxgetty ttyIAX12
m13:2345:respawn:/usr/sbin/faxgetty ttyIAX13
m14:2345:respawn:/usr/sbin/faxgetty ttyIAX14
m15:2345:respawn:/usr/sbin/faxgetty ttyIAX15
m16:2345:respawn:/usr/sbin/faxgetty ttyIAX16
m17:2345:respawn:/usr/sbin/faxgetty ttyIAX17
m18:2345:respawn:/usr/sbin/faxgetty ttyIAX18
m19:2345:respawn:/usr/sbin/faxgetty ttyIAX19

Restart init:

init q

Verify that faxgetty is in fact running:

[root@fax ~]# ps aux | grep faxgetty
uucp     21847  0.0  0.0  5400 1632 ?        Ss   18:54   0:00 /usr/sbin/faxgetty ttyIAX15
uucp     21848  0.0  0.0  5696 1632 ?        Ss   18:54   0:00 /usr/sbin/faxgetty ttyIAX7
uucp     21851  0.0  0.0  4472 1632 ?        Ss   18:54   0:00 /usr/sbin/faxgetty ttyIAX6
uucp     21852  0.0  0.0  6076 1636 ?        Ss   18:54   0:00 /usr/sbin/faxgetty ttyIAX5
uucp     21853  0.0  0.0  5792 1632 ?        Ss   18:54   0:00 /usr/sbin/faxgetty ttyIAX16
uucp     21854  0.0  0.0  6360 1632 ?        Ss   18:54   0:00 /usr/sbin/faxgetty ttyIAX3
uucp     21855  0.0  0.0  5368 1632 ?        Ss   18:54   0:00 /usr/sbin/faxgetty ttyIAX17
uucp     21856  0.0  0.0  5024 1632 ?        Ss   18:54   0:00 /usr/sbin/faxgetty ttyIAX2
uucp     21857  0.0  0.0  6236 1632 ?        Ss   18:54   0:00 /usr/sbin/faxgetty ttyIAX18
uucp     21858  0.0  0.0  4480 1632 ?        Ss   18:54   0:00 /usr/sbin/faxgetty ttyIAX14
uucp     21859  0.0  0.0  5376 1632 ?        Ss   18:54   0:00 /usr/sbin/faxgetty ttyIAX1
uucp     21860  0.0  0.0  6288 1632 ?        Ss   18:54   0:00 /usr/sbin/faxgetty ttyIAX4
uucp     21861  0.0  0.0  4736 1632 ?        Ss   18:54   0:00 /usr/sbin/faxgetty ttyIAX19
uucp     21862  0.0  0.0  4712 1632 ?        Ss   18:54   0:00 /usr/sbin/faxgetty ttyIAX0
uucp     21863  0.0  0.0  5664 1632 ?        Ss   18:54   0:00 /usr/sbin/faxgetty ttyIAX13
uucp     21864  0.0  0.0  5804 1636 ?        Ss   18:54   0:00 /usr/sbin/faxgetty ttyIAX11
uucp     21865  0.0  0.0  5176 1632 ?        Ss   18:54   0:00 /usr/sbin/faxgetty ttyIAX12
uucp     21866  0.0  0.0  6268 1636 ?        Ss   18:54   0:00 /usr/sbin/faxgetty ttyIAX8
uucp     21867  0.0  0.0  5728 1632 ?        Ss   18:54   0:00 /usr/sbin/faxgetty ttyIAX10
root     21874  0.0  0.0  4492  648 pts/3    S+   18:56   0:00 grep faxgetty

There’s only two things you should need to modify. One is the inward fax distribution script. It’s called ‘FaxDispatch’. Where it lives is somewhat installation/distribution-dependent. On our CentOS fax server, it is in ‘/var/spool/hylafax/etc/’.

This is a bash shell script at heart, and controls HylaFAX’s behaviour by setting two environment variables:

  • SENDTO - The e-mail address to which the output should be e-mailed.

  • FILETYPE - The format of the attachment (for instance, ‘pdf’).

You can put defaults for these at the top of the script:


# By default, e-mail unroutable faxes to the secretary.

SENDTO=secretary@faxes-receivable.evaristesys.com;

# In PDF format.

FILETYPE=pdf;

This script is passed numerous environment variables by HylaFAX (which invokes it internally) as well. Some of these ($SENDER, $CIDNUMBER, etc.) are mentioned in the comments in the stock file that is distributed with the package. However, if you are planning on routing by DNIS, you should know that the dialed number is passed to HylaFAX in this particular setup as $CALLID4. Not $CALLID, but $CALLID4. See the man page for faxrcvd(1) if you care.

Most information out there entreats you do something like this:

case "$CALLID4" in
        8885551212)
                 # All toll-free faxes.

                 SENDTO=bob@evaristesys.com;
                 FILETYPE=pdf;;
        *)
                # All others.

                SENDTO=employee@evaristesys.com;
                FILETYPE=pdf;;
esac

But remember that this is a shell script, so you can build all sorts of outboard logic into it. In our case, we have a script that dips the employee database and automatically figures out which e-mail address to route the fax to:

DEST_EMAIL=$(/usr/bin/perl /usr/local/fax_route_dip/fax_route_dip.pl $CALLID4)

echo "Routing to: $DEST_EMAIL ($CALLID4)" >> /tmp/hylafax_routing.dbg.out

if [ $DEST_EMAIL == "NIL" ];
then
        SENDTO=Faxmaster@evaristesys.com;
        FILETYPE=pdf
else
        SENDTO=$DEST_EMAIL
        FILETYPE=pdf
fi

You also need to create configuration files in the same ‘etc’ directory for every IAXmodem listener that contain basic fax and modem-related configuration parameters. For the most part, these are fairly unimportant, but they need to be there. The files are called ‘config.ttyIAXnn’, e.g. config.ttyIAX0:

[root@fax etc]# more config.ttyIAX0
CountryCode:		1
AreaCode:		800
FAXNumber:		+1.800.555.1212
LongDistancePrefix:	1
InternationalPrefix:	011
DialStringRules:	etc/dialrules
ServerTracing:		0xFFF
SessionTracing:		0xFFF
RecvFileMode:		0600
LogFileMode:		0600
DeviceMode:		0600
RingsBeforeAnswer:	1
SpeakerVolume:		off
GettyArgs:		"-h %l dx_%s"
LocalIdentifier:	"IAXmodem0"
TagLineFont:		etc/lutRS18.pcf
TagLineFormat:		"From %%l|%c|Page %%P of %%T"
MaxRecvPages:		200
#
#
# Modem-related stuff: should reflect modem command interface
# and hardware connection/cabling (e.g. flow control).
#
ModemType:      	Class1		# use this to supply a hint

#
# Enabling this will use the hfaxd-protocol to set Caller*ID
#
#ModemSetOriginCmd:	AT+VSID="%s","%d"

#
# If "glare" during initialization becomes a problem then take
# the modem off-hook during initialization, and then place it
# back on-hook when done.
#
#ModemResetCmds:	"ATH1nAT+VCID=1"	# enables CallID display
#ModemReadyCmds:	ATH0

Class1AdaptRecvCmd:	AT+FAR=1
Class1TMConnectDelay:	400		# counteract quick CONNECT response

#
# If you have trouble with V.17 receiving or sending,
# you may want to enable one of these, respectively.
#
#Class1RMQueryCmd:      "!24,48,72,96"  # enable this to disable V.17 receiving
#Class1TMQueryCmd:      "!24,48,72,96"  # enable this to disable V.17 sending

#
# You'll likely want Caller*ID display (also displays DID) enabled.
#
ModemResetCmds:		AT+VCID=1	# enables CallID display

#
# The pty does not support changing parity.
#
PagerTTYParity:		none

#
# If you are "missing" Caller*ID data on some calls (but not all)
# and if you do not have adequate glare protection you may want to
# not answer based on RINGs, but rather enable the CallIDAnswerLength
# for NDID, disable AT+VCID=1 and do this:
#
#RingsBeforeAnswer: 0
#ModemRingResponse: AT+VRID=1
# Uncomment DATE and TIME if you really want them, but you probably don't.
#CallIDPattern:          "DATE="
#CallIDPattern:          "TIME="
CallIDPattern:          "NMBR="
CallIDPattern:          "NAME="
CallIDPattern:		"ANID="
#CallIDPattern:          "USER="	# username provided by call
#CallIDPattern:          "PASS="	# password provided by call
#CallIDPattern:          "CDID="	# DID context in call
CallIDPattern:          "NDID="
#CallIDAnswerLength:	4

And that’s pretty much it. Now restart HylaFAX:

/etc/init.d/hylafax restart

And that should be all there is to it.

Of course, you will need to make sure you have a properly configured MTA (Mail Transfer Agent) running on the local server that can relay this mail outbound.

Happy trails!

NOTE: As of release 1.4, Asterisk has patches for native fax applications based on SpanDSP. I have not tested these, but it is worthwhile to take a look. If they work well, they should allow you to take IAXmodem and HylaFAX out of the equation. Also check out astfax for the e-mail integration portion.

1 On the end-subscriber level, of course. The network transport portion of the public switched telephone network (PSTN) has been digital and “computer-based” for a long time.

Why is PHPUnit / PHP crashing?

Posted in Action Items by Alex Balashov on the October 31st, 2007

Hear me now.

I have a simple unit test suite and test case written for the database connection handler for our in-house PHP development toolkit, EvPHPTK. It looks like this:


class DBConnPgTest extends PHPUnit_Framework_TestCase {

        /**
         * Attempt to connect to localhost PostgreSQL database 'template1'.
         */

         public function testDBConnOpen() {
                $db_conn = EvPHPTKFactory::DBConn('pgsql', 'localhost',
                                                  'somedb', 'somepassword',
                                                  'template1');

                $this->assertEquals(PGSQL_CONNECTION_OK,
                                        $db_conn->get_conn_status());
         }

PHPUnit 3.1.9 crashes after running the test:

PHPUnit 3.1.9 by Sebastian Bergmann.

.

Time: 0 seconds

OK (1 tests)
/bin/sh: line 1: 21070 Segmentation fault      phpunit $test_class tests/$test_class.php

It also crashes when running any of my other unit tests.

PHPUnit 2.3.6 crashes too. Whatever this problem is, it’s probably based on a dependency.

It completes every test first, and certainly performs the desired behaviour. But the SIGSEGV and the resulting status code returned to the shell causes my automatic Makefile-based test running scheme complain, as GNU Make is very particular about return codes — for all the right reasons related to failures during compilation.

/usr/bin/phpunit is really a PHP script wrapper, so debugging would have to be done on the PHP5 CLI binary itself. It is probably not compiled with debugging symbols by default, and I have no inclination whatsoever to compile it thusly, so there’s not going to be a lot of insight here:

sasha@arpeggio:/u/targets/target-evphptk$ gdb /usr/bin/php
GNU gdb 6.6.90.20070912-debian
Copyright (C) 2007 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later 
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type “show copying”
and “show warranty” for details.
This GDB was configured as “i486-linux-gnu”…
(no debugging symbols found)
Using host libthread_db library “/lib/i686/cmov/libthread_db.so.1″.
(gdb) r -e /usr/bin/phpunit DBConnPgTest tests/DBConnPgTest.php
Starting program: /usr/bin/php -e /usr/bin/phpunit DBConnPgTest tests/DBConnPgTest.php
(no debugging symbols found)
Failed to read a valid object file image from memory.
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
[Thread debugging using libthread_db enabled]
[New Thread 0xb78846b0 (LWP 21377)]
Error while reading shared library symbols:
Cannot find new threads: generic error
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
PHPUnit 3.1.9 by Sebastian Bergmann.

(no debugging symbols found)
(no debugging symbols found)
..

Time: 0 seconds

OK (2 tests)

Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0xb78846b0 (LWP 21377)]
0xb7099cc0 in ?? ()
(gdb) where
#0  0xb7099cc0 in ?? ()
#1  0xb78d8005 in CRYPTO_lock () from /usr/lib/i686/cmov/libcrypto.so.0.9.8
#2  0xb7942e0d in ?? () from /usr/lib/i686/cmov/libcrypto.so.0.9.8
#3  0×00000009 in ?? ()
#4  0×00000001 in ?? ()
#5  0xb79ba4bf in ?? () from /usr/lib/i686/cmov/libcrypto.so.0.9.8
#6  0×00000161 in ?? ()
#7  0xb79ca12c in ?? () from /usr/lib/i686/cmov/libcrypto.so.0.9.8
#8  0×085d7000 in ?? ()
#9  0xbff65638 in ?? ()
#10 0xb79445ba in ERR_free_strings ()
   from /usr/lib/i686/cmov/libcrypto.so.0.9.8
Backtrace stopped: frame did not save the PC

Race condition in the SSL libs? Free() problem? I know not. If you have any insights, please share!

UPDATE 2007-10-31: Thanks to Brian Morton over at Nerd Happens for helping me ascertain that this problem does not occur when the test does not make use of PHP PostgreSQL extensions.

UPDATE 2007-10-31: Seems to happen for any code that uses the PostgreSQL extensions running in the PHP5 CLI. Seems okay with mod_php though, at least as far as I can tell from the lack of any errors suggesting the contrary even on full verbosity mode. But, anyway, this is not a PHPUnit-specific problem.

UPDATE 2007-10-31: The problem was with libssl, or possibly the CURL library. I installed the Debian package libssl0.9.8-dbg (version of libssl with debugging symbols compiled in) and got this debug output:

(gdb) where
#0  0xb6f93db0 in ?? ()
#1  0xb7925005 in CRYPTO_lock (mode=9, type=1, file=0xb7a074bf "err.c",
    line=353) at cryptlib.c:489
#2  0xb798fe0d in int_err_del () at err.c:353
#3  0xb79915ba in ERR_free_strings () at err.c:672
#4  0xb76af307 in ?? () from /usr/lib/libcurl.so.4
#5  0xb76af2f9 in ?? () from /usr/lib/libcurl.so.4
#6  0xb76d15c0 in ?? () from /usr/lib/libcurl.so.4
#7  0xbfbfb328 in ?? ()
#8  0xb76c2c80 in ?? () from /usr/lib/libcurl.so.4
#9  0xb76c2c69 in ?? () from /usr/lib/libcurl.so.4
#10 0xb76d15c0 in ?? () from /usr/lib/libcurl.so.4
#11 0xbfbfb338 in ?? ()
#12 0xb76b8963 in curl_global_cleanup () from /usr/lib/libcurl.so.4
Backtrace stopped: previous frame inner to this frame (corrupt stack?)
(gdb) quit

Fixed by adding option sslmode=disable to parameter string to pg_connect() in EvPHPTK. No segmentation fault now.

Part 2: Integrating Mantis + ViewVC + Subversion for source code view.

Posted in Hacks by Alex Balashov on the October 25th, 2007

So, Part 1 of this exercise was interesting enough if you have a single project with a single Subversion repository and are willing to hard-code repository name values into the snippet provided.

If you have multiple repositories in Subversion and ViewVC, and, among other things, the value of the SVN Revision takes on a local context within each project, this won’t fly. Another custom field is needed.

We refer to the development installations of our projects as targets, and name everything related to the project based on that designation. Thus, I added another custom field to Mantis called ‘Target’ — it is mandatory, and contains the name of the target to which one is contributing the issue. Thus, for example, EVA becomes target-eva. The repository is also named target-eva.

Here is an updated code snippet with behaviour updated for this change. It does contain a somewhat inelegant initial loop (as stated in the lengthy comment) to pre-emptively find the field value of Target before iterating through them again for the purpose of display, which could be addressed by pushing the results of the iteration onto an array and unfolding them for display purposes. However, that is a level of modification I did not aspire to, although at this point that entire section of the page rendering code is quite thoroughly butchered as is. Basically, I’m lazy. But in our case, this isn’t a performance problem. Just adds an extra query for a table with a number of rows I can count on my fingers.

There are some other fixups as well, including updated links for Diffs and Patches that properly handle the situation of viewing revision 1, so that it doesn’t triggert a ViewVC-side Python exception when trying to compare it against revision 0, but at least yields more presentable results.

<!-- Custom Fields -->
<?php
        # -- Modifications by Alex Balashov -- #

        /*
         * Iterate through the custom fields and find the Target value
         * pre-emptively before attempting any SVN linkage below.  This
         * is necessary in our case as the Target field is met in the
         * loop *after* the SVN Revision field, if for no other reason
         * than alphabetical ordering.
         *
         * Obviously, this can be a performance hit;  it would be nicer to
         * keep to one loop and push the results into an array.  However,
         * I do not wish to modify the code so substantially, and besides,
         * our population of custom fields is very small.
         */

        foreach(custom_field_get_linked_ids($t_bug->project_id) as $t_id) {
                $t_def = custom_field_get_definition($t_id);

                if($t_def['name'] == 'Target') {
                        $svn_rep_name = string_custom_field_value($t_def, $t_id, $f_bug_id);
                }
        }

        # -- End modifications by Alex Balashov -- #

        $t_custom_fields_found = false;
        $t_related_custom_field_ids = custom_field_get_linked_ids( $t_bug->project_id );
        foreach( $t_related_custom_field_ids as $t_id ) {
                $t_def = custom_field_get_definition( $t_id );
                if( !$t_def['advanced'] && custom_field_has_read_access( $t_id, $f_bug_id ) ) {
                        $t_custom_fields_found = true;
?>
        <tr <?php echo helper_alternate_class() ?>>
                <td class="category">
                        <?php echo string_display( lang_get_defaulted( $t_def['name'] ) ) ?>
                </td>
                <td colspan="5">

                        <?php

                        # -- Modifications by Alex Balashov -- #

                        $f_val = string_custom_field_value($t_def, $t_id, $f_bug_id);

                        echo $f_val;

                        if($t_def['name'] == 'SVN Revision' && !empty($f_val)) {

                                $ch = curl_init('http://svn.dev.evaristesys.com/bin/cgi/viewvc.cgi?view=rev'
                                              . '&root='.$svn_rep_name.'&simple=1&revision='.$f_val);

                                curl_setopt($ch, CURLOPT_HEADER, FALSE);
                                curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
                                curl_setopt($ch, CURL_NOBODY, FALSE);

                                $rev_response = curl_exec($ch);

                                curl_close($ch);

                                if(preg_match('/S+/', $rev_response))
                                        echo "n<br />n<br />n";

                                foreach(preg_split('/s+/', $rev_response) as $f_ch) {
                                        if(preg_match('/^S+$/', $f_ch)) {
                                                $base_f_stem = "http://svn.dev.evaristesys.com/bin/cgi/viewvc.cgi/$f_ch"
;

                                                $curr_file = "$base_f_stem?view=markup&root=$svn_rep_name&revision=$f_va
l";
                                                $curr_log = "$base_f_stem?view=log&root=$svn_rep_name&revision=$f_val";

                                                $curr_diff = "$base_f_stem?root=$svn_rep_name&r1=$f_val&r2=".
                                                                                ($f_val > 1 ? ((int) $f_val - 1) : $f
_val) .
                                                                                "&pathrev=$f_val";

                                               $curr_patch = "$base_f_stem?view=patch&root=$svn_rep_name&r1=$f_val&r2="
 .
                                                                                ($f_val > 1 ? ((int) $f_val - 1) : $f
_val) .
                                                                                "&pathrev=$f_val";

                                                echo '<table border="0" width="80%" align="left" cellspacing=0 cellpa
dding=0>';

                                                echo "  <tr>n" .
                                                 "     <td width=\"55%\" valign=\"top\">" .
                                                 "       <strong>" .
                                                 "         <a href=\"$curr_file\">$f_ch</a>" .
                                                 "       </strong>n" .
                                                 "     </td>n" .
                                                 "     <td width=\"15%\" valign=\"top\">n" .
                                                       "[ <a href=\"$curr_diff\">Diff</a> ]" .
                                                 "     </td>n" .
                                                 "     <td width=\"15%\" valign=\"top\">n" .
                                                       "[ <a href=\"$curr_log\">Log</a> ] " .
                                                 "     </td>n" .
                                                 "     <td width=\"15%\" valign=\"top\">n" .
                                                       "[ <a href=\"$curr_patch\">Patch</a> ]" .
                                                 "     </td>n" .
                                                 "  </tr>n";

                                                echo "</table>n";
                                        }

                                }
                        }

                        # -- End modifications by Alex Balashov -- # 

                        ?>

And here is the patch against the old snippet, in case you’ve made extensive modifications and would like to manually reconcile:

--- bug_view_page.php.old       2007-10-25 17:08:34.000000000 -0400
+++ bug_view_page.php   2007-10-25 17:27:39.000000000 -0400
@@ -338,6 +338,31 @@

 <!-- Custom Fields -->
 <?php
+       # -- Modifications by Alex Balashov -- #
+
+       /*
+        * Iterate through the custom fields and find the Target value
+        * pre-emptively before attempting any SVN linkage below.  This
+        * is necessary in our case as the Target field is met in the
+        * loop *after* the SVN Revision field, if for no other reason
+        * than alphabetical ordering.
+        *
+        * Obviously, this can be a performance hit;  it would be nicer to
+        * keep to one loop and push the results into an array.  However,
+        * I do not wish to modify the code so substantially, and besides,
+        * our population of custom fields is very small.
+        */
+
+       foreach(custom_field_get_linked_ids($t_bug->project_id) as $t_id) {
+               $t_def = custom_field_get_definition($t_id);
+
+               if($t_def['name'] == 'Target') {
+                       $svn_rep_name = string_custom_field_value($t_def, $t_id, $f_bug_id);
+               }
+       }
+
+       # -- End modifications by Alex Balashov -- #
+
        $t_custom_fields_found = false;
        $t_related_custom_field_ids = custom_field_get_linked_ids( $t_bug->project_id );
        foreach( $t_related_custom_field_ids as $t_id ) {
@@ -350,17 +375,23 @@
                        <?php echo string_display( lang_get_defaulted( $t_def['name'] ) ) ?>
                </td>
                <td colspan="5">
-                       <?php print_custom_field_value( $t_def, $t_id, $f_bug_id ); ?>
-
-                       <?php
-
+
+                       <?php
+
                        # -- Modifications by Alex Balashov -- #
-
+
                        $f_val = string_custom_field_value($t_def, $t_id, $f_bug_id);

+                       echo $f_val;
+
                        if($t_def['name'] == 'SVN Revision' && !empty($f_val)) {

-                               $ch = curl_init('http://svn.dev.evaristesys.com/bin/cgi/viewvc.cgi?view=rev&root=target-
eva&simple=1&revis
ion='.$f_val);
+
+                               $ch = curl_init('http://svn.dev.evaristesys.com/bin/cgi/viewvc.cgi?view=rev'
+                                             . '&root='.$svn_rep_name.'&simple=1&revision='.$f_val);

                                curl_setopt($ch, CURLOPT_HEADER, FALSE);
                                curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
@@ -375,16 +406,18 @@

                                foreach(preg_split('/s+/', $rev_response) as $f_ch) {
                                        if(preg_match('/^S+$/', $f_ch)) {
-                                               $base_f_stem = 'http://svn.dev.evaristesys.com/bin/cgi/viewvc.cgi/' . $f
_ch;
+                                               $base_f_stem = "http://svn.dev.evaristesys.com/bin/cgi/viewvc.cgi/$f_ch"
;

-                                               $curr_file = $base_f_stem . '?view=markup&root=target-eva&revision=' . $
f_val;
-                                               $curr_log = $base_f_stem . '?view=log&root=target-eva&revision=' . $f_va
l;
-                                               $curr_diff = $base_f_stem . '?root=target-eva&r1='.$f_val.'&r2='.
-                                                                               ((int) $f_val - 1) . '&pathrev=' .
-                                                                               $f_val;
-                                               $curr_patch = $base_f_stem . '?view=patch&root=target-eva&r1='.$f_val.'&
r2=' .
-                                                                               ((int) $f_val - 1) . '&pathrev=' .
-                                                                               $f_val;
+                                               $curr_file = "$base_f_stem?view=markup&root=$svn_rep_name&revision=$f_va
l";
+                                               $curr_log = "$base_f_stem?view=log&root=$svn_rep_name&revision=$f_val";
+
+                                               $curr_diff = "$base_f_stem?root=$svn_rep_name&r1=$f_val&r2=".
+                                                                               ($f_val > 1 ? ((int) $f_val - 1) : $f
_val) .
+                                                                               "&pathrev=$f_val";
+
+                                               $curr_patch = "$base_f_stem?view=patch&root=$svn_rep_name&r1=$f_val&r2="
 .
+                                                                               ($f_val > 1 ? ((int) $f_val - 1) : $f
_val) .
+                                                                               "&pathrev=$f_val";

                                                echo '<table border="0" width="80%" align="left" cellspacing=0 cellpa
dding=0>';

EVA Milestone 3 complete.

Posted in Releases by Alex Balashov on the October 25th, 2007

Here are the technical issue notes:

  • Add date field to eva_biling_proc_output to indicate when that row was computed.
  • Add billing run query plan constraint support.
  • Add triggers to automatically enter default options values for each transaction type when billing horizon created.
  • Add options table for processing specific to transaction type, horizon.
  • Add constraints by CDR date to billing_proc .
  • Add billing run stored procedure.
  • Implement apply_billing_vector handling and return billing run column.
  • Implement analyse_voice_bearing_time stored procedure and corresponding hooks in apply_billing_vector .
  • Update cascade dependency maintenance triggers for new tables / relations.
  • Design table to contain contents of billing runs.
  • Design complete schema s for billing vectors and evaluation rulesets.

We are now fully ready to begin work on the web application backend and interface.

Mitigating Firewalls and the SSH Session Timeout Problems They Can Cause

Posted in Tips by Alex Balashov on the October 24th, 2007

We have a problem related to our development server that has been vexing Kelly and I for a while. The colocation provider shunts all traffic destined for subnets routed to that cabinet on their Layer 3 switch through a PIX firewall. Unfortunately, the PIX’s stateful TCP session management behaves in such a way–not unlike with many firewalls–that the state information is aged out after ten or fifteen minutes of no activity on the connection stream, and traffic is no longer passed.

SSH is not filtered on the PIX; this is done locally on the server. However, the traffic still passes through it, and is admitted in a stateful manner.

This means that we cannot leave persistent SSH sessions open. They will lock up and then time out with a read error. This is a very grevious inconvenience when one has a dozen windows open and is not using them all simultaneously, or simply wishes to be able to walk away and return to something later.

The OpenSSH client’s TCPKeepAlive option, which is on by default according to the man page, ought to fix this problem. But it doesn’t. The frequency of the keepalive message interval is probably too low, or perhaps the firewall does not consider TCP-level keepalives to warrant resetting the state expiration timer on that mapping. This would be because the TCP keepalives are out-of-band, not within the actual data stream payload. It is likely that the PIX does not think such metadata constitutes “activity” on the connection.

The solution is the ServerAliveInterval option. From the man page:

             Sets a timeout interval in seconds after which if no data has
             been received from the server, ssh(1) will send a message through
             the encrypted channel to request a response from the server.  The
             default is 0, indicating that these messages will not be sent to
             the server, or 300 if the BatchMode option is set.  This option
             applies to protocol version 2 only.  ProtocolKeepAlives is a
             Debian-specific compatibility alias for this option.

Put this option in your /etc/ssh/ssh_config and you should be set.

Setting it to about 30 seconds did the trick here.

EVA Milestone 2 complete.

Posted in Releases by Alex Balashov on the October 18th, 2007

EVA Milestone 2 has been completed. This rounds off the development of the core OpenSER-based signaling and routing engine, enabling us to transition to a focus on the billing application itself.

Here are the technical release notes, which are broad summaries of issues addressed:

  • CDR event FAILURE treatment of 487 Request Terminated in response to CANCEL.
  • Add ability to pin ingress billing edge to particular egress billing edge statically.
  • STYLE: Rework complex joins in stored procedures to use JOIN statements.
  • Redesign several stored procedure(s) to take advantage of function overloading.
  • Add portwise validation and selection support for ingress and egress billing edge hosts.
  • Find better way to deal with duplicate dialogue messages not resulting from retransmission.
  • Additional CDR events for broad categories of call completion failures.
  • Add support for OPTIONS “ping” requests.
  • Add triggers for cascading deletes in order to automatically maintain database integrity.
  • Create separate unique index on call_id column of eva_cdr.
  • Standardise all stored procedures’ local variable naming convention.
  • Fix default data type argument issue to egress_billing_edge_host_by_customer from OpenSER.
  • Route ACKs.
  • Handle REGISTER routing.
  • INVITEs (and possibly other transactions) appear in duplicate from time to time, violating unique keys.
  • Complete support for all basic CDR events in the course of a SIP dialogue.
  • Defective call flow - no matching by global GUID for messages from far end when different atomic SIP transaction.
  • Clean up formatting of stored procedures in schema/eva_main_procedures_pgsql.sql.
  • AVPs need to be deleted after use.
  • Add URI rewriting that allows actual request forwarding to an egress billing edge.

Integrating Mantis + ViewVC + Subversion for source code view - a minor hack.

Posted in Hacks by Alex Balashov on the October 18th, 2007

A reasonable desire with which the user of the Mantis bug/issue tracking package may be afflicted is to somehow integrate references to Subversion source control repositories into Mantis issues in a way that allows easy viewing of unified diffs and current versions of affected files in the project tree corresponding to an issue.

Sharing this desire here at Evariste as I do, I will share my implementation. Of course, some variations on this theme have been implemented elsewhere; Digium’s modified Mantis comes to mind. However, it doesn’t quite do what we wanted. Besides, I wouldn’t want to deny my slight tendency toward NIH syndrome, something to which Mr. Storm and my good friend Clint over at IPUrbia can readily attest.

Also, there are lots of other interpretations of what it means to integrate Mantis and Subversion. One of the more pervasive currents seems to be to automate the interaction between Subversion commits and Mantis issue updates, as elucidated in detail here. This is interesting, but not what I had in mind. I want to view the source code changes that correspond to issues in a simple way.

While I’m at violating rule number zero of not alienating audiences, belabouring what this article is not about: this article assumes a background in Linux/UNIX system administration, and trusts that you can and/or have set up Apache, MySQL, Subversion, Mantis and ViewVC as unassociated applications on your own. It is for people whose concern lies with this type of integration, so step-by-step installation instructions from the very beginning for all or some of these applications are not provided. If you want this, well, accept my apologies - it may be the wrong article for you.

I chose ViewVC as a web interface to our Subversion repositories on account of its cleanliness and simplicity. You may prefer a different application. All is not lost; most of the actual changes required are to Mantis code, so the instructions should be adaptable.

Step 1: Add your SVN repositories to ViewVC.

Edit viewvc.conf in the root of the ViewVC installation directory:

[general]

## This setting specifies each of the CVS roots on your system and assigns
# names to them. Each root should be given by a "name: path" value. Multiple
# roots should be separated by commas and can be placed on separate lines.
#

#cvs_roots = cvs: /home/cvsroot

#

# This setting specifies each of the Subversion roots (repositories)
# on your system and assigns names to them. Each root should be given
# by a "name: path" value. Multiple roots should be separated by
# commas and can be placed on separate lines.

#

svn_roots = your_repository_name: /full/path/to/repository/root

All the other defaults appear to suffice.

Make sure that the actual physical repository files are readable by the effective UID / GID under which Apache runs. No write access is required.

ViewVC helpfully uses Python Subversion bindings, allowing it to access repository data directly on the “back” side. Obviously, this is only beneficial if the repository is located on the same server as the Apache installation serving ViewVC. But it spares all the headache that would come with configuring its interaction with ’svn’ as a wrapper, authentication, running the ’svnserve’ daemon, etc.

Step 2: Add an ‘SVN Revision’ custom field to Mantis.

This generally requires logging in with an >= Administrator-level account, adding a custom field, and binding it to a particular project.

Go to Manage -> Manage Custom Fields to add a custom field. The one I added is typed numerical.

Then go to Manage -> Manage Projects and bind the field to a project(s). I missed this the first time around; if you do not do this, the custom field will not show up in your Mantis issues.

It is probably a savoury idea to set the field presentation to display on all tickets, but set it to be required on Resolved/Closed changes.

Step 3: Verify that the ‘SVN Revision’ custom field is in fact present on issues for the relevant project(s).

Try close/resolve an issue and populate the field with a valid revision number.

Step 4: Patch ‘bug_view_page.php’ in the Mantis root.

I am running Mantis v1.0.8 (stable at the time of this writing). The area you are looking to modify is in ‘bug_view_page.php’ roughly around line #353. It is in a code block preceded by the comment

<!-- Custom Fields -->

Throw this in:


                <td colspan="5">
                        <?php print_custom_field_value( $t_def, $t_id, $f_bug_id
 ); ?>

<?php

   # -- Modifications by Alex Balashov -- #

   $f_val = string_custom_field_value($t_def, $t_id, $f_bug_id);

   if($t_def['name'] == 'SVN Revision' && !empty($f_val)) {
    print_bracket_link('http://path/to/viewvc/install/root/bin/cgi/viewvc.cgi?view=rev&root=your_repository_name&revision='.$f_val, 'View SVN');

   }

   # -- End modifications by Alex Balashov -- #
?>

And, at the risk of engendering an anticlimactic ending, there you have it. On issues with the ‘SVN Revision’ field populated, the [ View SVN ] link should take you to ViewVC’s summary of all files touched by the commit on that revision:

mantis-svn-field-crop.jpg

If this satisfies you, then we’re done.

I’m not satisfied. We’re not done. This is entirely too much clicking. I want to see the source files affected by a revision directly in the Mantis issue, along with quick and easy links to see the current revision of those files and to diffs from previous revisions.

This was going to require a more sophisticated approach.

I was initially going to implement this jack with AJAX + JSON, but ran into two problems. The first is that my Mantis and ViewVC installations are on different virtual hosts. Firefox, at least, will not let you call XMLHttpRequest.open(’GET’, …) to a URL on a different host from the current one. Not without messing with browser security settings in and outside the DOM. This is a security measure designed to prevent cross-site scripting attacks. Of course, I could have put in a target on the Mantis host and used Apache ‘mod_rewrite’ to fool it, I suppose, but that is not very useful or portable to the world at large.

The other problem is that our ViewVC installation is password-protected using the usual ‘.htaccess’-based means. The information could not have been pulled across without having the user log into the ViewVC session first, and that is an enourmous pain. Besides, what if the login session expires, and so on?

So, after a little thinking I went with a much simpler approach: PHP + CURL. I then modified our .htaccess to allow unrestricted access to ViewVC from the local server, but issue an authentication challenge to everyone else. Here’s how:

AuthUserFile /u/svc/www/sites/viewvc/.htpasswd
AuthType Basic
AuthName 'SVN Repository Viewer: '

<Limit GET POST>
        Order Allow,Deny
        Allow from our.server.source.ip
        Satisfy any
        Require valid-user
</Limit>

Step 5: Create new revision view query parameter in ViewVC code.

Note: I am not actually a Python programmer. This is unlikely to be the most elegant approach.

Edit ‘lib/viewvc.py’ in the ViewVC root. Find the definition of the function _validate_regex and insert a new validation rule for a parameter called ’simple’ in the ‘_legal_params’ dictionary:

  'rev' : _re_validate_revnum,
  'revision' : _re_validate_revnum,
  'content-type' : _re_validate_mimetype,
  'simple' : _re_validate_number,

Step 6: Modify ‘view_revision’ function to reference a different page template when ’simple’ parameter is set:

Change the line at the end of ‘view_revision’ from:

generate_page(request, "revision", data);

To:

  if request.query_dict.get('simple'):
    generate_page(request, "revision_simple", data)
  else:
    generate_page(request, "revision", data);

Step 7: Create ’simple’ revision template.

Create a file called ‘revision_simple.ezt’ inside the ‘templates’ directory in the ViewVC root. Put this in it:

[if-any changes][for changes][changes.path] [end][end]

Step 8: Verify that this template works.

Access: http://path/to/your/viewvc/root/bin/cgi/viewvc.cgi?view=rev&root=your_svn_repository&revision=some_valid_revision&simple=1

Step 9: Modify Mantis code referenced above, around line 353 in ‘bug_view_page.php’.

Replace the code snippet suggested above with the following:

                        <?php 

                        # -- Modifications by Alex Balashov -- #

                        $f_val = string_custom_field_value($t_def, $t_id, $f_bug_id);

                        if($t_def['name'] == 'SVN Revision' && !empty($f_val)) {

                                $ch = curl_init('http://path/to/your/viewvc/root/bin/cgi/viewvc.cgi?view=rev&root=your_svn_repository&simple=1
&revision='.$f_val);

                                curl_setopt($ch, CURLOPT_HEADER, FALSE);
                                curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
                                curl_setopt($ch, CURL_NOBODY, FALSE);

                                $rev_response = curl_exec($ch);

                                curl_close($ch);

                                if(preg_match('/S+/', $rev_response))
                                        echo "n<br />n<br />n";

                                foreach(preg_split('/s+/', $rev_response) as $f_ch) {
                                        if(preg_match('/^S+$/', $f_ch)) {
                                                $base_f_stem = 'http://path/to/your/viewvc/root/bin/cgi/viewvc.cgi/' . $f_ch;

                                                $curr_file = $base_f_stem . '?view=markup&root=your_svn_repository&revision=' . $f_val;
                                                $curr_log = $base_f_stem . '?view=log&root=your_svn_repository&revision=' . $f_val;
                                                $curr_diff = $base_f_stem . '?root=your_svpn_repository&r1='.$f_val.'&r2='.
                                                                                ((int) $f_val - 1) . '&pathrev=' .
                                                                                $f_val;
                                                $curr_patch = $base_f_stem . '?view=patch&root=your_svn_repository&r1='.$f_val.'&r2=' .
                                                                                ((int) $f_val - 1) . '&pathrev=' .
                                                                                $f_val;

                                                echo '<table border="0" width="80%" align="left" cellspacing=0 cellpadding=0>';

                                                echo "  <tr>n" .
                                                 "     <td width=\"55%\" valign=\"top\">" .
                                                 "       <strong>" .
                                                 "         <a href=\"$curr_file\">$f_ch</a>" .
                                                 "       </strong>n" .
                                                 "     </td>n" .
                                                 "     <td width=\"15%\" valign=\"top\">n" .
                                                       "[ <a href=\"$curr_diff\">Diff</a> ]" .
                                                 "     </td>n" .
                                                 "     <td width=\"15%\" valign=\"top\">n" .
                                                       "[ <a href=\"$curr_log\">Log</a> ] " .
                                                 "     </td>n" .
                                                 "     <td width=\"15%\" valign=\"top\">n" .
                                                       "[ <a href=\"$curr_patch\">Patch</a> ]" .
                                                 "     </td>n" .
                                                 "  </tr>n";

                                                echo "</table>n";
                                        }

                                }
                        }

                        # -- End modifications by Alex Balashov -- # 

                        ?>

Step 10: Enjoy:

mantis-svn-fully-linked.jpg

Isn’t that much better? |

Early [backward] media.

Posted in Rants by Alex Balashov on the October 16th, 2007

I have a new pet peeve: early media / musical ringback applications.

Here I am, testing Global Crossing call completion to a variety of international destinations. About half the time I find myself the bewildered and unwitting audience to some sort of screeching music immediately upon completion of the signaling handshake. By the time these sounds undergo the formidable attenuation, noise, signal regeneration, crosstalk and transcoding that is brought to bear on the end-to-end voice quality of an international call, across a vast span of heterogenous network elements and transmission spans circling half the globe, I am moved to worry that I have troubled the bright splendour of sunbaked African grasslands with a disruptive call into a herd of mating zebras.

Yeah, you know how it is when the phone rings inopportunely; can’t you see I’m busy here? I should feel bad.

Early media is a method by which the perfectly cordial and civilised process of in-band ringback tone generation in inter-switch call setup (”alerting”, “call in progress”) disasterously fails in its misguided aspirations, rather akin to some grotesque mutation, perhaps an infected limb, or just a plainly nasty accident. Ringback noise is often mixed into the voice stream in-band in traditional telephony, rather than out-of-band progress/alerting messages relying on the proximate sending switch to generate the ringback toward the caller. This means you can theoretically send whatever audio you want as a backward acknowledgment toward the calling end before the call is actually answered, in a formal sense.

This has become popular with increasing numbers of mobile phone users. It is also somewhat supported in SIP, where it takes the form of a

200 OK

+
immediate one-way RTP
being sent in place of a 1xx-class provisional message (normally

183 Session In Progress

, and possibly

180 Ringing

).

I suppose it is possible that this has some legitimate applications in IVR systems, as the RFC suggests, or in situations where lengthy and continuous ringing time on the far end is desired, although I am somewhat at a loss for confidence to underwrite that claim.

However, music does not belong as ringback on ordinary phone calls, and least of all international ones.

Look, the telephone is not a music player. I’ll let music-on-hold slide because it does serve an understandable diversionary purpose. But this is just flippant, unprofessional, and childish. Look, it’s even atrophied the dignity of this rant’s tone to its own level. Truly unbecoming.

Really, this article could have been rendered in much shorter and less abusive form with my friend Robin Green’s terse pronouncement on this topic: “Just because you can, doesn’t mean you should.” Quite probably the single most relevant and urgent prescription for many features of the time in which we live, this aforementioned one being the least significant.

I slipped and fell on some great customer service.

Posted in Vendors by storm on the September 12th, 2007

After gathering quotes from various online and local printing companies, Alex and I agreed to invest in VistaPrint because of their excellent pricing and broad product base. We needed to go with someone who could not only print a few thousand business cards in three days, but also print tens of thousands of brochures and letterheads as well as shirts, accessories, etc. when the time came.

The initial buy was quick and painless since we designed the business cards ourselves and uploaded them via VistaPrint’s web application. After fifteen minutes of thoroughly configuring our business cards through their well-designed, snappy web store, the cards were purchased and on their way to be printed and shipped - when all was said and done we had a quality product overnighted for well under half the price of the other printing companies we researched.

Three days later, the business cards arrived and I quickly ripped open the package as if it were Christmas. Much to my dismay, they did not turn out as expected. The card’s lower left hand and upper right hand corners bleed, which means they have color that goes all the way to the edge of the business card. The cards they had printed and shipped–the cards we designed–did not bleed at all. The little corner bleed that did show up on some of the specimens did not align dimensionally with the stock at all. They looked as if the shade was rubber-stamped on them. So what we ended up with were thousands of business cards that were radically at odds with the design and wholly unsuitable for professional use.

After scouring the Terms of Service and agreement I had signed before purchasing the cards, I decided that this mistake warranted a complaint to the customer service department. If anything, I simply wanted them to print the business cards how we designed them.

Before dialing the number, though, it should be stated that I thought this was a foregone conclusion; a mistake at best. Rarely have I ever had a great customer service experience, and there’s a ridiculous amount of flame mail out there to turn anyone off from ever calling any company’s customer service hotline. After waiting on hold for about fifteen minutes, Ralston, the VistaPrint customer service representative, answered my call.

What unfolded during our fifteen minute conversation was wholly unexpected.

After hearing my complaint, Ralston agreed that it was indeed not our fault that the cards weren’t printed to spec - it simply happens sometimes. He put me on hold for about five minutes and I figured he was looking for a way out of having to refund our money or reprint the cards, but once he returned he had not only fixed the design for our business cards so that they would bleed to the edges of the card, but refunded our money (all of it, even the shipping fees), sent the new proofs directly to my email address, set up a shopping cart with an order for the redesigned business cards, and gave us $25 store credit.

I’m not exaggerating, five minutes. This, I thought, was the best customer service experience I have ever had, anywhere.

Ralston made some constructive changes to the proofs. Once I approved the modifications, the order was pushed through and set up to be air-shipped to my address as soon as they were printed. I received an automated email from VistaPrint requesting feedback for my customer service, and boy was I happy to give. Alex wanted me to post said feedback here, but it was done through a web form, so I don’t have it saved. However, I’ll be glad to regurgitate what I remember.

“VistaPrint. Wow. Wow. Thank you. I truly appreciate the patience and understanding expressed by your customer service representative, Ralston, and your willingness as a company to right your wrongs in an efficient, friendly manner. We will most certainly do business with you in the future, as well as refer any and all of our business acquaintances to you.”

We decided it would be a good idea to submit this article to the Consumerist as well - and VistaPrint, if you’re reading, thanks again.

The old adage holds true; failures are widely disseminated, while success goes quietly unrecognized, which is unfortunate for a great vendor like VistaPrint. Please know that your level of service and accommodation, underreported as it may be, does not go unnoticed or unappreciated.

**UPDATE**  The new business cards arrived in the mail today (a day early, no less) and they turned out looking exactly like the proofs Ralston showed me a few days back. New business cards with proper bleed on the corners in 3 days flat, free of charge (with $25 store credit). This company is all about the customer, clearly.

IP Telephony and Agent Sales.

Posted in Insightful Punditry by Alex Balashov on the August 30th, 2007

By way of Peter at RAD-INFO, there is an interesting post on the Phone+ Peer-to-Peer Blog by Dan Baldwin, Sales Director at ATEL Communications.

It relates some keen observations — in connection with his mission to VoiceCon 2007 — about SMB and enterprise acceptance of IP telephony, especially in relation to agent sales channels.

Next Page »