【PFC】pfctest PFC测试工具

原文:https://github.com/archjeb/pfctest

pfctest

此脚本的目的是提供一种简单的方法来测试 IEEE 802.1Qbb 标准中定义的基于优先级的流量控制。这使网络工程师和其他技术人员能够轻松测试其网络上的 PFC 设置以及各种 PFC 量子值(PFC quanta values )可能产生的影响。

Author

Jeremy Georges

Description

pfctest 根据每个流量类别(traffic class)的量子值(quanta value)创建 pfc 数据包。该工具的目的是帮助网络工程师和其他技术人员在他们的网络上测试他们的 PFC 实现。
它不打算被用于DoS 攻击网络。因此,使用风险自负,最好在实验室环境中使用。

PFC 数据包的以太网帧格式如下:

                 -------------------------
 Destination MAC |   01:80:C2:00:00:01   |
                 -------------------------
 Source MAC      |      Station MAC      |
                 -------------------------
 Ethertype       |         0x8808        |
                 -------------------------
 OpCode          |         0x0101        |
                 -------------------------
 Class Enable V  | 0x00 E7...E0          |   - Class-enable vector, 8 bits for each class 
                 -------------------------
 Time Class 0    |       0x0000          |
                 -------------------------
 Time Class 1    |       0x0000          |
                 -------------------------
 ...     
                 -------------------------
 Time Class 7    |       0x0000          |
                 -------------------------

注意:时间以quanta 为单位(赋值),其中每个quanta 代表以当前网络速度传输 512 位所需的时间。

例如,传输 512 位,高速以太网每比特需要 10ns,Gb 以太网是 1ns,10Gb 是每比特时间 0.1ns。

因此,如果 10Gb 链路 PFC 类的 quanta 设置为最大值 65535,则 0.1(512)*65535 = 3.3ms 暂停时间。
上面(报文中)的每个区块从Ethertype往下都是16位(2 个字节)

为特定类发送 0 的quanta 告诉接收器它可以为该类显式“取消暂停”。

 

Note: Time in quanta where each quantum represents time it takes to transmit 512 bits at the current network speed. For example, Fast Ethernet takes 10ns per bit, Gb Ethernet is 1ns and 10Gb is 0.1ns per bit time. So if quanta is set to max of 65535 for a 10Gb link PFC class, then 0.1(512)*65535 = 3.3ms pause time.

Each block above from Ethertype down is 16bits (2 octets)

Sending a quanta of 0 for a specific class tells a receiver that it can 'unpause' explictly for that class.

Usage

pfctest.py 需要一些参数。必须指定出口接口和 PFC 类。 Quanta 值可以是 0 - 65535。
此外,可以指定一个迭代值指定脚本将发送的数据包数量,(不指定时)默认只有一个数据包。

 用法: pfctest.py [options] arg1 arg2
 
 选项:
   -h, --help            show this help message and exit
   -V, --version         The version
   -d Interface, --device=Interface,输出数据包的接口
                        
   --p0                  Priority Flow Control Enable Class 0
   --p1                  Priority Flow Control Enable Class 1
   --p2                  Priority Flow Control Enable Class 2
   --p3                  Priority Flow Control Enable Class 3
   --p4                  Priority Flow Control Enable Class 4
   --p5                  Priority Flow Control Enable Class 5
   --p6                  Priority Flow Control Enable Class 6
   --p7                  Priority Flow Control Enable Class 7
   --q0=Quanta           Time in Quanta for Class 0
   --q1=Quanta           Time in Quanta for Class 1
   --q2=Quanta           Time in Quanta for Class 2
   --q3=Quanta           Time in Quanta for Class 3
   --q4=Quanta           Time in Quanta for Class 4
   --q5=Quanta           Time in Quanta for Class 5
   --q6=Quanta           Time in Quanta for Class 6
   --q7=Quanta           Time in Quanta for Class 7
   -i number, --iteration=number,迭代次数
                         

此外,请注意此脚本仅支持 Python 2.6/2.7 和 Linux。

