summaryrefslogtreecommitdiffstats
path: root/tools/pkt_gen/ixia/pass_fail.tcl
blob: e10b393c37ecac80d15d21451eadd7ab068d1a56 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
#!/usr/bin/env tclsh

# Copyright (c) 2014, Ixia
# Copyright (c) 2015, Intel Corporation
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

# This file is a modified version of a script generated by Ixia
# IxExplorer.

lappend auto_path [list $lib_path]

package req IxTclHal

###################################################################
########################## Configuration ##########################
###################################################################

# Verify that the IXIA chassis spec is given

set reqVars [list "host" "card" "port1" "port2"]

foreach var $reqVars {
    set var_ns [namespace which -variable "$var"]
    if { [string compare $var_ns ""] == 0 } {
        errorMsg "The '$var' variable is undefined. Did you set it?"
        return -1
    }
}

# constants

set fullHex                             "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF"
set hexToC5                             "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5"

#set payloadLookup(64)                   [string range $fullHex 0 11]
set payloadLookup(64)                   "000102030405"
set payloadLookup(128)                  "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445"
set payloadLookup(256)                  "$hexToC5"
set payloadLookup(512)                  "$fullHex$hexToC5"
set payloadLookup(1024)                 "$fullHex$fullHex$fullHex$hexToC5"

###################################################################
###################### Chassis Configuration ######################
###################################################################

if {[isUNIX]} {
    if {[ixConnectToTclServer $host]} {
        errorMsg "Error connecting to Tcl Server $host"
        return $::TCL_ERROR
    }
}

######### Chassis #########

# Now connect to the chassis
if [ixConnectToChassis $host] {
    ixPuts $::ixErrorInfo
    return 1
}

# Get the chassis ID to use in port lists
set chassis [ixGetChassisID $host]

#########  Ports #########

set portList [list [list $chassis $card $port1] \
                   [list $chassis $card $port2]]

# Clear ownership of the ports we’ll use
if [ixClearOwnership $portList force] {
    ixPuts $::ixErrorInfo
    return 1
}

# Take ownership of the ports we’ll use
if [ixTakeOwnership $portList] {
    ixPuts $::ixErrorInfo
    return 1
}

