Merge remote-tracking branch 'upstream/master'

pull/225/head
Stefan Mititelu 9 years ago
commit a3f0e30307

@ -158,9 +158,12 @@ option and which are reproduced below:
-F, --no-fallback Only start when kernel module is available
-i, --interface=[NAME/]IP[!IP] Local interface for RTP
-l, --listen-tcp=[IP:]PORT TCP port to listen on
-c, --listen-cli=[IP46:]PORT TCP port to listen on, CLI (command line interface)
-u, --listen-udp=[IP46:]PORT UDP port to listen on
-n, --listen-ng=[IP46:]PORT UDP port to listen on, NG protocol
-c, --listen-cli=[IP46:]PORT TCP port to listen on, CLI (command line interface)
-g, --graphite=IP46:PORT TCP address of graphite statistics server
-G, --graphite-interval=INT Graphite data statistics send interval
--graphite-prefix=STRING Graphite prefix for every line
-T, --tos=INT TOS value to set on streams
-o, --timeout=SECS RTP timeout
-s, --silent-timeout=SECS RTP timeout for muted
@ -185,10 +188,10 @@ option and which are reproduced below:
-d, --delete-delay Delay for deleting a session from memory.
--sip-source Use SIP source address by default
--dtls-passive Always prefer DTLS passive role
-g, --graphite=[IP46:]PORT TCP address of graphite statistics server
-G, --graphite-interval=INT Graphite data statistics send interval
--graphite-prefix=STRING Graphite prefix for every line
--max-sessions=INT Limit the number of maximum concurrent sessions
--homer=IP46:PORT Address of Homer server for RTCP stats
--homer-protocol=udp|tcp Transport protocol for Homer (default udp)
--homer-id=INT 'Capture ID' to use within the HEP protocol
Most of these options are indeed optional, with two exceptions. It's mandatory to specify at least one local
IP address through `--interface`, and at least one of the `--listen-...` options must be given.
@ -278,6 +281,18 @@ The options are described in more detail below.
TCP ip and port to listen for the CLI (command line interface).
* -g, --graphite
Address of the graphite statistics server.
* -w, --graphite-interval
Interval of the time when information is sent to the graphite server.
* --graphite-prefix
Add a prefix for every graphite line.
* -t, --tos
Takes an integer as argument and if given, specifies the TOS value that should be set in outgoing
@ -422,18 +437,6 @@ The options are described in more detail below.
Selects the internal format of the XMLRPC callback message for B2BUA call teardown. 0 is for SEMS,
1 is for a generic format containing the call-ID only.
* -g, --graphite
Address of the graphite statistics server.
* -w, --graphite-interval
Interval of the time when information is sent to the graphite server.
* --graphite-prefix
Add a prefix for every graphite line.
* --max-sessions
Limit the number of maximum concurrent sessions. Set at startup via MAX_SESSIONS in config file. Set at runtime via rtpengine-ctl util.
@ -443,6 +446,21 @@ The options are described in more detail below.
Disable feature: 'rtpengine-ctl set maxsessions -1'
By default, the feature is disabled (i.e. maxsessions == -1).
* --homer
Enables sending the decoded contents of RTCP packets to a Homer SIP capture server. The transport
is HEP version 3 and payload format is JSON. This argument takes an IP address and a port number
as value.
* --homer-protocol
Can be either "udp" or "tcp" with "udp" being the default.
* --homer-id
The HEP protocol used by Homer contains a "capture ID" used to distinguish different sources
of capture data. This ID can be specified using this argument.
A typical command line (enabling both UDP and NG protocols) thus may look like:

@ -67,7 +67,7 @@ endif
SRCS= main.c kernel.c poller.c aux.c control_tcp.c streambuf.c call.c control_udp.c redis.c \
bencode.c cookie_cache.c udp_listener.c control_ng.c sdp.c str.c stun.c rtcp.c \
crypto.c rtp.c call_interfaces.c dtls.c log.c cli.c graphite.c ice.c socket.c \
media_socket.c rtcp_xr.c
media_socket.c rtcp_xr.c homer.c
OBJS= $(SRCS:.c=.o)

