Openvpn

This post is a reminder to my brain who lately likes to forget basic things such an iptable rule to allow VPN clients to access internal LAN.

Yes, this afternoon I spent almost an hour and a half to try to figure out what went wrong with my newly installed openvpn. I was unable to access the LAN behind the server and I literally spent 90 minutes looking in the wrong place because my brain betrayed me and I completely forgot about adding a MASQUERADE rule to my firewall.

In my defense, I had problems with the installation, dependency problems and something close to the DLL hell that Windows has. This situation tricked me a lot because I assumed   this was the cause but when I decided to tcpdump the requests I saw the following:

[bash]

# tcpdump -i tun0
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on tun0, link-type RAW (Raw IP), capture size 65535 bytes
17:27:38.136712 IP 10.8.0.6 > 192.168.0.1: ICMP echo request, id 30234, seq 1, length 64
17:27:39.137333 IP 10.8.0.6 > 192.168.0.1: ICMP echo request, id 30234, seq 2, length 64

[/bash]

What! direct ping from tun to an IP visible only from eth0? Then I realized that I missed the:

[bash]

# iptables -t nat -A POSTROUTING -j MASQUERADE

[/bash]

Voila!

[bash]

# tcpdump -i tun0
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on tun0, link-type RAW (Raw IP), capture size 65535 bytes

17:39:55.968160 IP 10.8.0.6 > 192.168.0.1: ICMP echo request, id 30234, seq 738, length 64

17:39:55.968757 IP 192.168.0.1 > 10.8.0.6: ICMP echo reply, id 30234, seq 738, length 64

[/bash]

I must have installed openvpn at least 4 times before today, but still wasn’t enough to remember the right thing at the right moment. Won’t happen again, hopefully :D.

The magic of CLI

You want to turn something like this:
[bash]
chartsales_messages_es.properties
chartsales_messages.properties
closedpos_messages_es.properties
closedpos_messages.properties
closedproducts_messages_es.properties
closedproducts_messages.properties
customersdiary_messages_es.properties
customersdiary_messages.properties
customers_messages_es.properties
customers_messages.properties
inventoryb_messages_es.properties
inventoryb_messages.properties
inventorydiff_messages_es.properties
inventorydiff_messages.properties
inventory_messages_es.properties
inventory_messages.properties
people_messages_es.properties
people_messages.properties
productlabels_messages_es.properties
productlabels_messages.properties
productsales_messages_es.properties
productsales_messages.properties
productscatalog_messages_es.properties
productscatalog_messages.properties
products_messages_es.properties
products_messages.properties
taxes_messages_es.properties
taxes_messages.properties
usersales_messages_es.properties
usersales_messages.properties
[/bash]

into this:

[bash]
chartsales_messages.properties
chartsales_messages.properties
closedpos_messages.properties
closedpos_messages.properties
closedproducts_messages.properties
closedproducts_messages.properties
customersdiary_messages.properties
customersdiary_messages.properties
customers_messages.properties
customers_messages.properties
inventoryb_messages.properties
inventoryb_messages.properties
inventorydiff_messages.properties
inventorydiff_messages.properties
inventory_messages.properties
inventory_messages.properties
people_messages.properties
people_messages.properties
productlabels_messages.properties
productlabels_messages.properties
productsales_messages.properties
productsales_messages.properties
productscatalog_messages.properties
productscatalog_messages.properties
products_messages.properties
products_messages.properties
taxes_messages.properties
taxes_messages.properties
usersales_messages.properties
usersales_messages.properties
[/bash]

I can’t imagine an easy way to do it with Windows but with bash and any Unix like OS, you get it done in less than a minute.

[bash]
for line in $(ls *.properties)
do
mv $line `echo $line | sed “s/_es.properties/.properties/”`
done;
[/bash]

Dialer: tracking calls with Asterisk/AMI

Long story short: I was asked to write a real time dialer callable from a web page that takes the number from the parameters received, make the call, and play a pre-recorded message when the called picks up. This post is not about the dialer itself, it’s about tracking the call originated by it, detect the “answer” event, detect the “hangup” event and save a CDR.

The approach I chose involves AMI to generate and track the calls. It offers a really nice interface based on a text protocol and the most important thing, the API can be mapped in almost any programming language like C# (Asterisk.NET).

Some of the protocol internals

  1. The connection is made via TCP/IP sockets using user/password authentication.
  2. Once logged in, you receive every event on the system, even if that event doesn’t belong to you. This could be a real mess because it introduces a lot of noise into the dialer and the software must support some kind of filtering mechanism.
  3. Every event comes with certain data, including a field named Uniqueid which relates the events to a transaction.
  4. The order the events were received not necessarily match the order they were generated.

The native way to generate a call is through the OriginateAction but the problem is that OriginateAction doesn’t reply with the uniqueID of the transaction, it just tells you whether the call was successfully queued or not. Then, you start receiving the events with the uniqueIDs but you have no way to know if that call belongs to you, to another dialer instance, to a human making a call, etc.

Searching the web I found what I thought was the solution: use asynchronous origination.  The usual OriginateAction is a blocking call that returns only if success or failure. With async origination the answer to the request is delivered through the OriginateResponse event and with the parameters you receive, the so wanted uniqueID. I built the dialer on top of this “fix” just to later discover that if the call fails, the uniqueID is empty. Once again, you have no way to know which call failed and for what reason.