foreach portElem $portList {
    set chasNum [lindex $portElem 0]
    set cardNum [lindex $portElem 1]
    set portNum [lindex $portElem 2]

    port setFactoryDefaults $chasNum $cardNum $portNum
    port config -speed                                10000
    port config -flowControl                          true
    port config -transmitMode                         portTxModeAdvancedScheduler
    port config -receiveMode                          [expr $::portCapture|$::portRxModeWidePacketGroup]
    port config -advertise100FullDuplex               false
    port config -advertise100HalfDuplex               false
    port config -advertise10FullDuplex                false
    port config -advertise10HalfDuplex                false
    port config -portMode                             port10GigLanMode
    port config -enableTxRxSyncStatsMode              true
    port config -txRxSyncInterval                     2000
    port config -enableTransparentDynamicRateChange   true
    port config -enableDynamicMPLSMode                true
    if {[port set $chasNum $cardNum $portNum]} {
        errorMsg "Error calling port set $chasNum $cardNum $portNum"
    }

    packetGroup setDefault
    packetGroup config -numTimeBins                   1
    if {[packetGroup setRx $chasNum $cardNum $portNum]} {
        errorMsg "Error calling packetGroup setRx $chasNum $cardNum $portNum"
    }

    sfpPlus setDefault
    sfpPlus config -enableAutomaticDetect             false
    sfpPlus config -txPreTapControlValue              1
    sfpPlus config -txMainTapControlValue             63
    sfpPlus config -txPostTapControlValue             2
    sfpPlus config -rxEqualizerControlValue           0
    if {[sfpPlus set $chasNum $cardNum $portNum]} {
        errorMsg "Error calling sfpPlus set $chasNum $cardNum $portNum"
    }

    filter setDefault
    filter config -captureTriggerFrameSizeFrom        48
    filter config -captureTriggerFrameSizeTo          48
    filter config -captureFilterFrameSizeFrom         48
    filter config -captureFilterFrameSizeTo           48
    filter config -userDefinedStat1FrameSizeFrom      48
    filter config -userDefinedStat1FrameSizeTo        48
    filter config -userDefinedStat2FrameSizeFrom      48
    filter config -userDefinedStat2FrameSizeTo        48
    filter config -asyncTrigger1FrameSizeFrom         48
    filter config -asyncTrigger1FrameSizeTo           48
    filter config -asyncTrigger2FrameSizeFrom         48
    filter config -asyncTrigger2FrameSizeTo           48
    filter config -userDefinedStat1Enable             true
    filter config -userDefinedStat2Enable             true
    filter config -asyncTrigger1Enable                true
    filter config -asyncTrigger2Enable                true
    if {[filter set $chasNum $cardNum $portNum]} {
        errorMsg "Error calling filter set $chasNum $cardNum $portNum"
    }

    filterPallette setDefault
    filterPallette config -pattern1                   00
    filterPallette config -patternMask1               00
    filterPallette config -patternOffset1             20
    filterPallette config -patternOffset2             20
    if {[filterPallette set $chasNum $cardNum $portNum]} {
        errorMsg "Error calling filterPallette set $chasNum $cardNum $portNum"
    }

    capture setDefault
    capture config -sliceSize                         65536
    if {[capture set $chasNum $cardNum $portNum]} {
        errorMsg "Error calling capture set $chasNum $cardNum $portNum"
    }

    if {[interfaceTable select $chasNum $cardNum $portNum]} {
        errorMsg "Error calling interfaceTable select $chasNum $cardNum $portNum"
    }

    interfaceTable setDefault
    if {[interfaceTable set]} {
        errorMsg "Error calling interfaceTable set"
    }

    interfaceTable clearAllInterfaces
    if {[interfaceTable write]} {
        errorMsg "Error calling interfaceTable write"
    }

    ixEnablePortIntrinsicLatencyAdjustment $chasNum $cardNum $portNum true
}

ixWritePortsToHardware portList

if {[ixCheckLinkState $portList] != 0} {
    errorMsg "One or more port links are down"
}

