mirror of https://github.com/sipwise/rtpengine.git
Merged from branch rfuchs/3.0 Conflicts: daemon/sdp.c debian/changelog tests/simulator-ng.pl utils/ng-clientgit.mgm/mediaproxy-ng/master
parent
caa5edd58a
commit
f8f741b584
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,722 @@
|
||||
#include "call_interfaces.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <glib.h>
|
||||
#include <stdlib.h>
|
||||
#include <pcre.h>
|
||||
|
||||
#include "call.h"
|
||||
#include "aux.h"
|
||||
#include "log.h"
|
||||
#include "redis.h"
|
||||
#include "sdp.h"
|
||||
#include "bencode.h"
|
||||
#include "str.h"
|
||||
#include "control_tcp.h"
|
||||
#include "control_udp.h"
|
||||
|
||||
|
||||
|
||||
|
||||
static int call_stream_address_gstring(GString *o, struct packet_stream *ps, enum stream_address_format format) {
|
||||
int len, ret;
|
||||
char buf[64]; /* 64 bytes ought to be enough for anybody */
|
||||
|
||||
ret = call_stream_address(buf, ps, format, &len);
|
||||
g_string_append_len(o, buf, len);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static str *streams_print(GQueue *s, int start, int end, const char *prefix, enum stream_address_format format) {
|
||||
GString *o;
|
||||
int i, af, port;
|
||||
GList *l;
|
||||
struct call_media *media;
|
||||
struct packet_stream *ps;
|
||||
|
||||
o = g_string_new_str();
|
||||
if (prefix)
|
||||
g_string_append_printf(o, "%s ", prefix);
|
||||
|
||||
for (i = start; i < end; i++) {
|
||||
for (l = s->head; l; l = l->next) {
|
||||
media = l->data;
|
||||
if (media->index == i)
|
||||
goto found;
|
||||
}
|
||||
ilog(LOG_WARNING, "Requested media index %i not found", i);
|
||||
goto out;
|
||||
|
||||
found:
|
||||
if (!media->streams.head) {
|
||||
ilog(LOG_WARNING, "Media has no streams");
|
||||
goto out;
|
||||
}
|
||||
ps = media->streams.head->data;
|
||||
|
||||
if (format == SAF_TCP)
|
||||
call_stream_address_gstring(o, ps, format);
|
||||
|
||||
port = ps->sfd ? ps->sfd->fd.localport : 0;
|
||||
g_string_append_printf(o, (format == 1) ? "%i " : " %i", port);
|
||||
|
||||
if (format == SAF_UDP) {
|
||||
af = call_stream_address_gstring(o, ps, format);
|
||||
g_string_append_printf(o, " %c", (af == AF_INET) ? '4' : '6');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
out:
|
||||
g_string_append(o, "\n");
|
||||
|
||||
return g_string_free_str(o);
|
||||
}
|
||||
|
||||
static int addr_parse_udp(struct stream_params *sp, char **out) {
|
||||
u_int32_t ip4;
|
||||
const char *cp;
|
||||
char c;
|
||||
int i;
|
||||
|
||||
ZERO(*sp);
|
||||
if (out[RE_UDP_UL_ADDR4] && *out[RE_UDP_UL_ADDR4]) {
|
||||
ip4 = inet_addr(out[RE_UDP_UL_ADDR4]);
|
||||
if (ip4 == -1)
|
||||
goto fail;
|
||||
in4_to_6(&sp->rtp_endpoint.ip46, ip4);
|
||||
}
|
||||
else if (out[RE_UDP_UL_ADDR6] && *out[RE_UDP_UL_ADDR6]) {
|
||||
if (inet_pton(AF_INET6, out[RE_UDP_UL_ADDR6], &sp->rtp_endpoint.ip46) != 1)
|
||||
goto fail;
|
||||
}
|
||||
else
|
||||
goto fail;
|
||||
|
||||
sp->rtp_endpoint.port = atoi(out[RE_UDP_UL_PORT]);
|
||||
if (!sp->rtp_endpoint.port && strcmp(out[RE_UDP_UL_PORT], "0"))
|
||||
goto fail;
|
||||
|
||||
if (out[RE_UDP_UL_FLAGS] && *out[RE_UDP_UL_FLAGS]) {
|
||||
i = 0;
|
||||
for (cp =out[RE_UDP_UL_FLAGS]; *cp && i < 2; cp++) {
|
||||
c = chrtoupper(*cp);
|
||||
if (c == 'E')
|
||||
sp->direction[i++] = DIR_EXTERNAL;
|
||||
else if (c == 'I')
|
||||
sp->direction[i++] = DIR_INTERNAL;
|
||||
}
|
||||
}
|
||||
|
||||
if (out[RE_UDP_UL_NUM] && *out[RE_UDP_UL_NUM])
|
||||
sp->index = atoi(out[RE_UDP_UL_NUM]);
|
||||
if (!sp->index)
|
||||
sp->index = 1;
|
||||
sp->consecutive_ports = 1;
|
||||
|
||||
return 0;
|
||||
fail:
|
||||
return -1;
|
||||
}
|
||||
|
||||
static str *call_update_lookup_udp(char **out, struct callmaster *m, enum call_opmode opmode) {
|
||||
struct call *c;
|
||||
struct call_monologue *monologue;
|
||||
GQueue q = G_QUEUE_INIT;
|
||||
struct stream_params sp;
|
||||
str *ret, callid, viabranch, fromtag, totag = STR_NULL;
|
||||
|
||||
str_init(&callid, out[RE_UDP_UL_CALLID]);
|
||||
str_init(&viabranch, out[RE_UDP_UL_VIABRANCH]);
|
||||
str_init(&fromtag, out[RE_UDP_UL_FROMTAG]);
|
||||
if (opmode == OP_ANSWER)
|
||||
str_init(&totag, out[RE_UDP_UL_TOTAG]);
|
||||
|
||||
c = call_get_opmode(&callid, m, opmode);
|
||||
if (!c) {
|
||||
ilog(LOG_WARNING, "["STR_FORMAT"] Got UDP LOOKUP for unknown call-id",
|
||||
STR_FMT(&callid));
|
||||
return str_sprintf("%s 0 " IPF "\n", out[RE_UDP_COOKIE], IPP(m->conf.ipv4));
|
||||
}
|
||||
monologue = call_get_mono_dialogue(c, &fromtag, &totag);
|
||||
|
||||
if (addr_parse_udp(&sp, out))
|
||||
goto fail;
|
||||
|
||||
g_queue_push_tail(&q, &sp);
|
||||
/* XXX return value */
|
||||
monologue_offer_answer(monologue, &q, NULL);
|
||||
g_queue_clear(&q);
|
||||
|
||||
ret = streams_print(&monologue->medias, sp.index, sp.index, out[RE_UDP_COOKIE], SAF_UDP);
|
||||
rwlock_unlock_w(&c->master_lock);
|
||||
|
||||
redis_update(c, m->conf.redis);
|
||||
|
||||
ilog(LOG_INFO, "Returning to SIP proxy: "STR_FORMAT"", STR_FMT(ret));
|
||||
goto out;
|
||||
|
||||
fail:
|
||||
rwlock_unlock_w(&c->master_lock);
|
||||
ilog(LOG_WARNING, "Failed to parse a media stream: %s/%s:%s", out[RE_UDP_UL_ADDR4], out[RE_UDP_UL_ADDR6], out[RE_UDP_UL_PORT]);
|
||||
ret = str_sprintf("%s E8\n", out[RE_UDP_COOKIE]);
|
||||
out:
|
||||
obj_put(c);
|
||||
return ret;
|
||||
}
|
||||
|
||||
str *call_update_udp(char **out, struct callmaster *m) {
|
||||
return call_update_lookup_udp(out, m, OP_OFFER);
|
||||
}
|
||||
str *call_lookup_udp(char **out, struct callmaster *m) {
|
||||
return call_update_lookup_udp(out, m, OP_ANSWER);
|
||||
}
|
||||
|
||||
|
||||
static int info_parse_func(char **a, void **ret, void *p) {
|
||||
GHashTable *ih = p;
|
||||
|
||||
g_hash_table_replace(ih, a[0], a[1]);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void info_parse(const char *s, GHashTable *ih, struct callmaster *m) {
|
||||
pcre_multi_match(m->info_re, m->info_ree, s, 2, info_parse_func, ih, NULL);
|
||||
}
|
||||
|
||||
|
||||
static int streams_parse_func(char **a, void **ret, void *p) {
|
||||
struct stream_params *sp;
|
||||
u_int32_t ip;
|
||||
int *i;
|
||||
|
||||
i = p;
|
||||
sp = g_slice_alloc0(sizeof(*sp));
|
||||
|
||||
ip = inet_addr(a[0]);
|
||||
if (ip == -1)
|
||||
goto fail;
|
||||
|
||||
in4_to_6(&sp->rtp_endpoint.ip46, ip);
|
||||
sp->rtp_endpoint.port = atoi(a[1]);
|
||||
sp->index = ++(*i);
|
||||
sp->consecutive_ports = 1;
|
||||
|
||||
sp->rtcp_endpoint = sp->rtp_endpoint;
|
||||
sp->rtcp_endpoint.port++;
|
||||
|
||||
if (!sp->rtp_endpoint.port && strcmp(a[1], "0"))
|
||||
goto fail;
|
||||
|
||||
*ret = sp;
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
ilog(LOG_WARNING, "Failed to parse a media stream: %s:%s", a[0], a[1]);
|
||||
g_slice_free1(sizeof(*sp), sp);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
static void streams_parse(const char *s, struct callmaster *m, GQueue *q) {
|
||||
int i;
|
||||
i = 0;
|
||||
pcre_multi_match(m->streams_re, m->streams_ree, s, 3, streams_parse_func, &i, q);
|
||||
}
|
||||
|
||||
static void streams_free(GQueue *q) {
|
||||
struct stream_params *s;
|
||||
|
||||
while ((s = g_queue_pop_head(q))) {
|
||||
if (s->crypto.mki)
|
||||
free(s->crypto.mki);
|
||||
g_slice_free1(sizeof(*s), s);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
static str *call_request_lookup_tcp(char **out, struct callmaster *m, enum call_opmode opmode) {
|
||||
struct call *c;
|
||||
struct call_monologue *monologue;
|
||||
GQueue s = G_QUEUE_INIT;
|
||||
str *ret = NULL, callid, fromtag, totag = STR_NULL;
|
||||
GHashTable *infohash;
|
||||
|
||||
str_init(&callid, out[RE_TCP_RL_CALLID]);
|
||||
infohash = g_hash_table_new(g_str_hash, g_str_equal);
|
||||
c = call_get_opmode(&callid, m, opmode);
|
||||
if (!c) {
|
||||
ilog(LOG_WARNING, "["STR_FORMAT"] Got LOOKUP for unknown call-id", STR_FMT(&callid));
|
||||
goto out;
|
||||
}
|
||||
|
||||
info_parse(out[RE_TCP_RL_INFO], infohash, m);
|
||||
streams_parse(out[RE_TCP_RL_STREAMS], m, &s);
|
||||
str_init(&fromtag, g_hash_table_lookup(infohash, "fromtag"));
|
||||
if (!fromtag.s) {
|
||||
ilog(LOG_WARNING, "No from-tag in message");
|
||||
goto out2;
|
||||
}
|
||||
if (opmode == OP_ANSWER) {
|
||||
str_init(&totag, g_hash_table_lookup(infohash, "totag"));
|
||||
if (!totag.s) {
|
||||
ilog(LOG_WARNING, "No to-tag in message");
|
||||
goto out2;
|
||||
}
|
||||
}
|
||||
|
||||
monologue = call_get_mono_dialogue(c, &fromtag, &totag);
|
||||
/* XXX return value */
|
||||
monologue_offer_answer(monologue, &s, NULL);
|
||||
|
||||
ret = streams_print(&monologue->medias, 1, s.length, NULL, SAF_TCP);
|
||||
rwlock_unlock_w(&c->master_lock);
|
||||
|
||||
out2:
|
||||
streams_free(&s);
|
||||
|
||||
redis_update(c, m->conf.redis);
|
||||
|
||||
ilog(LOG_INFO, "Returning to SIP proxy: "STR_FORMAT"", STR_FMT0(ret));
|
||||
obj_put(c);
|
||||
|
||||
out:
|
||||
g_hash_table_destroy(infohash);
|
||||
return ret;
|
||||
}
|
||||
|
||||
str *call_request_tcp(char **out, struct callmaster *m) {
|
||||
return call_request_lookup_tcp(out, m, OP_OFFER);
|
||||
}
|
||||
str *call_lookup_tcp(char **out, struct callmaster *m) {
|
||||
return call_request_lookup_tcp(out, m, OP_ANSWER);
|
||||
}
|
||||
|
||||
str *call_delete_udp(char **out, struct callmaster *m) {
|
||||
str callid, branch, fromtag, totag;
|
||||
|
||||
__C_DBG("got delete for callid '%s' and viabranch '%s'",
|
||||
out[RE_UDP_DQ_CALLID], out[RE_UDP_DQ_VIABRANCH]);
|
||||
|
||||
str_init(&callid, out[RE_UDP_DQ_CALLID]);
|
||||
str_init(&branch, out[RE_UDP_DQ_VIABRANCH]);
|
||||
str_init(&fromtag, out[RE_UDP_DQ_FROMTAG]);
|
||||
str_init(&totag, out[RE_UDP_DQ_TOTAG]);
|
||||
|
||||
if (call_delete_branch(m, &callid, &branch, &fromtag, &totag, NULL))
|
||||
return str_sprintf("%s E8\n", out[RE_UDP_COOKIE]);
|
||||
|
||||
return str_sprintf("%s 0\n", out[RE_UDP_COOKIE]);
|
||||
}
|
||||
str *call_query_udp(char **out, struct callmaster *m) {
|
||||
struct call *c;
|
||||
str *ret, callid, fromtag, totag;
|
||||
struct call_stats stats;
|
||||
|
||||
__C_DBG("got query for callid '%s'", out[RE_UDP_DQ_CALLID]);
|
||||
|
||||
str_init(&callid, out[RE_UDP_DQ_CALLID]);
|
||||
str_init(&fromtag, out[RE_UDP_DQ_FROMTAG]);
|
||||
str_init(&totag, out[RE_UDP_DQ_TOTAG]);
|
||||
|
||||
c = call_get_opmode(&callid, m, OP_OTHER);
|
||||
if (!c) {
|
||||
ilog(LOG_INFO, "["STR_FORMAT"] Call-ID to query not found", STR_FMT(&callid));
|
||||
goto err;
|
||||
}
|
||||
|
||||
stats_query(c, &fromtag, &totag, &stats, NULL, NULL);
|
||||
|
||||
rwlock_unlock_w(&c->master_lock);
|
||||
|
||||
ret = str_sprintf("%s %lld "UINT64F" "UINT64F" "UINT64F" "UINT64F"\n", out[RE_UDP_COOKIE],
|
||||
(long long int) m->conf.silent_timeout - (poller_now - stats.newest),
|
||||
stats.totals[0].packets, stats.totals[1].packets,
|
||||
stats.totals[2].packets, stats.totals[3].packets);
|
||||
goto out;
|
||||
|
||||
err:
|
||||
if (c)
|
||||
rwlock_unlock_w(&c->master_lock);
|
||||
ret = str_sprintf("%s E8\n", out[RE_UDP_COOKIE]);
|
||||
goto out;
|
||||
|
||||
out:
|
||||
if (c)
|
||||
obj_put(c);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void call_delete_tcp(char **out, struct callmaster *m) {
|
||||
str callid;
|
||||
|
||||
str_init(&callid, out[RE_TCP_D_CALLID]);
|
||||
call_delete_branch(m, &callid, NULL, NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
static void call_status_iterator(struct call *c, struct control_stream *s) {
|
||||
// GList *l;
|
||||
// struct callstream *cs;
|
||||
// struct peer *p;
|
||||
// struct streamrelay *r1, *r2;
|
||||
// struct streamrelay *rx1, *rx2;
|
||||
// struct callmaster *m;
|
||||
// char addr1[64], addr2[64], addr3[64];
|
||||
|
||||
// m = c->callmaster;
|
||||
// mutex_lock(&c->master_lock);
|
||||
|
||||
control_stream_printf(s, "session "STR_FORMAT" - - - - %i\n",
|
||||
STR_FMT(&c->callid),
|
||||
(int) (poller_now - c->created));
|
||||
|
||||
/* XXX restore function */
|
||||
|
||||
// mutex_unlock(&c->master_lock);
|
||||
}
|
||||
|
||||
void calls_status_tcp(struct callmaster *m, struct control_stream *s) {
|
||||
struct stats st;
|
||||
GQueue q = G_QUEUE_INIT;
|
||||
struct call *c;
|
||||
|
||||
mutex_lock(&m->statslock);
|
||||
st = m->stats;
|
||||
mutex_unlock(&m->statslock);
|
||||
|
||||
callmaster_get_all_calls(m, &q);
|
||||
|
||||
control_stream_printf(s, "proxy %u "UINT64F"/"UINT64F"/"UINT64F"\n",
|
||||
g_queue_get_length(&q),
|
||||
st.bytes, st.bytes - st.errors,
|
||||
st.bytes * 2 - st.errors);
|
||||
|
||||
while (q.head) {
|
||||
c = g_queue_pop_head(&q);
|
||||
call_status_iterator(c, s);
|
||||
obj_put(c);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
static void call_ng_process_flags(struct sdp_ng_flags *out, bencode_item_t *input) {
|
||||
bencode_item_t *list, *it;
|
||||
int diridx;
|
||||
str s;
|
||||
|
||||
ZERO(*out);
|
||||
|
||||
if ((list = bencode_dictionary_get_expect(input, "flags", BENCODE_LIST))) {
|
||||
for (it = list->child; it; it = it->sibling) {
|
||||
if (!bencode_strcmp(it, "trust address"))
|
||||
out->trust_address = 1;
|
||||
else if (!bencode_strcmp(it, "symmetric"))
|
||||
out->symmetric = 1;
|
||||
else if (!bencode_strcmp(it, "asymmetric"))
|
||||
out->asymmetric = 1;
|
||||
else if (!bencode_strcmp(it, "trust-address"))
|
||||
out->trust_address = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if ((list = bencode_dictionary_get_expect(input, "replace", BENCODE_LIST))) {
|
||||
for (it = list->child; it; it = it->sibling) {
|
||||
if (!bencode_strcmp(it, "origin"))
|
||||
out->replace_origin = 1;
|
||||
else if (!bencode_strcmp(it, "session connection"))
|
||||
out->replace_sess_conn = 1;
|
||||
else if (!bencode_strcmp(it, "session-connection"))
|
||||
out->replace_sess_conn = 1;
|
||||
}
|
||||
}
|
||||
|
||||
diridx = 0;
|
||||
if ((list = bencode_dictionary_get_expect(input, "direction", BENCODE_LIST))) {
|
||||
for (it = list->child; it && diridx < 2; it = it->sibling) {
|
||||
if (!bencode_strcmp(it, "internal"))
|
||||
out->directions[diridx++] = DIR_INTERNAL;
|
||||
else if (!bencode_strcmp(it, "external"))
|
||||
out->directions[diridx++] = DIR_EXTERNAL;
|
||||
}
|
||||
}
|
||||
|
||||
list = bencode_dictionary_get_expect(input, "received from", BENCODE_LIST);
|
||||
if (!list)
|
||||
list = bencode_dictionary_get_expect(input, "received-from", BENCODE_LIST);
|
||||
if (list && (it = list->child)) {
|
||||
bencode_get_str(it, &out->received_from_family);
|
||||
bencode_get_str(it->sibling, &out->received_from_address);
|
||||
}
|
||||
|
||||
if (bencode_dictionary_get_str(input, "ICE", &s)) {
|
||||
if (!str_cmp(&s, "remove"))
|
||||
out->ice_remove = 1;
|
||||
else if (!str_cmp(&s, "force"))
|
||||
out->ice_force = 1;
|
||||
}
|
||||
|
||||
if ((list = bencode_dictionary_get_expect(input, "rtcp-mux", BENCODE_LIST))) {
|
||||
for (it = list->child; it; it = it->sibling) {
|
||||
if (!bencode_strcmp(it, "offer"))
|
||||
out->rtcp_mux_offer = 1;
|
||||
else if (!bencode_strcmp(it, "demux"))
|
||||
out->rtcp_mux_demux = 1;
|
||||
else if (!bencode_strcmp(it, "accept"))
|
||||
out->rtcp_mux_accept = 1;
|
||||
else if (!bencode_strcmp(it, "reject"))
|
||||
out->rtcp_mux_reject = 1;
|
||||
}
|
||||
}
|
||||
|
||||
bencode_dictionary_get_str(input, "transport protocol", &out->transport_protocol_str);
|
||||
if (!out->transport_protocol_str.s)
|
||||
bencode_dictionary_get_str(input, "transport-protocol", &out->transport_protocol_str);
|
||||
out->transport_protocol = transport_protocol(&out->transport_protocol_str);
|
||||
bencode_dictionary_get_str(input, "media address", &out->media_address);
|
||||
if (bencode_dictionary_get_str(input, "address family", &out->address_family_str))
|
||||
out->address_family = address_family(&out->address_family_str);
|
||||
}
|
||||
|
||||
static const char *call_offer_answer_ng(bencode_item_t *input, struct callmaster *m,
|
||||
bencode_item_t *output, enum call_opmode opmode)
|
||||
{
|
||||
str sdp, fromtag, totag = STR_NULL, callid;
|
||||
char *errstr;
|
||||
GQueue parsed = G_QUEUE_INIT;
|
||||
GQueue streams = G_QUEUE_INIT;
|
||||
struct call *call;
|
||||
struct call_monologue *monologue;
|
||||
int ret;
|
||||
struct sdp_ng_flags flags;
|
||||
struct sdp_chopper *chopper;
|
||||
|
||||
if (!bencode_dictionary_get_str(input, "sdp", &sdp))
|
||||
return "No SDP body in message";
|
||||
if (!bencode_dictionary_get_str(input, "call-id", &callid))
|
||||
return "No call-id in message";
|
||||
if (!bencode_dictionary_get_str(input, "from-tag", &fromtag))
|
||||
return "No from-tag in message";
|
||||
if (opmode == OP_ANSWER) {
|
||||
if (!bencode_dictionary_get_str(input, "to-tag", &totag))
|
||||
return "No to-tag in message";
|
||||
}
|
||||
//bencode_dictionary_get_str(input, "via-branch", &viabranch);
|
||||
|
||||
if (sdp_parse(&sdp, &parsed))
|
||||
return "Failed to parse SDP";
|
||||
|
||||
call_ng_process_flags(&flags, input);
|
||||
flags.opmode = opmode;
|
||||
|
||||
errstr = "Incomplete SDP specification";
|
||||
if (sdp_streams(&parsed, &streams, &flags))
|
||||
goto out;
|
||||
|
||||
call = call_get_opmode(&callid, m, opmode);
|
||||
errstr = "Unknown call-id";
|
||||
if (!call)
|
||||
goto out;
|
||||
|
||||
monologue = call_get_mono_dialogue(call, &fromtag, &totag);
|
||||
|
||||
chopper = sdp_chopper_new(&sdp);
|
||||
bencode_buffer_destroy_add(output->buffer, (free_func_t) sdp_chopper_destroy, chopper);
|
||||
/* XXX return value */
|
||||
monologue_offer_answer(monologue, &streams, &flags);
|
||||
ret = sdp_replace(chopper, &parsed, monologue, &flags);
|
||||
|
||||
rwlock_unlock_w(&call->master_lock);
|
||||
redis_update(call, m->conf.redis);
|
||||
obj_put(call);
|
||||
|
||||
errstr = "Error rewriting SDP";
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
bencode_dictionary_add_iovec(output, "sdp", &g_array_index(chopper->iov, struct iovec, 0),
|
||||
chopper->iov_num, chopper->str_len);
|
||||
bencode_dictionary_add_string(output, "result", "ok");
|
||||
|
||||
errstr = NULL;
|
||||
out:
|
||||
sdp_free(&parsed);
|
||||
streams_free(&streams);
|
||||
|
||||
return errstr;
|
||||
}
|
||||
|
||||
const char *call_offer_ng(bencode_item_t *input, struct callmaster *m, bencode_item_t *output) {
|
||||
return call_offer_answer_ng(input, m, output, OP_OFFER);
|
||||
}
|
||||
|
||||
const char *call_answer_ng(bencode_item_t *input, struct callmaster *m, bencode_item_t *output) {
|
||||
return call_offer_answer_ng(input, m, output, OP_ANSWER);
|
||||
}
|
||||
|
||||
const char *call_delete_ng(bencode_item_t *input, struct callmaster *m, bencode_item_t *output) {
|
||||
str fromtag, totag, viabranch, callid;
|
||||
bencode_item_t *flags, *it;
|
||||
int fatal = 0;
|
||||
|
||||
if (!bencode_dictionary_get_str(input, "call-id", &callid))
|
||||
return "No call-id in message";
|
||||
if (!bencode_dictionary_get_str(input, "from-tag", &fromtag))
|
||||
return "No from-tag in message";
|
||||
bencode_dictionary_get_str(input, "to-tag", &totag);
|
||||
bencode_dictionary_get_str(input, "via-branch", &viabranch);
|
||||
|
||||
flags = bencode_dictionary_get_expect(input, "flags", BENCODE_LIST);
|
||||
if (flags) {
|
||||
for (it = flags->child; it; it = it->sibling) {
|
||||
if (!bencode_strcmp(it, "fatal"))
|
||||
fatal = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (call_delete_branch(m, &callid, &viabranch, &fromtag, &totag, output)) {
|
||||
if (fatal)
|
||||
return "Call-ID not found or tags didn't match";
|
||||
bencode_dictionary_add_string(output, "warning", "Call-ID not found or tags didn't match");
|
||||
}
|
||||
|
||||
bencode_dictionary_add_string(output, "result", "ok");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#if 0
|
||||
static bencode_item_t *peer_address(bencode_buffer_t *b, struct stream *s) {
|
||||
bencode_item_t *d;
|
||||
char buf[64];
|
||||
|
||||
d = bencode_dictionary(b);
|
||||
if (IN6_IS_ADDR_V4MAPPED(&s->ip46)) {
|
||||
bencode_dictionary_add_string(d, "family", "IPv4");
|
||||
inet_ntop(AF_INET, &(s->ip46.s6_addr32[3]), buf, sizeof(buf));
|
||||
}
|
||||
else {
|
||||
bencode_dictionary_add_string(d, "family", "IPv6");
|
||||
inet_ntop(AF_INET6, &s->ip46, buf, sizeof(buf));
|
||||
}
|
||||
bencode_dictionary_add_string_dup(d, "address", buf);
|
||||
bencode_dictionary_add_integer(d, "port", s->port);
|
||||
|
||||
return d;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
static bencode_item_t *stats_encode(bencode_buffer_t *b, struct stats *s) {
|
||||
bencode_item_t *d;
|
||||
|
||||
d = bencode_dictionary(b);
|
||||
bencode_dictionary_add_integer(d, "packets", s->packets);
|
||||
bencode_dictionary_add_integer(d, "bytes", s->bytes);
|
||||
bencode_dictionary_add_integer(d, "errors", s->errors);
|
||||
return d;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
static bencode_item_t *streamrelay_stats(bencode_buffer_t *b, struct packet_stream *ps) {
|
||||
bencode_item_t *d;
|
||||
|
||||
d = bencode_dictionary(b);
|
||||
|
||||
// XXX
|
||||
//bencode_dictionary_add(d, "counters", stats_encode(b, &r->stats));
|
||||
//bencode_dictionary_add(d, "peer address", peer_address(b, &r->peer));
|
||||
//bencode_dictionary_add(d, "advertised peer address", peer_address(b, &r->peer_advertised));
|
||||
|
||||
bencode_dictionary_add_integer(d, "local port", ps->fd.localport);
|
||||
|
||||
return d;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
static bencode_item_t *rtp_rtcp_stats(bencode_buffer_t *b, struct stats *rtp, struct stats *rtcp) {
|
||||
bencode_item_t *s;
|
||||
s = bencode_dictionary(b);
|
||||
bencode_dictionary_add(s, "rtp", stats_encode(b, rtp));
|
||||
bencode_dictionary_add(s, "rtcp", stats_encode(b, rtcp));
|
||||
return s;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
XXX
|
||||
static bencode_item_t *peer_stats(bencode_buffer_t *b, struct peer *p) {
|
||||
bencode_item_t *d, *s;
|
||||
|
||||
d = bencode_dictionary(b);
|
||||
|
||||
bencode_dictionary_add_str_dup(d, "tag", &p->tag);
|
||||
if (p->codec)
|
||||
bencode_dictionary_add_string(d, "codec", p->codec);
|
||||
if (p->kernelized)
|
||||
bencode_dictionary_add_string(d, "status", "in kernel");
|
||||
else if (p->confirmed)
|
||||
bencode_dictionary_add_string(d, "status", "confirmed peer address");
|
||||
else if (p->filled)
|
||||
bencode_dictionary_add_string(d, "status", "known but unconfirmed peer address");
|
||||
else
|
||||
bencode_dictionary_add_string(d, "status", "unknown peer address");
|
||||
|
||||
s = bencode_dictionary_add_dictionary(d, "stats");
|
||||
bencode_dictionary_add(s, "rtp", streamrelay_stats(b, &p->rtps[0]));
|
||||
bencode_dictionary_add(s, "rtcp", streamrelay_stats(b, &p->rtps[1]));
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
static void ng_stats_cb(struct peer *p, struct peer *px, void *streams) {
|
||||
bencode_item_t *stream;
|
||||
|
||||
stream = bencode_list_add_list(streams);
|
||||
bencode_list_add(stream, peer_stats(stream->buffer, p));
|
||||
bencode_list_add(stream, peer_stats(stream->buffer, px));
|
||||
}
|
||||
#endif
|
||||
|
||||
/* call must be locked */
|
||||
void ng_call_stats(struct call *call, const str *fromtag, const str *totag, bencode_item_t *output) {
|
||||
//bencode_item_t *streams, *dict;
|
||||
// struct call_stats stats;
|
||||
|
||||
// bencode_dictionary_add_integer(output, "created", call->created);
|
||||
|
||||
//streams = bencode_dictionary_add_list(output, "streams");
|
||||
//stats_query(call, fromtag, totag, &stats, ng_stats_cb, streams); XXX
|
||||
|
||||
// dict = bencode_dictionary_add_dictionary(output, "totals");
|
||||
// bencode_dictionary_add(dict, "input", rtp_rtcp_stats(output->buffer, &stats.totals[0], &stats.totals[1]));
|
||||
// bencode_dictionary_add(dict, "output", rtp_rtcp_stats(output->buffer, &stats.totals[2], &stats.totals[3]));
|
||||
}
|
||||
|
||||
const char *call_query_ng(bencode_item_t *input, struct callmaster *m, bencode_item_t *output) {
|
||||
str callid, fromtag, totag;
|
||||
struct call *call;
|
||||
|
||||
if (!bencode_dictionary_get_str(input, "call-id", &callid))
|
||||
return "No call-id in message";
|
||||
call = call_get_opmode(&callid, m, OP_OTHER);
|
||||
if (!call)
|
||||
return "Unknown call-id";
|
||||
bencode_dictionary_get_str(input, "from-tag", &fromtag);
|
||||
bencode_dictionary_get_str(input, "to-tag", &totag);
|
||||
|
||||
bencode_dictionary_add_string(output, "result", "ok");
|
||||
ng_call_stats(call, &fromtag, &totag, output);
|
||||
rwlock_unlock_w(&call->master_lock);
|
||||
|
||||
return NULL;
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
#ifndef _CALL_INTERFACES_H_
|
||||
#define _CALL_INTERFACES_H_
|
||||
|
||||
|
||||
|
||||
#include "str.h"
|
||||
#include "bencode.h"
|
||||
|
||||
|
||||
|
||||
struct call;
|
||||
|
||||
|
||||
|
||||
void ng_call_stats(struct call *call, const str *fromtag, const str *totag, bencode_item_t *output);
|
||||
|
||||
|
||||
#endif
|
@ -0,0 +1,681 @@
|
||||
#include "dtls.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <glib.h>
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/x509.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <openssl/err.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "str.h"
|
||||
#include "aux.h"
|
||||
#include "crypto.h"
|
||||
#include "log.h"
|
||||
#include "call.h"
|
||||
#include "poller.h"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#define DTLS_DEBUG 0
|
||||
|
||||
#if DTLS_DEBUG
|
||||
#define __DBG(x...) ilog(LOG_DEBUG, x)
|
||||
#else
|
||||
#define __DBG(x...) ((void)0)
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
#define CERT_EXPIRY_TIME (60*60*24*30) /* 30 days */
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
static char ciphers_str[1024];
|
||||
|
||||
|
||||
|
||||
static unsigned int sha_1_func(unsigned char *, X509 *);
|
||||
static unsigned int sha_224_func(unsigned char *, X509 *);
|
||||
static unsigned int sha_256_func(unsigned char *, X509 *);
|
||||
static unsigned int sha_384_func(unsigned char *, X509 *);
|
||||
static unsigned int sha_512_func(unsigned char *, X509 *);
|
||||
|
||||
|
||||
|
||||
|
||||
static const struct dtls_hash_func hash_funcs[] = {
|
||||
{
|
||||
.name = "sha-1",
|
||||
.num_bytes = 160 / 8,
|
||||
.__func = sha_1_func,
|
||||
},
|
||||
{
|
||||
.name = "sha-224",
|
||||
.num_bytes = 224 / 8,
|
||||
.__func = sha_224_func,
|
||||
},
|
||||
{
|
||||
.name = "sha-256",
|
||||
.num_bytes = 256 / 8,
|
||||
.__func = sha_256_func,
|
||||
},
|
||||
{
|
||||
.name = "sha-384",
|
||||
.num_bytes = 384 / 8,
|
||||
.__func = sha_384_func,
|
||||
},
|
||||
{
|
||||
.name = "sha-512",
|
||||
.num_bytes = 512 / 8,
|
||||
.__func = sha_512_func,
|
||||
},
|
||||
};
|
||||
|
||||
const int num_hash_funcs = G_N_ELEMENTS(hash_funcs);
|
||||
|
||||
|
||||
|
||||
static struct dtls_cert *__dtls_cert;
|
||||
static rwlock_t __dtls_cert_lock;
|
||||
|
||||
|
||||
|
||||
const struct dtls_hash_func *dtls_find_hash_func(const str *s) {
|
||||
int i;
|
||||
const struct dtls_hash_func *hf;
|
||||
|
||||
for (i = 0; i < num_hash_funcs; i++) {
|
||||
hf = &hash_funcs[i];
|
||||
if (strlen(hf->name) != s->len)
|
||||
continue;
|
||||
if (!strncasecmp(s->s, hf->name, s->len))
|
||||
return hf;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void cert_free(void *p) {
|
||||
struct dtls_cert *cert = p;
|
||||
|
||||
if (cert->pkey)
|
||||
EVP_PKEY_free(cert->pkey);
|
||||
if (cert->x509)
|
||||
X509_free(cert->x509);
|
||||
}
|
||||
|
||||
static int cert_init() {
|
||||
X509 *x509 = NULL;
|
||||
EVP_PKEY *pkey = NULL;
|
||||
BIGNUM *exponent = NULL, *serial_number = NULL;
|
||||
RSA *rsa = NULL;
|
||||
ASN1_INTEGER *asn1_serial_number;
|
||||
X509_NAME *name;
|
||||
struct dtls_cert *new_cert;
|
||||
|
||||
ilog(LOG_INFO, "Generating new DTLS certificate");
|
||||
|
||||
/* objects */
|
||||
|
||||
pkey = EVP_PKEY_new();
|
||||
exponent = BN_new();
|
||||
rsa = RSA_new();
|
||||
serial_number = BN_new();
|
||||
name = X509_NAME_new();
|
||||
x509 = X509_new();
|
||||
if (!exponent || !pkey || !rsa || !serial_number || !name || !x509)
|
||||
goto err;
|
||||
|
||||
/* key */
|
||||
|
||||
if (!BN_set_word(exponent, 0x10001))
|
||||
goto err;
|
||||
|
||||
if (!RSA_generate_key_ex(rsa, 1024, exponent, NULL))
|
||||
goto err;
|
||||
|
||||
if (!EVP_PKEY_assign_RSA(pkey, rsa))
|
||||
goto err;
|
||||
|
||||
/* x509 cert */
|
||||
|
||||
if (!X509_set_pubkey(x509, pkey))
|
||||
goto err;
|
||||
|
||||
/* serial */
|
||||
|
||||
if (!BN_pseudo_rand(serial_number, 64, 0, 0))
|
||||
goto err;
|
||||
|
||||
asn1_serial_number = X509_get_serialNumber(x509);
|
||||
if (!asn1_serial_number)
|
||||
goto err;
|
||||
|
||||
if (!BN_to_ASN1_INTEGER(serial_number, asn1_serial_number))
|
||||
goto err;
|
||||
|
||||
/* version 1 */
|
||||
|
||||
if (!X509_set_version(x509, 0L))
|
||||
goto err;
|
||||
|
||||
/* common name */
|
||||
|
||||
if (!X509_NAME_add_entry_by_NID(name, NID_commonName, MBSTRING_UTF8,
|
||||
(unsigned char *) "mediaproxy-ng", -1, -1, 0))
|
||||
goto err;
|
||||
|
||||
if (!X509_set_subject_name(x509, name))
|
||||
goto err;
|
||||
|
||||
if (!X509_set_issuer_name(x509, name))
|
||||
goto err;
|
||||
|
||||
/* cert lifetime */
|
||||
|
||||
if (!X509_gmtime_adj(X509_get_notBefore(x509), -60*60*24))
|
||||
goto err;
|
||||
|
||||
if (!X509_gmtime_adj(X509_get_notAfter(x509), CERT_EXPIRY_TIME))
|
||||
goto err;
|
||||
|
||||
/* sign it */
|
||||
|
||||
if (!X509_sign(x509, pkey, EVP_sha1()))
|
||||
goto err;
|
||||
|
||||
/* digest */
|
||||
|
||||
new_cert = obj_alloc0("dtls_cert", sizeof(*new_cert), cert_free);
|
||||
new_cert->fingerprint.hash_func = &hash_funcs[0];
|
||||
dtls_fingerprint_hash(&new_cert->fingerprint, x509);
|
||||
|
||||
new_cert->x509 = x509;
|
||||
new_cert->pkey = pkey;
|
||||
new_cert->expires = time(NULL) + CERT_EXPIRY_TIME;
|
||||
|
||||
/* swap out certs */
|
||||
|
||||
rwlock_lock_w(&__dtls_cert_lock);
|
||||
|
||||
if (__dtls_cert)
|
||||
obj_put(__dtls_cert);
|
||||
__dtls_cert = new_cert;
|
||||
|
||||
rwlock_unlock_w(&__dtls_cert_lock);
|
||||
|
||||
/* cleanup */
|
||||
|
||||
BN_free(exponent);
|
||||
BN_free(serial_number);
|
||||
X509_NAME_free(name);
|
||||
|
||||
return 0;
|
||||
|
||||
err:
|
||||
ilog(LOG_ERROR, "Failed to generate DTLS certificate");
|
||||
|
||||
if (pkey)
|
||||
EVP_PKEY_free(pkey);
|
||||
if (exponent)
|
||||
BN_free(exponent);
|
||||
if (rsa)
|
||||
RSA_free(rsa);
|
||||
if (x509)
|
||||
X509_free(x509);
|
||||
if (serial_number)
|
||||
BN_free(serial_number);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int dtls_init() {
|
||||
int i;
|
||||
char *p;
|
||||
|
||||
rwlock_init(&__dtls_cert_lock);
|
||||
if (cert_init())
|
||||
return -1;
|
||||
|
||||
p = ciphers_str;
|
||||
for (i = 0; i < num_crypto_suites; i++) {
|
||||
if (!crypto_suites[i].dtls_name)
|
||||
continue;
|
||||
|
||||
p += sprintf(p, "%s:", crypto_suites[i].dtls_name);
|
||||
}
|
||||
|
||||
assert(p != ciphers_str);
|
||||
assert(p - ciphers_str < sizeof(ciphers_str));
|
||||
|
||||
p[-1] = '\0';
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __dtls_timer(void *p) {
|
||||
struct dtls_cert *c;
|
||||
long int left;
|
||||
|
||||
c = dtls_cert();
|
||||
left = c->expires - poller_now;
|
||||
if (left > CERT_EXPIRY_TIME/2)
|
||||
goto out;
|
||||
|
||||
cert_init();
|
||||
|
||||
out:
|
||||
obj_put(c);
|
||||
}
|
||||
|
||||
void dtls_timer(struct poller *p) {
|
||||
poller_add_timer(p, __dtls_timer, NULL);
|
||||
}
|
||||
|
||||
static unsigned int generic_func(unsigned char *o, X509 *x, const EVP_MD *md) {
|
||||
unsigned int n;
|
||||
assert(md != NULL);
|
||||
X509_digest(x, md, o, &n);
|
||||
return n;
|
||||
}
|
||||
|
||||
static unsigned int sha_1_func(unsigned char *o, X509 *x) {
|
||||
const EVP_MD *md;
|
||||
md = EVP_sha1();
|
||||
return generic_func(o, x, md);
|
||||
}
|
||||
static unsigned int sha_224_func(unsigned char *o, X509 *x) {
|
||||
const EVP_MD *md;
|
||||
md = EVP_sha224();
|
||||
return generic_func(o, x, md);
|
||||
}
|
||||
static unsigned int sha_256_func(unsigned char *o, X509 *x) {
|
||||
const EVP_MD *md;
|
||||
md = EVP_sha256();
|
||||
return generic_func(o, x, md);
|
||||
}
|
||||
static unsigned int sha_384_func(unsigned char *o, X509 *x) {
|
||||
const EVP_MD *md;
|
||||
md = EVP_sha384();
|
||||
return generic_func(o, x, md);
|
||||
}
|
||||
static unsigned int sha_512_func(unsigned char *o, X509 *x) {
|
||||
const EVP_MD *md;
|
||||
md = EVP_sha512();
|
||||
return generic_func(o, x, md);
|
||||
}
|
||||
|
||||
|
||||
struct dtls_cert *dtls_cert() {
|
||||
struct dtls_cert *ret;
|
||||
|
||||
rwlock_lock_r(&__dtls_cert_lock);
|
||||
ret = obj_get(__dtls_cert);
|
||||
rwlock_unlock_r(&__dtls_cert_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int verify_callback(int ok, X509_STORE_CTX *store) {
|
||||
SSL *ssl;
|
||||
struct stream_fd *sfd;
|
||||
struct packet_stream *ps;
|
||||
struct call_media *media;
|
||||
|
||||
ssl = X509_STORE_CTX_get_ex_data(store, SSL_get_ex_data_X509_STORE_CTX_idx());
|
||||
sfd = SSL_get_app_data(ssl);
|
||||
if (sfd->dtls.ssl != ssl)
|
||||
return 0;
|
||||
ps = sfd->stream;
|
||||
if (!ps)
|
||||
return 0;
|
||||
if (ps->fingerprint_verified)
|
||||
return 1;
|
||||
media = ps->media;
|
||||
if (!media)
|
||||
return 0;
|
||||
|
||||
ps->dtls_cert = X509_STORE_CTX_get_current_cert(store);
|
||||
|
||||
if (!media->fingerprint.hash_func)
|
||||
return 1; /* delay verification */
|
||||
|
||||
if (dtls_verify_cert(ps))
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int dtls_verify_cert(struct packet_stream *ps) {
|
||||
unsigned char fp[DTLS_MAX_DIGEST_LEN];
|
||||
struct call_media *media;
|
||||
|
||||
media = ps->media;
|
||||
if (!media)
|
||||
return -1;
|
||||
if (!ps->dtls_cert)
|
||||
return -1;
|
||||
|
||||
dtls_hash(media->fingerprint.hash_func, ps->dtls_cert, fp);
|
||||
|
||||
if (memcmp(media->fingerprint.digest, fp, media->fingerprint.hash_func->num_bytes)) {
|
||||
ilog(LOG_WARNING, "DTLS: Peer certificate rejected - fingerprint mismatch");
|
||||
__DBG("fingerprint expected: %02x%02x%02x%02x%02x%02x%02x%02x received: %02x%02x%02x%02x%02x%02x%02x%02x",
|
||||
media->fingerprint.digest[0], media->fingerprint.digest[1],
|
||||
media->fingerprint.digest[2], media->fingerprint.digest[3],
|
||||
media->fingerprint.digest[4], media->fingerprint.digest[5],
|
||||
media->fingerprint.digest[6], media->fingerprint.digest[7],
|
||||
fp[0], fp[1], fp[2], fp[3],
|
||||
fp[4], fp[5], fp[6], fp[7]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ps->fingerprint_verified = 1;
|
||||
ilog(LOG_INFO, "DTLS: Peer certificate accepted");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int try_connect(struct dtls_connection *d) {
|
||||
int ret, code;
|
||||
|
||||
if (d->connected)
|
||||
return 0;
|
||||
|
||||
__DBG("try_connect(%i)", d->active);
|
||||
|
||||
if (d->active)
|
||||
ret = SSL_connect(d->ssl);
|
||||
else
|
||||
ret = SSL_accept(d->ssl);
|
||||
|
||||
code = SSL_get_error(d->ssl, ret);
|
||||
|
||||
ret = 0;
|
||||
switch (code) {
|
||||
case SSL_ERROR_NONE:
|
||||
ilog(LOG_DEBUG, "DTLS handshake successful");
|
||||
d->connected = 1;
|
||||
ret = 1;
|
||||
break;
|
||||
|
||||
case SSL_ERROR_WANT_READ:
|
||||
case SSL_ERROR_WANT_WRITE:
|
||||
break;
|
||||
|
||||
default:
|
||||
ret = ERR_peek_last_error();
|
||||
ilog(LOG_ERROR, "DTLS error: %i (%s)", code, ERR_reason_error_string(ret));
|
||||
ret = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int dtls_connection_init(struct packet_stream *ps, int active, struct dtls_cert *cert) {
|
||||
struct dtls_connection *d = &ps->sfd->dtls;
|
||||
unsigned long err;
|
||||
|
||||
__DBG("dtls_connection_init(%i)", active);
|
||||
|
||||
if (d->init) {
|
||||
if (d->active == active)
|
||||
goto connect;
|
||||
dtls_connection_cleanup(d);
|
||||
}
|
||||
|
||||
d->ssl_ctx = SSL_CTX_new(active ? DTLSv1_client_method() : DTLSv1_server_method());
|
||||
if (!d->ssl_ctx)
|
||||
goto error;
|
||||
|
||||
if (SSL_CTX_use_certificate(d->ssl_ctx, cert->x509) != 1)
|
||||
goto error;
|
||||
if (SSL_CTX_use_PrivateKey(d->ssl_ctx, cert->pkey) != 1)
|
||||
goto error;
|
||||
|
||||
SSL_CTX_set_verify(d->ssl_ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
|
||||
verify_callback);
|
||||
SSL_CTX_set_verify_depth(d->ssl_ctx, 4);
|
||||
SSL_CTX_set_cipher_list(d->ssl_ctx, "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH");
|
||||
|
||||
if (SSL_CTX_set_tlsext_use_srtp(d->ssl_ctx, ciphers_str))
|
||||
goto error;
|
||||
|
||||
d->ssl = SSL_new(d->ssl_ctx);
|
||||
if (!d->ssl)
|
||||
goto error;
|
||||
|
||||
d->r_bio = BIO_new(BIO_s_mem());
|
||||
d->w_bio = BIO_new(BIO_s_mem());
|
||||
if (!d->r_bio || !d->w_bio)
|
||||
goto error;
|
||||
|
||||
SSL_set_app_data(d->ssl, ps->sfd); /* XXX obj reference here? */
|
||||
SSL_set_bio(d->ssl, d->r_bio, d->w_bio);
|
||||
SSL_set_mode(d->ssl, SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
|
||||
|
||||
d->init = 1;
|
||||
d->active = active;
|
||||
|
||||
connect:
|
||||
dtls(ps, NULL, NULL);
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
err = ERR_peek_last_error();
|
||||
if (d->r_bio)
|
||||
BIO_free(d->r_bio);
|
||||
if (d->w_bio)
|
||||
BIO_free(d->w_bio);
|
||||
if (d->ssl)
|
||||
SSL_free(d->ssl);
|
||||
if (d->ssl_ctx)
|
||||
SSL_CTX_free(d->ssl_ctx);
|
||||
ZERO(*d);
|
||||
ilog(LOG_ERROR, "Failed to init DTLS connection: %s", ERR_reason_error_string(err));
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int dtls_setup_crypto(struct packet_stream *ps, struct dtls_connection *d) {
|
||||
const char *err;
|
||||
SRTP_PROTECTION_PROFILE *spp;
|
||||
int i;
|
||||
const struct crypto_suite *cs;
|
||||
unsigned char keys[2 * (SRTP_MAX_MASTER_KEY_LEN + SRTP_MAX_MASTER_SALT_LEN)];
|
||||
struct crypto_params client, server;
|
||||
|
||||
err = "no SRTP protection profile negotiated";
|
||||
spp = SSL_get_selected_srtp_profile(d->ssl);
|
||||
if (!spp)
|
||||
goto error;
|
||||
|
||||
for (i = 0; i < num_crypto_suites; i++) {
|
||||
cs = &crypto_suites[i];
|
||||
if (!cs->dtls_name)
|
||||
continue;
|
||||
if (!strcmp(cs->dtls_name, spp->name))
|
||||
goto found;
|
||||
}
|
||||
|
||||
err = "unknown SRTP protection profile negotiated";
|
||||
goto error;
|
||||
|
||||
found:
|
||||
i = SSL_export_keying_material(d->ssl, keys, sizeof(keys), "EXTRACTOR-dtls_srtp",
|
||||
strlen("EXTRACTOR-dtls_srtp"), NULL, 0, 0);
|
||||
err = "failed to export keying material";
|
||||
if (i != 1)
|
||||
goto error;
|
||||
|
||||
/* got everything XXX except MKI */
|
||||
ZERO(client);
|
||||
ZERO(server);
|
||||
i = 0;
|
||||
|
||||
client.crypto_suite = server.crypto_suite = cs;
|
||||
|
||||
memcpy(client.master_key, &keys[i], cs->master_key_len);
|
||||
i += cs->master_key_len;
|
||||
memcpy(server.master_key, &keys[i], cs->master_key_len);
|
||||
i += cs->master_key_len;
|
||||
memcpy(client.master_salt, &keys[i], cs->master_salt_len);
|
||||
i += cs->master_salt_len;
|
||||
memcpy(server.master_salt, &keys[i], cs->master_salt_len);
|
||||
|
||||
__DBG("SRTP keys negotiated: "
|
||||
"c-m: %02x%02x%02x%02x%02x%02x%02x%02x "
|
||||
"c-s: %02x%02x%02x%02x%02x%02x%02x%02x "
|
||||
"s-m: %02x%02x%02x%02x%02x%02x%02x%02x "
|
||||
"s-s: %02x%02x%02x%02x%02x%02x%02x%02x",
|
||||
client.master_key[0], client.master_key[1], client.master_key[2], client.master_key[3],
|
||||
client.master_key[4], client.master_key[5], client.master_key[6], client.master_key[7],
|
||||
client.master_salt[0], client.master_salt[1], client.master_salt[2], client.master_salt[3],
|
||||
client.master_salt[4], client.master_salt[5], client.master_salt[6], client.master_salt[7],
|
||||
server.master_key[0], server.master_key[1], server.master_key[2], server.master_key[3],
|
||||
server.master_key[4], server.master_key[5], server.master_key[6], server.master_key[7],
|
||||
server.master_salt[0], server.master_salt[1], server.master_salt[2], server.master_salt[3],
|
||||
server.master_salt[4], server.master_salt[5], server.master_salt[6], server.master_salt[7]);
|
||||
|
||||
ilog(LOG_INFO, "DTLS-SRTP successfully negotiated");
|
||||
|
||||
if (d->active) {
|
||||
/* we're the client */
|
||||
crypto_init(&ps->crypto, &client);
|
||||
crypto_init(&ps->sfd->crypto, &server);
|
||||
}
|
||||
else {
|
||||
/* we're the server */
|
||||
crypto_init(&ps->crypto, &server);
|
||||
crypto_init(&ps->sfd->crypto, &client);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
if (!spp)
|
||||
ilog(LOG_ERROR, "Failed to set up SRTP after DTLS negotiation: %s", err);
|
||||
else
|
||||
ilog(LOG_ERROR, "Failed to set up SRTP after DTLS negotiation: %s (profile \"%s\")",
|
||||
err, spp->name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int dtls(struct packet_stream *ps, const str *s, struct sockaddr_in6 *fsin) {
|
||||
struct dtls_connection *d = &ps->sfd->dtls;
|
||||
int ret;
|
||||
unsigned char buf[0x10000], ctrl[256];
|
||||
struct msghdr mh;
|
||||
struct iovec iov;
|
||||
struct sockaddr_in6 sin;
|
||||
|
||||
if (s)
|
||||
__DBG("dtls packet input: len %u %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
|
||||
s->len,
|
||||
(unsigned char) s->s[0], (unsigned char) s->s[1], (unsigned char) s->s[2], (unsigned char) s->s[3],
|
||||
(unsigned char) s->s[4], (unsigned char) s->s[5], (unsigned char) s->s[6], (unsigned char) s->s[7],
|
||||
(unsigned char) s->s[8], (unsigned char) s->s[9], (unsigned char) s->s[10], (unsigned char) s->s[11],
|
||||
(unsigned char) s->s[12], (unsigned char) s->s[13], (unsigned char) s->s[14], (unsigned char) s->s[15]);
|
||||
|
||||
if (d->connected)
|
||||
return 0;
|
||||
|
||||
if (!d->init || !d->ssl)
|
||||
return -1;
|
||||
|
||||
if (s) {
|
||||
BIO_write(d->r_bio, s->s, s->len);
|
||||
/* we understand this as preference of DTLS over SDES */
|
||||
ps->media->sdes = 0;
|
||||
}
|
||||
|
||||
ret = try_connect(d);
|
||||
if (ret == -1) {
|
||||
if (ps->sfd)
|
||||
ilog(LOG_ERROR, "DTLS error on local port %hu", ps->sfd->fd.localport);
|
||||
/* fatal error */
|
||||
d->init = 0;
|
||||
/* XXX ?? */
|
||||
return 0;
|
||||
}
|
||||
else if (ret == 1) {
|
||||
/* connected! */
|
||||
if (dtls_setup_crypto(ps, d))
|
||||
/* XXX ?? */ ;
|
||||
if (ps->rtp && ps->rtcp && ps->rtcp_sibling && ps->media->rtcp_mux) {
|
||||
if (dtls_setup_crypto(ps->rtcp_sibling, d))
|
||||
/* XXX ?? */ ;
|
||||
}
|
||||
}
|
||||
|
||||
ret = BIO_ctrl_pending(d->w_bio);
|
||||
if (ret <= 0)
|
||||
return 0;
|
||||
|
||||
if (ret > sizeof(buf)) {
|
||||
ilog(LOG_ERROR, "BIO buffer overflow");
|
||||
BIO_reset(d->w_bio);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = BIO_read(d->w_bio, buf, ret);
|
||||
if (ret <= 0)
|
||||
return 0;
|
||||
|
||||
__DBG("dtls packet output: len %u %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
|
||||
ret,
|
||||
buf[0], buf[1], buf[2], buf[3],
|
||||
buf[4], buf[5], buf[6], buf[7],
|
||||
buf[8], buf[9], buf[10], buf[11],
|
||||
buf[12], buf[13], buf[14], buf[15]);
|
||||
|
||||
if (!fsin) {
|
||||
ZERO(sin);
|
||||
sin.sin6_family = AF_INET6;
|
||||
sin.sin6_addr = ps->endpoint.ip46;
|
||||
sin.sin6_port = htons(ps->endpoint.port);
|
||||
fsin = &sin;
|
||||
}
|
||||
|
||||
ZERO(mh);
|
||||
mh.msg_control = ctrl;
|
||||
mh.msg_controllen = sizeof(ctrl);
|
||||
mh.msg_name = fsin;
|
||||
mh.msg_namelen = sizeof(*fsin);
|
||||
mh.msg_iov = &iov;
|
||||
mh.msg_iovlen = 1;
|
||||
|
||||
ZERO(iov);
|
||||
iov.iov_base = buf;
|
||||
iov.iov_len = ret;
|
||||
|
||||
callmaster_msg_mh_src(ps->call->callmaster, &mh);
|
||||
|
||||
sendmsg(ps->sfd->fd.fd, &mh, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void dtls_connection_cleanup(struct dtls_connection *c) {
|
||||
__DBG("dtls_connection_cleanup");
|
||||
|
||||
if (c->ssl_ctx)
|
||||
SSL_CTX_free(c->ssl_ctx);
|
||||
if (c->ssl)
|
||||
SSL_free(c->ssl);
|
||||
if (!c->init) {
|
||||
if (c->r_bio)
|
||||
BIO_free(c->r_bio);
|
||||
if (c->w_bio)
|
||||
BIO_free(c->w_bio);
|
||||
}
|
||||
ZERO(*c);
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
#ifndef _DTLS_H_
|
||||
#define _DTLS_H_
|
||||
|
||||
|
||||
|
||||
#include <time.h>
|
||||
#include <openssl/x509.h>
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/bio.h>
|
||||
|
||||
#include "str.h"
|
||||
#include "obj.h"
|
||||
|
||||
|
||||
|
||||
|
||||
#define DTLS_MAX_DIGEST_LEN 64
|
||||
|
||||
|
||||
|
||||
|
||||
struct packet_stream;
|
||||
struct sockaddr_in6;
|
||||
struct poller;
|
||||
|
||||
|
||||
|
||||
struct dtls_hash_func {
|
||||
const char *name;
|
||||
unsigned int num_bytes;
|
||||
unsigned int (*__func)(unsigned char *, X509 *);
|
||||
};
|
||||
|
||||
struct dtls_fingerprint {
|
||||
unsigned char digest[DTLS_MAX_DIGEST_LEN];
|
||||
const struct dtls_hash_func *hash_func;
|
||||
};
|
||||
|
||||
struct dtls_cert {
|
||||
struct obj obj;
|
||||
struct dtls_fingerprint fingerprint;
|
||||
EVP_PKEY *pkey;
|
||||
X509 *x509;
|
||||
time_t expires;
|
||||
};
|
||||
|
||||
struct dtls_connection {
|
||||
SSL_CTX *ssl_ctx;
|
||||
SSL *ssl;
|
||||
BIO *r_bio, *w_bio;
|
||||
int init:1,
|
||||
active:1,
|
||||
connected:1;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
int dtls_init(void);
|
||||
void dtls_timer(struct poller *);
|
||||
|
||||
int dtls_verify_cert(struct packet_stream *ps);
|
||||
const struct dtls_hash_func *dtls_find_hash_func(const str *);
|
||||
struct dtls_cert *dtls_cert(void);
|
||||
|
||||
int dtls_connection_init(struct packet_stream *, int active, struct dtls_cert *cert);
|
||||
int dtls(struct packet_stream *, const str *s, struct sockaddr_in6 *sin);
|
||||
void dtls_connection_cleanup(struct dtls_connection *);
|
||||
|
||||
|
||||
|
||||
|
||||
static inline void __dtls_hash(const struct dtls_hash_func *hash_func, X509 *cert, unsigned char *out,
|
||||
unsigned int bufsize)
|
||||
{
|
||||
unsigned int n;
|
||||
|
||||
assert(bufsize >= hash_func->num_bytes);
|
||||
n = hash_func->__func(out, cert);
|
||||
assert(n == hash_func->num_bytes);
|
||||
}
|
||||
#define dtls_hash(hash_func, cert, outbuf) __dtls_hash(hash_func, cert, outbuf, sizeof(outbuf))
|
||||
|
||||
static inline void dtls_fingerprint_hash(struct dtls_fingerprint *fp, X509 *cert) {
|
||||
__dtls_hash(fp->hash_func, cert, fp->digest, sizeof(fp->digest));
|
||||
}
|
||||
|
||||
static inline int is_dtls(const str *s) {
|
||||
const unsigned char *b = (const void *) s->s;
|
||||
|
||||
if (s->len < 1)
|
||||
return 0;
|
||||
/* RFC 5764, 5.1.2 */
|
||||
if (b[0] >= 20 && b[0] <= 63)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
#endif
|
@ -0,0 +1,55 @@
|
||||
#include "log.h"
|
||||
#include <syslog.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <glib.h>
|
||||
#include "str.h"
|
||||
#include "call.h"
|
||||
|
||||
|
||||
|
||||
struct log_info __thread log_info;
|
||||
volatile gint log_level = LOG_INFO;
|
||||
|
||||
|
||||
|
||||
void ilog(int prio, const char *fmt, ...) {
|
||||
char prefix[256];
|
||||
char *msg;
|
||||
va_list ap;
|
||||
int ret;
|
||||
|
||||
if (prio > g_atomic_int_get(&log_level))
|
||||
return;
|
||||
|
||||
switch (log_info.e) {
|
||||
case LOG_INFO_NONE:
|
||||
prefix[0] = 0;
|
||||
break;
|
||||
case LOG_INFO_CALL:
|
||||
snprintf(prefix, sizeof(prefix), "["STR_FORMAT"] ",
|
||||
STR_FMT(&log_info.u.call->callid));
|
||||
break;
|
||||
case LOG_INFO_STREAM_FD:
|
||||
if (log_info.u.stream_fd->call)
|
||||
snprintf(prefix, sizeof(prefix), "["STR_FORMAT" port %5hu] ",
|
||||
STR_FMT(&log_info.u.stream_fd->call->callid),
|
||||
log_info.u.stream_fd->fd.localport);
|
||||
break;
|
||||
}
|
||||
|
||||
va_start(ap, fmt);
|
||||
ret = vasprintf(&msg, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
if (ret < 0) {
|
||||
syslog(LOG_ERROR, "Failed to print syslog message - message dropped");
|
||||
return;
|
||||
}
|
||||
|
||||
syslog(prio, "%s%s", prefix, msg);
|
||||
|
||||
free(msg);
|
||||
}
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in new issue