Python script support for ESM

Python script support for ESM

To provide programmable flexibility in ESM applications, the SR OS provides the following features with Python script support:

  • sub-ident-policy

  • radius-script-policy

  • python-policy

Python in SR OS overview

SR OS supports both Python2 and Python3 for Python scripts. Python3 only supports python-policy.

The SR OS Python2 script support is based on Python version 2.4.2. Python2 has a set of language features (such as functions, lists, and dictionaries) and a large set of packages that provide most of the Python2 functionality. The SR OS keeps the language features intact and reduces the number of packages available to provide the operator with a small, but flexible, scripting language. The APIs described in this user guide are Python2 APIs.

The only feature removed from the Python2 language is unicode support. The only packages provided to the operator are:

  • alc

    The SR OS-provided packages provide access to various ESM objects such as DHCPv4, DHCPv6, or RADIUS packets.

  • binascii

    The binascii package contains common ASCII decoding, like base64.

  • re

    The re package contains regular expression support.

  • struct

    The struct package parses and manipulates binary strings

    .
  • md5

    The MD5 package provides the MD5 message digest algorithm.

The MD5 package provides the MD5 message digest algorithm.

The SR OS Python3 script support is based on MicroPython version 3.4. Even though the majority of Python3 TPSDA APIs are similar to Python2 TPSDA API, there are some differences. For details about the Python3 TPSDA API, see SR BNG TPSDA Python3 API.

In addition to Python3 TPSDA API, the following Python3 libraries are also available:

  • MicroPython libraries:
    • uarray

    • ubinascii

    • ucollections

    • uhashlib

    • uio

    • ure

    • ustruct

    • utime (Nokia modified the implementation of this module. For usage information, see Nokia pySROS API.)

  • Standard libraries:

    • datetime

    • ipaddress

    • sys

Python policy – GTPv1-C API

The system provides a Python object for input GTPv1-C packet alc.gtp1c. The alc.gtp1c packet has following attributes to represent the GTPv1-C header fields as displayed in GTPv1-C header fields .

Table 1. GTPv1-C header fields
Class attributes GTPv1-C header field Access

alc.gtp1c.version

Integer, version field in the header

Read

alc.gtp1c.ptbit

boolean, true mean 1, false means 0; PT bit in the header

Read

alc.gtp1c.ebit

boolean, E bit in the header

Read

alc.gtp1c.sbit

boolean, S bit in the header

Read

alc.gtp1c.pnbit

boolean, PN bit in the header

Read

alc.gtp1c.type

Integer message type field in the header

Read

alc.gtp1c.len

Integer message length field in the header

Read

alc.gtp1c.teid

Unsigned integer, or None if TEID does not exist

Read

alc.gtp1c.seq

Integer sequence number in the header

Read

alc.gtp1c.npdu_number

Integer N-PDU Number field in the header

Read

alc.gtp1c.next_ext_type

Integer Next Extension Type field in the header

Read

alc.gtp1c.ext_list

Tuple, a tuple of extension headers; each element in the tuple represent one extension header: (ext_type, ext_content)

Read

The following is a list of alc.gtp1c functions:

  • alc.gtp1c.drop()

    The system drops the resulting packet.

  • alc.gtp1c.getIEList()

    The system returns a tuple of ie-type, each element represent one IE instance in the packet.

  • alc.gtp1c.get(ie_type)

    The system returns a tuple of str, each str is the IE data of specified ie_type instances. If the specified IE does not exist, then return (); if the IE length=0, then return ‟” for this IE instance.

  • alc.gtp1c.set(ie_type, ie_tuple)

    This function removes all instances of ie_type and insert a list of new IE instances. Each element in ie_tuple is a string, represent one instance of IE to be inserted. For each inserted IE instance, the type is ie_type. System uses the TV format for type<128 and TLV format for type>=128.

  • alc.gtp1c.clear(ie_type)

    This function removes all existing instances of ie_type.

Python policy – GTPv2-C API

The system provides a Python object for input GTPv2-C packet: alc.gtp2c.

Shown in GTPv2-C API , the alc.gtp2c packet has following attributes to represent the GTPv2-C header fields:

Table 2. GTPv2-C API
Class attributes GTPv2-C header field Access

alc.gtp2c.version

Integer version field in the header

Read

alc.gtp2c.pbit

boolean, true mean 1, false means 0; P bit in the header

Read

alc.gtp2c.tbit

boolean, T bit in the header

Read

alc.gtp2c.type

Integer message type field in the header

Read

alc.gtp2c.len

Integer message length field in the header

Read

alc.gtp2c.teid

Unsigned integer, or None if TEID does not exist

Read

alc.gtp2c.seq

Integer sequence number in the header

Read

The following is a list of alc.gtp2c functions:

  • alc.gtp2c.drop()

    The system drops the resulting packet.

  • alc.gtp2c.getIEList()

    The system returns a tuple of 2-element tuple (ie-type, ie-instance). Each 2-element tuple represent one IE instance in the packet. For example: a packet with the following IEs:

    • type:2, instance: 1

    • type:2, instance: 1

    • type:74, instance: 0

      The system returns ((2,1),(2,1),(74,0))

    • alc.gtp2c.get(ie_type, instance_id)

      The system returns a tuple of str. Each str is the IE data of specified (ie-type, instance-id). If the IE is a grouped IE, then the str is also the content of whole grouped IE. If the specified (ie_type, instance_id) does not exist, then return (); if the IE length=0, then return ‟” for this IE instance.

    • alc.gtp2c.set(ie_type, instance_id, ie_tuple)

      This function removes all instances of (ie_type, instance_id) and inserts a list of new IE instances. Each element in an ie_tuple is a string, represent one instance of IE to be inserted. For each inserted IE instance, the type is ie_type, the instance is instance_id.

    • alc.gtp2c.clear(ie_type, instance_id)

      This function removes all existing instances of (ie_type, instance-id).

    • alc.gtp2c.str_to_groupedIE (parent_ie_str)

      Parses the parent_ie_str and return a tuple of embedded IEs. Parent_ie_str is a string of data field of parent IE; each element of returned tuple is a tuple of (ie_type, ie_instance, ie_data), which represent one embedded IE. For example, with the following code:

      • ie_str = alc.gtp2c.get(93,0)[0]

      • ie_tuple = alc.gtp2c.str_to_groupedIE(ie_str)

      The above code parses the first instance of the bearer context with an instance=0. Assume the bearer context looks like that shown in Bearer context example:

      Figure 1. Bearer context example

      Then, the ie_tuple is

      ((5,0,’\x05’),(87,5’\x9f\xfe\x50\x02\x25\x0a\xbe\xac\xfe’),(80,0,’\x04\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00’))

      An exception is raised if the system cannot parse the parent_ie_str into a ie_tuple.

    • alc.gtp2c.groupedIE_to_str(grouped_ie_tuple)

      Converts the grouped_ie_tuple into a str and return the str (see the description of alc.gtp2c.str_to_groupedIE for the format grouped_ie_tuple). An exception is raised if the conversion fails. The following is an example:

      • ie_str = alc.gtp2c.get(93,0)[0]
      • ie_tuple = alc.gtp2c.str_to_groupedIE(ie_str)
      • fteid = list(ie_tuple[1])
      • fteid[1]=0
      • ie_str = alc.gtp2c.groupdedIE_to_str((ie_tuple[0],tuple(ftied),ie_tuple[2]))
      • alc.gtp2c.set(93,0,(ie_str,))

      With the previous example packet, the code changes the instance of the embedded F-TEID to 0.

Be aware that the alc.gtp2c.str_to_groupedIE (parent_ie_str) and alc.gtp2c.groupedIE_to_str(grouped_ie_tuple) do not check whether the input parameter is a grouped IE. The user should verify that the input parameter is a grouped IE before using these two functions.

Python changes

The following changes have been made to Python for it to run on an embedded system:

  • No files or sockets can be opened from inside Python scripts.

  • No system calls can be made from inside Python scripts nor is the posix package available.

  • The maximum recursion depth is fixed to twenty.

  • The total amount of dynamic memory available for Python itself and Python scripts (excluding the Python cache) is capped at 8 Mb for 32-bit SR OS and 16 Mb for 64-bit SR OS on a CPM.

  • The size of the Python script source file should be less than 60 kb.

Python support in sub-ident-policy

A Python script can be configured in sub-ident-policy to return following ESM attributes:

  • sub-id

  • sla-profile name

  • sub-profile name

  • ancp-string

The system runs the Python script configured in the sub-ident-policy against the received DHCPv4 ACK message. This is used as the input of the script. Within the script, the user can set the value with the above ESM attributes.

The alc package contains a DHCP object, and has the following members ( DHCP object members ).

Table 3. DHCP object members
Name Read Write Class

htype

integer

hlen

integer

hops

integer

flags

integer

ciaddr

integer

yiaddr

integer

siaddr

integer

giaddr

integer

chaddr

string

sname

string

file

string

options

TLV

sub_ident

string

sub_profile_string

string

sla_profile_string

string

ancp_string

string

app_profile_string

string

category_map_name

string

int_dest_id

string

The TLV type provides easy access to the value part of a stream of type-length-value variables, as is the case for the DHCP option field. In the example on Configuration, the circuit-ID is accessed as alc.dhcp.options[82][1].

Some DHCP servers do not echo the relay agent option (option 82) when the DHCP message was snooped instead of relayed. For the convenience of the operator, the relay agent option from the request message is returned when alc.dhcp.options[82] is called.

Configuration

Consider using script us5.py which sets the subscriber ID (sub_ident) based on the circuit-id in the DHCP message.

