SIP MRF ALG Support for RFC-7315 Implicit Registration

Problem this snippet solves:

The SIP MRF ALG, when used with an LSN pool, provides the ability to inspect SIP messages and modify all of the appropriate headers with a CGNAT LSN pool.

However, in 3GPP IMS networks, the REGISTER message uses a different SIP ID than the ID that is used in subsequent messages, such as a SUBSCRIBE. If you try to deploy the F5 ALG in this scenario, the F5 will block the SUBSCRIBE message because the From header does not match what was stored in the registration database. This is called implicit registration, and it is the mechanism by which a user is allowed to register simultaneously more than one of his/her Public User Identities.

When the initial REGISTER is sent by the UE, the F5 MRF ALG creates a database entry for the REGISTER event, and reserves and IP and port for the CGNAT translation. It keeps this listener and database entry alive until the end of the timeout in the EXPIRES header.

The next message is the 200 OK for the REGISTER. This contains a P-Associated-URI that gets used by the client in subsequent requests.

In subsequent messages, the client will use the value from the P-Associated-URI.

When the F5 sees the To: and From: headers with the new value, it drops the message because it doesn’t match what is in the database for that connection and creates an error in the log that it is an unregistered subscriber.

The iRule, to fix this problem, will watch for the SIP_RESPONSE event, which is triggered when the system receives a response from the server and the SIP headers have been parsed. This is before the “MR_INGRESS” event, which happens when the system is sending it to the Message Routing SIP MRF ALG.

In the SIP_RESPONSE event, we look for a CSeq that matches “* REGISTER”. If we see this, we get the P-Associated-URI value, and create a table entry that is indexed by the P-Associated-URI value with a value equal to the ID that was in the REGISTER To and From headers. The table will have a timeout value equal to the Expires header that was in the 200 OK response for the REGISTER.

When the SUBSCRIBE is sent and then this packet is received by the F5 and the SIP headers have been processed, it is known as the “SIP_REQUEST” event. When it is passed over to the MRF ALG, this is known as the “MR_INGRESS” event.

In the SIP_REQUEST event, we look for a SUBSCRIBE message. We lookup the P-Associated-URI value in the From header, and see if there is a corresponding table entry. If there is, we save a copy of the SIP From, and To headers. We then replace those headers by changing the P-Associated-URI value back to the MCPTT value.

Now, when MR_INGRESS fires, and it is sent to the SIP ALG, it sees that the From and To headers are the same as the database entry and it allow it. The next event, MR_EGRESS, means it has left the Message Routing ALG, and then the event that occurs is SIP_REQUEST_SEND. This means it has been processed by MRF, and is now being sent to the server.

In the SIP_REQUEST_SEND event, we change the headers back to how they were originally.

In addition, this iRule inserts and "ep=" header into the Contact header that contains the original client IP for a REGISTER message, and inserts a header called "P-UE-Flow-Info" that contains information about the original subscriber. This is needed by some CSCF servers.

How to use this snippet:

Create a virtual server with an MRF session and MRF routing profile and apply it. An example:

ltm virtual vs-sip-alg-v6 {
    destination 2002:abcd:f5:20::.sip
    ip-protocol udp
    last-modified-time 2018-09-04:11:20:51
    mask ffff:ffff:ffff:ffff::
    profiles {
        my-siprouter-alg { }
        my-sipsession-alg { }
        udp { }
    }
    rules {
        SIP-Headers
    }
    source-address-translation {
        pool sip-alg-lsn-pool-v6
        type lsn
    }
    translate-address enabled
    translate-port enabled
    vlans {
        clients
    }
    vlans-enabled
    vs-index 3
}


ltm message-routing sip profile router my-siprouter-alg {
    app-service none
    defaults-from siprouter-alg
    traffic-group traffic-group-1
}
ltm message-routing sip profile router siprouter-alg {
    app-service none
    defaults-from siprouter
    nonregister-subscriber-callout disabled
    operation-mode application-level-gateway
    traffic-group traffic-group-1
}
ltm message-routing sip profile session my-sipsession-alg {
    app-service none
    defaults-from sipsession-alg
}

Code :