#!/usr/bin/env python
#**********************************************************************
# pfctest.py - Written by Jeremy Georges
# Version 1.0  - 05/22/2015 - Initial Script
# Version 1.1  - 05/23/2015 -  Fixed minor parse bug
# version 1.2  - 05/27/2015 - Changed Time class range to use full 2 bytes (dec 0-65535)
# version 1.3  - 06/03/2015 - Fix an issue with the class parsing if Class Enable Vector didn't add up to to even two digit hex value
# version 1.4  - 11/19/2020 - Add frame padding so frame is 60 bytes plus 4 byte FCS. 
#***********************************************************************

"""
Tool to generate PFC packets.  

The purpose of this tool is it help Network Engineers and other technical staff to test their PFC implementation on their
network. It is not intended to be used to DoS a network by forcing hosts to pause to eternity. Therefore, use
at your own risk and preferably in a lab environment.


The Ethernet Frame format for PFC packets is the following:

                -------------------------
Destination MAC |   01:80:C2:00:00:01   |
                -------------------------
Source MAC      |      Station MAC      |
                -------------------------
Ethertype       |         0x8808        |
                -------------------------
OpCode          |         0x0101        |
                -------------------------
Class Enable V  | 0x00 E7...E0          |   - Class-enable vector, 8 bits for each class MSB E7 LSB E0. 1 enabled 0 disable
                -------------------------
Time Class 0    |       0x0000          |
                -------------------------
Time Class 1    |       0x0000          |
                -------------------------
...
                -------------------------
Time Class 7    |       0x0000          |
                -------------------------


Note: Time in quanta where each quantum represents time it takes to transmit 512 bits at the current network speed.

Each block above from Ethertype down is 16bits (2 octets)

If Class enable Vector set, but time set for class is 0, this tells remote stations to unpause for that class.

"""

#===========================================================
# Modules
#===========================================================
from socket import socket, AF_PACKET, SOCK_RAW
import sys, os
from struct import *
import binascii
import optparse

#===========================================================
# Variables
#===========================================================
VERSION='1.0'

#===========================================================
# Function Definitions
#===========================================================
# checksum functions needed for calculation checksum
def checksum(msg):
    s = 0
     
    # loop taking 2 characters at a time
    for i in range(0, len(msg), 2):
        w = ord(msg[i]) + (ord(msg[i+1]) << 8 )
        s = s + w
     
    s = (s>>16) + (s & 0xffff);
    s = s + (s >> 16);
     
    #complement and mask to 4 byte short
    s = ~s & 0xffff
     
    return s

   
