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 // Initialize the rcv_rtp process: Read user parameters and initialize globals
45 char argval
[MAX_PAYLOAD_SIZE
];
48 u_int32_t port
= 30000; // 4-byte variable to catch errors, see below
54 if (getarg(tx
.arg_string
,"help", NULL
)==1) {
58 "| RTP reception for jitter measurements.\n"
62 "| bar ...... Display modes: By default 'bar' is used and shows the RFC 3550 jitter as\n"
63 "| ASCII-based waterfall diagram.\n"
64 "| txt ...... The 'txt' mode prints all measurement values numerically upon each\n"
65 "| measurement interval.\n"
66 // "| curse ...... Shows all values and a diagram within an resizesable ncurses window.\n"
68 "| ssrc ....... Listen to the stream with the specified SSRC. You must specify this\n"
69 "| when there are concurrent streams, e. g. one in each direction.\n"
71 "| log ....... Write moving average also in a datafile (not only on terminal).\n"
72 "| logg ....... Like log but additionally write detailed real-time statistics in a data file\n"
73 "| path = <path> ....... Path to directory where datafiles can be stored (default: local directory).\n"
74 "| num = <10-%d> ...... number of packets to be received for averaging (default: %d).\n"
75 "| port = <0-65535> ....... Change if RTP packets are sent to a different port than 30000 (default).\n"
79 "| Mausezahn can log actual realtime measurement data in data files (in the specified path or\n"
80 "| current directory) but always prints the moving average on the command line (this can be disabled\n"
81 "| using the 'quiet' option (-q)).\n"
83 "| The realtime data file(s) consist of two columns:\n"
85 "| 1. relative timestamp in usec\n"
86 "| 2. 'true' jitter in usec\n"
88 "| where the 'true' jitter is calculated using the (relative) timestamps inside the received\n"
89 "| packets t(i) and the (relative) timestamps T(i) observed locally when packets are received using\n"
92 "| jitter(i) = [T(i) - T(i-1)] - [t(i) - t(i-1)] + jitter(i-1) .\n"
94 "| This method has two advantages: (i) we do not need to synchronize the clocks of sender and\n"
95 "| receiver, and (ii) the TX-side jitter (mainly caused by the kernel-scheduler) is subtracted\n"
96 "| so that we primarily measure the jitter caused by the network.\n"
98 "| The data files consist of seven columns:\n"
100 "| 1. relative timestamp in seconds\n"
101 "| 2. minimum jitter\n"
102 "| 3. average jitter\n"
103 "| 4. minimum jitter\n"
104 "| 5. estimated jitter variance according RFC-3550\n"
105 "| 6. packet drop count (total)\n"
106 "| 7. packet disorder count (total)\n"
108 "| All measurement values are done in usec and refer to the current set of samples (see parameter 'num').\n"
109 "| Note that an RFC-conform jitter (smoothed mean deviation) is calculated and collected in column five.\n"
110 "| The drop value refers to the current measurement window, while the total drop and disorder values are\n"
111 "| calculated using some weird estimation functions; the goal was to provide a 'time-less' estimation\n"
112 "| while being able to automatically resynchronize to a re-started RTP measurement stream.\n"
116 "| At the TX-station enter:\n"
118 "| # mz eth0 -t rtp -B 10.3.3.42 (optionally change rate via -d option, payload size via pld command)\n"
120 "| At the RX-station (10.3.3.42) enter:\n"
122 "| # mz eth0 -T rtp \"log, path=/tmp/mz/\"\n"
124 "\n", TIME_COUNT_MAX
, TIME_COUNT
);
129 // check argstring for arguments
131 if (getarg(tx
.arg_string
,"bar", NULL
)==1) {
135 if (getarg(tx
.arg_string
,"txt", NULL
)==1) {
139 if (getarg(tx
.arg_string
,"curses", NULL
)==1) {
140 rtp_dm
= BAR
; //NCURSES;
141 fprintf(stderr
, " XXX This Mausezahn version does not support ncurses windows.\n");
144 if (getarg(tx
.arg_string
,"width", argval
)==1) {
146 fprintf(stderr
, " mz/rcv_rtp: The 'width' parameter requires the display mode 'bar'\n");
149 bwidth
= (int) str2int(argval
); // [TODO] bwidth is currently not used
150 if (bwidth
>RCV_RTP_MAX_BAR_WIDTH
) {
151 fprintf(stderr
, "The width must not exceed %i\n",
152 RCV_RTP_MAX_BAR_WIDTH
);
157 if (getarg(tx
.arg_string
,"ssrc", argval
)==1) {
158 ssrc_s
= str2hex(argval
, mz_ssrc
, 4);
160 fprintf(stderr
, " mz/rtp_rcv: invalid ssrc!\n");
165 if (getarg(tx
.arg_string
,"log", NULL
)==1) {
169 if (getarg(tx
.arg_string
,"logg", NULL
)==1) {
174 if (getarg(tx
.arg_string
,"path", argval
)==1) {
175 len
= strlen(argval
);
177 fprintf(stderr
, " mz/Error: path must not exceed 128 characters!\n");
180 if (argval
[len
-1]!='/') {
181 strncat(argval
, "/",1); // ensure that all paths end with "/"
183 strncpy(path
, argval
, 128);
187 if (getarg(tx
.arg_string
,"num", argval
)==1) {
188 gind_max
= (u_int32_t
) str2int(argval
);
189 if (gind_max
> TIME_COUNT_MAX
) {
190 gind_max
= TIME_COUNT_MAX
;
191 fprintf(stderr
, " mz/Warning: num range is 10..%d. Will reset to %d.\n",
192 TIME_COUNT_MAX
, TIME_COUNT_MAX
);
194 else if (gind_max
< 10) {
196 fprintf(stderr
, " mz/Warning: num range is 10..%d. Will reset to 10.\n",
202 // initialize global filter string
203 strncpy (rtp_filter_str
, "udp dst port 30000", 64);
205 if (getarg(tx
.arg_string
,"port", argval
)==1) {
206 port
= (u_int32_t
) str2int(argval
);
209 fprintf(stderr
, " mz: Too large port number! Reset to default port (30000).\n");
212 sprintf(rtp_filter_str
, "udp dst port %u", (unsigned int) port
);
216 if (ssrc_s
==0) str2hex("ca:fe:fe:ed", mz_ssrc
, 4);
221 // get a new filename
222 timestamp_human(filename
, "rtp_avg_");
223 strncpy(dummy
, path
, 128);
224 strncat(dummy
, filename
, 64);
225 if (verbose
) fprintf(stderr
, " mz: Will open %s\n", dummy
);
227 fp
= fopen (dummy
, "w+");
234 gtotal
=0; // counts written data blocks
235 fprintf(fp
, "# Average jitter measurements made by Mausezahn " MAUSEZAHN_VERSION_SHORT
".\n");
236 fprintf(fp
, "# Timestamp is in seconds, all other values in microseconds.\n");
237 fprintf(fp
, "# Column values (from left to right):\n");
238 fprintf(fp
, "# 1. Timestamp\n"
242 "# 5. estimated jitter according RFC-3550\n"
243 "# 6. packet drop count (total)\n"
244 "# 7. packet disorder count (total)\n");
247 ///////////// also detailed log required /////////////
249 // get a new filename
250 timestamp_human(filename
, "rtp_rt_");
251 strncpy(dummy
, path
, 128);
252 strncat(dummy
, filename
, 64);
253 if (verbose
) fprintf(stderr
, " mz: Will open %s\n", dummy
);
255 fp2
= fopen (dummy
, "w+");
262 fprintf(fp2
, "# Jitter measurements by Mausezahn " MAUSEZAHN_VERSION_SHORT
".\n");
263 fprintf(fp2
, "# Timestamp (usec) , true jitter (nsec)\n");
281 ////////////////////////////////////////////////////////////////////////////////////////////
283 // Defines the pcap handler and the callback function
286 char errbuf
[PCAP_ERRBUF_SIZE
];
290 struct bpf_program filter
;
294 p
= pcap_open_live (tx
.device
,
295 MAXBYTES_TO_READ
, // max num of bytes to read
296 0, // 1 if promiscuous mode
297 PCAP_READ_TIMEOUT_MSEC
, // read timeout in msec
302 fprintf(stderr
," mz/rcv_rtp: %s\n",errbuf
);
308 &filter
, // the compiled version of the filter
309 rtp_filter_str
, // text version of filter
314 fprintf(stderr
," mz/rcv_rtp: Error calling pcap_compile\n");
320 if ( pcap_setfilter(p
, &filter
) == -1)
322 fprintf(stderr
," mz/rcv_rtp: Error setting filter\n");
331 1, // number of packets to wait
332 got_rtp_packet
, // name of callback function
333 NULL
); // optional additional arguments for callback function
339 // TODO: Currently we never reach this point!
340 fprintf(stderr
, " mz: receiving of RTP finished.\n");
349 // Compares two 4-byte variables byte by byte
350 // returns 0 if identical, 1 if different
351 inline int compare4B (u_int8_t
*ip1
, u_int8_t
*ip2
)
353 if (*ip1
!= *ip2
) return 1;
354 if (*(ip1
+1) != *(ip2
+1)) return 1;
355 if (*(ip1
+2) != *(ip2
+2)) return 1;
356 if (*(ip1
+3) != *(ip2
+3)) return 1;
365 // Handler function to do something when RTP messages are received
366 void got_rtp_packet(u_char
*args
,
367 const struct pcap_pkthdr
*header
, // statistics about the packet (see 'struct pcap_pkthdr')
368 const u_char
*packet
) // the bytestring sniffed
370 const struct struct_ethernet
*ethernet
;
371 const struct struct_ip
*ip
;
372 const struct struct_udp
*udp
;
373 const struct struct_rtp
*rtp
;
375 int size_ethernet
= sizeof(struct struct_ethernet
);
376 int size_ip
= sizeof(struct struct_ip
);
377 int size_udp
= sizeof(struct struct_udp
);
378 // int size_rtp = sizeof(struct struct_rtp);
380 ethernet
= (struct struct_ethernet
*)(packet
);
381 ip
= (struct struct_ip
*)(packet
+size_ethernet
);
382 udp
= (struct struct_udp
*)(packet
+size_ethernet
+size_ip
);
383 rtp
= (struct struct_rtp
*)(packet
+size_ethernet
+size_ip
+size_udp
);
404 static u_int32_t drop_last
=0, drop_prev
=0;
407 // check if the RTP packet is really from a Mausezahn instance:
408 if (compare4B((u_int8_t
*) &rtp
->ssrc
, mz_ssrc
)==0) {
409 // we got a valid RTP packet from a Mausezahn instance
410 // Get current SQNR and store it in 'sqnr_cur' in host byte order
411 x
= (u_int8_t
*) &rtp
->sqnr
;
412 y
= (u_int8_t
*) &sqnr_cur
;
418 /////////////////////////////////////////////////////////////////////
419 // Packet drop and disorder detection:
421 if (sqnr_next
==sqnr_cur
) { // correct SQNR received
424 } else if (sqnr_last
>sqnr_cur
) { // disordered sequence
426 if (drop
) drop
--; // don't get below 0
427 else { // drop reached zero: resync (restarted RTP stream?)
428 sqnr_last
= sqnr_cur
;
429 sqnr_next
= (++sqnr_last
);
432 } else { // packet drop
433 drop
+= (sqnr_cur
-sqnr_next
);
434 sqnr_last
= sqnr_cur
;
435 sqnr_next
= (++sqnr_last
);
438 // initial synchronization with observed SQNR:
439 sqnr_last
= sqnr_cur
;
440 sqnr_next
= (++sqnr_last
);
444 /////////////////////////////////////////////////////////////////////
447 // Get RX timestamp from pcap header
448 timeRX
[gind
].sec
= header
->ts
.tv_sec
;
449 timeRX
[gind
].nsec
= header
->ts
.tv_usec
*1000;
451 // Get TX timestamp from the packet
452 mops_hton4((u_int32_t
*) &rtp
->time_sec
, (u_int8_t
*) &timeTX
[gind
].sec
);
453 mops_hton4((u_int32_t
*) &rtp
->time_nsec
, (u_int8_t
*) &timeTX
[gind
].nsec
);
455 // printf("%li %li\n", (long int) timeTX[gind].sec, (long int) timeTX[gind].nsec);
459 ////////////////////////////////////////////////////////////////
460 if (gind
== gind_max
) { // array full, now calculate statistics
465 jitter_min
= 0xffffffff;
469 ///////////////////////////////////////////////////////
470 // calculate deltas and jitters
471 for (i
=2; i
<gind_max
; i
++) { // omit the first 2 data
472 // entries because of
473 // artificial high TX-delta!
475 ///////////////////////////////////////////////
476 // calculate deltaTX and deltaRX
478 s1
=timestamp_subtract (&timeTX
[i
], &timeTX
[i
-1], &deltaTX
);
479 s2
=timestamp_subtract (&timeRX
[i
], &timeRX
[i
-1], &deltaRX
);
480 if (s1
) fprintf(stderr
, " *** ***\n");
482 // Then calculate the precise jitter by considering
483 // also TX-jitter: (pseudo)jitter = deltaRX - deltaTX,
484 // hence we have positive and negative jitter (delay
485 // deviations) jitter entries are in +/- nanoseconds
486 jitter
[i
] = (deltaRX
.sec
*1000000000L + deltaRX
.nsec
)
487 - (deltaTX
.sec
*1000000000L + deltaTX
.nsec
);
488 // Calculate RFC 3550 jitter estimation. According to
489 // that RFC the jitter should be measured in timestamp
490 // units; however currently Mausezahn uses nanoseconds.
491 // (If we want to solve this: G.711 timestamp units are
492 // 125 usec, so jitter/=125 would be sufficient, AFAIK)
493 ltemp
= labs(jitter
[i
]) - jitter_rfc
;
494 jitter_rfc
+= (ltemp
>>4);
495 // Add previous pseudojitter to get the true jitter
496 // (See Documentation!)
497 jitter
[i
] += jitter
[i
-1];
499 ////////////////////////////////////////////////
504 ////////////////////////////////////////////////
505 // Determine avg, min, and max jitter within this time frame:
506 jitter_abs
= labs(jitter
[i
]);
507 jitter_avg
+= jitter_abs
;
508 if (jitter_abs
< jitter_min
) jitter_min
= jitter_abs
;
509 if (jitter_abs
> jitter_max
) jitter_max
= jitter_abs
;
511 ////////////////////////////////
513 /// PRINT IN FILE_2: Detailed jitter data ///
515 // Calculate relative timestamp for column 1 of the datafile
516 curtime
= timeRX
[i
].sec
*1000000+timeRX
[i
].nsec
/1000;
518 curtime
= curtime
- time0
;
519 } else { // this is only done once during the Mausezahn process
522 curtime
= curtime
- time0
;
524 fprintf(fp2
, "%lu, %li\n",
525 (long unsigned int) curtime
,
526 (long int) jitter
[i
]);
527 fflush(fp2
); // save everything immediately
528 // (CHECK if fsync() is additionally needed)
530 } // end for (i=2; i<gind_max; i++)
532 ////////////////////////////////////////////////////////
535 jitter_avg
= jitter_avg
/ (gind_max
-2); // average true jitter, always positive
537 if (drop
>=drop_prev
) { // because the total drop count may decrease(!) if disordered packets appear lately
538 drop_last
= drop
- drop_prev
;
542 // PRINT ON CLI: statistics data
545 dum
= (unsigned char*) &ip
->src
;
547 "Got %u packets from host %u.%u.%u.%u: %lu lost (%lu absolute lost, %lu out of order)\n"
548 " Jitter_RFC (low pass filtered) = %li usec\n"
549 " Samples jitter (min/avg/max) = %lu/%lu/%lu usec\n",
551 *(dum
),*(dum
+1),*(dum
+2),*(dum
+3),
552 (long unsigned int) drop_last
,
553 (long unsigned int) drop
,
554 (long unsigned int) dis
,
555 (long int) jitter_rfc
/1000,
556 (long unsigned int) jitter_min
/1000,
557 (long unsigned int) jitter_avg
/1000,
558 (long unsigned int) jitter_max
/1000);
562 print_jitterbar(jitter_rfc
/1000, drop_last
);
565 case NCURSES
: // would be nice...?
572 // Determine whether some packets got lost:
580 /// PRINT IN FILE_1: statistics only ///
583 timestamp_hms (ts_hms
);
585 "%s, %lu, %lu, %lu, %li, %u, %u\n",
587 (long unsigned int) jitter_min
/1000,
588 (long unsigned int) jitter_avg
/1000,
589 (long unsigned int) jitter_max
/1000,
590 (long int) jitter_rfc
/1000,
598 // Open another file if current file reaches a limit
600 if ((rtp_log
==2) && (gtotal
>MAX_DATA_BLOCKS
)) { // file big enough,
602 if (fclose(fp2
) == EOF
) {
608 fprintf(stderr
, " mz: %s written.\n",filename
);
610 timestamp_human(filename
, "rtp_"); // get a new filename
611 strncpy(dummy
, path
, 128);
612 strncat(dummy
, filename
, 64);
614 if (verbose
) fprintf(stderr
, " mz: Will open %s\n", dummy
);
616 if ( (fp2
= fopen (dummy
, "w+")) == NULL
) {
617 if (errno
!= EAGAIN
) {
622 fprintf(fp2
, "# Jitter measurements by Mausezahn "
623 MAUSEZAHN_VERSION_SHORT
".\n");
624 fprintf(fp2
, "# Timestamp (usec) , true jitter (nsec)\n");
626 } // statistics end *********************************************************************
633 void print_jitterbar (long int j
, u_int32_t d
)
635 // Determine actual data window by considering two events:
637 // 1) window move (j exceeds lower or upper limit)
638 // 2) window rescale (window moves happen too often or the variance
639 // of successive data points is too small)
641 // The most critical value is the chosen resolution (window range),
642 // especially the _initial_ resolution.
644 static long int range
=0, min
=0, max
=0, minvar
=0, j0
=0, dj
=0;
645 static int moved
=0, varcount
=0, barcount
=0;
646 char str
[128], bar
[150],
647 str1
[8], str2
[8], str3
[8], str4
[8];
651 // Initialize vars (start with an opened window)
652 // Note that 'range' is actually half of the window
655 if (range
<500) range
=500;
661 dj
= labs(j
-j0
); // no initialization: calculate jitter delta
664 // Move window when borders crossed:
665 if ((j
<min
) || (j
>max
)) {
671 fprintf(stdout
, "\nNOTE: +- Rescaled window to %4.2f msec\n", (double) range
/500);
675 fprintf(stdout
,"\n");
676 // printf("move event: min=%li max=%li\n", min, max);
679 // printf("normal event: min=%li max=%li\n", min, max);
683 // Increase range when window moved 5 times in a row
686 if (range
>10000000L) range
=10000000L;
688 if (minvar
<1000) minvar
=1000;
697 // printf("scale up event: min=%li max=%li\n", min, max);
698 fprintf(stdout
, "\nNOTE: ++ Rescaled window to %4.2f msec\n", (double) range
/500);
702 // Decrease range when jitter deltas are smaller than minvar
711 if (range
>j
) range
=j
;
716 if (minvar
<1000) minvar
=1000;
723 fprintf(stdout
, "\nNOTE: -- Rescaled window to %4.2f msec\n", (double) range
/500);
726 // printf("scale down event: min=%li max=%li\n", min, max);
739 sprintf(str1
,"%4.2f", (double) min
/1000);
740 sprintf(str2
,"%4.2f", (double) (min
+tmp
)/1000);
741 sprintf(str3
,"%4.2f", (double) (max
-tmp
)/1000);
742 sprintf(str4
,"%4.2f", (double) max
/1000);
745 "%-6s %-6s %-6s %-6s\n"
746 "|-------------------------|-------------------------|-------------------------|\n",
747 str1
, str2
, str3
, str4
);
751 anz
= 80*(j
-min
)/(2*range
);
753 memset((void*) str
, '#', anz
);
754 memset((void*) str
+anz
, ' ', 80-anz
);
758 memset((void*) str
, ' ', 80);
763 sprintf(bar
, "%s%4.2f msec !%lu dropped!", str
, (double) j
/1000, (unsigned long int) d
);
765 sprintf(bar
, "%s%4.2f msec", str
, (double) j
/1000);
767 fprintf(stdout
,"%s\n", bar
);