Thursday 1 September 2016

Cisco AXL Python SUDS - Extension Mobility Remote Login

I thought I would share the following script which comes in very handy when you need to login users remotley.
The script reads a standard csv input file with the following fields -
 dn,pn,uid  
 SEP001FCA34A525,UDP_AN_OTHER,A.OTHER  

Full Script:
 __author__ = 'Mitch.Dawson'  
   
 from suds.client import Client  
   
 # Define Authentication Credentials for Cluster  
 username = 'myaxluser'  
 password = 'myaxlpass'  
   
 # Define AXL URL Parameters for Cluster  
 url = 'https://CUCM:8443/axl/'  
   
 # Define WSDL Locations  
 wsdl = "file:///C:/pathto/AXLAPI.wsdl"  
   
 # Open and read csv File  
 data = open('login.csv', 'r').read()  
   
 # Split data per line  
 splitData = data.split('\n')  
   
 # Build SUDS Client Connection  
 client = Client(location=url, url=wsdl, retxml=False, username=username, password=password)  
   
 # loop through and break out individual components  
 for line in splitData[1:]:  
   dn = line.split(',')[0]  
   pn = line.split(',')[1]  
   uid = line.split(',')[2]  
   print(dn, pn, uid)  
   
   # Call client service "doDeviceLogin" method and pass in the parameters  
   x = client.service.doDeviceLogin(deviceName=dn, loginDuration='0', profileName=pn, userId=uid)  
   print(x)  

Friday 26 August 2016

Cisco AXL Python 3.4 SQL Query - Changing DN partitions

I recently wrote the following script as part of some tools for a phased migration of users to a new V11 cluster. We were routing pstn calls through the new cluster so could not place extensions of users not yet migrated into live partitions until their phones were cutover and registered on the new platform.
I therefore placed all extensions into a "Holding" partition  not a member of a CSS which enabled us to verify the user and device configuration without impacting the users, however this also created a following problems on cutover.

1) New Cluster - Requirement to move the migrated users extensions from holding to live
2) Old Cluster - Requirement to move the migrated users extensions from live to holding

When migrating hundreds of users, changing the partition indivudually across two clusters is a lot of work, so it was imperitive to try and automate the process - enter Python!

I previously obtained the pkid's of the relevent partitions with sql querys similar to the following:
1:  select pkid from routepartition where name = "PAR_HOLDING" 
We build the xml message with the following sql query:
1:  select dnorpattern from numplan where fkroutepartition ='72f20467-ddf1-52fb-5586-682a9ef3ea91'  
1:  msg = """  
2:      <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns="http://www.cisco.com/AXL/API/11.0">  
3:        <soapenv:Header/>  
4:        <soapenv:Body>  
5:         <ns:executeSQLQuery sequence="?">  
6:          <sql>select dnorpattern from numplan where fkroutepartition ='72f20467-ddf1-52fb-5586-682a9ef3ea91'  
7:          </sql>  
8:         </ns:executeSQLQuery>  
9:        </soapenv:Body>  
10:      </soapenv:Envelope>"""  
Post to the api
1:  # Create the Requests Connection  
2:    post = requests.post(url11, data=msg, headers=headers11query, verify=False, auth=('user', 'password'))  
We then parse and iterate over the returned xml string to obtain all dnorpattern instances by looking for xml tags which match "dnorpattern" and returning their text value. In this case the number length of the two clusters are different i.e. E164 on the new and four digits on the old. We create two variables, one "newdn" which holds the full E164 value and "olddn" which takes the last four digits, before passing the variables to the relevent functions which actually do the work of changing the partition.
1:  # Parse the response string  
2:    response = ElementTree.fromstring(post.content)  
3:    
4:    # Find phone element in root  
5:    result = response.iterfind(".//row/*")  
6:    
7:    for r in result:  
8:      time.sleep(1.5)  
9:      if r.tag == 'dnorpattern':  
10:        newdn = r.text  
11:        # Take e164 number and take last 4 digits  
12:        olddn = r.text[-4:]  
13:        # Pass e164 number to cluster11 function  
14:        cluster11(newdn)  
15:        # Pass 4 digit number to cluster86 function  
16:        cluster86(olddn)  