when CLIENT_ACCEPTED {

    #Get info about client in variables to use later in other events like SIP_REQUEST_SEND
    #This is needed for inserting the P-UE-Flow-Info information
    set clientaddr [IP::client_addr]
    set clientport [UDP::client_port]
    set vip [IP::local_addr]
    set vipport [UDP::local_port]

}
when MR_INGRESS {
}
when MR_EGRESS {
}

when SIP_REQUEST {
    #Look at the SIP method. If it is a SUBSCRIBE, NOTIFY, or INVITE, we need to look this up in the table and 
    switch "[SIP::method]" {
        "SUBSCRIBE" -
        "NOTIFY" -
        "INVITE" {
            #Mvalue is the value of the SIP ID inside the SIP::from header between the : and @ symbol
            #Example From: <>;tag=dc11036c the mvalue is +11395600061
            set mvalue "[lindex [split [lindex [split [SIP::from] @] 0] :] 1]"
            
            #Lookup the value in the table that shows the original SIP ID in the REGISTER message
            #Example if it was From: <312670100004861>;tag=08bcb27c
            #Would be 312670100004861
            #nvalue is that value we find in the table
            set nvalue "[table lookup $mvalue]"
            
            #Check if there is a table value. If so then grab old headers and then replace with new information
            if { $nvalue ne "" } {
                
                #Save the header values as X-From and X-To so we can put them back in the SIP_REQUEST_SEND event
                SIP::header insert X-From "[SIP::header value From]"
                SIP::header insert X-To "[SIP::header value To]"

                #Use these to pass into the procedure to get the new header value
                set oldfrom "[SIP::header value From]"
                set oldto "[SIP::header value To]"


                #Run proc to get the new header value that inserts new number into field
                set newfrom "[call replace_number_fromto_field $oldfrom $nvalue]"
                set newto "[call replace_number_fromto_field $oldto $nvalue]"

                #Replace the SIP From and To headers with the new values
                SIP::header replace From "$newfrom"
                SIP::header replace To "$newto"

            }
        }
        default {
        }
    }

}
when SIP_REQUEST_SEND {
    #If this is one of the events that we modified the headers, put the headers back the way they were.
    switch "[SIP::method]" {

        "SUBSCRIBE" -
        "NOTIFY" -
        "INVITE" {
            #Check if the X-From and X-To have anything in them. If they do, put the headers back with that information
            #Remove the X-From and X-To headers before sending to the server
            if { [SIP::header value X-From] ne "" } {
        
                SIP::header replace From "[SIP::header value X-From]"
                SIP::header remove X-From
            }
            if { [SIP::header value X-To] ne "" } {

                SIP::header replace To "[SIP::header value X-To]"
                SIP::header remove X-To
            }
            #The following only applies if this is an INVITE
            if { [SIP::method] eq "INVITE" } {
                #The following is the logic for inserting the P-UE-Flow-Info header
                #The UE address is just the client_address
                #The clientport is the UE original port
                set UE "\"\[$clientaddr\]:$clientport\""
                #Find the audio port 
                log local0. "First m= is [findstr [SIP::message] "m=audio " 8 " "] "
                set audioport "[findstr [SIP::message] "m=audio " 8 " "]"
                log local0. "Audioport is $audioport"
                set applicationport "[findstr [SIP::message] "m=application " 14 " "]"
                log local0. "Applicationport is $applicationport"
                set header "sig;src=$UE;msrc=\"\[[IP::local_addr]\]:$vipport\";dst=\"\[$vip\]:[UDP::local_port]\",audio;src=$UE;msrc=\"\[[IP::local_addr]\]:$vipport\";dst=\"\[$vip\]:[UDP::local_port]\""
                log local0. "P-UE-Flow-Info header is = $header"
                SIP::header insert P-UE-Flow-Info "$header"
            }
        }
        default {
        }
    }

}
when SIP_RESPONSE {
    #log local0. "Message in response is [SIP::message]"
    #Check if response is a REGISTER. If so create a table entry for it
    #Table entry is indexed by the P-Associated-URI with a value of the original SIP ID in the REGISTER message
    switch -glob "[SIP::header value CSeq]"  {
        "* REGISTER" {
            if { [SIP::header value P-Associated-URI] ne ""} {
                set myuri "[SIP::header value P-Associated-URI]"
                #Get the value between the : and the @ symbol
                #Example P-Associated-URI is
                #P-Associated-URI: <>,<>
                set pvalue "[lindex [split [lindex [split $myuri @] 0] :] 1]"

                #fvalue is the value between the : and @ symbol in the original from
                #example original from is sip:312670100004861@ims.mnc180.mcc310.3gppnetwork.org so fvalue is 312670100004861
                set fvalue "[lindex [split [lindex [split [SIP::from] @] 0] :] 1]"
                
                #We need to look at the Contact header to determine the value of the Expires there
                #A procedure called find_expires returns 0 if it couldn't parse it and the number if it could
                #
                set expires "[call find_expires [SIP::header value Contact]]"

                #Create a table entry indexed by the P-Associated-URI as the key with a value equal to the original SIP ID
                table set $pvalue $fvalue $expires
                
            } else {
                #If there was no P-Associated-URI we can't create a table entry
                log local0. "No P-Associated-URI found for [SIP::from] [SIP::to] in REGISTER response"
            }
        }
        "default" {
            #Default action for an event that didn't match above
        }
    }

}
when SIP_RESPONSE_SEND {
}

