perl生成以太网帧

实验室在仿真以太网相关Verilog代码的时候,有时候需要产生一定以太网帧数据供TestBench使用,手动编写64字节-1518字节的不同的以太网数据十分麻烦。参考上板调试时候使用思博伦TeseCenter网络测试仪的思想,我们可以使用Perl脚本批量生成需要的数据,参考代码如下:

#######################################################################################
# main.pl                                                                             #
# 段聪                                                                                #
# 2014/12/5-2014/12/8                                                                 #
# 说明:                                                                              #
#      1.生成以太网帧                                                                 #
#      2.生成三个文件,分别是帧数据文件,帧头文件,帧长文件                           #
#        输出文件在当前目录的output文件夹中                                           #
#      3.忽略4字节以太网CRC校验值(帧长60-1514)                                        #
#      4.CRC32校验算法使用C++编写,在子目录crc中                                      #
#                                                                                     #
# 运行环境:                                                                          #
#      perl/ActivePerl                                                                #
#######################################################################################
# LOG                                                                                 #
# V2:                                                                                 #
#1、(已解决)可以输入设备数n,每个设备都有一个MAC地址和IP地址                          #
#2、(已解决)ARP帧发送可控(开关),发送的ARP帧位设备的映射关系,                      #
#            暂定为只发送n个ARP,首先ARP帧                                            #
#3、(已解决)修改默认值问题                                                            #
#4、(已解决)生成按字节反序的数据文件 (file_reverse_bytes.pl)                          #
#                                                                                     #
#                                                                                     #
#######################################################################################

=pod

承载IP帧时
+------------+---------+-----------+--------------+-----------+---------+---------+-----------------+
| 以太网头部 | IP头部  | TCP端口号 | 数据填充     | 源设备号  | 帧编号  | CRC校验 | 以太网CRC(不选) |
|   14字节   | 20字节  |   4字节   | (64~1518)-50 | 4字节     | 4字节   |  4字节  |     4字节       |
+------------+---------+-----------+--------------+-----------+---------+---------+-----------------+

注:对(TCP端口号头部~行号)计算CRC校验值

承载ARP帧时
+------------+---------+--------------------------+-----------+---------+---------+-----------------+
| 以太网头部 | ARP头部 |        数据填充          | 源设备号  | 帧编号  | CRC校验 | 以太网CRC(不选) |
|   14字节   | 28字节  |        (64~1518)-54      | 4字节     | 4字节   |  4字节  |     4字节       |
+------------+---------+--------------------------+-----------+---------+---------+-----------------+

注:对(ARP头部~行号)计算CRC校验值
    行号是数据帧在文件中的行号,从0计数
    
MAC地址产生策略
    mac(0)   --> mac(0)+1
    mac(0)+1 --> mac(0)+2
    ...
    mac(0)+i --> mac(0)+i+1
    ...
    mac(0)+n --> mac(0)
    
=cut

#是否开启调试模式
#开启时候debug为1
$debug = 0;

#-------------------------------------------------------------
print "\n","*" x 15,"以太网帧生成器","*" x 15,"\n\n";

#------------------------常量定义-----------------------------
$ETHERNET_MAX_LENGTH = 1518; #以太网最长帧字节数
$ETHERNET_MIN_LENGTH = 64;   #以太网最短帧字节数
$ETHERNET_CRC_LENGTH = 4;    #以太网CRC字节数
#-------------------------------------------------------------

#------------------------创建文件-----------------------------
$outdir = "./output/";
$frame_file = $outdir."frame.txt";
$frame_length_file = $outdir."frame_length.txt";
$frame_header_file = $outdir."frame_header.txt";

unless (-d $outdir){
    #输出文件夹不存在
    mkdir $outdir;
}

open FRAME_FILE,">",$frame_file or die "打开文件失败";
open FRAME_LENGTH_FILE,">",$frame_length_file or die "打开文件失败";
open FRAME_HEADER_FILE,">",$frame_header_file or die "打开文件失败";

#-------------------------------------------------------------

print "输入帧数目(帧数目,直接回车设置为默认值10):";
$row_cnt = <STDIN>;
chomp($row_cnt);
if($row_cnt =~ /^$/){
    print "帧数目设置为默认值10\n";
    $row_cnt = 10;
}
else{
    while(!($row_cnt =~ /\d+/)){
        print "帧数目输入不是数字,重新输入:\n";
        $row_cnt = <STDIN>;
        chomp($row_cnt);
    }
}
print "-" x 20,"\n";

