Skip to content

Custom protocols

If Ostinato doesn't support a particular protocol, you can quickly and easily write a custom script.

Language

Scripts are written in JavaScript.

If you don't know JavaScript, don't worry - copy-paste and tools like ChatGPT will help greatly!

Tutorial

As an example, consider the MPLS frame format -

  0                   1                   2                   3
  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |                Label                  | Exp |S|     TTL       |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

The scripting environment has a protocol global object. You define a few simple methods for this object.

1. protocolFrameSize()

This function just needs to return the size of the protocol header in bytes. For MPLS it would be -

protocol.protocolFrameSize = function() {
    return 4;
}

2. protocolFrameValue()

This function returns an array of bytes - the content of the protocol header. For MPLS it might be something like this -

protocol.protocolFrameValue = function() {
    var tag = { label: 12701, exp: 5, s: 1, ttl: 64 };
    var tagVal = tag.label << 12 | tag.exp << 9 | tag.s << 8 | tag.ttl;

    var fv = Array(4);
    fv[0] = (tagVal >> 24) & 0xff;
    fv[1] = (tagVal >> 16) & 0xff;
    fv[2] = (tagVal >> 8) & 0xff;
    fv[3] = tagVal & 0xff;

    return fv;
}

To use different values for the MPLS fields, just change the below line as required. No change is required in the rest of the script -

var tag = { label: 12701, exp: 5, s: 1, ttl: 64 };

3. protocolId() - Optional

We would like Ostinato to auto-populate the EtherType, so we define one more function for the same -

protocol.protocolId = function() {
    return 0x8847; // MPLS EtherType
}

Specifying a name for our custom protocol, here's the full script required for MPLS -

protocol.name = 'MPLS';

protocol.protocolFrameSize = function() {
    return 4;
}

protocol.protocolFrameValue = function() {
    var tag = { label: 12701, exp: 5, s: 1, ttl: 64 };
    var tagVal = tag.label << 12 | tag.exp << 9 | tag.s << 8 | tag.ttl;

    var fv = Array(4);
    fv[0] = (tagVal >> 24) & 0xff;
    fv[1] = (tagVal >> 16) & 0xff;
    fv[2] = (tagVal >> 8) & 0xff;
    fv[3] = tagVal & 0xff;

    return fv;
}

protocol.protocolId = function() {
    return 0x8847;
}

That should be enough for most protocols; if your custom protocol needs something more, check out the reference section for more details. You can also check out more example user-scripts.

Custom user-scripts are available in the advanced mode of protocol selection -

Advanced Protocol Selection

Reference

The protocol object

Scripts have a protocol global object available to them with some built-in methods and some methods and properties that the user must provide.

"protocol" object
=================
User-specified Properties:
    - name: string
    . . .
User-specified Methods:
    - protocolFrameSize(index)
    - protocolFrameValue(index)
    . . .
Built-in Methods:
    - protocolFramePayloadSize(index)
    - payloadProtocolId(type)
    . . .

User-specified

Below methods/properties of the protocol object are specified by the user. At a minimum, the two methods marked Mandatory MUST be provided by the user, the remaining are optional - to be specified if required by the custom protocol.

Method/Property Type Description
protocolFrameSize(index)* Function Mandatory
returns the size of the protocol header in bytes
protocolFrameValue(index)* Function Mandatory
returns an array (without any holes) containing the protocol header bytes; size of the array should be same as what protocolFrameSize() returns
name String protocol name
Default value: Empty String
protocolId(type) Function returns the protocol id of your protocol based on type (one of ProtocolIdLlc, ProtocolIdEth, ProtocolIdIp or ProtocolIdTcpUdp); if your protocol immediately follows a LLC/Eth/IP/TCP/UDP Header, they will use the value that you return to fill-in their <protocol-id> fields.
Default value: 0
protocolFrameCksum(index, type, flags) Function returns the cksum of type for the protocol header; type is one of CksumIp, CksumIpPseudo, CksumTcpUdp
Default value: Calculated checksum as per the CksumType
protocolFrameValueVariable Boolean (Deprecated since version 0.7)
does the protocol contents vary at run time with every packet?
Default value: false
protocolFrameSizeVariable Boolean does the protocol header size vary at run time with every packet?
Default value: false
protocolFrameVariableCount Integer minimum number of frames required to vary its fields/size before the values are repeated again
Default value: 1 (protocol does not vary size/content)

Built-in

Below are built-in protocol object methods that can be called by the user-specified methods. They can only be called from the user-specified functions and cannot be redefined by the user.