Configure the URL to this script in a sub-ident-policy as follows:

---------------------------------------------- 
    sub-ident-policy "DSLAM" create
        description "Parse circuit IDs from different DSLAMs" 
        primary

            script-url "ftp://198.51.100.0/py/us5.py" 
            no shutdown
        exit
    exit
----------------------------------------------

And attach this sub-ident-policy to the sub-sla-mgmt from a SAP:

A:dut-A>config>service>vpls>sap# info
---------------------------------------------- 
    dhcp
        description "client side 
        lease-populate 50

        no shutdown 
    exit
    anti-spoof ip-mac
    sub-sla-mgmt
        sub-ident-policy "DSLAM" 
        no shutdown
    exit
----------------------------------------------

Note: DHCP snooping and relaying should be configured correctly for this to work.

Operator debugging

Verbose debug output is sent to debug-trace on compile errors, execution errors, execution output and the exported result variables.

A:dut-A>config>subscr-mgmt>sub-ident-pol>primary# script-url "ftp://xxx.xxx.xx.xx/py/
parsefail1.py"
A:dut-A>config>subscr-mgmt>sub-ident-pol>primary# no shutdown

1 2006/07/30 01:17:33.14 UTC MINOR: DEBUG #2001 - Python Compile Error
"Python Compile Error: parsefail1.py
    File "ftp://xxx.xxx.xx.xx/py/parsefail1.py", line 2 def invalid_function():
        ^
IndentationError: expected an indented block
"

A:dut-A>config>subscr-mgmt>sub-ident-pol>primary# script-url "ftp://xxx.xxx.xx.xx/py/
dump.py"

2 2006/07/30 01:24:55.50 UTC MINOR: DEBUG #2001 - Python Output
"Python Output: dump.py htype   = 0
hlen    = 0 hops    = 0 flags   = 0
ciaddr  = '0.0.0.0' yiaddr  = '0.0.0.0' siaddr  = '0.0.0.0' giaddr  = '0.0.0.0' chaddr  = ''
sname   = ''
file    = ''
options = '5\x01\x056\x04\n\x01\x07\n3\x04\x00\x00\x00\xb4\x01\x04\xff\xff\xff
\x00\x1c\x04\n\x02\x02\xffR\x0f\x01\rdut-A|1|1/1/1\xff' "

3 2006/07/30 01:24:55.50 UTC MINOR: DEBUG #2001 - Python Result
"Python Result: dump.py
"

A:dut-A>config>subscr-mgmt>sub-ident-pol>primary# script-url "ftp://xxx.xxx.xx.xx/py/end- less.py"

4 2006/07/30 01:30:17.27 UTC MINOR: DEBUG #2001 - Python Output
"Python Output: endless.py
"

5 2006/07/30 01:30:17.27 UTC MINOR: DEBUG #2001 - Python Error
"Python Error: endless.py
Traceback (most recent call last):
File "ftp://xxx.xxx.xx.xx/py/endless.py", line 2, in ? FatalError: script interrupted (timeout)
"

Note: All the Python Result events are empty because none of the scripts set any of the output variables.

Python scripts

Note:

The scripts in this section are test scripts and not scripts that the operator would normally use.

dump.py from alc import dhcp

def print_field(key, value):
print '%-8s = %r' % (key, value)

def ipaddr2a(ipaddr):
return '%d.%d.%d.%d' % (
(ipaddr & 0xFF000000) >> 24, (ipaddr & 0x00FF0000) >> 16, (ipaddr & 0x0000FF00) >> 8, (ipaddr & 0x000000FF))

print_field('htype',   dhcp.htype) print_field('hlen',    dhcp.hlen) print_field('hops',    dhcp.hops) print_field('flags',   dhcp.flags) print_field('ciaddr',  ipaddr2a(dhcp.ciaddr)) print_field('yiaddr',  ipaddr2a(dhcp.yiaddr)) print_field('siaddr',  ipaddr2a(dhcp.siaddr)) print_field('giaddr',  ipaddr2a(dhcp.giaddr)) print_field('chaddr',  dhcp.chaddr) print_field('sname',   dhcp.sname) print_field('file',    dhcp.file) print_field('options', str(dhcp.options))

Sample Python scripts

This section provides examples to show how the script can be used in the context of Enhanced Subscriber Management.

Note: These scripts are included for informational purposes only. The operator must customize the script to match their own network and processes.

Example

This script uses the IP address assigned by the DHCP server to derive both sub_ident and sla_profile_string.

Script:

1.  import alc
2.  yiaddr = alc.dhcp.yiaddr
3.  # Subscriber ID equals full client IP address.
4.  # Note: IP address 10.10.10.10 yields 'sub-168430090'
5.  # and not 'sub-10.10.10.10'
6.  alc.dhcp.sub_ident = 'sub-' + str(yiaddr)
7.  # DHCP server is configured such that the third byte (field) of the IP
8.  # address indicates the session Profile ID.
9.  alc.dhcp.sla_profile_string = 'sp-' + str((yiaddr & 0x0000FF00) >> 8)

Explanation:

Line 1 imports the library ‟alc” – Library imports can reside anywhere in the script if the items are imported before they are used.

Line 2 assigns the decimal value of the host’s IP address to a temporary variable ‟yiaddr”. Line 6: The text ‟sub_‟ followed by yiaddr is assigned to ‟sub_ident” string.

Line 9 the text ‟sp-‟ followed with the third byte of the IP address is assigned to the ‟sla- profile” string.

If this script is run, for example, with a DHCP-assigned IP address of:

yiaddr = 10.10.0.2

The following variables are returned:

sub_ident: sub-168427522(hex = A0A00002 = 10.10.0.2)
sla_ident: sp-0 

Example

This script returns the sub_profile_string and sla_profile_string, which are coded directly in the Option 82 string.

Script:

1.  import re
2.  import alc
3.  # option 82 formatted as follows:
4.  # "<subscriber Profile>-<sla-profile>”
5.  ident = str(alc.dhcp.options[82][1])
6.  alc.dhcp.sub_ident = ident
7.  tmp = re.match("(?P<sub>.+)-(?P<sla>.+)", str(ident))
8.  alc.dhcp.sub_profile_string = tmp.group("sub")
9.  alc.dhcp.sla_profile_string = tmp.group("sla")

Explanation:

Line 1-2 import the libraries ‟re” and ‟alc”. Library imports can reside anywhere in the script if the items are imported before they are used.

Line 6 assigns the full contents of the DHCP Option 82 field to the ‟sub_ident” variable.

Line 7 splits the options 82 string into two parts, separated by ‟-”.

Line 8 assigns the first part of the string to the variable ‟sub_profile_string”.

Line 9 assigns the second part of the string to the variable ‟sla_profile_string”.

If this script is run, for example, with DHCP option field:

options = \x52\x0D\x01\0x0Bmydsl-video

The following variables are returned:

sub_ident: mydsl-video 
sub_profile_string: mydsl 
sla_profile_string: video 

Limitations

'%' operator

While %f is supported, %g and %e are not supported.

Floating Point Arithmetic

The floating point arithmetic precision on the box is less than the precision required by the regression suites of Python.

1024.*1024.*1024. until five numbers after the point instead of seven and sqrt(9) equals to 3. for the first seven numbers after the point.

Using the round operator fixes these problems. For example, round(pow(2., 30)) equals round(1024.*1024.*1024.) and round(sqrt(9)) equals 3.

RADIUS script policy overview

Python scripts for RADIUS AAA packets support manipulation in subscriber management application. This feature is supported on the 7750 SR and 7450 ESS routers.

A Python script can be executed in following cases:

  • Before the system sends an access-request packet.

  • After the system receives an access-accept packet.

  • After the system receives an CoA-request packet.

  • Before the system sends an accounting-request packet.

The input of the script is the corresponding original packet; and the output of the packet is used as the new corresponding packet for further ESM AAA process.

The radius-script-policy contains URLs of a primary and an optional secondary Python script, which could be a local CF file path or a FTP URL. The configured radius-script-policy could be used in different ESM polices like authentication-policy or radius-accounting-policy.

The following operations are supported within the script:

  • Obtain the value of an existing attribute or VSA.

  • Modify the value of an existing attribute or VSA.

  • Add a new attribute or VSA.

  • Remove an existing attribute or VSA.

Note: The following RADIUS attributes or VSA are read-only to Python script:
  • Message-Authenticator

  • Alc-LI-Action

  • Alc-LI-Direction

  • Alc-LI-Destination

  • Alc-LI-FC

  • Alc-LI-Intercept-Id

  • Alc-LI-Session-Id

After Release 12.0R1, users should use a Python policy (instead of a RADIUS script policy) for RADIUS packet manipulation.

Python RADIUS API

The following new Python objects, alc.radius, have the following methods:

  • drop(): Drop the packet

  • header(): Return the dictionary object that includes RADIUS header information