proc find_expires { contact } {
    #Look for the word "expires=" and then get the number value after that and before the ";" character
    set found [scan [string range $contact [string first expires= $contact] end] {expires=%[0-9];} expires]
    #Return a value of 0 if the above couldn't be parsed properly
    #A good example is Contact: <312670100004861>;expires=3600;+sip.instance="";mobility="mobile";+g.3gpp.icsi-ref="urn%3Aurn-7%3A3gpp-service.ims.icsi.mcptt";video;+g.3gpp.mcptt

    if { $found } {
        return $expires
    } else {
        return 0
    }
}

proc replace_number_fromto_field { str newnumber } {
    #Parses the string passed in and splits it up into a scheme, number, domain, and tag
    #Then puts the newnumber string into there replacing the previous number
    #Supposed to be used with the SIP::header value from and SIP::header value to
    #
    scan $str {%[^:]:%[^@]@%[^;];%s} scheme number domain tag
    if { [info exists tag] } {
        set newheader "$scheme:$newnumber@$domain;$tag"
    } else {
        set newheader "$scheme:$newnumber@$domain"
    }
    return $newheader
}
proc replace_number_contact_field { str newnumber } {
    #Pass in a string called str and newnumber and replace the number in the string
    #Assumes a string similar to the following
    #Contact: <312670100004861>;+sip.instance="";mobility="mobile";+g.3gpp.icsi-ref="urn%3Aurn-7%3A3gpp-service.ims.icsi.mcptt";video;+g.3gpp.mcptt
    
    #Split this up by first ":" the "@" symbol and remainder 
    scan $str {%[^:]:%[^@]@%s} scheme number remainder
    set newheader "$scheme:$newnumber@$remainder"
    
    #Returns a header in the format above with the newnumber in place
    return $newheader
}
Published Sep 13, 2018
Version 1.0

Was this article helpful?

1 Comment

  • torzillo_89761's avatar
    torzillo_89761
    Historic F5 Account

    I think the audioport, videoport, and applicationport need to be modified as follows:
                Check if there is an audioport, if so append audio information to header
                Audioport is in the SDP information and is m=audio followed by a port number like m=audio 24680 RTP/AVP 98 99
                if { $audioport ne "" } {
                    set UE "\"\[$clientaddr\]:$audioport\""
                    append header ",audio;src=$UE;msrc=\"\[[IP::local_addr]\]:$audioport\";dst=\"\[$vip\]:$vipport\""
                }
                Append application port to header if applicable
                if { $applicationport ne "" } {
                    set UE "\"\[$clientaddr\]:$applicationport\""
                    append header ",application;src=$UE;msrc=\"\[[IP::local_addr]\]:$applicationport\";dst=\"\[$vip\]:$vipport\""
                }
                Append video port to header if applicable
                if { $videoport ne "" } {
                    set UE "\"\[$clientaddr\]:$videoport\""
                    append header ",video;src=$UE;msrc=\"\[[IP::local_addr]\]:$videoport\";dst=\"\[$vip\]:$vipport\""
                }