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 -
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.