proc sendTraffic { flowSpec trafficSpec } {
    # Send traffic from IXIA.
    #
    # Transmits traffic from Rx port (port1), and captures traffic at
    # Tx port (port2).
    #
    # Parameters:
    #   flowSpec - a dict detailing how the packet should be sent. Should be
    #     of format:
    #         {type, numpkts, duration, framerate}
    #   trafficSpec - a dict describing the packet to be sent. Should be
    #     of format:
    #         { l2, vlan, l3}
    #     where each item is in turn a dict detailing the configuration of each
    #     layer of the packet
    # Returns:
    #   Output from Rx end of Ixia if duration != 0, else 0

    ##################################################
    ################# Initialisation #################
    ##################################################

    # Configure global variables. See documentation on 'global' for more
    # information on why this is necessary
    #   https://www.tcl.tk/man/tcl8.5/tutorial/Tcl13.html
    global portList

    # Extract the provided dictionaries to local variables to simplify the
    # rest of the script

    # flow spec

    set streamType              [dict get $flowSpec type]
    set numPkts                 [dict get $flowSpec numpkts]
    set duration                [expr {[dict get $flowSpec duration] * 1000}]
    set frameRate               [dict get $flowSpec framerate]

    # traffic spec

    # extract nested dictionaries
    set trafficSpec_l2          [dict get $trafficSpec l2]
    set trafficSpec_l3          [dict get $trafficSpec l3]
    set trafficSpec_vlan        [dict get $trafficSpec vlan]

    set frameSize               [dict get $trafficSpec_l2 framesize]
    set srcMac                  [dict get $trafficSpec_l2 srcmac]
    set dstMac                  [dict get $trafficSpec_l2 dstmac]
    set srcPort                 [dict get $trafficSpec_l2 srcport]
    set dstPort                 [dict get $trafficSpec_l2 dstport]

    set proto                   [dict get $trafficSpec_l3 proto]
    set srcIp                   [dict get $trafficSpec_l3 srcip]
    set dstIp                   [dict get $trafficSpec_l3 dstip]

    set vlanEnabled             [dict get $trafficSpec_vlan enabled]
    if {$vlanEnabled == 1 } {
        # these keys won't exist if vlan wasn't enabled
        set vlanId                  [dict get $trafficSpec_vlan id]
        set vlanUserPrio            [dict get $trafficSpec_vlan priority]
        set vlanCfi                 [dict get $trafficSpec_vlan cfi]
    }

    ##################################################
    ##################### Streams ####################
    ##################################################

    streamRegion get $::chassis $::card $::port1
    if {[streamRegion enableGenerateWarningList $::chassis $::card $::port1 false]} {
        errorMsg "Error calling streamRegion enableGenerateWarningList  $::chassis $::card $::port1 false"
    }

    set streamId 1

    stream setDefault
    stream config -ifg                                9.6
    stream config -ifgMIN                             19.2
    stream config -ifgMAX                             25.6
    stream config -ibg                                9.6
    stream config -isg                                9.6
    stream config -rateMode                           streamRateModePercentRate
    stream config -percentPacketRate                  $frameRate
    stream config -framesize                          $frameSize
    stream config -frameType                          "08 00"
    stream config -sa                                 $srcMac
    stream config -da                                 $dstMac
    stream config -numSA                              16
    stream config -numDA                              16
    stream config -asyncIntEnable                     true
    stream config -dma                                $streamType
    stream config -numBursts                          1
    stream config -numFrames                          $numPkts
    stream config -patternType                        incrByte
    stream config -dataPattern                        x00010203
    stream config -pattern                            "00 01 02 03"

    protocol setDefault
    protocol config -name                             ipV4
    protocol config -ethernetType                     ethernetII
    if {$vlanEnabled == 1} {
        protocol config -enable802dot1qTag            vlanSingle
    }

    ip setDefault
    ip config -ipProtocol                             ipV4Protocol[string totitle $proto]
    ip config -checksum                               "f6 75"
    ip config -sourceIpAddr                           $srcIp
    ip config -sourceIpAddrRepeatCount                10
    ip config -sourceClass                            classA
    ip config -destIpAddr                             $dstIp
    ip config -destIpAddrRepeatCount                  10
    ip config -destClass                              classA
    ip config -destMacAddr                            $dstMac
    ip config -destDutIpAddr                          0.0.0.0
    ip config -ttl                                    64
    if {[ip set $::chassis $::card $::port1]} {
        errorMsg "Error calling ip set $::chassis $::card $::port1"
    }

    "$proto" setDefault
    "$proto" config -checksum                         "25 81"
    if {["$proto" set $::chassis $::card $::port1]} {
        errorMsg "Error calling $proto set $::chassis $::card $::port"
    }

    if {$vlanEnabled == 1 } {
        vlan setDefault
        vlan config -vlanID                               $vlanId
        vlan config -userPriority                         $vlanUserPrio
        vlan config -cfi                                  $vlanCfi
        vlan config -mode                                 vIdle
        vlan config -repeat                               10
        vlan config -step                                 1
        vlan config -maskval                              "0000XXXXXXXXXXXX"
        vlan config -protocolTagId                        vlanProtocolTag8100
    }

    if {[vlan set $::chassis $::card $::port1]} {
        errorMsg "Error calling vlan set $::chassis $::card $::port1"
    }

    if {[port isValidFeature $::chassis $::card $::port1 $::portFeatureTableUdf]} {
        tableUdf setDefault
        tableUdf clearColumns
        if {[tableUdf set $::chassis $::card $::port1]} {
            errorMsg "Error calling tableUdf set $::chassis $::card $::port1"
        }
    }

    if {[port isValidFeature $::chassis $::card $::port1 $::portFeatureRandomFrameSizeWeightedPair]} {
        weightedRandomFramesize setDefault
        if {[weightedRandomFramesize set $::chassis $::card $::port1]} {
            errorMsg "Error calling weightedRandomFramesize set $::chassis $::card $::port1"
        }
    }

    if {$proto == "tcp"} {
        tcp setDefault
        tcp config -sourcePort                            $srcPort
        tcp config -destPort                              $dstPort
        if {[tcp set $::chassis $::card $::port1 ]} {
            errorMsg "Error setting tcp on port $::chassis.$::card.$::port1"
        }

        if {$vlanEnabled != 1} {
            udf setDefault
            udf config -repeat                                1
            udf config -continuousCount                       true
            udf config -initval                               {00 00 00 01}
            udf config -updown                                uuuu
            udf config -cascadeType                           udfCascadeNone
            udf config -step                                  1

            packetGroup setDefault
            packetGroup config -insertSequenceSignature       true
            packetGroup config -sequenceNumberOffset          38
            packetGroup config -signatureOffset               42
            packetGroup config -signature                     "08 71 18 05"
            packetGroup config -groupIdOffset                 52
            packetGroup config -groupId                       $streamId
            packetGroup config -allocateUdf                   true
            if {[packetGroup setTx $::chassis $::card $::port1 $streamId]} {
                errorMsg "Error calling packetGroup setTx $::chassis $::card $::port1 $streamId"
            }
        }
    } elseif {$proto == "udp"} {
        udp setDefault
        udp config -sourcePort                            $srcPort
        udp config -destPort                              $dstPort
        if {[udp set $::chassis $::card $::port1]} {
            errorMsg "Error setting udp on port $::chassis.$::card.$::port1"
        }
    }

    if {[stream set $::chassis $::card $::port1 $streamId]} {
        errorMsg "Error calling stream set $::chassis $::card $::port1 $streamId"
    }

    incr streamId
    streamRegion generateWarningList $::chassis $::card $::port1
    ixWriteConfigToHardware portList -noProtocolServer

    if {[packetGroup getRx $::chassis $::card $::port2]} {
        errorMsg "Error calling packetGroup getRx $::chassis $::card $::port2"
    }

    ##################################################
    ######### Traffic Transmit and Results ###########
    ##################################################

    # Transmit traffic

    logMsg "Clearing stats for all ports"
    ixClearStats portList

    logMsg "Starting packet groups on port $::port2"
    ixStartPortPacketGroups $::chassis $::card $::port2

    logMsg "Starting Capture on port $::port2"
    ixStartPortCapture $::chassis $::card $::port2

    logMsg "Starting transmit on port $::port1"
    ixStartPortTransmit $::chassis $::card $::port1

    # If duration=0 is passed, exit after starting transmit

    if {$duration == 0} {
        logMsg "Sending traffic until interrupted"
        return
    }

    logMsg "Waiting for $duration ms"

    # Wait for duration - 1 second to get traffic rate

    after [expr "$duration - 1"]

    # Get result

    set result                                        [stopTraffic]

    if {$streamType == "contPacket"} {
        return $result
    } elseif {$streamType == "stopStream"} {
        set payError 0
        set seqError 0
        set captureLimit 3000

        # explode results from 'stopTraffic' for ease of use later
        set framesSent [lindex $result 0]
        set framesRecv [lindex $result 1]
        set bytesSent [lindex $result 2]
        set bytesRecv [lindex $result 3]

        if {$framesSent <= $captureLimit} {
            captureBuffer get $::chassis $::card $::port2 1 $framesSent
            set capturedFrames [captureBuffer cget -numFrames]

            set notCaptured [expr "$framesRecv - $capturedFrames"]
            if {$notCaptured != 0} {
                errorMsg "'$notCaptured' frames were not captured"
            }

            if {$proto == "tcp"} {
                for {set z 1} {$z <= $capturedFrames} {incr z} {
                    captureBuffer getframe $z
                    set capFrame [captureBuffer cget -frame]
                    regsub -all " " $capFrame "" frameNoSpaces
                    set frameNoSpaces

                    set startPayload 108
                    set endPayload [expr "[expr "$frameSize * 2"] - 9"]
                    set payload [string range $frameNoSpaces $startPayload $endPayload]

                    if {$vlanEnabled != 1} {
                        set startSequence 76
                        set endSequence 83
                        set sequence [string range $frameNoSpaces $startSequence $endSequence]
                        scan $sequence %x seqDecimal
                        set seqDecimal
                        if {"$payload" != $::payloadLookup($frameSize)} {
                            errorMsg "frame '$z' payload: invalid payload"
                            incr payError
                        }
                        # variable z increments from 1 to total number of packets
                        # captured TCP sequence numbers start at 0, not 1. When
                        # comparing sequence numbers for captured frames, reduce
                        # variable z by 1 i.e. frame 1 with sequence 0 is compared
                        # to expected sequence 0.
                        if {$seqDecimal != $z-1} {
                            errorMsg "frame '$z' sequence number: Found '$seqDecimal'. Expected '$z'"
                            incr seqError
                        }
                    }
                }
            }
            logMsg "Sequence Errors: $seqError"
            logMsg "Payload Errors:  $payError\n"
        } else {
            errorMsg "Too many packets for capture."
        }

        set result [list $framesSent $framesRecv $bytesSent $bytesRecv $payError $seqError]
        return $result
    } else {
        errorMsg "streamtype is not supported: '$streamType'"
    }
}