I was so pissed off because something as basic as this wasn’t correctly available, or at least, there is no documentation to expose an standard solution. I started considering parallel approaches to abandon AMI but reading the input parameters for OriginateAction a dirty workaround came to my mind.

OriginateAction takes as input a field named CallerID, this field is always available and always filled with information. I used this field to uniquely identify the call whether it succeeded or not. In my particular case, the calls goes to a GSM network so overwriting the callerID has no effect and the call follows its normal destination.

Bellow is part of the dialer class. If you can read C#, it would explain itself.

[csharp]

public class Dialer
{
ManagerConnection _managerConnection;
Timer _timer;
public delegate void CallFinalizedCallback(Dialer sender);
string _uniqueID = string.Empty;
bool _wasOriginated = false;
object _locker = new object();

List<HangupEvent> _hangupList = new List<HangupEvent>();

CallFinalizedCallback _callFinalized;
public CallFinalizedCallback CallFinalized
{
get {
return this._callFinalized;
}
set {
_callFinalized = value;
}
}

CallInfo _callInfo;
public CallInfo CallInfo
{
get
{
return this._callInfo;
}
}

public Dialer()
{
_managerConnection = new ManagerConnection(Configuration.ManagerHost,
Configuration.ManagerPort,
Configuration.ManagerUserName,
Configuration.ManagerPassword);

//_timer = new Timer(Handle_timeoutCallback, Configuration.CallNotificationTimeout);

AddEventHandlers();
}

void Handle_managerConnectionOriginateResponse (object sender, OriginateResponseEvent e)

{
if (!e.CallerIdNum.Equals(_callInfo.CallId.ToString()))
return;

Logger.Print(string.Format(“Call to {0}-{1} was originated”, _callInfo.CallId, e.Exten));

bool succeeded = e.Response.ToLower().Equals(“success”);

if (succeeded)
UpdateDialoutQueue(CallStatus.Established);
else
UpdateDialoutQueue(CallStatus.FinishedWithError);

lock(_locker)
{
_wasOriginated = true;
_uniqueID = _callInfo.CallId.ToString();
}

foreach(HangupEvent hEvent in _hangupList)
{
if (hEvent.CallerIdNum.Equals(e.CallerIdNum)) // the call failed for some reason
{
Handle_managerConnectionHangup(this, hEvent);
break;
}
}
_hangupList.Clear();

}

public Dialer(CallInfo callInfo, CallFinalizedCallback callback) : this()
{
_callInfo = callInfo;

if (callback == null)
throw new ArgumentException(“CallFinalizedCallback cannot be null”);

_callFinalized = callback;
}

void AddEventHandlers()
{
_managerConnection.Hangup += Handle_managerConnectionHangup;
_managerConnection.OriginateResponse += Handle_managerConnectionOriginateResponse;
}

public void DisconnectFromAMI()
{
_managerConnection.Hangup -= Handle_managerConnectionHangup;
_managerConnection.OriginateResponse -= Handle_managerConnectionOriginateResponse;
}

void Handle_timeoutCallback()
{
try
{
Logger.Print(string.Format(“Call {0}-{1} forced to finalize after timeout”, _callInfo.CallId, _callInfo.Extension));

UpdateDialoutQueue(CallStatus.Timedout);
//_timer.CancelAlarm();

if (_callFinalized != null)
_callFinalized(this);

DisconnectFromAMI();
}
catch(Exception ex)
{
Logger.Error(string.Format(“Handle_timeoutCallback: {0}”, ex.Message));
DisconnectFromAMI();
}
}

void Handle_managerConnectionHangup(object sender, HangupEvent e)
{
try
{
lock(_locker)
if (!_wasOriginated || !_uniqueID.Equals(e.CallerIdNum))
{
_hangupList.Add(e); // register this event in case it was fired before the OriginateAction event
return;
}

UpdateDialoutQueue(CallStatus.Finished, e.Cause, e.CauseTxt);
//_timer.CancelAlarm();

Logger.Print(string.Format(“Call {0}-{1} was hung up”, _callInfo.CallId, e.Channel));

if (_callFinalized != null)
_callFinalized(this);

DisconnectFromAMI();
}
catch(Exception ex)
{
Logger.Error(string.Format(“Handle_managerConnectionHangup: {0}”, ex.Message));
DisconnectFromAMI();
}
}

public void Call()
{
Call(_callInfo);
}

public void Call(CallInfo callInfo)
{
try
{
OriginateAction originate = new OriginateAction();

originate.Channel = callInfo.Channel;
originate.Priority = callInfo.Priority;
originate.Context = callInfo.Context;
originate.Exten = callInfo.Extension;
originate.CallerId = callInfo.CallId.ToString(); // <——–
originate.Timeout = 500000;
originate.Async = true;

_managerConnection.Login();

MarkCallAsProcessed();

//_timer.SetAlarm();

UpdateDialoutQueue(CallStatus.Trying);
ManagerResponse r = _managerConnection.SendAction(originate, originate.Timeout);
}
catch(Exception ex)
{
Logger.Error(string.Format(“Call: {0}”, ex.Message));

//_timer.CancelAlarm();

if (ex is Asterisk.NET.Manager.TimeoutException)
{
UpdateDialoutQueue(CallStatus.Timedout);
DisconnectFromAMI();
}
else
UpdateDialoutQueue(CallStatus.FinishedWithError);
}
}
}

[/csharp]