#---------------------设备数目--------------------------------

print "输入设备数目(直接回车设置为默认值5):";
$mac_cnt = <STDIN>;
chomp($mac_cnt);
if($mac_cnt =~ /^$/){
    print "设备数目设置为默认值5\n";
    $mac_cnt = 5;
}
else{
    while(!($mac_cnt =~ /\d+/)){
        print "设备数目不是数字,重新输入:\n";
        $mac_cnt = <STDIN>;
        chomp($mac_cnt);
    }
}
print "-" x 20,"\n";

#---------------------帧长产生策略----------------------------
print "选择帧长产生策略(1或2):\n";
print "直接回车默认选择1自增\n";
print "1. 自增\n";
print "2. 随机\n";
$frame_length_type = <STDIN>;
chomp($frame_length_type);
if($frame_length_type =~/^$/){
    print "帧长产生策略设置为默认自增\n";
    $frame_length_type = 1;
}
else{
    while($frame_length_type>2 || $frame_length_type<1){
        print "错误的输入,选择帧长产生策略(1或2):\n";
        $frame_length_type = <STDIN>;
    }
}
print "-" x 20,"\n";

#-------------------------------------------------------------
my $base_frame_length;
my $interval;
if($frame_length_type==1){
    print "\n选择了帧长自动增加...\n";
    print "直接回车设置为默认值64:\n";
    print "输入帧长起始值(64-1518):";
    $base_frame_length = <STDIN>;
    chomp($base_frame_length);
    if($base_frame_length =~/^$/){
        print "输入帧长起始值设置为默认值64\n";
        $base_frame_length = 64;
    }
    else{
        while($base_frame_length>1518 || $base_frame_length<64){
            print "帧长不在范围内,重新输入起始值(64-1518):\n";
            $base_frame_length = <STDIN>;
            chomp($base_frame_length);
        }
    }
    
    #-----------------------------------------------
    print "输入增加间隔(直接回车选择默认值1):\n";
    $interval = <STDIN>;
    chomp($interval);
    if($interval =~/^$/){
        print "增加间隔设置为为默认值64\n";
        $interval = 1;
    }
    else{
        while(!($interval =~ /\d+/)){
            print "输入错误,输入增加间隔:\n";
            $interval = <STDIN>;
            chomp($interval);
        }
    }
}
print "\n","-" x 20,"\n";

#--------------------MAC地址产生策略---------------------------
print "MAC地址产生策略:自增\n";
print "输入起始MAC地址(默认001122334455,直接回车选择默认):";
$base_mac = <STDIN>;
chomp($base_mac);
if($base_mac =~/^$/){
    print "起始MAC地址设置为默认001122334455\n";
    $base_mac = "001122334455";
}
else{
    #正则表达式判断输入格式是否正确
    while(!($base_mac =~ /[0-9a-fA-F]{12}/)){
        print "错误的输入,重新输入起始MAC地址(如001122334455):\n";
        $base_mac = <STDIN>;
        chomp($base_mac);
    }
}
print "\n","-" x 20,"\n";

#--------------------数据填充策略------------------------------
print "填充数据产生策略:(1或2,直接回车选择默认2伪随机):\n";
print "1. 固定\n";
print "2. 伪随机\n";
$fill_data_type = <STDIN>;
chomp($fill_data_type);
if($fill_data_type =~ /^$/){
    $fill_data_type = 2;
}
else{
    while($fill_data_type>3){
        print "错误的输入,选择填充数据产生策略(1或2):\n";
        $fill_data_type = <STDIN>;
        chomp($fill_data_type);
    }
}
print "\n","-" x 20,"\n";

my $fixed_fill_data;
if($fill_data_type==1){
    print "\n选择了固定数据填充...\n";
    print "输入填充数据(16位,例:aabb):";
    $fixed_fill_data = <STDIN>;
    chomp($fixed_fill_data);
}