alc.radius also provides methods to manipulate radius attributes via alc.radius.attributes, which has the following methods:

  • get(type): Return the first attribute with specified type as a string

  • getTuple(type): The same as above but returns a tuple of strings

  • getVSA(vendor, type): Return the first VSA as a string

  • getVSATuple(vendor, type): The same as above but returns a tuple of strings

  • getExt(type, ext-type): Return a string containing the value of the first Extended Type attribute (RFC 6929, Remote Authentication Dial In User Service (RADIUS) Protocol Extensions) matching the specified type and extended type

  • getExtTuple(type, ext-type): Return a tuple of strings. For each occurrence of an Extended Type attribute (RFC 6929) matching the specified type and extended type, the attribute value is included as a string entry in the tuple

  • getExtVSA(type, vendor, vendor-type): Return a string containing the value of the first Extended Vendor Specific attribute (RFC 6929) matching the specified type, vendor and vendor-type

  • getExtVSATuple(type, vendor, vendor-type): Return a tuple of strings. For each occurrence of an Extended Vendor Specific attribute (RFC 6929) matching the specified type, vendor and vendor-type, the attribute value is included as a string entry in the tuple.

  • getLongExtTuple(type, ext-type): Return a tuple of strings. For each occurrence of a Long Extended Type attribute (RFC 6929) matching the specified type and extended type, the attribute value is included as a string entry in the tuple. The value of all fragments of the same Long Extended Type attribute are concatenated in a single string entry.

  • getLongExtVSATuple(type, vendor, vendor-type): Return a tuple of strings. For each occurrence of a Long Extended Vendor Specific attribute (RFC 6929) matching the specified type, vendor and vendor-type, the attribute value is included as a string entry in the tuple. The value of all fragments of the same Long Extended Vendor Specific attribute are concatenated in a single string entry.

  • set(type, value): Set the specified attribute to the value. The value must be either a string or a tuple of strings.

  • setVSA(vendor, type, value): Set the specified VSA to the value. The value must be either a string or a tuple of strings.

  • setExt(type, ext-type, value): Set the specified Extended Type attribute value (RFC 6929). The value can be specified as a string (single attribute) or a tuple of strings (multiple attributes).

  • setExtVSA(type, vendor, vendor-type, value): Set the specified Extended Vendor Specific attribute value (RFC 6929). The value can be specified as a string (single attribute) or a tuple of strings (multiple attributes).

  • setLongExt(type, ext-type, value): Set the specified Long Extended Type attribute value (RFC 6929). The value can be specified as a string (single attribute) or a tuple of strings (multiple attributes). When the string value length is too long to fit in a single attribute (more than 251 octets), then the value field is fragmented across multiple attributes. The M-bit (more) is set in all but the last fragment.

  • setLongExtVSA(type, vendor, vendor-type, value): Set the specified Long Extended Vendor Specific attribute value (RFC 6929). The value can be specified as a string (single attribute) or a tuple of strings (multiple attributes). When the string value length is too long to fit in a single attribute (more than 246 octets), then the value field is fragmented across multiple attributes. The M-bit (more) is set in all but the last fragment.

  • clear(type): Remove the specified attribute

  • clearVSA(vendor, type): Remove the specified VSA

  • clearExt(type, ext-type): Remove all Extended Type or Long Extended Type attributes (RFC 6929) matching the specified type and extended type

  • clearExtVSA(type, vendor, vendor-type): Remove all Extended Vendor Specific or Long Extended Vendor Specific attributes (RFC 6929) matching the specified type, vendor and vendor-type

  • isSet(type): Return True if the specified attribute exists, False otherwise

  • isVSASet(vendor, type): Return True if the specified VSA exists, False otherwise

  • isExtSet(type, ext-type): Return True if an Extended Type or Long Extended Type attribute (RFC 6929) matching the specified type and extended type is present. Else return False

  • isExtVSASet(type, vendor, vendor-type): Return True if an Extended Vendor Specific or Long Extended Vendor Specific attribute (RFC 6929) matching the specified type, vendor and vendor-type is present. Else return False.

Extended attribute type, Long Extended attribute type, and Extended Vendor Specific data type are specified in RFC 6929 RADIUS Protocol Extensions.

Both the RADIUS client and RADIUS server must support RFC 6929 RADIUS Protocol Extensions to successfully use the Extended and Long Extended attribute types and the Extended Vendor Specific data type. Therefore, it is recommended to check the RADIUS servers for RFC 6929 support and upgrade if needed. As shown in RADIUS Python: interworking with RADIUS server with no RFC 6929 support, RADIUS Python can be used as a workaround to interwork with a RADIUS server that does not support these extensions. Use the general purpose [26-6527-242] Alc-Radius-Py VSA of type ‟octets” for this purpose.

Figure 2. RADIUS Python: interworking with RADIUS server with no RFC 6929 support

Sample script

From alc import radius

#1. Get the value of an existing Attribute

Username=radius.attributes.get(1)

#2. Modify an existing attribute

radius.attributes.set(1, 'Tom')

#3. Remove an existing attribute

radius.attributes.clear(1)

#4. Add a new attribute

radius.attributes.set(126,”WIFI-operator”) 

Python policy overview

The Python policy represents a general framework to support all existing and new python features. A Python policy allows users to configure a Python script for a specified ESM packet type (such as DHCP and RADIUS) in a specified direction (ingress or egress). The system executes the configured script when sending or receiving the specified type of packet.

The corresponding original packet is used as input within the script. The user can use the system-provided API to manipulate the input packet (such as add, change, remove option or attribute). The changed packet is used as the output for further ESM processing. With a DHCP transaction cache, the script can return ESM attributes.

Python policies support the following ESM packet types and applications:

  • DHCP Transaction Cache on CPM

  • DHCPv4 on CPM

  • DHCPv6 on CPM

  • Diameter on CPM

  • GTPv1-C/GTPv2-C

  • Python cache on CPM

  • RADIUS on CPM

  • RADIUS on ISA

There are two types of Python policies:

  • Centralized: Runs on the CPM and handles above protocol packets processed on the CPM.

  • Distributed: Runs on the ISA and handles packets processed locally on the ISA (such as the DSM). Currently in RADIUS only.

The user must specify the type when creating the Python policy.

The following is an example configuration of a specified group interface. The system executes cf1:/dhcpv4.py after receiving DHCPv4 discovery and before the system forwards a DHCPv4 request packet.

config>python# info 
----------------------------------------------
        python-script "dhcpv4" create
            primary-url "cf1:/dhcpv4.py"
            no shutdown
        exit
        python-policy "dhcp" create
            dhcp discover direction ingress script "dhcpv4"
            dhcp request direction egress script "dhcpv4"
        exit
---------------------------------------------
config>service>vprn>sub-if>grp-if>dhcp# info 
----------------------------------------------
                        python-policy "dhcp"
                        server 10.9.9.9 
                        lease-populate 100
                        gi-address 192.168.100.1
                        no shutdown
----------------------------------------------

Python policy – RADIUS API

The RADIUS API in Python policy uses the same API of the radius-script-policy.

Python policy – DHCPv4 API

  • The system provides a Python object for input DHCPv4 packet: alc.dhcpv4.

  • alc.dhcpv4 has following attributes to represent the DHCPv4 header fields:

    alc.dhcpv4 attributes displays alc.dhcpv4 attributes.

    Table 4. alc.dhcpv4 attributes
    Class attributes DHCPv4 header field Access

    alc.dhcpv4.pkt_len

    Integer Total length of original DHCPv4 packet (UDP/IP header excluded, including pad option) in bytes

    read

    alc.dhcpv4.pkt_netlen

    Integer Total length of original DHCPv4 packet (UDP/IP header excluded, pad option in the ‟options” field excluded) in bytes

    read

    alc.dhcpv4.op

    op

    read

    alc.dhcpv4.htype

    htype

    read/write

    alc.dhcpv4.hlen

    hlen

    read/write

    alc.dhcpv4.hops

    hops

    read/write

    alc.dhcpv4.xid

    xid

    read

    alc.dhcpv4.secs

    secs

    read/write

    alc.dhcpv4.flags

    flags

    read/write

    alc.dhcpv4.ciaddr

    ciaddr

    read/write

    alc.dhcpv4.yiaddr

    yiaddr

    read/write

    alc.dhcpv4.siaddr

    siaddr

    read/write

    alc.dhcpv4.giaddr

    giaddr

    read/write

    alc.dhcpv4.chaddr

    chaddr

    read/write

    alc.dhcpv4.sname

    sname

    read/write

    alc.dhcpv4.file

    file

    read/write

All attributes, except alc.dhcpv4.pkt_len and alc.dhcpv4.pkt_netlen, are string with value of the actual bytes in the header.

The following is a list all functions of alc.dhcpv4:

  • alc.dhcpv4.drop()

  • alc.dhcpv4. getOptionList()

  • alc.dhcpv4.pad(min_size=300)

  • alc.dhcpv4. get(op_code)

  • alc.dhcpv4. set(op_code,valTuple)

  • alc.dhcpv4. clear(op_code)

  • alc.dhcpv4.get_relayagent() / alc.dhcpv4.set_relayagent(D4OL)

  • alc.dhcpv4.get_vendorspecific() / alc.dhcpv4.set_vendorspecific (D4OL)

