static int rtp_data_bts(struct osmo_fd *fd, unsigned int what) { char buf[4096]; struct sockaddr_in addr; struct mgcp_endpoint *endp; int rc, proto; endp = (struct mgcp_endpoint *) fd->data; rc = receive_from(endp, fd->fd, &addr, buf, sizeof(buf)); if (rc <= 0) return -1; proto = fd == &endp->bts_end.rtp ? PROTO_RTP : PROTO_RTCP; /* We have no idea who called us, maybe it is the BTS. */ /* it was the BTS... */ discover_bts(endp, proto, &addr); if (memcmp(&endp->bts_end.addr, &addr.sin_addr, sizeof(addr.sin_addr)) != 0) { LOGP(DMGCP, LOGL_ERROR, "Data from wrong bts %s on 0x%x\n", inet_ntoa(addr.sin_addr), ENDPOINT_NUMBER(endp)); return -1; } if (endp->bts_end.rtp_port != addr.sin_port && endp->bts_end.rtcp_port != addr.sin_port) { LOGP(DMGCP, LOGL_ERROR, "Data from wrong bts source port %d on 0x%x\n", ntohs(addr.sin_port), ENDPOINT_NUMBER(endp)); return -1; } /* throw away the dummy message */ if (rc == 1 && buf[0] == DUMMY_LOAD) { LOGP(DMGCP, LOGL_NOTICE, "Filtered dummy from bts on 0x%x\n", ENDPOINT_NUMBER(endp)); return 0; } /* do this before the loop handling */ endp->bts_end.packets += 1; endp->bts_end.octets += rc; forward_data(fd->fd, &endp->taps[MGCP_TAP_BTS_IN], buf, rc); switch (endp->type) { case MGCP_RTP_DEFAULT: return send_to(endp, DEST_NETWORK, proto == PROTO_RTP, &addr, buf, rc); case MGCP_RTP_TRANSCODED: return send_transcoder(&endp->trans_bts, endp->cfg, proto == PROTO_RTP, buf, rc); } LOGP(DMGCP, LOGL_ERROR, "Bad MGCP type %u on endpoint %u\n", endp->type, ENDPOINT_NUMBER(endp)); return 0; }
static int rtp_data_net(struct osmo_fd *fd, unsigned int what) { char buf[4096]; struct sockaddr_in addr; struct mgcp_endpoint *endp; int rc, proto; endp = (struct mgcp_endpoint *) fd->data; rc = receive_from(endp, fd->fd, &addr, buf, sizeof(buf)); if (rc <= 0) return -1; if (memcmp(&addr.sin_addr, &endp->net_end.addr, sizeof(addr.sin_addr)) != 0) { LOGP(DMGCP, LOGL_ERROR, "Endpoint 0x%x data from wrong address %s vs. ", ENDPOINT_NUMBER(endp), inet_ntoa(addr.sin_addr)); LOGPC(DMGCP, LOGL_ERROR, "%s\n", inet_ntoa(endp->net_end.addr)); return -1; } if (endp->net_end.rtp_port != addr.sin_port && endp->net_end.rtcp_port != addr.sin_port) { LOGP(DMGCP, LOGL_ERROR, "Data from wrong source port %d on 0x%x\n", ntohs(addr.sin_port), ENDPOINT_NUMBER(endp)); return -1; } /* throw away the dummy message */ if (rc == 1 && buf[0] == DUMMY_LOAD) { LOGP(DMGCP, LOGL_NOTICE, "Filtered dummy from network on 0x%x\n", ENDPOINT_NUMBER(endp)); return 0; } proto = fd == &endp->net_end.rtp ? PROTO_RTP : PROTO_RTCP; endp->net_end.packets += 1; endp->net_end.octets += rc; forward_data(fd->fd, &endp->taps[MGCP_TAP_NET_IN], buf, rc); switch (endp->type) { case MGCP_RTP_DEFAULT: return send_to(endp, DEST_BTS, proto == PROTO_RTP, &addr, buf, rc); case MGCP_RTP_TRANSCODED: return send_transcoder(&endp->trans_net, endp->cfg, proto == PROTO_RTP, buf, rc); } LOGP(DMGCP, LOGL_ERROR, "Bad MGCP type %u on endpoint %u\n", endp->type, ENDPOINT_NUMBER(endp)); return 0; }
/* Set the timestamp offset according to the packet duration. */ static int adjust_rtp_timestamp_offset(struct mgcp_endpoint *endp, struct mgcp_rtp_state *state, struct mgcp_rtp_end *rtp_end, struct sockaddr_in *addr, int16_t delta_seq, uint32_t in_timestamp) { int32_t tsdelta = state->packet_duration; int timestamp_offset; uint32_t out_timestamp; if (tsdelta == 0) { tsdelta = state->out_stream.last_tsdelta; if (tsdelta != 0) { LOGP(DMGCP, LOGL_NOTICE, "A fixed packet duration is not available on 0x%x, " "using last output timestamp delta instead: %d " "from %s:%d in %d\n", ENDPOINT_NUMBER(endp), tsdelta, inet_ntoa(addr->sin_addr), ntohs(addr->sin_port), endp->conn_mode); } else { tsdelta = rtp_end->codec.rate * 20 / 1000; LOGP(DMGCP, LOGL_NOTICE, "Fixed packet duration and last timestamp delta " "are not available on 0x%x, " "using fixed 20ms instead: %d " "from %s:%d in %d\n", ENDPOINT_NUMBER(endp), tsdelta, inet_ntoa(addr->sin_addr), ntohs(addr->sin_port), endp->conn_mode); } } out_timestamp = state->out_stream.last_timestamp + delta_seq * tsdelta; timestamp_offset = out_timestamp - in_timestamp; if (state->timestamp_offset != timestamp_offset) { state->timestamp_offset = timestamp_offset; LOGP(DMGCP, LOGL_NOTICE, "Timestamp offset change on 0x%x SSRC: %u " "SeqNo delta: %d, TS offset: %d, " "from %s:%d in %d\n", ENDPOINT_NUMBER(endp), state->in_stream.ssrc, delta_seq, state->timestamp_offset, inet_ntoa(addr->sin_addr), ntohs(addr->sin_port), endp->conn_mode); } return timestamp_offset; }
static int allocate_port(struct mgcp_endpoint *endp, struct mgcp_rtp_end *end, struct mgcp_port_range *range, int (*alloc)(struct mgcp_endpoint *endp, int port)) { int i; if (range->mode == PORT_ALLOC_STATIC) { end->local_alloc = PORT_ALLOC_STATIC; return 0; } /* attempt to find a port */ for (i = 0; i < 200; ++i) { int rc; if (range->last_port >= range->range_end) range->last_port = range->range_start; rc = alloc(endp, range->last_port); range->last_port += 2; if (rc == 0) { end->local_alloc = PORT_ALLOC_DYNAMIC; return 0; } } LOGP(DMGCP, LOGL_ERROR, "Allocating a RTP/RTCP port failed 200 times 0x%x.\n", ENDPOINT_NUMBER(endp)); return -1; }
/* Set the timestamp offset according to the packet duration. */ static int align_rtp_timestamp_offset(struct mgcp_endpoint *endp, struct mgcp_rtp_state *state, struct mgcp_rtp_end *rtp_end, struct sockaddr_in *addr, uint32_t timestamp) { int timestamp_error = 0; int ptime = state->packet_duration; /* Align according to: T + Toffs - Tlast = k * Tptime */ timestamp_error = compute_timestamp_aligment_error( &state->out_stream, ptime, timestamp + state->timestamp_offset); if (timestamp_error) { state->timestamp_offset += ptime - timestamp_error; LOGP(DMGCP, LOGL_NOTICE, "Corrected timestamp alignment error of %d on 0x%x SSRC: %u " "new TS offset: %d, " "from %s:%d in %d\n", timestamp_error, ENDPOINT_NUMBER(endp), state->in_stream.ssrc, state->timestamp_offset, inet_ntoa(addr->sin_addr), ntohs(addr->sin_port), endp->conn_mode); } OSMO_ASSERT(compute_timestamp_aligment_error(&state->out_stream, ptime, timestamp + state->timestamp_offset) == 0); return timestamp_error; }
static void discover_bts(struct mgcp_endpoint *endp, int proto, struct sockaddr_in *addr) { struct mgcp_config *cfg = endp->cfg; if (proto == MGCP_PROTO_RTP && endp->bts_end.rtp_port == 0) { if (!cfg->bts_ip || memcmp(&addr->sin_addr, &cfg->bts_in, sizeof(cfg->bts_in)) == 0 || memcmp(&addr->sin_addr, &endp->bts_end.addr, sizeof(endp->bts_end.addr)) == 0) { endp->bts_end.rtp_port = addr->sin_port; endp->bts_end.addr = addr->sin_addr; LOGP(DMGCP, LOGL_NOTICE, "Found BTS for endpoint: 0x%x on port: %d/%d of %s\n", ENDPOINT_NUMBER(endp), ntohs(endp->bts_end.rtp_port), ntohs(endp->bts_end.rtcp_port), inet_ntoa(addr->sin_addr)); } } else if (proto == MGCP_PROTO_RTCP && endp->bts_end.rtcp_port == 0) { if (memcmp(&endp->bts_end.addr, &addr->sin_addr, sizeof(endp->bts_end.addr)) == 0) { endp->bts_end.rtcp_port = addr->sin_port; } } }
int mgcp_send_dummy(struct mgcp_endpoint *endp) { static char buf[] = { MGCP_DUMMY_LOAD }; int rc; rc = mgcp_udp_send(endp->net_end.rtp.fd, &endp->net_end.addr, endp->net_end.rtp_port, buf, 1); if (rc == -1) goto failed; if (endp->tcfg->omit_rtcp) return rc; rc = mgcp_udp_send(endp->net_end.rtcp.fd, &endp->net_end.addr, endp->net_end.rtcp_port, buf, 1); if (rc >= 0) return rc; failed: LOGP(DMGCP, LOGL_ERROR, "Failed to send dummy packet: %s on: 0x%x to %s\n", strerror(errno), ENDPOINT_NUMBER(endp), inet_ntoa(endp->net_end.addr)); return -1; }
void mgcp_free_endp(struct mgcp_endpoint *endp) { LOGP(DMGCP, LOGL_DEBUG, "Deleting endpoint on: 0x%x\n", ENDPOINT_NUMBER(endp)); endp->ci = CI_UNUSED; endp->allocated = 0; if (endp->callid) { talloc_free(endp->callid); endp->callid = NULL; } if (endp->local_options) { talloc_free(endp->local_options); endp->local_options = NULL; } mgcp_rtp_end_reset(&endp->bts_end); mgcp_rtp_end_reset(&endp->net_end); mgcp_rtp_end_reset(&endp->trans_net); mgcp_rtp_end_reset(&endp->trans_bts); endp->is_transcoded = 0; memset(&endp->net_state, 0, sizeof(endp->net_state)); memset(&endp->bts_state, 0, sizeof(endp->bts_state)); endp->conn_mode = endp->orig_mode = MGCP_CONN_NONE; endp->allow_patch = 0; memset(&endp->taps, 0, sizeof(endp->taps)); }
static int int_bind(const char *port, struct mgcp_rtp_end *end, int (*cb)(struct osmo_fd *, unsigned), struct mgcp_endpoint *_endp, int rtp_port) { if (end->rtp.fd != -1 || end->rtcp.fd != -1) { LOGP(DMGCP, LOGL_ERROR, "Previous %s was still bound on %d\n", port, ENDPOINT_NUMBER(_endp)); mgcp_free_rtp_port(end); } end->local_port = rtp_port; end->rtp.cb = cb; end->rtp.data = _endp; end->rtcp.data = _endp; end->rtcp.cb = cb; return bind_rtp(_endp->cfg, end, ENDPOINT_NUMBER(_endp)); }
static void delete_transcoder(struct mgcp_endpoint *endp) { int in_endp = ENDPOINT_NUMBER(endp); int out_endp = endp_back_channel(in_endp); if (!endp->is_transcoded) return; send_dlcx(endp, in_endp); send_dlcx(endp, out_endp); }
static int verify_call_id(const struct mgcp_endpoint *endp, const char *callid) { if (strcmp(endp->callid, callid) != 0) { LOGP(DMGCP, LOGL_ERROR, "CallIDs does not match on 0x%x. '%s' != '%s'\n", ENDPOINT_NUMBER(endp), endp->callid, callid); return -1; } return 0; }
static int rtp_data_transcoder(struct mgcp_rtp_end *end, struct mgcp_endpoint *_endp, int dest, struct osmo_fd *fd) { char buf[RTP_BUF_SIZE]; struct sockaddr_in addr; struct mgcp_config *cfg; int rc, proto; cfg = _endp->cfg; rc = receive_from(_endp, fd->fd, &addr, buf, sizeof(buf)); if (rc <= 0) return -1; proto = fd == &end->rtp ? MGCP_PROTO_RTP : MGCP_PROTO_RTCP; if (memcmp(&addr.sin_addr, &cfg->transcoder_in, sizeof(addr.sin_addr)) != 0) { LOGP(DMGCP, LOGL_ERROR, "Data not coming from transcoder dest: %d %s on 0x%x\n", dest, inet_ntoa(addr.sin_addr), ENDPOINT_NUMBER(_endp)); return -1; } if (end->rtp_port != addr.sin_port && end->rtcp_port != addr.sin_port) { LOGP(DMGCP, LOGL_ERROR, "Data from wrong transcoder dest %d source port %d on 0x%x\n", dest, ntohs(addr.sin_port), ENDPOINT_NUMBER(_endp)); return -1; } /* throw away the dummy message */ if (rc == 1 && buf[0] == MGCP_DUMMY_LOAD) { LOGP(DMGCP, LOGL_NOTICE, "Filtered dummy from transcoder dest %d on 0x%x\n", dest, ENDPOINT_NUMBER(_endp)); return 0; } end->packets += 1; return mgcp_send(_endp, dest, proto == MGCP_PROTO_RTP, &addr, buf, rc); }
static int verify_ci(const struct mgcp_endpoint *endp, const char *_ci) { uint32_t ci = strtoul(_ci, NULL, 10); if (ci != endp->ci) { LOGP(DMGCP, LOGL_ERROR, "ConnectionIdentifiers do not match on 0x%x. %u != %s\n", ENDPOINT_NUMBER(endp), endp->ci, _ci); return -1; } return 0; }
/* * This can request like DTMF detection and forward, fax detection... it * can also request when the notification should be send and such. We don't * do this right now. */ static struct msgb *handle_noti_req(struct mgcp_config *cfg, struct msgb *msg) { struct mgcp_msg_ptr data_ptrs[6]; const char *trans_id; struct mgcp_endpoint *endp; int found; found = mgcp_analyze_header(cfg, msg, data_ptrs, ARRAY_SIZE(data_ptrs), &trans_id, &endp); if (found != 0) return create_err_response(400, "RQNT", trans_id); if (!endp->allocated) { LOGP(DMGCP, LOGL_ERROR, "Endpoint is not used. 0x%x\n", ENDPOINT_NUMBER(endp)); return create_err_response(400, "RQNT", trans_id); } return create_ok_response(200, "RQNT", trans_id); }
static void create_transcoder(struct mgcp_endpoint *endp) { int port; int in_endp = ENDPOINT_NUMBER(endp); int out_endp = endp_back_channel(in_endp); if (!endp->is_transcoded) return; send_msg(endp, in_endp, endp->trans_bts.local_port, "CRCX", "sendrecv"); send_msg(endp, in_endp, endp->trans_bts.local_port, "MDCX", "sendrecv"); send_msg(endp, out_endp, endp->trans_net.local_port, "CRCX", "sendrecv"); send_msg(endp, out_endp, endp->trans_net.local_port, "MDCX", "sendrecv"); port = rtp_calculate_port(in_endp, endp->cfg->transcoder_remote_base); endp->trans_bts.rtp_port = htons(port); endp->trans_bts.rtcp_port = htons(port + 1); port = rtp_calculate_port(out_endp, endp->cfg->transcoder_remote_base); endp->trans_net.rtp_port = htons(port); endp->trans_net.rtcp_port = htons(port + 1); }
static int receive_from(struct mgcp_endpoint *endp, int fd, struct sockaddr_in *addr, char *buf, int bufsize) { int rc; socklen_t slen = sizeof(*addr); rc = recvfrom(fd, buf, bufsize, 0, (struct sockaddr *) addr, &slen); if (rc < 0) { LOGP(DMGCP, LOGL_ERROR, "Failed to receive message on: 0x%x errno: %d/%s\n", ENDPOINT_NUMBER(endp), errno, strerror(errno)); return -1; } /* do not forward aynthing... maybe there is a packet from the bts */ if (!endp->allocated) return -1; #warning "Slight spec violation. With connection mode recvonly we should attempt to forward." return rc; }
/** * The RFC 3550 Appendix A assumes there are multiple sources but * some of the supported endpoints (e.g. the nanoBTS) can only handle * one source and this code will patch packages to appear as if there * is only one source. * There is also no probation period for new sources. Every package * we receive will be seen as a switch in streams. */ void mgcp_patch_and_count(struct mgcp_endpoint *endp, struct mgcp_rtp_state *state, struct mgcp_rtp_end *rtp_end, struct sockaddr_in *addr, char *data, int len) { uint32_t arrival_time; int32_t transit, d; uint16_t seq, udelta; uint32_t timestamp, ssrc; struct rtp_hdr *rtp_hdr; int payload = rtp_end->codec.payload_type; if (len < sizeof(*rtp_hdr)) return; rtp_hdr = (struct rtp_hdr *) data; seq = ntohs(rtp_hdr->sequence); timestamp = ntohl(rtp_hdr->timestamp); arrival_time = get_current_ts(rtp_end->codec.rate); ssrc = ntohl(rtp_hdr->ssrc); transit = arrival_time - timestamp; if (!state->initialized) { state->in_stream.last_seq = seq - 1; state->in_stream.ssrc = state->orig_ssrc = ssrc; state->in_stream.last_tsdelta = 0; state->base_seq = seq; state->initialized = 1; state->jitter = 0; state->transit = transit; state->packet_duration = mgcp_rtp_packet_duration(endp, rtp_end); state->out_stream = state->in_stream; state->out_stream.last_timestamp = timestamp; state->out_stream.ssrc = ssrc - 1; /* force output SSRC change */ LOGP(DMGCP, LOGL_INFO, "Initializing stream on 0x%x SSRC: %u timestamp: %u " "pkt-duration: %d, from %s:%d in %d\n", ENDPOINT_NUMBER(endp), state->in_stream.ssrc, state->seq_offset, state->packet_duration, inet_ntoa(addr->sin_addr), ntohs(addr->sin_port), endp->conn_mode); if (state->packet_duration == 0) { state->packet_duration = rtp_end->codec.rate * 20 / 1000; LOGP(DMGCP, LOGL_NOTICE, "Fixed packet duration is not available on 0x%x, " "using fixed 20ms instead: %d from %s:%d in %d\n", ENDPOINT_NUMBER(endp), state->packet_duration, inet_ntoa(addr->sin_addr), ntohs(addr->sin_port), endp->conn_mode); } } else if (state->in_stream.ssrc != ssrc) { LOGP(DMGCP, LOGL_NOTICE, "The SSRC changed on 0x%x: %u -> %u " "from %s:%d in %d\n", ENDPOINT_NUMBER(endp), state->in_stream.ssrc, rtp_hdr->ssrc, inet_ntoa(addr->sin_addr), ntohs(addr->sin_port), endp->conn_mode); state->in_stream.ssrc = ssrc; state->jitter = 0; state->transit = transit; if (rtp_end->force_constant_ssrc) { int16_t delta_seq; /* Always increment seqno by 1 */ state->seq_offset = (state->out_stream.last_seq + 1) - seq; /* Estimate number of packets that would have been sent */ delta_seq = (arrival_time - state->in_stream.last_arrival_time + state->packet_duration/2) / state->packet_duration; adjust_rtp_timestamp_offset(endp, state, rtp_end, addr, delta_seq, timestamp); state->patch_ssrc = 1; ssrc = state->orig_ssrc; if (rtp_end->force_constant_ssrc != -1) rtp_end->force_constant_ssrc -= 1; LOGP(DMGCP, LOGL_NOTICE, "SSRC patching enabled on 0x%x SSRC: %u " "SeqNo offset: %d, TS offset: %d " "from %s:%d in %d\n", ENDPOINT_NUMBER(endp), state->in_stream.ssrc, state->seq_offset, state->timestamp_offset, inet_ntoa(addr->sin_addr), ntohs(addr->sin_port), endp->conn_mode); } state->in_stream.last_tsdelta = 0; } else { /* Compute current per-packet timestamp delta */ check_rtp_timestamp(endp, state, &state->in_stream, rtp_end, addr, seq, timestamp, "input", &state->in_stream.last_tsdelta); if (state->patch_ssrc) ssrc = state->orig_ssrc; } /* Save before patching */ state->in_stream.last_timestamp = timestamp; state->in_stream.last_seq = seq; state->in_stream.last_arrival_time = arrival_time; if (rtp_end->force_aligned_timing && state->out_stream.ssrc == ssrc && state->packet_duration) /* Align the timestamp offset */ align_rtp_timestamp_offset(endp, state, rtp_end, addr, timestamp); /* Store the updated SSRC back to the packet */ if (state->patch_ssrc) rtp_hdr->ssrc = htonl(ssrc); /* Apply the offset and store it back to the packet. * This won't change anything if the offset is 0, so the conditional is * omitted. */ seq += state->seq_offset; rtp_hdr->sequence = htons(seq); timestamp += state->timestamp_offset; rtp_hdr->timestamp = htonl(timestamp); /* Check again, whether the timestamps are still valid */ if (state->out_stream.ssrc == ssrc) check_rtp_timestamp(endp, state, &state->out_stream, rtp_end, addr, seq, timestamp, "output", &state->out_stream.last_tsdelta); /* * The below takes the shape of the validation from Appendix A. Check * if there is something weird with the sequence number, otherwise check * for a wrap around in the sequence number. * * Note that last_seq is used where the appendix mentions max_seq. */ udelta = seq - state->out_stream.last_seq; if (udelta < RTP_MAX_DROPOUT) { if (seq < state->out_stream.last_seq) state->cycles += RTP_SEQ_MOD; } else if (udelta <= RTP_SEQ_MOD - RTP_MAX_MISORDER) { LOGP(DMGCP, LOGL_NOTICE, "RTP seqno made a very large jump on 0x%x delta: %u\n", ENDPOINT_NUMBER(endp), udelta); } /* * Calculate the jitter between the two packages. The TS should be * taken closer to the read function. This was taken from the * Appendix A of RFC 3550. Timestamp and arrival_time have a 1/rate * resolution. */ d = transit - state->transit; state->transit = transit; if (d < 0) d = -d; state->jitter += d - ((state->jitter + 8) >> 4); /* Save output values */ state->out_stream.last_seq = seq; state->out_stream.last_timestamp = timestamp; state->out_stream.ssrc = ssrc; if (payload < 0) return; rtp_hdr->payload_type = payload; }
static int check_rtp_timestamp(struct mgcp_endpoint *endp, struct mgcp_rtp_state *state, struct mgcp_rtp_stream_state *sstate, struct mgcp_rtp_end *rtp_end, struct sockaddr_in *addr, uint16_t seq, uint32_t timestamp, const char *text, int32_t *tsdelta_out) { int32_t tsdelta; int32_t timestamp_error; /* Not fully intialized, skip */ if (sstate->last_tsdelta == 0 && timestamp == sstate->last_timestamp) return 0; if (seq == sstate->last_seq) { if (timestamp != sstate->last_timestamp) { sstate->err_ts_counter += 1; LOGP(DMGCP, LOGL_ERROR, "The %s timestamp delta is != 0 but the sequence " "number %d is the same, " "TS offset: %d, SeqNo offset: %d " "on 0x%x SSRC: %u timestamp: %u " "from %s:%d in %d\n", text, seq, state->timestamp_offset, state->seq_offset, ENDPOINT_NUMBER(endp), sstate->ssrc, timestamp, inet_ntoa(addr->sin_addr), ntohs(addr->sin_port), endp->conn_mode); } return 0; } tsdelta = (int32_t)(timestamp - sstate->last_timestamp) / (int16_t)(seq - sstate->last_seq); if (tsdelta == 0) { /* Don't update *tsdelta_out */ LOGP(DMGCP, LOGL_NOTICE, "The %s timestamp delta is %d " "on 0x%x SSRC: %u timestamp: %u " "from %s:%d in %d\n", text, tsdelta, ENDPOINT_NUMBER(endp), sstate->ssrc, timestamp, inet_ntoa(addr->sin_addr), ntohs(addr->sin_port), endp->conn_mode); return 0; } if (sstate->last_tsdelta != tsdelta) { if (sstate->last_tsdelta) { LOGP(DMGCP, LOGL_INFO, "The %s timestamp delta changes from %d to %d " "on 0x%x SSRC: %u timestamp: %u from %s:%d in %d\n", text, sstate->last_tsdelta, tsdelta, ENDPOINT_NUMBER(endp), sstate->ssrc, timestamp, inet_ntoa(addr->sin_addr), ntohs(addr->sin_port), endp->conn_mode); } } if (tsdelta_out) *tsdelta_out = tsdelta; timestamp_error = compute_timestamp_aligment_error(sstate, state->packet_duration, timestamp); if (timestamp_error) { sstate->err_ts_counter += 1; LOGP(DMGCP, LOGL_NOTICE, "The %s timestamp has an alignment error of %d " "on 0x%x SSRC: %u " "SeqNo delta: %d, TS delta: %d, dTS/dSeq: %d " "from %s:%d in mode %d. ptime: %d\n", text, timestamp_error, ENDPOINT_NUMBER(endp), sstate->ssrc, (int16_t)(seq - sstate->last_seq), (int32_t)(timestamp - sstate->last_timestamp), tsdelta, inet_ntoa(addr->sin_addr), ntohs(addr->sin_port), endp->conn_mode, state->packet_duration); } return 1; }
static struct msgb *handle_create_con(struct mgcp_config *cfg, struct msgb *msg) { struct mgcp_msg_ptr data_ptrs[6]; int found, i, line_start; const char *trans_id; struct mgcp_trunk_config *tcfg; struct mgcp_endpoint *endp; int error_code = 400; found = mgcp_analyze_header(cfg, msg, data_ptrs, ARRAY_SIZE(data_ptrs), &trans_id, &endp); if (found != 0) return create_err_response(510, "CRCX", trans_id); tcfg = endp->tcfg; if (endp->allocated) { if (tcfg->force_realloc) { LOGP(DMGCP, LOGL_NOTICE, "Endpoint 0x%x already allocated. Forcing realloc.\n", ENDPOINT_NUMBER(endp)); mgcp_free_endp(endp); if (cfg->realloc_cb) cfg->realloc_cb(tcfg, ENDPOINT_NUMBER(endp)); } else { LOGP(DMGCP, LOGL_ERROR, "Endpoint is already used. 0x%x\n", ENDPOINT_NUMBER(endp)); return create_err_response(400, "CRCX", trans_id); } } /* parse CallID C: and LocalParameters L: */ MSG_TOKENIZE_START switch (msg->l3h[line_start]) { case 'L': endp->local_options = talloc_strdup(tcfg->endpoints, (const char *)&msg->l3h[line_start + 3]); break; case 'C': endp->callid = talloc_strdup(tcfg->endpoints, (const char *)&msg->l3h[line_start + 3]); break; case 'M': if (parse_conn_mode((const char *)&msg->l3h[line_start + 3], &endp->conn_mode) != 0) { error_code = 517; goto error2; } endp->orig_mode = endp->conn_mode; break; default: LOGP(DMGCP, LOGL_NOTICE, "Unhandled option: '%c'/%d on 0x%x\n", msg->l3h[line_start], msg->l3h[line_start], ENDPOINT_NUMBER(endp)); break; } MSG_TOKENIZE_END /* initialize */ endp->net_end.rtp_port = endp->net_end.rtcp_port = endp->bts_end.rtp_port = endp->bts_end.rtcp_port = 0; /* set to zero until we get the info */ memset(&endp->net_end.addr, 0, sizeof(endp->net_end.addr)); /* bind to the port now */ if (allocate_ports(endp) != 0) goto error2; /* assign a local call identifier or fail */ endp->ci = generate_call_id(cfg); if (endp->ci == CI_UNUSED) goto error2; endp->allocated = 1; endp->bts_end.payload_type = tcfg->audio_payload; /* policy CB */ if (cfg->policy_cb) { switch (cfg->policy_cb(tcfg, ENDPOINT_NUMBER(endp), MGCP_ENDP_CRCX, trans_id)) { case MGCP_POLICY_REJECT: LOGP(DMGCP, LOGL_NOTICE, "CRCX rejected by policy on 0x%x\n", ENDPOINT_NUMBER(endp)); mgcp_free_endp(endp); return create_err_response(400, "CRCX", trans_id); break; case MGCP_POLICY_DEFER: /* stop processing */ create_transcoder(endp); return NULL; break; case MGCP_POLICY_CONT: /* just continue */ break; } } LOGP(DMGCP, LOGL_DEBUG, "Creating endpoint on: 0x%x CI: %u port: %u/%u\n", ENDPOINT_NUMBER(endp), endp->ci, endp->net_end.local_port, endp->bts_end.local_port); if (cfg->change_cb) cfg->change_cb(tcfg, ENDPOINT_NUMBER(endp), MGCP_ENDP_CRCX); create_transcoder(endp); return create_response_with_sdp(endp, "CRCX", trans_id); error: LOGP(DMGCP, LOGL_ERROR, "Malformed line: %s on 0x%x with: line_start: %d %d\n", osmo_hexdump(msg->l3h, msgb_l3len(msg)), ENDPOINT_NUMBER(endp), line_start, i); return create_err_response(error_code, "CRCX", trans_id); error2: mgcp_free_endp(endp); LOGP(DMGCP, LOGL_NOTICE, "Resource error on 0x%x\n", ENDPOINT_NUMBER(endp)); return create_err_response(error_code, "CRCX", trans_id); }
int mgcp_transcoding_setup(struct mgcp_endpoint *endp, struct mgcp_rtp_end *dst_end, struct mgcp_rtp_end *src_end) { struct mgcp_process_rtp_state *state; enum audio_format src_fmt, dst_fmt; const struct mgcp_rtp_codec *src_codec = &src_end->codec; const struct mgcp_rtp_codec *dst_codec = &dst_end->codec; /* cleanup first */ if (dst_end->rtp_process_data) { talloc_free(dst_end->rtp_process_data); dst_end->rtp_process_data = NULL; } if (!src_end) return 0; src_fmt = get_audio_format(src_codec); dst_fmt = get_audio_format(dst_codec); LOGP(DMGCP, LOGL_ERROR, "Checking transcoding: %s (%d) -> %s (%d)\n", src_codec->subtype_name, src_codec->payload_type, dst_codec->subtype_name, dst_codec->payload_type); if (src_fmt == AF_INVALID || dst_fmt == AF_INVALID) { if (!src_codec->subtype_name || !dst_codec->subtype_name) /* Not enough info, do nothing */ return 0; if (strcmp(src_codec->subtype_name, dst_codec->subtype_name) == 0) /* Nothing to do */ return 0; LOGP(DMGCP, LOGL_ERROR, "Cannot transcode: %s codec not supported (%s -> %s).\n", src_fmt != AF_INVALID ? "destination" : "source", src_codec->audio_name, dst_codec->audio_name); return -EINVAL; } if (src_codec->rate && dst_codec->rate && src_codec->rate != dst_codec->rate) { LOGP(DMGCP, LOGL_ERROR, "Cannot transcode: rate conversion (%d -> %d) not supported.\n", src_codec->rate, dst_codec->rate); return -EINVAL; } state = talloc_zero(endp->tcfg->cfg, struct mgcp_process_rtp_state); talloc_set_destructor(state, processing_state_destructor); dst_end->rtp_process_data = state; state->src_fmt = src_fmt; switch (state->src_fmt) { case AF_L16: case AF_S16: state->src_frame_size = 80 * sizeof(short); state->src_samples_per_frame = 80; break; case AF_GSM: state->src_frame_size = sizeof(gsm_frame); state->src_samples_per_frame = 160; state->src.gsm_handle = gsm_create(); if (!state->src.gsm_handle) { LOGP(DMGCP, LOGL_ERROR, "Failed to initialize GSM decoder.\n"); return -EINVAL; } break; #ifdef HAVE_BCG729 case AF_G729: state->src_frame_size = 10; state->src_samples_per_frame = 80; state->src.g729_dec = initBcg729DecoderChannel(); if (!state->src.g729_dec) { LOGP(DMGCP, LOGL_ERROR, "Failed to initialize G.729 decoder.\n"); return -EINVAL; } break; #endif case AF_PCMA: state->src_frame_size = 80; state->src_samples_per_frame = 80; break; default: break; } state->dst_fmt = dst_fmt; switch (state->dst_fmt) { case AF_L16: case AF_S16: state->dst_frame_size = 80*sizeof(short); state->dst_samples_per_frame = 80; break; case AF_GSM: state->dst_frame_size = sizeof(gsm_frame); state->dst_samples_per_frame = 160; state->dst.gsm_handle = gsm_create(); if (!state->dst.gsm_handle) { LOGP(DMGCP, LOGL_ERROR, "Failed to initialize GSM encoder.\n"); return -EINVAL; } break; #ifdef HAVE_BCG729 case AF_G729: state->dst_frame_size = 10; state->dst_samples_per_frame = 80; state->dst.g729_enc = initBcg729EncoderChannel(); if (!state->dst.g729_enc) { LOGP(DMGCP, LOGL_ERROR, "Failed to initialize G.729 decoder.\n"); return -EINVAL; } break; #endif case AF_PCMA: state->dst_frame_size = 80; state->dst_samples_per_frame = 80; break; default: break; } if (dst_end->force_output_ptime) state->dst_packet_duration = mgcp_rtp_packet_duration(endp, dst_end); LOGP(DMGCP, LOGL_INFO, "Initialized RTP processing on: 0x%x " "conv: %d (%d, %d, %s) -> %d (%d, %d, %s)\n", ENDPOINT_NUMBER(endp), src_fmt, src_codec->payload_type, src_codec->rate, src_end->fmtp_extra, dst_fmt, dst_codec->payload_type, dst_codec->rate, dst_end->fmtp_extra); return 0; }
static struct msgb *handle_delete_con(struct mgcp_config *cfg, struct msgb *msg) { struct mgcp_msg_ptr data_ptrs[6]; int found, i, line_start; const char *trans_id; struct mgcp_endpoint *endp; int error_code = 400; int silent = 0; found = mgcp_analyze_header(cfg, msg, data_ptrs, ARRAY_SIZE(data_ptrs), &trans_id, &endp); if (found != 0) return create_err_response(error_code, "DLCX", trans_id); if (!endp->allocated) { LOGP(DMGCP, LOGL_ERROR, "Endpoint is not used. 0x%x\n", ENDPOINT_NUMBER(endp)); return create_err_response(400, "DLCX", trans_id); } MSG_TOKENIZE_START switch (msg->l3h[line_start]) { case 'C': { if (verify_call_id(endp, (const char *)&msg->l3h[line_start + 3]) != 0) goto error3; break; } case 'I': { if (verify_ci(endp, (const char *)&msg->l3h[line_start + 3]) != 0) goto error3; break; case 'Z': silent = strcmp("noanswer", (const char *)&msg->l3h[line_start + 3]) == 0; break; } default: LOGP(DMGCP, LOGL_NOTICE, "Unhandled option: '%c'/%d on 0x%x\n", msg->l3h[line_start], msg->l3h[line_start], ENDPOINT_NUMBER(endp)); break; } MSG_TOKENIZE_END /* policy CB */ if (cfg->policy_cb) { switch (cfg->policy_cb(endp->tcfg, ENDPOINT_NUMBER(endp), MGCP_ENDP_DLCX, trans_id)) { case MGCP_POLICY_REJECT: LOGP(DMGCP, LOGL_NOTICE, "DLCX rejected by policy on 0x%x\n", ENDPOINT_NUMBER(endp)); if (silent) goto out_silent; return create_err_response(400, "DLCX", trans_id); break; case MGCP_POLICY_DEFER: /* stop processing */ delete_transcoder(endp); return NULL; break; case MGCP_POLICY_CONT: /* just continue */ break; } } /* free the connection */ LOGP(DMGCP, LOGL_DEBUG, "Deleted endpoint on: 0x%x Server: %s:%u\n", ENDPOINT_NUMBER(endp), inet_ntoa(endp->net_end.addr), ntohs(endp->net_end.rtp_port)); delete_transcoder(endp); mgcp_free_endp(endp); if (cfg->change_cb) cfg->change_cb(endp->tcfg, ENDPOINT_NUMBER(endp), MGCP_ENDP_DLCX); if (silent) goto out_silent; return create_ok_response(250, "DLCX", trans_id); error: LOGP(DMGCP, LOGL_ERROR, "Malformed line: %s on 0x%x with: line_start: %d %d\n", osmo_hexdump(msg->l3h, msgb_l3len(msg)), ENDPOINT_NUMBER(endp), line_start, i); return create_err_response(error_code, "DLCX", trans_id); error3: return create_err_response(error_code, "DLCX", trans_id); out_silent: return NULL; }
static struct msgb *handle_modify_con(struct mgcp_config *cfg, struct msgb *msg) { struct mgcp_msg_ptr data_ptrs[6]; int found, i, line_start; const char *trans_id; struct mgcp_endpoint *endp; int error_code = 500; int silent = 0; found = mgcp_analyze_header(cfg, msg, data_ptrs, ARRAY_SIZE(data_ptrs), &trans_id, &endp); if (found != 0) return create_err_response(510, "MDCX", trans_id); if (endp->ci == CI_UNUSED) { LOGP(DMGCP, LOGL_ERROR, "Endpoint is not holding a connection. 0x%x\n", ENDPOINT_NUMBER(endp)); return create_err_response(400, "MDCX", trans_id); } MSG_TOKENIZE_START switch (msg->l3h[line_start]) { case 'C': { if (verify_call_id(endp, (const char *)&msg->l3h[line_start + 3]) != 0) goto error3; break; } case 'I': { if (verify_ci(endp, (const char *)&msg->l3h[line_start + 3]) != 0) goto error3; break; } case 'L': /* skip */ break; case 'M': if (parse_conn_mode((const char *)&msg->l3h[line_start + 3], &endp->conn_mode) != 0) { error_code = 517; goto error3; } endp->orig_mode = endp->conn_mode; break; case 'Z': silent = strcmp("noanswer", (const char *)&msg->l3h[line_start + 3]) == 0; break; case '\0': /* SDP file begins */ break; case 'a': case 'o': case 's': case 't': case 'v': /* skip these SDP attributes */ break; case 'm': { int port; int payload; const char *param = (const char *)&msg->l3h[line_start]; if (sscanf(param, "m=audio %d RTP/AVP %d", &port, &payload) == 2) { endp->net_end.rtp_port = htons(port); endp->net_end.rtcp_port = htons(port + 1); endp->net_end.payload_type = payload; } break; } case 'c': { char ipv4[16]; const char *param = (const char *)&msg->l3h[line_start]; if (sscanf(param, "c=IN IP4 %15s", ipv4) == 1) { inet_aton(ipv4, &endp->net_end.addr); } break; } default: LOGP(DMGCP, LOGL_NOTICE, "Unhandled option: '%c'/%d on 0x%x\n", msg->l3h[line_start], msg->l3h[line_start], ENDPOINT_NUMBER(endp)); break; } MSG_TOKENIZE_END /* policy CB */ if (cfg->policy_cb) { switch (cfg->policy_cb(endp->tcfg, ENDPOINT_NUMBER(endp), MGCP_ENDP_MDCX, trans_id)) { case MGCP_POLICY_REJECT: LOGP(DMGCP, LOGL_NOTICE, "MDCX rejected by policy on 0x%x\n", ENDPOINT_NUMBER(endp)); if (silent) goto out_silent; return create_err_response(400, "MDCX", trans_id); break; case MGCP_POLICY_DEFER: /* stop processing */ return NULL; break; case MGCP_POLICY_CONT: /* just continue */ break; } } /* modify */ LOGP(DMGCP, LOGL_DEBUG, "Modified endpoint on: 0x%x Server: %s:%u\n", ENDPOINT_NUMBER(endp), inet_ntoa(endp->net_end.addr), ntohs(endp->net_end.rtp_port)); if (cfg->change_cb) cfg->change_cb(endp->tcfg, ENDPOINT_NUMBER(endp), MGCP_ENDP_MDCX); if (silent) goto out_silent; return create_response_with_sdp(endp, "MDCX", trans_id); error: LOGP(DMGCP, LOGL_ERROR, "Malformed line: %s on 0x%x with: line_start: %d %d %d\n", osmo_hexdump(msg->l3h, msgb_l3len(msg)), ENDPOINT_NUMBER(endp), line_start, i, msg->l3h[line_start]); return create_err_response(error_code, "MDCX", trans_id); error3: return create_err_response(error_code, "MDCX", trans_id); out_silent: return NULL; }
int mgcp_transcoding_process_rtp(struct mgcp_endpoint *endp, struct mgcp_rtp_end *dst_end, char *data, int *len, int buf_size) { struct mgcp_process_rtp_state *state; const size_t rtp_hdr_size = sizeof(struct rtp_hdr); struct rtp_hdr *rtp_hdr = (struct rtp_hdr *) data; char *payload_data = (char *) &rtp_hdr->data[0]; int payload_len = *len - rtp_hdr_size; uint8_t *src = (uint8_t *)payload_data; uint8_t *dst = (uint8_t *)payload_data; size_t nbytes = payload_len; size_t nsamples; size_t max_samples; uint32_t ts_no; int rc; state = check_transcode_state(endp, dst_end, rtp_hdr); if (!state) return 0; if (state->src_fmt == state->dst_fmt) { if (!state->dst_packet_duration) return 0; /* TODO: repackage without transcoding */ } /* If the remaining samples do not fit into a fixed ptime, * a) discard them, if the next packet is much later * b) add silence and * send it, if the current packet is not * yet too late * c) append the sample data, if the timestamp matches exactly */ /* TODO: check payload type (-> G.711 comfort noise) */ if (payload_len > 0) { ts_no = ntohl(rtp_hdr->timestamp); if (!state->is_running) { state->next_seq = ntohs(rtp_hdr->sequence); state->next_time = ts_no; state->is_running = 1; } if (state->sample_cnt > 0) { int32_t delta = ts_no - state->next_time; /* TODO: check sequence? reordering? packet loss? */ if (delta > state->sample_cnt) { /* There is a time gap between the last packet * and the current one. Just discard the * partial data that is left in the buffer. * TODO: This can be improved by adding silence * instead if the delta is small enough. */ LOGP(DMGCP, LOGL_NOTICE, "0x%x dropping sample buffer due delta=%d sample_cnt=%d\n", ENDPOINT_NUMBER(endp), delta, state->sample_cnt); state->sample_cnt = 0; state->next_time = ts_no; } else if (delta < 0) { LOGP(DMGCP, LOGL_NOTICE, "RTP time jumps backwards, delta = %d, " "discarding buffered samples\n", delta); state->sample_cnt = 0; state->sample_offs = 0; return -EAGAIN; } /* Make sure the samples start without offset */ if (state->sample_offs && state->sample_cnt) memmove(&state->samples[0], &state->samples[state->sample_offs], state->sample_cnt * sizeof(state->samples[0])); } state->sample_offs = 0; /* Append decoded audio to samples */ decode_audio(state, &src, &nbytes); if (nbytes > 0) LOGP(DMGCP, LOGL_NOTICE, "Skipped audio frame in RTP packet: %d octets\n", nbytes); } else ts_no = state->next_time; if (state->sample_cnt < state->dst_packet_duration) return -EAGAIN; max_samples = state->dst_packet_duration ? state->dst_packet_duration : state->sample_cnt; nsamples = state->sample_cnt; rc = encode_audio(state, dst, buf_size, max_samples); /* * There were no samples to encode? * TODO: how does this work for comfort noise? */ if (rc == 0) return -ENOMSG; /* Any other error during the encoding */ if (rc < 0) return rc; nsamples -= state->sample_cnt; *len = rtp_hdr_size + rc; rtp_hdr->sequence = htons(state->next_seq); rtp_hdr->timestamp = htonl(ts_no); state->next_seq += 1; state->next_time = ts_no + nsamples; /* * XXX: At this point we should always have consumed * samples. So doing OSMO_ASSERT(nsamples > 0) and returning * rtp_hdr_size should be fine. */ return nsamples ? rtp_hdr_size : 0; }
/** * The RFC 3550 Appendix A assumes there are multiple sources but * some of the supported endpoints (e.g. the nanoBTS) can only handle * one source and this code will patch packages to appear as if there * is only one source. * There is also no probation period for new sources. Every package * we receive will be seen as a switch in streams. */ static void patch_and_count(struct mgcp_endpoint *endp, struct mgcp_rtp_state *state, int payload, struct sockaddr_in *addr, char *data, int len) { uint32_t arrival_time; int32_t transit, d; uint16_t seq, udelta; uint32_t timestamp; struct rtp_hdr *rtp_hdr; if (len < sizeof(*rtp_hdr)) return; rtp_hdr = (struct rtp_hdr *) data; seq = ntohs(rtp_hdr->sequence); timestamp = ntohl(rtp_hdr->timestamp); arrival_time = get_current_ts(); if (!state->initialized) { state->base_seq = seq; state->max_seq = seq - 1; state->ssrc = state->orig_ssrc = rtp_hdr->ssrc; state->initialized = 1; state->last_timestamp = timestamp; state->jitter = 0; state->transit = arrival_time - timestamp; } else if (state->ssrc != rtp_hdr->ssrc) { state->ssrc = rtp_hdr->ssrc; state->seq_offset = (state->max_seq + 1) - seq; state->timestamp_offset = state->last_timestamp - timestamp; state->patch = endp->allow_patch; LOGP(DMGCP, LOGL_NOTICE, "The SSRC changed on 0x%x SSRC: %u offset: %d from %s:%d in %d\n", ENDPOINT_NUMBER(endp), state->ssrc, state->seq_offset, inet_ntoa(addr->sin_addr), ntohs(addr->sin_port), endp->conn_mode); } /* apply the offset and store it back to the packet */ if (state->patch) { seq += state->seq_offset; rtp_hdr->sequence = htons(seq); rtp_hdr->ssrc = state->orig_ssrc; timestamp += state->timestamp_offset; rtp_hdr->timestamp = htonl(timestamp); } /* * The below takes the shape of the validation from Appendix A. Check * if there is something weird with the sequence number, otherwise check * for a wrap around in the sequence number. */ udelta = seq - state->max_seq; if (udelta < RTP_MAX_DROPOUT) { if (seq < state->max_seq) state->cycles += RTP_SEQ_MOD; } else if (udelta <= RTP_SEQ_MOD - RTP_MAX_MISORDER) { LOGP(DMGCP, LOGL_NOTICE, "RTP seqno made a very large jump on 0x%x delta: %u\n", ENDPOINT_NUMBER(endp), udelta); } /* * calculate the jitter between the two packages. The TS should be * taken closer to the read function. This was taken from the * Appendix A of RFC 3550. The local timestamp has a usec resolution. */ transit = arrival_time - timestamp; d = transit - state->transit; state->transit = transit; if (d < 0) d = -d; state->jitter += d - ((state->jitter + 8) >> 4); state->max_seq = seq; state->last_timestamp = timestamp; if (payload < 0) return; rtp_hdr->payload_type = payload; }