#--------------------是否先生成ARP帧------------------------------
$gene_arp = "y";#默认生成ARP帧
print "是否先生成ARP帧(y/n,直接回车默认y生成)?";
$gene_arp = <STDIN>;
chomp($gene_arp);
if($gene_arp =~ /^$/){
    print "选择默认生成ARP帧\n";
    $gene_arp = "y";
}
else{
    while(!($gene_arp =~ /^[ynYN]$/)){
        #输入错误
        print "输入错误,是否生成ARP帧(y/n)\n";
        $gene_arp = <STDIN>;
        chomp($gene_arp);
    }
}

#########################################################################

#以太网头部
my $ethernet_header;
#以太网总帧长
my $frame_length;


#ARP帧长42字节=14字节以太网头部+28字节ARP头部
#每一帧都必须加入填充0以达到以太网的最小长度要求:60字节
my $arp_frame_length = 64;
#当前行号
my $current_row_str;
my $current_row = 0;
my $current_mac = $base_mac;

if($gene_arp eq "y" || $gene_arp eq "Y"){
    
    my $ethernet_type;
    my $mac_dst;
    my $mac_src;
    print "\n";
    #生成ARP帧(数目与设备数目相同)
    for($i=0;$i<$mac_cnt;$i++){

        print $i,"----ARP数据帧----\n";
    
        #----------------------
        $ethernet_type = "0806";
        #目的MAC地址0xFFFFFFFFFFFF
        $mac_dst = "ffffffffffff";
        #源MAC地址
        #print "base_mac is $base_mac\n";
        #print "base_mac hex is $base_mac".hex($base_mac)."\n";
        $mac_src = $current_mac;
        $current_mac = inc_mac($current_mac);
        #print $mac_src,"\n";
        $ethernet_header = $mac_dst.$mac_src.$ethernet_type;
        
        #------------------------------------------------------------------------------------
        #ARP帧头部
        #[2B硬件类型|2B协议类型|1B硬件地址长度|1B协议地址长度|2BOP|6B发送者硬件地址
        # |4B发送者IP|6B目标硬件地址|4B目标IP地址]
        my $hardware_type = dec2hexStr(0x0001,4);
        my $protocal_type = dec2hexStr(0x0800,4);
        my $hardware_size = dec2hexStr(0x06,2);
        my $protocal_size = dec2hexStr(0x04,2);
        my $op = dec2hexStr(0x0001,4);
        my $sender_mac = $mac_src;
        my $sender_ip = dec2hexStr(192,2).dec2hexStr(168,2).dec2hexStr(0,2).dec2hexStr($i,2);
        my $target_mac = dec2hexStr(0x00_00_00_00_00_00,12);
        my $target_ip = dec2hexStr(192,2).dec2hexStr(168,2).dec2hexStr(0,2).dec2hexStr($i+1,2);
        
        if($debug==1){
             print "length of hardware_type $hardware_type is ".length($hardware_type)."\n";
             print "length of protocal_type $protocal_type is ".length($protocal_type)."\n";
             print "length of hardware_size $hardware_size is ".length($hardware_size)."\n";
             print "length of protocal_size $protocal_size is ".length($protocal_size)."\n";
             print "length of op $op is ".length($op)."\n";
             print "length of sender_mac $sender_mac is ".length($sender_mac)."\n";
             print "length of sender_ip  $sender_ip is ".length($sender_ip)."\n";
             print "length of target_mac $target_mac is ".length($target_mac)."\n";
             print "length of target_ip  $target_ip is ".length($target_ip)."\n";
        }
        
        #------------------------------------------------------------------------------------
        my $data_size = int($arp_frame_length - 58);#填充的字节数
        my $payload .= "00" x $data_size;
        
        #------------------------------------------------------------------------------------
        #添加源设备号(MAC地址编号,32位)
        $device_no = dec2hexStr($i,8);
        #添加帧编号(32位)
        $current_row = $i;
        $current_row_str = dec2hexStr($i,8);
        
        #------------------------------------------------------------------------------------
        #ARP协议头部+数据
        $arp_protocal_head =  $hardware_type
                             .$protocal_type
                             .$hardware_size
                             .$protocal_size
                             .$op
                             .$sender_mac
                             .$sender_ip
                             .$target_mac
                             .$target_ip;
        
        $arp_protocal_data =  $payload
                             .$device_no
                             .$current_row_str;
                             
        $arp_protocal_all =  $arp_protocal_head.$arp_protocal_data;
        if($debug==1){
            print "length of arp_protocal_head is ".length($arp_protocal_head)."\n";
        }
        
        #添加CRC校验值
        $crc = getCRC32($arp_protocal_all);
        
        print FRAME_FILE $ethernet_header.$arp_protocal_all.$crc,"\n";
        print FRAME_HEADER_FILE "$mac_dst,$mac_src,$ethernet_type,$arp_protocal_head,$device_no,$current_row_str,$crc\n";
        print FRAME_LENGTH_FILE ($arp_frame_length-$ETHERNET_CRC_LENGTH)."\n";
    }
}