DHCPv4 allows using sname and file fields to store options. However all DHCPv4 functions only operates with the ‟options” field. If a customer wants to manipulate options in the sname/file field, they need to do the parsing work in the script. (extended string.tlvxy method could help here)

  • alc.dhcpv4. drop(): The system drops the result packet

  • alc.dhcpv4. getOptionList(): Returns a tuple that includes the option-code of the existing top level DHCPv4 options in the packet.

    • The order of the element in the tuple is as same as the options that appear in the packet.

    • If there are multiple instances of the same option, then each instance is one element in the tuple.

    • Pad option(0) is excluded.

    • End option(255) is included

    • Example: A DHCP discovery packet with msg-type/lease-time/request-addr/parameter-request-list/agent-info/end returns (53,51,50,55,82,255)

  • alc.dhcpv4.pad(min_size=300): This function pads the resulting DHCPv4 packet to the specified min_size with pad option(0) after executing the whole script. Padding is not added if the result packet is already >=min_size. The default value of min_size is 300. Although not defined in DHCPv4 RFC, many DHCPv4 implementation has a minimal length requirement of 300 bytes. So this function could pad the result packet to the specified min_size.

  • alc.dhcpv4. get(op_code): Returns a tuple that includes all instances of the specified top level option as a string. The value of this string is the exact bytes of the option as it appears in packet(excludes option-code and option-len).

    • If the specified option does not exist, then the function returns ()

    • If the instance of the specified option does not have the value (len=0 or does not have len and value part), then the function returns ‟” for that instance in the tuple.

    • Example: There is an address lease time option(51) in the packet, with value 60, then alc.dhcpv4.get(51) should return: (‛\x00\x00\x00\x3c’,)

  • alc.dhcpv4. set(op-code,valTuple): This function removes all the existing instances of specified top level option and insert a list of new options. Each element in valTuple is a string, representing one instance of the new option to be inserted; For each new option, the option-code is specified in op-code. The option-len is the length of the element. The rest of option is the element itself.

    Example: To insert an address lease time option(51) in the packet, with the value 60; use alc.dhcpv4.set(51, (‛\x00\x00\x00\x3c’,))

  • alc.dhcpv4. clear(op-code): This function removes all the existing instances of specified top level option.

  • Although alc.dhcpv4.get() and alc.dhcpv4.set() provide a generic way to manipulate DHCPv4 top level options, but some DHCPv4 options have a complex/hierarchical structure like option82 and option43. To provide a friendly access to these kinds of options, the system provides the following options’ specific functions:

    • alc.dhcpv4.get_relayagent() / alc.dhcpv4.set_relayagent(D4OL)

    • alc.dhcpv4.get_vendorspecific() / alc.dhcpv4.set_vendorspecific (D4OL)

    All alc.dhcpv4.get_XXX() returns a data structure:‟D4OL” (DHCPv4 Option List)

    • D4OL is a list. Each element in the list represents an instance of that option. For example, if there are 3 option82 in the ‟options” field of packet, then get_relayagent() returns a list of 3 elements. Each element represents one instance of the option in the packet.

    • Each element in D4OL is a dict (called dict as ‟D4O” in this example):

      The key of D4O is the sub-option- code. The value is a list of strings of sub-option-value of all instance of the sub-option.

    All alc.dhcpv4.set_XX(OPDL) accepts D4OL as the parameter. Remove all existing instances of the corresponding options and then insert the new options represented by specified D4OL.

Examples:

For a packet with an option82 like following

    Option: (82) Agent Information Option
        Length: 22
        Option (82) Suboption: (1) Agent Circuit ID
            Length: 8
            Agent Circuit ID: 4a616e737656e73
        Option 82 Suboption: (2) Agent Remote ID
            Length: 10
            Agent Remote ID 62617369632364617461

The option-data is (hex formatted) ‟01:08:4a:61:6e:73:73:65:6e:73:02:0a:62:61:73:69:63:23:64:61:74:61”

The following is an example script:

import alc

option82_list=alc.dhcpv4.get_relayagent()
#option82_list will be [{1:['\x4a\x61\x6e:73\x73\x65\x6e\x73',],2:['\x62\x61\x73\x69\x63\x23\x64\x61\x74\x61',]},]
Option82_list[0][2][0]='basic#video' #change remote-id to 'basic#video'
alc.dhcpv4.set_relayagent(option82_list)#update the option82

Python policy – DHCPv6 API

  • The system provides a Python object for input DHCPv6 packet: alc.dhcpv6

  • alc.dhcpv6 has following attributes to represent the DHCPv6 header fields:

    DHCPv6 header fields displays DHCPv6 header fields.

    Table 5. DHCPv6 header fields
    Class attributes DHCPv6 header field Client/server msg Relay msg Access

    alc.dhcpv6.pkt_len

    Integer, Total length of original DHCPv4 packet(UDP/IP header excluded) in bytes

    Read

    alc.dhcpv6.msg_type

    msg-type

    Read

    alc.dhcpv6.transaction_id

    transaction-id

    Read

    alc.dhcpv6.hop_count

    hop-count

    read/write

    alc.dhcpv6.link_addr

    link-address

    read/write

    alc.dhcpv6.peer_addr

    peer-address

    read/write

All header fields (as the attribute of alc.dhcpv6 class) are strings (except pkt_len) with exact bytes as it appears in the packet.

If the attribute does not exist in the specified msg-type, for example if the link_attr does not exist in client/server message(C/S msg), then its value should be None.

  • The following is a list of all functions in the class:

    • alc.dhcpv6.drop()

    • alc.dhcpv6. getOptionList()

    • alc.dhcpv6. get(op-code)

    • alc.dhcpv6. set(op-code,valTuple)

    • alc.dhcpv6. clear(op-code)

    • alc.dhcpv6.get_iana() / alc.dhcpv6.set_iana(OPDL)

    • alc.dhcpv6.get_iata() / alc.dhcpv6.set_iata(OPDL)

    • alc.dhcpv6.get_vendoropts() / alc.dhcpv6.set_vendoropts(OPDL)

    • alc.dhcpv6.get_iapd() / alc.dhcpv6.set_iapd(OPDL)

    • alc.dhcpv6.get_relaymsg()

    • alc.dhcpv6.set_relaymsg(packet)

  • alc.dhcpv6. drop(): The system drops the resulting packet.

  • alc.dhcpv6. getOptionList(): Returns a tuple that includes the option-code of the existing top level DHCPv6 options in the packet. The order of the element in the tuple is as same as the options appear in the packet. If there are multiple instances of same option, then each instance is one element in the tuple. For example:

    • A C/S Msg with Elapsed Time/Client Identifier/IANA/FQDN/Vendor Class/Option Request returns (8,1,3,39,16,6).

    • A Relay Msg with Relay Message option only returns (9)

  • alc.dhcpv6. get(op-code): Returns a tuple that includes all instances of the specified top level option as string The value of this string is the exact bytes of the option as it appears in packet (excludes option-code and option-len). If the specified option does not exist in the input packet, then it returns ().

    Examples:

    If there is a Status Code option in the packet, status-code 0 and status-msg:”Address Assigned”; then alc.dhcpv6.get(13) should return: (‛\x00\x00Address Assigned’,)

  • alc.dhcpv6. set(op-code,valTuple): This function removes all the existing instances of the specified top level option and insert a list of new options. Each element in valTuple is a string, representing one instance of the new option to be inserted. For each new option, the option-code is specified in op-code, the option-len is the length of the element, reset of option is the element itself.

    To insert a Status Code options with status-code 0 and status-msg:”Address Assigned”; use alc.dhcpv6.set(13, (‛\x00\x00Address Assigned’,))

  • alc.dhcpv6. clear(op-code): This function removes all the existing instances of specified top level option.

  • Although alc.dhcpv6.get() and alc.dhcpv6.set() provide a generic way to manipulate DHCPv6 top level options, but some DHCPv6 options have more complex/hierarchical structure like IA_NA/IA_TA, and so on. To provide a friendly access to these kinds of options, the system provides the following options specific functions:

    • alc.dhcpv6.get_iana() / alc.dhcpv6.set_iana(OPDL)

    • alc.dhcpv6.get_iata() / alc.dhcpv6.set_iata(OPDL)

    • alc.dhcpv6.get_vendoropts() / alc.dhcpv6.set_vendoropts(OPDL)

    • alc.dhcpv6.get_iapd() / alc.dhcpv6.set_iapd(OPDL)

    All alc.dhcpv6.get_XXX() returns a data structure:‟OPDL” (Option Data structure List)

    • OPDL is a list. Each element in the list represents an instance of that option. For example, if there are 3 IANA in the packet, then get_iana() returns a list of 3 elements, each element represent one instance of IANA option in the packet.

    • Each element in OPDL is a list, referred to as ‟OPD” in this list.

      • Each element in OPD represent one field in the option(option-code and option-len are not included), the order of the element is as same as the fields appear in the option

      • For field that could be parsed into sub-option by RFC, then the element is a dict, the key of this dict is the sub-option type, if sub-option is one of following supported-sub-option, the value to the key is a sub-option_OPDL represent the list of that specific sub-option

      • IAADDR(5)

      • Status Code(13)

      • IAPREFIX(26)

      Else, if the sub-option is not one of the above, then the value to the key is a list of string of sub-option-data, each string represent one instance of the sub-option.

      • The structure of sub-option_OPDL of IAADDR is: [[v6_addr,prefer_lifetime, valid_lifetime,sub-option_OPDL], ]

      • The structure of sub-option_OPDL of Status Code is: [[status-code,status-msg], and so on]

      • The structure of sub-option_OPDL of IAPREFIX is: [[prefer_lifetime,valid_lifetime,prefix-len,v6prefix,sub-option_OPDL],..]

      • For the field (by RFC definition) could be parsed into sub-options, but it does not actually exist, then the dict is empty {}

      • For field that cannot be parsed into sub-option by RFC, the element is a string of exact bytes of that field

All alc.dhcpv6.set_XX(OPDL) accepts an OPDL as the parameter. Remove all existing instances of the corresponding options and then insert new options represented by the specified OPDL.

  • alc.dhcpv6.get_iana()/alc.dhcpv6.get_iana(OPDL)

  • The general OPDL structure for these two functions is: [[IAID_val,T1_val,T2_val, sub-option_dict]]

  • The structure of sub-option_dict is:{sub-option-type:sub-option_val}

  • If sub-option is supported-sub-option, then sub-option_val is a sub-option_OPDL

  • For all other sub-options, the sub-option_val is a list of string of sub-option-data

Examples:

For a packet with an IANA option as in the following:

The option-data is (hex formatted)