proc stopTraffic {} {
    # Stop sending traffic from IXIA.
    #
    # Stops Transmit of traffic from Rx port.
    #
    # Returns:
    #   Output from Rx end of Ixia.

    ##################################################
    ################# Initialisation #################
    ##################################################

    # Configure global variables. See documentation on 'global' for more
    # information on why this is necessary
    #   https://www.tcl.tk/man/tcl8.5/tutorial/Tcl13.html
    global portList

    ##################################################
    ####### Stop Traffic Transmit and Results ########
    ##################################################

     # Read frame rate of transmission

    if {[stat getRate statAllStats $::chassis $::card $::port1]} {
        errorMsg "Error reading stat rate on port $::chassis $::card $::port1"
        return $::TCL_ERROR
    }

    set sendRate [stat cget -framesSent]
    set sendRateBytes [stat cget -bytesSent]

    if {[stat getRate statAllStats $::chassis $::card $::port2]} {
        errorMsg "Error reading stat rate on port $::chassis $::card $::port2"
        return $::TCL_ERROR
    }

    set recvRate [stat cget -framesReceived]
    set recvRateBytes [stat cget -bytesReceived]

    # Wait for a second, else we get funny framerate statistics
    after 1

    # Stop transmission of traffic
    ixStopTransmit portList

    if {[ixCheckTransmitDone portList] == $::TCL_ERROR} {
        return -code error
    } else {
        logMsg "Transmission is complete.\n"
    }

    ixStopPacketGroups portList
    ixStopCapture portList

    # Get statistics

    if {[stat get statAllStats $::chassis $::card $::port1]} {
        errorMsg "Error reading stat on port $::chassis $::card $::port1"
        return $::TCL_ERROR
    }

    set bytesSent [stat cget -bytesSent]
    set framesSent [stat cget -framesSent]

    if {[stat get statAllStats $::chassis $::card $::port2]} {
        errorMsg "Error reading stat on port $::chassis $::card $::port2"
        return $::TCL_ERROR
    }

    set bytesRecv [stat cget -bytesReceived]
    set framesRecv [stat cget -framesReceived]

    set bytesDropped [expr "$bytesSent - $bytesRecv"]
    set framesDropped [expr "$framesSent - $framesRecv"]

    logMsg "Frames Sent:       $framesSent"
    logMsg "Frames Recv:       $framesRecv"
    logMsg "Frames Dropped:    $framesDropped\n"

    logMsg "Bytes Sent:        $bytesSent"
    logMsg "Bytes Recv:        $bytesRecv"
    logMsg "Bytes Dropped:     $bytesDropped\n"

    logMsg "Frame Rate Sent:   $sendRate"
    logMsg "Frame Rate Recv:   $recvRate\n"

    set result [list $framesSent $framesRecv $bytesSent $bytesRecv $sendRate $recvRate $sendRateBytes $recvRateBytes]

    return $result
}