#########################################################################
#产生IP帧

$current_mac = $base_mac;
srand;
for($i=0;$i<$row_cnt;$i++){

    print $i,"----IP数据帧----\n";

    if($frame_length_type==1){
        #帧长自增
        $frame_length = $base_frame_length + $interval*$i;
        if($frame_length>$ETHERNET_MAX_LENGTH){
            $frame_length = $ETHERNET_MAX_LENGTH;
        }
    }
    else{
        #产生64-1518的随机数
        $frame_length = $ETHERNET_MIN_LENGTH + int(rand($ETHERNET_MAX_LENGTH-$ETHERNET_MIN_LENGTH));
    }

    #my $mac_src_ip = hex($base_mac)+$i%$mac_cnt;
    #源MAC地址 (自增)
    my $mac_src = $current_mac;
    #目的MAC地址(设定为下一条数据的源MAC)
    my $mac_dst = inc_mac($current_mac);
    $current_mac = $mac_dst;
    if(($i%$mac_cnt) == ($mac_cnt-1)){
        $mac_dst = $base_mac;
        $current_mac = $base_mac;
    }
    
    #------------------------------------------------------------------------------------
    
    #IP帧 TCP/UDP
    $ethernet_type = "0800";
    $ethernet_header = $mac_dst.$mac_src.$ethernet_type;
    
    #------------------------------------------------------------------------------------
    
    #【IP头部与载荷格式】
    #版本号:0100表示IPV4,0110表示IPV6.
    #[4位版本号|4位首部长度|8位服务类型|16位总字节数      ]
    #[16位标示                         |3位标志|13位片偏移]
    #[8位生存时间          |8位协议    |16位首部校验和    ]
    #[32位源IP地址                                        ]
    #[32位目的IP地址                                      ]
    #[16位源端口号                     |16位目的端口号    ]###开始TCP协议
    #[数据和填充]
    #[4字节数据CRC校验值]
    
    #---------------------------IP固定头部20字节-----------------------------------------
    #默认ipv4(4位)
    $ip_version = dec2hexStr(0b0100,1);
    #首部长度(4位,以4字节为单位,默认为5)
    $head_length = dec2hexStr(0b0101,1);
    #服务类型(8位,不用)
    $service_type = dec2hexStr(0b00000000,2);
    #首部和数据总字节数 = 帧长-18(16位)
    $total_bytes = dec2hexStr($frame_length-18,4);
    #标示
    $identification = dec2hexStr(0b0000_0000_0000_0000,4);
    #标志(不分片),偏移为0
    $flag_offset = dec2hexStr(0b010_0_0000_0000_0000,4);
    #生存时间(128)
    $ttl = dec2hexStr(128,2);
    #8位协议(默认TCP)
    $protocol = dec2hexStr(0x06,2);
    #首部校验和(不计算)
    $head_checksum = dec2hexStr(0x0000,4);
    #IP地址192.168.0.1/192.168.0.2
    $src_ip = dec2hexStr(192,2).dec2hexStr(168,2).dec2hexStr(0,2).dec2hexStr($i%$mac_cnt,2);
    #print $src_ip,"---源\n";
    $dst_ip = dec2hexStr(192,2).dec2hexStr(168,2).dec2hexStr(0,2).dec2hexStr($i%$mac_cnt+1,2);
    #print $dst_ip,"---目的\n";
    #------------------------------------------------------------------------------------
    
    #---------------------------TCP协议头部----------------------------------------------
    $src_port = dec2hexStr(100,4);#源端口
    $dst_port = dec2hexStr(80,4); #目的端口

    #------------------------------------------------------------------------------------
    #TCP协议其他头部和数据
    my $payload;
    my $data_size = int(($frame_length - 54)/2);#2字节的总数
    if($fill_data_type == 1){
        #固定填充
        for(1..$data_size){
            $payload .= $fixed_fill_data;
        }
    }
    else{
        #伪随机填充
        $last_data = 0xABCD;
        $payload = dec2hexStr($last_data,4);
        for(2..$data_size){
            $current_data = (~((($last_data & 0b0001_0000_0000_0000)<<3)^(($last_data & 0b0000_0000_0000_1000)<<12)^(($last_data & 0b0000_0000_0000_0010)<<14)^(($last_data & 0b0000_0000_0000_0001)<<15)))&(0b1000_0000_0000_0000)|(($last_data & 0b1111_1111_1111_1110)>>1);
            $payload .= dec2hexStr($current_data,4);
            #print dec2hexStr($current_data,4),"\n";
            $last_data = $current_data;
        }
        if(($frame_length - 50)%2==1){
            #还剩单独8bit
            $current_data = (~((($last_data & 0b0001_0000_0000_0000)<<3)^(($last_data & 0b0000_0000_0000_1000)<<12)^(($last_data & 0b0000_0000_0000_0010)<<14)^(($last_data & 0b0000_0000_0000_0001)<<15)))&(0b1000_0000_0000_0000)|(($last_data & 0b1111_1111_1111_1110)>>1);
            $payload .= substr(dec2hexStr($current_data,4),2,2);#取低8位
            #print substr(dec2hexStr($current_data,4),2,2),"\n";
        }
    }
    
    #------------------------------------------------------------------------------------
    #添加源设备号(MAC地址编号,32位)
    $device_no = dec2hexStr($i%$mac_cnt,8);
    #添加行号(32位)
    $current_row = $current_row + 1;
    $current_row_str = dec2hexStr($current_row,8);
    #------------------------------------------------------------------------------------
    
    #IP协议头部+数据
    $ip_protocal_head =  $ip_version
                        .$head_length
                        .$service_type
                        .$total_bytes
                        .$identification
                        .$flag_offset
                        .$ttl
                        .$protocol
                        .$head_checksum
                        .$src_ip
                        .$dst_ip;
                        
    $ip_protocal_data =  $src_port
                        .$dst_port
                        .$payload
                        .$device_no
                        .$current_row_str;
                        
    $ip_protocal_all =  $ip_protocal_head.$ip_protocal_data;
    
    #添加CRC校验值
    $crc = getCRC32($ip_protocal_data);        
    
    print FRAME_FILE $ethernet_header.$ip_protocal_all.$crc,"\n";
    print FRAME_HEADER_FILE "$mac_dst,$mac_src,$ethernet_type,$ip_protocal_head,$device_no,$current_row_str,$crc\n";
    print FRAME_LENGTH_FILE ($frame_length-$ETHERNET_CRC_LENGTH)."\n";
}