Full Code :
1:  __author__ = 'Mitch.Dawson'  
2:  import requests  
3:  from xml.etree import ElementTree  
4:  import time  
5:    
6:  # CUCM URL's  
7:  url11 = 'https://new:8443/axl/'  
8:  url86 = 'https://old:8443/axl/'  
9:    
10:  # V11 CUCM Headers  
11:  headers11query = {'Content-Type': 'text/xml',  
12:           'SOAPAction': 'CUCM:DB ver=11.0 executeSQLQuery'}  
13:    
14:  headers11update = {'Content-Type': 'text/xml',  
15:            'SOAPAction': 'CUCM:DB ver=11.0 executeSQLUpdate'}  
16:    
17:  # V8.6 CUCM Headers  
18:  headers86update = {'Content-Type': 'text/xml',  
19:            'SOAPAction': 'CUCM:DB ver=8.5 executeSQLUpdate'}  
20:    
21:    
22:  def findextensions():  
23:    
24:    """  
25:    Find numbers on cucm 11 cluster in PAR_HOLDING  
26:    :return:  
27:    """  
28:    
29:    msg = """  
30:      <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns="http://www.cisco.com/AXL/API/11.0">  
31:        <soapenv:Header/>  
32:        <soapenv:Body>  
33:         <ns:executeSQLQuery sequence="?">  
34:          <sql>select dnorpattern from numplan where fkroutepartition ='72f20467-ddf1-52fb-5586-682a9ef3ea91'  
35:          </sql>  
36:         </ns:executeSQLQuery>  
37:        </soapenv:Body>  
38:      </soapenv:Envelope>"""  
39:    
40:    # Create the Requests Connection  
41:    post = requests.post(url11, data=msg, headers=headers11query, verify=False, auth=('user', 'password'))  
42:        
43:    # Parse the response string  
44:    response = ElementTree.fromstring(post.content)  
45:    
46:    # Find phone element in root  
47:    result = response.iterfind(".//row/*")  
48:    
49:    for r in result:  
50:      time.sleep(1.5)  
51:      if r.tag == 'dnorpattern':  
52:        newdn = r.text  
53:        # Take e164 number and take last 4 digits  
54:        olddn = r.text[-4:]  
55:        # Pass e164 number to cluster11 function  
56:        cluster11(newdn)  
57:        # Pass 4 digit number to cluster86 function  
58:        cluster86(olddn)  
59:    
60:                
61:  def cluster11(dn):  
62:    
63:    """  
64:    Move patterns to PAR_RESOURCES  
65:    :param dn:  
66:    :return:  
67:    """  
68:    
69:    # Message to Post  
70:    msg = """  
71:      <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns="http://www.cisco.com/AXL/API/11.0">  
72:        <soapenv:Header/>  
73:        <soapenv:Body>  
74:         <ns:executeSQLUpdate sequence="?">  
75:          <sql>update numplan set fkroutepartition = 'bfc7a079-83a5-10cc-a944-74fb1f17064b'  
76:          where dnorpattern = "{0}"  
77:          </sql>  
78:         </ns:executeSQLUpdate>  
79:        </soapenv:Body>  
80:      </soapenv:Envelope>""".format(dn)  
81:    
82:    # Create the Requests Connection  
83:    post = requests.post(url11, data=msg, headers=headers11update, verify=False, auth=('user', 'password'))  
84:    
85:    # Parse the response string  
86:    response = ElementTree.fromstring(post.content)  
87:    
88:    # Find phone element in root  
89:    result = response.iterfind(".//return/*")  
90:    for i in result:  
91:      if i.tag == 'rowsUpdated':  
92:        if i.text == '1':  
93:          print('#### Successfully moved ' + str(dn) + ' to PAR_RESOURCES ####')  
94:        else:  
95:          print('#### The response indicates no rows were updated for dn ' + str(dn) + ' ####')  
96:    
97:    
98:  def cluster86(dn):  
99:    """  
100:    Move numbers to staging partition on 8.6 cluster  
101:    :param dn:  
102:    :return:  
103:    """  
104:    
105:    # Message to Post  
106:    msg = """  
107:        <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns="http://www.cisco.com/AXL/API/8.5">  
108:          <soapenv:Header/>  
109:          <soapenv:Body>  
110:           <ns:executeSQLUpdate sequence="?">  
111:            <sql>update numplan set fkroutepartition = '61de9659-bc10-fca0-aa5e-81dc4b7e2fe4'  
112:            where dnorpattern = "{0}"  
113:            </sql>  
114:           </ns:executeSQLUpdate>  
115:          </soapenv:Body>  
116:        </soapenv:Envelope>""".format(dn)  
117:    
118:    # Create the Requests Connection  
119:    post = requests.post(url86, data=msg, headers=headers86update, verify=False, auth=('user', 'password'))  
120:    
121:    # Parse the response string  
122:    response = ElementTree.fromstring(post.content)  
123:    
124:    # Find phone element in root  
125:    result = response.iterfind(".//return/*")  
126:    for i in result:  
127:      if i.tag == 'rowsUpdated':  
128:        if i.text == '1':  
129:          print('#### Successfully moved ' + str(dn) + ' to staging partition ####')  
130:        else:  
131:          print('#### The response indicates no rows were updated for dn ' + str(dn) + ' ####')  
132:    
133:    
134:  findextensions()  