proc rfcThroughputTest { testSpec trafficSpec } {
    # Execute RFC tests from IXIA.
    #
    # Wraps the sendTraffic proc, repeatedly calling it, storing the result and
    # performing an iterative binary search to find the highest possible RFC
    # transmission rate. Abides by the specification of RFC2544 as given by the
    # IETF:
    #
    #   https://www.ietf.org/rfc/rfc2544.txt
    #
    # Parameters:
    #   testSpec - a dict detailing how the test should be run. Should be
    #     of format:
    #         {numtrials, duration, lossrate}
    #   trafficSpec - a dict describing the packet to be sent. Should be
    #     of format:
    #         { l2, l3}
    #     where each item is in turn a dict detailing the configuration of each
    #     layer of the packet
    # Returns:
    #   Highest rate with acceptable packet loss.

    ##################################################
    ################# Initialisation #################
    ##################################################

    # Configure global variables. See documentation on 'global' for more
    # information on why this is necessary
    #   https://www.tcl.tk/man/tcl8.5/tutorial/Tcl13.html
    global portList

    # Extract the provided dictionaries to local variables to simplify the
    # rest of the script

    # testSpec

    set numTrials               [dict get $testSpec trials]  ;# we don't use this yet
    set duration                [dict get $testSpec duration]
    set lossRate                [dict get $testSpec lossrate]
    set multipleStream          [dict get $testSpec multipleStreams]  ;# we don't use this yet

    # variables used for binary search of results
    set min 1
    set max 100
    set diff [expr "$max - $min"]

    set result [list 0 0 0 0 0 0 0 0]  ;# best result found so far
    set percentRate 100  ;# starting throughput percentage rate

    ##################################################
    ######### Traffic Transmit and Results ###########
    ##################################################

    # iterate a maximum of 20 times, sending packets at a set  rate to
    # find fastest possible rate with acceptable packetloss
    #
    # As a reminder, the binary search works something like this:
    #
    #   percentRate < idealValue --> min = percentRate
    #   percentRate > idealValue --> max = percentRate
    #   percentRate = idealValue --> max = min = percentRate
    #
    for {set i 0} {$i < 20} {incr i} {
        dict set flowSpec type                        "contPacket"
        dict set flowSpec numpkts                     100 ;# this can be bypassed
        dict set flowSpec duration                    $duration
        dict set flowSpec framerate                   $percentRate

        set flowStats [sendTraffic $flowSpec $trafficSpec]

        # explode results from 'sendTraffic' for ease of use later
        set framesSent [lindex $flowStats 0]
        set framesRecv [lindex $flowStats 1]
        set sendRate [lindex $flowStats 4]

        set framesDropped [expr "$framesSent - $framesRecv"]
        if {$framesSent > 0} {
            set framesDroppedRate [expr "double($framesDropped) / $framesSent"]
        } else {
            set framesDroppedRate 100
        }

        # check if we've already found the rate before 10 iterations, i.e.
        # 'percentRate = idealValue'. This is as accurate as we can get with
        # integer values.
        if {[expr "$max - $min"] <= 0.5 } {
            break
        }

        # handle 'percentRate <= idealValue' case
        if {$framesDroppedRate <= $lossRate} {
            logMsg "Frame sendRate of '$sendRate' pps succeeded ('$framesDropped' frames dropped)"

            set result $flowStats
            set min $percentRate

            set percentRate [expr "$percentRate + ([expr "$max - $min"] * 0.5)"]
        # handle the 'percentRate > idealValue' case
        } else {
            if {$framesDropped == $framesSent} {
                errorMsg "Dropped all frames!"
            }

            errorMsg "Frame sendRate of '$sendRate' pps failed ('$framesDropped' frames dropped)"

            set max $percentRate
            set percentRate [expr "$percentRate - ([expr "$max - $min"] * 0.5)"]
        }
    }

    set bestRate [lindex $result 4]

    logMsg "$lossRate% packet loss rate: $bestRate"

    return $result
}