tcpreplay 发包速率控制算法研究 | 您所在的位置:网站首页 › jsp72时间控制设置时间 › tcpreplay 发包速率控制算法研究 |
*/ void send_packets(pcap_t *pcap, int cache_file_idx) { struct timeval last = { 0, 0 }, last_print_time = { 0, 0 }, print_delta, now; COUNTER packetnum = 0; struct pcap_pkthdr pkthdr; /*包头控制结构*/ const u_char *pktdata = NULL;/*包身数据结构*/ sendpacket_t *sp = options.intf1;/* 发包子控制结构*/ u_int32_t pktlen; /*包长度*/ 。。。。。。。。。。。。。。。。 delta_t delta_ctx; init_delta_time(&delta_ctx);/*存放当前时间*/
didsig = 0; /*为ONEATATIME模式注册信号*/ if (options.speed.mode != SPEED_ONEATATIME) {/*注册信号*/ (void)signal(SIGINT, catcher); } else { (void)signal(SIGINT, break_now); } 。。。。。。。。。。。。。。。。。。。 /* 主循环 */ while ((pktdata = get_next_packet(pcap, &pkthdr, cache_file_idx, prev_packet)) != NULL) { /*为ONEATATIME模式注册信号*/ if (didsig) break_now(0); 。。。。。。。。。。。。。。。。 packetnum++; 。。。。。。。。。。。。。。。 if (options.speed.mode != SPEED_TOPSPEED) do_sleep((struct timeval *)&pkthdr.ts, &last, pktlen, options.accurate, sp, packetnum, &delta_ctx); /*各种速率控制的实现,在这个函数里完成*/
/* 获取当前时间*/ start_delta_time(&delta_ctx); /*真正的发包在这里,通过调用第三方库实现 */ if (sendpacket(sp, pktdata, pktlen) < (int)pktlen) warnx("Unable to send packet: %s", sendpacket_geterr(sp)); /*last变量存放上个packet的抓取时间*/ if (timercmp(&last, &pkthdr.ts, 0) { send --; return; } */ /* 下面是第一个packet的处理*/ if (options.speed.mode == SPEED_PACKETRATE && options.speed.pps_multi) { send = options.speed.pps_multi - 1; if (first_time) { first_time = 0; return; } } if (gettimeofday(&now, NULL) < 0) /* 下面是第一个packet的时间变量初始化*/ if (pkts_sent == 0 || ((options.speed.mode != SPEED_MBPSRATE) && (counter == 0))) { start = now; timerclear(&sleep_until); timerclear(&didsleep); } else { /*如果不是第一个packet,算出前面N-1个包使用的时间*/ timersub(&now, &start, &sleep_until); } /*下面根据不同模式算出用户指定速率换算成的时间*/ switch(options.speed.mode) { case SPEED_MULTIPLIER: /*以该packet抓取的时间的一定倍数去回放 */ if (timerisset(last)) { if (timercmp(time, last, 0 ? options.speed.pps_multi : 1); NANOSEC_TO_TIMESPEC(ppnsec, &nap); } break; case SPEED_ONEATATIME: /* 点击一下终端发送一个 packet */ /* do we skip prompting for a key press? */ if (send == 0) { send = get_user_count(sp, counter); }
/* decrement our send counter */ send -- return; /* leave do_sleep() */ break; default: /*不是上面任一模式,报错退出*/ errx(-1, "Unknown/supported speed mode: %d", options.speed.mode); break; } /*下面算 pps 模式下的微调时间,大概思路是将上面算出的睡眠时间变量的nsec 精度级别上进行微调,方法是与一个随机数比较,大于它则 nsec 部分取整并增加一个单位,否则取整*/ /* * since we apply the adjuster to the sleep time, we can't modify nap */ memcpy(&nap_this_time, &nap, sizeof(nap_this_time)); if (accurate != ACCURATE_ABS_TIME) { switch (options.speed.mode) { case SPEED_MBPSRATE: case SPEED_MULTIPLIER:/*这两种模式不微调*/ break; /* Packets/sec is static, so we weight packets for .1usec accuracy */ case SPEED_PACKETRATE: /*这种模式才进行微调*/ if (nsec_adjuster < 0) nsec_adjuster = (nap_this_time.tv_nsec % 10000) / 1000; /* update in the range of 0-9 */ nsec_times = (nsec_times + 1) % 10; if (nsec_times < nsec_adjuster) { /* sorta looks like a no-op, but gives us a nice round usec number */ nap_this_time.tv_nsec = (nap_this_time.tv_nsec / 1000 * 1000) + 1000; } else { nap_this_time.tv_nsec -= (nap_this_time.tv_nsec % 1000); } break; default: errx(-1, "Unknown/supported speed mode: %d", options.speed.mode); } }
/*下面获取系统在发第N-1个packet的使用时间,并与用户设置速率换算成 的时间对比做差,如果用户速率换算成的时间更大,则它们的差就是需要睡眠的时间*/ get_delta_time(delta_ctx, &delta_time);/*第N-1个包实发时间*/ if (timesisset(&delta_time)) { if (timescmp(&nap_this_time, &delta_time, >)) {/*比较实发时间和用户设置时间*/ timessub(&nap_this_time, &delta_time, &nap_this_time); } else { timesclear(&nap_this_time); } } /*根据用户指定速率算出睡眠时间后,别忘了还需要通过adjuster进行微调*/ if (timesisset(&adjuster)) { if (timescmp(&nap_this_time, &adjuster, >)) { timessub(&nap_this_time, &adjuster, &nap_this_time); } else { timesclear(&nap_this_time); } } /*下面根据用户参数指定的睡眠模式进行睡眠*/ if (!timesisset(&nap_this_time)) return; /* nap_this_time = {0, 0} 不睡眠,直接返回*/ switch (accurate) { /* 否则,根据accurate 进行睡眠 */ #ifdef HAVE_SELECT case ACCURATE_SELECT: select_sleep(nap_this_time); break; #endif #ifdef HAVE_IOPERM case ACCURATE_IOPORT: ioport_sleep(nap_this_time); break; #endif #ifdef HAVE_RDTSC case ACCURATE_RDTSC: rdtsc_sleep(nap_this_time); break; #endif #ifdef HAVE_ABSOLUTE_TIME case ACCURATE_ABS_TIME: absolute_time_sleep(nap_this_time); break; #endif case ACCURATE_GTOD: gettimeofday_sleep(nap_this_time); break; case ACCURATE_NANOSLEEP: nanosleep_sleep(nap_this_time); break; default: errx(-1, "Unknown timer mode %d", accurate); } } 从以上实现可以看出,所谓速率控制,在实现上转换成了时间控制,为了提高时间变量操作的精确度,引入了两种级别的变量 timeval 和 timespec, 并增加了微调机制。此外,提供了多种用于睡眠的函数,以供不同操作系统下使用最合适的睡眠方法。 1.1.1 实验结果 1.1.1.1 实验1 环境:10G 发包板 Packet ID:* packet数:12921 字节数:16973900 Top speed模式,实际速率 2000 M/s 左右 设置1024 M/s, 实发速率 760 M/s 左右 设置500 M/s, 实发速率 450 M/s 左右 设置100 M/s, 实发速率 99 M/s 左右 设置50 M/s, 实发速率 49 M/s 左右 设置10 M/s, 实发速率 9.6 M/s 左右 设置5 M/s, 实发速率 4.9 M/s 左右
1.1.1.2 实验2 环境:10G 发包板 ID: * 包数:4 字节: 1930 设置1024 M/s, 实发速率 14 M/s 左右 设置500 M/s, 实发速率 14 M/s 左右 设置100 M/s, 实发速率 16.9 M/s 左右 设置50 M/s, 实发速率 13 M/s 左右 设置10 M/s, 实发速率7.5 M/s 左右 设置5 M/s, 实发速率 1.8 M/s 左右 设置1 M/s, 实发速率 1.34 M/s 左右
1.1.1.3 实验3 环境:10G 发包板 ID: * 包数:1 字节: 34 设置1024 M/s, 实发速率 0.3 M/s 左右 设置100 M/s, 实发速率 0.27 M/s 左右 设置1 M/s, 实发速率 0.32 M/s 左右 1.1.1.4 实验结果分析 从实验结果可以看出,速率控制是否准确与pcap包本身的大小有密切关系,当pcap的大小过小时,速率控制算法失效,反之,pcap包很大时,速率控制算法非常准确。 造成以上现象的原因,与时间控制变量的精度有关。由于精度过大,当pcap非常小(实验2和3相对于实验1而言)的时候,换算成的时间结果的一些关键点会被忽略,导致结果非常不一致。改进的方法可以尝试将所有时间变量改成 timespec 的情况,但这样一来又有问题,大多数睡眠的实现都只支持timeval,对于timespec精度级别的无法支持。 1.1.2 tcpreplay速率控制改进历史 1.1.2.1 1.4.* 版本的改进 1.4.beta5 版本的时候,用了nanosleep函数替代了原先的sleep函数,提高精度 1.4.2 版本的时候,用了 timerdiv 函数,在 Multi 这种模式下,算UST的时候更精确了。 上面这两次修改着力点是一样的,就是原先处理一个timeval,是 sec 和 usec 两个精度分别处理,现在先将sec换成usec的精度来算,就是变量整个精度提高了1百万。这样的结果是,包的大小比较小时,速度控制会更精确。 1.1.2.2 3.0.* 版本的改进 3.0.beta10作者在这一版本想出了一个睡眠的实现方法,据说比nanosleep更精确,叫做sleep_loop Sleep_loop原理是,先gettimeofday获取系统时间t1,将你要睡眠的时间t2与t1相加得t,然后在一个循环里,每次循环取gettimeofday与t比较,小于t就接着循环,直到不小于t。其代价是CPU使用率更高(事实上,在睡眠的时候,CPU会达到100) 1.1.2.3 3.3.* 版本的改进 3.3.0 比较大的改动:一是使得时间变量的精度升高,从usec提高1000倍到了nsec, 二是睡眠函数的实现更丰富,针对不同的操作系统使用不同的睡眠函数。 时间变量精度提高使得在处理小包的时候当然更精确,附带问题是给睡眠造成难题,因为不是所有睡眠实现都支持这么高精度的时间。为了解决这个问题,作者设计了几个调整函数adjust,作用大体说来是将nsec调整为usec,比如,nsec>500,就直接在usec+1,小于500,则usec-1;另外,对于Pkts的情况,提出了另外一种类似的调整。 此外,针对不同OS提供不同的睡眠实现,可以让更多用户获得好的体验,无论用户使用什么系统。
1.1.3 发现该算法的一个问题 在实现速率控制Mbps时,tcpreplay 实际上是用第 N+1 个包来调整第 N 个包。假设前面N-1个包已经发送完,在发送第N个包前,根据算法,会根据 len(N) 算出一个时间t1,这个t1会加在N-2个包实际使用时间T上,假设第N-1个包的实际发送时间为t2,这样,发第N个包前,就有两个时间,一个是 T+t1,一个是T+t2,后者是前面N-1个包发送的时间,前者是根据Mbps设置前面N-1个包应该发送的时间,如果 t1>t2,就要通过睡眠 (t1-t2)来使得前面 N-1 个包的发送时间等于T+t1,也就是达到用户设定时间,然后,发送第N个包,在发送第N+1个包前又要调整前面N个包的发送时间,这时候运行调整时间的包是第N+1个包,也就是说,tcpreplay总是用下一个包的长度来调整前一个包的时间(仅限于Mbps模式)。
这有什么影响呢?目前的分析结果是:tcpreplay的速率控制算法,也即时间调整算法,其实是一个逐包调整的过程,相当于每一个包都会在整体调整中贡献力量(除了第一个包)。所以,上述问题的关键是第一个包与其余包的对比情况,如果第一个包远远大于其他的包,很可能导致实际速率大于设定的速率。反之,影响不大
|
CopyRight 2018-2019 实验室设备网 版权所有 |