2 * Mausezahn - A fast versatile traffic generator
3 * Copyright (C) 2008-2010 Herbert Haas
5 * This program is free software; you can redistribute it and/or modify it under
6 * the terms of the GNU General Public License version 2 as published by the
7 * Free Software Foundation.
9 * This program is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
14 * You should have received a copy of the GNU General Public License along with
15 * this program; if not, see http://www.gnu.org/licenses/gpl-2.0.html
20 ///////////////////////////////////////////////////
31 ///////////////////////////////////////////////////
33 // Documentation about RTP traffic analysis
35 // See http://wiki.wireshark.org/RTP_statistics
42 static enum rtp_display_mode
{
50 static struct mz_timestamp
51 timeTX
[TIME_COUNT_MAX
],
52 timeRX
[TIME_COUNT_MAX
];
55 drop
, // packet drop count
56 dis
, // packet disorder count
57 gtotal
; // counts number of file write cycles (see "got_rtp_packet()")
59 static char rtp_filter_str
[64];
61 // Initialize the rcv_rtp process: Read user parameters and initialize globals
62 int rcv_rtp_init(void)
64 char argval
[MAX_PAYLOAD_SIZE
];
67 u_int32_t port
= 30000; // 4-byte variable to catch errors, see below
73 if (getarg(tx
.arg_string
,"help", NULL
)==1) {
77 "| RTP reception for jitter measurements.\n"
81 "| bar ...... Display modes: By default 'bar' is used and shows the RFC 3550 jitter as\n"
82 "| ASCII-based waterfall diagram.\n"
83 "| txt ...... The 'txt' mode prints all measurement values numerically upon each\n"
84 "| measurement interval.\n"
85 // "| curse ...... Shows all values and a diagram within an resizesable ncurses window.\n"
87 "| ssrc ....... Listen to the stream with the specified SSRC. You must specify this\n"
88 "| when there are concurrent streams, e. g. one in each direction.\n"
90 "| log ....... Write moving average also in a datafile (not only on terminal).\n"
91 "| logg ....... Like log but additionally write detailed real-time statistics in a data file\n"
92 "| path = <path> ....... Path to directory where datafiles can be stored (default: local directory).\n"
93 "| num = <10-%d> ...... number of packets to be received for averaging (default: %d).\n"
94 "| port = <0-65535> ....... Change if RTP packets are sent to a different port than 30000 (default).\n"
98 "| Mausezahn can log actual realtime measurement data in data files (in the specified path or\n"
99 "| current directory) but always prints the moving average on the command line (this can be disabled\n"
100 "| using the 'quiet' option (-q)).\n"
102 "| The realtime data file(s) consist of two columns:\n"
104 "| 1. relative timestamp in usec\n"
105 "| 2. 'true' jitter in usec\n"
107 "| where the 'true' jitter is calculated using the (relative) timestamps inside the received\n"
108 "| packets t(i) and the (relative) timestamps T(i) observed locally when packets are received using\n"
111 "| jitter(i) = [T(i) - T(i-1)] - [t(i) - t(i-1)] + jitter(i-1) .\n"
113 "| This method has two advantages: (i) we do not need to synchronize the clocks of sender and\n"
114 "| receiver, and (ii) the TX-side jitter (mainly caused by the kernel-scheduler) is subtracted\n"
115 "| so that we primarily measure the jitter caused by the network.\n"
117 "| The data files consist of seven columns:\n"
119 "| 1. relative timestamp in seconds\n"
120 "| 2. minimum jitter\n"
121 "| 3. average jitter\n"
122 "| 4. minimum jitter\n"
123 "| 5. estimated jitter variance according RFC-3550\n"
124 "| 6. packet drop count (total)\n"
125 "| 7. packet disorder count (total)\n"
127 "| All measurement values are done in usec and refer to the current set of samples (see parameter 'num').\n"
128 "| Note that an RFC-conform jitter (smoothed mean deviation) is calculated and collected in column five.\n"
129 "| The drop value refers to the current measurement window, while the total drop and disorder values are\n"
130 "| calculated using some weird estimation functions; the goal was to provide a 'time-less' estimation\n"
131 "| while being able to automatically resynchronize to a re-started RTP measurement stream.\n"
135 "| At the TX-station enter:\n"
137 "| # mz eth0 -t rtp -B 10.3.3.42 (optionally change rate via -d option, payload size via pld command)\n"
139 "| At the RX-station (10.3.3.42) enter:\n"
141 "| # mz eth0 -T rtp \"log, path=/tmp/mz/\"\n"
143 "\n", TIME_COUNT_MAX
, TIME_COUNT
);
148 // check argstring for arguments
150 if (getarg(tx
.arg_string
,"bar", NULL
)==1) {
154 if (getarg(tx
.arg_string
,"txt", NULL
)==1) {
158 if (getarg(tx
.arg_string
,"curses", NULL
)==1) {
159 rtp_dm
= BAR
; //NCURSES;
160 fprintf(stderr
, " XXX This Mausezahn version does not support ncurses windows.\n");
163 if (getarg(tx
.arg_string
,"width", argval
)==1) {
165 fprintf(stderr
, " mz/rcv_rtp: The 'width' parameter requires the display mode 'bar'\n");
168 bwidth
= (int) str2int(argval
); // [TODO] bwidth is currently not used
169 if (bwidth
>RCV_RTP_MAX_BAR_WIDTH
) {
170 fprintf(stderr
, "The width must not exceed %i\n",
171 RCV_RTP_MAX_BAR_WIDTH
);
176 if (getarg(tx
.arg_string
,"ssrc", argval
)==1) {
177 ssrc_s
= str2hex(argval
, mz_ssrc
, 4);
179 fprintf(stderr
, " mz/rtp_rcv: invalid ssrc!\n");
184 if (getarg(tx
.arg_string
,"log", NULL
)==1) {
188 if (getarg(tx
.arg_string
,"logg", NULL
)==1) {
193 if (getarg(tx
.arg_string
,"path", argval
)==1) {
194 len
= strlen(argval
);
196 fprintf(stderr
, " mz/Error: path must not exceed 128 characters!\n");
199 if (argval
[len
-1]!='/') {
200 strncat(argval
, "/",1); // ensure that all paths end with "/"
202 strncpy(path
, argval
, 128);
206 if (getarg(tx
.arg_string
,"num", argval
)==1) {
207 gind_max
= (u_int32_t
) str2int(argval
);
208 if (gind_max
> TIME_COUNT_MAX
) {
209 gind_max
= TIME_COUNT_MAX
;
210 fprintf(stderr
, " mz/Warning: num range is 10..%d. Will reset to %d.\n",
211 TIME_COUNT_MAX
, TIME_COUNT_MAX
);
213 else if (gind_max
< 10) {
215 fprintf(stderr
, " mz/Warning: num range is 10..%d. Will reset to 10.\n",
221 // initialize global filter string
222 strncpy (rtp_filter_str
, "udp dst port 30000", 64);
224 if (getarg(tx
.arg_string
,"port", argval
)==1) {
225 port
= (u_int32_t
) str2int(argval
);
228 fprintf(stderr
, " mz: Too large port number! Reset to default port (30000).\n");
231 sprintf(rtp_filter_str
, "udp dst port %u", (unsigned int) port
);
235 if (ssrc_s
==0) str2hex("ca:fe:fe:ed", mz_ssrc
, 4);
240 // get a new filename
241 timestamp_human(filename
, "rtp_avg_");
242 strncpy(dummy
, path
, 128);
243 strncat(dummy
, filename
, 64);
244 if (verbose
) fprintf(stderr
, " mz: Will open %s\n", dummy
);
246 fp
= fopen (dummy
, "w+");
253 gtotal
=0; // counts written data blocks
254 fprintf(fp
, "# Average jitter measurements made by Mausezahn " MAUSEZAHN_VERSION_SHORT
".\n");
255 fprintf(fp
, "# Timestamp is in seconds, all other values in microseconds.\n");
256 fprintf(fp
, "# Column values (from left to right):\n");
257 fprintf(fp
, "# 1. Timestamp\n"
261 "# 5. estimated jitter according RFC-3550\n"
262 "# 6. packet drop count (total)\n"
263 "# 7. packet disorder count (total)\n");
266 ///////////// also detailed log required /////////////
268 // get a new filename
269 timestamp_human(filename
, "rtp_rt_");
270 strncpy(dummy
, path
, 128);
271 strncat(dummy
, filename
, 64);
272 if (verbose
) fprintf(stderr
, " mz: Will open %s\n", dummy
);
274 fp2
= fopen (dummy
, "w+");
281 fprintf(fp2
, "# Jitter measurements by Mausezahn " MAUSEZAHN_VERSION_SHORT
".\n");
282 fprintf(fp2
, "# Timestamp (usec) , true jitter (nsec)\n");
300 ////////////////////////////////////////////////////////////////////////////////////////////
302 // Defines the pcap handler and the callback function
305 char errbuf
[PCAP_ERRBUF_SIZE
];
309 struct bpf_program filter
;
313 p
= pcap_open_live (tx
.device
,
314 MAXBYTES_TO_READ
, // max num of bytes to read
315 0, // 1 if promiscuous mode
316 PCAP_READ_TIMEOUT_MSEC
, // read timeout in msec
321 fprintf(stderr
," mz/rcv_rtp: %s\n",errbuf
);
327 &filter
, // the compiled version of the filter
328 rtp_filter_str
, // text version of filter
333 fprintf(stderr
," mz/rcv_rtp: Error calling pcap_compile\n");
339 if ( pcap_setfilter(p
, &filter
) == -1)
341 fprintf(stderr
," mz/rcv_rtp: Error setting filter\n");
350 1, // number of packets to wait
351 got_rtp_packet
, // name of callback function
352 NULL
); // optional additional arguments for callback function
358 // TODO: Currently we never reach this point!
359 fprintf(stderr
, " mz: receiving of RTP finished.\n");
368 // Compares two 4-byte variables byte by byte
369 // returns 0 if identical, 1 if different
370 inline int compare4B (u_int8_t
*ip1
, u_int8_t
*ip2
)
372 if (*ip1
!= *ip2
) return 1;
373 if (*(ip1
+1) != *(ip2
+1)) return 1;
374 if (*(ip1
+2) != *(ip2
+2)) return 1;
375 if (*(ip1
+3) != *(ip2
+3)) return 1;
384 // Handler function to do something when RTP messages are received
385 void got_rtp_packet(u_char
*args
,
386 const struct pcap_pkthdr
*header
, // statistics about the packet (see 'struct pcap_pkthdr')
387 const u_char
*packet
) // the bytestring sniffed
389 const struct struct_ethernet
*ethernet
;
390 const struct struct_ip
*ip
;
391 const struct struct_udp
*udp
;
392 const struct struct_rtp
*rtp
;
394 int size_ethernet
= sizeof(struct struct_ethernet
);
395 int size_ip
= sizeof(struct struct_ip
);
396 int size_udp
= sizeof(struct struct_udp
);
397 // int size_rtp = sizeof(struct struct_rtp);
399 ethernet
= (struct struct_ethernet
*)(packet
);
400 ip
= (struct struct_ip
*)(packet
+size_ethernet
);
401 udp
= (struct struct_udp
*)(packet
+size_ethernet
+size_ip
);
402 rtp
= (struct struct_rtp
*)(packet
+size_ethernet
+size_ip
+size_udp
);
423 static u_int32_t drop_last
=0, drop_prev
=0;
426 // check if the RTP packet is really from a Mausezahn instance:
427 if (compare4B((u_int8_t
*) &rtp
->ssrc
, mz_ssrc
)==0) {
428 // we got a valid RTP packet from a Mausezahn instance
429 // Get current SQNR and store it in 'sqnr_cur' in host byte order
430 x
= (u_int8_t
*) &rtp
->sqnr
;
431 y
= (u_int8_t
*) &sqnr_cur
;
437 /////////////////////////////////////////////////////////////////////
438 // Packet drop and disorder detection:
440 if (sqnr_next
==sqnr_cur
) { // correct SQNR received
443 } else if (sqnr_last
>sqnr_cur
) { // disordered sequence
445 if (drop
) drop
--; // don't get below 0
446 else { // drop reached zero: resync (restarted RTP stream?)
447 sqnr_last
= sqnr_cur
;
448 sqnr_next
= (++sqnr_last
);
451 } else { // packet drop
452 drop
+= (sqnr_cur
-sqnr_next
);
453 sqnr_last
= sqnr_cur
;
454 sqnr_next
= (++sqnr_last
);
457 // initial synchronization with observed SQNR:
458 sqnr_last
= sqnr_cur
;
459 sqnr_next
= (++sqnr_last
);
463 /////////////////////////////////////////////////////////////////////
466 // Get RX timestamp from pcap header
467 timeRX
[gind
].sec
= header
->ts
.tv_sec
;
468 timeRX
[gind
].nsec
= header
->ts
.tv_usec
*1000;
470 // Get TX timestamp from the packet
471 mops_hton4((u_int32_t
*) &rtp
->time_sec
, (u_int8_t
*) &timeTX
[gind
].sec
);
472 mops_hton4((u_int32_t
*) &rtp
->time_nsec
, (u_int8_t
*) &timeTX
[gind
].nsec
);
474 // printf("%li %li\n", (long int) timeTX[gind].sec, (long int) timeTX[gind].nsec);
478 ////////////////////////////////////////////////////////////////
479 if (gind
== gind_max
) { // array full, now calculate statistics
484 jitter_min
= 0xffffffff;
488 ///////////////////////////////////////////////////////
489 // calculate deltas and jitters
490 for (i
=2; i
<gind_max
; i
++) { // omit the first 2 data
491 // entries because of
492 // artificial high TX-delta!
494 ///////////////////////////////////////////////
495 // calculate deltaTX and deltaRX
497 s1
=timestamp_subtract (&timeTX
[i
], &timeTX
[i
-1], &deltaTX
);
498 s2
=timestamp_subtract (&timeRX
[i
], &timeRX
[i
-1], &deltaRX
);
499 if (s1
) fprintf(stderr
, " *** ***\n");
501 // Then calculate the precise jitter by considering
502 // also TX-jitter: (pseudo)jitter = deltaRX - deltaTX,
503 // hence we have positive and negative jitter (delay
504 // deviations) jitter entries are in +/- nanoseconds
505 jitter
[i
] = (deltaRX
.sec
*1000000000L + deltaRX
.nsec
)
506 - (deltaTX
.sec
*1000000000L + deltaTX
.nsec
);
507 // Calculate RFC 3550 jitter estimation. According to
508 // that RFC the jitter should be measured in timestamp
509 // units; however currently Mausezahn uses nanoseconds.
510 // (If we want to solve this: G.711 timestamp units are
511 // 125 usec, so jitter/=125 would be sufficient, AFAIK)
512 ltemp
= labs(jitter
[i
]) - jitter_rfc
;
513 jitter_rfc
+= (ltemp
>>4);
514 // Add previous pseudojitter to get the true jitter
515 // (See Documentation!)
516 jitter
[i
] += jitter
[i
-1];
518 ////////////////////////////////////////////////
523 ////////////////////////////////////////////////
524 // Determine avg, min, and max jitter within this time frame:
525 jitter_abs
= labs(jitter
[i
]);
526 jitter_avg
+= jitter_abs
;
527 if (jitter_abs
< jitter_min
) jitter_min
= jitter_abs
;
528 if (jitter_abs
> jitter_max
) jitter_max
= jitter_abs
;
530 ////////////////////////////////
532 /// PRINT IN FILE_2: Detailed jitter data ///
534 // Calculate relative timestamp for column 1 of the datafile
535 curtime
= timeRX
[i
].sec
*1000000+timeRX
[i
].nsec
/1000;
537 curtime
= curtime
- time0
;
538 } else { // this is only done once during the Mausezahn process
541 curtime
= curtime
- time0
;
543 fprintf(fp2
, "%lu, %li\n",
544 (long unsigned int) curtime
,
545 (long int) jitter
[i
]);
546 fflush(fp2
); // save everything immediately
547 // (CHECK if fsync() is additionally needed)
549 } // end for (i=2; i<gind_max; i++)
551 ////////////////////////////////////////////////////////
554 jitter_avg
= jitter_avg
/ (gind_max
-2); // average true jitter, always positive
556 if (drop
>=drop_prev
) { // because the total drop count may decrease(!) if disordered packets appear lately
557 drop_last
= drop
- drop_prev
;
561 // PRINT ON CLI: statistics data
564 dum
= (unsigned char*) &ip
->src
;
566 "Got %u packets from host %u.%u.%u.%u: %lu lost (%lu absolute lost, %lu out of order)\n"
567 " Jitter_RFC (low pass filtered) = %li usec\n"
568 " Samples jitter (min/avg/max) = %lu/%lu/%lu usec\n",
570 *(dum
),*(dum
+1),*(dum
+2),*(dum
+3),
571 (long unsigned int) drop_last
,
572 (long unsigned int) drop
,
573 (long unsigned int) dis
,
574 (long int) jitter_rfc
/1000,
575 (long unsigned int) jitter_min
/1000,
576 (long unsigned int) jitter_avg
/1000,
577 (long unsigned int) jitter_max
/1000);
581 print_jitterbar(jitter_rfc
/1000, drop_last
);
584 case NCURSES
: // would be nice...?
591 // Determine whether some packets got lost:
599 /// PRINT IN FILE_1: statistics only ///
602 timestamp_hms (ts_hms
);
604 "%s, %lu, %lu, %lu, %li, %u, %u\n",
606 (long unsigned int) jitter_min
/1000,
607 (long unsigned int) jitter_avg
/1000,
608 (long unsigned int) jitter_max
/1000,
609 (long int) jitter_rfc
/1000,
617 // Open another file if current file reaches a limit
619 if ((rtp_log
==2) && (gtotal
>MAX_DATA_BLOCKS
)) { // file big enough,
621 if (fclose(fp2
) == EOF
) {
627 fprintf(stderr
, " mz: %s written.\n",filename
);
629 timestamp_human(filename
, "rtp_"); // get a new filename
630 strncpy(dummy
, path
, 128);
631 strncat(dummy
, filename
, 64);
633 if (verbose
) fprintf(stderr
, " mz: Will open %s\n", dummy
);
635 if ( (fp2
= fopen (dummy
, "w+")) == NULL
) {
636 if (errno
!= EAGAIN
) {
641 fprintf(fp2
, "# Jitter measurements by Mausezahn "
642 MAUSEZAHN_VERSION_SHORT
".\n");
643 fprintf(fp2
, "# Timestamp (usec) , true jitter (nsec)\n");
645 } // statistics end *********************************************************************
652 void print_jitterbar (long int j
, u_int32_t d
)
654 // Determine actual data window by considering two events:
656 // 1) window move (j exceeds lower or upper limit)
657 // 2) window rescale (window moves happen too often or the variance
658 // of successive data points is too small)
660 // The most critical value is the chosen resolution (window range),
661 // especially the _initial_ resolution.
663 static long int range
=0, min
=0, max
=0, minvar
=0, j0
=0, dj
=0;
664 static int moved
=0, varcount
=0, barcount
=0;
665 char str
[128], bar
[150],
666 str1
[8], str2
[8], str3
[8], str4
[8];
670 // Initialize vars (start with an opened window)
671 // Note that 'range' is actually half of the window
674 if (range
<500) range
=500;
680 dj
= labs(j
-j0
); // no initialization: calculate jitter delta
683 // Move window when borders crossed:
684 if ((j
<min
) || (j
>max
)) {
690 fprintf(stdout
, "\nNOTE: +- Rescaled window to %4.2f msec\n", (double) range
/500);
694 fprintf(stdout
,"\n");
695 // printf("move event: min=%li max=%li\n", min, max);
698 // printf("normal event: min=%li max=%li\n", min, max);
702 // Increase range when window moved 5 times in a row
705 if (range
>10000000L) range
=10000000L;
707 if (minvar
<1000) minvar
=1000;
716 // printf("scale up event: min=%li max=%li\n", min, max);
717 fprintf(stdout
, "\nNOTE: ++ Rescaled window to %4.2f msec\n", (double) range
/500);
721 // Decrease range when jitter deltas are smaller than minvar
730 if (range
>j
) range
=j
;
735 if (minvar
<1000) minvar
=1000;
742 fprintf(stdout
, "\nNOTE: -- Rescaled window to %4.2f msec\n", (double) range
/500);
745 // printf("scale down event: min=%li max=%li\n", min, max);
758 sprintf(str1
,"%4.2f", (double) min
/1000);
759 sprintf(str2
,"%4.2f", (double) (min
+tmp
)/1000);
760 sprintf(str3
,"%4.2f", (double) (max
-tmp
)/1000);
761 sprintf(str4
,"%4.2f", (double) max
/1000);
764 "%-6s %-6s %-6s %-6s\n"
765 "|-------------------------|-------------------------|-------------------------|\n",
766 str1
, str2
, str3
, str4
);
770 anz
= 80*(j
-min
)/(2*range
);
772 memset((void*) str
, '#', anz
);
773 memset((void*) str
+anz
, ' ', 80-anz
);
777 memset((void*) str
, ' ', 80);
782 sprintf(bar
, "%s%4.2f msec !%lu dropped!", str
, (double) j
/1000, (unsigned long int) d
);
784 sprintf(bar
, "%s%4.2f msec", str
, (double) j
/1000);
786 fprintf(stdout
,"%s\n", bar
);