‟0f:f0:de:f1:00:02:a3:00:00:04:38:00:00:05:00:18:20:01:05:58:60:45:00:47:45:cc:d9:f2:57:27:ea:e0:00:05:46:00:00:05:46:00”

The following is an example script:

import alc

iana_list=alc.dhcpv6.get_iana()
#iana_list will be [['\x0f\xf0\xde\xf1','\x00\x02\xa3\x00','\x00\x04\\x38\x00',{5:[['\x20\x01\x05\x58\x60\x45\x00\x47\x45\xcc\xd9\xf2\x57\x27\xea\xe0','\x00\x05\x46\x00','\x00\x05\x46\x00',{}]]}]]
iana_list[0][1]='\x00\x00\x04\xb0' #change T1 to 1200
alc.dhcpv6.set_iana(iana_list)#update the iana

  • alc.dhcpv6.get_iata()/alc.dhcpv6.get_iata(OPDL)

    • The general OPDL structure for these two functions is: [[IAID_val, sub-option_dict]]

    • The structure of sub-option_dict is:{sub-option-type:sub-option_val}

    • If sub-option is supported-sub-option, then sub-option_val is a sub-option_OPDL

    • For all other sub-options, the sub-option_val is a list of string of sub-option-data

    Examples: These two function are very similar with IANA, so the examples are skipped here.

  • alc.dhcpv6.get_iapd()/alc.dhcpv6.get_iapd(OPDL)

  • The general OPDL structure for these two functions is: [[IAID_val,T1_va1,T2_val, sub-option_dict]]

  • The structure of sub-option_dict is:{sub-option-type:sub-option_val}

  • If sub-option is supported-sub-option, then sub-option_val is a sub-option_OPDL

  • For all other sub-options, the sub-option_val is a list of string of sub-option-data

    Examples: For a packet with IA_PD as in the following:

    The option-data is (hex formatted)

    ‟00:00:00:01:00:00:07:08:00:00:0b:40:00:1a:00:19:00:00:0e:10:00:01:51:80:38:20:01:0d:b8:00:02:00:00:00:00:00:00:00:00:00:00”
    
  • Following is an example script:

    
    import alc
    
    iapd_list=alc.dhcpv6.get_iapd()
    #iapd_list will be [['\x00\x00\x00\x01','\x00\x00\x07\x08','\x00\x00\x0b\x40',{26:[['\x00\x00\x0e\x10','\x00\x01\x51\x80','\x38', '\x20\x01\x0d\xb8\x00\x02\x00\x 00\x00\x00\x00\x00\x00\x00\x00\x00',{}]]}]]
    iapd_list[0][1]='\x00\x00\x04\xb0' #change T1 to 1200
    alc.dhcpv6.set_iapd(iapd_list)#update the iapd
    
    
  • alc.dhcpv6.get_vendoropts()/alc.dhcpv6.get_vendoropts (OPDL)

    • The general OPDL structure for these two functions is: [[enterpriseid_val, sub-option_dict]]

    • The structure of sub-option_dict is:{sub-option-type:sub-option_val}

    • If sub-option is supported-sub-option, then sub-option_val is a sub-option_OPDL

    • For all other sub-options, the sub-option_val is a list of string of sub-option-data

Examples: For a packet with vendor options as in the following:

The option-data is (hex formatted)

‟00:00:19:7f:00:01:00:09:69:61:2d:6e:61:5f:30:30:31:00:02:00:09:69:61:2d:70:64:5f:30:30:31:00:03:00:01:38:00:04:00:01:40”

The following is an example script:

import alc

vendoropts_list=alc.dhcpv6.get_vendoropts()
# vendoropts_list will be [['\x00\x00\x19\x7f', {1:['\x69\x61\x2d\x6e\x61\x5f\x30\x30\x31'],2:[ '\x69\x61\x2d\x70\x64\x5f\x30\x30\x31'],3:['\x38'], 4:['\x40']}]]
iapd_list[0][1][4][0]='\x60' #change sub-option 4’s value to 0x60
alc.dhcpv6.set_vendoropts(vendoropts_list)#update the vendor options

  • For DHCPv6 relay message, the ‟Relay Message” option embedded a full DHCPv6 packet and the embedded packet could itself have a ‟Replay Message” option which embedded another DHCPv6 packet.

    To provide direct access to embedded DHCPv6 packet, the system provides following functions:

    • alc.dhcpv6.get_relaymsg()

    • alc.dhcpv6.set_relaymsg(packet)

  • alc.dhcpv6. get_relaymsg(): This function returns a populated alc.dhcpv6 object. which means the returned object was initialized with the DHCPv6 packet embedded in ‟Relay Message” option as the input.

  • alc.dhcpv6. set_relaymsg(packet): This function accepts an alc.dhcpv6 object as a parameter. This object replaces existing ‟Relay Message” option.

Example-1 script for single relay message:

import alc
#input packet is a relay-reply msg
embed_dhcpv6_packet=alc.dhcpv6.get_relaymsg()
iana_list=embed_dhcpv6_packet.get_iana()
iana_list[0][1]='\x00\x00\x04\xb0' #change T1 to 1200
embed_dhcpv6_packet.set_iana(iana_list)#update the iana of the embedded packet
alc.dhcpv6.set_relaymsg(embed_dhcpv6_packet)#update the Relay Message option

Example-2 script for double relay message (relay of relay):

import alc
#input packet is a relay-reply msg
embed_lv1_packet=alc.dhcpv6.get_relaymsg() #get the level=1 embedded packet
embed_lv2_packet= embed_lv1_packet.get_relaymsg()#get level-2 packet embedded in level-1 packet
iana_list=embed_dhcpv6_packet.get_iana()
iana_list[0][1]='\x00\x00\x04\xb0' #change T1 to 1200
embed_dhcpv6_packet.set_iana(iana_list)#update the iana
embed_lv1_packet.set_relaymsg(embed_lv2_packet)#update the Relay Message option of lv1 msg
alc.dhcpv6.set_relaymsg(embed_lv1_packet)#update the Relay Message option of the top level msg

Python policy – Diameter API

The alc.diameter Python module provides an API for Diameter message manipulation.

Terminology used in the API description:

  • top-level-AVP

    AVP appearing at the top level in a Diameter message, in other words, not embedded in the Data field of a grouped AVP

  • embedded-AVP

    AVP embedded in the Data field of a grouped AVP. An embedded AVP can be a grouped AVP. This is called nesting.

  • AVP-tuple

    Python tuple with following values:

    (AVP code, vendor ID, flags)

    • AVP code: integer, AVP header field

    • Vendor ID: integer, AVP header field

    • flags: string, AVP header field

  • AVP-value-tuple

    Python tuple with following values:

    (flags, data)

    • flags: string, AVP header field

    • data: string, AVP data field

  • AVP-key-tuple

    Python tuple with following values:

    (AVP code, Vendor ID)

    • AVP code: integer, AVP header field

    • Vendor ID : integer, AVP header field

  • grouped-AVP-value-tuple

    Python tuple with following values:

    (flags, grouped-AVP-dictionary)

    flags: string, AVP header field

  • grouped-AVP-dictionary

    Python dictionary with following key:value pairs:

    {AVP-key-tuple: [AVP-value-tuple or grouped-AVP-value-tuple, …], ... }

    • key = AVP-key-tuple

    • value = list of AVP-value-tuples or list of grouped-AVP-value-tuples

  • grouped-AVP-decode-tuple

    Python tuple with the following values (AVP-key-tuple, …)

    tuple of AVP-key-tuples
  • AVP-order-tuple

    Python tuple with the following values (AVP-key-tuple, …)

    tuple of AVP-key-tuples

Diameter message header alc.diameter attributes displays attributes available in alc.diameter module providing access to the Diameter message header:

Table 6. Diameter message header alc.diameter attributes
Attribute Description Type Access

application_id

Diameter message header field

string

Read/Write

code

Diameter message header field

string

Read/Write

end_to_end_id

Diameter message header field

string

Read/Write

flags

Diameter message header field

string

Read/Write

hop_by_hop_id

Diameter message header field

string

Read/Write

msg_length

Diameter message header field. The value is the message length of the original diameter message.

string

Read

version

Diameter message header field

string

Read/Write

Message and AVP manipulation functionality alc.diameter methods displays methods available in alc.diameter module providing message and AVP manipulation functionality:

Table 7. Message and AVP manipulation functionality alc.diameter methods
Method Description

clear_avps(AVP code, vendor id)

AVP code, vendor id = top-level-AVP

Remove all instances of the specified AVP from the message. Applies to top-level-AVP’s only. If the specified AVP is not present, no python error is generated. Vendor ID value zero matches top-level-AVP’s without vendor ID field.

Return value: None

For example:

diameter.clear_avps(256, 12645)

drop()

Drop the Diameter message. Packet is consumed at TCP level (ack send). A drop triggers retransmits on Diameter level.

Return value: None

For example:

diameter.drop()

get_avps(AVP code, vendor id)

AVP code, vendor id = top-level-AVP

Returns a list of AVP-value-tuples. Each AVP-value-tuple represents an instance of the specified AVP in the message. Applies to top-level-AVP’s only. The position in the list corresponds with the position of the AVP instance in the message at that stage in the script. When executed before any clear or set AVP method, the list order corresponds with the AVP order in the received message. If the specified AVP is a grouped AVP, then the data contains all the embedded-AVP’s. An empty list is returned if the specified AVP is not present. Vendor ID value zero matches top-level-AVP’s without vendor ID field.

For example:

diameter.get_avps(263, 0)

[('@', 'bng.nokia.com;1398156449;28')]

get_avps_list()