@ -210,6 +210,7 @@ struct sdp_ng_flags;
struct local_interface;
struct call_monologue;
struct ice_agent;
struct homer_sender;
typedef bencode_buffer_t call_buffer_t;
@ -491,6 +492,8 @@ struct callmaster {
struct callmaster_config conf;
struct timeval latest_graphite_interval_start;
struct homer_sender *homer;
};
struct call_stats {

@ -241,8 +241,6 @@ void graphite_loop_run(struct callmaster *cm, endpoint_t *graphite_ep, int secon
fd_set wfds;
FD_ZERO(&wfds);
struct timeval tv;
int optval=0;
socklen_t optlen=sizeof(optval);
// sanity checks
if (!cm) {
@ -276,10 +274,10 @@ void graphite_loop_run(struct callmaster *cm, endpoint_t *graphite_ep, int secon
connection_state = STATE_DISCONNECTED;
return;
}
rc = getsockopt(graphite_sock.fd, SOL_SOCKET, SO_ERROR, &optval, &optlen);
if (rc) ilog(LOG_ERROR,"getsockopt failure.");
if (optval != 0) {
ilog(LOG_ERROR,"Socket connect failed. fd: %i, Reason: %s\n",graphite_sock.fd, strerror(optval));
rc = socket_error(&graphite_sock);
if (rc < 0) ilog(LOG_ERROR,"getsockopt failure.");
if (rc != 0) {
ilog(LOG_ERROR,"Socket connect failed. fd: %i, Reason: %s\n",graphite_sock.fd, strerror(rc));
close_socket(&graphite_sock);
connection_state = STATE_DISCONNECTED;
return;

@ -0,0 +1,559 @@
#include "homer.h"
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <string.h>
#include <glib.h>
#include <sys/time.h>
#include "log.h"
#include "aux.h"
#include "str.h"
#define SEND_QUEUE_LIMIT 200
struct homer_sender {
mutex_t lock;
endpoint_t endpoint;
int protocol;
int capture_id;
socket_t socket;
time_t retry;
GQueue send_queue;
GString *partial;
int (*state)(struct homer_sender *);
};
static int send_hepv3 (GString *s, const str *id, int, const endpoint_t *src, const endpoint_t *dst,
const struct timeval *);
// state handlers
static int __established(struct homer_sender *hs);
static int __in_progress(struct homer_sender *hs);
static int __no_socket(struct homer_sender *hs);
static void __reset(struct homer_sender *hs) {
close_socket(&hs->socket);
hs->state = __no_socket;
hs->retry = time(NULL) + 30;
// discard partially written packet
if (hs->partial)
g_string_free(hs->partial, TRUE);
hs->partial = NULL;
}
static int __attempt_send(struct homer_sender *hs, GString *gs) {
int ret;
ret = write(hs->socket.fd, gs->str, gs->len);
if (ret == gs->len) {
// full write
g_string_free(gs, TRUE);
return 0;
}
if (ret < 0) {
if (errno != EWOULDBLOCK && errno != EAGAIN) {
ilog(LOG_ERR, "Write error to Homer at %s: %s",
endpoint_print_buf(&hs->endpoint), strerror(errno));
__reset(hs);
return 1;
}
ilog(LOG_DEBUG, "Home write blocked");
// XXX use poller for blocked writes?
return 2;
}
// partial write
ilog(LOG_DEBUG, "Home write blocked (partial write)");
g_string_erase(gs, 0, ret);
return 3;
}
static int __established(struct homer_sender *hs) {
char buf[16];
int ret;
GString *gs;
// test connection with a dummy read
ret = read(hs->socket.fd, buf, sizeof(buf));
if (ret < 0) {
if (errno != EWOULDBLOCK && errno != EAGAIN) {
ilog(LOG_ERR, "Connection error from Homer at %s: %s",
endpoint_print_buf(&hs->endpoint), strerror(errno));
__reset(hs);
return -1;
}
}
// XXX handle return data from Homer?
if (hs->partial) {
ilog(LOG_DEBUG, "dequeue partial packet to Homer");
ret = __attempt_send(hs, hs->partial);
if (ret == 3 || ret == 2) // partial write or not sent at all
return 0;
if (ret == 1) // write error, takes care of deleting hs->partial
return -1;
// ret == 0 -> sent OK, drop through to unqueue
g_string_free(hs->partial, TRUE);
hs->partial = NULL;
}
// unqueue as much as we can
while ((gs = g_queue_pop_head(&hs->send_queue))) {
ilog(LOG_DEBUG, "dequeue send queue to Homer");
ret = __attempt_send(hs, gs);
if (ret == 0) // everything sent OK
continue;
if (ret == 3) { // partial write
hs->partial = gs;
return 0;
}
g_queue_push_head(&hs->send_queue, gs);
if (ret == 1) // write error
return -1;
// ret == 2 -> blocked
return 0;
}
// everything unqueued
return 0;
}
static int __check_conn(struct homer_sender *hs, int ret) {
if (ret == 0) {
ilog(LOG_INFO, "Connection to Homer at %s has been established",
endpoint_print_buf(&hs->endpoint));
hs->state = __established;
return hs->state(hs);
}
if (ret == 1) {
ilog(LOG_DEBUG, "connection to Homer is in progress");
hs->state = __in_progress;
return 0;
}
ilog(LOG_ERR, "Failed to connect to Homer at %s: %s",
endpoint_print_buf(&hs->endpoint), strerror(errno));
__reset(hs);
return -1;
}
static int __in_progress(struct homer_sender *hs) {
int ret;
ilog(LOG_DEBUG, "connection to Homer is in progress - checking");
ret = connect_socket_retry(&hs->socket);
return __check_conn(hs, ret);
}
static int __no_socket(struct homer_sender *hs) {
int ret;
if (hs->retry > time(NULL))
return 0;
ilog(LOG_INFO, "Connecting to Homer at %s", endpoint_print_buf(&hs->endpoint));
ret = connect_socket_nb(&hs->socket, hs->protocol, &hs->endpoint);
return __check_conn(hs, ret);
}
struct homer_sender *homer_sender_new(const endpoint_t *ep, int protocol, int capture_id) {
struct homer_sender *ret;
if (is_addr_unspecified(&ep->address))
return NULL;
ret = malloc(sizeof(*ret));
ZERO(*ret);
mutex_init(&ret->lock);
ret->endpoint = *ep;
ret->protocol = protocol;
ret->capture_id = capture_id;
ret->retry = time(NULL);
ret->state = __no_socket;
return ret;
}
// takes over the GString
int homer_send(struct homer_sender *hs, GString *s, const str *id, const endpoint_t *src,
const endpoint_t *dst, const struct timeval *tv)
{
if (!hs)
goto out;
if (!s)
goto out;
if (!s->len) // empty write, shouldn't happen
goto out;
ilog(LOG_DEBUG, "JSON to send to Homer: '"STR_FORMAT"'", G_STR_FMT(s));
if (send_hepv3(s, id, hs->capture_id, src, dst, tv))
goto out;
mutex_lock(&hs->lock);
if (hs->send_queue.length < SEND_QUEUE_LIMIT) {
g_queue_push_tail(&hs->send_queue, s);
s = NULL;
}
else
ilog(LOG_ERR, "Send queue length limit (%i) reached, dropping Homer message", SEND_QUEUE_LIMIT);
hs->state(hs);
mutex_unlock(&hs->lock);
out:
if (s)
g_string_free(s, TRUE);
return 0;
}
// from captagent transport_hep.[ch]
struct hep_chunk {
u_int16_t vendor_id;
u_int16_t type_id;
u_int16_t length;
} __attribute__((packed));
typedef struct hep_chunk hep_chunk_t;
struct hep_chunk_uint8 {
hep_chunk_t chunk;
u_int8_t data;
} __attribute__((packed));
typedef struct hep_chunk_uint8 hep_chunk_uint8_t;
struct hep_chunk_uint16 {
hep_chunk_t chunk;
u_int16_t data;
} __attribute__((packed));
typedef struct hep_chunk_uint16 hep_chunk_uint16_t;
struct hep_chunk_uint32 {
hep_chunk_t chunk;
u_int32_t data;
} __attribute__((packed));
typedef struct hep_chunk_uint32 hep_chunk_uint32_t;
struct hep_chunk_str {
hep_chunk_t chunk;
char *data;
} __attribute__((packed));
typedef struct hep_chunk_str hep_chunk_str_t;
struct hep_chunk_ip4 {
hep_chunk_t chunk;
struct in_addr data;
} __attribute__((packed));
typedef struct hep_chunk_ip4 hep_chunk_ip4_t;
struct hep_chunk_ip6 {
hep_chunk_t chunk;
struct in6_addr data;
} __attribute__((packed));
typedef struct hep_chunk_ip6 hep_chunk_ip6_t;
struct hep_ctrl {
char id[4];
u_int16_t length;
} __attribute__((packed));
typedef struct hep_ctrl hep_ctrl_t;
struct hep_chunk_payload {
hep_chunk_t chunk;
char *data;
} __attribute__((packed));
typedef struct hep_chunk_payload hep_chunk_payload_t;
/* Structure of HEP */
struct hep_generic {
hep_ctrl_t header;
hep_chunk_uint8_t ip_family;
hep_chunk_uint8_t ip_proto;
hep_chunk_uint16_t src_port;
hep_chunk_uint16_t dst_port;
hep_chunk_uint32_t time_sec;
hep_chunk_uint32_t time_usec;
hep_chunk_uint8_t proto_t;
hep_chunk_uint32_t capt_id;
} __attribute__((packed));
typedef struct hep_generic hep_generic_t;
#define PROTO_RTCP_JSON 0x05
// modifies the GString in place
static int send_hepv3 (GString *s, const str *id, int capt_id, const endpoint_t *src, const endpoint_t *dst,
const struct timeval *tv)
{
struct hep_generic *hg=NULL;
void* buffer;
unsigned int buflen=0, iplen=0,tlen=0;
hep_chunk_ip4_t src_ip4, dst_ip4;
hep_chunk_ip6_t src_ip6, dst_ip6;
hep_chunk_t payload_chunk;
//hep_chunk_t authkey_chunk;
hep_chunk_t correlation_chunk;
//static int errors = 0;
hg = malloc(sizeof(struct hep_generic));
memset(hg, 0, sizeof(struct hep_generic));
/* header set */
memcpy(hg->header.id, "\x48\x45\x50\x33", 4);
/* IP proto */
hg->ip_family.chunk.vendor_id = htons(0x0000);
hg->ip_family.chunk.type_id = htons(0x0001);
hg->ip_family.data = src->address.family->af;
hg->ip_family.chunk.length = htons(sizeof(hg->ip_family));
/* Proto ID */
hg->ip_proto.chunk.vendor_id = htons(0x0000);
hg->ip_proto.chunk.type_id = htons(0x0002);
hg->ip_proto.data = IPPROTO_UDP;
hg->ip_proto.chunk.length = htons(sizeof(hg->ip_proto));
/* IPv4 */
if(hg->ip_family.data == AF_INET) {
/* SRC IP */
src_ip4.chunk.vendor_id = htons(0x0000);
src_ip4.chunk.type_id = htons(0x0003);
src_ip4.data = src->address.u.ipv4;
src_ip4.chunk.length = htons(sizeof(src_ip4));
/* DST IP */
dst_ip4.chunk.vendor_id = htons(0x0000);
dst_ip4.chunk.type_id = htons(0x0004);
dst_ip4.data = dst->address.u.ipv4;
dst_ip4.chunk.length = htons(sizeof(dst_ip4));
iplen = sizeof(dst_ip4) + sizeof(src_ip4);
}
/* IPv6 */
else if(hg->ip_family.data == AF_INET6) {
/* SRC IPv6 */
src_ip6.chunk.vendor_id = htons(0x0000);
src_ip6.chunk.type_id = htons(0x0005);
src_ip6.data = src->address.u.ipv6;
src_ip6.chunk.length = htons(sizeof(src_ip6));
/* DST IPv6 */
dst_ip6.chunk.vendor_id = htons(0x0000);
dst_ip6.chunk.type_id = htons(0x0006);
dst_ip6.data = dst->address.u.ipv6;
dst_ip6.chunk.length = htons(sizeof(dst_ip6));
iplen = sizeof(dst_ip6) + sizeof(src_ip6);
}
/* SRC PORT */
hg->src_port.chunk.vendor_id = htons(0x0000);
hg->src_port.chunk.type_id = htons(0x0007);
hg->src_port.data = htons(src->port);
hg->src_port.chunk.length = htons(sizeof(hg->src_port));
/* DST PORT */
hg->dst_port.chunk.vendor_id = htons(0x0000);
hg->dst_port.chunk.type_id = htons(0x0008);
hg->dst_port.data = htons(dst->port);
hg->dst_port.chunk.length = htons(sizeof(hg->dst_port));
/* TIMESTAMP SEC */
hg->time_sec.chunk.vendor_id = htons(0x0000);
hg->time_sec.chunk.type_id = htons(0x0009);
hg->time_sec.data = htonl(tv->tv_sec);
hg->time_sec.chunk.length = htons(sizeof(hg->time_sec));
/* TIMESTAMP USEC */
hg->time_usec.chunk.vendor_id = htons(0x0000);
hg->time_usec.chunk.type_id = htons(0x000a);
hg->time_usec.data = htonl(tv->tv_usec);
hg->time_usec.chunk.length = htons(sizeof(hg->time_usec));
/* Protocol TYPE */
hg->proto_t.chunk.vendor_id = htons(0x0000);
hg->proto_t.chunk.type_id = htons(0x000b);
hg->proto_t.data = PROTO_RTCP_JSON;
hg->proto_t.chunk.length = htons(sizeof(hg->proto_t));
/* Capture ID */
hg->capt_id.chunk.vendor_id = htons(0x0000);
hg->capt_id.chunk.type_id = htons(0x000c);
hg->capt_id.data = capt_id;
hg->capt_id.chunk.length = htons(sizeof(hg->capt_id));
/* Payload */
payload_chunk.vendor_id = htons(0x0000);
payload_chunk.type_id = 0 ? htons(0x0010) : htons(0x000f);
payload_chunk.length = htons(sizeof(payload_chunk) + s->len);
tlen = sizeof(struct hep_generic) + s->len + iplen + sizeof(hep_chunk_t);
#if 0
/* auth key */
if(profile_transport[idx].capt_password != NULL) {
tlen += sizeof(hep_chunk_t);
/* Auth key */
authkey_chunk.vendor_id = htons(0x0000);
authkey_chunk.type_id = htons(0x000e);
authkey_chunk.length = htons(sizeof(authkey_chunk) + strlen(profile_transport[idx].capt_password));
tlen += strlen(profile_transport[idx].capt_password);
}
#endif
/* correlation key */
//if(rcinfo->correlation_id.s && rcinfo->correlation_id.len > 0) {
tlen += sizeof(hep_chunk_t);
/* Correlation key */
correlation_chunk.vendor_id = htons(0x0000);
correlation_chunk.type_id = htons(0x0011);
correlation_chunk.length = htons(sizeof(correlation_chunk) + id->len);
tlen += id->len;
//}
/* total */
hg->header.length = htons(tlen);
buffer = (void*)malloc(tlen);
if (buffer==0){
ilog(LOG_ERR, "ERROR: out of memory");
free(hg);
return -1;
}
memcpy((void*) buffer, hg, sizeof(struct hep_generic));
buflen = sizeof(struct hep_generic);
/* IPv4 */
if(hg->ip_family.data == AF_INET) {
/* SRC IP */
memcpy((void*) buffer+buflen, &src_ip4, sizeof(struct hep_chunk_ip4));
buflen += sizeof(struct hep_chunk_ip4);
memcpy((void*) buffer+buflen, &dst_ip4, sizeof(struct hep_chunk_ip4));
buflen += sizeof(struct hep_chunk_ip4);
}
/* IPv6 */
else if(hg->ip_family.data == AF_INET6) {
/* SRC IPv6 */
memcpy((void*) buffer+buflen, &src_ip4, sizeof(struct hep_chunk_ip6));
buflen += sizeof(struct hep_chunk_ip6);
memcpy((void*) buffer+buflen, &dst_ip6, sizeof(struct hep_chunk_ip6));
buflen += sizeof(struct hep_chunk_ip6);
}
#if 0
/* AUTH KEY CHUNK */
if(profile_transport[idx].capt_password != NULL) {
memcpy((void*) buffer+buflen, &authkey_chunk, sizeof(struct hep_chunk));
buflen += sizeof(struct hep_chunk);
/* Now copying payload self */
memcpy((void*) buffer+buflen, profile_transport[idx].capt_password, strlen(profile_transport[idx].capt_password));
buflen+=strlen(profile_transport[idx].capt_password);
}
#endif
/* Correlation KEY CHUNK */
//if(rcinfo->correlation_id.s && rcinfo->correlation_id.len > 0) {
memcpy((void*) buffer+buflen, &correlation_chunk, sizeof(struct hep_chunk));
buflen += sizeof(struct hep_chunk);
/* Now copying payload self */
memcpy((void*) buffer+buflen, id->s, id->len);
buflen+= id->len;
//}
/* PAYLOAD CHUNK */
memcpy((void*) buffer+buflen, &payload_chunk, sizeof(struct hep_chunk));
buflen += sizeof(struct hep_chunk);
/* Now copying payload self */
memcpy((void*) buffer+buflen, s->str, s->len);
buflen+=s->len;
#if 0
/* make sleep after 100 errors */
if(errors > 50) {
LERR( "HEP server is down... retrying after sleep...");
if(!profile_transport[idx].usessl) {
sleep(2);
if(init_hepsocket_blocking(idx)) {
profile_transport[idx].initfails++;
}
errors=0;
}
#ifdef USE_SSL
else {
sleep(2);
if(initSSL(idx)) profile_transport[idx].initfails++;
errors=0;
}
#endif /* USE SSL */
}
/* send this packet out of our socket */
if(send_data(buffer, buflen, idx)) {
errors++;
stats.errors_total++;
}
#endif
g_string_truncate(s, 0);
g_string_append_len(s, buffer, buflen);
/* FREE */
if(buffer) free(buffer);
if(hg) free(hg);
return 0;
}

@ -0,0 +1,15 @@
#ifndef __HOMER_H__
#define __HOMER_H__
#include "socket.h"
struct homer_sender;
struct homer_sender *homer_sender_new(const endpoint_t *, int, int);
int homer_send(struct homer_sender *, GString *, const str *, const endpoint_t *, const endpoint_t *,
const struct timeval *tv);
#endif

@ -29,6 +29,7 @@
#include "ice.h"
#include "socket.h"
#include "media_socket.h"
#include "homer.h"
@ -55,13 +56,16 @@ static char *pidfile;
static gboolean foreground;
static GQueue interfaces = G_QUEUE_INIT;
static GQueue keyspaces = G_QUEUE_INIT;
endpoint_t tcp_listen_ep;
endpoint_t udp_listen_ep;
endpoint_t ng_listen_ep;
endpoint_t cli_listen_ep;
endpoint_t graphite_ep;
endpoint_t redis_ep;
endpoint_t redis_write_ep;
static endpoint_t tcp_listen_ep;
static endpoint_t udp_listen_ep;
static endpoint_t ng_listen_ep;
static endpoint_t cli_listen_ep;
static endpoint_t graphite_ep;
static endpoint_t redis_ep;
static endpoint_t redis_write_ep;
static endpoint_t homer_ep;
static int homer_protocol = SOCK_DGRAM;
static int homer_id = 2001;
static int tos;
static int table = -1;
static int no_fallback;
@ -247,7 +251,7 @@ static int redis_ep_parse(endpoint_t *ep, int *db, char **auth, const char *auth
if (l < 0)
return -1;
*db = l;
if (endpoint_parse_any(ep, str))
if (endpoint_parse_any_full(ep, str))
return -1;
return 0;
}
@ -274,6 +278,8 @@ static void options(int *argc, char ***argv) {
char *log_facility_rtcp_s = NULL;
int version = 0;
int sip_source = 0;
char *homerp = NULL;
char *homerproto = NULL;
GOptionEntry e[] = {
{ "version", 'v', 0, G_OPTION_ARG_NONE, &version, "Print build time and exit", NULL },
@ -285,7 +291,7 @@ static void options(int *argc, char ***argv) {
{ "listen-udp", 'u', 0, G_OPTION_ARG_STRING, &listenudps, "UDP port to listen on", "[IP46:]PORT" },
{ "listen-ng", 'n', 0, G_OPTION_ARG_STRING, &listenngs, "UDP port to listen on, NG protocol", "[IP46:]PORT" },
{ "listen-cli", 'c', 0, G_OPTION_ARG_STRING, &listencli, "UDP port to listen on, CLI", "[IP46:]PORT" },
{ "graphite", 'g', 0, G_OPTION_ARG_STRING, &graphitep, "Address of the graphite server", "[IP46:]PORT" },
{ "graphite", 'g', 0, G_OPTION_ARG_STRING, &graphitep, "Address of the graphite server", "IP46:PORT" },
{ "graphite-interval", 'G', 0, G_OPTION_ARG_INT, &graphite_interval, "Graphite send interval in seconds", "INT" },
{ "graphite-prefix",0, 0, G_OPTION_ARG_STRING, &graphite_prefix_s, "Prefix for graphite line", "STRING"},
{ "tos", 'T', 0, G_OPTION_ARG_INT, &tos, "Default TOS value to set on streams", "INT" },
@ -312,6 +318,9 @@ static void options(int *argc, char ***argv) {
{ "sip-source", 0, 0, G_OPTION_ARG_NONE, &sip_source, "Use SIP source address by default", NULL },
{ "dtls-passive", 0, 0, G_OPTION_ARG_NONE, &dtls_passive_def,"Always prefer DTLS passive role", NULL },
{ "max-sessions", 0, 0, G_OPTION_ARG_INT, &max_sessions, "Limit of maximum number of sessions", "INT" },
{ "homer", 0, 0, G_OPTION_ARG_STRING, &homerp, "Address of Homer server for RTCP stats","IP46:PORT"},
{ "homer-protocol",0,0,G_OPTION_ARG_STRING, &homerproto, "Transport protocol for Homer (default udp)", "udp|tcp" },
{ "homer-id", 0, 0, G_OPTION_ARG_STRING, &homer_id, "'Capture ID' to use within the HEP protocol", "INT" },
{ NULL, }
};
@ -367,13 +376,26 @@ static void options(int *argc, char ***argv) {
die("Invalid IP or port (--listen-cli)");
}
if (graphitep) {if (endpoint_parse_any(&graphite_ep, graphitep))
if (graphitep) {if (endpoint_parse_any_full(&graphite_ep, graphitep))
die("Invalid IP or port (--graphite)");
}
if (graphite_prefix_s)
set_prefix(graphite_prefix_s);
if (homerp) {
if (endpoint_parse_any_full(&homer_ep, homerp))
die("Invalid IP or port (--homer)");
}
if (homerproto) {
if (!strcmp(homerproto, "tcp"))
homer_protocol = SOCK_STREAM;
else if (!strcmp(homerproto, "udp"))
homer_protocol = SOCK_DGRAM;
else
die("Invalid protocol (--homer-protocol)");
}
if (tos < 0 || tos > 255)
die("Invalid TOS value");
@ -634,6 +656,8 @@ no_kernel:
daemonize();
wpidfile();
ctx->m->homer = homer_sender_new(&homer_ep, homer_protocol, homer_id);
if (mc.redis) {
// start redis restore timer
gettimeofday(&redis_start, NULL);

@ -537,7 +537,7 @@ struct local_intf *get_any_interface_address(const struct logical_intf *lif, soc
static int get_port6(socket_t *r, unsigned int port, struct intf_spec *spec) {
if (open_socket(r, SOCK_DGRAM, port, &spec->local_address.addr))
return -1;
socket_timestamping(r);
return 0;
}
@ -1013,7 +1013,7 @@ noop:
/* XXX split this function into pieces */
/* called lock-free */
static int stream_packet(struct stream_fd *sfd, str *s, const endpoint_t *fsin) {
static int stream_packet(struct stream_fd *sfd, str *s, const endpoint_t *fsin, const struct timeval *tv) {
struct packet_stream *stream,
*sink = NULL,
*in_srtp, *out_srtp;
@ -1173,8 +1173,8 @@ loop_ok:
if (rwf_in)
handler_ret = rwf_in(s, in_srtp);
if (handler_ret >= 0) {
if (rtcp && _log_facility_rtcp)
parse_and_log_rtcp_report(sfd, s->s, s->len);
if (rtcp)
parse_and_log_rtcp_report(sfd, s, fsin, tv);
if (rwf_out)
handler_ret += rwf_out(s, out_srtp);
}
@ -1358,6 +1358,7 @@ static void stream_fd_readable(int fd, void *p, uintptr_t u) {
struct call *ca;
str s;
endpoint_t ep;
struct timeval tv;
if (sfd->socket.fd != fd)
goto out;
@ -1373,7 +1374,8 @@ static void stream_fd_readable(int fd, void *p, uintptr_t u) {
}
#endif
ret = socket_recvfrom(&sfd->socket, buf + RTP_BUFFER_HEAD_ROOM, MAX_RTP_PACKET_SIZE, &ep);
ret = socket_recvfrom_ts(&sfd->socket, buf + RTP_BUFFER_HEAD_ROOM, MAX_RTP_PACKET_SIZE,
&ep, &tv);
if (ret < 0) {
if (errno == EINTR)
@ -1387,7 +1389,7 @@ static void stream_fd_readable(int fd, void *p, uintptr_t u) {
ilog(LOG_WARNING, "UDP packet possibly truncated");
str_init_len(&s, buf + RTP_BUFFER_HEAD_ROOM, ret);
ret = stream_packet(sfd, &s, &ep);
ret = stream_packet(sfd, &s, &ep, &tv);
if (ret < 0) {
ilog(LOG_WARNING, "Write error on RTP socket: %s", strerror(-ret));
call_destroy(sfd->call);

@ -12,6 +12,7 @@
#include "rtp.h"
#include "crypto.h"
#include "rtcp_xr.h"
#include "homer.h"
@ -474,8 +475,9 @@ int rtcp_demux_is_rtcp(const str *s) {
return 1;
}
void print_rtcp_common(char** cdrbufcur, const pjmedia_rtcp_common *common) {
*cdrbufcur += sprintf(*cdrbufcur,"version=%u, padding=%u, count=%u, payloadtype=%u, length=%u, ssrc=%u, ",
static void print_rtcp_common(GString *log, const pjmedia_rtcp_common *common) {
if (log)
g_string_append_printf(log,"version=%u, padding=%u, count=%u, payloadtype=%u, length=%u, ssrc=%u, ",
common->version,
common->p,
common->count,
@ -484,23 +486,49 @@ void print_rtcp_common(char** cdrbufcur, const pjmedia_rtcp_common *common) {
ntohl(common->ssrc));
}
void print_rtcp_sr(char** cdrbufcur, const pjmedia_rtcp_sr* sr) {
*cdrbufcur += sprintf(*cdrbufcur,"ntp_sec=%u, ntp_fractions=%u, rtp_ts=%u, sender_packets=%u, sender_bytes=%u, ",
static void print_rtcp_sr(GString *log, const pjmedia_rtcp_sr* sr, GString *json) {
if (log)
g_string_append_printf(log,"ntp_sec=%u, ntp_fractions=%u, rtp_ts=%u, sender_packets=%u, sender_bytes=%u, ",
ntohl(sr->ntp_sec),
ntohl(sr->ntp_frac),
ntohl(sr->rtp_ts),
ntohl(sr->sender_pcount),
ntohl(sr->sender_bcount));
if (json)
g_string_append_printf(json, "\"sender_information\":{\"ntp_timestamp_sec\":%u,"
"\"ntp_timestamp_usec\":%u,\"octets\":%u,\"rtp_timestamp\":%u, \"packets\":%u},",
ntohl(sr->ntp_sec),
ntohl(sr->ntp_frac),
ntohl(sr->sender_bcount),
ntohl(sr->rtp_ts),
ntohl(sr->sender_pcount));
}
void print_rtcp_rr(char** cdrbufcur, const pjmedia_rtcp_rr* rr) {
static void print_rtcp_rr_list_start(pjmedia_rtcp_common *common, GString *json) {
if (json)
g_string_append_printf(json, "\"ssrc\":%u,\"type\":%u,\"report_count\":%u,\"report_blocks\":[",
ntohl(common->ssrc),
common->pt,
common->count);
}
static void print_rtcp_sdes_list_start(pjmedia_rtcp_common *common, GString *json) {
if (json)
g_string_append_printf(json, "\"sdes_ssrc\":%u,\"sdes_report_count\":%u,\"sdes_information\": [ ",
ntohl(common->ssrc),
common->count);
}
static void print_rtcp_rr(GString *log, const pjmedia_rtcp_rr* rr, GString *json) {
/* Get packet loss */
u_int32_t packet_loss=0;
packet_loss = (rr->total_lost_2 << 16) +
(rr->total_lost_1 << 8) +
rr->total_lost_0;
*cdrbufcur += sprintf(*cdrbufcur,"ssrc=%u, fraction_lost=%u, packet_loss=%u, last_seq=%u, jitter=%u, last_sr=%u, delay_since_last_sr=%u, ",
if (log)
g_string_append_printf(log,"ssrc=%u, fraction_lost=%u, packet_loss=%u, last_seq=%u, jitter=%u, last_sr=%u, delay_since_last_sr=%u, ",
ntohl(rr->ssrc),
rr->fract_lost,
packet_loss,
@ -508,47 +536,206 @@ void print_rtcp_rr(char** cdrbufcur, const pjmedia_rtcp_rr* rr) {
ntohl(rr->jitter),
ntohl(rr->lsr),
ntohl(rr->dlsr));
if (json)
g_string_append_printf(json, "{\"source_ssrc\":%u,"
"\"highest_seq_no\":%u,\"fraction_lost\":%u,\"ia_jitter\":%u,"
"\"packets_lost\":%u,\"lsr\":%u,\"dlsr\":%u},",
ntohl(rr->ssrc),
ntohl(rr->last_seq),
rr->fract_lost,
ntohl(rr->jitter),
packet_loss,
ntohl(rr->lsr),
ntohl(rr->dlsr));
}
void parse_and_log_rtcp_report(struct stream_fd *sfd, const void *pkt, long size) {
static void print_rtcp_sdes_item(GString *json, const rtcp_sdes_chunk_t *chunk, const rtcp_sdes_item_t *item,
const char *data)
{
int i;
if (json) {
g_string_append_printf(json, "{\"sdes_chunk_ssrc\":%u,\"type\":%u,\"text\":\"",
htonl(chunk->csrc),
item->type);
for (i = 0; i < item->len; i++) {
switch (data[i]) {
case '"':
g_string_append(json, "\\\"");
break;
case '\\':
g_string_append(json, "\\\\");
break;
case '\b':
g_string_append(json, "\\b");
break;
case '\f':
g_string_append(json, "\\f");
break;
case '\n':
g_string_append(json, "\\n");
break;
case '\r':
g_string_append(json, "\\r");
break;
case '\t':
g_string_append(json, "\\t");
break;
default:
g_string_append_c(json, data[i]);
break;
}
}
static const int CDRBUFLENGTH = 1024*1024*1; // 1 MB
char cdrbuffer[CDRBUFLENGTH];
char* cdrbufcur = cdrbuffer;
pjmedia_rtcp_common *common = (pjmedia_rtcp_common*) pkt;
const pjmedia_rtcp_rr *rr = NULL;
const pjmedia_rtcp_sr *sr = NULL;
g_string_append(json, "\"},");
}
}
cdrbufcur += sprintf(cdrbufcur,"["STR_FORMAT"] ", STR_FMT(&sfd->stream->call->callid));
static void str_sanitize(GString *s) {
while (s->len > 0 && (s->str[s->len - 1] == ' ' || s->str[s->len - 1] == ','))
g_string_truncate(s, s->len - 1);
}
if (size < sizeof(*common))
static void print_rtcp_list_end(GString *json) {
if (json) {
str_sanitize(json);
g_string_append_printf(json, "],");
}
}
void parse_and_log_rtcp_report(struct stream_fd *sfd, const str *ori_s, const endpoint_t *src,
const struct timeval *tv)
{
GString *log;
str iter_s, comp_s;
pjmedia_rtcp_common *common;
const pjmedia_rtcp_rr *rr;
const pjmedia_rtcp_sr *sr;
const rtcp_sdes_chunk_t *sdes_chunk;
const rtcp_sdes_item_t *sdes_item;
GString *json;
struct call *c = sfd->call;
struct callmaster *cm = c->callmaster;
int i;
log = _log_facility_rtcp ? g_string_new(NULL) : NULL;
json = cm->homer ? g_string_new("{ ") : NULL;
// anything to do?
if (!log && !json)
return;
print_rtcp_common(&cdrbufcur,common);
if (log)
g_string_append_printf(log, "["STR_FORMAT"] ", STR_FMT(&sfd->stream->call->callid));
iter_s = *ori_s;
while (iter_s.len) {
// procedure throughout here: first assign, then str_shift with check for
// return value (does the length sanity check), then access values.
// we use iter_s to iterate compound packets and comp_s to access component
// data.
common = (pjmedia_rtcp_common*) iter_s.s;
comp_s = iter_s;
if (str_shift(&comp_s, sizeof(*common))) // puts comp_s just past the common header
break;
if (str_shift(&iter_s, (ntohs(common->length) + 1) << 2)) // puts iter_s on the next compound packet
break;
print_rtcp_common(log, common);
/* Parse RTCP */
if (common->pt == RTCP_PT_SR) {
if (size < (sizeof(*common) + sizeof(*sr)))
return;
/* Parse RTCP */
switch (common->pt) {
case RTCP_PT_SR:
sr = (pjmedia_rtcp_sr*) ((comp_s.s));
if (str_shift(&comp_s, sizeof(*sr)))
break;
sr = (pjmedia_rtcp_sr*) (((char*)pkt) + sizeof(pjmedia_rtcp_common));
print_rtcp_sr(log, sr, json);
// fall through to RTCP_PT_RR
print_rtcp_sr(&cdrbufcur,sr);
case RTCP_PT_RR:
print_rtcp_rr_list_start(common, json);
if (common->count > 0 && size >= (sizeof(pjmedia_rtcp_sr_pkt))) {
rr = (pjmedia_rtcp_rr*)(((char*)pkt) + (sizeof(pjmedia_rtcp_common)
+ sizeof(pjmedia_rtcp_sr)));
print_rtcp_rr(&cdrbufcur,rr);
for (i = 0; i < common->count; i++) {
rr = (pjmedia_rtcp_rr*)((comp_s.s));
if (str_shift(&comp_s, sizeof(*rr)))
break;
print_rtcp_rr(log, rr, json);
}
print_rtcp_list_end(json);
break;
case RTCP_PT_XR:
pjmedia_rtcp_xr_rx_rtcp_xr(log, common, &comp_s);
break;
case RTCP_PT_SDES:
print_rtcp_sdes_list_start(common, json);
// the "common" header actually includes the SDES
// SSRC/CSRC chunk header, so we set our chunk header
// to its SDES field
sdes_chunk = (rtcp_sdes_chunk_t *) &common->ssrc;
// comp_s then points into the first SDES item
i = 0;
while (1) {
while (comp_s.len) {
sdes_item = (rtcp_sdes_item_t *) comp_s.s;
// check for zero type first
if (str_shift(&comp_s, 1))
break;
if (!sdes_item->type)
break;
if (str_shift(&comp_s, sizeof(*sdes_item) - 1))
break;
if (comp_s.len < sdes_item->len)
break;
print_rtcp_sdes_item(json, sdes_chunk, sdes_item, comp_s.s);
str_shift(&comp_s, sdes_item->len);
}
// remove padding to next chunk
while (comp_s.len % 4 != 0)
str_shift(&comp_s, 1);
// more chunks? set chunk header
i++;
if (i >= common->count)
break;
sdes_chunk = (rtcp_sdes_chunk_t *) comp_s.s;
if (str_shift(&comp_s, sizeof(*sdes_chunk)))
break;
}
print_rtcp_list_end(json);
break;
}
} else if (common->pt == RTCP_PT_RR && common->count > 0) {
if (size < (sizeof(*common) + sizeof(*rr)))
return;
}
rr = (pjmedia_rtcp_rr*)(((char*)pkt) + sizeof(pjmedia_rtcp_common));
print_rtcp_rr(&cdrbufcur,rr);
if (log) {
str_sanitize(log);
rtcplog(log->str);
}
} else if (common->pt == RTCP_PT_XR) {
pjmedia_rtcp_xr_rx_rtcp_xr(cdrbufcur, pkt, size);
if (json) {
str_sanitize(json);
g_string_append(json, " }");
homer_send(cm->homer, json, &c->callid, src, &sfd->socket.local, tv);
json = NULL;
}
rtcplog(cdrbuffer);
if (json)
g_string_free(json, TRUE);
if (log)
g_string_free(log, TRUE);
}

@ -3,6 +3,7 @@
#include "str.h"
#include "call.h"
#include <glib.h>
struct crypto_context;
@ -30,7 +31,7 @@ typedef struct pjmedia_rtcp_sr
u_int32_t rtp_ts; /**< RTP timestamp. */
u_int32_t sender_pcount; /**< Sender packet cound. */
u_int32_t sender_bcount; /**< Sender octet/bytes count. */
} pjmedia_rtcp_sr;
} __attribute__ ((packed)) pjmedia_rtcp_sr;
/**
@ -39,22 +40,36 @@ typedef struct pjmedia_rtcp_sr
typedef struct pjmedia_rtcp_rr
{
u_int32_t ssrc; /**< SSRC identification. */
#if defined(PJ_IS_BIG_ENDIAN) && PJ_IS_BIG_ENDIAN!=0
#if G_BYTE_ORDER == G_BIG_ENDIAN
u_int32_t fract_lost:8; /**< Fraction lost. */
u_int32_t total_lost_2:8; /**< Total lost, bit 16-23. */
u_int32_t total_lost_1:8; /**< Total lost, bit 8-15. */
u_int32_t total_lost_0:8; /**< Total lost, bit 0-7. */
#else
#elif G_BYTE_ORDER == G_LITTLE_ENDIAN
u_int32_t fract_lost:8; /**< Fraction lost. */
u_int32_t total_lost_2:8; /**< Total lost, bit 0-7. */
u_int32_t total_lost_1:8; /**< Total lost, bit 8-15. */
u_int32_t total_lost_0:8; /**< Total lost, bit 16-23. */
#else
#error "byte order unknown"
#endif
u_int32_t last_seq; /**< Last sequence number. */
u_int32_t jitter; /**< Jitter. */
u_int32_t lsr; /**< Last SR. */
u_int32_t dlsr; /**< Delay since last SR. */
} pjmedia_rtcp_rr;
} __attribute__ ((packed)) pjmedia_rtcp_rr;
typedef struct _rtcp_sdes_chunk
{
uint32_t csrc;
} __attribute__ ((packed)) rtcp_sdes_chunk_t;
typedef struct _rtcp_sdes_item
{
uint8_t type;
uint8_t len;
} __attribute__ ((packed)) rtcp_sdes_item_t;
/**
@ -62,7 +77,7 @@ typedef struct pjmedia_rtcp_rr
*/
typedef struct pjmedia_rtcp_common
{
#if defined(PJ_IS_BIG_ENDIAN) && PJ_IS_BIG_ENDIAN!=0
#if G_BYTE_ORDER == G_BIG_ENDIAN
unsigned version:2; /**< packet type */
unsigned p:1; /**< padding flag */
unsigned count:5; /**< varies by payload type */
@ -77,27 +92,6 @@ typedef struct pjmedia_rtcp_common
u_int32_t ssrc; /**< SSRC identification */
} pjmedia_rtcp_common;
/**
* This structure declares default RTCP packet (SR) that is sent by pjmedia.
* Incoming RTCP packet may have different format, and must be parsed
* manually by application.
*/
typedef struct pjmedia_rtcp_sr_pkt
{
pjmedia_rtcp_common common; /**< Common header. */
pjmedia_rtcp_sr sr; /**< Sender report. */
pjmedia_rtcp_rr rr; /**< variable-length list */
} pjmedia_rtcp_sr_pkt;
/**
* This structure declares RTCP RR (Receiver Report) packet.
*/
typedef struct pjmedia_rtcp_rr_pkt
{
pjmedia_rtcp_common common; /**< Common header. */
pjmedia_rtcp_rr rr; /**< variable-length list */
} pjmedia_rtcp_rr_pkt;
int rtcp_avpf2avp(str *);
int rtcp_avp2savp(str *, struct crypto_context *);
@ -105,6 +99,6 @@ int rtcp_savp2avp(str *, struct crypto_context *);
int rtcp_demux_is_rtcp(const str *);
void parse_and_log_rtcp_report(struct stream_fd *sfd, const void *pkt, long size);
void parse_and_log_rtcp_report(struct stream_fd *sfd, const str *, const endpoint_t *, const struct timeval *);
#endif

@ -4,9 +4,10 @@
* Created on: Mar 29, 2015
* Author: fmetz
*/
#include "rtcp_xr.h"
#include <stdio.h>
#include <arpa/inet.h>
#include "rtcp_xr.h"
#include <glib.h>
/* RTCP XR payload type */
#define RTCP_XR 207
@ -21,41 +22,31 @@
#define BT_VOIP_METRICS 7
void print_rtcp_xr_common(char* cdrbufcur,const pjmedia_rtcp_xr_pkt *rtcp_xr) {
cdrbufcur += sprintf(cdrbufcur,"version=%u, padding=%u, count=%u, payloadtype=%u, length=%u, ssrc=%u, ",
rtcp_xr->common.version,
rtcp_xr->common.p,
rtcp_xr->common.count,
rtcp_xr->common.pt,
rtcp_xr->common.length,
ntohl(rtcp_xr->common.ssrc));
}
void print_rtcp_xr_rb_header(char* cdrbufcur,const pjmedia_rtcp_xr_rb_header *rb_header) {
cdrbufcur += sprintf(cdrbufcur,"rb_header_blocktype=%u, rb_header_blockspecdata=%u, rb_header_blocklength=%u, ",
void print_rtcp_xr_rb_header(GString *log, const pjmedia_rtcp_xr_rb_header *rb_header) {
g_string_append_printf(log, "rb_header_blocktype=%u, rb_header_blockspecdata=%u, rb_header_blocklength=%u, ",
rb_header->bt,
rb_header->specific,
ntohs(rb_header->length));
}
void print_rtcp_xr_rb_rr_time(char* cdrbufcur,const pjmedia_rtcp_xr_rb_rr_time *rb_rr_time) {
print_rtcp_xr_rb_header(cdrbufcur,&rb_rr_time->header);
cdrbufcur += sprintf(cdrbufcur,"rb_rr_time_ntp_sec=%u, rb_rr_time_ntp_frac=%u, ",
void print_rtcp_xr_rb_rr_time(GString *log, const pjmedia_rtcp_xr_rb_rr_time *rb_rr_time) {
print_rtcp_xr_rb_header(log, &rb_rr_time->header);
g_string_append_printf(log, "rb_rr_time_ntp_sec=%u, rb_rr_time_ntp_frac=%u, ",
ntohl(rb_rr_time->ntp_sec),
ntohl(rb_rr_time->ntp_frac));
}
void print_rtcp_xr_rb_dlrr(char* cdrbufcur,const pjmedia_rtcp_xr_rb_dlrr *rb_dlrr) {
print_rtcp_xr_rb_header(cdrbufcur,&rb_dlrr->header);
cdrbufcur += sprintf(cdrbufcur,"rb_dlrr_ssrc=%u, rb_dlrr_lrr=%u, rb_dlrr_dlrr=%u, ",
void print_rtcp_xr_rb_dlrr(GString *log, const pjmedia_rtcp_xr_rb_dlrr *rb_dlrr) {
print_rtcp_xr_rb_header(log, &rb_dlrr->header);
g_string_append_printf(log, "rb_dlrr_ssrc=%u, rb_dlrr_lrr=%u, rb_dlrr_dlrr=%u, ",
ntohl(rb_dlrr->item.ssrc),
ntohl(rb_dlrr->item.lrr),
ntohl(rb_dlrr->item.dlrr));
}
void print_rtcp_xr_rb_stats(char* cdrbufcur,const pjmedia_rtcp_xr_rb_stats *rb_stats) {
print_rtcp_xr_rb_header(cdrbufcur,&rb_stats->header);
cdrbufcur += sprintf(cdrbufcur,"rb_stats_ssrc=%u, rb_stats_begin_seq=%u, rb_stats_end_seq=%u, rb_stats_lost_packets=%u, rb_stats_duplicate_packets=%u,"
void print_rtcp_xr_rb_stats(GString *log, const pjmedia_rtcp_xr_rb_stats *rb_stats) {
print_rtcp_xr_rb_header(log, &rb_stats->header);
g_string_append_printf(log, "rb_stats_ssrc=%u, rb_stats_begin_seq=%u, rb_stats_end_seq=%u, rb_stats_lost_packets=%u, rb_stats_duplicate_packets=%u,"
"rb_stats_jitter_min=%u, rb_stats_jitter_max=%u, rb_stats_jitter_mean=%u, rb_stats_jitter_deviation=%u,"
"rb_stats_toh_min=%u, rb_stats_toh_max=%u, rb_stats_toh_mean=%u, rb_stats_toh_deviation=%u, ",
ntohl(rb_stats->ssrc),
@ -73,9 +64,9 @@ void print_rtcp_xr_rb_stats(char* cdrbufcur,const pjmedia_rtcp_xr_rb_stats *rb_s
ntohl(rb_stats->toh_dev));
}
void print_rtcp_xr_rb_voip_mtc(char* cdrbufcur,const pjmedia_rtcp_xr_rb_voip_mtc *rb_voip_mtc) {
print_rtcp_xr_rb_header(cdrbufcur,&rb_voip_mtc->header);
cdrbufcur += sprintf(cdrbufcur,"rb_voip_mtc_ssrc=%u, rb_voip_mtc_loss_rate=%u, rb_voip_mtc_discard_rate=%u, rb_voip_mtc_burst_den=%u, "
void print_rtcp_xr_rb_voip_mtc(GString *log, const pjmedia_rtcp_xr_rb_voip_mtc *rb_voip_mtc) {
print_rtcp_xr_rb_header(log, &rb_voip_mtc->header);
g_string_append_printf(log, "rb_voip_mtc_ssrc=%u, rb_voip_mtc_loss_rate=%u, rb_voip_mtc_discard_rate=%u, rb_voip_mtc_burst_den=%u, "
"rb_voip_mtc_gap_den=%u, rb_voip_mtc_burst_dur=%u, rb_voip_mtc_gap_dur=%u, rb_voip_mtc_rnd_trip_delay=%u, "
"rb_voip_mtc_end_sys_delay=%u, rb_voip_mtc_signal_lvl=%u, rb_voip_mtc_noise_lvl=%u, rb_voip_mtc_rerl=%u, "
"rb_voip_mtc_gmin=%u, rb_voip_mtc_r_factor=%u, rb_voip_mtc_ext_r_factor=%u, rb_voip_mtc_mos_lq=%u, "
@ -104,60 +95,54 @@ void print_rtcp_xr_rb_voip_mtc(char* cdrbufcur,const pjmedia_rtcp_xr_rb_voip_mtc
ntohs(rb_voip_mtc->jb_abs_max));
}
void pjmedia_rtcp_xr_rx_rtcp_xr(char* cdrbufcur, const void *pkt, size_t size) {
void pjmedia_rtcp_xr_rx_rtcp_xr(GString *log, pjmedia_rtcp_common *common, str *s) {
const pjmedia_rtcp_xr_pkt *rtcp_xr = (pjmedia_rtcp_xr_pkt*) pkt;
const pjmedia_rtcp_xr_rb_rr_time *rb_rr_time = NULL;
const pjmedia_rtcp_xr_rb_dlrr *rb_dlrr = NULL;
const pjmedia_rtcp_xr_rb_stats *rb_stats = NULL;
const pjmedia_rtcp_xr_rb_voip_mtc *rb_voip_mtc = NULL;
const pjmedia_rtcp_xr_rb_header *rb_hdr = (pjmedia_rtcp_xr_rb_header*)
rtcp_xr->buf;
const pjmedia_rtcp_xr_rb_rr_time *rb_rr_time;
const pjmedia_rtcp_xr_rb_dlrr *rb_dlrr;
const pjmedia_rtcp_xr_rb_stats *rb_stats;
const pjmedia_rtcp_xr_rb_voip_mtc *rb_voip_mtc;
const pjmedia_rtcp_xr_rb_header *rb_hdr;
unsigned pkt_len, rb_len;
if (size < sizeof(*rtcp_xr))
if (!log)
return;
if (rtcp_xr->common.pt != RTCP_XR)
return;
print_rtcp_xr_common(cdrbufcur,rtcp_xr);
// packet length is guaranteed to be valid from upstream
pkt_len = ntohs((u_int16_t)rtcp_xr->common.length);
if ((pkt_len + 1) > (size / 4))
return;
pkt_len = (ntohs(common->length) + 1) << 2;
/* Parse report rpt_types */
while ((int32_t*)rb_hdr < (int32_t*)pkt + pkt_len)
while (pkt_len >= sizeof(*rb_hdr))
{
rb_len = ntohs((u_int16_t)rb_hdr->length);
rb_hdr = (pjmedia_rtcp_xr_rb_header*) s->s;
rb_len = (ntohs((u_int16_t)rb_hdr->length) + 1) << 2;
if (str_shift(s, rb_len))
break;
pkt_len -= rb_len;
/* Just skip any block with length == 0 (no report content) */
if (rb_len) {
if (rb_len > 4) {
switch (rb_hdr->bt) {
case BT_RR_TIME:
rb_rr_time = (pjmedia_rtcp_xr_rb_rr_time*) rb_hdr;
print_rtcp_xr_rb_rr_time(cdrbufcur,rb_rr_time);
print_rtcp_xr_rb_rr_time(log, rb_rr_time);
break;
case BT_DLRR:
rb_dlrr = (pjmedia_rtcp_xr_rb_dlrr*) rb_hdr;
print_rtcp_xr_rb_dlrr(cdrbufcur,rb_dlrr);
print_rtcp_xr_rb_dlrr(log, rb_dlrr);
break;
case BT_STATS:
rb_stats = (pjmedia_rtcp_xr_rb_stats*) rb_hdr;
print_rtcp_xr_rb_stats(cdrbufcur,rb_stats);
print_rtcp_xr_rb_stats(log, rb_stats);
break;
case BT_VOIP_METRICS:
rb_voip_mtc = (pjmedia_rtcp_xr_rb_voip_mtc*) rb_hdr;
print_rtcp_xr_rb_voip_mtc(cdrbufcur,rb_voip_mtc);
print_rtcp_xr_rb_voip_mtc(log, rb_voip_mtc);
break;
default:
break;
}
}
rb_hdr = (pjmedia_rtcp_xr_rb_header*)
((int32_t*)rb_hdr + rb_len + 1);
}
}

@ -10,6 +10,10 @@
#include <stdint.h>
#include <sys/types.h>
#include <glib.h>
#include "str.h"
#include "rtcp.h"
/**
* @defgroup PJMED_RTCP_XR RTCP Extended Report (XR) - RFC 3611
@ -180,42 +184,6 @@ typedef struct pjmedia_rtcp_xr_rb_voip_mtc
} pjmedia_rtcp_xr_rb_voip_mtc;
/**
* Constant of RTCP-XR content size.
*/
#define PJMEDIA_RTCP_XR_BUF_SIZE \
sizeof(pjmedia_rtcp_xr_rb_rr_time) + \
sizeof(pjmedia_rtcp_xr_rb_dlrr) + \
sizeof(pjmedia_rtcp_xr_rb_stats) + \
sizeof(pjmedia_rtcp_xr_rb_voip_mtc)
/**
* This structure declares RTCP XR (Extended Report) packet.
*/
typedef struct pjmedia_rtcp_xr_pkt
{
struct {
#if defined(PJ_IS_BIG_ENDIAN) && PJ_IS_BIG_ENDIAN!=0
unsigned version:2; /**< packet type */
unsigned p:1; /**< padding flag */
unsigned count:5; /**< varies by payload type */
unsigned pt:8; /**< payload type */
#else
unsigned count:5; /**< varies by payload type */
unsigned p:1; /**< padding flag */
unsigned version:2; /**< packet type */
unsigned pt:8; /**< payload type */
#endif
unsigned length:16; /**< packet length */
u_int32_t ssrc; /**< SSRC identification */
} common;
int8_t buf[PJMEDIA_RTCP_XR_BUF_SIZE];
/**< Content buffer */
} pjmedia_rtcp_xr_pkt;
/**
* This function is called internally by RTCP session when it receives
* incoming RTCP XR packets.
@ -223,7 +191,10 @@ typedef struct pjmedia_rtcp_xr_pkt
* @param rtcp_pkt The received RTCP XR packet.
* @param size Size of the incoming packet.
*/
void pjmedia_rtcp_xr_rx_rtcp_xr( char* cdrbufcur, const void *rtcp_pkt, size_t size);
void pjmedia_rtcp_xr_rx_rtcp_xr(GString *, pjmedia_rtcp_common *common, str *s);
#pragma pack()
#endif /* RTCP_XR_H_ */

@ -20,6 +20,7 @@ static int __ip4_is_specified(const sockaddr_t *a);
static int __ip6_is_specified(const sockaddr_t *a);
static int __ip_bind(socket_t *s, unsigned int, const sockaddr_t *);
static int __ip_connect(socket_t *s, const endpoint_t *);
static int __ip_timestamping(socket_t *s);
static int __ip4_sockaddr2endpoint(endpoint_t *, const void *);
static int __ip6_sockaddr2endpoint(endpoint_t *, const void *);
static int __ip4_endpoint2sockaddr(void *, const endpoint_t *);
@ -27,10 +28,12 @@ static int __ip6_endpoint2sockaddr(void *, const endpoint_t *);
static int __ip4_addrport2sockaddr(void *, const sockaddr_t *, unsigned int);
static int __ip6_addrport2sockaddr(void *, const sockaddr_t *, unsigned int);
static ssize_t __ip_recvfrom(socket_t *s, void *buf, size_t len, endpoint_t *ep);
static ssize_t __ip_recvfrom_ts(socket_t *s, void *buf, size_t len, endpoint_t *ep, struct timeval *);
static ssize_t __ip_sendmsg(socket_t *s, struct msghdr *mh, const endpoint_t *ep);
static ssize_t __ip_sendto(socket_t *s, const void *buf, size_t len, const endpoint_t *ep);
static int __ip4_tos(socket_t *, unsigned int);
static int __ip6_tos(socket_t *, unsigned int);
static int __ip_error(socket_t *s);
static void __ip4_endpoint2kernel(struct re_address *, const endpoint_t *);
static void __ip6_endpoint2kernel(struct re_address *, const endpoint_t *);
static void __ip4_kernel2endpoint(endpoint_t *ep, const struct re_address *ra);
@ -63,10 +66,13 @@ static struct socket_family __socket_families[__SF_LAST] = {
.addrport2sockaddr = __ip4_addrport2sockaddr,
.bind = __ip_bind,
.connect = __ip_connect,
.timestamping = __ip_timestamping,
.recvfrom = __ip_recvfrom,
.recvfrom_ts = __ip_recvfrom_ts,
.sendmsg = __ip_sendmsg,
.sendto = __ip_sendto,
.tos = __ip4_tos,
.error = __ip_error,
.endpoint2kernel = __ip4_endpoint2kernel,
.kernel2endpoint = __ip4_kernel2endpoint,
},
@ -87,10 +93,13 @@ static struct socket_family __socket_families[__SF_LAST] = {
.addrport2sockaddr = __ip6_addrport2sockaddr,
.bind = __ip_bind,
.connect = __ip_connect,
.timestamping = __ip_timestamping,
.recvfrom = __ip_recvfrom,
.recvfrom_ts = __ip_recvfrom_ts,
.sendmsg = __ip_sendmsg,
.sendto = __ip_sendto,
.tos = __ip6_tos,
.error = __ip_error,
.endpoint2kernel = __ip6_endpoint2kernel,
.kernel2endpoint = __ip6_kernel2endpoint,
},
@ -233,18 +242,52 @@ static int __ip_connect(socket_t *s, const endpoint_t *ep) {
}
return 0;
}
static ssize_t __ip_recvfrom(socket_t *s, void *buf, size_t len, endpoint_t *ep) {
static ssize_t __ip_recvfrom_ts(socket_t *s, void *buf, size_t len, endpoint_t *ep, struct timeval *tv) {
ssize_t ret;
struct sockaddr_storage sin;
socklen_t sinlen;
sinlen = s->family->sockaddr_size;
ret = recvfrom(s->fd, buf, len, 0, (void *) &sin, &sinlen);
struct msghdr msg;
struct iovec iov;
char ctrl[32];
struct cmsghdr *cm;
ZERO(msg);
msg.msg_name = &sin;
msg.msg_namelen = s->family->sockaddr_size;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = ctrl;
msg.msg_controllen = sizeof(ctrl);
ZERO(iov);
iov.iov_base = buf;
iov.iov_len = len;
ret = recvmsg(s->fd, &msg, 0);
if (ret < 0)
return ret;
s->family->sockaddr2endpoint(ep, &sin);
if (tv) {
for (cm = CMSG_FIRSTHDR(&msg); cm; cm = CMSG_NXTHDR(&msg, cm)) {
if (cm->cmsg_level == SOL_SOCKET && cm->cmsg_type == SO_TIMESTAMP) {
*tv = *((struct timeval *) CMSG_DATA(cm));
tv = NULL;
}
}
if (G_UNLIKELY(tv)) {
ilog(LOG_WARNING, "No receive timestamp received from kernel");
ZERO(*tv);
}
}
if (G_UNLIKELY((msg.msg_flags & MSG_TRUNC)))
ilog(LOG_WARNING, "Kernel indicates that data was truncated");
if (G_UNLIKELY((msg.msg_flags & MSG_CTRUNC)))
ilog(LOG_WARNING, "Kernel indicates that ancillary data was truncated");
return ret;
}
static ssize_t __ip_recvfrom(socket_t *s, void *buf, size_t len, endpoint_t *ep) {
return __ip_recvfrom_ts(s, buf, len, ep, NULL);
}
static ssize_t __ip_sendmsg(socket_t *s, struct msghdr *mh, const endpoint_t *ep) {
struct sockaddr_storage sin;
@ -270,6 +313,19 @@ static int __ip6_tos(socket_t *s, unsigned int tos) {
setsockopt(s->fd, IPPROTO_IPV6, IPV6_TCLASS, &tos, sizeof(tos));
return 0;
}
static int __ip_error(socket_t *s) {
int optval;
socklen_t optlen = sizeof(optval);
if (getsockopt(s->fd, SOL_SOCKET, SO_ERROR, &optval, &optlen))
return -1;
return optval;
}
static int __ip_timestamping(socket_t *s) {
int one = 1;
if (setsockopt(s->fd, SOL_SOCKET, SO_TIMESTAMP, &one, sizeof(one)))
return -1;
return 0;
}
static void __ip4_endpoint2kernel(struct re_address *ra, const endpoint_t *ep) {
ZERO(*ra);
ra->family = AF_INET;
@ -478,23 +534,15 @@ fail:
return -1;
}
int connect_socket_nb(socket_t *r, int type, const endpoint_t *ep) {
sockfamily_t *fam;
int connect_socket_retry(socket_t *r) {
int ret = 0;
fam = ep->address.family;
if (__socket(r, type, fam))
return -1;
nonblock(r->fd);
if (fam->connect(r, ep)) {
if (errno != EINPROGRESS)
if (r->family->connect(r, &r->remote)) {
if (errno != EINPROGRESS && errno != EALREADY)
goto fail;
ret = 1;
}
r->remote = *ep;
return ret;
fail:
@ -502,6 +550,19 @@ fail:
return -1;
}
int connect_socket_nb(socket_t *r, int type, const endpoint_t *ep) {
sockfamily_t *fam;
fam = ep->address.family;
if (__socket(r, type, fam))
return -1;
nonblock(r->fd);
r->remote = *ep;
return connect_socket_retry(r);
}
int close_socket(socket_t *r) {
if (!r || r->fd == -1) {
__C_DBG("close() syscall not called, fd=%d", r->fd);

@ -59,10 +59,13 @@ struct socket_family {
int (*addrport2sockaddr)(void *, const sockaddr_t *, unsigned int);
int (*bind)(socket_t *, unsigned int, const sockaddr_t *);
int (*connect)(socket_t *, const endpoint_t *);
int (*timestamping)(socket_t *);
ssize_t (*recvfrom)(socket_t *, void *, size_t, endpoint_t *);
ssize_t (*recvfrom_ts)(socket_t *, void *, size_t, endpoint_t *, struct timeval *);
ssize_t (*sendmsg)(socket_t *, struct msghdr *, const endpoint_t *);
ssize_t (*sendto)(socket_t *, const void *, size_t, const endpoint_t *);
int (*tos)(socket_t *, unsigned int);
int (*error)(socket_t *);
void (*endpoint2kernel)(struct re_address *, const endpoint_t *);
void (*kernel2endpoint)(endpoint_t *, const struct re_address *);
};
@ -144,8 +147,11 @@ INLINE int is_addr_unspecified(const sockaddr_t *a) {
return !a->family->is_specified(a);
}
#define socket_recvfrom(s,a...) (s)->family->recvfrom((s), a)
#define socket_recvfrom_ts(s,a...) (s)->family->recvfrom_ts((s), a)
#define socket_sendmsg(s,a...) (s)->family->sendmsg((s), a)
#define socket_sendto(s,a...) (s)->family->sendto((s), a)
#define socket_error(s) (s)->family->error((s))
#define socket_timestamping(s) (s)->family->timestamping((s))
INLINE ssize_t socket_sendiov(socket_t *s, const struct iovec *v, unsigned int len, const endpoint_t *dst) {
struct msghdr mh;
ZERO(mh);
@ -174,7 +180,8 @@ void socket_init(void);
int open_socket(socket_t *r, int type, unsigned int port, const sockaddr_t *);
int connect_socket(socket_t *r, int type, const endpoint_t *ep);
int connect_socket_nb(socket_t *r, int type, const endpoint_t *ep);
int connect_socket_nb(socket_t *r, int type, const endpoint_t *ep); // 1 == in progress
int connect_socket_retry(socket_t *r); // retries connect() while in progress
int close_socket(socket_t *r);
sockfamily_t *get_socket_family_rfc(const str *s);
@ -182,7 +189,7 @@ sockfamily_t *__get_socket_family_enum(enum socket_families);
int sockaddr_parse_any(sockaddr_t *dst, const char *src);
int sockaddr_parse_any_str(sockaddr_t *dst, const str *src);
int sockaddr_parse_str(sockaddr_t *dst, sockfamily_t *fam, const str *src);
int endpoint_parse_any(endpoint_t *, const char *);
int endpoint_parse_any(endpoint_t *, const char *); // address optional
void kernel2endpoint(endpoint_t *ep, const struct re_address *ra);
unsigned int sockaddr_hash(const sockaddr_t *);
@ -206,6 +213,16 @@ INLINE int endpoint_parse_port_any(endpoint_t *e, const char *p, unsigned int po
e->port = port;
return sockaddr_parse_any(&e->address, p);
}
// address required
INLINE int endpoint_parse_any_full(endpoint_t *d, const char *s) {
int ret;
ret = endpoint_parse_any(d, s);
if (ret)
return ret;
if (is_addr_unspecified(&d->address))
return -1;
return 0;
}
INLINE int ipv46_any_convert(endpoint_t *ep) {
if (ep->address.family->af != AF_INET)
return 0;

@ -23,6 +23,7 @@ typedef struct _str str;
#define STR_FORMAT "%.*s"
#define STR_FMT(str) (str)->len, (str)->s
#define STR_FMT0(str) ((str) ? (str)->len : 6), ((str) ? (str)->s : "(NULL)")
#define G_STR_FMT(gstr) (int) (gstr)->len, (gstr)->str // for glib GString
#define STR_NULL ((str) { NULL, 0 })
#define STR_EMPTY ((str) { "", 0 })
#define STR_CONST_INIT(str) { str, sizeof(str)-1 }

77
debian/changelog vendored

@ -1,3 +1,80 @@
ngcp-rtpengine (4.4.0.0+0~mr4.4.0.0) unstable; urgency=medium
[ Lucian Balaceanu ]
* [937bb71] Adding images to illustrate redis database layout
* [7ae955c] Fixing reversed parameters when calculating redis restore time
[ Guillem Jover ]
* [df2126f] MT#16473 Convert debian/copyright to machine-readable format
[ Pawel Kuzak ]
* [ca04c96] Make creation of iptables chain optional
* [3f4cfff] Fixed inconsistency in rtpengine-ctl list totals
[ Joseph Frazier ]
* [8fbb0d3] Add hiredis-devel to daemon deps in README.el.md
* [2877f76] Add hiredis-devel to daemon BuildRequires
[ Richard Fuchs ]
* [7635f51] add hiredis dependency to README
* [6f2dc00] allow srtp-debug-helper to specify the ROC
* [b85a9e3] fix two memory leaks
* [5926048] don't just ignore but also strip invalid/unknown a=crypto
* [c9d797a] retain outgoing DTLS role whenever possible
* [d81c8df] move struct sdp_ng_flags from sdp.h to call_interfaces.h
* [b0a3898] fix sfd assignments when remote port changes
* [5dbadc4] implement port latching option
* [38d031c] fix support for AF switching on the fly
* [43bcabf] fix logic for unspecified ipv6 addresses
* [ff26e00] add STUN SOFTWARE attribute into ICE checks and responses
* [59cfb4f] fix incorrect padding in stun software attr
* [5b33498] fix RTCP content output
* [77d074c] fix length of software STUN attribute
* [b828122] move advertised_address out of intf_spec into local_intf
* [1c56865] fix iovec overflows
* [27d18e2] fix stun xor port response
* [725638b] remove redis-role redundancy
* [d8e1e9f] simplify redis CLI options
* [3f1ae98] unify -r/-R and -w/-W options into single options
* [c1407d6] support Redis server authentication
* [f52884e] report errors returned from redis
* [c4f630c] fix kernel module build for 4.4+ kernels
* [926d9d3] MT#17699 augment module makefile version detection
[ smititelu ]
* [6378a1e] Graphite change global to local parameters
* [0395a24] Change the graphite connection state logic
* [e48252e] Change abort() behaviour of redis_check_conn()
* [7d03f3d] Improve Redis connection logging
* [50aef80] Rtpengine starts even if redis is down
* [33e72ba] Skip redundant log if r->ctx->err != NULL
* [0447177] Add NO_REDIS_REQUIRED new parameter
* [74ad505] Update README.md for NO_REDIS_REQUIRED parameter
* [65de793] Add config REDIS_NUM_THREADS param
* [eee5a65] Update doku for REDIS_NUM_THREADS
* [a6b4b86] Fix segfault when ps->component=0
* [bac271b] Add callmaster config lock
[ Frederic-Philippe Metz ]
* [e376335] Removed graphite hostname and 'totals' in graphite names
[ Michael Prokop ]
* [47a9564] MT#17699 Bump Standards-Version to 3.9.7
* [b4a6653] MT#17699 Fix m-a build error + Bump Standards-Version for ngcp-rtpengine-kernel
[ Stefan Mititelu ]
* [5e7640b] Add/Retrieve ps->component to/from redis
* [9be68a0] Add FINAL_TIMEOUT parameter
* [ef39aa3] Add rtpengine-ctl list/set timeout
* [4343ff0] Update rtpengine-cli set maxopenfiles
* [05302c2] Update rtpengine-cli set maxsessions
* [57aa566] Add offer/answer/delete processing statistics
* [a43996f] Add 'unidirectional' attribute
[ Sipwise Jenkins Builder ]
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Mon, 21 Mar 2016 22:31:18 +0100
ngcp-rtpengine (4.3.0.0+0~mr4.3.0.0) unstable; urgency=medium
[ Frederic-Philippe Metz ]

@ -36,3 +36,6 @@ TABLE=0
# GRAPHITE_PREFIX=myownprefix.
# MAX_SESSIONS=5000
# CREATE_IPTABLES_CHAIN=no
# HOMER=123.234.345.456:65432
# HOMER_PROTOCOL=udp
# HOMER_ID=2001

@ -89,6 +89,9 @@ OPTIONS="$OPTIONS --table=$TABLE"
[ -z "$GRAPHITE_INTERVAL" ] || OPTIONS="$OPTIONS --graphite-interval=$GRAPHITE_INTERVAL"
[ -z "$GRAPHITE_PREFIX" ] || OPTIONS="$OPTIONS --graphite-prefix=$GRAPHITE_PREFIX"
[ -z "$MAX_SESSIONS" ] || OPTIONS="$OPTIONS --max-sessions=$MAX_SESSIONS"
[ -z "$HOMER" ] || OPTIONS="$OPTIONS --homer=$HOMER"
[ -z "$HOMER_PROTOCOL" ] || OPTIONS="$OPTIONS --homer-protocol=$HOMER_PROTOCOL"
[ -z "$HOMER_ID" ] || OPTIONS="$OPTIONS --homer-id=$HOMER_ID"
if test "$FORK" = "no" ; then
OPTIONS="$OPTIONS --foreground"

@ -226,8 +226,27 @@ sub rtcp_sr {
my $secs = $now[0] + 2208988800;
my $frac = $now[1] / 1000000 * 2**32;
my $sr = pack('CCnN NNN NN', (2 << 6) | 1, 200, 12, rand(2**32), $secs, $frac,
12345, 0, 0);
$sr .= pack('N CCCC NNNN', 0, 0, 0, 0, 0, 0, 0, 0, 0);
12345, rand(12345), rand(4321));
$sr .= pack('N CCCC NNNN', rand(2**32), rand(256), rand(256), rand(256), rand(256),
rand(2**32), rand(2**32), rand(2**32), rand(2**32));
# sdes
$sr .= pack('CCn N CC a* CC a* CC a* C C N CC a* CC a* C CCC N CC a* C',
(2 << 6) | 3, 202, 16,
rand(2 ** 32), # csrc
1, 7, 'blah123', # cname
2, 6, 'foobar', # name
3, 7, 'foo@bar', # email,
0, # eol
0, # padding
rand(2 ** 32), # csrc
4, 5, '54321', # phone
5, 3, 'foo', # loc
0, # eol
0,0,0, # padding
rand(2 ** 32), # csrc
6, 5, 'fubar', # tool
0, # eol
);
return $sr;
}

Loading…
Cancel
Save