Function Description
payloadProtocolId(type) returns the protocol id of the subsequent protocol - use when your protocol has a "Protocol Id" field that you need to fill-in based on what protocol follows yours
protocolFrameOffset(index) returns the byte offset within the frame from which your protocol will start
protocolFramePayloadSize(index) returns the cumulative size of all subsequent protocols and the payload - use if your protocol has a "length" or "payloadLength" field that you need to fill-in
isProtocolFramePayloadValueVariable() returns a boolean indicating if the protocols that appear as payload to this protocol have varying fields; if your protocol has some fields dependent on the payload, you can use this function to determine if your protocol may need to vary its fields accordingly
isProtocolFramePayloadSizeVariable() returns a boolean indicating if the protocols that appear as payload to this protocol have a varying size; if your protocol has some fields dependent on the payload size, you can use this function to determine if your protocol may need to vary its fields accordingly
protocolFramePayloadVariableCount() returns the minimum number of frames required by the payload protocols to vary their fields; if your protocol has some fields dependent on the payload, you can use this function to determine your protocol's protocolFrameVariableCount
protocolFrameHeaderCksum(index, cksumType) returns the cumulative checksum of all protocol headers that appear before your protocol - useful for protocols like TCP/UDP which need a Pseudo-Ip Checksum of the preceding IP protocol; if cksumType is not specified, it defaults to CksumIp
protocolFramePayloadCksum(index, cksumType) returns the cumulative checksum of all protocol headers and payloads that appear after your protocol- use if your protocol has a "checksum" field that needs the checksum of the payload also; if cksumType is not specified, it defaults to CksumIp

The Protocol object

The Protocol object (different from protocol - note the change in case) is a read-only object providing some convenient constants as properties -

"Protocol" object
=================
Properties:
    - ProtocolIdLlc
    - ProtocolIdEth
    - ProtocolIdIp
    - ProtocolIdTcpUdp

    - CksumIp
    - CksumIpPseudo
    - CksumTcpUdp

Examples

Here are some example scripts. You can search in the forum archives for more.

ICMP

Here`s an example implementing ICMP. Note that ICMP is already available in Ostinato - this is just an example if we had to implement ICMP as a user-script.

protocol.name = 'ICMP'

protocol.protocolFrameSize = function() {
    return 8;
}

protocol.protocolFrameValue = function(index) {
    var type = 8; // Echo Request
    var code = 0;
    var id  = 0x9127; // example value
    var seq = 0x1234; // example value
    var sum = (type << 8 | code) + id + seq + (0xFFFF & ~protocol.protocolFramePayloadCksum());

    var pfv = new Array(8);

    pfv[0] = type;
    pfv[1] = code;

    while(sum>>16)
        sum = (sum & 0xFFFF) + (sum >> 16);

    sum = ~sum;
    pfv[2] = sum >> 8;
    pfv[3] = sum & 0xFF;

    pfv[4] = id >> 8;
    pfv[5] = id & 0xFF;

    pfv[6] = seq >> 8;
    pfv[7] = seq & 0xFF;

    return pfv;
}

protocol.protocolId = function(type) {
    if (type == Protocol.ProtocolIdIp)
        return 0x1;
    return 0;
}

IPv6

Here`s a slightly more involved example implementing IPv6 showing usage of more built-in functions. Note that IPv6 is already available in Ostinato - this is just an example if we had to implement IPv6 as a user-script.

protocol.name = "IPv6"

protocol.protocolFrameSize = function() {
    return 40;
}

protocol.protocolId = function(id_type)
{
    if (id_type == Protocol.ProtocolIdEth)
        return 0x86dd;

    if (id_type == Protocol.ProtocolIdIp)
        return 0x29;

    return 0;
}


protocol.protocolFrameValue = function(index)
{
    var len;
    var pfv = new Array(40);

    // Init all bytes to 0
    for (var i = 0; i < pfv.length; i++) {
        pfv[i] = 0;
    }

    // ip version = 6
    pfv[0] = 0x60;

    // payload length
    len = protocol.protocolFramePayloadSize(index);
    pfv[4] = len >> 8;
    pfv[5] = len & 0xFF;

    // Fill-in other fields as required

    // NextHeader
    pfv[6] = protocol.payloadProtocolId(Protocol.ProtocolIdIp);
    pfv[7] = 64; // HopLimit

    // Source IP (::1)
    pfv[23] = 0x1;

    // Destination IP (::2)
    pfv[39] = 0x2;

    return pfv;
}

// We need to provide protocolFrameCksum for CksumIpPseudo
// so that TCP/UDP can correctly calculate their cksums
// Do the cksum only over the IP addresses, the infra will
// add the cksum of payload length and protocol id to it
protocol.protocolFrameCksum = function(index, type, flags)
{
    var sum = 0;

    if (type == Protocol.CksumIpPseudo) {
        sum = 0x0001 + 0x0002
    }
    return ~sum & 0xFFFF;
}

Here are some more examples of Ostinato user scripts -

FAQ

What is the 'index' input param?

Some of the methods take an index param. index is incremented by 1 for every packet at runtime. In earlier versions of Ostinato you would use it to vary your protocol size/contents at runtime. With the Variable Fields feature this is no longer required but is still available for use - if required.

However, if you need to call a built-in method which takes the index argument, you must specify index as a param to your method and pass it to the built-in method.

Back to top