#########################################################################

close FRAME_FILE;
close FRAME_LENGTH_FILE;
close FRAME_HEADER_FILE;
print "\n--------------运行结束------------\n";
print "----输出文件为 $frame_file\n";
print "               $frame_header_file\n";
print "               $frame_length_file\n";

##############################子函数######################################

#十进制转16进制
sub dec2hexStr{
    my $fix_wdth = 0;
    if(@_ > 1)
    {
        $fix_wdth = 1;
    }
    my $data = shift @_;
    my $data16 = sprintf("%x", $data); 
    my $width = shift @_;
    if($fix_wdth==1)
    {
        if(length($data16)<$width)
        {
            $data16 = ("0" x ($width-length($data16))).$data16;
        }
    }
    return $data16;
}

#计算CRC校验值
sub getCRC32{
    $data = shift @_;
    $str = readpipe("./crc/exe/crc.exe $data");
    $str = substr($str,4,8);
    $str1 = substr($str,0,2);
    $str2 = substr($str,2,2);
    $str3 = substr($str,4,2);
    $str4 = substr($str,6,2);
    $crc = $str4.$str3.$str2.$str1;
    return $crc;
}

#mac地址加一
sub inc_mac{
    my $str = shift @_;
    my $mac_first_4 = substr($str,0,4);
    #printf("0x%x\n",hex($mac_first_4)+1);
   
    my $mac_last_8 = substr($str,4,8);
    if($mac_last_8 =~ /[fF]{8}/){
        $mac_first_4 = dec2hexStr(hex($mac_first_4)+1,4);
        #print $mac_first_4,"\n";
        return $mac_first_4."00000000";
    }
    else{
        $mac_last_8 = dec2hexStr(hex($mac_last_8)+1,8);
        return $mac_first_4.$mac_last_8;
    }
}

