The TRex RPC Server =================== :Author: Itay Marom, Dan Klein :email: trex-dev@cisco.com :revnumber: 1.1 :quotes.++: :numbered: :web_server_url: http://trex-tgn.cisco.com/trex :local_web_server_url: csi-wiki-01:8181/trex :toclevels: 4 == Change log [options="header",cols="^1,^h,3a"] |================= | Version | name | meaning | 1.00 | Itay Marom (imarom) | - first version | 1.01 | Dan Klein (danklei) | - added usage examples using Python code as Higher-level usage - added logic and explanation behind VM commands | 1.1 | Dan Klein (danklei) | - Fixed some consistency issues - added RPC interaction examples appendix | 1.2 | Hanoch Haim (hhaim) | - add tuple generator command | 1.3 | Hanoch Haim (hhaim) | - update VM instructions | 1.4 | Hanoch Haim (hhaim) | - add random trim instruction | 1.5 | Hanoch Haim (hhaim) | - add more instructions (v1.92) |================= == RPC Support On TRex TRex implements a RPC protocol in order to config, view and in general execute remote calls on TRex In this document we will provide information on how a client can implement the protocol used to communicate with TRex In general, we will describe the following: * *Transport Layer* - The transport layer used to communicate with TRex server * *RPC Reprensentation Protocol* - The format in which remote procedures are carried === Transport Layer TRex server transport layer is implemented using ZMQ. The default configuration is TCP on port 5555, however this is configurable. {zwsp} + The communication model is based on the request-reply ZMQ model: http://zguide.zeromq.org/page:all#Ask-and-Ye-Shall-Receive {zwsp} + for more on ZMQ and implementation please refer to: {zwsp} + http://zeromq.org/intro:read-the-manual === RPC Reprensentation Protocol The RPC reprensentation protocol is JSON RPC v2.0. Every request and response will be encoded in a JSON RPC v2.0 format. {zwsp} + For more info on JSON RPC v2.0 spec please refer to: {zwsp} + http://www.jsonrpc.org/specification {zwsp} + Later on in the document we will describe all the supported commands. === TRex RPC Mock Server Before we get into the commands, it's worth mentioning that TRex has a mock RPC server designed to allow playing around with the server in order to understand the response and perform adjustments to the request. TRex also provides a Python based console that can connect to the server (mock or real) and send various commands to the server. ==== Building The Mock Server Building the mock server is performed like this: [source,bash] ---- trex-core/linux> ./b configure trex-core/linux> ./b --target=mock-rpc-server-64-debug ---- ==== Running The Mock Server Launching the mock server is performed like this: [source,bash] ---- trex-core/scripts> ./mock-rpc-server-64-debug -= Starting RPC Server Mock =- Listening on tcp://localhost:5050 [ZMQ] Setting Server To Full Verbose Server Started ---- ==== Using The TRex Console To Interact When the mock server is up, you can already send commands to the server. {zwsp} + {zwsp} + Let's demonstrate the operation with the Python based TRex console: {zwsp} + [source,bash] ---- trex-core/scripts> ./trex-console Connecting To RPC Server On tcp://localhost:5050 [SUCCESS] -=TRex Console V1.0=- Type 'help' or '?' for supported actions TRex > ---- As we will see later on, a basic RPC command supported by the server is 'ping'. {zwsp} + Let's issue a ping command to the server and see what happens on both sides: {zwsp} + {zwsp} + On the 'client' side: [source,bash] ---- TRex > verbose on verbose set to on TRex > ping -> Pinging RPC server [verbose] Sending Request To Server: { "id": "l0tog11a", "jsonrpc": "2.0", "method": "ping", "params": null } [verbose] Server Response: { "id": "l0tog11a", "jsonrpc": "2.0", "result": {} } [SUCCESS] ---- On the 'server' side: [source,bash] ---- trex-core/scripts> ./mock-rpc-server-64-debug -= Starting RPC Server Mock =- Listening on tcp://localhost:5050 [ZMQ] Setting Server To Full Verbose Server Started [verbose][req resp] Server Received: { "id" : "maa5a3g1", "jsonrpc" : "2.0", "method" : "ping", "params" : null } [verbose][req resp] Server Replied: { "id" : "maa5a3g1", "jsonrpc" : "2.0", "result" : {} } ---- == RPC Server Component Position Illustration The following diagram illustres the RPC server component's place: image::images/rpc_server_big_picture.png[title="RPC Server Position",align="left",width=800, link="images/rpc_server_big_picture.png"] == RPC Server Port State Machine Any port on the server can be in numbered of states, each state provides other subset of the commands that are allowed to be executed. We define the following possible states: * *unowned* - The specific port is either unowned or another user is owning the port * *owned* - The specific port has been acquired by the client * *active* - The specific port is in the middle of injecting traffic - currently active Each port command will specify on which states it is possible to execute it. For port related commands valid only on 'owned' or 'active', a field called ''handler'' 'MUST' be passed along with the rest of the parameters. This will identify the connection: image::images/rpc_states.png[title="Port States",align="left",width=150, link="images/rpc_states.png"] == RPC Commands The following RPC commands are supported === Ping * *Name* - 'ping' * *Valid States* - 'not relevant' * *Description* - Pings the TRex server * *Paramters* - None * *Result* ['object'] - {} Example: [source,bash] ---- 'Request': { "jsonrpc": "2.0", "id": 1, "method": "ping", "params": null } 'Response': { "jsonrpc" : "2.0", "id" : 1, "result" : {} } ---- === Get Server Supported Commands * *Name* - 'get_supported_cmds' * *Valid States* - 'not relevant' * *Description* - Queries the server for all the supported commands * *Paramters* - None * *Result* ['array'] - A list of all the supported commands by the server Example: [source,bash] ---- 'Request': { "jsonrpc": "2.0", "id": 1, "method": "get_supported_cmds", "params": null } 'Response': { "jsonrpc": "2.0", "id": 1, "result": [ "remove_all_streams", "remove_stream", "add_stream", "get_reg_cmds", "ping", "test_sub", "get_version", "test_add" ] } ---- === Get Version * *Name* - 'get_version' * *Valid States* - 'not relevant' * *Description* - Queries the server for version information * *Paramters* - None * *Result* ['object'] - See table below .Object type 'return values for get_version' [options="header",cols="1,1,3"] |================= | Field | Type | Description | version | string | TRex version | build_date | string | build date | build_time | string | build time | built_by | string | who built this version |================= [source,bash] ---- 'Request': { "id": "wapkk8m6", "jsonrpc": "2.0", "method": "get_version", "params": null } 'Response': { "id": "wapkk8m6", "jsonrpc": "2.0", "result": { "build_date": "Sep 16 2015", "build_time": "12:33:01", "built_by": "imarom", "version": "v0.0" } } ---- === Get System Info * *Name* - 'get_system_info' * *Description* - Queries the server for system properties * *Paramters* - None * *Result* ['object'] - See table below .return value: 'get_system_info' [options="header",cols="1,1,3"] |================= | Field | Type | Description | dp_core_count | int | DP core count | core_type | string | DP core type | hostname | string | machine host name | uptime | string | uptime of the server | port_count | int | number of ports on the machine | ports | array | arary of object ''port'' - see below |================= .return value: 'get_system_info'.'port' [options="header",cols="1,1,3"] |================= | Field | Type | Description | driver | string | driver type | index | int | port index | speed | int | speed of the port (1, 10, 40, 100) |================= [source,bash] ---- 'Request': { "id": "zweuldlh", "jsonrpc": "2.0", "method": "get_system_info", "params": null } 'Response': { "id": "zweuldlh", "jsonrpc": "2.0", "result": { "core_type": "Intel(R) Xeon(R) CPU E5-2650 0 @ 2.00GHz", "dp_core_count": 1, "hostname": "csi-kiwi-03.cisco.com", "port_count": 4, "ports": [ { "driver": "rte_ixgbe_pmd", "index": 0, "speed": 10, }, { "driver": "rte_ixgbe_pmd", "index": 1, "speed": 10, }, { "driver": "rte_ixgbe_pmd", "index": 2, "speed": 10, }, { "driver": "rte_ixgbe_pmd", "index": 3, "speed": 10, } ] } } ---- === Get Port Status * *Name* - 'get_port_status' * *Valid States* - 'all' * *Description* - Queries the server for status * *Paramters* - ** *port_id* ['int'] - port id to query for owner * *Result* ['object'] - see below [source,bash] ---- 'Request': { "id": "pbxny90u", "jsonrpc": "2.0", "method": "get_port_status", "params": { "port_id": 2 } } 'Response': { "id": "pbxny90u", "jsonrpc": "2.0", "result": { "owner": "", "state": "STREAMS" } } ---- .return value: 'get_port_status' [options="header",cols="1,1,3"] |================= | Field | Type | Description | owner | string | name of current owner (or "" if none) | state | string | state of port (DOWN, IDLE, STREAMS, TX, PAUSE) |================= === Acquire * *Name* - 'Acquire' * *Valid States* - 'all' * *Description* - Takes ownership over the port * *Paramters* - ** *port_id* ['int'] - port id to take ownership ** *user* ['string'] - User name aquiring the system ** *force* ['boolean'] - force action even if another user is holding the port * *Result* ['string'] - handler for future sessions [source,bash] ---- 'Request': { "id": "b1tr56yz", "jsonrpc": "2.0", "method": "Acquire", "params": { "user": "itay" "port_id": 1 "force": false, } } 'Response': { "id": "b1tr56yz", "jsonrpc": "2.0", "result": "AQokC3ZA" } ---- === Release * *Name* - 'release' * *Valid States* - 'owned' * *Description* - Release owernship over the device * *Paramters* - ** *handler* ['string'] - unique connection handler ** *port_id* ['int'] - port id to release * *Result* ['object'] - {} [source,bash] ---- 'Request': { "id": "m785dxwd", "jsonrpc": "2.0", "method": "release", "params": { "handler": "37JncCHr" "port_id": 1 } } 'Response': { "id": "m785dxwd", "jsonrpc": "2.0", "result": {} } ---- === Add Stream * *Name* - 'add_stream' * *Valid States* - 'owned' * *Description* - Adds a stream to a port * *Paramters* ** *handler* ['string'] - unique connection handler ** *port_id* ['int'] - port id associated with this stream ** *stream_id* ['int'] - stream id associated with the stream object ** *stream* - object of type xref:stream_obj['stream'] * *Result* ['object'] - {} ==== Object type 'stream' anchor:stream_obj[] Add_stream gets a single parameter of type object. The format of that object is as follows: .Object type 'stream' [options="header",cols="1,1,3"] |================= | Field | Type | Description | enabled | boolean | is this stream enabled | self_start | boolean | is this stream triggered by starting injection or triggered by another stream | action_count | uint16_t | In case it is bigger than zero and next stream is not -1 (set) the number of goto will be limited to this number. Maximum value is 65K. default is zero. Zero means - not limit. | random_seed | uint32_t | For creating reproducible tests with random number, each stream can get a seed. this field is optional. In case of zero the seed value won't be taken | flags | uint16_t | bit 0 (LSB) : 1 - take the src MAC from the packet instead of config file. bit 1-2 (LSB) how to set the dest MAC ( stCFG_FILE = 0, stPKT = 1,stARP = 2 ) | isg | double | ['usec'] inter stream gap - delay time in usec until the stream is started | next_stream_id | int | next stream to start after this stream. -1 means stop after this stream | packet | object | object of type xref:packet_obj['packet'] | mode | object | object of type xref:mode_obj['mode'] | vm | object | array of objects of type xref:vm_obj['vm'] | rx_stats | object | object of type xref:rx_stats_obj['rx_stats'] |================= ===== Object type 'packet' anchor:packet_obj[] packet contains binary and meta data .Object type 'packet' [options="header",cols="1,1,3"] |================= | Field | Type | Description | binary | byte array | binary dump of the packet to be used in the stream as array of bytes | meta | string | meta data object. opaque to the RPC server. will be passed on queries |================= ===== Object type 'mode' anchor:mode_obj[] mode object can be 'one' of the following objects: .Object type 'rate' [options="header",cols="1,1,3"] |================= | Field | Type | Description | type | string | [''pps'',''bps_L1'',''bps_L2'',''percentage'' | value | double | rate |================= .Object type 'mode - continuous' [options="header",cols="1,1,3"] |================= | Field | Type | Description | type | string | ''continuous'' | rate | object | rate object |================= .Object type 'mode - single_burst' [options="header",cols="1,1,3"] |================= | Field | Type | Description | type | string | ''single_burst'' | rate | object | rate object | total pkts | int | total packets in the burst |================= .Object type 'mode - multi_burst' [options="header",cols="1,1,3"] |================= | Field | Type | Description | type | string | ''multi_burst'' | rate | object | rate object | pkts_per_burst | int | packets in a single burst | ibg | double | ['usec'] inter burst gap. delay between bursts in usec | count | int | number of bursts. ''0'' means loop forever, ''1'' will fall back to single burst |================= ===== Object type 'vm' anchor:vm_obj[] an Object that include instructions array and properties of the field engine program .Object type 'packet' [options="header",cols="1,1,3"] |================= | Field | Type | Description | Instructions | array | list of instructional objects | split_by_var | string | name of the field by which to split into threads | Restart | boolean | restart the field engine program when stream moving from inactive->active |================= Array of VM instruction objects to be used with this stream Any element in the array can be one of the following object types: .Object type 'vm - fix_checksum_ipv4' [options="header",cols="1,1,3"] |================= | Field | Type | Description | type | string | ''fix_checksum_ipv4'' | pkt_offset | uint16 | offset of the field to fix |================= .Object type 'vm - flow_var' [options="header",cols="1,1,3"] |================= | Field | Type | Description | type | string | ''flow_var''' | name | string | flow var name - this should be a unique identifier | size | [1,2,4,8] | size of the flow var in bytes | op | ['inc', 'dec', 'random'] | operation type to perform on the field | init_value | uint64_t as string | init value for the field | min_value | uint64_t as string | minimum value for the field | max_value | uint64_t as string | maximum value for the field | step | uint64_t as string | step, how much to inc or dec. 1 is the default (in case of 'random' this field is not used) |================= .Object type 'vm - write_flow_var' [options="header",cols="1,1,3"] |================= | Field | Type | Description | type | string | ''write_flow_var'' | name | string | flow var name to write | pkt_offset | uint16 | offset at the packet to perform the write | add_value | int | delta to add to the field prior to writing - can be negative | is_big_endian | boolean | should write as big endian or little |================= .Object type 'vm - trim_pkt_size' [options="header",cols="1,1,3"] |================= | Field | Type | Description | type | string | ''trim_pkt_size'' | name | string | flow var name to take the new trim packet size from. The var size should be valid packet size and less than template packet size. see `stl/udp_rand_size.yaml` for an example |================= .Object type 'vm - tuple_flow_var' [options="header",cols="1,1,3"] |================= | Field | Type | Description | type | string | ''tuple_flow_var''' | name | string | tuple generator name - this should be a unique identifier name.ip and name.port will be added | ip_min | uint32_t as string | ipv4 min ip as uint32_t e.g. 10.0.0.1 | ip_max | uint32_t as string | ipv4 max ip as uint32_t e.g. 10.0.1.255 | port_min | uint16_t as string | ipv4 min port as uint16_t e.g. 1025 | port_max | uint16_t as string | ipv4 max port as uint16_t e.g. 65000 | limit_flows | uint32_t as string | the number of flows. 0 means we will use all the ip/port min-max range | flags | uint16_t as string | 1 - unlimited number of flows. in case the first bit is enabled port_min and port_max is ignored and the maximum number of flows will be generated on those ips |================= an example of tuple_flow_var variable [source,bash] ---- ip_min = 10.0.0.1 ip_max = 10.0.0.5 port_min = 1025 port_max = 1028 limit_flows = 10 ---- .Results [options="header",cols="1,1,3"] |================= | IP | PORT | FLOW | 10.0.0.1 | 1025 | 1 | 10.0.0.2 | 1025 | 2 | 10.0.0.3 | 1025 | 3 | 10.0.0.4 | 1025 | 4 | 10.0.0.5 | 1025 | 5 | 10.0.0.1 | 1026 | 6 << the port is inc here | 10.0.0.2 | 1026 | 7 | 10.0.0.3 | 1026 | 8 | 10.0.0.4 | 1026 | 9 | 10.0.0.5 | 1026 | 10 | 10.0.0.1 | 1025 | 1 << back to the first flow |================= The variable name.port and name.ip could be written to any offset in the packet (usualy to src_ip and src_port as client) .Object type 'vm - write_mask_flow_var' [options="header",cols="1,1,3"] |================= | Field | Type | Description | type | string | ''write_mask_flow_var''' | name | string | flow variable name | pkt_offset | uint16_t as string | offset at the packet to perform the write | add_value | int32_t as string | delta to add to the field prior to writing - can be negative | pkt_cast_size | uint_t as string | size in bytes only 1,2,4 are valid | mask | uint32_t as string | 1 means care e.g. 0xff will write to only 8 LSB bits | shift | int8_t as string | Positive will shift left (multiply by x2) negative will shift right (divided by 2) e.g. 1 will multiply by 2 | is_big_endian | boolean | should write as big endian or little |================= .Pseudocode [source,bash] ---- uint32_t val=(cast_to_size)rd_from_varible("name"); # read flow-var val+=m_add_value; # add value if (m_shift>0) { # shift val=val<>(-m_shift); } } pkt_val=rd_from_pkt(pkt_offset) # RMW pkt_val = (pkt_val & ~m_mask) | (val & m_mask) wr_to_pkt(pkt_offset,pkt_val) ---- an example of tuple_flow_var variable [source,bash] ---- name = "a" (varible 2 byte start 1-10 inc ) pkt_cast_size = 1 ( cast to uint8_t ) add_value = 0 mask = 0xf0 shift = 4 is_big_endian =1 ---- .Results [options="header",cols="1,1,3"] |================= | var "a" | PKT- before write | PKT post write | 1 | 0x03 | 0x13 | 2 | 0x03 | 0x23 | 3 | 0x03 | 0x33 | 4 | 0x03 | 0x43 | 5 | 0x03 | 0x53 |================= The use cases of this instruction is to write to a bit field (valn/mpls) TIP: For more information and examples on VM objects please refer to: link:vm_doc.html[VM examples] ===== Object type 'rx_stats' anchor:rx_stats_obj[] Describes rx stats for the stream {zwsp} + IMPORTANT: In case rx_stats is enabled, meta data will be written in the end of the packet. please also consider the following constraints: ==== Constrains * *performance* - this will have performance impact as rx packets will be examined * *override* - up to 10 bytes at the end of the packet will be overidden by the meta data required ==== The bytes needed for activating 'rx_stats': * *stream_id* consumes 2 bytes * *seq_enabled* consumes 4 bytes * *latency_enabled* consumes 4 bytes so if no seq or latency are enabled 2 bytes will be used. if seq or latency alone are enabled, 6 bytes will be used. if both are enabled then 10 bytes will be used. .Object type 'rx_stats' [options="header",cols="1,1,3"] |================= | Field | Type | Description | enabled | boolean | is rx_stats enabled for this stream | stream_id | int | stream_id for which to collect rx_stats. + This could be stream_id different from the stream object which contains the rx_stats object. | seq_enabled | boolean | should write 32 bit sequence | latency_enabled | boolean | should write 32 bit latency |================= [source,bash] ---- 'Request': { "id": 1, "jsonrpc": "2.0", "method": "add_stream", "params": { "handler": "37JncCHr", "port_id": 1, "stream_id": 502 "stream": { "enabled": true, "isg": 4.3, "mode": { "rate": { "type": "pps", "value": 10 }, "total_pkts": 5000, "type": "single_burst" }, "next_stream_id": -1, "packet": { "binary": [ 4, 1, 255 ], "meta": "" }, "rx_stats": { "enabled": false }, "self_start": true, } } } 'Response': { "id": 1, "jsonrpc": "2.0", "result": {} } ---- This request-reply sequence demonstrate a method in which rx_stats are diabled. In case rx_stats feature is enabled, rx_object **must include** all rx_stats object fields as described above. === Remove Stream * *Name* - 'remove_stream' * *Valid States* - 'owned' * *Description* - Removes a stream from a port * *Paramters* ** *handler* ['string'] - unique connection handler ** *port_id* ['int'] - port assosicated with the stream. ** *stream_id* ['int'] - stream to remove * *Result* ['object'] - {} [source,bash] ---- 'Request': { "id": 1 "jsonrpc": "2.0", "method": "remove_stream", "params": { "handler": "37JncCHr", "port_id": 1, "stream_id": 502 } } 'Response': { "id": 1 "jsonrpc": "2.0", "result": {} } ---- === Get Stream ID List * *Name* - 'get_stream_list' * *Valid States* - 'unowned', 'owned', 'active' * *Description* - fetch all the assoicated streams for a port * *Paramters* ** *handler* ['string'] - unique connection handler ** *port_id* ['int'] - port to query for registered streams * *Result* ['array'] - array of 'stream_id' [source,bash] ---- 'Request': { "id": 1, "jsonrpc": "2.0", "method": "get_stream_list", "params": { "handler": "37JncCHr", "port_id": 1 } } 'Response': { "id": 1, "jsonrpc": "2.0", "result": [ 502, 18 ] } ---- === Get Stream * *Name* - 'get_stream' * *Valid States* - 'unowned', 'owned', 'active' * *Description* - get a specific stream object * *Paramters* ** *handler* ['string'] - unique connection handler ** *port_id* ['int'] - port for the associated stream ** *stream_id* ['int'] - the requested stream id * *Result* ['object'] - object xref:stream_obj['stream'] [source,bash] ---- 'Request': { "id": 1, "jsonrpc": "2.0", "method": "get_stream", "params": { "handler": "37JncCHr", "port_id": 1, "stream_id": 7 } } 'Response': { "id": 1, "jsonrpc": "2.0", "result": { "stream": { "enabled": true, "isg": 4.3, "mode": { "pps": 3, "type": "continuous" }, "next_stream_id": -1, "packet": { "binary": [ 4, 1, 255 ], "meta": "" }, "self_start": true } } } ---- === Remove All Streams * *Name* - 'remove_all_streams' * *Valid States* - 'owned' * *Description* - remove all streams from a port * *Paramters* ** *handler* ['string'] - unique connection handler ** *port_id* ['int'] - port for the associated stream * *Result* ['object'] - {} [source,bash] ---- 'Request': { "id": 1, "jsonrpc": "2.0", "method": "remove_all_streams", "params": { "handler": "37JncCHr", "port_id": 2 } } 'Response': { "id": 1, "jsonrpc": "2.0", "result": {} } ---- === Start Traffic * *Name* - 'start_traffic' * *Valid States* - 'owned' * *Description* - Starts the traffic on a specific port. if traffic has already started an error will be returned * *Paramters* ** *handler* ['string'] - unique connection handler ** *port_id* ['int'] - port id on which to start traffic * *Result* ['object'] - {} [source,bash] ---- 'Request': { "id": "b3llt8hs", "jsonrpc": "2.0", "method": "start_traffic", "params": { "handler": "37JncCHr", "port_id": 3 } 'Response': { "id": "b3llt8hs", "jsonrpc": "2.0", "result": {} } ---- === Stop Traffic * *Name* - 'stop_traffic' * *Valid States* - 'active' * *Description* - Stops the traffic on a specific port. if the port has already started nothing will happen * *Paramters* ** *handler* ['string'] - unique connection handler ** *port_id* ['int'] - port id on which to stop traffic * *Result* ['object'] - {} [source,bash] ---- 'Request': { "id": "h2fyhni7", "jsonrpc": "2.0", "method": "stop_traffic", "params": { "handler": "37JncCHr", "port_id": 3 } } 'Response': { "id": "h2fyhni7", "jsonrpc": "2.0", "result": {} } ---- === Get Global Stats * *Name* - 'get_global_stats' * *Valid States* - 'unowned', 'owned', 'active' * *Description* - Get machine global stats * *Paramters* - None * *Result* ['object'] - See Below .Return value of 'get_global_stats' [options="header",cols="1,1,3"] |================= | Field | Type | Description | state | string | server state: can be 'unowned', 'owned' or 'active' | cpu_util | double | DP CPU util. in % | tx_bps | double | total TX bits per second | rx_bps | double | total RX bits per second | tx_pps | double | total TX packets per second | rx_pps | double | total RX packets per second | total_tx_pkts | int | total TX packets | total_rx_pkts | int | total RX packets | total_tx_bytes | int | total TX bytes | total_rx_bytes | int | total RX bytes | tx_rx_error | int | total Tx/Rx errors |================= === Get Port Stats * *Name* - 'get_port_stats' * *Valid States* - 'unowned', 'owned', 'active' * *Description* - Get port stats * *Paramters* ** *port_id* [int] - The port id for query * *Result* ['object'] - See Below .Return value of 'get_port_stats' [options="header",cols="1,1,3"] |================= | Field | Type | Description | status | string | 'down', 'idle' or 'transmitting' | tx_bps | double | total TX bits per second | rx_bps | double | total RX bits per second | tx_pps | double | total TX packets per second | rx_pps | double | total RX packets per second | total_tx_pkts | int | total TX packets | total_rx_pkts | int | total RX packets | total_rx_bytes | int | total TX bytes | total_tx_bytes | int | total RX bytes | tx_rx_error | int | total Tx/Rx errors |================= === Get Stream Stats * *Name* - 'get_steram_stats' * *Valid States* - 'unowned', 'owned', 'active' * *Description* - Get port stats * *Paramters* ** *port_id* [int] - The port id for query ** *stream_id* [int] - The stream id for query * *Result* ['object'] - See Below .Return value of 'get_stream_stats' [options="header",cols="1,1,3"] |================= | Field | Type | Description | tx_bps | double | total TX bits per second | tx_pps | double | total TX packets per second | total_tx_pkts | int | total TX packets | total_tx_bytes | int | total TX bytes | rx_bps | double | total RX bits per second (if 'rx_stats' enabled) | rx_pps | double | total RX packets per second (if 'rx_stats' enabled) | total_rx_pkts | int | total RX packets (if 'rx_stats' enabled) | total_rx_bytes | int | total RX bytes (if 'rx_stats' enabled) | latency | array | array of 2 ordered elements average, maximum (if 'rx_stats' enabled) |================= == Typical Transactions Examples the following examples represents common scenarios. commands in [...] represents 'meta commands' and not real RPC commands such as 'repeat', 'wait' and etc. === Init/Boot This sequence represents a client implementing the protocol taking ownership over the server and preparing to perform work ==== Commands Flow * *ping* - Ping the server to verify the server is up * *get_owner* - if owner is not me or 'none' prompt to the user if he wants to force it * *acquire* - Ask or force for exclusive control over the server. save the 'handler' given for future commands * *get_version* - Verify the server is compatible with the GUI * *get_system_info* - Get the installed ports and cores * *get_stream_list* - for every port, get the list and sync the GUI * *get_stream* - for every stream in a port list, get the stream info and sync the GUI === Simple Traffic With Adding / Editing Streams describes a simple scenario where a user wants to add or edit one or more streams to one or more ports ==== Commands Flow * *[init]* - perform the init procedure from above * *[GUI add/edit streams]* - GUI provides the user a way to add or edit streams and sync them * *remove_all_streams* ['optional'] - remove all previous streams to start from scratch * *add_stream* - configure a specific port with a stream. * *['repeat previous']* - 'repeat' the above for how many ports and streams desired * *get_stream_list* ['optional'] - sanity - verify the server is synced with the GUI * *start_traffic* - start traffic on the specific port / all the ports * *get_global_stats* ['optional'] - make sure the machine is transmiting traffic * *['perfrom test']* - perform the required test * *stop_traffic* - when done, stop the traffic on the specific port / all the ports * *get_global_stats* ['optional'] - make sure the machine has stopped === Logout Describes the log off from the machine ==== Commands Flow * *stop_traffic* ['optional'] - if traffic has started - stop it * *get_global_stats* ['optional'] - make sure the machine has stopped * *remove_all_streams* ['optional'] - if you want to clear all the previous streams - use this * *release* - release the ownership over the device == Higher Level implementation examples The following examples represents common scenarios implemented by a higher layer, which uses the API described above. The examples are written in Python, however similar examples can be shown in any programming language. === CTRexPktBuilder class description `CTRexPktBuilder` is a Python module designed to provide a progammer API for dynamic packet building. Since the packet is built to be used by TRex, a `CTRexVM` subclass has been created to describe how TRex should use the described packet in its transmission. While the entire `CTRexPktBuilder` class (which is initialized by specifying the total length of the packet) responsible to both building the packet layer by layer, the `CTRexVM` class is responsible for controlling the ranging of the values as desribed in the <>, and other attributes being used by TRex data-plane once the server receives its streams. === Creating an example packet The following conde snippet describes how an ICMP Echo packet is built. [source, python, numbered] ---- from packet_builder import CTRexPktBuilder import dpkt pkt_bld = CTRexPktBuilder() # <1> pkt_bld.add_pkt_layer("l2", dpkt.ethernet.Ethernet()) # set Ethernet layer attributes pkt_bld.set_eth_layer_addr("l2", "src", "00:15:17:a7:75:a3") pkt_bld.set_eth_layer_addr("l2", "dst", "e0:5f:b9:69:e9:22") pkt_bld.set_layer_attr("l2", "type", dpkt.ethernet.ETH_TYPE_IP) # set IP layer attributes pkt_bld.add_pkt_layer("l3_ip", dpkt.ip.IP()) pkt_bld.set_ip_layer_addr("l3_ip", "src", "21.0.0.2") pkt_bld.set_ip_layer_addr("l3_ip", "dst", "22.0.0.12") pkt_bld.set_layer_attr("l3_ip", "p", dpkt.ip.IP_PROTO_ICMP) # set ICMP layer attributes pkt_bld.add_pkt_layer("icmp", dpkt.icmp.ICMP()) pkt_bld.set_layer_attr("icmp", "type", dpkt.icmp.ICMP_ECHO) # set Echo(ICMP) layer attributes pkt_bld.add_pkt_layer("icmp_echo", dpkt.icmp.ICMP.Echo()) pkt_bld.set_layer_attr("icmp_echo", "id", 24528) pkt_bld.set_layer_attr("icmp_echo", "seq", 11482) pkt_bld.set_pkt_payload('hello world') # finally, set IP header len with relation to payload data pkt_bld.set_layer_attr("l3_ip", "len", len(pkt_bld.get_layer('l3_ip'))) ---- <1> Initialize the packet builder instance. This example created a packet without any ranging to it, so in this case TRex is expected to reply the same packet over and over without any changes to it. When adding sending this packet as part of the <<_add_stream, Add Stream>> command, the packet content specified under `packet` would look for the created ICMP packet like this: [source, python] ---- >>> print pkt_bld.dump_pkt() [224, 95, 185, 105, 233, 34, 0, 21, 23, 167, 117, 163, 8, 0, 69, 0, 0, 39, 0, 0, 0, 0, 64, 1, 79, 201, 21, 0, 0, 2, 22, 0, 0, 12, 8, 0, 217, 134, 95, 208, 44, 218, 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100] ---- Each of the array items representing a byte data-representation, hence ranging from 0 to 255. === Create a packet with single ranging instruction The following example creates an HTTP GET packet, hence layering Ethernet/IP/TCP/HTTP. [source, python, numbered] ---- from packet_builder import CTRexPktBuilder import dpkt pkt_bld = CTRexPktBuilder() pkt_bld.add_pkt_layer("l2", dpkt.ethernet.Ethernet()) # set Ethernet layer attributes pkt_bld.set_eth_layer_addr("l2", "src", "00:15:17:a7:75:a3") pkt_bld.set_eth_layer_addr("l2", "dst", "e0:5f:b9:69:e9:22") pkt_bld.set_layer_attr("l2", "type", dpkt.ethernet.ETH_TYPE_IP) # set IP layer attributes pkt_bld.add_pkt_layer("l3_ip", dpkt.ip.IP()) pkt_bld.set_ip_layer_addr("l3_ip", "src", "21.0.0.2") pkt_bld.set_ip_layer_addr("l3_ip", "dst", "22.0.0.12") pkt_bld.set_layer_attr("l3_ip", "p", dpkt.ip.IP_PROTO_TCP) # set TCP layer attributes pkt_bld.add_pkt_layer("l4_tcp", dpkt.tcp.TCP()) pkt_bld.set_layer_attr("l4_tcp", "sport", 13311) pkt_bld.set_layer_attr("l4_tcp", "dport", 80) pkt_bld.set_layer_attr("l4_tcp", "flags", 0) pkt_bld.set_layer_attr("l4_tcp", "win", 32768) pkt_bld.set_layer_attr("l4_tcp", "seq", 0) # set packet payload, for example HTTP GET request pkt_bld.set_pkt_payload('GET /10k_60k HTTP/1.1\r\nHost: 22.0.0.3\r\nConnection: Keep-Alive\r\nUser-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)\r\nAccept: */*\r\nAccept-Language: en-us\r\nAccept-Encoding: gzip, deflate, compress\r\n\r\n') # finally, set IP header len with relation to payload data pkt_bld.set_layer_attr("l3_ip", "len", len(pkt_bld.get_layer('l3_ip'))) ---- Now, we extened the single packet created with three VM instructions, in order to range over the source IP of the packet. [source, python, numbered] ---- pkt_bld.set_vm_ip_range(ip_layer_name="l3_ip", # <1> ip_field="src", # <2> ip_init="10.0.0.1", ip_start="10.0.0.1", ip_end="10.0.0.255", add_value=1, operation="inc") ---- <1> `l3_ip` corresponds with the layer name given to the IP layer of the packet. This helps identifying and diffrenciate in packet containing more than one IP header. <2> the name of the field on which we want to range. Now, we added ranging for source IP starting from 10.0.0.1 to 10.0.0.255. This will generate the follwing VM instructions, which will be provided under `vm` field of the <<_add_stream, add_stream>> command: [source, python] ---- >>> print pkt_bld.vm.dump(), [{'name': 'l3__src', 'ins_name': 'flow_var', 'max_value': '167772415', 'min_value': '167772161', 'init_value': '167772161', 'size': 4, 'op': 'inc'}, {'is_big_endian': False, 'pkt_offset': 26, 'type': 'write_flow_var', 'name': 'l3__src', 'add_value': 1}, {'pkt_offset': 14, 'type': 'fix_checksum_ipv4'}] ---- As we can see, three instructions has been generated for this ranging criteria: 1. `flow_var` instruction - for defining the ranging parameters. 2. `write_flow_var` instruction - for specifying where and how the modification should take place. 3. `fix_checksum_ipv4` instruction - for updated the checksum field [WARNING] The order of the instruction **does matter**. In this example, if the `fix_checksum_ipv4` instruction would have been places prior to the `write_flow_var` instruction, the generated packet would have satyed with the old checksum values. [NOTE] By default, with each change to the IP header, a `fix_checksum_ipv4` instruction is added. This can be canceled by passing `add_checksum_inst=False` in functions which ranges over an IP field. === Create a packet with multiple ranging instructions Now, we shall extend our ranging and add another field to range on, this time we'll pick the TOS field of the IP header. So, we'll add the following code snippet **ontop of the ranging method we already applied**: [source, python, numbered] ---- pkt_bld.set_vm_custom_range(layer_name="l3_ip", hdr_field="tos", init_val="10", start_val="10", end_val="200", add_val=2, val_size=1, operation="inc") ---- So, in this case we chose to range the TOS field from 10 to 200 in steps of 2. Finally, let's see the expected JSON output of the VM instructions: [source, python] ---- >>> print pkt_bld.vm.dump() [{ 'init_value': '167772161', # <1> 'ins_name': 'flow_var', 'max_value': '167772415', 'min_value': '167772161', 'name': 'l3__src', 'op': 'inc', 'size': 4}, { 'init_value': '10', # <2> 'ins_name': 'flow_var', 'max_value': '200', 'min_value': '10', 'name': 'l3__tos', 'op': 'inc', 'size': 1}, { 'add_value': 2, # <3> 'is_big_endian': False, 'name': 'l3__tos', 'pkt_offset': 15, 'type': 'write_flow_var'}, { 'add_value': 1, # <4> 'is_big_endian': False, 'name': 'l3__src', 'pkt_offset': 26, 'type': 'write_flow_var'}, { 'pkt_offset': 14, 'type': 'fix_checksum_ipv4'} # <5> ] ---- <1> `flow_var` instruction for source IP. <2> `flow_var` instruction for TOS field <3> `write_flow_var` instruction for TOS. <4> `write_flow_var` instruction for source IP. <5> `fix_checksum_ipv4` instruction for both ranging options [NOTE] In this case only one checksum instruction has been generated, since both ranging options applies to the same IP header. :numbered!: [appendix] Interaction Examples -------------------- This appendix brings examples with data for the this RPC interaction. + <<_add_stream, add_stream>> method example ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The following example represents an interaction between the RPC client and the server's response. Simple single packet client request ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ On the following example, there's no VM instructions, rx_stats option is disabled and there's only a single packet which isn't connected to any other packet. [underline]#Client request# [source, bash] ---- { "id" : "2bqgd2r4", "jsonrpc" : "2.0", "method" : "add_stream", "params" : { "handler" : "37JncCHr", "port_id" : 1, "stream" : { "enabled" : true, "isg" : 0, "mode" : { "rate": { "type": "pps", "value": 100 }, "type" : "continuous" }, "next_stream_id" : -1, "packet" : { "binary" : [ 0, 80, 86, 128, 13, ... # more packet data 77, 79, 250, 154, 66 ], "meta" : "" }, "rx_stats" : { "enabled" : false }, "self_start" : true, "vm" : [] }, "stream_id" : 0 } } ---- [underline]#Server's response# [source, bash] ---- { "id" : "2bqgd2r4", "jsonrpc" : "2.0", "result" : {} } ---- Two linked packets with VM instructions client request ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ On the following example, a **batch request** is being issued to the server, containing two `add_stream` requests. [underline]#First request# + The first client request is similar to the previous example. + However, in this case the rx_stats object is enbaled and set to monitor ancestor's `stream_id` (which is 0 in this case). Ontop, this stream points to the next stream as the one to follow, as described under `next_stream_id` of `stream` object. [underline]#Second request# + In this stream the big difference is that it has VM instructions under the `vm` field of the `stream` object. Ontop, this stream is the last stream of the sequence, so `next_stream_id` of `stream` object is set to '-1'. [underline]#Client request# [source, bash] ---- [ { "id" : "tq49f6uj", "jsonrpc" : "2.0", "method" : "add_stream", "params" : { "handler" : "2JjzhMai", "port_id" : 3, "stream" : { "enabled" : true, "isg" : 0, "mode" : { "rate": { "type": "pps", "value": 100 }, "type" : "continuous" }, "next_stream_id" : 1, "packet" : { "binary" : [ 0, 80, 86, ... # more packet data 250, 154, 66 ], "meta" : "" }, "rx_stats" : { "enabled" : true, "latency_enabled" : false, "seq_enabled" : false, "stream_id" : 0 }, "self_start" : true, "vm" : [] }, "stream_id" : 0 } }, { "id" : "2m7i5olx", "jsonrpc" : "2.0", "method" : "add_stream", "params" : { "handler" : "2JjzhMai", "port_id" : 3, "stream" : { "enabled" : true, "isg" : 0, "mode" : { "rate": { "type": "pps", "value": 100 }, "type" : "continuous" }, "next_stream_id" : -1, "packet" : { "binary" : [ 0, 80, 86, 128, ... # more packet data 216, 148, 25 ], "meta" : "" }, "rx_stats" : { "enabled" : false }, "self_start" : false, "vm" : [ { "init_value" : "65537", "max_value" : "65551", "min_value" : "65537", "name" : "l3__src", "op" : "inc", "size" : 4, "type" : "flow_var" }, { "add_value" : 1, "is_big_endian" : false, "name" : "l3__src", "pkt_offset" : 34, "type" : "write_flow_var" } ] }, "stream_id" : 1 } } ] ---- [underline]#Server's response# [source, bash] ---- [ { "id" : "tq49f6uj", "jsonrpc" : "2.0", "result" : {} }, { "id" : "2m7i5olx", "jsonrpc" : "2.0", "result" : {} } ] ---- Another Example of tuple generator ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [source, bash] ---- - name: udp_64B stream: self_start: True packet: binary: stl/udp_64B_no_crc.pcap # pcap should not include CRC mode: type: continuous pps: 100 rx_stats: [] # program that define 1M flows with IP range 16.0.0.1-16.0.0.254 # we will create a script that do that for you # this is the low level instructions vm: [ { "type" : "tuple_flow_var", # name of the command "name" : "tuple_gen", # tuple_gen.ip tuple_gen.port can be used "ip_min" : 0x10000001, # min ip 16.0.0.1 "ip_max" : 0x100000fe, # max ip 16.0.0.254 "port_min" : 1025, # min port 1025 "port_max" : 65500, # max port 65500 "limit_flows" : 1000000, # number of flows "flags" : 0, # 1 - for unlimited }, { "type" : "write_flow_var", # command name "name" : "tuple_gen.ip", # varible to write "add_value" : 0, # no need to add value "is_big_endian" : true, # write as big edian "pkt_offset" : 26, # write tuple_gen.ip into ipv4.src_ip }, { "type" : "fix_checksum_ipv4", # fix ipv4 header checksum "pkt_offset" : 14, # offset of ipv4 header }, { "type" : "write_flow_var", # command name "name" : "tuple_gen.port", # varible to write "add_value" : 0, # no need to add value "is_big_endian" : true, # write as big edian "pkt_offset" : 34, # write tuple_gen.port into udp.src_port } ] ----