Returns a list of AVP-tuples. Each AVP-tuple represents an instance of an AVP in the message. Applies to top-level-AVP’s only. The position in the list corresponds with the position of the AVP in the message at that stage in the script. When executed before any clear or set AVP method, the list order corresponds with the AVP order in the received message. When multiple instances of an AVP are present in the message, then there are multiple instances in the list. The vendor ID has value zero when not present. Grouped AVPs cannot be distinguished from other AVPs in the list.

For example:

diameter.get_avps_list()

[(263, 0, '@'), (264, 0, '@'), (296, 0, '@'), (258, 0, '@'), (416, 0, '@'), (415, 0, '@'), (268, 0, '@'), (55, 0, '@'), (456, 0, '@'), (456, 0, '@'), (456, 0, '@'), (293, 0, '@'), (256, 12645, '\x80')]

get_grouped_avps(AVP code, vendor id, grouped-AVP-decode-tuple)

AVP code, vendor id = top-level-AVP

Returns a list of grouped-AVP-value-tuples with each grouped-AVP-dictionary entry representing an embedded AVP. Each grouped-AVP-value-tuple represents an instance of the specified AVP in the message. Applies to top-level-AVPs of type grouped only. The position in the list corresponds with the position of the grouped AVP instance in the message at that stage in the script. When executed before any clear or set AVP method, the list order corresponds with the AVP order in the received message.

If the grouped-AVP-decode-tuple is empty, only the specified top-level-AVP is expanded in a grouped-AVP-value-tuple, with each grouped-AVP-dictionary entry representing an embedded AVP and all dictionary values of type ‟list of AVP-value-tuples”

To expand nested AVPs (grouped AVPs embedded in a grouped AVP), the grouped top-level-AVP and grouped embedded-AVP to expand must be added to the grouped-AVP-decode-tuple. All grouped AVPs in the grouped-AVP-decode-tuple are expanded in a list of grouped-AVP-value-tuples provided that their embedding AVP is also in the list.

The position of the embedded AVPs in the grouped-AVP-dictionary does not correspond with the position in the grouped AVP.

If the specified top-level-AVP is not a grouped AVP, then a Python error is generated: ‟ValueError: malformed diameter message”.

For example:

To expand the Multiple Services Credit Control (456) grouped top level AVP:

diameter.get_grouped_avps(456,0,())

[('@', {(432, 0): [('@', '\x00\x00\x00\x01')], (431, 0): [('@', '\x00\x00\x01\xa4@\x00\x00\x0c\x00\x00\x00d')], (448, 0): [('@', '\x00\x00\x04\xb0')], (268, 0): [('@', '\x00\x00\x07\xd1')]}), ('@', {(432, 0): [('@', '\x00\x00\x00\x02')], (431, 0): [('@', '\x00\x00\x01\xa4@\x00\x00\x0c\x00\x00\x03\x84')], (448, 0): [('@', '\x00\x00\x04\xb0')], (268, 0): [('@', '\x00\x00\x07\xd1')]}), ('@', {(432, 0): [('@', '\x00\x00\x00\x03')], (431, 0): [('@', '\x00\x00\x01\xa4@\x00\x00\x0c\x00\x00\x00<')], (448, 0): [('@', '\x00\x00\x04\xb0')], (268, 0): [('@', '\x00\x00\x07\xd1')]})]

To expand the nested Granted-Service-Unit AVP (code 431) in the grouped Multiple Services Credit Control top level AVP (code 456):

diameter.get_grouped_avps(456,0,((456,0),(431,0)))

[('@', {(432, 0): [('@', '\x00\x00\x00\x01')], (431, 0): [('@', {(420, 0): [('@', '\x00\x00\x00d')]})], (448, 0): [('@', '\x00\x00\x04\xb0')], (268, 0): [('@', '\x00\x00\x07\xd1')]}), ('@', {(432, 0): [('@', '\x00\x00\x00\x02')], (431, 0): [('@', {(420, 0): [('@', '\x00\x00\x03\x84')]})], (448, 0): [('@', '\x00\x00\x04\xb0')], (268, 0): [('@', '\x00\x00\x07\xd1')]}), ('@', {(432, 0): [('@', '\x00\x00\x00\x03')], (431, 0): [('@', {(420, 0): [('@', '\x00\x00\x00<')]})], (448, 0): [('@', '\x00\x00\x04\xb0')], (268, 0): [('@', '\x00\x00\x07\xd1')]})]

set_avps(AVP code, vendor id, list of AVP-value-tuples)

AVP code, vendor id = top-level-AVP

Remove all instances of the specified top-level-AVP from the message. For each entry in the AVP-value-tuple list, a top-level-AVP instance is inserted.

If the specified vendor ID value is zero, then no vendor ID field is inserted and setting the Vendor-Specific bit in the flags field of the AVP value tuple results in a Python error: ‟ValueError: no vendor ID but vendor flag set”.

If the specified vendor ID value is non-zero, then a vendor ID field is inserted and not setting the Vendor-Specific bit in the flags field of the AVP value tuple results in a Python error: ‟ValueError: vendor ID but vendor flag not set”.

Padding between AVPs, AVP length and Diameter message length are adapted accordingly by the system.

Return value is None.

For example:

diameter.set_avps(461,0,[('\x40', 'Pytho.n-1'), ('\x40', 'Python-2')])

set_fixed_position_avps(AVP-order-tuple)

Put the specified top-level-AVPs at the beginning of the message in the order as specified in the AVP-order-tuple.

This method overrides the order of the top-level-AVPs in the resulting Diameter message. If for example the session-id AVP must appear as first in the message, then the corresponding AVP-key-tuple must be included in the first position of the AVP-order-tuple.

AVPs not present in the message but specified in the AVP-order-tuple are ignored.

AVPs present in the message and not specified in the AVP-order-tuple are included in the final message after the AVPs listed in the AVP-order-tuple. The order is deterministic but implementation specific.

This method can appear at any point in the script. The last call overrides the previous one.

From a black box viewpoint, this method is executed at the end of the script: the result of the call is not reflected in the list returned by a subsequent get_avp_list() call.

Return value: None

For example:

diameter.set_fixed_position_avps(((263,0), (264,0), (296,0), (268,0)

set_grouped_avps(AVP code, vendor id, list of grouped-AVP-value-tuples)

AVP code, vendor id = top-level-AVP

Remove all instances of the specified grouped top-level-AVP from the message. For each entry in the grouped-AVP-value-tuples list, a grouped top-level-AVP instance is inserted.

The order of the embedded-AVPs in the grouped AVP cannot be specified. If the specified vendor ID value is zero, then no vendor ID field is inserted and setting the Vendor-Specific bit in the flags field of the AVP value tuple will then result in a Python error: ‟ValueError: no vendor ID but vendor flag set”.

If the specified vendor ID value is non-zero, then a vendor ID field is inserted and not setting the Vendor-Specific bit in the flags field of the AVP value tuple will result in a Python error: ‟ValueError: vendor ID but vendor flag not set”.

Padding between AVPs, AVP length and Diameter message length are adapted accordingly.

Return value is None.

For example:

diameter.set_grouped_avps(456,0,[('@', {(432, 0): [('@', '\x00\x00\x00\x01')], (431, 0): [('@', {(420, 0): [('@', '\x00\x00\x00\x2b')]})], (448, 0): [('@', '\x00\x00\x04\xb0')], (268, 0): [('@', '\x00\x00\x07\xd1')]}), ('@', {(432, 0): [('@', '\x00\x00\x00\x03')], (431, 0): [('@', {(420, 0): [('@', '\x00\x00\x00\x53')]})], (448, 0): [('@', '\x00\x00\x04\xb0')], (268, 0): [('@', '\x00\x00\x07\xd1')]})])

To enable Diameter message manipulation using Python, a Python policy must be configured in the diameter-peer-policy. For example:

The Python policy specifies the Python script to use for each Diameter message type received or transmitted on a Diameter peer. In the ingress direction, the Python script is executed when the corresponding packet type is received on the Diameter peer but before further processing in the system. In the egress direction, the Python script is executed prior to sending the corresponding packet type on the Diameter peer. For example:

The Python script specifies the location of the script and optional protection mechanism. For example:

configure
    python
        python-script "diameter-1" create
            primary-url "ftp://usr:pwd@192.0.2.1/./py/diam-1.py"
            no shutdown
        exit
    exit

As an example, the diam-1.py script, clears the M-bit from the Event-Timestamp AVP (code 55):

    from alc import diameter
    avp55=diameter.get_avps(55,0)
    diameter.set_avps(55,0,[('\x00',avp55[0][1])])

Python policy – DHCP transaction cache API

A DHCP transaction cache (DTC) is a short-lived cache during DHCPv4/v6 transaction. The cache could be used to store user-chosen information or return ESM attributes via a Python script. The DTC’s lifetime is only during a single DHCP transaction (for example, only between discovery-offer, request-reply). This includes both alc.dtc.store() data and alc.dtc.setESM() data. DTC is also a transaction specific cache, which means the cached information could only be accessed by the Python script running in same DHCP transaction.

The following are the DTC APIs:

  • alc.dtc.derivedId: A string used as a LUDB lookup key

  • alc.dtc.store(cache-key, cache-value): Store the value with the specified cache-key in DTC, both key and value are string.

  • alc.dtc.retrieve(cache-key): Returns the cached value string according to the specified cache-key, raise exception if specified key does not exist.

  • alc.dtc.setESM(ESM-key, value): Sets the specified ESM attribute, which could be used when system creating the ESM host.

    Note:

    Because of the short-lived nature of DTC, setESM should be used in final DHCP transaction before system create ESM host., such as DHCPv4 REQUEST-ACK.

  • The following is a list of supported ESM-keys and corresponding Python type:

    • alc.dtc.accountingPolicy:str

    • alc.dtc.ancpString:str

    • alc.dtc.appProfileString:str

    • alc.dtc.catMapString:str

    • alc.dtc.defGw:str

    • alc.dtc.dhcpv4GIAddr:str

    • alc.dtc.dhcpv4Pool:str

    • alc.dtc.dhcpv4ServerAddr:str

    • alc.dtc.dhcpv6LinkAddr:str

    • alc.dtc.dhcpv6PreferredLifetime

    • alc.dtc.dhcpv6RebindTimer

    • alc.dtc.dhcpv6RenewTimer

    • alc.dtc.dhcpv6ServerAddr:str

    • alc.dtc.dhcpv6ValidLifetime

    • alc.dtc.intDestId:str

    • alc.dtc.ipAddress:str

    • alc.dtc.ipv4LeaseTime:int

    • alc.dtc.ipv4PrimDns:str

    • alc.dtc.ipv4SecDns:str

    • alc.dtc.ipv6Address:str

    • alc.dtc.ipv6DelegatedPrefix:str

    • alc.dtc.ipv6DelegatedPrefixLength:int

    • alc.dtc.ipv6PrefixPool:str

    • alc.dtc.ipv6PrimDns:str

    • alc.dtc.ipv6SecDns:str

    • alc.dtc.ipv6SlaacPrefix:str

    • alc.dtc.ipv6WanPool:str

    • alc.dtc.msapGroupInterface:str

    • alc.dtc.msapPolicy:str

    • alc.dtc.msapServiceId:str

    • alc.dtc.primNbns:str

    • alc.dtc.primNbns:str

    • alc.dtc.retailServiceId:str

    • alc.dtc.secNbns:str

    • alc.dtc.slaProfileString:str

    • alc.dtc.subIdent:str

    • alc.dtc.subnetMask:str

    • alc.dtc.subProfileString:str

Python for PPPoE API

The following are the system-provided Python API PPPoE packets:

  • The system provides a Python object for input PPPoE packet: alc.pppoe.

  • alc.pppoe class is always available even when Python script is trigger by other type of packet that encapsulated in PPPoE, such as LCP/IPCP/PAP/CHAP, and so on.

  • alc.pppoe has the following attributes to represent the PPPoE header fields.

System provided Python API PPPoE packets displays information about system-provided Python API PPPoE packets.

Table 8. System provided Python API PPPoE packets
Class attributes PPPoE header field Access

alc.pppoe.dest_mac

Str, destination MAC address; the format is ‟xx:xx:xx:xx:xx:xx”

Read

alc.pppoe.src_mac

Str, source MAC address; the format is ‟xx:xx:xx:xx:xx:xx”

Read

alc.pppoe.port_id

Str, the port-id of the PPPoE session like ‟1/1/3”

Read

alc.pppoe.vlan_tag

Str, the vlan tag of the PPPoE session like ‟100” or ‟100.200”

Read

alc.pppoe.ver

Integer, ver field in PPPoE header

Read

alc.pppoe.type

Integer, type field in PPPoE header

Read

alc.pppoe.code

Integer, code field in PPPoE header

Read

alc.pppoe.session_id

Integer, session_id field in PPPoE header

Read

alc.pppoe.len

Integer, length field in PPPoE header

Read

The following is a list of functions of alc.pppoe:

  • alc.pppoe.drop()

    The system drops the resulting packet.

  • alc.pppoe.getTagList()

    The system returns a tuple that includes tag-type of existing PPPoE tags in the packet. The order of the element in the tuple is as same as the tags appear in the packet. If there are multiple instances of same option, then each instance is one element in the tuple.

  • alc.pppoe.get(tag-type)

    The system returns a tuple of strings, each string represent one instance of the specified tags, the value of this string is exact bytes of the tag_value as it appears in packet (excludes tag_type and tag_len); if tag does not exist, return (); if a specific instance’s tag_length=0, return ‟” for this instance.

  • alc.pppoe.set(tag-type,valTuple)

    This function removes all the existing instances of specified tags and insert a list of new tags. Each element in valTuple is a string, represent one instance of the new tag to be inserted. For each new option, the type is specified in tag-type, the option-len is the length of the element, reset of option is the element itself.

  • alc.pppoe.clear(tag-type)

    This function removes all the existing instances of specified tags. In fact this function is a shortcut version of alc.pppoe.set(tag-type,()).

  • alc.pppoe.getPPP()

    This function returns an alc.ppp object which represent the LCP/IPCP/IP6CP packet encapsulated inside the PPPoE packet. It returns none if there is no such packet.

  • alc.pppoe.getPAP()

    This function returns an alc.pap object which represents the PAP packet encapsulated inside the PPPoE packet. It returns none if there is no PAP packet.

  • alc.pppoe.setPAP(pap_obj)

    This function accepts an alc.pap object as the parameter and replace the existing PAP packet that encapsulated in the PPPoE packet. An exception is raised when there is no existing PAP packet.

  • alc.pppoe.getCHAP()

    This function returns an alc.chap object, which represent the CHAP packet encapsulated inside the PPPoE packet. It returns none if there is no CHAP packet.

  • alc.pppoe.setCHAP(chap_obj)

    This function accepts an alc.chap object as the parameter and replace the existing CHAP packet that encapsulated in the PPPoE packet. An exception is raised when there is no existing CHAP packet.

Python API for PPP packet

The system provides a Python object for PPP packet encapsulated in PPPoE packets: alc.ppp.

Use alc.pppoe.getPPP() to get the alc.ppp object in the input PPPoE packet. This class only supports following control level protocol:
  • LCP (read-only)

  • IPCP (read-only)

  • IP6CP(read-only)

  • alc.ppp has following attributes to represent the PPP header fields:

PPP header field attributes displays information about PPP header field attributes.

Table 9. PPP header field attributes
Class attributes PPP header field Access

alc.ppp.protocol

Integer, protocol field in ppp header

Read

alc.ppp.code

Integer, code field in ppp protocol header

Read

alc.ppp.id

Integer, identifier field in ppp protocol header

Read

alc.ppp.len

Integer, length field in ppp protocol header

Read

The following is a list of functions of alc.ppp:

  • alc.ppp.getOptionList()

    Returns a tuple that includes type of existing PPP options in the packet. The order of the element in the tuple is as same as the options appear in the packet. If there are multiple instances of same option, then each instance is one element in the tuple.

  • alc.ppp.get(op-type)

    Returns a tuple of strings, each string represent one instance of the specified option, the value of this string is exact bytes of the option_data as it appears in packet (excludes option_type and option_len); if the option does not exist, return (); if a specific instance’s option_length=0, return ‟” for this instance.

Python API for PPP PAP

The system provides a python object for PAP packet encapsulated in the input PPPoE packets: alc.pap.

Use alc.pppoe.getPAP() to get the alc.pap object in the input PPPoE packet.

To apply changes to PAP packet, alc.pppoe.setPAP(new_alc.pap_obj) must be called.

PAP header fields provides PAP header field information. alc.pap has following attributes to represent the PAP header fields:

Table 10. PAP header fields
Class attributes PAP field Access

alc.pap.code

Integer, code field in pap protocol header

Read

alc.pap.id

Integer, identifier field in pap protocol header

Read

alc.pap.len

Integer, length field in pap protocol header

Read

The following is a list of functions in the class:

  • alc.pap.getCred()

    Only apply to Authentication-Request packet. Returns a tuple: (peer-id, password), both are str. If either of them has 0 length, it is ‟”; if the system failed to parse the packet, such as the length < 6 bytes or the packet is not an authentication-request, then the system raises an exception. If the packet has a wrong type it returns None (that is, no exception is raised).

  • alc.pap.setCred(cred_tuple)

    Only apply to Authentication-Request packet. Set the peer-id and password to cred_tuple; cred_tuple is a tuple:(peer-id, password) both are str. ‟” is allowed as the value for either of them. If the packet is not authentication-request, then system raises an exception.

  • alc.pap.getMsg()

    Only apply to Authentication-Ack/Nak packet. Return the message in the packet as a str; return ‟” if len of message is 0; raise an exception for wrong type of packet (such as an auth-request). If the packet has a wrong type it returns None (that is, no exception is raised).

  • alc.pap.setMsg(msg)

    Only apply to Authentication-Ack/Nak packet. set the message in the packet to parameter msg; msg is a str; ‟” is allowed; raise an exception for wrong type of packet (such as an auth-request).

Python API for PPP CHAP

The system provides a python object for CHAP packet encapsulated in the input PPPoE packets: alc.chap.

Use alc.pppoe.getCHAP() to get the alc.chap object in the input PPPoE packet.

To apply changes to PAP packet, alc.pppoe.setCHAP(new_alc.pap_obj) need to be called.

CHAP header fields provides CHAP header field information. alc.chap has following attributes to represent the CHAP header fields:

Table 11. CHAP header fields
Class attributes PAP field Access

alc.chap.code

Integer, code field in chap protocol header

Read

alc.chap.id

Integer, identifier field in chap protocol header

Read

alc.chap.len

Integer, length field in chap protocol header

Read

The following is a list of functions in the class:

  • alc.chap.getCred()

    Only apply to challenge/response packet. Returns a tuple: (name, challenge, response), both are str. If either of them has 0 length, it is be ‟”; if system failed to parse the packet, such as the length < minimal_length or the packet is not challenge/response, then system raise an exception. If the packet has a wrong type it returns None (that is, no exception is raised). The second value in the returned tuple either contains the challenge (in a challenge packet) or the response (in a response packet) value.

  • alc.chap.setCred(chap_cred_tuple)

    Only apply to challenge/response packets. Set the name and challenge to chap_cred_tuple; chap_cred_tuple is a tuple: (name, challenge, response) both are str. ‟” is allowed as the value for either of them. If packet is not challenge/response, then system raises an exception. The second value in the supplied tuple either contains the challenge (in a challenge packet) or the response (in a response packet) value.

  • alc.chap.getMsg()

    Only apply to Success/Failure packet. Return the message in the packet as a str; return ‟” if len of message is 0; raise an exception for wrong type of packet (such as challenge). If the packet has a wrong type it returns None (that is, no exception is raised).

  • alc.chap.setMsg(msg)

    Only apply to Success/Failure packet. set the message in the packet to parameter msg; msg is a str; ‟” is allowed; raise an exception for wrong type of packet (such as challenge).

Python ESM API

The system provides a python object: alc.esm, to provide flexible LUDB lookup and return ESM attributes directly from script.

alc.esm support both DHCPv4/v6 and PPPoE hosts.

alc.esm has following attribute and function:

  • alc.esm.derivedId: A string used as LUDB lookup key

  • alc.esm.set(ESM-key, value): Set the value of specified ESM attribute, it is used for ESM host creation.

alc.esm.derivedId should be set for all types of ingress packets that system used to access LUDB; for example PPPoE PADI and PADR, DHCP discovery and request.

The following is a list of supported ESM-key and its corresponding Python type:

  • alc.esm.accountingPolicy:str

  • alc.esm.ancpString:str

  • alc.esm.appProfileString:str

  • alc.esm.catMapString:str

  • alc.esm.defGw:str

  • alc.esm.dhcpv4GIAddr:str

  • alc.esm.dhcpv4Pool:str

  • alc.esm.dhcpv4ServerAddr:str

  • alc.esm.dhcpv6LinkAddr:str

  • alc.esm.dhcpv6ServerAddr:str

  • alc.esm.intDestId:str

  • alc.esm.ipAddress:str

  • alc.esm.ipv4LeaseTime:int

  • alc.esm.ipv4PrimDns:str

  • alc.esm.ipv4SecDns:str

  • alc.esm.ipv6Address:str

  • alc.esm.dhcpv6PreferredLifetime

  • alc.esm. dhcpv6RebindTimer

  • alc.esm. dhcpv6RenewTimer

  • alc.esm. dhcpv6ValidLifetime

  • alc.esm.ipv6DelegatedPrefix:str

  • alc.esm.ipv6DelegatedPrefixLength:int

  • alc.esm.ipv6PrefixPool:str

  • alc.esm.ipv6PrimDns:str

  • alc.esm.ipv6SecDns:str

  • alc.esm.ipv6SlaacPrefix:str

  • alc.esm.ipv6WanPool:str

  • alc.esm.msapGroupInterface:str

  • alc.esm.msapPolicy:str

  • alc.esm.msapServiceId:str

  • alc.esm.primNbns:str

  • alc.esm.retailServiceId:str

  • alc.esm.secNbns:str

  • alc.esm.slaProfileString:str

  • alc.esm.spiSharingGroupId:int

  • alc.esm.subIdent:str

  • alc.esm.subProfileString:str

  • alc.esm.subnetMask:str

  • alc.dtc.dhcpv6PreferredLifetime

  • alc.dtc.dhcpv6RebindTimer

  • alc.dtc.dhcpv6RenewTimer

  • alc.dtc.dhcpv6ValidLifetime

The following are PPPoE-specific keys:

  • alc.esm.padoDelay: int

  • alc.esm.pppAuthProtocol: str, {‛pap’|’chap’|’pref-chap’|’pref-pap’}

  • alc.esm.pppMTU: int

With PPPoE, alc.esm.set() could only be called ingress discovery (PADI, PADR) and authentication (PAP Authenticate, CHAP Response) packets. If it is called multiple times, the first one wins except the derived-id, while the alc.esm.padoDelay only applies when the system receives PADI. alc.esm.pppAuthProtocol and alc.esm.pppMTU can only be set when a PADR is received.

alc.esm.set() is blocked during LCP/IPCP/IP6CP phase.

The system raises an exception if alc.esm.set() is called at wrong time. Setting the ESM keys that do not apply to the host type are ignored; for example, setting PPP(oE) keys for DHCP hosts is ignored.

The value returned by alc.esm.set() take highest precedence than other sources like CLI/LUDB.

For alc.esm.set(ESM-key, value)

  • PPPoE can only be called for ingress discovery (PADI, PADR) and authentication (PAP Authenticate, CHAP Response) packets. An exception is raised otherwise (for example, wrong time, including egress discovery PADO and PADS, and authentication PAP Ack/Nak and CHAP Success/Failure).

  • The PPPoE key alc.esm.padoDelay can only be set when PADI is received (otherwise, it is too late).

  • Setting ESM keys for a PPPoE host which do not apply is ignored (that is, no exception is raised).

  • PPP keys alc.esm.pppAuthProtocol and alc.esm.pppMTU can only be set when PADR is received. It has no effect when PADI is received.

  • Setting PPP(oE) keys for a DHCP host is ignored.

Python cache support

Python cache support allows information to be shared across different run times of the same python script or even different python scripts in a programmatic way. It essentially provides a central memory cache and a set of APIs which allows the user store and retrieve strings. For example, a DHCP python-script could store a DHCP option into cache and later a RADIUS python-script could retrieve stored string and add it into access-request.

Each cached entry in the cache is a tuple of (key, val). key is used as entry ID. val is the string to be cached. Both key and val are strings. The max length of the key is 512 bytes. Future more, the combine length of key+val is limited by the configured value of entry-size size command in the python-policy.

The Python cache can be enabled per python-policy. Each python policy has its own cache memory which script in other python-policy cannot access. This also implies that the key of a cached entry in different a python policy could overlap.

The user can also specify the max number cache entry per python policy the command max-entries command. System has a global limit for python cache memory of 256MB.

The cached entries could be made persistent by saving it to CF card. This can be enabled with the persistence command in the Python policy.

Note: From memory consumption point of view, with MCS enabled, each cached entry has a corresponding MCS record, so each entry consumes twice amount of memory.

The system also supports syncing the python cache across chassis with MCS. This can be configured per python policy with the mcs-peer command in the python policy.

Each cached entry has a remaining lifetime. If it decreases over time, the system removes the cached entry if its remaining lifetime is 0. The remaining lifetime can be changed using a system- provided API. The initial lifetime of a newly created cache entry is 600 seconds. Python cache is supported only with CPM-based Python policies.

The following are the Python cache APIs in a module alc.cache:

  • alc.cache.save(val, key)

    Saves the val identified by the key into the cache. If there is an existing cache entry with same key, it is overwritten with the new val. In such overwritten cases, the lifetime of the entry is also reset to the default value of 600 seconds. An exception is raised if the save failed (for example, because the maximum number of entries is exceeded).

  • alc.cache.retrieve(key)

    Returns the stored entry’s val identified by the key. A KeyError exception is raised if the specified entry does not exist.

  • alc.cache.clear(key)

    Removes the cached entry identified by the key. Raise KeyError exception if the specified entry does not exist.

  • cache.get_lifetime(key)

    The system returns a integer as seconds of remaining lifetime of the specified entry. It returns none if the specified entry does not exist. An exception is raised for any other error.

  • cache.set_lifetime(key,new_lifetime)

    The new_lifetime value is an integer. The system sets the remaining lifetime of the specified entry to the number of seconds of the new_lifetime. An exception is raised for any error including specified entry does not exist. If the new_lifetime>=max_lifetime(configurable using the max-entry-life command in the python policy), then the system sets the actual lifetime to the max_lifetime.

Applying a Python policy

The following is a list of places that a Python policy could be applied:

  • Under capture SAP

    Apply to the DHCPv4/v6 packets sent/received on the capture SAP

  • Under group-interface

    Apply to DHCPv4/v6 packets sent/received on the group-interface

  • Under subscriber-interface

    Apply to DHCPv4 packets on the retail subscriber interface

  • In the radius-server-policy

    Apply to the RADIUS packets sent, received to, or from the RADIUS servers configured in the radius-server-policy

  • In the radius-proxy-server

    Apply to the RADIUS packets on the client side of proxy

  • In the diameter-peer-policy

    Apply to the Diameter packets sent or received on the Diameter peers configured in the policy

Python script protection

To protect the Python script from unintended changes, the SR OS supports a new Python script file format:SRPY. SRPY includes a key based hash (HMAC) of the original script content. When the system loads a script with SRPY format, a hash is computed by using a configured key and script content. The result hash is compared to the embedded hash. If it is the same, then this script is considered valid. Otherwise, the system aborts with a warning message.

Users can configure protection hmac-sha256 key key within a Python script. To mandate, all configured scripts must be in SRPY format.

The system provides a tools command (tools perform python-script protect) to convert a Python script into SRPY format.

There are also running time limitations for Python scripts to prevent DoS attacks:

  • Centralized:

    • Initial run: 100 ms

    • Subsequent run: 10ms

  • Distributed:

    • Initial run: 10 ms

    • Subsequent run: 1ms

Tips and tricks

  • Use xrange() instead of range().

  • Avoid too many string operations. The following scripts provide the same output:


# This script takes 2.5 seconds. s = ""
for c in 'A'*50000:
s += str(ord(c)) + ', ' print '[' + s[:-2] + ']'

# This script takes 0.1 seconds. print map(ord, 'A'*50000)