#============================
# MAIN
#============================
def main():
    usage = "usage: %prog [options] arg1 arg2"
    parser = optparse.OptionParser(usage=usage)
    parser.add_option("-V", "--version", action="store_true",dest="version", help="The version")
    parser.add_option("-d", "--device", type="string", dest="interface", help="The Interface to egress packets",metavar="Interface")
    parser.add_option('--p0',action="store_true", default=False, help="Priority Flow Control Enable Class 0")
    parser.add_option("--p1",action="store_true", default=False, help="Priority Flow Control Enable Class 1")
    parser.add_option("--p2",action="store_true", default=False, help="Priority Flow Control Enable Class 2")
    parser.add_option("--p3",action="store_true", default=False, help="Priority Flow Control Enable Class 3")
    parser.add_option("--p4",action="store_true", default=False, help="Priority Flow Control Enable Class 4")
    parser.add_option("--p5",action="store_true", default=False, help="Priority Flow Control Enable Class 5")
    parser.add_option("--p6",action="store_true", default=False, help="Priority Flow Control Enable Class 6")
    parser.add_option("--p7",action="store_true", default=False, help="Priority Flow Control Enable Class 7")
    parser.add_option("--q0", type="int", dest="quanta0", help="Time in Quanta for Class 0",metavar="Quanta")
    parser.add_option("--q1", type="int", dest="quanta1", help="Time in Quanta for Class 1",metavar="Quanta")
    parser.add_option("--q2", type="int", dest="quanta2", help="Time in Quanta for Class 2",metavar="Quanta")
    parser.add_option("--q3", type="int", dest="quanta3", help="Time in Quanta for Class 3",metavar="Quanta")
    parser.add_option("--q4", type="int", dest="quanta4", help="Time in Quanta for Class 4",metavar="Quanta")
    parser.add_option("--q5", type="int", dest="quanta5", help="Time in Quanta for Class 5",metavar="Quanta")
    parser.add_option("--q6", type="int", dest="quanta6", help="Time in Quanta for Class 6",metavar="Quanta")
    parser.add_option("--q7", type="int", dest="quanta7", help="Time in Quanta for Class 7",metavar="Quanta")
    parser.add_option("-i", "--iteration", type="int", dest="iteration", help="Number of times to iterate",metavar="number",default=1)
    (options, args) = parser.parse_args()

    if options.version:
        print os.path.basename(sys.argv[0]), "  Version: ", VERSION
        sys.exit(0)
    
    if options.interface is None:
        print "Egress Interface must be specified!"
        parser.print_help()
        sys.exit(1)
  
    try:  
       s = socket(AF_PACKET, SOCK_RAW)
    except:
        print "Unable to create socket. Check your permissions"
        sys.exit(1)
        
    s.bind((options.interface, 0))

    # Put together an ethernet frame here, using ASCII literals 
 
    src_addr = "\x01\x02\x03\x04\x05\x06"
    dst_addr = "\x01\x80\xC2\x00\x00\x01"
    opcode = "\x01\x01"
    ethertype = "\x88\x08"
    
    #Set initial value of classvector to all zeros
    #Its a two byte value where the upper two bytes should be set to zero
    #We'll stick with raw ASCII. Leave the upper 8 bits to all zeros in ASCII hex
    #
    #for the lower 8 bits, we'll we'll set it to an all 0 byte value in binary
    #then we'll just add bits. In the end, we'll convert that to ASCII again...its easy to concatenate
     
    #The lower bits are set based on which class is enabled where MSB is class 7, LSB is class0
    classvectorbyteUpper="\x00"
    classvectorbyteLower=0b00000000
    #Lets enable the appropriate class vectors.
    if options.p0:
        classvectorbyteLower=0b00000001+classvectorbyteLower
    if options.p1:
        classvectorbyteLower=0b00000010+classvectorbyteLower    
    if options.p2:
        classvectorbyteLower=0b00000100+classvectorbyteLower   
    if options.p3:
        classvectorbyteLower=0b00001000+classvectorbyteLower  
    if options.p4:
        classvectorbyteLower=0b00010000+classvectorbyteLower  
    if options.p5:
        classvectorbyteLower=0b00100000+classvectorbyteLower
    if options.p6:
        classvectorbyteLower=0b01000000+classvectorbyteLower
    if options.p7:
        classvectorbyteLower=0b10000000+classvectorbyteLower  
    #Need to covert to a string with escaped hex literal  
   
    #In the previous release this was
    #classvectorbyteLower = binascii.unhexlify(str(hex(classvectorbyteLower)).replace('0x',''))
    #but if the decimal value was below 8, we didn't have enough digits for unhexlify. So we'll do 
    #this hack to pad our number so its 2 hex digits, then we'll strip the 0x for our literal ascii
    classvectorbyteLower=binascii.unhexlify(format(classvectorbyteLower, '#04x').replace('0x',''))

    
    #Concatenate the full enable vector two bytes.
    classvector = classvectorbyteUpper + classvectorbyteLower
   
   
    # Build time for each class enabled.  
    # Each time class is 2 byte value for each pause frame. Time in quanta where each
    # quantum represents time int takes to transmit 512 bits at the current network speed.. 
    classtimebyteUpper="\x00"  
    
    if options.quanta0:
        if options.quanta0 <= 65535:
            #If its greater than 255, unhexlify will provide full hex for each byte
            #If its less than 255, then we need to prepend a 0 value for the high order byte
            if options.quanta0 > 255:
                pfc0=binascii.unhexlify(format(options.quanta0, '#06x').replace('0x',''))
            else:     
                pfc0=classtimebyteUpper+binascii.unhexlify(format(options.quanta0, '#04x').replace('0x','')) 
        else:
            print "Not a valid quanta value. But should be in the range of 0 - 65535"
            sys.exit(1)
    else: 
        #If no CLI argument, pass a zero value for this class       
        pfc0="\x00\x00"    
    if options.quanta1:
        if options.quanta1 <= 65535:
            if options.quanta1 > 255:
               pfc1=binascii.unhexlify(format(options.quanta1, '#06x').replace('0x',''))
            else:     
                pfc1=classtimebyteUpper+binascii.unhexlify(format(options.quanta1, '#04x').replace('0x',''))  
        else:
            print "Not a valid quanta value. But should be in the range of 0 - 65535"
            sys.exit(1)
    else: 
        #If no CLI argument, pass a zero value for this class       
        pfc1="\x00\x00"  
            
    if options.quanta2:
        if options.quanta2 <= 65535:
            if options.quanta2 > 255:
                pfc2=binascii.unhexlify(format(options.quanta2, '#06x').replace('0x',''))
            else:     
                pfc2=classtimebyteUpper+binascii.unhexlify(format(options.quanta2, '#04x').replace('0x','')) 
        else:
            print "Not a valid quanta value. But should be in the range of 0 - 65535"
            sys.exit(1)
    else: 
        #If no CLI argument, pass a zero value for this class       
        pfc2="\x00\x00"
   
    if options.quanta3:
        if options.quanta3 <= 65535:
            if options.quanta3 > 255:
                pfc3=binascii.unhexlify(format(options.quanta3, '#06x').replace('0x',''))
            else:     
                pfc3=classtimebyteUpper+binascii.unhexlify(format(options.quanta3, '#04x').replace('0x','')) 
        else:
            print "Not a valid quanta value. But should be in the range of 0 - 65535"
            sys.exit(1)
    else: 
        #If no CLI argument, pass a zero value for this class       
        pfc3="\x00\x00"

    if options.quanta4:
        if options.quanta4 <= 65535: 
            if options.quanta4 > 255:
                pfc4=binascii.unhexlify(format(options.quanta4, '#06x').replace('0x',''))
            else:     
                pfc4=classtimebyteUpper+binascii.unhexlify(format(options.quanta4, '#04x').replace('0x','')) 
        else:
            print "Not a valid quanta value. But should be in the range of 0 - 65535"
            sys.exit(1)
    else: 
        #If no CLI argument, pass a zero value for this class       
        pfc4="\x00\x00"
        
    if options.quanta5:
        if options.quanta5 <= 65535:
            if options.quanta5 > 255:
                pfc5=binascii.unhexlify(format(options.quanta5, '#06x').replace('0x',''))
            else:     
                pfc5=classtimebyteUpper+binascii.unhexlify(format(options.quanta5, '#04x').replace('0x','')) 
        else:
            print "Not a valid quanta value. But should be in the range of 0 - 65535"
            sys.exit(1)
    else: 
        #If no CLI argument, pass a zero value for this class       
        pfc5="\x00\x00"
    if options.quanta6:
        if options.quanta6 <= 65535:
            if options.quanta6 > 255:
                pfc6=binascii.unhexlify(format(options.quanta6, '#06x').replace('0x',''))
            else:     
                pfc6=classtimebyteUpper+binascii.unhexlify(format(options.quanta6, '#04x').replace('0x','')) 
        else:
            print "Not a valid quanta value. But should be in the range of 0 - 65535"
            sys.exit(1)
    else: 
        #If no CLI argument, pass a zero value for this class       
        pfc6="\x00\x00"
        
    if options.quanta7:
        if options.quanta7 <= 65535:
            if options.quanta7 > 255:
                pfc7=binascii.unhexlify(format(options.quanta7, '#06x').replace('0x',''))
            else:     
                pfc7=classtimebyteUpper+binascii.unhexlify(format(options.quanta7, '#04x').replace('0x','')) 
        else:
            print "Not a valid quanta value. But should be in the range of 0 - 65535"
            sys.exit(1)
    else: 
        #If no CLI argument, pass a zero value for this class       
        pfc7="\x00\x00"
  

    # This will give us 60bytes...assuming ethernet NIC will add the 4byte FCS to frame.
    # Just in case, we'll add the FCS at the end in case someone is playing around with ethtool and turning that off. 
    padding="\x00\x00"*10 
    fullpacketfields=dst_addr+src_addr+ethertype+opcode+classvector+pfc0+pfc1+pfc2+pfc3+pfc4+pfc5+pfc6+pfc7+padding
    x = checksum(fullpacketfields) 
    thechecksum=hex(x)
    fullrawpacket = fullpacketfields+thechecksum 
    print (len(fullrawpacket))  
    print "Generating %s Packet(s)" % options.iteration
    while options.iteration > 0:
        s.send(fullrawpacket)
        options.iteration -= 1
        
        
if __name__ == "__main__":
    main()

 

posted on 2022-10-04 01:23  bdy  阅读(116)  评论(0编辑  收藏  举报

导航