├── Makefile ├── README.md └── ptpmeasure.c /Makefile: -------------------------------------------------------------------------------- 1 | CC=gcc 2 | CFLAGS=-I. -O2 -g -std=gnu99 -Wall -Wextra `pkg-config --cflags bitstream` 3 | LDLIBS=-lpcap 4 | 5 | ptpmeasure: ptpmeasure.o 6 | 7 | clean: 8 | rm -f ptpmeasure ptpmeasure.o 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ptpmeasure: A measurement tool for PTP/ST 2110/ST 2022-6 2 | 3 | ptpmeasure is a simple tool to sanity-check ST 2110/2022-6 streams and make measurements relative to PTP. It has minimal dependencies and allows for multi-day measurements without the need to create large and unwieldy packet captures. Nor does it require many dependencies. 4 | 5 | More information about the measurements can be found here: [https://www.slideshare.net/kierank12/ibc-2022-ip-showcase-timestamps-in-st-2110-what-they-mean-and-how-to-measure-them](https://www.obe.tv/timestamps-in-st-2110-what-they-mean-and-how-to-measure-them/) 6 | 7 | ## Getting Started 8 | 9 | ### Dependencies 10 | 11 | * [bitstream](https://code.videolan.org/videolan/bitstream) 12 | * libpcap development headers (e.g On Ubuntu/Debian run `apt install libpcap-dev`) 13 | 14 | ### Supported Network Cards 15 | 16 | * Any network card with hardware timestamping of all packets with a VSS Ethernet Trailer such as the Silicom PE310G2TSI9P 17 | * Any network card with hardware timestamping of all packets via “adapter_unsynced” such as the Mellanox ConnectX5 18 | 19 | ### Initial setup 20 | 21 | * For VSS trailer cards, use the provided vendor tools to lock the card to PTP 22 | * For "adapter_unsynced" , use a command such as `ptp4l -m -q -i p9p1 -f ~/ptp-smpte.conf -s` where "ptp-smpte.conf" contains configuration matching your PTP configuration 23 | 24 | ## Using the tool 25 | 26 | ### Video 27 | 28 | To measure a 2110-20 29.97fps video flow from 238.16.1.10:5000 on NIC "eth3.38" in “adapter_unsynced” (`--mellanox`) mode: 29 | 30 | `sudo ./ptpmeasure 238.16.1.10 5000 eth3.38 --mellanox --2110-video --fps 30000/1001 --interlaced` 31 | 32 | An output like this will be generated: 33 | 34 | 2022-09-16 21:30:28+0100: First Packet arrived 0.616 ms after ideal, RTP-PTP offset -11.111us (-1 rtp). 35 | 2022-09-16 21:30:28+0100: First Packet arrived 0.630 ms after ideal, RTP-PTP offset 0.000us (0 rtp). 36 | 2022-09-16 21:30:28+0100: First Packet arrived 0.615 ms after ideal, RTP-PTP offset -11.111us (-1 rtp). 37 | 38 | Note for gapped output the first packet arriving ~600us after ideal is normal 39 | 40 | ### Audio 41 | 42 | To measure a 2110-30 audio flow from 238.16.1.11:5002 on NIC "eth3.38" in “adapter_unsynced” (`--mellanox`) mode: 43 | 44 | `sudo ./ptpmeasure 238.16.1.11 5002 eth3.38 --mellanox` 45 | 46 | An output like this will be generated: 47 | 48 | 2022-09-16 21:34:20+0100: RTP-PTP offset -291.666667 us. Audio samples 96 49 | 2022-09-16 21:34:20+0100: RTP-PTP offset -270.833333 us. Audio samples 96 50 | 2022-09-16 21:34:20+0100: RTP-PTP offset -270.833333 us. Audio samples 96 51 | 52 | ### Ancillary 53 | 54 | This tool can probe the packets of an ST 2110-40 (RFC 8331) flow and from the 55 | header print how many ancillary packets are in each network packet and the total 56 | length. For example: 57 | 58 | `sudo ./ptpmeasure 238.10.1.20 5004 eth0 --2110-ancillary` 59 | 60 | An output like the following will be generated: 61 | ``` 62 | marker: 0, ts: 1012828175, ext seq num: 26249442, len: 140, count: 1 63 | marker: 1, ts: 1012828175, ext seq num: 26249443, len: 0, count: 0 64 | marker: 0, ts: 1012829975, ext seq num: 26249444, len: 84, count: 1 65 | ``` 66 | 67 | If you provide the video framerate and interlacing then it will measure the 68 | arrival times of the packets relative to PTP and RTP in much the same manner as 69 | for ST 2110-20 video above. Example: 70 | 71 | `sudo ./ptpmeasure 238.10.1.20 5004 eth0 --2110-ancillary --fps 25/1 --interlaced` 72 | 73 | ``` 74 | 2025-09-05 16:38:33+0100: Packet arrived 0.325 ms after ideal, RTP-PTP offset -11.111us (-1 rtp), marker: 0. 75 | 2025-09-05 16:38:33+0100: Packet arrived 0.011 ms after ideal, RTP-PTP offset -20011.111us (-1801 rtp), marker: 1. 76 | 2025-09-05 16:38:33+0100: Packet arrived 0.342 ms after ideal, RTP-PTP offset -11.111us (-1 rtp), marker: 0. 77 | ``` 78 | -------------------------------------------------------------------------------- /ptpmeasure.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | ptpmeasure: An ST 2110/2022-6 PTP measurement tool 4 | Copyright (C) 2022 Open Broadcast Systems Ltd 5 | 6 | This program is free software; you can redistribute it and/or 7 | modify it under the terms of the GNU General Public License 8 | as published by the Free Software Foundation; either version 2 9 | of the License, or (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program; if not, write to the Free Software 18 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | 20 | */ 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | 49 | #include 50 | 51 | #define LEAP_SECONDS (37) 52 | 53 | # define AV_RB32(x) \ 54 | (((uint32_t)((const uint8_t*)(x))[0] << 24) | \ 55 | (((const uint8_t*)(x))[1] << 16) | \ 56 | (((const uint8_t*)(x))[2] << 8) | \ 57 | ((const uint8_t*)(x))[3]) 58 | 59 | static bool mellanox = true; 60 | 61 | struct rational { 62 | uint16_t num, den; 63 | } fps; 64 | 65 | static inline int time_for_log(char buf[256]) 66 | { 67 | time_t t = time(NULL); 68 | struct tm tm; 69 | localtime_r(&t, &tm); 70 | strftime(buf, 256, "%Y-%m-%d %H:%M:%S%z", &tm); 71 | return 0; 72 | } 73 | 74 | void got_packet(u_char *user, const struct pcap_pkthdr *header, const u_char *packet) 75 | { 76 | pcap_t *pcap = (pcap_t*)user; 77 | uint8_t *eth_payload = ethernet_payload((uint8_t*)packet); 78 | uint8_t *ip_pkt = ip_payload(eth_payload); 79 | uint8_t *udp_pkt = udp_payload(ip_pkt); 80 | uint8_t *rtp_data = rtp_payload(udp_pkt); 81 | uint32_t rtp_timestamp = rtp_get_timestamp(udp_pkt); 82 | 83 | /* Add leap seconds to convert from PC time to TAI */ 84 | uint64_t ptp_timestamp; 85 | if (mellanox) 86 | ptp_timestamp = header->ts.tv_sec; 87 | else 88 | ptp_timestamp = AV_RB32(&packet[header->len-9]) + LEAP_SECONDS; 89 | ptp_timestamp *= 48000; 90 | if (mellanox) 91 | ptp_timestamp += header->ts.tv_usec * 48000 / 1000000; 92 | else 93 | ptp_timestamp += AV_RB32(&packet[header->len-5]) * (uint64_t)48000 / 1e9; 94 | ptp_timestamp &= UINT32_MAX; 95 | 96 | int64_t delta = (int64_t)rtp_timestamp - ptp_timestamp; 97 | int len = header->len - (rtp_data - (uint8_t*)packet) - ((mellanox) ? 0 : 9); 98 | 99 | char time_buf[256]; 100 | if (time_for_log(time_buf)) 101 | pcap_breakloop(pcap); 102 | 103 | printf("%s: RTP-PTP offset %f us. Audio samples %i \n", 104 | time_buf, (float)delta*1e6/48000, len/3); 105 | } 106 | 107 | void got_packet_2110_video(u_char *user, const struct pcap_pkthdr *header, const u_char *packet) 108 | { 109 | pcap_t *pcap = (pcap_t*)user; 110 | uint8_t *ip_pkt = ethernet_payload((uint8_t*)packet); 111 | uint8_t *udp_pkt = ip_payload(ip_pkt); 112 | uint8_t *rtp_pkt = udp_payload(udp_pkt); 113 | 114 | static bool prev_marker = false; 115 | bool marker = rtp_check_marker(rtp_pkt); 116 | 117 | if (!prev_marker) { 118 | prev_marker = marker; 119 | return; 120 | } 121 | prev_marker = marker; 122 | 123 | uint32_t rtp_timestamp = rtp_get_timestamp(rtp_pkt); 124 | 125 | uint64_t arrival_time_90khz, arrival_time_ns; 126 | if (mellanox) 127 | arrival_time_ns = arrival_time_90khz = header->ts.tv_sec; 128 | else 129 | /* Add leap seconds to convert from PC time to TAI */ 130 | arrival_time_ns = arrival_time_90khz = AV_RB32(&packet[header->len-9]) + LEAP_SECONDS; 131 | 132 | arrival_time_90khz *= 90000; 133 | arrival_time_ns *= 1000000000; 134 | 135 | if (mellanox) { 136 | arrival_time_90khz += header->ts.tv_usec * 90000 / 1000000; 137 | arrival_time_ns += header->ts.tv_usec * 1000; 138 | } else { 139 | arrival_time_90khz += AV_RB32(&packet[header->len-5]) * UINT64_C(90000) / UINT64_C(1000000000); 140 | arrival_time_ns += AV_RB32(&packet[header->len-5]); 141 | } 142 | arrival_time_90khz &= UINT32_MAX; 143 | 144 | const uint64_t frame_time_ns = UINT64_C(1000000000) * fps.den; 145 | __uint128_t temp = fps.num; 146 | temp *= arrival_time_ns; 147 | temp %= frame_time_ns; 148 | uint64_t ideal_ptp_diff_ns = temp; 149 | 150 | temp = arrival_time_ns - ideal_ptp_diff_ns / fps.num; 151 | temp = temp * 90000 / 1000000000; 152 | temp = temp & UINT32_MAX; 153 | int64_t delta = (int64_t)rtp_timestamp - (int64_t)temp; 154 | 155 | char time_buf[256]; 156 | if (time_for_log(time_buf)) 157 | pcap_breakloop(pcap); 158 | 159 | if (ideal_ptp_diff_ns > frame_time_ns / 2) { 160 | printf("%s: First packet arrived %.3f ms before ideal, RTP-PTP offset %.3fus (%"PRId64" rtp).\n", 161 | time_buf, 162 | (frame_time_ns - ideal_ptp_diff_ns) / (1e6 * fps.num), 163 | delta*1e6/90000, delta); 164 | } 165 | 166 | else { 167 | printf("%s: First Packet arrived %.3f ms after ideal, RTP-PTP offset %.3fus (%"PRId64" rtp).\n", 168 | time_buf, 169 | ideal_ptp_diff_ns / (1e6 * fps.num), 170 | delta*1e6/90000, delta); 171 | } 172 | } 173 | 174 | void got_packet_2022_6(u_char *user, const struct pcap_pkthdr *header, const u_char *packet) 175 | { 176 | pcap_t *pcap = (pcap_t*)user; 177 | uint8_t *ip_pkt = ethernet_payload((uint8_t*)packet); 178 | uint8_t *udp_pkt = ip_payload(ip_pkt); 179 | uint8_t *rtp_pkt = udp_payload(udp_pkt); 180 | if (!rtp_check_marker(rtp_pkt)) 181 | return; 182 | 183 | if (rtp_get_type(rtp_pkt) != 98) 184 | printf("Error: RTP type not correct\n"); 185 | 186 | uint8_t *hbrmt_pkt = rtp_payload(rtp_pkt); 187 | uint8_t header_frate = smpte_hbrmt_get_frate(hbrmt_pkt); 188 | if (header_frate < 0x10 || header_frate > 0x1b) 189 | printf("Error: HBRMT frame rate out of range\n"); 190 | 191 | static const struct rational fps[] = { 192 | [0x10] = { 60, 1 }, 193 | [0x11] = { 60000, 1001 }, 194 | [0x12] = { 50, 1 }, 195 | [0x14] = { 48, 1 }, 196 | [0x15] = { 48000, 1001 }, 197 | [0x16] = { 30, 1 }, 198 | [0x17] = { 30000, 1001 }, 199 | [0x18] = { 25, 1 }, 200 | [0x1a] = { 24, 1 }, 201 | [0x1b] = { 24000, 1001 }, 202 | }; 203 | const uint64_t frame_time = UINT64_C(1000000000) * fps[header_frate].den; 204 | 205 | uint8_t header_frame = smpte_hbrmt_get_frame(hbrmt_pkt); 206 | uint32_t frame_size = 0; 207 | /* NTSC */ 208 | if (header_frame == 0x10) 209 | frame_size = 858 * 525 / 2 * 5; 210 | 211 | /* PAL */ 212 | else if (header_frame == 0x11) 213 | frame_size = 864 * 625 / 2 * 5; 214 | 215 | /* 720 lines */ 216 | else if (header_frame == 0x30) { 217 | if (header_frate == 0x12) 218 | frame_size = 1980 * 750 / 2 * 5; 219 | else 220 | frame_size = 1650 * 750 / 2 * 5; 221 | } 222 | 223 | /* 1080 lines */ 224 | else if (header_frame == 0x20 || header_frame == 0x21) { 225 | if (header_frate == 0x1b || header_frate == 0x1a) 226 | frame_size = 2750 * 1125 / 2 * 5; 227 | else if (header_frate == 0x18 || header_frate == 0x12) 228 | frame_size = 2640 * 1125 / 2 * 5; 229 | else 230 | frame_size = 2200 * 1125 / 2 * 5; 231 | } 232 | 233 | double packet_time = frame_time 234 | / (((frame_size + HBRMT_DATA_SIZE-1) / HBRMT_DATA_SIZE) * fps[header_frate].num); 235 | 236 | char time_buf[256]; 237 | if (time_for_log(time_buf)) 238 | pcap_breakloop(pcap); 239 | 240 | uint64_t recv_timestamp; 241 | if (mellanox) { 242 | recv_timestamp = header->ts.tv_sec * UINT64_C(1000000000) + header->ts.tv_usec * 1000; 243 | } else { 244 | uint32_t ts1 = AV_RB32(&packet[header->len-9]) + LEAP_SECONDS; 245 | uint32_t ts2 = AV_RB32(&packet[header->len-5]); 246 | recv_timestamp = ts1 * UINT64_C(1000000000) + ts2; 247 | } 248 | 249 | __uint128_t temp = fps[header_frate].num; 250 | temp *= recv_timestamp; 251 | temp %= frame_time; 252 | uint64_t ptp_epoch_diff = temp; 253 | 254 | static int64_t box[25] = {0}; 255 | static unsigned counter = 0; 256 | static int64_t sum = 0; 257 | 258 | if (ptp_epoch_diff > frame_time / 2) { 259 | sum -= box[counter]; 260 | box[counter] = (int64_t)ptp_epoch_diff - (int64_t)frame_time; 261 | sum += box[counter]; 262 | printf("%s: Marker arrived %.3f ms before epoch, %2.1f packets off, rolling average: %.3f\n", 263 | time_buf, 264 | (frame_time - ptp_epoch_diff) / (1e6 * fps[header_frate].num), 265 | (frame_time - ptp_epoch_diff) / (packet_time * fps[header_frate].num), 266 | sum / (25e6 * fps[header_frate].num)); 267 | } 268 | else { 269 | sum -= box[counter]; 270 | box[counter] = ptp_epoch_diff; 271 | sum += box[counter]; 272 | printf("%s: Marker arrived %.3f ms after epoch, %2.1f packets off, rolling average: %.3f\n", 273 | time_buf, 274 | ptp_epoch_diff / (1e6 * fps[header_frate].num), 275 | ptp_epoch_diff / (packet_time * fps[header_frate].num), 276 | sum / (25e6 * fps[header_frate].num)); 277 | } 278 | 279 | counter = (counter + 1) % 25; 280 | } 281 | 282 | void got_packet_ancillary(u_char *user, const struct pcap_pkthdr *header, const u_char *packet) 283 | { 284 | pcap_t *pcap = (pcap_t*)user; 285 | uint8_t *ip_pkt = ethernet_payload((uint8_t*)packet); 286 | uint8_t *udp_pkt = ip_payload(ip_pkt); 287 | uint8_t *rtp_pkt = udp_payload(udp_pkt); 288 | uint8_t *payload = rtp_payload(rtp_pkt); 289 | 290 | if (fps.num == 0) { 291 | printf("marker: %d, ts: %u, ext seq num: %u, len: %d, count: %d\n", 292 | rtp_check_marker(rtp_pkt), rtp_get_timestamp(rtp_pkt), 293 | (unsigned)rtp_get_seqnum(rtp_pkt) | rfc8331_get_extended_sequence_number(payload) << 16, 294 | rfc8331_get_length(payload), rfc8331_get_anc_count(payload)); 295 | return; 296 | } 297 | 298 | uint32_t rtp_timestamp = rtp_get_timestamp(rtp_pkt); 299 | 300 | uint64_t arrival_time_90khz, arrival_time_ns; 301 | if (mellanox) 302 | arrival_time_ns = arrival_time_90khz = header->ts.tv_sec; 303 | else 304 | /* Add leap seconds to convert from PC time to TAI */ 305 | arrival_time_ns = arrival_time_90khz = AV_RB32(&packet[header->len-9]) + LEAP_SECONDS; 306 | 307 | arrival_time_90khz *= 90000; 308 | arrival_time_ns *= 1000000000; 309 | 310 | if (mellanox) { 311 | arrival_time_90khz += header->ts.tv_usec * 90000 / 1000000; 312 | arrival_time_ns += header->ts.tv_usec * 1000; 313 | } else { 314 | arrival_time_90khz += AV_RB32(&packet[header->len-5]) * UINT64_C(90000) / UINT64_C(1000000000); 315 | arrival_time_ns += AV_RB32(&packet[header->len-5]); 316 | } 317 | arrival_time_90khz &= UINT32_MAX; 318 | 319 | const uint64_t frame_time_ns = UINT64_C(1000000000) * fps.den; 320 | __uint128_t temp = fps.num; 321 | temp *= arrival_time_ns; 322 | temp %= frame_time_ns; 323 | uint64_t ideal_ptp_diff_ns = temp; 324 | 325 | temp = arrival_time_ns - ideal_ptp_diff_ns / fps.num; 326 | temp = temp * 90000 / 1000000000; 327 | temp = temp & UINT32_MAX; 328 | int64_t delta = (int64_t)rtp_timestamp - (int64_t)temp; 329 | 330 | char time_buf[256]; 331 | if (time_for_log(time_buf)) 332 | pcap_breakloop(pcap); 333 | 334 | if (ideal_ptp_diff_ns > frame_time_ns / 2) { 335 | printf("%s: packet arrived %.3f ms before ideal, RTP-PTP offset %.3fus (%"PRId64" rtp), marker: %d.\n", 336 | time_buf, 337 | (frame_time_ns - ideal_ptp_diff_ns) / (1e6 * fps.num), 338 | delta*1e6/90000, delta, 339 | rtp_check_marker(rtp_pkt)); 340 | } 341 | 342 | else { 343 | printf("%s: Packet arrived %.3f ms after ideal, RTP-PTP offset %.3fus (%"PRId64" rtp), marker: %d.\n", 344 | time_buf, 345 | ideal_ptp_diff_ns / (1e6 * fps.num), 346 | delta*1e6/90000, delta, 347 | rtp_check_marker(rtp_pkt)); 348 | } 349 | } 350 | 351 | enum mode { 352 | MODE_2022_6 = 9000, 353 | MODE_2110_ANCILLARY, 354 | MODE_2110_AUDIO, 355 | MODE_2110_VIDEO, 356 | }; 357 | 358 | static const struct option cmd_options[] = { 359 | { "2022-6", no_argument, NULL, MODE_2022_6 }, 360 | { "2110-ancillary", no_argument, NULL, MODE_2110_ANCILLARY }, 361 | { "2110-video", no_argument, NULL, MODE_2110_VIDEO }, 362 | { "fps", required_argument, NULL, 'f' }, 363 | { "help", no_argument, NULL, 'h' }, 364 | { "interlaced", no_argument, NULL, 'i' }, 365 | { "list-ts-types", no_argument, NULL, 'l' }, 366 | { "mellanox", no_argument, NULL, 'm' }, 367 | { "packet-count", required_argument, NULL, 'c' }, 368 | { "silicom", no_argument, NULL, 's' }, 369 | { "ts-type", required_argument, NULL, 't' }, 370 | { NULL } 371 | }; 372 | 373 | #define lengthof(a) ((int)(sizeof(a) / sizeof(a[0]))) 374 | 375 | static void usage(const char *prog) 376 | { 377 | fprintf(stderr, "Usage: %s [options] \n" 378 | "Example: %s 239.255.255.250 1900 p9p1\n", prog, prog); 379 | fprintf(stderr, "long options:\n"); 380 | for (int i = 0; i < lengthof(cmd_options) - 1; i++) { 381 | if (cmd_options[i].val > 0x20 && cmd_options[i].val < 0x7f) 382 | fprintf(stderr, " -%c, --%s\n", cmd_options[i].val, cmd_options[i].name); 383 | else 384 | fprintf(stderr, " --%s\n", cmd_options[i].name); 385 | } 386 | } 387 | 388 | #define UBASE_CASE_TO_STR(Value) case Value: return #Value 389 | 390 | static const char *pcap_error(pcap_t *p, int value) 391 | { 392 | switch (value) { 393 | UBASE_CASE_TO_STR(PCAP_ERROR_ACTIVATED); 394 | UBASE_CASE_TO_STR(PCAP_ERROR_CANTSET_TSTAMP_TYPE); 395 | UBASE_CASE_TO_STR(PCAP_ERROR_IFACE_NOT_UP); 396 | UBASE_CASE_TO_STR(PCAP_ERROR_NO_SUCH_DEVICE); 397 | UBASE_CASE_TO_STR(PCAP_ERROR_PERM_DENIED); 398 | UBASE_CASE_TO_STR(PCAP_ERROR_PROMISC_PERM_DENIED); 399 | UBASE_CASE_TO_STR(PCAP_ERROR_RFMON_NOTSUP); 400 | UBASE_CASE_TO_STR(PCAP_WARNING_PROMISC_NOTSUP); 401 | UBASE_CASE_TO_STR(PCAP_WARNING_TSTAMP_TYPE_NOTSUP); 402 | case PCAP_ERROR: return pcap_geterr(p); 403 | case PCAP_WARNING: return pcap_geterr(p); 404 | default: return "unknown error"; 405 | } 406 | } 407 | 408 | int main(int argc, char *argv[]) 409 | { 410 | int i = 0; 411 | char filter_exp[100]; 412 | enum mode mode = MODE_2110_AUDIO; 413 | bool list_ts = false, interlaced = false; 414 | int packet_count = -1; 415 | int ret = 0; 416 | const char *ts_type = NULL; 417 | 418 | int opt; 419 | while ((opt = getopt_long(argc, argv, "c:f:hilmt:", cmd_options, NULL)) != -1) switch (opt) { 420 | case MODE_2022_6: 421 | mode = MODE_2022_6; 422 | break; 423 | 424 | case MODE_2110_ANCILLARY: 425 | mode = MODE_2110_ANCILLARY; 426 | break; 427 | 428 | case MODE_2110_VIDEO: 429 | mode = MODE_2110_VIDEO; 430 | break; 431 | 432 | case 'c': 433 | packet_count = atoi(optarg); 434 | break; 435 | 436 | case 'f': { 437 | int num = 0, den = 0; 438 | char c; 439 | if (sscanf(optarg, "%d/%d%c", &num, &den, &c) != 2) { 440 | fprintf(stderr, "unable to parse %s as framerate\n", optarg); 441 | return 1; 442 | } 443 | if (num <= 0 || num > 60000 || den <= 0 || den > 1001) { 444 | fprintf(stderr, "invalid framerate given: %d/%d\n", num, den); 445 | } 446 | fps = (struct rational) { num, den }; 447 | } break; 448 | 449 | case 'i': 450 | interlaced = true; 451 | break; 452 | 453 | case 'l': 454 | list_ts = true; 455 | break; 456 | 457 | case 'm': 458 | mellanox = true; 459 | break; 460 | 461 | case 's': 462 | mellanox = false; 463 | break; 464 | 465 | case 't': 466 | ts_type = optarg; 467 | break; 468 | 469 | case 'h': 470 | usage(argv[0]); 471 | return 0; 472 | default: 473 | usage(argv[0]); 474 | return 1; 475 | } 476 | 477 | if (optind + 3 != argc) { 478 | usage(argv[0]); 479 | return 1; 480 | } 481 | 482 | if (mode == MODE_2110_VIDEO && fps.num == 0) { 483 | fprintf(stderr, "2110 video requires that you give the frame rate with --fps, and --interlaced if necessary\n"); 484 | return 1; 485 | } 486 | 487 | if (interlaced) 488 | fps.num *= 2; 489 | 490 | if (mellanox && ts_type == NULL) 491 | ts_type = "adapter_unsynced"; 492 | 493 | char *group = argv[optind + 0]; 494 | int port = atoi(argv[optind + 1]); 495 | char *miface = argv[optind + 2]; 496 | char errbuf[PCAP_ERRBUF_SIZE]; 497 | 498 | struct sockaddr_in ip_src_addr; 499 | char *ip_src = NULL, *ip_dst = NULL; 500 | ip_dst = strchr(group, '@'); 501 | if (ip_dst) { 502 | *ip_dst = '\0'; 503 | ip_dst++; 504 | ip_src = group; 505 | 506 | struct ifaddrs *ifa = NULL; 507 | if (getifaddrs(&ifa) < 0) { 508 | ret = errno; 509 | fprintf(stderr, "getifaddrs: %m\n"); 510 | return ret; 511 | } 512 | 513 | bool have_sin = false; 514 | for (struct ifaddrs *ifap = ifa; ifap; ifap = ifap->ifa_next) { 515 | if (!strncmp(ifap->ifa_name, miface, IFNAMSIZ)) { 516 | if (ifap->ifa_addr->sa_family == AF_INET) { 517 | ip_src_addr = *(struct sockaddr_in *)ifap->ifa_addr; 518 | have_sin = true; 519 | break; 520 | } 521 | } 522 | } 523 | freeifaddrs(ifa); 524 | 525 | if (!have_sin) { 526 | fprintf(stderr, "unable to get IP address for %s\n", miface); 527 | return 1; 528 | } 529 | } 530 | 531 | else { 532 | ip_dst = group; 533 | } 534 | 535 | 536 | struct bpf_program fp; /* The compiled filter expression */ 537 | bpf_u_int32 mask; /* The netmask of our sniffing device */ 538 | bpf_u_int32 net; /* The IP of our sniffing device */ 539 | 540 | if(pcap_lookupnet(miface, &net, &mask, errbuf) == -1) { 541 | fprintf(stderr, "Can't get netmask for device %s\n", miface); 542 | net = 0; 543 | mask = 0; 544 | } 545 | 546 | pcap_t *pcap = pcap_create(miface, errbuf); 547 | if(pcap == NULL) { 548 | fprintf(stderr, "Couldn't open device %s: %s\n", miface, errbuf); 549 | return -1; 550 | } 551 | 552 | if (list_ts) { 553 | int *list; 554 | int num = pcap_list_tstamp_types(pcap, &list); 555 | if (num == PCAP_ERROR) { 556 | pcap_perror(pcap, "pcap_list_tstamp_types"); 557 | return -1; 558 | } 559 | 560 | for (int i = 0; i < num; i++) { 561 | const char *name = pcap_tstamp_type_val_to_name(list[i]); 562 | const char *desc = pcap_tstamp_type_val_to_description(list[i]); 563 | if (!name || !desc) { 564 | ret = -1; 565 | break; 566 | } 567 | printf("%d: %s (%s)\n", list[i], name, desc); 568 | } 569 | 570 | pcap_free_tstamp_types(list); 571 | return ret; 572 | } 573 | 574 | ret = pcap_set_snaplen(pcap, 1500); 575 | if (ret) { 576 | fprintf(stderr, "pcap_set_snaplen: %s\n", pcap_error(pcap, ret)); 577 | return -1; 578 | } 579 | 580 | ret = pcap_set_promisc(pcap, 0); 581 | if (ret) { 582 | fprintf(stderr, "pcap_set_promisc: %s\n", pcap_error(pcap, ret)); 583 | return -1; 584 | } 585 | 586 | ret = pcap_set_timeout(pcap, 100); 587 | if (ret) { 588 | fprintf(stderr, "pcap_set_timeout: %s\n", pcap_error(pcap, ret)); 589 | return -1; 590 | } 591 | 592 | if (ts_type != NULL) { 593 | int type = pcap_tstamp_type_name_to_val(ts_type); 594 | if (type == PCAP_ERROR) { 595 | fprintf(stderr, "invalid type name (%s), check the list\n", optarg); 596 | return EINVAL; 597 | } 598 | ret = pcap_set_tstamp_type(pcap, type); 599 | if (ret != 0) { 600 | fprintf(stderr, "pcap_set_tstamp_type: %s\n", pcap_error(pcap, ret)); 601 | return -1; 602 | } 603 | } 604 | 605 | ret = pcap_activate(pcap); 606 | if (ret) { 607 | /* error */ 608 | if (ret < 0) { 609 | fprintf(stderr, "pcap_activate: %s\n", pcap_error(pcap, ret)); 610 | return -1; 611 | } 612 | /* warning */ 613 | if (ret > 0) 614 | fprintf(stderr, "pcap_activate: %s\n", pcap_error(pcap, ret)); 615 | } 616 | 617 | snprintf(filter_exp, sizeof(filter_exp), "ip dst host %s and port %i", ip_dst, port); 618 | 619 | if (pcap_compile(pcap, &fp, filter_exp, 0, net) == -1) { 620 | fprintf(stderr, "Couldn't parse filter %s: %s\n", filter_exp, pcap_geterr(pcap)); 621 | return -1; 622 | } 623 | if (pcap_setfilter(pcap, &fp) == -1) { 624 | fprintf(stderr, "Couldn't install filter %s: %s\n", filter_exp, pcap_geterr(pcap)); 625 | return -1; 626 | } 627 | 628 | /* Create socket */ 629 | int fd = socket(AF_INET, SOCK_DGRAM, 0); 630 | if(fd < 0) { 631 | perror("socket"); 632 | return -11; 633 | } 634 | 635 | i = 1; 636 | if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&i, sizeof(i)) == -1) { 637 | perror("setsockopt SOL_SOCKET SO_REUSEADDR"); 638 | close(fd); 639 | return -1; 640 | } 641 | 642 | struct sockaddr_in addr; 643 | memset(&addr, 0, sizeof(addr)); 644 | addr.sin_family = AF_INET; 645 | addr.sin_addr.s_addr = htonl(INADDR_ANY); 646 | addr.sin_port = htons(port); 647 | 648 | /* Bind */ 649 | if (bind(fd, (struct sockaddr*) &addr, sizeof(addr)) < 0) { 650 | perror("bind"); 651 | return -1; 652 | } 653 | 654 | if (ip_src) { 655 | /* Source-specific multicast */ 656 | struct ip_mreq_source imr; 657 | imr.imr_multiaddr.s_addr = inet_addr(ip_dst); 658 | imr.imr_interface.s_addr = ip_src_addr.sin_addr.s_addr; 659 | imr.imr_sourceaddr.s_addr = inet_addr(ip_src); 660 | 661 | if (setsockopt(fd, IPPROTO_IP, IP_ADD_SOURCE_MEMBERSHIP, (char *)&imr, sizeof(imr)) < 0) { 662 | fprintf(stderr, "couldn't join multicast group (%m)\n"); 663 | return 1; 664 | } 665 | } else { 666 | if (IN_MULTICAST(ntohl(inet_addr(ip_dst)))) { 667 | struct ip_mreqn mreq; 668 | mreq.imr_multiaddr.s_addr = inet_addr(ip_dst); 669 | mreq.imr_address.s_addr = INADDR_ANY; 670 | mreq.imr_ifindex = if_nametoindex(miface); 671 | if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*) &mreq, sizeof(mreq)) < 0){ 672 | perror("setsockopt IPPROTO_IP IP_ADD_MEMBERSHIP"); 673 | return -1; 674 | } 675 | } 676 | } 677 | 678 | switch (mode) { 679 | case MODE_2022_6: 680 | pcap_loop(pcap, packet_count, got_packet_2022_6, (u_char*)pcap); 681 | break; 682 | 683 | case MODE_2110_ANCILLARY: 684 | pcap_loop(pcap, packet_count, got_packet_ancillary, (u_char*)pcap); 685 | break; 686 | 687 | case MODE_2110_VIDEO: 688 | pcap_loop(pcap, packet_count, got_packet_2110_video, (u_char*)pcap); 689 | break; 690 | 691 | case MODE_2110_AUDIO: 692 | pcap_loop(pcap, packet_count, got_packet, (u_char*)pcap); 693 | break; 694 | } 695 | 696 | close(fd); 697 | 698 | return 0; 699 | } 700 | 701 | --------------------------------------------------------------------------------