├── README.md ├── src ├── Makefile.am ├── bytestoint.h ├── rtp.c ├── writer.c ├── utils.c ├── aoip-recorder.h ├── aoip-recorder.c ├── ar_config.c └── socket.c ├── test ├── fixtures │ ├── mono.sdp │ ├── xnode.sdp │ └── aoip44.sdp ├── Makefile.am ├── check_rtp.tc └── check_config.tc ├── Makefile.am ├── .travis.yml ├── .gitignore ├── LICENSE ├── autogen.sh └── configure.ac /README.md: -------------------------------------------------------------------------------- 1 | AoIP Recorder 2 | ============= 3 | 4 | License: MIT 5 | -------------------------------------------------------------------------------- /src/Makefile.am: -------------------------------------------------------------------------------- 1 | 2 | bin_PROGRAMS = aoip-recorder 3 | aoip_recorder_SOURCES = \ 4 | aoip-recorder.c \ 5 | aoip-recorder.h \ 6 | ar_config.c \ 7 | bytestoint.h \ 8 | rtp.c \ 9 | socket.c \ 10 | utils.c \ 11 | writer.c 12 | -------------------------------------------------------------------------------- /test/fixtures/mono.sdp: -------------------------------------------------------------------------------- 1 | v=0 2 | o=njh 3745070387 3745070387 IN IP4 10.101.10.179 3 | s=My Session 4 | t=0 0 5 | m=audio 1234 RTP/AVP 11 6 | c=IN IP4 224.123.234.56/5 7 | a=rtpmap:96 L16/220500/1 8 | a=rtpmap:0 PCMU/8000/1 9 | a=rtpmap:11 L16/44100/1 10 | -------------------------------------------------------------------------------- /test/fixtures/xnode.sdp: -------------------------------------------------------------------------------- 1 | v=0 2 | o=- 1 1 IN IP4 10.10.10.5 3 | s=Channel 501 4 | c=IN IP4 239.192.10.5 5 | t=0 0 6 | a=type:multicast 7 | m=audio 5004 RTP/AVP 96 8 | a=rtpmap:96 L24/48000/2 9 | a=ts-refclk:ptp=IEEE1588-2008:54-58-10-FF-FE-62-13-45:0 10 | a=mediaclk:direct=0 11 | a=sync-time:0 12 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | AUTOMAKE_OPTIONS = foreign 2 | 3 | SUBDIRS = src . 4 | 5 | if HAVE_CHECK 6 | SUBDIRS += test 7 | endif 8 | 9 | test: check 10 | 11 | # Copy README.md to README when building distribution 12 | dist-hook: 13 | [ -f README.md ] && cat README.md > README || true 14 | [ -f NEWS.md ] && cat NEWS.md > NEWS || true 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | sudo: false 3 | 4 | compiler: 5 | - gcc 6 | 7 | addons: 8 | apt: 9 | packages: 10 | - build-essential 11 | - pkg-config 12 | - automake 13 | - autoconf 14 | - libtool 15 | - libsndfile1-dev 16 | 17 | script: 18 | - ./autogen.sh 19 | - make 20 | - make distcheck 21 | -------------------------------------------------------------------------------- /test/fixtures/aoip44.sdp: -------------------------------------------------------------------------------- 1 | v=0 2 | o=- 19868418 19868458 IN IP4 169.254.98.63 3 | s=AOIP44-serial-9999 : 2 4 | c=IN IP4 239.65.125.63/32 5 | t=0 0 6 | a=keywds:Dante 7 | m=audio 5004 RTP/AVP 97 8 | i=2 channels: TxChan 0, TxChan 1 9 | a=recvonly 10 | a=rtpmap:97 L24/48000/2 11 | a=ptime:1 12 | a=ts-refclk:ptp=IEEE1588-2008:00-00-00-FF-FE-00-00-00:0 13 | a=mediaclk:direct=1199069607 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | *.ko 4 | *.obj 5 | *.elf 6 | 7 | # Temporary files 8 | *.log 9 | 10 | # Automake generated files 11 | .deps 12 | .libs 13 | Makefile 14 | Makefile.in 15 | 16 | # NEWS and README are generated from the markdown files 17 | /README 18 | /NEWS 19 | 20 | /aclocal.m4 21 | /autom4te.cache 22 | /build-scripts/* 23 | /config.cache 24 | /config.log 25 | /config.status 26 | /configure 27 | 28 | /src/config.h 29 | /src/config.h.in 30 | /src/aoip-recorder 31 | /src/stamp-h1 32 | 33 | # Generated Test files 34 | /test/*.c 35 | /test/*.t 36 | /test/*.trs 37 | -------------------------------------------------------------------------------- /test/Makefile.am: -------------------------------------------------------------------------------- 1 | AM_CFLAGS = $(CHECK_CFLAGS) \ 2 | -DFIXTURE_DIR=\"$(srcdir)/fixtures/\" \ 3 | -I$(top_srcdir)/src 4 | AM_LDFLAGS = $(CHECK_LIBS) 5 | 6 | check_PROGRAMS = check_config.t check_rtp.t 7 | TESTS = $(check_PROGRAMS) 8 | 9 | .tc.c: 10 | checkmk $< > $@ || rm -f $@ 11 | 12 | check_config_t_SOURCES = \ 13 | check_config.tc \ 14 | $(top_srcdir)/src/ar_config.c \ 15 | $(top_srcdir)/src/utils.c 16 | 17 | check_rtp_t_SOURCES = \ 18 | check_rtp.tc \ 19 | $(top_srcdir)/src/rtp.c \ 20 | $(top_srcdir)/src/socket.c \ 21 | $(top_srcdir)/src/utils.c 22 | 23 | EXTRA_DIST = \ 24 | fixtures/aoip44.sdp \ 25 | fixtures/mono.sdp \ 26 | fixtures/xnode.sdp 27 | 28 | CLEANFILES = *.c 29 | -------------------------------------------------------------------------------- /src/bytestoint.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | bytestoint.h 4 | 5 | Copyright (C) 2018 Nicholas Humfrey 6 | License: MIT 7 | 8 | */ 9 | 10 | #ifndef BYTES_TO_INT_H 11 | #define BYTES_TO_INT_H 12 | 13 | // Convert big-endian 16-bit array of bytes to an integer 14 | #define bytesToInt16(a) (((uint16_t)(a)[0] << 8) | (a)[1]) 15 | 16 | // Convert big-endian 24-bit array of bytes to an integer 17 | // Leaves the bottom 8-bits set to 0 18 | #define bytesToInt24(a) (((uint32_t)(a)[0] << 24) | \ 19 | ((uint32_t)(a)[1] << 16) | \ 20 | ((uint32_t)(a)[2] << 8)) 21 | 22 | // Convert big-endian 32-bit array of bytes to an integer 23 | #define bytesToInt32(a) (((uint32_t)(a)[0] << 24) | \ 24 | ((uint32_t)(a)[1] << 16) | \ 25 | ((uint32_t)(a)[2] << 8) | (a)[3]) 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /test/check_rtp.tc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "aoip-recorder.h" 5 | 6 | #suite aoip_recorder_rtp 7 | 8 | #test test_parse 9 | const uint8_t data[] = { 10 | 0x80, 0x61, 0xee, 0x14, 0xa2, 0x32, 0x12, 0x4c, 11 | 0xe9, 0xf8, 0xd8, 0x33, 0xf8, 0x88, 0x63, 0xf8, 12 | 0x58, 0xef, 0xf5, 0x7b, 0x2c, 0xf5, 0x34, 0xe7 13 | }; 14 | ar_rtp_packet_t packet; 15 | memcpy(packet.buffer, data, sizeof(data)); 16 | packet.length = sizeof(data); 17 | 18 | ar_rtp_parse(&packet); 19 | ck_assert_int_eq(packet.version, 2); 20 | ck_assert_int_eq(packet.padding, 0); 21 | ck_assert_int_eq(packet.extension, 0); 22 | ck_assert_int_eq(packet.csrc_count, 0); 23 | ck_assert_int_eq(packet.marker, 0); 24 | ck_assert_int_eq(packet.payload_type, 97); 25 | 26 | ck_assert_int_eq(packet.sequence, 60948); 27 | ck_assert_int_eq(packet.timestamp, 2721190476); 28 | ck_assert_int_eq(packet.ssrc, 0xe9f8d833); 29 | 30 | ck_assert_int_eq(packet.payload_length, 12); 31 | ck_assert_int_eq(packet.payload[0], 0xf8); 32 | ck_assert_int_eq(packet.payload[1], 0x88); 33 | ck_assert_int_eq(packet.payload[2], 0x63); 34 | ck_assert_int_eq(packet.payload[3], 0xf8); 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Nicholas Humfrey 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/rtp.c: -------------------------------------------------------------------------------- 1 | /* 2 | rtp.c 3 | 4 | AoIP Recorder 5 | Copyright (C) 2018 Nicholas Humfrey 6 | License: MIT 7 | */ 8 | 9 | #include 10 | 11 | #include "config.h" 12 | #include "bytestoint.h" 13 | #include "aoip-recorder.h" 14 | 15 | 16 | #define bitMask(byte, mask, shift) ((byte & (mask << shift)) >> shift) 17 | 18 | int ar_rtp_parse( ar_rtp_packet_t* packet ) 19 | { 20 | int header_len = RTP_HEADER_LENGTH; 21 | 22 | // Byte 1 23 | packet->version = bitMask(packet->buffer[0], 0x02, 6); 24 | packet->padding = bitMask(packet->buffer[0], 0x01, 5); 25 | packet->extension = bitMask(packet->buffer[0], 0x01, 4); 26 | packet->csrc_count = bitMask(packet->buffer[0], 0x0F, 0); 27 | 28 | // Byte 2 29 | packet->marker = bitMask(packet->buffer[1], 0x01, 7); 30 | packet->payload_type = bitMask(packet->buffer[1], 0x7F, 0); 31 | 32 | // Bytes 3 and 4 33 | packet->sequence = bytesToInt16(&packet->buffer[2]); 34 | 35 | // Bytes 5-8 36 | packet->timestamp = bytesToInt32(&packet->buffer[4]); 37 | 38 | // Bytes 9-12 39 | packet->ssrc = bytesToInt32(&packet->buffer[8]); 40 | 41 | // Calculate the size of the payload 42 | // FIXME: skip over header extension 43 | header_len += (packet->csrc_count * 4); 44 | packet->payload_length = packet->length - header_len; 45 | packet->payload = packet->buffer + header_len; 46 | 47 | // FIXME: Remove padding from payload_length 48 | 49 | // Success 50 | return 0; 51 | } 52 | 53 | 54 | int ar_rtp_recv( ar_socket_t* socket, ar_rtp_packet_t* packet ) 55 | { 56 | int len = ar_socket_recv(socket, &packet->buffer, sizeof(packet->buffer)); 57 | 58 | // Failure or too short to be an RTP packet? 59 | if (len <= RTP_HEADER_LENGTH) return -1; 60 | 61 | packet->length = len; 62 | 63 | return ar_rtp_parse(packet); 64 | } 65 | -------------------------------------------------------------------------------- /autogen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Run this to set up the build system: configure, makefiles, etc. 3 | 4 | package="aoip-recorder" 5 | 6 | 7 | srcdir=`dirname $0` 8 | test -z "$srcdir" && srcdir=. 9 | 10 | cd "$srcdir" 11 | DIE=0 12 | 13 | (autoheader --version) < /dev/null > /dev/null 2>&1 || { 14 | echo 15 | echo "You must have autoconf installed to compile $package." 16 | echo "Download the appropriate package for your distribution," 17 | echo "or get the source tarball at ftp://ftp.gnu.org/pub/gnu/" 18 | DIE=1 19 | } 20 | 21 | (autoconf --version) < /dev/null > /dev/null 2>&1 || { 22 | echo 23 | echo "You must have autoconf installed to compile $package." 24 | echo "Download the appropriate package for your distribution," 25 | echo "or get the source tarball at ftp://ftp.gnu.org/pub/gnu/" 26 | DIE=1 27 | } 28 | 29 | (automake --version) < /dev/null > /dev/null 2>&1 || { 30 | echo 31 | echo "You must have automake installed to compile $package." 32 | echo "Download the appropriate package for your system," 33 | echo "or get the source from one of the GNU ftp sites" 34 | echo "listed in http://www.gnu.org/order/ftp.html" 35 | DIE=1 36 | } 37 | 38 | (pkg-config --version) < /dev/null > /dev/null 2>&1 || { 39 | echo 40 | echo "You must have pkg-config installed to compile $package." 41 | echo "Download the appropriate package for your system," 42 | echo "or get the source from http://pkgconfig.freedesktop.org/" 43 | DIE=1 44 | } 45 | 46 | 47 | if test "$DIE" -eq 1; then 48 | exit 1 49 | fi 50 | 51 | 52 | 53 | echo "Generating configuration files for $package, please wait...." 54 | 55 | run_cmd() { 56 | echo " running $* ..." 57 | if ! $*; then 58 | echo failed! 59 | exit 1 60 | fi 61 | } 62 | 63 | 64 | # Because git doesn't support empty directories 65 | if [ ! -d "$srcdir/build-scripts" ]; then 66 | mkdir "$srcdir/build-scripts" 67 | fi 68 | 69 | run_cmd aclocal 70 | run_cmd autoheader 71 | run_cmd automake --add-missing --copy 72 | run_cmd autoconf 73 | 74 | $srcdir/configure && echo 75 | -------------------------------------------------------------------------------- /src/writer.c: -------------------------------------------------------------------------------- 1 | /* 2 | writer.c 3 | 4 | AoIP Recorder 5 | Copyright (C) 2018 Nicholas Humfrey 6 | License: MIT 7 | */ 8 | 9 | #include 10 | #include 11 | 12 | #include "aoip-recorder.h" 13 | #include "bytestoint.h" 14 | 15 | 16 | 17 | SNDFILE * ar_writer_open( ar_config_t *config, const char* path) 18 | { 19 | SF_INFO sfinfo; 20 | 21 | sfinfo.format = SF_FORMAT_WAV | SF_ENDIAN_FILE; 22 | sfinfo.samplerate = config->sample_rate; 23 | sfinfo.channels = config->channel_count; 24 | 25 | switch (config->sample_size) { 26 | case 16: 27 | sfinfo.format |= SF_FORMAT_PCM_16; 28 | break; 29 | case 24: 30 | sfinfo.format |= SF_FORMAT_PCM_24; 31 | break; 32 | case 32: 33 | sfinfo.format |= SF_FORMAT_PCM_32; 34 | break; 35 | default: 36 | ar_error("Unsupported sample size: %d", config->sample_size); 37 | return NULL; 38 | break; 39 | } 40 | 41 | // Check that the format is valid 42 | if (!sf_format_check(&sfinfo)) { 43 | ar_error( "Output format is not valid." ); 44 | return NULL; 45 | } 46 | 47 | return sf_open(path, SFM_WRITE, &sfinfo); 48 | } 49 | 50 | void ar_writer_write(SNDFILE *file, uint8_t* payload, int payload_length) 51 | { 52 | int s32[RTP_MAX_PAYLOAD / 3]; 53 | sf_count_t written = 0; 54 | sf_count_t count = 0; 55 | int byte; 56 | 57 | if (payload_length > RTP_MAX_PAYLOAD) { 58 | ar_error("payload length is greater than maximum RTP payload size"); 59 | return; 60 | } 61 | 62 | if (payload_length % 3 != 0) { 63 | ar_warn("payload length is not a multiple of 3"); 64 | } 65 | 66 | // Convert payload to an array of 32-bit integers 67 | for(byte=0; byte < payload_length; byte += 3) { 68 | s32[count++] = bytesToInt24(&payload[byte]); 69 | } 70 | 71 | written = sf_write_int(file, s32, count); 72 | if (written != count) { 73 | ar_error("Failed to write audio to disk: %s", sf_strerror(file)); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | dnl Require autoconf version >= 2.59 2 | AC_PREREQ(2.59) 3 | 4 | 5 | dnl ############# Initialization 6 | 7 | AC_INIT([aoip-recorder], [0.0.1], []) 8 | AC_CONFIG_SRCDIR([src/aoip-recorder.c]) 9 | AC_CONFIG_AUX_DIR([build-scripts]) 10 | AC_CONFIG_MACRO_DIR([build-scripts]) 11 | AM_INIT_AUTOMAKE 12 | 13 | 14 | dnl ############# Compiler and tools Checks 15 | 16 | AC_PROG_CC 17 | AC_PROG_INSTALL 18 | AC_PROG_LN_S 19 | AC_C_INLINE 20 | 21 | 22 | 23 | dnl ############## Library Checks 24 | 25 | AC_CHECK_LIB([m], [sqrt], , [AC_MSG_ERROR(Can't find libm)]) 26 | AC_CHECK_LIB([m], [lrintf]) 27 | AC_CHECK_LIB([mx], [powf]) 28 | 29 | # Check for libsndfile 30 | PKG_CHECK_MODULES(SNDFILE, sndfile >= 1.0.18) 31 | 32 | 33 | PKG_CHECK_MODULES(CHECK, check >= 0.9.4, have_check="yes", have_check="no") 34 | if test x"$have_check" = "xyes"; then 35 | AC_CHECK_PROG(have_checkmk, [checkmk], [yes], [no]) 36 | if test x"$have_checkmk" = "xyes"; then 37 | AC_DEFINE([HAVE_CHECK], 1, [Define to 1 if check library is available]) 38 | else 39 | AC_MSG_WARN([Command 'checkmk' not found.]) 40 | AC_MSG_WARN([Download it here: http://micah.cowan.name/projects/checkmk/]) 41 | fi 42 | fi 43 | AM_CONDITIONAL(HAVE_CHECK, test x"$have_check" = "xyes" && 44 | test x"$have_checkmk" = "xyes") 45 | 46 | 47 | dnl ############## Header Checks 48 | 49 | AC_HEADER_STDC 50 | AC_CHECK_HEADERS([stdlib.h string.h unistd.h]) 51 | AC_CHECK_HEADERS( [termios.h] ) 52 | 53 | 54 | 55 | dnl ############## Function Checks 56 | 57 | AC_CHECK_FUNCS( usleep ) 58 | 59 | 60 | 61 | dnl ############## Compiler and Linker Flags 62 | 63 | CFLAGS="$CFLAGS -Wunused -Wall $SNDFILE_CFLAGS" 64 | LIBS="$LIBS $SNDFILE_LIBS" 65 | 66 | 67 | 68 | dnl ############## Output files 69 | 70 | AC_CONFIG_HEADERS([src/config.h]) 71 | 72 | AC_CONFIG_FILES([ 73 | Makefile 74 | src/Makefile 75 | test/Makefile 76 | ]) 77 | 78 | AC_OUTPUT 79 | 80 | 81 | dnl ############## Summary 82 | 83 | echo "" 84 | echo "Building $PACKAGE_NAME version $PACKAGE_VERSION." 85 | echo "" 86 | echo "Next type 'make' to begin compilation." 87 | 88 | -------------------------------------------------------------------------------- /src/utils.c: -------------------------------------------------------------------------------- 1 | /* 2 | utils.c 3 | 4 | AoIP Recorder 5 | Copyright (C) 2018 Nicholas Humfrey 6 | License: MIT 7 | */ 8 | 9 | #include "config.h" 10 | #include "aoip-recorder.h" 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | int running = TRUE; 19 | int exit_code = 0; 20 | int quiet = 0; 21 | int verbose = 0; 22 | 23 | static void termination_handler(int signum) 24 | { 25 | running = FALSE; 26 | switch(signum) { 27 | case SIGTERM: ar_info("Got termination signal"); break; 28 | case SIGINT: ar_info("Got interupt signal"); break; 29 | } 30 | signal(signum, termination_handler); 31 | } 32 | 33 | 34 | void setup_signal_hander() 35 | { 36 | signal(SIGTERM, termination_handler); 37 | signal(SIGINT, termination_handler); 38 | signal(SIGHUP, termination_handler); 39 | } 40 | 41 | void ar_log(ar_log_level level, const char *fmt, ...) 42 | { 43 | time_t t = time(NULL); 44 | char *time_str; 45 | va_list args; 46 | 47 | // Display the message level 48 | switch(level) { 49 | case AR_LOG_DEBUG: 50 | if (!verbose) 51 | return; 52 | fprintf(stderr, "[DEBUG] "); 53 | break; 54 | case AR_LOG_INFO: 55 | if (quiet) 56 | return; 57 | fprintf(stderr, "[INFO] "); 58 | break; 59 | case AR_LOG_WARN: 60 | fprintf(stderr, "[WARNING] "); 61 | break; 62 | case AR_LOG_ERROR: 63 | fprintf(stderr, "[ERROR] "); 64 | break; 65 | default: 66 | fprintf(stderr, "[UNKNOWN] "); 67 | break; 68 | } 69 | 70 | // Display timestamp 71 | time_str = ctime(&t); 72 | time_str[strlen(time_str) - 1] = 0; // remove \n 73 | fprintf(stderr, "%s ", time_str); 74 | 75 | // Display the error message 76 | va_start(args, fmt); 77 | vfprintf(stderr, fmt, args); 78 | fprintf(stderr, "\n"); 79 | va_end(args); 80 | 81 | // If an erron then stop 82 | if (level == AR_LOG_ERROR) { 83 | // Exit with a non-zero exit code if there was a fatal error 84 | exit_code++; 85 | if (running) { 86 | // Quit gracefully 87 | running = 0; 88 | } else { 89 | fprintf(stderr, "Fatal error while quiting; exiting immediately.\n"); 90 | exit(-1); 91 | } 92 | } 93 | } 94 | 95 | -------------------------------------------------------------------------------- /test/check_config.tc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "aoip-recorder.h" 5 | 6 | #suite aoip_recorder_config 7 | 8 | #test test_defaults 9 | ar_config_t config; 10 | ar_config_set_defaults(&config); 11 | ck_assert(config.address == NULL); 12 | ck_assert_str_eq(config.port, "5004"); 13 | ck_assert(config.ifname == NULL); 14 | ck_assert_int_eq(config.payload_type, -1); 15 | ck_assert_int_eq(config.sample_size, 24); 16 | ck_assert_int_eq(config.sample_rate, 48000); 17 | ck_assert_int_eq(config.channel_count, 2); 18 | 19 | #test test_set_address 20 | ar_config_t config; 21 | ar_config_set_defaults(&config); 22 | ar_config_set_address(&config, "127.0.0.1"); 23 | ck_assert_str_eq(config.address, "127.0.0.1"); 24 | ar_config_set_address(&config, NULL); 25 | ck_assert(config.address == NULL); 26 | ar_config_free(&config); 27 | 28 | #test test_set_port 29 | ar_config_t config; 30 | ar_config_set_defaults(&config); 31 | ar_config_set_port(&config, "5004"); 32 | ck_assert_str_eq(config.port, "5004"); 33 | ar_config_set_port(&config, NULL); 34 | ck_assert(config.port == NULL); 35 | ar_config_free(&config); 36 | 37 | #test test_set_ifname 38 | ar_config_t config; 39 | ar_config_set_defaults(&config); 40 | ar_config_set_ifname(&config, "eth0"); 41 | ck_assert_str_eq(config.ifname, "eth0"); 42 | ar_config_set_ifname(&config, NULL); 43 | ck_assert(config.ifname == NULL); 44 | ar_config_free(&config); 45 | 46 | #test test_set_sample_format_L16 47 | ar_config_t config; 48 | ar_config_set_sample_format(&config, "L16"); 49 | ck_assert_int_eq(config.sample_size, 16); 50 | 51 | #test test_set_sample_format_L24 52 | ar_config_t config; 53 | ar_config_set_sample_format(&config, "L24"); 54 | ck_assert_int_eq(config.sample_size, 24); 55 | 56 | #test test_set_sample_format_L32 57 | ar_config_t config; 58 | ar_config_set_sample_format(&config, "L32"); 59 | ck_assert_int_eq(config.sample_size, 32); 60 | 61 | // FIXME: Travis does not support this 62 | // #test-exit(-1) test_set_sample_format_PCM 63 | // ar_config_t config; 64 | // ar_config_set_sample_format(&config, "PCM"); 65 | 66 | #test test_sdp_aoip44 67 | ar_config_t config; 68 | ar_config_set_defaults(&config); 69 | 70 | ar_config_parse_sdp(&config, FIXTURE_DIR "aoip44.sdp"); 71 | ck_assert_str_eq(config.address, "239.65.125.63"); 72 | ck_assert_str_eq(config.port, "5004"); 73 | ck_assert_int_eq(config.payload_type, 97); 74 | ck_assert_int_eq(config.sample_size, 24); 75 | ck_assert_int_eq(config.sample_rate, 48000); 76 | ck_assert_int_eq(config.channel_count, 2); 77 | 78 | 79 | #test test_sdp_xnode 80 | ar_config_t config; 81 | ar_config_set_defaults(&config); 82 | 83 | ar_config_parse_sdp(&config, FIXTURE_DIR "xnode.sdp"); 84 | ck_assert_str_eq(config.address, "239.192.10.5"); 85 | ck_assert_str_eq(config.port, "5004"); 86 | ck_assert_int_eq(config.payload_type, 96); 87 | ck_assert_int_eq(config.sample_size, 24); 88 | ck_assert_int_eq(config.sample_rate, 48000); 89 | ck_assert_int_eq(config.channel_count, 2); 90 | 91 | 92 | #test test_sdp_mono 93 | ar_config_t config; 94 | ar_config_set_defaults(&config); 95 | 96 | ar_config_parse_sdp(&config, FIXTURE_DIR "mono.sdp"); 97 | ck_assert_str_eq(config.address, "224.123.234.56"); 98 | ck_assert_str_eq(config.port, "1234"); 99 | ck_assert_int_eq(config.payload_type, 11); 100 | ck_assert_int_eq(config.sample_size, 16); 101 | ck_assert_int_eq(config.sample_rate, 44100); 102 | ck_assert_int_eq(config.channel_count, 1); 103 | -------------------------------------------------------------------------------- /src/aoip-recorder.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | aoip-recorder.h 4 | 5 | Copyright (C) 2018 Nicholas Humfrey 6 | License: MIT 7 | 8 | */ 9 | 10 | #include "config.h" 11 | 12 | #ifndef AOIP_RECORDER_H 13 | #define AOIP_RECORDER_H 14 | 15 | #include 16 | #include 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include 24 | 25 | 26 | #define DEFAULT_PORT "5004" 27 | #define DEFAULT_SAMPLE_RATE 48000 28 | #define DEFAULT_SAMPLE_SIZE 24 29 | #define DEFAULT_CHANNEL_COUNT 2 30 | #define DEFAULT_PACKET_BUFFER_SIZE 4 31 | #define DEFAULT_FILE_DURATION 10.0 32 | 33 | #define RTP_MAX_PAYLOAD (1440) 34 | #define RTP_HEADER_LENGTH (12) 35 | 36 | 37 | #ifndef TRUE 38 | #define TRUE (1) 39 | #endif 40 | 41 | #ifndef FALSE 42 | #define FALSE (0) 43 | #endif 44 | 45 | 46 | 47 | typedef struct 48 | { 49 | char* address; 50 | char* port; 51 | char* ifname; 52 | 53 | int payload_type; 54 | int sample_rate; 55 | int sample_size; 56 | int channel_count; 57 | 58 | int packet_buffer_size; 59 | float file_duration; 60 | 61 | } ar_config_t; 62 | 63 | 64 | typedef struct 65 | { 66 | int fd; 67 | int is_multicast; 68 | 69 | struct sockaddr_storage saddr; 70 | 71 | union { 72 | struct ipv6_mreq imr6; 73 | struct ip_mreq imr; 74 | }; 75 | 76 | } ar_socket_t; 77 | 78 | typedef struct 79 | { 80 | uint8_t version; 81 | uint8_t padding; 82 | uint8_t extension; 83 | uint8_t csrc_count; 84 | uint8_t marker; 85 | uint8_t payload_type; 86 | 87 | uint16_t sequence; 88 | uint32_t timestamp; 89 | uint32_t ssrc; 90 | 91 | uint16_t payload_length; 92 | uint8_t *payload; 93 | 94 | uint16_t length; 95 | uint8_t buffer[1500]; 96 | 97 | } ar_rtp_packet_t; 98 | 99 | 100 | int ar_socket_open(ar_socket_t* sock, ar_config_t *config); 101 | int ar_socket_recv(ar_socket_t* sock, void* data, unsigned int len); 102 | void ar_socket_close(ar_socket_t* sock); 103 | 104 | void ar_config_set_defaults(ar_config_t *config); 105 | void ar_config_set_address(ar_config_t *config, const char *address); 106 | void ar_config_set_port(ar_config_t *config, const char *port); 107 | void ar_config_set_ifname(ar_config_t *config, const char *ifname); 108 | void ar_config_set_sample_format(ar_config_t *config, const char *fmt); 109 | void ar_config_set_payload_type(ar_config_t *config, int payload_type); 110 | void ar_config_parse_sdp(ar_config_t *config, const char* filename); 111 | void ar_config_free(ar_config_t *config); 112 | 113 | int ar_rtp_parse( ar_rtp_packet_t* packet ); 114 | int ar_rtp_recv( ar_socket_t* socket, ar_rtp_packet_t* packet ); 115 | 116 | SNDFILE *ar_writer_open( ar_config_t *config, const char* path); 117 | void ar_writer_write(SNDFILE *file, uint8_t* payload, int payload_length); 118 | 119 | 120 | // ------- Logging --------- 121 | 122 | void setup_signal_hander(); 123 | extern int running; 124 | extern int exit_code; 125 | extern int verbose; 126 | extern int quiet; 127 | 128 | typedef enum { 129 | AR_LOG_DEBUG, 130 | AR_LOG_INFO, 131 | AR_LOG_WARN, 132 | AR_LOG_ERROR 133 | } ar_log_level; 134 | 135 | 136 | void ar_log(ar_log_level level, const char *fmt, ...); 137 | 138 | // Only display debug if verbose 139 | #define ar_debug( ... ) \ 140 | ar_log(AR_LOG_DEBUG, __VA_ARGS__ ) 141 | 142 | // Don't show info when quiet 143 | #define ar_info( ... ) \ 144 | ar_log(AR_LOG_INFO, __VA_ARGS__ ) 145 | 146 | #define ar_warn( ... ) \ 147 | ar_log(AR_LOG_WARN, __VA_ARGS__ ) 148 | 149 | // All errors are fatal 150 | #define ar_error( ... ) \ 151 | ar_log(AR_LOG_ERROR, __VA_ARGS__ ) 152 | 153 | 154 | #endif 155 | -------------------------------------------------------------------------------- /src/aoip-recorder.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | aoip-recorder.c 4 | 5 | Copyright (C) 2018 Nicholas Humfrey 6 | License: MIT 7 | 8 | */ 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "aoip-recorder.h" 17 | 18 | 19 | // Globals 20 | ar_config_t config; 21 | ar_socket_t sock; 22 | 23 | 24 | static void usage(const char * progname) 25 | { 26 | fprintf(stderr, "AoIP Recorder version %s\n\n", PACKAGE_VERSION); 27 | fprintf(stderr, "%s [options] []\n", progname); 28 | fprintf(stderr, " -a
IP Address\n"); 29 | fprintf(stderr, " -i Interface Name to listen on\n"); 30 | fprintf(stderr, " -p Port Number (default %s)\n", DEFAULT_PORT); 31 | fprintf(stderr, " -r Sample Rate (default %d)\n", DEFAULT_SAMPLE_RATE); 32 | fprintf(stderr, " -f Audio Format (default L%d)\n", DEFAULT_SAMPLE_SIZE); 33 | fprintf(stderr, " -c Channel Count (default %d)\n", DEFAULT_CHANNEL_COUNT); 34 | fprintf(stderr, " -b Packet Buffer Size (default %d)\n", DEFAULT_PACKET_BUFFER_SIZE); 35 | fprintf(stderr, " -d File Duration (default %2.2f)\n", DEFAULT_FILE_DURATION); 36 | fprintf(stderr, " -v Verbose Logging\n"); 37 | fprintf(stderr, " -q Quiet Logging\n"); 38 | 39 | exit(EXIT_FAILURE); 40 | } 41 | 42 | static void parse_opts(int argc, char **argv, ar_config_t *config) 43 | { 44 | const char* progname = argv[0]; 45 | int ch; 46 | 47 | // Parse the options/switches 48 | while ((ch = getopt(argc, argv, "a:p:i:r:f:c:b:d:vq?h")) != -1) { 49 | switch (ch) { 50 | case 'a': 51 | ar_config_set_address(config, optarg); 52 | break; 53 | case 'p': 54 | ar_config_set_port(config, optarg); 55 | break; 56 | case 'i': 57 | ar_config_set_ifname(config, optarg); 58 | break; 59 | case 'r': 60 | config->sample_rate = atoi(optarg); 61 | break; 62 | case 'f': 63 | ar_config_set_sample_format(config, optarg); 64 | break; 65 | case 'c': 66 | config->channel_count = atoi(optarg); 67 | break; 68 | case 'b': 69 | config->packet_buffer_size = atoi(optarg); 70 | break; 71 | case 'd': 72 | config->file_duration = atoi(optarg); 73 | break; 74 | case 'v': 75 | verbose = TRUE; 76 | break; 77 | case 'q': 78 | quiet = TRUE; 79 | break; 80 | case '?': 81 | case 'h': 82 | default: 83 | usage(progname); 84 | } 85 | } 86 | 87 | // Check remaining arguments 88 | argc -= optind; 89 | argv += optind; 90 | if (argc == 1) { 91 | ar_config_parse_sdp(config, argv[0]); 92 | } else if (argc > 1) { 93 | usage(progname); 94 | } 95 | 96 | // Validate parameters 97 | if (quiet && verbose) { 98 | ar_error("Can't be quiet and verbose at the same time."); 99 | usage(progname); 100 | } 101 | 102 | if (config->address == NULL || strlen(config->address) < 1) { 103 | ar_error("No address specified"); 104 | usage(progname); 105 | } 106 | } 107 | 108 | int main(int argc, char *argv[]) 109 | { 110 | SNDFILE * file; 111 | int result; 112 | 113 | ar_config_set_defaults(&config); 114 | parse_opts(argc, argv, &config); 115 | setup_signal_hander(); 116 | 117 | result = ar_socket_open(&sock, &config); 118 | if (result) { 119 | return EXIT_FAILURE; 120 | } 121 | 122 | file = ar_writer_open(&config, "recording.wav"); 123 | if (file == NULL) { 124 | ar_error("Failed to open output file"); 125 | } 126 | 127 | while(running) { 128 | ar_rtp_packet_t packet; 129 | 130 | int result = ar_rtp_recv(&sock, &packet); 131 | if (result < 0) break; 132 | 133 | // Is the Payload Type what we were expecting? 134 | if (config.payload_type == -1) { 135 | ar_info("Payload type of first packet: %d", packet.payload_type); 136 | ar_config_set_payload_type(&config, packet.payload_type); 137 | } else if (config.payload_type != packet.payload_type) { 138 | ar_warn("Received unexpected Payload Type: %d", packet.payload_type); 139 | } 140 | 141 | ar_debug("RTP packet ts=%lu seq=%u", packet.timestamp, packet.sequence); 142 | 143 | ar_writer_write(file, packet.payload, packet.payload_length); 144 | } 145 | 146 | if (file) { 147 | sf_close(file); 148 | } 149 | 150 | ar_socket_close(&sock); 151 | ar_config_free(&config); 152 | 153 | return exit_code; 154 | } 155 | -------------------------------------------------------------------------------- /src/ar_config.c: -------------------------------------------------------------------------------- 1 | /* 2 | ar_config.c 3 | 4 | AoIP Recorder 5 | Copyright (C) 2018 Nicholas Humfrey 6 | License: MIT 7 | */ 8 | 9 | #include "config.h" 10 | #include "aoip-recorder.h" 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | void ar_config_set_defaults(ar_config_t *config) 20 | { 21 | memset(config, 0, sizeof(*config)); 22 | 23 | ar_config_set_address(config, NULL); 24 | ar_config_set_port(config, DEFAULT_PORT); 25 | ar_config_set_ifname(config, NULL); 26 | 27 | config->payload_type = -1; 28 | config->sample_rate = DEFAULT_SAMPLE_RATE; 29 | config->sample_size = DEFAULT_SAMPLE_SIZE; 30 | config->channel_count = DEFAULT_CHANNEL_COUNT; 31 | config->packet_buffer_size = DEFAULT_PACKET_BUFFER_SIZE; 32 | config->file_duration = DEFAULT_FILE_DURATION; 33 | } 34 | 35 | void ar_config_set_address(ar_config_t *config, const char *address) 36 | { 37 | if (config->address) { 38 | free(config->address); 39 | } 40 | 41 | if (address && strlen(address) > 1) { 42 | config->address = strdup(address); 43 | } else { 44 | config->address = NULL; 45 | } 46 | } 47 | 48 | void ar_config_set_port(ar_config_t *config, const char *port) 49 | { 50 | if (config->port) { 51 | free(config->port); 52 | } 53 | 54 | if (port && strlen(port) > 1) { 55 | config->port = strdup(port); 56 | } else { 57 | config->port = NULL; 58 | } 59 | } 60 | 61 | void ar_config_set_ifname(ar_config_t *config, const char *ifname) 62 | { 63 | if (config->ifname) { 64 | free(config->ifname); 65 | } 66 | 67 | if (ifname && strlen(ifname) > 1) { 68 | config->ifname = strdup(ifname); 69 | } else { 70 | config->ifname = NULL; 71 | } 72 | } 73 | 74 | void ar_config_set_sample_format(ar_config_t *config, const char *fmt) 75 | { 76 | if (strcmp(fmt, "L16") == 0) { 77 | config->sample_size = 16; 78 | } else if (strcmp(fmt, "L24") == 0) { 79 | config->sample_size = 24; 80 | } else if (strcmp(fmt, "L32") == 0) { 81 | config->sample_size = 32; 82 | } else { 83 | ar_error("Unsupported audio sample format: %s", fmt); 84 | exit(-1); 85 | } 86 | } 87 | 88 | void ar_config_set_payload_type(ar_config_t *config, int payload_type) 89 | { 90 | config->payload_type = payload_type; 91 | 92 | if (payload_type == 10) { 93 | config->sample_size = 16; 94 | config->sample_rate = 44100; 95 | config->channel_count = 2; 96 | } else if (payload_type == 11) { 97 | config->sample_size = 16; 98 | config->sample_rate = 44100; 99 | config->channel_count = 1; 100 | } else if (payload_type < 96) { 101 | ar_error("Unsupported static payload type: %d", payload_type); 102 | } 103 | } 104 | 105 | static void sdp_connection_parse(ar_config_t *config, char* line) 106 | { 107 | char *nettype = strsep(&line, " "); 108 | char *addrtype = strsep(&line, " "); 109 | char *addr = strsep(&line, " /"); 110 | 111 | if (addr == NULL || strcmp(nettype, "IN") != 0) { 112 | ar_error("SDP net type is not 'IN': %s", nettype); 113 | } 114 | 115 | if (addr == NULL || (strcmp(addrtype, "IP4") != 0 && strcmp(addrtype, "IP6") != 0)) { 116 | ar_warn("SDP Address type is not IP4/IP6: %s", nettype); 117 | } 118 | 119 | if (addr == NULL || strlen(addr) > 7) { 120 | ar_config_set_address(config, addr); 121 | } else { 122 | ar_error("Invalid connection address: %s", addr); 123 | } 124 | } 125 | 126 | static void sdp_media_parse(ar_config_t *config, char* line) 127 | { 128 | char *media = strsep(&line, " "); 129 | char *port = strsep(&line, " "); 130 | char *proto = strsep(&line, " "); 131 | char *fmt = strsep(&line, " "); 132 | 133 | if (media == NULL || strcmp(media, "audio") != 0) { 134 | ar_error("SDP media type is not audio: %s", media); 135 | } 136 | 137 | if (port == NULL || strlen(port) > 2) { 138 | ar_config_set_port(config, port); 139 | } else { 140 | ar_error("Invalid connection port: %s", port); 141 | } 142 | 143 | if (proto == NULL || strcmp(proto, "RTP/AVP") != 0) { 144 | ar_error("SDP transport protocol is not RTP/AVP: %s", proto); 145 | } 146 | 147 | if (fmt == NULL || strlen(fmt) > 2) { 148 | ar_error("SDP media format is not valid: %s", fmt); 149 | } else { 150 | ar_config_set_payload_type(config, atoi(fmt)); 151 | ar_debug("SDP Payload Type: %d", config->payload_type); 152 | } 153 | } 154 | 155 | static void sdp_attribute_parse(ar_config_t *config, char* line) 156 | { 157 | char *attr = strsep(&line, ":"); 158 | 159 | if (strcmp(attr, "rtpmap") == 0) { 160 | char *pt = strsep(&line, " "); 161 | 162 | if (pt != NULL && atoi(pt) == config->payload_type) { 163 | char *format = strsep(&line, "/"); 164 | char *sample_rate = strsep(&line, "/"); 165 | char *channel_count = strsep(&line, "/"); 166 | ar_config_set_sample_format(config, format); 167 | config->sample_rate = atoi(sample_rate); 168 | config->channel_count = atoi(channel_count); 169 | ar_debug( 170 | "SDP Audio Format: L%d/%d/%d", 171 | config->sample_size, 172 | config->sample_rate, 173 | config->channel_count 174 | ); 175 | } 176 | } 177 | } 178 | 179 | void ar_config_parse_sdp(ar_config_t *config, const char* filename) 180 | { 181 | FILE* file = NULL; 182 | 183 | if (strcmp(filename, "-") == 0) { 184 | file = stdin; 185 | } else { 186 | file = fopen(filename, "rb"); 187 | } 188 | 189 | if (!file) { 190 | ar_error("Failed to open file: %s", strerror(errno)); 191 | return; 192 | } 193 | 194 | while(!feof(file)) { 195 | char line[1024]; 196 | char *result; 197 | int i; 198 | 199 | result = fgets(line, sizeof(line), file); 200 | if (result == NULL) 201 | break; 202 | 203 | // Remove whitespace from the end of the line 204 | for(i=strlen(line); i > 0; i--) { 205 | if (isspace(line[i]) || line[i] == '\0') { 206 | line[i] = '\0'; 207 | } else { 208 | break; 209 | } 210 | } 211 | 212 | if (strlen(line) < 3) { 213 | ar_warn("Invalid line in SDP file: line is too short"); 214 | continue; 215 | } 216 | 217 | if (line[1] != '=') { 218 | ar_warn("Invalid line in SDP file: second character of line isn't ="); 219 | continue; 220 | } 221 | 222 | switch (line[0]) { 223 | case 'v': 224 | if (strcmp(&line[2], "0") != 0) { 225 | ar_warn("SDP version number is not 0: %s", &line[2]); 226 | } 227 | break; 228 | 229 | case 's': 230 | ar_info("SDP Subject: %s", &line[2]); 231 | break; 232 | 233 | case 'c': 234 | sdp_connection_parse(config, &line[2]); 235 | break; 236 | 237 | case 'm': 238 | sdp_media_parse(config, &line[2]); 239 | break; 240 | 241 | case 'a': 242 | sdp_attribute_parse(config, &line[2]); 243 | break; 244 | } 245 | } 246 | 247 | fclose(file); 248 | } 249 | 250 | void ar_config_free(ar_config_t *config) 251 | { 252 | if (config->address) { 253 | free(config->address); 254 | config->address = NULL; 255 | } 256 | 257 | if (config->port) { 258 | free(config->port); 259 | config->port = NULL; 260 | } 261 | 262 | if (config->ifname) { 263 | free(config->ifname); 264 | config->ifname = NULL; 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /src/socket.c: -------------------------------------------------------------------------------- 1 | /* 2 | socket.c 3 | 4 | AoIP Recorder 5 | Copyright (C) 2018 Nicholas Humfrey 6 | License: MIT 7 | */ 8 | 9 | #include "config.h" 10 | #include "aoip-recorder.h" 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #ifdef HAVE_UNISTD_H 18 | #include 19 | #endif 20 | 21 | #ifdef HAVE_SYS_TYPES_H 22 | #include 23 | #endif 24 | 25 | #include 26 | #include 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | 37 | // Added to ensure compilation with KAME 38 | #ifndef IPV6_ADD_MEMBERSHIP 39 | #ifdef IPV6_JOIN_GROUP 40 | #define IPV6_ADD_MEMBERSHIP IPV6_JOIN_GROUP 41 | #define IPV6_DROP_MEMBERSHIP IPV6_LEAVE_GROUP 42 | #endif 43 | #endif 44 | 45 | 46 | static int _bind_socket(ar_socket_t *sock, const char* address, const char* port) 47 | { 48 | struct addrinfo hints, *res, *cur; 49 | int error = -1; 50 | int retval = -1; 51 | 52 | // Setup hints for getaddrinfo 53 | memset(&hints, 0, sizeof(hints)); 54 | hints.ai_family = AF_UNSPEC; 55 | hints.ai_socktype = SOCK_DGRAM; 56 | 57 | error = getaddrinfo(address, port, &hints, &res); 58 | if (error || res == NULL) { 59 | ar_warn("getaddrinfo failed: %s", gai_strerror(error)); 60 | return error; 61 | } 62 | 63 | // Check each of the results 64 | cur = res; 65 | while (cur) { 66 | sock->fd = socket(cur->ai_family, cur->ai_socktype, cur->ai_protocol); 67 | 68 | if (sock->fd >= 0) { 69 | int one = 1; 70 | // These socket options help re-binding to a socket 71 | // after a previous process was killed 72 | 73 | #ifdef SO_REUSEADDR 74 | if (setsockopt(sock->fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one))) { 75 | perror("SO_REUSEADDR failed"); 76 | } 77 | #endif 78 | 79 | #ifdef SO_REUSEPORT 80 | if (setsockopt(sock->fd, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one))) { 81 | perror("SO_REUSEPORT failed"); 82 | } 83 | #endif 84 | 85 | if (bind(sock->fd, cur->ai_addr, cur->ai_addrlen) == 0) { 86 | // Success! 87 | memcpy( &sock->saddr, cur->ai_addr, cur->ai_addrlen ); 88 | retval = 0; 89 | break; 90 | } 91 | 92 | close(sock->fd); 93 | } 94 | cur=cur->ai_next; 95 | } 96 | 97 | freeaddrinfo( res ); 98 | 99 | return retval; 100 | } 101 | 102 | 103 | static int _is_multicast(struct sockaddr_storage *addr) 104 | { 105 | 106 | switch (addr->ss_family) { 107 | case AF_INET: { 108 | struct sockaddr_in *addr4=(struct sockaddr_in *)addr; 109 | return IN_MULTICAST(ntohl(addr4->sin_addr.s_addr)); 110 | } 111 | break; 112 | 113 | case AF_INET6: { 114 | struct sockaddr_in6 *addr6=(struct sockaddr_in6 *)addr; 115 | return IN6_IS_ADDR_MULTICAST(&addr6->sin6_addr); 116 | } 117 | break; 118 | 119 | default: { 120 | return -1; 121 | } 122 | } 123 | } 124 | 125 | static int _get_interface_ipv4_addr(const char* ifname, struct in_addr *addr) 126 | { 127 | struct ifaddrs *addrs, *cur; 128 | int retval = -1; 129 | int foundAddress = FALSE; 130 | 131 | if (ifname == NULL || strlen(ifname) < 1) { 132 | addr->s_addr = INADDR_ANY; 133 | return 0; 134 | } 135 | 136 | // Get a linked list of all the interfaces 137 | retval = getifaddrs(&addrs); 138 | if (retval < 0) { 139 | return retval; 140 | } 141 | 142 | // Iterate through each of the interfaces 143 | for(cur = addrs; cur; cur = cur->ifa_next) 144 | { 145 | if (cur->ifa_addr && cur->ifa_addr->sa_family == AF_INET) { 146 | if (strcmp(cur->ifa_name, ifname) == 0) { 147 | struct sockaddr_in *sockaddr = (struct sockaddr_in *)cur->ifa_addr; 148 | addr->s_addr = sockaddr->sin_addr.s_addr; 149 | foundAddress = TRUE; 150 | break; 151 | } 152 | } 153 | } 154 | 155 | freeifaddrs(addrs); 156 | 157 | return foundAddress ? 0 : -1; 158 | } 159 | 160 | 161 | static int _join_group( ar_socket_t *sock, const char* ifname) 162 | { 163 | unsigned int if_index = 0; 164 | int retval = -1; 165 | 166 | // If a network interface name was given, check it exists 167 | if (ifname != NULL && strlen(ifname) > 0) { 168 | if_index = if_nametoindex(ifname); 169 | if (if_index == 0) { 170 | if (errno == ENXIO) { 171 | ar_error("Network interface not found: %s", ifname); 172 | return -1; 173 | } else { 174 | ar_error("Error looking up interface: %s", strerror(errno)); 175 | } 176 | } 177 | } 178 | 179 | switch (sock->saddr.ss_family) { 180 | case AF_INET: 181 | 182 | sock->imr.imr_multiaddr.s_addr= 183 | ((struct sockaddr_in*)&sock->saddr)->sin_addr.s_addr; 184 | 185 | retval = _get_interface_ipv4_addr( ifname, &sock->imr.imr_interface); 186 | if (retval) { 187 | ar_error("Failed to get IPv4 address of network interface"); 188 | } else { 189 | retval = setsockopt(sock->fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, 190 | &sock->imr, sizeof(sock->imr)); 191 | if (retval < 0) { 192 | ar_warn("IP_ADD_MEMBERSHIP failed: %s", strerror(errno)); 193 | } 194 | } 195 | break; 196 | 197 | case AF_INET6: 198 | 199 | memcpy(&sock->imr6.ipv6mr_multiaddr, 200 | &(((struct sockaddr_in6*)&sock->saddr)->sin6_addr), 201 | sizeof(struct in6_addr)); 202 | 203 | sock->imr6.ipv6mr_interface = if_index; 204 | 205 | retval = setsockopt(sock->fd, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, 206 | &sock->imr6, sizeof(sock->imr6)); 207 | 208 | if (retval < 0) { 209 | ar_warn("IPV6_ADD_MEMBERSHIP failed: %s", strerror(errno)); 210 | } 211 | break; 212 | } 213 | 214 | return retval; 215 | } 216 | 217 | 218 | static int _leave_group( ar_socket_t* sock ) 219 | { 220 | int retval = -1; 221 | 222 | switch (sock->saddr.ss_family) { 223 | case AF_INET: { 224 | retval= setsockopt(sock->fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, 225 | &(sock->imr), sizeof(sock->imr)); 226 | if (retval<-1) 227 | perror("IP_DROP_MEMBERSHIP failed"); 228 | } 229 | break; 230 | 231 | case AF_INET6: { 232 | retval= setsockopt(sock->fd, IPPROTO_IPV6, IPV6_DROP_MEMBERSHIP, 233 | &(sock->imr6), sizeof(sock->imr6)); 234 | if (retval<-1) 235 | perror("IPV6_DROP_MEMBERSHIP failed"); 236 | } 237 | break; 238 | 239 | } 240 | 241 | return retval; 242 | } 243 | 244 | 245 | int ar_socket_open(ar_socket_t* sock, ar_config_t *config) 246 | { 247 | // Initialise 248 | memset(sock, 0, sizeof(ar_socket_t)); 249 | sock->fd = 0; 250 | sock->is_multicast = 0; // not joined yet 251 | 252 | ar_info("Opening socket: %s/%s", config->address, config->port); 253 | 254 | if (_bind_socket(sock, config->address, config->port)) { 255 | ar_error("Failed to open socket."); 256 | return -1; 257 | } 258 | 259 | // Join multicast group ? 260 | sock->is_multicast = _is_multicast( &sock->saddr ); 261 | if (sock->is_multicast == 1) { 262 | 263 | ar_debug("Joining multicast group"); 264 | if (_join_group(sock, config->ifname)) { 265 | sock->is_multicast = 0; 266 | ar_socket_close(sock); 267 | return -1; 268 | } 269 | 270 | } else if (sock->is_multicast != 0) { 271 | ar_error("Error checking if address is multicast"); 272 | } 273 | 274 | return 0; 275 | } 276 | 277 | 278 | int ar_socket_recv( ar_socket_t* sock, void* data, unsigned int len) 279 | { 280 | fd_set readfds; 281 | struct timeval timeout; 282 | int packet_len, retval; 283 | 284 | timeout.tv_sec = 10; 285 | timeout.tv_usec = 0; 286 | 287 | // Watch socket to see when it has input. 288 | FD_ZERO(&readfds); 289 | FD_SET(sock->fd, &readfds); 290 | retval = select(FD_SETSIZE, &readfds, NULL, NULL, &timeout); 291 | 292 | // Check return value 293 | if (retval == -1) { 294 | perror("select()"); 295 | return -1; 296 | 297 | } else if (retval==0) { 298 | ar_warn("Timed out waiting for packet after %ld seconds", timeout.tv_sec); 299 | return 0; 300 | } 301 | 302 | // Packet is waiting - read it in 303 | packet_len = recv(sock->fd, data, len, 0); 304 | 305 | return packet_len; 306 | } 307 | 308 | 309 | void ar_socket_close(ar_socket_t* sock ) 310 | { 311 | // Drop Multicast membership 312 | if (sock->is_multicast) 313 | { 314 | _leave_group( sock ); 315 | } 316 | 317 | // Close the sockets 318 | if (sock->fd >= 0) { 319 | close(sock->fd); 320 | sock->fd = -1; 321 | } 322 | } 323 | --------------------------------------------------------------------------------