对发送和接收的文件进行对比的脚本:

#######################################################################################
# compare_file.pl                                                                     #
# 段聪                                                                                #
# 2014/12/5-2014/12/8                                                                 #
# 说明:                                                                              #
#     对比发送的数据和接收的数据文件                                                  #
#                                                                                     #
#                                                                                     #
#######################################################################################

print "输入接收文件名称:";

$file_send = "./output/frame.txt";
$file_receive = <STDIN>;#"./output/frame_receive.txt";

open(SEND,$file_send) or die "打开文件 $file_send 失败\n";
open(RECEIVE,$file_receive) or die "打开文件 $file_receive 失败\n";

#-------------------------------------------------------------------

print "\n","-" x 10,"开始对比","-" x 10,"\n";


#对接收文件按照发送编号进行排序
my %send_hash;
my $send_no_str;
my $send_no;
my $row = 0;
#接收文件
while($line = <RECEIVE>){
    chomp($line);
    $row++;
    $send_no_str = substr($line,length($line)-16,8);
    $send_hash{$send_no_str} = $row.",".$line;
    #print $row.",".$line,"\n";
}

#按键值排序
my @keys_sort = sort keys %send_hash;

#读取发送文件
print "\n";
$row = 0;
my $receive_line;
my @x,@y;
while($line = <SEND>){
    chomp($line);
    $row++;
    
    $send_no_str = substr($line,length($line)-16,8);
    $receive_all = $send_hash{$send_no_str};
    @receive_line_with_row = split ',',$receive_all;
    $receive_line_row = $receive_line_with_row[0];
    $receive_line = $receive_line_with_row[1];
    
    if($line ne $receive_line){
        #查找不同的地方
        @x = split '', $line;
        @y = split '', $receive_line;
        $result = join '',
                  map { $x[$_] eq $y[$_] ? "*" : $y[$_] }
                  0 .. $#y;
        print "发送文件第 $row 行与接收文件第 $receive_line_row 行不一致,发送编号$send_no\n";
        print "发送:$line\n";
        print "接收:$receive_line\n";
        print "差异:$result\n\n"
    }
    else{
        #print "--\n";
    }
}

#-------------------------------------------------------------------
close SEND;
close RECEIVE;

print "\n","-" x 10,"对比结束","-" x 10,"\n";


如果需要翻转字节顺序,可以使用下面的代码:

#######################################################################################
# file_reverse_bytes.pl                                                               #
# 段聪                                                                                #
# 2014/12/5-2014/12/8                                                                 #
# 说明:                                                                              #
#     对main.pl生成的帧数据文件frame.txt按字节进行反序                                #
#                                                                                     #
#                                                                                     #
#######################################################################################

$input_file = "./output/frame.txt";
$output_file = substr($input_file,0,length($input_file)-4)."_reverse.txt";

open(INPUT_FILE,"<",$input_file) or die "打开文件 $input_file 失败\n";
open(OUTPUT_FILE,">",$output_file) or die "打开文件 $output_file 失败\n";

print "\n","*"x 30,"\n\n";
print "\n----开始反序处理----\n\n";
$cnt = 0;

while($line = <INPUT_FILE>){
    $cnt++;
    print "正在处理第 $cnt 行...\n";
    chomp($line);
    #遍历每一行进行处理
    print OUTPUT_FILE reverse_line($line)."\n";
}

close INPUT_FILE;
close OUTPUT_FILE;

print "\n----处理结束----\n";
print "----输出文件为 $output_file\n\n";

sub reverse_line{
    $str = shift @_;
    $str_reverse = reverse $str;
    my $str_result;
    for($i=0;$i<length($str)-1;$i+=2){
        $str_result.= reverse substr($str_reverse,$i,2);
    }
    return $str_result;
}

输出文件数据截图如下:




                
相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页