Thursday 25 August 2016

Cisco Cucm - Block OffNet To OffNet Transfer

I got called to look into an issue for a customer who were in the process of migrating users from an old 8.6 to a new 11.0 cluster. PSTN access was being routed through the new cluster and calls worked fine, however there were issues with transfers failing in certain scenarios. 

The call flow was as follows - 

PSTN ----> SIP GW ----> V11 CUCM ----> V11 User ----> V8.6 CUCM ----> V8.6 User

After making a number of test calls and taking the appropriate traces I found the following in the SDL logs on the V11 cluster.

69285831.001 |15:11:13.743 |AppInfo |Transferring - Cannot Complete Transfer with PrimaryTransferredIsOffnetDevice =1, SecondaryTransferDestinationIsOffnetDevice =1

The message is clear that CUCM believes this to be an "OffNet To OffNet Transfer" which on this particular cluster was blocked in the service parameters for Toll Fraud prevention, however in my view this appeared to be an OffNet originated call, with a fully On-Net transfer and therfore shouldnt have met the block criteria.

I decided to look at the Sip Trunk between the cluster, which on investigation had been correctly classified as "On-Net"



I then looked at the route pattern that was being used to route from V11 to the 8.6 and found the culprit.



As you can see, the call classification is incorrectly set as "offnet". Once changed to "OnNet" transfers worked as expected.

Monday 25 April 2011

The Netscreen 5GT and PPPOE Broadband Providers

If you are like me and need to have remote access to your systems over your broadband connection then you should carefully consider the type of firewall you have securing the connection.

Most isp supplied  firewalls and routers provide varying levels of functionality in the form of port forwarding/dmz access but these can be tricky to configure and miss some of the advanced features like Ipsec which are useful for secure "on the road" remote access.

A few years ago I worked for a Service provider that used Netscreen firewalls for CPE, and it was here that I discovered the powerful features that make the smaller models ideal for soho environments.  

My home firewall is an older 5GT model that I picked up a few years ago on eBay for around £60  although there are now various flavours including an ADSL and Wireless model.
The 5GT has five 10/100 ethernet ports one for the internet side and four for the LAN in standard trust/untrust mode, for simplicity I would have opted for a cable broadband connection with ethernet hand off and enabled dhcp on the Netscreen untrust port to obtain the public ip address, however there is no cable availability in my area so ADSL and PPPOE (ppp over Ethernet) was the only option.

I obtained a second hand adsl modem/router from ebay that supports RFC 1483 ADSL Bridging mode to terminate the line and provide an Ethernet port for my netscreen to connect to the isp with pppoe.

The next problem you will have is finding a good broadband provider that supports pppoe because as you may know pppoa(ppp over atm) is the typical encapsulation for UK adsl connections.
After a false promise from TalkTalk and a few hours troubleshooting they admitted they didn't support pppoe and that their salesmen would "say anything to get a sale" - so I promptly cancelled. 

I looked into BT as I know they support pppoe on all of their broadband offerings however I found them pricey and in the end opted for Post Office Broadband which unofficially resells BT Broadband under their own banner but at their own prices.

I know have my Netscreen providing the following services -

- Ipsec dialup vpn for on the road remote access to LAN devices and NAS
- Voip media proxy using SIP ALG to ITSP
- Sip access to Asterisk PBX when on the road.
- Ssh access to Linux servers from Internet

Jab