Attachment: https://bennettadelson.wordpress.com/2012/06/04/using-udp-sip-with-exchange-um-and-lync-2010/kamailio-cfg/ (remember to change extension)
Attachment: asterisk.tar.gz (remember to change extension)
I am working on and off with a client that is deploying Exchange 2010 Unified Messaging and Lync 2010 in their environment. They want to use Exchange UM with a hosted SIP-based VoIP system from a provider that I will refer to as “PhoneCo” for the sake of discussion. Furthermore, they want their Lync environment to work with the Exchange voicemail, and by the way, think it would be nice if they could experiment with Enterprise Voice functionality. Luckily, PhoneCo offers SIP trunks, and will trunk from the hosted VoIP environment to Exchange UM. So all is good, right?
The Problem Statement
Ha ha, of course I am joking. Because although Microsoft talks SIP, and PhoneCo talks SIP, we hit upon a long-standing issue. Microsoft refuses to support UDP SIP (they have their reasons, I won’t debate the point here) while PhoneCo refuses to support TCP SIP. Thus, we have an impasse.
Solution Overview
The official, standard answer to this is to use a Session Border Controller (SBC), which is essentially a SIP middleman box that can do UDP on one end and TCP on the other. A typical SBC also includes firewalling intelligence to prevent denial-of-service and other such nasty behavior. As a result, they generally start at thousands and quickly get into tens of thousands of dollars. In this customer’s case, the SIP trunk is going to be over a private MPLS connection directly between the hosted PBX and the on-premises Microsoft tools, so the customer didn’t want to pay for a lot of security they didn’t need just to deal with this issue.
The customer found a commercial product named Brekeke SIP Server that appears to be $500 to start. This is nice in that (1) it is commercial and (2) it can run on Windows, although it is Java-based so it’s a little messy and gives you one more thing to deal with patching every day or two.
We wanted to see if there was an open-source way to solve this problem. We found a way, and this post documented what we came up with. I have replicated the scenario in a lab, and have since actually simplified things a bit. I have also corrected something we had done to work around an Asterisk “bug” (in quotes because the bug states it’s not really an Asterisk bug) that came up while we were simulating the PhoneCo setup.
So first, here’s the list of VMs that are in the UC Lab:
Hostname | IP | Description |
dc.uclab.local |
172.30.1.10 |
Domain Controller |
exchange.uclab.local |
172.30.1.12 |
Exchange 2010 |
freepbx.uclab.local |
172.30.1.11 |
PhoneCo stand-in |
lync.uclab.local |
172.30.1.13 |
Lync 2010 |
siprouter.uclab.local |
172.30.1.14 |
SIP middleman |
tmg.uclab.local |
172.30.1.1 |
TMG 2010 |
internalclient.uclab.local |
172.30.1.100 |
Test Lync/SIP client |
The PhoneCo stand-in is a FreePBX installation using the FreePBX Linux distribution. I am not going to go into details on installing that into a VM because there are plenty of guides on getting that to work. For the purposes of this post I’m going to pretend Asterisk can’t do TCP SIP because that’s what we are looking at with PhoneCo. This also means ignoring all the online info about getting Asterisk to talk to Lync and Exchange using TCP SIP. (Note: Some of these guides assuming port 5065 for talking to Exchange, which is a partial solution. I’ll get into why that’s wrong later on.)
The SIP middleman – SIP router – is a Linux-based CentOS machine running the Kamailio open-source SIP router package. Kamailio is a mature, solid package that is quite amazing in some of what it can do, but I’m ignoring about 99% of it, I think. We may end up needing some of the NAT support eventually at the client, which I’m not getting into here and don’t need for the lab, but otherwise a lot of functionality is actually not in play here.
Preparing the CentOS Machine
So let’s get to it.
- I began with a basic minimal CentOS 6.2 installation. Note that I’ve had repeated issues with the Hyper-V Integration Components on this OS so far, so I didn’t bother with them – for a lab it’s not critical. For production you’d care a lot more – the customer uses VMware so this particular issue did not come up.
- Next, I logged in as root via SSH (PuTTY is your friend here) and accepted the key when prompted:
- I ran
yum update
to get all of the current updates for the OS, and rebooted to get the updated kernel loaded. - Using vi, I created
/etc/yum.repos.d/kamailio.repo
with:[kamailio] name=Kamailio baseurl=http://download.opensuse.org/repositories/home:/kamailio:/telephony/CentOS_CentOS-6/ enabled=1 gpgcheck=0
This looks like this:
- I confirmed that the new repository was visible with
yum repolist
: - I then confirmed that there was a package I could install in that repository with
yum list kamailio
:
- After confirming the package, I installed it with
yum install kamailio
:
- So now I need to configure the beast. Kamailio comes with a very long sample configuration file. Most of it is noise for my use. I tried to trim it down as safely as possible, as well as better fit what I wanted. So using the following commands I saved the shipped file:
cd /etc/kamailio mv kamailio.cfg kamailio.cfg.original vi /etc/kamailio.cfg
And then made mine, which I will explain later after finishing the build instructions:
#!KAMAILIO # Remote Hosts #!subst "/SIP_UDP_HOST/172.30.1.11/" #!subst "/EXCHANGE_UM/172.30.1.12/" #!subst "/LYNC_MEDIATION/172.30.1.13/" listen=172.30.1.14:5060 listen=172.30.1.14:5065 listen=172.30.1.14:5067 ####### Global Parameters ######### memdbg=5 memlog=5 debug=2 log_facility=LOG_LOCAL0 fork=yes children=4 disable_tcp=no auto_aliases=no /* uncomment and configure the following line if you want Kamailio to bind on a specific interface/port/proto (default bind on all available) */ #listen=udp:10.0.0.10:5060 # life time of TCP connection when there is no traffic # - a bit higher than registration expires to cope with UA behind NAT tcp_connection_lifetime=3605 ####### Modules Section ######## mpath="/usr/lib/kamailio/modules_k/:/usr/lib/kamailio/modules/" loadmodule "kex.so" loadmodule "tm.so" loadmodule "tmx.so" loadmodule "sl.so" loadmodule "pv.so" loadmodule "maxfwd.so" loadmodule "usrloc.so" loadmodule "textops.so" loadmodule "siputils.so" loadmodule "xlog.so" loadmodule "sanity.so" loadmodule "ctl.so" loadmodule "cfg_rpc.so" loadmodule "mi_rpc.so" # ----- tm params ----- # auto-discard branches from previous serial forking leg modparam("tm", "failure_reply_mode", 3) # default retransmission timeout: 30sec modparam("tm", "fr_timer", 30000) # default invite retransmission timeout after 1xx: 120sec modparam("tm", "fr_inv_timer", 120000) server_header="Server: PhoneCo Intransigence Coping Solution (PICS) 2.0"; ####### Routing Logic ######## route { if(is_method("OPTIONS")) { xlog("L_INFO","OPTIONS from $si"); sl_send_reply("200", "Yes, Microsoft, I am alive"); exit(); } xlog("L_INFO", "*** M=$rm RURI=$ru F=$fu T=$tu IP=$si ID=$ci"); # Route Exchange extensions if((to_uri=~"sip:5992") || (to_uri=~"sip:5999")) { xlog("L_NOTICE", "EXCHANGE UM call, $proto port $op, $ru, $fU"); t_on_reply("1"); # https://issues.asterisk.org/jira/browse/ASTERISK-16862 # http://imaucblog.com/archive/2009/10/03/part-1-how-to-integrate-exchange-2010-or-2007-with-trixbox-2-8/ replace("Diversion: <sip:5999@SIP_UDP_HOST>;reason=unconditional","MCB-Stripped-Header: Diversion"); switch($op) { case 5060: xlog("L_NOTICE", "Redirecting to TCP 5060"); t_relay_to("tcp:EXCHANGE_UM:5060"); exit(); break; case 5065: xlog("L_NOTICE", "Redirecting to TCP 5065"); t_relay_to("tcp:EXCHANGE_UM:5065"); exit(); break; case 5067: xlog("L_NOTICE", "Redirecting to TCP 5067"); t_relay_to("tcp:EXCHANGE_UM:5067"); exit(); break; } } # Route Lync extensions if(to_uri=~"sip:5...") { replace("To: <sip:", "To: <sip:+"); xlog("L_NOTICE", "LYNC call to $tu"); t_relay_to("tcp:LYNC_MEDIATION:5068"); exit(); } # Route the rest to Asterisk xlog("L_NOTICE", "Asterisk call to $tu"); forward_udp("SIP_UDP_HOST", 5060); } onreply_route[1] { xlog("L_NOTICE", "Handling reply from Exchange relay, status $rs"); switch($rs) { case 302: xlog("L_NOTICE", "Saw 302 Redirect response, checking details..."); if(search(";transport=Tcp")) { xlog("L_NOTICE", "Saw TCP redirection, changing redirection to UDP"); replace(";transport=Tcp", ";transport=Udp"); } else { xlog("L_NOTICE", "302 was not matched (!)"); } exit(); break; case 100: xlog("L_NOTICE", "Saw 100, leaving alone..."); exit(); break; } }
- I stared the daemon (read: service) with
/etc/rc.d/init.d/kamailio start
and confirmed that it started with/etc/rc.d/init.d/kamailio status
: - I confirmed it was listening (
netstat –an | grep 506
): - I then opened up the firewall to allow those ports in (okay, thats a lie – I floundered a bit before remembering I had to do this) by editing
/etc/sysconfig/iptables
and adding after the--dport 22
line:-A INPUT -p tcp -m state --state NEW -m tcp --dport 5060 -j ACCEPT -A INPUT -p tcp -m state --state NEW -m tcp --dport 5065 -j ACCEPT -A INPUT -p tcp -m state --state NEW -m tcp --dport 5067 -j ACCEPT -A INPUT -p udp -m state --state NEW -m udp --dport 5060 -j ACCEPT -A INPUT -p udp -m state --state NEW -m udp --dport 5065 -j ACCEPT -A INPUT -p udp -m state --state NEW -m udp --dport 5067 -j ACCEPT
- I then made this kick in by restarting the firewall with
/etc/rc.d/init.d/iptables restart
. - I next added system logger support for the configured log source by editing
/etc/rsyslog.conf
and adding:local0.* /var/log/kamailio.log
- I then made this kick in by reloading the logger configuration with
/etc/rc.d/init.d/syslog reload.
- I don’t want this log to grow uncontrollably so I configured the
logrotate
daemon to make a new log every day and save seven of them by creating/etc/logrotate.d/kamailio
with:/var/log/kamailio.log { rotate 7 missingok daily }
Preparing Exchange 2010 and Lync 2010
This is normal Exchange and Lync SIP configuration so I’m not going to get into great detail here. The following are the key points:
- Make sure Lync has a TCP listener on port
5068
on the mediation server of your choice. There’s no high availability here so pick one and go. As quick hints of where this is done in Topology Builder:
After publishing and runningBootstrapper
(Lync Setup) on the Mediation Server as instructed by Topology Builder I ran into (what I consider to be) a bug in Lync shown via the event log – there wereLS Mediation Server
messages25075
and25031
indicating no TCP port is enabled, then that the TCP port was requested but ignored. Restarting theMediation Server
service sorted it out. The Kamailio log will show this working (e. g.tail /var/log/kamailio.log
):
- For Exchange, make sure you have TCP enabled on the UM server (requires a service restart to kick in) and that you have an appropriate IP gateway and unsecured telephone extension dial plan configured against that gateway:
And that’s it!
So What Does the Configuration Mean?
OK, so what the heck does the configuration I gave you above mean? Let’s go through it:
#!KAMAILIO
This is a signature for the configuration file.
# Remote Hosts #!subst "/SIP_UDP_HOST/172.30.1.11/" #!subst "/EXCHANGE_UM/172.30.1.12/" #!subst "/LYNC_MEDIATION/172.30.1.13/" listen=172.30.1.14:5060 listen=172.30.1.14:5065 listen=172.30.1.14:5067
This is the super important customization part. The three subst
lines replace all references to those text strings with the appropriate IP addresses, while the listen lines allow the router to accept traffic on its IP on three ports – 5060
, 5065
, and 5067
. The latter two are because Exchange – for reasons known to Microsoft but not me – takes UM connections on port 5060
but then redirects them to 5065
or 5067
. Remember how above I said that some sites use 5065
and that’s wrong? That’s because they are assuming all redirects are to 5065
, but Exchange might want 5067
.
Anyway, the next lines are some configuration stuff that is from the default that I left alone mainly because either the settings were fine (e. g. the syslog facility used) or because I didn’t know the implications in changing them (e. g. the children process count); there’s also the enabling of TCP (normally disabled):
####### Global Parameters ######### memdbg=5 memlog=5 debug=2 log_facility=LOG_LOCAL0 fork=yes children=4 disable_tcp=no auto_aliases=no # life time of TCP connection when there is no traffic # - a bit higher than registration expires to cope with UA behind NAT tcp_connection_lifetime=3605
Next are the modules that I am loading. I know I need some of these for sure – there are others I don’t know about so I left well-enough alone and kept them there:
####### Modules Section ######## mpath="/usr/lib/kamailio/modules_k/:/usr/lib/kamailio/modules/" loadmodule "kex.so" loadmodule "tm.so" loadmodule "tmx.so" loadmodule "sl.so" loadmodule "pv.so" loadmodule "maxfwd.so" loadmodule "usrloc.so" loadmodule "textops.so" loadmodule "siputils.so" loadmodule "xlog.so" loadmodule "sanity.so" loadmodule "ctl.so" loadmodule "cfg_rpc.so" loadmodule "mi_rpc.so" # ----- tm params ----- # auto-discard branches from previous serial forking leg modparam("tm", "failure_reply_mode", 3) # default retransmission timeout: 30sec modparam("tm", "fr_timer", 30000) # default invite retransmission timeout after 1xx: 120sec modparam("tm", "fr_inv_timer", 120000)
The next line sets a server header seen in the SIP headers. It is a fun way to point out that PhoneCo was annoying me as well as to hide the actual software being used:
server_header="Server: PhoneCo Intransigence Coping Solution (PICS) 2.0"
Now comes the real meat. It starts the routing logic for incoming SIP calls looking for the OPTIONS call that Lync and Exchange make every nanosecond (approximately) to check to see if their SIP peers are alive. Hence the status text – the code is all that really matters:
####### Routing Logic ######## route { if(is_method("OPTIONS")) { xlog("L_INFO","OPTIONS from $si"); sl_send_reply("200", "Yes, Microsoft, I am alive"); exit(); }
The next line just acts as a debugging log showing what came in:
xlog("L_INFO", "*** M=$rm RURI=$ru F=$fu T=$tu IP=$si ID=$ci");
The dollar-sign pseudo-variables are documented here, should you care: http://www.kamailio.org/wiki/cookbooks/3.2.x/pseudovariables
Anyway, moving on, we have the Exchange routing. Looking at this now, I probably want the two extensions (one for the auto-attendant and one for subscriber access) to be substituted variables, but that will be 2.1 I guess:
# Route Exchange extensions if((to_uri=~"sip:5992") || (to_uri=~"sip:5999")) { xlog("L_NOTICE", "EXCHANGE UM call, $proto port $op, $ru, $fU"); t_on_reply("1");
This basically says “if a SIP call is made to extension 5992 or extension 5999, then do this…” and starts by indicating that we are going to do a transactional SIP redirect that, when we see a reply, should go to reply handler “1
“, which will come later. After that, we have:
# https://issues.asterisk.org/jira/browse/ASTERISK-16862 # http://imaucblog.com/archive/2009/10/03/part-1-how-to-integrate-exchange-2010-or-2007-with-trixbox-2-8/ replace("Diversion: <sip:5999@SIP_UDP_HOST>;reason=unconditional","MCB-Stripped-Header: Diversion");
Why is this here? Basically, Asterisk does something we don’t want it to do on the Exchange redirect – adds an extra SIP Diversion
header – and we want that extra header to go away. I need to replace it with something though, so I just made up a vendor header and used that. This is safe as SIP agents – like HTTP server and clients – ignore headers that they don’t know. Next, we take the UDP session and do a transactional redirect to TCP:
switch($op) { case 5060: xlog("L_NOTICE", "Redirecting to TCP 5060"); t_relay_to("tcp:EXCHANGE_UM:5060"); exit(); break; case 5065: xlog("L_NOTICE", "Redirecting to TCP 5065"); t_relay_to("tcp:EXCHANGE_UM:5065"); exit(); break; case 5067: xlog("L_NOTICE", "Redirecting to TCP 5067"); t_relay_to("tcp:EXCHANGE_UM:5067"); exit(); break; } }
I couldn’t come up with a “smart” way to do this better; this is a little wordy but it is clear what is happening. I next route the Lync calls (adding the E.164 “+
” sign along the way) based on extension pattern (all other 5xxx
extensions besides the two special case ones above), with all others going to the Asterisk side:
# Route Lync extensions if(to_uri=~"sip:5...") { replace("To: <sip:", "To: <sip:+"); xlog("L_NOTICE", "LYNC call to $tu"); t_relay_to("tcp:LYNC_MEDIATION:5068"); exit(); } # Route the rest to Asterisk xlog("L_NOTICE", "Asterisk call to $tu"); forward_udp("SIP_UDP_HOST", 5060); }
Notice that I do forward_udp
instead of t_relay_to
because I don’t care about maintaining transactional state in the case of going back to Asterisk, so there’s no reason to waste resources on it. I just tell Kamailio to throw it over the wall and forget about it.
Finally, I handle the reply from Exchange. This is why I made the Exchange piece transactional:
onreply_route[1] { xlog("L_NOTICE", "Handling reply from Exchange relay, status $rs"); switch($rs) { case 302: xlog("L_NOTICE", "Saw 302 Redirect response, checking details..."); if(search(";transport=Tcp")) { xlog("L_NOTICE", "Saw TCP redirection, changing redirection to UDP"); replace(";transport=Tcp", ";transport=Udp"); } else { xlog("L_NOTICE", "302 was not matched (!)"); } exit(); break; case 100: xlog("L_NOTICE", "Saw 100, leaving alone..."); exit(); break; }
Notice if I get a redirect from Exchange (which I will for port 5060
) I change that from a Tcp
redirect to a Udp
redirect, then send it on its way.
So, this is what is in the lab right now. I think this works – until PhoneCo gets the line in place we won’t know 100% but I think this is close if it isn’t completely right. We’ll see.
Hope this helps you in your integration scenarios!
— Michael C. Bazarewsky
Principal Consultant, Windows Server and Security
Thank you so much for this!
I have this working great with an online SIP trunk service that does not support TCP –> Lync.
I am now trying to use Kamailio and this script (with modifications) to allow me to use my old SIP ATA (a Linksys PAP2T) in combination with the New-CsAnalogDevice cmdlet.. I’m hoping to figure it out soon but wouldn’t mind some help! Please let me know if interested, Thanks!
LikeLike
Robin,
Glad you have it working to connect the provider to Lync!
With respect to an ATA, we have this working in a lab for a different customer, but we are not using Kamailio in that case. Instead, we’re using an Asterisk virtual machine to act as the central point of connectivity. Asterisk is using UDP with the SIP trunk, TCP with Lync, and UDP with the ATA (a Cisco ATA 186 in this case) because the ATA can’t use SIP TCP. Calls are working in all directions including caller ID. You need to use a recent-enough version of Asterisk to do SIP over TCP without patching or funny business – we’re using the AsteriskNOW 1.7.1 distribution in the lab but 1.8 has been out long enough that I’d think seriously about that for production.
That all said, I suspect you can use Kamailio as well with Kamailio acting as a middle man and New-CsAnalogDevice as you propose, giving Lync Kamailio’s IP and a different port for the ATA and having the ATA register with Kamailio (again, different port), and forwarding both ways just like we’re doing here. I don’t have that particular configuration working but that would be a reasonable configuration I think.
Hope that halps!
LikeLike
Do you happen have the astrix side of configuration?
LikeLike
Frank,
I added the whole configuration folder as an attachment. Because the lab was set up with FreePBX the files are not necessarily as clean and straightforward as a manual configuration. That said, I’m not sure there’s much value in them – adding a trunk is fairly straightforward, and I think it’s going to be easier to figure out how to do that than try to get it out of the configuration. But I attached it anyway so you could decide.
Good luck!
LikeLike
Your blog has help me achieve the dial out part but some odd reason I can not get to dial in.. The truck is setup as you suggestions and add the trunk rule to out route but inbound is really my issue.
My system has on DID and is currently setup so when some one calls it prompts for an extension.
… I do want let you know I modify your kamialio file so Exchange is not need since some of consultants we have are not on my company’s exchange and rather not be on it.
I just like it so when a person prompts ext freepbx is configure so when the person dials extention 102 it also dial the lync extension of 1122
your create creating a truck quite easy but I just can not figure what other steps I need to get the dial in to work.
Thank you for the config file but I would prefer screen shoot or step on how to achive since this is in the freepbx is current in production.
Thank you in Advance
LikeLike
Frank,
I can’t easily get screen shots any time in the near future but here are a couple of possible other places to look:
http://www.fintechcommunications.com/blog/?p=60
In general the best place to start when troubleshooting Lync/Asterisk integration is to run asterisk’s console (-rvvvvvvvvvvvvvvvvvv should work fine) and the Lync logging tool and collect while trying the call, then see what the logs tell you. I’ve found that between the two sides it’s usually fairly easy to find the issue as one or the other or both tell you exactly what is going wrong.
LikeLike
Hello
This gives me a hope to achieve on what i am trying .I haven’t test out your solution but i am pretty sure it will work.But if you don’t mind your time i would like to ask you about my project.
Lync is great as far as presence and im go.asterisk is great with all the pbx features but not scalable and presence and im features are very limited.
I came up with a dial plan and agi script to intruduce lync users as asterisk users that way a lync user can login to asterisk vm and use all asterisk pbx features .for example you dial *97 from lync client comedian voice mail asks the vm password
now i am working on the presence part of the project.
Basically what i am trying to do is that a call comes to ddi to asterisk then asterisk forwards to call to lync client, if there is a forwarding on the client side client picks up the call on his cellphone
everything on this scenerio works well .however when the call is answered on the cell phone lync client shows as available i think it should show it as on a call.
I know there is a live channel to client’s cell phone from asterisk so i was thinking to use kamailio simple presence server option .and maybe lync presence subscription to kamailo presence (I don’t even know if it is doable though :))
so the question is what i am trying to do is possible and if so what and how do you think i can achieve this.
Thank you so much for taking the time to even read this
MK
LikeLike
MK,
That is a reasonable question. As you have deduced, the issue is that Lync doesn’t know what is going on once the call is handed off to the cell phone, because it’s not in the communication path at that point. I don’t think this is directly possible, but would be possible with the right integration code. There’s a question on the Microsoft forums about this:
http://social.technet.microsoft.com/Forums/lync/en-US/0fc72210-b7dd-4c8e-bd94-c3fdd03572d0/integrating-lync-with-3rd-party-presence-server
and the conclusion there is that you will need to write custom integration code. I’ve never done that kind of integration coding with Kamailio, but I can’t believe it will be THAT hard for someone with the right experience.
LikeLike
I will work on this and let you know the progress.
Thank you so much
LikeLike
hi
i can not run this part:
/etc/rc.d/init.d/kamailio start
with a reply:
bash: /etc/rc.d/init.d/kamailio: Is a directory
could you help me?
LikeLike